From 4b27593d07faf6ca1303c2c737fcabfebd8c9748 Mon Sep 17 00:00:00 2001 From: "Alexander J. Pfleger" <70842573+AJPfleger@users.noreply.github.com> Date: Thu, 5 Dec 2024 18:02:41 +0100 Subject: [PATCH 1/5] fix: remove narrowing conversion from `std::size_t` to `int` (#3932) I went through the cases, where we did `cast` from `std::size_t` and implemented solutions to avoid this. closes: - https://github.com/acts-project/acts/issues/3913 ## Summary by CodeRabbit - **Bug Fixes** - Improved type safety in various algorithms by changing loop index types from `int` to `std::size_t`. - Enhanced clarity in boundary condition checks within algorithms. - **Documentation** - Corrected comments for clarity in the `DigitizationAlgorithm` class. - **Chores** - Streamlined conditional logic in the `IndexedGridFiller` and `RefittingAlgorithm` classes for better readability and maintenance. --- .../src/Detector/detail/IndexedGridFiller.cpp | 20 ++++++------------- .../Digitization/DigitizationAlgorithm.hpp | 4 ++-- .../TrackFitting/src/RefittingAlgorithm.cpp | 3 ++- .../src/TrackFittingAlgorithm.cpp | 3 ++- .../Digitization/UncorrelatedHitSmearer.hpp | 2 +- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/Core/src/Detector/detail/IndexedGridFiller.cpp b/Core/src/Detector/detail/IndexedGridFiller.cpp index 6e2f8cfd114..88e6dd608b0 100644 --- a/Core/src/Detector/detail/IndexedGridFiller.cpp +++ b/Core/src/Detector/detail/IndexedGridFiller.cpp @@ -47,14 +47,10 @@ std::vector Acts::Experimental::detail::binSequence( rBins.reserve(bmax - bmin + 1u + 2 * expand); // handle bmin:/max expand it down (for bound, don't fill underflow) if (type == Acts::AxisBoundaryType::Bound) { - bmin = (static_cast(bmin) - static_cast(expand) > 0) - ? bmin - expand - : 1u; + bmin = bmin > expand ? bmin - expand : 1u; bmax = (bmax + expand <= nBins) ? bmax + expand : nBins; } else if (type == Acts::AxisBoundaryType::Open) { - bmin = (static_cast(bmin) - static_cast(expand) >= 0) - ? bmin - expand - : 0u; + bmin = bmin >= expand ? bmin - expand : 0u; bmax = (bmax + expand <= nBins + 1u) ? bmax + expand : nBins + 1u; } fill_linear(bmin, bmax); @@ -62,14 +58,11 @@ std::vector Acts::Experimental::detail::binSequence( // Close case std::size_t span = bmax - bmin + 1u + 2 * expand; // Safe with respect to the closure point, treat as bound - if (2 * span < nBins && (bmax + expand <= nBins) && - (static_cast(bmin) - static_cast(expand) > 0)) { + if (2 * span < nBins && (bmax + expand <= nBins) && (bmin > expand)) { return binSequence({bmin, bmax}, expand, nBins, Acts::AxisBoundaryType::Bound); } else if (2 * span < nBins) { - bmin = static_cast(bmin) - static_cast(expand) > 0 - ? bmin - expand - : 1u; + bmin = bmin > expand ? bmin - expand : 1u; bmax = bmax + expand <= nBins ? bmax + expand : nBins; fill_linear(bmin, bmax); // deal with expansions over the phi boundary @@ -77,9 +70,8 @@ std::vector Acts::Experimental::detail::binSequence( std::size_t overstep = (bmax + expand - nBins); fill_linear(1u, overstep); } - if (static_cast(bmin) - static_cast(expand) < 1) { - std::size_t understep = - abs(static_cast(bmin) - static_cast(expand)); + if (bmin <= expand) { + std::size_t understep = expand - bmin; fill_linear(nBins - understep, nBins); } std::ranges::sort(rBins); diff --git a/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp b/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp index 331408faa9a..8e9ffc6e58b 100644 --- a/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp +++ b/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp @@ -122,7 +122,7 @@ class DigitizationAlgorithm final : public IAlgorithm { Config m_cfg; /// Digitizers within geometry hierarchy Acts::GeometryHierarchyMap m_digitizers; - /// Geometric digtizer + /// Geometric digitizer ActsFatras::Channelizer m_channelizer; using CellsMap = @@ -153,7 +153,7 @@ class DigitizationAlgorithm final : public IAlgorithm { // Copy the geometric configuration impl.geometric = cfg.geometricDigiConfig; // Prepare the smearing configuration - for (int i = 0; i < static_cast(kSmearDIM); ++i) { + for (std::size_t i = 0; i < kSmearDIM; ++i) { impl.smearing.indices[i] = cfg.smearingDigiConfig.at(i).index; impl.smearing.smearFunctions[i] = cfg.smearingDigiConfig.at(i).smearFunction; diff --git a/Examples/Algorithms/TrackFitting/src/RefittingAlgorithm.cpp b/Examples/Algorithms/TrackFitting/src/RefittingAlgorithm.cpp index 03aa74fa58f..79eb367589b 100644 --- a/Examples/Algorithms/TrackFitting/src/RefittingAlgorithm.cpp +++ b/Examples/Algorithms/TrackFitting/src/RefittingAlgorithm.cpp @@ -64,7 +64,8 @@ ActsExamples::ProcessCode ActsExamples::RefittingAlgorithm::execute( auto itrack = 0ul; for (const auto& track : inputTracks) { // Check if you are not in picking mode - if (m_cfg.pickTrack > -1 && m_cfg.pickTrack != static_cast(itrack++)) { + if (m_cfg.pickTrack > -1 && + static_cast(m_cfg.pickTrack) != itrack++) { continue; } diff --git a/Examples/Algorithms/TrackFitting/src/TrackFittingAlgorithm.cpp b/Examples/Algorithms/TrackFitting/src/TrackFittingAlgorithm.cpp index 50360e3d3b4..d79a864bc73 100644 --- a/Examples/Algorithms/TrackFitting/src/TrackFittingAlgorithm.cpp +++ b/Examples/Algorithms/TrackFitting/src/TrackFittingAlgorithm.cpp @@ -103,7 +103,8 @@ ActsExamples::ProcessCode ActsExamples::TrackFittingAlgorithm::execute( std::vector trackSourceLinks; for (std::size_t itrack = 0; itrack < protoTracks.size(); ++itrack) { // Check if you are not in picking mode - if (m_cfg.pickTrack > -1 && m_cfg.pickTrack != static_cast(itrack)) { + if (m_cfg.pickTrack > -1 && + static_cast(m_cfg.pickTrack) != itrack) { continue; } diff --git a/Fatras/include/ActsFatras/Digitization/UncorrelatedHitSmearer.hpp b/Fatras/include/ActsFatras/Digitization/UncorrelatedHitSmearer.hpp index caf5efa8c61..a4825b2a99b 100644 --- a/Fatras/include/ActsFatras/Digitization/UncorrelatedHitSmearer.hpp +++ b/Fatras/include/ActsFatras/Digitization/UncorrelatedHitSmearer.hpp @@ -86,7 +86,7 @@ struct BoundParametersSmearer { ParametersVector par = ParametersVector::Zero(); CovarianceMatrix cov = CovarianceMatrix::Zero(); - for (int i = 0; i < static_cast(kSize); ++i) { + for (std::size_t i = 0; i < kSize; ++i) { auto res = smearFunctions[i](boundParams[indices[i]], rng); if (!res.ok()) { return Result::failure(res.error()); From 0d5586ea1cef4ce3e6843c6a7475eb7346d4720f Mon Sep 17 00:00:00 2001 From: "Alexander J. Pfleger" <70842573+AJPfleger@users.noreply.github.com> Date: Thu, 5 Dec 2024 21:25:26 +0100 Subject: [PATCH 2/5] refactor: brace-enclosed initializers for pair/tuple returns (#3931) ## Summary by CodeRabbit ## Release Notes - **New Features** - Enhanced readability in multiple methods by adopting brace initialization for return statements, simplifying the syntax without changing functionality. - **Bug Fixes** - Improved error handling in various functions to ensure robustness against invalid inputs. - **Documentation** - Updated comments for clarity and corrected minor typographical errors in method descriptions. - **Tests** - Simplified return statements in test cases, ensuring consistency in coding style while maintaining existing tests and their assertions. --- .../MultiComponentTrackParameters.hpp | 4 +-- .../Acts/Seeding/BinnedGroupIterator.ipp | 2 +- Core/include/Acts/Seeding/SeedFinder.ipp | 7 +++-- .../detail/KalmanGlobalCovariance.hpp | 2 +- Core/include/Acts/Utilities/GridBinFinder.ipp | 4 +-- Core/include/Acts/Utilities/TrackHelpers.hpp | 14 +++++----- .../Propagator/detail/CovarianceEngine.cpp | 3 +-- .../detail/SympyCovarianceEngine.cpp | 3 +-- Core/src/Surfaces/detail/AlignmentHelper.cpp | 4 +-- .../Surfaces/detail/AnnulusBoundsHelper.cpp | 2 +- Core/src/Utilities/SpacePointUtility.cpp | 2 +- .../Vertexing/AdaptiveGridTrackDensity.cpp | 2 +- Core/src/Vertexing/ImpactPointEstimator.cpp | 4 +-- Core/src/Vertexing/Vertex.cpp | 2 +- .../src/AlignedDetector.cpp | 3 +-- .../DD4hepDetector/src/DD4hepDetector.cpp | 3 +-- .../GenericDetector/src/GenericDetector.cpp | 3 +-- .../TGeoDetector/src/TGeoDetector.cpp | 3 +-- .../src/TelescopeDetector.cpp | 3 +-- .../src/EventData/ScalingCalibrator.cpp | 26 +++++++++---------- Examples/Framework/src/Utilities/Paths.cpp | 4 +-- .../Io/Root/RootMaterialDecorator.hpp | 2 +- ...RootNuclearInteractionParametersWriter.cpp | 12 ++++----- .../Io/Root/src/RootTrackStatesWriter.cpp | 6 ++--- .../NuclearInteractionParametrisation.cpp | 10 +++---- Examples/Scripts/compareRootFiles.hpp | 3 +-- .../ElectroMagnetic/PhotonConversion.hpp | 14 +++++----- .../NuclearInteraction/NuclearInteraction.hpp | 20 +++++++------- .../NuclearInteraction/NuclearInteraction.cpp | 2 +- Plugins/DD4hep/src/DD4hepBlueprintFactory.cpp | 5 ++-- Plugins/Geant4/src/Geant4Converters.cpp | 14 +++++----- .../GeoModel/src/GeoModelBlueprintCreater.cpp | 10 +++---- .../src/detail/GeoModelExtentHelper.cpp | 4 +-- .../Core/Geometry/LayerCreatorTests.cpp | 2 +- .../Geometry/SurfaceArrayCreatorTests.cpp | 2 +- .../SpacePointBuilderTests.cpp | 2 +- .../Core/Vertexing/VertexingDataHelper.hpp | 2 +- .../Cuda/Seeding/SeedFinderCudaTest.cpp | 2 +- .../UnitTests/Plugins/Cuda/Seeding2/main.cpp | 2 +- .../Geant4/Geant4SurfaceProviderTests.cpp | 2 +- 40 files changed, 102 insertions(+), 114 deletions(-) diff --git a/Core/include/Acts/EventData/MultiComponentTrackParameters.hpp b/Core/include/Acts/EventData/MultiComponentTrackParameters.hpp index cf932fc0470..5e079af0df7 100644 --- a/Core/include/Acts/EventData/MultiComponentTrackParameters.hpp +++ b/Core/include/Acts/EventData/MultiComponentTrackParameters.hpp @@ -129,11 +129,11 @@ class MultiComponentBoundTrackParameters { /// Get the weight and a GenericBoundTrackParameters object for one component std::pair operator[](std::size_t i) const { - return std::make_pair( + return { std::get(m_components[i]), Parameters(m_surface, std::get(m_components[i]), std::get>(m_components[i]), - m_particleHypothesis)); + m_particleHypothesis)}; } /// Parameters vector. diff --git a/Core/include/Acts/Seeding/BinnedGroupIterator.ipp b/Core/include/Acts/Seeding/BinnedGroupIterator.ipp index 70cd691f5db..8bcd0ac04b8 100644 --- a/Core/include/Acts/Seeding/BinnedGroupIterator.ipp +++ b/Core/include/Acts/Seeding/BinnedGroupIterator.ipp @@ -64,7 +64,7 @@ Acts::BinnedGroupIterator::operator*() const { #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wstringop-overread" #endif - return std::make_tuple(std::move(bottoms), global_index, std::move(tops)); + return {std::move(bottoms), global_index, std::move(tops)}; #if defined(__GNUC__) && __GNUC__ >= 12 && !defined(__clang__) #pragma GCC diagnostic pop #endif diff --git a/Core/include/Acts/Seeding/SeedFinder.ipp b/Core/include/Acts/Seeding/SeedFinder.ipp index e6ccb9f589f..b61947bb634 100644 --- a/Core/include/Acts/Seeding/SeedFinder.ipp +++ b/Core/include/Acts/Seeding/SeedFinder.ipp @@ -839,7 +839,7 @@ std::pair SeedFinder:: const external_spacepoint_t& spM, const Acts::Range1D& rMiddleSPRange) const { if (m_config.useVariableMiddleSPRange) { - return std::make_pair(rMiddleSPRange.min(), rMiddleSPRange.max()); + return {rMiddleSPRange.min(), rMiddleSPRange.max()}; } if (!m_config.rRangeMiddleSP.empty()) { /// get zBin position of the middle SP @@ -848,10 +848,9 @@ std::pair SeedFinder:: int zBin = std::distance(m_config.zBinEdges.begin(), pVal); /// protects against zM at the limit of zBinEdges zBin == 0 ? zBin : --zBin; - return std::make_pair(m_config.rRangeMiddleSP[zBin][0], - m_config.rRangeMiddleSP[zBin][1]); + return {m_config.rRangeMiddleSP[zBin][0], m_config.rRangeMiddleSP[zBin][1]}; } - return std::make_pair(m_config.rMinMiddle, m_config.rMaxMiddle); + return {m_config.rMinMiddle, m_config.rMaxMiddle}; } } // namespace Acts diff --git a/Core/include/Acts/TrackFitting/detail/KalmanGlobalCovariance.hpp b/Core/include/Acts/TrackFitting/detail/KalmanGlobalCovariance.hpp index cf9a3f36b01..66dfe439fd8 100644 --- a/Core/include/Acts/TrackFitting/detail/KalmanGlobalCovariance.hpp +++ b/Core/include/Acts/TrackFitting/detail/KalmanGlobalCovariance.hpp @@ -95,7 +95,7 @@ globalTrackParametersCovariance(const traj_t& multiTraj, prev_ts = ts; }); - return std::make_pair(fullGlobalTrackParamsCov, stateRowIndices); + return {fullGlobalTrackParamsCov, stateRowIndices}; } } // namespace Acts::detail diff --git a/Core/include/Acts/Utilities/GridBinFinder.ipp b/Core/include/Acts/Utilities/GridBinFinder.ipp index 8b06748ffec..37db6082bb0 100644 --- a/Core/include/Acts/Utilities/GridBinFinder.ipp +++ b/Core/include/Acts/Utilities/GridBinFinder.ipp @@ -45,9 +45,9 @@ std::array, DIM> Acts::GridBinFinder::getSizePerAxis( using value_t = typename std::decay_t; if constexpr (std::is_same_v) { assert(val >= 0); - return std::make_pair(-val, val); + return {-val, val}; } else if constexpr (std::is_same_v, value_t>) { - return std::make_pair(-val.first, val.second); + return {-val.first, val.second}; } else { assert(locPosition.size() > i); assert(locPosition[i] > 0ul); diff --git a/Core/include/Acts/Utilities/TrackHelpers.hpp b/Core/include/Acts/Utilities/TrackHelpers.hpp index 36d37fab714..dc3765be7f6 100644 --- a/Core/include/Acts/Utilities/TrackHelpers.hpp +++ b/Core/include/Acts/Utilities/TrackHelpers.hpp @@ -209,7 +209,7 @@ findTrackStateForExtrapolation( } ACTS_VERBOSE("found intersection at " << intersection.pathLength()); - return std::make_pair(*first, intersection.pathLength()); + return std::pair(*first, intersection.pathLength()); } case TrackExtrapolationStrategy::last: { @@ -229,7 +229,7 @@ findTrackStateForExtrapolation( } ACTS_VERBOSE("found intersection at " << intersection.pathLength()); - return std::make_pair(*last, intersection.pathLength()); + return std::pair(*last, intersection.pathLength()); } case TrackExtrapolationStrategy::firstOrLast: { @@ -256,13 +256,13 @@ findTrackStateForExtrapolation( if (intersectionFirst.isValid() && absDistanceFirst <= absDistanceLast) { ACTS_VERBOSE("using first track state with intersection at " << intersectionFirst.pathLength()); - return std::make_pair(*first, intersectionFirst.pathLength()); + return std::pair(*first, intersectionFirst.pathLength()); } if (intersectionLast.isValid() && absDistanceLast <= absDistanceFirst) { ACTS_VERBOSE("using last track state with intersection at " << intersectionLast.pathLength()); - return std::make_pair(*last, intersectionLast.pathLength()); + return std::pair(*last, intersectionLast.pathLength()); } ACTS_ERROR("no intersection found"); @@ -531,7 +531,7 @@ calculatePredictedResidual(track_state_proxy_t trackState) { MeasurementMatrix residualCovariance = measurementCovariance + predictedCovariance; - return std::pair(residual, residualCovariance); + return {residual, residualCovariance}; } /// Helper function to calculate the filtered residual and its covariance @@ -568,7 +568,7 @@ calculateFilteredResidual(track_state_proxy_t trackState) { MeasurementMatrix residualCovariance = measurementCovariance + filteredCovariance; - return std::pair(residual, residualCovariance); + return {residual, residualCovariance}; } /// Helper function to calculate the smoothed residual and its covariance @@ -605,7 +605,7 @@ calculateSmoothedResidual(track_state_proxy_t trackState) { MeasurementMatrix residualCovariance = measurementCovariance + smoothedCovariance; - return std::pair(residual, residualCovariance); + return {residual, residualCovariance}; } /// Helper function to calculate the predicted chi2 diff --git a/Core/src/Propagator/detail/CovarianceEngine.cpp b/Core/src/Propagator/detail/CovarianceEngine.cpp index a89016bce3c..ca2f57b88cf 100644 --- a/Core/src/Propagator/detail/CovarianceEngine.cpp +++ b/Core/src/Propagator/detail/CovarianceEngine.cpp @@ -97,8 +97,7 @@ CurvilinearState detail::curvilinearState( pos4, direction, freeParameters[eFreeQOverP], std::move(cov), particleHypothesis); // Create the curvilinear state - return std::make_tuple(std::move(curvilinearParams), fullTransportJacobian, - accumulatedPath); + return {std::move(curvilinearParams), fullTransportJacobian, accumulatedPath}; } void detail::transportCovarianceToBound( diff --git a/Core/src/Propagator/detail/SympyCovarianceEngine.cpp b/Core/src/Propagator/detail/SympyCovarianceEngine.cpp index f10171cc5ec..488a2d69ec9 100644 --- a/Core/src/Propagator/detail/SympyCovarianceEngine.cpp +++ b/Core/src/Propagator/detail/SympyCovarianceEngine.cpp @@ -86,8 +86,7 @@ CurvilinearState sympy::curvilinearState( pos4, direction, freeParameters[eFreeQOverP], std::move(cov), particleHypothesis); // Create the curvilinear state - return std::make_tuple(std::move(curvilinearParams), fullTransportJacobian, - accumulatedPath); + return {std::move(curvilinearParams), fullTransportJacobian, accumulatedPath}; } void sympy::transportCovarianceToBound( diff --git a/Core/src/Surfaces/detail/AlignmentHelper.cpp b/Core/src/Surfaces/detail/AlignmentHelper.cpp index 92c1fb7d2a1..b5308d60c84 100644 --- a/Core/src/Surfaces/detail/AlignmentHelper.cpp +++ b/Core/src/Surfaces/detail/AlignmentHelper.cpp @@ -87,6 +87,6 @@ Acts::detail::RotationToAxes Acts::detail::rotationToLocalAxesDerivative( rotToCompositeLocalYAxis * relRotation(1, 2) + rotToCompositeLocalZAxis * relRotation(2, 2); - return std::make_tuple(std::move(rotToLocalXAxis), std::move(rotToLocalYAxis), - std::move(rotToLocalZAxis)); + return {std::move(rotToLocalXAxis), std::move(rotToLocalYAxis), + std::move(rotToLocalZAxis)}; } diff --git a/Core/src/Surfaces/detail/AnnulusBoundsHelper.cpp b/Core/src/Surfaces/detail/AnnulusBoundsHelper.cpp index b46bfaf5b8a..4ceab6d594b 100644 --- a/Core/src/Surfaces/detail/AnnulusBoundsHelper.cpp +++ b/Core/src/Surfaces/detail/AnnulusBoundsHelper.cpp @@ -63,5 +63,5 @@ Acts::detail::AnnulusBoundsHelper::create(const Transform3& transform, auto annulusBounds = std::make_shared( rMin, rMax, phiMin, phiMax, originShift, phiShift); - return std::make_tuple(annulusBounds, boundsTransform); + return {annulusBounds, boundsTransform}; } diff --git a/Core/src/Utilities/SpacePointUtility.cpp b/Core/src/Utilities/SpacePointUtility.cpp index 32470e1ebb9..6f662fe20bd 100644 --- a/Core/src/Utilities/SpacePointUtility.cpp +++ b/Core/src/Utilities/SpacePointUtility.cpp @@ -99,7 +99,7 @@ SpacePointUtility::globalCoords( tcov = std::nullopt; } - return std::make_tuple(globalPos, globalTime, gcov, tcov); + return {globalPos, globalTime, gcov, tcov}; } Vector2 SpacePointUtility::calcRhoZVars( diff --git a/Core/src/Vertexing/AdaptiveGridTrackDensity.cpp b/Core/src/Vertexing/AdaptiveGridTrackDensity.cpp index d08b9db4913..31daae4b039 100644 --- a/Core/src/Vertexing/AdaptiveGridTrackDensity.cpp +++ b/Core/src/Vertexing/AdaptiveGridTrackDensity.cpp @@ -156,7 +156,7 @@ AdaptiveGridTrackDensity::getMaxZTPosition(DensityMap& densityMap) const { double maxZ = getSpatialBinCenter(bin.first); double maxT = getTemporalBinCenter(bin.second); - return std::make_pair(maxZ, maxT); + return std::pair(maxZ, maxT); } Result diff --git a/Core/src/Vertexing/ImpactPointEstimator.cpp b/Core/src/Vertexing/ImpactPointEstimator.cpp index df9f75fd9fa..51f0ebe24db 100644 --- a/Core/src/Vertexing/ImpactPointEstimator.cpp +++ b/Core/src/Vertexing/ImpactPointEstimator.cpp @@ -217,7 +217,7 @@ Result> getDistanceAndMomentumImpl( Vector4 deltaRStraightTrack{Vector4::Zero()}; deltaRStraightTrack.head() = pcaStraightTrack - vtxPos; - return std::make_pair(deltaRStraightTrack, momDirStraightTrack); + return std::pair(deltaRStraightTrack, momDirStraightTrack); } // Charged particles in a constant B field follow a helical trajectory. In @@ -291,7 +291,7 @@ Result> getDistanceAndMomentumImpl( Vector4 deltaR{Vector4::Zero()}; deltaR.head() = pca - vtxPos; - return std::make_pair(deltaR, momDir); + return std::pair(deltaR, momDir); } } // namespace diff --git a/Core/src/Vertexing/Vertex.cpp b/Core/src/Vertexing/Vertex.cpp index 7c2c54cc7ac..4368f3b2faa 100644 --- a/Core/src/Vertexing/Vertex.cpp +++ b/Core/src/Vertexing/Vertex.cpp @@ -74,7 +74,7 @@ const std::vector& Vertex::tracks() const { } std::pair Vertex::fitQuality() const { - return std::pair(m_chiSquared, m_numberDoF); + return {m_chiSquared, m_numberDoF}; } void Vertex::setPosition(const Vector3& position) { diff --git a/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp b/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp index 84e050c8470..9a9c5901543 100644 --- a/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp +++ b/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp @@ -106,8 +106,7 @@ auto AlignedDetector::finalize( } // return the pair of geometry and the alignment decorator(s) - return std::make_pair( - std::move(aTrackingGeometry), std::move(aContextDecorators)); + return {std::move(aTrackingGeometry), std::move(aContextDecorators)}; } } // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp b/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp index 4ecae308134..a2c12dfaaa7 100644 --- a/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp +++ b/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp @@ -42,8 +42,7 @@ auto DD4hepDetector::finalize( } ContextDecorators dd4ContextDecorators = {}; // return the pair of geometry and empty decorators - return std::make_pair( - std::move(dd4tGeometry), std::move(dd4ContextDecorators)); + return {std::move(dd4tGeometry), std::move(dd4ContextDecorators)}; } auto DD4hepDetector::finalize( diff --git a/Examples/Detectors/GenericDetector/src/GenericDetector.cpp b/Examples/Detectors/GenericDetector/src/GenericDetector.cpp index bf033fc1e3e..b80b769708d 100644 --- a/Examples/Detectors/GenericDetector/src/GenericDetector.cpp +++ b/Examples/Detectors/GenericDetector/src/GenericDetector.cpp @@ -27,8 +27,7 @@ auto GenericDetector::finalize( cfg.volumeLogLevel); ContextDecorators gContextDecorators = {}; // return the pair of geometry and empty decorators - return std::make_pair( - std::move(gGeometry), std::move(gContextDecorators)); + return {std::move(gGeometry), std::move(gContextDecorators)}; } } // namespace ActsExamples diff --git a/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp b/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp index 0ece428a438..f594b681d49 100644 --- a/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp +++ b/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp @@ -375,8 +375,7 @@ auto TGeoDetector::finalize( ContextDecorators tgeoContextDecorators = {}; // Return the pair of geometry and empty decorators - return std::make_pair( - std::move(tgeoTrackingGeometry), std::move(tgeoContextDecorators)); + return {std::move(tgeoTrackingGeometry), std::move(tgeoContextDecorators)}; } void TGeoDetector::Config::readJson(const std::string& jsonFile) { diff --git a/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp b/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp index bbf1aff4106..ccba63a1c34 100644 --- a/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp +++ b/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp @@ -61,8 +61,7 @@ auto TelescopeDetector::finalize( static_cast(cfg.binValue)); ContextDecorators gContextDecorators = {}; // return the pair of geometry and empty decorators - return std::make_pair( - std::move(gGeometry), std::move(gContextDecorators)); + return {std::move(gGeometry), std::move(gContextDecorators)}; } } // namespace ActsExamples diff --git a/Examples/Framework/src/EventData/ScalingCalibrator.cpp b/Examples/Framework/src/EventData/ScalingCalibrator.cpp index 50e693e8b15..068f6221a25 100644 --- a/Examples/Framework/src/EventData/ScalingCalibrator.cpp +++ b/Examples/Framework/src/EventData/ScalingCalibrator.cpp @@ -52,22 +52,22 @@ std::pair parseMapKey( std::regex reg("^map_([0-9]+)-([0-9]+)-([0-9]+)_([xy]_.*)$"); std::smatch matches; - if (std::regex_search(mapkey, matches, reg) && matches.size() == 5) { - std::size_t vol = std::stoull(matches[1].str()); - std::size_t lyr = std::stoull(matches[2].str()); - std::size_t mod = std::stoull(matches[3].str()); + if (!std::regex_search(mapkey, matches, reg) || matches.size() != 5) { + throw std::runtime_error("Invalid map key: " + mapkey); + } - Acts::GeometryIdentifier geoId; - geoId.setVolume(vol); - geoId.setLayer(lyr); - geoId.setSensitive(mod); + std::size_t vol = std::stoull(matches[1].str()); + std::size_t lyr = std::stoull(matches[2].str()); + std::size_t mod = std::stoull(matches[3].str()); - std::string var(matches[4].str()); + Acts::GeometryIdentifier geoId; + geoId.setVolume(vol); + geoId.setLayer(lyr); + geoId.setSensitive(mod); - return std::make_pair(geoId, var); - } else { - throw std::runtime_error("Invalid map key: " + mapkey); - } + std::string var(matches[4].str()); + + return {geoId, var}; } std::map diff --git a/Examples/Framework/src/Utilities/Paths.cpp b/Examples/Framework/src/Utilities/Paths.cpp index 02bf5889c68..4de064d6eb0 100644 --- a/Examples/Framework/src/Utilities/Paths.cpp +++ b/Examples/Framework/src/Utilities/Paths.cpp @@ -110,7 +110,7 @@ std::pair ActsExamples::determineEventFilesRange( // should only occur if no files matched and the initial values persisted. if (eventMax < eventMin) { - return std::make_pair(0u, 0u); + return {0u, 0u}; } - return std::make_pair(eventMin, eventMax + 1); + return {eventMin, eventMax + 1}; } diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootMaterialDecorator.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootMaterialDecorator.hpp index f5bab09fb70..0cc10482c9e 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/RootMaterialDecorator.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootMaterialDecorator.hpp @@ -128,7 +128,7 @@ class RootMaterialDecorator : public Acts::IMaterialDecorator { /// Return the maps const Acts::DetectorMaterialMaps materialMaps() const { - return std::make_pair(m_surfaceMaterialMap, m_volumeMaterialMap); + return {m_surfaceMaterialMap, m_volumeMaterialMap}; } /// Get readonly access to the config parameters diff --git a/Examples/Io/Root/src/RootNuclearInteractionParametersWriter.cpp b/Examples/Io/Root/src/RootNuclearInteractionParametersWriter.cpp index 995f7bdf57d..265fde166b9 100644 --- a/Examples/Io/Root/src/RootNuclearInteractionParametersWriter.cpp +++ b/Examples/Io/Root/src/RootNuclearInteractionParametersWriter.cpp @@ -121,7 +121,7 @@ buildNotNormalisedMap(TH1F const* hist) { if (integral == 0.) { histoBorders.clear(); temp_HistoContents.clear(); - return std::make_tuple(histoBorders, temp_HistoContents, integral); + return {histoBorders, temp_HistoContents, integral}; } // Set the bin borders @@ -130,7 +130,7 @@ buildNotNormalisedMap(TH1F const* hist) { } histoBorders[nBins] = hist->GetXaxis()->GetXmax(); - return std::make_tuple(histoBorders, temp_HistoContents, integral); + return {histoBorders, temp_HistoContents, integral}; } /// @brief This function combines neighbouring bins with the same value @@ -169,7 +169,7 @@ std::pair, std::vector> buildMap( // Fast exit if the histogram is empty if (histoContents.empty()) { - return std::make_pair(std::get<0>(map), std::vector()); + return {std::get<0>(map), std::vector()}; } // Set the bin content @@ -183,7 +183,7 @@ std::pair, std::vector> buildMap( auto histoBorders = std::get<0>(map); reduceMap(histoBorders, normalisedHistoContents); - return std::make_pair(histoBorders, normalisedHistoContents); + return {histoBorders, normalisedHistoContents}; } /// @brief This method transforms a probability distribution into components @@ -208,7 +208,7 @@ std::pair, std::vector> buildMap( // Fast exit if the histogram is empty if (histoContents.empty()) { - return std::make_pair(std::get<0>(map), std::vector()); + return {std::get<0>(map), std::vector()}; } // Set the bin content @@ -223,7 +223,7 @@ std::pair, std::vector> buildMap( std::vector histoBorders = std::get<0>(map); reduceMap(histoBorders, normalisedHistoContents); - return std::make_pair(histoBorders, normalisedHistoContents); + return {histoBorders, normalisedHistoContents}; } /// @brief This method builds decomposed cumulative probability distributions diff --git a/Examples/Io/Root/src/RootTrackStatesWriter.cpp b/Examples/Io/Root/src/RootTrackStatesWriter.cpp index 37d7cdf2fc9..ee199e48ff3 100644 --- a/Examples/Io/Root/src/RootTrackStatesWriter.cpp +++ b/Examples/Io/Root/src/RootTrackStatesWriter.cpp @@ -471,13 +471,13 @@ ProcessCode RootTrackStatesWriter::writeT(const AlgorithmContext& ctx, auto getTrackParams = [&](unsigned int ipar) -> std::optional> { if (ipar == ePredicted && state.hasPredicted()) { - return std::make_pair(state.predicted(), state.predictedCovariance()); + return std::pair(state.predicted(), state.predictedCovariance()); } if (ipar == eFiltered && state.hasFiltered()) { - return std::make_pair(state.filtered(), state.filteredCovariance()); + return std::pair(state.filtered(), state.filteredCovariance()); } if (ipar == eSmoothed && state.hasSmoothed()) { - return std::make_pair(state.smoothed(), state.smoothedCovariance()); + return std::pair(state.smoothed(), state.smoothedCovariance()); } if (ipar == eUnbiased && state.hasSmoothed() && state.hasProjector() && state.hasCalibrated()) { diff --git a/Examples/Io/Root/src/detail/NuclearInteractionParametrisation.cpp b/Examples/Io/Root/src/detail/NuclearInteractionParametrisation.cpp index 244c30ca039..ee89b828590 100644 --- a/Examples/Io/Root/src/detail/NuclearInteractionParametrisation.cpp +++ b/Examples/Io/Root/src/detail/NuclearInteractionParametrisation.cpp @@ -87,7 +87,7 @@ std::pair calculateMeanAndCovariance( } covariance /= events.size(); - return std::make_pair(mean, covariance); + return {mean, covariance}; } EigenspaceComponents calculateEigenspace(const Vector& mean, @@ -99,7 +99,7 @@ EigenspaceComponents calculateEigenspace(const Vector& mean, // Transform the mean vector into eigenspace Vector meanEigenspace = eigenvectors * mean; - return std::make_tuple(eigenvalues, eigenvectors, meanEigenspace); + return {eigenvalues, eigenvectors, meanEigenspace}; } Parametrisation buildMomentumParameters(const EventCollection& events, @@ -122,7 +122,7 @@ Parametrisation buildMomentumParameters(const EventCollection& events, EigenspaceComponents eigenspaceElements = calculateEigenspace(meanAndCovariance.first, meanAndCovariance.second); // Calculate the cumulative distributions - return std::make_pair(eigenspaceElements, histos); + return {eigenspaceElements, histos}; } EventProperties prepareMomenta(const EventCollection& events, @@ -255,7 +255,7 @@ Parametrisation buildInvariantMassParameters(const EventCollection& events, EigenspaceComponents eigenspaceElements = calculateEigenspace(meanAndCovariance.first, meanAndCovariance.second); // Calculate the cumulative distributions - return std::make_pair(eigenspaceElements, histos); + return {eigenspaceElements, histos}; } std::unordered_map> @@ -341,7 +341,7 @@ cumulativeMultiplicityProbability(const EventCollection& events, } } - return std::make_pair(softHisto, hardHisto); + return {softHisto, hardHisto}; } TVectorF softProbability(const EventCollection& events) { diff --git a/Examples/Scripts/compareRootFiles.hpp b/Examples/Scripts/compareRootFiles.hpp index 464230373be..a7043b15e96 100644 --- a/Examples/Scripts/compareRootFiles.hpp +++ b/Examples/Scripts/compareRootFiles.hpp @@ -34,8 +34,7 @@ class AnyVector { static std::pair*> create(Args&&... args) { std::vector* vector = new std::vector(std::forward(args)...); std::function deleter = [vector] { delete vector; }; - return std::make_pair( - AnyVector{static_cast(vector), std::move(deleter)}, vector); + return {AnyVector{static_cast(vector), std::move(deleter)}, vector}; } // Default-construct a null type-erased vector diff --git a/Fatras/include/ActsFatras/Physics/ElectroMagnetic/PhotonConversion.hpp b/Fatras/include/ActsFatras/Physics/ElectroMagnetic/PhotonConversion.hpp index af45481258e..582cc073bc4 100644 --- a/Fatras/include/ActsFatras/Physics/ElectroMagnetic/PhotonConversion.hpp +++ b/Fatras/include/ActsFatras/Physics/ElectroMagnetic/PhotonConversion.hpp @@ -129,8 +129,8 @@ std::pair PhotonConversion::generatePathLimits( // Fast exit if not a photon or the energy is too low if (particle.pdg() != Acts::PdgParticle::eGamma || particle.absoluteMomentum() < (2 * kElectronMass)) { - return std::make_pair(std::numeric_limits::infinity(), - std::numeric_limits::infinity()); + return {std::numeric_limits::infinity(), + std::numeric_limits::infinity()}; } // Use for the moment only Al data - Yung Tsai - Rev.Mod.Particle Physics Vol. @@ -155,11 +155,11 @@ std::pair PhotonConversion::generatePathLimits( std::uniform_real_distribution uniformDistribution{0., 1.}; // This is a transformation of eq. 3.75 - return std::make_pair(-9. / 7. * - std::log(conversionProbScaleFactor * - (1 - uniformDistribution(generator))) / - (1. - xi), - std::numeric_limits::infinity()); + return {-9. / 7. * + std::log(conversionProbScaleFactor * + (1 - uniformDistribution(generator))) / + (1. - xi), + std::numeric_limits::infinity()}; } template diff --git a/Fatras/include/ActsFatras/Physics/NuclearInteraction/NuclearInteraction.hpp b/Fatras/include/ActsFatras/Physics/NuclearInteraction/NuclearInteraction.hpp index d372cf8ffc9..d0a0d84c36c 100644 --- a/Fatras/include/ActsFatras/Physics/NuclearInteraction/NuclearInteraction.hpp +++ b/Fatras/include/ActsFatras/Physics/NuclearInteraction/NuclearInteraction.hpp @@ -58,8 +58,8 @@ struct NuclearInteraction { const Particle& particle) const { // Fast exit: No parameterisation provided if (multiParticleParameterisation.empty()) { - return std::make_pair(std::numeric_limits::infinity(), - std::numeric_limits::infinity()); + return {std::numeric_limits::infinity(), + std::numeric_limits::infinity()}; } // Find the parametrisation that corresponds to the particle type for (const auto& particleParametrisation : multiParticleParameterisation) { @@ -77,15 +77,13 @@ struct NuclearInteraction { // Set the L0 limit if not done already const auto& distribution = parametrisation.nuclearInteractionProbability; - auto limits = - std::make_pair(std::numeric_limits::infinity(), - sampleContinuousValues( - uniformDistribution(generator), distribution)); - return limits; + return {std::numeric_limits::infinity(), + sampleContinuousValues(uniformDistribution(generator), + distribution)}; } } - return std::make_pair(std::numeric_limits::infinity(), - std::numeric_limits::infinity()); + return {std::numeric_limits::infinity(), + std::numeric_limits::infinity()}; } /// This method performs a nuclear interaction. @@ -483,14 +481,14 @@ NuclearInteraction::sampleKinematics( if (trials == nMatchingTrialsTotal) { return std::nullopt; } - // Re-sampole invariant masses if no fitting momenta were found + // Re-sample invariant masses if no fitting momenta were found if (trials++ % nMatchingTrials == 0) { invariantMasses = sampleInvariantMasses(generator, parameters); } else { momenta = sampleMomenta(generator, parameters, momentum); } } - return std::make_pair(momenta, invariantMasses); + return std::pair(momenta, invariantMasses); } template diff --git a/Fatras/src/Physics/NuclearInteraction/NuclearInteraction.cpp b/Fatras/src/Physics/NuclearInteraction/NuclearInteraction.cpp index 84ab93921fe..ad1e0bbb6a6 100644 --- a/Fatras/src/Physics/NuclearInteraction/NuclearInteraction.cpp +++ b/Fatras/src/Physics/NuclearInteraction/NuclearInteraction.cpp @@ -141,7 +141,7 @@ std::pair NuclearInteraction::globalAngle(double phi1, const float theta = std::acos(vectorSum.z() / vectorSum.norm()); const float phi = std::atan2(vectorSum.y(), vectorSum.x()); - return std::make_pair(phi, theta); + return {phi, theta}; } bool NuclearInteraction::match(const Acts::ActsDynamicVector& momenta, diff --git a/Plugins/DD4hep/src/DD4hepBlueprintFactory.cpp b/Plugins/DD4hep/src/DD4hepBlueprintFactory.cpp index bf653fff801..06ff97153dd 100644 --- a/Plugins/DD4hep/src/DD4hepBlueprintFactory.cpp +++ b/Plugins/DD4hep/src/DD4hepBlueprintFactory.cpp @@ -207,7 +207,7 @@ Acts::Experimental::DD4hepBlueprintFactory::extractExternals( aux += "vol. binning : " + binningString; } // Return the tuple - return std::make_tuple(transform, bValueType, bValues, bBinning, aux); + return {transform, bValueType, bValues, bBinning, aux}; } std::tuple, @@ -309,6 +309,5 @@ Acts::Experimental::DD4hepBlueprintFactory::extractInternals( std::make_shared(geoIdCfg); } - return std::make_tuple(internalsBuilder, rootsFinderBuilder, geoIdGenerator, - aux, ext); + return {internalsBuilder, rootsFinderBuilder, geoIdGenerator, aux, ext}; } diff --git a/Plugins/Geant4/src/Geant4Converters.cpp b/Plugins/Geant4/src/Geant4Converters.cpp index 1608f45bb10..6db950f570a 100644 --- a/Plugins/Geant4/src/Geant4Converters.cpp +++ b/Plugins/Geant4/src/Geant4Converters.cpp @@ -110,7 +110,7 @@ Acts::Geant4ShapeConverter::cylinderBounds(const G4Tubs& g4Tubs) { } double thickness = g4Tubs.GetOuterRadius() - g4Tubs.GetInnerRadius(); auto cBounds = std::make_shared(tArray); - return std::make_tuple(std::move(cBounds), thickness); + return {std::move(cBounds), thickness}; } std::tuple, double> @@ -133,7 +133,7 @@ Acts::Geant4ShapeConverter::radialBounds(const G4Tubs& g4Tubs) { } double thickness = g4Tubs.GetZHalfLength() * 2; auto rBounds = std::make_shared(tArray); - return std::make_tuple(std::move(rBounds), thickness); + return {std::move(rBounds), thickness}; } std::shared_ptr Acts::Geant4ShapeConverter::lineBounds( @@ -173,7 +173,7 @@ Acts::Geant4ShapeConverter::rectangleBounds(const G4Box& g4Box) { } auto rBounds = std::make_shared(hG4XYZ[std::abs(rAxes[0u])], hG4XYZ[std::abs(rAxes[1u])]); - return std::make_tuple(std::move(rBounds), rAxes, thickness); + return {std::move(rBounds), rAxes, thickness}; } std::tuple, std::array, double> @@ -226,7 +226,7 @@ Acts::Geant4ShapeConverter::trapezoidBounds(const G4Trd& g4Trd) { auto tBounds = std::make_shared( halfLengthXminY, halfLengthXmaxY, halfLengthY); - return std::make_tuple(std::move(tBounds), rAxes, thickness); + return {std::move(tBounds), rAxes, thickness}; } std::tuple, std::array, double> @@ -234,19 +234,19 @@ Acts::Geant4ShapeConverter::planarBounds(const G4VSolid& g4Solid) { const G4Box* box = dynamic_cast(&g4Solid); if (box != nullptr) { auto [rBounds, axes, thickness] = rectangleBounds(*box); - return std::make_tuple(std::move(rBounds), axes, thickness); + return {std::move(rBounds), axes, thickness}; } const G4Trd* trd = dynamic_cast(&g4Solid); if (trd != nullptr) { auto [tBounds, axes, thickness] = trapezoidBounds(*trd); - return std::make_tuple(std::move(tBounds), axes, thickness); + return {std::move(tBounds), axes, thickness}; } std::shared_ptr pBounds = nullptr; std::array rAxes = {}; double rThickness = 0.; - return std::make_tuple(std::move(pBounds), rAxes, rThickness); + return {std::move(pBounds), rAxes, rThickness}; } namespace { diff --git a/Plugins/GeoModel/src/GeoModelBlueprintCreater.cpp b/Plugins/GeoModel/src/GeoModelBlueprintCreater.cpp index 429321828d6..081834d7069 100644 --- a/Plugins/GeoModel/src/GeoModelBlueprintCreater.cpp +++ b/Plugins/GeoModel/src/GeoModelBlueprintCreater.cpp @@ -320,7 +320,7 @@ Acts::GeoModelBlueprintCreater::createInternalStructureBuilder( const std::vector& internalConstraints) const { // Check if the internals entry is empty if (entry.internals.empty()) { - return std::make_tuple(nullptr, Extent()); + return {nullptr, Extent()}; } // Build a layer structure @@ -393,10 +393,10 @@ Acts::GeoModelBlueprintCreater::createInternalStructureBuilder( lsbCfg.nMinimalSurfaces = surfaces.size() + 1u; } - return std::make_tuple( + return { std::make_shared( lsbCfg, m_logger->clone(entry.name + "_LayerStructureBuilder")), - internalExtent); + internalExtent}; } else { throw std::invalid_argument( @@ -404,7 +404,7 @@ Acts::GeoModelBlueprintCreater::createInternalStructureBuilder( entry.internals[1u] + "' / or now kdt surfaces provided."); } } - return std::make_tuple(nullptr, Extent()); + return {nullptr, Extent()}; } std::tuple, @@ -434,5 +434,5 @@ Acts::GeoModelBlueprintCreater::parseBounds( "supported for the moment."); } - return std::make_tuple(boundsType, extent, boundValues, translation); + return {boundsType, extent, boundValues, translation}; } diff --git a/Plugins/GeoModel/src/detail/GeoModelExtentHelper.cpp b/Plugins/GeoModel/src/detail/GeoModelExtentHelper.cpp index daab4fd10e6..3734aefb8fe 100644 --- a/Plugins/GeoModel/src/detail/GeoModelExtentHelper.cpp +++ b/Plugins/GeoModel/src/detail/GeoModelExtentHelper.cpp @@ -111,7 +111,7 @@ Acts::detail::GeoModelExentHelper::extentFromTable( BinningValue bValue = bvCyl.at(iv); double val = std::numeric_limits::max(); bool isMin = (iv % 2 == 0); - // Case "e" : exxternal extent + // Case "e" : external extent if (value == "e") { // External parameters do not constrain it if (!externalExtent.constrains(bValue)) { @@ -157,5 +157,5 @@ Acts::detail::GeoModelExentHelper::extentFromTable( } } - return std::make_tuple(boundsType, extent); + return {boundsType, extent}; } diff --git a/Tests/UnitTests/Core/Geometry/LayerCreatorTests.cpp b/Tests/UnitTests/Core/Geometry/LayerCreatorTests.cpp index 13aec3ff545..20f43a3b68e 100644 --- a/Tests/UnitTests/Core/Geometry/LayerCreatorTests.cpp +++ b/Tests/UnitTests/Core/Geometry/LayerCreatorTests.cpp @@ -230,7 +230,7 @@ struct LayerCreatorFixture { } } - return std::make_pair(res, pairs); + return {res, pairs}; } }; diff --git a/Tests/UnitTests/Core/Geometry/SurfaceArrayCreatorTests.cpp b/Tests/UnitTests/Core/Geometry/SurfaceArrayCreatorTests.cpp index 00b6509ef25..6d69c1667da 100644 --- a/Tests/UnitTests/Core/Geometry/SurfaceArrayCreatorTests.cpp +++ b/Tests/UnitTests/Core/Geometry/SurfaceArrayCreatorTests.cpp @@ -228,7 +228,7 @@ struct SurfaceArrayCreatorFixture { } } - return std::make_pair(res, pairs); + return {res, pairs}; } }; diff --git a/Tests/UnitTests/Core/SpacePointFormation/SpacePointBuilderTests.cpp b/Tests/UnitTests/Core/SpacePointFormation/SpacePointBuilderTests.cpp index c20239bb078..6d043178320 100644 --- a/Tests/UnitTests/Core/SpacePointFormation/SpacePointBuilderTests.cpp +++ b/Tests/UnitTests/Core/SpacePointFormation/SpacePointBuilderTests.cpp @@ -93,7 +93,7 @@ std::pair stripEnds( auto gPos1 = surface->localToGlobal(gctx, lpos1, globalFakeMom); auto gPos2 = surface->localToGlobal(gctx, lpos2, globalFakeMom); - return std::make_pair(gPos1, gPos2); + return {gPos1, gPos2}; } // Create a test context diff --git a/Tests/UnitTests/Core/Vertexing/VertexingDataHelper.hpp b/Tests/UnitTests/Core/Vertexing/VertexingDataHelper.hpp index d984b4c8c44..383b5b84d2c 100644 --- a/Tests/UnitTests/Core/Vertexing/VertexingDataHelper.hpp +++ b/Tests/UnitTests/Core/Vertexing/VertexingDataHelper.hpp @@ -143,7 +143,7 @@ readTracksAndVertexCSV(const std::string& toolString, vertices.push_back(vertexInfo); } - return std::make_tuple(beamspotConstraint, vertices, tracks); + return {beamspotConstraint, vertices, tracks}; } } // namespace Acts::Test diff --git a/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp b/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp index 1b31f083fd8..87d61b1df71 100644 --- a/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp +++ b/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp @@ -231,7 +231,7 @@ int main(int argc, char** argv) { -> std::tuple> { Acts::Vector3 position(sp.x(), sp.y(), sp.z()); Acts::Vector2 variance(sp.varianceR, sp.varianceZ); - return std::make_tuple(position, variance, std::nullopt); + return {position, variance, std::nullopt}; }; // setup spacepoint grid config diff --git a/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp b/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp index b63d549e37f..3b134aca9a0 100644 --- a/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp +++ b/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp @@ -113,7 +113,7 @@ int main(int argc, char* argv[]) { -> std::tuple> { Acts::Vector3 position(sp.x(), sp.y(), sp.z()); Acts::Vector2 covariance(sp.m_varianceR, sp.m_varianceZ); - return std::make_tuple(position, covariance, std::nullopt); + return {position, covariance, std::nullopt}; }; // extent used to store r range for middle spacepoint diff --git a/Tests/UnitTests/Plugins/Geant4/Geant4SurfaceProviderTests.cpp b/Tests/UnitTests/Plugins/Geant4/Geant4SurfaceProviderTests.cpp index 49521ae786d..7508fe9a148 100644 --- a/Tests/UnitTests/Plugins/Geant4/Geant4SurfaceProviderTests.cpp +++ b/Tests/UnitTests/Plugins/Geant4/Geant4SurfaceProviderTests.cpp @@ -123,7 +123,7 @@ ConstructGeant4World() { parser.SetOutputFileOverwrite(true); parser.Write(gdmlPath.string(), physWorld); - return std::make_tuple(physWorld, names); + return {physWorld, names}; } auto gctx = Acts::GeometryContext(); From 5c410858ee4560add02c828df61274fdaddc3f41 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Thu, 5 Dec 2024 23:50:53 +0100 Subject: [PATCH 3/5] refactor: Create particle/simhit to measurement maps in `DigitizationAlgorithm` in Examples (#3944) We have a lot of cases where we need particle/simhit -> measurement relations but on the whiteboard we only have the opposite. I believe it makes more sense to store the relations in both directions on the whiteboard instead of inverting in the downstream algorithms. I use this new map in a couple of occasions and cleaned out wrongly defined typedefs apart from my usual Examples cleanup. ## Summary by CodeRabbit - **New Features** - Added new output parameters for the digitization algorithm: `outputParticleMeasurementsMap` and `outputSimHitMeasurementsMap`. - **Bug Fixes** - Enhanced error handling for configuration parameters related to output collections in the digitization algorithm. - **Documentation** - Updated comments and variable names for clarity, particularly around measurements versus hits. - **Refactor** - Simplified class inheritance and updated data types across several components to improve consistency and clarity. - **Chores** - Removed unnecessary header files and type aliases to streamline the codebase. --- .../Digitization/DigitizationAlgorithm.hpp | 9 +++ .../src/DigitizationAlgorithm.cpp | 22 +++++-- .../TrackFitting/SurfaceSortingAlgorithm.hpp | 11 +--- .../src/SurfaceSortingAlgorithm.cpp | 21 +++---- .../TruthTracking/TrackTruthMatcher.hpp | 6 +- .../TruthTracking/TruthSeedingAlgorithm.cpp | 54 ++++++++-------- .../TruthTracking/TruthSeedingAlgorithm.hpp | 18 ++---- .../TruthTracking/TruthTrackFinder.cpp | 32 ++++------ .../TruthTracking/TruthTrackFinder.hpp | 14 ++--- .../TruthTracking/TruthVertexFinder.hpp | 6 +- .../include/ActsExamples/EventData/Index.hpp | 15 +++-- .../ActsExamples/EventData/Measurement.hpp | 7 +++ .../include/ActsExamples/EventData/SimHit.hpp | 6 -- .../ActsExamples/Io/Csv/CsvSeedWriter.hpp | 7 ++- .../ActsExamples/Io/Csv/CsvTrackWriter.hpp | 43 +++++++------ .../Io/Root/RootTrackParameterWriter.hpp | 12 ++-- .../Io/Root/RootTrackStatesWriter.hpp | 8 +-- .../Io/Root/SeedingPerformanceWriter.hpp | 8 +-- .../Io/Root/TrackFinderNTupleWriter.hpp | 5 +- .../Io/Root/src/RootTrackParameterWriter.cpp | 7 +-- .../Io/Root/src/TrackFinderNTupleWriter.cpp | 63 +++++++++---------- .../python/acts/examples/reconstruction.py | 6 +- .../Python/python/acts/examples/simulation.py | 2 + Examples/Python/src/Digitization.cpp | 2 + Examples/Python/src/Output.cpp | 5 +- Examples/Python/src/TruthTracking.cpp | 4 +- 26 files changed, 183 insertions(+), 210 deletions(-) diff --git a/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp b/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp index 8e9ffc6e58b..a7e9b31bb7d 100644 --- a/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp +++ b/Examples/Algorithms/Digitization/include/ActsExamples/Digitization/DigitizationAlgorithm.hpp @@ -48,6 +48,10 @@ class DigitizationAlgorithm final : public IAlgorithm { std::string outputMeasurementParticlesMap = "measurement_particles_map"; /// Output collection to map measured hits to simulated hits. std::string outputMeasurementSimHitsMap = "measurement_simhits_map"; + /// Output collection to map particles to measurements. + std::string outputParticleMeasurementsMap = "particle_measurements_map"; + /// Output collection to map particles to simulated hits. + std::string outputSimHitMeasurementsMap = "simhit_measurements_map"; /// Map of surface by identifier to allow local - to global std::unordered_map @@ -140,6 +144,11 @@ class DigitizationAlgorithm final : public IAlgorithm { WriteDataHandle> m_outputMeasurementSimHitsMap{ this, "OutputMeasurementSimHitsMap"}; + WriteDataHandle> m_outputParticleMeasurementsMap{ + this, "OutputParticleMeasurementsMap"}; + WriteDataHandle> m_outputSimHitMeasurementsMap{ + this, "OutputSimHitMeasurementsMap"}; + /// Construct a fixed-size smearer from a configuration. /// /// It's templated on the smearing dimension given by @tparam kSmearDIM diff --git a/Examples/Algorithms/Digitization/src/DigitizationAlgorithm.cpp b/Examples/Algorithms/Digitization/src/DigitizationAlgorithm.cpp index d7b4754cb3a..e55ac39d09b 100644 --- a/Examples/Algorithms/Digitization/src/DigitizationAlgorithm.cpp +++ b/Examples/Algorithms/Digitization/src/DigitizationAlgorithm.cpp @@ -16,12 +16,9 @@ #include "ActsExamples/EventData/GeometryContainers.hpp" #include "ActsExamples/EventData/Index.hpp" #include "ActsExamples/Framework/AlgorithmContext.hpp" -#include "ActsExamples/Utilities/Range.hpp" -#include "ActsFatras/EventData/Barcode.hpp" #include #include -#include #include #include #include @@ -61,12 +58,23 @@ DigitizationAlgorithm::DigitizationAlgorithm(Config config, throw std::invalid_argument( "Missing hit-to-simulated-hits map output collection"); } + if (m_cfg.outputParticleMeasurementsMap.empty()) { + throw std::invalid_argument( + "Missing particle-to-measurements map output collection"); + } + if (m_cfg.outputSimHitMeasurementsMap.empty()) { + throw std::invalid_argument( + "Missing particle-to-simulated-hits map output collection"); + } m_outputMeasurements.initialize(m_cfg.outputMeasurements); m_outputClusters.initialize(m_cfg.outputClusters); m_outputMeasurementParticlesMap.initialize( m_cfg.outputMeasurementParticlesMap); m_outputMeasurementSimHitsMap.initialize(m_cfg.outputMeasurementSimHitsMap); + m_outputParticleMeasurementsMap.initialize( + m_cfg.outputParticleMeasurementsMap); + m_outputSimHitMeasurementsMap.initialize(m_cfg.outputSimHitMeasurementsMap); } if (m_cfg.doOutputCells) { @@ -141,7 +149,7 @@ ProcessCode DigitizationAlgorithm::execute(const AlgorithmContext& ctx) const { MeasurementContainer measurements; ClusterContainer clusters; - IndexMultimap measurementParticlesMap; + IndexMultimap measurementParticlesMap; IndexMultimap measurementSimHitsMap; measurements.reserve(simHits.size()); measurementParticlesMap.reserve(simHits.size()); @@ -302,6 +310,12 @@ ProcessCode DigitizationAlgorithm::execute(const AlgorithmContext& ctx) const { m_outputMeasurements(ctx, std::move(measurements)); m_outputClusters(ctx, std::move(clusters)); + // invert them before they are moved + m_outputParticleMeasurementsMap( + ctx, invertIndexMultimap(measurementParticlesMap)); + m_outputSimHitMeasurementsMap(ctx, + invertIndexMultimap(measurementSimHitsMap)); + m_outputMeasurementParticlesMap(ctx, std::move(measurementParticlesMap)); m_outputMeasurementSimHitsMap(ctx, std::move(measurementSimHitsMap)); } diff --git a/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp b/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp index 02f85bff2e1..6315e192e67 100644 --- a/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp +++ b/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp @@ -9,25 +9,16 @@ #pragma once #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/Index.hpp" -#include "ActsExamples/EventData/IndexSourceLink.hpp" #include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/ProtoTrack.hpp" #include "ActsExamples/EventData/SimHit.hpp" -#include "ActsExamples/EventData/Track.hpp" #include "ActsExamples/Framework/DataHandle.hpp" #include "ActsExamples/Framework/IAlgorithm.hpp" #include "ActsExamples/Framework/ProcessCode.hpp" -#include -#include #include -#include namespace ActsExamples { -struct AlgorithmContext; - -using TrackHitList = std::map; class SurfaceSortingAlgorithm final : public IAlgorithm { public: @@ -55,7 +46,7 @@ class SurfaceSortingAlgorithm final : public IAlgorithm { ReadDataHandle m_inputProtoTracks{this, "InputProtoTracks"}; ReadDataHandle m_inputSimHits{this, "InputSimHits"}; - ReadDataHandle m_inputMeasurementSimHitsMap{ + ReadDataHandle m_inputMeasurementSimHitsMap{ this, "InputMeasurementSimHitsMap"}; WriteDataHandle m_outputProtoTracks{this, "OutputProtoTracks"}; diff --git a/Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp b/Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp index 040634efbe9..57a79ab2db0 100644 --- a/Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp +++ b/Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp @@ -9,23 +9,18 @@ #include "ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp" #include "ActsExamples/EventData/ProtoTrack.hpp" -#include "ActsExamples/EventData/SimHit.hpp" #include "ActsFatras/EventData/Hit.hpp" #include -#include #include #include #include namespace ActsExamples { -struct AlgorithmContext; -} // namespace ActsExamples -ActsExamples::SurfaceSortingAlgorithm::SurfaceSortingAlgorithm( - Config cfg, Acts::Logging::Level level) - : ActsExamples::IAlgorithm("SurfaceSortingAlgorithm", level), - m_cfg(std::move(cfg)) { +SurfaceSortingAlgorithm::SurfaceSortingAlgorithm(Config cfg, + Acts::Logging::Level level) + : IAlgorithm("SurfaceSortingAlgorithm", level), m_cfg(std::move(cfg)) { if (m_cfg.inputProtoTracks.empty()) { throw std::invalid_argument("Missing input proto track collection"); } @@ -45,15 +40,15 @@ ActsExamples::SurfaceSortingAlgorithm::SurfaceSortingAlgorithm( m_outputProtoTracks.initialize(m_cfg.outputProtoTracks); } -ActsExamples::ProcessCode ActsExamples::SurfaceSortingAlgorithm::execute( - const ActsExamples::AlgorithmContext& ctx) const { +ProcessCode SurfaceSortingAlgorithm::execute( + const AlgorithmContext& ctx) const { const auto& protoTracks = m_inputProtoTracks(ctx); const auto& simHits = m_inputSimHits(ctx); const auto& simHitsMap = m_inputMeasurementSimHitsMap(ctx); ProtoTrackContainer sortedTracks; sortedTracks.reserve(protoTracks.size()); - TrackHitList trackHitList; + std::map trackHitList; for (std::size_t itrack = 0; itrack < protoTracks.size(); ++itrack) { const auto& protoTrack = protoTracks[itrack]; @@ -83,5 +78,7 @@ ActsExamples::ProcessCode ActsExamples::SurfaceSortingAlgorithm::execute( m_outputProtoTracks(ctx, std::move(sortedTracks)); - return ActsExamples::ProcessCode::SUCCESS; + return ProcessCode::SUCCESS; } + +} // namespace ActsExamples diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackTruthMatcher.hpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackTruthMatcher.hpp index 5f8d89a98b0..6c11d4e0c43 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackTruthMatcher.hpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackTruthMatcher.hpp @@ -9,7 +9,7 @@ #pragma once #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/SimHit.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/EventData/Track.hpp" #include "ActsExamples/EventData/TruthMatching.hpp" @@ -21,8 +21,6 @@ namespace ActsExamples { -struct AlgorithmContext; - /// Matches tracks to truth particles and vice versa class TrackTruthMatcher final : public IAlgorithm { public: @@ -56,7 +54,7 @@ class TrackTruthMatcher final : public IAlgorithm { ReadDataHandle m_inputTracks{this, "InputTracks"}; ReadDataHandle m_inputParticles{this, "InputParticles"}; - ReadDataHandle m_inputMeasurementParticlesMap{ + ReadDataHandle m_inputMeasurementParticlesMap{ this, "InputMeasurementParticlesMap"}; WriteDataHandle m_outputTrackParticleMatching{ this, "OutputTrackParticleMatching"}; diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.cpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.cpp index 6ea0225e200..2e5d1c4cd15 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.cpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.cpp @@ -12,7 +12,6 @@ #include "ActsExamples/EventData/IndexSourceLink.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Utilities/Range.hpp" -#include "ActsFatras/EventData/Particle.hpp" #include #include @@ -25,18 +24,16 @@ #include namespace ActsExamples { -struct AlgorithmContext; -} // namespace ActsExamples -ActsExamples::TruthSeedingAlgorithm::TruthSeedingAlgorithm( - ActsExamples::TruthSeedingAlgorithm::Config cfg, Acts::Logging::Level lvl) - : ActsExamples::IAlgorithm("TruthSeedingAlgorithm", lvl), - m_cfg(std::move(cfg)) { +TruthSeedingAlgorithm::TruthSeedingAlgorithm(Config cfg, + Acts::Logging::Level lvl) + : IAlgorithm("TruthSeedingAlgorithm", lvl), m_cfg(std::move(cfg)) { if (m_cfg.inputParticles.empty()) { throw std::invalid_argument("Missing input truth particles collection"); } - if (m_cfg.inputMeasurementParticlesMap.empty()) { - throw std::invalid_argument("Missing input hit-particles map collection"); + if (m_cfg.inputParticleMeasurementsMap.empty()) { + throw std::invalid_argument( + "Missing input particle-measurements map collection"); } if (m_cfg.inputSpacePoints.empty()) { throw std::invalid_argument("Missing seeds or space point collection"); @@ -65,20 +62,16 @@ ActsExamples::TruthSeedingAlgorithm::TruthSeedingAlgorithm( } m_inputParticles.initialize(m_cfg.inputParticles); - m_inputMeasurementParticlesMap.initialize(m_cfg.inputMeasurementParticlesMap); + m_inputParticleMeasurementsMap.initialize(m_cfg.inputParticleMeasurementsMap); m_outputParticles.initialize(m_cfg.outputParticles); m_outputProtoTracks.initialize(m_cfg.outputProtoTracks); m_outputSeeds.initialize(m_cfg.outputSeeds); } -ActsExamples::ProcessCode ActsExamples::TruthSeedingAlgorithm::execute( - const ActsExamples::AlgorithmContext& ctx) const { +ProcessCode TruthSeedingAlgorithm::execute(const AlgorithmContext& ctx) const { // prepare input collections const auto& particles = m_inputParticles(ctx); - const auto& hitParticlesMap = m_inputMeasurementParticlesMap(ctx); - // compute particle_id -> {hit_id...} map from the - // hit_id -> {particle_id...} map on the fly. - const auto& particleHitsMap = invertIndexMultimap(hitParticlesMap); + const auto& particleMeasurementsMap = m_inputParticleMeasurementsMap(ctx); // construct the combined input container of space point pointers from all // configured input sources. @@ -120,27 +113,28 @@ ActsExamples::ProcessCode ActsExamples::TruthSeedingAlgorithm::execute( } for (const auto& particle : particles) { - // find the corresponding hits for this particle - const auto& hits = - makeRange(particleHitsMap.equal_range(particle.particleId())); - // fill hit indices to create the proto track + // find the corresponding measurements for this particle + const auto& measurements = + makeRange(particleMeasurementsMap.equal_range(particle.particleId())); + // fill measurement indices to create the proto track ProtoTrack track; - track.reserve(hits.size()); - for (const auto& hit : hits) { - track.push_back(hit.second); + track.reserve(measurements.size()); + for (const auto& measurement : measurements) { + track.push_back(measurement.second); } - // The list of hits and the initial start parameters + // The list of measurements and the initial start parameters if (track.size() < 3) { - ACTS_WARNING("Particle " << particle << " has less than 3 hits"); + ACTS_WARNING("Particle " << particle << " has less than 3 measurements"); continue; } // Space points on the proto track std::vector spacePointsOnTrack; spacePointsOnTrack.reserve(track.size()); - // Loop over the hit index on the proto track to find the space points - for (const auto& hitIndex : track) { - auto it = spMap.find(hitIndex); + // Loop over the measurement index on the proto track to find the space + // points + for (const auto& measurementIndex : track) { + auto it = spMap.find(measurementIndex); if (it != spMap.end()) { spacePointsOnTrack.push_back(it->second); } @@ -198,5 +192,7 @@ ActsExamples::ProcessCode ActsExamples::TruthSeedingAlgorithm::execute( m_outputProtoTracks(ctx, std::move(tracks)); m_outputSeeds(ctx, std::move(seeds)); - return ActsExamples::ProcessCode::SUCCESS; + return ProcessCode::SUCCESS; } + +} // namespace ActsExamples diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.hpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.hpp index a6cb0416160..5807ef54cd0 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.hpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthSeedingAlgorithm.hpp @@ -11,7 +11,6 @@ #include "Acts/Definitions/Units.hpp" #include "Acts/Utilities/Logger.hpp" #include "ActsExamples/EventData/ProtoTrack.hpp" -#include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/EventData/SimSeed.hpp" #include "ActsExamples/EventData/SimSpacePoint.hpp" @@ -23,16 +22,7 @@ #include #include -namespace ActsFatras { -class Barcode; -} // namespace ActsFatras - -namespace Acts { -class TrackingGeometry; -} - namespace ActsExamples { -struct AlgorithmContext; /// Construct track seeds from particles. class TruthSeedingAlgorithm final : public IAlgorithm { @@ -40,8 +30,8 @@ class TruthSeedingAlgorithm final : public IAlgorithm { struct Config { /// The input truth particles that should be used for truth seeding. std::string inputParticles; - /// The input hit-particles map collection. - std::string inputMeasurementParticlesMap; + /// The input particle-measurements map collection. + std::string inputParticleMeasurementsMap; /// Input space point collections. /// /// We allow multiple space point collections to allow different parts of @@ -80,8 +70,8 @@ class TruthSeedingAlgorithm final : public IAlgorithm { Config m_cfg; ReadDataHandle m_inputParticles{this, "InputParticles"}; - ReadDataHandle m_inputMeasurementParticlesMap{ - this, "InputMeasurementParticlesMaps"}; + ReadDataHandle> m_inputParticleMeasurementsMap{ + this, "InputParticleMeasurementsMap"}; std::vector>> m_inputSpacePoints{}; diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.cpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.cpp index 54953ce7c2b..84b08693635 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.cpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.cpp @@ -8,23 +8,15 @@ #include "ActsExamples/TruthTracking/TruthTrackFinder.hpp" -#include "Acts/Utilities/MultiIndex.hpp" -#include "ActsExamples/EventData/Index.hpp" #include "ActsExamples/EventData/ProtoTrack.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Utilities/Range.hpp" -#include "ActsFatras/EventData/Particle.hpp" -#include #include #include #include namespace ActsExamples { -struct AlgorithmContext; -} // namespace ActsExamples - -using namespace ActsExamples; TruthTrackFinder::TruthTrackFinder(const Config& config, Acts::Logging::Level level) @@ -32,7 +24,7 @@ TruthTrackFinder::TruthTrackFinder(const Config& config, if (m_cfg.inputParticles.empty()) { throw std::invalid_argument("Missing input truth particles collection"); } - if (m_cfg.inputMeasurementParticlesMap.empty()) { + if (m_cfg.inputParticleMeasurementsMap.empty()) { throw std::invalid_argument("Missing input hit-particles map collection"); } if (m_cfg.outputProtoTracks.empty()) { @@ -40,17 +32,14 @@ TruthTrackFinder::TruthTrackFinder(const Config& config, } m_inputParticles.initialize(m_cfg.inputParticles); - m_inputMeasurementParticlesMap.initialize(m_cfg.inputMeasurementParticlesMap); + m_inputParticleMeasurementsMap.initialize(m_cfg.inputParticleMeasurementsMap); m_outputProtoTracks.initialize(m_cfg.outputProtoTracks); } ProcessCode TruthTrackFinder::execute(const AlgorithmContext& ctx) const { // prepare input collections const auto& particles = m_inputParticles(ctx); - const auto& hitParticlesMap = m_inputMeasurementParticlesMap(ctx); - // compute particle_id -> {hit_id...} map from the - // hit_id -> {particle_id...} map on the fly. - const auto& particleHitsMap = invertIndexMultimap(hitParticlesMap); + const auto& particleMeasurementsMap = m_inputParticleMeasurementsMap(ctx); // prepare output collection ProtoTrackContainer tracks; @@ -59,14 +48,15 @@ ProcessCode TruthTrackFinder::execute(const AlgorithmContext& ctx) const { ACTS_VERBOSE("Create prototracks for " << particles.size() << " particles"); for (const auto& particle : particles) { // find the corresponding hits for this particle - const auto& hits = - makeRange(particleHitsMap.equal_range(particle.particleId())); - ACTS_VERBOSE(" - Prototrack from " << hits.size() << " hits"); + const auto& measurements = + makeRange(particleMeasurementsMap.equal_range(particle.particleId())); + ACTS_VERBOSE(" - Prototrack from " << measurements.size() + << " measurements"); // fill hit indices to create the proto track ProtoTrack track; - track.reserve(hits.size()); - for (const auto& hit : hits) { - track.emplace_back(hit.second); + track.reserve(measurements.size()); + for (const auto& measurement : measurements) { + track.emplace_back(measurement.second); } // add proto track to the output collection tracks.emplace_back(std::move(track)); @@ -75,3 +65,5 @@ ProcessCode TruthTrackFinder::execute(const AlgorithmContext& ctx) const { m_outputProtoTracks(ctx, std::move(tracks)); return ProcessCode::SUCCESS; } + +} // namespace ActsExamples diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.hpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.hpp index 8f7affb79c3..39c42f651b3 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.hpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthTrackFinder.hpp @@ -10,7 +10,6 @@ #include "Acts/Utilities/Logger.hpp" #include "ActsExamples/EventData/ProtoTrack.hpp" -#include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Framework/DataHandle.hpp" #include "ActsExamples/Framework/IAlgorithm.hpp" @@ -18,12 +17,7 @@ #include -namespace ActsFatras { -class Barcode; -} // namespace ActsFatras - namespace ActsExamples { -struct AlgorithmContext; /// Convert true particle tracks into "reconstructed" proto tracks. /// @@ -37,8 +31,8 @@ class TruthTrackFinder final : public IAlgorithm { struct Config { /// The input truth particles that should be used to create proto tracks. std::string inputParticles; - /// The input hit-particles map collection. - std::string inputMeasurementParticlesMap; + /// The input particle-measurements map collection. + std::string inputParticleMeasurementsMap; /// The output proto tracks collection. std::string outputProtoTracks; }; @@ -55,8 +49,8 @@ class TruthTrackFinder final : public IAlgorithm { ReadDataHandle m_inputParticles{this, "InputParticles"}; - ReadDataHandle m_inputMeasurementParticlesMap{ - this, "InputMeasurementParticlesMap"}; + ReadDataHandle> m_inputParticleMeasurementsMap{ + this, "InputParticleMeasurementsMap"}; WriteDataHandle m_outputProtoTracks{this, "OutputProtoTracks"}; diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthVertexFinder.hpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthVertexFinder.hpp index 2ea7d401866..bbbefa2dd96 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthVertexFinder.hpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TruthVertexFinder.hpp @@ -9,6 +9,7 @@ #pragma once #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/ProtoVertex.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/EventData/Track.hpp" @@ -19,13 +20,10 @@ #include namespace ActsExamples { -struct AlgorithmContext; /// Group particles into proto vertices using truth information. class TruthVertexFinder final : public IAlgorithm { public: - using HitParticlesMap = ActsExamples::IndexMultimap; - struct Config { /// The input tracks that should be used to create proto vertices. std::string inputTracks; @@ -55,7 +53,7 @@ class TruthVertexFinder final : public IAlgorithm { ReadDataHandle m_inputTracks{this, "InputTracks"}; ReadDataHandle m_inputParticles{this, "InputParticles"}; - ReadDataHandle m_inputMeasurementParticlesMap{ + ReadDataHandle m_inputMeasurementParticlesMap{ this, "InputMeasurementParticlesMap"}; WriteDataHandle m_outputProtoVertices{ this, "OutputProtoVertices"}; diff --git a/Examples/Framework/include/ActsExamples/EventData/Index.hpp b/Examples/Framework/include/ActsExamples/EventData/Index.hpp index 46e431ea76a..5750737b05f 100644 --- a/Examples/Framework/include/ActsExamples/EventData/Index.hpp +++ b/Examples/Framework/include/ActsExamples/EventData/Index.hpp @@ -30,18 +30,21 @@ using Index = std::uint32_t; template using IndexMultimap = boost::container::flat_multimap; -/// Invert the multimap, i.e. from a -> {b...} to b -> {a...}. +/// Store the inverse of an index multimap, i.e. from a -> {b...} to b -> +/// {a...}. /// /// @note This assumes that the value in the initial multimap is itself a /// sortable index-like object, as would be the case when mapping e.g. /// hit ids to particle ids/ barcodes. template -inline boost::container::flat_multimap invertIndexMultimap( - const IndexMultimap& multimap) { - using InverseMultimap = boost::container::flat_multimap; +using InverseMultimap = boost::container::flat_multimap; +/// Invert the multimap, i.e. from a -> {b...} to b -> {a...} +template +inline InverseMultimap invertIndexMultimap( + const IndexMultimap& multimap) { // switch key-value without enforcing the new ordering (linear copy) - typename InverseMultimap::sequence_type unordered; + typename InverseMultimap::sequence_type unordered; unordered.reserve(multimap.size()); for (auto&& [index, value] : multimap) { // value is now the key and the index is now the value @@ -49,7 +52,7 @@ inline boost::container::flat_multimap invertIndexMultimap( } // adopting the unordered sequence will reestablish the correct order - InverseMultimap inverse; + InverseMultimap inverse; #if BOOST_VERSION < 107800 for (const auto& i : unordered) { inverse.insert(i); diff --git a/Examples/Framework/include/ActsExamples/EventData/Measurement.hpp b/Examples/Framework/include/ActsExamples/EventData/Measurement.hpp index 251d7bc293a..86a8be27aba 100644 --- a/Examples/Framework/include/ActsExamples/EventData/Measurement.hpp +++ b/Examples/Framework/include/ActsExamples/EventData/Measurement.hpp @@ -17,6 +17,7 @@ #include "ActsExamples/EventData/GeometryContainers.hpp" #include "ActsExamples/EventData/IndexSourceLink.hpp" #include "ActsExamples/EventData/MeasurementConcept.hpp" +#include "ActsExamples/EventData/SimParticle.hpp" #include #include @@ -531,4 +532,10 @@ static_assert( std::random_access_iterator && std::random_access_iterator); +using MeasurementSimHitsMap = IndexMultimap; +using MeasurementParticlesMap = IndexMultimap; + +using SimHitMeasurementsMap = InverseMultimap; +using ParticleMeasurementsMap = InverseMultimap; + } // namespace ActsExamples diff --git a/Examples/Framework/include/ActsExamples/EventData/SimHit.hpp b/Examples/Framework/include/ActsExamples/EventData/SimHit.hpp index be10ce2639c..8c7854082fd 100644 --- a/Examples/Framework/include/ActsExamples/EventData/SimHit.hpp +++ b/Examples/Framework/include/ActsExamples/EventData/SimHit.hpp @@ -9,8 +9,6 @@ #pragma once #include "ActsExamples/EventData/GeometryContainers.hpp" -#include "ActsExamples/EventData/Index.hpp" -#include "ActsExamples/EventData/SimParticle.hpp" #include "ActsFatras/EventData/Hit.hpp" namespace ActsExamples { @@ -19,8 +17,4 @@ using SimHit = ::ActsFatras::Hit; /// Store hits ordered by geometry identifier. using SimHitContainer = GeometryIdMultiset; -using HitParticlesMap = IndexMultimap; - -using HitSimHitsMap = IndexMultimap; - } // namespace ActsExamples diff --git a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSeedWriter.hpp b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSeedWriter.hpp index 670183f7c29..d893c5aae42 100644 --- a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSeedWriter.hpp +++ b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSeedWriter.hpp @@ -8,6 +8,7 @@ #pragma once +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/ProtoTrack.hpp" #include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/EventData/SimParticle.hpp" @@ -76,9 +77,9 @@ class CsvSeedWriter : public WriterT { ReadDataHandle m_inputParticles{this, "InputParticles"}; ReadDataHandle m_inputSimSeeds{this, "InputSimSeeds"}; ReadDataHandle m_inputSimHits{this, "InputSimHits"}; - ReadDataHandle m_inputMeasurementParticlesMap{ + ReadDataHandle m_inputMeasurementParticlesMap{ this, "InputMeasurementParticlesMap"}; - ReadDataHandle m_inputMeasurementSimHitsMap{ + ReadDataHandle m_inputMeasurementSimHitsMap{ this, "InputMeasurementSimHitsMap"}; /// @brief Struct for brief seed summary info @@ -95,7 +96,7 @@ class CsvSeedWriter : public WriterT { float truthDistance = -1; std::string seedType = "unknown"; ProtoTrack measurementsID; - }; // trackInfo struct + }; }; } // namespace ActsExamples diff --git a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvTrackWriter.hpp b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvTrackWriter.hpp index 06a449e2615..d3bbfe989f7 100644 --- a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvTrackWriter.hpp +++ b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvTrackWriter.hpp @@ -11,7 +11,7 @@ #include "Acts/Definitions/Units.hpp" #include "Acts/EventData/MultiTrajectoryHelpers.hpp" #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/SimHit.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/Track.hpp" #include "ActsExamples/Framework/ProcessCode.hpp" #include "ActsExamples/Framework/WriterT.hpp" @@ -22,12 +22,6 @@ #include #include -namespace ActsExamples { -struct AlgorithmContext; -} // namespace ActsExamples - -using namespace Acts::UnitLiterals; - namespace ActsExamples { /// @class CsvTrackWriter @@ -46,16 +40,24 @@ namespace ActsExamples { class CsvTrackWriter : public WriterT { public: struct Config { - std::string inputTracks; ///< Input track collection - std::string outputDir; ///< where to place output files - std::string fileName = "CKFtracks.csv"; ///< name of the output files - std::string - inputMeasurementParticlesMap; ///< Input hit-particles map collection - std::size_t outputPrecision = 6; ///< floating point precision - std::size_t nMeasurementsMin = 7; ///< Min number of measurements - bool onlyTruthMatched = false; ///< Only write truth matched tracks - double truthMatchProbMin = 0.5; ///< Probability threshold for fake tracks - double ptMin = 1_GeV; ///< Min pt of tracks + /// Input track collection + std::string inputTracks; + /// where to place output files + std::string outputDir; + /// name of the output files + std::string fileName = "CKFtracks.csv"; + /// Input hit-particles map collection + std::string inputMeasurementParticlesMap; + /// floating point precision + std::size_t outputPrecision = 6; + /// Min number of measurements + std::size_t nMeasurementsMin = 7; + /// Only write truth matched tracks + bool onlyTruthMatched = false; + /// Probability threshold for fake tracks + double truthMatchProbMin = 0.5; + /// Min pt of tracks + double ptMin = 1 * Acts::UnitConstants::GeV; }; /// constructor @@ -75,9 +77,10 @@ class CsvTrackWriter : public WriterT { const ConstTrackContainer& tracks) override; private: - Config m_cfg; //!< Nested configuration struct + /// Nested configuration struct + Config m_cfg; - ReadDataHandle m_inputMeasurementParticlesMap{ + ReadDataHandle m_inputMeasurementParticlesMap{ this, "InputMeasurementParticlesMap"}; /// @brief Struct for brief trajectory summary info @@ -91,7 +94,7 @@ class CsvTrackWriter : public WriterT { double truthMatchProb = 0; std::optional fittedParameters; std::vector measurementsID; - }; // TrackInfo struct + }; }; } // namespace ActsExamples diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackParameterWriter.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackParameterWriter.hpp index 9f5b2fa2c42..22540f90399 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackParameterWriter.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackParameterWriter.hpp @@ -9,7 +9,7 @@ #pragma once #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/Index.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/ProtoTrack.hpp" #include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/EventData/SimParticle.hpp" @@ -25,16 +25,14 @@ class TFile; class TTree; namespace ActsExamples { -struct AlgorithmContext; - -using TrackParameterWriter = WriterT; /// Write out the track parameters from both simulation and those estimated from /// reconstructed seeds into a TTree /// /// Each entry in the TTree corresponds to one seed for optimum writing /// speed. The event number is part of the written data. -class RootTrackParameterWriter final : public TrackParameterWriter { +class RootTrackParameterWriter final + : public WriterT { public: struct Config { /// Input estimated track parameters collection. @@ -87,9 +85,9 @@ class RootTrackParameterWriter final : public TrackParameterWriter { "InputProtoTracks"}; ReadDataHandle m_inputParticles{this, "InputParticles"}; ReadDataHandle m_inputSimHits{this, "InputSimHits"}; - ReadDataHandle m_inputMeasurementParticlesMap{ + ReadDataHandle m_inputMeasurementParticlesMap{ this, "InputMeasurementParticlesMap"}; - ReadDataHandle m_inputMeasurementSimHitsMap{ + ReadDataHandle m_inputMeasurementSimHitsMap{ this, "InputMeasurementSimHitsMap"}; /// Mutex used to protect multi-threaded writes diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackStatesWriter.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackStatesWriter.hpp index bacb794113e..73da1c56e48 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackStatesWriter.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootTrackStatesWriter.hpp @@ -9,7 +9,7 @@ #pragma once #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/Index.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/EventData/Track.hpp" @@ -26,12 +26,8 @@ class TFile; class TTree; -namespace ActsFatras { -class Barcode; -} // namespace ActsFatras namespace ActsExamples { -struct AlgorithmContext; /// @class RootTrackStatesWriter /// @@ -108,7 +104,7 @@ class RootTrackStatesWriter final : public WriterT { ReadDataHandle m_inputTrackParticleMatching{ this, "InputTrackParticleMatching"}; ReadDataHandle m_inputSimHits{this, "InputSimHits"}; - ReadDataHandle m_inputMeasurementSimHitsMap{ + ReadDataHandle m_inputMeasurementSimHitsMap{ this, "InputMeasurementSimHitsMap"}; /// Mutex used to protect multi-threaded writes diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/SeedingPerformanceWriter.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/SeedingPerformanceWriter.hpp index 49006631b92..b85ab90a0ca 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/SeedingPerformanceWriter.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/SeedingPerformanceWriter.hpp @@ -9,7 +9,7 @@ #pragma once #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/SimHit.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/EventData/SimSeed.hpp" #include "ActsExamples/Framework/DataHandle.hpp" @@ -24,12 +24,8 @@ class TFile; class TTree; -namespace ActsFatras { -class Barcode; -} // namespace ActsFatras namespace ActsExamples { -struct AlgorithmContext; class SeedingPerformanceWriter final : public WriterT { public: @@ -84,7 +80,7 @@ class SeedingPerformanceWriter final : public WriterT { std::size_t m_nTotalDuplicatedParticles = 0; ReadDataHandle m_inputParticles{this, "InputParticles"}; - ReadDataHandle m_inputMeasurementParticlesMap{ + ReadDataHandle m_inputMeasurementParticlesMap{ this, "InputMeasurementParticlesMaps"}; }; diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/TrackFinderNTupleWriter.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/TrackFinderNTupleWriter.hpp index 799471be784..fc090773b98 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/TrackFinderNTupleWriter.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/TrackFinderNTupleWriter.hpp @@ -18,7 +18,6 @@ #include namespace ActsExamples { -struct AlgorithmContext; /// Write track finder performance measures. /// @@ -31,8 +30,8 @@ class TrackFinderNTupleWriter final : public WriterT { std::string inputTracks; /// Input particles collection. std::string inputParticles; - /// Input hit-particles map collection. - std::string inputMeasurementParticlesMap; + /// Input particle-measurements map collection. + std::string inputParticleMeasurementsMap; /// Input proto track-particle matching. std::string inputTrackParticleMatching; /// Output filename. diff --git a/Examples/Io/Root/src/RootTrackParameterWriter.cpp b/Examples/Io/Root/src/RootTrackParameterWriter.cpp index a4e74a44e2c..520a875f1a8 100644 --- a/Examples/Io/Root/src/RootTrackParameterWriter.cpp +++ b/Examples/Io/Root/src/RootTrackParameterWriter.cpp @@ -18,16 +18,13 @@ #include "ActsExamples/Validation/TrackClassification.hpp" #include "ActsFatras/EventData/Barcode.hpp" #include "ActsFatras/EventData/Hit.hpp" -#include "ActsFatras/EventData/Particle.hpp" #include #include #include #include -#include #include #include -#include #include #include @@ -43,8 +40,8 @@ namespace ActsExamples { RootTrackParameterWriter::RootTrackParameterWriter( const RootTrackParameterWriter::Config& config, Acts::Logging::Level level) - : TrackParameterWriter(config.inputTrackParameters, - "RootTrackParameterWriter", level), + : WriterT(config.inputTrackParameters, + "RootTrackParameterWriter", level), m_cfg(config) { if (m_cfg.inputProtoTracks.empty()) { throw std::invalid_argument("Missing proto tracks input collection"); diff --git a/Examples/Io/Root/src/TrackFinderNTupleWriter.cpp b/Examples/Io/Root/src/TrackFinderNTupleWriter.cpp index 3ddfc2b6f84..4b9aa94b4d0 100644 --- a/Examples/Io/Root/src/TrackFinderNTupleWriter.cpp +++ b/Examples/Io/Root/src/TrackFinderNTupleWriter.cpp @@ -9,8 +9,7 @@ #include "ActsExamples/Io/Root/TrackFinderNTupleWriter.hpp" #include "Acts/Definitions/Units.hpp" -#include "ActsExamples/EventData/Index.hpp" -#include "ActsExamples/EventData/SimHit.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/EventData/TruthMatching.hpp" #include "ActsExamples/Framework/AlgorithmContext.hpp" @@ -18,7 +17,6 @@ #include "ActsExamples/Utilities/Range.hpp" #include "ActsExamples/Validation/TrackClassification.hpp" #include "ActsFatras/EventData/Barcode.hpp" -#include "ActsFatras/EventData/Particle.hpp" #include #include @@ -32,11 +30,13 @@ #include #include -struct ActsExamples::TrackFinderNTupleWriter::Impl { +namespace ActsExamples { + +struct TrackFinderNTupleWriter::Impl { Config cfg; ReadDataHandle inputParticles; - ReadDataHandle inputMeasurementParticlesMap; + ReadDataHandle inputParticleMeasurementsMap; ReadDataHandle inputTrackParticleMatching; TFile* file = nullptr; @@ -78,7 +78,7 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { // particle charge in e float prtQ = 0; // particle reconstruction - UShort_t prtNumHits = 0; // number of hits for this particle + UShort_t prtNumMeasurements = 0; // number of hits for this particle UShort_t prtNumTracks = 0; // number of tracks this particle was reconstructed in UShort_t prtNumTracksMajority = @@ -89,7 +89,7 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { Impl(TrackFinderNTupleWriter* parent, Config&& c, const Acts::Logger& l) : cfg(std::move(c)), inputParticles{parent, "InputParticles"}, - inputMeasurementParticlesMap{parent, "InputMeasurementParticlesMap"}, + inputParticleMeasurementsMap{parent, "InputParticleMeasurementsMap"}, inputTrackParticleMatching{parent, "InputTrackParticleMatching"}, _logger(l) { if (cfg.inputTracks.empty()) { @@ -98,8 +98,9 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { if (cfg.inputParticles.empty()) { throw std::invalid_argument("Missing particles input collection"); } - if (cfg.inputMeasurementParticlesMap.empty()) { - throw std::invalid_argument("Missing hit-particles map input collection"); + if (cfg.inputParticleMeasurementsMap.empty()) { + throw std::invalid_argument( + "Missing particle-measurements map input collection"); } if (cfg.inputTrackParticleMatching.empty()) { throw std::invalid_argument( @@ -110,7 +111,7 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { } inputParticles.initialize(cfg.inputParticles); - inputMeasurementParticlesMap.initialize(cfg.inputMeasurementParticlesMap); + inputParticleMeasurementsMap.initialize(cfg.inputParticleMeasurementsMap); inputTrackParticleMatching.initialize(cfg.inputTrackParticleMatching); // the output file can not be given externally since TFile accesses to the @@ -146,7 +147,7 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { prtTree->Branch("pz", &prtPz); prtTree->Branch("m", &prtM); prtTree->Branch("q", &prtQ); - prtTree->Branch("nhits", &prtNumHits); + prtTree->Branch("nhits", &prtNumMeasurements); prtTree->Branch("ntracks", &prtNumTracks); prtTree->Branch("ntracks_majority", &prtNumTracksMajority); } @@ -155,10 +156,8 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { void write(std::uint64_t eventId, const ConstTrackContainer& tracks, const SimParticleContainer& particles, - const HitParticlesMap& hitParticlesMap, + const ParticleMeasurementsMap& particleMeasurementsMap, const TrackParticleMatching& trackParticleMatching) { - const auto& particleHitsMap = invertIndexMultimap(hitParticlesMap); - // How often a particle was reconstructed. std::unordered_map reconCount; reconCount.reserve(particles.size()); @@ -209,8 +208,8 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { for (const auto& phc : particleMatch.contributingParticles) { trkParticleId.push_back(phc.particleId.value()); // count total number of hits for this particle - auto trueParticleHits = - makeRange(particleHitsMap.equal_range(phc.particleId.value())); + auto trueParticleHits = makeRange( + particleMeasurementsMap.equal_range(phc.particleId.value())); trkParticleNumHitsTotal.push_back(trueParticleHits.size()); trkParticleNumHitsOnTrack.push_back(phc.hitCount); } @@ -223,9 +222,9 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { { std::lock_guard guardPrt(trkMutex); for (const auto& particle : particles) { - // find all hits for this particle - auto hits = - makeRange(particleHitsMap.equal_range(particle.particleId())); + // find all measurements for this particle + auto measurements = makeRange( + particleMeasurementsMap.equal_range(particle.particleId())); // identification prtEventId = eventId; @@ -243,7 +242,7 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { prtM = particle.mass() / Acts::UnitConstants::GeV; prtQ = particle.charge() / Acts::UnitConstants::e; // reconstruction - prtNumHits = hits.size(); + prtNumMeasurements = measurements.size(); auto nt = reconCount.find(particle.particleId()); prtNumTracks = (nt != reconCount.end()) ? nt->second : 0u; auto nm = majorityCount.find(particle.particleId()); @@ -265,31 +264,31 @@ struct ActsExamples::TrackFinderNTupleWriter::Impl { } }; -ActsExamples::TrackFinderNTupleWriter::TrackFinderNTupleWriter( - ActsExamples::TrackFinderNTupleWriter::Config config, - Acts::Logging::Level level) +TrackFinderNTupleWriter::TrackFinderNTupleWriter( + TrackFinderNTupleWriter::Config config, Acts::Logging::Level level) : WriterT(config.inputTracks, "TrackFinderNTupleWriter", level), m_impl(std::make_unique(this, std::move(config), logger())) {} -ActsExamples::TrackFinderNTupleWriter::~TrackFinderNTupleWriter() = default; +TrackFinderNTupleWriter::~TrackFinderNTupleWriter() = default; -ActsExamples::ProcessCode ActsExamples::TrackFinderNTupleWriter::writeT( - const ActsExamples::AlgorithmContext& ctx, - const ActsExamples::ConstTrackContainer& tracks) { +ProcessCode TrackFinderNTupleWriter::writeT(const AlgorithmContext& ctx, + const ConstTrackContainer& tracks) { const auto& particles = m_impl->inputParticles(ctx); - const auto& hitParticlesMap = m_impl->inputMeasurementParticlesMap(ctx); + const auto& particleMeasurementsMap = + m_impl->inputParticleMeasurementsMap(ctx); const auto& trackParticleMatching = m_impl->inputTrackParticleMatching(ctx); - m_impl->write(ctx.eventNumber, tracks, particles, hitParticlesMap, + m_impl->write(ctx.eventNumber, tracks, particles, particleMeasurementsMap, trackParticleMatching); return ProcessCode::SUCCESS; } -ActsExamples::ProcessCode ActsExamples::TrackFinderNTupleWriter::finalize() { +ProcessCode TrackFinderNTupleWriter::finalize() { m_impl->close(); return ProcessCode::SUCCESS; } -const ActsExamples::TrackFinderNTupleWriter::Config& -ActsExamples::TrackFinderNTupleWriter::config() const { +const TrackFinderNTupleWriter::Config& TrackFinderNTupleWriter::config() const { return m_impl->cfg; } + +} // namespace ActsExamples diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index a365572cabc..ceb234f1419 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -580,7 +580,7 @@ def addTruthSmearedSeeding( truthTrkFndAlg = acts.examples.TruthTrackFinder( level=logLevel, inputParticles=selectedParticles, - inputMeasurementParticlesMap="measurement_particles_map", + inputParticleMeasurementsMap="particle_measurements_map", outputProtoTracks="truth_particle_tracks", ) s.addAlgorithm(truthTrkFndAlg) @@ -601,7 +601,7 @@ def addTruthEstimatedSeeding( truthSeeding = acts.examples.TruthSeedingAlgorithm( level=logLevel, inputParticles=inputParticles, - inputMeasurementParticlesMap="measurement_particles_map", + inputParticleMeasurementsMap="particle_measurements_map", inputSpacePoints=[spacePoints], outputParticles="truth_seeded_particles", outputProtoTracks="truth_particle_tracks", @@ -1857,7 +1857,7 @@ def addExaTrkX( inputProtoTracks=findingAlg.config.outputProtoTracks, # the original selected particles after digitization inputParticles="particles_initial", - inputMeasurementParticlesMap="measurement_particles_map", + inputParticleMeasurementsMap="particle_measurements_map", inputTrackParticleMatching=matchAlg.config.outputTrackParticleMatching, filePath=str(Path(outputDirRoot) / "performance_track_finding.root"), ) diff --git a/Examples/Python/python/acts/examples/simulation.py b/Examples/Python/python/acts/examples/simulation.py index d076a79b30b..411f4410361 100644 --- a/Examples/Python/python/acts/examples/simulation.py +++ b/Examples/Python/python/acts/examples/simulation.py @@ -811,6 +811,8 @@ def addDigitization( outputMeasurements="measurements", outputMeasurementParticlesMap="measurement_particles_map", outputMeasurementSimHitsMap="measurement_simhits_map", + outputParticleMeasurementsMap="particle_measurements_map", + outputSimHitMeasurementsMap="simhit_measurements_map", **acts.examples.defaultKWArgs( doMerge=doMerge, ), diff --git a/Examples/Python/src/Digitization.cpp b/Examples/Python/src/Digitization.cpp index 5bcce4b74ca..cfafb7bf627 100644 --- a/Examples/Python/src/Digitization.cpp +++ b/Examples/Python/src/Digitization.cpp @@ -55,6 +55,8 @@ void addDigitization(Context& ctx) { ACTS_PYTHON_MEMBER(outputClusters); ACTS_PYTHON_MEMBER(outputMeasurementParticlesMap); ACTS_PYTHON_MEMBER(outputMeasurementSimHitsMap); + ACTS_PYTHON_MEMBER(outputParticleMeasurementsMap); + ACTS_PYTHON_MEMBER(outputSimHitMeasurementsMap); ACTS_PYTHON_MEMBER(surfaceByIdentifier); ACTS_PYTHON_MEMBER(randomNumbers); ACTS_PYTHON_MEMBER(doOutputCells); diff --git a/Examples/Python/src/Output.cpp b/Examples/Python/src/Output.cpp index 251ca2b6357..066d704d964 100644 --- a/Examples/Python/src/Output.cpp +++ b/Examples/Python/src/Output.cpp @@ -6,13 +6,10 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -#include "Acts/Definitions/TrackParametrization.hpp" -#include "Acts/Geometry/GeometryHierarchyMap.hpp" #include "Acts/Plugins/Python/Utilities.hpp" #include "Acts/Utilities/Logger.hpp" #include "Acts/Visualization/IVisualization3D.hpp" #include "Acts/Visualization/ViewConfig.hpp" -#include "ActsExamples/Digitization/DigitizationAlgorithm.hpp" #include "ActsExamples/Io/Csv/CsvBFieldWriter.hpp" #include "ActsExamples/Io/Csv/CsvExaTrkXGraphWriter.hpp" #include "ActsExamples/Io/Csv/CsvMeasurementWriter.hpp" @@ -206,7 +203,7 @@ void addOutput(Context& ctx) { ACTS_PYTHON_DECLARE_WRITER(ActsExamples::TrackFinderNTupleWriter, mex, "TrackFinderNTupleWriter", inputTracks, - inputParticles, inputMeasurementParticlesMap, + inputParticles, inputParticleMeasurementsMap, inputTrackParticleMatching, filePath, fileMode, treeNameTracks, treeNameParticles); diff --git a/Examples/Python/src/TruthTracking.cpp b/Examples/Python/src/TruthTracking.cpp index 319ee24e2ee..4799d93dbdb 100644 --- a/Examples/Python/src/TruthTracking.cpp +++ b/Examples/Python/src/TruthTracking.cpp @@ -40,7 +40,7 @@ void addTruthTracking(Context& ctx) { ACTS_PYTHON_DECLARE_ALGORITHM( ActsExamples::TruthTrackFinder, mex, "TruthTrackFinder", inputParticles, - inputMeasurementParticlesMap, outputProtoTracks); + inputParticleMeasurementsMap, outputProtoTracks); ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::ParticleTrackParamExtractor, mex, "ParticleTrackParamExtractor", inputParticles, @@ -159,7 +159,7 @@ void addTruthTracking(Context& ctx) { ACTS_PYTHON_DECLARE_ALGORITHM( ActsExamples::TruthSeedingAlgorithm, mex, "TruthSeedingAlgorithm", - inputParticles, inputMeasurementParticlesMap, inputSpacePoints, + inputParticles, inputParticleMeasurementsMap, inputSpacePoints, outputParticles, outputSeeds, outputProtoTracks, deltaRMin, deltaRMax); ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::HitSelector, mex, "HitSelector", From a646e0655aa429c3a8ef79d11a0ed5b2f335ce0a Mon Sep 17 00:00:00 2001 From: Andreas Salzburger Date: Fri, 6 Dec 2024 01:12:12 +0100 Subject: [PATCH 4/5] feat: move spline to helper in Visualization (#3950) This PR moves the spline-interpolation from `Examples/Plugins/Obj` to `Core/Visualization` because it can be used in other visualisation areas as well. I've added also a UnitTest since this is from some `experimetnal/usupported` Eigen directory, but it works nicely! --- .../Acts/Visualization/Interpolation3D.hpp | 96 +++++++++++++++++++ .../ActsExamples/Io/Obj/ObjSimHitWriter.hpp | 10 +- Examples/Io/Obj/src/ObjSimHitWriter.cpp | 60 +++--------- Examples/Python/src/Output.cpp | 4 +- .../Core/Visualization/CMakeLists.txt | 1 + .../Visualization/Interpolation3DTests.cpp | 92 ++++++++++++++++++ 6 files changed, 211 insertions(+), 52 deletions(-) create mode 100644 Core/include/Acts/Visualization/Interpolation3D.hpp create mode 100644 Tests/UnitTests/Core/Visualization/Interpolation3DTests.cpp diff --git a/Core/include/Acts/Visualization/Interpolation3D.hpp b/Core/include/Acts/Visualization/Interpolation3D.hpp new file mode 100644 index 00000000000..ea8e649869e --- /dev/null +++ b/Core/include/Acts/Visualization/Interpolation3D.hpp @@ -0,0 +1,96 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/Definitions/Algebra.hpp" + +#include + +namespace Acts::Interpolation3D { + +/// @brief Helper function to interpolate points using a spline +/// from Eigen +/// +/// The only requirement is that the input trajectory type has +/// a method empty() and size() and that the elements can be +/// accessed with operator[] and have themselves a operator[] to +/// access the coordinates. +/// +/// @tparam input_trajectory_type input trajectory type +/// +/// @param inputsRaw input vector points +/// @param nPoints number of interpolation points +/// @param keepOriginalHits keep the original hits in the trajectory +/// +/// @return std::vector interpolated points +template +trajectory_type spline(const trajectory_type& inputsRaw, std::size_t nPoints, + bool keepOriginalHits = false) { + trajectory_type output; + if (inputsRaw.empty()) { + return output; + } + + using InputVectorType = typename trajectory_type::value_type; + + std::vector inputs; + // If input type is a vector of Vector3 we can use it directly + if constexpr (std::is_same_v>) { + inputs = inputsRaw; + } else { + inputs.reserve(inputsRaw.size()); + for (const auto& input : inputsRaw) { + inputs.push_back(Vector3(input[0], input[1], input[2])); + } + } + + // Don't do anything if we have less than 3 points or less interpolation + // points than input points + if (inputsRaw.size() < 3 || nPoints <= inputsRaw.size()) { + return inputsRaw; + } else { + Eigen::MatrixXd points(3, inputs.size()); + for (std::size_t i = 0; i < inputs.size(); ++i) { + points.col(i) = inputs[i].transpose(); + } + Eigen::Spline spline3D = + Eigen::SplineFitting>::Interpolate(points, 2); + + double step = 1. / (nPoints - 1); + for (std::size_t i = 0; i < nPoints; ++i) { + double t = i * step; + InputVectorType point; + point[0] = spline3D(t)[0]; + point[1] = spline3D(t)[1]; + point[2] = spline3D(t)[2]; + output.push_back(point); + } + } + // If we want to keep the original hits, we add them to the output + // (first and last are there anyway) + if (keepOriginalHits) { + output.insert(output.begin(), inputsRaw.begin() + 1, inputsRaw.end() - 1); + // We need to sort the output in distance to first + std::sort(output.begin(), output.end(), + [&inputs](const auto& a, const auto& b) { + const auto ifront = inputs.front(); + double da2 = (a[0] - ifront[0]) * (a[0] - ifront[0]) + + (a[1] - ifront[1]) * (a[1] - ifront[1]) + + (a[2] - ifront[2]) * (a[2] - ifront[2]); + double db2 = (b[0] - ifront[0]) * (b[0] - ifront[0]) + + (b[1] - ifront[1]) * (b[1] - ifront[1]) + + (b[2] - ifront[2]) * (b[2] - ifront[2]); + return da2 < db2; + }); + } + + return output; +} + +} // namespace Acts::Interpolation3D diff --git a/Examples/Io/Obj/include/ActsExamples/Io/Obj/ObjSimHitWriter.hpp b/Examples/Io/Obj/include/ActsExamples/Io/Obj/ObjSimHitWriter.hpp index 8ccd92645b9..b510bd3c035 100644 --- a/Examples/Io/Obj/include/ActsExamples/Io/Obj/ObjSimHitWriter.hpp +++ b/Examples/Io/Obj/include/ActsExamples/Io/Obj/ObjSimHitWriter.hpp @@ -32,6 +32,9 @@ struct AlgorithmContext; /// event000000002-.obj /// event000000002-_trajectory.obj /// +/// +/// The trajectory can be smoothed using a spline interpolation, where +/// nInterpolatedPoints points are added between each hit. class ObjSimHitWriter : public WriterT { public: struct Config { @@ -49,8 +52,11 @@ class ObjSimHitWriter : public WriterT { double momentumThreshold = 0.05 * Acts::UnitConstants::GeV; /// Momentum threshold for trajectories double momentumThresholdTraj = 0.05 * Acts::UnitConstants::GeV; - /// Number of points to interpolate between hits - std::size_t nInterpolatedPoints = 10; + /// Number of points to interpolated between hits to smooth the + /// trajectory view in the obj file. + std::size_t nInterpolatedPoints = 4; + /// Keep the original hits in the trajectory file + bool keepOriginalHits = false; }; /// Construct the particle writer. diff --git a/Examples/Io/Obj/src/ObjSimHitWriter.cpp b/Examples/Io/Obj/src/ObjSimHitWriter.cpp index 564ff9c5409..e5552b627e2 100644 --- a/Examples/Io/Obj/src/ObjSimHitWriter.cpp +++ b/Examples/Io/Obj/src/ObjSimHitWriter.cpp @@ -11,6 +11,7 @@ #include "Acts/Definitions/Algebra.hpp" #include "Acts/Definitions/Common.hpp" #include "Acts/Geometry/GeometryIdentifier.hpp" +#include "Acts/Visualization/Interpolation3D.hpp" #include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/Framework/AlgorithmContext.hpp" #include "ActsExamples/Utilities/Paths.hpp" @@ -22,47 +23,6 @@ #include #include -#include - -namespace { - -/// @brief Helper function to interpolate points -/// -/// @tparam input_vector_type -/// @param inputs input vector points -/// @param nPoints number of interpolation points -/// -/// @return std::vector interpolated points -template -std::vector interpolatedPoints( - const std::vector& inputs, std::size_t nPoints) { - std::vector output; - - if (nPoints < 2) { - // No interpolation done return simply the output vector - for (const auto& input : inputs) { - output.push_back(input.template head<3>()); - } - - } else { - Eigen::MatrixXd points(3, inputs.size()); - for (std::size_t i = 0; i < inputs.size(); ++i) { - points.col(i) = inputs[i].template head<3>().transpose(); - } - Eigen::Spline spline3D = - Eigen::SplineFitting>::Interpolate(points, 2); - - double step = 1. / (nPoints - 1); - for (std::size_t i = 0; i < nPoints; ++i) { - double t = i * step; - output.push_back(spline3D(t)); - } - } - return output; -} - -} // namespace - ActsExamples::ObjSimHitWriter::ObjSimHitWriter( const ActsExamples::ObjSimHitWriter::Config& config, Acts::Logging::Level level) @@ -152,15 +112,17 @@ ActsExamples::ProcessCode ActsExamples::ObjSimHitWriter::writeT( } osHits << '\n'; - // Interpolate the points - std::vector trajectory; - if (pHits.size() < 3) { - for (const auto& hit : pHits) { - trajectory.push_back(hit.template head<3>()); - } + // Interpolate the points, a minimum number of 3 hits is necessary for + // that + std::vector trajectory; + if (pHits.size() < 3 || m_cfg.nInterpolatedPoints == 0) { + trajectory = pHits; } else { - trajectory = - interpolatedPoints(pHits, pHits.size() * m_cfg.nInterpolatedPoints); + // The total number of points is the number of hits times the number of + // interpolated points plus the number of hits + trajectory = Acts::Interpolation3D::spline( + pHits, pHits.size() * (m_cfg.nInterpolatedPoints + 1) - 1, + m_cfg.keepOriginalHits); } osTrajectory << "o particle_trajectory_" << pId << std::endl; diff --git a/Examples/Python/src/Output.cpp b/Examples/Python/src/Output.cpp index 066d704d964..41f7bb5f564 100644 --- a/Examples/Python/src/Output.cpp +++ b/Examples/Python/src/Output.cpp @@ -111,7 +111,9 @@ void addOutput(Context& ctx) { ACTS_PYTHON_DECLARE_WRITER(ActsExamples::ObjSimHitWriter, mex, "ObjSimHitWriter", inputSimHits, outputDir, - outputStem, outputPrecision, drawConnections); + outputStem, outputPrecision, drawConnections, + momentumThreshold, momentumThresholdTraj, + nInterpolatedPoints, keepOriginalHits); { auto c = py::class_(m, "ViewConfig").def(py::init<>()); diff --git a/Tests/UnitTests/Core/Visualization/CMakeLists.txt b/Tests/UnitTests/Core/Visualization/CMakeLists.txt index ca6768e876b..97d76217917 100644 --- a/Tests/UnitTests/Core/Visualization/CMakeLists.txt +++ b/Tests/UnitTests/Core/Visualization/CMakeLists.txt @@ -1,5 +1,6 @@ add_unittest(Visualization3D Visualization3DTests.cpp) add_unittest(EventDataView3D EventDataView3DTests.cpp) +add_unittest(Interpolation3D Interpolation3DTests.cpp) add_unittest(PrimitivesView3D PrimitivesView3DTests.cpp) add_unittest(SurfaceView3D SurfaceView3DTests.cpp) add_unittest(TrackingGeometryView3D TrackingGeometryView3DTests.cpp) diff --git a/Tests/UnitTests/Core/Visualization/Interpolation3DTests.cpp b/Tests/UnitTests/Core/Visualization/Interpolation3DTests.cpp new file mode 100644 index 00000000000..ba19c55b03f --- /dev/null +++ b/Tests/UnitTests/Core/Visualization/Interpolation3DTests.cpp @@ -0,0 +1,92 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include + +#include "Acts/Tests/CommonHelpers/FloatComparisons.hpp" +#include "Acts/Visualization/Interpolation3D.hpp" + +#include + +namespace Acts::Test { + +BOOST_AUTO_TEST_SUITE(Visualization) + +BOOST_AUTO_TEST_CASE(SplineInterpolationEigen) { + /// Define the input vector + double R = 10.; + std::vector inputs; + + // Interpolate the points options + std::vector trajectory; + + // Empty in empty out check + trajectory = Acts::Interpolation3D::spline(inputs, 10); + BOOST_CHECK(trajectory.empty()); + + for (double phi = 0; phi < 2 * std::numbers::pi; + phi += std::numbers::pi / 4) { + inputs.push_back(Acts::Vector3(R * cos(phi), R * sin(phi), 0.)); + } + + // (0) - nothing happens + trajectory = Acts::Interpolation3D::spline(inputs, 1); + // Check input and output size are the same + BOOST_CHECK_EQUAL(trajectory.size(), inputs.size()); + + // (1) - interpolate between the points with 12 points in total + trajectory = Acts::Interpolation3D::spline(inputs, 12); + // Check the output size is correct + BOOST_CHECK_EQUAL(trajectory.size(), 12); + + for (const auto& point : trajectory) { + // Check the interpolated points are on the circle + // with a tolerance of course + CHECK_CLOSE_ABS(point.norm(), R, 0.1); + // Verify points remain in the XY plane + CHECK_CLOSE_ABS(point.z(), 0., 0.1); + } +} + +BOOST_AUTO_TEST_CASE(SplineInterpolationArray) { + /// Define the input vector + std::vector> inputs; + + for (double x = 0; x < 10; x += 1) { + inputs.push_back({x, x * x, 0.}); + } + + // This time we keep the original hits + auto trajectory = Acts::Interpolation3D::spline(inputs, 100, true); + + // Check the outpu type is correct + constexpr bool isOutput = + std::is_same_v; + BOOST_CHECK(isOutput); + + // Check the output size is correct + BOOST_CHECK_EQUAL(trajectory.size(), 108); +} + +BOOST_AUTO_TEST_CASE(SplineInterpolationErrors) { + std::vector> inputs; + + // Test with single point + inputs.push_back({0., 0., 0.}); + auto result = Acts::Interpolation3D::spline(inputs, 10); + BOOST_CHECK_EQUAL(result.size(), 1); + + // Test with two points + inputs.push_back({1., 1., 1.}); + result = Acts::Interpolation3D::spline(inputs, 10); + BOOST_CHECK_EQUAL(result.size(), 2); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace Acts::Test From f57e260aaa32eb68bba3e8b71fa960cb79ab1417 Mon Sep 17 00:00:00 2001 From: Corentin ALLAIRE <62873125+Corentin-Allaire@users.noreply.github.com> Date: Fri, 6 Dec 2024 02:27:07 +0100 Subject: [PATCH 5/5] refactor: Move MLAmbiguitySolver to Core (#3272) This PR moves the MLAmbiguitySolver to Core, this will allow us to test it more easily with ATLAS in the future. It also removes the DBScan version of this algorithm as it was shown to be way less effective. ## Summary by CodeRabbit - **New Features** - Enhanced performance monitoring capabilities with the addition of machine learning-based metrics. - Integrated a new machine learning function for ambiguity resolution in track finding workflows. - Introduced a new `TrackTruthMatcher` algorithm to improve track validation. - **Bug Fixes** - Corrected naming conventions for the `CsvSpacePointWriter` class and its methods. - **Chores** - Removed deprecated configurations and algorithms to streamline the codebase. - Updated header inclusions to reflect new naming conventions and class structures. --- CI/physmon/phys_perf_mon.sh | 9 ++ .../performance_finding_ckf_ml_solver.root | Bin 0 -> 28287 bytes .../performance_fitting_ckf_ml_solver.root | Bin 0 -> 169787 bytes .../physmon_trackfinding_ttbar_pu200.py | 27 ++++ .../AmbiguityNetworkConcept.hpp | 51 +++++++ .../AmbiguityResolutionML.hpp | 136 ++++++++++++++++++ .../detail/AmbiguityTrackClustering.hpp | 10 +- .../TrackFinding/AmbiguityTrackClustering.cpp | 5 +- .../Algorithms/TrackFindingML/CMakeLists.txt | 2 - .../AmbiguityDBScanClustering.hpp | 79 ---------- .../TrackFindingML/AmbiguityResolutionML.hpp | 48 ------- .../AmbiguityResolutionMLAlgorithm.hpp | 17 ++- .../AmbiguityResolutionMLDBScanAlgorithm.hpp | 68 --------- .../TrackFindingML/SeedFilterMLAlgorithm.hpp | 2 +- .../src/AmbiguityResolutionML.cpp | 76 ---------- .../src/AmbiguityResolutionMLAlgorithm.cpp | 43 +++++- .../AmbiguityResolutionMLDBScanAlgorithm.cpp | 55 ------- ...ointWriter.hpp => CsvSpacePointWriter.hpp} | 6 +- Examples/Io/Csv/src/CsvSpacePointWriter.cpp | 14 +- .../python/acts/examples/reconstruction.py | 101 ++++++------- Examples/Python/src/Onnx.cpp | 6 - Examples/Python/src/Output.cpp | 6 +- .../ambiguity_solver_network.py | 2 +- .../Plugins/Onnx/AmbiguityTrackClassifier.hpp | 17 --- 24 files changed, 339 insertions(+), 441 deletions(-) create mode 100644 CI/physmon/reference/trackfinding_ttbar_pu200/performance_finding_ckf_ml_solver.root create mode 100644 CI/physmon/reference/trackfinding_ttbar_pu200/performance_fitting_ckf_ml_solver.root create mode 100644 Core/include/Acts/AmbiguityResolution/AmbiguityNetworkConcept.hpp create mode 100644 Core/include/Acts/AmbiguityResolution/AmbiguityResolutionML.hpp delete mode 100644 Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityDBScanClustering.hpp delete mode 100644 Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp delete mode 100644 Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLDBScanAlgorithm.hpp delete mode 100644 Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionML.cpp delete mode 100644 Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLDBScanAlgorithm.cpp rename Examples/Io/Csv/include/ActsExamples/Io/Csv/{CsvSpacepointWriter.hpp => CsvSpacePointWriter.hpp} (93%) diff --git a/CI/physmon/phys_perf_mon.sh b/CI/physmon/phys_perf_mon.sh index c2944f0dd06..8482f92c4b6 100755 --- a/CI/physmon/phys_perf_mon.sh +++ b/CI/physmon/phys_perf_mon.sh @@ -265,6 +265,15 @@ function trackfinding() { $path/performance_finding_ckf_ambi.html \ $path/performance_finding_ckf_ambi fi + + if [ -f $refdir/$path/performance_finding_ckf_ml_solver.root ]; then + run_histcmp \ + $outdir/data/$path/performance_finding_ckf_ml_solver.root \ + $refdir/$path/performance_finding_ckf_ml_solver.root \ + "ML Ambisolver | ${name}" \ + $path/performance_finding_ckf_ml_solver.html \ + $path/performance_finding_ckf_ml_solver + fi } function vertexing() { diff --git a/CI/physmon/reference/trackfinding_ttbar_pu200/performance_finding_ckf_ml_solver.root b/CI/physmon/reference/trackfinding_ttbar_pu200/performance_finding_ckf_ml_solver.root new file mode 100644 index 0000000000000000000000000000000000000000..db22a916d9e2e307133652a03777039151c07eda GIT binary patch literal 28287 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%0AmgXq8r> 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

gcS!*XG4UwDg*j#lb-2lEt4h zr1KpUcqI-K_ck*FSy8NXNC&aYROAO`IkO#lSfoV}aPOsH_dmYq*dr_`(=p*6ICIrW zpyMOti_n`G6a-pX)Xi}lah~~^-#u8>Wn4xK>e3A`IeQ({kIjrm@@d>|SV+#!YAjTQ z30?y)s!z#_H?VdTJvzPzFJDak>b3mBMIfI^aa-Qu?Cl=zA5N5M~ima`XZ6=tTEZf&`Lg9naN-8H62W7f%OWW zRKn4S-ESwgC!%<9w{h(f)?j8DQh&XME7w&*@R^jy*4(V_`WcIMx{+=esr@8iREI%#GOkkvUiEs%ANNu%4(H` z>S%2Q8mrsdt5sf|mB@sSnL97Q@6Y)*Yh%8Q%?Sw?;n!JLmN44t^v#OYPJ>NKU) zQ~ZXs(_4+3j)Ua`O~DaMg3mCcO+W;NsX*y!kKm(r&D?p!Me*mDz?>0S-!O zII)lih>!;W516$&&#WzcjuoEUUQNJ@-G8&UXn$m__NoN`SlmC7)@QcDLO~S%6(h(h zfURxsBV&0Up?Ae-;MVJkuS_g2MMWS8!skQs;Pp-e6*)M_SI4S_3ySYh!mT)8_t^RG zg16)xg*P#|P*YleaWdVihMCa#B%8N;zvVZQn)EBRkLto(3@x*Xl=$;7vlIqPIJDoS za!c_9;r@VZpt6TURkH^2$Tp{%9q}LkX$NE@181by2XppZA3_L)y!i zLd{17qEWoaPO{&+q?rQZUG!d4^qF4CAcF8j&TXsNyGyy8RFv@VXK(cg5QSI0rqKE{ zREEX-CH+~|1c&Jf&1f6vFe>QOXWe!X>5mYxc=T*MoXTV-hHhAYbTQYB&%PQY z0hz_2zS4Twb>p;pjRjTKs3eO<5tvkBdqs$dz9>|9AoMjR*`>l!8;9XRo_AUZ)q9m# zY;89HcC*)u>S7dx*Sg(xIf$k@(U2M)jSaS5rEDlkn_Tb(YSP0a1?#hO1D zUi>VTK)PHlz#T{;s+{9HO6_X-38f39>X)UoGDb+n36Xo@0rkR$c(jVU=DI$;ynAd( z+2y|dlUEE&h%H%FkwxIz9rMjt8kQ&pJwUHsnJFPHAa0RapJN#nBS=K7a5Ws1e#Z|d z;PX1TMKI31okEZF{I-5Zyj;Qkon?EhFG$t$wi=3GbixeFn3f}5{Ue_Ft&vf4+{*#P z6SjBy2-eB1$@t~s>4m4B-T3tF*J-eh!Ez^uW8MK%^%lsBVbDJH%u*}q{W^Wq13-8HP6I4f&8mP zei$b!3vN{Eef(1XH9|vo-Vu-K4N~{3KuppJqj$wIxAisCw;}U!i0?lY8Z|WnGn@}4 z?6=Jn9e3gE>5nQW({LAMwg*$OvZ)MMWvMs7*2?rqj@!%;0p-q|Y_cvpNYU#vKBTnR zbvCY*R?2;4eKhF8b5q;bkiPBfx%Na#vFsd*9jGdwe59+n)H^@%{N@xtYm7i80OTp= zpUnPi?cj7-7{VAqBlB-Xqll2{ofWAq|lxJIKgM|zMfC7POZdjl7c7@-WJE}g2)$+Ty(VQu`KWSp`NSJ8v`)$(HNU6NmlHF5( zQs+?{@_RHy;c}@<#SFQ?elDm{mO>JQxa>?eMu--wDdR}-sdazH&MM^iSb|(;h?}M& zV6=%64-O5`D{*;O2B71&-GuF^5^uSN6DHPAdHwITZr$)sn8?01Lne0qNl z4R0<&tPt>hJU(&Ui_-FrqJ%0}=jn; z`hVf_VWU3Xr0#FH!cVw182=kC_TMde&tu>J7dRN=>817?IL|Zq)6@Bnn?`wh4)%tQ z&KAbtqTB!HQ7Ab){nFaMp$a~ucKvUtn12uW{uj`^(RAQO90-HophcgcZNWL?f8H?O NpHJhzzz3s1{13N1BBlTU literal 0 HcmV?d00001 diff --git a/CI/physmon/reference/trackfinding_ttbar_pu200/performance_fitting_ckf_ml_solver.root b/CI/physmon/reference/trackfinding_ttbar_pu200/performance_fitting_ckf_ml_solver.root new file mode 100644 index 0000000000000000000000000000000000000000..0a40506d1bbb157e084c011c7355e0f89d8e4e11 GIT binary patch literal 169787 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

cwG5RQ5c6~huF(+No|z*6 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 QZ)KLGu=u z#4)5fZ|sCtk`gepiuyep^O!&1t)asHK6oO`tKzuJ<3`?BbCp8t#YVB@)*u&^qc_uD z^8)v>ICV1>yeAe{-d_?F!!lf8Zip@i6zpCKQSp9n^mg%g|9HcT z45J+n2OM!}8}##n^+Ph1-5R(<9!FiULY&(J*)1NVC;78vEuN`tfq5PW(F$L|ULiB0 zZ$k@bM^eyRHin{^%Bjy@y@w)m1Kgx#lm!_#Gk5!*aW{IIw`KVK(<)nKmUwuBt(1+x z5;eI-9h1=6GQ&#;=Ht|C{+f5lWyx%+MN&=bb;T5G|BQ>4fgZHb_U`3e4Bp+)vvJ5T zW?nix$b8`jkv=c`Gb{dcbXOrG^wM>13htR&DIWKDFqxpPxi32yx^WWv*TS3LZpf7XQmIL7wmf5_ytcqP3{(C}kYYsl zAo=q`H(Xd~*il+HEn(Y`N@5|T74Fdfu734b0l&@W#VD9IG*&uLB zU^L4eprjQwzL0BoSAyH$^P9iuExG=GA2YxFH|5|z zyNhxEo;d&a=9N%%G`D{>7Gq1QD7^;QIai=g0li8;iarGuky^K00ps$;MKm;;ewWpa zEOeJIk1R|Vy{`!+mNqdkTtJtljhF-t?!JqYN}fRf8Jhn(X(6}H86u@=H158h`G9!1 z0*>AIv3$will&ehBK9Fl@!1(AMUd6NPPv)0mhwj56YJmHG9vL@j^ot1rwOXQ8S%)n zp3lK1H6DZE1JdtBC09k>bH#<>W=e{%eIZwmeM9rp6+1@IHoSo-Qbb~dVCwx(;eotJ zg1{H_Z)Y+g5tPRL7f5A%DY|3>0%k=r*{_dB-1uGwdAV%-FnK<42M8|0O2Ges%aE-2 z-pf^Hmmn#z;j60RqOOv>=ad-UkiZ8;twxz^sXshVNoGi%LyMpv*0HxkpZ!U^R

t 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 ODv_=;u?i*MqyGVKd5+Hj literal 0 HcmV?d00001 diff --git a/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py b/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py index 99f12d0170d..4b4c75e1f36 100755 --- a/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py +++ b/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py @@ -19,7 +19,9 @@ CkfConfig, addCKFTracks, addAmbiguityResolution, + addAmbiguityResolutionML, AmbiguityResolutionConfig, + AmbiguityResolutionMLConfig, addVertexFitting, VertexFinder, TrackSelectorConfig, @@ -134,6 +136,17 @@ outputDirRoot=tp, ) + addAmbiguityResolutionML( + s, + AmbiguityResolutionMLConfig( + maximumSharedHits=3, maximumIterations=1000000, nMeasurementsMin=6 + ), + tracks="ckf_tracks", + outputDirRoot=tp, + onnxModelFile=Path(__file__).resolve().parent.parent.parent.parent + / "thirdparty/OpenDataDetector/data/duplicateClassifier.onnx", + ) + addAmbiguityResolution( s, AmbiguityResolutionConfig( @@ -141,6 +154,7 @@ maximumIterations=100000, nMeasurementsMin=6, ), + tracks="ckf_tracks", outputDirRoot=tp, ) @@ -187,6 +201,17 @@ tp / "performance_fitting_ambi.root", tp / "performance_fitting_ckf_ambi.root", ) + + shutil.move( + tp / "performance_finding_ambiML.root", + tp / "performance_finding_ckf_ml_solver.root", + ) + + shutil.move( + tp / "performance_fitting_ambiML.root", + tp / "performance_fitting_ckf_ml_solver.root", + ) + for vertexing in ["amvf_gauss_notime", "amvf_grid_time"]: shutil.move( tp / f"{vertexing}/performance_vertexing.root", @@ -200,6 +225,8 @@ "performance_fitting_ckf.root", "performance_finding_ckf_ambi.root", "performance_fitting_ckf_ambi.root", + "performance_finding_ckf_ml_solver.root", + "performance_fitting_ckf_ml_solver.root", "performance_vertexing_amvf_gauss_notime.root", "performance_vertexing_amvf_grid_time.root", ]: diff --git a/Core/include/Acts/AmbiguityResolution/AmbiguityNetworkConcept.hpp b/Core/include/Acts/AmbiguityResolution/AmbiguityNetworkConcept.hpp new file mode 100644 index 00000000000..a69e1fee9fe --- /dev/null +++ b/Core/include/Acts/AmbiguityResolution/AmbiguityNetworkConcept.hpp @@ -0,0 +1,51 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/EventData/TrackContainer.hpp" +#include "Acts/EventData/TrackContainerFrontendConcept.hpp" +#include "Acts/EventData/VectorMultiTrajectory.hpp" +#include "Acts/EventData/VectorTrackContainer.hpp" +#include "Acts/Utilities/Concepts.hpp" + +namespace Acts { + +/// @brief Concept for the ambiguity network used in the ambiguity resolution +/// +/// The ambiguity network correspond to the AmbiguityTrackClassifier found in +/// the Onnx plugin. It is used to score the tracks and select the best ones. +/// +/// The constructor of the Ambiguity Solver network should take string as input +/// corresponding to the path of the ONNX model. +/// The implementation of the Ambiguity Solver network should have two methods: +/// - inferScores: takes clusters (a list of track ID associated with a cluster +/// ID) and the track container and return an outputTensor (list of scores for +/// each track in the clusters). +/// - trackSelection: Takes clusters and the output tensor from the inferScores +/// method and return the list of track ID to keep. +/// +/// @tparam N the type of the network +template +concept AmbiguityNetworkConcept = requires( + TrackContainer &tracks, + std::unordered_map> &clusters, + std::vector> &outputTensor, const char *modelPath, + network_t &n) { + { network_t(modelPath) } -> std::same_as; + + { + n.inferScores(clusters, tracks) + } -> std::same_as>>; + { + n.trackSelection(clusters, outputTensor) + } -> std::same_as>; +}; + +} // namespace Acts diff --git a/Core/include/Acts/AmbiguityResolution/AmbiguityResolutionML.hpp b/Core/include/Acts/AmbiguityResolution/AmbiguityResolutionML.hpp new file mode 100644 index 00000000000..66717e3f8ee --- /dev/null +++ b/Core/include/Acts/AmbiguityResolution/AmbiguityResolutionML.hpp @@ -0,0 +1,136 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#pragma once + +#include "Acts/AmbiguityResolution/AmbiguityNetworkConcept.hpp" +#include "Acts/Definitions/Units.hpp" +#include "Acts/EventData/TrackContainer.hpp" +#include "Acts/Utilities/Delegate.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include +#include +#include +#include +#include + +namespace Acts { + +/// Generic implementation of the machine learning ambiguity resolution +/// Contains method for data preparations +template +class AmbiguityResolutionML { + public: + struct Config { + /// Path to the model file for the duplicate neural network + std::string inputDuplicateNN = ""; + /// Minimum number of measurement to form a track. + std::size_t nMeasurementsMin = 7; + }; + /// Construct the ambiguity resolution algorithm. + /// + /// @param cfg is the algorithm configuration + /// @param logger is the logging instance + AmbiguityResolutionML(const Config& cfg, + std::unique_ptr logger = getDefaultLogger( + "AmbiguityResolutionML", Logging::INFO)) + : m_cfg{cfg}, + m_duplicateClassifier(m_cfg.inputDuplicateNN.c_str()), + m_logger{std::move(logger)} {} + + /// Associate the hits to the tracks + /// + /// This algorithm performs the mapping of hits ID to track ID. Our final goal + /// is too loop over all the tracks (and their associated hits) by order of + /// decreasing number hits for this we use a multimap where the key is the + /// number of hits as this will automatically perform the sorting. + /// + /// @param tracks is the input track container + /// @param sourceLinkHash is the hash function for the source link, will be used to associate to tracks + /// @param sourceLinkEquality is the equality function for the source link used used to associated hits to tracks + /// @return an ordered list containing pairs of track ID and associated measurement ID + template + std::multimap>> + mapTrackHits(const track_container_t& tracks, + const source_link_hash_t& sourceLinkHash, + const source_link_equality_t& sourceLinkEquality) const { + // A map to store (and generate) the measurement index for each source link + auto measurementIndexMap = + std::unordered_map(0, sourceLinkHash, + sourceLinkEquality); + + // A map to store the track Id and their associated measurements ID, a + // multimap is used to automatically sort the tracks by the number of + // measurements + std::multimap>> + trackMap; + std::size_t trackIndex = 0; + std::vector measurements; + // Loop over all the trajectories in the events + for (const auto& track : tracks) { + // Kick out tracks that do not fulfill our initial requirements + if (track.nMeasurements() < m_cfg.nMeasurementsMin) { + continue; + } + measurements.clear(); + for (auto ts : track.trackStatesReversed()) { + if (ts.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) { + SourceLink sourceLink = ts.getUncalibratedSourceLink(); + // assign a new measurement index if the source link was not seen yet + auto emplace = measurementIndexMap.try_emplace( + sourceLink, measurementIndexMap.size()); + measurements.push_back(emplace.first->second); + } + } + trackMap.emplace(track.nMeasurements(), + std::make_pair(trackIndex, measurements)); + ++trackIndex; + } + return trackMap; + } + + /// Select the track associated with each cluster + /// + /// In this algorithm the call the neural network to score the tracks and then + /// select the track with the highest score in each cluster + /// + /// @param clusters is a map of clusters, each cluster correspond to a vector of track ID + /// @param tracks is the input track container + /// @return a vector of trackID corresponding tho the good tracks + template + std::vector solveAmbiguity( + std::unordered_map>& clusters, + const track_container_t& tracks) const { + std::vector> outputTensor = + m_duplicateClassifier.inferScores(clusters, tracks); + std::vector goodTracks = + m_duplicateClassifier.trackSelection(clusters, outputTensor); + + return goodTracks; + } + + private: + // Configuration + Config m_cfg; + + // The neural network for duplicate classification, the network + // implementation is chosen with the AmbiguityNetwork template parameter + AmbiguityNetwork m_duplicateClassifier; + + /// Logging instance + std::unique_ptr m_logger = nullptr; + + /// Private access to logging instance + const Logger& logger() const { return *m_logger; } +}; + +} // namespace Acts diff --git a/Core/include/Acts/TrackFinding/detail/AmbiguityTrackClustering.hpp b/Core/include/Acts/TrackFinding/detail/AmbiguityTrackClustering.hpp index 6bc565f80eb..644a0fa34fd 100644 --- a/Core/include/Acts/TrackFinding/detail/AmbiguityTrackClustering.hpp +++ b/Core/include/Acts/TrackFinding/detail/AmbiguityTrackClustering.hpp @@ -15,7 +15,15 @@ namespace Acts::detail { -/// Clusterise tracks based on shared hits +/// Cluster tracks based on shared hits. +/// +/// In this algorithm we will loop through all the tracks by decreasing number +/// of measurements. Cluster are created when a new track is encountered that +/// doesn't share hits with the leading track of a previous cluster (with the +/// leading track defined as the track that lead to the cluster creation). If a +/// track shares hits with the leading track of a cluster, it is added to that +/// cluster. If a track shares hits with multiple clusters, it is associated to +/// the cluster with the leading track with the most hits. /// /// @param trackMap : Multimap storing pair of track ID and vector of measurement ID. The keys are the number of measurement and are just there to facilitate the ordering. /// @return an unordered map representing the clusters, the keys the ID of the primary track of each cluster and the store a vector of track IDs. diff --git a/Core/src/TrackFinding/AmbiguityTrackClustering.cpp b/Core/src/TrackFinding/AmbiguityTrackClustering.cpp index ff0e95cbb3d..8586f571aec 100644 --- a/Core/src/TrackFinding/AmbiguityTrackClustering.cpp +++ b/Core/src/TrackFinding/AmbiguityTrackClustering.cpp @@ -21,8 +21,9 @@ Acts::detail::clusterDuplicateTracks( // different clusters. std::unordered_map hitToTrack; - // Loop over all the tracks - for (const auto& [_, trackValue] : trackMap) { + // Loop backward over all the tracks + for (auto track = trackMap.rbegin(); track != trackMap.rend(); ++track) { + const auto& trackValue = track->second; std::vector hits = trackValue.second; auto matchedTrack = hitToTrack.end(); // Loop over all the hits in the track diff --git a/Examples/Algorithms/TrackFindingML/CMakeLists.txt b/Examples/Algorithms/TrackFindingML/CMakeLists.txt index d826b224f2c..80f55f60579 100644 --- a/Examples/Algorithms/TrackFindingML/CMakeLists.txt +++ b/Examples/Algorithms/TrackFindingML/CMakeLists.txt @@ -1,7 +1,5 @@ set(SOURCES - src/AmbiguityResolutionML.cpp src/AmbiguityResolutionMLAlgorithm.cpp - src/AmbiguityResolutionMLDBScanAlgorithm.cpp src/SeedFilterMLAlgorithm.cpp ) diff --git a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityDBScanClustering.hpp b/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityDBScanClustering.hpp deleted file mode 100644 index 45fe4cb5480..00000000000 --- a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityDBScanClustering.hpp +++ /dev/null @@ -1,79 +0,0 @@ -// This file is part of the ACTS project. -// -// Copyright (C) 2016 CERN for the benefit of the ACTS project -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -#pragma once - -#include "Acts/EventData/TrackContainer.hpp" -#include "Acts/EventData/TrackContainerFrontendConcept.hpp" -#include "Acts/TrackFinding/detail/AmbiguityTrackClustering.hpp" -#include "Acts/Utilities/DBScan.hpp" - -#include -#include -#include - -namespace Acts { - -/// Clusterise tracks based on shared hits -/// -/// @param trackMap Multimap storing pair of track ID and vector of measurement ID. The keys are the number of measurement and are just there to facilitate the ordering. -/// @param tracks Track container with all the track to be clustered -/// @param epsilon Maximum distance between 2 tracks to be clustered -/// @param minPoints Minimum number of tracks to create a cluster -/// @return an unordered map representing the clusters, the keys the ID of the primary track of each cluster and the store a vector of track IDs. -template -std::unordered_map> dbscanTrackClustering( - std::multimap>>& - trackMap, - const track_container_t& tracks, float epsilon = 0.07, int minPoints = 2) { - // Unordered map associating a vector with all the track ID of a cluster to - // the ID of the first track of the cluster - std::unordered_map> cluster; - // Unordered map associating hits to the ID of the first track of the - // different clusters. - std::unordered_map hitToTrack; - - // Initialize a DBScan of dimension 4 (phi, eta, z, Pt) - using DBSCAN = Acts::DBScan<4, double, 4>; - DBSCAN dbscan(epsilon, minPoints, true); - - std::vector> data; - std::size_t trackID = 0; - std::vector clusterAssignments; - - // Get the input feature of the network for all the tracks - for (const auto& [key, val] : trackMap) { - auto traj = tracks.getTrack(val.first); - data.push_back({Acts::VectorHelpers::eta(traj.momentum()), - Acts::VectorHelpers::phi(traj.momentum())}); - } - std::size_t clusterNb = dbscan.cluster(data, clusterAssignments); - - // Cluster track with DBScan - std::vector< - std::multimap>>> - dbscanClusters(clusterNb); - for (const auto& [key, val] : trackMap) { - std::size_t clusterID = clusterAssignments[trackID]; - dbscanClusters[clusterID].emplace(key, val); - trackID++; - } - - // Perform a subClustering of the DBScan cluster using the measurement ID - // clustering - for (const auto& dbscanCluster : dbscanClusters) { - auto subCluster = Acts::detail::clusterDuplicateTracks(dbscanCluster); - cluster.merge(subCluster); - if (!subCluster.empty()) { - std::cout << "Overlapping track ID, there must be an error" << std::endl; - } - } - return cluster; -} - -} // namespace Acts diff --git a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp b/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp deleted file mode 100644 index ea967a23c22..00000000000 --- a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp +++ /dev/null @@ -1,48 +0,0 @@ -// This file is part of the ACTS project. -// -// Copyright (C) 2016 CERN for the benefit of the ACTS project -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -#pragma once - -#include "ActsExamples/EventData/Track.hpp" -#include "ActsExamples/Framework/IAlgorithm.hpp" - -#include -#include -#include - -namespace ActsExamples { - -/// Generic implementation of the machine learning ambiguity resolution -/// Contains method for data preparations -class AmbiguityResolutionML : public IAlgorithm { - public: - /// Construct the ambiguity resolution algorithm. - /// - /// @param name name of the algorithm - /// @param lvl is the logging level - AmbiguityResolutionML(std::string name, Acts::Logging::Level lvl); - - protected: - /// Associated measurements ID to Tracks ID - /// - /// @param tracks is the input track container - /// @param nMeasurementsMin minimum number of measurement per track - /// @return an ordered list containing pairs of track ID and associated measurement ID - std::multimap>> - mapTrackHits(const ConstTrackContainer& tracks, int nMeasurementsMin) const; - - /// Prepare the output track container to be written - /// - /// @param tracks is the input track container - /// @param goodTracks is list of the IDs of all the tracks we want to keep - ConstTrackContainer prepareOutputTrack( - const ConstTrackContainer& tracks, - std::vector& goodTracks) const; -}; - -} // namespace ActsExamples diff --git a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLAlgorithm.hpp b/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLAlgorithm.hpp index 2e5d22ac01d..e591b2e201a 100644 --- a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLAlgorithm.hpp +++ b/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLAlgorithm.hpp @@ -8,10 +8,11 @@ #pragma once +#include "Acts/AmbiguityResolution/AmbiguityResolutionML.hpp" #include "Acts/Plugins/Onnx/AmbiguityTrackClassifier.hpp" #include "ActsExamples/EventData/Track.hpp" #include "ActsExamples/Framework/DataHandle.hpp" -#include "ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp" +#include "ActsExamples/Framework/IAlgorithm.hpp" #include @@ -23,7 +24,10 @@ namespace ActsExamples { /// 1) Cluster together nearby tracks using shared hits /// 2) For each track use a neural network to compute a score /// 3) In each cluster keep the track with the highest score -class AmbiguityResolutionMLAlgorithm final : public AmbiguityResolutionML { +class AmbiguityResolutionMLAlgorithm final : public IAlgorithm { + using AmbiguityResolution = + Acts::AmbiguityResolutionML; + public: struct Config { /// Input track collection. @@ -33,7 +37,11 @@ class AmbiguityResolutionMLAlgorithm final : public AmbiguityResolutionML { /// Output track collection. std::string outputTracks; /// Minimum number of measurement to form a track. - int nMeasurementsMin = 7; + std::size_t nMeasurementsMin = 7; + /// Construct the ML ambiguity resolution configuration. + AmbiguityResolution::Config toAmbiguityResolutionMLConfig() const { + return {inputDuplicateNN, nMeasurementsMin}; + } }; /// Construct the ambiguity resolution algorithm. @@ -53,8 +61,7 @@ class AmbiguityResolutionMLAlgorithm final : public AmbiguityResolutionML { private: Config m_cfg; - // ONNX model for track selection - Acts::AmbiguityTrackClassifier m_duplicateClassifier; + AmbiguityResolution m_ambiML; ReadDataHandle m_inputTracks{this, "InputTracks"}; WriteDataHandle m_outputTracks{this, "OutputTracks"}; }; diff --git a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLDBScanAlgorithm.hpp b/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLDBScanAlgorithm.hpp deleted file mode 100644 index 6ad387ffdc4..00000000000 --- a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/AmbiguityResolutionMLDBScanAlgorithm.hpp +++ /dev/null @@ -1,68 +0,0 @@ -// This file is part of the ACTS project. -// -// Copyright (C) 2016 CERN for the benefit of the ACTS project -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -#pragma once - -#include "Acts/Plugins/Onnx/AmbiguityTrackClassifier.hpp" -#include "ActsExamples/EventData/Track.hpp" -#include "ActsExamples/Framework/DataHandle.hpp" -#include "ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp" - -#include - -namespace ActsExamples { - -/// Evicts tracks that seem to be duplicated and fake. -/// -/// The implementation works as follows: -/// 1) Cluster together nearby tracks using a DBScan -/// 2) Create subcluster based on tracks with shared hits -/// 3) For each track use a neural network to compute a score -/// 4) In each cluster keep the track with the highest score -class AmbiguityResolutionMLDBScanAlgorithm final - : public AmbiguityResolutionML { - public: - struct Config { - /// Input trajectories collection. - std::string inputTracks; - /// Path to the ONNX model for the duplicate neural network - std::string inputDuplicateNN; - /// Output trajectories collection. - std::string outputTracks; - /// Minimum number of measurement to form a track. - int nMeasurementsMin = 7; - /// Maximum distance between 2 tracks to be clustered in the DBScan - float epsilonDBScan = 0.07; - /// Minimum number of tracks to create a cluster in the DBScan - int minPointsDBScan = 2; - }; - - /// Construct the ambiguity resolution algorithm. - /// - /// @param cfg is the algorithm configuration - /// @param lvl is the logging level - AmbiguityResolutionMLDBScanAlgorithm(Config cfg, Acts::Logging::Level lvl); - - /// Run the ambiguity resolution algorithm. - /// - /// @param cxt is the algorithm context with event information - /// @return a process code indication success or failure - ProcessCode execute(const AlgorithmContext& ctx) const final; - - /// Const access to the config - const Config& config() const { return m_cfg; } - - private: - Config m_cfg; - // ONNX model for track selection - Acts::AmbiguityTrackClassifier m_duplicateClassifier; - ReadDataHandle m_inputTracks{this, "InputTracks"}; - WriteDataHandle m_outputTracks{this, "OutputTracks"}; -}; - -} // namespace ActsExamples diff --git a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/SeedFilterMLAlgorithm.hpp b/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/SeedFilterMLAlgorithm.hpp index 6472a6e8d2b..53659a33e4f 100644 --- a/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/SeedFilterMLAlgorithm.hpp +++ b/Examples/Algorithms/TrackFindingML/include/ActsExamples/TrackFindingML/SeedFilterMLAlgorithm.hpp @@ -12,7 +12,7 @@ #include "ActsExamples/EventData/SimSeed.hpp" #include "ActsExamples/EventData/Track.hpp" #include "ActsExamples/Framework/DataHandle.hpp" -#include "ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp" +#include "ActsExamples/Framework/IAlgorithm.hpp" #include diff --git a/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionML.cpp b/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionML.cpp deleted file mode 100644 index f3282a15d94..00000000000 --- a/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionML.cpp +++ /dev/null @@ -1,76 +0,0 @@ -// This file is part of the ACTS project. -// -// Copyright (C) 2016 CERN for the benefit of the ACTS project -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -#include "ActsExamples/TrackFindingML/AmbiguityResolutionML.hpp" - -#include "ActsExamples/EventData/IndexSourceLink.hpp" -#include "ActsExamples/EventData/Measurement.hpp" - -ActsExamples::AmbiguityResolutionML::AmbiguityResolutionML( - std::string name, Acts::Logging::Level lvl) - : ActsExamples::IAlgorithm(name, lvl) {} - -std::multimap>> -ActsExamples::AmbiguityResolutionML::mapTrackHits( - const ActsExamples::ConstTrackContainer& tracks, - int nMeasurementsMin) const { - std::multimap>> trackMap; - // Loop over all the trajectories in the events - for (const auto& track : tracks) { - std::vector hits; - int nbMeasurements = 0; - // Store the hits id for the trajectory and compute the number of - // measurement - tracks.trackStateContainer().visitBackwards( - track.tipIndex(), [&](const auto& state) { - if (state.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) { - std::size_t indexHit = - state.getUncalibratedSourceLink() - .template get() - .index(); - hits.emplace_back(indexHit); - ++nbMeasurements; - } - }); - if (nbMeasurements < nMeasurementsMin) { - continue; - } - trackMap.emplace(nbMeasurements, std::make_pair(track.index(), hits)); - } - return trackMap; -} - -ActsExamples::ConstTrackContainer -ActsExamples::AmbiguityResolutionML::prepareOutputTrack( - const ActsExamples::ConstTrackContainer& tracks, - std::vector& goodTracks) const { - std::shared_ptr trackStateContainer = - tracks.trackStateContainerHolder(); - auto trackContainer = std::make_shared(); - trackContainer->reserve(goodTracks.size()); - // Temporary empty track state container: we don't change the original one, - // but we need one for filtering - auto tempTrackStateContainer = - std::make_shared(); - - TrackContainer solvedTracks{trackContainer, tempTrackStateContainer}; - solvedTracks.ensureDynamicColumns(tracks); - - for (auto&& iTrack : goodTracks) { - auto destProxy = solvedTracks.makeTrack(); - auto srcProxy = tracks.getTrack(iTrack); - destProxy.copyFrom(srcProxy, false); - destProxy.tipIndex() = srcProxy.tipIndex(); - } - - ConstTrackContainer outputTracks{ - std::make_shared( - std::move(*trackContainer)), - trackStateContainer}; - return outputTracks; -} diff --git a/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLAlgorithm.cpp b/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLAlgorithm.cpp index ba2e9abeb98..805913e60ae 100644 --- a/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLAlgorithm.cpp +++ b/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLAlgorithm.cpp @@ -8,18 +8,30 @@ #include "ActsExamples/TrackFindingML/AmbiguityResolutionMLAlgorithm.hpp" +#include "ActsExamples/EventData/IndexSourceLink.hpp" +#include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/Framework/ProcessCode.hpp" #include #include +static std::size_t sourceLinkHash(const Acts::SourceLink& a) { + return static_cast( + a.get().index()); +} + +static bool sourceLinkEquality(const Acts::SourceLink& a, + const Acts::SourceLink& b) { + return a.get().index() == + b.get().index(); +} + ActsExamples::AmbiguityResolutionMLAlgorithm::AmbiguityResolutionMLAlgorithm( ActsExamples::AmbiguityResolutionMLAlgorithm::Config cfg, Acts::Logging::Level lvl) - : ActsExamples::AmbiguityResolutionML("AmbiguityResolutionMLAlgorithm", - lvl), + : ActsExamples::IAlgorithm("AmbiguityResolutionMLAlgorithm", lvl), m_cfg(std::move(cfg)), - m_duplicateClassifier(m_cfg.inputDuplicateNN.c_str()) { + m_ambiML(m_cfg.toAmbiguityResolutionMLConfig(), logger().clone()) { if (m_cfg.inputTracks.empty()) { throw std::invalid_argument("Missing trajectories input collection"); } @@ -34,15 +46,32 @@ ActsExamples::ProcessCode ActsExamples::AmbiguityResolutionMLAlgorithm::execute( const AlgorithmContext& ctx) const { // Read input data const auto& tracks = m_inputTracks(ctx); - // Associate measurement to their respective tracks + // Associate measurement to their respective tracks to prepare the track + // shared hits based clustering std::multimap>> - trackMap = mapTrackHits(tracks, m_cfg.nMeasurementsMin); + trackMap = + m_ambiML.mapTrackHits(tracks, &sourceLinkHash, &sourceLinkEquality); + // Cluster the tracks based on the shared hits auto cluster = Acts::detail::clusterDuplicateTracks(trackMap); // Select the ID of the track we want to keep std::vector goodTracks = - m_duplicateClassifier.solveAmbiguity(cluster, tracks); + m_ambiML.solveAmbiguity(cluster, tracks); // Prepare the output track collection from the IDs - auto outputTracks = prepareOutputTrack(tracks, goodTracks); + TrackContainer solvedTracks{std::make_shared(), + std::make_shared()}; + solvedTracks.ensureDynamicColumns(tracks); + for (auto iTrack : goodTracks) { + auto destProxy = solvedTracks.makeTrack(); + auto srcProxy = tracks.getTrack(iTrack); + destProxy.copyFrom(srcProxy, false); + destProxy.tipIndex() = srcProxy.tipIndex(); + } + + ActsExamples::ConstTrackContainer outputTracks{ + std::make_shared( + std::move(solvedTracks.container())), + tracks.trackStateContainerHolder()}; + m_outputTracks(ctx, std::move(outputTracks)); return ActsExamples::ProcessCode::SUCCESS; diff --git a/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLDBScanAlgorithm.cpp b/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLDBScanAlgorithm.cpp deleted file mode 100644 index 6f0c7529208..00000000000 --- a/Examples/Algorithms/TrackFindingML/src/AmbiguityResolutionMLDBScanAlgorithm.cpp +++ /dev/null @@ -1,55 +0,0 @@ -// This file is part of the ACTS project. -// -// Copyright (C) 2016 CERN for the benefit of the ACTS project -// -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at https://mozilla.org/MPL/2.0/. - -#include "ActsExamples/TrackFindingML/AmbiguityResolutionMLDBScanAlgorithm.hpp" - -#include "ActsExamples/Framework/ProcessCode.hpp" -#include "ActsExamples/Framework/WhiteBoard.hpp" -#include "ActsExamples/TrackFindingML/AmbiguityDBScanClustering.hpp" - -#include -#include - -ActsExamples::AmbiguityResolutionMLDBScanAlgorithm:: - AmbiguityResolutionMLDBScanAlgorithm( - ActsExamples::AmbiguityResolutionMLDBScanAlgorithm::Config cfg, - Acts::Logging::Level lvl) - : ActsExamples::AmbiguityResolutionML( - "AmbiguityResolutionMLDBScanAlgorithm", lvl), - m_cfg(std::move(cfg)), - m_duplicateClassifier(m_cfg.inputDuplicateNN.c_str()) { - if (m_cfg.inputTracks.empty()) { - throw std::invalid_argument("Missing trajectories input collection"); - } - if (m_cfg.outputTracks.empty()) { - throw std::invalid_argument("Missing trajectories output collection"); - } - m_inputTracks.initialize(m_cfg.inputTracks); - m_outputTracks.initialize(m_cfg.outputTracks); -} - -ActsExamples::ProcessCode -ActsExamples::AmbiguityResolutionMLDBScanAlgorithm::execute( - const AlgorithmContext& ctx) const { - // Read input data - const auto& tracks = m_inputTracks(ctx); - // Associate measurement to their respective tracks - std::multimap>> - trackMap = mapTrackHits(tracks, m_cfg.nMeasurementsMin); - // Cluster the tracks using DBscan - auto cluster = Acts::dbscanTrackClustering( - trackMap, tracks, m_cfg.epsilonDBScan, m_cfg.minPointsDBScan); - // Select the ID of the track we want to keep - std::vector goodTracks = - m_duplicateClassifier.solveAmbiguity(cluster, tracks); - // Prepare the output track collection from the IDs - auto outputTracks = prepareOutputTrack(tracks, goodTracks); - m_outputTracks(ctx, std::move(outputTracks)); - - return ActsExamples::ProcessCode::SUCCESS; -} diff --git a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacepointWriter.hpp b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacePointWriter.hpp similarity index 93% rename from Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacepointWriter.hpp rename to Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacePointWriter.hpp index 1e24eb4bc97..ea7315d4927 100644 --- a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacepointWriter.hpp +++ b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacePointWriter.hpp @@ -34,7 +34,7 @@ namespace ActsExamples { /// ... /// /// Intrinsically thread-safe as one file per event. -class CsvSpacepointWriter final : public WriterT { +class CsvSpacePointWriter final : public WriterT { public: struct Config { /// Which measurement collection to write. @@ -48,10 +48,10 @@ class CsvSpacepointWriter final : public WriterT { /// Constructor with /// @param config configuration struct /// @param level logging level - CsvSpacepointWriter(const Config& config, Acts::Logging::Level level); + CsvSpacePointWriter(const Config& config, Acts::Logging::Level level); /// Virtual destructor - ~CsvSpacepointWriter() override; + ~CsvSpacePointWriter() override; /// End-of-run hook ProcessCode finalize() override; diff --git a/Examples/Io/Csv/src/CsvSpacePointWriter.cpp b/Examples/Io/Csv/src/CsvSpacePointWriter.cpp index 9eb440f7692..4e71020a390 100644 --- a/Examples/Io/Csv/src/CsvSpacePointWriter.cpp +++ b/Examples/Io/Csv/src/CsvSpacePointWriter.cpp @@ -6,7 +6,7 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at https://mozilla.org/MPL/2.0/. -#include "ActsExamples/Io/Csv/CsvSpacepointWriter.hpp" +#include "ActsExamples/Io/Csv/CsvSpacePointWriter.hpp" #include "Acts/EventData/SourceLink.hpp" #include "Acts/Geometry/GeometryIdentifier.hpp" @@ -24,20 +24,20 @@ #include "CsvOutputData.hpp" -ActsExamples::CsvSpacepointWriter::CsvSpacepointWriter( - const ActsExamples::CsvSpacepointWriter::Config& config, +ActsExamples::CsvSpacePointWriter::CsvSpacePointWriter( + const ActsExamples::CsvSpacePointWriter::Config& config, Acts::Logging::Level level) - : WriterT(config.inputSpacepoints, "CsvSpacepointWriter", level), + : WriterT(config.inputSpacepoints, "CsvSpacePointWriter", level), m_cfg(config) {} -ActsExamples::CsvSpacepointWriter::~CsvSpacepointWriter() = default; +ActsExamples::CsvSpacePointWriter::~CsvSpacePointWriter() = default; -ActsExamples::ProcessCode ActsExamples::CsvSpacepointWriter::finalize() { +ActsExamples::ProcessCode ActsExamples::CsvSpacePointWriter::finalize() { // Write the tree return ProcessCode::SUCCESS; } -ActsExamples::ProcessCode ActsExamples::CsvSpacepointWriter::writeT( +ActsExamples::ProcessCode ActsExamples::CsvSpacePointWriter::writeT( const AlgorithmContext& ctx, const SimSpacePointContainer& spacepoints) { // Open per-event file for all components std::string pathSP = diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index ceb234f1419..727bf9e9785 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -234,12 +234,6 @@ def trackSelectorDefaultKWArgs(c): defaults=[None] * 3, ) -AmbiguityResolutionMLDBScanConfig = namedtuple( - "AmbiguityResolutionMLDBScanConfig", - ["nMeasurementsMin", "epsilonDBScan", "minPointsDBScan"], - defaults=[None] * 3, -) - SeedFilterMLDBScanConfig = namedtuple( "SeedFilterMLDBScanConfig", ["epsilonDBScan", "minPointsDBScan", "minSeedScore"], @@ -1972,14 +1966,31 @@ def addScoreBasedAmbiguityResolution( s.addAlgorithm(algScoreBased) s.addWhiteboardAlias("tracks", algScoreBased.config.outputTracks) + matchAlg = acts.examples.TrackTruthMatcher( + level=customLogLevel(), + inputTracks=algScoreBased.config.outputTracks, + inputParticles="particles", + inputMeasurementParticlesMap="measurement_particles_map", + outputTrackParticleMatching="ambi_scorebased_track_particle_matching", + outputParticleTrackMatching="ambi_scorebased_particle_track_matching", + doubleMatching=True, + ) + s.addAlgorithm(matchAlg) + s.addWhiteboardAlias( + "track_particle_matching", matchAlg.config.outputTrackParticleMatching + ) + s.addWhiteboardAlias( + "particle_track_matching", matchAlg.config.outputParticleTrackMatching + ) + addTrackWriters( s, name="ambi_scorebased", tracks=algScoreBased.config.outputTracks, outputDirCsv=outputDirCsv, outputDirRoot=outputDirRoot, - writeSummary=writeTrackStates, - writeStates=writeTrackSummary, + writeSummary=writeTrackSummary, + writeStates=writeTrackStates, writeFitterPerformance=writePerformance, writeFinderPerformance=writePerformance, writeCovMat=writeCovMat, @@ -1995,6 +2006,7 @@ def addScoreBasedAmbiguityResolution( def addAmbiguityResolutionML( s, config: AmbiguityResolutionMLConfig = AmbiguityResolutionMLConfig(), + tracks: str = "tracks", onnxModelFile: Optional[Union[Path, str]] = None, outputDirCsv: Optional[Union[Path, str]] = None, outputDirRoot: Optional[Union[Path, str]] = None, @@ -2008,10 +2020,9 @@ def addAmbiguityResolutionML( from acts.examples import GreedyAmbiguityResolutionAlgorithm customLogLevel = acts.examples.defaultLogging(s, logLevel) - algML = AmbiguityResolutionMLAlgorithm( level=customLogLevel(), - inputTracks="tracks", + inputTracks=tracks, inputDuplicateNN=onnxModelFile, outputTracks="ambiTracksML", **acts.examples.defaultKWArgs( @@ -2033,63 +2044,33 @@ def addAmbiguityResolutionML( s.addAlgorithm(algML) s.addAlgorithm(algGreedy) - addTrackWriters( - s, - name="ambiML", - tracks=algGreedy.config.outputTracks, - outputDirCsv=outputDirCsv, - outputDirRoot=outputDirRoot, - writeSummary=writeTrackStates, - writeStates=writeTrackSummary, - writeFitterPerformance=writePerformance, - writeFinderPerformance=writePerformance, - writeCovMat=writeCovMat, - logLevel=logLevel, - ) + s.addWhiteboardAlias("tracks", algGreedy.config.outputTracks) - return s - - -@acts.examples.NamedTypeArgs( - config=AmbiguityResolutionMLDBScanConfig, -) -def addAmbiguityResolutionMLDBScan( - s, - config: AmbiguityResolutionMLDBScanConfig = AmbiguityResolutionMLDBScanConfig(), - onnxModelFile: Optional[Union[Path, str]] = None, - outputDirCsv: Optional[Union[Path, str]] = None, - outputDirRoot: Optional[Union[Path, str]] = None, - writeTrackSummary: bool = True, - writeTrackStates: bool = False, - writePerformance: bool = True, - writeCovMat=False, - logLevel: Optional[acts.logging.Level] = None, -) -> None: - from acts.examples import AmbiguityResolutionMLDBScanAlgorithm - - customLogLevel = acts.examples.defaultLogging(s, logLevel) - - alg = AmbiguityResolutionMLDBScanAlgorithm( + matchAlg = acts.examples.TrackTruthMatcher( level=customLogLevel(), - inputTracks="tracks", - inputDuplicateNN=onnxModelFile, - outputTracks="ambiTracksMLDBScan", - **acts.examples.defaultKWArgs( - nMeasurementsMin=config.nMeasurementsMin, - epsilonDBScan=config.epsilonDBScan, - minPointsDBScan=config.minPointsDBScan, - ), + inputTracks=algGreedy.config.outputTracks, + inputParticles="particles", + inputMeasurementParticlesMap="measurement_particles_map", + outputTrackParticleMatching="ambiML_track_particle_matching", + outputParticleTrackMatching="ambiML_particle_track_matching", + doubleMatching=True, + ) + s.addAlgorithm(matchAlg) + s.addWhiteboardAlias( + "track_particle_matching", matchAlg.config.outputTrackParticleMatching + ) + s.addWhiteboardAlias( + "particle_track_matching", matchAlg.config.outputParticleTrackMatching ) - s.addAlgorithm(alg) addTrackWriters( s, - name="ambiMLDBScan", - trajectories=alg.config.outputTracks, - outputDirRoot=outputDirRoot, + name="ambiML", + tracks=algGreedy.config.outputTracks, outputDirCsv=outputDirCsv, - writeSummary=writeTrackStates, - writeStates=writeTrackSummary, + outputDirRoot=outputDirRoot, + writeSummary=writeTrackSummary, + writeStates=writeTrackStates, writeFitterPerformance=writePerformance, writeFinderPerformance=writePerformance, writeCovMat=writeCovMat, diff --git a/Examples/Python/src/Onnx.cpp b/Examples/Python/src/Onnx.cpp index c22e595bb43..8bd75a3047a 100644 --- a/Examples/Python/src/Onnx.cpp +++ b/Examples/Python/src/Onnx.cpp @@ -8,7 +8,6 @@ #include "Acts/Plugins/Python/Utilities.hpp" #include "ActsExamples/TrackFindingML/AmbiguityResolutionMLAlgorithm.hpp" -#include "ActsExamples/TrackFindingML/AmbiguityResolutionMLDBScanAlgorithm.hpp" #include "ActsExamples/TrackFindingML/SeedFilterMLAlgorithm.hpp" #include @@ -31,11 +30,6 @@ void addOnnx(Context& ctx) { inputTracks, inputDuplicateNN, outputTracks, nMeasurementsMin); - ACTS_PYTHON_DECLARE_ALGORITHM( - ActsExamples::AmbiguityResolutionMLDBScanAlgorithm, onnx, - "AmbiguityResolutionMLDBScanAlgorithm", inputTracks, inputDuplicateNN, - outputTracks, nMeasurementsMin, epsilonDBScan, minPointsDBScan); - ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::SeedFilterMLAlgorithm, onnx, "SeedFilterMLAlgorithm", inputTrackParameters, inputSimSeeds, inputSeedFilterNN, diff --git a/Examples/Python/src/Output.cpp b/Examples/Python/src/Output.cpp index 41f7bb5f564..838649816f6 100644 --- a/Examples/Python/src/Output.cpp +++ b/Examples/Python/src/Output.cpp @@ -17,8 +17,8 @@ #include "ActsExamples/Io/Csv/CsvProtoTrackWriter.hpp" #include "ActsExamples/Io/Csv/CsvSeedWriter.hpp" #include "ActsExamples/Io/Csv/CsvSimHitWriter.hpp" +#include "ActsExamples/Io/Csv/CsvSpacePointWriter.hpp" #include "ActsExamples/Io/Csv/CsvSpacePointsBucketWriter.hpp" -#include "ActsExamples/Io/Csv/CsvSpacepointWriter.hpp" #include "ActsExamples/Io/Csv/CsvTrackParameterWriter.hpp" #include "ActsExamples/Io/Csv/CsvTrackWriter.hpp" #include "ActsExamples/Io/Csv/CsvTrackingGeometryWriter.hpp" @@ -375,8 +375,8 @@ void addOutput(Context& ctx) { "CsvSimHitWriter", inputSimHits, outputDir, outputStem, outputPrecision); - ACTS_PYTHON_DECLARE_WRITER(ActsExamples::CsvSpacepointWriter, mex, - "CsvSpacepointWriter", inputSpacepoints, outputDir, + ACTS_PYTHON_DECLARE_WRITER(ActsExamples::CsvSpacePointWriter, mex, + "CsvSpacePointWriter", inputSpacepoints, outputDir, outputPrecision); ACTS_PYTHON_DECLARE_WRITER(ActsExamples::CsvSpacePointsBucketWriter, mex, diff --git a/Examples/Scripts/Python/MLAmbiguityResolution/ambiguity_solver_network.py b/Examples/Scripts/Python/MLAmbiguityResolution/ambiguity_solver_network.py index 15f596414f7..5e241fdf717 100644 --- a/Examples/Scripts/Python/MLAmbiguityResolution/ambiguity_solver_network.py +++ b/Examples/Scripts/Python/MLAmbiguityResolution/ambiguity_solver_network.py @@ -16,7 +16,7 @@ def prepareDataSet(data: pd.DataFrame) -> pd.DataFrame: data = data # Remove tracks with less than 7 measurements data = data[data["nMeasurements"] > 6] - # data = data.sort_values("good/duplicate/fake", ascending=False) + data = data.sort_values("good/duplicate/fake", ascending=False) # Remove pure duplicate (tracks purely identical) keep the ones good one if among them. data = data.drop_duplicates( subset=[ diff --git a/Plugins/Onnx/include/Acts/Plugins/Onnx/AmbiguityTrackClassifier.hpp b/Plugins/Onnx/include/Acts/Plugins/Onnx/AmbiguityTrackClassifier.hpp index 3e1450f2b85..b0ddbd09bc7 100644 --- a/Plugins/Onnx/include/Acts/Plugins/Onnx/AmbiguityTrackClassifier.hpp +++ b/Plugins/Onnx/include/Acts/Plugins/Onnx/AmbiguityTrackClassifier.hpp @@ -102,23 +102,6 @@ class AmbiguityTrackClassifier { return goodTracks; } - /// Select the track associated with each cluster - /// - /// @param clusters is a map of clusters, each cluster correspond to a vector of track ID - /// @param tracks is the input track container - /// @return a vector of trackID corresponding tho the good tracks - template - std::vector solveAmbiguity( - std::unordered_map>& clusters, - const track_container_t& tracks) const { - std::vector> outputTensor = - inferScores(clusters, tracks); - std::vector goodTracks = - trackSelection(clusters, outputTensor); - - return goodTracks; - } - private: // ONNX environment Ort::Env m_env;