1use std::fmt::{Debug, Display};
2
3use laddu_amplitudes::DecayAmplitudeExt;
4use laddu_core::{
5 data::{Dataset, DatasetMetadata, EventLike, OwnedEvent},
6 reaction::{Decay, Particle, Reaction},
7 traits::Variable,
8 variables::{
9 Angles, CosTheta, IntoP4Selection, Mandelstam, Mass, P4Selection, Phi, PolAngle,
10 PolMagnitude, Polarization, VariableExpression,
11 },
12 LadduResult,
13};
14use numpy::PyArray1;
15use pyo3::{exceptions::PyValueError, prelude::*, types::PyTuple};
16use serde::{Deserialize, Serialize};
17
18use crate::{
19 amplitudes::{py_tags, PyExpression},
20 data::{PyDataset, PyEvent},
21 quantum::angular_momentum::{
22 parse_angular_momentum, parse_orbital_angular_momentum, parse_projection,
23 },
24 vectors::PyVec4,
25};
26
27#[derive(FromPyObject, Clone, Serialize, Deserialize)]
28pub enum PyVariable {
29 #[pyo3(transparent)]
30 Mass(PyMass),
31 #[pyo3(transparent)]
32 CosTheta(PyCosTheta),
33 #[pyo3(transparent)]
34 Phi(PyPhi),
35 #[pyo3(transparent)]
36 PolAngle(PyPolAngle),
37 #[pyo3(transparent)]
38 PolMagnitude(PyPolMagnitude),
39 #[pyo3(transparent)]
40 Mandelstam(PyMandelstam),
41}
42
43impl Debug for PyVariable {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 Self::Mass(v) => write!(f, "{:?}", v.0),
47 Self::CosTheta(v) => write!(f, "{:?}", v.0),
48 Self::Phi(v) => write!(f, "{:?}", v.0),
49 Self::PolAngle(v) => write!(f, "{:?}", v.0),
50 Self::PolMagnitude(v) => write!(f, "{:?}", v.0),
51 Self::Mandelstam(v) => write!(f, "{:?}", v.0),
52 }
53 }
54}
55impl Display for PyVariable {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 match self {
58 Self::Mass(v) => write!(f, "{}", v.0),
59 Self::CosTheta(v) => write!(f, "{}", v.0),
60 Self::Phi(v) => write!(f, "{}", v.0),
61 Self::PolAngle(v) => write!(f, "{}", v.0),
62 Self::PolMagnitude(v) => write!(f, "{}", v.0),
63 Self::Mandelstam(v) => write!(f, "{}", v.0),
64 }
65 }
66}
67
68impl PyVariable {
69 pub(crate) fn bind_in_place(&mut self, metadata: &DatasetMetadata) -> PyResult<()> {
70 match self {
71 Self::Mass(mass) => mass.0.bind(metadata).map_err(PyErr::from),
72 Self::CosTheta(cos_theta) => cos_theta.0.bind(metadata).map_err(PyErr::from),
73 Self::Phi(phi) => phi.0.bind(metadata).map_err(PyErr::from),
74 Self::PolAngle(pol_angle) => pol_angle.0.bind(metadata).map_err(PyErr::from),
75 Self::PolMagnitude(pol_magnitude) => {
76 pol_magnitude.0.bind(metadata).map_err(PyErr::from)
77 }
78 Self::Mandelstam(mandelstam) => mandelstam.0.bind(metadata).map_err(PyErr::from),
79 }
80 }
81
82 pub(crate) fn bound(&self, metadata: &DatasetMetadata) -> PyResult<Self> {
83 let mut cloned = self.clone();
84 cloned.bind_in_place(metadata)?;
85 Ok(cloned)
86 }
87
88 pub(crate) fn evaluate_event(&self, event: &OwnedEvent) -> PyResult<f64> {
89 Ok(self.value(event))
90 }
91}
92
93#[pyclass(name = "VariableExpression", module = "laddu")]
94pub struct PyVariableExpression(pub VariableExpression);
95
96#[pymethods]
97impl PyVariableExpression {
98 fn __and__(&self, rhs: &PyVariableExpression) -> PyVariableExpression {
99 PyVariableExpression(self.0.clone() & rhs.0.clone())
100 }
101 fn __or__(&self, rhs: &PyVariableExpression) -> PyVariableExpression {
102 PyVariableExpression(self.0.clone() | rhs.0.clone())
103 }
104 fn __invert__(&self) -> PyVariableExpression {
105 PyVariableExpression(!self.0.clone())
106 }
107 fn __str__(&self) -> String {
108 format!("{}", self.0)
109 }
110}
111
112#[derive(Clone, FromPyObject)]
113pub enum PyP4SelectionInput {
114 #[pyo3(transparent)]
115 Name(String),
116 #[pyo3(transparent)]
117 Names(Vec<String>),
118}
119
120impl PyP4SelectionInput {
121 fn into_selection(self) -> P4Selection {
122 match self {
123 PyP4SelectionInput::Name(name) => name.into_selection(),
124 PyP4SelectionInput::Names(names) => names.into_selection(),
125 }
126 }
127}
128
129#[pyclass(name = "Particle", module = "laddu", from_py_object)]
131#[derive(Clone, Serialize, Deserialize)]
132pub struct PyParticle(pub Particle);
133
134#[pymethods]
135impl PyParticle {
136 #[staticmethod]
138 fn stored(id: &str) -> Self {
139 Self(Particle::stored(id))
140 }
141
142 #[staticmethod]
144 fn fixed(label: &str, p4: &PyVec4) -> Self {
145 Self(Particle::fixed(label, p4.0))
146 }
147
148 #[staticmethod]
150 fn missing(label: &str) -> Self {
151 Self(Particle::missing(label))
152 }
153
154 #[staticmethod]
156 fn composite(label: &str, daughters: &Bound<'_, PyTuple>) -> PyResult<Self> {
157 if daughters.len() != 2 {
158 return Err(PyValueError::new_err(
159 "composite particles require exactly two ordered daughters",
160 ));
161 }
162 let daughter_1 = daughters.get_item(0)?.extract::<PyParticle>()?;
163 let daughter_2 = daughters.get_item(1)?.extract::<PyParticle>()?;
164 Ok(Self(Particle::composite(
165 label,
166 (&daughter_1.0, &daughter_2.0),
167 )?))
168 }
169
170 #[getter]
172 fn label(&self) -> String {
173 self.0.label().to_string()
174 }
175
176 fn __repr__(&self) -> String {
177 format!("{:?}", self.0)
178 }
179
180 fn __str__(&self) -> String {
181 self.0.to_string()
182 }
183}
184
185#[pyclass(name = "Reaction", module = "laddu", from_py_object)]
187#[derive(Clone, Serialize, Deserialize)]
188pub struct PyReaction(pub Reaction);
189
190#[pymethods]
191impl PyReaction {
192 #[staticmethod]
194 fn two_to_two(
195 p1: &PyParticle,
196 p2: &PyParticle,
197 p3: &PyParticle,
198 p4: &PyParticle,
199 ) -> PyResult<Self> {
200 Ok(Self(Reaction::two_to_two(&p1.0, &p2.0, &p3.0, &p4.0)?))
201 }
202
203 fn mass(&self, particle: &str) -> PyMass {
205 PyMass(self.0.mass(particle))
206 }
207
208 fn decay(&self, parent: &str) -> PyResult<PyDecay> {
210 Ok(PyDecay(self.0.decay(parent)?))
211 }
212
213 fn mandelstam(&self, channel: &str) -> PyResult<PyMandelstam> {
215 Ok(PyMandelstam(self.0.mandelstam(channel.parse()?)?))
216 }
217
218 fn pol_angle(&self, angle_aux: String) -> PyPolAngle {
220 PyPolAngle(self.0.pol_angle(angle_aux))
221 }
222
223 fn polarization(&self, pol_magnitude: String, pol_angle: String) -> PyResult<PyPolarization> {
225 if pol_magnitude == pol_angle {
226 return Err(PyValueError::new_err(
227 "`pol_magnitude` and `pol_angle` must reference distinct auxiliary columns",
228 ));
229 }
230 Ok(PyPolarization(
231 self.0.polarization(pol_magnitude, pol_angle),
232 ))
233 }
234
235 fn __repr__(&self) -> String {
236 format!("{:?}", self.0)
237 }
238
239 fn __str__(&self) -> String {
240 format!("{:?}", self.0)
241 }
242}
243
244#[pyclass(name = "Decay", module = "laddu", from_py_object)]
246#[derive(Clone, Serialize, Deserialize)]
247pub struct PyDecay(pub Decay);
248
249#[pymethods]
250impl PyDecay {
251 #[getter]
253 fn reaction(&self) -> PyReaction {
254 PyReaction(self.0.reaction().clone())
255 }
256
257 #[getter]
259 fn parent(&self) -> String {
260 self.0.parent().to_string()
261 }
262
263 #[getter]
265 fn daughter_1(&self) -> String {
266 self.0.daughter_1().to_string()
267 }
268
269 #[getter]
271 fn daughter_2(&self) -> String {
272 self.0.daughter_2().to_string()
273 }
274
275 fn daughters(&self) -> Vec<String> {
277 self.0.daughters().into_iter().map(str::to_string).collect()
278 }
279
280 fn mass(&self) -> PyMass {
282 PyMass(self.0.mass())
283 }
284
285 fn parent_mass(&self) -> PyMass {
287 PyMass(self.0.parent_mass())
288 }
289
290 fn daughter_1_mass(&self) -> PyMass {
292 PyMass(self.0.daughter_1_mass())
293 }
294
295 fn daughter_2_mass(&self) -> PyMass {
297 PyMass(self.0.daughter_2_mass())
298 }
299
300 fn daughter_mass(&self, daughter: &str) -> PyResult<PyMass> {
302 Ok(PyMass(self.0.daughter_mass(daughter)?))
303 }
304
305 #[pyo3(signature=(daughter, frame="Helicity"))]
307 fn costheta(&self, daughter: &str, frame: &str) -> PyResult<PyCosTheta> {
308 Ok(PyCosTheta(self.0.costheta(daughter, frame.parse()?)?))
309 }
310
311 #[pyo3(signature=(daughter, frame="Helicity"))]
313 fn phi(&self, daughter: &str, frame: &str) -> PyResult<PyPhi> {
314 Ok(PyPhi(self.0.phi(daughter, frame.parse()?)?))
315 }
316
317 #[pyo3(signature=(daughter, frame="Helicity"))]
319 fn angles(&self, daughter: &str, frame: &str) -> PyResult<PyAngles> {
320 Ok(PyAngles(self.0.angles(daughter, frame.parse()?)?))
321 }
322
323 #[pyo3(signature=(*tags, spin, projection, daughter, lambda_1, lambda_2, frame="Helicity"))]
325 #[allow(clippy::too_many_arguments)]
326 fn helicity_factor(
327 &self,
328 tags: &Bound<'_, PyTuple>,
329 spin: &Bound<'_, PyAny>,
330 projection: &Bound<'_, PyAny>,
331 daughter: &str,
332 lambda_1: &Bound<'_, PyAny>,
333 lambda_2: &Bound<'_, PyAny>,
334 frame: &str,
335 ) -> PyResult<PyExpression> {
336 Ok(PyExpression(self.0.helicity_factor(
337 py_tags(tags)?,
338 parse_angular_momentum(spin)?,
339 parse_projection(projection)?,
340 daughter,
341 parse_projection(lambda_1)?,
342 parse_projection(lambda_2)?,
343 frame.parse()?,
344 )?))
345 }
346
347 #[pyo3(signature=(*tags, spin, projection, orbital_l, coupled_spin, daughter, daughter_1_spin, daughter_2_spin, lambda_1, lambda_2, frame="Helicity"))]
349 #[allow(clippy::too_many_arguments)]
350 fn canonical_factor(
351 &self,
352 tags: &Bound<'_, PyTuple>,
353 spin: &Bound<'_, PyAny>,
354 projection: &Bound<'_, PyAny>,
355 orbital_l: &Bound<'_, PyAny>,
356 coupled_spin: &Bound<'_, PyAny>,
357 daughter: &str,
358 daughter_1_spin: &Bound<'_, PyAny>,
359 daughter_2_spin: &Bound<'_, PyAny>,
360 lambda_1: &Bound<'_, PyAny>,
361 lambda_2: &Bound<'_, PyAny>,
362 frame: &str,
363 ) -> PyResult<PyExpression> {
364 Ok(PyExpression(self.0.canonical_factor(
365 py_tags(tags)?,
366 parse_angular_momentum(spin)?,
367 parse_projection(projection)?,
368 parse_orbital_angular_momentum(orbital_l)?,
369 parse_angular_momentum(coupled_spin)?,
370 daughter,
371 parse_angular_momentum(daughter_1_spin)?,
372 parse_angular_momentum(daughter_2_spin)?,
373 parse_projection(lambda_1)?,
374 parse_projection(lambda_2)?,
375 frame.parse()?,
376 )?))
377 }
378
379 fn __repr__(&self) -> String {
380 format!("{:?}", self.0)
381 }
382
383 fn __str__(&self) -> String {
384 format!("{:?}", self.0)
385 }
386}
387
388#[pyclass(name = "Mass", module = "laddu", from_py_object)]
403#[derive(Clone, Serialize, Deserialize)]
404pub struct PyMass(pub Mass);
405
406#[pymethods]
407impl PyMass {
408 #[new]
409 fn new(constituents: PyP4SelectionInput) -> Self {
410 Self(Mass::new(constituents.into_selection()))
411 }
412 fn value(&self, event: &PyEvent) -> PyResult<f64> {
425 let metadata = event
426 .metadata_opt()
427 .ok_or_else(|| PyValueError::new_err(
428 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
429 ))?;
430 let mut variable = self.0.clone();
431 variable.bind(metadata).map_err(PyErr::from)?;
432 Ok(variable.value(&event.event))
433 }
434 fn value_on<'py>(
447 &self,
448 py: Python<'py>,
449 dataset: &PyDataset,
450 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
451 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
452 Ok(PyArray1::from_vec(py, values))
453 }
454 fn __eq__(&self, value: f64) -> PyVariableExpression {
455 PyVariableExpression(self.0.eq(value))
456 }
457 fn __lt__(&self, value: f64) -> PyVariableExpression {
458 PyVariableExpression(self.0.lt(value))
459 }
460 fn __gt__(&self, value: f64) -> PyVariableExpression {
461 PyVariableExpression(self.0.gt(value))
462 }
463 fn __le__(&self, value: f64) -> PyVariableExpression {
464 PyVariableExpression(self.0.le(value))
465 }
466 fn __ge__(&self, value: f64) -> PyVariableExpression {
467 PyVariableExpression(self.0.ge(value))
468 }
469 fn __repr__(&self) -> String {
470 format!("{:?}", self.0)
471 }
472 fn __str__(&self) -> String {
473 format!("{}", self.0)
474 }
475}
476
477#[pyclass(name = "CosTheta", module = "laddu", from_py_object)]
516#[derive(Clone, Serialize, Deserialize)]
517pub struct PyCosTheta(pub CosTheta);
518
519#[pymethods]
520impl PyCosTheta {
521 fn value(&self, event: &PyEvent) -> PyResult<f64> {
534 let metadata = event
535 .metadata_opt()
536 .ok_or_else(|| PyValueError::new_err(
537 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
538 ))?;
539 let mut variable = self.0.clone();
540 variable.bind(metadata).map_err(PyErr::from)?;
541 Ok(variable.value(&event.event))
542 }
543 fn value_on<'py>(
556 &self,
557 py: Python<'py>,
558 dataset: &PyDataset,
559 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
560 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
561 Ok(PyArray1::from_vec(py, values))
562 }
563 fn __eq__(&self, value: f64) -> PyVariableExpression {
564 PyVariableExpression(self.0.eq(value))
565 }
566 fn __lt__(&self, value: f64) -> PyVariableExpression {
567 PyVariableExpression(self.0.lt(value))
568 }
569 fn __gt__(&self, value: f64) -> PyVariableExpression {
570 PyVariableExpression(self.0.gt(value))
571 }
572 fn __le__(&self, value: f64) -> PyVariableExpression {
573 PyVariableExpression(self.0.le(value))
574 }
575 fn __ge__(&self, value: f64) -> PyVariableExpression {
576 PyVariableExpression(self.0.ge(value))
577 }
578 fn __repr__(&self) -> String {
579 format!("{:?}", self.0)
580 }
581 fn __str__(&self) -> String {
582 format!("{}", self.0)
583 }
584}
585
586#[pyclass(name = "Phi", module = "laddu", from_py_object)]
626#[derive(Clone, Serialize, Deserialize)]
627pub struct PyPhi(pub Phi);
628
629#[pymethods]
630impl PyPhi {
631 fn value(&self, event: &PyEvent) -> PyResult<f64> {
644 let metadata = event
645 .metadata_opt()
646 .ok_or_else(|| PyValueError::new_err(
647 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
648 ))?;
649 let mut variable = self.0.clone();
650 variable.bind(metadata).map_err(PyErr::from)?;
651 Ok(variable.value(&event.event))
652 }
653 fn value_on<'py>(
666 &self,
667 py: Python<'py>,
668 dataset: &PyDataset,
669 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
670 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
671 Ok(PyArray1::from_vec(py, values))
672 }
673 fn __eq__(&self, value: f64) -> PyVariableExpression {
674 PyVariableExpression(self.0.eq(value))
675 }
676 fn __lt__(&self, value: f64) -> PyVariableExpression {
677 PyVariableExpression(self.0.lt(value))
678 }
679 fn __gt__(&self, value: f64) -> PyVariableExpression {
680 PyVariableExpression(self.0.gt(value))
681 }
682 fn __le__(&self, value: f64) -> PyVariableExpression {
683 PyVariableExpression(self.0.le(value))
684 }
685 fn __ge__(&self, value: f64) -> PyVariableExpression {
686 PyVariableExpression(self.0.ge(value))
687 }
688 fn __repr__(&self) -> String {
689 format!("{:?}", self.0)
690 }
691 fn __str__(&self) -> String {
692 format!("{}", self.0)
693 }
694}
695
696#[pyclass(name = "Angles", module = "laddu", skip_from_py_object)]
722#[derive(Clone)]
723pub struct PyAngles(pub Angles);
724#[pymethods]
725impl PyAngles {
726 #[getter]
733 fn costheta(&self) -> PyCosTheta {
734 PyCosTheta(self.0.costheta.clone())
735 }
736 #[getter]
743 fn phi(&self) -> PyPhi {
744 PyPhi(self.0.phi.clone())
745 }
746 fn __repr__(&self) -> String {
747 format!("{:?}", self.0)
748 }
749 fn __str__(&self) -> String {
750 format!("{}", self.0)
751 }
752}
753
754#[pyclass(name = "PolAngle", module = "laddu", from_py_object)]
767#[derive(Clone, Serialize, Deserialize)]
768pub struct PyPolAngle(pub PolAngle);
769
770#[pymethods]
771impl PyPolAngle {
772 #[new]
773 fn new(reaction: PyReaction, pol_angle: String) -> Self {
774 Self(PolAngle::new(reaction.0.clone(), pol_angle))
775 }
776 fn value(&self, event: &PyEvent) -> PyResult<f64> {
789 let metadata = event
790 .metadata_opt()
791 .ok_or_else(|| PyValueError::new_err(
792 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
793 ))?;
794 let mut variable = self.0.clone();
795 variable.bind(metadata).map_err(PyErr::from)?;
796 Ok(variable.value(&event.event))
797 }
798 fn value_on<'py>(
811 &self,
812 py: Python<'py>,
813 dataset: &PyDataset,
814 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
815 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
816 Ok(PyArray1::from_vec(py, values))
817 }
818 fn __eq__(&self, value: f64) -> PyVariableExpression {
819 PyVariableExpression(self.0.eq(value))
820 }
821 fn __lt__(&self, value: f64) -> PyVariableExpression {
822 PyVariableExpression(self.0.lt(value))
823 }
824 fn __gt__(&self, value: f64) -> PyVariableExpression {
825 PyVariableExpression(self.0.gt(value))
826 }
827 fn __le__(&self, value: f64) -> PyVariableExpression {
828 PyVariableExpression(self.0.le(value))
829 }
830 fn __ge__(&self, value: f64) -> PyVariableExpression {
831 PyVariableExpression(self.0.ge(value))
832 }
833 fn __repr__(&self) -> String {
834 format!("{:?}", self.0)
835 }
836 fn __str__(&self) -> String {
837 format!("{}", self.0)
838 }
839}
840
841#[pyclass(name = "PolMagnitude", module = "laddu", from_py_object)]
856#[derive(Clone, Serialize, Deserialize)]
857pub struct PyPolMagnitude(pub PolMagnitude);
858
859#[pymethods]
860impl PyPolMagnitude {
861 #[new]
862 fn new(pol_magnitude: String) -> Self {
863 Self(PolMagnitude::new(pol_magnitude))
864 }
865 fn value(&self, event: &PyEvent) -> PyResult<f64> {
878 let metadata = event
879 .metadata_opt()
880 .ok_or_else(|| PyValueError::new_err(
881 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
882 ))?;
883 let mut variable = self.0.clone();
884 variable.bind(metadata).map_err(PyErr::from)?;
885 Ok(variable.value(&event.event))
886 }
887 fn value_on<'py>(
900 &self,
901 py: Python<'py>,
902 dataset: &PyDataset,
903 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
904 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
905 Ok(PyArray1::from_vec(py, values))
906 }
907 fn __eq__(&self, value: f64) -> PyVariableExpression {
908 PyVariableExpression(self.0.eq(value))
909 }
910 fn __lt__(&self, value: f64) -> PyVariableExpression {
911 PyVariableExpression(self.0.lt(value))
912 }
913 fn __gt__(&self, value: f64) -> PyVariableExpression {
914 PyVariableExpression(self.0.gt(value))
915 }
916 fn __le__(&self, value: f64) -> PyVariableExpression {
917 PyVariableExpression(self.0.le(value))
918 }
919 fn __ge__(&self, value: f64) -> PyVariableExpression {
920 PyVariableExpression(self.0.ge(value))
921 }
922 fn __repr__(&self) -> String {
923 format!("{:?}", self.0)
924 }
925 fn __str__(&self) -> String {
926 format!("{}", self.0)
927 }
928}
929
930#[pyclass(name = "Polarization", module = "laddu", skip_from_py_object)]
950#[derive(Clone)]
951pub struct PyPolarization(pub Polarization);
952#[pymethods]
953impl PyPolarization {
954 #[new]
955 #[pyo3(signature=(reaction, *, pol_magnitude, pol_angle))]
956 fn new(reaction: PyReaction, pol_magnitude: String, pol_angle: String) -> PyResult<Self> {
957 if pol_magnitude == pol_angle {
958 return Err(PyValueError::new_err(
959 "`pol_magnitude` and `pol_angle` must reference distinct auxiliary columns",
960 ));
961 }
962 let polarization = Polarization::new(reaction.0.clone(), pol_magnitude, pol_angle);
963 Ok(PyPolarization(polarization))
964 }
965 #[getter]
972 fn pol_magnitude(&self) -> PyPolMagnitude {
973 PyPolMagnitude(self.0.pol_magnitude.clone())
974 }
975 #[getter]
982 fn pol_angle(&self) -> PyPolAngle {
983 PyPolAngle(self.0.pol_angle.clone())
984 }
985 fn __repr__(&self) -> String {
986 format!("{:?}", self.0)
987 }
988 fn __str__(&self) -> String {
989 format!("{}", self.0)
990 }
991}
992
993#[pyclass(name = "Mandelstam", module = "laddu", from_py_object)]
1022#[derive(Clone, Serialize, Deserialize)]
1023pub struct PyMandelstam(pub Mandelstam);
1024
1025#[pymethods]
1026impl PyMandelstam {
1027 #[new]
1028 fn new(reaction: PyReaction, channel: &str) -> PyResult<Self> {
1029 Ok(Self(reaction.0.mandelstam(channel.parse()?)?))
1030 }
1031 fn value(&self, event: &PyEvent) -> PyResult<f64> {
1044 let metadata = event
1045 .metadata_opt()
1046 .ok_or_else(|| PyValueError::new_err(
1047 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
1048 ))?;
1049 let mut variable = self.0.clone();
1050 variable.bind(metadata).map_err(PyErr::from)?;
1051 Ok(variable.value(&event.event))
1052 }
1053 fn value_on<'py>(
1066 &self,
1067 py: Python<'py>,
1068 dataset: &PyDataset,
1069 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
1070 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
1071 Ok(PyArray1::from_vec(py, values))
1072 }
1073 fn __eq__(&self, value: f64) -> PyVariableExpression {
1074 PyVariableExpression(self.0.eq(value))
1075 }
1076 fn __lt__(&self, value: f64) -> PyVariableExpression {
1077 PyVariableExpression(self.0.lt(value))
1078 }
1079 fn __gt__(&self, value: f64) -> PyVariableExpression {
1080 PyVariableExpression(self.0.gt(value))
1081 }
1082 fn __le__(&self, value: f64) -> PyVariableExpression {
1083 PyVariableExpression(self.0.le(value))
1084 }
1085 fn __ge__(&self, value: f64) -> PyVariableExpression {
1086 PyVariableExpression(self.0.ge(value))
1087 }
1088 fn __repr__(&self) -> String {
1089 format!("{:?}", self.0)
1090 }
1091 fn __str__(&self) -> String {
1092 format!("{}", self.0)
1093 }
1094}
1095
1096#[typetag::serde]
1097impl Variable for PyVariable {
1098 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
1099 match self {
1100 PyVariable::Mass(mass) => mass.0.bind(metadata),
1101 PyVariable::CosTheta(cos_theta) => cos_theta.0.bind(metadata),
1102 PyVariable::Phi(phi) => phi.0.bind(metadata),
1103 PyVariable::PolAngle(pol_angle) => pol_angle.0.bind(metadata),
1104 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.bind(metadata),
1105 PyVariable::Mandelstam(mandelstam) => mandelstam.0.bind(metadata),
1106 }
1107 }
1108
1109 fn value_on(&self, dataset: &Dataset) -> LadduResult<Vec<f64>> {
1110 match self {
1111 PyVariable::Mass(mass) => mass.0.value_on(dataset),
1112 PyVariable::CosTheta(cos_theta) => cos_theta.0.value_on(dataset),
1113 PyVariable::Phi(phi) => phi.0.value_on(dataset),
1114 PyVariable::PolAngle(pol_angle) => pol_angle.0.value_on(dataset),
1115 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value_on(dataset),
1116 PyVariable::Mandelstam(mandelstam) => mandelstam.0.value_on(dataset),
1117 }
1118 }
1119
1120 fn value(&self, event: &dyn EventLike) -> f64 {
1121 match self {
1122 PyVariable::Mass(mass) => mass.0.value(event),
1123 PyVariable::CosTheta(cos_theta) => cos_theta.0.value(event),
1124 PyVariable::Phi(phi) => phi.0.value(event),
1125 PyVariable::PolAngle(pol_angle) => pol_angle.0.value(event),
1126 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value(event),
1127 PyVariable::Mandelstam(mandelstam) => mandelstam.0.value(event),
1128 }
1129 }
1130}