Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-12-16 09:23:34

0001 // This file is part of the ACTS project.
0002 //
0003 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0004 //
0005 // This Source Code Form is subject to the terms of the Mozilla Public
0006 // License, v. 2.0. If a copy of the MPL was not distributed with this
0007 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
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   // we rely on the direction not being used by the surface
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 }  // namespace
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   // ensure geometry selection contains only valid inputs
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   // remove geometry selection duplicates
0186   //
0187   // the geometry selections must be mutually exclusive, i.e. if we have a
0188   // selection that contains both a volume and a layer within that same volume,
0189   // we would create the space points for the layer twice.
0190   const auto isDuplicate = [](Acts::GeometryIdentifier ref,
0191                               Acts::GeometryIdentifier cmp) {
0192     // code assumes ref < cmp and that only volume and layer can be non-zero
0193     // root node always contains everything
0194     if (ref.volume() == 0) {
0195       return true;
0196     }
0197     // unequal volumes always means separate hierarchies
0198     if (ref.volume() != cmp.volume()) {
0199       return false;
0200     }
0201     // within the same volume hierarchy only consider layers
0202     return (ref.layer() == cmp.layer());
0203   };
0204   // sort geometry selection so the unique filtering works
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   // We need to use a default geometry context here to access the center
0238   // coordinates of modules.
0239   Acts::GeometryContext gctx;
0240 
0241   // Build strip partner map, i.e., which modules are stereo partners
0242   // As a heuristic we assume that the stereo partners are the modules
0243   // which have the shortest mutual distance
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     // Apply volume/layer range
0255     auto rangeLayer =
0256         selectLowestNonZeroGeometryObject(allSensitives, selector);
0257 
0258     // Apply selector on extra if extra != 0
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     // Very dumb all-to-all search
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     // select volume/layer depending on what is set in the geometry id
0328     auto range =
0329         selectLowestNonZeroGeometryObject(measurements.orderedIndices(), geoId);
0330     // groupByModule only works with geometry containers, not with an
0331     // arbitrary range. do the equivalent grouping manually
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   // Build strip spacepoints
0357   ACTS_DEBUG("Build strip spacepoints");
0358 
0359   Acts::StripSpacePointBuilder::ClusterPairingOptions pairingOptions;
0360 
0361   // Loop over the geometry selections
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     // select volume/layer depending on what is set in the geometry id
0369     const auto layerRange =
0370         selectLowestNonZeroGeometryObject(measurements.orderedIndices(), sel);
0371     // Apply filter for extra-id, since this cannot be done with
0372     // selectLowestNonZeroGeometryObject
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     // groupByModule only works with geometry containers, not with an
0380     // arbitrary range. do the equivalent grouping manually
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     // Collect the IDs of processed surfaces to avoid reprocessing surface pairs
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       // Avoid producing spacepoints twice
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     // Loop over the collected source link pairs
0471     for (const auto& [sourceLink1, sourceLink2] : stripSourceLinkPairs) {
0472       // In the following, we collect the 3D coordinates of the strip endpoints
0473       // We derive these information directly from the geometry for now, even
0474       // though it might be more robust to provide them in a config file from
0475       // outside in the long run
0476       //
0477       // The basic idea here is to express the surface bounds as
0478       // RectangleBounds, and then use the min/max x/y to compute the local
0479       // position of the strip end. This of course is not well defined for
0480       // non-rectangle surfaces, but we anyways have no realistic digitization
0481       // for those cases in ODD or the generic detector.
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 }  // namespace ActsExamples