Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SeedFinderOrthogonal.ipp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file SeedFinderOrthogonal.ipp
1 // This file is part of the Acts project.
2 //
3 // Copyright (C) 2023 CERN for the benefit of the Acts project
4 //
5 // This Source Code Form is subject to the terms of the Mozilla Public
6 // License, v. 2.0. If a copy of the MPL was not distributed with this
7 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 
15 
16 #include <cmath>
17 #include <functional>
18 #include <numeric>
19 #include <type_traits>
20 
21 namespace Acts {
22 template <typename external_spacepoint_t>
24  const internal_sp_t &low) const -> typename tree_t::range_t {
25  float colMin = m_config.collisionRegionMin;
26  float colMax = m_config.collisionRegionMax;
27  float pL = low.phi();
28  float rL = low.radius();
29  float zL = low.z();
30 
31  typename tree_t::range_t res;
32 
33  /*
34  * Cut: Ensure that we search only in φ_min ≤ φ ≤ φ_max, as defined by the
35  * seeding configuration.
36  */
37  res[DimPhi].shrinkMin(m_config.phiMin);
38  res[DimPhi].shrinkMax(m_config.phiMax);
39 
40  /*
41  * Cut: Ensure that we search only in r ≤ r_max, as defined by the seeding
42  * configuration.
43  */
44  res[DimR].shrinkMax(m_config.rMax);
45 
46  /*
47  * Cut: Ensure that we search only in z_min ≤ z ≤ z_max, as defined by the
48  * seeding configuration.
49  */
50  res[DimZ].shrinkMin(m_config.zMin);
51  res[DimZ].shrinkMax(m_config.zMax);
52 
53  /*
54  * Cut: Ensure that we search only in Δr_min ≤ r - r_L ≤ Δr_max, as defined
55  * by the seeding configuration and the given lower spacepoint.
56  */
57  res[DimR].shrinkMin(rL + m_config.deltaRMinTopSP);
58  res[DimR].shrinkMax(rL + m_config.deltaRMaxTopSP);
59 
60  /*
61  * Cut: Now that we have constrained r, we can use that new r range to
62  * further constrain z.
63  */
64  float zMax = (res[DimR].max() / rL) * (zL - colMin) + colMin;
65  float zMin = colMax - (res[DimR].max() / rL) * (colMax - zL);
66 
67  /*
68  * This cut only works if z_low is outside the collision region for z.
69  */
70  if (zL > colMin) {
71  res[DimZ].shrinkMax(zMax);
72  } else if (zL < colMax) {
73  res[DimZ].shrinkMin(zMin);
74  }
75 
76  /*
77  * Cut: Shrink the z-range using the maximum cot(θ).
78  */
79  res[DimZ].shrinkMin(zL - m_config.cotThetaMax * (res[DimR].max() - rL));
80  res[DimZ].shrinkMax(zL + m_config.cotThetaMax * (res[DimR].max() - rL));
81 
82  /*
83  * Cut: Shrink the φ range, such that Δφ_min ≤ φ - φ_L ≤ Δφ_max
84  */
85  res[DimPhi].shrinkMin(pL - m_config.deltaPhiMax);
86  res[DimPhi].shrinkMax(pL + m_config.deltaPhiMax);
87 
88  return res;
89 }
90 
91 template <typename external_spacepoint_t>
93  const internal_sp_t &high) const -> typename tree_t::range_t {
94  float pM = high.phi();
95  float rM = high.radius();
96 
97  typename tree_t::range_t res;
98 
99  /*
100  * Cut: Ensure that we search only in φ_min ≤ φ ≤ φ_max, as defined by the
101  * seeding configuration.
102  */
103  res[DimPhi].shrinkMin(m_config.phiMin);
104  res[DimPhi].shrinkMax(m_config.phiMax);
105 
106  /*
107  * Cut: Ensure that we search only in r ≤ r_max, as defined by the seeding
108  * configuration.
109  */
110  res[DimR].shrinkMax(m_config.rMax);
111 
112  /*
113  * Cut: Ensure that we search only in z_min ≤ z ≤ z_max, as defined by the
114  * seeding configuration.
115  */
116  res[DimZ].shrinkMin(m_config.zMin);
117  res[DimZ].shrinkMax(m_config.zMax);
118 
119  /*
120  * Cut: Ensure that we search only in Δr_min ≤ r_H - r ≤ Δr_max, as defined
121  * by the seeding configuration and the given higher spacepoint.
122  */
123  res[DimR].shrinkMin(rM - m_config.deltaRMaxBottomSP);
124  res[DimR].shrinkMax(rM - m_config.deltaRMinBottomSP);
125 
126  /*
127  * Cut: Now that we have constrained r, we can use that new r range to
128  * further constrain z.
129  */
130  float fracR = res[DimR].min() / rM;
131 
132  float zMin = (high.z() - m_config.collisionRegionMin) * fracR +
133  m_config.collisionRegionMin;
134  float zMax = (high.z() - m_config.collisionRegionMax) * fracR +
135  m_config.collisionRegionMax;
136 
137  res[DimZ].shrinkMin(std::min(zMin, high.z()));
138  res[DimZ].shrinkMax(std::max(zMax, high.z()));
139 
140  /*
141  * Cut: Shrink the φ range, such that Δφ_min ≤ φ - φ_H ≤ Δφ_max
142  */
143  res[DimPhi].shrinkMin(pM - m_config.deltaPhiMax);
144  res[DimPhi].shrinkMax(pM + m_config.deltaPhiMax);
145 
146  return res;
147 }
148 
149 template <typename external_spacepoint_t>
151  const SeedFinderOptions &options, const internal_sp_t &low,
152  const internal_sp_t &high, bool isMiddleInverted) const {
153  float rL = low.radius();
154  float rH = high.radius();
155 
156  float zL = low.z();
157  float zH = high.z();
158 
159  float deltaR = rH - rL;
160 
161  float deltaZ = (zH - zL);
162  float cotTheta = deltaZ / deltaR;
163  /*
164  * Cut: Ensure that the origin of the dublet (the intersection of the line
165  * between them with the z axis) lies within the collision region.
166  */
167  float zOrigin = zL - rL * cotTheta;
168  if (zOrigin < m_config.collisionRegionMin ||
169  zOrigin > m_config.collisionRegionMax) {
170  return false;
171  }
172 
173  // cut on the max curvature calculated from dublets
174  // first transform the space point coordinates into a frame such that the
175  // central space point SPm is in the origin of the frame and the x axis
176  // points away from the interaction point in addition to a translation
177  // transformation we also perform a rotation in order to keep the
178  // curvature of the circle tangent to the x axis
179  if (m_config.interactionPointCut) {
180  float xVal = (high.x() - low.x()) * (low.x() / rL) +
181  (high.y() - low.y()) * (low.y() / rL);
182  float yVal = (high.y() - low.y()) * (low.x() / rL) -
183  (high.x() - low.x()) * (low.y() / rL);
184 
185  const int sign = isMiddleInverted ? -1 : 1;
186 
187  if (std::abs(rL * yVal) > sign * m_config.impactMax * xVal) {
188  // conformal transformation u=x/(x²+y²) v=y/(x²+y²) transform the
189  // circle into straight lines in the u/v plane the line equation can
190  // be described in terms of aCoef and bCoef, where v = aCoef * u +
191  // bCoef
192  float uT = xVal / (xVal * xVal + yVal * yVal);
193  float vT = yVal / (xVal * xVal + yVal * yVal);
194  // in the rotated frame the interaction point is positioned at x = -rM
195  // and y ~= impactParam
196  float uIP = -1. / rL;
197  float vIP = m_config.impactMax / (rL * rL);
198  if (sign * yVal > 0.) {
199  vIP = -vIP;
200  }
201  // we can obtain aCoef as the slope dv/du of the linear function,
202  // estimated using du and dv between the two SP bCoef is obtained by
203  // inserting aCoef into the linear equation
204  float aCoef = (vT - vIP) / (uT - uIP);
205  float bCoef = vIP - aCoef * uIP;
206  // the distance of the straight line from the origin (radius of the
207  // circle) is related to aCoef and bCoef by d^2 = bCoef^2 / (1 +
208  // aCoef^2) = 1 / (radius^2) and we can apply the cut on the curvature
209  if ((bCoef * bCoef) > (1 + aCoef * aCoef) / options.minHelixDiameter2) {
210  return false;
211  }
212  }
213  }
214 
215  /*
216  * Cut: Ensure that the forward angle (z / r) lies within reasonable bounds,
217  * which is to say the absolute value must be smaller than the max cot(θ).
218  */
219  if (std::fabs(cotTheta) > m_config.cotThetaMax) {
220  return false;
221  }
222  /*
223  * Cut: Ensure that z-distance between SPs is within max and min values.
224  */
225  if (std::abs(deltaZ) > m_config.deltaZMax) {
226  return false;
227  }
228 
229  return true;
230 }
231 
232 template <typename external_spacepoint_t>
235  : m_config(config) {
236  if (not config.isInInternalUnits) {
237  throw std::runtime_error(
238  "SeedFinderOrthogonalConfig not in ACTS internal units in "
239  "SeedFinderOrthogonal");
240  }
241 }
242 
243 template <typename external_spacepoint_t>
245  const SeedFinderOptions &options, internal_sp_t &middle,
246  std::vector<internal_sp_t *> &bottom, std::vector<internal_sp_t *> &top,
247  SeedFilterState seedFilterState,
249  &candidates_collector,
250  Acts::SpacePointData &spacePointData) const {
251  float rM = middle.radius();
252  float zM = middle.z();
253  float varianceRM = middle.varianceR();
254  float varianceZM = middle.varianceZ();
255 
256  // apply cut on the number of top SP if seedConfirmation is true
257  if (m_config.seedConfirmation == true) {
258  // check if middle SP is in the central or forward region
259  SeedConfirmationRangeConfig seedConfRange =
260  (zM > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
261  zM < m_config.centralSeedConfirmationRange.zMinSeedConf)
262  ? m_config.forwardSeedConfirmationRange
263  : m_config.centralSeedConfirmationRange;
264  // set the minimum number of top SP depending on whether the middle SP is
265  // in the central or forward region
266  seedFilterState.nTopSeedConf = rM > seedConfRange.rMaxSeedConf
267  ? seedConfRange.nTopForLargeR
268  : seedConfRange.nTopForSmallR;
269  // set max bottom radius for seed confirmation
270  seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
271  // continue if number of top SPs is smaller than minimum
272  if (top.size() < seedFilterState.nTopSeedConf) {
273  return;
274  }
275  }
276 
277  std::vector<const internal_sp_t *> top_valid;
278  std::vector<float> curvatures;
279  std::vector<float> impactParameters;
280 
281  // contains parameters required to calculate circle with linear equation
282  // ...for bottom-middle
283  std::vector<LinCircle> linCircleBottom;
284  // ...for middle-top
285  std::vector<LinCircle> linCircleTop;
286 
287  // transform coordinates
288  transformCoordinates(spacePointData, bottom, middle, true, linCircleBottom);
289  transformCoordinates(spacePointData, top, middle, false, linCircleTop);
290 
291  // sort: make index vector
292  std::vector<std::size_t> sorted_bottoms(linCircleBottom.size());
293  for (std::size_t i(0); i < sorted_bottoms.size(); ++i) {
294  sorted_bottoms[i] = i;
295  }
296 
297  std::vector<std::size_t> sorted_tops(linCircleTop.size());
298  for (std::size_t i(0); i < sorted_tops.size(); ++i) {
299  sorted_tops[i] = i;
300  }
301 
302  std::sort(
303  sorted_bottoms.begin(), sorted_bottoms.end(),
304  [&linCircleBottom](const std::size_t a, const std::size_t b) -> bool {
305  return linCircleBottom[a].cotTheta < linCircleBottom[b].cotTheta;
306  });
307 
308  std::sort(sorted_tops.begin(), sorted_tops.end(),
309  [&linCircleTop](const std::size_t a, const std::size_t b) -> bool {
310  return linCircleTop[a].cotTheta < linCircleTop[b].cotTheta;
311  });
312 
313  std::vector<float> tanMT;
314  tanMT.reserve(top.size());
315 
316  size_t numTopSP = top.size();
317  for (size_t t = 0; t < numTopSP; t++) {
318  tanMT.push_back(
319  std::atan2(top[t]->radius() - middle.radius(), top[t]->z() - zM));
320  }
321 
322  size_t t0 = 0;
323 
324  for (const std::size_t b : sorted_bottoms) {
325  // break if we reached the last top SP
326  if (t0 == numTopSP) {
327  break;
328  }
329 
330  auto lb = linCircleBottom[b];
331 
332  // 1+(cot^2(theta)) = 1/sin^2(theta)
333  float iSinTheta2 = (1. + lb.cotTheta * lb.cotTheta);
334  float sigmaSquaredPtDependent = iSinTheta2 * options.sigmapT2perRadius;
335  // calculate max scattering for min momentum at the seed's theta angle
336  // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
337  // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
338  // scattering
339  // but to avoid trig functions we approximate cot by scaling by
340  // 1/sin^4(theta)
341  // resolving with pT to p scaling --> only divide by sin^2(theta)
342  // max approximation error for allowed scattering angles of 0.04 rad at
343  // eta=infinity: ~8.5%
344  float scatteringInRegion2 = options.multipleScattering2 * iSinTheta2;
345 
346  // minimum number of compatible top SPs to trigger the filter for a certain
347  // middle bottom pair if seedConfirmation is false we always ask for at
348  // least one compatible top to trigger the filter
349  size_t minCompatibleTopSPs = 2;
350  if (!m_config.seedConfirmation or
351  bottom[b]->radius() > seedFilterState.rMaxSeedConf) {
352  minCompatibleTopSPs = 1;
353  }
354  if (m_config.seedConfirmation and seedFilterState.numQualitySeeds) {
355  minCompatibleTopSPs++;
356  }
357 
358  // clear all vectors used in each inner for loop
359  top_valid.clear();
360  curvatures.clear();
361  impactParameters.clear();
362 
363  float tanLM = std::atan2(rM - bottom[b]->radius(), zM - bottom[b]->z());
364 
365  for (size_t index_t = t0; index_t < numTopSP; index_t++) {
366  const std::size_t t = sorted_tops[index_t];
367  auto lt = linCircleTop[t];
368 
369  if (std::abs(tanLM - tanMT[t]) > 0.005) {
370  continue;
371  }
372 
373  // add errors of spB-spM and spM-spT pairs and add the correlation term
374  // for errors on spM
375  float error2 = lt.Er + lb.Er +
376  2 * (lb.cotTheta * lt.cotTheta * varianceRM + varianceZM) *
377  lb.iDeltaR * lt.iDeltaR;
378 
379  float deltaCotTheta = lb.cotTheta - lt.cotTheta;
380  float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
381 
382  // Apply a cut on the compatibility between the r-z slope of the two
383  // seed segments. This is done by comparing the squared difference
384  // between slopes, and comparing to the squared uncertainty in this
385  // difference - we keep a seed if the difference is compatible within
386  // the assumed uncertainties. The uncertainties get contribution from
387  // the space-point-related squared error (error2) and a scattering term
388  // calculated assuming the minimum pt we expect to reconstruct
389  // (scatteringInRegion2). This assumes gaussian error propagation which
390  // allows just adding the two errors if they are uncorrelated (which is
391  // fair for scattering and measurement uncertainties)
392  if (deltaCotTheta2 > (error2 + scatteringInRegion2)) {
393  // skip top SPs based on cotTheta sorting when producing triplets
394  // break if cotTheta from bottom SP < cotTheta from top SP because
395  // the SP are sorted by cotTheta
396  if (deltaCotTheta < 0) {
397  break;
398  }
399  t0 = index_t + 1;
400  continue;
401  }
402 
403  float dU = lt.U - lb.U;
404 
405  // A and B are evaluated as a function of the circumference parameters
406  // x_0 and y_0
407  float A = (lt.V - lb.V) / dU;
408  float S2 = 1. + A * A;
409  float B = lb.V - A * lb.U;
410  float B2 = B * B;
411  // sqrt(S2)/B = 2 * helixradius
412  // calculated radius must not be smaller than minimum radius
413  if (S2 < B2 * options.minHelixDiameter2) {
414  continue;
415  }
416 
417  // 1/helixradius: (B/sqrt(S2))*2 (we leave everything squared)
418  // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
419  // from rad to deltaCotTheta
420  float p2scatterSigma = B2 / S2 * sigmaSquaredPtDependent;
421  if (!std::isinf(m_config.maxPtScattering)) {
422  // if pT > maxPtScattering, calculate allowed scattering angle using
423  // maxPtScattering instead of pt.
424  if (B2 == 0 or options.pTPerHelixRadius * std::sqrt(S2 / B2) >
425  2. * m_config.maxPtScattering) {
426  float pTscatterSigma =
427  (m_config.highland / m_config.maxPtScattering) *
428  m_config.sigmaScattering;
429  p2scatterSigma = pTscatterSigma * pTscatterSigma * iSinTheta2;
430  }
431  }
432  // if deltaTheta larger than allowed scattering for calculated pT, skip
433  if (deltaCotTheta2 > (error2 + p2scatterSigma)) {
434  if (deltaCotTheta < 0) {
435  break;
436  }
437  t0 = index_t;
438  continue;
439  }
440 
441  // A and B allow calculation of impact params in U/V plane with linear
442  // function
443  // (in contrast to having to solve a quadratic function in x/y plane)
444  float Im = std::abs((A - B * rM) * rM);
445 
446  if (Im <= m_config.impactMax) {
447  top_valid.push_back(top[t]);
448  // inverse diameter is signed depending on if the curvature is
449  // positive/negative in phi
450  curvatures.push_back(B / std::sqrt(S2));
451  impactParameters.push_back(Im);
452  }
453  }
454 
455  // continue if number of top SPs is smaller than minimum required for filter
456  if (top.size() < minCompatibleTopSPs) {
457  continue;
458  }
459 
460  seedFilterState.zOrigin = zM - rM * lb.cotTheta;
461 
462  m_config.seedFilter->filterSeeds_2SpFixed(
463  spacePointData, *bottom[b], middle, top_valid, curvatures,
464  impactParameters, seedFilterState, candidates_collector);
465 
466  } // loop on bottoms
467 }
468 
469 template <typename external_spacepoint_t>
470 template <typename output_container_t>
472  const SeedFinderOptions &options, const tree_t &tree,
473  output_container_t &out_cont, const typename tree_t::pair_t &middle_p,
474  Acts::SpacePointData &spacePointData) const {
475  using range_t = typename tree_t::range_t;
476  internal_sp_t &middle = *middle_p.second;
477 
478  /*
479  * Prepare four output vectors for seed candidates:
480  *
481  * bottom_lh_v denotes the candidates bottom seed points, assuming that the
482  * track has monotonically _increasing_ z position. bottom_hl_v denotes the
483  * candidate bottom points assuming that the track has monotonically
484  * _decreasing_ z position. top_lh_v are the candidate top points for an
485  * increasing z track, and top_hl_v are the candidate top points for a
486  * decreasing z track.
487  */
488  std::vector<internal_sp_t *> bottom_lh_v, bottom_hl_v, top_lh_v, top_hl_v;
489 
490  /*
491  * Storage for seed candidates
492  */
493  std::size_t max_num_quality_seeds_per_spm =
494  m_config.seedFilter->getSeedFilterConfig().maxQualitySeedsPerSpMConf;
495  std::size_t max_num_seeds_per_spm =
496  m_config.seedFilter->getSeedFilterConfig().maxSeedsPerSpMConf;
497 
499  candidates_collector;
500  candidates_collector.setMaxElements(max_num_seeds_per_spm,
501  max_num_quality_seeds_per_spm);
502 
503  /*
504  * Calculate the search ranges for bottom and top candidates for this middle
505  * space point.
506  */
507  range_t bottom_r = validTupleOrthoRangeHL(middle);
508  range_t top_r = validTupleOrthoRangeLH(middle);
509 
510  /*
511  * Calculate the value of cot(θ) for this middle spacepoint.
512  */
513  float myCotTheta =
514  std::max(std::abs(middle.z() / middle.radius()), m_config.cotThetaMax);
515 
516  /*
517  * Calculate the maximum Δr, given that we have already constrained our
518  * search space.
519  */
520  float deltaRMaxTop = top_r[DimR].max() - middle.radius();
521  float deltaRMaxBottom = middle.radius() - bottom_r[DimR].min();
522 
523  /*
524  * Create the search range for the bottom spacepoint assuming a
525  * monotonically increasing z track, by calculating the minimum z value from
526  * the cot(θ), and by setting the maximum to the z position of the middle
527  * spacepoint - if the z position is higher than the middle point, then it
528  * would be a decreasing z track!
529  */
530  range_t bottom_lh_r = bottom_r;
531  bottom_lh_r[DimZ].shrink(middle.z() - myCotTheta * deltaRMaxBottom,
532  middle.z());
533 
534  /*
535  * Calculate the search ranges for the other four sets of points in a
536  * similar fashion.
537  */
538  range_t top_lh_r = top_r;
539  top_lh_r[DimZ].shrink(middle.z(), middle.z() + myCotTheta * deltaRMaxTop);
540 
541  range_t bottom_hl_r = bottom_r;
542  bottom_hl_r[DimZ].shrink(middle.z(),
543  middle.z() + myCotTheta * deltaRMaxBottom);
544  range_t top_hl_r = top_r;
545  top_hl_r[DimZ].shrink(middle.z() - myCotTheta * deltaRMaxTop, middle.z());
546 
547  /*
548  * Make sure the candidate vectors are clear, in case we've used them
549  * before.
550  */
551  bottom_lh_v.clear();
552  bottom_hl_v.clear();
553  top_lh_v.clear();
554  top_hl_v.clear();
555 
556  /*
557  * Now, we will actually search for the spaces. Remembering that we combine
558  * bottom and top candidates for increasing and decreasing tracks
559  * separately, we will first check whether both the search ranges for
560  * increasing tracks are not degenerate - if they are, we will never find
561  * any seeds and we do not need to bother doing the search.
562  */
563  if (!bottom_lh_r.degenerate() && !top_lh_r.degenerate()) {
564  /*
565  * Search the trees for points that lie in the given search range.
566  */
567  tree.rangeSearchMapDiscard(top_lh_r,
568  [this, &options, &middle, &top_lh_v](
569  const typename tree_t::coordinate_t &,
570  const typename tree_t::value_t &top) {
571  if (validTuple(options, *top, middle, true)) {
572  top_lh_v.push_back(top);
573  }
574  });
575  }
576 
577  /*
578  * Perform the same search for candidate bottom spacepoints, but for
579  * monotonically decreasing z tracks.
580  */
581  if (!bottom_hl_r.degenerate() && !top_hl_r.degenerate()) {
582  tree.rangeSearchMapDiscard(top_hl_r,
583  [this, &options, &middle, &top_hl_v](
584  const typename tree_t::coordinate_t &,
585  const typename tree_t::value_t &top) {
586  if (validTuple(options, middle, *top, false)) {
587  top_hl_v.push_back(top);
588  }
589  });
590  }
591 
592  // apply cut on the number of top SP if seedConfirmation is true
593  SeedFilterState seedFilterState;
594  bool search_bot_hl = true;
595  bool search_bot_lh = true;
596  if (m_config.seedConfirmation) {
597  // check if middle SP is in the central or forward region
598  SeedConfirmationRangeConfig seedConfRange =
599  (middle.z() > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
600  middle.z() < m_config.centralSeedConfirmationRange.zMinSeedConf)
601  ? m_config.forwardSeedConfirmationRange
602  : m_config.centralSeedConfirmationRange;
603  // set the minimum number of top SP depending on whether the middle SP is
604  // in the central or forward region
605  seedFilterState.nTopSeedConf = middle.radius() > seedConfRange.rMaxSeedConf
606  ? seedConfRange.nTopForLargeR
607  : seedConfRange.nTopForSmallR;
608  // set max bottom radius for seed confirmation
609  seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
610  // continue if number of top SPs is smaller than minimum
611  if (top_lh_v.size() < seedFilterState.nTopSeedConf) {
612  search_bot_lh = false;
613  }
614  if (top_hl_v.size() < seedFilterState.nTopSeedConf) {
615  search_bot_hl = false;
616  }
617  }
618 
619  /*
620  * Next, we perform a search for bottom candidates in increasing z tracks,
621  * which only makes sense if we found any bottom candidates.
622  */
623  if (!top_lh_v.empty() and search_bot_lh) {
625  bottom_lh_r, [this, &options, &middle, &bottom_lh_v](
626  const typename tree_t::coordinate_t &,
627  const typename tree_t::value_t &bottom) {
628  if (validTuple(options, *bottom, middle, false)) {
629  bottom_lh_v.push_back(bottom);
630  }
631  });
632  }
633 
634  /*
635  * And repeat for the top spacepoints for decreasing z tracks!
636  */
637  if (!top_hl_v.empty() and search_bot_hl) {
639  bottom_hl_r, [this, &options, &middle, &bottom_hl_v](
640  const typename tree_t::coordinate_t &,
641  const typename tree_t::value_t &bottom) {
642  if (validTuple(options, middle, *bottom, true)) {
643  bottom_hl_v.push_back(bottom);
644  }
645  });
646  }
647 
648  /*
649  * If we have candidates for increasing z tracks, we try to combine them.
650  */
651  if (!bottom_lh_v.empty() && !top_lh_v.empty()) {
652  filterCandidates(options, middle, bottom_lh_v, top_lh_v, seedFilterState,
653  candidates_collector, spacePointData);
654  }
655  /*
656  * Try to combine candidates for decreasing z tracks.
657  */
658  if (!bottom_hl_v.empty() && !top_hl_v.empty()) {
659  filterCandidates(options, middle, bottom_hl_v, top_hl_v, seedFilterState,
660  candidates_collector, spacePointData);
661  }
662  /*
663  * Run a seed filter, just like in other seeding algorithms.
664  */
665  if ((!bottom_lh_v.empty() && !top_lh_v.empty()) or
666  (!bottom_hl_v.empty() && !top_hl_v.empty())) {
667  m_config.seedFilter->filterSeeds_1SpFixed(
668  spacePointData, candidates_collector, seedFilterState.numQualitySeeds,
669  std::back_inserter(out_cont));
670  }
671 }
672 
673 template <typename external_spacepoint_t>
675  const std::vector<internal_sp_t *> &spacePoints) const -> tree_t {
676  std::vector<typename tree_t::pair_t> points;
677 
678  /*
679  * For every input point, we create a coordinate-pointer pair, which we then
680  * linearly pass to the k-d tree constructor. That constructor will take
681  * care of sorting the pairs and splitting the space.
682  */
683  for (internal_sp_t *sp : spacePoints) {
684  typename tree_t::coordinate_t point;
685 
686  point[DimPhi] = sp->phi();
687  point[DimR] = sp->radius();
688  point[DimZ] = sp->z();
689 
690  points.emplace_back(point, sp);
691  }
692 
693  return tree_t(std::move(points));
694 }
695 
696 template <typename external_spacepoint_t>
697 template <typename input_container_t, typename output_container_t,
698  typename callable_t>
701  const input_container_t &spacePoints, output_container_t &out_cont,
702  callable_t &&extract_coordinates) const {
703  if (not options.isInInternalUnits) {
704  throw std::runtime_error(
705  "SeedFinderOptions not in ACTS internal units in "
706  "SeedFinderOrthogonal");
707  }
708  /*
709  * The template parameters we accept are a little too generic, so we want to
710  * run some basic checks to make sure the containers have the correct value
711  * types.
712  */
713  static_assert(std::is_same_v<typename output_container_t::value_type,
715  "Output iterator container type must accept seeds.");
716  static_assert(std::is_same_v<typename input_container_t::value_type,
717  const external_spacepoint_t *>,
718  "Input container must contain external spacepoints.");
719 
720  /*
721  * Sadly, for the time being, we will need to construct our internal space
722  * points on the heap. This adds some additional overhead and work. Here we
723  * take each external spacepoint, allocate a corresponding internal space
724  * point, and save it in a vector.
725  */
726  Acts::Extent rRangeSPExtent;
727  std::size_t counter = 0;
728  std::vector<internal_sp_t *> internalSpacePoints;
729  Acts::SpacePointData spacePointData;
730  spacePointData.resize(spacePoints.size());
731 
732  for (const external_spacepoint_t *p : spacePoints) {
733  auto [position, variance] = extract_coordinates(p);
734  internalSpacePoints.push_back(new InternalSpacePoint<external_spacepoint_t>(
735  counter++, *p, position, options.beamPos, variance));
736  // store x,y,z values in extent
737  rRangeSPExtent.extend(position);
738  }
739  // variable middle SP radial region of interest
740  const Acts::Range1D<float> rMiddleSPRange(
741  std::floor(rRangeSPExtent.min(Acts::binR) / 2) * 2 +
742  m_config.deltaRMiddleMinSPRange,
743  std::floor(rRangeSPExtent.max(Acts::binR) / 2) * 2 -
744  m_config.deltaRMiddleMaxSPRange);
745 
746  /*
747  * Construct the k-d tree from these points. Note that this not consume or
748  * take ownership of the points.
749  */
750  tree_t tree = createTree(internalSpacePoints);
751  /*
752  * Run the seeding algorithm by iterating over all the points in the tree
753  * and seeing what happens if we take them to be our middle spacepoint.
754  */
755  for (const typename tree_t::pair_t &middle_p : tree) {
756  internal_sp_t &middle = *middle_p.second;
757  auto rM = middle.radius();
758 
759  /*
760  * Cut: Ensure that the middle spacepoint lies within a valid r-region for
761  * middle points.
762  */
763  if (m_config.useVariableMiddleSPRange) {
764  if (rM < rMiddleSPRange.min() || rM > rMiddleSPRange.max()) {
765  continue;
766  }
767  } else {
768  if (rM > m_config.rMaxMiddle || rM < m_config.rMinMiddle) {
769  continue;
770  }
771  }
772 
773  // remove all middle SPs outside phi and z region of interest
774  if (middle.z() < m_config.zOutermostLayers.first or
775  middle.z() > m_config.zOutermostLayers.second) {
776  continue;
777  }
778  float spPhi = middle.phi();
779  if (spPhi > m_config.phiMax or spPhi < m_config.phiMin) {
780  continue;
781  }
782 
783  processFromMiddleSP(options, tree, out_cont, middle_p, spacePointData);
784  }
785 
786  /*
787  * Don't forget to get rid of all the spacepoints we just allocated!
788  */
789  for (const internal_sp_t *p : internalSpacePoints) {
790  delete p;
791  }
792 }
793 
794 template <typename external_spacepoint_t>
795 template <typename input_container_t, typename callable_t>
796 std::vector<Seed<external_spacepoint_t>>
799  const input_container_t &spacePoints,
800  callable_t &&extract_coordinates) const {
801  std::vector<seed_t> r;
802  createSeeds(options, spacePoints, r,
803  std::forward<callable_t>(extract_coordinates));
804  return r;
805 }
806 
807 } // namespace Acts