Vectores de Lorentz, IDs de partículas PDG, agrupamiento de jets, ¡ay!#

Vectores de Lorentz#

Siguiendo con la filosofía de “muchos paquetes pequeños”, los vectores 2D/3D/Lorentz son manejados por un paquete llamado Vector. Aquí es donde puedes encontrar cálculos como deltaR y transformaciones de coordenadas.

import vector

uno = vector.obj(px=1, py=0, pz=0)
dos = vector.obj(px=0, py=1, pz=1)

uno + dos
MomentumObject3D(px=1, py=1, pz=1)
uno.deltaR(dos)
np.float64(1.8011719796199461)
uno.to_rhophieta()
MomentumObject3D(pt=1.0, phi=0.0, eta=0.0)
dos.to_rhophieta()
MomentumObject3D(pt=1.0, phi=1.5707963267948966, eta=0.881373587019543)

Para integrarse con el resto del ecosistema, Vector debe ser una librería orientada a arreglos. Los arreglos de vectores 2D/3D/Lorentz se procesan en masa.

MomentumNumpy2D, MomentumNumpy3D, MomentumNumpy4D son subtipos de arreglos NumPy: los arreglos de NumPy pueden ser convertidos a estos tipos y obtener todas las funciones vectoriales.

import skhep_testdata, uproot
import awkward as ak
import vector

tree = uproot.open(skhep_testdata.data_path("uproot-Zmumu.root"))["events"]

uno = ak.to_numpy(tree.arrays(filter_name=["E1", "p[xyz]1"]))
dos = ak.to_numpy(tree.arrays(filter_name=["E2", "p[xyz]2"]))

uno.dtype.names = ("E", "px", "py", "pz")
dos.dtype.names = ("E", "px", "py", "pz")

uno = uno.view(vector.MomentumNumpy4D)
dos = dos.view(vector.MomentumNumpy4D)

uno + dos
MomentumNumpy4D([( -7.0508504 ,   1.31371932, -116.3919462 , 142.82374098),
                 ( -6.07723788,   0.86288157, -117.74020834, 144.54679534),
                 ( -5.76527367,   0.72893471, -117.22250173, 143.92770728), ...,
                 (-35.664423  , -24.9064416 , -226.76744871, 250.05025691),
                 (-36.41664408, -25.19899466, -228.38003444, 251.853268  ),
                 (-36.30874217, -25.19705013, -228.65597631, 252.14934978)],
                shape=(2304,), dtype=[('x', '<f8'), ('y', '<f8'), ('z', '<f8'), ('t', '<f8')])
uno.deltaR(dos)
array([3.10530402, 3.10550819, 3.10547199, ..., 2.81363587, 2.81359933,
       2.81354694], shape=(2304,))
uno.to_rhophieta()
MomentumNumpy3D([(44.7322,  2.74126  , -1.21769), (38.8311, -0.440873 , -1.05139),
                 (38.8311, -0.440873 , -1.05139), ...,
                 (32.3997,  0.0370275, -1.57044), (32.3997,  0.0370275, -1.57044),
                 (32.5076,  0.0369644, -1.57078)],
                shape=(2304,), dtype=[('rho', '<f8'), ('phi', '<f8'), ('eta', '<f8')])
dos.to_rhophieta()
MomentumNumpy3D([(37.7582, -0.441078, -1.05138), (44.7322,  2.74126 , -1.21769),
                 (44.3927,  2.7413  , -1.21776), ...,
                 (72.8781, -2.77524 , -1.4827 ), (73.6852, -2.77519 , -1.48227),
                 (73.6852, -2.77519 , -1.48227)],
                shape=(2304,), dtype=[('rho', '<f8'), ('phi', '<f8'), ('eta', '<f8')])

Después de llamar a vector.register_awkward(), "Momentum2D", "Momentum3D", "Momentum4D" son nombres de registros que Awkward Array reconocerá para obtener todas las funciones vectoriales.

vector.register_awkward()

tree = uproot.open(skhep_testdata.data_path("uproot-HZZ.root"))["events"]

