LDMX Software
TrackingUtils.h
1#ifndef TRACKUTILS_H_
2#define TRACKUTILS_H_
3
4// TODO:: MAKE A CXX!!
5
6// Recoil back layers numbering scheme for module_
7
8// +Y /\ 4 3 2 1 0
9// |
10// |
11// -Y \/ 9 8 7 6 5
12// -X <---- ----> +X
13
14// ModN (x_, y_, z_)
15// 0 (96, 40, z2)
16// 1 (48, 40, z1)
17// 2 (0, 40, z2)
18// 3 (-48, 40, z1)
19// 4 (-96, 40, z2)
20
21// 5 (96, -40, z2)
22// 6 (48, -40, z1)
23// 7 (0, -40, z2)
24// 8 (-48, -40, z1)
25// 9 (-96, -40, z2)
26
27// ---< SimCore >---//
29#include "Tracking/Sim/LdmxSpacePoint.h"
30
31// --- Tracking ---//
32#include "Tracking/Event/Track.h"
33
34// --- < ACTS > --- //
35#include "Acts/Definitions/Algebra.hpp"
36#include "Acts/Definitions/PdgParticle.hpp"
37#include "Acts/Definitions/TrackParametrization.hpp"
38#include "Acts/Definitions/Units.hpp"
39#include "Acts/EventData/TrackParameters.hpp"
40#include "Acts/Surfaces/PerigeeSurface.hpp"
41#include "Acts/Surfaces/PlaneSurface.hpp"
42#include "Acts/Surfaces/Surface.hpp"
43#include "Tracking/Event/Measurement.h"
44#include "Tracking/Sim/IndexSourceLink.h"
45
46namespace tracking {
47namespace sim {
48namespace utils {
49
50/*
51 It looks like the recoil is subdetector ID 4 and tagger is subdetector ID 1
52https://github.com/LDMX-Software/ldmx-sw/blob/0476ccc407e068560518e0614aa83b6eda22e186/DetDescr/include/DetDescr/DetectorID.h#L11-L24
53So you could bit shift and mask to get these numbers
54https://github.com/LDMX-Software/ldmx-sw/blob/0476ccc407e068560518e0614aa83b6eda22e186/DetDescr/include/DetDescr/DetectorID.h#L38-L39
55(sim_tracker_hit.getID() >> 26)&0x3f
56or if you are in ldmx-sw, it is easier, more robust, and just as performant to
57wrap the ID in the helper class and use its accessors sd =
58TrackerID(sim_tracker_hit.getID()).subdet(); if (sd ==
59SubdetectorID::SD_TRACKER_RECOIL) {
60 // hit in recoil
61} else if (sd == SubdetectorID::SD_TRACKER_TAGGER) {
62 // hit in tagger
63} else {
64 // this should never happen since the TrackerID constructor checks for
65mal-formed IDs
66}
67*/
68
69// This method returns the sensor ID
70inline int getSensorID(const ldmx::SimTrackerHit& hit) {
71 bool debug = false;
72
73 int vol = 2;
74
75 // TODO!! FIX THIS HARDCODE!
76 if (hit.getPosition()[2] > 0) vol = 3;
77
78 unsigned int sensor_id = 0;
79 unsigned int layer_id = 0;
80
81 // tagger numbering scheme for surfaces mapping
82 // Layers from 1 to 14 => transform to 0->13
83 if (vol == 2) {
84 sensor_id = (hit.getLayerID() + 1) % 2; // 0,1,0,1 ...
85
86 // v12
87 // layerId = (hit.getLayerID() + 1) / 2; //1,2,3,4,5,6,7
88 // v14
89 layer_id = 7 - ((hit.getLayerID() - 1) / 2);
90 }
91
92 // recoil numbering scheme for surfaces mapping
93 if (vol == 3) {
94 // For axial-stereo modules use the same numbering scheme as the tagger
95 if (hit.getLayerID() < 9) {
96 sensor_id = (hit.getLayerID() + 1) % 2;
97 layer_id = (hit.getLayerID() + 1) / 2;
98 }
99
100 // For the axial only modules
101 else {
102 sensor_id = hit.getModuleID();
103 layer_id = (hit.getLayerID() + 2) / 2; // 9->11 /2 = 5 10->12 / 2 = 6
104 }
105 }
106
107 // vol * 1000 + ly * 100 + sensor
108 unsigned int index = vol * 1000 + layer_id * 100 + sensor_id;
109
110 if (debug) {
111 std::cout << "LdmxSpacePointConverter::Check index::" << vol << "--"
112 << layer_id << "--" << sensor_id << "==>" << index << std::endl;
113 std::cout << vol << "===" << hit.getLayerID() << "===" << hit.getModuleID()
114 << std::endl;
115 }
116
117 return index;
118}
119
120// This method converts a SimHit in a LdmxSpacePoint for the Acts seeder.
121// (1) Rotate the coordinates into acts::seedFinder coordinates defined by
122// B-Field along z_ axis [Z_ldmx -> X_acts, X_ldmx->Y_acts, Y_ldmx->Z_acts] (2)
123// Saves the error information. At the moment the errors are fixed. They should
124// be obtained from the digitized hits_.
125
126// TODO::Move to shared pointers?!
127// TODO::Pass to instances?
128// Vol==2 for tagger, Vol==3 for recoil
129
130inline ldmx::LdmxSpacePoint* convertSimHitToLdmxSpacePoint(
131 const ldmx::SimTrackerHit& hit, unsigned int vol = 2, double sigma_u = 0.05,
132 double sigma_v = 1.) {
133 unsigned int index = getSensorID(hit);
134
135 // Rotate position
136 float ldmxsp_x = hit.getPosition()[2];
137 float ldmxsp_y = hit.getPosition()[0];
138 float ldmxsp_z = hit.getPosition()[1];
139
140 return new ldmx::LdmxSpacePoint(ldmxsp_x, ldmxsp_y, ldmxsp_z, hit.getTime(),
141 index, hit.getEdep(), sigma_u * sigma_u,
142 sigma_v * sigma_v, hit.getID());
143}
144
145// BoundSymMatrix doesn't exist in v36 .. use BoundSquareMatrix
146// have to change this everywhere .. I think using BoundSysMatrix was defined
147// exactly the same as BoundSquareMatrix is now in ACTs
148inline void flatCov(Acts::BoundSquareMatrix cov, std::vector<double>& v_cov) {
149 v_cov.clear();
150 v_cov.reserve(cov.rows() * (cov.rows() + 1) / 2);
151 for (int i = 0; i < cov.rows(); i++)
152 for (int j = i; j < cov.cols(); j++) v_cov.push_back(cov(i, j));
153}
154
155inline Acts::BoundSquareMatrix unpackCov(const std::vector<double>& v_cov) {
156 Acts::BoundSquareMatrix cov;
157 int e{0};
158 for (int i = 0; i < cov.rows(); i++)
159 for (int j = i; j < cov.cols(); j++) {
160 cov(i, j) = v_cov.at(e);
161 cov(j, i) = cov(i, j);
162 e++;
163 }
164
165 return cov;
166}
167
168// Rotate LDMX global -> ACTS frame: z_ldmx->x_acts, x_ldmx->y_acts,
169// y_ldmx->z_acts (0 0 1) * (x,y,z)_ldmx = x_acts (1 0 0) * (x,y,z)_ldmx =
170// y_acts (0 1 0) * (x,y,z)_ldmx = z_acts
171inline Acts::SquareMatrix3 ldmx2ActsRotation() {
172 Acts::SquareMatrix3 r;
173 r << 0., 0., 1., 1., 0., 0., 0., 1., 0.;
174 return r;
175}
176
177inline Acts::Vector3 ldmx2Acts(Acts::Vector3 ldmx_v) {
178 return ldmx2ActsRotation() * ldmx_v;
179}
180
181// Rotate ACTS frame -> LDMX global (inverse of ldmx2Acts, i.e. transpose):
182// x_ldmx = y_acts, y_ldmx = z_acts, z_ldmx = x_acts
183inline Acts::SquareMatrix3 acts2LdmxRotation() {
184 return ldmx2ActsRotation().transpose();
185}
186
187inline Acts::Vector3 acts2Ldmx(Acts::Vector3 acts_v) {
188 return acts2LdmxRotation() * acts_v;
189}
190
191// Transform position, momentum and charge to free parameters
192
193inline Acts::FreeVector toFreeParameters(Acts::Vector3 pos_, Acts::Vector3 mom,
194 Acts::ActsScalar q) {
195 Acts::FreeVector free_params;
196 Acts::ActsScalar p = mom.norm() * Acts::UnitConstants::MeV;
197
198 free_params[Acts::eFreePos0] = pos_(Acts::ePos0) * Acts::UnitConstants::mm;
199 free_params[Acts::eFreePos1] = pos_(Acts::ePos1) * Acts::UnitConstants::mm;
200 free_params[Acts::eFreePos2] = pos_(Acts::ePos2) * Acts::UnitConstants::mm;
201 free_params[Acts::eFreeTime] = 0.;
202 free_params[Acts::eFreeDir0] = mom(0) / mom.norm();
203 free_params[Acts::eFreeDir1] = mom(1) / mom.norm();
204 free_params[Acts::eFreeDir2] = mom(2) / mom.norm();
205 free_params[Acts::eFreeQOverP] =
206 (q != Acts::ActsScalar(0)) ? (q / p) : 0.; // 1. / p instead?
207
208 return free_params;
209}
210
211// Pack the acts track parameters into something that is serializable for the
212// event bus
213
214inline std::vector<double> convertActsToLdmxPars(Acts::BoundVector acts_par) {
215 std::vector<double> v_ldmx(
216 acts_par.data(), acts_par.data() + acts_par.rows() * acts_par.cols());
217 return v_ldmx;
218}
219
220inline Acts::BoundVector boundState(const ldmx::Track& trk) {
221 Acts::BoundVector param_vec;
222 param_vec << trk.getD0(), trk.getZ0(), trk.getPhi(), trk.getTheta(),
223 trk.getQoP(), trk.getT();
224 return param_vec;
225}
226
227inline Acts::BoundTrackParameters boundTrackParameters(
228 const ldmx::Track& trk, std::shared_ptr<Acts::PerigeeSurface> perigee) {
229 Acts::BoundVector param_vec = boundState(trk);
230 Acts::BoundSquareMatrix cov_mat = unpackCov(trk.getPerigeeCov());
231 auto part_hypo{Acts::SinglyChargedParticleHypothesis::electron()};
232 return Acts::BoundTrackParameters(perigee, param_vec, std::move(cov_mat),
233 part_hypo);
234}
235
236// Return an unbound surface
237inline const std::shared_ptr<Acts::PlaneSurface> unboundSurface(
238 double xloc, double yloc = 0., double zloc = 0.) {
239 // Define the target surface - be careful:
240 // x_ - downstream
241 // y_ - left (when looking along x_)
242 // z_ - up
243 // Passing identity here means that your target surface is oriented in the
244 // same way
245 Acts::RotationMatrix3 surf_rotation = Acts::RotationMatrix3::Zero();
246 // u direction along +Y
247 surf_rotation(1, 0) = 1;
248 // v direction along +Z
249 surf_rotation(2, 1) = 1;
250 // w direction along +X
251 surf_rotation(0, 2) = 1;
252
253 Acts::Vector3 pos(xloc, yloc, zloc);
254 Acts::Translation3 surf_translation(pos);
255 Acts::Transform3 surf_transform(surf_translation * surf_rotation);
256
257 // Unbounded surface
258 const std::shared_ptr<Acts::PlaneSurface> target_surface =
259 Acts::Surface::makeShared<Acts::PlaneSurface>(surf_transform);
260
261 return Acts::Surface::makeShared<Acts::PlaneSurface>(surf_transform);
262}
263
264// This method returns a source link index
265inline std::size_t sourceLinkHash(const Acts::SourceLink& a) {
266 return static_cast<std::size_t>(
268}
269
270// This method checks if two source links are equal by index
271inline bool sourceLinkEquality(const Acts::SourceLink& a,
272 const Acts::SourceLink& b) {
273 return a.get<acts_examples::IndexSourceLink>().index() ==
274 b.get<acts_examples::IndexSourceLink>().index();
275}
276/*
277 * Build a TrackState from ACTS BoundTrackParameters.
278 * All output quantities (position, momentum, covariance) are in the LDMX
279 * global frame: x=horizontal, y=vertical, z=downstream.
280 *
281 * Covariance transformation steps:
282 * 1. Bound (6x6) -> Free (8x8) via ACTS bound-to-free Jacobian
283 * 2. Drop time row/col -> 7x7
284 * 3. 7D (x,y,z,d0,d1,d2,qop) -> 6D Cartesian (x,y,z,px,py,pz) in ACTS frame
285 * 4. Rotate 6x6 covariance ACTS -> LDMX via block-diagonal rotation
286 * 5. Flatten upper triangle -> 21-element vector
287 */
288inline ldmx::Track::TrackState makeTrackState(
289 const Acts::GeometryContext& gctx,
290 const Acts::BoundTrackParameters& bound_pars,
291 ldmx::TrackStateType ts_type = ldmx::Invalid) {
293 new_ts.ts_type_ = ts_type;
294
295 const double p = bound_pars.absoluteMomentum(); // GeV
296 const Acts::Vector3 acts_pos = bound_pars.position(gctx);
297 const Acts::Vector3 acts_dir = bound_pars.direction();
298
299 // Rotate position and momentum to LDMX frame
300 const Acts::SquareMatrix3 r = acts2LdmxRotation();
301 const Acts::Vector3 ldmx_pos = r * acts_pos;
302 const Acts::Vector3 ldmx_mom = r * (acts_dir * p);
303
304 new_ts.pos_ = {ldmx_pos[0], ldmx_pos[1], ldmx_pos[2]};
305 // Convert momentum from ACTS native units (GeV) to MeV
306 new_ts.mom_ = {ldmx_mom[0] / Acts::UnitConstants::MeV,
307 ldmx_mom[1] / Acts::UnitConstants::MeV,
308 ldmx_mom[2] / Acts::UnitConstants::MeV};
309
310 const auto& bound_cov = bound_pars.covariance();
311 if (!bound_cov.has_value()) {
312 std::cerr << "TrackingUtils::makeTrackState: bound covariance missing\n";
313 return new_ts;
314 }
315
316 // Step 1: Bound (6x6) -> Free (8x8) covariance
317 const Acts::BoundToFreeMatrix j_btf =
318 bound_pars.referenceSurface().boundToFreeJacobian(gctx, acts_pos,
319 acts_dir);
320 const Acts::FreeSquareMatrix free_cov =
321 j_btf * bound_cov.value() * j_btf.transpose();
322
323 // Step 2: Drop time row/col (eFreeTime = 3) -> 7x7
324 // Remaining indices: pos(0,1,2), dir(4,5,6), qop(7)
325 constexpr std::array<int, 7> k_keep = {
326 Acts::eFreePos0, Acts::eFreePos1, Acts::eFreePos2, Acts::eFreeDir0,
327 Acts::eFreeDir1, Acts::eFreeDir2, Acts::eFreeQOverP};
328 Eigen::Matrix<double, 7, 7> free_cov7;
329 for (int i = 0; i < 7; ++i)
330 for (int j = 0; j < 7; ++j)
331 free_cov7(i, j) = free_cov(k_keep[i], k_keep[j]);
332
333 // Step 3: Jacobian from 7D free-no-time -> 6D Cartesian in ACTS frame
334 // p_i = dir_i * p, dp_i/d(dir_j) = p*delta_ij, dp_i/d(qop) = -dir_i*p/qop
335 const double qop = bound_pars.parameters()[Acts::eBoundQOverP];
336 Eigen::Matrix<double, 6, 7> j_fp = Eigen::Matrix<double, 6, 7>::Zero();
337 j_fp.block<3, 3>(0, 0) = Eigen::Matrix3d::Identity(); // pos -> pos
338 j_fp.block<3, 3>(3, 3) = p * Eigen::Matrix3d::Identity(); // dir -> mom
339 j_fp(3, 6) = -acts_dir[0] * p / qop; // qop -> px
340 j_fp(4, 6) = -acts_dir[1] * p / qop; // qop -> py
341 j_fp(5, 6) = -acts_dir[2] * p / qop; // qop -> pz
342
343 const Eigen::Matrix<double, 6, 6> cov_acts =
344 j_fp * free_cov7 * j_fp.transpose();
345
346 // Step 4: Rotate covariance ACTS -> LDMX using block-diagonal R_6 = diag(R,R)
347 Eigen::Matrix<double, 6, 6> r_6 = Eigen::Matrix<double, 6, 6>::Zero();
348 r_6.block<3, 3>(0, 0) = r;
349 r_6.block<3, 3>(3, 3) = r;
350 const Eigen::Matrix<double, 6, 6> cov_ldmx = r_6 * cov_acts * r_6.transpose();
351
352 // Step 5: Flatten upper triangle -> 21 elements.
353 // Scale momentum rows/cols from GeV to MeV:
354 // pos-pos (i<3, j<3): x1 [mm^2]
355 // pos-mom (i<3, j>=3): x1000 [mm*MeV]
356 // mom-mom (i>=3, j>=3): x1e6 [MeV^2]
357 const double mev = Acts::UnitConstants::MeV;
358 new_ts.pos_mom_cov_.reserve(21);
359 for (int i = 0; i < 6; ++i) {
360 for (int j = i; j < 6; ++j) {
361 double scale = 1.0;
362 if (i >= 3) scale /= mev; // row is momentum (GeV -> MeV)
363 if (j >= 3) scale /= mev; // col is momentum (GeV -> MeV)
364 new_ts.pos_mom_cov_.push_back(cov_ldmx(i, j) * scale);
365 }
366 }
367
368 return new_ts;
369}
370} // namespace utils
371} // namespace sim
372} // namespace tracking
373
374#endif
Class which encapsulates information from a hit in a simulated tracking detector.
Represents a simulated tracker hit in the simulation.
int getModuleID() const
Get the module ID associated with a hit.
float getEdep() const
Get the energy deposited on the hit [MeV].
std::vector< float > getPosition() const
Get the XYZ position of the hit [mm].
int getID() const
Get the detector ID of the hit.
float getTime() const
Get the global time of the hit [ns].
int getLayerID() const
Get the geometric layer ID of the hit.
Implementation of a track object.
Definition Track.h:53
The measurement calibrator can be a function or a class/struct able to retrieve the sim hits containe...