Project
Loading...
Searching...
No Matches
TrkTGeoBlueprintBuilder.cxx
Go to the documentation of this file.
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
16
18
19#include "Acts/Definitions/Units.hpp"
20#include "Acts/Geometry/Blueprint.hpp"
21#include "Acts/Geometry/BlueprintOptions.hpp"
22#include "Acts/Geometry/ContainerBlueprintNode.hpp"
23#include "Acts/Geometry/CylinderVolumeBounds.hpp"
24#include "Acts/Geometry/GeometryIdentifierBlueprintNode.hpp"
25#include "Acts/Geometry/LayerBlueprintNode.hpp"
26#include "Acts/Geometry/TrackingVolume.hpp"
27#include "Acts/Geometry/VolumeAttachmentStrategy.hpp"
28#include "Acts/Geometry/VolumeResizeStrategy.hpp"
29#include "Acts/Surfaces/CylinderBounds.hpp"
30#include "Acts/Surfaces/Surface.hpp"
31#include "ActsPlugins/Root/TGeoAxes.hpp"
32#include "ActsPlugins/Root/TGeoDetectorElement.hpp"
33#include "ActsPlugins/Root/TGeoLayerBuilder.hpp"
34#include "ActsPlugins/Root/TGeoParser.hpp"
35
36#include <algorithm>
37#include <cmath>
38#include <fstream>
39#include <memory>
40#include <string>
41#include <ranges>
42#include <stdexcept>
43#include <string_view>
44#include <utility>
45#include <vector>
46
47#include "TGeoManager.h"
48#include "TGeoMatrix.h"
49#include "TGeoNode.h"
50#include "TGeoVolume.h"
51
52namespace o2::alice3
53{
54using namespace Acts;
55using namespace Acts::Experimental;
56using namespace Acts::UnitLiterals;
57
58namespace
59{
60constexpr double kTGeoToActs = 10.; // cm -> mm
61constexpr double kVertexLayerTolerance = 5. * 1_mm;
62constexpr double kBarrelLayerTolerance = 15. * 1_mm;
63constexpr double kLayerEnvelopeWidth = 2. * 1_mm;
64
65using ElementPtr = std::shared_ptr<ActsPlugins::TGeoDetectorElement>;
66using KeyedSurface = std::pair<double, std::shared_ptr<Surface>>;
67
68struct LayerGroup {
69 double radius = 0.;
70 std::vector<std::shared_ptr<Surface>> surfaces;
71};
72
73std::vector<LayerGroup> clusterLayers(std::vector<KeyedSurface> elements, double tolerance)
74{
75 std::vector<LayerGroup> layers;
76 if (elements.empty()) {
77 return layers;
78 }
79
80 std::ranges::sort(elements, {}, &KeyedSurface::first);
81 LayerGroup group;
82 double previous = elements.front().first;
83 double sum = 0.;
84
85 for (const auto& [radius, surface] : elements) {
86 if (!group.surfaces.empty() && radius - previous > tolerance) {
87 group.radius = sum / group.surfaces.size();
88 layers.push_back(std::move(group));
89 group = LayerGroup{};
90 sum = 0.;
91 }
92 group.surfaces.push_back(surface);
93 sum += radius;
94 previous = radius;
95 }
96 group.radius = sum / group.surfaces.size();
97 layers.push_back(std::move(group));
98 return layers;
99}
100
101} // namespace
102
104
106
108 const Acts::GeometryContext& gctx)
109{
110 ACTS_LOCAL_LOGGER(getDefaultLogger("ALICE3TRKAuto", mConfig.logLevel));
111 ACTS_INFO("Building ACTS TRK geometry from the live TGeo '" << tgeo.GetName() << "'");
112
113 auto* topVolume = tgeo.GetTopVolume();
114 if (!topVolume) {
115 throw std::runtime_error("ALICE3 TRK: no top volume in the loaded TGeo geometry");
116 }
117
118 // TGeoParser selects the placed TRK sensitive nodes, including the VD
119 // PETALCASE surfaces. Avoid parsing every support/passive simulation volume.
120 ActsPlugins::TGeoParser::Options options;
121 options.volumeNames = {topVolume->GetName()};
122 options.targetNames = {"*TRKSensor*"};
123 options.unit = kTGeoToActs;
124
125 ActsPlugins::TGeoParser::State state;
126 state.volume = topVolume;
127 state.onBranch = true;
128 TGeoHMatrix transform = TGeoIdentity(topVolume->GetName());
129 ActsPlugins::TGeoParser::select(state, options, transform);
130
131 if (state.selectedNodes.empty()) {
132 throw std::runtime_error("ALICE3 TRK: no TRK sensitive TGeo nodes found (*TRKSensor*)");
133 }
134
135 const auto thinY = ActsPlugins::TGeoAxes::parse("ZXY");
136 const auto thinZ = ActsPlugins::TGeoAxes::parse("XYZ");
137 std::vector<ElementPtr> elements;
138 std::vector<KeyedSurface> vertexSurfaces;
139 std::vector<KeyedSurface> barrelSurfaces;
140 elements.reserve(state.selectedNodes.size());
141
142 for (const auto& snode : state.selectedNodes) {
143 const std::string_view name = snode.node->GetVolume()->GetName();
144 const bool vertex = name.find("PETALCASE") != std::string_view::npos;
145 const auto axes = vertex ? thinZ : thinY;
146
147 auto element = ActsPlugins::TGeoLayerBuilder::defaultElementFactory(
148 ActsPlugins::TGeoDetectorElement::Identifier{}, *snode.node,
149 *snode.transform, axes, kTGeoToActs, nullptr);
150 auto surface = element->surface().getSharedPtr();
151
152 if (vertex && surface->type() != Surface::SurfaceType::Cylinder) {
153 throw std::runtime_error("ALICE3 TRK: expected a cylindrical vertex-detector sensor: " + std::string(name));
154 }
155 // TRK is a barrel detector. A misidentified forward surface must not
156 // silently enter a cylindrical layer and break ACTS navigation.
157 const auto center = surface->center(gctx);
158 const double radius = (surface->type() == Surface::SurfaceType::Cylinder)
159 ? surface->bounds().values()[CylinderBounds::eR]
160 : std::hypot(center.x(), center.y());
161 if (!vertex) {
162 const auto normal = surface->normal(gctx, center, Vector3::UnitZ());
163 if (std::abs(normal.z()) > 0.7) {
164 throw std::runtime_error("ALICE3 TRK: a non-barrel surface matched *TRKSensor*: " + std::string(name));
165 }
166 }
167
168 (vertex ? vertexSurfaces : barrelSurfaces).emplace_back(radius, std::move(surface));
169 elements.push_back(std::move(element));
170 }
171
172 auto layers = clusterLayers(std::move(vertexSurfaces), kVertexLayerTolerance);
173 auto barrelLayers = clusterLayers(std::move(barrelSurfaces), kBarrelLayerTolerance);
174 for (auto& layer : barrelLayers) {
175 layers.push_back(std::move(layer));
176 }
177 std::ranges::sort(layers, {}, &LayerGroup::radius);
178
179 if (layers.empty()) {
180 throw std::runtime_error("ALICE3 TRK: no barrel layers found in the loaded TGeo");
181 }
182 ACTS_INFO("Found " << elements.size() << " sensors in " << layers.size() << " TRK layers");
183 for (std::size_t i = 0; i < layers.size(); ++i) {
184 ACTS_INFO(" TRK layer " << i << ": R=" << layers[i].radius / 1_mm
185 << " mm, sensors=" << layers[i].surfaces.size());
186 }
187
188 Blueprint::Config config;
189 // Enlarge the inner world radius to include the beamline (r = 0).
190 const double innerEnvelope = layers.front().radius + 20. * 1_mm;
191 config.envelope = ExtentEnvelope{{.z = {20. * 1_mm, 20. * 1_mm},
192 .r = {innerEnvelope, 20. * 1_mm}}};
193 Blueprint root{config};
194 auto& ids = root.withGeometryIdentifier();
195 const auto byRadius = [](const TrackingVolume& a, const TrackingVolume& b) {
196 const auto midRadius = [](const TrackingVolume& volume) {
197 const auto& bounds = dynamic_cast<const CylinderVolumeBounds&>(volume.volumeBounds());
198 return 0.5 * (bounds.get(CylinderVolumeBounds::eMinR) + bounds.get(CylinderVolumeBounds::eMaxR));
199 };
200 return midRadius(a) < midRadius(b);
201 };
202 // One self-contained TRK region. IDs are deterministic within this builder,
203 // but they are NOT compatible with the full-detector geometry/material map.
204 ids.setAllVolumeIdsTo(40).incrementLayerIds(1).sortBy(byRadius);
205 auto& barrel = ids.addCylinderContainer("TRKBarrel", AxisDirection::AxisR);
206 barrel.setAttachmentStrategy(VolumeAttachmentStrategy::Gap);
207 barrel.setResizeStrategies(VolumeResizeStrategy::Gap, VolumeResizeStrategy::Gap);
208
209 const ExtentEnvelope layerEnvelope = ExtentEnvelope{{.z = {kLayerEnvelopeWidth, kLayerEnvelopeWidth},
210 .r = {kLayerEnvelopeWidth, kLayerEnvelopeWidth}}};
211 for (std::size_t i = 0; i < layers.size(); ++i) {
212 barrel.addLayer("TRKBarrel_L" + std::to_string(i), [&layers, &layerEnvelope, i](LayerBlueprintNode& node) {
213 node.setSurfaces(layers[i].surfaces);
214 node.setLayerType(LayerBlueprintNode::LayerType::Cylinder);
215 node.setEnvelope(layerEnvelope);
216 node.setUseCenterOfGravity(false, false, true);
217 });
218 }
219
220 if (!mConfig.graphvizFile.empty()) {
221 std::ofstream out(mConfig.graphvizFile);
222 if (!out) {
223 throw std::runtime_error("ALICE3 TRK: cannot write Graphviz file: " + mConfig.graphvizFile);
224 }
225 root.graphviz(out);
226 }
227
228 BlueprintOptions opts;
229 auto trackingGeometry = root.construct(opts, gctx, logger());
230
232 output.elementStore.reserve(elements.size());
233 for (const auto& element : elements) {
234 output.elementStore.push_back(element);
235 }
236 output.geometry = std::shared_ptr<Acts::TrackingGeometry>(std::move(trackingGeometry));
237 ACTS_INFO("ACTS TRK geometry constructed from the loaded TGeo");
238 return output;
239}
240
241} // namespace o2::alice3
atype::type element
std::unique_ptr< expressions::Node > node
uint64_t vertex
Definition RawEventData.h:9
std::vector< double > sum
int32_t i
SurfaceTrackState state
float center
void output(const std::map< std::string, ChannelStat > &channels)
Definition rawdump.cxx:197
const Acts::Surface * surface
std::vector< std::shared_ptr< Surface > > surfaces
double radius
Build an ACTS TRK tracking geometry from the TGeo geometry already loaded by O2.
o2::acts::TrackingGeometryOutput build(TGeoManager &tgeo, const Acts::GeometryContext &gctx) override
GLuint * ids
Definition glcorearb.h:647
GLuint const GLchar * name
Definition glcorearb.h:781
GLboolean GLboolean GLboolean b
Definition glcorearb.h:1233
GLboolean GLuint group
Definition glcorearb.h:3991
GLenum GLuint GLint GLint layer
Definition glcorearb.h:1310
GLboolean GLboolean GLboolean GLboolean a
Definition glcorearb.h:1233
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52
std::vector< std::shared_ptr< const Acts::SurfacePlacementBase > > elementStore