Analytic continuation
Analytic continuation¶
import logging
import warnings
import ampform
import graphviz
import matplotlib.pyplot as plt
import pandas as pd
import qrules
from IPython.display import Math
from tensorwaves.data import (
IntensityDistributionGenerator,
SympyDataTransformer,
TFPhaseSpaceGenerator,
TFUniformRealNumberGenerator,
)
from tensorwaves.function.sympy import create_parametrized_function
logger = logging.getLogger()
logger.setLevel(logging.ERROR)
warnings.filterwarnings("ignore")
Sometimes qrules
finds resonances that lie outside phase space, because resonances can ‘leak’ into phase space. The example below is simplified and the two selected resonances are a bit unusual, but it serves to illustrate how to handle these sub-threshold resonances.
reaction = qrules.generate_transitions(
initial_state="D0",
final_state=["K-", "K+", "K0"],
allowed_intermediate_particles=["a(0)(980)0", "a(2)(1320)+"],
formalism="canonical-helicity",
)
dot = qrules.io.asdot(
reaction,
collapse_graphs=True,
render_node=False,
)
graphviz.Source(dot)
Of the two resonances, \(a_0(980)\) lies just below threshold ― it’s mass is smaller than the the masses of the two decay products combined:
pdg = qrules.load_pdg()
a_meson = pdg["a(0)(980)0"]
k_plus = pdg["K+"]
k_minus = pdg["K+"]
two_k_mass = round(k_minus.mass + k_plus.mass, 3)
display(
Math(
Rf"m_{{{a_meson.latex}}} = {a_meson.mass} \pm"
Rf" {a_meson.width}\;\mathrm{{GeV}}"
),
Math(
Rf"m_{{{k_plus.latex}}} + m_{{{k_minus.latex}}} ="
Rf" {two_k_mass}\;\mathrm{{GeV}}"
),
)
To correctly describe the dynamics for this resonance, we should use make use of analytic continuation. As opposed to Step 1: Formulate model, we now use create_analytic_breit_wigner()
(a relativistic Breit-Wigner with analytic continuation) instead of create_relativistic_breit_wigner_with_ff()
:
from ampform.dynamics.builder import (
create_analytic_breit_wigner,
create_non_dynamic_with_ff,
create_relativistic_breit_wigner_with_ff,
)
model_builder = ampform.get_builder(reaction)
model_builder.set_dynamics(
"J/psi(1S)",
create_non_dynamic_with_ff,
)
model_builder.set_dynamics(
"a(0)(980)0",
create_analytic_breit_wigner,
)
model_builder.set_dynamics(
"a(2)(1320)+",
create_relativistic_breit_wigner_with_ff,
)
model = model_builder.formulate()
The effect can be seen once we generate data. Despite the fact that the resonance lies outside phase space, there is still a contribution to the intensity:
intensity = create_parametrized_function(
expression=model.expression.doit(),
parameters=model.parameter_defaults,
backend="jax",
)
helicity_transformer = SympyDataTransformer.from_sympy(
model.kinematic_variables, backend="numpy"
)
rng = TFUniformRealNumberGenerator(seed=0)
phsp_generator = TFPhaseSpaceGenerator(
initial_state_mass=reaction.initial_state[-1].mass,
final_state_masses={i: p.mass for i, p in reaction.final_state.items()},
)
data_generator = IntensityDistributionGenerator(
domain_generator=phsp_generator,
function=intensity,
domain_transformer=helicity_transformer,
)
data_momenta = data_generator.generate(2_000, rng)
import numpy as np
from matplotlib import cm
resonances = sorted(
reaction.get_intermediate_particles(),
key=lambda p: p.mass,
)
evenly_spaced_interval = np.linspace(0, 1, len(resonances))
colors = [cm.rainbow(x) for x in evenly_spaced_interval]
def indicate_masses():
plt.xlabel("$m_{02}$ [GeV]")
for i, p in enumerate(resonances):
plt.gca().axvline(
x=p.mass, linestyle="dotted", label=p.name, color=colors[i]
)
data = helicity_transformer(data_momenta)
data_frame = pd.DataFrame(data)
data_frame["m_02"].hist(bins=50, alpha=0.5, density=True)
indicate_masses()
plt.legend();