From 5319fc3f9226801ce8791cd636f3e34c87d2c7f5 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Mon, 22 Dec 2025 14:26:40 +0100 Subject: [PATCH 01/72] Add a simple runtime test --- test/CMakeLists.txt | 38 ++++++ test/options/MAIA_CKF_Tracking.py | 185 ++++++++++++++++++++++++++++++ 2 files changed, 223 insertions(+) create mode 100644 test/options/MAIA_CKF_Tracking.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6898adc9..b7f32ba1 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -55,3 +55,41 @@ add_geometry_load_test($ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD add_geometry_load_test($ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v02/ILD_FCCee_v02.xml) add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v07/CLD_o2_v07.xml) add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml) + + +add_test(NAME sim_Maia + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ddsim --compactFile=$ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml + --runType=batch -G -N=1 + --outputFile=particle_gun_MAIA_SIM.edm4hep.root + --gun.multiplicity 50 + --gun.particle "mu-" + --gun.distribution uniform + --gun.thetaMax 2.8 # rougly 160 degrees + --gun.thetaMin -2.8 # rougly -160 degrees + --gun.momentumMin 5 + --gun.momentumMax 500 +) + +add_test(NAME reco_MAIA + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKF_Tracking.py + --compactFile=$ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml + --trackingGeoFile=${CMAKE_CURRENT_LIST_DIR}/../data/MAIA_v0.root + --geoDescFile=${CMAKE_CURRENT_LIST_DIR}/../data/MAIA_v0.json + --materialFile=${CMAKE_CURRENT_LIST_DIR}/../data/MAIA_v0_material.json +) +set_test_env(reco_MAIA) + +# Setup test dependencies such that they are run in the right order always +set_tests_properties( + sim_Maia + PROPERTIES + FIXTURES_SETUP sim_maia_fixture +) + +set_tests_properties( + reco_MAIA + PROPERTIES + FIXTURES_REQUIRED sim_maia_fixture +) diff --git a/test/options/MAIA_CKF_Tracking.py b/test/options/MAIA_CKF_Tracking.py new file mode 100644 index 00000000..4daa0d41 --- /dev/null +++ b/test/options/MAIA_CKF_Tracking.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 + +import os + +from Gaudi.Configuration import INFO, VERBOSE +from Gaudi.Configurables import ( + ACTSSeededCKFTrackingAlg, + CollectionMerger, + DDPlanarDigi, + EventDataSvc, + GeoSvc, +) + +from k4FWCore import ApplicationMgr, IOSvc +from k4FWCore.parseArgs import parser + +parser.add_argument( + "--compactFile", + help="The geometry compact file to use for reconstruction", + type=str, +) +parser.add_argument( + "--trackingGeoFile", + help="The TGeo file containing the tracking geometry", + type=str, +) +parser.add_argument( + "--materialFile", + help="The file containing the material mapping for the tracking geometry", + type=str, +) +parser.add_argument( + "--geoDescFile", help="The JSON file describing the subdetectors", type=str +) + +args = parser.parse_known_args()[0] + + +svcList = [ + GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), + EventDataSvc("EventDataSvc"), +] + +iosvc = IOSvc( + "IOSvc", + Input=["particle_gun_MAIA_SIM.edm4hep.root"], + Output="maia_ckf_tracking_reco.edm4hep.root", +) + + +algList = [] + + +for name in ("VertexBarrel", "VertexEndcap"): + algList.append( + DDPlanarDigi( + f"{name}Digitizer", + CorrectTimesForPropagation=True, + IsStrip=False, + ResolutionT=[0.03], + ResolutionU=[0.005], + ResolutionV=[0.005], + SubDetectorName="Vertex", + TimeWindowMax=[0.15], + TimeWindowMin=[-0.09], + UseTimeWindow=True, + SimTrackHitCollectionName=[f"{name}Collection"], + SimTrkHitRelCollection=[f"{name}HitsRelations"], + TrackerHitCollectionName=[f"{name}Hits"], + ) + ) + +for name in ( + "InnerTrackerBarrel", + "InnerTrackerEndcap", + "OuterTrackerBarrel", + "OuterTrackerEndcap", +): + algList.append( + DDPlanarDigi( + f"{name}Digitizer", + CorrectTimesForPropagation=True, + IsStrip="InnerBarrel" in name or "OuterEndcap" in name, # Is this true? + ResolutionT=[0.06], + ResolutionU=[0.007], + ResolutionV=[0.09], + SubDetectorName=name.replace("Barrel", "s").replace("Endcap", "s"), + TimeWindowMax=[0.3], + TimeWindowMin=[-0.18], + UseTimeWindow=True, + SimTrackHitCollectionName=[f"{name}Collection"], + SimTrkHitRelCollection=[f"{name}HitsRelations"], + TrackerHitCollectionName=[f"{name}Hits"], + ) + ) + + +algList.append( + CollectionMerger( + "MergeHits", + InputCollections=[ + "VertexBarrelHits", + "VertexEndcapHits", + "InnerTrackerBarrelHits", + "InnerTrackerEndcapHits", + "OuterTrackerBarrelHits", + "OuterTrackerEndcapHits", + ], + OutputCollection=["MergedTrackerHits"], + ) +) + +# algList.append( +# CollectionMerger( +# "MergeHitsRelations", +# InputCollections=[ +# "VertexBarrelHitsRelations", +# "VertexEndcapHitsRelations", +# "InnerTrackerBarrelHitsRelations", +# "InnerTrackerEndcapHitsRelations", +# "OuterTrackerBarrelHitsRelations", +# "OuterTrackerEndcapHitsRelations", +# ], +# OutputCollection=["MergedTrackerHitsRelations"], +# ) +# ) + +ckf_tracking = ACTSSeededCKFTrackingAlg( + "CKFTracking", + MatFile=args.materialFile, + TGeoFile=args.trackingGeoFile, + TGeoDescFile=args.geoDescFile, + DetectorSchema=args.compactFile, + RunCKF="True", + CKF_Chi2CutOff=10, + SeedFinding_RMax=150, + SeedFinding_MinPt=500, + SeedFinding_ImpactMax=3, + CKF_NumMeasurementsCutOff=1, + SeedFinding_SigmaScattering=50, + SeedFinding_CollisionRegion=6, + SeedFinding_RadLengthPerSeed=0.1, + SeedingLayers=[ + "13", + "2", + "13", + "6", + "13", + "10", + "13", + "14", + "14", + "2", + "14", + "6", + "14", + "10", + "14", + "14", + "15", + "2", + "15", + "6", + "15", + "10", + "15", + "14", + "8", + "2", + "17", + "2", + "18", + "2", + ], + OutputTrackCollectionName=["AllTracks"], + OutputSeedCollectionName=["SeedTracks"], + InputTrackerHitCollectionName=["MergedTrackerHits"], + OutputLevel=VERBOSE, +) +algList.append(ckf_tracking) + + +ApplicationMgr( + TopAlg=algList, ExtSvc=svcList, OutputLevel=INFO, EvtSel="NONE", EvtMax=-1 +) From 5df80cdbe3708051e7bc1ce264e95b48a4a5f074 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Mon, 22 Dec 2025 14:52:50 +0100 Subject: [PATCH 02/72] Guard ODD tests via environment variable enable tests for mucoll image --- .github/workflows/mucoll-ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/mucoll-ci.yml b/.github/workflows/mucoll-ci.yml index c33ad962..f11c5d83 100644 --- a/.github/workflows/mucoll-ci.yml +++ b/.github/workflows/mucoll-ci.yml @@ -59,8 +59,7 @@ jobs: -DCMAKE_INSTALL_PREFIX=$(pwd)/install \ -DCMAKE_CXX_FLAGS=" -fdiagnostics-color=always -Werror -Wno-error=deprecated-declarations" cmake --build build - # Tests need OpenDataDetector, which is not present in the mucoll image atm - # ctest --test-dir build -j$(nproc) --output-on-failure + ctest --test-dir build -j$(nproc) --output-on-failure cmake --build build --target install ccache -s From f027dcc8a3a7f4520ec153c2fff1ce4269bb3612 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Mon, 22 Dec 2025 15:42:14 +0100 Subject: [PATCH 03/72] Use more events for test to make sure failures are detected It looks like using only one event and EvtMax=-1 hides failures somehow --- test/CMakeLists.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b7f32ba1..b784295c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -57,16 +57,16 @@ add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v07/CLD_o2_v07.x add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml) -add_test(NAME sim_Maia +add_test(NAME sim_MAIA WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND ddsim --compactFile=$ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml - --runType=batch -G -N=1 + --runType=batch -G -N=10 --outputFile=particle_gun_MAIA_SIM.edm4hep.root - --gun.multiplicity 50 + --gun.multiplicity 10 --gun.particle "mu-" --gun.distribution uniform - --gun.thetaMax 2.8 # rougly 160 degrees - --gun.thetaMin -2.8 # rougly -160 degrees + --gun.thetaMax 2.96 # rougly 170 degrees + --gun.thetaMin -2.96 # rougly -170 degrees --gun.momentumMin 5 --gun.momentumMax 500 ) @@ -83,7 +83,7 @@ set_test_env(reco_MAIA) # Setup test dependencies such that they are run in the right order always set_tests_properties( - sim_Maia + sim_MAIA PROPERTIES FIXTURES_SETUP sim_maia_fixture ) From a9ffd5902fb9246584b2d47632e74fe546ca8033 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 12 Mar 2026 16:12:06 +0100 Subject: [PATCH 04/72] Add CKFTrackingAlg using ActsGeoSvc (Gen3 geometry path) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New seeded CKF tracking algorithm that is entirely independent from ACTSAlgBase. Obtains tracking geometry, magnetic field, and cellID→surface mapping directly from IActsGeoSvc instead of building its own geometry from TGeo files. - CKFTracking.hxx: reusable helpers parseSeedingLayers() and makeInitialCovariance() extracted from the existing algorithm - CKFTrackingAlg.cpp: self-contained algorithm using ActsGeoSvc; default-constructs GeometryContext, MagneticFieldContext, CalibrationContext as required by the ACTS framework - Helpers: accept shared_ptr so both algorithms (const and non-const field pointers) can use the helper - CMakeLists: register CKFTrackingAlg.cpp as a plugin source Co-Authored-By: Claude Opus 4.6 --- k4ActsTracking/CMakeLists.txt | 1 + .../include/k4ActsTracking/CKFTracking.hxx | 92 +++ .../src/components/CKFTrackingAlg.cpp | 698 ++++++++++++++++++ 3 files changed, 791 insertions(+) create mode 100644 k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx create mode 100644 k4ActsTracking/src/components/CKFTrackingAlg.cpp diff --git a/k4ActsTracking/CMakeLists.txt b/k4ActsTracking/CMakeLists.txt index 9bb0f90a..25f99a24 100644 --- a/k4ActsTracking/CMakeLists.txt +++ b/k4ActsTracking/CMakeLists.txt @@ -43,6 +43,7 @@ set(_plugin_sources src/components/TrackTruthAlg.cxx src/components/ActsTestPropagator.cpp src/components/DD4hepBlueprintConstruction.cpp + src/components/CKFTrackingAlg.cpp ) gaudi_add_module(k4ActsTrackingPlugins diff --git a/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx b/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx new file mode 100644 index 00000000..661a9214 --- /dev/null +++ b/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2014-2024 Key4hep-Project. + * + * This file is part of Key4hep. + * See https://key4hep.github.io/key4hep-doc/ for further info. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#pragma once + +// ACTS +#include + +// ACTSTracking +#include "k4ActsTracking/GeometryIdSelector.hxx" +#include "k4ActsTracking/Measurement.hxx" +#include "k4ActsTracking/SeedSpacePoint.hxx" +#include "k4ActsTracking/SourceLink.hxx" + +// Standard +#include +#include +#include +#include + +namespace ACTSTracking { + + //! Parse seeding layer strings into a geometry ID selector + /** + * Parses a flat list of alternating volume/layer string tokens into an + * \c Acts::GeometryIdentifier selection. Use "*" as a wildcard for either + * the volume or the layer component. + * + * \param seedingLayers Flat vector of (volume, layer) string pairs. + * \return Geometry ID selector for seed-layer filtering. + * \throws std::runtime_error if the vector has an odd number of entries. + */ + inline GeometryIdSelector parseSeedingLayers(const std::vector& seedingLayers) { + std::vector layers; + std::copy_if(seedingLayers.begin(), seedingLayers.end(), std::back_inserter(layers), + [](const std::string& s) { return !s.empty(); }); + + if (layers.size() % 2 != 0) { + throw std::runtime_error("SeedingLayers needs an even number of entries"); + } + + std::vector geoSelection; + for (std::size_t i = 0; i < layers.size(); i += 2) { + Acts::GeometryIdentifier geoid; + if (layers[i] != "*") // volume + geoid = geoid.withVolume(std::stoi(layers[i])); + if (layers[i + 1] != "*") // layer + geoid = geoid.withLayer(std::stoi(layers[i + 1])); + geoSelection.push_back(geoid); + } + + return GeometryIdSelector(geoSelection); + } + + //! Build a diagonal initial track covariance matrix from per-parameter error estimates + /** + * \param p Absolute momentum [ACTS units]. + * \param errPos Local-position error [ACTS units]. + * \param errPhi Azimuthal angle error [rad]. + * \param errLambda Polar angle (lambda) error [rad]. + * \param errRelP Relative momentum error (dimensionless fraction). + * \param errTime Time error [ACTS units]. + * \return 5×5 diagonal bound covariance matrix. + */ + inline Acts::BoundMatrix makeInitialCovariance(double p, double errPos, double errPhi, double errLambda, + double errRelP, double errTime) { + Acts::BoundMatrix cov = Acts::BoundMatrix::Zero(); + cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = std::pow(errPos, 2); + cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = std::pow(errPos, 2); + cov(Acts::eBoundTime, Acts::eBoundTime) = std::pow(errTime, 2); + cov(Acts::eBoundPhi, Acts::eBoundPhi) = std::pow(errPhi, 2); + cov(Acts::eBoundTheta, Acts::eBoundTheta) = std::pow(errLambda, 2); + cov(Acts::eBoundQOverP, Acts::eBoundQOverP) = std::pow(errRelP * p / (p * p), 2); + return cov; + } + +} // namespace ACTSTracking diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp new file mode 100644 index 00000000..ffc6e679 --- /dev/null +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -0,0 +1,698 @@ +/* + * Copyright (c) 2014-2024 Key4hep-Project. + * + * This file is part of Key4hep. + * See https://key4hep.github.io/key4hep-doc/ for further info. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +// k4ActsTracking +#include "k4ActsTracking/CKFTracking.hxx" +#include "k4ActsTracking/Helpers.hxx" +#include "k4ActsTracking/IActsGeoSvc.h" +#include "k4ActsTracking/MeasurementCalibrator.hxx" +#include "k4ActsTracking/Measurement.hxx" +#include "k4ActsTracking/GeometryIdSelector.hxx" +#include "k4ActsTracking/SeedSpacePoint.hxx" +#include "k4ActsTracking/SourceLink.hxx" +#include "k4ActsTracking/SpacePointContainer.hxx" + +// k4FWCore +#include +#include + +// edm4hep +#include +#include +#include +#include +#include +#include + +// Gaudi +#include +#include + +// ACTS +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// TBB +#include +#include +#include +#include + +// Standard +#include +#include +#include +#include +#include + +using namespace Acts::UnitLiterals; + +// --------------------------------------------------------------------------- +// Local type aliases +// --------------------------------------------------------------------------- +namespace { + +using TrackContainer = Acts::TrackContainer; +using TrackFinderOptions = Acts::CombinatorialKalmanFilterOptions; + +using SSPoint = typename Acts::SpacePointContainer< + ACTSTracking::SpacePointContainer>, + Acts::detail::RefHolder>::SpacePointProxyType; + +using SSPointGrid = Acts::CylindricalSpacePointGrid; + +using Stepper = Acts::EigenStepper<>; +using Navigator = Acts::Navigator; +using Propagator = Acts::Propagator; +using CKF = Acts::CombinatorialKalmanFilter; + +} // namespace + +// --------------------------------------------------------------------------- +// Algorithm declaration +// --------------------------------------------------------------------------- + +/** + * @brief Seeded CKF tracking algorithm using ActsGeoSvc. + * + * Functionally equivalent to ACTSSeededCKFTrackingAlg but obtains the + * tracking geometry, magnetic field and surface–cellID mapping directly + * from IActsGeoSvc instead of inheriting from ACTSAlgBase. + * + * ACTS contexts (geometry, magnetic-field, calibration) are default- + * constructed as documented by the ACTS framework. + */ +struct CKFTrackingAlg final : k4FWCore::MultiTransformer< + std::tuple( + const edm4hep::TrackerHitPlaneCollection&, + const edm4hep::TrackerHitSimTrackerHitLinkCollection&)> { + + CKFTrackingAlg(const std::string& name, ISvcLocator* svcLoc); + + StatusCode initialize() override; + + std::tuple operator()( + const edm4hep::TrackerHitPlaneCollection& trackerHitCollection, + const edm4hep::TrackerHitSimTrackerHitLinkCollection& trackerHitRelations) const override; + + // ----- private helpers --------------------------------------------------- + + std::vector findSeeds(const Acts::SeedFinder& finder, + const Acts::SeedFinderOptions& finderOpts, + const auto& spacePointGroup, + const SSPointGrid& grid, + Acts::Range1D middleSpRange, + std::size_t mutSpDataSize, + edm4hep::TrackCollection& seedCollection, + Acts::MagneticFieldProvider::Cache& magCache) const; + + StatusCode tracking(const std::vector& paramseeds, const CKF& trackFinder, + const TrackFinderOptions& ckfOptions, Acts::MagneticFieldProvider::Cache& magCache, + edm4hep::TrackCollection& trackCollection) const; + + // ----- Gaudi properties -------------------------------------------------- + + /// @name Run control + ///@{ + Gaudi::Property m_runCKF{this, "RunCKF", true, "Run tracking using CKF. False means stop at seeding."}; + Gaudi::Property m_propagateBackward{this, "PropagateBackward", false, + "Extrapolates tracks towards beamline."}; + ///@} + + /// @name Seed-finding configuration + ///@{ + Gaudi::Property m_seedFinding_rMax{this, "SeedFinding_RMax", 150, + "Maximum radius of hits to consider."}; + Gaudi::Property m_seedFinding_deltaRMin{this, "SeedFinding_DeltaRMin", 5, + "Minimum dR between hits in a seed."}; + Gaudi::Property m_seedFinding_deltaRMax{this, "SeedFinding_DeltaRMax", 80, + "Maximum dR between hits in a seed."}; + Gaudi::Property m_seedFinding_deltaRMinTop{this, "SeedFinding_DeltaRMinTop", 0.f, + "Minimum dR between reference and outer hits in a seed."}; + Gaudi::Property m_seedFinding_deltaRMaxTop{this, "SeedFinding_DeltaRMaxTop", 0.f, + "Maximum dR between reference and outer hits in a seed."}; + Gaudi::Property m_seedFinding_deltaRMinBottom{this, "SeedFinding_DeltaRMinBottom", 0.f, + "Minimum dR between reference and inner hits in a seed."}; + Gaudi::Property m_seedFinding_deltaRMaxBottom{this, "SeedFinding_DeltaRMaxBottom", 0.f, + "Maximum dR between reference and inner hits in a seed."}; + Gaudi::Property m_seedFinding_collisionRegion{this, "SeedFinding_CollisionRegion", 75.0, + "Half-size of the collision region along z."}; + Gaudi::Property m_seedFinding_zMax{this, "SeedFinding_ZMax", 600.0, "Maximum |z| of hits to consider."}; + Gaudi::Property m_seedFinding_sigmaScattering{this, "SeedFinding_SigmaScattering", 50.0, + "Number of sigmas allowed in scattering angle."}; + Gaudi::Property m_seedFinding_radLengthPerSeed{this, "SeedFinding_RadLengthPerSeed", 0.1, + "Average radiation length per seed."}; + Gaudi::Property m_seedFinding_minPt{this, "SeedFinding_MinPt", 500.0, "Minimum pT of tracks to seed."}; + Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0 * Acts::UnitConstants::mm, + "Maximum d0 of tracks to seed."}; + + std::vector m_default_empty_vec; + Gaudi::Property> m_seedFinding_zBinEdges{this, "SeedFinding_zBinEdges", + m_default_empty_vec, + "Custom z bin edges for the seeding grid."}; + Gaudi::Property m_zTopBinLen{this, "SeedFinding_zTopBinLen", 1, + "Number of top bins along Z for seeding."}; + Gaudi::Property m_zBottomBinLen{this, "SeedFinding_zBottomBinLen", 1, + "Number of bottom bins along Z for seeding."}; + Gaudi::Property m_phiTopBinLen{this, "SeedFinding_phiTopBinLen", 1, + "Number of top bins along phi for seeding."}; + Gaudi::Property m_phiBottomBinLen{this, "SeedFinding_phiBottomBinLen", 1, + "Number of bottom bins along phi for seeding."}; + ///@} + + /// @name Track-fit initial error estimates + ///@{ + Gaudi::Property m_initialTrackError_pos{this, "InitialTrackError_Pos", 10 * Acts::UnitConstants::um, + "Initial track error for local position."}; + Gaudi::Property m_initialTrackError_phi{this, "InitialTrackError_Phi", 1 * Acts::UnitConstants::degree, + "Initial track error for phi."}; + Gaudi::Property m_initialTrackError_relP{this, "InitialTrackError_RelP", 0.25, + "Initial track error for momentum (relative)."}; + Gaudi::Property m_initialTrackError_lambda{this, "InitialTrackError_Lambda", + 1 * Acts::UnitConstants::degree, + "Initial track error for lambda."}; + Gaudi::Property m_initialTrackError_time{this, "InitialTrackError_Time", 100 * Acts::UnitConstants::ns, + "Initial track error for time."}; + Gaudi::Property m_CKF_chi2CutOff{this, "CKF_Chi2CutOff", 15, "Maximum local chi2 contribution."}; + Gaudi::Property m_CKF_numMeasurementsCutOff{this, "CKF_NumMeasurementsCutOff", 10, + "Maximum measurements on a single surface."}; + ///@} + + /// @name Seeding layer selection + ///@{ + Gaudi::Property> m_seedingLayers{this, "SeedingLayers", m_default_empty_vec, + "Volume/layer pairs for seed creation."}; + ACTSTracking::GeometryIdSelector m_seedGeometrySelection; + ///@} + + /// @name Multi-threading + ///@{ + Gaudi::Property m_numThreads{this, "NumThreads", 1, "Number of threads for internal TBB parallelism."}; + ///@} + +private: + SmartIF m_actsGeoSvc; + + mutable std::mutex m_seedMutex{}; + mutable std::mutex m_trackMutex{}; +}; + +DECLARE_COMPONENT(CKFTrackingAlg) + +// --------------------------------------------------------------------------- +// Constructor +// --------------------------------------------------------------------------- + +CKFTrackingAlg::CKFTrackingAlg(const std::string& name, ISvcLocator* svcLoc) + : MultiTransformer( + name, svcLoc, + {KeyValues("InputTrackerHitCollectionName", {"TrackerHits"}), + KeyValues("InputTrackerHitRelationCollectionName", {"TrackerHitRelations"})}, + {KeyValues("OutputSeedCollectionName", {"SeedTracks"}), + KeyValues("OutputTrackCollectionName", {"Tracks"})}) {} + +// --------------------------------------------------------------------------- +// initialize +// --------------------------------------------------------------------------- + +StatusCode CKFTrackingAlg::initialize() { + m_actsGeoSvc = svcLoc()->service("ActsGeoSvc"); + K4_GAUDI_CHECK(m_actsGeoSvc); + + m_seedGeometrySelection = ACTSTracking::parseSeedingLayers(m_seedingLayers.value()); + + // Apply deltaR fallback defaults + if (m_seedFinding_deltaRMinTop == 0.f) m_seedFinding_deltaRMinTop = m_seedFinding_deltaRMin; + if (m_seedFinding_deltaRMaxTop == 0.f) m_seedFinding_deltaRMaxTop = m_seedFinding_deltaRMax; + if (m_seedFinding_deltaRMinBottom == 0.f) m_seedFinding_deltaRMinBottom = m_seedFinding_deltaRMin; + if (m_seedFinding_deltaRMaxBottom == 0.f) m_seedFinding_deltaRMaxBottom = m_seedFinding_deltaRMax; + + debug() << "Initialization of CKFTrackingAlg successful" << endmsg; + return StatusCode::SUCCESS; +} + +// --------------------------------------------------------------------------- +// operator() — event processing +// --------------------------------------------------------------------------- + +std::tuple CKFTrackingAlg::operator()( + const edm4hep::TrackerHitPlaneCollection& trackerHitCollection, + const edm4hep::TrackerHitSimTrackerHitLinkCollection& /*trackerHitRelations*/) const { + + edm4hep::TrackCollection seedCollection; + edm4hep::TrackCollection trackCollection; + + // Default-construct ACTS contexts + const Acts::GeometryContext geoCtx = Acts::GeometryContext::dangerouslyDefaultConstruct(); + const Acts::MagneticFieldContext magCtx{}; + const Acts::CalibrationContext calCtx{}; + + const auto& cellIdToSurface = m_actsGeoSvc->cellIdToSurfaceMap(); + + // ----- Phase 1: convert EDM4HEP hits → ACTS objects ---------------------- + + std::vector> sortedHits; + ACTSTracking::SourceLinkContainer sourceLinks; + ACTSTracking::MeasurementContainer measurements; + ACTSTracking::SeedSpacePointContainer spacePoints; + + sortedHits.reserve(trackerHitCollection.size()); + + for (const auto& hit : trackerHitCollection) { + debug() << "Adding hit " << hit.id() << endmsg; + auto it = cellIdToSurface.find(hit.getCellID()); + if (it == cellIdToSurface.end()) { + warning() << "No surface found for cellID " << hit.getCellID() << " — skipping hit" << endmsg; + continue; + } + sortedHits.push_back({it->second->geometryId(), hit}); + } + debug() << "Working with " << sortedHits.size() << " hits." << endmsg; + + // Sort hits by geometry ID for efficient SourceLink multiset insertion + auto compare = [](const auto& a, const auto& b) { return a.first < b.first; }; + tbb::task_arena arena(m_numThreads.value()); + if (m_numThreads > 1) { + arena.execute([&] { tbb::parallel_sort(sortedHits.begin(), sortedHits.end(), compare); }); + } else { + std::sort(sortedHits.begin(), sortedHits.end(), compare); + } + + sourceLinks.reserve(sortedHits.size()); + + for (const auto& hitPair : sortedHits) { + const Acts::Surface* surface = m_actsGeoSvc->trackingGeometry()->findSurface(hitPair.first); + if (surface == nullptr) { + warning() << "Surface with geoID " << hitPair.first << " not found in tracking geometry — skipping" << endmsg; + continue; + } + + const edm4hep::Vector3d& edmGlobalPos = hitPair.second.getPosition(); + Acts::Vector3 globalPos = {edmGlobalPos.x, edmGlobalPos.y, edmGlobalPos.z}; + + debug() << "Converting hit " << hitPair.second.id() << " to local position (pos = " << edmGlobalPos + << ") using surface with geoId " << hitPair.first << endmsg; + + Acts::Result lpResult = surface->globalToLocal(geoCtx, globalPos, {0, 0, 0}, 0.5_um); + if (!lpResult.ok()) { + warning() << "Global to local transformation did not succeed" << endmsg; + Acts::Vector3 loc3DFrame = surface->localToGlobalTransform(geoCtx).inverse() * globalPos; + debug() << "Local position: " << loc3DFrame.x() << ", " << loc3DFrame.y() << ", " << loc3DFrame.z() + << " z tolerance: " << 0.5_um << endmsg; + continue; + } + + Acts::Vector2 loc = lpResult.value(); + + Acts::SquareMatrix2 localCov = Acts::SquareMatrix2::Zero(); + const edm4hep::TrackerHitPlane hitplane = hitPair.second; + localCov(0, 0) = std::pow(hitplane.getDu() * Acts::UnitConstants::mm, 2); + localCov(1, 1) = std::pow(hitplane.getDv() * Acts::UnitConstants::mm, 2); + + ACTSTracking::SourceLink sourceLink(surface->geometryId(), measurements.size(), hitPair.second); + Acts::SourceLink srcWrap{sourceLink}; + ACTSTracking::Measurement meas = + ACTSTracking::makeMeasurement(srcWrap, loc, localCov, Acts::eBoundLoc0, Acts::eBoundLoc1); + + measurements.push_back(meas); + sourceLinks.emplace_hint(sourceLinks.end(), sourceLink); + + // Create space point for seeding if this surface is selected + if (m_seedGeometrySelection.check(surface->geometryId())) { + Acts::RotationMatrix3 rotLocalToGlobal = surface->referenceFrame(geoCtx, globalPos, {0, 0, 0}); + + // Jacobian from global (x,y,z) to (rho, z) + double x = globalPos[Acts::ePos0]; + double y = globalPos[Acts::ePos1]; + double scale = 2 / std::hypot(x, y); + Acts::Matrix<2, 3> jacXyzToRhoZ = Acts::Matrix<2, 3>::Zero(); + jacXyzToRhoZ(0, Acts::ePos0) = scale * x; + jacXyzToRhoZ(0, Acts::ePos1) = scale * y; + jacXyzToRhoZ(1, Acts::ePos2) = 1; + const auto jac = jacXyzToRhoZ * rotLocalToGlobal.block<3, 2>(Acts::ePos0, Acts::ePos0); + const auto var = (jac * localCov * jac.transpose()).diagonal(); + + spacePoints.push_back(ACTSTracking::SeedSpacePoint(globalPos, var[0], var[1], sourceLink)); + } + } + + debug() << "Created " << spacePoints.size() << " space points" << endmsg; + + // ----- Phase 2: configure seed finder ------------------------------------ + + Acts::MagneticFieldProvider::Cache magCache = m_actsGeoSvc->magneticField()->makeCache(magCtx); + + static const Acts::Vector3 zeropos(0, 0, 0); + + Acts::SeedFinderConfig finderCfg; + finderCfg.rMax = m_seedFinding_rMax; + finderCfg.deltaRMin = m_seedFinding_deltaRMin; + finderCfg.deltaRMax = m_seedFinding_deltaRMax; + finderCfg.deltaRMinTopSP = m_seedFinding_deltaRMinTop; + finderCfg.deltaRMaxTopSP = m_seedFinding_deltaRMaxTop; + finderCfg.deltaRMinBottomSP = m_seedFinding_deltaRMinBottom; + finderCfg.deltaRMaxBottomSP = m_seedFinding_deltaRMaxBottom; + finderCfg.collisionRegionMin = -m_seedFinding_collisionRegion; + finderCfg.collisionRegionMax = m_seedFinding_collisionRegion; + finderCfg.zMin = -m_seedFinding_zMax; + finderCfg.zMax = m_seedFinding_zMax; + finderCfg.maxSeedsPerSpM = 1; + finderCfg.cotThetaMax = 7.40627; // ~2.7 η + finderCfg.sigmaScattering = m_seedFinding_sigmaScattering; + finderCfg.radLengthPerSeed = m_seedFinding_radLengthPerSeed; + finderCfg.minPt = m_seedFinding_minPt * Acts::UnitConstants::MeV; + finderCfg.impactMax = m_seedFinding_impactMax * Acts::UnitConstants::mm; + finderCfg.useVariableMiddleSPRange = true; + + Acts::SeedFilterConfig filterCfg; + filterCfg.maxSeedsPerSpM = finderCfg.maxSeedsPerSpM; + finderCfg.seedFilter = std::make_unique>(filterCfg); + finderCfg = finderCfg.calculateDerivedQuantities(); + + Acts::SeedFinderOptions finderOpts; + finderOpts.bFieldInZ = (*m_actsGeoSvc->magneticField()->getField(zeropos, magCache))[2]; + finderOpts.beamPos = {0, 0}; + finderOpts = finderOpts.calculateDerivedQuantities(finderCfg); + + Acts::CylindricalSpacePointGridConfig gridCfg; + gridCfg.cotThetaMax = finderCfg.cotThetaMax; + gridCfg.deltaRMax = finderCfg.deltaRMax; + gridCfg.minPt = finderCfg.minPt; + gridCfg.rMax = finderCfg.rMax; + gridCfg.zMax = finderCfg.zMax; + gridCfg.zMin = finderCfg.zMin; + gridCfg.impactMax = finderCfg.impactMax; + if (!m_seedFinding_zBinEdges.empty()) { + gridCfg.zBinEdges.resize(m_seedFinding_zBinEdges.size()); + for (std::size_t k = 0; k < m_seedFinding_zBinEdges.size(); k++) { + float pos = std::atof(m_seedFinding_zBinEdges[k].c_str()); + if (pos >= finderCfg.zMin && pos < finderCfg.zMax) { + gridCfg.zBinEdges[k] = pos; + } else { + warning() << "Wrong parameter SeedFinding_zBinEdges; default used" << endmsg; + gridCfg.zBinEdges.clear(); + break; + } + } + } + + Acts::CylindricalSpacePointGridOptions gridOpts; + gridOpts.bFieldInZ = (*m_actsGeoSvc->magneticField()->getField(zeropos, magCache))[2]; + + // Wrap space points for ACTS seed finder + std::vector spacePointPtrs(spacePoints.size(), nullptr); + std::transform(spacePoints.begin(), spacePoints.end(), spacePointPtrs.begin(), + [](const ACTSTracking::SeedSpacePoint& sp) { return &sp; }); + + Acts::SpacePointContainerConfig spConfig; + spConfig.useDetailedDoubleMeasurementInfo = finderCfg.useDetailedDoubleMeasurementInfo; + Acts::SpacePointContainerOptions spOptions; + spOptions.beamPos = {0., 0.}; + + ACTSTracking::SpacePointContainer container(spacePointPtrs); + Acts::SpacePointContainer spContainer(spConfig, spOptions, + container); + + SSPointGrid grid = Acts::CylindricalSpacePointGridCreator::createGrid(gridCfg, gridOpts); + Acts::CylindricalSpacePointGridCreator::fillGrid(finderCfg, finderOpts, grid, spContainer); + + const Acts::GridBinFinder<3ul> bottomBinFinder(m_phiBottomBinLen.value(), m_zBottomBinLen.value(), 0); + const Acts::GridBinFinder<3ul> topBinFinder(m_phiTopBinLen.value(), m_zTopBinLen.value(), 0); + + Acts::SeedFinder finder(finderCfg); + + // ----- Phase 3: configure CKF -------------------------------------------- + + Navigator::Config navigatorCfg{m_actsGeoSvc->trackingGeometry()}; + navigatorCfg.resolvePassive = false; + navigatorCfg.resolveMaterial = true; + navigatorCfg.resolveSensitive = true; + + Stepper stepper(m_actsGeoSvc->magneticField()); + Navigator navigator(navigatorCfg); + Propagator propagator(std::move(stepper), std::move(navigator)); + CKF trackFinder(std::move(propagator)); + + Acts::MeasurementSelector::Config measurementSelectorCfg = { + {Acts::GeometryIdentifier(), {{}, {m_CKF_chi2CutOff}, {(std::size_t)(m_CKF_numMeasurementsCutOff)}}}}; + + Acts::PropagatorPlainOptions pOptions{geoCtx, magCtx}; + pOptions.maxSteps = 10000; + if (m_propagateBackward) { + pOptions.direction = Acts::Direction::Backward(); + } + + Acts::GainMatrixUpdater kfUpdater; + Acts::MeasurementSelector measSel{measurementSelectorCfg}; + ACTSTracking::MeasurementCalibrator measCal{measurements}; + + ACTSTracking::SourceLinkAccessor slAccessor; + slAccessor.container = &sourceLinks; + + using TrackStateCreatorType = Acts::TrackStateCreator; + TrackStateCreatorType trackStateCreator; + trackStateCreator.sourceLinkAccessor.template connect<&ACTSTracking::SourceLinkAccessor::range>(&slAccessor); + trackStateCreator.calibrator.template connect<&ACTSTracking::MeasurementCalibrator::calibrate>(&measCal); + trackStateCreator.measurementSelector + .template connect<&Acts::MeasurementSelector::select>(&measSel); + + Acts::CombinatorialKalmanFilterExtensions extensions; + extensions.updater.connect<&Acts::GainMatrixUpdater::operator()>(&kfUpdater); + extensions.createTrackStates.template connect<&TrackStateCreatorType::createTrackStates>(&trackStateCreator); + + TrackFinderOptions ckfOptions = TrackFinderOptions(geoCtx, magCtx, calCtx, extensions, pOptions); + + // ----- Phase 4: group space points and run seeding + tracking ------------ + + float minRange = std::numeric_limits::max(); + float maxRange = std::numeric_limits::lowest(); + for (const auto& coll : grid) { + if (coll.empty()) continue; + minRange = std::min(coll.front()->radius(), minRange); + maxRange = std::max(coll.back()->radius(), maxRange); + } + + auto spacePointsGrouping = Acts::CylindricalBinnedGroup(std::move(grid), bottomBinFinder, topBinFinder); + + const Acts::Range1D rMiddleSPRange(std::floor(minRange / 2) * 2 + finderCfg.deltaRMiddleMinSPRange, + std::floor(maxRange / 2) * 2 - finderCfg.deltaRMiddleMaxSPRange); + + using GroupIterator = decltype(spacePointsGrouping.begin()); + using GroupValue = std::decay_t())>; + std::vector spacePointGroups; + spacePointGroups.reserve(spacePointsGrouping.grid().size()); + for (auto spGroup : spacePointsGrouping) { + spacePointGroups.push_back(spGroup); + } + + auto parallelSeedingAndTracking = [&](const tbb::blocked_range& r) { + for (size_t i = r.begin(); i != r.end(); ++i) { + const auto paramseeds = findSeeds(finder, finderOpts, spacePointGroups[i], spacePointsGrouping.grid(), + rMiddleSPRange, spContainer.size(), seedCollection, magCache); + if (!m_runCKF) continue; + if (!tracking(paramseeds, trackFinder, ckfOptions, magCache, trackCollection).isSuccess()) { + warning() << "Tracking failed for this event" << endmsg; + } + } + }; + + if (m_numThreads > 1) { + arena.execute([&] { + tbb::parallel_for(tbb::blocked_range(0, spacePointGroups.size()), parallelSeedingAndTracking); + }); + } else { + for (size_t i = 0; i < spacePointGroups.size(); ++i) { + parallelSeedingAndTracking(tbb::blocked_range(i, i + 1)); + } + } + + debug() << "Track Collection Size: " << trackCollection.size() << endmsg; + return std::make_tuple(std::move(seedCollection), std::move(trackCollection)); +} + +// --------------------------------------------------------------------------- +// findSeeds — build seeds from a space-point group and estimate parameters +// --------------------------------------------------------------------------- + +std::vector CKFTrackingAlg::findSeeds( + const Acts::SeedFinder& finder, const Acts::SeedFinderOptions& finderOpts, + const auto& spacePointGroup, const SSPointGrid& grid, Acts::Range1D middleSpRange, + std::size_t mutSpDataSize, edm4hep::TrackCollection& seedCollection, + Acts::MagneticFieldProvider::Cache& magCache) const { + + const Acts::GeometryContext geoCtx = Acts::GeometryContext::dangerouslyDefaultConstruct(); + + const auto& [bottom, middle, top] = spacePointGroup; + + std::vector> seeds; + std::vector paramseeds; + Acts::SeedFinder::SeedingState state; + state.spacePointMutableData.resize(mutSpDataSize); + + finder.createSeedsForGroup(finderOpts, state, grid, seeds, bottom, middle, top, middleSpRange); + + // Unwrap proxy types back to concrete SeedSpacePoint + std::vector> f_seeds; + f_seeds.reserve(seeds.size()); + for (const Acts::Seed& seed : seeds) { + const auto& sps = seed.sp(); + f_seeds.emplace_back(*sps[0]->externalSpacePoint(), *sps[1]->externalSpacePoint(), + *sps[2]->externalSpacePoint()); + } + + for (const auto& seed : f_seeds) { + const ACTSTracking::SeedSpacePoint* bottomSP = seed.sp().front(); + const auto& sourceLink = bottomSP->sourceLink(); + const Acts::GeometryIdentifier& geoId = sourceLink.geometryId(); + const Acts::Surface* surface = m_actsGeoSvc->trackingGeometry()->findSurface(geoId); + if (surface == nullptr) { + warning() << "Surface with geoID " << geoId << " not found in tracking geometry" << endmsg; + continue; + } + + // Magnetic field at the seed position + const Acts::Vector3 seedPos(bottomSP->x(), bottomSP->y(), bottomSP->z()); + Acts::Result seedField = m_actsGeoSvc->magneticField()->getField(seedPos, magCache); + if (!seedField.ok()) { + throw std::runtime_error("Field lookup error: " + std::to_string(seedField.error().value())); + } + + Acts::Result optParams = + Acts::estimateTrackParamsFromSeed(geoCtx, seed.sp(), *surface, *seedField); + if (!optParams.ok()) { + debug() << "Failed estimation of track parameters for seed." << endmsg; + continue; + } + + const Acts::BoundVector& params = *optParams; + float p = std::abs(1.f / params[Acts::eBoundQOverP]); + + Acts::BoundMatrix cov = + ACTSTracking::makeInitialCovariance(p, m_initialTrackError_pos, m_initialTrackError_phi, + m_initialTrackError_lambda, m_initialTrackError_relP, + m_initialTrackError_time); + + Acts::BoundTrackParameters paramseed(surface->getSharedPtr(), params, cov, Acts::ParticleHypothesis::pion()); + paramseeds.push_back(paramseed); + + // Compute seed track state before acquiring the lock + Acts::Vector3 globalPos = + surface->localToGlobal(geoCtx, {params[Acts::eBoundLoc0], params[Acts::eBoundLoc1]}, {0, 0, 0}); + Acts::Result hitField = m_actsGeoSvc->magneticField()->getField(globalPos, magCache); + if (!hitField.ok()) { + throw std::runtime_error("Field lookup error: " + std::to_string(hitField.error().value())); + } + + auto seedTrackState = ACTSTracking::ACTS2edm4hep_trackState(edm4hep::TrackState::AtFirstHit, paramseed, + (*hitField)[2] / Acts::UnitConstants::T); + + { + std::lock_guard lock(m_seedMutex); + auto seedTrack = seedCollection.create(); + for (const ACTSTracking::SeedSpacePoint* sp : seed.sp()) { + seedTrack.addToTrackerHits(sp->sourceLink().edm4hepHit()); + } + seedTrack.addToTrackStates(seedTrackState); + } + + debug() << "Seed Parameters" << std::endl << paramseed << endmsg; + } + + debug() << "Seeds found: " << paramseeds.size() << endmsg; + return paramseeds; +} + +// --------------------------------------------------------------------------- +// tracking — run CKF for each seed and collect results +// --------------------------------------------------------------------------- + +StatusCode CKFTrackingAlg::tracking(const std::vector& paramseeds, + const CKF& trackFinder, const TrackFinderOptions& ckfOptions, + Acts::MagneticFieldProvider::Cache& magCache, + edm4hep::TrackCollection& trackCollection) const { + const Acts::GeometryContext geoCtx = Acts::GeometryContext::dangerouslyDefaultConstruct(); + + debug() << "Starting CKF track finding with " << paramseeds.size() << " seeds." << endmsg; + + auto trackContainer = std::make_shared(); + auto trackStateContainer = std::make_shared(); + TrackContainer tracks(trackContainer, trackStateContainer); + + auto trackStart = std::chrono::high_resolution_clock::now(); + + for (std::size_t iseed = 0; iseed < paramseeds.size(); ++iseed) { + tracks.clear(); + auto result = trackFinder.findTracks(paramseeds.at(iseed), ckfOptions, tracks); + if (result.ok()) { + const auto& fitOutput = result.value(); + for (const TrackContainer::TrackProxy& trackItem : fitOutput) { + auto trackTip = tracks.makeTrack(); + trackTip.copyFrom(trackItem); + auto smoothResult = Acts::smoothTrack(geoCtx, trackTip); + if (!smoothResult.ok()) { + warning() << "Track smoothing error: " << smoothResult.error() << endmsg; + continue; + } + + debug() << "Trajectory Summary" << endmsg; + debug() << "\tchi2Sum " << trackTip.chi2() << endmsg; + debug() << "\tNDF " << trackTip.nDoF() << endmsg; + debug() << "\tnHoles " << trackTip.nHoles() << endmsg; + debug() << "\tnMeasurements " << trackTip.nMeasurements() << endmsg; + debug() << "\tnOutliers " << trackTip.nOutliers() << endmsg; + debug() << "\tnStates " << trackTip.nTrackStates() << endmsg; + + auto track = ACTSTracking::ACTS2edm4hep_track(trackTip, m_actsGeoSvc->magneticField(), magCache); + { + std::lock_guard lock{m_trackMutex}; + trackCollection.push_back(track); + } + } + } else { + warning() << "Track fit error: " << result.error() << endmsg; + } + } + + auto trackEnd = std::chrono::high_resolution_clock::now(); + std::chrono::duration trackDuration = trackEnd - trackStart; + (void)trackDuration; + + return StatusCode::SUCCESS; +} From b50d830966a2242b0c13ae009ed87bdfac44927d Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Thu, 12 Mar 2026 16:34:09 +0100 Subject: [PATCH 05/72] Add test for CKFTrackingAlg with MAIA_v0 (Gen3 geometry path) Adds an options file and CTest entry for the new CKFTrackingAlg that uses ActsGeoSvc (Blueprint-based geometry) instead of algorithm-level TGeo file loading. The test runs on the same simulated MAIA data as the existing reco_MAIA test and verifies that the algorithm initialises and processes hits without crashing. SeedingLayers is left empty pending geometry-ID inspection of the Gen3 path; the test exercises the hit-conversion pipeline and CKF setup even without seeds. --- test/CMakeLists.txt | 15 +++ test/options/MAIA_CKFTrackingAlg.py | 165 ++++++++++++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 test/options/MAIA_CKFTrackingAlg.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b784295c..533f04a5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -88,8 +88,23 @@ set_tests_properties( FIXTURES_SETUP sim_maia_fixture ) +# Gen3 geometry path: CKFTrackingAlg driven by ActsGeoSvc +add_test(NAME reco_MAIA_Gen3 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKFTrackingAlg.py + --compactFile=$ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml +) +set_test_env(reco_MAIA_Gen3) + +set_tests_properties( + sim_MAIA + PROPERTIES + FIXTURES_SETUP sim_maia_fixture +) set_tests_properties( reco_MAIA + reco_MAIA_Gen3 + PROPERTIES FIXTURES_REQUIRED sim_maia_fixture ) diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py new file mode 100644 index 00000000..d5afeadb --- /dev/null +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -0,0 +1,165 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2014-2024 Key4hep-Project. +# +# This file is part of Key4hep. +# See https://key4hep.github.io/key4hep-doc/ for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# Options file for the Gen3-geometry CKF tracking algorithm (CKFTrackingAlg). +# Uses ActsGeoSvc for geometry, magnetic field, and hit-to-surface mapping, +# instead of the algorithm-level TGeo file loading used by ACTSSeededCKFTrackingAlg. + +import os + +from Gaudi.Configuration import INFO, VERBOSE +from Gaudi.Configurables import ( + ActsGeoSvc, + CKFTrackingAlg, + CollectionMerger, + DDPlanarDigi, + EventDataSvc, + GeoSvc, +) + +from k4FWCore import ApplicationMgr, IOSvc +from k4FWCore.parseArgs import parser + +parser.add_argument( + "--compactFile", + help="The geometry compact file to use for reconstruction", + type=str, +) + +args = parser.parse_known_args()[0] + + +svcList = [ + GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), + ActsGeoSvc("ActsGeoSvc"), + EventDataSvc("EventDataSvc"), +] + +iosvc = IOSvc( + "IOSvc", + Input=["particle_gun_MAIA_SIM.edm4hep.root"], + Output="maia_ckftrackingalg_reco.edm4hep.root", +) + + +algList = [] + + +for name in ("VertexBarrel", "VertexEndcap"): + algList.append( + DDPlanarDigi( + f"{name}Digitizer", + CorrectTimesForPropagation=True, + IsStrip=False, + ResolutionT=[0.03], + ResolutionU=[0.005], + ResolutionV=[0.005], + SubDetectorName="Vertex", + TimeWindowMax=[0.15], + TimeWindowMin=[-0.09], + UseTimeWindow=True, + SimTrackHitCollectionName=[f"{name}Collection"], + SimTrkHitRelCollection=[f"{name}HitsRelations"], + TrackerHitCollectionName=[f"{name}Hits"], + ) + ) + +for name in ( + "InnerTrackerBarrel", + "InnerTrackerEndcap", + "OuterTrackerBarrel", + "OuterTrackerEndcap", +): + algList.append( + DDPlanarDigi( + f"{name}Digitizer", + CorrectTimesForPropagation=True, + IsStrip="InnerBarrel" in name or "OuterEndcap" in name, + ResolutionT=[0.06], + ResolutionU=[0.007], + ResolutionV=[0.09], + SubDetectorName=name.replace("Barrel", "s").replace("Endcap", "s"), + TimeWindowMax=[0.3], + TimeWindowMin=[-0.18], + UseTimeWindow=True, + SimTrackHitCollectionName=[f"{name}Collection"], + SimTrkHitRelCollection=[f"{name}HitsRelations"], + TrackerHitCollectionName=[f"{name}Hits"], + ) + ) + + +algList.append( + CollectionMerger( + "MergeHits", + InputCollections=[ + "VertexBarrelHits", + "VertexEndcapHits", + "InnerTrackerBarrelHits", + "InnerTrackerEndcapHits", + "OuterTrackerBarrelHits", + "OuterTrackerEndcapHits", + ], + OutputCollection=["MergedTrackerHits"], + ) +) + +algList.append( + CollectionMerger( + "MergeHitsRelations", + InputCollections=[ + "VertexBarrelHitsRelations", + "VertexEndcapHitsRelations", + "InnerTrackerBarrelHitsRelations", + "InnerTrackerEndcapHitsRelations", + "OuterTrackerBarrelHitsRelations", + "OuterTrackerEndcapHitsRelations", + ], + OutputCollection=["MergedTrackerHitsRelations"], + ) +) + +ckf_tracking = CKFTrackingAlg( + "CKFTracking", + RunCKF=True, + CKF_Chi2CutOff=10, + SeedFinding_RMax=150, + SeedFinding_MinPt=500, + SeedFinding_ImpactMax=3, + CKF_NumMeasurementsCutOff=1, + SeedFinding_SigmaScattering=50, + SeedFinding_CollisionRegion=6, + SeedFinding_RadLengthPerSeed=0.1, + # Seeding layers use (volume, layer) pairs from the Gen3 / Blueprint + # geometry. The volume/layer numbering differs from the TGeo-based path + # used by ACTSSeededCKFTrackingAlg — update these once the geometry IDs + # have been inspected at runtime (e.g. via ActsTestPropagator output). + SeedingLayers=[], + OutputTrackCollectionName=["AllTracks"], + OutputSeedCollectionName=["SeedTracks"], + InputTrackerHitCollectionName=["MergedTrackerHits"], + InputTrackerHitRelationCollectionName=["MergedTrackerHitsRelations"], + OutputLevel=VERBOSE, +) +algList.append(ckf_tracking) + + +ApplicationMgr( + TopAlg=algList, ExtSvc=svcList, OutputLevel=INFO, EvtSel="NONE", EvtMax=-1 +) From 2bff99ab07703d592fc739bf6a01f610772baf22 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 17 Mar 2026 17:08:32 +0100 Subject: [PATCH 06/72] Properly shoot muons for tests --- test/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 533f04a5..3bb2c56c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -67,8 +67,8 @@ add_test(NAME sim_MAIA --gun.distribution uniform --gun.thetaMax 2.96 # rougly 170 degrees --gun.thetaMin -2.96 # rougly -170 degrees - --gun.momentumMin 5 - --gun.momentumMax 500 + --gun.momentumMin 5000 + --gun.momentumMax 500000 ) add_test(NAME reco_MAIA From 37617ad76d74b678da0301fbeb27744b4323874e Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 17 Mar 2026 17:09:01 +0100 Subject: [PATCH 07/72] First version of seed selection based off CellIDs --- .../include/k4ActsTracking/CKFTracking.hxx | 52 +++++ .../include/k4ActsTracking/IActsGeoSvc.h | 8 +- k4ActsTracking/src/components/ActsGeoSvc.cpp | 3 + k4ActsTracking/src/components/ActsGeoSvc.h | 5 + .../src/components/CKFTrackingAlg.cpp | 200 ++++++++---------- test/CMakeLists.txt | 5 - test/options/MAIA_CKFTrackingAlg.py | 10 +- 7 files changed, 162 insertions(+), 121 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx b/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx index 661a9214..67be4a09 100644 --- a/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx +++ b/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx @@ -23,14 +23,20 @@ // ACTSTracking #include "k4ActsTracking/GeometryIdSelector.hxx" +#include "k4ActsTracking/IActsGeoSvc.h" #include "k4ActsTracking/Measurement.hxx" #include "k4ActsTracking/SeedSpacePoint.hxx" #include "k4ActsTracking/SourceLink.hxx" +// DD4hep +#include + // Standard #include +#include #include #include +#include #include namespace ACTSTracking { @@ -67,6 +73,52 @@ namespace ACTSTracking { return GeometryIdSelector(geoSelection); } + //! Build a set of CellIDs whose decoded (system, layer) fields match any of the user-specified pairs + /** + * Iterates over \p cellIdMap (the full CellID→Surface map from IActsGeoSvc) and returns + * those CellIDs whose \c system and \c layer fields — decoded with a BitFieldCoder built + * from \p encodingString — match at least one entry in the flat \p layers list. + * + * The \p layers list must have an even number of entries interpreted as + * alternating (system, layer) string pairs. Use \c "*" as a wildcard. + * + * \param layers Flat vector of (system, layer) string pairs. + * \param cellIdMap The CellID-to-surface map from IActsGeoSvc::cellIdToSurfaceMap(). + * \param encodingString DD4hep BitFieldCoder encoding string from IActsGeoSvc::cellIDEncodingString(). + * \return Set of matching CellIDs for use in O(1) seed space-point filtering. + * \throws std::runtime_error if the vector has an odd number of entries. + */ + inline std::unordered_set parseSeedingLayersCellID( + const std::vector& layers, + const IActsGeoSvc::CellIDSurfaceMap& cellIdMap, + const std::string& encodingString) { + if (layers.size() % 2 != 0) + throw std::runtime_error("SeedingLayersCellID needs an even number of entries"); + + // Parse (system, layer) pairs; -1 means wildcard + std::vector> selection; + for (std::size_t i = 0; i < layers.size(); i += 2) { + const int64_t sys = (layers[i] == "*") ? -1 : std::stoi(layers[i]); + const int64_t lay = (layers[i + 1] == "*") ? -1 : std::stoi(layers[i + 1]); + selection.emplace_back(sys, lay); + } + + dd4hep::DDSegmentation::BitFieldCoder decoder{encodingString}; + std::unordered_set seedCellIDs; + for (const auto& [cellID, surface] : cellIdMap) { + const int64_t sys = decoder.get(cellID, "system"); + const int64_t lay = decoder.get(cellID, "layer"); + for (const auto& [reqSys, reqLay] : selection) { + if ((reqSys == -1 || sys == reqSys) && (reqLay == -1 || lay == reqLay)) { + seedCellIDs.insert(cellID); + break; + } + } + } + + return seedCellIDs; + } + //! Build a diagonal initial track covariance matrix from per-parameter error estimates /** * \param p Absolute momentum [ACTS units]. diff --git a/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h b/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h index 250ed777..6fbea36d 100644 --- a/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h +++ b/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h @@ -24,6 +24,7 @@ #include #include +#include #include namespace dd4hep { @@ -45,9 +46,10 @@ class GAUDI_API IActsGeoSvc : virtual public IService { public: DeclareInterfaceID(IActsGeoSvc, 1, 0); - virtual std::shared_ptr trackingGeometry() const = 0; - virtual std::shared_ptr magneticField() const = 0; - virtual const CellIDSurfaceMap& cellIdToSurfaceMap() const = 0; + virtual std::shared_ptr trackingGeometry() const = 0; + virtual std::shared_ptr magneticField() const = 0; + virtual const CellIDSurfaceMap& cellIdToSurfaceMap() const = 0; + virtual std::string cellIDEncodingString() const = 0; virtual ~IActsGeoSvc() = default; }; diff --git a/k4ActsTracking/src/components/ActsGeoSvc.cpp b/k4ActsTracking/src/components/ActsGeoSvc.cpp index 0b1f7bce..13c5ac96 100644 --- a/k4ActsTracking/src/components/ActsGeoSvc.cpp +++ b/k4ActsTracking/src/components/ActsGeoSvc.cpp @@ -73,6 +73,9 @@ StatusCode ActsGeoSvc::initialize() { m_geoSvc = Gaudi::svcLocator()->service("GeoSvc"); K4_GAUDI_CHECK(m_geoSvc); + m_cellIDEncodingString = m_geoSvc->getDetector()->constantAsString(m_encodingStringConstant.value()); + debug() << "CellID encoding string: " << m_cellIDEncodingString << endmsg; + std::array magneticFieldVector = {0, 0, 0}; std::array position = {0, 0, 0}; m_geoSvc->getDetector()->field().magneticField(position.data(), magneticFieldVector.data()); diff --git a/k4ActsTracking/src/components/ActsGeoSvc.h b/k4ActsTracking/src/components/ActsGeoSvc.h index 53a743e5..fcc62d46 100644 --- a/k4ActsTracking/src/components/ActsGeoSvc.h +++ b/k4ActsTracking/src/components/ActsGeoSvc.h @@ -63,8 +63,12 @@ class ActsGeoSvc : public extends { "Name of the 3D visualization file"}; Gaudi::Property m_dumpVisualization{this, "DumpVisualization", false, "Whether or not to create a 3D visualization dump"}; + Gaudi::Property m_encodingStringConstant{ + this, "EncodingStringVariable", "GlobalTrackerReadoutID", + "Name of the DD4hep constant holding the CellID encoding string."}; const CellIDSurfaceMap& cellIdToSurfaceMap() const override { return m_cellIDToSurface; } + std::string cellIDEncodingString() const override { return m_cellIDEncodingString; } private: using BlueprintBuilder = ActsPlugins::DD4hep::BlueprintBuilder; @@ -76,6 +80,7 @@ class ActsGeoSvc : public extends { std::shared_ptr m_magneticField{nullptr}; std::unordered_map m_cellIDToSurface{}; std::unordered_map m_bluePrintPopulationFuncs{}; + std::string m_cellIDEncodingString{}; }; inline std::shared_ptr ActsGeoSvc::trackingGeometry() const { return m_trackingGeo; } diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index ffc6e679..9b33d1c1 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -21,9 +21,8 @@ #include "k4ActsTracking/CKFTracking.hxx" #include "k4ActsTracking/Helpers.hxx" #include "k4ActsTracking/IActsGeoSvc.h" -#include "k4ActsTracking/MeasurementCalibrator.hxx" #include "k4ActsTracking/Measurement.hxx" -#include "k4ActsTracking/GeometryIdSelector.hxx" +#include "k4ActsTracking/MeasurementCalibrator.hxx" #include "k4ActsTracking/SeedSpacePoint.hxx" #include "k4ActsTracking/SourceLink.hxx" #include "k4ActsTracking/SpacePointContainer.hxx" @@ -33,10 +32,7 @@ #include // edm4hep -#include #include -#include -#include #include #include @@ -44,6 +40,9 @@ #include #include +// DD4hep +#include + // ACTS #include #include @@ -76,39 +75,15 @@ // Standard #include +#include #include #include #include +#include #include using namespace Acts::UnitLiterals; -// --------------------------------------------------------------------------- -// Local type aliases -// --------------------------------------------------------------------------- -namespace { - -using TrackContainer = Acts::TrackContainer; -using TrackFinderOptions = Acts::CombinatorialKalmanFilterOptions; - -using SSPoint = typename Acts::SpacePointContainer< - ACTSTracking::SpacePointContainer>, - Acts::detail::RefHolder>::SpacePointProxyType; - -using SSPointGrid = Acts::CylindricalSpacePointGrid; - -using Stepper = Acts::EigenStepper<>; -using Navigator = Acts::Navigator; -using Propagator = Acts::Propagator; -using CKF = Acts::CombinatorialKalmanFilter; - -} // namespace - -// --------------------------------------------------------------------------- -// Algorithm declaration -// --------------------------------------------------------------------------- - /** * @brief Seeded CKF tracking algorithm using ActsGeoSvc. * @@ -119,10 +94,22 @@ using CKF = Acts::CombinatorialKalmanFilter; * ACTS contexts (geometry, magnetic-field, calibration) are default- * constructed as documented by the ACTS framework. */ -struct CKFTrackingAlg final : k4FWCore::MultiTransformer< - std::tuple( - const edm4hep::TrackerHitPlaneCollection&, - const edm4hep::TrackerHitSimTrackerHitLinkCollection&)> { +struct CKFTrackingAlg final + : k4FWCore::MultiTransformer( + const edm4hep::TrackerHitPlaneCollection&, const edm4hep::TrackerHitSimTrackerHitLinkCollection&)> { + using TrackContainer = Acts::TrackContainer; + using TrackFinderOptions = Acts::CombinatorialKalmanFilterOptions; + + using SSPoint = typename Acts::SpacePointContainer< + ACTSTracking::SpacePointContainer>, + Acts::detail::RefHolder>::SpacePointProxyType; + + using SSPointGrid = Acts::CylindricalSpacePointGrid; + + using Stepper = Acts::EigenStepper<>; + using Navigator = Acts::Navigator; + using Propagator = Acts::Propagator; + using CKF = Acts::CombinatorialKalmanFilter; CKFTrackingAlg(const std::string& name, ISvcLocator* svcLoc); @@ -136,12 +123,10 @@ struct CKFTrackingAlg final : k4FWCore::MultiTransformer< std::vector findSeeds(const Acts::SeedFinder& finder, const Acts::SeedFinderOptions& finderOpts, - const auto& spacePointGroup, - const SSPointGrid& grid, - Acts::Range1D middleSpRange, - std::size_t mutSpDataSize, - edm4hep::TrackCollection& seedCollection, - Acts::MagneticFieldProvider::Cache& magCache) const; + const auto& spacePointGroup, const SSPointGrid& grid, + Acts::Range1D middleSpRange, std::size_t mutSpDataSize, + edm4hep::TrackCollection& seedCollection, + Acts::MagneticFieldProvider::Cache& magCache) const; StatusCode tracking(const std::vector& paramseeds, const CKF& trackFinder, const TrackFinderOptions& ckfOptions, Acts::MagneticFieldProvider::Cache& magCache, @@ -152,14 +137,12 @@ struct CKFTrackingAlg final : k4FWCore::MultiTransformer< /// @name Run control ///@{ Gaudi::Property m_runCKF{this, "RunCKF", true, "Run tracking using CKF. False means stop at seeding."}; - Gaudi::Property m_propagateBackward{this, "PropagateBackward", false, - "Extrapolates tracks towards beamline."}; + Gaudi::Property m_propagateBackward{this, "PropagateBackward", false, "Extrapolates tracks towards beamline."}; ///@} /// @name Seed-finding configuration ///@{ - Gaudi::Property m_seedFinding_rMax{this, "SeedFinding_RMax", 150, - "Maximum radius of hits to consider."}; + Gaudi::Property m_seedFinding_rMax{this, "SeedFinding_RMax", 150, "Maximum radius of hits to consider."}; Gaudi::Property m_seedFinding_deltaRMin{this, "SeedFinding_DeltaRMin", 5, "Minimum dR between hits in a seed."}; Gaudi::Property m_seedFinding_deltaRMax{this, "SeedFinding_DeltaRMax", 80, @@ -183,43 +166,41 @@ struct CKFTrackingAlg final : k4FWCore::MultiTransformer< Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0 * Acts::UnitConstants::mm, "Maximum d0 of tracks to seed."}; - std::vector m_default_empty_vec; - Gaudi::Property> m_seedFinding_zBinEdges{this, "SeedFinding_zBinEdges", - m_default_empty_vec, + std::vector m_default_empty_vec; + Gaudi::Property> m_seedFinding_zBinEdges{this, "SeedFinding_zBinEdges", m_default_empty_vec, "Custom z bin edges for the seeding grid."}; - Gaudi::Property m_zTopBinLen{this, "SeedFinding_zTopBinLen", 1, - "Number of top bins along Z for seeding."}; + Gaudi::Property m_zTopBinLen{this, "SeedFinding_zTopBinLen", 1, "Number of top bins along Z for seeding."}; Gaudi::Property m_zBottomBinLen{this, "SeedFinding_zBottomBinLen", 1, "Number of bottom bins along Z for seeding."}; - Gaudi::Property m_phiTopBinLen{this, "SeedFinding_phiTopBinLen", 1, - "Number of top bins along phi for seeding."}; + Gaudi::Property m_phiTopBinLen{this, "SeedFinding_phiTopBinLen", 1, "Number of top bins along phi for seeding."}; Gaudi::Property m_phiBottomBinLen{this, "SeedFinding_phiBottomBinLen", 1, "Number of bottom bins along phi for seeding."}; ///@} /// @name Track-fit initial error estimates ///@{ - Gaudi::Property m_initialTrackError_pos{this, "InitialTrackError_Pos", 10 * Acts::UnitConstants::um, + Gaudi::Property m_initialTrackError_pos{this, "InitialTrackError_Pos", 10 * Acts::UnitConstants::um, "Initial track error for local position."}; - Gaudi::Property m_initialTrackError_phi{this, "InitialTrackError_Phi", 1 * Acts::UnitConstants::degree, + Gaudi::Property m_initialTrackError_phi{this, "InitialTrackError_Phi", 1 * Acts::UnitConstants::degree, "Initial track error for phi."}; - Gaudi::Property m_initialTrackError_relP{this, "InitialTrackError_RelP", 0.25, + Gaudi::Property m_initialTrackError_relP{this, "InitialTrackError_RelP", 0.25, "Initial track error for momentum (relative)."}; - Gaudi::Property m_initialTrackError_lambda{this, "InitialTrackError_Lambda", - 1 * Acts::UnitConstants::degree, + Gaudi::Property m_initialTrackError_lambda{this, "InitialTrackError_Lambda", 1 * Acts::UnitConstants::degree, "Initial track error for lambda."}; - Gaudi::Property m_initialTrackError_time{this, "InitialTrackError_Time", 100 * Acts::UnitConstants::ns, + Gaudi::Property m_initialTrackError_time{this, "InitialTrackError_Time", 100 * Acts::UnitConstants::ns, "Initial track error for time."}; Gaudi::Property m_CKF_chi2CutOff{this, "CKF_Chi2CutOff", 15, "Maximum local chi2 contribution."}; Gaudi::Property m_CKF_numMeasurementsCutOff{this, "CKF_NumMeasurementsCutOff", 10, - "Maximum measurements on a single surface."}; + "Maximum measurements on a single surface."}; ///@} /// @name Seeding layer selection ///@{ - Gaudi::Property> m_seedingLayers{this, "SeedingLayers", m_default_empty_vec, - "Volume/layer pairs for seed creation."}; - ACTSTracking::GeometryIdSelector m_seedGeometrySelection; + Gaudi::Property> m_seedingLayersCellID{ + this, "SeedingLayersCellID", m_default_empty_vec, + "DD4hep (system, layer) pairs for seed space-point selection. " + "Flat list of alternating system and layer values; use \"*\" as wildcard."}; + std::unordered_set m_seedCellIDSet; ///@} /// @name Multi-threading @@ -245,8 +226,8 @@ CKFTrackingAlg::CKFTrackingAlg(const std::string& name, ISvcLocator* svcLoc) name, svcLoc, {KeyValues("InputTrackerHitCollectionName", {"TrackerHits"}), KeyValues("InputTrackerHitRelationCollectionName", {"TrackerHitRelations"})}, - {KeyValues("OutputSeedCollectionName", {"SeedTracks"}), - KeyValues("OutputTrackCollectionName", {"Tracks"})}) {} + {KeyValues("OutputSeedCollectionName", {"SeedTracks"}), KeyValues("OutputTrackCollectionName", {"Tracks"})}) { +} // --------------------------------------------------------------------------- // initialize @@ -256,13 +237,18 @@ StatusCode CKFTrackingAlg::initialize() { m_actsGeoSvc = svcLoc()->service("ActsGeoSvc"); K4_GAUDI_CHECK(m_actsGeoSvc); - m_seedGeometrySelection = ACTSTracking::parseSeedingLayers(m_seedingLayers.value()); + m_seedCellIDSet = ACTSTracking::parseSeedingLayersCellID( + m_seedingLayersCellID.value(), m_actsGeoSvc->cellIdToSurfaceMap(), m_actsGeoSvc->cellIDEncodingString()); // Apply deltaR fallback defaults - if (m_seedFinding_deltaRMinTop == 0.f) m_seedFinding_deltaRMinTop = m_seedFinding_deltaRMin; - if (m_seedFinding_deltaRMaxTop == 0.f) m_seedFinding_deltaRMaxTop = m_seedFinding_deltaRMax; - if (m_seedFinding_deltaRMinBottom == 0.f) m_seedFinding_deltaRMinBottom = m_seedFinding_deltaRMin; - if (m_seedFinding_deltaRMaxBottom == 0.f) m_seedFinding_deltaRMaxBottom = m_seedFinding_deltaRMax; + if (m_seedFinding_deltaRMinTop == 0.f) + m_seedFinding_deltaRMinTop = m_seedFinding_deltaRMin; + if (m_seedFinding_deltaRMaxTop == 0.f) + m_seedFinding_deltaRMaxTop = m_seedFinding_deltaRMax; + if (m_seedFinding_deltaRMinBottom == 0.f) + m_seedFinding_deltaRMinBottom = m_seedFinding_deltaRMin; + if (m_seedFinding_deltaRMaxBottom == 0.f) + m_seedFinding_deltaRMaxBottom = m_seedFinding_deltaRMax; debug() << "Initialization of CKFTrackingAlg successful" << endmsg; return StatusCode::SUCCESS; @@ -273,25 +259,26 @@ StatusCode CKFTrackingAlg::initialize() { // --------------------------------------------------------------------------- std::tuple CKFTrackingAlg::operator()( - const edm4hep::TrackerHitPlaneCollection& trackerHitCollection, + const edm4hep::TrackerHitPlaneCollection& trackerHitCollection, const edm4hep::TrackerHitSimTrackerHitLinkCollection& /*trackerHitRelations*/) const { - edm4hep::TrackCollection seedCollection; edm4hep::TrackCollection trackCollection; // Default-construct ACTS contexts - const Acts::GeometryContext geoCtx = Acts::GeometryContext::dangerouslyDefaultConstruct(); + const Acts::GeometryContext geoCtx = Acts::GeometryContext::dangerouslyDefaultConstruct(); const Acts::MagneticFieldContext magCtx{}; - const Acts::CalibrationContext calCtx{}; + const Acts::CalibrationContext calCtx{}; const auto& cellIdToSurface = m_actsGeoSvc->cellIdToSurfaceMap(); + dd4hep::DDSegmentation::BitFieldCoder decoder{m_actsGeoSvc->cellIDEncodingString()}; + // ----- Phase 1: convert EDM4HEP hits → ACTS objects ---------------------- std::vector> sortedHits; - ACTSTracking::SourceLinkContainer sourceLinks; - ACTSTracking::MeasurementContainer measurements; - ACTSTracking::SeedSpacePointContainer spacePoints; + ACTSTracking::SourceLinkContainer sourceLinks; + ACTSTracking::MeasurementContainer measurements; + ACTSTracking::SeedSpacePointContainer spacePoints; sortedHits.reserve(trackerHitCollection.size()); @@ -307,7 +294,7 @@ std::tuple CKFTrackingAlg::o debug() << "Working with " << sortedHits.size() << " hits." << endmsg; // Sort hits by geometry ID for efficient SourceLink multiset insertion - auto compare = [](const auto& a, const auto& b) { return a.first < b.first; }; + auto compare = [](const auto& a, const auto& b) { return a.first < b.first; }; tbb::task_arena arena(m_numThreads.value()); if (m_numThreads > 1) { arena.execute([&] { tbb::parallel_sort(sortedHits.begin(), sortedHits.end(), compare); }); @@ -346,16 +333,18 @@ std::tuple CKFTrackingAlg::o localCov(0, 0) = std::pow(hitplane.getDu() * Acts::UnitConstants::mm, 2); localCov(1, 1) = std::pow(hitplane.getDv() * Acts::UnitConstants::mm, 2); - ACTSTracking::SourceLink sourceLink(surface->geometryId(), measurements.size(), hitPair.second); - Acts::SourceLink srcWrap{sourceLink}; + ACTSTracking::SourceLink sourceLink(surface->geometryId(), measurements.size(), hitPair.second); + Acts::SourceLink srcWrap{sourceLink}; ACTSTracking::Measurement meas = ACTSTracking::makeMeasurement(srcWrap, loc, localCov, Acts::eBoundLoc0, Acts::eBoundLoc1); measurements.push_back(meas); sourceLinks.emplace_hint(sourceLinks.end(), sourceLink); + debug() << "Seed selection for spacepoint w/ cellId: " << decoder.valueString(hitPair.second.getCellID()) << endmsg; // Create space point for seeding if this surface is selected - if (m_seedGeometrySelection.check(surface->geometryId())) { + if (m_seedCellIDSet.count(hitPair.second.getCellID())) { + debug() << "Accepted" << endmsg; Acts::RotationMatrix3 rotLocalToGlobal = surface->referenceFrame(geoCtx, globalPos, {0, 0, 0}); // Jacobian from global (x,y,z) to (rho, z) @@ -390,9 +379,9 @@ std::tuple CKFTrackingAlg::o finderCfg.deltaRMinBottomSP = m_seedFinding_deltaRMinBottom; finderCfg.deltaRMaxBottomSP = m_seedFinding_deltaRMaxBottom; finderCfg.collisionRegionMin = -m_seedFinding_collisionRegion; - finderCfg.collisionRegionMax = m_seedFinding_collisionRegion; + finderCfg.collisionRegionMax = m_seedFinding_collisionRegion; finderCfg.zMin = -m_seedFinding_zMax; - finderCfg.zMax = m_seedFinding_zMax; + finderCfg.zMax = m_seedFinding_zMax; finderCfg.maxSeedsPerSpM = 1; finderCfg.cotThetaMax = 7.40627; // ~2.7 η finderCfg.sigmaScattering = m_seedFinding_sigmaScattering; @@ -447,8 +436,7 @@ std::tuple CKFTrackingAlg::o spOptions.beamPos = {0., 0.}; ACTSTracking::SpacePointContainer container(spacePointPtrs); - Acts::SpacePointContainer spContainer(spConfig, spOptions, - container); + Acts::SpacePointContainer spContainer(spConfig, spOptions, container); SSPointGrid grid = Acts::CylindricalSpacePointGridCreator::createGrid(gridCfg, gridOpts); Acts::CylindricalSpacePointGridCreator::fillGrid(finderCfg, finderOpts, grid, spContainer); @@ -479,8 +467,8 @@ std::tuple CKFTrackingAlg::o pOptions.direction = Acts::Direction::Backward(); } - Acts::GainMatrixUpdater kfUpdater; - Acts::MeasurementSelector measSel{measurementSelectorCfg}; + Acts::GainMatrixUpdater kfUpdater; + Acts::MeasurementSelector measSel{measurementSelectorCfg}; ACTSTracking::MeasurementCalibrator measCal{measurements}; ACTSTracking::SourceLinkAccessor slAccessor; @@ -504,7 +492,8 @@ std::tuple CKFTrackingAlg::o float minRange = std::numeric_limits::max(); float maxRange = std::numeric_limits::lowest(); for (const auto& coll : grid) { - if (coll.empty()) continue; + if (coll.empty()) + continue; minRange = std::min(coll.front()->radius(), minRange); maxRange = std::max(coll.back()->radius(), maxRange); } @@ -526,7 +515,8 @@ std::tuple CKFTrackingAlg::o for (size_t i = r.begin(); i != r.end(); ++i) { const auto paramseeds = findSeeds(finder, finderOpts, spacePointGroups[i], spacePointsGrouping.grid(), rMiddleSPRange, spContainer.size(), seedCollection, magCache); - if (!m_runCKF) continue; + if (!m_runCKF) + continue; if (!tracking(paramseeds, trackFinder, ckfOptions, magCache, trackCollection).isSuccess()) { warning() << "Tracking failed for this event" << endmsg; } @@ -534,9 +524,8 @@ std::tuple CKFTrackingAlg::o }; if (m_numThreads > 1) { - arena.execute([&] { - tbb::parallel_for(tbb::blocked_range(0, spacePointGroups.size()), parallelSeedingAndTracking); - }); + arena.execute( + [&] { tbb::parallel_for(tbb::blocked_range(0, spacePointGroups.size()), parallelSeedingAndTracking); }); } else { for (size_t i = 0; i < spacePointGroups.size(); ++i) { parallelSeedingAndTracking(tbb::blocked_range(i, i + 1)); @@ -553,16 +542,14 @@ std::tuple CKFTrackingAlg::o std::vector CKFTrackingAlg::findSeeds( const Acts::SeedFinder& finder, const Acts::SeedFinderOptions& finderOpts, - const auto& spacePointGroup, const SSPointGrid& grid, Acts::Range1D middleSpRange, - std::size_t mutSpDataSize, edm4hep::TrackCollection& seedCollection, - Acts::MagneticFieldProvider::Cache& magCache) const { - + const auto& spacePointGroup, const SSPointGrid& grid, Acts::Range1D middleSpRange, std::size_t mutSpDataSize, + edm4hep::TrackCollection& seedCollection, Acts::MagneticFieldProvider::Cache& magCache) const { const Acts::GeometryContext geoCtx = Acts::GeometryContext::dangerouslyDefaultConstruct(); const auto& [bottom, middle, top] = spacePointGroup; - std::vector> seeds; - std::vector paramseeds; + std::vector> seeds; + std::vector paramseeds; Acts::SeedFinder::SeedingState state; state.spacePointMutableData.resize(mutSpDataSize); @@ -573,8 +560,7 @@ std::vector CKFTrackingAlg::findSeeds( f_seeds.reserve(seeds.size()); for (const Acts::Seed& seed : seeds) { const auto& sps = seed.sp(); - f_seeds.emplace_back(*sps[0]->externalSpacePoint(), *sps[1]->externalSpacePoint(), - *sps[2]->externalSpacePoint()); + f_seeds.emplace_back(*sps[0]->externalSpacePoint(), *sps[1]->externalSpacePoint(), *sps[2]->externalSpacePoint()); } for (const auto& seed : f_seeds) { @@ -604,10 +590,9 @@ std::vector CKFTrackingAlg::findSeeds( const Acts::BoundVector& params = *optParams; float p = std::abs(1.f / params[Acts::eBoundQOverP]); - Acts::BoundMatrix cov = - ACTSTracking::makeInitialCovariance(p, m_initialTrackError_pos, m_initialTrackError_phi, - m_initialTrackError_lambda, m_initialTrackError_relP, - m_initialTrackError_time); + Acts::BoundMatrix cov = ACTSTracking::makeInitialCovariance(p, m_initialTrackError_pos, m_initialTrackError_phi, + m_initialTrackError_lambda, m_initialTrackError_relP, + m_initialTrackError_time); Acts::BoundTrackParameters paramseed(surface->getSharedPtr(), params, cov, Acts::ParticleHypothesis::pion()); paramseeds.push_back(paramseed); @@ -621,7 +606,7 @@ std::vector CKFTrackingAlg::findSeeds( } auto seedTrackState = ACTSTracking::ACTS2edm4hep_trackState(edm4hep::TrackState::AtFirstHit, paramseed, - (*hitField)[2] / Acts::UnitConstants::T); + (*hitField)[2] / Acts::UnitConstants::T); { std::lock_guard lock(m_seedMutex); @@ -643,10 +628,9 @@ std::vector CKFTrackingAlg::findSeeds( // tracking — run CKF for each seed and collect results // --------------------------------------------------------------------------- -StatusCode CKFTrackingAlg::tracking(const std::vector& paramseeds, - const CKF& trackFinder, const TrackFinderOptions& ckfOptions, - Acts::MagneticFieldProvider::Cache& magCache, - edm4hep::TrackCollection& trackCollection) const { +StatusCode CKFTrackingAlg::tracking(const std::vector& paramseeds, const CKF& trackFinder, + const TrackFinderOptions& ckfOptions, Acts::MagneticFieldProvider::Cache& magCache, + edm4hep::TrackCollection& trackCollection) const { const Acts::GeometryContext geoCtx = Acts::GeometryContext::dangerouslyDefaultConstruct(); debug() << "Starting CKF track finding with " << paramseeds.size() << " seeds." << endmsg; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3bb2c56c..ef9750c8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -96,11 +96,6 @@ add_test(NAME reco_MAIA_Gen3 ) set_test_env(reco_MAIA_Gen3) -set_tests_properties( - sim_MAIA - PROPERTIES - FIXTURES_SETUP sim_maia_fixture -) set_tests_properties( reco_MAIA reco_MAIA_Gen3 diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index d5afeadb..11383af4 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -146,11 +146,11 @@ SeedFinding_SigmaScattering=50, SeedFinding_CollisionRegion=6, SeedFinding_RadLengthPerSeed=0.1, - # Seeding layers use (volume, layer) pairs from the Gen3 / Blueprint - # geometry. The volume/layer numbering differs from the TGeo-based path - # used by ACTSSeededCKFTrackingAlg — update these once the geometry IDs - # have been inspected at runtime (e.g. via ActsTestPropagator output). - SeedingLayers=[], + # SeedingLayersCellID uses DD4hep-native (system, layer) pairs, readable + # directly from the MAIA_v0 compact XML. The values below seed on the + # first three VertexBarrel layers (system=1, layers 0–2). + # Use "*" as a wildcard, e.g. ["1", "*"] selects all VertexBarrel layers. + SeedingLayersCellID=["1", "*", "2", "1", "2", "2", "2", "3"], OutputTrackCollectionName=["AllTracks"], OutputSeedCollectionName=["SeedTracks"], InputTrackerHitCollectionName=["MergedTrackerHits"], From 971933c4d2f20613cc7e13c84c3dc3838324ba85 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 14:52:31 +0100 Subject: [PATCH 08/72] Add CellID based selection tool --- k4ActsTracking/CMakeLists.txt | 2 + .../include/k4ActsTracking/CellIDSelector.h | 49 ++++++ k4ActsTracking/src/CellIDSelector.cxx | 164 ++++++++++++++++++ test/CMakeLists.txt | 2 + test/unittests/CMakeLists.txt | 54 ++++++ test/unittests/CellIDSelectionTests.cpp | 154 ++++++++++++++++ 6 files changed, 425 insertions(+) create mode 100644 k4ActsTracking/include/k4ActsTracking/CellIDSelector.h create mode 100644 k4ActsTracking/src/CellIDSelector.cxx create mode 100644 test/unittests/CMakeLists.txt create mode 100644 test/unittests/CellIDSelectionTests.cpp diff --git a/k4ActsTracking/CMakeLists.txt b/k4ActsTracking/CMakeLists.txt index 25f99a24..d1583fed 100644 --- a/k4ActsTracking/CMakeLists.txt +++ b/k4ActsTracking/CMakeLists.txt @@ -21,7 +21,9 @@ set(ACTS_LIBRARY_TARGETS "Acts::Core") gaudi_add_library(k4ActsTracking SOURCES src/ActsGaudiLogger.cpp + src/CellIDSelector.cxx LINK + DD4hep::DDCore k4FWCore::k4FWCore ${ACTS_LIBRARY_TARGETS} ) diff --git a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h new file mode 100644 index 00000000..c37ca883 --- /dev/null +++ b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2014-2024 Key4hep-Project. + * + * This file is part of Key4hep. + * See https://key4hep.github.io/key4hep-doc/ for further info. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#ifndef K4ACTSTRACKING_CELLIDSELECTOR_H +#define K4ACTSTRACKING_CELLIDSELECTOR_H + +#include +#include + +#include +#include + +namespace k4ActsTracking { + + class CellIDSelector { + public: + struct Selector { + dd4hep::CellID mask; + dd4hep::CellID value; + }; + + CellIDSelector(const std::string& encodingString, const std::vector& selections); + + bool accept(const dd4hep::CellID cellID) const; + + std::vector getSelectionMasks(const std::string& selection) const; + + private: + dd4hep::BitFieldCoder m_decoder{}; + std::vector m_selectors{}; + }; +} // namespace k4ActsTracking + +#endif // K4ACTSTRACKING_CELLIDSELECTOR_H diff --git a/k4ActsTracking/src/CellIDSelector.cxx b/k4ActsTracking/src/CellIDSelector.cxx new file mode 100644 index 00000000..b5a25d48 --- /dev/null +++ b/k4ActsTracking/src/CellIDSelector.cxx @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2014-2024 Key4hep-Project. + * + * This file is part of Key4hep. + * See https://key4hep.github.io/key4hep-doc/ for further info. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "k4ActsTracking/CellIDSelector.h" + +#include +#include +#include +#include +#include +#include + +#include + +namespace { + auto splitString(const std::string_view str, const char delim) { + namespace rv = std::ranges::views; + + return str | rv::split(delim) | + rv::transform([](auto&& subrange) { return std::string_view(subrange.begin(), subrange.end()); }); + } + + // Implement a very simple polyfill to maintain compatibility with c++20 + template auto to_vector(R&& range) { + std::vector container; + if constexpr (std::ranges::sized_range) { + container.reserve(std::ranges::size(range)); + } + std::ranges::copy(range, std::back_inserter(container)); + return container; + } + + std::pair> getFieldAndValues(auto partialSelection) { + const auto fieldConfig = to_vector(splitString(partialSelection, ':')); + if (fieldConfig.size() != 2) { + throw std::invalid_argument(std::string(partialSelection) + "' is not a valid selection string"); + } + + const auto fieldName = std::string(fieldConfig[0]); + const auto fieldValue = fieldConfig[1]; + if (fieldValue == "*") { + return {fieldName, {}}; + } + + namespace rv = std::ranges::views; + auto layers = splitString(fieldValue, '|') | rv::transform([&](auto&& elem) { + int val; + if (std::from_chars(elem.data(), elem.data() + elem.size(), val).ec != std::errc{}) { + throw std::invalid_argument("'" + std::string(elem) + "' in '" + std::string(partialSelection) + + "' cannot be converted to an integer"); + } + return val; + }); + return {fieldName, to_vector(layers)}; + } + + struct FieldValue { + std::string name; + int value; + }; + + std::vector> cartesianProductFields(auto&& fieldsAndValues) { + // fieldsAndValues is a range with elements of shape pair>. Each element in the range represents a field and values for + // that field. What we want is each combination of {(field_i, value_i), + // (field_j, value_j), (...)} + std::vector> product{}; + + for (const auto& [field, values] : fieldsAndValues) { + std::vector thisFieldValues{}; + thisFieldValues.reserve(values.size()); + for (const int value : values) { + thisFieldValues.emplace_back(field, value); + } + + // If we are in the first field product is still empty and we simply store + // each individual value from this field there. + if (product.empty()) { + for (auto&& fv : thisFieldValues) { + product.emplace_back(std::vector{std::move(fv)}); + } + } else { + // Otherwise we create new products by attaching the new field / values + // to the existing ones and then simply replace the existing ones + std::vector> newProduct{}; + for (auto existing : product) { + for (const auto& fv : thisFieldValues) { + existing.emplace_back(fv); + newProduct.emplace_back(existing); + } + } + product = std::move(newProduct); + } + } + + return product; + } + +} // namespace + +namespace k4ActsTracking { + CellIDSelector::CellIDSelector(const std::string& encodingString, const std::vector& selections) + : m_decoder(encodingString) { + for (const auto& selection : selections) { + for (auto&& sel : getSelectionMasks(selection)) { + m_selectors.emplace_back(std::move(sel)); + } + } + } + + std::vector CellIDSelector::getSelectionMasks(const std::string& selection) const { + // Get all the field names and values where we actually have to apply a + // selection in the end. I.e. if a field is configured with '*', we filter + // it out here already since it will be applied in the selection in any + // case. + namespace rv = std::ranges::views; + auto fieldsAndValues = splitString(selection, ',') | + rv::transform([](auto&& part) { return getFieldAndValues(part); }) | + rv::filter([](auto&& v) { return !v.second.empty(); }); + + dd4hep::CellID mask = 0; + for (const auto& [field, _] : fieldsAndValues) { + mask |= m_decoder[field].mask(); + } + + std::vector selectors{}; + for (const auto& fieldsValues : cartesianProductFields(fieldsAndValues)) { + dd4hep::CellID value{}; + for (const auto& [name, val] : fieldsValues) { + m_decoder.set(value, std::string(name), val); + } + selectors.emplace_back(mask, value); + } + + return selectors; + } + + bool CellIDSelector::accept(const dd4hep::CellID cellID) const { + for (const auto& selector : m_selectors) { + // The passed CellID has to be equal to any of the configured values after + // masking irrelvant parts + if ((selector.value & selector.mask) == (cellID & selector.mask)) { + return true; + } + } + return false; + } +} // namespace k4ActsTracking diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ef9750c8..1fbf6bcb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -103,3 +103,5 @@ set_tests_properties( PROPERTIES FIXTURES_REQUIRED sim_maia_fixture ) + +add_subdirectory(unittests) diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt new file mode 100644 index 00000000..2cab5b4d --- /dev/null +++ b/test/unittests/CMakeLists.txt @@ -0,0 +1,54 @@ +set(CATCH2_MIN_VERSION 3.5.0) +if(USE_EXTERNAL_CATCH2) + if (USE_EXTERNAL_CATCH2 STREQUAL AUTO) + find_package(Catch2 ${CATCH2_MIN_VERSION}) + else() + find_package(Catch2 ${CATCH2_MIN_VERSION} REQUIRED) + endif() +endif() + +if(NOT Catch2_FOUND) + message(STATUS "Fetching local copy of Catch2 library for unit-tests...") + # Build Catch2 with the default flags, to avoid generating warnings when we + # build it + set(CXX_FLAGS_CMAKE_USED ${CMAKE_CXX_FLAGS}) + set(CMAKE_CXX_FLAGS ${CXX_FLAGS_CMAKE_DEFAULTS}) + Include(FetchContent) + FetchContent_Declare( + Catch2 + GIT_REPOSITORY https://github.com/catchorg/Catch2.git + GIT_TAG v${CATCH2_MIN_VERSION} + ) + FetchContent_MakeAvailable(Catch2) + set(CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras ${CMAKE_MODULE_PATH}) + + # Disable clang-tidy on external contents + set_target_properties(Catch2 PROPERTIES CXX_CLANG_TIDY "") + + # Hack around the fact, that the include directories are not declared as + # SYSTEM for the targets defined this way. Otherwise warnings can still occur + # in Catch2 code when templates are evaluated (which happens quite a bit) + get_target_property(CATCH2_IF_INC_DIRS Catch2 INTERFACE_INCLUDE_DIRECTORIES) + set_target_properties(Catch2 PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${CATCH2_IF_INC_DIRS}") + + # Reset the flags + set(CMAKE_CXX_FLAGS ${CXX_FLAGS_CMAKE_USED}) +else() + # Same hack as above for the system-installed Catch2: mark its include + # directories as SYSTEM to suppress warnings from Catch2 headers + get_target_property(CATCH2_IF_INC_DIRS Catch2::Catch2 INTERFACE_INCLUDE_DIRECTORIES) + set_target_properties(Catch2::Catch2 PROPERTIES INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${CATCH2_IF_INC_DIRS}") +endif() + +add_executable(unittest_k4ActsTracking CellIDSelectionTests.cpp) +target_link_libraries(unittest_k4ActsTracking PUBLIC k4ActsTracking PRIVATE Catch2::Catch2WithMain) +# Catch2 TEST_CASE/SECTION macros expand __COUNTER__ at the call site in user +# code, so marking Catch2 headers as system includes is not sufficient to +# suppress this warning, present with Clang 22 +target_compile_options(unittest_k4ActsTracking PRIVATE -Wno-c2y-extensions) + +include(Catch) +catch_discover_tests(unittest_k4ActsTracking + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} + TEST_PREFIX "UT_" # make it possible to filter easily with -R ^UT +) diff --git a/test/unittests/CellIDSelectionTests.cpp b/test/unittests/CellIDSelectionTests.cpp new file mode 100644 index 00000000..b6f123e4 --- /dev/null +++ b/test/unittests/CellIDSelectionTests.cpp @@ -0,0 +1,154 @@ + +#include "k4ActsTracking/CellIDSelector.h" + +#include +#include + +#include +#include "catch2/catch_test_macros.hpp" +#include "catch2/generators/catch_generators.hpp" +#include "catch2/generators/catch_generators_adapters.hpp" +#include "catch2/generators/catch_generators_random.hpp" +#include "catch2/matchers/catch_matchers_range_equals.hpp" + +#include +#include + +using namespace k4ActsTracking; + +TEST_CASE("CellIDSelector::accept single selection") { + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + const auto encoder = dd4hep::BitFieldCoder(encodingString); + const auto selector = CellIDSelector{encodingString, {"system:8,layer:3"}}; + + dd4hep::CellID cellID{0}; + encoder.set(cellID, "system", 8); + encoder.set(cellID, "layer", 3); + encoder.set(cellID, "side", 0); + encoder.set(cellID, "module", 5); + encoder.set(cellID, "sensor", 42); + + REQUIRE(selector.accept(cellID)); + + encoder.set(cellID, "sensor", 123); + encoder.set(cellID, "module", 12); + REQUIRE(selector.accept(cellID)); + + encoder.set(cellID, "system", 5); + REQUIRE_FALSE(selector.accept(cellID)); + + encoder.set(cellID, "system", 8); + encoder.set(cellID, "layer", 16); + REQUIRE_FALSE(selector.accept(cellID)); +} + +TEST_CASE("CellIDSelector::accept multiple selections") { + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + const auto encoder = dd4hep::BitFieldCoder(encodingString); + + const auto selector = CellIDSelector{encodingString, {"system:5,layer:1|4|5", "system:3,layer:2|6|8", "sensor:42"}}; + + dd4hep::CellID cellID{0}; + encoder.set(cellID, "side", -1); + encoder.set(cellID, "module", 5); + + encoder.set(cellID, "system", 8); + encoder.set(cellID, "layer", 4); + // Layer would match but system doesn't + REQUIRE_FALSE(selector.accept(cellID)); + + encoder.set(cellID, "sensor", 42); + encoder.set(cellID, "layer", 3); + // Nothing matches except sensor:42 from the last selection + REQUIRE(selector.accept(cellID)); + + encoder.set(cellID, "sensor", 123); + encoder.set(cellID, "system", 5); + // system:5 would match, but layers are off + REQUIRE_FALSE(selector.accept(cellID)); + encoder.set(cellID, "layer", 5); + REQUIRE(selector.accept(cellID)); + + encoder.set(cellID, "system", 3); + REQUIRE_FALSE(selector.accept(cellID)); + encoder.set(cellID, "layer", 8); + REQUIRE(selector.accept(cellID)); +} + +TEST_CASE("CellIDSelector::accept empty selection") { + const auto selector = CellIDSelector{"system:8,side:-2,layer:5,module:7,sensor:10", {}}; + const auto cellID = GENERATE(take(100, random(0UL, std::numeric_limits::max()))); + REQUIRE_FALSE(selector.accept(cellID)); +} + +TEST_CASE("CellIDSelector::getSelectionMasks") { + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + const auto selector = CellIDSelector{encodingString, {}}; + + // Selectors that select everything should not get a mask + REQUIRE(selector.getSelectionMasks("system:*").empty()); + REQUIRE(selector.getSelectionMasks("layer:*,system:*").empty()); + + constexpr dd4hep::CellID systemMask = (0x0001ULL << 8) - 1; + constexpr dd4hep::CellID layerMask = ((0x0001ULL << 5) - 1) << (8 + 2); + constexpr dd4hep::CellID sensorMask = ((0x0001ULL << 10) - 1) << (8 + 2 + 5 + 7); + + constexpr auto allValues = [](const auto& sel) { + namespace rv = std::ranges::views; + return rv::transform(sel, [](const auto& elem) { return elem.value; }); + }; + + auto sel = selector.getSelectionMasks("system:8"); + REQUIRE(sel.size() == 1); + REQUIRE(sel[0].mask == systemMask); + REQUIRE(sel[0].value == 8); + + sel = selector.getSelectionMasks("layer:2"); + REQUIRE(sel.size() == 1); + REQUIRE(sel[0].mask == layerMask); + REQUIRE(sel[0].value == ((2 << (8 + 2)))); + + sel = selector.getSelectionMasks("system:3,layer:*"); + REQUIRE(sel.size() == 1); + REQUIRE(sel[0].mask == systemMask); + REQUIRE(sel[0].value == 3); + + sel = selector.getSelectionMasks("system:4,layer:3"); + REQUIRE(sel.size() == 1); + REQUIRE(sel[0].mask == (systemMask | layerMask)); + REQUIRE(sel[0].value == (4 + (3 << (8 + 2)))); + + using Catch::Matchers::UnorderedRangeEquals; + + sel = selector.getSelectionMasks("system:3|5"); + REQUIRE(sel.size() == 2); + for (const auto s : sel) { + REQUIRE(s.mask == systemMask); + } + REQUIRE_THAT(allValues(sel), UnorderedRangeEquals(std::vector{3, 5})); + + sel = selector.getSelectionMasks("system:3|5,layer:1|12|10"); + REQUIRE(sel.size() == 6); + for (const auto s : sel) { + REQUIRE(s.mask == (systemMask | layerMask)); + } + REQUIRE_THAT(allValues(sel), UnorderedRangeEquals(std::vector{ + 3 + (1 << (8 + 2)), + 5 + (1 << (8 + 2)), + 3 + (10 << (8 + 2)), + 5 + (10 << (8 + 2)), + 3 + (12 << (8 + 2)), + 5 + (12 << (8 + 2)), + })); + + sel = selector.getSelectionMasks("system:*,layer:1|3|5,sensor:3"); + REQUIRE(sel.size() == 3); + for (const auto s : sel) { + REQUIRE(s.mask == (layerMask | sensorMask)); + } + REQUIRE_THAT(allValues(sel), UnorderedRangeEquals(std::vector{ + (1 << (8 + 2)) + (3 << (8 + 2 + 5 + 7)), + (3 << (8 + 2)) + (3 << (8 + 2 + 5 + 7)), + (5 << (8 + 2)) + (3 << (8 + 2 + 5 + 7)), + })); +} From 307f45bd9dceae87c77909be607b49b9844ac519 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 14:58:26 +0100 Subject: [PATCH 09/72] Add documentation --- .../include/k4ActsTracking/CellIDSelector.h | 79 ++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h index c37ca883..5c20c06e 100644 --- a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h +++ b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h @@ -27,17 +27,92 @@ namespace k4ActsTracking { + /// Selects detector hits by matching their DD4hep CellID against a set of + /// user-defined selection strings. + /// + /// Each selection string describes a conjunction (AND) of field constraints. + /// Multiple selection strings passed to the constructor are evaluated as a + /// disjunction (OR): a CellID is accepted if it satisfies **at least one** of + /// them. + /// + /// ## Selection string grammar + /// + /// A selection string is a comma-separated list of `:` + /// constraints, where `` must be a field name present in the DD4hep + /// encoding string supplied to the constructor. + /// + /// ### Value syntax + /// + /// | Syntax | Meaning | + /// |-----------------|----------------------------------------------| + /// | `field:N` | Field must equal the integer `N` | + /// | `field:*` | Wildcard — field is not constrained | + /// | `field:N\|M\|…` | Field must equal one of `N`, `M`, … (OR) | + /// + /// Fields that appear in the encoding string but are absent from the + /// selection are treated as wildcards. + /// + /// ### Examples + /// + /// ``` + /// // Accept hits in system 8, layer 3 (any module, sensor, side, …) + /// CellIDSelector sel(enc, {"system:8,layer:3"}); + /// + /// // Accept hits in system 5 on layers 1, 4, or 5 OR + /// // in system 3 on layers 2, 6, or 8 OR + /// // any hit whose sensor field equals 42 + /// CellIDSelector sel(enc, {"system:5,layer:1|4|5", + /// "system:3,layer:2|6|8", + /// "sensor:42"}); + /// + /// // Wildcard: system is unconstrained, layer must be 3 + /// CellIDSelector sel(enc, {"system:*,layer:3"}); + /// + /// // Empty selection list — rejects every CellID + /// CellIDSelector sel(enc, {}); + /// ``` + /// + /// ### Expansion to (mask, value) pairs + /// + /// Internally each selection string is expanded into one or more + /// `Selector{mask, value}` pairs via the Cartesian product of the + /// per-field value lists. A CellID passes the selector when + /// `(cellID & mask) == (value & mask)`. Wildcard fields contribute + /// neither to `mask` nor to `value`. + /// + /// A selection string with only wildcard fields produces **no** selectors + /// and therefore never accepts any CellID on its own. class CellIDSelector { public: + /// A single bitmask/value pair produced from one element of the Cartesian + /// product expansion of a selection string. struct Selector { - dd4hep::CellID mask; - dd4hep::CellID value; + dd4hep::CellID mask; ///< Bits that are relevant for this selector + dd4hep::CellID value; ///< Expected bit pattern after masking }; + /// Construct a selector from a DD4hep encoding string and a list of + /// selection strings. + /// + /// @param encodingString DD4hep BitFieldCoder descriptor, e.g. + /// `"system:8,side:-2,layer:5,module:7,sensor:10"` + /// @param selections List of selection strings (OR-ed together). + /// An empty list rejects every CellID. + /// @throws std::invalid_argument if a selection string is malformed or + /// references an unknown field. CellIDSelector(const std::string& encodingString, const std::vector& selections); + /// Return `true` if @p cellID satisfies at least one configured selector. bool accept(const dd4hep::CellID cellID) const; + /// Expand a single selection string into its `Selector` pairs. + /// + /// Useful for inspection and testing. The returned vector contains one + /// entry per element of the Cartesian product of all non-wildcard field + /// value lists. An all-wildcard selection string returns an empty vector. + /// + /// @param selection A single selection string (see class-level grammar). + /// @throws std::invalid_argument if the string is malformed. std::vector getSelectionMasks(const std::string& selection) const; private: From ddbd518dd24116962da373cb3e5c471e6a308a7e Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 15:09:08 +0100 Subject: [PATCH 10/72] Ensure that wildcard only selectors select everything --- .../include/k4ActsTracking/CellIDSelector.h | 5 ++--- k4ActsTracking/src/CellIDSelector.cxx | 11 ++++++++-- test/unittests/CellIDSelectionTests.cpp | 20 +++++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h index 5c20c06e..8a53e531 100644 --- a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h +++ b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h @@ -67,6 +67,8 @@ namespace k4ActsTracking { /// /// // Wildcard: system is unconstrained, layer must be 3 /// CellIDSelector sel(enc, {"system:*,layer:3"}); + /// // This is equivalent to + /// CellIDSelector sel(enc, {"layer:3"}); /// /// // Empty selection list — rejects every CellID /// CellIDSelector sel(enc, {}); @@ -79,9 +81,6 @@ namespace k4ActsTracking { /// per-field value lists. A CellID passes the selector when /// `(cellID & mask) == (value & mask)`. Wildcard fields contribute /// neither to `mask` nor to `value`. - /// - /// A selection string with only wildcard fields produces **no** selectors - /// and therefore never accepts any CellID on its own. class CellIDSelector { public: /// A single bitmask/value pair produced from one element of the Cartesian diff --git a/k4ActsTracking/src/CellIDSelector.cxx b/k4ActsTracking/src/CellIDSelector.cxx index b5a25d48..eeb8f10f 100644 --- a/k4ActsTracking/src/CellIDSelector.cxx +++ b/k4ActsTracking/src/CellIDSelector.cxx @@ -118,8 +118,15 @@ namespace k4ActsTracking { CellIDSelector::CellIDSelector(const std::string& encodingString, const std::vector& selections) : m_decoder(encodingString) { for (const auto& selection : selections) { - for (auto&& sel : getSelectionMasks(selection)) { - m_selectors.emplace_back(std::move(sel)); + auto masks = getSelectionMasks(selection); + if (masks.empty()) { + // All fields are wildcards: this selection accepts every CellID, + // represented by a zero mask (no bits to check). + m_selectors.emplace_back(0ULL, 0ULL); + } else { + for (auto&& sel : masks) { + m_selectors.emplace_back(std::move(sel)); + } } } } diff --git a/test/unittests/CellIDSelectionTests.cpp b/test/unittests/CellIDSelectionTests.cpp index b6f123e4..798ec13d 100644 --- a/test/unittests/CellIDSelectionTests.cpp +++ b/test/unittests/CellIDSelectionTests.cpp @@ -81,6 +81,26 @@ TEST_CASE("CellIDSelector::accept empty selection") { REQUIRE_FALSE(selector.accept(cellID)); } +TEST_CASE("CellIDSelector::accept wildcard selection") { + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + const auto encoder = dd4hep::BitFieldCoder(encodingString); + + // "system:*" means any system value - should accept all CellIDs + const auto selectorAll = CellIDSelector{encodingString, {"system:*"}}; + const auto cellID = GENERATE(take(100, random(0UL, std::numeric_limits::max()))); + REQUIRE(selectorAll.accept(cellID)); + + // "system:*,layer:3" means any system but layer must be 3 + dd4hep::CellID specific{0}; + encoder.set(specific, "layer", 3); + encoder.set(specific, "system", 7); // any system value + const auto selectorLayerOnly = CellIDSelector{encodingString, {"system:*,layer:3"}}; + REQUIRE(selectorLayerOnly.accept(specific)); + + encoder.set(specific, "layer", 5); + REQUIRE_FALSE(selectorLayerOnly.accept(specific)); +} + TEST_CASE("CellIDSelector::getSelectionMasks") { const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; const auto selector = CellIDSelector{encodingString, {}}; From cc89b14780e9cadd46c1edfc65d6c4ee65a7161c Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 15:22:47 +0100 Subject: [PATCH 11/72] Add tests to ensure exceptions are thrown --- test/unittests/CellIDSelectionTests.cpp | 47 +++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test/unittests/CellIDSelectionTests.cpp b/test/unittests/CellIDSelectionTests.cpp index 798ec13d..2bd49f68 100644 --- a/test/unittests/CellIDSelectionTests.cpp +++ b/test/unittests/CellIDSelectionTests.cpp @@ -172,3 +172,50 @@ TEST_CASE("CellIDSelector::getSelectionMasks") { (5 << (8 + 2)) + (3 << (8 + 2 + 5 + 7)), })); } + +TEST_CASE("CellIDSelector failure modes: malformed selection strings") { + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + + // No colon separator: "field:value" format is required + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system5"}), std::invalid_argument); + + // Multiple colons make the split ambiguous (field name contains ":") + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:3:5"}), std::invalid_argument); + + // Non-integer value that cannot be parsed + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:abc"}), std::invalid_argument); + + // Empty value after colon (no integer, not a wildcard) + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:"}), std::invalid_argument); + + // Empty string as selection + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {""}), std::invalid_argument); + + // Pipe-separated list with a non-integer entry + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:1|abc|3"}), std::invalid_argument); + + // Pipe-separated list with an empty entry + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:1|"}), std::invalid_argument); + + // Multi-field: valid first field, non-integer value in second + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:3,layer:abc"}), std::invalid_argument); + + // Multi-field: valid first field, missing colon in second + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:3,layer3"}), std::invalid_argument); + + // Multi-field: trailing comma produces an empty token for the second field + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:3,"}), std::invalid_argument); + + // Multi-field: leading comma produces an empty token for the first field + REQUIRE_THROWS_AS(CellIDSelector(encodingString, {",layer:3"}), std::invalid_argument); +} + +TEST_CASE("CellIDSelector failure modes: unknown field name") { + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + + // Field name not present in the encoding string + REQUIRE_THROWS(CellIDSelector(encodingString, {"nonexistent:5"})); + + // Unknown field among valid ones in the same selection + REQUIRE_THROWS(CellIDSelector(encodingString, {"system:3,typo:1"})); +} From 496ac119d27d5478557e9409682dba510aa0d6f5 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 15:32:56 +0100 Subject: [PATCH 12/72] Add more tests and checks for potential failure modes --- .../include/k4ActsTracking/CellIDSelector.h | 4 ++- k4ActsTracking/src/CellIDSelector.cxx | 7 ++++ test/unittests/CellIDSelectionTests.cpp | 36 +++++++++++++++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h index 8a53e531..43af2590 100644 --- a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h +++ b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h @@ -50,7 +50,9 @@ namespace k4ActsTracking { /// | `field:N\|M\|…` | Field must equal one of `N`, `M`, … (OR) | /// /// Fields that appear in the encoding string but are absent from the - /// selection are treated as wildcards. + /// selection are treated as wildcards. Specifying the same field more than + /// once in a single selection string is not detected as an error: only the + /// last occurrence takes effect. /// /// ### Examples /// diff --git a/k4ActsTracking/src/CellIDSelector.cxx b/k4ActsTracking/src/CellIDSelector.cxx index eeb8f10f..628d9fbd 100644 --- a/k4ActsTracking/src/CellIDSelector.cxx +++ b/k4ActsTracking/src/CellIDSelector.cxx @@ -54,6 +54,10 @@ namespace { const auto fieldName = std::string(fieldConfig[0]); const auto fieldValue = fieldConfig[1]; + if (fieldValue.empty()) { + throw std::invalid_argument("'" + std::string(partialSelection) + + "' has an empty value — use '*' for a wildcard"); + } if (fieldValue == "*") { return {fieldName, {}}; } @@ -132,6 +136,9 @@ namespace k4ActsTracking { } std::vector CellIDSelector::getSelectionMasks(const std::string& selection) const { + if (selection.empty()) { + throw std::invalid_argument("selection string must not be empty"); + } // Get all the field names and values where we actually have to apply a // selection in the end. I.e. if a field is configured with '*', we filter // it out here already since it will be applied in the selection in any diff --git a/test/unittests/CellIDSelectionTests.cpp b/test/unittests/CellIDSelectionTests.cpp index 2bd49f68..51f95f88 100644 --- a/test/unittests/CellIDSelectionTests.cpp +++ b/test/unittests/CellIDSelectionTests.cpp @@ -185,10 +185,10 @@ TEST_CASE("CellIDSelector failure modes: malformed selection strings") { // Non-integer value that cannot be parsed REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:abc"}), std::invalid_argument); - // Empty value after colon (no integer, not a wildcard) + // Empty value after colon is not a valid wildcard — use '*' explicitly REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:"}), std::invalid_argument); - // Empty string as selection + // Empty string as selection is not valid REQUIRE_THROWS_AS(CellIDSelector(encodingString, {""}), std::invalid_argument); // Pipe-separated list with a non-integer entry @@ -219,3 +219,35 @@ TEST_CASE("CellIDSelector failure modes: unknown field name") { // Unknown field among valid ones in the same selection REQUIRE_THROWS(CellIDSelector(encodingString, {"system:3,typo:1"})); } + +TEST_CASE("CellIDSelector failure modes: value overflow throws") { + // system is 8 bits wide (values 0-255). DD4hep's BitFieldCoder::set is + // range-checked: specifying 300 throws rather than truncating silently. + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + REQUIRE_THROWS(CellIDSelector(encodingString, {"system:300"})); + + // Same for a value in a pipe list that is in range — succeeds. + REQUIRE_NOTHROW(CellIDSelector(encodingString, {"system:255"})); + REQUIRE_NOTHROW(CellIDSelector(encodingString, {"system:0|255"})); + + // Out-of-range value inside a pipe list also throws. + REQUIRE_THROWS(CellIDSelector(encodingString, {"system:1|300"})); +} + +TEST_CASE("CellIDSelector failure modes: duplicate field uses last value") { + // "system:3,system:5" contains system twice. The Cartesian product expansion + // produces one combination {(system,3),(system,5)} and the second set() call + // overwrites the first, so the effective selector is system==5 only. + const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; + const auto encoder = dd4hep::BitFieldCoder(encodingString); + const auto selector = CellIDSelector{encodingString, {"system:3,system:5"}}; + + dd4hep::CellID cellID{0}; + encoder.set(cellID, "system", 5); + // system:5 matches because the second value wins + REQUIRE(selector.accept(cellID)); + + encoder.set(cellID, "system", 3); + // system:3 does NOT match even though it appears in the selection string + REQUIRE_FALSE(selector.accept(cellID)); +} From 31b372b266da7c5e91abff62603f4e76370ebe66 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 17:12:52 +0100 Subject: [PATCH 13/72] Use the new CellIDSelector for the new CKF algorithm --- .../include/k4ActsTracking/CKFTracking.hxx | 95 +------------------ .../src/components/CKFTrackingAlg.cpp | 18 ++-- test/options/MAIA_CKFTrackingAlg.py | 10 +- 3 files changed, 17 insertions(+), 106 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx b/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx index 67be4a09..03817753 100644 --- a/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx +++ b/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx @@ -21,104 +21,11 @@ // ACTS #include -// ACTSTracking -#include "k4ActsTracking/GeometryIdSelector.hxx" -#include "k4ActsTracking/IActsGeoSvc.h" -#include "k4ActsTracking/Measurement.hxx" -#include "k4ActsTracking/SeedSpacePoint.hxx" -#include "k4ActsTracking/SourceLink.hxx" - -// DD4hep -#include - // Standard #include -#include -#include -#include -#include -#include namespace ACTSTracking { - //! Parse seeding layer strings into a geometry ID selector - /** - * Parses a flat list of alternating volume/layer string tokens into an - * \c Acts::GeometryIdentifier selection. Use "*" as a wildcard for either - * the volume or the layer component. - * - * \param seedingLayers Flat vector of (volume, layer) string pairs. - * \return Geometry ID selector for seed-layer filtering. - * \throws std::runtime_error if the vector has an odd number of entries. - */ - inline GeometryIdSelector parseSeedingLayers(const std::vector& seedingLayers) { - std::vector layers; - std::copy_if(seedingLayers.begin(), seedingLayers.end(), std::back_inserter(layers), - [](const std::string& s) { return !s.empty(); }); - - if (layers.size() % 2 != 0) { - throw std::runtime_error("SeedingLayers needs an even number of entries"); - } - - std::vector geoSelection; - for (std::size_t i = 0; i < layers.size(); i += 2) { - Acts::GeometryIdentifier geoid; - if (layers[i] != "*") // volume - geoid = geoid.withVolume(std::stoi(layers[i])); - if (layers[i + 1] != "*") // layer - geoid = geoid.withLayer(std::stoi(layers[i + 1])); - geoSelection.push_back(geoid); - } - - return GeometryIdSelector(geoSelection); - } - - //! Build a set of CellIDs whose decoded (system, layer) fields match any of the user-specified pairs - /** - * Iterates over \p cellIdMap (the full CellID→Surface map from IActsGeoSvc) and returns - * those CellIDs whose \c system and \c layer fields — decoded with a BitFieldCoder built - * from \p encodingString — match at least one entry in the flat \p layers list. - * - * The \p layers list must have an even number of entries interpreted as - * alternating (system, layer) string pairs. Use \c "*" as a wildcard. - * - * \param layers Flat vector of (system, layer) string pairs. - * \param cellIdMap The CellID-to-surface map from IActsGeoSvc::cellIdToSurfaceMap(). - * \param encodingString DD4hep BitFieldCoder encoding string from IActsGeoSvc::cellIDEncodingString(). - * \return Set of matching CellIDs for use in O(1) seed space-point filtering. - * \throws std::runtime_error if the vector has an odd number of entries. - */ - inline std::unordered_set parseSeedingLayersCellID( - const std::vector& layers, - const IActsGeoSvc::CellIDSurfaceMap& cellIdMap, - const std::string& encodingString) { - if (layers.size() % 2 != 0) - throw std::runtime_error("SeedingLayersCellID needs an even number of entries"); - - // Parse (system, layer) pairs; -1 means wildcard - std::vector> selection; - for (std::size_t i = 0; i < layers.size(); i += 2) { - const int64_t sys = (layers[i] == "*") ? -1 : std::stoi(layers[i]); - const int64_t lay = (layers[i + 1] == "*") ? -1 : std::stoi(layers[i + 1]); - selection.emplace_back(sys, lay); - } - - dd4hep::DDSegmentation::BitFieldCoder decoder{encodingString}; - std::unordered_set seedCellIDs; - for (const auto& [cellID, surface] : cellIdMap) { - const int64_t sys = decoder.get(cellID, "system"); - const int64_t lay = decoder.get(cellID, "layer"); - for (const auto& [reqSys, reqLay] : selection) { - if ((reqSys == -1 || sys == reqSys) && (reqLay == -1 || lay == reqLay)) { - seedCellIDs.insert(cellID); - break; - } - } - } - - return seedCellIDs; - } - //! Build a diagonal initial track covariance matrix from per-parameter error estimates /** * \param p Absolute momentum [ACTS units]. @@ -130,7 +37,7 @@ namespace ACTSTracking { * \return 5×5 diagonal bound covariance matrix. */ inline Acts::BoundMatrix makeInitialCovariance(double p, double errPos, double errPhi, double errLambda, - double errRelP, double errTime) { + double errRelP, double errTime) { Acts::BoundMatrix cov = Acts::BoundMatrix::Zero(); cov(Acts::eBoundLoc0, Acts::eBoundLoc0) = std::pow(errPos, 2); cov(Acts::eBoundLoc1, Acts::eBoundLoc1) = std::pow(errPos, 2); diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index 9b33d1c1..3d453f2c 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -19,6 +19,7 @@ // k4ActsTracking #include "k4ActsTracking/CKFTracking.hxx" +#include "k4ActsTracking/CellIDSelector.h" #include "k4ActsTracking/Helpers.hxx" #include "k4ActsTracking/IActsGeoSvc.h" #include "k4ActsTracking/Measurement.hxx" @@ -78,8 +79,8 @@ #include #include #include +#include #include -#include #include using namespace Acts::UnitLiterals; @@ -198,9 +199,10 @@ struct CKFTrackingAlg final ///@{ Gaudi::Property> m_seedingLayersCellID{ this, "SeedingLayersCellID", m_default_empty_vec, - "DD4hep (system, layer) pairs for seed space-point selection. " - "Flat list of alternating system and layer values; use \"*\" as wildcard."}; - std::unordered_set m_seedCellIDSet; + "CellIDSelector selection strings for seed space-point filtering. " + "Each entry is a comma-separated list of field:value constraints (e.g. \"system:1,layer:2|3\"). " + "Multiple entries are OR-ed together. Omitted fields act as wildcards."}; + std::optional m_seedSelector; ///@} /// @name Multi-threading @@ -234,11 +236,13 @@ CKFTrackingAlg::CKFTrackingAlg(const std::string& name, ISvcLocator* svcLoc) // --------------------------------------------------------------------------- StatusCode CKFTrackingAlg::initialize() { + debug() << "CKFTracking::initialize()" << endmsg; m_actsGeoSvc = svcLoc()->service("ActsGeoSvc"); K4_GAUDI_CHECK(m_actsGeoSvc); - m_seedCellIDSet = ACTSTracking::parseSeedingLayersCellID( - m_seedingLayersCellID.value(), m_actsGeoSvc->cellIdToSurfaceMap(), m_actsGeoSvc->cellIDEncodingString()); + debug() << "Creating SeedSelector" << endmsg; + m_seedSelector.emplace(m_actsGeoSvc->cellIDEncodingString(), m_seedingLayersCellID.value()); + debug() << "Done" << endmsg; // Apply deltaR fallback defaults if (m_seedFinding_deltaRMinTop == 0.f) @@ -343,7 +347,7 @@ std::tuple CKFTrackingAlg::o debug() << "Seed selection for spacepoint w/ cellId: " << decoder.valueString(hitPair.second.getCellID()) << endmsg; // Create space point for seeding if this surface is selected - if (m_seedCellIDSet.count(hitPair.second.getCellID())) { + if (m_seedSelector->accept(hitPair.second.getCellID())) { debug() << "Accepted" << endmsg; Acts::RotationMatrix3 rotLocalToGlobal = surface->referenceFrame(geoCtx, globalPos, {0, 0, 0}); diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index 11383af4..f8024e61 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -146,11 +146,11 @@ SeedFinding_SigmaScattering=50, SeedFinding_CollisionRegion=6, SeedFinding_RadLengthPerSeed=0.1, - # SeedingLayersCellID uses DD4hep-native (system, layer) pairs, readable - # directly from the MAIA_v0 compact XML. The values below seed on the - # first three VertexBarrel layers (system=1, layers 0–2). - # Use "*" as a wildcard, e.g. ["1", "*"] selects all VertexBarrel layers. - SeedingLayersCellID=["1", "*", "2", "1", "2", "2", "2", "3"], + # SeedingLayersCellID uses CellIDSelector selection strings, where each + # entry is a comma-separated list of field:value constraints read directly + # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. + # Omitted fields act as wildcards; "|" separates multiple values for a field. + SeedingLayersCellID=["system:1", "system:2,layer:1|2|3"], OutputTrackCollectionName=["AllTracks"], OutputSeedCollectionName=["SeedTracks"], InputTrackerHitCollectionName=["MergedTrackerHits"], From 205827300132cbd619103dca2f1baf9a206f855f Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 17:22:54 +0100 Subject: [PATCH 14/72] Remove wildcard selection possibility from CellIDSelector --- .../include/k4ActsTracking/CellIDSelector.h | 16 +++---- k4ActsTracking/src/CellIDSelector.cxx | 26 ++--------- test/unittests/CellIDSelectionTests.cpp | 43 +------------------ 3 files changed, 10 insertions(+), 75 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h index 43af2590..cc4d5a06 100644 --- a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h +++ b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h @@ -46,11 +46,10 @@ namespace k4ActsTracking { /// | Syntax | Meaning | /// |-----------------|----------------------------------------------| /// | `field:N` | Field must equal the integer `N` | - /// | `field:*` | Wildcard — field is not constrained | /// | `field:N\|M\|…` | Field must equal one of `N`, `M`, … (OR) | /// /// Fields that appear in the encoding string but are absent from the - /// selection are treated as wildcards. Specifying the same field more than + /// selection are unconstrained. Specifying the same field more than /// once in a single selection string is not detected as an error: only the /// last occurrence takes effect. /// @@ -67,11 +66,6 @@ namespace k4ActsTracking { /// "system:3,layer:2|6|8", /// "sensor:42"}); /// - /// // Wildcard: system is unconstrained, layer must be 3 - /// CellIDSelector sel(enc, {"system:*,layer:3"}); - /// // This is equivalent to - /// CellIDSelector sel(enc, {"layer:3"}); - /// /// // Empty selection list — rejects every CellID /// CellIDSelector sel(enc, {}); /// ``` @@ -81,8 +75,8 @@ namespace k4ActsTracking { /// Internally each selection string is expanded into one or more /// `Selector{mask, value}` pairs via the Cartesian product of the /// per-field value lists. A CellID passes the selector when - /// `(cellID & mask) == (value & mask)`. Wildcard fields contribute - /// neither to `mask` nor to `value`. + /// `(cellID & mask) == (value & mask)`. Fields absent from the selection + /// contribute neither to `mask` nor to `value`. class CellIDSelector { public: /// A single bitmask/value pair produced from one element of the Cartesian @@ -109,8 +103,8 @@ namespace k4ActsTracking { /// Expand a single selection string into its `Selector` pairs. /// /// Useful for inspection and testing. The returned vector contains one - /// entry per element of the Cartesian product of all non-wildcard field - /// value lists. An all-wildcard selection string returns an empty vector. + /// entry per element of the Cartesian product of all constrained field + /// value lists. /// /// @param selection A single selection string (see class-level grammar). /// @throws std::invalid_argument if the string is malformed. diff --git a/k4ActsTracking/src/CellIDSelector.cxx b/k4ActsTracking/src/CellIDSelector.cxx index 628d9fbd..47db0cb0 100644 --- a/k4ActsTracking/src/CellIDSelector.cxx +++ b/k4ActsTracking/src/CellIDSelector.cxx @@ -26,8 +26,6 @@ #include #include -#include - namespace { auto splitString(const std::string_view str, const char delim) { namespace rv = std::ranges::views; @@ -55,11 +53,7 @@ namespace { const auto fieldName = std::string(fieldConfig[0]); const auto fieldValue = fieldConfig[1]; if (fieldValue.empty()) { - throw std::invalid_argument("'" + std::string(partialSelection) + - "' has an empty value — use '*' for a wildcard"); - } - if (fieldValue == "*") { - return {fieldName, {}}; + throw std::invalid_argument("'" + std::string(partialSelection) + "' has an empty value"); } namespace rv = std::ranges::views; @@ -122,15 +116,8 @@ namespace k4ActsTracking { CellIDSelector::CellIDSelector(const std::string& encodingString, const std::vector& selections) : m_decoder(encodingString) { for (const auto& selection : selections) { - auto masks = getSelectionMasks(selection); - if (masks.empty()) { - // All fields are wildcards: this selection accepts every CellID, - // represented by a zero mask (no bits to check). - m_selectors.emplace_back(0ULL, 0ULL); - } else { - for (auto&& sel : masks) { - m_selectors.emplace_back(std::move(sel)); - } + for (auto&& sel : getSelectionMasks(selection)) { + m_selectors.emplace_back(std::move(sel)); } } } @@ -139,14 +126,9 @@ namespace k4ActsTracking { if (selection.empty()) { throw std::invalid_argument("selection string must not be empty"); } - // Get all the field names and values where we actually have to apply a - // selection in the end. I.e. if a field is configured with '*', we filter - // it out here already since it will be applied in the selection in any - // case. namespace rv = std::ranges::views; auto fieldsAndValues = splitString(selection, ',') | - rv::transform([](auto&& part) { return getFieldAndValues(part); }) | - rv::filter([](auto&& v) { return !v.second.empty(); }); + rv::transform([](auto&& part) { return getFieldAndValues(part); }); dd4hep::CellID mask = 0; for (const auto& [field, _] : fieldsAndValues) { diff --git a/test/unittests/CellIDSelectionTests.cpp b/test/unittests/CellIDSelectionTests.cpp index 51f95f88..f8de40fb 100644 --- a/test/unittests/CellIDSelectionTests.cpp +++ b/test/unittests/CellIDSelectionTests.cpp @@ -81,37 +81,12 @@ TEST_CASE("CellIDSelector::accept empty selection") { REQUIRE_FALSE(selector.accept(cellID)); } -TEST_CASE("CellIDSelector::accept wildcard selection") { - const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; - const auto encoder = dd4hep::BitFieldCoder(encodingString); - - // "system:*" means any system value - should accept all CellIDs - const auto selectorAll = CellIDSelector{encodingString, {"system:*"}}; - const auto cellID = GENERATE(take(100, random(0UL, std::numeric_limits::max()))); - REQUIRE(selectorAll.accept(cellID)); - - // "system:*,layer:3" means any system but layer must be 3 - dd4hep::CellID specific{0}; - encoder.set(specific, "layer", 3); - encoder.set(specific, "system", 7); // any system value - const auto selectorLayerOnly = CellIDSelector{encodingString, {"system:*,layer:3"}}; - REQUIRE(selectorLayerOnly.accept(specific)); - - encoder.set(specific, "layer", 5); - REQUIRE_FALSE(selectorLayerOnly.accept(specific)); -} - TEST_CASE("CellIDSelector::getSelectionMasks") { const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; const auto selector = CellIDSelector{encodingString, {}}; - // Selectors that select everything should not get a mask - REQUIRE(selector.getSelectionMasks("system:*").empty()); - REQUIRE(selector.getSelectionMasks("layer:*,system:*").empty()); - constexpr dd4hep::CellID systemMask = (0x0001ULL << 8) - 1; constexpr dd4hep::CellID layerMask = ((0x0001ULL << 5) - 1) << (8 + 2); - constexpr dd4hep::CellID sensorMask = ((0x0001ULL << 10) - 1) << (8 + 2 + 5 + 7); constexpr auto allValues = [](const auto& sel) { namespace rv = std::ranges::views; @@ -128,11 +103,6 @@ TEST_CASE("CellIDSelector::getSelectionMasks") { REQUIRE(sel[0].mask == layerMask); REQUIRE(sel[0].value == ((2 << (8 + 2)))); - sel = selector.getSelectionMasks("system:3,layer:*"); - REQUIRE(sel.size() == 1); - REQUIRE(sel[0].mask == systemMask); - REQUIRE(sel[0].value == 3); - sel = selector.getSelectionMasks("system:4,layer:3"); REQUIRE(sel.size() == 1); REQUIRE(sel[0].mask == (systemMask | layerMask)); @@ -160,17 +130,6 @@ TEST_CASE("CellIDSelector::getSelectionMasks") { 3 + (12 << (8 + 2)), 5 + (12 << (8 + 2)), })); - - sel = selector.getSelectionMasks("system:*,layer:1|3|5,sensor:3"); - REQUIRE(sel.size() == 3); - for (const auto s : sel) { - REQUIRE(s.mask == (layerMask | sensorMask)); - } - REQUIRE_THAT(allValues(sel), UnorderedRangeEquals(std::vector{ - (1 << (8 + 2)) + (3 << (8 + 2 + 5 + 7)), - (3 << (8 + 2)) + (3 << (8 + 2 + 5 + 7)), - (5 << (8 + 2)) + (3 << (8 + 2 + 5 + 7)), - })); } TEST_CASE("CellIDSelector failure modes: malformed selection strings") { @@ -185,7 +144,7 @@ TEST_CASE("CellIDSelector failure modes: malformed selection strings") { // Non-integer value that cannot be parsed REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:abc"}), std::invalid_argument); - // Empty value after colon is not a valid wildcard — use '*' explicitly + // Empty value after colon REQUIRE_THROWS_AS(CellIDSelector(encodingString, {"system:"}), std::invalid_argument); // Empty string as selection is not valid From f6499bdfcfb9f3e8baf36842d43d3f6b678b68a8 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 17:38:58 +0100 Subject: [PATCH 15/72] Reduce internal state of selector --- .../include/k4ActsTracking/CellIDSelector.h | 10 +++++++-- k4ActsTracking/src/CellIDSelector.cxx | 22 ++++++++++--------- test/unittests/CellIDSelectionTests.cpp | 11 +++++----- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h index cc4d5a06..c5cfc6cd 100644 --- a/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h +++ b/k4ActsTracking/include/k4ActsTracking/CellIDSelector.h @@ -86,6 +86,13 @@ namespace k4ActsTracking { dd4hep::CellID value; ///< Expected bit pattern after masking }; + CellIDSelector() = default; + CellIDSelector(const CellIDSelector&) = default; + CellIDSelector& operator=(const CellIDSelector&) = default; + CellIDSelector(CellIDSelector&&) = default; + CellIDSelector& operator=(CellIDSelector&&) = default; + ~CellIDSelector() = default; + /// Construct a selector from a DD4hep encoding string and a list of /// selection strings. /// @@ -108,10 +115,9 @@ namespace k4ActsTracking { /// /// @param selection A single selection string (see class-level grammar). /// @throws std::invalid_argument if the string is malformed. - std::vector getSelectionMasks(const std::string& selection) const; + static std::vector getSelectionMasks(const std::string& selection, const dd4hep::BitFieldCoder& decoder); private: - dd4hep::BitFieldCoder m_decoder{}; std::vector m_selectors{}; }; } // namespace k4ActsTracking diff --git a/k4ActsTracking/src/CellIDSelector.cxx b/k4ActsTracking/src/CellIDSelector.cxx index 47db0cb0..cd6bf582 100644 --- a/k4ActsTracking/src/CellIDSelector.cxx +++ b/k4ActsTracking/src/CellIDSelector.cxx @@ -44,7 +44,7 @@ namespace { return container; } - std::pair> getFieldAndValues(auto partialSelection) { + std::pair> getFieldAndValues(std::string_view partialSelection) { const auto fieldConfig = to_vector(splitString(partialSelection, ':')); if (fieldConfig.size() != 2) { throw std::invalid_argument(std::string(partialSelection) + "' is not a valid selection string"); @@ -113,33 +113,35 @@ namespace { } // namespace namespace k4ActsTracking { - CellIDSelector::CellIDSelector(const std::string& encodingString, const std::vector& selections) - : m_decoder(encodingString) { + CellIDSelector::CellIDSelector(const std::string& encodingString, const std::vector& selections) { + dd4hep::BitFieldCoder decoder{encodingString}; + for (const auto& selection : selections) { - for (auto&& sel : getSelectionMasks(selection)) { + for (auto&& sel : getSelectionMasks(selection, decoder)) { m_selectors.emplace_back(std::move(sel)); } } } - std::vector CellIDSelector::getSelectionMasks(const std::string& selection) const { + std::vector CellIDSelector::getSelectionMasks(const std::string& selection, + const dd4hep::BitFieldCoder& decoder) { if (selection.empty()) { throw std::invalid_argument("selection string must not be empty"); } - namespace rv = std::ranges::views; - auto fieldsAndValues = splitString(selection, ',') | - rv::transform([](auto&& part) { return getFieldAndValues(part); }); + namespace rv = std::ranges::views; + auto fieldsAndValues = + splitString(selection, ',') | rv::transform([](auto&& part) { return getFieldAndValues(part); }); dd4hep::CellID mask = 0; for (const auto& [field, _] : fieldsAndValues) { - mask |= m_decoder[field].mask(); + mask |= decoder[field].mask(); } std::vector selectors{}; for (const auto& fieldsValues : cartesianProductFields(fieldsAndValues)) { dd4hep::CellID value{}; for (const auto& [name, val] : fieldsValues) { - m_decoder.set(value, std::string(name), val); + decoder.set(value, std::string(name), val); } selectors.emplace_back(mask, value); } diff --git a/test/unittests/CellIDSelectionTests.cpp b/test/unittests/CellIDSelectionTests.cpp index f8de40fb..cabe6b58 100644 --- a/test/unittests/CellIDSelectionTests.cpp +++ b/test/unittests/CellIDSelectionTests.cpp @@ -83,7 +83,6 @@ TEST_CASE("CellIDSelector::accept empty selection") { TEST_CASE("CellIDSelector::getSelectionMasks") { const std::string encodingString = "system:8,side:-2,layer:5,module:7,sensor:10"; - const auto selector = CellIDSelector{encodingString, {}}; constexpr dd4hep::CellID systemMask = (0x0001ULL << 8) - 1; constexpr dd4hep::CellID layerMask = ((0x0001ULL << 5) - 1) << (8 + 2); @@ -93,31 +92,31 @@ TEST_CASE("CellIDSelector::getSelectionMasks") { return rv::transform(sel, [](const auto& elem) { return elem.value; }); }; - auto sel = selector.getSelectionMasks("system:8"); + auto sel = CellIDSelector::getSelectionMasks("system:8", encodingString); REQUIRE(sel.size() == 1); REQUIRE(sel[0].mask == systemMask); REQUIRE(sel[0].value == 8); - sel = selector.getSelectionMasks("layer:2"); + sel = CellIDSelector::getSelectionMasks("layer:2", encodingString); REQUIRE(sel.size() == 1); REQUIRE(sel[0].mask == layerMask); REQUIRE(sel[0].value == ((2 << (8 + 2)))); - sel = selector.getSelectionMasks("system:4,layer:3"); + sel = CellIDSelector::getSelectionMasks("system:4,layer:3", encodingString); REQUIRE(sel.size() == 1); REQUIRE(sel[0].mask == (systemMask | layerMask)); REQUIRE(sel[0].value == (4 + (3 << (8 + 2)))); using Catch::Matchers::UnorderedRangeEquals; - sel = selector.getSelectionMasks("system:3|5"); + sel = CellIDSelector::getSelectionMasks("system:3|5", encodingString); REQUIRE(sel.size() == 2); for (const auto s : sel) { REQUIRE(s.mask == systemMask); } REQUIRE_THAT(allValues(sel), UnorderedRangeEquals(std::vector{3, 5})); - sel = selector.getSelectionMasks("system:3|5,layer:1|12|10"); + sel = CellIDSelector::getSelectionMasks("system:3|5,layer:1|12|10", encodingString); REQUIRE(sel.size() == 6); for (const auto s : sel) { REQUIRE(s.mask == (systemMask | layerMask)); From 466ab155fdfd4ed21c909b4d8ed4600649767ed8 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 18 Mar 2026 17:39:17 +0100 Subject: [PATCH 16/72] Store the selector on the stack and rename property --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 12 +++++++----- test/options/MAIA_CKFTrackingAlg.py | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index 3d453f2c..cb031aa5 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -197,12 +197,11 @@ struct CKFTrackingAlg final /// @name Seeding layer selection ///@{ - Gaudi::Property> m_seedingLayersCellID{ - this, "SeedingLayersCellID", m_default_empty_vec, + Gaudi::Property> m_seedingSensorsCellIDs{ + this, "SeedingSensorsCellIDs", m_default_empty_vec, "CellIDSelector selection strings for seed space-point filtering. " "Each entry is a comma-separated list of field:value constraints (e.g. \"system:1,layer:2|3\"). " "Multiple entries are OR-ed together. Omitted fields act as wildcards."}; - std::optional m_seedSelector; ///@} /// @name Multi-threading @@ -213,6 +212,8 @@ struct CKFTrackingAlg final private: SmartIF m_actsGeoSvc; + k4ActsTracking::CellIDSelector m_seedSelector{}; + mutable std::mutex m_seedMutex{}; mutable std::mutex m_trackMutex{}; }; @@ -241,7 +242,8 @@ StatusCode CKFTrackingAlg::initialize() { K4_GAUDI_CHECK(m_actsGeoSvc); debug() << "Creating SeedSelector" << endmsg; - m_seedSelector.emplace(m_actsGeoSvc->cellIDEncodingString(), m_seedingLayersCellID.value()); + m_seedSelector = + k4ActsTracking::CellIDSelector(m_actsGeoSvc->cellIDEncodingString(), m_seedingSensorsCellIDs.value()); debug() << "Done" << endmsg; // Apply deltaR fallback defaults @@ -347,7 +349,7 @@ std::tuple CKFTrackingAlg::o debug() << "Seed selection for spacepoint w/ cellId: " << decoder.valueString(hitPair.second.getCellID()) << endmsg; // Create space point for seeding if this surface is selected - if (m_seedSelector->accept(hitPair.second.getCellID())) { + if (m_seedSelector.accept(hitPair.second.getCellID())) { debug() << "Accepted" << endmsg; Acts::RotationMatrix3 rotLocalToGlobal = surface->referenceFrame(geoCtx, globalPos, {0, 0, 0}); diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index f8024e61..7092a526 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -150,7 +150,7 @@ # entry is a comma-separated list of field:value constraints read directly # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. # Omitted fields act as wildcards; "|" separates multiple values for a field. - SeedingLayersCellID=["system:1", "system:2,layer:1|2|3"], + SeedingSensorsCellIDs=["system:1", "system:2,layer:1|2|3"], OutputTrackCollectionName=["AllTracks"], OutputSeedCollectionName=["SeedTracks"], InputTrackerHitCollectionName=["MergedTrackerHits"], From 21a60635337ccac1f5a7f0d4601fd26186bbd2ab Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Fri, 20 Mar 2026 15:04:45 +0100 Subject: [PATCH 17/72] Remove unnecessary debug outputs and comments --- .../src/components/CKFTrackingAlg.cpp | 72 +++++-------------- 1 file changed, 16 insertions(+), 56 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index cb031aa5..ca72fde1 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -32,6 +32,7 @@ #include #include +#include // edm4hep #include #include @@ -74,8 +75,9 @@ #include #include +#include + // Standard -#include #include #include #include @@ -85,6 +87,8 @@ using namespace Acts::UnitLiterals; +template <> struct fmt::formatter : fmt::ostream_formatter {}; + /** * @brief Seeded CKF tracking algorithm using ActsGeoSvc. * @@ -121,7 +125,7 @@ struct CKFTrackingAlg final const edm4hep::TrackerHitSimTrackerHitLinkCollection& trackerHitRelations) const override; // ----- private helpers --------------------------------------------------- - +private: std::vector findSeeds(const Acts::SeedFinder& finder, const Acts::SeedFinderOptions& finderOpts, const auto& spacePointGroup, const SSPointGrid& grid, @@ -232,19 +236,12 @@ CKFTrackingAlg::CKFTrackingAlg(const std::string& name, ISvcLocator* svcLoc) {KeyValues("OutputSeedCollectionName", {"SeedTracks"}), KeyValues("OutputTrackCollectionName", {"Tracks"})}) { } -// --------------------------------------------------------------------------- -// initialize -// --------------------------------------------------------------------------- - StatusCode CKFTrackingAlg::initialize() { - debug() << "CKFTracking::initialize()" << endmsg; m_actsGeoSvc = svcLoc()->service("ActsGeoSvc"); K4_GAUDI_CHECK(m_actsGeoSvc); - debug() << "Creating SeedSelector" << endmsg; m_seedSelector = k4ActsTracking::CellIDSelector(m_actsGeoSvc->cellIDEncodingString(), m_seedingSensorsCellIDs.value()); - debug() << "Done" << endmsg; // Apply deltaR fallback defaults if (m_seedFinding_deltaRMinTop == 0.f) @@ -256,14 +253,9 @@ StatusCode CKFTrackingAlg::initialize() { if (m_seedFinding_deltaRMaxBottom == 0.f) m_seedFinding_deltaRMaxBottom = m_seedFinding_deltaRMax; - debug() << "Initialization of CKFTrackingAlg successful" << endmsg; return StatusCode::SUCCESS; } -// --------------------------------------------------------------------------- -// operator() — event processing -// --------------------------------------------------------------------------- - std::tuple CKFTrackingAlg::operator()( const edm4hep::TrackerHitPlaneCollection& trackerHitCollection, const edm4hep::TrackerHitSimTrackerHitLinkCollection& /*trackerHitRelations*/) const { @@ -279,8 +271,6 @@ std::tuple CKFTrackingAlg::o dd4hep::DDSegmentation::BitFieldCoder decoder{m_actsGeoSvc->cellIDEncodingString()}; - // ----- Phase 1: convert EDM4HEP hits → ACTS objects ---------------------- - std::vector> sortedHits; ACTSTracking::SourceLinkContainer sourceLinks; ACTSTracking::MeasurementContainer measurements; @@ -289,10 +279,10 @@ std::tuple CKFTrackingAlg::o sortedHits.reserve(trackerHitCollection.size()); for (const auto& hit : trackerHitCollection) { - debug() << "Adding hit " << hit.id() << endmsg; + verbose() << fmt::format("Adding hit {} with cell id {:x}", hit.id(), hit.getCellID()) << endmsg; auto it = cellIdToSurface.find(hit.getCellID()); if (it == cellIdToSurface.end()) { - warning() << "No surface found for cellID " << hit.getCellID() << " — skipping hit" << endmsg; + warning() << "No surface found for cellID " << hit.getCellID() << ". skipping hit for tracking." << endmsg; continue; } sortedHits.push_back({it->second->geometryId(), hit}); @@ -313,22 +303,20 @@ std::tuple CKFTrackingAlg::o for (const auto& hitPair : sortedHits) { const Acts::Surface* surface = m_actsGeoSvc->trackingGeometry()->findSurface(hitPair.first); if (surface == nullptr) { - warning() << "Surface with geoID " << hitPair.first << " not found in tracking geometry — skipping" << endmsg; + warning() << "Surface with geoID " << hitPair.first + << " not found in tracking geometry. Skipping hit for tracking." << endmsg; continue; } const edm4hep::Vector3d& edmGlobalPos = hitPair.second.getPosition(); Acts::Vector3 globalPos = {edmGlobalPos.x, edmGlobalPos.y, edmGlobalPos.z}; - debug() << "Converting hit " << hitPair.second.id() << " to local position (pos = " << edmGlobalPos - << ") using surface with geoId " << hitPair.first << endmsg; + verbose() << "Converting hit " << hitPair.second.id() << " to local position (pos = " << edmGlobalPos + << ") using surface with geoId " << hitPair.first << endmsg; Acts::Result lpResult = surface->globalToLocal(geoCtx, globalPos, {0, 0, 0}, 0.5_um); if (!lpResult.ok()) { - warning() << "Global to local transformation did not succeed" << endmsg; - Acts::Vector3 loc3DFrame = surface->localToGlobalTransform(geoCtx).inverse() * globalPos; - debug() << "Local position: " << loc3DFrame.x() << ", " << loc3DFrame.y() << ", " << loc3DFrame.z() - << " z tolerance: " << 0.5_um << endmsg; + warning() << "Global to local transformation did not succeed for hit. Skipping it in tracking." << endmsg; continue; } @@ -347,10 +335,8 @@ std::tuple CKFTrackingAlg::o measurements.push_back(meas); sourceLinks.emplace_hint(sourceLinks.end(), sourceLink); - debug() << "Seed selection for spacepoint w/ cellId: " << decoder.valueString(hitPair.second.getCellID()) << endmsg; // Create space point for seeding if this surface is selected if (m_seedSelector.accept(hitPair.second.getCellID())) { - debug() << "Accepted" << endmsg; Acts::RotationMatrix3 rotLocalToGlobal = surface->referenceFrame(geoCtx, globalPos, {0, 0, 0}); // Jacobian from global (x,y,z) to (rho, z) @@ -368,9 +354,9 @@ std::tuple CKFTrackingAlg::o } } - debug() << "Created " << spacePoints.size() << " space points" << endmsg; - - // ----- Phase 2: configure seed finder ------------------------------------ + debug() << fmt::format("Created {} sourceLinks and {} space points for seeding", sourceLinks.size(), + spacePoints.size()) + << endmsg; Acts::MagneticFieldProvider::Cache magCache = m_actsGeoSvc->magneticField()->makeCache(magCtx); @@ -452,8 +438,6 @@ std::tuple CKFTrackingAlg::o Acts::SeedFinder finder(finderCfg); - // ----- Phase 3: configure CKF -------------------------------------------- - Navigator::Config navigatorCfg{m_actsGeoSvc->trackingGeometry()}; navigatorCfg.resolvePassive = false; navigatorCfg.resolveMaterial = true; @@ -493,8 +477,6 @@ std::tuple CKFTrackingAlg::o TrackFinderOptions ckfOptions = TrackFinderOptions(geoCtx, magCtx, calCtx, extensions, pOptions); - // ----- Phase 4: group space points and run seeding + tracking ------------ - float minRange = std::numeric_limits::max(); float maxRange = std::numeric_limits::lowest(); for (const auto& coll : grid) { @@ -542,10 +524,6 @@ std::tuple CKFTrackingAlg::o return std::make_tuple(std::move(seedCollection), std::move(trackCollection)); } -// --------------------------------------------------------------------------- -// findSeeds — build seeds from a space-point group and estimate parameters -// --------------------------------------------------------------------------- - std::vector CKFTrackingAlg::findSeeds( const Acts::SeedFinder& finder, const Acts::SeedFinderOptions& finderOpts, const auto& spacePointGroup, const SSPointGrid& grid, Acts::Range1D middleSpRange, std::size_t mutSpDataSize, @@ -630,10 +608,6 @@ std::vector CKFTrackingAlg::findSeeds( return paramseeds; } -// --------------------------------------------------------------------------- -// tracking — run CKF for each seed and collect results -// --------------------------------------------------------------------------- - StatusCode CKFTrackingAlg::tracking(const std::vector& paramseeds, const CKF& trackFinder, const TrackFinderOptions& ckfOptions, Acts::MagneticFieldProvider::Cache& magCache, edm4hep::TrackCollection& trackCollection) const { @@ -645,8 +619,6 @@ StatusCode CKFTrackingAlg::tracking(const std::vector(); TrackContainer tracks(trackContainer, trackStateContainer); - auto trackStart = std::chrono::high_resolution_clock::now(); - for (std::size_t iseed = 0; iseed < paramseeds.size(); ++iseed) { tracks.clear(); auto result = trackFinder.findTracks(paramseeds.at(iseed), ckfOptions, tracks); @@ -661,14 +633,6 @@ StatusCode CKFTrackingAlg::tracking(const std::vectormagneticField(), magCache); { std::lock_guard lock{m_trackMutex}; @@ -680,9 +644,5 @@ StatusCode CKFTrackingAlg::tracking(const std::vector trackDuration = trackEnd - trackStart; - (void)trackDuration; - return StatusCode::SUCCESS; } From 40160d5597b87993249b8256858af3b69fd55f2b Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 21 Apr 2026 15:04:45 +0200 Subject: [PATCH 18/72] Try to discover an external Catch2 properly --- test/unittests/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 2cab5b4d..8b3abc71 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -1,4 +1,8 @@ set(CATCH2_MIN_VERSION 3.5.0) + +set(USE_EXTERNAL_CATCH2 AUTO CACHE STRING "Link against an external Catch2 v3 static library, otherwise build it locally") +set_property(CACHE USE_EXTERNAL_CATCH2 PROPERTY STRINGS AUTO ON OFF) + if(USE_EXTERNAL_CATCH2) if (USE_EXTERNAL_CATCH2 STREQUAL AUTO) find_package(Catch2 ${CATCH2_MIN_VERSION}) From 3abbe73b77986afa24457ec4cc6db37b775190e9 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 21 Apr 2026 15:09:04 +0200 Subject: [PATCH 19/72] Fix pre-commit issues --- .../include/k4ActsTracking/IActsGeoSvc.h | 8 ++++---- test/options/MAIA_CKFTrackingAlg.py | 1 + test/options/MAIA_CKF_Tracking.py | 20 +++++++++++++++++-- test/unittests/CMakeLists.txt | 19 ++++++++++++++++++ test/unittests/CellIDSelectionTests.cpp | 18 +++++++++++++++++ 5 files changed, 60 insertions(+), 6 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h b/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h index 6fbea36d..efa20a3e 100644 --- a/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h +++ b/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h @@ -46,10 +46,10 @@ class GAUDI_API IActsGeoSvc : virtual public IService { public: DeclareInterfaceID(IActsGeoSvc, 1, 0); - virtual std::shared_ptr trackingGeometry() const = 0; - virtual std::shared_ptr magneticField() const = 0; - virtual const CellIDSurfaceMap& cellIdToSurfaceMap() const = 0; - virtual std::string cellIDEncodingString() const = 0; + virtual std::shared_ptr trackingGeometry() const = 0; + virtual std::shared_ptr magneticField() const = 0; + virtual const CellIDSurfaceMap& cellIdToSurfaceMap() const = 0; + virtual std::string cellIDEncodingString() const = 0; virtual ~IActsGeoSvc() = default; }; diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index 7092a526..6a9f17a2 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -17,6 +17,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # + # Options file for the Gen3-geometry CKF tracking algorithm (CKFTrackingAlg). # Uses ActsGeoSvc for geometry, magnetic field, and hit-to-surface mapping, # instead of the algorithm-level TGeo file loading used by ACTSSeededCKFTrackingAlg. diff --git a/test/options/MAIA_CKF_Tracking.py b/test/options/MAIA_CKF_Tracking.py index 4daa0d41..3c7615ef 100644 --- a/test/options/MAIA_CKF_Tracking.py +++ b/test/options/MAIA_CKF_Tracking.py @@ -1,6 +1,22 @@ #!/usr/bin/env python3 - -import os +# +# Copyright (c) 2014-2024 Key4hep-Project. +# +# This file is part of Key4hep. +# See https://key4hep.github.io/key4hep-doc/ for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# from Gaudi.Configuration import INFO, VERBOSE from Gaudi.Configurables import ( diff --git a/test/unittests/CMakeLists.txt b/test/unittests/CMakeLists.txt index 8b3abc71..a283d78e 100644 --- a/test/unittests/CMakeLists.txt +++ b/test/unittests/CMakeLists.txt @@ -1,3 +1,22 @@ +#[[ +Copyright (c) 2014-2024 Key4hep-Project. + +This file is part of Key4hep. +See https://key4hep.github.io/key4hep-doc/ for further info. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +]] + set(CATCH2_MIN_VERSION 3.5.0) set(USE_EXTERNAL_CATCH2 AUTO CACHE STRING "Link against an external Catch2 v3 static library, otherwise build it locally") diff --git a/test/unittests/CellIDSelectionTests.cpp b/test/unittests/CellIDSelectionTests.cpp index cabe6b58..4b539fc0 100644 --- a/test/unittests/CellIDSelectionTests.cpp +++ b/test/unittests/CellIDSelectionTests.cpp @@ -1,3 +1,21 @@ +/* + * Copyright (c) 2014-2024 Key4hep-Project. + * + * This file is part of Key4hep. + * See https://key4hep.github.io/key4hep-doc/ for further info. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ #include "k4ActsTracking/CellIDSelector.h" From f23b3461601fdc986b91c55308e9e84d562caf60 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 21 Apr 2026 16:15:41 +0200 Subject: [PATCH 20/72] Re-organize tests a bit --- test/CMakeLists.txt | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 1fbf6bcb..10e679f6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -71,6 +71,13 @@ add_test(NAME sim_MAIA --gun.momentumMax 500000 ) +# Setup test dependencies such that they are run in the right order always +set_tests_properties( + sim_MAIA + PROPERTIES + FIXTURES_SETUP sim_maia_fixture +) + add_test(NAME reco_MAIA WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKF_Tracking.py @@ -81,13 +88,6 @@ add_test(NAME reco_MAIA ) set_test_env(reco_MAIA) -# Setup test dependencies such that they are run in the right order always -set_tests_properties( - sim_MAIA - PROPERTIES - FIXTURES_SETUP sim_maia_fixture -) - # Gen3 geometry path: CKFTrackingAlg driven by ActsGeoSvc add_test(NAME reco_MAIA_Gen3 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} From 7a30a16f74441ce0b84295bf9c7dfd2e9434bd95 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 21 Apr 2026 17:03:05 +0200 Subject: [PATCH 21/72] Remove tests based on old geometry Not working due to some geometry issues --- test/CMakeLists.txt | 13 -- test/options/MAIA_CKF_Tracking.py | 201 ------------------------------ 2 files changed, 214 deletions(-) delete mode 100644 test/options/MAIA_CKF_Tracking.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 10e679f6..0ddadd95 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -71,24 +71,12 @@ add_test(NAME sim_MAIA --gun.momentumMax 500000 ) -# Setup test dependencies such that they are run in the right order always set_tests_properties( sim_MAIA PROPERTIES FIXTURES_SETUP sim_maia_fixture ) -add_test(NAME reco_MAIA - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKF_Tracking.py - --compactFile=$ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml - --trackingGeoFile=${CMAKE_CURRENT_LIST_DIR}/../data/MAIA_v0.root - --geoDescFile=${CMAKE_CURRENT_LIST_DIR}/../data/MAIA_v0.json - --materialFile=${CMAKE_CURRENT_LIST_DIR}/../data/MAIA_v0_material.json -) -set_test_env(reco_MAIA) - -# Gen3 geometry path: CKFTrackingAlg driven by ActsGeoSvc add_test(NAME reco_MAIA_Gen3 WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKFTrackingAlg.py @@ -97,7 +85,6 @@ add_test(NAME reco_MAIA_Gen3 set_test_env(reco_MAIA_Gen3) set_tests_properties( - reco_MAIA reco_MAIA_Gen3 PROPERTIES diff --git a/test/options/MAIA_CKF_Tracking.py b/test/options/MAIA_CKF_Tracking.py deleted file mode 100644 index 3c7615ef..00000000 --- a/test/options/MAIA_CKF_Tracking.py +++ /dev/null @@ -1,201 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (c) 2014-2024 Key4hep-Project. -# -# This file is part of Key4hep. -# See https://key4hep.github.io/key4hep-doc/ for further info. -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -# - -from Gaudi.Configuration import INFO, VERBOSE -from Gaudi.Configurables import ( - ACTSSeededCKFTrackingAlg, - CollectionMerger, - DDPlanarDigi, - EventDataSvc, - GeoSvc, -) - -from k4FWCore import ApplicationMgr, IOSvc -from k4FWCore.parseArgs import parser - -parser.add_argument( - "--compactFile", - help="The geometry compact file to use for reconstruction", - type=str, -) -parser.add_argument( - "--trackingGeoFile", - help="The TGeo file containing the tracking geometry", - type=str, -) -parser.add_argument( - "--materialFile", - help="The file containing the material mapping for the tracking geometry", - type=str, -) -parser.add_argument( - "--geoDescFile", help="The JSON file describing the subdetectors", type=str -) - -args = parser.parse_known_args()[0] - - -svcList = [ - GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), - EventDataSvc("EventDataSvc"), -] - -iosvc = IOSvc( - "IOSvc", - Input=["particle_gun_MAIA_SIM.edm4hep.root"], - Output="maia_ckf_tracking_reco.edm4hep.root", -) - - -algList = [] - - -for name in ("VertexBarrel", "VertexEndcap"): - algList.append( - DDPlanarDigi( - f"{name}Digitizer", - CorrectTimesForPropagation=True, - IsStrip=False, - ResolutionT=[0.03], - ResolutionU=[0.005], - ResolutionV=[0.005], - SubDetectorName="Vertex", - TimeWindowMax=[0.15], - TimeWindowMin=[-0.09], - UseTimeWindow=True, - SimTrackHitCollectionName=[f"{name}Collection"], - SimTrkHitRelCollection=[f"{name}HitsRelations"], - TrackerHitCollectionName=[f"{name}Hits"], - ) - ) - -for name in ( - "InnerTrackerBarrel", - "InnerTrackerEndcap", - "OuterTrackerBarrel", - "OuterTrackerEndcap", -): - algList.append( - DDPlanarDigi( - f"{name}Digitizer", - CorrectTimesForPropagation=True, - IsStrip="InnerBarrel" in name or "OuterEndcap" in name, # Is this true? - ResolutionT=[0.06], - ResolutionU=[0.007], - ResolutionV=[0.09], - SubDetectorName=name.replace("Barrel", "s").replace("Endcap", "s"), - TimeWindowMax=[0.3], - TimeWindowMin=[-0.18], - UseTimeWindow=True, - SimTrackHitCollectionName=[f"{name}Collection"], - SimTrkHitRelCollection=[f"{name}HitsRelations"], - TrackerHitCollectionName=[f"{name}Hits"], - ) - ) - - -algList.append( - CollectionMerger( - "MergeHits", - InputCollections=[ - "VertexBarrelHits", - "VertexEndcapHits", - "InnerTrackerBarrelHits", - "InnerTrackerEndcapHits", - "OuterTrackerBarrelHits", - "OuterTrackerEndcapHits", - ], - OutputCollection=["MergedTrackerHits"], - ) -) - -# algList.append( -# CollectionMerger( -# "MergeHitsRelations", -# InputCollections=[ -# "VertexBarrelHitsRelations", -# "VertexEndcapHitsRelations", -# "InnerTrackerBarrelHitsRelations", -# "InnerTrackerEndcapHitsRelations", -# "OuterTrackerBarrelHitsRelations", -# "OuterTrackerEndcapHitsRelations", -# ], -# OutputCollection=["MergedTrackerHitsRelations"], -# ) -# ) - -ckf_tracking = ACTSSeededCKFTrackingAlg( - "CKFTracking", - MatFile=args.materialFile, - TGeoFile=args.trackingGeoFile, - TGeoDescFile=args.geoDescFile, - DetectorSchema=args.compactFile, - RunCKF="True", - CKF_Chi2CutOff=10, - SeedFinding_RMax=150, - SeedFinding_MinPt=500, - SeedFinding_ImpactMax=3, - CKF_NumMeasurementsCutOff=1, - SeedFinding_SigmaScattering=50, - SeedFinding_CollisionRegion=6, - SeedFinding_RadLengthPerSeed=0.1, - SeedingLayers=[ - "13", - "2", - "13", - "6", - "13", - "10", - "13", - "14", - "14", - "2", - "14", - "6", - "14", - "10", - "14", - "14", - "15", - "2", - "15", - "6", - "15", - "10", - "15", - "14", - "8", - "2", - "17", - "2", - "18", - "2", - ], - OutputTrackCollectionName=["AllTracks"], - OutputSeedCollectionName=["SeedTracks"], - InputTrackerHitCollectionName=["MergedTrackerHits"], - OutputLevel=VERBOSE, -) -algList.append(ckf_tracking) - - -ApplicationMgr( - TopAlg=algList, ExtSvc=svcList, OutputLevel=INFO, EvtSel="NONE", EvtMax=-1 -) From 3b0d05044e571adfe79fd83c7161009cb31a77ea Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Tue, 28 Apr 2026 15:57:50 +0200 Subject: [PATCH 22/72] Add CLD tracking test --- test/CMakeLists.txt | 48 +++++++++++++- test/options/CLD_CKFTracking.py | 109 ++++++++++++++++++++++++++++++++ 2 files changed, 156 insertions(+), 1 deletion(-) create mode 100644 test/options/CLD_CKFTracking.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0ddadd95..a301c552 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -17,6 +17,8 @@ See the License for the specific language governing permissions and limitations under the License. ]] +add_subdirectory(unittests) + set(test_environment "\ LD_LIBRARY_PATH=\ ${PROJECT_BINARY_DIR}:\ @@ -91,4 +93,48 @@ set_tests_properties( FIXTURES_REQUIRED sim_maia_fixture ) -add_subdirectory(unittests) +add_test(NAME clone_CLDConfig + COMMAND bash -c "if [ ! -d CLDConfig ]; then git clone https://github.com/key4hep/CLDConfig.git --depth 1; fi && cp -r CLDConfig/CLDConfig . && sed -i -e '/Overlay.Overlay/d' -e '/HighLevelReco.RecoMCTruthLink/d' CLDConfig/CLDReconstruction.py" +) +set_tests_properties(clone_CLDConfig PROPERTIES FIXTURES_SETUP CLDConfig) + +add_test(NAME sim_CLD_o2_v08 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig + COMMAND ddsim --compactFile $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml + --outputFile particle_gun_CLD_o2_v08_SIM.edm4hep.root + --runType=batch -G -N=10 + --gun.multiplicity 10 + --gun.particle "mu-" + --gun.distribution uniform + --gun.momentumMin 1500 + --gun.momentumMax 100000 +) +set_tests_properties(sim_CLD_o2_v08 + PROPERTIES + FIXTURES_REQUIRED CLDConfig + FIXTURES_SETUP sim_CLD_fixture +) + +add_test(NAME std_reco_CLD_o2_v08 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig + COMMAND k4run CLDReconstruction.py + --compactFile $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml + --inputFiles=particle_gun_CLD_o2_v08_SIM.edm4hep.root + --trackingOnly + --outputBasename particle_gun_CLD_o2_v08 +) +set_test_env(std_reco_CLD_o2_v08) +set_tests_properties(std_reco_CLD_o2_v08 + PROPERTIES + FIXTURES_REQUIRED sim_CLD_fixture + FIXTURES_SETUP reco_CLD_fixture + +) + +add_test(NAME ckf_tracking_CLD_o2_v08 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig + COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/CLD_CKFTracking.py + --compactFile $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml +) +set_test_env(ckf_tracking_CLD_o2_v08) +set_tests_properties(ckf_tracking_CLD_o2_v08 PROPERTIES FIXTURES_REQUIRED reco_CLD_fixture) diff --git a/test/options/CLD_CKFTracking.py b/test/options/CLD_CKFTracking.py new file mode 100644 index 00000000..36d52153 --- /dev/null +++ b/test/options/CLD_CKFTracking.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2014-2024 Key4hep-Project. +# +# This file is part of Key4hep. +# See https://key4hep.github.io/key4hep-doc/ for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Options file for the Gen3-geometry CKF tracking algorithm (CKFTrackingAlg). +# Uses ActsGeoSvc for geometry, magnetic field, and hit-to-surface mapping, +# instead of the algorithm-level TGeo file loading used by ACTSSeededCKFTrackingAlg. + +from Gaudi.Configuration import INFO, VERBOSE +from Gaudi.Configurables import ( + GeoSvc, + ActsGeoSvc, + CKFTrackingAlg, + CollectionMerger, + EventDataSvc, +) +from k4FWCore import ApplicationMgr, IOSvc +from k4FWCore.parseArgs import parser + +parser.add_argument( + "--compactFile", + help="The geometry compact file to use for reconstruction", + type=str, +) +args = parser.parse_known_args()[0] +svcList = [ + GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), + ActsGeoSvc("ActsGeoSvc"), + EventDataSvc("EventDataSvc"), +] + +iosvc = IOSvc( + "IOSvc", + Input=["particle_gun_CLD_o2_v08_REC.edm4hep.root"], + Output="particle_gun_CLD_o2_v08_ACTS_CKFTracking.edm4hep.root", +) + +hit_merger = CollectionMerger( + "MergeHits", + InputCollections=[ + "VXDTrackerHits", + "VXDEndcapTrackerHits", + "ITrackerHits", + "OTrackerHits", + "ITrackerEndcapHits", + "OTrackerEndcapHits", + ], + OutputCollection="AllTrackerHits", +) +hit_rel_merger = CollectionMerger( + "MergeHitRelations", + InputCollections=[ + "VXDTrackerHitRelations", + "VXDEndcapTrackerHitRelations", + "InnerTrackerBarrelHitsRelations", + "InnerTrackerEndcapHitsRelations", + "OuterTrackerBarrelHitsRelations", + "OuterTrackerEndcapHitsRelations", + ], + OutputCollection="AllTrackerHitRelations", +) + +ckf_tracking = CKFTrackingAlg( + "CKFTracking", + RunCKF=True, + CKF_Chi2CutOff=10, + SeedFinding_RMax=150, + SeedFinding_MinPt=500, + SeedFinding_ImpactMax=3, + CKF_NumMeasurementsCutOff=1, + SeedFinding_SigmaScattering=50, + SeedFinding_CollisionRegion=6, + SeedFinding_RadLengthPerSeed=0.1, + # SeedingLayersCellID uses CellIDSelector selection strings, where each + # entry is a comma-separated list of field:value constraints read directly + # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. + # Omitted fields act as wildcards; "|" separates multiple values for a field. + SeedingSensorsCellIDs=["system:1|2", "system:3|4,layer:1"], + OutputTrackCollectionName=["CKFTracks"], + OutputSeedCollectionName=["CKFSeedTracks"], + InputTrackerHitCollectionName=hit_merger.OutputCollection, + InputTrackerHitRelationCollectionName=hit_rel_merger.OutputCollection, + OutputLevel=VERBOSE, +) + + +ApplicationMgr( + TopAlg=[hit_merger, hit_rel_merger, ckf_tracking], + ExtSvc=svcList, + OutputLevel=INFO, + EvtSel="NONE", + EvtMax=-1, +) From 9ef961d263f30c146530b0549928b603fb5d77e8 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 11:42:16 +0200 Subject: [PATCH 23/72] Fix config after upstream changes --- test/options/MAIA_CKFTrackingAlg.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index 6a9f17a2..d951465a 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -117,7 +117,7 @@ "OuterTrackerBarrelHits", "OuterTrackerEndcapHits", ], - OutputCollection=["MergedTrackerHits"], + OutputCollection="MergedTrackerHits", ) ) @@ -132,7 +132,7 @@ "OuterTrackerBarrelHitsRelations", "OuterTrackerEndcapHitsRelations", ], - OutputCollection=["MergedTrackerHitsRelations"], + OutputCollection="MergedTrackerHitsRelations", ) ) From 18657c38f6015a3fdeea1f6086853f3b1489d2fd Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 11:53:32 +0200 Subject: [PATCH 24/72] Use single KeyValue inputs and outputs to avoid misconfiguration --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 10 ++++------ test/options/CLD_CKFTracking.py | 8 ++++---- test/options/MAIA_CKFTrackingAlg.py | 8 ++++---- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index ca72fde1..b81845ce 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -229,12 +229,10 @@ DECLARE_COMPONENT(CKFTrackingAlg) // --------------------------------------------------------------------------- CKFTrackingAlg::CKFTrackingAlg(const std::string& name, ISvcLocator* svcLoc) - : MultiTransformer( - name, svcLoc, - {KeyValues("InputTrackerHitCollectionName", {"TrackerHits"}), - KeyValues("InputTrackerHitRelationCollectionName", {"TrackerHitRelations"})}, - {KeyValues("OutputSeedCollectionName", {"SeedTracks"}), KeyValues("OutputTrackCollectionName", {"Tracks"})}) { -} + : MultiTransformer(name, svcLoc, + {KeyValue("InputTrackerHitCollection", "TrackerHits"), + KeyValue("InputTrackerHitRelationCollection", "TrackerHitRelations")}, + {KeyValue("OutputSeedCollection", "SeedTracks"), KeyValue("OutputTrackCollection", "Tracks")}) {} StatusCode CKFTrackingAlg::initialize() { m_actsGeoSvc = svcLoc()->service("ActsGeoSvc"); diff --git a/test/options/CLD_CKFTracking.py b/test/options/CLD_CKFTracking.py index 36d52153..87e305ea 100644 --- a/test/options/CLD_CKFTracking.py +++ b/test/options/CLD_CKFTracking.py @@ -92,10 +92,10 @@ # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. # Omitted fields act as wildcards; "|" separates multiple values for a field. SeedingSensorsCellIDs=["system:1|2", "system:3|4,layer:1"], - OutputTrackCollectionName=["CKFTracks"], - OutputSeedCollectionName=["CKFSeedTracks"], - InputTrackerHitCollectionName=hit_merger.OutputCollection, - InputTrackerHitRelationCollectionName=hit_rel_merger.OutputCollection, + OutputTrackCollection="CKFTracks", + OutputSeedCollection="CKFTrackSeeds", + InputTrackerHitCollection=hit_merger.OutputCollection, + InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, OutputLevel=VERBOSE, ) diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index d951465a..c6041d8a 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -152,10 +152,10 @@ # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. # Omitted fields act as wildcards; "|" separates multiple values for a field. SeedingSensorsCellIDs=["system:1", "system:2,layer:1|2|3"], - OutputTrackCollectionName=["AllTracks"], - OutputSeedCollectionName=["SeedTracks"], - InputTrackerHitCollectionName=["MergedTrackerHits"], - InputTrackerHitRelationCollectionName=["MergedTrackerHitsRelations"], + OutputTrackCollection="CKFTracks", + OutputSeedCollection="CKFTrackSeeds", + InputTrackerHitCollection="MergedTrackerHits", + InputTrackerHitRelationCollection="MergedTrackerHitsRelations", OutputLevel=VERBOSE, ) algList.append(ckf_tracking) From dbbe8296e9f3c0ffdaece74f194beb33b188ec36 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 13:26:20 +0200 Subject: [PATCH 25/72] Add tests for ILD_FCCee_v01 --- test/CMakeLists.txt | 48 +++++++++++++++ test/options/ILD_CKFTracking.py | 102 ++++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) create mode 100644 test/options/ILD_CKFTracking.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a301c552..8331b6c5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -138,3 +138,51 @@ add_test(NAME ckf_tracking_CLD_o2_v08 ) set_test_env(ckf_tracking_CLD_o2_v08) set_tests_properties(ckf_tracking_CLD_o2_v08 PROPERTIES FIXTURES_REQUIRED reco_CLD_fixture) + + + +add_test(NAME clone_ILDConfig + COMMAND bash -c "if [ ! -d ILDConfig ]; then git clone https://github.com/iLCSoft/ILDConfig.git --depth 1; fi" +) +set_tests_properties(clone_ILDConfig PROPERTIES FIXTURES_SETUP ILDConfig) +add_test(NAME sim_ILD_FCCee_v01 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production + COMMAND ddsim --compactFile $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml + --outputFile particle_gun_ILD_FCCee_v01_SIM.edm4hep.root + --runType=batch -G -N=10 + --gun.multiplicity 10 + --gun.particle "mu-" + --gun.distribution uniform + --gun.momentumMin 1500 + --gun.momentumMax 1000000 +) +set_tests_properties(sim_ILD_FCCee_v01 + PROPERTIES + FIXTURES_REQUIRED ILDConfig + FIXTURES_SETUP sim_ILD_FCCee_v01_fixture +) + +add_test(NAME std_reco_ILD_FCCee_v01 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production + COMMAND k4run ILDReconstruction.py + --compactFile $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml + --inputFiles particle_gun_ILD_FCCee_v01_SIM.edm4hep.root + --trackingOnly + --outputFileBase particle_gun_ILD_FCCee_v01 +) +set_tests_properties(std_reco_ILD_FCCee_v01 + PROPERTIES + FIXTURES_REQUIRED sim_ILD_FCCee_v01_fixture + FIXTURES_SETUP reco_ILD_fixture +) + +add_test(NAME ckf_tracking_ILD_FCCee_v01 + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production + COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/ILD_CKFTracking.py + --compactFile $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml +) +set_test_env(ckf_tracking_ILD_FCCee_v01) +set_tests_properties(ckf_tracking_ILD_FCCee_v01 + PROPERTIES + FIXTURES_REQUIRED reco_ILD_fixture +) diff --git a/test/options/ILD_CKFTracking.py b/test/options/ILD_CKFTracking.py new file mode 100644 index 00000000..96b27777 --- /dev/null +++ b/test/options/ILD_CKFTracking.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2014-2024 Key4hep-Project. +# +# This file is part of Key4hep. +# See https://key4hep.github.io/key4hep-doc/ for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from Gaudi.Configuration import INFO, VERBOSE +from Gaudi.Configurables import ( + GeoSvc, + ActsGeoSvc, + CKFTrackingAlg, + CollectionMerger, + EventDataSvc, +) +from k4FWCore import ApplicationMgr, IOSvc +from k4FWCore.parseArgs import parser + +parser.add_argument( + "--compactFile", + help="The geometry compact file to use for reconstruction", + type=str, +) +args = parser.parse_known_args()[0] +svcList = [ + GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), + ActsGeoSvc("ActsGeoSvc"), + EventDataSvc("EventDataSvc"), +] + +iosvc = IOSvc( + "IOSvc", + Input=["particle_gun_ILD_FCCee_v01_REC.edm4hep.root"], + Output="particle_gun_ILD_FCCee_v01_ACTS_CKFTracking.edm4hep.root", +) + +hit_merger = CollectionMerger( + "MergeHits", + InputCollections=[ + "VertexBarrelTrackerHits", + "VertexEndcapTrackerHits", + "InnerTrackerBarrelHits", + "InnerTrackerEndcapHits", + "SETTrackerHits", + ], + OutputCollection="AllTrackerHits", +) +hit_rel_merger = CollectionMerger( + "MergeHitRelations", + InputCollections=[ + "VertexBarrelTrackerHitRelations", + "VertexEndcapTrackerHitRelations", + "InnerTrackerBarrelHitRelations", + "InnerTrackerEndcapHitRelations", + "SETTrackerHitRelations", + ], + OutputCollection="AllTrackerHitRelations", +) + +ckf_tracking = CKFTrackingAlg( + "CKFTracking", + RunCKF=True, + CKF_Chi2CutOff=10, + SeedFinding_RMax=150, + SeedFinding_MinPt=500, + SeedFinding_ImpactMax=3, + CKF_NumMeasurementsCutOff=1, + SeedFinding_SigmaScattering=50, + SeedFinding_CollisionRegion=6, + SeedFinding_RadLengthPerSeed=0.1, + # SeedingLayersCellID uses CellIDSelector selection strings, where each + # entry is a comma-separated list of field:value constraints read directly + # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. + # Omitted fields act as wildcards; "|" separates multiple values for a field. + SeedingSensorsCellIDs=["system:1|2", "system:3|5,layer:1"], + OutputTrackCollection="CKFTracks", + OutputSeedCollection="CKFTrackSeeds", + InputTrackerHitCollection=hit_merger.OutputCollection, + InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, + OutputLevel=VERBOSE, +) + +ApplicationMgr( + TopAlg=[hit_merger, hit_rel_merger, ckf_tracking], + ExtSvc=svcList, + OutputLevel=INFO, + EvtSel="NONE", + EvtMax=-1, +) From 2141d0694c8bd7edd0c41083abc17cd4e8d0e22b Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 14:40:54 +0200 Subject: [PATCH 26/72] Introduce cmake functions for testing First Claude pass --- test/CMakeLists.txt | 249 ++++++++++++++++++++++++++------------------ 1 file changed, 147 insertions(+), 102 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8331b6c5..3b0b91a8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -59,130 +59,175 @@ add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v07/CLD_o2_v07.x add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml) -add_test(NAME sim_MAIA - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMAND ddsim --compactFile=$ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml - --runType=batch -G -N=10 - --outputFile=particle_gun_MAIA_SIM.edm4hep.root - --gun.multiplicity 10 - --gun.particle "mu-" - --gun.distribution uniform - --gun.thetaMax 2.96 # rougly 170 degrees - --gun.thetaMin -2.96 # rougly -170 degrees - --gun.momentumMin 5000 - --gun.momentumMax 500000 -) +# add_clone_config(URL +# [POST_CLONE ]) +# +# Derives the clone directory, test name (clone_), and fixture name () +# from the URL basename. Skips the clone if the directory already exists. +function(add_clone_config) + cmake_parse_arguments(ARG "" "URL;POST_CLONE" "" ${ARGN}) + get_filename_component(_dir "${ARG_URL}" NAME_WE) + set(_cmd "if [ ! -d ${_dir} ]; then git clone ${ARG_URL} --depth 1; fi") + if(ARG_POST_CLONE) + string(APPEND _cmd " && ${ARG_POST_CLONE}") + endif() + add_test(NAME clone_${_dir} COMMAND bash -c "${_cmd}") + set_tests_properties(clone_${_dir} PROPERTIES FIXTURES_SETUP ${_dir}) +endfunction() -set_tests_properties( - sim_MAIA - PROPERTIES - FIXTURES_SETUP sim_maia_fixture -) +# add_sim_test( +# DDSIM_EXTRA_ARGS ... +# [WORKING_DIRECTORY ] +# [FIXTURES_REQUIRED ...] [FIXTURES_SETUP ]) +# +# Runs ddsim with a fixed 10-event mu- particle gun (multiplicity 10, uniform +# distribution). Detector-specific args (--compactFile, --outputFile, +# --gun.momentumMin/Max, etc.) go in DDSIM_EXTRA_ARGS and are forwarded verbatim. +function(add_sim_test _name) + cmake_parse_arguments(ARG "" + "WORKING_DIRECTORY;FIXTURES_SETUP" + "EXTRA_ARGS;FIXTURES_REQUIRED" ${ARGN}) + if(NOT ARG_WORKING_DIRECTORY) + set(ARG_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + add_test(NAME ${_name} + WORKING_DIRECTORY ${ARG_WORKING_DIRECTORY} + COMMAND ddsim --runType=batch -G -N=10 + --gun.multiplicity 10 + --gun.particle mu- + --gun.distribution uniform + ${ARG_EXTRA_ARGS} + ) + if(ARG_FIXTURES_REQUIRED) + set_tests_properties(${_name} PROPERTIES FIXTURES_REQUIRED "${ARG_FIXTURES_REQUIRED}") + endif() + if(ARG_FIXTURES_SETUP) + set_tests_properties(${_name} PROPERTIES FIXTURES_SETUP "${ARG_FIXTURES_SETUP}") + endif() +endfunction() -add_test(NAME reco_MAIA_Gen3 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKFTrackingAlg.py - --compactFile=$ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml -) -set_test_env(reco_MAIA_Gen3) +# add_k4run_test( +# OPTIONS_FILE +# [WORKING_DIRECTORY ] +# [EXTRA_ARGS ...] +# [FIXTURES_REQUIRED ...] [FIXTURES_SETUP ] +# [USE_TEST_ENV]) +# +# USE_TEST_ENV applies the build-tree env (needed when the option file loads +# this project's Gaudi plugins). +function(add_k4run_test _name) + cmake_parse_arguments(ARG "USE_TEST_ENV" + "OPTIONS_FILE;WORKING_DIRECTORY;FIXTURES_SETUP" + "EXTRA_ARGS;FIXTURES_REQUIRED" ${ARGN}) + if(NOT ARG_WORKING_DIRECTORY) + set(ARG_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + endif() + add_test(NAME ${_name} + WORKING_DIRECTORY ${ARG_WORKING_DIRECTORY} + COMMAND k4run ${ARG_OPTIONS_FILE} ${ARG_EXTRA_ARGS} + ) + if(ARG_USE_TEST_ENV) + set_test_env(${_name}) + endif() + if(ARG_FIXTURES_REQUIRED) + set_tests_properties(${_name} PROPERTIES FIXTURES_REQUIRED "${ARG_FIXTURES_REQUIRED}") + endif() + if(ARG_FIXTURES_SETUP) + set_tests_properties(${_name} PROPERTIES FIXTURES_SETUP "${ARG_FIXTURES_SETUP}") + endif() +endfunction() -set_tests_properties( - reco_MAIA_Gen3 - PROPERTIES - FIXTURES_REQUIRED sim_maia_fixture +# === MAIA chain (no upstream config, no standard reco baseline) === +set(_maia_compact $ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml) + +add_sim_test(sim_MAIA + EXTRA_ARGS + --compactFile=${_maia_compact} + --outputFile=particle_gun_MAIA_SIM.edm4hep.root + --gun.momentumMin 5000 --gun.momentumMax 500000 + --gun.thetaMin -2.96 --gun.thetaMax 2.96 # roughly +/-170 degrees + FIXTURES_SETUP sim_maia_fixture ) -add_test(NAME clone_CLDConfig - COMMAND bash -c "if [ ! -d CLDConfig ]; then git clone https://github.com/key4hep/CLDConfig.git --depth 1; fi && cp -r CLDConfig/CLDConfig . && sed -i -e '/Overlay.Overlay/d' -e '/HighLevelReco.RecoMCTruthLink/d' CLDConfig/CLDReconstruction.py" +add_k4run_test(reco_MAIA_Gen3 + OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKFTrackingAlg.py + EXTRA_ARGS --compactFile=${_maia_compact} + USE_TEST_ENV + FIXTURES_REQUIRED sim_maia_fixture ) -set_tests_properties(clone_CLDConfig PROPERTIES FIXTURES_SETUP CLDConfig) - -add_test(NAME sim_CLD_o2_v08 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig - COMMAND ddsim --compactFile $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml - --outputFile particle_gun_CLD_o2_v08_SIM.edm4hep.root - --runType=batch -G -N=10 - --gun.multiplicity 10 - --gun.particle "mu-" - --gun.distribution uniform - --gun.momentumMin 1500 - --gun.momentumMax 100000 + + +# === CLD chain === +set(_cld_compact $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml) +set(_cld_dir ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig) + +add_clone_config( + URL https://github.com/key4hep/CLDConfig.git + # POST_CLONE "cp -r CLDConfig/CLDConfig . && sed -i -e '/Overlay.Overlay/d' -e '/HighLevelReco.RecoMCTruthLink/d' CLDConfig/CLDReconstruction.py" ) -set_tests_properties(sim_CLD_o2_v08 - PROPERTIES + +add_sim_test(sim_CLD_o2_v08 + WORKING_DIRECTORY ${_cld_dir} + EXTRA_ARGS + --compactFile=${_cld_compact} + --outputFile=particle_gun_CLD_o2_v08_SIM.edm4hep.root + --gun.momentumMin 1500 --gun.momentumMax 100000 FIXTURES_REQUIRED CLDConfig FIXTURES_SETUP sim_CLD_fixture ) -add_test(NAME std_reco_CLD_o2_v08 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig - COMMAND k4run CLDReconstruction.py - --compactFile $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml - --inputFiles=particle_gun_CLD_o2_v08_SIM.edm4hep.root - --trackingOnly - --outputBasename particle_gun_CLD_o2_v08 -) -set_test_env(std_reco_CLD_o2_v08) -set_tests_properties(std_reco_CLD_o2_v08 - PROPERTIES +add_k4run_test(std_reco_CLD_o2_v08 + WORKING_DIRECTORY ${_cld_dir} + OPTIONS_FILE CLDReconstruction.py + EXTRA_ARGS --compactFile ${_cld_compact} + --inputFiles=particle_gun_CLD_o2_v08_SIM.edm4hep.root + --trackingOnly + --outputBasename particle_gun_CLD_o2_v08 FIXTURES_REQUIRED sim_CLD_fixture FIXTURES_SETUP reco_CLD_fixture - ) -add_test(NAME ckf_tracking_CLD_o2_v08 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig - COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/CLD_CKFTracking.py - --compactFile $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml +add_k4run_test(ckf_tracking_CLD_o2_v08 + WORKING_DIRECTORY ${_cld_dir} + OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/CLD_CKFTracking.py + EXTRA_ARGS --compactFile ${_cld_compact} + USE_TEST_ENV + FIXTURES_REQUIRED reco_CLD_fixture ) -set_test_env(ckf_tracking_CLD_o2_v08) -set_tests_properties(ckf_tracking_CLD_o2_v08 PROPERTIES FIXTURES_REQUIRED reco_CLD_fixture) +# === ILD chain === +set(_ild_compact $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml) +set(_ild_dir ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production) -add_test(NAME clone_ILDConfig - COMMAND bash -c "if [ ! -d ILDConfig ]; then git clone https://github.com/iLCSoft/ILDConfig.git --depth 1; fi" -) -set_tests_properties(clone_ILDConfig PROPERTIES FIXTURES_SETUP ILDConfig) -add_test(NAME sim_ILD_FCCee_v01 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production - COMMAND ddsim --compactFile $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml - --outputFile particle_gun_ILD_FCCee_v01_SIM.edm4hep.root - --runType=batch -G -N=10 - --gun.multiplicity 10 - --gun.particle "mu-" - --gun.distribution uniform - --gun.momentumMin 1500 - --gun.momentumMax 1000000 -) -set_tests_properties(sim_ILD_FCCee_v01 - PROPERTIES - FIXTURES_REQUIRED ILDConfig - FIXTURES_SETUP sim_ILD_FCCee_v01_fixture -) +add_clone_config(URL https://github.com/iLCSoft/ILDConfig.git) -add_test(NAME std_reco_ILD_FCCee_v01 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production - COMMAND k4run ILDReconstruction.py - --compactFile $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml - --inputFiles particle_gun_ILD_FCCee_v01_SIM.edm4hep.root - --trackingOnly - --outputFileBase particle_gun_ILD_FCCee_v01 -) -set_tests_properties(std_reco_ILD_FCCee_v01 - PROPERTIES - FIXTURES_REQUIRED sim_ILD_FCCee_v01_fixture - FIXTURES_SETUP reco_ILD_fixture +add_sim_test(sim_ILD_FCCee_v01 + WORKING_DIRECTORY ${_ild_dir} + EXTRA_ARGS + --compactFile=${_ild_compact} + --outputFile=particle_gun_ILD_FCCee_v01_SIM.edm4hep.root + --gun.momentumMin 1500 --gun.momentumMax 1000000 + FIXTURES_REQUIRED ILDConfig + FIXTURES_SETUP sim_ILD_FCCee_v01_fixture ) -add_test(NAME ckf_tracking_ILD_FCCee_v01 - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production - COMMAND k4run ${CMAKE_CURRENT_LIST_DIR}/options/ILD_CKFTracking.py - --compactFile $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml +add_k4run_test(std_reco_ILD_FCCee_v01 + WORKING_DIRECTORY ${_ild_dir} + OPTIONS_FILE ILDReconstruction.py + EXTRA_ARGS --compactFile ${_ild_compact} + --inputFiles particle_gun_ILD_FCCee_v01_SIM.edm4hep.root + --trackingOnly + --outputFileBase particle_gun_ILD_FCCee_v01 + FIXTURES_REQUIRED sim_ILD_FCCee_v01_fixture + FIXTURES_SETUP reco_ILD_fixture ) -set_test_env(ckf_tracking_ILD_FCCee_v01) -set_tests_properties(ckf_tracking_ILD_FCCee_v01 - PROPERTIES - FIXTURES_REQUIRED reco_ILD_fixture + +add_k4run_test(ckf_tracking_ILD_FCCee_v01 + WORKING_DIRECTORY ${_ild_dir} + OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/ILD_CKFTracking.py + EXTRA_ARGS --compactFile ${_ild_compact} + USE_TEST_ENV + FIXTURES_REQUIRED reco_ILD_fixture ) From 436f265dd5fcf3006d3b8f778f9f297272bd12bb Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 15:00:04 +0200 Subject: [PATCH 27/72] Re-use existing helper function --- test/CMakeLists.txt | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3b0b91a8..fa613e15 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,29 +36,6 @@ function(set_test_env _testname) set_tests_properties(${_testname} PROPERTIES ENVIRONMENT "${test_environment}") endfunction() -# add_geometry_load_test( ) -# -# Add a test that loads the passed compact file and converts the geometry into -# an ACTS geometry before dumping it to an .obj file -function(add_geometry_load_test _compact_file) - get_filename_component(_name "${_compact_file}" NAME_WE) - add_test(NAME load_geo_${_name} - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMAND k4run ${PROJECT_SOURCE_DIR}/k4ActsTracking/examples/test_visualize_acts_geo.py - --compactFile "${_compact_file}" - --test-propagation - ) - set_test_env(load_geo_${_name}) -endfunction() - -add_geometry_load_test($ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml) -add_geometry_load_test($ENV{k4geo_DIR}/MuColl/MuSIC/compact/MuSIC_v2/MuSIC_v2.xml) -add_geometry_load_test($ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml) -add_geometry_load_test($ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v02/ILD_FCCee_v02.xml) -add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v07/CLD_o2_v07.xml) -add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml) - - # add_clone_config(URL # [POST_CLONE ]) # @@ -137,6 +114,27 @@ function(add_k4run_test _name) endif() endfunction() +# add_geometry_load_test( ) +# +# Add a test that loads the passed compact file and converts the geometry into +# an ACTS geometry before dumping it to an .obj file +function(add_geometry_load_test _compact_file) + get_filename_component(_name "${_compact_file}" NAME_WE) + add_k4run_test(load_geo_${_name} + OPTIONS_FILE ${PROJECT_SOURCE_DIR}/k4ActsTracking/examples/test_visualize_acts_geo.py + EXTRA_ARGS --compactFile "${_compact_file}" + --test-propagation + USE_TEST_ENV + ) +endfunction() + +add_geometry_load_test($ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml) +add_geometry_load_test($ENV{k4geo_DIR}/MuColl/MuSIC/compact/MuSIC_v2/MuSIC_v2.xml) +add_geometry_load_test($ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml) +add_geometry_load_test($ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v02/ILD_FCCee_v02.xml) +add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v07/CLD_o2_v07.xml) +add_geometry_load_test($ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml) + # === MAIA chain (no upstream config, no standard reco baseline) === set(_maia_compact $ENV{k4geo_DIR}/MuColl/MAIA/compact/MAIA_v0/MAIA_v0.xml) From 8850e09c629b1cbc2b4a472050ddeec994c82ee1 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 15:07:55 +0200 Subject: [PATCH 28/72] Omit post-processing of CLDConfig Not necessary in our case --- test/CMakeLists.txt | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index fa613e15..8ffbc79d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,18 +36,14 @@ function(set_test_env _testname) set_tests_properties(${_testname} PROPERTIES ENVIRONMENT "${test_environment}") endfunction() -# add_clone_config(URL -# [POST_CLONE ]) +# add_clone_config(URL ) # # Derives the clone directory, test name (clone_), and fixture name () # from the URL basename. Skips the clone if the directory already exists. function(add_clone_config) - cmake_parse_arguments(ARG "" "URL;POST_CLONE" "" ${ARGN}) + cmake_parse_arguments(ARG "" "URL" "" ${ARGN}) get_filename_component(_dir "${ARG_URL}" NAME_WE) set(_cmd "if [ ! -d ${_dir} ]; then git clone ${ARG_URL} --depth 1; fi") - if(ARG_POST_CLONE) - string(APPEND _cmd " && ${ARG_POST_CLONE}") - endif() add_test(NAME clone_${_dir} COMMAND bash -c "${_cmd}") set_tests_properties(clone_${_dir} PROPERTIES FIXTURES_SETUP ${_dir}) endfunction() @@ -158,12 +154,9 @@ add_k4run_test(reco_MAIA_Gen3 # === CLD chain === set(_cld_compact $ENV{k4geo_DIR}/FCCee/CLD/compact/CLD_o2_v08/CLD_o2_v08.xml) -set(_cld_dir ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig) +set(_cld_dir ${CMAKE_CURRENT_BINARY_DIR}/CLDConfig/CLDConfig) -add_clone_config( - URL https://github.com/key4hep/CLDConfig.git - # POST_CLONE "cp -r CLDConfig/CLDConfig . && sed -i -e '/Overlay.Overlay/d' -e '/HighLevelReco.RecoMCTruthLink/d' CLDConfig/CLDReconstruction.py" -) +add_clone_config(URL https://github.com/key4hep/CLDConfig.git) add_sim_test(sim_CLD_o2_v08 WORKING_DIRECTORY ${_cld_dir} From 9849091b7794f087f8bcce8b165f9da332efe005 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 15:08:18 +0200 Subject: [PATCH 29/72] Extract common functionality into helpers --- test/CMakeLists.txt | 6 ++ test/options/CLD_CKFTracking.py | 73 +++---------- test/options/ILD_CKFTracking.py | 68 +++---------- test/options/MAIA_CKFTrackingAlg.py | 118 ++++++---------------- test/options/test_helpers/_ckf_helpers.py | 100 ++++++++++++++++++ 5 files changed, 164 insertions(+), 201 deletions(-) create mode 100644 test/options/test_helpers/_ckf_helpers.py diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8ffbc79d..aa55716e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -147,6 +147,8 @@ add_sim_test(sim_MAIA add_k4run_test(reco_MAIA_Gen3 OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/MAIA_CKFTrackingAlg.py EXTRA_ARGS --compactFile=${_maia_compact} + --IOSvc.Input particle_gun_MAIA_SIM.edm4hep.root + --IOSvc.Output particle_gun_MAIA_CKFTracks.edm4hep.root USE_TEST_ENV FIXTURES_REQUIRED sim_maia_fixture ) @@ -183,6 +185,8 @@ add_k4run_test(ckf_tracking_CLD_o2_v08 WORKING_DIRECTORY ${_cld_dir} OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/CLD_CKFTracking.py EXTRA_ARGS --compactFile ${_cld_compact} + --IOSvc.Input particle_gun_CLD_o2_v08_REC.edm4hep.root + --IOSvc.Output particle_gun_CLD_o2_v08_CKFTracks.edm4hep.root USE_TEST_ENV FIXTURES_REQUIRED reco_CLD_fixture ) @@ -219,6 +223,8 @@ add_k4run_test(ckf_tracking_ILD_FCCee_v01 WORKING_DIRECTORY ${_ild_dir} OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/ILD_CKFTracking.py EXTRA_ARGS --compactFile ${_ild_compact} + --IOSvc.Input particle_gun_ILD_FCCee_v01_REC.edm4hep.root + --IOSvc.Output particle_gun_ILD_FCCee_v01_CKFTracks.edm4hep.root USE_TEST_ENV FIXTURES_REQUIRED reco_ILD_fixture ) diff --git a/test/options/CLD_CKFTracking.py b/test/options/CLD_CKFTracking.py index 87e305ea..66b04b4a 100644 --- a/test/options/CLD_CKFTracking.py +++ b/test/options/CLD_CKFTracking.py @@ -18,42 +18,20 @@ # limitations under the License. # -# Options file for the Gen3-geometry CKF tracking algorithm (CKFTrackingAlg). -# Uses ActsGeoSvc for geometry, magnetic field, and hit-to-surface mapping, -# instead of the algorithm-level TGeo file loading used by ACTSSeededCKFTrackingAlg. - -from Gaudi.Configuration import INFO, VERBOSE -from Gaudi.Configurables import ( - GeoSvc, - ActsGeoSvc, - CKFTrackingAlg, - CollectionMerger, - EventDataSvc, -) +from Gaudi.Configuration import INFO from k4FWCore import ApplicationMgr, IOSvc -from k4FWCore.parseArgs import parser -parser.add_argument( - "--compactFile", - help="The geometry compact file to use for reconstruction", - type=str, +from test_helpers._ckf_helpers import ( + make_ckf_tracking, + make_services, + make_hit_mergers, ) -args = parser.parse_known_args()[0] -svcList = [ - GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), - ActsGeoSvc("ActsGeoSvc"), - EventDataSvc("EventDataSvc"), -] -iosvc = IOSvc( - "IOSvc", - Input=["particle_gun_CLD_o2_v08_REC.edm4hep.root"], - Output="particle_gun_CLD_o2_v08_ACTS_CKFTracking.edm4hep.root", -) +svcList = make_services() +iosvc = IOSvc("IOSvc") -hit_merger = CollectionMerger( - "MergeHits", - InputCollections=[ +hit_merger, hit_rel_merger = make_hit_mergers( + hit_collections=[ "VXDTrackerHits", "VXDEndcapTrackerHits", "ITrackerHits", @@ -61,11 +39,7 @@ "ITrackerEndcapHits", "OTrackerEndcapHits", ], - OutputCollection="AllTrackerHits", -) -hit_rel_merger = CollectionMerger( - "MergeHitRelations", - InputCollections=[ + relation_collections=[ "VXDTrackerHitRelations", "VXDEndcapTrackerHitRelations", "InnerTrackerBarrelHitsRelations", @@ -73,33 +47,14 @@ "OuterTrackerBarrelHitsRelations", "OuterTrackerEndcapHitsRelations", ], - OutputCollection="AllTrackerHitRelations", ) -ckf_tracking = CKFTrackingAlg( - "CKFTracking", - RunCKF=True, - CKF_Chi2CutOff=10, - SeedFinding_RMax=150, - SeedFinding_MinPt=500, - SeedFinding_ImpactMax=3, - CKF_NumMeasurementsCutOff=1, - SeedFinding_SigmaScattering=50, - SeedFinding_CollisionRegion=6, - SeedFinding_RadLengthPerSeed=0.1, - # SeedingLayersCellID uses CellIDSelector selection strings, where each - # entry is a comma-separated list of field:value constraints read directly - # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. - # Omitted fields act as wildcards; "|" separates multiple values for a field. - SeedingSensorsCellIDs=["system:1|2", "system:3|4,layer:1"], - OutputTrackCollection="CKFTracks", - OutputSeedCollection="CKFTrackSeeds", - InputTrackerHitCollection=hit_merger.OutputCollection, - InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, - OutputLevel=VERBOSE, +ckf_tracking = make_ckf_tracking( + hit_merger, + hit_rel_merger, + seeding_cellids=["system:1|2", "system:3|4,layer:1"], ) - ApplicationMgr( TopAlg=[hit_merger, hit_rel_merger, ckf_tracking], ExtSvc=svcList, diff --git a/test/options/ILD_CKFTracking.py b/test/options/ILD_CKFTracking.py index 96b27777..f0136cbf 100644 --- a/test/options/ILD_CKFTracking.py +++ b/test/options/ILD_CKFTracking.py @@ -18,79 +18,39 @@ # limitations under the License. # -from Gaudi.Configuration import INFO, VERBOSE -from Gaudi.Configurables import ( - GeoSvc, - ActsGeoSvc, - CKFTrackingAlg, - CollectionMerger, - EventDataSvc, -) +from Gaudi.Configuration import INFO from k4FWCore import ApplicationMgr, IOSvc -from k4FWCore.parseArgs import parser -parser.add_argument( - "--compactFile", - help="The geometry compact file to use for reconstruction", - type=str, +from test_helpers._ckf_helpers import ( + make_ckf_tracking, + make_services, + make_hit_mergers, ) -args = parser.parse_known_args()[0] -svcList = [ - GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), - ActsGeoSvc("ActsGeoSvc"), - EventDataSvc("EventDataSvc"), -] -iosvc = IOSvc( - "IOSvc", - Input=["particle_gun_ILD_FCCee_v01_REC.edm4hep.root"], - Output="particle_gun_ILD_FCCee_v01_ACTS_CKFTracking.edm4hep.root", -) +svcList = make_services() +iosvc = IOSvc("IOSvc") -hit_merger = CollectionMerger( - "MergeHits", - InputCollections=[ +hit_merger, hit_rel_merger = make_hit_mergers( + hit_collections=[ "VertexBarrelTrackerHits", "VertexEndcapTrackerHits", "InnerTrackerBarrelHits", "InnerTrackerEndcapHits", "SETTrackerHits", ], - OutputCollection="AllTrackerHits", -) -hit_rel_merger = CollectionMerger( - "MergeHitRelations", - InputCollections=[ + relation_collections=[ "VertexBarrelTrackerHitRelations", "VertexEndcapTrackerHitRelations", "InnerTrackerBarrelHitRelations", "InnerTrackerEndcapHitRelations", "SETTrackerHitRelations", ], - OutputCollection="AllTrackerHitRelations", ) -ckf_tracking = CKFTrackingAlg( - "CKFTracking", - RunCKF=True, - CKF_Chi2CutOff=10, - SeedFinding_RMax=150, - SeedFinding_MinPt=500, - SeedFinding_ImpactMax=3, - CKF_NumMeasurementsCutOff=1, - SeedFinding_SigmaScattering=50, - SeedFinding_CollisionRegion=6, - SeedFinding_RadLengthPerSeed=0.1, - # SeedingLayersCellID uses CellIDSelector selection strings, where each - # entry is a comma-separated list of field:value constraints read directly - # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. - # Omitted fields act as wildcards; "|" separates multiple values for a field. - SeedingSensorsCellIDs=["system:1|2", "system:3|5,layer:1"], - OutputTrackCollection="CKFTracks", - OutputSeedCollection="CKFTrackSeeds", - InputTrackerHitCollection=hit_merger.OutputCollection, - InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, - OutputLevel=VERBOSE, +ckf_tracking = make_ckf_tracking( + hit_merger, + hit_rel_merger, + seeding_cellids=["system:1|2", "system:3|5,layer:1"], ) ApplicationMgr( diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index c6041d8a..5468fc57 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -18,50 +18,21 @@ # limitations under the License. # -# Options file for the Gen3-geometry CKF tracking algorithm (CKFTrackingAlg). -# Uses ActsGeoSvc for geometry, magnetic field, and hit-to-surface mapping, -# instead of the algorithm-level TGeo file loading used by ACTSSeededCKFTrackingAlg. - -import os - -from Gaudi.Configuration import INFO, VERBOSE -from Gaudi.Configurables import ( - ActsGeoSvc, - CKFTrackingAlg, - CollectionMerger, - DDPlanarDigi, - EventDataSvc, - GeoSvc, -) - +from Gaudi.Configuration import INFO +from Gaudi.Configurables import DDPlanarDigi from k4FWCore import ApplicationMgr, IOSvc -from k4FWCore.parseArgs import parser - -parser.add_argument( - "--compactFile", - help="The geometry compact file to use for reconstruction", - type=str, -) - -args = parser.parse_known_args()[0] - -svcList = [ - GeoSvc("GeoSvc", detectors=[args.compactFile], EnableGeant4Geo=False), - ActsGeoSvc("ActsGeoSvc"), - EventDataSvc("EventDataSvc"), -] - -iosvc = IOSvc( - "IOSvc", - Input=["particle_gun_MAIA_SIM.edm4hep.root"], - Output="maia_ckftrackingalg_reco.edm4hep.root", +from _ckf_helpers import ( + make_ckf_tracking, + make_services, + make_hit_mergers, ) +svcList = make_services() +iosvc = IOSvc("IOSvc") algList = [] - for name in ("VertexBarrel", "VertexEndcap"): algList.append( DDPlanarDigi( @@ -105,62 +76,33 @@ ) ) - -algList.append( - CollectionMerger( - "MergeHits", - InputCollections=[ - "VertexBarrelHits", - "VertexEndcapHits", - "InnerTrackerBarrelHits", - "InnerTrackerEndcapHits", - "OuterTrackerBarrelHits", - "OuterTrackerEndcapHits", - ], - OutputCollection="MergedTrackerHits", - ) -) - -algList.append( - CollectionMerger( - "MergeHitsRelations", - InputCollections=[ - "VertexBarrelHitsRelations", - "VertexEndcapHitsRelations", - "InnerTrackerBarrelHitsRelations", - "InnerTrackerEndcapHitsRelations", - "OuterTrackerBarrelHitsRelations", - "OuterTrackerEndcapHitsRelations", - ], - OutputCollection="MergedTrackerHitsRelations", - ) +hit_merger, hit_rel_merger = make_hit_mergers( + hit_collections=[ + "VertexBarrelHits", + "VertexEndcapHits", + "InnerTrackerBarrelHits", + "InnerTrackerEndcapHits", + "OuterTrackerBarrelHits", + "OuterTrackerEndcapHits", + ], + relation_collections=[ + "VertexBarrelHitsRelations", + "VertexEndcapHitsRelations", + "InnerTrackerBarrelHitsRelations", + "InnerTrackerEndcapHitsRelations", + "OuterTrackerBarrelHitsRelations", + "OuterTrackerEndcapHitsRelations", + ], ) +algList.extend([hit_merger, hit_rel_merger]) -ckf_tracking = CKFTrackingAlg( - "CKFTracking", - RunCKF=True, - CKF_Chi2CutOff=10, - SeedFinding_RMax=150, - SeedFinding_MinPt=500, - SeedFinding_ImpactMax=3, - CKF_NumMeasurementsCutOff=1, - SeedFinding_SigmaScattering=50, - SeedFinding_CollisionRegion=6, - SeedFinding_RadLengthPerSeed=0.1, - # SeedingLayersCellID uses CellIDSelector selection strings, where each - # entry is a comma-separated list of field:value constraints read directly - # from the MAIA_v0 compact XML encoding. Multiple entries are OR-ed. - # Omitted fields act as wildcards; "|" separates multiple values for a field. - SeedingSensorsCellIDs=["system:1", "system:2,layer:1|2|3"], - OutputTrackCollection="CKFTracks", - OutputSeedCollection="CKFTrackSeeds", - InputTrackerHitCollection="MergedTrackerHits", - InputTrackerHitRelationCollection="MergedTrackerHitsRelations", - OutputLevel=VERBOSE, +ckf_tracking = make_ckf_tracking( + hit_merger, + hit_rel_merger, + seeding_cellids=["system:1", "system:2,layer:1|2|3"], ) algList.append(ckf_tracking) - ApplicationMgr( TopAlg=algList, ExtSvc=svcList, OutputLevel=INFO, EvtSel="NONE", EvtMax=-1 ) diff --git a/test/options/test_helpers/_ckf_helpers.py b/test/options/test_helpers/_ckf_helpers.py new file mode 100644 index 00000000..399c6d23 --- /dev/null +++ b/test/options/test_helpers/_ckf_helpers.py @@ -0,0 +1,100 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2014-2024 Key4hep-Project. +# +# This file is part of Key4hep. +# See https://key4hep.github.io/key4hep-doc/ for further info. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from Gaudi.Configuration import VERBOSE +from Gaudi.Configurables import ( + ActsGeoSvc, + CKFTrackingAlg, + CollectionMerger, + EventDataSvc, + GeoSvc, +) +from k4FWCore.parseArgs import parser + + +def _get_compact_file(): + parser.add_argument( + "--compactFile", + help="The geometry compact file to use for reconstruction", + type=str, + ) + args = parser.parse_known_args()[0] + return args.compactFile + + +def make_services(): + """Configure all the necessary services (including getting the geometry from + the command line geometry)""" + compact_file = _get_compact_file() + return [ + GeoSvc("GeoSvc", detectors=[compact_file], EnableGeant4Geo=False), + ActsGeoSvc("ActsGeoSvc"), + EventDataSvc("EventDataSvc"), + ] + + +def make_hit_mergers( + hit_collections, + relation_collections, + hits_output="AllTrackerHits", + relations_output="AllTrackerHitRelations", +): + """Configure the hit (and relation) mergers""" + hit_merger = CollectionMerger( + "MergeHits", + InputCollections=hit_collections, + OutputCollection=hits_output, + ) + hit_rel_merger = CollectionMerger( + "MergeHitRelations", + InputCollections=relation_collections, + OutputCollection=relations_output, + ) + return hit_merger, hit_rel_merger + + +def make_ckf_tracking( + hit_merger, + hit_rel_merger, + seeding_cellids, +): + """Configure the CKFTrackingAlg such that it does **some** tracking + + NOTE: This is really just an example on how to configure it for some + technical tests! This will not work for any meaningful tracking! + """ + return CKFTrackingAlg( + "CKFTracking", + RunCKF=True, + CKF_Chi2CutOff=10, + SeedFinding_RMax=150, + SeedFinding_MinPt=500, + SeedFinding_ImpactMax=3, + CKF_NumMeasurementsCutOff=1, + SeedFinding_SigmaScattering=50, + SeedFinding_CollisionRegion=6, + SeedFinding_RadLengthPerSeed=0.1, + SeedingSensorsCellIDs=seeding_cellids, + OutputTrackCollection="CKFTracks", + OutputSeedCollection="CKFTrackSeeds", + InputTrackerHitCollection=hit_merger.OutputCollection, + InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, + OutputLevel=VERBOSE, + ) From ec65873ab7676dcd2204c66d5bfb85b222e96bc4 Mon Sep 17 00:00:00 2001 From: Thomas Madlener Date: Wed, 29 Apr 2026 16:39:12 +0200 Subject: [PATCH 30/72] Try to set the proper test environment --- test/CMakeLists.txt | 1 + test/options/CLD_CKFTracking.py | 2 +- test/options/ILD_CKFTracking.py | 2 +- test/options/{test_helpers => }/_ckf_helpers.py | 0 4 files changed, 3 insertions(+), 2 deletions(-) rename test/options/{test_helpers => }/_ckf_helpers.py (100%) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index aa55716e..291b3950 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -25,6 +25,7 @@ ${PROJECT_BINARY_DIR}:\ ${PROJECT_BINARY_DIR}/${PROJECT_NAME}:\ $ENV{LD_LIBRARY_PATH}; PYTHONPATH=\ +${CMAKE_CURRENT_SOURCE_DIR}/options:\ ${PROJECT_BINARY_DIR}/${PROJECT_NAME}/genConfDir:\ $ENV{PYTHONPATH};\ GAUDI_PLUGIN_PATH=\ diff --git a/test/options/CLD_CKFTracking.py b/test/options/CLD_CKFTracking.py index 66b04b4a..ce963d85 100644 --- a/test/options/CLD_CKFTracking.py +++ b/test/options/CLD_CKFTracking.py @@ -21,7 +21,7 @@ from Gaudi.Configuration import INFO from k4FWCore import ApplicationMgr, IOSvc -from test_helpers._ckf_helpers import ( +from _ckf_helpers import ( make_ckf_tracking, make_services, make_hit_mergers, diff --git a/test/options/ILD_CKFTracking.py b/test/options/ILD_CKFTracking.py index f0136cbf..fddd0e72 100644 --- a/test/options/ILD_CKFTracking.py +++ b/test/options/ILD_CKFTracking.py @@ -21,7 +21,7 @@ from Gaudi.Configuration import INFO from k4FWCore import ApplicationMgr, IOSvc -from test_helpers._ckf_helpers import ( +from _ckf_helpers import ( make_ckf_tracking, make_services, make_hit_mergers, diff --git a/test/options/test_helpers/_ckf_helpers.py b/test/options/_ckf_helpers.py similarity index 100% rename from test/options/test_helpers/_ckf_helpers.py rename to test/options/_ckf_helpers.py From 3c117808d642adc007b7956bdba4f3421d8df4a1 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Thu, 30 Apr 2026 10:10:08 +0200 Subject: [PATCH 31/72] ckf import fix by Claude --- test/options/CLD_CKFTracking.py | 5 +++++ test/options/ILD_CKFTracking.py | 5 +++++ test/options/MAIA_CKFTrackingAlg.py | 5 +++++ test/options/_ckf_helpers.py | 19 +++++++++++++++---- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/test/options/CLD_CKFTracking.py b/test/options/CLD_CKFTracking.py index ce963d85..ccb11128 100644 --- a/test/options/CLD_CKFTracking.py +++ b/test/options/CLD_CKFTracking.py @@ -18,9 +18,14 @@ # limitations under the License. # +import os +import sys + from Gaudi.Configuration import INFO from k4FWCore import ApplicationMgr, IOSvc +sys.path.insert(0, os.path.dirname(__file__)) + from _ckf_helpers import ( make_ckf_tracking, make_services, diff --git a/test/options/ILD_CKFTracking.py b/test/options/ILD_CKFTracking.py index fddd0e72..d725a702 100644 --- a/test/options/ILD_CKFTracking.py +++ b/test/options/ILD_CKFTracking.py @@ -18,9 +18,14 @@ # limitations under the License. # +import os +import sys + from Gaudi.Configuration import INFO from k4FWCore import ApplicationMgr, IOSvc +sys.path.insert(0, os.path.dirname(__file__)) + from _ckf_helpers import ( make_ckf_tracking, make_services, diff --git a/test/options/MAIA_CKFTrackingAlg.py b/test/options/MAIA_CKFTrackingAlg.py index 5468fc57..15449090 100644 --- a/test/options/MAIA_CKFTrackingAlg.py +++ b/test/options/MAIA_CKFTrackingAlg.py @@ -18,10 +18,15 @@ # limitations under the License. # +import os +import sys + from Gaudi.Configuration import INFO from Gaudi.Configurables import DDPlanarDigi from k4FWCore import ApplicationMgr, IOSvc +sys.path.insert(0, os.path.dirname(__file__)) + from _ckf_helpers import ( make_ckf_tracking, make_services, diff --git a/test/options/_ckf_helpers.py b/test/options/_ckf_helpers.py index 399c6d23..0e72e218 100644 --- a/test/options/_ckf_helpers.py +++ b/test/options/_ckf_helpers.py @@ -60,12 +60,12 @@ def make_hit_mergers( hit_merger = CollectionMerger( "MergeHits", InputCollections=hit_collections, - OutputCollection=hits_output, + OutputCollection=[hits_output], ) hit_rel_merger = CollectionMerger( "MergeHitRelations", InputCollections=relation_collections, - OutputCollection=relations_output, + OutputCollection=[relations_output], ) return hit_merger, hit_rel_merger @@ -80,6 +80,17 @@ def make_ckf_tracking( NOTE: This is really just an example on how to configure it for some technical tests! This will not work for any meaningful tracking! """ + merged_hits = ( + hit_merger.OutputCollection[0] + if isinstance(hit_merger.OutputCollection, list) + else hit_merger.OutputCollection + ) + merged_relations = ( + hit_rel_merger.OutputCollection[0] + if isinstance(hit_rel_merger.OutputCollection, list) + else hit_rel_merger.OutputCollection + ) + return CKFTrackingAlg( "CKFTracking", RunCKF=True, @@ -94,7 +105,7 @@ def make_ckf_tracking( SeedingSensorsCellIDs=seeding_cellids, OutputTrackCollection="CKFTracks", OutputSeedCollection="CKFTrackSeeds", - InputTrackerHitCollection=hit_merger.OutputCollection, - InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, + InputTrackerHitCollection=merged_hits, + InputTrackerHitRelationCollection=merged_relations, OutputLevel=VERBOSE, ) From 6c6d94308a2b02e5e9779cb9845aee524f32cc02 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Thu, 30 Apr 2026 10:25:32 +0200 Subject: [PATCH 32/72] undo hit merger change --- test/options/_ckf_helpers.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/test/options/_ckf_helpers.py b/test/options/_ckf_helpers.py index 0e72e218..3499dbdb 100644 --- a/test/options/_ckf_helpers.py +++ b/test/options/_ckf_helpers.py @@ -60,12 +60,12 @@ def make_hit_mergers( hit_merger = CollectionMerger( "MergeHits", InputCollections=hit_collections, - OutputCollection=[hits_output], + OutputCollection=hits_output, ) hit_rel_merger = CollectionMerger( "MergeHitRelations", InputCollections=relation_collections, - OutputCollection=[relations_output], + OutputCollection=relations_output, ) return hit_merger, hit_rel_merger @@ -80,16 +80,6 @@ def make_ckf_tracking( NOTE: This is really just an example on how to configure it for some technical tests! This will not work for any meaningful tracking! """ - merged_hits = ( - hit_merger.OutputCollection[0] - if isinstance(hit_merger.OutputCollection, list) - else hit_merger.OutputCollection - ) - merged_relations = ( - hit_rel_merger.OutputCollection[0] - if isinstance(hit_rel_merger.OutputCollection, list) - else hit_rel_merger.OutputCollection - ) return CKFTrackingAlg( "CKFTracking", @@ -105,7 +95,7 @@ def make_ckf_tracking( SeedingSensorsCellIDs=seeding_cellids, OutputTrackCollection="CKFTracks", OutputSeedCollection="CKFTrackSeeds", - InputTrackerHitCollection=merged_hits, - InputTrackerHitRelationCollection=merged_relations, + InputTrackerHitCollection=hit_merger.OutputCollection, + InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, OutputLevel=VERBOSE, ) From 7dabc3fc70b378286e8636b655118a3f168f5f71 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Thu, 30 Apr 2026 15:54:05 +0000 Subject: [PATCH 33/72] attempt at fixing mucoll ci --- test/options/_ckf_helpers.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/test/options/_ckf_helpers.py b/test/options/_ckf_helpers.py index 3499dbdb..495f29b0 100644 --- a/test/options/_ckf_helpers.py +++ b/test/options/_ckf_helpers.py @@ -18,6 +18,10 @@ # limitations under the License. # +import importlib.util +import os +import sys + from Gaudi.Configuration import VERBOSE from Gaudi.Configurables import ( ActsGeoSvc, @@ -28,6 +32,12 @@ ) from k4FWCore.parseArgs import parser +def _first_collection_name(value): + """Return a collection name from either a scalar or single-item list property.""" + if isinstance(value, (list, tuple)): + return value[0] + return value + def _get_compact_file(): parser.add_argument( @@ -60,12 +70,12 @@ def make_hit_mergers( hit_merger = CollectionMerger( "MergeHits", InputCollections=hit_collections, - OutputCollection=hits_output, + OutputCollection=[hits_output], ) hit_rel_merger = CollectionMerger( "MergeHitRelations", InputCollections=relation_collections, - OutputCollection=relations_output, + OutputCollection=[relations_output], ) return hit_merger, hit_rel_merger @@ -95,7 +105,7 @@ def make_ckf_tracking( SeedingSensorsCellIDs=seeding_cellids, OutputTrackCollection="CKFTracks", OutputSeedCollection="CKFTrackSeeds", - InputTrackerHitCollection=hit_merger.OutputCollection, - InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, + InputTrackerHitCollection=_first_collection_name(hit_merger.OutputCollection), + InputTrackerHitRelationCollection=_first_collection_name(hit_rel_merger.OutputCollection), OutputLevel=VERBOSE, ) From e28596708b5f50e2edd157c5e8fa0e415769b6ef Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Wed, 13 May 2026 11:50:32 +0200 Subject: [PATCH 34/72] fix missing escape char --- test/CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 291b3950..20b271a0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -23,12 +23,13 @@ set(test_environment "\ LD_LIBRARY_PATH=\ ${PROJECT_BINARY_DIR}:\ ${PROJECT_BINARY_DIR}/${PROJECT_NAME}:\ -$ENV{LD_LIBRARY_PATH}; +$ENV{LD_LIBRARY_PATH};\ PYTHONPATH=\ ${CMAKE_CURRENT_SOURCE_DIR}/options:\ ${PROJECT_BINARY_DIR}/${PROJECT_NAME}/genConfDir:\ $ENV{PYTHONPATH};\ GAUDI_PLUGIN_PATH=\ +${PROJECT_BINARY_DIR}:\ ${PROJECT_BINARY_DIR}/${PROJECT_NAME}:\ $ENV{GAUDI_PLUGIN_PATH}" ) From 16795d57660a988b37a48cb106c9e26752dcd90d Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Wed, 13 May 2026 13:09:24 +0200 Subject: [PATCH 35/72] rolling back unneeded copilot changes --- test/options/_ckf_helpers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/options/_ckf_helpers.py b/test/options/_ckf_helpers.py index 495f29b0..305e4ab0 100644 --- a/test/options/_ckf_helpers.py +++ b/test/options/_ckf_helpers.py @@ -70,12 +70,12 @@ def make_hit_mergers( hit_merger = CollectionMerger( "MergeHits", InputCollections=hit_collections, - OutputCollection=[hits_output], + OutputCollection=hits_output, ) hit_rel_merger = CollectionMerger( "MergeHitRelations", InputCollections=relation_collections, - OutputCollection=[relations_output], + OutputCollection=relations_output, ) return hit_merger, hit_rel_merger @@ -105,7 +105,7 @@ def make_ckf_tracking( SeedingSensorsCellIDs=seeding_cellids, OutputTrackCollection="CKFTracks", OutputSeedCollection="CKFTrackSeeds", - InputTrackerHitCollection=_first_collection_name(hit_merger.OutputCollection), - InputTrackerHitRelationCollection=_first_collection_name(hit_rel_merger.OutputCollection), + InputTrackerHitCollection=hit_merger.OutputCollection, + InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, OutputLevel=VERBOSE, ) From 3760fdf7981803cebb8588caea30b38fa493c1ee Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 26 May 2026 15:17:53 +0200 Subject: [PATCH 36/72] typos --- k4ActsTracking/src/CellIDSelector.cxx | 4 ++-- k4ActsTracking/src/components/ActsGeoSvc.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/k4ActsTracking/src/CellIDSelector.cxx b/k4ActsTracking/src/CellIDSelector.cxx index cd6bf582..ce6c7fbc 100644 --- a/k4ActsTracking/src/CellIDSelector.cxx +++ b/k4ActsTracking/src/CellIDSelector.cxx @@ -34,7 +34,7 @@ namespace { rv::transform([](auto&& subrange) { return std::string_view(subrange.begin(), subrange.end()); }); } - // Implement a very simple polyfill to maintain compatibility with c++20 + // Implement a very simple polyfill to maintain compatibility with c++23 template auto to_vector(R&& range) { std::vector container; if constexpr (std::ranges::sized_range) { @@ -152,7 +152,7 @@ namespace k4ActsTracking { bool CellIDSelector::accept(const dd4hep::CellID cellID) const { for (const auto& selector : m_selectors) { // The passed CellID has to be equal to any of the configured values after - // masking irrelvant parts + // masking irrelevant parts if ((selector.value & selector.mask) == (cellID & selector.mask)) { return true; } diff --git a/k4ActsTracking/src/components/ActsGeoSvc.cpp b/k4ActsTracking/src/components/ActsGeoSvc.cpp index 13c5ac96..97806db3 100644 --- a/k4ActsTracking/src/components/ActsGeoSvc.cpp +++ b/k4ActsTracking/src/components/ActsGeoSvc.cpp @@ -152,7 +152,7 @@ StatusCode ActsGeoSvc::initialize() { } if (m_dumpVisualization.value()) { - info() << "Creating visualiztion" << endmsg; + info() << "Creating visualization" << endmsg; // Adjust the scale here to make it easier to import in blender Acts::ObjVisualization3D vis{4, 0.001}; m_trackingGeo->visualize(vis, gctxt); From a546daf73d1857defb71d49bcc6c51379896243c Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 26 May 2026 15:23:31 +0200 Subject: [PATCH 37/72] match type concrete implementation --- k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h | 5 +++-- test/options/_ckf_helpers.py | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h b/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h index efa20a3e..63a44626 100644 --- a/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h +++ b/k4ActsTracking/include/k4ActsTracking/IActsGeoSvc.h @@ -22,7 +22,8 @@ #include -#include +#include + #include #include #include @@ -41,7 +42,7 @@ namespace Acts { class GAUDI_API IActsGeoSvc : virtual public IService { public: - using CellIDSurfaceMap = std::unordered_map; + using CellIDSurfaceMap = std::unordered_map; public: DeclareInterfaceID(IActsGeoSvc, 1, 0); diff --git a/test/options/_ckf_helpers.py b/test/options/_ckf_helpers.py index 305e4ab0..9e5c22ce 100644 --- a/test/options/_ckf_helpers.py +++ b/test/options/_ckf_helpers.py @@ -18,11 +18,10 @@ # limitations under the License. # -import importlib.util import os import sys -from Gaudi.Configuration import VERBOSE +from Gaudi.Configuration import INFO from Gaudi.Configurables import ( ActsGeoSvc, CKFTrackingAlg, @@ -107,5 +106,5 @@ def make_ckf_tracking( OutputSeedCollection="CKFTrackSeeds", InputTrackerHitCollection=hit_merger.OutputCollection, InputTrackerHitRelationCollection=hit_rel_merger.OutputCollection, - OutputLevel=VERBOSE, + OutputLevel=INFO, ) From e116b0fb58f460feb6bc0a3e06787f68b216f017 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 26 May 2026 15:30:14 +0200 Subject: [PATCH 38/72] removed unused function --- test/options/_ckf_helpers.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/options/_ckf_helpers.py b/test/options/_ckf_helpers.py index 9e5c22ce..0bf487f9 100644 --- a/test/options/_ckf_helpers.py +++ b/test/options/_ckf_helpers.py @@ -31,12 +31,6 @@ ) from k4FWCore.parseArgs import parser -def _first_collection_name(value): - """Return a collection name from either a scalar or single-item list property.""" - if isinstance(value, (list, tuple)): - return value[0] - return value - def _get_compact_file(): parser.add_argument( From 735966852dfbfc3999c2a899da8e10ff1266f58b Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 26 May 2026 15:35:39 +0200 Subject: [PATCH 39/72] made units explicit in property description --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index b81845ce..504b14e8 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -167,9 +167,8 @@ struct CKFTrackingAlg final "Number of sigmas allowed in scattering angle."}; Gaudi::Property m_seedFinding_radLengthPerSeed{this, "SeedFinding_RadLengthPerSeed", 0.1, "Average radiation length per seed."}; - Gaudi::Property m_seedFinding_minPt{this, "SeedFinding_MinPt", 500.0, "Minimum pT of tracks to seed."}; - Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0 * Acts::UnitConstants::mm, - "Maximum d0 of tracks to seed."}; + Gaudi::Property m_seedFinding_minPt{this, "SeedFinding_MinPt", 500.0, "Minimum pT of tracks to seed [MeV]."}; + Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0, "Maximum d0 of tracks to seed [mm]."}; std::vector m_default_empty_vec; Gaudi::Property> m_seedFinding_zBinEdges{this, "SeedFinding_zBinEdges", m_default_empty_vec, From 1f27141218f6542fbc843b93cc86cf8e673b9275 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 26 May 2026 15:40:08 +0200 Subject: [PATCH 40/72] pre-commit --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index 504b14e8..313d5b5d 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -168,7 +168,8 @@ struct CKFTrackingAlg final Gaudi::Property m_seedFinding_radLengthPerSeed{this, "SeedFinding_RadLengthPerSeed", 0.1, "Average radiation length per seed."}; Gaudi::Property m_seedFinding_minPt{this, "SeedFinding_MinPt", 500.0, "Minimum pT of tracks to seed [MeV]."}; - Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0, "Maximum d0 of tracks to seed [mm]."}; + Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0, + "Maximum d0 of tracks to seed [mm]."}; std::vector m_default_empty_vec; Gaudi::Property> m_seedFinding_zBinEdges{this, "SeedFinding_zBinEdges", m_default_empty_vec, From da72dfd982ee3e429c83603b50462aaa2afffc9e Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 26 May 2026 15:46:56 +0200 Subject: [PATCH 41/72] pre-commit --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index 313d5b5d..dd616340 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -168,8 +168,8 @@ struct CKFTrackingAlg final Gaudi::Property m_seedFinding_radLengthPerSeed{this, "SeedFinding_RadLengthPerSeed", 0.1, "Average radiation length per seed."}; Gaudi::Property m_seedFinding_minPt{this, "SeedFinding_MinPt", 500.0, "Minimum pT of tracks to seed [MeV]."}; - Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0, - "Maximum d0 of tracks to seed [mm]."}; + Gaudi::Property m_seedFinding_impactMax{this, "SeedFinding_ImpactMax", 3.0, + "Maximum d0 of tracks to seed [mm]."}; std::vector m_default_empty_vec; Gaudi::Property> m_seedFinding_zBinEdges{this, "SeedFinding_zBinEdges", m_default_empty_vec, From 1fc8342ec218da79c9579d8b2be957efcbd886a9 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 26 May 2026 15:52:24 +0200 Subject: [PATCH 42/72] removing duplicated private flag --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index dd616340..d05cd934 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -213,7 +213,6 @@ struct CKFTrackingAlg final Gaudi::Property m_numThreads{this, "NumThreads", 1, "Number of threads for internal TBB parallelism."}; ///@} -private: SmartIF m_actsGeoSvc; k4ActsTracking::CellIDSelector m_seedSelector{}; From b3c6fe4991bac0b1dd457fc66718d6a77be2c756 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Thu, 28 May 2026 09:27:12 +0200 Subject: [PATCH 43/72] set pythonpath for ild tests --- test/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 20b271a0..7b9f5dc6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -198,6 +198,10 @@ add_k4run_test(ckf_tracking_CLD_o2_v08 set(_ild_compact $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml) set(_ild_dir ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production) +# Explicitly add working directory to PYTHONPATH for ILD tests +set_tests_properties(std_reco_ILD_FCCee_v01 PROPERTIES + ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") + add_clone_config(URL https://github.com/iLCSoft/ILDConfig.git) add_sim_test(sim_ILD_FCCee_v01 From 24686c32111206b4d36b7d0f0ed29df6748195e5 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Thu, 28 May 2026 09:30:01 +0200 Subject: [PATCH 44/72] move set block --- test/CMakeLists.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7b9f5dc6..b08c7db3 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -198,10 +198,6 @@ add_k4run_test(ckf_tracking_CLD_o2_v08 set(_ild_compact $ENV{k4geo_DIR}/FCCee/ILD_FCCee/compact/ILD_FCCee_v01/ILD_FCCee_v01.xml) set(_ild_dir ${CMAKE_CURRENT_BINARY_DIR}/ILDConfig/StandardConfig/production) -# Explicitly add working directory to PYTHONPATH for ILD tests -set_tests_properties(std_reco_ILD_FCCee_v01 PROPERTIES - ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") - add_clone_config(URL https://github.com/iLCSoft/ILDConfig.git) add_sim_test(sim_ILD_FCCee_v01 @@ -225,6 +221,10 @@ add_k4run_test(std_reco_ILD_FCCee_v01 FIXTURES_SETUP reco_ILD_fixture ) +# Explicitly add working directory to PYTHONPATH for ILD tests +set_tests_properties(std_reco_ILD_FCCee_v01 PROPERTIES + ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") + add_k4run_test(ckf_tracking_ILD_FCCee_v01 WORKING_DIRECTORY ${_ild_dir} OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/ILD_CKFTracking.py From f6ca18578c4482b7a2c818038d61b01854703ed3 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Thu, 28 May 2026 09:38:45 +0200 Subject: [PATCH 45/72] move to set property --- test/CMakeLists.txt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b08c7db3..e843e906 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -219,12 +219,9 @@ add_k4run_test(std_reco_ILD_FCCee_v01 --outputFileBase particle_gun_ILD_FCCee_v01 FIXTURES_REQUIRED sim_ILD_FCCee_v01_fixture FIXTURES_SETUP reco_ILD_fixture + USE_TEST_ENV ) -# Explicitly add working directory to PYTHONPATH for ILD tests -set_tests_properties(std_reco_ILD_FCCee_v01 PROPERTIES - ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") - add_k4run_test(ckf_tracking_ILD_FCCee_v01 WORKING_DIRECTORY ${_ild_dir} OPTIONS_FILE ${CMAKE_CURRENT_LIST_DIR}/options/ILD_CKFTracking.py @@ -234,3 +231,9 @@ add_k4run_test(ckf_tracking_ILD_FCCee_v01 USE_TEST_ENV FIXTURES_REQUIRED reco_ILD_fixture ) + +# Explicitly add working directory to PYTHONPATH for ILD tests +set_property(TEST std_reco_ILD_FCCee_v01 APPEND PROPERTY + ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") +set_property(TEST ckf_tracking_ILD_FCCee_v01 APPEND PROPERTY + ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") \ No newline at end of file From 2265de90560637d9e65283fbe62d249e34d31502 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 13:41:38 +0200 Subject: [PATCH 46/72] exclude ILD/CLD tests from mucoll workflow --- .github/workflows/mucoll-ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mucoll-ci.yml b/.github/workflows/mucoll-ci.yml index f11c5d83..2af90797 100644 --- a/.github/workflows/mucoll-ci.yml +++ b/.github/workflows/mucoll-ci.yml @@ -14,6 +14,8 @@ concurrency: jobs: build-mucoll-image: runs-on: ubuntu-latest + env: + CTEST_EXCLUDE_REGEX: '(CLD|ILD)' steps: - name: Free Disk Space (Ubuntu) uses: endersonmenezes/free-disk-space@v2 @@ -59,7 +61,7 @@ jobs: -DCMAKE_INSTALL_PREFIX=$(pwd)/install \ -DCMAKE_CXX_FLAGS=" -fdiagnostics-color=always -Werror -Wno-error=deprecated-declarations" cmake --build build - ctest --test-dir build -j$(nproc) --output-on-failure + ctest --test-dir build -j$(nproc) --output-on-failure -E "${CTEST_EXCLUDE_REGEX}" cmake --build build --target install ccache -s From fbd7588f7e12b6504324e43b0017d3855856d970 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 13:50:40 +0200 Subject: [PATCH 47/72] update mucoll action --- .github/workflows/mucoll-ci.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mucoll-ci.yml b/.github/workflows/mucoll-ci.yml index 2af90797..66a84351 100644 --- a/.github/workflows/mucoll-ci.yml +++ b/.github/workflows/mucoll-ci.yml @@ -18,7 +18,7 @@ jobs: CTEST_EXCLUDE_REGEX: '(CLD|ILD)' steps: - name: Free Disk Space (Ubuntu) - uses: endersonmenezes/free-disk-space@v2 + uses: endersonmenezes/free-disk-space@v3 with: remove_android: true remove_dotnet: true @@ -27,9 +27,11 @@ jobs: remove_swap: true remove_packages: "azure-cli google-cloud-cli microsoft-edge-stable google-chrome-stable firefox postgresql* temurin-* *llvm* mysql* dotnet-sdk-*" remove_packages_one_command: true - remove_folders: "/usr/share/swift /usr/share/miniconda /usr/share/az* /usr/share/glade* /usr/local/lib/node_modules /usr/local/share/chromium /usr/local/share/powershell" + remove_folders: "/usr/share/swift /usr/share/miniconda /usr/share/az* /usr/local/lib/node_modules /usr/local/share/chromium /usr/local/share/powershell /usr/local/julia /usr/local/aws-cli /usr/local/aws-sam-cli /usr/share/gradle" + rm_cmd: "rmz" # Use 'rmz' for faster deletion (default: 'rm') + rmz_version: "3.1.1" # Required when rm_cmd is 'rmz' testing: false - - uses: actions/checkout@v5 + - uses: actions/checkout@v6 - shell: bash run: echo "NOW=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - uses: actions/cache/restore@v4 @@ -70,7 +72,7 @@ jobs: chmod +x ${GITHUB_WORKSPACE}/build.sh docker exec ci_container /bin/bash -c "/packages/build.sh" - - uses: actions/cache/save@v4 + - uses: actions/cache/save@v5 if: always() with: path: ~/.cache/ccache From 145c04a19352ac3ec337c23cc1a636629c802f98 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 14:03:09 +0200 Subject: [PATCH 48/72] update mucoll action --- .github/workflows/mucoll-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mucoll-ci.yml b/.github/workflows/mucoll-ci.yml index 66a84351..b3b9430e 100644 --- a/.github/workflows/mucoll-ci.yml +++ b/.github/workflows/mucoll-ci.yml @@ -15,7 +15,7 @@ jobs: build-mucoll-image: runs-on: ubuntu-latest env: - CTEST_EXCLUDE_REGEX: '(CLD|ILD)' + CTEST_EXCLUDE_REGEX: "(CLD|ILD)" steps: - name: Free Disk Space (Ubuntu) uses: endersonmenezes/free-disk-space@v3 From 45c28d377b0ac68c3135a204e89eecd132cfe14f Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 14:13:18 +0200 Subject: [PATCH 49/72] remote workflow doesn't reproduce local behaviour --- .github/workflows/mucoll-ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/mucoll-ci.yml b/.github/workflows/mucoll-ci.yml index b3b9430e..06fa9c4c 100644 --- a/.github/workflows/mucoll-ci.yml +++ b/.github/workflows/mucoll-ci.yml @@ -14,8 +14,6 @@ concurrency: jobs: build-mucoll-image: runs-on: ubuntu-latest - env: - CTEST_EXCLUDE_REGEX: "(CLD|ILD)" steps: - name: Free Disk Space (Ubuntu) uses: endersonmenezes/free-disk-space@v3 @@ -63,7 +61,7 @@ jobs: -DCMAKE_INSTALL_PREFIX=$(pwd)/install \ -DCMAKE_CXX_FLAGS=" -fdiagnostics-color=always -Werror -Wno-error=deprecated-declarations" cmake --build build - ctest --test-dir build -j$(nproc) --output-on-failure -E "${CTEST_EXCLUDE_REGEX}" + ctest --test-dir build -j$(nproc) --output-on-failure -E "(CLD|ILD)" cmake --build build --target install ccache -s From e7c0492bfd44519ce930da5ac43cf1990e078c86 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 14:25:57 +0200 Subject: [PATCH 50/72] node js 20 action update --- .github/workflows/mucoll-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/mucoll-ci.yml b/.github/workflows/mucoll-ci.yml index 06fa9c4c..7912a3ca 100644 --- a/.github/workflows/mucoll-ci.yml +++ b/.github/workflows/mucoll-ci.yml @@ -32,7 +32,7 @@ jobs: - uses: actions/checkout@v6 - shell: bash run: echo "NOW=$(date +'%Y-%m-%d')" >> $GITHUB_ENV - - uses: actions/cache/restore@v4 + - uses: actions/cache/restore@v5 with: path: ~/.cache/ccache key: ccache-mucoll-image-${{ env.NOW }} From cf42768e990ad38fb46a442300bf8577002da59b Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 14:32:20 +0200 Subject: [PATCH 51/72] add newline to make precommit happy --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e843e906..b387f2c8 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -236,4 +236,4 @@ add_k4run_test(ckf_tracking_ILD_FCCee_v01 set_property(TEST std_reco_ILD_FCCee_v01 APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") set_property(TEST ckf_tracking_ILD_FCCee_v01 APPEND PROPERTY - ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") \ No newline at end of file + ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") From 19e5b0b83bc9c184856b68d165e263f1d12c0491 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 15:35:31 +0200 Subject: [PATCH 52/72] move up --- .github/workflows/downstream-build.yaml | 2 +- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/downstream-build.yaml b/.github/workflows/downstream-build.yaml index f46a6646..fddbec59 100644 --- a/.github/workflows/downstream-build.yaml +++ b/.github/workflows/downstream-build.yaml @@ -17,5 +17,5 @@ jobs: strategy: fail-fast: false steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: key4hep/key4hep-actions/downstream-build@main diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index d05cd934..71c7b3b3 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -87,6 +87,8 @@ using namespace Acts::UnitLiterals; +DECLARE_COMPONENT(CKFTrackingAlg) + template <> struct fmt::formatter : fmt::ostream_formatter {}; /** @@ -221,7 +223,7 @@ struct CKFTrackingAlg final mutable std::mutex m_trackMutex{}; }; -DECLARE_COMPONENT(CKFTrackingAlg) + // --------------------------------------------------------------------------- // Constructor From 6bc94a2ee01b06065995eaf20f21f0357bcd84e1 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 15:36:43 +0200 Subject: [PATCH 53/72] random property --- k4ActsTracking/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/k4ActsTracking/CMakeLists.txt b/k4ActsTracking/CMakeLists.txt index d1583fed..be897ee8 100644 --- a/k4ActsTracking/CMakeLists.txt +++ b/k4ActsTracking/CMakeLists.txt @@ -72,6 +72,7 @@ install(TARGETS k4ActsTrackingPlugins COMPONENT dev) set(GAUDI_GENCONF_DIR "genConfDir") +set_property(TARGET k4ActsTrackingPlugins PROPERTY GAUDI_GENCONF TRUE) function(set_test_env _testname) set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT From 8c8eed762c35d6f3e9c652016a60931fdac98d22 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 16:14:52 +0200 Subject: [PATCH 54/72] at the end --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index 71c7b3b3..dee4fbf0 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -87,8 +87,6 @@ using namespace Acts::UnitLiterals; -DECLARE_COMPONENT(CKFTrackingAlg) - template <> struct fmt::formatter : fmt::ostream_formatter {}; /** @@ -645,3 +643,5 @@ StatusCode CKFTrackingAlg::tracking(const std::vector Date: Mon, 1 Jun 2026 16:47:03 +0200 Subject: [PATCH 55/72] rollback --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index dee4fbf0..d05cd934 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -221,7 +221,7 @@ struct CKFTrackingAlg final mutable std::mutex m_trackMutex{}; }; - +DECLARE_COMPONENT(CKFTrackingAlg) // --------------------------------------------------------------------------- // Constructor @@ -643,5 +643,3 @@ StatusCode CKFTrackingAlg::tracking(const std::vector Date: Mon, 1 Jun 2026 17:29:23 +0200 Subject: [PATCH 56/72] rename to alg? --- .../k4ActsTracking/{CKFTracking.hxx => CKFTrackingAlg.hxx} | 0 k4ActsTracking/src/components/CKFTrackingAlg.cpp | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename k4ActsTracking/include/k4ActsTracking/{CKFTracking.hxx => CKFTrackingAlg.hxx} (100%) diff --git a/k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx b/k4ActsTracking/include/k4ActsTracking/CKFTrackingAlg.hxx similarity index 100% rename from k4ActsTracking/include/k4ActsTracking/CKFTracking.hxx rename to k4ActsTracking/include/k4ActsTracking/CKFTrackingAlg.hxx diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index d05cd934..dacba5c5 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -18,7 +18,7 @@ */ // k4ActsTracking -#include "k4ActsTracking/CKFTracking.hxx" +#include "k4ActsTracking/CKFTrackingAlg.hxx" #include "k4ActsTracking/CellIDSelector.h" #include "k4ActsTracking/Helpers.hxx" #include "k4ActsTracking/IActsGeoSvc.h" From fa1fc298d0c1c1c63a4be02a8d5b98334e490b93 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 18:13:36 +0200 Subject: [PATCH 57/72] CMakelist change --- k4ActsTracking/CMakeLists.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/k4ActsTracking/CMakeLists.txt b/k4ActsTracking/CMakeLists.txt index be897ee8..cec6b38c 100644 --- a/k4ActsTracking/CMakeLists.txt +++ b/k4ActsTracking/CMakeLists.txt @@ -76,9 +76,9 @@ set_property(TARGET k4ActsTrackingPlugins PROPERTY GAUDI_GENCONF TRUE) function(set_test_env _testname) set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT - LD_LIBRARY_PATH=${CMAKE_BINARY_DIR}:$:$:$:$:$:$ENV{LD_LIBRARY_PATH} - PYTHONPATH=${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}/${GAUDI_GENCONF_DIR}:$/../python:$ENV{PYTHONPATH} + LD_LIBRARY_PATH=$:$:$:$:$ENV{LD_LIBRARY_PATH} + PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}/${GAUDI_GENCONF_DIR}:$/../python:$ENV{PYTHONPATH} PATH=$/../bin:$ENV{PATH} K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ - ) + ) endfunction() From e794ee26bb27135c0da1fca4343fece52142fa68 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 18:27:07 +0200 Subject: [PATCH 58/72] obviously another random attempt --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index dacba5c5..d5b0aa25 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -221,8 +221,6 @@ struct CKFTrackingAlg final mutable std::mutex m_trackMutex{}; }; -DECLARE_COMPONENT(CKFTrackingAlg) - // --------------------------------------------------------------------------- // Constructor // --------------------------------------------------------------------------- @@ -643,3 +641,5 @@ StatusCode CKFTrackingAlg::tracking(const std::vector Date: Mon, 1 Jun 2026 18:36:29 +0200 Subject: [PATCH 59/72] obviously another random attempt --- k4ActsTracking/src/components/CKFTrackingAlg.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index d5b0aa25..8031365a 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -642,4 +642,4 @@ StatusCode CKFTrackingAlg::tracking(const std::vector Date: Mon, 1 Jun 2026 19:03:48 +0200 Subject: [PATCH 60/72] one more --- test/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index b387f2c8..ca74f380 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -237,3 +237,7 @@ set_property(TEST std_reco_ILD_FCCee_v01 APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") set_property(TEST ckf_tracking_ILD_FCCee_v01 APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") + +set_tests_properties(ckf_tracking_CLD_o2_v08 PROPERTIES + ENVIRONMENT "GAUDI_PLUGIN_PATH=${CMAKE_INSTALL_PREFIX}/lib:$ENV{GAUDI_PLUGIN_PATH};LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib:$ENV{LD_LIBRARY_PATH}" +) \ No newline at end of file From 264bda266d4350ace26c4ebb2f73dcff33a686cf Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 21:01:11 +0200 Subject: [PATCH 61/72] change test env again --- k4ActsTracking/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/k4ActsTracking/CMakeLists.txt b/k4ActsTracking/CMakeLists.txt index cec6b38c..d6b2fe18 100644 --- a/k4ActsTracking/CMakeLists.txt +++ b/k4ActsTracking/CMakeLists.txt @@ -77,8 +77,8 @@ set_property(TARGET k4ActsTrackingPlugins PROPERTY GAUDI_GENCONF TRUE) function(set_test_env _testname) set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT LD_LIBRARY_PATH=$:$:$:$:$ENV{LD_LIBRARY_PATH} - PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}/${GAUDI_GENCONF_DIR}:$/../python:$ENV{PYTHONPATH} + PYTHONPATH=$/${GAUDI_GENCONF_DIR}:$/../python:$ENV{PYTHONPATH} PATH=$/../bin:$ENV{PATH} K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ ) -endfunction() +endfunction() \ No newline at end of file From b530ae70cb27126b765f63ab2275c5305b86edfb Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 21:14:17 +0200 Subject: [PATCH 62/72] is this the fix? --- k4ActsTracking/CMakeLists.txt | 9 --------- test/CMakeLists.txt | 11 ++++++----- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/k4ActsTracking/CMakeLists.txt b/k4ActsTracking/CMakeLists.txt index d6b2fe18..4acc93b7 100644 --- a/k4ActsTracking/CMakeLists.txt +++ b/k4ActsTracking/CMakeLists.txt @@ -73,12 +73,3 @@ install(TARGETS k4ActsTrackingPlugins set(GAUDI_GENCONF_DIR "genConfDir") set_property(TARGET k4ActsTrackingPlugins PROPERTY GAUDI_GENCONF TRUE) - -function(set_test_env _testname) - set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT - LD_LIBRARY_PATH=$:$:$:$:$ENV{LD_LIBRARY_PATH} - PYTHONPATH=$/${GAUDI_GENCONF_DIR}:$/../python:$ENV{PYTHONPATH} - PATH=$/../bin:$ENV{PATH} - K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ - ) -endfunction() \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index ca74f380..8284439a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,7 +35,12 @@ $ENV{GAUDI_PLUGIN_PATH}" ) function(set_test_env _testname) - set_tests_properties(${_testname} PROPERTIES ENVIRONMENT "${test_environment}") + set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT + LD_LIBRARY_PATH=$:$:$:$:$ENV{LD_LIBRARY_PATH} + PYTHONPATH=$/${GAUDI_GENCONF_DIR}:$/../python:$ENV{PYTHONPATH} + PATH=$/../bin:$ENV{PATH} + K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ + ) endfunction() # add_clone_config(URL ) @@ -237,7 +242,3 @@ set_property(TEST std_reco_ILD_FCCee_v01 APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") set_property(TEST ckf_tracking_ILD_FCCee_v01 APPEND PROPERTY ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") - -set_tests_properties(ckf_tracking_CLD_o2_v08 PROPERTIES - ENVIRONMENT "GAUDI_PLUGIN_PATH=${CMAKE_INSTALL_PREFIX}/lib:$ENV{GAUDI_PLUGIN_PATH};LD_LIBRARY_PATH=${CMAKE_INSTALL_PREFIX}/lib:$ENV{LD_LIBRARY_PATH}" -) \ No newline at end of file From 9aa1a5da2b68ba76c4576f82e152802fac7a0cea Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 21:26:22 +0200 Subject: [PATCH 63/72] hardcode genconf --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8284439a..88936440 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -37,7 +37,7 @@ $ENV{GAUDI_PLUGIN_PATH}" function(set_test_env _testname) set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT LD_LIBRARY_PATH=$:$:$:$:$ENV{LD_LIBRARY_PATH} - PYTHONPATH=$/${GAUDI_GENCONF_DIR}:$/../python:$ENV{PYTHONPATH} + PYTHONPATH=$/genConfDir:$/../python:$ENV{PYTHONPATH} PATH=$/../bin:$ENV{PATH} K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ ) From b773e0293f5079cbd24933560876f788b20a90ab Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 22:05:17 +0200 Subject: [PATCH 64/72] prepend PROJECT_BINARY_DIR --- test/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 88936440..a3726a8d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -36,7 +36,7 @@ $ENV{GAUDI_PLUGIN_PATH}" function(set_test_env _testname) set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT - LD_LIBRARY_PATH=$:$:$:$:$ENV{LD_LIBRARY_PATH} + LD_LIBRARY_PATH=${PROJECT_BINARY_DIR}:$:$:$:$:$ENV{LD_LIBRARY_PATH} PYTHONPATH=$/genConfDir:$/../python:$ENV{PYTHONPATH} PATH=$/../bin:$ENV{PATH} K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ From 3e036770850f46bcf8cb005148bac08bf1f6d317 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 23:34:03 +0200 Subject: [PATCH 65/72] build hook --- .github/workflows/.key4hep-build-env | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/.key4hep-build-env diff --git a/.github/workflows/.key4hep-build-env b/.github/workflows/.key4hep-build-env new file mode 100644 index 00000000..81b9ef50 --- /dev/null +++ b/.github/workflows/.key4hep-build-env @@ -0,0 +1,32 @@ +# Sourced by key4hep/key4hep-actions/key4hep-build from inside the build/ +# directory, immediately before `cmake ..`. +# +# Why this exists +# --------------- +# Some of the stacks we build against already ship a copy of k4ActsTracking +# (notably the LCG `devkey-head` view used for the nightly alma9 job). On the +# `*.hsf.org/key4hep` stacks this is handled by `k4_local_repo`, which makes the +# freshly built tree shadow the in-stack copy -- but `k4_local_repo` does not +# exist in the LCG views, so nothing shadows it there. +# +# Gaudi generates the runtime component list by invoking +# listcomponents --output k4ActsTrackingPlugins.components libk4ActsTrackingPlugins.so +# with the *bare soname*. The dynamic loader resolves that name through +# LD_LIBRARY_PATH, which (in the generated build environment) starts with the +# stack's lib directory and does NOT contain the build tree. It therefore +# introspects the stale in-stack libk4ActsTrackingPlugins.so and copies its +# component list verbatim -- silently dropping any component that is new in this +# checkout (e.g. CKFTrackingAlg). At runtime Gaudi then reports the missing +# component as "unknown (No factory available)". (genconf is unaffected because +# it is handed the absolute build-tree path.) +# +# Prepending the build-tree plugin directory (where libk4ActsTrackingPlugins.so +# is linked) makes the bare soname resolve to the freshly built library. This +# value is captured into the generated build environment (k4actstrackingenv.sh) +# at configure time and is what the `run` wrapper uses when it drives +# listcomponents during the build. Harmless on the stacks where k4_local_repo +# already does the right thing. +# +# NOTE: the action `cd`s into build/ before sourcing this file, so ${PWD} is the +# build directory and ${PWD}/k4ActsTracking is the plugin library output dir. +export LD_LIBRARY_PATH="${PWD}/k4ActsTracking:${LD_LIBRARY_PATH}" From a50c0977a569fb8b0d3347227519898756048b65 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Mon, 1 Jun 2026 23:42:57 +0200 Subject: [PATCH 66/72] rollback test env --- test/CMakeLists.txt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a3726a8d..3cab957c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -35,14 +35,18 @@ $ENV{GAUDI_PLUGIN_PATH}" ) function(set_test_env _testname) - set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT - LD_LIBRARY_PATH=${PROJECT_BINARY_DIR}:$:$:$:$:$ENV{LD_LIBRARY_PATH} - PYTHONPATH=$/genConfDir:$/../python:$ENV{PYTHONPATH} - PATH=$/../bin:$ENV{PATH} - K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ - ) + set_tests_properties(${_testname} PROPERTIES ENVIRONMENT "${test_environment}") endfunction() +#function(set_test_env _testname) +# set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT +# LD_LIBRARY_PATH=${PROJECT_BINARY_DIR}:$:$:$:$:$ENV{LD_LIBRARY_PATH} +# PYTHONPATH=$/genConfDir:$/../python:$ENV{PYTHONPATH} +# PATH=$/../bin:$ENV{PATH} +# K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ +# ) +#endfunction() + # add_clone_config(URL ) # # Derives the clone directory, test name (clone_), and fixture name () From 285c9548e38b52796e607a769552a2d11920d808 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 2 Jun 2026 15:11:05 +0200 Subject: [PATCH 67/72] preload --- test/CMakeLists.txt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 3cab957c..7f1c9721 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -19,7 +19,18 @@ limitations under the License. add_subdirectory(unittests) +# Force-load the freshly built Gaudi plugin module. +# +# When the sourced key4hep stack already ships this package (e.g. the nightly +# view contains an older libk4ActsTrackingPlugins.so), Gaudi's PluginService +# resolves plugin libraries relative to where libGaudiPluginService.so lives +# (the view) and ignores LD_LIBRARY_PATH/GAUDI_PLUGIN_PATH for the dlopen. The +# local build is then shadowed by the stale view copy, so components added on +# this branch (e.g. CKFTrackingAlg) are reported as "No factory available". +# LD_PRELOADing the locally built module makes its factories register first; +# PluginService keeps the first registration, so the local components win. set(test_environment "\ +LD_PRELOAD=$;\ LD_LIBRARY_PATH=\ ${PROJECT_BINARY_DIR}:\ ${PROJECT_BINARY_DIR}/${PROJECT_NAME}:\ From 74fa9750d640a73edfa3a40d3022e39d2eeccb32 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 2 Jun 2026 15:26:10 +0200 Subject: [PATCH 68/72] cleanup of previos fix attempt --- .github/workflows/.key4hep-build-env | 32 ---------------------------- 1 file changed, 32 deletions(-) delete mode 100644 .github/workflows/.key4hep-build-env diff --git a/.github/workflows/.key4hep-build-env b/.github/workflows/.key4hep-build-env deleted file mode 100644 index 81b9ef50..00000000 --- a/.github/workflows/.key4hep-build-env +++ /dev/null @@ -1,32 +0,0 @@ -# Sourced by key4hep/key4hep-actions/key4hep-build from inside the build/ -# directory, immediately before `cmake ..`. -# -# Why this exists -# --------------- -# Some of the stacks we build against already ship a copy of k4ActsTracking -# (notably the LCG `devkey-head` view used for the nightly alma9 job). On the -# `*.hsf.org/key4hep` stacks this is handled by `k4_local_repo`, which makes the -# freshly built tree shadow the in-stack copy -- but `k4_local_repo` does not -# exist in the LCG views, so nothing shadows it there. -# -# Gaudi generates the runtime component list by invoking -# listcomponents --output k4ActsTrackingPlugins.components libk4ActsTrackingPlugins.so -# with the *bare soname*. The dynamic loader resolves that name through -# LD_LIBRARY_PATH, which (in the generated build environment) starts with the -# stack's lib directory and does NOT contain the build tree. It therefore -# introspects the stale in-stack libk4ActsTrackingPlugins.so and copies its -# component list verbatim -- silently dropping any component that is new in this -# checkout (e.g. CKFTrackingAlg). At runtime Gaudi then reports the missing -# component as "unknown (No factory available)". (genconf is unaffected because -# it is handed the absolute build-tree path.) -# -# Prepending the build-tree plugin directory (where libk4ActsTrackingPlugins.so -# is linked) makes the bare soname resolve to the freshly built library. This -# value is captured into the generated build environment (k4actstrackingenv.sh) -# at configure time and is what the `run` wrapper uses when it drives -# listcomponents during the build. Harmless on the stacks where k4_local_repo -# already does the right thing. -# -# NOTE: the action `cd`s into build/ before sourcing this file, so ${PWD} is the -# build directory and ${PWD}/k4ActsTracking is the plugin library output dir. -export LD_LIBRARY_PATH="${PWD}/k4ActsTracking:${LD_LIBRARY_PATH}" From f7a5e7e5f625fa6eaa4b1a51093ff7c94ec5aff6 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 2 Jun 2026 21:09:21 +0200 Subject: [PATCH 69/72] updated ILD tests --- test/CMakeLists.txt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7f1c9721..8e175d40 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -252,8 +252,20 @@ add_k4run_test(ckf_tracking_ILD_FCCee_v01 FIXTURES_REQUIRED reco_ILD_fixture ) -# Explicitly add working directory to PYTHONPATH for ILD tests +# Explicitly add the ILD working directory to PYTHONPATH for ILD tests. +# +# This APPENDs a second PYTHONPATH= entry to the test ENVIRONMENT; CTest applies +# env entries in order, so a later duplicate of the same variable OVERRIDES the +# earlier one set by USE_TEST_ENV. We must therefore keep the local genConfDir +# (and options) ahead of the inherited $ENV{PYTHONPATH} here too — otherwise the +# CVMFS view's k4ActsTrackingPluginsConf (which lacks CKFTrackingAlg) shadows the +# local configurable and k4run fails with "Configurable CKFTrackingAlg not found". +set(_ild_pythonpath "\ +${_ild_dir}:\ +${CMAKE_CURRENT_SOURCE_DIR}/options:\ +${PROJECT_BINARY_DIR}/${PROJECT_NAME}/genConfDir:\ +$ENV{PYTHONPATH}") set_property(TEST std_reco_ILD_FCCee_v01 APPEND PROPERTY - ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") + ENVIRONMENT "PYTHONPATH=${_ild_pythonpath}") set_property(TEST ckf_tracking_ILD_FCCee_v01 APPEND PROPERTY - ENVIRONMENT "PYTHONPATH=${_ild_dir}:$ENV{PYTHONPATH}") + ENVIRONMENT "PYTHONPATH=${_ild_pythonpath}") From 61b57c41d45bdf113b46a9e5d72a5069456b434e Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 2 Jun 2026 21:40:40 +0200 Subject: [PATCH 70/72] comments from Paul --- .../src/components/CKFTrackingAlg.cpp | 33 ++++++++++++------- test/CMakeLists.txt | 9 ----- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/k4ActsTracking/src/components/CKFTrackingAlg.cpp b/k4ActsTracking/src/components/CKFTrackingAlg.cpp index 8031365a..55b765ff 100644 --- a/k4ActsTracking/src/components/CKFTrackingAlg.cpp +++ b/k4ActsTracking/src/components/CKFTrackingAlg.cpp @@ -215,6 +215,10 @@ struct CKFTrackingAlg final SmartIF m_actsGeoSvc; + // Track finder (propagator) is geometry/field dependent only, so it is built + // once in initialize() and reused (read-only) across events and threads. + std::optional m_trackFinder{}; + k4ActsTracking::CellIDSelector m_seedSelector{}; mutable std::mutex m_seedMutex{}; @@ -248,6 +252,18 @@ StatusCode CKFTrackingAlg::initialize() { if (m_seedFinding_deltaRMaxBottom == 0.f) m_seedFinding_deltaRMaxBottom = m_seedFinding_deltaRMax; + // The propagator and CKF only depend on the tracking geometry and magnetic + // field, both available here, so build them once instead of per event. + Navigator::Config navigatorCfg{m_actsGeoSvc->trackingGeometry()}; + navigatorCfg.resolvePassive = false; + navigatorCfg.resolveMaterial = true; + navigatorCfg.resolveSensitive = true; + + Stepper stepper(m_actsGeoSvc->magneticField()); + Navigator navigator(navigatorCfg); + Propagator propagator(std::move(stepper), std::move(navigator)); + m_trackFinder.emplace(std::move(propagator)); + return StatusCode::SUCCESS; } @@ -433,15 +449,7 @@ std::tuple CKFTrackingAlg::o Acts::SeedFinder finder(finderCfg); - Navigator::Config navigatorCfg{m_actsGeoSvc->trackingGeometry()}; - navigatorCfg.resolvePassive = false; - navigatorCfg.resolveMaterial = true; - navigatorCfg.resolveSensitive = true; - - Stepper stepper(m_actsGeoSvc->magneticField()); - Navigator navigator(navigatorCfg); - Propagator propagator(std::move(stepper), std::move(navigator)); - CKF trackFinder(std::move(propagator)); + const CKF& trackFinder = *m_trackFinder; Acts::MeasurementSelector::Config measurementSelectorCfg = { {Acts::GeometryIdentifier(), {{}, {m_CKF_chi2CutOff}, {(std::size_t)(m_CKF_numMeasurementsCutOff)}}}}; @@ -495,12 +503,15 @@ std::tuple CKFTrackingAlg::o } auto parallelSeedingAndTracking = [&](const tbb::blocked_range& r) { + // The magnetic-field cache is mutated on every field lookup, so each + // parallel invocation needs its own cache rather than sharing one. + Acts::MagneticFieldProvider::Cache localMagCache = m_actsGeoSvc->magneticField()->makeCache(magCtx); for (size_t i = r.begin(); i != r.end(); ++i) { const auto paramseeds = findSeeds(finder, finderOpts, spacePointGroups[i], spacePointsGrouping.grid(), - rMiddleSPRange, spContainer.size(), seedCollection, magCache); + rMiddleSPRange, spContainer.size(), seedCollection, localMagCache); if (!m_runCKF) continue; - if (!tracking(paramseeds, trackFinder, ckfOptions, magCache, trackCollection).isSuccess()) { + if (!tracking(paramseeds, trackFinder, ckfOptions, localMagCache, trackCollection).isSuccess()) { warning() << "Tracking failed for this event" << endmsg; } } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8e175d40..17fe74ea 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -49,15 +49,6 @@ function(set_test_env _testname) set_tests_properties(${_testname} PROPERTIES ENVIRONMENT "${test_environment}") endfunction() -#function(set_test_env _testname) -# set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT -# LD_LIBRARY_PATH=${PROJECT_BINARY_DIR}:$:$:$:$:$ENV{LD_LIBRARY_PATH} -# PYTHONPATH=$/genConfDir:$/../python:$ENV{PYTHONPATH} -# PATH=$/../bin:$ENV{PATH} -# K4ACTSTRACKING=${CMAKE_CURRENT_LIST_DIR}/ -# ) -#endfunction() - # add_clone_config(URL ) # # Derives the clone directory, test name (clone_), and fixture name () From 2f38a4f67b594f58501b8a5b5bf1210e1d492785 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 2 Jun 2026 22:36:29 +0200 Subject: [PATCH 71/72] minor cleanup --- .gitignore | 7 +++++++ .../include/k4ActsTracking/ACTSSeededCKFTrackingAlg.hxx | 5 +---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index d988e494..0c80bde2 100644 --- a/.gitignore +++ b/.gitignore @@ -251,3 +251,10 @@ test/gaudi_opts/testConverterConstants.py .claude/ .codex/ .aider.* + +# OS junk +.DS_Store + +# Detector/material files fetched by data/download_files.sh +/data/*.root +/data/*.json diff --git a/k4ActsTracking/include/k4ActsTracking/ACTSSeededCKFTrackingAlg.hxx b/k4ActsTracking/include/k4ActsTracking/ACTSSeededCKFTrackingAlg.hxx index 2916c729..1651b900 100644 --- a/k4ActsTracking/include/k4ActsTracking/ACTSSeededCKFTrackingAlg.hxx +++ b/k4ActsTracking/include/k4ActsTracking/ACTSSeededCKFTrackingAlg.hxx @@ -203,9 +203,6 @@ protected: Gaudi::Property m_numThreads{this, "NumThreads", 1, "Number of threads to use for internal multithreading."}; ///@} - // Thread-safe counter - // mutable Gaudi::Accumulators::Counter<> m_fitFails{this, "FitFails"}; - private: // Mutexes for threadsafe container filling of seeds and tracks mutable std::mutex m_seedMutex{}; @@ -302,7 +299,7 @@ std::vector ACTSSeededCKFTrackingAlg::findSeeds( seedTrack.addToTrackStates(seedTrackState); } - debug() << "Seed Paramemeters" << std::endl << paramseed << endmsg; + debug() << "Seed Parameters" << std::endl << paramseed << endmsg; } debug() << "Seeds found: " << std::endl << paramseeds.size() << endmsg; From f9ddd5441229df2e79256455ea0d21bc6fa37eb1 Mon Sep 17 00:00:00 2001 From: Federico Meloni Date: Tue, 2 Jun 2026 22:47:48 +0200 Subject: [PATCH 72/72] update README --- README.md | 107 +++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 97 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 9e0baf9e..a223e264 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,112 @@ # k4ActsTracking +[![Key4hep build](https://github.com/key4hep/k4ActsTracking/actions/workflows/key4hep-build.yaml/badge.svg)](https://github.com/key4hep/k4ActsTracking/actions/workflows/key4hep-build.yaml) +[![downstream-build](https://github.com/key4hep/k4ActsTracking/actions/workflows/downstream-build.yaml/badge.svg)](https://github.com/key4hep/k4ActsTracking/actions/workflows/downstream-build.yaml) +[![MuColl Image Build](https://github.com/key4hep/k4ActsTracking/actions/workflows/mucoll-ci.yml/badge.svg)](https://github.com/key4hep/k4ActsTracking/actions/workflows/mucoll-ci.yml) +[![pre-commit](https://github.com/key4hep/k4ActsTracking/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/key4hep/k4ActsTracking/actions/workflows/pre-commit.yml) -This repository contains the necessary tools to use ACTS functionality in Key4hep - +`k4ActsTracking` provides [ACTS](https://acts.readthedocs.io/)-based track +reconstruction components for the [Key4hep](https://key4hep.github.io/key4hep-doc/) +software stack. It converts DD4hep tracking geometries into ACTS geometries and +exposes a set of Gaudi algorithms and services (geometry conversion, seeding, +combinatorial Kalman filter track finding, duplicate removal, track filtering +and truth matching) usable from `k4run` option files. ## Dependencies -* Acts +* [ACTS](https://github.com/acts-project/acts) (`Core`, `PluginDD4hep`, + `PluginJson`, `PluginRoot`) +* [DD4hep](https://github.com/AIDASoft/DD4hep) (`DDCore`, `DDRec`) +* [EDM4hep](https://github.com/key4hep/EDM4hep) +* [k4FWCore](https://github.com/key4hep/k4FWCore) +* [Gaudi](https://gitlab.cern.ch/gaudi/Gaudi) +* TBB -* DD4hep +All of these are provided by a Key4hep release. The easiest way to get a working +environment is to source a nightly or stable Key4hep stack, e.g.: -* k4FWCore +```sh +source /cvmfs/sw-nightlies.hsf.org/key4hep/setup.sh +``` -## Installation +## Building +```sh +mkdir -p build install +cmake -B build -S . -GNinja \ + -DCMAKE_CXX_STANDARD=20 \ + -DCMAKE_INSTALL_PREFIX=$(pwd)/install \ + -DCMAKE_CXX_FLAGS=" -fdiagnostics-color=always -Werror -Wno-error=deprecated-declarations" +cmake --build build +cmake --build build --target install +``` +After installing, make the package visible to Gaudi/`k4run`: + +```sh +source ../install/setup.sh # if generated, otherwise set the paths below +# or, manually: +export LD_LIBRARY_PATH=$PWD/install/lib:$LD_LIBRARY_PATH +export PYTHONPATH=$PWD/install/python:$PYTHONPATH ``` -mkdir build install -cd build; -cmake .. -DCMAKE_INSTALL_PREFIX=../install -make install +## Components + +The Gaudi plugin module `k4ActsTrackingPlugins` provides, among others: + +* **`ActsGeoSvc`** — builds an ACTS tracking geometry from a DD4hep compact + file (consumed via `GeoSvc`). Can optionally dump the converted geometry to an + `.obj` file for visualization. +* **`CKFTrackingAlg`** — seeding plus combinatorial Kalman filter (CKF) track + finding on EDM4hep tracker hits. +* **`ACTSSeededCKFTrackingAlg`** — alternative seeded CKF tracking algorithm. +* **`ACTSDuplicateRemoval`** — removes duplicate tracks produced by the CKF. +* **`FilterTracksAlg`** — applies quality cuts to a track collection. +* **`TrackTruthAlg`** — associates reconstructed tracks with truth particles. +* **`ActsTestPropagator`** — propagates ACTS particle-gun tracks through the + converted geometry (useful for geometry validation). + +## Usage + +The algorithms are configured and run through `k4run` option files. See the +examples and the test option files for working configurations: + +* [`k4ActsTracking/examples/test_visualize_acts_geo.py`](k4ActsTracking/examples/test_visualize_acts_geo.py) + — load a compact file, convert it to ACTS geometry, optionally dump an `.obj` + and run the test propagator: + ```sh + k4run k4ActsTracking/examples/test_visualize_acts_geo.py \ + --compactFile .xml --test-propagation + ``` + +* [`test/options/MAIA_CKFTrackingAlg.py`](test/options/MAIA_CKFTrackingAlg.py), + [`test/options/CLD_CKFTracking.py`](test/options/CLD_CKFTracking.py), + [`test/options/ILD_CKFTracking.py`](test/options/ILD_CKFTracking.py) — full + digitization + CKF tracking chains for the MAIA, CLD and ILD detectors. The + shared helpers live in + [`test/options/_ckf_helpers.py`](test/options/_ckf_helpers.py). + + > **Note:** the parameters in these option files are tuned only for technical + > tests and are *not* a meaningful physics tracking configuration. + +## Tests + +Tests are built when `BUILD_TESTING` is on (default) and run with `ctest` from +the build directory: + +```sh +cd build +ctest --output-on-failure ``` + +The suite covers C++ unit tests plus end-to-end chains (`ddsim` simulation → +reconstruction → CKF tracking) for the MAIA, CLD and ILD geometries. The CLD and +ILD chains clone the corresponding upstream config repositories +([`CLDConfig`](https://github.com/key4hep/CLDConfig), +[`ILDConfig`](https://github.com/iLCSoft/ILDConfig)) on the fly, and geometries +are taken from `k4geo` (via `$k4geo_DIR`). + +## License + +Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE).