From 7a1f6f6a5ecb49ba821ad46c48da6f33fe0f8455 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Thu, 19 Mar 2026 17:33:17 +0100 Subject: [PATCH 01/11] do it --- Examples/Scripts/Python/gnn_module_map_odd.py | 22 +++---- Python/Examples/python/reconstruction.py | 57 +++++++++++-------- Python/Examples/tests/root_file_hashes.txt | 18 +++--- Python/Examples/tests/test_examples.py | 8 ++- 4 files changed, 58 insertions(+), 47 deletions(-) diff --git a/Examples/Scripts/Python/gnn_module_map_odd.py b/Examples/Scripts/Python/gnn_module_map_odd.py index de45ce77c79..0681ce7d0f0 100644 --- a/Examples/Scripts/Python/gnn_module_map_odd.py +++ b/Examples/Scripts/Python/gnn_module_map_odd.py @@ -25,7 +25,7 @@ ParticleConfig, addFatras, addDigitization, - addGenParticleSelection, + addDigiParticleSelection, ParticleSelectorConfig, ) from acts.examples.reconstruction import addGnn, addSpacePointsMaking @@ -94,16 +94,6 @@ def runGnnModuleMap( rnd=rnd, ) - addGenParticleSelection( - s, - ParticleSelectorConfig( - rho=(0.0, 24 * u.mm), - absZ=(0.0, 1.0 * u.m), - eta=(-3.0, 3.0), - pt=(150 * u.MeV, None), - ), - ) - # FATRAS simulation addFatras( s, @@ -127,6 +117,16 @@ def runGnnModuleMap( logLevel=acts.logging.INFO, ) + addDigiParticleSelection( + s, + ParticleSelectorConfig( + pt=(1.0 * u.GeV, None), + eta=(-3.0, 3.0), + measurements=(7, None), + removeNeutral=True, + ), + ) + addSpacePointsMaking( s, trackingGeometry, diff --git a/Python/Examples/python/reconstruction.py b/Python/Examples/python/reconstruction.py index a9ff1a5a040..78cbacc7534 100644 --- a/Python/Examples/python/reconstruction.py +++ b/Python/Examples/python/reconstruction.py @@ -1819,6 +1819,7 @@ def addTrackWriters( writeStates: bool = False, writeFitterPerformance: bool = False, writeFinderPerformance: bool = False, + writeFinderNTuple: bool = False, logLevel: Optional[acts.logging.Level] = None, writeCovMat=False, ): @@ -1879,6 +1880,17 @@ def addTrackWriters( ) s.addWriter(trackFinderPerfWriter) + if writeFinderNTuple: + nTupleWriter = RootTrackFinderNTupleWriter( + level=customLogLevel(), + inputTracks="tracks", + inputParticles="particles_selected", + inputParticleMeasurementsMap="particle_measurements_map", + inputTrackParticleMatching="track_particle_matching", + filePath=str(Path(outputDirRoot) / f"ntuple_finding_{name}.root"), + ) + s.addWriter(nTupleWriter) + if outputDirCsv is not None: outputDirCsv = Path(outputDirCsv) if not outputDirCsv.exists(): @@ -1999,24 +2011,24 @@ def addGnn( s.addWhiteboardAlias("protoTracks", findingAlg.config.outputProtoTracks) # Convert proto tracks to tracks - s.addAlgorithm( - acts.examples.ProtoTracksToTracks( - level=customLogLevel(), - inputProtoTracks="protoTracks", - inputMeasurements="measurements", - outputTracks="tracks", - ) + convAlg = acts.examples.ProtoTracksToTracks( + level=customLogLevel(), + inputProtoTracks=findingAlg.config.outputProtoTracks, + inputMeasurements="measurements", + outputTracks="gnn-tracks", ) + s.addAlgorithm(convAlg) + s.addWhiteboardAlias("tracks", convAlg.config.outputTracks) # Truth matching matchAlg = acts.examples.TrackTruthMatcher( level=customLogLevel(), - inputTracks="tracks", - inputParticles="particles", + inputTracks=convAlg.config.outputTracks, + inputParticles="particles_selected", inputMeasurementParticlesMap="measurement_particles_map", outputTrackParticleMatching="gnn_track_particle_matching", outputParticleTrackMatching="gnn_particle_track_matching", - doubleMatching=True, + doubleMatching=False, ) s.addAlgorithm(matchAlg) s.addWhiteboardAlias( @@ -2026,21 +2038,16 @@ def addGnn( "particle_track_matching", matchAlg.config.outputParticleTrackMatching ) - # Optional performance writer - if outputDirRoot is not None: - assert ( - ACTS_EXAMPLES_ROOT_AVAILABLE - ), "ROOT output requested but ROOT is not available" - s.addWriter( - RootTrackFinderNTupleWriter( - level=customLogLevel(), - inputTracks="tracks", - inputParticles="particles", - inputParticleMeasurementsMap="particle_measurements_map", - inputTrackParticleMatching=matchAlg.config.outputTrackParticleMatching, - filePath=str(Path(outputDirRoot) / "performance_track_finding.root"), - ) - ) + addTrackWriters( + s, + name="gnn", + tracks=convAlg.config.outputTracks, + outputDirRoot=outputDirRoot, + outputDirCsv=None, + writeFinderPerformance=True, + writeSummary=False, + writeFinderNTuple=True, + ) return s diff --git a/Python/Examples/tests/root_file_hashes.txt b/Python/Examples/tests/root_file_hashes.txt index 31b990a5c7b..92fc64b1d10 100644 --- a/Python/Examples/tests/root_file_hashes.txt +++ b/Python/Examples/tests/root_file_hashes.txt @@ -7,7 +7,7 @@ test_seeding__estimatedparams.root: 18375210d597e956ff4c37d7c424e409522008c5eedb test_seeding__performance_seeding.root: 992f9c611d30dde0d3f3ab676bab19ada61ab6a4442828e27b65ec5e5b7a2880 test_seeding__particles.root: a0744d4373572ff91bf85d2f52ca18421c092325bb3ec1cf06f17211da5c649b test_seeding__particles_simulation.root: 7fed798e6219f6c33698885ce5b5cb736b5f760f393d03378efb89666f018611 -test_hashing_seeding__estimatedparams.root: 24ae339846e5bba75fadd68c4b05a20f4d06dcf5d15eb5e9219a3d28983eca18 +test_hashing_seeding__estimatedparams.root: 0a95bb2ac04ad4ac428ad3dcf43dfc2e15388a5ca41d30269fdf45855643d270 test_seeding_orthogonal__estimatedparams.root: 18375210d597e956ff4c37d7c424e409522008c5eedb94189fb51342020a6d2a test_seeding_orthogonal__performance_seeding.root: 60fbedcf5cb2b37cd8e526251940564432890d3a159d231ed819e915a904682c test_seeding_orthogonal__particles.root: a0744d4373572ff91bf85d2f52ca18421c092325bb3ec1cf06f17211da5c649b @@ -18,14 +18,14 @@ test_itk_seeding__particles.root: 0a2a55d7e6b0a34216dc56ccc949d129d2fe26985e8cea test_itk_seeding__particles_simulation.root: 2efdfa518f0ff17f0354c52ba40a9785dcaea275a947032d83eeceb8c57dd098 test_propagation__propagation_summary.root: 08a06070a4691c9aee4413af3e0c865e3bc28bd3c3aa6e24d5004b9a02744cee test_material_recording__geant4_material_tracks.root: bbee7c5060c61715cd3d1db7d43933091686d8d6268ba573723b820bbf3439d8 -test_truth_tracking_gsf[generic]__trackstates_gsf.root: 690fd3941d224d99176e65c785bb2afc282f59ff674417528ed76f3b7f24af3b -test_truth_tracking_gsf[generic]__tracksummary_gsf.root: 1f1f3748e5ca8b6623fc1d2d51a983ff24de99c0a09d5fe0e2e85b531abd5b24 -test_truth_tracking_gsf[odd]__trackstates_gsf.root: f30a420f1823ae8185d0e1f15411c8d47e06be007bb91910c3123f485ff5e12b -test_truth_tracking_gsf[odd]__tracksummary_gsf.root: 62dab5c03aefd2496e28d66e465958090b58c42c21142b52d315e1fe541164fb -test_refitting[generic]__trackstates_gsf_refit.root: 3793b0857fcc7c071fe321812aed4864fcafa466df54dcc1848c3fc962cdcf97 -test_refitting[generic]__tracksummary_gsf_refit.root: 58a7447d486728c6185f674dd26c39cfd629b396b3d18384c2f231e6d551aceb -test_refitting[odd]__trackstates_gsf_refit.root: 8c21a00894c4c3158fcbbcad4fd4cf87808d480d48c326577713afda91c780cc -test_refitting[odd]__tracksummary_gsf_refit.root: 63f421f935f0618167d536da3c16e861beb66c5b8d847958aba08823fadfb512 +test_truth_tracking_gsf[generic]__trackstates_gsf.root: e732b7ebecacf6d483956fb464d687440b12ff8a4f706f4a0399801ef038262b +test_truth_tracking_gsf[generic]__tracksummary_gsf.root: 098ac84a47e9b10b8ccf16aff993625553dfb06632ffea61ddad6d8bf9042b88 +test_truth_tracking_gsf[odd]__trackstates_gsf.root: 0cf6ebf890d8809a6beaf8bbcbcf33d012fce1af0b3044305d86290649c12adc +test_truth_tracking_gsf[odd]__tracksummary_gsf.root: e0b4fd4cffb6d49b5737d8908f3c06a6aa38ddd3b38bb5682319de8006f6d922 +test_refitting[generic]__trackstates_gsf_refit.root: c45b7e8c014a32ad81dece4fa452b96ca318bc832f393232b2aaae1b49e1d81f +test_refitting[generic]__tracksummary_gsf_refit.root: 3bb4978789bc33c528145f8a6582b505c3faf69a62bc48c505b21281f9fedfb0 +test_refitting[odd]__trackstates_gsf_refit.root: b6a3e0fcdc268180fd044cd391cb3c13e5a139c7606a39654d8df55494c0b25e +test_refitting[odd]__tracksummary_gsf_refit.root: 215ce336aeb143ec1462ce427b2e76febe7fd3209e735a00d1a5a311a8cb312f test_particle_gun__particles.root: 967b17b2b2c92126d82b847f65176b35024cbf12c1d6c1523abb8c1b6bc9dae4 test_material_mapping__material-map_tracks.root: a01c5b2742ed0ff2d5ceeae8e2a06dd6f7748832538c475a3b2fdb725a39da73 test_material_mapping__propagation-material.root: 8e8eb393b0a5d8a7fd9c1c6933cbe3a196b4e448a98f52740e34c50b95cc156a diff --git a/Python/Examples/tests/test_examples.py b/Python/Examples/tests/test_examples.py index d52f6117560..55c25fccddf 100644 --- a/Python/Examples/tests/test_examples.py +++ b/Python/Examples/tests/test_examples.py @@ -1194,9 +1194,13 @@ def test_gnn_module_map(tmp_path, assert_root_hash, backend, hardware): ) # Verify output - output_file = tmp_path / "performance_track_finding.root" + output_file = tmp_path / "performance_finding_gnn.root" assert output_file.exists() - assert_root_hash("performance_track_finding.root", output_file) + assert_root_hash("performance_finding_gnn.root", output_file) + + output_file = tmp_path / "ntuple_finding_gnn.root" + assert output_file.exists() + assert_root_hash("ntuple_finding_gnn.root", output_file) @pytest.mark.odd From 7744e690c6586f1c0600bf9f036411f48d888a20 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Thu, 19 Mar 2026 17:38:25 +0100 Subject: [PATCH 02/11] lint --- Python/Examples/tests/test_examples.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Python/Examples/tests/test_examples.py b/Python/Examples/tests/test_examples.py index 55c25fccddf..0c8306be11c 100644 --- a/Python/Examples/tests/test_examples.py +++ b/Python/Examples/tests/test_examples.py @@ -1197,7 +1197,7 @@ def test_gnn_module_map(tmp_path, assert_root_hash, backend, hardware): output_file = tmp_path / "performance_finding_gnn.root" assert output_file.exists() assert_root_hash("performance_finding_gnn.root", output_file) - + output_file = tmp_path / "ntuple_finding_gnn.root" assert output_file.exists() assert_root_hash("ntuple_finding_gnn.root", output_file) From 80ab77818b64ed660cf2cdb9ceb3d7e1cfb95999 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 20 Mar 2026 13:09:42 +0100 Subject: [PATCH 03/11] fix spacepoint - track association --- .../TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp | 2 +- Examples/Scripts/Python/gnn.py | 12 ++++++++++++ Examples/Scripts/Python/gnn4itk_example.py | 10 ++++++++++ Python/Examples/tests/test_examples.py | 12 +++++++----- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp b/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp index 580f1de4754..3987871b913 100644 --- a/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp +++ b/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp @@ -226,7 +226,7 @@ ProcessCode TrackFindingAlgorithmGnn::execute( onetrack.reserve(candidate.size()); for (auto i : candidate) { - for (const auto& sl : spacePoints.at(i).sourceLinks()) { + for (const auto& sl : sortedSpacePoints.at(i).sourceLinks()) { onetrack.push_back(sl.template get().index()); } } diff --git a/Examples/Scripts/Python/gnn.py b/Examples/Scripts/Python/gnn.py index a9c02427a28..667d9234883 100755 --- a/Examples/Scripts/Python/gnn.py +++ b/Examples/Scripts/Python/gnn.py @@ -7,6 +7,7 @@ import acts import acts.examples import acts.examples.gnn +from acts.examples.simulation import addDigiParticleSelection, ParticleSelectorConfig from acts.examples.reconstruction import addGnn, addSpacePointsMaking from acts.examples.gnn import ( TorchMetricLearning, @@ -43,6 +44,17 @@ def runGnnMetricLearning( s=s, ) + addDigiParticleSelection( + s, + ParticleSelectorConfig( + pt=(1.0 * u.GeV, None), + eta=(-3.0, 3.0), + measurements=(7, None), + removeNeutral=True, + ), + ) + + addSpacePointsMaking( s, geoSelectionConfigFile=geometrySelection, diff --git a/Examples/Scripts/Python/gnn4itk_example.py b/Examples/Scripts/Python/gnn4itk_example.py index 4edb63e834f..d193b5eb357 100644 --- a/Examples/Scripts/Python/gnn4itk_example.py +++ b/Examples/Scripts/Python/gnn4itk_example.py @@ -16,6 +16,7 @@ import acts import acts.examples from acts.examples.reconstruction import addGnn +from acts.examples.simulation import ParticleSelectorConfig, addDigiParticleSelection from acts.examples.gnn import ( ModuleMapCuda, CudaTrackBuilding, @@ -81,6 +82,15 @@ def runGNN4ITk( ) ) + # Select primary particles with minimum 7 hits and 1 GeV pT for efficiency evaluation + s.addWhiteboardAlias("particles_simulated_selected", "particles") + particleSelectorConfig = ParticleSelectorConfig( + pt=(1.0 * u.GeV, None), + hits=(7, None), + removeSecondaries=True, + ) + addDigiParticleSelection(s, particleSelectorConfig, logLevel=logLevel) + # Configure GNN stages for module map workflow # All parameters hardcoded based on ITk configuration diff --git a/Python/Examples/tests/test_examples.py b/Python/Examples/tests/test_examples.py index 0c8306be11c..ad19cf5e1a8 100644 --- a/Python/Examples/tests/test_examples.py +++ b/Python/Examples/tests/test_examples.py @@ -1095,8 +1095,9 @@ def test_gnn_metric_learning(tmp_path, trk_geo, field, assert_root_hash, hardwar if hardware == "cpu": pytest.skip("CPU not yet supported") - root_file = "performance_track_finding.root" - assert not (tmp_path / root_file).exists() + root_files = ["performance_finding_gnn.root", "ntuple_finding_gnn.root"] + for f in root_files: + assert not (tmp_path / f).exists() # Check if models exist using MODEL_STORAGE environment variable model_storage = os.environ.get("MODEL_STORAGE") @@ -1133,10 +1134,11 @@ def test_gnn_metric_learning(tmp_path, trk_geo, field, assert_root_hash, hardwar print(e.output.decode("utf-8")) raise - rfp = tmp_path / root_file - assert rfp.exists() + for f in root_files: + rfp = tmp_path / f + assert rfp.exists() - assert_root_hash(root_file, rfp) + assert_root_hash(f, rfp) @pytest.mark.odd From a866ff58534910940edc23ca1c6ec839aa0f9bb2 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 20 Mar 2026 13:10:23 +0100 Subject: [PATCH 04/11] lint --- Examples/Scripts/Python/gnn.py | 1 - Examples/Scripts/Python/gnn4itk_example.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Examples/Scripts/Python/gnn.py b/Examples/Scripts/Python/gnn.py index 667d9234883..eb9ac745135 100755 --- a/Examples/Scripts/Python/gnn.py +++ b/Examples/Scripts/Python/gnn.py @@ -54,7 +54,6 @@ def runGnnMetricLearning( ), ) - addSpacePointsMaking( s, geoSelectionConfigFile=geometrySelection, diff --git a/Examples/Scripts/Python/gnn4itk_example.py b/Examples/Scripts/Python/gnn4itk_example.py index d193b5eb357..712117c8eb6 100644 --- a/Examples/Scripts/Python/gnn4itk_example.py +++ b/Examples/Scripts/Python/gnn4itk_example.py @@ -82,7 +82,7 @@ def runGNN4ITk( ) ) - # Select primary particles with minimum 7 hits and 1 GeV pT for efficiency evaluation + # Select primary particles with minimum 7 hits and 1 GeV pT for efficiency evaluation s.addWhiteboardAlias("particles_simulated_selected", "particles") particleSelectorConfig = ParticleSelectorConfig( pt=(1.0 * u.GeV, None), From 06e845142b1445a23c15a45b94350351d6ad498c Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Thu, 19 Mar 2026 11:34:22 +0100 Subject: [PATCH 05/11] build: update ModuleMapGraph to v1.4.0 Headers moved from flat namespace to MMG/ subdirectory in v1.4.0. Update all includes accordingly. Co-Authored-By: Claude Sonnet 4.6 --- .../Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh | 2 +- Plugins/Gnn/src/ModuleMapCuda.cu | 8 ++++---- cmake/ActsExternSources.cmake | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh b/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh index a95dcc2a4d6..64040943e99 100644 --- a/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh +++ b/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh @@ -8,7 +8,7 @@ #pragma once -#include +#include #include #include #include diff --git a/Plugins/Gnn/src/ModuleMapCuda.cu b/Plugins/Gnn/src/ModuleMapCuda.cu index e091e8b78b7..c74df840665 100644 --- a/Plugins/Gnn/src/ModuleMapCuda.cu +++ b/Plugins/Gnn/src/ModuleMapCuda.cu @@ -11,10 +11,10 @@ #include "ActsPlugins/Gnn/detail/CudaUtils.hpp" #include "ActsPlugins/Gnn/detail/ModuleMapUtils.cuh" -#include -#include -#include -#include +#include +#include +#include +#include #include #include diff --git a/cmake/ActsExternSources.cmake b/cmake/ActsExternSources.cmake index 79449190e8e..ecc803a2719 100644 --- a/cmake/ActsExternSources.cmake +++ b/cmake/ActsExternSources.cmake @@ -91,7 +91,7 @@ set(ACTS_ODD_SOURCE mark_as_advanced(ACTS_ODD_SOURCE) set(ACTS_MODULEMAPGRAPH_SOURCE - "GIT_REPOSITORY;https://gitlab.cern.ch/gnn4itkteam/ModuleMapGraph;GIT_TAG;1.2.0" + "GIT_REPOSITORY;https://gitlab.cern.ch/gnn4itkteam/ModuleMapGraph;GIT_TAG;1.4.0" CACHE STRING "Source to take ModuleMapGraph from" ) From 18b632265ad6e6509b25ea55907e67ed910e23b3 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Thu, 19 Mar 2026 11:34:30 +0100 Subject: [PATCH 06/11] test: use new module map files compatible with MMG v1.4.0 The old module map ROOT files stored cut parameters as Double_t branches, which is incompatible with v1.4.0's Float_t reader. Switch to the new pre-built files in new_module_map/ which match the new format. Co-Authored-By: Claude Sonnet 4.6 --- Python/Examples/tests/test_examples.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Python/Examples/tests/test_examples.py b/Python/Examples/tests/test_examples.py index ad19cf5e1a8..1e872c2906b 100644 --- a/Python/Examples/tests/test_examples.py +++ b/Python/Examples/tests/test_examples.py @@ -1158,14 +1158,14 @@ def test_gnn_module_map(tmp_path, assert_root_hash, backend, hardware): # Map backend to file extension model_ext = ".pt" if backend == "torch" else ".onnx" + repo_root = Path(__file__).parent.parent.parent.parent + # Dict of required files - used for checking and as kwargs required_files = { - "moduleMapPath": str(ci_models / "module_map_odd_2k_events.1e-03.float"), + "moduleMapPath": str(repo_root / "new_module_map" / "mm_odd_test"), "gnnModel": str(ci_models / f"gnn_odd_module_map{model_ext}"), } - repo_root = Path(__file__).parent.parent.parent.parent - # Check if all required files exist assert Path(required_files["moduleMapPath"] + ".doublets.root").exists() assert Path(required_files["moduleMapPath"] + ".triplets.root").exists() From 177cd3c9ebaf7daf2f01786d44ecece3979ece62 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 20 Mar 2026 17:05:40 +0100 Subject: [PATCH 07/11] update --- CI/dependencies/download_models.sh | 4 ++-- Examples/Scripts/Python/gnn_module_map_odd.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CI/dependencies/download_models.sh b/CI/dependencies/download_models.sh index 592310b94fd..e270ad74126 100755 --- a/CI/dependencies/download_models.sh +++ b/CI/dependencies/download_models.sh @@ -32,5 +32,5 @@ download \ 1185060ce697bbc96c9dc32b85e5f0eb4db1f64a645c0fc4d2cb2731cb2ef3dc download \ - https://acts.web.cern.ch/ci/gnn/odd_module_map_v01.tar \ - 59f0457f0043bac8594e9f5a3310a709244de980a7b0c206d7d0d95f15455d73 + https://acts.web.cern.ch/ci/gnn/odd_module_map_v02.tar \ + 1954d20ec0d947476dda06a6982774a193a8ad15dca4dec621b53761f329d493 diff --git a/Examples/Scripts/Python/gnn_module_map_odd.py b/Examples/Scripts/Python/gnn_module_map_odd.py index 0681ce7d0f0..aad33b63308 100644 --- a/Examples/Scripts/Python/gnn_module_map_odd.py +++ b/Examples/Scripts/Python/gnn_module_map_odd.py @@ -237,7 +237,7 @@ def runGnnModuleMap( model_storage = os.environ.get("MODEL_STORAGE") assert model_storage is not None, "MODEL_STORAGE environment variable is not set" ci_models_odd = Path(model_storage) - moduleMapPath = str(ci_models_odd / "module_map_odd_2k_events.1e-03.float") + moduleMapPath = str(ci_models_odd / "module_map_odd_2k_events.1e-03.float.v1_3_PATCH") gnnModel = str(ci_models_odd / "gnn_odd_module_map.pt") outputDir = Path.cwd() events = 100 From 5f3d9c1ad67e32ea0e7bc48e900e041ad2d958ee Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 20 Mar 2026 17:11:40 +0100 Subject: [PATCH 08/11] reset stuff to main --- Examples/Scripts/Python/gnn.py | 11 ---------- Examples/Scripts/Python/gnn4itk_example.py | 10 --------- Examples/Scripts/Python/gnn_module_map_odd.py | 22 +++++++++---------- 3 files changed, 11 insertions(+), 32 deletions(-) diff --git a/Examples/Scripts/Python/gnn.py b/Examples/Scripts/Python/gnn.py index eb9ac745135..a9c02427a28 100755 --- a/Examples/Scripts/Python/gnn.py +++ b/Examples/Scripts/Python/gnn.py @@ -7,7 +7,6 @@ import acts import acts.examples import acts.examples.gnn -from acts.examples.simulation import addDigiParticleSelection, ParticleSelectorConfig from acts.examples.reconstruction import addGnn, addSpacePointsMaking from acts.examples.gnn import ( TorchMetricLearning, @@ -44,16 +43,6 @@ def runGnnMetricLearning( s=s, ) - addDigiParticleSelection( - s, - ParticleSelectorConfig( - pt=(1.0 * u.GeV, None), - eta=(-3.0, 3.0), - measurements=(7, None), - removeNeutral=True, - ), - ) - addSpacePointsMaking( s, geoSelectionConfigFile=geometrySelection, diff --git a/Examples/Scripts/Python/gnn4itk_example.py b/Examples/Scripts/Python/gnn4itk_example.py index 712117c8eb6..4edb63e834f 100644 --- a/Examples/Scripts/Python/gnn4itk_example.py +++ b/Examples/Scripts/Python/gnn4itk_example.py @@ -16,7 +16,6 @@ import acts import acts.examples from acts.examples.reconstruction import addGnn -from acts.examples.simulation import ParticleSelectorConfig, addDigiParticleSelection from acts.examples.gnn import ( ModuleMapCuda, CudaTrackBuilding, @@ -82,15 +81,6 @@ def runGNN4ITk( ) ) - # Select primary particles with minimum 7 hits and 1 GeV pT for efficiency evaluation - s.addWhiteboardAlias("particles_simulated_selected", "particles") - particleSelectorConfig = ParticleSelectorConfig( - pt=(1.0 * u.GeV, None), - hits=(7, None), - removeSecondaries=True, - ) - addDigiParticleSelection(s, particleSelectorConfig, logLevel=logLevel) - # Configure GNN stages for module map workflow # All parameters hardcoded based on ITk configuration diff --git a/Examples/Scripts/Python/gnn_module_map_odd.py b/Examples/Scripts/Python/gnn_module_map_odd.py index aad33b63308..b7c95af1078 100644 --- a/Examples/Scripts/Python/gnn_module_map_odd.py +++ b/Examples/Scripts/Python/gnn_module_map_odd.py @@ -25,7 +25,7 @@ ParticleConfig, addFatras, addDigitization, - addDigiParticleSelection, + addGenParticleSelection, ParticleSelectorConfig, ) from acts.examples.reconstruction import addGnn, addSpacePointsMaking @@ -94,6 +94,16 @@ def runGnnModuleMap( rnd=rnd, ) + addGenParticleSelection( + s, + ParticleSelectorConfig( + rho=(0.0, 24 * u.mm), + absZ=(0.0, 1.0 * u.m), + eta=(-3.0, 3.0), + pt=(150 * u.MeV, None), + ), + ) + # FATRAS simulation addFatras( s, @@ -117,16 +127,6 @@ def runGnnModuleMap( logLevel=acts.logging.INFO, ) - addDigiParticleSelection( - s, - ParticleSelectorConfig( - pt=(1.0 * u.GeV, None), - eta=(-3.0, 3.0), - measurements=(7, None), - removeNeutral=True, - ), - ) - addSpacePointsMaking( s, trackingGeometry, From 5f1e0a774c75facd74fbf9a123d22e5879b2d759 Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 20 Mar 2026 17:14:32 +0100 Subject: [PATCH 09/11] update --- .../src/TrackFindingAlgorithmGnn.cpp | 2 +- Python/Examples/python/reconstruction.py | 57 ++++++++----------- Python/Examples/tests/root_file_hashes.txt | 18 +++--- Python/Examples/tests/test_examples.py | 26 ++++----- 4 files changed, 45 insertions(+), 58 deletions(-) diff --git a/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp b/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp index 3987871b913..580f1de4754 100644 --- a/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp +++ b/Examples/Algorithms/TrackFindingGnn/src/TrackFindingAlgorithmGnn.cpp @@ -226,7 +226,7 @@ ProcessCode TrackFindingAlgorithmGnn::execute( onetrack.reserve(candidate.size()); for (auto i : candidate) { - for (const auto& sl : sortedSpacePoints.at(i).sourceLinks()) { + for (const auto& sl : spacePoints.at(i).sourceLinks()) { onetrack.push_back(sl.template get().index()); } } diff --git a/Python/Examples/python/reconstruction.py b/Python/Examples/python/reconstruction.py index 78cbacc7534..a9ff1a5a040 100644 --- a/Python/Examples/python/reconstruction.py +++ b/Python/Examples/python/reconstruction.py @@ -1819,7 +1819,6 @@ def addTrackWriters( writeStates: bool = False, writeFitterPerformance: bool = False, writeFinderPerformance: bool = False, - writeFinderNTuple: bool = False, logLevel: Optional[acts.logging.Level] = None, writeCovMat=False, ): @@ -1880,17 +1879,6 @@ def addTrackWriters( ) s.addWriter(trackFinderPerfWriter) - if writeFinderNTuple: - nTupleWriter = RootTrackFinderNTupleWriter( - level=customLogLevel(), - inputTracks="tracks", - inputParticles="particles_selected", - inputParticleMeasurementsMap="particle_measurements_map", - inputTrackParticleMatching="track_particle_matching", - filePath=str(Path(outputDirRoot) / f"ntuple_finding_{name}.root"), - ) - s.addWriter(nTupleWriter) - if outputDirCsv is not None: outputDirCsv = Path(outputDirCsv) if not outputDirCsv.exists(): @@ -2011,24 +1999,24 @@ def addGnn( s.addWhiteboardAlias("protoTracks", findingAlg.config.outputProtoTracks) # Convert proto tracks to tracks - convAlg = acts.examples.ProtoTracksToTracks( - level=customLogLevel(), - inputProtoTracks=findingAlg.config.outputProtoTracks, - inputMeasurements="measurements", - outputTracks="gnn-tracks", + s.addAlgorithm( + acts.examples.ProtoTracksToTracks( + level=customLogLevel(), + inputProtoTracks="protoTracks", + inputMeasurements="measurements", + outputTracks="tracks", + ) ) - s.addAlgorithm(convAlg) - s.addWhiteboardAlias("tracks", convAlg.config.outputTracks) # Truth matching matchAlg = acts.examples.TrackTruthMatcher( level=customLogLevel(), - inputTracks=convAlg.config.outputTracks, - inputParticles="particles_selected", + inputTracks="tracks", + inputParticles="particles", inputMeasurementParticlesMap="measurement_particles_map", outputTrackParticleMatching="gnn_track_particle_matching", outputParticleTrackMatching="gnn_particle_track_matching", - doubleMatching=False, + doubleMatching=True, ) s.addAlgorithm(matchAlg) s.addWhiteboardAlias( @@ -2038,16 +2026,21 @@ def addGnn( "particle_track_matching", matchAlg.config.outputParticleTrackMatching ) - addTrackWriters( - s, - name="gnn", - tracks=convAlg.config.outputTracks, - outputDirRoot=outputDirRoot, - outputDirCsv=None, - writeFinderPerformance=True, - writeSummary=False, - writeFinderNTuple=True, - ) + # Optional performance writer + if outputDirRoot is not None: + assert ( + ACTS_EXAMPLES_ROOT_AVAILABLE + ), "ROOT output requested but ROOT is not available" + s.addWriter( + RootTrackFinderNTupleWriter( + level=customLogLevel(), + inputTracks="tracks", + inputParticles="particles", + inputParticleMeasurementsMap="particle_measurements_map", + inputTrackParticleMatching=matchAlg.config.outputTrackParticleMatching, + filePath=str(Path(outputDirRoot) / "performance_track_finding.root"), + ) + ) return s diff --git a/Python/Examples/tests/root_file_hashes.txt b/Python/Examples/tests/root_file_hashes.txt index 92fc64b1d10..31b990a5c7b 100644 --- a/Python/Examples/tests/root_file_hashes.txt +++ b/Python/Examples/tests/root_file_hashes.txt @@ -7,7 +7,7 @@ test_seeding__estimatedparams.root: 18375210d597e956ff4c37d7c424e409522008c5eedb test_seeding__performance_seeding.root: 992f9c611d30dde0d3f3ab676bab19ada61ab6a4442828e27b65ec5e5b7a2880 test_seeding__particles.root: a0744d4373572ff91bf85d2f52ca18421c092325bb3ec1cf06f17211da5c649b test_seeding__particles_simulation.root: 7fed798e6219f6c33698885ce5b5cb736b5f760f393d03378efb89666f018611 -test_hashing_seeding__estimatedparams.root: 0a95bb2ac04ad4ac428ad3dcf43dfc2e15388a5ca41d30269fdf45855643d270 +test_hashing_seeding__estimatedparams.root: 24ae339846e5bba75fadd68c4b05a20f4d06dcf5d15eb5e9219a3d28983eca18 test_seeding_orthogonal__estimatedparams.root: 18375210d597e956ff4c37d7c424e409522008c5eedb94189fb51342020a6d2a test_seeding_orthogonal__performance_seeding.root: 60fbedcf5cb2b37cd8e526251940564432890d3a159d231ed819e915a904682c test_seeding_orthogonal__particles.root: a0744d4373572ff91bf85d2f52ca18421c092325bb3ec1cf06f17211da5c649b @@ -18,14 +18,14 @@ test_itk_seeding__particles.root: 0a2a55d7e6b0a34216dc56ccc949d129d2fe26985e8cea test_itk_seeding__particles_simulation.root: 2efdfa518f0ff17f0354c52ba40a9785dcaea275a947032d83eeceb8c57dd098 test_propagation__propagation_summary.root: 08a06070a4691c9aee4413af3e0c865e3bc28bd3c3aa6e24d5004b9a02744cee test_material_recording__geant4_material_tracks.root: bbee7c5060c61715cd3d1db7d43933091686d8d6268ba573723b820bbf3439d8 -test_truth_tracking_gsf[generic]__trackstates_gsf.root: e732b7ebecacf6d483956fb464d687440b12ff8a4f706f4a0399801ef038262b -test_truth_tracking_gsf[generic]__tracksummary_gsf.root: 098ac84a47e9b10b8ccf16aff993625553dfb06632ffea61ddad6d8bf9042b88 -test_truth_tracking_gsf[odd]__trackstates_gsf.root: 0cf6ebf890d8809a6beaf8bbcbcf33d012fce1af0b3044305d86290649c12adc -test_truth_tracking_gsf[odd]__tracksummary_gsf.root: e0b4fd4cffb6d49b5737d8908f3c06a6aa38ddd3b38bb5682319de8006f6d922 -test_refitting[generic]__trackstates_gsf_refit.root: c45b7e8c014a32ad81dece4fa452b96ca318bc832f393232b2aaae1b49e1d81f -test_refitting[generic]__tracksummary_gsf_refit.root: 3bb4978789bc33c528145f8a6582b505c3faf69a62bc48c505b21281f9fedfb0 -test_refitting[odd]__trackstates_gsf_refit.root: b6a3e0fcdc268180fd044cd391cb3c13e5a139c7606a39654d8df55494c0b25e -test_refitting[odd]__tracksummary_gsf_refit.root: 215ce336aeb143ec1462ce427b2e76febe7fd3209e735a00d1a5a311a8cb312f +test_truth_tracking_gsf[generic]__trackstates_gsf.root: 690fd3941d224d99176e65c785bb2afc282f59ff674417528ed76f3b7f24af3b +test_truth_tracking_gsf[generic]__tracksummary_gsf.root: 1f1f3748e5ca8b6623fc1d2d51a983ff24de99c0a09d5fe0e2e85b531abd5b24 +test_truth_tracking_gsf[odd]__trackstates_gsf.root: f30a420f1823ae8185d0e1f15411c8d47e06be007bb91910c3123f485ff5e12b +test_truth_tracking_gsf[odd]__tracksummary_gsf.root: 62dab5c03aefd2496e28d66e465958090b58c42c21142b52d315e1fe541164fb +test_refitting[generic]__trackstates_gsf_refit.root: 3793b0857fcc7c071fe321812aed4864fcafa466df54dcc1848c3fc962cdcf97 +test_refitting[generic]__tracksummary_gsf_refit.root: 58a7447d486728c6185f674dd26c39cfd629b396b3d18384c2f231e6d551aceb +test_refitting[odd]__trackstates_gsf_refit.root: 8c21a00894c4c3158fcbbcad4fd4cf87808d480d48c326577713afda91c780cc +test_refitting[odd]__tracksummary_gsf_refit.root: 63f421f935f0618167d536da3c16e861beb66c5b8d847958aba08823fadfb512 test_particle_gun__particles.root: 967b17b2b2c92126d82b847f65176b35024cbf12c1d6c1523abb8c1b6bc9dae4 test_material_mapping__material-map_tracks.root: a01c5b2742ed0ff2d5ceeae8e2a06dd6f7748832538c475a3b2fdb725a39da73 test_material_mapping__propagation-material.root: 8e8eb393b0a5d8a7fd9c1c6933cbe3a196b4e448a98f52740e34c50b95cc156a diff --git a/Python/Examples/tests/test_examples.py b/Python/Examples/tests/test_examples.py index 1e872c2906b..29d497d5fb4 100644 --- a/Python/Examples/tests/test_examples.py +++ b/Python/Examples/tests/test_examples.py @@ -1095,9 +1095,8 @@ def test_gnn_metric_learning(tmp_path, trk_geo, field, assert_root_hash, hardwar if hardware == "cpu": pytest.skip("CPU not yet supported") - root_files = ["performance_finding_gnn.root", "ntuple_finding_gnn.root"] - for f in root_files: - assert not (tmp_path / f).exists() + root_file = "performance_track_finding.root" + assert not (tmp_path / root_file).exists() # Check if models exist using MODEL_STORAGE environment variable model_storage = os.environ.get("MODEL_STORAGE") @@ -1134,11 +1133,10 @@ def test_gnn_metric_learning(tmp_path, trk_geo, field, assert_root_hash, hardwar print(e.output.decode("utf-8")) raise - for f in root_files: - rfp = tmp_path / f - assert rfp.exists() + rfp = tmp_path / root_file + assert rfp.exists() - assert_root_hash(f, rfp) + assert_root_hash(root_file, rfp) @pytest.mark.odd @@ -1158,14 +1156,14 @@ def test_gnn_module_map(tmp_path, assert_root_hash, backend, hardware): # Map backend to file extension model_ext = ".pt" if backend == "torch" else ".onnx" - repo_root = Path(__file__).parent.parent.parent.parent - # Dict of required files - used for checking and as kwargs required_files = { - "moduleMapPath": str(repo_root / "new_module_map" / "mm_odd_test"), + "moduleMapPath": str(ci_models / "module_map_odd_2k_events.1e-03.float.v1_3_PATCH"), "gnnModel": str(ci_models / f"gnn_odd_module_map{model_ext}"), } + repo_root = Path(__file__).parent.parent.parent.parent + # Check if all required files exist assert Path(required_files["moduleMapPath"] + ".doublets.root").exists() assert Path(required_files["moduleMapPath"] + ".triplets.root").exists() @@ -1196,13 +1194,9 @@ def test_gnn_module_map(tmp_path, assert_root_hash, backend, hardware): ) # Verify output - output_file = tmp_path / "performance_finding_gnn.root" - assert output_file.exists() - assert_root_hash("performance_finding_gnn.root", output_file) - - output_file = tmp_path / "ntuple_finding_gnn.root" + output_file = tmp_path / "performance_track_finding.root" assert output_file.exists() - assert_root_hash("ntuple_finding_gnn.root", output_file) + assert_root_hash("performance_track_finding.root", output_file) @pytest.mark.odd From 6cc0d70013294b23854c25ef41798b60e5267efd Mon Sep 17 00:00:00 2001 From: Benjamin Huth Date: Fri, 20 Mar 2026 17:56:32 +0100 Subject: [PATCH 10/11] linst --- Examples/Scripts/Python/gnn_module_map_odd.py | 4 +++- Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh | 2 +- Plugins/Gnn/src/ModuleMapCuda.cu | 4 ++-- Python/Examples/tests/test_examples.py | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Examples/Scripts/Python/gnn_module_map_odd.py b/Examples/Scripts/Python/gnn_module_map_odd.py index b7c95af1078..cde29f44c87 100644 --- a/Examples/Scripts/Python/gnn_module_map_odd.py +++ b/Examples/Scripts/Python/gnn_module_map_odd.py @@ -237,7 +237,9 @@ def runGnnModuleMap( model_storage = os.environ.get("MODEL_STORAGE") assert model_storage is not None, "MODEL_STORAGE environment variable is not set" ci_models_odd = Path(model_storage) - moduleMapPath = str(ci_models_odd / "module_map_odd_2k_events.1e-03.float.v1_3_PATCH") + moduleMapPath = str( + ci_models_odd / "module_map_odd_2k_events.1e-03.float.v1_3_PATCH" + ) gnnModel = str(ci_models_odd / "gnn_odd_module_map.pt") outputDir = Path.cwd() events = 100 diff --git a/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh b/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh index 64040943e99..e7d90bb9cbb 100644 --- a/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh +++ b/Plugins/Gnn/include/ActsPlugins/Gnn/detail/ModuleMapUtils.cuh @@ -8,11 +8,11 @@ #pragma once -#include #include #include #include +#include #include #define USE_LAUNCH_BOUNDS diff --git a/Plugins/Gnn/src/ModuleMapCuda.cu b/Plugins/Gnn/src/ModuleMapCuda.cu index c74df840665..18e5a0ad709 100644 --- a/Plugins/Gnn/src/ModuleMapCuda.cu +++ b/Plugins/Gnn/src/ModuleMapCuda.cu @@ -11,12 +11,12 @@ #include "ActsPlugins/Gnn/detail/CudaUtils.hpp" #include "ActsPlugins/Gnn/detail/ModuleMapUtils.cuh" +#include + #include #include #include #include -#include - #include #include #include diff --git a/Python/Examples/tests/test_examples.py b/Python/Examples/tests/test_examples.py index 29d497d5fb4..472e71dcd36 100644 --- a/Python/Examples/tests/test_examples.py +++ b/Python/Examples/tests/test_examples.py @@ -1158,7 +1158,9 @@ def test_gnn_module_map(tmp_path, assert_root_hash, backend, hardware): # Dict of required files - used for checking and as kwargs required_files = { - "moduleMapPath": str(ci_models / "module_map_odd_2k_events.1e-03.float.v1_3_PATCH"), + "moduleMapPath": str( + ci_models / "module_map_odd_2k_events.1e-03.float.v1_3_PATCH" + ), "gnnModel": str(ci_models / f"gnn_odd_module_map{model_ext}"), } From b240fa8430ccde4041b25e9e12f146dc9a1d9443 Mon Sep 17 00:00:00 2001 From: Benjamin Huth <37871400+benjaminhuth@users.noreply.github.com> Date: Sun, 22 Mar 2026 13:37:44 +0100 Subject: [PATCH 11/11] Update root_file_hashes.txt with new hash values --- Python/Examples/tests/root_file_hashes.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Python/Examples/tests/root_file_hashes.txt b/Python/Examples/tests/root_file_hashes.txt index 6ebdf7314fd..71806bb201e 100644 --- a/Python/Examples/tests/root_file_hashes.txt +++ b/Python/Examples/tests/root_file_hashes.txt @@ -76,9 +76,9 @@ test_root_clusters_writer[configKwConstructor]__clusters.root: e842df4fe04eefff3 test_root_clusters_writer[kwargsConstructor]__clusters.root: e842df4fe04eefff3df5f32cd1026e93286be62b8040dc700a2aff557c56dec8 test_gnn_metric_learning[gpu]__performance_finding_gnn.root: c17fb877bb165e28db0a2b99881763093e7fc9a707c045feb6a6a6b68e0dd660 test_gnn_metric_learning[gpu]__ntuple_finding_gnn.root: 3f0fb36af55441994a154ea2a93978ba1930d4e87bf043f8ae9527e283bf1894 -test_gnn_module_map[gpu-torch]__ntuple_finding_gnn.root: ae0c828b57f4d7b7e608fa92175af418647aece75b7ac1946bf30eb8e617d046 +test_gnn_module_map[gpu-torch]__ntuple_finding_gnn.root: 4ba97c2ab6595e07886d69217819168d5b93520ea61286fe7b19b807440a1e63 test_gnn_module_map[gpu-torch]__performance_finding_gnn.root: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 -test_gnn_module_map[gpu-onnx]__ntuple_finding_gnn.root: 664be95535722685effd783b148ed2cec61906aee20f5a531bedbd5253d2b101 +test_gnn_module_map[gpu-onnx]__ntuple_finding_gnn.root: d159b21aab3e05561023bd9c79e0ae45e37b467f2aab8f4f8ab4c6ace7dfe0ae test_gnn_module_map[gpu-onnx]__performance_finding_gnn.root: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 test_ML_Ambiguity_Solver__performance_finding_ambiML.root: c17fb877bb165e28db0a2b99881763093e7fc9a707c045feb6a6a6b68e0dd660 test_truth_tracking_kalman[generic-0.0]__trackstates_kf.root: 42a49abcef0277ca061350cc03c1ac9841e119055e2778662ec6a731e316ecd2