From 670ca618fa6b6558179985008effca996b26556a Mon Sep 17 00:00:00 2001 From: Dmitry Romanov <romanov@jlab.org> Date: Fri, 13 Aug 2021 18:40:52 +0000 Subject: [PATCH] Resolve "Native parametrization of Negative Hybrid Calorimeter" --- CMakeLists.txt | 9 +- compact/definitions.xml | 2 +- compact/ecal.xml | 3 +- compact/hybrid_ecal.xml | 73 +++++++++++ src/HybridCalorimeter_geo.cpp | 237 ++++++++++++++++++++++++++++++++++ 5 files changed, 320 insertions(+), 4 deletions(-) create mode 100644 compact/hybrid_ecal.xml create mode 100644 src/HybridCalorimeter_geo.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index bfef7ec7..854bd0b2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,10 +6,14 @@ PROJECT(athena ) set(CMAKE_CXX_STANDARD 17) -find_package( DD4hep REQUIRED COMPONENTS DDCore DDG4 ) +find_package( DD4hep REQUIRED COMPONENTS DDCore DDG4) find_package(Acts REQUIRED COMPONENTS Core PluginIdentification PluginTGeo PluginDD4hep ) +find_package(fmt) +#find_library(FMT_LIBRARY fmt) + + #----------------------------------------------------------------------------------- set(a_lib_name athena) @@ -28,6 +32,7 @@ dd4hep_add_plugin(${a_lib_name} SOURCES src/GaseousRICH_geo.cpp src/GeometryHelpers.cpp src/HomogeneousCalorimeter_geo.cpp + src/HybridCalorimeter_geo.cpp src/MRich_geo.cpp src/PolyhedraEndcapCalorimeter2_geo.cpp src/ShashlikCalorimeter_geo.cpp @@ -38,7 +43,7 @@ dd4hep_add_plugin(${a_lib_name} SOURCES USES ActsCore ActsPluginDD4hep ) target_link_libraries(${a_lib_name} - PUBLIC DD4hep::DDCore DD4hep::DDRec + PUBLIC DD4hep::DDCore DD4hep::DDRec fmt::fmt ) #----------------------------------------------------------------------------------- diff --git a/compact/definitions.xml b/compact/definitions.xml index 162f937e..9f96a84b 100644 --- a/compact/definitions.xml +++ b/compact/definitions.xml @@ -486,7 +486,7 @@ end of the solenoid coils. EcalEndcapP_rmin and EcalEndcapN_rmin need to be set in sync with the forward and backward detectors </documentation> <constant name="EcalEndcapP_rmin" value="200.0*mm"/> - <constant name="EcalEndcapN_rmin" value="300.0*mm"/> + <constant name="EcalEndcapN_rmin" value="100.0*mm"/> <constant name="HcalEndcapP_rmin" value="EcalEndcapP_rmin"/> <constant name="HcalEndcapN_rmin" value="EcalEndcapN_rmin"/> diff --git a/compact/ecal.xml b/compact/ecal.xml index de9ed8c6..4f54d205 100644 --- a/compact/ecal.xml +++ b/compact/ecal.xml @@ -11,7 +11,8 @@ <include ref="ci_ecal.xml"/> <!--<include ref="compact/ci_ecal_shashlik.xml"/>--> <!--<include ref="compact/ce_ecal.xml"/>--> - <include ref="ce_ecal_crystal_glass.xml"/> +<!-- <include ref="ce_ecal_crystal_glass.xml"/>--> + <include ref="hybrid_ecal.xml"/> <!-- <include ref="compact/ecal_barrel.xml"/> --> <!-- <include ref="compact/ecal_barrel_hybrid.xml"/> --> <include ref="ecal_barrel_interlayers.xml"/> diff --git a/compact/hybrid_ecal.xml b/compact/hybrid_ecal.xml new file mode 100644 index 00000000..d53ced04 --- /dev/null +++ b/compact/hybrid_ecal.xml @@ -0,0 +1,73 @@ +<lccdd> + <define> + <comment> + Transition area. + The idea behind this parametrization is that: + one glass module with its wrap is always + a size of 4 crystal modules with its wraps. + Then the transition area (where glass meets crystals) has no gaps + + +----------------+----------------+ + | +----+ +----+ | +----------+ | + | | | | | | | | | + | +----+ +----+ | | | | + | +----+ +----+ | | | | + | | | | | | | | | + | +----+ +----+ | +----------+ | + +----------------+----------------+ + crystal glass + + This implies that: + GlassModule_wrap = 2*CrystalModule_wrap + GlassModule_sx = 2*CrystalModule_sx + GlassModule_sy = 2*CrystalModule_sy + + </comment> + + <constant name="CrystalModule_width" value="20.00*mm"/> + <constant name="CrystalModule_length" value="200.00*mm"/> + <constant name="CrystalModule_wrap" value="0.50*mm"/> + <constant name="CrystalModule_z0" value="10.*cm"/> + + <constant name="GlassModule_width" value="2*CrystalModule_width"/> + <constant name="GlassModule_length" value="40.00*cm"/> + <constant name="GlassModule_wrap" value="2*CrystalModule_wrap"/> + <constant name="GlassModule_z0" value="0.0*cm"/> + + <constant name="EcalEndcapN_rmax" value="80*cm"/> + <constant name="EcalEndcapN_rmin" value="5*cm"/> + <constant name="EcalEndcapN_thickness" value="GlassModule_length"/> + <constant name="EcalEndcapN_z0" value="-EcalEndcapN_zmin - EcalEndcapN_thickness/2"/> + <constant name="EcalEndcapNCrystal_rmax" value="40*cm"/> + + <constant name="CrystalModule_distance" value="CrystalModule_width + CrystalModule_wrap"/> + <constant name="GlassModule_distance" value="GlassModule_width + GlassModule_wrap"/> + </define> + + <display> + <vis name="HybridEcalOuterVis" alpha="0.5" r= "0.3" g="0.3" b="0.3" showDaughters="true" visible="true"/> + </display> + + <detectors> + + <documentation level="10"> + #### Backwards Endcap EM Calorimeter + + Backwards Endcap EM Calorimeter, placements generated by script + </documentation> + <detector id="ECalEndcapN_ID" name="EcalEndcapN" type="HybridCalorimeter" readout="EcalEndcapNHits"> + <position x="0" y="0" z="EcalEndcapN_z0"/> + <rotation x="0" y="0" z="0"/> + </detector> + </detectors> + <readouts> + <comment>Effectively no segmentation, the segmentation is used to provide cell dimension info</comment> + <readout name="EcalEndcapNHits"> + <segmentation type="MultiSegmentation" key="sector"> + <segmentation name="CrystalSeg" key_value="1" type="CartesianGridXY" grid_size_x="CrystalModule_distance" grid_size_y="CrystalModule_distance"/> + <segmentation name="GlassSeg" key_value="2" type="CartesianGridXY" grid_size_x="GlassModule_distance" grid_size_y="GlassModule_distance"/> + </segmentation> + <id>system:8,sector:4,module:20,x:32:-16,y:-16</id> + </readout> + </readouts> +</lccdd> diff --git a/src/HybridCalorimeter_geo.cpp b/src/HybridCalorimeter_geo.cpp new file mode 100644 index 00000000..58869213 --- /dev/null +++ b/src/HybridCalorimeter_geo.cpp @@ -0,0 +1,237 @@ +//========================================================================== +// 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("PbGlass"); + auto crystal_material = desc.material("PbWO4"); + auto air_material = desc.material("Air"); + + double ROut = desc.constantAsDouble("EcalEndcapN_rmax"); + double RIn = desc.constantAsDouble("EcalEndcapN_rmin"); + double SizeZ = desc.constantAsDouble("EcalEndcapN_thickness"); + double glass_shift_z = desc.constantAsDouble("GlassModule_z0"); + double Thickness = desc.constantAsDouble("EcalEndcapN_thickness"); + double trans_radius = desc.constantAsDouble("EcalEndcapNCrystal_rmax"); + double Glass_ShiftZ = 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_shift_z = desc.constantAsDouble("CrystalModule_z0"); + + // RIn and ROut will define outer tube embedding the calorimeter + // centers_rin/out define the maximum radius of module centers + // so that modules are not overlapping with mother tube volume + double hypotenuse = sqrt(0.5 * glass_distance * glass_distance); + double centers_rin = RIn + hypotenuse + 1*mm; + double centers_rout = ROut - hypotenuse - 1*mm; + + // envelope + + // Assembly assembly(detName); + + Tube outer_tube(RIn, ROut, SizeZ / 2.0, 0., 360.0 * deg); + Volume ecal_vol("negative_ecal", outer_tube, air_material); + ecal_vol.setVisAttributes(desc.visAttributes("HybridEcalOuterVis")); + + double Glass_OuterR = ROut - 1 * cm ; + double Glass_InnerR = trans_radius; + glass_shift_z = Thickness / 2. - Glass_Thickness / 2.; + + // 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")); + + 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")); + + // GLASS + double diameter = 2 * Glass_OuterR; + + // How many towers do we have per row/columnt? + // Add a gap + diameter as if we have N towers, we have N-1 gaps; + int towersInRow = std::ceil((diameter + Glass_Gap) / (Glass_Width + Glass_Gap)); + + // 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; + +// fmt::print("\nCE EMCAL GLASS SQUARE START\n"); +// fmt::print("Glass_Thickness = {} cm;\n", Glass_Thickness / cm); +// fmt::print("Glass_Width = {} cm;\n", Glass_Width / cm); +// fmt::print("Glass_Gap = {} cm;\n", Glass_Gap / cm); +// fmt::print("Glass_InnerR = {} cm;\n", Glass_InnerR / cm); +// fmt::print("Glass_OuterR = {} cm;\n", Glass_OuterR / cm); +// fmt::print("Glass_PosZ = {} cm;\n", glass_shift_z / cm); +// fmt::print("Towers in Row/Col = {} cm;\n", glass_shift_z / cm); +// fmt::print("Top left tower pos = {:<10} {:<10} cm;\n", -leftTowerPos / cm, topTowerPos / cm); +// fmt::print("#Towers info:\n"); +// fmt::print("#{:<5} {:<6} {:<3} {:<3} {:>10} {:>10} {}\n", "idx", "code", "col", "row", "x", "y", "name"); + + + // We first do a "dry run", not really placing modules, + // but figuring out the ID scheme, number of modules, etc. + int glassModuleCount = 0; + int crystalModuleCount = 0; + int firstCrystRow = 1000000; // The first row, where crystals are started + int firstCrystCol = 1000000; // The fist column, where crystals are started + for(int rowIndex=0; rowIndex < towersInRow; rowIndex++) { + for(int colIndex=0; colIndex < towersInRow; colIndex++) { + double glass_x = leftTowerPos + colIndex * glass_distance; + double glass_y = topTowerPos + rowIndex * glass_distance; + double r = sqrt(glass_x * glass_x + glass_y * glass_y); + + if (r < centers_rout && r > centers_rin) { + // we are placing something + if(r<trans_radius) { + // 4 crystal modules will be placed + crystalModuleCount+=4; + + // Finding the first col and row where crystals start + // is the same algorithm as finding a minimum in array + if(colIndex<firstCrystCol) { + firstCrystCol = colIndex; + } + if(rowIndex<firstCrystRow) { + firstCrystRow = rowIndex; + } + } + else + { + // glass module will be places + glassModuleCount++; + } + } + } + } + // fmt::print("#Towers info:\n"); + // fmt::print("#{:<5} {:<6} {:<3} {:<3} {:>10} {:>10} {}\n", "idx", "code", "col", "row", "x", "y", "name"); + int glass_module_index = 0; + int cryst_module_index = 0; + for(int rowIndex=0; rowIndex < towersInRow; rowIndex++) { + for(int colIndex=0; colIndex < towersInRow; colIndex++) { + double glass_x = leftTowerPos + colIndex * (Glass_Width + Glass_Gap); + double glass_y = topTowerPos + rowIndex * (Glass_Width + Glass_Gap); + double r = sqrt(glass_x * glass_x + glass_y * glass_y); + + if (r < centers_rout && r > centers_rin) { + int code = 1000 * rowIndex + colIndex; + std::string name = fmt::format("ce_EMCAL_glass_phys_{}", code); + + if(r<trans_radius) { + + // first crystal module + double crystal_x = glass_x - crystal_distance / 2; + double crystal_y = glass_y - crystal_distance / 2; + auto placement = ecal_vol.placeVolume(crystal_module, Position(crystal_x, crystal_y, Crystal_shift_z)); + placement.addPhysVolID("sector", 1); + placement.addPhysVolID("module", cryst_module_index++); + + // second crystal module + crystal_x = glass_x + crystal_distance / 2; + crystal_y = glass_y - crystal_distance / 2; + placement = ecal_vol.placeVolume(crystal_module, Position(crystal_x, crystal_y, Crystal_shift_z)); + placement.addPhysVolID("sector", 1); + placement.addPhysVolID("module", cryst_module_index++); + + // third crystal module + crystal_x = glass_x - crystal_distance / 2; + crystal_y = glass_y + crystal_distance / 2; + placement = ecal_vol.placeVolume(crystal_module, Position(crystal_x, crystal_y, Crystal_shift_z)); + placement.addPhysVolID("sector", 1); + placement.addPhysVolID("module", cryst_module_index++); + + // forth crystal module + crystal_x = glass_x + crystal_distance / 2; + crystal_y = glass_y + crystal_distance / 2; + placement = ecal_vol.placeVolume(crystal_module, Position(crystal_x, crystal_y, Crystal_shift_z)); + placement.addPhysVolID("sector", 1); + placement.addPhysVolID("module", cryst_module_index++); + } + else + { + // glass module + auto placement = ecal_vol.placeVolume(glass_module, Position(glass_x, glass_y, glass_shift_z)); + placement.addPhysVolID("sector", 2); + placement.addPhysVolID("module", glass_module_index++); + } + + + // fmt::print(" {:<5} {:<6} {:<3} {:<3} {:>10.4f} {:>10.4f} {}\n", towerIndex, code, colIndex, rowIndex, x / cm, y / cm, name); + 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) + -- GitLab