From ad47ae893b8cc1b1c168f813bb4947f604985110 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Wed, 5 Feb 2025 14:30:22 +0100 Subject: [PATCH 01/19] WIP --- .../ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp | 4 +-- .../src/EDM4hepMeasurementOutputConverter.cpp | 12 +++---- Examples/Io/EDM4hep/src/EDM4hepUtil.cpp | 36 +++++++++++++------ 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp index ed3217f947d..d24cbe15c34 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp @@ -106,9 +106,7 @@ VariableBoundMeasurementProxy readMeasurement( /// - cluster channels are written to inappropriate fields /// - local 2D coordinates and time are written to position void writeMeasurement(const ConstVariableBoundMeasurementProxy& from, - edm4hep::MutableTrackerHitPlane to, - const Cluster* fromCluster, - edm4hep::TrackerHit3DCollection& toClusters, + edm4hep::MutableTrackerHitLocal to, const MapGeometryIdTo& geometryMapper); /// Writes a trajectory to EDM4hep. diff --git a/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp b/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp index 754a61027e9..bb582cd803d 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp @@ -34,8 +34,9 @@ ProcessCode EDM4hepMeasurementOutputConverter::execute( const AlgorithmContext& ctx) const { ClusterContainer clusters; - edm4hep::TrackerHitPlaneCollection hitsPlane; - edm4hep::TrackerHit3DCollection hits; + podio::Frame frame; + + edm4hep::TrackerHitLocalCollection hits; if (!m_cfg.inputClusters.empty()) { ACTS_VERBOSE("Fetch clusters for writing: " << m_cfg.inputClusters); @@ -50,15 +51,12 @@ ProcessCode EDM4hepMeasurementOutputConverter::execute( for (Index hitIdx = 0u; hitIdx < measurements.size(); ++hitIdx) { ConstVariableBoundMeasurementProxy from = measurements.getMeasurement(hitIdx); - const Cluster* fromCluster = clusters.empty() ? nullptr : &clusters[hitIdx]; - auto to = hitsPlane.create(); + auto to = hits.create(); EDM4hepUtil::writeMeasurement( - from, to, fromCluster, hits, - [](Acts::GeometryIdentifier id) { return id.value(); }); + from, to, [](Acts::GeometryIdentifier id) { return id.value(); }); } - m_outputTrackerHitsPlane(ctx, std::move(hitsPlane)); m_outputTrackerHitsRaw(ctx, std::move(hits)); return ProcessCode::SUCCESS; diff --git a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp index acf2e701953..6d12510950a 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp @@ -177,9 +177,7 @@ VariableBoundMeasurementProxy EDM4hepUtil::readMeasurement( void EDM4hepUtil::writeMeasurement( const ConstVariableBoundMeasurementProxy& from, - edm4hep::MutableTrackerHitPlane to, const Cluster* /*fromCluster*/, - edm4hep::TrackerHit3DCollection& /*toClusters*/, - const MapGeometryIdTo& geometryMapper) { + edm4hep::MutableTrackerHitLocal to, const MapGeometryIdTo& geometryMapper) { Acts::GeometryIdentifier geoId = from.geometryId(); if (geometryMapper) { @@ -197,14 +195,30 @@ void EDM4hepUtil::writeMeasurement( to.setPosition({parameters[Acts::eBoundLoc0], parameters[Acts::eBoundLoc1], parameters[Acts::eBoundTime]}); - to.setCovMatrix({ - static_cast(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)), - static_cast(covariance(Acts::eBoundLoc1, Acts::eBoundLoc0)), - static_cast(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)), - 0, - 0, - 0, - }); + for (double value : std::span{ + covariance.data(), + static_cast(covariance.size() * covariance.size())}) { + to.addToCovariance(value); + } + + // to.addToCovariance + + // std::span(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)), + // static_cast(covariance(Acts::eBoundLoc1, Acts::eBoundLoc0)), + // static_cast(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)), + // 0, + // 0, + // 0, + // }); // @TODO: Check if we can write cell info } From c4e49471b7691c6a80dfd521df1f52958e18900a Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Wed, 5 Mar 2025 16:58:46 +0100 Subject: [PATCH 02/19] (wip) refactoring of edm4hep writer to wip TrackerHitLocal --- .../EDM4hepMeasurementOutputConverter.hpp | 18 +- .../ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp | 9 +- .../src/EDM4hepMeasurementOutputConverter.cpp | 24 +- Examples/Io/EDM4hep/src/EDM4hepUtil.cpp | 62 ++--- Plugins/EDM4hep/edm.yml | 20 ++ .../ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 50 +++- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 90 ++++++ Python/Examples/src/plugins/EDM4hep.cpp | 3 +- .../UnitTests/Plugins/EDM4hep/CMakeLists.txt | 8 +- .../EDM4hep/EDM4HepMeasurementWriteTests.cpp | 261 ++++++++++++++++++ 10 files changed, 469 insertions(+), 76 deletions(-) create mode 100644 Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp index 164c0791cfe..fce28793a76 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp @@ -33,12 +33,12 @@ class EDM4hepMeasurementOutputConverter final : public PodioOutputConverter { struct Config { /// Which measurement collection to write. std::string inputMeasurements; - /// Which cluster collection to write (optional) - std::string inputClusters; - /// Name of the output tracker hit plane collection. - std::string outputTrackerHitsPlane = "ActsTrackerHitsPlane"; /// Name of the output tracker hit raw collection. - std::string outputTrackerHitsRaw = "ActsTrackerHitsRaw"; + std::string outputTrackerHitsLocal = "ActsTrackerHitsLocal"; + + /// Map of surface by identifier to allow local - to global + std::unordered_map + surfaceByIdentifier; }; /// Constructor with @@ -64,12 +64,8 @@ class EDM4hepMeasurementOutputConverter final : public PodioOutputConverter { ReadDataHandle m_inputMeasurements{this, "InputMeasurements"}; - ReadDataHandle m_inputClusters{this, "InputClusters"}; - - CollectionBaseWriteHandle m_outputTrackerHitsPlane{this, - "OutputTrackerHitsPlane"}; - CollectionBaseWriteHandle m_outputTrackerHitsRaw{this, - "OutputTrackerHitsRaw"}; + CollectionBaseWriteHandle m_outputTrackerHitsLocal{this, + "OutputTrackerHitsLocal"}; }; } // namespace ActsExamples diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp index d24cbe15c34..f09421a4083 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp @@ -14,7 +14,7 @@ #include "ActsExamples/EventData/SimParticle.hpp" #include "ActsExamples/EventData/Trajectories.hpp" #include "ActsFatras/EventData/Hit.hpp" -#include "ActsPlugins/EDM4hep/TrackerHitCompatibility.hpp" +#include #include @@ -105,9 +105,10 @@ VariableBoundMeasurementProxy readMeasurement( /// Known issues: /// - cluster channels are written to inappropriate fields /// - local 2D coordinates and time are written to position -void writeMeasurement(const ConstVariableBoundMeasurementProxy& from, - edm4hep::MutableTrackerHitLocal to, - const MapGeometryIdTo& geometryMapper); +void writeMeasurement(const Acts::GeometryContext& gctx, + const ConstVariableBoundMeasurementProxy& from, + ActsPodioEdm::MutableTrackerHitLocal to, + const Acts::Surface& surface); /// Writes a trajectory to EDM4hep. /// diff --git a/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp b/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp index bb582cd803d..9e99a939d97 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp @@ -11,6 +11,10 @@ #include "ActsExamples/EventData/Cluster.hpp" #include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp" +#include "ActsPlugins/EDM4hep/TrackerHitCompatibility.hpp" +#include + +#include #include #include @@ -25,23 +29,14 @@ EDM4hepMeasurementOutputConverter::EDM4hepMeasurementOutputConverter( std::move(logger)), m_cfg(config) { m_inputMeasurements.initialize(m_cfg.inputMeasurements); - m_inputClusters.maybeInitialize(m_cfg.inputClusters); - m_outputTrackerHitsPlane.initialize(m_cfg.outputTrackerHitsPlane); - m_outputTrackerHitsRaw.initialize(m_cfg.outputTrackerHitsRaw); + m_outputTrackerHitsLocal.initialize(m_cfg.outputTrackerHitsLocal); } ProcessCode EDM4hepMeasurementOutputConverter::execute( const AlgorithmContext& ctx) const { ClusterContainer clusters; - podio::Frame frame; - - edm4hep::TrackerHitLocalCollection hits; - - if (!m_cfg.inputClusters.empty()) { - ACTS_VERBOSE("Fetch clusters for writing: " << m_cfg.inputClusters); - clusters = m_inputClusters(ctx); - } + ActsPodioEdm::TrackerHitLocalCollection hits; const auto measurements = m_inputMeasurements(ctx); @@ -54,17 +49,18 @@ ProcessCode EDM4hepMeasurementOutputConverter::execute( auto to = hits.create(); EDM4hepUtil::writeMeasurement( - from, to, [](Acts::GeometryIdentifier id) { return id.value(); }); + ctx.geoContext, from, to, + *m_cfg.surfaceByIdentifier.at(from.geometryId())); } - m_outputTrackerHitsRaw(ctx, std::move(hits)); + m_outputTrackerHitsLocal(ctx, std::move(hits)); return ProcessCode::SUCCESS; } std::vector EDM4hepMeasurementOutputConverter::collections() const { - return {m_cfg.outputTrackerHitsPlane, m_cfg.outputTrackerHitsRaw}; + return {m_cfg.outputTrackerHitsLocal}; } } // namespace ActsExamples diff --git a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp index 6d12510950a..350d80b5e45 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp @@ -16,8 +16,11 @@ #include "ActsExamples/EventData/Index.hpp" #include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/Validation/TrackClassification.hpp" +#include "ActsPlugins/DD4hep/DD4hepDetectorElement.hpp" #include "ActsPlugins/EDM4hep/EDM4hepUtil.hpp" +#include + #include "edm4hep/TrackState.h" using namespace Acts::UnitLiterals; @@ -176,51 +179,28 @@ VariableBoundMeasurementProxy EDM4hepUtil::readMeasurement( } void EDM4hepUtil::writeMeasurement( + const Acts::GeometryContext& gctx, const ConstVariableBoundMeasurementProxy& from, - edm4hep::MutableTrackerHitLocal to, const MapGeometryIdTo& geometryMapper) { - Acts::GeometryIdentifier geoId = from.geometryId(); + ActsPodioEdm::MutableTrackerHitLocal to, const Acts::Surface& surface) { + long dim = from.size(); - if (geometryMapper) { - // no need for digitization as we only want to identify the sensor - to.setCellID(geometryMapper(geoId)); + const auto* placement = surface.surfacePlacement(); + if (placement == nullptr) { + throw std::runtime_error("Surface placement not found"); } - - const auto& parameters = from.fullParameters(); - const auto& covariance = from.fullCovariance(); - - to.setTime(parameters[Acts::eBoundTime] / Acts::UnitConstants::ns); - - to.setType(ActsPlugins::EDM4hepUtil::EDM4HEP_ACTS_POSITION_TYPE); - // TODO set uv (which are in global spherical coordinates with r=1) - to.setPosition({parameters[Acts::eBoundLoc0], parameters[Acts::eBoundLoc1], - parameters[Acts::eBoundTime]}); - - for (double value : std::span{ - covariance.data(), - static_cast(covariance.size() * covariance.size())}) { - to.addToCovariance(value); + const auto* dd4hepDetectorElement = + dynamic_cast(placement); + if (dd4hepDetectorElement == nullptr) { + throw std::runtime_error( + "Surface placement is not a DD4hepDetectorElement"); } - // to.addToCovariance - - // std::span(covariance(Acts::eBoundLoc0, Acts::eBoundLoc0)), - // static_cast(covariance(Acts::eBoundLoc1, Acts::eBoundLoc0)), - // static_cast(covariance(Acts::eBoundLoc1, Acts::eBoundLoc1)), - // 0, - // 0, - // 0, - // }); + std::uint64_t cellId = dd4hepDetectorElement->sourceElement().volumeID(); - // @TODO: Check if we can write cell info + ActsPlugins::EDM4hepUtil::writeMeasurement( + gctx, {from.parameters().data(), dim}, + {from.covariance().data(), dim, dim}, from.subspaceHelper().indices(), + cellId, surface, to); } void EDM4hepUtil::writeTrajectory( @@ -270,8 +250,8 @@ void EDM4hepUtil::writeTrajectory( trackState.omega = converted.values[4]; trackState.time = converted.values[5]; - // Converted parameters are relative to an ad-hoc perigee surface created at - // the hit location + // Converted parameters are relative to an ad-hoc perigee surface created + // at the hit location auto center = converted.surface->center(gctx); trackState.referencePoint.x = center.x(); trackState.referencePoint.y = center.y(); diff --git a/Plugins/EDM4hep/edm.yml b/Plugins/EDM4hep/edm.yml index a9d1bd6bcdf..de0b084a752 100644 --- a/Plugins/EDM4hep/edm.yml +++ b/Plugins/EDM4hep/edm.yml @@ -121,3 +121,23 @@ datatypes: std::size_t size() const { return indices_size(); } + + ActsPodioEdm::TrackerHitLocal: + Description: "Sensor-local tracker hit" + Author: "Paul Gessinger, CERN" + Members: + # These are chosen to be compatible with the `edm4hep::TrackerHit` interface + # (https://github.com/key4hep/EDM4hep/blob/c877c7fadf412db3c1126e7b410d8074a78014f2/edm4hep.yaml#L652-L666V) + # to allow for potential upstreaming + - uint64_t cellID // ID of the sensor that created this hit + - int32_t type // type of raw data hit, use depends on writer + - int32_t quality // quality bit flag of the hit + - float time [ns] // time of the hit (depends on measurement type) + - float eDep [GeV] // energy deposited on the hit (optional) + - float eDepError [GeV] // error measured on EDep (optional) + - edm4hep::Vector3d position [mm] // global hit position (depends on measurement type) + + VectorMembers: + - float measurement // Entries in the measurement value vector + - float covariance // Entries in the measurement covariance vector. Order is column major + diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index 65e72afa678..c5f8c4f29fc 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -19,7 +19,14 @@ #include "Acts/Surfaces/PerigeeSurface.hpp" #include "Acts/Surfaces/Surface.hpp" #include "Acts/Utilities/Logger.hpp" -#include +<<<<<<< conflict 1 of 2 % % % % % % % diff from : zvmxkwyn e48722ef "WIP"(parents of rebased revision) +\\\\\\\ to + : zvmxkwyn 063a5a37 "WIP"(rebase destination) + +#include ++ ++ ++ +xqlwmkrn + 2f9d577c "(wip) refactoring of edm4hep writer to wip TrackerHitLocal"( + rebased revision) +#include + >>>>>>> conflict 1 of 2 ends #include @@ -36,8 +43,8 @@ #if podio_VERSION_MAJOR == 0 || \ (podio_VERSION_MAJOR == 1 && podio_VERSION_MINOR <= 2) -template <> -struct std::hash { + template <> + struct std::hash { std::size_t operator()(const podio::ObjectID& id) const noexcept { auto hash_collectionID = std::hash{}(id.collectionID); auto hash_index = std::hash{}(id.index); @@ -336,4 +343,41 @@ constexpr bool kEdm4hepVertexHasTime = void writeVertex(const Acts::Vertex& vertex, edm4hep::MutableVertex to); +namespace detail { +// These functions are exposed here so they can be used from the unit tests +std::uint32_t encodeIndices(std::span indices); +boost::container::static_vector decodeIndices( + std::uint32_t type); +} // namespace detail + +/// Write a measurement to an EDM4hep tracker hit +/// +/// This function converts an ACTS measurement into the EDM4hep format. It +/// handles: +/// - Position conversion from local to global coordinates (in mm) +/// - Time storage (in ns) +/// - Measurement values and covariance matrix storage +/// - Encoding of measurement indices into a 32-bit integer: +/// - First 4 bits: number of indices (max 6) +/// - Next 4 bits per index: which parameter is being measured (0-6) +/// +/// The function will throw if: +/// - The number of indices exceeds 6 +/// - Any index is larger than 6 +/// - There's a size mismatch between parameters and covariance matrix +/// +/// @param gctx The geometry context +/// @param parameters The parameters of the measurement +/// @param covariance The covariance of the measurement +/// @param indices The indices of the measurement +/// @param cellId The cell ID of the measurement +/// @param surface The surface of the measurement +/// @param to The EDM4hep tracker hit to write to +void writeMeasurement( + const Acts::GeometryContext& gctx, + const Eigen::Map& parameters, + const Eigen::Map& covariance, + std::span indices, std::uint64_t cellId, + const Acts::Surface& surface, ActsPodioEdm::MutableTrackerHitLocal to); + } // namespace ActsPlugins::EDM4hepUtil diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index 1cb7caa53e1..686460eb39a 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -294,4 +294,94 @@ void writeVertex(const Vertex& vertex, edm4hep::MutableVertex to) { writeVertex(vertex, to); } +namespace detail { +std::uint32_t encodeIndices(std::span indices) { + if (indices.size() > eBoundSize) { + throw std::runtime_error( + "Number of indices exceeds maximum of 6 for EDM4hep"); + } + std::uint32_t result = 0; + + std::uint8_t shift = 0; + result |= (indices.size() << 0); + shift += 4; + + for (std::uint8_t index : indices) { + if (index > eBoundSize) { + throw std::runtime_error( + "Index out of range: can only encode indices up to 4 bits (0-15)"); + } + result |= (index << shift); + shift += 4; + } + return result; +} + +boost::container::static_vector decodeIndices( + std::uint32_t type) { + boost::container::static_vector result; + std::uint8_t size = type & 0xF; + if (size > eBoundSize) { + throw std::runtime_error( + "Number of indices exceeds maximum of 6 for EDM4hep"); + } + result.resize(size); + for (std::size_t i = 0; i < result.size(); ++i) { + result[i] = (type >> ((i + 1) * 4)) & 0xF; + if (result[i] > eBoundSize) { + throw std::runtime_error( + "Index out of range: can only encode indices up to 4 bits (0-15)"); + } + } + return result; +} +} // namespace detail + +void writeMeasurement(const GeometryContext& gctx, + const Eigen::Map& parameters, + const Eigen::Map& covariance, + std::span indices, + std::uint64_t cellId, const Acts::Surface& surface, + ActsPodioEdm::MutableTrackerHitLocal to) { + if (parameters.size() != covariance.rows() || + covariance.rows() != covariance.cols() || parameters.size() < 0 || + indices.size() != static_cast(parameters.size())) { + throw std::runtime_error( + "Size mismatch between parameters and covariance matrix"); + } + + std::size_t dim = static_cast(parameters.size()); + + if (cellId != 0) { + to.setCellID(cellId); + } + + to.setType(detail::encodeIndices(indices)); + + auto loc0 = std::ranges::find(indices, eBoundLoc0); + auto loc1 = std::ranges::find(indices, eBoundLoc1); + auto time = std::ranges::find(indices, eBoundTime); + + if (loc0 != indices.end() && loc1 != indices.end()) { + Vector2 loc{parameters[std::distance(indices.begin(), loc0)], + parameters[std::distance(indices.begin(), loc1)]}; + Vector3 global = surface.localToGlobal(gctx, loc, Vector3::UnitZ()); + global /= Acts::UnitConstants::mm; + to.setPosition({global.x(), global.y(), global.z()}); + } + + if (time != indices.end()) { + to.setTime(parameters[std::distance(indices.begin(), time)] / + Acts::UnitConstants::ns); + } + + for (double value : std::span{parameters.data(), dim}) { + to.addToMeasurement(value); + } + + for (double value : std::span{covariance.data(), dim * dim}) { + to.addToCovariance(value); + } +} + } // namespace ActsPlugins::EDM4hepUtil diff --git a/Python/Examples/src/plugins/EDM4hep.cpp b/Python/Examples/src/plugins/EDM4hep.cpp index 4d99df8e042..cf6c02ace04 100644 --- a/Python/Examples/src/plugins/EDM4hep.cpp +++ b/Python/Examples/src/plugins/EDM4hep.cpp @@ -111,8 +111,7 @@ PYBIND11_MODULE(ActsExamplesPythonBindingsEDM4hep, m) { auto [alg, config] = declareAlgorithm( m, "EDM4hepMeasurementOutputConverter"); - ACTS_PYTHON_STRUCT(config, inputMeasurements, inputClusters, - outputTrackerHitsPlane, outputTrackerHitsRaw); + ACTS_PYTHON_STRUCT(config, inputMeasurements, outputTrackerHitsLocal); } { diff --git a/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt b/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt index 9ab6e9e91ba..ab9a6b9e4ed 100644 --- a/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt +++ b/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt @@ -8,9 +8,15 @@ target_link_libraries( set(unittest_extra_libraries Acts::PluginEDM4hep) add_unittest(PodioTrackContainer PodioTrackContainerTest.cpp) add_unittest(PodioTrackStateContainer PodioTrackStateContainerTest.cpp) - add_unittest(EDM4HepVertexWriteTest EDM4HepVertexWriteTests.cpp) + target_link_libraries( ActsUnitTestEDM4HepVertexWriteTest PUBLIC ActsPluginEDM4hep ) + +add_unittest(EDM4HepMeasurementWriteTest EDM4HepMeasurementWriteTests.cpp) +target_link_libraries( + ActsUnitTestEDM4HepMeasurementWriteTest + PUBLIC Acts::PluginEDM4hep +) diff --git a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp new file mode 100644 index 00000000000..efd523b91ea --- /dev/null +++ b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp @@ -0,0 +1,261 @@ +// This file is part of the ACTS project. +// +// Copyright (C) 2016 CERN for the benefit of the ACTS project +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at https://mozilla.org/MPL/2.0/. + +#include +#include + +#include "Acts/Definitions/Algebra.hpp" +#include "Acts/Definitions/TrackParametrization.hpp" +#include "Acts/EventData/SubspaceHelpers.hpp" +#include "Acts/EventData/detail/GenerateParameters.hpp" +#include "Acts/Geometry/GeometryContext.hpp" +#include "Acts/Surfaces/DiscSurface.hpp" +#include "ActsPlugins/EDM4hep/EDM4hepUtil.hpp" +#include "ActsTests/CommonHelpers/FloatComparisons.hpp" +#include + +using namespace Acts; +using namespace ActsPlugins; +using namespace Acts::UnitLiterals; +using namespace Acts::detail::Test; + +namespace { +std::default_random_engine rng(123); +auto gctx = GeometryContext::dangerouslyDefaultConstruct(); +} // namespace + +BOOST_AUTO_TEST_SUITE(EDM4hepMeasurementWriterTest) + +BOOST_AUTO_TEST_CASE(WriteMeasurement) { + auto [parameters, covariance] = + generateParametersCovariance(rng); + + std::uint64_t cellId = 1234; + + ActsPodioEdm::TrackerHitLocalCollection hits; + auto to = hits.create(); + + std::vector indices = {eBoundLoc0, eBoundLoc1}; + FixedSubspaceHelper helper(indices); + + Vector2 measPos = helper.projectVector(parameters); + ActsSquareMatrix<2> measCov = helper.projectMatrix(covariance); + + auto surface = Surface::makeShared( + Transform3::Identity() * Translation3(Vector3{1, 2, 3}), 1_mm, 1_mm); + + Vector3 global = surface->localToGlobal(gctx, measPos, Vector3::UnitZ()); + + EDM4hepUtil::writeMeasurement(gctx, {measPos.data(), 2}, + {measCov.data(), 2, 2}, indices, cellId, + *surface, to); + + BOOST_CHECK_EQUAL(to.getCellID(), cellId); + BOOST_CHECK_EQUAL(to.getPosition()[0] * 1_mm, global.x()); + BOOST_CHECK_EQUAL(to.getPosition()[1] * 1_mm, global.y()); + BOOST_CHECK_EQUAL(to.getPosition()[2] * 1_mm, global.z()); + + // Time should be zero since we don't have time measurement + BOOST_CHECK_EQUAL(to.getTime() * 1_ns, 0.0); + + auto meas = to.getMeasurement(); + BOOST_CHECK_EQUAL(meas.size(), 2); + CHECK_CLOSE_REL(meas[0], measPos.x(), 1e-6); + CHECK_CLOSE_REL(meas[1], measPos.y(), 1e-6); + + auto cov = to.getCovariance(); + BOOST_CHECK_EQUAL(cov.size(), 4); + CHECK_CLOSE_REL(cov[0], measCov(0, 0), 1e-6); + CHECK_CLOSE_REL(cov[1], measCov(0, 1), 1e-6); + CHECK_CLOSE_REL(cov[2], measCov(1, 0), 1e-6); + CHECK_CLOSE_REL(cov[3], measCov(1, 1), 1e-6); + + auto unpackedIndices = EDM4hepUtil::detail::decodeIndices(to.getType()); + BOOST_CHECK_EQUAL(unpackedIndices.size(), 2); + BOOST_CHECK_EQUAL(unpackedIndices[0], eBoundLoc0); + BOOST_CHECK_EQUAL(unpackedIndices[1], eBoundLoc1); +} + +BOOST_AUTO_TEST_CASE(WriteMeasurementNoPosition) { + auto [parameters, covariance] = + generateParametersCovariance(rng); + + std::uint64_t cellId = 1234; + + ActsPodioEdm::TrackerHitLocalCollection hits; + auto to = hits.create(); + + // Only measure phi and theta + std::vector indices = {eBoundPhi, eBoundTheta}; + FixedSubspaceHelper helper(indices); + + Vector2 measPos = helper.projectVector(parameters); + ActsSquareMatrix<2> measCov = helper.projectMatrix(covariance); + + auto surface = Surface::makeShared( + Transform3::Identity() * Translation3(Vector3{1, 2, 3}), 1_mm, 1_mm); + + EDM4hepUtil::writeMeasurement(gctx, {measPos.data(), 2}, + {measCov.data(), 2, 2}, indices, cellId, + *surface, to); + + BOOST_CHECK_EQUAL(to.getCellID(), cellId); + // Position should be zero since we don't have loc0/loc1 + BOOST_CHECK_EQUAL(to.getPosition()[0] * 1_mm, 0.0); + BOOST_CHECK_EQUAL(to.getPosition()[1] * 1_mm, 0.0); + BOOST_CHECK_EQUAL(to.getPosition()[2] * 1_mm, 0.0); + + auto meas = to.getMeasurement(); + BOOST_CHECK_EQUAL(meas.size(), 2); + CHECK_CLOSE_REL(meas[0], measPos.x(), 1e-6); + CHECK_CLOSE_REL(meas[1], measPos.y(), 1e-6); + + auto cov = to.getCovariance(); + BOOST_CHECK_EQUAL(cov.size(), 4); + CHECK_CLOSE_REL(cov[0], measCov(0, 0), 1e-6); + CHECK_CLOSE_REL(cov[1], measCov(0, 1), 1e-6); + CHECK_CLOSE_REL(cov[2], measCov(1, 0), 1e-6); + CHECK_CLOSE_REL(cov[3], measCov(1, 1), 1e-6); + + auto unpackedIndices = EDM4hepUtil::detail::decodeIndices(to.getType()); + BOOST_CHECK_EQUAL(unpackedIndices.size(), 2); + BOOST_CHECK_EQUAL(unpackedIndices[0], eBoundPhi); + BOOST_CHECK_EQUAL(unpackedIndices[1], eBoundTheta); +} + +BOOST_AUTO_TEST_CASE(WriteMeasurementWithTime) { + auto [parameters, covariance] = + generateParametersCovariance(rng); + + std::uint64_t cellId = 1234; + + ActsPodioEdm::TrackerHitLocalCollection hits; + auto to = hits.create(); + + // Measure loc0, loc1, and time + std::vector indices = {eBoundLoc0, eBoundLoc1, eBoundTime}; + FixedSubspaceHelper helper(indices); + + Vector3 measPos = helper.projectVector(parameters); + ActsSquareMatrix<3> measCov = helper.projectMatrix(covariance); + + auto surface = Surface::makeShared( + Transform3::Identity() * Translation3(Vector3{1, 2, 3}), 1_mm, 1_mm); + + Vector3 global = + surface->localToGlobal(gctx, measPos.head<2>(), Vector3::UnitZ()); + + EDM4hepUtil::writeMeasurement(gctx, {measPos.data(), 3}, + {measCov.data(), 3, 3}, indices, cellId, + *surface, to); + + BOOST_CHECK_EQUAL(to.getCellID(), cellId); + BOOST_CHECK_EQUAL(to.getPosition()[0] * 1_mm, global.x()); + BOOST_CHECK_EQUAL(to.getPosition()[1] * 1_mm, global.y()); + BOOST_CHECK_EQUAL(to.getPosition()[2] * 1_mm, global.z()); + // Time should be set since we have time measurement + CHECK_CLOSE_REL(to.getTime() * 1_ns, parameters[eBoundTime], 1e-6); + + auto meas = to.getMeasurement(); + BOOST_CHECK_EQUAL(meas.size(), 3); + CHECK_CLOSE_REL(meas[0], measPos.x(), 1e-6); + CHECK_CLOSE_REL(meas[1], measPos.y(), 1e-6); + CHECK_CLOSE_REL(meas[2], measPos.z(), 1e-6); + + auto cov = to.getCovariance(); + BOOST_CHECK_EQUAL(cov.size(), 9); + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + CHECK_CLOSE_REL(cov[i * 3 + j], measCov(i, j), 1e-6); + } + } + + auto unpackedIndices = EDM4hepUtil::detail::decodeIndices(to.getType()); + BOOST_CHECK_EQUAL(unpackedIndices.size(), 3); + BOOST_CHECK_EQUAL(unpackedIndices[0], eBoundLoc0); + BOOST_CHECK_EQUAL(unpackedIndices[1], eBoundLoc1); + BOOST_CHECK_EQUAL(unpackedIndices[2], eBoundTime); +} + +BOOST_AUTO_TEST_CASE(EncodeDecodeIndices) { + // Test empty span + { + std::vector indices = {}; + auto encoded = EDM4hepUtil::detail::encodeIndices(indices); + auto decoded = EDM4hepUtil::detail::decodeIndices(encoded); + BOOST_CHECK_EQUAL(decoded.size(), 0); + } + + // Test single value + { + std::vector indices = {3}; + auto encoded = EDM4hepUtil::detail::encodeIndices(indices); + auto decoded = EDM4hepUtil::detail::decodeIndices(encoded); + BOOST_CHECK_EQUAL(decoded.size(), 1); + BOOST_CHECK_EQUAL(decoded.at(0), 3); + } + + // Test maximum length (6) + { + std::vector indices = {0, 1, 2, 3, 4, 5}; + auto encoded = EDM4hepUtil::detail::encodeIndices(indices); + auto decoded = EDM4hepUtil::detail::decodeIndices(encoded); + BOOST_CHECK_EQUAL(decoded.size(), 6); + for (std::size_t i = 0; i < 6; ++i) { + BOOST_CHECK_EQUAL(decoded.at(i), i); + } + } + + // Test maximum value (6) + { + std::vector indices = {6, 6, 6}; + auto encoded = EDM4hepUtil::detail::encodeIndices(indices); + auto decoded = EDM4hepUtil::detail::decodeIndices(encoded); + BOOST_CHECK_EQUAL(decoded.size(), 3); + for (std::size_t i = 0; i < 3; ++i) { + BOOST_CHECK_EQUAL(decoded.at(i), 6); + } + } + + // Test mixed values + { + std::vector indices = {2, 5, 1, 4}; + auto encoded = EDM4hepUtil::detail::encodeIndices(indices); + auto decoded = EDM4hepUtil::detail::decodeIndices(encoded); + BOOST_CHECK_EQUAL(decoded.size(), 4); + BOOST_CHECK_EQUAL(decoded.at(0), 2); + BOOST_CHECK_EQUAL(decoded.at(1), 5); + BOOST_CHECK_EQUAL(decoded.at(2), 1); + BOOST_CHECK_EQUAL(decoded.at(3), 4); + } +} + +BOOST_AUTO_TEST_CASE(EncodeDecodeIndicesErrors) { + // Test exceeding maximum length (7 values) + { + std::vector indices = {0, 1, 2, 3, 4, 5, 6}; + BOOST_CHECK_THROW(EDM4hepUtil::detail::encodeIndices(indices), + std::runtime_error); + } + + // Test exceeding maximum value (7) + { + std::vector indices = {7}; + BOOST_CHECK_THROW(EDM4hepUtil::detail::encodeIndices(indices), + std::runtime_error); + } + + // Test mixed valid/invalid values + { + std::vector indices = {2, 7, 1}; + BOOST_CHECK_THROW(EDM4hepUtil::detail::encodeIndices(indices), + std::runtime_error); + } +} + +BOOST_AUTO_TEST_SUITE_END() From ac79c572f17b7c28943bc77f320d39fa94ad65b3 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 5 Feb 2026 15:06:44 +0100 Subject: [PATCH 03/19] cleanup of the measurement index packing / unpacking --- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 38 +++++++++++++++---- .../EDM4hep/EDM4HepMeasurementWriteTests.cpp | 4 +- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index 686460eb39a..f8314335a35 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -105,7 +105,7 @@ void packCovariance(const SquareMatrix<6>& from, float* to) { for (int i = 0; i < from.rows(); i++) { for (int j = 0; j <= i; j++) { std::size_t k = (i + 1) * i / 2 + j; - to[k] = from(i, j); + to[k] = static_cast(from(i, j)); } } } @@ -295,6 +295,21 @@ void writeVertex(const Vertex& vertex, edm4hep::MutableVertex to) { } namespace detail { +/// Encode a list of bound parameter indices into a 32-bit integer. +/// +/// This function bit-packs up to 6 parameter indices (each 0-5 corresponding to +/// eBoundLoc0, eBoundLoc1, eBoundPhi, eBoundTheta, eBoundQOverP, eBoundTime) +/// into a single 32-bit unsigned integer for storage in EDM4hep format. +/// +/// Bit layout: +/// - Bits 0-3: Number of indices (size) +/// - Bits 4-7: First index +/// - Bits 8-11: Second index +/// - Bits 12-15: Third index +/// - (and so on, up to 6 indices total) +/// +/// @param indices Span of parameter indices to encode (max 6 elements) +/// @return Packed 32-bit unsigned integer containing all indices std::uint32_t encodeIndices(std::span indices) { if (indices.size() > eBoundSize) { throw std::runtime_error( @@ -311,12 +326,20 @@ std::uint32_t encodeIndices(std::span indices) { throw std::runtime_error( "Index out of range: can only encode indices up to 4 bits (0-15)"); } - result |= (index << shift); + result |= (static_cast(index) << shift); shift += 4; } return result; } +/// Decode a 32-bit integer back into a list of bound parameter indices. +/// +/// This function unpacks a bit-packed integer (created by encodeIndices) back +/// into the original list of parameter indices. See encodeIndices for the bit +/// layout specification. +/// +/// @param type Packed 32-bit unsigned integer containing encoded indices +/// @return Vector of decoded parameter indices (0-5 for each bound parameter) boost::container::static_vector decodeIndices( std::uint32_t type) { boost::container::static_vector result; @@ -350,7 +373,7 @@ void writeMeasurement(const GeometryContext& gctx, "Size mismatch between parameters and covariance matrix"); } - std::size_t dim = static_cast(parameters.size()); + auto dim = static_cast(parameters.size()); if (cellId != 0) { to.setCellID(cellId); @@ -371,16 +394,17 @@ void writeMeasurement(const GeometryContext& gctx, } if (time != indices.end()) { - to.setTime(parameters[std::distance(indices.begin(), time)] / - Acts::UnitConstants::ns); + std::size_t timeOffset = std::distance(indices.begin(), time); + to.setTime( + static_cast(parameters[timeOffset] / Acts::UnitConstants::ns)); } for (double value : std::span{parameters.data(), dim}) { - to.addToMeasurement(value); + to.addToMeasurement(static_cast(value)); } for (double value : std::span{covariance.data(), dim * dim}) { - to.addToCovariance(value); + to.addToCovariance(static_cast(value)); } } diff --git a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp index efd523b91ea..a63beae7fad 100644 --- a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp +++ b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp @@ -186,7 +186,7 @@ BOOST_AUTO_TEST_CASE(EncodeDecodeIndices) { // Test empty span { std::vector indices = {}; - auto encoded = EDM4hepUtil::detail::encodeIndices(indices); + std::uint32_t encoded = EDM4hepUtil::detail::encodeIndices(indices); auto decoded = EDM4hepUtil::detail::decodeIndices(encoded); BOOST_CHECK_EQUAL(decoded.size(), 0); } @@ -194,7 +194,7 @@ BOOST_AUTO_TEST_CASE(EncodeDecodeIndices) { // Test single value { std::vector indices = {3}; - auto encoded = EDM4hepUtil::detail::encodeIndices(indices); + std::uint32_t encoded = EDM4hepUtil::detail::encodeIndices(indices); auto decoded = EDM4hepUtil::detail::decodeIndices(encoded); BOOST_CHECK_EQUAL(decoded.size(), 1); BOOST_CHECK_EQUAL(decoded.at(0), 3); From 3e527c58c4ace4ea3590966a90c363967e2bfc23 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 5 Feb 2026 17:31:54 +0100 Subject: [PATCH 04/19] sonar fixes --- .../ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index c5f8c4f29fc..7ed6ba4da69 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -124,12 +124,12 @@ void writeTrack(const Acts::GeometryContext& gctx, track_proxy_t track, auto setParameters = [](edm4hep::TrackState& trackState, const detail::Parameters& params) { - trackState.D0 = params.values[0]; - trackState.Z0 = params.values[1]; - trackState.phi = params.values[2]; - trackState.tanLambda = params.values[3]; - trackState.omega = params.values[4]; - trackState.time = params.values[5]; + trackState.D0 = static_cast(params.values[0]); + trackState.Z0 = static_cast(params.values[1]); + trackState.phi = static_cast(params.values[2]); + trackState.tanLambda = static_cast(params.values[3]); + trackState.omega = static_cast(params.values[4]); + trackState.time = static_cast(params.values[5]); if (params.covariance) { detail::packCovariance(params.covariance.value(), @@ -140,8 +140,7 @@ void writeTrack(const Acts::GeometryContext& gctx, track_proxy_t track, ACTS_VERBOSE("Converting " << track.nTrackStates() << " track states"); for (const auto& state : track.trackStatesReversed()) { - auto typeFlags = state.typeFlags(); - if (!typeFlags.isMeasurement()) { + if (!state.typeFlags().isMeasurement()) { continue; } @@ -167,9 +166,9 @@ void writeTrack(const Acts::GeometryContext& gctx, track_proxy_t track, // Converted parameters are relative to an ad-hoc perigee surface created at // the hit location auto center = converted.surface->center(gctx); - trackState.referencePoint.x = center.x(); - trackState.referencePoint.y = center.y(); - trackState.referencePoint.z = center.z(); + trackState.referencePoint.x = static_cast(center.x()); + trackState.referencePoint.y = static_cast(center.y()); + trackState.referencePoint.z = static_cast(center.z()); ACTS_VERBOSE("- ref surface ctr: " << center.transpose()); } outTrackStates.front().location = edm4hep::TrackState::AtLastHit; @@ -200,13 +199,13 @@ void writeTrack(const Acts::GeometryContext& gctx, track_proxy_t track, // track itself, but if that's not a perigee surface, another ad-hoc perigee // at the position will be created. auto center = converted.surface->center(gctx); - ipState.referencePoint.x = center.x(); - ipState.referencePoint.y = center.y(); - ipState.referencePoint.z = center.z(); + ipState.referencePoint.x = static_cast(center.x()); + ipState.referencePoint.y = static_cast(center.y()); + ipState.referencePoint.z = static_cast(center.z()); ACTS_VERBOSE("- ref surface ctr: " << center.transpose()); - for (auto& trackState : outTrackStates) { + for (const auto& trackState : outTrackStates) { to.addToTrackStates(trackState); } } @@ -225,8 +224,7 @@ void readTrack(const edm4hep::Track& from, track_proxy_t& track, double Bz, std::optional ipState; - auto unpack = - [](const edm4hep::TrackState& trackState) -> detail::Parameters { + auto unpack = [](const edm4hep::TrackState& trackState) { detail::Parameters params; params.covariance = BoundMatrix::Zero(); params.values = BoundVector::Zero(); From f76c9e335980852a52116cc3c47c6c0d42d4d51f Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Wed, 18 Feb 2026 18:39:47 +0100 Subject: [PATCH 05/19] update after rebase --- .../include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 12 ++++++------ Plugins/EDM4hep/src/EDM4hepUtil.cpp | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index 7ed6ba4da69..3f62d5e092c 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -371,11 +371,11 @@ boost::container::static_vector decodeIndices( /// @param cellId The cell ID of the measurement /// @param surface The surface of the measurement /// @param to The EDM4hep tracker hit to write to -void writeMeasurement( - const Acts::GeometryContext& gctx, - const Eigen::Map& parameters, - const Eigen::Map& covariance, - std::span indices, std::uint64_t cellId, - const Acts::Surface& surface, ActsPodioEdm::MutableTrackerHitLocal to); +void writeMeasurement(const Acts::GeometryContext& gctx, + const Eigen::Map& parameters, + const Eigen::Map& covariance, + std::span indices, + std::uint64_t cellId, const Acts::Surface& surface, + ActsPodioEdm::MutableTrackerHitLocal to); } // namespace ActsPlugins::EDM4hepUtil diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index f8314335a35..e4c14e57c5d 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -361,8 +361,8 @@ boost::container::static_vector decodeIndices( } // namespace detail void writeMeasurement(const GeometryContext& gctx, - const Eigen::Map& parameters, - const Eigen::Map& covariance, + const Eigen::Map& parameters, + const Eigen::Map& covariance, std::span indices, std::uint64_t cellId, const Acts::Surface& surface, ActsPodioEdm::MutableTrackerHitLocal to) { From 8fdb7293342bb25be182b1b41789be7400acf769 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 19 Feb 2026 18:35:43 +0100 Subject: [PATCH 06/19] whitespace fix --- Plugins/EDM4hep/edm.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/Plugins/EDM4hep/edm.yml b/Plugins/EDM4hep/edm.yml index de0b084a752..c3e0a9f7ee5 100644 --- a/Plugins/EDM4hep/edm.yml +++ b/Plugins/EDM4hep/edm.yml @@ -140,4 +140,3 @@ datatypes: VectorMembers: - float measurement // Entries in the measurement value vector - float covariance // Entries in the measurement covariance vector. Order is column major - From 4b9bbfccc04d84db53568f0ac2ee85fb26381056 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 19 Feb 2026 18:45:29 +0100 Subject: [PATCH 07/19] use const ref over value --- .../ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp | 20 ++++++++++--------- Examples/Io/EDM4hep/src/EDM4hepUtil.cpp | 2 +- .../ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 2 +- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 2 +- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp index f09421a4083..1ef88685bbd 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp @@ -95,19 +95,21 @@ VariableBoundMeasurementProxy readMeasurement( const edm4hep::TrackerHit3DCollection* fromClusters, Cluster* toCluster, const MapGeometryIdFrom& geometryMapper); -/// Writes a measurement cluster to EDM4hep. +/// Writes an ACTS measurement to EDM4hep. /// -/// Inpersistent information: -/// - hit index -/// - 1D local coords? -/// - segment path +/// Converts bound parameters (local coordinates, time) and covariance from an +/// ACTS measurement to a MutableTrackerHitLocal. The surface is used +/// to obtain the detector cell ID from the DD4hepDetectorElement and to +/// transform local coordinates to global position. /// -/// Known issues: -/// - cluster channels are written to inappropriate fields -/// - local 2D coordinates and time are written to position +/// @param gctx The geometry context for coordinate transformations. +/// @param from The ACTS measurement to convert. +/// @param to The EDM4hep tracker hit to write to. +/// @param surface The surface associated with the measurement (must have a +/// DD4hepDetectorElement placement). void writeMeasurement(const Acts::GeometryContext& gctx, const ConstVariableBoundMeasurementProxy& from, - ActsPodioEdm::MutableTrackerHitLocal to, + ActsPodioEdm::MutableTrackerHitLocal& to, const Acts::Surface& surface); /// Writes a trajectory to EDM4hep. diff --git a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp index 350d80b5e45..daa244df121 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp @@ -181,7 +181,7 @@ VariableBoundMeasurementProxy EDM4hepUtil::readMeasurement( void EDM4hepUtil::writeMeasurement( const Acts::GeometryContext& gctx, const ConstVariableBoundMeasurementProxy& from, - ActsPodioEdm::MutableTrackerHitLocal to, const Acts::Surface& surface) { + ActsPodioEdm::MutableTrackerHitLocal& to, const Acts::Surface& surface) { long dim = from.size(); const auto* placement = surface.surfacePlacement(); diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index 3f62d5e092c..f976a2c290c 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -376,6 +376,6 @@ void writeMeasurement(const Acts::GeometryContext& gctx, const Eigen::Map& covariance, std::span indices, std::uint64_t cellId, const Acts::Surface& surface, - ActsPodioEdm::MutableTrackerHitLocal to); + ActsPodioEdm::MutableTrackerHitLocal& to); } // namespace ActsPlugins::EDM4hepUtil diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index e4c14e57c5d..6a7fcfcbe8d 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -365,7 +365,7 @@ void writeMeasurement(const GeometryContext& gctx, const Eigen::Map& covariance, std::span indices, std::uint64_t cellId, const Acts::Surface& surface, - ActsPodioEdm::MutableTrackerHitLocal to) { + ActsPodioEdm::MutableTrackerHitLocal& to) { if (parameters.size() != covariance.rows() || covariance.rows() != covariance.cols() || parameters.size() < 0 || indices.size() != static_cast(parameters.size())) { From f2288d771fa283e57e451440c1c0d93fdf1e0133 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 19 Feb 2026 21:46:58 +0100 Subject: [PATCH 08/19] deprecated typedefs --- .../Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp index a63beae7fad..949c4be28e6 100644 --- a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp +++ b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(WriteMeasurement) { FixedSubspaceHelper helper(indices); Vector2 measPos = helper.projectVector(parameters); - ActsSquareMatrix<2> measCov = helper.projectMatrix(covariance); + SquareMatrix<2> measCov = helper.projectMatrix(covariance); auto surface = Surface::makeShared( Transform3::Identity() * Translation3(Vector3{1, 2, 3}), 1_mm, 1_mm); @@ -95,7 +95,7 @@ BOOST_AUTO_TEST_CASE(WriteMeasurementNoPosition) { FixedSubspaceHelper helper(indices); Vector2 measPos = helper.projectVector(parameters); - ActsSquareMatrix<2> measCov = helper.projectMatrix(covariance); + SquareMatrix<2> measCov = helper.projectMatrix(covariance); auto surface = Surface::makeShared( Transform3::Identity() * Translation3(Vector3{1, 2, 3}), 1_mm, 1_mm); @@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE(WriteMeasurementWithTime) { FixedSubspaceHelper helper(indices); Vector3 measPos = helper.projectVector(parameters); - ActsSquareMatrix<3> measCov = helper.projectMatrix(covariance); + SquareMatrix<3> measCov = helper.projectMatrix(covariance); auto surface = Surface::makeShared( Transform3::Identity() * Translation3(Vector3{1, 2, 3}), 1_mm, 1_mm); From bc13f2fa8a9cbe18b33539622cbd973d84421287 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 19 Feb 2026 22:53:39 +0100 Subject: [PATCH 09/19] fix writer --- .../EDM4hepMeasurementOutputConverter.hpp | 7 +- .../src/EDM4hepMeasurementOutputConverter.cpp | 20 ++- Python/Examples/src/plugins/EDM4hep.cpp | 3 +- Python/Examples/tests/test_edm4hep.py | 143 +++++++++++++----- 4 files changed, 129 insertions(+), 44 deletions(-) diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp index fce28793a76..d2a55d280f8 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp @@ -8,12 +8,14 @@ #pragma once +#include "Acts/Geometry/TrackingGeometry.hpp" #include "ActsExamples/EventData/Cluster.hpp" #include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/Framework/DataHandle.hpp" #include "ActsExamples/Io/Podio/CollectionBaseWriteHandle.hpp" #include "ActsExamples/Io/Podio/PodioOutputConverter.hpp" +#include #include namespace ActsExamples { @@ -36,9 +38,8 @@ class EDM4hepMeasurementOutputConverter final : public PodioOutputConverter { /// Name of the output tracker hit raw collection. std::string outputTrackerHitsLocal = "ActsTrackerHitsLocal"; - /// Map of surface by identifier to allow local - to global - std::unordered_map - surfaceByIdentifier; + /// Tracking geometry for surface lookup (local-to-global transform). + std::shared_ptr trackingGeometry; }; /// Constructor with diff --git a/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp b/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp index 9e99a939d97..39b7906f793 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepMeasurementOutputConverter.cpp @@ -28,14 +28,17 @@ EDM4hepMeasurementOutputConverter::EDM4hepMeasurementOutputConverter( : PodioOutputConverter("EDM4hepMeasurementOutputConverter", std::move(logger)), m_cfg(config) { + if (m_cfg.trackingGeometry == nullptr) { + throw std::runtime_error( + "EDM4hepMeasurementOutputConverter: trackingGeometry is null"); + } + m_inputMeasurements.initialize(m_cfg.inputMeasurements); m_outputTrackerHitsLocal.initialize(m_cfg.outputTrackerHitsLocal); } ProcessCode EDM4hepMeasurementOutputConverter::execute( const AlgorithmContext& ctx) const { - ClusterContainer clusters; - ActsPodioEdm::TrackerHitLocalCollection hits; const auto measurements = m_inputMeasurements(ctx); @@ -47,10 +50,17 @@ ProcessCode EDM4hepMeasurementOutputConverter::execute( ConstVariableBoundMeasurementProxy from = measurements.getMeasurement(hitIdx); + const Acts::Surface* surface = + m_cfg.trackingGeometry->findSurface(from.geometryId()); + if (surface == nullptr) { + throw std::runtime_error( + "EDM4hepMeasurementOutputConverter: surface not found for geometry " + "id " + + std::to_string(from.geometryId().value())); + } + auto to = hits.create(); - EDM4hepUtil::writeMeasurement( - ctx.geoContext, from, to, - *m_cfg.surfaceByIdentifier.at(from.geometryId())); + EDM4hepUtil::writeMeasurement(ctx.geoContext, from, to, *surface); } m_outputTrackerHitsLocal(ctx, std::move(hits)); diff --git a/Python/Examples/src/plugins/EDM4hep.cpp b/Python/Examples/src/plugins/EDM4hep.cpp index cf6c02ace04..9dbfcd1882d 100644 --- a/Python/Examples/src/plugins/EDM4hep.cpp +++ b/Python/Examples/src/plugins/EDM4hep.cpp @@ -111,7 +111,8 @@ PYBIND11_MODULE(ActsExamplesPythonBindingsEDM4hep, m) { auto [alg, config] = declareAlgorithm( m, "EDM4hepMeasurementOutputConverter"); - ACTS_PYTHON_STRUCT(config, inputMeasurements, outputTrackerHitsLocal); + ACTS_PYTHON_STRUCT(config, inputMeasurements, outputTrackerHitsLocal, + trackingGeometry); } { diff --git a/Python/Examples/tests/test_edm4hep.py b/Python/Examples/tests/test_edm4hep.py index 92753cfd5ca..8e8d4f0f88c 100644 --- a/Python/Examples/tests/test_edm4hep.py +++ b/Python/Examples/tests/test_edm4hep.py @@ -8,6 +8,7 @@ import filelock from helpers import ( + dd4hepEnabled, edm4hepEnabled, podioEnabled, AssertCollectionExistsAlg, @@ -20,6 +21,7 @@ Sequencer, GenericDetector, ) +import acts.examples.json from acts.examples.odd import getOpenDataDetector, getOpenDataDetectorDirectory @@ -53,34 +55,69 @@ def assert_podio( @pytest.mark.edm4hep +@pytest.mark.odd @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up") -def test_edm4hep_measurement_writer(tmp_path, fatras): +@pytest.mark.skipif(not dd4hepEnabled, reason="DD4hep not set up") +def test_edm4hep_measurement_writer(tmp_path, ptcl_gun, rng): from acts.examples.edm4hep import EDM4hepMeasurementOutputConverter, PodioWriter s = Sequencer(numThreads=1, events=10) - _, simAlg, digiAlg = fatras(s) + evGen, h3conv = ptcl_gun(s) - out = tmp_path / "measurements_edm4hep.root" + with getOpenDataDetector() as detector: + trackingGeometry = detector.trackingGeometry() + field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) - converter = EDM4hepMeasurementOutputConverter( - level=acts.logging.VERBOSE, - inputMeasurements=digiAlg.config.outputMeasurements, - inputClusters=digiAlg.config.outputClusters, - outputTrackerHitsPlane="tracker_hits_plane", - outputTrackerHitsRaw="tracker_hits_raw", - ) - s.addAlgorithm(converter) + simAlg = acts.examples.FatrasSimulation( + level=acts.logging.INFO, + inputParticles=h3conv.config.outputParticles, + outputParticles="particles_simulated", + outputSimHits="simhits", + randomNumbers=rng, + trackingGeometry=trackingGeometry, + magneticField=field, + generateHitsOnSensitive=True, + emScattering=False, + emEnergyLossIonisation=False, + emEnergyLossRadiation=False, + emPhotonConversion=False, + ) + s.addAlgorithm(simAlg) + + digiCfg = acts.examples.DigitizationAlgorithm.Config( + digitizationConfigs=acts.examples.json.readDigiConfigFromJson( + str( + Path(__file__).parent.parent.parent.parent + / "Examples/Configs/odd-digi-smearing-config.json" + ) + ), + surfaceByIdentifier=trackingGeometry.geoIdSurfaceMap(), + randomNumbers=rng, + inputSimHits=simAlg.config.outputSimHits, + ) + digiAlg = acts.examples.DigitizationAlgorithm(digiCfg, acts.logging.INFO) + s.addAlgorithm(digiAlg) - s.addWriter( - PodioWriter( + out = tmp_path / "measurements_edm4hep.root" + + converter = EDM4hepMeasurementOutputConverter( level=acts.logging.VERBOSE, - outputPath=str(out), - category="events", - collections=converter.collections, + inputMeasurements=digiAlg.config.outputMeasurements, + outputTrackerHitsLocal="tracker_hits_local", + trackingGeometry=trackingGeometry, ) - ) + s.addAlgorithm(converter) - s.run() + s.addWriter( + PodioWriter( + level=acts.logging.VERBOSE, + outputPath=str(out), + category="events", + collections=converter.collections, + ) + ) + + s.run() assert os.path.isfile(out) assert os.stat(out).st_size > 10 @@ -88,7 +125,7 @@ def test_edm4hep_measurement_writer(tmp_path, fatras): assert_podio( out, "events", - collections=set(["tracker_hits_plane", "tracker_hits_raw"]), + collections=set(["tracker_hits_local"]), nevents=10, ) @@ -489,8 +526,10 @@ def test_edm4hep_simhit_particle_reader(tmp_path, ddsim_input): @pytest.mark.edm4hep +@pytest.mark.odd @pytest.mark.skipif(not edm4hepEnabled, reason="EDM4hep is not set up") -def test_edm4hep_measurement_reader(tmp_path, fatras): +@pytest.mark.skipif(not dd4hepEnabled, reason="DD4hep not set up") +def test_edm4hep_measurement_reader(tmp_path, ptcl_gun, rng): from acts.examples.edm4hep import ( EDM4hepMeasurementOutputConverter, EDM4hepMeasurementInputConverter, @@ -498,25 +537,59 @@ def test_edm4hep_measurement_reader(tmp_path, fatras): from acts.examples.edm4hep import PodioWriter, PodioReader s = Sequencer(numThreads=1, events=10) - _, simAlg, digiAlg = fatras(s) + evGen, h3conv = ptcl_gun(s) - out = tmp_path / "measurements_edm4hep.root" + with getOpenDataDetector() as detector: + trackingGeometry = detector.trackingGeometry() + field = acts.ConstantBField(acts.Vector3(0, 0, 2 * u.T)) - converter = EDM4hepMeasurementOutputConverter( - level=acts.logging.INFO, - inputMeasurements=digiAlg.config.outputMeasurements, - inputClusters=digiAlg.config.outputClusters, - ) - s.addAlgorithm(converter) - s.addWriter( - PodioWriter( + simAlg = acts.examples.FatrasSimulation( level=acts.logging.INFO, - outputPath=str(out), - category="events", - collections=converter.collections, + inputParticles=h3conv.config.outputParticles, + outputParticles="particles_simulated", + outputSimHits="simhits", + randomNumbers=rng, + trackingGeometry=trackingGeometry, + magneticField=field, + generateHitsOnSensitive=True, + emScattering=False, + emEnergyLossIonisation=False, + emEnergyLossRadiation=False, + emPhotonConversion=False, ) - ) - s.run() + s.addAlgorithm(simAlg) + + digiCfg = acts.examples.DigitizationAlgorithm.Config( + digitizationConfigs=acts.examples.json.readDigiConfigFromJson( + str( + Path(__file__).parent.parent.parent.parent + / "Examples/Configs/odd-digi-smearing-config.json" + ) + ), + surfaceByIdentifier=trackingGeometry.geoIdSurfaceMap(), + randomNumbers=rng, + inputSimHits=simAlg.config.outputSimHits, + ) + digiAlg = acts.examples.DigitizationAlgorithm(digiCfg, acts.logging.INFO) + s.addAlgorithm(digiAlg) + + out = tmp_path / "measurements_edm4hep.root" + + converter = EDM4hepMeasurementOutputConverter( + level=acts.logging.INFO, + inputMeasurements=digiAlg.config.outputMeasurements, + trackingGeometry=trackingGeometry, + ) + s.addAlgorithm(converter) + s.addWriter( + PodioWriter( + level=acts.logging.INFO, + outputPath=str(out), + category="events", + collections=converter.collections, + ) + ) + s.run() # read back in s = Sequencer(numThreads=1) From 75f14cacdfd6de94118ebdd573876703f3c024fb Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 19 Feb 2026 23:03:26 +0100 Subject: [PATCH 10/19] add read function --- .../ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 31 ++++++++++++----- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 33 +++++++++++++++++++ .../UnitTests/Plugins/EDM4hep/CMakeLists.txt | 4 +-- ...eTests.cpp => EDM4HepMeasurementTests.cpp} | 18 +++++++++- 4 files changed, 74 insertions(+), 12 deletions(-) rename Tests/UnitTests/Plugins/EDM4hep/{EDM4HepMeasurementWriteTests.cpp => EDM4HepMeasurementTests.cpp} (91%) diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index f976a2c290c..065e013dbde 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -19,15 +19,10 @@ #include "Acts/Surfaces/PerigeeSurface.hpp" #include "Acts/Surfaces/Surface.hpp" #include "Acts/Utilities/Logger.hpp" -<<<<<<< conflict 1 of 2 % % % % % % % diff from : zvmxkwyn e48722ef "WIP"(parents of rebased revision) -\\\\\\\ to - : zvmxkwyn 063a5a37 "WIP"(rebase destination) + -#include ++ ++ ++ +xqlwmkrn - 2f9d577c "(wip) refactoring of edm4hep writer to wip TrackerHitLocal"( - rebased revision) #include - >>>>>>> conflict 1 of 2 ends +#include +#include #include #include @@ -43,8 +38,8 @@ #if podio_VERSION_MAJOR == 0 || \ (podio_VERSION_MAJOR == 1 && podio_VERSION_MINOR <= 2) - template <> - struct std::hash { +template <> +struct std::hash { std::size_t operator()(const podio::ObjectID& id) const noexcept { auto hash_collectionID = std::hash{}(id.collectionID); auto hash_index = std::hash{}(id.index); @@ -378,4 +373,22 @@ void writeMeasurement(const Acts::GeometryContext& gctx, std::uint64_t cellId, const Acts::Surface& surface, ActsPodioEdm::MutableTrackerHitLocal& to); +/// Data extracted when reading a measurement from EDM4hep +struct MeasurementData { + Acts::DynamicVector parameters; + Acts::DynamicMatrix covariance; + boost::container::static_vector indices; + std::uint64_t cellId{0}; +}; + +/// Read a measurement from an EDM4hep tracker hit +/// +/// This function extracts measurement parameters, covariance, and indices from +/// an EDM4hep TrackerHitLocal. It is the inverse of writeMeasurement. +/// +/// @param from The EDM4hep tracker hit to read from +/// @return The extracted measurement data (parameters, covariance, indices, +/// cellId) +MeasurementData readMeasurement(const ActsPodioEdm::TrackerHitLocal& from); + } // namespace ActsPlugins::EDM4hepUtil diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index 6a7fcfcbe8d..7177b457a62 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -408,4 +408,37 @@ void writeMeasurement(const GeometryContext& gctx, } } +MeasurementData readMeasurement( + const ActsPodioEdm::TrackerHitLocal& from) { + auto indices = detail::decodeIndices(from.getType()); + auto meas = from.getMeasurement(); + auto cov = from.getCovariance(); + + const auto dim = static_cast(indices.size()); + if (meas.size() != dim || cov.size() != dim * dim) { + throw std::runtime_error( + "Size mismatch in EDM4hep tracker hit: measurement size " + + std::to_string(meas.size()) + ", covariance size " + + std::to_string(cov.size()) + ", indices size " + std::to_string(dim)); + } + + MeasurementData result; + result.cellId = from.getCellID(); + result.indices = std::move(indices); + + result.parameters.resize(dim); + for (std::size_t i = 0; i < dim; ++i) { + result.parameters(i) = meas[i]; + } + + result.covariance.resize(dim, dim); + for (std::size_t i = 0; i < dim; ++i) { + for (std::size_t j = 0; j < dim; ++j) { + result.covariance(i, j) = cov[i * dim + j]; // row-major + } + } + + return result; +} + } // namespace ActsPlugins::EDM4hepUtil diff --git a/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt b/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt index ab9a6b9e4ed..62c27d4dc0a 100644 --- a/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt +++ b/Tests/UnitTests/Plugins/EDM4hep/CMakeLists.txt @@ -15,8 +15,8 @@ target_link_libraries( PUBLIC ActsPluginEDM4hep ) -add_unittest(EDM4HepMeasurementWriteTest EDM4HepMeasurementWriteTests.cpp) +add_unittest(EDM4HepMeasurementTest EDM4HepMeasurementTests.cpp) target_link_libraries( - ActsUnitTestEDM4HepMeasurementWriteTest + ActsUnitTestEDM4HepMeasurementTest PUBLIC Acts::PluginEDM4hep ) diff --git a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementTests.cpp similarity index 91% rename from Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp rename to Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementTests.cpp index 949c4be28e6..20358a55ec9 100644 --- a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementWriteTests.cpp +++ b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementTests.cpp @@ -29,7 +29,7 @@ std::default_random_engine rng(123); auto gctx = GeometryContext::dangerouslyDefaultConstruct(); } // namespace -BOOST_AUTO_TEST_SUITE(EDM4hepMeasurementWriterTest) +BOOST_AUTO_TEST_SUITE(EDM4hepMeasurementTest) BOOST_AUTO_TEST_CASE(WriteMeasurement) { auto [parameters, covariance] = @@ -79,6 +79,22 @@ BOOST_AUTO_TEST_CASE(WriteMeasurement) { BOOST_CHECK_EQUAL(unpackedIndices.size(), 2); BOOST_CHECK_EQUAL(unpackedIndices[0], eBoundLoc0); BOOST_CHECK_EQUAL(unpackedIndices[1], eBoundLoc1); + + // Round-trip: read back and verify + auto read = EDM4hepUtil::readMeasurement(to); + BOOST_CHECK_EQUAL(read.cellId, cellId); + BOOST_CHECK_EQUAL(read.indices.size(), 2); + BOOST_CHECK_EQUAL(read.indices[0], eBoundLoc0); + BOOST_CHECK_EQUAL(read.indices[1], eBoundLoc1); + BOOST_CHECK_EQUAL(read.parameters.size(), 2); + CHECK_CLOSE_REL(read.parameters(0), measPos.x(), 1e-6); + CHECK_CLOSE_REL(read.parameters(1), measPos.y(), 1e-6); + BOOST_CHECK_EQUAL(read.covariance.rows(), 2); + BOOST_CHECK_EQUAL(read.covariance.cols(), 2); + CHECK_CLOSE_REL(read.covariance(0, 0), measCov(0, 0), 1e-6); + CHECK_CLOSE_REL(read.covariance(0, 1), measCov(0, 1), 1e-6); + CHECK_CLOSE_REL(read.covariance(1, 0), measCov(1, 0), 1e-6); + CHECK_CLOSE_REL(read.covariance(1, 1), measCov(1, 1), 1e-6); } BOOST_AUTO_TEST_CASE(WriteMeasurementNoPosition) { From d47b7cf487cc61d4a3c2d398ccd10d4fbae84a4a Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 19 Feb 2026 23:13:26 +0100 Subject: [PATCH 11/19] add examples level reading helper --- .../ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp | 16 ++++++++++++++++ Examples/Io/EDM4hep/src/EDM4hepUtil.cpp | 12 ++++++++++++ 2 files changed, 28 insertions(+) diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp index 1ef88685bbd..40f09ec95f9 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp @@ -15,6 +15,7 @@ #include "ActsExamples/EventData/Trajectories.hpp" #include "ActsFatras/EventData/Hit.hpp" #include +#include #include @@ -112,6 +113,21 @@ void writeMeasurement(const Acts::GeometryContext& gctx, ActsPodioEdm::MutableTrackerHitLocal& to, const Acts::Surface& surface); +/// Reads a measurement from an EDM4hep TrackerHitLocal. +/// +/// Converts bound parameters, covariance, and indices from an EDM4hep +/// TrackerHitLocal into an ACTS measurement. The geometry mapper is used to +/// convert the cell ID to a geometry identifier. +/// +/// @param container The measurement container to insert into. +/// @param from The EDM4hep tracker hit to read from. +/// @param geometryMapper Function to map cell ID to geometry identifier. +/// @return Proxy to the created measurement. +VariableBoundMeasurementProxy readMeasurement( + MeasurementContainer& container, + const ActsPodioEdm::TrackerHitLocal& from, + const MapGeometryIdFrom& geometryMapper); + /// Writes a trajectory to EDM4hep. /// /// Inpersistent information: diff --git a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp index daa244df121..6b29b0553b2 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp @@ -18,6 +18,7 @@ #include "ActsExamples/Validation/TrackClassification.hpp" #include "ActsPlugins/DD4hep/DD4hepDetectorElement.hpp" #include "ActsPlugins/EDM4hep/EDM4hepUtil.hpp" +#include #include @@ -203,6 +204,17 @@ void EDM4hepUtil::writeMeasurement( cellId, surface, to); } +VariableBoundMeasurementProxy EDM4hepUtil::readMeasurement( + MeasurementContainer& container, const ActsPodioEdm::TrackerHitLocal& from, + const MapGeometryIdFrom& geometryMapper) { + auto data = ActsPlugins::EDM4hepUtil::readMeasurement(from); + Acts::GeometryIdentifier geometryId = geometryMapper(data.cellId); + + return container.emplaceMeasurement( + static_cast(data.indices.size()), geometryId, data.indices, + data.parameters, data.covariance); +} + void EDM4hepUtil::writeTrajectory( const Acts::GeometryContext& gctx, double Bz, const Trajectories& from, edm4hep::MutableTrack to, std::size_t fromIndex, From 1d89f277d388c953f1a7e79eb1d8e66d218c2304 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Thu, 19 Feb 2026 23:16:56 +0100 Subject: [PATCH 12/19] update the read input converter to use new type --- .../EDM4hepMeasurementInputConverter.hpp | 22 ++++----- .../src/EDM4hepMeasurementInputConverter.cpp | 45 ++++++++++++------- Python/Examples/src/plugins/EDM4hep.cpp | 7 +-- Python/Examples/tests/test_edm4hep.py | 45 ++++++++++--------- 4 files changed, 68 insertions(+), 51 deletions(-) diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp index 947bd561bd3..0a46e9b53aa 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp @@ -12,33 +12,33 @@ #include "ActsExamples/EventData/Cluster.hpp" #include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/Framework/DataHandle.hpp" +#include "ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp" #include "ActsExamples/Io/Podio/PodioInputConverter.hpp" +#include #include namespace ActsExamples { -/// Read in a measurement cluster collection as EDM4hep from a @c podio::Frame. -/// -/// Inpersistent information: -/// - hit index -/// - 1D local coords? -/// - segment path -/// -/// Known issues: -/// - cluster channels are read from inappropriate fields -/// - local 2D coordinates and time are read from position +class DD4hepDetector; + +/// Read in a measurement collection from EDM4hep TrackerHitLocal format. class EDM4hepMeasurementInputConverter final : public PodioInputConverter { public: struct Config { /// Where to read the input frame from. std::string inputFrame; + /// Name of the input tracker hit local collection. + std::string inputTrackerHitsLocal = "ActsTrackerHitsLocal"; /// Output measurement collection. std::string outputMeasurements; /// Output measurement to sim hit collection. std::string outputMeasurementSimHitsMap; /// Output cluster collection (optional). std::string outputClusters; + + /// DD4hep detector for cellID to geometry identifier resolution. + std::shared_ptr dd4hepDetector; }; /// Construct the cluster reader. @@ -59,6 +59,8 @@ class EDM4hepMeasurementInputConverter final : public PodioInputConverter { private: Config m_cfg; + EDM4hepUtil::MapGeometryIdFrom m_geometryMapper; + WriteDataHandle m_outputMeasurements{ this, "OutputMeasurements"}; diff --git a/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp b/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp index 6de7228db0f..f8612d16cb1 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp @@ -8,15 +8,16 @@ #include "ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp" +#include "ActsExamples/DD4hepDetector/DD4hepDetector.hpp" #include "ActsExamples/EventData/Cluster.hpp" #include "ActsExamples/EventData/Measurement.hpp" #include "ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp" -#include "ActsPlugins/EDM4hep/PodioUtil.hpp" +#include "ActsPlugins/DD4hep/DD4hepDetectorElement.hpp" +#include #include -#include -#include +#include #include namespace ActsExamples { @@ -30,34 +31,46 @@ EDM4hepMeasurementInputConverter::EDM4hepMeasurementInputConverter( if (m_cfg.outputMeasurements.empty()) { throw std::invalid_argument("Missing measurement output collection"); } + if (m_cfg.dd4hepDetector == nullptr) { + throw std::invalid_argument("Missing DD4hep detector"); + } m_outputMeasurements.initialize(m_cfg.outputMeasurements); m_outputMeasurementSimHitsMap.initialize(m_cfg.outputMeasurementSimHitsMap); m_outputClusters.maybeInitialize(m_cfg.outputClusters); + + m_geometryMapper = [detector = m_cfg.dd4hepDetector](std::uint64_t cellId) { + const auto& vm = detector->dd4hepDetector().volumeManager(); + const auto detElement = vm.lookupDetElement(cellId); + + const auto* ext = + detElement.extension( + false); + if (ext == nullptr) { + throw std::runtime_error( + "EDM4hepMeasurementInputConverter: DetElement has no " + "DD4hepDetectorElementExtension for cellId " + + std::to_string(cellId)); + } + return ext->detectorElement().surface().geometryId(); + }; } ProcessCode EDM4hepMeasurementInputConverter::convert( const AlgorithmContext& ctx, const podio::Frame& frame) const { MeasurementContainer measurements; ClusterContainer clusters; - // TODO what about those? IndexMultimap measurementSimHitsMap; - const auto& trackerHitPlaneCollection = - frame.get("ActsTrackerHitsPlane"); - const auto& trackerHitRawCollection = - frame.get("ActsTrackerHitsRaw"); - - for (const auto& trackerHitPlane : trackerHitPlaneCollection) { - Cluster cluster; - EDM4hepUtil::readMeasurement( - measurements, trackerHitPlane, &trackerHitRawCollection, &cluster, - [](std::uint64_t cellId) { return Acts::GeometryIdentifier(cellId); }); + const auto& trackerHitLocalCollection = + frame.get( + m_cfg.inputTrackerHitsLocal); - clusters.push_back(std::move(cluster)); + for (const auto& trackerHitLocal : trackerHitLocalCollection) { + EDM4hepUtil::readMeasurement(measurements, trackerHitLocal, + m_geometryMapper); } - // Write the data to the EventStore m_outputMeasurements(ctx, std::move(measurements)); m_outputMeasurementSimHitsMap(ctx, std::move(measurementSimHitsMap)); if (!m_cfg.outputClusters.empty()) { diff --git a/Python/Examples/src/plugins/EDM4hep.cpp b/Python/Examples/src/plugins/EDM4hep.cpp index 9dbfcd1882d..67a981f3680 100644 --- a/Python/Examples/src/plugins/EDM4hep.cpp +++ b/Python/Examples/src/plugins/EDM4hep.cpp @@ -100,11 +100,12 @@ PYBIND11_MODULE(ActsExamplesPythonBindingsEDM4hep, m) { } { - auto [alg, c] = + auto [alg, config] = declareAlgorithm( m, "EDM4hepMeasurementInputConverter"); - ACTS_PYTHON_STRUCT(c, inputFrame, outputMeasurements, - outputMeasurementSimHitsMap, outputClusters); + ACTS_PYTHON_STRUCT(config, inputFrame, inputTrackerHitsLocal, + outputMeasurements, outputMeasurementSimHitsMap, + outputClusters, dd4hepDetector); } { diff --git a/Python/Examples/tests/test_edm4hep.py b/Python/Examples/tests/test_edm4hep.py index 8e8d4f0f88c..3ed8cc61fb2 100644 --- a/Python/Examples/tests/test_edm4hep.py +++ b/Python/Examples/tests/test_edm4hep.py @@ -591,34 +591,35 @@ def test_edm4hep_measurement_reader(tmp_path, ptcl_gun, rng): ) s.run() - # read back in - s = Sequencer(numThreads=1) + # read back in + s = Sequencer(numThreads=1) - s.addReader( - PodioReader( - level=acts.logging.WARNING, - inputPath=str(out), - outputFrame="events", - category="events", + s.addReader( + PodioReader( + level=acts.logging.WARNING, + inputPath=str(out), + outputFrame="events", + category="events", + ) ) - ) - s.addAlgorithm( - EDM4hepMeasurementInputConverter( - level=acts.logging.WARNING, - inputFrame="events", - outputMeasurements="measurements", - outputMeasurementSimHitsMap="simhitsmap", + s.addAlgorithm( + EDM4hepMeasurementInputConverter( + level=acts.logging.WARNING, + inputFrame="events", + outputMeasurements="measurements", + outputMeasurementSimHitsMap="simhitsmap", + dd4hepDetector=detector, + ) ) - ) - alg = AssertCollectionExistsAlg( - ["measurements", "simhitsmap"], "check_alg", acts.logging.WARNING - ) - s.addAlgorithm(alg) + alg = AssertCollectionExistsAlg( + ["measurements", "simhitsmap"], "check_alg", acts.logging.WARNING + ) + s.addAlgorithm(alg) - s.run() + s.run() - assert alg.events_seen == 10 + assert alg.events_seen == 10 @pytest.mark.edm4hep From 2ae44afe8a6566b13980c4a8e8a79d1bdb739ad3 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 20 Feb 2026 17:27:34 +0100 Subject: [PATCH 13/19] fix missing include --- Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index 065e013dbde..dc4c3dae920 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -19,6 +19,7 @@ #include "Acts/Surfaces/PerigeeSurface.hpp" #include "Acts/Surfaces/Surface.hpp" #include "Acts/Utilities/Logger.hpp" +#include "Acts/Vertexing/Vertex.hpp" #include #include From 1a9dec6e66b198b7c9e675f89ff1146ae35e9827 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 20 Feb 2026 18:28:55 +0100 Subject: [PATCH 14/19] fix docs errors --- Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index dc4c3dae920..13c59299993 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -376,9 +376,13 @@ void writeMeasurement(const Acts::GeometryContext& gctx, /// Data extracted when reading a measurement from EDM4hep struct MeasurementData { + /// Measurement parameters (local coordinates) Acts::DynamicVector parameters; + /// Covariance matrix of the measurement Acts::DynamicMatrix covariance; + /// Indices of the measured parameters boost::container::static_vector indices; + /// Cell ID of the measurement std::uint64_t cellId{0}; }; From c1649d4cc36d53bf2c0af606f57840b450fe1a84 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 20 Feb 2026 22:19:25 +0100 Subject: [PATCH 15/19] format --- .../Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp | 3 +-- Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp | 2 +- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp index 40f09ec95f9..544b39014c8 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepUtil.hpp @@ -124,8 +124,7 @@ void writeMeasurement(const Acts::GeometryContext& gctx, /// @param geometryMapper Function to map cell ID to geometry identifier. /// @return Proxy to the created measurement. VariableBoundMeasurementProxy readMeasurement( - MeasurementContainer& container, - const ActsPodioEdm::TrackerHitLocal& from, + MeasurementContainer& container, const ActsPodioEdm::TrackerHitLocal& from, const MapGeometryIdFrom& geometryMapper); /// Writes a trajectory to EDM4hep. diff --git a/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp b/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp index f8612d16cb1..59d60093076 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepMeasurementInputConverter.cpp @@ -68,7 +68,7 @@ ProcessCode EDM4hepMeasurementInputConverter::convert( for (const auto& trackerHitLocal : trackerHitLocalCollection) { EDM4hepUtil::readMeasurement(measurements, trackerHitLocal, - m_geometryMapper); + m_geometryMapper); } m_outputMeasurements(ctx, std::move(measurements)); diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index 7177b457a62..120e3b65f1a 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -408,8 +408,7 @@ void writeMeasurement(const GeometryContext& gctx, } } -MeasurementData readMeasurement( - const ActsPodioEdm::TrackerHitLocal& from) { +MeasurementData readMeasurement(const ActsPodioEdm::TrackerHitLocal& from) { auto indices = detail::decodeIndices(from.getType()); auto meas = from.getMeasurement(); auto cov = from.getCovariance(); From 684dafbb8468aef20b134114bd7878ebf0c23ab3 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 27 Feb 2026 10:23:53 +0100 Subject: [PATCH 16/19] use std::format in exception --- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index 120e3b65f1a..41aea1686b1 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -416,9 +416,9 @@ MeasurementData readMeasurement(const ActsPodioEdm::TrackerHitLocal& from) { const auto dim = static_cast(indices.size()); if (meas.size() != dim || cov.size() != dim * dim) { throw std::runtime_error( - "Size mismatch in EDM4hep tracker hit: measurement size " + - std::to_string(meas.size()) + ", covariance size " + - std::to_string(cov.size()) + ", indices size " + std::to_string(dim)); + std::format("Size mismatch in EDM4hep tracker hit: measurement size " + "{}, covariance size {}, indices size {}", + meas.size(), cov.size(), dim)); } MeasurementData result; From b9a0d5d3db1d47699b55aba06801300311bdd3a0 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 27 Feb 2026 10:27:50 +0100 Subject: [PATCH 17/19] use BoundVector/Matrix + subspace --- Examples/Io/EDM4hep/src/EDM4hepUtil.cpp | 14 ++++++++++++-- .../ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 19 +++++++++++-------- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 12 +++++------- .../EDM4hep/EDM4HepMeasurementTests.cpp | 15 ++++++--------- 4 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp index 6b29b0553b2..c34f44dce53 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp @@ -210,9 +210,19 @@ VariableBoundMeasurementProxy EDM4hepUtil::readMeasurement( auto data = ActsPlugins::EDM4hepUtil::readMeasurement(from); Acts::GeometryIdentifier geometryId = geometryMapper(data.cellId); + const auto dim = data.indices.size(); + Acts::DynamicVector parameters(dim); + Acts::DynamicMatrix covariance(dim, dim); + for (std::size_t i = 0; i < dim; ++i) { + parameters(i) = data.parameters(data.indices[i]); + for (std::size_t j = 0; j < dim; ++j) { + covariance(i, j) = data.covariance(data.indices[i], data.indices[j]); + } + } + return container.emplaceMeasurement( - static_cast(data.indices.size()), geometryId, data.indices, - data.parameters, data.covariance); + static_cast(dim), geometryId, data.indices, parameters, + covariance); } void EDM4hepUtil::writeTrajectory( diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index 13c59299993..6df3f6d1c55 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -15,6 +15,7 @@ #include "Acts/EventData/TrackParameters.hpp" #include "Acts/EventData/TrackProxyConcept.hpp" #include "Acts/EventData/TrackStatePropMask.hpp" +#include "Acts/EventData/Types.hpp" #include "Acts/Geometry/GeometryContext.hpp" #include "Acts/Surfaces/PerigeeSurface.hpp" #include "Acts/Surfaces/Surface.hpp" @@ -26,6 +27,7 @@ #include #include +#include #include #include #include @@ -340,8 +342,8 @@ void writeVertex(const Acts::Vertex& vertex, edm4hep::MutableVertex to); namespace detail { // These functions are exposed here so they can be used from the unit tests std::uint32_t encodeIndices(std::span indices); -boost::container::static_vector decodeIndices( - std::uint32_t type); +boost::container::static_vector +decodeIndices(std::uint32_t type); } // namespace detail /// Write a measurement to an EDM4hep tracker hit @@ -376,12 +378,13 @@ void writeMeasurement(const Acts::GeometryContext& gctx, /// Data extracted when reading a measurement from EDM4hep struct MeasurementData { - /// Measurement parameters (local coordinates) - Acts::DynamicVector parameters; - /// Covariance matrix of the measurement - Acts::DynamicMatrix covariance; - /// Indices of the measured parameters - boost::container::static_vector indices; + /// Measurement parameters (local coordinates, full bound space) + Acts::BoundVector parameters{Acts::BoundVector::Zero()}; + /// Covariance matrix of the measurement (full bound space) + Acts::BoundMatrix covariance{Acts::BoundMatrix::Zero()}; + /// Indices of the measured parameters (subspace) + boost::container::static_vector + indices; /// Cell ID of the measurement std::uint64_t cellId{0}; }; diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index 41aea1686b1..dd1805f6f07 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -340,9 +340,9 @@ std::uint32_t encodeIndices(std::span indices) { /// /// @param type Packed 32-bit unsigned integer containing encoded indices /// @return Vector of decoded parameter indices (0-5 for each bound parameter) -boost::container::static_vector decodeIndices( +boost::container::static_vector decodeIndices( std::uint32_t type) { - boost::container::static_vector result; + boost::container::static_vector result; std::uint8_t size = type & 0xF; if (size > eBoundSize) { throw std::runtime_error( @@ -425,15 +425,13 @@ MeasurementData readMeasurement(const ActsPodioEdm::TrackerHitLocal& from) { result.cellId = from.getCellID(); result.indices = std::move(indices); - result.parameters.resize(dim); for (std::size_t i = 0; i < dim; ++i) { - result.parameters(i) = meas[i]; + result.parameters(result.indices[i]) = meas[i]; } - - result.covariance.resize(dim, dim); for (std::size_t i = 0; i < dim; ++i) { for (std::size_t j = 0; j < dim; ++j) { - result.covariance(i, j) = cov[i * dim + j]; // row-major + result.covariance(result.indices[i], result.indices[j]) = + cov[i * dim + j]; // row-major } } diff --git a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementTests.cpp b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementTests.cpp index 20358a55ec9..ebbe7f14aef 100644 --- a/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementTests.cpp +++ b/Tests/UnitTests/Plugins/EDM4hep/EDM4HepMeasurementTests.cpp @@ -86,15 +86,12 @@ BOOST_AUTO_TEST_CASE(WriteMeasurement) { BOOST_CHECK_EQUAL(read.indices.size(), 2); BOOST_CHECK_EQUAL(read.indices[0], eBoundLoc0); BOOST_CHECK_EQUAL(read.indices[1], eBoundLoc1); - BOOST_CHECK_EQUAL(read.parameters.size(), 2); - CHECK_CLOSE_REL(read.parameters(0), measPos.x(), 1e-6); - CHECK_CLOSE_REL(read.parameters(1), measPos.y(), 1e-6); - BOOST_CHECK_EQUAL(read.covariance.rows(), 2); - BOOST_CHECK_EQUAL(read.covariance.cols(), 2); - CHECK_CLOSE_REL(read.covariance(0, 0), measCov(0, 0), 1e-6); - CHECK_CLOSE_REL(read.covariance(0, 1), measCov(0, 1), 1e-6); - CHECK_CLOSE_REL(read.covariance(1, 0), measCov(1, 0), 1e-6); - CHECK_CLOSE_REL(read.covariance(1, 1), measCov(1, 1), 1e-6); + CHECK_CLOSE_REL(read.parameters(eBoundLoc0), measPos.x(), 1e-6); + CHECK_CLOSE_REL(read.parameters(eBoundLoc1), measPos.y(), 1e-6); + CHECK_CLOSE_REL(read.covariance(eBoundLoc0, eBoundLoc0), measCov(0, 0), 1e-6); + CHECK_CLOSE_REL(read.covariance(eBoundLoc0, eBoundLoc1), measCov(0, 1), 1e-6); + CHECK_CLOSE_REL(read.covariance(eBoundLoc1, eBoundLoc0), measCov(1, 0), 1e-6); + CHECK_CLOSE_REL(read.covariance(eBoundLoc1, eBoundLoc1), measCov(1, 1), 1e-6); } BOOST_AUTO_TEST_CASE(WriteMeasurementNoPosition) { From a2742a4d2380e300841fe871d09271d29d772e54 Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 27 Feb 2026 12:06:00 +0100 Subject: [PATCH 18/19] PR feedback --- .../Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp | 2 +- .../Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp | 2 +- Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp | 4 ++-- Plugins/EDM4hep/src/EDM4hepUtil.cpp | 2 +- Python/Examples/tests/test_edm4hep.py | 2 ++ 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp index 0a46e9b53aa..372f2a18a4f 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementInputConverter.hpp @@ -29,7 +29,7 @@ class EDM4hepMeasurementInputConverter final : public PodioInputConverter { /// Where to read the input frame from. std::string inputFrame; /// Name of the input tracker hit local collection. - std::string inputTrackerHitsLocal = "ActsTrackerHitsLocal"; + std::string inputTrackerHitsLocal; /// Output measurement collection. std::string outputMeasurements; /// Output measurement to sim hit collection. diff --git a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp index d2a55d280f8..fc5a66c3e14 100644 --- a/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp +++ b/Examples/Io/EDM4hep/include/ActsExamples/Io/EDM4hep/EDM4hepMeasurementOutputConverter.hpp @@ -36,7 +36,7 @@ class EDM4hepMeasurementOutputConverter final : public PodioOutputConverter { /// Which measurement collection to write. std::string inputMeasurements; /// Name of the output tracker hit raw collection. - std::string outputTrackerHitsLocal = "ActsTrackerHitsLocal"; + std::string outputTrackerHitsLocal; /// Tracking geometry for surface lookup (local-to-global transform). std::shared_ptr trackingGeometry; diff --git a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp index 6df3f6d1c55..814a628f0f7 100644 --- a/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp +++ b/Plugins/EDM4hep/include/ActsPlugins/EDM4hep/EDM4hepUtil.hpp @@ -21,8 +21,8 @@ #include "Acts/Surfaces/Surface.hpp" #include "Acts/Utilities/Logger.hpp" #include "Acts/Vertexing/Vertex.hpp" -#include -#include +#include "ActsPodioEdm/MutableTrackerHitLocal.h" +#include "ActsPodioEdm/TrackerHitLocal.h" #include #include diff --git a/Plugins/EDM4hep/src/EDM4hepUtil.cpp b/Plugins/EDM4hep/src/EDM4hepUtil.cpp index dd1805f6f07..8ef05088e19 100644 --- a/Plugins/EDM4hep/src/EDM4hepUtil.cpp +++ b/Plugins/EDM4hep/src/EDM4hepUtil.cpp @@ -413,7 +413,7 @@ MeasurementData readMeasurement(const ActsPodioEdm::TrackerHitLocal& from) { auto meas = from.getMeasurement(); auto cov = from.getCovariance(); - const auto dim = static_cast(indices.size()); + const auto dim = indices.size(); if (meas.size() != dim || cov.size() != dim * dim) { throw std::runtime_error( std::format("Size mismatch in EDM4hep tracker hit: measurement size " diff --git a/Python/Examples/tests/test_edm4hep.py b/Python/Examples/tests/test_edm4hep.py index 3ed8cc61fb2..efd9705679a 100644 --- a/Python/Examples/tests/test_edm4hep.py +++ b/Python/Examples/tests/test_edm4hep.py @@ -578,6 +578,7 @@ def test_edm4hep_measurement_reader(tmp_path, ptcl_gun, rng): converter = EDM4hepMeasurementOutputConverter( level=acts.logging.INFO, inputMeasurements=digiAlg.config.outputMeasurements, + outputTrackerHitsLocal="ActsTrackerHitsLocal", trackingGeometry=trackingGeometry, ) s.addAlgorithm(converter) @@ -606,6 +607,7 @@ def test_edm4hep_measurement_reader(tmp_path, ptcl_gun, rng): EDM4hepMeasurementInputConverter( level=acts.logging.WARNING, inputFrame="events", + inputTrackerHitsLocal="ActsTrackerHitsLocal", outputMeasurements="measurements", outputMeasurementSimHitsMap="simhitsmap", dd4hepDetector=detector, From 5cb072702865729efa9257b3a39cd3fe4cbb36db Mon Sep 17 00:00:00 2001 From: Paul Gessinger Date: Fri, 27 Feb 2026 13:15:10 +0100 Subject: [PATCH 19/19] clang format --- Examples/Io/EDM4hep/src/EDM4hepUtil.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp index c34f44dce53..6b18c8bb943 100644 --- a/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp +++ b/Examples/Io/EDM4hep/src/EDM4hepUtil.cpp @@ -220,9 +220,9 @@ VariableBoundMeasurementProxy EDM4hepUtil::readMeasurement( } } - return container.emplaceMeasurement( - static_cast(dim), geometryId, data.indices, parameters, - covariance); + return container.emplaceMeasurement(static_cast(dim), + geometryId, data.indices, parameters, + covariance); } void EDM4hepUtil::writeTrajectory(