Project
Loading...
Searching...
No Matches
SurfaceIndexMap.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 <algorithm>
20#include <cmath>
21#include <cstdint>
22#include <sstream>
23#include <stdexcept>
24
25#include "Acts/Definitions/Units.hpp"
26#include "Acts/Geometry/GeometryContext.hpp"
27#include "Acts/Geometry/TrackingGeometry.hpp"
28#include "Acts/Surfaces/BoundaryTolerance.hpp"
29#include "Acts/Surfaces/Surface.hpp"
30#include "Acts/Surfaces/SurfacePlacementBase.hpp"
31#include "ActsPlugins/Root/TGeoDetectorElement.hpp"
32
33#include <TGeoManager.h>
34#include <TGeoMatrix.h>
35#include <TGeoNode.h>
36
38#include "Framework/Logger.h"
39
40namespace o2::acts
41{
42
43namespace
44{
45const std::vector<int> gNoSensors{};
46} // namespace
47
48void SurfaceIndexMap::add(int sensorID, const Acts::Surface& surface)
49{
50 const auto geoId = surface.geometryId();
51 auto [it, inserted] = mBySensor.try_emplace(sensorID, Entry{&surface, geoId});
52 if (!inserted) {
53 throw std::runtime_error("o2::acts::SurfaceIndexMap: sensor " + std::to_string(sensorID) +
54 " is already mapped");
55 }
56 mByGeoId[geoId].push_back(sensorID);
57}
58
59const Acts::Surface* SurfaceIndexMap::getSurface(int sensorID) const
60{
61 const auto it = mBySensor.find(sensorID);
62 return it == mBySensor.end() ? nullptr : it->second.surface;
63}
64
65Acts::GeometryIdentifier SurfaceIndexMap::getGeometryId(int sensorID) const
66{
67 const auto it = mBySensor.find(sensorID);
68 return it == mBySensor.end() ? Acts::GeometryIdentifier{} : it->second.geoId;
69}
70
71const std::vector<int>& SurfaceIndexMap::getSensorIDs(Acts::GeometryIdentifier geoId) const
72{
73 const auto it = mByGeoId.find(geoId);
74 return it == mByGeoId.end() ? gNoSensors : it->second;
75}
76
78{
79 mBySensor.clear();
80 mByGeoId.clear();
81}
82
83namespace
84{
85
88class CentreLookup
89{
90 public:
91 explicit CentreLookup(double cellSize) : mCellSize(cellSize) {}
92
93 void add(const Acts::Surface* surface, const Acts::Vector3& position)
94 {
95 mEntries.push_back({surface, position});
96 mCells[cellOf(position)].push_back(mEntries.size() - 1);
97 }
98
100 const Acts::Surface* findNearest(const Acts::Vector3& position, double tolerance) const
101 {
102 const auto cell = cellOf(position);
103 const Acts::Surface* best = nullptr;
104 double bestDist2 = tolerance * tolerance;
105 for (std::int64_t dx = -1; dx <= 1; ++dx) {
106 for (std::int64_t dy = -1; dy <= 1; ++dy) {
107 for (std::int64_t dz = -1; dz <= 1; ++dz) {
108 const auto it = mCells.find(Cell{cell.x + dx, cell.y + dy, cell.z + dz});
109 if (it == mCells.end()) {
110 continue;
111 }
112 for (const auto idx : it->second) {
113 const double dist2 = (mEntries[idx].position - position).squaredNorm();
114 if (dist2 <= bestDist2) {
115 bestDist2 = dist2;
116 best = mEntries[idx].surface;
117 }
118 }
119 }
120 }
121 }
122 return best;
123 }
124
125 std::size_t size() const { return mEntries.size(); }
126
127 private:
128 struct Entry {
129 const Acts::Surface* surface = nullptr;
130 Acts::Vector3 position;
131 };
132 struct Cell {
133 std::int64_t x = 0, y = 0, z = 0;
134 bool operator==(const Cell&) const = default;
135 };
136 struct CellHash {
137 std::size_t operator()(const Cell& c) const noexcept
138 {
139 std::size_t h = static_cast<std::size_t>(c.x) * 0x9e3779b97f4a7c15ULL;
140 h ^= static_cast<std::size_t>(c.y) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
141 h ^= static_cast<std::size_t>(c.z) + 0x9e3779b97f4a7c15ULL + (h << 6) + (h >> 2);
142 return h;
143 }
144 };
145
146 Cell cellOf(const Acts::Vector3& p) const
147 {
148 return Cell{static_cast<std::int64_t>(std::floor(p[0] / mCellSize)),
149 static_cast<std::int64_t>(std::floor(p[1] / mCellSize)),
150 static_cast<std::int64_t>(std::floor(p[2] / mCellSize))};
151 }
152
153 double mCellSize = 1.;
154 std::vector<Entry> mEntries;
155 std::unordered_map<Cell, std::vector<std::size_t>, CellHash> mCells;
156};
157
158} // namespace
159
160namespace
161{
162
164struct SensitiveSurfaces {
165 CentreLookup byCentre;
166 std::vector<const Acts::Surface*> layerSurfaces;
167 std::unordered_map<const TGeoNode*, std::vector<const Acts::Surface*>> byNode;
168 std::size_t nWithoutNode = 0;
169
170 explicit SensitiveSurfaces(double cellSize) : byCentre(cellSize) {}
171};
172
173SensitiveSurfaces collectSensitive(const Acts::TrackingGeometry& geometry,
174 const Acts::GeometryContext& gctx, double cellSize)
175{
176 SensitiveSurfaces out(cellSize);
177 geometry.apply([&](const Acts::Surface& surface) {
178 const auto* placement = surface.surfacePlacement();
179 if (placement == nullptr || !placement->isSensitive()) {
180 return;
181 }
182 const auto* tgeoElement = dynamic_cast<const ActsPlugins::TGeoDetectorElement*>(placement);
183 if (tgeoElement != nullptr) {
184 out.byNode[&tgeoElement->tgeoNode()].push_back(&surface);
185 } else {
186 ++out.nWithoutNode;
187 }
188 const auto type = surface.type();
189 if (type == Acts::Surface::SurfaceType::Cylinder || type == Acts::Surface::SurfaceType::Disc) {
190 out.layerSurfaces.push_back(&surface);
191 } else {
192 out.byCentre.add(&surface, surface.center(gctx));
193 }
194 });
195 return out;
196}
197
199struct SensorPlacement {
200 Acts::Vector3 position;
201 Acts::RotationMatrix3 rotation;
202};
203
204SensorPlacement sensorPlacementOf(const o2::detectors::DetMatrixCache& cache, int sensorID)
205{
206 double rot[9] = {0.};
207 double tra[3] = {0.};
208 cache.getMatrixL2G(sensorID).GetComponents(rot[0], rot[1], rot[2], tra[0],
209 rot[3], rot[4], rot[5], tra[1],
210 rot[6], rot[7], rot[8], tra[2]);
211 SensorPlacement out;
212 out.position = Acts::Vector3{tra[0] * Acts::UnitConstants::cm, tra[1] * Acts::UnitConstants::cm,
213 tra[2] * Acts::UnitConstants::cm};
214 out.rotation << rot[0], rot[1], rot[2], rot[3], rot[4], rot[5], rot[6], rot[7], rot[8];
215 return out;
216}
217
222const Acts::Surface* matchByNode(const SensitiveSurfaces& surfaces, const std::string& path,
223 const Acts::GeometryContext& gctx, const SensorPlacement& placement,
224 double tolerance, bool& ambiguous)
225{
226 ambiguous = false;
227 if (gGeoManager == nullptr) {
228 throw std::runtime_error("o2::acts::makeSurfaceIndex: no TGeo geometry loaded");
229 }
230 gGeoManager->PushPath();
231 const bool ok = gGeoManager->cd(path.c_str());
232 const TGeoNode* node = ok ? gGeoManager->GetCurrentNode() : nullptr;
233 gGeoManager->PopPath();
234 if (node == nullptr) {
235 return nullptr;
236 }
237
238 const auto it = surfaces.byNode.find(node);
239 if (it == surfaces.byNode.end()) {
240 return nullptr;
241 }
242 if (it->second.size() == 1) {
243 return it->second.front();
244 }
245 const Acts::Surface* best = nullptr;
246 for (const auto* surface : it->second) {
247 if ((surface->localToGlobalTransform(gctx).translation() - placement.position).norm() <= tolerance) {
248 if (best != nullptr) {
249 ambiguous = true;
250 return nullptr;
251 }
252 best = surface;
253 }
254 }
255 return best;
256}
257
260const Acts::Surface* matchByPlacement(const SensitiveSurfaces& surfaces,
261 const Acts::GeometryContext& gctx,
262 const SensorPlacement& placement, double tolerance,
263 bool& ambiguous)
264{
265 ambiguous = false;
266
267 // Rotations are dimensionless; a generous bound on the TGeoHMatrix -> Eigen round trip.
268 constexpr double kRotationTolerance = 1e-9;
269
270 if (const auto* surface = surfaces.byCentre.findNearest(placement.position, tolerance);
271 surface != nullptr) {
272 return surface;
273 }
274
275 // A sensor modelled as a whole cylinder or disc: its origin is generally not a
276 // useful key (an ALICE 3 vertex-detector petal is a tube segment, so its origin
277 // is on the beam axis), so the full transform is compared instead. Every match
278 // is collected rather than taking the first: the three layers of one petal share
279 // both origin and rotation and differ only in radius, which the matrix cache does
280 // not carry, so those are genuinely unresolvable this way and must be reported.
281 const Acts::Surface* best = nullptr;
282 for (const auto* surface : surfaces.layerSurfaces) {
283 const auto& transform = surface->localToGlobalTransform(gctx);
284 if ((transform.translation() - placement.position).norm() <= tolerance &&
285 (transform.rotation() - placement.rotation).norm() <= kRotationTolerance) {
286 if (best != nullptr) {
287 ambiguous = true;
288 return nullptr;
289 }
290 best = surface;
291 }
292 }
293 return best;
294}
295
296} // namespace
297
298SurfaceIndexMap makeSurfaceIndex(const Acts::TrackingGeometry& geometry,
299 const Acts::GeometryContext& gctx,
301 double toleranceCm,
302 SensorPathProvider pathProvider)
303{
304 if (!cache.isBuilt()) {
305 throw std::runtime_error("o2::acts::makeSurfaceIndex: the detector matrix cache is not built");
306 }
307 if (cache.getCacheL2G().getSize() == 0) {
308 throw std::runtime_error(
309 "o2::acts::makeSurfaceIndex: the L2G matrix cache is empty. Call fillMatrixCache() with the "
310 "L2G bit set before building the index.");
311 }
312
313 // ACTS works in mm, O2 in cm; Acts::UnitConstants::cm is exactly that factor.
314 const double tolerance = toleranceCm * Acts::UnitConstants::cm;
315
316 const auto surfaces = collectSensitive(geometry, gctx, std::max(tolerance, 1e-6));
317 const int nSensors = cache.getSize();
318
319 LOGP(info,
320 "Matching {} {} sensors against {} sensitive ACTS surfaces ({} per-sensor, {} whole-layer) "
321 "by {} (tolerance {} cm)",
322 nSensors, cache.getDetID().getName(), surfaces.byCentre.size() + surfaces.layerSurfaces.size(),
323 surfaces.byCentre.size(), surfaces.layerSurfaces.size(),
324 pathProvider ? "TGeo node" : "placement", toleranceCm);
325 if (pathProvider && surfaces.nWithoutNode != 0) {
326 LOGP(warn, "{} sensitive surfaces have no TGeo detector element and cannot be matched by node",
327 surfaces.nWithoutNode);
328 }
329
331 std::size_t nUnmatched = 0;
332 std::size_t nAmbiguous = 0;
333 std::unordered_map<Acts::GeometryIdentifier, int> claimedBy;
334 std::ostringstream failures;
335 int nReported = 0;
336
337 for (int sensorID = 0; sensorID < nSensors; ++sensorID) {
338 const auto placement = sensorPlacementOf(cache, sensorID);
339
340 bool ambiguous = false;
341 const Acts::Surface* match =
342 pathProvider
343 ? matchByNode(surfaces, pathProvider(sensorID), gctx, placement, tolerance, ambiguous)
344 : matchByPlacement(surfaces, gctx, placement, tolerance, ambiguous);
345
346 if (match == nullptr) {
347 ambiguous ? ++nAmbiguous : ++nUnmatched;
348 if (nReported < 10) {
349 failures << "\n sensor " << sensorID << " at ("
350 << placement.position[0] / Acts::UnitConstants::cm << ", "
351 << placement.position[1] / Acts::UnitConstants::cm << ", "
352 << placement.position[2] / Acts::UnitConstants::cm << ") cm "
353 << (ambiguous ? "matches several sensitive ACTS surfaces" : "matches no sensitive ACTS surface");
354 ++nReported;
355 }
356 continue;
357 }
358
359 // One surface per sensor. Relaxing this hides exactly the failure the
360 // ambiguity check above is there to catch, so it is enforced.
361 const auto geoId = match->geometryId();
362 const auto [it, inserted] = claimedBy.try_emplace(geoId, sensorID);
363 if (!inserted) {
364 throw std::runtime_error(
365 "o2::acts::makeSurfaceIndex: sensors " + std::to_string(it->second) + " and " +
366 std::to_string(sensorID) + " both resolve to the ACTS surface with geometry id " +
367 std::to_string(geoId.value()) +
368 ". They are indistinguishable by the information available, so the index would be wrong.");
369 }
370 index.add(sensorID, *match);
371 }
372
373 if (nUnmatched != 0 || nAmbiguous != 0) {
374 std::string hint;
375 if (nAmbiguous != 0 && !pathProvider) {
376 hint =
377 " Pass a SensorPathProvider (e.g. GeometryTGeo::getMatrixPath) so sensors are identified by "
378 "their TGeo node instead of their placement.";
379 }
380 throw std::runtime_error("o2::acts::makeSurfaceIndex: of " + std::to_string(nSensors) +
381 " sensors, " + std::to_string(nUnmatched) + " matched no surface and " +
382 std::to_string(nAmbiguous) + " were ambiguous." + hint + failures.str());
383 }
384
385 LOGP(info, "Sensor index built: {} sensors on {} surfaces", index.size(), index.getNSurfaces());
386
387 return index;
388}
389
390} // namespace o2::acts
std::unique_ptr< expressions::Node > node
Timeframe settings Average distance in cm of events merged into Randomize distance around average distance of merged events Simulate correct bunch interactions instead of placing only the average number of events A value[n] sets TFlen for[n] collisions in Number of bunches per trainsort Number of bunch trains Instantaneous interaction rate Do not simulate border Total number of collisions to be placed in the interior of all time Do not select random but walk over array of events in stride steps h Event generator settings h configStandalone Use CPU for GPU Force usage of the specified GPU device no CPU fallback Continue processing after an error First event to Number of events to Number of iterations to Number of iterations to Number of initial iterations excluded from const char e
uint32_t c
Definition RawData.h:2
Acts::Vector3 position
std::size_t nWithoutNode
std::vector< const Acts::Surface * > layerSurfaces
Acts::RotationMatrix3 rotation
CentreLookup byCentre
const Acts::Surface * surface
std::unordered_map< const TGeoNode *, std::vector< const Acts::Surface * > > byNode
Lookup between O2 sensor indices and ACTS surfaces.
std::vector< std::shared_ptr< Surface > > surfaces
Class for time synchronization of RawReader instances.
const Acts::Surface * getSurface(int sensorID) const
const std::vector< int > & getSensorIDs(Acts::GeometryIdentifier geoId) const
void add(int sensorID, const Acts::Surface &surface)
Acts::GeometryIdentifier getGeometryId(int sensorID) const
static constexpr const char * getName(ID id)
names of defined detectors
Definition DetID.h:148
const o2::detectors::DetID & getDetID() const
const Mat3D & getMatrixL2G(int sensID) const
const MatrixCache< Mat3D > & getCacheL2G() const
bool match(const std::vector< std::string > &queries, const char *pattern)
Definition dcs-ccdb.cxx:229
GLint GLenum GLint x
Definition glcorearb.h:403
GLsizeiptr size
Definition glcorearb.h:659
GLuint index
Definition glcorearb.h:781
GLint y
Definition glcorearb.h:270
GLint GLint GLsizei GLint GLenum GLenum type
Definition glcorearb.h:275
GLsizei const GLchar *const * path
Definition glcorearb.h:3591
GLdouble GLdouble GLdouble z
Definition glcorearb.h:843
SurfaceIndexMap makeSurfaceIndex(const Acts::TrackingGeometry &geometry, const Acts::GeometryContext &gctx, const o2::detectors::DetMatrixCache &cache, double toleranceCm=1e-3, SensorPathProvider pathProvider={})
std::function< std::string(int sensorID)> SensorPathProvider
Full TGeo node path of one sensor, e.g. o2::trk::GeometryTGeo::getMatrixPath().
const bool const bool const int FollowDirection BestTrial TrackITSInternal< NLayers > & best
bool operator==(const observer_ptr< W1 > &p1, const observer_ptr< W2 > &p2)
std::string to_string(gsl::span< T, Size > span)
Definition common.h:52