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