Building a mapping for a Feynman diagram¶
The previous examples built a mapping from a flat list of masses. This example instead
follows one Feynman diagram, using Diagram and
Topology. The diagram is a single-quark t-channel exchange
feeding a resonant W boson, u d~ > mu+ vm g. It has one t-channel propagator and one
resonant s-channel propagator, so the mapping can sample the W resonance directly instead of
missing it.
Building the diagram¶
Diagram takes the incoming and outgoing masses, a list of
Propagator and the vertices connecting them. A vertex is a
triple of line labels: i for an incoming line, o for an outgoing one and p for a
propagator, each followed by its 0-based index:
import numpy as np
import madspace as ms
M_W, W_W = 80.379, 2.085 # W mass and width, in GeV
diagram = ms.Diagram(
[0.0, 0.0], # incoming u, d~
[0.0, 0.0, 0.0], # outgoing g, mu+, vm
[
ms.Propagator(mass=0.0, width=0.0, pdg_id=2), # t-channel u-quark
ms.Propagator(mass=M_W, width=W_W, pdg_id=24), # s-channel W+
],
[["i0", "o0", "p0"], ["p0", "i1", "p1"], ["p1", "o1", "o2"]],
)
The first vertex is the incoming u quark radiating the gluon and turning into the
t-channel propagator. The second vertex absorbs the d~ and produces the W. The third
vertex is the W decay.
Inspecting the topology¶
Topology turns a diagram into one integration channel. It can
be printed directly, which shows the t-channel propagator and the s-channel decay tree it
builds:
topology = ms.Topology(diagram)
print(topology)
├── incoming: index=0, mass=0
├── incoming: index=1, mass=0
└── t-channel: (order=0, mass=0, width=0)
├── outgoing: index=0, mass=0
└── decay: order=0, mass=80.379, width=2.085, e_min=0, e_max=0, pdg_id=24, on_shell=true, on_shell_boundary=false
├── outgoing: index=1, mass=0
└── outgoing: index=2, mass=0
Sampling the resonance¶
Building a PhaseSpaceMapping from the topology
samples the W invariant mass around its pole, using the propagator’s mass and width:
mapping = ms.PhaseSpaceMapping(topology, 13000.0)
rng = np.random.default_rng(0)
r = rng.random((100000, mapping.random_dim()))
momenta, x1, x2, det = mapping.map_forward([r])
p_mu, p_vm = momenta[:, 3], momenta[:, 4]
p_sum = p_mu + p_vm
m_inv = np.sqrt(np.abs(p_sum[:, 0] ** 2 - np.sum(p_sum[:, 1:] ** 2, axis=1)))
print(f"fraction within 5 GeV of the W mass: {(np.abs(m_inv - M_W) < 5).mean():.3f}")
Most of the sample lands close to the pole, even though the window is narrow compared to the full available energy:
fraction within 5 GeV of the W mass: 0.877
For comparison, a flat mapping built from the same external masses, with mode="rambo",
has no notion of this resonance and only finds a tiny fraction of its events there:
flat = ms.PhaseSpaceMapping([0.0, 0.0, 0.0, 0.0, 0.0], 13000.0, mode="rambo")
momenta_flat, _, _, _ = flat.map_forward([r])
p_sum_flat = momenta_flat[:, 3] + momenta_flat[:, 4]
m_inv_flat = np.sqrt(np.abs(p_sum_flat[:, 0] ** 2 - np.sum(p_sum_flat[:, 1:] ** 2, axis=1)))
print(f"fraction within 5 GeV of the W mass: {(np.abs(m_inv_flat - M_W) < 5).mean():.3f}")
fraction within 5 GeV of the W mass: 0.009