laddu_amplitudes/
kmatrix.rs

1use std::array;
2
3use nalgebra::{matrix, vector};
4use nalgebra::{SMatrix, SVector};
5use num::traits::ConstOne;
6use num::traits::FloatConst;
7use serde::{Deserialize, Serialize};
8
9use laddu_core::{
10    amplitudes::{Amplitude, AmplitudeID, ParameterLike},
11    data::Event,
12    resources::{Cache, ComplexVectorID, MatrixID, ParameterID, Parameters, Resources},
13    utils::{
14        functions::{blatt_weisskopf, chi_plus, rho},
15        variables::{Mass, Variable},
16    },
17    Complex, DVector, Float, LadduError,
18};
19
20#[cfg(feature = "python")]
21use laddu_python::{
22    amplitudes::{PyAmplitude, PyParameterLike},
23    utils::variables::PyMass,
24};
25#[cfg(feature = "python")]
26use pyo3::prelude::*;
27
28/// An Adler zero term used in a K-matrix.
29#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
30pub struct AdlerZero {
31    /// The zero position $`s_0`$.
32    pub s_0: Float,
33    /// The normalization factor $`s_\text{norm}`$.
34    pub s_norm: Float,
35}
36
37/// Methods for computing various parts of a K-matrix with fixed couplings and mass poles.
38#[derive(Clone, Debug, Serialize, Deserialize)]
39pub struct FixedKMatrix<const CHANNELS: usize, const RESONANCES: usize> {
40    g: SMatrix<Float, CHANNELS, RESONANCES>,
41    c: SMatrix<Float, CHANNELS, CHANNELS>,
42    m1s: SVector<Float, CHANNELS>,
43    m2s: SVector<Float, CHANNELS>,
44    mrs: SVector<Float, RESONANCES>,
45    adler_zero: Option<AdlerZero>,
46    l: usize,
47}
48impl<const CHANNELS: usize, const RESONANCES: usize> FixedKMatrix<CHANNELS, RESONANCES> {
49    fn c_mat(&self, s: Float) -> SMatrix<Complex<Float>, CHANNELS, CHANNELS> {
50        SMatrix::from_diagonal(&SVector::from_fn(|i, _| {
51            let m1 = self.m1s[i];
52            let m2 = self.m2s[i];
53            ((rho(s, m1, m2)
54                * Complex::ln(
55                    (chi_plus(s, m1, m2) + rho(s, m1, m2)) / (chi_plus(s, m1, m2) - rho(s, m1, m2)),
56                ))
57                - (chi_plus(s, m1, m2) * ((m2 - m1) / (m1 + m2)) * Float::ln(m2 / m1)))
58                / Float::PI()
59        }))
60    }
61    fn barrier_mat(&self, s: Float) -> SMatrix<Float, CHANNELS, RESONANCES> {
62        let m0 = Float::sqrt(s);
63        SMatrix::from_fn(|i, a| {
64            let m1 = self.m1s[i];
65            let m2 = self.m2s[i];
66            let mr = self.mrs[a];
67            blatt_weisskopf(m0, m1, m2, self.l) / blatt_weisskopf(mr, m1, m2, self.l)
68        })
69    }
70    fn product_of_poles(&self, s: Float) -> Float {
71        self.mrs.map(|m| m.powi(2) - s).product()
72    }
73    fn product_of_poles_except_one(&self, s: Float, a_i: usize) -> Float {
74        self.mrs
75            .iter()
76            .enumerate()
77            .filter_map(|(a_j, m_j)| {
78                if a_j != a_i {
79                    Some(m_j.powi(2) - s)
80                } else {
81                    None
82                }
83            })
84            .product()
85    }
86
87    fn k_mat(&self, s: Float) -> SMatrix<Complex<Float>, CHANNELS, CHANNELS> {
88        let bf = self.barrier_mat(s);
89        SMatrix::from_fn(|i, j| {
90            self.adler_zero
91                .map_or(Float::ONE, |az| (s - az.s_0) / az.s_norm)
92                * (0..RESONANCES)
93                    .map(|a| {
94                        Complex::from(
95                            bf[(i, a)] * bf[(j, a)] * self.g[(i, a)] * self.g[(j, a)]
96                                + (self.c[(i, j)] * (self.mrs[a].powi(2) - s)),
97                        ) * self.product_of_poles_except_one(s, a)
98                    })
99                    .sum::<Complex<Float>>()
100        })
101    }
102
103    fn ikc_inv_vec(&self, s: Float, channel: usize) -> SVector<Complex<Float>, CHANNELS> {
104        let i_mat: SMatrix<Complex<Float>, CHANNELS, CHANNELS> = SMatrix::identity();
105        let k_mat = self.k_mat(s);
106        let c_mat = self.c_mat(s);
107        let ikc_mat = i_mat.scale(self.product_of_poles(s)) + k_mat * c_mat;
108        let ikc_inv_mat = ikc_mat.try_inverse().expect("Matrix inverse failed!");
109        ikc_inv_mat.row(channel).transpose()
110    }
111
112    fn p_vec_constants(&self, s: Float) -> SMatrix<Float, CHANNELS, RESONANCES> {
113        let barrier_mat = self.barrier_mat(s);
114        SMatrix::from_fn(|i, a| {
115            barrier_mat[(i, a)] * self.g[(i, a)] * self.product_of_poles_except_one(s, a)
116        })
117    }
118
119    fn compute(
120        betas: &SVector<Complex<Float>, RESONANCES>,
121        ikc_inv_vec: &SVector<Complex<Float>, CHANNELS>,
122        p_vec_constants: &SMatrix<Float, CHANNELS, RESONANCES>,
123    ) -> Complex<Float> {
124        let p_vec: SVector<Complex<Float>, CHANNELS> = SVector::from_fn(|j, _| {
125            (0..RESONANCES)
126                .map(|a| betas[a] * p_vec_constants[(j, a)])
127                .sum()
128        });
129        ikc_inv_vec.dot(&p_vec)
130    }
131
132    fn compute_gradient(
133        ikc_inv_vec: &SVector<Complex<Float>, CHANNELS>,
134        p_vec_constants: &SMatrix<Float, CHANNELS, RESONANCES>,
135    ) -> DVector<Complex<Float>> {
136        DVector::from_fn(RESONANCES, |a, _| {
137            (0..RESONANCES)
138                .map(|j| ikc_inv_vec[j] * p_vec_constants[(j, a)])
139                .sum()
140        })
141    }
142}
143
144/// A K-matrix parameterization for $`f_0`$ particles described by Kopf et al.[^1] with fixed couplings and mass poles
145/// (free production couplings only).
146///
147/// [^1]: Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of $`\bar{p}p`$-, $`\pi^- p`$- and $`\pi \pi`$-Data. The European Physical Journal C, 81(12). [doi:10.1140/epjc/s10052-021-09821-2](https://doi.org/10.1140/epjc/s10052-021-09821-2)
148#[derive(Clone, Serialize, Deserialize)]
149pub struct KopfKMatrixF0 {
150    name: String,
151    channel: usize,
152    mass: Mass,
153    constants: FixedKMatrix<5, 5>,
154    couplings_real: [ParameterLike; 5],
155    couplings_imag: [ParameterLike; 5],
156    couplings_indices_real: [ParameterID; 5],
157    couplings_indices_imag: [ParameterID; 5],
158    ikc_cache_index: ComplexVectorID<5>,
159    p_vec_cache_index: MatrixID<5, 5>,
160}
161
162impl KopfKMatrixF0 {
163    /// Construct a new [`KopfKMatrixF0`] with the given name, production couplings, channel,
164    /// and input mass.
165    ///
166    /// | Channel index | Channel |
167    /// | ------------- | ------- |
168    /// | 0             | $`\pi\pi`$ |
169    /// | 1             | $`2\pi 2\pi`$ |
170    /// | 2             | $`K\bar{K}`$ |
171    /// | 3             | $`\eta\eta`$ |
172    /// | 4             | $`\eta\eta'`$ |
173    ///
174    /// | Pole names |
175    /// | ---------- |
176    /// | $`f_0(500)`$ |
177    /// | $`f_0(980)`$ |
178    /// | $`f_0(1370)`$ |
179    /// | $`f_0(1500)`$ |
180    /// | $`f_0(1710)`$ |
181    pub fn new(
182        name: &str,
183        couplings: [[ParameterLike; 2]; 5],
184        channel: usize,
185        mass: &Mass,
186    ) -> Box<Self> {
187        let mut couplings_real: [ParameterLike; 5] = array::from_fn(|_| ParameterLike::default());
188        let mut couplings_imag: [ParameterLike; 5] = array::from_fn(|_| ParameterLike::default());
189        for i in 0..5 {
190            couplings_real[i] = couplings[i][0].clone();
191            couplings_imag[i] = couplings[i][1].clone();
192        }
193        Self {
194            name: name.to_string(),
195            channel,
196            mass: mass.clone(),
197            constants: FixedKMatrix {
198                g: matrix![
199                     0.74987,  0.06401, -0.23417,  0.01270, -0.14242;
200                    -0.01257,  0.00204, -0.01032,  0.26700,  0.22780;
201                     0.27536,  0.77413,  0.72283,  0.09214,  0.15981;
202                    -0.15102,  0.50999,  0.11934,  0.02742,  0.16272;
203                     0.36103,  0.13112,  0.36792, -0.04025, -0.17397
204
205                ],
206                c: matrix![
207                     0.03728,  0.00000, -0.01398, -0.02203,  0.01397;
208                     0.00000,  0.00000,  0.00000,  0.00000,  0.00000;
209                    -0.01398,  0.00000,  0.02349,  0.03101, -0.04003;
210                    -0.02203,  0.00000,  0.03101, -0.13769, -0.06722;
211                     0.01397,  0.00000, -0.04003, -0.06722, -0.28401
212                ],
213                m1s: vector![0.1349768, 2.0 * 0.1349768, 0.493677, 0.547862, 0.547862],
214                m2s: vector![0.1349768, 2.0 * 0.1349768, 0.497611, 0.547862, 0.95778],
215                mrs: vector![0.51461, 0.90630, 1.23089, 1.46104, 1.69611],
216                adler_zero: Some(AdlerZero {
217                    s_0: 0.0091125,
218                    s_norm: 1.0,
219                }),
220                l: 0,
221            },
222            couplings_real,
223            couplings_imag,
224            couplings_indices_real: [ParameterID::default(); 5],
225            couplings_indices_imag: [ParameterID::default(); 5],
226            ikc_cache_index: ComplexVectorID::default(),
227            p_vec_cache_index: MatrixID::default(),
228        }
229        .into()
230    }
231}
232
233#[typetag::serde]
234impl Amplitude for KopfKMatrixF0 {
235    fn register(&mut self, resources: &mut Resources) -> Result<AmplitudeID, LadduError> {
236        for i in 0..self.couplings_indices_real.len() {
237            self.couplings_indices_real[i] = resources.register_parameter(&self.couplings_real[i]);
238            self.couplings_indices_imag[i] = resources.register_parameter(&self.couplings_imag[i]);
239        }
240        self.ikc_cache_index = resources
241            .register_complex_vector(Some(&format!("KopfKMatrixF0<{}> ikc_vec", self.name)));
242        self.p_vec_cache_index =
243            resources.register_matrix(Some(&format!("KopfKMatrixF0<{}> p_vec", self.name)));
244        resources.register_amplitude(&self.name)
245    }
246
247    fn precompute(&self, event: &Event, cache: &mut Cache) {
248        let s = self.mass.value(event).powi(2);
249        cache.store_complex_vector(
250            self.ikc_cache_index,
251            self.constants.ikc_inv_vec(s, self.channel),
252        );
253        cache.store_matrix(self.p_vec_cache_index, self.constants.p_vec_constants(s));
254    }
255
256    fn compute(&self, parameters: &Parameters, _event: &Event, cache: &Cache) -> Complex<Float> {
257        let betas = SVector::from_fn(|i, _| {
258            Complex::new(
259                parameters.get(self.couplings_indices_real[i]),
260                parameters.get(self.couplings_indices_imag[i]),
261            )
262        });
263        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
264        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
265        FixedKMatrix::compute(&betas, &ikc_inv_vec, &p_vec_constants)
266    }
267
268    fn compute_gradient(
269        &self,
270        _parameters: &Parameters,
271        _event: &Event,
272        cache: &Cache,
273        gradient: &mut DVector<Complex<Float>>,
274    ) {
275        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
276        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
277        let internal_gradient = FixedKMatrix::compute_gradient(&ikc_inv_vec, &p_vec_constants);
278        for i in 0..5 {
279            if let ParameterID::Parameter(index) = self.couplings_indices_real[i] {
280                gradient[index] = internal_gradient[i];
281            }
282            if let ParameterID::Parameter(index) = self.couplings_indices_imag[i] {
283                gradient[index] = Complex::<Float>::I * internal_gradient[i];
284            }
285        }
286    }
287}
288
289/// A fixed K-Matrix Amplitude for :math:`f_0` mesons
290///
291/// Parameters
292/// ----------
293/// name : str
294///     The Amplitude name
295/// couplings : list of list of laddu.ParameterLike
296///     Each initial-state coupling (as a list of pairs of real and imaginary parts)
297/// channel : int
298///     The channel onto which the K-Matrix is projected
299/// mass: laddu.Mass
300///     The total mass of the resonance
301///
302/// Returns
303/// -------
304/// laddu.Amplitude
305///     An Amplitude which can be registered by a laddu.Manager
306///
307/// See Also
308/// --------
309/// laddu.Manager
310///
311/// Notes
312/// -----
313/// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data
314/// from that paper, leaving the couplings to the initial state free
315///
316/// +---------------+-------------------+
317/// | Channel index | Channel           |
318/// +===============+===================+
319/// | 0             | :math:`\pi\pi`    |
320/// +---------------+-------------------+
321/// | 1             | :math:`2\pi 2\pi` |
322/// +---------------+-------------------+
323/// | 2             | :math:`K\bar{K}`  |
324/// +---------------+-------------------+
325/// | 3             | :math:`\eta\eta`  |
326/// +---------------+-------------------+
327/// | 4             | :math:`\eta\eta'` |
328/// +---------------+-------------------+
329///
330/// +-------------------+
331/// | Pole names        |
332/// +===================+
333/// | :math:`f_0(500)`  |
334/// +-------------------+
335/// | :math:`f_0(980)`  |
336/// +-------------------+
337/// | :math:`f_0(1370)` |
338/// +-------------------+
339/// | :math:`f_0(1500)` |
340/// +-------------------+
341/// | :math:`f_0(1710)` |
342/// +-------------------+
343///
344/// .. [Kopf] Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of :math:`\bar{p}p`-, :math:`\pi^- p`- and :math:`\pi \pi`-Data. The European Physical Journal C, 81(12). `doi:10.1140/epjc/s10052-021-09821-2 <https://doi.org/10.1140/epjc/s10052-021-09821-2>`__
345///
346#[cfg(feature = "python")]
347#[pyfunction(name = "KopfKMatrixF0")]
348pub fn py_kopf_kmatrix_f0(
349    name: &str,
350    couplings: [[PyParameterLike; 2]; 5],
351    channel: usize,
352    mass: PyMass,
353) -> PyAmplitude {
354    PyAmplitude(KopfKMatrixF0::new(
355        name,
356        array::from_fn(|i| array::from_fn(|j| couplings[i][j].clone().0)),
357        channel,
358        &mass.0,
359    ))
360}
361
362/// A K-matrix parameterization for $`f_2`$ particles described by Kopf et al.[^1] with fixed couplings and mass poles
363/// (free production couplings only).
364///
365/// [^1]: Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of $`\bar{p}p`$-, $`\pi^- p`$- and $`\pi \pi`$-Data. The European Physical Journal C, 81(12). [doi:10.1140/epjc/s10052-021-09821-2](https://doi.org/10.1140/epjc/s10052-021-09821-2)
366#[derive(Clone, Serialize, Deserialize)]
367pub struct KopfKMatrixF2 {
368    name: String,
369    channel: usize,
370    mass: Mass,
371    constants: FixedKMatrix<4, 4>,
372    couplings_real: [ParameterLike; 4],
373    couplings_imag: [ParameterLike; 4],
374    couplings_indices_real: [ParameterID; 4],
375    couplings_indices_imag: [ParameterID; 4],
376    ikc_cache_index: ComplexVectorID<4>,
377    p_vec_cache_index: MatrixID<4, 4>,
378}
379
380impl KopfKMatrixF2 {
381    /// Construct a new [`KopfKMatrixF2`] with the given name, production couplings, channel,
382    /// and input mass.
383    ///
384    /// | Channel index | Channel |
385    /// | ------------- | ------- |
386    /// | 0             | $`\pi\pi`$ |
387    /// | 1             | $`2\pi 2\pi`$ |
388    /// | 2             | $`K\bar{K}`$ |
389    /// | 3             | $`\eta\eta`$ |
390    ///
391    /// | Pole names |
392    /// | ---------- |
393    /// | $`f_2(1270)`$ |
394    /// | $`f_2'(1525)`$ |
395    /// | $`f_2(1810)`$ |
396    /// | $`f_2(1950)`$ |
397    pub fn new(
398        name: &str,
399        couplings: [[ParameterLike; 2]; 4],
400        channel: usize,
401        mass: &Mass,
402    ) -> Box<Self> {
403        let mut couplings_real: [ParameterLike; 4] = array::from_fn(|_| ParameterLike::default());
404        let mut couplings_imag: [ParameterLike; 4] = array::from_fn(|_| ParameterLike::default());
405        for i in 0..4 {
406            couplings_real[i] = couplings[i][0].clone();
407            couplings_imag[i] = couplings[i][1].clone();
408        }
409        Self {
410            name: name.to_string(),
411            channel,
412            mass: mass.clone(),
413            constants: FixedKMatrix {
414                g: matrix![
415                     0.40033,  0.01820, -0.06709, -0.49924;
416                     0.15479,  0.17300,  0.22941,  0.19295;
417                    -0.08900,  0.32393, -0.43133,  0.27975;
418                    -0.00113,  0.15256,  0.23721, -0.03987
419                ],
420                c: matrix![
421                    -0.04319,  0.00000,  0.00984,  0.01028;
422                     0.00000,  0.00000,  0.00000,  0.00000;
423                     0.00984,  0.00000, -0.07344,  0.05533;
424                     0.01028,  0.00000,  0.05533, -0.05183
425                ],
426                m1s: vector![0.1349768, 2.0 * 0.1349768, 0.493677, 0.547862],
427                m2s: vector![0.1349768, 2.0 * 0.1349768, 0.497611, 0.547862],
428                mrs: vector![1.15299, 1.48359, 1.72923, 1.96700],
429                adler_zero: None,
430                l: 2,
431            },
432            couplings_real,
433            couplings_imag,
434            couplings_indices_real: [ParameterID::default(); 4],
435            couplings_indices_imag: [ParameterID::default(); 4],
436            ikc_cache_index: ComplexVectorID::default(),
437            p_vec_cache_index: MatrixID::default(),
438        }
439        .into()
440    }
441}
442
443#[typetag::serde]
444impl Amplitude for KopfKMatrixF2 {
445    fn register(&mut self, resources: &mut Resources) -> Result<AmplitudeID, LadduError> {
446        for i in 0..self.couplings_indices_real.len() {
447            self.couplings_indices_real[i] = resources.register_parameter(&self.couplings_real[i]);
448            self.couplings_indices_imag[i] = resources.register_parameter(&self.couplings_imag[i]);
449        }
450        self.ikc_cache_index = resources
451            .register_complex_vector(Some(&format!("KopfKMatrixF2<{}> ikc_vec", self.name)));
452        self.p_vec_cache_index =
453            resources.register_matrix(Some(&format!("KopfKMatrixF2<{}> p_vec", self.name)));
454        resources.register_amplitude(&self.name)
455    }
456
457    fn precompute(&self, event: &Event, cache: &mut Cache) {
458        let s = self.mass.value(event).powi(2);
459        cache.store_complex_vector(
460            self.ikc_cache_index,
461            self.constants.ikc_inv_vec(s, self.channel),
462        );
463        cache.store_matrix(self.p_vec_cache_index, self.constants.p_vec_constants(s));
464    }
465
466    fn compute(&self, parameters: &Parameters, _event: &Event, cache: &Cache) -> Complex<Float> {
467        let betas = SVector::from_fn(|i, _| {
468            Complex::new(
469                parameters.get(self.couplings_indices_real[i]),
470                parameters.get(self.couplings_indices_imag[i]),
471            )
472        });
473        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
474        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
475        FixedKMatrix::compute(&betas, &ikc_inv_vec, &p_vec_constants)
476    }
477
478    fn compute_gradient(
479        &self,
480        _parameters: &Parameters,
481        _event: &Event,
482        cache: &Cache,
483        gradient: &mut DVector<Complex<Float>>,
484    ) {
485        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
486        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
487        let internal_gradient = FixedKMatrix::compute_gradient(&ikc_inv_vec, &p_vec_constants);
488        for i in 0..4 {
489            if let ParameterID::Parameter(index) = self.couplings_indices_real[i] {
490                gradient[index] = internal_gradient[i];
491            }
492            if let ParameterID::Parameter(index) = self.couplings_indices_imag[i] {
493                gradient[index] = Complex::<Float>::I * internal_gradient[i];
494            }
495        }
496    }
497}
498
499/// A fixed K-Matrix Amplitude for :math:`f_2` mesons
500///
501/// Parameters
502/// ----------
503/// name : str
504///     The Amplitude name
505/// couplings : list of list of laddu.ParameterLike
506///     Each initial-state coupling (as a list of pairs of real and imaginary parts)
507/// channel : int
508///     The channel onto which the K-Matrix is projected
509/// mass: laddu.Mass
510///     The total mass of the resonance
511///
512/// Returns
513/// -------
514/// laddu.Amplitude
515///     An Amplitude which can be registered by a laddu.Manager
516///
517/// See Also
518/// --------
519/// laddu.Manager
520///
521/// Notes
522/// -----
523/// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data
524/// from that paper, leaving the couplings to the initial state free
525///
526/// +---------------+-------------------+
527/// | Channel index | Channel           |
528/// +===============+===================+
529/// | 0             | :math:`\pi\pi`    |
530/// +---------------+-------------------+
531/// | 1             | :math:`2\pi 2\pi` |
532/// +---------------+-------------------+
533/// | 2             | :math:`K\bar{K}`  |
534/// +---------------+-------------------+
535/// | 3             | :math:`\eta\eta`  |
536/// +---------------+-------------------+
537///
538/// +---------------------+
539/// | Pole names          |
540/// +=====================+
541/// | :math:`f_2(1270)`   |
542/// +---------------------+
543/// | :math:`f_2'(1525)`  |
544/// +---------------------+
545/// | :math:`f_2(1810)`   |
546/// +---------------------+
547/// | :math:`f_2(1950)`   |
548/// +---------------------+
549///
550#[cfg(feature = "python")]
551#[pyfunction(name = "KopfKMatrixF2")]
552pub fn py_kopf_kmatrix_f2(
553    name: &str,
554    couplings: [[PyParameterLike; 2]; 4],
555    channel: usize,
556    mass: PyMass,
557) -> PyAmplitude {
558    PyAmplitude(KopfKMatrixF2::new(
559        name,
560        array::from_fn(|i| array::from_fn(|j| couplings[i][j].clone().0)),
561        channel,
562        &mass.0,
563    ))
564}
565
566/// A K-matrix parameterization for $`a_0`$ particles described by Kopf et al.[^1] with fixed couplings and mass poles
567/// (free production couplings only).
568///
569/// [^1]: Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of $`\bar{p}p`$-, $`\pi^- p`$- and $`\pi \pi`$-Data. The European Physical Journal C, 81(12). [doi:10.1140/epjc/s10052-021-09821-2](https://doi.org/10.1140/epjc/s10052-021-09821-2)
570#[derive(Clone, Serialize, Deserialize)]
571pub struct KopfKMatrixA0 {
572    name: String,
573    channel: usize,
574    mass: Mass,
575    constants: FixedKMatrix<2, 2>,
576    couplings_real: [ParameterLike; 2],
577    couplings_imag: [ParameterLike; 2],
578    couplings_indices_real: [ParameterID; 2],
579    couplings_indices_imag: [ParameterID; 2],
580    ikc_cache_index: ComplexVectorID<2>,
581    p_vec_cache_index: MatrixID<2, 2>,
582}
583
584impl KopfKMatrixA0 {
585    /// Construct a new [`KopfKMatrixA0`] with the given name, production couplings, channel,
586    /// and input mass.
587    ///
588    /// | Channel index | Channel |
589    /// | ------------- | ------- |
590    /// | 0             | $`\pi\eta`$ |
591    /// | 1             | $`K\bar{K}`$ |
592    ///
593    /// | Pole names |
594    /// | ---------- |
595    /// | $`a_0(980)`$ |
596    /// | $`a_0(1450)`$ |
597    pub fn new(
598        name: &str,
599        couplings: [[ParameterLike; 2]; 2],
600        channel: usize,
601        mass: &Mass,
602    ) -> Box<Self> {
603        let mut couplings_real: [ParameterLike; 2] = array::from_fn(|_| ParameterLike::default());
604        let mut couplings_imag: [ParameterLike; 2] = array::from_fn(|_| ParameterLike::default());
605        for i in 0..2 {
606            couplings_real[i] = couplings[i][0].clone();
607            couplings_imag[i] = couplings[i][1].clone();
608        }
609        Self {
610            name: name.to_string(),
611            channel,
612            mass: mass.clone(),
613            constants: FixedKMatrix {
614                g: matrix![
615                     0.43215,  0.19000;
616                    -0.28825,  0.43372
617                ],
618                c: matrix![
619                     0.00000,  0.00000;
620                     0.00000,  0.00000
621                ],
622                m1s: vector![0.1349768, 0.493677],
623                m2s: vector![0.547862, 0.497611],
624                mrs: vector![0.95395, 1.26767],
625                adler_zero: None,
626                l: 0,
627            },
628            couplings_real,
629            couplings_imag,
630            couplings_indices_real: [ParameterID::default(); 2],
631            couplings_indices_imag: [ParameterID::default(); 2],
632            ikc_cache_index: ComplexVectorID::default(),
633            p_vec_cache_index: MatrixID::default(),
634        }
635        .into()
636    }
637}
638
639#[typetag::serde]
640impl Amplitude for KopfKMatrixA0 {
641    fn register(&mut self, resources: &mut Resources) -> Result<AmplitudeID, LadduError> {
642        for i in 0..self.couplings_indices_real.len() {
643            self.couplings_indices_real[i] = resources.register_parameter(&self.couplings_real[i]);
644            self.couplings_indices_imag[i] = resources.register_parameter(&self.couplings_imag[i]);
645        }
646        self.ikc_cache_index = resources
647            .register_complex_vector(Some(&format!("KopfKMatrixA0<{}> ikc_vec", self.name)));
648        self.p_vec_cache_index =
649            resources.register_matrix(Some(&format!("KopfKMatrixA0<{}> p_vec", self.name)));
650        resources.register_amplitude(&self.name)
651    }
652
653    fn precompute(&self, event: &Event, cache: &mut Cache) {
654        let s = self.mass.value(event).powi(2);
655        cache.store_complex_vector(
656            self.ikc_cache_index,
657            self.constants.ikc_inv_vec(s, self.channel),
658        );
659        cache.store_matrix(self.p_vec_cache_index, self.constants.p_vec_constants(s));
660    }
661
662    fn compute(&self, parameters: &Parameters, _event: &Event, cache: &Cache) -> Complex<Float> {
663        let betas = SVector::from_fn(|i, _| {
664            Complex::new(
665                parameters.get(self.couplings_indices_real[i]),
666                parameters.get(self.couplings_indices_imag[i]),
667            )
668        });
669        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
670        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
671        FixedKMatrix::compute(&betas, &ikc_inv_vec, &p_vec_constants)
672    }
673
674    fn compute_gradient(
675        &self,
676        _parameters: &Parameters,
677        _event: &Event,
678        cache: &Cache,
679        gradient: &mut DVector<Complex<Float>>,
680    ) {
681        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
682        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
683        let internal_gradient = FixedKMatrix::compute_gradient(&ikc_inv_vec, &p_vec_constants);
684        for i in 0..2 {
685            if let ParameterID::Parameter(index) = self.couplings_indices_real[i] {
686                gradient[index] = internal_gradient[i];
687            }
688            if let ParameterID::Parameter(index) = self.couplings_indices_imag[i] {
689                gradient[index] = Complex::<Float>::I * internal_gradient[i];
690            }
691        }
692    }
693}
694
695/// A fixed K-Matrix Amplitude for :math:`a_0` mesons
696///
697/// Parameters
698/// ----------
699/// name : str
700///     The Amplitude name
701/// couplings : list of list of laddu.ParameterLike
702///     Each initial-state coupling (as a list of pairs of real and imaginary parts)
703/// channel : int
704///     The channel onto which the K-Matrix is projected
705/// mass: laddu.Mass
706///     The total mass of the resonance
707///
708/// Returns
709/// -------
710/// laddu.Amplitude
711///     An Amplitude which can be registered by a laddu.Manager
712///
713/// See Also
714/// --------
715/// laddu.Manager
716///
717/// Notes
718/// -----
719/// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data
720/// from that paper, leaving the couplings to the initial state free
721///
722/// +---------------+-------------------+
723/// | Channel index | Channel           |
724/// +===============+===================+
725/// | 0             | :math:`\pi\eta`   |
726/// +---------------+-------------------+
727/// | 1             | :math:`K\bar{K}`  |
728/// +---------------+-------------------+
729///
730/// +-------------------+
731/// | Pole names        |
732/// +===================+
733/// | :math:`a_0(980)`  |
734/// +-------------------+
735/// | :math:`a_0(1450)` |
736/// +-------------------+
737///
738#[cfg(feature = "python")]
739#[pyfunction(name = "KopfKMatrixA0")]
740pub fn py_kopf_kmatrix_a0(
741    name: &str,
742    couplings: [[PyParameterLike; 2]; 2],
743    channel: usize,
744    mass: PyMass,
745) -> PyAmplitude {
746    PyAmplitude(KopfKMatrixA0::new(
747        name,
748        array::from_fn(|i| array::from_fn(|j| couplings[i][j].clone().0)),
749        channel,
750        &mass.0,
751    ))
752}
753
754/// A K-matrix parameterization for $`a_2`$ particles described by Kopf et al.[^1] with fixed couplings and mass poles
755/// (free production couplings only).
756///
757/// [^1]: Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of $`\bar{p}p`$-, $`\pi^- p`$- and $`\pi \pi`$-Data. The European Physical Journal C, 81(12). [doi:10.1140/epjc/s10052-021-09821-2](https://doi.org/10.1140/epjc/s10052-021-09821-2)
758#[derive(Clone, Serialize, Deserialize)]
759pub struct KopfKMatrixA2 {
760    name: String,
761    channel: usize,
762    mass: Mass,
763    constants: FixedKMatrix<3, 2>,
764    couplings_real: [ParameterLike; 2],
765    couplings_imag: [ParameterLike; 2],
766    couplings_indices_real: [ParameterID; 2],
767    couplings_indices_imag: [ParameterID; 2],
768    ikc_cache_index: ComplexVectorID<3>,
769    p_vec_cache_index: MatrixID<3, 2>,
770}
771
772impl KopfKMatrixA2 {
773    /// Construct a new [`KopfKMatrixA2`] with the given name, production couplings, channel,
774    /// and input mass.
775    ///
776    /// | Channel index | Channel |
777    /// | ------------- | ------- |
778    /// | 0             | $`\pi\eta`$ |
779    /// | 1             | $`K\bar{K}`$ |
780    /// | 2             | $`\pi\eta'`$ |
781    ///
782    /// | Pole names |
783    /// | ---------- |
784    /// | $`a_2(1320)`$ |
785    /// | $`a_2(1700)`$ |
786    pub fn new(
787        name: &str,
788        couplings: [[ParameterLike; 2]; 2],
789        channel: usize,
790        mass: &Mass,
791    ) -> Box<Self> {
792        let mut couplings_real: [ParameterLike; 2] = array::from_fn(|_| ParameterLike::default());
793        let mut couplings_imag: [ParameterLike; 2] = array::from_fn(|_| ParameterLike::default());
794        for i in 0..2 {
795            couplings_real[i] = couplings[i][0].clone();
796            couplings_imag[i] = couplings[i][1].clone();
797        }
798        Self {
799            name: name.to_string(),
800            channel,
801            mass: mass.clone(),
802            constants: FixedKMatrix {
803                g: matrix![
804                     0.30073,  0.68567;
805                     0.21426,  0.12543;
806                    -0.09162,  0.00184
807
808                ],
809                c: matrix![
810                    -0.40184,  0.00033, -0.08707;
811                     0.00033, -0.21416, -0.06193;
812                    -0.08707, -0.06193, -0.17435
813                ],
814                m1s: vector![0.1349768, 0.493677, 0.1349768],
815                m2s: vector![0.547862, 0.497611, 0.95778],
816                mrs: vector![1.30080, 1.75351],
817                adler_zero: None,
818                l: 2,
819            },
820            couplings_real,
821            couplings_imag,
822            couplings_indices_real: [ParameterID::default(); 2],
823            couplings_indices_imag: [ParameterID::default(); 2],
824            ikc_cache_index: ComplexVectorID::default(),
825            p_vec_cache_index: MatrixID::default(),
826        }
827        .into()
828    }
829}
830
831#[typetag::serde]
832impl Amplitude for KopfKMatrixA2 {
833    fn register(&mut self, resources: &mut Resources) -> Result<AmplitudeID, LadduError> {
834        for i in 0..self.couplings_indices_real.len() {
835            self.couplings_indices_real[i] = resources.register_parameter(&self.couplings_real[i]);
836            self.couplings_indices_imag[i] = resources.register_parameter(&self.couplings_imag[i]);
837        }
838        self.ikc_cache_index = resources
839            .register_complex_vector(Some(&format!("KopfKMatrixA2<{}> ikc_vec", self.name)));
840        self.p_vec_cache_index =
841            resources.register_matrix(Some(&format!("KopfKMatrixA2<{}> p_vec", self.name)));
842        resources.register_amplitude(&self.name)
843    }
844
845    fn precompute(&self, event: &Event, cache: &mut Cache) {
846        let s = self.mass.value(event).powi(2);
847        cache.store_complex_vector(
848            self.ikc_cache_index,
849            self.constants.ikc_inv_vec(s, self.channel),
850        );
851        cache.store_matrix(self.p_vec_cache_index, self.constants.p_vec_constants(s));
852    }
853
854    fn compute(&self, parameters: &Parameters, _event: &Event, cache: &Cache) -> Complex<Float> {
855        let betas = SVector::from_fn(|i, _| {
856            Complex::new(
857                parameters.get(self.couplings_indices_real[i]),
858                parameters.get(self.couplings_indices_imag[i]),
859            )
860        });
861        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
862        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
863        FixedKMatrix::compute(&betas, &ikc_inv_vec, &p_vec_constants)
864    }
865
866    fn compute_gradient(
867        &self,
868        _parameters: &Parameters,
869        _event: &Event,
870        cache: &Cache,
871        gradient: &mut DVector<Complex<Float>>,
872    ) {
873        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
874        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
875        let internal_gradient = FixedKMatrix::compute_gradient(&ikc_inv_vec, &p_vec_constants);
876        for i in 0..2 {
877            if let ParameterID::Parameter(index) = self.couplings_indices_real[i] {
878                gradient[index] = internal_gradient[i];
879            }
880            if let ParameterID::Parameter(index) = self.couplings_indices_imag[i] {
881                gradient[index] = Complex::<Float>::I * internal_gradient[i];
882            }
883        }
884    }
885}
886
887/// A fixed K-Matrix Amplitude for :math:`a_2` mesons
888///
889/// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data
890/// from that paper, leaving the couplings to the initial state free
891///
892/// Parameters
893/// ----------
894/// name : str
895///     The Amplitude name
896/// couplings : list of list of laddu.ParameterLike
897///     Each initial-state coupling (as a list of pairs of real and imaginary parts)
898/// channel : int
899///     The channel onto which the K-Matrix is projected
900/// mass: laddu.Mass
901///     The total mass of the resonance
902///
903/// Returns
904/// -------
905/// laddu.Amplitude
906///     An Amplitude which can be registered by a laddu.Manager
907///
908/// See Also
909/// --------
910/// laddu.Manager
911///
912/// Notes
913/// -----
914/// +---------------+-------------------+
915/// | Channel index | Channel           |
916/// +===============+===================+
917/// | 0             | :math:`\pi\eta`   |
918/// +---------------+-------------------+
919/// | 1             | :math:`K\bar{K}`  |
920/// +---------------+-------------------+
921/// | 2             | :math:`\pi\eta'`  |
922/// +---------------+-------------------+
923///
924/// +-------------------+
925/// | Pole names        |
926/// +===================+
927/// | :math:`a_2(1320)` |
928/// +-------------------+
929/// | :math:`a_2(1700)` |
930/// +-------------------+
931///
932#[cfg(feature = "python")]
933#[pyfunction(name = "KopfKMatrixA2")]
934pub fn py_kopf_kmatrix_a2(
935    name: &str,
936    couplings: [[PyParameterLike; 2]; 2],
937    channel: usize,
938    mass: PyMass,
939) -> PyAmplitude {
940    PyAmplitude(KopfKMatrixA2::new(
941        name,
942        array::from_fn(|i| array::from_fn(|j| couplings[i][j].clone().0)),
943        channel,
944        &mass.0,
945    ))
946}
947
948/// A K-matrix parameterization for $`\rho`$ particles described by Kopf et al.[^1] with fixed couplings and mass poles
949/// (free production couplings only).
950///
951/// [^1]: Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of $`\bar{p}p`$-, $`\pi^- p`$- and $`\pi \pi`$-Data. The European Physical Journal C, 81(12). [doi:10.1140/epjc/s10052-021-09821-2](https://doi.org/10.1140/epjc/s10052-021-09821-2)
952#[derive(Clone, Serialize, Deserialize)]
953pub struct KopfKMatrixRho {
954    name: String,
955    channel: usize,
956    mass: Mass,
957    constants: FixedKMatrix<3, 2>,
958    couplings_real: [ParameterLike; 2],
959    couplings_imag: [ParameterLike; 2],
960    couplings_indices_real: [ParameterID; 2],
961    couplings_indices_imag: [ParameterID; 2],
962    ikc_cache_index: ComplexVectorID<3>,
963    p_vec_cache_index: MatrixID<3, 2>,
964}
965
966impl KopfKMatrixRho {
967    /// Construct a new [`KopfKMatrixRho`] with the given name, production couplings, channel,
968    /// and input mass.
969    ///
970    /// | Channel index | Channel |
971    /// | ------------- | ------- |
972    /// | 0             | $`\pi\pi`$ |
973    /// | 1             | $`2\pi 2\pi`$ |
974    /// | 2             | $`K\bar{K}`$ |
975    ///
976    /// | Pole names |
977    /// | ---------- |
978    /// | $`\rho(770)`$ |
979    /// | $`\rho(1700)`$ |
980    pub fn new(
981        name: &str,
982        couplings: [[ParameterLike; 2]; 2],
983        channel: usize,
984        mass: &Mass,
985    ) -> Box<Self> {
986        let mut couplings_real: [ParameterLike; 2] = array::from_fn(|_| ParameterLike::default());
987        let mut couplings_imag: [ParameterLike; 2] = array::from_fn(|_| ParameterLike::default());
988        for i in 0..2 {
989            couplings_real[i] = couplings[i][0].clone();
990            couplings_imag[i] = couplings[i][1].clone();
991        }
992        Self {
993            name: name.to_string(),
994            channel,
995            mass: mass.clone(),
996            constants: FixedKMatrix {
997                g: matrix![
998                     0.28023,  0.16318;
999                     0.01806,  0.53879;
1000                     0.06501,  0.00495
1001                ],
1002                c: matrix![
1003                    -0.06948,  0.00000,  0.07958;
1004                     0.00000,  0.00000,  0.00000;
1005                     0.07958,  0.00000, -0.60000
1006                ],
1007                m1s: vector![0.1349768, 2.0 * 0.1349768, 0.493677],
1008                m2s: vector![0.1349768, 2.0 * 0.1349768, 0.497611],
1009                mrs: vector![0.71093, 1.58660],
1010                adler_zero: None,
1011                l: 1,
1012            },
1013            couplings_real,
1014            couplings_imag,
1015            couplings_indices_real: [ParameterID::default(); 2],
1016            couplings_indices_imag: [ParameterID::default(); 2],
1017            ikc_cache_index: ComplexVectorID::default(),
1018            p_vec_cache_index: MatrixID::default(),
1019        }
1020        .into()
1021    }
1022}
1023
1024#[typetag::serde]
1025impl Amplitude for KopfKMatrixRho {
1026    fn register(&mut self, resources: &mut Resources) -> Result<AmplitudeID, LadduError> {
1027        for i in 0..self.couplings_indices_real.len() {
1028            self.couplings_indices_real[i] = resources.register_parameter(&self.couplings_real[i]);
1029            self.couplings_indices_imag[i] = resources.register_parameter(&self.couplings_imag[i]);
1030        }
1031        self.ikc_cache_index = resources
1032            .register_complex_vector(Some(&format!("KopfKMatrixRho<{}> ikc_vec", self.name)));
1033        self.p_vec_cache_index =
1034            resources.register_matrix(Some(&format!("KopfKMatrixRho<{}> p_vec", self.name)));
1035        resources.register_amplitude(&self.name)
1036    }
1037
1038    fn precompute(&self, event: &Event, cache: &mut Cache) {
1039        let s = self.mass.value(event).powi(2);
1040        cache.store_complex_vector(
1041            self.ikc_cache_index,
1042            self.constants.ikc_inv_vec(s, self.channel),
1043        );
1044        cache.store_matrix(self.p_vec_cache_index, self.constants.p_vec_constants(s));
1045    }
1046
1047    fn compute(&self, parameters: &Parameters, _event: &Event, cache: &Cache) -> Complex<Float> {
1048        let betas = SVector::from_fn(|i, _| {
1049            Complex::new(
1050                parameters.get(self.couplings_indices_real[i]),
1051                parameters.get(self.couplings_indices_imag[i]),
1052            )
1053        });
1054        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
1055        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
1056        FixedKMatrix::compute(&betas, &ikc_inv_vec, &p_vec_constants)
1057    }
1058
1059    fn compute_gradient(
1060        &self,
1061        _parameters: &Parameters,
1062        _event: &Event,
1063        cache: &Cache,
1064        gradient: &mut DVector<Complex<Float>>,
1065    ) {
1066        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
1067        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
1068        let internal_gradient = FixedKMatrix::compute_gradient(&ikc_inv_vec, &p_vec_constants);
1069        for i in 0..2 {
1070            if let ParameterID::Parameter(index) = self.couplings_indices_real[i] {
1071                gradient[index] = internal_gradient[i];
1072            }
1073            if let ParameterID::Parameter(index) = self.couplings_indices_imag[i] {
1074                gradient[index] = Complex::<Float>::I * internal_gradient[i];
1075            }
1076        }
1077    }
1078}
1079
1080/// A fixed K-Matrix Amplitude for :math:`\rho` mesons
1081///
1082/// Parameters
1083/// ----------
1084/// name : str
1085///     The Amplitude name
1086/// couplings : list of list of laddu.ParameterLike
1087///     Each initial-state coupling (as a list of pairs of real and imaginary parts)
1088/// channel : int
1089///     The channel onto which the K-Matrix is projected
1090/// mass: laddu.Mass
1091///     The total mass of the resonance
1092///
1093/// Returns
1094/// -------
1095/// laddu.Amplitude
1096///     An Amplitude which can be registered by a laddu.Manager
1097///
1098/// See Also
1099/// --------
1100/// laddu.Manager
1101///
1102/// Notes
1103/// -----
1104/// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data
1105/// from that paper, leaving the couplings to the initial state free
1106///
1107/// +---------------+-------------------+
1108/// | Channel index | Channel           |
1109/// +===============+===================+
1110/// | 0             | :math:`\pi\pi`    |
1111/// +---------------+-------------------+
1112/// | 1             | :math:`2\pi 2\pi` |
1113/// +---------------+-------------------+
1114/// | 2             | :math:`K\bar{K}`  |
1115/// +---------------+-------------------+
1116///
1117/// +--------------------+
1118/// | Pole names         |
1119/// +====================+
1120/// | :math:`\rho(770)`  |
1121/// +--------------------+
1122/// | :math:`\rho(1700)` |
1123/// +--------------------+
1124///
1125#[cfg(feature = "python")]
1126#[pyfunction(name = "KopfKMatrixRho")]
1127pub fn py_kopf_kmatrix_rho(
1128    name: &str,
1129    couplings: [[PyParameterLike; 2]; 2],
1130    channel: usize,
1131    mass: PyMass,
1132) -> PyAmplitude {
1133    PyAmplitude(KopfKMatrixRho::new(
1134        name,
1135        array::from_fn(|i| array::from_fn(|j| couplings[i][j].clone().0)),
1136        channel,
1137        &mass.0,
1138    ))
1139}
1140
1141/// A K-matrix parameterization for the $`\pi_1`$ hybrid candidate described by Kopf et al.[^1] with fixed couplings and mass poles
1142/// (free production couplings only).
1143///
1144/// [^1]: Kopf, B., Albrecht, M., Koch, H., Küßner, M., Pychy, J., Qin, X., & Wiedner, U. (2021). Investigation of the lightest hybrid meson candidate with a coupled-channel analysis of $`\bar{p}p`$-, $`\pi^- p`$- and $`\pi \pi`$-Data. The European Physical Journal C, 81(12). [doi:10.1140/epjc/s10052-021-09821-2](https://doi.org/10.1140/epjc/s10052-021-09821-2)
1145#[derive(Clone, Serialize, Deserialize)]
1146pub struct KopfKMatrixPi1 {
1147    name: String,
1148    channel: usize,
1149    mass: Mass,
1150    constants: FixedKMatrix<2, 1>,
1151    couplings_real: [ParameterLike; 1],
1152    couplings_imag: [ParameterLike; 1],
1153    couplings_indices_real: [ParameterID; 1],
1154    couplings_indices_imag: [ParameterID; 1],
1155    ikc_cache_index: ComplexVectorID<2>,
1156    p_vec_cache_index: MatrixID<2, 1>,
1157}
1158
1159impl KopfKMatrixPi1 {
1160    /// Construct a new [`KopfKMatrixPi1`] with the given name, production couplings, channel,
1161    /// and input mass.
1162    ///
1163    /// | Channel index | Channel |
1164    /// | ------------- | ------- |
1165    /// | 0             | $`\pi\eta`$ |
1166    /// | 1             | $`\pi\eta'`$ |
1167    ///
1168    /// | Pole names |
1169    /// | ---------- |
1170    /// | $`\pi_1(1600)`$ |
1171    pub fn new(
1172        name: &str,
1173        couplings: [[ParameterLike; 2]; 1],
1174        channel: usize,
1175        mass: &Mass,
1176    ) -> Box<Self> {
1177        let mut couplings_real: [ParameterLike; 1] = array::from_fn(|_| ParameterLike::default());
1178        let mut couplings_imag: [ParameterLike; 1] = array::from_fn(|_| ParameterLike::default());
1179        for i in 0..1 {
1180            couplings_real[i] = couplings[i][0].clone();
1181            couplings_imag[i] = couplings[i][1].clone();
1182        }
1183        Self {
1184            name: name.to_string(),
1185            channel,
1186            mass: mass.clone(),
1187            constants: FixedKMatrix {
1188                g: matrix![
1189                     0.80564;
1190                     1.04595
1191                ],
1192                c: matrix![
1193                    1.05000,  0.15163;
1194                    0.15163, -0.24611
1195                ],
1196                m1s: vector![0.1349768, 0.1349768],
1197                m2s: vector![0.547862, 0.95778],
1198                mrs: vector![1.38552],
1199                adler_zero: None,
1200                l: 1,
1201            },
1202            couplings_real,
1203            couplings_imag,
1204            couplings_indices_real: [ParameterID::default(); 1],
1205            couplings_indices_imag: [ParameterID::default(); 1],
1206            ikc_cache_index: ComplexVectorID::default(),
1207            p_vec_cache_index: MatrixID::default(),
1208        }
1209        .into()
1210    }
1211}
1212
1213#[typetag::serde]
1214impl Amplitude for KopfKMatrixPi1 {
1215    fn register(&mut self, resources: &mut Resources) -> Result<AmplitudeID, LadduError> {
1216        for i in 0..self.couplings_indices_real.len() {
1217            self.couplings_indices_real[i] = resources.register_parameter(&self.couplings_real[i]);
1218            self.couplings_indices_imag[i] = resources.register_parameter(&self.couplings_imag[i]);
1219        }
1220        self.ikc_cache_index = resources
1221            .register_complex_vector(Some(&format!("KopfKMatrixPi1<{}> ikc_vec", self.name)));
1222        self.p_vec_cache_index =
1223            resources.register_matrix(Some(&format!("KopfKMatrixPi1<{}> p_vec", self.name)));
1224        resources.register_amplitude(&self.name)
1225    }
1226
1227    fn precompute(&self, event: &Event, cache: &mut Cache) {
1228        let s = self.mass.value(event).powi(2);
1229        cache.store_complex_vector(
1230            self.ikc_cache_index,
1231            self.constants.ikc_inv_vec(s, self.channel),
1232        );
1233        cache.store_matrix(self.p_vec_cache_index, self.constants.p_vec_constants(s));
1234    }
1235
1236    fn compute(&self, parameters: &Parameters, _event: &Event, cache: &Cache) -> Complex<Float> {
1237        let betas = SVector::from_fn(|i, _| {
1238            Complex::new(
1239                parameters.get(self.couplings_indices_real[i]),
1240                parameters.get(self.couplings_indices_imag[i]),
1241            )
1242        });
1243        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
1244        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
1245        FixedKMatrix::compute(&betas, &ikc_inv_vec, &p_vec_constants)
1246    }
1247
1248    fn compute_gradient(
1249        &self,
1250        _parameters: &Parameters,
1251        _event: &Event,
1252        cache: &Cache,
1253        gradient: &mut DVector<Complex<Float>>,
1254    ) {
1255        let ikc_inv_vec = cache.get_complex_vector(self.ikc_cache_index);
1256        let p_vec_constants = cache.get_matrix(self.p_vec_cache_index);
1257        let internal_gradient = FixedKMatrix::compute_gradient(&ikc_inv_vec, &p_vec_constants);
1258        if let ParameterID::Parameter(index) = self.couplings_indices_real[0] {
1259            gradient[index] = internal_gradient[0];
1260        }
1261        if let ParameterID::Parameter(index) = self.couplings_indices_imag[0] {
1262            gradient[index] = Complex::<Float>::I * internal_gradient[0];
1263        }
1264    }
1265}
1266
1267/// A fixed K-Matrix Amplitude for the :math:`\pi_1(1600)` hybrid meson
1268///
1269/// Parameters
1270/// ----------
1271/// name : str
1272///     The Amplitude name
1273/// couplings : list of list of laddu.ParameterLike
1274///     Each initial-state coupling (as a list of pairs of real and imaginary parts)
1275/// channel : int
1276///     The channel onto which the K-Matrix is projected
1277/// mass: laddu.Mass
1278///     The total mass of the resonance
1279///
1280/// Returns
1281/// -------
1282/// laddu.Amplitude
1283///     An Amplitude which can be registered by a laddu.Manager
1284///
1285/// See Also
1286/// --------
1287/// laddu.Manager
1288///
1289/// Notes
1290/// -----
1291/// This Amplitude follows the prescription of [Kopf]_ and fixes the K-Matrix to data
1292/// from that paper, leaving the couplings to the initial state free
1293///
1294/// +---------------+-------------------+
1295/// | Channel index | Channel           |
1296/// +===============+===================+
1297/// | 0             | :math:`\pi\eta`   |
1298/// +---------------+-------------------+
1299/// | 1             | :math:`\pi\eta'`  |
1300/// +---------------+-------------------+
1301///
1302/// +---------------------+
1303/// | Pole names          |
1304/// +=====================+
1305/// | :math:`\pi_1(1600)` |
1306/// +---------------------+
1307///
1308#[cfg(feature = "python")]
1309#[pyfunction(name = "KopfKMatrixPi1")]
1310pub fn py_kopf_kmatrix_pi1(
1311    name: &str,
1312    couplings: [[PyParameterLike; 2]; 1],
1313    channel: usize,
1314    mass: PyMass,
1315) -> PyAmplitude {
1316    PyAmplitude(KopfKMatrixPi1::new(
1317        name,
1318        array::from_fn(|i| array::from_fn(|j| couplings[i][j].clone().0)),
1319        channel,
1320        &mass.0,
1321    ))
1322}
1323
1324#[cfg(test)]
1325mod tests {
1326    // Note: These tests are not exhaustive, they only check one channel
1327    use std::sync::Arc;
1328
1329    use super::*;
1330    use approx::assert_relative_eq;
1331    use laddu_core::{data::test_dataset, parameter, Manager, Mass};
1332
1333    #[test]
1334    fn test_f0_evaluation() {
1335        let mut manager = Manager::default();
1336        let res_mass = Mass::new([2, 3]);
1337        let amp = KopfKMatrixF0::new(
1338            "f0",
1339            [
1340                [parameter("p0"), parameter("p1")],
1341                [parameter("p2"), parameter("p3")],
1342                [parameter("p4"), parameter("p5")],
1343                [parameter("p6"), parameter("p7")],
1344                [parameter("p8"), parameter("p9")],
1345            ],
1346            1,
1347            &res_mass,
1348        );
1349        let aid = manager.register(amp).unwrap();
1350
1351        let dataset = Arc::new(test_dataset());
1352        let expr = aid.into();
1353        let model = manager.model(&expr);
1354        let evaluator = model.load(&dataset);
1355
1356        let result = evaluator.evaluate(&[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]);
1357
1358        assert_relative_eq!(result[0].re, 0.26749455, epsilon = Float::EPSILON.sqrt());
1359        assert_relative_eq!(result[0].im, 0.72894511, epsilon = Float::EPSILON.sqrt());
1360    }
1361
1362    #[test]
1363    fn test_f0_gradient() {
1364        let mut manager = Manager::default();
1365        let res_mass = Mass::new([2, 3]);
1366        let amp = KopfKMatrixF0::new(
1367            "f0",
1368            [
1369                [parameter("p0"), parameter("p1")],
1370                [parameter("p2"), parameter("p3")],
1371                [parameter("p4"), parameter("p5")],
1372                [parameter("p6"), parameter("p7")],
1373                [parameter("p8"), parameter("p9")],
1374            ],
1375            1,
1376            &res_mass,
1377        );
1378        let aid = manager.register(amp).unwrap();
1379
1380        let dataset = Arc::new(test_dataset());
1381        let expr = aid.into();
1382        let model = manager.model(&expr);
1383        let evaluator = model.load(&dataset);
1384
1385        let result =
1386            evaluator.evaluate_gradient(&[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0]);
1387
1388        assert_relative_eq!(result[0][0].re, -0.0324912, epsilon = Float::EPSILON.cbrt());
1389        assert_relative_eq!(result[0][0].im, -0.0110734, epsilon = Float::EPSILON.cbrt());
1390        assert_relative_eq!(result[0][1].re, -result[0][0].im);
1391        assert_relative_eq!(result[0][1].im, result[0][0].re);
1392        assert_relative_eq!(result[0][2].re, 0.0241053, epsilon = Float::EPSILON.cbrt());
1393        assert_relative_eq!(result[0][2].im, 0.0079184, epsilon = Float::EPSILON.cbrt());
1394        assert_relative_eq!(result[0][3].re, -result[0][2].im);
1395        assert_relative_eq!(result[0][3].im, result[0][2].re);
1396        assert_relative_eq!(result[0][4].re, -0.0316345, epsilon = Float::EPSILON.cbrt());
1397        assert_relative_eq!(result[0][4].im, 0.0149155, epsilon = Float::EPSILON.cbrt());
1398        assert_relative_eq!(result[0][5].re, -result[0][4].im);
1399        assert_relative_eq!(result[0][5].im, result[0][4].re);
1400        assert_relative_eq!(result[0][6].re, 0.5838982, epsilon = Float::EPSILON.cbrt());
1401        assert_relative_eq!(result[0][6].im, 0.2071617, epsilon = Float::EPSILON.cbrt());
1402        assert_relative_eq!(result[0][7].re, -result[0][6].im);
1403        assert_relative_eq!(result[0][7].im, result[0][6].re);
1404        assert_relative_eq!(result[0][8].re, 0.0914546, epsilon = Float::EPSILON.cbrt());
1405        assert_relative_eq!(result[0][8].im, 0.0360771, epsilon = Float::EPSILON.cbrt());
1406        assert_relative_eq!(result[0][9].re, -result[0][8].im);
1407        assert_relative_eq!(result[0][9].im, result[0][8].re);
1408    }
1409
1410    #[test]
1411    fn test_f2_evaluation() {
1412        let mut manager = Manager::default();
1413        let res_mass = Mass::new([2, 3]);
1414        let amp = KopfKMatrixF2::new(
1415            "f2",
1416            [
1417                [parameter("p0"), parameter("p1")],
1418                [parameter("p2"), parameter("p3")],
1419                [parameter("p4"), parameter("p5")],
1420                [parameter("p6"), parameter("p7")],
1421            ],
1422            1,
1423            &res_mass,
1424        );
1425        let aid = manager.register(amp).unwrap();
1426
1427        let dataset = Arc::new(test_dataset());
1428        let expr = aid.into();
1429        let model = manager.model(&expr);
1430        let evaluator = model.load(&dataset);
1431
1432        let result = evaluator.evaluate(&[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]);
1433
1434        assert_relative_eq!(result[0].re, 0.02523304, epsilon = Float::EPSILON.sqrt());
1435        assert_relative_eq!(result[0].im, 0.39712393, epsilon = Float::EPSILON.sqrt());
1436    }
1437
1438    #[test]
1439    fn test_f2_gradient() {
1440        let mut manager = Manager::default();
1441        let res_mass = Mass::new([2, 3]);
1442        let amp = KopfKMatrixF2::new(
1443            "f2",
1444            [
1445                [parameter("p0"), parameter("p1")],
1446                [parameter("p2"), parameter("p3")],
1447                [parameter("p4"), parameter("p5")],
1448                [parameter("p6"), parameter("p7")],
1449            ],
1450            1,
1451            &res_mass,
1452        );
1453        let aid = manager.register(amp).unwrap();
1454
1455        let dataset = Arc::new(test_dataset());
1456        let expr = aid.into();
1457        let model = manager.model(&expr);
1458        let evaluator = model.load(&dataset);
1459
1460        let result = evaluator.evaluate_gradient(&[0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]);
1461
1462        assert_relative_eq!(result[0][0].re, -0.3078948, epsilon = Float::EPSILON.cbrt());
1463        assert_relative_eq!(result[0][0].im, 0.3808689, epsilon = Float::EPSILON.cbrt());
1464        assert_relative_eq!(result[0][1].re, -result[0][0].im);
1465        assert_relative_eq!(result[0][1].im, result[0][0].re);
1466        assert_relative_eq!(result[0][2].re, 0.4290085, epsilon = Float::EPSILON.cbrt());
1467        assert_relative_eq!(result[0][2].im, 0.0799660, epsilon = Float::EPSILON.cbrt());
1468        assert_relative_eq!(result[0][3].re, -result[0][2].im);
1469        assert_relative_eq!(result[0][3].im, result[0][2].re);
1470        assert_relative_eq!(result[0][4].re, 0.1657487, epsilon = Float::EPSILON.cbrt());
1471        assert_relative_eq!(result[0][4].im, -0.0041382, epsilon = Float::EPSILON.cbrt());
1472        assert_relative_eq!(result[0][5].re, -result[0][4].im);
1473        assert_relative_eq!(result[0][5].im, result[0][4].re);
1474        assert_relative_eq!(result[0][6].re, 0.0594691, epsilon = Float::EPSILON.cbrt());
1475        assert_relative_eq!(result[0][6].im, 0.1143819, epsilon = Float::EPSILON.cbrt());
1476        assert_relative_eq!(result[0][7].re, -result[0][6].im);
1477        assert_relative_eq!(result[0][7].im, result[0][6].re);
1478    }
1479
1480    #[test]
1481    fn test_a0_evaluation() {
1482        let mut manager = Manager::default();
1483        let res_mass = Mass::new([2, 3]);
1484        let amp = KopfKMatrixA0::new(
1485            "a0",
1486            [
1487                [parameter("p0"), parameter("p1")],
1488                [parameter("p2"), parameter("p3")],
1489            ],
1490            1,
1491            &res_mass,
1492        );
1493        let aid = manager.register(amp).unwrap();
1494
1495        let dataset = Arc::new(test_dataset());
1496        let expr = aid.into();
1497        let model = manager.model(&expr);
1498        let evaluator = model.load(&dataset);
1499
1500        let result = evaluator.evaluate(&[0.1, 0.2, 0.3, 0.4]);
1501
1502        assert_relative_eq!(result[0].re, -0.80027591, epsilon = Float::EPSILON.sqrt());
1503        assert_relative_eq!(result[0].im, -0.13593066, epsilon = Float::EPSILON.sqrt());
1504    }
1505
1506    #[test]
1507    fn test_a0_gradient() {
1508        let mut manager = Manager::default();
1509        let res_mass = Mass::new([2, 3]);
1510        let amp = KopfKMatrixA0::new(
1511            "a0",
1512            [
1513                [parameter("p0"), parameter("p1")],
1514                [parameter("p2"), parameter("p3")],
1515            ],
1516            1,
1517            &res_mass,
1518        );
1519        let aid = manager.register(amp).unwrap();
1520
1521        let dataset = Arc::new(test_dataset());
1522        let expr = aid.into();
1523        let model = manager.model(&expr);
1524        let evaluator = model.load(&dataset);
1525
1526        let result = evaluator.evaluate_gradient(&[0.1, 0.2, 0.3, 0.4]);
1527
1528        assert_relative_eq!(result[0][0].re, 0.2906192, epsilon = Float::EPSILON.cbrt());
1529        assert_relative_eq!(result[0][0].im, -0.0998906, epsilon = Float::EPSILON.cbrt());
1530        assert_relative_eq!(result[0][1].re, -result[0][0].im);
1531        assert_relative_eq!(result[0][1].im, result[0][0].re);
1532        assert_relative_eq!(result[0][2].re, -1.3136838, epsilon = Float::EPSILON.cbrt());
1533        assert_relative_eq!(result[0][2].im, 1.1380269, epsilon = Float::EPSILON.cbrt());
1534        assert_relative_eq!(result[0][3].re, -result[0][2].im);
1535        assert_relative_eq!(result[0][3].im, result[0][2].re);
1536    }
1537
1538    #[test]
1539    fn test_a2_evaluation() {
1540        let mut manager = Manager::default();
1541        let res_mass = Mass::new([2, 3]);
1542        let amp = KopfKMatrixA2::new(
1543            "a2",
1544            [
1545                [parameter("p0"), parameter("p1")],
1546                [parameter("p2"), parameter("p3")],
1547            ],
1548            1,
1549            &res_mass,
1550        );
1551        let aid = manager.register(amp).unwrap();
1552
1553        let dataset = Arc::new(test_dataset());
1554        let expr = aid.into();
1555        let model = manager.model(&expr);
1556        let evaluator = model.load(&dataset);
1557
1558        let result = evaluator.evaluate(&[0.1, 0.2, 0.3, 0.4]);
1559
1560        assert_relative_eq!(result[0].re, -0.20926617, epsilon = Float::EPSILON.sqrt());
1561        assert_relative_eq!(result[0].im, -0.0985062, epsilon = Float::EPSILON.sqrt());
1562    }
1563
1564    #[test]
1565    fn test_a2_gradient() {
1566        let mut manager = Manager::default();
1567        let res_mass = Mass::new([2, 3]);
1568        let amp = KopfKMatrixA2::new(
1569            "a2",
1570            [
1571                [parameter("p0"), parameter("p1")],
1572                [parameter("p2"), parameter("p3")],
1573            ],
1574            1,
1575            &res_mass,
1576        );
1577        let aid = manager.register(amp).unwrap();
1578
1579        let dataset = Arc::new(test_dataset());
1580        let expr = aid.into();
1581        let model = manager.model(&expr);
1582        let evaluator = model.load(&dataset);
1583
1584        let result = evaluator.evaluate_gradient(&[0.1, 0.2, 0.3, 0.4]);
1585
1586        assert_relative_eq!(result[0][0].re, -0.5756896, epsilon = Float::EPSILON.cbrt());
1587        assert_relative_eq!(result[0][0].im, 0.9398863, epsilon = Float::EPSILON.cbrt());
1588        assert_relative_eq!(result[0][1].re, -result[0][0].im);
1589        assert_relative_eq!(result[0][1].im, result[0][0].re);
1590        assert_relative_eq!(result[0][2].re, -0.0811143, epsilon = Float::EPSILON.cbrt());
1591        assert_relative_eq!(result[0][2].im, -0.1522787, epsilon = Float::EPSILON.cbrt());
1592        assert_relative_eq!(result[0][3].re, -result[0][2].im);
1593        assert_relative_eq!(result[0][3].im, result[0][2].re);
1594    }
1595
1596    #[test]
1597    fn test_rho_evaluation() {
1598        let mut manager = Manager::default();
1599        let res_mass = Mass::new([2, 3]);
1600        let amp = KopfKMatrixRho::new(
1601            "rho",
1602            [
1603                [parameter("p0"), parameter("p1")],
1604                [parameter("p2"), parameter("p3")],
1605            ],
1606            1,
1607            &res_mass,
1608        );
1609        let aid = manager.register(amp).unwrap();
1610
1611        let dataset = Arc::new(test_dataset());
1612        let expr = aid.into();
1613        let model = manager.model(&expr);
1614        let evaluator = model.load(&dataset);
1615
1616        let result = evaluator.evaluate(&[0.1, 0.2, 0.3, 0.4]);
1617
1618        assert_relative_eq!(result[0].re, 0.09483558, epsilon = Float::EPSILON.sqrt());
1619        assert_relative_eq!(result[0].im, 0.26091837, epsilon = Float::EPSILON.sqrt());
1620    }
1621
1622    #[test]
1623    fn test_rho_gradient() {
1624        let mut manager = Manager::default();
1625        let res_mass = Mass::new([2, 3]);
1626        let amp = KopfKMatrixRho::new(
1627            "rho",
1628            [
1629                [parameter("p0"), parameter("p1")],
1630                [parameter("p2"), parameter("p3")],
1631            ],
1632            1,
1633            &res_mass,
1634        );
1635        let aid = manager.register(amp).unwrap();
1636
1637        let dataset = Arc::new(test_dataset());
1638        let expr = aid.into();
1639        let model = manager.model(&expr);
1640        let evaluator = model.load(&dataset);
1641
1642        let result = evaluator.evaluate_gradient(&[0.1, 0.2, 0.3, 0.4]);
1643
1644        assert_relative_eq!(result[0][0].re, 0.0265203, epsilon = Float::EPSILON.cbrt());
1645        assert_relative_eq!(result[0][0].im, -0.0266026, epsilon = Float::EPSILON.cbrt());
1646        assert_relative_eq!(result[0][1].re, -result[0][0].im);
1647        assert_relative_eq!(result[0][1].im, result[0][0].re);
1648        assert_relative_eq!(result[0][2].re, 0.5172379, epsilon = Float::EPSILON.cbrt());
1649        assert_relative_eq!(result[0][2].im, 0.1707373, epsilon = Float::EPSILON.cbrt());
1650        assert_relative_eq!(result[0][3].re, -result[0][2].im);
1651        assert_relative_eq!(result[0][3].im, result[0][2].re);
1652    }
1653
1654    #[test]
1655    fn test_pi1_evaluation() {
1656        let mut manager = Manager::default();
1657        let res_mass = Mass::new([2, 3]);
1658        let amp = KopfKMatrixPi1::new("pi1", [[parameter("p0"), parameter("p1")]], 1, &res_mass);
1659        let aid = manager.register(amp).unwrap();
1660
1661        let dataset = Arc::new(test_dataset());
1662        let expr = aid.into();
1663        let model = manager.model(&expr);
1664        let evaluator = model.load(&dataset);
1665
1666        let result = evaluator.evaluate(&[0.1, 0.2]);
1667
1668        assert_relative_eq!(result[0].re, -0.11017586, epsilon = Float::EPSILON.sqrt());
1669        assert_relative_eq!(result[0].im, 0.26387172, epsilon = Float::EPSILON.sqrt());
1670    }
1671
1672    #[test]
1673    fn test_pi1_gradient() {
1674        let mut manager = Manager::default();
1675        let res_mass = Mass::new([2, 3]);
1676        let amp = KopfKMatrixPi1::new("pi1", [[parameter("p0"), parameter("p1")]], 1, &res_mass);
1677        let aid = manager.register(amp).unwrap();
1678
1679        let dataset = Arc::new(test_dataset());
1680        let expr = aid.into();
1681        let model = manager.model(&expr);
1682        let evaluator = model.load(&dataset);
1683
1684        let result = evaluator.evaluate_gradient(&[0.1, 0.2]);
1685
1686        assert_relative_eq!(
1687            result[0][0].re,
1688            -14.7987174,
1689            epsilon = Float::EPSILON.cbrt()
1690        );
1691        assert_relative_eq!(result[0][0].im, -5.8430094, epsilon = Float::EPSILON.cbrt());
1692        assert_relative_eq!(result[0][1].re, -result[0][0].im);
1693        assert_relative_eq!(result[0][1].im, result[0][0].re);
1694    }
1695}