Analysis Software
Documentation for sPHENIX simulation software
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
SeedFinder.ipp
Go to the documentation of this file. Or view the newest version in sPHENIX GitHub for file SeedFinder.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 
9 #include <algorithm>
10 #include <cmath>
11 #include <numeric>
12 #include <type_traits>
13 
14 namespace Acts {
15 
16 template <typename external_spacepoint_t, typename platform_t>
19  : m_config(config) {
20  if (not config.isInInternalUnits) {
21  throw std::runtime_error(
22  "SeedFinderConfig not in ACTS internal units in SeedFinder");
23  }
24  if (std::isnan(config.deltaRMaxTopSP)) {
25  throw std::runtime_error("Value of deltaRMaxTopSP was not initialised");
26  }
27  if (std::isnan(config.deltaRMinTopSP)) {
28  throw std::runtime_error("Value of deltaRMinTopSP was not initialised");
29  }
30  if (std::isnan(config.deltaRMaxBottomSP)) {
31  throw std::runtime_error("Value of deltaRMaxBottomSP was not initialised");
32  }
33  if (std::isnan(config.deltaRMinBottomSP)) {
34  throw std::runtime_error("Value of deltaRMinBottomSP was not initialised");
35  }
36 }
37 
38 template <typename external_spacepoint_t, typename platform_t>
39 template <template <typename...> typename container_t, typename sp_range_t>
43  std::back_insert_iterator<container_t<Seed<external_spacepoint_t>>> outIt,
44  const sp_range_t& bottomSPsIdx, const std::size_t middleSPsIdx,
45  const sp_range_t& topSPsIdx,
46  const Acts::Range1D<float>& rMiddleSPRange) const {
47  if (not options.isInInternalUnits) {
48  throw std::runtime_error(
49  "SeedFinderOptions not in ACTS internal units in SeedFinder");
50  }
51 
52  // This is used for seed filtering later
53  const std::size_t max_num_seeds_per_spm =
54  m_config.seedFilter->getSeedFilterConfig().maxSeedsPerSpMConf;
55  const std::size_t max_num_quality_seeds_per_spm =
56  m_config.seedFilter->getSeedFilterConfig().maxQualitySeedsPerSpMConf;
57 
58  state.candidates_collector.setMaxElements(max_num_seeds_per_spm,
59  max_num_quality_seeds_per_spm);
60 
61  // If there are no bottom or top bins, just return and waste no time
62  if (bottomSPsIdx.size() == 0 or topSPsIdx.size() == 0) {
63  return;
64  }
65 
66  // Get the middle space point candidates
67  const auto& middleSPs = grid.at(middleSPsIdx);
68 
69  // neighbours
70  // clear previous results
71  state.bottomNeighbours.clear();
72  state.topNeighbours.clear();
73 
74  // Fill
75  // bottoms
76  for (const std::size_t idx : bottomSPsIdx) {
77  state.bottomNeighbours.emplace_back(
78  grid, idx, middleSPs.front()->radius() - m_config.deltaRMaxBottomSP);
79  }
80  // tops
81  for (const std::size_t idx : topSPsIdx) {
82  state.topNeighbours.emplace_back(
83  grid, idx, middleSPs.front()->radius() + m_config.deltaRMinTopSP);
84  }
85 
86  for (const auto& spM : middleSPs) {
87  float rM = spM->radius();
88 
89  // check if spM is outside our radial region of interest
90  if (m_config.useVariableMiddleSPRange) {
91  if (rM < rMiddleSPRange.min()) {
92  continue;
93  }
94  if (rM > rMiddleSPRange.max()) {
95  // break because SPs are sorted in r
96  break;
97  }
98  } else if (not m_config.rRangeMiddleSP.empty()) {
100  auto pVal = std::lower_bound(m_config.zBinEdges.begin(),
101  m_config.zBinEdges.end(), spM->z());
102  int zBin = std::distance(m_config.zBinEdges.begin(), pVal);
104  zBin == 0 ? zBin : --zBin;
105  if (rM < m_config.rRangeMiddleSP[zBin][0]) {
106  continue;
107  }
108  if (rM > m_config.rRangeMiddleSP[zBin][1]) {
109  // break because SPs are sorted in r
110  break;
111  }
112  } else {
113  if (rM < m_config.rMinMiddle) {
114  continue;
115  }
116  if (rM > m_config.rMaxMiddle) {
117  // break because SPs are sorted in r
118  break;
119  }
120  }
121 
122  // remove middle SPs on the last layer since there would be no outer SPs to
123  // complete a seed
124  float zM = spM->z();
125  if (zM < m_config.zOutermostLayers.first or
126  zM > m_config.zOutermostLayers.second) {
127  continue;
128  }
129 
130  const float uIP = -1. / rM;
131  const float cosPhiM = -spM->x() * uIP;
132  const float sinPhiM = -spM->y() * uIP;
133  const float uIP2 = uIP * uIP;
134 
135  // Iterate over middle-top dublets
136  getCompatibleDoublets<Acts::SpacePointCandidateType::eTop>(
137  state.spacePointData, options, grid, state.topNeighbours, *spM.get(),
138  state.linCircleTop, state.compatTopSP, m_config.deltaRMinTopSP,
139  m_config.deltaRMaxTopSP, uIP, uIP2, cosPhiM, sinPhiM);
140 
141  // no top SP found -> try next spM
142  if (state.compatTopSP.empty()) {
143  continue;
144  }
145 
146  // apply cut on the number of top SP if seedConfirmation is true
147  SeedFilterState seedFilterState;
148  if (m_config.seedConfirmation) {
149  // check if middle SP is in the central or forward region
150  SeedConfirmationRangeConfig seedConfRange =
151  (zM > m_config.centralSeedConfirmationRange.zMaxSeedConf ||
152  zM < m_config.centralSeedConfirmationRange.zMinSeedConf)
153  ? m_config.forwardSeedConfirmationRange
154  : m_config.centralSeedConfirmationRange;
155  // set the minimum number of top SP depending on whether the middle SP is
156  // in the central or forward region
157  seedFilterState.nTopSeedConf = rM > seedConfRange.rMaxSeedConf
158  ? seedConfRange.nTopForLargeR
159  : seedConfRange.nTopForSmallR;
160  // set max bottom radius for seed confirmation
161  seedFilterState.rMaxSeedConf = seedConfRange.rMaxSeedConf;
162  // continue if number of top SPs is smaller than minimum
163  if (state.compatTopSP.size() < seedFilterState.nTopSeedConf) {
164  continue;
165  }
166  }
167 
168  // Iterate over middle-bottom dublets
169  getCompatibleDoublets<Acts::SpacePointCandidateType::eBottom>(
170  state.spacePointData, options, grid, state.bottomNeighbours, *spM.get(),
171  state.linCircleBottom, state.compatBottomSP, m_config.deltaRMinBottomSP,
172  m_config.deltaRMaxBottomSP, uIP, uIP2, cosPhiM, sinPhiM);
173 
174  // no bottom SP found -> try next spM
175  if (state.compatBottomSP.empty()) {
176  continue;
177  }
178 
179  // filter candidates
180  if (m_config.useDetailedDoubleMeasurementInfo) {
181  filterCandidates<Acts::DetectorMeasurementInfo::eDetailed>(
182  state.spacePointData, *spM.get(), options, seedFilterState, state);
183  } else {
184  filterCandidates<Acts::DetectorMeasurementInfo::eDefault>(
185  state.spacePointData, *spM.get(), options, seedFilterState, state);
186  }
187 
188  m_config.seedFilter->filterSeeds_1SpFixed(
190  seedFilterState.numQualitySeeds, outIt);
191 
192  } // loop on mediums
193 }
194 
195 template <typename external_spacepoint_t, typename platform_t>
196 template <Acts::SpacePointCandidateType candidateType, typename out_range_t>
197 inline void
199  Acts::SpacePointData& spacePointData,
202  boost::container::small_vector<Neighbour<external_spacepoint_t>, 9>&
203  otherSPsNeighbours,
205  std::vector<LinCircle>& linCircleVec, out_range_t& outVec,
206  const float deltaRMinSP, const float deltaRMaxSP, const float uIP,
207  const float uIP2, const float cosPhiM, const float sinPhiM) const {
208  float impactMax = m_config.impactMax;
209  if constexpr (candidateType == Acts::SpacePointCandidateType::eBottom) {
210  impactMax = -impactMax;
211  }
212 
213  outVec.clear();
214  linCircleVec.clear();
215 
216  // get number of neighbour SPs
217  std::size_t nsp = 0;
218  for (const auto& otherSPCol : otherSPsNeighbours) {
219  nsp += grid.at(otherSPCol.index).size();
220  }
221 
222  linCircleVec.reserve(nsp);
223  outVec.reserve(nsp);
224 
225  const float rM = mediumSP.radius();
226  const float xM = mediumSP.x();
227  const float yM = mediumSP.y();
228  const float zM = mediumSP.z();
229  const float varianceRM = mediumSP.varianceR();
230  const float varianceZM = mediumSP.varianceZ();
231 
232  float vIPAbs = 0;
233  if (m_config.interactionPointCut) {
234  // equivalent to m_config.impactMax / (rM * rM);
235  vIPAbs = impactMax * uIP2;
236  }
237 
238  float deltaR = 0.;
239  float deltaZ = 0.;
240 
241  for (auto& otherSPCol : otherSPsNeighbours) {
242  const auto& otherSPs = grid.at(otherSPCol.index);
243  if (otherSPs.size() == 0) {
244  continue;
245  }
246 
247  // we make a copy of the iterator here since we need it to remain
248  // the same in the Neighbour object
249  auto min_itr = otherSPCol.itr;
250 
251  // find the first SP inside the radius region of interest and update
252  // the iterator so we don't need to look at the other SPs again
253  for (; min_itr != otherSPs.end(); ++min_itr) {
254  const auto& otherSP = *min_itr;
255  if constexpr (candidateType == Acts::SpacePointCandidateType::eBottom) {
256  // if r-distance is too big, try next SP in bin
257  if ((rM - otherSP->radius()) <= deltaRMaxSP) {
258  break;
259  }
260  } else {
261  // if r-distance is too small, try next SP in bin
262  if ((otherSP->radius() - rM) >= deltaRMinSP) {
263  break;
264  }
265  }
266  }
267  // We update the iterator in the Neighbour object
268  // that mean that we have changed the middle space point
269  // and the lower bound has moved accordingly
270  otherSPCol.itr = min_itr;
271 
272  for (; min_itr != otherSPs.end(); ++min_itr) {
273  const auto& otherSP = *min_itr;
274 
275  if constexpr (candidateType == Acts::SpacePointCandidateType::eBottom) {
276  deltaR = (rM - otherSP->radius());
277 
278  // if r-distance is too small, try next SP in bin
279  if (deltaR < deltaRMinSP) {
280  break;
281  }
282  } else {
283  deltaR = (otherSP->radius() - rM);
284 
285  // if r-distance is too big, try next SP in bin
286  if (deltaR > deltaRMaxSP) {
287  break;
288  }
289  }
290 
291  if constexpr (candidateType == Acts::SpacePointCandidateType::eBottom) {
292  deltaZ = (zM - otherSP->z());
293  } else {
294  deltaZ = (otherSP->z() - zM);
295  }
296 
297  // the longitudinal impact parameter zOrigin is defined as (zM - rM *
298  // cotTheta) where cotTheta is the ratio Z/R (forward angle) of space
299  // point duplet but instead we calculate (zOrigin * deltaR) and multiply
300  // collisionRegion by deltaR to avoid divisions
301  const float zOriginTimesDeltaR = (zM * deltaR - rM * deltaZ);
302  // check if duplet origin on z axis within collision region
303  if (zOriginTimesDeltaR < m_config.collisionRegionMin * deltaR or
304  zOriginTimesDeltaR > m_config.collisionRegionMax * deltaR) {
305  continue;
306  }
307 
308  // if interactionPointCut is false we apply z cuts before coordinate
309  // transformation to avoid unnecessary calculations. If
310  // interactionPointCut is true we apply the curvature cut first because it
311  // is more frequent but requires the coordinate transformation
312  if (not m_config.interactionPointCut) {
313  // check if duplet cotTheta is within the region of interest
314  // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
315  // cotThetaMax by deltaR to avoid division
316  if (deltaZ > m_config.cotThetaMax * deltaR or
317  deltaZ < -m_config.cotThetaMax * deltaR) {
318  continue;
319  }
320  // if z-distance between SPs is within max and min values
321  if (deltaZ > m_config.deltaZMax or deltaZ < -m_config.deltaZMax) {
322  continue;
323  }
324 
325  // transform SP coordinates to the u-v reference frame
326  const float deltaX = otherSP->x() - xM;
327  const float deltaY = otherSP->y() - yM;
328 
329  const float xNewFrame = deltaX * cosPhiM + deltaY * sinPhiM;
330  const float yNewFrame = deltaY * cosPhiM - deltaX * sinPhiM;
331 
332  const float deltaR2 = (deltaX * deltaX + deltaY * deltaY);
333  const float iDeltaR2 = 1. / deltaR2;
334 
335  const float uT = xNewFrame * iDeltaR2;
336  const float vT = yNewFrame * iDeltaR2;
337 
338  const float iDeltaR = std::sqrt(iDeltaR2);
339  const float cotTheta = deltaZ * iDeltaR;
340 
341  const float Er =
342  ((varianceZM + otherSP->varianceZ()) +
343  (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
344  iDeltaR2;
345 
346  // fill output vectors
347  linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
348  yNewFrame);
349  spacePointData.setDeltaR(otherSP->index(),
350  std::sqrt(deltaR2 + (deltaZ * deltaZ)));
351  outVec.push_back(otherSP.get());
352  continue;
353  }
354 
355  // transform SP coordinates to the u-v reference frame
356  const float deltaX = otherSP->x() - xM;
357  const float deltaY = otherSP->y() - yM;
358 
359  const float xNewFrame = deltaX * cosPhiM + deltaY * sinPhiM;
360  const float yNewFrame = deltaY * cosPhiM - deltaX * sinPhiM;
361 
362  const float deltaR2 = (deltaX * deltaX + deltaY * deltaY);
363  const float iDeltaR2 = 1. / deltaR2;
364 
365  const float uT = xNewFrame * iDeltaR2;
366  const float vT = yNewFrame * iDeltaR2;
367 
368  // interactionPointCut == true we apply this cut first cuts before
369  // coordinate transformation to avoid unnecessary calculations
370  if (std::abs(rM * yNewFrame) <= impactMax * xNewFrame) {
371  // check if duplet cotTheta is within the region of interest
372  // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
373  // cotThetaMax by deltaR to avoid division
374  if (deltaZ > m_config.cotThetaMax * deltaR or
375  deltaZ < -m_config.cotThetaMax * deltaR) {
376  continue;
377  }
378 
379  const float iDeltaR = std::sqrt(iDeltaR2);
380  const float cotTheta = deltaZ * iDeltaR;
381 
382  const float Er =
383  ((varianceZM + otherSP->varianceZ()) +
384  (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
385  iDeltaR2;
386 
387  // fill output vectors
388  linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
389  yNewFrame);
390  spacePointData.setDeltaR(otherSP->index(),
391  std::sqrt(deltaR2 + (deltaZ * deltaZ)));
392  outVec.emplace_back(otherSP.get());
393  continue;
394  }
395 
396  // in the rotated frame the interaction point is positioned at x = -rM
397  // and y ~= impactParam
398  const float vIP = (yNewFrame > 0.) ? -vIPAbs : vIPAbs;
399 
400  // we can obtain aCoef as the slope dv/du of the linear function,
401  // estimated using du and dv between the two SP bCoef is obtained by
402  // inserting aCoef into the linear equation
403  const float aCoef = (vT - vIP) / (uT - uIP);
404  const float bCoef = vIP - aCoef * uIP;
405  // the distance of the straight line from the origin (radius of the
406  // circle) is related to aCoef and bCoef by d^2 = bCoef^2 / (1 +
407  // aCoef^2) = 1 / (radius^2) and we can apply the cut on the curvature
408  if ((bCoef * bCoef) * options.minHelixDiameter2 > (1 + aCoef * aCoef)) {
409  continue;
410  }
411 
412  // check if duplet cotTheta is within the region of interest
413  // cotTheta is defined as (deltaZ / deltaR) but instead we multiply
414  // cotThetaMax by deltaR to avoid division
415  if (deltaZ > m_config.cotThetaMax * deltaR or
416  deltaZ < -m_config.cotThetaMax * deltaR) {
417  continue;
418  }
419 
420  const float iDeltaR = std::sqrt(iDeltaR2);
421  const float cotTheta = deltaZ * iDeltaR;
422 
423  const float Er =
424  ((varianceZM + otherSP->varianceZ()) +
425  (cotTheta * cotTheta) * (varianceRM + otherSP->varianceR())) *
426  iDeltaR2;
427 
428  // fill output vectors
429  linCircleVec.emplace_back(cotTheta, iDeltaR, Er, uT, vT, xNewFrame,
430  yNewFrame);
431  spacePointData.setDeltaR(otherSP->index(),
432  std::sqrt(deltaR2 + (deltaZ * deltaZ)));
433  outVec.emplace_back(otherSP.get());
434  }
435  }
436 }
437 
438 template <typename external_spacepoint_t, typename platform_t>
439 template <Acts::DetectorMeasurementInfo detailedMeasurement>
441  Acts::SpacePointData& spacePointData,
443  const Acts::SeedFinderOptions& options, SeedFilterState& seedFilterState,
444  SeedingState& state) const {
445  float rM = spM.radius();
446  float cosPhiM = spM.x() / rM;
447  float sinPhiM = spM.y() / rM;
448  float varianceRM = spM.varianceR();
449  float varianceZM = spM.varianceZ();
450 
451  std::size_t numTopSP = state.compatTopSP.size();
452 
453  // sort: make index vector
454  std::vector<std::size_t> sorted_bottoms(state.linCircleBottom.size());
455  for (std::size_t i(0); i < sorted_bottoms.size(); ++i) {
456  sorted_bottoms[i] = i;
457  }
458 
459  std::vector<std::size_t> sorted_tops(state.linCircleTop.size());
460  for (std::size_t i(0); i < sorted_tops.size(); ++i) {
461  sorted_tops[i] = i;
462  }
463 
464  if constexpr (detailedMeasurement ==
465  Acts::DetectorMeasurementInfo::eDefault) {
466  std::sort(sorted_bottoms.begin(), sorted_bottoms.end(),
467  [&state](const std::size_t a, const std::size_t b) -> bool {
468  return state.linCircleBottom[a].cotTheta <
469  state.linCircleBottom[b].cotTheta;
470  });
471 
472  std::sort(sorted_tops.begin(), sorted_tops.end(),
473  [&state](const std::size_t a, const std::size_t b) -> bool {
474  return state.linCircleTop[a].cotTheta <
475  state.linCircleTop[b].cotTheta;
476  });
477  }
478 
479  // Reserve enough space, in case current capacity is too little
480  state.topSpVec.reserve(numTopSP);
481  state.curvatures.reserve(numTopSP);
482  state.impactParameters.reserve(numTopSP);
483 
484  size_t t0 = 0;
485 
486  // clear previous results and then loop on bottoms and tops
487  state.candidates_collector.clear();
488 
489  for (const std::size_t b : sorted_bottoms) {
490  // break if we reached the last top SP
491  if (t0 == numTopSP) {
492  break;
493  }
494 
495  auto lb = state.linCircleBottom[b];
496  float cotThetaB = lb.cotTheta;
497  float Vb = lb.V;
498  float Ub = lb.U;
499  float ErB = lb.Er;
500  float iDeltaRB = lb.iDeltaR;
501 
502  // 1+(cot^2(theta)) = 1/sin^2(theta)
503  float iSinTheta2 = (1. + cotThetaB * cotThetaB);
504  float sigmaSquaredPtDependent = iSinTheta2 * options.sigmapT2perRadius;
505  // calculate max scattering for min momentum at the seed's theta angle
506  // scaling scatteringAngle^2 by sin^2(theta) to convert pT^2 to p^2
507  // accurate would be taking 1/atan(thetaBottom)-1/atan(thetaTop) <
508  // scattering
509  // but to avoid trig functions we approximate cot by scaling by
510  // 1/sin^4(theta)
511  // resolving with pT to p scaling --> only divide by sin^2(theta)
512  // max approximation error for allowed scattering angles of 0.04 rad at
513  // eta=infinity: ~8.5%
514  float scatteringInRegion2 = options.multipleScattering2 * iSinTheta2;
515 
516  float sinTheta = 1 / std::sqrt(iSinTheta2);
517  float cosTheta = cotThetaB * sinTheta;
518 
519  // clear all vectors used in each inner for loop
520  state.topSpVec.clear();
521  state.curvatures.clear();
522  state.impactParameters.clear();
523 
524  // coordinate transformation and checks for middle spacepoint
525  // x and y terms for the rotation from UV to XY plane
526  float rotationTermsUVtoXY[2] = {0, 0};
527  if constexpr (detailedMeasurement ==
528  Acts::DetectorMeasurementInfo::eDetailed) {
529  rotationTermsUVtoXY[0] = cosPhiM * sinTheta;
530  rotationTermsUVtoXY[1] = sinPhiM * sinTheta;
531  }
532 
533  // minimum number of compatible top SPs to trigger the filter for a certain
534  // middle bottom pair if seedConfirmation is false we always ask for at
535  // least one compatible top to trigger the filter
536  size_t minCompatibleTopSPs = 2;
537  if (!m_config.seedConfirmation or
538  state.compatBottomSP[b]->radius() > seedFilterState.rMaxSeedConf) {
539  minCompatibleTopSPs = 1;
540  }
541  if (m_config.seedConfirmation and seedFilterState.numQualitySeeds) {
542  minCompatibleTopSPs++;
543  }
544 
545  for (size_t index_t = t0; index_t < numTopSP; index_t++) {
546  const std::size_t t = sorted_tops[index_t];
547 
548  auto lt = state.linCircleTop[t];
549 
550  float cotThetaT = lt.cotTheta;
551  float rMxy = 0.;
552  float ub = 0.;
553  float vb = 0.;
554  float ut = 0.;
555  float vt = 0.;
556  double rMTransf[3];
557  float xB = 0.;
558  float yB = 0.;
559  float xT = 0.;
560  float yT = 0.;
561  float iDeltaRB2 = 0.;
562  float iDeltaRT2 = 0.;
563 
564  if constexpr (detailedMeasurement ==
565  Acts::DetectorMeasurementInfo::eDetailed) {
566  // protects against division by 0
567  float dU = lt.U - Ub;
568  if (dU == 0.) {
569  continue;
570  }
571  // A and B are evaluated as a function of the circumference parameters
572  // x_0 and y_0
573  float A0 = (lt.V - Vb) / dU;
574 
575  float zPositionMiddle = cosTheta * std::sqrt(1 + A0 * A0);
576 
577  // position of Middle SP converted from UV to XY assuming cotTheta
578  // evaluated from the Bottom and Middle SPs double
579  double positionMiddle[3] = {
580  rotationTermsUVtoXY[0] - rotationTermsUVtoXY[1] * A0,
581  rotationTermsUVtoXY[0] * A0 + rotationTermsUVtoXY[1],
582  zPositionMiddle};
583 
584  if (!xyzCoordinateCheck(spacePointData, m_config, spM, positionMiddle,
585  rMTransf)) {
586  continue;
587  }
588 
589  // coordinate transformation and checks for bottom spacepoint
590  float B0 = 2. * (Vb - A0 * Ub);
591  float Cb = 1. - B0 * lb.y;
592  float Sb = A0 + B0 * lb.x;
593  double positionBottom[3] = {
594  rotationTermsUVtoXY[0] * Cb - rotationTermsUVtoXY[1] * Sb,
595  rotationTermsUVtoXY[0] * Sb + rotationTermsUVtoXY[1] * Cb,
596  zPositionMiddle};
597 
598  auto spB = state.compatBottomSP[b];
599  double rBTransf[3];
600  if (!xyzCoordinateCheck(spacePointData, m_config, *spB, positionBottom,
601  rBTransf)) {
602  continue;
603  }
604 
605  // coordinate transformation and checks for top spacepoint
606  float Ct = 1. - B0 * lt.y;
607  float St = A0 + B0 * lt.x;
608  double positionTop[3] = {
609  rotationTermsUVtoXY[0] * Ct - rotationTermsUVtoXY[1] * St,
610  rotationTermsUVtoXY[0] * St + rotationTermsUVtoXY[1] * Ct,
611  zPositionMiddle};
612 
613  auto spT = state.compatTopSP[t];
614  double rTTransf[3];
615  if (!xyzCoordinateCheck(spacePointData, m_config, *spT, positionTop,
616  rTTransf)) {
617  continue;
618  }
619 
620  // bottom and top coordinates in the spM reference frame
621  xB = rBTransf[0] - rMTransf[0];
622  yB = rBTransf[1] - rMTransf[1];
623  float zB = rBTransf[2] - rMTransf[2];
624  xT = rTTransf[0] - rMTransf[0];
625  yT = rTTransf[1] - rMTransf[1];
626  float zT = rTTransf[2] - rMTransf[2];
627 
628  iDeltaRB2 = 1. / (xB * xB + yB * yB);
629  iDeltaRT2 = 1. / (xT * xT + yT * yT);
630 
631  cotThetaB = -zB * std::sqrt(iDeltaRB2);
632  cotThetaT = zT * std::sqrt(iDeltaRT2);
633  }
634 
635  // use geometric average
636  float cotThetaAvg2 = cotThetaB * cotThetaT;
637  if constexpr (detailedMeasurement ==
638  Acts::DetectorMeasurementInfo::eDetailed) {
639  // use arithmetic average
640  float averageCotTheta = 0.5 * (cotThetaB + cotThetaT);
641  cotThetaAvg2 = averageCotTheta * averageCotTheta;
642  } else if (cotThetaAvg2 <= 0) {
643  continue;
644  }
645 
646  // add errors of spB-spM and spM-spT pairs and add the correlation term
647  // for errors on spM
648  float error2 =
649  lt.Er + ErB +
650  2 * (cotThetaAvg2 * varianceRM + varianceZM) * iDeltaRB * lt.iDeltaR;
651 
652  float deltaCotTheta = cotThetaB - cotThetaT;
653  float deltaCotTheta2 = deltaCotTheta * deltaCotTheta;
654 
655  // Apply a cut on the compatibility between the r-z slope of the two
656  // seed segments. This is done by comparing the squared difference
657  // between slopes, and comparing to the squared uncertainty in this
658  // difference - we keep a seed if the difference is compatible within
659  // the assumed uncertainties. The uncertainties get contribution from
660  // the space-point-related squared error (error2) and a scattering term
661  // calculated assuming the minimum pt we expect to reconstruct
662  // (scatteringInRegion2). This assumes gaussian error propagation which
663  // allows just adding the two errors if they are uncorrelated (which is
664  // fair for scattering and measurement uncertainties)
665  if (deltaCotTheta2 > (error2 + scatteringInRegion2)) {
666  // skip top SPs based on cotTheta sorting when producing triplets
667  if constexpr (detailedMeasurement ==
668  Acts::DetectorMeasurementInfo::eDetailed) {
669  continue;
670  }
671  // break if cotTheta from bottom SP < cotTheta from top SP because
672  // the SP are sorted by cotTheta
673  if (cotThetaB - cotThetaT < 0) {
674  break;
675  }
676  t0 = index_t + 1;
677  continue;
678  }
679 
680  if constexpr (detailedMeasurement ==
681  Acts::DetectorMeasurementInfo::eDetailed) {
682  rMxy = std::sqrt(rMTransf[0] * rMTransf[0] + rMTransf[1] * rMTransf[1]);
683  double irMxy = 1 / rMxy;
684  float Ax = rMTransf[0] * irMxy;
685  float Ay = rMTransf[1] * irMxy;
686 
687  ub = (xB * Ax + yB * Ay) * iDeltaRB2;
688  vb = (yB * Ax - xB * Ay) * iDeltaRB2;
689  ut = (xT * Ax + yT * Ay) * iDeltaRT2;
690  vt = (yT * Ax - xT * Ay) * iDeltaRT2;
691  }
692 
693  float dU = 0;
694  float A = 0;
695  float S2 = 0;
696  float B = 0;
697  float B2 = 0;
698 
699  if constexpr (detailedMeasurement ==
700  Acts::DetectorMeasurementInfo::eDetailed) {
701  dU = ut - ub;
702  // protects against division by 0
703  if (dU == 0.) {
704  continue;
705  }
706  A = (vt - vb) / dU;
707  S2 = 1. + A * A;
708  B = vb - A * ub;
709  B2 = B * B;
710  } else {
711  dU = lt.U - Ub;
712  // protects against division by 0
713  if (dU == 0.) {
714  continue;
715  }
716  // A and B are evaluated as a function of the circumference parameters
717  // x_0 and y_0
718  A = (lt.V - Vb) / dU;
719  S2 = 1. + A * A;
720  B = Vb - A * Ub;
721  B2 = B * B;
722  }
723 
724  // sqrt(S2)/B = 2 * helixradius
725  // calculated radius must not be smaller than minimum radius
726  if (S2 < B2 * options.minHelixDiameter2 * m_config.helixCut) {
727  continue;
728  }
729 
730  // refinement of the cut on the compatibility between the r-z slope of
731  // the two seed segments using a scattering term scaled by the actual
732  // measured pT (p2scatterSigma)
733  float iHelixDiameter2 = B2 / S2;
734  // convert p(T) to p scaling by sin^2(theta) AND scale by 1/sin^4(theta)
735  // from rad to deltaCotTheta
736  float p2scatterSigma = iHelixDiameter2 * sigmaSquaredPtDependent;
737  if (!std::isinf(m_config.maxPtScattering)) {
738  // if pT > maxPtScattering, calculate allowed scattering angle using
739  // maxPtScattering instead of pt.
740  // To avoid 0-divison the pT check is skipped in case of B2==0, and
741  // p2scatterSigma is calculated directly from maxPtScattering
742  if (B2 == 0 or options.pTPerHelixRadius * std::sqrt(S2 / B2) >
743  2. * m_config.maxPtScattering) {
744  float pTscatterSigma =
745  (m_config.highland / m_config.maxPtScattering) *
746  m_config.sigmaScattering;
747  p2scatterSigma = pTscatterSigma * pTscatterSigma * iSinTheta2;
748  }
749  }
750 
751  // if deltaTheta larger than allowed scattering for calculated pT, skip
752  if (deltaCotTheta2 > (error2 + p2scatterSigma)) {
753  if constexpr (detailedMeasurement ==
754  Acts::DetectorMeasurementInfo::eDetailed) {
755  continue;
756  }
757  if (cotThetaB - cotThetaT < 0) {
758  break;
759  }
760  t0 = index_t;
761  continue;
762  }
763  // A and B allow calculation of impact params in U/V plane with linear
764  // function
765  // (in contrast to having to solve a quadratic function in x/y plane)
766  float Im = 0;
767  if constexpr (detailedMeasurement ==
768  Acts::DetectorMeasurementInfo::eDetailed) {
769  Im = std::abs((A - B * rMxy) * rMxy);
770  } else {
771  Im = std::abs((A - B * rM) * rM);
772  }
773 
774  if (Im > m_config.impactMax) {
775  continue;
776  }
777 
778  state.topSpVec.push_back(state.compatTopSP[t]);
779  // inverse diameter is signed depending on if the curvature is
780  // positive/negative in phi
781  state.curvatures.push_back(B / std::sqrt(S2));
782  state.impactParameters.push_back(Im);
783  } // loop on tops
784 
785  // continue if number of top SPs is smaller than minimum required for filter
786  if (state.topSpVec.size() < minCompatibleTopSPs) {
787  continue;
788  }
789 
790  seedFilterState.zOrigin = spM.z() - rM * lb.cotTheta;
791 
792  m_config.seedFilter->filterSeeds_2SpFixed(
793  state.spacePointData, *state.compatBottomSP[b], spM, state.topSpVec,
794  state.curvatures, state.impactParameters, seedFilterState,
795  state.candidates_collector);
796  } // loop on bottoms
797 }
798 
799 template <typename external_spacepoint_t, typename platform_t>
800 template <typename sp_range_t>
801 std::vector<Seed<external_spacepoint_t>>
805  const sp_range_t& bottomSPs, const std::size_t middleSPs,
806  const sp_range_t& topSPs) const {
808  const Acts::Range1D<float> rMiddleSPRange;
809  std::vector<Seed<external_spacepoint_t>> ret;
810 
811  createSeedsForGroup(options, state, grid, std::back_inserter(ret), bottomSPs,
812  middleSPs, topSPs, rMiddleSPRange);
813 
814  return ret;
815 }
816 } // namespace Acts