Skip to content

Commit afad806

Browse files
pbutticlaude
andcommitted
ALICE3: ACTS tracking geometry provider
Adds the Acts::TrackingGeometry that an ACTS-based reconstruction needs, built from the TGeo geometry O2 already has live in memory. ACTS stays an optional dependency: without it these packages produce no targets. O2::ACTSInterface (Detectors/ACTS/interface), no DPL dependency: - TrackingGeometryManager, a lazily-built process-wide provider following the o2::base::Propagator::Instance() / GeometryTGeo::Instance() pattern. It fetches nothing itself; callers make gGeoManager available first, normally by declaring GRPGeomRequest::Aligned. - ITrackingGeometryBuilder, the detector-specific seam, plus the element store the geometry does not own but depends on. - SurfaceIndexMap, the O2 sensor <-> ACTS surface lookup. - MagneticFieldAdapter, an Acts::MagneticFieldProvider over the O2 field. O2::ACTSWorkflow (Detectors/ACTS/workflow): ActsGeometryService, the same provider reachable through the DPL ServiceRegistry with ServiceKind::DeviceGlobal. O2::ALICE3ACTS (Detectors/Upgrades/ALICE3/ACTS): the Gen3 Blueprint builder, ported from actsO2 ActsAlgorithms/Geometry/src/ALICE3Gen3Geometry.cpp. The construction logic is unchanged so the geometry identifiers stay compatible with the standalone chain's material maps and digitisation/seeding configs; verified by decorating an O2-built geometry with actsO2's own material map. Differences from the original: it reads a TGeoManager handed in by the caller instead of importing a ROOT file, and its process-lifetime static stores became member state so several builders can coexist in one process. Sensors are identified by the TGeo node the surface was built from, not by position. A transform is not an identity: the vertex-detector petals are tube segments, so the three layers of a petal share both origin and rotation and differ only in radius, which the O2 matrix cache does not carry. Position-only matching is kept as a fallback and reports such cases as ambiguous instead of assigning them arbitrarily. macros/run_test.sh generates an ALICE 3 geometry with o2-sim and checks every TRK chip against its ACTS surface; config/gen3_geometry_config.json is the matching geometry description. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent d80c5cb commit afad806

25 files changed

Lines changed: 3207 additions & 0 deletions

‎Detectors/ACTS/CMakeLists.txt‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
# All rights not expressly granted are reserved.
4+
#
5+
# This software is distributed under the terms of the GNU General Public
6+
# License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
#
8+
# In applying this license CERN does not waive the privileges and immunities
9+
# granted to it by virtue of its status as an Intergovernmental Organization
10+
# or submit itself to any jurisdiction.
11+
12+
# The whole package is optional: ACTS is an optional O2 dependency
13+
# (see dependencies/O2Dependencies.cmake) and standard builds do not ship it.
14+
if(NOT Acts_FOUND)
15+
message(STATUS "ACTS not found, skipping the O2 ACTS interface")
16+
return()
17+
endif()
18+
19+
add_subdirectory(interface)
20+
add_subdirectory(workflow)

