From 8fae9ef160edd7feb2f1c624b572f45aa6e6db42 Mon Sep 17 00:00:00 2001 From: Benjamin Huth <37871400+benjaminhuth@users.noreply.github.com> Date: Fri, 11 Nov 2022 12:31:16 +0100 Subject: [PATCH 01/57] refactor: Large GSF refactoring (#1629) This is a major refactoring of the GSF. It changes the propagation structure, and the way the GSF records states to the `MultiTrajectory`. It also removes the smoothing code, since it is not really useful at the moment, as the component states are not exported anyways. should be merged after #1609 and #1627 and #1628 We should add performance monitoring in this or a later PR Fixes #1666 --- .../Acts/Propagator/MultiEigenStepperLoop.ipp | 15 +- .../Acts/Propagator/MultiStepperAborters.hpp | 27 ++ .../Acts/TrackFitting/GaussianSumFitter.hpp | 213 +++--------- Core/include/Acts/TrackFitting/GsfOptions.hpp | 2 + .../Acts/TrackFitting/detail/GsfActor.hpp | 323 +++++++++++------- .../Acts/TrackFitting/detail/GsfSmoothing.hpp | 304 ----------------- .../Acts/TrackFitting/detail/GsfUtils.hpp | 47 ++- .../detail/KLMixtureReduction.hpp | 1 + .../TrackFitting/GsfFitterFunction.hpp | 4 +- .../TrackFitting/src/GsfFitterFunction.cpp | 8 +- .../python/acts/examples/reconstruction.py | 1 + Examples/Python/src/TrackFitting.cpp | 6 +- Examples/Python/tests/root_file_hashes.txt | 8 +- .../Core/TrackFitting/FitterTestsCommon.hpp | 15 +- .../UnitTests/Core/TrackFitting/GsfTests.cpp | 26 +- 15 files changed, 347 insertions(+), 653 deletions(-) delete mode 100644 Core/include/Acts/TrackFitting/detail/GsfSmoothing.hpp diff --git a/Core/include/Acts/Propagator/MultiEigenStepperLoop.ipp b/Core/include/Acts/Propagator/MultiEigenStepperLoop.ipp index 71755e12fc5..91248f15ea9 100644 --- a/Core/include/Acts/Propagator/MultiEigenStepperLoop.ipp +++ b/Core/include/Acts/Propagator/MultiEigenStepperLoop.ipp @@ -114,15 +114,6 @@ Result MultiEigenStepperLoop::step( const auto& logger = state.options.logger; State& stepping = state.stepping; - // It is not possible to remove components from the vector, since the - // GSF actor relies on the fact that the ordering and number of - // components does not change - auto invalidateComponent = [](auto& cmp) { - cmp.status = Intersection3D::Status::missed; - cmp.weight = 0.0; - cmp.state.pars.template segment<3>(eFreeDir0) = Vector3::Zero(); - }; - // Lambda for reweighting the components auto reweight = [](auto& cmps) { ActsScalar sumOfWeights = 0.0; @@ -147,10 +138,11 @@ Result MultiEigenStepperLoop::step( m_stepLimitAfterFirstComponentOnSurface) { for (auto& cmp : stepping.components) { if (cmp.status != Intersection3D::Status::onSurface) { - invalidateComponent(cmp); + cmp.status = Intersection3D::Status::missed; } } + removeMissedComponents(stepping); reweight(stepping.components); ACTS_VERBOSE("Stepper performed " @@ -194,12 +186,13 @@ Result MultiEigenStepperLoop::step( accumulatedPathLength += component.weight * results.back()->value(); } else { ++errorSteps; - invalidateComponent(component); + component.status = Intersection3D::Status::missed; } } // Since we have invalidated some components, we need to reweight if (errorSteps > 0) { + removeMissedComponents(stepping); reweight(stepping.components); } diff --git a/Core/include/Acts/Propagator/MultiStepperAborters.hpp b/Core/include/Acts/Propagator/MultiStepperAborters.hpp index 3c28a7f5ac3..9cd7dbc2a6a 100644 --- a/Core/include/Acts/Propagator/MultiStepperAborters.hpp +++ b/Core/include/Acts/Propagator/MultiStepperAborters.hpp @@ -17,6 +17,16 @@ namespace Acts { struct MultiStepperSurfaceReached { MultiStepperSurfaceReached() = default; + /// If this is set, we are also happy if the mean of the components is on the + /// surface. How the averaging is performed depends on the stepper + /// implementation + bool averageOnSurface = true; + + /// A configurable tolerance within which distance to the intersection we + /// consider the surface as reached. Has no effect if averageOnSurface is + /// false + double averageOnSurfaceTolerance = 0.2; + /// boolean operator for abort condition without using the result /// /// @tparam propagator_state_t Type of the propagator state @@ -40,6 +50,7 @@ struct MultiStepperSurfaceReached { template bool operator()(propagator_state_t& state, const stepper_t& stepper, const Surface& targetSurface) const { + const auto& logger = state.options.logger; bool reached = true; const auto oldCurrentSurface = state.navigation.currentSurface; @@ -52,6 +63,22 @@ struct MultiStepperSurfaceReached { } } + // However, if mean of all is on surface, we are happy as well + if (averageOnSurface) { + const auto sIntersection = targetSurface.intersect( + state.geoContext, stepper.position(state.stepping), + state.stepping.navDir * stepper.direction(state.stepping), true); + + if (sIntersection.intersection.status == + Intersection3D::Status::onSurface or + sIntersection.intersection.pathLength < averageOnSurfaceTolerance) { + ACTS_VERBOSE("Reached target in average mode"); + state.navigation.currentSurface = &targetSurface; + state.navigation.targetReached = true; + return true; + } + } + // These values are changed by the single component aborters but must be // reset if not all components are on the target if (!reached) { diff --git a/Core/include/Acts/TrackFitting/GaussianSumFitter.hpp b/Core/include/Acts/TrackFitting/GaussianSumFitter.hpp index 5ae5924c674..9658c234a84 100644 --- a/Core/include/Acts/TrackFitting/GaussianSumFitter.hpp +++ b/Core/include/Acts/TrackFitting/GaussianSumFitter.hpp @@ -49,23 +49,23 @@ namespace Experimental { /// @tparam traj_t The MultiTrajectory type (backend) /// /// @note This GSF implementation tries to be as compatible to the KalmanFitter -/// as possible. However, there are certain differences at the moment: -/// * There is always a backward pass during fitting. -/// * There are only measurement states in the result -/// * Passed-again-surfaces is always empty at the moment -/// * Probably some more differences which I don't think of at the moment. +/// as possible. However, strict compatibility is not garantueed. +/// @note Currently there is no possibility to export the states of the +/// individual components from the GSF, the only information returned in the +/// MultiTrajectory are the means of the states. Therefore, also NO dedicated +/// component smoothing is performed as described e.g. by R. Fruewirth. template struct GaussianSumFitter { GaussianSumFitter(propagator_t&& propagator, bethe_heitler_approx_t&& bha) : m_propagator(std::move(propagator)), - m_bethe_heitler_approx(std::move(bha)) {} + m_betheHeitlerApproximation(std::move(bha)) {} /// The propagator instance used by the fit function propagator_t m_propagator; /// The fitter holds the instance of the bethe heitler approx - bethe_heitler_approx_t m_bethe_heitler_approx; + bethe_heitler_approx_t m_betheHeitlerApproximation; /// The navigator type using GsfNavigator = typename propagator_t::Navigator; @@ -98,7 +98,7 @@ struct GaussianSumFitter { propOptions.actionList.template get() .navSurfaces = sSequence; propOptions.actionList.template get() - .m_cfg.bethe_heitler_approx = &m_bethe_heitler_approx; + .m_cfg.bethe_heitler_approx = &m_betheHeitlerApproximation; return propOptions; }; @@ -121,7 +121,7 @@ struct GaussianSumFitter { propOptions.actionList.template get() .navSurfaces = std::move(backwardSequence); propOptions.actionList.template get() - .m_cfg.bethe_heitler_approx = &m_bethe_heitler_approx; + .m_cfg.bethe_heitler_approx = &m_betheHeitlerApproximation; return propOptions; }; @@ -152,7 +152,7 @@ struct GaussianSumFitter { opts.geoContext, opts.magFieldContext, logger); propOptions.setPlainOptions(opts.propagatorPlainOptions); propOptions.actionList.template get() - .m_cfg.bethe_heitler_approx = &m_bethe_heitler_approx; + .m_cfg.bethe_heitler_approx = &m_betheHeitlerApproximation; return propOptions; }; @@ -168,7 +168,7 @@ struct GaussianSumFitter { propOptions.setPlainOptions(opts.propagatorPlainOptions); propOptions.actionList.template get() - .m_cfg.bethe_heitler_approx = &m_bethe_heitler_approx; + .m_cfg.bethe_heitler_approx = &m_betheHeitlerApproximation; return propOptions; }; @@ -249,13 +249,10 @@ struct GaussianSumFitter { // Catch the actor and set the measurements auto& actor = fwdPropOptions.actionList.template get(); + actor.setOptions(options); actor.m_cfg.inputMeasurements = inputMeasurements; - actor.m_cfg.maxComponents = options.maxComponents; - actor.m_cfg.extensions = options.extensions; - actor.m_cfg.abortOnError = options.abortOnError; - actor.m_cfg.disableAllMaterialHandling = - options.disableAllMaterialHandling; actor.m_cfg.numberMeasurements = inputMeasurements.size(); + actor.m_cfg.inReversePass = false; fwdPropOptions.direction = gsfForward; @@ -276,8 +273,6 @@ struct GaussianSumFitter { if constexpr (not IsMultiParameters::value) { using Charge = typename IsMultiParameters::Charge; - r.parentTips.resize(1, MultiTrajectoryTraits::kInvalid); - MultiComponentBoundTrackParameters params( sParameters.referenceSurface().getSharedPtr(), sParameters.parameters(), sParameters.covariance()); @@ -285,9 +280,6 @@ struct GaussianSumFitter { return m_propagator.propagate(params, fwdPropOptions, std::move(inputResult)); } else { - r.parentTips.resize(sParameters.components().size(), - MultiTrajectoryTraits::kInvalid); - return m_propagator.propagate(sParameters, fwdPropOptions, std::move(inputResult)); } @@ -303,7 +295,7 @@ struct GaussianSumFitter { return return_error_or_abort(fwdGsfResult.result.error()); } - if (fwdGsfResult.processedStates == 0) { + if (fwdGsfResult.measurementStates == 0) { return return_error_or_abort(GsfError::NoStatesCreated); } @@ -323,12 +315,10 @@ struct GaussianSumFitter { auto bwdPropOptions = bwdPropInitializer(options, logger); auto& actor = bwdPropOptions.actionList.template get(); + actor.setOptions(options); actor.m_cfg.inputMeasurements = inputMeasurements; - actor.m_cfg.maxComponents = options.maxComponents; - actor.m_cfg.abortOnError = options.abortOnError; - actor.m_cfg.disableAllMaterialHandling = - options.disableAllMaterialHandling; - actor.m_cfg.extensions = options.extensions; + actor.m_cfg.inReversePass = true; + actor.setOptions(options); bwdPropOptions.direction = gsfBackward; @@ -357,57 +347,26 @@ struct GaussianSumFitter { r.fittedStates = trajectory; - // We take the last measurement state (filtered) from the forward result - // as the first measurement state in the backward result (predicted and - // filtered), so we can skip the Kalman update on the first surface as - // this would be redundant. We combine this with the construction of the - // propagation start parameters to ensure they are consistent. - std::vector> cmps; - std::shared_ptr surface; - - for (const auto idx : fwdGsfResult.lastMeasurementTips) { - // TODO This should not happen, but very rarely does. Maybe investigate - // later - if (fwdGsfResult.weightsOfStates.at(idx) == 0) { - continue; - } - - r.currentTips.push_back( - r.fittedStates->addTrackState(TrackStatePropMask::All)); - - auto proxy = r.fittedStates->getTrackState(r.currentTips.back()); - proxy.copyFrom(fwdGsfResult.fittedStates->getTrackState(idx)); - r.weightsOfStates[r.currentTips.back()] = - fwdGsfResult.weightsOfStates.at(idx); - - proxy.shareFrom(proxy, PM::Filtered, PM::Predicted); - - // Avoid accessing the surface for every component, since it should be - // the same - if (not surface) { - surface = proxy.referenceSurface().getSharedPtr(); - } - - cmps.push_back({fwdGsfResult.weightsOfStates.at(idx), proxy.filtered(), - proxy.filteredCovariance()}); - } + assert( + (fwdGsfResult.lastMeasurementTip != MultiTrajectoryTraits::kInvalid && + "tip is invalid")); - if (cmps.empty()) { - return ResultType{GsfError::NoComponentCreated}; - } + auto proxy = + r.fittedStates->getTrackState(fwdGsfResult.lastMeasurementTip); + proxy.filtered() = proxy.predicted(); + proxy.filteredCovariance() = proxy.predictedCovariance(); - r.visitedSurfaces.insert(surface->geometryId()); - r.parentTips = r.currentTips; + r.currentTip = fwdGsfResult.lastMeasurementTip; + r.visitedSurfaces.push_back(&proxy.referenceSurface()); r.measurementStates++; r.processedStates++; - const auto params = - MultiComponentBoundTrackParameters(surface, cmps); + const auto& params = *fwdGsfResult.lastMeasurementState; - return m_propagator - .template propagate( - params, target, bwdPropOptions, std::move(inputResult)); + return m_propagator.template propagate, + decltype(bwdPropOptions), + MultiStepperSurfaceReached>( + params, target, bwdPropOptions, std::move(inputResult)); }(); if (!bwdResult.ok()) { @@ -425,9 +384,9 @@ struct GaussianSumFitter { } //////////////////////////////////// - // Smooth and create Kalman Result + // Create Kalman Result //////////////////////////////////// - ACTS_VERBOSE("Gsf: Do smoothing"); + ACTS_VERBOSE("Gsf - States summary:"); ACTS_VERBOSE("- Fwd measurement states: " << fwdGsfResult.measurementStates << ", holes: " << fwdGsfResult.measurementHoles); @@ -435,101 +394,25 @@ struct GaussianSumFitter { << ", holes: " << bwdGsfResult.measurementHoles); - auto smoothResult = detail::smoothAndCombineTrajectories( - *fwdGsfResult.fittedStates, fwdGsfResult.currentTips, - fwdGsfResult.weightsOfStates, *bwdGsfResult.fittedStates, - bwdGsfResult.currentTips, bwdGsfResult.weightsOfStates, logger); - - // Cannot use structured binding since they cannot be captured in lambda - auto& kalmanResult = std::get<0>(smoothResult); - - // Some test - if (std::get<1>(smoothResult).empty()) { - return return_error_or_abort(GsfError::NoStatesCreated); - } - - // Compute the missed active surfaces as the union of the forward and - // backward pass missed active surfaces - // TODO this is quite expencive computationally, maybe just use from fwd? - { - auto fwdActSurf = fwdGsfResult.missedActiveSurfaces; - std::sort(fwdActSurf.begin(), fwdActSurf.end()); - - auto bwdActSurf = bwdGsfResult.missedActiveSurfaces; - std::sort(bwdActSurf.begin(), bwdActSurf.end()); - - std::vector missedActiveSurfaces; - std::set_union(fwdActSurf.begin(), fwdActSurf.end(), bwdActSurf.begin(), - bwdActSurf.end(), - std::back_inserter(missedActiveSurfaces)); - - kalmanResult.missedActiveSurfaces = missedActiveSurfaces; + // TODO should this be warning level? it happens quite often... Investigate! + if (bwdGsfResult.measurementStates != fwdGsfResult.measurementStates) { + ACTS_DEBUG("Fwd and bwd measuerement states do not match"); } - ////////////////////////////////////////////////////////////////// - // Propagate back to reference surface with smoothed parameters // - ////////////////////////////////////////////////////////////////// + KalmanFitterResult kalmanResult; if (options.referenceSurface) { - ACTS_VERBOSE("+-----------------------------------------------+"); - ACTS_VERBOSE("| Gsf: Do propagation back to reference surface |"); - ACTS_VERBOSE("+-----------------------------------------------+"); - auto lastResult = [&]() -> Result> { - const auto& [surface, lastSmoothedState] = - std::get<1>(smoothResult).front(); - - throw_assert( - detail::weightsAreNormalized( - lastSmoothedState, - [](const auto& tuple) { return std::get(tuple); }), - ""); - - const MultiComponentBoundTrackParameters params( - surface->getSharedPtr(), lastSmoothedState); - - auto lastPropOptions = bwdPropInitializer(options, logger); - - auto& actor = lastPropOptions.actionList.template get(); - actor.m_cfg.maxComponents = options.maxComponents; - actor.m_cfg.abortOnError = options.abortOnError; - actor.m_cfg.disableAllMaterialHandling = - options.disableAllMaterialHandling; - - lastPropOptions.direction = gsfBackward; - - typename propagator_t::template action_list_t_result_t< - BoundTrackParameters, decltype(lastPropOptions.actionList)> - inputResult; - - auto& r = inputResult.template get>(); - - r.fittedStates = trajectory; - r.parentTips.resize(params.components().size(), - MultiTrajectoryTraits::kInvalid); - - // Add the initial surface to the list of already visited surfaces, so - // that the material effects are not applied twice - r.visitedSurfaces.insert(surface->geometryId()); - - auto result = - m_propagator - .template propagate( - params, *options.referenceSurface, lastPropOptions, - std::move(inputResult)); - - if (!result.ok()) { - return result.error(); - } else { - return std::move(result->endParameters); - } - }(); - - if (!lastResult.ok()) { - return return_error_or_abort(lastResult.error()); - } - - kalmanResult.fittedParameters = **lastResult; + kalmanResult.fittedParameters = *bwdResult->endParameters; } + kalmanResult.fittedStates = fwdGsfResult.fittedStates; + kalmanResult.lastTrackIndex = fwdGsfResult.currentTip; + kalmanResult.lastMeasurementIndex = fwdGsfResult.lastMeasurementTip; + kalmanResult.smoothed = false; + kalmanResult.finished = true; + kalmanResult.reversed = true; + kalmanResult.measurementStates = fwdGsfResult.measurementStates; + kalmanResult.measurementHoles = fwdGsfResult.measurementHoles; + kalmanResult.processedStates = fwdGsfResult.processedStates; + kalmanResult.missedActiveSurfaces = fwdGsfResult.missedActiveSurfaces; return kalmanResult; } diff --git a/Core/include/Acts/TrackFitting/GsfOptions.hpp b/Core/include/Acts/TrackFitting/GsfOptions.hpp index 3c85343a4db..3b40d36e932 100644 --- a/Core/include/Acts/TrackFitting/GsfOptions.hpp +++ b/Core/include/Acts/TrackFitting/GsfOptions.hpp @@ -70,6 +70,8 @@ struct GsfOptions { std::size_t maxComponents = 4; + double weightCutoff = 1.e-4; + bool abortOnError = true; bool disableAllMaterialHandling = false; diff --git a/Core/include/Acts/TrackFitting/detail/GsfActor.hpp b/Core/include/Acts/TrackFitting/detail/GsfActor.hpp index 58594a7935f..61aebd851de 100644 --- a/Core/include/Acts/TrackFitting/detail/GsfActor.hpp +++ b/Core/include/Acts/TrackFitting/detail/GsfActor.hpp @@ -19,7 +19,6 @@ #include "Acts/TrackFitting/GsfError.hpp" #include "Acts/TrackFitting/GsfOptions.hpp" #include "Acts/TrackFitting/KalmanFitter.hpp" -#include "Acts/TrackFitting/detail/GsfSmoothing.hpp" #include "Acts/TrackFitting/detail/GsfUtils.hpp" #include "Acts/TrackFitting/detail/KLMixtureReduction.hpp" #include "Acts/TrackFitting/detail/KalmanUpdateHelpers.hpp" @@ -37,29 +36,24 @@ struct GsfResult { /// The multi-trajectory which stores the graph of components std::shared_ptr fittedStates; - /// This provides the weights for the states in the MultiTrajectory. Each - /// entry maps to one track state. TODO This is a workaround until the - /// MultiTrajectory can handle weights - std::map weightsOfStates; + /// The current top index of the MultiTrajectory + MultiTrajectoryTraits::IndexType currentTip = MultiTrajectoryTraits::kInvalid; - /// The current indexes for the newest components in the multi trajectory - /// (this includes material, hole and outlier states) - std::vector currentTips; + /// The last tip referring to a measurement state in the MultiTrajectory + MultiTrajectoryTraits::IndexType lastMeasurementTip = + MultiTrajectoryTraits::kInvalid; - /// The last tips referring to a measuerement state so we do not need so - /// search them recursively later - std::vector lastMeasurementTips; - - /// We must capture the parent tips to ensure that we can keep track of the - /// last states in the multitrajectory after the component convolution and - /// reduction - std::vector parentTips; + /// The last multi-component measurement state. Used to initialize the + /// backward pass. + std::optional> + lastMeasurementState; /// Some counting std::size_t measurementStates = 0; std::size_t measurementHoles = 0; std::size_t processedStates = 0; - std::set visitedSurfaces; + + std::vector visitedSurfaces; std::vector missedActiveSurfaces; // Propagate potential errors to the outside @@ -108,6 +102,11 @@ struct GsfActor { /// The extensions Experimental::GsfExtensions extensions; + + /// Wether we are in the reverse pass or not. This is more reliable than + /// checking the navigation direction, because in principle the fitter can + /// be started backwards in the first pass + bool inReversePass = false; } m_cfg; /// Stores meta information about the components @@ -133,6 +132,12 @@ struct GsfActor { std::optional boundCov; }; + struct TemporaryStates { + traj_t traj; + std::vector tips; + std::map weights; + }; + /// Broadcast Cache Type using ComponentCache = std::tuple; @@ -150,8 +155,14 @@ struct GsfActor { assert(result.fittedStates && "No MultiTrajectory set"); const auto& logger = state.options.logger; + // Return is we found an error earlier + if (not result.result.ok()) { + ACTS_WARNING("result.result not ok, return!") + return; + } + // Set error or abort utility - auto set_error_or_abort = [&](auto error) { + auto setErrorOrAbort = [&](auto error) { if (m_cfg.abortOnError) { std::abort(); } else { @@ -184,18 +195,10 @@ struct GsfActor { const detail::ScopedGsfInfoPrinterAndChecker printer(state, stepper, missed_count); - if (result.parentTips.size() != stepper.numberComponents(state.stepping)) { - ACTS_ERROR("component number mismatch:" - << result.parentTips.size() << " vs " - << stepper.numberComponents(state.stepping)); - - return set_error_or_abort( - Experimental::GsfError::ComponentNumberMismatch); - } - // There seem to be cases where this is not always after initializing the // navigation from a surface. Some later functions assume this criterium - // to be fulfilled. + // to be fulfilled. (The first surface when starting navigation from + // surface?) bool on_surface = reachable_count == 0 && missed_count < stepper.numberComponents(state.stepping); @@ -206,15 +209,25 @@ struct GsfActor { // Early return if we already were on this surface TODO why is this // necessary - const auto [it, success] = - result.visitedSurfaces.insert(surface.geometryId()); + const bool visited = std::find(result.visitedSurfaces.begin(), + result.visitedSurfaces.end(), + &surface) != result.visitedSurfaces.end(); - if (!success) { + if (visited) { ACTS_VERBOSE("Already visited surface, return"); return; } - removeMissedComponents(state, stepper, result.parentTips); + result.visitedSurfaces.push_back(&surface); + + // Remove the missed components and normalize + // TODO should be redundant if stepper behaves correctly but do for now to + // be safe + stepper.removeMissedComponents(state.stepping); + + auto stepperComponents = stepper.componentIterable(state.stepping); + detail::normalizeWeights( + stepperComponents, [](auto& cmp) -> double& { return cmp.weight(); }); // Check what we have on this surface const auto found_source_link = @@ -236,8 +249,8 @@ struct GsfActor { if (not haveMaterial && not haveMeasurement) { // No hole before first measurement if (result.processedStates > 0 && surface.associatedDetectorElement()) { - noMeasurementUpdate(state, stepper, result, true); - result.parentTips = result.currentTips; + TemporaryStates tmpStates; + noMeasurementUpdate(state, stepper, result, tmpStates, true); } return; } @@ -262,32 +275,45 @@ struct GsfActor { // state with the filtered components. // NOTE because of early return before we know that we have a measurement if (not haveMaterial) { - kalmanUpdate(state, stepper, result, found_source_link->second); + TemporaryStates tmpStates; - result.parentTips = updateStepper(state, stepper, result); + auto res = kalmanUpdate(state, stepper, result, tmpStates, + found_source_link->second); + if (not res.ok()) { + setErrorOrAbort(res.error()); + return; + } + + updateStepper(state, stepper, tmpStates); } // We have material, we thus need a component cache since we will // convolute the components and later reduce them again before updating // the stepper else { - std::vector componentCache; + TemporaryStates tmpStates; + Result res; if (haveMeasurement) { - kalmanUpdate(state, stepper, result, found_source_link->second); - - convoluteComponents(state, stepper, result, componentCache); + res = kalmanUpdate(state, stepper, result, tmpStates, + found_source_link->second); } else { - noMeasurementUpdate(state, stepper, result, false); + res = noMeasurementUpdate(state, stepper, result, tmpStates, false); + } - convoluteComponents(state, stepper, result, componentCache); + if (not res.ok()) { + setErrorOrAbort(res.error()); + return; } + std::vector componentCache; + convoluteComponents(state, stepper, tmpStates, componentCache); + reduceComponents(stepper, surface, componentCache); removeLowWeightComponents(componentCache); - result.parentTips = updateStepper(state, stepper, componentCache); + updateStepper(state, stepper, componentCache); } // If we only done preUpdate before, now do postUpdate @@ -306,11 +332,11 @@ struct GsfActor { template void convoluteComponents(propagator_state_t& state, const stepper_t& stepper, - const result_type& result, + const TemporaryStates& tmpStates, std::vector& componentCache) const { auto cmps = stepper.componentIterable(state.stepping); - for (auto [idx, cmp] : zip(result.currentTips, cmps)) { - auto proxy = result.fittedStates->getTrackState(idx); + for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) { + auto proxy = tmpStates.traj.getTrackState(idx); MetaCache mcache; mcache.parentIndex = idx; @@ -323,7 +349,7 @@ struct GsfActor { BoundTrackParameters bound(proxy.referenceSurface().getSharedPtr(), proxy.filtered(), proxy.filteredCovariance()); - applyBetheHeitler(state, bound, result.weightsOfStates.at(idx), mcache, + applyBetheHeitler(state, bound, tmpStates.weights.at(idx), mcache, componentCache); } } @@ -435,40 +461,6 @@ struct GsfActor { }); } - /// Removes the components which are missed and update the list of parent tips - /// for the MultiTrajectory - template - void removeMissedComponents( - propagator_state_t& state, const stepper_t& stepper, - std::vector& tips) const { - std::vector new_tips; - auto components = stepper.componentIterable(state.stepping); - double sum_w = 0.0; - - for (auto [tip, cmp] : zip(tips, components)) { - if (cmp.status() == Intersection3D::Status::onSurface) { - sum_w += cmp.weight(); - new_tips.push_back(tip); - } - } - - // If the remaining weights are close to zero, re-sanitize all weights - if (sum_w < m_cfg.weightCutoff) { - for (auto cmp : components) { - cmp.weight() = 1.0; - } - } - - tips = new_tips; - stepper.removeMissedComponents(state.stepping); - - detail::normalizeWeights(components, - [](auto& cmp) -> double& { return cmp.weight(); }); - - throw_assert(stepper.numberComponents(state.stepping) == tips.size(), - "size mismatch"); - } - /// Remove components with low weights and renormalize from the component /// cache /// TODO This function does not expect normalized components, but this @@ -488,29 +480,24 @@ struct GsfActor { /// Function that updates the stepper from the MultiTrajectory template - std::vector updateStepper( - propagator_state_t& state, const stepper_t& stepper, - const result_type& result) const { - std::vector newTips; - + void updateStepper(propagator_state_t& state, const stepper_t& stepper, + const TemporaryStates& tmpStates) const { auto cmps = stepper.componentIterable(state.stepping); - for (auto [idx, cmp] : zip(result.currentTips, cmps)) { + for (auto [idx, cmp] : zip(tmpStates.tips, cmps)) { // we set ignored components to missed, so we can remove them after // the loop - if (result.weightsOfStates.at(idx) < m_cfg.weightCutoff) { + if (tmpStates.weights.at(idx) < m_cfg.weightCutoff) { cmp.status() = Intersection3D::Status::missed; continue; } - auto proxy = result.fittedStates->getTrackState(idx); + auto proxy = tmpStates.traj.getTrackState(idx); cmp.pars() = MultiTrajectoryHelpers::freeFiltered(state.options.geoContext, proxy); cmp.cov() = proxy.filteredCovariance(); - cmp.weight() = result.weightsOfStates.at(idx); - - newTips.push_back(idx); + cmp.weight() = tmpStates.weights.at(idx); } stepper.removeMissedComponents(state.stepping); @@ -519,21 +506,15 @@ struct GsfActor { // optimized detail::normalizeWeights(cmps, [&](auto cmp) -> double& { return cmp.weight(); }); - - return newTips; } /// Function that updates the stepper from the ComponentCache template - std::vector updateStepper( - propagator_state_t& state, const stepper_t& stepper, - const std::vector& componentCache) const { + void updateStepper(propagator_state_t& state, const stepper_t& stepper, + const std::vector& componentCache) const { const auto& surface = *state.navigation.currentSurface; const auto& logger = state.options.logger; - // We collect new tips in the loop - std::vector new_parent_tips; - // Clear components before adding new ones stepper.clearComponents(state.stepping); @@ -551,31 +532,25 @@ struct GsfActor { continue; } - // Only add index after we are sure the component was successful added - new_parent_tips.push_back(meta.parentIndex); - auto& cmp = *res; - cmp.jacobian() = meta.jacobian; - cmp.jacToGlobal() = meta.jacToGlobal; + cmp.jacToGlobal() = surface.boundToFreeJacobian(state.geoContext, pars); cmp.pathAccumulated() = meta.pathLength; + + // TODO check if they are not anyways reset to identity or zero + cmp.jacobian() = meta.jacobian; cmp.derivative() = meta.derivative; cmp.jacTransport() = meta.jacTransport; } - - return new_parent_tips; } /// This function performs the kalman update, computes the new posterior /// weights, renormalizes all components, and does some statistics. template Result kalmanUpdate(propagator_state_t& state, const stepper_t& stepper, - result_type& result, + result_type& result, TemporaryStates& tmpStates, const SourceLink& source_link) const { const auto& surface = *state.navigation.currentSurface; - // We will overwrite this soon with new components - result.currentTips.clear(); - // Boolean flag, to distinguish measurement and outlier states. This flag // is only modified by the valid-measurement-branch, so only if there // isn't any valid measuerement state, the flag stays false and the state @@ -583,13 +558,13 @@ struct GsfActor { bool is_valid_measurement = false; auto cmps = stepper.componentIterable(state.stepping); - for (auto [idx, cmp] : zip(result.parentTips, cmps)) { + for (auto cmp : cmps) { auto singleState = cmp.singleState(state); const auto& singleStepper = cmp.singleStepper(stepper); auto trackStateProxyRes = detail::kalmanHandleMeasurement( singleState, singleStepper, m_cfg.extensions, surface, source_link, - *result.fittedStates, idx, false); + tmpStates.traj, MultiTrajectoryTraits::kInvalid, false); if (!trackStateProxyRes.ok()) { return trackStateProxyRes.error(); @@ -604,29 +579,47 @@ struct GsfActor { is_valid_measurement = true; } - result.currentTips.push_back(trackStateProxy.index()); - result.weightsOfStates[result.currentTips.back()] = cmp.weight(); + tmpStates.tips.push_back(trackStateProxy.index()); + tmpStates.weights[tmpStates.tips.back()] = cmp.weight(); } - computePosteriorWeights(*result.fittedStates, result.currentTips, - result.weightsOfStates); + computePosteriorWeights(tmpStates.traj, tmpStates.tips, tmpStates.weights); - detail::normalizeWeights(result.currentTips, [&](auto idx) -> double& { - return result.weightsOfStates.at(idx); + detail::normalizeWeights(tmpStates.tips, [&](auto idx) -> double& { + return tmpStates.weights.at(idx); }); // Do the statistics ++result.processedStates; - // We also need to save outlier states here, otherwise they would not be - // included in the MT if they are at the end of the track - result.lastMeasurementTips = result.currentTips; - // TODO should outlier states also be counted here? if (is_valid_measurement) { ++result.measurementStates; } + addCombinedState(result, tmpStates, surface); + result.lastMeasurementTip = result.currentTip; + + using FiltProjector = + MultiTrajectoryProjector; + FiltProjector proj{tmpStates.traj, tmpStates.weights}; + + std::vector> v; + + // TODO Check why can zero weights can occur + for (const auto& idx : tmpStates.tips) { + const auto [w, p, c] = proj(idx); + if (w > 0.0) { + v.push_back({w, p, *c}); + } + } + + normalizeWeights(v, [](auto& c) -> double& { return std::get(c); }); + + result.lastMeasurementState = + MultiComponentBoundTrackParameters( + surface.getSharedPtr(), std::move(v)); + // Return sucess return Acts::Result::success(); } @@ -635,26 +628,24 @@ struct GsfActor { Result noMeasurementUpdate(propagator_state_t& state, const stepper_t& stepper, result_type& result, + TemporaryStates& tmpStates, bool doCovTransport) const { const auto& surface = *state.navigation.currentSurface; - // We will overwrite this soon with new components - result.currentTips.clear(); - // Initialize as true, so that any component can flip it. However, all // components should behave the same bool is_hole = true; auto cmps = stepper.componentIterable(state.stepping); - for (auto [idx, cmp] : zip(result.parentTips, cmps)) { + for (auto cmp : cmps) { auto singleState = cmp.singleState(state); const auto& singleStepper = cmp.singleStepper(stepper); // There is some redundant checking inside this function, but do this for // now until we measure this is significant auto trackStateProxyRes = detail::kalmanHandleNoMeasurement( - singleState, singleStepper, surface, *result.fittedStates, idx, - doCovTransport); + singleState, singleStepper, surface, tmpStates.traj, + MultiTrajectoryTraits::kInvalid, doCovTransport); if (!trackStateProxyRes.ok()) { return trackStateProxyRes.error(); @@ -666,8 +657,8 @@ struct GsfActor { is_hole = false; } - result.currentTips.push_back(trackStateProxy.index()); - result.weightsOfStates[result.currentTips.back()] = cmp.weight(); + tmpStates.tips.push_back(trackStateProxy.index()); + tmpStates.weights[tmpStates.tips.back()] = cmp.weight(); } // These things should only be done once for all components @@ -678,6 +669,8 @@ struct GsfActor { ++result.processedStates; + addCombinedState(result, tmpStates, surface); + return Result::success(); } @@ -719,6 +712,74 @@ struct GsfActor { } } } + + void addCombinedState(result_type& result, const TemporaryStates& tmpStates, + const Surface& surface) const { + using PredProjector = + MultiTrajectoryProjector; + using FiltProjector = + MultiTrajectoryProjector; + + // We do not need smoothed and jacobian for now + const auto mask = TrackStatePropMask::Calibrated | + TrackStatePropMask::Predicted | + TrackStatePropMask::Filtered; + + if (not m_cfg.inReversePass) { + // The predicted state is the forward pass + const auto [filtMean, filtCov] = + angleDescriptionSwitch(surface, [&](const auto& desc) { + return combineGaussianMixture( + tmpStates.tips, + FiltProjector{tmpStates.traj, tmpStates.weights}, desc); + }); + + result.currentTip = + result.fittedStates->addTrackState(mask, result.currentTip); + auto proxy = result.fittedStates->getTrackState(result.currentTip); + auto firstCmpProxy = tmpStates.traj.getTrackState(tmpStates.tips.front()); + + proxy.setReferenceSurface(surface.getSharedPtr()); + proxy.copyFrom(firstCmpProxy, mask); + + // We set predicted & filtered the same so that the fields are not + // uninitialized when not finding this state in the reverse pass. + proxy.predicted() = filtMean; + proxy.predictedCovariance() = filtCov.value(); + proxy.filtered() = filtMean; + proxy.filteredCovariance() = filtCov.value(); + } else { + assert((result.currentTip != MultiTrajectoryTraits::kInvalid && + "tip not valid")); + result.fittedStates->applyBackwards( + result.currentTip, [&](auto trackState) { + auto fSurface = &trackState.referenceSurface(); + if (fSurface == &surface) { + const auto [filtMean, filtCov] = + angleDescriptionSwitch(surface, [&](const auto& desc) { + return combineGaussianMixture( + tmpStates.tips, + FiltProjector{tmpStates.traj, tmpStates.weights}, desc); + }); + + trackState.filtered() = filtMean; + trackState.filteredCovariance() = filtCov.value(); + return false; + } + return true; + }); + } + } + + /// Set the relevant options that can be set from the Options struct all in + /// one place + void setOptions(const Acts::Experimental::GsfOptions& options) { + m_cfg.maxComponents = options.maxComponents; + m_cfg.extensions = options.extensions; + m_cfg.abortOnError = options.abortOnError; + m_cfg.disableAllMaterialHandling = options.disableAllMaterialHandling; + m_cfg.weightCutoff = options.weightCutoff; + } }; } // namespace detail diff --git a/Core/include/Acts/TrackFitting/detail/GsfSmoothing.hpp b/Core/include/Acts/TrackFitting/detail/GsfSmoothing.hpp deleted file mode 100644 index 0f7962b2860..00000000000 --- a/Core/include/Acts/TrackFitting/detail/GsfSmoothing.hpp +++ /dev/null @@ -1,304 +0,0 @@ -// This file is part of the Acts project. -// -// Copyright (C) 2021 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 http://mozilla.org/MPL/2.0/. - -#pragma once - -#include "Acts/TrackFitting/GsfError.hpp" -#include "Acts/TrackFitting/KalmanFitter.hpp" -#include "Acts/TrackFitting/detail/GsfUtils.hpp" -#include "Acts/Utilities/detail/gaussian_mixture_helpers.hpp" - -namespace Acts { - -namespace detail { - -template -class ScopeGuard { - Function f; - - public: - ScopeGuard(Function &&fun) : f(std::move(fun)) {} - ~ScopeGuard() { f(); } -}; - -/// @brief Smoothing function, which takes two ranges of -/// MultiTrajectory-indices and the corresponding projectors. -template -auto bayesianSmoothing(component_iterator_t fwdBegin, - component_iterator_t fwdEnd, - component_iterator_t bwdBegin, - component_iterator_t bwdEnd, - fwd_projector_t fwdProjector = fwd_projector_t{}, - bwd_projector_t bwdProjector = bwd_projector_t{}, - ActsScalar weightCutoff = 1.e-16) { - std::vector>> - smoothedState; - - using ResType = Result; - - for (auto fwd = fwdBegin; fwd != fwdEnd; ++fwd) { - const auto &[weight_a, pars_a, cov_a] = fwdProjector(*fwd); - throw_assert(cov_a, "for now we require a covariance here"); - - for (auto bwd = bwdBegin; bwd != bwdEnd; ++bwd) { - const auto &[weight_b, pars_b, cov_b] = bwdProjector(*bwd); - throw_assert(cov_b, "for now we require a covariance here"); - - const auto summedCov = *cov_a + *cov_b; - - const auto K = *cov_a * summedCov.inverse(); - const auto new_pars = (pars_a + K * (pars_b - pars_a)).eval(); - const auto new_cov = (K * *cov_b).eval(); - - const auto diff = pars_a - pars_b; - const ActsScalar exponent = diff.transpose() * summedCov.inverse() * diff; - - const auto new_weight = std::exp(-0.5 * exponent) * weight_a * weight_b; - - if (std::isfinite(new_weight) and new_weight > weightCutoff) { - smoothedState.push_back({new_weight, new_pars, new_cov}); - } - } - } - - if (smoothedState.empty()) { - return ResType(Experimental::GsfError::SmoothingFailed); - } - - normalizeWeights(smoothedState, [](auto &tuple) -> decltype(auto) { - return std::get(tuple); - }); - - throw_assert(weightsAreNormalized( - smoothedState, - [](const auto &tuple) { return std::get(tuple); }), - "smoothed state not normalized"); - - return ResType(smoothedState); -} - -/// Enumeration type to allow templating on the state we want to project on with -/// a MultiTrajectory -enum class StatesType { ePredicted, eFiltered, eSmoothed }; - -inline std::ostream &operator<<(std::ostream &os, StatesType type) { - constexpr static std::array names = {"predicted", "filtered", "smoothed"}; - os << names[static_cast(type)]; - return os; -} - -/// @brief Projector type which maps a MultiTrajectory-Index to a tuple of -/// [weight, parameters, covariance]. Therefore, it contains a MultiTrajectory -/// and for now a std::map for the weights -template -struct MultiTrajectoryProjector { - const MultiTrajectory &mt; - const std::map &weights; - - auto operator()(MultiTrajectoryTraits::IndexType idx) const { - const auto proxy = mt.getTrackState(idx); - switch (type) { - case StatesType::ePredicted: - return std::make_tuple(weights.at(idx), proxy.predicted(), - std::optional{proxy.predictedCovariance()}); - case StatesType::eFiltered: - return std::make_tuple(weights.at(idx), proxy.filtered(), - std::optional{proxy.filteredCovariance()}); - case StatesType::eSmoothed: - return std::make_tuple(weights.at(idx), proxy.smoothed(), - std::optional{proxy.smoothedCovariance()}); - } - } -}; - -/// @brief This function takes two MultiTrajectory objects and corresponding -/// index lists (one of the backward pass, one of the forward pass), combines -/// them, applies smoothing, and returns a new, single-component MultiTrajectory -/// @tparam ReturnSmootedStates If set to true, the function returns not only -/// combined MultiTrajectory, but also a std::vector contianing the -/// component-wise smoothed states -/// TODO this function does not handle outliers correctly at the moment I think -/// TODO change std::vector< size_t > to boost::small_vector for better -/// performance -template -auto smoothAndCombineTrajectories( - const MultiTrajectory &fwd, - const std::vector &fwdStartTips, - const std::map &fwdWeights, - const MultiTrajectory &bwd, - const std::vector &bwdStartTips, - const std::map &bwdWeights, - LoggerWrapper logger = getDummyLogger()) { - // This vector gets only filled if ReturnSmootedStates is true - std::vector>>>> - smoothedStates; - - // Use backward trajectory as basic trajectory, so that final trajectory is - // ordered correctly. We ensure also that they are unique. - std::vector bwdTips = bwdStartTips; - - // Ensures that the bwd tips are unique and do not contain kInvalid which - // represents an invalid trajectory state - auto sortUniqueValidateBwdTips = [&]() { - std::sort(bwdTips.begin(), bwdTips.end()); - bwdTips.erase(std::unique(bwdTips.begin(), bwdTips.end()), bwdTips.end()); - - auto invalid_it = std::find(bwdTips.begin(), bwdTips.end(), - MultiTrajectoryTraits::kInvalid); - if (invalid_it != bwdTips.end()) { - bwdTips.erase(invalid_it); - } - }; - - sortUniqueValidateBwdTips(); - - KalmanFitterResult result; - result.fittedStates = std::make_shared(); - - while (!bwdTips.empty()) { - // Ensure that we update the bwd tips whenever we go to the next iteration - // (This allows using continue etc in the loop) - ScopeGuard scopeGuard([&]() { - for (auto &tip : bwdTips) { - const auto p = bwd.getTrackState(tip); - tip = p.previous(); - } - - sortUniqueValidateBwdTips(); - }); - - const auto firstBwdState = bwd.getTrackState(bwdTips.front()); - const auto ¤tSurface = firstBwdState.referenceSurface(); - - // Search corresponding forward tips - const auto bwdGeoId = currentSurface.geometryId(); - std::vector fwdTips; - - for (const auto tip : fwdStartTips) { - fwd.visitBackwards(tip, [&](const auto &state) { - if (state.referenceSurface().geometryId() == bwdGeoId) { - fwdTips.push_back(state.index()); - } - }); - } - - // Check if we have forward tips - if (fwdTips.empty()) { - ACTS_WARNING("Did not find forward states on surface " << bwdGeoId); - continue; - } - - // Ensure we have no duplicates - std::sort(fwdTips.begin(), fwdTips.end()); - fwdTips.erase(std::unique(fwdTips.begin(), fwdTips.end()), fwdTips.end()); - - // Add state to MultiTrajectory - result.lastTrackIndex = result.fittedStates->addTrackState( - TrackStatePropMask::All, result.lastTrackIndex); - result.processedStates++; - - auto proxy = result.fittedStates->getTrackState(result.lastTrackIndex); - - // This way we copy all relevant flags and the calibrated field. However - // this assumes that the relevant flags do not differ between components - proxy.copyFrom(firstBwdState); - - // Define some Projector types we need in the following - using PredProjector = - MultiTrajectoryProjector; - using FiltProjector = - MultiTrajectoryProjector; - - if (proxy.typeFlags().test(Acts::TrackStateFlag::HoleFlag)) { - result.measurementHoles++; - } else { - // We also need to save outlier states here, otherwise they would not be - // included in the MT if they are at the end of the track - result.lastMeasurementIndex = result.lastTrackIndex; - } - - // If we have a hole or an outlier, just take the combination of filtered - // and predicted and no smoothed state - if (not proxy.typeFlags().test(Acts::TrackStateFlag::MeasurementFlag)) { - const auto [mean, cov] = - angleDescriptionSwitch(currentSurface, [&](const auto &desc) { - return combineGaussianMixture(bwdTips, - FiltProjector{bwd, bwdWeights}, desc); - }); - - proxy.predicted() = mean; - proxy.predictedCovariance() = cov.value(); - using PM = TrackStatePropMask; - proxy.shareFrom(proxy, PM::Predicted, PM::Filtered); - - } - // If we have a measurement, do the smoothing - else { - result.measurementStates++; - - // The predicted state is the forward pass - const auto [fwdMeanPred, fwdCovPred] = - angleDescriptionSwitch(currentSurface, [&](const auto &desc) { - return combineGaussianMixture(fwdTips, - PredProjector{fwd, fwdWeights}, desc); - }); - proxy.predicted() = fwdMeanPred; - proxy.predictedCovariance() = fwdCovPred.value(); - - // The filtered state is the backward pass - const auto [bwdMeanFilt, bwdCovFilt] = - angleDescriptionSwitch(currentSurface, [&](const auto &desc) { - return combineGaussianMixture(bwdTips, - FiltProjector{bwd, bwdWeights}, desc); - }); - proxy.filtered() = bwdMeanFilt; - proxy.filteredCovariance() = bwdCovFilt.value(); - - // Do the smoothing - auto smoothedStateResult = bayesianSmoothing( - fwdTips.begin(), fwdTips.end(), bwdTips.begin(), bwdTips.end(), - PredProjector{fwd, fwdWeights}, FiltProjector{bwd, bwdWeights}); - - if (!smoothedStateResult.ok()) { - ACTS_WARNING("Smoothing failed on " << bwdGeoId); - continue; - } - - const auto &smoothedState = *smoothedStateResult; - - if constexpr (ReturnSmootedStates) { - smoothedStates.push_back({¤tSurface, smoothedState}); - } - - // The smoothed state is a combination - const auto [smoothedMean, smoothedCov] = - angleDescriptionSwitch(currentSurface, [&](const auto &desc) { - return combineGaussianMixture(smoothedState, Identity{}, desc); - }); - proxy.smoothed() = smoothedMean; - proxy.smoothedCovariance() = smoothedCov.value(); - ACTS_VERBOSE("Added smoothed state to MultiTrajectory"); - } - } - - result.smoothed = true; - result.reversed = true; - result.finished = true; - - if constexpr (ReturnSmootedStates) { - return std::make_tuple(result, smoothedStates); - } else { - return std::make_tuple(result); - } -} - -} // namespace detail -} // namespace Acts diff --git a/Core/include/Acts/TrackFitting/detail/GsfUtils.hpp b/Core/include/Acts/TrackFitting/detail/GsfUtils.hpp index 51faa9f66d4..cd129c7d27d 100644 --- a/Core/include/Acts/TrackFitting/detail/GsfUtils.hpp +++ b/Core/include/Acts/TrackFitting/detail/GsfUtils.hpp @@ -55,6 +55,7 @@ void normalizeWeights(component_range_t &cmps, const projector_t &proj) { // semantics, otherwise there is a `cannot bind ... to ...` error for (auto it = cmps.begin(); it != cmps.end(); ++it) { decltype(auto) cmp = *it; + throw_assert(std::isfinite(proj(cmp)), "weight not finite:" << proj(cmp)); sum_of_weights += proj(cmp); } @@ -86,25 +87,25 @@ class ScopedGsfInfoPrinterAndChecker { ACTS_VERBOSE(" #" << i++ << " pos: " << getVector(eFreePos0) << ", dir: " << getVector(eFreeDir0) << ", weight: " << cmp.weight() << ", status: " << cmp.status() - << ", qop: " << cmp.pars()[eFreeQOverP]); + << ", qop: " << cmp.pars()[eFreeQOverP] + << ", det(cov): " << cmp.cov().determinant()); } } void checks(const std::string_view &where) const { const auto cmps = m_stepper.constComponentIterable(m_state.stepping); - // If all components are missed, their weights have been reset to zero. // In this case the weights might not be normalized and not even be // finite due to a division by zero. if (m_stepper.numberComponents(m_state.stepping) > m_missedCount) { - throw_assert(detail::weightsAreNormalized( - cmps, [](const auto &cmp) { return cmp.weight(); }), - "not normalized at " << where); - throw_assert( std::all_of(cmps.begin(), cmps.end(), [](auto cmp) { return std::isfinite(cmp.weight()); }), "some weights are not finite at " << where); + + throw_assert(detail::weightsAreNormalized( + cmps, [](const auto &cmp) { return cmp.weight(); }), + "not normalized at " << where); } } @@ -199,5 +200,39 @@ void computePosteriorWeights( } } +/// Enumeration type to allow templating on the state we want to project on with +/// a MultiTrajectory +enum class StatesType { ePredicted, eFiltered, eSmoothed }; + +inline std::ostream &operator<<(std::ostream &os, StatesType type) { + constexpr static std::array names = {"predicted", "filtered", "smoothed"}; + os << names[static_cast(type)]; + return os; +} + +/// @brief Projector type which maps a MultiTrajectory-Index to a tuple of +/// [weight, parameters, covariance]. Therefore, it contains a MultiTrajectory +/// and for now a std::map for the weights +template +struct MultiTrajectoryProjector { + const MultiTrajectory &mt; + const std::map &weights; + + auto operator()(MultiTrajectoryTraits::IndexType idx) const { + const auto proxy = mt.getTrackState(idx); + switch (type) { + case StatesType::ePredicted: + return std::make_tuple(weights.at(idx), proxy.predicted(), + std::optional{proxy.predictedCovariance()}); + case StatesType::eFiltered: + return std::make_tuple(weights.at(idx), proxy.filtered(), + std::optional{proxy.filteredCovariance()}); + case StatesType::eSmoothed: + return std::make_tuple(weights.at(idx), proxy.smoothed(), + std::optional{proxy.smoothedCovariance()}); + } + } +}; + } // namespace detail } // namespace Acts diff --git a/Core/include/Acts/TrackFitting/detail/KLMixtureReduction.hpp b/Core/include/Acts/TrackFitting/detail/KLMixtureReduction.hpp index 7804452d257..4ba32c50ae7 100644 --- a/Core/include/Acts/TrackFitting/detail/KLMixtureReduction.hpp +++ b/Core/include/Acts/TrackFitting/detail/KLMixtureReduction.hpp @@ -41,6 +41,7 @@ auto computeKLDistance(const component_t &a, const component_t &b, throw_assert(kl >= 0.0, "kl-distance should be positive, but is: " << kl << "(qop_a: " << parsA << "+-" << covA << ", qop_b: " << parsB << "+-" << covB << ")"); + return kl; } diff --git a/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/GsfFitterFunction.hpp b/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/GsfFitterFunction.hpp index db71304a6ce..4c585cfaa43 100644 --- a/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/GsfFitterFunction.hpp +++ b/Examples/Algorithms/TrackFitting/include/ActsExamples/TrackFitting/GsfFitterFunction.hpp @@ -31,7 +31,7 @@ makeGsfFitterFunction( std::shared_ptr trackingGeometry, std::shared_ptr magneticField, BetheHeitlerApprox betheHeitlerApprox, std::size_t maxComponents, - Acts::FinalReductionMethod finalReductionMethod, bool abortOnError, - bool disableAllMaterialHandling); + double weightCutoff, Acts::FinalReductionMethod finalReductionMethod, + bool abortOnError, bool disableAllMaterialHandling); } // namespace ActsExamples diff --git a/Examples/Algorithms/TrackFitting/src/GsfFitterFunction.cpp b/Examples/Algorithms/TrackFitting/src/GsfFitterFunction.cpp index 036083b1c6b..26c890c6b11 100644 --- a/Examples/Algorithms/TrackFitting/src/GsfFitterFunction.cpp +++ b/Examples/Algorithms/TrackFitting/src/GsfFitterFunction.cpp @@ -47,6 +47,7 @@ struct GsfFitterFunctionImpl Acts::GainMatrixUpdater updater; std::size_t maxComponents; + double weightCutoff; bool abortOnError; bool disableAllMaterialHandling; @@ -70,8 +71,10 @@ struct GsfFitterFunctionImpl options.propOptions, &(*options.referenceSurface), maxComponents, + weightCutoff, abortOnError, disableAllMaterialHandling}; + gsfOptions.extensions.calibrator .template connect<&ActsExamples::MeasurementCalibrator::calibrate>( &options.calibrator.get()); @@ -111,8 +114,8 @@ ActsExamples::makeGsfFitterFunction( std::shared_ptr trackingGeometry, std::shared_ptr magneticField, BetheHeitlerApprox betheHeitlerApprox, std::size_t maxComponents, - Acts::FinalReductionMethod finalReductionMethod, bool abortOnError, - bool disableAllMaterialHandling) { + double weightCutoff, Acts::FinalReductionMethod finalReductionMethod, + bool abortOnError, bool disableAllMaterialHandling) { MultiStepper stepper(std::move(magneticField), finalReductionMethod); // Standard fitter @@ -135,6 +138,7 @@ ActsExamples::makeGsfFitterFunction( auto fitterFunction = std::make_shared( std::move(trackFitter), std::move(directTrackFitter)); fitterFunction->maxComponents = maxComponents; + fitterFunction->weightCutoff = weightCutoff; fitterFunction->abortOnError = abortOnError; fitterFunction->disableAllMaterialHandling = disableAllMaterialHandling; diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index 710e9983a35..37196b67093 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -647,6 +647,7 @@ def addTruthTrackingGsf( "abortOnError": False, "disableAllMaterialHandling": False, "finalReductionMethod": acts.examples.FinalReductionMethod.mean, + "weightCutoff": 1.0e-4, } gsfAlg = acts.examples.TrackFittingAlgorithm( diff --git a/Examples/Python/src/TrackFitting.cpp b/Examples/Python/src/TrackFitting.cpp index ceb49a977b6..dc3b4986631 100644 --- a/Examples/Python/src/TrackFitting.cpp +++ b/Examples/Python/src/TrackFitting.cpp @@ -88,13 +88,13 @@ void addTrackFitting(Context& ctx) { "makeGsfFitterFunction", py::overload_cast, std::shared_ptr, - BetheHeitlerApprox, std::size_t, + BetheHeitlerApprox, std::size_t, double, Acts::FinalReductionMethod, bool, bool>( &ActsExamples::makeGsfFitterFunction), py::arg("trackingGeometry"), py::arg("magneticField"), py::arg("betheHeitlerApprox"), py::arg("maxComponents"), - py::arg("finalReductionMethod"), py::arg("abortOnError"), - py::arg("disableAllMaterialHandling")); + py::arg("weightCutoff"), py::arg("finalReductionMethod"), + py::arg("abortOnError"), py::arg("disableAllMaterialHandling")); } { diff --git a/Examples/Python/tests/root_file_hashes.txt b/Examples/Python/tests/root_file_hashes.txt index f780ef4b523..01f40bd9173 100644 --- a/Examples/Python/tests/root_file_hashes.txt +++ b/Examples/Python/tests/root_file_hashes.txt @@ -74,10 +74,10 @@ test_truth_tracking_kalman[odd-1000.0]__trackstates_fitter.root: 5ea6ca504f89355 test_truth_tracking_kalman[odd-1000.0]__tracksummary_fitter.root: c7b3ff9d8d3c19ac378ed7f7c63f396fe504674999efb3131d1009a9c5d3bf2f test_truth_tracking_kalman[odd-1000.0]__performance_track_finder.root: 76a990d595b6e097da2bed447783bd63044956e5649a5dd6fd7a6a3434786877 -test_truth_tracking_gsf[generic]__trackstates_gsf.root: c9f3d30d13ee8eac8c69a01600ea89a8bd80cf89154ecc69a48d60a6afd26405 -test_truth_tracking_gsf[generic]__tracksummary_gsf.root: 7199b3912e089b93cc010eee74356d4f23edaae2eff7c586b836911e65ae6e9a -test_truth_tracking_gsf[odd]__trackstates_gsf.root: 97ebc8f5ab5d5242596eaca2dfbed864a65580cc18c02cad634c6357cf449ab7 -test_truth_tracking_gsf[odd]__tracksummary_gsf.root: 928277c50d249abd548185f8536e1614e9ab958e0e7a9521ef0966acdcccb9d2 +test_truth_tracking_gsf[generic]__trackstates_gsf.root: ed0f918d88268a280d93a143d4ec87ebd4bb6fce17567f31a04d05ae91aa3de9 +test_truth_tracking_gsf[generic]__tracksummary_gsf.root: bc7b6538b200e619cf32d84d4f258b060c52170d651e0c52fea21d4126d64943 +test_truth_tracking_gsf[odd]__trackstates_gsf.root: dd4062fe35168d8de2bfaebf17cf838b87988165ebc844c452702d38381ff0f9 +test_truth_tracking_gsf[odd]__tracksummary_gsf.root: 002a558c4c36bc7d3405af69edab936e50a3d8eeb434f29a3fce80f3d82c5453 test_digitization_example_input__measurements.root: ccc92f0ad538d1b62d98f19f947970bcc491843e54d8ffeed16ad2e226b8caee test_digitization_example_input__particles.root: 78a89f365177423d0834ea6f1bd8afe1488e72b12a25066a20bd9050f5407860 diff --git a/Tests/UnitTests/Core/TrackFitting/FitterTestsCommon.hpp b/Tests/UnitTests/Core/TrackFitting/FitterTestsCommon.hpp index 226166e597a..17364b79afc 100644 --- a/Tests/UnitTests/Core/TrackFitting/FitterTestsCommon.hpp +++ b/Tests/UnitTests/Core/TrackFitting/FitterTestsCommon.hpp @@ -48,8 +48,9 @@ struct TestOutlierFinder { if (not state.hasCalibrated() or not state.hasPredicted()) { return false; } - auto residuals = state.effectiveCalibrated() - - state.effectiveProjector() * state.predicted(); + auto residuals = (state.effectiveCalibrated() - + state.effectiveProjector() * state.predicted()) + .eval(); auto distance = residuals.norm(); return (distanceMax <= distance); } @@ -201,7 +202,7 @@ struct FitterTester { BOOST_CHECK_EQUAL(val.measurementStates, sourceLinks.size()); BOOST_CHECK_EQUAL(val.missedActiveSurfaces.size(), 0u); // count the number of `smoothed` states - if (expected_reversed) { + if (expected_reversed && expected_smoothed) { size_t nSmoothed = 0; val.fittedStates->visitBackwards(val.lastMeasurementIndex, [&nSmoothed](const auto& state) { @@ -247,7 +248,7 @@ struct FitterTester { BOOST_CHECK(val.finished); BOOST_CHECK_EQUAL(val.missedActiveSurfaces.size(), 0u); // count the number of `smoothed` states - if (expected_reversed) { + if (expected_reversed && expected_smoothed) { size_t nSmoothed = 0; val.fittedStates->visitBackwards(val.lastMeasurementIndex, [&nSmoothed](const auto& state) { @@ -312,7 +313,8 @@ struct FitterTester { BOOST_REQUIRE(res.ok()); const auto& val = res.value(); - BOOST_CHECK_NE(val.lastMeasurementIndex, SIZE_MAX); + BOOST_CHECK_NE(val.lastMeasurementIndex, + Acts::MultiTrajectoryTraits::kInvalid); BOOST_REQUIRE(val.fittedParameters); parameters = val.fittedParameters->parameters(); BOOST_CHECK_EQUAL(val.measurementStates, sourceLinks.size()); @@ -331,7 +333,8 @@ struct FitterTester { BOOST_REQUIRE(res.ok()); const auto& val = res.value(); - BOOST_CHECK_NE(val.lastMeasurementIndex, SIZE_MAX); + BOOST_CHECK_NE(val.lastMeasurementIndex, + Acts::MultiTrajectoryTraits::kInvalid); BOOST_REQUIRE(val.fittedParameters); // check consistency w/ un-shuffled measurements CHECK_CLOSE_ABS(val.fittedParameters->parameters(), parameters, 1e-5); diff --git a/Tests/UnitTests/Core/TrackFitting/GsfTests.cpp b/Tests/UnitTests/Core/TrackFitting/GsfTests.cpp index 7a5f468b262..d9b766c15c2 100644 --- a/Tests/UnitTests/Core/TrackFitting/GsfTests.cpp +++ b/Tests/UnitTests/Core/TrackFitting/GsfTests.cpp @@ -127,7 +127,7 @@ BOOST_AUTO_TEST_CASE(ZeroFieldNoSurfaceForward) { auto options = makeDefaultGsfOptions(); tester.test_ZeroFieldNoSurfaceForward(gsfZero, options, multi_pars, rng, true, - true); + false); } BOOST_AUTO_TEST_CASE(ZeroFieldWithSurfaceForward) { @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(ZeroFieldWithSurfaceForward) { auto options = makeDefaultGsfOptions(); tester.test_ZeroFieldWithSurfaceForward(gsfZero, options, multi_pars, rng, - true, true); + true, false); } BOOST_AUTO_TEST_CASE(ZeroFieldWithSurfaceBackward) { @@ -143,7 +143,7 @@ BOOST_AUTO_TEST_CASE(ZeroFieldWithSurfaceBackward) { auto options = makeDefaultGsfOptions(); tester.test_ZeroFieldWithSurfaceBackward(gsfZero, options, multi_pars, rng, - true, true); + true, false); } BOOST_AUTO_TEST_CASE(ZeroFieldWithSurfaceAtExit) { @@ -151,21 +151,21 @@ BOOST_AUTO_TEST_CASE(ZeroFieldWithSurfaceAtExit) { auto options = makeDefaultGsfOptions(); tester.test_ZeroFieldWithSurfaceBackward(gsfZero, options, multi_pars, rng, - true, true); + true, false); } BOOST_AUTO_TEST_CASE(ZeroFieldShuffled) { auto multi_pars = makeParameters(); auto options = makeDefaultGsfOptions(); - tester.test_ZeroFieldShuffled(gsfZero, options, multi_pars, rng, true, true); + tester.test_ZeroFieldShuffled(gsfZero, options, multi_pars, rng, true, false); } BOOST_AUTO_TEST_CASE(ZeroFieldWithHole) { auto options = makeDefaultGsfOptions(); auto multi_pars = makeParameters(); - tester.test_ZeroFieldWithHole(gsfZero, options, multi_pars, rng, true, true); + tester.test_ZeroFieldWithHole(gsfZero, options, multi_pars, rng, true, false); } BOOST_AUTO_TEST_CASE(ZeroFieldWithOutliers) { @@ -179,19 +179,7 @@ BOOST_AUTO_TEST_CASE(ZeroFieldWithOutliers) { auto multi_pars = makeParameters(); tester.test_ZeroFieldWithOutliers(gsfZero, options, multi_pars, rng, true, - true); -} - -// NOTE This test makes no sense for the GSF since there is always reverse -// filtering BOOST_AUTO_TEST_CASE(ZeroFieldWithReverseFiltering) { ... } - -// TODO this is not really Kalman fitter specific. is probably better tested -// with a synthetic trajectory. -BOOST_AUTO_TEST_CASE(GlobalCovariance) { - auto options = makeDefaultGsfOptions(); - auto multi_pars = makeParameters(); - - tester.test_GlobalCovariance(gsfZero, options, multi_pars, rng); + false); } BOOST_AUTO_TEST_SUITE_END() From 131121e2eeb3bac4352a5683ce2a68925b64c182 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Fri, 11 Nov 2022 13:14:24 +0100 Subject: [PATCH 02/57] refactor: replace track params with trajectories in examples (#1670) somehow we ended up passing through track parameters and trajectory tips through our algorithm chain which can be replaced by trajectories the only caveat is particle smearing which will produce track parameters which should be passed down the chain. so track selector and vertexing will still except track parameters as an input --- CI/physmon/physmon.py | 4 +- .../AmbiguityResolutionAlgorithm.hpp | 10 +- .../TrackFinding/TrackFindingAlgorithm.hpp | 4 - .../src/AmbiguityResolutionAlgorithm.cpp | 70 ++++---- .../src/TrackFindingAlgorithm.cpp | 25 --- .../TruthTracking/TrackSelector.cpp | 101 ++++++----- .../TruthTracking/TrackSelector.hpp | 16 +- .../AdaptiveMultiVertexFinderAlgorithm.hpp | 4 +- .../IterativeVertexFinderAlgorithm.hpp | 4 +- .../TutorialVertexFinderAlgorithm.hpp | 4 +- .../Vertexing/VertexFitterAlgorithm.hpp | 4 +- .../AdaptiveMultiVertexFinderAlgorithm.cpp | 13 +- .../src/IterativeVertexFinderAlgorithm.cpp | 13 +- .../src/TutorialVertexFinderAlgorithm.cpp | 5 +- .../Vertexing/src/VertexFitterAlgorithm.cpp | 19 ++- .../Vertexing/src/VertexingHelpers.hpp | 32 ++++ .../ActsExamples/EventData/Trajectories.hpp | 5 + .../Io/Performance/CKFPerformanceWriter.cpp | 158 +++++++++--------- .../Io/Performance/CKFPerformanceWriter.hpp | 2 - .../TrackFitterPerformanceWriter.cpp | 5 - .../Io/Root/RootVertexPerformanceWriter.hpp | 9 +- .../Root/src/RootTrajectoryStatesWriter.cpp | 5 - .../Root/src/RootTrajectorySummaryWriter.cpp | 5 - .../Root/src/RootVertexPerformanceWriter.cpp | 106 ++++++------ .../python/acts/examples/reconstruction.py | 90 +++++----- Examples/Python/src/Output.cpp | 13 +- Examples/Python/src/TrackFinding.cpp | 11 +- Examples/Python/src/TruthTracking.cpp | 4 +- Examples/Python/src/Vertexing.cpp | 23 +-- Examples/Scripts/Python/vertex_fitting.py | 3 +- 30 files changed, 386 insertions(+), 381 deletions(-) diff --git a/CI/physmon/physmon.py b/CI/physmon/physmon.py index a775aefe24b..a154c5e33ff 100755 --- a/CI/physmon/physmon.py +++ b/CI/physmon/physmon.py @@ -173,8 +173,8 @@ if truthEstimatedSeeded else SeedingAlgorithm.Default, geoSelectionConfigFile=geoSel, - outputDirRoot=tp, rnd=rnd, # only used by SeedingAlgorithm.TruthSmeared + outputDirRoot=tp, ) addCKFTracks( @@ -304,8 +304,8 @@ TrackParamsEstimationConfig(deltaR=(10.0 * u.mm, None)), seedingAlgorithm=SeedingAlgorithm.Default, geoSelectionConfigFile=geoSel, - outputDirRoot=None, rnd=rnd, # only used by SeedingAlgorithm.TruthSmeared + outputDirRoot=None, ) addCKFTracks( diff --git a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/AmbiguityResolutionAlgorithm.hpp b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/AmbiguityResolutionAlgorithm.hpp index b9b5cbc40d9..521641c6daa 100644 --- a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/AmbiguityResolutionAlgorithm.hpp +++ b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/AmbiguityResolutionAlgorithm.hpp @@ -32,14 +32,8 @@ class AmbiguityResolutionAlgorithm final : public BareAlgorithm { std::string inputSourceLinks; /// Input trajectories collection. std::string inputTrajectories; - /// Input track parameters collection. - std::string inputTrackParameters; - /// Input track parameters tips w.r.t outputTrajectories. - std::string inputTrackParametersTips; - /// Output track parameters collection. - std::string outputTrackParameters; - /// Output track parameters tips w.r.t outputTrajectories. - std::string outputTrackParametersTips; + /// Output trajectories collection. + std::string outputTrajectories; /// Maximum amount of shared hits per track. std::uint32_t maximumSharedHits = 1; diff --git a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/TrackFindingAlgorithm.hpp b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/TrackFindingAlgorithm.hpp index 8e9cd61ce1f..d62d1612bfe 100644 --- a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/TrackFindingAlgorithm.hpp +++ b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/TrackFindingAlgorithm.hpp @@ -65,10 +65,6 @@ class TrackFindingAlgorithm final : public BareAlgorithm { std::string inputInitialTrackParameters; /// Output find trajectories collection. std::string outputTrajectories; - /// Output track parameters collection. - std::string outputTrackParameters; - /// Output track parameters tips w.r.t outputTrajectories. - std::string outputTrackParametersTips; /// Type erased track finder function. std::shared_ptr findTracks; /// CKF measurement selector config diff --git a/Examples/Algorithms/TrackFinding/src/AmbiguityResolutionAlgorithm.cpp b/Examples/Algorithms/TrackFinding/src/AmbiguityResolutionAlgorithm.cpp index 29f635d6643..86dce07954f 100644 --- a/Examples/Algorithms/TrackFinding/src/AmbiguityResolutionAlgorithm.cpp +++ b/Examples/Algorithms/TrackFinding/src/AmbiguityResolutionAlgorithm.cpp @@ -30,19 +30,8 @@ ActsExamples::AmbiguityResolutionAlgorithm::AmbiguityResolutionAlgorithm( if (m_cfg.inputTrajectories.empty()) { throw std::invalid_argument("Missing trajectories input collection"); } - if (m_cfg.inputTrackParameters.empty()) { - throw std::invalid_argument("Missing track parameter input collection"); - } - if (m_cfg.inputTrackParametersTips.empty()) { - throw std::invalid_argument( - "Missing track parameters tips input collection"); - } - if (m_cfg.outputTrackParameters.empty()) { - throw std::invalid_argument("Missing track parameter output collection"); - } - if (m_cfg.outputTrackParametersTips.empty()) { - throw std::invalid_argument( - "Missing track parameters tips output collection"); + if (m_cfg.outputTrajectories.empty()) { + throw std::invalid_argument("Missing trajectories output collection"); } } @@ -123,19 +112,25 @@ ActsExamples::ProcessCode ActsExamples::AmbiguityResolutionAlgorithm::execute( ctx.eventStore.get(m_cfg.inputSourceLinks); const auto& trajectories = ctx.eventStore.get(m_cfg.inputTrajectories); - const auto& trackParameters = - ctx.eventStore.get(m_cfg.inputTrackParameters); - const auto& trackTips = - ctx.eventStore.get>>( - m_cfg.inputTrackParametersTips); - throw_assert(trackParameters.size() == trackTips.size(), - "Track tip count does not match track parameter count"); + TrackParametersContainer trackParameters; + std::vector> trackTips; + + for (std::size_t iTraj = 0; iTraj < trajectories.size(); ++iTraj) { + const auto& traj = trajectories[iTraj]; + for (auto tip : traj.tips()) { + if (!traj.hasTrackParameters(tip)) { + continue; + } + trackParameters.push_back(traj.trackParameters(tip)); + trackTips.emplace_back(iTraj, tip); + } + } std::vector hitCount(trackParameters.size(), 0); for (std::size_t i = 0; i < trackParameters.size(); ++i) { - const auto [indexTraj, tip] = trackTips[i]; - const auto& traj = trajectories[indexTraj]; + const auto [iTraj, tip] = trackTips[i]; + const auto& traj = trajectories[iTraj]; hitCount[i] = computeTrackHits(traj.multiTrajectory(), tip); } @@ -179,18 +174,27 @@ ActsExamples::ProcessCode ActsExamples::AmbiguityResolutionAlgorithm::execute( ACTS_INFO("Resolved to " << trackIndices.size() << " tracks from " << trackParameters.size()); - TrackParametersContainer outputTrackParameters; - std::vector> outputTrackParametersTips; - outputTrackParameters.reserve(trackIndices.size()); - outputTrackParametersTips.reserve(trackIndices.size()); - for (auto indexTrack : trackIndices) { - outputTrackParameters.push_back(trackParameters[indexTrack]); - outputTrackParametersTips.push_back(trackTips[indexTrack]); + TrajectoriesContainer outputTrajectories; + outputTrajectories.reserve(trajectories.size()); + for (std::size_t iTraj = 0; iTraj < trajectories.size(); ++iTraj) { + const auto& traj = trajectories[iTraj]; + + std::vector tips; + Trajectories::IndexedParameters parameters; + + for (auto iTrack : trackIndices) { + if (trackTips[iTrack].first != iTraj) { + continue; + } + const auto tip = trackTips[iTrack].second; + tips.push_back(tip); + parameters.emplace(tip, trackParameters[iTrack]); + } + + outputTrajectories.emplace_back(traj.multiTrajectoryPtr(), tips, + parameters); } - ctx.eventStore.add(m_cfg.outputTrackParameters, - std::move(outputTrackParameters)); - ctx.eventStore.add(m_cfg.outputTrackParametersTips, - std::move(outputTrackParametersTips)); + ctx.eventStore.add(m_cfg.outputTrajectories, std::move(outputTrajectories)); return ActsExamples::ProcessCode::SUCCESS; } diff --git a/Examples/Algorithms/TrackFinding/src/TrackFindingAlgorithm.cpp b/Examples/Algorithms/TrackFinding/src/TrackFindingAlgorithm.cpp index 1aae14e1c58..817c2f2c93a 100644 --- a/Examples/Algorithms/TrackFinding/src/TrackFindingAlgorithm.cpp +++ b/Examples/Algorithms/TrackFinding/src/TrackFindingAlgorithm.cpp @@ -40,15 +40,6 @@ ActsExamples::TrackFindingAlgorithm::TrackFindingAlgorithm( if (m_cfg.outputTrajectories.empty()) { throw std::invalid_argument("Missing trajectories output collection"); } - - if (m_cfg.outputTrackParameters.empty()) { - throw std::invalid_argument( - "Missing track parameter tips output collection"); - } - - if (m_cfg.outputTrackParametersTips.empty()) { - throw std::invalid_argument("Missing track parameters output collection"); - } } ActsExamples::ProcessCode ActsExamples::TrackFindingAlgorithm::execute( @@ -65,10 +56,6 @@ ActsExamples::ProcessCode ActsExamples::TrackFindingAlgorithm::execute( TrajectoriesContainer trajectories; trajectories.reserve(initialParameters.size()); - // Prepare the output data with TrackParameters - TrackParametersContainer trackParametersContainer; - std::vector> trackParametersTips; - // Construct a perigee surface as the target surface auto pSurface = Acts::Surface::makeShared( Acts::Vector3{0., 0., 0.}); @@ -129,14 +116,6 @@ ActsExamples::ProcessCode ActsExamples::TrackFindingAlgorithm::execute( trajectories.emplace_back(trackFindingOutput.fittedStates, trackFindingOutput.lastMeasurementIndices, trackFindingOutput.fittedParameters); - - const auto& traj = trajectories.back(); - for (const auto tip : traj.tips()) { - if (traj.hasTrackParameters(tip)) { - trackParametersContainer.push_back(traj.trackParameters(tip)); - trackParametersTips.push_back({trajectories.size() - 1, tip}); - } - } } else { ACTS_WARNING("Track finding failed for seed " << iseed << " with error" << result.error()); @@ -153,10 +132,6 @@ ActsExamples::ProcessCode ActsExamples::TrackFindingAlgorithm::execute( m_memoryStatistics.local().hist += mtj->statistics().hist; ctx.eventStore.add(m_cfg.outputTrajectories, std::move(trajectories)); - ctx.eventStore.add(m_cfg.outputTrackParameters, - std::move(trackParametersContainer)); - ctx.eventStore.add(m_cfg.outputTrackParametersTips, - std::move(trackParametersTips)); return ActsExamples::ProcessCode::SUCCESS; } diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.cpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.cpp index 0d960338545..646406d180d 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.cpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.cpp @@ -10,6 +10,7 @@ #include "Acts/Utilities/ThrowAssert.hpp" #include "ActsExamples/EventData/Track.hpp" +#include "ActsExamples/EventData/Trajectories.hpp" #include "ActsExamples/Framework/WhiteBoard.hpp" #include @@ -20,11 +21,9 @@ ActsExamples::TrackSelector::TrackSelector(const Config& config, Acts::Logging::Level level) : BareAlgorithm("TrackSelector", level), m_cfg(config) { - if (m_cfg.inputTrackParameters.empty()) { - throw std::invalid_argument("Missing input track parameters collection"); - } - if (m_cfg.outputTrackParameters.empty()) { - throw std::invalid_argument("Missing output track parameters collection"); + if (m_cfg.inputTrackParameters.empty() == m_cfg.inputTrajectories.empty()) { + throw std::invalid_argument( + "Exactly one of track parameters or trajectories input must be set"); } } @@ -55,49 +54,65 @@ ActsExamples::ProcessCode ActsExamples::TrackSelector::execute( m_cfg.timeMax); }; - // prepare input and output containers - const auto& inputTrackParameters = - ctx.eventStore.get(m_cfg.inputTrackParameters); - std::optional>> - inputTrackParametersTips; - if (!m_cfg.inputTrackParametersTips.empty()) { - inputTrackParametersTips = - ctx.eventStore.get>>( - m_cfg.inputTrackParametersTips); - throw_assert( - inputTrackParameters.size() == inputTrackParametersTips->size(), - "Track parameters tips count does not match track parameters count"); - } + if (!m_cfg.inputTrackParameters.empty()) { + const auto& inputTrackParameters = + ctx.eventStore.get( + m_cfg.inputTrackParameters); + TrackParametersContainer outputTrackParameters; + outputTrackParameters.reserve(inputTrackParameters.size()); - TrackParametersContainer outputTrackParameters; - std::vector> outputTrackParametersTips; - outputTrackParameters.reserve(inputTrackParameters.size()); - if (inputTrackParametersTips) { - outputTrackParametersTips.reserve(inputTrackParametersTips->size()); + // copy selected tracks and record initial track index + for (uint32_t i = 0; i < inputTrackParameters.size(); ++i) { + const auto& trk = inputTrackParameters[i]; + if (isValidTrack(trk)) { + outputTrackParameters.push_back(trk); + } + } + outputTrackParameters.shrink_to_fit(); + + ACTS_DEBUG("event " << ctx.eventNumber << " selected " + << outputTrackParameters.size() << " from " + << inputTrackParameters.size() + << " tracks in track parameters"); + + ctx.eventStore.add(m_cfg.outputTrackParameters, + std::move(outputTrackParameters)); } - // copy selected tracks and record initial track index - for (uint32_t i = 0; i < inputTrackParameters.size(); ++i) { - const auto& trk = inputTrackParameters[i]; - if (isValidTrack(trk)) { - outputTrackParameters.push_back(trk); - if (inputTrackParametersTips) { - outputTrackParametersTips.push_back((*inputTrackParametersTips)[i]); + if (!m_cfg.inputTrajectories.empty()) { + const auto& inputTrajectories = + ctx.eventStore.get(m_cfg.inputTrajectories); + TrajectoriesContainer outputTrajectories; + outputTrajectories.reserve(inputTrajectories.size()); + + std::size_t inputCount = 0; + std::size_t outputCount = 0; + for (const auto& trajectories : inputTrajectories) { + std::vector tips; + Trajectories::IndexedParameters parameters; + + for (auto tip : trajectories.tips()) { + if (!trajectories.hasTrackParameters(tip)) { + continue; + } + ++inputCount; + if (!isValidTrack(trajectories.trackParameters(tip))) { + continue; + } + tips.push_back(tip); + parameters.emplace(tip, trajectories.trackParameters(tip)); + ++outputCount; } + + outputTrajectories.emplace_back(trajectories.multiTrajectoryPtr(), tips, + parameters); } + + ACTS_DEBUG("event " << ctx.eventNumber << " selected " << outputCount + << " from " << inputCount << " tracks in trajectories"); + + ctx.eventStore.add(m_cfg.outputTrajectories, std::move(outputTrajectories)); } - outputTrackParameters.shrink_to_fit(); - outputTrackParametersTips.shrink_to_fit(); - - ACTS_DEBUG("event " << ctx.eventNumber << " selected " - << outputTrackParameters.size() << " from " - << inputTrackParameters.size() << " tracks"); - - ctx.eventStore.add(m_cfg.outputTrackParameters, - std::move(outputTrackParameters)); - if (inputTrackParametersTips) { - ctx.eventStore.add(m_cfg.outputTrackParametersTips, - std::move(outputTrackParametersTips)); - } + return ProcessCode::SUCCESS; } diff --git a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.hpp b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.hpp index 346cff21085..52a3dc081c7 100644 --- a/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.hpp +++ b/Examples/Algorithms/TruthTracking/ActsExamples/TruthTracking/TrackSelector.hpp @@ -19,14 +19,18 @@ namespace ActsExamples { class TrackSelector final : public BareAlgorithm { public: struct Config { - /// Input track parameters collection. + /// Optional. Input track parameters collection. Mutually exclusive with + /// trajectories input. std::string inputTrackParameters; - /// Optional. Input track parameters tips w.r.t a trajectories container. - std::string inputTrackParametersTips; - /// Output track parameters collection. + /// Optional. Input trajectories container. Mutually exclusive with track + /// parameters input. + std::string inputTrajectories; + /// Optional. Output track parameters collection. Will only be set if track + /// parameters input was set. std::string outputTrackParameters; - /// Optional. Output track parameters tips w.r.t a trajectories container. - std::string outputTrackParametersTips; + /// Optional. Output trajectories container. Will only be set if + /// trajectories input was set + std::string outputTrajectories; // Minimum/maximum local positions. double loc0Min = -std::numeric_limits::infinity(); diff --git a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/AdaptiveMultiVertexFinderAlgorithm.hpp b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/AdaptiveMultiVertexFinderAlgorithm.hpp index fea97645aa9..427d99a1b61 100644 --- a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/AdaptiveMultiVertexFinderAlgorithm.hpp +++ b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/AdaptiveMultiVertexFinderAlgorithm.hpp @@ -19,8 +19,10 @@ namespace ActsExamples { class AdaptiveMultiVertexFinderAlgorithm final : public BareAlgorithm { public: struct Config { - /// Input track parameters collection + /// Optional. Input track parameters collection std::string inputTrackParameters; + /// Optional. Input trajectories container. + std::string inputTrajectories; /// Output proto vertex collection std::string outputProtoVertices; /// Output vertex collection diff --git a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/IterativeVertexFinderAlgorithm.hpp b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/IterativeVertexFinderAlgorithm.hpp index c25976828ff..2b09b210f7e 100644 --- a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/IterativeVertexFinderAlgorithm.hpp +++ b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/IterativeVertexFinderAlgorithm.hpp @@ -19,8 +19,10 @@ namespace ActsExamples { class IterativeVertexFinderAlgorithm final : public BareAlgorithm { public: struct Config { - /// Input track parameters collection + /// Optional. Input track parameters collection std::string inputTrackParameters; + /// Optional. Input trajectories container. + std::string inputTrajectories; /// Output proto vertex collection std::string outputProtoVertices; /// Output vertex collection diff --git a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/TutorialVertexFinderAlgorithm.hpp b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/TutorialVertexFinderAlgorithm.hpp index 42e3bb2e592..1664f9845e1 100644 --- a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/TutorialVertexFinderAlgorithm.hpp +++ b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/TutorialVertexFinderAlgorithm.hpp @@ -19,8 +19,10 @@ namespace ActsExamples { class TutorialVertexFinderAlgorithm final : public BareAlgorithm { public: struct Config { - /// Input track parameters collection + /// Optional. Input track parameters collection std::string inputTrackParameters; + /// Optional. Input trajectories container. + std::string inputTrajectories; /// Output proto vertex collection std::string outputProtoVertices; /// The magnetic field diff --git a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/VertexFitterAlgorithm.hpp b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/VertexFitterAlgorithm.hpp index fc9374cfb3c..1ab2b0a750a 100644 --- a/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/VertexFitterAlgorithm.hpp +++ b/Examples/Algorithms/Vertexing/include/ActsExamples/Vertexing/VertexFitterAlgorithm.hpp @@ -20,8 +20,10 @@ namespace ActsExamples { class VertexFitterAlgorithm final : public BareAlgorithm { public: struct Config { - /// Input track parameters collection + /// Optional. Input track parameters collection std::string inputTrackParameters; + /// Optional. Input trajectories container. + std::string inputTrajectories; /// Input proto vertex collection std::string inputProtoVertices; /// Output vertex collection diff --git a/Examples/Algorithms/Vertexing/src/AdaptiveMultiVertexFinderAlgorithm.cpp b/Examples/Algorithms/Vertexing/src/AdaptiveMultiVertexFinderAlgorithm.cpp index e830aa6a53d..530a597f698 100644 --- a/Examples/Algorithms/Vertexing/src/AdaptiveMultiVertexFinderAlgorithm.cpp +++ b/Examples/Algorithms/Vertexing/src/AdaptiveMultiVertexFinderAlgorithm.cpp @@ -26,6 +26,7 @@ #include "Acts/Vertexing/VertexingOptions.hpp" #include "ActsExamples/EventData/ProtoVertex.hpp" #include "ActsExamples/EventData/Track.hpp" +#include "ActsExamples/EventData/Trajectories.hpp" #include "ActsExamples/Framework/WhiteBoard.hpp" #include @@ -37,8 +38,9 @@ ActsExamples::AdaptiveMultiVertexFinderAlgorithm:: Acts::Logging::Level level) : ActsExamples::BareAlgorithm("AdaptiveMultiVertexFinder", level), m_cfg(config) { - if (m_cfg.inputTrackParameters.empty()) { - throw std::invalid_argument("Missing input track parameters collection"); + if (m_cfg.inputTrackParameters.empty() == m_cfg.inputTrajectories.empty()) { + throw std::invalid_argument( + "You have to either provide track parameters or trajectories"); } if (m_cfg.outputProtoVertices.empty()) { throw std::invalid_argument("Missing output proto vertices collection"); @@ -55,10 +57,9 @@ ActsExamples::ProcessCode ActsExamples::AdaptiveMultiVertexFinderAlgorithm::execute( const ActsExamples::AlgorithmContext& ctx) const { // retrieve input tracks and convert into the expected format - const auto& inputTrackParameters = - ctx.eventStore.get(m_cfg.inputTrackParameters); - const auto& inputTrackPointers = - makeTrackParametersPointerContainer(inputTrackParameters); + + auto [inputTrackParameters, inputTrackPointers] = + makeParameterContainers(m_cfg, ctx); ////////////////////////////////////////////// /* Full tutorial example code for reference */ diff --git a/Examples/Algorithms/Vertexing/src/IterativeVertexFinderAlgorithm.cpp b/Examples/Algorithms/Vertexing/src/IterativeVertexFinderAlgorithm.cpp index e54af812586..91475e54af1 100644 --- a/Examples/Algorithms/Vertexing/src/IterativeVertexFinderAlgorithm.cpp +++ b/Examples/Algorithms/Vertexing/src/IterativeVertexFinderAlgorithm.cpp @@ -28,6 +28,7 @@ #include "Acts/Vertexing/ZScanVertexFinder.hpp" #include "ActsExamples/EventData/ProtoVertex.hpp" #include "ActsExamples/EventData/Track.hpp" +#include "ActsExamples/EventData/Trajectories.hpp" #include "ActsExamples/Framework/RandomNumbers.hpp" #include "ActsExamples/Framework/WhiteBoard.hpp" @@ -39,8 +40,9 @@ ActsExamples::IterativeVertexFinderAlgorithm::IterativeVertexFinderAlgorithm( const Config& config, Acts::Logging::Level level) : ActsExamples::BareAlgorithm("IterativeVertexFinder", level), m_cfg(config) { - if (m_cfg.inputTrackParameters.empty()) { - throw std::invalid_argument("Missing input track parameters collection"); + if (m_cfg.inputTrackParameters.empty() == m_cfg.inputTrajectories.empty()) { + throw std::invalid_argument( + "You have to either provide track parameters or trajectories"); } if (m_cfg.outputProtoVertices.empty()) { throw std::invalid_argument("Missing output proto vertices collection"); @@ -56,10 +58,9 @@ ActsExamples::IterativeVertexFinderAlgorithm::IterativeVertexFinderAlgorithm( ActsExamples::ProcessCode ActsExamples::IterativeVertexFinderAlgorithm::execute( const ActsExamples::AlgorithmContext& ctx) const { // retrieve input tracks and convert into the expected format - const auto& inputTrackParameters = - ctx.eventStore.get(m_cfg.inputTrackParameters); - const auto& inputTrackPointers = - makeTrackParametersPointerContainer(inputTrackParameters); + + auto [inputTrackParameters, inputTrackPointers] = + makeParameterContainers(m_cfg, ctx); using Propagator = Acts::Propagator>; using PropagatorOptions = Acts::PropagatorOptions<>; diff --git a/Examples/Algorithms/Vertexing/src/TutorialVertexFinderAlgorithm.cpp b/Examples/Algorithms/Vertexing/src/TutorialVertexFinderAlgorithm.cpp index 9a8a2577b7b..e0b3efa5dd1 100644 --- a/Examples/Algorithms/Vertexing/src/TutorialVertexFinderAlgorithm.cpp +++ b/Examples/Algorithms/Vertexing/src/TutorialVertexFinderAlgorithm.cpp @@ -30,8 +30,9 @@ ActsExamples::TutorialVertexFinderAlgorithm::TutorialVertexFinderAlgorithm( const Config& cfg, Acts::Logging::Level lvl) : ActsExamples::BareAlgorithm("TutorialVertexFinder", lvl), m_cfg(cfg) { - if (m_cfg.inputTrackParameters.empty()) { - throw std::invalid_argument("Missing input track parameters collection"); + if (m_cfg.inputTrackParameters.empty() == m_cfg.inputTrajectories.empty()) { + throw std::invalid_argument( + "You have to either provide track parameters or trajectories"); } if (m_cfg.outputProtoVertices.empty()) { throw std::invalid_argument("Missing output proto vertices collection"); diff --git a/Examples/Algorithms/Vertexing/src/VertexFitterAlgorithm.cpp b/Examples/Algorithms/Vertexing/src/VertexFitterAlgorithm.cpp index 3d24f360981..a772a180c3a 100644 --- a/Examples/Algorithms/Vertexing/src/VertexFitterAlgorithm.cpp +++ b/Examples/Algorithms/Vertexing/src/VertexFitterAlgorithm.cpp @@ -20,15 +20,19 @@ #include "Acts/Vertexing/VertexingOptions.hpp" #include "ActsExamples/EventData/ProtoVertex.hpp" #include "ActsExamples/EventData/Track.hpp" +#include "ActsExamples/EventData/Trajectories.hpp" #include "ActsExamples/Framework/WhiteBoard.hpp" #include +#include "VertexingHelpers.hpp" + ActsExamples::VertexFitterAlgorithm::VertexFitterAlgorithm( const Config& cfg, Acts::Logging::Level lvl) : ActsExamples::BareAlgorithm("VertexFit", lvl), m_cfg(cfg) { - if (m_cfg.inputTrackParameters.empty()) { - throw std::invalid_argument("Missing input track parameters collection"); + if (m_cfg.inputTrackParameters.empty() == m_cfg.inputTrajectories.empty()) { + throw std::invalid_argument( + "You have to either provide track parameters or trajectories"); } if (m_cfg.inputProtoVertices.empty()) { throw std::invalid_argument("Missing input proto vertices collection"); @@ -63,9 +67,10 @@ ActsExamples::ProcessCode ActsExamples::VertexFitterAlgorithm::execute( ACTS_VERBOSE("Read from '" << m_cfg.inputTrackParameters << "'"); ACTS_VERBOSE("Read from '" << m_cfg.inputProtoVertices << "'"); - const auto& trackParameters = - ctx.eventStore.get(m_cfg.inputTrackParameters); - ACTS_VERBOSE("Have " << trackParameters.size() << " track parameters"); + auto [inputTrackParameters, inputTrackPointers] = + makeParameterContainers(m_cfg, ctx); + + ACTS_VERBOSE("Have " << inputTrackParameters.size() << " track parameters"); const auto& protoVertices = ctx.eventStore.get(m_cfg.inputProtoVertices); ACTS_VERBOSE("Have " << protoVertices.size() << " proto vertices"); @@ -87,12 +92,12 @@ ActsExamples::ProcessCode ActsExamples::VertexFitterAlgorithm::execute( inputTrackPtrCollection.clear(); inputTrackPtrCollection.reserve(protoVertex.size()); for (const auto& trackIdx : protoVertex) { - if (trackIdx >= trackParameters.size()) { + if (trackIdx >= inputTrackParameters.size()) { ACTS_ERROR("track parameters " << trackIdx << " does not exist"); continue; } - inputTrackPtrCollection.push_back(&trackParameters[trackIdx]); + inputTrackPtrCollection.push_back(inputTrackPointers[trackIdx]); } if (!m_cfg.doConstrainedFit) { diff --git a/Examples/Algorithms/Vertexing/src/VertexingHelpers.hpp b/Examples/Algorithms/Vertexing/src/VertexingHelpers.hpp index 5d915c9e833..f0527f163b3 100644 --- a/Examples/Algorithms/Vertexing/src/VertexingHelpers.hpp +++ b/Examples/Algorithms/Vertexing/src/VertexingHelpers.hpp @@ -11,6 +11,7 @@ #include "Acts/Vertexing/Vertex.hpp" #include "ActsExamples/EventData/ProtoVertex.hpp" #include "ActsExamples/EventData/Track.hpp" +#include "ActsExamples/EventData/Trajectories.hpp" #include @@ -32,6 +33,37 @@ makeTrackParametersPointerContainer( return trackParametersPointers; } +template +auto makeParameterContainers(const config_t& config, + const ActsExamples::AlgorithmContext& ctx) { + std::vector inputTrackParameters; + std::vector inputTrackPointers; + + if (!config.inputTrackParameters.empty()) { + const auto& tmp = + ctx.eventStore.get>( + config.inputTrackParameters); + inputTrackParameters = tmp; + inputTrackPointers = makeTrackParametersPointerContainer(tmp); + } else { + const auto& inputTrajectories = + ctx.eventStore.get(config.inputTrajectories); + + for (const auto& trajectories : inputTrajectories) { + for (auto tip : trajectories.tips()) { + if (!trajectories.hasTrackParameters(tip)) { + continue; + } + const auto& trackParam = trajectories.trackParameters(tip); + inputTrackParameters.push_back(trackParam); + inputTrackPointers.push_back(&trackParam); + } + } + } + + return std::make_tuple(inputTrackParameters, inputTrackPointers); +} + /// Create proto vertices from reconstructed vertices. /// /// @param trackParameters input track parameters container diff --git a/Examples/Framework/include/ActsExamples/EventData/Trajectories.hpp b/Examples/Framework/include/ActsExamples/EventData/Trajectories.hpp index e09f310122e..d8031ed4c17 100644 --- a/Examples/Framework/include/ActsExamples/EventData/Trajectories.hpp +++ b/Examples/Framework/include/ActsExamples/EventData/Trajectories.hpp @@ -56,6 +56,11 @@ struct Trajectories final { /// Access the underlying multi trajectory. const MultiTrajectory& multiTrajectory() const { return *m_multiTrajectory; } + /// Access the underlying multi trajectory pointer. + const std::shared_ptr& multiTrajectoryPtr() const { + return m_multiTrajectory; + } + /// Access the tip indices that identify valid trajectories. const std::vector& tips() const { return m_trackTips; diff --git a/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.cpp b/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.cpp index ad96cb8b80c..c2f9da6593b 100644 --- a/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.cpp +++ b/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.cpp @@ -30,10 +30,6 @@ ActsExamples::CKFPerformanceWriter::CKFPerformanceWriter( m_duplicationPlotTool(m_cfg.duplicationPlotToolConfig, lvl), m_trackSummaryPlotTool(m_cfg.trackSummaryPlotToolConfig, lvl) { // trajectories collection name is already checked by base ctor - if (m_cfg.inputTrackParametersTips.empty()) { - throw std::invalid_argument( - "Missing track parameters tips input collection"); - } if (m_cfg.inputParticles.empty()) { throw std::invalid_argument("Missing particles input collection"); } @@ -129,9 +125,6 @@ ActsExamples::ProcessCode ActsExamples::CKFPerformanceWriter::writeT( using RecoTrackInfo = std::pair; using Acts::VectorHelpers::perp; - const auto& trackTips = - ctx.eventStore.get>>( - m_cfg.inputTrackParametersTips); // Read truth input collections const auto& particles = ctx.eventStore.get(m_cfg.inputParticles); @@ -152,87 +145,90 @@ ActsExamples::ProcessCode ActsExamples::CKFPerformanceWriter::writeT( std::vector inputFeatures(3); // Loop over all trajectories - for (auto [itraj, trackTip] : trackTips) { - const auto& traj = trajectories[itraj]; + for (std::size_t iTraj = 0; iTraj < trajectories.size(); ++iTraj) { + const auto& traj = trajectories[iTraj]; const auto& mj = traj.multiTrajectory(); - auto trajState = - Acts::MultiTrajectoryHelpers::trajectoryState(mj, trackTip); + for (auto trackTip : traj.tips()) { + auto trajState = + Acts::MultiTrajectoryHelpers::trajectoryState(mj, trackTip); - // Reco track selection - //@TODO: add interface for applying others cuts on reco tracks: - // -> pT, d0, z0, detector-specific hits/holes number cut - if (trajState.nMeasurements < m_cfg.nMeasurementsMin) { - continue; - } - // Check if the reco track has fitted track parameters - if (not traj.hasTrackParameters(trackTip)) { - ACTS_WARNING( - "No fitted track parameters for trajectory with entry index = " - << trackTip); - continue; - } - const auto& fittedParameters = traj.trackParameters(trackTip); - // Requirement on the pT of the track - const auto& momentum = fittedParameters.momentum(); - const auto pT = perp(momentum); - if (pT < m_cfg.ptMin) { - continue; - } - // Fill the trajectory summary info - m_trackSummaryPlotTool.fill(m_trackSummaryPlotCache, fittedParameters, - trajState.nStates, trajState.nMeasurements, - trajState.nOutliers, trajState.nHoles, - trajState.nSharedHits); + // Reco track selection + //@TODO: add interface for applying others cuts on reco tracks: + // -> pT, d0, z0, detector-specific hits/holes number cut + if (trajState.nMeasurements < m_cfg.nMeasurementsMin) { + continue; + } + // Check if the reco track has fitted track parameters + if (not traj.hasTrackParameters(trackTip)) { + ACTS_WARNING( + "No fitted track parameters for trajectory with entry index = " + << trackTip); + continue; + } + const auto& fittedParameters = traj.trackParameters(trackTip); + // Requirement on the pT of the track + const auto& momentum = fittedParameters.momentum(); + const auto pT = perp(momentum); + if (pT < m_cfg.ptMin) { + continue; + } + // Fill the trajectory summary info + m_trackSummaryPlotTool.fill(m_trackSummaryPlotCache, fittedParameters, + trajState.nStates, trajState.nMeasurements, + trajState.nOutliers, trajState.nHoles, + trajState.nSharedHits); - // Get the majority truth particle to this track - identifyContributingParticles(hitParticlesMap, traj, trackTip, - particleHitCounts); - if (particleHitCounts.empty()) { - ACTS_WARNING( - "No truth particle associated with this trajectory with entry " - "index = " - << trackTip); - continue; - } - // Get the majority particleId and majority particle counts - // Note that the majority particle might be not in the truth seeds - // collection - ActsFatras::Barcode majorityParticleId = - particleHitCounts.front().particleId; - size_t nMajorityHits = particleHitCounts.front().hitCount; + // Get the majority truth particle to this track + identifyContributingParticles(hitParticlesMap, traj, trackTip, + particleHitCounts); + if (particleHitCounts.empty()) { + ACTS_WARNING( + "No truth particle associated with this trajectory with entry " + "index = " + << trackTip); + continue; + } + // Get the majority particleId and majority particle counts + // Note that the majority particle might be not in the truth seeds + // collection + ActsFatras::Barcode majorityParticleId = + particleHitCounts.front().particleId; + size_t nMajorityHits = particleHitCounts.front().hitCount; - // Check if the trajectory is matched with truth. - // If not, it will be classified as 'fake' - bool isFake = false; - if (nMajorityHits * 1. / trajState.nMeasurements >= - m_cfg.truthMatchProbMin) { - matched[majorityParticleId].push_back({nMajorityHits, fittedParameters}); - } else { - isFake = true; - unmatched[majorityParticleId]++; - } - // Fill fake rate plots - m_fakeRatePlotTool.fill(m_fakeRatePlotCache, fittedParameters, isFake); + // Check if the trajectory is matched with truth. + // If not, it will be classified as 'fake' + bool isFake = false; + if (nMajorityHits * 1. / trajState.nMeasurements >= + m_cfg.truthMatchProbMin) { + matched[majorityParticleId].push_back( + {nMajorityHits, fittedParameters}); + } else { + isFake = true; + unmatched[majorityParticleId]++; + } + // Fill fake rate plots + m_fakeRatePlotTool.fill(m_fakeRatePlotCache, fittedParameters, isFake); - // Use neural network classification for duplication rate plots - // Currently, the network used for this example can only handle - // good/duplicate classification, so need to manually exclude fake tracks - if (m_cfg.duplicatedPredictor && !isFake) { - inputFeatures[0] = trajState.nMeasurements; - inputFeatures[1] = trajState.nOutliers; - inputFeatures[2] = trajState.chi2Sum * 1.0 / trajState.NDF; - // predict if current trajectory is 'duplicate' - bool isDuplicated = m_cfg.duplicatedPredictor(inputFeatures); - // Add to number of duplicated particles - if (isDuplicated) { - m_nTotalDuplicateTracks++; + // Use neural network classification for duplication rate plots + // Currently, the network used for this example can only handle + // good/duplicate classification, so need to manually exclude fake tracks + if (m_cfg.duplicatedPredictor && !isFake) { + inputFeatures[0] = trajState.nMeasurements; + inputFeatures[1] = trajState.nOutliers; + inputFeatures[2] = trajState.chi2Sum * 1.0 / trajState.NDF; + // predict if current trajectory is 'duplicate' + bool isDuplicated = m_cfg.duplicatedPredictor(inputFeatures); + // Add to number of duplicated particles + if (isDuplicated) { + m_nTotalDuplicateTracks++; + } + // Fill the duplication rate + m_duplicationPlotTool.fill(m_duplicationPlotCache, fittedParameters, + isDuplicated); } - // Fill the duplication rate - m_duplicationPlotTool.fill(m_duplicationPlotCache, fittedParameters, - isDuplicated); + // Counting number of total trajectories + m_nTotalTracks++; } - // Counting number of total trajectories - m_nTotalTracks++; } // Use truth-based classification for duplication rate plots diff --git a/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.hpp b/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.hpp index a544214a826..75b2f640f37 100644 --- a/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.hpp +++ b/Examples/Io/Performance/ActsExamples/Io/Performance/CKFPerformanceWriter.hpp @@ -36,8 +36,6 @@ class CKFPerformanceWriter final : public WriterT { struct Config { /// Input (found) trajectories collection. std::string inputTrajectories; - /// Input track parameters tips w.r.t inputTrajectories. - std::string inputTrackParametersTips; /// Input particles collection. std::string inputParticles; /// Input hit-particles map collection. diff --git a/Examples/Io/Performance/ActsExamples/Io/Performance/TrackFitterPerformanceWriter.cpp b/Examples/Io/Performance/ActsExamples/Io/Performance/TrackFitterPerformanceWriter.cpp index 495013ea8f4..a1220213aa1 100644 --- a/Examples/Io/Performance/ActsExamples/Io/Performance/TrackFitterPerformanceWriter.cpp +++ b/Examples/Io/Performance/ActsExamples/Io/Performance/TrackFitterPerformanceWriter.cpp @@ -105,11 +105,6 @@ ActsExamples::ProcessCode ActsExamples::TrackFitterPerformanceWriter::writeT( for (size_t itraj = 0; itraj < trajectories.size(); ++itraj) { const auto& traj = trajectories[itraj]; - if (traj.empty()) { - ACTS_WARNING("Empty trajectories object " << itraj); - continue; - } - // The trajectory entry indices and the multiTrajectory const auto& trackTips = traj.tips(); const auto& mj = traj.multiTrajectory(); diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootVertexPerformanceWriter.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootVertexPerformanceWriter.hpp index ee13cbf6f06..db1012c8284 100644 --- a/Examples/Io/Root/include/ActsExamples/Io/Root/RootVertexPerformanceWriter.hpp +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootVertexPerformanceWriter.hpp @@ -37,15 +37,12 @@ class RootVertexPerformanceWriter final std::string inputAllTruthParticles; /// Selected input truth particle collection. std::string inputSelectedTruthParticles; + /// Optional. Input track parameters. + std::string inputTrackParameters; /// Optional. Truth particles associated to tracks. Using 1:1 matching if /// given. std::string inputAssociatedTruthParticles; - /// Input track parameters. - std::string inputTrackParameters; - /// Input track parameters tips (points from `inputTrackParameters` to - /// `inputTrajectories`). - std::string inputTrackParametersTips; - /// Trajectories object from track finidng. + /// Optional. Trajectories object from track finidng. std::string inputTrajectories; /// Input hit-particles map collection. std::string inputMeasurementParticlesMap; diff --git a/Examples/Io/Root/src/RootTrajectoryStatesWriter.cpp b/Examples/Io/Root/src/RootTrajectoryStatesWriter.cpp index d806e31b86a..3398376717b 100644 --- a/Examples/Io/Root/src/RootTrajectoryStatesWriter.cpp +++ b/Examples/Io/Root/src/RootTrajectoryStatesWriter.cpp @@ -266,11 +266,6 @@ ActsExamples::ProcessCode ActsExamples::RootTrajectoryStatesWriter::writeT( for (size_t itraj = 0; itraj < trajectories.size(); ++itraj) { const auto& traj = trajectories[itraj]; - if (traj.empty()) { - ACTS_WARNING("Empty trajectories object " << itraj); - continue; - } - // The trajectory index m_multiTrajNr = itraj; diff --git a/Examples/Io/Root/src/RootTrajectorySummaryWriter.cpp b/Examples/Io/Root/src/RootTrajectorySummaryWriter.cpp index 182c0b24d7a..12103d183a6 100644 --- a/Examples/Io/Root/src/RootTrajectorySummaryWriter.cpp +++ b/Examples/Io/Root/src/RootTrajectorySummaryWriter.cpp @@ -171,11 +171,6 @@ ActsExamples::ProcessCode ActsExamples::RootTrajectorySummaryWriter::writeT( for (size_t itraj = 0; itraj < trajectories.size(); ++itraj) { const auto& traj = trajectories[itraj]; - if (traj.empty()) { - ACTS_WARNING("Empty trajectories object " << itraj); - continue; - } - // The trajectory index m_multiTrajNr.push_back(itraj); diff --git a/Examples/Io/Root/src/RootVertexPerformanceWriter.cpp b/Examples/Io/Root/src/RootVertexPerformanceWriter.cpp index 65ba23d7c12..662a4e0ce28 100644 --- a/Examples/Io/Root/src/RootVertexPerformanceWriter.cpp +++ b/Examples/Io/Root/src/RootVertexPerformanceWriter.cpp @@ -19,6 +19,7 @@ #include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/EventData/SimHit.hpp" #include "ActsExamples/EventData/SimParticle.hpp" +#include "ActsExamples/EventData/Trajectories.hpp" #include "ActsExamples/Utilities/Paths.hpp" #include "ActsExamples/Utilities/Range.hpp" #include "ActsExamples/Validation/TrackClassification.hpp" @@ -52,19 +53,6 @@ ActsExamples::RootVertexPerformanceWriter::RootVertexPerformanceWriter( throw std::invalid_argument( "Collection with selected truth particles missing"); } - if (m_cfg.inputAssociatedTruthParticles.empty() && - (m_cfg.inputTrackParametersTips.empty() || - m_cfg.inputTrajectories.empty())) { - throw std::invalid_argument( - "You need to either provide collection of truth particles matching 1:1 " - "to tracks, or track indices and all-tips container to do truth " - "matching"); - } - - if (m_cfg.inputTrackParameters.empty()) { - throw std::invalid_argument( - "Collection with all fitted track parameters missing"); - } // Setup ROOT I/O auto path = m_cfg.filePath; @@ -201,16 +189,28 @@ ActsExamples::ProcessCode ActsExamples::RootVertexPerformanceWriter::writeT( ACTS_VERBOSE("Total number of detector-accepted truth primary vertices : " << m_nVtxDetAcceptance); - const auto& trackParameters = - ctx.eventStore.get>( - m_cfg.inputTrackParameters); - - ACTS_VERBOSE( - "Total number of reconstructed tracks : " << trackParameters.size()); - + std::vector trackParameters; SimParticleContainer associatedTruthParticles; if (!m_cfg.inputAssociatedTruthParticles.empty()) { + if (!m_cfg.inputTrackParameters.empty()) { + trackParameters = + ctx.eventStore.get>( + m_cfg.inputTrackParameters); + } else { + const auto& inputTrajectories = + ctx.eventStore.get(m_cfg.inputTrajectories); + + for (const auto& trajectories : inputTrajectories) { + for (auto tip : trajectories.tips()) { + if (!trajectories.hasTrackParameters(tip)) { + continue; + } + trackParameters.push_back(trajectories.trackParameters(tip)); + } + } + } + // Read track-associated truth particle input collection associatedTruthParticles = ctx.eventStore.get( m_cfg.inputAssociatedTruthParticles); @@ -238,11 +238,8 @@ ActsExamples::ProcessCode ActsExamples::RootVertexPerformanceWriter::writeT( } } else { // get active tips - const auto& trajectories = + const auto& inputTrajectories = ctx.eventStore.get(m_cfg.inputTrajectories); - const auto& trackTips = - ctx.eventStore.get>>( - m_cfg.inputTrackParametersTips); std::vector particleHitCounts; @@ -250,38 +247,47 @@ ActsExamples::ProcessCode ActsExamples::RootVertexPerformanceWriter::writeT( const auto& hitParticlesMap = ctx.eventStore.get(m_cfg.inputMeasurementParticlesMap); - for (size_t i = 0; i < trackParameters.size(); i++) { - auto& [iTraj, tip] = trackTips[i]; - const auto& traj = trajectories[iTraj]; - identifyContributingParticles(hitParticlesMap, traj, tip, - particleHitCounts); - ActsFatras::Barcode majorityParticleId = - particleHitCounts.front().particleId; - size_t nMajorityHits = particleHitCounts.front().hitCount; - - auto trajState = Acts::MultiTrajectoryHelpers::trajectoryState( - traj.multiTrajectory(), tip); - - if (nMajorityHits * 1. / trajState.nMeasurements < - m_cfg.truthMatchProbMin) { - continue; - } + for (const auto& trajectories : inputTrajectories) { + for (auto tip : trajectories.tips()) { + if (!trajectories.hasTrackParameters(tip)) { + continue; + } - auto it = std::find_if(allTruthParticles.begin(), allTruthParticles.end(), - [&](const auto& tp) { - return tp.particleId() == majorityParticleId; - }); + trackParameters.push_back(trajectories.trackParameters(tip)); - if (it == allTruthParticles.end()) { - continue; - } + identifyContributingParticles(hitParticlesMap, trajectories, tip, + particleHitCounts); + ActsFatras::Barcode majorityParticleId = + particleHitCounts.front().particleId; + size_t nMajorityHits = particleHitCounts.front().hitCount; + + auto trajState = Acts::MultiTrajectoryHelpers::trajectoryState( + trajectories.multiTrajectory(), tip); + + if (nMajorityHits * 1. / trajState.nMeasurements < + m_cfg.truthMatchProbMin) { + continue; + } + + auto it = std::find_if(allTruthParticles.begin(), + allTruthParticles.end(), [&](const auto& tp) { + return tp.particleId() == majorityParticleId; + }); - const auto& majorityParticle = *it; - associatedTruthParticles.emplace_hint(associatedTruthParticles.end(), - majorityParticle); + if (it == allTruthParticles.end()) { + continue; + } + + const auto& majorityParticle = *it; + associatedTruthParticles.emplace_hint(associatedTruthParticles.end(), + majorityParticle); + } } } + ACTS_VERBOSE( + "Total number of reconstructed tracks : " << trackParameters.size()); + // Get number of track-associated true primary vertices m_nVtxReconstructable = getNumberOfReconstructableVertices(associatedTruthParticles); diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index 37196b67093..5a7334c0e1b 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -619,7 +619,7 @@ def addKalmanTracks( inputSourceLinks="sourcelinks", inputProtoTracks=inputProtoTracks, inputInitialTrackParameters="estimatedparameters", - outputTrajectories="trajectories", + outputTrajectories="kfTrajectories", directNavigation=directNavigation, pickTrack=-1, trackingGeometry=trackingGeometry, @@ -629,6 +629,8 @@ def addKalmanTracks( ) s.addAlgorithm(fitAlg) + s.addWhiteboardAlias("trajectories", fitAlg.config.outputTrajectories) + return s @@ -729,9 +731,7 @@ def addCKFTracks( inputMeasurements="measurements", inputSourceLinks="sourcelinks", inputInitialTrackParameters="estimatedparameters", - outputTrajectories="trajectories", - outputTrackParameters="fittedTrackParameters", - outputTrackParametersTips="fittedTrackParametersTips", + outputTrajectories="ckfTrajectories", findTracks=acts.examples.TrackFindingAlgorithm.makeTrackFinderFunction( trackingGeometry, field ), @@ -739,28 +739,17 @@ def addCKFTracks( s.addAlgorithm(trackFinder) s.addWhiteboardAlias("trajectories", trackFinder.config.outputTrajectories) - s.addWhiteboardAlias("trackParameters", trackFinder.config.outputTrackParameters) - s.addWhiteboardAlias( - "trackParametersTips", trackFinder.config.outputTrackParametersTips - ) if trackSelectorRanges is not None: trackSelector = addTrackSelection( s, trackSelectorRanges, - inputTrackParameters=trackFinder.config.outputTrackParameters, - inputTrackParametersTips=trackFinder.config.outputTrackParametersTips, - outputTrackParameters="selectedFittedTrackParameters", - outputTrackParametersTips="selectedFittedTrackParametersTips", + inputTrajectories=trackFinder.config.outputTrajectories, + outputTrajectories="selectedTrajectories", logLevel=customLogLevel(), ) - s.addWhiteboardAlias( - "trackParameters", trackSelector.config.outputTrackParameters - ) - s.addWhiteboardAlias( - "trackParametersTips", trackSelector.config.outputTrackParametersTips - ) + s.addWhiteboardAlias("trajectories", trackSelector.config.outputTrajectories) if outputDirRoot is not None: outputDirRoot = Path(outputDirRoot) @@ -805,7 +794,6 @@ def addCKFTracks( level=customLogLevel(), inputParticles=selectedParticles, inputTrajectories=trackFinder.config.outputTrajectories, - inputTrackParametersTips=trackFinder.config.outputTrackParametersTips, inputMeasurementParticlesMap="measurement_particles_map", **acts.examples.defaultKWArgs( # The bottom seed could be the first, second or third hits on the truth track @@ -839,10 +827,10 @@ def addCKFTracks( def addTrackSelection( s: acts.examples.Sequencer, trackSelectorRanges: TrackSelectorRanges, - inputTrackParameters: str, - inputTrackParametersTips: str, - outputTrackParameters: str, - outputTrackParametersTips: str, + inputTrackParameters: Optional[str] = None, + inputTrajectories: Optional[str] = None, + outputTrackParameters: Optional[str] = None, + outputTrajectories: Optional[str] = None, logLevel: Optional[acts.logging.Level] = None, ) -> acts.examples.TrackSelector: @@ -850,10 +838,14 @@ def addTrackSelection( trackSelector = acts.examples.TrackSelector( level=customLogLevel(), - inputTrackParameters=inputTrackParameters, - inputTrackParametersTips=inputTrackParametersTips, - outputTrackParameters=outputTrackParameters, - outputTrackParametersTips=outputTrackParametersTips, + inputTrackParameters=inputTrackParameters + if inputTrackParameters is not None + else "", + inputTrajectories=inputTrajectories if inputTrajectories is not None else "", + outputTrackParameters=outputTrackParameters + if outputTrackParameters is not None + else "", + outputTrajectories=outputTrajectories if outputTrajectories is not None else "", **acts.examples.defaultKWArgs( loc0Min=trackSelectorRanges.loc0[0], loc0Max=trackSelectorRanges.loc0[1], @@ -990,18 +982,14 @@ def addAmbiguityResolution( level=customLogLevel(), inputSourceLinks="sourcelinks", inputTrajectories="trajectories", - inputTrackParameters="trackParameters", - inputTrackParametersTips="trackParametersTips", - outputTrackParameters="filteredTrackParameters", - outputTrackParametersTips="filteredTrackParametersTips", + outputTrajectories="filteredTrajectories", **acts.examples.defaultKWArgs( maximumSharedHits=config.maximumSharedHits, ), ) s.addAlgorithm(alg) - s.addWhiteboardAlias("trackParameters", alg.config.outputTrackParameters) - s.addWhiteboardAlias("trackParametersTips", alg.config.outputTrackParametersTips) + s.addWhiteboardAlias("trajectories", alg.config.outputTrajectories) if outputDirRoot is not None: outputDirRoot = Path(outputDirRoot) @@ -1012,7 +1000,6 @@ def addAmbiguityResolution( level=customLogLevel(), inputParticles="truth_seeds_selected", inputTrajectories=alg.config.inputTrajectories, - inputTrackParametersTips=alg.config.outputTrackParametersTips, inputMeasurementParticlesMap="measurement_particles_map", **acts.examples.defaultKWArgs( nMeasurementsMin=ckfPerformanceConfigArg.nMeasurementsMin, @@ -1039,10 +1026,9 @@ def addVertexFitting( s, field, outputDirRoot: Optional[Union[Path, str]] = None, - associatedParticles: Optional[str] = None, trajectories: Optional[str] = "trajectories", - trackParameters: str = "trackParameters", - trackParametersTips: Optional[str] = "trackParametersTips", + trackParameters: Optional[str] = None, + associatedParticles: Optional[str] = None, vertexFinder: VertexFinder = VertexFinder.Truth, trackSelectorRanges: Optional[TrackSelectorRanges] = None, logLevel: Optional[acts.logging.Level] = None, @@ -1072,6 +1058,10 @@ def addVertexFitting( RootVertexPerformanceWriter, ) + trajectories = trajectories if trajectories is not None else "" + trackParameters = trackParameters if trackParameters is not None else "" + associatedParticles = associatedParticles if associatedParticles is not None else "" + customLogLevel = acts.examples.defaultLogging(s, logLevel) if trackSelectorRanges is not None: @@ -1079,17 +1069,15 @@ def addVertexFitting( s, trackSelectorRanges, inputTrackParameters=trackParameters, - inputTrackParametersTips=trackParametersTips, + inputTrajectories=trajectories, outputTrackParameters="selectedTrackParametersVertexing", - outputTrackParametersTips="selectedTrackParametersTipsVertexing", + outputTrajectories="selectedTrajectoriesVertexing", logLevel=customLogLevel(), ) - trackParameters = trackSelector.config.outputTrackParameters - trackParametersTips = ( - trackSelector.config.outputTrackParametersTips - if trackParametersTips is not None - else None + trajectories = trackSelector.config.outputTrajectories if trajectories else "" + trackParameters = ( + trackSelector.config.outputTrackParameters if trackParameters else "" ) inputParticles = "particles_input" @@ -1108,6 +1096,7 @@ def addVertexFitting( fitVertices = VertexFitterAlgorithm( level=customLogLevel(), bField=field, + inputTrajectories=trajectories, inputTrackParameters=trackParameters, inputProtoVertices=findVertices.config.outputProtoVertices, outputVertices=outputVertices, @@ -1117,6 +1106,7 @@ def addVertexFitting( findVertices = IterativeVertexFinderAlgorithm( level=customLogLevel(), bField=field, + inputTrajectories=trajectories, inputTrackParameters=trackParameters, outputProtoVertices="protovertices", outputVertices=outputVertices, @@ -1127,6 +1117,7 @@ def addVertexFitting( findVertices = AdaptiveMultiVertexFinderAlgorithm( level=customLogLevel(), bField=field, + inputTrajectories=trajectories, inputTrackParameters=trackParameters, outputProtoVertices="protovertices", outputVertices=outputVertices, @@ -1150,17 +1141,12 @@ def addVertexFitting( level=customLogLevel(), inputAllTruthParticles=inputParticles, inputSelectedTruthParticles=selectedParticles, - inputAssociatedTruthParticles=associatedParticles - if associatedParticles is not None - else "", inputMeasurementParticlesMap="measurement_particles_map", - inputTrajectories=trajectories if trajectories is not None else "", + inputTrajectories=trajectories, inputTrackParameters=trackParameters, - inputTrackParametersTips=trackParametersTips - if trackParametersTips is not None - else "", + inputAssociatedTruthParticles=associatedParticles, inputVertices=outputVertices, - minTrackVtxMatchFraction=0.0 if associatedParticles is None else 0.5, + minTrackVtxMatchFraction=0.5 if associatedParticles else 0.0, inputTime=outputTime, treeName="vertexing", filePath=str(outputDirRoot / "performance_vertexing.root"), diff --git a/Examples/Python/src/Output.cpp b/Examples/Python/src/Output.cpp index 95be2865f5b..62fa19bafdc 100644 --- a/Examples/Python/src/Output.cpp +++ b/Examples/Python/src/Output.cpp @@ -285,8 +285,8 @@ void addOutput(Context& ctx) { ACTS_PYTHON_DECLARE_WRITER( ActsExamples::RootVertexPerformanceWriter, mex, "RootVertexPerformanceWriter", inputAllTruthParticles, - inputSelectedTruthParticles, inputAssociatedTruthParticles, - inputTrackParameters, inputTrackParametersTips, inputTrajectories, + inputSelectedTruthParticles, inputTrackParameters, + inputAssociatedTruthParticles, inputTrackParameters, inputTrajectories, inputMeasurementParticlesMap, inputVertices, inputTime, filePath, treeName, fileMode, minTrackVtxMatchFraction, truthMatchProbMin); @@ -321,11 +321,10 @@ void addOutput(Context& ctx) { ACTS_PYTHON_DECLARE_WRITER( ActsExamples::CKFPerformanceWriter, mex, "CKFPerformanceWriter", - inputTrajectories, inputTrackParametersTips, inputParticles, - inputMeasurementParticlesMap, filePath, fileMode, effPlotToolConfig, - fakeRatePlotToolConfig, duplicationPlotToolConfig, - trackSummaryPlotToolConfig, truthMatchProbMin, nMeasurementsMin, ptMin, - duplicatedPredictor); + inputTrajectories, inputParticles, inputMeasurementParticlesMap, filePath, + fileMode, effPlotToolConfig, fakeRatePlotToolConfig, + duplicationPlotToolConfig, trackSummaryPlotToolConfig, truthMatchProbMin, + nMeasurementsMin, ptMin, duplicatedPredictor); ACTS_PYTHON_DECLARE_WRITER( ActsExamples::RootNuclearInteractionParametersWriter, mex, diff --git a/Examples/Python/src/TrackFinding.cpp b/Examples/Python/src/TrackFinding.cpp index 36bba38d326..ba9201e2cd6 100644 --- a/Examples/Python/src/TrackFinding.cpp +++ b/Examples/Python/src/TrackFinding.cpp @@ -245,8 +245,6 @@ void addTrackFinding(Context& ctx) { ACTS_PYTHON_MEMBER(inputSourceLinks); ACTS_PYTHON_MEMBER(inputInitialTrackParameters); ACTS_PYTHON_MEMBER(outputTrajectories); - ACTS_PYTHON_MEMBER(outputTrackParameters); - ACTS_PYTHON_MEMBER(outputTrackParametersTips); ACTS_PYTHON_MEMBER(findTracks); ACTS_PYTHON_MEMBER(measurementSelectorCfg); ACTS_PYTHON_STRUCT_END(); @@ -304,11 +302,10 @@ void addTrackFinding(Context& ctx) { .def(py::init(constructor)); } - ACTS_PYTHON_DECLARE_ALGORITHM( - ActsExamples::AmbiguityResolutionAlgorithm, mex, - "AmbiguityResolutionAlgorithm", inputSourceLinks, inputTrajectories, - inputTrackParameters, inputTrackParametersTips, outputTrackParameters, - outputTrackParametersTips, maximumSharedHits); + ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::AmbiguityResolutionAlgorithm, mex, + "AmbiguityResolutionAlgorithm", + inputSourceLinks, inputTrajectories, + outputTrajectories, maximumSharedHits); } } // namespace Acts::Python diff --git a/Examples/Python/src/TruthTracking.cpp b/Examples/Python/src/TruthTracking.cpp index 2b9a68f6127..d6bd1511426 100644 --- a/Examples/Python/src/TruthTracking.cpp +++ b/Examples/Python/src/TruthTracking.cpp @@ -137,9 +137,9 @@ void addTruthTracking(Context& ctx) { ACTS_PYTHON_STRUCT_BEGIN(c, Config); ACTS_PYTHON_MEMBER(inputTrackParameters); - ACTS_PYTHON_MEMBER(inputTrackParametersTips); + ACTS_PYTHON_MEMBER(inputTrajectories); ACTS_PYTHON_MEMBER(outputTrackParameters); - ACTS_PYTHON_MEMBER(outputTrackParametersTips); + ACTS_PYTHON_MEMBER(outputTrajectories); ACTS_PYTHON_MEMBER(loc0Min); ACTS_PYTHON_MEMBER(loc0Max); ACTS_PYTHON_MEMBER(loc1Min); diff --git a/Examples/Python/src/Vertexing.cpp b/Examples/Python/src/Vertexing.cpp index d37887fa516..02a4cab898a 100644 --- a/Examples/Python/src/Vertexing.cpp +++ b/Examples/Python/src/Vertexing.cpp @@ -30,22 +30,23 @@ void addVertexing(Context& ctx) { ACTS_PYTHON_DECLARE_ALGORITHM( ActsExamples::AdaptiveMultiVertexFinderAlgorithm, mex, "AdaptiveMultiVertexFinderAlgorithm", inputTrackParameters, - outputProtoVertices, outputVertices, outputTime, bField); + inputTrajectories, outputProtoVertices, outputVertices, outputTime, + bField); - ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::IterativeVertexFinderAlgorithm, - mex, "IterativeVertexFinderAlgorithm", - inputTrackParameters, outputProtoVertices, - outputVertices, outputTime, bField); + ACTS_PYTHON_DECLARE_ALGORITHM( + ActsExamples::IterativeVertexFinderAlgorithm, mex, + "IterativeVertexFinderAlgorithm", inputTrackParameters, inputTrajectories, + outputProtoVertices, outputVertices, outputTime, bField); ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::TutorialVertexFinderAlgorithm, mex, "TutorialVertexFinderAlgorithm", - inputTrackParameters, outputProtoVertices, - bField); + inputTrackParameters, inputTrajectories, + outputProtoVertices, bField); - ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::VertexFitterAlgorithm, mex, - "VertexFitterAlgorithm", inputTrackParameters, - inputProtoVertices, outputVertices, bField, - doConstrainedFit, constraintPos, constraintCov); + ACTS_PYTHON_DECLARE_ALGORITHM( + ActsExamples::VertexFitterAlgorithm, mex, "VertexFitterAlgorithm", + inputTrackParameters, inputTrajectories, inputProtoVertices, + outputVertices, bField, doConstrainedFit, constraintPos, constraintCov); } } // namespace Acts::Python diff --git a/Examples/Scripts/Python/vertex_fitting.py b/Examples/Scripts/Python/vertex_fitting.py index 16504428241..edde951c4fb 100755 --- a/Examples/Scripts/Python/vertex_fitting.py +++ b/Examples/Scripts/Python/vertex_fitting.py @@ -99,10 +99,9 @@ def runVertexFitting( addVertexFitting( s, field, + trackParameters=trackParameters, associatedParticles=associatedParticles, trajectories=None, - trackParameters=trackParameters, - trackParametersTips=None, vertexFinder=vertexFinder, outputDirRoot=outputDir if outputRoot else None, ) From 20b005f2b8338e85c3ced8bf9339dac0390cb983 Mon Sep 17 00:00:00 2001 From: Tomasz Bold <68424406+tboldagh@users.noreply.github.com> Date: Fri, 11 Nov 2022 17:07:12 +0100 Subject: [PATCH 03/57] feat: Support space points writing (#1671) This PR adds python binding for integrating Space Points writing as CSV in python scripts. It also adds writer algorithm to wite Space Points in ROOT file. --- .../Io/Csv/CsvSpacepointWriter.hpp | 2 +- Examples/Io/Csv/src/CsvParticleWriter.cpp | 2 +- Examples/Io/Root/CMakeLists.txt | 1 + .../Io/Root/RootSpacepointWriter.hpp | 87 +++++++++++++++++ Examples/Io/Root/src/RootSpacepointWriter.cpp | 97 +++++++++++++++++++ Examples/Python/src/Output.cpp | 10 ++ 6 files changed, 197 insertions(+), 2 deletions(-) create mode 100644 Examples/Io/Root/include/ActsExamples/Io/Root/RootSpacepointWriter.hpp create mode 100644 Examples/Io/Root/src/RootSpacepointWriter.cpp diff --git a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacepointWriter.hpp b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacepointWriter.hpp index ce06e6563f8..9a01563517c 100644 --- a/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacepointWriter.hpp +++ b/Examples/Io/Csv/include/ActsExamples/Io/Csv/CsvSpacepointWriter.hpp @@ -1,6 +1,6 @@ // This file is part of the Acts project. // -// Copyright (C) 2021 CERN for the benefit of the Acts project +// Copyright (C) 2022 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 diff --git a/Examples/Io/Csv/src/CsvParticleWriter.cpp b/Examples/Io/Csv/src/CsvParticleWriter.cpp index 29f9f5a2fd6..79b8ff657e0 100644 --- a/Examples/Io/Csv/src/CsvParticleWriter.cpp +++ b/Examples/Io/Csv/src/CsvParticleWriter.cpp @@ -1,6 +1,6 @@ // This file is part of the Acts project. // -// Copyright (C) 2019 CERN for the benefit of the Acts project +// Copyright (C) 2022 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 diff --git a/Examples/Io/Root/CMakeLists.txt b/Examples/Io/Root/CMakeLists.txt index 0f82360a714..bc7a0a7af77 100644 --- a/Examples/Io/Root/CMakeLists.txt +++ b/Examples/Io/Root/CMakeLists.txt @@ -10,6 +10,7 @@ add_library( src/RootParticleReader.cpp src/RootPropagationStepsWriter.cpp src/RootSimHitWriter.cpp + src/RootSpacepointWriter.cpp src/RootTrackParameterWriter.cpp src/RootTrajectoryStatesWriter.cpp src/RootTrajectorySummaryReader.cpp diff --git a/Examples/Io/Root/include/ActsExamples/Io/Root/RootSpacepointWriter.hpp b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSpacepointWriter.hpp new file mode 100644 index 00000000000..a5d3f83abf7 --- /dev/null +++ b/Examples/Io/Root/include/ActsExamples/Io/Root/RootSpacepointWriter.hpp @@ -0,0 +1,87 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2022 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 http://mozilla.org/MPL/2.0/. + +#pragma once + +#include "ActsExamples/EventData/SimSpacePoint.hpp" +#include "ActsExamples/Framework/WriterT.hpp" + +#include +#include +#include + +class TFile; +class TTree; + +namespace ActsExamples { + +/// Write out space points as a flat TTree. +/// +/// Each entry in the TTree corresponds to one space point for optimum writing +/// speed. The event number is part of the written data. +/// +/// Safe to use from multiple writer threads. To avoid thread-saftey issues, +/// the writer must be the sole owner of the underlying file. Thus, the +/// output file pointer can not be given from the outside. +class RootSpacepointWriter final : public WriterT { + public: + struct Config { + /// Input particle collection to write. + std::string inputSpacepoints; + /// Path to the output file. + std::string filePath; + /// Output file access mode. + std::string fileMode = "RECREATE"; + /// Name of the tree within the output file. + std::string treeName = "spacepoints"; + }; + + /// Construct the particle writer. + /// + /// @param config is the configuration object + /// @param level is the logging level + RootSpacepointWriter(const Config& config, Acts::Logging::Level level); + + /// Ensure underlying file is closed. + ~RootSpacepointWriter() final override; + + /// End-of-run hook + ProcessCode endRun() final override; + + /// Get readonly access to the config parameters + const Config& config() const { return m_cfg; } + + protected: + /// Type-specific write implementation. + /// + /// @param[in] ctx is the algorithm context + /// @param[in] hits are the hits to be written + ProcessCode writeT(const AlgorithmContext& ctx, + const SimSpacePointContainer& hits) final override; + + private: + Config m_cfg; + std::mutex m_writeMutex; + TFile* m_outputFile = nullptr; + TTree* m_outputTree = nullptr; + /// Event identifier. + uint32_t m_eventId = 0; + /// Hit surface identifier. + uint64_t m_measurementId = 0; + /// Space point surface identifier. + uint64_t m_geometryId = 0; + /// Global space point position components in mm. + float m_x = std::numeric_limits::infinity(); + float m_y = std::numeric_limits::infinity(); + float m_z = std::numeric_limits::infinity(); + // Global space point position uncertainties + float m_var_r = std::numeric_limits::infinity(); + float m_var_z = std::numeric_limits::infinity(); +}; + +} // namespace ActsExamples diff --git a/Examples/Io/Root/src/RootSpacepointWriter.cpp b/Examples/Io/Root/src/RootSpacepointWriter.cpp new file mode 100644 index 00000000000..b1808a2ab5d --- /dev/null +++ b/Examples/Io/Root/src/RootSpacepointWriter.cpp @@ -0,0 +1,97 @@ +// This file is part of the Acts project. +// +// Copyright (C) 2022 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 http://mozilla.org/MPL/2.0/. + +#include "ActsExamples/Io/Root/RootSpacepointWriter.hpp" + +#include "Acts/Definitions/Units.hpp" + +#include +#include + +#include +#include + +ActsExamples::RootSpacepointWriter::RootSpacepointWriter( + const ActsExamples::RootSpacepointWriter::Config& config, + Acts::Logging::Level level) + : WriterT(config.inputSpacepoints, "RootSpacepointWriter", level), + m_cfg(config) { + // inputParticles is already checked by base constructor + if (m_cfg.filePath.empty()) { + throw std::invalid_argument("Missing file path"); + } + if (m_cfg.treeName.empty()) { + throw std::invalid_argument("Missing tree name"); + } + + // open root file and create the tree + m_outputFile = TFile::Open(m_cfg.filePath.c_str(), m_cfg.fileMode.c_str()); + if (m_outputFile == nullptr) { + throw std::ios_base::failure("Could not open '" + m_cfg.filePath + "'"); + } + m_outputFile->cd(); + m_outputTree = new TTree(m_cfg.treeName.c_str(), m_cfg.treeName.c_str()); + if (m_outputTree == nullptr) { + throw std::bad_alloc(); + } + + // setup the branches + m_outputTree->Branch("event_id", &m_eventId); + m_outputTree->Branch("measurement_id", &m_measurementId, "measurement_id/l"); + m_outputTree->Branch("geometry_id", &m_geometryId, "geometry_id/l"); + m_outputTree->Branch("x", &m_x); + m_outputTree->Branch("y", &m_y); + m_outputTree->Branch("z", &m_z); + m_outputTree->Branch("var_r", &m_var_r); + m_outputTree->Branch("var_z", &m_var_z); +} + +ActsExamples::RootSpacepointWriter::~RootSpacepointWriter() { + if (m_outputFile != nullptr) { + m_outputFile->Close(); + } +} + +ActsExamples::ProcessCode ActsExamples::RootSpacepointWriter::endRun() { + m_outputFile->cd(); + m_outputTree->Write(); + ACTS_VERBOSE("Wrote hits to tree '" << m_cfg.treeName << "' in '" + << m_cfg.filePath << "'"); + m_outputFile->Close(); + return ProcessCode::SUCCESS; +} + +ActsExamples::ProcessCode ActsExamples::RootSpacepointWriter::writeT( + const AlgorithmContext& ctx, + const ActsExamples::SimSpacePointContainer& spacepoints) { + // ensure exclusive access to tree/file while writing + std::lock_guard lock(m_writeMutex); + + // Get the event number + m_eventId = ctx.eventNumber; + for (const auto& sp : spacepoints) { + const auto slinkPtr = + dynamic_cast(sp.sourceLinks()[0]); + if (slinkPtr == nullptr) { + ACTS_ERROR("Missing source link for a space point"); + return ProcessCode::ABORT; + } + m_measurementId = slinkPtr->index(); + m_geometryId = slinkPtr->geometryId().value(); + // write sp position + m_x = sp.x() / Acts::UnitConstants::mm; + m_y = sp.y() / Acts::UnitConstants::mm; + m_z = sp.z() / Acts::UnitConstants::mm; + // write sp dimensions + m_var_r = sp.varianceR() / Acts::UnitConstants::mm; + m_var_z = sp.varianceZ() / Acts::UnitConstants::mm; + // Fill the tree + m_outputTree->Fill(); + } + return ActsExamples::ProcessCode::SUCCESS; +} diff --git a/Examples/Python/src/Output.cpp b/Examples/Python/src/Output.cpp index 62fa19bafdc..a655d40b6a0 100644 --- a/Examples/Python/src/Output.cpp +++ b/Examples/Python/src/Output.cpp @@ -13,6 +13,7 @@ #include "ActsExamples/Io/Csv/CsvParticleWriter.hpp" #include "ActsExamples/Io/Csv/CsvPlanarClusterWriter.hpp" #include "ActsExamples/Io/Csv/CsvSimHitWriter.hpp" +#include "ActsExamples/Io/Csv/CsvSpacepointWriter.hpp" #include "ActsExamples/Io/Csv/CsvTrackingGeometryWriter.hpp" #include "ActsExamples/Io/NuclearInteractions/RootNuclearInteractionParametersWriter.hpp" #include "ActsExamples/Io/Performance/CKFPerformanceWriter.hpp" @@ -27,6 +28,7 @@ #include "ActsExamples/Io/Root/RootPlanarClusterWriter.hpp" #include "ActsExamples/Io/Root/RootPropagationStepsWriter.hpp" #include "ActsExamples/Io/Root/RootSimHitWriter.hpp" +#include "ActsExamples/Io/Root/RootSpacepointWriter.hpp" #include "ActsExamples/Io/Root/RootTrackParameterWriter.hpp" #include "ActsExamples/Io/Root/RootTrajectoryStatesWriter.hpp" #include "ActsExamples/Io/Root/RootTrajectorySummaryWriter.hpp" @@ -271,6 +273,10 @@ void addOutput(Context& ctx) { "RootSimHitWriter", inputSimHits, filePath, fileMode, treeName); + ACTS_PYTHON_DECLARE_WRITER(ActsExamples::RootSpacepointWriter, mex, + "RootSpacepointWriter", inputSpacepoints, filePath, + fileMode, treeName); + ACTS_PYTHON_DECLARE_WRITER( ActsExamples::RootTrajectoryStatesWriter, mex, "RootTrajectoryStatesWriter", inputTrajectories, inputParticles, @@ -309,6 +315,10 @@ void addOutput(Context& ctx) { "CsvSimHitWriter", inputSimHits, outputDir, outputStem, outputPrecision); + ACTS_PYTHON_DECLARE_WRITER(ActsExamples::CsvSpacepointWriter, mex, + "CsvSpacepointWriter", inputSpacepoints, outputDir, + outputPrecision); + ACTS_PYTHON_DECLARE_WRITER( ActsExamples::CsvMultiTrajectoryWriter, mex, "CsvMultiTrajectoryWriter", inputTrajectories, outputDir, inputMeasurementParticlesMap, From f66119a5a786530c38e8fe2caec02f5834af55bb Mon Sep 17 00:00:00 2001 From: jahreda <57440432+jahreda@users.noreply.github.com> Date: Fri, 11 Nov 2022 12:01:58 -0600 Subject: [PATCH 04/57] docs: how to get clang-format changes from CI (#1667) Add explanation of how to download clang-format recommended changes from the CI job --- docs/contribution/run_formatting.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/contribution/run_formatting.md b/docs/contribution/run_formatting.md index b47b1846885..61a3d62970e 100644 --- a/docs/contribution/run_formatting.md +++ b/docs/contribution/run_formatting.md @@ -31,6 +31,11 @@ are to use your package manager (e.g. Ubuntu distributions usually offer a set o versions to install), or to use statically linked binaries from [here](https://github.com/muttleyxd/clang-tools-static-binaries)[^1]. +You can also download the required changes by clicking on *Summary* on the top left-hand +portion of the CI job and scrolling down to the bottom of the page (see *Changed*). +However, it is suggested to run the `CI/check_format` locally before committing, to not +clog the shared resources with repeated checks. + ## Python formatting Formatting of the Python source code uses the library From eb36186592ac4e3f9fb0177417210d6c1c0dee8f Mon Sep 17 00:00:00 2001 From: Tomasz Bold <68424406+tboldagh@users.noreply.github.com> Date: Sat, 12 Nov 2022 11:31:19 +0100 Subject: [PATCH 05/57] refactor!: Move beam spot position and mag field value from SeedFinderConfig to SeedFinderOptions (#1630) BREAKING CHANGE: This PR changes API of the SeedFinder. Now, in addition to the Config object obtains also Options object. This way the Config object does not need to change in the client code (e.g. ATLAS), even if the beam spot moves. This PR addresses half of the issue https://github.com/acts-project/acts/issues/1522 but not yet closes it. A similar move would need to happen for magnetic field. --- CI/physmon/physmon.py | 5 +- Core/include/Acts/Seeding/BinnedSPGroup.hpp | 10 +++- Core/include/Acts/Seeding/BinnedSPGroup.ipp | 14 ++--- Core/include/Acts/Seeding/SeedFinder.hpp | 6 +- Core/include/Acts/Seeding/SeedFinder.ipp | 8 ++- .../include/Acts/Seeding/SeedFinderConfig.hpp | 30 ++++++---- .../Acts/Seeding/SeedFinderOrthogonal.hpp | 6 +- .../Acts/Seeding/SeedFinderOrthogonal.ipp | 7 ++- .../Seeding/SeedFinderOrthogonalConfig.hpp | 10 ---- .../TrackFinding/SeedingAlgorithm.hpp | 1 + .../SeedingOrthogonalAlgorithm.hpp | 2 + .../TrackFinding/src/SeedingAlgorithm.cpp | 7 ++- .../src/SeedingOrthogonalAlgorithm.cpp | 5 +- .../Python/python/acts/examples/__init__.py | 3 +- Examples/Python/python/acts/examples/itk.py | 7 ++- .../python/acts/examples/reconstruction.py | 55 ++++++++++--------- Examples/Python/src/TrackFinding.cpp | 26 +++++---- .../Reconstruction/Common/RecCKFTracks.cpp | 4 +- .../Reconstruction/Common/SeedingExample.cpp | 4 +- Examples/Scripts/Optimization/ckf.py | 3 +- Examples/Scripts/Python/ckf_tracks.py | 3 +- Examples/Scripts/Python/itk_seeding.py | 24 ++++---- Examples/Scripts/Python/seeding.py | 5 +- .../Acts/Plugins/Cuda/Seeding/SeedFinder.hpp | 4 +- .../Acts/Plugins/Cuda/Seeding/SeedFinder.ipp | 7 ++- .../Acts/Plugins/Cuda/Seeding2/SeedFinder.hpp | 3 + .../Acts/Plugins/Cuda/Seeding2/SeedFinder.ipp | 4 +- .../Acts/Plugins/Sycl/Seeding/SeedFinder.hpp | 2 + .../Acts/Plugins/Sycl/Seeding/SeedFinder.ipp | 4 +- .../UnitTests/Core/Seeding/SeedFinderTest.cpp | 14 +++-- .../Cuda/Seeding/SeedFinderCudaTest.cpp | 15 ++--- .../UnitTests/Plugins/Cuda/Seeding2/main.cpp | 13 +++-- .../Sycl/Seeding/SeedFinderSyclTest.cpp | 25 +++++---- 33 files changed, 199 insertions(+), 137 deletions(-) diff --git a/CI/physmon/physmon.py b/CI/physmon/physmon.py index a154c5e33ff..d72a9b155ed 100755 --- a/CI/physmon/physmon.py +++ b/CI/physmon/physmon.py @@ -34,6 +34,7 @@ TruthSeedRanges, ParticleSmearingSigmas, SeedFinderConfigArg, + SeedFinderOptionsArg, SeedingAlgorithm, TrackParamsEstimationConfig, addCKFTracks, @@ -163,9 +164,9 @@ sigmaScattering=5, radLengthPerSeed=0.1, minPt=500 * u.MeV, - bFieldInZ=1.99724 * u.T, impactMax=3 * u.mm, ), + SeedFinderOptionsArg(bFieldInZ=1.99724 * u.T, beamPos=(0.0, 0.0)), TrackParamsEstimationConfig(deltaR=(10.0 * u.mm, None)), seedingAlgorithm=SeedingAlgorithm.TruthSmeared if truthSmearedSeeded @@ -298,9 +299,9 @@ sigmaScattering=5, radLengthPerSeed=0.1, minPt=500 * u.MeV, - bFieldInZ=1.99724 * u.T, impactMax=3 * u.mm, ), + SeedFinderOptionsArg(bFieldInZ=1.99724 * u.T), TrackParamsEstimationConfig(deltaR=(10.0 * u.mm, None)), seedingAlgorithm=SeedingAlgorithm.Default, geoSelectionConfigFile=geoSel, diff --git a/Core/include/Acts/Seeding/BinnedSPGroup.hpp b/Core/include/Acts/Seeding/BinnedSPGroup.hpp index f381e687958..dbb94e10bb5 100644 --- a/Core/include/Acts/Seeding/BinnedSPGroup.hpp +++ b/Core/include/Acts/Seeding/BinnedSPGroup.hpp @@ -265,16 +265,20 @@ class BinnedSPGroup { public: BinnedSPGroup() = delete; + using GlobalPositionFunctor = + std::function( + const external_spacepoint_t&, float, float, float)>; + template BinnedSPGroup( spacepoint_iterator_t spBegin, spacepoint_iterator_t spEnd, - std::function( - const external_spacepoint_t&, float, float, float)>, + GlobalPositionFunctor toGlobal, std::shared_ptr> botBinFinder, std::shared_ptr> tBinFinder, std::unique_ptr> grid, Acts::Extent rRangeSPExtent, - const SeedFinderConfig& _config); + const SeedFinderConfig& _config, + const SeedFinderOptions& _options); size_t size() { return m_binnedSP->size(); } diff --git a/Core/include/Acts/Seeding/BinnedSPGroup.ipp b/Core/include/Acts/Seeding/BinnedSPGroup.ipp index 470d479d573..dca7459f9f2 100644 --- a/Core/include/Acts/Seeding/BinnedSPGroup.ipp +++ b/Core/include/Acts/Seeding/BinnedSPGroup.ipp @@ -9,15 +9,15 @@ template template Acts::BinnedSPGroup::BinnedSPGroup( spacepoint_iterator_t spBegin, spacepoint_iterator_t spEnd, - std::function( - const external_spacepoint_t&, float, float, float)> - globTool, + GlobalPositionFunctor toGlobal, std::shared_ptr> botBinFinder, std::shared_ptr> tBinFinder, std::unique_ptr> grid, Acts::Extent rRangeSPExtent, - const SeedFinderConfig& _config) { + const SeedFinderConfig& _config, + const SeedFinderOptions& _options) { auto config = _config.toInternalUnits(); + auto options = _options.toInternalUnits(); static_assert( std::is_same< typename std::iterator_traits::value_type, @@ -35,7 +35,7 @@ Acts::BinnedSPGroup::BinnedSPGroup( // create number of bins equal to number of millimeters rMax // (worst case minR: configured minR + 1mm) // binSizeR allows to increase or reduce numRBins if needed - size_t numRBins = (config.rMax + config.beamPos.norm()) / config.binSizeR; + size_t numRBins = (config.rMax + options.beamPos.norm()) / config.binSizeR; std::vector< std::vector>>> rBins(numRBins); @@ -45,7 +45,7 @@ Acts::BinnedSPGroup::BinnedSPGroup( } const external_spacepoint_t& sp = **it; const auto& [spPosition, variance] = - globTool(sp, config.zAlign, config.rAlign, config.sigmaError); + toGlobal(sp, config.zAlign, config.rAlign, config.sigmaError); float spX = spPosition[0]; float spY = spPosition[1]; @@ -63,7 +63,7 @@ Acts::BinnedSPGroup::BinnedSPGroup( } auto isp = std::make_unique>( - sp, spPosition, config.beamPos, variance); + sp, spPosition, options.beamPos, variance); // calculate r-Bin index and protect against overflow (underflow not // possible) size_t rIndex = isp->radius() / config.binSizeR; diff --git a/Core/include/Acts/Seeding/SeedFinder.hpp b/Core/include/Acts/Seeding/SeedFinder.hpp index d850cf179ca..7fc90e6928a 100644 --- a/Core/include/Acts/Seeding/SeedFinder.hpp +++ b/Core/include/Acts/Seeding/SeedFinder.hpp @@ -8,6 +8,7 @@ #pragma once +#include "Acts/Definitions/Units.hpp" #include "Acts/Geometry/Extent.hpp" #include "Acts/Seeding/InternalSeed.hpp" #include "Acts/Seeding/InternalSpacePoint.hpp" @@ -56,7 +57,9 @@ class SeedFinder { /// The only constructor. Requires a config object. /// @param config the configuration for the SeedFinder - SeedFinder(Acts::SeedFinderConfig config); + /// @param options frequently changing configuration (like beam position) + SeedFinder(Acts::SeedFinderConfig config, + const Acts::SeedFinderOptions& options); ~SeedFinder() = default; /** @name Disallow default instantiation, copy, assignment */ //@{ @@ -106,6 +109,7 @@ class SeedFinder { private: Acts::SeedFinderConfig m_config; + Acts::SeedFinderOptions m_options; }; } // namespace Acts diff --git a/Core/include/Acts/Seeding/SeedFinder.ipp b/Core/include/Acts/Seeding/SeedFinder.ipp index 62e968dfc69..1491ef5fe15 100644 --- a/Core/include/Acts/Seeding/SeedFinder.ipp +++ b/Core/include/Acts/Seeding/SeedFinder.ipp @@ -16,18 +16,20 @@ namespace Acts { template SeedFinder::SeedFinder( - Acts::SeedFinderConfig config) - : m_config(config.toInternalUnits()) { + Acts::SeedFinderConfig config, + const Acts::SeedFinderOptions& options) + : m_config(config.toInternalUnits()), m_options(options.toInternalUnits()) { // calculation of scattering using the highland formula // convert pT to p once theta angle is known m_config.highland = 13.6 * std::sqrt(m_config.radLengthPerSeed) * (1 + 0.038 * std::log(m_config.radLengthPerSeed)); float maxScatteringAngle = m_config.highland / m_config.minPt; m_config.maxScatteringAngle2 = maxScatteringAngle * maxScatteringAngle; + // helix radius in homogeneous magnetic field. Units are Kilotesla, MeV and // millimeter // TODO: change using ACTS units - m_config.pTPerHelixRadius = 300. * m_config.bFieldInZ; + m_config.pTPerHelixRadius = 300. * m_options.bFieldInZ; m_config.minHelixDiameter2 = std::pow(m_config.minPt * 2 / m_config.pTPerHelixRadius, 2); m_config.pT2perRadius = diff --git a/Core/include/Acts/Seeding/SeedFinderConfig.hpp b/Core/include/Acts/Seeding/SeedFinderConfig.hpp index 07387ff5639..2efb963cac9 100644 --- a/Core/include/Acts/Seeding/SeedFinderConfig.hpp +++ b/Core/include/Acts/Seeding/SeedFinderConfig.hpp @@ -116,12 +116,6 @@ struct SeedFinderConfig { // which will make seeding very slow! float rMin = 33 * Acts::UnitConstants::mm; - float bFieldInZ = 2.08 * Acts::UnitConstants::T; - // location of beam in x,y plane. - // used as offset for Space Points - Acts::Vector2 beamPos{0 * Acts::UnitConstants::mm, - 0 * Acts::UnitConstants::mm}; - std::vector zBinsCustomLooping = {}; // average radiation lengths of material on the length of a seed. used for @@ -194,12 +188,8 @@ struct SeedFinderConfig { config.zMax /= 1_mm; config.rMax /= 1_mm; config.rMin /= 1_mm; - config.bFieldInZ /= 1000. * 1_T; config.deltaZMax /= 1_mm; - config.beamPos[0] /= 1_mm; - config.beamPos[1] /= 1_mm; - config.zAlign /= 1_mm; config.rAlign /= 1_mm; @@ -209,4 +199,24 @@ struct SeedFinderConfig { } }; +struct SeedFinderOptions { + // location of beam in x,y plane. + // used as offset for Space Points + Acts::Vector2 beamPos{0 * Acts::UnitConstants::mm, + 0 * Acts::UnitConstants::mm}; + + float bFieldInZ = 2.08 * Acts::UnitConstants::T; + + SeedFinderOptions toInternalUnits() const { + using namespace Acts::UnitLiterals; + SeedFinderOptions options = *this; + options.beamPos[0] /= 1_mm; + options.beamPos[1] /= 1_mm; + + options.bFieldInZ /= 1000. * 1_T; + + return options; + } +}; + } // namespace Acts diff --git a/Core/include/Acts/Seeding/SeedFinderOrthogonal.hpp b/Core/include/Acts/Seeding/SeedFinderOrthogonal.hpp index ac110882cf7..a08bbca6d58 100644 --- a/Core/include/Acts/Seeding/SeedFinderOrthogonal.hpp +++ b/Core/include/Acts/Seeding/SeedFinderOrthogonal.hpp @@ -10,6 +10,7 @@ #include "Acts/Seeding/InternalSeed.hpp" #include "Acts/Seeding/InternalSpacePoint.hpp" +#include "Acts/Seeding/SeedFinderConfig.hpp" #include "Acts/Seeding/SeedFinderOrthogonalConfig.hpp" #include @@ -54,9 +55,11 @@ class SeedFinderOrthogonal { * @brief Construct a new orthogonal seed finder. * * @param config The configuration parameters for this seed finder. + * @param options frequently changing configuration (like beam position) */ SeedFinderOrthogonal( - Acts::SeedFinderOrthogonalConfig config); + const Acts::SeedFinderOrthogonalConfig &config, + const Acts::SeedFinderOptions &options); /** * @brief Destroy the orthogonal seed finder object. @@ -223,6 +226,7 @@ class SeedFinderOrthogonal { * @brief The configuration for the seeding algorithm. */ Acts::SeedFinderOrthogonalConfig m_config; + Acts::SeedFinderOptions m_options; }; } // namespace Acts diff --git a/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp b/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp index 51e0da80be3..adcc40ad588 100644 --- a/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp +++ b/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp @@ -220,8 +220,9 @@ bool SeedFinderOrthogonal::validTuple( template SeedFinderOrthogonal::SeedFinderOrthogonal( - SeedFinderOrthogonalConfig config) - : m_config(config.toInternalUnits()) { + const SeedFinderOrthogonalConfig &config, + const SeedFinderOptions &options) + : m_config(config.toInternalUnits()), m_options(options.toInternalUnits()) { // calculation of scattering using the highland formula // convert pT to p once theta angle is known m_config.highland = 13.6 * std::sqrt(config.radLengthPerSeed) * @@ -231,7 +232,7 @@ SeedFinderOrthogonal::SeedFinderOrthogonal( // helix radius in homogeneous magnetic field. Units are Kilotesla, MeV and // millimeter // TODO: change using ACTS units - m_config.pTPerHelixRadius = 300. * config.bFieldInZ; + m_config.pTPerHelixRadius = 300. * m_options.bFieldInZ; m_config.minHelixDiameter2 = std::pow(config.minPt * 2 / config.pTPerHelixRadius, 2); m_config.pT2perRadius = diff --git a/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp b/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp index 29c867f6605..23bf1297d4f 100644 --- a/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp +++ b/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp @@ -69,12 +69,6 @@ struct SeedFinderOrthogonalConfig { float deltaPhiMax = 0.085; - float bFieldInZ = 2.08 * Acts::UnitConstants::T; - // location of beam in x,y plane. - // used as offset for Space Points - Acts::Vector2 beamPos{0 * Acts::UnitConstants::mm, - 0 * Acts::UnitConstants::mm}; - // cut to the maximum value of delta z between SPs float deltaZMax = std::numeric_limits::infinity() * Acts::UnitConstants::mm; @@ -122,10 +116,6 @@ struct SeedFinderOrthogonalConfig { config.zMax /= 1_mm; config.rMax /= 1_mm; config.rMin /= 1_mm; - config.bFieldInZ /= 1000. * 1_T; - - config.beamPos[0] /= 1_mm; - config.beamPos[1] /= 1_mm; return config; } diff --git a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingAlgorithm.hpp b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingAlgorithm.hpp index 604afb43118..a774747a5da 100644 --- a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingAlgorithm.hpp +++ b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingAlgorithm.hpp @@ -39,6 +39,7 @@ class SeedingAlgorithm final : public BareAlgorithm { Acts::SeedFilterConfig seedFilterConfig; Acts::SeedFinderConfig seedFinderConfig; Acts::SpacePointGridConfig gridConfig; + Acts::SeedFinderOptions seedFinderOptions; // allow for different values of rMax in gridConfig and seedFinderConfig bool allowSeparateRMax = false; diff --git a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingOrthogonalAlgorithm.hpp b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingOrthogonalAlgorithm.hpp index ef06c11cba9..6fd36845b85 100644 --- a/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingOrthogonalAlgorithm.hpp +++ b/Examples/Algorithms/TrackFinding/include/ActsExamples/TrackFinding/SeedingOrthogonalAlgorithm.hpp @@ -10,6 +10,7 @@ #include "Acts/Seeding/InternalSeed.hpp" #include "Acts/Seeding/SeedFilterConfig.hpp" +#include "Acts/Seeding/SeedFinderConfig.hpp" #include "Acts/Seeding/SeedFinderOrthogonalConfig.hpp" #include "Acts/Seeding/SpacePointGrid.hpp" #include "Acts/Utilities/KDTree.hpp" @@ -41,6 +42,7 @@ class SeedingOrthogonalAlgorithm final : public BareAlgorithm { Acts::SeedFilterConfig seedFilterConfig; Acts::SeedFinderOrthogonalConfig seedFinderConfig; + Acts::SeedFinderOptions seedFinderOptions; }; /// Construct the seeding algorithm. diff --git a/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp b/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp index 86fd0a4d334..01e1bfefc09 100644 --- a/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp +++ b/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp @@ -108,7 +108,7 @@ ActsExamples::SeedingAlgorithm::SeedingAlgorithm( throw std::invalid_argument("Inconsistent config minPt"); } - if (m_cfg.gridConfig.bFieldInZ != m_cfg.seedFinderConfig.bFieldInZ) { + if (m_cfg.gridConfig.bFieldInZ != m_cfg.seedFinderOptions.bFieldInZ) { throw std::invalid_argument("Inconsistent config bFieldInZ"); } @@ -218,8 +218,9 @@ ActsExamples::ProcessCode ActsExamples::SeedingAlgorithm::execute( auto spacePointsGrouping = Acts::BinnedSPGroup( spacePointPtrs.begin(), spacePointPtrs.end(), extractGlobalQuantities, bottomBinFinder, topBinFinder, std::move(grid), rRangeSPExtent, - m_cfg.seedFinderConfig); - auto finder = Acts::SeedFinder(m_cfg.seedFinderConfig); + m_cfg.seedFinderConfig, m_cfg.seedFinderOptions); + auto finder = Acts::SeedFinder(m_cfg.seedFinderConfig, + m_cfg.seedFinderOptions); /// variable middle SP radial region of interest const Acts::Range1D rMiddleSPRange( diff --git a/Examples/Algorithms/TrackFinding/src/SeedingOrthogonalAlgorithm.cpp b/Examples/Algorithms/TrackFinding/src/SeedingOrthogonalAlgorithm.cpp index 70ee73d7cf7..4caad173e38 100644 --- a/Examples/Algorithms/TrackFinding/src/SeedingOrthogonalAlgorithm.cpp +++ b/Examples/Algorithms/TrackFinding/src/SeedingOrthogonalAlgorithm.cpp @@ -57,7 +57,7 @@ ActsExamples::SeedingOrthogonalAlgorithm::SeedingOrthogonalAlgorithm( // millimeter // TODO: change using ACTS units m_cfg.seedFinderConfig.pTPerHelixRadius = - 300. * m_cfg.seedFinderConfig.bFieldInZ; + 300. * m_cfg.seedFinderOptions.bFieldInZ; m_cfg.seedFinderConfig.minHelixDiameter2 = std::pow(m_cfg.seedFinderConfig.minPt * 2 / m_cfg.seedFinderConfig.pTPerHelixRadius, @@ -79,7 +79,8 @@ ActsExamples::ProcessCode ActsExamples::SeedingOrthogonalAlgorithm::execute( } } - Acts::SeedFinderOrthogonal finder(m_cfg.seedFinderConfig); + Acts::SeedFinderOrthogonal finder(m_cfg.seedFinderConfig, + m_cfg.seedFinderOptions); SimSeedContainer seeds = finder.createSeeds(spacePoints); diff --git a/Examples/Python/python/acts/examples/__init__.py b/Examples/Python/python/acts/examples/__init__.py index f89da8da137..d007b5ce906 100644 --- a/Examples/Python/python/acts/examples/__init__.py +++ b/Examples/Python/python/acts/examples/__init__.py @@ -157,8 +157,9 @@ def NamedTypeArgsWrapper(*args, **kwargs): if k is None: newargs.append(a) if i > len(newargs): + types = [type(a).__name__ for a in args] raise TypeError( - f"{func.__name__}() positional argument {i} follows named-type arguments, which were converted to keyword arguments" + f"{func.__name__}() positional argument {i} of type {type(a)} follows named-type arguments, which were converted to keyword arguments. All argument types: {types}" ) elif k in kwargs: raise TypeError(f"{func.__name__}() keyword argument repeated: {k}") diff --git a/Examples/Python/python/acts/examples/itk.py b/Examples/Python/python/acts/examples/itk.py index 638c3f07004..95d469d6045 100644 --- a/Examples/Python/python/acts/examples/itk.py +++ b/Examples/Python/python/acts/examples/itk.py @@ -7,6 +7,7 @@ from acts.examples.reconstruction import ( SeedFinderConfigArg, + SeedFinderOptionsArg, SeedFilterConfigArg, SpacePointGridConfigArg, SeedingAlgorithmConfigArg, @@ -473,7 +474,6 @@ def itkSeedingAlgConfig(inputSpacePointsType): sigmaScattering=sigmaScattering, radLengthPerSeed=radLengthPerSeed, minPt=minPt, - bFieldInZ=bFieldInZ, impactMax=impactMax, interactionPointCut=interactionPointCut, arithmeticAverageCotTheta=arithmeticAverageCotTheta, @@ -496,8 +496,10 @@ def itkSeedingAlgConfig(inputSpacePointsType): collisionRegion=(collisionRegionMin, collisionRegionMax), r=(None, rMaxSeedFinderConfig), z=(zMin, zMax), - beamPos=beamPos, ) + + seedFinderOptionsArg = SeedFinderOptionsArg(bFieldInZ=bFieldInZ, beamPos=beamPos) + seedFilterConfigArg = SeedFilterConfigArg( impactWeightFactor=impactWeightFactor, zOriginWeightFactor=zOriginWeightFactor, @@ -527,6 +529,7 @@ def itkSeedingAlgConfig(inputSpacePointsType): return ( seedFinderConfigArg, + seedFinderOptionsArg, seedFilterConfigArg, spacePointGridConfigArg, seedingAlgorithmConfigArg, diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index 5a7334c0e1b..95cb45c4310 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -33,7 +33,6 @@ "sigmaScattering", "radLengthPerSeed", "minPt", - "bFieldInZ", "impactMax", "interactionPointCut", "arithmeticAverageCotTheta", @@ -56,9 +55,11 @@ "collisionRegion", # (min,max) "r", # (min,max) "z", # (min,max) - "beamPos", # (x,y) ], - defaults=[None] * 21 + [(None, None)] * 8, + defaults=[None] * 20 + [(None, None)] * 7, +) +SeedFinderOptionsArg = namedtuple( + "SeedFinderOptions", ["beamPos", "bFieldInZ"], defaults=[(None, None), None] ) SeedFilterConfigArg = namedtuple( @@ -134,6 +135,7 @@ truthSeedRanges=TruthSeedRanges, particleSmearingSigmas=ParticleSmearingSigmas, seedFinderConfigArg=SeedFinderConfigArg, + seedFinderOptionsArg=SeedFinderOptionsArg, seedFilterConfigArg=SeedFilterConfigArg, spacePointGridConfigArg=SpacePointGridConfigArg, seedingAlgorithmConfigArg=SeedingAlgorithmConfigArg, @@ -150,6 +152,7 @@ def addSeeding( particleSmearingSigmas: ParticleSmearingSigmas = ParticleSmearingSigmas(), initialVarInflation: Optional[list] = None, seedFinderConfigArg: SeedFinderConfigArg = SeedFinderConfigArg(), + seedFinderOptionsArg: SeedFinderOptionsArg = SeedFinderOptionsArg(), seedFilterConfigArg: SeedFilterConfigArg = SeedFilterConfigArg(), spacePointGridConfigArg: SpacePointGridConfigArg = SpacePointGridConfigArg(), seedingAlgorithmConfigArg: SeedingAlgorithmConfigArg = SeedingAlgorithmConfigArg(), @@ -183,6 +186,8 @@ def addSeeding( seedFinderConfigArg : SeedFinderConfigArg(maxSeedsPerSpM, cotThetaMax, sigmaScattering, radLengthPerSeed, minPt, bFieldInZ, impactMax, interactionPointCut, arithmeticAverageCotTheta, deltaZMax, maxPtScattering, zBinEdges, skipPreviousTopSP, zBinsCustomLooping, rRangeMiddleSP, useVariableMiddleSPRange, binSizeR, forceRadialSorting, seedConfirmation, centralSeedConfirmationRange, forwardSeedConfirmationRange, deltaR, deltaRBottomSP, deltaRTopSP, deltaRMiddleSPRange, collisionRegion, r, z, beamPos) SeedFinderConfig settings. deltaR, deltaRBottomSP, deltaRTopSP, deltaRMiddleSPRange, collisionRegion, r, z are ranges specified as a tuple of (min,max). beamPos is specified as (x,y). Defaults specified in Core/include/Acts/Seeding/SeedFinderConfig.hpp + seedFinderOptionsArg : SeedFinderOptionsArg(bFieldInZ, beamPos) + Defaults specified in Core/include/Acts/Seeding/SeedFinderConfig.hpp seedFilterConfigArg : SeedFilterConfigArg(compatSeedWeight, compatSeedLimit, numSeedIncrement, seedWeightIncrement, seedConfirmation, curvatureSortingInFilter, maxSeedsPerSpMConf, maxQualitySeedsPerSpMConf, useDeltaRorTopRadius) Defaults specified in Core/include/Acts/Seeding/SeedFinderConfig.hpp spacePointGridConfigArg : SpacePointGridConfigArg(rMax, zBinEdges, phiBinDeflectionCoverage, phi, impactMax) @@ -328,7 +333,6 @@ def addSeeding( sigmaScattering=seedFinderConfigArg.sigmaScattering, radLengthPerSeed=seedFinderConfigArg.radLengthPerSeed, minPt=seedFinderConfigArg.minPt, - bFieldInZ=seedFinderConfigArg.bFieldInZ, impactMax=seedFinderConfigArg.impactMax, interactionPointCut=seedFinderConfigArg.interactionPointCut, arithmeticAverageCotTheta=seedFinderConfigArg.arithmeticAverageCotTheta, @@ -344,18 +348,18 @@ def addSeeding( seedConfirmation=seedFinderConfigArg.seedConfirmation, centralSeedConfirmationRange=seedFinderConfigArg.centralSeedConfirmationRange, forwardSeedConfirmationRange=seedFinderConfigArg.forwardSeedConfirmationRange, - beamPos=( - None - if seedFinderConfigArg.beamPos is None - or all([x is None for x in seedFinderConfigArg.beamPos]) - else acts.Vector2( - seedFinderConfigArg.beamPos[0] or 0.0, - seedFinderConfigArg.beamPos[1] or 0.0, - ) - ), ), ) - + seedFinderOptions = acts.SeedFinderOptions( + **acts.examples.defaultKWArgs( + beamPos=acts.Vector2(0.0, 0.0) + if seedFinderOptionsArg.beamPos == (None, None) + else acts.Vector2( + seedFinderOptionsArg.beamPos[0], seedFinderOptionsArg.beamPos[1] + ), + bFieldInZ=seedFinderOptionsArg.bFieldInZ, + ) + ) seedFilterConfig = acts.SeedFilterConfig( **acts.examples.defaultKWArgs( maxSeedsPerSpM=seedFinderConfig.maxSeedsPerSpM, @@ -382,7 +386,7 @@ def addSeeding( gridConfig = acts.SpacePointGridConfig( **acts.examples.defaultKWArgs( - bFieldInZ=seedFinderConfig.bFieldInZ, + bFieldInZ=seedFinderOptions.bFieldInZ, minPt=seedFinderConfig.minPt, rMax=( seedFinderConfig.rMax @@ -419,6 +423,7 @@ def addSeeding( gridConfig=gridConfig, seedFilterConfig=seedFilterConfig, seedFinderConfig=seedFinderConfig, + seedFinderOptions=seedFinderOptions, ) s.addAlgorithm(seedingAlg) inputProtoTracks = seedingAlg.config.outputProtoTracks @@ -459,7 +464,6 @@ def addSeeding( sigmaScattering=seedFinderConfigArg.sigmaScattering, radLengthPerSeed=seedFinderConfigArg.radLengthPerSeed, minPt=seedFinderConfigArg.minPt, - bFieldInZ=seedFinderConfigArg.bFieldInZ, impactMax=seedFinderConfigArg.impactMax, interactionPointCut=seedFinderConfigArg.interactionPointCut, deltaZMax=seedFinderConfigArg.deltaZMax, @@ -467,18 +471,16 @@ def addSeeding( seedConfirmation=seedFinderConfigArg.seedConfirmation, centralSeedConfirmationRange=seedFinderConfigArg.centralSeedConfirmationRange, forwardSeedConfirmationRange=seedFinderConfigArg.forwardSeedConfirmationRange, - beamPos=( - None - if seedFinderConfigArg.beamPos is None - or all([x is None for x in seedFinderConfigArg.beamPos]) - else acts.Vector2( - seedFinderConfigArg.beamPos[0] or 0.0, - seedFinderConfigArg.beamPos[1] or 0.0, - ) - ), ), ) - + seedFinderOptions = SeedFinderOptionsArg( + **acts.examples.defaultKWArgs( + bFieldInZ=seedFinderOptionsArg.bFieldInZ, + beamPos=acts.Vector2(0.0, 0.0) + if seedFinderOptionsArg.beamPos == (None, None) + else seedFinderOptionsArg.beamPos, + ) + ) seedFilterConfig = acts.SeedFilterConfig( **acts.examples.defaultKWArgs( maxSeedsPerSpM=seedFinderConfig.maxSeedsPerSpM, @@ -500,7 +502,6 @@ def addSeeding( useDeltaRorTopRadius=seedFilterConfigArg.useDeltaRorTopRadius, ) ) - seedingAlg = acts.examples.SeedingOrthogonalAlgorithm( level=customLogLevel(), inputSpacePoints=[spAlg.config.outputSpacePoints], diff --git a/Examples/Python/src/TrackFinding.cpp b/Examples/Python/src/TrackFinding.cpp index ba9201e2cd6..3f597870650 100644 --- a/Examples/Python/src/TrackFinding.cpp +++ b/Examples/Python/src/TrackFinding.cpp @@ -85,8 +85,6 @@ void addTrackFinding(Context& ctx) { ACTS_PYTHON_MEMBER(zMax); ACTS_PYTHON_MEMBER(rMax); ACTS_PYTHON_MEMBER(rMin); - ACTS_PYTHON_MEMBER(bFieldInZ); - ACTS_PYTHON_MEMBER(beamPos); ACTS_PYTHON_MEMBER(radLengthPerSeed); ACTS_PYTHON_MEMBER(zAlign); ACTS_PYTHON_MEMBER(rAlign); @@ -119,7 +117,15 @@ void addTrackFinding(Context& ctx) { ACTS_PYTHON_STRUCT_END(); patchKwargsConstructor(c); } - + { + using seedOptions = Acts::SeedFinderOptions; + auto c = py::class_(m, "SeedFinderOptions").def(py::init<>()); + ACTS_PYTHON_STRUCT_BEGIN(c, seedOptions); + ACTS_PYTHON_MEMBER(beamPos); + ACTS_PYTHON_MEMBER(bFieldInZ); + ACTS_PYTHON_STRUCT_END(); + patchKwargsConstructor(c); + } { using Config = Acts::SeedFinderOrthogonalConfig; auto c = @@ -144,8 +150,6 @@ void addTrackFinding(Context& ctx) { ACTS_PYTHON_MEMBER(zMax); ACTS_PYTHON_MEMBER(rMax); ACTS_PYTHON_MEMBER(rMin); - ACTS_PYTHON_MEMBER(bFieldInZ); - ACTS_PYTHON_MEMBER(beamPos); ACTS_PYTHON_MEMBER(radLengthPerSeed); ACTS_PYTHON_MEMBER(deltaZMax); ACTS_PYTHON_MEMBER(skipPreviousTopSP); @@ -206,13 +210,13 @@ void addTrackFinding(Context& ctx) { ACTS_PYTHON_DECLARE_ALGORITHM( ActsExamples::SeedingAlgorithm, mex, "SeedingAlgorithm", inputSpacePoints, outputSeeds, outputProtoTracks, seedFilterConfig, seedFinderConfig, - gridConfig, allowSeparateRMax, zBinNeighborsTop, zBinNeighborsBottom, - numPhiNeighbors); + seedFinderOptions, gridConfig, allowSeparateRMax, zBinNeighborsTop, + zBinNeighborsBottom, numPhiNeighbors); - ACTS_PYTHON_DECLARE_ALGORITHM(ActsExamples::SeedingOrthogonalAlgorithm, mex, - "SeedingOrthogonalAlgorithm", inputSpacePoints, - outputSeeds, outputProtoTracks, - seedFilterConfig, seedFinderConfig); + ACTS_PYTHON_DECLARE_ALGORITHM( + ActsExamples::SeedingOrthogonalAlgorithm, mex, + "SeedingOrthogonalAlgorithm", inputSpacePoints, outputSeeds, + outputProtoTracks, seedFilterConfig, seedFinderConfig, seedFinderOptions); ACTS_PYTHON_DECLARE_ALGORITHM( ActsExamples::TrackParamsEstimationAlgorithm, mex, diff --git a/Examples/Run/Reconstruction/Common/RecCKFTracks.cpp b/Examples/Run/Reconstruction/Common/RecCKFTracks.cpp index dff849e8959..20aeac2407c 100644 --- a/Examples/Run/Reconstruction/Common/RecCKFTracks.cpp +++ b/Examples/Run/Reconstruction/Common/RecCKFTracks.cpp @@ -205,9 +205,9 @@ int runRecCKFTracks(int argc, char* argv[], seedingCfg.seedFinderConfig.minPt = seedingCfg.gridConfig.minPt; seedingCfg.gridConfig.bFieldInZ = 1.99724_T; - seedingCfg.seedFinderConfig.bFieldInZ = seedingCfg.gridConfig.bFieldInZ; - seedingCfg.seedFinderConfig.beamPos = {0_mm, 0_mm}; + seedingCfg.seedFinderOptions.bFieldInZ = seedingCfg.gridConfig.bFieldInZ; + seedingCfg.seedFinderOptions.beamPos = {0_mm, 0_mm}; seedingCfg.seedFinderConfig.impactMax = 3._mm; diff --git a/Examples/Run/Reconstruction/Common/SeedingExample.cpp b/Examples/Run/Reconstruction/Common/SeedingExample.cpp index 3a1d9878870..d7a1d11c604 100644 --- a/Examples/Run/Reconstruction/Common/SeedingExample.cpp +++ b/Examples/Run/Reconstruction/Common/SeedingExample.cpp @@ -158,9 +158,9 @@ int runSeedingExample(int argc, char* argv[], seedingCfg.seedFinderConfig.minPt = seedingCfg.gridConfig.minPt; seedingCfg.gridConfig.bFieldInZ = 1.99724_T; - seedingCfg.seedFinderConfig.bFieldInZ = seedingCfg.gridConfig.bFieldInZ; - seedingCfg.seedFinderConfig.beamPos = {0_mm, 0_mm}; + seedingCfg.seedFinderOptions.bFieldInZ = seedingCfg.gridConfig.bFieldInZ; + seedingCfg.seedFinderOptions.beamPos = {0_mm, 0_mm}; seedingCfg.seedFinderConfig.impactMax = 3._mm; diff --git a/Examples/Scripts/Optimization/ckf.py b/Examples/Scripts/Optimization/ckf.py index f126d176c8f..4f4abdfa0fd 100755 --- a/Examples/Scripts/Optimization/ckf.py +++ b/Examples/Scripts/Optimization/ckf.py @@ -130,6 +130,7 @@ def runCKFTracks( TruthSeedRanges, ParticleSmearingSigmas, SeedFinderConfigArg, + SeedFinderOptionsArg, SeedingAlgorithm, TrackParamsEstimationConfig, CKFPerformanceConfig, @@ -202,9 +203,9 @@ def runCKFTracks( radLengthPerSeed=RadLengthPerSeed, maxPtScattering=MaxPtScattering * u.GeV, minPt=500 * u.MeV, - bFieldInZ=1.99724 * u.T, impactMax=ImpactMax * u.mm, ), + SeedFinderOptionsArg(bFieldInZ=1.99724 * u.T, beamPos=(0.0, 0, 0)), TrackParamsEstimationConfig(deltaR=(10.0 * u.mm, None)), seedingAlgorithm=SeedingAlgorithm.TruthSmeared if truthSmearedSeeded diff --git a/Examples/Scripts/Python/ckf_tracks.py b/Examples/Scripts/Python/ckf_tracks.py index 93dcf6f26e6..8ee58c294d8 100755 --- a/Examples/Scripts/Python/ckf_tracks.py +++ b/Examples/Scripts/Python/ckf_tracks.py @@ -38,6 +38,7 @@ def runCKFTracks( TruthSeedRanges, ParticleSmearingSigmas, SeedFinderConfigArg, + SeedFinderOptionsArg, SeedingAlgorithm, TrackParamsEstimationConfig, CKFPerformanceConfig, @@ -105,9 +106,9 @@ def runCKFTracks( sigmaScattering=5, radLengthPerSeed=0.1, minPt=500 * u.MeV, - bFieldInZ=1.99724 * u.T, impactMax=3 * u.mm, ), + SeedFinderOptionsArg(bFieldInZ=1.99724 * u.T, beamPos=(0.0, 0.0)), TrackParamsEstimationConfig(deltaR=(10.0 * u.mm, None)), seedingAlgorithm=SeedingAlgorithm.TruthSmeared if truthSmearedSeeded diff --git a/Examples/Scripts/Python/itk_seeding.py b/Examples/Scripts/Python/itk_seeding.py index d696865c26a..886c9c37b35 100755 --- a/Examples/Scripts/Python/itk_seeding.py +++ b/Examples/Scripts/Python/itk_seeding.py @@ -11,6 +11,7 @@ from collections import namedtuple from acts.examples.reconstruction import ( SeedFinderConfigArg, + SeedFinderOptionsArg, SeedFilterConfigArg, SpacePointGridConfigArg, SeedingAlgorithmConfigArg, @@ -25,6 +26,7 @@ def addITkSeedingCsv( s, inputSPs, seedFinderConfigArg: SeedFinderConfigArg = SeedFinderConfigArg(), + seedFinderOptionsArg: SeedFinderOptionsArg = SeedFinderOptionsArg(), seedFilterConfigArg: SeedFilterConfigArg = SeedFilterConfigArg(), spacePointGridConfigArg: SpacePointGridConfigArg = SpacePointGridConfigArg(), seedingAlgorithmConfigArg: SeedingAlgorithmConfigArg = SeedingAlgorithmConfigArg(), @@ -51,7 +53,6 @@ def addITkSeedingCsv( sigmaScattering=seedFinderConfigArg.sigmaScattering, radLengthPerSeed=seedFinderConfigArg.radLengthPerSeed, minPt=seedFinderConfigArg.minPt, - bFieldInZ=seedFinderConfigArg.bFieldInZ, impactMax=seedFinderConfigArg.impactMax, interactionPointCut=seedFinderConfigArg.interactionPointCut, arithmeticAverageCotTheta=seedFinderConfigArg.arithmeticAverageCotTheta, @@ -67,14 +68,16 @@ def addITkSeedingCsv( seedConfirmation=seedFinderConfigArg.seedConfirmation, centralSeedConfirmationRange=seedFinderConfigArg.centralSeedConfirmationRange, forwardSeedConfirmationRange=seedFinderConfigArg.forwardSeedConfirmationRange, - beamPos=( - None - if seedFinderConfigArg.beamPos is None - or all([x is None for x in seedFinderConfigArg.beamPos]) - else acts.Vector2( - seedFinderConfigArg.beamPos[0] or 0.0, - seedFinderConfigArg.beamPos[1] or 0.0, - ) + ), + ) + + seedFinderOptions = acts.SeedFinderOptions( + **acts.examples.defaultKWArgs( + bFieldInZ=seedFinderConfigArg.bFieldInZ, + beamPos=acts.Vector2(0.0, 0.0) + if seedFinderOptionsArg.beamPos is (None, None) + else acts.Vector2( + seedFinderOptionsArg.beamPos[0], seedFinderOptionsArg.beamPos[0] ), ), ) @@ -91,7 +94,7 @@ def addITkSeedingCsv( seedConfirmation=seedFilterConfigArg.seedConfirmation, centralSeedConfirmationRange=seedFinderConfig.centralSeedConfirmationRange, forwardSeedConfirmationRange=seedFinderConfig.forwardSeedConfirmationRange, - curvatureSortingInFilter=seedFilterConfigArg.curvatureSortingInFilter, + curvatureSortingInFilter=seedFilterConfigArg.curvatxureSortingInFilter, maxSeedsPerSpMConf=seedFilterConfigArg.maxSeedsPerSpMConf, maxQualitySeedsPerSpMConf=seedFilterConfigArg.maxQualitySeedsPerSpMConf, useDeltaRorTopRadius=seedFilterConfigArg.useDeltaRorTopRadius, @@ -131,6 +134,7 @@ def addITkSeedingCsv( gridConfig=gridConfig, seedFilterConfig=seedFilterConfig, seedFinderConfig=seedFinderConfig, + seedFinderOptions=seedFinderOptions, ) s = s or acts.examples.Sequencer( diff --git a/Examples/Scripts/Python/seeding.py b/Examples/Scripts/Python/seeding.py index b3cc722eace..b2959bb1eb3 100755 --- a/Examples/Scripts/Python/seeding.py +++ b/Examples/Scripts/Python/seeding.py @@ -101,6 +101,7 @@ def runSeeding( TruthSeedRanges, ParticleSmearingSigmas, SeedFinderConfigArg, + SeedFinderOptionsArg, ) addSeeding( @@ -118,9 +119,11 @@ def runSeeding( sigmaScattering=50, radLengthPerSeed=0.1, minPt=500 * u.MeV, - bFieldInZ=1.99724 * u.T, impactMax=3 * u.mm, ), + SeedFinderOptionsArg( + bFieldInZ=1.99724 * u.T, + ), acts.logging.VERBOSE, seedingAlgorithm=seedingAlgorithm, geoSelectionConfigFile=srcdir diff --git a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.hpp b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.hpp index 4c40f5b4b64..5ebf83bac9a 100644 --- a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.hpp +++ b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.hpp @@ -34,7 +34,8 @@ class SeedFinder { /////////////////////////////////////////////////////////////////// public: - SeedFinder(Acts::SeedFinderConfig config); + SeedFinder(const Acts::SeedFinderConfig& config, + const Acts::SeedFinderOptions& options); ~SeedFinder() = default; /** @name Disallow default instantiation, copy, assignment */ @@ -59,6 +60,7 @@ class SeedFinder { private: Acts::SeedFinderConfig m_config; + Acts::SeedFinderOptions m_options; }; } // namespace Acts diff --git a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.ipp b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.ipp index 82fa0060a5e..f4a55892bf7 100644 --- a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.ipp +++ b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding/SeedFinder.ipp @@ -15,8 +15,9 @@ namespace Acts { template SeedFinder::SeedFinder( - Acts::SeedFinderConfig config) - : m_config(config.toInternalUnits()) { + const Acts::SeedFinderConfig& config, + const Acts::SeedFinderOptions& options) + : m_config(config.toInternalUnits()), m_options(options.toInternalUnits()) { // calculation of scattering using the highland formula // convert pT to p once theta angle is known m_config.highland = 13.6 * std::sqrt(m_config.radLengthPerSeed) * @@ -27,7 +28,7 @@ SeedFinder::SeedFinder( // helix radius in homogeneous magnetic field. Units are Kilotesla, MeV and // millimeter // TODO: change using ACTS units - m_config.pTPerHelixRadius = 300. * m_config.bFieldInZ; + m_config.pTPerHelixRadius = 300. * m_options.bFieldInZ; m_config.minHelixDiameter2 = std::pow(m_config.minPt * 2 / m_config.pTPerHelixRadius, 2); m_config.pT2perRadius = diff --git a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.hpp b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.hpp index f993b392c61..28fdb0c1b8b 100644 --- a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.hpp +++ b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.hpp @@ -33,6 +33,7 @@ class SeedFinder { /// Create a CUDA backed seed finder object /// /// @param commonConfig Configuration shared with @c Acts::SeedFinder + /// @param seedFinderOptions options als shared with Acts::SeedFinder /// @param seedFilterConfig Configuration shared with @c Acts::SeedFilter /// @param tripletFilterConfig Configuration for the GPU based triplet /// filtering @@ -40,6 +41,7 @@ class SeedFinder { /// @param logger A @c Logger instance /// SeedFinder(SeedFinderConfig commonConfig, + const SeedFinderOptions& seedFinderOptions, const SeedFilterConfig& seedFilterConfig, const TripletFilterConfig& tripletFilterConfig, int device = 0, std::unique_ptr logger = @@ -70,6 +72,7 @@ class SeedFinder { /// Configuration for the seed finder SeedFinderConfig m_commonConfig; + SeedFinderOptions m_seedFinderOptions; /// Configuration for the (host) seed filter SeedFilterConfig m_seedFilterConfig; /// Configuration for the (device) triplet filter diff --git a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.ipp b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.ipp index 300a040b72a..f96be937559 100644 --- a/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.ipp +++ b/Plugins/Cuda/include/Acts/Plugins/Cuda/Seeding2/SeedFinder.ipp @@ -31,10 +31,12 @@ namespace Cuda { template SeedFinder::SeedFinder( Acts::SeedFinderConfig commonConfig, + const Acts::SeedFinderOptions& seedFinderOptions, const SeedFilterConfig& seedFilterConfig, const TripletFilterConfig& tripletFilterConfig, int device, std::unique_ptr incomingLogger) : m_commonConfig(commonConfig.toInternalUnits()), + m_seedFinderOptions(seedFinderOptions.toInternalUnits()), m_seedFilterConfig(seedFilterConfig.toInternalUnits()), m_tripletFilterConfig(tripletFilterConfig), m_device(device), @@ -50,7 +52,7 @@ SeedFinder::SeedFinder( // helix radius in homogeneous magnetic field. Units are Kilotesla, MeV and // millimeter // TODO: change using ACTS units - m_commonConfig.pTPerHelixRadius = 300. * m_commonConfig.bFieldInZ; + m_commonConfig.pTPerHelixRadius = 300. * m_seedFinderOptions.bFieldInZ; m_commonConfig.minHelixDiameter2 = std::pow(m_commonConfig.minPt * 2 / m_commonConfig.pTPerHelixRadius, 2); m_commonConfig.pT2perRadius = diff --git a/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.hpp b/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.hpp index 19bf3718aa1..e84d7571fb3 100644 --- a/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.hpp +++ b/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.hpp @@ -26,6 +26,7 @@ template class SeedFinder { public: SeedFinder(Acts::SeedFinderConfig config, + const Acts::SeedFinderOptions& options, const Acts::Sycl::DeviceExperimentCuts& cuts, Acts::Sycl::QueueWrapper wrappedQueue, vecmem::memory_resource& resource, @@ -51,6 +52,7 @@ class SeedFinder { private: Acts::SeedFinderConfig m_config; + Acts::SeedFinderOptions m_options; /// Experiment specific cuts Acts::Sycl::DeviceExperimentCuts m_deviceCuts; diff --git a/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.ipp b/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.ipp index a5299baf42d..83b4e91780b 100644 --- a/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.ipp +++ b/Plugins/Sycl/include/Acts/Plugins/Sycl/Seeding/SeedFinder.ipp @@ -26,10 +26,12 @@ namespace Acts::Sycl { template SeedFinder::SeedFinder( Acts::SeedFinderConfig config, + const Acts::SeedFinderOptions& options, const Acts::Sycl::DeviceExperimentCuts& cuts, Acts::Sycl::QueueWrapper wrappedQueue, vecmem::memory_resource& resource, vecmem::memory_resource* device_resource) : m_config(config.toInternalUnits()), + m_options(options.toInternalUnits()), m_deviceCuts(cuts), m_wrappedQueue(std::move(wrappedQueue)), m_resource(&resource), @@ -39,7 +41,7 @@ SeedFinder::SeedFinder( (1 + 0.038f * std::log(m_config.radLengthPerSeed)); float maxScatteringAngle = m_config.highland / m_config.minPt; m_config.maxScatteringAngle2 = maxScatteringAngle * maxScatteringAngle; - m_config.pTPerHelixRadius = 300.f * m_config.bFieldInZ; + m_config.pTPerHelixRadius = 300.f * m_options.bFieldInZ; m_config.minHelixDiameter2 = std::pow(m_config.minPt * 2 / m_config.pTPerHelixRadius, 2); m_config.pT2perRadius = diff --git a/Tests/UnitTests/Core/Seeding/SeedFinderTest.cpp b/Tests/UnitTests/Core/Seeding/SeedFinderTest.cpp index 18b69af4724..c10950a6f39 100644 --- a/Tests/UnitTests/Core/Seeding/SeedFinderTest.cpp +++ b/Tests/UnitTests/Core/Seeding/SeedFinderTest.cpp @@ -134,11 +134,15 @@ int main(int argc, char** argv) { config.sigmaScattering = 1.00000; config.minPt = 500._MeV; - config.bFieldInZ = 1.99724_T; - config.beamPos = {-.5_mm, -.5_mm}; config.impactMax = 10._mm; + config.useVariableMiddleSPRange = false; + + Acts::SeedFinderOptions options; + options.beamPos = {-.5_mm, -.5_mm}; + options.bFieldInZ = 1.99724_T; + int numPhiNeighbors = 1; // extent used to store r range for middle spacepoint @@ -158,7 +162,7 @@ int main(int argc, char** argv) { Acts::ATLASCuts atlasCuts = Acts::ATLASCuts(); config.seedFilter = std::make_unique>( Acts::SeedFilter(sfconf, &atlasCuts)); - Acts::SeedFinder a(config); + Acts::SeedFinder a(config, options); // covariance tool, sets covariances per spacepoint as required auto ct = [=](const SpacePoint& sp, float, float, @@ -170,7 +174,7 @@ int main(int argc, char** argv) { // setup spacepoint grid config Acts::SpacePointGridConfig gridConf; - gridConf.bFieldInZ = config.bFieldInZ; + gridConf.bFieldInZ = options.bFieldInZ; gridConf.minPt = config.minPt; gridConf.rMax = config.rMax; gridConf.zMax = config.zMax; @@ -182,7 +186,7 @@ int main(int argc, char** argv) { Acts::SpacePointGridCreator::createGrid(gridConf); auto spGroup = Acts::BinnedSPGroup( spVec.begin(), spVec.end(), ct, bottomBinFinder, topBinFinder, - std::move(grid), rRangeSPExtent, config); + std::move(grid), rRangeSPExtent, config, options); std::vector>> seedVector; decltype(a)::SeedingState state; diff --git a/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp b/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp index f81d16b3b9c..d73fc710302 100644 --- a/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp +++ b/Tests/UnitTests/Plugins/Cuda/Seeding/SeedFinderCudaTest.cpp @@ -185,11 +185,12 @@ int main(int argc, char** argv) { config.sigmaScattering = 1.00000; config.minPt = 500._MeV; - config.bFieldInZ = 1.99724_T; - - config.beamPos = {-.5_mm, -.5_mm}; config.impactMax = 10._mm; + Acts::SeedFinderOptions options; + options.bFieldInZ = 1.99724_T; + options.beamPos = {-.5_mm, -.5_mm}; + int numPhiNeighbors = 1; // extent used to store r range for middle spacepoint @@ -218,8 +219,8 @@ int main(int argc, char** argv) { Acts::ATLASCuts atlasCuts = Acts::ATLASCuts(); config.seedFilter = std::make_unique>( Acts::SeedFilter(sfconf, &atlasCuts)); - Acts::SeedFinder seedFinder_cpu(config); - Acts::SeedFinder seedFinder_cuda(config); + Acts::SeedFinder seedFinder_cpu(config, options); + Acts::SeedFinder seedFinder_cuda(config, options); // covariance tool, sets covariances per spacepoint as required auto ct = [=](const SpacePoint& sp, float, float, @@ -231,7 +232,7 @@ int main(int argc, char** argv) { // setup spacepoint grid config Acts::SpacePointGridConfig gridConf; - gridConf.bFieldInZ = config.bFieldInZ; + gridConf.bFieldInZ = options.bFieldInZ; gridConf.minPt = config.minPt; gridConf.rMax = config.rMax; gridConf.zMax = config.zMax; @@ -243,7 +244,7 @@ int main(int argc, char** argv) { Acts::SpacePointGridCreator::createGrid(gridConf); auto spGroup = Acts::BinnedSPGroup( spVec.begin(), spVec.end(), ct, bottomBinFinder, topBinFinder, - std::move(grid), rRangeSPExtent, config); + std::move(grid), rRangeSPExtent, config, options); auto end_pre = std::chrono::system_clock::now(); std::chrono::duration elapsec_pre = end_pre - start_pre; diff --git a/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp b/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp index 27420649e62..6d1fc1e831b 100644 --- a/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp +++ b/Tests/UnitTests/Plugins/Cuda/Seeding2/main.cpp @@ -80,9 +80,10 @@ int main(int argc, char* argv[]) { sfConfig.cotThetaMax = 7.40627; sfConfig.sigmaScattering = 1.00000; sfConfig.minPt = 500._MeV; - sfConfig.bFieldInZ = 1.99724_T; - sfConfig.beamPos = {-.5_mm, -.5_mm}; sfConfig.impactMax = 10._mm; + Acts::SeedFinderOptions sfOptions; + sfOptions.bFieldInZ = 1.99724_T; + sfOptions.beamPos = {-.5_mm, -.5_mm}; // Use a size slightly smaller than what modern GPUs are capable of. This is // because for debugging we can't use all available threads in a block, and @@ -93,7 +94,7 @@ int main(int argc, char* argv[]) { // Set up the spacepoint grid configuration. Acts::SpacePointGridConfig gridConfig; - gridConfig.bFieldInZ = sfConfig.bFieldInZ; + gridConfig.bFieldInZ = sfOptions.bFieldInZ; gridConfig.minPt = sfConfig.minPt; gridConfig.rMax = sfConfig.rMax; gridConfig.zMax = sfConfig.zMax; @@ -120,7 +121,7 @@ int main(int argc, char* argv[]) { Acts::SpacePointGridCreator::createGrid(gridConfig); auto spGroup = Acts::BinnedSPGroup( spView.begin(), spView.end(), ct, bottomBinFinder, topBinFinder, - std::move(grid), rRangeSPExtent, sfConfig); + std::move(grid), rRangeSPExtent, sfConfig, sfOptions); // Make a convenient iterator that will be used multiple times later on. auto spGroup_end = spGroup.end(); @@ -153,9 +154,9 @@ int main(int argc, char* argv[]) { auto deviceCuts = testDeviceCuts(); // Set up the seedFinder objects. - Acts::SeedFinder seedFinder_host(sfConfig); + Acts::SeedFinder seedFinder_host(sfConfig, sfOptions); Acts::Cuda::SeedFinder seedFinder_device( - sfConfig, filterConfig, deviceCuts, cmdl.cudaDevice); + sfConfig, sfOptions, filterConfig, deviceCuts, cmdl.cudaDevice); // // Perform the seed finding on the host. diff --git a/Tests/UnitTests/Plugins/Sycl/Seeding/SeedFinderSyclTest.cpp b/Tests/UnitTests/Plugins/Sycl/Seeding/SeedFinderSyclTest.cpp index 57e12201cd3..309cd7f3e0e 100644 --- a/Tests/UnitTests/Plugins/Sycl/Seeding/SeedFinderSyclTest.cpp +++ b/Tests/UnitTests/Plugins/Sycl/Seeding/SeedFinderSyclTest.cpp @@ -103,22 +103,26 @@ auto setupSeedFinderConfiguration() config.cotThetaMax = 7.40627; config.sigmaScattering = 1.00000; config.minPt = 500._MeV; - config.bFieldInZ = 1.99724_T; - config.beamPos = {-.5_mm, -.5_mm}; config.impactMax = 10._mm; - // for sycl config.nTrplPerSpBLimit = 100; config.nAvgTrplPerSpBLimit = 6; return config; } +auto setupSeedFinderOptions() { + Acts::SeedFinderOptions options; + options.bFieldInZ = 1.99724_T; + options.beamPos = {-.5_mm, -.5_mm}; + return options; +} + template auto setupSpacePointGridConfig( - const Acts::SeedFinderConfig& config) - -> Acts::SpacePointGridConfig { + const Acts::SeedFinderConfig& config, + const Acts::SeedFinderOptions& options) -> Acts::SpacePointGridConfig { Acts::SpacePointGridConfig gridConf{}; - gridConf.bFieldInZ = config.bFieldInZ; + gridConf.bFieldInZ = options.bFieldInZ; gridConf.minPt = config.minPt; gridConf.rMax = config.rMax; gridConf.zMax = config.zMax; @@ -156,6 +160,7 @@ auto main(int argc, char** argv) -> int { auto topBinFinder = std::make_shared>( Acts::BinFinder(zBinNeighborsTop, numPhiNeighbors)); auto config = setupSeedFinderConfiguration(); + auto options = setupSeedFinderOptions(); Acts::ATLASCuts atlasCuts = Acts::ATLASCuts(); Acts::Sycl::DeviceExperimentCuts deviceAtlasCuts; @@ -170,8 +175,8 @@ auto main(int argc, char** argv) -> int { vecmem::sycl::host_memory_resource resource(queue.getQueue()); vecmem::sycl::device_memory_resource device_resource(queue.getQueue()); Acts::Sycl::SeedFinder syclSeedFinder( - config, deviceAtlasCuts, queue, resource, &device_resource); - Acts::SeedFinder normalSeedFinder(config); + config, options, deviceAtlasCuts, queue, resource, &device_resource); + Acts::SeedFinder normalSeedFinder(config, options); auto globalTool = [=](const SpacePoint& sp, float /*unused*/, float /*unused*/, float_t /*unused*/) -> std::pair { @@ -182,11 +187,11 @@ auto main(int argc, char** argv) -> int { std::unique_ptr> grid = Acts::SpacePointGridCreator::createGrid( - setupSpacePointGridConfig(config)); + setupSpacePointGridConfig(config, options)); auto spGroup = Acts::BinnedSPGroup( spVec.begin(), spVec.end(), globalTool, bottomBinFinder, topBinFinder, - std::move(grid), rRangeSPExtent, config); + std::move(grid), rRangeSPExtent, config, options); auto end_prep = std::chrono::system_clock::now(); From e5164e88364a8514d60222a13400243c2a254230 Mon Sep 17 00:00:00 2001 From: Benjamin Huth <37871400+benjaminhuth@users.noreply.github.com> Date: Mon, 14 Nov 2022 07:29:06 +0100 Subject: [PATCH 06/57] docs: Improve GSF docs and add figures (#1626) Adds some figures and a more detailed explanation of the algorithm --- docs/core/track_fitting.md | 37 +- docs/figures/gsf_bethe_heitler_approx.svg | 1502 ++++++++++++++++++++ docs/figures/gsf_overview.svg | 1545 +++++++++++++++++++++ 3 files changed, 3077 insertions(+), 7 deletions(-) create mode 100644 docs/figures/gsf_bethe_heitler_approx.svg create mode 100644 docs/figures/gsf_overview.svg diff --git a/docs/core/track_fitting.md b/docs/core/track_fitting.md index fb90d555b6c..6700ea11ee4 100644 --- a/docs/core/track_fitting.md +++ b/docs/core/track_fitting.md @@ -30,9 +30,8 @@ The KF can give us those three interpretations as sets of track parameters: This chapter will be extended in the future. ::: - (gsf_core)= -## Gaussian Sum Filter +## Gaussian Sum Filter (GSF) :::{note} The GSF is not considered as production ready yet, therefore it is located in the namespace `Acts::Experimental`. @@ -44,18 +43,42 @@ $$ p(\vec{x}) = \sum_i w_i \varphi(\vec{x}; \mu_i, \Sigma_i), \quad \sum_i w_i = 1 $$ -A common use case of this is electron fitting. The energy-loss of Bremsstrahlung for electrons in matter are highly non-gaussian, and thus are not modeled accurately by the default material interactions in the Kalman Filter. Instead, the Bremsstrahlung is modeled as a Bethe-Heitler distribution, which is approximated as a gaussian mixture. +A common use case of this is electron fitting. The energy-loss of Bremsstrahlung for electrons in matter are highly non-Gaussian, and thus cannot be modelled accurately by the default material interactions in the Kalman Filter. Instead, the Bremsstrahlung is modelled as a Bethe-Heitler distribution, where $z$ is the fraction of the energy remaining after the interaction ($E_f/E_i$), and $t$ is the material thickness in terms of the radiation length: + +$$ +f(z) = \frac{(- \ln z)^{c-1}}{\Gamma(c)}, \quad c = t/\ln 2 +$$ + +(figBetheHeitler)= +:::{figure} ../figures/gsf_bethe_heitler_approx.svg +:width: 450px +:align: center +The true Bethe-Heitler distribution compared with a gaussian mixture approximation (in thin lines the individual components are drawn) at t = 0.1 (corresponds to ~ 10mm Silicon). +::: + +To be able to handle this with the Kalman filter mechanics, this distribution is approximated by a gaussian mixture as well (see {numref}`figBetheHeitler`). The GSF Algorithm works then as follows (see also {numref}`figGsf`) -### Implementation +* On a surface with material, the Bethe-Heitler energy-loss distribution is approximated with a fixed number of gaussian components for each component. Since this way the number of components would grow exponentially with each material interaction, components that are close in terms of their *Kullback–Leibler divergence* are merged to limit the computational cost. +* On a measurement surface, for each component a Kalman update is performed. Afterwards, the component weights are corrected according to each components compatibility with the measurement. -To implement the GSF, a special stepper is needed, that can handle a multi-component state internally: The {class}`Acts::MultiEigenStepperLoop`. On a surface with material, the Bethe-Heitler energy-loss distribution is approximated with a fixed number of gaussian distributions for each component. Since the number of components would grow exponentially with each material interaction, components that are close in terms of their *Kullback–Leibler divergence* are merged to limit the computational cost. The Kalman update mechanism is based on the code for the {class}`Acts::KalmanFitter`. +(figGsf)= +:::{figure} ../figures/gsf_overview.svg +:width: 450px +:align: center +Simplified overview of the GSF algorithm. +::: -At the end of the fit the multi-component state must be reduced to a single set of parameters with a corresponding covariance matrix. This is supported by the {class}`Acts::MultiEigenStepperLoop` in two different ways currently: The *mean* method computes the mean of the state and the covariance matrix of the multi-component state, whereas the *maximum weight* method just returns the component with the maximum weight. This can be configured in the constructor of the {class}`Acts::MultiEigenStepperLoop` with the {enum}`Acts::FinalReductionMethod`. In the future there is planned to add a *mode* finding method as well. +### The Multi-Stepper +To implement the GSF, a special stepper is needed, that can handle a multi-component state internally: The {class}`Acts::MultiEigenStepperLoop`, which is based on the {class}`Acts::EigenStepper` and thus shares a lot of code with it. It interfaces to the navigation as one aggregate state to limit the navigation overhead, but internally processes a multi-component state. How this aggregation is performed can be configured via a template parameter, by default weighted average is used ({struct}`WeightedComponentReducerLoop`). + +At the end of the fit the multi-component state must be reduced to a single set of parameters with a corresponding covariance matrix. This is supported by the {class}`Acts::MultiEigenStepperLoop` in two different ways currently: The *mean* method computes the mean and the covariance matrix of the multi-component state, whereas the *maximum weight* method just returns the component with the maximum weight. This can be configured in the constructor of the {class}`Acts::MultiEigenStepperLoop` with the {enum}`Acts::FinalReductionMethod`. In the future there is planned to add a *mode* finding method as well. :::{note} In practice it turned out that the *maximum weight* method leads to better results so far. ::: +Even though the multi-stepper interface exposes only one aggregate state and thus is compatible with most standard tools, there is a special aborter is required to stop the navigation when the surface is reached, the {class}`Acts::MultiStepperSurfaceReached`. It checks if all components have reached the target surface already and updates their state accordingly. Optionally, it also can stop the propagation when the aggregate state reaches the surface. + ### Using the GSF @@ -74,7 +97,7 @@ To simplify integration, the GSF returns a {class}`Acts::KalmanFitterResult` obj A GSF example can be found in the Acts Examples Framework [here](https://github.com/acts-project/acts/blob/main/Examples/Scripts/Python/truth_tracking_gsf.py). -### Customizing the Bethe-Heitler approximation +### Customising the Bethe-Heitler approximation The GSF needs an approximation of the Bethe-Heitler distribution as a Gaussian mixture on each material interaction (see above). This task is delegated to a separate class, that can be provided by a template parameter to {class}`Acts::Experimental::GaussianSumFitter`, so in principle it can be implemented in different ways. diff --git a/docs/figures/gsf_bethe_heitler_approx.svg b/docs/figures/gsf_bethe_heitler_approx.svg new file mode 100644 index 00000000000..0f401a1f1db --- /dev/null +++ b/docs/figures/gsf_bethe_heitler_approx.svg @@ -0,0 +1,1502 @@ + + + + + + + + 2022-10-26T13:05:12.816859 + image/svg+xml + + + Matplotlib v3.6.0, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/figures/gsf_overview.svg b/docs/figures/gsf_overview.svg new file mode 100644 index 00000000000..d90952285a3 --- /dev/null +++ b/docs/figures/gsf_overview.svg @@ -0,0 +1,1545 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From de75b216833cb8c940a274e37f5a78b8c182ea63 Mon Sep 17 00:00:00 2001 From: Benjamin Huth <37871400+benjaminhuth@users.noreply.github.com> Date: Mon, 14 Nov 2022 12:41:54 +0100 Subject: [PATCH 07/57] feat: Basic GSF physics performance monitoring (#1640) This sets up some basic performance monitoring for the GSF, it may need to be tuned in the future --- CI/physmon/comment_template.md | 22 ++++++++++++-- CI/physmon/gsf.yml | 13 +++++++++ CI/physmon/phys_perf_mon.sh | 8 ++++++ CI/physmon/physmon.py | 27 +++++++++++++++++- CI/physmon/reference/performance_gsf.root | Bin 0 -> 97063 bytes .../python/acts/examples/reconstruction.py | 2 +- Examples/Python/tests/root_file_hashes.txt | 4 +-- Examples/Scripts/Python/truth_tracking_gsf.py | 10 +++++++ 8 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 CI/physmon/gsf.yml create mode 100644 CI/physmon/reference/performance_gsf.root diff --git a/CI/physmon/comment_template.md b/CI/physmon/comment_template.md index ab9aaf6fd30..9277da061f7 100644 --- a/CI/physmon/comment_template.md +++ b/CI/physmon/comment_template.md @@ -1,6 +1,6 @@ ## :bar_chart: Physics performance monitoring for {{ commit }} {% if has_errors %} -> :red_square: **ERROR** The result has missing elements! +> :red_square: **ERROR** The result has missing elements! > This is likely a physmon job failure {% endif %} @@ -8,7 +8,8 @@ CKF: {{ make_url("seeded", "ckf_seeded.html") }}, {{ make_url("truth smeared", "ckf_truth_smeared.html") }}, {{ make_url("truth estimated", "ckf_truth_estimated.html") }} IVF: {{ make_url("seeded", "ivf_seeded.html") }}, {{ make_url("truth smeared", "ivf_truth_smeared.html") }}, {{ make_url("truth estimated", "ivf_truth_estimated.html") }} {{ make_url("Ambiguity resolution", "ambi_seeded.html") }} -{{ make_url("Truth tracking", "truth_tracking.html") }} +{{ make_url("Truth tracking (Kalman Filter)", "truth_tracking.html") }} +{{ make_url("Truth tracking (GSF)", "gsf.html")}} ### Vertexing {{ "" if all_exist( "vertexing_mu_scan.pdf", @@ -79,7 +80,7 @@ IVF: {{ make_url("seeded", "ivf_seeded.html") }}, {{ make_url("truth smeared", " {% endcall %} -### Truth tracking {{ "" if exists("truth_tracking_plots") else ":x: "}} +### Truth tracking (Kalman Filter) {{ "" if exists("truth_tracking_plots") else ":x: "}} {% call detail_block("Truth tracking", "truth_tracking_plots") %} @@ -97,3 +98,18 @@ IVF: {{ make_url("seeded", "ivf_seeded.html") }}, {{ make_url("truth smeared", " {%- endfor %} {% endcall %} + +### Truth tracking (GSF) {{ "" if exists("truth_tracking_plots") else ":x: "}} + +{% call detail_block("Truth tracking", "truth_tracking_plots") %} + +{% for url in [ + "pull_d0.pdf", + "res_d0.pdf", + "pull_qop.pdf", + "res_qop.pdf", +] -%} +{{- make_image("gsf_plots/"+url, "50%") -}} +{%- endfor %} + +{% endcall %} diff --git a/CI/physmon/gsf.yml b/CI/physmon/gsf.yml new file mode 100644 index 00000000000..9a85e92618e --- /dev/null +++ b/CI/physmon/gsf.yml @@ -0,0 +1,13 @@ +checks: + "*": + Chi2Test: + threshold: 0.01 + KolmogorovTest: + threshold: 0.68 + RatioCheck: + threshold: 3 + ResidualCheck: + threshold: 3 + IntegralCheck: + threshold: 3 + diff --git a/CI/physmon/phys_perf_mon.sh b/CI/physmon/phys_perf_mon.sh index bc38ec90c02..a1c54875e43 100755 --- a/CI/physmon/phys_perf_mon.sh +++ b/CI/physmon/phys_perf_mon.sh @@ -72,6 +72,14 @@ full_chain truth_smeared full_chain truth_estimated full_chain seeded +run \ + $outdir/performance_gsf.root \ + $refdir/performance_gsf.root \ + --title "Truth tracking (GSF)" \ + -c CI/physmon/gsf.yml \ + -o $outdir/gsf.html \ + -p $outdir/gsf_plots + run \ $outdir/performance_truth_tracking.root \ $refdir/performance_truth_tracking.root \ diff --git a/CI/physmon/physmon.py b/CI/physmon/physmon.py index d72a9b155ed..b4992fc8b9c 100755 --- a/CI/physmon/physmon.py +++ b/CI/physmon/physmon.py @@ -19,6 +19,7 @@ acts.logging.setFailureThreshold(acts.logging.FATAL) from truth_tracking_kalman import runTruthTrackingKalman +from truth_tracking_gsf import runTruthTrackingGsf from common import getOpenDataDetectorDirectory from acts.examples.odd import getOpenDataDetector from acts.examples.simulation import ( @@ -74,9 +75,11 @@ field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) -s = acts.examples.Sequencer(events=10000, numThreads=-1, logLevel=acts.logging.INFO) + +### Truth tracking with Kalman Filter with tempfile.TemporaryDirectory() as temp: + s = acts.examples.Sequencer(events=10000, numThreads=-1, logLevel=acts.logging.INFO) tp = Path(temp) runTruthTrackingKalman( trackingGeometry, @@ -94,6 +97,28 @@ shutil.copy(perf_file, outdir / "performance_truth_tracking.root") +### GSF + +with tempfile.TemporaryDirectory() as temp: + s = acts.examples.Sequencer(events=500, numThreads=-1, logLevel=acts.logging.INFO) + + tp = Path(temp) + runTruthTrackingGsf( + trackingGeometry, + digiConfig, + field, + outputDir=tp, + s=s, + ) + + s.run() + del s + + perf_file = tp / "performance_gsf.root" + assert perf_file.exists(), "Performance file not found" + shutil.copy(perf_file, outdir / "performance_gsf.root") + + ### CKF track finding variations for truthSmearedSeeded, truthEstimatedSeeded, label in [ diff --git a/CI/physmon/reference/performance_gsf.root b/CI/physmon/reference/performance_gsf.root new file mode 100644 index 0000000000000000000000000000000000000000..b8d4355fd7618d295da95e6bd369e438e4d3c6fa GIT binary patch literal 97063 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

)P1JJS{$4UM`!%| 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 E0QdqUwEzGB literal 0 HcmV?d00001 diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index 95cb45c4310..cbe408c936b 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -649,7 +649,7 @@ def addTruthTrackingGsf( "maxComponents": 12, "abortOnError": False, "disableAllMaterialHandling": False, - "finalReductionMethod": acts.examples.FinalReductionMethod.mean, + "finalReductionMethod": acts.examples.FinalReductionMethod.maxWeight, "weightCutoff": 1.0e-4, } diff --git a/Examples/Python/tests/root_file_hashes.txt b/Examples/Python/tests/root_file_hashes.txt index 01f40bd9173..7af34d22cf5 100644 --- a/Examples/Python/tests/root_file_hashes.txt +++ b/Examples/Python/tests/root_file_hashes.txt @@ -75,9 +75,9 @@ test_truth_tracking_kalman[odd-1000.0]__tracksummary_fitter.root: c7b3ff9d8d3c19 test_truth_tracking_kalman[odd-1000.0]__performance_track_finder.root: 76a990d595b6e097da2bed447783bd63044956e5649a5dd6fd7a6a3434786877 test_truth_tracking_gsf[generic]__trackstates_gsf.root: ed0f918d88268a280d93a143d4ec87ebd4bb6fce17567f31a04d05ae91aa3de9 -test_truth_tracking_gsf[generic]__tracksummary_gsf.root: bc7b6538b200e619cf32d84d4f258b060c52170d651e0c52fea21d4126d64943 +test_truth_tracking_gsf[generic]__tracksummary_gsf.root: 0d4522fd1eae264296ef2fc159d11f97aa92f43e33830710c57aa37aa3b64169 test_truth_tracking_gsf[odd]__trackstates_gsf.root: dd4062fe35168d8de2bfaebf17cf838b87988165ebc844c452702d38381ff0f9 -test_truth_tracking_gsf[odd]__tracksummary_gsf.root: 002a558c4c36bc7d3405af69edab936e50a3d8eeb434f29a3fce80f3d82c5453 +test_truth_tracking_gsf[odd]__tracksummary_gsf.root: f5d0436af2c2e42fe5532051b6998f4d1a2a2ca52eb6ae212da3f6914dc571d7 test_digitization_example_input__measurements.root: ccc92f0ad538d1b62d98f19f947970bcc491843e54d8ffeed16ad2e226b8caee test_digitization_example_input__particles.root: 78a89f365177423d0834ea6f1bd8afe1488e72b12a25066a20bd9050f5407860 diff --git a/Examples/Scripts/Python/truth_tracking_gsf.py b/Examples/Scripts/Python/truth_tracking_gsf.py index 94e52eb7ec9..dcd623d2db5 100755 --- a/Examples/Scripts/Python/truth_tracking_gsf.py +++ b/Examples/Scripts/Python/truth_tracking_gsf.py @@ -123,6 +123,16 @@ def runTruthTrackingGsf( ) ) + s.addWriter( + acts.examples.TrackFitterPerformanceWriter( + level=acts.logging.INFO, + inputTrajectories="gsf_trajectories", + inputParticles="truth_seeds_selected", + inputMeasurementParticlesMap="measurement_particles_map", + filePath=str(outputDir / "performance_gsf.root"), + ) + ) + return s From 99380cb3b08316bd657480159bd3f303175bfeca Mon Sep 17 00:00:00 2001 From: Luis Falda Coelho <56648068+LuisFelipeCoelho@users.noreply.github.com> Date: Tue, 15 Nov 2022 13:56:35 +0100 Subject: [PATCH 08/57] feat: variable r range in orthogonal seeding (#1621) This was included in the orthodox seeding in #1084 --- Core/include/Acts/Seeding/SeedFinder.ipp | 12 ++++++- .../include/Acts/Seeding/SeedFinderConfig.hpp | 12 +++++-- .../Acts/Seeding/SeedFinderOrthogonal.ipp | 36 ++++++++++++++----- .../Seeding/SeedFinderOrthogonalConfig.hpp | 8 +++++ .../TrackFinding/src/SeedingAlgorithm.cpp | 2 +- .../python/acts/examples/reconstruction.py | 2 ++ Examples/Python/src/TrackFinding.cpp | 12 +++++-- 7 files changed, 67 insertions(+), 17 deletions(-) diff --git a/Core/include/Acts/Seeding/SeedFinder.ipp b/Core/include/Acts/Seeding/SeedFinder.ipp index 1491ef5fe15..a649ecbb306 100644 --- a/Core/include/Acts/Seeding/SeedFinder.ipp +++ b/Core/include/Acts/Seeding/SeedFinder.ipp @@ -51,7 +51,7 @@ void SeedFinder::createSeedsForGroup( float varianceRM = spM->varianceR(); float varianceZM = spM->varianceZ(); - /// check if spM is outside our radial region of interest + // check if spM is outside our radial region of interest if (m_config.useVariableMiddleSPRange) { if (rM < rMiddleSPRange.min()) { continue; @@ -80,6 +80,16 @@ void SeedFinder::createSeedsForGroup( } continue; } + } else { + if (rM > m_config.rMaxMiddle) { + continue; + } + if (rM < m_config.rMinMiddle) { + if (m_config.forceRadialSorting) { + break; + } + continue; + } } state.compatTopSP.clear(); diff --git a/Core/include/Acts/Seeding/SeedFinderConfig.hpp b/Core/include/Acts/Seeding/SeedFinderConfig.hpp index 2efb963cac9..92e54831c30 100644 --- a/Core/include/Acts/Seeding/SeedFinderConfig.hpp +++ b/Core/include/Acts/Seeding/SeedFinderConfig.hpp @@ -45,14 +45,20 @@ struct SeedFinderConfig { float deltaRMaxBottomSP = std::numeric_limits::quiet_NaN(); // radial bin size for filling space point grid float binSizeR = 1. * Acts::UnitConstants::mm; - // force sorting in R in space point grid bins + + // force sorting of middle SPs in radius bool forceRadialSorting = false; // radial range for middle SP - std::vector> rRangeMiddleSP; - bool useVariableMiddleSPRange = false; + // variable range based on SP radius + bool useVariableMiddleSPRange = true; float deltaRMiddleMinSPRange = 10. * Acts::UnitConstants::mm; float deltaRMiddleMaxSPRange = 10. * Acts::UnitConstants::mm; + // range defined in vector for each z region + std::vector> rRangeMiddleSP; + // range defined by rMinMiddle and rMaxMiddle + float rMinMiddle = 60.f * Acts::UnitConstants::mm; + float rMaxMiddle = 120.f * Acts::UnitConstants::mm; // cut to the maximum value of delta z between SPs float deltaZMax = diff --git a/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp b/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp index adcc40ad588..db740ea9e9e 100644 --- a/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp +++ b/Core/include/Acts/Seeding/SeedFinderOrthogonal.ipp @@ -458,15 +458,6 @@ void SeedFinderOrthogonal::processFromMiddleSP( */ std::vector bottom_lh_v, bottom_hl_v, top_lh_v, top_hl_v; - /* - * Cut: Ensure that the middle spacepoint lies within a valid r-region for - * middle points. - */ - if (middle.radius() > m_config.rMaxMiddle || - middle.radius() < m_config.rMinMiddle) { - return; - } - /* * Calculate the search ranges for bottom and top candidates for this middle * space point. @@ -663,13 +654,23 @@ void SeedFinderOrthogonal::createSeeds( * take each external spacepoint, allocate a corresponding internal space * point, and save it in a vector. */ + Acts::Extent rRangeSPExtent; std::vector internalSpacePoints; for (const external_spacepoint_t *p : spacePoints) { internalSpacePoints.push_back(new InternalSpacePoint( *p, {p->x(), p->y(), p->z()}, {0.0, 0.0}, {p->varianceR(), p->varianceZ()})); + // store x,y,z values in extent + rRangeSPExtent.extend({p->x(), p->y(), p->z()}); } + // variable middle SP radial region of interest + const Acts::Range1D rMiddleSPRange( + std::floor(rRangeSPExtent.min(Acts::binR) / 2) * 2 + + m_config.deltaRMiddleMinSPRange, + std::floor(rRangeSPExtent.max(Acts::binR) / 2) * 2 - + m_config.deltaRMiddleMaxSPRange); + /* * Construct the k-d tree from these points. Note that this not consume or * take ownership of the points. @@ -681,6 +682,23 @@ void SeedFinderOrthogonal::createSeeds( * seeing what happens if we take them to be our middle spacepoint. */ for (const typename tree_t::pair_t &middle_p : tree) { + internal_sp_t &middle = *middle_p.second; + auto rM = middle.radius(); + + /* + * Cut: Ensure that the middle spacepoint lies within a valid r-region for + * middle points. + */ + if (m_config.useVariableMiddleSPRange) { + if (rM < rMiddleSPRange.min() || rM > rMiddleSPRange.max()) { + continue; + } + } else { + if (rM > m_config.rMaxMiddle || rM < m_config.rMinMiddle) { + continue; + } + } + processFromMiddleSP(tree, out_cont, middle_p); } diff --git a/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp b/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp index 23bf1297d4f..ff9a84621a8 100644 --- a/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp +++ b/Core/include/Acts/Seeding/SeedFinderOrthogonalConfig.hpp @@ -64,6 +64,14 @@ struct SeedFinderOrthogonalConfig { // which will make seeding very slow! float rMin = 33 * Acts::UnitConstants::mm; + // radial range for middle SP + // variable range based on SP radius + bool useVariableMiddleSPRange = true; + float deltaRMiddleMinSPRange = 10. * Acts::UnitConstants::mm; + float deltaRMiddleMaxSPRange = 10. * Acts::UnitConstants::mm; + // range defined in vector for each z region + std::vector> rRangeMiddleSP; + // range defined by rMinMiddle and rMaxMiddle float rMinMiddle = 60.f * Acts::UnitConstants::mm; float rMaxMiddle = 120.f * Acts::UnitConstants::mm; diff --git a/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp b/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp index 01e1bfefc09..ea36fbc04f4 100644 --- a/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp +++ b/Examples/Algorithms/TrackFinding/src/SeedingAlgorithm.cpp @@ -222,7 +222,7 @@ ActsExamples::ProcessCode ActsExamples::SeedingAlgorithm::execute( auto finder = Acts::SeedFinder(m_cfg.seedFinderConfig, m_cfg.seedFinderOptions); - /// variable middle SP radial region of interest + // variable middle SP radial region of interest const Acts::Range1D rMiddleSPRange( std::floor(rRangeSPExtent.min(Acts::binR) / 2) * 2 + m_cfg.seedFinderConfig.deltaRMiddleMinSPRange, diff --git a/Examples/Python/python/acts/examples/reconstruction.py b/Examples/Python/python/acts/examples/reconstruction.py index cbe408c936b..185708ddec9 100644 --- a/Examples/Python/python/acts/examples/reconstruction.py +++ b/Examples/Python/python/acts/examples/reconstruction.py @@ -468,6 +468,8 @@ def addSeeding( interactionPointCut=seedFinderConfigArg.interactionPointCut, deltaZMax=seedFinderConfigArg.deltaZMax, maxPtScattering=seedFinderConfigArg.maxPtScattering, + rRangeMiddleSP=seedFinderConfigArg.rRangeMiddleSP, + useVariableMiddleSPRange=seedFinderConfigArg.useVariableMiddleSPRange, seedConfirmation=seedFinderConfigArg.seedConfirmation, centralSeedConfirmationRange=seedFinderConfigArg.centralSeedConfirmationRange, forwardSeedConfirmationRange=seedFinderConfigArg.forwardSeedConfirmationRange, diff --git a/Examples/Python/src/TrackFinding.cpp b/Examples/Python/src/TrackFinding.cpp index 3f597870650..547b443b38c 100644 --- a/Examples/Python/src/TrackFinding.cpp +++ b/Examples/Python/src/TrackFinding.cpp @@ -103,10 +103,12 @@ void addTrackFinding(Context& ctx) { ACTS_PYTHON_MEMBER(skipPreviousTopSP); ACTS_PYTHON_MEMBER(interactionPointCut); ACTS_PYTHON_MEMBER(zBinsCustomLooping); - ACTS_PYTHON_MEMBER(rRangeMiddleSP); ACTS_PYTHON_MEMBER(useVariableMiddleSPRange); ACTS_PYTHON_MEMBER(deltaRMiddleMinSPRange); ACTS_PYTHON_MEMBER(deltaRMiddleMaxSPRange); + ACTS_PYTHON_MEMBER(rRangeMiddleSP); + ACTS_PYTHON_MEMBER(rMinMiddle); + ACTS_PYTHON_MEMBER(rMaxMiddle); ACTS_PYTHON_MEMBER(binSizeR); ACTS_PYTHON_MEMBER(forceRadialSorting); ACTS_PYTHON_MEMBER(seedConfirmation); @@ -154,14 +156,18 @@ void addTrackFinding(Context& ctx) { ACTS_PYTHON_MEMBER(deltaZMax); ACTS_PYTHON_MEMBER(skipPreviousTopSP); ACTS_PYTHON_MEMBER(interactionPointCut); - ACTS_PYTHON_MEMBER(rMinMiddle); - ACTS_PYTHON_MEMBER(rMaxMiddle); ACTS_PYTHON_MEMBER(deltaPhiMax); ACTS_PYTHON_MEMBER(highland); ACTS_PYTHON_MEMBER(maxScatteringAngle2); ACTS_PYTHON_MEMBER(pTPerHelixRadius); ACTS_PYTHON_MEMBER(minHelixDiameter2); ACTS_PYTHON_MEMBER(pT2perRadius); + ACTS_PYTHON_MEMBER(useVariableMiddleSPRange); + ACTS_PYTHON_MEMBER(deltaRMiddleMinSPRange); + ACTS_PYTHON_MEMBER(deltaRMiddleMaxSPRange); + ACTS_PYTHON_MEMBER(rRangeMiddleSP); + ACTS_PYTHON_MEMBER(rMinMiddle); + ACTS_PYTHON_MEMBER(rMaxMiddle); ACTS_PYTHON_MEMBER(seedConfirmation); ACTS_PYTHON_MEMBER(centralSeedConfirmationRange); ACTS_PYTHON_MEMBER(forwardSeedConfirmationRange); From b0181198eef9a81f959b502481b38d2d0f22ac26 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Tue, 15 Nov 2022 16:13:06 +0100 Subject: [PATCH 09/57] feat: Owning delegate (#1674) This PR changes `Acts::Delegate` so it can **optionally** own it's payload (i.e. isn't copyable and destroys it correctly). The default remains to not own it. The memory footprint of a non-owning stays as before, the owning delegate gets an extra pointer to a deleter function. I added an alias `OwningDelegate`. --- Core/include/Acts/Utilities/Delegate.hpp | 145 ++++++++++++++---- .../Core/Utilities/DelegateTests.cpp | 133 ++++++++++++++++ 2 files changed, 252 insertions(+), 26 deletions(-) diff --git a/Core/include/Acts/Utilities/Delegate.hpp b/Core/include/Acts/Utilities/Delegate.hpp index d6a69c0acfc..002102426dc 100644 --- a/Core/include/Acts/Utilities/Delegate.hpp +++ b/Core/include/Acts/Utilities/Delegate.hpp @@ -12,26 +12,35 @@ #include #include +#include +#include namespace Acts { -template +/// Ownership enum for @c Delegate +enum class DelegateType { Owning, NonOwning }; + +// Specialization needed for defaulting ownership and for R(Args...) syntax +template class Delegate; -/// Delegate type that allows type erasure of a callable without allocation and -/// with a single level of indirection. -/// This type can support: +/// Delegate type that allows type erasure of a callable without allocation +/// and with a single level of indirection. This type can support: /// - a free function pointer /// - a pointer to a member function alongside an instance pointer -/// @note @c Delegate does not assume ownership of the instance. -/// You need to ensure that the lifetime of the callable -/// instance is longer than that of the @c Delegate. +/// @note @c Delegate by default does not assume ownership of the instance. +/// In that case You need to ensure that the lifetime of the callable +/// instance is longer than that of the @c Delegate. If you set @c O +/// to @c DelegateType::Owning, it will assume ownership. /// @note Currently @c Delegate only supports callables that are ``const`` /// @tparam R Return type of the function signature +/// @tparam O Ownership type of the delegate: Owning or NonOwning /// @tparam Args Types of the arguments of the function signatures /// -template -class Delegate { +template +class Delegate { + static constexpr DelegateType kOwnership = O; + /// Alias of the return type using return_type = R; /// Alias to the function pointer type this class will store @@ -39,22 +48,33 @@ class Delegate { using function_ptr_type = return_type (*)(Args...); + using deleter_type = void (*)(const void *); + template using isSignatureCompatible = decltype(std::declval() = std::declval()); + using OwningDelegate = Delegate; + using NonOwningDelegate = Delegate; template using isNoFunPtr = std::enable_if_t< - not std::is_convertible_v, function_type>>; + not std::is_convertible_v, function_type> and + not std::is_same_v, OwningDelegate> and + not std::is_same_v, NonOwningDelegate>>; public: Delegate() = default; + Delegate(Delegate &&) = default; + Delegate &operator=(Delegate &&) = default; + Delegate(const Delegate &) = default; + Delegate &operator=(const Delegate &) = default; + /// 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)`. + /// i.e. if the signature of the delegate is `void(int)`, the + /// callable's signature has to be `void(const void*, int)`. Delegate(function_type callable) { connect(callable); } /// Constructor with a possibly stateful function object. @@ -75,8 +95,8 @@ class Delegate { /// 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*`. - /// i.e. if the signature of the delegate is `void(int)`, the callable's - /// signature has to be `void(const void*, int)`. + /// i.e. if the signature of the delegate is `void(int)`, the + /// callable's signature has to be `void(const void*, int)`. void operator=(function_type callable) { connect(callable); } /// Assignment operator with possibly stateful function object. @@ -99,7 +119,7 @@ class Delegate { /// @tparam Callable The compile-time free function pointer template void connect() { - m_payload = nullptr; + m_payload.payload = nullptr; static_assert( Concepts::is_detected { connect<&Callable::operator(), Callable>(&callable); } - /// Connection with rvalue reference is deleted, should catch assignment from - /// temporary objects and thus invalid pointers + /// Connection with rvalue reference is deleted, should catch assignment + /// from temporary objects and thus invalid pointers template > void connect(Callable &&) = delete; /// 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*`. - /// i.e. if the signature of the delegate is `void(int)`, the callable's - /// signature has to be `void(const void*, int)`. + /// i.e. if the signature of the delegate is `void(int)`, the + /// callable's signature has to be `void(const void*, int)`. void connect(function_type callable) { - m_payload = nullptr; + if constexpr (kOwnership == DelegateType::NonOwning) { + m_payload.payload = nullptr; + } m_function = callable; } @@ -143,7 +165,8 @@ class Delegate { /// @param instance The instance on which the member function pointer should be called on /// @note @c Delegate does not assume owner ship over @p instance. You need to ensure /// it's lifetime is longer than that of @c Delegate. - template + template > void connect(const Type *instance) { using member_ptr_type = return_type (Type::*)(Args...) const; @@ -152,7 +175,39 @@ class Delegate { "Callable given does not correspond exactly to required call " "signature"); - m_payload = instance; + m_payload.payload = instance; + + m_function = [](const void *payload, Args... args) -> return_type { + assert(payload != nullptr && "Payload is required, but not set"); + const auto *concretePayload = static_cast(payload); + return std::invoke(Callable, concretePayload, + std::forward(args)...); + }; + } + + /// Connect a member function to be called on an instance + /// @tparam Callable The compile-time member function pointer + /// @tparam Type The type of the instance the member function should be called on + /// @param instance The instance on which the member function pointer should be called on + /// @note @c Delegate assumes owner ship over @p instance. + template + void connect(std::unique_ptr instance) { + using member_ptr_type = return_type (Type::*)(Args...) const; + static_assert(Concepts::is_detected::value, + "Callable given does not correspond exactly to required call " + "signature"); + + if constexpr (kOwnership == DelegateType::Owning) { + m_payload.payload = std::unique_ptr( + instance.release(), [](const void *payload) { + const auto *concretePayload = static_cast(payload); + delete concretePayload; + }); + } else { + m_payload.payload = instance.release(); + } + m_function = [](const void *payload, Args... args) -> return_type { assert(payload != nullptr && "Payload is required, but not set"); const auto *concretePayload = static_cast(payload); @@ -166,7 +221,8 @@ class Delegate { /// @return Return value of the contained function return_type operator()(Args... args) const { assert(connected() && "Delegate is not connected"); - return std::invoke(m_function, m_payload, std::forward(args)...); + return std::invoke(m_function, m_payload.ptr(), + std::forward(args)...); } /// Return whether this delegate is currently connected @@ -179,14 +235,51 @@ class Delegate { /// Disconnect this delegate, meaning it cannot be called anymore void disconnect() { - m_payload = nullptr; + m_payload.clear(); m_function = nullptr; } private: - /// Stores the instance pointer - const void *m_payload{nullptr}; + // Deleter that does not do anything + static void noopDeleter(const void *) {} + + /// @cond + + // Payload object without a deleter + struct NonOwningPayload { + void clear() { payload = nullptr; } + + const void *ptr() const { return payload; } + + const void *payload{nullptr}; + }; + + // Payload object with a deleter + struct OwningPayload { + void clear() { payload.reset(); } + + const void *ptr() const { return payload.get(); } + + std::unique_ptr payload{nullptr, &noopDeleter}; + }; + + /// Stores the instance pointer and maybe a deleter + std::conditional_t + m_payload; + + /// @endcond + /// Stores the function pointer wrapping the compile time function pointer given in @c connect(). function_type m_function{nullptr}; }; + +template +class OwningDelegate; + +/// Alias for an owning delegate +template +class OwningDelegate + : public Delegate {}; + } // namespace Acts diff --git a/Tests/UnitTests/Core/Utilities/DelegateTests.cpp b/Tests/UnitTests/Core/Utilities/DelegateTests.cpp index fa8dc58dadf..775b4b5821f 100644 --- a/Tests/UnitTests/Core/Utilities/DelegateTests.cpp +++ b/Tests/UnitTests/Core/Utilities/DelegateTests.cpp @@ -12,6 +12,7 @@ #include "Acts/Tests/CommonHelpers/FloatComparisons.hpp" #include "Acts/Utilities/Delegate.hpp" +#include #include #include #include @@ -199,4 +200,136 @@ BOOST_AUTO_TEST_CASE(StatefullLambdas) { // d.connect([&](int a){ v.push_back(a); return v.size(); }); } +struct CheckDestructor { + CheckDestructor(bool* _out) : destructorCalled{_out} {} + + bool* destructorCalled; + + int func() const { return 4; } + + ~CheckDestructor() { (*destructorCalled) = true; } +}; + +int owningTest() { + return 8; +} + +int owningTest2(const void*) { + return 8; +} + +BOOST_AUTO_TEST_CASE(OwningDelegateTest) { + { + auto s = std::make_unique(); + Delegate d; + d.connect<&SignatureTest::modify>(std::move(s)); + + int v = 0; + d(v, 42); + BOOST_CHECK_EQUAL(v, 42); + } + + { + bool destructorCalled = false; + auto s = std::make_unique(&destructorCalled); + { + BOOST_CHECK_EQUAL(destructorCalled, false); + Delegate d; + BOOST_CHECK_EQUAL(destructorCalled, false); + d.connect<&CheckDestructor::func>(s.get()); + BOOST_CHECK_EQUAL(destructorCalled, false); + Delegate dCopy{d}; + BOOST_CHECK_EQUAL(d(), 4); + BOOST_CHECK_EQUAL(dCopy(), 4); + BOOST_CHECK_EQUAL(destructorCalled, false); + } + // destructor not called after non-owning delegate goes out of scope + BOOST_CHECK_EQUAL(destructorCalled, false); + + { + BOOST_CHECK_EQUAL(destructorCalled, false); + Delegate d; + // This doesn't compile: owning delegate is not copyable + // Delegate dCopy = d; + BOOST_CHECK_EQUAL(destructorCalled, false); + // This doesn't compile: owning delegate cannot accept raw pointer + // instance + // d.connect<&CheckDestructor::func>(s.get()); + d.connect<&CheckDestructor::func>(std::move(s)); + BOOST_CHECK_EQUAL(destructorCalled, false); + BOOST_CHECK_EQUAL(d(), 4); + BOOST_CHECK_EQUAL(destructorCalled, false); + } + // destructor called after owning delegate goes out of scope + BOOST_CHECK_EQUAL(destructorCalled, true); + + destructorCalled = false; + s = std::make_unique(&destructorCalled); + { + BOOST_CHECK_EQUAL(destructorCalled, false); + OwningDelegate d; + // This doesn't compile: owning delegate is not copyable + // OwningDelegate dCopy = d; + BOOST_CHECK_EQUAL(destructorCalled, false); + d.connect<&CheckDestructor::func>(std::move(s)); + BOOST_CHECK_EQUAL(destructorCalled, false); + BOOST_CHECK_EQUAL(d(), 4); + BOOST_CHECK_EQUAL(destructorCalled, false); + } + // destructor called after owning delegate goes out of scope + BOOST_CHECK_EQUAL(destructorCalled, true); + } + + { + bool destructorCalled = false; + auto s = std::make_unique(&destructorCalled); + { + BOOST_CHECK_EQUAL(destructorCalled, false); + Delegate d; + BOOST_CHECK_EQUAL(destructorCalled, false); + d.connect<&CheckDestructor::func>(s.get()); + Delegate dCopy{d}; + BOOST_CHECK_EQUAL(destructorCalled, false); + BOOST_CHECK_EQUAL(d(), 4); + BOOST_CHECK_EQUAL(dCopy(), 4); + BOOST_CHECK_EQUAL(destructorCalled, false); + d.disconnect(); + BOOST_CHECK_EQUAL(destructorCalled, false); + } + + { + BOOST_CHECK_EQUAL(destructorCalled, false); + Delegate d; + // This doesn't compile: owning delegate is not copyable + // Delegate dCopy = d; + BOOST_CHECK_EQUAL(destructorCalled, false); + // This doesn't compile: owning delegate cannot accept raw pointer + // instance + // d.connect<&CheckDestructor::func>(s.get()); + d.connect<&CheckDestructor::func>(std::move(s)); + BOOST_CHECK_EQUAL(destructorCalled, false); + BOOST_CHECK_EQUAL(d(), 4); + BOOST_CHECK_EQUAL(destructorCalled, false); + d.disconnect(); + BOOST_CHECK_EQUAL(destructorCalled, true); + } + // destructor called after owning delegate goes out of scope + BOOST_CHECK_EQUAL(destructorCalled, true); + } + + { + OwningDelegate d; + d.connect<&owningTest>(); + BOOST_CHECK_EQUAL(d(), 8); + + d.disconnect(); + d.connect<&owningTest>(); + BOOST_CHECK_EQUAL(d(), 8); + + d.disconnect(); + d.connect(owningTest2); + BOOST_CHECK_EQUAL(d(), 8); + } +} + BOOST_AUTO_TEST_SUITE_END() From 85a67758f79f3b01aed4025592ab6a5e5ffbd323 Mon Sep 17 00:00:00 2001 From: Andreas Stefl Date: Wed, 16 Nov 2022 12:18:04 +0100 Subject: [PATCH 10/57] chore: clang tidy headers (#1662) fix clang tidy for header files. also includes fixes for clang-tidy warnings from @paulgessinger replacement for https://github.com/acts-project/acts/pull/1440 Co-authored-by: Paul Gessinger <1058585+paulgessinger@users.noreply.github.com> --- .gitlab-ci.yml | 29 ++- .../ActsAlignment/Kernel/Alignment.ipp | 2 - .../Kernel/detail/AlignmentEngine.hpp | 6 +- CI/clang_tidy/limits.yml | 22 ++- CI/clang_tidy/parse_clang_tidy.py | 13 +- CI/clang_tidy/requirements.txt | 2 +- CI/clang_tidy/run_clang_tidy.sh | 1 - .../Acts/Clusterization/Clusterization.ipp | 12 +- Core/include/Acts/Definitions/Common.hpp | 6 +- .../Acts/Digitization/PlanarModuleCluster.hpp | 8 +- Core/include/Acts/EventData/Charge.hpp | 8 +- .../MultiComponentBoundTrackParameters.hpp | 12 +- .../Acts/EventData/MultiTrajectory.hpp | 4 + .../Acts/EventData/VectorMultiTrajectory.hpp | 4 +- .../Acts/Geometry/BoundarySurfaceT.hpp | 4 +- .../Acts/Geometry/ConeVolumeBounds.hpp | 6 +- .../Acts/Geometry/CylinderVolumeBounds.hpp | 2 +- .../Acts/Geometry/SurfaceArrayCreator.hpp | 16 +- .../Acts/Geometry/TrapezoidVolumeBounds.hpp | 2 +- .../MagneticField/detail/SmallObjectCache.hpp | 6 +- .../Acts/Material/InterpolatedMaterialMap.hpp | 6 +- .../Acts/Material/MaterialMapUtils.hpp | 5 +- .../Acts/Material/VolumeMaterialMapper.hpp | 3 +- Core/include/Acts/Propagator/AtlasStepper.hpp | 49 ++--- .../Acts/Propagator/CovarianceTransport.hpp | 4 +- Core/include/Acts/Propagator/EigenStepper.hpp | 2 +- Core/include/Acts/Propagator/EigenStepper.ipp | 2 +- .../Acts/Propagator/MultiEigenStepperLoop.hpp | 5 +- .../Acts/Propagator/MultiEigenStepperLoop.ipp | 2 +- .../Acts/Propagator/RiddersPropagator.ipp | 5 +- .../Acts/Propagator/StepperExtensionList.hpp | 2 +- .../Acts/Propagator/detail/Auctioneer.hpp | 2 +- .../GenericDenseEnvironmentExtension.hpp | 14 +- .../detail/PointwiseMaterialInteraction.hpp | 2 +- .../detail/VolumeMaterialInteraction.hpp | 5 +- Core/include/Acts/Seeding/BinFinder.hpp | 16 +- Core/include/Acts/Seeding/BinFinder.ipp | 17 +- Core/include/Acts/Seeding/BinnedSPGroup.hpp | 9 +- .../Seeding/EstimateTrackParamsFromSeed.hpp | 2 +- .../Acts/Seeding/InternalSpacePoint.hpp | 1 + Core/include/Acts/Seeding/SeedFilter.hpp | 4 +- Core/include/Acts/Seeding/SeedFilter.ipp | 8 +- Core/include/Acts/Seeding/SeedFinder.hpp | 2 +- Core/include/Acts/Seeding/SeedFinder.ipp | 20 +- .../Acts/Seeding/SeedFinderOrthogonal.ipp | 3 +- Core/include/Acts/Seeding/SeedFinderUtils.ipp | 10 +- Core/include/Acts/Seeding/SpacePointGrid.hpp | 16 +- Core/include/Acts/Seeding/SpacePointGrid.ipp | 2 +- .../SpacePointFormation/SpacePointBuilder.hpp | 2 +- .../detail/SpacePointBuilder.ipp | 17 +- Core/include/Acts/Surfaces/AnnulusBounds.hpp | 6 +- .../Acts/Surfaces/ConvexPolygonBounds.ipp | 2 +- .../Acts/Surfaces/DiscTrapezoidBounds.hpp | 2 +- Core/include/Acts/Surfaces/LineSurface.hpp | 2 +- Core/include/Acts/Surfaces/PlaneSurface.hpp | 8 +- Core/include/Acts/Surfaces/SurfaceArray.hpp | 7 +- .../CombinatorialKalmanFilter.hpp | 8 +- .../Acts/TrackFitting/BetheHeitlerApprox.hpp | 4 +- .../Acts/TrackFitting/GaussianSumFitter.hpp | 2 +- Core/include/Acts/TrackFitting/GsfOptions.hpp | 2 + .../Acts/TrackFitting/KalmanFitter.hpp | 9 +- .../Acts/TrackFitting/detail/GsfActor.hpp | 15 +- Core/include/Acts/Utilities/BinAdjustment.hpp | 8 +- Core/include/Acts/Utilities/BinUtility.hpp | 9 +- Core/include/Acts/Utilities/BinnedArray.hpp | 4 +- Core/include/Acts/Utilities/BinnedArrayXD.hpp | 4 +- Core/include/Acts/Utilities/BinningData.hpp | 35 ++-- Core/include/Acts/Utilities/BoundingBox.hpp | 4 +- Core/include/Acts/Utilities/BoundingBox.ipp | 11 +- Core/include/Acts/Utilities/Delegate.hpp | 2 +- Core/include/Acts/Utilities/Frustum.ipp | 5 +- Core/include/Acts/Utilities/HashedString.hpp | 12 +- Core/include/Acts/Utilities/KDTree.hpp | 18 +- Core/include/Acts/Utilities/Logger.hpp | 2 +- .../Acts/Utilities/SpacePointUtility.hpp | 2 +- Core/include/Acts/Utilities/detail/Axis.hpp | 4 +- .../Acts/Utilities/detail/ContextType.hpp | 2 +- Core/include/Acts/Utilities/detail/Grid.hpp | 2 +- .../detail/RealQuadraticEquation.hpp | 13 +- .../Acts/Utilities/detail/grid_helper.hpp | 43 +++-- .../Utilities/detail/interpolation_impl.hpp | 2 +- .../Vertexing/AdaptiveGridTrackDensity.ipp | 2 +- .../Vertexing/AdaptiveMultiVertexFinder.ipp | 8 +- .../Vertexing/FullBilloirVertexFitter.ipp | 2 +- .../Vertexing/GaussianGridTrackDensity.ipp | 2 +- .../Acts/Vertexing/HelicalTrackLinearizer.ipp | 4 +- .../Acts/Vertexing/ImpactPointEstimator.ipp | 2 +- .../Vertexing/KalmanVertexTrackUpdater.ipp | 2 +- Core/include/Acts/Vertexing/TrackAtVertex.hpp | 14 +- .../detail/PlyVisualization3D.ipp | 2 +- Core/src/Geometry/CylinderVolumeBounds.cpp | 2 +- Core/src/Geometry/LayerCreator.cpp | 2 +- Core/src/Geometry/ProtoLayerHelper.cpp | 2 +- Core/src/Geometry/SurfaceArrayCreator.cpp | 9 +- Core/src/Geometry/TrackingVolume.cpp | 2 +- Core/src/MagneticField/BFieldMapUtils.cpp | 8 +- Core/src/Material/AverageMaterials.cpp | 2 +- Core/src/Material/MaterialGridHelper.cpp | 20 +- Core/src/Material/MaterialMapUtils.cpp | 9 +- Core/src/Material/VolumeMaterialMapper.cpp | 7 +- Core/src/Propagator/CovarianceTransport.cpp | 10 +- .../Propagator/detail/CovarianceEngine.cpp | 16 +- Core/src/Surfaces/AnnulusBounds.cpp | 2 +- Core/src/Surfaces/LineSurface.cpp | 6 +- Core/src/Surfaces/PlaneSurface.cpp | 6 +- Core/src/Surfaces/Surface.cpp | 1 - Core/src/Utilities/Logger.cpp | 2 +- Core/src/Visualization/GeometryView3D.cpp | 6 +- .../Alignment/AlignmentAlgorithm.hpp | 2 +- .../Alignment/src/AlignmentAlgorithm.cpp | 4 +- .../src/AlignmentAlgorithmFunction.cpp | 2 +- .../Digitization/DigitizationAlgorithm.hpp | 2 +- .../Digitization/MeasurementCreation.hpp | 2 +- .../Digitization/PlanarSteppingAlgorithm.hpp | 2 +- .../ActsExamples/Digitization/Smearers.hpp | 4 +- .../src/DigitizationAlgorithm.cpp | 2 +- .../Digitization/src/ModuleClusters.cpp | 2 +- .../src/PlanarSteppingAlgorithm.cpp | 6 +- .../ActsExamples/Fatras/FatrasSimulation.hpp | 5 +- .../DDG4/DDG4DetectorConstruction.hpp | 2 +- .../Geant4/GdmlDetectorConstruction.hpp | 2 +- .../ActsExamples/Geant4/Geant4Simulation.hpp | 4 +- .../Geant4/MaterialPhysicsList.hpp | 8 +- .../Geant4/MaterialSteppingAction.hpp | 4 +- .../Geant4/SensitiveSteppingAction.hpp | 2 +- .../Geant4/SensitiveSurfaceMapper.hpp | 2 +- .../Geant4/SimParticleTranslation.hpp | 4 +- .../TelescopeG4DetectorConstruction.hpp | 2 +- .../Geant4/src/SensitiveSurfaceMapper.cpp | 4 +- .../src/TelescopeG4DetectorConstruction.cpp | 2 +- .../Geant4HepMC/EventRecording.hpp | 4 +- .../Geant4HepMC/src/EventAction.cpp | 12 +- .../Geant4HepMC/src/EventAction.hpp | 8 +- .../Geant4HepMC/src/EventRecording.cpp | 4 +- .../src/PrimaryGeneratorAction.hpp | 2 +- .../Algorithms/Geant4HepMC/src/RunAction.hpp | 6 +- .../Geant4HepMC/src/SteppingAction.hpp | 4 +- .../Generators/EventGenerator.hpp | 2 +- .../Generators/Pythia8ProcessGenerator.hpp | 2 +- .../HepMC/HepMCProcessExtractor.hpp | 4 +- .../HepMC/src/HepMCProcessExtractor.cpp | 9 +- .../MappingMaterialDecorator.hpp | 17 +- .../MaterialMapping/MaterialMapping.hpp | 4 +- .../MaterialMapping/src/MaterialMapping.cpp | 8 +- .../ActsExamples/Printers/HitsPrinter.hpp | 2 +- .../Printers/ParticlesPrinter.hpp | 2 +- .../Printers/TrackParametersPrinter.cpp | 4 +- .../Printers/TrackParametersPrinter.hpp | 2 +- .../Propagation/PropagationAlgorithm.hpp | 6 +- .../Propagation/PropagationOptions.hpp | 2 +- .../AmbiguityResolutionAlgorithm.hpp | 2 +- .../TrackFinding/SeedingAlgorithm.hpp | 4 +- .../SeedingOrthogonalAlgorithm.hpp | 2 +- .../TrackFinding/SpacePointMaker.hpp | 2 +- .../TrackFinding/TrackFindingAlgorithm.hpp | 2 +- .../TrackParamsEstimationAlgorithm.hpp | 2 +- .../TrajectoriesToPrototracks.hpp | 5 +- .../TrackFinding/src/SpacePointMaker.cpp | 6 +- .../src/TrackFindingAlgorithmFunction.cpp | 2 +- .../src/TrackParamsEstimationAlgorithm.cpp | 2 +- .../TrackFindingAlgorithmExaTrkX.hpp | 2 +- .../TrackFitting/TrackFittingAlgorithm.hpp | 2 +- .../TrackFitting/src/GsfFitterFunction.cpp | 10 +- .../TrackFitting/src/KalmanFitterFunction.cpp | 8 +- .../TrackFittingChi2Algorithm.hpp | 2 +- .../src/TrackFittingChi2AlgorithmFunction.cpp | 2 +- .../TruthTracking/ParticleSmearing.hpp | 2 +- .../TruthTracking/TruthSeedSelector.hpp | 2 +- .../TruthTracking/TruthTrackFinder.hpp | 2 +- .../TruthTracking/TruthVertexFinder.cpp | 2 +- .../ExternalAlignmentDecorator.hpp | 6 +- .../ExternallyAlignedDetectorElement.hpp | 4 +- .../InternalAlignmentDecorator.hpp | 6 +- .../InternallyAlignedDetectorElement.hpp | 2 +- .../DD4hepDetector/DD4hepDetector.hpp | 4 +- .../DD4hepDetector/DD4hepGeometryService.hpp | 2 +- .../DD4hepDetector/src/DD4hepDetector.cpp | 2 +- .../GenericDetector/BuildGenericDetector.hpp | 2 +- .../GenericDetectorElement.hpp | 12 +- .../GenericDetector/LayerBuilderT.hpp | 14 +- .../GenericDetector/ProtoLayerCreatorT.hpp | 33 ++-- .../src/GenericDetectorElement.cpp | 8 +- .../MagneticField/FieldMapRootIo.hpp | 18 +- .../MagneticField/FieldMapTextIo.hpp | 16 +- .../MagneticField/ScalableBFieldService.hpp | 4 +- .../MagneticField/src/FieldMapRootIo.cpp | 26 +-- .../MagneticField/src/FieldMapTextIo.cpp | 12 +- .../TGeoDetector/TGeoDetector.hpp | 10 +- .../TGeoDetector/TGeoITkModuleSplitter.hpp | 13 +- .../TGeoDetector/src/TGeoDetector.cpp | 16 +- .../src/TGeoITkModuleSplitter.cpp | 6 +- .../TelescopeDetector/TelescopeDetector.hpp | 3 +- .../TelescopeDetectorElement.hpp | 2 +- .../src/BuildTelescopeDetector.cpp | 5 +- .../src/TelescopeDetector.cpp | 8 +- .../src/TelescopeDetectorElement.cpp | 4 +- Examples/Framework/CMakeLists.txt | 7 +- .../EventData/IndexSourceLink.hpp | 2 +- .../ActsExamples/EventData/SimSpacePoint.hpp | 12 +- .../ActsExamples/Framework/BareAlgorithm.hpp | 5 +- .../ActsExamples/Framework/BareService.hpp | 2 +- .../ActsExamples/Framework/WhiteBoard.hpp | 2 +- .../ActsExamples/Framework/WriterT.hpp | 4 +- .../ActsExamples/Utilities/Helpers.hpp | 10 +- .../Validation/TrackSummaryPlotTool.hpp | 2 +- .../Framework/src/Framework/BareService.cpp | 2 +- .../Framework/src/Framework/Sequencer.cpp | 7 +- Examples/Io/Csv/CMakeLists.txt | 7 +- .../ActsExamples/Io/Csv/CsvBFieldWriter.hpp | 4 +- .../Io/Csv/CsvMeasurementReader.hpp | 6 +- .../Io/Csv/CsvMeasurementWriter.hpp | 6 +- .../Io/Csv/CsvMultiTrajectoryWriter.hpp | 10 +- .../ActsExamples/Io/Csv/CsvParticleReader.hpp | 6 +- .../ActsExamples/Io/Csv/CsvParticleWriter.hpp | 2 +- .../Io/Csv/CsvPlanarClusterReader.hpp | 6 +- .../Io/Csv/CsvPlanarClusterWriter.hpp | 6 +- .../ActsExamples/Io/Csv/CsvSimHitReader.hpp | 6 +- .../ActsExamples/Io/Csv/CsvSimHitWriter.hpp | 2 +- .../Io/Csv/CsvSpacePointReader.hpp | 8 +- .../Io/Csv/CsvSpacepointWriter.hpp | 6 +- .../Io/Csv/CsvTrackingGeometryWriter.hpp | 8 +- Examples/Io/Csv/src/CsvBFieldWriter.cpp | 2 +- Examples/Io/Csv/src/CsvOutputData.hpp | 85 ++++---- .../Io/Csv/src/CsvPlanarClusterReader.cpp | 4 +- Examples/Io/Csv/src/CsvSpacePointWriter.cpp | 2 +- .../Io/Csv/src/CsvTrackingGeometryWriter.cpp | 7 +- .../ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp | 18 +- .../src/EDM4hepMultiTrajectoryWriter.cpp | 5 +- .../Io/EDM4hep/src/EDM4hepParticleReader.cpp | 2 +- .../Io/EDM4hep/src/EDM4hepParticleWriter.cpp | 12 +- .../Io/EDM4hep/src/EDM4hepSimHitReader.cpp | 6 +- Examples/Io/EDM4hep/src/EDM4hepUtil.cpp | 18 +- .../ActsExamples/Io/HepMC3/HepMC3Event.hpp | 4 +- .../ActsExamples/Io/HepMC3/HepMC3Particle.hpp | 28 +-- .../ActsExamples/Io/HepMC3/HepMC3Reader.hpp | 6 +- .../ActsExamples/Io/HepMC3/HepMC3Vertex.hpp | 36 ++-- .../ActsExamples/Io/HepMC3/HepMC3Writer.hpp | 5 +- Examples/Io/HepMC3/src/HepMC3Event.cpp | 12 +- Examples/Io/HepMC3/src/HepMC3Particle.cpp | 27 +-- Examples/Io/HepMC3/src/HepMC3Vertex.cpp | 39 ++-- .../Io/Json/JsonMaterialWriter.hpp | 2 +- .../Io/Json/JsonSpacePointWriter.hpp | 4 +- .../Io/Json/JsonSurfacesWriter.hpp | 8 +- Examples/Io/Json/src/JsonSurfacesWriter.cpp | 9 +- ...RootNuclearInteractionParametersWriter.hpp | 9 +- ...RootNuclearInteractionParametersWriter.cpp | 5 +- .../Plugins/Obj/ObjPropagationStepsWriter.hpp | 9 +- .../Io/Performance/CKFPerformanceWriter.hpp | 8 +- .../Performance/SeedingPerformanceWriter.hpp | 4 +- .../TrackFinderPerformanceWriter.cpp | 32 +-- .../TrackFinderPerformanceWriter.hpp | 6 +- .../TrackFitterPerformanceWriter.hpp | 8 +- .../Io/Root/RootMaterialDecorator.hpp | 2 +- .../Io/Root/RootMaterialTrackReader.hpp | 31 ++- .../Io/Root/RootMaterialTrackWriter.hpp | 32 +-- .../Io/Root/RootMaterialWriter.hpp | 2 +- .../Io/Root/RootMeasurementWriter.hpp | 22 +-- .../Io/Root/RootParticleReader.hpp | 11 +- .../Io/Root/RootParticleWriter.hpp | 8 +- .../Io/Root/RootPlanarClusterWriter.hpp | 32 +-- .../Io/Root/RootPropagationStepsWriter.hpp | 15 +- .../ActsExamples/Io/Root/RootSimHitWriter.hpp | 32 +-- .../Io/Root/RootSpacepointWriter.hpp | 6 +- .../Io/Root/RootTrackParameterWriter.hpp | 7 +- .../Io/Root/RootTrajectoryStatesWriter.hpp | 10 +- .../Io/Root/RootTrajectorySummaryReader.hpp | 9 +- .../Io/Root/RootTrajectorySummaryWriter.hpp | 6 +- .../Io/Root/RootVertexPerformanceWriter.hpp | 6 +- Examples/Io/Root/src/RootBFieldWriter.cpp | 20 +- .../Io/Root/src/RootMaterialDecorator.cpp | 3 +- .../Io/Root/src/RootMaterialTrackReader.cpp | 6 +- Examples/Io/Root/src/RootParticleReader.cpp | 4 +- .../Root/src/RootTrajectorySummaryReader.cpp | 4 +- .../ActsExamples/Io/Svg/SvgPointWriter.hpp | 2 +- Examples/Python/src/Base.cpp | 1 + Examples/Python/src/Detector.cpp | 7 +- Examples/Python/src/DigitizationStub.cpp | 2 +- Examples/Python/src/EDM4hepStub.cpp | 2 +- .../Python/src/ExaTrkXTrackFindingStub.cpp | 2 +- Examples/Python/src/Geant4Component.cpp | 7 +- Examples/Python/src/Geant4HepMC3Stub.cpp | 2 +- Examples/Python/src/Generators.cpp | 4 +- Examples/Python/src/HepMC3Stub.cpp | 2 +- Examples/Python/src/MagneticField.cpp | 8 +- Examples/Python/src/Pythia8Stub.cpp | 2 +- Examples/Python/src/TrackFinding.cpp | 5 +- .../Alignment/Common/DetectorAlignment.cpp | 13 +- .../Alignment/Common/DetectorAlignment.hpp | 5 +- Examples/Run/Common/CMakeLists.txt | 9 +- .../ActsExamples/Geometry/MaterialWiper.hpp | 4 +- .../ActsExamples/Options/CommonOptions.hpp | 2 +- .../Reconstruction/ReconstructionBase.hpp | 8 +- .../Run/Common/src/CommonMaterialMapping.cpp | 2 +- Examples/Run/Common/src/CommonOptions.cpp | 2 +- Examples/Run/Common/src/CommonSimulation.cpp | 2 +- .../Run/Common/src/MagneticFieldOptions.cpp | 2 +- .../Run/Common/src/MaterialValidationBase.cpp | 10 +- .../Common/src/NuclearInteractionOptions.cpp | 6 +- .../Run/Common/src/ParticleGunOptions.cpp | 2 +- .../Run/Common/src/PropagationExampleBase.cpp | 2 +- .../Run/Common/src/ReconstructionBase.cpp | 6 +- .../Common/DigitizationConfigExample.cpp | 2 +- .../Common/DigitizationConfigExample.hpp | 2 +- .../Common/DigitizationExample.cpp | 4 +- .../Common/DigitizationExample.hpp | 2 +- .../ActsExamples/Fatras/FatrasCommon.hpp | 2 +- .../Run/Fatras/Common/src/FatrasCommon.cpp | 8 +- .../ActsExamples/Geant4/Geant4Common.hpp | 8 +- .../Run/Geant4/Common/src/Geant4Common.cpp | 16 +- .../Run/HelloWorld/HelloLoggerAlgorithm.hpp | 3 +- .../Run/HelloWorld/HelloRandomAlgorithm.cpp | 2 +- .../Run/HelloWorld/HelloRandomAlgorithm.hpp | 5 +- Examples/Run/HelloWorld/HelloService.hpp | 4 +- .../HelloWorld/HelloWhiteBoardAlgorithm.hpp | 3 +- .../Common/MeasurementsToSpacepoints.cpp | 7 +- .../Common/MeasurementsToSpacepoints.hpp | 5 +- .../Reconstruction/Common/RecCKFTracks.cpp | 7 +- .../Reconstruction/Common/RecCKFTracks.hpp | 5 +- .../Reconstruction/Common/RecChi2Tracks.cpp | 7 +- .../Reconstruction/Common/RecChi2Tracks.hpp | 5 +- .../Reconstruction/Common/RecTruthTracks.cpp | 7 +- .../Reconstruction/Common/RecTruthTracks.hpp | 5 +- .../Reconstruction/Common/SeedingExample.cpp | 7 +- .../Reconstruction/Common/SeedingExample.hpp | 5 +- Examples/Run/Show/ShowFatrasGeneric.cpp | 2 +- .../MaterialMapping/materialComposition.C | 7 +- .../Scripts/TrackingPerformance/CommonUtils.h | 26 +-- .../Scripts/TrackingPerformance/TreeReader.h | 10 +- .../boundParamResolution.C | 35 ++-- Examples/Scripts/compareRootFiles.hpp | 15 +- .../Geant4/DummyDetectorConstruction.hpp | 6 +- .../include/ActsFatras/Geant4/Geant4Decay.hpp | 10 +- .../ActsFatras/Digitization/Channelizer.hpp | 6 +- .../Digitization/DigitizationData.hpp | 2 +- .../Digitization/PlanarSurfaceMask.hpp | 2 +- .../Digitization/UncorrelatedHitSmearer.hpp | 6 +- .../ActsFatras/Kernel/InteractionList.hpp | 23 ++- .../include/ActsFatras/Kernel/Simulation.hpp | 2 +- .../Kernel/detail/SimulationActor.hpp | 6 +- .../ElectroMagnetic/PhotonConversion.hpp | 11 +- .../ElectroMagnetic/detail/GeneralMixture.hpp | 25 +-- .../NuclearInteraction/NuclearInteraction.hpp | 35 ++-- .../NuclearInteractionParameters.hpp | 10 +- .../Selectors/ParticleSelectors.hpp | 2 +- .../ActsFatras/Selectors/SurfaceSelectors.hpp | 8 +- .../Selectors/detail/combine_selectors.hpp | 2 +- Fatras/src/Physics/StandardInteractions.cpp | 12 +- Fatras/src/Utilities/LandauDistribution.cpp | 2 +- .../Autodiff/AutodiffExtensionWrapper.hpp | 2 +- .../Cuda/Seeding2/TripletFilterConfig.hpp | 12 +- .../Acts/Plugins/Cuda/Utilities/CpuMatrix.hpp | 12 +- .../Acts/Plugins/Cuda/Utilities/CpuScalar.hpp | 12 +- .../Acts/Plugins/Cuda/Utilities/CpuVector.hpp | 12 +- .../Plugins/DD4hep/DD4hepDetectorElement.hpp | 3 +- Plugins/DD4hep/src/DD4hepDetectorElement.cpp | 3 +- Plugins/DD4hep/src/DD4hepVolumeBuilder.cpp | 2 +- .../Plugins/ExaTrkX/ExaTrkXTrackFinding.hpp | 77 ++++++++ Plugins/Json/CMakeLists.txt | 6 +- .../GeometryHierarchyMapJsonConverter.hpp | 12 +- .../Plugins/Json/MaterialJsonConverter.hpp | 8 +- .../Json/SurfaceBoundsJsonConverter.hpp | 2 +- .../Plugins/Json/SurfaceJsonConverter.hpp | 4 +- .../Json/VolumeBoundsJsonConverter.hpp | 2 +- Plugins/Json/src/AlgebraJsonConverter.cpp | 6 +- Plugins/Json/src/MaterialMapJsonConverter.cpp | 7 +- Plugins/Json/src/SurfaceJsonConverter.cpp | 7 +- .../include/Acts/Seeding/AtlasSeedFinder.hpp | 182 +++++++++--------- .../include/Acts/Seeding/AtlasSeedFinder.ipp | 70 +++---- .../Acts/Seeding/LegacyInternalSeed.hpp | 34 ++-- .../include/Acts/Seeding/LegacySeed.hpp | 17 +- .../Legacy/include/Acts/Seeding/SPForSeed.hpp | 40 ++-- Plugins/Sycl/src/Utilities/Arrays.hpp | 2 +- .../Plugins/TGeo/TGeoCylinderDiscSplitter.hpp | 6 +- .../Acts/Plugins/TGeo/TGeoDetectorElement.hpp | 4 +- Plugins/TGeo/src/TGeoDetectorElement.cpp | 8 +- Plugins/TGeo/src/TGeoLayerBuilder.cpp | 8 +- Tests/Benchmarks/AnnulusBoundsBenchmark.cpp | 4 +- Tests/Benchmarks/BoundaryCheckBenchmark.cpp | 4 +- .../Tests/CommonHelpers/BenchmarkTools.hpp | 7 +- .../CommonHelpers/CubicTrackingGeometry.hpp | 10 +- .../CommonHelpers/DetectorElementStub.hpp | 17 +- .../Tests/CommonHelpers/LineSurfaceStub.hpp | 10 +- .../Tests/CommonHelpers/TestSpacePoint.hpp | 14 +- .../Fatras/FatrasSimulationTests.cpp | 2 +- .../Legacy/ATLSeedingIntegrationTest.cpp | 12 +- .../Alignment/Kernel/AlignmentTests.cpp | 11 +- .../EventData/BoundTrackParametersTests.cpp | 4 +- .../Core/Geometry/AlignmentContextTests.cpp | 2 +- .../Core/Geometry/BVHDataTestCase.hpp | 2 +- .../Core/Geometry/CuboidVolumeBoundsTests.cpp | 2 +- .../Geometry/CuboidVolumeBuilderTests.cpp | 3 +- .../CutoutCylinderVolumeBoundsTests.cpp | 2 +- .../Geometry/CylinderVolumeBoundsTests.cpp | 2 +- Tests/UnitTests/Core/Geometry/LayerStub.hpp | 2 +- .../Geometry/SurfaceArrayCreatorTests.cpp | 2 +- .../Geometry/TrackingGeometryClosureTests.cpp | 18 +- .../TrackingGeometryGeometryIdTests.cpp | 26 +-- .../MagneticField/SolenoidBFieldTests.cpp | 2 +- .../Core/Propagator/AbortListTests.cpp | 32 +-- .../Core/Propagator/NavigatorTests.cpp | 4 +- .../Propagator/StraightLineStepperTests.cpp | 2 +- .../VolumeMaterialInteractionTests.cpp | 12 +- Tests/UnitTests/Core/Seeding/ATLASCuts.hpp | 17 +- .../EstimateTrackParamsFromSeedTest.cpp | 2 +- .../UnitTests/Core/Seeding/SeedFinderTest.cpp | 8 +- .../SpacePointBuilderTests.cpp | 10 +- .../Core/Surfaces/AnnulusBoundsTests.cpp | 2 +- .../Core/Surfaces/ConeBoundsTests.cpp | 2 +- .../Surfaces/ConvexPolygonBoundsTests.cpp | 2 +- .../Core/Surfaces/CylinderBoundsTests.cpp | 2 +- .../Surfaces/DiscTrapezoidBoundsTests.cpp | 2 +- .../Core/Surfaces/EllipseBoundsTests.cpp | 2 +- .../Core/Surfaces/LineBoundsTests.cpp | 2 +- .../Core/Surfaces/RadialBoundsTests.cpp | 2 +- .../Core/Surfaces/RectangleBoundsTests.cpp | 2 +- Tests/UnitTests/Core/Surfaces/SurfaceStub.hpp | 6 +- .../Core/Surfaces/TrapezoidBoundsTests.cpp | 2 +- .../CombinatorialKalmanFilterTests.cpp | 6 +- .../Core/TrackFitting/Chi2FitterTests.cpp | 4 +- .../Core/TrackFitting/FitterTestsCommon.hpp | 6 +- .../TrackFitting/GsfComponentMergingTests.cpp | 2 +- .../Core/Utilities/BoundingBoxTest.cpp | 2 +- .../Core/Utilities/DelegateTests.cpp | 4 +- .../Core/Utilities/ExtendableTests.cpp | 2 +- .../Utilities/FiniteStateMachineTests.cpp | 68 ++++--- .../Core/Utilities/SubspaceTests.cpp | 2 +- .../Core/Utilities/TypeTraitsTest.cpp | 20 +- .../AdaptiveMultiVertexFinderTests.cpp | 6 +- .../FullBilloirVertexFitterTests.cpp | 2 +- .../Vertexing/IterativeVertexFinderTests.cpp | 2 +- .../TrackDensityVertexFinderTests.cpp | 2 +- .../Core/Vertexing/VertexingDataHelper.hpp | 12 +- .../Core/Vertexing/ZScanVertexFinderTests.cpp | 2 +- .../Visualization/EventDataView3DBase.hpp | 8 +- .../Visualization/EventDataView3DTests.cpp | 8 +- .../Visualization/PrimitivesView3DTests.cpp | 4 +- .../Core/Visualization/SurfaceView3DTests.cpp | 8 +- .../TrackingGeometryView3DTests.cpp | 8 +- .../Visualization/Visualization3DTests.cpp | 10 +- .../Core/Visualization/VolumeView3DTests.cpp | 8 +- .../Fatras/Kernel/ContinuousProcessTests.cpp | 10 +- .../Fatras/Kernel/InteractionListTests.cpp | 22 ++- .../Fatras/Kernel/SimulationActorTests.cpp | 24 ++- .../Plugins/Cuda/Seeding2/TestHostCuts.hpp | 17 +- cmake/ActsStaticAnalysis.cmake | 47 +---- cmake/ActsTargetLinkLibrariesSystem.cmake | 31 +++ 446 files changed, 1989 insertions(+), 1799 deletions(-) create mode 100644 Plugins/ExaTrkX/include/Acts/Plugins/ExaTrkX/ExaTrkXTrackFinding.hpp create mode 100644 cmake/ActsTargetLinkLibrariesSystem.cmake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6fb1b13e0f4..e0fd7d0b0e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -12,10 +12,13 @@ clang_tidy: - git checkout $HEAD_SHA - > apt-get update - && apt-get install -y clang-10 clang-tidy-10 g++-8 libstdc++-8-dev - && ln -s /usr/bin/clang++-10 /usr/bin/clang++ - && ln -s /usr/bin/clang-10 /usr/bin/clang - && ln -s /usr/bin/clang-tidy-10 /usr/bin/clang-tidy + && apt-get install -y g++-8 libstdc++-8-dev parallel software-properties-common + && curl -L https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add + && add-apt-repository 'deb http://apt.llvm.org/focal/ llvm-toolchain-focal main' + && apt-get install -y clang-16 clang-tidy-16 + && ln -s /usr/bin/clang++-16 /usr/bin/clang++ + && ln -s /usr/bin/clang-16 /usr/bin/clang + && ln -s /usr/bin/clang-tidy-16 /usr/bin/clang-tidy && mkdir -p /opt/lib/gcc/x86_64-linux-gnu && ln -s /usr/lib/gcc/x86_64-linux-gnu/8/ /opt/lib/gcc/x86_64-linux-gnu/ && clang++ --gcc-toolchain=/opt -v @@ -30,10 +33,24 @@ clang_tidy: -DACTS_RUN_CLANG_TIDY=on - mkdir clang-tidy - - CI/clang_tidy/run_clang_tidy.sh build > clang-tidy/clang-tidy.log + + # Main clang-tidy run during cmake compilation + - CI/clang_tidy/run_clang_tidy.sh build | tee clang-tidy/clang-tidy.log + + # Install dependencies for processing scripts - pip install -r CI/clang_tidy/requirements.txt - - CI/clang_tidy/parse_clang_tidy.py clang-tidy/clang-tidy.log clang-tidy/clang-tidy.json --exclude "*thirdparty*" + + # Parse the main clang-tidy run + - > + CI/clang_tidy/parse_clang_tidy.py + clang-tidy/clang-tidy.log + clang-tidy/clang-tidy.json + --exclude "*thirdparty*" + + # Check the combined report against the defined limits - CI/clang_tidy/check_clang_tidy.py --report clang-tidy/clang-tidy.json --config CI/clang_tidy/limits.yml + + # Generate an html report - codereport clang-tidy/clang-tidy.json clang-tidy/html build: diff --git a/Alignment/include/ActsAlignment/Kernel/Alignment.ipp b/Alignment/include/ActsAlignment/Kernel/Alignment.ipp index 05b52189de1..bb36ee02907 100644 --- a/Alignment/include/ActsAlignment/Kernel/Alignment.ipp +++ b/Alignment/include/ActsAlignment/Kernel/Alignment.ipp @@ -301,7 +301,6 @@ ActsAlignment::Alignment::align( // Screen out the final aligned parameters // @todo if (alignmentParametersUpdated) { - unsigned int iDetElement = 0; for (const auto& det : alignOptions.alignedDetElements) { const auto& surface = &det->surface(); const auto& transform = @@ -318,7 +317,6 @@ ActsAlignment::Alignment::align( ACTS_VERBOSE( "Euler angles (rotZ, rotY, rotX) = " << rotAngles.transpose()); ACTS_VERBOSE("Rotation marix = \n" << rotation); - iDetElement++; } } else { ACTS_DEBUG("Alignment parameters is not updated."); diff --git a/Alignment/include/ActsAlignment/Kernel/detail/AlignmentEngine.hpp b/Alignment/include/ActsAlignment/Kernel/detail/AlignmentEngine.hpp index cfdca4cac41..7f441858fc1 100644 --- a/Alignment/include/ActsAlignment/Kernel/detail/AlignmentEngine.hpp +++ b/Alignment/include/ActsAlignment/Kernel/detail/AlignmentEngine.hpp @@ -118,7 +118,8 @@ TrackAlignmentState trackAlignmentState( std::vector> measurementStates; measurementStates.reserve(15); // Number of smoothed states on the track - size_t nSmoothedStates = 0; + // size_t nSmoothedStates = 0; // commented because clang-tidy complains about + // unused // Number of alignable surfaces on the track size_t nAlignSurfaces = 0; @@ -126,7 +127,8 @@ TrackAlignmentState trackAlignmentState( multiTraj.visitBackwards(entryIndex, [&](const auto& ts) { // Remember the number of smoothed states if (ts.hasSmoothed()) { - nSmoothedStates++; + // nSmoothedStates++; // commented because clang-tidy complains about + // unused } else { // @note: this should in principle never happen now. But still keep it as a note return true; diff --git a/CI/clang_tidy/limits.yml b/CI/clang_tidy/limits.yml index d7bbf2e532d..32280f036eb 100644 --- a/CI/clang_tidy/limits.yml +++ b/CI/clang_tidy/limits.yml @@ -1,15 +1,19 @@ limits: - # "readability-inconsistent-declaration-parameter-name": 0 - # "readability-named-parameter": 0 + "readability-inconsistent-declaration-parameter-name": 0 + "readability-named-parameter": 0 "readability-container-size-empty": 0 - "modernize-use-using": 0 "readability-braces-around-statements": 0 - "modernize-use-override": 0 - "modernize-use-equals-default" : 0 "readability-implicit-bool-cast": 0 "readability-implicit-bool-conversion": 0 - # "modernize-use-default-member-init": 0 - # "performance-unnecessary-value-param": 0 - # "modernize-use-equals-default": 0 + "modernize-use-using": 0 + "modernize-use-override": 0 + "modernize-use-equals-default" : 0 + "modernize-use-default-member-init": 0 "modernize-use-nullptr": 0 - "performance-move-const-arg": 0 \ No newline at end of file + "performance-unnecessary-value-param": 0 + "performance-move-const-arg": 0 + "performance-for-range-copy": 0 + "cppcoreguidelines-pro-type-member-init": 0 + "cppcoreguidelines-init-variables": 0 + "clang-analyzer-optin.cplusplus.UninitializedObject": 0 + "clang-diagnostic-error": 0 diff --git a/CI/clang_tidy/parse_clang_tidy.py b/CI/clang_tidy/parse_clang_tidy.py index 1019332b9e0..9e0b0669d60 100755 --- a/CI/clang_tidy/parse_clang_tidy.py +++ b/CI/clang_tidy/parse_clang_tidy.py @@ -24,7 +24,7 @@ def parse_clang_tidy_item(itemstr): try: m = re.match( - r"(?P[/.\-+\w]+):(?P\d+):(?P\d+): (?P.*?):(?P[\s\S]*?)\[(?P.*)\]\n(?P[\s\S]*)", + r"(?P[/.\-+\w]+):(?P\d+):(?P\d+): (?P.*?):(?P[\s\S]*)\[(?P.*)\]\n(?P[\s\S]*)", itemstr, ) @@ -35,7 +35,7 @@ def parse_clang_tidy_item(itemstr): line=int(m.group("line")), col=int(m.group("col")), # message=m.group("msg").strip(), - message="\n".join(lines[1:]), + message=m.group("msg").strip() + "\n" + "\n".join(lines[1:]), code=m.group("code"), severity=m.group("sev"), ) @@ -101,6 +101,12 @@ def main(): default=[], help="Only include files that match any of these patterns", ) + p.add_argument( + "--ignore", + action="append", + default=[], + help="Ignore items with codes matching any of these patterns", + ) p.add_argument("--cwd", type=Path) p.add_argument("--strip-common", action="store_true") @@ -116,6 +122,9 @@ def select(item): accept = accept and all(fnmatch(item.path, e) for e in args.filter) accept = accept and not any(fnmatch(item.path, e) for e in args.exclude) + + accept = accept and not any(fnmatch(item.code, i) for i in args.ignore) + return accept items = list(filter(select, items)) diff --git a/CI/clang_tidy/requirements.txt b/CI/clang_tidy/requirements.txt index 3bdba2b7511..6b961485b81 100644 --- a/CI/clang_tidy/requirements.txt +++ b/CI/clang_tidy/requirements.txt @@ -6,7 +6,7 @@ # appdirs==1.4.4 # via fs -codereport==0.3.0 +codereport==0.3.2 # via -r CI/clang_tidy/requirements.in commonmark==0.9.1 # via rich diff --git a/CI/clang_tidy/run_clang_tidy.sh b/CI/clang_tidy/run_clang_tidy.sh index 2fb696199a6..b30d712ebf5 100755 --- a/CI/clang_tidy/run_clang_tidy.sh +++ b/CI/clang_tidy/run_clang_tidy.sh @@ -6,4 +6,3 @@ export NINJA_STATUS="[ninja][%f/%t] " pushd $build_dir ninja | grep -v '\[ninja\]' popd - diff --git a/Core/include/Acts/Clusterization/Clusterization.ipp b/Core/include/Acts/Clusterization/Clusterization.ipp index d2ad50b5dc7..62f28d07a5c 100644 --- a/Core/include/Acts/Clusterization/Clusterization.ipp +++ b/Core/include/Acts/Clusterization/Clusterization.ipp @@ -74,8 +74,7 @@ struct Compare { class DisjointSets { public: DisjointSets(size_t initial_size = 128) - : m_globalId(1), - m_size(initial_size), + : m_size(initial_size), m_rank(m_size), m_parent(m_size), m_ds(&m_rank[0], &m_parent[0]) {} @@ -97,7 +96,7 @@ class DisjointSets { Label findSet(size_t x) { return static_cast