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
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