array = tree.arrays(filter_name=["Muon_E", "Muon_P[xyz]"])

muones = ak.zip(
    {"px": array.Muon_Px, "py": array.Muon_Py, "pz": array.Muon_Pz, "E": array.Muon_E},
    with_name="Momentum4D",
)
mu1, mu2 = ak.unzip(ak.combinations(muones, 2))

mu1 + mu2
[[{x: -15.2, y: -11, z: -19.5, t: 94.2}],
 [],
 [{x: 49.8, y: 8.08, z: 48.1, t: 102}],
 [{x: 98.8, y: -99.8, z: 739, t: 758}],
 [{x: 84.9, y: 92.7, z: -69.6, t: 172}],
 [{x: 3.43, y: 10.3, z: 28.3, t: 97}],
 [{x: 42.1, y: -47, z: -151, t: 172}],
 [],
 [{x: -41.7, y: 87.6, z: 18.2, t: 133}],
 [{x: -85.1, y: 18.1, z: 112, t: 172}],
 ...,
 [],
 [],
 [],
 [{x: 2.94, y: 18.4, z: -262, t: 273}],
 [],
 [],
 [],
 [],
 []]
-----------------------------------------
type: 2421 * var * Momentum4D[
    x: float32,
    y: float32,
    z: float32,
    t: float32
]
mu1.deltaR(mu2)
[[2.95],
 [],
 [2.13],
 [1.14],
 [1.43],
 [3.44],
 [1.49],
 [],
 [2.87],
 [1.86],
 ...,
 [],
 [],
 [],
 [2.76],
 [],
 [],
 [],
 [],
 []]
--------------------------
type: 2421 * var * float32
muones.to_rhophieta()
[[{rho: 54.2, phi: -2.92, eta: -0.15, px: -52.9, py: -11.7}, {...}],
 [{rho: 24.4, phi: -1.6, eta: 0.754, px: -0.816, py: -24.4}],
 [{rho: 53.6, phi: -0.417, eta: 0.207, px: 49, py: -21.7}, {...}],
 [{rho: 88.6, phi: -1.32, eta: 2.22, px: 22.1, py: -85.8}, {...}],
 [{rho: 81, phi: 0.979, eta: -0.955, px: 45.2, py: 67.2}, {rho: ..., ...}],
 [{rho: 41.6, phi: 1.35, eta: -0.345, px: 9.23, py: 40.6}, {...}],
 [{rho: 44.4, phi: -1.28, eta: -1.76, px: 12.5, py: -42.5}, {...}],
 [{rho: 38.4, phi: -0.43, eta: 2.11, px: 34.9, py: -16}],
 [{rho: 106, phi: 2.09, eta: 0.329, px: -53.2, py: 92}, {rho: 12.3, ...}],
 [{rho: 85.5, phi: 2.47, eta: 0.6, px: -67, py: 53.2}, {rho: 39.5, ...}],
 ...,
 [{rho: 35.3, phi: 1.13, eta: -2.19, px: 14.9, py: 32}],
 [{rho: 42.6, phi: -2.17, eta: -0.437, px: -24.2, py: -35}],
 [{rho: 43.2, phi: -1.79, eta: -1.19, px: -9.2, py: -42.2}],
 [{rho: 45, phi: 0.696, eta: -1.92, px: 34.5, py: 28.8}, {rho: 33.2, ...}],
 [{rho: 41.9, phi: -2.79, eta: 1.18, px: -39.3, py: -14.6}],
 [{rho: 37.8, phi: -0.384, eta: 2.15, px: 35.1, py: -14.2}],
 [{rho: 33.5, phi: -2.67, eta: -1.24, px: -29.8, py: -15.3}],
 [{rho: 63.6, phi: 1.55, eta: 1.67, px: 1.14, py: 63.6}],
 [{rho: 42.9, phi: -0.98, eta: 1.06, px: 23.9, py: -35.7}]]
---------------------------------------------------------------------------
type: 2421 * var * Momentum3D[
    rho: float32,
    phi: float32,
    eta: float32,
    px: float32,
    py: float32
]