‎Detectors/ACTS/README.md‎

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
# ACTS tracking geometry in O2
2+
3+
Provides the shared, immutable `Acts::TrackingGeometry` that ACTS-based
4+
reconstruction needs, built from the TGeo geometry O2 already has live in memory.
5+
6+
ACTS is an **optional** O2 dependency (`dependencies/O2Dependencies.cmake`). Without
7+
it this package produces no targets and `O2_WITH_ACTS` is not defined, so anything
8+
using it must be guarded.
9+
10+
## Libraries
11+
12+
| Target | Contents |
13+
| --- | --- |
14+
| `O2::ACTSInterface` | `TrackingGeometryManager` (the provider), `ITrackingGeometryBuilder` (the detector-specific seam), `SurfaceIndexMap` (sensor ↔ surface lookup), `MagneticFieldAdapter` (`Acts::MagneticFieldProvider` over the O2 field). No DPL dependency. |
15+
| `O2::ACTSWorkflow` | `ActsGeometryService`, the same provider reachable through the DPL `ServiceRegistry`. |
16+
| `O2::ALICE3ACTS` | `Gen3BlueprintBuilder`, the ALICE 3 builder (under `Detectors/Upgrades/ALICE3/ACTS`). |
17+
18+
## Using it from a DPL task
19+
20+
The manager fetches nothing itself: it needs `gGeoManager` to be live, which is what
21+
`GRPGeomRequest::Aligned` arranges. Note that a task that only used the material LUT
22+
before will have been passing `GRPGeomRequest::None` and has to be switched.
23+
24+
```cpp
25+
// spec factory
26+
auto ggRequest = std::make_shared<o2::base::GRPGeomRequest>(
27+
false, false, false, true /*GRPMagField*/, false,
28+
o2::base::GRPGeomRequest::Aligned, inputs, true);
29+
30+
void init(InitContext& ic)
31+
{
32+
o2::base::GRPGeomHelper::instance().setRequest(mCCDBReq);
33+
34+
o2::alice3::Gen3BlueprintBuilder::Config cfg;
35+
cfg.geometryConfigFile = ic.options().get<std::string>("acts-geometry-config");
36+
o2::acts::TrackingGeometryManager::instance().setBuilder(
37+
std::make_unique<o2::alice3::Gen3BlueprintBuilder>(cfg));
38+
}
39+
40+
void run(ProcessingContext& pc)
41+
{
42+
o2::base::GRPGeomHelper::instance().checkUpdates(pc); // gGeoManager now live
43+
const auto& tg = o2::acts::TrackingGeometryManager::instance().get(); // built once
44+
}
45+
```
46+
47+
To reach it through the registry instead, put `o2::acts::defaultServicesWithActsGeometry()`
48+
into the `DataProcessorSpec`'s `requiredServices` and use
49+
`pc.services().get<o2::acts::ActsGeometryService>()`. Both paths hand out the same object.
50+
51+
## Mapping O2 clusters onto ACTS surfaces
52+
53+
`makeSurfaceIndex()` (or `TrackingGeometryManager::getIndex(cache, tol, pathProvider)`,
54+
which caches per detector) maps every sensor of a `DetMatrixCache`-derived geometry helper
55+
onto a sensitive ACTS surface.
56+
57+
**Pass a `SensorPathProvider` whenever the detector has one.** With it, sensors are
58+
identified by the TGeo node the surface was built from — an identity, so it cannot
59+
mis-assign:
60+
61+
```cpp
62+
auto* trkGeo = o2::trk::GeometryTGeo::Instance();
63+
trkGeo->fillMatrixCache(o2::math_utils::bit2Mask(o2::math_utils::TransformType::L2G));
64+
const auto& index = mgr.getIndex(*trkGeo, 1e-3,
65+
[trkGeo](int chipID) { return std::string(trkGeo->getMatrixPath(chipID).Data()); });
66+
```
67+
68+
Without it the fallback matches on the sensor's local-to-global transform, which is exact
69+
where a transform identifies a sensor but not always: the ALICE 3 vertex-detector petals
70+
are tube segments, so the three layers of a petal share both origin and rotation and
71+
differ only in radius, which the matrix cache does not carry. Those are reported as
72+
ambiguous rather than assigned arbitrarily, and `makeSurfaceIndex()` throws. One surface
73+
per sensor is enforced in both modes.
74+
75+
## The Gen3 geometry description
76+
77+
`Detectors/Upgrades/ALICE3/ACTS/config/gen3_geometry_config.json` carries everything
78+
geometry-specific: sensor name globs, region boundaries, per-subsystem clustering
79+
tolerances, passive material structures and the pinned volume IDs. All lengths in mm.
80+
81+
It is tuned for the layout `run_test.sh` generates — verified against an o2-sim geometry of
82+
that layout: the sensor globs cover every sensitive volume family (and correctly exclude
83+
`FT3Sensor_Inactive_*`), `*EOSCard*` matches the 504 end-of-stave volumes, the passive
84+
cylinder radii and half-lengths match the real support volumes, and the resulting
85+
volume/layer/surface table is identical to the one actsO2's reference geometry produces.
86+
87+
One known imprecision, inherited and present for both geometries: `TRK_MID_CarbonSupport`
88+
is declared with `halfZ = 1420` while the real `TRK_MID_CARBONSUPPORT` volume has
89+
`dz = 1410 mm`. It only affects the z-extent of that passive layer's material receiver.
90+
91+
## Material
92+
93+
Gen3 construction takes no `IMaterialDecorator`, so an ACTS JSON material map is applied
94+
afterwards via `setMaterialMapFile()`. Material maps are keyed on
95+
`Acts::GeometryIdentifier`, so a structural change to a builder invalidates existing
96+
maps; the manager raises rather than silently producing a material-free geometry.
97+
98+
## Checking a geometry
99+
100+
`Detectors/Upgrades/ALICE3/ACTS/macros/run_test.sh` does the whole thing: it runs
101+
`o2-sim-serial-run5` with the ALICE 3 layout the ACTS chain is developed against, then
102+
builds the tracking geometry from the result and checks every TRK chip against its ACTS
103+
surface. Run it from a scratch directory — `o2-sim` writes into `$PWD`:
104+
105+
```bash
106+
mkdir -p /tmp/acts && cd /tmp/acts
107+
bash <O2-source>/Detectors/Upgrades/ALICE3/ACTS/macros/run_test.sh
108+
```
109+
110+
The geometry description it needs is shipped in this package
111+
(`Detectors/Upgrades/ALICE3/ACTS/config/gen3_geometry_config.json`, installed to
112+
`$O2_ROOT/share/Detectors/Upgrades/ALICE3/ACTS/config/`) and matches the layout the script
113+
generates. Set `GEN3_CONFIG` to point at a different geometry's config.
114+
115+
Knobs: `nEvents`, `generator`, `modules`, `GEN3_CONFIG`, `SKIP_ALIGNMENT`. The last one
116+
defaults to 1 because `o2-sim` finishes by fetching alignment from CCDB, which needs a
117+
valid alien token and aborts without one — after the geometry file has already been
118+
written. The ACTS geometry is built from the ideal geometry, so skipping it costs nothing.
119+
120+
The macro `CheckActsTrackingGeometry.C` can also be run on its own against an existing
121+
`o2sim_geometry.root`; it prints the volume/layer/surface table, which is the authority for
122+
the `(volume, layer)` keys that digitisation and seeding configurations use. It needs ACTS,
123+
Eigen and `$O2_ROOT/include` on `ROOT_INCLUDE_PATH` — `run_test.sh` sets that up.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
# See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
# All rights not expressly granted are reserved.
4+
#
5+
# This software is distributed under the terms of the GNU General Public
6+
# License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
#
8+
# In applying this license CERN does not waive the privileges and immunities
9+
# granted to it by virtue of its status as an Intergovernmental Organization
10+
# or submit itself to any jurisdiction.
11+
12+
o2_add_library(ACTSInterface
13+
TARGETVARNAME targetName
14+
SOURCES src/TrackingGeometryManager.cxx
15+
src/SurfaceIndexMap.cxx
16+
src/MagneticFieldAdapter.cxx
17+
PUBLIC_LINK_LIBRARIES O2::DetectorsBase
18+
O2::DetectorsCommonDataFormats
19+
O2::Field
20+
O2::Framework
21+
ROOT::Geom
22+
Acts::Core
23+
Acts::PluginRoot
24+
Acts::PluginJson)
25+
26+
target_compile_definitions(${targetName} PUBLIC O2_WITH_ACTS)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
///
13+
/// \file ITrackingGeometryBuilder.h
14+
/// \brief Detector-agnostic interface for building an Acts::TrackingGeometry from TGeo
15+
/// \author Paolo Butti
16+
///
17+
18+
#ifndef ALICEO2_ACTS_ITRACKINGGEOMETRYBUILDER_H
19+
#define ALICEO2_ACTS_ITRACKINGGEOMETRYBUILDER_H
20+
21+
#include <memory>
22+
#include <vector>
23+
24+
#include "Acts/Geometry/GeometryContext.hpp"
25+
#include "Acts/Geometry/TrackingGeometry.hpp"
26+
#include "Acts/Surfaces/SurfacePlacementBase.hpp"
27+
28+
#include "ACTSInterface/SurfaceIndexMap.h"
29+
30+
class TGeoManager;
31+
32+
namespace o2::acts
33+
{
34+
35+
/// Everything a builder produces, kept together because the parts have a
36+
/// lifetime dependency on each other.
37+
struct TrackingGeometryOutput {
38+
/// Non-const so the manager can still decorate it with material before
39+
/// handing it out; it is published as shared_ptr<const> afterwards.
40+
std::shared_ptr<Acts::TrackingGeometry> geometry;
41+
42+
/// Detector elements backing the sensitive surfaces.
43+
///
44+
/// Acts::TrackingGeometry does NOT own these: Acts::Surface keeps a raw,
45+
/// non-owning back-pointer to its placement (Surface::surfacePlacement()).
46+
/// Destroying this store while \a geometry is alive is a dangling-pointer bug,
47+
/// so it must be kept for at least as long as the geometry.
48+
std::vector<std::shared_ptr<const Acts::SurfacePlacementBase>> elementStore;
49+
50+
/// Sensor <-> surface lookup. May be empty if the builder cannot provide one.
51+
SurfaceIndexMap index;
52+
};
53+
54+
/// Builds an Acts::TrackingGeometry from a TGeo tree.
55+
///
56+
/// Implementations are detector specific and live with their detector (see
57+
/// o2::alice3::Gen3BlueprintBuilder). They are injected into
58+
/// TrackingGeometryManager at runtime, so this library never depends on any
59+
/// concrete detector.
60+
class ITrackingGeometryBuilder
61+
{
62+
public:
63+
virtual ~ITrackingGeometryBuilder() = default;
64+
65+
/// Build from an already-loaded TGeo tree.
66+
///
67+
/// \param tgeo the geometry manager to read; implementations must not import
68+
/// or otherwise replace it, since it is normally O2's live
69+
/// gGeoManager shared with the rest of the workflow
70+
/// \param gctx nominal context used while placing surfaces
71+
virtual TrackingGeometryOutput build(TGeoManager& tgeo, const Acts::GeometryContext& gctx) = 0;
72+
73+
/// Short name, used in log messages.
74+
virtual const char* getName() const = 0;
75+
};
76+
77+
} // namespace o2::acts
78+
79+
#endif // ALICEO2_ACTS_ITRACKINGGEOMETRYBUILDER_H
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright 2019-2026 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
///
13+
/// \file MagneticFieldAdapter.h
14+
/// \brief Acts::MagneticFieldProvider backed by the O2 field
15+
/// \author Paolo Butti
16+
///
17+
18+
#ifndef ALICEO2_ACTS_MAGNETICFIELDADAPTER_H
19+
#define ALICEO2_ACTS_MAGNETICFIELDADAPTER_H
20+
21+
#include "Acts/MagneticField/MagneticFieldProvider.hpp"
22+
23+
namespace o2::field
24+
{
25+
class MagneticField;
26+
}
27+
28+
namespace o2::acts
29+
{
30+
31+
/// Exposes the O2 magnetic field to ACTS.
32+
///
33+
/// Holds no field of its own: it reads the process-global field that
34+
/// o2::base::Propagator::initFieldFromGRP() installs into
35+
/// TGeoGlobalMagField::Instance() from the GRPMagField CCDB object. The
36+
/// underlying object stays valid across field rescalings -- GRPGeomHelper
37+
/// rescales the existing MagneticField in place rather than replacing it -- so
38+
/// an adapter built once keeps returning up-to-date values.
39+
///
40+
/// Units: O2 works in kGauss and cm, ACTS in its native tesla/mm system, and the
41+
/// conversion is applied here.
42+
class MagneticFieldAdapter : public Acts::MagneticFieldProvider
43+
{
44+
public:
45+
/// Use the field currently installed in TGeoGlobalMagField.
46+
/// \throw std::runtime_error if no field has been initialised
47+
MagneticFieldAdapter();
48+
49+
/// Use an explicitly provided field.
50+
explicit MagneticFieldAdapter(o2::field::MagneticField* field);
51+
52+
Cache makeCache(const Acts::MagneticFieldContext& mctx) const final;
53+
54+
Acts::Result<Acts::Vector3> getField(const Acts::Vector3& position, Cache& cache) const final;
55+
56+
const o2::field::MagneticField* getO2Field() const { return mField; }
57+
58+
private:
59+
struct CacheImpl {
60+
};
61+
62+
o2::field::MagneticField* mField = nullptr;
63+
};
64+
65+
} // namespace o2::acts
66+
67+
#endif // ALICEO2_ACTS_MAGNETICFIELDADAPTER_H

0 commit comments

Comments
 (0)