1use std::fmt::{Debug, Display};
2
3use laddu_amplitudes::{DecayAmplitudeExt, ProductionAmplitudeExt};
4use laddu_core::{
5 data::{Dataset, DatasetMetadata, EventLike, OwnedEvent},
6 reaction::{Decay, Particle, Production, 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 production(&self) -> PyResult<PyProduction> {
215 Ok(PyProduction(self.0.production()?))
216 }
217
218 fn mandelstam(&self, channel: &str) -> PyResult<PyMandelstam> {
220 Ok(PyMandelstam(self.0.mandelstam(channel.parse()?)?))
221 }
222
223 fn pol_angle(&self, angle_aux: String) -> PyPolAngle {
225 PyPolAngle(self.0.pol_angle(angle_aux))
226 }
227
228 #[pyo3(signature=(*, pol_magnitude, pol_angle))]
230 fn polarization(&self, pol_magnitude: String, pol_angle: String) -> PyResult<PyPolarization> {
231 if pol_magnitude == pol_angle {
232 return Err(PyValueError::new_err(
233 "`pol_magnitude` and `pol_angle` must reference distinct auxiliary columns",
234 ));
235 }
236 Ok(PyPolarization(
237 self.0.polarization(pol_magnitude, pol_angle),
238 ))
239 }
240
241 fn __repr__(&self) -> String {
242 format!("{:?}", self.0)
243 }
244
245 fn __str__(&self) -> String {
246 format!("{:?}", self.0)
247 }
248}
249
250#[pyclass(name = "Decay", module = "laddu", from_py_object)]
252#[derive(Clone, Serialize, Deserialize)]
253pub struct PyDecay(pub Decay);
254
255#[pymethods]
256impl PyDecay {
257 #[getter]
259 fn reaction(&self) -> PyReaction {
260 PyReaction(self.0.reaction().clone())
261 }
262
263 #[getter]
265 fn parent(&self) -> String {
266 self.0.parent().to_string()
267 }
268
269 #[getter]
271 fn daughter_1(&self) -> String {
272 self.0.daughter_1().to_string()
273 }
274
275 #[getter]
277 fn daughter_2(&self) -> String {
278 self.0.daughter_2().to_string()
279 }
280
281 fn daughters(&self) -> Vec<String> {
283 self.0.daughters().into_iter().map(str::to_string).collect()
284 }
285
286 fn mass(&self) -> PyMass {
288 PyMass(self.0.mass())
289 }
290
291 fn parent_mass(&self) -> PyMass {
293 PyMass(self.0.parent_mass())
294 }
295
296 fn daughter_1_mass(&self) -> PyMass {
298 PyMass(self.0.daughter_1_mass())
299 }
300
301 fn daughter_2_mass(&self) -> PyMass {
303 PyMass(self.0.daughter_2_mass())
304 }
305
306 fn daughter_mass(&self, daughter: &str) -> PyResult<PyMass> {
308 Ok(PyMass(self.0.daughter_mass(daughter)?))
309 }
310
311 #[pyo3(signature=(daughter, frame="Helicity"))]
313 fn costheta(&self, daughter: &str, frame: &str) -> PyResult<PyCosTheta> {
314 Ok(PyCosTheta(self.0.costheta(daughter, frame.parse()?)?))
315 }
316
317 #[pyo3(signature=(daughter, frame="Helicity"))]
319 fn phi(&self, daughter: &str, frame: &str) -> PyResult<PyPhi> {
320 Ok(PyPhi(self.0.phi(daughter, frame.parse()?)?))
321 }
322
323 #[pyo3(signature=(daughter, frame="Helicity"))]
325 fn angles(&self, daughter: &str, frame: &str) -> PyResult<PyAngles> {
326 Ok(PyAngles(self.0.angles(daughter, frame.parse()?)?))
327 }
328
329 #[pyo3(signature=(*tags, spin, projection, daughter, lambda_1, lambda_2, frame="Helicity"))]
331 #[allow(clippy::too_many_arguments)]
332 fn helicity_factor(
333 &self,
334 tags: &Bound<'_, PyTuple>,
335 spin: &Bound<'_, PyAny>,
336 projection: &Bound<'_, PyAny>,
337 daughter: &str,
338 lambda_1: &Bound<'_, PyAny>,
339 lambda_2: &Bound<'_, PyAny>,
340 frame: &str,
341 ) -> PyResult<PyExpression> {
342 Ok(PyExpression(self.0.helicity_factor(
343 py_tags(tags)?,
344 parse_angular_momentum(spin)?,
345 parse_projection(projection)?,
346 daughter,
347 parse_projection(lambda_1)?,
348 parse_projection(lambda_2)?,
349 frame.parse()?,
350 )?))
351 }
352
353 #[pyo3(signature=(*tags, spin, projection, orbital_l, coupled_spin, daughter, daughter_1_spin, daughter_2_spin, lambda_1, lambda_2, frame="Helicity"))]
355 #[allow(clippy::too_many_arguments)]
356 fn canonical_factor(
357 &self,
358 tags: &Bound<'_, PyTuple>,
359 spin: &Bound<'_, PyAny>,
360 projection: &Bound<'_, PyAny>,
361 orbital_l: &Bound<'_, PyAny>,
362 coupled_spin: &Bound<'_, PyAny>,
363 daughter: &str,
364 daughter_1_spin: &Bound<'_, PyAny>,
365 daughter_2_spin: &Bound<'_, PyAny>,
366 lambda_1: &Bound<'_, PyAny>,
367 lambda_2: &Bound<'_, PyAny>,
368 frame: &str,
369 ) -> PyResult<PyExpression> {
370 Ok(PyExpression(self.0.canonical_factor(
371 py_tags(tags)?,
372 parse_angular_momentum(spin)?,
373 parse_projection(projection)?,
374 parse_orbital_angular_momentum(orbital_l)?,
375 parse_angular_momentum(coupled_spin)?,
376 daughter,
377 parse_angular_momentum(daughter_1_spin)?,
378 parse_angular_momentum(daughter_2_spin)?,
379 parse_projection(lambda_1)?,
380 parse_projection(lambda_2)?,
381 frame.parse()?,
382 )?))
383 }
384
385 fn __repr__(&self) -> String {
386 format!("{:?}", self.0)
387 }
388
389 fn __str__(&self) -> String {
390 format!("{:?}", self.0)
391 }
392}
393
394#[pyclass(name = "Production", module = "laddu", from_py_object)]
396#[derive(Clone, Serialize, Deserialize)]
397pub struct PyProduction(pub Production);
398
399#[pymethods]
400impl PyProduction {
401 #[getter]
403 fn reaction(&self) -> PyReaction {
404 PyReaction(self.0.reaction().clone())
405 }
406
407 #[getter]
409 fn produced(&self) -> String {
410 self.0.produced().to_string()
411 }
412
413 #[getter]
415 fn recoil(&self) -> String {
416 self.0.recoil().to_string()
417 }
418
419 #[pyo3(signature=(frame="Helicity"))]
421 fn costheta(&self, frame: &str) -> PyResult<PyCosTheta> {
422 Ok(PyCosTheta(self.0.costheta(frame.parse()?)?))
423 }
424
425 #[pyo3(signature=(frame="Helicity"))]
427 fn phi(&self, frame: &str) -> PyResult<PyPhi> {
428 Ok(PyPhi(self.0.phi(frame.parse()?)?))
429 }
430
431 #[pyo3(signature=(frame="Helicity"))]
433 fn angles(&self, frame: &str) -> PyResult<PyAngles> {
434 Ok(PyAngles(self.0.angles(frame.parse()?)?))
435 }
436
437 #[pyo3(signature=(*tags, spin, projection, lambda_produced, lambda_recoil, frame="Helicity"))]
439 #[allow(clippy::too_many_arguments)]
440 fn helicity_factor(
441 &self,
442 tags: &Bound<'_, PyTuple>,
443 spin: &Bound<'_, PyAny>,
444 projection: &Bound<'_, PyAny>,
445 lambda_produced: &Bound<'_, PyAny>,
446 lambda_recoil: &Bound<'_, PyAny>,
447 frame: &str,
448 ) -> PyResult<PyExpression> {
449 Ok(PyExpression(self.0.helicity_factor(
450 py_tags(tags)?,
451 parse_angular_momentum(spin)?,
452 parse_projection(projection)?,
453 parse_projection(lambda_produced)?,
454 parse_projection(lambda_recoil)?,
455 frame.parse()?,
456 )?))
457 }
458
459 #[pyo3(signature=(*tags, spin, projection, orbital_l, coupled_spin, produced_spin, recoil_spin, lambda_produced, lambda_recoil, frame="Helicity"))]
461 #[allow(clippy::too_many_arguments)]
462 fn canonical_factor(
463 &self,
464 tags: &Bound<'_, PyTuple>,
465 spin: &Bound<'_, PyAny>,
466 projection: &Bound<'_, PyAny>,
467 orbital_l: &Bound<'_, PyAny>,
468 coupled_spin: &Bound<'_, PyAny>,
469 produced_spin: &Bound<'_, PyAny>,
470 recoil_spin: &Bound<'_, PyAny>,
471 lambda_produced: &Bound<'_, PyAny>,
472 lambda_recoil: &Bound<'_, PyAny>,
473 frame: &str,
474 ) -> PyResult<PyExpression> {
475 Ok(PyExpression(self.0.canonical_factor(
476 py_tags(tags)?,
477 parse_angular_momentum(spin)?,
478 parse_projection(projection)?,
479 parse_orbital_angular_momentum(orbital_l)?,
480 parse_angular_momentum(coupled_spin)?,
481 parse_angular_momentum(produced_spin)?,
482 parse_angular_momentum(recoil_spin)?,
483 parse_projection(lambda_produced)?,
484 parse_projection(lambda_recoil)?,
485 frame.parse()?,
486 )?))
487 }
488
489 fn __repr__(&self) -> String {
490 format!("{:?}", self.0)
491 }
492
493 fn __str__(&self) -> String {
494 format!("{:?}", self.0)
495 }
496}
497
498#[pyclass(name = "Mass", module = "laddu", from_py_object)]
513#[derive(Clone, Serialize, Deserialize)]
514pub struct PyMass(pub Mass);
515
516#[pymethods]
517impl PyMass {
518 #[new]
519 fn new(constituents: PyP4SelectionInput) -> Self {
520 Self(Mass::new(constituents.into_selection()))
521 }
522 fn value(&self, event: &PyEvent) -> PyResult<f64> {
535 let metadata = event
536 .metadata_opt()
537 .ok_or_else(|| PyValueError::new_err(
538 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
539 ))?;
540 let mut variable = self.0.clone();
541 variable.bind(metadata).map_err(PyErr::from)?;
542 Ok(variable.value(&event.event))
543 }
544 fn value_on<'py>(
557 &self,
558 py: Python<'py>,
559 dataset: &PyDataset,
560 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
561 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
562 Ok(PyArray1::from_vec(py, values))
563 }
564 fn __eq__(&self, value: f64) -> PyVariableExpression {
565 PyVariableExpression(self.0.eq(value))
566 }
567 fn __lt__(&self, value: f64) -> PyVariableExpression {
568 PyVariableExpression(self.0.lt(value))
569 }
570 fn __gt__(&self, value: f64) -> PyVariableExpression {
571 PyVariableExpression(self.0.gt(value))
572 }
573 fn __le__(&self, value: f64) -> PyVariableExpression {
574 PyVariableExpression(self.0.le(value))
575 }
576 fn __ge__(&self, value: f64) -> PyVariableExpression {
577 PyVariableExpression(self.0.ge(value))
578 }
579 fn __repr__(&self) -> String {
580 format!("{:?}", self.0)
581 }
582 fn __str__(&self) -> String {
583 format!("{}", self.0)
584 }
585}
586
587#[pyclass(name = "CosTheta", module = "laddu", from_py_object)]
626#[derive(Clone, Serialize, Deserialize)]
627pub struct PyCosTheta(pub CosTheta);
628
629#[pymethods]
630impl PyCosTheta {
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 = "Phi", module = "laddu", from_py_object)]
736#[derive(Clone, Serialize, Deserialize)]
737pub struct PyPhi(pub Phi);
738
739#[pymethods]
740impl PyPhi {
741 fn value(&self, event: &PyEvent) -> PyResult<f64> {
754 let metadata = event
755 .metadata_opt()
756 .ok_or_else(|| PyValueError::new_err(
757 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
758 ))?;
759 let mut variable = self.0.clone();
760 variable.bind(metadata).map_err(PyErr::from)?;
761 Ok(variable.value(&event.event))
762 }
763 fn value_on<'py>(
776 &self,
777 py: Python<'py>,
778 dataset: &PyDataset,
779 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
780 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
781 Ok(PyArray1::from_vec(py, values))
782 }
783 fn __eq__(&self, value: f64) -> PyVariableExpression {
784 PyVariableExpression(self.0.eq(value))
785 }
786 fn __lt__(&self, value: f64) -> PyVariableExpression {
787 PyVariableExpression(self.0.lt(value))
788 }
789 fn __gt__(&self, value: f64) -> PyVariableExpression {
790 PyVariableExpression(self.0.gt(value))
791 }
792 fn __le__(&self, value: f64) -> PyVariableExpression {
793 PyVariableExpression(self.0.le(value))
794 }
795 fn __ge__(&self, value: f64) -> PyVariableExpression {
796 PyVariableExpression(self.0.ge(value))
797 }
798 fn __repr__(&self) -> String {
799 format!("{:?}", self.0)
800 }
801 fn __str__(&self) -> String {
802 format!("{}", self.0)
803 }
804}
805
806#[pyclass(name = "Angles", module = "laddu", skip_from_py_object)]
832#[derive(Clone)]
833pub struct PyAngles(pub Angles);
834#[pymethods]
835impl PyAngles {
836 #[getter]
843 fn costheta(&self) -> PyCosTheta {
844 PyCosTheta(self.0.costheta.clone())
845 }
846 #[getter]
853 fn phi(&self) -> PyPhi {
854 PyPhi(self.0.phi.clone())
855 }
856 fn __repr__(&self) -> String {
857 format!("{:?}", self.0)
858 }
859 fn __str__(&self) -> String {
860 format!("{}", self.0)
861 }
862}
863
864#[pyclass(name = "PolAngle", module = "laddu", from_py_object)]
877#[derive(Clone, Serialize, Deserialize)]
878pub struct PyPolAngle(pub PolAngle);
879
880#[pymethods]
881impl PyPolAngle {
882 #[new]
883 fn new(reaction: PyReaction, pol_angle: String) -> Self {
884 Self(PolAngle::new(reaction.0.clone(), pol_angle))
885 }
886 fn value(&self, event: &PyEvent) -> PyResult<f64> {
899 let metadata = event
900 .metadata_opt()
901 .ok_or_else(|| PyValueError::new_err(
902 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
903 ))?;
904 let mut variable = self.0.clone();
905 variable.bind(metadata).map_err(PyErr::from)?;
906 Ok(variable.value(&event.event))
907 }
908 fn value_on<'py>(
921 &self,
922 py: Python<'py>,
923 dataset: &PyDataset,
924 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
925 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
926 Ok(PyArray1::from_vec(py, values))
927 }
928 fn __eq__(&self, value: f64) -> PyVariableExpression {
929 PyVariableExpression(self.0.eq(value))
930 }
931 fn __lt__(&self, value: f64) -> PyVariableExpression {
932 PyVariableExpression(self.0.lt(value))
933 }
934 fn __gt__(&self, value: f64) -> PyVariableExpression {
935 PyVariableExpression(self.0.gt(value))
936 }
937 fn __le__(&self, value: f64) -> PyVariableExpression {
938 PyVariableExpression(self.0.le(value))
939 }
940 fn __ge__(&self, value: f64) -> PyVariableExpression {
941 PyVariableExpression(self.0.ge(value))
942 }
943 fn __repr__(&self) -> String {
944 format!("{:?}", self.0)
945 }
946 fn __str__(&self) -> String {
947 format!("{}", self.0)
948 }
949}
950
951#[pyclass(name = "PolMagnitude", module = "laddu", from_py_object)]
966#[derive(Clone, Serialize, Deserialize)]
967pub struct PyPolMagnitude(pub PolMagnitude);
968
969#[pymethods]
970impl PyPolMagnitude {
971 #[new]
972 fn new(pol_magnitude: String) -> Self {
973 Self(PolMagnitude::new(pol_magnitude))
974 }
975 fn value(&self, event: &PyEvent) -> PyResult<f64> {
988 let metadata = event
989 .metadata_opt()
990 .ok_or_else(|| PyValueError::new_err(
991 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
992 ))?;
993 let mut variable = self.0.clone();
994 variable.bind(metadata).map_err(PyErr::from)?;
995 Ok(variable.value(&event.event))
996 }
997 fn value_on<'py>(
1010 &self,
1011 py: Python<'py>,
1012 dataset: &PyDataset,
1013 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
1014 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
1015 Ok(PyArray1::from_vec(py, values))
1016 }
1017 fn __eq__(&self, value: f64) -> PyVariableExpression {
1018 PyVariableExpression(self.0.eq(value))
1019 }
1020 fn __lt__(&self, value: f64) -> PyVariableExpression {
1021 PyVariableExpression(self.0.lt(value))
1022 }
1023 fn __gt__(&self, value: f64) -> PyVariableExpression {
1024 PyVariableExpression(self.0.gt(value))
1025 }
1026 fn __le__(&self, value: f64) -> PyVariableExpression {
1027 PyVariableExpression(self.0.le(value))
1028 }
1029 fn __ge__(&self, value: f64) -> PyVariableExpression {
1030 PyVariableExpression(self.0.ge(value))
1031 }
1032 fn __repr__(&self) -> String {
1033 format!("{:?}", self.0)
1034 }
1035 fn __str__(&self) -> String {
1036 format!("{}", self.0)
1037 }
1038}
1039
1040#[pyclass(name = "Polarization", module = "laddu", skip_from_py_object)]
1060#[derive(Clone)]
1061pub struct PyPolarization(pub Polarization);
1062#[pymethods]
1063impl PyPolarization {
1064 #[new]
1065 #[pyo3(signature=(reaction, *, pol_magnitude, pol_angle))]
1066 fn new(reaction: PyReaction, pol_magnitude: String, pol_angle: String) -> PyResult<Self> {
1067 if pol_magnitude == pol_angle {
1068 return Err(PyValueError::new_err(
1069 "`pol_magnitude` and `pol_angle` must reference distinct auxiliary columns",
1070 ));
1071 }
1072 let polarization = Polarization::new(reaction.0.clone(), pol_magnitude, pol_angle);
1073 Ok(PyPolarization(polarization))
1074 }
1075 #[getter]
1082 fn pol_magnitude(&self) -> PyPolMagnitude {
1083 PyPolMagnitude(self.0.pol_magnitude.clone())
1084 }
1085 #[getter]
1092 fn pol_angle(&self) -> PyPolAngle {
1093 PyPolAngle(self.0.pol_angle.clone())
1094 }
1095 fn __repr__(&self) -> String {
1096 format!("{:?}", self.0)
1097 }
1098 fn __str__(&self) -> String {
1099 format!("{}", self.0)
1100 }
1101}
1102
1103#[pyclass(name = "Mandelstam", module = "laddu", from_py_object)]
1132#[derive(Clone, Serialize, Deserialize)]
1133pub struct PyMandelstam(pub Mandelstam);
1134
1135#[pymethods]
1136impl PyMandelstam {
1137 #[new]
1138 fn new(reaction: PyReaction, channel: &str) -> PyResult<Self> {
1139 Ok(Self(reaction.0.mandelstam(channel.parse()?)?))
1140 }
1141 fn value(&self, event: &PyEvent) -> PyResult<f64> {
1154 let metadata = event
1155 .metadata_opt()
1156 .ok_or_else(|| PyValueError::new_err(
1157 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
1158 ))?;
1159 let mut variable = self.0.clone();
1160 variable.bind(metadata).map_err(PyErr::from)?;
1161 Ok(variable.value(&event.event))
1162 }
1163 fn value_on<'py>(
1176 &self,
1177 py: Python<'py>,
1178 dataset: &PyDataset,
1179 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
1180 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
1181 Ok(PyArray1::from_vec(py, values))
1182 }
1183 fn __eq__(&self, value: f64) -> PyVariableExpression {
1184 PyVariableExpression(self.0.eq(value))
1185 }
1186 fn __lt__(&self, value: f64) -> PyVariableExpression {
1187 PyVariableExpression(self.0.lt(value))
1188 }
1189 fn __gt__(&self, value: f64) -> PyVariableExpression {
1190 PyVariableExpression(self.0.gt(value))
1191 }
1192 fn __le__(&self, value: f64) -> PyVariableExpression {
1193 PyVariableExpression(self.0.le(value))
1194 }
1195 fn __ge__(&self, value: f64) -> PyVariableExpression {
1196 PyVariableExpression(self.0.ge(value))
1197 }
1198 fn __repr__(&self) -> String {
1199 format!("{:?}", self.0)
1200 }
1201 fn __str__(&self) -> String {
1202 format!("{}", self.0)
1203 }
1204}
1205
1206#[typetag::serde]
1207impl Variable for PyVariable {
1208 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
1209 match self {
1210 PyVariable::Mass(mass) => mass.0.bind(metadata),
1211 PyVariable::CosTheta(cos_theta) => cos_theta.0.bind(metadata),
1212 PyVariable::Phi(phi) => phi.0.bind(metadata),
1213 PyVariable::PolAngle(pol_angle) => pol_angle.0.bind(metadata),
1214 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.bind(metadata),
1215 PyVariable::Mandelstam(mandelstam) => mandelstam.0.bind(metadata),
1216 }
1217 }
1218
1219 fn value_on(&self, dataset: &Dataset) -> LadduResult<Vec<f64>> {
1220 match self {
1221 PyVariable::Mass(mass) => mass.0.value_on(dataset),
1222 PyVariable::CosTheta(cos_theta) => cos_theta.0.value_on(dataset),
1223 PyVariable::Phi(phi) => phi.0.value_on(dataset),
1224 PyVariable::PolAngle(pol_angle) => pol_angle.0.value_on(dataset),
1225 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value_on(dataset),
1226 PyVariable::Mandelstam(mandelstam) => mandelstam.0.value_on(dataset),
1227 }
1228 }
1229
1230 fn value(&self, event: &dyn EventLike) -> f64 {
1231 match self {
1232 PyVariable::Mass(mass) => mass.0.value(event),
1233 PyVariable::CosTheta(cos_theta) => cos_theta.0.value(event),
1234 PyVariable::Phi(phi) => phi.0.value(event),
1235 PyVariable::PolAngle(pol_angle) => pol_angle.0.value(event),
1236 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value(event),
1237 PyVariable::Mandelstam(mandelstam) => mandelstam.0.value(event),
1238 }
1239 }
1240}