Skip to content
Snippets Groups Projects
HybridCalorimeter_geo.cpp 8.77 KiB
Newer Older
  • Learn to ignore specific revisions
  • //==========================================================================
    //  A general implementation for homogeneous calorimeter
    //  it supports three types of placements
    //  1. Individual module placement with module dimensions and positions
    //  2. Array placement with module dimensions and numbers of row and columns
    //  3. Disk placement with module dimensions and (Rmin, Rmax), and (Phimin, Phimax)
    //  4. Lines placement with module dimensions and (mirrorx, mirrory)
    //     (NOTE: anchor point is the 0th block of the line instead of line center)
    //--------------------------------------------------------------------------
    //  Author: Chao Peng (ANL)
    //  Date: 06/09/2021
    //==========================================================================
    
    #include "GeometryHelpers.h"
    #include "DD4hep/DetFactoryHelper.h"
    #include <XML/Helper.h>
    #include <iostream>
    #include <algorithm>
    #include <tuple>
    #include <math.h>
    #include <fmt/core.h>
    
    using namespace dd4hep;
    
    // main
    static Ref_t create_detector(Detector& desc, xml::Handle_t handle, SensitiveDetector sens)
    {
    
      using namespace std;
      using namespace fmt;
    
        xml::DetElement detElem = handle;
        std::string detName = detElem.nameStr();
        int detID = detElem.id();
        DetElement det(detName, detID);
        sens.setType("calorimeter");
    
    
        auto glass_material = desc.material("SciGlass");
    
        auto crystal_material = desc.material("PbWO4");
        auto air_material = desc.material("Air");
    
        double ROut = desc.constantAsDouble("EcalEndcapN_rmax");
    
        double RIn_el = desc.constantAsDouble("EcalEndcapN_rmin1");
        double ionCutout_dphi = desc.constantAsDouble("EcalEndcapNIonCutout_dphi");
        double RIn = desc.constantAsDouble("EcalEndcapN_rmin2");
    
        double SizeZ = desc.constantAsDouble("EcalEndcapN_thickness");
    
        double thickness = desc.constantAsDouble("EcalEndcapN_thickness");
    
        double trans_radius = desc.constantAsDouble("EcalEndcapNCrystal_rmax");
    
        double Glass_z0 = desc.constantAsDouble("GlassModule_z0");
    
        double Glass_Width = desc.constantAsDouble("GlassModule_width");
    
        double Glass_thickness = desc.constantAsDouble("GlassModule_length");
    
        double Glass_Gap = desc.constantAsDouble("GlassModule_wrap");
        double glass_distance = desc.constantAsDouble("GlassModule_distance");
    
        double Crystal_Width = desc.constantAsDouble("CrystalModule_width");
    
        double Crystal_thickness = desc.constantAsDouble("CrystalModule_length");
    
        double Crystal_Gap = desc.constantAsDouble("CrystalModule_wrap");
        double crystal_distance = desc.constantAsDouble("CrystalModule_distance");
    
        double Crystal_z0 = desc.constantAsDouble("CrystalModule_z0");
    
    
        // RIn and ROut will define outer tube embedding the calorimeter
    
        // centers_rmin/out define the maximum radius of module centers
    
        // so that modules are not overlapping with mother tube volume
    
        const double glassHypotenuse = std::hypot(glass_distance, glass_distance)/2;
        const double crystalHypotenuse = glassHypotenuse/2;
        // Offset these values a bit so we don't miss edge-blocks
        const double glassCenters_rmax  = ROut - glassHypotenuse + 1 * mm;
        const double crystalCenters_rmin = RIn + crystalHypotenuse - 1 * mm;
        // Also limits of the inner crystal blocks fill
        const double cutout_tan = tan(ionCutout_dphi/2);
        const double cutout_rmin = RIn_el + crystalHypotenuse - 1 * mm;
    
        // Offset to align the modules at the zmin of the endcap,
        const double Crystal_offset = -0.5 * (Crystal_thickness - thickness);
    
        const double Glass_offset = -0.5*(Glass_thickness - thickness);
    
    
        // consists of an glass tube of the full thickness, and a crystal inner tube
        // for the crystal that allows us to get closet to the beampipe without
        // overlaps, and a partial electron tube that allows us to get closer to the
        // electron beampipe in the region where there is no ion beampipe
        Tube glass_tube(min(RIn + glassHypotenuse*2, trans_radius), ROut, SizeZ / 2.0, 0., 360.0 * deg);
        Tube crystal_tube(RIn, min(RIn + glassHypotenuse*2, trans_radius), Crystal_thickness/ 2.0, 0., 360.0 * deg);
        Tube electron_tube(RIn_el, RIn, Crystal_thickness / 2., ionCutout_dphi / 2., 360.0 * deg - ionCutout_dphi / 2);
        UnionSolid outer_envelope(glass_tube, crystal_tube, Position(0,0,Crystal_offset));
        UnionSolid envelope(outer_envelope, electron_tube, Position(0,0,Crystal_offset));
        Volume ecal_vol("negative_ecal", envelope, air_material);
    
        ecal_vol.setVisAttributes(desc.visAttributes("HybridEcalOuterVis"));
    
    
        double Glass_OuterR = ROut - 1 * cm ;
        double Glass_InnerR = trans_radius;
    
        // Geometry of modules
    
        Box glass_box("glass_box", Glass_Width * 0.5, Glass_Width * 0.5, Glass_thickness * 0.5);
    
        Volume glass_module("glass_module", glass_box, glass_material);
        glass_module.setVisAttributes(desc.visAttributes("EcalEndcapNModuleVis"));
    
        glass_module.setSensitiveDetector(sens);
    
        Box crystal_box("crystal_box",  Crystal_Width* 0.5, Crystal_Width * 0.5, Crystal_thickness * 0.5);
    
        Volume crystal_module("crystal_module", crystal_box, crystal_material);
        crystal_module.setVisAttributes(desc.visAttributes("EcalEndcapNModuleVis"));
    
        crystal_module.setSensitiveDetector(sens);
    
    
        // GLASS
        double diameter = 2 * Glass_OuterR;
    
    
        // Can we fit an even or odd amount of glass blocks within our rmax?
        // This determines the transition points between crystal and glass as we need the
        // outer crystal arangement to be in multiples of 2 (aligned with glass)
        const int towersInRow = std::ceil((diameter + Glass_Gap) /  (Glass_Width + Glass_Gap));
        const bool align_even = (towersInRow % 2);
    
    
        // Is it odd or even number of towersInRow
        double leftTowerPos, topTowerPos;
        if(towersInRow%2) {
          //             |
          //      [ ][ ][ ][ ][ ]
          //       ^     |
          int towersInHalfRow = std::ceil(towersInRow/2.0);
          topTowerPos = leftTowerPos = -towersInHalfRow * (Glass_Width + Glass_Gap);
    
        } else {
          //               |
          //      [ ][ ][ ][ ][ ][ ]
          //       ^      |
          int towersInHalfRow = towersInRow/2;
          topTowerPos = leftTowerPos = -(towersInHalfRow - 0.5) * (Glass_Width + Glass_Gap);
        }
    
        int moduleIndex = 0;
    
        int glass_module_index = 0;
        int cryst_module_index = 0;
        for(int rowIndex=0; rowIndex < towersInRow; rowIndex++) {
          for(int colIndex=0; colIndex < towersInRow; colIndex++) {
    
            const double glass_x = leftTowerPos + colIndex * (Glass_Width + Glass_Gap);
            const double glass_y = topTowerPos + rowIndex * (Glass_Width + Glass_Gap);
            const double r = std::hypot(glass_x, glass_y);
            // crystal if within the transition radius (as defined by the equivalent glass
            // block)
            if (r < trans_radius) {
              for (const auto dx : {-1, 1}) {
                for (const auto& dy : {-1, 1}) {
                  const double crystal_x = glass_x + dx * crystal_distance / 2;
                  const double crystal_y = glass_y + dy * crystal_distance / 2;
                  const double crystal_r = std::hypot(crystal_x, crystal_y);
                  // check if crystal in the main crystal ring?
                  const bool mainRing = (crystal_r > crystalCenters_rmin);
                  const bool innerRing = !mainRing && crystal_r > cutout_rmin;
                  const bool ionCutout = crystal_x > 0 && fabs(crystal_y / crystal_x) < cutout_tan;
                  if (mainRing || (innerRing && !ionCutout)) {
                    auto placement =
                        ecal_vol.placeVolume(crystal_module, Position(crystal_x, crystal_y, Crystal_z0 + Crystal_offset));
                    placement.addPhysVolID("sector", 1);
                    placement.addPhysVolID("module", cryst_module_index++);
                  }
                }
    
            // Glass block if within the rmax
            } else if (r < glassCenters_rmax) {
              // glass module
              auto placement = ecal_vol.placeVolume(glass_module, Position(glass_x, glass_y, Glass_z0 + Glass_offset));
              placement.addPhysVolID("sector", 2);
              placement.addPhysVolID("module", glass_module_index++);
    
    Whitney Armstrong's avatar
    Whitney Armstrong committed
    
        desc.add(Constant("EcalEndcapN_NModules_Sector1", std::to_string(cryst_module_index)));
        desc.add(Constant("EcalEndcapN_NModules_Sector2", std::to_string(glass_module_index)));
    
    //    fmt::print("Total Glass modules: {}\n", towerIndex);
    //    fmt::print("CE EMCAL GLASS END\n\n");
    
         // detector position and rotation
         auto pos = detElem.position();
         auto rot = detElem.rotation();
         Volume motherVol = desc.pickMotherVolume(det);
         Transform3D tr(RotationZYX(rot.z(), rot.y(), rot.x()), Position(pos.x(), pos.y(), pos.z()));
         PlacedVolume envPV = motherVol.placeVolume(ecal_vol, tr);
         envPV.addPhysVolID("system", detID);
         det.setPlacement(envPV);
         return det;
    
    }
    
    //@}
    DECLARE_DETELEMENT(HybridCalorimeter, create_detector)