Source code for ampform.dynamics.kmatrix

r"""Experimental, symbol :math:`\boldsymbol{K}`-matrix implementations.

See :doc:`/usage/dynamics/k-matrix`.

This module is an implementation of :doc:`compwa-org:report/005`,
:doc:`compwa-org:report/009`, and :doc:`compwa-org:report/010`. It works with
classes to keep the code organized and to enable caching of the matrix
multiplications, but this might change once these dynamics are implemented into
the amplitude builder.
"""
from __future__ import annotations

import functools
from abc import ABC, abstractmethod

import sympy as sp

from ampform.dynamics import (
    BlattWeisskopfSquared,
    BreakupMomentumSquared,
    EnergyDependentWidth,
    PhaseSpaceFactor,
    PhaseSpaceFactorProtocol,
)
from ampform.sympy import create_symbol_matrix


[docs]class TMatrix(ABC):
[docs] @classmethod @abstractmethod def formulate( cls, n_channels, n_poles, parametrize: bool = True, **kwargs ) -> sp.MutableDenseMatrix: """Formulate :math:`K`-matrix with its own parametrization."""
[docs]class RelativisticKMatrix(TMatrix): @staticmethod @functools.lru_cache(maxsize=None) def _create_matrices( n_channels, return_t_hat: bool = False ) -> tuple[sp.MutableDenseMatrix, sp.MutableDenseMatrix]: # pylint: disable=no-member rho = _create_rho_matrix(n_channels) sqrt_rho: sp.MutableDenseMatrix = sp.sqrt(rho).doit() sqrt_rho_conj = sp.conjugate(sqrt_rho) k_matrix = create_symbol_matrix("K", n_channels, n_channels) t_hat = k_matrix * (sp.eye(n_channels) - sp.I * rho * k_matrix).inv() if return_t_hat: return t_hat, k_matrix t_matrix = sqrt_rho_conj * t_hat * sqrt_rho return t_matrix, k_matrix
[docs] @classmethod def formulate( cls, n_channels, n_poles, parametrize: bool = True, **kwargs ) -> sp.MutableDenseMatrix: r"""Implementation of :eq:`T-hat in terms of K-hat`. Args: n_channels: Number of coupled channels. n_poles: Number of poles. parametrize: Set to `False` if don't want to parametrize and only get symbols for the matrix multiplication of :math:`\boldsymbol{K}` and :math:`\boldsymbol{\rho}`. return_t_hat: Set to `True` if you want to get the Lorentz-invariant :math:`\boldsymbol{\hat{T}}`-matrix instead of the :math:`\boldsymbol{T}`-matrix from Eq. :eq:`K-hat and T-hat`. """ return_t_hat: bool = kwargs.pop("return_t_hat", False) t_matrix, k_matrix = cls._create_matrices(n_channels, return_t_hat) if not parametrize: return t_matrix phsp_factor: PhaseSpaceFactorProtocol = kwargs.get( "phsp_factor", PhaseSpaceFactor ) s = sp.Symbol("s") m_a = sp.IndexedBase("m_a") m_b = sp.IndexedBase("m_b") return t_matrix.xreplace( { k_matrix[i, j]: cls.parametrization( i=i, j=j, s=s, pole_position=sp.IndexedBase("m"), pole_width=sp.IndexedBase("Gamma"), m_a=m_a, m_b=m_b, residue_constant=sp.IndexedBase("gamma"), n_poles=n_poles, pole_id=sp.Symbol("R", integer=True, positive=True), angular_momentum=kwargs.get("angular_momentum", 0), meson_radius=kwargs.get("meson_radius", 1), phsp_factor=phsp_factor, ) for i in range(n_channels) for j in range(n_channels) } ).xreplace( { sp.Symbol(f"rho{i}"): phsp_factor(s, m_a[i], m_b[i]) for i in range(n_channels) } )
[docs] @staticmethod def parametrization( # pylint: disable=too-many-arguments, too-many-locals i, j, s, pole_position: sp.IndexedBase, pole_width: sp.IndexedBase, m_a: sp.IndexedBase, m_b: sp.IndexedBase, residue_constant: sp.IndexedBase, n_poles, pole_id, angular_momentum=0, meson_radius=1, phsp_factor: PhaseSpaceFactorProtocol | None = None, ) -> sp.Expr: def residue_function(pole_id, i) -> sp.Expr: return residue_constant[pole_id, i] * sp.sqrt( pole_position[pole_id] * EnergyDependentWidth( s=s, mass0=pole_position[pole_id], gamma0=pole_width[pole_id, i], m_a=m_a[i], m_b=m_b[i], angular_momentum=angular_momentum, meson_radius=meson_radius, phsp_factor=phsp_factor, ) ) g_i = residue_function(pole_id, i) g_j = residue_function(pole_id, j) parametrization = (g_i * g_j) / (pole_position[pole_id] ** 2 - s) return sp.Sum(parametrization, (pole_id, 1, n_poles))
[docs]class NonRelativisticKMatrix(TMatrix): @staticmethod @functools.lru_cache(maxsize=None) def _create_matrices( n_channels, ) -> tuple[sp.MutableDenseMatrix, sp.MutableDenseMatrix]: k_matrix = create_symbol_matrix("K", n_channels, n_channels) t_matrix = k_matrix * (sp.eye(n_channels) - sp.I * k_matrix).inv() return t_matrix, k_matrix
[docs] @classmethod def formulate( cls, n_channels, n_poles, parametrize: bool = True, **kwargs, ) -> sp.MutableDenseMatrix: t_matrix, k_matrix = cls._create_matrices(n_channels) if not parametrize: return t_matrix return t_matrix.xreplace( { k_matrix[i, j]: cls.parametrization( i=i, j=j, s=sp.Symbol("s"), pole_position=sp.IndexedBase("m"), pole_width=sp.IndexedBase("Gamma"), residue_constant=sp.IndexedBase("gamma"), n_poles=n_poles, pole_id=sp.Symbol("R", integer=True, positive=True), ) for i in range(n_channels) for j in range(n_channels) } )
[docs] @staticmethod def parametrization( # pylint: disable=too-many-arguments i, j, s, pole_position: sp.IndexedBase, pole_width: sp.IndexedBase, residue_constant: sp.IndexedBase, n_poles, pole_id, ) -> sp.Expr: def residue_function(pole_id, i) -> sp.Expr: return residue_constant[pole_id, i] * sp.sqrt( pole_position[pole_id] * pole_width[pole_id, i] ) g_i = residue_function(pole_id, i) g_j = residue_function(pole_id, j) parametrization = (g_i * g_j) / (pole_position[pole_id] ** 2 - s) return sp.Sum(parametrization, (pole_id, 1, n_poles))
[docs]class NonRelativisticPVector(TMatrix): @staticmethod @functools.lru_cache(maxsize=None) def _create_matrices( n_channels, ) -> tuple[ sp.MutableDenseMatrix, sp.MutableDenseMatrix, sp.MutableDenseMatrix ]: k_matrix = create_symbol_matrix("K", m=n_channels, n=n_channels) p_vector = create_symbol_matrix("P", m=n_channels, n=1) f_vector = (sp.eye(n_channels) - sp.I * k_matrix).inv() * p_vector return f_vector, k_matrix, p_vector
[docs] @classmethod def formulate( cls, n_channels, n_poles, parametrize: bool = True, **kwargs, ) -> sp.MutableDenseMatrix: f_vector, k_matrix, p_vector = cls._create_matrices(n_channels) if not parametrize: return f_vector s = sp.Symbol("s") pole_position = sp.IndexedBase("m") pole_width = sp.IndexedBase("Gamma") residue_constant = sp.IndexedBase("gamma") pole_id = sp.Symbol("R", integer=True, positive=True) return f_vector.xreplace( { k_matrix[i, j]: NonRelativisticKMatrix.parametrization( i=i, j=j, s=s, pole_position=pole_position, pole_width=pole_width, residue_constant=residue_constant, n_poles=n_poles, pole_id=pole_id, ) for i in range(n_channels) for j in range(n_channels) } ).xreplace( { p_vector[i]: cls.parametrization( i=i, s=sp.Symbol("s"), pole_position=pole_position, pole_width=pole_width, residue_constant=residue_constant, beta_constant=sp.IndexedBase("beta"), n_poles=n_poles, pole_id=pole_id, ) for i in range(n_channels) } )
[docs] @staticmethod def parametrization( # pylint: disable=too-many-arguments i, s, pole_position: sp.IndexedBase, pole_width: sp.IndexedBase, residue_constant: sp.IndexedBase, beta_constant: sp.IndexedBase, n_poles, pole_id, ) -> sp.Expr: beta = beta_constant[pole_id] gamma = residue_constant[pole_id, i] mass = pole_position[pole_id] width = pole_width[pole_id, i] parametrization = beta * gamma * mass * width / (mass**2 - s) return sp.Sum(parametrization, (pole_id, 1, n_poles))
[docs]class RelativisticPVector(TMatrix): @staticmethod @functools.lru_cache(maxsize=None) def _create_matrices( n_channels, return_f_hat: bool = False ) -> tuple[ sp.MutableDenseMatrix, sp.MutableDenseMatrix, sp.MutableDenseMatrix ]: # pylint: disable=no-member k_matrix = create_symbol_matrix("K", m=n_channels, n=n_channels) rho = _create_rho_matrix(n_channels) sqrt_rho: sp.MutableDenseMatrix = sp.sqrt(rho).doit() sqrt_rho_conj: sp.MutableDenseMatrix = sp.conjugate(sqrt_rho) k_matrix = create_symbol_matrix("K", n_channels, n_channels) k_hat = sqrt_rho_conj.inv() * k_matrix * sqrt_rho.inv() p_vector = create_symbol_matrix("P", m=n_channels, n=1) f_hat = (sp.eye(n_channels) - sp.I * k_hat * rho).inv() * p_vector if return_f_hat: return f_hat, k_matrix, p_vector f_vector = sqrt_rho * f_hat return f_vector, k_matrix, p_vector
[docs] @classmethod def formulate( # pylint: disable=too-many-locals cls, n_channels, n_poles, parametrize: bool = True, **kwargs ) -> sp.MutableDenseMatrix: r"""Implementation of :eq:`F in terms of P`. Args: n_channels: Number of coupled channels. n_poles: Number of poles. parametrize: Set to `False` if don't want to parametrize and only get symbols for the matrix multiplication of :math:`\boldsymbol{K}` and :math:`\boldsymbol{\rho}`. return_f_hat: Set to `True` if you want to get the Lorentz-invariant :math:`\hat{F}`-vector instead of the :math:`T`-vector from Eq. :eq:`invariant vectors`. """ return_f_hat: bool = kwargs.pop("return_f_hat", False) f_vector, k_matrix, p_vector = cls._create_matrices( n_channels, return_f_hat ) if not parametrize: return f_vector phsp_factor: PhaseSpaceFactorProtocol = kwargs.get( "phsp_factor", PhaseSpaceFactor ) s = sp.Symbol("s") pole_position = sp.IndexedBase("m") pole_width = sp.IndexedBase("Gamma") residue_constant = sp.IndexedBase("gamma") m_a = sp.IndexedBase("m_a") m_b = sp.IndexedBase("m_b") pole_id = sp.Symbol("R", integer=True, positive=True) angular_momentum = kwargs.get("angular_momentum", 0) meson_radius = kwargs.get("meson_radius", 1) return ( f_vector.xreplace( { k_matrix[i, j]: RelativisticKMatrix.parametrization( i=i, j=j, s=s, pole_position=pole_position, pole_width=pole_width, m_a=m_a, m_b=m_b, residue_constant=residue_constant, n_poles=n_poles, pole_id=pole_id, angular_momentum=angular_momentum, meson_radius=meson_radius, ) for i in range(n_channels) for j in range(n_channels) } ) .xreplace( { p_vector[i]: cls.parametrization( i=i, s=s, pole_position=pole_position, pole_width=pole_width, m_a=m_a, m_b=m_b, beta_constant=sp.IndexedBase("beta"), residue_constant=residue_constant, n_poles=n_poles, pole_id=pole_id, angular_momentum=angular_momentum, meson_radius=meson_radius, ) for i in range(n_channels) } ) .xreplace( { sp.Symbol(f"rho{i}"): phsp_factor(s, m_a[i], m_b[i]) for i in range(n_channels) } ) )
[docs] @staticmethod def parametrization( # pylint: disable=too-many-arguments, too-many-locals i, s, pole_position: sp.IndexedBase, pole_width: sp.IndexedBase, m_a: sp.IndexedBase, m_b: sp.IndexedBase, beta_constant: sp.IndexedBase, residue_constant: sp.IndexedBase, n_poles, pole_id, angular_momentum=0, meson_radius=1, ) -> sp.Expr: beta = beta_constant[pole_id] gamma = residue_constant[pole_id, i] mass0 = pole_position[pole_id] width = pole_width[pole_id, i] q_squared = BreakupMomentumSquared(s, m_a[i], m_b[i]) form_factor_squared = BlattWeisskopfSquared( angular_momentum, z=q_squared * meson_radius**2 ) form_factor = sp.sqrt(form_factor_squared) return sp.Sum( beta * gamma * mass0 * width * form_factor / (mass0**2 - s), (pole_id, 1, n_poles), )
def _create_rho_matrix(n_channels) -> sp.MutableDenseMatrix: """Create a phase space matrix with :code:`n_channels`. >>> _create_rho_matrix(n_channels=2) Matrix([ [rho0, 0], [ 0, rho1]]) """ rho_matrix: sp.MutableDenseMatrix = sp.zeros(n_channels, n_channels) for i in range(n_channels): rho_matrix[i, i] = sp.Symbol(f"rho{i}") return rho_matrix