Belle II Software development
ParameterVariables.cc
1/**************************************************************************
2 * basf2 (Belle II Analysis Software Framework) *
3 * Author: The Belle II Collaboration *
4 * *
5 * See git log for contributors and copyright holders. *
6 * This file is licensed under LGPL-3.0, see LICENSE.md. *
7 **************************************************************************/
8
9// Own header.
10#include <analysis/variables/ParameterVariables.h>
11
12// include VariableManager
13#include <analysis/VariableManager/Manager.h>
14
15#include <analysis/dataobjects/Particle.h>
16#include <analysis/utility/PCmsLabTransform.h>
17#include <analysis/utility/ReferenceFrame.h>
18
19#include <framework/logging/Logger.h>
20#include <framework/datastore/StoreArray.h>
21
22#include <mdst/dataobjects/MCParticle.h>
23
24#include <Math/Boost.h>
25#include <Math/Vector4D.h>
26#include <Math/VectorUtil.h>
27#include <TVectorF.h>
28
29#include <cmath>
30
31
32namespace Belle2 {
37 namespace Variable {
38
39 bool almostContains(const std::vector<double>& vector, int value)
40 {
41 for (const auto& item : vector)
42 if (std::abs(value - item) < 1e-3)
43 return true;
44 return false;
45 }
46
47 double RandomChoice(const Particle*, const std::vector<double>& choices)
48 {
49 int r = std::rand() % choices.size() + 1;
50 auto it = choices.begin();
51 std::advance(it, r);
52 return *it;
53 }
54
55 int NumberOfMCParticlesInEvent(const Particle*, const std::vector<double>& pdgs)
56 {
57 StoreArray<MCParticle> mcParticles;
58 int counter = 0;
59 for (int i = 0; i < mcParticles.getEntries(); ++i) {
60 if (mcParticles[i]->getStatus(MCParticle::c_PrimaryParticle) and almostContains(pdgs, std::abs(mcParticles[i]->getPDG())))
61 counter++;
62 }
63 return counter;
64 }
65
66 double isAncestorOf(const Particle* part, const std::vector<double>& daughterIDs)
67 {
68 if (part == nullptr)
69 return Const::doubleNaN;
70
71 // If particle has no MC relation, MC chain doesn't exist
72 const MCParticle* mcpart = part->getMCParticle();
73 if (mcpart == nullptr)
74 return Const::doubleNaN;
75
76 if (daughterIDs.empty())
77 B2FATAL("Wrong number of arguments for parameter function isAncestorOf. At least one needed!");
78
79 // Get to the daughter of interest
80 const Particle* curParticle = part;
81 double isAncestor = 0.0;
82
83 for (unsigned int i = 0; i < daughterIDs.size(); i++) {
84 int nCurDaughters = curParticle->getNDaughters();
85 if (nCurDaughters == 0)
86 B2FATAL("Assumed mother of particle at argument " << i << " has no daughters!");
87 if (daughterIDs[i] >= nCurDaughters)
88 B2FATAL("Assumed mother of particle at argument " << i << " has only " << nCurDaughters
89 << " daughters, but daughter at position " << daughterIDs[i] << " expected!");
90 const Particle* curDaughter = curParticle->getDaughter(daughterIDs[i]);
91 if (curDaughter == nullptr)
92 return Const::doubleNaN;
93 curParticle = curDaughter;
94 }
95
96 // Daughter obtained, get MC particle of daughter
97 const MCParticle* finalMCDaughter = curParticle->getMCParticle();
98 if (finalMCDaughter == nullptr)
99 return Const::doubleNaN;
100
101 // Go up the MC chain, check for ancestor
102 const MCParticle* curMCParticle = finalMCDaughter;
103
104 while (curMCParticle != nullptr) {
105 const MCParticle* curMCMother = curMCParticle->getMother();
106 if (curMCMother == nullptr)
107 return 0.0;
108 else {
109 if (curMCMother->getArrayIndex() == mcpart->getArrayIndex()) {
110 isAncestor++;
111 break;
112 } else {
113 curMCParticle = curMCMother;
114 isAncestor++;
115 }
116 }
117 }
118 return isAncestor;
119 }
120
121 double hasAncestor(const Particle* part, const std::vector<double>& args)
122 {
123 if (part == nullptr)
124 return Const::doubleNaN;
125
126 // If particle has no MC relation, MC chain doesn't exist
127 const MCParticle* mcpart = part->getMCParticle();
128 if (mcpart == nullptr)
129 return Const::doubleNaN;
130
131 int m_PDG, m_sign = 0;
132
133 if (args.empty())
134 B2FATAL("Wrong number of arguments for variable hasAncestor!");
135 else if (args.size() == 1) {
136 if (args[0] == 0)
137 B2FATAL("PDG code in variable hasAncestor is 0!");
138 else
139 m_PDG = args[0];
140 } else if (args.size() == 2) {
141 if (args[0] == 0 or (args[1] != 0 and args[1] != 1))
142 B2FATAL("PDG code in variable hasAncestor is 0 or second argument is not 0 or 1!");
143 else {
144 m_PDG = args[0];
145 m_sign = args[1];
146 }
147 } else {
148 B2FATAL("Too many arguments for variable hasAncestor!");
149 }
150
151 unsigned int nLevels = 0;
152
153 const MCParticle* curMCParticle = mcpart;
154
155 while (curMCParticle != nullptr) {
156 const MCParticle* curMCMother = curMCParticle->getMother();
157 if (curMCMother == nullptr)
158 return 0;
159
160 int pdg = curMCMother->getPDG();
161 if (m_sign == 0)
162 pdg = abs(pdg);
163
164 if (pdg == m_PDG) {
165 ++nLevels;
166 break;
167 } else {
168 ++nLevels;
169 curMCParticle = curMCMother;
170 }
171 }
172 return nLevels;
173 }
174
175
176 double daughterInvariantMass(const Particle* particle, const std::vector<double>& daughter_indexes)
177 {
178 if (!particle)
179 return Const::doubleNaN;
180
181 ROOT::Math::PxPyPzEVector sum;
182 const auto& daughters = particle->getDaughters();
183 int nDaughters = static_cast<int>(daughters.size());
184
185 for (auto& double_daughter : daughter_indexes) {
186 long daughter = std::lround(double_daughter);
187 if (daughter >= nDaughters)
188 return Const::doubleNaN;
189
190 sum += daughters[daughter]->get4Vector();
191 }
192
193 return sum.M();
194 }
195
196 double daughterMCInvariantMass(const Particle* particle, const std::vector<double>& daughter_indexes)
197 {
198 if (!particle)
199 return Const::doubleNaN;
200
201 ROOT::Math::PxPyPzEVector sum;
202 const auto& daughters = particle->getDaughters();
203 int nDaughters = static_cast<int>(daughters.size());
204
205 for (auto& double_daughter : daughter_indexes) {
206 long daughter = std::lround(double_daughter);
207 if (daughter >= nDaughters)
208 return Const::doubleNaN;
209
210 const MCParticle* mcdaughter = daughters[daughter]->getMCParticle();
211 if (!mcdaughter)
212 return Const::doubleNaN;
213
214 sum += mcdaughter->get4Vector();
215 }
216
217 return sum.M();
218 }
219
220
221 double massDifference(const Particle* particle, const std::vector<double>& daughters)
222 {
223 if (!particle)
224 return Const::doubleNaN;
225
226 long daughter = std::lround(daughters[0]);
227 if (daughter >= static_cast<int>(particle->getNDaughters()))
228 return Const::doubleNaN;
229
230 double motherMass = particle->getMass();
231 double daughterMass = particle->getDaughter(daughter)->getMass();
232
233 return motherMass - daughterMass;
234 }
235
236 double massDifferenceError(const Particle* particle, const std::vector<double>& daughters)
237 {
238 if (!particle)
239 return Const::doubleNaN;
240
241 long daughter = std::lround(daughters[0]);
242 if (daughter >= static_cast<int>(particle->getNDaughters()))
243 return Const::doubleNaN;
244
245 float result = 0.0;
246
247 ROOT::Math::PxPyPzEVector thisDaughterMomentum = particle->getDaughter(daughter)->get4Vector();
248
249 TMatrixFSym thisDaughterCovM(Particle::c_DimMomentum);
250 thisDaughterCovM = particle->getDaughter(daughter)->getMomentumErrorMatrix();
251 TMatrixFSym othrDaughterCovM(Particle::c_DimMomentum);
252
253 for (int j = 0; j < int(particle->getNDaughters()); ++j) {
254 if (j == daughter)
255 continue;
256
257 othrDaughterCovM += particle->getDaughter(j)->getMomentumErrorMatrix();
258 }
259
260 TMatrixFSym covarianceMatrix(2 * Particle::c_DimMomentum);
261 covarianceMatrix.SetSub(0, thisDaughterCovM);
262 covarianceMatrix.SetSub(4, othrDaughterCovM);
263
264 double motherMass = particle->getMass();
265 double daughterMass = particle->getDaughter(daughter)->getMass();
266
267 TVectorF jacobian(2 * Particle::c_DimMomentum);
268 /* Formulas to understand derivatives:
269 *
270 * Delta M = m(mother) - m(daughter) = (m(daughter) + m(other)) - m(daughter) // Here it is important not to combine the terms
271 * = sqrt((E(d) + E(o))^2 - (px(d) + px(o))^2 - (py(d) + py(o))^2 - (pz(d) + pz(o))^2)
272 * - sqrt(E(d)^2 - px(d)^2 - py(d)^2 - pz(d)^2)
273 *
274 * jacobian[0) = del (Delta M) / del px(d) with del E(d) / del px(d) = 0
275 */
276 jacobian[0] = thisDaughterMomentum.Px() / daughterMass - particle->getPx() / motherMass;
277 jacobian[1] = thisDaughterMomentum.Py() / daughterMass - particle->getPy() / motherMass;
278 jacobian[2] = thisDaughterMomentum.Pz() / daughterMass - particle->getPz() / motherMass;
279 jacobian[3] = particle->getEnergy() / motherMass - thisDaughterMomentum.E() / daughterMass;
280 jacobian[4] = -1.0 * particle->getPx() / motherMass;
281 jacobian[5] = -1.0 * particle->getPy() / motherMass;
282 jacobian[6] = -1.0 * particle->getPz() / motherMass;
283 jacobian[7] = 1.0 * particle->getEnergy() / motherMass;
284
285 result = jacobian * (covarianceMatrix * jacobian);
286
287 if (result < 0.0)
288 result = 0.0;
289
290 return TMath::Sqrt(result);
291 }
292
293 double massDifferenceSignificance(const Particle* particle, const std::vector<double>& daughters)
294 {
295 if (!particle)
296 return Const::doubleNaN;
297
298 long daughter = std::lround(daughters[0]);
299 if (daughter >= static_cast<int>(particle->getNDaughters()))
300 return Const::doubleNaN;
301
302 double massDiff = massDifference(particle, daughters);
303 double massDiffErr = massDifferenceError(particle, daughters);
304
305 double massDiffNominal = particle->getPDGMass() - particle->getDaughter(daughter)->getPDGMass();
306
307 return (massDiff - massDiffNominal) / massDiffErr;
308 }
309
310 // Decay Kinematics -------------------------------------------------------
311 double particleDecayAngle(const Particle* particle, const std::vector<double>& daughters)
312 {
313 if (!particle)
314 return Const::doubleNaN;
315
316 PCmsLabTransform T;
317 ROOT::Math::PxPyPzEVector m = - T.getBeamFourMomentum();
318
319 ROOT::Math::PxPyPzEVector motherMomentum = particle->get4Vector();
320 ROOT::Math::XYZVector motherBoost = motherMomentum.BoostToCM();
321
322 long daughter = std::lround(daughters[0]);
323 if (daughter >= static_cast<int>(particle->getNDaughters()))
324 return Const::doubleNaN;
325
326 ROOT::Math::PxPyPzEVector daugMomentum = particle->getDaughter(daughter)->get4Vector();
327 daugMomentum = ROOT::Math::Boost(motherBoost) * daugMomentum;
328
329 m = ROOT::Math::Boost(motherBoost) * m;
330
331 return ROOT::Math::VectorUtil::Angle(daugMomentum, m);
332 }
333
334 double pointingAngle(const Particle* particle, const std::vector<double>& daughters)
335 {
336 if (!particle)
337 return Const::doubleNaN;
338
339 long daughter = std::lround(daughters[0]);
340 if (daughter >= static_cast<int>(particle->getNDaughters()))
341 return Const::doubleNaN;
342
343 if (particle->getDaughter(daughter)->getNDaughters() < 2)
344 return Const::doubleNaN;
345
346 ROOT::Math::XYZVector productionVertex = particle->getVertex();
347 ROOT::Math::XYZVector decayVertex = particle->getDaughter(daughter)->getVertex();
348
349 ROOT::Math::XYZVector vertexDiffVector = decayVertex - productionVertex;
350
351 const auto& frame = ReferenceFrame::GetCurrent();
352 ROOT::Math::PxPyPzEVector daughterMomentumVector = frame.getMomentum(particle->getDaughter(daughter));
353
354 return ROOT::Math::VectorUtil::Angle(daughterMomentumVector, vertexDiffVector);
355 }
356
357 double azimuthalAngleInDecayPlane(const Particle* particle, const std::vector<double>& daughters)
358 {
359 if (!particle)
360 return Const::doubleNaN;
361
362 int nDaughters = static_cast<int>(particle->getNDaughters());
363
364 long daughter1 = std::lround(daughters[0]);
365 long daughter2 = std::lround(daughters[1]);
366 if (daughter1 >= nDaughters || daughter2 >= nDaughters)
367 return Const::doubleNaN;
368
369 PCmsLabTransform T;
370 ROOT::Math::PxPyPzEVector m = T.getBeamFourMomentum();
371 ROOT::Math::PxPyPzEVector p = particle->get4Vector();
372 ROOT::Math::PxPyPzEVector d1 = particle->getDaughter(daughter1)->get4Vector();
373 ROOT::Math::PxPyPzEVector d2 = particle->getDaughter(daughter2)->get4Vector();
374
375 ROOT::Math::PxPyPzEVector l;
376 l.SetPx(p.Py() * (d1.Pz() * d2.E() - d1.E() * d2.Pz()) + p.Pz() * (d1.E() * d2.Py() - d1.Py() * d2.E())
377 + p.E() * (d1.Py() * d2.Pz() - d1.Pz() * d2.Py()));
378 l.SetPy(p.Px() * (d1.E() * d2.Pz() - d1.Pz() * d2.E()) + p.Pz() * (d1.Px() * d2.E() - d1.E() * d2.Px())
379 + p.E() * (d1.Pz() * d2.Px() - d1.Px() * d2.Pz()));
380 l.SetPz(p.Px() * (d1.Py() * d2.E() - d1.E() * d2.Py()) + p.Py() * (d1.E() * d2.Px() - d1.Px() * d2.E())
381 + p.E() * (d1.Px() * d2.Py() - d1.Py() * d2.Px()));
382 l.SetE(-(p.Px() * (d1.Pz() * d2.Py() - d1.Py() * d2.Pz()) + p.Py() * (d1.Px() * d2.Pz() - d1.Pz() * d2.Px())
383 + p.Pz() * (d1.Py() * d2.Px() - d1.Px() * d2.Py())));
384
385 double m_times_p = m.Dot(p);
386 double m_times_l = m.Dot(l);
387 double m_times_d1 = m.Dot(d1);
388 double l_times_d1 = l.Dot(d1);
389 double d1_times_p = d1.Dot(p);
390 double m_abs = TMath::Sqrt(pow(m_times_p / p.M(), 2) - m.M2());
391 double d1_abs = TMath::Sqrt(pow(d1_times_p / p.M(), 2) - d1.M2());
392 double cos_phi = -m_times_l / (m_abs * TMath::Sqrt(-l.M2()));
393 double m_parallel_abs = m_abs * TMath::Sqrt(1 - cos_phi * cos_phi);
394 double m_parallel_times_d1 = m_times_p * d1_times_p / p.M2() + m_times_l * l_times_d1 / l.M2() - m_times_d1;
395
396 return TMath::ACos(-m_parallel_times_d1 / (m_parallel_abs * d1_abs));
397 }
398
399 double Constant(const Particle*, const std::vector<double>& constant)
400 {
401 return constant[0];
402 }
403
404
405
406 VARIABLE_GROUP("ParameterFunctions");
407 REGISTER_VARIABLE("NumberOfMCParticlesInEvent(pdgcode)", NumberOfMCParticlesInEvent, R"DOC(
408 [Eventbased] Returns number of MC Particles (including anti-particles) with the given pdgcode in the event.
409
410 Used in the FEI to determine to calculate reconstruction efficiencies.
411
412 The variable is event-based and does not need a valid particle pointer as input.)DOC");
413 REGISTER_VARIABLE("isAncestorOf(i, j, ...)", isAncestorOf, R"DOC(
414 Returns a positive integer if daughter at position particle->daughter(i)->daughter(j)... is an ancestor of the related MC particle, 0 otherwise.
415
416 Positive integer represents the number of steps needed to get from final MC daughter to ancestor.
417 If any particle or MCparticle is a nullptr, NaN is returned. If MC relations of any particle doesn't exist, -1.0 is returned.)DOC");
418 REGISTER_VARIABLE("hasAncestor(PDG, abs)", hasAncestor, R"DOC(
419
420 Returns a positive integer if an ancestor with the given PDG code is found, 0 otherwise.
421
422 The integer is the level where the ancestor was found, 1: first mother, 2: grandmother, etc.
423
424 Second argument is optional, 1 means that the sign of the PDG code is taken into account, default is 0.
425
426 If there is no MC relations found, -1 is returned. In case of nullptr particle, NaN is returned.)DOC");
427 REGISTER_VARIABLE("daughterInvariantMass(i, j, ...)", daughterInvariantMass, R"DOC(
428Returns invariant mass of the given daughter particles. E.g.:
429
430* daughterInvariantMass(0, 1) returns the invariant mass of the first and second daughter.
431* daughterInvariantMass(0, 1, 2) returns the invariant mass of the first, second and third daughter.
432
433Useful to identify intermediate resonances in a decay, which weren't reconstructed explicitly.
434
435Returns NaN if particle is nullptr or if the given daughter-index is out of bound (>= number of daughters).
436
437)DOC", "GeV/:math:`\\text{c}^2`");
438 MAKE_DEPRECATED("daughterInvariantMass", true, "light-2203-zeus", R"DOC(
439 The variable `daughterInvM` provides exactly the same functionality.)DOC");
440 REGISTER_VARIABLE("daughterMCInvariantMass(i, j, ...)", daughterMCInvariantMass, R"DOC(
441Returns true invariant mass of the given daughter particles, same behaviour as daughterInvariantMass variable.
442
443)DOC", "GeV/:math:`\\text{c}^2`");
444 REGISTER_VARIABLE("decayAngle(i)", particleDecayAngle, R"DOC(
445Angle in the mother's rest frame between the reverted CMS momentum vector and the direction of the i-th daughter
446
447)DOC", "rad");
448 REGISTER_VARIABLE("pointingAngle(i)", pointingAngle, R"DOC(
449Angle between i-th daughter's momentum vector and vector connecting production and decay vertex of i-th daughter.
450This makes only sense if the i-th daughter has itself daughter particles and therefore a properly defined vertex.
451
452)DOC", "rad");
453 REGISTER_VARIABLE("azimuthalAngleInDecayPlane(i, j)", azimuthalAngleInDecayPlane, R"DOC(
454Azimuthal angle of i-th daughter in decay plane towards projection of particle momentum into decay plane.
455
456First we define the following symbols:
457
458* P: four-momentum vector of decaying particle in whose decay plane the azimuthal angle is measured
459* M: "mother" of p, however not necessarily the direct mother but any higher state, here the CMS itself is chosen
460* D1: daughter for which the azimuthal angle is supposed to be calculated
461* D2: another daughter needed to span the decay plane
462* L: normal to the decay plane (four-component vector)
463
464L can be defined via the following relation:
465
466.. math:: L^{\sigma} = \delta^{\sigma\nu} \epsilon_{\mu\nu\alpha\beta} P^{\mu}D1^{\alpha}D2^{\beta}
467
468The azimuthal angle is given by
469
470.. math:: \phi \equiv \cos^{-1} \left(\frac{-\vec{M_{\parallel}} \cdot \vec{D1}}{|\vec{M_{\parallel}}| \cdot |\vec{D1}|}\right)
471
472For a frame independent formulation the three component vectors need to be written via invariant four-momentum vectors.
473
474.. math::
475
476 -\vec{M_{\parallel}} \cdot \vec{D1} &= \biggl[M - \frac{(M \cdot L)L}{L^2}\biggr] \cdot D1 - \frac{(M \cdot P)(D1 \cdot P)}{m^2_P}\\
477 |\vec{M_{\parallel}}| &= |\vec{M}| \sqrt{1 - \cos^2 \psi}\\
478 |\vec{M}| &= \sqrt{\frac{(M \cdot P)^2}{m^2_P} - m^2_M}\\
479 \cos \psi &= \frac{\vec{M} \cdot \vec{L}}{|\vec{M}| \cdot |\vec{L}|} = \frac{-M \cdot L}{|\vec{M}| \cdot \sqrt{-L^2}}\\
480 |\vec{D1}| &= \sqrt{\frac{(D1 \cdot P)^2}{m^2_P} - m^2_{D1}}
481
482)DOC", "rad");
483
484 REGISTER_VARIABLE("massDifference(i)", massDifference, "Difference in invariant masses of this particle and its i-th daughter\n\n",
485 "GeV/:math:`\\text{c}^2`");
486 REGISTER_VARIABLE("massDifferenceError(i)", massDifferenceError,
487 "Estimated uncertainty on difference in invariant masses of this particle and its i-th daughter\n\n", "GeV/:math:`\\text{c}^2`");
488 REGISTER_VARIABLE("massDifferenceSignificance(i)", massDifferenceSignificance,
489 "Signed significance of the deviation from the nominal mass difference of this particle and its i-th daughter [(massDiff - NOMINAL_MASS_DIFF)/ErrMassDiff]");
490
491 REGISTER_VARIABLE("constant(float i)", Constant, R"DOC(
492 Returns i.
493
494 Useful for debugging purposes and in conjunction with the formula meta-variable.)DOC");
495
496 REGISTER_VARIABLE("randomChoice(i, j, ...)", RandomChoice, R"DOC(
497 Returns random element of given numbers.
498
499 Useful for testing purposes.)DOC");
500
501
502 }
504}
static const double doubleNaN
quiet_NaN
Definition Const.h:703
@ c_PrimaryParticle
bit 0: Particle is primary particle.
Definition MCParticle.h:47
static const ReferenceFrame & GetCurrent()
Get current rest frame.
#define MAKE_DEPRECATED(name, make_fatal, version, description)
Registers a variable as deprecated.
Definition Manager.h:456
Abstract base class for different kinds of events.