File indexing completed on 2026-06-17 07:50:49
0001
0002
0003
0004 #include <Evaluator/DD4hepUnits.h>
0005 #include <TVector3.h>
0006 #include <edm4eic/Cluster.h>
0007 #include <edm4eic/ReconstructedParticleCollection.h>
0008 #include <edm4eic/Vertex.h>
0009 #include <edm4hep/Vector3f.h>
0010 #include <stdint.h>
0011 #include <algorithm>
0012 #include <array>
0013 #include <cmath>
0014 #include <cstddef>
0015 #include <stdexcept>
0016 #include <tuple>
0017 #include <vector>
0018
0019 #include "LambdaReconstruction.h"
0020 #include "TLorentzVector.h"
0021
0022 namespace eicrecon {
0023
0024 void LambdaReconstruction::init() {
0025 try {
0026 m_zMax = m_detector->constant<double>(m_cfg.offsetPositionName);
0027 } catch (std::runtime_error&) {
0028 m_zMax = 35800;
0029 trace("Failed to get {} from the detector, using default value of {}", m_cfg.offsetPositionName,
0030 m_zMax);
0031 }
0032 }
0033
0034
0035
0036 bool LambdaReconstruction::reconstruct_from_triplet(
0037 const edm4eic::ReconstructedParticle& n_in, const edm4eic::ReconstructedParticle& g1_in,
0038 const edm4eic::ReconstructedParticle& g2_in,
0039 edm4eic::ReconstructedParticleCollection* out_lambdas,
0040 edm4eic::ReconstructedParticleCollection* out_decay_products) const {
0041 static const double m_neutron = m_particleSvc.particle(2112).mass;
0042 static const double m_pi0 = m_particleSvc.particle(111).mass;
0043 static const double m_lambda = m_particleSvc.particle(3122).mass;
0044
0045 const double En = n_in.getEnergy();
0046 const double E1 = g1_in.getEnergy();
0047 const double E2 = g2_in.getEnergy();
0048
0049 if (En <= m_neutron) {
0050 return false;
0051 }
0052 const double pn = std::sqrt(std::max(0.0, En * En - m_neutron * m_neutron));
0053
0054 TVector3 xn;
0055 TVector3 x1;
0056 TVector3 x2;
0057
0058 if (n_in.clusters_size() == 0 || g1_in.clusters_size() == 0 || g2_in.clusters_size() == 0) {
0059 return false;
0060 }
0061
0062 const auto rn = n_in.getClusters(0).getPosition();
0063 const auto rg1 = g1_in.getClusters(0).getPosition();
0064 const auto rg2 = g2_in.getClusters(0).getPosition();
0065
0066 xn.SetXYZ(rn.x * dd4hep::mm, rn.y * dd4hep::mm, rn.z * dd4hep::mm);
0067 x1.SetXYZ(rg1.x * dd4hep::mm, rg1.y * dd4hep::mm, rg1.z * dd4hep::mm);
0068 x2.SetXYZ(rg2.x * dd4hep::mm, rg2.y * dd4hep::mm, rg2.z * dd4hep::mm);
0069
0070 xn.RotateY(-m_cfg.globalToProtonRotation);
0071 x1.RotateY(-m_cfg.globalToProtonRotation);
0072 x2.RotateY(-m_cfg.globalToProtonRotation);
0073
0074 TVector3 vtx(0, 0, 0);
0075 double f = 0.0;
0076 double df = 0.5;
0077
0078 const double theta_open_expected = 2 * std::asin(m_pi0 / (2 * std::sqrt(E1 * E2)));
0079
0080 TLorentzVector n;
0081 TLorentzVector g1;
0082 TLorentzVector g2;
0083 TLorentzVector lambda;
0084
0085 for (int i = 0; i < m_cfg.iterations; i++) {
0086 n = {pn * (xn - vtx).Unit(), En};
0087 g1 = {E1 * (x1 - vtx).Unit(), E1};
0088 g2 = {E2 * (x2 - vtx).Unit(), E2};
0089 lambda = n + g1 + g2;
0090
0091 const double theta_open = g1.Angle(g2.Vect());
0092 if (theta_open > theta_open_expected) {
0093 f -= df;
0094 } else if (theta_open < theta_open_expected) {
0095 f += df;
0096 }
0097
0098 vtx = lambda.Vect() * (f * m_zMax / lambda.Z());
0099 df /= 2.0;
0100 }
0101
0102 const double mass_rec = lambda.M();
0103 if (std::abs(mass_rec - m_lambda) > m_cfg.lambdaMassWindow * m_lambda) {
0104 return false;
0105 }
0106
0107
0108 vtx.RotateY(m_cfg.globalToProtonRotation);
0109 lambda.RotateY(m_cfg.globalToProtonRotation);
0110 n.RotateY(m_cfg.globalToProtonRotation);
0111 g1.RotateY(m_cfg.globalToProtonRotation);
0112 g2.RotateY(m_cfg.globalToProtonRotation);
0113
0114
0115 auto b = -lambda.BoostVector();
0116 n.Boost(b);
0117 g1.Boost(b);
0118 g2.Boost(b);
0119
0120
0121 vtx = vtx * (1.0 / dd4hep::mm);
0122
0123
0124 auto rec_lambda = out_lambdas->create();
0125 rec_lambda.setPDG(3122);
0126 rec_lambda.setEnergy(lambda.E());
0127 rec_lambda.setMomentum({static_cast<float>(lambda.X()), static_cast<float>(lambda.Y()),
0128 static_cast<float>(lambda.Z())});
0129 rec_lambda.setReferencePoint(
0130 {static_cast<float>(vtx.X()), static_cast<float>(vtx.Y()), static_cast<float>(vtx.Z())});
0131 rec_lambda.setCharge(0);
0132 rec_lambda.setMass(mass_rec);
0133
0134
0135 auto neutron_cm = out_decay_products->create();
0136 neutron_cm.setPDG(2112);
0137 neutron_cm.setEnergy(n.E());
0138 neutron_cm.setMomentum(
0139 {static_cast<float>(n.X()), static_cast<float>(n.Y()), static_cast<float>(n.Z())});
0140 neutron_cm.setReferencePoint(
0141 {static_cast<float>(vtx.X()), static_cast<float>(vtx.Y()), static_cast<float>(vtx.Z())});
0142 neutron_cm.setCharge(0);
0143 neutron_cm.setMass(m_neutron);
0144
0145
0146 auto gamma1_cm = out_decay_products->create();
0147 gamma1_cm.setPDG(22);
0148 gamma1_cm.setEnergy(g1.E());
0149 gamma1_cm.setMomentum(
0150 {static_cast<float>(g1.X()), static_cast<float>(g1.Y()), static_cast<float>(g1.Z())});
0151 gamma1_cm.setReferencePoint(
0152 {static_cast<float>(vtx.X()), static_cast<float>(vtx.Y()), static_cast<float>(vtx.Z())});
0153 gamma1_cm.setCharge(0);
0154 gamma1_cm.setMass(0);
0155
0156 auto gamma2_cm = out_decay_products->create();
0157 gamma2_cm.setPDG(22);
0158 gamma2_cm.setEnergy(g2.E());
0159 gamma2_cm.setMomentum(
0160 {static_cast<float>(g2.X()), static_cast<float>(g2.Y()), static_cast<float>(g2.Z())});
0161 gamma2_cm.setReferencePoint(
0162 {static_cast<float>(vtx.X()), static_cast<float>(vtx.Y()), static_cast<float>(vtx.Z())});
0163 gamma2_cm.setCharge(0);
0164 gamma2_cm.setMass(0);
0165
0166 rec_lambda.addToParticles(n_in);
0167 rec_lambda.addToParticles(g1_in);
0168 rec_lambda.addToParticles(g2_in);
0169
0170 neutron_cm.addToParticles(n_in);
0171 gamma1_cm.addToParticles(g1_in);
0172 gamma2_cm.addToParticles(g2_in);
0173
0174 return true;
0175 }
0176
0177 void LambdaReconstruction::process(const LambdaReconstruction::Input& input,
0178 const LambdaReconstruction::Output& output) const {
0179 const auto [neutralsHcal, neutralsB0, neutralsEcalEndcapP, neutralsLFHCAL] = input;
0180 auto [out_lambdas, out_decay_products] = output;
0181
0182 const double m_pi0 = m_particleSvc.particle(111).mass;
0183 const double m_lambda = m_particleSvc.particle(3122).mass;
0184
0185
0186
0187
0188
0189
0190
0191
0192
0193
0194
0195 std::vector<edm4eic::ReconstructedParticle> neutrons_zdc;
0196 std::vector<edm4eic::ReconstructedParticle> neutrons_other;
0197 std::vector<edm4eic::ReconstructedParticle> gammas_zdc;
0198 std::vector<edm4eic::ReconstructedParticle> gammas_other;
0199
0200 struct NeutralInputDescription {
0201 const edm4eic::ReconstructedParticleCollection* coll;
0202 bool use_gamma;
0203 bool use_neutron;
0204 bool is_zdc;
0205 };
0206
0207 const std::array<NeutralInputDescription, 4> input_descs{{
0208 {neutralsHcal, true, true, true},
0209 {neutralsB0, true, false, false},
0210 {neutralsEcalEndcapP, true, false, false},
0211 {neutralsLFHCAL, false, true, false},
0212 }};
0213
0214 for (const auto& desc : input_descs) {
0215 for (const auto& part : *desc.coll) {
0216 if (desc.use_neutron && part.getPDG() == 2112) {
0217 (desc.is_zdc ? neutrons_zdc : neutrons_other).push_back(part);
0218 }
0219 if (desc.use_gamma && part.getPDG() == 22) {
0220 (desc.is_zdc ? gammas_zdc : gammas_other).push_back(part);
0221 }
0222 }
0223 }
0224
0225 if (neutrons_zdc.empty() && neutrons_other.empty()) {
0226 debug("No neutrons in input collection");
0227 return;
0228 }
0229
0230
0231
0232
0233
0234
0235 using ParticleT = edm4eic::ReconstructedParticle;
0236
0237 std::vector<ParticleT> gamma_pool;
0238 std::vector<bool> gamma_is_zdc;
0239
0240 gamma_pool.reserve(gammas_zdc.size() + gammas_other.size());
0241 gamma_is_zdc.reserve(gammas_zdc.size() + gammas_other.size());
0242
0243 for (const auto& g : gammas_zdc) {
0244 gamma_pool.push_back(g);
0245 gamma_is_zdc.push_back(1);
0246 }
0247 for (const auto& g : gammas_other) {
0248 gamma_pool.push_back(g);
0249 gamma_is_zdc.push_back(0);
0250 }
0251
0252 if (gamma_pool.size() < 2) {
0253 debug("Less than 2 gammas found ({})", gamma_pool.size());
0254 return;
0255 }
0256
0257
0258
0259
0260
0261 auto invMass2 = [](const ParticleT& a, const ParticleT& b) {
0262 const auto pa = a.getMomentum();
0263 const auto pb = b.getMomentum();
0264
0265 const double Ea = a.getEnergy();
0266 const double Eb = b.getEnergy();
0267
0268 const double px = pa.x + pb.x;
0269 const double py = pa.y + pb.y;
0270 const double pz = pa.z + pb.z;
0271 const double E = Ea + Eb;
0272
0273 return E * E - (px * px + py * py + pz * pz);
0274 };
0275
0276 auto invMass2_3 = [](const ParticleT& a, const ParticleT& b, const ParticleT& c) {
0277 const auto pa = a.getMomentum();
0278 const auto pb = b.getMomentum();
0279 const auto pc = c.getMomentum();
0280
0281 const double Ea = a.getEnergy();
0282 const double Eb = b.getEnergy();
0283 const double Ec = c.getEnergy();
0284
0285 const double px = pa.x + pb.x + pc.x;
0286 const double py = pa.y + pb.y + pc.y;
0287 const double pz = pa.z + pb.z + pc.z;
0288 const double E = Ea + Eb + Ec;
0289
0290 return E * E - (px * px + py * py + pz * pz);
0291 };
0292
0293
0294
0295
0296
0297 enum class GammaCategory : uint8_t { TwoZDC = 0, OneZDC = 1, ZeroZDC = 2 };
0298
0299 struct Pi0Pair {
0300 int i;
0301 int j;
0302 GammaCategory cat;
0303 double dmpi0_cand;
0304 };
0305
0306 std::vector<Pi0Pair> pi0_pairs;
0307 pi0_pairs.reserve(gamma_pool.size() * (gamma_pool.size() - 1) / 2);
0308
0309 for (size_t i = 0; i < gamma_pool.size(); ++i) {
0310 for (size_t j = i + 1; j < gamma_pool.size(); ++j) {
0311 const auto& g1 = gamma_pool[i];
0312 const auto& g2 = gamma_pool[j];
0313
0314 const double m2 = invMass2(g1, g2);
0315 if (m2 <= 0.0) {
0316 continue;
0317 }
0318
0319 const double m = std::sqrt(m2);
0320 const double dm = std::abs(m - m_pi0);
0321 if (dm > m_cfg.pi0Window * m_pi0) {
0322 continue;
0323 }
0324
0325 const bool z1 = gamma_is_zdc[i];
0326 const bool z2 = gamma_is_zdc[j];
0327
0328 const GammaCategory cat = (z1 && z2) ? GammaCategory::TwoZDC
0329 : (z1 || z2) ? GammaCategory::OneZDC
0330 : GammaCategory::ZeroZDC;
0331
0332 pi0_pairs.push_back({static_cast<int>(i), static_cast<int>(j), cat, dm});
0333 }
0334 }
0335
0336 if (pi0_pairs.empty()) {
0337 return;
0338 }
0339
0340
0341
0342
0343
0344 enum class NeutronCategory : uint8_t { ZDC = 0, Other = 1 };
0345
0346 struct LambdaCandidate {
0347 int g_i;
0348 int g_j;
0349 int n_idx;
0350 NeutronCategory n_cat;
0351 GammaCategory g_cat;
0352 double chi2;
0353 double E;
0354 double pz;
0355 };
0356
0357 std::vector<LambdaCandidate> cands;
0358 cands.reserve(64);
0359
0360 auto try_neutrons = [&](const auto& neutron_list, NeutronCategory n_cat) {
0361 for (const auto& pp : pi0_pairs) {
0362 const auto& g1 = gamma_pool[pp.i];
0363 const auto& g2 = gamma_pool[pp.j];
0364
0365 for (size_t in = 0; in < neutron_list.size(); ++in) {
0366 const auto& n = neutron_list[in];
0367
0368 const double m2L = invMass2_3(n, g1, g2);
0369 if (m2L <= 0.0) {
0370 continue;
0371 }
0372
0373 const double mL = std::sqrt(m2L);
0374 const double dL = mL - m_lambda;
0375
0376 if (std::abs(dL) > m_cfg.lambdaMassWindow * m_lambda) {
0377 continue;
0378 }
0379
0380 const double pi0_term = pp.dmpi0_cand / (m_cfg.pi0Window * m_pi0);
0381 const double lambda_term = dL / (m_cfg.lambdaMassWindow * m_lambda);
0382
0383 const double chi2 = pi0_term * pi0_term + lambda_term * lambda_term;
0384
0385 const double E = n.getEnergy() + g1.getEnergy() + g2.getEnergy();
0386
0387 const auto pn = n.getMomentum();
0388 const auto pg1 = g1.getMomentum();
0389 const auto pg2 = g2.getMomentum();
0390
0391 const double pz = pn.z + pg1.z + pg2.z;
0392
0393 cands.push_back({pp.i, pp.j, static_cast<int>(in), n_cat, pp.cat, chi2, E, pz});
0394 }
0395 }
0396 };
0397
0398 if (!neutrons_zdc.empty()) {
0399 try_neutrons(neutrons_zdc, NeutronCategory::ZDC);
0400 }
0401 if (!neutrons_other.empty()) {
0402 try_neutrons(neutrons_other, NeutronCategory::Other);
0403 }
0404
0405 if (cands.empty()) {
0406 return;
0407 }
0408
0409
0410
0411
0412
0413
0414
0415
0416
0417
0418
0419 auto better = [&](const LambdaCandidate& a, const LambdaCandidate& b) -> bool {
0420 if (a.n_cat != b.n_cat) {
0421 return static_cast<int>(a.n_cat) < static_cast<int>(b.n_cat);
0422 }
0423 if (a.g_cat != b.g_cat) {
0424 return static_cast<int>(a.g_cat) < static_cast<int>(b.g_cat);
0425 }
0426 if (a.chi2 != b.chi2) {
0427 return a.chi2 < b.chi2;
0428 }
0429 if (a.pz != b.pz) {
0430 return a.pz > b.pz;
0431 }
0432 return a.E > b.E;
0433 };
0434
0435 int best_k = 0;
0436 for (int k = 1; k < static_cast<int>(cands.size()); ++k) {
0437 if (better(cands[k], cands[best_k])) {
0438 best_k = k;
0439 }
0440 }
0441
0442 const auto& best = cands[best_k];
0443
0444 const auto& g1 = gamma_pool[best.g_i];
0445 const auto& g2 = gamma_pool[best.g_j];
0446 const auto& n =
0447 (best.n_cat == NeutronCategory::ZDC) ? neutrons_zdc[best.n_idx] : neutrons_other[best.n_idx];
0448
0449 reconstruct_from_triplet(n, g1, g2, out_lambdas, out_decay_products);
0450 }
0451
0452 }