Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-05-30 08:05:09

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022 Chao Peng, Maria Zurek, Whitney Armstrong
0003 
0004 // Detector plugin to support a hybrid central barrel calorimeter
0005 // The detector consists of interlayers of Pb/ScFi (segmentation in global r, phi) and W/Si (segmentation in local x, y)
0006 // Assembly is used as the envelope so two different detectors can be interlayered with each other
0007 //
0008 //
0009 // 06/19/2021: Implementation of the Sci Fiber geometry. M. Żurek
0010 // 07/09/2021: Support interlayers between multiple detectors. C. Peng
0011 // 07/23/2021: Add assemblies as mother volumes of fibers to reduce the number of daughter volumes. C. Peng, M. Żurek
0012 //     Reference: TGeo performance issue with large number of daughter volumes
0013 //     https://indico.cern.ch/event/967418/contributions/4075358/attachments/2128099/3583278/201009_shKo_dd4hep.pdf
0014 // 07/24/2021: Changed support implementation to avoid too many uses of boolean geometries. DAWN view seems to have
0015 //     issue dealing with it. C. Peng
0016 
0017 #include "DD4hep/DetFactoryHelper.h"
0018 #include "Math/Point2D.h"
0019 #include "TGeoPolygon.h"
0020 #include "XML/Layering.h"
0021 #include "XML/Utilities.h"
0022 #include <functional>
0023 
0024 using namespace dd4hep;
0025 
0026 typedef ROOT::Math::XYPoint Point;
0027 // fiber placement helpers, defined below
0028 struct FiberGrid {
0029   int ix = 0, iy = 0;
0030   std::vector<Point> points;
0031   Point mean_centroid    = Point(0., 0.);
0032   Assembly* assembly_ptr = nullptr;
0033 
0034   // initialize with grid id and points
0035   FiberGrid(int i, int j, const std::vector<Point>& pts) : ix(i), iy(j), points(pts) {
0036     if (pts.empty()) {
0037       return;
0038     }
0039 
0040     double mx = 0., my = 0.;
0041     for (auto& p : pts) {
0042       mx += p.x();
0043       my += p.y();
0044     }
0045     mx /= static_cast<double>(pts.size());
0046     my /= static_cast<double>(pts.size());
0047     mean_centroid = Point(mx, my);
0048   };
0049 };
0050 
0051 std::vector<Point> fiberPositions(double r, double sx, double sz, double trx, double trz,
0052                                   double phi, bool shift_first = false, double stol = 1e-2);
0053 std::vector<FiberGrid> gridPoints(int div_n_phi, double div_dr, double x, double z, double phi);
0054 
0055 // geometry helpers
0056 void buildFibers(Detector& desc, SensitiveDetector& sens, Volume& mother, int layer_nunber,
0057                  xml_comp_t x_fiber, const std::tuple<double, double, double, double>& dimensions);
0058 void buildSupport(Detector& desc, Volume& mother, xml_comp_t x_support,
0059                   const std::tuple<double, double, double, double>& dimensions);
0060 
0061 // barrel ecal layers contained in an assembly
0062 static Ref_t create_detector(Detector& desc, xml_h e, SensitiveDetector sens) {
0063   Layering layering(e);
0064   xml_det_t x_det      = e;
0065   Material air         = desc.air();
0066   int det_id           = x_det.id();
0067   double offset        = x_det.attr<double>(_Unicode(offset));
0068   xml_comp_t x_dim     = x_det.dimensions();
0069   int nsides           = x_dim.numsides();
0070   double inner_r       = x_dim.rmin();
0071   double dphi          = (2 * M_PI / nsides);
0072   double hphi          = dphi / 2;
0073   std::string det_name = x_det.nameStr();
0074 
0075   DetElement sdet(det_name, det_id);
0076   Volume motherVol = desc.pickMotherVolume(sdet);
0077 
0078   // apply any detector type flags set in XML
0079   dd4hep::xml::setDetectorTypeFlag(x_det, sdet);
0080 
0081   Assembly envelope(det_name);
0082   Transform3D tr_global = Translation3D(0, 0, offset) * RotationZ(0);
0083   PlacedVolume env_phv  = motherVol.placeVolume(envelope, tr_global);
0084   sens.setType("calorimeter");
0085 
0086   env_phv.addPhysVolID("system", det_id);
0087   sdet.setPlacement(env_phv);
0088 
0089   // build a single sector
0090   DetElement sector_det("sector0", det_id);
0091   Assembly mod_vol("sector");
0092   sector_det.setTypeFlag(sdet.typeFlag()); // make sure type flags are propagated
0093 
0094   // keep tracking of the total thickness
0095   double l_pos_z = inner_r;
0096   { // =====  buildBarrelStave(desc, sens, module_volume) =====
0097     // Parameters for computing the layer X dimension:
0098     double tan_hphi = std::tan(hphi);
0099     double l_dim_y  = x_dim.z() / 2.;
0100 
0101     // Loop over the sets of layer elements in the detector.
0102     int l_num = 1;
0103     for (xml_coll_t li(x_det, _U(layer)); li; ++li) {
0104       xml_comp_t x_layer     = li;
0105       int repeat             = x_layer.repeat();
0106       double l_space_between = getAttrOrDefault(x_layer, _Unicode(space_between), 0.);
0107       double l_space_before  = getAttrOrDefault(x_layer, _Unicode(space_before), 0.);
0108       l_pos_z += l_space_before;
0109       // Loop over number of repeats for this layer.
0110       for (int j = 0; j < repeat; j++) {
0111         std::string l_name = Form("layer%d", l_num);
0112         double l_thickness = layering.layer(l_num - 1)->thickness(); // Layer's thickness.
0113         double l_dim_x     = tan_hphi * l_pos_z;
0114         l_pos_z += l_thickness;
0115 
0116         Position l_pos(0, 0, l_pos_z - l_thickness / 2.); // Position of the layer.
0117         double l_trd_x1 = l_dim_x;
0118         double l_trd_x2 = l_dim_x + l_thickness * tan_hphi;
0119         double l_trd_y1 = l_dim_y;
0120         double l_trd_y2 = l_trd_y1;
0121         double l_trd_z  = l_thickness / 2;
0122         Trapezoid l_shape(l_trd_x1, l_trd_x2, l_trd_y1, l_trd_y2, l_trd_z);
0123         Volume l_vol(l_name, l_shape, air);
0124         DetElement layer(sector_det, l_name, det_id);
0125         layer.setTypeFlag(sdet.typeFlag()); // make sure type flags are propagated
0126 
0127         // Loop over the sublayers or slices for this layer.
0128         int s_num      = 1;
0129         double s_pos_z = -(l_thickness / 2.);
0130         for (xml_coll_t si(x_layer, _U(slice)); si; ++si) {
0131           xml_comp_t x_slice = si;
0132           std::string s_name = Form("slice%d", s_num);
0133           double s_thick     = x_slice.thickness();
0134           double s_trd_x1    = l_dim_x + (s_pos_z + l_thickness / 2) * tan_hphi;
0135           double s_trd_x2    = l_dim_x + (s_pos_z + l_thickness / 2 + s_thick) * tan_hphi;
0136           double s_trd_y1    = l_trd_y1;
0137           double s_trd_y2    = s_trd_y1;
0138           double s_trd_z     = s_thick / 2.;
0139           Trapezoid s_shape(s_trd_x1, s_trd_x2, s_trd_y1, s_trd_y2, s_trd_z);
0140           Volume s_vol(s_name, s_shape, desc.material(x_slice.materialStr()));
0141           DetElement slice(layer, s_name, det_id);
0142           slice.setTypeFlag(sdet.typeFlag()); // make sure type flags are propagated
0143 
0144           // build fibers
0145           if (x_slice.hasChild(_Unicode(fiber))) {
0146             buildFibers(desc, sens, s_vol, l_num, x_slice.child(_Unicode(fiber)),
0147                         {s_trd_x1, s_thick, l_dim_y, hphi});
0148           }
0149 
0150           if (x_slice.isSensitive()) {
0151             s_vol.setSensitiveDetector(sens);
0152           }
0153           s_vol.setAttributes(desc, x_slice.regionStr(), x_slice.limitsStr(), x_slice.visStr());
0154 
0155           // Slice placement.
0156           PlacedVolume slice_phv = l_vol.placeVolume(s_vol, Position(0, 0, s_pos_z + s_thick / 2));
0157           slice_phv.addPhysVolID("slice", s_num);
0158           slice.setPlacement(slice_phv);
0159           // Increment Z position of slice.
0160           s_pos_z += s_thick;
0161           ++s_num;
0162         }
0163 
0164         // Set region, limitset, and vis of layer.
0165         l_vol.setAttributes(desc, x_layer.regionStr(), x_layer.limitsStr(), x_layer.visStr());
0166 
0167         PlacedVolume layer_phv = mod_vol.placeVolume(l_vol, l_pos);
0168         layer_phv.addPhysVolID("layer", l_num);
0169         layer.setPlacement(layer_phv);
0170         // Increment to next layer Z position. Do not add space_between for the last layer
0171         if (j < repeat - 1) {
0172           l_pos_z += l_space_between;
0173         }
0174         ++l_num;
0175       }
0176     }
0177   }
0178   // Phi start for a sector.
0179   double phi = M_PI / nsides;
0180   // Create nsides sectors.
0181   for (int i = 0; i < nsides; i++, phi -= dphi) { // i is module number
0182     // Compute the sector position
0183     Transform3D tr(RotationZYX(0, phi, M_PI * 0.5), Translation3D(0, 0, 0));
0184     PlacedVolume pv = envelope.placeVolume(mod_vol, tr);
0185     pv.addPhysVolID("sector", i + 1);
0186     DetElement sd = (i == 0) ? sector_det : sector_det.clone(Form("sector%d", i));
0187     sd.setTypeFlag(sdet.typeFlag()); // make sure type flags are propagated
0188     sd.setPlacement(pv);
0189     sdet.add(sd);
0190   }
0191 
0192   // optional sector attributes
0193   if (x_det.hasChild(_Unicode(sectors))) {
0194     xml_comp_t x_sectors = x_det.child(_Unicode(sectors));
0195     mod_vol.setVisAttributes(desc.visAttributes(x_sectors.visStr()));
0196   }
0197   if (x_det.hasChild(_U(support))) {
0198     buildSupport(desc, mod_vol, x_det.child(_U(support)), {inner_r, l_pos_z, x_dim.z(), hphi});
0199   }
0200 
0201   // Set envelope volume attributes.
0202   envelope.setAttributes(desc, x_det.regionStr(), x_det.limitsStr(), x_det.visStr());
0203   return sdet;
0204 }
0205 
0206 void buildFibers(Detector& desc, SensitiveDetector& sens, Volume& s_vol, int layer_number,
0207                  xml_comp_t x_fiber, const std::tuple<double, double, double, double>& dimensions) {
0208   auto [s_trd_x1, s_thick, s_length, hphi] = dimensions;
0209   double f_radius                          = getAttrOrDefault(x_fiber, _U(radius), 0.1 * cm);
0210   double f_cladding_thickness = getAttrOrDefault(x_fiber, _Unicode(cladding_thickness), 0.0 * cm);
0211   double f_spacing_x          = getAttrOrDefault(x_fiber, _Unicode(spacing_x), 0.122 * cm);
0212   double f_spacing_z          = getAttrOrDefault(x_fiber, _Unicode(spacing_z), 0.134 * cm);
0213   int grid_n_phi              = getAttrOrDefault(x_fiber, _Unicode(grid_n_phi), 5);
0214   double grid_dr              = getAttrOrDefault(x_fiber, _Unicode(grid_dr), 2.0 * cm);
0215   std::string f_id_grid = getAttrOrDefault<std::string>(x_fiber, _Unicode(identifier_grid), "grid");
0216   std::string f_id_fiber =
0217       getAttrOrDefault<std::string>(x_fiber, _Unicode(identifier_fiber), "fiber");
0218 
0219   // Set up the readout grid for the fiber layers
0220   // Trapezoid is divided into segments with equal dz and equal number of divisions in x
0221   // Every segment is a polygon that can be attached later to the lightguide
0222   // The grid size is assumed to be ~2x2 cm (starting values). This is to be larger than
0223   // SiPM chip (for GlueX 13mmx13mm: 4x4 grid 3mmx3mm with 3600 50×50 μm pixels each)
0224   // See, e.g., https://arxiv.org/abs/1801.03088 Fig. 2d
0225 
0226   // fiber and its cladding
0227   double f_radius_core = f_radius - f_cladding_thickness;
0228   Tube f_tube_clad(0, f_radius, s_length);
0229   Volume f_vol_clad("fiber_vol", f_tube_clad, desc.material(x_fiber.materialStr()));
0230   Tube f_tube_core(0, f_radius_core, s_length);
0231   Volume f_vol_core("fiber_core_vol", f_tube_core, desc.material(x_fiber.materialStr()));
0232   if (x_fiber.isSensitive()) {
0233     f_vol_core.setSensitiveDetector(sens);
0234   }
0235   f_vol_core.setAttributes(desc, x_fiber.regionStr(), x_fiber.limitsStr(), x_fiber.visStr());
0236   f_vol_clad.placeVolume(f_vol_core);
0237 
0238   // calculate polygonal grid coordinates (vertices)
0239   auto grids = gridPoints(grid_n_phi, grid_dr, s_trd_x1, s_thick, hphi);
0240   std::vector<int> f_id_count(grids.size(), 0);
0241   // use layer_number % 2 to add correct shifts for the adjacent fibers at layer boundary
0242   auto f_pos = fiberPositions(f_radius, f_spacing_x, f_spacing_z, s_trd_x1, s_thick, hphi,
0243                               (layer_number % 2 == 0));
0244   // a helper struct to speed up searching
0245   struct Fiber {
0246     Point pos;
0247     bool assigned = false;
0248     Fiber(const Point& p) : pos(p) {};
0249   };
0250   std::vector<Fiber> fibers(f_pos.begin(), f_pos.end());
0251 
0252   // build assembly for each grid and put fibers in
0253   for (auto& gr : grids) {
0254     Assembly grid_vol(Form("fiber_grid_%i_%i", gr.ix, gr.iy));
0255 
0256     // loop over all fibers that are not assigned to a grid
0257     int f_id = 1;
0258     for (auto& fi : fibers) {
0259       if (fi.assigned) {
0260         continue;
0261       }
0262 
0263       // use TGeoPolygon to help check if fiber is inside a grid
0264       TGeoPolygon poly(gr.points.size());
0265       std::vector<double> vx, vy;
0266       std::transform(gr.points.begin(), gr.points.end(), back_inserter(vx), std::mem_fn(&Point::x));
0267       std::transform(gr.points.begin(), gr.points.end(), back_inserter(vy), std::mem_fn(&Point::y));
0268       poly.SetXY(vx.data(), vy.data());
0269       poly.FinishPolygon();
0270 
0271       double f_xy[2] = {fi.pos.x(), fi.pos.y()};
0272       if (not poly.Contains(f_xy)) {
0273         continue;
0274       }
0275 
0276       // place fiber in grid
0277       auto p        = fi.pos - gr.mean_centroid;
0278       auto clad_phv = grid_vol.placeVolume(f_vol_clad, Position(p.x(), p.y(), 0.));
0279       clad_phv.addPhysVolID(f_id_fiber, f_id);
0280       fi.assigned = true;
0281       f_id++;
0282     }
0283 
0284     // only add this if this grid has fibers
0285     if (f_id > 1) {
0286       // fiber is along y-axis of the layer volume, so grids are arranged on X-Z plane
0287       Transform3D gr_tr(RotationZYX(0., 0., M_PI * 0.5),
0288                         Position(gr.mean_centroid.x(), 0., gr.mean_centroid.y()));
0289       auto grid_phv = s_vol.placeVolume(grid_vol, gr_tr);
0290       grid_phv.addPhysVolID(f_id_grid, gr.ix + gr.iy * grid_n_phi + 1);
0291       grid_vol.ptr()->Voxelize("");
0292     }
0293   }
0294 
0295   /*
0296   // sanity check
0297   size_t missing_fibers = 0;
0298   for (auto &fi : fibers) {
0299     if (not fi.assigned) {
0300       missing_fibers++;
0301     }
0302   }
0303   std::cout << "built " << fibers.size() << " fibers, "
0304             << missing_fibers << " of them failed to find a grid" << std::endl;
0305   */
0306 }
0307 
0308 // simple aluminum sheet cover
0309 // dimensions: (inner r, position in z, length, phi)
0310 void buildSupport(Detector& desc, Volume& mod_vol, xml_comp_t x_support,
0311                   const std::tuple<double, double, double, double>& dimensions) {
0312   auto [inner_r, pos_z, sector_length, hphi] = dimensions;
0313   double support_thickness = getAttrOrDefault(x_support, _Unicode(thickness), 3. * cm);
0314   auto material            = desc.material(x_support.materialStr());
0315   double trd_y             = sector_length / 2.;
0316   double trd_x1_support    = std::tan(hphi) * pos_z;
0317   double trd_x2_support    = std::tan(hphi) * (pos_z + support_thickness);
0318 
0319   Trapezoid s_shape(trd_x1_support, trd_x2_support, trd_y, trd_y, support_thickness / 2.);
0320   Volume s_vol("support_layer", s_shape, material);
0321   s_vol.setVisAttributes(desc.visAttributes(x_support.visStr()));
0322   mod_vol.placeVolume(s_vol, Position(0.0, 0.0, pos_z + support_thickness / 2.));
0323 }
0324 
0325 // Fill fiber lattice into trapezoid starting from position (0,0) in x-z coordinate system
0326 std::vector<Point> fiberPositions(double r, double sx, double sz, double trx, double trz,
0327                                   double phi, bool shift, double stol) {
0328   // r      - fiber radius
0329   // sx, sz - spacing between fibers in x, z
0330   // trx    - half-length of the shorter (bottom) base of the trapezoid
0331   // trz    - height of the trapezoid
0332   // phi    - angle between z and trapezoid arm
0333   // stol   - spacing tolerance
0334 
0335   std::vector<Point> positions;
0336   int z_layers = floor((trz / 2 - r - stol) / sz); // number of layers that fits in half trapezoid-z
0337 
0338   double px = 0., pz = 0.;
0339   int start_line = shift ? 1 : 0;
0340 
0341   for (int l = -z_layers; l < z_layers + 1; l++) {
0342     std::vector<Point> xline;
0343     pz           = l * sz;
0344     double x_max = trx + (trz / 2. + pz) * tan(phi) - stol; // calculate max x at particular z_pos
0345     (abs(l) % 2 == start_line) ? px = 0. : px = sx / 2;     // account for spacing/2 shift
0346 
0347     while (px < (x_max - r)) {
0348       xline.push_back(Point(px, pz));
0349       if (px != 0.)
0350         xline.push_back(Point(-px, pz)); // using symmetry around x=0
0351       px += sx;
0352     }
0353 
0354     // Sort fiber IDs for a better organization
0355     sort(xline.begin(), xline.end(),
0356          [](const Point& p1, const Point& p2) { return p1.x() < p2.x(); });
0357     positions.insert(positions.end(), xline.begin(), xline.end());
0358   }
0359   return positions;
0360 }
0361 
0362 // Determine the number of divisions for the readout grid for the fiber layers
0363 // Calculate dimensions of the polygonal grid
0364 std::vector<FiberGrid> gridPoints(int div_n_phi, double div_dr, double trd_x1, double height,
0365                                   double phi) {
0366   /*
0367   // TODO: move this test to xml file
0368   double SiPMsize = 13.0 * mm;
0369   double grid_min = SiPMsize + 3.0 * mm;
0370 
0371   if (dz < grid_min) {
0372     dz = grid_min;
0373   }
0374 
0375   if (dx < grid_min) {
0376     dx = grid_min;
0377   }
0378   */
0379   // number of divisions
0380   int nph = div_n_phi;
0381   int nr  = floor(height / div_dr);
0382   if (nr == 0) {
0383     nr++;
0384   }
0385 
0386   // grid vertices
0387   std::vector<FiberGrid> results;
0388   double dr = height / nr;
0389 
0390   for (int ir = 0; ir <= nr; ir++) {
0391     for (int iph = 0; iph <= nph; iph++) {
0392       double A_y = -height / 2. + ir * dr;
0393       double B_y = -height / 2. + (ir + 1) * dr;
0394 
0395       double botl_dr = 2 * (trd_x1 + ir * dr * tan(phi));
0396       double topl_dr = 2 * (trd_x1 + (ir + 1) * dr * tan(phi));
0397 
0398       double botl_dph_dr = botl_dr / nph;
0399       double topl_dph_dr = topl_dr / nph;
0400 
0401       double A_x = -botl_dr / 2. + iph * botl_dph_dr;
0402       double B_x = -topl_dr / 2. + iph * topl_dph_dr;
0403 
0404       double C_y = B_y;
0405       double D_y = A_y;
0406       double C_x = B_x + topl_dph_dr;
0407       double D_x = A_x + botl_dph_dr;
0408 
0409       auto A = Point(A_x, A_y);
0410       auto B = Point(B_x, B_y);
0411       auto C = Point(C_x, C_y);
0412       auto D = Point(D_x, D_y);
0413 
0414       // vertex points filled in the clock-wise direction
0415       results.emplace_back(FiberGrid(iph, ir, {A, B, C, D}));
0416     }
0417   }
0418 
0419   return results;
0420 }
0421 
0422 DECLARE_DETELEMENT(epic_EcalBarrelScFi, create_detector)