Propiedades de partículas e identificadores PDG#

La librería Particle proporciona todas las masas de partículas, anchos de decaimiento y más del PDG. Además, contiene una serie de herramientas para consultar programáticamente las propiedades de las partículas y usar varios esquemas de identificación.

import particle
from hepunits import GeV

particle.Particle.findall("pi")
[<Particle: name="pi0", pdgid=111, mass=134.9768 ± 0.0005 MeV>,
 <Particle: name="pi+", pdgid=211, mass=139.57039 ± 0.00018 MeV>,
 <Particle: name="pi-", pdgid=-211, mass=139.57039 ± 0.00018 MeV>,
 <Particle: name="pi(2)(1670)0", pdgid=10115, mass=1670.6 + 2.9 - 1.2 MeV>,
 <Particle: name="pi(2)(1670)+", pdgid=10215, mass=1670.6 + 2.9 - 1.2 MeV>,
 <Particle: name="pi(2)(1670)-", pdgid=-10215, mass=1670.6 + 2.9 - 1.2 MeV>,
 <Particle: name="pi(1300)0", pdgid=100111, mass=1300 ± 100 MeV>,
 <Particle: name="pi(1300)+", pdgid=100211, mass=1300 ± 100 MeV>,
 <Particle: name="pi(1300)-", pdgid=-100211, mass=1300 ± 100 MeV>,
 <Particle: name="pi(1)(1400)0", pdgid=9000113, mass=None>,
 <Particle: name="pi(1)(1400)+", pdgid=9000213, mass=None>,
 <Particle: name="pi(1)(1400)-", pdgid=-9000213, mass=None>,
 <Particle: name="pi(1800)0", pdgid=9010111, mass=1810 + 9 - 11 MeV>,
 <Particle: name="pi(1)(1600)0", pdgid=9010113, mass=1645 + 40 - 17 MeV>,
 <Particle: name="pi(1800)+", pdgid=9010211, mass=1810 + 9 - 11 MeV>,
 <Particle: name="pi(1800)-", pdgid=-9010211, mass=1810 + 9 - 11 MeV>,
 <Particle: name="pi(1)(1600)+", pdgid=9010213, mass=1645 + 40 - 17 MeV>,
 <Particle: name="pi(1)(1600)-", pdgid=-9010213, mass=1645 + 40 - 17 MeV>]
boson_z = particle.Particle.from_name("Z0")
boson_z.mass / GeV, boson_z.width / GeV
(91.188, 2.4955)
print(boson_z.describe())
Name: Z0             ID: 23           Latex: $Z^{0}$
Mass  = 91188.0 ± 2.0 MeV
Width = 2495.5 ± 2.3 MeV
Q (charge)        = 0       J (total angular) = 1.0      P (space parity) = None
C (charge parity) = None    I (isospin)       = None     G (G-parity)     = None
    Antiparticle name: Z0 (antiparticle status: Same)
particle.Particle.from_pdgid(111)
\[\pi^{0}\]
particle.Particle.findall(
    lambda p: p.pdgid.is_meson and p.pdgid.has_strange and p.pdgid.has_charm
)
[<Particle: name="D(s)+", pdgid=431, mass=1968.35 ± 0.07 MeV>,
 <Particle: name="D(s)-", pdgid=-431, mass=1968.35 ± 0.07 MeV>,
 <Particle: name="D(s)*+", pdgid=433, mass=2112.2 ± 0.4 MeV>,
 <Particle: name="D(s)*-", pdgid=-433, mass=2112.2 ± 0.4 MeV>,
 <Particle: name="D(s2)*(2573)+", pdgid=435, mass=2569.1 ± 0.8 MeV>,
 <Particle: name="D(s2)*(2573)-", pdgid=-435, mass=2569.1 ± 0.8 MeV>,
 <Particle: name="D(s0)*(2317)+", pdgid=10431, mass=2317.8 ± 0.5 MeV>,
 <Particle: name="D(s0)*(2317)-", pdgid=-10431, mass=2317.8 ± 0.5 MeV>,
 <Particle: name="D(s1)(2536)+", pdgid=10433, mass=2535.11 ± 0.06 MeV>,
 <Particle: name="D(s1)(2536)-", pdgid=-10433, mass=2535.11 ± 0.06 MeV>,
 <Particle: name="D(s1)(2460)+", pdgid=20433, mass=2459.5 ± 0.6 MeV>,
 <Particle: name="D(s1)(2460)-", pdgid=-20433, mass=2459.5 ± 0.6 MeV>]
