Belle II Software light-2609-luna
HelicityVariables.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/HelicityVariables.h>
11
12#include <analysis/variables/EventVariables.h>
13
14#include <analysis/dataobjects/Particle.h>
15
16#include <analysis/utility/ReferenceFrame.h>
17#include <analysis/VariableManager/Manager.h>
18
19#include <framework/gearbox/Const.h>
20
21#include <Math/Boost.h>
22#include <Math/Vector4D.h>
23#include <Math/VectorUtil.h>
24using namespace ROOT::Math;
25#include <cmath>
26
27namespace Belle2 {
32 namespace Variable {
33
34 double cosHelicityAngleMomentum(const Particle* part)
35 {
36
37 const auto& frame = ReferenceFrame::GetCurrent();
38 XYZVector motherBoost = frame.getMomentum(part).BoostToCM();
39 PxPyPzEVector motherMomentum = frame.getMomentum(part);
40 const auto& daughters = part -> getDaughters() ;
41
42 if (daughters.size() == 2) {
43
44 // Only for pi0 -> gamma gamma, gamma -> e+ e-
45 bool isOneConversion = false;
46 if (part->getPDGCode() == Const::pi0.getPDGCode()) {
47 for (const auto* idaughter : daughters) {
48 // both daughter must be gamma
49 if (idaughter -> getPDGCode() != Const::photon.getPDGCode()) {
50 isOneConversion = false;
51 break;
52 }
53 // check if one of gammas has two daughters
54 if (idaughter -> getNDaughters() == 2) {
55 if (std::abs(idaughter -> getDaughters()[0]-> getPDGCode()) == Const::electron.getPDGCode()
56 && std::abs(idaughter -> getDaughters()[1]-> getPDGCode()) == Const::electron.getPDGCode()) { // e+ e-
57 isOneConversion = true;
58 }
59 }
60 }
61 }
62
63 if (isOneConversion) {
64 B2WARNING("cosHelicityAngleMomentum: Special treatment for pi0->gamma gamma, gamma -> e+ e-, is called. "
65 "This treatment is going to be deprecated and we recommend using ``cosHelicityAngleMomentumPi0Dalitz`` "
66 "If you find this message in another case, it must be a bug. Please report it to the software mailing list.");
67
68 //only for pi0 decay where one gamma converts
69 PxPyPzEVector pGamma;
70 for (const auto* idaughter : daughters) {
71 if (idaughter -> getNDaughters() == 2) continue;
72 else pGamma = frame.getMomentum(idaughter);
73 }
74
75 pGamma = Boost(motherBoost) * pGamma;
76
77 return VectorUtil::CosTheta(motherMomentum, pGamma);
78
79 } else {
80 PxPyPzEVector pDaughter1 = frame.getMomentum(daughters[0]);
81 PxPyPzEVector pDaughter2 = frame.getMomentum(daughters[1]);
82
83 pDaughter1 = Boost(motherBoost) * pDaughter1;
84 pDaughter2 = Boost(motherBoost) * pDaughter2;
85
86 PxPyPzEVector p12 = pDaughter2 - pDaughter1;
87
88 return VectorUtil::CosTheta(motherMomentum, p12);
89 }
90
91 } else if (daughters.size() == 3) {
92
93 PxPyPzEVector pDaughter1 = frame.getMomentum(daughters[0]);
94 PxPyPzEVector pDaughter2 = frame.getMomentum(daughters[1]);
95 PxPyPzEVector pDaughter3 = frame.getMomentum(daughters[2]);
96
97 pDaughter1 = Boost(motherBoost) * pDaughter1;
98 pDaughter2 = Boost(motherBoost) * pDaughter2;
99 pDaughter3 = Boost(motherBoost) * pDaughter3;
100
101 XYZVector p12 = (pDaughter2 - pDaughter1).Vect();
102 XYZVector p13 = (pDaughter3 - pDaughter1).Vect();
103
104 XYZVector n = p12.Cross(p13);
105
106 return VectorUtil::CosTheta(motherMomentum, n);
107
108 } else return Const::doubleNaN;
109
110 }
111
112 double cosHelicityAngleMomentumPi0Dalitz(const Particle* part)
113 {
114
115 const auto& frame = ReferenceFrame::GetCurrent();
116 XYZVector motherBoost = frame.getMomentum(part).BoostToCM();
117 PxPyPzEVector motherMomentum = frame.getMomentum(part);
118 const auto& daughters = part -> getDaughters() ;
119
120
121 if (daughters.size() == 3) {
122
123 PxPyPzEVector pGamma;
124
125 for (const auto* idaughter : daughters) {
126 if (std::abs(idaughter -> getPDGCode()) == Const::photon.getPDGCode()) {
127 pGamma = frame.getMomentum(idaughter);
128 break;
129 }
130 }
131 pGamma = Boost(motherBoost) * pGamma;
132
133 return VectorUtil::CosTheta(motherMomentum, pGamma);
134
135 } else if (daughters.size() == 2) { // only for pi0 -> gamma gamma, gamma -> e+ e-
136
137 PxPyPzEVector pGamma;
138
139 // both daughters must be gamma
140 if (daughters[0] -> getPDGCode() != Const::photon.getPDGCode() or
141 daughters[1] -> getPDGCode() != Const::photon.getPDGCode())
142 return Const::doubleNaN;
143
144 if (daughters[0] -> getNDaughters() == 2 and daughters[1] -> getNDaughters() == 0) {
145 if (std::abs(daughters[0] -> getDaughters()[0]-> getPDGCode()) == Const::electron.getPDGCode()
146 && std::abs(daughters[0] -> getDaughters()[1]-> getPDGCode()) == Const::electron.getPDGCode()) { // e+ e-
147 pGamma = frame.getMomentum(daughters[1]);
148 } else {
149 return Const::doubleNaN;
150 }
151 } else if (daughters[0] -> getNDaughters() == 0 and daughters[1] -> getNDaughters() == 2) {
152 if (std::abs(daughters[1] -> getDaughters()[0]-> getPDGCode()) == Const::electron.getPDGCode()
153 && std::abs(daughters[1] -> getDaughters()[1]-> getPDGCode()) == Const::electron.getPDGCode()) { // e+ e-
154 pGamma = frame.getMomentum(daughters[0]);
155 } else {
156 return Const::doubleNaN;
157 }
158 } else {
159 return Const::doubleNaN;
160 }
161
162 pGamma = Boost(motherBoost) * pGamma;
163
164 return VectorUtil::CosTheta(motherMomentum, pGamma);
165
166 } else return Const::doubleNaN;
167
168 }
169
170
171 double cosHelicityAngleBeamMomentum(const Particle* mother, const std::vector<double>& index)
172 {
173 if (index.size() != 1) {
174 B2FATAL("Wrong number of arguments for cosHelicityAngleIfCMSIsTheMother");
175 }
176
177 int idau = std::lround(index[0]);
178
179 const Particle* part = mother->getDaughter(idau);
180 if (!part) {
181 B2FATAL("Couldn't find the " << idau << "th daughter");
182 }
183
184 PxPyPzEVector beam4Vector(getBeamPx(nullptr), getBeamPy(nullptr), getBeamPz(nullptr), getBeamE(nullptr));
185 PxPyPzEVector part4Vector = part->get4Vector();
186 PxPyPzEVector mother4Vector = mother->get4Vector();
187
188 XYZVector motherBoost = mother4Vector.BoostToCM();
189
190 beam4Vector = Boost(motherBoost) * beam4Vector;
191 part4Vector = Boost(motherBoost) * part4Vector;
192
193 return - VectorUtil::CosTheta(part4Vector, beam4Vector);
194 }
195
196
197 double cosHelicityAngle(const Particle* mother, const std::vector<double>& indices)
198 {
199 if (indices.size() != 2) {
200 B2FATAL("Wrong number of arguments for cosHelicityAngleIfRefFrameIsTheDaughter: two are needed.");
201 }
202
203 int iDau = std::lround(indices[0]);
204 int iGrandDau = std::lround(indices[1]);
205
206 const Particle* daughter = mother->getDaughter(iDau);
207 if (!daughter)
208 B2FATAL("Couldn't find the " << iDau << "th daughter.");
209
210 const Particle* grandDaughter = daughter->getDaughter(iGrandDau);
211 if (!grandDaughter)
212 B2FATAL("Couldn't find the " << iGrandDau << "th daughter of the " << iDau << "th daughter.");
213
214 PxPyPzEVector mother4Vector = mother->get4Vector();
215 PxPyPzEVector daughter4Vector = daughter->get4Vector();
216 PxPyPzEVector grandDaughter4Vector = grandDaughter->get4Vector();
217
218 XYZVector daughterBoost = daughter4Vector.BoostToCM();
219
220 // We boost the momentum of the mother and of the granddaughter to the reference frame of the daughter.
221 grandDaughter4Vector = Boost(daughterBoost) * grandDaughter4Vector;
222 mother4Vector = Boost(daughterBoost) * mother4Vector;
223
224 return - VectorUtil::CosTheta(grandDaughter4Vector, mother4Vector);
225 }
226
227 double cosAcoplanarityAngle(const Particle* mother, const std::vector<double>& granddaughters)
228 {
229 if (granddaughters.size() != 2) {
230 B2FATAL("Wrong number of arguments for cosAcoplanarityAngleIfRefFrameIsTheMother: two are needed.");
231 }
232
233 if (mother->getNDaughters() != 2)
234 B2FATAL("cosAcoplanarityAngleIfRefFrameIsTheMother: this variable works only for two-body decays.");
235
236 int iGrandDau1 = std::lround(granddaughters[0]);
237 int iGrandDau2 = std::lround(granddaughters[1]);
238
239 const Particle* daughter1 = mother->getDaughter(0);
240 const Particle* daughter2 = mother->getDaughter(1);
241
242 const Particle* grandDaughter1 = daughter1->getDaughter(iGrandDau1);
243 if (!grandDaughter1)
244 B2FATAL("Couldn't find the " << iGrandDau1 << "th daughter of the first daughter.");
245
246 const Particle* grandDaughter2 = daughter2->getDaughter(iGrandDau2);
247 if (!grandDaughter2)
248 B2FATAL("Couldn't find the " << iGrandDau2 << "th daughter of the second daughter.");
249
250 PxPyPzEVector mother4Vector = mother->get4Vector();
251 PxPyPzEVector daughter4Vector1 = daughter1->get4Vector();
252 PxPyPzEVector daughter4Vector2 = daughter2->get4Vector();
253 PxPyPzEVector grandDaughter4Vector1 = grandDaughter1->get4Vector();
254 PxPyPzEVector grandDaughter4Vector2 = grandDaughter2->get4Vector();
255
256 XYZVector motherBoost = mother4Vector.BoostToCM();
257 XYZVector daughter1Boost = daughter4Vector1.BoostToCM();
258 XYZVector daughter2Boost = daughter4Vector2.BoostToCM();
259
260 // Boosting daughters to reference frame of the mother
261 daughter4Vector1 = Boost(motherBoost) * daughter4Vector1;
262 daughter4Vector2 = Boost(motherBoost) * daughter4Vector2;
263
264 // Boosting each granddaughter to reference frame of its mother
265 grandDaughter4Vector1 = Boost(daughter1Boost) * grandDaughter4Vector1;
266 grandDaughter4Vector2 = Boost(daughter2Boost) * grandDaughter4Vector2;
267
268 // We calculate the normal vectors of the decay two planes
269 XYZVector normalVector1 = daughter4Vector1.Vect().Cross(grandDaughter4Vector1.Vect());
270 XYZVector normalVector2 = daughter4Vector2.Vect().Cross(grandDaughter4Vector2.Vect());
271
272 return VectorUtil::CosTheta(normalVector1, normalVector2);
273 }
274
275 double cosHelicityAnglePrimary(const Particle* part)
276 {
277 return part->getCosHelicity();
278 }
279
280 double cosHelicityAngleDaughter(const Particle* part, const std::vector<double>& indices)
281 {
282 if ((indices.size() == 0) || (indices.size() > 2)) {
283 B2FATAL("Wrong number of arguments for cosHelicityAngleDaughter: one or two are needed.");
284 }
285
286 int iDaughter = std::lround(indices[0]);
287 int iGrandDaughter = 0;
288 if (indices.size() == 2) {
289 iGrandDaughter = std::lround(indices[1]);
290 }
291
292 return part->getCosHelicityDaughter(iDaughter, iGrandDaughter);
293 }
294
295 double acoplanarityAngle(const Particle* part)
296 {
297 return part->getAcoplanarity();
298 }
299
300
301 double cosHelicityAngleForQuasiTwoBodyDecay(const Particle* mother, const std::vector<double>& indices)
302 {
303 if (indices.size() != 2) {
304 B2FATAL("Wrong number of arguments for cosHelicityAngleForQuasiTwoBodyDecay: two are needed.");
305 }
306
307 if (mother->getNDaughters() != 3)
308 return Const::doubleNaN;
309
310 int iDau = std::lround(indices[0]);
311 int jDau = std::lround(indices[1]);
312
313 const Particle* iDaughter = mother->getDaughter(iDau);
314 if (!iDaughter)
315 return Const::doubleNaN;
316
317 const Particle* jDaughter = mother->getDaughter(jDau);
318 if (!jDaughter)
319 return Const::doubleNaN;
320
321 PxPyPzEVector mother4Vector = mother->get4Vector();
322 PxPyPzEVector iDaughter4Vector = iDaughter->get4Vector();
323 PxPyPzEVector jDaughter4Vector = jDaughter->get4Vector();
324
325 PxPyPzEVector resonance4Vector = iDaughter4Vector + jDaughter4Vector;
326 XYZVector resonanceBoost = resonance4Vector.BoostToCM();
327
328 iDaughter4Vector = Boost(resonanceBoost) * iDaughter4Vector;
329 mother4Vector = Boost(resonanceBoost) * mother4Vector;
330
331 return - VectorUtil::CosTheta(iDaughter4Vector, mother4Vector);
332 }
333
334 Manager::FunctionPtr momentaTripleProduct(const std::vector<std::string>& arguments)
335 {
336 if (arguments.size() != 3) {
337 B2FATAL("Wrong number of arguments for momentaTripleProduct: three (particles) are needed.");
338 }
339
340 // wrap with func and return it
341 auto func = [arguments](const Particle * mother) -> double {
342 auto iDau = arguments[0];
343 auto jDau = arguments[1];
344 auto kDau = arguments[2];
345
346 const Particle* iDaughter = mother->getParticleFromGeneralizedIndexString(iDau);
347 if (!iDaughter) B2FATAL("Couldn't find the " << iDau << "th daughter.");
348 const Particle* jDaughter = mother->getParticleFromGeneralizedIndexString(jDau);
349 if (!jDaughter) B2FATAL("Couldn't find the " << jDau << "th daughter.");
350 const Particle* kDaughter = mother->getParticleFromGeneralizedIndexString(kDau);
351 if (!kDaughter) B2FATAL("Couldn't find the " << kDau << "th daughter.");
352
353 PxPyPzEVector mother4Vector = mother->get4Vector();
354 PxPyPzEVector iDaughter4Vector = iDaughter->get4Vector();
355 PxPyPzEVector jDaughter4Vector = jDaughter->get4Vector();
356 PxPyPzEVector kDaughter4Vector = kDaughter->get4Vector();
357
358 XYZVector motherBoost = mother4Vector.BoostToCM();
359
360 // We boost the momenta of offspring to the reference frame of the mother.
361 iDaughter4Vector = Boost(motherBoost) * iDaughter4Vector;
362 jDaughter4Vector = Boost(motherBoost) * jDaughter4Vector;
363 kDaughter4Vector = Boost(motherBoost) * kDaughter4Vector;
364
365 // cross product: p_j x p_k
366 XYZVector jkDaughterCrossProduct = jDaughter4Vector.Vect().Cross(kDaughter4Vector.Vect());
367 // triple product: p_i * (p_j x p_k)
368 return iDaughter4Vector.Vect().Dot(jkDaughterCrossProduct) ;
369 };
370 return func;
371 }
372
373
374 VARIABLE_GROUP("Helicity variables");
375
376 REGISTER_VARIABLE("cosHelicityAngleMomentum", cosHelicityAngleMomentum, R"DOC(
377Returns the cosine of an angle whose definition changes depending on how many daughters the particle has; otherwise it returns 0.0.
378
379.. topic:: For two daughters
380
381 The angle is between the vector defined by the momentum difference of the two daughters in the frame of the given particle
382 (the mother) and the momentum of the given particle in the lab frame.
383
384.. topic:: For three daughters
385
386 The angle is between the normal vector of the plane defined by the momenta of the daughters in the frame of the given particle
387 (the mother) and the momentum of the given particle in the lab frame.
388
389)DOC");
390 REGISTER_VARIABLE("cosHelicityAngleMomentumPi0Dalitz", cosHelicityAngleMomentumPi0Dalitz, R"DOC(
391Returns the cosine of the angle of the photon in the frame of the given particle (the mother) and the momentum
392of the given particle in the lab frame, otherwise it returns 0.0.
393
394.. attention::
395
396 This variable should only be used for the decays :math:`\pi^0 \to e^+ e^- \gamma` and
397 :math:`\pi^0 \to \gamma \gamma, \gamma \to e^+ e^-`.
398
399)DOC");
400 REGISTER_VARIABLE("cosHelicityAngleBeamMomentum(i)", cosHelicityAngleBeamMomentum, R"DOC(
401Returns the cosine of the helicity angle of the :math:`i`-th daughter of the particle provided,
402assuming that the mother of the provided particle corresponds to the centre-of-mass system, whose parameters are
403automatically loaded by the function, given the accelerator's conditions.
404
405)DOC");
406 REGISTER_VARIABLE("cosHelicityAngle(i, j)", cosHelicityAngle, R"DOC(
407Returns the cosine of the helicity angle between the momentum of the selected granddaughter (index ``j``) and
408the direction opposite to the momentum of the provided particle, both calculated in the reference frame of the selected daughter (index ``i``).
409This variable is useful for angular analyses of :math:`B`-meson decays into two vector particles.
410
411For example, for the decay :math:`B^0 \to \left(J/\psi \to \mu^+ \mu^-\right) \left(K^{*0} \to K^+ \pi^-\right)`, if the provided particle
412is :math:`B^0` and the selected indices are (0, 0), the variable will return the angle between the momentum of the :math:`\mu^+` and the
413direction opposite to the momentum of the :math:`B^0`, with both momenta in the rest frame of the :math:`J/\psi`.
414
415.. seealso:: The polarisation of :math:`B` decays is reviewed in this `PDG review <https://pdg.lbl.gov/2026/web/viewer.html?file=../reviews/rpp2026-rev-b-decays-polarization.pdf>`_.
416
417)DOC");
418 REGISTER_VARIABLE("cosAcoplanarityAngle(i, j)", cosAcoplanarityAngle, R"DOC(
419Returns the cosine of the acoplanarity angle which, for a two-body decay, is defined as the angle between the two normal vectors
420of the decay planes in the reference frame of the mother. Each normal vector is defined as the cross product of the momentum of
421one daughter (in the frame of the mother) and the momentum of one of its granddaughters (in the frame of the daughter). The two integers
422``i`` and ``j`` index the granddaughters for the first and second daughters, respectively.
423
424For example, for the decay :math:`B^0 \to \left(J/\psi \to \mu^+ \mu^-\right) \left(K^{*0} \to K^+ \pi^-\right)`, if the provided particle
425is :math:`B^0` and the selected indices are (0, 0), the variable will return the acoplanarity using the :math:`\mu^+` and :math:`K^+` granddaughters.
426
427.. seealso:: The polarisation of :math:`B` decays is reviewed in this `PDG review <https://pdg.lbl.gov/2026/web/viewer.html?file=../reviews/rpp2026-rev-b-decays-polarization.pdf>`_.
428
429)DOC");
430 REGISTER_VARIABLE("cosHelicityAnglePrimary", cosHelicityAnglePrimary, R"DOC(
431Returns the cosine of the helicity angle (see ``cosHelicityAngle``) assuming the CM system as the mother rest frame.
432
433.. seealso:: The polarisation of :math:`B` decays is reviewed in this `PDG review <https://pdg.lbl.gov/2026/web/viewer.html?file=../reviews/rpp2026-rev-b-decays-polarization.pdf>`_.
434
435)DOC");
436 REGISTER_VARIABLE("cosHelicityAngleDaughter(i[, j])", cosHelicityAngleDaughter, R"DOC(
437Returns the cosine of the helicity angle of the daughter at index ``i``. The optional second argument is the index of the granddaughter that defines the angle
438and by default is 0.
439
440For example, for the decay: :math:`B^0 \to \left(J/\psi \to \mu^+ \mu^-\right) \left(K^{*0} \to K^+ \pi^-\right)`, if the provided particle is :math:`B^0`
441and the selected index is 0, the variable will return the helicity angle of the :math:`\mu^+`. If the selected index is 1 the variable will return the
442helicity angle of the :math:`K^+` (defined via the rest frame of the :math:`K^{*0}`). In rare cases, if one wanted the helicity angle of the second granddaughter,
443indices (1, 1) would return the helicity angle of the :math:`\pi^-`.
444
445.. seealso:: The polarisation of :math:`B` decays is reviewed in this `PDG review <https://pdg.lbl.gov/2026/web/viewer.html?file=../reviews/rpp2026-rev-b-decays-polarization.pdf>`_.
446
447)DOC");
448 REGISTER_VARIABLE("acoplanarityAngle", acoplanarityAngle, R"DOC(
449Returns the acoplanarity angle, as described in the definition for ``cosAcoplanarityAngle``, assuming a two body decay of the particle and
450its daughters.
451
452.. seealso:: The polarisation of :math:`B` decays is reviewed in this `PDG review <https://pdg.lbl.gov/2026/web/viewer.html?file=../reviews/rpp2026-rev-b-decays-polarization.pdf>`_.
453
454)DOC", "rad");
455 REGISTER_VARIABLE("cosHelicityAngleForQuasiTwoBodyDecay(i, j)", cosHelicityAngleForQuasiTwoBodyDecay, R"DOC(
456Returns the cosine of the helicity angle between the momentum of the provided particle and the momentum of the daughter at
457index :math:`i` in the reference frame of the two selected daughters (at index :math:`i` and :math:`j`) combined.
458
459For example, for the decay :math:`\bar{B}^0 \to D^+ K^- K^{*0}`, if the provided particle is :math:`\bar{B}^0` and
460the selected indices are (1, 2), the variable will return the angle between the momentum of the :math:`\bar{B}^0` and
461the momentum of the :math:`K^-`, both in the rest frame of the :math:`K^- K^{*0}` combination.
462
463.. important::
464 The variable is supposed to be used for analyses of quasi-two-body decays. The number of daughters of the given particle
465 must be three, otherwise the variable returns ``NaN``.
466
467)DOC");
468 REGISTER_METAVARIABLE("momentaTripleProduct(i,j,k)", momentaTripleProduct, R"DOC(
469Returns a triple product of the three momenta as defined by
470
471.. math::
472 C_T=\vec{p}_i\cdot(\vec{p}_j\times\vec{p}_k)
473
474where :math:`i, j` and :math:`k` are indices of the daughters of the provided particle.
475
476For example, in a four body decay :math:`M \rightarrow d_1 d_2 d_3 d_4`, for the selected indices (0, 1, 2), the variable will return
477:math:`C_T` calculated using the momenta of the particles :math:`d_1, d_2` and :math:`d_3`. In instances of secondary decays such as
478:math:`M \rightarrow R (\rightarrow d_1 d_2) d_3 d_4`, the selected indices (0:0, 1, 2) returns :math:`C_T` calculated using the
479momenta of the particles :math:`d_1, d_3` and :math:`d_4`.
480
481)DOC", Manager::VariableDataType::c_double);
482
483 }
485}
int getPDGCode() const
PDG code.
Definition Const.h:474
static const ParticleType pi0
neutral pion particle
Definition Const.h:675
static const double doubleNaN
quiet_NaN
Definition Const.h:704
static const ParticleType photon
photon particle
Definition Const.h:674
static const ChargedStable electron
electron particle
Definition Const.h:660
static const ReferenceFrame & GetCurrent()
Get current rest frame.
std::function< VarVariant(const Particle *)> FunctionPtr
functions stored take a const Particle* and return VarVariant.
Definition Manager.h:112
Abstract base class for different kinds of events.