LArSoft  v07_13_02
Liquid Argon Software toolkit - http://larsoft.org/
CCTrackMaker_module.cc
Go to the documentation of this file.
1 //
3 // CCTrackMaker
4 //
5 // Make tracks using ClusterCrawler clusters and vertex info
6 //
7 // baller@fnal.gov, October 2014
8 // May 2015: Major re-write
9 //
11 
12 // C++ includes
13 #include <cmath>
14 #include <algorithm>
15 #include <iostream>
16 #include <iomanip>
17 #include <fstream>
18 #include <vector>
19 #include <string>
20 
21 // Framework includes
25 #include "fhiclcpp/ParameterSet.h"
31 
32 // LArSoft includes
43 #include "larevt/CalibrationDBI/Interface/ChannelStatusService.h"
44 #include "larevt/CalibrationDBI/Interface/ChannelStatusProvider.h"
45 
50 
51 
52 struct CluLen{
53  int index;
54  int length;
55 };
56 
57 bool greaterThan (CluLen c1, CluLen c2) { return (c1.length > c2.length);}
58 bool lessThan (CluLen c1, CluLen c2) { return (c1.length < c2.length);}
59 
60 namespace trkf {
61 
62  class CCTrackMaker : public art::EDProducer {
63 
64  public:
65 
66  explicit CCTrackMaker(fhicl::ParameterSet const& pset);
67  virtual ~CCTrackMaker();
68 
69  void reconfigure(fhicl::ParameterSet const& p);
70  void produce(art::Event& evt);
71  void beginJob();
72  // void endJob();
73 
74  private:
75 
76  std::string fHitModuleLabel;
77  std::string fClusterModuleLabel;
78  std::string fVertexModuleLabel;
79 
80  // services
83 
86 
87  // Track matching parameters
88  unsigned short algIndex;
89  std::vector<short> fMatchAlgs;
90  std::vector<float> fXMatchErr;
91  std::vector<float> fAngleMatchErr;
92  std::vector<float> fChgAsymFactor;
93  std::vector<float> fMatchMinLen;
94  std::vector<bool> fMakeAlgTracks;
95 
96  // Cluster merging parameters
97  float fMaxDAng;
98  float fChainMaxdX;
99  float fChainVtxAng;
103 
104  float fChgWindow;
105  float fWirePitch;
106  // cosmic ray tagging
109 
110  bool fMakePFPs;
111 
112  // vertex fitting
113  unsigned short fNVtxTrkHitsFit;
115 
116  // temp
117  bool fuBCode;
118 
119  // debugging inputs
120  short fDebugAlg;
121  short fDebugPlane;
124  bool prt;
125 
126  unsigned short nplanes;
127  unsigned int cstat;
128  unsigned int tpc;
129 
130  // hit indexing info
131  std::array<unsigned int, 3> firstWire;
132  std::array<unsigned int, 3> lastWire;
133  std::array<unsigned int, 3> firstHit;
134  std::array<unsigned int, 3> lastHit;
135  std::array<std::vector< std::pair<int, int> >, 3> WireHitRange;
136 
137  std::vector<art::Ptr<recob::Hit>> allhits;
138 
139  // Cluster parameters
140  struct clPar{
141  std::array<float, 2> Wire; // Begin/End Wire
142  std::array<float, 2> X; // Begin/End X
143  std::array<short, 2> Time; // Begin/End Time
144  std::array<float, 2> Slope; // Begin/End slope
145  std::array<float, 2> Charge; // Begin/End charge
146  std::array<float, 2> ChgNear; // Charge near the cluster at each end
147  std::array<float, 2> Angle; // Begin/End angle (radians)
148  std::array<short, 2> Dir; // Product of end * slope
149  std::array<short, 2> VtxIndex; // Vertex index
150  std::array<short, 2> mVtxIndex; // "Maybe" Vertex index
151  std::array<short, 2> BrkIndex; // Broken cluster index
152  std::array<float, 2> MergeError; // Broken cluster merge error (See MakeClusterChains)
153  unsigned short EvtIndex; // index of the cluster in clusterlist
154  short InTrack; // cluster -> chain index
155  unsigned short Length; // cluster length (wires)
156  float TotChg; // total charge of cluster (or series of clusters)
157  };
158  // vector of cluster parameters in each plane
159  std::array<std::vector<clPar>, 3> cls;
160 
161  // cluster chain parameters
162  struct ClsChainPar{
163  std::array<float, 2> Wire; // Begin/End Wire
164  std::array<float, 2> X; // Begin/End X
165  std::array<float, 2> Time; // Begin/End Time
166  std::array<float, 2> Slope; // Begin/End slope
167  std::array<float, 2> Angle; // Begin/End angle (radians)
168  std::array<short, 2> VtxIndex; // Vertex index
169  std::array<short, 2> Dir;
170  std::array<float, 2> ChgNear; // Charge near the cluster at each end
171  std::array<short, 2> mBrkIndex; // a "Maybe" cluster index
172  unsigned short Length;
173  float TotChg;
174  std::vector<unsigned short> ClsIndex;
175  std::vector<unsigned short> Order;
176  short InTrack; // cluster -> track ID (-1 if none, 0 if under construction)
177  };
178  // vector of cluster parameters in each plane
179  std::array<std::vector<ClsChainPar>, 3> clsChain;
180 
181  // 3D Vertex info
182  struct vtxPar{
183  short ID;
184  unsigned short EvtIndex;
185  float X;
186  float Y;
187  float Z;
188  std::array<unsigned short, 3> nClusInPln;
189  bool Neutrino;
190  };
191 
192  std::vector<vtxPar> vtx;
193 
194  // cluster indices assigned to one vertex. Filled in VtxMatch
195  std::array<std::vector<unsigned short>, 3> vxCls;
196 
197  struct TrkPar{
198  short ID;
199  unsigned short Proc; // 1 = VtxMatch, 2 = ...
200  std::array< std::vector<art::Ptr<recob::Hit>>, 3> TrkHits;
201  std::vector<TVector3> TrjPos;
202  std::vector<TVector3> TrjDir;
203  std::array<short, 2> VtxIndex;
204  std::vector<unsigned short> ClsEvtIndices;
205  float Length;
206  short ChgOrder;
207  short MomID;
208  std::array<bool, 2> EndInTPC;
209  std::array<bool, 2> GoodEnd; // set true if there are hits in all planes at the end point
210  std::vector<short> DtrID;
211  short PDGCode;
212  };
213  std::vector<TrkPar> trk;
214 
215  // Array of pointers to hits in each plane for one track
216  std::array< std::vector<art::Ptr<recob::Hit>>, 3> trkHits;
217  // and for one seed
218  std::array< std::vector<art::Ptr<recob::Hit>>, 3> seedHits;
219  // relative charge normalization between planes
220  std::array< float, 3> ChgNorm;
221 
222  // Vector of PFParticle -> track IDs. The first element will be
223  // track ID = 0 indicating that this is a neutrino PFParticle and
224  // there is no associated track
225  std::vector<unsigned short> pfpToTrkID;
226 
227  // characterize the match between clusters in 2 or 3 planes
228  struct MatchPars {
229  std::array<short, 3> Cls;
230  std::array<unsigned short, 3> End;
231  std::array<float, 3> Chg;
232  short Vtx;
233  float dWir; // wire difference at the matching end
234  float dAng; // angle difference at the matching end
235  float dX; // X difference
236  float Err; // Wire,Angle,Time match error
237  short oVtx;
238  float odWir; // wire difference at the other end
239  float odAng; // angle difference at the other end
240  float odX; // time difference at the other end
241  float dSP; // space point difference
242  float oErr; // dAngle dX match error
243  };
244  // vector of many match combinations
245  std::vector<MatchPars> matcomb;
246 
247  void PrintClusters() const;
248 
249  void PrintTracks() const;
250 
251  void MakeClusterChains(art::FindManyP<recob::Hit> const& fmCluHits);
252  float dXClTraj(art::FindManyP<recob::Hit> const& fmCluHits, unsigned short ipl, unsigned short icl1, unsigned short end1, unsigned short icl2);
253  void FillChgNear();
254  void FillWireHitRange();
255 
256  // Find clusters that point to vertices but do not have a
257  // cluster-vertex association made by ClusterCrawler
258  void FindMaybeVertices();
259  // match clusters associated with vertices
260  void VtxMatch(art::FindManyP<recob::Hit> const& fmCluHits);
261  // match clusters in all planes
262  void PlnMatch(art::FindManyP<recob::Hit> const& fmCluHits);
263  // match clusters in all planes
264  void AngMatch(art::FindManyP<recob::Hit> const& fmCluHits);
265 
266  // Make the track/vertex and mother/daughter relationships
267  void MakeFamily();
268  void TagCosmics();
269 
270  void FitVertices();
271 
272  // fill the end matching parameters in the MatchPars struct
273  void FillEndMatch(MatchPars& match);
274  // 2D version
275  void FillEndMatch2(MatchPars& match);
276 
277  float ChargeAsym(std::array<float, 3>& mChg);
278 
279  void FindMidPointMatch(art::FindManyP<recob::Hit> const& fmCluHits, MatchPars& match, unsigned short kkpl, unsigned short kkcl, unsigned short kkend, float& kkWir, float& kkX);
280 
281  bool FindMissingCluster(unsigned short kpl, short& kcl, unsigned short& kend, float kWir, float kX, float okWir, float okX);
282 
283  bool DupMatch(MatchPars& match);
284 
285  void SortMatches(art::FindManyP<recob::Hit> const& fmCluHits, unsigned short procCode);
286 
287  // fill the trkHits array using information.
288  void FillTrkHits(art::FindManyP<recob::Hit> const& fmCluHits, unsigned short imat);
289 
290  // Seed hits for the seed - hit association
291 // void FindSeedHits(unsigned short itk, unsigned short& end);
292 
293  // store the track in the trk vector
294  void StoreTrack(art::FindManyP<recob::Hit> const& fmCluHits, unsigned short imat, unsigned short procCode);
295 
296  // returns the charge along the line between (wire1, time1) and (wire2, time2)
297  float ChargeNear(unsigned short ipl, unsigned short wire1, float time1, unsigned short wire2, float time2);
298 
299  // inflate cuts at large angle
300  float AngleFactor(float slope);
301 
302  }; // class CCTrackMaker
303 
304  //-------------------------------------------------
305  CCTrackMaker::CCTrackMaker(fhicl::ParameterSet const& pset)
306  {
307  this->reconfigure(pset);
308  produces< std::vector<recob::PFParticle> >();
309  produces< art::Assns<recob::PFParticle, recob::Track> >();
310  produces< art::Assns<recob::PFParticle, recob::Cluster> >();
311  produces< art::Assns<recob::PFParticle, recob::Seed> >();
312  produces< art::Assns<recob::PFParticle, recob::Vertex> >();
313  produces< std::vector<recob::Vertex> >();
314  produces< std::vector<recob::Track> >();
315  produces< art::Assns<recob::Track, recob::Hit> >();
316  produces<std::vector<recob::Seed> >();
317  //produces< art::Assns<recob::Seed, recob::Hit> >();
318  }
319 
320  //-------------------------------------------------
321  void CCTrackMaker::reconfigure(fhicl::ParameterSet const& pset)
322  {
323  fHitModuleLabel = pset.get< std::string >("HitModuleLabel");
324  fClusterModuleLabel = pset.get< std::string >("ClusterModuleLabel");
325  fVertexModuleLabel = pset.get< std::string >("VertexModuleLabel");
326  // track matching
327  fMatchAlgs = pset.get< std::vector<short> >("MatchAlgs");
328  fXMatchErr = pset.get< std::vector<float> >("XMatchErr");
329  fAngleMatchErr = pset.get< std::vector<float> >("AngleMatchErr");
330  fChgAsymFactor = pset.get< std::vector<float> >("ChgAsymFactor");
331  fMatchMinLen = pset.get< std::vector<float> >("MatchMinLen");
332  fMakeAlgTracks = pset.get< std::vector<bool> >("MakeAlgTracks");
333  // Cluster merging
334  fMaxDAng = pset.get< float >("MaxDAng");
335  fChainMaxdX = pset.get< float >("ChainMaxdX");
336  fChainVtxAng = pset.get< float >("ChainVtxAng");
337  fMergeChgAsym = pset.get< float >("MergeChgAsym");
338  // Cosmic ray tagging
339  fFiducialCut = pset.get< float >("FiducialCut");
340  fDeltaRayCut = pset.get< float >("DeltaRayCut");
341  // make PFParticles
342  fMakePFPs = pset.get< bool >("MakePFPs");
343  // vertex fitting
344  fNVtxTrkHitsFit = pset.get< unsigned short >("NVtxTrkHitsFit");
345  fHitFitErrFac = pset.get< float >("HitFitErrFac");
346  // uB code
347  fuBCode = pset.get< bool >("uBCode");
348  // debugging inputs
349  fDebugAlg = pset.get< short >("DebugAlg");
350  fDebugPlane = pset.get< short >("DebugPlane");
351  fDebugCluster = pset.get< short >("DebugCluster");
352  fPrintAllClusters = pset.get< bool >("PrintAllClusters");
353 
354  // Consistency check
355  if(fMatchAlgs.size() > fXMatchErr.size() || fMatchAlgs.size() > fAngleMatchErr.size()
356  || fMatchAlgs.size() > fChgAsymFactor.size() || fMatchAlgs.size() > fMatchMinLen.size()
357  || fMatchAlgs.size() > fMakeAlgTracks.size()) {
358  mf::LogError("CCTM")<<"Incompatible fcl input vector sizes";
359  return;
360  }
361  // Reality check
362  for(unsigned short ii = 0; ii < fMatchAlgs.size(); ++ii) {
363  if(fAngleMatchErr[ii] <= 0 || fXMatchErr[ii] <= 0) {
364  mf::LogError("CCTM")<<"Invalid matching parameters "<<fAngleMatchErr[ii]<<" "<<fXMatchErr[ii];
365  return;
366  }
367  } // ii
368 
369  } // reconfigure
370 
371  //-------------------------------------------------
372  CCTrackMaker::~CCTrackMaker()
373  {
374  }
375 
376  //-------------------------------------------------
378  {
379  }
380 
381  /*
382  //-------------------------------------------------
383  void CCTrackMaker::endJob()
384  {
385  }
386  */
387  //------------------------------------------------------------------------------------//
388  void CCTrackMaker::produce(art::Event& evt)
389  {
390 
391  detprop = lar::providerFrom<detinfo::DetectorPropertiesService>();
392 
393  fWirePitch = geom->WirePitch();
394 
395  fChgWindow = 40; // window (ticks) for identifying shower-like clusters
396 
397  std::unique_ptr<std::vector<recob::Track>> tcol(new std::vector<recob::Track>);
398  std::unique_ptr<art::Assns<recob::Track, recob::Hit> > thassn (new art::Assns<recob::Track, recob::Hit>);
399 
400  std::unique_ptr<std::vector<recob::Vertex>> vcol(new std::vector<recob::Vertex>);
401 
402  std::unique_ptr<std::vector<recob::PFParticle>> pcol(new std::vector<recob::PFParticle>);
403 
404  std::unique_ptr< art::Assns<recob::PFParticle, recob::Track> > ptassn( new art::Assns<recob::PFParticle, recob::Track> );
405  std::unique_ptr< art::Assns<recob::PFParticle, recob::Cluster> > pcassn( new art::Assns<recob::PFParticle, recob::Cluster> );
406  std::unique_ptr< art::Assns<recob::PFParticle, recob::Seed> > psassn( new art::Assns<recob::PFParticle, recob::Seed> );
407  std::unique_ptr< art::Assns<recob::PFParticle, recob::Vertex> > pvassn( new art::Assns<recob::PFParticle, recob::Vertex> );
408 
409  // seed collection
410  std::unique_ptr<std::vector<recob::Seed>> scol(new std::vector<recob::Seed>);
411  std::unique_ptr<art::Assns<recob::Seed, recob::Hit> > shassn (new art::Assns<recob::Seed, recob::Hit>);
412 
413  // all hits
414  art::Handle< std::vector<recob::Hit> > allhitsListHandle;
415  // cluster list
416  art::Handle< std::vector<recob::Cluster> > clusterListHandle;
417  std::vector<art::Ptr<recob::Cluster>> clusterlist;
418  // ClusterCrawler Vertices
420  std::vector<art::Ptr<recob::Vertex>> vtxlist;
421 
422  // get Hits
423  allhits.clear();
424  if (evt.getByLabel(fHitModuleLabel, allhitsListHandle))
425  art::fill_ptr_vector(allhits, allhitsListHandle);
426 
427  // get Clusters
428  if (evt.getByLabel(fClusterModuleLabel, clusterListHandle))
429  art::fill_ptr_vector(clusterlist, clusterListHandle);
430  if(clusterlist.size() == 0) return;
431  // get cluster - hit associations
432  art::FindManyP<recob::Hit> fmCluHits(clusterListHandle, evt, fClusterModuleLabel);
433  // pfmCluHits.reset(something goes here);
434 
435  // get Vertices
436  if (evt.getByLabel(fVertexModuleLabel, VtxListHandle))
437  art::fill_ptr_vector(vtxlist, VtxListHandle);
438  art::FindManyP<recob::Cluster, unsigned short> fmVtxCls(VtxListHandle, evt, fVertexModuleLabel);
439 
440  std::vector<CluLen> clulens;
441 
442  unsigned short ipl, icl, end, itr, tID, tIndex;
443 
444  // maximum error (see MakeClusterChains) for considering clusters broken
445  fMaxMergeError = 30;
446  // Cut on the error for a definitely broken cluster. Clusters with fMergeErrorCut < MergeError < fMaxMergeError
447  // are possibly broken clusters but we will consider these when matching between planes
448  fMergeErrorCut = 10;
449 
450  std::vector< art::Ptr<recob::Hit > > tmpHits;
451  std::vector< art::Ptr<recob::Cluster > > tmpCls;
452  std::vector< art::Ptr<recob::Vertex > > tmpVtx;
453 
454  // vector for PFParticle constructor
455  std::vector<size_t> dtrIndices;
456 
457  // some vectors for recob::Seed
458  double sPos[3], sDir[3];
459  double sErr[3] = {0,0,0};
460 
461  // check consistency between clusters and associated hits
462  std::vector<art::Ptr<recob::Hit>> clusterhits;
463  for(icl = 0; icl < clusterlist.size(); ++icl) {
464  ipl = clusterlist[icl]->Plane().Plane;
465  clusterhits = fmCluHits.at(icl);
466  if(clusterhits[0]->WireID().Wire != std::nearbyint(clusterlist[icl]->EndWire())) {
467  std::cout<<"CCTM Cluster-Hit End wire mis-match "<<clusterhits[0]->WireID().Wire<<" vs "<<std::nearbyint(clusterlist[icl]->EndWire())<<" Bail out! \n";
468  return;
469  }
470  for(unsigned short iht = 0; iht < clusterhits.size(); ++iht) {
471  if(clusterhits[iht]->WireID().Plane != ipl) {
472  std::cout<<"CCTM Cluster-Hit plane mis-match "<<ipl<<" vs "<<clusterhits[iht]->WireID().Plane<<" on hit "<<iht<<" Bail out! \n";
473  return;
474  } // hit-cluster plane mis-match
475  } // iht
476  } // icl
477  // end check consistency
478 
479 // std::cout<<"************ event "<<evt.event()<<"\n";
480 
481  vtx.clear();
482  trk.clear();
483  for(cstat = 0; cstat < geom->Ncryostats(); ++cstat) {
484  for(tpc = 0; tpc < geom->Cryostat(cstat).NTPC(); ++tpc) {
485  nplanes = geom->Cryostat(cstat).TPC(tpc).Nplanes();
486  if(nplanes > 3) continue;
487  for(ipl = 0; ipl < 3; ++ipl) {
488  cls[ipl].clear();
489  clsChain[ipl].clear();
490  trkHits[ipl].clear();
491  } // ipl
492  // FillWireHitRange also calculates the charge in each plane
494  for(ipl = 0; ipl < nplanes; ++ipl) {
495  clulens.clear();
496  // sort clusters by increasing End wire number
497  for(icl = 0; icl < clusterlist.size(); ++icl) {
498  if(clusterlist[icl]->Plane().Cryostat != cstat) continue;
499  if(clusterlist[icl]->Plane().TPC != tpc) continue;
500  if(clusterlist[icl]->Plane().Plane != ipl) continue;
501  CluLen clulen;
502  clulen.index = icl;
503  clulen.length = clusterlist[icl]->EndWire();
504  clulens.push_back(clulen);
505  }
506  if(clulens.size() == 0) continue;
507  // sort clusters
508  std::sort (clulens.begin(),clulens.end(), lessThan);
509  if(clulens.size() == 0) continue;
510  for(unsigned short ii = 0; ii < clulens.size(); ++ii) {
511  const unsigned short icl = clulens[ii].index;
512  clPar clstr;
513  clstr.EvtIndex = icl;
514  recob::Cluster const& cluster = *(clusterlist[icl]);
515  // Begin info -> end index 1 (DS)
516  clstr.Wire[1] = cluster.StartWire();
517  clstr.Time[1] = cluster.StartTick();
518  clstr.X[1] = (float)detprop->ConvertTicksToX(cluster.StartTick(), ipl, tpc, cstat);
519  clstr.Angle[1] = cluster.StartAngle();
520  clstr.Slope[1] = std::tan(cluster.StartAngle());
521  clstr.Dir[1] = 0;
522 // if(fabs(clstr.Slope[1]) > 0.02) clstr.Dir[1] = -1 * (2*(clstr.Slope[1]>0)-1);
523  clstr.Charge[1] = ChgNorm[ipl] * cluster.StartCharge();
524  // this will be filled later
525  clstr.ChgNear[1] = 0;
526  clstr.VtxIndex[1] = -1;
527  clstr.mVtxIndex[1] = -1;
528  clstr.BrkIndex[1] = -1;
529  clstr.MergeError[1] = fMaxMergeError;
530  // End info -> end index 0 (US)
531  clstr.Wire[0] = cluster.EndWire();
532  clstr.Time[0] = cluster.EndTick();
533  clstr.X[0] = (float)detprop->ConvertTicksToX(cluster.EndTick(), ipl, tpc, cstat);
534  clstr.Angle[0] = cluster.EndAngle();
535  clstr.Slope[0] = std::tan(cluster.EndAngle());
536  clstr.Dir[0] = 0;
537 // if(fabs(clstr.Slope[0]) > 0.02) clstr.Dir[0] = 1 * (2*(clstr.Slope[0]>0)-1);
538  if(clstr.Time[1] > clstr.Time[0]) {
539  clstr.Dir[0] = 1; clstr.Dir[1] = -1;
540  } else {
541  clstr.Dir[0] = -1; clstr.Dir[1] = 1;
542  }
543  clstr.Charge[0] = ChgNorm[ipl] * cluster.EndCharge();
544  // this will be filled later
545  clstr.ChgNear[1] = 0;
546  clstr.VtxIndex[0] = -1;
547  clstr.mVtxIndex[0] = -1;
548  clstr.BrkIndex[0] = -1;
549  clstr.MergeError[0] = fMaxMergeError;
550  // other info
551  clstr.InTrack = -1;
552  clstr.Length = (unsigned short)(0.5 + clstr.Wire[1] - clstr.Wire[0]);
553  clstr.TotChg = ChgNorm[ipl] * cluster.Integral();
554  if(clstr.TotChg <= 0) clstr.TotChg = 1;
555  clusterhits = fmCluHits.at(icl);
556  if(clusterhits.size() == 0) {
557  mf::LogError("CCTM")<<"No associated cluster hits "<<icl;
558  continue;
559  }
560  // correct charge for missing cluster hits
561  clstr.TotChg *= clstr.Length / (float)clusterhits.size();
562  cls[ipl].push_back(clstr);
563  } // ii (icl)
564  } // ipl
565 
566 
567 // std::cout<<"FillChgNear "<<evt.event()<<"\n";
568  FillChgNear();
569 
570  // and finally the vertices
571  double xyz[3];
572  for(unsigned short ivx = 0; ivx < vtxlist.size(); ++ivx) {
573  vtxPar aVtx;
574  aVtx.ID = ivx + 1;
575  aVtx.EvtIndex = ivx;
576  vtxlist[ivx]->XYZ(xyz);
577  aVtx.X = xyz[0];
578  aVtx.Y = xyz[1];
579  aVtx.Z = xyz[2];
580  aVtx.nClusInPln[0] = 0;
581  aVtx.nClusInPln[1] = 0;
582  aVtx.nClusInPln[2] = 0;
583  std::vector<art::Ptr<recob::Cluster>> const& vtxCls = fmVtxCls.at(ivx);
584  std::vector<const unsigned short*> const& vtxClsEnd = fmVtxCls.data(ivx);
585  for(unsigned short vcass = 0; vcass < vtxCls.size(); ++vcass) {
586  icl = vtxCls[vcass].key();
587  // the cluster plane
588  ipl = vtxCls[vcass]->Plane().Plane;
589  end = *vtxClsEnd[vcass];
590  if(end > 1) throw cet::exception("CCTM")<<"Invalid end data from vertex - cluster association"<<end;
591  bool gotit = false;
592  // CCTM end is opposite of CC end
593  end = 1 - end;
594  for(unsigned short jcl = 0; jcl < cls[ipl].size(); ++jcl) {
595  if(cls[ipl][jcl].EvtIndex == icl) {
596  cls[ipl][jcl].VtxIndex[end] = ivx;
597  ++aVtx.nClusInPln[ipl];
598  gotit = true;
599  break;
600  } // index check
601  } // jcl
602  if(!gotit) throw cet::exception("CCTM")<<"Bad index from vertex - cluster association"<<icl;
603  } // icl
604  vtx.push_back(aVtx);
605  } // ivx
606  // Find broken clusters
607  MakeClusterChains(fmCluHits);
608  FindMaybeVertices();
609 
610  // call algorithms in the specified order
611  matcomb.clear();
612  for(algIndex = 0; algIndex < fMatchAlgs.size(); ++algIndex) {
613  if(fMatchAlgs[algIndex] == 1) {
614  prt = (fDebugAlg == 1);
615  VtxMatch(fmCluHits);
616  if(fMakeAlgTracks[algIndex]) SortMatches(fmCluHits, 1);
617  } else
618  if(fMatchAlgs[algIndex] == 2) {
619  prt = (fDebugAlg == 2);
620  PlnMatch(fmCluHits);
621  if(fMakeAlgTracks[algIndex]) SortMatches(fmCluHits, 2);
622  }
623  if(prt) PrintClusters();
624  } // algIndex
625  prt = false;
626  pfpToTrkID.clear();
627  // Determine the vertex/track hierarchy
628  if(fMakePFPs) {
629  TagCosmics();
630  MakeFamily();
631  }
632  FitVertices();
633 
634  // Make PFParticles -> tracks
635  for(unsigned short ipf = 0; ipf < pfpToTrkID.size(); ++ipf) {
636  // trackID of this PFP
637  tID = pfpToTrkID[ipf];
638  if(tID > trk.size()+1) {
639  mf::LogWarning("CCTM")<<"Bad track ID";
640  continue;
641  }
642  dtrIndices.clear();
643  // load the daughter PFP indices
644  mf::LogVerbatim("CCTM")<<"PFParticle "<<ipf<<" tID "<<tID;
645  for(unsigned short jpf = 0; jpf < pfpToTrkID.size(); ++jpf) {
646  itr = pfpToTrkID[jpf] - 1; // convert from track ID to track index
647  if(trk[itr].MomID == tID) dtrIndices.push_back(jpf);
648  if(trk[itr].MomID == tID) mf::LogVerbatim("CCTM")<<" dtr jpf "<<jpf<<" itr "<<itr;
649  } // jpf
650  unsigned short parentIndex = USHRT_MAX;
651  if(tID == 0) {
652  // make neutrino PFP USHRT_MAX == primary PFP
653  recob::PFParticle pfp(14, ipf, parentIndex, dtrIndices);
654  pcol->emplace_back(std::move(pfp));
655  for(unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
656  if(!vtx[ivx].Neutrino) continue;
657  // make the vertex
658  xyz[0] = vtx[ivx].X;
659  xyz[1] = vtx[ivx].Y;
660  xyz[2] = vtx[ivx].Z;
661  size_t vStart = vcol->size();
662  recob::Vertex vertex(xyz, vtx[ivx].ID);
663  vcol->emplace_back(std::move(vertex));
664  size_t vEnd = vcol->size();
665  // PFParticle - vertex association
666  util::CreateAssn(*this, evt, *pcol, *vcol, *pvassn, vStart, vEnd);
667  vtx[ivx].ID = -vtx[ivx].ID;
668  break;
669  } // ivx
670  } else if(tID > 0){
671  // make non-neutrino PFP. Need to find the parent PFP index
672  // Track index of this PFP
673  tIndex = tID - 1;
674  // trackID of this PFP parent
675  for(unsigned short ii = 0; ii < pfpToTrkID.size(); ++ii) {
676  if(pfpToTrkID[ii] == trk[tIndex].MomID) {
677  parentIndex = ii;
678  break;
679  }
680  } // ii
681  // PFParticle
682  mf::LogVerbatim("CCTM")<<"daughters tID "<<tID<<" pdg "<<trk[tIndex].PDGCode<<" ipf "<<ipf<<" parentIndex "<<parentIndex<<" dtr size "<<dtrIndices.size();
683  recob::PFParticle pfp(trk[tIndex].PDGCode, ipf, parentIndex, dtrIndices);
684  pcol->emplace_back(std::move(pfp));
685  // track
686  // make the track
687  size_t tStart = tcol->size();
690  recob::Track::Flags_t(trk[itr].TrjPos.size()), false),
692  tcol->emplace_back(std::move(track));
693  size_t tEnd = tcol->size();
694  // PFParticle - track association
695  util::CreateAssn(*this, evt, *pcol, *tcol, *ptassn, tStart, tEnd);
696  // flag this track as already put in the event
697  trk[tIndex].ID = -trk[tIndex].ID;
698  // track -> hits association
699  tmpHits.clear();
700  for(ipl = 0; ipl < nplanes; ++ipl)
701  tmpHits.insert(tmpHits.end(), trk[tIndex].TrkHits[ipl].begin(), trk[tIndex].TrkHits[ipl].end());
702  util::CreateAssn(*this, evt, *tcol, tmpHits, *thassn);
703  // Find seed hits and the end of the track that is best
704  end = 0;
705 // FindSeedHits(tIndex, end);
706  unsigned short itj = 0;
707  if(end > 0) itj = trk[tIndex].TrjPos.size() - 1;
708  for(unsigned short ii = 0; ii < 3; ++ii) {
709  sPos[ii] = trk[tIndex].TrjPos[itj](ii);
710  sDir[ii] = trk[tIndex].TrjDir[itj](ii);
711  } // ii
712  size_t sStart = scol->size();
713  recob::Seed seed(sPos, sDir, sErr, sErr);
714  scol->emplace_back(std::move(seed));
715  size_t sEnd = scol->size();
716  // PFP-seed association
717  util::CreateAssn(*this, evt, *pcol, *scol, *psassn, sStart, sEnd);
718  // Seed-hit association
719  tmpHits.clear();
720  for(ipl = 0; ipl < nplanes; ++ipl)
721  tmpHits.insert(tmpHits.end(), seedHits[ipl].begin(), seedHits[ipl].end());
722  util::CreateAssn(*this, evt, *scol, tmpHits, *shassn);
723  // cluster association
724  // PFP-cluster association
725  tmpCls.clear();
726  for(unsigned short ii = 0; ii < trk[tIndex].ClsEvtIndices.size(); ++ii) {
727  icl = trk[tIndex].ClsEvtIndices[ii];
728  tmpCls.push_back(clusterlist[icl]);
729  } // ii
730  util::CreateAssn(*this, evt, *pcol, tmpCls, *pcassn);
731  } // non-neutrino PFP
732  } // ipf
733  // make non-PFP tracks
734  for(unsigned short itr = 0; itr < trk.size(); ++itr) {
735  // ignore already saved tracks
736  if(trk[itr].ID < 0) continue;
739  recob::Track::Flags_t(trk[itr].TrjPos.size()), false),
741  tcol->emplace_back(std::move(track));
742  tmpHits.clear();
743  for(ipl = 0; ipl < nplanes; ++ipl)
744  tmpHits.insert(tmpHits.end(), trk[itr].TrkHits[ipl].begin(), trk[itr].TrkHits[ipl].end());
745  util::CreateAssn(*this, evt, *tcol, tmpHits, *thassn);
746  } // itr
747  // make remnant vertices
748  for(unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
749  if(vtx[ivx].ID < 0) continue;
750  xyz[0] = vtx[ivx].X;
751  xyz[1] = vtx[ivx].Y;
752  xyz[2] = vtx[ivx].Z;
753  recob::Vertex vertex(xyz, vtx[ivx].ID);
754  vcol->emplace_back(std::move(vertex));
755  }
756  if(fDebugAlg > 0) PrintTracks();
757 
758  double orphanLen = 0;
759  for(ipl = 0; ipl < nplanes; ++ipl) {
760  for(icl = 0; icl < cls[ipl].size(); ++icl) {
761  if(cls[ipl][icl].Length > 40 && cls[ipl][icl].InTrack < 0) {
762  orphanLen += cls[ipl][icl].Length;
763  // unused cluster
764  mf::LogVerbatim("CCTM")<<"Orphan long cluster "<<ipl<<":"<<icl<<":"<<cls[ipl][icl].Wire[0]
765  <<":"<<(int)cls[ipl][icl].Time[0]<<" length "<<cls[ipl][icl].Length;
766  }
767  } // icl
768 
769  cls[ipl].clear();
770  clsChain[ipl].clear();
771  } // ipl
772  std::cout<<"Total orphan length "<<orphanLen<<"\n";
773  trkHits[ipl].clear();
774  seedHits[ipl].clear();
775  vxCls[ipl].clear();
776  } // tpc
777  } // cstat
778 
779  evt.put(std::move(pcol));
780  evt.put(std::move(ptassn));
781  evt.put(std::move(pcassn));
782  evt.put(std::move(pvassn));
783  evt.put(std::move(psassn));
784  evt.put(std::move(tcol));
785  evt.put(std::move(thassn));
786  evt.put(std::move(scol));
787  evt.put(std::move(vcol));
788 
789  // final cleanup
790  vtx.clear();
791  trk.clear();
792  allhits.clear();
793  matcomb.clear();
794  pfpToTrkID.clear();
795 
796 
797  } // produce
798 
800 /*
801  void CCTrackMaker::FindSeedHits(unsigned short itk, unsigned short& end)
802  {
803  // Returns a subset of track hits that are near the start position and form
804  // a decently fit line
805 
806  unsigned short ipl;
807  for(ipl = 0; ipl < 3; ++ipl) seedHits[ipl].clear();
808 
809  unsigned short maxLen = 0, minLen = USHRT_MAX, midLen = 0;
810  for(unsigned short tEnd = 0; tEnd < 2; ++tEnd) {
811  if(!trk[itk].GoodEnd[tEnd]) continue;
812  for(ipl = 0; ipl < nplanes; ++ipl) {
813  if(trkHits[ipl].size() > maxLen) maxLen = trkHits[ipl].size();
814  if(trkHits[ipl].size() < minLen) minLen = trkHits[ipl].size();
815  }
816  // Return all track hits if it is short
817  if(maxLen < 10) {
818  for(ipl = 0; ipl < nplanes; ++ipl) seedHits[ipl] = trkHits[ipl];
819  end = tEnd;
820  return;
821  } // short track
822  // start a seed using 2 hits in the "middle length" plane
823  unsigned short midLenPln = 0;
824  for(ipl = 0; ipl < nplanes; ++ipl) {
825  if(trkHits[ipl].size() >= minLen && trkHits[ipl].size() <= maxLen) {
826  midLenPln = ipl;
827  midLen = trkHits[ipl].size();
828  break;
829  }
830  } // ipl
831  // ratio of hit lengths in each plane
832  std::array<float, 3> hitRat;
833  for(ipl = 0; ipl < nplanes; ++ipl) hitRat[ipl] = (float)trkHits[ipl].size() / float(midLen);
834  // working vectors passed to TrackLineFitAlg
835  std::vector<geo::WireID> hitWID;
836  std::vector<double> hitX;
837  std::vector<double> hitXErr;
838  TVector3 xyz, dir;
839  float ChiDOF;
840  double xOrigin = TrjPos[tEnd](0);
841  fTrackLineFitAlg.TrkLineFit(hitWID, hitX, hitXErr, xOrigin, xyz, dir, ChiDOF);
842 
843  end = tEnd;
844  return;
845  } // tend
846 
847  } // FindSeedHits
848 */
849 
851  void CCTrackMaker::FitVertices()
852  {
853 
854  std::vector<std::vector<geo::WireID>> hitWID;
855  std::vector<std::vector<double>> hitX;
856  std::vector<std::vector<double>> hitXErr;
857  TVector3 pos, posErr;
858  std::vector<TVector3> trkDir;
859  std::vector<TVector3> trkDirErr;
860  float ChiDOF;
861 
862  if(fNVtxTrkHitsFit == 0) return;
863 
864  unsigned short indx, indx2, iht, nHitsFit;
865 
866 // mf::LogVerbatim("CCTM")<<"Inside FitVertices "<<vtx.size()<<" trks "<<trk.size()<<"\n";
867 
868  for(unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
869  if(!vtx[ivx].Neutrino) continue;
870  hitWID.clear();
871  hitX.clear();
872  hitXErr.clear();
873  trkDir.clear();
874  // find the tracks associated with this vertex
875  unsigned int thePln, theTPC, theCst;
876  for(unsigned short itk = 0; itk < trk.size(); ++itk) {
877  for(unsigned short end = 0; end < 2; ++end) {
878  if(trk[itk].VtxIndex[end] != ivx) continue;
879  unsigned short itj = 0;
880  if(end == 1) itj = trk[itk].TrjPos.size() - 1;
881 // mf::LogVerbatim("CCTM")<<"ivx "<<ivx<<" trk "<<itk<<" end "<<end<<" TrjPos "<<trk[itk].TrjPos[itj](0)<<" "<<trk[itk].TrjPos[itj](1)<<" "
882 // <<trk[itk].TrjPos[itj](2)<<" TrjDir "<<trk[itk].TrjDir[itj](0)<<" "<<trk[itk].TrjDir[itj](1)<<" "<<trk[itk].TrjDir[itj](2);
883  // increase the size of the hit vectors by 1 for this track
884  indx = hitX.size();
885  hitWID.resize(indx + 1);
886  hitX.resize(indx + 1);
887  hitXErr.resize(indx + 1);
888  trkDir.resize(indx + 1);
889  trkDir[indx] = trk[itk].TrjDir[itj];
890  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) {
891  if(trk[itk].TrkHits[ipl].size() < 2) continue;
892  // make slots for the hits on this track in this plane
893  nHitsFit = trk[itk].TrkHits[ipl].size();
894  if(nHitsFit > fNVtxTrkHitsFit) nHitsFit = fNVtxTrkHitsFit;
895  indx2 = hitWID[indx].size();
896  hitWID[indx].resize(indx2 + nHitsFit);
897  hitX[indx].resize(indx2 + nHitsFit);
898  hitXErr[indx].resize(indx2 + nHitsFit);
899  for(unsigned short ii = 0; ii < nHitsFit; ++ii) {
900  if(end == 0) {
901  iht = ii;
902  } else {
903  iht = trk[itk].TrkHits[ipl].size() - ii - 1;
904  }
905  hitWID[indx][indx2 + ii] = trk[itk].TrkHits[ipl][iht]->WireID();
906  thePln = trk[itk].TrkHits[ipl][iht]->WireID().Plane;
907  theTPC = trk[itk].TrkHits[ipl][iht]->WireID().TPC;
908  theCst = trk[itk].TrkHits[ipl][iht]->WireID().Cryostat;
909  hitX[indx][indx2 + ii] = detprop->ConvertTicksToX(trk[itk].TrkHits[ipl][iht]->PeakTime(), thePln, theTPC, theCst);
910  hitXErr[indx][indx2 + ii] = fHitFitErrFac * trk[itk].TrkHits[ipl][iht]->RMS();
911 // mf::LogVerbatim("CCTM")<<"CCTM "<<itk<<" indx "<<indx<<" ii "<<ii<<" WID "<<trk[itk].TrkHits[ipl][iht]->WireID()
912 // <<" X "<<hitX[indx][indx2 + ii]<<" XErr "<<hitXErr[indx][indx2 + ii]<<" RMS "<<trk[itk].TrkHits[ipl][iht]->RMS()
913 // <<" SigmaPeakTime "<<trk[itk].TrkHits[ipl][iht]->SigmaPeakTime();
914  } // ii
915  } // ipl
916  } // end
917  } // itk
918  if(hitX.size() < 2) {
919  mf::LogVerbatim("CCTM")<<"Not enough hits to fit vtx "<<ivx;
920  continue;
921  } // hitX.size() < 2
922  pos(0) = vtx[ivx].X;
923  pos(1) = vtx[ivx].Y;
924  pos(2) = vtx[ivx].Z;
925  fVertexFitAlg.VertexFit(hitWID, hitX, hitXErr, pos, posErr, trkDir, trkDirErr, ChiDOF);
926 /*
927  mf::LogVerbatim("CCTM")<<"FV: CC Vtx pos "<<vtx[ivx].X<<" "<<vtx[ivx].Y<<" "<<vtx[ivx].Z;
928  mf::LogVerbatim("CCTM")<<"FV: Fitted "<<pos[0]<<" "<<pos[1]<<" "<<pos[2]<<" ChiDOF "<<ChiDOF;
929  mf::LogVerbatim("CCTM")<<"FV: Errors "<<posErr[0]<<" "<<posErr[1]<<" "<<posErr[2];
930 */
931  if(ChiDOF > 3000) continue;
932  // update the vertex position
933  vtx[ivx].X = pos(0);
934  vtx[ivx].Y = pos(1);
935  vtx[ivx].Z = pos(2);
936  // and the track trajectory
937  unsigned short fitTrk = 0;
938  for(unsigned short itk = 0; itk < trk.size(); ++itk) {
939  for(unsigned short end = 0; end < 2; ++end) {
940  if(trk[itk].VtxIndex[end] != ivx) continue;
941  unsigned short itj = 0;
942  if(end == 1) itj = trk[itk].TrjPos.size() - 1;
943  trk[itk].TrjDir[itj] = trkDir[fitTrk];
944  ++fitTrk;
945  } // end
946  } // itk
947  } // ivx
948  } // FitVertices
949 
951  void CCTrackMaker::FillChgNear()
952  {
953  // fills the CHgNear array
954 
955  unsigned short wire, nwires, indx;
956  float dir, ctime, cx, chgWinLo, chgWinHi;
957  float cnear;
958 
959  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) {
960  for(unsigned short icl = 0; icl < cls[ipl].size(); ++icl) {
961  // find the nearby charge at the US and DS ends
962  nwires = cls[ipl][icl].Length / 2;
963  if(nwires < 2) continue;
964  // maximum of 30 wires for long clusters
965  if(nwires > 30) nwires = 30;
966  for(unsigned short end = 0; end < 2; ++end) {
967  cnear = 0;
968  // direction for adding/subtracting wire numbers
969  dir = 1 - 2 * end;
970  for(unsigned short w = 0; w < nwires; ++w) {
971  wire = cls[ipl][icl].Wire[end] + dir * w;
972  cx = cls[ipl][icl].X[end] + dir * w * cls[ipl][icl].Slope[end] * fWirePitch;
973  ctime = detprop->ConvertXToTicks(cx, ipl, tpc, cstat);
974  chgWinLo = ctime - fChgWindow;
975  chgWinHi = ctime + fChgWindow;
976  indx = wire - firstWire[ipl];
977  if(WireHitRange[ipl][indx].first < 0) continue;
978  unsigned int firhit = WireHitRange[ipl][indx].first;
979  unsigned int lashit = WireHitRange[ipl][indx].second;
980  for(unsigned int hit = firhit; hit < lashit; ++hit) {
981  if(hit > allhits.size() - 1) {
982  mf::LogError("CCTM")<<"FillChgNear bad lashit "<<lashit<<" size "<<allhits.size()<<"\n";
983  continue;
984  }
985  if(allhits[hit]->PeakTime() < chgWinLo) continue;
986  if(allhits[hit]->PeakTime() > chgWinHi) continue;
987  cnear += allhits[hit]->Integral();
988  } // hit
989  } // w
990  cnear /= (float)(nwires-1);
991  if(cnear > cls[ipl][icl].Charge[end]) {
992  cls[ipl][icl].ChgNear[end] = ChgNorm[ipl] * cnear / cls[ipl][icl].Charge[end];
993  } else {
994  cls[ipl][icl].ChgNear[end] = 0;
995  }
996  } // end
997  } // icl
998  } //ipl
999 
1000  } // FillChgNear
1001 
1002 
1004  void CCTrackMaker::MakeFamily()
1005  {
1006  // define the track/vertex and mother/daughter relationships
1007 
1008  unsigned short ivx, itr, ipl, ii, jtr;
1009  unsigned short nus, nds, nuhs, ndhs;
1010  float longUSTrk, longDSTrk, qual;
1011 
1012  // min distance^2 between a neutrino vertex candidate and a through
1013  // going muon
1014  float tgMuonCut2 = 9;
1015 
1016  // struct for neutrino vertex candidates
1017  struct NuVtx {
1018  unsigned short VtxIndex;
1019  unsigned short nUSTk;
1020  unsigned short nDSTk;
1021  unsigned short nUSHit;
1022  unsigned short nDSHit;
1023  float longUSTrk;
1024  float longDSTrk;
1025  float Qual;
1026  };
1027  std::vector<NuVtx> nuVtxCand;
1028 
1029  NuVtx aNuVtx;
1030 
1031  // analyze all of the vertices
1032  float best = 999, dx, dy, dz, dr;
1033  short imbest = -1;
1034  bool skipVtx;
1035  unsigned short itj;
1036  for(ivx = 0; ivx < vtx.size(); ++ivx) {
1037  vtx[ivx].Neutrino = false;
1038  nus = 0; nds = 0; nuhs = 0; ndhs = 0;
1039  longUSTrk = 0; longDSTrk = 0;
1040  skipVtx = false;
1041  // skip vertices that are close to through-going muons
1042  for(itr = 0; itr < trk.size(); ++itr) {
1043  if(trk[itr].ID < 0) continue;
1044  if(trk[itr].PDGCode != 13) continue;
1045  for(itj = 0; itj < trk[itr].TrjPos.size(); ++itj) {
1046  dx = trk[itr].TrjPos[itj](0) - vtx[ivx].X;
1047  dy = trk[itr].TrjPos[itj](1) - vtx[ivx].Y;
1048  dz = trk[itr].TrjPos[itj](2) - vtx[ivx].Z;
1049  dr = dx * dx + dy * dy + dz * dz;
1050  if(dr < tgMuonCut2) {
1051  skipVtx = true;
1052  break;
1053  }
1054  if(skipVtx) break;
1055  } // itj
1056  if(skipVtx) break;
1057  } // itr
1058  if(skipVtx) continue;
1059  for(itr = 0; itr < trk.size(); ++itr) {
1060  if(trk[itr].ID < 0) continue;
1061  if(trk[itr].VtxIndex[0] == ivx) {
1062  // DS-going track
1063  ++nds;
1064  if(trk[itr].Length > longDSTrk) longDSTrk = trk[itr].Length;
1065  for(ipl = 0; ipl < nplanes; ++ipl) ndhs += trk[itr].TrkHits[ipl].size();
1066 // std::cout<<"MakeFamily: ivx "<<ivx<<" DS itr "<<trk[itr].ID<<" len "<<trk[itr].Length<<"\n";
1067  } // DS-going track
1068  // Reject this vertex as a neutrino candidate if the track being
1069  // considered has a starting vertex
1070  if(trk[itr].VtxIndex[1] == ivx && trk[itr].VtxIndex[0] >= 0) {
1071  skipVtx = true;
1072  break;
1073  } // trk[itr].VtxIndex[0] > 0
1074  if(trk[itr].VtxIndex[1] == ivx && trk[itr].VtxIndex[0] < 0) {
1075  // US-going track w no US vertex
1076  ++nus;
1077  if(trk[itr].Length > longUSTrk) longUSTrk = trk[itr].Length;
1078  for(ipl = 0; ipl < nplanes; ++ipl) nuhs += trk[itr].TrkHits[ipl].size();
1079 // std::cout<<"MakeFamily: ivx "<<ivx<<" US itr "<<trk[itr].ID<<" len "<<trk[itr].Length<<"\n";
1080  } // US-going track
1081  } // itr
1082  if(skipVtx) continue;
1083  if(nds == 0) continue;
1084  qual = 1 / (float)nds;
1085  qual /= (float)ndhs;
1086  if(nus > 0) qual *= (float)nuhs / (float)ndhs;
1087  if(qual < best) {
1088  best = qual;
1089  imbest = ivx;
1090  }
1091  if(nds > 0 && longDSTrk > 5) {
1092  // at least one longish track going DS
1093  aNuVtx.VtxIndex = ivx;
1094  aNuVtx.nUSTk = nus;
1095  aNuVtx.nDSTk = nds;
1096  aNuVtx.nUSHit = nuhs;
1097  aNuVtx.nDSHit = ndhs;
1098  aNuVtx.longUSTrk = longUSTrk;
1099  aNuVtx.longDSTrk = longDSTrk;
1100  aNuVtx.Qual = qual;
1101  nuVtxCand.push_back(aNuVtx);
1102  }
1103  } // ivx
1104 /*
1105  mf::LogVerbatim("CCTM")<<"Neutrino vtx candidates";
1106  for(unsigned short ican = 0; ican < nuVtxCand.size(); ++ican)
1107  mf::LogVerbatim("CCTM")<<"Can "<<ican<<" vtx "<<nuVtxCand[ican].VtxIndex<<" nUSTk "<<nuVtxCand[ican].nUSTk
1108  <<" nUSHit "<<nuVtxCand[ican].nUSHit<<" longUS "<<nuVtxCand[ican].longUSTrk<<" nDSTk "<<nuVtxCand[ican].nDSTk
1109  <<" nDSHit "<<nuVtxCand[ican].nDSHit<<" longDS "<<nuVtxCand[ican].longDSTrk<<" Qual "<<nuVtxCand[ican].Qual;
1110 */
1111  if(imbest < 0) return;
1112 
1113  // Found the neutrino interaction vertex
1114  ivx = imbest;
1115  vtx[ivx].Neutrino = true;
1116  // Put a 0 into the PFParticle -> track vector to identify a neutrino
1117  // track with no trajectory or hits
1118  pfpToTrkID.push_back(0);
1119 
1120  // list of DS-going current generation daughters so we can fix next
1121  // generation daughter assignments. This code section assumes that there
1122  // are no decays or 2ndry interactions with US-going primary tracks.
1123  std::vector<unsigned short> dtrGen;
1124  std::vector<unsigned short> dtrNextGen;
1125  for(itr = 0; itr < trk.size(); ++itr) {
1126  if(trk[itr].ID < 0) continue;
1127  if(trk[itr].VtxIndex[0] == ivx) {
1128  // DS-going primary track
1129  trk[itr].MomID = 0;
1130  // call every track coming from a neutrino vertex a proton
1131  trk[itr].PDGCode = 2212;
1132  pfpToTrkID.push_back(trk[itr].ID);
1133  dtrGen.push_back(itr);
1134  } // DS-going primary track
1135  if(trk[itr].VtxIndex[1] == ivx) {
1136  // US-going primary track
1137  trk[itr].MomID = 0;
1138  // call every track coming from a neutrino vertex a proton
1139  trk[itr].PDGCode = 2212;
1140  pfpToTrkID.push_back(trk[itr].ID);
1141  // reverse the track trajectory
1142  std::reverse(trk[itr].TrjPos.begin(), trk[itr].TrjPos.end());
1143  for(ii = 0; ii < trk[itr].TrjDir.size(); ++ii)
1144  trk[itr].TrjDir[ii] = -trk[itr].TrjDir[ii];
1145  } // DS-going primary track
1146  } // itr
1147 
1148  if(dtrGen.size() == 0) return;
1149 
1150  unsigned short tmp, indx;
1151  unsigned short nit = 0;
1152 
1153  // follow daughters through all generations (< 10). Daughter tracks
1154  // may go US or DS
1155  while(nit < 10) {
1156  ++nit;
1157  dtrNextGen.clear();
1158  // Look for next generation daughters
1159  for(ii = 0; ii < dtrGen.size(); ++ii) {
1160  itr = dtrGen[ii];
1161  if(trk[itr].VtxIndex[1] >= 0) {
1162  // found a DS vertex
1163  ivx = trk[itr].VtxIndex[1];
1164  // look for a track associated with this vertex
1165  for(jtr = 0; jtr < trk.size(); ++jtr) {
1166  if(jtr == itr) continue;
1167  if(trk[jtr].VtxIndex[0] == ivx) {
1168  // DS-going track
1169  indx = trk[itr].DtrID.size();
1170  trk[itr].DtrID.resize(indx + 1);
1171  trk[itr].DtrID[indx] = jtr;
1172 // std::cout<<"itr "<<itr<<" dtr "<<jtr<<" DtrID size "<<trk[itr].DtrID.size()<<"\n";
1173  trk[jtr].MomID = trk[itr].ID;
1174  // call all daughters pions
1175  trk[jtr].PDGCode = 211;
1176  dtrNextGen.push_back(jtr);
1177  pfpToTrkID.push_back(trk[jtr].ID);
1178  } // DS-going track
1179  if(trk[jtr].VtxIndex[1] == ivx) {
1180  // US-going track
1181  indx = trk[itr].DtrID.size();
1182  trk[itr].DtrID.resize(indx + 1);
1183  trk[itr].DtrID[indx] = jtr;
1184 // std::cout<<"itr "<<itr<<" dtr "<<jtr<<" DtrID size "<<trk[itr].DtrID.size()<<"\n";
1185  trk[jtr].MomID = trk[itr].ID;
1186  // call all daughters pions
1187  trk[jtr].PDGCode = 211;
1188  dtrNextGen.push_back(jtr);
1189  pfpToTrkID.push_back(trk[jtr].ID);
1190  // reverse the track trajectory
1191  std::reverse(trk[jtr].TrjPos.begin(), trk[jtr].TrjPos.end());
1192  for(unsigned short jj = 0; jj < trk[jtr].TrjDir.size(); ++jj)
1193  trk[jtr].TrjDir[jj] = -trk[jtr].TrjDir[jj];
1194  // interchange the trk - vtx assignments
1195  tmp = trk[jtr].VtxIndex[0];
1196  trk[jtr].VtxIndex[0] = trk[jtr].VtxIndex[1];
1197  trk[jtr].VtxIndex[1] = tmp;
1198  } // DS-going track
1199  } // jtr
1200  } // trk[itr].VtxIndex[0] >= 0
1201  } // ii (itr)
1202  // break out if no next gen daughters found
1203  if(dtrNextGen.size() == 0) break;
1204  dtrGen = dtrNextGen;
1205  } // nit
1206 
1207  } // MakeFamily
1208 
1210  void CCTrackMaker::TagCosmics()
1211  {
1212  // Make cosmic ray PFParticles
1213  unsigned short ipf, itj;
1214  bool skipit = true;
1215 
1216  // Y,Z limits of the detector
1217  double local[3] = {0.,0.,0.};
1218  double world[3] = {0.,0.,0.};
1219 
1220  const geo::TPCGeo &thetpc = geom->TPC(tpc, cstat);
1221  thetpc.LocalToWorld(local,world);
1222  float XLo = world[0] - geom->DetHalfWidth(tpc,cstat) + fFiducialCut;
1223  float XHi = world[0] + geom->DetHalfWidth(tpc,cstat) - fFiducialCut;
1224  float YLo = world[1] - geom->DetHalfHeight(tpc,cstat) + fFiducialCut;
1225  float YHi = world[1] + geom->DetHalfHeight(tpc,cstat) - fFiducialCut;
1226  float ZLo = world[2] - geom->DetLength(tpc,cstat)/2 + fFiducialCut;
1227  float ZHi = world[2] + geom->DetLength(tpc,cstat)/2 - fFiducialCut;
1228 
1229  bool startsIn, endsIn;
1230 
1231  for(unsigned short itk = 0; itk < trk.size(); ++itk) {
1232  // ignore already used tracks
1233  if(trk[itk].ID < 0) continue;
1234  // ignore short tracks (< 10 cm)
1235  if(trk[itk].Length < 10) continue;
1236  // check for already identified PFPs
1237  skipit = false;
1238  for(ipf = 0; ipf < pfpToTrkID.size(); ++ipf) {
1239  if(pfpToTrkID[ipf] == trk[itk].ID) {
1240  skipit = true;
1241  break;
1242  }
1243  } // ipf
1244  if(skipit) continue;
1245  startsIn = true;
1246  if(trk[itk].TrjPos[0](0) < XLo || trk[itk].TrjPos[0](0) > XHi) startsIn = false;
1247  if(trk[itk].TrjPos[0](1) < YLo || trk[itk].TrjPos[0](1) > YHi) startsIn = false;
1248  if(trk[itk].TrjPos[0](2) < ZLo || trk[itk].TrjPos[0](2) > ZHi) startsIn = false;
1249 // std::cout<<"Trk "<<trk[itk].ID<<" X0 "<<(int)trk[itk].TrjPos[0](0)<<" Y0 "<<(int)trk[itk].TrjPos[0](1)<<" Z0 "<<(int)trk[itk].TrjPos[0](2)<<" startsIn "<<startsIn<<"\n";
1250  if(startsIn) continue;
1251  endsIn = true;
1252  itj = trk[itk].TrjPos.size() - 1;
1253  if(trk[itk].TrjPos[itj](0) < XLo || trk[itk].TrjPos[itj](0) > XHi) endsIn = false;
1254  if(trk[itk].TrjPos[itj](1) < YLo || trk[itk].TrjPos[itj](1) > YHi) endsIn = false;
1255  if(trk[itk].TrjPos[itj](2) < ZLo || trk[itk].TrjPos[itj](2) > ZHi) endsIn = false;
1256 // std::cout<<" X1 "<<(int)trk[itk].TrjPos[itj](0)<<" Y1 "<<(int)trk[itk].TrjPos[itj](1)<<" Z1 "<<(int)trk[itk].TrjPos[itj](2)<<" endsIn "<<endsIn<<"\n";
1257  if(endsIn) continue;
1258  // call it a cosmic muon
1259  trk[itk].PDGCode = 13;
1260  pfpToTrkID.push_back(trk[itk].ID);
1261  } // itk
1262 
1263  if(fDeltaRayCut <= 0) return;
1264 
1265  for(unsigned short itk = 0; itk < trk.size(); ++itk) {
1266  // find a tagged cosmic ray
1267  if(trk[itk].PDGCode != 13) continue;
1268 
1269  } // itk
1270 
1271  } // TagCosmics
1272 
1274  void CCTrackMaker::VtxMatch(art::FindManyP<recob::Hit> const& fmCluHits)
1275  {
1276  // Use vertex assignments to match clusters
1277  unsigned short ivx, ii, ipl, icl, jj, jpl, jcl, kk, kpl, kcl;
1278  short idir, iend, jdir, jend, kdir, kend, ioend;
1279 
1280  for(ivx = 0; ivx < vtx.size(); ++ivx) {
1281 
1282  // list of cluster chains associated with this vertex in each plane
1283  for(ipl = 0; ipl < nplanes; ++ipl) {
1284  vxCls[ipl].clear();
1285  for(icl = 0; icl < clsChain[ipl].size(); ++icl) {
1286  if(clsChain[ipl][icl].InTrack >= 0) continue;
1287  for(iend = 0; iend < 2; ++iend) {
1288  if(clsChain[ipl][icl].VtxIndex[iend] == vtx[ivx].EvtIndex) vxCls[ipl].push_back(icl);
1289  } // end
1290  } // icl
1291  } // ipl
1292 
1293  if(prt) {
1294  mf::LogVerbatim myprt("CCTM");
1295  myprt<<"VtxMatch: Vertex ID "<<vtx[ivx].EvtIndex<<"\n";
1296  for(ipl = 0; ipl < nplanes; ++ipl) {
1297  myprt<<"ipl "<<ipl<<" cls";
1298  for(unsigned short ii = 0; ii < vxCls[ipl].size(); ++ii) myprt<<" "<<vxCls[ipl][ii];
1299  myprt<<"\n";
1300  } // ipl
1301  } // prt
1302  // match between planes, requiring clusters matched to this vertex
1303  iend = 0; jend = 0;
1304  bool gotkcl;
1305  float totErr;
1306  for(ipl = 0; ipl < nplanes; ++ipl) {
1307  if(nplanes == 2 && ipl > 0) continue;
1308  for(ii = 0; ii < vxCls[ipl].size(); ++ii) {
1309  icl = vxCls[ipl][ii];
1310  // ignore used clusters
1311  if(clsChain[ipl][icl].InTrack >= 0) continue;
1312  jpl = (ipl + 1) % nplanes;
1313  kpl = (jpl + 1) % nplanes;
1314  for(jj = 0; jj < vxCls[jpl].size(); ++jj) {
1315  jcl = vxCls[jpl][jj];
1316  if(clsChain[jpl][jcl].InTrack >= 0) continue;
1317  for(iend = 0; iend < 2; ++iend) {
1318  if(clsChain[ipl][icl].VtxIndex[iend] != vtx[ivx].EvtIndex) continue;
1319  ioend = 1 - iend;
1320  idir = clsChain[ipl][icl].Dir[iend];
1321  for(jend = 0; jend < 2; ++jend) {
1322  if(clsChain[jpl][jcl].VtxIndex[jend] != vtx[ivx].EvtIndex) continue;
1323  jdir = clsChain[jpl][jcl].Dir[jend];
1324  if(idir != 0 && jdir != 0 && idir != jdir) continue;
1325  // ignore outrageously bad other end X matches
1326  if(fabs(clsChain[jpl][jcl].X[1 - jend] - clsChain[ipl][icl].X[ioend]) > 50) continue;
1327  MatchPars match;
1328  match.Cls[ipl] = icl; match.End[ipl] = iend;
1329  match.Cls[jpl] = jcl; match.End[jpl] = jend;
1330  match.Vtx = ivx; match.oVtx = -1;
1331  // set large so that DupMatch doesn't get confused when called before FillEndMatch
1332  match.Err = 1E6; match.oErr = 1E6;
1333  if(nplanes == 2) {
1334 // mf::LogVerbatim("CCTM")<<"chk "<<ipl<<":"<<match.Cls[ipl]<<":"<<match.End[ipl]<<" and "<<jpl<<":"<<match.Cls[jpl]<<":"<<match.End[jpl];
1335  FillEndMatch2(match);
1336  if(prt) mf::LogVerbatim("CCTM")<<"FillEndMatch2: Err "<<match.Err<<" oErr "<<match.oErr;
1337  if(match.Err + match.oErr > 100) continue;
1338  if(DupMatch(match)) continue;
1339  matcomb.push_back(match);
1340  continue;
1341  }
1342  match.Cls[kpl] = -1; match.End[kpl] = 0;
1343  if(prt) mf::LogVerbatim("CCTM")<<"VtxMatch: check "<<ipl<<":"<<icl<<":"<<iend<<" and "<<jpl<<":"<<jcl<<":"<<jend<<" for cluster in kpl "<<kpl;
1344  gotkcl = false;
1345  for(kk = 0; kk < vxCls[kpl].size(); ++kk) {
1346  kcl = vxCls[kpl][kk];
1347  if(clsChain[kpl][kcl].InTrack >= 0) continue;
1348  for(kend = 0; kend < 2; ++kend) {
1349  kdir = clsChain[kpl][kcl].Dir[kend];
1350  if(idir != 0 && kdir != 0 && idir != kdir) continue;
1351  if(clsChain[kpl][kcl].VtxIndex[kend] != vtx[ivx].EvtIndex) continue;
1352  // rough check of other end match
1353  // TODO: SHOWER-LIKE CLUSTER CHECK
1354  match.Cls[kpl] = kcl; match.End[kpl] = kend;
1355  // first call to ignore redundant matches
1356  if(DupMatch(match)) continue;
1357  FillEndMatch(match);
1358 // mf::LogVerbatim("CCTM")<<" Chg "<<match.Chg[kpl]<<" Err "<<match.Err<<" oErr "<<match.oErr;
1359  // ignore if no signal at the other end
1360  if(match.Chg[kpl] <= 0) continue;
1361  if(match.Err + match.oErr > 100) continue;
1362  // second call to keep matches with better error
1363  if(DupMatch(match)) continue;
1364  matcomb.push_back(match);
1365  gotkcl = true;
1366 // break;
1367  } // kend
1368  } // kk -> kcl
1369  if(gotkcl) continue;
1370  // look for a cluster that missed the vertex assignment
1371  float best = 10;
1372  short kbst = -1;
1373  unsigned short kbend = 0;
1374  if(prt) mf::LogVerbatim("CCTM")<<"VtxMatch: look for missed cluster chain in kpl";
1375  for(kcl = 0; kcl < clsChain[kpl].size(); ++kcl) {
1376  if(clsChain[kpl][kcl].InTrack >= 0) continue;
1377  for(kend = 0; kend < 2; ++kend) {
1378  kdir = clsChain[kpl][kcl].Dir[kend];
1379  if(idir != 0 && kdir != 0 && idir != kdir) continue;
1380  if(clsChain[kpl][kcl].VtxIndex[kend] >= 0) continue;
1381  // make a rough dX cut at the match end
1382  if(fabs(clsChain[kpl][kcl].X[kend] - vtx[ivx].X) > 5) continue;
1383  // and at the other end
1384  if(fabs(clsChain[kpl][kcl].X[1 - kend] - clsChain[ipl][icl].X[ioend]) > 50) continue;
1385  // check the error
1386  match.Cls[kpl] = kcl; match.End[kpl] = kend;
1387  if(DupMatch(match)) continue;
1388  FillEndMatch(match);
1389  totErr = match.Err + match.oErr;
1390  if(prt) {
1391  mf::LogVerbatim myprt("CCTM");
1392  myprt<<"VtxMatch: Chk missing cluster match ";
1393  for(unsigned short ii = 0; ii < nplanes; ++ii)
1394  myprt<<" "<<ii<<":"<<match.Cls[ii]<<":"<<match.End[ii];
1395  myprt<<" Err "<<match.Err<<"\n";
1396  }
1397  if(totErr > 100) continue;
1398  if(totErr < best) {
1399  best = totErr;
1400  kbst = kcl;
1401  kbend = kend;
1402  }
1403  } // kend
1404  } // kcl
1405  if(kbst >= 0) {
1406  // found a decent match
1407  match.Cls[kpl] = kbst; match.End[kpl] = kbend;
1408  FillEndMatch(match);
1409  matcomb.push_back(match);
1410  // assign the vertex to this cluster
1411  clsChain[kpl][kbst].VtxIndex[kbend] = ivx;
1412  // and update vxCls
1413  vxCls[kpl].push_back(kbst);
1414  } else {
1415  // Try a 2 plane match if a 3 plane match didn't work
1416  match.Cls[kpl] = -1; match.End[kpl] = 0;
1417  if(DupMatch(match)) continue;
1418  FillEndMatch(match);
1419  if(match.Err + match.oErr < 100) matcomb.push_back(match);
1420  }
1421  } // jend
1422  } // iend
1423  } // jj
1424  } // ii -> icl
1425  } // ipl
1426 
1427  if(matcomb.size() == 0) continue;
1428  SortMatches(fmCluHits, 1);
1429 
1430  } // ivx
1431 
1432  for(ipl = 0; ipl < 3; ++ipl) vxCls[ipl].clear();
1433 
1434  } // VtxMatch
1435 
1437  void CCTrackMaker::FindMaybeVertices()
1438  {
1439  // Project clusters to vertices and fill mVtxIndex. No requirement is
1440  // made that charge exists on the line between the Begin (End) of the
1441  // cluster and the vertex
1442  unsigned short ipl, icl, end, ivx, oend;
1443  float best, dWire, dX;
1444  short ibstvx;
1445 
1446  if(vtx.size() == 0) return;
1447 
1448  for(ipl = 0; ipl < nplanes; ++ipl) {
1449  for(icl = 0; icl < cls[ipl].size(); ++icl) {
1450  for(end = 0; end < 2; ++end) {
1451  // ignore already attached clusters
1452  if(cls[ipl][icl].VtxIndex[end] >= 0) continue;
1453  ibstvx = -1;
1454  best = 1.;
1455  // index of the other end
1456  oend = 1 - end;
1457  for(ivx = 0; ivx < vtx.size(); ++ ivx) {
1458  // ignore if the other end is attached to this vertex (which can happen with short clusters)
1459  if(cls[ipl][icl].VtxIndex[oend] == ivx) continue;
1460  dWire = geom->WireCoordinate(vtx[ivx].Y, vtx[ivx].Z, ipl, tpc, cstat) - cls[ipl][icl].Wire[end];
1461  /*
1462  if(prt) std::cout<<"FMV: ipl "<<ipl<<" icl "<<icl<<" end "<<end
1463  <<" vtx wire "<<geom->WireCoordinate(vtx[ivx].Y, vtx[ivx].Z, ipl, tpc, cstat)
1464  <<" cls wire "<<cls[ipl][icl].Wire[end]
1465  <<" dWire "<<dWire<<"\n";
1466  */
1467  if(end == 0) {
1468  if(dWire > 30 || dWire < -2) continue;
1469  } else {
1470  if(dWire < -30 || dWire > 2) continue;
1471  }
1472  // project the cluster to the vertex wire
1473  dX = fabs(cls[ipl][icl].X[end] + cls[ipl][icl].Slope[end] * fWirePitch * dWire - vtx[ivx].X);
1474  // if(prt) std::cout<<"dX "<<dX<<"\n";
1475  if(dX < best) {
1476  best = dX;
1477  ibstvx = ivx;
1478  }
1479  } // ivx
1480  if(ibstvx >= 0) {
1481  // attach
1482  cls[ipl][icl].VtxIndex[end] = ibstvx;
1483  cls[ipl][icl].mVtxIndex[end] = ibstvx;
1484  }
1485  } // end
1486  } // icl
1487  } // ipl
1488 
1489  } // FindMaybeVertices
1490 
1492  void CCTrackMaker::MakeClusterChains(art::FindManyP<recob::Hit> const& fmCluHits)
1493  {
1494 
1495  unsigned short ipl, icl, icl1, icl2;
1496  float dw, dx, dWCut, dw1Max, dw2Max;
1497  float dA, dA2, dACut = fMaxDAng, chgAsymCut;
1498  float dXCut, chgasym, mrgErr;
1499  // long straight clusters
1500  bool ls1, ls2;
1501  bool gotprt = false;
1502  for(ipl = 0; ipl < nplanes; ++ipl) {
1503  if(cls[ipl].size() > 1) {
1504  for(icl1 = 0; icl1 < cls[ipl].size() - 1; ++icl1) {
1505  prt = (fDebugAlg == 666 && ipl == fDebugPlane && icl1 == fDebugCluster);
1506  if(prt) gotprt = true;
1507  // maximum delta Wire overlap is 10% of the total length
1508  dw1Max = 0.6 * cls[ipl][icl1].Length;
1509  ls1 = (cls[ipl][icl1].Length > 100 && fabs(cls[ipl][icl1].Angle[0] - cls[ipl][icl1].Angle[1]) < 0.04);
1510  for(icl2 = icl1 + 1; icl2 < cls[ipl].size(); ++icl2) {
1511  ls2 = (cls[ipl][icl2].Length > 100 && fabs(cls[ipl][icl2].Angle[0] - cls[ipl][icl2].Angle[1]) < 0.04);
1512  dw2Max = 0.6 * cls[ipl][icl2].Length;
1513  // set overlap cut to be the shorter of the two
1514  dWCut = dw1Max;
1515  if(dw2Max < dWCut) dWCut = dw2Max;
1516  // but not exceeding 20 for very long clusters
1517  if(dWCut > 100) dWCut = 100;
1518  if(dWCut < 2) dWCut = 2;
1519  chgAsymCut = fMergeChgAsym;
1520  // Compare end 1 of icl1 with end 0 of icl2
1521 
1522  if(prt) mf::LogVerbatim("CCTM")<<"MCC P:C:W icl1 "<<ipl<<":"<<icl1<<":"<<cls[ipl][icl1].Wire[1]<<" vtx "<<cls[ipl][icl1].VtxIndex[1]<<" ls1 "<<ls1<<" icl2 "<<ipl<<":"<<icl2<<":"<<cls[ipl][icl2].Wire[0]<<" vtx "<<cls[ipl][icl2].VtxIndex[0]<<" ls2 "<<ls2<<" dWCut "<<dWCut;
1523  if(std::abs(cls[ipl][icl1].Wire[1] - cls[ipl][icl2].Wire[0]) > dWCut) continue;
1524  // ignore if the clusters begin/end on the same wire
1525 // if(cls[ipl][icl1].Wire[1] == cls[ipl][icl2].Wire[0]) continue;
1526  // or if the angle exceeds the cut
1527  float af = AngleFactor(cls[ipl][icl1].Slope[1]);
1528  dACut = fMaxDAng * af;
1529  dXCut = fChainMaxdX * 5 * af;
1530  dA = fabs(cls[ipl][icl1].Angle[1] - cls[ipl][icl2].Angle[0]);
1531  // compare the match angle at the opposite ends of the clusters.
1532  // May have a bad end/begin angle if there is a delta-ray in the middle
1533  dA2 = fabs(cls[ipl][icl1].Angle[0] - cls[ipl][icl2].Angle[1]);
1534 
1535  if(prt) mf::LogVerbatim("CCTM")<<" dA "<<dA<<" dA2 "<<dA2<<" DACut "<<dACut<<" dXCut "<<dXCut;
1536 
1537  if(dA2 < dA) dA = dA2;
1538  // ignore vertices that have only two associated clusters and
1539  // the angle is small
1540  if(dA < fChainVtxAng && cls[ipl][icl1].VtxIndex[1] >= 0) {
1541  dw = fWirePitch * (cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1]);
1542  dx = cls[ipl][icl1].X[1] + cls[ipl][icl1].Slope[1] * dw * fWirePitch - cls[ipl][icl2].X[0];
1543  unsigned short ivx = cls[ipl][icl1].VtxIndex[1];
1544  if(vtx[ivx].nClusInPln[ipl] == 2 && fabs(dx) < 1) {
1545  cls[ipl][icl1].VtxIndex[1] = -2;
1546  cls[ipl][icl2].VtxIndex[0] = -2;
1547  vtx[ivx].nClusInPln[ipl] = 0;
1548  if(prt) mf::LogVerbatim("CCTM")<<" clobbered vertex "<<ivx;
1549  } // vertices match
1550  } // dA < 0.1 && ...
1551 
1552  // don't merge if a vertex exists at these ends
1553  if(cls[ipl][icl1].VtxIndex[1] >= 0) continue;
1554  if(cls[ipl][icl2].VtxIndex[0] >= 0) continue;
1555 
1556  // expand the angle match cut for clusters that appear to be stopping
1557  if(cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1] < 3 &&
1558  (cls[ipl][icl1].Length < 3 || cls[ipl][icl2].Length < 3) ) {
1559  if(prt) mf::LogVerbatim("CCTM")<<"Stopping cluster";
1560  dACut *= 1.5;
1561  chgAsymCut *= 1.5;
1562  dXCut *= 3;
1563  } // stopping US cluster
1564 
1565  // find the angle made by the endpoints of the clusters
1566  dw = fWirePitch * (cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1]);
1567  if(dw != 0) {
1568  dx = cls[ipl][icl2].X[0] - cls[ipl][icl1].X[1];
1569  float dA3 = std::abs(atan(dx / dw) - cls[ipl][icl1].Angle[1]);
1570  if(prt) mf::LogVerbatim("CCTM")<<" dA3 "<<dA3;
1571  if(dA3 > dA) dA = dA3;
1572  }
1573 
1574  // angle matching
1575  if(dA > dACut) continue;
1576 
1577  if(prt) mf::LogVerbatim("CCTM")<<" rough dX "<<fabs(cls[ipl][icl1].X[1] - cls[ipl][icl2].X[0])<<" cut = 20";
1578 
1579  // make a rough dX cut
1580  if(fabs(cls[ipl][icl1].X[1] - cls[ipl][icl2].X[0]) > 20) continue;
1581 
1582  // handle cosmic ray clusters that are broken at delta rays
1583  if(ls1 || ls2) {
1584  // tighter angle cuts but no charge cut
1585  if(dA > fChainVtxAng) continue;
1586  } else {
1587  chgasym = fabs(cls[ipl][icl1].Charge[1] - cls[ipl][icl2].Charge[0]);
1588  chgasym /= cls[ipl][icl1].Charge[1] + cls[ipl][icl2].Charge[0];
1589  if(prt) mf::LogVerbatim("CCTM")<<" chgasym "<<chgasym<<" cut "<<chgAsymCut;
1590  if(chgasym > chgAsymCut) continue;
1591  } // ls1 || ls2
1592  // project the longer cluster to the end of the shorter one
1593  if(cls[ipl][icl1].Length > cls[ipl][icl2].Length) {
1594  dw = fWirePitch * (cls[ipl][icl2].Wire[0] - cls[ipl][icl1].Wire[1]);
1595  dx = cls[ipl][icl1].X[1] + cls[ipl][icl1].Slope[1] * dw * fWirePitch - cls[ipl][icl2].X[0];
1596  } else {
1597  dw = fWirePitch * (cls[ipl][icl1].Wire[1] - cls[ipl][icl2].Wire[0]);
1598  dx = cls[ipl][icl2].X[0] + cls[ipl][icl2].Slope[0] * dw * fWirePitch - cls[ipl][icl1].X[1];
1599  }
1600 
1601  // handle overlapping clusters
1602  if(dA2 < 0.01 && abs(dx) > dXCut && dx < -1) {
1603  dx = dXClTraj(fmCluHits, ipl, icl1, 1, icl2);
1604  if(prt) mf::LogVerbatim("CCTM")<<" new dx from dXClTraj "<<dx;
1605  }
1606 
1607  if(prt) mf::LogVerbatim("CCTM")<<" X0 "<<cls[ipl][icl1].X[1]<<" slp "<<cls[ipl][icl1].Slope[1]<<" dw "<<dw<<" oX "<<cls[ipl][icl2].X[0]<<" dx "<<dx<<" cut "<<dXCut;
1608 
1609  if(fabs(dx) > dXCut) continue;
1610 
1611  // calculate a merge error that will be used to adjudicate between multiple merge attempts AngleFactor
1612  float xerr = dx / dXCut;
1613  float aerr = dA / dACut;
1614  mrgErr = xerr * xerr + aerr * aerr;
1615 
1616  if(prt) mf::LogVerbatim("CCTM")<<"icl1 mrgErr "<<mrgErr<<" MergeError "<<cls[ipl][icl1].MergeError[1]<<" icl2 MergeError "<<cls[ipl][icl2].MergeError[0];
1617 
1618  // this merge better than a previous one?
1619  if(mrgErr > cls[ipl][icl1].MergeError[1]) continue;
1620  if(mrgErr > cls[ipl][icl2].MergeError[0]) continue;
1621 
1622  // un-merge icl1 - this should always be true but check anyway
1623  if(cls[ipl][icl1].BrkIndex[1] >= 0) {
1624  unsigned short ocl = cls[ipl][icl1].BrkIndex[1];
1625  if(prt) mf::LogVerbatim("CCTM")<<"clobber old icl1 BrkIndex "<<ocl;
1626  if(cls[ipl][ocl].BrkIndex[0] == icl1) {
1627  cls[ipl][ocl].BrkIndex[0] = -1;
1628  cls[ipl][ocl].MergeError[0] = fMaxMergeError;
1629  }
1630  if(cls[ipl][ocl].BrkIndex[1] == icl1) {
1631  cls[ipl][ocl].BrkIndex[1] = -1;
1632  cls[ipl][ocl].MergeError[1] = fMaxMergeError;
1633  }
1634  } // cls[ipl][icl1].BrkIndex[1] >= 0
1635  cls[ipl][icl1].BrkIndex[1] = icl2;
1636  cls[ipl][icl1].MergeError[1] = mrgErr;
1637 
1638  // un-merge icl2
1639  if(cls[ipl][icl2].BrkIndex[0] >= 0) {
1640  unsigned short ocl = cls[ipl][icl2].BrkIndex[0];
1641  if(prt) mf::LogVerbatim("CCTM")<<"clobber old icl2 BrkIndex "<<ocl;
1642  if(cls[ipl][ocl].BrkIndex[0] == icl1) {
1643  cls[ipl][ocl].BrkIndex[0] = -1;
1644  cls[ipl][ocl].MergeError[0] = fMaxMergeError;
1645  }
1646  if(cls[ipl][ocl].BrkIndex[1] == icl1) {
1647  cls[ipl][ocl].BrkIndex[1] = -1;
1648  cls[ipl][ocl].MergeError[1] = fMaxMergeError;
1649  }
1650  } // cls[ipl][icl2].BrkIndex[0] >= 0
1651  cls[ipl][icl2].BrkIndex[0] = icl1;
1652  cls[ipl][icl2].MergeError[0] = mrgErr;
1653  if(prt) mf::LogVerbatim("CCTM")<<" merge "<<icl1<<" and "<<icl2;
1654 
1655  } // icl2
1656  } // icl1
1657 
1658  // look for broken clusters in which have a C shape similar to a Begin-Begin vertex. The clusters
1659  // will have large and opposite sign angles
1660  bool gotone;
1661  for(icl1 = 0; icl1 < cls[ipl].size() - 1; ++icl1) {
1662  gotone = false;
1663  for(icl2 = icl1 + 1; icl2 < cls[ipl].size(); ++icl2) {
1664  // check both ends
1665  for(unsigned short end = 0; end < 2; ++end) {
1666  // Ignore already identified broken clusters
1667  if(cls[ipl][icl1].BrkIndex[end] >= 0) continue;
1668  if(cls[ipl][icl2].BrkIndex[end] >= 0) continue;
1669 // if(prt) mf::LogVerbatim("CCTM")<<"BrokenC: clusters "<<cls[ipl][icl1].Wire[end]<<":"<<(int)cls[ipl][icl1].Time[end]<<" "<<cls[ipl][icl2].Wire[end]<<":"<<(int)cls[ipl][icl2].Time[end]<<" angles "<<cls[ipl][icl1].Angle[end]<<" "<<cls[ipl][icl2].Angle[end];
1670  // require a large angle cluster
1671  if(fabs(cls[ipl][icl1].Angle[end]) < 1) continue;
1672  // and a second large angle cluster
1673  if(fabs(cls[ipl][icl2].Angle[end]) < 1) continue;
1674  if(prt) mf::LogVerbatim("CCTM")<<"BrokenC: clusters "<<cls[ipl][icl1].Wire[end]<<":"<<(int)cls[ipl][icl1].Time[end]<<" "<<cls[ipl][icl2].Wire[end]<<":"<<(int)cls[ipl][icl2].Time[end]<<" angles "<<cls[ipl][icl1].Angle[end]<<" "<<cls[ipl][icl2].Angle[end]<<" dWire "<<fabs(cls[ipl][icl1].Wire[end] - cls[ipl][icl2].Wire[end]);
1675  if(fabs(cls[ipl][icl1].Wire[end] - cls[ipl][icl2].Wire[end]) > 5) continue;
1676  // This is really crude but maybe OK
1677  // project 1 -> 2
1678  float dsl = cls[ipl][icl2].Slope[end] - cls[ipl][icl1].Slope[end];
1679  float fvw = (cls[ipl][icl1].X[end] - cls[ipl][icl1].Wire[end] * cls[ipl][icl1].Slope[end] - cls[ipl][icl2].X[end] + cls[ipl][icl2].Wire[end] * cls[ipl][icl2].Slope[end]) / dsl;
1680  if(prt) mf::LogVerbatim("CCTM")<<" fvw "<<fvw;
1681  if(fabs(cls[ipl][icl1].Wire[end] - fvw) > 4) continue;
1682  if(fabs(cls[ipl][icl2].Wire[end] - fvw) > 4) continue;
1683  cls[ipl][icl1].BrkIndex[end] = icl2;
1684  // TODO This could use some improvement if necessary
1685  cls[ipl][icl1].MergeError[end] = 1;
1686  cls[ipl][icl2].BrkIndex[end] = icl1;
1687  cls[ipl][icl2].MergeError[end] = 1;
1688  gotone = true;
1689  dx = fabs(cls[ipl][icl1].X[end] - cls[ipl][icl2].X[end]);
1690  if(prt) mf::LogVerbatim("CCTM")<<"BrokenC: icl1:W "<<icl1<<":"<<cls[ipl][icl1].Wire[end]<<" icl2:W "<<icl2<<":"<<cls[ipl][icl2].Wire[end]<<" end "<<end<<" dx "<<dx;
1691  } // end
1692  if(gotone) break;
1693  } // icl2
1694  } // icl1
1695 
1696  } // cls[ipl].size() > 1
1697 
1698  // follow mother-daughter broken clusters and put them in the cluster chain array
1699  unsigned short end, mom, momBrkEnd, dtrBrkEnd, nit;
1700  short dtr;
1701 
1702  std::vector<bool> gotcl(cls[ipl].size());
1703  for(icl = 0; icl < cls[ipl].size(); ++icl) gotcl[icl] = false;
1704  if(prt) mf::LogVerbatim("CCTM")<<"ipl "<<ipl<<" cls.size() "<<cls[ipl].size()<<"\n";
1705 
1706  std::vector<unsigned short> sCluster;
1707  std::vector<unsigned short> sOrder;
1708  for(icl = 0; icl < cls[ipl].size(); ++icl) {
1709  sCluster.clear();
1710  sOrder.clear();
1711  if(gotcl[icl]) continue;
1712  // don't start with a cluster broken at both ends
1713  if(cls[ipl][icl].BrkIndex[0] >= 0 && cls[ipl][icl].BrkIndex[1] >= 0) continue;
1714  for(end = 0; end < 2; ++end) {
1715  if(cls[ipl][icl].BrkIndex[end] < 0) continue;
1716  if(cls[ipl][icl].MergeError[end] > fMergeErrorCut) continue;
1717  gotcl[icl] = true;
1718  mom = icl;
1719  // end where the mom is broken
1720  momBrkEnd = end;
1721  sCluster.push_back(mom);
1722  if(momBrkEnd == 1) {
1723  // typical case - broken at the DS end
1724  sOrder.push_back(0);
1725  } else {
1726  // broken at the US end
1727  sOrder.push_back(1);
1728  }
1729  dtr = cls[ipl][icl].BrkIndex[end];
1730 // std::cout<<"Starting mom:momBrkEnd "<<mom<<":"<<momBrkEnd<<" dtr "<<dtr<<"\n";
1731  nit = 0;
1732  while(dtr >= 0 && dtr < (short)cls[ipl].size() && nit < cls[ipl].size()) {
1733  // determine which end of the dtr should be attached to mom
1734  for(dtrBrkEnd = 0; dtrBrkEnd < 2; ++dtrBrkEnd) if(cls[ipl][dtr].BrkIndex[dtrBrkEnd] == mom) break;
1735  if(dtrBrkEnd == 2) {
1736 // mf::LogError("CCTM")<<"Cant find dtrBrkEnd for cluster "<<icl<<" dtr "<<dtr<<" in plane "<<ipl;
1737  gotcl[icl] = false;
1738  break;
1739  }
1740  // check for reasonable merge error
1741  if(cls[ipl][dtr].MergeError[dtrBrkEnd] < fMergeErrorCut) {
1742  sCluster.push_back(dtr);
1743  sOrder.push_back(dtrBrkEnd);
1744  gotcl[dtr] = true;
1745  }
1746 // std::cout<<" dtr:dtrBrkEnd "<<dtr<<":"<<dtrBrkEnd<<" momBrkEnd "<<momBrkEnd<<"\n";
1747  ++nit;
1748  // set up to check the new mom
1749  mom = dtr;
1750  // at the other end
1751  momBrkEnd = 1 - dtrBrkEnd;
1752  // with the new dtr
1753  dtr = cls[ipl][mom].BrkIndex[momBrkEnd];
1754 // std::cout<<" new mom:momBrkEnd "<<mom<<":"<<momBrkEnd<<" new dtr "<<dtr<<"\n";
1755  } // dtr >= 0 ...
1756  if(dtrBrkEnd == 2) continue;
1757  } // end
1758 
1759  if(!gotcl[icl]) {
1760  // a single unbroken cluster
1761  sCluster.push_back(icl);
1762  sOrder.push_back(0);
1763  }
1764 
1765  if(sCluster.size() == 0) {
1766  mf::LogError("CCTM")<<"MakeClusterChains error in plane "<<ipl<<" cluster "<<icl;
1767  return;
1768  }
1769 /*
1770  std::cout<<ipl<<" icl "<<icl<<" sCluster ";
1771  for(unsigned short ii = 0; ii < sCluster.size(); ++ii) std::cout<<" "<<sCluster[ii]<<":"<<sOrder[ii];
1772  std::cout<<"\n";
1773 */
1774  ClsChainPar ccp;
1775  // fill the struct parameters assuming this is a cluster chain containing only one cluster
1776  unsigned short jcl = sCluster[0];
1777  if(jcl > cls[ipl].size()) std::cout<<"oops MCC\n";
1778  unsigned short oend;
1779  for(end = 0; end < 2; ++end) {
1780  oend = end;
1781  if(sOrder[0] > 0) oend = 1 - end;
1782  ccp.Wire[end] = cls[ipl][jcl].Wire[oend];
1783  ccp.Time[end] = cls[ipl][jcl].Time[oend];
1784  ccp.X[end] = cls[ipl][jcl].X[oend];
1785  ccp.Slope[end] = cls[ipl][jcl].Slope[oend];
1786  ccp.Angle[end] = cls[ipl][jcl].Angle[oend];
1787  ccp.Dir[end] = cls[ipl][icl].Dir[oend];
1788  ccp.VtxIndex[end] = cls[ipl][jcl].VtxIndex[oend];
1789  ccp.ChgNear[end] = cls[ipl][jcl].ChgNear[oend];
1790  ccp.mBrkIndex[end] = cls[ipl][jcl].BrkIndex[oend];
1791  } // end
1792  ccp.Length = cls[ipl][icl].Length;
1793  ccp.TotChg = cls[ipl][icl].TotChg;
1794  ccp.InTrack = -1;
1795 // std::cout<<"sOrder0 "<<sOrder[0]<<" "<<(int)ccp.Wire[0]<<":"<<(int)ccp.Time[0]<<" dir "<<ccp.Dir[0]<<" "<<(int)ccp.Wire[1]<<":"<<(int)ccp.Time[1]<<" dir "<<ccp.Dir[1]<<"\n";
1796 
1797  for(unsigned short ii = 1; ii < sCluster.size(); ++ii) {
1798  jcl = sCluster[ii];
1799  if(jcl > cls[ipl].size()) std::cout<<"oops MCC\n";
1800  // end is the end where the break is being mended
1801  end = sOrder[ii];
1802  if(end > 1) std::cout<<"oops2 MCC\n";
1803  oend = 1 - end;
1804  // update the parameters at the other end of the chain
1805  ccp.Wire[1] = cls[ipl][jcl].Wire[oend];
1806  ccp.Time[1] = cls[ipl][jcl].Time[oend];
1807  ccp.X[1] = cls[ipl][jcl].X[oend];
1808  ccp.Slope[1] = cls[ipl][jcl].Slope[oend];
1809  ccp.Angle[1] = cls[ipl][jcl].Angle[oend];
1810  ccp.Dir[1] = cls[ipl][jcl].Dir[oend];
1811  ccp.VtxIndex[1] = cls[ipl][jcl].VtxIndex[oend];
1812  ccp.ChgNear[1] = cls[ipl][jcl].ChgNear[oend];
1813  ccp.mBrkIndex[1] = cls[ipl][jcl].BrkIndex[oend];
1814  ccp.Length += cls[ipl][jcl].Length;
1815  ccp.TotChg += cls[ipl][jcl].TotChg;
1816 // std::cout<<"Update "<<end<<" "<<(int)ccp.Wire[0]<<":"<<(int)ccp.Time[0]<<" dir "<<ccp.Dir[0]<<" "<<(int)ccp.Wire[1]<<":"<<(int)ccp.Time[1]<<" dir "<<ccp.Dir[1]<<"\n";
1817  } // ii
1818  ccp.ClsIndex = sCluster;
1819  ccp.Order = sOrder;
1820  // redo the direction
1821  if(ccp.Time[1] > ccp.Time[0]) {
1822  ccp.Dir[0] = 1; ccp.Dir[1] = -1;
1823  } else {
1824  ccp.Dir[0] = -1; ccp.Dir[1] = 1;
1825  }
1826  clsChain[ipl].push_back(ccp);
1827 
1828  } // icl
1829 
1830  // re-index mBrkIndex to point to a cluster chain, not a cluster
1831  unsigned short brkCls;
1832  bool gotit;
1833  for(unsigned short ccl = 0; ccl < clsChain[ipl].size(); ++ccl) {
1834  for(unsigned short end = 0; end < 2; ++end) {
1835  if(clsChain[ipl][ccl].mBrkIndex[end] < 0) continue;
1836  brkCls = clsChain[ipl][ccl].mBrkIndex[end];
1837  gotit = false;
1838  // find this cluster index in a cluster chain
1839  for(unsigned short ccl2 = 0; ccl2 < clsChain[ipl].size(); ++ccl2) {
1840  if(ccl2 == ccl) continue;
1841  if(std::find(clsChain[ipl][ccl2].ClsIndex.begin(), clsChain[ipl][ccl2].ClsIndex.end(), brkCls) == clsChain[ipl][ccl2].ClsIndex.end()) continue;
1842  // found it
1843  clsChain[ipl][ccl].mBrkIndex[end] = ccl2;
1844  gotit = true;
1845  break;
1846  } // ccl2
1847 // if(gotit) std::cout<<"gotit ccl "<<ccl<<" end "<<end<<" new mBrkIndex "<<clsChain[ipl][ccl].mBrkIndex[end]<<"\n";
1848  if(!gotit) mf::LogError("CCTM")<<"MCC: Cluster chain "<<ccl<<" end "<<end<<" Failed to find brkCls "<<brkCls<<" in plane "<<ipl;
1849  } // end
1850  } // ccl
1851 
1852  } // ipl
1853 
1854 
1855  if(gotprt) PrintClusters();
1856  prt = false;
1857 
1858  } // MakeClusterChains
1859 
1861  float CCTrackMaker::dXClTraj(art::FindManyP<recob::Hit> const& fmCluHits, unsigned short ipl, unsigned short icl1, unsigned short end1, unsigned short icl2)
1862  {
1863  // project cluster icl1 at end1 to find the best intersection with icl2
1864  float dw, dx, best = 999;
1865  std::vector<art::Ptr<recob::Hit>> clusterhits = fmCluHits.at(cls[ipl][icl1].EvtIndex);
1866  for(unsigned short hit = 0; hit < clusterhits.size(); ++hit) {
1867  dw = clusterhits[hit]->WireID().Wire - cls[ipl][icl1].Wire[end1];
1868  dx = fabs(cls[ipl][icl1].Time[end1] + dw * fWirePitch * cls[ipl][icl1].Slope[end1] - clusterhits[hit]->PeakTime());
1869  if(dx < best) best = dx;
1870  if(dx < 0.01) break;
1871  } // hit
1872  return best;
1873  } // dXClTraj
1874 
1876  void CCTrackMaker::StoreTrack(art::FindManyP<recob::Hit> const& fmCluHits,
1877  unsigned short imat, unsigned short procCode)
1878  {
1879  // store the current "under construction" track in the trk vector
1880 
1881  TrkPar newtrk;
1882 
1883  if(imat > matcomb.size() - 1) {
1884  mf::LogError("CCTM")<<"Bad imat in StoreTrack";
1885  return;
1886  }
1887 
1888  // ensure there are at least 2 hits in at least 2 planes
1889  unsigned short nhitinpl = 0;
1890  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) if(trkHits[ipl].size() > 1) ++nhitinpl;
1891  if(nhitinpl < 2) {
1892  mf::LogError("CCTM")<<"StoreTrack: Not enough hits in each plane\n";
1893  return;
1894  }
1895  if(prt) mf::LogVerbatim("CCTM")<<"In StoreTrack: matcomb "<<imat<<" cluster chains "<<matcomb[imat].Cls[0]<<" "<<matcomb[imat].Cls[1]<<" "<<matcomb[imat].Cls[2];
1896 
1897  // Track hit vectors for fitting the trajectory
1898  std::array<std::vector<geo::WireID>,3> trkWID;
1899  std::array<std::vector<double>,3> trkX;
1900  std::array<std::vector<double>,3> trkXErr;
1901 
1902  // track trajectory for a track
1903  std::vector<TVector3> trkPos;
1904  std::vector<TVector3> trkDir;
1905 
1906  newtrk.ID = trk.size() + 1;
1907  newtrk.Proc = procCode;
1908  newtrk.TrkHits = trkHits;
1909  // BUG the double brace syntax is required to work around clang bug 21629
1910  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
1911  newtrk.VtxIndex = {{-1, -1}};
1912  newtrk.ChgOrder = 0;
1913  newtrk.MomID = -1;
1914  // BUG the double brace syntax is required to work around clang bug 21629
1915  // (https://bugs.llvm.org/show_bug.cgi?id=21629)
1916  newtrk.EndInTPC = {{false, false}};
1917  newtrk.GoodEnd = {{false, false}};
1918  newtrk.DtrID = {0};
1919  newtrk.PDGCode = -1;
1920 
1921  unsigned short ipl, icl, iht;
1922 
1923  if(prt) mf::LogVerbatim("CCTM")<<"CCTM: Make traj for track "<<newtrk.ID<<" procCode "<<procCode<<" nhits in planes "<<trkHits[0].size()<<" "<<trkHits[1].size()<<" "<<trkHits[2].size();
1924  // make the track trajectory
1925  if(nplanes == 2) {
1926  trkWID[2].resize(0);
1927  trkX[2].resize(0);
1928  trkXErr[2].resize(0);
1929  }
1930  for(ipl = 0; ipl < nplanes; ++ipl) {
1931  trkWID[ipl].resize(trkHits[ipl].size());
1932  trkX[ipl].resize(trkHits[ipl].size());
1933  trkXErr[ipl].resize(trkHits[ipl].size());
1934  for(iht = 0; iht < trkHits[ipl].size(); ++iht) {
1935  trkWID[ipl][iht] = trkHits[ipl][iht]->WireID();
1936  trkX[ipl][iht] = detprop->ConvertTicksToX(trkHits[ipl][iht]->PeakTime(),ipl, tpc, cstat);
1937  trkXErr[ipl][iht] = fHitFitErrFac * trkHits[ipl][iht]->RMS() * trkHits[ipl][iht]->Multiplicity();
1938 // std::cout<<iht<<" "<<trkWID[ipl][iht]<<" "<<trkX[ipl][iht]<<" "<<trkXErr[ipl][iht]<<"\n";
1939  } // iht
1940  } // ipl
1941  fTrackTrajectoryAlg.TrackTrajectory(trkWID, trkX, trkXErr, trkPos, trkDir);
1942  if(trkPos.size() < 2) {
1943  mf::LogError("CCTM")<<"StoreTrack: No trajectory points on failed track "<<newtrk.ID
1944  <<" in StoreTrack: matcomb "<<imat<<" cluster chains "<<matcomb[imat].Cls[0]<<" "<<matcomb[imat].Cls[1]<<" "<<matcomb[imat].Cls[2];
1945  // make a garbage trajectory
1946  trkPos.resize(2);
1947  trkPos[1](2) = 1;
1948  trkDir.resize(2);
1949  trkDir[1](2) = 1;
1950  }
1951  newtrk.TrjPos = trkPos;
1952  newtrk.TrjDir = trkDir;
1953 
1954  if(prt) mf::LogVerbatim("CCTM")<<" number of traj points "<<trkPos.size();
1955 
1956  // determine if each end is good in the sense that there are hits in each plane
1957  // that are consistent in time and are presumed to form a good 3D space point
1958  unsigned short end, nClose, indx, jndx;
1959  float xErr;
1960  for(end = 0; end < 2; ++end) {
1961  nClose = 0;
1962  for(ipl = 0; ipl < nplanes - 1; ++ipl) {
1963  if(trkX[ipl].size() == 0) continue;
1964  for(unsigned short jpl = ipl + 1; jpl < nplanes; ++jpl) {
1965  if(trkX[jpl].size() == 0) continue;
1966  if(end == 0) {
1967  indx = 0;
1968  jndx = 0;
1969  } else {
1970  indx = trkXErr[ipl].size() - 1;
1971  jndx = trkXErr[jpl].size() - 1;
1972  }
1973  xErr = 3 * (trkXErr[ipl][indx] + trkXErr[jpl][jndx]);
1974  if(std::abs(trkX[ipl][indx] - trkX[jpl][jndx]) < xErr) ++nClose;
1975  } // jpl
1976  } // ipl
1977  if(nClose == nplanes) newtrk.GoodEnd[end] = true;
1978  } // end
1979 
1980  // set trajectory end points to a vertex if one exists
1981  unsigned short ivx, itj, ccl;
1982  float dx, dy, dz, dr0, dr1;
1983  unsigned short attachEnd;
1984  for(end = 0; end < 2; ++end) {
1985  ivx = USHRT_MAX;
1986  if(end == 0 && matcomb[imat].Vtx >= 0) ivx = matcomb[imat].Vtx;
1987  if(end == 1 && matcomb[imat].oVtx >= 0) ivx = matcomb[imat].oVtx;
1988  if(ivx == USHRT_MAX) continue;
1989  // determine the proper end using the TrjPos order and brute force
1990  itj = 0;
1991  dx = vtx[ivx].X - newtrk.TrjPos[itj](0);
1992  dy = vtx[ivx].Y - newtrk.TrjPos[itj](1);
1993  dz = vtx[ivx].Z - newtrk.TrjPos[itj](2);
1994  dr0 = dx*dx + dy*dy + dz*dz;
1995  itj = newtrk.TrjPos.size() - 1;
1996  dx = vtx[ivx].X - newtrk.TrjPos[itj](0);
1997  dy = vtx[ivx].Y - newtrk.TrjPos[itj](1);
1998  dz = vtx[ivx].Z - newtrk.TrjPos[itj](2);
1999  dr1 = dx*dx + dy*dy + dz*dz;
2000  attachEnd = 1;
2001  if(dr0 < dr1) {
2002  itj = 0;
2003  attachEnd = 0;
2004  // a really bad match to the vertex
2005  if(dr0 > 5) return;
2006  } else {
2007  // a really bad match to the vertex
2008  if(dr1 > 5) return;
2009  }
2010  newtrk.TrjPos[itj](0) = vtx[ivx].X;
2011  newtrk.TrjPos[itj](1) = vtx[ivx].Y;
2012  newtrk.TrjPos[itj](2) = vtx[ivx].Z;
2013  newtrk.VtxIndex[attachEnd] = ivx;
2014  // correct the trajectory direction
2015  TVector3 dir;
2016  if(itj == 0) {
2017  dir = newtrk.TrjPos[1] - newtrk.TrjPos[0];
2018  newtrk.TrjDir[0] = dir.Unit();
2019  } else {
2020  dir = newtrk.TrjPos[itj - 1] - newtrk.TrjPos[itj];
2021  newtrk.TrjDir[itj] = dir.Unit();
2022  }
2023  } // end
2024 
2025  if(newtrk.VtxIndex[0] >= 0 && newtrk.VtxIndex[0] == newtrk.VtxIndex[1]) {
2026  mf::LogError("CCTM")<<"StoreTrack: Trying to attach a vertex to both ends of a track. imat = "<<imat;
2027  return;
2028  }
2029 
2030  // calculate the length
2031  newtrk.Length = 0;
2032  float norm;
2033  double X, Y, Z;
2034  for(unsigned short itj = 1; itj < newtrk.TrjPos.size(); ++itj) {
2035  X = newtrk.TrjPos[itj](0) - newtrk.TrjPos[itj-1](0);
2036  Y = newtrk.TrjPos[itj](1) - newtrk.TrjPos[itj-1](1);
2037  Z = newtrk.TrjPos[itj](2) - newtrk.TrjPos[itj-1](2);
2038  norm = sqrt(X*X + Y*Y + Z*Z);
2039  newtrk.Length += norm;
2040  }
2041 
2042  // store the cluster -> track assignment
2043  newtrk.ClsEvtIndices.clear();
2044  for(ipl = 0; ipl < nplanes; ++ipl) {
2045  if(matcomb[imat].Cls[ipl] < 0) continue;
2046  ccl = matcomb[imat].Cls[ipl];
2047  if(ccl > clsChain[ipl].size()) std::cout<<"oops StoreTrack\n";
2048  clsChain[ipl][ccl].InTrack = newtrk.ID;
2049  for(unsigned short icc = 0; icc < clsChain[ipl][ccl].ClsIndex.size(); ++icc) {
2050  icl = clsChain[ipl][ccl].ClsIndex[icc];
2051  if(icl > cls[ipl].size()) std::cout<<"oops StoreTrack\n";
2052  cls[ipl][icl].InTrack = newtrk.ID;
2053  if(cls[ipl][icl].EvtIndex > fmCluHits.size() - 1) {
2054  std::cout<<"ooops2 store track EvtIndex "<<cls[ipl][icl].EvtIndex<<" size "<<fmCluHits.size()<<" icl "<<icl<<"\n";
2055  continue;
2056  }
2057  newtrk.ClsEvtIndices.push_back(cls[ipl][icl].EvtIndex);
2058  } // icc
2059  } // ipl
2060 
2061  if(prt) mf::LogVerbatim("CCTM")<<" track ID "<<newtrk.ID<<" stored in StoreTrack";
2062 
2063  trk.push_back(newtrk);
2064  } // StoreTrack
2065 /*
2067  void CCTrackMaker::AngMatch(art::FindManyP<recob::Hit> const& fmCluHits)
2068  {
2069  // look for long unused cluster chains and match them using angle
2070  std::array<std::vector<unsigned short>, 3> ccUnused;
2071 
2072  unsigned short ipl, icc, jpl, kpl, ii, jj, kk, icl, jcl, kcl, iend, jend, kend;
2073  short idir, jdir, kdir;
2074  float islp = 0, jslp = 0, kslp = 0, kAng = 0, sigmaA = 0, matchErr = 0;
2075 
2076  prt = (fDebugPlane >= 0);
2077 
2078  for(ipl = 0; ipl < nplanes; ++ipl) {
2079  for(icc = 0; icc < clsChain[ipl].size(); ++icc) {
2080  if(clsChain[ipl][icc].InTrack < 0 && clsChain[ipl][icc].Length > fAngMatchMinLen) ccUnused[ipl].push_back(icc);
2081  } // icc
2082  if(prt) {
2083  mf::LogVerbatim myprt("CCTM");
2084  myprt<<"AngMatch: ipl "<<ipl<<" ccUnused";
2085  for(icc = 0; icc < ccUnused[ipl].size(); ++icc) myprt<<" "<<ccUnused[ipl][icc];;
2086  }
2087  } // ipl
2088 
2089 
2090  if(ccUnused[0].size() == 0 && ccUnused[1].size() == 0 && ccUnused[2].size() == 0) return;
2091 
2092  std::array<float, 3> mchg;
2093  matcomb.clear();
2094 
2095  float xMatchWght = 1;
2096  if(fuBCode) xMatchWght = 0.1;
2097 
2098  for(ipl = 0; ipl < nplanes; ++ipl) {
2099  jpl = (ipl + 1) % nplanes;
2100  kpl = (jpl + 1) % nplanes;
2101  for(ii = 0; ii < ccUnused[ipl].size(); ++ii) {
2102  icl = ccUnused[ipl][ii];
2103  prt = (ipl == fDebugPlane && icl == fDebugCluster);
2104  for(jj = 0; jj < ccUnused[jpl].size(); ++jj) {
2105  jcl = ccUnused[jpl][jj];
2106  // make first charge asymmetry cut
2107  mchg[0] = clsChain[ipl][icl].TotChg;
2108  mchg[1] = clsChain[jpl][jcl].TotChg;
2109  mchg[2] = mchg[1];
2110  if(prt) mf::LogVerbatim("CCTM")<<"AngMatch: ipl:icl "<<ipl<<":"<<icl<<" jpl:jcl "<<jpl<<":"<<jcl<<" chg asym"<<ChargeAsym(mchg);
2111  if(ChargeAsym(mchg) > 0.5) continue;
2112  kslp = geom->ThirdPlaneSlope(ipl, islp, jpl, jslp, tpc, cstat);
2113  for(kk = 0; kk < ccUnused[kpl].size(); ++kk) {
2114  kcl = ccUnused[kpl][kk];
2115  // make second charge asymmetry cut
2116  mchg[0] = clsChain[ipl][icl].TotChg;
2117  mchg[1] = clsChain[jpl][jcl].TotChg;
2118  mchg[2] = clsChain[kpl][kcl].TotChg;
2119  if(!fuBCode && ChargeAsym(mchg) > 0.5) continue;
2120  // check the ends
2121  for(iend = 0; iend < 2; ++iend) {
2122  idir = clsChain[ipl][icl].Dir[iend];
2123  islp = clsChain[ipl][icl].Slope[iend];
2124  for(jend = 0; jend < 2; ++jend) {
2125  jdir = clsChain[jpl][jcl].Dir[jend];
2126  if(idir != 0 && jdir != 0 && idir != jdir) continue;
2127  jslp = clsChain[jpl][jcl].Slope[jend];
2128  // expected angle in kpl
2129  kslp = geom->ThirdPlaneSlope(ipl, islp, jpl, jslp, tpc, cstat);
2130  kAng = atan(kslp);
2131  sigmaA = fAngleMatchErr * AngleFactor(kslp);
2132  for(kend = 0; kend < 2; ++kend) {
2133  kdir = clsChain[kpl][kcl].Dir[kend];
2134  if(idir != 0 && kdir != 0 && idir != kdir) continue;
2135  matchErr = fabs(clsChain[kpl][kcl].Angle[kend] - kAng) / sigmaA;
2136  if(prt) mf::LogVerbatim("CCTM")<<" ipl:icl:iend "<<ipl<<":"<<icl<<":"<<iend<<" jpl:jcl:jend "<<jpl<<":"<<jcl<<":"<<jend<<" kpl:kcl:kend "<<kpl<<":"<<kcl<<":"<<kend<<" matchErr "<<matchErr;
2137  if(matchErr > 5) continue;
2138  MatchPars match;
2139  match.Cls[ipl] = icl; match.End[ipl] = iend;
2140  match.Cls[jpl] = jcl; match.End[jpl] = jend;
2141  match.Cls[kpl] = kcl; match.End[kpl] = kend;
2142  if(DupMatch(match)) continue;
2143  match.Chg[ipl] = clsChain[ipl][icl].TotChg;
2144  match.Chg[jpl] = clsChain[jpl][jcl].TotChg;
2145  match.Chg[kpl] = clsChain[kpl][kcl].TotChg;
2146  match.Vtx = -1;
2147  match.dWir = fabs(0.5 * (clsChain[ipl][icl].Wire[iend] + clsChain[jpl][jcl].Wire[jend]) - clsChain[kpl][kcl].Wire[kend]);
2148  match.dAng = fabs(clsChain[kpl][kcl].Angle[kend] - kAng);
2149  match.dX = xMatchWght * fabs(0.5 * (clsChain[ipl][icl].X[iend] + clsChain[jpl][jcl].X[jend]) - clsChain[kpl][kcl].X[kend]);
2150  if(prt) mf::LogVerbatim("CCTM")<<" match.dX "<<match.dX;
2151  if(!fuBCode && match.dX > 20) continue;
2152  if(std::abs(kAng) > 0.3 && match.dAng < 0.03) std::cout<<"match "<<ipl<<":"<<icl<<" "<<jpl<<":"<<jcl<<" "<<kpl<<":"<<kcl<<" "<<match.dAng<<"\n";
2153  // add X match error with 1 cm rms
2154 // match.Err = matchErr + match.dX;
2155  match.Err = 0.;
2156  match.oVtx = -1;
2157  match.odWir = 0;
2158  match.odAng = 0;
2159  match.odX = 0;
2160  match.oErr = 0;
2161  matcomb.push_back(match);
2162  } // kend
2163  } // jend
2164  } // iend
2165  } // kk
2166  } // jj
2167  } // ii
2168  } // ipl
2169 
2170  if(matcomb.size() == 0) return;
2171 
2172  SortMatches(fmCluHits, 3);
2173 
2174  prt = false;
2175  } // AngMatch
2176 */
2178  void CCTrackMaker::PlnMatch(art::FindManyP<recob::Hit> const& fmCluHits)
2179  {
2180  // Match clusters in all planes
2181  // unsigned short ipl, icl, jpl, jcl, kpl, kcl;
2182  bool ignoreSign;
2183  float kSlp, kAng, kX, kWir, okWir;
2184  short idir, ioend, jdir, joend, kdir;
2185 
2186  double yp, zp;
2187  float tpcSizeY = geom->DetHalfWidth();
2188  float tpcSizeZ = geom->DetLength();
2189 
2190 
2191  float dxcut = 2;
2192  float dxkcut;
2193  float dwcut = 6;
2194  if(fuBCode) {
2195  dxcut = 20;
2196  dwcut = 60;
2197  }
2198 
2199  // temp array for making a rough charge asymmetry cut
2200  std::array<float, 3> mchg;
2201 
2202  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) {
2203  for(unsigned short icl = 0; icl < clsChain[ipl].size(); ++icl) {
2204  if(clsChain[ipl][icl].InTrack >= 0) continue;
2205  // skip short clusters
2206  if(clsChain[ipl][icl].Length < fMatchMinLen[algIndex]) continue;
2207  unsigned short jpl = (ipl + 1) % nplanes;
2208  unsigned short kpl = (jpl + 1) % nplanes;
2209  for(unsigned short jcl = 0; jcl < clsChain[jpl].size(); ++jcl) {
2210  if(clsChain[jpl][jcl].InTrack >= 0) continue;
2211  // skip short clusters
2212  if(clsChain[jpl][jcl].Length < fMatchMinLen[algIndex]) continue;
2213  // make first charge asymmetry cut
2214  mchg[0] = clsChain[ipl][icl].TotChg;
2215  mchg[1] = clsChain[jpl][jcl].TotChg;
2216  mchg[2] = mchg[1];
2217  if(fChgAsymFactor[algIndex] > 0 && ChargeAsym(mchg) > 0.5) continue;
2218  for(unsigned short iend = 0; iend < 2; ++iend) {
2219  idir = clsChain[ipl][icl].Dir[iend];
2220  for(unsigned short jend = 0; jend < 2; ++jend) {
2221  jdir = clsChain[jpl][jcl].Dir[jend];
2222  if(idir != 0 && jdir != 0 && idir != jdir) continue;
2223  // make an X cut
2224  if(fabs(clsChain[ipl][icl].X[iend] - clsChain[jpl][jcl].X[jend]) > dxcut) continue;
2225  ioend = 1 - iend; joend = 1 - jend;
2226  // Find the expected third (k) plane parameters
2227  kSlp = geom->ThirdPlaneSlope(ipl, clsChain[ipl][icl].Slope[iend], jpl, clsChain[jpl][jcl].Slope[jend], tpc, cstat);
2228  kAng = atan(kSlp);
2229  // Ensure the match end is within the TPC
2230  geom->IntersectionPoint((unsigned int)(0.5+clsChain[ipl][icl].Wire[iend]),
2231  (unsigned int)(0.5+clsChain[jpl][jcl].Wire[jend]),
2232  ipl, jpl, cstat, tpc, yp, zp);
2233  if(yp > tpcSizeY || yp < -tpcSizeY) continue;
2234  if(zp < 0 || zp > tpcSizeZ) continue;
2235  kX = 0.5 * (clsChain[ipl][icl].X[iend] + clsChain[jpl][jcl].X[jend]);
2236  kWir = geom->WireCoordinate(yp, zp, kpl, tpc, cstat);
2237  // now look at the other end
2238  geom->IntersectionPoint((unsigned int)(0.5+clsChain[ipl][icl].Wire[ioend]),
2239  (unsigned int)(0.5+clsChain[jpl][jcl].Wire[joend]),
2240  ipl, jpl, cstat, tpc, yp, zp);
2241  if(yp > tpcSizeY || yp < -tpcSizeY) continue;
2242  if(zp < 0 || zp > tpcSizeZ) continue;
2243  okWir = geom->WireCoordinate(yp, zp, kpl, tpc, cstat);
2244  if(prt) mf::LogVerbatim("CCTM")<<"PlnMatch: chk i "<<ipl<<":"<<icl<<":"<<iend
2245  <<" idir "<<idir<<" X "<<clsChain[ipl][icl].X[iend]<<" j "<<jpl<<":"<<jcl<<":"<<jend
2246  <<" jdir "<<jdir<<" X "<<clsChain[jpl][jcl].X[jend];
2247 
2248  if(prt) mf::LogVerbatim("CCTM")<<"PlnMatch: chk j "<<ipl<<":"<<icl<<":"<<iend
2249  <<" "<<jpl<<":"<<jcl<<":"<<jend<<" iSlp "<<std::setprecision(2)<<clsChain[ipl][icl].Slope[iend]
2250  <<" jSlp "<<std::setprecision(2)<<clsChain[jpl][jcl].Slope[jend]<<" kWir "<<(int)kWir
2251  <<" okWir "<<(int)okWir<<" kSlp "<<std::setprecision(2)<<kSlp<<" kAng "
2252  <<std::setprecision(2)<<kAng<<" kX "<<std::setprecision(1)<<kX;
2253 
2254  // handle the case near pi/2, where the errors on large slopes
2255  // could result in a wrong-sign kAng
2256  ignoreSign = (fabs(kSlp) > 1.5);
2257  if(ignoreSign) kAng = fabs(kAng);
2258  dxkcut = dxcut * AngleFactor(kSlp);
2259  bool gotkcl = false;
2260  for(unsigned short kcl = 0; kcl < clsChain[kpl].size(); ++kcl) {
2261  if(clsChain[kpl][kcl].InTrack >= 0) continue;
2262  // make second charge asymmetry cut
2263  mchg[0] = clsChain[ipl][icl].TotChg;
2264  mchg[1] = clsChain[jpl][jcl].TotChg;
2265  mchg[2] = clsChain[kpl][kcl].TotChg;
2266  if(fChgAsymFactor[algIndex] > 0 && ChargeAsym(mchg) > 0.5) continue;
2267  for(unsigned short kend = 0; kend < 2; ++kend) {
2268  kdir = clsChain[kpl][kcl].Dir[kend];
2269  if(idir != 0 && kdir != 0 && idir != kdir) continue;
2270  if(prt) mf::LogVerbatim("CCTM")<<" kcl "<<kcl<<" kend "<<kend
2271  <<" dx "<<std::abs(clsChain[kpl][kcl].X[kend] - kX)<<" dxkcut "<<dxkcut;
2272  if(std::abs(clsChain[kpl][kcl].X[kend] - kX) > dxkcut) continue;
2273  // rough dWire cut
2274  if(prt) mf::LogVerbatim("CCTM")<<" kcl "<<kcl<<" kend "<<kend
2275  <<" dw "<<(clsChain[kpl][kcl].Wire[kend] - kWir)<<" ignoreSign "<<ignoreSign;
2276  if(fabs(clsChain[kpl][kcl].Wire[kend] - kWir) > dwcut) continue;
2277  if(prt) mf::LogVerbatim("CCTM")<<" chk k "<<kpl<<":"<<kcl<<":"<<kend;
2278  MatchPars match;
2279  match.Cls[ipl] = icl; match.End[ipl] = iend;
2280  match.Cls[jpl] = jcl; match.End[jpl] = jend;
2281  match.Cls[kpl] = kcl; match.End[kpl] = kend;
2282  match.Err = 100;
2283  if(DupMatch(match)) continue;
2284  match.Chg[ipl] = 0; match.Chg[jpl] = 0; match.Chg[kpl] = 0;
2285  match.Vtx = clsChain[ipl][icl].VtxIndex[iend];
2286  match.oVtx = -1;
2287  FillEndMatch(match);
2288  if(prt) mf::LogVerbatim("CCTM")<<" PlnMatch: match k "<<kpl<<":"<<match.Cls[kpl]
2289  <<":"<<match.End[kpl]<<" oChg "<<match.Chg[kpl]<<" mErr "<<match.Err<<" oErr "<<match.oErr;
2290  if(match.Chg[kpl] == 0) continue;
2291  if(match.Err > 10 || match.oErr > 10) continue;
2292  if(prt) mf::LogVerbatim("CCTM")<<" dup? ";
2293  if(DupMatch(match)) continue;
2294  matcomb.push_back(match);
2295  gotkcl = true;
2296  } // kend
2297  } // kcl
2298  if(prt) mf::LogVerbatim("CCTM")<<" PlnMatch: gotkcl "<<gotkcl;
2299  if(!gotkcl) {
2300  // make a 2-plane match and try again
2301  MatchPars match;
2302  match.Cls[ipl] = icl; match.End[ipl] = iend;
2303  match.Cls[jpl] = jcl; match.End[jpl] = jend;
2304  match.Cls[kpl] = -1; match.End[kpl] = 0;
2305  match.Err = 100;
2306  if(DupMatch(match)) continue;
2307  match.Chg[ipl] = 0; match.Chg[jpl] = 0; match.Chg[kpl] = 0;
2308  match.Vtx = clsChain[ipl][icl].VtxIndex[iend];
2309  match.oVtx = -1;
2310  FillEndMatch(match);
2311  if(prt) mf::LogVerbatim("CCTM")<<" Tried 2-plane match"<<" k "<<kpl<<":"<<match.Cls[kpl]
2312  <<":"<<match.End[kpl]<<" Chg "<<match.Chg[kpl]<<" Err "<<match.Err<<" match.oErr "<<match.oErr;
2313  if(match.Chg[kpl] <= 0) continue;
2314  if(match.Err > 10 || match.oErr > 10) continue;
2315  matcomb.push_back(match);
2316  } // !gotkcl
2317  } // jend
2318  } // iend
2319  } // jcl
2320  } // icl
2321  } // ipl
2322 
2323  if(matcomb.size() == 0) return;
2324 
2325  } // PlnMatch
2326 
2328  bool CCTrackMaker::DupMatch(MatchPars& match)
2329  {
2330 
2331  unsigned short nMatCl, nMiss;
2332  float toterr = match.Err + match.oErr;
2333  for(unsigned int imat = 0; imat < matcomb.size(); ++imat) {
2334  // check for exact matches
2335  if(match.Cls[0] == matcomb[imat].Cls[0] &&
2336  match.Cls[1] == matcomb[imat].Cls[1] &&
2337  match.Cls[2] == matcomb[imat].Cls[2]) {
2338 
2339  // compare the error
2340  if(toterr < matcomb[imat].Err + matcomb[imat].oErr) {
2341  // keep the better one
2342  matcomb[imat].End[0] = match.End[0];
2343  matcomb[imat].End[1] = match.End[1];
2344  matcomb[imat].End[2] = match.End[2];
2345  matcomb[imat].Vtx = match.Vtx;
2346  matcomb[imat].dWir = match.dWir;
2347  matcomb[imat].dAng = match.dAng;
2348  matcomb[imat].dX = match.dX;
2349  matcomb[imat].Err = match.Err;
2350  matcomb[imat].oVtx = match.oVtx;
2351  matcomb[imat].odWir = match.odWir;
2352  matcomb[imat].odAng = match.odAng;
2353  matcomb[imat].odX = match.odX;
2354  matcomb[imat].oErr = match.oErr;
2355  }
2356  return true;
2357  } // test
2358  // check for a 3-plane match vs 2-plane match
2359  nMatCl = 0;
2360  nMiss = 0;
2361  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) {
2362  if(match.Cls[ipl] >= 0) {
2363  if(match.Cls[ipl] == matcomb[imat].Cls[ipl] && (match.End[0] == matcomb[imat].End[0] || match.End[1] == matcomb[imat].End[1])) ++nMatCl;
2364  } else {
2365  ++nMiss;
2366  }
2367  } // ipl
2368  if(nMatCl == 2 && nMiss == 1) return true;
2369  } // imat
2370  return false;
2371  } // DupMatch
2372 
2374  void CCTrackMaker::SortMatches(art::FindManyP<recob::Hit> const& fmCluHits, unsigned short procCode)
2375  {
2376  // sort cluster matches by increasing total match error. Find the minimum total error of all
2377  // cluster match combinations and make tracks from them
2378  CluLen merr;
2379  std::vector<CluLen> materr;
2380  unsigned int ii, im;
2381 
2382  if(matcomb.size() == 0) return;
2383 
2384  // sort by decreasing error
2385  for(ii = 0; ii < matcomb.size(); ++ii) {
2386  merr.index = ii;
2387  merr.length = matcomb[ii].Err + matcomb[ii].oErr;
2388  materr.push_back(merr);
2389  } // ii
2390  std::sort(materr.begin(), materr.end(), lessThan);
2391 
2392  if(prt) {
2393  mf::LogVerbatim myprt("CCTM");
2394  myprt<<"SortMatches\n";
2395  myprt<<" ii im Vx Err dW dA dX oVx oErr odW odA odX Asym icl jcl kcl \n";
2396  for(ii = 0; ii < materr.size(); ++ii) {
2397  im = materr[ii].index;
2398  float asym = fabs(matcomb[im].Chg[0] - matcomb[im].Chg[1]) /
2399  (matcomb[im].Chg[0] + matcomb[im].Chg[1]);
2400  asym *= fabs(matcomb[im].Chg[1] - matcomb[im].Chg[2]) /
2401  (matcomb[im].Chg[1] + matcomb[im].Chg[2]);
2402  myprt<<std::fixed<<std::right
2403  <<std::setw(5)<<ii<<std::setw(5)<<im
2404  <<std::setw(4)<<matcomb[im].Vtx
2405  <<std::setw(7)<<std::setprecision(2)<<matcomb[im].Err
2406  <<std::setw(7)<<std::setprecision(1)<<matcomb[im].dWir
2407  <<std::setw(7)<<std::setprecision(2)<<matcomb[im].dAng
2408  <<std::setw(7)<<std::setprecision(2)<<matcomb[im].dX
2409  <<std::setw(4)<<matcomb[im].oVtx
2410  <<std::setw(7)<<std::setprecision(2)<<matcomb[im].oErr
2411  <<std::setw(7)<<std::setprecision(1)<<matcomb[im].odWir
2412  <<std::setw(7)<<std::setprecision(2)<<matcomb[im].odAng
2413  <<std::setw(7)<<std::setprecision(2)<<matcomb[im].odX
2414  <<std::setw(7)<<std::setprecision(3)<<asym
2415  <<" 0:"<<matcomb[im].Cls[0]<<":"<<matcomb[im].End[0]
2416  <<" 1:"<<matcomb[im].Cls[1]<<":"<<matcomb[im].End[1];
2417  if(nplanes > 2) myprt<<" 2:"<<matcomb[im].Cls[2]<<":"<<matcomb[im].End[2];
2418  myprt<<"\n";
2419  } // ii
2420  } // prt
2421 
2422  // define an array to ensure clusters are only used once
2423  std::array<std::vector<bool>, 3> pclUsed;
2424  unsigned short ipl;
2425  for(ipl = 0; ipl < nplanes; ++ipl) {
2426  pclUsed[ipl].resize(clsChain[ipl].size());
2427 // std::fill(pclUsed[ipl].begin(), pclUsed[ipl].end(), false);
2428  }
2429 
2430  // count the total number of clusters and length used in matcomb
2431  unsigned short matcombTotCl = 0;
2432  float matcombTotLen = 0;
2433  unsigned short icl;
2434  for(ii = 0; ii < matcomb.size(); ++ii) {
2435  for(ipl = 0; ipl < nplanes; ++ipl) {
2436  if(matcomb[ii].Cls[ipl] < 0) continue;
2437  icl = matcomb[ii].Cls[ipl];
2438  ++matcombTotCl;
2439  matcombTotLen += clsChain[ipl][icl].Length;
2440  }
2441  }
2442 
2443  if(prt) mf::LogVerbatim("CCTM")<<"Number of clusters to match "<<matcombTotCl <<" total length "<<matcombTotLen;
2444 
2445  if(matcombTotLen <= 0) {
2446  mf::LogError("CCTM")<<"SortMatches: bad matcomb total length "<<matcombTotLen;
2447  return;
2448  }
2449 
2450  // vector of matcomb indices of unique cluster matches
2451  std::vector<unsigned short> matIndex;
2452  // vector of matcomb indices of unique cluster matches that have the best total error
2453  std::vector<unsigned short> bestMatIndex;
2454  float totLen, totErr, bestTotErr = 9999;
2455  // start with the best match
2456  unsigned short jj, jm, nused, jcl;
2457  // fraction of the length of all clustters in matcomb that are used in a match
2458  float fracLen;
2459 
2460  for(ii = 0; ii < materr.size(); ++ii) {
2461  im = materr[ii].index;
2462  matIndex.clear();
2463  // skip really bad matches
2464  if(matcomb[im].Err > bestTotErr) continue;
2465  totLen = 0;
2466  // initialize pclUsed and flag the clusters in this match
2467  // mf::LogVerbatim("CCTM")<<"chk ii "<<ii<<" clusters "<<matcomb[im].Cls[0]<<" "<<matcomb[im].Cls[1]<<" "<<matcomb[im].Cls[2];
2468  for(ipl = 0; ipl < nplanes; ++ipl) {
2469  // initialize to no clusters used
2470  std::fill(pclUsed[ipl].begin(), pclUsed[ipl].end(), false);
2471  // check for 2 plane match
2472  if(matcomb[im].Cls[ipl] < 0) continue;
2473  icl = matcomb[im].Cls[ipl];
2474  pclUsed[ipl][icl] = true;
2475  totLen += clsChain[ipl][icl].Length;
2476  } // ipl
2477  // Initialize the error sum
2478  totErr = matcomb[im].Err;
2479  // Save the index
2480  matIndex.push_back(im);
2481  // look for matches in the rest of the list that are not already matched.
2482  for(jj = 0; jj < materr.size(); ++jj) {
2483  if(jj == ii) continue;
2484  jm = materr[jj].index;
2485  // skip really bad matches
2486  if(matcomb[jm].Err > bestTotErr) continue;
2487  // mf::LogVerbatim("CCTM")<<"chk match jj "<<jj<<" clusters "<<matcomb[jm].Cls[0]<<" "<<matcomb[jm].Cls[1]<<" "<<matcomb[jm].Cls[2];
2488  // check for non-unique cluster indices
2489  nused = 0;
2490  for(ipl = 0; ipl < nplanes; ++ipl) {
2491  if(matcomb[jm].Cls[ipl] < 0) continue;
2492  jcl = matcomb[jm].Cls[ipl];
2493  if(pclUsed[ipl][jcl]) ++nused;
2494  // This cluster chain was used in a previous match
2495  if(nused > 0) break;
2496  totLen += clsChain[ipl][jcl].Length;
2497  } // ipl
2498  // at least one of the clusters in this match have been used
2499  if(nused != 0) continue;
2500  // found a match with an unmatched set of clusters. Update the total error and flag them
2501  totErr += matcomb[jm].Err;
2502  matIndex.push_back(jm);
2503  // Flag the clusters used and see if all of them are used
2504  for(ipl = 0; ipl < nplanes; ++ipl) {
2505  if(matcomb[jm].Cls[ipl] < 0) continue;
2506  jcl = matcomb[jm].Cls[ipl];
2507  pclUsed[ipl][jcl] = true;
2508  } // ipl
2509  } // jm
2510  if(totLen == 0) continue;
2511  nused = 0;
2512  for(ipl = 0; ipl < nplanes; ++ipl) {
2513  for(unsigned short indx = 0; indx < pclUsed[ipl].size(); ++indx) if(pclUsed[ipl][indx]) ++nused;
2514  } // ipl
2515  if(totLen > matcombTotLen) std::cout<<"Oops "<<totLen<<" "<<matcombTotLen<<"\n";
2516  // weight the total error by the total length of all clusters
2517  fracLen = totLen / matcombTotLen;
2518 // totErr = totErr * nused / totLen;
2519 // totErr = totErr * matIndex.size() / fracLen;
2520  totErr /= fracLen;
2521  if(prt) {
2522  mf::LogVerbatim myprt("CCTM");
2523  myprt<<"match "<<im<<" totErr "<<totErr<<" nused "<<nused<<" fracLen "<<fracLen<<" totLen "<<totLen<<" mat: ";
2524  for(unsigned short indx = 0; indx < matIndex.size(); ++indx) myprt<<" "<<matIndex[indx];
2525  } // prt
2526  // check for more used clusters and a better total error
2527 // if(totErr < bestTotErr) {
2528  if(totErr < bestTotErr) {
2529  bestTotErr = totErr;
2530  bestMatIndex = matIndex;
2531  if(nused == matcombTotCl) break;
2532  if(prt) {
2533  mf::LogVerbatim myprt("CCTM");
2534  myprt<<"bestTotErr "<<bestTotErr<<" nused "<<nused<<" matcombTotCl "<<matcombTotCl<<" mat: ";
2535  for(unsigned short indx = 0; indx < bestMatIndex.size(); ++indx) myprt<<" "<<bestMatIndex[indx];
2536  } // prt
2537  // stop looking if we have found everything
2538  if(fracLen > 0.999) break;
2539  } // totErr < bestTotErr
2540  } // im
2541 
2542  if(bestTotErr > 9000) return;
2543 
2544  for(ii = 0; ii < bestMatIndex.size(); ++ii) {
2545  im = bestMatIndex[ii];
2546  // if(prt) mf::LogVerbatim("CCTM")<<"SM: "<<ii<<" im "<<im<<" 0:"<<matcomb[im].Cls[0]<<":"<<matcomb[im].End[0]<<" 1:"<<matcomb[im].Cls[1]<<":"<<matcomb[im].End[1]<<" 2:"<<matcomb[im].Cls[2]<<":"<<matcomb[im].End[2];
2547  // std::cout<<"FillTrkHits "<<ii<<"\n";
2548  FillTrkHits(fmCluHits, im);
2549  // look for missing clusters?
2550  // store this track with processor code 1
2551  StoreTrack(fmCluHits, im, procCode);
2552  } // ii
2553 
2554  } // SortMatches
2555 
2557  void CCTrackMaker::FillEndMatch2(MatchPars& match)
2558  {
2559  // 2D version of FillEndMatch
2560 
2561  match.Err = 100;
2562  match.oErr = 100;
2563  match.Chg[2] = 0;
2564  match.dWir = 0; match.dAng = 0;
2565  match.odWir = 0; match.odAng = 0;
2566 
2567  unsigned short ipl = 0;
2568  unsigned short jpl = 1;
2569 // mf::LogVerbatim("CCTM")<<"chk "<<ipl<<":"<<match.Cls[ipl]<<":"<<match.End[ipl]<<" and "<<jpl<<":"<<match.Cls[jpl]<<":"<<match.End[jpl];
2570 
2571  if(match.Cls[0] < 0 || match.Cls[1] < 0) return;
2572 
2573  unsigned short icl = match.Cls[ipl];
2574  unsigned short iend = match.End[ipl];
2575  match.Chg[ipl] = clsChain[ipl][icl].TotChg;
2576  // cluster i match end
2577  float miX = clsChain[ipl][icl].X[iend];
2578  // cluster i other end
2579  unsigned short oiend = 1 - iend;
2580  float oiX = clsChain[ipl][icl].X[oiend];
2581 
2582  unsigned short jcl = match.Cls[jpl];
2583  unsigned short jend = match.End[jpl];
2584  match.Chg[jpl] = clsChain[jpl][jcl].TotChg;
2585  // cluster j match end
2586  float mjX = clsChain[jpl][jcl].X[jend];
2587  // cluster j other end
2588  unsigned short ojend = 1 - jend;
2589  float ojX = clsChain[jpl][jcl].X[ojend];
2590 
2591  // look for a match end vertex match
2592  match.Vtx = -1;
2593  if(clsChain[ipl][icl].VtxIndex[iend] >= 0 &&
2594  clsChain[ipl][icl].VtxIndex[iend] == clsChain[jpl][jcl].VtxIndex[jend]) {
2595  match.Vtx = clsChain[ipl][icl].VtxIndex[iend];
2596  miX = vtx[match.Vtx].X;
2597  mjX = vtx[match.Vtx].X;
2598  }
2599 
2600  // look for an other end vertex match
2601  match.oVtx = -1;
2602  if(clsChain[ipl][icl].VtxIndex[oiend] >= 0 &&
2603  clsChain[ipl][icl].VtxIndex[oiend] == clsChain[jpl][jcl].VtxIndex[ojend]) {
2604  match.oVtx = clsChain[ipl][icl].VtxIndex[oiend];
2605  oiX = vtx[match.oVtx].X;
2606  ojX = vtx[match.oVtx].X;
2607  }
2608 
2609  // find the charge asymmetry
2610  float chgAsym = 1;
2611  if(fChgAsymFactor[algIndex] > 0) {
2612  chgAsym = fabs(match.Chg[ipl] - match.Chg[jpl]) / (match.Chg[ipl] + match.Chg[jpl]);
2613  if(chgAsym > 0.5) return;
2614  chgAsym = 1 + fChgAsymFactor[algIndex] * chgAsym;
2615  }
2616 
2617  // find the error at the match end
2618  float maxSlp = fabs(clsChain[ipl][icl].Slope[iend]);
2619  if(fabs(clsChain[jpl][jcl].Slope[jend]) > maxSlp) maxSlp = fabs(clsChain[jpl][jcl].Slope[jend]);
2620  float sigmaX = fXMatchErr[algIndex] + std::max(maxSlp, (float)20);
2621  match.dX = fabs(miX - mjX);
2622  match.Err = chgAsym * match.dX / sigmaX;
2623 
2624 
2625  // find the error at the other end
2626  maxSlp = fabs(clsChain[ipl][icl].Slope[oiend]);
2627  if(fabs(clsChain[jpl][jcl].Slope[ojend]) > maxSlp) maxSlp = fabs(clsChain[jpl][jcl].Slope[ojend]);
2628  sigmaX = fXMatchErr[algIndex] + std::max(maxSlp, (float)20);
2629  match.odX = fabs(oiX - ojX);
2630  match.oErr = chgAsym * match.odX / sigmaX;
2631 
2632  if(prt) mf::LogVerbatim("CCTM")<<"FEM2: m "<<ipl<<":"<<icl<<":"<<iend<<" miX "<<miX
2633  <<" - "<<jpl<<":"<<jcl<<":"<<jend<<" mjX "<<mjX<<" match.dX "<<match.dX
2634  <<" match.Err "<<match.Err<<" chgAsym "<<chgAsym<<" o "<<" oiX "<<oiX
2635  <<" ojX "<<ojX<<" match.odX "<<match.odX<<" match.oErr "<<match.oErr<<"\n";
2636 
2637 
2638 } // FillEndMatch2
2639 
2641  void CCTrackMaker::FillEndMatch(MatchPars& match)
2642  {
2643  // fill the matching parameters for this cluster match. The calling routine
2644  // should set the match end vertex ID (if applicable) as well as the
2645  // cluster IDs and matching ends in each plane. Note that the matching variables
2646  // Note that dWir, dAng and dTim are not filled if there is a vertex (match.Vtx >= 0).
2647  // Likewise, odWir, odAng and odX are not filled if there is a vertex match
2648  // at the other end
2649 
2650  if(nplanes == 2) {
2651  FillEndMatch2(match);
2652  return;
2653  }
2654 
2655  std::array<short, 3> mVtx;
2656  std::array<short, 3> oVtx;
2657  std::array<float, 3> oWir;
2658  std::array<float, 3> oSlp;
2659  std::array<float, 3> oAng;
2660  std::array<float, 3> oX;
2661 
2662  std::array<float, 3> mChg;
2663 
2664  unsigned short ii, ipl, iend, jpl, jend, kpl, kend, oend;
2665  short icl, jcl, kcl;
2666 
2667  for(ipl = 0; ipl < 3; ++ipl) {
2668  mVtx[ipl] = -1; oVtx[ipl] = -1;
2669  oWir[ipl] = -66; oSlp[ipl] = -66; oAng[ipl] = -66; oX[ipl] = -66;
2670  mChg[ipl] = -1;
2671  } // ipl
2672 
2673  // initialize parameters that shouldn't have been set by the calling routine
2674  match.dWir = 0; match.dAng = 0; match.dX = 0; match.Err = 100;
2675  match.odWir = 0; match.odAng = 0; match.odX = 0; match.oErr = 100;
2676  match.oVtx = -1;
2677 
2678  if(prt) {
2679  mf::LogVerbatim myprt("CCTM");
2680  myprt<<"FEM ";
2681  for(ipl = 0; ipl < nplanes; ++ipl) {
2682  myprt<<" "<<ipl<<":"<<match.Cls[ipl]<<":"<<match.End[ipl];
2683  }
2684  }
2685 
2686  short missingPlane = -1;
2687  unsigned short nClInPln = 0;
2688  // number of vertex matches at each end
2689  short aVtx = -1;
2690  unsigned short novxmat = 0;
2691  short aoVtx = -1;
2692  unsigned short nvxmat = 0;
2693  unsigned short nShortCl = 0;
2694  // fill the other end parameters in each plane
2695  for(ipl = 0; ipl < nplanes; ++ipl) {
2696  if(match.Cls[ipl] < 0) {
2697  missingPlane = ipl;
2698  continue;
2699  }
2700  ++nClInPln;
2701  icl = match.Cls[ipl];
2702  match.Chg[ipl] = clsChain[ipl][icl].TotChg;
2703  mChg[ipl] = clsChain[ipl][icl].TotChg;
2704  iend = match.End[ipl];
2705  mVtx[ipl] = clsChain[ipl][icl].VtxIndex[iend];
2706  if(clsChain[ipl][icl].Length < 6) ++nShortCl;
2707  if(mVtx[ipl] >= 0) {
2708  if(aVtx < 0) aVtx = mVtx[ipl];
2709  if(mVtx[ipl] == aVtx) ++nvxmat;
2710  }
2711  if(prt ) mf::LogVerbatim("CCTM")<<"FEM: m "<<ipl<<":"<<icl<<":"<<iend<<" Vtx "<<mVtx[ipl]<<" Wir "<<clsChain[ipl][icl].Wire[iend]<<std::fixed<<std::setprecision(3)<<" Slp "<<clsChain[ipl][icl].Slope[iend]<<std::fixed<<std::setprecision(1)<<" X "<<clsChain[ipl][icl].X[iend];
2712 
2713  oend = 1 - iend;
2714  oWir[ipl] = clsChain[ipl][icl].Wire[oend];
2715  oAng[ipl] = clsChain[ipl][icl].Angle[oend];
2716  oSlp[ipl] = clsChain[ipl][icl].Slope[oend];
2717  oX[ipl] = clsChain[ipl][icl].X[oend];
2718  oVtx[ipl] = clsChain[ipl][icl].VtxIndex[oend];
2719  if(oVtx[ipl] >= 0) {
2720  if(aoVtx < 0) aoVtx = oVtx[ipl];
2721  if(oVtx[ipl] == aoVtx) ++novxmat;
2722  }
2723 
2724  if(prt) mf::LogVerbatim("CCTM")<<" o "<<ipl<<":"<<icl<<":"<<oend<<" oVtx "<<oVtx[ipl]<<" oWir "<<oWir[ipl]<<std::fixed<<std::setprecision(3)<<" oSlp "<<oSlp[ipl]<<std::fixed<<std::setprecision(1)<<" oX "<<oX[ipl]<<" Chg "<<(int)mChg[ipl];
2725 
2726  } // ipl
2727 
2728  bool isShort = (nShortCl > 1);
2729 
2730  if(nClInPln < 2) {
2731  mf::LogWarning("CCTM")<<"Not enough matched planes supplied";
2732  return;
2733  }
2734 
2735  if(prt) mf::LogVerbatim("CCTM")<<"FEM: Vtx m "<<aVtx<<" count "<<nvxmat
2736  <<" o "<<aoVtx<<" count "<<novxmat
2737  <<" missingPlane "<<missingPlane
2738  <<" nClInPln "<<nClInPln;
2739 
2740  // perfect match
2741  if(nvxmat == 3 && novxmat == 3) {
2742  match.Vtx = aVtx; match.Err = 0;
2743  match.oVtx = aoVtx; match.oErr = 0;
2744  return;
2745  }
2746 
2747  // 2-plane vertex match?
2748  // factors applied to error = 1 (no vtx), 0.5 (2 pln vtx), 0.33 (3 pln vtx)
2749  float vxFactor = 1;
2750  float ovxFactor = 1;
2751  if(nClInPln == 3) {
2752  // a cluster in all 3 planes
2753  if(nvxmat == 3) {
2754  // and all vertex assignments agree at the match end
2755  match.Vtx = aVtx; vxFactor = 0.33;
2756  }
2757  if(novxmat == 3) {
2758  // and all vertex assignments agree at the other end
2759  match.oVtx = aoVtx; ovxFactor = 0.33;
2760  }
2761  } else {
2762  // a cluster in 2 planes
2763  if(nvxmat == 2) {
2764  match.Vtx = aVtx; vxFactor = 0.5;
2765  }
2766  if(novxmat == 2) {
2767  match.oVtx = aoVtx; ovxFactor = 0.5;
2768  }
2769  } // nClInPln
2770 
2771  // find wire, X and Time at both ends
2772 
2773  // Find the "other end" matching parameters with
2774  // two cases: a 3-plane match or a 2-plane match
2775  // and with/without an other end vertex
2776 
2777  double ypos, zpos;
2778  float kWir, okWir;
2779  float kSlp, okSlp, kAng, okAng, okX, kX, kTim, okTim;
2780 
2781  if(nClInPln == 3) {
2782  ipl = 0; jpl = 1; kpl = 2;
2783  } else {
2784  // 2-plane match
2785  kpl = missingPlane;
2786  if(kpl == 0) {
2787  ipl = 1; jpl = 2;
2788  } else if(kpl == 1) {
2789  ipl = 2; jpl = 0;
2790  } else {
2791  ipl = 0; jpl = 1;
2792  } // kpl test
2793  } // missing plane
2794  iend = match.End[ipl]; jend = match.End[jpl];
2795  icl = match.Cls[ipl]; jcl = match.Cls[jpl];
2796  if(nplanes > 2) {
2797  kcl = match.Cls[kpl];
2798  kend = match.End[kpl];
2799  }
2800 
2802  okSlp = geom->ThirdPlaneSlope(ipl, oSlp[ipl], jpl, oSlp[jpl], tpc, cstat);
2803  okAng = atan(okSlp);
2804  // handle the case near pi/2, where the errors on large slopes could result in
2805  // a wrong-sign kAng
2806  bool ignoreSign = (fabs(okSlp) > 10);
2807  if(ignoreSign) okAng = fabs(okAng);
2808  if(match.oVtx >= 0) {
2809  // a vertex exists at the other end
2810  okWir = geom->WireCoordinate(vtx[match.oVtx].Y, vtx[match.oVtx].Z, kpl, tpc, cstat);
2811  okX = vtx[match.oVtx].X;
2812  } else {
2813  // no vertex at the other end
2814  geom->IntersectionPoint(oWir[ipl], oWir[jpl],ipl, jpl, cstat, tpc, ypos, zpos);
2815  okWir = (0.5 + geom->WireCoordinate(ypos, zpos, kpl, tpc, cstat));
2816  okX = 0.5 * (oX[ipl] + oX[jpl]);
2817  }
2818  okTim = detprop->ConvertXToTicks(okX, kpl, tpc, cstat);
2819  if(prt) mf::LogVerbatim("CCTM")<<"FEM: oEnd"<<" kpl "<<kpl<<" okSlp "<<okSlp<<" okAng "
2820  <<okAng<<" okWir "<<(int)okWir<<" okX "<<okX;
2821 
2822 
2824 
2825  kSlp = geom->ThirdPlaneSlope(ipl, clsChain[ipl][icl].Slope[iend], jpl, clsChain[jpl][jcl].Slope[jend], tpc, cstat);
2826  kAng = atan(kSlp);
2827  if(ignoreSign) kAng = fabs(kAng);
2828  if(match.Vtx >= 0) {
2829  if(vtx.size() == 0 || (unsigned int)match.Vtx > vtx.size() - 1) {
2830  mf::LogError("CCTM")<<"FEM: Bad match.Vtx "<<match.Vtx<<" vtx size "<<vtx.size();
2831  return;
2832  }
2833  // a vertex exists at the match end
2834  kWir = geom->WireCoordinate(vtx[match.Vtx].Y, vtx[match.Vtx].Z, kpl, tpc, cstat);
2835  kX = vtx[match.Vtx].X;
2836  } else {
2837  // no vertex at the match end
2838  geom->IntersectionPoint(clsChain[ipl][icl].Wire[iend], clsChain[jpl][jcl].Wire[jend], ipl, jpl, cstat, tpc, ypos, zpos);
2839  kWir = (0.5 + geom->WireCoordinate(ypos, zpos, kpl, tpc, cstat));
2840  kX = 0.5 * (clsChain[ipl][icl].X[iend] + clsChain[jpl][jcl].X[jend]);
2841  }
2842  kTim = detprop->ConvertXToTicks(kX, kpl, tpc, cstat);
2843  if(prt) mf::LogVerbatim("CCTM")<<"FEM: mEnd"<<" kpl "<<kpl<<" kSlp "<<kSlp<<" kAng "<<kAng<<" kX "<<kX;
2844 
2845  // try to find a 3-plane match using this information
2846  if(nClInPln < 3 && FindMissingCluster(kpl, kcl, kend, kWir, kX, okWir, okX)) {
2847  nClInPln = 3;
2848  // update local variables
2849  match.Cls[kpl] = kcl;
2850  match.End[kpl] = kend;
2851  match.Chg[kpl] = clsChain[kpl][kcl].TotChg;
2852  mChg[kpl] = clsChain[kpl][kcl].TotChg;
2853  oend = 1 - kend;
2854  oWir[kpl] = clsChain[kpl][kcl].Wire[oend];
2855  oX[kpl] = clsChain[kpl][kcl].X[oend];
2856  oAng[kpl] = clsChain[kpl][kcl].Angle[oend];
2857  oSlp[kpl] = clsChain[kpl][kcl].Slope[oend];
2858  } // FindMissingCluster
2859 
2860  // decide whether to continue with a 2-plane match. The distance between match and other end should
2861  // be large enough to create a cluster
2862  if(nClInPln == 2 && fabs(okWir - kWir) > 3) return;
2863 
2864  // Calculate the cluster charge asymmetry. This factor will be applied
2865  // to the error of the end matches
2866  float chgAsym = 1;
2867  // Get the charge in the plane without a matching cluster
2868  if(nClInPln < 3 && mChg[missingPlane] <= 0) {
2869  if(missingPlane != kpl) mf::LogError("CCTM")<<"FEM bad missingPlane "<<missingPlane<<" "<<kpl<<"\n";
2870  mChg[kpl] = ChargeNear(kpl, (unsigned short)kWir, kTim, (unsigned short)okWir, okTim);
2871  match.Chg[kpl] = mChg[kpl];
2872  if(prt) mf::LogVerbatim("CCTM")<<"FEM: Missing cluster in "<<kpl<<" ChargeNear "<<(int)kWir<<":"<<(int)kTim
2873  <<" "<<(int)okWir<<":"<<(int)okTim<<" chg "<<mChg[kpl];
2874  if(mChg[kpl] <= 0) return;
2875  }
2876 
2877  if(fChgAsymFactor[algIndex] > 0) {
2878  chgAsym = ChargeAsym(mChg);
2879  if(chgAsym > 0.5) return;
2880  chgAsym = 1 + fChgAsymFactor[algIndex] * chgAsym;
2881  }
2882 
2883  if(prt) mf::LogVerbatim("CCTM")<<"FEM: charge asymmetry factor "<<chgAsym;
2884  float sigmaX, sigmaA;
2885  float da, dx, dw;
2886 
2888  // check for vertex consistency at the match end
2889  aVtx = -1;
2890  bool allPlnVtxMatch = false;
2891  if(nClInPln == 3) {
2892  unsigned short nmvtx = 0;
2893  for(ii = 0; ii < nplanes; ++ii) {
2894  if(mVtx[ii] >= 0) {
2895  if(aVtx < 0) aVtx = mVtx[ii];
2896  ++nmvtx;
2897  }
2898  } // ii
2899  // same vertex in all planes
2900  if(nmvtx ) allPlnVtxMatch = true;
2901  } // nClInPln
2902 
2903  // inflate the X match error to allow for missing one wire on the end of a cluster
2904  sigmaX = fXMatchErr[algIndex] + std::max(kSlp, (float)20);
2905  sigmaA = fAngleMatchErr[algIndex] * AngleFactor(kSlp);
2906  if(prt) mf::LogVerbatim("CCTM")<<"bb "<<algIndex<<" "<<fXMatchErr[algIndex]<<" "<<fAngleMatchErr[algIndex]<<" kslp "<<kSlp;
2907 
2908  if(nClInPln == 3) {
2909  kcl = match.Cls[kpl];
2910  kend = match.End[kpl];
2911  dw = kWir - clsChain[kpl][kcl].Wire[kend];
2912  match.dWir = dw;
2913  if(fabs(match.dWir) > 100) return;
2914  if(match.Vtx >= 0) {
2915  match.dX = kX - clsChain[kpl][kcl].X[kend];
2916  } else {
2917  match.dX = std::abs(clsChain[ipl][icl].X[iend] - clsChain[jpl][jcl].X[jend]) +
2918  std::abs(clsChain[ipl][icl].X[iend] - clsChain[kpl][kcl].X[kend]);
2919  }
2920  if(prt) mf::LogVerbatim("CCTM")<<" dw "<<dw<<" dx "<<match.dX;
2921  // TODO: Angle matching has problems with short clusters
2922  if(!isShort) {
2923  if(ignoreSign) {
2924  match.dAng = kAng - fabs(clsChain[kpl][kcl].Angle[kend]);
2925  } else {
2926  match.dAng = kAng - clsChain[kpl][kcl].Angle[kend];
2927  }
2928  } // !isShort
2929  da = fabs(match.dAng) / sigmaA;
2930  dx = fabs(match.dX) / sigmaX;
2931  if(allPlnVtxMatch) {
2932  // matched vertex. Use angle for match error
2933  match.Err = vxFactor * chgAsym * da / 3;
2934  if(prt) mf::LogVerbatim("CCTM")<<" 3-pln w Vtx match.Err "<<match.Err;
2935  } else {
2936  dw /= 2;
2937  // divide by 9
2938  match.Err = vxFactor * chgAsym * sqrt(dx*dx + da*da + dw*dw) / 9;
2939  if(prt) mf::LogVerbatim("CCTM")<<" 3-pln match.Err "<<match.Err;
2940  }
2941  } else {
2942  // 2-plane match
2943  match.dWir = -1;
2944  match.dAng = -1;
2945  match.dX = clsChain[ipl][icl].X[iend] - clsChain[jpl][jcl].X[jend];
2946  // degrade error by 3 for 2-plane matches
2947  match.Err = 3 + vxFactor * chgAsym * fabs(match.dX) / sigmaX;
2948  if(prt) mf::LogVerbatim("CCTM")<<" 2-pln Err "<<match.Err;
2949  } // !(nClInPln == 3)
2950 
2952  if(nClInPln == 3) {
2953  // A cluster in all 3 planes
2954  dw = okWir - oWir[kpl];
2955  match.odWir = dw;
2956  if(match.oVtx >= 0) {
2957  match.odX = okX - oX[kpl];
2958  } else {
2959  match.odX = std::abs(oX[ipl] - oX[jpl]) + std::abs(oX[ipl] - oX[kpl]);
2960  }
2961  if(prt) mf::LogVerbatim("CCTM")<<" odw "<<match.odWir<<" odx "<<match.odX<<" sigmaX "<<sigmaX;
2962  // TODO: CHECK FOR SHOWER-LIKE CLUSTER OTHER END MATCH
2963  if(!isShort) {
2964  if(ignoreSign) {
2965  match.odAng = okAng - fabs(oAng[kpl]);
2966  } else {
2967  match.odAng = okAng - oAng[kpl];
2968  }
2969  } // !isShort
2970  da = match.odAng / sigmaA;
2971  dx = fabs(match.odX) / sigmaX;
2972  // error for wire number match
2973  dw /= 2;
2974  // divide by number of planes with clusters * 3 for dx, da and dw
2975  match.oErr = ovxFactor * chgAsym * sqrt(dx*dx + da*da + dw*dw) / 9;
2976  if(prt) mf::LogVerbatim("CCTM")<<" 3-pln match.oErr "<<match.oErr;
2977  } else {
2978  // Only 2 clusters in 3 planes
2979  match.odX = (oX[ipl] - oX[jpl]) / sigmaX;
2980  match.oErr = 3 + ovxFactor * chgAsym * fabs(match.odX);
2981  if(prt) mf::LogVerbatim("CCTM")<<" 2-pln match.oErr "<<match.oErr;
2982  }
2983  // std::cout<<"FEM done\n";
2984 
2985  /*
2986  if(nClInPln == 3 && fabs(match.dAng) < 20) {
2987  if(fabs(match.dX) > 0.5) mf::LogVerbatim("CCTM")<<"Bad dX "<<match.dX<<" "<<dx<<" clusters "<<ipl<<":"<<icl<<" "<<jpl<<":"<<jcl<<" "<<kpl<<":"<<kcl<<"\n";
2988  // temp code to grep for ntuple elements in the output
2989  mf::LogVerbatim("CCTM")<<"ntup "<<kSlp<<" "<<match.dX<<" "<<chgAsym<<" "<<match.dAng<<" "<<match.dX<<" "<<match.dWir<<" "<<match.Err<<" "<<match.odAng<<" "<<match.odX<<" "<<match.odWir<<" "<<match.oErr;
2990  if(chgAsym > 1.5) mf::LogVerbatim("CCTM")<<"BadAsym "<<"0:"<<match.Cls[0]<<":"<<match.End[0]<<" 1:"<<match.Cls[1]<<":"<<match.End[1]<<" 2:"<<match.Cls[2]<<":"<<match.End[2];
2991  }
2992  */
2993  } // FillEndMatch
2994 
2995 
2997  void CCTrackMaker::FindMidPointMatch(art::FindManyP<recob::Hit> const& fmCluHits, MatchPars& match, unsigned short kkpl, unsigned short kkcl, unsigned short kkend, float& kkWir, float& kkX)
2998  {
2999  // Cluster chain kkcl is broken due to a failure in MakeClusterChains. Find the intersection of
3000  // the other two clusters in match at the appropriate end of cluster kkcl
3001 
3002  kkWir = -1;
3003  // find the match point at the other end of the original match point
3004  kkend = 1 - kkend;
3005  // mf::LogVerbatim("CCTM")<<"FMPM "<<kkpl<<":"<<kkcl<<":"<<kkend;
3006  kkX = clsChain[kkpl][kkcl].X[kkend];
3007  float matchTime = detprop->ConvertXToTicks(kkX, kkpl, tpc, cstat);
3008 
3009  // vector of wire numbers that have the most similar time
3010  std::vector<unsigned int> wirs;
3011  std::vector<unsigned int> plns;
3012  std::vector<art::Ptr<recob::Hit>> clusterhits;
3013  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) {
3014  if(ipl == kkpl) continue;
3015  // this shouldn't happen but check anyway
3016  if(match.Cls[ipl] < 0) continue;
3017  float dTime, best = 99999;
3018  unsigned short wire = 0;
3019  unsigned short icl, ccl = match.Cls[ipl];
3020  // loop over all clusters in the chain
3021  for(unsigned short ii = 0; ii < clsChain[kkpl][ccl].ClsIndex.size(); ++ii) {
3022  icl = clsChain[kkpl][ccl].ClsIndex[ii];
3023  if(cls[ipl][icl].EvtIndex > fmCluHits.size() -1) {
3024  std::cout<<"Bad ClsIndex in FMPM\n";
3025  exit(1);
3026  }
3027  clusterhits = fmCluHits.at(cls[ipl][icl].EvtIndex);
3028  // loop over all the hits in each cluster
3029  for(unsigned short iht = 0; iht < clusterhits.size(); ++iht) {
3030  dTime = fabs(clusterhits[iht]->PeakTime() - matchTime);
3031  if(dTime < best) {
3032  best = dTime;
3033  wire = clusterhits[iht]->WireID().Wire;
3034  }
3035  } // iht
3036  } // ii
3037  wirs.push_back(wire);
3038  plns.push_back(ipl);
3039  // mf::LogVerbatim("CCTM")<<" ipl "<<ipl<<" wire "<<wire;
3040  } // ipl
3041  if(wirs.size() != 2) return;
3042  double Y, Z;
3043  geom->IntersectionPoint(wirs[0], wirs[1], plns[0], plns[1], cstat, tpc, Y, Z);
3044  kkWir = geom->WireCoordinate(Y, Z, kkpl, tpc, cstat);
3045 
3046  } // FindMidPointMatch
3047 
3049  bool CCTrackMaker::FindMissingCluster(unsigned short kpl, short& kcl, unsigned short& kend, float kWir, float kX, float okWir, float okX)
3050  {
3051  // try to attach a missing cluster to the cluster chain kcl. kend is the "match end"
3052 
3053  unsigned short okend;
3054  float dxcut;
3055 
3056  if(kcl >= 0) return false;
3057 
3058  // Look for a missing cluster with loose cuts
3059  float kslp = fabs((okX - kX) / (okWir - kWir));
3060  if(kslp > 20) kslp = 20;
3061  // expand dX cut assuming there is a missing hit on the end of a cluster => 1 wire
3062  dxcut = 3 * fXMatchErr[algIndex] + kslp;
3063  unsigned short nfound = 0;
3064  unsigned short foundCl = 0, foundEnd = 0;
3065  for(unsigned short ccl = 0; ccl < clsChain[kpl].size(); ++ccl) {
3066  if(clsChain[kpl][ccl].InTrack >= 0) continue;
3067  // require a match at both ends
3068  for(unsigned short end = 0; end < 2; ++end) {
3069  okend = 1 - end;
3070  if(fabs(clsChain[kpl][ccl].Wire[end] - kWir) > 4) continue;
3071  if(fabs(clsChain[kpl][ccl].Wire[okend] - okWir) > 4) continue;
3072  // mf::LogVerbatim("CCTM")<<"FMC chk "<<kpl<<":"<<ccl<<":"<<end<<" dW "<<clsChain[kpl][ccl].Wire[end] - kWir<<" odW "<<clsChain[kpl][ccl].Wire[okend] - okWir<<" ccl X "<<clsChain[kpl][ccl].X[end]<<" kX "<<kX<<" ccl oX "<<clsChain[kpl][ccl].X[okend]<<" okX "<<okX<<" dxcut "<<dxcut;
3073  // require at least one end to match
3074  if(fabs(clsChain[kpl][ccl].X[end] - kX) > dxcut && fabs(clsChain[kpl][ccl].X[okend] - okX) > dxcut) continue;
3075  ++nfound;
3076  foundCl = ccl;
3077  foundEnd = end;
3078  } // end
3079  } // ccl
3080  if(nfound == 0) return false;
3081  if(nfound > 1) {
3082  mf::LogVerbatim("CCTM")<<"FindMissingCluster: Found too many matches. Write some code "<<nfound;
3083  return false;
3084  }
3085  kcl = foundCl;
3086  kend = foundEnd;
3087  return true;
3088 
3089  } // FindMissingCluster
3090 
3092  float CCTrackMaker::ChargeAsym(std::array<float, 3>& mChg)
3093  {
3094  // find charge asymmetry between the cluster with the highest (lowest)
3095  // charge
3096  float big = 0, small = 1.E9;
3097  for(unsigned short ii = 0; ii < 3; ++ii) {
3098  if(mChg[ii] < small) small = mChg[ii];
3099  if(mChg[ii] > big) big = mChg[ii];
3100  }
3101  // chgAsym varies between 0 (perfect charge match) and 1 (dreadfull)
3102  return (big - small) / (big + small);
3103  } // CalculateChargeAsym
3104 
3105 
3107  void CCTrackMaker::FillTrkHits(art::FindManyP<recob::Hit> const& fmCluHits, unsigned short imat)
3108  {
3109  // Fills the trkHits vector using cluster hits associated with the match combo imat
3110 
3111  unsigned short ipl;
3112 
3113  for(ipl = 0; ipl < 3; ++ipl) trkHits[ipl].clear();
3114 
3115  if(imat > matcomb.size()) return;
3116 
3117  unsigned short indx;
3118  std::vector<art::Ptr<recob::Hit>> clusterhits;
3119  unsigned short icc, ccl, icl, ecl, iht, ii;
3120  short endOrder, fillOrder;
3121 
3122  if(prt) mf::LogVerbatim("CCTM")<<"In FillTrkHits: matcomb "<<imat<<" cluster chains "<<matcomb[imat].Cls[0]<<" "<<matcomb[imat].Cls[1]<<" "<<matcomb[imat].Cls[2];
3123 
3124  for(ipl = 0; ipl < nplanes; ++ipl) {
3125  if(matcomb[imat].Cls[ipl] < 0) continue;
3126  // ccl is the cluster chain index
3127  ccl = matcomb[imat].Cls[ipl];
3128  // endOrder = 1 for normal order (hits added from US to DS), and -1 for reverse order
3129  endOrder = 1 - 2 * matcomb[imat].End[ipl];
3130  // re-order the sequence of cluster indices for reverse order
3131  if(endOrder < 0) {
3132  std::reverse(clsChain[ipl][ccl].ClsIndex.begin(), clsChain[ipl][ccl].ClsIndex.end());
3133  std::reverse(clsChain[ipl][ccl].Order.begin(), clsChain[ipl][ccl].Order.end());
3134  for(ii = 0; ii < clsChain[ipl][ccl].Order.size(); ++ii) clsChain[ipl][ccl].Order[ii] = 1 - clsChain[ipl][ccl].Order[ii];
3135  }
3136  if(ccl > clsChain[ipl].size() - 1) {
3137  mf::LogError("CCTM")<<"Bad cluster chain index "<<ccl<<" in plane "<<ipl;
3138  continue;
3139  }
3140  // loop over all the clusters in the chain
3141  for(icc = 0; icc < clsChain[ipl][ccl].ClsIndex.size(); ++icc) {
3142  icl = clsChain[ipl][ccl].ClsIndex[icc];
3143  if(icl > fmCluHits.size() - 1) {
3144  std::cout<<"oops in FTH "<<icl<<" clsChain size "<<clsChain[ipl].size()<<"\n";
3145  exit(1);
3146  }
3147  ecl = cls[ipl][icl].EvtIndex;
3148  if(ecl > fmCluHits.size() - 1) {
3149  std::cout<<"FTH bad EvtIndex "<<ecl<<" fmCluHits size "<<fmCluHits.size()<<"\n";
3150  continue;
3151  }
3152  clusterhits = fmCluHits.at(ecl);
3153  if(clusterhits.size() == 0) {
3154  std::cout<<"FTH no cluster hits for EvtIndex "<<cls[ipl][icl].EvtIndex<<"\n";
3155  continue;
3156  }
3157  indx = trkHits[ipl].size();
3158  trkHits[ipl].resize(indx + clusterhits.size());
3159  // ensure the hit fill ordering is consistent
3160  fillOrder = 1 - 2 * clsChain[ipl][ccl].Order[icc];
3161 // mf::LogVerbatim("CCTM")<<"FillOrder ipl "<<ipl<<" ccl "<<ccl<<" icl "<<icl<<" endOrder "<<endOrder<<" fillOrder "<<fillOrder;
3162  if(fillOrder == 1) {
3163 // mf::LogVerbatim("CCTM")<<" first hit "<<clusterhits[0]->WireID().Wire<<":"<<(int)clusterhits[0]->PeakTime();
3164  for(iht = 0; iht < clusterhits.size(); ++iht) {
3165  if(indx + iht > trkHits[ipl].size() - 1) std::cout<<"FTH oops3\n";
3166  trkHits[ipl][indx + iht] = clusterhits[iht];
3167  }
3168  } else {
3169 // iht = clusterhits.size() - 1;
3170 // mf::LogVerbatim("CCTM")<<" first hit "<<clusterhits[iht]->WireID().Wire<<":"<<(int)clusterhits[iht]->PeakTime();
3171  for(ii = 0; ii < clusterhits.size(); ++ii) {
3172  iht = clusterhits.size() - 1 - ii;
3173  if(indx + ii > trkHits[ipl].size() - 1) std::cout<<"FTH oops4\n";
3174  trkHits[ipl][indx + ii] = clusterhits[iht];
3175  } // ii
3176  }
3177  } // icc
3178  ii = trkHits[ipl].size() - 1;
3179  if(prt) mf::LogVerbatim("CCTM")<<"plane "<<ipl<<" first p "<<trkHits[ipl][0]->WireID().Plane<<" w "<<trkHits[ipl][0]->WireID().Wire<<":"<<(int)trkHits[ipl][0]->PeakTime()<<" last p "<<trkHits[ipl][ii]->WireID().Plane<<" w "<<trkHits[ipl][ii]->WireID().Wire<<":"<<(int)trkHits[ipl][ii]->PeakTime();
3180 // for(ii = 0; ii < trkHits[ipl].size(); ++ii) mf::LogVerbatim("CCTM")<<ii<<" p "<<trkHits[ipl][ii]->WireID().Plane<<" w "<<trkHits[ipl][ii]->WireID().Wire<<" t "<<(int)trkHits[ipl][ii]->PeakTime();
3181  } // ipl
3182 
3183  // TODO Check the ends of trkHits to see if there are missing hits that should have been included
3184  // in a cluster
3185 
3186  } // FillTrkHits
3187 
3189  void CCTrackMaker::PrintTracks() const
3190  {
3191  mf::LogVerbatim myprt("CCTM");
3192  myprt<<"********* PrintTracks \n";
3193  myprt<<"vtx Index X Y Z\n";
3194  for(unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
3195  myprt<<std::right<<std::setw(4)<<ivx<<std::setw(4)<<vtx[ivx].EvtIndex;
3196  myprt<<std::fixed;
3197  myprt<<std::right<<std::setw(10)<<std::setprecision(1)<<vtx[ivx].X;
3198  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<vtx[ivx].Y;
3199  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<vtx[ivx].Z;
3200  if(vtx[ivx].Neutrino) myprt<<" Neutrino vertex";
3201  myprt<<"\n";
3202  } // ivx
3203 
3204  myprt<<">>>>>>>>>> Tracks \n";
3205  myprt<<"trk ID Proc nht nTrj sX sY sZ eX eY eZ sVx eVx sGd eGd ChgOrd dirZ Mom PDG ClsIndices\n";
3206  for(unsigned short itr = 0; itr < trk.size(); ++itr) {
3207  myprt<<std::right<<std::setw(3)<<itr<<std::setw(4)<<trk[itr].ID;
3208  myprt<<std::right<<std::setw(5)<<std::setw(4)<<trk[itr].Proc;
3209  unsigned short nht = 0;
3210  for(unsigned short ii = 0; ii < 3; ++ii) nht += trk[itr].TrkHits[ii].size();
3211  myprt<<std::right<<std::setw(5)<<nht;
3212  myprt<<std::setw(4)<<trk[itr].TrjPos.size();
3213  myprt<<std::fixed;
3214  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<trk[itr].TrjPos[0](0);
3215  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<trk[itr].TrjPos[0](1);
3216  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<trk[itr].TrjPos[0](2);
3217  unsigned short itj = trk[itr].TrjPos.size() - 1;
3218  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<trk[itr].TrjPos[itj](0);
3219  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<trk[itr].TrjPos[itj](1);
3220  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<trk[itr].TrjPos[itj](2);
3221  myprt<<std::setw(4)<<trk[itr].VtxIndex[0]<<std::setw(4)<<trk[itr].VtxIndex[1];
3222  myprt<<std::setw(4)<<trk[itr].GoodEnd[0];
3223  myprt<<std::setw(4)<<trk[itr].GoodEnd[1];
3224  myprt<<std::setw(4)<<trk[itr].ChgOrder;
3225  myprt<<std::right<<std::setw(10)<<std::setprecision(3)<<trk[itr].TrjDir[itj](2);
3226  myprt<<std::right<<std::setw(4)<<trk[itr].MomID;
3227  myprt<<std::right<<std::setw(5)<<trk[itr].PDGCode<<" ";
3228  for(unsigned short ii = 0; ii < trk[itr].ClsEvtIndices.size(); ++ii) myprt<<" "<<trk[itr].ClsEvtIndices[ii];
3229  myprt<<"\n";
3230  } // itr
3231 
3232  } // PrintTracks
3233 
3234 
3237  {
3238 
3239  unsigned short iTime;
3240  mf::LogVerbatim myprt("CCTM");
3241  myprt<<"******* PrintClusters ********* Num_Clusters_in Wire:Time\n";
3242  myprt<<"vtx Index X Y Z Pln0 Pln1 Pln2 Pln0 Pln1 Pln2\n";
3243  for(unsigned short ivx = 0; ivx < vtx.size(); ++ivx) {
3244  myprt<<std::right<<std::setw(3)<<ivx<<std::setw(7)<<ivx;
3245  myprt<<std::fixed;
3246  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<vtx[ivx].X;
3247  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<vtx[ivx].Y;
3248  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<vtx[ivx].Z;
3249  myprt<<std::right<<std::setw(5)<<vtx[ivx].nClusInPln[0];
3250  myprt<<std::right<<std::setw(5)<<vtx[ivx].nClusInPln[1];
3251  myprt<<std::right<<std::setw(5)<<vtx[ivx].nClusInPln[2];
3252  myprt<<" ";
3253  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) {
3254  int time = (0.5 + detprop->ConvertXToTicks(vtx[ivx].X, ipl, tpc, cstat));
3255  int wire = geom->WireCoordinate(vtx[ivx].Y, vtx[ivx].Z, ipl, tpc, cstat);
3256  myprt<<std::right<<std::setw(7)<<wire<<":"<<time;
3257  }
3258 
3259  myprt<<"\n";
3260  } // ivx
3261 
3262  for(unsigned short ipl = 0; ipl < nplanes; ++ipl) {
3263  myprt<<">>>>>>>>>> Cluster chains in Plane "<<ipl<<"\n";
3264  myprt<<"ipl ccl Len Chg W0:T0 Ang0 Dir0 Vx0 mBk0 W1:T1 Ang1 Dir1 Vx1 mBk1 InTk cls:Order \n";
3265  for(unsigned short ccl = 0; ccl < clsChain[ipl].size(); ++ccl) {
3266  myprt<<std::right<<std::setw(3)<<ipl;
3267  myprt<<std::right<<std::setw(5)<<ccl;
3268  myprt<<std::right<<std::setw(6)<<clsChain[ipl][ccl].Length;
3269  myprt<<std::right<<std::setw(8)<<(int)clsChain[ipl][ccl].TotChg;
3270  for(unsigned short end = 0; end < 2; ++end) {
3271  iTime = clsChain[ipl][ccl].Time[end];
3272  myprt<<std::right<<std::setw(5)<<(int)clsChain[ipl][ccl].Wire[end]
3273  <<":"<<std::setprecision(1)<<iTime;
3274  if(iTime < 10) {
3275  myprt<<" ";
3276  } else if(iTime < 100) {
3277  myprt<<" ";
3278  } else if(iTime < 1000) myprt<<" ";
3279  myprt<<std::right<<std::setw(7)<<std::setprecision(2)<<clsChain[ipl][ccl].Angle[end];
3280  myprt<<std::right<<std::setw(5)<<clsChain[ipl][ccl].Dir[end];
3281  myprt<<std::right<<std::setw(5)<<clsChain[ipl][ccl].VtxIndex[end];
3282  myprt<<std::fixed<<std::right<<std::setw(6)<<std::setprecision(1)<<clsChain[ipl][ccl].mBrkIndex[end];
3283 // myprt<<std::fixed<<std::right<<std::setw(6)<<std::setprecision(1)<<clsChain[ipl][ccl].ChgNear[end];
3284  }
3285  myprt<<std::right<<std::setw(7)<<clsChain[ipl][ccl].InTrack;
3286  myprt<<" ";
3287  for(unsigned short ii = 0; ii < clsChain[ipl][ccl].ClsIndex.size(); ++ii)
3288  myprt<<" "<<clsChain[ipl][ccl].ClsIndex[ii]<<":"<<clsChain[ipl][ccl].Order[ii];
3289  myprt<<"\n";
3290  } // ccl
3291  if(fPrintAllClusters) {
3292  myprt<<">>>>>>>>>> Clusters in Plane "<<ipl<<"\n";
3293  myprt<<"ipl icl Evt Len Chg W0:T0 Ang0 Dir0 Vx0 CN0 W1:T1 Ang1 Dir1 Vx1 CN1 InTk Brk0 MrgEr0 Brk1 MrgEr1\n";
3294  for(unsigned short icl = 0; icl < cls[ipl].size(); ++icl) {
3295  myprt<<std::right<<std::setw(3)<<ipl;
3296  myprt<<std::right<<std::setw(5)<<icl;
3297  myprt<<std::right<<std::setw(5)<<cls[ipl][icl].EvtIndex;
3298  myprt<<std::right<<std::setw(6)<<cls[ipl][icl].Length;
3299  myprt<<std::right<<std::setw(8)<<(int)cls[ipl][icl].TotChg;
3300  for(unsigned short end = 0; end < 2; ++end) {
3301  iTime = cls[ipl][icl].Time[end];
3302  myprt<<std::right<<std::setw(5)<<(int)cls[ipl][icl].Wire[end]<<":"<<iTime;
3303  if(iTime < 10) {
3304  myprt<<" ";
3305  } else if(iTime < 100) {
3306  myprt<<" ";
3307  } else if(iTime < 1000) myprt<<" ";
3308  myprt<<std::right<<std::setw(7)<<std::setprecision(2)<<cls[ipl][icl].Angle[end];
3309  myprt<<std::right<<std::setw(5)<<cls[ipl][icl].Dir[end];
3310  myprt<<std::right<<std::setw(5)<<cls[ipl][icl].VtxIndex[end];
3311  myprt<<std::fixed<<std::right<<std::setw(5)<<std::setprecision(1)<<cls[ipl][icl].ChgNear[end];
3312  }
3313  myprt<<std::fixed;
3314  myprt<<std::right<<std::setw(5)<<cls[ipl][icl].InTrack;
3315  myprt<<std::right<<std::setw(5)<<(int)cls[ipl][icl].BrkIndex[0];
3316  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<cls[ipl][icl].MergeError[0];
3317  myprt<<std::right<<std::setw(5)<<(int)cls[ipl][icl].BrkIndex[1];
3318  myprt<<std::right<<std::setw(7)<<std::setprecision(1)<<cls[ipl][icl].MergeError[1];
3319  myprt<<"\n";
3320  } // icl
3321  } // fPrintAllClusters
3322  } // ipl
3323  } // PrintClusters
3324 
3326  float CCTrackMaker::AngleFactor(float slope)
3327  {
3328  float slp = fabs(slope);
3329  if(slp > 10.) slp = 30.;
3330  // return a value between 1 and 46
3331  return 1 + 0.05 * slp * slp;
3332  } // AngleFactor
3333 
3335  float CCTrackMaker::ChargeNear(unsigned short ipl, unsigned short wire1, float time1, unsigned short wire2, float time2)
3336  {
3337  // returns the hit charge along a line between (wire1, time1) and
3338  // (wire2, time2)
3339 
3340  // put in increasing wire order (wire2 > wire1)
3341  unsigned short w1 = wire1;
3342  unsigned short w2 = wire2;
3343  double t1 = time1;
3344  double t2 = time2;
3345  double slp, prtime;
3346  if(w1 == w2) {
3347  slp = 0;
3348  } else {
3349  if(w1 > w2) {
3350  w1 = wire2;
3351  w2 = wire1;
3352  t1 = time2;
3353  t2 = time1;
3354  }
3355  slp = (t2 - t1) / (w2 - w1);
3356  }
3357 
3358  unsigned short wire;
3359 
3360  float chg = 0;
3361  for(unsigned short hit = 0; hit < allhits.size(); ++hit) {
3362  if(allhits[hit]->WireID().Cryostat != cstat) continue;
3363  if(allhits[hit]->WireID().TPC != tpc) continue;
3364  if(allhits[hit]->WireID().Plane != ipl) continue;
3365  wire = allhits[hit]->WireID().Wire;
3366  if(wire < w1) continue;
3367  if(wire > w2) continue;
3368  prtime = t1 + (wire - w1) * slp;
3369  // std::cout<<"prtime "<<wire<<":"<<(int)prtime<<" hit "<<allhits[hit]->PeakTimeMinusRMS(3)<<" "<<allhits[hit]->PeakTimePlusRMS(3)<<"\n";
3370  if(prtime > allhits[hit]->PeakTimePlusRMS(3)) continue;
3371  if(prtime < allhits[hit]->PeakTimeMinusRMS(3)) continue;
3372  chg += ChgNorm[ipl] * allhits[hit]->Integral();
3373  } // hit
3374  return chg;
3375  } // ChargeNear
3376 
3379  {
3380  // fills the WireHitRange vector. Slightly modified version of the one in ClusterCrawlerAlg
3381 
3382  unsigned short ipl;
3383 
3384  // initialize everything
3385  for(ipl = 0; ipl < 3; ++ipl) {
3386  firstWire[ipl] = INT_MAX;
3387  lastWire[ipl] = 0;
3388  firstHit[ipl] = INT_MAX;
3389  lastHit[ipl] = 0;
3390  WireHitRange[ipl].clear();
3391  ChgNorm[ipl] = 0;
3392  }
3393 
3394  // find the first and last wire with a hit
3395  unsigned short oldipl = 0;
3396  for(unsigned int hit = 0; hit < allhits.size(); ++hit) {
3397  if(allhits[hit]->WireID().Cryostat != cstat) continue;
3398  if(allhits[hit]->WireID().TPC != tpc) continue;
3399  ipl = allhits[hit]->WireID().Plane;
3400  if(allhits[hit]->WireID().Wire > geom->Nwires(ipl, tpc, cstat)) {
3401  if(lastWire[ipl] < firstWire[ipl]) {
3402  mf::LogError("CCTM")<<"Invalid WireID().Wire "<<allhits[hit]->WireID().Wire;
3403  return;
3404  }
3405  }
3406 // ChgNorm[ipl] += allhits[hit]->Integral();
3407  if(ipl < oldipl) {
3408  mf::LogError("CCTM")<<"Hits are not in increasing-plane order\n";
3409  return;
3410  }
3411  oldipl = ipl;
3412  if(firstHit[ipl] == INT_MAX) {
3413  firstHit[ipl] = hit;
3414  firstWire[ipl] = allhits[hit]->WireID().Wire;
3415  }
3416  lastHit[ipl] = hit;
3417  lastWire[ipl] = allhits[hit]->WireID().Wire;
3418  } // hit
3419 
3420  // xxx
3421  for(ipl = 0; ipl < nplanes; ++ipl) {
3422  if(lastWire[ipl] < firstWire[ipl]) {
3423  mf::LogError("CCTM")<<"Invalid first/last wire in plane "<<ipl;
3424  return;
3425  }
3426  } // ipl
3427 
3428  // normalize charge in induction planes to the collection plane
3429 // for(ipl = 0; ipl < nplanes; ++ipl) ChgNorm[ipl] = ChgNorm[nplanes - 1] / ChgNorm[ipl];
3430  for(ipl = 0; ipl < nplanes; ++ipl) ChgNorm[ipl] = 1;
3431 
3432  // get the service to learn about channel status
3433  //lariov::ChannelStatusProvider const& channelStatus
3434  // = art::ServiceHandle<lariov::ChannelStatusService>()->GetProvider();
3435 
3436  // now we can define the WireHitRange vector.
3437  int sflag, nwires, wire;
3438  unsigned int indx, thisWire, thisHit, lastFirstHit;
3439  //uint32_t chan;
3440  for(ipl = 0; ipl < nplanes; ++ipl) {
3441  nwires = lastWire[ipl] - firstWire[ipl] + 1;
3442  WireHitRange[ipl].resize(nwires);
3443  // start by defining the "no hits on wire" condition
3444  sflag = -2;
3445  for(wire = 0; wire < nwires; ++wire) WireHitRange[ipl][wire] = std::make_pair(sflag, sflag);
3446  // overwrite with the "dead wires" condition
3447  sflag = -1;
3448  for(wire = 0; wire < nwires; ++wire) {
3449  //chan = geom->PlaneWireToChannel(ipl, wire, tpc, cstat);
3450  //if(channelStatus.IsBad(chan)) {
3451  // indx = wire - firstWire[ipl];
3452  // WireHitRange[ipl][indx] = std::make_pair(sflag, sflag);
3453  //}
3454  } // wire
3455  // next overwrite with the index of the first/last hit on each wire
3456  lastWire[ipl] = firstWire[ipl];
3457  thisHit = firstHit[ipl];
3458  lastFirstHit = firstHit[ipl];
3459  for(unsigned int hit = firstHit[ipl]; hit <= lastHit[ipl]; ++hit) {
3460  thisWire = allhits[hit]->WireID().Wire;
3461  if(thisWire > lastWire[ipl]) {
3462  indx = lastWire[ipl] - firstWire[ipl];
3463  int tmp1 = lastFirstHit;
3464  int tmp2 = thisHit;
3465  WireHitRange[ipl][indx] = std::make_pair(tmp1, tmp2);
3466  lastWire[ipl] = thisWire;
3467  lastFirstHit = thisHit;
3468  } else if(thisWire < lastWire[ipl]) {
3469  mf::LogError("CCTM")<<"Hit not in proper order in plane "<<ipl;
3470  exit(1);
3471  }
3472  ++thisHit;
3473  } // hit
3474  // define the last wire
3475  indx = lastWire[ipl] - firstWire[ipl];
3476  int tmp1 = lastFirstHit;
3477  ++lastHit[ipl];
3478  int tmp2 = lastHit[ipl];
3479  WireHitRange[ipl][indx] = std::make_pair(tmp1, tmp2);
3480  // add one to lastWire and lastHit for more standard indexing
3481  ++lastWire[ipl];
3482  } // ipl
3483 
3484  // error checking
3485  for(ipl = 0; ipl < nplanes; ++ipl) {
3486  if(firstWire[ipl] < INT_MAX) continue;
3487  if(lastWire[ipl] > 0) continue;
3488  if(firstHit[ipl] < INT_MAX) continue;
3489  if(lastHit[ipl] > 0) continue;
3490  std::cout<<"FWHR problem\n";
3491  exit(1);
3492  } // ipl
3493 
3494  unsigned int nht = 0;
3495  std::vector<bool> hchk(allhits.size());
3496  for(unsigned int ii = 0; ii < hchk.size(); ++ii) hchk[ii] = false;
3497  for(ipl = 0; ipl < nplanes; ++ipl) {
3498  for(unsigned int w = firstWire[ipl]; w < lastWire[ipl]; ++w) {
3499  indx = w - firstWire[ipl];
3500  if(indx > lastWire[ipl]) {
3501  std::cout<<"FWHR bad index "<<indx<<"\n";
3502  exit(1);
3503  }
3504  // no hit on this wire
3505  if(WireHitRange[ipl][indx].first < 0) continue;
3506  unsigned int firhit = WireHitRange[ipl][indx].first;
3507  unsigned int lashit = WireHitRange[ipl][indx].second;
3508  for(unsigned int hit = firhit; hit < lashit; ++hit) {
3509  ++nht;
3510  if(hit > allhits.size() -1) {
3511  std::cout<<"FWHR: Bad hchk "<<hit<<" size "<<allhits.size()<<"\n";
3512  continue;
3513  }
3514  hchk[hit] = true;
3515  if(allhits[hit]->WireID().Plane != ipl || allhits[hit]->WireID().Wire != w) {
3516  std::cout<<"FWHR bad plane "<<allhits[hit]->WireID().Plane<<" "<<ipl<<" or wire "<<allhits[hit]->WireID().Wire<<" "<<w<<"\n";
3517  exit(1);
3518  }
3519  } // hit
3520  } // w
3521  } // ipl
3522 
3523  if(nht != allhits.size()) {
3524  std::cout<<"FWHR hit count problem "<<nht<<" "<<allhits.size()<<"\n";
3525  for(unsigned int ii = 0; ii < hchk.size(); ++ii) if(!hchk[ii]) std::cout<<" "<<ii<<" "<<allhits[ii]->WireID().Plane<<" "<<allhits[ii]->WireID().Wire<<" "<<(int)allhits[ii]->PeakTime()<<"\n";
3526  exit(1);
3527  }
3528 
3529  } // FillWireHitRange
3531 
3532 } // namespace
MaybeLogger_< ELseverityLevel::ELsev_info, true > LogVerbatim
std::vector< MatchPars > matcomb
constexpr auto const & right(const_AssnsIter< L, R, D, Dir > const &a, const_AssnsIter< L, R, D, Dir > const &b)
Definition: AssnsIter.h:112
std::array< std::vector< art::Ptr< recob::Hit > >, 3 > TrkHits
TTree * t1
Definition: plottest35.C:26
std::string fVertexModuleLabel
ROOT::Math::SMatrix< Double32_t, 5, 5, ROOT::Math::MatRepSym< Double32_t, 5 > > SMatrixSym55
Definition: TrackingTypes.h:85
std::vector< Vector_t > convertCollToVector(std::vector< Vector > const &coll)
Definition: TrackingTypes.h:58
std::vector< float > fChgAsymFactor
std::array< unsigned short, 3 > End
Declaration of signal hit object.
void PrintClusters()
std::array< unsigned int, 3 > firstWire
Float_t Y
Definition: plot.C:39
Planes which measure X direction.
Definition: geo_types.h:81
Geometry information for a single TPC.
Definition: TPCGeo.h:37
std::vector< art::Ptr< recob::Hit > > allhits
std::array< short, 2 > Dir
TrackTrajectory::Flags_t Flags_t
Definition: Track.h:71
float StartWire() const
Returns the wire coordinate of the start of the cluster.
Definition: Cluster.h:286
Plane(const Point_t &planePos, const Vector_t &planeDir)
Constructor from reference position on the plane and direction orthogonal to the plane.
Definition: TrackingPlane.h:61
std::string fClusterModuleLabel
TrackTrajectoryAlg fTrackTrajectoryAlg
Set of hits with a 2D structure.
Definition: Cluster.h:71
std::array< short, 2 > BrkIndex
float EndTick() const
Returns the tick coordinate of the end of the cluster.
Definition: Cluster.h:342
Float_t tmp
Definition: plot.C:37
std::vector< unsigned short > pfpToTrkID
data_const_reference data(size_type i) const
Definition: FindManyP.h:458
std::vector< TVector3 > TrjPos
std::vector< TrkPar > trk
MaybeLogger_< ELseverityLevel::ELsev_error, false > LogError
Cluster finding and building.
std::array< float, 3 > ChgNorm
std::vector< float > fAngleMatchErr
std::array< float, 2 > ChgNear
std::array< std::vector< clPar >, 3 > cls
float StartAngle() const
Returns the starting angle of the cluster.
Definition: Cluster.h:475
Definition of vertex object for LArSoft.
Definition: Vertex.h:35
std::array< float, 2 > Slope
std::array< bool, 2 > EndInTPC
std::array< std::vector< art::Ptr< recob::Hit > >, 3 > seedHits
Float_t Z
Definition: plot.C:39
ProductID put(std::unique_ptr< PROD > &&product)
Definition: Event.h:102
std::array< std::vector< ClsChainPar >, 3 > clsChain
float EndCharge() const
Returns the charge on the last wire of the cluster.
Definition: Cluster.h:498
Int_t max
Definition: plot.C:27
TCanvas * c1
Definition: plotHisto.C:7
TCanvas * c2
Definition: plot_hist.C:75
#define DEFINE_ART_MODULE(klass)
Definition: ModuleMacros.h:42
Provides recob::Track data product.
void beginJob()
Definition: Breakpoints.cc:14
long seed
Definition: chem4.cc:68
A trajectory in space reconstructed from hits.
std::array< std::vector< std::pair< int, int > >, 3 > WireHitRange
std::vector< float > fMatchMinLen
T get(std::string const &key) const
Definition: ParameterSet.h:231
std::vector< evd::details::RawDigitInfo_t >::const_iterator begin(RawDigitCacheDataClass const &cache)
std::vector< unsigned short > Order
std::array< short, 2 > VtxIndex
std::array< short, 2 > mVtxIndex
std::vector< vtxPar > vtx
std::array< float, 2 > MergeError
void fill(const art::PtrVector< recob::Hit > &hits, int only_plane)
std::array< bool, 2 > GoodEnd
bool CreateAssn(PRODUCER const &prod, art::Event &evt, std::vector< T > const &a, art::Ptr< U > const &b, art::Assns< U, T > &assn, std::string a_instance, size_t indx=UINT_MAX)
Creates a single one-to-one association.
std::vector< Point_t > convertCollToPoint(std::vector< Point > const &coll)
Definition: TrackingTypes.h:55
Declaration of cluster object.
std::array< unsigned int, 3 > firstHit
std::array< std::vector< art::Ptr< recob::Hit > >, 3 > trkHits
std::array< float, 2 > Charge
std::vector< unsigned short > ClsIndex
std::vector< unsigned short > ClsEvtIndices
Detector simulation of raw signals on wires.
std::array< unsigned int, 3 > lastWire
TTree * t2
Definition: plottest35.C:36
Class defining a plane for tracking. It provides various functionalities to convert track parameters ...
Definition: TrackingPlane.h:37
Encapsulate the geometry of a wire.
bool greaterThan(CluLen c1, CluLen c2)
Hierarchical representation of particle flow.
Definition: PFParticle.h:44
Utility object to perform functions of association.
Float_t norm
Encapsulate the construction of a single detector plane.
float StartCharge() const
Returns the charge on the first wire of the cluster.
Definition: Cluster.h:454
TDirectory * dir
Definition: macro.C:5
bool getByLabel(std::string const &label, std::string const &productInstanceName, Handle< PROD > &result) const
Definition: DataViewImpl.h:344
std::array< short, 2 > VtxIndex
void clear()
Definition: Handle.h:237
std::array< unsigned int, 3 > lastHit
std::array< float, 2 > X
MaybeLogger_< ELseverityLevel::ELsev_warning, false > LogWarning
art::ServiceHandle< geo::Geometry > geom
bool lessThan(CluLen c1, CluLen c2)
bool FillWireHitRange(TCSlice &slc)
Definition: Utils.cxx:3944
std::vector< evd::details::RawDigitInfo_t >::const_iterator end(RawDigitCacheDataClass const &cache)
std::vector< TVector3 > TrjDir
TCEvent evt
Definition: DataStructs.cxx:5
std::vector< bool > fMakeAlgTracks
std::vector< short > fMatchAlgs
std::vector< float > fXMatchErr
void fill_ptr_vector(std::vector< Ptr< T >> &ptrs, H const &h)
Definition: Ptr.h:464
float EndAngle() const
Returns the ending angle of the cluster.
Definition: Cluster.h:519
recob::tracking::Plane Plane
Definition: TrackState.h:17
Float_t X
Definition: plot.C:39
Float_t track
Definition: plot.C:34
std::array< float, 2 > Wire
Float_t w
Definition: plot.C:23
float StartTick() const
Returns the tick coordinate of the start of the cluster.
Definition: Cluster.h:297
void LocalToWorld(const double *tpc, double *world) const
Transform point from local TPC frame to world frame.
Definition: TPCGeo.h:490
std::array< short, 2 > Time
art framework interface to geometry description
Track from a non-cascading particle.A recob::Track consists of a recob::TrackTrajectory, plus additional members relevant for a "fitted" track:
Definition: Track.h:52
std::array< float, 2 > Angle
vec_iX clear()
cet::coded_exception< error, detail::translate > exception
Definition: exception.h:33
const detinfo::DetectorProperties * detprop
Encapsulate the construction of a single detector plane.
std::array< std::vector< unsigned short >, 3 > vxCls
std::array< unsigned short, 3 > nClusInPln
unsigned short fNVtxTrkHitsFit
float Integral() const
Returns the total charge of the cluster from hit shape.
Definition: Cluster.h:600
float EndWire() const
Returns the wire coordinate of the end of the cluster.
Definition: Cluster.h:329
vertex reconstruction