laddu_python/utils/
variables.rs

1use crate::data::{PyDataset, PyEvent};
2use laddu_core::{
3    data::{Dataset, Event},
4    traits::Variable,
5    utils::variables::{
6        Angles, CosTheta, Mandelstam, Mass, Phi, PolAngle, PolMagnitude, Polarization,
7        VariableExpression,
8    },
9    Float,
10};
11use numpy::PyArray1;
12use pyo3::prelude::*;
13use serde::{Deserialize, Serialize};
14use std::fmt::{Debug, Display};
15
16#[derive(FromPyObject, Clone, Serialize, Deserialize)]
17pub enum PyVariable {
18    #[pyo3(transparent)]
19    Mass(PyMass),
20    #[pyo3(transparent)]
21    CosTheta(PyCosTheta),
22    #[pyo3(transparent)]
23    Phi(PyPhi),
24    #[pyo3(transparent)]
25    PolAngle(PyPolAngle),
26    #[pyo3(transparent)]
27    PolMagnitude(PyPolMagnitude),
28    #[pyo3(transparent)]
29    Mandelstam(PyMandelstam),
30}
31
32impl Debug for PyVariable {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        match self {
35            Self::Mass(v) => write!(f, "{:?}", v.0),
36            Self::CosTheta(v) => write!(f, "{:?}", v.0),
37            Self::Phi(v) => write!(f, "{:?}", v.0),
38            Self::PolAngle(v) => write!(f, "{:?}", v.0),
39            Self::PolMagnitude(v) => write!(f, "{:?}", v.0),
40            Self::Mandelstam(v) => write!(f, "{:?}", v.0),
41        }
42    }
43}
44impl Display for PyVariable {
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            Self::Mass(v) => write!(f, "{}", v.0),
48            Self::CosTheta(v) => write!(f, "{}", v.0),
49            Self::Phi(v) => write!(f, "{}", v.0),
50            Self::PolAngle(v) => write!(f, "{}", v.0),
51            Self::PolMagnitude(v) => write!(f, "{}", v.0),
52            Self::Mandelstam(v) => write!(f, "{}", v.0),
53        }
54    }
55}
56
57#[pyclass(name = "VariableExpression", module = "laddu")]
58pub struct PyVariableExpression(pub VariableExpression);
59
60#[pymethods]
61impl PyVariableExpression {
62    fn __and__(&self, rhs: &PyVariableExpression) -> PyVariableExpression {
63        PyVariableExpression(self.0.clone() & rhs.0.clone())
64    }
65    fn __or__(&self, rhs: &PyVariableExpression) -> PyVariableExpression {
66        PyVariableExpression(self.0.clone() | rhs.0.clone())
67    }
68    fn __invert__(&self) -> PyVariableExpression {
69        PyVariableExpression(!self.0.clone())
70    }
71    fn __str__(&self) -> String {
72        format!("{}", self.0)
73    }
74}
75
76/// The invariant mass of an arbitrary combination of constituent particles in an Event
77///
78/// This variable is calculated by summing up the 4-momenta of each particle listed by index in
79/// `constituents` and taking the invariant magnitude of the resulting 4-vector.
80///
81/// Parameters
82/// ----------
83/// constituents : list of int
84///     The indices of particles to combine to create the final 4-momentum
85///
86/// See Also
87/// --------
88/// laddu.utils.vectors.Vec4.m
89///
90#[pyclass(name = "Mass", module = "laddu")]
91#[derive(Clone, Serialize, Deserialize)]
92pub struct PyMass(pub Mass);
93
94#[pymethods]
95impl PyMass {
96    #[new]
97    fn new(constituents: Vec<usize>) -> Self {
98        Self(Mass::new(&constituents))
99    }
100    /// The value of this Variable for the given Event
101    ///
102    /// Parameters
103    /// ----------
104    /// event : Event
105    ///     The Event upon which the Variable is calculated
106    ///
107    /// Returns
108    /// -------
109    /// value : float
110    ///     The value of the Variable for the given `event`
111    ///
112    fn value(&self, event: &PyEvent) -> Float {
113        self.0.value(&event.0)
114    }
115    /// All values of this Variable on the given Dataset
116    ///
117    /// Parameters
118    /// ----------
119    /// dataset : Dataset
120    ///     The Dataset upon which the Variable is calculated
121    ///
122    /// Returns
123    /// -------
124    /// values : array_like
125    ///     The values of the Variable for each Event in the given `dataset`
126    ///
127    fn value_on<'py>(&self, py: Python<'py>, dataset: &PyDataset) -> Bound<'py, PyArray1<Float>> {
128        PyArray1::from_slice(py, &self.0.value_on(&dataset.0))
129    }
130    fn __eq__(&self, value: Float) -> PyVariableExpression {
131        PyVariableExpression(self.0.eq(value))
132    }
133    fn __lt__(&self, value: Float) -> PyVariableExpression {
134        PyVariableExpression(self.0.lt(value))
135    }
136    fn __gt__(&self, value: Float) -> PyVariableExpression {
137        PyVariableExpression(self.0.gt(value))
138    }
139    fn __le__(&self, value: Float) -> PyVariableExpression {
140        PyVariableExpression(self.0.le(value))
141    }
142    fn __ge__(&self, value: Float) -> PyVariableExpression {
143        PyVariableExpression(self.0.ge(value))
144    }
145    fn __repr__(&self) -> String {
146        format!("{:?}", self.0)
147    }
148    fn __str__(&self) -> String {
149        format!("{}", self.0)
150    }
151}
152
153/// The cosine of the polar decay angle in the rest frame of the given `resonance`
154///
155/// This Variable is calculated by forming the given frame (helicity or Gottfried-Jackson) and
156/// calculating the spherical angles according to one of the decaying `daughter` particles.
157///
158/// The helicity frame is defined in terms of the following Cartesian axes in the rest frame of
159/// the `resonance`:
160///
161/// .. math:: \hat{z} \propto -\vec{p}'_{\text{recoil}}
162/// .. math:: \hat{y} \propto \vec{p}_{\text{beam}} \times (-\vec{p}_{\text{recoil}})
163/// .. math:: \hat{x} = \hat{y} \times \hat{z}
164///
165/// where primed vectors are in the rest frame of the `resonance` and unprimed vectors are in
166/// the center-of-momentum frame.
167///
168/// The Gottfried-Jackson frame differs only in the definition of :math:`\hat{z}`:
169///
170/// .. math:: \hat{z} \propto \vec{p}'_{\text{beam}}
171///
172/// Parameters
173/// ----------
174/// beam : int
175///     The index of the `beam` particle
176/// recoil : list of int
177///     Indices of particles which are combined to form the recoiling particle (particles which
178///     are not `beam` or part of the `resonance`)
179/// daughter : list of int
180///     Indices of particles which are combined to form one of the decay products of the
181///     `resonance`
182/// resonance : list of int
183///     Indices of particles which are combined to form the `resonance`
184/// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'}
185///     The frame to use in the  calculation
186///
187/// Raises
188/// ------
189/// ValueError
190///     If `frame` is not one of the valid options
191///
192/// See Also
193/// --------
194/// laddu.utils.vectors.Vec3.costheta
195///
196#[pyclass(name = "CosTheta", module = "laddu")]
197#[derive(Clone, Serialize, Deserialize)]
198pub struct PyCosTheta(pub CosTheta);
199
200#[pymethods]
201impl PyCosTheta {
202    #[new]
203    #[pyo3(signature=(beam, recoil, daughter, resonance, frame="Helicity"))]
204    fn new(
205        beam: usize,
206        recoil: Vec<usize>,
207        daughter: Vec<usize>,
208        resonance: Vec<usize>,
209        frame: &str,
210    ) -> PyResult<Self> {
211        Ok(Self(CosTheta::new(
212            beam,
213            &recoil,
214            &daughter,
215            &resonance,
216            frame.parse()?,
217        )))
218    }
219    /// The value of this Variable for the given Event
220    ///
221    /// Parameters
222    /// ----------
223    /// event : Event
224    ///     The Event upon which the Variable is calculated
225    ///
226    /// Returns
227    /// -------
228    /// value : float
229    ///     The value of the Variable for the given `event`
230    ///
231    fn value(&self, event: &PyEvent) -> Float {
232        self.0.value(&event.0)
233    }
234    /// All values of this Variable on the given Dataset
235    ///
236    /// Parameters
237    /// ----------
238    /// dataset : Dataset
239    ///     The Dataset upon which the Variable is calculated
240    ///
241    /// Returns
242    /// -------
243    /// values : array_like
244    ///     The values of the Variable for each Event in the given `dataset`
245    ///
246    fn value_on<'py>(&self, py: Python<'py>, dataset: &PyDataset) -> Bound<'py, PyArray1<Float>> {
247        PyArray1::from_slice(py, &self.0.value_on(&dataset.0))
248    }
249    fn __eq__(&self, value: Float) -> PyVariableExpression {
250        PyVariableExpression(self.0.eq(value))
251    }
252    fn __lt__(&self, value: Float) -> PyVariableExpression {
253        PyVariableExpression(self.0.lt(value))
254    }
255    fn __gt__(&self, value: Float) -> PyVariableExpression {
256        PyVariableExpression(self.0.gt(value))
257    }
258    fn __le__(&self, value: Float) -> PyVariableExpression {
259        PyVariableExpression(self.0.le(value))
260    }
261    fn __ge__(&self, value: Float) -> PyVariableExpression {
262        PyVariableExpression(self.0.ge(value))
263    }
264    fn __repr__(&self) -> String {
265        format!("{:?}", self.0)
266    }
267    fn __str__(&self) -> String {
268        format!("{}", self.0)
269    }
270}
271
272/// The aziumuthal decay angle in the rest frame of the given `resonance`
273///
274/// This Variable is calculated by forming the given frame (helicity or Gottfried-Jackson) and
275/// calculating the spherical angles according to one of the decaying `daughter` particles.
276///
277/// The helicity frame is defined in terms of the following Cartesian axes in the rest frame of
278/// the `resonance`:
279///
280/// .. math:: \hat{z} \propto -\vec{p}'_{\text{recoil}}
281/// .. math:: \hat{y} \propto \vec{p}_{\text{beam}} \times (-\vec{p}_{\text{recoil}})
282/// .. math:: \hat{x} = \hat{y} \times \hat{z}
283///
284/// where primed vectors are in the rest frame of the `resonance` and unprimed vectors are in
285/// the center-of-momentum frame.
286///
287/// The Gottfried-Jackson frame differs only in the definition of :math:`\hat{z}`:
288///
289/// .. math:: \hat{z} \propto \vec{p}'_{\text{beam}}
290///
291/// Parameters
292/// ----------
293/// beam : int
294///     The index of the `beam` particle
295/// recoil : list of int
296///     Indices of particles which are combined to form the recoiling particle (particles which
297///     are not `beam` or part of the `resonance`)
298/// daughter : list of int
299///     Indices of particles which are combined to form one of the decay products of the
300///     `resonance`
301/// resonance : list of int
302///     Indices of particles which are combined to form the `resonance`
303/// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'}
304///     The frame to use in the  calculation
305///
306/// Raises
307/// ------
308/// ValueError
309///     If `frame` is not one of the valid options
310///
311///
312/// See Also
313/// --------
314/// laddu.utils.vectors.Vec3.phi
315///
316#[pyclass(name = "Phi", module = "laddu")]
317#[derive(Clone, Serialize, Deserialize)]
318pub struct PyPhi(pub Phi);
319
320#[pymethods]
321impl PyPhi {
322    #[new]
323    #[pyo3(signature=(beam, recoil, daughter, resonance, frame="Helicity"))]
324    fn new(
325        beam: usize,
326        recoil: Vec<usize>,
327        daughter: Vec<usize>,
328        resonance: Vec<usize>,
329        frame: &str,
330    ) -> PyResult<Self> {
331        Ok(Self(Phi::new(
332            beam,
333            &recoil,
334            &daughter,
335            &resonance,
336            frame.parse()?,
337        )))
338    }
339    /// The value of this Variable for the given Event
340    ///
341    /// Parameters
342    /// ----------
343    /// event : Event
344    ///     The Event upon which the Variable is calculated
345    ///
346    /// Returns
347    /// -------
348    /// value : float
349    ///     The value of the Variable for the given `event`
350    ///
351    fn value(&self, event: &PyEvent) -> Float {
352        self.0.value(&event.0)
353    }
354    /// All values of this Variable on the given Dataset
355    ///
356    /// Parameters
357    /// ----------
358    /// dataset : Dataset
359    ///     The Dataset upon which the Variable is calculated
360    ///
361    /// Returns
362    /// -------
363    /// values : array_like
364    ///     The values of the Variable for each Event in the given `dataset`
365    ///
366    fn value_on<'py>(&self, py: Python<'py>, dataset: &PyDataset) -> Bound<'py, PyArray1<Float>> {
367        PyArray1::from_slice(py, &self.0.value_on(&dataset.0))
368    }
369    fn __eq__(&self, value: Float) -> PyVariableExpression {
370        PyVariableExpression(self.0.eq(value))
371    }
372    fn __lt__(&self, value: Float) -> PyVariableExpression {
373        PyVariableExpression(self.0.lt(value))
374    }
375    fn __gt__(&self, value: Float) -> PyVariableExpression {
376        PyVariableExpression(self.0.gt(value))
377    }
378    fn __le__(&self, value: Float) -> PyVariableExpression {
379        PyVariableExpression(self.0.le(value))
380    }
381    fn __ge__(&self, value: Float) -> PyVariableExpression {
382        PyVariableExpression(self.0.ge(value))
383    }
384    fn __repr__(&self) -> String {
385        format!("{:?}", self.0)
386    }
387    fn __str__(&self) -> String {
388        format!("{}", self.0)
389    }
390}
391
392/// A Variable used to define both spherical decay angles in the given frame
393///
394/// This class combines ``laddu.CosTheta`` and ``laddu.Phi`` into a single
395/// object
396///
397/// Parameters
398/// ----------
399/// beam : int
400///     The index of the `beam` particle
401/// recoil : list of int
402///     Indices of particles which are combined to form the recoiling particle (particles which
403///     are not `beam` or part of the `resonance`)
404/// daughter : list of int
405///     Indices of particles which are combined to form one of the decay products of the
406///     `resonance`
407/// resonance : list of int
408///     Indices of particles which are combined to form the `resonance`
409/// frame : {'Helicity', 'HX', 'HEL', 'GottfriedJackson', 'Gottfried Jackson', 'GJ', 'Gottfried-Jackson'}
410///     The frame to use in the  calculation
411///
412/// Raises
413/// ------
414/// ValueError
415///     If `frame` is not one of the valid options
416///
417/// See Also
418/// --------
419/// laddu.CosTheta
420/// laddu.Phi
421///
422#[pyclass(name = "Angles", module = "laddu")]
423#[derive(Clone)]
424pub struct PyAngles(pub Angles);
425#[pymethods]
426impl PyAngles {
427    #[new]
428    #[pyo3(signature=(beam, recoil, daughter, resonance, frame="Helicity"))]
429    fn new(
430        beam: usize,
431        recoil: Vec<usize>,
432        daughter: Vec<usize>,
433        resonance: Vec<usize>,
434        frame: &str,
435    ) -> PyResult<Self> {
436        Ok(Self(Angles::new(
437            beam,
438            &recoil,
439            &daughter,
440            &resonance,
441            frame.parse()?,
442        )))
443    }
444    /// The Variable representing the cosine of the polar spherical decay angle
445    ///
446    /// Returns
447    /// -------
448    /// CosTheta
449    ///
450    #[getter]
451    fn costheta(&self) -> PyCosTheta {
452        PyCosTheta(self.0.costheta.clone())
453    }
454    // The Variable representing the polar azimuthal decay angle
455    //
456    // Returns
457    // -------
458    // Phi
459    //
460    #[getter]
461    fn phi(&self) -> PyPhi {
462        PyPhi(self.0.phi.clone())
463    }
464    fn __repr__(&self) -> String {
465        format!("{:?}", self.0)
466    }
467    fn __str__(&self) -> String {
468        format!("{}", self.0)
469    }
470}
471
472/// The polar angle of the given polarization vector with respect to the production plane
473///
474/// The `beam` and `recoil` particles define the plane of production, and this Variable
475/// describes the polar angle of the `beam` relative to this plane
476///
477/// Parameters
478/// ----------
479/// beam : int
480///     The index of the `beam` particle
481/// recoil : list of int
482///     Indices of particles which are combined to form the recoiling particle (particles which
483///     are not `beam` or part of the `resonance`)
484/// beam_polarization : int
485///     The index of the auxiliary vector in storing the `beam` particle's polarization
486///
487#[pyclass(name = "PolAngle", module = "laddu")]
488#[derive(Clone, Serialize, Deserialize)]
489pub struct PyPolAngle(pub PolAngle);
490
491#[pymethods]
492impl PyPolAngle {
493    #[new]
494    fn new(beam: usize, recoil: Vec<usize>, beam_polarization: usize) -> Self {
495        Self(PolAngle::new(beam, &recoil, beam_polarization))
496    }
497    /// The value of this Variable for the given Event
498    ///
499    /// Parameters
500    /// ----------
501    /// event : Event
502    ///     The Event upon which the Variable is calculated
503    ///
504    /// Returns
505    /// -------
506    /// value : float
507    ///     The value of the Variable for the given `event`
508    ///
509    fn value(&self, event: &PyEvent) -> Float {
510        self.0.value(&event.0)
511    }
512    /// All values of this Variable on the given Dataset
513    ///
514    /// Parameters
515    /// ----------
516    /// dataset : Dataset
517    ///     The Dataset upon which the Variable is calculated
518    ///
519    /// Returns
520    /// -------
521    /// values : array_like
522    ///     The values of the Variable for each Event in the given `dataset`
523    ///
524    fn value_on<'py>(&self, py: Python<'py>, dataset: &PyDataset) -> Bound<'py, PyArray1<Float>> {
525        PyArray1::from_slice(py, &self.0.value_on(&dataset.0))
526    }
527    fn __eq__(&self, value: Float) -> PyVariableExpression {
528        PyVariableExpression(self.0.eq(value))
529    }
530    fn __lt__(&self, value: Float) -> PyVariableExpression {
531        PyVariableExpression(self.0.lt(value))
532    }
533    fn __gt__(&self, value: Float) -> PyVariableExpression {
534        PyVariableExpression(self.0.gt(value))
535    }
536    fn __le__(&self, value: Float) -> PyVariableExpression {
537        PyVariableExpression(self.0.le(value))
538    }
539    fn __ge__(&self, value: Float) -> PyVariableExpression {
540        PyVariableExpression(self.0.ge(value))
541    }
542    fn __repr__(&self) -> String {
543        format!("{:?}", self.0)
544    }
545    fn __str__(&self) -> String {
546        format!("{}", self.0)
547    }
548}
549
550/// The magnitude of the given particle's polarization vector
551///
552/// This Variable simply represents the magnitude of the polarization vector of the particle
553/// with the index `beam`
554///
555/// Parameters
556/// ----------
557/// beam_polarization : int
558///     The index of the auxiliary vector in storing the `beam` particle's polarization
559///
560/// See Also
561/// --------
562/// laddu.utils.vectors.Vec3.mag
563///
564#[pyclass(name = "PolMagnitude", module = "laddu")]
565#[derive(Clone, Serialize, Deserialize)]
566pub struct PyPolMagnitude(pub PolMagnitude);
567
568#[pymethods]
569impl PyPolMagnitude {
570    #[new]
571    fn new(beam_polarization: usize) -> Self {
572        Self(PolMagnitude::new(beam_polarization))
573    }
574    /// The value of this Variable for the given Event
575    ///
576    /// Parameters
577    /// ----------
578    /// event : Event
579    ///     The Event upon which the Variable is calculated
580    ///
581    /// Returns
582    /// -------
583    /// value : float
584    ///     The value of the Variable for the given `event`
585    ///
586    fn value(&self, event: &PyEvent) -> Float {
587        self.0.value(&event.0)
588    }
589    /// All values of this Variable on the given Dataset
590    ///
591    /// Parameters
592    /// ----------
593    /// dataset : Dataset
594    ///     The Dataset upon which the Variable is calculated
595    ///
596    /// Returns
597    /// -------
598    /// values : array_like
599    ///     The values of the Variable for each Event in the given `dataset`
600    ///
601    fn value_on<'py>(&self, py: Python<'py>, dataset: &PyDataset) -> Bound<'py, PyArray1<Float>> {
602        PyArray1::from_slice(py, &self.0.value_on(&dataset.0))
603    }
604    fn __eq__(&self, value: Float) -> PyVariableExpression {
605        PyVariableExpression(self.0.eq(value))
606    }
607    fn __lt__(&self, value: Float) -> PyVariableExpression {
608        PyVariableExpression(self.0.lt(value))
609    }
610    fn __gt__(&self, value: Float) -> PyVariableExpression {
611        PyVariableExpression(self.0.gt(value))
612    }
613    fn __le__(&self, value: Float) -> PyVariableExpression {
614        PyVariableExpression(self.0.le(value))
615    }
616    fn __ge__(&self, value: Float) -> PyVariableExpression {
617        PyVariableExpression(self.0.ge(value))
618    }
619    fn __repr__(&self) -> String {
620        format!("{:?}", self.0)
621    }
622    fn __str__(&self) -> String {
623        format!("{}", self.0)
624    }
625}
626
627/// A Variable used to define both the polarization angle and magnitude of the given particle``
628///
629/// This class combines ``laddu.PolAngle`` and ``laddu.PolMagnitude`` into a single
630/// object
631///
632/// Parameters
633/// ----------
634/// beam : int
635///     The index of the `beam` particle
636/// recoil : list of int
637///     Indices of particles which are combined to form the recoiling particle (particles which
638///     are not `beam` or part of the `resonance`)
639/// beam_polarization : int
640///     The index of the auxiliary vector in storing the `beam` particle's polarization
641///
642/// See Also
643/// --------
644/// laddu.PolAngle
645/// laddu.PolMagnitude
646///
647#[pyclass(name = "Polarization", module = "laddu")]
648#[derive(Clone)]
649pub struct PyPolarization(pub Polarization);
650#[pymethods]
651impl PyPolarization {
652    #[new]
653    fn new(beam: usize, recoil: Vec<usize>, beam_polarization: usize) -> Self {
654        PyPolarization(Polarization::new(beam, &recoil, beam_polarization))
655    }
656    /// The Variable representing the magnitude of the polarization vector
657    ///
658    /// Returns
659    /// -------
660    /// PolMagnitude
661    ///
662    #[getter]
663    fn pol_magnitude(&self) -> PyPolMagnitude {
664        PyPolMagnitude(self.0.pol_magnitude)
665    }
666    /// The Variable representing the polar angle of the polarization vector
667    ///
668    /// Returns
669    /// -------
670    /// PolAngle
671    ///
672    #[getter]
673    fn pol_angle(&self) -> PyPolAngle {
674        PyPolAngle(self.0.pol_angle.clone())
675    }
676    fn __repr__(&self) -> String {
677        format!("{:?}", self.0)
678    }
679    fn __str__(&self) -> String {
680        format!("{}", self.0)
681    }
682}
683
684/// Mandelstam variables s, t, and u
685///
686/// By convention, the metric is chosen to be :math:`(+---)` and the variables are defined as follows
687/// (ignoring factors of :math:`c`):
688///
689/// .. math:: s = (p_1 + p_2)^2 = (p_3 + p_4)^2
690///
691/// .. math:: t = (p_1 - p_3)^2 = (p_4 - p_2)^2
692///
693/// .. math:: u = (p_1 - p_4)^2 = (p_3 - p_2)^2
694///
695/// Parameters
696/// ----------
697/// p1: list of int
698///     The indices of particles to combine to create :math:`p_1` in the diagram
699/// p2: list of int
700///     The indices of particles to combine to create :math:`p_2` in the diagram
701/// p3: list of int
702///     The indices of particles to combine to create :math:`p_3` in the diagram
703/// p4: list of int
704///     The indices of particles to combine to create :math:`p_4` in the diagram
705/// channel: {'s', 't', 'u', 'S', 'T', 'U'}
706///     The Mandelstam channel to calculate
707///
708/// Raises
709/// ------
710/// Exception
711///     If more than one particle list is empty
712/// ValueError
713///     If `channel` is not one of the valid options
714///
715/// Notes
716/// -----
717/// At most one of the input particles may be omitted by using an empty list. This will cause
718/// the calculation to use whichever equality listed above does not contain that particle.
719///
720/// By default, the first equality is used if no particle lists are empty.
721///
722#[pyclass(name = "Mandelstam", module = "laddu")]
723#[derive(Clone, Serialize, Deserialize)]
724pub struct PyMandelstam(pub Mandelstam);
725
726#[pymethods]
727impl PyMandelstam {
728    #[new]
729    fn new(
730        p1: Vec<usize>,
731        p2: Vec<usize>,
732        p3: Vec<usize>,
733        p4: Vec<usize>,
734        channel: &str,
735    ) -> PyResult<Self> {
736        Ok(Self(Mandelstam::new(p1, p2, p3, p4, channel.parse()?)?))
737    }
738    /// The value of this Variable for the given Event
739    ///
740    /// Parameters
741    /// ----------
742    /// event : Event
743    ///     The Event upon which the Variable is calculated
744    ///
745    /// Returns
746    /// -------
747    /// value : float
748    ///     The value of the Variable for the given `event`
749    ///
750    fn value(&self, event: &PyEvent) -> Float {
751        self.0.value(&event.0)
752    }
753    /// All values of this Variable on the given Dataset
754    ///
755    /// Parameters
756    /// ----------
757    /// dataset : Dataset
758    ///     The Dataset upon which the Variable is calculated
759    ///
760    /// Returns
761    /// -------
762    /// values : array_like
763    ///     The values of the Variable for each Event in the given `dataset`
764    ///
765    fn value_on<'py>(&self, py: Python<'py>, dataset: &PyDataset) -> Bound<'py, PyArray1<Float>> {
766        PyArray1::from_slice(py, &self.0.value_on(&dataset.0))
767    }
768    fn __eq__(&self, value: Float) -> PyVariableExpression {
769        PyVariableExpression(self.0.eq(value))
770    }
771    fn __lt__(&self, value: Float) -> PyVariableExpression {
772        PyVariableExpression(self.0.lt(value))
773    }
774    fn __gt__(&self, value: Float) -> PyVariableExpression {
775        PyVariableExpression(self.0.gt(value))
776    }
777    fn __le__(&self, value: Float) -> PyVariableExpression {
778        PyVariableExpression(self.0.le(value))
779    }
780    fn __ge__(&self, value: Float) -> PyVariableExpression {
781        PyVariableExpression(self.0.ge(value))
782    }
783    fn __repr__(&self) -> String {
784        format!("{:?}", self.0)
785    }
786    fn __str__(&self) -> String {
787        format!("{}", self.0)
788    }
789}
790
791#[typetag::serde]
792impl Variable for PyVariable {
793    fn value_on(&self, dataset: &Dataset) -> Vec<Float> {
794        match self {
795            PyVariable::Mass(mass) => mass.0.value_on(dataset),
796            PyVariable::CosTheta(cos_theta) => cos_theta.0.value_on(dataset),
797            PyVariable::Phi(phi) => phi.0.value_on(dataset),
798            PyVariable::PolAngle(pol_angle) => pol_angle.0.value_on(dataset),
799            PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value_on(dataset),
800            PyVariable::Mandelstam(mandelstam) => mandelstam.0.value_on(dataset),
801        }
802    }
803
804    fn value(&self, event: &Event) -> Float {
805        match self {
806            PyVariable::Mass(mass) => mass.0.value(event),
807            PyVariable::CosTheta(cos_theta) => cos_theta.0.value(event),
808            PyVariable::Phi(phi) => phi.0.value(event),
809            PyVariable::PolAngle(pol_angle) => pol_angle.0.value(event),
810            PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value(event),
811            PyVariable::Mandelstam(mandelstam) => mandelstam.0.value(event),
812        }
813    }
814}