EIC Software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
HoughTree.cxx
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file HoughTree.cxx
1 //
2 // AYK (ayk@bnl.gov)
3 //
4 // Hough transform track finder;
5 //
6 // Initial port from OLYMPUS sources: Oct'2015;
7 //
8 
9 #include <cassert>
10 #include <cstdio>
11 #include <cstdlib>
12 #include <cstring>
13 
14 #include <HoughTree.h>
15 
16 // ---------------------------------------------------------------------------------------
17 
19  mInitialized(false),
20  mVerbosityLevel(0),
21  mBorrowedHitCounterMax(0),
22  mBorrowedPlusMissingHitCounterMax(0),
23  mGeometryLocked(false),
24  mMinOkHitCounter(0),
25  mMaxOkHitCounter(0),
26  mBlindCellDecisionLevel(0),
27  mCellAcceptanceDecisionLevel(0),
28  mCellTree(0),
29  mBinaryTree(0),
30  mCurrMinOkHitCounter(0),
31  mMatchCandidateCount(0),
32  mSingleCellEdgeNum(1),
33  mFastTreeSearchMode(false),
34  mTrackQualityIterationNum(1)
35 {
36  // Allocate blind cell pointer;
37  mBlindCell = new HoughCell(this);
38 } // HoughTree::HoughTree()
39 
40 // ---------------------------------------------------------------------------------------
41 
42 int HoughTree::AddDimension(const char *name, double min, double max)
43 {
44  // Some sanity check is needed;
45  if (mGeometryLocked) return -1;
46 
47  mDimensions.push_back(HoughDimension(name, min, max));
48 
49  mSingleCellEdgeNum *= 2;
50 
51  return 0;
52 } // HoughTree::AddDimension()
53 
54 // ---------------------------------------------------------------------------------------
55 
56 int HoughTree::AddResolutionLevelCore(const unsigned div[])
57 {
58  // Well, prefer do all the work in this constructor; passing 'this' looks
59  // ugly, but is the easiest way to hide the ResolutionLevel internals;
60  // follows below in this call;
61  mResolutionLevels.push_back(new ResolutionLevel(this, div));
62 
63  // Yes, current highest level per default;
65 
66  return 0;
67 } // HoughTree::AddResolutionLevelCore()
68 
69 // ---------------------------------------------------------------------------------------
70 
71 static unsigned bits(__u64 value)
72 {
73  unsigned count = 0;
74 
75  // Subtract 1 and shift to the right till get zero value;
76  for(value--; value; count++)
77  value >>= 1;
78 
79  return count;
80 } // bits()
81 
82 // ---------------------------------------------------------------------------------------
83 
85 {
86  t_btree_index multi = 1;
87  ResolutionLevel *high = GetLevel(GetLdim() - 1);
88 
89  for(int iq=0; iq<mDimensions.size(); iq++) {
90  // Put a crude overflow check; NB: '+1' because need number of cell
91  // edges, not cell centers;
92  if (bits(multi) + bits(high->GetTotalDivisionNumber(iq)+1) > _MAX_BTREE_INDEX_DEPTH_ + 1) {
93  printf("HoughTree::AllocateLookUpTable(): t_btree_index width too small for this granularity!\n");
94  return -1;
95  } //if
96 
97  multi *= high->GetTotalDivisionNumber(iq) + 1;
98  } //for iq
99 
100  // NB: even on the 0-th level (no split) there are 2^ndim cube edges;
101  // FIXME: a less efficient temporary hack (just a plain pointer array);
102  mBinaryTree = new t_hough_range*[multi];
103  memset(mBinaryTree, 0x00, multi*sizeof(t_hough_range*));
104 
105  return 0;
106 } // HoughTree::AllocateLookUpTable()
107 
108 // ---------------------------------------------------------------------------------------
109 
110 int HoughTree::AddResolutionLevel(const unsigned div[])
111 {
112  unsigned ndim = mDimensions.size();
113 
114  if (!ndim || !div || mInitialized) return -1;
115 
116  // If the very first level is not defined yet, do it; just declare
117  // "no division" for all parameters;
118  if (!GetLdim()) {
119  unsigned pdim[ndim];
120 
121  for(int iq=0; iq<ndim; iq++)
122  pdim[iq] = 1;
123 
124  if (AddResolutionLevelCore(pdim)) return -1;
125  } //if
126 
127  // Lock parameter space geometry;
128  mGeometryLocked = true;
129 
130  // Allocate next level and return;
131  return AddResolutionLevelCore(div);
132 } // HoughTree::AddResolutionLevel()
133 
134 // ---------------------------------------------------------------------------------------
135 
136 HoughNodeGroup *HoughTree::AddNodeGroup(unsigned id, unsigned cdim, const double min[],
137  const double max[], const double gra[])
138 {
139  // FIXME: move this check to HoughNodeGroup constructor;
140  if (mInitialized || !cdim || !min || !max || !gra) return 0;
141 
142  HoughNodeGroup *ngroup = AllocateNodeGroup(id);
143  ngroup->ConfigureCoordinateDescriptors(cdim, min, max, gra);
144  mGroups.push_back(ngroup);
145 
146  // Just allocate these std::map entries once forever;
147  mGlimits[id] = mGcounters[id] = 0;
148 
149  return ngroup;
150 } // HoughTree::AddNodeGroup()
151 #if _LATER_
152 HoughNodeGroup *HoughTree::AddNodeGroup(void *ptr, double min, double max, double gra, SpaceGridType gridType)
153 {
154  //if (!wdim) return -1;
155 
156  const double qmin[1] = {min}, qmax[1] = {max}, qgra[1] = {gra};
157 
158  return AddNodeGroup(ptr, 0, 1, qmin, qmax, qgra, gridType);
159 } // HoughTree::AddNodeGroup()
160 
161 HoughNodeGroup *HoughTree::AddNodeGroup(void *ptr, unsigned cdim, const double min[], const double max[],
162  const double gra[], SpaceGridType gridType)
163 {
164  return AddNodeGroup(ptr, 0, cdim, min, max, gra, gridType);
165 } // HoughTree::AddNodeGroup()
166 #endif
167 // ---------------------------------------------------------------------------------------
168 
169 static void mk_prefix(unsigned lv, char arr[])
170 {
171  sprintf(arr, "%2d", lv);
172  for(unsigned iq=0; iq<lv; iq++)
173  sprintf(arr + strlen(arr), "%s", " ");
174 } // mk_prefix()
175 
176 // ---------------------------------------------------------------------------------------
177 
178 HoughCell *HoughTree::GetInitializedCell(unsigned lv, const unsigned id[])
179 {
180  ResolutionLevel *level = GetLevel(lv);
181  HoughCell *cell = new HoughCell(this);
182 
183 #if _CHECK_
184  // For now the logic is: 1) calculate track parameterizations for
185  // all 2**GetDdim() cell parameter rectangle edges, 2) calculate crossing
186  // of these tracks with all the GetGdim() registering planes; if all
187  // crossing points are in the acceptance, just assign range variables;
188  // if at least one {edge,plane} combination falls out of acceptance,
189  // go to higher resolution levels up to
190  // cell_acceptance_decision_level, perform calculations there
191  // and take range for this given cell as an OR of all its daughter
192  // cells; NB: at the cell_acceptance_decision_level and higher
193  // levels range for a given plane will just be the OR of ALL EDGES;
194  // this clearly exagerates acceptance a bit, but one has to consider
195  // using fine granularity at >=cell_acceptance_decision_level
196  // resolution levels anyway;
197 #endif
198  {
199  int daughter_calculation_needed = 0;
200  // gdim, ddim: save typing;
201  unsigned ok_group_sum = 0, gdim = GetGdim(), ddim = GetDdim();
202  // Reset all ok[] entries to false first;
203  bool ok[gdim]; memset(ok, 0x00, sizeof(ok));
204 
205  // Loop through all cell rectangle edges;
206  for(int ip=0; ip<mSingleCellEdgeNum; ip++) {
207  unsigned lr[ddim];
208 
209  // Calculate individual ddim-indices (basically left-right); order does not
210  // really matter here?; reversed order below just for a better printout?;
211  for(int iq=0; iq<ddim; iq++)
212  lr[ddim-iq-1] = (ip >> iq) & 0x1;
213 
214  {
215  double par[ddim];
216  t_hough_range range[gdim], *rptr = range;
217  t_btree_index idx = 0;
218 
219  // Try to get stored value first; calculate index;
220  {
221  ResolutionLevel *high = GetLevel(GetLdim() - 1);
222 
223  // Same logic as in add_hough_resolution_level_core(); NB: dimensions
224  // are +1 here (need cell edges);
225  {
226  // id(a,b,c,d) = a*d2*d3*d4 + b*d3*d4 + c*d4 + d; sdim[] is just array of
227  // coefficients by {a,b,c,d}; FIXME: unify with earlier code!;
228  unsigned sdim[ddim];
229 
230  for(int iq=ddim-1; iq>=0; iq--)
231  sdim[iq] = iq == ddim-1 ? 1 : (high->GetTotalDivisionNumber(iq+1)+1)*sdim[iq+1];
232 
233  // Calculate cell edge multi-index at the highest resolution level;
234  for(int iq=0; iq<ddim; iq++)
235  idx += sdim[iq]*(id[iq]+lr[iq])*
236  (high->GetTotalDivisionNumber(iq)/level->GetTotalDivisionNumber(iq));
237  }
238  }
239 
240  // If lookup entry exists, use it; unless need to calculate offsets
241  // (because otherwise average values will be screwed up);
242  if (mBinaryTree[idx])
243  rptr = mBinaryTree[idx];
244  else {
245  // Fill out parameter vector for this rectangle edge;
246  for(int iq=0; iq<ddim; iq++)
247  par[iq] = mDimensions[iq].GetMin() + (id[iq]+lr[iq])*level->GetCellSize(iq);
248 
249  // Call user-defined routine which would fill range[] array;
250  MappingCall(par, range);
251 
252  // Allocate the lookup entry;
253  {
254  t_hough_range *ptr = mBinaryTree[idx] = new t_hough_range[gdim];
255 
256  // Copy over the result for further usage;
257  for(int iq=0; iq<gdim; iq++)
258  ptr[iq] = range[iq];
259  }
260  } //if
261 
262  // Check "out of range" condition; fill out of range arrays;
263  for(int gr=0; gr<gdim; gr++) {
264  HoughNodeGroup *group = mGroups[gr];
265 
266  // NB: for now do not break if any of the return indices is out
267  // of range (well, this is inefficient, fix later);
268  if (rptr[gr] & __OUT_OF_RANGE_BIT_) {
269  if (lv < mCellAcceptanceDecisionLevel) {
270  daughter_calculation_needed = 1;
271 
272  // THINK: this fallout will affect offsets calculations; perhaps
273  // just check "offsets" pointer here?; think & return back later;
274 #if _THINK_
275  // Well, if at least one Ok edge found for each of the groups,
276  // and daughter calculation will be required anyway, makes no
277  // sense to continue calculations on this level, right?;
278  if (ok_group_sum == gdim) goto _fall_out;
279 #endif
280 
281  // So that cell->range[gr] will not be affected;
282  continue;
283  } //if
284  }
285  else {
286  // Account once for each group;
287  if (!ok[gr]) ok_group_sum++;
288 
289  // Well, at least one edge was Ok for this group;
290  ok[gr] = true;
291  } //if
292 
293  // Update ranges; for cell_acceptance_decision_level and higher this
294  // will be done no matter was this edge out of acceptance or not;
295  cell->UpdateRanges(this, rptr);
296  } //for gr
297  }
298  } //for ip
299 
300 _fall_out:
301  // Well, at the mBlindCellDecisionLevel I still want to make sure that
302  // at least one edge was fine for a given group (NB: on this level cell ranges
303  // are assigned anyway, no matter in or out of acceptance edges were);
304  cell->ResetRanges(this, ok);
305 
306  // Use absolutely min number of hits as a condition here (apparently
307  // this initialization is the same for the whole program life time,
308  // no matter with which mCurrMinOkHitCounter I'm running right now);
310  goto _cleanup;
311 
312  // Ok, if daughter calculation is requested (which basically means part
313  // of the parameter rectangle edges were out of acceptance for at least
314  // one registering plane), allocate daughter memory and calculate these
315  // cells recursively;
316  if (daughter_calculation_needed) {
317  // Unify with the other similar code later;
318  unsigned idq[ddim];
319 
320  // For sure I can enter each cell initialization routine only once; so
321  // if daughter calculation is needed now, I have not touched any of these
322  // daughters yet;
323  assert(!cell->DaughtersArrayAllocated());
324 
325  // Allocate daughter array with NULL pointers; NB: "lv != lnum-1" condition
326  // has been checked when assigning "daughter_calculation_needed" flag;
327  ResolutionLevel *next = GetLevel(lv+1);
328  cell->AllocateDaughterCells(next->GetDaughterCellNumber());
329 
330  // Well, make it clean; so reset to _OUT_OF_RANGE_BIT_ first;
331  cell->ResetRanges(this);
332 
333  // Now loop through all the daughters and use their from[]/to[] ranges;
334  for(unsigned iq=0; iq<next->GetDaughterCellNumber(); iq++) {
335  for(int ip=0; ip<ddim; ip++)
336  idq[ip] = id[ip]*next->GetParameterSplitFactor(ip) + next->Remap(iq,ip);
337 
338  {
339  // Yes, initialization is for sure needed here (handle this cell
340  // for the first time, sure);
341  HoughCell *dcell = GetInitializedCell(lv+1, idq);
342  cell->SetDaughter(iq, dcell);
343 
344  // Update ranges on parent cell using daughter cell ones;
345  if (dcell != mBlindCell) {
346  cell->UpdateRanges(this, dcell->From());
347  cell->UpdateRanges(this, dcell->To());
348  } //if
349  }
350  } //for iq
351  } //if
352  }
353 
354  // Now, after all tricks including daughter cell range calculations, count
355  // useful group number on this (parent) cell; FIXME: yes, perhaps once again;
356  if (GetUsefulGroupCount(cell) >= mMinOkHitCounter) return cell;
357 
358 _cleanup:
359  delete cell;
360 
361  return mBlindCell;
362 } // HoughTree::GetInitializedCell()
363 
364 // ---------------------------------------------------------------------------------------
365 
367 {
368  std::map<MatchCandidate*, unsigned> competitors;
369 
370  // Clearly track candidate can only be a subset of some other track
371  // it shares part of its member hits; this approach is clearly more efficient
372  // than arranging a loop through ALL other track candidates (even if applying
373  // some neighborehood restricitions);
374  for(int gr=0; gr<mGroups.size(); gr++) {
375  for(unsigned mm=0; mm<match->GetLinearMemberCount(gr); mm++) {
376  GroupMember *member = match->GetSelMember(gr, mm);
377 
378  if (!member) continue;
379 
380  // Loop through all track candidates which claim to own this member hit;
381  for(std::set<MatchCandidate*>::iterator it=member->Begin(); it != member->End(); it++) {
382  MatchCandidate *qmatch = *it;
383 
384  if (qmatch != match) competitors[qmatch]++;
385  } //for it
386  } //for mm
387  } //for gr
388 
389  // Loop through all the competitors and compare overall hit counts;
390  unsigned myHitCount = match->GetAliveMemberCount();
391  for(std::map<MatchCandidate*, unsigned>::iterator it=competitors.begin();
392  it != competitors.end(); it++)
393  if (it->second >= myHitCount)
394  return true;
395 
396  return false;
397 } // HoughTree::IsSubset()
398 
399 // ---------------------------------------------------------------------------------------
400 
401 #define _NEW_SUBSET_LOGIC_
402 
403 unsigned HoughTree::CheckCell(unsigned lv, const unsigned id[], HoughCell **pcell,
404  const std::vector<GroupMember*> members[])
405 {
406 #ifdef __APPLE__
407  assert(0);
408 #else
409  // So this trick does not work with Clang; figure a workaround later if even want to run
410  // this code under Mac OS;
411  std::vector<GroupMember*> qmembers[mGroups.size()];
412 
413  ResolutionLevel *level = GetLevel(lv);
414 
415  // Well, if cell has not been calculated yet, do it; allocate the frame,
416  // call overlap calculation routine (track finder context: calculate
417  // track parameters at the parameter rectangle edges and assign overlap
418  // ranges with the registering planes); NB: this may require daughter
419  // cell initialization/calculation (see GetInitializedCell() call details)!;
420  if (!*pcell) *pcell = GetInitializedCell(lv, id);
421 
422  {
423  unsigned ok_hit_counter = 0, ok_added_group_counter = 0;
424  HoughCell *cell = *pcell;
425  // NB: match_candidate_num will NOT be changed between here
426  // and below track candidate filling routine (just because recursion
427  // is not involved inbetween); is actually used on track finder decision
428  // level only; assign always though (make compiler happy);
430 
431  for(std::map<unsigned,unsigned>::iterator it=mGcounters.begin();
432  it!=mGcounters.end(); it++)
433  it->second = 0;
434 
435  // Ok, if after initialization cell pointer was released and
436  // assigned back to a "blind" one, cell has no overlap with real
437  // tracks in *ANY* of the registering planes; nothing to talk about,
438  // just return non-error code;
439  if (cell == mBlindCell) return 0;
440 
441  // Ok, otherwise this cell in parameter space has overlap with
442  // real tracks; check how many hits do I actually have here;
443  // loop through all the groups (registering planes);
444  for(int gr=0; gr<mGroups.size(); gr++) {
445  int ok_group = 0, ok_added_group = 0;
446  HoughNodeGroup *group = mGroups[gr];
447 
448  // NB: some plane(s) may have no overlap with tracks from this
449  // parameter space cell (out of acceptance); check on that;
450  if (cell->From(gr) != __OUT_OF_RANGE_BIT_)
451  // Calculate OR of all its members on this level;
452  for(unsigned mm=0; mm<members[gr].size(); mm++) {
453  GroupMember *member = members[gr][mm];
454 
455  // Sort of a CPU-saving hack; see logic in 'if (group->Overlap())';
456  if (IsBusy(member) && !mBorrowedHitCounterMax) continue;
457 
458  t_hough_range gfrom = member->From();
459  t_hough_range gto = member->To();
460  // THINK: by construction cell->From(gr)<=cell->To(gr) in all components (see
461  // HoughCell::UpdateRanges() call); then this range expansion makes sense (From()
462  // components go down, To() components go up and they are all still guaranteed
463  // to be properly ordered); see assert() in group->Overlap() call though;
464  unsigned smearing = group->GetPhaseSpaceSmearing();
465  // Check if smearing=0 (save some CPU time; not much though);
466  t_hough_range gmin = smearing ? group->OffsetThisValue(cell->From(gr), -(int)smearing) : cell->From(gr);
467  t_hough_range gmax = smearing ? group->OffsetThisValue(cell->To (gr), smearing) : cell->To (gr);
468 
469  // NB: do not break once one Ok hit in a given group found, since
470  // need an overall number of participating hits;
471  // FIXME: may be just pass 4 parameters?;
472  if (group->Overlap(std::pair<t_hough_range,t_hough_range>(gfrom, gto),
473  std::pair<t_hough_range,t_hough_range>(gmin, gmax)))
474  {
475  ok_group = 1;
476  ok_hit_counter++;
477 
478  // Yes, count groups which have "new" (not BUSY) hits separately;
479  if (!IsBusy(member)) ok_added_group = 1;
480 
481  // Same hack -> see comment below;
482  if (!IsBusy(member) || mBorrowedHitCounterMax)
483  qmembers[gr].push_back(member);
484 
485  // Yes, needed only on the highest level (or whatever track
486  // finder decision level -> fix this place then!);
487  if (lv == GetLdim() - 1) {
488  // A small hack: if borrowed hits are not allowed at all, does not make
489  // any sense to add such a member; FIXME: should do ambiguity resolution
490  // correctly in case of non-zero mBorrowedHitCounterMax (so that
491  // non-borrowed hit count does not go below limit -> part of the hits
492  // should just become immune);
493  if (!IsBusy(member) || mBorrowedHitCounterMax)
494  match->AddMember(gr, member);
495  } //if
496  } //if
497  } //if..for mm
498 
499  // FIXME: arrange an early-drop-out condition here (once too many plane
500  // groups have no hits, there is no reason to continue);
501  //if (!ok_group) return 0;
502 
503  match->IncrementOkGroupCounter(ok_group);
504  ok_added_group_counter += ok_added_group;
505  // And also increment special node group counters separately
506  // (feature not used for now in STAR FWD case);
507  if (ok_group) mGcounters[group->GetGroupId()]++;
508 
509  if (mVerbosityLevel >= 5 && !ok_group) {
510  char str[1000];
511  mk_prefix(lv, str);
512 
513  printf("%s - gr=%d [%4d .. %4d]\n", str, gr, cell->From(gr), cell->To(gr));
514  if (cell->From(gr) != __OUT_OF_RANGE_BIT_)
515  for(unsigned mm=0; mm<group->GetMemberCount(); mm++) {
516  GroupMember *member = group->GetMember(mm);
517 
518  // THINK: may also want to mark BUSY members here?;
519 
520  t_hough_range gfrom = member->From();
521 
522  // Should not I print out 'gto' as well?;
523  printf("%s -* %4d\n", str, gfrom);
524  } //for mm .. if
525  }
526 #if 0
527  else
528  {
529  char str[1000];
530  mk_prefix(lv, str);
531 
532  printf("%s + gr=%d [%4d .. %4d]\n", str, gr, cell->from[gr], cell->to[gr]);
533  }
534 #endif
535  } //for gr
536 
537  if (mVerbosityLevel >= 5) {
538  char str[1000];
539 
540  // Well, could be done better; let it be;
541  mk_prefix(lv, str);
542 
543  printf("%s --> check result: %d\n", str, match->GetOkGroupCounter());
544  fflush(stdout);
545  } //if
546 
547  // Ok, now if hit count is smaller than threshold (or perhaps have
548  // more sophisticated logic later?), just return (branch is dead);
549  if (match->GetOkGroupCounter() < mCurrMinOkHitCounter) return 0;
550 
551  for(std::map<unsigned,unsigned>::iterator it=mGlimits.begin();
552  it!=mGlimits.end(); it++)
553  if (it->second && mGcounters[it->first] < it->second)
554  return 0;
555 
556  {
557  unsigned borrowed_hit_counter =
558  match->GetOkGroupCounter() - ok_added_group_counter;
559 
560  // And also number of added "new" fired planes should be sufficiently high;
561  // allow to borrow only (very) few already used (BUSY) hits;
562  if (borrowed_hit_counter > mBorrowedHitCounterMax) return 0;
563 
564  // And the sum of borrowed and missing should not be too high;
565  if (borrowed_hit_counter + (mMaxOkHitCounter - match->GetOkGroupCounter()) >
567  return 0;
568  }
569 
570  // If I'm at the highest resolution level, store track candidate;
571  if (lv == GetLdim() - 1) {
572 #ifndef _NEW_SUBSET_LOGIC_
573  int duplicate = 0;
574 
575  // Check against all previous candidates; indeed if member hits are
576  // the same (even that match->mId[] differ), makes no sense to duplicate;
577  for(unsigned tc=0; tc<mMatchCandidateCount; tc++) {
578  MatchCandidate *reference = mMatchCandidates[tc];
579 
580  if (match->IsSubset(this, reference)) {
581  // Ok, same set found --> set "duplicate" flag & fall out;
582  duplicate = 1;
583  goto _fall_out;
584  } //if
585  } //for tc
586 
587  //bool qq = IsSubset(match);
588  //assert((qq && duplicate) || (!qq && !duplicate));
589 
590 _fall_out:
591  if (!duplicate) {
592 #else
593  if (!IsSubset(match)) {
594 #endif
595  // Fine, then assign a new track candidate entry;
596  match->ShapeItUpForInspection(this, id);
597 
598 #if 1
599  if (mFastTreeSearchMode) {
600  while (!match->IsReadyForFinalFit())
601  ResolveAmbiguitiesNg(match);
602 
603  FinalFit(match);
604 
605  if (match->IsActive()) {
606  for(unsigned gr=0; gr<mGroups.size(); gr++) {
607  // Indeed by this point track has given away all other hits but single
608  // per plane group; FIXME: allow >1 hit per plane group later;
609  assert(match->GetAliveMemberCount(gr) <= 1);
610 
611  GroupMember *member = match->GetFirstAliveMember(gr);
612  // THINK: can happen on lower 'min' loop levels?;
613  if (!member) continue;
614 
615  member->SetBusyFlag();
616  } //for gr
617 
618  mMatchCandidateCount++;
619  } //if
620  }
621  else
622 #endif
623  mMatchCandidateCount++;
624  } //if
625 
626  // Yes, return overall number of participating hits; calling routine
627  // (like lower resolution cell handler) should compare this number to
628  // its hit counter and if they match will NOT try to search for tracks
629  // in other daughter cells; THINK: no matter, was this a new track or not?;
630  return ok_hit_counter;
631  }
632  else {
633  // Otherwise call this routine recursively at higher resolution
634  // level(s), for all this cell daughters' cells (except for the "blind"
635  // ones, of course);
636  unsigned idq[mDimensions.size()];
637 
638  // Allocate daughter array with NULL pointers if not done yet;
639  // if memory allocation fails, return without any clean-up;
640  ResolutionLevel *next = GetLevel(lv+1);
641  if (!cell->DaughtersArrayAllocated())
643 
644  // Loop through all the daughter cells;
645  for(unsigned iq=0; iq<next->GetDaughterCellNumber(); iq++) {
646  // FIXME: well, this is dirty style of course;
647  HoughCell **dcell = cell->GetDaughterPtr(iq);
648 
649  // Do not bother to check "blind end" cells;
650  if (*dcell == mBlindCell) continue;
651 
652  // idq[] calculation is a bit tricky;
653  for(int ip=0; ip<mDimensions.size(); ip++)
654  idq[ip] = id[ip]*next->GetParameterSplitFactor(ip) + next->Remap(iq,ip);
655 
656  // Well, candidate array overflow (or out-of-memory condition)
657  // are almost the only error return codes;
658  {
659  unsigned ret = CheckCell(lv+1, idq, dcell, qmembers);
660 
661  // Well, if '-1' return code, fall out immediately;
662  //if (ret == -1)
663  //return -1;
664  //else
665 
666  // If track was found, and number of member hits is the same
667  // as I have in this (parent) cell, makes no sense to look for
668  // track candidates in other daughter cells, right?;
669  if (ret == ok_hit_counter) return ret;
670  }
671  } //for iq
672 
673  // Well, if daughter(s) succeeded to find track(s), but used less
674  // hits, then other hit combinations could be successful either, so
675  // can not help parent cell to take a decision to stop other
676  // daughter investigation; just return 0;
677  return 0;
678  } //if
679  }
680 #endif
681 } // HoughTree::CheckCell()
682 
683 // ---------------------------------------------------------------------------------------
684 
685 #if _LATER_
686 void HoughTree::PrintTrackCandidateArray(unsigned from, unsigned to,
687  TrackCandidatePrintoutFlag flag) const
688 {
689  for(unsigned tc=from; tc<=to; tc++) {
690  const MatchCandidate *match = mMatchCandidates[tc];
691 
692  // Just skip track candidates which were merged into the other ones;
693  if (flag == _ACTIVE_CANDIDATES_ && !match->IsActive()) continue;
694 
695  unsigned multi_max = 0, ok_hit_counter = 0, ok_group_counter = 0;
696 
697  for(int gr=0; gr<mGroups.size(); gr++) {
698  if (match->GetMemberCount(gr) > multi_max)
699  multi_max = match->GetMemberCount(gr);
700 
701  if (match->GetMemberCount(gr)) ok_group_counter++;
702  ok_hit_counter += match->GetMemberCount(gr);
703  } //for iq
704 
705  for(unsigned iq=0; iq<multi_max; iq++) {
706  if (iq)
707  printf(" ");
708  else {
709  const unsigned *id = match->GetIdPtr();
710 
711  printf("%2d -> tr.candidate#%02d (%c): id[]: %4d/%4d/%4d:: %2d/%2d hits/groups total -> ",
712  GetLdim()-1, tc, match->IsActive() ? '*' : ' ',
713  id[0], id[1], mDimensions.size() == 3 ? id[2] : 0,
714  ok_hit_counter, ok_group_counter);
715  } //if
716 #if _LATER_
717  for(int gr=0; gr<mGroups.size(); gr++)
718  // NB: Kalman filter pass could have removed certain member hits (members[][] = NULL);
719  if (match->mnum[gr] > iq && match->members[gr][iq])
720  // So here I'm using only 'from' wire for printout, right?;
721  printf(" %3d", match->members[gr][iq]->_from >> (groups[gr]._nd+groups[gr]._nh));
722  else
723  printf(" ");
724 #endif
725  printf(iq == multi_max-1 ? "\n\n" : "\n");
726  } //for iq
727  } //for tc
728 } // HoughTree::PrintTrackCandidateArray()
729 #endif
730 
731 // ---------------------------------------------------------------------------------------
732 
733 //
734 // THINK: this routine was actually checked carefully only in HoughTree::LaunchPatternFinder();
735 //
736 
738 {
739  unsigned counter = 0;
740 
741  // Ok, now deal with all track candidates found so far; first purge
742  // track candidate array; it can happen that earlier found tracks
743  // are in fact subsets of the later found ones (this possibility is not
744  // checked in checkCell()); to make it clear: number of fired planes
745  // may even be the same, but later found track candidates (say, at the
746  // same current "min" value) may have extra
747  // hits in some of the planes; use the same algorithm as in checkCell();
748 #if _THINK_
749  // NB: per design it can NOT happen, that earlier found subset tracks were
750  // passed through KF already (since only tracks with number of planes hit
751  // matching current "min" limit were analyzed);
752 #endif
753  for(unsigned tc=0; tc<mMatchCandidateCount; tc++) {
754  MatchCandidate *match = mMatchCandidates[tc];
755 
756  // Do not touch track candidates already selected as BEST (and perhaps
757  // lost part of their hits in KF-pass);
758  if (!match->IsActive() /*|| match->IsAlreadyAccountedAsGoodTrack()*/) continue;
759 
760 #ifdef _NEW_SUBSET_LOGIC_
761  if (IsSubset(match)) {
762  match->SetInactive();
763  counter++;
764  } //if
765 #else
766  for(unsigned tcr=tc+1; tcr<mMatchCandidateCount; tcr++) {
767  MatchCandidate *reference = mMatchCandidates[tcr];
768 
769  if (!reference->IsActive()) continue;
770 
771  if (match->IsSubset(this, reference)) {
772  // Can not happen; otherwise something was wrong in logic;
773  //assert(!match->passed_kalman_filter);
774 
775  //printf("Found subset track candidate -> de-activating!\n");
776  match->SetInactive();
777  counter++;
778 
779  // No need to check against the other patterns;
780  goto _next_match;
781  } /*if*/
782  } /*for tcr*/
783 
784  _next_match:
785  continue;
786 #endif
787  } //for tc
788 
789  return counter;
790 } // HoughTree::PurgeDuplicateTracks()
791 
792 // ---------------------------------------------------------------------------------------
793 
795 {
796  // Lock configuration; do not mind to do it several times;
797  mInitialized = true;
798 
799  // Reset number of match candidates (track candidates at highest
800  // resolution level in tracking context);
802 
803  //
804  // Original OLYMPUS implementation was aimed at finding a single "best" track
805  // candidate; STAR forward tracker upgrade has completely different requirements,
806  // so rework the logic of this call completely:
807  //
808  // - arrange a single loop over min. allowed hits; start from highest value
809  // (indeed efficiency is expected to be high); so if there are N planes and
810  // min. allowed hit count is M=N-2, there will be 3 loops (wich this limit
811  // first set to N, then to N-1, and then to N-2); this is probably not the
812  // most efficient way of doing things, but should - at least if the highest
813  // level resolution is pushed to its limits - allow to avoid having a "bush"
814  // of lower-hit-count track candidates around true tracks with max. possible
815  // hit count; yes, they would probably be gone during purging process, yet
816  // this can cost a combinatorial CPU overhead;
817  //
818  // - in each cycle of this loop:
819  //
820  // - purge track candidate array (it can happen, that two track candidates
821  // have pretty much identical set of hits, except for say one plane where
822  // one of the tracks has a 2-d hit not present in the first track);
823  //
824  // - for each of remaining track candidates try to find at least one
825  // combination of M hits (see M definition above), which produces a fit
826  // with sufficiently low chi^2; this is costly indeed; do not consider
827  // combinatorial approach if track candidate has >1 hit in some of the
828  // planes, but rather "anneal" such configurations in a way fit converges
829  // in number of steps, proportional to the number of excess hits;
830  //
831  // - discard track candidates which fail this primary fit;
832  //
833  // - build array of vertex overlaps which have "hit deficit" (say, 3 tracks
834  // want to share 2 hits in some plane); build arrays of tracks which are
835  // mutually interconnected by these vertices; figure out, whether part
836  // of the member tracks have to be dropped, because min. hit condition can
837  // not be met for all of them at once (say 2 track candidates share 5 hits,
838  // but differ in the other N-5 ones; clearly if N-2 is the minimum allowed
839  // hit count per track, one of them should be discarded); do this based on
840  // 1) max fired plane counter, 2) if equal, lowest chi^2 after the fit;
841  //
842  // - mark hits of remaining tracks as BUSY for the cases when say 3 tracks
843  // share hit group consisting of 3 hits; in redundant cases (say a single
844  // track has 2 hits in some plane) either do not set BUSY flag or set it
845  // conditionally (THINK later);
846  //
847  // - enter next cycle with presumably several hits marked as BUSY; continue
848  // track search with less stringent min. allowed hit count condition, assuming,
849  // that part of the hits can be "borrowed" from already found tracks;
850  //
851  // - eventually will have a set of track candidates, which probably overlap
852  // with each other through their plane hits; build isolated groups of track
853  // candidates and allocate hits within these groups using some sort of
854  // "annealing" procedure;
855  //
856  // -> if track candidate had >1 hit in some planes, KF-fit starts under
857  // assumption that coordinate in this plane is defined only within the
858  // suggested hit range - so it is (last-first)/2 with a flat distribution;
859  // then the first track approximation is built, closest hit in each of
860  // ambiguos planes selected and final KF-pass is run; THINK: may be in steps?;
861  //
862 
863  // Want to find track candidates with max hit count first --> arrange
864  // a loop decreasing min number of hit planes limit; this may not be the
865  // most efficient way of doing things, but it is very logical;
866  for(unsigned min=mMaxOkHitCounter; min>=mMinOkHitCounter; min--) {
868 
869  // Yes, min-qua loops should be in this order;
870  for(unsigned qua=0; qua<mTrackQualityIterationNum; qua++) {
872 
873  // Loop indefinitely with this "min" value until there are no more new tracks found;
874  for( ; ; ) {
875  unsigned last_track_candidate_count = mMatchCandidateCount;
876 
877  {
878 #ifdef __APPLE__
879  assert(0);
880 #else
881  // Same story as in HoughTree::CheckCell(): need a Mac OS workaround at some point;
882  std::vector<GroupMember*> buffer[mGroups.size()];
883 
884  for(int gr=0; gr<mGroups.size(); gr++) {
885  HoughNodeGroup *group = mGroups[gr];
886 
887  for(unsigned mm=0; mm<group->GetMemberCount(); mm++) {
888  GroupMember *member = group->GetMember(mm);
889 
890  buffer[gr].push_back(member);
891  } //for mm
892  } //for gr
893 
894  unsigned id[mDimensions.size()];
895  // Yes, just enter the 0-level with its single cell;
896  memset(id, 0x00, sizeof(id));
897 
898  /*int ret =*/ CheckCell(0, id, &mCellTree, buffer);
899 
900  // THINK: can happen only if memory allocation fails (or some other generic
901  // problem like user call failure or mapping table import problem);
902  //if (ret == -1) return -1;
903  //assert(ret != -1);
904 #endif
905  }
906 
907  printf("\n\n------------------------------------------------------------------\n");
908  printf("%3d new track candidate(s) ... -> ", mMatchCandidateCount - last_track_candidate_count);
909 
910  unsigned counter = PurgeDuplicateTracks();
911 
912  printf("%3d survived ... \n", mMatchCandidateCount - last_track_candidate_count - counter);
913 
914  // Split track candidates into non-overlapping groups;
915  std::vector<MatchCandidateGroup> mMatchCandidateGroups;
916 
917 #if 1
918  if (mFastTreeSearchMode) goto _fast_tree_search_mode;
919 #endif
920 
921  // NB: match->mGrouped & match->mAccountedAsGoodTrack are false here
922  // as reset in ShapeItUpForInspection() during tree search;
923  for(unsigned tc=last_track_candidate_count; tc<mMatchCandidateCount; tc++) {
924  MatchCandidate *match = mMatchCandidates[tc];
925 
926  // Skip disabled candidates (most likely subsets of other ones);
927  if (!match->IsActive()) continue;
928 
929  // Also skip those, which are grouped already as a result of recursion;
930  if (match->IsGrouped()) continue;
931 
932  // Start new group and initialize it with this match candidate; the
933  // recursion happens inside this call;
934  mMatchCandidateGroups.push_back(MatchCandidateGroup(match));
935  } //for tc
936 
937  // FIXME: siam track splitter will modify mMatchCandidateCount internally, so
938  // for safety reasons one should not use mMatchCandidateCount rather than in the
939  // later GetLinearMatchCandidateCount() call in FwdTrackFinder::Exec();
940 
941  printf(" --> %2d group(s) ...\n", (unsigned)mMatchCandidateGroups.size());
942  //return 0;
943 
944  // Now can work with groups of non-overlapping track candidates separately;
945  for(unsigned gr=0; gr<mMatchCandidateGroups.size(); gr++) {
946  MatchCandidateGroup *mgroup = &mMatchCandidateGroups[gr];
947 
948  printf("\n gr#%02d -> %2d track(s)\n", gr, mgroup->GetCandidateCount());
949 
950 #if _OLD_
951  // Split siam tracks first; FIXME: at max missing hit level only for
952  // now (and assuming this limit means "no missing hits");
953  if (min == mMaxOkHitCounter) {
954  unsigned trCounter = 0;
955  std::vector<MatchCandidate*> newMatches;
956 
957  for(std::multimap<__u64, MatchCandidate*>::iterator it=mgroup->Begin();
958  it != mgroup->End(); it++) {
959  MatchCandidate *match = it->second;
960 
961  if (match->SiamGroupCandidate(min)) {
962  printf("tr#%2d -> siam group candidate: doit!\n", trCounter++);
963 
964  // NB: this call will change mMatchCandidateGroups.size() internally;
965  SeparateSiamTracks(match, min, &newMatches);
966  } //if
967  } //for it
968  //continue;
969 
970  // Add newcomers to the group pool;
971  for(unsigned tr=0; tr<newMatches.size(); tr++) {
972  mgroup->AddCandidate(newMatches[tr]);
973  } //for tr
974 
975  if (newMatches.size())
976  printf("\n gr#%02d -> %2d track(s)\n", gr, mgroup->GetCandidateCount());
977  }
978 #endif
979 
980  unsigned trCounter = 0;
981  for(std::multimap<__u64, MatchCandidate*>::iterator it=mgroup->Begin();
982  it != mgroup->End(); it++) {
983  MatchCandidate *match = it->second;
984 
985  if (!match->IsActive()) continue;
986 
987  // Easy case; debug later;
988 #if 0
989  //if (!match->HasAmbiguousHits()) {
990  if (match->HasAmbiguousHits()) {
991  //printf("tr#%2d -> no ambiguities : skip!\n", trCounter++);
992  printf("tr#%2d -> ambiguities : skip!\n", trCounter++);
993  match->SetInactive();
994  continue;
995  } //if
996 #endif
997 
998 #if _OLD_
999  // FIXME: precaution for "missing hit non-trivial" case (see code above);
1000  if (match->SiamGroupCandidate(min)) {
1001  match->SetInactive();
1002  continue;
1003  } //if
1004 
1005  // Get rid of hit ambiguities; perhaps in a few iterations;
1006  // NB: in Fwd-case at this point match->mPassedKalmanFilterOnce is 'false' ->
1007  // ResolveAmbiguities() will be called at least once;
1008  while (!match->IsReadyForFinalFit()) {
1009  //printf(" tc#%02: ambiguous!\n", tc);
1010 
1011  // This call is supposed to resolve at least one ambiguity,
1012  // so the 'while' loop can not run indefinitely;
1013  ResolveAmbiguities(match);
1014  } //while
1015 #else
1016  while (!match->IsReadyForFinalFit())
1017  // This call is supposed to resolve at least one ambiguity,
1018  // so the 'while' loop can not run indefinitely;
1019  ResolveAmbiguitiesNg(match);
1020 #endif
1021 
1022  assert(match->IsActive());
1023  FinalFit(match);
1024  } //for it
1025 
1026  // Tracks may still have overlaps in part of their hits; this basically
1027  // means, that in the present approach part of the tracks should go;
1028  // since at least one more global iteration with the same "min" hit
1029  // counter will be done, removed tracks have chance to return back,
1030  // taking hits which they refused (with a hope for better chi^2 fit)
1031  // during present iteration; FIXME: logic here needs to be optimized further;
1032  {
1033  // Will be ordered according to chi^2 CCDF (or such, a virtual method);
1034  std::multimap<double, MatchCandidate*> finalCandidates;
1035 
1036  for(std::multimap<__u64, MatchCandidate*>::iterator it=mgroup->Begin();
1037  it != mgroup->End(); it++) {
1038  MatchCandidate *match = it->second;
1039 
1040  finalCandidates.insert(std::pair<double,
1041  MatchCandidate*>(match->GetTrackQualityParameter(), match));
1042  } //for it
1043 
1044  // Now loop through all tracks ordered in chi^2 CCDF and effectively purge
1045  // those which probably not own proper hits; NB: reversed order in CCDF values;
1046  for(std::multimap<double, MatchCandidate*>::reverse_iterator it = finalCandidates.rbegin();
1047  it != finalCandidates.rend(); it++) {
1048  MatchCandidate *match = it->second;
1049 
1050  // SKip those which are dead already;
1051  if (!match->IsActive()) continue;
1052 
1053  // Check remaining hit count; it may have fallen below the limit because other (better)
1054  // tracks acquired part of the hits;
1055  if (match->GetAliveGroupCount() < min) {
1056  //assert(0);
1057  match->SetInactive();
1058  }
1059  else {
1060  for(unsigned gr=0; gr<mGroups.size(); gr++) {
1061  // Indeed by this point track has given away all other hits but single
1062  // per plane group; FIXME: allow >1 hit per plane group later;
1063  assert(match->GetAliveMemberCount(gr) <= 1);
1064 
1065  GroupMember *member = match->GetFirstAliveMember(gr);
1066  // THINK: can happen on lower 'min' loop levels?;
1067  if (!member) continue;
1068 
1069  // Remove used hits from other tracks which belong to this group;
1070  for(std::multimap<double, MatchCandidate*>::reverse_iterator is = finalCandidates.rbegin();
1071  is != finalCandidates.rend(); is++) {
1072  MatchCandidate *qmatch = is->second;
1073 
1074  if (!qmatch->IsActive()) continue;
1075 
1076  // Skip myself; FIXME: optimize loop starting point;
1077  if (qmatch == match) continue;
1078 
1079  // For the same reason as in case of 'match' pointer, right?;
1080  assert(qmatch->GetAliveMemberCount(gr) <= 1);
1081  GroupMember *qmember = qmatch->GetFirstAliveMember(gr);
1082  // THINK: can happen on lower 'min' loop levels?;
1083  if (!qmember) continue;
1084 
1085  // But if they match, clean up;
1086  if (member == qmember) qmatch->ResetMemberPtr(gr, qmember);
1087  } //for is
1088 
1089  // Yes, only now eventually claim, that this hit is busy; so other track
1090  // candidates who wanted to own it will effectively die during this
1091  // search+fit pass and will have to try to recover using other hit(s)
1092  // in this plane group during the next iteration on the same 'min" level
1093  // (which is guaranteed to happen as long as at least one new track was
1094  // found and indeed it was);
1095  member->SetBusyFlag();
1096  } //for gr
1097  } //if
1098  } //for it
1099  }
1100  } //for gr
1101 
1102  _fast_tree_search_mode:
1103  // Check whether good candidates were added; break and switch to lower "min"
1104  // hit count limit if none;
1105  {
1106  unsigned newTrackCount = 0;
1107 
1108  for(unsigned tc=last_track_candidate_count; tc<mMatchCandidateCount; tc++) {
1109  MatchCandidate *match = mMatchCandidates[tc];
1110 
1111  if (match->IsActive()) newTrackCount++;
1112  } //for tc
1113 
1114  //if (mCurrMinOkHitCounter == 5 && newTrackCount) printf("@555@\n");
1115  printf("@GGG@: min=%2d, added %4d new track(s) ...\n", min, newTrackCount);
1116 
1117  if (!newTrackCount) break;
1118  }
1119  } //for inf
1120  } //for qua
1121  } //for min
1122 
1123  {
1124  unsigned counter = PurgeDuplicateTracks();
1125  if (counter) printf(" @PPP@ Purged %2d more duplicate track(s)!\n", counter);
1126  }
1127 
1128  return 0;
1129 } // HoughTree::LaunchPatternFinder()
1130 
1131 // ---------------------------------------------------------------------------------------