Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2025-01-18 09:15:58

0001 // SPDX-License-Identifier: LGPL-3.0-or-later
0002 // Copyright (C) 2022-2024 Whitney Armstrong, Nivedith Ramasubramanian, Yann Bedfer
0003 
0004 /** \addtogroup Trackers Trackers
0005  * \brief Type: **Barrel Cylinder MPGD with frames**.
0006  * \author Nivedith Ramasubramanian
0007  * Modified by M. Posik, Y. Bedfer
0008  *
0009  * \ingroup trackers
0010  *
0011  * @{
0012  */
0013 #include "DD4hep/DetFactoryHelper.h"
0014 #include "DD4hep/Printout.h"
0015 #include "DD4hep/Shapes.h"
0016 #include "DDRec/DetectorData.h"
0017 #include "DDRec/Surface.h"
0018 #include "XML/Layering.h"
0019 #include "XML/Utilities.h"
0020 #include <array>
0021 #include "DD4hepDetectorHelper.h"
0022 
0023 using namespace std;
0024 using namespace dd4hep;
0025 using namespace dd4hep::rec;
0026 using namespace dd4hep::detail;
0027 
0028 #include "Math/Vector2D.h"
0029 using ROOT::Math::XYVector;
0030 
0031 /** Micromegas Barrel Tracker with space frame
0032  *
0033  * - Designed to process "mpgd_barrel.xml".
0034  *
0035  * - Derived from "BarrelTrackerWithFrame_geo.cpp".
0036  *
0037  * - "support" tag not addressed.
0038  *
0039  * - "frame" tag within the module element.
0040  *
0041  * - Several models of modules, with each a distinct radius of curvature
0042  *  but a single XML <module> and a single <layer>.
0043  *
0044  * \code
0045  * \endcode
0046  *
0047  *
0048  * @author Yann Bedfer
0049  */
0050 static Ref_t create_MPGDCylinderBarrelTracker(Detector& description, xml_h e,
0051                                               SensitiveDetector sens) {
0052   xml_det_t x_det = e;
0053   Material air    = description.air();
0054   int det_id      = x_det.id();
0055   string det_name = x_det.nameStr();
0056   DetElement sdet(det_name, det_id);
0057 
0058   vector<Volume> volumes;
0059   vector<PlacedVolume> sensitives;
0060   vector<VolPlane> volplane_surfaces;
0061 
0062   PlacedVolume pv;
0063 
0064   //#define DEBUG_MPGDCylinderBarrelTracker
0065 #ifdef DEBUG_MPGDCylinderBarrelTracker
0066   // TEMPORARILY INCREASE VERBOSITY level for debugging purposes
0067   PrintLevel priorPrintLevel = printLevel();
0068   setPrintLevel(DEBUG);
0069 #endif
0070 
0071   // Set detector type flag
0072   dd4hep::xml::setDetectorTypeFlag(x_det, sdet);
0073   auto& params = DD4hepDetectorHelper::ensureExtension<dd4hep::rec::VariantParameters>(sdet);
0074 
0075   // Add the volume boundary material if configured
0076   for (xml_coll_t bmat(x_det, _Unicode(boundary_material)); bmat; ++bmat) {
0077     xml_comp_t x_boundary_material = bmat;
0078     DD4hepDetectorHelper::xmlToProtoSurfaceMaterial(x_boundary_material, params,
0079                                                     "boundary_material");
0080   }
0081 
0082   dd4hep::xml::Dimension dimensions(x_det.dimensions());
0083   Assembly assembly(det_name);
0084 
0085   sens.setType("tracker");
0086 
0087   // ********** MODULE
0088   // ***** ONE AND ONLY ONE MODULE
0089   xml_coll_t modules(x_det, _U(module));
0090   if (modules.size() != 1) {
0091     // Present detector constructor can only handle ONE <module> tag
0092     printout(ERROR, "MPGDCylinderBarrelTracker", "Number of modules = %u. Must be = 1",
0093              modules.size());
0094     throw runtime_error("Logics error in building modules.");
0095   }
0096   xml_comp_t x_mod   = modules;
0097   string m_nam       = x_mod.nameStr();
0098   double stave_width = dimensions.width(), det_length = dimensions.length();
0099   printout(DEBUG, "MPGDCylinderBarrelTracker", "Module \"%s\" width = %.2f cm length = %.2f cm",
0100            m_nam.c_str(), stave_width / cm, det_length / cm);
0101 
0102   // ********** LAYER
0103   // ***** ONE AND ONLY ONE LAYER
0104   xml_coll_t li(x_det, _U(layer));
0105   if (li.size() != 1) {
0106     printout(ERROR, "MPGDCylinderBarrelTracker", "Number of layers = %d. Must be = 1",
0107              (int)li.size());
0108     throw runtime_error("Logics error in building modules.");
0109   }
0110   // ***** RETRIEVE PARAMETERS
0111   xml_comp_t x_layer = li;
0112   int lay_id         = x_layer.id();
0113   if (x_layer.moduleStr() != m_nam) {
0114     printout(ERROR, "MPGDCylinderBarrelTracker", "Layer \"%s\" does not match module \"%s\"",
0115              x_layer.moduleStr().c_str(), m_nam.c_str());
0116     throw runtime_error("Logics error in building layer.");
0117   }
0118   xml_comp_t x_barrel  = x_layer.child(_U(barrel_envelope));
0119   double barrel_length = x_barrel.z_length();
0120   double barrel_z0     = getAttrOrDefault(x_barrel, _U(z0), 0.);
0121   // ***** LAYOUTS
0122   xml_comp_t x_layout = x_layer.child(_U(rphi_layout));
0123   double phi0         = x_layout.phi0();             // Starting phi of first module.
0124   xml_comp_t z_layout = x_layer.child(_U(z_layout)); // Get the <z_layout> element.
0125   double z_gap        = z_layout.gap();
0126   // ***** UNVALID LAYOUT PROPERTIES
0127   // # of staves (along phi) and sectors (along z) are now derived from stave
0128   // width and sector length. Used to be specified directly. In order to remind
0129   // the user this is no longer the case, let's forbid the use of the
0130   // corresponding (and a few more) tags.
0131   const int nUnvalids                     = 4;
0132   const xml::Tag_t unvalidTags[nUnvalids] = {_U(phi_tilt), _U(nphi), _U(rc), _U(dr)};
0133   for (int uv = 0; uv < nUnvalids; uv++) {
0134     if (x_barrel.hasChild(unvalidTags[uv])) {
0135       const string tag = _U(nphi);
0136       printout(ERROR, "MPGDCylinderBarrelTracker",
0137                "Layer \"%s\": Unvalid property \"%s\" in \"rphi_layout\"", m_nam.c_str(),
0138                tag.c_str());
0139       throw runtime_error("Logics error in building modules.");
0140     }
0141   }
0142 
0143   // ***** MODELS
0144   // Model = set of two radii of curvature used alternatingly for staves
0145   //       + an offset to be applied radially.
0146   // - The offset may be null if the two radii are different enough that
0147   //  staves adjacent along phi do not overlap.
0148   // - There needs to be 2, as of 2024/03: Inner and outer.
0149   // StaveModel = radius of curvature (=> "nphi", given "rmin")
0150   //            + offset
0151   // sector2Models[2][2]: Which model for [inner,outer][r1,r2]
0152   typedef struct {
0153     string name;
0154     double rmin;
0155     // "rsensor" is supposed to have been set equal to the radius specified in
0156     // the segmentation and is used here to double-check the consistency
0157     // between the stack of module components and that segmentation radius.
0158     double rsensor;
0159     int nphi;
0160     double offset;
0161     int io;
0162   } StaveModel;
0163   StaveModel staveModels[4];
0164   int sector2Models[2][2];
0165   xml_coll_t mi(x_mod, _U(model));
0166   if (mi.size() != 2) {
0167     printout(ERROR, "MPGDCylinderBarrelTracker", "Number of models = %d. Must be = 2",
0168              (int)mi.size());
0169     throw runtime_error("Logics error in building modules.");
0170   }
0171   int nStaveModels;
0172   for (nStaveModels = 0; mi; ++mi) {
0173     xml_comp_t x_model = mi;
0174     double rmin1 = x_model.rmin1(), rmin2 = x_model.rmin2();
0175     double rsensor = x_model.attr<double>(_Unicode(rsensor));
0176     // Determine "nphi" from stave width and radius
0177     int nphi  = int(2 * M_PI * rmin1 / stave_width + .5),
0178         nphi2 = int(2 * M_PI * rmin2 / stave_width + .5);
0179     if (nphi2 != nphi) {
0180       printout(ERROR, "MPGDCylinderBarrelTracker",
0181                "Model \"%s\": rmin1,2 = %.2f,%.2f cm are incompatible", x_model.nameStr().c_str(),
0182                rmin1, rmin2);
0183       throw runtime_error("Logics error in building modules.");
0184     }
0185     double offset          = x_model.offset();
0186     StaveModel& staveModel = staveModels[nStaveModels++];
0187     staveModel.rmin        = rmin1;
0188     staveModel.rsensor     = rsensor;
0189     staveModel.nphi        = nphi;
0190     staveModel.offset      = offset;
0191     int io;
0192     if (nStaveModels == 1) // 1st encountered "x_model" => ...
0193       io = 0;              // ...it's the inner one => io = 0.
0194     else
0195       io = 1;
0196     staveModel.io        = io;
0197     sector2Models[io][0] = nStaveModels - 1;
0198     if (fabs(rmin2 - rmin1) > 1.e6) {
0199       StaveModel& staveMode2 = staveModels[nStaveModels++];
0200       staveMode2.rmin        = rmin2;
0201       staveModel.rsensor     = rsensor + rmin2 - rmin1;
0202       staveModel.nphi        = nphi;
0203       staveMode2.offset      = offset;
0204       staveMode2.io          = io;
0205       staveModel.name        = x_model.nameStr() + string("_1");
0206       staveMode2.name        = x_model.nameStr() + string("_2");
0207     } else
0208       staveModel.name = x_model.nameStr();
0209     sector2Models[io][1] = nStaveModels - 1;
0210   }
0211   printout(DEBUG, "MPGDCylinderBarrelTracker", "%d Stave Models:", nStaveModels);
0212   for (int iSM = 0; iSM < nStaveModels; iSM++) {
0213     StaveModel& sM = staveModels[iSM];
0214     printout(DEBUG, "MPGDCylinderBarrelTracker",
0215              "Stave Model #%d \"%s\": rmin = %.2f cm offset = ±%.2f cm %s", iSM, sM.name.c_str(),
0216              sM.rmin, sM.offset / 2, sM.io ? "Outer" : "Inner");
0217   }
0218 
0219   // ***** FRAMES
0220   // There must be two:
0221   // - Outward frame (wider, because supporting connectors)
0222   // - Otherwise frame.
0223   // Widest is taken as outward frame.
0224   typedef struct {
0225     string name;
0226     double width;
0227     string material;
0228     string vis;
0229   } Frame;
0230   Frame frames[2];
0231   xml_coll_t fi(x_mod, _U(frame));
0232   if (fi.size() != 2) {
0233     printout(ERROR, "MPGDCylinderBarrelTracker", "Number of frames = %d. Must be = 2",
0234              (int)fi.size());
0235     throw runtime_error("Logics error in building modules.");
0236   }
0237   printout(DEBUG, "MPGDCylinderBarrelTracker", "2 Frames:");
0238   int iFr;
0239   for (iFr = 0; fi; ++fi, iFr++) {
0240     xml_comp_t x_frame = fi;
0241     string name        = x_frame.nameStr();
0242     double width       = x_frame.width();
0243     string material = x_frame.materialStr(), vis = x_frame.visStr();
0244     Frame& frame   = frames[iFr];
0245     frame.name     = name;
0246     frame.width    = width;
0247     frame.material = material;
0248     frame.vis      = vis;
0249   }
0250   if (frames[0].width < frames[1].width) // Outward = widest must be first
0251     swap(frames[0], frames[1]);
0252   for (iFr = 0; iFr < 2; iFr++) {
0253     Frame& frame = frames[iFr];
0254     printout(DEBUG, "MPGDCylinderBarrelTracker",
0255              "Frame #%d \"%s\": width = %.2f cm material = \"%s\" vis = \"%s\"", iFr,
0256              frame.name.c_str(), frame.width, frame.material.c_str(), frame.vis.c_str());
0257   }
0258 
0259   // ***** SERVICE
0260   xml_coll_t si(x_mod, _Unicode(service));
0261   if (si.size() != 1) {
0262     printout(ERROR, "MPGDCylinderBarrelTracker", "Number of services = %d. Must be = 1",
0263              (int)si.size());
0264     throw runtime_error("Logics error in building modules.");
0265   }
0266   xml_comp_t x_service     = si;
0267   double service_thickness = x_service.thickness();
0268   printout(DEBUG, "MPGDCylinderBarrelTracker",
0269            "1 Service \"%s\": thickness = %.4f cm, material = \"%s\"", x_service.nameStr().c_str(),
0270            service_thickness, x_service.materialStr().c_str());
0271 
0272   // ***** TOTAL THICKNESS from components (used to build frames)
0273   double total_thickness = 0;
0274   xml_coll_t ci(x_mod, _U(module_component));
0275   for (ci.reset(), total_thickness = 0.0; ci; ++ci) {
0276     const xml_comp_t x_comp = ci;
0277     printout(DEBUG, "MPGDCylinderBarrelTracker", "\"%s\": \t total_thickness %.4f cm",
0278              x_comp.nameStr().c_str(), total_thickness / cm);
0279     total_thickness += x_comp.thickness();
0280   }
0281   printout(DEBUG, "MPGDCylinderBarrelTracker", " => total_thickness %.4f cm", total_thickness / cm);
0282 
0283   // Now that we know total thickness, let's "printout" the characteristics
0284   // of the models (extrema, phi superposition).
0285   printout(DEBUG, "MPGDCylinderBarrelTracker", "2 Sector Models:");
0286   double outerPhiSuperpos = 0; // Later used to define phi range of service to inner
0287   for (int io = 0; io < 2; io++) {
0288     StaveModel& sM0 = staveModels[sector2Models[io][0]];
0289     StaveModel& sM1 = staveModels[sector2Models[io][1]];
0290     XYVector vOffset(sM0.offset / 2, 0);
0291     double phiEdge0 = stave_width / 2 / sM0.rmin;
0292     XYVector vEdge0(sM0.rmin * cos(phiEdge0), sM0.rmin * sin(phiEdge0));
0293     vEdge0 -= vOffset;
0294     // Module0: Stave model has smaller curvature than circle centred on
0295     // beam axis. => Minimum is in the middle.
0296     double RMn = sM0.rmin - sM0.offset / 2, Mag0 = sqrt(vEdge0.Mag2());
0297     double phiEdge1 = stave_width / 2 / sM1.rmin;
0298     XYVector vEdge1(sM1.rmin * cos(phiEdge1), sM1.rmin * sin(phiEdge1));
0299     vEdge1 += vOffset;
0300     // Module1: Stave model has larger curvature than circle centred on
0301     // beam axis. => Maximum is in the middle.
0302     double RMx = sM1.rmin + sM1.offset / 2, Mag1 = sqrt(vEdge1.Mag2());
0303     double dPhi = acos(vEdge0.Dot(vEdge1) / Mag0 / Mag1);
0304     RMx += total_thickness;
0305     if (io == 1) {
0306       RMn -= service_thickness; // Outer sector: acount for services to inner sector
0307       outerPhiSuperpos = dPhi;
0308     }
0309     printout(
0310         DEBUG, "MPGDCylinderBarrelTracker",
0311         "Sector Model #%d,\"%s\" = \"%s\"+\"%s\": phi overlap = %.1f deg, Extrema R = %.2f,%.2f",
0312         io, io ? "Outer" : "Inner", sM0.name.c_str(), sM1.name.c_str(), dPhi / M_PI * 180, RMn,
0313         RMx);
0314   }
0315 
0316   // ********** LOOP OVER STAVE MODELS
0317   double total_length = 0; // Total length including frames
0318   for (int iSM = 0; iSM < nStaveModels; iSM++) {
0319     // "sensor_number" = Bit field in cellID identifying the sensitive surface.
0320     // It is used to set up the MultiSegmentation discrimination scheme
0321     // catering for the distinct radii of the inner/outer sectors.
0322     int sensor_number      = iSM;
0323     StaveModel& staveModel = staveModels[iSM];
0324     // phi range, when excluding frames
0325     double stave_rmin = staveModel.rmin;
0326     double dphi       = stave_width / stave_rmin / 2;
0327     double phi_start = -dphi, phi_end = dphi;
0328 
0329     // ***** ASSEMBLY VOLUME: ONE PER STAVE MODEL
0330     Assembly m_vol(staveModel.name);
0331     volumes.push_back(m_vol);
0332     m_vol.setVisAttributes(description.visAttributes(x_mod.visStr()));
0333 
0334     // Stave frames
0335     double zthickness, stave_rmax = stave_rmin + total_thickness;
0336     Frame &out_frame = frames[0], &frame = frames[1];
0337     total_length = det_length + out_frame.width + frame.width;
0338     // Outward frame
0339     zthickness = out_frame.width;
0340     Tube frame_tube_O(stave_rmin, stave_rmax, zthickness / 2, phi_start, phi_end);
0341     Volume frame_vol_O(out_frame.name, frame_tube_O, description.material(out_frame.material));
0342     m_vol.placeVolume(frame_vol_O, Position(0, 0, -(total_length - zthickness) / 2));
0343     // Inward frame
0344     zthickness = frame.width; // Update "zthickness"
0345     Tube frame_tube_I(stave_rmin, stave_rmax, zthickness / 2, phi_start, phi_end);
0346     Volume frame_vol_I(frame.name, frame_tube_I, description.material(frame.material));
0347     m_vol.placeVolume(frame_vol_I, Position(0, 0, (total_length - zthickness) / 2));
0348 
0349     // Start/stop frames
0350     double frame_dphi =
0351         zthickness / stave_rmin; //converting the thickness of the frame to angular radians.
0352     Tube frame_tube_3(stave_rmin, stave_rmax, total_length / 2, phi_start - frame_dphi, phi_start);
0353     const string start_frame_nam("StartFrame");
0354     Volume frame_vol_3(start_frame_nam, frame_tube_3, description.material(frame.material));
0355     m_vol.placeVolume(frame_vol_3);
0356 
0357     Tube frame_tube_4(stave_rmin, stave_rmax, total_length / 2, phi_end, phi_end + frame_dphi);
0358     const string stop_frame_nam("StopFrame");
0359     Volume frame_vol_4(stop_frame_nam, frame_tube_4, description.material(frame.material));
0360     m_vol.placeVolume(frame_vol_4);
0361 
0362     frame_vol_O.setVisAttributes(description, out_frame.vis);
0363     frame_vol_I.setVisAttributes(description, frame.vis);
0364     frame_vol_3.setVisAttributes(description, frame.vis);
0365     frame_vol_4.setVisAttributes(description, frame.vis);
0366 
0367     // ***** OUTER SECTORS: SERVICES TO INNER
0368     // Don't know what the purpose of the "(inner|outer)_thickness" arg.s
0369     // to the surface volume (see infra) and whether "inner_thickness" has to
0370     // include the extra thickness corresponding to the services.
0371     // Note: The phi range of the service volume is set so that it's smaller
0372     // than that of the rest of components. This, in order to avoid the
0373     // addition of a thickness of service to the already thick superposition
0374     // of 2 module thicknesses (or module+frame) on the edge.
0375     if (staveModel.io == 1) {
0376       // Superposition along Z
0377       double outerPos  = barrel_length / 2 - total_length / 2;
0378       double innerPos  = (total_length + z_gap) / 2;
0379       double zSuperpos = total_length - outerPos + innerPos;
0380       // Parameters
0381       double serv_thickness = service_thickness;
0382       double serv_rmin      = stave_rmin - service_thickness;
0383       double serv_length    = total_length - zSuperpos;
0384       // phi range: stay away from phi overlap and frame, add 1mm margin
0385       double dPhi       = outerPhiSuperpos / 2 + (frame.width + .1) / stave_rmin;
0386       double serv_start = phi_start + dPhi, serv_stop = phi_end - dPhi;
0387       Tube c_tube(serv_rmin, serv_rmin + serv_thickness, serv_length / 2, serv_start, serv_stop);
0388       Volume c_vol(x_service.nameStr(), c_tube, description.material(x_service.materialStr()));
0389       pv = m_vol.placeVolume(c_vol, Position(0, 0, -zSuperpos / 2));
0390       c_vol.setRegion(description, x_service.regionStr());
0391       c_vol.setLimitSet(description, x_service.limitsStr());
0392       c_vol.setVisAttributes(description, x_service.visStr());
0393     }
0394 
0395     // ********** LOOP OVER COMPONENTS
0396     double comp_rmin          = stave_rmin;
0397     double thickness_so_far   = 0;
0398     xml_comp_t* sensitiveComp = 0;
0399     for (xml_coll_t mci(x_mod, _U(module_component)); mci; ++mci) {
0400       xml_comp_t x_comp     = mci;
0401       const string c_nam    = x_comp.nameStr();
0402       double comp_thickness = x_comp.thickness();
0403       Tube c_tube(comp_rmin, comp_rmin + comp_thickness, det_length / 2, phi_start, phi_end);
0404       Volume c_vol(c_nam, c_tube, description.material(x_comp.materialStr()));
0405       pv = m_vol.placeVolume(c_vol, Position(0, 0, (out_frame.width - frame.width) / 2));
0406       c_vol.setRegion(description, x_comp.regionStr());
0407       c_vol.setLimitSet(description, x_comp.limitsStr());
0408       c_vol.setVisAttributes(description, x_comp.visStr());
0409       if (x_comp.isSensitive()) {
0410         // ***** SENSITIVE VOLUME
0411         if (sensitiveComp) {
0412           printout(ERROR, "MPGDCylinderBarrelTracker",
0413                    "Component \"%s\": 2nd sensitive component in module \"%s\" (1st is \"%s\"). "
0414                    "One only allowed.",
0415                    c_nam.c_str(), m_nam.c_str(), sensitiveComp->nameStr().c_str());
0416           throw runtime_error("Logics error in building modules.");
0417         }
0418         sensitiveComp = &x_comp; // TODO: Add second sensitive
0419         pv.addPhysVolID("sensor", sensor_number);
0420         c_vol.setSensitiveDetector(sens);
0421         sensitives.push_back(pv);
0422 
0423         // -------- create a measurement plane for the tracking surface attached to the sensitive volume -----
0424         Vector3D u(-1., 0., 0.);
0425         Vector3D v(0., -1., 0.);
0426         Vector3D n(0., 0., 1.);
0427 
0428         // Compute the inner (i.e. thickness until mid-sensitive-volume) and
0429         //             outer (from mid-sensitive-volume to top)
0430         // thicknesses that need to be assigned to the tracking surface
0431         // depending on wether the support is above or below the sensor (!?)
0432         double inner_thickness = thickness_so_far + comp_thickness / 2;
0433         double outer_thickness = total_thickness - inner_thickness;
0434         // Consistency(+/-1um) check: segmentation = stack of module components
0435         double rXCheck = comp_rmin + comp_thickness / 2;
0436         if (fabs(staveModel.rsensor - rXCheck) > .0001 / cm) {
0437           printout(ERROR, "MPGDCylinderBarrelTracker",
0438                    "Sensitive Component \"%s\" of StaveModel #%d,\"%s\": rsensor(%.4f cm) != "
0439                    "radius @ sensitive surface(%.4f cm)",
0440                    iSM, c_nam.c_str(), staveModel.name.c_str(), staveModel.rsensor / cm,
0441                    rXCheck / cm);
0442           throw runtime_error("Logics error in building modules.");
0443         }
0444         printout(DEBUG, "MPGDCylinderBarrelTracker",
0445                  "Stave Model #%d,\"%s\": Sensitive surface @ R = %.4f (%.4f,%.4f) cm", iSM,
0446                  staveModel.name.c_str(), staveModel.rsensor / cm, inner_thickness / cm,
0447                  outer_thickness / cm);
0448 
0449         SurfaceType type(SurfaceType::Sensitive);
0450         VolPlane surf(c_vol, type, inner_thickness, outer_thickness, u, v, n); //,o ) ;
0451         volplane_surfaces.push_back(surf);
0452       }
0453       comp_rmin += comp_thickness;
0454       thickness_so_far += comp_thickness;
0455     } //end of module component loop
0456   } //end of stave model loop
0457 
0458   // ********** LAYER
0459   // ***** ENVELOPE
0460   string lay_nam = det_name + _toString(x_layer.id(), "_layer%d");
0461   Tube lay_tub(x_barrel.inner_r(), x_barrel.outer_r(), barrel_length / 2);
0462   Volume lay_vol(lay_nam, lay_tub, air); // Create the layer envelope volume.
0463   Position lay_pos(0, 0, barrel_z0);
0464   lay_vol.setVisAttributes(description.visAttributes(x_layer.visStr()));
0465   printout(DEBUG, "MPGDCylinderBarrelTracker",
0466            "Layer \"%s\": rmin,max = %.2f,%.2f cm 1/2length = %.2f cm", lay_nam.c_str(),
0467            x_barrel.inner_r(), x_barrel.outer_r(), barrel_length / 2);
0468 
0469   DetElement lay_elt(sdet, lay_nam, lay_id);
0470 
0471   // the local coordinate systems of modules in dd4hep and acts differ
0472   // see http://acts.web.cern.ch/ACTS/latest/doc/group__DD4hepPlugins.html
0473   auto& layerParams =
0474       DD4hepDetectorHelper::ensureExtension<dd4hep::rec::VariantParameters>(lay_elt);
0475 
0476   // ********** LOOP OVER THE SECTORS IN z
0477   // ***** SECTOR POSITIONS ALONG Z
0478   // These are the 4 central values in Z where the four sets of modules, called
0479   // sectors, will be placed.
0480   double modz_pos[4] = {-barrel_length / 2 + (total_length) / 2, -(total_length + z_gap) / 2,
0481                         +(total_length + z_gap) / 2, +barrel_length / 2 - (total_length) / 2};
0482   int nModules       = 0;
0483   for (int iz = 0; iz < 4; iz++) {
0484     int io                = (iz == 1 || iz == 2) ? 0 : 1;
0485     int iSMs[2]           = {sector2Models[io][0], sector2Models[io][1]};
0486     int iSM0              = iSMs[0];
0487     const StaveModel& sm0 = staveModels[iSM0];
0488     int nphi              = sm0.nphi;
0489     double offset         = sm0.offset;
0490     double phi_incr       = 2 * M_PI / nphi; // Phi increment for one module.
0491     // ***** LOOP OVER THE STAVES IN phi.
0492     int iphi;
0493     double phic;
0494     for (iphi = 0, phic = phi0; iphi < nphi; iphi++, phic += phi_incr, nModules++) {
0495       // Every other module...
0496       int jphi = iphi % 2, iV = iSMs[jphi];    // ...swap stave volume/sensitive
0497       double rc = (2 * jphi - 1) * offset / 2; // ...flip sign of offset
0498       if (iz >= 2)
0499         rc *= -1;    // Swap >0 and <0 offsets.
0500       double x1, y1; // Coordinates of the centre of curvature of
0501       x1                 = rc * std::cos(phic);
0502       y1                 = rc * std::sin(phic);
0503       string module_name = _toString(8 * iz + iphi, "module%02d");
0504       DetElement mod_elt(lay_elt, module_name, nModules);
0505       printout(DEBUG, "MPGDCylinderBarrelTracker",
0506                "System %d Layer \"%s\",id=%d Module \"%s\",id=%d: x,y,r: %7.4f,%7.4f,%7.4f cm",
0507                det_id, lay_nam.c_str(), lay_id, module_name.c_str(), nModules, x1 / cm, y1 / cm,
0508                sqrt(x1 * x1 + y1 * y1) / cm);
0509       RotationZYX rot;
0510       rot = RotationZYX(phic, 0, 0);
0511       if (iz >= 2) // Rotate so that outward-frame faces outwards.
0512         // Note: The product of rotations seems to have to be done in this
0513         // order (i.e. rotation by "phi" about Z times rotation by pi about Y.
0514         // This, for reason I don't fully understand. Anyway, a consequence of
0515         // that is that, in order to have iz=2,3 not out of phase w/ iz=0,1,
0516         // one has to swap >0 and <0 offsets (see instruction supra).
0517         rot *= RotationZYX(0, M_PI, 0);
0518       Transform3D tr(rot, Position(x1, y1, modz_pos[iz]));
0519       Volume& module_vol = volumes[iV];
0520       pv                 = lay_vol.placeVolume(module_vol, tr);
0521       pv.addPhysVolID("module", nModules);
0522       mod_elt.setPlacement(pv);
0523       // ***** SENSITIVE COMPONENT
0524       PlacedVolume& sens_pv = sensitives[iV];
0525       DetElement comp_de(
0526           mod_elt, std::string("de_") + sens_pv.volume().name() + _toString(8 * iz + iphi, "%02d"),
0527           nModules);
0528       comp_de.setPlacement(sens_pv);
0529       auto& comp_de_params =
0530           DD4hepDetectorHelper::ensureExtension<dd4hep::rec::VariantParameters>(comp_de);
0531       comp_de_params.set<string>("axis_definitions", "XYZ");
0532       volSurfaceList(comp_de)->push_back(volplane_surfaces[iV]);
0533     }
0534   }
0535 
0536   for (xml_coll_t lmat(x_layer, _Unicode(layer_material)); lmat; ++lmat) {
0537     xml_comp_t x_layer_material = lmat;
0538     DD4hepDetectorHelper::xmlToProtoSurfaceMaterial(x_layer_material, layerParams,
0539                                                     "layer_material");
0540   }
0541 
0542   // ***** CREATE THE PhysicalVolume FOR THE LAYER.
0543   pv = assembly.placeVolume(lay_vol, lay_pos); // Place layer in mother
0544   pv.addPhysVolID("layer", lay_id);            // Set the layer ID.
0545   lay_elt.setAttributes(description, lay_vol, x_layer.regionStr(), x_layer.limitsStr(),
0546                         x_layer.visStr());
0547   lay_elt.setPlacement(pv);
0548 
0549   sdet.setAttributes(description, assembly, x_det.regionStr(), x_det.limitsStr(), x_det.visStr());
0550   assembly.setVisAttributes(description.invisible());
0551   pv = description.pickMotherVolume(sdet).placeVolume(assembly);
0552   pv.addPhysVolID("system", det_id); // Set the subdetector system ID.
0553   sdet.setPlacement(pv);
0554 
0555 #ifdef DEBUG_MPGDCylinderBarrelTracker
0556   // Reset initial print level before exiting
0557   setPrintLevel(priorPrintLevel);
0558 #endif
0559 
0560   return sdet;
0561 }
0562 
0563 //@}
0564 // clang-format off
0565 DECLARE_DETELEMENT(epic_CylinderMPGDBarrel, create_MPGDCylinderBarrelTracker)