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 01/21] 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 02/21] 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 03/21] 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 04/21] 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 05/21] 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; From feac68618ac7da66fe811ca42cfe00187bdcacf5 Mon Sep 17 00:00:00 2001 From: "Alexander J. Pfleger" <70842573+AJPfleger@users.noreply.github.com> Date: Fri, 6 Dec 2024 03:44:46 +0100 Subject: [PATCH 06/21] refactor(gx2f): put parts into separate compile units (#3946) This reduces the memory impact of the GX2F during compilation by: - extracting heavy parts to a separate compile unit - removing the templating from the extracted parts (to make them moveable in the first place) ## Impact main: ``` [ 6.03M, max: 1072.02M] [ 9.74s] - Core/src/TrackFitting/GlobalChiSquareFitter.cpp [ 7.05M, max: 3364.18M] [ 52.19s] - Examples/Algorithms/TrackFitting/src/GlobalChiSquareFitterFunction.cpp [ 6.93M, max: 4196.06M] [ 85.36s] - Tests/UnitTests/Core/TrackFitting/Gx2fTests.cpp ``` after addMeasurementToGx2fSumsBackend: ``` [ 6.04M, max: 1408.93M] [ 16.37s] - Core/src/TrackFitting/GlobalChiSquareFitter.cpp [ 6.91M, max: 2299.42M] [ 29.93s] - Examples/Algorithms/TrackFitting/src/GlobalChiSquareFitterFunction.cpp [ 6.94M, max: 3269.05M] [ 59.98s] - Tests/UnitTests/Core/TrackFitting/Gx2fTests.cpp ``` after computeGx2fDeltaParams: ``` [ 6.40M, max: 1769.83M] [ 20.65s] - Core/src/TrackFitting/GlobalChiSquareFitter.cpp [ 5.94M, max: 1805.96M] [ 24.37s] - Examples/Algorithms/TrackFitting/src/GlobalChiSquareFitterFunction.cpp [ 7.23M, max: 2510.50M] [ 53.56s] - Tests/UnitTests/Core/TrackFitting/Gx2fTests.cpp ``` ## Other discussion on the topic ### Performance of dynamic matrix This still needs investigation. Maybe we can template the functions again on `kMeasDims` ### Untemplate temporary track This was the first attempt, by using `VectorMultiTrajectory` for the internal tracks. This fails in Athena, because there the `Mutable...` is used. This comes in with the calibrator. Currently, it might work with 2 calibrators (one for the fitting Actor and one for the final Actor). Better solution would be to have type-erasure, which might come in the future. ## Summary by CodeRabbit - **New Features** - Introduced a new method for adding measurement data into the fitting system, enhancing the integration process. - Added a function to compute delta parameters for improved clarity in the fitting process. - **Improvements** - Streamlined measurement handling and error management within the fitting framework for better efficiency. - Enhanced the modularity of the fitting process, improving maintainability and clarity. --- .../TrackFitting/GlobalChiSquareFitter.hpp | 128 ++++++------------ .../TrackFitting/GlobalChiSquareFitter.cpp | 95 +++++++++++++ 2 files changed, 133 insertions(+), 90 deletions(-) diff --git a/Core/include/Acts/TrackFitting/GlobalChiSquareFitter.hpp b/Core/include/Acts/TrackFitting/GlobalChiSquareFitter.hpp index dc0bc221e9a..5e46bc0dba3 100644 --- a/Core/include/Acts/TrackFitting/GlobalChiSquareFitter.hpp +++ b/Core/include/Acts/TrackFitting/GlobalChiSquareFitter.hpp @@ -361,6 +361,29 @@ struct Gx2fSystem { std::size_t m_ndf = 0u; }; +/// @brief Adds a measurement to the GX2F equation system in a modular backend function. +/// +/// This function processes measurement data and integrates it into the GX2F +/// system. +/// +/// @param extendedSystem All parameters of the current equation system to update. +/// @param jacobianFromStart The Jacobian matrix from the start to the current state. +/// @param covarianceMeasurement The covariance matrix of the measurement. +/// @param predicted The predicted state vector based on the track state. +/// @param measurement The measurement vector. +/// @param projector The projection matrix. +/// @param logger A logger instance. +/// +/// @note The dynamic Eigen matrices are suboptimal. We could think of +/// templating again in the future on kMeasDims. We currently use dynamic +/// matrices to reduce the memory during compile time. +void addMeasurementToGx2fSumsBackend( + Gx2fSystem& extendedSystem, + const std::vector& jacobianFromStart, + const Eigen::MatrixXd& covarianceMeasurement, const BoundVector& predicted, + const Eigen::VectorXd& measurement, const Eigen::MatrixXd& projector, + const Logger& logger); + /// @brief Process measurements and fill the aMatrix and bVector /// /// The function processes each measurement for the GX2F Actor fitting process. @@ -370,7 +393,7 @@ struct Gx2fSystem { /// @tparam kMeasDim Number of dimensions of the measurement /// @tparam track_state_t The type of the track state /// -/// @param extendedSystem All parameters of the current equation system +/// @param extendedSystem All parameters of the current equation system to update /// @param jacobianFromStart The Jacobian matrix from start to the current state /// @param trackState The track state to analyse /// @param logger A logger instance @@ -379,44 +402,9 @@ void addMeasurementToGx2fSums(Gx2fSystem& extendedSystem, const std::vector& jacobianFromStart, const track_state_t& trackState, const Logger& logger) { - // First we get back the covariance and try to invert it. If the inversion - // fails, we can already abort. const ActsSquareMatrix covarianceMeasurement = trackState.template calibratedCovariance(); - const auto safeInvCovMeasurement = safeInverse(covarianceMeasurement); - if (!safeInvCovMeasurement) { - ACTS_WARNING("addMeasurementToGx2fSums: safeInvCovMeasurement failed."); - ACTS_VERBOSE(" covarianceMeasurement:\n" << covarianceMeasurement); - return; - } - - // Create an extended Jacobian. This one contains only eBoundSize rows, - // because the rest is irrelevant. We fill it in the next steps. - // TODO make dimsExtendedParams template with unrolling - Eigen::MatrixXd extendedJacobian = - Eigen::MatrixXd::Zero(eBoundSize, extendedSystem.nDims()); - - // This part of the Jacobian comes from the material-less propagation - extendedJacobian.topLeftCorner() = - jacobianFromStart[0]; - - // If we have material, loop here over all Jacobians. We add extra columns for - // their phi-theta projections. These parts account for the propagation of the - // scattering angles. - for (std::size_t matSurface = 1; matSurface < jacobianFromStart.size(); - matSurface++) { - const BoundMatrix jac = jacobianFromStart[matSurface]; - - const ActsMatrix jacPhiTheta = - jac * Gx2fConstants::phiThetaProjector; - - // The position, where we need to insert the values in the extended Jacobian - const std::size_t deltaPosition = eBoundSize + 2 * (matSurface - 1); - - extendedJacobian.block(0, deltaPosition) = jacPhiTheta; - } - const BoundVector predicted = trackState.smoothed(); const ActsVector measurement = @@ -425,54 +413,9 @@ void addMeasurementToGx2fSums(Gx2fSystem& extendedSystem, const ActsMatrix projector = trackState.template projectorSubspaceHelper().projector(); - const Eigen::MatrixXd projJacobian = projector * extendedJacobian; - - const ActsMatrix projPredicted = projector * predicted; - - const ActsVector residual = measurement - projPredicted; - - // Finally contribute to chi2sum, aMatrix, and bVector - extendedSystem.chi2() += - (residual.transpose() * (*safeInvCovMeasurement) * residual)(0, 0); - - extendedSystem.aMatrix() += - (projJacobian.transpose() * (*safeInvCovMeasurement) * projJacobian) - .eval(); - - extendedSystem.bVector() += - (residual.transpose() * (*safeInvCovMeasurement) * projJacobian) - .eval() - .transpose(); - - ACTS_VERBOSE( - "Contributions in addMeasurementToGx2fSums:\n" - << " kMeasDim: " << kMeasDim << "\n" - << " predicted: " << predicted.transpose() << "\n" - << " measurement: " << measurement.transpose() << "\n" - << " covarianceMeasurement:\n" - << covarianceMeasurement << "\n" - << " projector:\n" - << projector.eval() << "\n" - << " projJacobian:\n" - << projJacobian.eval() << "\n" - << " projPredicted: " << (projPredicted.transpose()).eval() << "\n" - << " residual: " << (residual.transpose()).eval() << "\n" - << " extendedJacobian:\n" - << extendedJacobian << "\n" - << " aMatrix contribution:\n" - << (projJacobian.transpose() * (*safeInvCovMeasurement) * projJacobian) - .eval() - << "\n" - << " bVector contribution: " - << (residual.transpose() * (*safeInvCovMeasurement) * projJacobian).eval() - << "\n" - << " chi2sum contribution: " - << (residual.transpose() * (*safeInvCovMeasurement) * residual)(0, 0) - << "\n" - << " safeInvCovMeasurement:\n" - << (*safeInvCovMeasurement)); - - return; + addMeasurementToGx2fSumsBackend(extendedSystem, jacobianFromStart, + covarianceMeasurement, predicted, measurement, + projector, logger); } /// @brief Process material and fill the aMatrix and bVector @@ -694,6 +637,15 @@ std::size_t countMaterialStates( return nMaterialSurfaces; } +/// @brief Solve the gx2f system to get the delta parameters for the update +/// +/// This function computes the delta parameters for the GX2F Actor fitting +/// process by solving the linear equation system [a] * delta = b. It uses the +/// column-pivoting Householder QR decomposition for numerical stability. +/// +/// @param extendedSystem All parameters of the current equation system +Eigen::VectorXd computeGx2fDeltaParams(const Gx2fSystem& extendedSystem); + /// @brief Update parameters (and scattering angles if applicable) /// /// @param params Parameters to be updated @@ -1391,10 +1343,8 @@ class Gx2Fitter { return Experimental::GlobalChiSquareFitterError::NotEnoughMeasurements; } - // calculate delta params [a] * delta = b Eigen::VectorXd deltaParamsExtended = - extendedSystem.aMatrix().colPivHouseholderQr().solve( - extendedSystem.bVector()); + computeGx2fDeltaParams(extendedSystem); ACTS_VERBOSE("aMatrix:\n" << extendedSystem.aMatrix() << "\n" @@ -1558,10 +1508,8 @@ class Gx2Fitter { return Experimental::GlobalChiSquareFitterError::NotEnoughMeasurements; } - // calculate delta params [a] * delta = b Eigen::VectorXd deltaParamsExtended = - extendedSystem.aMatrix().colPivHouseholderQr().solve( - extendedSystem.bVector()); + computeGx2fDeltaParams(extendedSystem); ACTS_VERBOSE("aMatrix:\n" << extendedSystem.aMatrix() << "\n" diff --git a/Core/src/TrackFitting/GlobalChiSquareFitter.cpp b/Core/src/TrackFitting/GlobalChiSquareFitter.cpp index 50b8b4cab8c..4062124496e 100644 --- a/Core/src/TrackFitting/GlobalChiSquareFitter.cpp +++ b/Core/src/TrackFitting/GlobalChiSquareFitter.cpp @@ -50,3 +50,98 @@ void Acts::Experimental::updateGx2fCovarianceParams( return; } + +void Acts::Experimental::addMeasurementToGx2fSumsBackend( + Gx2fSystem& extendedSystem, + const std::vector& jacobianFromStart, + const Eigen::MatrixXd& covarianceMeasurement, const BoundVector& predicted, + const Eigen::VectorXd& measurement, const Eigen::MatrixXd& projector, + const Logger& logger) { + // First, w try to invert the covariance matrix. If the inversion fails, we + // can already abort. + const auto safeInvCovMeasurement = safeInverse(covarianceMeasurement); + if (!safeInvCovMeasurement) { + ACTS_WARNING("addMeasurementToGx2fSums: safeInvCovMeasurement failed."); + ACTS_VERBOSE(" covarianceMeasurement:\n" << covarianceMeasurement); + return; + } + + // Create an extended Jacobian. This one contains only eBoundSize rows, + // because the rest is irrelevant. We fill it in the next steps. + // TODO make dimsExtendedParams template with unrolling + Eigen::MatrixXd extendedJacobian = + Eigen::MatrixXd::Zero(eBoundSize, extendedSystem.nDims()); + + // This part of the Jacobian comes from the material-less propagation + extendedJacobian.topLeftCorner() = + jacobianFromStart[0]; + + // If we have material, loop here over all Jacobians. We add extra columns for + // their phi-theta projections. These parts account for the propagation of the + // scattering angles. + for (std::size_t matSurface = 1; matSurface < jacobianFromStart.size(); + matSurface++) { + const BoundMatrix jac = jacobianFromStart[matSurface]; + + const ActsMatrix jacPhiTheta = + jac * Gx2fConstants::phiThetaProjector; + + // The position, where we need to insert the values in the extended Jacobian + const std::size_t deltaPosition = eBoundSize + 2 * (matSurface - 1); + + extendedJacobian.template block(0, deltaPosition) = + jacPhiTheta; + } + + const Eigen::MatrixXd projJacobian = projector * extendedJacobian; + + const Eigen::VectorXd projPredicted = projector * predicted; + + const Eigen::VectorXd residual = measurement - projPredicted; + + // Finally contribute to chi2sum, aMatrix, and bVector + extendedSystem.chi2() += + (residual.transpose() * (*safeInvCovMeasurement) * residual)(0, 0); + + extendedSystem.aMatrix() += + (projJacobian.transpose() * (*safeInvCovMeasurement) * projJacobian) + .eval(); + + extendedSystem.bVector() += + (residual.transpose() * (*safeInvCovMeasurement) * projJacobian) + .eval() + .transpose(); + + ACTS_VERBOSE( + "Contributions in addMeasurementToGx2fSums:\n" + << " predicted: " << predicted.transpose() << "\n" + << " measurement: " << measurement.transpose() << "\n" + << " covarianceMeasurement:\n" + << covarianceMeasurement << "\n" + << " projector:\n" + << projector.eval() << "\n" + << " projJacobian:\n" + << projJacobian.eval() << "\n" + << " projPredicted: " << (projPredicted.transpose()).eval() << "\n" + << " residual: " << (residual.transpose()).eval() << "\n" + << " extendedJacobian:\n" + << extendedJacobian << "\n" + << " aMatrix contribution:\n" + << (projJacobian.transpose() * (*safeInvCovMeasurement) * projJacobian) + .eval() + << "\n" + << " bVector contribution: " + << (residual.transpose() * (*safeInvCovMeasurement) * projJacobian).eval() + << "\n" + << " chi2sum contribution: " + << (residual.transpose() * (*safeInvCovMeasurement) * residual)(0, 0) + << "\n" + << " safeInvCovMeasurement:\n" + << (*safeInvCovMeasurement)); +} + +Eigen::VectorXd Acts::Experimental::computeGx2fDeltaParams( + const Acts::Experimental::Gx2fSystem& extendedSystem) { + return extendedSystem.aMatrix().colPivHouseholderQr().solve( + extendedSystem.bVector()); +} From 8eba09e4ca2ecd6ab34330788f21e0f1c04e976d Mon Sep 17 00:00:00 2001 From: Andreas Salzburger Date: Fri, 6 Dec 2024 11:05:21 +0100 Subject: [PATCH 07/21] feat: TGeo python binding (#3885) This PR adds some first python bindings for the `TGeo` plugin. The added functionality is mainly for testing reason, it allows to you test whether sensitive elements from a `TGeo` description (or those one picks as sensitive) can be translated into TGeoDetectorElements. --- Examples/Python/CMakeLists.txt | 7 +++ Examples/Python/src/ModuleEntry.cpp | 2 + Examples/Python/src/TGeo.cpp | 87 +++++++++++++++++++++++++++++ Examples/Python/src/TGeoStub.cpp | 15 +++++ 4 files changed, 111 insertions(+) create mode 100644 Examples/Python/src/TGeo.cpp create mode 100644 Examples/Python/src/TGeoStub.cpp diff --git a/Examples/Python/CMakeLists.txt b/Examples/Python/CMakeLists.txt index 0eb4b1ff4ec..dc359db0d0d 100644 --- a/Examples/Python/CMakeLists.txt +++ b/Examples/Python/CMakeLists.txt @@ -97,6 +97,13 @@ else() target_sources(ActsPythonBindings PRIVATE src/GeoModelStub.cpp) endif() +if(ACTS_BUILD_PLUGIN_TGEO) + target_link_libraries(ActsPythonBindings PUBLIC ActsPluginTGeo) + target_sources(ActsPythonBindings PRIVATE src/TGeo.cpp) +else() + target_sources(ActsPythonBindings PRIVATE src/TGeoStub.cpp) +endif() + if(ACTS_BUILD_PLUGIN_TRACCC) target_link_libraries(ActsPythonBindings PUBLIC ActsPluginDetray) target_sources(ActsPythonBindings PRIVATE src/Detray.cpp) diff --git a/Examples/Python/src/ModuleEntry.cpp b/Examples/Python/src/ModuleEntry.cpp index a74d277f7a5..a78f04f9565 100644 --- a/Examples/Python/src/ModuleEntry.cpp +++ b/Examples/Python/src/ModuleEntry.cpp @@ -75,6 +75,7 @@ void addUtilities(Context& ctx); void addDigitization(Context& ctx); void addPythia8(Context& ctx); void addGeoModel(Context& ctx); +void addTGeo(Context& ctx); void addJson(Context& ctx); void addDetray(Context& ctx); void addHepMC3(Context& ctx); @@ -146,6 +147,7 @@ PYBIND11_MODULE(ActsPythonBindings, m) { addPythia8(ctx); addJson(ctx); addGeoModel(ctx); + addTGeo(ctx); addDetray(ctx); addHepMC3(ctx); addExaTrkXTrackFinding(ctx); diff --git a/Examples/Python/src/TGeo.cpp b/Examples/Python/src/TGeo.cpp new file mode 100644 index 00000000000..c67c2f3d653 --- /dev/null +++ b/Examples/Python/src/TGeo.cpp @@ -0,0 +1,87 @@ +// 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 "Acts/Plugins/Python/Utilities.hpp" +#include "Acts/Plugins/TGeo/TGeoDetectorElement.hpp" +#include "Acts/Plugins/TGeo/TGeoLayerBuilder.hpp" +#include "Acts/Plugins/TGeo/TGeoParser.hpp" + +#include + +#include +#include +#include +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; + +namespace Acts::Python { +void addTGeo(Context& ctx) { + auto [m, mex] = ctx.get("main", "examples"); + + auto tgeo = mex.def_submodule("tgeo"); + + { + py::class_>( + tgeo, "TGeoDetectorElement") + .def("surface", [](const Acts::TGeoDetectorElement& self) { + return self.surface().getSharedPtr(); + }); + } + + { + /// Helper function to test if the automatic geometry conversion works + /// + /// @param rootFileName is the name of the GDML file + /// @param sensitiveMatches is a list of strings to match sensitive volumes + /// @param localAxes is the TGeo->ACTS axis conversion convention + /// @param scaleConversion is a unit scalor conversion factor + tgeo.def("_convertToElements", + [](const std::string& rootFileName, + const std::vector& sensitiveMatches, + const std::string& localAxes, double scaleConversion) { + // Return vector + std::vector> + tgElements; + // TGeo import + TGeoManager::Import(rootFileName.c_str()); + if (gGeoManager != nullptr) { + auto tVolume = gGeoManager->GetTopVolume(); + if (tVolume != nullptr) { + TGeoHMatrix gmatrix = TGeoIdentity(tVolume->GetName()); + + TGeoParser::Options tgpOptions; + tgpOptions.volumeNames = {tVolume->GetName()}; + tgpOptions.targetNames = sensitiveMatches; + tgpOptions.unit = scaleConversion; + TGeoParser::State tgpState; + tgpState.volume = tVolume; + tgpState.onBranch = true; + + TGeoParser::select(tgpState, tgpOptions, gmatrix); + tgElements.reserve(tgpState.selectedNodes.size()); + + for (const auto& snode : tgpState.selectedNodes) { + auto identifier = Acts::TGeoDetectorElement::Identifier(); + auto tgElement = TGeoLayerBuilder::defaultElementFactory( + identifier, *snode.node, *snode.transform, localAxes, + scaleConversion, nullptr); + tgElements.emplace_back(tgElement); + } + } + } + // Return the elements + return tgElements; + }); + } +} + +} // namespace Acts::Python diff --git a/Examples/Python/src/TGeoStub.cpp b/Examples/Python/src/TGeoStub.cpp new file mode 100644 index 00000000000..09a800e693f --- /dev/null +++ b/Examples/Python/src/TGeoStub.cpp @@ -0,0 +1,15 @@ +// 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/. + +namespace Acts::Python { +struct Context; +} // namespace Acts::Python + +namespace Acts::Python { +void addTGeo(Context& /*ctx*/) {} +} // namespace Acts::Python From 2862173924e232c0ed3800ba6fc0f9abb59f3d6e Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 6 Dec 2024 17:42:32 +0100 Subject: [PATCH 08/21] ci: Lint updates (#3958) 1. Remove redundant extra CI jobs that are covered by the pre-commit job 2. Make codespell write changes (more convenient to use from pre-commit) --- .github/workflows/checks.yml | 81 ++---------------------------------- .pre-commit-config.yaml | 3 +- 2 files changed, 5 insertions(+), 79 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index e84044776a8..59ba76bb00a 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -38,51 +38,6 @@ jobs: - name: Run pre-commit run: pre-commit run --all-files --show-diff-on-failure - license: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Check - run: > - sudo apt-get install -y git - && CI/check_license.py . --exclude "*thirdparty/*" - include_guards: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Check - run: > - CI/check_include_guards.py . --fail-global --exclude "*thirdparty/*" - pragma_once: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check - run: > - CI/check_pragma_once.sh - type_t: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Check - run: > - CI/check_type_t.py . --exclude "thirdparty/*" - boost_test_macro: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Check - run: > - CI/check_boost_test_macro.sh smearing_config: runs-on: ubuntu-latest steps: @@ -93,39 +48,7 @@ jobs: - name: Check run: > CI/check_smearing_config.py . - cmake_options: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Check - run: > - docs/parse_cmake_options.py CMakeLists.txt --write docs/getting_started.md --verify - spelling: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Install codespell - run: > - pip install codespell==2.2.5 - - name: Check - run: > - CI/check_spelling - math_macros: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: '3.12' - - name: Check - run: > - CI/check_math_macros.py . --exclude "thirdparty/*" + missing_includes: runs-on: ubuntu-latest steps: @@ -136,6 +59,7 @@ jobs: - name: Check run: > CI/missing_include_check.sh + fpe_masks: runs-on: ubuntu-latest steps: @@ -149,6 +73,7 @@ jobs: - name: Check run: > CI/check_fpe_masks.py --token ${{ secrets.GITHUB_TOKEN }} + unused_files: runs-on: ubuntu-latest steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 4158678a3e0..d4c224bf3c7 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -35,7 +35,8 @@ repos: - id: codespell args: [ "-S", "*.ipynb,*.onnx,_build,*.svg", - "-I", "./CI/codespell_ignore.txt" + "-I", "./CI/codespell_ignore.txt", + "-w" ] exclude: ^CI/.*$ From 12ea68c11818c0a95ab0687719cbda781037870c Mon Sep 17 00:00:00 2001 From: Benjamin Huth <37871400+benjaminhuth@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:24:21 +0100 Subject: [PATCH 09/21] ci: Reenable some ExaTrkX tests (#3395) These tests should work even though cugraph is not available anymore... ## Summary by CodeRabbit - **New Features** - Introduced new test jobs for enhanced testing capabilities. - Added a new algorithm for converting prototracks to tracks, improving data processing. - **Bug Fixes** - Updated input parameters for existing algorithms to ensure consistency and accuracy in data flow. - **Chores** - Improved cache management in the CI pipeline for better build efficiency. - Updated hash values for test files to reflect recent changes. --- .gitlab-ci.yml | 67 ++++++++++------- .../TrackFindingExaTrkX/CMakeLists.txt | 1 + .../python/acts/examples/reconstruction.py | 24 ++++--- Examples/Python/tests/root_file_hashes.txt | 2 +- Examples/Scripts/Python/exatrkx.py | 71 ++++++++++++------- 5 files changed, 102 insertions(+), 63 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c897c947010..e278b18eade 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -142,33 +142,46 @@ build_exatrkx: - cmake --build build -- -j6 - ccache -s -# test_exatrkx_unittests: -# stage: test -# needs: -# - build_exatrkx -# image: ghcr.io/acts-project/ubuntu2204_exatrkx:63 -# tags: -# - docker-gpu-nvidia -# script: -# - ctest --test-dir build -R ExaTrkX -# -# test_exatrkx_python: -# stage: test -# needs: -# - build_exatrkx -# image: ghcr.io/acts-project/ubuntu2204_exatrkx:63 -# tags: -# - docker-gpu-nvidia -# script: -# - apt-get update -y -# - apt-get install -y python3 libxxhash0 -# - source build/this_acts_withdeps.sh -# - git clone $CLONE_URL src -# - cd src -# - git checkout $HEAD_SHA -# - pip3 install -r Examples/Python/tests/requirements.txt -# - nvidia-smi -# - pytest -rFsv -k test_exatrkx +test_exatrkx_unittests: + stage: test + needs: + - build_exatrkx + image: ghcr.io/acts-project/ubuntu2204_exatrkx:63 + variables: + DEPENDENCY_URL: https://acts.web.cern.ch/ACTS/ci/ubuntu-22.04/deps.$DEPENDENCY_TAG.tar.zst + tags: + - docker-gpu-nvidia + script: + + - apt-get update -y + - git clone $CLONE_URL src + - source src/CI/dependencies.sh + - ctest --test-dir build -R ExaTrkX + +test_exatrkx_python: + stage: test + needs: + - build_exatrkx + image: ghcr.io/acts-project/ubuntu2204_exatrkx:63 + variables: + DEPENDENCY_URL: https://acts.web.cern.ch/ACTS/ci/ubuntu-22.04/deps.$DEPENDENCY_TAG.tar.zst + tags: + - docker-gpu-nvidia + script: + - apt-get update -y + - git clone $CLONE_URL src + - cd src + - git checkout $HEAD_SHA + - nvidia-smi + - source CI/dependencies.sh + - source ../build/this_acts_withdeps.sh + - python3 -m pip install -r Examples/Python/tests/requirements.txt + - echo $PYTHONPATH + - which python3 + - python3 --version + - python3 -c "import acts" + - pytest -rFsv -k torch --collect-only + - pytest -rFsv -k gpu-torch # For now only test torch GPU pipeline build_linux_ubuntu: stage: build diff --git a/Examples/Algorithms/TrackFindingExaTrkX/CMakeLists.txt b/Examples/Algorithms/TrackFindingExaTrkX/CMakeLists.txt index 85866466ec6..64255b89b14 100644 --- a/Examples/Algorithms/TrackFindingExaTrkX/CMakeLists.txt +++ b/Examples/Algorithms/TrackFindingExaTrkX/CMakeLists.txt @@ -5,6 +5,7 @@ add_library( src/PrototracksToParameters.cpp src/TrackFindingFromPrototrackAlgorithm.cpp src/TruthGraphBuilder.cpp + src/createFeatures.cpp ) target_include_directories( diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index 727bf9e9785..43792827e8d 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -1786,13 +1786,12 @@ def addExaTrkX( if backend == ExaTrkXBackend.Torch: metricLearningConfig["modelPath"] = str(modelDir / "embed.pt") - metricLearningConfig["numFeatures"] = 3 + metricLearningConfig["selectedFeatures"] = [0, 1, 2] filterConfig["modelPath"] = str(modelDir / "filter.pt") - filterConfig["nChunks"] = 10 - filterConfig["numFeatures"] = 3 + filterConfig["selectedFeatures"] = [0, 1, 2] gnnConfig["modelPath"] = str(modelDir / "gnn.pt") gnnConfig["undirected"] = True - gnnConfig["numFeatures"] = 3 + gnnConfig["selectedFeatures"] = [0, 1, 2] graphConstructor = acts.examples.TorchMetricLearning(**metricLearningConfig) edgeClassifiers = [ @@ -1824,11 +1823,18 @@ def addExaTrkX( s.addAlgorithm(findingAlg) s.addWhiteboardAlias("prototracks", findingAlg.config.outputProtoTracks) - # TODO convert prototracks to tracks + s.addAlgorithm( + acts.examples.PrototracksToTracks( + level=customLogLevel(), + inputProtoTracks="prototracks", + inputMeasurements="measurements", + outputTracks="tracks", + ) + ) matchAlg = acts.examples.TrackTruthMatcher( level=customLogLevel(), - inputProtoTracks=findingAlg.config.outputProtoTracks, + inputTracks="tracks", inputParticles="particles", inputMeasurementParticlesMap="measurement_particles_map", outputTrackParticleMatching="exatrkx_track_particle_matching", @@ -1843,14 +1849,12 @@ def addExaTrkX( "particle_track_matching", matchAlg.config.outputParticleTrackMatching ) - # Write truth track finding / seeding performance if outputDirRoot is not None: s.addWriter( acts.examples.TrackFinderNTupleWriter( level=customLogLevel(), - inputProtoTracks=findingAlg.config.outputProtoTracks, - # the original selected particles after digitization - inputParticles="particles_initial", + inputTracks="tracks", + inputParticles="particles", inputParticleMeasurementsMap="particle_measurements_map", inputTrackParticleMatching=matchAlg.config.outputTrackParticleMatching, filePath=str(Path(outputDirRoot) / "performance_track_finding.root"), diff --git a/Examples/Python/tests/root_file_hashes.txt b/Examples/Python/tests/root_file_hashes.txt index 6eeaaebff44..e1b2833c2eb 100644 --- a/Examples/Python/tests/root_file_hashes.txt +++ b/Examples/Python/tests/root_file_hashes.txt @@ -72,7 +72,7 @@ test_root_clusters_writer[configKwConstructor]__clusters.root: e842df4fe04eefff3 test_root_clusters_writer[kwargsConstructor]__clusters.root: e842df4fe04eefff3df5f32cd1026e93286be62b8040dc700a2aff557c56dec8 test_exatrkx[cpu-torch]__performance_track_finding.root: 36b3045589c4c17c038dbc87943366f4af4440f7eea6887afb763871ac149b05 test_exatrkx[gpu-onnx]__performance_track_finding.root: 9090de10ffb1489d3f1993e2a3081a3038227e3e5c453e98a9a4f33ea3d6d817 -test_exatrkx[gpu-torch]__performance_track_finding.root: 36b3045589c4c17c038dbc87943366f4af4440f7eea6887afb763871ac149b05 +test_exatrkx[gpu-torch]__performance_track_finding.root: 6b658fa22c7532e082eaab7aa4b71b852f1c324adcc59d1156aff45124b222d9 test_ML_Ambiguity_Solver__performance_finding_ambiML.root: 166dd8bb189097c4957b7b02c04c41267868d72d9a08c4bb892985b06849cb76 test_refitting[odd]__trackstates_gsf_refit.root: e297749dc1e7eda3b8dea13defa0499986c584740d93e723a901b498b8e90c71 test_refitting[odd]__tracksummary_gsf_refit.root: d5085882e45a0b699194dff9f40a36e9291227bf65f9aaaf9087f9242ef5ae22 diff --git a/Examples/Scripts/Python/exatrkx.py b/Examples/Scripts/Python/exatrkx.py index b2a151fc4e7..5a8920a318a 100755 --- a/Examples/Scripts/Python/exatrkx.py +++ b/Examples/Scripts/Python/exatrkx.py @@ -1,16 +1,52 @@ #!/usr/bin/env python3 from pathlib import Path +import os +import sys import acts.examples import acts +from acts.examples.reconstruction import addExaTrkX, ExaTrkXBackend from acts import UnitConstants as u +from digitization import runDigitization + + +def runGNNTrackFinding( + trackingGeometry, + field, + outputDir, + digiConfigFile, + geometrySelection, + backend, + modelDir, + outputRoot=False, + outputCsv=False, + s=None, +): + s = runDigitization( + trackingGeometry, + field, + outputDir, + digiConfigFile=digiConfigFile, + particlesInput=None, + outputRoot=outputRoot, + outputCsv=outputCsv, + s=s, + ) + + addExaTrkX( + s, + trackingGeometry, + geometrySelection, + modelDir, + backend=backend, + outputDirRoot=outputDir if outputRoot else None, + ) + + s.run() + if "__main__" == __name__: - import os - import sys - from digitization import runDigitization - from acts.examples.reconstruction import addExaTrkX, ExaTrkXBackend backend = ExaTrkXBackend.Torch @@ -19,16 +55,10 @@ if "torch" in sys.argv: backend = ExaTrkXBackend.Torch - srcdir = Path(__file__).resolve().parent.parent.parent.parent - detector, trackingGeometry, decorators = acts.examples.GenericDetector.create() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) - inputParticlePath = Path("particles.root") - if not inputParticlePath.exists(): - inputParticlePath = None - srcdir = Path(__file__).resolve().parent.parent.parent.parent geometrySelection = ( @@ -60,24 +90,15 @@ rnd = acts.examples.RandomNumbers() outputDir = Path(os.getcwd()) - s = runDigitization( + runGNNTrackFinding( trackingGeometry, field, outputDir, - digiConfigFile=digiConfigFile, - particlesInput=inputParticlePath, - outputRoot=True, - outputCsv=True, - s=s, - ) - - addExaTrkX( - s, - trackingGeometry, + digiConfigFile, geometrySelection, + backend, modelDir, - outputDir, - backend=backend, + outputRoot=True, + outputCsv=False, + s=s, ) - - s.run() From 03315f87b1c6a1d4ff17341e160f484753af81e1 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Fri, 6 Dec 2024 23:41:36 +0100 Subject: [PATCH 10/21] fix: Distinguish between hits and measurements in `ParticleSelector` in Examples (#3947) This clears out an oversight in a previous PR https://github.com/acts-project/acts/pull/3742 where I replaced numbers of measurements with number of hits. This number can be different and should not be confused. I add an optional input for the particle to measurement map which is then queried if present and used for the number of measurement cuts. blocked by: - https://github.com/acts-project/acts/pull/3944 ## Summary by CodeRabbit ## Release Notes - **New Features** - Enhanced particle selection with new configuration options for minimum and maximum hit counts. - Updated particle selection criteria to focus on hit counts instead of measurement counts. - **Bug Fixes** - Improved validation checks for input measurements to ensure proper initialization. - **Documentation** - Updated comments and documentation to reflect changes in parameter naming from "measurements" to "hits." - **Tests** - Adjusted test cases to align with the new parameter naming conventions. - **Chores** - Minor code formatting and organization improvements throughout various scripts. --- .../workflows/physmon_trackfinding_1muon.py | 2 +- .../physmon_trackfinding_4muon_50vertices.py | 2 +- .../physmon_trackfinding_ttbar_pu200.py | 2 +- .../TruthTracking/ParticleSelector.cpp | 43 +++++++++++++++---- .../TruthTracking/ParticleSelector.hpp | 11 ++++- .../Python/python/acts/examples/simulation.py | 5 ++- Examples/Python/src/TruthTracking.cpp | 2 + Examples/Python/tests/test_examples.py | 2 +- Examples/Scripts/Optimization/ckf.py | 2 +- Examples/Scripts/Python/ckf_tracks.py | 2 +- Examples/Scripts/Python/full_chain_itk.py | 2 +- .../Scripts/Python/full_chain_itk_Gbts.py | 2 +- Examples/Scripts/Python/full_chain_odd.py | 4 +- Examples/Scripts/Python/full_chain_odd_LRT.py | 4 +- Examples/Scripts/Python/full_chain_test.py | 2 +- Examples/Scripts/Python/hashing_seeding.py | 2 +- Examples/Scripts/Python/seeding.py | 2 +- Examples/Scripts/Python/truth_tracking_gsf.py | 2 +- .../Scripts/Python/truth_tracking_gx2f.py | 2 +- .../Scripts/Python/truth_tracking_kalman.py | 2 +- 20 files changed, 69 insertions(+), 28 deletions(-) diff --git a/CI/physmon/workflows/physmon_trackfinding_1muon.py b/CI/physmon/workflows/physmon_trackfinding_1muon.py index 90305112099..028b30ce5e5 100755 --- a/CI/physmon/workflows/physmon_trackfinding_1muon.py +++ b/CI/physmon/workflows/physmon_trackfinding_1muon.py @@ -74,7 +74,7 @@ def run_ckf_tracking(label, seeding): rnd=rnd, postSelectParticles=ParticleSelectorConfig( pt=(0.9 * u.GeV, None), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), ) diff --git a/CI/physmon/workflows/physmon_trackfinding_4muon_50vertices.py b/CI/physmon/workflows/physmon_trackfinding_4muon_50vertices.py index 1a119c8ed6c..85debced2b4 100755 --- a/CI/physmon/workflows/physmon_trackfinding_4muon_50vertices.py +++ b/CI/physmon/workflows/physmon_trackfinding_4muon_50vertices.py @@ -71,7 +71,7 @@ rnd=rnd, postSelectParticles=ParticleSelectorConfig( pt=(0.9 * u.GeV, None), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), ) diff --git a/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py b/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py index 4b4c75e1f36..de4021a5055 100755 --- a/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py +++ b/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py @@ -71,7 +71,7 @@ ), postSelectParticles=ParticleSelectorConfig( pt=(0.5 * u.GeV, None), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), ) diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.cpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.cpp index b4ef0928da0..6896763f32c 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.cpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.cpp @@ -8,8 +8,8 @@ #include "ActsExamples/TruthTracking/ParticleSelector.hpp" -#include "Acts/Definitions/Common.hpp" #include "Acts/Utilities/VectorHelpers.hpp" +#include "ActsExamples/EventData/Index.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Framework/AlgorithmContext.hpp" @@ -17,8 +17,10 @@ #include #include -ActsExamples::ParticleSelector::ParticleSelector(const Config& config, - Acts::Logging::Level level) +namespace ActsExamples { + +ParticleSelector::ParticleSelector(const Config& config, + Acts::Logging::Level level) : IAlgorithm("ParticleSelector", level), m_cfg(config) { if (m_cfg.inputParticles.empty()) { throw std::invalid_argument("Missing input particles collection"); @@ -28,8 +30,17 @@ ActsExamples::ParticleSelector::ParticleSelector(const Config& config, } m_inputParticles.initialize(m_cfg.inputParticles); + m_inputParticleMeasurementsMap.maybeInitialize( + m_cfg.inputParticleMeasurementsMap); m_outputParticles.initialize(m_cfg.outputParticles); + if (!m_inputParticleMeasurementsMap.isInitialized() && + (m_cfg.measurementsMin > 0 || + m_cfg.measurementsMax < std::numeric_limits::max())) { + throw std::invalid_argument( + "Measurement-based cuts require the inputMeasurementParticlesMap"); + } + ACTS_DEBUG("selection particle rho [" << m_cfg.rhoMin << "," << m_cfg.rhoMax << ")"); ACTS_DEBUG("selection particle |z| [" << m_cfg.absZMin << "," << m_cfg.absZMax @@ -46,6 +57,8 @@ ActsExamples::ParticleSelector::ParticleSelector(const Config& config, << ")"); ACTS_DEBUG("selection particle m [" << m_cfg.mMin << "," << m_cfg.mMax << ")"); + ACTS_DEBUG("selection particle hits [" << m_cfg.hitsMin << "," + << m_cfg.hitsMax << ")"); ACTS_DEBUG("selection particle measurements [" << m_cfg.measurementsMin << "," << m_cfg.measurementsMax << ")"); ACTS_DEBUG("remove charged particles " << m_cfg.removeCharged); @@ -59,12 +72,18 @@ ActsExamples::ParticleSelector::ParticleSelector(const Config& config, << m_cfg.maxPrimaryVertexId << ")"); } -ActsExamples::ProcessCode ActsExamples::ParticleSelector::execute( - const AlgorithmContext& ctx) const { +ProcessCode ParticleSelector::execute(const AlgorithmContext& ctx) const { // prepare input/ output types const SimParticleContainer& inputParticles = m_inputParticles(ctx); + const static InverseMultimap emptyMeasurementParticlesMap; + const InverseMultimap& inputMeasurementParticlesMap = + m_inputParticleMeasurementsMap.isInitialized() + ? m_inputParticleMeasurementsMap(ctx) + : emptyMeasurementParticlesMap; + std::size_t nInvalidCharge = 0; + std::size_t nInvalidHitCount = 0; std::size_t nInvalidMeasurementCount = 0; // helper functions to select tracks @@ -87,9 +106,14 @@ ActsExamples::ProcessCode ActsExamples::ParticleSelector::execute( nInvalidCharge += static_cast(!validCharge); - bool validMeasurementCount = - within(p.numberOfHits(), m_cfg.measurementsMin, m_cfg.measurementsMax); + const bool validHitCount = + within(p.numberOfHits(), m_cfg.hitsMin, m_cfg.hitsMax); + nInvalidHitCount += static_cast(!validHitCount); + const std::size_t measurementCount = + inputMeasurementParticlesMap.count(p.particleId()); + const bool validMeasurementCount = + within(measurementCount, m_cfg.measurementsMin, m_cfg.measurementsMax); nInvalidMeasurementCount += static_cast(!validMeasurementCount); @@ -103,7 +127,7 @@ ActsExamples::ProcessCode ActsExamples::ParticleSelector::execute( } return validPdg && validCharge && validSecondary && validPrimaryVertexId && - validMeasurementCount && + validHitCount && validMeasurementCount && within(p.transverseMomentum(), m_cfg.ptMin, m_cfg.ptMax) && within(std::abs(eta), m_cfg.absEtaMin, m_cfg.absEtaMax) && within(eta, m_cfg.etaMin, m_cfg.etaMax) && @@ -132,6 +156,7 @@ ActsExamples::ProcessCode ActsExamples::ParticleSelector::execute( << outputParticles.size() << " from " << inputParticles.size() << " particles"); ACTS_DEBUG("filtered out because of charge: " << nInvalidCharge); + ACTS_DEBUG("filtered out because of hit count: " << nInvalidHitCount); ACTS_DEBUG("filtered out because of measurement count: " << nInvalidMeasurementCount); @@ -139,3 +164,5 @@ ActsExamples::ProcessCode ActsExamples::ParticleSelector::execute( return ProcessCode::SUCCESS; } + +} // namespace ActsExamples diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.hpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.hpp index 4bd465d1b4d..4c57c5cbeef 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.hpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/ParticleSelector.hpp @@ -9,6 +9,7 @@ #pragma once #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/EventData/Index.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Framework/DataHandle.hpp" #include "ActsExamples/Framework/IAlgorithm.hpp" @@ -26,6 +27,9 @@ class ParticleSelector final : public IAlgorithm { struct Config { /// The input particles collection. std::string inputParticles; + /// (Optionally) The input particle measurements map. Only required for + /// measurement-based cuts. + std::string inputParticleMeasurementsMap; /// The output particles collection. std::string outputParticles; @@ -51,7 +55,10 @@ class ParticleSelector final : public IAlgorithm { // Rest mass cuts double mMin = 0; double mMax = std::numeric_limits::infinity(); - /// Measurement number cuts + // Hit count cuts + std::size_t hitsMin = 0; + std::size_t hitsMax = std::numeric_limits::max(); + // Measurement number cuts std::size_t measurementsMin = 0; std::size_t measurementsMax = std::numeric_limits::max(); /// Remove charged particles. @@ -81,6 +88,8 @@ class ParticleSelector final : public IAlgorithm { Config m_cfg; ReadDataHandle m_inputParticles{this, "InputParticles"}; + ReadDataHandle> m_inputParticleMeasurementsMap{ + this, "InputParticleMeasurementsMap"}; WriteDataHandle m_outputParticles{this, "OutputParticles"}; diff --git a/Examples/Python/python/acts/examples/simulation.py b/Examples/Python/python/acts/examples/simulation.py index 411f4410361..ff690e2a777 100644 --- a/Examples/Python/python/acts/examples/simulation.py +++ b/Examples/Python/python/acts/examples/simulation.py @@ -41,12 +41,13 @@ "absEta", # (min,max) "pt", # (min,max) "m", # (min,max) + "hits", # (min,max) "measurements", # (min,max) "removeCharged", # bool "removeNeutral", # bool "removeSecondaries", # bool ], - defaults=[(None, None)] * 9 + [None] * 3, + defaults=[(None, None)] * 10 + [None] * 3, ) @@ -393,6 +394,8 @@ def addParticleSelection( ptMax=config.pt[1], mMin=config.m[0], mMax=config.m[1], + hitsMin=config.hits[0], + hitsMax=config.hits[1], measurementsMin=config.measurements[0], measurementsMax=config.measurements[1], removeCharged=config.removeCharged, diff --git a/Examples/Python/src/TruthTracking.cpp b/Examples/Python/src/TruthTracking.cpp index 4799d93dbdb..4d3569f4656 100644 --- a/Examples/Python/src/TruthTracking.cpp +++ b/Examples/Python/src/TruthTracking.cpp @@ -84,6 +84,8 @@ void addTruthTracking(Context& ctx) { ACTS_PYTHON_MEMBER(mMax); ACTS_PYTHON_MEMBER(ptMin); ACTS_PYTHON_MEMBER(ptMax); + ACTS_PYTHON_MEMBER(hitsMin); + ACTS_PYTHON_MEMBER(hitsMax); ACTS_PYTHON_MEMBER(measurementsMin); ACTS_PYTHON_MEMBER(measurementsMax); ACTS_PYTHON_MEMBER(removeCharged); diff --git a/Examples/Python/tests/test_examples.py b/Examples/Python/tests/test_examples.py index 2261e58cea4..8441514c26b 100644 --- a/Examples/Python/tests/test_examples.py +++ b/Examples/Python/tests/test_examples.py @@ -427,7 +427,7 @@ def test_itk_seeding(tmp_path, trk_geo, field, assert_root_hash): postSelectParticles=ParticleSelectorConfig( pt=(0.9 * u.GeV, None), eta=(-4, 4), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), ) diff --git a/Examples/Scripts/Optimization/ckf.py b/Examples/Scripts/Optimization/ckf.py index e0cd5fd9870..0077f35317b 100755 --- a/Examples/Scripts/Optimization/ckf.py +++ b/Examples/Scripts/Optimization/ckf.py @@ -171,7 +171,7 @@ def runCKFTracks( rnd=rnd, postSelectParticles=ParticleSelectorConfig( pt=(0.5 * u.GeV, None), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), ) diff --git a/Examples/Scripts/Python/ckf_tracks.py b/Examples/Scripts/Python/ckf_tracks.py index 75ec8a7c316..15a50466799 100755 --- a/Examples/Scripts/Python/ckf_tracks.py +++ b/Examples/Scripts/Python/ckf_tracks.py @@ -82,7 +82,7 @@ def runCKFTracks( rnd=rnd, postSelectParticles=ParticleSelectorConfig( pt=(0.5 * u.GeV, None), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), ) diff --git a/Examples/Scripts/Python/full_chain_itk.py b/Examples/Scripts/Python/full_chain_itk.py index b5c17278a1e..afb1df6a12b 100755 --- a/Examples/Scripts/Python/full_chain_itk.py +++ b/Examples/Scripts/Python/full_chain_itk.py @@ -74,7 +74,7 @@ postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-4.0, 4.0), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), outputDirRoot=outputDir, diff --git a/Examples/Scripts/Python/full_chain_itk_Gbts.py b/Examples/Scripts/Python/full_chain_itk_Gbts.py index ac9cc8ae8fc..a939bd7e930 100755 --- a/Examples/Scripts/Python/full_chain_itk_Gbts.py +++ b/Examples/Scripts/Python/full_chain_itk_Gbts.py @@ -69,7 +69,7 @@ postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-4.0, 4.0), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), outputDirRoot=outputDir, diff --git a/Examples/Scripts/Python/full_chain_odd.py b/Examples/Scripts/Python/full_chain_odd.py index 7ddaa02054f..50a13219afa 100755 --- a/Examples/Scripts/Python/full_chain_odd.py +++ b/Examples/Scripts/Python/full_chain_odd.py @@ -278,7 +278,7 @@ postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-3.0, 3.0), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), outputDirRoot=outputDir if args.output_root else None, @@ -306,7 +306,7 @@ postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-3.0, 3.0), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), enableInteractions=True, diff --git a/Examples/Scripts/Python/full_chain_odd_LRT.py b/Examples/Scripts/Python/full_chain_odd_LRT.py index cfa03b5336c..57f7ce85876 100644 --- a/Examples/Scripts/Python/full_chain_odd_LRT.py +++ b/Examples/Scripts/Python/full_chain_odd_LRT.py @@ -273,7 +273,7 @@ postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-3.0, 3.0), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), outputDirRoot=outputDir if args.output_root else None, @@ -300,7 +300,7 @@ postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-3.0, 3.0), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), enableInteractions=True, diff --git a/Examples/Scripts/Python/full_chain_test.py b/Examples/Scripts/Python/full_chain_test.py index 595dbd43a4e..49d7dacfeed 100755 --- a/Examples/Scripts/Python/full_chain_test.py +++ b/Examples/Scripts/Python/full_chain_test.py @@ -370,7 +370,7 @@ def full_chain(args): postSelectParticles = ParticleSelectorConfig( pt=(ptMin, None), eta=etaRange if not args.generic_detector else (None, None), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ) diff --git a/Examples/Scripts/Python/hashing_seeding.py b/Examples/Scripts/Python/hashing_seeding.py index 770c34e5185..10f68b5cea9 100755 --- a/Examples/Scripts/Python/hashing_seeding.py +++ b/Examples/Scripts/Python/hashing_seeding.py @@ -215,7 +215,7 @@ def runHashingSeeding( postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-eta, eta), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), enableInteractions=True, diff --git a/Examples/Scripts/Python/seeding.py b/Examples/Scripts/Python/seeding.py index 3beb6071aae..1e5b9e50d6b 100755 --- a/Examples/Scripts/Python/seeding.py +++ b/Examples/Scripts/Python/seeding.py @@ -90,7 +90,7 @@ def runSeeding( postSelectParticles=ParticleSelectorConfig( pt=(1.0 * u.GeV, None), eta=(-2.5, 2.5), - measurements=(9, None), + hits=(9, None), removeNeutral=True, ), ) diff --git a/Examples/Scripts/Python/truth_tracking_gsf.py b/Examples/Scripts/Python/truth_tracking_gsf.py index ae8aca06d6c..4f194a65662 100755 --- a/Examples/Scripts/Python/truth_tracking_gsf.py +++ b/Examples/Scripts/Python/truth_tracking_gsf.py @@ -79,7 +79,7 @@ def runTruthTrackingGsf( enableInteractions=True, postSelectParticles=ParticleSelectorConfig( pt=(0.9 * u.GeV, None), - measurements=(7, None), + hits=(7, None), removeNeutral=True, removeSecondaries=True, ), diff --git a/Examples/Scripts/Python/truth_tracking_gx2f.py b/Examples/Scripts/Python/truth_tracking_gx2f.py index 8503dc982f4..31ebb447d42 100644 --- a/Examples/Scripts/Python/truth_tracking_gx2f.py +++ b/Examples/Scripts/Python/truth_tracking_gx2f.py @@ -76,7 +76,7 @@ def runTruthTrackingGx2f( enableInteractions=True, postSelectParticles=ParticleSelectorConfig( pt=(0.9 * u.GeV, None), - measurements=(7, None), + hits=(7, None), removeNeutral=True, removeSecondaries=True, ), diff --git a/Examples/Scripts/Python/truth_tracking_kalman.py b/Examples/Scripts/Python/truth_tracking_kalman.py index 91c18f1dd28..971f0c4bab4 100755 --- a/Examples/Scripts/Python/truth_tracking_kalman.py +++ b/Examples/Scripts/Python/truth_tracking_kalman.py @@ -84,7 +84,7 @@ def runTruthTrackingKalman( enableInteractions=True, postSelectParticles=ParticleSelectorConfig( pt=(0.9 * u.GeV, None), - measurements=(7, None), + hits=(7, None), removeNeutral=True, removeSecondaries=True, ), From 5e8efd662189ef54fddbd328c535309a97d4c857 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 7 Dec 2024 00:58:52 +0100 Subject: [PATCH 11/21] refactor: Remove `SurfaceSortingAlgorithm` from Examples (#3952) In https://github.com/acts-project/acts/pull/3944 we found out that this algorithm is not needed anymore ## Summary by CodeRabbit - **New Features** - Enhanced configurability for machine learning-based seed filtering with the addition of an ONNX model parameter. - Streamlined seeding and track fitting algorithms for improved modularity. - **Bug Fixes** - Removed unnecessary complexity by eliminating the `directNavigation` parameter from multiple functions. - **Documentation** - Updated test suite to improve clarity and maintainability. - **Chores** - Removed obsolete files and references related to the `SurfaceSortingAlgorithm` class to streamline the codebase. --- .../Algorithms/TrackFitting/CMakeLists.txt | 1 - .../TrackFitting/SurfaceSortingAlgorithm.hpp | 55 ------------ .../src/SurfaceSortingAlgorithm.cpp | 84 ------------------- .../python/acts/examples/reconstruction.py | 12 --- Examples/Python/src/TrackFitting.cpp | 17 ---- Examples/Python/tests/root_file_hashes.txt | 24 ++---- Examples/Python/tests/test_algorithms.py | 2 - Examples/Python/tests/test_examples.py | 4 +- .../Scripts/Python/truth_tracking_kalman.py | 2 - 9 files changed, 9 insertions(+), 192 deletions(-) delete mode 100644 Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp delete mode 100644 Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp diff --git a/Examples/Algorithms/TrackFitting/CMakeLists.txt b/Examples/Algorithms/TrackFitting/CMakeLists.txt index 07e26091384..a6a9a5afe3b 100644 --- a/Examples/Algorithms/TrackFitting/CMakeLists.txt +++ b/Examples/Algorithms/TrackFitting/CMakeLists.txt @@ -2,7 +2,6 @@ add_library( ActsExamplesTrackFitting SHARED src/RefittingCalibrator.cpp - src/SurfaceSortingAlgorithm.cpp src/TrackFittingAlgorithm.cpp src/KalmanFitterFunction.cpp src/RefittingAlgorithm.cpp diff --git a/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp b/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp deleted file mode 100644 index 6315e192e67..00000000000 --- a/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp +++ /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/. - -#pragma once - -#include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/Measurement.hpp" -#include "ActsExamples/EventData/ProtoTrack.hpp" -#include "ActsExamples/EventData/SimHit.hpp" -#include "ActsExamples/Framework/DataHandle.hpp" -#include "ActsExamples/Framework/IAlgorithm.hpp" -#include "ActsExamples/Framework/ProcessCode.hpp" - -#include - -namespace ActsExamples { - -class SurfaceSortingAlgorithm final : public IAlgorithm { - public: - struct Config { - /// Input proto track collection - std::string inputProtoTracks; - /// Input simulated hit collection - std::string inputSimHits; - /// Input measurement to simulated hit map for truth position - std::string inputMeasurementSimHitsMap; - /// Output proto track collection - std::string outputProtoTracks; - }; - - SurfaceSortingAlgorithm(Config cfg, Acts::Logging::Level level); - - ActsExamples::ProcessCode execute(const AlgorithmContext& ctx) const final; - - /// Get readonly access to the config parameters - const Config& config() const { return m_cfg; } - - private: - Config m_cfg; - - ReadDataHandle m_inputProtoTracks{this, - "InputProtoTracks"}; - ReadDataHandle m_inputSimHits{this, "InputSimHits"}; - ReadDataHandle m_inputMeasurementSimHitsMap{ - this, "InputMeasurementSimHitsMap"}; - WriteDataHandle m_outputProtoTracks{this, - "OutputProtoTracks"}; -}; - -} // namespace ActsExamples diff --git a/Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp b/Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp deleted file mode 100644 index 57a79ab2db0..00000000000 --- a/Examples/Algorithms/TrackFitting/src/SurfaceSortingAlgorithm.cpp +++ /dev/null @@ -1,84 +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/TrackFitting/SurfaceSortingAlgorithm.hpp" - -#include "ActsExamples/EventData/ProtoTrack.hpp" -#include "ActsFatras/EventData/Hit.hpp" - -#include -#include -#include -#include - -namespace ActsExamples { - -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"); - } - if (m_cfg.inputSimHits.empty()) { - throw std::invalid_argument("Missing input simulated hits collection"); - } - if (m_cfg.inputMeasurementSimHitsMap.empty()) { - throw std::invalid_argument("Missing input measurement sim hits map"); - } - if (m_cfg.outputProtoTracks.empty()) { - throw std::invalid_argument("Missing output proto track collection"); - } - - m_inputProtoTracks.initialize(m_cfg.inputProtoTracks); - m_inputSimHits.initialize(m_cfg.inputSimHits); - m_inputMeasurementSimHitsMap.initialize(m_cfg.inputMeasurementSimHitsMap); - m_outputProtoTracks.initialize(m_cfg.outputProtoTracks); -} - -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()); - std::map trackHitList; - - for (std::size_t itrack = 0; itrack < protoTracks.size(); ++itrack) { - const auto& protoTrack = protoTracks[itrack]; - - ProtoTrack sortedProtoTrack; - sortedProtoTrack.reserve(protoTrack.size()); - trackHitList.clear(); - - if (protoTrack.empty()) { - continue; - } - - for (const auto hit : protoTrack) { - const auto simHitIndex = simHitsMap.find(hit)->second; - auto simHit = simHits.nth(simHitIndex); - auto simHitTime = simHit->time(); - trackHitList.insert(std::make_pair(simHitTime, hit)); - } - - /// Map will now be sorted by truth hit time - for (auto const& [time, hit] : trackHitList) { - sortedProtoTrack.emplace_back(hit); - } - - sortedTracks.emplace_back(std::move(sortedProtoTrack)); - } - - m_outputProtoTracks(ctx, std::move(sortedTracks)); - - return ProcessCode::SUCCESS; -} - -} // namespace ActsExamples diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index 43792827e8d..0e30961fe39 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -1288,7 +1288,6 @@ def addKalmanTracks( s: acts.examples.Sequencer, trackingGeometry: acts.TrackingGeometry, field: acts.MagneticFieldProvider, - directNavigation: bool = False, reverseFilteringMomThreshold: float = 0 * u.GeV, inputProtoTracks: str = "truth_particle_tracks", multipleScattering: bool = True, @@ -1299,17 +1298,6 @@ def addKalmanTracks( ) -> None: customLogLevel = acts.examples.defaultLogging(s, logLevel) - if directNavigation: - srfSortAlg = acts.examples.SurfaceSortingAlgorithm( - level=customLogLevel(), - inputProtoTracks=inputProtoTracks, - inputSimHits="simhits", - inputMeasurementSimHitsMap="measurement_simhits_map", - outputProtoTracks="sorted_truth_particle_tracks", - ) - s.addAlgorithm(srfSortAlg) - inputProtoTracks = srfSortAlg.config.outputProtoTracks - kalmanOptions = { "multipleScattering": multipleScattering, "energyLoss": energyLoss, diff --git a/Examples/Python/src/TrackFitting.cpp b/Examples/Python/src/TrackFitting.cpp index c8dc72f9eef..6d1b9818e51 100644 --- a/Examples/Python/src/TrackFitting.cpp +++ b/Examples/Python/src/TrackFitting.cpp @@ -6,35 +6,23 @@ // 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/Algebra.hpp" #include "Acts/EventData/detail/CorrectedTransformationFreeToBound.hpp" #include "Acts/Plugins/Python/Utilities.hpp" #include "Acts/TrackFitting/BetheHeitlerApprox.hpp" #include "Acts/TrackFitting/GsfOptions.hpp" #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/EventData/Cluster.hpp" #include "ActsExamples/EventData/MeasurementCalibration.hpp" #include "ActsExamples/EventData/ScalingCalibrator.hpp" #include "ActsExamples/TrackFitting/RefittingAlgorithm.hpp" -#include "ActsExamples/TrackFitting/SurfaceSortingAlgorithm.hpp" #include "ActsExamples/TrackFitting/TrackFitterFunction.hpp" #include "ActsExamples/TrackFitting/TrackFittingAlgorithm.hpp" #include #include -#include #include #include -namespace Acts { -class MagneticFieldProvider; -class TrackingGeometry; -} // namespace Acts -namespace ActsExamples { -class IAlgorithm; -} // namespace ActsExamples - namespace py = pybind11; using namespace ActsExamples; @@ -46,11 +34,6 @@ namespace Acts::Python { void addTrackFitting(Context& ctx) { auto mex = ctx.get("examples"); - ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::SurfaceSortingAlgorithm, mex, - "SurfaceSortingAlgorithm", inputProtoTracks, - inputSimHits, inputMeasurementSimHitsMap, - outputProtoTracks); - ACTS_PYTHON_DECLARE_ALGORITHM( ActsExamples::TrackFittingAlgorithm, mex, "TrackFittingAlgorithm", inputMeasurements, inputProtoTracks, inputInitialTrackParameters, diff --git a/Examples/Python/tests/root_file_hashes.txt b/Examples/Python/tests/root_file_hashes.txt index e1b2833c2eb..07d42c66ad1 100644 --- a/Examples/Python/tests/root_file_hashes.txt +++ b/Examples/Python/tests/root_file_hashes.txt @@ -78,19 +78,11 @@ test_refitting[odd]__trackstates_gsf_refit.root: e297749dc1e7eda3b8dea13defa0499 test_refitting[odd]__tracksummary_gsf_refit.root: d5085882e45a0b699194dff9f40a36e9291227bf65f9aaaf9087f9242ef5ae22 test_refitting[generic]__trackstates_gsf_refit.root: 4424fdf2f27575db825c1a59f8e53a1595946211cbd5b2c8d3a2f71cdcc77ae9 test_refitting[generic]__tracksummary_gsf_refit.root: 562deecee4cfb97ceee72eff53d63da079e3249fb62d6bcd556e6f27d495dfd9 -test_truth_tracking_kalman[generic-False-0.0]__trackstates_kf.root: 9f77962b92037cb760b1629a602b1dae61f45e659c45d9a87baa784f6190960e -test_truth_tracking_kalman[generic-False-0.0]__tracksummary_kf.root: 562deecee4cfb97ceee72eff53d63da079e3249fb62d6bcd556e6f27d495dfd9 -test_truth_tracking_kalman[generic-False-1000.0]__trackstates_kf.root: 56a1bd989b9c1316b9098c65fa75df9e6683e62e35ae68d8f72d27220be0fd7d -test_truth_tracking_kalman[generic-False-1000.0]__tracksummary_kf.root: 2d85be3a5dff01a1076e80f8c225aca32f65b30dc9c3551b610ac2f12d006a39 -test_truth_tracking_kalman[generic-True-0.0]__trackstates_kf.root: 9f77962b92037cb760b1629a602b1dae61f45e659c45d9a87baa784f6190960e -test_truth_tracking_kalman[generic-True-0.0]__tracksummary_kf.root: 562deecee4cfb97ceee72eff53d63da079e3249fb62d6bcd556e6f27d495dfd9 -test_truth_tracking_kalman[generic-True-1000.0]__trackstates_kf.root: 56a1bd989b9c1316b9098c65fa75df9e6683e62e35ae68d8f72d27220be0fd7d -test_truth_tracking_kalman[generic-True-1000.0]__tracksummary_kf.root: 2d85be3a5dff01a1076e80f8c225aca32f65b30dc9c3551b610ac2f12d006a39 -test_truth_tracking_kalman[odd-False-0.0]__trackstates_kf.root: 7e144571b19aaf00002aef4f5bec5d488b96fb9ed8e1b2904c3756b31be83513 -test_truth_tracking_kalman[odd-False-0.0]__tracksummary_kf.root: d5085882e45a0b699194dff9f40a36e9291227bf65f9aaaf9087f9242ef5ae22 -test_truth_tracking_kalman[odd-False-1000.0]__trackstates_kf.root: efdf37f56fa3ef85265cda61853f4c0f989e3d4f4745b5e351c9bcca78cd93cc -test_truth_tracking_kalman[odd-False-1000.0]__tracksummary_kf.root: b84fabd6c1b71c360019cd719400746ccff1e565a983ca23d0621790b8843e44 -test_truth_tracking_kalman[odd-True-0.0]__trackstates_kf.root: 7e144571b19aaf00002aef4f5bec5d488b96fb9ed8e1b2904c3756b31be83513 -test_truth_tracking_kalman[odd-True-0.0]__tracksummary_kf.root: d5085882e45a0b699194dff9f40a36e9291227bf65f9aaaf9087f9242ef5ae22 -test_truth_tracking_kalman[odd-True-1000.0]__trackstates_kf.root: efdf37f56fa3ef85265cda61853f4c0f989e3d4f4745b5e351c9bcca78cd93cc -test_truth_tracking_kalman[odd-True-1000.0]__tracksummary_kf.root: b84fabd6c1b71c360019cd719400746ccff1e565a983ca23d0621790b8843e44 +test_truth_tracking_kalman[generic-0.0]__trackstates_kf.root: 9f77962b92037cb760b1629a602b1dae61f45e659c45d9a87baa784f6190960e +test_truth_tracking_kalman[generic-0.0]__tracksummary_kf.root: 562deecee4cfb97ceee72eff53d63da079e3249fb62d6bcd556e6f27d495dfd9 +test_truth_tracking_kalman[generic-1000.0]__trackstates_kf.root: 56a1bd989b9c1316b9098c65fa75df9e6683e62e35ae68d8f72d27220be0fd7d +test_truth_tracking_kalman[generic-1000.0]__tracksummary_kf.root: 2d85be3a5dff01a1076e80f8c225aca32f65b30dc9c3551b610ac2f12d006a39 +test_truth_tracking_kalman[odd-0.0]__trackstates_kf.root: 7e144571b19aaf00002aef4f5bec5d488b96fb9ed8e1b2904c3756b31be83513 +test_truth_tracking_kalman[odd-0.0]__tracksummary_kf.root: d5085882e45a0b699194dff9f40a36e9291227bf65f9aaaf9087f9242ef5ae22 +test_truth_tracking_kalman[odd-1000.0]__trackstates_kf.root: efdf37f56fa3ef85265cda61853f4c0f989e3d4f4745b5e351c9bcca78cd93cc +test_truth_tracking_kalman[odd-1000.0]__tracksummary_kf.root: b84fabd6c1b71c360019cd719400746ccff1e565a983ca23d0621790b8843e44 diff --git a/Examples/Python/tests/test_algorithms.py b/Examples/Python/tests/test_algorithms.py index 6311dc6460a..0f1e1f01a61 100644 --- a/Examples/Python/tests/test_algorithms.py +++ b/Examples/Python/tests/test_algorithms.py @@ -17,7 +17,6 @@ TrackParameterSmearing, TrackSelectorAlgorithm, TrackFittingAlgorithm, - SurfaceSortingAlgorithm, ParticlesPrinter, TrackParametersPrinter, PropagationAlgorithm, @@ -47,7 +46,6 @@ TrackParameterSmearing, TrackSelectorAlgorithm, TrackFittingAlgorithm, - SurfaceSortingAlgorithm, ParticlesPrinter, TrackParametersPrinter, PropagationAlgorithm, diff --git a/Examples/Python/tests/test_examples.py b/Examples/Python/tests/test_examples.py index 8441514c26b..daf40b6685a 100644 --- a/Examples/Python/tests/test_examples.py +++ b/Examples/Python/tests/test_examples.py @@ -579,9 +579,8 @@ def test_event_recording(tmp_path): @pytest.mark.parametrize("revFiltMomThresh", [0 * u.GeV, 1 * u.TeV]) -@pytest.mark.parametrize("directNavigation", [False, True]) def test_truth_tracking_kalman( - tmp_path, assert_root_hash, revFiltMomThresh, directNavigation, detector_config + tmp_path, assert_root_hash, revFiltMomThresh, detector_config ): root_files = [ ("trackstates_kf.root", "trackstates", 19), @@ -607,7 +606,6 @@ def test_truth_tracking_kalman( digiConfigFile=detector_config.digiConfigFile, outputDir=tmp_path, reverseFilteringMomThreshold=revFiltMomThresh, - directNavigation=directNavigation, s=seq, ) diff --git a/Examples/Scripts/Python/truth_tracking_kalman.py b/Examples/Scripts/Python/truth_tracking_kalman.py index 971f0c4bab4..3b857fd41da 100755 --- a/Examples/Scripts/Python/truth_tracking_kalman.py +++ b/Examples/Scripts/Python/truth_tracking_kalman.py @@ -17,7 +17,6 @@ def runTruthTrackingKalman( inputParticlePath: Optional[Path] = None, inputHitsPath: Optional[Path] = None, decorators=[], - directNavigation=False, reverseFilteringMomThreshold=0 * u.GeV, s: acts.examples.Sequencer = None, ): @@ -122,7 +121,6 @@ def runTruthTrackingKalman( s, trackingGeometry, field, - directNavigation, reverseFilteringMomThreshold, ) From 1ebd328a63591dd259e9f5f65e7a56bb227c1bf3 Mon Sep 17 00:00:00 2001 From: "Alexander J. Pfleger" <70842573+AJPfleger@users.noreply.github.com> Date: Sat, 7 Dec 2024 12:48:54 +0100 Subject: [PATCH 12/21] fix: typo in variable in vertex muon scan script (#3959) ## Summary by CodeRabbit - **Bug Fixes** - Corrected error handling in the vertex_mu_scan script to properly append NaN values to the time array. - **Chores** - Made spelling corrections in the CI/codespell_ignore.txt file to enhance clarity and correctness. --- CI/codespell_ignore.txt | 1 - Examples/Scripts/vertex_mu_scan.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CI/codespell_ignore.txt b/CI/codespell_ignore.txt index 13a3328440f..40dd321d0f5 100644 --- a/CI/codespell_ignore.txt +++ b/CI/codespell_ignore.txt @@ -5,7 +5,6 @@ coner dthe iself sortings -fime gaus te parm diff --git a/Examples/Scripts/vertex_mu_scan.py b/Examples/Scripts/vertex_mu_scan.py index ee8977fe9df..0de15b20097 100755 --- a/Examples/Scripts/vertex_mu_scan.py +++ b/Examples/Scripts/vertex_mu_scan.py @@ -39,7 +39,7 @@ def main(files: List[Path], output: str, title: str = ""): if time_file.exists(): time = numpy.append(time, float(time_file.read_text())) else: - fime.append(float("nan")) + time.append(float("nan")) rf = uproot.open(f"{file}:vertexing") From f16ed67a706a0b1ab3ea84291a8d737f705881e6 Mon Sep 17 00:00:00 2001 From: Stephen Nicholas Swatman Date: Sat, 7 Dec 2024 14:57:20 +0100 Subject: [PATCH 13/21] fix: Checkout branch in ExaTrk CI (#3967) Currently, the ExaTrk CI is trying to run a dependency script before having properly checked out the right branch, possible causing a CI error. This commit ensures that the target branch is properly checked out before trying to use any files in the repository. ## Summary by CodeRabbit - **Chores** - Improved CI/CD pipeline configuration for better build and test job management. - Standardized caching mechanisms and dependency handling across jobs. - Enhanced clarity and maintainability of the configuration. --- .gitlab-ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e278b18eade..25aff340111 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -155,7 +155,10 @@ test_exatrkx_unittests: - apt-get update -y - git clone $CLONE_URL src - - source src/CI/dependencies.sh + - cd src + - git checkout $HEAD_SHA + - source CI/dependencies.sh + - cd .. - ctest --test-dir build -R ExaTrkX test_exatrkx_python: From 9067679f3908b08e8ce55c5c61d75fde0b9061a9 Mon Sep 17 00:00:00 2001 From: Stephen Nicholas Swatman Date: Sat, 7 Dec 2024 16:29:01 +0100 Subject: [PATCH 14/21] feat: Add monadic functions to `Result` (#3957) In order to make the `Result` type easier to use, this commit adds three new functions: * `Result::value_or` allows the user to obtain the value or a provided default value. * `Result::transform` models functorial mapping, allowing users to modify values inside results. * `Result::and_then` models monadic binding, allowing users to build complex chains of actions on results. Implemented are lvalue and rvalue versions of these functions as well as tests. ![image](https://github.com/user-attachments/assets/591e64a2-e4fb-4ce6-9d34-166b28f7a837) ## Summary by CodeRabbit - **New Features** - Enhanced `Result` class with new methods for improved error handling and value transformation: - `value_or`: Retrieve valid value or default. - `transform`: Apply a function to the valid value. - `and_then`: Chain functions based on the result's validity. - **Tests** - Added comprehensive test cases to validate the new functionalities of the `Result` class, ensuring reliability and correctness: - `ValueOrResult`: Validates the `value_or` method. - `TransformResult`: Assesses the `transform` method. - `AndThenResult`: Evaluates the `and_then` method. --- Core/include/Acts/Utilities/Result.hpp | 141 ++++++++++++++++++ .../UnitTests/Core/Utilities/ResultTests.cpp | 88 +++++++++++ 2 files changed, 229 insertions(+) diff --git a/Core/include/Acts/Utilities/Result.hpp b/Core/include/Acts/Utilities/Result.hpp index 9410fa0edfe..475019fc99b 100644 --- a/Core/include/Acts/Utilities/Result.hpp +++ b/Core/include/Acts/Utilities/Result.hpp @@ -30,6 +30,9 @@ class Result { Result(std::variant&& var) : m_var(std::move(var)) {} public: + using ValueType = T; + using ErrorType = E; + /// Default construction is disallowed. Result() = delete; @@ -172,6 +175,144 @@ class Result { return std::move(std::get(m_var)); } + /// Retrieves the valid value from the result object, or returns a default + /// value if no valid value exists. + /// + /// @param[in] v The default value to use if no valid value exists. + /// @note This is the lvalue version. + /// @note This function always returns by value. + /// @return Either the valid value, or the given substitute. + template + std::conditional_t, const T&, T> value_or(U&& v) const& + requires(std::same_as, T>) + { + if (ok()) { + return value(); + } else { + return std::forward(v); + } + } + + /// Retrieves the valid value from the result object, or returns a default + /// value if no valid value exists. + /// + /// @param[in] v The default value to use if no valid value exists. + /// @note This is the rvalue version which moves the value out. + /// @note This function always returns by value. + /// @return Either the valid value, or the given substitute. + template + T value_or(U&& v) && + requires(std::same_as, T>) + { + if (ok()) { + return std::move(*this).value(); + } else { + return std::forward(v); + } + } + + /// Transforms the value contained in this result. + /// + /// Applying a function `f` to a valid value `x` returns `f(x)`, while + /// applying `f` to an invalid value returns another invalid value. + /// + /// @param[in] callable The transformation function to apply. + /// @note This is the lvalue version. + /// @note This functions is `fmap` on the functor in `A` of `Result`. + /// @return The modified valid value if exists, or an error otherwise. + template + auto transform(C&& callable) const& + requires std::invocable + { + using CallableReturnType = decltype(std::declval()(std::declval())); + using R = Result, E>; + if (ok()) { + return R::success(callable(value())); + } else { + return R::failure(error()); + } + } + + /// Transforms the value contained in this result. + /// + /// Applying a function `f` to a valid value `x` returns `f(x)`, while + /// applying `f` to an invalid value returns another invalid value. + /// + /// @param[in] callable The transformation function to apply. + /// @note This is the rvalue version. + /// @note This functions is `fmap` on the functor in `A` of `Result`. + /// @return The modified valid value if exists, or an error otherwise. + template + auto transform(C&& callable) && + requires std::invocable + { + using CallableReturnType = decltype(std::declval()(std::declval())); + using R = Result, E>; + if (ok()) { + return R::success(callable(std::move(*this).value())); + } else { + return R::failure(std::move(*this).error()); + } + } + + /// Bind a function to this result monadically. + /// + /// This function takes a function `f` and, if this result contains a valid + /// value `x`, returns `f(x)`. If the type of `x` is `T`, then `f` is + /// expected to accept type `T` and return `Result`. In this case, + /// `transform` would return the unhelpful type `Result>`, so + /// `and_then` strips away the outer layer to return `Result`. If the + /// value is invalid, this returns an invalid value in `Result`. + /// + /// @param[in] callable The transformation function to apply. + /// @note This is the lvalue version. + /// @note This functions is `>>=` on the functor in `A` of `Result`. + /// @return The modified valid value if exists, or an error otherwise. + template + auto and_then(C&& callable) const& + requires std::invocable + { + using R = decltype(std::declval()(std::declval())); + + static_assert(std::same_as, + "bind must take a callable with the same error type"); + + if (ok()) { + return callable(value()); + } else { + return R::failure(error()); + } + } + + /// Bind a function to this result monadically. + /// + /// This function takes a function `f` and, if this result contains a valid + /// value `x`, returns `f(x)`. If the type of `x` is `T`, then `f` is + /// expected to accept type `T` and return `Result`. In this case, + /// `transform` would return the unhelpful type `Result>`, so + /// `and_then` strips away the outer layer to return `Result`. If the + /// value is invalid, this returns an invalid value in `Result`. + /// + /// @param[in] callable The transformation function to apply. + /// @note This is the rvalue version. + /// @note This functions is `>>=` on the functor in `A` of `Result`. + /// @return The modified valid value if exists, or an error otherwise. + template + auto and_then(C&& callable) && + requires std::invocable + { + using R = decltype(std::declval()(std::declval())); + + static_assert(std::same_as, + "bind must take a callable with the same error type"); + + if (ok()) { + return callable(std::move(*this).value()); + } else { + return R::failure(std::move(*this).error()); + } + } + private: std::variant m_var; diff --git a/Tests/UnitTests/Core/Utilities/ResultTests.cpp b/Tests/UnitTests/Core/Utilities/ResultTests.cpp index 0a873f16d4c..748e0d7211e 100644 --- a/Tests/UnitTests/Core/Utilities/ResultTests.cpp +++ b/Tests/UnitTests/Core/Utilities/ResultTests.cpp @@ -333,6 +333,94 @@ BOOST_AUTO_TEST_CASE(BoolResult) { BOOST_CHECK_EQUAL(res.error(), MyError::Failure); } +BOOST_AUTO_TEST_CASE(ValueOrResult) { + using Result = Result; + + Result res = Result::success(5); + BOOST_CHECK_EQUAL(res.value_or(42), 5); + + res = Result::failure(MyError::Failure); + BOOST_CHECK_EQUAL(res.value_or(42), 42); + + BOOST_CHECK_EQUAL(Result::success(5).value_or(42), 5); + BOOST_CHECK_EQUAL(Result::failure(MyError::Failure).value_or(42), 42); + + int val = 25; + const int cval = 30; + + BOOST_CHECK_EQUAL(Result::success(5).value_or(val), 5); + BOOST_CHECK_EQUAL(Result::success(5).value_or(cval), 5); + BOOST_CHECK_EQUAL(Result::failure(MyError::Failure).value_or(val), 25); + BOOST_CHECK_EQUAL(Result::failure(MyError::Failure).value_or(cval), 30); + + res = Result::success(5); + + BOOST_CHECK_EQUAL(res.value_or(val), 5); + BOOST_CHECK_EQUAL(&(res.value_or(val)), &res.value()); + BOOST_CHECK_EQUAL(res.value_or(cval), 5); + BOOST_CHECK_EQUAL(&(res.value_or(cval)), &res.value()); + + res = Result::failure(MyError::Failure); + + BOOST_CHECK_EQUAL(res.value_or(val), 25); + BOOST_CHECK_EQUAL(res.value_or(cval), 30); + BOOST_CHECK_EQUAL(&(res.value_or(val)), &val); + BOOST_CHECK_EQUAL(&(res.value_or(cval)), &cval); +} + +BOOST_AUTO_TEST_CASE(TransformResult) { + using Result = Result; + + auto f1 = [](int x) { return 2 * x; }; + + Result res = Result::success(5); + Result res2 = res.transform(f1); + BOOST_CHECK(res2.ok()); + BOOST_CHECK_EQUAL(*res2, 10); + + res = Result::failure(MyError::Failure); + res2 = res.transform(f1); + BOOST_CHECK(!res2.ok()); + + BOOST_CHECK(Result::success(5).transform(f1).ok()); + BOOST_CHECK_EQUAL(Result::success(5).transform(f1).value(), 10); + + BOOST_CHECK(!Result::failure(MyError::Failure).transform(f1).ok()); +} + +BOOST_AUTO_TEST_CASE(AndThenResult) { + using Result1 = Result; + using Result2 = Result; + + auto f1 = [](int x) -> Result2 { + return Result2::success("hello " + std::to_string(x)); + }; + auto f2 = [](int) -> Result2 { return Result2::failure(MyError::Failure); }; + + Result1 res = Result1::success(5); + Result2 res2 = res.and_then(f1); + BOOST_CHECK(res2.ok()); + BOOST_CHECK_EQUAL(*res2, "hello 5"); + + res2 = res.and_then(f2); + BOOST_CHECK(!res2.ok()); + + res = Result1::failure(MyError::Failure); + res2 = res.and_then(f1); + BOOST_CHECK(!res2.ok()); + + res2 = res.and_then(f2); + BOOST_CHECK(!res2.ok()); + + BOOST_CHECK(Result1::success(5).and_then(f1).ok()); + BOOST_CHECK_EQUAL(Result1::success(5).and_then(f1).value(), "hello 5"); + + BOOST_CHECK(!Result1::success(5).and_then(f2).ok()); + + BOOST_CHECK(!Result1::failure(MyError::Failure).and_then(f1).ok()); + + BOOST_CHECK(!Result1::failure(MyError::Failure).and_then(f2).ok()); +} BOOST_AUTO_TEST_SUITE_END() } // namespace Acts::Test From 341d2b0b64cd4ccfc26cb69dea2006a875e5eab5 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Sat, 7 Dec 2024 18:22:45 +0100 Subject: [PATCH 15/21] refactor: Rework detector handling in Examples (#3498) - Concrete detector interface for examples - Align interfaces of different detectors - Remove `DD4hepGeometryService` - Improve DDG4 construction - `unique_ptr` for DD4hep detector with dedicated name - Concrete `Geant4Mapping` instance for conversion - Simplify CMake for optional Geant4 construction for different detectors - Move Geant4 construction code to detector folder --- CI/physmon/physmon_common.py | 4 +- Core/include/Acts/Utilities/Logger.hpp | 4 +- Examples/Algorithms/Geant4/CMakeLists.txt | 38 +--- .../DDG4/DDG4DetectorConstruction.hpp | 74 -------- ...tory.hpp => Geant4ConstructionOptions.hpp} | 16 +- .../ActsExamples/Geant4/Geant4Simulation.hpp | 22 +-- .../ActsExamples/Geant4/RegionCreator.hpp | 25 +-- .../TelescopeG4DetectorConstruction.hpp | 56 ------ .../Geant4/src/DDG4DetectorConstruction.cpp | 76 -------- .../Geant4/src/GdmlDetectorConstruction.cpp | 49 ----- .../Geant4/src/Geant4Simulation.cpp | 8 +- .../Algorithms/Geant4/src/RegionCreator.cpp | 19 +- .../Geant4HepMC/EventRecording.hpp | 9 +- .../Geant4HepMC/src/EventRecording.cpp | 6 +- Examples/Detectors/CMakeLists.txt | 2 + Examples/Detectors/Common/CMakeLists.txt | 14 ++ .../ActsExamples/DetectorCommons/Detector.hpp | 69 +++++++ Examples/Detectors/Common/src/Detector.cpp | 58 ++++++ .../ContextualDetector/CMakeLists.txt | 8 +- .../ContextualDetector/AlignedDetector.hpp | 32 +--- .../src/AlignedDetector.cpp | 91 +++++---- .../Detectors/DD4hepDetector/CMakeLists.txt | 37 +++- .../DD4hepDetector/DD4hepDetector.hpp | 150 ++++++++------- .../DD4hepDetector/DD4hepGeometryService.hpp | 137 -------------- .../DDG4DetectorConstruction.hpp | 50 +++++ .../DD4hepDetector/src/DD4hepDetector.cpp | 163 ++++++++++------ .../src/DD4hepDetectorGeant4.cpp | 22 +++ .../src/DD4hepDetectorGeant4Stub.cpp | 19 ++ .../src/DD4hepGeometryService.cpp | 174 ------------------ .../src/DDG4DetectorConstruction.cpp | 52 ++++++ .../Detectors/Geant4Detector/CMakeLists.txt | 17 +- .../Geant4Detector/GdmlDetector.hpp | 36 ++++ .../GdmlDetectorConstruction.hpp | 28 +-- .../Geant4Detector/Geant4Detector.hpp | 51 +---- .../Geant4Detector/src/GdmlDetector.cpp | 27 +++ .../src/GdmlDetectorConstruction.cpp | 41 +++++ .../Geant4Detector/src/Geant4Detector.cpp | 77 +++----- .../Detectors/GenericDetector/CMakeLists.txt | 8 +- .../GenericDetector/GenericDetector.hpp | 37 ++-- .../GenericDetector/src/GenericDetector.cpp | 32 ++-- .../Detectors/GeoModelDetector/CMakeLists.txt | 41 +++++ .../GeoModelDetector/GeoModelDetector.hpp | 37 ++++ .../GeoModelGeant4DetectorConstruction.hpp} | 31 +--- .../GeoModelDetector/src/GeoModelDetector.cpp | 24 +++ .../src/GeoModelDetectorGeant4.cpp | 21 +++ .../src/GeoModelDetectorGeant4Stub.cpp | 19 ++ .../GeoModelGeant4DetectorConstruction.cpp} | 33 ++-- .../src/MockupSectorBuilder.cpp | 17 +- .../Detectors/TGeoDetector/CMakeLists.txt | 2 + .../TGeoDetector/JsonTGeoDetectorConfig.hpp | 10 +- .../TGeoDetector/TGeoDetector.hpp | 32 ++-- .../TGeoDetector/src/TGeoDetector.cpp | 26 ++- .../TelescopeDetector/CMakeLists.txt | 23 ++- .../TelescopeDetector/TelescopeDetector.hpp | 27 ++- .../TelescopeG4DetectorConstruction.hpp | 38 ++++ .../src/TelescopeDetector.cpp | 43 ++--- .../src/TelescopeDetectorGeant4.cpp | 20 ++ .../src/TelescopeDetectorGeant4Stub.cpp | 19 ++ .../src/TelescopeG4DetectorConstruction.cpp | 29 +-- .../ActsExamples/Io/EDM4hep/EDM4hepReader.hpp | 2 +- Examples/Io/EDM4hep/src/EDM4hepReader.cpp | 5 +- Examples/Python/CMakeLists.txt | 55 +----- Examples/Python/python/acts/_adapter.py | 50 ----- .../Python/python/acts/examples/__init__.py | 4 +- .../Python/python/acts/examples/dd4hep.py | 7 +- .../python/acts/examples/geant4/dd4hep.py | 20 -- .../python/acts/examples/geant4/geomodel.py | 6 - Examples/Python/python/acts/examples/itk.py | 6 +- Examples/Python/python/acts/examples/odd.py | 36 +--- .../Python/python/acts/examples/simulation.py | 44 +---- Examples/Python/src/Covfie.cpp | 2 + Examples/Python/src/DD4hepComponent.cpp | 50 ++--- Examples/Python/src/Detector.cpp | 171 ++++++++--------- Examples/Python/src/Geant4Component.cpp | 110 ++++------- Examples/Python/src/Geant4DD4hepComponent.cpp | 34 ---- .../Python/src/Geant4GeoModelComponent.cpp | 36 ---- Examples/Python/src/Geant4HepMC3.cpp | 6 +- Examples/Python/src/GeoModel.cpp | 18 ++ Examples/Python/src/GeometryBuildingGen1.cpp | 23 ++- Examples/Python/src/ModuleEntry.cpp | 17 +- Examples/Python/tests/conftest.py | 39 ++-- Examples/Python/tests/test_detectors.py | 25 ++- Examples/Python/tests/test_examples.py | 89 ++++----- Examples/Python/tests/test_reader.py | 12 +- Examples/Python/tests/test_writer.py | 18 +- .../GsfDebugger/make_gsf_verbose_log.py | 6 +- Examples/Scripts/Optimization/ckf.py | 4 +- .../Orion/material_mapping_optimisation.py | 15 +- Examples/Scripts/Python/ckf_tracks.py | 4 +- Examples/Scripts/Python/digitization.py | 4 +- .../Scripts/Python/digitization_config.py | 3 +- Examples/Scripts/Python/event_recording.py | 13 +- Examples/Scripts/Python/exatrkx.py | 4 +- Examples/Scripts/Python/fatras.py | 4 +- Examples/Scripts/Python/full_chain_odd.py | 6 +- Examples/Scripts/Python/full_chain_odd_LRT.py | 6 +- Examples/Scripts/Python/full_chain_test.py | 8 +- Examples/Scripts/Python/geant4.py | 4 +- Examples/Scripts/Python/geant4_parallel.py | 4 +- Examples/Scripts/Python/geometry.py | 10 +- Examples/Scripts/Python/hashing_seeding.py | 8 +- Examples/Scripts/Python/material_mapping.py | 4 +- .../Scripts/Python/material_mapping_core.py | 3 +- Examples/Scripts/Python/material_recording.py | 26 +-- .../Scripts/Python/material_validation.py | 6 +- .../Python/material_validation_core.py | 5 +- Examples/Scripts/Python/propagation.py | 14 +- Examples/Scripts/Python/seeding.py | 5 +- .../Scripts/Python/telescope_simulation.py | 3 +- ...elescope_track_params_lookup_generation.py | 3 +- Examples/Scripts/Python/truth_tracking_gsf.py | 6 +- .../Python/truth_tracking_gsf_refitting.py | 6 +- .../Scripts/Python/truth_tracking_gx2f.py | 6 +- .../Scripts/Python/truth_tracking_kalman.py | 6 +- .../Python/truth_tracking_kalman_refitting.py | 7 +- .../Python/truth_tracking_telescope.py | 3 +- Examples/Scripts/Python/vertex_fitting.py | 3 +- docs/examples/full_chain_odd.md | 6 +- docs/examples/howto/material_mapping.rst | 4 +- docs/examples/python_bindings.rst | 3 +- docs/getting_started.md | 4 +- 121 files changed, 1584 insertions(+), 1857 deletions(-) delete mode 100644 Examples/Algorithms/Geant4/include/ActsExamples/DDG4/DDG4DetectorConstruction.hpp rename Examples/Algorithms/Geant4/include/ActsExamples/Geant4/{DetectorConstructionFactory.hpp => Geant4ConstructionOptions.hpp} (53%) delete mode 100644 Examples/Algorithms/Geant4/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp delete mode 100644 Examples/Algorithms/Geant4/src/DDG4DetectorConstruction.cpp delete mode 100644 Examples/Algorithms/Geant4/src/GdmlDetectorConstruction.cpp create mode 100644 Examples/Detectors/Common/CMakeLists.txt create mode 100644 Examples/Detectors/Common/include/ActsExamples/DetectorCommons/Detector.hpp create mode 100644 Examples/Detectors/Common/src/Detector.cpp delete mode 100644 Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepGeometryService.hpp create mode 100644 Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DDG4DetectorConstruction.hpp create mode 100644 Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4.cpp create mode 100644 Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4Stub.cpp delete mode 100644 Examples/Detectors/DD4hepDetector/src/DD4hepGeometryService.cpp create mode 100644 Examples/Detectors/DD4hepDetector/src/DDG4DetectorConstruction.cpp create mode 100644 Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/GdmlDetector.hpp rename Examples/{Algorithms/Geant4/include/ActsExamples/Geant4 => Detectors/Geant4Detector/include/ActsExamples/Geant4Detector}/GdmlDetectorConstruction.hpp (56%) create mode 100644 Examples/Detectors/Geant4Detector/src/GdmlDetector.cpp create mode 100644 Examples/Detectors/Geant4Detector/src/GdmlDetectorConstruction.cpp create mode 100644 Examples/Detectors/GeoModelDetector/CMakeLists.txt create mode 100644 Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelDetector.hpp rename Examples/{Algorithms/Geant4/include/ActsExamples/GeoModelG4/GeoModelDetectorConstruction.hpp => Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelGeant4DetectorConstruction.hpp} (52%) create mode 100644 Examples/Detectors/GeoModelDetector/src/GeoModelDetector.cpp create mode 100644 Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4.cpp create mode 100644 Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4Stub.cpp rename Examples/{Algorithms/Geant4/src/GeoModelDetectorConstruction.cpp => Detectors/GeoModelDetector/src/GeoModelGeant4DetectorConstruction.cpp} (53%) create mode 100644 Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp create mode 100644 Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4.cpp create mode 100644 Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4Stub.cpp rename Examples/{Algorithms/Geant4 => Detectors/TelescopeDetector}/src/TelescopeG4DetectorConstruction.cpp (87%) delete mode 100644 Examples/Python/python/acts/examples/geant4/dd4hep.py delete mode 100644 Examples/Python/python/acts/examples/geant4/geomodel.py delete mode 100644 Examples/Python/src/Geant4DD4hepComponent.cpp delete mode 100644 Examples/Python/src/Geant4GeoModelComponent.cpp diff --git a/CI/physmon/physmon_common.py b/CI/physmon/physmon_common.py index 32c056e2820..a451dee943b 100644 --- a/CI/physmon/physmon_common.py +++ b/CI/physmon/physmon_common.py @@ -33,7 +33,9 @@ def makeSetup() -> PhysmonSetup: level=acts.logging.INFO, ) - detector, trackingGeometry, decorators = getOpenDataDetector(matDeco) + detector = getOpenDataDetector(matDeco) + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() setup = PhysmonSetup( detector=detector, trackingGeometry=trackingGeometry, diff --git a/Core/include/Acts/Utilities/Logger.hpp b/Core/include/Acts/Utilities/Logger.hpp index 83dc989ca85..b9e499b74f3 100644 --- a/Core/include/Acts/Utilities/Logger.hpp +++ b/Core/include/Acts/Utilities/Logger.hpp @@ -7,11 +7,9 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. #pragma once + // STL include(s) -#include #include -#include -#include #include #include #include diff --git a/Examples/Algorithms/Geant4/CMakeLists.txt b/Examples/Algorithms/Geant4/CMakeLists.txt index a7dd786bccf..cb056036f00 100644 --- a/Examples/Algorithms/Geant4/CMakeLists.txt +++ b/Examples/Algorithms/Geant4/CMakeLists.txt @@ -1,8 +1,6 @@ add_library( ActsExamplesGeant4 SHARED - src/GdmlDetectorConstruction.cpp - src/TelescopeG4DetectorConstruction.cpp src/Geant4Simulation.cpp src/MagneticFieldWrapper.cpp src/MaterialPhysicsList.cpp @@ -32,43 +30,9 @@ target_link_libraries( PUBLIC ActsCore ActsExamplesFramework - ActsExamplesDetectorTelescope + ActsExamplesDetectorsCommon Boost::headers ${Geant4_LIBRARIES} ) -if(ACTS_BUILD_EXAMPLES_DD4HEP) - if(${DD4hep_VERSION} VERSION_LESS 1.11) - target_include_directories( - ActsExamplesGeant4 - PRIVATE ${DD4hep_INCLUDE_DIRS} - ) - target_link_libraries( - ActsExamplesGeant4 - PRIVATE ${DD4hep_DDCORE_LIBRARY} ${DD4hep_DDG4_LIBRARY} - ) - else() - target_link_libraries( - ActsExamplesGeant4 - PUBLIC ActsExamplesDetectorDD4hep DD4hep::DDCore DD4hep::DDG4 - ) - endif() - - target_sources(ActsExamplesGeant4 PUBLIC src/DDG4DetectorConstruction.cpp) -endif() - -if(ACTS_BUILD_PLUGIN_GEOMODEL) - target_sources( - ActsExamplesGeant4 - PUBLIC src/GeoModelDetectorConstruction.cpp - ) - - find_library(GeoModel2G4_LIBRARY GeoModel2G4 REQUIRED) - - target_link_libraries( - ActsExamplesGeant4 - PUBLIC ActsPluginGeoModel ${GeoModel2G4_LIBRARY} - ) -endif() - install(TARGETS ActsExamplesGeant4 LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/DDG4/DDG4DetectorConstruction.hpp b/Examples/Algorithms/Geant4/include/ActsExamples/DDG4/DDG4DetectorConstruction.hpp deleted file mode 100644 index 6706b5c20e4..00000000000 --- a/Examples/Algorithms/Geant4/include/ActsExamples/DDG4/DDG4DetectorConstruction.hpp +++ /dev/null @@ -1,74 +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/Geant4/DetectorConstructionFactory.hpp" -#include "ActsExamples/Geant4/RegionCreator.hpp" - -#include - -#include - -class G4VPhysicalVolume; - -namespace dd4hep { -class Detector; -} - -namespace ActsExamples { - -struct DD4hepDetector; - -/// Construct the Geant4 detector from a DD4hep description. -class DDG4DetectorConstruction final : public G4VUserDetectorConstruction { - public: - DDG4DetectorConstruction( - std::shared_ptr detector, - std::vector> regionCreators = {}); - ~DDG4DetectorConstruction() final; - - /// Convert the stored DD4hep detector to a Geant4 description. - /// - /// Transfers ownership of the created object as all volumes (including world) - /// are deleted in ~G4PhysicalVolumeStore(). - /// - /// @note for facilitating configuration within the ACTS framework the world - /// volume is cached - G4VPhysicalVolume* Construct() final; - - private: - /// The Acts DD4hep detector instance - std::shared_ptr m_detector; - /// Region creators - std::vector> m_regionCreators; - /// The world volume - G4VPhysicalVolume* m_world = nullptr; - - /// The DD4hep detector instance - dd4hep::Detector& dd4hepDetector() const; -}; - -class DDG4DetectorConstructionFactory final - : public Geant4::DetectorConstructionFactory { - public: - DDG4DetectorConstructionFactory( - std::shared_ptr detector, - std::vector> regionCreators = {}); - ~DDG4DetectorConstructionFactory() final; - - std::unique_ptr factorize() const override; - - private: - /// The Acts DD4hep detector instance - std::shared_ptr m_detector; - /// Region creators - std::vector> m_regionCreators; -}; - -} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/DetectorConstructionFactory.hpp b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4ConstructionOptions.hpp similarity index 53% rename from Examples/Algorithms/Geant4/include/ActsExamples/Geant4/DetectorConstructionFactory.hpp rename to Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4ConstructionOptions.hpp index 16cb2c3eae6..a767227ae3e 100644 --- a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/DetectorConstructionFactory.hpp +++ b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4ConstructionOptions.hpp @@ -9,18 +9,16 @@ #pragma once #include - -#include +#include namespace ActsExamples::Geant4 { +class RegionCreator; +} // namespace ActsExamples::Geant4 -/// Silly Geant4 will destroy the detector construction after the run manager is -/// destructed. This class works around it by factorizing a factory. -class DetectorConstructionFactory { - public: - virtual ~DetectorConstructionFactory() = default; +namespace ActsExamples { - virtual std::unique_ptr factorize() const = 0; +struct Geant4ConstructionOptions { + std::vector> regionCreators; }; -} // namespace ActsExamples::Geant4 +} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp index 917b87a7a27..089c1c2b3cd 100644 --- a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp +++ b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp @@ -10,6 +10,7 @@ #include "Acts/Material/MaterialInteraction.hpp" #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include "ActsExamples/EventData/PropagationSummary.hpp" #include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/EventData/SimParticle.hpp" @@ -17,6 +18,7 @@ #include "ActsExamples/Framework/IAlgorithm.hpp" #include "ActsExamples/Framework/ProcessCode.hpp" #include "ActsExamples/Framework/RandomNumbers.hpp" +#include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp" #include "ActsExamples/Geant4/SensitiveSurfaceMapper.hpp" #include @@ -44,10 +46,8 @@ namespace ActsExamples { struct Geant4Handle; namespace Geant4 { -class DetectorConstructionFactory; class SensitiveSurfaceMapper; struct EventStore; -class RegionCreator; } // namespace Geant4 /// Abstracts common Geant4 Acts algorithm behaviour. @@ -61,10 +61,11 @@ class Geant4SimulationBase : public IAlgorithm { /// Random number service. std::shared_ptr randomNumbers; - /// Detector construction object. - /// G4RunManager will take care of deletion - std::shared_ptr - detectorConstructionFactory; + /// Geant4 construction options. + Geant4ConstructionOptions constructionOptions; + + /// Detector instance to access Geant4 geometry construction. + std::shared_ptr detector; /// Optional Geant4 instance overwrite. std::shared_ptr geant4Handle; @@ -81,8 +82,7 @@ class Geant4SimulationBase : public IAlgorithm { /// Algorithm execute method, called once per event with context /// /// @param ctx the AlgorithmContext for this event - ActsExamples::ProcessCode execute( - const ActsExamples::AlgorithmContext& ctx) const override; + ProcessCode execute(const ActsExamples::AlgorithmContext& ctx) const override; /// Readonly access to the configuration virtual const Config& config() const = 0; @@ -166,8 +166,7 @@ class Geant4Simulation final : public Geant4SimulationBase { /// Algorithm execute method, called once per event with context /// /// @param ctx the AlgorithmContext for this event - ActsExamples::ProcessCode execute( - const ActsExamples::AlgorithmContext& ctx) const final; + ProcessCode execute(const ActsExamples::AlgorithmContext& ctx) const final; /// Readonly access to the configuration const Config& config() const final { return m_cfg; } @@ -209,8 +208,7 @@ class Geant4MaterialRecording final : public Geant4SimulationBase { /// Algorithm execute method, called once per event with context /// /// @param ctx the AlgorithmContext for this event - ActsExamples::ProcessCode execute( - const ActsExamples::AlgorithmContext& ctx) const final; + ProcessCode execute(const ActsExamples::AlgorithmContext& ctx) const final; /// Readonly access to the configuration const Config& config() const final { return m_cfg; } diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/RegionCreator.hpp b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/RegionCreator.hpp index 5327e4f37eb..8977449eff8 100644 --- a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/RegionCreator.hpp +++ b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/RegionCreator.hpp @@ -13,6 +13,8 @@ #include #include +class G4Region; + namespace ActsExamples::Geant4 { /// Geant4 Region Creator @@ -24,6 +26,9 @@ class RegionCreator { public: /// Nested configuration struct for the Geant4 region creator struct Config { + /// Region name + std::string name; + /// Process cut to be applied for gammas, in mm double gammaCut{}; @@ -37,35 +42,25 @@ class RegionCreator { double protonCut{}; /// Volume list to be included in this region - std::vector volumes{}; + std::vector volumes; }; /// Region creator constructor /// /// @param cfg is the configuration struct - /// @param name is the region name - /// @param level is the logging level to be used - RegionCreator(const Config& cfg, std::string name, - Acts::Logging::Level level); + explicit RegionCreator(const Config& cfg); /// Construct the region - void construct(); + /// @note The lifetime of the returned region is managed by Geant4 + G4Region* buildRegion( + const Acts::Logger& logger = Acts::getDummyLogger()) const; /// Readonly access to the configuration const Config& config() const { return m_cfg; } private: - /// Region name - std::string m_name; - /// Config instance Config m_cfg; - - /// Private access method to the logging instance - const Acts::Logger& logger() const { return *m_logger; } - - /// The looging instance - std::unique_ptr m_logger; }; } // namespace ActsExamples::Geant4 diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp b/Examples/Algorithms/Geant4/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp deleted file mode 100644 index dba44638152..00000000000 --- a/Examples/Algorithms/Geant4/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp +++ /dev/null @@ -1,56 +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/Geant4/DetectorConstructionFactory.hpp" -#include "ActsExamples/Geant4/RegionCreator.hpp" -#include "ActsExamples/TelescopeDetector/TelescopeDetector.hpp" - -#include "G4VUserDetectorConstruction.hh" - -class G4VPhysicalVolume; -class G4LogicalVolume; - -namespace ActsExamples { - -class TelescopeG4DetectorConstruction final - : public G4VUserDetectorConstruction { - public: - TelescopeG4DetectorConstruction( - const TelescopeDetector::Config& cfg, - std::vector> regionCreators = {}); - - G4VPhysicalVolume* Construct() final; - - private: - /// The configuration of the telescope detector - TelescopeDetector::Config m_cfg; - /// Region creators - std::vector> m_regionCreators; - /// The world volume - G4VPhysicalVolume* m_world{}; -}; - -class TelescopeG4DetectorConstructionFactory final - : public Geant4::DetectorConstructionFactory { - public: - TelescopeG4DetectorConstructionFactory( - const TelescopeDetector::Config& cfg, - std::vector> regionCreators = {}); - - std::unique_ptr factorize() const override; - - private: - /// The configuration of the telescope detector - TelescopeDetector::Config m_cfg; - /// Region creators - std::vector> m_regionCreators; -}; - -} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/src/DDG4DetectorConstruction.cpp b/Examples/Algorithms/Geant4/src/DDG4DetectorConstruction.cpp deleted file mode 100644 index 1b24da8f273..00000000000 --- a/Examples/Algorithms/Geant4/src/DDG4DetectorConstruction.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/DDG4/DDG4DetectorConstruction.hpp" - -#include "ActsExamples/DD4hepDetector/DD4hepDetector.hpp" - -#include -#include - -#include -#include -#include -#include -#include -#include - -class G4VPhysicalVolume; - -namespace ActsExamples { - -DDG4DetectorConstruction::DDG4DetectorConstruction( - std::shared_ptr detector, - std::vector> regionCreators) - : G4VUserDetectorConstruction(), - m_detector(std::move(detector)), - m_regionCreators(std::move(regionCreators)) {} - -DDG4DetectorConstruction::~DDG4DetectorConstruction() = default; - -dd4hep::Detector& DDG4DetectorConstruction::dd4hepDetector() const { - return m_detector->geometryService->detector(); -} - -// See DD4hep::Simulation::Geant4DetectorConstruction::Construct() -G4VPhysicalVolume* DDG4DetectorConstruction::Construct() { - if (m_world == nullptr) { - dd4hep::sim::Geant4Mapping& g4map = dd4hep::sim::Geant4Mapping::instance(); - auto conv = dd4hep::sim::Geant4Converter(dd4hepDetector(), - dd4hep::PrintLevel::VERBOSE); - dd4hep::sim::Geant4GeometryInfo* geoInfo = - conv.create(dd4hepDetector().world()).detach(); - g4map.attach(geoInfo); - // All volumes are deleted in ~G4PhysicalVolumeStore() - m_world = geoInfo->world(); - // Create Geant4 volume manager - g4map.volumeManager(); - - // Create regions - for (const auto& regionCreator : m_regionCreators) { - regionCreator->construct(); - } - } - return m_world; -} - -DDG4DetectorConstructionFactory::DDG4DetectorConstructionFactory( - std::shared_ptr detector, - std::vector> regionCreators) - : m_detector(std::move(detector)), - m_regionCreators(std::move(regionCreators)) {} - -DDG4DetectorConstructionFactory::~DDG4DetectorConstructionFactory() = default; - -std::unique_ptr -DDG4DetectorConstructionFactory::factorize() const { - return std::make_unique(m_detector, - m_regionCreators); -} - -} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/src/GdmlDetectorConstruction.cpp b/Examples/Algorithms/Geant4/src/GdmlDetectorConstruction.cpp deleted file mode 100644 index 242597ac175..00000000000 --- a/Examples/Algorithms/Geant4/src/GdmlDetectorConstruction.cpp +++ /dev/null @@ -1,49 +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/Geant4/GdmlDetectorConstruction.hpp" - -#include - -#include - -class G4VPhysicalVolume; - -using namespace ActsExamples; - -GdmlDetectorConstruction::GdmlDetectorConstruction( - std::string path, - std::vector> regionCreators) - : G4VUserDetectorConstruction(), - m_path(std::move(path)), - m_regionCreators(std::move(regionCreators)) {} - -G4VPhysicalVolume* GdmlDetectorConstruction::Construct() { - if (m_world == nullptr) { - G4GDMLParser parser; - // TODO how to handle errors - parser.Read(m_path); - m_world = parser.GetWorldVolume(); - - // Create regions - for (const auto& regionCreator : m_regionCreators) { - regionCreator->construct(); - } - } - return m_world; -} - -GdmlDetectorConstructionFactory::GdmlDetectorConstructionFactory( - std::string path, - std::vector> regionCreators) - : m_path(std::move(path)), m_regionCreators(std::move(regionCreators)) {} - -std::unique_ptr -GdmlDetectorConstructionFactory::factorize() const { - return std::make_unique(m_path, m_regionCreators); -} diff --git a/Examples/Algorithms/Geant4/src/Geant4Simulation.cpp b/Examples/Algorithms/Geant4/src/Geant4Simulation.cpp index 0f26f8037b1..9ae93e97087 100644 --- a/Examples/Algorithms/Geant4/src/Geant4Simulation.cpp +++ b/Examples/Algorithms/Geant4/src/Geant4Simulation.cpp @@ -15,7 +15,6 @@ #include "ActsExamples/Framework/IAlgorithm.hpp" #include "ActsExamples/Framework/RandomNumbers.hpp" #include "ActsExamples/Framework/WhiteBoard.hpp" -#include "ActsExamples/Geant4/DetectorConstructionFactory.hpp" #include "ActsExamples/Geant4/EventStore.hpp" #include "ActsExamples/Geant4/Geant4Manager.hpp" #include "ActsExamples/Geant4/MagneticFieldWrapper.hpp" @@ -54,7 +53,7 @@ Geant4SimulationBase::Geant4SimulationBase(const Config& cfg, std::string name, if (cfg.inputParticles.empty()) { throw std::invalid_argument("Missing input particle collection"); } - if (cfg.detectorConstructionFactory == nullptr) { + if (cfg.detector == nullptr) { throw std::invalid_argument("Missing detector construction factory"); } if (cfg.randomNumbers == nullptr) { @@ -82,7 +81,10 @@ void Geant4SimulationBase::commonInitialization() { } // G4RunManager will take care of deletion m_detectorConstruction = - config().detectorConstructionFactory->factorize().release(); + config() + .detector + ->buildGeant4DetectorConstruction(config().constructionOptions) + .release(); runManager().SetUserInitialization(m_detectorConstruction); runManager().InitializeGeometry(); } diff --git a/Examples/Algorithms/Geant4/src/RegionCreator.cpp b/Examples/Algorithms/Geant4/src/RegionCreator.cpp index c38dcf83078..f3561293035 100644 --- a/Examples/Algorithms/Geant4/src/RegionCreator.cpp +++ b/Examples/Algorithms/Geant4/src/RegionCreator.cpp @@ -8,6 +8,8 @@ #include "ActsExamples/Geant4/RegionCreator.hpp" +#include "Acts/Utilities/Logger.hpp" + #include #include #include @@ -15,15 +17,11 @@ namespace ActsExamples::Geant4 { -RegionCreator::RegionCreator(const Config& cfg, std::string name, - Acts::Logging::Level level) - : m_name(std::move(name)), - m_cfg(cfg), - m_logger(Acts::getDefaultLogger(m_name, level)) {} +RegionCreator::RegionCreator(const Config& cfg) : m_cfg(cfg) {} -void RegionCreator::construct() { +G4Region* RegionCreator::buildRegion(const Acts::Logger& logger) const { // create a new G4Region - G4Region* region = new G4Region(m_name); + G4Region* region = new G4Region(m_cfg.name); // loop over volumes and find the ones in the list std::size_t nVolumes{0}; @@ -43,12 +41,13 @@ void RegionCreator::construct() { if (nVolumesCurrent == 0) { ACTS_WARNING("No volumes matching \"" << volumeName << "\" found in G4 LogicalVolumeStore. " - << m_name << " G4PhysicsRegion may not behave as intended."); + << m_cfg.name + << " G4PhysicsRegion may not behave as intended."); } nVolumes += nVolumesCurrent; } - ACTS_INFO("Created region " << m_name); + ACTS_INFO("Created region " << m_cfg.name); ACTS_INFO("A total of " << nVolumes << " volumes were assigned"); // create a G4ProductionCuts object and set appropriate values @@ -66,6 +65,8 @@ void RegionCreator::construct() { // assign cuts to the region region->SetProductionCuts(cuts); + + return region; } } // namespace ActsExamples::Geant4 diff --git a/Examples/Algorithms/Geant4HepMC/include/ActsExamples/Geant4HepMC/EventRecording.hpp b/Examples/Algorithms/Geant4HepMC/include/ActsExamples/Geant4HepMC/EventRecording.hpp index 6f45940182c..fead7f18d97 100644 --- a/Examples/Algorithms/Geant4HepMC/include/ActsExamples/Geant4HepMC/EventRecording.hpp +++ b/Examples/Algorithms/Geant4HepMC/include/ActsExamples/Geant4HepMC/EventRecording.hpp @@ -9,11 +9,13 @@ #pragma once #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Framework/DataHandle.hpp" #include "ActsExamples/Framework/IAlgorithm.hpp" #include "ActsExamples/Framework/ProcessCode.hpp" #include "ActsExamples/Framework/SequenceElement.hpp" +#include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp" #include #include @@ -38,8 +40,11 @@ class EventRecording final : public ActsExamples::IAlgorithm { /// The recorded events output std::string outputHepMcTracks = "geant-outcome-tracks"; - std::shared_ptr - detectorConstructionFactory; + /// Geant4 construction options. + Geant4ConstructionOptions constructionOptions; + + /// Detector instance to access Geant4 geometry construction. + std::shared_ptr detector; /// random number seed 1 int seed1 = 12345; diff --git a/Examples/Algorithms/Geant4HepMC/src/EventRecording.cpp b/Examples/Algorithms/Geant4HepMC/src/EventRecording.cpp index 93c27b0c1c1..51826af837a 100644 --- a/Examples/Algorithms/Geant4HepMC/src/EventRecording.cpp +++ b/Examples/Algorithms/Geant4HepMC/src/EventRecording.cpp @@ -10,7 +10,6 @@ #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/Framework/WhiteBoard.hpp" -#include "ActsExamples/Geant4/DetectorConstructionFactory.hpp" #include @@ -41,7 +40,7 @@ EventRecording::EventRecording(const EventRecording::Config& config, if (m_cfg.outputHepMcTracks.empty()) { throw std::invalid_argument("Missing output event collection"); } - if (m_cfg.detectorConstructionFactory == nullptr) { + if (m_cfg.detector == nullptr) { throw std::invalid_argument("Missing detector construction object"); } @@ -52,7 +51,8 @@ EventRecording::EventRecording(const EventRecording::Config& config, // G4RunManager deals with the lifetime of these objects m_runManager->SetUserInitialization( - m_cfg.detectorConstructionFactory->factorize().release()); + m_cfg.detector->buildGeant4DetectorConstruction(m_cfg.constructionOptions) + .release()); m_runManager->SetUserInitialization(new FTFP_BERT); m_runManager->SetUserAction(new Geant4::HepMC3::RunAction()); m_runManager->SetUserAction( diff --git a/Examples/Detectors/CMakeLists.txt b/Examples/Detectors/CMakeLists.txt index 7e7aed5daed..ee63c6a3eb5 100644 --- a/Examples/Detectors/CMakeLists.txt +++ b/Examples/Detectors/CMakeLists.txt @@ -1,3 +1,4 @@ +add_subdirectory(Common) add_subdirectory(ContextualDetector) add_subdirectory_if(DD4hepDetector ACTS_BUILD_EXAMPLES_DD4HEP) add_subdirectory(GenericDetector) @@ -7,3 +8,4 @@ add_subdirectory(TGeoDetector) add_subdirectory(ITkModuleSplitting) add_subdirectory(TelescopeDetector) add_subdirectory_if(MuonSpectrometerMockupDetector ACTS_BUILD_EXAMPLES_GEANT4) +add_subdirectory_if(GeoModelDetector ACTS_BUILD_PLUGIN_GEOMODEL) diff --git a/Examples/Detectors/Common/CMakeLists.txt b/Examples/Detectors/Common/CMakeLists.txt new file mode 100644 index 00000000000..1464b2274c8 --- /dev/null +++ b/Examples/Detectors/Common/CMakeLists.txt @@ -0,0 +1,14 @@ +add_library(ActsExamplesDetectorsCommon SHARED src/Detector.cpp) +target_include_directories( + ActsExamplesDetectorsCommon + PUBLIC $ +) +target_link_libraries( + ActsExamplesDetectorsCommon + PUBLIC ActsCore ActsExamplesFramework +) + +install( + TARGETS ActsExamplesDetectorsCommon + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/Examples/Detectors/Common/include/ActsExamples/DetectorCommons/Detector.hpp b/Examples/Detectors/Common/include/ActsExamples/DetectorCommons/Detector.hpp new file mode 100644 index 00000000000..40e026f1935 --- /dev/null +++ b/Examples/Detectors/Common/include/ActsExamples/DetectorCommons/Detector.hpp @@ -0,0 +1,69 @@ +// 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/Geometry/GeometryContext.hpp" + +#include +#include + +namespace Acts { +class GeometryContext; +class TrackingGeometry; +class DetectorElementBase; +class Logger; +namespace Experimental { +class Detector; +} // namespace Experimental +} // namespace Acts + +namespace ActsExamples { +class IContextDecorator; +struct Geant4ConstructionOptions; +} // namespace ActsExamples + +class G4VUserDetectorConstruction; + +namespace ActsExamples { + +/// Base class for detector instances +class Detector { + public: + explicit Detector(std::unique_ptr logger); + virtual ~Detector(); + + virtual const Acts::GeometryContext& nominalGeometryContext() const; + + virtual std::shared_ptr trackingGeometry() + const; + virtual std::shared_ptr gen2Geometry() const; + virtual std::vector> contextDecorators() + const; + + /// Build the Geant4 detector construction + /// @note This throws an exception if Geant4 is not enabled + /// @param options The Geant4 construction options + /// @return The Geant4 detector construction + virtual std::unique_ptr + buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const; + + protected: + const Acts::Logger& logger() const; + + std::unique_ptr m_logger; + + Acts::GeometryContext m_nominalGeometryContext; + std::shared_ptr m_trackingGeometry; + std::shared_ptr m_gen2Geometry; + std::vector> m_detectorStore; + std::vector> m_contextDecorators; +}; + +} // namespace ActsExamples diff --git a/Examples/Detectors/Common/src/Detector.cpp b/Examples/Detectors/Common/src/Detector.cpp new file mode 100644 index 00000000000..ce1291a2485 --- /dev/null +++ b/Examples/Detectors/Common/src/Detector.cpp @@ -0,0 +1,58 @@ +// 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/DetectorCommons/Detector.hpp" + +#include "Acts/Detector/Detector.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/Framework/IContextDecorator.hpp" + +namespace ActsExamples { + +Detector::Detector(std::unique_ptr logger) + : m_logger(std::move(logger)) {} + +Detector::~Detector() = default; + +std::vector> Detector::contextDecorators() + const { + return m_contextDecorators; +} + +std::unique_ptr +Detector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& /*options*/) const { + throw std::runtime_error("Geant4 detector construction is not available."); +} + +const Acts::GeometryContext& Detector::nominalGeometryContext() const { + return m_nominalGeometryContext; +} + +std::shared_ptr Detector::trackingGeometry() + const { + if (m_trackingGeometry == nullptr) { + throw std::runtime_error("Tracking geometry is not built"); + } + return m_trackingGeometry; +} + +std::shared_ptr Detector::gen2Geometry() const { + if (m_gen2Geometry == nullptr) { + throw std::runtime_error("Gen2 geometry is not built"); + } + return m_gen2Geometry; +} + +const Acts::Logger& Detector::logger() const { + return *m_logger; +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/ContextualDetector/CMakeLists.txt b/Examples/Detectors/ContextualDetector/CMakeLists.txt index 2cb61207c53..550a4cb450a 100644 --- a/Examples/Detectors/ContextualDetector/CMakeLists.txt +++ b/Examples/Detectors/ContextualDetector/CMakeLists.txt @@ -5,13 +5,19 @@ add_library( src/InternalAlignmentDecorator.cpp src/ExternalAlignmentDecorator.cpp ) + target_include_directories( ActsExamplesDetectorContextual PUBLIC $ ) + target_link_libraries( ActsExamplesDetectorContextual - PUBLIC ActsCore ActsExamplesFramework ActsExamplesDetectorGeneric + PUBLIC + ActsCore + ActsExamplesFramework + ActsExamplesDetectorsCommon + ActsExamplesDetectorGeneric ) install( diff --git a/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp b/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp index cfd12568d63..0859c174e5a 100644 --- a/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp +++ b/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp @@ -10,31 +10,18 @@ #include "Acts/Definitions/Units.hpp" #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include "ActsExamples/GenericDetector/GenericDetector.hpp" #include -#include -#include -#include - -namespace Acts { -class TrackingGeometry; -class IMaterialDecorator; -} // namespace Acts namespace ActsExamples { -class IContextDecorator; -class GenericDetectorElement; - class InternallyAlignedDetectorElement; class InternalAlignmentDecorator; -class AlignedDetector { +class AlignedDetector : public Detector { public: - using ContextDecorators = std::vector>; - using TrackingGeometryPtr = std::shared_ptr; - struct Config : public GenericDetector::Config { /// Seed for the decorator random numbers. std::size_t seed = 1324354657; @@ -59,21 +46,14 @@ class AlignedDetector { enum class Mode { Internal, External }; Mode mode = Mode::Internal; - }; - std::pair finalize( - const Config& cfg, - std::shared_ptr mdecorator); + std::shared_ptr materialDecorator; + }; - std::vector>>& - detectorStore() { - return m_detectorStore; - } + explicit AlignedDetector(const Config& cfg); private: - /// The Store of the detector elements (lifetime: job) - std::vector>> - m_detectorStore; + Config m_cfg; }; } // namespace ActsExamples diff --git a/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp b/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp index 9a9c5901543..3284723cebc 100644 --- a/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp +++ b/Examples/Detectors/ContextualDetector/src/AlignedDetector.cpp @@ -9,7 +9,6 @@ #include "ActsExamples/ContextualDetector/AlignedDetector.hpp" #include "Acts/Geometry/GeometryContext.hpp" -#include "Acts/Geometry/TrackingGeometry.hpp" #include "Acts/Utilities/Logger.hpp" #include "ActsExamples/ContextualDetector/AlignmentDecorator.hpp" #include "ActsExamples/ContextualDetector/ExternalAlignmentDecorator.hpp" @@ -19,94 +18,88 @@ #include "ActsExamples/Framework/RandomNumbers.hpp" #include "ActsExamples/GenericDetector/BuildGenericDetector.hpp" +#include + namespace ActsExamples { -auto AlignedDetector::finalize( - const Config& cfg, - std::shared_ptr mdecorator) - -> std::pair { - ContextDecorators aContextDecorators; +AlignedDetector::AlignedDetector(const Config& cfg) + : Detector(Acts::getDefaultLogger("AlignedDetector", cfg.logLevel)), + m_cfg(cfg) { + if (m_cfg.mode == Config::Mode::External) { + InternallyAlignedDetectorElement::ContextType nominalContext; + m_nominalGeometryContext = Acts::GeometryContext(nominalContext); + } else { + InternallyAlignedDetectorElement::ContextType nominalContext; + nominalContext.nominal = true; + m_nominalGeometryContext = Acts::GeometryContext(nominalContext); + } // Let's create a random number service RandomNumbers::Config randomNumberConfig; - randomNumberConfig.seed = cfg.seed; + randomNumberConfig.seed = m_cfg.seed; auto randomNumberSvc = std::make_shared(randomNumberConfig); auto fillDecoratorConfig = [&](AlignmentDecorator::Config& config) { - config.iovSize = cfg.iovSize; - config.flushSize = cfg.flushSize; - config.doGarbageCollection = cfg.doGarbageCollection; + config.iovSize = m_cfg.iovSize; + config.flushSize = m_cfg.flushSize; + config.doGarbageCollection = m_cfg.doGarbageCollection; // The misalignments - config.gSigmaX = cfg.sigmaInPlane; - config.gSigmaY = cfg.sigmaInPlane; - config.gSigmaZ = cfg.sigmaOutPlane; - config.aSigmaX = cfg.sigmaOutRot; - config.aSigmaY = cfg.sigmaOutRot; - config.aSigmaZ = cfg.sigmaInRot; + config.gSigmaX = m_cfg.sigmaInPlane; + config.gSigmaY = m_cfg.sigmaInPlane; + config.gSigmaZ = m_cfg.sigmaOutPlane; + config.aSigmaX = m_cfg.sigmaOutRot; + config.aSigmaY = m_cfg.sigmaOutRot; + config.aSigmaZ = m_cfg.sigmaInRot; config.randomNumberSvc = randomNumberSvc; - config.firstIovNominal = cfg.firstIovNominal; + config.firstIovNominal = m_cfg.firstIovNominal; }; - TrackingGeometryPtr aTrackingGeometry; - if (cfg.mode == Config::Mode::External) { - ExternallyAlignedDetectorElement::ContextType nominalContext; - Acts::GeometryContext geometryContext(nominalContext); - + if (m_cfg.mode == Config::Mode::External) { ExternalAlignmentDecorator::Config agcsConfig; fillDecoratorConfig(agcsConfig); std::vector>> - detectorStore; + specificDetectorStore; - aTrackingGeometry = + m_trackingGeometry = Generic::buildDetector( - geometryContext, detectorStore, cfg.buildLevel, - std::move(mdecorator), cfg.buildProto, cfg.surfaceLogLevel, - cfg.layerLogLevel, cfg.volumeLogLevel); - - agcsConfig.trackingGeometry = aTrackingGeometry; + m_nominalGeometryContext, specificDetectorStore, m_cfg.buildLevel, + m_cfg.materialDecorator, m_cfg.buildProto, m_cfg.surfaceLogLevel, + m_cfg.layerLogLevel, m_cfg.volumeLogLevel); + agcsConfig.trackingGeometry = m_trackingGeometry; // need to upcast to store in this object as well - for (auto& lstore : detectorStore) { - auto& target = m_detectorStore.emplace_back(); + for (auto& lstore : specificDetectorStore) { for (auto& ldet : lstore) { - target.push_back(ldet); + m_detectorStore.push_back(ldet); } } - aContextDecorators.push_back(std::make_shared( + m_contextDecorators.push_back(std::make_shared( std::move(agcsConfig), - Acts::getDefaultLogger("AlignmentDecorator", cfg.decoratorLogLevel))); + Acts::getDefaultLogger("AlignmentDecorator", m_cfg.decoratorLogLevel))); } else { - InternallyAlignedDetectorElement::ContextType nominalContext; - nominalContext.nominal = true; - Acts::GeometryContext geometryContext(nominalContext); - InternalAlignmentDecorator::Config agcsConfig; fillDecoratorConfig(agcsConfig); - aTrackingGeometry = + m_trackingGeometry = Generic::buildDetector( - geometryContext, agcsConfig.detectorStore, cfg.buildLevel, - std::move(mdecorator), cfg.buildProto, cfg.surfaceLogLevel, - cfg.layerLogLevel, cfg.volumeLogLevel); + m_nominalGeometryContext, agcsConfig.detectorStore, + m_cfg.buildLevel, m_cfg.materialDecorator, m_cfg.buildProto, + m_cfg.surfaceLogLevel, m_cfg.layerLogLevel, m_cfg.volumeLogLevel); // need to upcast to store in this object as well for (auto& lstore : agcsConfig.detectorStore) { - auto& target = m_detectorStore.emplace_back(); for (auto& ldet : lstore) { - target.push_back(ldet); + m_detectorStore.push_back(ldet); } } - aContextDecorators.push_back(std::make_shared( + m_contextDecorators.push_back(std::make_shared( std::move(agcsConfig), - Acts::getDefaultLogger("AlignmentDecorator", cfg.decoratorLogLevel))); + Acts::getDefaultLogger("AlignmentDecorator", m_cfg.decoratorLogLevel))); } - - // return the pair of geometry and the alignment decorator(s) - return {std::move(aTrackingGeometry), std::move(aContextDecorators)}; } } // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/CMakeLists.txt b/Examples/Detectors/DD4hepDetector/CMakeLists.txt index 244b4f20a6d..d09e8e1345f 100644 --- a/Examples/Detectors/DD4hepDetector/CMakeLists.txt +++ b/Examples/Detectors/DD4hepDetector/CMakeLists.txt @@ -1,9 +1,4 @@ -add_library( - ActsExamplesDetectorDD4hep - SHARED - src/DD4hepDetector.cpp - src/DD4hepGeometryService.cpp -) +add_library(ActsExamplesDetectorDD4hep SHARED src/DD4hepDetector.cpp) target_include_directories( ActsExamplesDetectorDD4hep @@ -11,9 +6,37 @@ target_include_directories( ) target_link_libraries( ActsExamplesDetectorDD4hep - PUBLIC ActsCore ActsPluginDD4hep ActsExamplesFramework + PUBLIC + ActsCore + ActsPluginDD4hep + ActsExamplesFramework + ActsExamplesDetectorsCommon ) +if(ACTS_BUILD_EXAMPLES_GEANT4) + if(${DD4hep_VERSION} VERSION_LESS 1.11) + target_link_libraries( + ActsExamplesDetectorDD4hep + PUBLIC ActsExamplesGeant4 ${DD4hep_DDG4_LIBRARY} + ) + else() + target_link_libraries( + ActsExamplesDetectorDD4hep + PUBLIC ActsExamplesGeant4 DD4hep::DDG4 + ) + endif() + + target_sources( + ActsExamplesDetectorDD4hep + PUBLIC src/DDG4DetectorConstruction.cpp src/DD4hepDetectorGeant4.cpp + ) +else() + target_sources( + ActsExamplesDetectorDD4hep + PUBLIC src/DD4hepDetectorGeant4Stub.cpp + ) +endif() + if(${DD4hep_VERSION} VERSION_LESS 1.11) target_include_directories( ActsExamplesDetectorDD4hep diff --git a/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepDetector.hpp b/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepDetector.hpp index 422af198ff5..acb17042e78 100644 --- a/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepDetector.hpp +++ b/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepDetector.hpp @@ -8,84 +8,96 @@ #pragma once -#include "Acts/Plugins/DD4hep/DD4hepDetectorElement.hpp" -#include "Acts/Plugins/DD4hep/DD4hepDetectorStructure.hpp" -#include "ActsExamples/DD4hepDetector/DD4hepGeometryService.hpp" - +#include "Acts/Definitions/Units.hpp" +#include "Acts/Geometry/GeometryIdentifier.hpp" +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "Acts/Material/IMaterialDecorator.hpp" +#include "Acts/Utilities/BinningType.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" + +#include #include -#include -#include +#include #include +class TGeoNode; + namespace dd4hep { class Detector; +class DetElement; } // namespace dd4hep -namespace Acts { -class TrackingGeometry; -class IMaterialDecorator; -class DD4hepFieldAdapter; -namespace Experimental { -class Detector; -} // namespace Experimental -} // namespace Acts - namespace ActsExamples { -class IContextDecorator; - -struct DD4hepDetector { - /// @brief The context decorators - using ContextDecorators = std::vector>; - - /// @brief The tracking geometry - using TrackingGeometryPtr = std::shared_ptr; - - /// @brief The detector geometry - using DetectorPtr = std::shared_ptr; - - /// @brief Default constructor - DD4hepDetector() = default; - /// @brief Constructor from geometry service - /// @param _geometryService the geometry service - explicit DD4hepDetector( - std::shared_ptr _geometryService); - /// @brief Default destructor - ~DD4hepDetector() = default; - - /// @brief The DD4hep geometry service - std::shared_ptr geometryService = nullptr; - - /// @brief Build the tracking geometry from the DD4hep geometry - /// - /// @param config is the configuration of the geometry service - /// @param mdecorator is the material decorator provided - /// - /// @return a pair of tracking geometry and context decorators - std::pair finalize( - DD4hepGeometryService::Config config, - std::shared_ptr mdecorator); - - /// @brief Build the detector from the DD4hep geometry - /// - /// @param gctx is the geometry context - /// @param options is the options struct for the building process - /// - /// @note the lifetime of the detector store has to exceed that of the - /// detector object as the converted surfaces point back to the - /// detector elements - /// - /// @return a tuple of detector, context decorators, and the element store - std::tuple - finalize( - const Acts::GeometryContext& gctx, - const Acts::Experimental::DD4hepDetectorStructure::Options& options = {}); - - void drop(); - - /// @brief Access to the DD4hep field - /// @return a shared pointer to the DD4hep field - std::shared_ptr field() const; +void sortFCChhDetElements(std::vector& det); + +/// @class DD4hepDetector +/// +/// @brief geometries from dd4hep input +/// +/// The DD4hepDetector creates the DD4hep, the TGeo and the ACTS +/// TrackingGeometry from DD4hep xml input. +class DD4hepDetector : public Detector { + public: + struct Config { + /// Log level for the geometry service. + Acts::Logging::Level logLevel = Acts::Logging::Level::INFO; + /// Log level for DD4hep itself + Acts::Logging::Level dd4hepLogLevel = Acts::Logging::Level::WARNING; + /// XML-file with the detector description + std::vector xmlFileNames; + /// The name of the service + std::string name = "default"; + /// Binningtype in phi + Acts::BinningType bTypePhi = Acts::equidistant; + /// Binningtype in r + Acts::BinningType bTypeR = Acts::arbitrary; + /// Binningtype in z + Acts::BinningType bTypeZ = Acts::equidistant; + /// The tolerance added to the geometrical extension in r + /// of the layers contained to build the volume envelope around + /// @note this parameter only needs to be set if the volumes containing + /// the + /// layers (e.g. barrel, endcap volumes) have no specific shape + /// (assemblies) + double envelopeR = 1 * Acts::UnitConstants::mm; + /// The tolerance added to the geometrical extension in z + /// of the layers contained to build the volume envelope around + /// @note this parameter only needs to be set if the volumes containing + /// the layers (e.g. barrel, endcap volumes) have no specific shape + /// (assemblies) + double envelopeZ = 1 * Acts::UnitConstants::mm; + double defaultLayerThickness = 1e-10; + std::function& detectors)> + sortDetectors = sortFCChhDetElements; + /// Material decorator + std::shared_ptr materialDecorator; + + /// Optional geometry identifier hook to be used during closure + std::shared_ptr geometryIdentifierHook = + std::make_shared(); + }; + + explicit DD4hepDetector(const Config& cfg); + + /// Interface method to access to the DD4hep geometry + dd4hep::Detector& dd4hepDetector(); + + /// Interface method to Access the TGeo geometry + /// @return The world TGeoNode (physical volume) + TGeoNode& tgeoGeometry(); + + std::unique_ptr buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const override; + + private: + Config m_cfg; + + /// Pointer to the interface to the DD4hep geometry + std::shared_ptr m_detector; + + std::unique_ptr buildDD4hepGeometry() const; }; } // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepGeometryService.hpp b/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepGeometryService.hpp deleted file mode 100644 index 1111f8de8e7..00000000000 --- a/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DD4hepGeometryService.hpp +++ /dev/null @@ -1,137 +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/Geometry/GeometryContext.hpp" -#include "Acts/Geometry/GeometryIdentifier.hpp" -#include "ActsExamples/Framework/ProcessCode.hpp" -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -class TGeoNode; -namespace Acts { -class IMaterialDecorator; -class TrackingGeometry; -} // namespace Acts -namespace dd4hep { -class Detector; -} // namespace dd4hep - -namespace ActsExamples { - -void sortFCChhDetElements(std::vector& det); - -/// @class DD4hepGeometryService -/// -/// @brief service creating geometries from dd4hep input -/// -/// The DD4hepGeometryService creates the DD4hep, the TGeo and the ACTS -/// TrackingGeometry -/// from DD4hep xml input. The geometries are created only on demand. -class DD4hepGeometryService { - public: - struct Config { - /// Log level for the geometry service. - Acts::Logging::Level logLevel = Acts::Logging::Level::INFO; - /// Log level for DD4hep itself - Acts::Logging::Level dd4hepLogLevel = Acts::Logging::Level::WARNING; - /// XML-file with the detector description - std::vector xmlFileNames; - /// The name of the service - std::string name = "default"; - /// Binningtype in phi - Acts::BinningType bTypePhi = Acts::equidistant; - /// Binningtype in r - Acts::BinningType bTypeR = Acts::arbitrary; - /// Binningtype in z - Acts::BinningType bTypeZ = Acts::equidistant; - /// The tolerance added to the geometrical extension in r - /// of the layers contained to build the volume envelope around - /// @note this parameter only needs to be set if the volumes containing - /// the - /// layers (e.g. barrel, endcap volumes) have no specific shape - /// (assemblies) - double envelopeR = 1 * Acts::UnitConstants::mm; - /// The tolerance added to the geometrical extension in z - /// of the layers contained to build the volume envelope around - /// @note this parameter only needs to be set if the volumes containing - /// the layers (e.g. barrel, endcap volumes) have no specific shape - /// (assemblies) - double envelopeZ = 1 * Acts::UnitConstants::mm; - double defaultLayerThickness = 10e-10; - std::function& detectors)> - sortDetectors = sortFCChhDetElements; - /// Material decorator - std::shared_ptr matDecorator; - - /// Optional geometry identifier hook to be used during closure - std::shared_ptr geometryIdentifierHook = - std::make_shared(); - }; - - explicit DD4hepGeometryService(const Config& cfg); - DD4hepGeometryService(const DD4hepGeometryService&) = delete; - DD4hepGeometryService(DD4hepGeometryService&&) = delete; - ~DD4hepGeometryService(); - DD4hepGeometryService& operator=(const DD4hepGeometryService&) = delete; - DD4hepGeometryService& operator=(DD4hepGeometryService&&) = delete; - - /// Interface method to access to the DD4hep geometry - dd4hep::Detector& detector(); - - /// Interface method to access the DD4hep geometry - /// @return The world DD4hep DetElement - dd4hep::DetElement& geometry(); - - /// Interface method to Access the TGeo geometry - /// @return The world TGeoNode (physical volume) - TGeoNode& tgeoGeometry(); - - /// Interface method to access the ACTS TrackingGeometry - /// - /// @param gctx is the geometry context object - std::shared_ptr trackingGeometry( - const Acts::GeometryContext& gctx); - - void drop(); - - private: - /// Private method to initiate building of the DD4hep geometry - ProcessCode buildDD4hepGeometry(); - - /// Private method to initiate building of the ACTS tracking geometry - ProcessCode buildTrackingGeometry(const Acts::GeometryContext& gctx); - - /// The config class - Config m_cfg; - /// Pointer to the interface to the DD4hep geometry - dd4hep::Detector* m_detector = nullptr; - /// The world DD4hep DetElement - dd4hep::DetElement m_geometry; - /// The ACTS TrackingGeometry - std::shared_ptr m_trackingGeometry; - - const Acts::Logger& logger() const { return *m_logger; } - - std::unique_ptr m_logger; -}; - -} // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DDG4DetectorConstruction.hpp b/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DDG4DetectorConstruction.hpp new file mode 100644 index 00000000000..aef260161c3 --- /dev/null +++ b/Examples/Detectors/DD4hepDetector/include/ActsExamples/DD4hepDetector/DDG4DetectorConstruction.hpp @@ -0,0 +1,50 @@ +// 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/Geant4/Geant4ConstructionOptions.hpp" + +#include + +#include + +class G4VPhysicalVolume; + +namespace dd4hep { +class Detector; +} // namespace dd4hep + +namespace ActsExamples { +class DD4hepDetector; + +/// Construct the Geant4 detector from a DD4hep description. +class DDG4DetectorConstruction final : public G4VUserDetectorConstruction { + public: + explicit DDG4DetectorConstruction(std::shared_ptr detector, + const Geant4ConstructionOptions& options); + + /// Convert the stored DD4hep detector to a Geant4 description. + /// + /// Transfers ownership of the created object as all volumes (including world) + /// are deleted in ~G4PhysicalVolumeStore(). + /// + /// @note for facilitating configuration within the ACTS framework the world + /// volume is cached + G4VPhysicalVolume* Construct() final; + + private: + /// The DD4hep detector instance + std::shared_ptr m_detector; + /// Construction options + Geant4ConstructionOptions m_options; + /// The world volume + G4VPhysicalVolume* m_world = nullptr; +}; + +} // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp b/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp index a2c12dfaaa7..a411a32a028 100644 --- a/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp +++ b/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp @@ -8,77 +8,136 @@ #include "ActsExamples/DD4hepDetector/DD4hepDetector.hpp" -#include "Acts/Geometry/GeometryContext.hpp" -#include "Acts/Plugins/DD4hep/DD4hepFieldAdapter.hpp" -#include "ActsExamples/DD4hepDetector/DD4hepGeometryService.hpp" +#include "Acts/Plugins/DD4hep/ConvertDD4hepDetector.hpp" +#include "Acts/Utilities/Logger.hpp" +#include #include #include -#include #include #include -#include -#include +#include +#include +#include +#include namespace ActsExamples { -DD4hepDetector::DD4hepDetector( - std::shared_ptr _geometryService) - : geometryService(std::move(_geometryService)) {} - -auto DD4hepDetector::finalize( - DD4hepGeometryService::Config config, - std::shared_ptr mdecorator) - -> std::pair { - Acts::GeometryContext dd4HepContext; - config.matDecorator = std::move(mdecorator); - geometryService = std::make_shared(config); - TrackingGeometryPtr dd4tGeometry = - geometryService->trackingGeometry(dd4HepContext); - if (!dd4tGeometry) { - throw std::runtime_error{ - "Did not receive tracking geometry from DD4hep geometry service"}; - } - ContextDecorators dd4ContextDecorators = {}; - // return the pair of geometry and empty decorators - return {std::move(dd4tGeometry), std::move(dd4ContextDecorators)}; -} - -auto DD4hepDetector::finalize( - const Acts::GeometryContext& gctx, - const Acts::Experimental::DD4hepDetectorStructure::Options& options) - -> std::tuple { - if (geometryService == nullptr) { - throw std::runtime_error{ - "No DD4hep geometry service configured, can not build " - "TrackingGeometry."}; +DD4hepDetector::DD4hepDetector(const Config& cfg) + : Detector(Acts::getDefaultLogger("DD4hepDetector", cfg.logLevel)), + m_cfg(cfg) { + if (m_cfg.xmlFileNames.empty()) { + throw std::invalid_argument("Missing DD4hep XML filenames"); } - auto world = geometryService->geometry(); - // Build the detector structure - Acts::Experimental::DD4hepDetectorStructure dd4hepStructure( - Acts::getDefaultLogger("DD4hepDetectorStructure", options.logLevel)); + m_nominalGeometryContext = Acts::GeometryContext(); - /// @return a detector and the detector store - auto [detector, detectorElements] = - dd4hepStructure.construct(gctx, world, options); + m_detector = buildDD4hepGeometry(); - // Prepare the return objects - ContextDecorators contextDecorators = {}; + auto logger = Acts::getDefaultLogger("DD4hepConversion", m_cfg.logLevel); + m_trackingGeometry = Acts::convertDD4hepDetector( + m_detector->world(), *logger, m_cfg.bTypePhi, m_cfg.bTypeR, m_cfg.bTypeZ, + m_cfg.envelopeR, m_cfg.envelopeZ, m_cfg.defaultLayerThickness, + m_cfg.sortDetectors, m_nominalGeometryContext, m_cfg.materialDecorator, + m_cfg.geometryIdentifierHook); +} - return {detector, contextDecorators, detectorElements}; +dd4hep::Detector& DD4hepDetector::dd4hepDetector() { + return *m_detector; } -void DD4hepDetector::drop() { - geometryService->drop(); +TGeoNode& DD4hepDetector::tgeoGeometry() { + return *m_detector->world().placement().ptr(); } -std::shared_ptr DD4hepDetector::field() const { - const auto& detector = geometryService->detector(); +std::unique_ptr DD4hepDetector::buildDD4hepGeometry() const { + const int old_gErrorIgnoreLevel = gErrorIgnoreLevel; + switch (m_cfg.dd4hepLogLevel) { + case Acts::Logging::Level::VERBOSE: + dd4hep::setPrintLevel(dd4hep::PrintLevel::VERBOSE); + break; + case Acts::Logging::Level::DEBUG: + dd4hep::setPrintLevel(dd4hep::PrintLevel::DEBUG); + break; + case Acts::Logging::Level::INFO: + dd4hep::setPrintLevel(dd4hep::PrintLevel::INFO); + break; + case Acts::Logging::Level::WARNING: + dd4hep::setPrintLevel(dd4hep::PrintLevel::WARNING); + gErrorIgnoreLevel = kWarning; + break; + case Acts::Logging::Level::ERROR: + dd4hep::setPrintLevel(dd4hep::PrintLevel::ERROR); + gErrorIgnoreLevel = kError; + break; + case Acts::Logging::Level::FATAL: + dd4hep::setPrintLevel(dd4hep::PrintLevel::FATAL); + gErrorIgnoreLevel = kFatal; + break; + case Acts::Logging::Level::MAX: + dd4hep::setPrintLevel(dd4hep::PrintLevel::ALWAYS); + break; + } + // completely silence std::cout as DD4HEP is using it for logging + if (m_cfg.dd4hepLogLevel >= Acts::Logging::Level::WARNING) { + std::cout.setstate(std::ios_base::failbit); + } + + std::unique_ptr detector = + dd4hep::Detector::make_unique(m_cfg.name); + for (auto& file : m_cfg.xmlFileNames) { + detector->fromCompact(file.c_str()); + } + detector->volumeManager(); + detector->apply("DD4hepVolumeManager", 0, nullptr); + + // restore the logging + gErrorIgnoreLevel = old_gErrorIgnoreLevel; + std::cout.clear(); - return std::make_shared(detector.field()); + return detector; } } // namespace ActsExamples + +void ActsExamples::sortFCChhDetElements(std::vector& det) { + std::vector tracker; + std::vector eCal; + std::vector hCal; + std::vector muon; + for (auto& detElement : det) { + std::string detName = detElement.name(); + if (detName.find("Muon") != std::string::npos) { + muon.push_back(detElement); + } else if (detName.find("ECal") != std::string::npos) { + eCal.push_back(detElement); + } else if (detName.find("HCal") != std::string::npos) { + hCal.push_back(detElement); + } else { + tracker.push_back(detElement); + } + } + sort(muon.begin(), muon.end(), + [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); + sort(eCal.begin(), eCal.end(), + [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); + sort(hCal.begin(), hCal.end(), + [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); + sort(tracker.begin(), tracker.end(), + [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); + det.clear(); + det = tracker; + + det.insert(det.end(), eCal.begin(), eCal.end()); + det.insert(det.end(), hCal.begin(), hCal.end()); + det.insert(det.end(), muon.begin(), muon.end()); +} diff --git a/Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4.cpp b/Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4.cpp new file mode 100644 index 00000000000..ab2510fe8ae --- /dev/null +++ b/Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4.cpp @@ -0,0 +1,22 @@ +// 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/DD4hepDetector/DD4hepDetector.hpp" +#include "ActsExamples/DD4hepDetector/DDG4DetectorConstruction.hpp" + +#include + +namespace ActsExamples { + +std::unique_ptr +DD4hepDetector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const { + return std::make_unique(m_detector, options); +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4Stub.cpp b/Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4Stub.cpp new file mode 100644 index 00000000000..ea08bab2bfc --- /dev/null +++ b/Examples/Detectors/DD4hepDetector/src/DD4hepDetectorGeant4Stub.cpp @@ -0,0 +1,19 @@ +// 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/DD4hepDetector/DD4hepDetector.hpp" + +namespace ActsExamples { + +std::unique_ptr +DD4hepDetector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& /*options*/) const { + throw std::runtime_error("Geant4 is not enabled"); +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/src/DD4hepGeometryService.cpp b/Examples/Detectors/DD4hepDetector/src/DD4hepGeometryService.cpp deleted file mode 100644 index c746dd84343..00000000000 --- a/Examples/Detectors/DD4hepDetector/src/DD4hepGeometryService.cpp +++ /dev/null @@ -1,174 +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/DD4hepDetector/DD4hepGeometryService.hpp" - -#include "Acts/Geometry/TrackingGeometry.hpp" -#include "Acts/Plugins/DD4hep/ConvertDD4hepDetector.hpp" -#include "Acts/Utilities/Logger.hpp" - -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace ActsExamples { - -DD4hepGeometryService::DD4hepGeometryService(const Config& cfg) - : m_cfg(cfg), - m_logger{Acts::getDefaultLogger("DD4hepGeometryService", cfg.logLevel)} { - if (m_cfg.xmlFileNames.empty()) { - throw std::invalid_argument("Missing DD4hep XML filenames"); - } -} - -DD4hepGeometryService::~DD4hepGeometryService() { - drop(); -} - -ProcessCode DD4hepGeometryService::buildDD4hepGeometry() { - const int old_gErrorIgnoreLevel = gErrorIgnoreLevel; - switch (m_cfg.dd4hepLogLevel) { - case Acts::Logging::Level::VERBOSE: - dd4hep::setPrintLevel(dd4hep::PrintLevel::VERBOSE); - break; - case Acts::Logging::Level::DEBUG: - dd4hep::setPrintLevel(dd4hep::PrintLevel::DEBUG); - break; - case Acts::Logging::Level::INFO: - dd4hep::setPrintLevel(dd4hep::PrintLevel::INFO); - break; - case Acts::Logging::Level::WARNING: - dd4hep::setPrintLevel(dd4hep::PrintLevel::WARNING); - gErrorIgnoreLevel = kWarning; - break; - case Acts::Logging::Level::ERROR: - dd4hep::setPrintLevel(dd4hep::PrintLevel::ERROR); - gErrorIgnoreLevel = kError; - break; - case Acts::Logging::Level::FATAL: - dd4hep::setPrintLevel(dd4hep::PrintLevel::FATAL); - gErrorIgnoreLevel = kFatal; - break; - case Acts::Logging::Level::MAX: - dd4hep::setPrintLevel(dd4hep::PrintLevel::ALWAYS); - break; - } - // completely silence std::cout as DD4HEP is using it for logging - if (m_cfg.dd4hepLogLevel >= Acts::Logging::Level::WARNING) { - std::cout.setstate(std::ios_base::failbit); - } - - m_detector = &dd4hep::Detector::getInstance(); - for (auto& file : m_cfg.xmlFileNames) { - m_detector->fromCompact(file.c_str()); - } - m_detector->volumeManager(); - m_detector->apply("DD4hepVolumeManager", 0, nullptr); - m_geometry = m_detector->world(); - - // restore the logging - gErrorIgnoreLevel = old_gErrorIgnoreLevel; - std::cout.clear(); - - return ProcessCode::SUCCESS; -} - -dd4hep::Detector& DD4hepGeometryService::detector() { - if (m_detector == nullptr) { - buildDD4hepGeometry(); - } - return *m_detector; -} - -dd4hep::DetElement& DD4hepGeometryService::geometry() { - if (!m_geometry) { - buildDD4hepGeometry(); - } - return m_geometry; -} - -TGeoNode& DD4hepGeometryService::tgeoGeometry() { - if (!m_geometry) { - buildDD4hepGeometry(); - } - return *m_geometry.placement().ptr(); -} - -ProcessCode DD4hepGeometryService::buildTrackingGeometry( - const Acts::GeometryContext& gctx) { - // Set the tracking geometry - auto logger = Acts::getDefaultLogger("DD4hepConversion", m_cfg.logLevel); - m_trackingGeometry = Acts::convertDD4hepDetector( - geometry(), *logger, m_cfg.bTypePhi, m_cfg.bTypeR, m_cfg.bTypeZ, - m_cfg.envelopeR, m_cfg.envelopeZ, m_cfg.defaultLayerThickness, - m_cfg.sortDetectors, gctx, m_cfg.matDecorator, - m_cfg.geometryIdentifierHook); - return ProcessCode::SUCCESS; -} - -std::shared_ptr -DD4hepGeometryService::trackingGeometry(const Acts::GeometryContext& gctx) { - if (!m_trackingGeometry) { - buildTrackingGeometry(gctx); - } - return m_trackingGeometry; -} - -void DD4hepGeometryService::drop() { - if (m_detector == nullptr) { - return; - } - dd4hep::Detector::destroyInstance(m_cfg.name); - m_detector = nullptr; - m_geometry = dd4hep::DetElement(); - m_trackingGeometry = nullptr; -} - -void sortFCChhDetElements(std::vector& det) { - std::vector tracker; - std::vector eCal; - std::vector hCal; - std::vector muon; - - for (auto& detElement : det) { - std::string detName = detElement.name(); - if (detName.find("Muon") != std::string::npos) { - muon.push_back(detElement); - } else if (detName.find("ECal") != std::string::npos) { - eCal.push_back(detElement); - } else if (detName.find("HCal") != std::string::npos) { - hCal.push_back(detElement); - } else { - tracker.push_back(detElement); - } - } - - auto byId = [](const dd4hep::DetElement& a, - const dd4hep::DetElement& b) -> bool { - return a.id() < b.id(); - }; - sort(muon.begin(), muon.end(), byId); - sort(eCal.begin(), eCal.end(), byId); - sort(hCal.begin(), hCal.end(), byId); - sort(tracker.begin(), tracker.end(), byId); - - det.clear(); - det = tracker; - - det.insert(det.end(), eCal.begin(), eCal.end()); - det.insert(det.end(), hCal.begin(), hCal.end()); - det.insert(det.end(), muon.begin(), muon.end()); -} - -} // namespace ActsExamples diff --git a/Examples/Detectors/DD4hepDetector/src/DDG4DetectorConstruction.cpp b/Examples/Detectors/DD4hepDetector/src/DDG4DetectorConstruction.cpp new file mode 100644 index 00000000000..41700ae063e --- /dev/null +++ b/Examples/Detectors/DD4hepDetector/src/DDG4DetectorConstruction.cpp @@ -0,0 +1,52 @@ +// 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/DD4hepDetector/DDG4DetectorConstruction.hpp" + +#include "ActsExamples/DD4hepDetector/DD4hepDetector.hpp" +#include "ActsExamples/Geant4/RegionCreator.hpp" + +#include +#include +#include +#include +#include + +class G4VPhysicalVolume; + +namespace ActsExamples { + +DDG4DetectorConstruction::DDG4DetectorConstruction( + std::shared_ptr detector, + const Geant4ConstructionOptions& options) + : m_detector(std::move(detector)), m_options(options) {} + +// See DD4hep::Simulation::Geant4DetectorConstruction::Construct() +G4VPhysicalVolume* DDG4DetectorConstruction::Construct() { + if (m_world == nullptr) { + dd4hep::sim::Geant4Mapping g4map(*m_detector); + dd4hep::sim::Geant4Converter g4conv(*m_detector, + dd4hep::PrintLevel::VERBOSE); + dd4hep::sim::Geant4GeometryInfo* geoInfo = + g4conv.create(m_detector->world()).detach(); + g4map.attach(geoInfo); + // All volumes are deleted in ~G4PhysicalVolumeStore() + m_world = geoInfo->world(); + // Create Geant4 volume manager + g4map.volumeManager(); + + // Create regions + for (const auto& regionCreator : m_options.regionCreators) { + regionCreator->buildRegion(); + } + } + + return m_world; +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/Geant4Detector/CMakeLists.txt b/Examples/Detectors/Geant4Detector/CMakeLists.txt index 8a83657de5a..541271e65a5 100644 --- a/Examples/Detectors/Geant4Detector/CMakeLists.txt +++ b/Examples/Detectors/Geant4Detector/CMakeLists.txt @@ -1,11 +1,24 @@ -add_library(ActsExamplesDetectorGeant4 SHARED src/Geant4Detector.cpp) +add_library( + ActsExamplesDetectorGeant4 + SHARED + src/Geant4Detector.cpp + src/GdmlDetectorConstruction.cpp + src/GdmlDetector.cpp +) + target_include_directories( ActsExamplesDetectorGeant4 PUBLIC $ ) + target_link_libraries( ActsExamplesDetectorGeant4 - PUBLIC ActsCore ActsExamplesFramework ActsExamplesGeant4 ActsPluginGeant4 + PUBLIC + ActsCore + ActsExamplesFramework + ActsExamplesGeant4 + ActsPluginGeant4 + ActsExamplesDetectorsCommon ) install( diff --git a/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/GdmlDetector.hpp b/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/GdmlDetector.hpp new file mode 100644 index 00000000000..df2c00778be --- /dev/null +++ b/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/GdmlDetector.hpp @@ -0,0 +1,36 @@ +// 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/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" + +#include + +namespace ActsExamples { + +class GdmlDetector : public Detector { + public: + struct Config { + std::string path; + + /// Logging level of the child tools + Acts::Logging::Level logLevel = Acts::Logging::INFO; + }; + + explicit GdmlDetector(const Config& cfg); + + std::unique_ptr buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const override; + + private: + Config m_cfg; +}; + +} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/GdmlDetectorConstruction.hpp b/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/GdmlDetectorConstruction.hpp similarity index 56% rename from Examples/Algorithms/Geant4/include/ActsExamples/Geant4/GdmlDetectorConstruction.hpp rename to Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/GdmlDetectorConstruction.hpp index 69156e94e06..c712023f19e 100644 --- a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/GdmlDetectorConstruction.hpp +++ b/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/GdmlDetectorConstruction.hpp @@ -8,8 +8,7 @@ #pragma once -#include "ActsExamples/Geant4/DetectorConstructionFactory.hpp" -#include "ActsExamples/Geant4/RegionCreator.hpp" +#include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp" #include @@ -24,9 +23,8 @@ class GdmlDetectorConstruction final : public G4VUserDetectorConstruction { public: /// @param path is the path to the Gdml file /// @param regionCreators are the region creators - GdmlDetectorConstruction( - std::string path, - std::vector> regionCreators = {}); + GdmlDetectorConstruction(std::string path, + const Geant4ConstructionOptions& options); /// Read the file and parse it to construct the Geant4 description /// @@ -37,26 +35,10 @@ class GdmlDetectorConstruction final : public G4VUserDetectorConstruction { private: /// Path to the Gdml file std::string m_path; - /// Region creators - std::vector> m_regionCreators; + /// Construction options + Geant4ConstructionOptions m_options; /// Cached world volume G4VPhysicalVolume* m_world = nullptr; }; -class GdmlDetectorConstructionFactory final - : public Geant4::DetectorConstructionFactory { - public: - GdmlDetectorConstructionFactory( - std::string path, - std::vector> regionCreators = {}); - - std::unique_ptr factorize() const override; - - private: - /// Path to the Gdml file - std::string m_path; - /// Region creators - std::vector> m_regionCreators; -}; - } // namespace ActsExamples diff --git a/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/Geant4Detector.hpp b/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/Geant4Detector.hpp index 32c7a7568ea..cad39584a36 100644 --- a/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/Geant4Detector.hpp +++ b/Examples/Detectors/Geant4Detector/include/ActsExamples/Geant4Detector/Geant4Detector.hpp @@ -12,6 +12,7 @@ #include "Acts/Geometry/GeometryIdentifier.hpp" #include "Acts/Plugins/Geant4/Geant4DetectorSurfaceFactory.hpp" #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include #include @@ -20,29 +21,9 @@ class G4VPhysicalVolume; -namespace Acts { -class TrackingGeometry; -class Geant4DetectorElement; -class Surface; - -namespace Experimental { -class Detector; -} -} // namespace Acts - namespace ActsExamples { -class IContextDecorator; - -struct Geant4Detector { - using DetectorElements = - std::vector>; - using DetectorPtr = std::shared_ptr; - using Surfaces = std::vector>; - - using ContextDecorators = std::vector>; - using TrackingGeometryPtr = std::shared_ptr; - +struct Geant4Detector : public Detector { /// Nested configuration struct struct Config { /// The detector/geometry name @@ -60,32 +41,20 @@ struct Geant4Detector { Acts::Logging::Level logLevel = Acts::Logging::INFO; }; - /// @brief Construct an Acts::Detector from a Geant4 world volume - /// @param cfg the configuration of the Geant4 detector - /// @param logger a logger instance - /// @return a tuple of an Acts::Detector object, a ContextDecorator & the created detector elements - std::tuple - constructDetector(const Config& cfg, const Acts::Logger& logger); - - /// @brief Construct a TrackingGeometry from a Geant4 world volume using the KDTreeTrackingGeometryBuilder builder - /// - /// @param cfg the configuration of the Geant4 detector - /// @param kdtCfg the configuration of the KDTreeTrackingGeometryBuilder - /// @param logger a logger instance - /// - /// @return a tuple of an Acts::TrackingGeometry object, a ContextDecorator & the created detector elements - std::tuple - constructTrackingGeometry(const Config& cfg, const Acts::Logger& logger); - - private: /// @brief Convert Geant4VPhysicalVolume objects into Acts components /// /// @param cfg the configuration of the Geant4 detector /// @param logger a logger instance /// /// @return a tuple of surfaces and detector elements - std::tuple convertGeant4Volumes( - const Config& cfg, const Acts::Logger& logger) const; + static std::tuple>, + std::vector>> + buildGeant4Volumes(const Config& cfg, const Acts::Logger& logger); + + explicit Geant4Detector(const Config& cfg); + + private: + Config m_cfg; }; } // namespace ActsExamples diff --git a/Examples/Detectors/Geant4Detector/src/GdmlDetector.cpp b/Examples/Detectors/Geant4Detector/src/GdmlDetector.cpp new file mode 100644 index 00000000000..75f0a6ba036 --- /dev/null +++ b/Examples/Detectors/Geant4Detector/src/GdmlDetector.cpp @@ -0,0 +1,27 @@ +// 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/Geant4Detector/GdmlDetector.hpp" + +#include "ActsExamples/Geant4Detector/GdmlDetectorConstruction.hpp" + +#include + +namespace ActsExamples { + +GdmlDetector::GdmlDetector(const Config& cfg) + : Detector(Acts::getDefaultLogger("GdmlDetector", cfg.logLevel)), + m_cfg(cfg) {} + +std::unique_ptr +GdmlDetector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const { + return std::make_unique(m_cfg.path, options); +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/Geant4Detector/src/GdmlDetectorConstruction.cpp b/Examples/Detectors/Geant4Detector/src/GdmlDetectorConstruction.cpp new file mode 100644 index 00000000000..b617cf687b6 --- /dev/null +++ b/Examples/Detectors/Geant4Detector/src/GdmlDetectorConstruction.cpp @@ -0,0 +1,41 @@ +// 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/Geant4Detector/GdmlDetectorConstruction.hpp" + +#include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp" +#include "ActsExamples/Geant4/RegionCreator.hpp" + +#include + +#include + +namespace ActsExamples { + +GdmlDetectorConstruction::GdmlDetectorConstruction( + std::string path, const Geant4ConstructionOptions& options) + : G4VUserDetectorConstruction(), + m_path(std::move(path)), + m_options(options) {} + +G4VPhysicalVolume* GdmlDetectorConstruction::Construct() { + if (m_world == nullptr) { + G4GDMLParser parser; + // TODO how to handle errors + parser.Read(m_path); + m_world = parser.GetWorldVolume(); + + // Create regions + for (const auto& regionCreator : m_options.regionCreators) { + regionCreator->buildRegion(); + } + } + return m_world; +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/Geant4Detector/src/Geant4Detector.cpp b/Examples/Detectors/Geant4Detector/src/Geant4Detector.cpp index 67ab547db26..15642e9f912 100644 --- a/Examples/Detectors/Geant4Detector/src/Geant4Detector.cpp +++ b/Examples/Detectors/Geant4Detector/src/Geant4Detector.cpp @@ -9,14 +9,16 @@ #include "ActsExamples/Geant4Detector/Geant4Detector.hpp" #include "Acts/Geometry/CylinderVolumeHelper.hpp" -#include "Acts/Geometry/GeometryContext.hpp" #include "Acts/Geometry/KDTreeTrackingGeometryBuilder.hpp" #include "Acts/Geometry/LayerArrayCreator.hpp" #include "Acts/Geometry/LayerCreator.hpp" #include "Acts/Geometry/SurfaceArrayCreator.hpp" #include "Acts/Geometry/TrackingGeometry.hpp" #include "Acts/Geometry/TrackingVolumeArrayCreator.hpp" +#include "Acts/Plugins/Geant4/Geant4DetectorElement.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include #include #include @@ -25,88 +27,67 @@ namespace ActsExamples { -auto Geant4Detector::constructDetector(const Geant4Detector::Config& cfg, - const Acts::Logger& logger) - -> std::tuple { - if (cfg.g4World == nullptr) { - throw std::invalid_argument( - "Geant4Detector: no world Geant4 volume provided"); - } - - ACTS_INFO("Building an Acts::Detector called '" - << cfg.name << "' from the Geant4PhysVolume '" - << cfg.g4World->GetName() << "'"); - - DetectorPtr detector = nullptr; - ContextDecorators decorators = {}; - - auto [surfaces, elements] = convertGeant4Volumes(cfg, logger); - - return std::tie(detector, decorators, elements); -} - -auto Geant4Detector::constructTrackingGeometry( - const Geant4Detector::Config& cfg, const Acts::Logger& logger) - -> std::tuple { - if (cfg.g4World == nullptr) { +Geant4Detector::Geant4Detector(const Config& cfg) + : Detector(Acts::getDefaultLogger("Geant4Detector", cfg.logLevel)), + m_cfg(cfg) { + if (m_cfg.g4World == nullptr) { throw std::invalid_argument( "Geant4Detector: no world Geant4 volume provided"); } ACTS_INFO("Building an Acts::TrackingGeometry called '" - << cfg.name << "' from the Geant4PhysVolume '" - << cfg.g4World->GetName() << "'"); + << m_cfg.name << "' from the Geant4PhysVolume '" + << m_cfg.g4World->GetName() << "'"); - ContextDecorators decorators = {}; + m_nominalGeometryContext = Acts::GeometryContext(); - auto [surfaces, elements] = convertGeant4Volumes(cfg, logger); + auto [surfaces, elements] = buildGeant4Volumes(cfg, logger()); // Surface array creator auto surfaceArrayCreator = std::make_shared( - Acts::SurfaceArrayCreator::Config(), logger.clone("SurfaceArrayCreator")); + Acts::SurfaceArrayCreator::Config(), + logger().clone("SurfaceArrayCreator")); // Layer Creator Acts::LayerCreator::Config lcConfig; lcConfig.surfaceArrayCreator = surfaceArrayCreator; auto layerCreator = std::make_shared( - lcConfig, logger.clone("LayerCreator")); + lcConfig, logger().clone("LayerCreator")); // Layer array creator Acts::LayerArrayCreator::Config lacConfig; auto layerArrayCreator = std::make_shared( - lacConfig, logger.clone("LayerArrayCreator")); + lacConfig, logger().clone("LayerArrayCreator")); // Tracking volume array creator Acts::TrackingVolumeArrayCreator::Config tvacConfig; auto tVolumeArrayCreator = std::make_shared( - tvacConfig, logger.clone("TrackingVolumeArrayCreator")); + tvacConfig, logger().clone("TrackingVolumeArrayCreator")); // configure the cylinder volume helper Acts::CylinderVolumeHelper::Config cvhConfig; cvhConfig.layerArrayCreator = layerArrayCreator; cvhConfig.trackingVolumeArrayCreator = tVolumeArrayCreator; auto cylinderVolumeHelper = std::make_shared( - cvhConfig, logger.clone("CylinderVolumeHelper")); + cvhConfig, logger().clone("CylinderVolumeHelper")); // Configure the tracking geometry builder, copy the surfaces in Acts::KDTreeTrackingGeometryBuilder::Config kdtCfg; kdtCfg.surfaces = surfaces; kdtCfg.layerCreator = layerCreator; kdtCfg.trackingVolumeHelper = cylinderVolumeHelper; - kdtCfg.protoDetector = cfg.protoDetector; - kdtCfg.geometryIdentifierHook = cfg.geometryIdentifierHook; + kdtCfg.protoDetector = m_cfg.protoDetector; + kdtCfg.geometryIdentifierHook = m_cfg.geometryIdentifierHook; // The KDT tracking geometry builder auto kdtBuilder = Acts::KDTreeTrackingGeometryBuilder( - kdtCfg, logger.clone("KDTreeTrackingGeometryBuilder")); + kdtCfg, logger().clone("KDTreeTrackingGeometryBuilder")); - Acts::GeometryContext tContext; - TrackingGeometryPtr trackingGeometry = kdtBuilder.trackingGeometry(tContext); - - return std::tie(trackingGeometry, decorators, elements); + m_trackingGeometry = kdtBuilder.trackingGeometry(m_nominalGeometryContext); } -auto Geant4Detector::convertGeant4Volumes(const Geant4Detector::Config& cfg, - const Acts::Logger& logger) const - -> std::tuple { +std::tuple>, + std::vector>> +Geant4Detector::buildGeant4Volumes(const Config& cfg, + const Acts::Logger& logger) { // Generate the surface cache Acts::Geant4DetectorSurfaceFactory::Cache g4SurfaceCache; G4Transform3D g4ToWorld; @@ -123,8 +104,8 @@ auto Geant4Detector::convertGeant4Volumes(const Geant4Detector::Config& cfg, ACTS_INFO("Found " << g4SurfaceCache.convertedMaterials << " converted Geant4 Material slabs."); - Surfaces surfaces = {}; - DetectorElements elements = {}; + std::vector> surfaces; + std::vector> elements; // Reserve the right amount of surfaces surfaces.reserve(g4SurfaceCache.sensitiveSurfaces.size() + @@ -133,14 +114,14 @@ auto Geant4Detector::convertGeant4Volumes(const Geant4Detector::Config& cfg, // Add the sensitive surfaces for (const auto& [e, s] : g4SurfaceCache.sensitiveSurfaces) { - elements.push_back(e); surfaces.push_back(s); + elements.push_back(e); } // Add the passive surfaces surfaces.insert(surfaces.end(), g4SurfaceCache.passiveSurfaces.begin(), g4SurfaceCache.passiveSurfaces.end()); - return std::tie(surfaces, elements); + return {std::move(surfaces), std::move(elements)}; } } // namespace ActsExamples diff --git a/Examples/Detectors/GenericDetector/CMakeLists.txt b/Examples/Detectors/GenericDetector/CMakeLists.txt index 242e710df42..7260a22b128 100644 --- a/Examples/Detectors/GenericDetector/CMakeLists.txt +++ b/Examples/Detectors/GenericDetector/CMakeLists.txt @@ -5,12 +5,16 @@ add_library( src/GenericDetector.cpp src/GenericDetectorElement.cpp ) + target_include_directories( ActsExamplesDetectorGeneric PUBLIC $ ) -target_link_libraries(ActsExamplesDetectorGeneric PUBLIC ActsCore) -target_link_libraries(ActsExamplesDetectorGeneric PUBLIC ActsExamplesFramework) + +target_link_libraries( + ActsExamplesDetectorGeneric + PUBLIC ActsCore ActsExamplesFramework ActsExamplesDetectorsCommon +) install( TARGETS ActsExamplesDetectorGeneric diff --git a/Examples/Detectors/GenericDetector/include/ActsExamples/GenericDetector/GenericDetector.hpp b/Examples/Detectors/GenericDetector/include/ActsExamples/GenericDetector/GenericDetector.hpp index 21a1f85bec6..5b8517baad1 100644 --- a/Examples/Detectors/GenericDetector/include/ActsExamples/GenericDetector/GenericDetector.hpp +++ b/Examples/Detectors/GenericDetector/include/ActsExamples/GenericDetector/GenericDetector.hpp @@ -9,44 +9,35 @@ #pragma once #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include #include -#include -#include namespace Acts { -class TrackingGeometry; class IMaterialDecorator; -} // namespace Acts - -namespace ActsExamples { -class IContextDecorator; -} // namespace ActsExamples +} namespace ActsExamples { class GenericDetectorElement; -struct GenericDetector { - using ContextDecorators = std::vector>; - using TrackingGeometryPtr = std::shared_ptr; - +class GenericDetector : public Detector { + public: struct Config { - std::size_t buildLevel{3}; - Acts::Logging::Level surfaceLogLevel{Acts::Logging::INFO}; - Acts::Logging::Level layerLogLevel{Acts::Logging::INFO}; - Acts::Logging::Level volumeLogLevel{Acts::Logging::INFO}; - bool buildProto{false}; + std::size_t buildLevel = 3; + Acts::Logging::Level logLevel = Acts::Logging::INFO; + Acts::Logging::Level surfaceLogLevel = Acts::Logging::INFO; + Acts::Logging::Level layerLogLevel = Acts::Logging::INFO; + Acts::Logging::Level volumeLogLevel = Acts::Logging::INFO; + bool buildProto = false; + std::shared_ptr materialDecorator; }; - /// The Store of the detector elements (lifetime: job) - std::vector>> - detectorStore; + explicit GenericDetector(const Config& cfg); - std::pair finalize( - const Config& cfg, - std::shared_ptr mdecorator); + private: + Config m_cfg; }; } // namespace ActsExamples diff --git a/Examples/Detectors/GenericDetector/src/GenericDetector.cpp b/Examples/Detectors/GenericDetector/src/GenericDetector.cpp index b80b769708d..f996d225e79 100644 --- a/Examples/Detectors/GenericDetector/src/GenericDetector.cpp +++ b/Examples/Detectors/GenericDetector/src/GenericDetector.cpp @@ -8,26 +8,28 @@ #include "ActsExamples/GenericDetector/GenericDetector.hpp" -#include "Acts/Geometry/TrackingGeometry.hpp" #include "ActsExamples/GenericDetector/BuildGenericDetector.hpp" #include "ActsExamples/GenericDetector/GenericDetectorElement.hpp" namespace ActsExamples { -auto GenericDetector::finalize( - const Config& cfg, - std::shared_ptr mdecorator) - -> std::pair { - GenericDetectorElement::ContextType nominalContext; - /// Return the generic detector - TrackingGeometryPtr gGeometry = - Generic::buildDetector( - nominalContext, detectorStore, cfg.buildLevel, std::move(mdecorator), - cfg.buildProto, cfg.surfaceLogLevel, cfg.layerLogLevel, - cfg.volumeLogLevel); - ContextDecorators gContextDecorators = {}; - // return the pair of geometry and empty decorators - return {std::move(gGeometry), std::move(gContextDecorators)}; +GenericDetector::GenericDetector(const Config& cfg) + : Detector(Acts::getDefaultLogger("GenericDetector", cfg.logLevel)), + m_cfg(cfg) { + m_nominalGeometryContext = Acts::GeometryContext(); + + std::vector>> + specificDetectorStore; + m_trackingGeometry = Generic::buildDetector( + m_nominalGeometryContext, specificDetectorStore, m_cfg.buildLevel, + m_cfg.materialDecorator, m_cfg.buildProto, m_cfg.surfaceLogLevel, + m_cfg.layerLogLevel, m_cfg.volumeLogLevel); + + for (const auto& something : specificDetectorStore) { + for (const auto& element : something) { + m_detectorStore.push_back(element); + } + } } } // namespace ActsExamples diff --git a/Examples/Detectors/GeoModelDetector/CMakeLists.txt b/Examples/Detectors/GeoModelDetector/CMakeLists.txt new file mode 100644 index 00000000000..af7c6d7e494 --- /dev/null +++ b/Examples/Detectors/GeoModelDetector/CMakeLists.txt @@ -0,0 +1,41 @@ +add_library(ActsExamplesDetectorGeoModel SHARED src/GeoModelDetector.cpp) + +target_include_directories( + ActsExamplesDetectorGeoModel + PUBLIC $ +) + +target_link_libraries( + ActsExamplesDetectorGeoModel + PUBLIC + ActsCore + ActsExamplesFramework + ActsPluginGeoModel + ActsExamplesDetectorsCommon +) + +if(ACTS_BUILD_EXAMPLES_GEANT4) + find_library(GeoModel2G4_LIBRARY GeoModel2G4 REQUIRED) + + target_link_libraries( + ActsExamplesDetectorGeoModel + PUBLIC ActsExamplesGeant4 ${GeoModel2G4_LIBRARY} + ) + + target_sources( + ActsExamplesDetectorGeoModel + PUBLIC + src/GeoModelGeant4DetectorConstruction.cpp + src/GeoModelDetectorGeant4.cpp + ) +else() + target_sources( + ActsExamplesDetectorDD4hep + PUBLIC src/GeoModelDetectorGeant4Stub.cpp + ) +endif() + +install( + TARGETS ActsExamplesDetectorGeoModel + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} +) diff --git a/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelDetector.hpp b/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelDetector.hpp new file mode 100644 index 00000000000..6d687dcc625 --- /dev/null +++ b/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelDetector.hpp @@ -0,0 +1,37 @@ +// 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/GeoModel/GeoModelTree.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" + +#include + +namespace ActsExamples { + +struct GeoModelDetector : public Detector { + struct Config { + std::string path; + + /// Logging level of the child tools + Acts::Logging::Level logLevel = Acts::Logging::INFO; + }; + + explicit GeoModelDetector(const Config& cfg); + + std::unique_ptr buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const override; + + private: + Config m_cfg; + Acts::GeoModelTree m_geoModel; +}; + +} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/GeoModelG4/GeoModelDetectorConstruction.hpp b/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelGeant4DetectorConstruction.hpp similarity index 52% rename from Examples/Algorithms/Geant4/include/ActsExamples/GeoModelG4/GeoModelDetectorConstruction.hpp rename to Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelGeant4DetectorConstruction.hpp index d3070bbc590..fd8390d566c 100644 --- a/Examples/Algorithms/Geant4/include/ActsExamples/GeoModelG4/GeoModelDetectorConstruction.hpp +++ b/Examples/Detectors/GeoModelDetector/include/ActsExamples/GeoModelDetector/GeoModelGeant4DetectorConstruction.hpp @@ -9,8 +9,7 @@ #pragma once #include "Acts/Plugins/GeoModel/GeoModelTree.hpp" -#include "ActsExamples/Geant4/DetectorConstructionFactory.hpp" -#include "ActsExamples/Geant4/RegionCreator.hpp" +#include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp" #include @@ -19,13 +18,13 @@ class G4VPhysicalVolume; namespace ActsExamples { /// Construct the Geant4 detector from a GeoModel world volume -class GeoModelDetectorConstruction final : public G4VUserDetectorConstruction { +class GeoModelGeant4DetectorConstruction final + : public G4VUserDetectorConstruction { public: /// @param geoModelTree is the GeoModel tree containing the world volume /// @param regionCreators are the region creators - GeoModelDetectorConstruction( - const Acts::GeoModelTree& geoModelTree, - std::vector> regionCreators = {}); + GeoModelGeant4DetectorConstruction(const Acts::GeoModelTree& geoModelTree, + const Geant4ConstructionOptions& options); /// Read the file and parse it to construct the Geant4 description /// @@ -36,26 +35,10 @@ class GeoModelDetectorConstruction final : public G4VUserDetectorConstruction { private: /// The GeoModel tree Acts::GeoModelTree m_geoModelTree; - /// Region creators - std::vector> m_regionCreators; + /// Construction options + Geant4ConstructionOptions m_options; /// The world volume G4VPhysicalVolume* m_g4World = nullptr; }; -class GeoModelDetectorConstructionFactory final - : public Geant4::DetectorConstructionFactory { - public: - GeoModelDetectorConstructionFactory( - const Acts::GeoModelTree& geoModelTree, - std::vector> regionCreators = {}); - - std::unique_ptr factorize() const override; - - private: - /// The GeoModel tree - Acts::GeoModelTree m_geoModelTree; - /// Region creators - std::vector> m_regionCreators; -}; - } // namespace ActsExamples diff --git a/Examples/Detectors/GeoModelDetector/src/GeoModelDetector.cpp b/Examples/Detectors/GeoModelDetector/src/GeoModelDetector.cpp new file mode 100644 index 00000000000..12e151385ed --- /dev/null +++ b/Examples/Detectors/GeoModelDetector/src/GeoModelDetector.cpp @@ -0,0 +1,24 @@ +// 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/GeoModelDetector/GeoModelDetector.hpp" + +#include "Acts/Plugins/GeoModel/GeoModelReader.hpp" +#include "Acts/Plugins/GeoModel/GeoModelTree.hpp" + +#include + +namespace ActsExamples { + +GeoModelDetector::GeoModelDetector(const Config& cfg) + : Detector(Acts::getDefaultLogger("GeoModelDetector", cfg.logLevel)), + m_cfg(cfg) { + m_geoModel = Acts::GeoModelReader::readFromDb(m_cfg.path); +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4.cpp b/Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4.cpp new file mode 100644 index 00000000000..1035ea9bcca --- /dev/null +++ b/Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4.cpp @@ -0,0 +1,21 @@ +// 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/GeoModelDetector/GeoModelDetector.hpp" +#include "ActsExamples/GeoModelDetector/GeoModelGeant4DetectorConstruction.hpp" + +namespace ActsExamples { + +std::unique_ptr +GeoModelDetector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const { + return std::make_unique(m_geoModel, + options); +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4Stub.cpp b/Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4Stub.cpp new file mode 100644 index 00000000000..2d19ee1770a --- /dev/null +++ b/Examples/Detectors/GeoModelDetector/src/GeoModelDetectorGeant4Stub.cpp @@ -0,0 +1,19 @@ +// 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/GeoModelDetector/GeoModelDetector.hpp" + +namespace ActsExamples { + +std::unique_ptr +GeoModelDetector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& /*options*/) const { + throw std::runtime_error("Geant4 is not enabled"); +} + +} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/src/GeoModelDetectorConstruction.cpp b/Examples/Detectors/GeoModelDetector/src/GeoModelGeant4DetectorConstruction.cpp similarity index 53% rename from Examples/Algorithms/Geant4/src/GeoModelDetectorConstruction.cpp rename to Examples/Detectors/GeoModelDetector/src/GeoModelGeant4DetectorConstruction.cpp index 8d33622714b..d58151ba921 100644 --- a/Examples/Algorithms/Geant4/src/GeoModelDetectorConstruction.cpp +++ b/Examples/Detectors/GeoModelDetector/src/GeoModelGeant4DetectorConstruction.cpp @@ -6,9 +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 "ActsExamples/GeoModelG4/GeoModelDetectorConstruction.hpp" +#include "ActsExamples/GeoModelDetector/GeoModelGeant4DetectorConstruction.hpp" -#include +#include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp" +#include "ActsExamples/Geant4/RegionCreator.hpp" #include #include @@ -17,22 +18,22 @@ #include #include -using namespace ActsExamples; +namespace ActsExamples { -GeoModelDetectorConstruction::GeoModelDetectorConstruction( +GeoModelGeant4DetectorConstruction::GeoModelGeant4DetectorConstruction( const Acts::GeoModelTree& geoModelTree, - std::vector> regionCreators) + const Geant4ConstructionOptions& options) : G4VUserDetectorConstruction(), m_geoModelTree(geoModelTree), - m_regionCreators(std::move(regionCreators)) { + m_options(options) { if (geoModelTree.worldVolume == nullptr) { throw std::invalid_argument( - "GeoModelDetectorConstruction: " + "GeoModelGeant4DetectorConstruction: " "GeoModel world volume is nullptr"); } } -G4VPhysicalVolume* GeoModelDetectorConstruction::Construct() { +G4VPhysicalVolume* GeoModelGeant4DetectorConstruction::Construct() { if (m_g4World == nullptr) { ExtParameterisedVolumeBuilder builder(m_geoModelTree.worldVolumeName); G4LogicalVolume* g4WorldLog = builder.Build(m_geoModelTree.worldVolume); @@ -41,21 +42,11 @@ G4VPhysicalVolume* GeoModelDetectorConstruction::Construct() { m_geoModelTree.worldVolumeName, nullptr, false, 0); // Create regions - for (const auto& regionCreator : m_regionCreators) { - regionCreator->construct(); + for (const auto& regionCreator : m_options.regionCreators) { + regionCreator->buildRegion(); } } return m_g4World; } -GeoModelDetectorConstructionFactory::GeoModelDetectorConstructionFactory( - const Acts::GeoModelTree& geoModelTree, - std::vector> regionCreators) - : m_geoModelTree(geoModelTree), - m_regionCreators(std::move(regionCreators)) {} - -std::unique_ptr -GeoModelDetectorConstructionFactory::factorize() const { - return std::make_unique(m_geoModelTree, - m_regionCreators); -} +} // namespace ActsExamples diff --git a/Examples/Detectors/MuonSpectrometerMockupDetector/src/MockupSectorBuilder.cpp b/Examples/Detectors/MuonSpectrometerMockupDetector/src/MockupSectorBuilder.cpp index 585aef06756..71cd8c83907 100644 --- a/Examples/Detectors/MuonSpectrometerMockupDetector/src/MockupSectorBuilder.cpp +++ b/Examples/Detectors/MuonSpectrometerMockupDetector/src/MockupSectorBuilder.cpp @@ -28,7 +28,7 @@ #include "Acts/Visualization/GeometryView3D.hpp" #include "Acts/Visualization/ObjVisualization3D.hpp" #include "Acts/Visualization/ViewConfig.hpp" -#include "ActsExamples/Geant4/GdmlDetectorConstruction.hpp" +#include "ActsExamples/Geant4Detector/GdmlDetectorConstruction.hpp" #include "ActsExamples/Geant4Detector/Geant4Detector.hpp" #include @@ -47,7 +47,7 @@ namespace ActsExamples { MockupSectorBuilder::MockupSectorBuilder( const MockupSectorBuilder::Config& config) { mCfg = config; - GdmlDetectorConstruction geo_gdml(mCfg.gdmlPath); + GdmlDetectorConstruction geo_gdml(mCfg.gdmlPath, {}); g4World = geo_gdml.Construct(); } @@ -78,13 +78,14 @@ MockupSectorBuilder::buildChamber( g4SurfaceOptions.passiveSurfaceSelector = g4Passive; g4WorldConfig.g4SurfaceOptions = g4SurfaceOptions; - auto g4detector = Geant4Detector(); - - auto [detector, surfaces, detectorElements] = - g4detector.constructDetector(g4WorldConfig, Acts::getDummyLogger()); + auto g4Detector = Geant4Detector(g4WorldConfig); + // Trigger the build of the detector + auto [surface, elements] = Geant4Detector::buildGeant4Volumes( + g4WorldConfig, + *Acts::getDefaultLogger("MockupSectorBuilder", Acts::Logging::INFO)); // The vector that holds the converted sensitive surfaces of the chamber - std::vector> strawSurfaces = {}; + std::vector> strawSurfaces; std::array, 3> min_max; std::fill(min_max.begin(), min_max.end(), @@ -92,7 +93,7 @@ MockupSectorBuilder::buildChamber( -std::numeric_limits::max())); // Convert the physical volumes of the detector elements to straw surfaces - for (auto& detectorElement : detectorElements) { + for (const auto& detectorElement : elements) { auto context = Acts::GeometryContext(); auto g4conv = Acts::Geant4PhysicalVolumeConverter(); diff --git a/Examples/Detectors/TGeoDetector/CMakeLists.txt b/Examples/Detectors/TGeoDetector/CMakeLists.txt index 3df36c62f15..11fac0c179c 100644 --- a/Examples/Detectors/TGeoDetector/CMakeLists.txt +++ b/Examples/Detectors/TGeoDetector/CMakeLists.txt @@ -9,6 +9,7 @@ target_include_directories( ActsExamplesDetectorTGeo PUBLIC $ ) + target_link_libraries( ActsExamplesDetectorTGeo PUBLIC @@ -17,6 +18,7 @@ target_link_libraries( ActsPluginJson ActsExamplesFramework ActsExamplesDetectorGeneric + ActsExamplesDetectorsCommon ActsExamplesITkModuleSplitting ) diff --git a/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/JsonTGeoDetectorConfig.hpp b/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/JsonTGeoDetectorConfig.hpp index c1b006f6593..893367aa890 100644 --- a/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/JsonTGeoDetectorConfig.hpp +++ b/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/JsonTGeoDetectorConfig.hpp @@ -18,6 +18,8 @@ #include #include +#include + // Namespace of the module splitters namespace Acts { @@ -53,9 +55,7 @@ NLOHMANN_JSON_SERIALIZE_ENUM(Acts::BinningType, } // namespace Acts -namespace ActsExamples { - -namespace Options { +namespace ActsExamples::Options { /// Read config for options interval void from_json(const nlohmann::json& j, Interval& interval) { @@ -70,7 +70,9 @@ void to_json(nlohmann::json& j, const Interval& interval) { {"upper", interval.upper.value_or(0)}}; } -} // namespace Options +} // namespace ActsExamples::Options + +namespace ActsExamples { void from_json(const nlohmann::json& j, TGeoITkModuleSplitter::Config& msc) { msc.barrelMap = diff --git a/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/TGeoDetector.hpp b/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/TGeoDetector.hpp index 41134fdfa54..3cad24ecdd8 100644 --- a/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/TGeoDetector.hpp +++ b/Examples/Detectors/TGeoDetector/include/ActsExamples/TGeoDetector/TGeoDetector.hpp @@ -9,9 +9,11 @@ #pragma once #include "Acts/Geometry/GeometryIdentifier.hpp" +#include "Acts/Material/IMaterialDecorator.hpp" #include "Acts/Plugins/TGeo/TGeoLayerBuilder.hpp" #include "Acts/Utilities/BinningType.hpp" #include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include "ActsExamples/Utilities/Options.hpp" #include @@ -22,27 +24,12 @@ #include #include -namespace Acts { -class TGeoDetectorElement; -class TrackingGeometry; -class IMaterialDecorator; -} // namespace Acts - namespace ActsExamples { -class IContextDecorator; - -struct TGeoDetector { - using DetectorElementPtr = std::shared_ptr; - using DetectorStore = std::vector; - - using ContextDecorators = std::vector>; - using TrackingGeometryPtr = std::shared_ptr; - - /// The Store of the detector elements (lifetime: job) - DetectorStore detectorStore; - +class TGeoDetector : public Detector { + public: struct Config { + Acts::Logging::Level logLevel = Acts::Logging::WARNING; Acts::Logging::Level surfaceLogLevel = Acts::Logging::WARNING; Acts::Logging::Level layerLogLevel = Acts::Logging::WARNING; Acts::Logging::Level volumeLogLevel = Acts::Logging::WARNING; @@ -66,6 +53,8 @@ struct TGeoDetector { std::shared_ptr geometryIdentifierHook = std::make_shared(); + std::shared_ptr materialDecorator; + enum SubVolume : std::size_t { Negative = 0, Central, Positive }; template @@ -147,9 +136,10 @@ struct TGeoDetector { static void readTGeoLayerBuilderConfigsFile(const std::string& path, Config& config); - std::pair finalize( - const Config& cfg, - std::shared_ptr mdecorator); + explicit TGeoDetector(const Config& cfg); + + private: + Config m_cfg; }; } // namespace ActsExamples diff --git a/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp b/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp index f594b681d49..884cfc8345d 100644 --- a/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp +++ b/Examples/Detectors/TGeoDetector/src/TGeoDetector.cpp @@ -10,6 +10,7 @@ #include "Acts/Geometry/CylinderVolumeBuilder.hpp" #include "Acts/Geometry/CylinderVolumeHelper.hpp" +#include "Acts/Geometry/DetectorElementBase.hpp" #include "Acts/Geometry/GeometryContext.hpp" #include "Acts/Geometry/ITrackingVolumeBuilder.hpp" #include "Acts/Geometry/LayerArrayCreator.hpp" @@ -26,7 +27,6 @@ #include "Acts/Utilities/Logger.hpp" #include "ActsExamples/TGeoDetector/JsonTGeoDetectorConfig.hpp" #include "ActsExamples/TGeoDetector/TGeoITkModuleSplitter.hpp" -#include "ActsExamples/Utilities/Options.hpp" #include #include @@ -34,6 +34,7 @@ #include #include #include +#include #include #include @@ -41,7 +42,6 @@ #include "TGeoManager.h" namespace ActsExamples { -using namespace Options; namespace { @@ -159,7 +159,7 @@ std::vector makeLayerBuilderConfigs( /// @param vm is the variable map from the options std::shared_ptr buildTGeoDetector( const TGeoDetector::Config& config, const Acts::GeometryContext& context, - std::vector>& + std::vector>& detElementStore, std::shared_ptr mdecorator, const Acts::Logger& logger) { @@ -364,18 +364,14 @@ void TGeoDetector::readTGeoLayerBuilderConfigsFile(const std::string& path, } } -auto TGeoDetector::finalize( - const Config& cfg, - std::shared_ptr mdecorator) - -> std::pair { - Acts::GeometryContext tGeoContext; - auto logger = Acts::getDefaultLogger("TGeoDetector", Acts::Logging::INFO); - TrackingGeometryPtr tgeoTrackingGeometry = buildTGeoDetector( - cfg, tGeoContext, detectorStore, std::move(mdecorator), *logger); - - ContextDecorators tgeoContextDecorators = {}; - // Return the pair of geometry and empty decorators - return {std::move(tgeoTrackingGeometry), std::move(tgeoContextDecorators)}; +TGeoDetector::TGeoDetector(const Config& cfg) + : Detector(Acts::getDefaultLogger("TGeoDetector", cfg.logLevel)), + m_cfg(cfg) { + m_nominalGeometryContext = Acts::GeometryContext(); + + m_trackingGeometry = + buildTGeoDetector(m_cfg, m_nominalGeometryContext, m_detectorStore, + m_cfg.materialDecorator, logger()); } void TGeoDetector::Config::readJson(const std::string& jsonFile) { diff --git a/Examples/Detectors/TelescopeDetector/CMakeLists.txt b/Examples/Detectors/TelescopeDetector/CMakeLists.txt index 9b41a082c42..793465cd6ca 100644 --- a/Examples/Detectors/TelescopeDetector/CMakeLists.txt +++ b/Examples/Detectors/TelescopeDetector/CMakeLists.txt @@ -5,15 +5,36 @@ add_library( src/TelescopeDetectorElement.cpp src/BuildTelescopeDetector.cpp ) + target_include_directories( ActsExamplesDetectorTelescope PUBLIC $ ) + target_link_libraries( ActsExamplesDetectorTelescope - PUBLIC ActsCore ActsExamplesFramework + PUBLIC ActsCore ActsExamplesFramework ActsExamplesDetectorsCommon ) +if(ACTS_BUILD_EXAMPLES_GEANT4) + target_link_libraries( + ActsExamplesDetectorTelescope + PUBLIC ActsExamplesGeant4 + ) + + target_sources( + ActsExamplesDetectorTelescope + PUBLIC + src/TelescopeDetectorGeant4.cpp + src/TelescopeG4DetectorConstruction.cpp + ) +else() + target_sources( + ActsExamplesDetectorTelescope + PUBLIC src/TelescopeDetectorGeant4Stub.cpp + ) +endif() + install( TARGETS ActsExamplesDetectorTelescope LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeDetector.hpp b/Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeDetector.hpp index 76b81984fb0..17145d4d301 100644 --- a/Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeDetector.hpp +++ b/Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeDetector.hpp @@ -9,26 +9,21 @@ #pragma once #include "Acts/Definitions/Units.hpp" -#include "Acts/Geometry/DetectorElementBase.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include #include -#include #include namespace Acts { -class TrackingGeometry; class IMaterialDecorator; } // namespace Acts namespace ActsExamples { -class IContextDecorator; - -struct TelescopeDetector { - using ContextDecorators = std::vector>; - using TrackingGeometryPtr = std::shared_ptr; - +class TelescopeDetector : public Detector { + public: struct Config { std::vector positions{{0, 30, 60, 120, 150, 180}}; std::vector stereos{{0, 0, 0, 0, 0, 0}}; @@ -37,15 +32,17 @@ struct TelescopeDetector { double thickness{80 * Acts::UnitConstants::um}; int surfaceType{0}; int binValue{2}; + std::shared_ptr materialDecorator; + Acts::Logging::Level logLevel{Acts::Logging::WARNING}; }; - Config config; - /// The store of the detector elements (lifetime: job) - std::vector> detectorStore; + explicit TelescopeDetector(const Config& cfg); + + std::unique_ptr buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const override; - std::pair finalize( - const Config& cfg, - const std::shared_ptr& mdecorator); + private: + Config m_cfg; }; } // namespace ActsExamples diff --git a/Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp b/Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp new file mode 100644 index 00000000000..62ee1cc0c28 --- /dev/null +++ b/Examples/Detectors/TelescopeDetector/include/ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp @@ -0,0 +1,38 @@ +// 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/Geant4/Geant4ConstructionOptions.hpp" +#include "ActsExamples/TelescopeDetector/TelescopeDetector.hpp" + +#include "G4VUserDetectorConstruction.hh" + +class G4VPhysicalVolume; +class G4LogicalVolume; + +namespace ActsExamples { + +class TelescopeG4DetectorConstruction final + : public G4VUserDetectorConstruction { + public: + TelescopeG4DetectorConstruction(const TelescopeDetector::Config& cfg, + const Geant4ConstructionOptions& options); + + G4VPhysicalVolume* Construct() final; + + private: + /// The configuration of the telescope detector + TelescopeDetector::Config m_cfg; + /// The Geant4 construction options + Geant4ConstructionOptions m_options; + /// The world volume + G4VPhysicalVolume* m_world{}; +}; + +} // namespace ActsExamples diff --git a/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp b/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp index ccba63a1c34..414658204b8 100644 --- a/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp +++ b/Examples/Detectors/TelescopeDetector/src/TelescopeDetector.cpp @@ -9,59 +9,42 @@ #include "ActsExamples/TelescopeDetector/TelescopeDetector.hpp" #include "Acts/Geometry/GeometryContext.hpp" -#include "Acts/Geometry/TrackingGeometry.hpp" -#include "Acts/Utilities/BinningType.hpp" #include "ActsExamples/TelescopeDetector/BuildTelescopeDetector.hpp" -#include "ActsExamples/TelescopeDetector/TelescopeDetectorElement.hpp" -#include #include namespace ActsExamples { -auto TelescopeDetector::finalize( - const Config& cfg, const std::shared_ptr& - /*mdecorator*/) -> std::pair { - TelescopeDetectorElement::ContextType nominalContext; - - if (cfg.surfaceType > 1) { +TelescopeDetector::TelescopeDetector(const Config& cfg) + : Detector(Acts::getDefaultLogger("TelescopeDetector", cfg.logLevel)), + m_cfg(cfg) { + if (m_cfg.surfaceType > 1) { throw std::invalid_argument( "The surface type could either be 0 for plane surface or 1 for disc " "surface."); } - if (cfg.binValue > 2) { + if (m_cfg.binValue > 2) { throw std::invalid_argument("The axis value could only be 0, 1, or 2."); } // Check if the bounds values are valid - if (cfg.surfaceType == 1 && cfg.bounds[0] >= cfg.bounds[1]) { + if (m_cfg.surfaceType == 1 && m_cfg.bounds[0] >= m_cfg.bounds[1]) { throw std::invalid_argument( "The minR should be smaller than the maxR for disc surface bounds."); } - if (cfg.positions.size() != cfg.stereos.size()) { + if (m_cfg.positions.size() != m_cfg.stereos.size()) { throw std::invalid_argument( "The number of provided positions must match the number of " "provided stereo angles."); } - config = cfg; - - // Sort the provided distances - std::vector positions = cfg.positions; - std::vector stereos = cfg.stereos; - std::ranges::sort(positions); - - Acts::GeometryContext geometryContext(nominalContext); + m_nominalGeometryContext = Acts::GeometryContext(); - // Return the telescope detector - TrackingGeometryPtr gGeometry = - buildTelescopeDetector(geometryContext, detectorStore, positions, stereos, - cfg.offsets, cfg.bounds, cfg.thickness, - static_cast(cfg.surfaceType), - static_cast(cfg.binValue)); - ContextDecorators gContextDecorators = {}; - // return the pair of geometry and empty decorators - return {std::move(gGeometry), std::move(gContextDecorators)}; + m_trackingGeometry = buildTelescopeDetector( + m_nominalGeometryContext, m_detectorStore, m_cfg.positions, m_cfg.stereos, + m_cfg.offsets, m_cfg.bounds, m_cfg.thickness, + static_cast(m_cfg.surfaceType), + static_cast(m_cfg.binValue)); } } // namespace ActsExamples diff --git a/Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4.cpp b/Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4.cpp new file mode 100644 index 00000000000..f385a0a98ef --- /dev/null +++ b/Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4.cpp @@ -0,0 +1,20 @@ +// 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/TelescopeDetector/TelescopeDetector.hpp" +#include "ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp" + +namespace ActsExamples { + +std::unique_ptr +TelescopeDetector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& options) const { + return std::make_unique(m_cfg, options); +} + +} // namespace ActsExamples diff --git a/Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4Stub.cpp b/Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4Stub.cpp new file mode 100644 index 00000000000..93bc305c292 --- /dev/null +++ b/Examples/Detectors/TelescopeDetector/src/TelescopeDetectorGeant4Stub.cpp @@ -0,0 +1,19 @@ +// 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/TelescopeDetector/TelescopeDetector.hpp" + +namespace ActsExamples { + +std::unique_ptr +TelescopeDetector::buildGeant4DetectorConstruction( + const Geant4ConstructionOptions& /*options*/) const { + throw std::runtime_error("Geant4 is not enabled"); +} + +} // namespace ActsExamples diff --git a/Examples/Algorithms/Geant4/src/TelescopeG4DetectorConstruction.cpp b/Examples/Detectors/TelescopeDetector/src/TelescopeG4DetectorConstruction.cpp similarity index 87% rename from Examples/Algorithms/Geant4/src/TelescopeG4DetectorConstruction.cpp rename to Examples/Detectors/TelescopeDetector/src/TelescopeG4DetectorConstruction.cpp index e8412a18c93..2566c069f61 100644 --- a/Examples/Algorithms/Geant4/src/TelescopeG4DetectorConstruction.cpp +++ b/Examples/Detectors/TelescopeDetector/src/TelescopeG4DetectorConstruction.cpp @@ -8,16 +8,10 @@ #include "ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp" -#include "Acts/Utilities/BinningType.hpp" #include "Acts/Utilities/ThrowAssert.hpp" +#include "ActsExamples/Geant4/RegionCreator.hpp" #include "ActsExamples/TelescopeDetector/BuildTelescopeDetector.hpp" - -#include -#include -#include -#include -#include -#include +#include "ActsExamples/TelescopeDetector/TelescopeDetector.hpp" #include "G4Box.hh" #include "G4LogicalVolume.hh" @@ -30,8 +24,8 @@ namespace ActsExamples { TelescopeG4DetectorConstruction::TelescopeG4DetectorConstruction( const TelescopeDetector::Config& cfg, - std::vector> regionCreators) - : m_cfg(cfg), m_regionCreators(std::move(regionCreators)) { + const Geant4ConstructionOptions& options) + : m_cfg(cfg), m_options(options) { throw_assert(cfg.surfaceType == static_cast(TelescopeSurfaceType::Plane), "only plan is supported right now"); } @@ -160,22 +154,11 @@ G4VPhysicalVolume* TelescopeG4DetectorConstruction::Construct() { } // Create regions - for (const auto& regionCreator : m_regionCreators) { - regionCreator->construct(); + for (const auto& regionCreator : m_options.regionCreators) { + regionCreator->buildRegion(); } return m_world; } -TelescopeG4DetectorConstructionFactory::TelescopeG4DetectorConstructionFactory( - const TelescopeDetector::Config& cfg, - std::vector> regionCreators) - : m_cfg(cfg), m_regionCreators(std::move(regionCreators)) {} - -std::unique_ptr -TelescopeG4DetectorConstructionFactory::factorize() const { - return std::make_unique(m_cfg, - m_regionCreators); -} - } // namespace ActsExamples diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepReader.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepReader.hpp index 07c4e771857..60c6a7fb3cf 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepReader.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepReader.hpp @@ -25,7 +25,7 @@ namespace ActsExamples { -struct DD4hepDetector; +class DD4hepDetector; /// Read particles from EDM4hep. /// diff --git a/Examples/Io/EDM4hep/src/EDM4hepReader.cpp b/Examples/Io/EDM4hep/src/EDM4hepReader.cpp index 0b82a20181b..0be54783e8e 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepReader.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepReader.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -323,8 +324,8 @@ ProcessCode EDM4hepReader::read(const AlgorithmContext& ctx) { [&](std::uint64_t cellId) { ACTS_VERBOSE("CellID: " << cellId); - const auto& vm = m_cfg.dd4hepDetector->geometryService->detector() - .volumeManager(); + const auto& vm = + m_cfg.dd4hepDetector->dd4hepDetector().volumeManager(); const auto detElement = vm.lookupDetElement(cellId); diff --git a/Examples/Python/CMakeLists.txt b/Examples/Python/CMakeLists.txt index dc359db0d0d..b7e7a5b4112 100644 --- a/Examples/Python/CMakeLists.txt +++ b/Examples/Python/CMakeLists.txt @@ -91,7 +91,10 @@ else() endif() if(ACTS_BUILD_PLUGIN_GEOMODEL) - target_link_libraries(ActsPythonBindings PUBLIC ActsPluginGeoModel) + target_link_libraries( + ActsPythonBindings + PUBLIC ActsPluginGeoModel ActsExamplesDetectorGeoModel + ) target_sources(ActsPythonBindings PRIVATE src/GeoModel.cpp) else() target_sources(ActsPythonBindings PRIVATE src/GeoModelStub.cpp) @@ -177,56 +180,6 @@ if(ACTS_BUILD_EXAMPLES_GEANT4) ) list(APPEND py_files examples/geant4/__init__.py) - if(ACTS_BUILD_PLUGIN_DD4HEP AND ACTS_BUILD_EXAMPLES_DD4HEP) - pybind11_add_module(ActsPythonBindingsDDG4 src/Geant4DD4hepComponent.cpp) - target_link_libraries( - ActsPythonBindingsDDG4 - PUBLIC - ActsPythonUtilities - ActsExamplesGeant4 - ActsExamplesDetectorDD4hep - ) - add_dependencies(ActsPythonBindings ActsPythonBindingsDDG4) - - install( - TARGETS ActsPythonBindingsDDG4 DESTINATION ${_python_install_dir} - ) - set_target_properties( - ActsPythonBindingsDDG4 - PROPERTIES INSTALL_RPATH "\$ORIGIN/../../${CMAKE_INSTALL_LIBDIR}" - ) - set_target_properties( - ActsPythonBindingsDDG4 - PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${_python_dir}/acts - ) - list(APPEND py_files examples/geant4/dd4hep.py) - endif() - - if(ACTS_BUILD_PLUGIN_GEOMODEL) - pybind11_add_module(ActsPythonBindingsGeoModelG4 src/Geant4GeoModelComponent.cpp) - target_link_libraries( - ActsPythonBindingsGeoModelG4 - PUBLIC ActsPythonUtilities ActsExamplesGeant4 - ) - add_dependencies(ActsPythonBindings ActsPythonBindingsGeoModelG4) - install( - TARGETS - ActsPythonBindingsGeoModelG4 - DESTINATION - ${_python_install_dir} - ) - set_target_properties( - ActsPythonBindingsGeoModelG4 - PROPERTIES INSTALL_RPATH "\$ORIGIN/../../${CMAKE_INSTALL_LIBDIR}" - ) - set_target_properties( - ActsPythonBindingsGeoModelG4 - PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${_python_dir}/acts - ) - - list(APPEND py_files examples/geant4/geomodel.py) - endif() - if(ACTS_BUILD_EXAMPLES_HEPMC3) target_link_libraries( ActsPythonBindingsGeant4 diff --git a/Examples/Python/python/acts/_adapter.py b/Examples/Python/python/acts/_adapter.py index 493fb53b508..ad1962fd079 100644 --- a/Examples/Python/python/acts/_adapter.py +++ b/Examples/Python/python/acts/_adapter.py @@ -2,9 +2,6 @@ import functools from typing import Optional, Callable, Dict, Any from pathlib import Path -from collections import namedtuple - -import acts def _make_config_adapter(fn): @@ -93,53 +90,6 @@ def _patch_config(m): if name == "Config": _patchKwargsConstructor(cls) - if name.endswith("Detector"): - continue - if hasattr(cls, "Config"): cls.__init__ = _make_config_adapter(cls.__init__) _patchKwargsConstructor(cls.Config) - - -def _detector_create(cls, config_class=None): - def create(*args, mdecorator=None, **kwargs): - if mdecorator is not None: - if not isinstance(mdecorator, inspect.unwrap(acts.IMaterialDecorator)): - raise TypeError("Material decorator is not valid") - if config_class is None: - cfg = cls.Config() - else: - cfg = config_class() - _kwargs = {} - for k, v in kwargs.items(): - try: - setattr(cfg, k, v) - except AttributeError: - _kwargs[k] = v - det = cls() - tg, deco = det.finalize(cfg, mdecorator, *args, **_kwargs) - Detector = namedtuple( - "Detector", ["detector", "trackingGeometry", "decorators"] - ) - - class DetectorContextManager(Detector): - def __new__(cls, detector, trackingGeometry, decorators): - return super(DetectorContextManager, cls).__new__( - cls, detector, trackingGeometry, decorators - ) - - def __enter__(self): - return self - - def __exit__(self, *args): - pass - - return DetectorContextManager(det, tg, deco) - - return create - - -def _patch_detectors(m): - for name, cls in inspect.getmembers(m, inspect.isclass): - if name.endswith("Detector"): - cls.create = _detector_create(cls) diff --git a/Examples/Python/python/acts/examples/__init__.py b/Examples/Python/python/acts/examples/__init__.py index 162e0272d77..5c1a903ad50 100644 --- a/Examples/Python/python/acts/examples/__init__.py +++ b/Examples/Python/python/acts/examples/__init__.py @@ -7,7 +7,7 @@ from acts.ActsPythonBindings._examples import * from acts import ActsPythonBindings import acts -from acts._adapter import _patch_config, _patch_detectors, _patchKwargsConstructor +from acts._adapter import _patch_config, _patchKwargsConstructor _propagators = [] _concrete_propagators = [] @@ -34,8 +34,6 @@ def ConcretePropagator(propagator): _patch_config(ActsPythonBindings._examples) -_patch_detectors(ActsPythonBindings._examples) - # Manually patch ExaTrkX constructors # Need to do it this way, since they are not always present for module in [ diff --git a/Examples/Python/python/acts/examples/dd4hep.py b/Examples/Python/python/acts/examples/dd4hep.py index cd23533dfda..811c97f5657 100644 --- a/Examples/Python/python/acts/examples/dd4hep.py +++ b/Examples/Python/python/acts/examples/dd4hep.py @@ -12,14 +12,9 @@ print("Error encountered importing DD4hep. Likely you need to set LD_LIBRARY_PATH.") sys.exit(1) -from acts._adapter import _patch_config, _detector_create, _patch_detectors +from acts._adapter import _patch_config from acts import ActsPythonBindingsDD4hep _patch_config(ActsPythonBindingsDD4hep) -_patch_detectors(ActsPythonBindingsDD4hep) -ActsPythonBindingsDD4hep.DD4hepDetector.create = _detector_create( - ActsPythonBindingsDD4hep.DD4hepDetector, - ActsPythonBindingsDD4hep.DD4hepGeometryService.Config, -) from acts.ActsPythonBindingsDD4hep import * diff --git a/Examples/Python/python/acts/examples/geant4/dd4hep.py b/Examples/Python/python/acts/examples/geant4/dd4hep.py deleted file mode 100644 index 2d16253e403..00000000000 --- a/Examples/Python/python/acts/examples/geant4/dd4hep.py +++ /dev/null @@ -1,20 +0,0 @@ -import subprocess -import sys - - -# Cannot conveniently catch linker errors, so we launch a suprocess to -# try importing and see if it works in order to provide a useful error message -try: - subprocess.check_call( - [sys.executable, "-c", "from acts import ActsPythonBindingsDDG4"] - ) -except subprocess.CalledProcessError as e: - print("Error encountered importing DD4hep. Likely you need to set LD_LIBRARY_PATH.") - sys.exit(1) - -from acts._adapter import _patch_config -from acts import ActsPythonBindingsDDG4 - -_patch_config(ActsPythonBindingsDDG4) - -from acts.ActsPythonBindingsDDG4 import * diff --git a/Examples/Python/python/acts/examples/geant4/geomodel.py b/Examples/Python/python/acts/examples/geant4/geomodel.py deleted file mode 100644 index bbc5882eaf3..00000000000 --- a/Examples/Python/python/acts/examples/geant4/geomodel.py +++ /dev/null @@ -1,6 +0,0 @@ -from acts._adapter import _patch_config -from acts import ActsPythonBindingsGeoModelG4 - -_patch_config(ActsPythonBindingsGeoModelG4) - -from acts.ActsPythonBindingsGeoModelG4 import * diff --git a/Examples/Python/python/acts/examples/itk.py b/Examples/Python/python/acts/examples/itk.py index b8e2eba8796..0141e367ee9 100644 --- a/Examples/Python/python/acts/examples/itk.py +++ b/Examples/Python/python/acts/examples/itk.py @@ -53,7 +53,7 @@ def buildITkGeometry( if jsonconfig: jsonFile = geo_dir / "itk-hgtd/tgeo-atlas-itk-hgtd.json" logger.info("Create geometry from %s", jsonFile.absolute()) - return TGeoDetector.create( + return TGeoDetector( jsonFile=str(jsonFile), fileName=str(tgeo_fileName), surfaceLogLevel=customLogLevel(), @@ -72,7 +72,7 @@ def buildITkGeometry( # in the root file (it changed ATLAS-P2-23 -> ATLAS-P2-RUN4-01-00-00). # `TGeoParser` searches the tree below `subVolumeName` for all elements that match any of the # list of `sensitiveNames` wildcards and also fall inside the `rRange`/`zRange` selections. - # If no `TGeoDetectorElements`` are found for an ACTS `Volume()`, then `TGeoDetector.create()` + # If no `TGeoDetectorElements`` are found for an ACTS `Volume()`, then `TGeoDetector()` # raises an exception along the lines of: # 1. Missing tracking geometry - or # 2. Incorrect binning configuration found: Number of configurations does not match number of protolayers @@ -83,7 +83,7 @@ def buildITkGeometry( # * browsing `TGeoManager` with ROOT's `TBrowser` (easy to navigate, but have to scan through long lists by eye). # If the detector has moved significantly, it may be necessary to change the `rRange`/`zRange`. # This specification should be kept in sync with `itk-hgtd/tgeo-atlas-itk-hgtd.json`. - return TGeoDetector.create( + return TGeoDetector( fileName=str(tgeo_fileName), mdecorator=matDeco, buildBeamPipe=True, diff --git a/Examples/Python/python/acts/examples/odd.py b/Examples/Python/python/acts/examples/odd.py index 5e187948ee2..e0edef15776 100644 --- a/Examples/Python/python/acts/examples/odd.py +++ b/Examples/Python/python/acts/examples/odd.py @@ -1,7 +1,6 @@ import os import sys import math -from collections import namedtuple from pathlib import Path from typing import Optional import acts @@ -87,36 +86,19 @@ def geoid_hook(geoid, surface): return geoid - dd4hepConfig = acts.examples.dd4hep.DD4hepGeometryService.Config( - xmlFileNames=[str(odd_xml)], - logLevel=customLogLevel(), - dd4hepLogLevel=customLogLevel(minLevel=acts.logging.WARNING), - geometryIdentifierHook=acts.GeometryIdentifierHook(geoid_hook), - ) - detector = acts.examples.dd4hep.DD4hepDetector() - if mdecorator is None: mdecorator = acts.examples.RootMaterialDecorator( fileName=str(odd_dir / "data/odd-material-maps.root"), level=customLogLevel(minLevel=acts.logging.WARNING), ) - trackingGeometry, decorators = detector.finalize(dd4hepConfig, mdecorator) - - OpenDataDetector = namedtuple( - "OpenDataDetector", ["detector", "trackingGeometry", "decorators"] + dd4hepConfig = acts.examples.dd4hep.DD4hepDetector.Config( + xmlFileNames=[str(odd_xml)], + name="OpenDataDetector", + logLevel=customLogLevel(), + dd4hepLogLevel=customLogLevel(minLevel=acts.logging.WARNING), + geometryIdentifierHook=acts.GeometryIdentifierHook(geoid_hook), + materialDecorator=mdecorator, ) - - class OpenDataDetectorContextManager(OpenDataDetector): - def __new__(cls, detector, trackingGeometry, decorators): - return super(OpenDataDetectorContextManager, cls).__new__( - cls, detector, trackingGeometry, decorators - ) - - def __enter__(self): - return self - - def __exit__(self, *args): - self.detector.drop() - - return OpenDataDetectorContextManager(detector, trackingGeometry, decorators) + detector = acts.examples.dd4hep.DD4hepDetector(dd4hepConfig) + return detector diff --git a/Examples/Python/python/acts/examples/simulation.py b/Examples/Python/python/acts/examples/simulation.py index ff690e2a777..a82c16e4198 100644 --- a/Examples/Python/python/acts/examples/simulation.py +++ b/Examples/Python/python/acts/examples/simulation.py @@ -585,40 +585,6 @@ def addSimWriters( ) -def getG4DetectorConstructionFactory( - detector: Any, - regionList: List[Any] = [], -) -> Any: - try: - from acts.examples import TelescopeDetector - from acts.examples.geant4 import TelescopeG4DetectorConstructionFactory - - if type(detector) is TelescopeDetector: - return TelescopeG4DetectorConstructionFactory(detector, regionList) - except Exception as e: - print(e) - - try: - from acts.examples.dd4hep import DD4hepDetector - from acts.examples.geant4.dd4hep import DDG4DetectorConstructionFactory - - if type(detector) is DD4hepDetector: - return DDG4DetectorConstructionFactory(detector, regionList) - except Exception as e: - print(e) - - try: - from acts import geomodel as gm - from acts.examples.geant4.geomodel import GeoModelDetectorConstructionFactory - - if type(detector) is gm.GeoModelTree: - return GeoModelDetectorConstructionFactory(detector, regionList) - except Exception as e: - print(e) - - raise AttributeError(f"cannot find a suitable detector construction for {detector}") - - # holds the Geant4Handle for potential reuse __geant4Handle = None @@ -629,7 +595,6 @@ def addGeant4( trackingGeometry: Union[acts.TrackingGeometry, acts.Detector], field: acts.MagneticFieldProvider, rnd: acts.examples.RandomNumbers, - g4DetectorConstructionFactory: Optional[Any] = None, volumeMappings: List[str] = [], materialMappings: List[str] = ["Silicon"], inputParticles: str = "particles_input", @@ -697,13 +662,6 @@ def addGeant4( s.addWhiteboardAlias("particles_selected", particlesPreSelected) - if g4DetectorConstructionFactory is None: - if detector is None: - raise AttributeError("detector not given") - g4DetectorConstructionFactory = getG4DetectorConstructionFactory( - detector, regionList - ) - global __geant4Handle smmConfig = SensitiveSurfaceMapper.Config() @@ -717,7 +675,7 @@ def addGeant4( alg = Geant4Simulation( level=customLogLevel(), geant4Handle=__geant4Handle, - detectorConstructionFactory=g4DetectorConstructionFactory, + detector=detector, randomNumbers=rnd, inputParticles=particlesPreSelected, outputParticles=outputParticles, diff --git a/Examples/Python/src/Covfie.cpp b/Examples/Python/src/Covfie.cpp index 5a630400d88..27ceb17373f 100644 --- a/Examples/Python/src/Covfie.cpp +++ b/Examples/Python/src/Covfie.cpp @@ -9,6 +9,8 @@ #include "Acts/Plugins/Covfie/FieldConversion.hpp" #include "Acts/Plugins/Python/Utilities.hpp" +#include + #include #include diff --git a/Examples/Python/src/DD4hepComponent.cpp b/Examples/Python/src/DD4hepComponent.cpp index e38a33cf82c..1efd050fef5 100644 --- a/Examples/Python/src/DD4hepComponent.cpp +++ b/Examples/Python/src/DD4hepComponent.cpp @@ -14,12 +14,11 @@ #include "Acts/Plugins/Python/Utilities.hpp" #include "Acts/Utilities/Logger.hpp" #include "ActsExamples/DD4hepDetector/DD4hepDetector.hpp" -#include "ActsExamples/DD4hepDetector/DD4hepGeometryService.hpp" -#include "ActsExamples/Framework/IContextDecorator.hpp" #include #include +#include #include #include @@ -29,15 +28,19 @@ using namespace Acts::Python; PYBIND11_MODULE(ActsPythonBindingsDD4hep, m) { { - using Config = DD4hepGeometryService::Config; - auto s = py::class_>( - m, "DD4hepGeometryService") - .def(py::init()) - .def("drop", &DD4hepGeometryService::drop); - - auto c = py::class_(s, "Config").def(py::init<>()); - ACTS_PYTHON_STRUCT_BEGIN(c, Config); + py::class_>( + m, "DD4hepDetectorElement"); + } + + { + auto f = + py::class_>( + m, "DD4hepDetector") + .def(py::init()); + + auto c = py::class_(f, "Config").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, DD4hepDetector::Config); ACTS_PYTHON_MEMBER(logLevel); ACTS_PYTHON_MEMBER(dd4hepLogLevel); ACTS_PYTHON_MEMBER(xmlFileNames); @@ -48,6 +51,7 @@ PYBIND11_MODULE(ActsPythonBindingsDD4hep, m) { ACTS_PYTHON_MEMBER(envelopeR); ACTS_PYTHON_MEMBER(envelopeZ); ACTS_PYTHON_MEMBER(defaultLayerThickness); + ACTS_PYTHON_MEMBER(materialDecorator); ACTS_PYTHON_MEMBER(geometryIdentifierHook); ACTS_PYTHON_STRUCT_END(); @@ -60,12 +64,6 @@ PYBIND11_MODULE(ActsPythonBindingsDD4hep, m) { "DD4hepFieldAdapter"); } - { - py::class_>( - m, "DD4hepDetectorElement"); - } - { m.def("createDD4hepIdGeoIdMap", [](const Acts::TrackingGeometry& tGeometry) @@ -146,22 +144,4 @@ PYBIND11_MODULE(ActsPythonBindingsDD4hep, m) { options.geoIdGenerator = chainedGeoIdGenerator; }); } - - { - py::class_>( - m, "DD4hepDetector") - .def(py::init<>()) - .def(py::init>()) - .def("finalize", - py::overload_cast>( - &DD4hepDetector::finalize)) - .def("finalize", - py::overload_cast< - const Acts::GeometryContext&, - const Acts::Experimental::DD4hepDetectorStructure::Options&>( - &DD4hepDetector::finalize)) - .def("drop", &DD4hepDetector::drop) - .def_property_readonly("field", &DD4hepDetector::field); - } } diff --git a/Examples/Python/src/Detector.cpp b/Examples/Python/src/Detector.cpp index 94770ce7283..59a7cd13d06 100644 --- a/Examples/Python/src/Detector.cpp +++ b/Examples/Python/src/Detector.cpp @@ -6,11 +6,15 @@ // 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/Detector/Detector.hpp" + +#include "Acts/Geometry/DetectorElementBase.hpp" #include "Acts/Geometry/TrackingGeometry.hpp" #include "Acts/Material/IMaterialDecorator.hpp" #include "Acts/Plugins/Python/Utilities.hpp" #include "Acts/Utilities/BinningType.hpp" #include "ActsExamples/ContextualDetector/AlignedDetector.hpp" +#include "ActsExamples/DetectorCommons/Detector.hpp" #include "ActsExamples/Framework/IContextDecorator.hpp" #include "ActsExamples/GenericDetector/GenericDetector.hpp" #include "ActsExamples/TGeoDetector/TGeoDetector.hpp" @@ -33,6 +37,7 @@ namespace Acts::Python { void addDetector(Context& ctx) { auto [m, mex] = ctx.get("main", "examples"); + { py::class_>( mex, "IContextDecorator") @@ -41,68 +46,69 @@ void addDetector(Context& ctx) { } { - using Detector = GenericDetector; - using Config = Detector::Config; - - auto gd = - py::class_>(mex, "GenericDetector") - .def(py::init<>()) - .def("finalize", - py::overload_cast< - const Config&, - std::shared_ptr>( - &Detector::finalize)); - - py::class_(gd, "Config") - .def(py::init<>()) - .def_readwrite("buildLevel", &Config::buildLevel) - .def_readwrite("surfaceLogLevel", &Config::surfaceLogLevel) - .def_readwrite("layerLogLevel", &Config::layerLogLevel) - .def_readwrite("volumeLogLevel", &Config::volumeLogLevel) - .def_readwrite("buildProto", &Config::buildProto); + py::class_>(mex, "DetectorBase") + .def("nominalGeometryContext", &Detector::nominalGeometryContext) + .def("trackingGeometry", &Detector::trackingGeometry) + .def("gen2Geometry", &Detector::gen2Geometry) + .def("contextDecorators", &Detector::contextDecorators) + .def("__enter__", + [](const std::shared_ptr& self) { return self; }) + .def("__exit__", + [](std::shared_ptr& self, + const std::optional&, + const std::optional&, + const std::optional&) { self.reset(); }); } { - using Detector = TelescopeDetector; - using Config = Detector::Config; - - auto td = - py::class_>(mex, - "TelescopeDetector") - .def(py::init<>()) - .def("finalize", - py::overload_cast< - const Config&, - const std::shared_ptr&>( - &Detector::finalize)); - - py::class_(td, "Config") - .def(py::init<>()) - .def_readwrite("positions", &Config::positions) - .def_readwrite("stereos", &Config::stereos) - .def_readwrite("offsets", &Config::offsets) - .def_readwrite("bounds", &Config::bounds) - .def_readwrite("thickness", &Config::thickness) - .def_readwrite("surfaceType", &Config::surfaceType) - .def_readwrite("binValue", &Config::binValue); + auto d = + py::class_>( + mex, "GenericDetector") + .def(py::init()); + + auto c = py::class_(d, "Config").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, GenericDetector::Config); + ACTS_PYTHON_MEMBER(buildLevel); + ACTS_PYTHON_MEMBER(logLevel); + ACTS_PYTHON_MEMBER(surfaceLogLevel); + ACTS_PYTHON_MEMBER(layerLogLevel); + ACTS_PYTHON_MEMBER(volumeLogLevel); + ACTS_PYTHON_MEMBER(buildProto); + ACTS_PYTHON_MEMBER(materialDecorator); + ACTS_PYTHON_STRUCT_END(); } { - using Detector = AlignedDetector; - using Config = Detector::Config; + auto d = + py::class_>(mex, "TelescopeDetector") + .def(py::init()); + + auto c = + py::class_(d, "Config").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, TelescopeDetector::Config); + ACTS_PYTHON_MEMBER(positions); + ACTS_PYTHON_MEMBER(stereos); + ACTS_PYTHON_MEMBER(offsets); + ACTS_PYTHON_MEMBER(bounds); + ACTS_PYTHON_MEMBER(thickness); + ACTS_PYTHON_MEMBER(surfaceType); + ACTS_PYTHON_MEMBER(binValue); + ACTS_PYTHON_MEMBER(materialDecorator); + ACTS_PYTHON_MEMBER(logLevel); + ACTS_PYTHON_STRUCT_END(); + } + { auto d = - py::class_>(mex, "AlignedDetector") - .def(py::init<>()) - .def("finalize", - py::overload_cast< - const Config&, - std::shared_ptr>( - &Detector::finalize)); - - auto c = py::class_(d, "Config") + py::class_>( + mex, "AlignedDetector") + .def(py::init()); + + auto c = py::class_( + d, "Config") .def(py::init<>()); - ACTS_PYTHON_STRUCT_BEGIN(c, Config); + ACTS_PYTHON_STRUCT_BEGIN(c, AlignedDetector::Config); ACTS_PYTHON_MEMBER(seed); ACTS_PYTHON_MEMBER(iovSize); ACTS_PYTHON_MEMBER(flushSize); @@ -116,23 +122,15 @@ void addDetector(Context& ctx) { ACTS_PYTHON_MEMBER(mode); ACTS_PYTHON_STRUCT_END(); - py::enum_(c, "Mode") - .value("Internal", Config::Mode::Internal) - .value("External", Config::Mode::External); + py::enum_(c, "Mode") + .value("Internal", AlignedDetector::Config::Mode::Internal) + .value("External", AlignedDetector::Config::Mode::External); } { - using Detector = TGeoDetector; - using Config = Detector::Config; - - auto d = - py::class_>(mex, "TGeoDetector") - .def(py::init<>()) - .def("finalize", - py::overload_cast< - const Config&, - std::shared_ptr>( - &Detector::finalize)); + auto d = py::class_>( + mex, "TGeoDetector") + .def(py::init()); py::class_(mex, "Interval") .def(py::init<>()) @@ -140,23 +138,25 @@ void addDetector(Context& ctx) { .def_readwrite("lower", &Options::Interval::lower) .def_readwrite("upper", &Options::Interval::upper); - auto c = py::class_(d, "Config").def(py::init<>()); + auto c = py::class_(d, "Config").def(py::init<>()); - c.def_property( - "jsonFile", nullptr, - [](Config& cfg, const std::string& file) { cfg.readJson(file); }); + c.def_property("jsonFile", nullptr, + [](TGeoDetector::Config& cfg, const std::string& file) { + cfg.readJson(file); + }); - py::enum_(c, "SubVolume") - .value("Negative", Config::SubVolume::Negative) - .value("Central", Config::SubVolume::Central) - .value("Positive", Config::SubVolume::Positive); + py::enum_(c, "SubVolume") + .value("Negative", TGeoDetector::Config::SubVolume::Negative) + .value("Central", TGeoDetector::Config::SubVolume::Central) + .value("Positive", TGeoDetector::Config::SubVolume::Positive); py::enum_(c, "BinningType") .value("equidistant", Acts::BinningType::equidistant) .value("arbitrary", Acts::BinningType::arbitrary); - auto volume = py::class_(c, "Volume").def(py::init<>()); - ACTS_PYTHON_STRUCT_BEGIN(volume, Config::Volume); + auto volume = + py::class_(c, "Volume").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(volume, TGeoDetector::Config::Volume); ACTS_PYTHON_MEMBER(name); ACTS_PYTHON_MEMBER(binToleranceR); ACTS_PYTHON_MEMBER(binTolerancePhi); @@ -185,15 +185,18 @@ void addDetector(Context& ctx) { auto regTriplet = [&c](const std::string& name, auto v) { using type = decltype(v); - py::class_>(c, name.c_str()) + py::class_>(c, name.c_str()) .def(py::init<>()) .def(py::init()) .def(py::init()) - .def_readwrite("negative", &Config::LayerTriplet::negative) - .def_readwrite("central", &Config::LayerTriplet::central) - .def_readwrite("positive", &Config::LayerTriplet::positive) - .def("at", py::overload_cast( - &Config::LayerTriplet::at)); + .def_readwrite("negative", + &TGeoDetector::Config::LayerTriplet::negative) + .def_readwrite("central", + &TGeoDetector::Config::LayerTriplet::central) + .def_readwrite("positive", + &TGeoDetector::Config::LayerTriplet::positive) + .def("at", py::overload_cast( + &TGeoDetector::Config::LayerTriplet::at)); }; regTriplet("LayerTripletBool", true); @@ -204,7 +207,7 @@ void addDetector(Context& ctx) { regTriplet("LayerTripletVectorBinning", std::vector>{}); - ACTS_PYTHON_STRUCT_BEGIN(c, Config); + ACTS_PYTHON_STRUCT_BEGIN(c, TGeoDetector::Config); ACTS_PYTHON_MEMBER(surfaceLogLevel); ACTS_PYTHON_MEMBER(layerLogLevel); ACTS_PYTHON_MEMBER(volumeLogLevel); diff --git a/Examples/Python/src/Geant4Component.cpp b/Examples/Python/src/Geant4Component.cpp index d1f59e3973f..96129e6cc2f 100644 --- a/Examples/Python/src/Geant4Component.cpp +++ b/Examples/Python/src/Geant4Component.cpp @@ -16,17 +16,15 @@ #include "Acts/Plugins/Python/Utilities.hpp" #include "Acts/Surfaces/SurfaceVisitorConcept.hpp" #include "Acts/Utilities/Logger.hpp" -#include "ActsExamples/Framework/IContextDecorator.hpp" -#include "ActsExamples/Geant4/DetectorConstructionFactory.hpp" -#include "ActsExamples/Geant4/GdmlDetectorConstruction.hpp" +#include "ActsExamples/Geant4/Geant4ConstructionOptions.hpp" #include "ActsExamples/Geant4/Geant4Manager.hpp" #include "ActsExamples/Geant4/Geant4Simulation.hpp" #include "ActsExamples/Geant4/RegionCreator.hpp" #include "ActsExamples/Geant4/SensitiveSurfaceMapper.hpp" +#include "ActsExamples/Geant4Detector/GdmlDetector.hpp" +#include "ActsExamples/Geant4Detector/GdmlDetectorConstruction.hpp" #include "ActsExamples/Geant4Detector/Geant4Detector.hpp" #include "ActsExamples/MuonSpectrometerMockupDetector/MockupSectorBuilder.hpp" -#include "ActsExamples/TelescopeDetector/TelescopeDetector.hpp" -#include "ActsExamples/TelescopeDetector/TelescopeG4DetectorConstruction.hpp" #include #include @@ -86,10 +84,6 @@ struct ExperimentalSensitiveCandidates }; PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { - py::class_>( - mod, "DetectorConstructionFactory"); - py::class_>( mod, "Geant4Manager") .def_static("instance", &Geant4Manager::instance, @@ -99,6 +93,15 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { py::class_>(mod, "Geant4Handle") .def("tweakLogging", &Geant4Handle::tweakLogging); + { + py::class_>( + mod, "Geant4ConstructionOptions") + .def(py::init<>()) + .def_readwrite("regionCreators", + &Geant4ConstructionOptions::regionCreators); + } + { using Algorithm = Geant4SimulationBase; using Config = Algorithm::Config; @@ -112,7 +115,7 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { ACTS_PYTHON_STRUCT_BEGIN(c1, Config); ACTS_PYTHON_MEMBER(inputParticles); ACTS_PYTHON_MEMBER(randomNumbers); - ACTS_PYTHON_MEMBER(detectorConstructionFactory); + ACTS_PYTHON_MEMBER(detector); ACTS_PYTHON_MEMBER(geant4Handle); ACTS_PYTHON_STRUCT_END(); } @@ -140,7 +143,7 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { sm.def("create", [](const Config& cfg, Acts::Logging::Level level, - const std::shared_ptr tGeometry) { + const std::shared_ptr& tGeometry) { // Set a new surface finder Config ccfg = cfg; auto candidateSurfaces = @@ -169,10 +172,11 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { sm.def( "remapSensitiveNames", [](Geant4::SensitiveSurfaceMapper& self, State& state, - GeometryContext& gctx, Geant4::DetectorConstructionFactory& factory, - Transform3& transform) { + GeometryContext& gctx, Detector& detector, Transform3& transform) { return self.remapSensitiveNames( - state, gctx, factory.factorize()->Construct(), transform); + state, gctx, + detector.buildGeant4DetectorConstruction({})->Construct(), + transform); }, "state"_a, "gctx"_a, "g4physicalVolume"_a, "motherTransform"_a); sm.def("checkMapping", &Geant4::SensitiveSurfaceMapper::checkMapping, @@ -230,30 +234,6 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { ACTS_PYTHON_STRUCT_END(); } - { - py::class_>( - mod, "GdmlDetectorConstructionFactory") - .def(py::init>>(), - py::arg("path"), - py::arg("regionCreators") = - std::vector>()); - } - - { - py::class_>( - mod, "TelescopeG4DetectorConstructionFactory") - .def(py::init>>(), - py::arg("cfg"), - py::arg("regionCreators") = - std::vector>()); - } - { using ISelector = Acts::IGeant4PhysicalVolumeSelector; auto is = py::class_>( @@ -277,33 +257,13 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { } { - py::class_>( - mod, "Geant4DetectorElement"); - - using Detector = Geant4Detector; - using Config = Detector::Config; - - auto g = - py::class_>(mod, "Geant4Detector") - .def(py::init<>()) - .def( - "constructDetector", - [](Detector& self, const Config& cfg, Logging::Level logLevel) { - auto logger = getDefaultLogger("Geant4Detector", logLevel); - return self.constructDetector(cfg, *logger); - }, - py::arg("cfg"), py::arg("logLevel") = Logging::INFO) - .def( - "constructTrackingGeometry", - [](Detector& self, const Config& cfg, Logging::Level logLevel) { - auto logger = getDefaultLogger("Geant4Detector", logLevel); - return self.constructTrackingGeometry(cfg, *logger); - }, - py::arg("cfg"), py::arg("logLevel") = Logging::INFO); - - auto c = py::class_(g, "Config").def(py::init<>()); - ACTS_PYTHON_STRUCT_BEGIN(c, Config); + auto f = + py::class_>( + mod, "Geant4Detector") + .def(py::init()); + + auto c = py::class_(f, "Config").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, Geant4Detector::Config); ACTS_PYTHON_MEMBER(name); ACTS_PYTHON_MEMBER(g4World); ACTS_PYTHON_MEMBER(g4SurfaceOptions); @@ -313,6 +273,18 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { ACTS_PYTHON_STRUCT_END(); } + { + auto f = py::class_>( + mod, "GdmlDetector") + .def(py::init()); + + auto c = py::class_(f, "Config").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, GdmlDetector::Config); + ACTS_PYTHON_MEMBER(path); + ACTS_PYTHON_MEMBER(logLevel); + ACTS_PYTHON_STRUCT_END(); + } + { /// Helper function to test if the automatic geometry conversion works /// @@ -326,7 +298,7 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { passiveMatches, bool convertMaterial) { // Initiate the detector construction & retrieve world - ActsExamples::GdmlDetectorConstruction gdmlContruction(gdmlFileName); + ActsExamples::GdmlDetectorConstruction gdmlContruction(gdmlFileName, {}); const auto* world = gdmlContruction.Construct(); // Create the selectors @@ -406,10 +378,8 @@ PYBIND11_MODULE(ActsPythonBindingsGeant4, mod) { { using Tool = Geant4::RegionCreator; using Config = Tool::Config; - auto tool = py::class_>(mod, "RegionCreator") - .def(py::init(), - py::arg("config"), py::arg("name"), - py::arg("logLevel") = Logging::INFO) + auto tool = py::class_(mod, "RegionCreator") + .def(py::init(), py::arg("config")) .def_property_readonly("config", &Tool::config); auto c = py::class_(tool, "Config").def(py::init<>()); diff --git a/Examples/Python/src/Geant4DD4hepComponent.cpp b/Examples/Python/src/Geant4DD4hepComponent.cpp deleted file mode 100644 index 409bca273d1..00000000000 --- a/Examples/Python/src/Geant4DD4hepComponent.cpp +++ /dev/null @@ -1,34 +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/DD4hepDetector/DD4hepDetector.hpp" -#include "ActsExamples/DDG4/DDG4DetectorConstruction.hpp" -#include "ActsExamples/Geant4/RegionCreator.hpp" - -#include -#include -#include - -namespace py = pybind11; - -using namespace ActsExamples; -using namespace Acts; - -PYBIND11_MODULE(ActsPythonBindingsDDG4, m) { - py::module_::import("acts.ActsPythonBindingsGeant4"); - - py::class_>( - m, "DDG4DetectorConstructionFactory") - .def(py::init, - std::vector>>(), - py::arg("detector"), - py::arg("regionCreators") = - std::vector>()); -} diff --git a/Examples/Python/src/Geant4GeoModelComponent.cpp b/Examples/Python/src/Geant4GeoModelComponent.cpp deleted file mode 100644 index d9c372984db..00000000000 --- a/Examples/Python/src/Geant4GeoModelComponent.cpp +++ /dev/null @@ -1,36 +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/Geant4/DetectorConstructionFactory.hpp" -#include "ActsExamples/Geant4/RegionCreator.hpp" -#include "ActsExamples/GeoModelG4/GeoModelDetectorConstruction.hpp" - -#include -#include -#include - -class GeoVPhysVol; - -namespace py = pybind11; - -using namespace ActsExamples; -using namespace Acts; - -PYBIND11_MODULE(ActsPythonBindingsGeoModelG4, m) { - py::module_::import("acts.ActsPythonBindingsGeant4"); - - py::class_>( - m, "GeoModelDetectorConstructionFactory") - .def(py::init>>(), - py::arg("geoModelTree"), - py::arg("regionCreators") = - std::vector>()); -} diff --git a/Examples/Python/src/Geant4HepMC3.cpp b/Examples/Python/src/Geant4HepMC3.cpp index 913a23da60f..596cf744660 100644 --- a/Examples/Python/src/Geant4HepMC3.cpp +++ b/Examples/Python/src/Geant4HepMC3.cpp @@ -7,11 +7,8 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. #include "Acts/Plugins/Python/Utilities.hpp" -#include "ActsExamples/Geant4/DetectorConstructionFactory.hpp" #include "ActsExamples/Geant4HepMC/EventRecording.hpp" -#include - #include #include @@ -29,8 +26,7 @@ void addGeant4HepMC3(Context& ctx) { ACTS_PYTHON_DECLARE_ALGORITHM( EventRecording, h3, "EventRecording", inputParticles, outputHepMcTracks, - detectorConstructionFactory, seed1, seed2, processesCombine, - processSelect, processesReject); + detector, seed1, seed2, processesCombine, processSelect, processesReject); } } // namespace Acts::Python diff --git a/Examples/Python/src/GeoModel.cpp b/Examples/Python/src/GeoModel.cpp index 58609a573d5..899781a1f2c 100644 --- a/Examples/Python/src/GeoModel.cpp +++ b/Examples/Python/src/GeoModel.cpp @@ -27,6 +27,7 @@ #include "Acts/Surfaces/DiscSurface.hpp" #include "Acts/Surfaces/PlaneSurface.hpp" #include "Acts/Surfaces/RectangleBounds.hpp" +#include "ActsExamples/GeoModelDetector/GeoModelDetector.hpp" #include "ActsExamples/ITkModuleSplitting/ITkModuleSplitting.hpp" #include @@ -40,6 +41,7 @@ namespace py = pybind11; using namespace pybind11::literals; namespace Acts::Python { + void addGeoModel(Context& ctx) { auto m = ctx.get("main"); @@ -59,6 +61,21 @@ void addGeoModel(Context& ctx) { return self.surface().getSharedPtr(); }); + { + auto f = + py::class_>( + gm, "GeoModelDetector") + .def(py::init()); + + auto c = py::class_(f, "Config") + .def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, ActsExamples::GeoModelDetector::Config); + ACTS_PYTHON_MEMBER(path); + ACTS_PYTHON_MEMBER(logLevel); + ACTS_PYTHON_STRUCT_END(); + } + // Shape converters { py::class_ #include -#include #include #include @@ -44,14 +41,15 @@ void addGeometryBuildingGen1(Context &ctx) { [](const Acts::LayerCreator &self, const GeometryContext &gctx, SurfacePtrVector surfaces, std::size_t binsPhi, std::size_t binsZ) { - return self.cylinderLayer(gctx, surfaces, binsPhi, binsZ); + return self.cylinderLayer(gctx, std::move(surfaces), binsPhi, + binsZ); }) - .def("discLayer", - [](const Acts::LayerCreator &self, const GeometryContext &gctx, - SurfacePtrVector surfaces, std::size_t binsR, - std::size_t binsPhi) { - return self.discLayer(gctx, surfaces, binsR, binsPhi); - }); + .def("discLayer", [](const Acts::LayerCreator &self, + const GeometryContext &gctx, + SurfacePtrVector surfaces, std::size_t binsR, + std::size_t binsPhi) { + return self.discLayer(gctx, std::move(surfaces), binsR, binsPhi); + }); auto config = py::class_(creator, "Config").def(py::init<>()); @@ -115,8 +113,9 @@ void addGeometryBuildingGen1(Context &ctx) { GeometryContext gctx, const LayerVector &layers, std::shared_ptr volumeBounds, const Transform3 &trafo, const std::string &name) { - return self.createTrackingVolume( - gctx, layers, {}, volumeBounds, {}, trafo, name); + return self.createTrackingVolume(gctx, layers, {}, + std::move(volumeBounds), {}, + trafo, name); }) .def("createContainerTrackingVolume", &Acts::CylinderVolumeHelper::createContainerTrackingVolume); diff --git a/Examples/Python/src/ModuleEntry.cpp b/Examples/Python/src/ModuleEntry.cpp index a78f04f9565..9f494dddee6 100644 --- a/Examples/Python/src/ModuleEntry.cpp +++ b/Examples/Python/src/ModuleEntry.cpp @@ -7,25 +7,10 @@ // file, You can obtain one at https://mozilla.org/MPL/2.0/. #include "Acts/ActsVersion.hpp" -#include "Acts/Geometry/GeometryContext.hpp" -#include "Acts/Geometry/GeometryIdentifier.hpp" -#include "Acts/MagneticField/MagneticFieldContext.hpp" -#include "Acts/Plugins/FpeMonitoring/FpeMonitor.hpp" #include "Acts/Plugins/Python/Utilities.hpp" -#include "Acts/Utilities/CalibrationContext.hpp" -#include "Acts/Utilities/Logger.hpp" - -#include -#include -#include -#include -#include -#include -#include -#include + #include #include -#include #include #include diff --git a/Examples/Python/tests/conftest.py b/Examples/Python/tests/conftest.py index 4bd8a430730..c1eb489bb62 100644 --- a/Examples/Python/tests/conftest.py +++ b/Examples/Python/tests/conftest.py @@ -237,14 +237,17 @@ def _basic_prop_seq_factory(geo, s=None): @pytest.fixture def trk_geo(): - detector, geo, contextDecorators = acts.examples.GenericDetector.create() - yield geo + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() + yield trackingGeometry DetectorConfig = namedtuple( "DetectorConfig", [ - "detectorTuple", + "detector", + "trackingGeometry", + "decorators", "geometrySelection", "digiConfigFile", "name", @@ -257,9 +260,13 @@ def detector_config(request): srcdir = Path(__file__).resolve().parent.parent.parent.parent if request.param == "generic": - detectorTuple = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() return DetectorConfig( - detectorTuple, + detector, + trackingGeometry, + decorators, geometrySelection=( srcdir / "Examples/Algorithms/TrackFinding/share/geoSelection-genericDetector.json" @@ -278,9 +285,13 @@ def detector_config(request): srcdir / "thirdparty/OpenDataDetector/data/odd-material-maps.root", level=acts.logging.INFO, ) - detectorTuple = getOpenDataDetector(matDeco) + detector = getOpenDataDetector(matDeco) + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() return DetectorConfig( - detectorTuple, + detector, + trackingGeometry, + decorators, digiConfigFile=( srcdir / "thirdparty/OpenDataDetector/config/odd-digi-smearing-config.json" @@ -375,14 +386,8 @@ def _do_material_recording(d: Path): s = acts.examples.Sequencer(events=2, numThreads=1) - with getOpenDataDetector() as (detector, trackingGeometry, decorators): - detectorConstructionFactory = ( - acts.examples.geant4.dd4hep.DDG4DetectorConstructionFactory(detector) - ) - - runMaterialRecording( - detectorConstructionFactory, str(d), tracksPerEvent=100, s=s - ) + with getOpenDataDetector() as detector: + runMaterialRecording(detector, str(d), tracksPerEvent=100, s=s) s.run() @@ -396,7 +401,9 @@ def material_recording_session(): pytest.skip("DD4hep recording requested, but DD4hep is not set up") with tempfile.TemporaryDirectory() as d: - p = multiprocessing.Process(target=_do_material_recording, args=(d,)) + # explicitly ask for "spawn" as CI failures were observed with "fork" + spawn_context = multiprocessing.get_context("spawn") + p = spawn_context.Process(target=_do_material_recording, args=(d,)) p.start() p.join() if p.exitcode != 0: diff --git a/Examples/Python/tests/test_detectors.py b/Examples/Python/tests/test_detectors.py index 3de27524db9..bbfb715697f 100644 --- a/Examples/Python/tests/test_detectors.py +++ b/Examples/Python/tests/test_detectors.py @@ -27,41 +27,50 @@ def check_extra_odd(srf): def test_generic_geometry(): - detector, geo, contextDecorators = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() + contextDecorators = detector.contextDecorators() assert detector is not None - assert geo is not None + assert trackingGeometry is not None assert contextDecorators is not None - assert count_surfaces(geo) == 18728 + assert count_surfaces(trackingGeometry) == 18728 def test_telescope_geometry(): n_surfaces = 10 - detector, geo, contextDecorators = acts.examples.TelescopeDetector.create( + config = acts.examples.TelescopeDetector.Config( bounds=[100, 100], positions=[10 * i for i in range(n_surfaces)], stereos=[0] * n_surfaces, binValue=0, ) + detector = acts.examples.TelescopeDetector(config) + trackingGeometry = detector.trackingGeometry() + contextDecorators = detector.contextDecorators() assert detector is not None - assert geo is not None + assert trackingGeometry is not None assert contextDecorators is not None - assert count_surfaces(geo) == n_surfaces + assert count_surfaces(trackingGeometry) == n_surfaces @pytest.mark.skipif(not dd4hepEnabled, reason="DD4hep is not set up") def test_odd(): - with getOpenDataDetector() as (detector, trackingGeometry, decorators): + with getOpenDataDetector() as detector: + trackingGeometry = detector.trackingGeometry() + trackingGeometry.visitSurfaces(check_extra_odd) assert count_surfaces(trackingGeometry) == 18824 def test_aligned_detector(): - detector, trackingGeometry, decorators = acts.examples.AlignedDetector.create() + detector = acts.examples.AlignedDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() assert detector is not None assert trackingGeometry is not None diff --git a/Examples/Python/tests/test_examples.py b/Examples/Python/tests/test_examples.py index daf40b6685a..4e7ea9b14c3 100644 --- a/Examples/Python/tests/test_examples.py +++ b/Examples/Python/tests/test_examples.py @@ -149,7 +149,7 @@ def test_geant4(tmp_path, assert_root_hash): # This test literally only ensures that the geant 4 example can run without erroring out # just to make sure it can build the odd - with getOpenDataDetector() as (detector, trackingGeometry, decorators): + with getOpenDataDetector(): pass csv = tmp_path / "csv" @@ -592,8 +592,7 @@ def test_truth_tracking_kalman( fp = tmp_path / fn assert not fp.exists() - print("with") - with detector_config.detectorTuple as (detector, trackingGeometry, decorators): + with detector_config.detector: from truth_tracking_kalman import runTruthTrackingKalman field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) @@ -601,7 +600,7 @@ def test_truth_tracking_kalman( seq = Sequencer(events=10, numThreads=1) runTruthTrackingKalman( - trackingGeometry=trackingGeometry, + trackingGeometry=detector_config.trackingGeometry, field=field, digiConfigFile=detector_config.digiConfigFile, outputDir=tmp_path, @@ -610,7 +609,6 @@ def test_truth_tracking_kalman( ) seq.run() - print("done") for fn, tn, ee in root_files: fp = tmp_path / fn @@ -657,10 +655,10 @@ def test_truth_tracking_gsf(tmp_path, assert_root_hash, detector_config): fp = tmp_path / fn assert not fp.exists() - with detector_config.detectorTuple as (detector, trackingGeometry, decorators): + with detector_config.detector: runTruthTrackingGsf( - trackingGeometry=trackingGeometry, - decorators=decorators, + trackingGeometry=detector_config.trackingGeometry, + decorators=detector_config.decorators, field=field, digiConfigFile=detector_config.digiConfigFile, outputDir=tmp_path, @@ -689,11 +687,11 @@ def test_refitting(tmp_path, detector_config, assert_root_hash): numThreads=1, ) - with detector_config.detectorTuple as (detector, trackingGeometry, decorators): + with detector_config.detector: # Only check if it runs without errors right known # Changes in fitter behaviour should be caught by other tests runRefittingGsf( - trackingGeometry=trackingGeometry, + trackingGeometry=detector_config.trackingGeometry, field=field, digiConfigFile=detector_config.digiConfigFile, outputDir=tmp_path, @@ -757,7 +755,10 @@ def test_material_mapping(material_recording, tmp_path, assert_root_hash): s = Sequencer(numThreads=1) - with getOpenDataDetector(mdecorator) as (detector, trackingGeometry, decorators): + with getOpenDataDetector(mdecorator) as detector: + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() + runMaterialMapping( trackingGeometry, decorators, @@ -790,11 +791,12 @@ def test_material_mapping(material_recording, tmp_path, assert_root_hash): s = Sequencer(events=10, numThreads=1) - with getOpenDataDetector(mdecorator=acts.IMaterialDecorator.fromFile(mat_file)) as ( - detector, - trackingGeometry, - decorators, - ): + with getOpenDataDetector( + mdecorator=acts.IMaterialDecorator.fromFile(mat_file) + ) as detector: + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() + runMaterialValidation( 10, 1000, trackingGeometry, decorators, field, outputDir=str(tmp_path), s=s ) @@ -824,11 +826,12 @@ def test_volume_material_mapping(material_recording, tmp_path, assert_root_hash) s = Sequencer(numThreads=1) - with getOpenDataDetector(mdecorator=acts.IMaterialDecorator.fromFile(geo_map)) as ( - detector, - trackingGeometry, - decorators, - ): + with getOpenDataDetector( + mdecorator=acts.IMaterialDecorator.fromFile(geo_map) + ) as detector: + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() + runMaterialMapping( trackingGeometry, decorators, @@ -862,11 +865,12 @@ def test_volume_material_mapping(material_recording, tmp_path, assert_root_hash) s = Sequencer(events=10, numThreads=1) - with getOpenDataDetector(mdecorator=acts.IMaterialDecorator.fromFile(mat_file)) as ( - detector, - trackingGeometry, - decorators, - ): + with getOpenDataDetector( + mdecorator=acts.IMaterialDecorator.fromFile(mat_file) + ) as detector: + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() + runMaterialValidation( 10, 1000, @@ -886,11 +890,12 @@ def test_volume_material_mapping(material_recording, tmp_path, assert_root_hash) @pytest.mark.parametrize( - "geoFactory,nobj", + "detectorFactory,aligned,nobj", [ - (GenericDetector.create, 450), + (GenericDetector, True, 450), pytest.param( getOpenDataDetector, + True, 540, marks=[ pytest.mark.skipif(not dd4hepEnabled, reason="DD4hep not set up"), @@ -898,12 +903,14 @@ def test_volume_material_mapping(material_recording, tmp_path, assert_root_hash) pytest.mark.odd, ], ), - (functools.partial(AlignedDetector.create, iovSize=1), 450), + (functools.partial(AlignedDetector, iovSize=1), False, 450), ], ) @pytest.mark.slow -def test_geometry_example(geoFactory, nobj, tmp_path): - detector, trackingGeometry, decorators = geoFactory() +def test_geometry_example(detectorFactory, aligned, nobj, tmp_path): + detector = detectorFactory() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() from geometry import runGeometry @@ -940,12 +947,12 @@ def test_geometry_example(geoFactory, nobj, tmp_path): contents = [f.read_text() for f in detector_files] ref = contents[0] for c in contents[1:]: - if isinstance(detector, AlignedDetector): - assert c != ref, "Detector writeout is expected to be different" - else: + if aligned: assert c == ref, "Detector writeout is expected to be identical" + else: + assert c != ref, "Detector writeout is expected to be different" - if not isinstance(detector, AlignedDetector): + if aligned: for f in [json_dir / f"event{i:>09}-detector.json" for i in range(events)]: assert detector_file.exists() with f.open() as fh: @@ -1163,10 +1170,10 @@ def test_ckf_tracks_example( from ckf_tracks import runCKFTracks - with detector_config.detectorTuple as (detector, trackingGeometry, decorators): + with detector_config.detector: runCKFTracks( - trackingGeometry, - decorators, + detector_config.trackingGeometry, + detector_config.decorators, field=field, outputCsv=True, outputDir=tmp_path, @@ -1199,7 +1206,7 @@ def test_full_chain_odd_example(tmp_path): # This test literally only ensures that the full chain example can run without erroring out # just to make sure it can build the odd - with getOpenDataDetector() as (detector, trackingGeometry, decorators): + with getOpenDataDetector(): pass script = ( @@ -1232,7 +1239,7 @@ def test_full_chain_odd_example_pythia_geant4(tmp_path): # This test literally only ensures that the full chain example can run without erroring out # just to make sure it can build the odd - with getOpenDataDetector() as (detector, trackingGeometry, decorators): + with getOpenDataDetector(): pass script = ( @@ -1286,7 +1293,7 @@ def test_ML_Ambiguity_Solver(tmp_path, assert_root_hash): assert not (tmp_path / root_file).exists() # just to make sure it can build the odd - with getOpenDataDetector() as (detector, trackingGeometry, decorators): + with getOpenDataDetector(): pass script = ( diff --git a/Examples/Python/tests/test_reader.py b/Examples/Python/tests/test_reader.py index 104a4e0f98f..46b1c9c229f 100644 --- a/Examples/Python/tests/test_reader.py +++ b/Examples/Python/tests/test_reader.py @@ -290,7 +290,9 @@ def test_edm4hep_simhit_particle_reader(tmp_path): tmp_file = str(tmp_path / "output_edm4hep.root") odd_xml_file = str(getOpenDataDetectorDirectory() / "xml" / "OpenDataDetector.xml") - p = multiprocessing.Process( + # explicitly ask for "spawn" as CI failures were observed with "fork" + spawn_context = multiprocessing.get_context("spawn") + p = spawn_context.Process( target=generate_input_test_edm4hep_simhit_reader, args=(odd_xml_file, tmp_file) ) p.start() @@ -300,7 +302,9 @@ def test_edm4hep_simhit_particle_reader(tmp_path): s = Sequencer(numThreads=1) - with getOpenDataDetector() as (detector, trackingGeometry, decorators): + with getOpenDataDetector() as detector: + trackingGeometry = detector.trackingGeometry() + s.addReader( EDM4hepReader( level=acts.logging.INFO, @@ -386,7 +390,9 @@ def test_edm4hep_measurement_reader(tmp_path, fatras, conf_const): def test_edm4hep_tracks_reader(tmp_path): from acts.examples.edm4hep import EDM4hepTrackWriter, EDM4hepTrackReader - detector, trackingGeometry, decorators = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() + field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) from truth_tracking_kalman import runTruthTrackingKalman diff --git a/Examples/Python/tests/test_writer.py b/Examples/Python/tests/test_writer.py index ac64fa4048f..2de90c931ab 100644 --- a/Examples/Python/tests/test_writer.py +++ b/Examples/Python/tests/test_writer.py @@ -199,7 +199,8 @@ def test_root_simhits_writer(tmp_path, fatras, conf_const, assert_root_hash): @pytest.mark.root def test_root_tracksummary_writer(tmp_path, fatras, conf_const): - detector, trackingGeometry, decorators = GenericDetector.create() + detector = GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) s = Sequencer(numThreads=1, events=10) @@ -361,9 +362,10 @@ def test_csv_writer_interface(writer, conf_const, tmp_path, trk_geo): def test_root_material_writer(tmp_path, assert_root_hash): from acts.examples.dd4hep import DD4hepDetector - detector, trackingGeometry, _ = DD4hepDetector.create( + detector = DD4hepDetector( xmlFileNames=[str(getOpenDataDetectorDirectory() / "xml/OpenDataDetector.xml")] ) + trackingGeometry = detector.trackingGeometry() out = tmp_path / "material.root" @@ -385,9 +387,10 @@ def test_root_material_writer(tmp_path, assert_root_hash): def test_json_material_writer(tmp_path, fmt): from acts.examples.dd4hep import DD4hepDetector - detector, trackingGeometry, _ = DD4hepDetector.create( + detector = DD4hepDetector( xmlFileNames=[str(getOpenDataDetectorDirectory() / "xml/OpenDataDetector.xml")] ) + trackingGeometry = detector.trackingGeometry() out = (tmp_path / "material").with_suffix("." + fmt.name.lower()) @@ -404,7 +407,8 @@ def test_json_material_writer(tmp_path, fmt): @pytest.mark.csv def test_csv_multitrajectory_writer(tmp_path): - detector, trackingGeometry, decorators = GenericDetector.create() + detector = GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) from truth_tracking_kalman import runTruthTrackingKalman @@ -602,7 +606,8 @@ def test_edm4hep_particle_writer(tmp_path, conf_const, ptcl_gun): def test_edm4hep_multitrajectory_writer(tmp_path): from acts.examples.edm4hep import EDM4hepMultiTrajectoryWriter - detector, trackingGeometry, decorators = GenericDetector.create() + detector = GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) from truth_tracking_kalman import runTruthTrackingKalman @@ -651,7 +656,8 @@ def test_edm4hep_multitrajectory_writer(tmp_path): def test_edm4hep_tracks_writer(tmp_path): from acts.examples.edm4hep import EDM4hepTrackWriter - detector, trackingGeometry, decorators = GenericDetector.create() + detector = GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) from truth_tracking_kalman import runTruthTrackingKalman diff --git a/Examples/Scripts/GsfDebugger/make_gsf_verbose_log.py b/Examples/Scripts/GsfDebugger/make_gsf_verbose_log.py index c72e183feae..7f4b466ee84 100755 --- a/Examples/Scripts/GsfDebugger/make_gsf_verbose_log.py +++ b/Examples/Scripts/GsfDebugger/make_gsf_verbose_log.py @@ -1,7 +1,6 @@ #!/bin/python3 -import sys + from pathlib import Path -import os import acts import acts.examples @@ -20,7 +19,8 @@ ) assert digiConfigFile.exists() - detector, trackingGeometry, decorators = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/Examples/Scripts/Optimization/ckf.py b/Examples/Scripts/Optimization/ckf.py index 0077f35317b..7de27b98465 100755 --- a/Examples/Scripts/Optimization/ckf.py +++ b/Examples/Scripts/Optimization/ckf.py @@ -259,7 +259,9 @@ def runCKFTracks( srcdir = Path(__file__).resolve().parent.parent.parent.parent - detector, trackingGeometry, decorators = GenericDetector.create() + detector = GenericDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/Examples/Scripts/Python/Auto-tuning/Orion/material_mapping_optimisation.py b/Examples/Scripts/Python/Auto-tuning/Orion/material_mapping_optimisation.py index 821320a33a2..d64c7056d7c 100755 --- a/Examples/Scripts/Python/Auto-tuning/Orion/material_mapping_optimisation.py +++ b/Examples/Scripts/Python/Auto-tuning/Orion/material_mapping_optimisation.py @@ -167,7 +167,8 @@ def runMaterialMappingVariance( matDeco = acts.IMaterialDecorator.fromFile( str(os.path.join(inputPath, "geometry-map.json")) ) - detectorTemp, trackingGeometryTemp, decoratorsTemp = getOpenDataDetector(matDeco) + detectorTemp = getOpenDataDetector(matDeco) + trackingGeometryTemp = detectorTemp.trackingGeometry() matMapDeco = acts.MappingMaterialDecorator( tGeometry=trackingGeometryTemp, level=acts.logging.ERROR ) @@ -175,7 +176,9 @@ def runMaterialMappingVariance( matMapDeco.setBinningMap(binMap) # Decorate the detector with the MappingMaterialDecorator - detector, trackingGeometry, decorators = getOpenDataDetector(matMapDeco) + detector = getOpenDataDetector(matMapDeco) + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() # Sequence for the mapping, only use one thread when mapping material sMap = acts.examples.Sequencer( @@ -207,7 +210,9 @@ def runMaterialMappingVariance( # Use the material map from the previous mapping as an input cborMap = os.path.join(pathExp, (mapName + ".cbor")) matDecoVar = acts.IMaterialDecorator.fromFile(cborMap) - detectorVar, trackingGeometryVar, decoratorsVar = getOpenDataDetector(matDecoVar) + detectorVar = getOpenDataDetector(matDecoVar) + trackingGeometryVar = detectorVar.trackingGeometry() + decoratorsVar = detectorVar.contextDecorators() s = acts.examples.Sequencer(events=events, numThreads=1, logLevel=acts.logging.INFO) for decorator in decoratorsVar: s.addContextDecorator(decorator) @@ -463,7 +468,9 @@ def surfaceExperiment(key, nbJobs, pathDB, pathResult, pipeBin, pipeResult, doPl matDeco = acts.IMaterialDecorator.fromFile( str(os.path.join(args.inputPath, "geometry-map.json")) ) - detector, trackingGeometry, decorators = getOpenDataDetector(matDeco) + detector = getOpenDataDetector(matDeco) + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() # Use the MappingMaterialDecorator to create a binning map that can be optimised matMapDeco = acts.MappingMaterialDecorator( diff --git a/Examples/Scripts/Python/ckf_tracks.py b/Examples/Scripts/Python/ckf_tracks.py index 15a50466799..1ef32c844ee 100755 --- a/Examples/Scripts/Python/ckf_tracks.py +++ b/Examples/Scripts/Python/ckf_tracks.py @@ -179,7 +179,9 @@ def runCKFTracks( if "__main__" == __name__: srcdir = Path(__file__).resolve().parent.parent.parent.parent - detector, trackingGeometry, decorators = GenericDetector.create() + detector = GenericDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/Examples/Scripts/Python/digitization.py b/Examples/Scripts/Python/digitization.py index a69a8f01c45..3e2479983af 100755 --- a/Examples/Scripts/Python/digitization.py +++ b/Examples/Scripts/Python/digitization.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 + from pathlib import Path from typing import Optional @@ -75,7 +76,8 @@ def runDigitization( if "__main__" == __name__: - detector, trackingGeometry, _ = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() digiConfigFile = ( Path(__file__).resolve().parent.parent.parent.parent diff --git a/Examples/Scripts/Python/digitization_config.py b/Examples/Scripts/Python/digitization_config.py index 1b6de07f477..13e41dc1898 100755 --- a/Examples/Scripts/Python/digitization_config.py +++ b/Examples/Scripts/Python/digitization_config.py @@ -33,7 +33,8 @@ def runDigitizationConfig( if "__main__" == __name__: - detector, trackingGeometry, _ = GenericDetector.create() + detector = GenericDetector() + trackingGeometry = detector.trackingGeometry() runDigitizationConfig( trackingGeometry=trackingGeometry, diff --git a/Examples/Scripts/Python/event_recording.py b/Examples/Scripts/Python/event_recording.py index 04cbb135dbe..8c31799c768 100755 --- a/Examples/Scripts/Python/event_recording.py +++ b/Examples/Scripts/Python/event_recording.py @@ -7,7 +7,6 @@ import acts.examples.hepmc3 import acts.examples.dd4hep import acts.examples.geant4 -import acts.examples.geant4.dd4hep import acts.examples.geant4.hepmc3 from acts.examples.odd import getOpenDataDetector @@ -15,7 +14,7 @@ u = acts.UnitConstants -def runEventRecording(detectorConstructionFactory, outputDir, s=None): +def runEventRecording(detector, outputDir, s=None): hepmc_dir = os.path.join(outputDir, "hepmc3") if not os.path.exists(hepmc_dir): os.mkdir(hepmc_dir) @@ -54,7 +53,7 @@ def runEventRecording(detectorConstructionFactory, outputDir, s=None): outputHepMcTracks="geant-event", seed1=43, seed2=44, - detectorConstructionFactory=detectorConstructionFactory, + detector=detector, ) erAlg = acts.examples.geant4.hepmc3.EventRecording( @@ -76,13 +75,9 @@ def runEventRecording(detectorConstructionFactory, outputDir, s=None): if "__main__" == __name__: - detector, trackingGeometry, decorators = getOpenDataDetector() - - detectorConstructionFactory = ( - acts.examples.geant4.dd4hep.DDG4DetectorConstructionFactory(detector) - ) + detector = getOpenDataDetector() runEventRecording( - detectorConstructionFactory=detectorConstructionFactory, + detector=detector, outputDir=os.getcwd(), ).run() diff --git a/Examples/Scripts/Python/exatrkx.py b/Examples/Scripts/Python/exatrkx.py index 5a8920a318a..77cb9b1a785 100755 --- a/Examples/Scripts/Python/exatrkx.py +++ b/Examples/Scripts/Python/exatrkx.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 + from pathlib import Path import os import sys @@ -55,7 +56,8 @@ def runGNNTrackFinding( if "torch" in sys.argv: backend = ExaTrkXBackend.Torch - detector, trackingGeometry, decorators = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/Examples/Scripts/Python/fatras.py b/Examples/Scripts/Python/fatras.py index 58fb8b5f565..9a28ed8eed2 100755 --- a/Examples/Scripts/Python/fatras.py +++ b/Examples/Scripts/Python/fatras.py @@ -31,8 +31,8 @@ def runFatras(trackingGeometry, field, outputDir, s: acts.examples.Sequencer = N if "__main__" == __name__: gdc = acts.examples.GenericDetector.Config() - detector = acts.examples.GenericDetector() - trackingGeometry, contextDecorators = detector.finalize(gdc, None) + gd = acts.examples.GenericDetector(gdc) + trackingGeometry, decorators, _ = gd.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/Examples/Scripts/Python/full_chain_odd.py b/Examples/Scripts/Python/full_chain_odd.py index 50a13219afa..3b7261c47dd 100755 --- a/Examples/Scripts/Python/full_chain_odd.py +++ b/Examples/Scripts/Python/full_chain_odd.py @@ -168,9 +168,9 @@ oddSeedingSel = geoDir / "config/odd-seeding-config.json" oddMaterialDeco = acts.IMaterialDecorator.fromFile(oddMaterialMap) -detector, trackingGeometry, decorators = getOpenDataDetector( - odd_dir=geoDir, mdecorator=oddMaterialDeco -) +detector = getOpenDataDetector(odd_dir=geoDir, mdecorator=oddMaterialDeco) +trackingGeometry = detector.trackingGeometry() +decorators = detector.contextDecorators() field = acts.ConstantBField(acts.Vector3(0.0, 0.0, 2.0 * u.T)) rnd = acts.examples.RandomNumbers(seed=42) diff --git a/Examples/Scripts/Python/full_chain_odd_LRT.py b/Examples/Scripts/Python/full_chain_odd_LRT.py index 57f7ce85876..36a552af0c8 100644 --- a/Examples/Scripts/Python/full_chain_odd_LRT.py +++ b/Examples/Scripts/Python/full_chain_odd_LRT.py @@ -160,9 +160,9 @@ oddSeedingSel = geoDir / "config/odd-seeding-config.json" oddMaterialDeco = acts.IMaterialDecorator.fromFile(oddMaterialMap) -detector, trackingGeometry, decorators = getOpenDataDetector( - odd_dir=geoDir, mdecorator=oddMaterialDeco -) +detector = getOpenDataDetector(odd_dir=geoDir, mdecorator=oddMaterialDeco) +trackingGeometry = detector.trackingGeometry() +decorators = detector.contextDecorators() field = acts.ConstantBField(acts.Vector3(0.0, 0.0, 2.0 * u.T)) rnd = acts.examples.RandomNumbers(seed=42) diff --git a/Examples/Scripts/Python/full_chain_test.py b/Examples/Scripts/Python/full_chain_test.py index 49d7dacfeed..e7c945094f6 100755 --- a/Examples/Scripts/Python/full_chain_test.py +++ b/Examples/Scripts/Python/full_chain_test.py @@ -285,7 +285,9 @@ def full_chain(args): args.digi_config = geo_dir / "Examples/Algorithms/Digitization/share/default-smearing-config-generic.json" seedingConfigFile = geo_dir / "Examples/Algorithms/TrackFinding/share/geoSelection-genericDetector.json" args.bf_constant = True - detector, trackingGeometry, decorators = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() elif args.odd: import acts.examples.odd etaRange = (-3.0, 3.0) @@ -301,10 +303,12 @@ def full_chain(args): if args.material_config is None: args.material_config = geo_dir / "data/odd-material-maps.root" args.bf_constant = True - detector, trackingGeometry, decorators = acts.examples.odd.getOpenDataDetector( + detector = getOpenDataDetector( odd_dir=geo_dir, mdecorator=acts.IMaterialDecorator.fromFile(args.material_config), ) + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() elif args.itk: import acts.examples.itk as itk etaRange = (-4.0, 4.0) diff --git a/Examples/Scripts/Python/geant4.py b/Examples/Scripts/Python/geant4.py index 65e154c2704..49a41cc32fd 100755 --- a/Examples/Scripts/Python/geant4.py +++ b/Examples/Scripts/Python/geant4.py @@ -81,5 +81,7 @@ def runGeant4( [detector, contextors, store] = dd4hepDetector.finalize(geoContext, cOptions) runGeant4(detector, detector, field, Path.cwd()).run() else: - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() runGeant4(detector, trackingGeometry, field, Path.cwd()).run() diff --git a/Examples/Scripts/Python/geant4_parallel.py b/Examples/Scripts/Python/geant4_parallel.py index d9a8f59f8c2..7f39fc18ec8 100755 --- a/Examples/Scripts/Python/geant4_parallel.py +++ b/Examples/Scripts/Python/geant4_parallel.py @@ -61,7 +61,9 @@ def runGeant4EventRange(detector, trackingGeometry, beginEvent, endEvent, output if "__main__" == __name__: from acts.examples.odd import getOpenDataDetector - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() n_events = 100 n_jobs = 8 diff --git a/Examples/Scripts/Python/geometry.py b/Examples/Scripts/Python/geometry.py index 396fec3151a..e3a9f14b99a 100755 --- a/Examples/Scripts/Python/geometry.py +++ b/Examples/Scripts/Python/geometry.py @@ -7,8 +7,6 @@ from acts import MaterialMapJsonConverter from acts.examples.odd import getOpenDataDetector from acts.examples import ( - GenericDetector, - AlignedDetector, WhiteBoard, AlgorithmContext, ProcessCode, @@ -90,9 +88,11 @@ def runGeometry( if "__main__" == __name__: - # detector, trackingGeometry, decorators = AlignedDetector.create() - # detector, trackingGeometry, decorators = GenericDetector.create() - detector, trackingGeometry, decorators = getOpenDataDetector() + # detector = AlignedDetector() + # detector = GenericDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() runGeometry(trackingGeometry, decorators, outputDir=os.getcwd()) diff --git a/Examples/Scripts/Python/hashing_seeding.py b/Examples/Scripts/Python/hashing_seeding.py index 10f68b5cea9..b13ee204604 100755 --- a/Examples/Scripts/Python/hashing_seeding.py +++ b/Examples/Scripts/Python/hashing_seeding.py @@ -125,9 +125,8 @@ def getDetectorInfo(self): oddSeedingSel = geoDir / "config/odd-seeding-config.json" oddMaterialDeco = acts.IMaterialDecorator.fromFile(oddMaterialMap) - detector, trackingGeometry, decorators = getOpenDataDetector( - odd_dir=geoDir, mdecorator=oddMaterialDeco - ) + detector = getOpenDataDetector(odd_dir=geoDir, mdecorator=oddMaterialDeco) + trackingGeometry = detector.trackingGeometry() digiConfig = oddDigiConfig @@ -136,7 +135,8 @@ def getDetectorInfo(self): elif self.detector == DetectorName.generic: print("Create detector and tracking geometry") - detector, trackingGeometry, a = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() digiConfig = ( actsExamplesDir / "Algorithms/Digitization/share/default-smearing-config-generic.json" diff --git a/Examples/Scripts/Python/material_mapping.py b/Examples/Scripts/Python/material_mapping.py index 775115cf61b..559c3b7a663 100755 --- a/Examples/Scripts/Python/material_mapping.py +++ b/Examples/Scripts/Python/material_mapping.py @@ -155,7 +155,9 @@ def runMaterialMapping( mapName = args.outFile.split(".")[0] - detector, trackingGeometry, decorators = getOpenDataDetector(None) + detector = getOpenDataDetector(None) + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() runMaterialMapping( trackingGeometry, diff --git a/Examples/Scripts/Python/material_mapping_core.py b/Examples/Scripts/Python/material_mapping_core.py index 0bd9a7415de..e5a6e865246 100644 --- a/Examples/Scripts/Python/material_mapping_core.py +++ b/Examples/Scripts/Python/material_mapping_core.py @@ -300,7 +300,8 @@ def runMaterialMapping(surfaces, inputFile, outputFile, outputMap, loglevel): if args.matconfig != "": matDeco = acts.IMaterialDecorator.fromFile(args.matconfig) - [detector, trackingGeometry, decorators] = getOpenDataDetector(matDeco) + detector = getOpenDataDetector(matDeco) + trackingGeometry = detector.trackingGeometry() materialSurfaces = trackingGeometry.extractMaterialSurfaces() diff --git a/Examples/Scripts/Python/material_recording.py b/Examples/Scripts/Python/material_recording.py index 9de5914d45a..fe4ff238ca5 100755 --- a/Examples/Scripts/Python/material_recording.py +++ b/Examples/Scripts/Python/material_recording.py @@ -15,7 +15,6 @@ import acts.examples.dd4hep import acts.examples.geant4 -import acts.examples.geant4.dd4hep from acts.examples.odd import getOpenDataDetector try: @@ -30,7 +29,7 @@ def runMaterialRecording( - detectorConstructionFactory, + detector, outputDir, tracksPerEvent=10000, s=None, @@ -73,7 +72,7 @@ def runMaterialRecording( g4Alg = acts.examples.geant4.Geant4MaterialRecording( level=acts.logging.INFO, - detectorConstructionFactory=detectorConstructionFactory, + detector=detector, randomNumbers=rnd, inputParticles=evGen.config.outputParticles, outputMaterialTracks="material-tracks", @@ -108,27 +107,16 @@ def main(): args = p.parse_args() - detectorConstructionFactory = None + detector = None if args.input == "": - detector, trackingGeometry, decorators = getOpenDataDetector() - - detectorConstructionFactory = ( - acts.examples.geant4.dd4hep.DDG4DetectorConstructionFactory(detector) - ) + detector = getOpenDataDetector() elif args.input.endswith(".gdml"): - detectorConstructionFactory = ( - acts.examples.geant4.GdmlDetectorConstructionFactory(args.input) - ) + detector = acts.examples.geant4.GdmlDetector(path=args.input) elif args.input.endswith(".sqlite") or args.input.endswith(".db"): - geoModelTree = acts.geomodel.readFromDb(args.input) - detectorConstructionFactory = ( - acts.examples.geant4.geomodel.GeoModelDetectorConstructionFactory( - geoModelTree - ) - ) + detector = acts.examples.GeoModelDetector(path=args.input) runMaterialRecording( - detectorConstructionFactory=detectorConstructionFactory, + detector=detector, tracksPerEvent=args.tracks, outputDir=os.getcwd(), s=acts.examples.Sequencer(events=args.events, numThreads=1), diff --git a/Examples/Scripts/Python/material_validation.py b/Examples/Scripts/Python/material_validation.py index 94c0f21b94a..a516262d2d4 100755 --- a/Examples/Scripts/Python/material_validation.py +++ b/Examples/Scripts/Python/material_validation.py @@ -97,9 +97,9 @@ def runMaterialValidation( acts.IMaterialDecorator.fromFile(args.map) if args.map != None else None ) - detector, trackingGeometry, decorators = getOpenDataDetector( - mdecorator=materialDecorator - ) + detector = getOpenDataDetector(materialDecorator) + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() field = acts.ConstantBField(acts.Vector3(0, 0, 0 * acts.UnitConstants.T)) diff --git a/Examples/Scripts/Python/material_validation_core.py b/Examples/Scripts/Python/material_validation_core.py index 7e2ae129dcf..1d81054c091 100644 --- a/Examples/Scripts/Python/material_validation_core.py +++ b/Examples/Scripts/Python/material_validation_core.py @@ -244,9 +244,8 @@ def runMaterialValidation(s, ntracks, surfaces, outputFile, seed, loglevel): materialSurfaces = detector.extractMaterialSurfaces() else: - [detector, trackingGeometry, decorators] = getOpenDataDetector( - materialDecorator - ) + detector = getOpenDataDetector(materialDecorator) + trackingGeometry = detector.trackingGeometry() materialSurfaces = trackingGeometry.extractMaterialSurfaces() diff --git a/Examples/Scripts/Python/propagation.py b/Examples/Scripts/Python/propagation.py index 59be3385ca0..72401d8113e 100755 --- a/Examples/Scripts/Python/propagation.py +++ b/Examples/Scripts/Python/propagation.py @@ -72,14 +72,10 @@ def runPropagation(trackingGeometry, field, outputDir, s=None, decorators=[]): # matDeco = acts.IMaterialDecorator.fromFile("material.root") ## Generic detector: Default - ( - detector, - trackingGeometry, - contextDecorators, - ) = GenericDetector.create(mdecorator=matDeco) + detector = GenericDetector(mdecorator=matDeco) ## Alternative: Aligned detector in a couple of modes - # detector, trackingGeometry, contextDecorators = AlignedDetector.create( + # detector = AlignedDetector( # decoratorLogLevel=acts.logging.INFO, # # These parameters need to be tuned so that GC doesn't break # # with multiple threads @@ -94,8 +90,10 @@ def runPropagation(trackingGeometry, field, outputDir, s=None, decorators=[]): ## Alternative: DD4hep detector # dd4hepCfg = acts.examples.DD4hepDetector.Config() # dd4hepCfg.xmlFileNames = [str(getOpenDataDetectorDirectory()/"xml/OpenDataDetector.xml")] - # detector = acts.examples.DD4hepDetector() - # trackingGeometry, contextDecorators = detector.finalize(dd4hepCfg, None) + # detector = acts.examples.DD4hepDetector(dd4hepCfg) + + trackingGeometry = detector.trackingGeometry() + contextDecorators = detector.contextDecorators() ## Magnetic field setup: Default: constant 2T longitudinal field field = acts.ConstantBField(acts.Vector3(0, 0, 2 * acts.UnitConstants.T)) diff --git a/Examples/Scripts/Python/seeding.py b/Examples/Scripts/Python/seeding.py index 1e5b9e50d6b..ce1914accc7 100755 --- a/Examples/Scripts/Python/seeding.py +++ b/Examples/Scripts/Python/seeding.py @@ -152,8 +152,9 @@ def runSeeding( ) args = p.parse_args() - # detector, trackingGeometry, decorators = getOpenDataDetector() - detector, trackingGeometry, decorators = acts.examples.GenericDetector.create() + # detector = getOpenDataDetector() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/Examples/Scripts/Python/telescope_simulation.py b/Examples/Scripts/Python/telescope_simulation.py index e47c7969232..97e6214c893 100755 --- a/Examples/Scripts/Python/telescope_simulation.py +++ b/Examples/Scripts/Python/telescope_simulation.py @@ -16,12 +16,13 @@ u = acts.UnitConstants if "__main__" == __name__: - detector, trackingGeometry, decorators = acts.examples.TelescopeDetector.create( + detector = acts.examples.TelescopeDetector( bounds=[200, 200], positions=[30, 60, 90, 120, 150, 180, 210, 240, 270], stereos=[0, 0, 0, 0, 0, 0, 0, 0, 0], binValue=2, ) + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/Examples/Scripts/Python/telescope_track_params_lookup_generation.py b/Examples/Scripts/Python/telescope_track_params_lookup_generation.py index ecdffc20ec3..4f3eada72b1 100644 --- a/Examples/Scripts/Python/telescope_track_params_lookup_generation.py +++ b/Examples/Scripts/Python/telescope_track_params_lookup_generation.py @@ -99,13 +99,14 @@ def estimateLookup(trackingGeometry, numEvents, outputPath): args = p.parse_args() # Initialize the geometry - detector, trackingGeometry, decorators = acts.examples.TelescopeDetector.create( + detector = acts.examples.TelescopeDetector( bounds=[4, 10], positions=[30, 60, 90], stereos=[0, 0, 0], binValue=2, surfaceType=0, ) + trackingGeometry = detector.trackingGeometry() # Estimate the lookup estimateLookup(trackingGeometry, args.events, args.output) diff --git a/Examples/Scripts/Python/truth_tracking_gsf.py b/Examples/Scripts/Python/truth_tracking_gsf.py index 4f194a65662..7d6e05c07c7 100755 --- a/Examples/Scripts/Python/truth_tracking_gsf.py +++ b/Examples/Scripts/Python/truth_tracking_gsf.py @@ -163,13 +163,15 @@ def runTruthTrackingGsf( # ODD from acts.examples.odd import getOpenDataDetector - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() digiConfigFile = ( srcdir / "thirdparty/OpenDataDetector/config/odd-digi-smearing-config.json" ) ## GenericDetector - # detector, trackingGeometry, _ = acts.examples.GenericDetector.create() + # detector = acts.examples.GenericDetector() + # trackingGeometry = detector.trackingGeometry() # digiConfigFile = ( # srcdir # / "Examples/Algorithms/Digitization/share/default-smearing-config-generic.json" diff --git a/Examples/Scripts/Python/truth_tracking_gsf_refitting.py b/Examples/Scripts/Python/truth_tracking_gsf_refitting.py index 686f4af06c2..79bd5c41b50 100755 --- a/Examples/Scripts/Python/truth_tracking_gsf_refitting.py +++ b/Examples/Scripts/Python/truth_tracking_gsf_refitting.py @@ -102,13 +102,15 @@ def runRefittingGsf( # ODD from acts.examples.odd import getOpenDataDetector - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() digiConfigFile = ( srcdir / "thirdparty/OpenDataDetector/config/odd-digi-smearing-config.json" ) ## GenericDetector - # detector, trackingGeometry, _ = acts.examples.GenericDetector.create() + # detector = acts.examples.GenericDetector() + # trackingGeometry = detector.trackingGeometry() # digiConfigFile = ( # srcdir # / "Examples/Algorithms/Digitization/share/default-smearing-config-generic.json" diff --git a/Examples/Scripts/Python/truth_tracking_gx2f.py b/Examples/Scripts/Python/truth_tracking_gx2f.py index 31ebb447d42..2c692ca87cc 100644 --- a/Examples/Scripts/Python/truth_tracking_gx2f.py +++ b/Examples/Scripts/Python/truth_tracking_gx2f.py @@ -163,13 +163,15 @@ def runTruthTrackingGx2f( # ODD from acts.examples.odd import getOpenDataDetector - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() digiConfigFile = ( srcdir / "thirdparty/OpenDataDetector/config/odd-digi-smearing-config.json" ) ## GenericDetector - # detector, trackingGeometry, _ = acts.examples.GenericDetector.create() + # detector = acts.examples.GenericDetector() + # trackingGeometry = detector.trackingGeometry() # digiConfigFile = ( # srcdir # / "Examples/Algorithms/Digitization/share/default-smearing-config-generic.json" diff --git a/Examples/Scripts/Python/truth_tracking_kalman.py b/Examples/Scripts/Python/truth_tracking_kalman.py index 3b857fd41da..0102f1db8ee 100755 --- a/Examples/Scripts/Python/truth_tracking_kalman.py +++ b/Examples/Scripts/Python/truth_tracking_kalman.py @@ -177,13 +177,15 @@ def runTruthTrackingKalman( # ODD from acts.examples.odd import getOpenDataDetector - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() digiConfigFile = ( srcdir / "thirdparty/OpenDataDetector/config/odd-digi-smearing-config.json" ) ## GenericDetector - # detector, trackingGeometry, _ = acts.examples.GenericDetector.create() + # detector = acts.examples.GenericDetector() + # trackingGeometry = detector.trackingGeometry() # digiConfigFile = ( # srcdir # / "Examples/Algorithms/Digitization/share/default-smearing-config-generic.json" diff --git a/Examples/Scripts/Python/truth_tracking_kalman_refitting.py b/Examples/Scripts/Python/truth_tracking_kalman_refitting.py index 46578423f33..2d84808a15c 100755 --- a/Examples/Scripts/Python/truth_tracking_kalman_refitting.py +++ b/Examples/Scripts/Python/truth_tracking_kalman_refitting.py @@ -100,13 +100,16 @@ def runRefittingKf( # ODD from acts.examples.odd import getOpenDataDetector - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() digiConfigFile = ( srcdir / "thirdparty/OpenDataDetector/config/odd-digi-smearing-config.json" ) ## GenericDetector - # detector, trackingGeometry, _ = acts.examples.GenericDetector.create() + # detector = acts.examples.GenericDetector() + # trackingGeometry = detector.trackingGeometry() # digiConfigFile = ( # srcdir # / "Examples/Algorithms/Digitization/share/default-smearing-config-generic.json" diff --git a/Examples/Scripts/Python/truth_tracking_telescope.py b/Examples/Scripts/Python/truth_tracking_telescope.py index b179fb9afa7..503c05b0a19 100755 --- a/Examples/Scripts/Python/truth_tracking_telescope.py +++ b/Examples/Scripts/Python/truth_tracking_telescope.py @@ -10,11 +10,12 @@ u = acts.UnitConstants if "__main__" == __name__: - detector, trackingGeometry, decorators = acts.examples.TelescopeDetector.create( + detector = acts.examples.TelescopeDetector( bounds=[200, 200], positions=[30, 60, 90, 120, 150, 180, 210, 240, 270], stereos=[0] * 9, ) + trackingGeometry = detector.trackingGeometry() srcdir = Path(__file__).resolve().parent.parent.parent.parent diff --git a/Examples/Scripts/Python/vertex_fitting.py b/Examples/Scripts/Python/vertex_fitting.py index c6088f618a6..5add3b8f1c1 100755 --- a/Examples/Scripts/Python/vertex_fitting.py +++ b/Examples/Scripts/Python/vertex_fitting.py @@ -119,7 +119,8 @@ def runVertexFitting( if "__main__" == __name__: - detector, trackingGeometry, decorators = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) diff --git a/docs/examples/full_chain_odd.md b/docs/examples/full_chain_odd.md index 5589d9f18d8..24f7dd8ca52 100644 --- a/docs/examples/full_chain_odd.md +++ b/docs/examples/full_chain_odd.md @@ -12,9 +12,9 @@ oddDir = getOpenDataDetectorDirectory() oddMaterialMap = oddDir / "data/odd-material-maps.root" oddMaterialDeco = acts.IMaterialDecorator.fromFile(oddMaterialMap) -detector, trackingGeometry, decorators = getOpenDataDetector( - mdecorator=oddMaterialDeco, -) +detector = getOpenDataDetector(mdecorator=oddMaterialDeco) +trackingGeometry = detector.trackingGeometry() +decorators = detector.contextDecorators() ``` In our simple example we assume a homogeneous magnetic field along the beam axis with 2 T. The magnetic field is passed to all the different algorithms in our simulation and the reconstruction pipeline. diff --git a/docs/examples/howto/material_mapping.rst b/docs/examples/howto/material_mapping.rst index a8657a470a6..601d0b25ba9 100644 --- a/docs/examples/howto/material_mapping.rst +++ b/docs/examples/howto/material_mapping.rst @@ -55,7 +55,9 @@ For the following example we will be remapping the material of the ODD, we will .. code-block:: console - detector, trackingGeometry, decorators = getOpenDataDetector() + detector = getOpenDataDetector() + trackingGeometry = detector.trackingGeometry() + decorators = detector.contextDecorators() This algorithm is useful to obtain a visualisation of your detector using the different types of output available (``output-obj`` gives ``.obj`` with a 3D representation of the different subdetectors, for example). Here, we use ``output-json`` to obtain a map of all the surfaces and volumes in the detector with a ``ProtoSurfaceMaterial`` (or a ``ProtoVolumeMaterial``), ``mat-output-allmaterial`` ensure that a ``ProtoSurfaceMaterial`` (or a ``ProtoVolumeMaterial``) is associated to all the surfaces (or volumes), enforcing all of them to be written. Four types of surfaces exist: diff --git a/docs/examples/python_bindings.rst b/docs/examples/python_bindings.rst index fe0cd1dcb9f..69cead57aa7 100644 --- a/docs/examples/python_bindings.rst +++ b/docs/examples/python_bindings.rst @@ -22,7 +22,8 @@ sets up the particle propagation and runs a few events. import acts import acts.examples - detector, trackingGeometry, contextDecorators = acts.examples.GenericDetector.create() + detector = acts.examples.GenericDetector() + trackingGeometry = detector.trackingGeometry() s = acts.examples.Sequencer(events=10) rnd = acts.examples.RandomNumbers(seed=42) diff --git a/docs/getting_started.md b/docs/getting_started.md index aff8e38a3b0..03365dd9f40 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -375,7 +375,9 @@ You can now use the ODD in the python binding by using: ```python oddMaterialDeco = acts.IMaterialDecorator.fromFile("PATH_TO_Acts/thirdparty/OpenDataDetector/data/odd-material-maps.root") -detector, trackingGeometry, decorators = getOpenDataDetector(oddMaterialDeco) +detector = getOpenDataDetector(oddMaterialDeco) +trackingGeometry = detector.trackingGeometry() +decorators = detector.contextDecorators() ``` ## Using ACTS From 47db0b917b575cc1b956fff0dbc3b09f925ed3c5 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Mon, 9 Dec 2024 10:46:03 +0100 Subject: [PATCH 16/21] chore: Sonar fixes after detector refactor (#3968) Blocked by: - #3498 ## Summary by CodeRabbit - **New Features** - Enhanced safety in the `DD4hepDetector` class by using const references in loop iterations. - Updated sorting mechanism for several vectors to utilize modern C++ practices. - **Bug Fixes** - Adjusted data types in the `Config` struct of the `AlignedDetector` class for better performance and accuracy. - Removed obsolete member variable from the `Config` struct in the `AlignedDetector` class. Co-authored-by: Andreas Stefl <487211+andiwand@users.noreply.github.com> --- .../ActsExamples/Geant4/Geant4Simulation.hpp | 2 +- .../ContextualDetector/AlignedDetector.hpp | 10 ++--- .../DD4hepDetector/src/DD4hepDetector.cpp | 38 +++++++++---------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp index 089c1c2b3cd..df9dd637eca 100644 --- a/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp +++ b/Examples/Algorithms/Geant4/include/ActsExamples/Geant4/Geant4Simulation.hpp @@ -208,7 +208,7 @@ class Geant4MaterialRecording final : public Geant4SimulationBase { /// Algorithm execute method, called once per event with context /// /// @param ctx the AlgorithmContext for this event - ProcessCode execute(const ActsExamples::AlgorithmContext& ctx) const final; + ProcessCode execute(const ActsExamples::AlgorithmContext& ctx) const override; /// Readonly access to the configuration const Config& config() const final { return m_cfg; } diff --git a/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp b/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp index 0859c174e5a..7a184f16fc1 100644 --- a/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp +++ b/Examples/Detectors/ContextualDetector/include/ActsExamples/ContextualDetector/AlignedDetector.hpp @@ -13,8 +13,6 @@ #include "ActsExamples/DetectorCommons/Detector.hpp" #include "ActsExamples/GenericDetector/GenericDetector.hpp" -#include - namespace ActsExamples { class InternallyAlignedDetectorElement; @@ -24,11 +22,11 @@ class AlignedDetector : public Detector { public: struct Config : public GenericDetector::Config { /// Seed for the decorator random numbers. - std::size_t seed = 1324354657; + unsigned int seed = 1324354657; /// Size of a valid IOV. - std::size_t iovSize = 100; + unsigned int iovSize = 100; /// Span until garbage collection is active. - std::size_t flushSize = 200; + unsigned int flushSize = 200; /// Run the garbage collection? bool doGarbageCollection = true; /// Sigma of the in-plane misalignment @@ -46,8 +44,6 @@ class AlignedDetector : public Detector { enum class Mode { Internal, External }; Mode mode = Mode::Internal; - - std::shared_ptr materialDecorator; }; explicit AlignedDetector(const Config& cfg); diff --git a/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp b/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp index a411a32a028..6ad70fa7d70 100644 --- a/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp +++ b/Examples/Detectors/DD4hepDetector/src/DD4hepDetector.cpp @@ -86,8 +86,8 @@ std::unique_ptr DD4hepDetector::buildDD4hepGeometry() const { std::unique_ptr detector = dd4hep::Detector::make_unique(m_cfg.name); - for (auto& file : m_cfg.xmlFileNames) { - detector->fromCompact(file.c_str()); + for (const auto& file : m_cfg.xmlFileNames) { + detector->fromCompact(file); } detector->volumeManager(); detector->apply("DD4hepVolumeManager", 0, nullptr); @@ -106,7 +106,7 @@ void ActsExamples::sortFCChhDetElements(std::vector& det) { std::vector eCal; std::vector hCal; std::vector muon; - for (auto& detElement : det) { + for (const auto& detElement : det) { std::string detName = detElement.name(); if (detName.find("Muon") != std::string::npos) { muon.push_back(detElement); @@ -118,22 +118,22 @@ void ActsExamples::sortFCChhDetElements(std::vector& det) { tracker.push_back(detElement); } } - sort(muon.begin(), muon.end(), - [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { - return (a.id() < b.id()); - }); - sort(eCal.begin(), eCal.end(), - [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { - return (a.id() < b.id()); - }); - sort(hCal.begin(), hCal.end(), - [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { - return (a.id() < b.id()); - }); - sort(tracker.begin(), tracker.end(), - [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { - return (a.id() < b.id()); - }); + std::ranges::sort( + muon, [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); + std::ranges::sort( + eCal, [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); + std::ranges::sort( + hCal, [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); + std::ranges::sort( + tracker, [](const dd4hep::DetElement& a, const dd4hep::DetElement& b) { + return (a.id() < b.id()); + }); det.clear(); det = tracker; From 0a11443eef9ff85e9a0b5f1a6b15f6f424eaef98 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 9 Dec 2024 12:31:07 +0100 Subject: [PATCH 17/21] chore: Clean event generator includes in Examples (#3964) --- .../Generators/ActsExamples/Generators/EventGenerator.cpp | 5 ----- .../Generators/ActsExamples/Generators/EventGenerator.hpp | 1 - .../ActsExamples/Generators/ParametricParticleGenerator.cpp | 1 - .../ActsExamples/Generators/Pythia8ProcessGenerator.cpp | 2 -- 4 files changed, 9 deletions(-) diff --git a/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.cpp b/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.cpp index 9d5c1b873d3..7c1b629430e 100644 --- a/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.cpp +++ b/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.cpp @@ -8,19 +8,14 @@ #include "ActsExamples/Generators/EventGenerator.hpp" -#include "Acts/Surfaces/PerigeeSurface.hpp" #include "ActsExamples/EventData/SimVertex.hpp" -#include "ActsExamples/EventData/Track.hpp" #include "ActsExamples/Framework/AlgorithmContext.hpp" #include "ActsFatras/EventData/Barcode.hpp" -#include "ActsFatras/EventData/Particle.hpp" #include #include -#include #include #include -#include namespace ActsExamples { diff --git a/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.hpp b/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.hpp index 7a76ac03d71..1690b203d2e 100644 --- a/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.hpp +++ b/Examples/Algorithms/Generators/ActsExamples/Generators/EventGenerator.hpp @@ -24,7 +24,6 @@ #include namespace ActsExamples { -struct AlgorithmContext; /// Event generator based on separate particles and vertex generators. /// diff --git a/Examples/Algorithms/Generators/ActsExamples/Generators/ParametricParticleGenerator.cpp b/Examples/Algorithms/Generators/ActsExamples/Generators/ParametricParticleGenerator.cpp index 00411fc64c3..9da5e1a6e1a 100644 --- a/Examples/Algorithms/Generators/ActsExamples/Generators/ParametricParticleGenerator.cpp +++ b/Examples/Algorithms/Generators/ActsExamples/Generators/ParametricParticleGenerator.cpp @@ -13,7 +13,6 @@ #include "Acts/Utilities/AngleHelpers.hpp" #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsFatras/EventData/Barcode.hpp" -#include "ActsFatras/EventData/Particle.hpp" #include #include diff --git a/Examples/Algorithms/GeneratorsPythia8/ActsExamples/Generators/Pythia8ProcessGenerator.cpp b/Examples/Algorithms/GeneratorsPythia8/ActsExamples/Generators/Pythia8ProcessGenerator.cpp index 1b774a7bdf0..916addff0ae 100644 --- a/Examples/Algorithms/GeneratorsPythia8/ActsExamples/Generators/Pythia8ProcessGenerator.cpp +++ b/Examples/Algorithms/GeneratorsPythia8/ActsExamples/Generators/Pythia8ProcessGenerator.cpp @@ -11,10 +11,8 @@ #include "Acts/Utilities/MathHelpers.hpp" #include "ActsExamples/EventData/SimVertex.hpp" #include "ActsFatras/EventData/Barcode.hpp" -#include "ActsFatras/EventData/Particle.hpp" #include -#include #include #include #include From 94ae506310982400b4c8b0739a3fa6cec89c9527 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Mon, 9 Dec 2024 14:20:57 +0100 Subject: [PATCH 18/21] fix: Workaround Pythia8 race condition in ttbar physmon (#3965) Until https://github.com/acts-project/acts/issues/3963 is fixed we have to stick to single threaded processing with ttbar ## Summary by CodeRabbit - **Bug Fixes** - Adjusted threading configuration to single-threaded execution to enhance stability and prevent race conditions during simulations. --- CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py b/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py index de4021a5055..d26e5e78873 100755 --- a/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py +++ b/CI/physmon/workflows/physmon_trackfinding_ttbar_pu200.py @@ -35,9 +35,10 @@ with tempfile.TemporaryDirectory() as temp: + # Running with a single thread to avoid rance conditions with Pythia8, see https://github.com/acts-project/acts/issues/3963 s = acts.examples.Sequencer( events=3, - numThreads=-1, + numThreads=1, # run with single thread logLevel=acts.logging.INFO, ) From bd88be8f5b2064aa28e543b402ada978f0816b44 Mon Sep 17 00:00:00 2001 From: "Alexander J. Pfleger" <70842573+AJPfleger@users.noreply.github.com> Date: Mon, 9 Dec 2024 16:15:51 +0100 Subject: [PATCH 19/21] fix: typo in json digitization config (#3960) ## Summary by CodeRabbit - **Bug Fixes** - Corrected spelling errors in the spell-check ignore list. - Fixed a typo from "Digitial" to "Digital" in JSON serialization and deserialization functions, ensuring accurate representation of smearing configuration. - **Chores** - Updated internal documentation for spelling corrections. --- CI/codespell_ignore.txt | 1 - Examples/Algorithms/Digitization/scripts/smearing-config.py | 2 +- Examples/Io/Json/src/JsonDigitizationConfig.cpp | 4 ++-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/CI/codespell_ignore.txt b/CI/codespell_ignore.txt index 40dd321d0f5..e6ab8123413 100644 --- a/CI/codespell_ignore.txt +++ b/CI/codespell_ignore.txt @@ -11,7 +11,6 @@ parm writet localy lastr -digitial exprot pring aline diff --git a/Examples/Algorithms/Digitization/scripts/smearing-config.py b/Examples/Algorithms/Digitization/scripts/smearing-config.py index 5c176d86318..64dc1b1e3e9 100644 --- a/Examples/Algorithms/Digitization/scripts/smearing-config.py +++ b/Examples/Algorithms/Digitization/scripts/smearing-config.py @@ -137,7 +137,7 @@ def block_to_json(args): data["range"] = ps[1:] data["type"] = "GaussClipped" elif t in [3, 4]: - data["type"] = "Uniform" if t == 3 else "Digitial" + data["type"] = "Uniform" if t == 3 else "Digital" pitch = ps[0] low = ps[1] diff --git a/Examples/Io/Json/src/JsonDigitizationConfig.cpp b/Examples/Io/Json/src/JsonDigitizationConfig.cpp index 8e3b3abe2cc..63c92965212 100644 --- a/Examples/Io/Json/src/JsonDigitizationConfig.cpp +++ b/Examples/Io/Json/src/JsonDigitizationConfig.cpp @@ -64,7 +64,7 @@ void to_json(nlohmann::json& j, const ActsFatras::SingleParameterSmearFunction< // Digital auto digital = f.target(); if (digital != nullptr) { - j["type"] = "Digitial"; + j["type"] = "Digital"; j["bindata"] = nlohmann::json(digital->binningData); return; } @@ -98,7 +98,7 @@ void from_json( Acts::BinningData bd; from_json(j["bindata"], bd); f = Digitization::Uniform(bd); - } else if (sType == "Digitial") { + } else if (sType == "Digital") { Acts::BinningData bd; from_json(j["bindata"], bd); f = Digitization::Digital(bd); From d3cedd0be4dd54deeb846bd5f48f509b4540b416 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Tue, 10 Dec 2024 16:49:45 +0100 Subject: [PATCH 20/21] feat: Make TransformRange fulfill range concept (#3971) This allows using it with range algorithms --- .../include/Acts/Utilities/TransformRange.hpp | 16 +++++++++++++ .../Core/Utilities/TransformRangeTests.cpp | 24 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/Core/include/Acts/Utilities/TransformRange.hpp b/Core/include/Acts/Utilities/TransformRange.hpp index 4c7653c49e2..58e30b4f8c4 100644 --- a/Core/include/Acts/Utilities/TransformRange.hpp +++ b/Core/include/Acts/Utilities/TransformRange.hpp @@ -169,6 +169,8 @@ struct TransformRangeIterator { /// Construct an iterator from an underlying iterator explicit TransformRangeIterator(iterator_t iterator) : m_iterator(iterator) {} + TransformRangeIterator() = default; + /// Return a reference to the value that is transformed by the callable /// @return Reference to the transformed value reference operator*() { return Callable::apply(*m_iterator); } @@ -184,6 +186,14 @@ struct TransformRangeIterator { return *this; } + /// Advance the iterator + /// @return Reference to the iterator + TransformRangeIterator operator++(int) { + auto tmp = *this; + ++m_iterator; + return tmp; + } + /// Compare two iterators for equality /// @param other The other iterator to compare to bool operator==(const TransformRangeIterator& other) const { @@ -219,3 +229,9 @@ struct DotGet { }; } // namespace Acts::detail + +/// @cond +template +constexpr bool std::ranges::enable_borrowed_range< + Acts::detail::TransformRange> = true; +/// @endcond diff --git a/Tests/UnitTests/Core/Utilities/TransformRangeTests.cpp b/Tests/UnitTests/Core/Utilities/TransformRangeTests.cpp index d7ce6b56877..3b369d56307 100644 --- a/Tests/UnitTests/Core/Utilities/TransformRangeTests.cpp +++ b/Tests/UnitTests/Core/Utilities/TransformRangeTests.cpp @@ -6,10 +6,13 @@ // 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 #include "Acts/Utilities/TransformRange.hpp" +#include + using namespace Acts; BOOST_AUTO_TEST_SUITE(TransformRangeTests) @@ -99,6 +102,14 @@ BOOST_AUTO_TEST_CASE(TransformRangeDeref) { static_assert(std::is_same_v); checkSameAddresses(v, r); + std::vector unpacked; + std::ranges::transform(r, std::back_inserter(unpacked), + [](auto val) { return val; }); + std::vector exp = {1, 2, 4}; + + BOOST_CHECK_EQUAL_COLLECTIONS(exp.begin(), exp.end(), unpacked.begin(), + unpacked.end()); + auto cr = detail::TransformRange{detail::ConstDereference{}, raw_v}; static_assert(std::is_same_v); static_assert(std::is_same_v); @@ -108,6 +119,13 @@ BOOST_AUTO_TEST_CASE(TransformRangeDeref) { static_assert(std::is_same_v); static_assert(std::is_same_v); checkSameAddresses(v, r); + + unpacked.clear(); + std::ranges::transform(cr, std::back_inserter(unpacked), + [](auto val) { return val; }); + + BOOST_CHECK_EQUAL_COLLECTIONS(exp.begin(), exp.end(), unpacked.begin(), + unpacked.end()); } } @@ -127,6 +145,12 @@ BOOST_AUTO_TEST_CASE(TransformRangeDeref) { static_assert(std::is_same_v); static_assert(std::is_same_v); checkSameAddresses(v, r); + + std::vector unpacked; + std::ranges::transform(r, std::back_inserter(unpacked), + [](auto val) { return val; }); + + BOOST_CHECK(unpacked == std::vector({1, 2, 3})); } std::vector raw_v; From 6c910a6640dd1b66ab95b612d0c57bbfc3b8dd0b Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Tue, 10 Dec 2024 21:46:35 +0100 Subject: [PATCH 21/21] feat: Gen3 blueprint geometry construction (#3869) This PR implements building a tracking geometry using the blueprint building mechanism. ``` +---------------+ +-----------+ | | | | | Root | | v | | | +---------------+ +---------------+ | | | | | | Child 1 | +----------+ | | | v +----------+ +---------------+ +---------------+ | | | +--------------+ | Child 2 | v +----------+ | | .---------. | | +---------------+ / \ | v ( Proc node ) | +---------------+ `. ,' | | | `-------' | | Child 3 | | | | | | | +---------------+ +---------+ ``` The construction phases are documented in @c BlueprintNode, which is the base class for all nodes in the tree. Part of: - #3502 Blocked by: - #3816 - #3818 - https://github.com/acts-project/acts/pull/3971 --- Core/include/Acts/Geometry/Blueprint.hpp | 105 ++++ Core/include/Acts/Geometry/BlueprintNode.hpp | 295 ++++++++++ .../Acts/Geometry/BlueprintOptions.hpp | 28 + .../Acts/Geometry/CompositePortalLink.hpp | 2 + .../CylinderContainerBlueprintNode.hpp | 157 ++++++ Core/include/Acts/Geometry/GridPortalLink.hpp | 12 + .../Acts/Geometry/LayerBlueprintNode.hpp | 141 +++++ .../MaterialDesignatorBlueprintNode.hpp | 101 ++++ Core/include/Acts/Geometry/Portal.hpp | 4 + .../Acts/Geometry/StaticBlueprintNode.hpp | 67 +++ .../Acts/Material/ProtoVolumeMaterial.hpp | 3 +- Core/include/Acts/Seeding/PathSeeder.hpp | 2 - Core/include/Acts/Utilities/Delegate.hpp | 9 +- Core/src/Geometry/Blueprint.cpp | 157 ++++++ Core/src/Geometry/BlueprintNode.cpp | 173 ++++++ Core/src/Geometry/BlueprintOptions.cpp | 29 + Core/src/Geometry/CMakeLists.txt | 7 + .../CylinderContainerBlueprintNode.cpp | 257 +++++++++ Core/src/Geometry/LayerBlueprintNode.cpp | 149 +++++ .../MaterialDesignatorBlueprintNode.cpp | 191 +++++++ Core/src/Geometry/PortalLinkBase.cpp | 10 +- Core/src/Geometry/StaticBlueprintNode.cpp | 170 ++++++ Examples/Python/CMakeLists.txt | 1 + Examples/Python/src/Blueprint.cpp | 432 ++++++++++++++ Examples/Python/src/Geometry.cpp | 15 + Examples/Python/tests/test_blueprint.py | 80 +++ Examples/Scripts/Python/blueprint.py | 99 ++++ Tests/UnitTests/Core/Detector/CMakeLists.txt | 2 +- .../Core/Geometry/BlueprintApiTests.cpp | 409 ++++++++++++++ .../Core/Geometry/BlueprintTests.cpp | 531 ++++++++++++++++++ Tests/UnitTests/Core/Geometry/CMakeLists.txt | 2 + docs/core/geometry/concepts.md | 75 +++ docs/core/geometry/construction.md | 43 ++ docs/core/geometry/index.md | 3 + docs/core/geometry/layerless/layerless.md | 6 +- docs/known-warnings.txt | 1 + 36 files changed, 3754 insertions(+), 14 deletions(-) create mode 100644 Core/include/Acts/Geometry/Blueprint.hpp create mode 100644 Core/include/Acts/Geometry/BlueprintNode.hpp create mode 100644 Core/include/Acts/Geometry/BlueprintOptions.hpp create mode 100644 Core/include/Acts/Geometry/CylinderContainerBlueprintNode.hpp create mode 100644 Core/include/Acts/Geometry/LayerBlueprintNode.hpp create mode 100644 Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp create mode 100644 Core/include/Acts/Geometry/StaticBlueprintNode.hpp create mode 100644 Core/src/Geometry/Blueprint.cpp create mode 100644 Core/src/Geometry/BlueprintNode.cpp create mode 100644 Core/src/Geometry/BlueprintOptions.cpp create mode 100644 Core/src/Geometry/CylinderContainerBlueprintNode.cpp create mode 100644 Core/src/Geometry/LayerBlueprintNode.cpp create mode 100644 Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp create mode 100644 Core/src/Geometry/StaticBlueprintNode.cpp create mode 100644 Examples/Python/src/Blueprint.cpp create mode 100644 Examples/Python/tests/test_blueprint.py create mode 100755 Examples/Scripts/Python/blueprint.py create mode 100644 Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp create mode 100644 Tests/UnitTests/Core/Geometry/BlueprintTests.cpp create mode 100644 docs/core/geometry/concepts.md create mode 100644 docs/core/geometry/construction.md diff --git a/Core/include/Acts/Geometry/Blueprint.hpp b/Core/include/Acts/Geometry/Blueprint.hpp new file mode 100644 index 00000000000..db96654218e --- /dev/null +++ b/Core/include/Acts/Geometry/Blueprint.hpp @@ -0,0 +1,105 @@ +// 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/Geometry/BlueprintNode.hpp" +#include "Acts/Geometry/PortalShell.hpp" +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" + +namespace Acts { + +class GeometryContext; + +/// This class is the top-level entry point to build a tracking geometry using +/// the blueprint building mechanism. It forms the root of a tree of nodes where +/// each node performs a portion of the construction. This top-level class has +/// the main construction methods that execute the construction of the geometry. +/// +/// ``` +/// +---------------+ +-----------+ +/// | | | | +/// | Root | | v +/// | | | +---------------+ +/// +---------------+ | | | +/// | | | Child 1 | +/// +----------+ | | | +/// v +----------+ +---------------+ +/// +---------------+ | +/// | | +--------------+ +/// | Child 2 | v +----------+ +/// | | .---------. | | +/// +---------------+ / \ | v +/// ( Proc node ) | +---------------+ +/// `. ,' | | | +/// `-------' | | Child 3 | +/// | | | | +/// | | +---------------+ +/// +---------+ +/// ``` +/// +/// The construction phases are documented in @c BlueprintNode, which is the +/// base class for all nodes in the tree. +/// @note This class inherits from @c BlueprintNode, but hides the main +/// blueprint construction phase overloads. The @c Blueprint class is +/// only ever intended to be the top-level node, and not anywhere else +/// in the tree. +class Blueprint : public BlueprintNode { + public: + struct Config { + /// Determine how much envelope space to produce from the highest volume + /// in the geometry hierarchy and the world volume. + ExtentEnvelope envelope = ExtentEnvelope::Zero(); + + /// The geometry identifier hook, passed through the `TrackingGeometry` + /// constructor. This will be superseded by a more integrated approach to + /// the identification scheme + GeometryIdentifierHook geometryIdentifierHook = {}; + }; + + /// Constructor from a config object + /// @param config The configuration object + explicit Blueprint(const Config& config); + + /// Construct the tracking geometry from the blueprint tree + /// @param options The construction options, see @c BlueprintOptions + /// @param gctx The geometry context for construction. In almost all cases, + /// this should be the *nominal* geometry context + /// @param logger The logger to use for output during construction + std::unique_ptr construct( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()); + + protected: + /// The name of the blueprint node, always "Root" + /// @return The name + const std::string& name() const override; + + /// @copydoc BlueprintNode::build + Volume& build(const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// @copydoc BlueprintNode::connect + PortalShellBase& connect( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// @copydoc BlueprintNode::finalize + void finalize(const BlueprintOptions& options, const GeometryContext& gctx, + TrackingVolume& parent, + const Logger& logger = Acts::getDummyLogger()) override; + + /// @copydoc BlueprintNode::addToGraphviz + void addToGraphviz(std::ostream& os) const override; + + private: + Config m_cfg; +}; + +} // namespace Acts diff --git a/Core/include/Acts/Geometry/BlueprintNode.hpp b/Core/include/Acts/Geometry/BlueprintNode.hpp new file mode 100644 index 00000000000..e159c463067 --- /dev/null +++ b/Core/include/Acts/Geometry/BlueprintNode.hpp @@ -0,0 +1,295 @@ +// 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 "Acts/Geometry/BlueprintOptions.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/NavigationPolicyFactory.hpp" +#include "Acts/Utilities/BinningType.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "Acts/Utilities/TransformRange.hpp" + +#include +#include +#include +#include + +namespace Acts { + +class Volume; +class TrackingVolume; +class VolumeBounds; +class PortalShellBase; +class CylinderContainerBlueprintNode; +class MaterialDesignatorBlueprintNode; +class StaticBlueprintNode; +class LayerBlueprintNode; + +/// Base class for all nodes in the blueprint tree. This class defines the +/// three-phase construction process. The three phases are +/// +/// -# **Build**: Construct volume representation + compute final sizing +/// -# **Connect**: Create and connect portals at volume boundaries +/// -# **Finalize**: Register portals with volumes + create acceleration +/// structures +/// +/// During the *build* phase, the `build` method of all nodes in the tree are +/// called recursively. Some nodes, like @ref Acts::CylinderContainerBlueprintNode, +/// will take action on the volumes returns from its children, and perform +/// sizing to connect them. See the @ref Acts::CylinderContainerBlueprintNode and @ref +/// Acts::CylinderVolumeStack documentation for details on how the sizing is +/// carried out. +class BlueprintNode { + public: + /// Virtual destructor to ensure correct cleanup + virtual ~BlueprintNode() = default; + + /// Get the name of this node + virtual const std::string& name() const = 0; + + /// @anchor construction + /// @name Construction methods + /// These methods constitute the primary interface of the node that + /// participates in the geometry construction. + /// @{ + + /// This method is called during the *build* phase of the blueprint tree + /// construction. It returns a single @ref Acts::Volume which represents transform + /// and bounds of the entire subtree. This does not have to correspond to the + /// final @ref Acts::TrackingVolume, some node types will produce temporary volume + /// representations. Lifetime of the returned volume is managed by the source + /// node! + /// Nodes higher in the hierarchy will issue resizes down the tree hierarchy. + /// This is not done through a direct hierarchy, but coordinated by the + /// respective node type, by internally consulting its children. + /// + /// @note Generally, you should not need to to call this method directly. + /// The construction should usually be done through the special + /// @ref Acts::Blueprint class. + /// + /// @param options The global construction options + /// @param gctx The geometry context for construction (usually nominal) + /// @param logger The logger to use for output during construction + /// @return The volume used for communicating transform and size up the hierarchy + virtual Volume& build(const BlueprintOptions& options, + const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) = 0; + + /// This method is called during the *connect* phase. This phase handles the + /// creation and connection of *portals* (instances of @ref Acts::PortalLinkBase). + /// After the build-phase has completed, the volume sizes are **final**. Each + /// node will consult its fully sized volume to produce *boundary surfaces*. + /// Each boundary surface is then turned into a @ref Acts::TrivialPortalLink, which + /// in turn produces a one-sided portal (see @ref Acts::Portal documentation) + /// + /// Some nodes (like @ref Acts::CylinderContainerBlueprintNode) will take action on + /// their children, and unify the connected portals. + /// + /// After a node's processing has completed, it returns a reference to a @ref + /// Acts::PortalShellBase, which represents a set of portals in a specific + /// geometry arrangement. The returned object lifetime is managed by the + /// returning node. + /// + /// @param options The global construction options + /// @param gctx The geometry context for construction (usually nominal) + /// @param logger The logger to use for output during construction + virtual PortalShellBase& connect( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) = 0; + + /// This method is called during the *finalize* phase. This phase handles: + /// + /// - Registering portals into their final volumes + /// - Registering volumes into their parents + /// - Creating navigation policies + /// - (In future) Handle geometry identification assignment + /// + /// At the end of this phase, each node will have transferred any temporary + /// resources created during the build, that need to be retained, into the + /// final @ref Acts::TrackingGeometry, and can be safely destroyed. + /// + /// @note The @p parent for volumes, portals, etc to be registered in is passed in **as an + /// argument**, rather than being implicitly determined from the + /// **parent node**. This is done so that nodes can remove themselves + /// from the final volume hierarchy, like container nodes or the + /// @ref Acts::MaterialDesignatorBlueprintNode. + /// + /// @param options The global construction options + /// @param gctx The geometry context for construction (usually nominal) + /// @param parent The parent volume to register in + /// @param logger The logger to use for output during construction + virtual void finalize(const BlueprintOptions& options, + const GeometryContext& gctx, TrackingVolume& parent, + const Logger& logger = Acts::getDummyLogger()) = 0; + + /// @} + + /// @anchor convenience + /// @name Convenience methods + /// These methods are meant to make the construction of a blueprint tree in + /// code more ergonomic. + /// They usually take an optional `callback` parameter. The primary use for + /// this parameter is structural, as it facilitates introducing scopes to + /// indicate in code that objects are nested. + /// + /// ```cpp + /// Blueprint::Config cfg; + /// auto root = std::make_unique(cfg); + /// root->addStaticVolume( + /// base, std::make_shared(50_mm, 400_mm, 1000_mm), + /// "PixelWrapper", [&](auto& wrapper) { + /// // This scope can be used to equip `wrapper` + /// }); + /// ``` + /// + /// Alternatively, they can also be used without a callback, as the newly + /// created node is also returned by reference: + /// + /// ``` + /// auto& wrapper = root->addStaticVolume( + /// base, std::make_shared(50_mm, 400_mm, 1000_mm), + /// "PixelWrapper"); + /// ``` + /// + /// In both cases, it's not necessary to register the newly created node + /// with a parent node. + /// + /// @{ + + /// This method creates a new @ref Acts::StaticBlueprintNode wrapping @p + /// volume and adds it to this node as a child. + /// @param volume The volume to add + /// @param callback An optional callback that receives the node as an argument + /// @return A reference to the created node + StaticBlueprintNode& addStaticVolume( + std::unique_ptr volume, + const std::function& callback = {}); + + /// Alternative overload for creating a @ref Acts::StaticBlueprintNode. This + /// overload will invoke the constructor of @ref Acts::TrackingVolume and use + /// that volume to create the node. + /// @param transform The transform of the volume + /// @param volumeBounds The bounds of the volume + /// @param volumeName The name of the volume + /// @param callback An optional callback that receives the node as an argument + StaticBlueprintNode& addStaticVolume( + const Transform3& transform, std::shared_ptr volumeBounds, + const std::string& volumeName = "undefined", + const std::function& callback = {}); + + /// Convenience method for creating a @ref Acts::CylinderContainerBlueprintNode. + /// @param name The name of the container node. This name is only reflected + /// in the node tree for debugging, as no extra volumes is created + /// for the container. + /// @param direction The direction of the stack configuration. See + /// @ref Acts::CylinderVolumeStack for details. + /// @param callback An optional callback that receives the node as an argument + CylinderContainerBlueprintNode& addCylinderContainer( + const std::string& name, BinningValue direction, + const std::function& + callback = {}); + + /// Convenience method for creating a @ref Acts::MaterialDesignatorBlueprintNode. + /// @param name The name of the material designator node. Used for debugging + /// the node tree only. + /// @param callback An optional callback that receives the node as an argument + MaterialDesignatorBlueprintNode& addMaterial( + const std::string& name, + const std::function& + callback = {}); + + /// Convenience method for creating a @ref Acts::LayerBlueprintNode. + /// @param name The name of the layer node. + /// @param callback An optional callback that receives the node as an argument + LayerBlueprintNode& addLayer( + const std::string& name, + const std::function& callback = {}); + + /// @} + + /// Register a @p child to this node. + /// @warning This method throws if adding the child would create a + /// cycle in the blueprint tree! + /// @param child The child node to add + /// @return A reference this node (not the child!) + BlueprintNode& addChild(std::shared_ptr child); + + /// A range-like object that allows range based for loops and index access. + /// This type's iterators and accessors return mutable references when + /// dereferenced. + using MutableChildRange = + detail::TransformRange>>; + + /// A range-like object that allows range based for loops and index access. + /// This type's iterators and accessors return const references when + /// dereferenced. + using ChildRange = + detail::TransformRange>>; + + /// Return a @ref MutableChildRange to the children of this node. + /// @return A range-like object to the children + MutableChildRange children(); + + /// Return a @ref ChildRange to the children of this node. + /// @return A range-like object to the children + ChildRange children() const; + + /// Remove all children from this node + void clearChildren(); + + /// Return the depth of this node in the blueprint tree. A depth of zero means + /// this node does not have a parent. + /// @return The depth of this node + std::size_t depth() const; + + /// Print the node tree starting from this node to graphviz format + /// @param os The stream to print to + void graphviz(std::ostream& os) const; + + /// Method that writes a representatiohn of **this node only** to graphviz. + /// This should generally not be called on its own, but through the @ref + /// BlueprintNode::graphviz method. + /// @param os The stream to print to + virtual void addToGraphviz(std::ostream& os) const; + + /// Print a representation of this node to the stream + /// @param os The stream to print to + /// @param node The node to print + /// @return The output stream + friend std::ostream& operator<<(std::ostream& os, const BlueprintNode& node) { + node.toStream(os); + return os; + } + + protected: + /// Virtual method to determine stream representation. + /// @note This method is called by the stream operator. + virtual void toStream(std::ostream& os) const; + + /// Set the depth to @p depth and update children recursively + void setDepth(std::size_t depth); + + /// Printing helper returning a prefix including an indent depending on the + /// depth. + /// @return The prefix string + std::string prefix() const; + + /// An indentation depending on the depth of this node. + /// @return The indentation string + std::string indent() const; + + private: + std::size_t m_depth{0}; + std::vector> m_children{}; +}; + +} // namespace Acts diff --git a/Core/include/Acts/Geometry/BlueprintOptions.hpp b/Core/include/Acts/Geometry/BlueprintOptions.hpp new file mode 100644 index 00000000000..305ff5d399d --- /dev/null +++ b/Core/include/Acts/Geometry/BlueprintOptions.hpp @@ -0,0 +1,28 @@ +// 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/Geometry/NavigationPolicyFactory.hpp" + +#include + +namespace Acts { + +struct BlueprintOptions { + std::shared_ptr defaultNavigationPolicyFactory{ + makeDefaultNavigationPolicyFactory()}; + + void validate() const; + + private: + static std::unique_ptr + makeDefaultNavigationPolicyFactory(); +}; + +} // namespace Acts diff --git a/Core/include/Acts/Geometry/CompositePortalLink.hpp b/Core/include/Acts/Geometry/CompositePortalLink.hpp index 6586d875b79..231af047b6c 100644 --- a/Core/include/Acts/Geometry/CompositePortalLink.hpp +++ b/Core/include/Acts/Geometry/CompositePortalLink.hpp @@ -23,6 +23,7 @@ class Surface; /// Composite portal links can graft together other portal link instances, for /// example grids that could not be merged due to invalid binnings. /// +/// ``` /// +-------+ +-------+ /// | | | | /// | | | | @@ -36,6 +37,7 @@ class Surface; /// | | +-------+ /// | | | | /// +-------+ +-------+ +/// ``` /// /// During resolution, it will consult each of it's children and return /// the result on the first surface where the lookup position is within diff --git a/Core/include/Acts/Geometry/CylinderContainerBlueprintNode.hpp b/Core/include/Acts/Geometry/CylinderContainerBlueprintNode.hpp new file mode 100644 index 00000000000..637f1594c0c --- /dev/null +++ b/Core/include/Acts/Geometry/CylinderContainerBlueprintNode.hpp @@ -0,0 +1,157 @@ +// 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/Geometry/BlueprintNode.hpp" +#include "Acts/Geometry/CylinderVolumeStack.hpp" +#include "Acts/Geometry/PortalShell.hpp" +#include "Acts/Utilities/BinningType.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include + +namespace Acts { + +/// This class handles the case of wrapping a set of cylinder-shaped children +/// and stacking them in a configured direction. +/// The stacking is done using @ref CylinderVolumeStack. +/// The container does not result in an extra volume in the hierarchy, as all +/// input volumes and any gap volumes produced are directly registered in the +/// volume of the parent of this node. +/// @note This node assumes all children produce only cylinder volumes! It throws +/// if this is not the case. +class CylinderContainerBlueprintNode final : public BlueprintNode { + public: + /// Main constructor for the cylinder container node. + /// @param name The name of the node (for debug only) + /// @param direction The stacking direction + /// @param attachmentStrategy The attachment strategy for the stack + /// @param resizeStrategy The resize strategy + /// @note The parameters are passed through to @ref CylinderVolumeStack, + /// see documentation of that class for more information + CylinderContainerBlueprintNode( + const std::string& name, BinningValue direction, + CylinderVolumeStack::AttachmentStrategy attachmentStrategy = + CylinderVolumeStack::AttachmentStrategy::Midpoint, + CylinderVolumeStack::ResizeStrategy resizeStrategy = + CylinderVolumeStack::ResizeStrategy::Expand); + + /// @copydoc BlueprintNode::name + const std::string& name() const override; + + /// This participates in the construction of the geometry via the blueprint + /// tree. The steps are approximately as follows: + /// -# Collect all child volumes + /// -# Package them into a @ref Acts::CylinderVolumeStack, which performs + /// sizing and/or gap creation + /// -# Return the @ref Acts::CylinderVolumeStack as a volume up the tree + /// + /// @param options The global blueprint options + /// @param gctx The geometry context (nominal usually) + /// @param logger The logger to use + /// @return The combined @ref Acts::CylinderVolumeStack + Volume& build(const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// This participates in the construction of the geometry via the blueprint + /// tree. The steps are approximately as follows: + /// -# Walk through all volumes that were created by the build phase + /// -# Check if they are: *real* child volumes or gap volumes + /// - If gap volume: produce a @ref Acts::TrackingVolume, and wrap it in a single use shell + /// - If child volume: locate the right child node it came from, call + /// ` connect` and collect the returned shell + /// -# Produce a combined @ref Acts::CylinderStackPortalShell from all the shells + /// -# Return that shell representation + /// + /// @param options The global blueprint options + /// @param gctx The geometry context (nominal usually) + /// @param logger The logger to use + /// @return The combined @ref Acts::CylinderStackPortalShell + CylinderStackPortalShell& connect( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// This participates in the construction of the geometry via the blueprint + /// tree. The steps are approximately as follows: + /// -# Register portals created for gap volumes, as they're not handled by + /// dedicated nodes + /// -# Register gap volumes in the @p parent volume + /// -# Create a configured @ref Acts::INavigationPolicy for the gap + /// -# Call `finalize` on all children while passing through @p parent. + /// + /// @param options The global blueprint options + /// @param gctx The geometry context (nominal usually) + /// @param parent The parent volume + /// @param logger The logger to use + void finalize(const BlueprintOptions& options, const GeometryContext& gctx, + TrackingVolume& parent, const Logger& logger) override; + + /// Setter for the stacking direction + /// @param direction The stacking direction + /// @return This node for chaining + CylinderContainerBlueprintNode& setDirection(BinningValue direction); + + /// Setter for the attachment strategy + /// @param attachmentStrategy The attachment strategy + /// @return This node for chaining + CylinderContainerBlueprintNode& setAttachmentStrategy( + CylinderVolumeStack::AttachmentStrategy attachmentStrategy); + + /// Setter for the resize strategy + /// @param resizeStrategy The resize strategy + /// @return This node for chaining + CylinderContainerBlueprintNode& setResizeStrategy( + CylinderVolumeStack::ResizeStrategy resizeStrategy); + + /// Accessor to the stacking direction + /// @return The stacking direction + BinningValue direction() const; + + /// Accessor to the attachment strategy + /// @return The attachment strategy + CylinderVolumeStack::AttachmentStrategy attachmentStrategy() const; + + /// Accessor to the resize strategy + /// @return The resize strategy + CylinderVolumeStack::ResizeStrategy resizeStrategy() const; + + private: + /// @copydoc BlueprintNode::addToGraphviz + void addToGraphviz(std::ostream& os) const override; + + /// Helper function to check if a volume was created as a gap volume. + /// @param volume The volume to check + /// @return True if the volume is a gap volume, false otherwise + bool isGapVolume(const Volume& volume) const; + + std::vector collectChildShells( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger); + + std::string m_name; + + BinningValue m_direction = BinningValue::binZ; + + CylinderVolumeStack::AttachmentStrategy m_attachmentStrategy{ + CylinderVolumeStack::AttachmentStrategy::Midpoint}; + + CylinderVolumeStack::ResizeStrategy m_resizeStrategy{ + CylinderVolumeStack::ResizeStrategy::Expand}; + + // Is only initialized during `build` + std::vector m_childVolumes; + std::unique_ptr m_stack{nullptr}; + std::map m_volumeToNode; + std::vector, + std::unique_ptr>> + m_gaps; + std::unique_ptr m_shell{nullptr}; +}; + +} // namespace Acts diff --git a/Core/include/Acts/Geometry/GridPortalLink.hpp b/Core/include/Acts/Geometry/GridPortalLink.hpp index 8bf1d06ee08..94e98867905 100644 --- a/Core/include/Acts/Geometry/GridPortalLink.hpp +++ b/Core/include/Acts/Geometry/GridPortalLink.hpp @@ -111,6 +111,7 @@ class GridPortalLink : public PortalLinkBase { /// /// 1D merge scenarios: /// + /// ``` /// +----------------------------------------------------------+ /// |Colinear | /// | | @@ -126,9 +127,11 @@ class GridPortalLink : public PortalLinkBase { /// | +-------+-------+-------+-------+-------+-------+ | /// | | /// +----------------------------------------------------------+ + /// ``` /// /// Two grid along a shared direction are merged along their shared direction /// + /// ``` /// +-------------------------------------------------+ /// |Parallel | /// | | @@ -147,10 +150,12 @@ class GridPortalLink : public PortalLinkBase { /// | +-------+ +-------+ +-------+-------+ | /// | | /// +-------------------------------------------------+ + /// ``` /// /// Two grids along a shared direction a merged in the direction that is /// orthogonal to their shared direction. /// + /// ``` /// +-------------------------------------------+ /// |Perpendicular | /// | | @@ -180,6 +185,7 @@ class GridPortalLink : public PortalLinkBase { /// | +-------+-------+-------+ | /// | | /// +-------------------------------------------+ + /// ``` /// /// Two grids whose directions are not shared are merged (ordering does not /// matter here). The routine will expand one of the grids to match the @@ -192,6 +198,7 @@ class GridPortalLink : public PortalLinkBase { /// side. The 1D grid is expanded to match the binning in the as-of-yet /// unbinned direction with the binning taken from the 2D grid. /// + /// ``` /// +-----------------------------------------+ /// |2D + 1D | /// | | @@ -215,7 +222,9 @@ class GridPortalLink : public PortalLinkBase { /// | | | | | /// | +-------+-------+ | /// +-----------------------------------------+ + /// ``` /// + /// ``` /// +--------------------------------------------------------------+ /// |2D + 1D | /// | | @@ -234,6 +243,7 @@ class GridPortalLink : public PortalLinkBase { /// | +-------+-------+ +-------+ +-------+-------+-------+ | /// | | /// +--------------------------------------------------------------+ + /// ``` /// /// 2D merges /// The grids need to already share a common axis. If that is not the case, @@ -241,6 +251,7 @@ class GridPortalLink : public PortalLinkBase { /// merging as a fallback if needed. /// Ordering and direction does not matter here. /// + /// ``` /// +-----------------------------------------+ /// |2D + 2D | /// | | @@ -273,6 +284,7 @@ class GridPortalLink : public PortalLinkBase { /// | +-------+-------+-------+-------+ | /// | | /// +-----------------------------------------+ + /// ``` /// /// @param a The first grid portal link /// @param b The second grid portal link diff --git a/Core/include/Acts/Geometry/LayerBlueprintNode.hpp b/Core/include/Acts/Geometry/LayerBlueprintNode.hpp new file mode 100644 index 00000000000..5eb2e747df8 --- /dev/null +++ b/Core/include/Acts/Geometry/LayerBlueprintNode.hpp @@ -0,0 +1,141 @@ +// 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/Geometry/StaticBlueprintNode.hpp" + +#include + +namespace Acts { + +/// The layer node is essentially an auto-sizing wrapper around a set of +/// surfaces. +/// @note This implementation is **preliminary** and will likely change +/// in the future. +/// It defers most of the functionality to @ref Acts::StaticBlueprintNode, +/// after the initial volume creation is completed. +/// +/// The layer volume is created to wrap around the surfaces registered with +/// this node. The orientation of the resulting volume defaults to the identity +/// matrix. If another orientation is desired, this can be set with the @ref +/// Acts::LayerBlueprintNode::setTransform. See @ref Acts::ProtoLayer for +/// details on the auto-sizing from surfaces. +/// +class LayerBlueprintNode : public StaticBlueprintNode { + public: + /// Enum that lists out the supported layer types. + enum class LayerType { + /// A cylinder layer + Cylinder, + + /// A disc layer + Disc, + + /// A plane layer + /// @note This is not yet implemented + Plane + }; + + /// Constructor for a layer node. + /// @param name The name of the layer + explicit LayerBlueprintNode(const std::string& name) + : StaticBlueprintNode{nullptr}, m_name(name) {} + + /// @copydoc BlueprintNode::name + const std::string& name() const override; + + /// This function participates in the geometry construction. + /// It will: + /// -# Analyze the surfaces provided and produce a wrapping volume + /// -# Register the surfaces with the volume + /// -# Return the volume + /// @note At least one surfaces needs to be registered via + /// @ref Acts::LayerBlueprintNode::setSurfaces before + /// geometry construction. + Volume& build(const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// Register a set of surfaces with the layer node. + /// @param surfaces The surfaces to register + /// @return Reference to this node for chaining + LayerBlueprintNode& setSurfaces( + std::vector> surfaces); + + /// Access the registered surfaces. + /// @return The registered surfaces + const std::vector>& surfaces() const; + + /// Set the transformation of the layer node. + /// This can be used to specifically orient the resulting layer volume. + /// @param transform The transformation to set + /// @return Reference to this node for chaining + LayerBlueprintNode& setTransform(const Transform3& transform); + + /// Access the transformation of the layer node. + /// @return The transformation + const Transform3& transform() const; + + /// Set the envelope of the layer node. This configures the amount of space to + /// add around the contained surfaces. + /// @param envelope The envelope to set + /// @return Reference to this node for chaining + LayerBlueprintNode& setEnvelope(const ExtentEnvelope& envelope); + + /// Access the envelope of the layer node. + /// @return The envelope + const ExtentEnvelope& envelope() const; + + /// Set the layer type of the layer node. + /// @param layerType The layer type to set + /// @return Reference to this node for chaining + LayerBlueprintNode& setLayerType(LayerType layerType); + + /// Access the layer type of the layer node. + /// @return The layer type + const LayerType& layerType() const; + + /// Output operator for the layer type enum. + /// @param os The output stream + /// @param type The layer type + friend std::ostream& operator<<(std::ostream& os, + LayerBlueprintNode::LayerType type) { + switch (type) { + using enum LayerBlueprintNode::LayerType; + case Cylinder: + os << "Cylinder"; + break; + case Disc: + os << "Disc"; + break; + case Plane: + os << "Plane"; + break; + } + return os; + } + + private: + /// @copydoc Acts::BlueprintNode::addToGraphviz + void addToGraphviz(std::ostream& os) const override; + + /// Helper method that performs the volume creation from the configured + /// surfaces. It converts from an @p extent object to an instance of @ref + /// Acts::VolumeBounds. + /// @param extent The extent to use for the volume creation + /// @param logger The logger to use + void buildVolume(const Extent& extent, const Logger& logger); + + std::string m_name; + std::vector> m_surfaces{}; + Transform3 m_transform = Transform3::Identity(); + ExtentEnvelope m_envelope = ExtentEnvelope::Zero(); + LayerType m_layerType = LayerType::Cylinder; +}; + +} // namespace Acts diff --git a/Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp b/Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp new file mode 100644 index 00000000000..dbb4663fe71 --- /dev/null +++ b/Core/include/Acts/Geometry/MaterialDesignatorBlueprintNode.hpp @@ -0,0 +1,101 @@ +// 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/Detector/ProtoBinning.hpp" +#include "Acts/Geometry/BlueprintNode.hpp" +#include "Acts/Geometry/PortalShell.hpp" + +#include + +namespace Acts { + +/// This node type registers material proxies into its child volume during the +/// blueprint construction. It is configured ahead of time which volume faces to +/// mark up, and how do to so. +/// @note This node can only have a single child. This is not an error during +/// tree building, but during geometry construction. +/// @note This currently only supports a cylinder volume child +class MaterialDesignatorBlueprintNode final : public BlueprintNode { + public: + // @TODO: This needs cuboid volume storage as well + // @TODO: I don't love the type + using BinningConfig = std::variant>>; + + /// Main constructor for the material designator node. + /// @param name The name of the node (for debug only) + explicit MaterialDesignatorBlueprintNode(const std::string& name) + : m_name(name) {} + + /// @copydoc BlueprintNode::name + const std::string& name() const override; + + /// @copydoc BlueprintNode::toStream + void toStream(std::ostream& os) const override; + + /// This method participates in the geometry construction. + /// It checks that this node only has a single child, is correctly configured, + /// and forwards the call. + /// @param options The global blueprint options + /// @param gctx The geometry context (nominal usually) + /// @param logger The logger to use + /// @return The child volume + Volume& build(const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// This method participates in the geometry construction. + /// It receives the populated portal shell from its only child and attaches + /// material proxies by consulting the configuration stored in the node. + /// @note Currently, this node will unconditionally attach + /// @ref Acts::ProtoGridSurfaceMaterial + /// + /// @param options The global blueprint options + /// @param gctx The geometry context (nominal usually) + /// @param logger The logger to use + /// @return The portal shell with material proxies attached + PortalShellBase& connect( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// This method participates in the geometry construction. + /// Passes through the call to its only child. + /// @param options The global blueprint options + /// @param gctx The geometry context (nominal usually) + /// @param parent The parent volume + /// @param logger The logger to use during construction + void finalize(const BlueprintOptions& options, const GeometryContext& gctx, + TrackingVolume& parent, const Logger& logger) override; + + /// Retrieve the binning configuration + /// @return The binning configuration + const std::optional& binning() const; + + /// Set the binning configuration + /// @param binning The binning configuration + MaterialDesignatorBlueprintNode& setBinning(BinningConfig binning); + + private: + /// @copydoc BlueprintNode::addToGraphviz + void addToGraphviz(std::ostream& os) const override; + + void handleCylinderBinning( + CylinderPortalShell& cylShell, + const std::vector< + std::tuple>& binning, + const Logger& logger); + + std::string m_name{}; + + std::optional m_binning{}; +}; + +} // namespace Acts diff --git a/Core/include/Acts/Geometry/Portal.hpp b/Core/include/Acts/Geometry/Portal.hpp index 722b433d036..3d1a065026f 100644 --- a/Core/include/Acts/Geometry/Portal.hpp +++ b/Core/include/Acts/Geometry/Portal.hpp @@ -110,6 +110,7 @@ class Portal { /// precision). The resulting portal will have one portal along the shared /// surface's normal vector, and one opposite that vector. /// + /// ``` /// portal1 portal2 /// +---+ +---+ /// | | | | @@ -118,6 +119,7 @@ class Portal { /// | | | | /// | | | | /// +---+ +---+ + /// ``` /// /// @note The input portals need to have compatible link loadaout, e.g. one /// portal needs to have the *along normal* slot filled, and the @@ -140,6 +142,7 @@ class Portal { /// relative to one another (e.g. one along one opposite), the function will /// throw an exception. /// + /// ``` /// ^ ^ /// | | /// portal1| portal2| @@ -149,6 +152,7 @@ class Portal { /// | | /// | | /// v v + /// ``` /// /// @note This is a destructive operation on both portals, their /// links will be moved to produce merged links, which can fail diff --git a/Core/include/Acts/Geometry/StaticBlueprintNode.hpp b/Core/include/Acts/Geometry/StaticBlueprintNode.hpp new file mode 100644 index 00000000000..f1b2b9cbd0a --- /dev/null +++ b/Core/include/Acts/Geometry/StaticBlueprintNode.hpp @@ -0,0 +1,67 @@ +// 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/Geometry/BlueprintNode.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/PortalShell.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" + +namespace Acts { + +/// The static blueprint node wraps a single already-constructred @c TrackingVolume. +/// The node will present this volume to its hierarchy. The volume is given as +/// mutable, and will be potentially enlarged in order to connect to neighboring +/// volumes. +/// - In case the volume already has child volumes, they will be retained. +/// - In case the volume already has a registered navigation policy, it will be +/// overwritten with the one configured on this node, regardless of content. +class StaticBlueprintNode : public BlueprintNode { + public: + /// Construct the static node from an existing volume + /// @param volume The volume to wrap + explicit StaticBlueprintNode(std::unique_ptr volume); + + /// Get the name of this node. It is automatically taken from the wrapped + /// volume + /// @return The name of the volume + const std::string& name() const override; + + /// @copydoc BlueprintNode::build + /// Build-phase of the blueprint construction. Returns the wrapped volume for + /// sizing. + Volume& build(const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// @copydoc BlueprintNode::connect + PortalShellBase& connect( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger = Acts::getDummyLogger()) override; + + /// @copydoc BlueprintNode::finalize + void finalize(const BlueprintOptions& options, const GeometryContext& gctx, + TrackingVolume& parent, + const Logger& logger = Acts::getDummyLogger()) override; + + virtual StaticBlueprintNode& setNavigationPolicyFactory( + std::shared_ptr navigationPolicyFactory); + + const NavigationPolicyFactory* navigationPolicyFactory() const; + + protected: + void addToGraphviz(std::ostream& os) const override; + + std::unique_ptr m_volume; + + std::unique_ptr m_shell; + + std::shared_ptr m_navigationPolicyFactory; +}; + +} // namespace Acts diff --git a/Core/include/Acts/Material/ProtoVolumeMaterial.hpp b/Core/include/Acts/Material/ProtoVolumeMaterial.hpp index 38206e8f6dc..dd302e31ccb 100644 --- a/Core/include/Acts/Material/ProtoVolumeMaterial.hpp +++ b/Core/include/Acts/Material/ProtoVolumeMaterial.hpp @@ -72,12 +72,11 @@ class ProtoVolumeMaterial : public IVolumeMaterial { Material m_material; }; -/// Return the material inline const Acts::Material Acts::ProtoVolumeMaterial::material( const Acts::Vector3& /*position*/) const { return m_material; } -/// Return the bin Utility + inline const Acts::BinUtility& Acts::ProtoVolumeMaterial::binUtility() const { return m_binUtility; } diff --git a/Core/include/Acts/Seeding/PathSeeder.hpp b/Core/include/Acts/Seeding/PathSeeder.hpp index 87ceda365c8..f619f1a0ab4 100644 --- a/Core/include/Acts/Seeding/PathSeeder.hpp +++ b/Core/include/Acts/Seeding/PathSeeder.hpp @@ -108,8 +108,6 @@ class PathSeeder { /// @param gctx The geometry context /// @param sourceLinkGridLookup The lookup table for the source links /// @param seedCollection The collection of seeds to fill - /// - /// @return The vector of seeds template void findSeeds(const GeometryContext& gctx, const std::unordered_map& diff --git a/Core/include/Acts/Utilities/Delegate.hpp b/Core/include/Acts/Utilities/Delegate.hpp index a0ef2cf7252..8cac473abb9 100644 --- a/Core/include/Acts/Utilities/Delegate.hpp +++ b/Core/include/Acts/Utilities/Delegate.hpp @@ -50,7 +50,6 @@ class Delegate { /// Alias of the return type using return_type = R; using holder_type = H; - /// Alias to the function pointer type this class will store using function_type = return_type (*)(const holder_type *, Args...); using function_ptr_type = return_type (*)(Args...); using signature_type = R(Args...); @@ -81,12 +80,14 @@ class Delegate { Delegate(const Delegate &) noexcept = default; Delegate &operator=(const Delegate &) noexcept = default; + /// @cond /// Constructor with an explicit runtime callable /// @param callable The runtime value of the callable /// @note The function signature requires the first argument of the callable is `const void*`. /// i.e. if the signature of the delegate is `void(int)`, the /// callable's signature has to be `void(const void*, int)`. explicit Delegate(function_type callable) { connect(callable); } + /// @endcond /// Constructor with a possibly stateful function object. /// @tparam Callable Type of the callable @@ -129,6 +130,7 @@ class Delegate { requires(isNoFunPtr::value) = delete; + /// @cond /// Assignment operator with an explicit runtime callable /// @param callable The runtime value of the callable /// @note The function signature requires the first argument of the callable is `const void*`. @@ -147,6 +149,7 @@ class Delegate { { connect(callable); } + /// @endcond /// Assignment operator from rvalue reference is deleted, should catch /// assignment from temporary objects and thus invalid pointers @@ -155,6 +158,7 @@ class Delegate { requires(isNoFunPtr::value) = delete; + /// @cond /// Connect a free function pointer. /// @note The function pointer must be ``constexpr`` for @c Delegate to accept it /// @tparam Callable The compile-time free function pointer @@ -175,6 +179,7 @@ class Delegate { return std::invoke(Callable, std::forward(args)...); }; } + /// @endcond /// Assignment operator with possibly stateful function object. /// @tparam Callable Type of the callable @@ -195,6 +200,7 @@ class Delegate { requires(isNoFunPtr::value) = delete; + /// @cond /// Connect anything that is assignable to the function pointer /// @param callable The runtime value of the callable /// @note The function signature requires the first argument of the callable is `const void*`. @@ -206,6 +212,7 @@ class Delegate { } m_function = callable; } + /// @endcond template void connect(function_type callable, const Type *instance) diff --git a/Core/src/Geometry/Blueprint.cpp b/Core/src/Geometry/Blueprint.cpp new file mode 100644 index 00000000000..f4531acaa2f --- /dev/null +++ b/Core/src/Geometry/Blueprint.cpp @@ -0,0 +1,157 @@ +// 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 "Acts/Geometry/Blueprint.hpp" + +#include "Acts/Geometry/CuboidVolumeBounds.hpp" +#include "Acts/Geometry/CylinderVolumeBounds.hpp" +#include "Acts/Geometry/Extent.hpp" +#include "Acts/Geometry/PortalShell.hpp" +#include "Acts/Geometry/VolumeBounds.hpp" +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Utilities/GraphViz.hpp" + +namespace { +const std::string s_rootName = "Root"; +} + +namespace Acts { + +Blueprint::Blueprint(const Config &config) : m_cfg(config) {} + +const std::string &Blueprint::name() const { + return s_rootName; +} + +Volume &Blueprint::build(const BlueprintOptions & /*options*/, + const GeometryContext & /*gctx*/, + const Logger & /*logger*/) { + throw std::logic_error("Root node cannot be built"); +} + +PortalShellBase &Blueprint::connect(const BlueprintOptions & /*options*/, + const GeometryContext & /*gctx*/, + const Logger & /*logger*/) { + throw std::logic_error("Root node cannot be connected"); +} + +void Blueprint::finalize(const BlueprintOptions & /*options*/, + const GeometryContext & /*gctx*/, + TrackingVolume & /*parent*/, + const Logger & /*logger*/) { + throw std::logic_error("Root node cannot be finalized"); +} + +void Blueprint::addToGraphviz(std::ostream &os) const { + GraphViz::Node node{ + .id = name(), .label = "World", .shape = GraphViz::Shape::House}; + + os << node; + BlueprintNode::addToGraphviz(os); +} + +std::unique_ptr Blueprint::construct( + const BlueprintOptions &options, const GeometryContext &gctx, + const Logger &logger) { + using enum BinningValue; + + ACTS_INFO(prefix() << "Building tracking geometry from blueprint tree"); + + options.validate(); + + if (m_cfg.envelope == ExtentEnvelope::Zero()) { + ACTS_WARNING(prefix() << "Root node is configured with zero envelope. This " + "might lead to navigation issues"); + } + + if (children().size() != 1) { + ACTS_ERROR(prefix() << "Root node must have exactly one child"); + throw std::logic_error("Root node must have exactly one child"); + } + + auto &child = children().at(0); + + ACTS_DEBUG(prefix() << "Executing building on tree"); + Volume &topVolume = child.build(options, gctx, logger); + const auto &bounds = topVolume.volumeBounds(); + + std::stringstream ss; + bounds.toStream(ss); + ACTS_DEBUG(prefix() << "have top volume: " << ss.str() << "\n" + << topVolume.transform().matrix()); + + std::shared_ptr worldBounds; + + if (const auto *cyl = dynamic_cast(&bounds); + cyl != nullptr) { + using enum CylinderVolumeBounds::BoundValues; + + // Make a copy that we'll modify + auto newBounds = std::make_shared(*cyl); + + const auto &zEnv = m_cfg.envelope[binZ]; + if (zEnv[0] != zEnv[1]) { + ACTS_ERROR( + prefix() << "Root node cylinder envelope for z must be symmetric"); + throw std::logic_error( + "Root node cylinder envelope for z must be " + "symmetric"); + } + + const auto &rEnv = m_cfg.envelope[binR]; + + newBounds->set({ + {eHalfLengthZ, newBounds->get(eHalfLengthZ) + zEnv[0]}, + {eMinR, std::max(0.0, newBounds->get(eMinR) - rEnv[0])}, + {eMaxR, newBounds->get(eMaxR) + rEnv[1]}, + }); + + worldBounds = std::move(newBounds); + + } else if (const auto *box = + dynamic_cast(&bounds); + box != nullptr) { + throw std::logic_error{"Not implemented"}; + } else { + throw std::logic_error{"Unsupported volume bounds type"}; + } + + ACTS_DEBUG(prefix() << "New root volume bounds are: " << *worldBounds); + + auto world = std::make_unique( + topVolume.transform(), std::move(worldBounds), "World"); + + // @TODO: This needs to become configurable + world->setNavigationPolicy( + options.defaultNavigationPolicyFactory->build(gctx, *world, logger)); + + // Need one-sided portal shell that connects outwards to nullptr + SingleCylinderPortalShell worldShell{*world}; + worldShell.applyToVolume(); + + auto &shell = child.connect(options, gctx, logger); + + shell.fill(*world); + + child.finalize(options, gctx, *world, logger); + + std::set> names; + + world->visitVolumes([&names, &logger, this](const auto *volume) { + if (names.contains(volume->volumeName())) { + ACTS_ERROR(prefix() << "Duplicate volume name: " << volume->volumeName()); + throw std::logic_error("Duplicate volume name"); + } + names.insert(volume->volumeName()); + }); + + return std::make_unique( + std::move(world), nullptr, m_cfg.geometryIdentifierHook, logger); +} + +} // namespace Acts diff --git a/Core/src/Geometry/BlueprintNode.cpp b/Core/src/Geometry/BlueprintNode.cpp new file mode 100644 index 00000000000..13c03495494 --- /dev/null +++ b/Core/src/Geometry/BlueprintNode.cpp @@ -0,0 +1,173 @@ +// 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 "Acts/Geometry/BlueprintNode.hpp" + +#include "Acts/Geometry/Blueprint.hpp" +#include "Acts/Geometry/CylinderContainerBlueprintNode.hpp" +#include "Acts/Geometry/LayerBlueprintNode.hpp" +#include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp" +#include "Acts/Geometry/StaticBlueprintNode.hpp" +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Navigation/TryAllNavigationPolicy.hpp" + +#include +#include + +namespace Acts { + +namespace { +bool hasDescendent(const BlueprintNode& descendent, + const BlueprintNode& ancestor) { + if (&descendent == &ancestor) { + return true; + } + + return std::ranges::any_of(ancestor.children(), + [&](const auto& child) -> bool { + return hasDescendent(descendent, child); + }); +} +} // namespace + +void BlueprintNode::toStream(std::ostream& os) const { + os << "BlueprintNode(" << name() << ")"; +} + +BlueprintNode& BlueprintNode::addChild(std::shared_ptr child) { + if (!child) { + throw std::invalid_argument("Child is nullptr"); + } + + if (dynamic_cast(child.get()) != nullptr) { + throw std::invalid_argument("Cannot add a Blueprint as a child"); + } + + if (child->depth() != 0) { + throw std::invalid_argument("Child has already been added to another node"); + } + + if (hasDescendent(*this, *child)) { + throw std::invalid_argument("Adding child would create a cycle"); + } + + child->setDepth(m_depth + 1); + m_children.push_back(std::move(child)); + return *this; +} + +BlueprintNode::MutableChildRange BlueprintNode::children() { + return MutableChildRange{m_children}; +} + +BlueprintNode::ChildRange BlueprintNode::children() const { + return ChildRange{m_children}; +} + +std::size_t BlueprintNode::depth() const { + return m_depth; +} + +void BlueprintNode::setDepth(std::size_t depth) { + m_depth = depth; + for (auto& child : children()) { + child.setDepth(depth + 1); + } +} + +std::string BlueprintNode::indent() const { + return std::string(m_depth * 2, ' '); +} + +std::string BlueprintNode::prefix() const { + return indent() + "[" + name() + "]: "; +} + +StaticBlueprintNode& BlueprintNode::addStaticVolume( + std::unique_ptr volume, + const std::function& callback) { + if (!volume) { + throw std::invalid_argument("Volume is nullptr"); + } + + auto child = std::make_shared(std::move(volume)); + addChild(child); + + if (callback) { + callback(*child); + } + return *child; +} + +StaticBlueprintNode& BlueprintNode::addStaticVolume( + const Transform3& transform, std::shared_ptr volumeBounds, + const std::string& volumeName, + const std::function& callback) { + return addStaticVolume(std::make_unique( + transform, std::move(volumeBounds), volumeName), + callback); +} + +CylinderContainerBlueprintNode& BlueprintNode::addCylinderContainer( + const std::string& name, BinningValue direction, + const std::function& + callback) { + auto cylinder = + std::make_shared(name, direction); + addChild(cylinder); + if (callback) { + callback(*cylinder); + } + return *cylinder; +} + +MaterialDesignatorBlueprintNode& BlueprintNode::addMaterial( + const std::string& name, + const std::function& + callback) { + auto material = std::make_shared(name); + addChild(material); + if (callback) { + callback(*material); + } + return *material; +} + +LayerBlueprintNode& BlueprintNode::addLayer( + const std::string& name, + const std::function& callback) { + auto layer = std::make_shared(name); + addChild(layer); + if (callback) { + callback(*layer); + } + return *layer; +} + +void BlueprintNode::clearChildren() { + for (auto& child : children()) { + child.setDepth(0); + } + m_children.clear(); +} + +void BlueprintNode::graphviz(std::ostream& os) const { + os << "digraph BlueprintNode {" << std::endl; + addToGraphviz(os); + os << "}" << std::endl; +} + +void BlueprintNode::addToGraphviz(std::ostream& os) const { + for (const auto& child : children()) { + os << indent() << "\"" << name() << "\" -> \"" << child.name() << "\";" + << std::endl; + child.addToGraphviz(os); + } +} + +} // namespace Acts diff --git a/Core/src/Geometry/BlueprintOptions.cpp b/Core/src/Geometry/BlueprintOptions.cpp new file mode 100644 index 00000000000..07913665861 --- /dev/null +++ b/Core/src/Geometry/BlueprintOptions.cpp @@ -0,0 +1,29 @@ +// 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 "Acts/Geometry/BlueprintOptions.hpp" + +#include "Acts/Geometry/NavigationPolicyFactory.hpp" +#include "Acts/Navigation/TryAllNavigationPolicy.hpp" + +namespace Acts { + +void BlueprintOptions::validate() const { + if (!defaultNavigationPolicyFactory) { + throw std::invalid_argument("Navigation policy factory is nullptr"); + } +} + +std::unique_ptr +BlueprintOptions::makeDefaultNavigationPolicyFactory() { + return NavigationPolicyFactory::make() + .add() + .asUniquePtr(); +} + +} // namespace Acts diff --git a/Core/src/Geometry/CMakeLists.txt b/Core/src/Geometry/CMakeLists.txt index 74f6e0e126d..189c4396112 100644 --- a/Core/src/Geometry/CMakeLists.txt +++ b/Core/src/Geometry/CMakeLists.txt @@ -43,4 +43,11 @@ target_sources( PortalLinkBase.cpp PortalError.cpp PortalShell.cpp + BlueprintNode.cpp + Blueprint.cpp + BlueprintOptions.cpp + CylinderContainerBlueprintNode.cpp + StaticBlueprintNode.cpp + LayerBlueprintNode.cpp + MaterialDesignatorBlueprintNode.cpp ) diff --git a/Core/src/Geometry/CylinderContainerBlueprintNode.cpp b/Core/src/Geometry/CylinderContainerBlueprintNode.cpp new file mode 100644 index 00000000000..284b30949ac --- /dev/null +++ b/Core/src/Geometry/CylinderContainerBlueprintNode.cpp @@ -0,0 +1,257 @@ +// 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 "Acts/Geometry/CylinderContainerBlueprintNode.hpp" + +#include "Acts/Geometry/BlueprintNode.hpp" +#include "Acts/Geometry/CylinderVolumeStack.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/PortalShell.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Utilities/GraphViz.hpp" +#include "Acts/Visualization/GeometryView3D.hpp" + +#include +#include + +namespace Acts { + +CylinderContainerBlueprintNode::CylinderContainerBlueprintNode( + const std::string& name, BinningValue direction, + CylinderVolumeStack::AttachmentStrategy attachmentStrategy, + CylinderVolumeStack::ResizeStrategy resizeStrategy) + : m_name(name), + m_direction(direction), + m_attachmentStrategy(attachmentStrategy), + m_resizeStrategy(resizeStrategy) {} + +const std::string& CylinderContainerBlueprintNode::name() const { + return m_name; +} + +Volume& CylinderContainerBlueprintNode::build(const BlueprintOptions& options, + const GeometryContext& gctx, + const Logger& logger) { + ACTS_DEBUG(prefix() << "cylinder container build (dir=" << m_direction + << ")"); + + if (m_stack != nullptr) { + ACTS_ERROR(prefix() << "Volume is already built"); + throw std::runtime_error("Volume is already built"); + } + + for (auto& child : children()) { + Volume& volume = child.build(options, gctx, logger); + m_childVolumes.push_back(&volume); + // We need to remember which volume we got from which child, so we can + // assemble a correct portal shell later + m_volumeToNode[&volume] = &child; + } + ACTS_VERBOSE(prefix() << "-> Collected " << m_childVolumes.size() + << " child volumes"); + + ACTS_VERBOSE(prefix() << "-> Building the stack"); + m_stack = std::make_unique(m_childVolumes, m_direction, + m_attachmentStrategy, + m_resizeStrategy, logger); + ACTS_DEBUG(prefix() << "-> Stack bounds are: " << m_stack->volumeBounds()); + + ACTS_DEBUG(prefix() << " *** build complete ***"); + + return *m_stack; +} + +std::vector +CylinderContainerBlueprintNode::collectChildShells( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger) { + std::vector shells; + ACTS_DEBUG(prefix() << "Have " << m_childVolumes.size() << " child volumes"); + for (Volume* volume : m_childVolumes) { + if (isGapVolume(*volume)) { + // We need to create a TrackingVolume from the gap and put it in the shell + auto gap = std::make_unique(*volume); + gap->setVolumeName(name() + "::Gap" + std::to_string(m_gaps.size() + 1)); + ACTS_DEBUG(prefix() << " ~> Gap volume (" << gap->volumeName() + << "): " << gap->volumeBounds()); + auto shell = std::make_unique(*gap); + assert(shell->isValid()); + shells.push_back(shell.get()); + + m_gaps.emplace_back(std::move(shell), std::move(gap)); + + } else { + // Figure out which child we got this volume from + auto it = m_volumeToNode.find(volume); + if (it == m_volumeToNode.end()) { + throw std::runtime_error("Volume not found in child volumes"); + } + BlueprintNode& child = *it->second; + + ACTS_DEBUG(prefix() << " ~> Child (" << child.name() + << ") volume: " << volume->volumeBounds()); + + auto* shell = dynamic_cast( + &child.connect(options, gctx, logger)); + if (shell == nullptr) { + ACTS_ERROR(prefix() + << "Child volume of cylinder stack is not a cylinder"); + throw std::runtime_error( + "Child volume of cylinder stack is not a cylinder"); + } + assert(shell->isValid()); + + shells.push_back(shell); + } + } + return shells; +} + +CylinderStackPortalShell& CylinderContainerBlueprintNode::connect( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger) { + ACTS_DEBUG(prefix() << "Cylinder container connect"); + if (m_stack == nullptr) { + ACTS_ERROR(prefix() << "Volume is not built"); + throw std::runtime_error("Volume is not built"); + } + + ACTS_DEBUG(prefix() << "Collecting child shells from " << children().size() + << " children"); + + // We have child volumes and gaps as bare Volumes in `m_childVolumes` after + // `build()` has completed. For the stack shell, we need TrackingVolumes in + // the right order. + + std::vector shells = + collectChildShells(options, gctx, logger); + + // Sanity checks + throw_assert(shells.size() == m_childVolumes.size(), + "Number of shells does not match number of child volumes"); + + throw_assert(std::ranges::none_of( + shells, [](const auto* shell) { return shell == nullptr; }), + "Invalid shell pointer"); + + throw_assert(std::ranges::all_of( + shells, [](const auto* shell) { return shell->isValid(); }), + "Invalid shell"); + + ACTS_DEBUG(prefix() << "Producing merged cylinder stack shell in " + << m_direction << " direction from " << shells.size() + << " shells"); + m_shell = std::make_unique(gctx, std::move(shells), + m_direction, logger); + + assert(m_shell != nullptr && "No shell was built at the end of connect"); + assert(m_shell->isValid() && "Shell is not valid at the end of connect"); + return *m_shell; +} + +void CylinderContainerBlueprintNode::finalize(const BlueprintOptions& options, + const GeometryContext& gctx, + TrackingVolume& parent, + const Logger& logger) { + ACTS_DEBUG(prefix() << "Finalizing cylinder container"); + + if (m_stack == nullptr) { + ACTS_ERROR(prefix() << "Volume is not built"); + throw std::runtime_error("Volume is not built"); + } + + if (m_shell == nullptr) { + ACTS_ERROR(prefix() << "Volume is not connected"); + throw std::runtime_error("Volume is not connected"); + } + + const auto* policyFactory = options.defaultNavigationPolicyFactory.get(); + + ACTS_DEBUG(prefix() << "Registering " << m_gaps.size() + << " gap volumes with parent"); + for (auto& [shell, gap] : m_gaps) { + auto* gapPtr = gap.get(); + parent.addVolume(std::move(gap)); + shell->applyToVolume(); + auto policy = policyFactory->build(gctx, *gapPtr, logger); + gapPtr->setNavigationPolicy(std::move(policy)); + } + + ACTS_DEBUG(prefix() << "Finalizing " << children().size() << " children"); + + for (auto& child : children()) { + child.finalize(options, gctx, parent, logger); + } +} + +bool CylinderContainerBlueprintNode::isGapVolume(const Volume& volume) const { + assert(m_stack != nullptr); + return std::ranges::any_of( + m_stack->gaps(), [&](const auto& gap) { return gap.get() == &volume; }); +} + +CylinderContainerBlueprintNode& CylinderContainerBlueprintNode::setDirection( + BinningValue direction) { + if (m_stack != nullptr) { + throw std::runtime_error("Cannot change direction after build"); + } + m_direction = direction; + return *this; +} + +CylinderContainerBlueprintNode& +CylinderContainerBlueprintNode::setAttachmentStrategy( + CylinderVolumeStack::AttachmentStrategy attachmentStrategy) { + if (m_stack != nullptr) { + throw std::runtime_error("Cannot change direction after build"); + } + m_attachmentStrategy = attachmentStrategy; + return *this; +} + +CylinderContainerBlueprintNode& +CylinderContainerBlueprintNode::setResizeStrategy( + CylinderVolumeStack::ResizeStrategy resizeStrategy) { + if (m_stack != nullptr) { + throw std::runtime_error("Cannot change direction after build"); + } + m_resizeStrategy = resizeStrategy; + return *this; +} + +void CylinderContainerBlueprintNode::addToGraphviz(std::ostream& os) const { + std::stringstream ss; + ss << "" + name() + ""; + ss << "
CylinderContainer"; + ss << "
dir: " << m_direction; + GraphViz::Node node{ + .id = name(), .label = ss.str(), .shape = GraphViz::Shape::DoubleOctagon}; + os << node << std::endl; + for (const auto& child : children()) { + os << indent() << GraphViz::Edge{{.id = name()}, {.id = child.name()}} + << std::endl; + child.addToGraphviz(os); + } +} + +BinningValue CylinderContainerBlueprintNode::direction() const { + return m_direction; +} + +CylinderVolumeStack::AttachmentStrategy +CylinderContainerBlueprintNode::attachmentStrategy() const { + return m_attachmentStrategy; +} + +CylinderVolumeStack::ResizeStrategy +CylinderContainerBlueprintNode::resizeStrategy() const { + return m_resizeStrategy; +} + +} // namespace Acts diff --git a/Core/src/Geometry/LayerBlueprintNode.cpp b/Core/src/Geometry/LayerBlueprintNode.cpp new file mode 100644 index 00000000000..de2e2cdb8cd --- /dev/null +++ b/Core/src/Geometry/LayerBlueprintNode.cpp @@ -0,0 +1,149 @@ +// 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 "Acts/Geometry/LayerBlueprintNode.hpp" + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Geometry/CuboidVolumeBounds.hpp" +#include "Acts/Geometry/CylinderVolumeBounds.hpp" +#include "Acts/Geometry/ProtoLayer.hpp" +#include "Acts/Geometry/VolumeBounds.hpp" +#include "Acts/Utilities/GraphViz.hpp" + +namespace Acts { + +Volume& LayerBlueprintNode::build(const BlueprintOptions& options, + const GeometryContext& gctx, + const Logger& logger) { + if (m_surfaces.empty()) { + ACTS_ERROR("LayerBlueprintNode: no surfaces provided"); + throw std::invalid_argument("LayerBlueprintNode: no surfaces provided"); + } + + ACTS_DEBUG(prefix() << "Building Layer " << name() << " from " + << m_surfaces.size() << " surfaces"); + ACTS_VERBOSE(prefix() << " -> layer type: " << m_layerType); + ACTS_VERBOSE(prefix() << " -> transform:\n" << m_transform.matrix()); + + Extent extent; + + ProtoLayer protoLayer{gctx, m_surfaces, m_transform.inverse()}; + ACTS_VERBOSE(prefix() << "Built proto layer: " << protoLayer); + + extent.addConstrain(protoLayer.extent, m_envelope); + + ACTS_VERBOSE(prefix() << " -> layer extent: " << extent); + + buildVolume(extent, logger); + assert(m_volume != nullptr && "Volume not built from proto layer"); + + for (auto& surface : m_surfaces) { + m_volume->addSurface(surface); + } + + return StaticBlueprintNode::build(options, gctx, logger); +} + +void LayerBlueprintNode::buildVolume(const Extent& extent, + const Logger& logger) { + ACTS_VERBOSE(prefix() << "Building volume for layer " << name()); + using enum BinningValue; + using enum LayerType; + + std::shared_ptr bounds; + switch (m_layerType) { + case Cylinder: + case Disc: { + double minR = extent.min(binR); + double maxR = extent.max(binR); + double hlZ = extent.interval(binZ) / 2.0; + bounds = std::make_shared(minR, maxR, hlZ); + break; + } + case Plane: { + double hlX = extent.interval(binX) / 2.0; + double hlY = extent.interval(binY) / 2.0; + double hlZ = extent.interval(binZ) / 2.0; + bounds = std::make_shared(hlX, hlY, hlZ); + break; + } + } + + assert(bounds != nullptr); + + ACTS_VERBOSE(prefix() << " -> bounds: " << *bounds); + + Transform3 transform = m_transform; + transform.translation() = + Vector3{extent.medium(binX), extent.medium(binY), extent.medium(binZ)}; + + ACTS_VERBOSE(prefix() << " -> adjusted transform:\n" << transform.matrix()); + + m_volume = + std::make_unique(transform, std::move(bounds), m_name); +} + +const std::string& LayerBlueprintNode::name() const { + return m_name; +} + +LayerBlueprintNode& LayerBlueprintNode::setSurfaces( + std::vector> surfaces) { + m_surfaces = std::move(surfaces); + return *this; +} + +const std::vector>& LayerBlueprintNode::surfaces() + const { + return m_surfaces; +} + +LayerBlueprintNode& LayerBlueprintNode::setTransform( + const Transform3& transform) { + m_transform = transform; + return *this; +} + +const Transform3& LayerBlueprintNode::transform() const { + return m_transform; +} + +LayerBlueprintNode& LayerBlueprintNode::setEnvelope( + const ExtentEnvelope& envelope) { + m_envelope = envelope; + return *this; +} + +const ExtentEnvelope& LayerBlueprintNode::envelope() const { + return m_envelope; +} + +LayerBlueprintNode& LayerBlueprintNode::setLayerType(LayerType layerType) { + m_layerType = layerType; + return *this; +} + +const LayerBlueprintNode::LayerType& LayerBlueprintNode::layerType() const { + return m_layerType; +} + +void LayerBlueprintNode::addToGraphviz(std::ostream& os) const { + std::stringstream ss; + ss << "" << name() << ""; + ss << "
"; + ss << m_layerType; + + GraphViz::Node node{ + .id = name(), .label = ss.str(), .shape = GraphViz::Shape::Diamond}; + + os << node; + + BlueprintNode::addToGraphviz(os); +} + +} // namespace Acts diff --git a/Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp b/Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp new file mode 100644 index 00000000000..0e89b65d6e3 --- /dev/null +++ b/Core/src/Geometry/MaterialDesignatorBlueprintNode.cpp @@ -0,0 +1,191 @@ +// 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 "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp" + +#include "Acts/Detector/ProtoBinning.hpp" +#include "Acts/Geometry/CylinderVolumeBounds.hpp" +#include "Acts/Geometry/Portal.hpp" +#include "Acts/Material/ProtoSurfaceMaterial.hpp" +#include "Acts/Surfaces/Surface.hpp" +#include "Acts/Utilities/GraphViz.hpp" +#include "Acts/Utilities/Helpers.hpp" + +namespace Acts { + +const std::string& MaterialDesignatorBlueprintNode::name() const { + return m_name; +} + +void MaterialDesignatorBlueprintNode::toStream(std::ostream& os) const { + os << "MaterialDesignatorBlueprintNode(" << name() << ")"; +} + +Volume& MaterialDesignatorBlueprintNode::build(const BlueprintOptions& options, + const GeometryContext& gctx, + const Logger& logger) { + if (children().size() != 1) { + ACTS_ERROR(prefix() << "MaterialDesignatorBlueprintNode must have exactly " + "one child, but has " + << children().size()); + throw std::runtime_error( + "MaterialDesignatorBlueprintNode must have exactly one child"); + } + + if (!m_binning) { + ACTS_ERROR(prefix() << "Binning is not set"); + throw std::runtime_error("Binning is not set"); + } + + return children().at(0).build(options, gctx, logger); +} + +void MaterialDesignatorBlueprintNode::handleCylinderBinning( + CylinderPortalShell& cylShell, + const std::vector< + std::tuple>& binning, + const Logger& logger) { + ACTS_DEBUG(prefix() << "Binning is set to compatible type"); + using enum CylinderVolumeBounds::Face; + + for (auto& [face, loc0, loc1] : binning) { + if (face == OuterCylinder || face == InnerCylinder) { + if (loc0.binValue != BinningValue::binRPhi) { + ACTS_ERROR(prefix() << "Binning is not in RPhi"); + throw std::runtime_error("Binning is not in RPhi"); + } + + if (loc1.binValue != BinningValue::binZ) { + ACTS_ERROR(prefix() << "Binning is not in Z"); + throw std::runtime_error("Binning is not in Z"); + } + } + + if (face == PositiveDisc || face == NegativeDisc) { + if (loc0.binValue != BinningValue::binR) { + ACTS_ERROR(prefix() << "Binning is not in R"); + throw std::runtime_error("Binning is not in R"); + } + if (loc1.binValue != BinningValue::binPhi) { + ACTS_ERROR(prefix() << "Binning is not in Phi"); + throw std::runtime_error("Binning is not in Phi"); + } + } + + Experimental::BinningDescription desc{.binning = {loc0, loc1}}; + ACTS_DEBUG(prefix() << "~> Assigning proto binning " << desc.toString() + << " to face " << face); + + auto material = std::make_shared(std::move(desc)); + + auto portal = cylShell.portal(face); + if (portal == nullptr) { + ACTS_ERROR(prefix() << "Portal is nullptr"); + throw std::runtime_error("Portal is nullptr"); + } + portal->surface().assignSurfaceMaterial(std::move(material)); + } +} + +PortalShellBase& MaterialDesignatorBlueprintNode::connect( + const BlueprintOptions& options, const GeometryContext& gctx, + const Logger& logger) { + ACTS_DEBUG(prefix() << "MaterialDesignatorBlueprintNode::connect"); + if (children().size() != 1) { + ACTS_ERROR(prefix() << "MaterialDesignatorBlueprintNode must have exactly " + "one child, but has " + << children().size()); + throw std::runtime_error( + "MaterialDesignatorBlueprintNode must have exactly one child"); + } + if (!m_binning) { + ACTS_ERROR(prefix() << "Binning is not set"); + throw std::runtime_error("Binning is not set"); + } + + auto& shell = children().at(0).connect(options, gctx, logger); + + ACTS_DEBUG(prefix() << "Received shell from child " + << children().at(0).name()); + + if (auto* cylShell = dynamic_cast(&shell)) { + ACTS_DEBUG(prefix() << "Connecting cylinder shell"); + + if (const auto* binning = std::get_if>>(&m_binning.value()); + binning != nullptr) { + handleCylinderBinning(*cylShell, *binning, logger); + } else { + ACTS_ERROR(prefix() << "Binning is set to unknown type"); + throw std::runtime_error("Unknown binning type"); + } + + } + // @TODO: Handle cuboid volume shell here + else { + ACTS_ERROR(prefix() << "Shell is not supported"); + throw std::runtime_error("Shell is not supported"); + } + + return shell; +} + +void MaterialDesignatorBlueprintNode::finalize(const BlueprintOptions& options, + const GeometryContext& gctx, + TrackingVolume& parent, + const Logger& logger) { + if (children().size() != 1) { + throw std::runtime_error( + "MaterialDesignatorBlueprintNode must have exactly one child"); + } + return children().at(0).finalize(options, gctx, parent, logger); +} + +void MaterialDesignatorBlueprintNode::addToGraphviz(std::ostream& os) const { + if (!m_binning) { + throw std::runtime_error("Binning is not set"); + } + + std::stringstream ss; + ss << "" + name() + ""; + ss << "
CylinderContainer"; + + std::visit( + overloaded{ + [&](const std::vector< + std::tuple>& binning) { + for (const auto& [face, loc0, loc1] : binning) { + ss << "
" << face; + ss << ": " << loc0.binValue << "=" << loc0.bins(); + ss << ", " << loc1.binValue << "=" << loc1.bins(); + } + }, + [](const auto& /*binning*/) { + // No output in all other cases + }}, + m_binning.value()); + os << GraphViz::Node{ + .id = name(), .label = ss.str(), .shape = GraphViz::Shape::Hexagon}; + BlueprintNode::addToGraphviz(os); +} + +const std::optional& +MaterialDesignatorBlueprintNode::binning() const { + return m_binning; +} + +MaterialDesignatorBlueprintNode& MaterialDesignatorBlueprintNode::setBinning( + BinningConfig binning) { + m_binning = std::move(binning); + return *this; +} + +} // namespace Acts diff --git a/Core/src/Geometry/PortalLinkBase.cpp b/Core/src/Geometry/PortalLinkBase.cpp index bdc65f8a552..f558fe19238 100644 --- a/Core/src/Geometry/PortalLinkBase.cpp +++ b/Core/src/Geometry/PortalLinkBase.cpp @@ -109,7 +109,7 @@ std::unique_ptr PortalLinkBase::merge( } else if (const auto* bTrivial = dynamic_cast(b.get()); bTrivial != nullptr) { - ACTS_VERBOSE( + ACTS_WARNING( "Merging a grid portal with a trivial portal (via composite)"); return std::make_unique(std::move(a), std::move(b), direction); @@ -128,7 +128,7 @@ std::unique_ptr PortalLinkBase::merge( aTrivial != nullptr) { if (const auto* bGrid = dynamic_cast(b.get()); bGrid) { - ACTS_VERBOSE( + ACTS_WARNING( "Merging a trivial portal with a grid portal (via composite)"); return std::make_unique(std::move(a), std::move(b), direction); @@ -141,7 +141,7 @@ std::unique_ptr PortalLinkBase::merge( direction); } else if (dynamic_cast(b.get()) != nullptr) { - ACTS_WARNING("Merging a trivial portal with a composite portal"); + ACTS_VERBOSE("Merging a trivial portal with a composite portal"); return std::make_unique(std::move(a), std::move(b), direction); @@ -156,12 +156,12 @@ std::unique_ptr PortalLinkBase::merge( direction); } else if (dynamic_cast(b.get()) != nullptr) { - ACTS_WARNING("Merging a composite portal with a trivial portal"); + ACTS_VERBOSE("Merging a composite portal with a trivial portal"); return std::make_unique(std::move(a), std::move(b), direction); } else if (dynamic_cast(b.get()) != nullptr) { - ACTS_WARNING("Merging two composite portals"); + ACTS_VERBOSE("Merging two composite portals"); return std::make_unique(std::move(a), std::move(b), direction); diff --git a/Core/src/Geometry/StaticBlueprintNode.cpp b/Core/src/Geometry/StaticBlueprintNode.cpp new file mode 100644 index 00000000000..12defdcc2eb --- /dev/null +++ b/Core/src/Geometry/StaticBlueprintNode.cpp @@ -0,0 +1,170 @@ +// 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 "Acts/Geometry/StaticBlueprintNode.hpp" + +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/PortalShell.hpp" +#include "Acts/Geometry/VolumeBounds.hpp" +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Utilities/GraphViz.hpp" +#include "Acts/Visualization/GeometryView3D.hpp" + +namespace Acts { + +StaticBlueprintNode::StaticBlueprintNode(std::unique_ptr volume) + : m_volume(std::move(volume)) {} + +Volume& StaticBlueprintNode::build(const BlueprintOptions& options, + const GeometryContext& gctx, + const Logger& logger) { + ACTS_DEBUG(prefix() << "static build"); + if (!m_volume) { + throw std::runtime_error("Volume is not built"); + } + + ACTS_DEBUG(prefix() << "Building volume (" << name() << ") with " + << children().size() << " children"); + for (auto& child : children()) { + child.build(options, gctx, logger); + } + + ACTS_DEBUG(prefix() << "-> returning volume " << *m_volume); + return *m_volume; +} + +PortalShellBase& StaticBlueprintNode::connect(const BlueprintOptions& options, + const GeometryContext& gctx, + const Logger& logger) { + ACTS_DEBUG(prefix() << "Static connect"); + if (m_volume == nullptr) { + throw std::runtime_error("Volume is not present"); + } + + ACTS_DEBUG(prefix() << "Connecting parent volume (" << name() << ") with " + << children().size() << " children"); + + for (auto& child : children()) { + auto& shell = child.connect(options, gctx, logger); + // Register ourselves on the outside of the shell + shell.fill(*m_volume); + } + + VolumeBounds::BoundsType type = m_volume->volumeBounds().type(); + if (type == VolumeBounds::eCylinder) { + m_shell = std::make_unique(*m_volume); + + } else if (type == VolumeBounds::eCuboid) { + throw std::logic_error("Cuboid is not implemented yet"); + + } else { + throw std::logic_error("Volume type is not supported"); + } + + assert(m_shell != nullptr && + "No shell was built at the end of StaticBlueprintNode::connect"); + assert(m_shell->isValid() && + "Shell is not valid at the end of StaticBlueprintNode::connect"); + return *m_shell; +} + +void StaticBlueprintNode::finalize(const BlueprintOptions& options, + const GeometryContext& gctx, + TrackingVolume& parent, + const Logger& logger) { + ACTS_DEBUG(prefix() << "Finalizing static volume"); + + if (!m_volume) { + ACTS_ERROR(prefix() << "Volume is not built"); + throw std::runtime_error("Volume is not built"); + } + + if (!m_shell) { + ACTS_ERROR(prefix() << "Shell is not built"); + throw std::runtime_error("Shell is not built"); + } + + for (auto& child : children()) { + child.finalize(options, gctx, *m_volume, logger); + } + + ACTS_DEBUG(prefix() << "Registering " << m_shell->size() + << " portals into volume " << m_volume->volumeName()); + m_shell->applyToVolume(); + + ACTS_DEBUG(prefix() << " Adding volume (" << m_volume->volumeName() + << ") to parent volume (" << parent.volumeName() << ")"); + + const auto* policyFactory = options.defaultNavigationPolicyFactory.get(); + + if (m_navigationPolicyFactory) { + policyFactory = m_navigationPolicyFactory.get(); + } + m_volume->setNavigationPolicy(policyFactory->build(gctx, *m_volume, logger)); + + parent.addVolume(std::move(m_volume)); +} + +const std::string& StaticBlueprintNode::name() const { + static const std::string uninitialized = "uninitialized"; + if (m_volume == nullptr) { + return uninitialized; + } + return m_volume->volumeName(); +} + +StaticBlueprintNode& StaticBlueprintNode::setNavigationPolicyFactory( + std::shared_ptr navigationPolicyFactory) { + m_navigationPolicyFactory = std::move(navigationPolicyFactory); + return *this; +} + +const NavigationPolicyFactory* StaticBlueprintNode::navigationPolicyFactory() + const { + return m_navigationPolicyFactory.get(); +} + +void StaticBlueprintNode::addToGraphviz(std::ostream& os) const { + std::stringstream ss; + ss << "" << name() << ""; + ss << "
"; + if (m_volume == nullptr) { + throw std::runtime_error("Volume is not built"); + } + switch (m_volume->volumeBounds().type()) { + case VolumeBounds::eCylinder: + ss << "Cylinder"; + break; + case VolumeBounds::eCuboid: + ss << "Cuboid"; + break; + case VolumeBounds::eCone: + ss << "Cone"; + break; + case VolumeBounds::eCutoutCylinder: + ss << "CutoutCylinder"; + break; + case VolumeBounds::eGenericCuboid: + ss << "GenericCuboid"; + break; + case VolumeBounds::eTrapezoid: + ss << "Trapezoid"; + break; + default: + ss << "Other"; + } + + GraphViz::Node node{ + .id = name(), .label = ss.str(), .shape = GraphViz::Shape::Rectangle}; + + os << node; + + BlueprintNode::addToGraphviz(os); +} + +} // namespace Acts diff --git a/Examples/Python/CMakeLists.txt b/Examples/Python/CMakeLists.txt index b7e7a5b4112..acca27e91f4 100644 --- a/Examples/Python/CMakeLists.txt +++ b/Examples/Python/CMakeLists.txt @@ -16,6 +16,7 @@ pybind11_add_module(ActsPythonBindings src/Material.cpp src/Geometry.cpp src/GeometryBuildingGen1.cpp + src/Blueprint.cpp src/ExampleAlgorithms.cpp src/MagneticField.cpp src/Output.cpp diff --git a/Examples/Python/src/Blueprint.cpp b/Examples/Python/src/Blueprint.cpp new file mode 100644 index 00000000000..f5ce4ddc3d0 --- /dev/null +++ b/Examples/Python/src/Blueprint.cpp @@ -0,0 +1,432 @@ +// 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 "Acts/Geometry/Blueprint.hpp" + +#include "Acts/Definitions/Units.hpp" +#include "Acts/Geometry/BlueprintNode.hpp" +#include "Acts/Geometry/CylinderContainerBlueprintNode.hpp" +#include "Acts/Geometry/CylinderVolumeStack.hpp" +#include "Acts/Geometry/LayerBlueprintNode.hpp" +#include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp" +#include "Acts/Geometry/StaticBlueprintNode.hpp" +#include "Acts/Navigation/NavigationStream.hpp" +#include "Acts/Plugins/Python/Utilities.hpp" +#include "Acts/Utilities/BinningType.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include +#include + +#include +#include +#include +#include + +namespace py = pybind11; +using namespace pybind11::literals; + +namespace Acts::Python { +namespace { +using std::uniform_real_distribution; + +// This is temporary! +void pseudoNavigation(const TrackingGeometry& trackingGeometry, + const GeometryContext& gctx, std::filesystem::path& path, + std::size_t runs, std::size_t substepsPerCm, + std::pair etaRange, + Logging::Level logLevel) { + using namespace Acts::UnitLiterals; + + ACTS_LOCAL_LOGGER(getDefaultLogger("pseudoNavigation", logLevel)); + + std::ofstream csv{path}; + csv << "x,y,z,volume,boundary,sensitive,material" << std::endl; + + std::mt19937 rnd{42}; + + std::uniform_real_distribution<> dist{-1, 1}; + std::uniform_real_distribution<> subStepDist{0.01, 0.99}; + + double thetaMin = 2 * std::atan(std::exp(-etaRange.first)); + double thetaMax = 2 * std::atan(std::exp(-etaRange.second)); + std::uniform_real_distribution<> thetaDist{thetaMin, thetaMax}; + + using namespace Acts::UnitLiterals; + + for (std::size_t run = 0; run < runs; run++) { + Vector3 position = Vector3::Zero(); + + double theta = thetaDist(rnd); + double phi = 2 * std::numbers::pi * dist(rnd); + + Vector3 direction; + direction[0] = std::sin(theta) * std::cos(phi); + direction[1] = std::sin(theta) * std::sin(phi); + direction[2] = std::cos(theta); + + ACTS_VERBOSE("start navigation " << run); + ACTS_VERBOSE("pos: " << position.transpose()); + ACTS_VERBOSE("dir: " << direction.transpose()); + ACTS_VERBOSE(direction.norm()); + + std::mt19937 rng{static_cast(run)}; + + const auto* volume = trackingGeometry.lowestTrackingVolume(gctx, position); + assert(volume != nullptr); + ACTS_VERBOSE(volume->volumeName()); + + NavigationStream main; + const TrackingVolume* currentVolume = volume; + + csv << run << "," << position[0] << "," << position[1] << "," + << position[2]; + csv << "," << volume->geometryId().volume(); + csv << "," << volume->geometryId().boundary(); + csv << "," << volume->geometryId().sensitive(); + csv << "," << 0; + csv << std::endl; + + ACTS_VERBOSE("start pseudo navigation"); + + auto writeIntersection = [&](const Vector3& pos, const Surface& surface) { + csv << run << "," << pos[0] << "," << pos[1] << "," << pos[2]; + csv << "," << surface.geometryId().volume(); + csv << "," << surface.geometryId().boundary(); + csv << "," << surface.geometryId().sensitive(); + csv << "," << (surface.surfaceMaterial() != nullptr ? 1 : 0); + csv << std::endl; + }; + + for (std::size_t i = 0; i < 100; i++) { + assert(currentVolume != nullptr); + main = NavigationStream{}; + + AppendOnlyNavigationStream navStream{main}; + currentVolume->initializeNavigationCandidates( + {.position = position, .direction = direction}, navStream, logger()); + + ACTS_VERBOSE(main.candidates().size() << " candidates"); + + for (const auto& candidate : main.candidates()) { + ACTS_VERBOSE(" -> " << candidate.surface().geometryId()); + ACTS_VERBOSE(" " << candidate.surface().toStream(gctx)); + } + + ACTS_VERBOSE("initializing candidates"); + main.initialize(gctx, {position, direction}, BoundaryTolerance::None()); + + ACTS_VERBOSE(main.candidates().size() << " candidates remaining"); + + for (const auto& candidate : main.candidates()) { + ACTS_VERBOSE(" -> " << candidate.surface().geometryId()); + ACTS_VERBOSE(" " << candidate.surface().toStream(gctx)); + } + + if (main.currentCandidate().surface().isOnSurface(gctx, position, + direction)) { + ACTS_VERBOSE( + "Already on surface at initialization, skipping candidate"); + + writeIntersection(position, main.currentCandidate().surface()); + + if (!main.switchToNextCandidate()) { + ACTS_WARNING("candidates exhausted unexpectedly"); + break; + } + } + + bool terminated = false; + while (main.remainingCandidates() > 0) { + const auto& candidate = main.currentCandidate(); + + ACTS_VERBOSE(candidate.portal); + ACTS_VERBOSE(candidate.intersection.position().transpose()); + + ACTS_VERBOSE("moving to position: " << position.transpose() << " (r=" + << VectorHelpers::perp(position) + << ")"); + + Vector3 delta = candidate.intersection.position() - position; + + std::size_t substeps = + std::max(1l, std::lround(delta.norm() / 10_cm * substepsPerCm)); + + for (std::size_t j = 0; j < substeps; j++) { + Vector3 subpos = position + subStepDist(rng) * delta; + csv << run << "," << subpos[0] << "," << subpos[1] << "," + << subpos[2]; + csv << "," << currentVolume->geometryId().volume(); + csv << ",0,0,0"; // zero boundary and sensitive ids + csv << std::endl; + } + + position = candidate.intersection.position(); + ACTS_VERBOSE(" -> " + << position.transpose() + << " (r=" << VectorHelpers::perp(position) << ")"); + + writeIntersection(position, candidate.surface()); + + if (candidate.portal != nullptr) { + ACTS_VERBOSE( + "On portal: " << candidate.portal->surface().toStream(gctx)); + currentVolume = + candidate.portal->resolveVolume(gctx, position, direction) + .value(); + + if (currentVolume == nullptr) { + ACTS_VERBOSE("switched to nullptr -> we're done"); + terminated = true; + } + break; + + } else { + ACTS_VERBOSE("Not on portal"); + } + + main.switchToNextCandidate(); + } + + if (terminated) { + ACTS_VERBOSE("Terminate pseudo navigation"); + break; + } + + ACTS_VERBOSE("switched to " << currentVolume->volumeName()); + + ACTS_VERBOSE("-----"); + } + } +} + +} // namespace + +void addBlueprint(Context& ctx) { + auto m = ctx.get("main"); + + auto blueprintNode = + py::class_>( + m, "BlueprintNode"); + + auto rootNode = + py::class_>( + m, "Blueprint"); + + rootNode + .def(py::init()) + // Return value needs to be shared pointer because python otherwise + // can't manage the lifetime + .def( + "construct", + [](Blueprint& self, const BlueprintOptions& options, + const GeometryContext& gctx, + Logging::Level level) -> std::shared_ptr { + return self.construct(options, gctx, + *getDefaultLogger("Blueprint", level)); + }, + py::arg("options"), py::arg("gctx"), + py::arg("level") = Logging::INFO); + + { + auto c = py::class_(rootNode, "Config").def(py::init()); + ACTS_PYTHON_STRUCT_BEGIN(c, Blueprint::Config); + ACTS_PYTHON_MEMBER(envelope); + ACTS_PYTHON_MEMBER(geometryIdentifierHook); + ACTS_PYTHON_STRUCT_END(); + } + + auto addContextManagerProtocol = [](class_& cls) { + using type = typename class_::type; + cls.def("__enter__", [](type& self) -> type& { return self; }) + .def("__exit__", [](type& /*self*/, const py::object& /*exc_type*/, + const py::object& /*exc_value*/, + const py::object& /*traceback*/) { + // No action needed on exit + }); + }; + + auto addNodeMethods = [&blueprintNode](const std::string& name, + auto&& callable, auto&&... args) { + blueprintNode.def(name.c_str(), callable, args...) + .def(("add" + name).c_str(), callable, args...); + }; + + blueprintNode + .def("__str__", + [](const BlueprintNode& self) { + std::stringstream ss; + ss << self; + return ss.str(); + }) + .def("addChild", &BlueprintNode::addChild) + .def_property_readonly("children", + py::overload_cast<>(&BlueprintNode::children)) + .def("clearChildren", &BlueprintNode::clearChildren) + .def_property_readonly("name", &BlueprintNode::name) + .def_property_readonly("depth", &BlueprintNode::depth) + .def("graphviz", [](BlueprintNode& self, const py::object& fh) { + std::stringstream ss; + self.graphviz(ss); + fh.attr("write")(ss.str()); + }); + + py::class_(m, "BlueprintOptions") + .def(py::init<>()) + .def_readwrite("defaultNavigationPolicyFactory", + &BlueprintOptions::defaultNavigationPolicyFactory); + + py::class_(blueprintNode, + "MutableChildRange") + .def( + "__iter__", + [](BlueprintNode::MutableChildRange& self) { + return py::make_iterator(self.begin(), self.end()); + }, + py::keep_alive<0, 1>()) + .def( + "__getitem__", + [](BlueprintNode::MutableChildRange& self, + int i) -> Acts::BlueprintNode& { + if (i < 0) { + i += self.size(); + } + return self.at(i); + }, + py::return_value_policy::reference_internal) + .def("__len__", [](const BlueprintNode::MutableChildRange& self) { + return self.size(); + }); + + auto staticNode = + py::class_>( + m, "StaticBlueprintNode") + .def(py::init([](const Transform3& transform, + const std::shared_ptr& bounds, + const std::string& name) { + return std::make_shared( + std::make_unique(transform, bounds, + name)); + }), + py::arg("transform"), py::arg("bounds"), + py::arg("name") = "undefined") + .def_property("navigationPolicyFactory", + &Acts::StaticBlueprintNode::navigationPolicyFactory, + &Acts::StaticBlueprintNode::setNavigationPolicyFactory); + + addContextManagerProtocol(staticNode); + + addNodeMethods( + "StaticVolume", + [](BlueprintNode& self, const Transform3& transform, + const std::shared_ptr& bounds, const std::string& name) { + auto node = std::make_shared( + std::make_unique(transform, bounds, name)); + self.addChild(node); + return node; + }, + py::arg("transform"), py::arg("bounds"), py::arg("name") = "undefined"); + + auto cylNode = + py::class_>( + m, "CylinderContainerBlueprintNode") + .def(py::init(), + py::arg("name"), py::arg("direction"), + py::arg("attachmentStrategy") = + CylinderVolumeStack::AttachmentStrategy::Gap, + py::arg("resizeStrategy") = + CylinderVolumeStack::ResizeStrategy::Gap) + .def_property( + "attachmentStrategy", + &Acts::CylinderContainerBlueprintNode::attachmentStrategy, + &Acts::CylinderContainerBlueprintNode::setAttachmentStrategy) + .def_property( + "resizeStrategy", + &Acts::CylinderContainerBlueprintNode::resizeStrategy, + &Acts::CylinderContainerBlueprintNode::setResizeStrategy) + .def_property("direction", + &Acts::CylinderContainerBlueprintNode::direction, + &Acts::CylinderContainerBlueprintNode::setDirection); + + addContextManagerProtocol(cylNode); + + addNodeMethods( + "CylinderContainer", + [](BlueprintNode& self, const std::string& name, BinningValue direction) { + auto cylinder = + std::make_shared(name, direction); + self.addChild(cylinder); + return cylinder; + }, + py::arg("name"), py::arg("direction")); + + auto matNode = + py::class_>( + m, "MaterialDesignatorBlueprintNode") + .def(py::init(), "name"_a) + .def_property("binning", &MaterialDesignatorBlueprintNode::binning, + &MaterialDesignatorBlueprintNode::setBinning); + + addContextManagerProtocol(matNode); + + addNodeMethods( + "Material", + [](BlueprintNode& self, const std::string& name) { + auto child = std::make_shared(name); + self.addChild(child); + return child; + }, + "name"_a); + + auto layerNode = + py::class_>( + m, "LayerBlueprintNode") + .def(py::init(), py::arg("name")) + .def_property_readonly("name", &Acts::LayerBlueprintNode::name) + .def_property("surfaces", &Acts::LayerBlueprintNode::surfaces, + &Acts::LayerBlueprintNode::setSurfaces) + .def_property("transform", &Acts::LayerBlueprintNode::transform, + &Acts::LayerBlueprintNode::setTransform) + .def_property("envelope", &Acts::LayerBlueprintNode::envelope, + &Acts::LayerBlueprintNode::setEnvelope) + .def_property("layerType", &Acts::LayerBlueprintNode::layerType, + &Acts::LayerBlueprintNode::setLayerType) + .def_property("navigationPolicyFactory", + &Acts::LayerBlueprintNode::navigationPolicyFactory, + &Acts::LayerBlueprintNode::setNavigationPolicyFactory); + + py::enum_(layerNode, "LayerType") + .value("Cylinder", Acts::LayerBlueprintNode::LayerType::Cylinder) + .value("Disc", Acts::LayerBlueprintNode::LayerType::Disc) + .value("Plane", Acts::LayerBlueprintNode::LayerType::Plane); + + addContextManagerProtocol(layerNode); + + addNodeMethods( + "Layer", + [](BlueprintNode& self, const std::string& name) { + auto child = std::make_shared(name); + self.addChild(child); + return child; + }, + py::arg("name")); + + // TEMPORARY + m.def("pseudoNavigation", &pseudoNavigation, "trackingGeometry"_a, "gctx"_a, + "path"_a, "runs"_a, "substepsPerCm"_a = 2, + "etaRange"_a = std::pair{-4.5, 4.5}, "logLevel"_a = Logging::INFO); +} + +} // namespace Acts::Python diff --git a/Examples/Python/src/Geometry.cpp b/Examples/Python/src/Geometry.cpp index 4c15e0dcf94..e9bbbfa350e 100644 --- a/Examples/Python/src/Geometry.cpp +++ b/Examples/Python/src/Geometry.cpp @@ -30,6 +30,7 @@ #include "Acts/Geometry/GeometryContext.hpp" #include "Acts/Geometry/GeometryHierarchyMap.hpp" #include "Acts/Geometry/GeometryIdentifier.hpp" +#include "Acts/Geometry/ProtoLayer.hpp" #include "Acts/Geometry/TrackingGeometry.hpp" #include "Acts/Geometry/Volume.hpp" #include "Acts/Geometry/VolumeBounds.hpp" @@ -91,6 +92,9 @@ struct IdentifierSurfacesCollector { } // namespace namespace Acts::Python { + +void addBlueprint(Context& ctx); + void addGeometry(Context& ctx) { auto m = ctx.get("main"); @@ -301,6 +305,8 @@ void addGeometry(Context& ctx) { .value("Gap", CylinderVolumeStack::ResizeStrategy::Gap) .value("Expand", CylinderVolumeStack::ResizeStrategy::Expand); } + + addBlueprint(ctx); } void addExperimentalGeometry(Context& ctx) { @@ -702,6 +708,15 @@ void addExperimentalGeometry(Context& ctx) { ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::VolumeAssociationTest, mex, "VolumeAssociationTest", name, ntests, randomNumbers, randomRange, detector); + + py::class_(m, "ProtoLayer") + .def(py::init>&, + const Transform3&>(), + "gctx"_a, "surfaces"_a, "transform"_a = Transform3::Identity()) + .def("min", &ProtoLayer::min, "bval"_a, "addenv"_a = true) + .def("max", &ProtoLayer::max, "bval"_a, "addenv"_a = true) + .def_property_readonly("surfaces", &ProtoLayer::surfaces); } } // namespace Acts::Python diff --git a/Examples/Python/tests/test_blueprint.py b/Examples/Python/tests/test_blueprint.py new file mode 100644 index 00000000000..8471bf52105 --- /dev/null +++ b/Examples/Python/tests/test_blueprint.py @@ -0,0 +1,80 @@ +import pytest + +import acts + +mm = acts.UnitConstants.mm +m = acts.UnitConstants.m +degree = acts.UnitConstants.degree + +bv = acts.BinningValue + +gctx = acts.GeometryContext() +logLevel = acts.logging.VERBOSE + + +def test_zdirection_container_blueprint(tmp_path): + + def write(root: acts.BlueprintNode, stage: int): + gz = tmp_path / f"blueprint_{stage}.dot" + print(gz) + with gz.open("w") as fh: + root.graphviz(fh) + + base = acts.Transform3.Identity() + + root = acts.Blueprint(envelope=acts.ExtentEnvelope(r=[10 * mm, 10 * mm])) + assert root.depth == 0 + + barrel = root.addCylinderContainer("Barrel", direction=bv.binR) + + assert barrel.depth == 1 + + r = 25 * mm + for i in range(1, 3): + r += 50 * mm + bounds = acts.CylinderVolumeBounds(r, r + 20 * mm, 200 * mm) + vol = barrel.addStaticVolume(base, bounds, name=f"Barrel_{i}") + assert vol.depth == 2 + + write(barrel, 1) + + root.clearChildren() + + assert barrel.depth == 0 + + det = root.addCylinderContainer("Detector", direction=bv.binZ) + + assert det.depth == 1 + + with det.CylinderContainer("nEC", direction=bv.binZ) as ec: + assert ec.depth == 2 + z = -200 + for i in range(1, 3): + z -= 200 * mm + bounds = acts.CylinderVolumeBounds(100 * mm, 150 * mm, 50 * mm) + + trf = base * acts.Translation3(acts.Vector3(0, 0, z)) + + vol = ec.addStaticVolume(trf, bounds, name=f"nEC_{i}") + assert vol.depth == 3 + + write(ec, 2) + + det.addChild(barrel) + assert barrel.depth == 2 + + write(det, 3) + + with det.CylinderContainer("pEC", direction=bv.binZ) as ec: + assert ec.depth == 2 + z = 200 + for i in range(1, 3): + z += 200 * mm + bounds = acts.CylinderVolumeBounds(100 * mm, 150 * mm, 50 * mm) + + trf = base * acts.Translation3(acts.Vector3(0, 0, z)) + + vol = ec.addStaticVolume(trf, bounds, name=f"pEC_{i}") + assert vol.depth == 3 + + write(root, 4) diff --git a/Examples/Scripts/Python/blueprint.py b/Examples/Scripts/Python/blueprint.py new file mode 100755 index 00000000000..0dca3a6629f --- /dev/null +++ b/Examples/Scripts/Python/blueprint.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 + +from pathlib import Path + +import acts + +mm = acts.UnitConstants.mm +degree = acts.UnitConstants.degree + +root = acts.Blueprint(envelope=acts.ExtentEnvelope(r=[10 * mm, 10 * mm])) + + +pixel = root.addCylinderContainer(direction=acts.BinningValue.binZ, name="Pixel") +print(repr(pixel)) + +trf = acts.Transform3.Identity() * acts.Translation3(acts.Vector3(0, 0, 0 * mm)) + + +if True: + barrel = acts.CylinderContainerBlueprintNode( + "PixelBarrel", + acts.BinningValue.binR, + attachmentStrategy=acts.CylinderVolumeStack.AttachmentStrategy.Gap, + resizeStrategy=acts.CylinderVolumeStack.ResizeStrategy.Gap, + ) + pixel.addChild(barrel) + + print("Barrel") + r = 25 * mm + for i in range(0, 4): + r += 50 * mm + bounds = acts.CylinderVolumeBounds(r, r + 20 * mm, 200 * mm) + print(bounds) + brlLayer = barrel.addStaticVolume(trf, bounds, name=f"PixelBarrelLayer{i}") + assert brlLayer.name == f"PixelBarrelLayer{i}" + + +if True: + + with pixel.CylinderContainer("PixelPosEndcap", acts.BinningValue.binZ) as ec: + print("Positive Endcap") + + ec.attachmentStrategy = acts.CylinderVolumeStack.AttachmentStrategy.Gap + ec.resizeStrategy = acts.CylinderVolumeStack.ResizeStrategy.Gap + + z = 200 + for i in range(0, 4): + z += 200 * mm + bounds = acts.CylinderVolumeBounds(100 * mm, 150 * mm, 50 * mm) + print(bounds) + + trf = acts.Transform3.Identity() * acts.Translation3(acts.Vector3(0, 0, z)) + + with ec.StaticVolume(trf, bounds, name=f"PixelPosEndcapDisk{i}") as disc: + print("Add disk", i) + + assert disc.name == f"PixelPosEndcapDisk{i}" + + +if True: + with pixel.Material() as mat: + with mat.CylinderContainer( + direction=acts.BinningValue.binZ, name="PixelNegEndcap" + ) as ec: + ec.attachmentStrategy = acts.CylinderVolumeStack.AttachmentStrategy.Gap + + print("Negative Endcap") + + z = -200 + for i in range(0, 4): + z -= 200 * mm + bounds = acts.CylinderVolumeBounds(200 * mm, 300 * mm, 50 * mm) + print(bounds) + + trf = acts.Transform3.Identity() * acts.Translation3( + acts.Vector3(0, 0, z) + ) + + with ec.StaticVolume( + trf, bounds, name=f"PixelNegEndcapDisk{i}" + ) as disk: + print("Add disk", i) + assert disk.name == f"PixelNegEndcapDisk{i}" + + +with open("blueprint.dot", "w") as fh: + root.graphviz(fh) + + +gctx = acts.GeometryContext() +trackingGeometry = root.construct( + options=acts.BlueprintNode.Options(), gctx=gctx, level=acts.logging.VERBOSE +) + +vis = acts.ObjVisualization3D() +trackingGeometry.visualize(vis, gctx) +with Path("blueprint.obj").open("w") as fh: + vis.write(fh) +# print("DONE") diff --git a/Tests/UnitTests/Core/Detector/CMakeLists.txt b/Tests/UnitTests/Core/Detector/CMakeLists.txt index eff298adb84..0c51456d7e4 100644 --- a/Tests/UnitTests/Core/Detector/CMakeLists.txt +++ b/Tests/UnitTests/Core/Detector/CMakeLists.txt @@ -1,4 +1,4 @@ -add_unittest(Blueprint BlueprintTests.cpp) +add_unittest(Gen2Blueprint BlueprintTests.cpp) add_unittest(BlueprintHelper BlueprintHelperTests.cpp) add_unittest(CylindricalContainerBuilder CylindricalContainerBuilderTests.cpp) add_unittest(CylindricalDetectorFromBlueprint CylindricalDetectorFromBlueprintTests.cpp) diff --git a/Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp b/Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp new file mode 100644 index 00000000000..737f410e24b --- /dev/null +++ b/Tests/UnitTests/Core/Geometry/BlueprintApiTests.cpp @@ -0,0 +1,409 @@ +// 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 +#include +#include + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Definitions/Units.hpp" +#include "Acts/Detector/ProtoBinning.hpp" +#include "Acts/Geometry/Blueprint.hpp" +#include "Acts/Geometry/CylinderContainerBlueprintNode.hpp" +#include "Acts/Geometry/CylinderVolumeBounds.hpp" +#include "Acts/Geometry/CylinderVolumeStack.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/LayerBlueprintNode.hpp" +#include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp" +#include "Acts/Geometry/TrackingGeometry.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" +#include "Acts/Navigation/INavigationPolicy.hpp" +#include "Acts/Navigation/NavigationStream.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Tests/CommonHelpers/DetectorElementStub.hpp" +#include "Acts/Utilities/Logger.hpp" +#include "Acts/Visualization/GeometryView3D.hpp" +#include "Acts/Visualization/ObjVisualization3D.hpp" + +#include +#include +#include + +using namespace Acts::UnitLiterals; + +namespace Acts::Test { + +auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::DEBUG); + +GeometryContext gctx; + +inline std::vector> makeFanLayer( + const Transform3& base, + std::vector>& elements, + double r = 300_mm, std::size_t nSensors = 8, double thickness = 0) { + auto recBounds = std::make_shared(40_mm, 60_mm); + + double deltaPhi = 2 * std::numbers::pi / nSensors; + std::vector> surfaces; + for (std::size_t i = 0; i < nSensors; i++) { + // Create a fan of sensors + + Transform3 trf = base * AngleAxis3{deltaPhi * i, Vector3::UnitZ()} * + Translation3(Vector3::UnitX() * r); + + if (i % 2 == 0) { + trf = trf * Translation3{Vector3::UnitZ() * 5_mm}; + } + + auto& element = elements.emplace_back( + std::make_unique(trf, recBounds, thickness)); + + element->surface().assignDetectorElement(*element); + + surfaces.push_back(element->surface().getSharedPtr()); + } + return surfaces; +} + +inline std::vector> makeBarrelLayer( + const Transform3& base, + std::vector>& elements, + double r = 300_mm, std::size_t nStaves = 10, int nSensorsPerStave = 8, + double thickness = 0, double hlPhi = 40_mm, double hlZ = 60_mm) { + auto recBounds = std::make_shared(hlPhi, hlZ); + + double deltaPhi = 2 * std::numbers::pi / nStaves; + std::vector> surfaces; + + for (std::size_t istave = 0; istave < nStaves; istave++) { + for (int isensor = -nSensorsPerStave; isensor <= nSensorsPerStave; + isensor++) { + double z = isensor * (2 * hlZ + 5_mm); + + Transform3 trf = base * Translation3(Vector3::UnitZ() * z) * + AngleAxis3{deltaPhi * istave, Vector3::UnitZ()} * + Translation3(Vector3::UnitX() * r) * + AngleAxis3{10_degree, Vector3::UnitZ()} * + AngleAxis3{90_degree, Vector3::UnitY()} * + AngleAxis3{90_degree, Vector3::UnitZ()}; + auto& element = elements.emplace_back( + std::make_unique(trf, recBounds, thickness)); + element->surface().assignDetectorElement(*element); + surfaces.push_back(element->surface().getSharedPtr()); + } + } + + return surfaces; +} + +BOOST_AUTO_TEST_SUITE(Geometry); + +BOOST_AUTO_TEST_SUITE(BlueprintApiTest); + +void pseudoNavigation(const TrackingGeometry& trackingGeometry, + Vector3 position, const Vector3& direction, + std::ostream& csv, std::size_t run, + std::size_t substepsPerCm, const Logger& logger) { + ACTS_VERBOSE("start navigation " << run); + ACTS_VERBOSE("dir: " << direction.transpose()); + ACTS_VERBOSE(direction.norm()); + + std::mt19937 rng{static_cast(run)}; + std::uniform_real_distribution<> dist{0.01, 0.99}; + + const auto* volume = trackingGeometry.lowestTrackingVolume(gctx, position); + BOOST_REQUIRE_NE(volume, nullptr); + ACTS_VERBOSE(volume->volumeName()); + + NavigationStream main; + const TrackingVolume* currentVolume = volume; + + csv << run << "," << position[0] << "," << position[1] << "," << position[2]; + csv << "," << volume->geometryId().volume(); + csv << "," << volume->geometryId().boundary(); + csv << "," << volume->geometryId().sensitive(); + csv << std::endl; + + ACTS_VERBOSE("start pseudo navigation"); + + for (std::size_t i = 0; i < 100; i++) { + main = NavigationStream{}; + AppendOnlyNavigationStream stream{main}; + + currentVolume->initializeNavigationCandidates( + {.position = position, .direction = direction}, stream, logger); + + ACTS_VERBOSE(main.candidates().size() << " candidates"); + + for (const auto& candidate : main.candidates()) { + ACTS_VERBOSE(" -> " << candidate.surface().geometryId()); + ACTS_VERBOSE(" " << candidate.surface().toStream(gctx)); + } + + ACTS_VERBOSE("initializing candidates"); + main.initialize(gctx, {position, direction}, BoundaryTolerance::None()); + + ACTS_VERBOSE(main.candidates().size() << " candidates remaining"); + + for (const auto& candidate : main.candidates()) { + ACTS_VERBOSE(" -> " << candidate.surface().geometryId()); + ACTS_VERBOSE(" " << candidate.surface().toStream(gctx)); + } + + if (main.currentCandidate().surface().isOnSurface(gctx, position, + direction)) { + ACTS_VERBOSE("Already on surface at initialization, skipping candidate"); + + auto id = main.currentCandidate().surface().geometryId(); + csv << run << "," << position[0] << "," << position[1] << "," + << position[2]; + csv << "," << id.volume(); + csv << "," << id.boundary(); + csv << "," << id.sensitive(); + csv << std::endl; + if (!main.switchToNextCandidate()) { + ACTS_WARNING("candidates exhausted unexpectedly"); + break; + } + } + + auto writeIntersection = [&](const Vector3& pos, const Surface& surface) { + csv << run << "," << pos[0] << "," << pos[1] << "," << pos[2]; + csv << "," << surface.geometryId().volume(); + csv << "," << surface.geometryId().boundary(); + csv << "," << surface.geometryId().sensitive(); + csv << std::endl; + }; + + bool terminated = false; + while (main.remainingCandidates() > 0) { + const auto& candidate = main.currentCandidate(); + + ACTS_VERBOSE(candidate.portal); + ACTS_VERBOSE(candidate.intersection.position().transpose()); + + ACTS_VERBOSE("moving to position: " << position.transpose() << " (r=" + << VectorHelpers::perp(position) + << ")"); + + Vector3 delta = candidate.intersection.position() - position; + + std::size_t substeps = + std::max(1l, std::lround(delta.norm() / 10_cm * substepsPerCm)); + + for (std::size_t j = 0; j < substeps; j++) { + // position += delta / (substeps + 1); + Vector3 subpos = position + dist(rng) * delta; + csv << run << "," << subpos[0] << "," << subpos[1] << "," << subpos[2]; + csv << "," << currentVolume->geometryId().volume(); + csv << ",0,0"; // zero boundary and sensitive ids + csv << std::endl; + } + + position = candidate.intersection.position(); + ACTS_VERBOSE(" -> " + << position.transpose() + << " (r=" << VectorHelpers::perp(position) << ")"); + + writeIntersection(position, candidate.surface()); + + if (candidate.portal != nullptr) { + ACTS_VERBOSE( + "On portal: " << candidate.portal->surface().toStream(gctx)); + currentVolume = + candidate.portal->resolveVolume(gctx, position, direction).value(); + + if (currentVolume == nullptr) { + ACTS_VERBOSE("switched to nullptr -> we're done"); + terminated = true; + } + break; + + } else { + ACTS_VERBOSE("Not on portal"); + } + + main.switchToNextCandidate(); + } + + if (terminated) { + ACTS_VERBOSE("Terminate pseudo navigation"); + break; + } + + ACTS_VERBOSE("switched to " << currentVolume->volumeName()); + + ACTS_VERBOSE("-----"); + } +} + +BOOST_AUTO_TEST_CASE(NodeApiTestContainers) { + // Transform3 base{AngleAxis3{30_degree, Vector3{1, 0, 0}}}; + Transform3 base{Transform3::Identity()}; + + std::vector> detectorElements; + auto makeFan = [&](const Transform3& layerBase, auto&&..., double r, + std::size_t nSensors, double thickness) { + return makeFanLayer(layerBase, detectorElements, r, nSensors, thickness); + }; + + Blueprint::Config cfg; + cfg.envelope[BinningValue::binZ] = {20_mm, 20_mm}; + cfg.envelope[BinningValue::binR] = {0_mm, 20_mm}; + auto root = std::make_unique(cfg); + + root->addMaterial("GlobalMaterial", [&](MaterialDesignatorBlueprintNode& + mat) { + Experimental::ProtoBinning zBinning{BinningValue::binZ, + AxisBoundaryType::Bound, 20}; + + Experimental::ProtoBinning rPhiBinning{BinningValue::binRPhi, + AxisBoundaryType::Bound, 20}; + + mat.setBinning(std::vector{std::tuple{ + CylinderVolumeBounds::Face::OuterCylinder, rPhiBinning, zBinning}}); + + mat.addCylinderContainer("Detector", BinningValue::binR, [&](auto& det) { + det.addCylinderContainer("Pixel", BinningValue::binZ, [&](auto& cyl) { + cyl.setAttachmentStrategy(CylinderVolumeStack::AttachmentStrategy::Gap) + .setResizeStrategy(CylinderVolumeStack::ResizeStrategy::Gap); + + cyl.addCylinderContainer( + "PixelNegativeEndcap", BinningValue::binZ, [&](auto& ec) { + ec.setAttachmentStrategy( + CylinderVolumeStack::AttachmentStrategy::Gap); + + auto makeLayer = [&](const Transform3& trf, auto& layer) { + std::vector> surfaces; + auto layerSurfaces = makeFan(trf, 300_mm, 10, 2_mm); + std::copy(layerSurfaces.begin(), layerSurfaces.end(), + std::back_inserter(surfaces)); + layerSurfaces = makeFan(trf, 500_mm, 16, 2_mm); + std::copy(layerSurfaces.begin(), layerSurfaces.end(), + std::back_inserter(surfaces)); + + layer.setSurfaces(surfaces) + .setLayerType(LayerBlueprintNode::LayerType::Disc) + .setEnvelope(ExtentEnvelope{{ + .z = {5_mm, 5_mm}, + .r = {10_mm, 20_mm}, + }}) + .setTransform(base); + }; + + ec.addLayer("PixelNeg1", [&](auto& layer) { + makeLayer(base * Translation3{Vector3{0, 0, -700_mm}}, layer); + }); + + ec.addLayer("PixelNeg2", [&](auto& layer) { + makeLayer(base * Translation3{Vector3{0, 0, -500_mm}}, layer); + }); + }); + + cyl.addCylinderContainer( + "PixelBarrel", BinningValue::binR, [&](auto& brl) { + brl.setAttachmentStrategy( + CylinderVolumeStack::AttachmentStrategy::Gap) + .setResizeStrategy(CylinderVolumeStack::ResizeStrategy::Gap); + + auto makeLayer = [&](const std::string& name, double r, + std::size_t nStaves, int nSensorsPerStave) { + brl.addLayer(name, [&](auto& layer) { + std::vector> surfaces = + makeBarrelLayer(base, detectorElements, r, nStaves, + nSensorsPerStave, 2.5_mm, 10_mm, 20_mm); + + layer.setSurfaces(surfaces) + .setLayerType(LayerBlueprintNode::LayerType::Cylinder) + .setEnvelope(ExtentEnvelope{{ + .z = {5_mm, 5_mm}, + .r = {1_mm, 1_mm}, + }}) + .setTransform(base); + }); + }; + + makeLayer("PixelLayer0", 30_mm, 18, 5); + makeLayer("PixelLayer1", 90_mm, 30, 6); + + brl.addStaticVolume(base, + std::make_shared( + 100_mm, 110_mm, 250_mm), + "PixelSupport"); + + makeLayer("PixelLayer2", 150_mm, 40, 7); + makeLayer("PixelLayer3", 250_mm, 70, 8); + }); + + auto& ec = + cyl.addCylinderContainer("PixelPosWrapper", BinningValue::binR); + ec.setResizeStrategy(CylinderVolumeStack::ResizeStrategy::Gap); + ec.addStaticVolume(std::make_unique( + base * Translation3{Vector3{0, 0, 600_mm}}, + std::make_shared(150_mm, 390_mm, 200_mm), + "PixelPositiveEndcap")); + }); + + det.addStaticVolume( + base, std::make_shared(0_mm, 23_mm, 1000_mm), + "BeamPipe"); + }); + }); + + std::ofstream dot{"api_test_container.dot"}; + root->graphviz(dot); + + auto trackingGeometry = root->construct({}, gctx, *logger); + + trackingGeometry->visitVolumes([&](const TrackingVolume* volume) { + std::cout << volume->volumeName() << std::endl; + std::cout << " -> id: " << volume->geometryId() << std::endl; + std::cout << " -> " << volume->portals().size() << " portals" << std::endl; + }); + + ObjVisualization3D vis; + + trackingGeometry->visualize(vis, gctx, {}, {}); + + vis.write("api_test_container.obj"); + + Vector3 position = Vector3::Zero(); + std::ofstream csv{"api_test_container.csv"}; + csv << "x,y,z,volume,boundary,sensitive" << std::endl; + + std::mt19937 rnd{42}; + + std::uniform_real_distribution<> dist{-1, 1}; + + double etaWidth = 3; + double thetaMin = 2 * std::atan(std::exp(-etaWidth)); + double thetaMax = 2 * std::atan(std::exp(etaWidth)); + std::uniform_real_distribution<> thetaDist{thetaMin, thetaMax}; + + using namespace Acts::UnitLiterals; + + for (std::size_t i = 0; i < 5000; i++) { + double theta = thetaDist(rnd); + double phi = 2 * std::numbers::pi * dist(rnd); + + Vector3 direction; + direction[0] = std::sin(theta) * std::cos(phi); + direction[1] = std::sin(theta) * std::sin(phi); + direction[2] = std::cos(theta); + + pseudoNavigation(*trackingGeometry, position, direction, csv, i, 2, + *logger->clone(std::nullopt, Logging::DEBUG)); + } +} + +BOOST_AUTO_TEST_SUITE_END(); + +BOOST_AUTO_TEST_SUITE_END(); + +} // namespace Acts::Test diff --git a/Tests/UnitTests/Core/Geometry/BlueprintTests.cpp b/Tests/UnitTests/Core/Geometry/BlueprintTests.cpp new file mode 100644 index 00000000000..c3a0ecc028f --- /dev/null +++ b/Tests/UnitTests/Core/Geometry/BlueprintTests.cpp @@ -0,0 +1,531 @@ +// 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 +#include +#include + +#include "Acts/Definitions/Units.hpp" +#include "Acts/Geometry/Blueprint.hpp" +#include "Acts/Geometry/BlueprintNode.hpp" +#include "Acts/Geometry/CylinderContainerBlueprintNode.hpp" +#include "Acts/Geometry/CylinderVolumeBounds.hpp" +#include "Acts/Geometry/CylinderVolumeStack.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Geometry/LayerBlueprintNode.hpp" +#include "Acts/Geometry/MaterialDesignatorBlueprintNode.hpp" +#include "Acts/Geometry/StaticBlueprintNode.hpp" +#include "Acts/Geometry/TrackingVolume.hpp" +#include "Acts/Material/BinnedSurfaceMaterial.hpp" +#include "Acts/Material/ProtoSurfaceMaterial.hpp" +#include "Acts/Surfaces/RectangleBounds.hpp" +#include "Acts/Tests/CommonHelpers/DetectorElementStub.hpp" +#include "Acts/Utilities/BinningType.hpp" +#include "Acts/Utilities/Logger.hpp" + +#include +#include +#include + +using namespace Acts::UnitLiterals; + +namespace Acts::Test { + +auto logger = Acts::getDefaultLogger("UnitTests", Acts::Logging::INFO); + +GeometryContext gctx; + +namespace { + +auto nameLookup(const TrackingGeometry& geo) { + return [&](const std::string& name) -> const TrackingVolume& { + const TrackingVolume* volume = nullptr; + + geo.visitVolumes([&](const TrackingVolume* v) { + if (v->volumeName() == name) { + volume = v; + } + }); + + if (volume == nullptr) { + throw std::runtime_error("Volume not found: " + name); + } + return *volume; + }; +} + +std::size_t countVolumes(const TrackingGeometry& geo) { + std::size_t nVolumes = 0; + geo.visitVolumes([&](const TrackingVolume* /*volume*/) { nVolumes++; }); + return nVolumes; +} + +} // namespace + +BOOST_AUTO_TEST_SUITE(Geometry); + +BOOST_AUTO_TEST_SUITE(BlueprintNodeTest); + +BOOST_AUTO_TEST_CASE(InvalidRoot) { + Logging::ScopedFailureThreshold threshold{Logging::Level::FATAL}; + + Blueprint::Config cfg; + Blueprint root{cfg}; + BOOST_CHECK_THROW(root.construct({}, gctx, *logger), std::logic_error); + + // More than one child is also invalid + auto cylBounds = std::make_shared(10_mm, 20_mm, 100_mm); + root.addChild( + std::make_unique(std::make_unique( + Transform3::Identity(), cylBounds, "child1"))); + root.addChild( + std::make_unique(std::make_unique( + Transform3::Identity(), cylBounds, "child2"))); + + BOOST_CHECK_THROW(root.construct({}, gctx, *logger), std::logic_error); +} + +class DummyNode : public BlueprintNode { + public: + explicit DummyNode(const std::string& name) : m_name(name) {} + + const std::string& name() const override { return m_name; } + + Volume& build(const BlueprintOptions& /*options*/, + const GeometryContext& /*gctx*/, + const Acts::Logger& /*logger*/) override { + throw std::logic_error("Not implemented"); + } + + PortalShellBase& connect(const BlueprintOptions& /*options*/, + const GeometryContext& /*gctx*/, + const Logger& /*logger */) override { + throw std::logic_error("Not implemented"); + } + + void finalize(const BlueprintOptions& /*options*/, + const GeometryContext& /*gctx*/, TrackingVolume& /*parent*/, + const Logger& /*logger*/) override { + throw std::logic_error("Not implemented"); + } + + private: + std::string m_name; +}; + +BOOST_AUTO_TEST_CASE(RootCannotBeChild) { + auto node = std::make_shared("node"); + Blueprint::Config cfg; + auto root = std::make_shared(cfg); + + BOOST_CHECK_THROW(node->addChild(root), std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(AddChildInvalid) { + auto node = std::make_shared("node"); + + // Add self + BOOST_CHECK_THROW(node->addChild(node), std::invalid_argument); + + // Add nullptr + BOOST_CHECK_THROW(node->addChild(nullptr), std::invalid_argument); + + auto nodeB = std::make_shared("nodeB"); + auto nodeC = std::make_shared("nodeC"); + + node->addChild(nodeB); + nodeB->addChild(nodeC); + BOOST_CHECK_THROW(nodeC->addChild(node), std::invalid_argument); + + // already has parent, can't be added as a child anywhere else + BOOST_CHECK_THROW(node->addChild(nodeC), std::invalid_argument); +} + +BOOST_AUTO_TEST_CASE(Depth) { + auto node1 = std::make_shared("node1"); + auto node2 = std::make_shared("node2"); + auto node3 = std::make_shared("node3"); + + BOOST_CHECK_EQUAL(node1->depth(), 0); + BOOST_CHECK_EQUAL(node2->depth(), 0); + BOOST_CHECK_EQUAL(node3->depth(), 0); + + node2->addChild(node3); + BOOST_CHECK_EQUAL(node2->depth(), 0); + BOOST_CHECK_EQUAL(node3->depth(), 1); + + node1->addChild(node2); + BOOST_CHECK_EQUAL(node1->depth(), 0); + BOOST_CHECK_EQUAL(node2->depth(), 1); + BOOST_CHECK_EQUAL(node3->depth(), 2); +} + +BOOST_AUTO_TEST_CASE(Static) { + Blueprint::Config cfg; + cfg.envelope[BinningValue::binZ] = {20_mm, 20_mm}; + cfg.envelope[BinningValue::binR] = {1_mm, 2_mm}; + Blueprint root{cfg}; + + double hlZ = 30_mm; + auto cylBounds = std::make_shared(10_mm, 20_mm, hlZ); + auto cyl = std::make_unique(Transform3::Identity(), cylBounds, + "child"); + + root.addStaticVolume(std::move(cyl)); + + BOOST_CHECK_EQUAL(root.children().size(), 1); + + auto tGeometry = root.construct({}, gctx, *logger); + + BOOST_REQUIRE(tGeometry); + + BOOST_CHECK_EQUAL(tGeometry->highestTrackingVolume()->volumes().size(), 1); + + BOOST_CHECK_EQUAL(countVolumes(*tGeometry), 2); + + auto lookup = nameLookup(*tGeometry); + auto actCyl = + dynamic_cast(lookup("child").volumeBounds()); + // Size as given + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 10_mm); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 20_mm); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), hlZ); + + auto worldCyl = + dynamic_cast(lookup("World").volumeBounds()); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 9_mm); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 22_mm); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), + hlZ + 20_mm); + + BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8); +} + +BOOST_AUTO_TEST_CASE(CylinderContainer) { + Blueprint::Config cfg; + cfg.envelope[BinningValue::binZ] = {20_mm, 20_mm}; + cfg.envelope[BinningValue::binR] = {2_mm, 20_mm}; + auto root = std::make_unique(cfg); + + auto& cyl = root->addCylinderContainer("Container", BinningValue::binZ); + cyl.setAttachmentStrategy(CylinderVolumeStack::AttachmentStrategy::Gap); + + double z0 = -200_mm; + double hlZ = 30_mm; + auto cylBounds = std::make_shared(10_mm, 20_mm, hlZ); + for (std::size_t i = 0; i < 3; i++) { + auto childCyl = std::make_unique( + Transform3::Identity() * + Translation3{Vector3{0, 0, z0 + i * 2 * hlZ * 1.2}}, + cylBounds, "child" + std::to_string(i)); + cyl.addStaticVolume(std::move(childCyl)); + } + + auto tGeometry = root->construct({}, gctx, *logger); + + // 4 real volumes + 2 gaps + BOOST_CHECK_EQUAL(countVolumes(*tGeometry), 6); + + auto lookup = nameLookup(*tGeometry); + auto worldCyl = + dynamic_cast(lookup("World").volumeBounds()); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 8_mm); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 40_mm); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 122_mm); + + BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8); + + for (std::size_t i = 0; i < 3; i++) { + auto actCyl = dynamic_cast( + lookup("child" + std::to_string(i)).volumeBounds()); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 10_mm); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 20_mm); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), hlZ); + } + + for (std::size_t i = 0; i < 2; i++) { + auto gapCyl = dynamic_cast( + lookup("Container::Gap" + std::to_string(i + 1)).volumeBounds()); + BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eMinR), 10_mm); + BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eMaxR), 20_mm); + BOOST_CHECK_EQUAL(gapCyl.get(CylinderVolumeBounds::eHalfLengthZ), 6_mm); + } +} + +BOOST_AUTO_TEST_CASE(Confined) { + Transform3 base{Transform3::Identity()}; + + Blueprint::Config cfg; + cfg.envelope[BinningValue::binZ] = {20_mm, 20_mm}; + cfg.envelope[BinningValue::binR] = {2_mm, 20_mm}; + auto root = std::make_unique(cfg); + + root->addStaticVolume( + base, std::make_shared(50_mm, 400_mm, 1000_mm), + "PixelWrapper", [&](auto& wrap) { + double rMin = 100_mm; + double rMax = 350_mm; + double hlZ = 100_mm; + + wrap.addStaticVolume( + base * Translation3{Vector3{0, 0, -600_mm}}, + std::make_shared(rMin, rMax, hlZ), + "PixelNeg1"); + + wrap.addStaticVolume( + base * Translation3{Vector3{0, 0, -200_mm}}, + std::make_shared(rMin, rMax, hlZ), + "PixelNeg2"); + + wrap.addStaticVolume( + base * Translation3{Vector3{0, 0, 200_mm}}, + std::make_shared(rMin, rMax, hlZ), + "PixelPos1"); + + wrap.addStaticVolume( + base * Translation3{Vector3{0, 0, 600_mm}}, + std::make_shared(rMin, rMax, hlZ), + "PixelPos2"); + }); + + auto trackingGeometry = root->construct({}, gctx, *logger); + + // overall dimensions are the wrapper volume + envelope + auto lookup = nameLookup(*trackingGeometry); + auto worldCyl = + dynamic_cast(lookup("World").volumeBounds()); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMinR), 48_mm); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eMaxR), 420_mm); + BOOST_CHECK_EQUAL(worldCyl.get(CylinderVolumeBounds::eHalfLengthZ), 1020_mm); + + // 4 outer portals and 4 inner + BOOST_CHECK_EQUAL(lookup("World").portals().size(), 8); + BOOST_CHECK_EQUAL(lookup("World").volumes().size(), 1); + + auto wrapperCyl = dynamic_cast( + lookup("PixelWrapper").volumeBounds()); + BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eMinR), 50_mm); + BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eMaxR), 400_mm); + BOOST_CHECK_EQUAL(wrapperCyl.get(CylinderVolumeBounds::eHalfLengthZ), + 1000_mm); + BOOST_CHECK_EQUAL(lookup("PixelWrapper").portals().size(), 4 + 4 * 4); + BOOST_CHECK_EQUAL(lookup("PixelWrapper").volumes().size(), 4); + + for (const auto& name : + {"PixelNeg1", "PixelNeg2", "PixelPos1", "PixelPos2"}) { + auto actCyl = + dynamic_cast(lookup(name).volumeBounds()); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMinR), 100_mm); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eMaxR), 350_mm); + BOOST_CHECK_EQUAL(actCyl.get(CylinderVolumeBounds::eHalfLengthZ), 100_mm); + BOOST_CHECK_EQUAL(lookup(name).portals().size(), 4); + } +} + +BOOST_AUTO_TEST_CASE(DiscLayer) { + double yrot = 45_degree; + Transform3 base = Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()}; + + std::vector> surfaces; + std::vector> elements; + double r = 300_mm; + std::size_t nSensors = 8; + double thickness = 2.5_mm; + auto recBounds = std::make_shared(40_mm, 60_mm); + + double deltaPhi = 2 * std::numbers::pi / nSensors; + for (std::size_t i = 0; i < nSensors; i++) { + // Create a fan of sensors + + Transform3 trf = base * AngleAxis3{deltaPhi * i, Vector3::UnitZ()} * + Translation3(Vector3::UnitX() * r); + + if (i % 2 == 0) { + trf = trf * Translation3{Vector3::UnitZ() * 5_mm}; + } + + auto& element = elements.emplace_back( + std::make_unique(trf, recBounds, thickness)); + + element->surface().assignDetectorElement(*element); + + surfaces.push_back(element->surface().getSharedPtr()); + } + + Blueprint root{{.envelope = ExtentEnvelope{{ + .z = {2_mm, 2_mm}, + .r = {3_mm, 5_mm}, + }}}}; + + root.addLayer("Layer0", [&](auto& layer) { + layer.setSurfaces(surfaces) + .setLayerType(LayerBlueprintNode::LayerType::Disc) + .setEnvelope(ExtentEnvelope{{ + .z = {0.1_mm, 0.1_mm}, + .r = {1_mm, 1_mm}, + }}) + .setTransform(base); + }); + + auto trackingGeometry = root.construct({}, gctx, *logger); + + std::size_t nSurfaces = 0; + + trackingGeometry->visitSurfaces([&](const Surface* surface) { + if (surface->associatedDetectorElement() != nullptr) { + nSurfaces++; + } + }); + + BOOST_CHECK_EQUAL(nSurfaces, surfaces.size()); + BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2); + auto lookup = nameLookup(*trackingGeometry); + auto layerCyl = dynamic_cast( + lookup("Layer0").volumeBounds()); + BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 258.9999999_mm, + 1e-6); + BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR), 346.25353003_mm, + 1e-6); + BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 3.85_mm, + 1e-6); +} + +BOOST_AUTO_TEST_CASE(CylinderLayer) { + double yrot = 0_degree; + Transform3 base = Transform3::Identity() * AngleAxis3{yrot, Vector3::UnitY()}; + + std::vector> surfaces; + std::vector> elements; + + double r = 300_mm; + std::size_t nStaves = 10; + int nSensorsPerStave = 8; + double thickness = 0; + double hlPhi = 40_mm; + double hlZ = 60_mm; + auto recBounds = std::make_shared(hlPhi, hlZ); + + double deltaPhi = 2 * std::numbers::pi / nStaves; + + for (std::size_t istave = 0; istave < nStaves; istave++) { + for (int isensor = -nSensorsPerStave; isensor <= nSensorsPerStave; + isensor++) { + double z = isensor * (2 * hlZ + 5_mm); + + Transform3 trf = base * Translation3(Vector3::UnitZ() * z) * + AngleAxis3{deltaPhi * istave, Vector3::UnitZ()} * + Translation3(Vector3::UnitX() * r) * + AngleAxis3{10_degree, Vector3::UnitZ()} * + AngleAxis3{90_degree, Vector3::UnitY()} * + AngleAxis3{90_degree, Vector3::UnitZ()}; + auto& element = elements.emplace_back( + std::make_unique(trf, recBounds, thickness)); + element->surface().assignDetectorElement(*element); + surfaces.push_back(element->surface().getSharedPtr()); + } + } + + Blueprint root{{.envelope = ExtentEnvelope{{ + .z = {2_mm, 2_mm}, + .r = {3_mm, 5_mm}, + }}}}; + + root.addLayer("Layer0", [&](auto& layer) { + layer.setSurfaces(surfaces) + .setLayerType(LayerBlueprintNode::LayerType::Cylinder) + .setEnvelope(ExtentEnvelope{{ + .z = {10_mm, 10_mm}, + .r = {20_mm, 10_mm}, + }}) + .setTransform(base); + }); + + auto trackingGeometry = root.construct({}, gctx, *logger); + + std::size_t nSurfaces = 0; + + trackingGeometry->visitSurfaces([&](const Surface* surface) { + if (surface->associatedDetectorElement() != nullptr) { + nSurfaces++; + } + }); + + BOOST_CHECK_EQUAL(nSurfaces, surfaces.size()); + BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2); + auto lookup = nameLookup(*trackingGeometry); + auto layerCyl = dynamic_cast( + lookup("Layer0").volumeBounds()); + BOOST_CHECK_EQUAL(lookup("Layer0").portals().size(), 4); + BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMinR), 275.6897761_mm, + 1e-6); + BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eMaxR), 319.4633358_mm, + 1e-6); + BOOST_CHECK_CLOSE(layerCyl.get(CylinderVolumeBounds::eHalfLengthZ), 1070_mm, + 1e-6); +} + +BOOST_AUTO_TEST_CASE(Material) { + Blueprint::Config cfg; + cfg.envelope[BinningValue::binZ] = {20_mm, 20_mm}; + cfg.envelope[BinningValue::binR] = {1_mm, 2_mm}; + Blueprint root{cfg}; + + double hlZ = 30_mm; + auto cylBounds = std::make_shared(10_mm, 20_mm, hlZ); + auto cyl = std::make_unique(Transform3::Identity(), cylBounds, + "child"); + + using enum BinningValue; + using enum CylinderVolumeBounds::Face; + using enum AxisBoundaryType; + + root.addMaterial("Material", [&](auto& mat) { + // @TODO: This API is not great + mat.setBinning(std::vector{ + std::tuple{NegativeDisc, Experimental::ProtoBinning{binR, Bound, 5}, + Experimental::ProtoBinning{binPhi, Bound, 10}}, + std::tuple{PositiveDisc, Experimental::ProtoBinning{binR, Bound, 15}, + Experimental::ProtoBinning{binPhi, Bound, 20}}, + }); + + mat.addStaticVolume(std::move(cyl)); + }); + + auto trackingGeometry = root.construct({}, gctx, *logger); + + BOOST_CHECK_EQUAL(countVolumes(*trackingGeometry), 2); + auto lookup = nameLookup(*trackingGeometry); + auto& child = lookup("child"); + + const auto* negDisc = child.portals().at(0).surface().surfaceMaterial(); + const auto* posDisc = child.portals().at(1).surface().surfaceMaterial(); + BOOST_CHECK_NE(negDisc, nullptr); + BOOST_CHECK_NE(posDisc, nullptr); + + const auto& negDiscMat = + dynamic_cast(*negDisc); + const auto& posDiscMat = + dynamic_cast(*posDisc); + + BOOST_CHECK_EQUAL(negDiscMat.binning().binning.at(0).bins(), 5); + BOOST_CHECK_EQUAL(negDiscMat.binning().binning.at(1).bins(), 10); + BOOST_CHECK_EQUAL(posDiscMat.binning().binning.at(0).bins(), 15); + BOOST_CHECK_EQUAL(posDiscMat.binning().binning.at(1).bins(), 20); + + for (std::size_t i = 2; i < child.portals().size(); i++) { + BOOST_CHECK_EQUAL(child.portals().at(i).surface().surfaceMaterial(), + nullptr); + } +} + +BOOST_AUTO_TEST_SUITE_END(); + +BOOST_AUTO_TEST_SUITE_END(); + +} // namespace Acts::Test diff --git a/Tests/UnitTests/Core/Geometry/CMakeLists.txt b/Tests/UnitTests/Core/Geometry/CMakeLists.txt index 0226e46d7d0..41c218129fe 100644 --- a/Tests/UnitTests/Core/Geometry/CMakeLists.txt +++ b/Tests/UnitTests/Core/Geometry/CMakeLists.txt @@ -35,3 +35,5 @@ add_unittest(CylinderVolumeStack CylinderVolumeStackTests.cpp) add_unittest(PortalLink PortalLinkTests.cpp) add_unittest(Portal PortalTests.cpp) add_unittest(PortalShell PortalShellTests.cpp) +add_unittest(Blueprint BlueprintTests.cpp) +add_unittest(BlueprintApi BlueprintApiTests.cpp) diff --git a/docs/core/geometry/concepts.md b/docs/core/geometry/concepts.md new file mode 100644 index 00000000000..abd2d31951a --- /dev/null +++ b/docs/core/geometry/concepts.md @@ -0,0 +1,75 @@ +# Concepts + +:::{todo} +Not complete yet +::: + +## Tracking geometry + +## Volume + +### Volume bounds + +## Tracking volume + +## Portals + +:::{doxygenclass} Acts::Portal +::: + +### Portal links + +:::{doxygenclass} Acts::PortalLinkBase +::: + +#### Trivial portal link + +:::{doxygenclass} Acts::TrivialPortalLink +::: + +#### Grid portal link + +:::{doxygenclass} Acts::GridPortalLink +::: + +:::{doxygenclass} Acts::GridPortalLinkT +::: + +#### Composite portal link + +:::{doxygenclass} Acts::CompositePortalLink +::: + +### Portal shells + +:::{doxygenclass} Acts::PortalShellBase +::: + +:::{doxygenclass} Acts::CylinderPortalShell +::: + +:::{doxygenclass} Acts::SingleCylinderPortalShell +::: + +:::{doxygenclass} Acts::CylinderStackPortalShell +::: + +### Navigation policy + +:::{doxygenclass} Acts::INavigationPolicy +::: + +:::{doxygenclass} Acts::MultiNavigationPolicyBase +::: + +:::{doxygenclass} Acts::MultiNavigationPolicy +::: + +:::{doxygenclass} Acts::SurfaceArrayNavigationPolicy +::: + +:::{doxygenclass} Acts::TryAllNavigationPolicy +::: + +:::{doxygenstruct} Acts::ProtoLayer +::: diff --git a/docs/core/geometry/construction.md b/docs/core/geometry/construction.md new file mode 100644 index 00000000000..5bcebdb24be --- /dev/null +++ b/docs/core/geometry/construction.md @@ -0,0 +1,43 @@ +# Construction + +:::{todo} +Not complete yet +::: + +## Blueprint tracking geometry construction + +:::{doxygenclass} Acts::BlueprintNode +::: + +:::{doxygenclass} Acts::Blueprint +::: + +### Container nodes + +:::{doxygenclass} Acts::CylinderContainerBlueprintNode +::: + +### Material nodes + +:::{doxygenclass} Acts::MaterialDesignatorBlueprintNode +::: + +### Geometry identification specification + +### *Layers* and other nodes + +:::{doxygenclass} Acts::StaticBlueprintNode +::: + +:::{doxygenclass} Acts::LayerBlueprintNode +::: + +## API + +### C++ API Example + +### Python API Examples + +### Plugin usage + +### Extension capabilities diff --git a/docs/core/geometry/index.md b/docs/core/geometry/index.md index b0009a36ef1..2bcb5cdec3f 100644 --- a/docs/core/geometry/index.md +++ b/docs/core/geometry/index.md @@ -1,4 +1,5 @@ (geometry_impl)= + # Geometry module The ACTS geometry model is strongly based on the ATLAS Tracking geometry. Its @@ -15,9 +16,11 @@ logical layers will be modelled as volumes, see [](layerless_geometry). :::{toctree} :maxdepth: 1 +concepts geometry_id material surfaces legacy/legacy +construction layerless/layerless ::: diff --git a/docs/core/geometry/layerless/layerless.md b/docs/core/geometry/layerless/layerless.md index b9784ac1f74..98e1624592a 100644 --- a/docs/core/geometry/layerless/layerless.md +++ b/docs/core/geometry/layerless/layerless.md @@ -1,13 +1,13 @@ (layerless_geometry)= + # Layerless geometry ## Geometry module rosetta stone -:::{todo} -Describe replacements of `TrackingGeometry`, `TrackingVolume` etc. and how the classes map to one another. +:::{note} +The combination of the original (Gen 1) geometry classes and the new *layerless* modelling (Gen 2, this page) will result in a combined Gen 3 geometry model. ::: - :::{toctree} building ::: diff --git a/docs/known-warnings.txt b/docs/known-warnings.txt index 2138dc11bb9..2c23b8a9319 100644 --- a/docs/known-warnings.txt +++ b/docs/known-warnings.txt @@ -5,5 +5,6 @@ .*Duplicate explicit target name: .* .*undefined label: .*class_acts_1_1_convex_polygon_bounds_3_01_polygon_dynamic_01_4.* .*undefined label: .*class_acts_1_1_grid_surface_material_t.* +.*undefined label: .*namespace_acts_.* # I think these are because we use specialization .*undefined label: .*bdt.*