print(particle.PDGID(211).info())
A              None
J              0.0
L              0
S              0
Z              None
abspid         211
charge         1.0
has_bottom     False
has_charm      False
has_down       True
has_fundamental_anti False
has_strange    False
has_top        False
has_up         True
is_Qball       False
is_Rhadron     False
is_SUSY        False
is_baryon      False
is_diquark     False
is_dyon        False
is_excited_quark_or_lepton False
is_gauge_boson_or_higgs False
is_generator_specific False
is_hadron      True
is_lepton      False
is_meson       True
is_nucleus     False
is_pentaquark  False
is_quark       False
is_sm_gauge_boson_or_higgs False
is_sm_lepton   False
is_sm_quark    False
is_special_particle False
is_technicolor False
is_valid       True
j_spin         1
l_spin         1
s_spin         1
three_charge   3
pdgid = particle.Corsika7ID(11).to_pdgid()
particle.Particle.from_pdgid(pdgid)
\[K^{+}\]

Agrupamiento de jets#

En una colisión pp de alta energía, por ejemplo, se produce una lluvia de hadrones que se agrupan en jets de partículas, y este método/proceso se llama agrupamiento de jets (jet-clustering). El algoritmo de agrupamiento de jets anti-kt es uno de los algoritmos utilizados para combinar partículas/hadrones que están cerca entre sí en jets.

Algunas personas necesitan realizar agrupamiento de jets a nivel de análisis. El paquete fastjet hace posible hacerlo un array (Awkward) a la vez.

import skhep_testdata, uproot
import awkward as ak
import particle
from hepunits import GeV
import vector

vector.register_awkward()

picodst = uproot.open(
    "https://pivarski-princeton.s3.amazonaws.com/pythia_ppZee_run17emb.picoDst.root:PicoDst"
)
px, py, pz = ak.unzip(
    picodst.arrays(filter_name=["Track/Track.mPMomentum[XYZ]"], entry_stop=100)
)

probable_mass = particle.Particle.from_name("pi+").mass / GeV

pseudojets = ak.zip(
    {"px": px, "py": py, "pz": pz, "mass": probable_mass}, with_name="Momentum4D"
)
good_pseudojets = pseudojets[pseudojets.pt > 0.1]

import fastjet

jetdef = fastjet.JetDefinition(fastjet.antikt_algorithm, 1.0)

clusterseq = fastjet.ClusterSequence(good_pseudojets, jetdef)
clusterseq.inclusive_jets()

ak.num(good_pseudojets), ak.num(clusterseq.inclusive_jets())
#--------------------------------------------------------------------------
#                         FastJet release 3.4.0
#                 M. Cacciari, G.P. Salam and G. Soyez                  
#     A software package for jet finding and analysis at colliders      
#                           http://fastjet.fr                           
#	                                                                      
# Please cite EPJC72(2012)1896 [arXiv:1111.6097] if you use this package
# for scientific work and optionally PLB641(2006)57 [hep-ph/0512210].   
#                                                                       
# FastJet is provided without warranty under the GNU GPL v2 or higher.  
# It uses T. Chan's closest pair algorithm, S. Fortune's Voronoi code,
# CGAL and 3rd party plugin jet algorithms. See COPYING file for details.
#--------------------------------------------------------------------------
(<Array [24, 19, 13, 36, 39, 48, ..., 57, 41, 35, 34, 20, 93] type='100 * int64'>,
 <Array [8, 6, 6, 7, 8, 9, 8, 7, ..., 9, 8, 7, 6, 7, 4, 11] type='100 * int64'>)