LDMX Software
TrackingUtils.cxx
1#include "Tracking/Sim/TrackingUtils.h"
2
3#include "Acts/Definitions/Units.hpp"
4#include "Acts/Surfaces/Surface.hpp"
6#include "Tracking/Sim/IndexSourceLink.h"
7
8namespace tracking {
9namespace sim {
10namespace utils {
11
12// This method returns the sensor ID
13int getSensorID(const ldmx::SimTrackerHit& hit) {
14 bool debug = false;
15
16 ldmx::TrackerID tid(hit.getID());
17 auto subdet = tid.subdet();
18 // Test-stand tracker surfaces are built into the same Acts tracking volume as
19 // the recoil (they occupy the recoil region), so they share vol == 3; only
20 // the layer/sensor mapping differs (see the vol == 3 block below).
21 int vol = (subdet == ldmx::SD_TRACKER_RECOIL ||
22 subdet == ldmx::SD_TRACKER_TESTSTAND)
23 ? 3
24 : 2;
25
26 unsigned int sensor_id = 0;
27 unsigned int layer_id = 0;
28
29 // tagger numbering scheme for surfaces mapping
30 // Layers from 1 to 14 => transform to 0->13
31 if (vol == 2) {
32 sensor_id = (hit.getLayerID() + 1) % 2; // 0,1,0,1 ...
33
34 // v12
35 // layerId = (hit.getLayerID() + 1) / 2; //1,2,3,4,5,6,7
36 // v14
37 layer_id = 7 - ((hit.getLayerID() - 1) / 2);
38 }
39
40 // recoil numbering scheme for surfaces mapping
41 if (vol == 3) {
42 if (subdet == ldmx::SD_TRACKER_TESTSTAND) {
43 // Test-stand tracker (ESA25, cosmic). Its GDML is hardware-correct: the
44 // STEREO sensor sits upstream (even layerID: copy 20,40) and the AXIAL
45 // sensor downstream (odd layerID: copy 10,30). Acts numbers a station's
46 // two sensors by z, so sensor 0 = upstream (stereo). Hence odd layerID ->
47 // sensor 1, even layerID -> sensor 0, i.e. sensor = layerID % 2 -- the
48 // opposite parity to the recoil branch below, which assumes the v14
49 // z-order (axial upstream). Isolating it here leaves v14/recoil
50 // untouched.
51 sensor_id = hit.getLayerID() % 2;
52 layer_id = (hit.getLayerID() + 1) / 2;
53 }
54
55 // For axial-stereo modules use the same numbering scheme as the tagger
56 else if (hit.getLayerID() < 9) {
57 sensor_id = (hit.getLayerID() + 1) % 2;
58 layer_id = (hit.getLayerID() + 1) / 2;
59 }
60
61 // For the axial only modules
62 else {
63 sensor_id = hit.getModuleID();
64 layer_id = (hit.getLayerID() + 2) / 2; // 9->11 /2 = 5 10->12 / 2 = 6
65 }
66 }
67
68 // vol * 1000 + ly * 100 + sensor
69 unsigned int index = vol * 1000 + layer_id * 100 + sensor_id;
70
71 if (debug) {
72 std::cout << "LdmxSpacePointConverter::Check index::" << vol << "--"
73 << layer_id << "--" << sensor_id << "==>" << index << std::endl;
74 std::cout << vol << "===" << hit.getLayerID() << "===" << hit.getModuleID()
75 << std::endl;
76 }
77
78 return index;
79}
80
81// This method converts a SimHit in a LdmxSpacePoint for the Acts seeder.
82// (1) Rotate the coordinates into acts::seedFinder coordinates defined by
83// B-Field along z_ axis [Z_ldmx -> X_acts, X_ldmx->Y_acts, Y_ldmx->Z_acts]
84// (2) Saves the error information. At the moment the errors are fixed. They
85// should be obtained from the digitized hits_.
86// Vol==2 for tagger, Vol==3 for recoil
87ldmx::LdmxSpacePoint* convertSimHitToLdmxSpacePoint(
88 const ldmx::SimTrackerHit& hit, unsigned int vol, double sigma_u,
89 double sigma_v) {
90 unsigned int index = getSensorID(hit);
91
92 // Rotate position
93 float ldmxsp_x = hit.getPosition()[2];
94 float ldmxsp_y = hit.getPosition()[0];
95 float ldmxsp_z = hit.getPosition()[1];
96
97 return new ldmx::LdmxSpacePoint(ldmxsp_x, ldmxsp_y, ldmxsp_z, hit.getTime(),
98 index, hit.getEdep(), sigma_u * sigma_u,
99 sigma_v * sigma_v, hit.getID());
100}
101
102void flatCov(Acts::BoundMatrix cov, std::vector<double>& v_cov) {
103 v_cov.clear();
104 v_cov.reserve(cov.rows() * (cov.rows() + 1) / 2);
105 for (int i = 0; i < cov.rows(); i++)
106 for (int j = i; j < cov.cols(); j++) v_cov.push_back(cov(i, j));
107}
108
109Acts::BoundMatrix unpackCov(const std::vector<double>& v_cov) {
110 Acts::BoundMatrix cov;
111 int e{0};
112 for (int i = 0; i < cov.rows(); i++)
113 for (int j = i; j < cov.cols(); j++) {
114 cov(i, j) = v_cov.at(e);
115 cov(j, i) = cov(i, j);
116 e++;
117 }
118
119 return cov;
120}
121
122// Rotate LDMX global -> ACTS frame: z_ldmx->x_acts, x_ldmx->y_acts,
123// y_ldmx->z_acts (0 0 1) * (x,y,z)_ldmx = x_acts (1 0 0) * (x,y,z)_ldmx =
124// y_acts (0 1 0) * (x,y,z)_ldmx = z_acts
125Acts::SquareMatrix3 ldmx2ActsRotation() {
126 Acts::SquareMatrix3 r;
127 r << 0., 0., 1., 1., 0., 0., 0., 1., 0.;
128 return r;
129}
130
131Acts::Vector3 ldmx2Acts(Acts::Vector3 ldmx_v) {
132 return ldmx2ActsRotation() * ldmx_v;
133}
134
135// Rotate ACTS frame -> LDMX global (inverse of ldmx2Acts, i.e. transpose):
136// x_ldmx = y_acts, y_ldmx = z_acts, z_ldmx = x_acts
137Acts::SquareMatrix3 acts2LdmxRotation() {
138 return ldmx2ActsRotation().transpose();
139}
140
141Acts::Vector3 acts2Ldmx(Acts::Vector3 acts_v) {
142 return acts2LdmxRotation() * acts_v;
143}
144
145// Transform position, momentum and charge to free parameters
146Acts::FreeVector toFreeParameters(Acts::Vector3 pos_, Acts::Vector3 mom,
147 double q) {
148 Acts::FreeVector free_params;
149 double p = mom.norm() * Acts::UnitConstants::MeV;
150
151 free_params[Acts::eFreePos0] = pos_(Acts::ePos0) * Acts::UnitConstants::mm;
152 free_params[Acts::eFreePos1] = pos_(Acts::ePos1) * Acts::UnitConstants::mm;
153 free_params[Acts::eFreePos2] = pos_(Acts::ePos2) * Acts::UnitConstants::mm;
154 free_params[Acts::eFreeTime] = 0.;
155 free_params[Acts::eFreeDir0] = mom(0) / mom.norm();
156 free_params[Acts::eFreeDir1] = mom(1) / mom.norm();
157 free_params[Acts::eFreeDir2] = mom(2) / mom.norm();
158 free_params[Acts::eFreeQOverP] =
159 (q != double(0)) ? (q / p) : 0.; // 1. / p instead?
160
161 return free_params;
162}
163
164// Pack the acts track parameters into something that is serializable for the
165// event bus
166std::vector<double> convertActsToLdmxPars(Acts::BoundVector acts_par) {
167 std::vector<double> v_ldmx(
168 acts_par.data(), acts_par.data() + acts_par.rows() * acts_par.cols());
169 return v_ldmx;
170}
171
172Acts::BoundVector boundState(const ldmx::Track& trk) {
173 Acts::BoundVector param_vec;
174 param_vec << trk.getD0(), trk.getZ0(), trk.getPhi(), trk.getTheta(),
175 trk.getQoP(), trk.getT();
176 return param_vec;
177}
178
179Acts::BoundTrackParameters boundTrackParameters(
180 const ldmx::Track& trk, std::shared_ptr<Acts::PerigeeSurface> perigee) {
181 Acts::BoundVector param_vec = boundState(trk);
182 Acts::BoundMatrix cov_mat = unpackCov(trk.getPerigeeCov());
183 auto part_hypo{Acts::ParticleHypothesis::electron()};
184 return Acts::BoundTrackParameters(perigee, param_vec, std::move(cov_mat),
185 part_hypo);
186}
187
188// Return an unbound surface
189const std::shared_ptr<Acts::PlaneSurface> unboundSurface(double xloc,
190 double yloc,
191 double zloc) {
192 // Define the target surface - be careful:
193 // x_ - downstream
194 // y_ - left (when looking along x_)
195 // z_ - up
196 // Passing identity here means that your target surface is oriented in the
197 // same way
198 Acts::RotationMatrix3 surf_rotation = Acts::RotationMatrix3::Zero();
199 // u direction along +Y
200 surf_rotation(1, 0) = 1;
201 // v direction along +Z
202 surf_rotation(2, 1) = 1;
203 // w direction along +X
204 surf_rotation(0, 2) = 1;
205
206 Acts::Vector3 pos(xloc, yloc, zloc);
207 Acts::Translation3 surf_translation(pos);
208 Acts::Transform3 surf_transform(surf_translation * surf_rotation);
209
210 // Unbounded surface
211 const std::shared_ptr<Acts::PlaneSurface> target_surface =
212 Acts::Surface::makeShared<Acts::PlaneSurface>(surf_transform);
213
214 return Acts::Surface::makeShared<Acts::PlaneSurface>(surf_transform);
215}
216
217// This method returns a source link index
218std::size_t sourceLinkHash(const Acts::SourceLink& a) {
219 return static_cast<std::size_t>(
221}
222
223// This method checks if two source links are equal by index
224bool sourceLinkEquality(const Acts::SourceLink& a, const Acts::SourceLink& b) {
225 return a.get<acts_examples::IndexSourceLink>().index() ==
226 b.get<acts_examples::IndexSourceLink>().index();
227}
228
229/*
230 * Build a TrackState from ACTS BoundTrackParameters.
231 * All output quantities (position, momentum, covariance) are in the LDMX
232 * global frame: x=horizontal, y=vertical, z=downstream.
233 *
234 * Covariance transformation steps:
235 * 1. Bound (6x6) -> Free (8x8) via ACTS bound-to-free Jacobian
236 * 2. Drop time row/col -> 7x7
237 * 3. 7D (x,y,z,d0,d1,d2,qop) -> 6D Cartesian (x,y,z,px,py,pz) in ACTS frame
238 * 4. Rotate 6x6 covariance ACTS -> LDMX via block-diagonal rotation
239 * 5. Flatten upper triangle -> 21-element vector
240 */
241ldmx::Track::TrackState makeTrackState(
242 const Acts::GeometryContext& gctx,
243 const Acts::BoundTrackParameters& bound_pars,
244 ldmx::TrackStateType ts_type) {
246 new_ts.ts_type_ = ts_type;
247
248 const double p = bound_pars.absoluteMomentum(); // GeV
249 const Acts::Vector3 acts_pos = bound_pars.position(gctx);
250 const Acts::Vector3 acts_dir = bound_pars.direction();
251
252 // Rotate position and momentum to LDMX frame
253 const Acts::SquareMatrix3 r = acts2LdmxRotation();
254 const Acts::Vector3 ldmx_pos = r * acts_pos;
255 const Acts::Vector3 ldmx_mom = r * (acts_dir * p);
256
257 new_ts.pos_ = {ldmx_pos[0], ldmx_pos[1], ldmx_pos[2]};
258 // Convert momentum from ACTS native units (GeV) to MeV
259 new_ts.mom_ = {ldmx_mom[0] / Acts::UnitConstants::MeV,
260 ldmx_mom[1] / Acts::UnitConstants::MeV,
261 ldmx_mom[2] / Acts::UnitConstants::MeV};
262
263 const auto& bound_cov = bound_pars.covariance();
264 if (!bound_cov.has_value()) {
265 std::cerr << "TrackingUtils::makeTrackState: bound covariance missing\n";
266 return new_ts;
267 }
268
269 // Step 1: Bound (6x6) -> Free (8x8) covariance
270 const Acts::BoundToFreeMatrix j_btf =
271 bound_pars.referenceSurface().boundToFreeJacobian(gctx, acts_pos,
272 acts_dir);
273 const Acts::FreeMatrix free_cov =
274 j_btf * bound_cov.value() * j_btf.transpose();
275
276 // Step 2: Drop time row/col (eFreeTime = 3) -> 7x7
277 // Remaining indices: pos(0,1,2), dir(4,5,6), qop(7)
278 constexpr std::array<int, 7> k_keep = {
279 Acts::eFreePos0, Acts::eFreePos1, Acts::eFreePos2, Acts::eFreeDir0,
280 Acts::eFreeDir1, Acts::eFreeDir2, Acts::eFreeQOverP};
281 Eigen::Matrix<double, 7, 7> free_cov7;
282 for (int i = 0; i < 7; ++i)
283 for (int j = 0; j < 7; ++j)
284 free_cov7(i, j) = free_cov(k_keep[i], k_keep[j]);
285
286 // Step 3: Jacobian from 7D free-no-time -> 6D Cartesian in ACTS frame
287 // p_i = dir_i * p, dp_i/d(dir_j) = p*delta_ij, dp_i/d(qop) = -dir_i*p/qop
288 const double qop = bound_pars.parameters()[Acts::eBoundQOverP];
289 Eigen::Matrix<double, 6, 7> j_fp = Eigen::Matrix<double, 6, 7>::Zero();
290 j_fp.block<3, 3>(0, 0) = Eigen::Matrix3d::Identity(); // pos -> pos
291 j_fp.block<3, 3>(3, 3) = p * Eigen::Matrix3d::Identity(); // dir -> mom
292 j_fp(3, 6) = -acts_dir[0] * p / qop; // qop -> px
293 j_fp(4, 6) = -acts_dir[1] * p / qop; // qop -> py
294 j_fp(5, 6) = -acts_dir[2] * p / qop; // qop -> pz
295
296 const Eigen::Matrix<double, 6, 6> cov_acts =
297 j_fp * free_cov7 * j_fp.transpose();
298
299 // Step 4: Rotate covariance ACTS -> LDMX using block-diagonal R_6 = diag(R,R)
300 Eigen::Matrix<double, 6, 6> r_6 = Eigen::Matrix<double, 6, 6>::Zero();
301 r_6.block<3, 3>(0, 0) = r;
302 r_6.block<3, 3>(3, 3) = r;
303 const Eigen::Matrix<double, 6, 6> cov_ldmx = r_6 * cov_acts * r_6.transpose();
304
305 // Step 5: Flatten upper triangle -> 21 elements.
306 // Scale momentum rows/cols from GeV to MeV:
307 // pos-pos (i<3, j<3): x1 [mm^2]
308 // pos-mom (i<3, j>=3): x1000 [mm*MeV]
309 // mom-mom (i>=3, j>=3): x1e6 [MeV^2]
310 const double mev = Acts::UnitConstants::MeV;
311 new_ts.pos_mom_cov_.reserve(21);
312 for (int i = 0; i < 6; ++i) {
313 for (int j = i; j < 6; ++j) {
314 double scale = 1.0;
315 if (i >= 3) scale /= mev; // row is momentum (GeV -> MeV)
316 if (j >= 3) scale /= mev; // col is momentum (GeV -> MeV)
317 new_ts.pos_mom_cov_.push_back(cov_ldmx(i, j) * scale);
318 }
319 }
320
321 return new_ts;
322}
323
324} // namespace utils
325} // namespace sim
326} // namespace tracking
Class that defines a Tracker detector ID with a module number.
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:54
Extension of DetectorID providing access to layer and module number for tracker IDs.
Definition TrackerID.h:20
The measurement calibrator can be a function or a class/struct able to retrieve the sim hits containe...