LDMX Software
EcalGeometry.cxx
2
3#include <cmath>
4#include <iomanip>
5#include <iostream>
6#include <tuple>
7
8#include "Framework/Exception/Exception.h"
9#include "TGraph.h"
10#include "TList.h"
11
12namespace ldmx {
13
14static double distance(const std::pair<double, double>& p1,
15 const std::pair<double, double>& p2) {
16 return sqrt((p1.first - p2.first) * (p1.first - p2.first) +
17 (p1.second - p2.second) * (p1.second - p2.second));
18}
19
20[[maybe_unused]] static double distance(
21 const std::tuple<double, double, double>& p1,
22 const std::tuple<double, double, double>& p2) {
23 return sqrt((std::get<0>(p1) - std::get<0>(p2)) *
24 (std::get<0>(p1) - std::get<0>(p2)) +
25 (std::get<1>(p1) - std::get<1>(p2)) *
26 (std::get<1>(p1) - std::get<1>(p2)) +
27 (std::get<2>(p1) - std::get<2>(p2)) *
28 (std::get<2>(p1) - std::get<2>(p2)));
29}
30
34static void rotate(double& p, double& q) {
35 double tmp{p};
36 p = -q;
37 q = tmp;
38}
39
43static void unrotate(double& p, double& q) {
44 double tmp{p};
45 p = q;
46 q = -tmp;
47}
48
50 : framework::ConditionsObject(EcalGeometry::CONDITIONS_OBJECT_NAME) {
51 layer_z_positions_ = ps.get<std::vector<double>>("layer_z_positions");
52 ecal_front_z_ = ps.get<double>("ecal_front_z");
53 module_r_min_ = ps.get<double>("module_min_r");
54 gap_ = ps.get<double>("gap");
55 n_cell_r_height_ = ps.get<double>("n_cell_r_height");
56 corners_side_up_ = ps.get<bool>("corners_side_up");
57 layer_shift_x_ = ps.get<double>("layer_shift_x");
58 layer_shift_y_ = ps.get<double>("layer_shift_y");
59 layer_shift_odd_ = ps.get<bool>("layer_shift_odd");
60 layer_shift_odd_bilayer_ = ps.get<bool>("layer_shift_odd_bilayer");
61 si_thickness_ = ps.get<double>("si_thickness");
62
64 EXCEPTION_RAISE("BadConf",
65 "Cannot shift both odd sensitive layers and odd bilayers");
66 }
67
68 module_r_max_ = module_r_min_ * (2 / sqrt(3));
70 cell_r_min_ = (sqrt(3.) / 2.) * cell_r_max_;
71
72 ldmx_log(debug) << "Building module map with gap " << std::setprecision(2)
73 << gap_ << ", nCellRHeight " << n_cell_r_height_
74 << ", min/max radii of cell " << cell_r_min_ << " / "
75 << cell_r_max_ << ", and module " << module_r_min_ << " / "
77
83
84 ldmx_log(trace) << "Geometry fully constructed";
85}
86
87EcalID EcalGeometry::getID(double x, double y, double z, bool fallible) const {
88 int layer_id{-1};
89 for (const auto& [lid, layer_xyz] : layer_pos_xy_) {
90 // check if the z coordinate is within the layer thickness
91 if (std::abs(std::get<2>(layer_xyz) - z) < si_thickness_) {
92 layer_id = lid;
93 break;
94 }
95 }
96 if (layer_id < 0) {
97 if (fallible) {
98 return ldmx::EcalID(0);
99 } else {
100 EXCEPTION_RAISE("BadConf", "z = " + std::to_string(z) +
101 " mm is not within any"
102 " of the configured layers.");
103 }
104 }
105 return getID(x, y, layer_id, fallible);
106}
107
108EcalID EcalGeometry::getID(double x, double y, int layer_id,
109 bool fallible) const {
110 // now assume we know the layer
111 // shift to center of layer
112 // and convert to flower coordinates
113 double p{x - std::get<0>(layer_pos_xy_.at(layer_id))},
114 q{y - std::get<1>(layer_pos_xy_.at(layer_id))};
115
116 // deduce module ID
117 // there are only 7 modules so we just loop through them
118 // all and pick out the module ID that we are inside of
119
120 int module_id{-1};
121 for (auto const& [mid, module_xy] : module_pos_xy_) {
122 double probe_x{p - module_xy.first}, probe_y{q - module_xy.second};
123 if (corners_side_up_) rotate(probe_x, probe_y);
124 if (isInside(probe_x / module_r_max_, probe_y / module_r_max_)) {
125 module_id = mid;
126 break;
127 }
128 }
129
130 if (module_id < 0) {
131 if (fallible) {
132 return ldmx::EcalID(0);
133 } else {
134 EXCEPTION_RAISE(
135 "BadConf",
136 TString::Format(
137 "Coordinates relative to layer (p,q) = (%.2f, %.2f) mm "
138 "derived from world coordinates (%.2f, %.2f) mm with layer = %d "
139 "are not inside any module.",
140 p, q, x, y, layer_id)
141 .Data());
142 }
143 }
144
145 return getID(x, y, layer_id, module_id);
146}
147
148EcalID EcalGeometry::getID(double x, double y, int layer_id, int module_id,
149 bool fallible) const {
150 // now assume we know the layer and module
151 // shift to center of layer and then center of module
152 double p{x - std::get<0>(layer_pos_xy_.at(layer_id)) -
153 module_pos_xy_.at(module_id).first},
154 q{y - std::get<1>(layer_pos_xy_.at(layer_id)) -
155 module_pos_xy_.at(module_id).second};
156
157 // need to rotate
158 if (corners_side_up_) rotate(p, q);
159
160 // deduce cell ID
161 int cell_id = cell_id_in_module_.FindBin(p, q) - 1;
162
163 if (cell_id < 0) {
164 if (fallible) {
165 return ldmx::EcalID(0);
166 } else {
167 EXCEPTION_RAISE(
168 "BadConf",
169 TString::Format(
170 "Relative cell coordinates (%.2f, %.2f) mm "
171 "derived from world coordinates (%.2f, %.2f) mm with layer = %d "
172 "and module = %d are outside module hexagon",
173 p, q, x, y, layer_id, module_id)
174 .Data());
175 }
176 }
177
178 return EcalID(layer_id, module_id, cell_id);
179}
180
181std::tuple<double, double, double> EcalGeometry::getPosition(EcalID id) const {
182 return cell_global_pos_.at(id);
183}
184
185std::pair<double, double> EcalGeometry::getPositionInModule(int cell_id) const {
186 auto pq = cell_pos_in_module_.at(cell_id);
187
188 // going from (p,q) to (x,y) is a unrotate
189 if (corners_side_up_) unrotate(pq.first, pq.second);
190
191 return pq;
192}
193
195 ldmx_log(debug) << "Building layer map with " << layer_z_positions_.size()
196 << " layers";
198 if (layer_shift_odd_) {
199 ldmx_log(debug) << "Shifting odd layers by (x,y) = (" << layer_shift_x_
200 << ", " << layer_shift_y_ << ") mm";
201 } else {
202 ldmx_log(debug) << "Shifting odd bilayers by (x,y) = (" << layer_shift_x_
203 << ", " << layer_shift_y_ << ") mm";
204 }
205 }
206
207 for (std::size_t i_layer{0}; i_layer < layer_z_positions_.size(); ++i_layer) {
208 // default is centered on z-axis
209 double x{0}, y{0}, z{ecal_front_z_ + layer_z_positions_.at(i_layer)};
210 if (layer_shift_odd_ and (i_layer % 2 == 1)) {
211 x += layer_shift_x_;
212 y += layer_shift_y_;
213 } else if (layer_shift_odd_bilayer_ and ((i_layer / 2) % 2 == 1)) {
214 x += layer_shift_x_;
215 y += layer_shift_y_;
216 }
217 ldmx_log(trace) << " Layer " << i_layer << " has center at (" << x
218 << ", " << y << ", " << z << ") mm";
219
220 layer_pos_xy_[i_layer] = std::make_tuple(x, y, z);
221 }
222}
223
225 static const double c_pi = 3.14159265358979323846;
226
227 // the center module (module_id == 0) has always been (and will always be?)
228 // centered with respect to the layer position
229 module_pos_xy_[0] = std::pair<double, double>(0., 0.);
230
231 // for flat-side-up designs (v12 and earlier), the modules are numbered 1 on
232 // positive y-axis and then counter-clockwise until 6
233 //
234 // for corner-side-up designes (v13 and later), the modules are numbered 1 on
235 // positive x-axis and then counter-clockwise until 6.
236 for (unsigned id = 1; id < 7; id++) {
237 // flat-side-up
238 double x = (2. * module_r_min_ + gap_) * sin((id - 1) * (c_pi / 3.));
239 double y = (2. * module_r_min_ + gap_) * cos((id - 1) * (c_pi / 3.));
240 if (corners_side_up_) {
241 // re-calculating to make sure centers match GDML
242 x = (2. * module_r_min_ + gap_) * cos((id - 1) * (c_pi / 3.));
243 y = -(2. * module_r_min_ + gap_) * sin((id - 1) * (c_pi / 3.));
244 }
245 module_pos_xy_[id] = std::pair<double, double>(x, y);
246 ldmx_log(trace) << " Module " << id << " is centered at (x,y) = " << "("
247 << x << ", " << y << ") mm";
248 }
249}
250
271 TH2Poly grid_map;
272
273 // make hexagonal grid [boundary is rectangle] larger than the module
274 double grid_min_p = 0., grid_min_q = 0.; // start at the origin
275 int num_p_cells = 0, num_q_cells = 0;
276
277 // first x-cell is only a half
278 grid_min_p -= cell_r_min_;
279 num_p_cells++;
280 while (grid_min_p > -1 * module_r_max_) {
281 // decrement x by cell center-to-flat diameter
282 grid_min_p -= 2 * cell_r_min_;
283 num_p_cells++;
284 }
285 while (grid_min_q > -1 * module_r_min_) {
286 // decrement y by cell center-to-corner radius
287 // alternate between a full corner-to-corner diameter
288 // and a side of a cell (center-to-corner radius)
289 if (num_q_cells % 2 == 0)
290 grid_min_q -= 1 * cell_r_max_;
291 else
292 grid_min_q -= 2 * cell_r_max_;
293 num_q_cells++;
294 }
295 // only counted one half of the cells
296 num_p_cells *= 2;
297 num_q_cells *= 2;
298
299 grid_map.Honeycomb(grid_min_p, grid_min_q, cell_r_max_, num_p_cells,
300 num_q_cells);
301
302 ldmx_log(trace) << std::setprecision(2)
303 << "Building buildCellMap with cell rmin: " << cell_r_min_
304 << " cell rmax: " << cell_r_max_ << " (gridMinP,gridMinQ) = ("
305 << grid_min_p << "," << grid_min_q << ")"
306 << " (numPCells,numQCells) = (" << num_p_cells << ","
307 << num_q_cells << ")";
308
309 // copy cells lying within module boundaries to a module grid
310 TListIter next(grid_map.GetBins()); // a TH2Poly is a TList of TH2PolyBin
311 TH2PolyBin* poly_bin = 0;
312 TGraph* poly = 0; // a polygon returned by TH2Poly is a TGraph
313 // cells_in_module_ IDs go from 0 to N-1, not equal to original grid cell ID
314 int cell_id = 0;
315 while ((poly_bin = (TH2PolyBin*)next())) {
316 // these bins are coming from the honeycomb
317 // grid so we assume that they are regular
318 // hexagons i.e. has 6 vertices
319 poly = (TGraph*)poly_bin->GetPolygon();
320
321 // decide whether to copy polygon to new map.
322 // use all vertices in case of cut-off edge polygons.
323 int num_vertices_inside = 0;
324 double vertex_p[6], vertex_q[6]; // vertices of the cell
325 bool isinside[6]; // which vertices are inside
326 ldmx_log(trace) << " Cell vertices";
327 for (unsigned i = 0; i < 6; i++) {
328 poly->GetPoint(i, vertex_p[i], vertex_q[i]);
329 ldmx_log(trace) << " vtx # " << i;
330 ldmx_log(trace) << " vtx p,q " << vertex_p[i] << " " << vertex_q[i];
331
332 isinside[i] =
333 isInside(vertex_p[i] / module_r_max_, vertex_q[i] / module_r_max_);
334 if (isinside[i]) num_vertices_inside++;
335 }
336
337 if (num_vertices_inside > 1) {
338 // Include this cell if more than one of its vertices is inside the module
339 // hexagon
340 double actual_p[8], actual_q[8];
341 int num_vertices{0};
342 if (num_vertices_inside < 6) {
343 // This cell is stradling the edge of the module
344 // and is NOT cleanly cut by module edge
345
346 ldmx_log(trace) << " Polygon " << cell_id
347 << " has vertices poking out of module hexagon.";
348
349 // loop through vertices
350 for (int i = 0; i < 6; i++) {
351 int up = i == 5 ? 0 : i + 1;
352 int dn = i == 0 ? 5 : i - 1;
353 if (isinside[i] and (not isinside[up] or not isinside[dn])) {
354 // this vertex is inside the module hexagon and is adjacent to a
355 // vertex outside
356 // ==> project this vertex onto the nearest edge of the module
357 // hexagon
358
359 // determine which side of hexagon we should project onto
360 double edge_origin_p, edge_origin_q;
361 double edge_dest_p, edge_dest_q;
362 if (vertex_p[i] < -module_r_max_ / 2.) {
363 // sloped edge on negative-x side
364 edge_origin_p = -1. * module_r_max_;
365 edge_origin_q = 0.;
366 edge_dest_p = -0.5 * module_r_max_;
367 edge_dest_q = module_r_min_;
368 } else if (vertex_p[i] > module_r_max_ / 2.) {
369 // sloped edge on positive-x side
370 edge_origin_p = 0.5 * module_r_max_;
371 edge_origin_q = module_r_min_;
372 edge_dest_p = module_r_max_;
373 edge_dest_q = 0.;
374 } else {
375 // flat edge at top
376 edge_origin_p = 0.5 * module_r_max_;
377 edge_origin_q = module_r_min_;
378 edge_dest_p = -0.5 * module_r_max_;
379 edge_dest_q = module_r_min_;
380 }
381 // flip to bottom half if below x-axis
382 if (vertex_q[i] < 0) {
383 edge_dest_q *= -1;
384 edge_origin_q *= -1;
385 }
386
387 // get edge slope vector
388 double edge_slope_p = edge_dest_p - edge_origin_p;
389 double edge_slope_q = edge_dest_q - edge_origin_q;
390
391 ldmx_log(trace)
392 << "Vertex " << i
393 << " is inside and adjacent to a vertex outside the module.";
394 ldmx_log(trace) << "Working on edge with slope (" << edge_slope_p
395 << "," << edge_slope_q << ")" << " and origin ("
396 << edge_origin_p << "," << edge_origin_q << ")";
397
398 // project vertices adjacent to the vertex outside the module onto
399 // the module edge
400 double projection_factor =
401 ((vertex_p[i] - edge_origin_p) * edge_slope_p +
402 (vertex_q[i] - edge_origin_q) * edge_slope_q) /
403 (edge_slope_p * edge_slope_p + edge_slope_q * edge_slope_q);
404
405 double proj_p = edge_origin_p + projection_factor * edge_slope_p;
406 double proj_q = edge_origin_q + projection_factor * edge_slope_q;
407
408 if (not isinside[up]) {
409 // the next point is outside
410 actual_p[num_vertices] = vertex_p[i];
411 actual_q[num_vertices] = vertex_q[i];
412 actual_p[num_vertices + 1] = proj_p;
413 actual_q[num_vertices + 1] = proj_q;
414 } else {
415 // the previous point was outside
416 actual_p[num_vertices] = proj_p;
417 actual_q[num_vertices] = proj_q;
418 actual_p[num_vertices + 1] = vertex_p[i];
419 actual_q[num_vertices + 1] = vertex_q[i];
420 }
421 num_vertices += 2;
422
423 ldmx_log(trace) << "New Vertex " << i << " : (" << vertex_p[i]
424 << "," << vertex_q[i] << ")";
425
426 } else {
427 actual_p[num_vertices] = vertex_p[i];
428 actual_q[num_vertices] = vertex_q[i];
429 num_vertices++;
430 } // should we project or not
431 } // loop through vertices
432 } else {
433 // all 6 inside, just copy the vertices over
434 num_vertices = 6;
435 for (int i = 0; i < 6; i++) {
436 actual_p[i] = vertex_p[i];
437 actual_q[i] = vertex_q[i];
438 }
439 } // if numVerticesInside is less than 5
440
441 // TH2Poly needs to have its own copy of the polygon TGraph
442 // otherwise, we get a seg fault when EcalGeometry is destructed
443 // because the polygon that was copied over from gridMap is deleted at
444 // the end of this function
445 cell_id_in_module_.AddBin(num_vertices, actual_p, actual_q);
446
450 double p = (poly_bin->GetXMax() + poly_bin->GetXMin()) / 2.;
451 double q = (poly_bin->GetYMax() + poly_bin->GetYMin()) / 2.;
452
453 ldmx_log(trace) << " Copying poly with ID " << poly_bin->GetBinNumber()
454 << " and (p,q) (" << std::setprecision(2) << p << "," << q
455 << ")";
456 // save cell location as center of ENTIRE hexagon
457 cell_pos_in_module_[cell_id] = std::pair<double, double>(p, q);
458 ++cell_id; // incrememnt cell ID
459 } // if num vertices inside is > 1
460 } // loop over larger grid spanning module hexagon
461 return;
462}
463
465 ldmx_log(trace) << "Building cellModule position map";
467 for (auto const& [module_id, module_xy] : module_pos_xy_) {
468 for (auto const& [cell_id, cell_pq] : cell_pos_in_module_) {
469 double cell_x{cell_pq.first}, cell_y{cell_pq.second};
470 // convert from (p,q) to (x,y) space
471 // when the corners are not up, x = p and y = q
472 // so no transformation needs to be done
473 if (corners_side_up_) unrotate(cell_x, cell_y);
474
475 // calculate cell's pq relative to entire layer center
476 auto cell_rel_to_layer =
477 std::make_pair(module_xy.first + cell_x, module_xy.second + cell_y);
478
479 // now add the layer-center values to get the global position
480 // of the cell
481 cell_pos_in_layer_[EcalID(0, module_id, cell_id)] = cell_rel_to_layer;
482 }
483 }
484
486 for (auto const& [layer_id, layer_xyz] : layer_pos_xy_) {
487 for (auto const& [flat_id, rel_to_layer] : cell_pos_in_layer_) {
488 // now add the layer-center values to get the global position
489 // of the cell
490 cell_global_pos_[EcalID(layer_id, flat_id.module(), flat_id.cell())] =
491 std::make_tuple(rel_to_layer.first + std::get<0>(layer_xyz),
492 rel_to_layer.second + std::get<1>(layer_xyz),
493 std::get<2>(layer_xyz));
494 }
495 }
496
497 ldmx_log(trace) << "Cell module map contains " << cell_global_pos_.size()
498 << " entries. ";
499 return;
500}
501
516 ldmx_log(trace) << "Building Nearest and Next-Nearest Neighbor maps";
517
518 nn_map_.clear();
519 nnn_map_.clear();
520 for (auto const& [center_id, center_xyz] : cell_pos_in_layer_) {
521 for (auto const& [probe_id, probe_xyz] : cell_pos_in_layer_) {
523 double dist = distance(probe_xyz, center_xyz);
524 if (dist > 1 * cell_r_min_ && dist <= 3. * cell_r_min_) {
525 nn_map_[center_id].push_back(probe_id);
526 } else if (dist > 3. * cell_r_min_ && dist <= 4.5 * cell_r_min_) {
527 nnn_map_[center_id].push_back(probe_id);
528 }
529 }
530 // Keeping this commented out until the SimIDs string method is implemented
531 // ldmx_log(debug) << " Found " << nn_map_[center_id].size() << " NN and "
532 // << nnn_map_[center_id].size() << " NNN for cell " << center_id;
533 }
534 return;
535}
536
538 // https://math.stackexchange.com/questions/1210572/find-the-distance-to-the-edge-of-a-hexagon
539 std::pair<double, double> cell_location = cell_pos_in_module_.at(id.cell());
540 double x = fabs(cell_location.first); // bring to first quadrant
541 double y = fabs(cell_location.second);
542 double r = sqrt(x * x + y * y);
543 double theta = (r > 1E-3) ? fabs(std::atan(y / x)) : 0;
544 if (x < module_r_max_ / 2.)
545 return (module_r_min_ -
546 y); // closest line is straight vertical to top edge
547 double dist =
548 sqrt(3.) * module_r_max_ / (std::sin(theta) + sqrt(3.) * std::cos(theta));
549 return dist;
550}
551
552bool EcalGeometry::isInside(double normX, double normY) const {
553 ldmx_log(trace) << std::fixed << std::setprecision(2)
554 << "Checking if normXY=(" << normX << "," << normY
555 << ") is inside.";
556 normX = fabs(normX), normY = fabs(normY);
557 double xvec = -1, yvec = -1. / sqrt(3);
558 double xref = 0.5, yref = sqrt(3) / 2.;
559 if ((normX > 1.) || (normY > yref)) {
560 ldmx_log(trace) << "They are outside quadrant.";
561 return false;
562 }
563 double dot_prod = (xvec * (normX - xref) + yvec * (normY - yref));
564 ldmx_log(trace) << std::fixed << std::setprecision(2)
565 << "They are inside quadrant. Dot product (>0 is inside): "
566 << dot_prod;
567 return (dot_prod > 0.);
568}
569
570} // namespace ldmx
Class that translates raw positions of ECal module hits into cells in a hexagonal readout.
Class encapsulating parameters for configuring a processor.
Definition Parameters.h:26
const T & get(const std::string &name) const
Retrieve the parameter of the given name.
Definition Parameters.h:75
Translation between real-space positions and cell IDs within the ECal.
std::map< EcalID, std::tuple< double, double, double > > cell_global_pos_
Position of cell centers relative to world geometry.
std::map< EcalID, std::vector< EcalID > > nnn_map_
Map of cell ID to neighbors of neighbor cells.
EcalID getID(double x, double y, double z, bool fallible=false) const
Get a cell's ID number from its position.
std::tuple< double, double, double > getPosition(EcalID id) const
Get a cell's position from its ID number.
void buildCellModuleMap()
Constructs the positions of all the cells in a layer relative to the ecal center.
double layer_shift_y_
shift of layers in the y-direction [mm]
double distanceToEdge(EcalID id) const
Distance to module edge, and whether cell is on edge of module.
std::pair< double, double > getPositionInModule(int cell_id) const
Get a cell's position within a module.
std::map< int, std::pair< double, double > > module_pos_xy_
Postion of module centers relative to the center of the layer in world coordinates.
void buildCellMap()
Constructs the flat-bottomed hexagonal grid (cellID) of corner-down hexagonal cells.
bool layer_shift_odd_
shift odd layers
double module_r_max_
Center-to-Corner Radius of module hexagon [mm].
double cell_r_min_
Center-to-Flat Radius of cell hexagon [mm].
std::map< int, std::tuple< double, double, double > > layer_pos_xy_
Position of layer centers in world coordinates (uses layer ID as key)
double n_cell_r_height_
Number of cell center-to-corner radii (one side of the cell) from the bottom to the top of the module...
std::map< int, std::pair< double, double > > cell_pos_in_module_
Position of cell centers relative to center of module in p,q space.
double layer_shift_x_
shift of layers in the x-direction [mm]
void buildModuleMap()
Constructs the positions of the seven modules (moduleID) relative to the ecal center.
std::map< EcalID, std::pair< double, double > > cell_pos_in_layer_
Position of cell centers relative to center of layer in world coordinates.
std::map< EcalID, std::vector< EcalID > > nn_map_
Map of cell ID to neighboring cells.
std::vector< double > layer_z_positions_
The layer Z postions are with respect to the front of the ECal [mm].
bool isInside(double normX, double normY) const
Determines if point (x,y), already normed to max hexagon radius, lies within a hexagon.
double si_thickness_
Thickness of the Si sensitive layer [mm].
bool layer_shift_odd_bilayer_
shift odd bi layers
void buildLayerMap()
Constructs the positions of the layers in world coordinates.
EcalGeometry(const framework::config::Parameters &ps)
Class constructor, for use only by the provider.
double cell_r_max_
Center-to-Corner Radius of cell hexagon [mm].
double ecal_front_z_
Front of ECal relative to world geometry [mm].
double gap_
Gap between module flat sides [mm].
double module_r_min_
Center-to-Flat Radius of module hexagon [mm].
TH2Poly cell_id_in_module_
Honeycomb Binning from ROOT.
void buildNeighborMaps()
Construts NNMap and NNNMap.
bool corners_side_up_
indicator of geometry orientation if true, flower shape's corners side (ie: side with two modules) is...
Extension of DetectorID providing access to ECal layers and cell numbers in a hex grid.
Definition EcalID.h:20
All classes in the ldmx-sw project use this namespace.