File indexing completed on 2025-12-16 09:23:34
0001
0002
0003
0004
0005
0006
0007
0008
0009 #include "ActsExamples/TrackFinding/SpacePointMaker.hpp"
0010
0011 #include "Acts/Definitions/Algebra.hpp"
0012 #include "Acts/EventData/SourceLink.hpp"
0013 #include "Acts/EventData/SubspaceHelpers.hpp"
0014 #include "Acts/Geometry/GeometryContext.hpp"
0015 #include "Acts/Geometry/GeometryIdentifier.hpp"
0016 #include "Acts/SpacePointFormation2/PixelSpacePointBuilder.hpp"
0017 #include "Acts/SpacePointFormation2/StripSpacePointBuilder.hpp"
0018 #include "Acts/Surfaces/PlanarBounds.hpp"
0019 #include "Acts/Surfaces/RectangleBounds.hpp"
0020 #include "Acts/Surfaces/Surface.hpp"
0021 #include "Acts/Utilities/Logger.hpp"
0022 #include "ActsExamples/EventData/GeometryContainers.hpp"
0023 #include "ActsExamples/EventData/IndexSourceLink.hpp"
0024 #include "ActsExamples/EventData/Measurement.hpp"
0025 #include "ActsExamples/EventData/SimSpacePoint.hpp"
0026 #include "ActsExamples/Framework/AlgorithmContext.hpp"
0027 #include "ActsExamples/Framework/ProcessCode.hpp"
0028 #include "ActsExamples/Utilities/GroupBy.hpp"
0029
0030 #include <algorithm>
0031 #include <functional>
0032 #include <iterator>
0033 #include <ostream>
0034 #include <stdexcept>
0035 #include <utility>
0036
0037 namespace ActsExamples {
0038
0039 namespace {
0040
0041 SimSpacePoint createPixelSpacePoint(
0042 const Acts::GeometryContext& gctx, const Acts::Surface& surface,
0043 const ConstVariableBoundMeasurementProxy& measurement,
0044 const IndexSourceLink& sourceLink) {
0045 const Acts::Vector2 local = measurement.fullParameters().head<2>();
0046 const Acts::SquareMatrix2 localCov =
0047 measurement.fullCovariance().topLeftCorner<2, 2>();
0048 std::optional<double> t = std::nullopt;
0049 std::optional<double> varT = std::nullopt;
0050 if (measurement.contains(Acts::eBoundTime)) {
0051 t = measurement.fullParameters()[Acts::eBoundTime];
0052 varT = measurement.fullCovariance()(Acts::eBoundTime, Acts::eBoundTime);
0053 }
0054
0055
0056 const Acts::Vector3 global =
0057 surface.localToGlobal(gctx, local, Acts::Vector3::Zero());
0058 const Acts::Vector2 varZR = Acts::PixelSpacePointBuilder::computeVarianceZR(
0059 gctx, surface, global, localCov);
0060
0061 SimSpacePoint result(global, t, varZR[1], varZR[0], varT,
0062 {Acts::SourceLink(sourceLink)});
0063 return result;
0064 }
0065
0066 Acts::StripSpacePointBuilder::StripEnds getStripEnds(
0067 const Acts::GeometryContext& gctx, const Acts::Surface& surface,
0068 const ConstVariableBoundMeasurementProxy& measurement) {
0069 const auto* bounds =
0070 dynamic_cast<const Acts::PlanarBounds*>(&surface.bounds());
0071 if (bounds == nullptr) {
0072 throw std::invalid_argument(
0073 "SpacePointMaker: Encountered non-planar surface");
0074 }
0075 const Acts::RectangleBounds& boundingBox = bounds->boundingBox();
0076
0077 const Acts::VariableBoundSubspaceHelper subspace =
0078 measurement.subspaceHelper();
0079 if (subspace.size() != 1) {
0080 throw std::invalid_argument(
0081 "SpacePointMaker: Encountered non-strip measurement");
0082 }
0083
0084 const double negEnd = boundingBox.get(Acts::RectangleBounds::eMinY);
0085 const double posEnd = boundingBox.get(Acts::RectangleBounds::eMaxY);
0086 const double loc0 = measurement.fullParameters()[Acts::eBoundLoc0];
0087
0088 const Acts::Vector2 localTop = {loc0, posEnd};
0089 const Acts::Vector2 localBottom = {loc0, negEnd};
0090
0091 const Acts::Vector3 globalTop =
0092 surface.localToGlobal(gctx, localTop, Acts::Vector3::Zero());
0093 const Acts::Vector3 globalBottom =
0094 surface.localToGlobal(gctx, localBottom, Acts::Vector3::Zero());
0095
0096 return Acts::StripSpacePointBuilder::StripEnds{globalTop, globalBottom};
0097 }
0098
0099 Acts::Result<SimSpacePoint> createStripSpacePoint(
0100 const Acts::GeometryContext& gctx, const Acts::Surface& surface1,
0101 const Acts::Surface& surface2,
0102 const ConstVariableBoundMeasurementProxy& measurement1,
0103 const ConstVariableBoundMeasurementProxy& measurement2,
0104 const IndexSourceLink& sourceLink1, const IndexSourceLink& sourceLink2) {
0105 const Acts::StripSpacePointBuilder::StripEnds stripEnds1 =
0106 getStripEnds(gctx, surface1, measurement1);
0107 const Acts::StripSpacePointBuilder::StripEnds stripEnds2 =
0108 getStripEnds(gctx, surface2, measurement2);
0109
0110 const Acts::StripSpacePointBuilder::ConstrainedOptions options{};
0111 const Acts::Result<Acts::Vector3> spacePoint =
0112 Acts::StripSpacePointBuilder::computeConstrainedSpacePoint(
0113 stripEnds1, stripEnds2, options);
0114 if (!spacePoint.ok()) {
0115 return spacePoint.error();
0116 }
0117
0118 const double var1 =
0119 measurement1.fullCovariance()(Acts::eBoundLoc0, Acts::eBoundLoc0);
0120 const double var2 =
0121 measurement2.fullCovariance()(Acts::eBoundLoc0, Acts::eBoundLoc0);
0122
0123 const Acts::Vector3 btmToTop1 = stripEnds1.top - stripEnds1.bottom;
0124 const Acts::Vector3 btmToTop2 = stripEnds2.top - stripEnds2.bottom;
0125 const double theta = std::acos(btmToTop1.dot(btmToTop2) /
0126 (btmToTop1.norm() * btmToTop2.norm()));
0127
0128 const Acts::Vector2 varZR = Acts::StripSpacePointBuilder::computeVarianceZR(
0129 gctx, surface1, *spacePoint, var1, var2, theta);
0130
0131 const double topHalfStripLength =
0132 0.5 * (stripEnds1.top - stripEnds1.bottom).norm();
0133 const double bottomHalfStripLength =
0134 0.5 * (stripEnds2.top - stripEnds2.bottom).norm();
0135 const Acts::Vector3 topStripDirection =
0136 (stripEnds1.top - stripEnds1.bottom).normalized();
0137 const Acts::Vector3 bottomStripDirection =
0138 (stripEnds2.top - stripEnds2.bottom).normalized();
0139 const Acts::Vector3 stripCenterDistance =
0140 (0.5 * (stripEnds1.top + stripEnds1.bottom) -
0141 0.5 * (stripEnds2.top + stripEnds2.bottom));
0142 const Acts::Vector3 topStripCenterPosition =
0143 0.5 * (stripEnds1.top + stripEnds1.bottom);
0144
0145 SimSpacePoint result(
0146 *spacePoint, std::nullopt, varZR[1], varZR[0], std::nullopt,
0147 {Acts::SourceLink(sourceLink1), Acts::SourceLink(sourceLink2)},
0148 topHalfStripLength, bottomHalfStripLength, topStripDirection,
0149 bottomStripDirection, stripCenterDistance, topStripCenterPosition);
0150 return result;
0151 }
0152
0153 }
0154
0155 SpacePointMaker::SpacePointMaker(Config cfg, Acts::Logging::Level lvl)
0156 : IAlgorithm("SpacePointMaker", lvl), m_cfg(std::move(cfg)) {
0157 if (m_cfg.inputMeasurements.empty()) {
0158 throw std::invalid_argument("Missing measurement input collection");
0159 }
0160 if (m_cfg.outputSpacePoints.empty()) {
0161 throw std::invalid_argument("Missing space point output collection");
0162 }
0163 if (!m_cfg.trackingGeometry) {
0164 throw std::invalid_argument("Missing tracking geometry");
0165 }
0166 if (m_cfg.geometrySelection.empty() && m_cfg.stripGeometrySelection.empty()) {
0167 throw std::invalid_argument("Missing space point maker geometry selection");
0168 }
0169
0170 m_inputMeasurements.initialize(m_cfg.inputMeasurements);
0171 m_outputSpacePoints.initialize(m_cfg.outputSpacePoints);
0172
0173
0174 for (const auto& geoSelection :
0175 {m_cfg.geometrySelection, m_cfg.stripGeometrySelection}) {
0176 for (const auto& geoId : geoSelection) {
0177 if ((geoId.approach() != 0u) || (geoId.boundary() != 0u) ||
0178 (geoId.sensitive() != 0u)) {
0179 throw std::invalid_argument(
0180 "Invalid geometry selection: only volume and layer are allowed to "
0181 "be set");
0182 }
0183 }
0184 }
0185
0186
0187
0188
0189
0190 const auto isDuplicate = [](Acts::GeometryIdentifier ref,
0191 Acts::GeometryIdentifier cmp) {
0192
0193
0194 if (ref.volume() == 0) {
0195 return true;
0196 }
0197
0198 if (ref.volume() != cmp.volume()) {
0199 return false;
0200 }
0201
0202 return (ref.layer() == cmp.layer());
0203 };
0204
0205 std::ranges::sort(m_cfg.geometrySelection,
0206 std::less<Acts::GeometryIdentifier>{});
0207 const auto geoSelLastUnique =
0208 std::ranges::unique(m_cfg.geometrySelection, isDuplicate);
0209 if (geoSelLastUnique.begin() != geoSelLastUnique.end()) {
0210 ACTS_WARNING("Removed " << std::distance(geoSelLastUnique.begin(),
0211 geoSelLastUnique.end())
0212 << " geometry selection duplicates");
0213 m_cfg.geometrySelection.erase(geoSelLastUnique.begin(),
0214 geoSelLastUnique.end());
0215 }
0216
0217 if (!m_cfg.stripGeometrySelection.empty()) {
0218 initializeStripPartners();
0219 }
0220 }
0221
0222 ProcessCode SpacePointMaker::initialize() {
0223 ACTS_INFO("Space point geometry selection:");
0224 for (const auto& geoId : m_cfg.geometrySelection) {
0225 ACTS_INFO(" " << geoId);
0226 }
0227
0228 return ProcessCode::SUCCESS;
0229 }
0230
0231 void SpacePointMaker::initializeStripPartners() {
0232 ACTS_INFO("Strip space point geometry selection:");
0233 for (const auto& geoId : m_cfg.stripGeometrySelection) {
0234 ACTS_INFO(" " << geoId);
0235 }
0236
0237
0238
0239 Acts::GeometryContext gctx;
0240
0241
0242
0243
0244 std::vector<const Acts::Surface*> allSensitivesVector;
0245 m_cfg.trackingGeometry->visitSurfaces(
0246 [&](const auto surface) { allSensitivesVector.push_back(surface); },
0247 true);
0248 std::ranges::sort(allSensitivesVector, detail::CompareGeometryId{},
0249 detail::GeometryIdGetter{});
0250 GeometryIdMultiset<const Acts::Surface*> allSensitives(
0251 allSensitivesVector.begin(), allSensitivesVector.end());
0252
0253 for (auto selector : m_cfg.stripGeometrySelection) {
0254
0255 auto rangeLayer =
0256 selectLowestNonZeroGeometryObject(allSensitives, selector);
0257
0258
0259 auto range = rangeLayer | std::views::filter([&](auto srf) {
0260 return srf->geometryId().extra() != 0
0261 ? srf->geometryId().extra() == selector.extra()
0262 : true;
0263 });
0264
0265 const auto sizeBefore = m_stripPartner.size();
0266 const std::size_t nSurfaces = std::distance(range.begin(), range.end());
0267
0268 if (nSurfaces < 2) {
0269 ACTS_WARNING("Only " << nSurfaces << " surfaces for selector " << selector
0270 << ", skip");
0271 continue;
0272 }
0273 ACTS_DEBUG("Found " << nSurfaces << " surfaces for selector " << selector);
0274
0275
0276 for (auto mod1 : range) {
0277 if (m_stripPartner.contains(mod1->geometryId())) {
0278 continue;
0279 }
0280
0281 const Acts::Surface* partner = nullptr;
0282 double minDist = std::numeric_limits<double>::max();
0283
0284 for (auto mod2 : range) {
0285 if (mod1 == mod2) {
0286 continue;
0287 }
0288 auto c1 = mod1->center(gctx);
0289 auto c2 = mod2->center(gctx);
0290 if (minDist > (c1 - c2).norm()) {
0291 minDist = (c1 - c2).norm();
0292 partner = mod2;
0293 }
0294 }
0295
0296 ACTS_VERBOSE("Found stereo pair: " << mod1->geometryId() << " <-> "
0297 << partner->geometryId());
0298 ACTS_VERBOSE("- " << mod1->center(gctx).transpose() << " <-> "
0299 << partner->center(gctx).transpose());
0300 const auto [it1, success1] =
0301 m_stripPartner.insert({mod1->geometryId(), partner->geometryId()});
0302 const auto [it2, success2] =
0303 m_stripPartner.insert({partner->geometryId(), mod1->geometryId()});
0304 if (!success1 || !success2) {
0305 throw std::runtime_error("error inserting in map");
0306 }
0307 }
0308
0309 const std::size_t sizeAfter = m_stripPartner.size();
0310 const std::size_t missing = nSurfaces - (sizeAfter - sizeBefore);
0311 if (missing > 0) {
0312 ACTS_WARNING("Did not find a stereo partner for " << missing
0313 << " surfaces");
0314 }
0315 }
0316 }
0317
0318 ProcessCode SpacePointMaker::execute(const AlgorithmContext& ctx) const {
0319 const IndexSourceLink::SurfaceAccessor surfaceAccessor(
0320 *m_cfg.trackingGeometry);
0321
0322 const auto& measurements = m_inputMeasurements(ctx);
0323
0324 SimSpacePointContainer spacePoints;
0325
0326 for (Acts::GeometryIdentifier geoId : m_cfg.geometrySelection) {
0327
0328 auto range =
0329 selectLowestNonZeroGeometryObject(measurements.orderedIndices(), geoId);
0330
0331
0332 const auto groupedByModule = makeGroupBy(range, detail::GeometryIdGetter());
0333
0334 for (const auto& [moduleGeoId, moduleSourceLinks] : groupedByModule) {
0335 for (const auto& sourceLink : moduleSourceLinks) {
0336 const Acts::Surface& surface =
0337 *surfaceAccessor(Acts::SourceLink(sourceLink));
0338 const ConstVariableBoundMeasurementProxy measurement =
0339 measurements.getMeasurement(sourceLink.index());
0340
0341 if (!measurement.contains(Acts::eBoundLoc0) ||
0342 !measurement.contains(Acts::eBoundLoc1)) {
0343 ACTS_WARNING("Encountered non-pixel measurement");
0344 continue;
0345 }
0346
0347 spacePoints.emplace_back(createPixelSpacePoint(
0348 ctx.geoContext, surface, measurement, sourceLink));
0349 }
0350 }
0351 }
0352
0353 const std::size_t nPixelSpacePoints = spacePoints.size();
0354 ACTS_DEBUG("Created " << nPixelSpacePoints << " pixel space points");
0355
0356
0357 ACTS_DEBUG("Build strip spacepoints");
0358
0359 Acts::StripSpacePointBuilder::ClusterPairingOptions pairingOptions;
0360
0361
0362 std::vector<std::pair<IndexSourceLink, IndexSourceLink>> stripSourceLinkPairs;
0363 for (auto sel : m_cfg.stripGeometrySelection) {
0364 const std::size_t nSpacepointsBefore = spacePoints.size();
0365 stripSourceLinkPairs.clear();
0366 ACTS_VERBOSE("Process strip selection " << sel);
0367
0368
0369 const auto layerRange =
0370 selectLowestNonZeroGeometryObject(measurements.orderedIndices(), sel);
0371
0372
0373 auto range = layerRange | std::views::filter([&](auto sl) {
0374 return sl.geometryId().extra() != 0
0375 ? sl.geometryId().extra() == sel.extra()
0376 : true;
0377 });
0378
0379
0380
0381 const auto groupedByModule = makeGroupBy(range, detail::GeometryIdGetter());
0382
0383 using SourceLinkRange = decltype((*groupedByModule.begin()).second);
0384
0385 std::unordered_map<Acts::GeometryIdentifier, std::optional<SourceLinkRange>>
0386 mapByModule;
0387 for (const auto& [moduleGeoId, moduleSourceLinks] : groupedByModule) {
0388 mapByModule[moduleGeoId] = moduleSourceLinks;
0389 }
0390
0391
0392 std::set<Acts::GeometryIdentifier> done;
0393 for (const auto& [mod1, mod1SourceLinks] : mapByModule) {
0394 ACTS_VERBOSE("Process " << mod1 << " with " << mod1SourceLinks->size()
0395 << " source links");
0396 const Acts::GeometryIdentifier mod2 = m_stripPartner.at(mod1);
0397
0398
0399 if (done.contains(mod2)) {
0400 ACTS_VERBOSE("- Already processed " << mod2 << ", continue");
0401 continue;
0402 }
0403 if (!mapByModule.contains(mod2)) {
0404 ACTS_VERBOSE("- No source links on stereo partner " << mod2);
0405 continue;
0406 }
0407
0408 const auto& mod2SourceLinks = mapByModule.at(mod2);
0409
0410 ACTS_VERBOSE("- Partner " << mod2 << " with " << mod2SourceLinks->size()
0411 << " source links");
0412
0413 for (const IndexSourceLink& sourceLink1 : *mod1SourceLinks) {
0414 const Acts::Surface& surface1 =
0415 *surfaceAccessor(Acts::SourceLink(sourceLink1));
0416 const ConstVariableBoundMeasurementProxy measurement1 =
0417 measurements.getMeasurement(sourceLink1.index());
0418
0419 if (!measurement1.contains(Acts::eBoundLoc0)) {
0420 ACTS_WARNING("Encountered non-strip measurement");
0421 continue;
0422 }
0423
0424 Acts::Vector2 local1 = measurement1.fullParameters().head<2>();
0425 local1[1] = surface1.center(ctx.geoContext)[1];
0426 const Acts::Vector3 global1 = surface1.localToGlobal(
0427 ctx.geoContext, local1, Acts::Vector3::Zero());
0428
0429 std::optional<double> minDistance;
0430 std::optional<IndexSourceLink> bestSourceLink2;
0431
0432 for (const IndexSourceLink& sourceLink2 : *mod2SourceLinks) {
0433 const Acts::Surface& surface2 =
0434 *surfaceAccessor(Acts::SourceLink(sourceLink2));
0435 const ConstVariableBoundMeasurementProxy measurement2 =
0436 measurements.getMeasurement(sourceLink2.index());
0437
0438 if (!measurement2.contains(Acts::eBoundLoc0)) {
0439 ACTS_WARNING("Encountered non-strip measurement");
0440 continue;
0441 }
0442
0443 Acts::Vector2 local2 = measurement2.fullParameters().head<2>();
0444 local2[1] = surface2.center(ctx.geoContext)[1];
0445 const Acts::Vector3 global2 = surface1.localToGlobal(
0446 ctx.geoContext, local2, Acts::Vector3::Zero());
0447
0448 const Acts::Result<double> distance =
0449 Acts::StripSpacePointBuilder::computeClusterPairDistance(
0450 global1, global2, pairingOptions);
0451 if (distance.ok() && (!minDistance.has_value() ||
0452 distance.value() < minDistance.value())) {
0453 minDistance = *distance;
0454 bestSourceLink2 = sourceLink2;
0455 }
0456 }
0457
0458 if (bestSourceLink2) {
0459 stripSourceLinkPairs.emplace_back(sourceLink1, *bestSourceLink2);
0460 ACTS_VERBOSE("Found source link pair: " << sourceLink1.index()
0461 << " <-> "
0462 << bestSourceLink2->index());
0463 }
0464 }
0465
0466 done.insert(mod1);
0467 done.insert(mod2);
0468 }
0469
0470
0471 for (const auto& [sourceLink1, sourceLink2] : stripSourceLinkPairs) {
0472
0473
0474
0475
0476
0477
0478
0479
0480
0481
0482
0483 const Acts::Surface& surface1 =
0484 *surfaceAccessor(Acts::SourceLink(sourceLink1));
0485 const Acts::Surface& surface2 =
0486 *surfaceAccessor(Acts::SourceLink(sourceLink2));
0487
0488 const ConstVariableBoundMeasurementProxy measurement1 =
0489 measurements.getMeasurement(sourceLink1.index());
0490 const ConstVariableBoundMeasurementProxy measurement2 =
0491 measurements.getMeasurement(sourceLink2.index());
0492
0493 const Acts::Result<SimSpacePoint> spacePoint = createStripSpacePoint(
0494 ctx.geoContext, surface1, surface2, measurement1, measurement2,
0495 sourceLink1, sourceLink2);
0496 if (spacePoint.ok()) {
0497 spacePoints.push_back(*spacePoint);
0498 }
0499 }
0500
0501 ACTS_DEBUG("Built " << spacePoints.size() - nSpacepointsBefore
0502 << " spacepoints for selector " << sel);
0503 }
0504
0505 spacePoints.shrink_to_fit();
0506
0507 ACTS_DEBUG("Created " << spacePoints.size() - nPixelSpacePoints
0508 << " strip space points");
0509 ACTS_DEBUG("Created " << spacePoints.size() << " space points");
0510 m_outputSpacePoints(ctx, std::move(spacePoints));
0511
0512 return ProcessCode::SUCCESS;
0513 }
0514
0515 }