tracking: add silicon tracker alignment workflow (MeasurementToMille) - #2603
tracking: add silicon tracker alignment workflow (MeasurementToMille)#2603wdconinc wants to merge 1 commit into
Conversation
Adds the infrastructure for a Millepede-II global alignment workflow
for the ePIC silicon tracking detectors, following the two-stage
thread-safe design:
1. EICrecon fills edm4eic::AlignmentDerivativeSet collections (PODIO,
thread-safe) via the new MeasurementToMille algorithm.
2. An offline tool (scripts/podio_to_mille.py in eic/epic) converts
the PODIO output to the binary Mille format consumed by pede.
New files:
src/algorithms/tracking/SiliconAlignmentLabels.h
- Defines the 12 silicon alignment layers and their Millepede label
convention: label = layer_index * 6 + dof + 1 (1-based, 72 total)
- buildSiliconSurfaceLabelMap() walks the ACTS TrackingGeometry at
init time and maps GeometryIdentifier -> layer index via DD4hep
DetElement path matching; no hardcoded geometry IDs
src/algorithms/tracking/MeasurementToMilleConfig.h
- Config: maxChi2PerNDF, minMomentum, fixedLayers
src/algorithms/tracking/MeasurementToMille.{h,cc}
- Per-event algorithm: quality-cuts tracks, iterates
track.getMeasurements(), fills one AlignmentDerivativeSet per
(track, silicon surface) pair
- Local/global derivatives currently simplified (TODO: replace with
Acts::detail::makeTrackAlignmentState() once ACTS integration is
complete)
src/factories/tracking/MeasurementToMille_factory.h
- JOmniFactory wiring with PodioInput/PodioOutput and exposed
ParameterRef entries for all config fields
src/global/alignment/alignment.cc + CMakeLists.txt
- New 'alignment' JANA plugin registering the factory for
CentralCKFTracks + CentralTrackerMeasurements ->
SiliconAlignmentDerivatives; layer 0 fixed as reference
.codespell-ignore
- Adds 'millepede' and 'mille' (correct names of alignment software)
Requires edm4eic >= 8.10.0 for edm4eic::AlignmentDerivativeSet.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds an initial (PODIO-based) Millepede-II alignment derivatives production workflow for the ePIC silicon tracker by introducing a new global alignment plugin and a MeasurementToMille tracking algorithm.
Changes:
- Add new
src/global/alignmentJANA plugin wiringCentralCKFTracks(+ measurements) toSiliconAlignmentDerivatives. - Introduce
MeasurementToMillealgorithm + config and silicon layer/label mapping utilities. - Update global CMake to build the new alignment plugin; extend codespell ignore list for “mille/millepede”.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| src/global/CMakeLists.txt | Adds the new alignment subdirectory to the global build. |
| src/global/alignment/CMakeLists.txt | Defines the new alignment plugin target and dependencies. |
| src/global/alignment/alignment.cc | Registers MeasurementToMille_factory to produce SiliconAlignmentDerivatives. |
| src/factories/tracking/MeasurementToMille_factory.h | New JOmniFactory bridging PODIO inputs/outputs to the algorithm. |
| src/algorithms/tracking/SiliconAlignmentLabels.h | Provides silicon layer path table + (layer,DOF) Mille label scheme + surface→layer map builder. |
| src/algorithms/tracking/MeasurementToMilleConfig.h | Adds configuration for track selection and fixed layers. |
| src/algorithms/tracking/MeasurementToMille.h | Declares the new alignment-derivative filling algorithm. |
| src/algorithms/tracking/MeasurementToMille.cc | Implements per-track/per-measurement derivative set filling (currently simplified placeholders). |
| src/algorithms/tracking/CMakeLists.txt | Notes the new EDM4eic dependency requirement for the tracking algorithms library. |
| .codespell-ignore | Adds “millepede” and “mille” to the ignore list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| # NOTE: The edm4eic::AlignmentDerivativeSet type is defined in the alignment | ||
| # branch of EDM4eic (~/git/EDM4eic/build-align/edm4eic/). Once it is merged | ||
| # into an official EDM4eic release and found by find_package(EDM4EIC), no extra | ||
| # include path is needed. Until then, add the build directory manually: | ||
| # | ||
| # plugin_include_directories(${PLUGIN_NAME} SYSTEM PUBLIC | ||
| # $ENV{HOME}/git/EDM4eic/build-align) |
There was a problem hiding this comment.
The NOTE block refers to a developer-local build path ($ENV{HOME}/git/EDM4eic/build-align). Committing local paths into CMake can confuse users and doesn’t enforce the required EDM4eic version at configure time. Prefer adding a proper version requirement (e.g., bump EDM4EIC_VERSION_MIN / find_package(EDM4EIC 8.10.0) for this plugin) and/or a configure-time check that errors out when AlignmentDerivativeSet isn’t available, instead of documenting a local include workaround.
| # NOTE: The edm4eic::AlignmentDerivativeSet type is defined in the alignment | |
| # branch of EDM4eic (~/git/EDM4eic/build-align/edm4eic/). Once it is merged | |
| # into an official EDM4eic release and found by find_package(EDM4EIC), no extra | |
| # include path is needed. Until then, add the build directory manually: | |
| # | |
| # plugin_include_directories(${PLUGIN_NAME} SYSTEM PUBLIC | |
| # $ENV{HOME}/git/EDM4eic/build-align) | |
| # Require an EDM4eic installation that provides AlignmentDerivativeSet via the | |
| # package discovered by plugin_add_event_model(${PLUGIN_NAME}). | |
| find_path(EDM4EIC_ALIGNMENT_DERIVATIVESET_INCLUDE_DIR | |
| NAMES edm4eic/AlignmentDerivativeSet.h | |
| HINTS ${EDM4EIC_INCLUDE_DIRS} | |
| ) | |
| if(NOT EDM4EIC_ALIGNMENT_DERIVATIVESET_INCLUDE_DIR) | |
| message(FATAL_ERROR | |
| "${PLUGIN_NAME} requires an EDM4eic installation that provides " | |
| "edm4eic/AlignmentDerivativeSet.h. Please configure against a compatible " | |
| "EDM4eic version instead of adding a developer-local include path.") | |
| endif() |
| // Copyright (C) 2025 ePIC Collaboration | ||
|
|
||
| #include <JANA/JApplication.h> | ||
| #include <JANA/JApplicationFwd.h> |
There was a problem hiding this comment.
This plugin directly includes edm4eic/AlignmentDerivativeSetCollection.h, but the repository’s CMake currently only requires EDM4eic >= 8.0. This will hard-fail compilation for users with EDM4eic < 8.10.0. Consider adding an explicit EDM4eic version guard here (like src/global/tracking/tracking.cc does with edm4eic/EDM4eicVersion.h) so the build fails with a clear message, and/or update the project-level EDM4EIC_VERSION_MIN accordingly.
| #include <JANA/JApplicationFwd.h> | |
| #include <JANA/JApplicationFwd.h> | |
| #include <edm4eic/EDM4eicVersion.h> | |
| #if EDM4EIC_VERSION_MAJOR < 8 || (EDM4EIC_VERSION_MAJOR == 8 && EDM4EIC_VERSION_MINOR < 10) | |
| #error "src/global/alignment/alignment.cc requires EDM4eic >= 8.10.0 because it uses edm4eic/AlignmentDerivativeSetCollection.h" | |
| #endif |
| "0-based layer indices to fix in Millepede"}; | ||
|
|
||
| Service<AlgorithmsInit_service> m_algorithmsInit{this}; | ||
|
|
There was a problem hiding this comment.
MeasurementToMille_factory declares Service<AlgorithmsInit_service> but does not include the header that declares AlgorithmsInit_service. Unlike src/global/tracking/tracking.cc, alignment.cc includes this factory header without previously including services/algorithms_init/AlgorithmsInit_service.h, so this will not compile. Include services/algorithms_init/AlgorithmsInit_service.h (or at least forward-declare AlgorithmsInit_service) in this header to remove include-order dependencies.
| const float measuredU = meas.getLoc().a; | ||
| const float residual = measuredU; // placeholder: predicted = 0 | ||
| const float residualUncert = std::sqrt(std::abs(meas.getCovariance().xx)); |
There was a problem hiding this comment.
residualUncert is computed as sqrt(abs(cov.xx)), which can silently turn an invalid/negative variance into a positive uncertainty and may also yield 0 for non-positive variances. It’s safer to validate the covariance element (e.g., require cov.xx > 0) and skip/log the measurement (or set a minimal floor) when it’s non-physical, so downstream Millepede weighting can’t be corrupted.
| const float measuredU = meas.getLoc().a; | |
| const float residual = measuredU; // placeholder: predicted = 0 | |
| const float residualUncert = std::sqrt(std::abs(meas.getCovariance().xx)); | |
| const float measuredU = meas.getLoc().a; | |
| const float residual = measuredU; // placeholder: predicted = 0 | |
| const float variance = meas.getCovariance().xx; | |
| if (!std::isfinite(variance) || variance <= 0.f) { | |
| debug("Skipping measurement on surface {} in layer {}: non-physical local-u variance={}", geoId, | |
| layerIndex, variance); | |
| continue; | |
| } | |
| const float residualUncert = std::sqrt(variance); |
| plugin_link_libraries(${PLUGIN_NAME} Eigen3::Eigen particle_service_library) | ||
|
|
||
| # MeasurementToMille.cc uses edm4eic::AlignmentDerivativeSet, available in | ||
| # edm4eic >= 8.10.0. |
There was a problem hiding this comment.
The added note documents that MeasurementToMille requires edm4eic >= 8.10.0, but the build system doesn’t enforce this in CMake for this target. Consider turning this into an actual configure-time check (or raising EDM4EIC_VERSION_MIN) so users get a clear error instead of a compile failure when AlignmentDerivativeSet headers are missing.
| # edm4eic >= 8.10.0. | |
| # edm4eic >= 8.10.0. | |
| if(DEFINED EDM4EIC_VERSION) | |
| if(EDM4EIC_VERSION VERSION_LESS "8.10.0") | |
| message(FATAL_ERROR | |
| "${PLUGIN_NAME} requires edm4eic >= 8.10.0 because " | |
| "MeasurementToMille.cc uses edm4eic::AlignmentDerivativeSet " | |
| "(found: ${EDM4EIC_VERSION})") | |
| endif() | |
| else() | |
| message(FATAL_ERROR | |
| "${PLUGIN_NAME} requires edm4eic >= 8.10.0 because " | |
| "MeasurementToMille.cc uses edm4eic::AlignmentDerivativeSet, " | |
| "but EDM4EIC_VERSION is not defined so the requirement cannot be " | |
| "verified at configure time") | |
| endif() |
| /// Return the Millepede base label (DOF 0 label) for a given 0-based layer index. | ||
| /// All DOF labels for this layer are [baseLabel, baseLabel + kAlignNDOF - 1]. | ||
| inline int siliconAlignmentBaseLabel(int layerIndex) { return layerIndex * kAlignNDOF + 1; } | ||
|
|
||
| /// Return the Millepede label for a specific (layer, DOF) pair. | ||
| inline int siliconAlignmentLabel(int layerIndex, AlignmentDOF dof) { | ||
| return siliconAlignmentBaseLabel(layerIndex) + static_cast<int>(dof); | ||
| } | ||
|
|
||
| /// Decode a Millepede label back to (layerIndex, dof). | ||
| /// Returns std::nullopt if the label is out of range. | ||
| inline std::optional<std::pair<int, AlignmentDOF>> decodeSiliconAlignmentLabel(int label) { | ||
| if (label < 1 || label > static_cast<int>(kNSiliconLayers) * kAlignNDOF) { | ||
| return std::nullopt; | ||
| } | ||
| int zero_based = label - 1; | ||
| int layerIndex = zero_based / kAlignNDOF; | ||
| auto dof = static_cast<AlignmentDOF>(zero_based % kAlignNDOF); | ||
| return std::make_pair(layerIndex, dof); | ||
| } |
There was a problem hiding this comment.
SiliconAlignmentLabels introduces label encoding/decoding logic and the layer path table, but there are no unit tests covering boundary conditions (e.g., label 0/out-of-range, first/last valid label, round-trip encode→decode). There is existing Catch2 algorithm test infrastructure under src/tests/algorithms_test/, so adding a small test file for these pure functions would help prevent silent label-scheme regressions.
Summary
Adds the infrastructure for a Millepede-II global alignment workflow for the ePIC silicon tracking detectors.
Design
A two-stage, thread-safe pipeline:
edm4eic::AlignmentDerivativeSetcollections per event via the newMeasurementToMillealgorithm (JANA2 / PODIO, fully thread-safe).scripts/podio_to_mille.pyin eic/epic converts the PODIO output to the binary Mille format consumed bypede.Writing directly to a binary Mille file from inside JANA2 is not thread-safe; the PODIO intermediate step solves this cleanly.
New files
SiliconAlignmentLabels.hlayer×6+dof+1(72 total);buildSiliconSurfaceLabelMap()walks ACTS geometry at init, mapsGeometryIdentifier→layervia DD4hep path matchingMeasurementToMilleConfig.hmaxChi2PerNDF,minMomentum,fixedLayersMeasurementToMille.{h,cc}track.getMeasurements(), emits one derivative set per silicon surface hitMeasurementToMille_factory.hJOmniFactorywithPodioInput/PodioOutputand exposedParameterReffor all config fieldssrc/global/alignment/alignmentJANA plugin; inputs:CentralCKFTracks+CentralTrackerMeasurements; output:SiliconAlignmentDerivativesKnown limitations / TODOs
[1,0,0,0,0]local,[-1,0,0,0,0,0]global). The correct derivatives require callingActs::detail::makeTrackAlignmentState()on the ACTS Kalman smoother track states. This is marked as TODO in the code and will be addressed once the ACTS Alignment kernel is accessible from EICrecon.measured_u(predicted = 0 placeholder). Same fix needed.Dependencies
Requires
edm4eic >= 8.10.0foredm4eic::AlignmentDerivativeSet(see eic/EDM4eic#161).Related