16 #include <ecl/modules/eclShowerShape/ECLShowerShapeModule.h>
19 #include <boost/algorithm/string/predicate.hpp>
25 #include <framework/datastore/RelationVector.h>
26 #include <framework/logging/Logger.h>
27 #include <framework/geometry/B2Vector3.h>
30 #include <mva/interface/Expert.h>
31 #include <mva/interface/Weightfile.h>
32 #include <mva/interface/Interface.h>
33 #include <mva/dataobjects/DatabaseRepresentationOfWeightfile.h>
36 #include <ecl/dataobjects/ECLCalDigit.h>
37 #include <ecl/dataobjects/ECLConnectedRegion.h>
38 #include <ecl/geometry/ECLGeometryPar.h>
39 #include <ecl/dataobjects/ECLShower.h>
40 #include <ecl/geometry/ECLNeighbours.h>
41 #include <ecl/dbobjects/ECLShowerShapeSecondMomentCorrection.h>
57 m_eclConnectedRegions(eclConnectedRegionArrayName()),
58 m_secondMomentCorrectionArray("ecl_shower_shape_second_moment_corrections")
61 setDescription(
"ECLShowerShapeModule: Calculate ECL shower shape variables (e.g. E9oE21)");
62 setPropertyFlags(c_ParallelProcessingCertified);
64 addParam(
"zernike_n1_rho0", m_zernike_n1_rho0,
65 "Scaling factor for radial distances in a plane perpendicular to direction to shower for the n photon hypothesis in Zernike moment calculation.",
68 addParam(
"zernike_n2_rho0", m_zernike_n2_rho0,
69 "Scaling factor for radial distances in a plane perpendicular to direction to shower for the neutral hadron hypothesis in Zernike moment calculation. ",
72 addParam(
"zernike_useFarCrystals", m_zernike_useFarCrystals,
73 "Determines if Digits with rho > rho0 are used for the zernike moment calculation. If they are, their radial distance will be set to rho0.",
76 addParam(
"zernike_MVAidentifier_FWD", m_zernike_MVAidentifier_FWD,
"The Zernike moment MVA database identifier for forward endcap.",
77 std::string{
"ecl_showershape_zernike_mva_fwd"});
78 addParam(
"zernike_MVAidentifier_BRL", m_zernike_MVAidentifier_BRL,
"The Zernike moment MVA database identifier for barrel.",
79 std::string{
"ecl_showershape_zernike_mva_brl"});
80 addParam(
"zernike_MVAidentifier_BWD", m_zernike_MVAidentifier_BWD,
81 "The Zernike moment MVA database identifier for backward endcap.", std::string{
"ecl_showershape_zernike_mva_bwd"});
83 addParam(
"avgCrystalDimension", m_avgCrystalDimension,
84 "Average crystal dimension used in lateral energy calculation.",
96 if (not(boost::ends_with(identifier,
".root") or boost::ends_with(identifier,
".xml"))) {
97 weightFileRepresentation = std::unique_ptr<DBObjPtr<DatabaseRepresentationOfWeightfile>>(
new
107 m_neighbourMap9 = std::unique_ptr<ECL::ECLNeighbours>(
new ECL::ECLNeighbours(
"N", 1));
108 m_neighbourMap21 = std::unique_ptr<ECL::ECLNeighbours>(
new ECL::ECLNeighbours(
"NC", 2));
110 initializeMVAweightFiles(m_zernike_MVAidentifier_FWD, m_weightfile_representation_FWD);
111 initializeMVAweightFiles(m_zernike_MVAidentifier_BRL, m_weightfile_representation_BRL);
112 initializeMVAweightFiles(m_zernike_MVAidentifier_BWD, m_weightfile_representation_BWD);
127 if (weightFileRepresentation) {
135 if (weightFileRepresentation->hasChanged()) {
136 std::stringstream ss((*weightFileRepresentation)->m_data);
149 if (m_numZernikeMVAvariables != general_options.m_variables.size())
150 B2FATAL(
"Expecting " << m_numZernikeMVAvariables <<
" varibales, found " << general_options.m_variables.size());
152 expert = supported_interfaces[general_options.m_method]->getExpert();
153 expert->load(weightfile);
156 if (weightFileRepresentation == m_weightfile_representation_BRL) {
157 std::vector<float> dummy(general_options.m_variables.size(), 0);
158 m_dataset = std::unique_ptr<MVA::SingleDataset>(
new MVA::SingleDataset(general_options, dummy, 0));
164 initializeMVA(m_zernike_MVAidentifier_FWD, m_weightfile_representation_FWD, m_expert_FWD);
165 initializeMVA(m_zernike_MVAidentifier_BRL, m_weightfile_representation_BRL, m_expert_BRL);
166 initializeMVA(m_zernike_MVAidentifier_BWD, m_weightfile_representation_BWD, m_expert_BWD);
169 if (m_secondMomentCorrectionArray.hasChanged()) {
170 if (m_secondMomentCorrectionArray) prepareSecondMomentCorrectionsCallback();
171 else B2ERROR(
"ECLShowerShapeModule::beginRun - Couldn't find second moment correction for current run");
179 std::vector<ProjectedECLDigit> projectedECLDigits = projectECLDigits(*eclShower);
181 const double showerEnergy = eclShower->
getEnergy();
182 const double showerTheta = eclShower->
getTheta();
183 const double showerPhi = eclShower->
getPhi();
186 double sumEnergies = 0.0;
187 for (
const auto& projectedECLDigit : projectedECLDigits) sumEnergies += projectedECLDigit.energy;
195 const double absZernike40 = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 4, 0, rho0);
196 const double absZernike51 = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 5, 1, rho0);
198 const double secondMomentCorrection = getSecondMomentCorrection(showerTheta, showerPhi, hypothesisID);
199 B2DEBUG(175,
"Second moment angular correction: " << secondMomentCorrection <<
" (theta(rad)=" << showerTheta <<
", phi(rad)=" <<
200 showerPhi <<
",hypothesisId=" << hypothesisID <<
202 const double secondMoment = computeSecondMoment(projectedECLDigits, showerEnergy) * secondMomentCorrection;
203 B2DEBUG(175,
"Second moment after correction: " << secondMoment);
205 const double LATenergy = computeLateralEnergy(projectedECLDigits, m_avgCrystalDimension);
212 eclShower->
setE1oE9(computeE1oE9(*eclShower));
215 if (calculateZernikeMVA) {
222 m_dataset->m_input[0 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 1, 1, rho0);
223 m_dataset->m_input[1 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 2, 0, rho0);
224 m_dataset->m_input[2 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 2, 2, rho0);
225 m_dataset->m_input[3 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 3, 1, rho0);
226 m_dataset->m_input[4 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 3, 3, rho0);
227 m_dataset->m_input[5 + indexOffset] = absZernike40;
228 m_dataset->m_input[6 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 4, 2, rho0);
229 m_dataset->m_input[7 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 4, 4, rho0);
230 m_dataset->m_input[8 + indexOffset] = absZernike51;
231 m_dataset->m_input[9 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 5, 3, rho0);
232 m_dataset->m_input[10 + indexOffset] = computeAbsZernikeMoment(projectedECLDigits, sumEnergies, 5, 5, rho0);
237 if (eclShower->
getTheta() < m_BRLthetaMin) eclShower->
setZernikeMVA(m_expert_FWD->apply(*m_dataset)[0]);
239 else if (eclShower->
getTheta() > m_BRLthetaMax) eclShower->
setZernikeMVA(m_expert_BWD->apply(*m_dataset)[0]);
241 else eclShower->
setZernikeMVA(m_expert_BRL->apply(*m_dataset)[0]);
248 for (
auto& eclCR : m_eclConnectedRegions) {
253 for (
auto& eclShower : eclCR.getRelationsWith<
ECLShower>(eclShowerArrayName())) {
255 N2shower = &eclShower;
256 setShowerShapeVariables(N2shower,
true);
262 bool found_N2shower =
true;
263 if (N2shower ==
nullptr) found_N2shower =
false;
265 double prodN1zernikeMVAs = 1.0;
267 for (
auto& eclShower : eclCR.getRelationsWith<
ECLShower>(eclShowerArrayName())) {
271 bool calculateZernikeMVA =
true;
272 if (!found_N2shower || eclShower.getHypothesisId() !=
ECLShower::c_nPhotons) calculateZernikeMVA =
false;
274 setShowerShapeVariables(&eclShower, calculateZernikeMVA);
276 if (eclShower.getHypothesisId() ==
ECLShower::c_nPhotons) prodN1zernikeMVAs *= eclShower.getZernikeMVA();
280 if (N2shower) N2shower->
setZernikeMVA(1.0 - prodN1zernikeMVAs);
286 std::vector<ProjectedECLDigit> tmpProjectedECLDigits;
292 const double showerR = shower.
getR();
293 const double showerTheta = shower.
getTheta();
294 const double showerPhi = shower.
getPhi();
300 const B2Vector3D showerDirection = (1.0 / showerPosition.
Mag()) * showerPosition;
307 xPrimeDirection *= 1.0 / xPrimeDirection.
Mag();
310 yPrimeDirection *= 1.0 / yPrimeDirection.
Mag();
318 for (
unsigned int iRelation = 0; iRelation < showerDigitRelations.size(); ++iRelation) {
319 const auto calDigit = showerDigitRelations.object(iRelation);
326 const auto weight = showerDigitRelations.weight(iRelation);
327 tmpProjectedDigit.
energy = weight * calDigit->getEnergy();
332 const int cellId = calDigit->getCellId();
333 B2Vector3D calDigitPosition = geometry->GetCrystalPos(cellId - 1);
337 const double angleDigitShower = calDigitPosition.
Angle(showerPosition);
338 tmpProjectedDigit.
rho = showerR * TMath::Tan(angleDigitShower);
345 B2Vector3D projectedDigitDirection = calDigitPosition - calDigitPosition.
Dot(showerDirection) * showerDirection;
346 tmpProjectedDigit.
alpha = projectedDigitDirection.
Angle(xPrimeDirection);
349 if (projectedDigitDirection.
Angle(yPrimeDirection) > TMath::Pi() / 2.0)
350 tmpProjectedDigit.
alpha = 2.0 * TMath::Pi() - tmpProjectedDigit.
alpha;
351 tmpProjectedECLDigits.push_back(tmpProjectedDigit);
354 return tmpProjectedECLDigits;
367 const double avgCrystalDimension)
const
371 if (projectedDigits.size() < 3.0)
return 0;
374 double maxEnergy(0), secondMaxEnergy(0);
375 unsigned int iMax(0), iSecondMax(0);
377 for (
unsigned int iProjecteDigit = 0; iProjecteDigit < projectedDigits.size(); iProjecteDigit++) {
378 if (projectedDigits[iProjecteDigit].energy > maxEnergy) {
379 secondMaxEnergy = maxEnergy;
381 maxEnergy = projectedDigits[iProjecteDigit].energy;
382 iMax = iProjecteDigit;
383 }
else if (projectedDigits[iProjecteDigit].energy > secondMaxEnergy) {
384 secondMaxEnergy = projectedDigits[iProjecteDigit].energy;
385 iSecondMax = iProjecteDigit;
391 for (
unsigned int iProjecteDigit = 0; iProjecteDigit < projectedDigits.size(); iProjecteDigit++) {
394 if (iProjecteDigit == iMax || iProjecteDigit == iSecondMax)
continue;
396 double rho = projectedDigits[iProjecteDigit].rho;
397 double rho2 = rho * rho;
398 double energy = projectedDigits[iProjecteDigit].energy;
399 sumE += energy * rho2;
403 const double r0sq = avgCrystalDimension * avgCrystalDimension;
404 return sumE / (sumE + r0sq * (maxEnergy + secondMaxEnergy));
410 if (n == 1 && m == 1)
return rho;
411 if (n == 2 && m == 0)
return 2.0 * rho * rho - 1.0;
412 if (n == 2 && m == 2)
return rho * rho;
413 if (n == 3 && m == 1)
return 3.0 * rho * rho * rho - 2.0 * rho;
414 if (n == 3 && m == 3)
return rho * rho * rho;
415 if (n == 4 && m == 0)
return 6.0 * rho * rho * rho * rho - 6.0 * rho * rho + 1.0;
416 if (n == 4 && m == 2)
return 4.0 * rho * rho * rho * rho - 3.0 * rho * rho;
417 if (n == 4 && m == 4)
return rho * rho * rho * rho;
418 if (n == 5 && m == 1)
return 10.0 * rho * rho * rho * rho * rho - 12.0 * rho * rho * rho + 3.0 * rho;
419 if (n == 5 && m == 3)
return 5.0 * rho * rho * rho * rho * rho - 4.0 * rho * rho * rho;
420 if (n == 5 && m == 5)
return rho * rho * rho * rho * rho;
423 double returnVal = 0;
424 for (
int idx = 0; idx <= (n - std::abs(m)) / 2; ++idx)
425 returnVal += std::pow(-1, idx) * TMath::Factorial(n - idx) / TMath::Factorial(idx)
426 / TMath::Factorial((n + std::abs(m)) / 2 - idx) / TMath::Factorial((n - std::abs(m)) / 2 - idx) * std::pow(rho, n - 2 * idx);
434 if (rho > 1.0)
return std::complex<double>(0, 0);
436 std::complex<double> i(0, 1);
437 std::complex<double> exponent = std::exp(i * std::complex<double>(m * alpha, 0));
438 return std::complex<double>(Rnm(n, m, rho), 0) * exponent;
443 const double totalEnergy,
const int n,
const int m,
const double rho0)
const
445 if (totalEnergy <= 0.0)
return 0.0;
448 if (n < 0 || m < 0)
return 0.0;
449 if (m > n)
return 0.0;
451 std::complex<double> sum(0.0, 0.0);
453 for (
const auto projectedDigit : projectedDigits) {
454 double normalizedRho = projectedDigit.rho / rho0;
456 if (normalizedRho > 1.0) {
457 if (!m_zernike_useFarCrystals)
continue;
458 else normalizedRho = 1.0;
461 sum += projectedDigit.energy * std::conj(zernikeValue(normalizedRho, projectedDigit.alpha, n, m));
463 return (n + 1.0) / TMath::Pi() * std::abs(sum) / totalEnergy;
467 const double totalEnergy)
const
469 if (totalEnergy <= 0.0)
return 0.0;
473 for (
const auto projectedDigit : projectedDigits) sum += projectedDigit.energy * projectedDigit.rho * projectedDigit.rho;
475 return sum / totalEnergy;
484 if (centralCellId == 0)
return 0.0;
487 const std::vector< short int > n9 = m_neighbourMap9->getNeighbours(centralCellId);
489 double energy1 = 0.0;
490 double energy9 = 0.0;
494 for (
unsigned int iRel = 0; iRel < relatedDigitsPairs.size(); iRel++) {
495 const auto caldigit = relatedDigitsPairs.object(iRel);
496 const auto weight = relatedDigitsPairs.weight(iRel);
497 const auto energy = caldigit->getEnergy();
498 const int cellid = caldigit->getCellId();
501 if (cellid == centralCellId) {
502 energy1 = weight * energy;
506 const auto it9 = std::find(n9.begin(), n9.end(), cellid);
507 if (it9 != n9.end()) {
508 energy9 += weight * energy;
513 if (energy9 > 1e-9)
return energy1 / energy9;
521 if (centralCellId == 0)
return 0.0;
524 const std::vector< short int > n9 = m_neighbourMap9->getNeighbours(centralCellId);
525 const std::vector< short int > n21 = m_neighbourMap21->getNeighbours(centralCellId);
527 double energy9 = 0.0;
528 double energy21 = 0.0;
532 for (
unsigned int iRel = 0; iRel < relatedDigitsPairs.size(); iRel++) {
533 const auto caldigit = relatedDigitsPairs.object(iRel);
534 const auto weight = relatedDigitsPairs.weight(iRel);
535 const auto energy = caldigit->getEnergy();
536 const int cellid = caldigit->getCellId();
539 const auto it9 = std::find(n9.begin(), n9.end(), cellid);
540 if (it9 != n9.end()) {
541 energy9 += weight * energy;
545 const auto it21 = std::find(n21.begin(), n21.end(), cellid);
546 if (it21 != n21.end()) {
547 energy21 += weight * energy;
552 if (energy21 > 1e-9)
return energy9 / energy21;
560 for (
auto iType = 0; iType < 2; ++iType)
561 for (
auto iHypothesis = 0; iHypothesis < 10; ++iHypothesis)
562 m_secondMomentCorrections[iType][iHypothesis] = TGraph();
566 const int type = correction.getType();
567 const int hypothesis = correction.getHypothesisId();
568 if (type < 0 or type > 1 or hypothesis < 1 or hypothesis > 9) {
569 B2FATAL(
"Invalid type or hypothesis for second moment corrections.");
572 m_secondMomentCorrections[type][hypothesis] = correction.getCorrection();
580 B2FATAL(
"Missing corrections for second moments..");
587 double thetadeg = theta * TMath::RadToDeg();
590 if (thetadeg < 0.1) thetadeg = 0.1;
591 else if (thetadeg > 179.9) thetadeg = 179.9;
594 double phideg = phi * TMath::RadToDeg();
597 if (phideg < -179.9) phideg = -179.9;
598 else if (phideg > 179.9) phideg = 179.9;
602 if (hypothesis < 1 or hypothesis > 10) {
603 B2FATAL(
"Invalid hypothesis for second moment corrections.");
606 const double thetaCorrection = m_secondMomentCorrections[c_thetaType][hypothesis].Eval(thetadeg);
607 const double phiCorrection = m_secondMomentCorrections[c_phiType][hypothesis].Eval(phideg);
609 B2DEBUG(175,
"Second momen theta crrection = " << thetaCorrection <<
", phi correction = " << phiCorrection);
611 return thetaCorrection * phiCorrection;