Helicity versus canonical

Helicity versus canonical#

Hide code cell content

import logging

import matplotlib as mpl
import numpy as np
import qrules
import sympy as sp
from IPython.display import Markdown, Math
from matplotlib import cm
from qrules.io import asmermaid

import ampform
from ampform.io import aslatex

LOGGER = logging.getLogger()
LOGGER.setLevel(logging.ERROR)
logging.getLogger("qrules.transition").setLevel(logging.ERROR)  # hide progress bar

In this notebook, we have a look at the decay

\[D_1(2420)^0 \to a_1(1260)^+ K^- \to (K^+K^0)K^-\]

in order to see the difference between a HelicityModel formulated in the canonical basis and one formulated in the helicity basis. To simplify things, we only look at spin projection \(+1\) for \(D_1(2420)^0\), because the intensities for each of the spin projections of \(D_1(2420)^0\) are incoherent, no matter which spin formalism we choose.

Tip

For more information about the helicity formalism, see [Chung, 2014], [Richman, 1984], and [Kutschke, 1996].

First, we use qrules.generate_transitions() to generate a ReactionInfo instance for both formalisms:

def generate_transitions(formalism: str):
    reaction = qrules.generate_transitions(
        initial_state=("D(1)(2420)0", [+1]),
        final_state=["K+", "K-", "K~0"],
        allowed_intermediate_particles=["a(1)(1260)+"],
        formalism=formalism,
    )
    builder = ampform.get_builder(reaction)
    return builder.formulate()


cano_model = generate_transitions("canonical-helicity")
heli_model = generate_transitions("helicity")

From components and parameter_defaults, we can see that the canonical formalism has a larger number of amplitudes.

Hide code cell source

Markdown(f"""
| Formalism | Coefficients | Amplitudes |
| --- | ---: | ---: |
| Canonical | {len(cano_model.parameter_defaults)} | {len(cano_model.components) - 1} |
| Helicity | {len(heli_model.parameter_defaults)} | {len(heli_model.components) - 1} |
""")

Formalism

Coefficients

Amplitudes

Canonical

3

8

Helicity

3

3

The reason for this is that canonical basis distinguishes amplitudes over their \(LS\)-combinations. This becomes clear if we define \(a\) to be the amplitude without coefficient (\(A = C a\)), and consider what the full, coherent intensity looks like.

If we write the full intensity as \(I = \left|\sum_i A_i\right|^2\), then we have, in the case of the canonical basis:

Hide code cell source

def extract_amplitude_substitutions(model, colorize=False):
    amplitude_to_symbol = {}
    amplitude_names = sorted(c for c in model.components if c.startswith("A"))
    n_colors = len(amplitude_names)
    color_map = cm.brg(np.linspace(0, 1, num=n_colors + 1)[:-1])
    color_iter = (mpl.colors.to_hex(color) for color in color_map)
    for name in amplitude_names:
        expr = model.components[name]
        for par in model.parameter_defaults:
            if par in expr.args:
                expr /= par
        name = "a" + name[1:]
        if colorize:
            color = next(color_iter)
            name = Rf"\textcolor{{{color}}}{{{name}}}"
        symbol = sp.Symbol(name)
        amplitude_to_symbol[expr] = symbol
    return amplitude_to_symbol


cano_amplitude_to_symbol = extract_amplitude_substitutions(cano_model)
heli_amplitude_to_symbol = extract_amplitude_substitutions(heli_model)


def render_amplitude_summation(model, colorize=False):
    amplitude_to_symbol = extract_amplitude_substitutions(model, colorize)
    collected_expr = sp.collect(
        model.expression.subs(amplitude_to_symbol).args[0].args[0],
        tuple(model.parameter_defaults),
    )
    terms = collected_expr.args
    latex = ""
    latex += R"\begin{aligned}"
    latex += Rf"\sum_i A_i & = {sp.latex(terms[0])}\\"
    for term in terms[1:]:
        latex += Rf"& + {sp.latex(term)} \\"
    latex += R"\end{aligned}"
    return Math(latex)


