Usage¶
The base of phasespace
is the Particle
object.
This object, which represents a particle, either stable or decaying, has only one mandatory argument, its name.
In most cases (except for the top particle of a decay), one wants to also specify its mass, which can be either
a number or tf.constant
, or a function.
Functions are used to specify the mass of particles such as resonances, which are not fixed but vary according to
a broad distribution.
These mass functions get three arguments, and must return a TensorFlow
Tensor:
- The minimum mass allowed by the decay chain.
- The maximum mass available.
- The number of events to generate, i.e., the size of the output tensor, which must be of shape (1,
n_events
).
This function signature allows to handle threshold effects cleanly, giving enough information to produce kinematically
allowed decays (NB: phasespace
will throw an error if a kinematically forbidden decay is requested).
With these considerations in mind, one can build a decay chain by using the set_children
method of the Particle
class (which returns the class itself). As an example, to build the \(B^{0}\to K^{*}\gamma\) decay in which
\(K^*\to K\pi\), we would write:
from phasespace import Particle
KSTARZ_MASS = 895.81
PION_MASS = 139.57018
KAON_MASS = 493.677
pion = Particle('pi+', PION_MASS)
kaon = Particle('K+', KAON_MASS)
kstar = Particle('K*', KSTARZ_MASS).set_children(pion, kaon)
gamma = Particle('gamma', 0)
bz = Particle('B0').set_children(kstar, gamma)
The graph can then be generated by using the generate
method, which, similarly to TGenPhaseSpace
, gets an input
4-vector (shape (4,)) of the top particle, and, optionally, the number of events to generate.
If the input 4-vector is of shape (4, n), then n events are generated.
Since generate
just creates a TensorFlow
graph, we need to use tf.Session.run()
to actually run the generation:
N_EVENTS = 1000
B0_MASS = 5279.58
B0_AT_REST = [0.0, 0.0, 0.0, B0_MASS]
with tf.Session() as sess:
weights, particles = sess.run(bz.generate(B0_AT_REST, N_EVENTS))
As discussed, this would be equivalent to:
B0_MASS = 5279.58
B0s_AT_REST = [[0.0, 0.0, 0.0, B0_MASS] for _ in range(N_EVENTS)]
with tf.Session() as sess:
weights, particles = sess.run(bz.generate(B0s_AT_REST))
This latter approach can be useful if the momentum of the top particle is generated according to
some distribution, for example the kinematics of the LHC (see test_kstargamma_kstarnonresonant_lhc
and test_k1gamma_kstarnonresonant_lhc
in tests/test_physics.py
to see how this could be done).
The objects returned by Particle.generate
are the normalized weights vector (the weight of each event divided by
the maximum size of the phase space given the decay chain), of shape (N_EVENTS, ), and a dictionary containing
the generated 4-momenta for each particle, of shape (4, N_EVENTS), labelled by their name:
>>> particles
{'K*': array([[-2259.88717495, 742.20158838, -1419.57804967, ...,
385.51632682, 890.89417859, -1938.80489221],
[ -491.3119786 , -2348.67021741, -2049.19459865, ...,
-932.58261761, -1054.16217965, -1669.40481126],
[-1106.5946257 , 711.27644522, -598.85626591, ...,
-2356.84025605, -2160.57372728, -164.77965753],
[ 2715.78804872, 2715.78804872, 2715.78804872, ...,
2715.78804872, 2715.78804872, 2715.78804872]]),
'K+': array([[-1918.74294565, 363.10302225, -830.13803095, ...,
9.28960349, 850.87382095, -895.29815921],
[ -566.15415012, -956.94044749, -1217.14751182, ...,
-243.52446264, -1095.04308712, -1078.03237584],
[-1108.26109897, 534.79579335, -652.41135612, ...,
-901.56453631, -2069.39723754, -244.1159568 ],
[ 2339.67191226, 1255.90698132, 1685.21060224, ...,
1056.37401241, 2539.53293518, 1505.66336806]]),
'gamma': array([[2259.88717495, -742.20158838, 1419.57804967, ..., -385.51632682,
-890.89417859, 1938.80489221],
[ 491.3119786 , 2348.67021741, 2049.19459865, ..., 932.58261761,
1054.16217965, 1669.40481126],
[1106.5946257 , -711.27644522, 598.85626591, ..., 2356.84025605,
2160.57372728, 164.77965753],
[2563.79195128, 2563.79195128, 2563.79195128, ..., 2563.79195128,
2563.79195128, 2563.79195128]]),
'pi+': array([[ -341.14422931, 379.09856613, -589.44001872, ...,
376.22672333, 40.02035764, -1043.506733 ],
[ 74.84217153, -1391.72976992, -832.04708683, ...,
-689.05815497, 40.88090746, -591.37243542],
[ 1.66647327, 176.48065186, 53.55509021, ...,
-1455.27571974, -91.17648974, 79.33629927],
[ 376.11613646, 1459.8810674 , 1030.57744648, ...,
1659.41403631, 176.25511354, 1210.12468065]])}
If interested in the unnormalized event weights, one can use the generate_unnormalized
method, which
returns the raw weights, the per-event maximum weight and the particle dictionary as before.
To generate the mass of a resonance, we need to give a function as its mass. Following with the same example as above, and approximating the resonance shape by a gaussian, we could write the \(B^{0}\to K^{*}\gamma\) decay chain as:
import tensorflow as tf
import tensorflow_probability as tfp
from phasespace import Particle
KSTARZ_MASS = 895.81
KSTARZ_WIDTH = 47.4
def kstar_mass(min_mass, max_mass, n_events):
kstar_mass = KSTARZ_MASS * ones
min_mass = tf.broadcast_to(min_mass, (1, n_events))
max_mass = tf.broadcast_to(max_mass, (1, n_events))
kstar_mass = tfp.distributions.TruncatedNormal(loc=KSTARZ_MASS,
scale=KSTARZ_WIDTH,
low=min_mass,
high=max_mass).sample()
bz = Particle('B0').set_children(Particle('K*0', mass=kstar_mass)
.set_children(Particle('K+', mass=KAON_MASS),
Particle('pi-', mass=PION_MASS)),
Particle('gamma', mass=0.0))
Shortcut for simple decays¶
The generation of simple n-body decays can be done using the generate
function of phasespace
, which has a
very similar interface to TGenPhaseSpace
. For example, to generate \(B^0\to K\pi\), we would do:
import phasespace
import tensorflow as tf
N_EVENTS = 1000
B0_MASS = 5279.58
B0_AT_REST = [0.0, 0.0, 0.0, B0_MASS]
PION_MASS = 139.57018
KAON_MASS = 493.677
with tf.Session() as sess:
weights, particles = sess.run(phasespace.generate(B0_AT_REST,
[PION_MASS, KAON_MASS],
N_EVENTS))
In this case, since particles are unnamed, the particles
object contains a list of (4, N_EVENTS) tensors in the order
of the particles specified in the generate
call.
Internally, this function builds a decay chain using Particle
, and therefore the same considerations as before apply;
for example, it is possible to not specify the number of events and give a list of input momenta.