-
Sylvester Joosten authoredSylvester Joosten authored
TopologicalCellCluster.cpp 9.52 KiB
/*
* Topological Cell Clustering Algorithm for Sampling Calorimetry
* 1. group all the adjacent modules
* 2. TODO split local maxima (seems no need for Imaging Calorimeter with extremely fine
* granularity)
*
* Author: Chao Peng (ANL), 04/06/2021
* References: https://arxiv.org/pdf/1603.02934.pdf
*
*/
#include "fmt/format.h"
#include <algorithm>
#include "Gaudi/Property.h"
#include "GaudiAlg/GaudiAlgorithm.h"
#include "GaudiAlg/GaudiTool.h"
#include "GaudiAlg/Transformer.h"
#include "GaudiKernel/PhysicalConstants.h"
#include "GaudiKernel/RndmGenerators.h"
#include "GaudiKernel/ToolHandle.h"
#include "DD4hep/BitFieldCoder.h"
#include "DDRec/CellIDPositionConverter.h"
#include "DDRec/Surface.h"
#include "DDRec/SurfaceManager.h"
// FCCSW
#include "JugBase/DataHandle.h"
#include "JugBase/IGeoSvc.h"
// Event Model related classes
#include "eicd/CalorimeterHitCollection.h"
#include "eicd/ClusterCollection.h"
using namespace Gaudi::Units;
namespace Jug::Reco {
/** Topological Cell Clustering Algorithm for Sampling Calorimetry.
*
* 1. group all the adjacent modules
* 2. TODO split local maxima (seems no need for Imaging Calorimeter with extremely fine
* granularity)
*
* \ingroup reco
*/
class TopologicalCellCluster : public GaudiAlgorithm {
public:
// maximum difference in layer numbers that can be considered as neighbours
Gaudi::Property<int> m_adjLayerDiff{this, "adjLayerDiff", 1};
// geometry service name
Gaudi::Property<std::string> m_geoSvcName{this, "geoServiceName", "GeoSvc"};
// name of readout class
Gaudi::Property<std::string> m_readout{this, "readoutClass", ""};
// name of layer field in readout
Gaudi::Property<std::string> m_layerField{this, "layerField", "layer"};
// name of sector field in readout
Gaudi::Property<std::string> m_sectorField{this, "sectorField", "sector"};
// maximum distance of local (x, y) to be considered as neighbors at the same layer
Gaudi::Property<std::vector<double>> u_localRanges{this, "localRanges", {1.0 * mm, 1.0 * mm}};
// maximum distance of global (eta, phi) to be considered as neighbors at different layers
Gaudi::Property<std::vector<double>> u_adjLayerRanges{
this, "adjLayerRanges", {0.01 * M_PI, 0.01 * M_PI}};
// maximum global distance to be considered as neighbors in different sectors
Gaudi::Property<double> m_adjSectorDist{this, "adjSectorDist", 1.0 * cm};
// minimum cluster center energy (to be considered as a seed for cluster)
// @TODO One can not simply find a center by edep with extremely fine granularity.
// Projecting to (eta, phi) with crude pixel size may help determine the center edep, which can
// happen in the following reconstruction step
Gaudi::Property<double> m_minClusterCenterEdep{this, "minClusterCenterEdep", 50 * keV};
// input hits collection
DataHandle<eic::CalorimeterHitCollection> m_inputHitCollection{"inputHitCollection",
Gaudi::DataHandle::Reader, this};
// output cluster collection
DataHandle<eic::ClusterCollection> m_outputClusterCollection{"outputClusterCollection",
Gaudi::DataHandle::Writer, this};
// output split hits collection
// @TODO not implemented, as with extreme fine granularity, there is no need to split hits
// DataHandle<eic::CalorimeterHitCollection>
// m_splitHitCollection{"splitHitCollection", Gaudi::DataHandle::Writer, this};
SmartIF<IGeoSvc> m_geoSvc;
// visit readout fields
dd4hep::BitFieldCoder* id_dec;
size_t sector_idx, layer_idx;
// ill-formed: using GaudiAlgorithm::GaudiAlgorithm;
TopologicalCellCluster(const std::string& name, ISvcLocator* svcLoc)
: GaudiAlgorithm(name, svcLoc)
{
declareProperty("inputHitCollection", m_inputHitCollection, "");
declareProperty("outputClusterCollection", m_outputClusterCollection, "");
}
StatusCode initialize() override
{
if (GaudiAlgorithm::initialize().isFailure()) {
return StatusCode::FAILURE;
}
// check local clustering range
if (u_localRanges.size() < 2) {
error() << "Need 2-dimensional ranges for same-layer clustering, only have "
<< u_localRanges.size() << endmsg;
return StatusCode::FAILURE;
}
info() << "Same layer hits group ranges"
<< " (" << u_localRanges[0] / mm << " mm, " << u_localRanges[1] / mm << " mm)"
<< endmsg;
// check adjacent layer clustering range
if (u_adjLayerRanges.size() < 2) {
error() << "Need 2-dimensional ranges for adjacent-layer clustering, only have "
<< u_adjLayerRanges.size() << endmsg;
return StatusCode::FAILURE;
}
info() << "Same layer hits group ranges"
<< " (" << u_adjLayerRanges[0] / M_PI * 1000. << " mrad, "
<< u_adjLayerRanges[1] / M_PI * 1000. << " mrad)" << endmsg;
// check geometry service
m_geoSvc = service(m_geoSvcName);
if (!m_geoSvc) {
error() << "Unable to locate Geometry Service. "
<< "Make sure you have GeoSvc and SimSvc in the right order in the configuration."
<< endmsg;
return StatusCode::FAILURE;
}
if (m_readout.value().empty()) {
error() << "readoutClass is not provided, it is needed to know the fields in readout ids"
<< endmsg;
return StatusCode::FAILURE;
}
try {
id_dec = m_geoSvc->detector()->readout(m_readout).idSpec().decoder();
sector_idx = id_dec->index(m_sectorField);
layer_idx = id_dec->index(m_layerField);
} catch (...) {
error() << "Failed to load ID decoder for " << m_readout << endmsg;
return StatusCode::FAILURE;
}
return StatusCode::SUCCESS;
}
StatusCode execute() override
{
// input collections
const auto& hits = *m_inputHitCollection.get();
// Create output collections
auto& clusters = *m_outputClusterCollection.createAndPut();
// group neighboring hits
std::vector<bool> visits(hits.size(), false);
std::vector<std::vector<eic::CalorimeterHit>> groups;
for (size_t i = 0; i < hits.size(); ++i) {
// already in a group, or not energetic enough to form a cluster
if (visits[i] || hits[i].energy() < m_minClusterCenterEdep) {
continue;
}
groups.emplace_back();
// create a new group, and group all the neighboring hits
dfs_group(groups.back(), i, hits, visits);
}
debug() << "we have " << groups.size() << " groups of hits" << endmsg;
for (const auto& group : groups) {
auto cl = clusters.create();
}
return StatusCode::SUCCESS;
}
private:
template <typename T>
static inline T pow2(const T& x)
{
return x * x;
}
// helper function to group hits
bool is_neighbor(const eic::ConstCalorimeterHit& h1, const eic::ConstCalorimeterHit& h2) const
{
// we will merge different sectors later using global positions
int s1 = id_dec->get(h1.cellID(), sector_idx);
int s2 = id_dec->get(h2.cellID(), sector_idx);
if (s1 != s2) {
return std::sqrt(pow2(h1.position().x - h2.position().x) + pow2(h1.position().y - h2.position().y) + pow2(h1.position().z - h2.position().z)) <=
m_adjSectorDist;
}
int l1 = id_dec->get(h1.cellID(), layer_idx);
int l2 = id_dec->get(h2.cellID(), layer_idx);
// layer check
int ldiff = std::abs(l1 - l2);
// same layer, check local positions
if (!ldiff) {
return (std::abs(h1.local().local_x - h2.local().local_x) <= u_localRanges[0]) &&
(std::abs(h1.local().local_y - h2.local().local_y) <= u_localRanges[1]);
} else if (ldiff <= m_adjLayerDiff) {
double eta1, phi1, r1, eta2, phi2, r2;
calc_eta_phi_r(h1.position().x, h1.position().y, h1.position().z, eta1, phi1, r1);
calc_eta_phi_r(h2.position().x, h2.position().y, h2.position().z, eta2, phi2, r2);
return (std::abs(eta1 - eta2) <= u_adjLayerRanges[0]) &&
(std::abs(phi1 - phi2) <= u_adjLayerRanges[1]);
}
// not in adjacent layers
return false;
}
static void calc_eta_phi_r(double x, double y, double z, double& eta, double& phi, double& r)
{
r = std::sqrt(x * x + y * y + z * z);
phi = std::atan2(y, x);
double theta = std::acos(z / r);
eta = -std::log(std::tan(theta / 2.));
}
// grouping function with Depth-First Search
void dfs_group(std::vector<eic::CalorimeterHit>& group, int idx,
const eic::CalorimeterHitCollection& hits, std::vector<bool>& visits) const
{
const eic::CalorimeterHit hit{hits[idx].cellID(), hits[idx].clusterID(),
hits[idx].layerID(), hits[idx].sectorID(),
hits[idx].energy(), hits[idx].time(),
hits[idx].position(), hits[idx].local(),
hits[idx].dimension(), 1};
group.push_back(hit);
visits[idx] = true;
for (size_t i = 0; i < hits.size(); ++i) {
// visited, or not a neighbor
if (visits[i] || !is_neighbor(hit, hits[i])) {
continue;
}
dfs_group(group, i, hits, visits);
}
}
};
DECLARE_COMPONENT(TopologicalCellCluster)
} // namespace Jug::Reco