render_amplitude_summation(cano_model, colorize=True)
\[\begin{split}\displaystyle \begin{aligned}\sum_i A_i & = C_{D_{1}(2420)^{0} \xrightarrow[S=1]{L=0} K^{-} a_{1}(1260)^{+}; a_{1}(1260)^{+} \xrightarrow[S=0]{L=1} K^{+} \overline{K}^{0}} \left(\textcolor{#0000ff}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=0} K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}} + \textcolor{#4000bf}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=0} K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; {a_{1}(1260)^{+}}_{-1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}} + \textcolor{#80007f}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=0} K^{-}_{0} {a_{1}(1260)^{+}}_{0}; {a_{1}(1260)^{+}}_{0} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}}\right)\\& + C_{D_{1}(2420)^{0} \xrightarrow[S=1]{L=1} K^{-} a_{1}(1260)^{+}; a_{1}(1260)^{+} \xrightarrow[S=0]{L=1} K^{+} \overline{K}^{0}} \left(\textcolor{#c0003f}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=1} K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}} + \textcolor{#fe0100}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=1} K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; {a_{1}(1260)^{+}}_{-1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}}\right) \\& + C_{D_{1}(2420)^{0} \xrightarrow[S=1]{L=2} K^{-} a_{1}(1260)^{+}; a_{1}(1260)^{+} \xrightarrow[S=0]{L=1} K^{+} \overline{K}^{0}} \left(\textcolor{#3ec100}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=2} K^{-}_{0} {a_{1}(1260)^{+}}_{0}; {a_{1}(1260)^{+}}_{0} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}} + \textcolor{#7e8100}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=2} K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; {a_{1}(1260)^{+}}_{-1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}} + \textcolor{#be4100}{a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=2} K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}}}\right) \\\end{aligned}\end{split}\]

In the helicity basis, the \(LS\)-combinations have been summed over already and we can only see an amplitude for each helicity:

Hide code cell source

render_amplitude_summation(heli_model)
\[\begin{split}\displaystyle \begin{aligned}\sum_i A_i & = C_{D_{1}(2420)^{0} \to K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; a_{1}(1260)^{+} \to K^{+}_{0} \overline{K}^{0}_{0}} a_{{D_{1}(2420)^{0}}_{+1} \to K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \to K^{+}_{0} \overline{K}^{0}_{0}}\\& + C_{D_{1}(2420)^{0} \to K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; a_{1}(1260)^{+} \to K^{+}_{0} \overline{K}^{0}_{0}} a_{{D_{1}(2420)^{0}}_{+1} \to K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; {a_{1}(1260)^{+}}_{-1} \to K^{+}_{0} \overline{K}^{0}_{0}} \\& + C_{D_{1}(2420)^{0} \to K^{-}_{0} {a_{1}(1260)^{+}}_{0}; a_{1}(1260)^{+} \to K^{+}_{0} \overline{K}^{0}_{0}} a_{{D_{1}(2420)^{0}}_{+1} \to K^{-}_{0} {a_{1}(1260)^{+}}_{0}; {a_{1}(1260)^{+}}_{0} \to K^{+}_{0} \overline{K}^{0}_{0}} \\\end{aligned}\end{split}\]

Amplitudes in the canonical basis are formulated with regard to their \(LS\)-couplings. As such, they contain additional Clebsch–Gordan coefficients that serve as expansion coefficients.

Hide code cell source

def extract_amplitudes(model):
    return {
        expr: sp.Symbol(name)
        for name, expr in model.components.items()
        if name.startswith("A")
    }


cano_amplitudes = extract_amplitudes(cano_model)
heli_amplitudes = extract_amplitudes(heli_model)

expression, symbol = next(iter(cano_amplitude_to_symbol.items()))
Math(aslatex({symbol: expression}))
\[\begin{split}\displaystyle \begin{aligned} a_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=0} K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \;&=\; C^{0,0}_{0,0,0,0} C^{1,1}_{0,0,1,1} C^{1,0}_{1,0,0,0} C^{1,1}_{1,1,0,0} D^{1}_{1,0}\left(- \phi^{02}_{0},\theta^{02}_{0},0\right) D^{1}_{1,1}\left(- \phi_{02},\theta_{02},0\right) \\ \end{aligned}\end{split}\]

In the helicity basis, these Clebsch–Gordan coefficients and Wigner-\(D\) functions have been summed up, leaving only a Wigner-\(D\) for each node in the decay chain (two in this case):

Hide code cell source

expression, symbol = next(iter(heli_amplitude_to_symbol.items()))
Math(aslatex({symbol: expression}))
\[\begin{split}\displaystyle \begin{aligned} a_{{D_{1}(2420)^{0}}_{+1} \to K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \to K^{+}_{0} \overline{K}^{0}_{0}} \;&=\; D^{1}_{1,0}\left(- \phi^{02}_{0},\theta^{02}_{0},0\right) D^{1}_{1,1}\left(- \phi_{02},\theta_{02},0\right) \\ \end{aligned}\end{split}\]

See formulate_isobar_wigner_d() and formulate_isobar_cg_coefficients() for how these Wigner-\(D\) functions and Clebsch–Gordan coefficients are computed for each node on a Transition.

We can see this also from the original ReactionInfo objects. Let’s select only the transitions where the \(a_1(1260)^+\) resonance has spin projection \(-1\) (taken to be helicity \(-1\) in the helicity formalism). We then see just one Transition in the helicity basis and three transitions in the canonical basis:

Hide code cell source

def render_selection(model):
    transitions = model.reaction_info.transitions
    selection = filter(lambda s: s.states[3].spin_projection == -1, transitions)
    return asmermaid(
        selection,
        markdown=True,
        render_final_state_id=False,
        render_node=True,
    )


Markdown(f"""
**Helicity** basis:

{render_selection(heli_model)}

**Canonical** basis:

{render_selection(cano_model)}
""")

Helicity basis:

        flowchart LR
    T0_0["$$K^{+}\left[0\right]$$"]
    T0_1["$$K^{-}\left[0\right]$$"]
    T0_2["$$\overline{K}^{0}\left[0\right]$$"]
    T0_A["$$D_{1}(2420)^{0}\left[\text{+}1\right]$$"]
    T0_N0@{ shape: text, label: " " }
    T0_N1@{ shape: text, label: " " }
    T0_3("$$a_{1}(1260)^{+}\left[\text{-}1\right]$$")
    T0_A --- T0_N0
    T0_N0 --- T0_3
    T0_3 --- T0_N1
    T0_N0 --- T0_1
    T0_N1 --- T0_0
    T0_N1 --- T0_2
    

Canonical basis:

        flowchart LR
    T0_0["$$K^{+}\left[0\right]$$"]
    T0_1["$$K^{-}\left[0\right]$$"]
    T0_2["$$\overline{K}^{0}\left[0\right]$$"]
    T0_A["$$D_{1}(2420)^{0}\left[\text{+}1\right]$$"]
    T0_N0(("$$\begin{gathered} L = \left|2,0\right\rangle \\\ S = \left|1,\text{+}1\right\rangle \end{gathered}$$"))
    T0_N1(("$$\begin{gathered} L = \left|1,0\right\rangle \\\ S = \left|0,0\right\rangle \end{gathered}$$"))
    T0_3("$$a_{1}(1260)^{+}\left[\text{-}1\right]$$")
    T0_A --- T0_N0
    T0_N0 --- T0_3
    T0_3 --- T0_N1
    T0_N0 --- T0_1
    T0_N1 --- T0_0
    T0_N1 --- T0_2
    T1_0["$$K^{+}\left[0\right]$$"]
    T1_1["$$K^{-}\left[0\right]$$"]
    T1_2["$$\overline{K}^{0}\left[0\right]$$"]
    T1_A["$$D_{1}(2420)^{0}\left[\text{+}1\right]$$"]
    T1_N0(("$$\begin{gathered} L = \left|1,0\right\rangle \\\ S = \left|1,\text{+}1\right\rangle \end{gathered}$$"))
    T1_N1(("$$\begin{gathered} L = \left|1,0\right\rangle \\\ S = \left|0,0\right\rangle \end{gathered}$$"))
    T1_3("$$a_{1}(1260)^{+}\left[\text{-}1\right]$$")
    T1_A --- T1_N0
    T1_N0 --- T1_3
    T1_3 --- T1_N1
    T1_N0 --- T1_1
    T1_N1 --- T1_0
    T1_N1 --- T1_2
    T2_0["$$K^{+}\left[0\right]$$"]
    T2_1["$$K^{-}\left[0\right]$$"]
    T2_2["$$\overline{K}^{0}\left[0\right]$$"]
    T2_A["$$D_{1}(2420)^{0}\left[\text{+}1\right]$$"]
    T2_N0(("$$\begin{gathered} L = \left|0,0\right\rangle \\\ S = \left|1,\text{+}1\right\rangle \end{gathered}$$"))
    T2_N1(("$$\begin{gathered} L = \left|1,0\right\rangle \\\ S = \left|0,0\right\rangle \end{gathered}$$"))
    T2_3("$$a_{1}(1260)^{+}\left[\text{-}1\right]$$")
    T2_A --- T2_N0
    T2_N0 --- T2_3
    T2_3 --- T2_N1
    T2_N0 --- T2_1
    T2_N1 --- T2_0
    T2_N1 --- T2_2
    

Coefficient names#

In the previous section, we saw that the HelicityAmplitudeBuilder by default generates coefficient names that only contain helicities of the decay products, while coefficients generated by the CanonicalAmplitudeBuilder contain only \(LS\)-combinations. It’s possible to tweak this behavior with the naming attribute. Here are two extreme examples, where we generate coefficient names that contain \(LS\)-combinations, the helicities of each parent state, and the helicity of each decay product, as well as a HelicityModel of which the coefficient names only contain information about the resonances:

reaction = qrules.generate_transitions(
    initial_state=("D(1)(2420)0", [+1]),
    final_state=["K+", "K-", "K~0"],
    allowed_intermediate_particles=["a(1)(1260)+"],
    formalism="canonical-helicity",
)
builder = ampform.get_builder(reaction)
builder.naming.insert_parent_helicities = True
builder.naming.insert_child_helicities = True
builder.naming.insert_ls_combinations = True
model = builder.formulate()

Hide code cell source

amplitudes = [c for c in model.components if c.startswith("A")]
assert len(model.parameter_defaults) == len(amplitudes)
Math(aslatex(list(model.parameter_defaults)))
\[\begin{split}\displaystyle \begin{array}{c} C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=0} K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; {a_{1}(1260)^{+}}_{-1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=1} K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; {a_{1}(1260)^{+}}_{-1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=2} K^{-}_{0} {a_{1}(1260)^{+}}_{-1}; {a_{1}(1260)^{+}}_{-1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=0} K^{-}_{0} {a_{1}(1260)^{+}}_{0}; {a_{1}(1260)^{+}}_{0} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=2} K^{-}_{0} {a_{1}(1260)^{+}}_{0}; {a_{1}(1260)^{+}}_{0} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=0} K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=1} K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ C_{{D_{1}(2420)^{0}}_{+1} \xrightarrow[S=1]{L=2} K^{-}_{0} {a_{1}(1260)^{+}}_{+1}; {a_{1}(1260)^{+}}_{+1} \xrightarrow[S=0]{L=1} K^{+}_{0} \overline{K}^{0}_{0}} \\ \end{array}\end{split}\]
builder.naming.insert_parent_helicities = False
builder.naming.insert_child_helicities = False
builder.naming.insert_ls_combinations = False
model = builder.formulate()

Hide code cell source

\[\begin{split}\displaystyle \begin{array}{c} C_{D_{1}(2420)^{0} \to K^{-} a_{1}(1260)^{+}; a_{1}(1260)^{+} \to K^{+} \overline{K}^{0}} \\ \end{array}\end{split}\]