Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-10 09:33:24

0001 // Copyright (C) 2023 Sebouh Paul
0002 // SPDX-License-Identifier: LGPL-3.0-or-later
0003 
0004 // References:
0005 //   https://arxiv.org/abs/2308.06939
0006 
0007 #include <DD4hep/Alignments.h>
0008 #include <DD4hep/DetElement.h>
0009 #include <DD4hep/Objects.h>
0010 #include <DD4hep/VolumeManager.h>
0011 #include <Evaluator/DD4hepUnits.h>
0012 #include <Math/GenVector/Cartesian3D.h>
0013 #include <Math/GenVector/DisplacementVector3D.h>
0014 #include <edm4hep/RawCalorimeterHit.h>
0015 #include <edm4hep/Vector3f.h>
0016 #include <algorithm>
0017 #include <cmath>
0018 #include <cstdlib>
0019 #include <numbers>
0020 #include <tuple>
0021 #include <vector>
0022 
0023 #include "HEXPLIT.h"
0024 #include "algorithms/calorimetry/HEXPLITConfig.h"
0025 
0026 namespace eicrecon {
0027 
0028 //positions where the overlapping cells are relative to a given cell (in units of hexagon side length)
0029 const std::vector<double> HEXPLIT::neighbor_offsets_x_H4 = []() {
0030   std::vector<double> x;
0031   double rs[2]      = {1.5, std::numbers::sqrt3 / 2.};
0032   double offsets[2] = {0, M_PI / 2};
0033   for (int i = 0; i < 2; i++) {
0034     for (int j = 0; j < 6; j += 1) {
0035       x.push_back(rs[i] * cos(j * M_PI / 3 + offsets[i]));
0036     }
0037   }
0038   return x;
0039 }();
0040 
0041 const std::vector<double> HEXPLIT::neighbor_offsets_y_H4 = []() {
0042   std::vector<double> y;
0043   double rs[2]      = {1.5, std::numbers::sqrt3 / 2.};
0044   double offsets[2] = {0, M_PI / 2};
0045   for (int i = 0; i < 2; i++) {
0046     for (int j = 0; j < 6; j += 1) {
0047       y.push_back(rs[i] * sin(j * M_PI / 3 + offsets[i]));
0048     }
0049   }
0050   return y;
0051 }();
0052 
0053 //indices of the neighboring cells which overlap to produce a given subcell
0054 const std::vector<std::vector<int>> HEXPLIT::neighbor_indices_H4 = {
0055     {0, 11, 10}, {1, 6, 11}, {2, 7, 6}, {3, 8, 7},  {4, 9, 8},   {5, 10, 9},
0056     {6, 11, 7},  {7, 6, 8},  {8, 7, 9}, {9, 8, 10}, {10, 9, 11}, {11, 10, 6}};
0057 
0058 //positions of the centers of subcells
0059 const std::vector<double> HEXPLIT::subcell_offsets_x_H4 = []() {
0060   std::vector<double> x;
0061   double rs[2]      = {0.75, std::numbers::sqrt3 / 4.};
0062   double offsets[2] = {0, M_PI / 2};
0063   for (int i = 0; i < 2; i++) {
0064     for (int j = 0; j < 6; j += 1) {
0065       x.push_back(rs[i] * cos(j * M_PI / 3 + offsets[i]));
0066     }
0067   }
0068   return x;
0069 }();
0070 
0071 const std::vector<double> HEXPLIT::subcell_offsets_y_H4 = []() {
0072   std::vector<double> y;
0073   double rs[2]      = {0.75, std::numbers::sqrt3 / 4.};
0074   double offsets[2] = {0, M_PI / 2};
0075   for (int i = 0; i < 2; i++) {
0076     for (int j = 0; j < 6; j += 1) {
0077       y.push_back(rs[i] * sin(j * M_PI / 3 + offsets[i]));
0078     }
0079   }
0080   return y;
0081 }();
0082 
0083 const std::vector<double> HEXPLIT::neighbor_offsets_x_S2 = {1, -1, -1, 1};
0084 const std::vector<double> HEXPLIT::neighbor_offsets_y_S2 = {1, 1, -1, -1};
0085 
0086 const std::vector<std::vector<int>> HEXPLIT::neighbor_indices_S2 = {{0}, {1}, {2}, {3}};
0087 
0088 const std::vector<double> HEXPLIT::subcell_offsets_x_S2 = {0.5, -0.5, -0.5, 0.5};
0089 const std::vector<double> HEXPLIT::subcell_offsets_y_S2 = {0.5, 0.5, -0.5, -0.5};
0090 
0091 void HEXPLIT::init() {
0092   if (m_cfg.stag_type == HEXPLITConfig::StaggerType::H4) {
0093     stag = stag_H4;
0094   } else if (m_cfg.stag_type == HEXPLITConfig::StaggerType::S2) {
0095     stag = stag_S2;
0096   } else if (m_cfg.stag_type == HEXPLITConfig::StaggerType::H3) {
0097     error("H3 staggering not implemented yet in EICrecon");
0098   }
0099 }
0100 
0101 void HEXPLIT::process(const HEXPLIT::Input& input, const HEXPLIT::Output& output) const {
0102 
0103   const auto [hits]  = input;
0104   auto [subcellHits] = output;
0105 
0106   double MIP   = m_cfg.MIP / dd4hep::GeV;
0107   double delta = m_cfg.delta_in_MIPs * MIP;
0108   double Emin  = m_cfg.Emin_in_MIPs * MIP;
0109 
0110   auto volman = m_detector->volumeManager();
0111 
0112   for (const auto& hit : *hits) {
0113     //skip hits that do not pass E and t cuts
0114     if (hit.getEnergy() < Emin) {
0115       continue;
0116     }
0117 
0118     //keep track of the energy in each neighboring cell
0119     std::vector<double> Eneighbors(stag.NEIGHBORS, 0.0);
0120 
0121     double sl = hit.getDimension().x / 2.;
0122     for (const auto& other_hit : *hits) {
0123       // maximum distance between where the neighboring cell is and where it should be
0124       // based on an ideal geometry using the staggered tessellation pattern.
0125       // Deviations could arise from rounding errors or from detector misalignment.
0126       double tol = 0.1; // in units of side lengths.
0127 
0128       //only look at hits nearby within two layers of the current layer
0129       int dz = std::abs(hit.getLayer() - other_hit.getLayer());
0130       if (dz > 2 || dz == 0) {
0131         continue;
0132       }
0133       if (other_hit.getEnergy() < Emin) {
0134         continue;
0135       }
0136       //difference in transverse position (in units of side lengths)
0137       double dx = (other_hit.getLocal().x - hit.getLocal().x) / sl;
0138       double dy = (other_hit.getLocal().y - hit.getLocal().y) / sl;
0139       if (std::abs(dx) > 2 || std::abs(dy) > std::numbers::sqrt3) {
0140         continue;
0141       }
0142 
0143       //loop over locations of the neighboring cells
0144       //and check if the jth hit matches this location
0145       for (int k = 0; k < stag.NEIGHBORS; k++) {
0146         if (std::abs(dx - stag.neighbor_offsets_x[k]) < tol &&
0147             std::abs(dy - stag.neighbor_offsets_y[k]) < tol) {
0148           Eneighbors[k] += other_hit.getEnergy();
0149           break;
0150         }
0151       }
0152     }
0153 
0154     std::vector<double> weights(stag.SUBCELLS, 0.0);
0155     for (int k = 0; k < stag.NEIGHBORS; k++) {
0156       Eneighbors[k] = std::max(Eneighbors[k], delta);
0157     }
0158     double sum_weights = 0;
0159     if (m_cfg.stag_type == HEXPLITConfig::StaggerType::H4)
0160       for (int k = 0; k < stag.SUBCELLS; k++) {
0161         weights[k] = Eneighbors[stag.neighbor_indices[k][0]] *
0162                      Eneighbors[stag.neighbor_indices[k][1]] *
0163                      Eneighbors[stag.neighbor_indices[k][2]];
0164         sum_weights += weights[k];
0165       }
0166     else if (m_cfg.stag_type == HEXPLITConfig::StaggerType::S2) {
0167       for (int k = 0; k < stag.SUBCELLS; k++) {
0168         weights[k] = Eneighbors[stag.neighbor_indices[k][0]];
0169         sum_weights += weights[k];
0170       }
0171     } else if (m_cfg.stag_type == HEXPLITConfig::StaggerType::H3) {
0172       for (int k = 0; k < stag.SUBCELLS; k++) {
0173         weights[k] =
0174             Eneighbors[stag.neighbor_indices[k][0]] * Eneighbors[stag.neighbor_indices[k][1]];
0175         sum_weights += weights[k];
0176       }
0177     }
0178     for (int k = 0; k < stag.SUBCELLS; k++) {
0179 
0180       //create the subcell hits.  First determine their positions in local coordinates.
0181       const decltype(edm4eic::CalorimeterHitData::local) local(
0182           hit.getLocal().x + stag.subcell_offsets_x[k] * sl,
0183           hit.getLocal().y + stag.subcell_offsets_y[k] * sl, hit.getLocal().z);
0184 
0185       //convert this to a position object so that the global position can be determined
0186       dd4hep::Position local_position;
0187       local_position.SetX(local.x * dd4hep::mm);
0188       local_position.SetY(local.y * dd4hep::mm);
0189       local_position.SetZ(local.z * dd4hep::mm);
0190 
0191       dd4hep::Position global_position;
0192       try {
0193 
0194         //also convert this to the detector's global coordinates.  To do: check if this is correct
0195         auto alignment = volman.lookupDetElement(hit.getCellID()).nominal();
0196 
0197         global_position = alignment.localToWorld(local_position);
0198 
0199       } catch (...) {
0200         // do this to prevent errors when running the test on the mock detector
0201         warning("Cannot find transformation from local to global coordinates.");
0202         global_position = local_position;
0203       }
0204 
0205       //convert this from position object to a vector object
0206       const decltype(edm4eic::CalorimeterHitData::position) position = {
0207           static_cast<float>(global_position.X() / dd4hep::mm),
0208           static_cast<float>(global_position.Y() / dd4hep::mm),
0209           static_cast<float>(global_position.Z() / dd4hep::mm)};
0210 
0211       //bounding box dimensions depend on the orientation of the rhombus
0212       int orientation = static_cast<int>(k % 3 == 0);
0213       const decltype(edm4eic::CalorimeterHitData::dimension) dimension(
0214           sl * (orientation != 0 ? 1 : 1.5),
0215           sl * std::numbers::sqrt3 / 2. * (orientation != 0 ? 2 : 1), hit.getDimension()[2]);
0216       auto subcell = subcellHits->create(
0217           hit.getCellID(), hit.getEnergy() * weights[k] / sum_weights, 0, hit.getTime(), 0,
0218           position, dimension, hit.getSector(), hit.getLayer(), local);
0219       if (hit.getRawHit().isAvailable()) {
0220         // Provenance matches the energy - all of a subcell's energy comes from the central hit.
0221         // Downstream truth-weighting is then exact for cleanly isolated clusters, and a
0222         // reasonable approximation when a cell's subcells are split across clusters.
0223         subcell.setRawHit(hit.getRawHit());
0224       }
0225     }
0226   }
0227 }
0228 
0229 } // namespace eicrecon