Warning, file /acts/Examples/Algorithms/TrackFinding/src/AdaptiveHoughTransformSeeder.cpp was not indexed
or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/TrackFinding/AdaptiveHoughTransformSeeder.hpp"
0010
0011 #include "Acts/Definitions/Units.hpp"
0012 #include "Acts/EventData/SourceLink.hpp"
0013 #include "Acts/Utilities/ScopedTimer.hpp"
0014 #include "ActsExamples/Framework/AlgorithmContext.hpp"
0015
0016 #include <algorithm>
0017 #include <cmath>
0018 #include <iterator>
0019 #include <map>
0020 #include <numeric>
0021 #include <ostream>
0022 #include <stdexcept>
0023
0024 namespace ActsExamples {
0025
0026
0027 AccumulatorSection::AccumulatorSection(float xw, float yw, float xBegin,
0028 float yBegin, int div,
0029 const std::vector<unsigned> &indices,
0030 const std::vector<float> &history)
0031 : m_xSize(xw),
0032 m_ySize(yw),
0033 m_xBegin(xBegin),
0034 m_yBegin(yBegin),
0035 m_divisionLevel(div),
0036 m_indices(indices),
0037 m_history(history) {}
0038
0039 void AccumulatorSection::updateDimensions(float xw, float yw, float xBegin,
0040 float yBegin) {
0041 m_xSize = xw;
0042 m_ySize = yw;
0043 m_xBegin = xBegin;
0044 m_yBegin = yBegin;
0045 }
0046
0047 void AccumulatorSection::expand(float xs, float ys) {
0048 m_xBegin = m_xBegin + 0.5f * m_xSize - m_xSize * xs * 0.5f;
0049 m_yBegin = m_yBegin + 0.5f * m_ySize - m_ySize * ys * 0.5f;
0050 m_xSize *= xs;
0051 m_ySize *= ys;
0052 }
0053
0054 AccumulatorSection AccumulatorSection::bottomLeft(float xFraction,
0055 float yFraction) const {
0056 return AccumulatorSection(m_xSize * xFraction, m_ySize * yFraction, m_xBegin,
0057 m_yBegin, m_divisionLevel + 1, m_indices,
0058 m_history);
0059 }
0060 AccumulatorSection AccumulatorSection::topLeft(float xFraction,
0061 float yFraction) const {
0062 return AccumulatorSection(m_xSize * xFraction, m_ySize * yFraction, m_xBegin,
0063 m_yBegin + m_ySize - m_ySize * yFraction,
0064 m_divisionLevel + 1, m_indices, m_history);
0065 }
0066 AccumulatorSection AccumulatorSection::topRight(float xFraction,
0067 float yFraction) const {
0068 return AccumulatorSection(m_xSize * xFraction, m_ySize * yFraction,
0069 m_xBegin + m_xSize - m_xSize * xFraction,
0070 m_yBegin + m_ySize - m_ySize * yFraction,
0071 m_divisionLevel + 1, m_indices, m_history);
0072 }
0073 AccumulatorSection AccumulatorSection::bottomRight(float xFraction,
0074 float yFraction) const {
0075 return AccumulatorSection(m_xSize * xFraction, m_ySize * yFraction,
0076 m_xBegin + m_xSize - m_xSize * xFraction, m_yBegin,
0077 m_divisionLevel + 1, m_indices, m_history);
0078 }
0079 AccumulatorSection AccumulatorSection::bottom(float yFraction) const {
0080 return bottomLeft(1.0, yFraction);
0081 }
0082 AccumulatorSection AccumulatorSection::top(float yFraction) const {
0083 return topLeft(1.0, yFraction);
0084 }
0085 AccumulatorSection AccumulatorSection::left(float xFraction) const {
0086 return bottomLeft(xFraction, 1.0);
0087 }
0088 AccumulatorSection AccumulatorSection::right(float xFraction) const {
0089 return bottomRight(xFraction, 1.0);
0090 }
0091
0092 AdaptiveHoughTransformSeeder::AdaptiveHoughTransformSeeder(
0093 ActsExamples::AdaptiveHoughTransformSeeder::Config cfg,
0094 Acts::Logging::Level lvl)
0095 : ActsExamples::IAlgorithm("AdaptiveHoughTransformSeeder", lvl),
0096 m_cfg(std::move(cfg)),
0097 m_logger(Acts::getDefaultLogger("AdaptiveHoughTransformSeeder", lvl)) {
0098 for (const auto &spName : config().inputSpacePoints) {
0099 const auto &handle = m_inputSpacePoints.emplace_back(
0100 std::make_unique<ReadDataHandle<SimSpacePointContainer>>(
0101 this,
0102 "InputSpacePoints#" + std::to_string(m_inputSpacePoints.size())));
0103 handle->initialize(spName);
0104 }
0105 if (config().outputSeeds.empty()) {
0106 throw std::invalid_argument(
0107 "AdaptiveHoughTransformSeeder: Output seeds collection name empty");
0108 }
0109 m_outputSeeds.initialize(config().outputSeeds);
0110 }
0111
0112
0113
0114
0115
0116
0117 namespace {
0118 constexpr unsigned int phiSplitMinIndex = 0;
0119 constexpr unsigned int phiSplitWidthIndex = 1;
0120 constexpr unsigned int zSplitMinIndex = 2;
0121 constexpr unsigned int zSplitWidthIndex = 3;
0122 constexpr unsigned int cotThetaSplitMinIndex = 4;
0123 constexpr unsigned int cotThetaSplitWidthIndex = 5;
0124 }
0125
0126 ProcessCode AdaptiveHoughTransformSeeder::execute(
0127 const AlgorithmContext &ctx) const {
0128
0129 std::vector<PreprocessedMeasurement> measurements;
0130 preparePreprocessedMeasurements(ctx, measurements);
0131
0132
0133 std::vector<AccumulatorSection> stack1;
0134 fillStackPhiSplit(stack1, measurements);
0135
0136
0137
0138 for (auto §ion : stack1) {
0139 section.updateDimensions(2.0f * config().zRange,
0140 2.0f * config().cotThetaRange, -config().zRange,
0141 -config().cotThetaRange);
0142 }
0143 std::vector<AccumulatorSection> stack2;
0144 {
0145 Acts::ScopedTimer st("splitInZCotTheta", logger());
0146 processStackZCotThetaSplit(stack1, stack2, measurements);
0147 if (config().doSecondPhase) {
0148 for (AccumulatorSection §ion : stack2) {
0149 section.setHistory(zSplitMinIndex, section.xBegin());
0150 section.setHistory(zSplitWidthIndex, section.xSize());
0151 section.setHistory(cotThetaSplitMinIndex, section.yBegin());
0152 section.setHistory(cotThetaSplitWidthIndex, section.ySize());
0153 }
0154 }
0155 }
0156 ACTS_DEBUG("past StackZCotThetaSplit stack1 size "
0157 << stack1.size() << " stack2 size " << stack2.size());
0158 for (auto §ion : stack2) {
0159 ACTS_DEBUG("Post z_vertex cot theta regions "
0160 << section.count() << " for region starting from "
0161 << section.xBegin() << " " << section.yBegin());
0162
0163
0164 section.updateDimensions(
0165 section.history(phiSplitWidthIndex), 2.0f * config().qOverPtMin,
0166 section.history(phiSplitMinIndex), -config().qOverPtMin);
0167 }
0168 {
0169 Acts::ScopedTimer st("processQOverPtPhi", logger());
0170 processStackQOverPtPhi(stack2, stack1, measurements);
0171 }
0172 ACTS_DEBUG("past StackQOverPtPhi stack1 size "
0173 << stack1.size() << " stack2 size " << stack2.size());
0174
0175 if (m_cfg.deduplicate) {
0176 Acts::ScopedTimer st("deduplication", logger());
0177 deduplicate(stack1);
0178 ACTS_DEBUG("past deduplication " << stack1.size());
0179 }
0180
0181
0182 if (config().doSecondPhase) {
0183 Acts::ScopedTimer st("secondPhase", logger());
0184
0185 for (auto §ion : stack1) {
0186 section.updateDimensions(section.history(zSplitWidthIndex),
0187 section.history(cotThetaSplitWidthIndex),
0188 section.history(zSplitMinIndex),
0189 section.history(cotThetaSplitMinIndex));
0190 }
0191 processStackZCotTheta(stack1, stack2, measurements);
0192 ACTS_DEBUG("past StackZCotTheta stack1 size "
0193 << stack1.size() << " stack2 size " << stack2.size());
0194
0195 if (m_cfg.deduplicate) {
0196 Acts::ScopedTimer st2("deduplication", logger());
0197 deduplicate(stack2);
0198 ACTS_DEBUG("past second deduplication " << stack2.size());
0199 }
0200 }
0201
0202 std::vector<AccumulatorSection> &solutions =
0203 config().doSecondPhase ? stack2 : stack1;
0204 Acts::ScopedTimer st("seedsMaking", logger());
0205
0206
0207 SimSeedContainer seeds;
0208 seeds.reserve(solutions.size());
0209
0210 ACTS_VERBOSE("Number of solutions " << solutions.size());
0211 makeSeeds(seeds, solutions, measurements);
0212
0213 m_outputSeeds(ctx, std::move(seeds));
0214
0215 return ActsExamples::ProcessCode::SUCCESS;
0216 }
0217
0218 void AdaptiveHoughTransformSeeder::preparePreprocessedMeasurements(
0219 const AlgorithmContext &ctx,
0220 std::vector<PreprocessedMeasurement> &measurements) const {
0221 for (const auto &isp : m_inputSpacePoints) {
0222 const auto &spContainer = (*isp)(ctx);
0223 ACTS_DEBUG("Inserting " << spContainer.size()
0224 << " space points from collection\"" << isp->key()
0225 << "\"");
0226
0227 for (const SimSpacePoint &sp : spContainer) {
0228 const double phi = std::atan2(sp.y(), sp.x());
0229 const double invr = 1.0 / sp.r();
0230 measurements.emplace_back(
0231 invr, phi, sp.z(),
0232 Acts::SourceLink(static_cast<const SimSpacePoint *>(&sp)));
0233
0234 if (phi < -std::numbers::pi + config().phiWrap * std::numbers::pi) {
0235 measurements.emplace_back(
0236 invr, phi + 2 * std::numbers::pi, sp.z(),
0237 Acts::SourceLink(static_cast<const SimSpacePoint *>(&sp)));
0238 }
0239 if (phi > std::numbers::pi - config().phiWrap * std::numbers::pi) {
0240 measurements.emplace_back(
0241 invr, phi - 2 * std::numbers::pi, sp.z(),
0242 Acts::SourceLink(static_cast<const SimSpacePoint *>(&sp)));
0243 }
0244 }
0245 }
0246 ACTS_DEBUG("Total number of " << measurements.size()
0247 << " used to account for phi wrapping");
0248 }
0249
0250 void AdaptiveHoughTransformSeeder::fillStackPhiSplit(
0251 std::vector<AccumulatorSection> &stack,
0252 const std::vector<PreprocessedMeasurement> &measurements) const {
0253 Acts::ScopedTimer st("splitInQuadrants", logger());
0254 const int nSplits = 8;
0255 const auto wedgeWidth = static_cast<float>(2.0 * std::numbers::pi / nSplits);
0256
0257 for (int phiIndex = 0; phiIndex < nSplits; phiIndex++) {
0258 const auto startPhi = static_cast<float>(
0259 wedgeWidth * static_cast<float>(phiIndex) - std::numbers::pi);
0260 stack.emplace_back(1.2f * wedgeWidth, 2.0f * config().qOverPtMin, startPhi,
0261 -config().qOverPtMin);
0262 stack.back().indices().resize(measurements.size());
0263 std::iota(std::begin(stack.back().indices()),
0264 std::end(stack.back().indices()), 0);
0265 stack.back().setHistory(phiSplitMinIndex, startPhi);
0266 stack.back().setHistory(phiSplitWidthIndex, wedgeWidth);
0267
0268 updateSection(stack.back(), measurements, m_qOverPtPhiLineParams);
0269 ACTS_DEBUG("Initial split " << stack.back().count()
0270 << " for region starting from " << startPhi);
0271 for (unsigned index : stack.back().indices()) {
0272 const PreprocessedMeasurement &m = measurements[index];
0273 ACTS_DEBUG("phi section "
0274 << startPhi << " x: " << std::cos(m.phi) / m.invr
0275 << " y: " << std::sin(m.phi) / m.invr << " z: " << m.z);
0276 }
0277 }
0278 }
0279
0280 void AdaptiveHoughTransformSeeder::processStackQOverPtPhi(
0281 std::vector<AccumulatorSection> &input,
0282 std::vector<AccumulatorSection> &output,
0283 const std::vector<PreprocessedMeasurement> &measurements) const {
0284 struct Stats {
0285 double area{};
0286 int nSections{};
0287 int nLines{};
0288 int discardedByThresholdCut{};
0289 int discardedByCrossingCut{};
0290 };
0291 std::map<int, Stats> sStat;
0292 ExplorationOptions opt;
0293 opt.xMinBinSize = config().phiMinBinSize;
0294 opt.yMinBinSize = config().qOverPtMinBinSize;
0295 opt.lineParamFunctor = m_qOverPtPhiLineParams;
0296 opt.decisionFunctor = [&sStat, &cfg = m_cfg, &opt, this](
0297 const AccumulatorSection §ion,
0298 const std::vector<PreprocessedMeasurement> &mes) {
0299 using enum ExplorationOptions<PreprocessedMeasurement>::Decision;
0300 if (section.divisionLevel() <= 8) {
0301 return Drill;
0302 }
0303
0304 if (section.count() < cfg.threshold) {
0305 sStat[section.divisionLevel()].discardedByThresholdCut += 1;
0306 return Discard;
0307 }
0308 if (section.count() < 3 * cfg.threshold &&
0309 !passIntersectionsCheck(section, mes, opt.lineParamFunctor,
0310 cfg.threshold * (cfg.threshold - 1))) {
0311 sStat[section.divisionLevel()].discardedByCrossingCut += 1;
0312 return Discard;
0313 }
0314
0315 if (section.count() >= cfg.threshold &&
0316 section.count() <= cfg.noiseThreshold &&
0317 section.xSize() <= cfg.phiMinBinSize &&
0318 section.ySize() <= cfg.qOverPtMinBinSize) {
0319 return Accept;
0320 }
0321 sStat[section.divisionLevel()].area += section.xSize() * section.ySize();
0322 sStat[section.divisionLevel()].nSections += 1;
0323 sStat[section.divisionLevel()].nLines += section.count();
0324 return Drill;
0325 };
0326
0327 exploreParametersSpace(input, measurements, opt, output);
0328 const unsigned nl =
0329 std::accumulate(output.begin(), output.end(), 0,
0330 [](unsigned sum, const AccumulatorSection &s) {
0331 return sum + s.count();
0332 });
0333 ACTS_DEBUG("size " << sStat.size());
0334 for (const auto &[div, stats] : sStat) {
0335 ACTS_DEBUG("Area in used by div: "
0336 << div << " area: " << stats.area << " avglines: "
0337 << (stats.nSections > 0 ? stats.nLines / stats.nSections : 0)
0338 << " n sections: " << stats.nSections
0339 << " thresholdCut: " << stats.discardedByThresholdCut
0340 << " crossingCut: " << stats.discardedByCrossingCut);
0341 }
0342 ACTS_DEBUG("Phi - qOverPt scan finds "
0343 << output.size() << " solutions, included measurements " << nl);
0344 }
0345
0346 void AdaptiveHoughTransformSeeder::processStackZCotTheta(
0347 std::vector<AccumulatorSection> &input,
0348 std::vector<AccumulatorSection> &output,
0349 const std::vector<PreprocessedMeasurement> &measurements) const {
0350 ExplorationOptions opt;
0351 opt.xMinBinSize = config().zMinBinSize;
0352 opt.yMinBinSize = config().cotThetaMinBinSize;
0353 opt.lineParamFunctor = m_zCotThetaLineParams;
0354 opt.decisionFunctor = [&cfg = m_cfg](
0355 const AccumulatorSection §ion,
0356 const std::vector<PreprocessedMeasurement> &) {
0357 using enum ExplorationOptions<PreprocessedMeasurement>::Decision;
0358
0359 if (section.count() < cfg.threshold) {
0360 return Discard;
0361 }
0362 if (section.count() >= cfg.threshold &&
0363 section.xSize() <= cfg.zMinBinSize &&
0364 section.ySize() <= cfg.cotThetaMinBinSize) {
0365 return Accept;
0366 }
0367 return Drill;
0368 };
0369
0370 exploreParametersSpace(input, measurements, opt, output);
0371 const unsigned nl =
0372 std::accumulate(output.begin(), output.end(), 0,
0373 [](unsigned sum, const AccumulatorSection &s) {
0374 return sum + s.count();
0375 });
0376 ACTS_DEBUG("Z - cotTheta scan finds " << output.size()
0377 << " included measurements " << nl);
0378 }
0379
0380 void AdaptiveHoughTransformSeeder::processStackZCotThetaSplit(
0381 std::vector<AccumulatorSection> &input,
0382 std::vector<AccumulatorSection> &output,
0383 const std::vector<PreprocessedMeasurement> &measurements) const {
0384 ExplorationOptions opt;
0385 opt.xMinBinSize = 101.0f * Acts::UnitConstants::mm;
0386 opt.yMinBinSize = 10.1f;
0387 opt.lineParamFunctor = m_zCotThetaLineParams;
0388 opt.decisionFunctor = [&cfg = m_cfg, &opt](
0389 const AccumulatorSection §ion,
0390 const std::vector<PreprocessedMeasurement> &) {
0391 using enum ExplorationOptions<PreprocessedMeasurement>::Decision;
0392 if (section.count() < cfg.threshold) {
0393 return Discard;
0394 }
0395 if (section.count() >= cfg.threshold &&
0396 section.xSize() <= opt.xMinBinSize &&
0397 section.ySize() <= opt.yMinBinSize) {
0398 return Accept;
0399 }
0400 return Drill;
0401 };
0402
0403 exploreParametersSpace(input, measurements, opt, output);
0404 const unsigned nl =
0405 std::accumulate(output.begin(), output.end(), 0,
0406 [](unsigned sum, const AccumulatorSection &s) {
0407 return sum + s.count();
0408 });
0409 ACTS_DEBUG("Z - cotTheta split scan finds "
0410 << output.size() << " included measurements " << nl);
0411 }
0412
0413 void AdaptiveHoughTransformSeeder::makeSeeds(
0414 SimSeedContainer &seeds, const std::vector<AccumulatorSection> &solutions,
0415 const std::vector<PreprocessedMeasurement> &measurements) const {
0416 std::size_t seedIndex = 0;
0417 for (const AccumulatorSection &s : solutions) {
0418 unsigned spIndex = 0;
0419 std::array<const SimSpacePoint *, 3> sp = {nullptr, nullptr, nullptr};
0420 std::vector<unsigned> sortedIndices = s.indices();
0421 std::ranges::sort(sortedIndices, [&measurements](unsigned i1, unsigned i2) {
0422 const auto &m1 = measurements[i1];
0423 const auto &m2 = measurements[i2];
0424 return m1.invr > m2.invr;
0425 });
0426
0427 for (unsigned sidx : sortedIndices) {
0428 const PreprocessedMeasurement &m = measurements[sidx];
0429 sp[spIndex] = m.link.get<const SimSpacePoint *>();
0430 if (spIndex == 0 || std::abs(sp[spIndex]->r() - sp[spIndex - 1]->r()) >
0431 5. * Acts::UnitConstants::mm) {
0432 spIndex++;
0433 }
0434 if (spIndex >= sp.size()) {
0435 break;
0436 }
0437 }
0438 if (sp[2] == nullptr) {
0439 ACTS_VERBOSE("this solution has less than 3 SP, ignoring");
0440 continue;
0441 }
0442
0443 auto cotThetaEstimate = static_cast<float>((sp[2]->z() - sp[0]->z()) /
0444 (sp[2]->r() - sp[0]->r()));
0445 auto cotThetaEstimate01 = static_cast<float>((sp[1]->z() - sp[0]->z()) /
0446 (sp[1]->r() - sp[0]->r()));
0447 auto cotThetaEstimate12 = static_cast<float>((sp[2]->z() - sp[1]->z()) /
0448 (sp[2]->r() - sp[1]->r()));
0449 if (std::abs(cotThetaEstimate01 - cotThetaEstimate12) > 0.1f) {
0450 ACTS_VERBOSE("Large difference in cotTheta " << cotThetaEstimate01 << " "
0451 << cotThetaEstimate12);
0452 continue;
0453 }
0454 seeds.emplace_back(*sp[0], *sp[1], *sp[2]);
0455 auto z = static_cast<float>(sp[1]->z() - sp[1]->r() * cotThetaEstimate);
0456 seeds.back().setVertexZ(z);
0457
0458 ACTS_VERBOSE(seedIndex << ": solution x: " << s.xBegin() << " " << s.xSize()
0459 << " y: " << s.yBegin() << " " << s.ySize()
0460 << " nlines: " << s.count() << " vertex z: " << z
0461 << " r0,1,2: " << sp[0]->r() << " " << sp[1]->r()
0462 << " " << sp[2]->r() << " cot,01,12 "
0463 << cotThetaEstimate << " " << cotThetaEstimate01
0464 << " " << cotThetaEstimate12);
0465
0466
0467
0468 seeds.back().setQuality(1.0);
0469 seedIndex++;
0470 }
0471 }
0472
0473 bool AdaptiveHoughTransformSeeder::passIntersectionsCheck(
0474 const AccumulatorSection §ion,
0475 const std::vector<PreprocessedMeasurement> &measurements,
0476 const LineParamFunctor &lineFunctor, unsigned threshold) const {
0477 unsigned inside = 0;
0478 for (std::size_t idx1 = 0; idx1 < section.count(); ++idx1) {
0479 const auto &m1 = measurements[section.indices()[idx1]];
0480 std::function<float(float)> line1 =
0481 std::bind_front(lineFunctor, std::cref(m1));
0482 for (std::size_t idx2 = idx1 + 1; idx2 < section.count(); ++idx2) {
0483 const auto &m2 = measurements[section.indices()[idx2]];
0484 std::function<float(float)> line2 =
0485 std::bind_front(lineFunctor, std::cref(m2));
0486 if (section.isCrossingInside(line1, line2)) {
0487 inside++;
0488 if (inside >= threshold) {
0489 return true;
0490 }
0491 }
0492 }
0493 }
0494 ACTS_VERBOSE("Number of crossings inside of section " << inside);
0495 return inside >= threshold;
0496 }
0497 void AdaptiveHoughTransformSeeder::deduplicate(
0498 std::vector<AccumulatorSection> &input) const {
0499 std::vector<const AccumulatorSection *> op;
0500 op.reserve(input.size());
0501 std::transform(input.begin(), input.end(), std::back_inserter(op),
0502 [](const AccumulatorSection &s) { return &s; });
0503
0504 auto binaryPredSort = [](const AccumulatorSection *a,
0505 const AccumulatorSection *b) {
0506 return a->indices() < b->indices();
0507 };
0508 auto binaryPredUnique = [](const AccumulatorSection *a,
0509 const AccumulatorSection *b) {
0510 return a->indices() == b->indices();
0511 };
0512
0513 std::ranges::sort(op, binaryPredSort);
0514 auto [rbegin, rend] = std::ranges::unique(op, binaryPredUnique);
0515 std::vector<AccumulatorSection> temp;
0516 for (auto sPtr = std::begin(op); sPtr != rbegin; ++sPtr) {
0517 temp.push_back(**sPtr);
0518 }
0519 input = std::move(temp);
0520 }
0521
0522 }