1use crate::{
2 amplitudes::PyExpression,
3 data::{PyDataset, PyEvent},
4 utils::vectors::PyVec4,
5};
6use laddu_core::{
7 data::{Dataset, DatasetMetadata, Event, NamedEventView},
8 traits::Variable,
9 utils::angular_momentum::{AngularMomentum, AngularMomentumProjection, OrbitalAngularMomentum},
10 utils::reaction::{Decay, Particle, Reaction},
11 utils::variables::{
12 Angles, CosTheta, IntoP4Selection, Mandelstam, Mass, P4Selection, Phi, PolAngle,
13 PolMagnitude, Polarization, VariableExpression,
14 },
15 LadduError, LadduResult,
16};
17use num::rational::Ratio;
18use numpy::PyArray1;
19use pyo3::{exceptions::PyValueError, prelude::*, types::PyBool};
20use serde::{Deserialize, Serialize};
21use std::fmt::{Debug, Display};
22
23#[derive(FromPyObject, Clone, Serialize, Deserialize)]
24pub enum PyVariable {
25 #[pyo3(transparent)]
26 Mass(PyMass),
27 #[pyo3(transparent)]
28 CosTheta(PyCosTheta),
29 #[pyo3(transparent)]
30 Phi(PyPhi),
31 #[pyo3(transparent)]
32 PolAngle(PyPolAngle),
33 #[pyo3(transparent)]
34 PolMagnitude(PyPolMagnitude),
35 #[pyo3(transparent)]
36 Mandelstam(PyMandelstam),
37}
38
39impl Debug for PyVariable {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match self {
42 Self::Mass(v) => write!(f, "{:?}", v.0),
43 Self::CosTheta(v) => write!(f, "{:?}", v.0),
44 Self::Phi(v) => write!(f, "{:?}", v.0),
45 Self::PolAngle(v) => write!(f, "{:?}", v.0),
46 Self::PolMagnitude(v) => write!(f, "{:?}", v.0),
47 Self::Mandelstam(v) => write!(f, "{:?}", v.0),
48 }
49 }
50}
51impl Display for PyVariable {
52 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
53 match self {
54 Self::Mass(v) => write!(f, "{}", v.0),
55 Self::CosTheta(v) => write!(f, "{}", v.0),
56 Self::Phi(v) => write!(f, "{}", v.0),
57 Self::PolAngle(v) => write!(f, "{}", v.0),
58 Self::PolMagnitude(v) => write!(f, "{}", v.0),
59 Self::Mandelstam(v) => write!(f, "{}", v.0),
60 }
61 }
62}
63
64impl PyVariable {
65 pub(crate) fn bind_in_place(&mut self, metadata: &DatasetMetadata) -> PyResult<()> {
66 match self {
67 Self::Mass(mass) => mass.0.bind(metadata).map_err(PyErr::from),
68 Self::CosTheta(cos_theta) => cos_theta.0.bind(metadata).map_err(PyErr::from),
69 Self::Phi(phi) => phi.0.bind(metadata).map_err(PyErr::from),
70 Self::PolAngle(pol_angle) => pol_angle.0.bind(metadata).map_err(PyErr::from),
71 Self::PolMagnitude(pol_magnitude) => {
72 pol_magnitude.0.bind(metadata).map_err(PyErr::from)
73 }
74 Self::Mandelstam(mandelstam) => mandelstam.0.bind(metadata).map_err(PyErr::from),
75 }
76 }
77
78 pub(crate) fn bound(&self, metadata: &DatasetMetadata) -> PyResult<Self> {
79 let mut cloned = self.clone();
80 cloned.bind_in_place(metadata)?;
81 Ok(cloned)
82 }
83
84 pub(crate) fn evaluate_event(&self, event: &Event) -> PyResult<f64> {
85 let dataset = Dataset::new_with_metadata(vec![event.data_arc()], event.metadata_arc());
86 let event_view = dataset.event_view(0);
87 Ok(self.value(&event_view))
88 }
89}
90
91#[pyclass(name = "VariableExpression", module = "laddu")]
92pub struct PyVariableExpression(pub VariableExpression);
93
94#[pymethods]
95impl PyVariableExpression {
96 fn __and__(&self, rhs: &PyVariableExpression) -> PyVariableExpression {
97 PyVariableExpression(self.0.clone() & rhs.0.clone())
98 }
99 fn __or__(&self, rhs: &PyVariableExpression) -> PyVariableExpression {
100 PyVariableExpression(self.0.clone() | rhs.0.clone())
101 }
102 fn __invert__(&self) -> PyVariableExpression {
103 PyVariableExpression(!self.0.clone())
104 }
105 fn __str__(&self) -> String {
106 format!("{}", self.0)
107 }
108}
109
110#[derive(Clone, FromPyObject)]
111pub enum PyP4SelectionInput {
112 #[pyo3(transparent)]
113 Name(String),
114 #[pyo3(transparent)]
115 Names(Vec<String>),
116}
117
118impl PyP4SelectionInput {
119 fn into_selection(self) -> P4Selection {
120 match self {
121 PyP4SelectionInput::Name(name) => name.into_selection(),
122 PyP4SelectionInput::Names(names) => names.into_selection(),
123 }
124 }
125}
126
127#[pyclass(name = "Particle", module = "laddu", from_py_object)]
129#[derive(Clone, Serialize, Deserialize)]
130pub struct PyParticle(pub Particle);
131
132#[pymethods]
133impl PyParticle {
134 #[staticmethod]
136 fn measured(label: &str, p4: PyP4SelectionInput) -> Self {
137 Self(Particle::measured(label, p4.into_selection()))
138 }
139
140 #[staticmethod]
142 fn fixed(label: &str, p4: &PyVec4) -> Self {
143 Self(Particle::fixed(label, p4.0))
144 }
145
146 #[staticmethod]
148 fn missing(label: &str) -> Self {
149 Self(Particle::missing(label))
150 }
151
152 #[staticmethod]
154 fn composite(label: &str, daughters: Vec<PyParticle>) -> PyResult<Self> {
155 Ok(Self(Particle::composite(
156 label,
157 daughters.iter().map(|daughter| &daughter.0),
158 )?))
159 }
160
161 #[getter]
163 fn label(&self) -> String {
164 self.0.label().to_string()
165 }
166
167 fn __repr__(&self) -> String {
168 format!("{:?}", self.0)
169 }
170
171 fn __str__(&self) -> String {
172 self.0.to_string()
173 }
174}
175
176#[pyclass(name = "Reaction", module = "laddu", from_py_object)]
178#[derive(Clone, Serialize, Deserialize)]
179pub struct PyReaction(pub Reaction);
180
181#[pymethods]
182impl PyReaction {
183 #[staticmethod]
185 fn two_to_two(
186 p1: &PyParticle,
187 p2: &PyParticle,
188 p3: &PyParticle,
189 p4: &PyParticle,
190 ) -> PyResult<Self> {
191 Ok(Self(Reaction::two_to_two(&p1.0, &p2.0, &p3.0, &p4.0)?))
192 }
193
194 fn mass(&self, particle: &PyParticle) -> PyMass {
196 PyMass(self.0.mass(&particle.0))
197 }
198
199 fn decay(&self, parent: &PyParticle) -> PyResult<PyDecay> {
201 Ok(PyDecay(self.0.decay(&parent.0)?))
202 }
203
204 fn mandelstam(&self, channel: &str) -> PyResult<PyMandelstam> {
206 Ok(PyMandelstam(self.0.mandelstam(channel.parse()?)))
207 }
208
209 fn pol_angle(&self, angle_aux: String) -> PyPolAngle {
211 PyPolAngle(self.0.pol_angle(angle_aux))
212 }
213
214 fn polarization(&self, pol_magnitude: String, pol_angle: String) -> PyResult<PyPolarization> {
216 if pol_magnitude == pol_angle {
217 return Err(PyValueError::new_err(
218 "`pol_magnitude` and `pol_angle` must reference distinct auxiliary columns",
219 ));
220 }
221 Ok(PyPolarization(
222 self.0.polarization(pol_magnitude, pol_angle),
223 ))
224 }
225
226 fn __repr__(&self) -> String {
227 format!("{:?}", self.0)
228 }
229}
230
231#[pyclass(name = "Decay", module = "laddu", from_py_object)]
233#[derive(Clone, Serialize, Deserialize)]
234pub struct PyDecay(pub Decay);
235
236#[pymethods]
237impl PyDecay {
238 #[getter]
240 fn parent(&self) -> PyParticle {
241 PyParticle(self.0.parent().clone())
242 }
243
244 #[getter]
246 fn daughter_1(&self) -> PyParticle {
247 PyParticle(self.0.daughter_1().clone())
248 }
249
250 #[getter]
252 fn daughter_2(&self) -> PyParticle {
253 PyParticle(self.0.daughter_2().clone())
254 }
255
256 fn daughters(&self) -> Vec<PyParticle> {
258 self.0
259 .daughters()
260 .into_iter()
261 .map(|daughter| PyParticle(daughter.clone()))
262 .collect()
263 }
264
265 fn mass(&self) -> PyMass {
267 PyMass(self.0.mass())
268 }
269
270 fn parent_mass(&self) -> PyMass {
272 PyMass(self.0.parent_mass())
273 }
274
275 fn daughter_1_mass(&self) -> PyMass {
277 PyMass(self.0.daughter_1_mass())
278 }
279
280 fn daughter_2_mass(&self) -> PyMass {
282 PyMass(self.0.daughter_2_mass())
283 }
284
285 fn daughter_mass(&self, daughter: &PyParticle) -> PyResult<PyMass> {
287 Ok(PyMass(self.0.daughter_mass(&daughter.0)?))
288 }
289
290 #[pyo3(signature=(daughter, frame="Helicity"))]
292 fn costheta(&self, daughter: &PyParticle, frame: &str) -> PyResult<PyCosTheta> {
293 Ok(PyCosTheta(self.0.costheta(&daughter.0, frame.parse()?)?))
294 }
295
296 #[pyo3(signature=(daughter, frame="Helicity"))]
298 fn phi(&self, daughter: &PyParticle, frame: &str) -> PyResult<PyPhi> {
299 Ok(PyPhi(self.0.phi(&daughter.0, frame.parse()?)?))
300 }
301
302 #[pyo3(signature=(daughter, frame="Helicity"))]
304 fn angles(&self, daughter: &PyParticle, frame: &str) -> PyResult<PyAngles> {
305 Ok(PyAngles(self.0.angles(&daughter.0, frame.parse()?)?))
306 }
307
308 #[pyo3(signature=(name, spin, projection, daughter, lambda_1, lambda_2, frame="Helicity"))]
310 #[allow(clippy::too_many_arguments)]
311 fn helicity_factor(
312 &self,
313 name: &str,
314 spin: &Bound<'_, PyAny>,
315 projection: &Bound<'_, PyAny>,
316 daughter: &PyParticle,
317 lambda_1: &Bound<'_, PyAny>,
318 lambda_2: &Bound<'_, PyAny>,
319 frame: &str,
320 ) -> PyResult<PyExpression> {
321 Ok(PyExpression(self.0.helicity_factor(
322 name,
323 parse_angular_momentum(spin)?,
324 parse_projection(projection)?,
325 &daughter.0,
326 parse_projection(lambda_1)?,
327 parse_projection(lambda_2)?,
328 frame.parse()?,
329 )?))
330 }
331
332 #[pyo3(signature=(name, spin, projection, orbital_l, coupled_spin, daughter, daughter_1_spin, daughter_2_spin, lambda_1, lambda_2, frame="Helicity"))]
334 #[allow(clippy::too_many_arguments)]
335 fn canonical_factor(
336 &self,
337 name: &str,
338 spin: &Bound<'_, PyAny>,
339 projection: &Bound<'_, PyAny>,
340 orbital_l: &Bound<'_, PyAny>,
341 coupled_spin: &Bound<'_, PyAny>,
342 daughter: &PyParticle,
343 daughter_1_spin: &Bound<'_, PyAny>,
344 daughter_2_spin: &Bound<'_, PyAny>,
345 lambda_1: &Bound<'_, PyAny>,
346 lambda_2: &Bound<'_, PyAny>,
347 frame: &str,
348 ) -> PyResult<PyExpression> {
349 Ok(PyExpression(self.0.canonical_factor(
350 name,
351 parse_angular_momentum(spin)?,
352 parse_projection(projection)?,
353 parse_orbital_angular_momentum(orbital_l)?,
354 parse_angular_momentum(coupled_spin)?,
355 &daughter.0,
356 parse_angular_momentum(daughter_1_spin)?,
357 parse_angular_momentum(daughter_2_spin)?,
358 parse_projection(lambda_1)?,
359 parse_projection(lambda_2)?,
360 frame.parse()?,
361 )?))
362 }
363
364 fn __repr__(&self) -> String {
365 format!("{:?}", self.0)
366 }
367}
368
369fn parse_angular_momentum(input: &Bound<'_, PyAny>) -> PyResult<AngularMomentum> {
370 parse_ratio_like(input)
371 .and_then(AngularMomentum::from_ratio)
372 .map_err(py_value_error)
373}
374
375fn parse_projection(input: &Bound<'_, PyAny>) -> PyResult<AngularMomentumProjection> {
376 parse_ratio_like(input)
377 .and_then(AngularMomentumProjection::from_ratio)
378 .map_err(py_value_error)
379}
380
381fn parse_orbital_angular_momentum(input: &Bound<'_, PyAny>) -> PyResult<OrbitalAngularMomentum> {
382 parse_ratio_like(input)
383 .and_then(OrbitalAngularMomentum::from_ratio)
384 .map_err(py_value_error)
385}
386
387fn parse_ratio_like(input: &Bound<'_, PyAny>) -> LadduResult<Ratio<i32>> {
388 if input.is_instance_of::<PyBool>() {
389 return Err(LadduError::Custom(
390 "quantum number cannot be a bool".to_string(),
391 ));
392 }
393 if let Ok(value) = input.extract::<i32>() {
394 return Ok(Ratio::from_integer(value));
395 }
396 if let Ok(value) = input.extract::<f64>() {
397 let twice = AngularMomentumProjection::from_f64(value)?.value();
398 return Ok(Ratio::new(twice, 2));
399 }
400 let numerator = input
401 .getattr("numerator")
402 .and_then(|value| value.extract::<i32>());
403 let denominator = input
404 .getattr("denominator")
405 .and_then(|value| value.extract::<i32>());
406 if let (Ok(numerator), Ok(denominator)) = (numerator, denominator) {
407 if denominator == 0 {
408 return Err(LadduError::Custom(
409 "quantum number denominator cannot be zero".to_string(),
410 ));
411 }
412 return Ok(Ratio::new(numerator, denominator));
413 }
414 Err(LadduError::Custom(
415 "quantum number must be an int, float, or fractions.Fraction".to_string(),
416 ))
417}
418
419fn py_value_error(err: LadduError) -> PyErr {
420 PyValueError::new_err(err.to_string())
421}
422#[pyclass(name = "Mass", module = "laddu", from_py_object)]
437#[derive(Clone, Serialize, Deserialize)]
438pub struct PyMass(pub Mass);
439
440#[pymethods]
441impl PyMass {
442 #[new]
443 fn new(constituents: PyP4SelectionInput) -> Self {
444 Self(Mass::new(constituents.into_selection()))
445 }
446 fn value(&self, event: &PyEvent) -> PyResult<f64> {
459 let metadata = event
460 .metadata_opt()
461 .ok_or_else(|| PyValueError::new_err(
462 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
463 ))?;
464 let mut variable = self.0.clone();
465 variable.bind(metadata).map_err(PyErr::from)?;
466 let dataset =
467 Dataset::new_with_metadata(vec![event.event.data_arc()], event.event.metadata_arc());
468 let event_view = dataset.event_view(0);
469 Ok(variable.value(&event_view))
470 }
471 fn value_on<'py>(
484 &self,
485 py: Python<'py>,
486 dataset: &PyDataset,
487 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
488 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
489 Ok(PyArray1::from_vec(py, values))
490 }
491 fn __eq__(&self, value: f64) -> PyVariableExpression {
492 PyVariableExpression(self.0.eq(value))
493 }
494 fn __lt__(&self, value: f64) -> PyVariableExpression {
495 PyVariableExpression(self.0.lt(value))
496 }
497 fn __gt__(&self, value: f64) -> PyVariableExpression {
498 PyVariableExpression(self.0.gt(value))
499 }
500 fn __le__(&self, value: f64) -> PyVariableExpression {
501 PyVariableExpression(self.0.le(value))
502 }
503 fn __ge__(&self, value: f64) -> PyVariableExpression {
504 PyVariableExpression(self.0.ge(value))
505 }
506 fn __repr__(&self) -> String {
507 format!("{:?}", self.0)
508 }
509 fn __str__(&self) -> String {
510 format!("{}", self.0)
511 }
512}
513
514#[pyclass(name = "CosTheta", module = "laddu", from_py_object)]
553#[derive(Clone, Serialize, Deserialize)]
554pub struct PyCosTheta(pub CosTheta);
555
556#[pymethods]
557impl PyCosTheta {
558 fn value(&self, event: &PyEvent) -> PyResult<f64> {
571 let metadata = event
572 .metadata_opt()
573 .ok_or_else(|| PyValueError::new_err(
574 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
575 ))?;
576 let mut variable = self.0.clone();
577 variable.bind(metadata).map_err(PyErr::from)?;
578 let dataset =
579 Dataset::new_with_metadata(vec![event.event.data_arc()], event.event.metadata_arc());
580 let event_view = dataset.event_view(0);
581 Ok(variable.value(&event_view))
582 }
583 fn value_on<'py>(
596 &self,
597 py: Python<'py>,
598 dataset: &PyDataset,
599 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
600 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
601 Ok(PyArray1::from_vec(py, values))
602 }
603 fn __eq__(&self, value: f64) -> PyVariableExpression {
604 PyVariableExpression(self.0.eq(value))
605 }
606 fn __lt__(&self, value: f64) -> PyVariableExpression {
607 PyVariableExpression(self.0.lt(value))
608 }
609 fn __gt__(&self, value: f64) -> PyVariableExpression {
610 PyVariableExpression(self.0.gt(value))
611 }
612 fn __le__(&self, value: f64) -> PyVariableExpression {
613 PyVariableExpression(self.0.le(value))
614 }
615 fn __ge__(&self, value: f64) -> PyVariableExpression {
616 PyVariableExpression(self.0.ge(value))
617 }
618 fn __repr__(&self) -> String {
619 format!("{:?}", self.0)
620 }
621 fn __str__(&self) -> String {
622 format!("{}", self.0)
623 }
624}
625
626#[pyclass(name = "Phi", module = "laddu", from_py_object)]
666#[derive(Clone, Serialize, Deserialize)]
667pub struct PyPhi(pub Phi);
668
669#[pymethods]
670impl PyPhi {
671 fn value(&self, event: &PyEvent) -> PyResult<f64> {
684 let metadata = event
685 .metadata_opt()
686 .ok_or_else(|| PyValueError::new_err(
687 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
688 ))?;
689 let mut variable = self.0.clone();
690 variable.bind(metadata).map_err(PyErr::from)?;
691 let dataset =
692 Dataset::new_with_metadata(vec![event.event.data_arc()], event.event.metadata_arc());
693 let event_view = dataset.event_view(0);
694 Ok(variable.value(&event_view))
695 }
696 fn value_on<'py>(
709 &self,
710 py: Python<'py>,
711 dataset: &PyDataset,
712 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
713 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
714 Ok(PyArray1::from_vec(py, values))
715 }
716 fn __eq__(&self, value: f64) -> PyVariableExpression {
717 PyVariableExpression(self.0.eq(value))
718 }
719 fn __lt__(&self, value: f64) -> PyVariableExpression {
720 PyVariableExpression(self.0.lt(value))
721 }
722 fn __gt__(&self, value: f64) -> PyVariableExpression {
723 PyVariableExpression(self.0.gt(value))
724 }
725 fn __le__(&self, value: f64) -> PyVariableExpression {
726 PyVariableExpression(self.0.le(value))
727 }
728 fn __ge__(&self, value: f64) -> PyVariableExpression {
729 PyVariableExpression(self.0.ge(value))
730 }
731 fn __repr__(&self) -> String {
732 format!("{:?}", self.0)
733 }
734 fn __str__(&self) -> String {
735 format!("{}", self.0)
736 }
737}
738
739#[pyclass(name = "Angles", module = "laddu", skip_from_py_object)]
765#[derive(Clone)]
766pub struct PyAngles(pub Angles);
767#[pymethods]
768impl PyAngles {
769 #[getter]
776 fn costheta(&self) -> PyCosTheta {
777 PyCosTheta(self.0.costheta.clone())
778 }
779 #[getter]
786 fn phi(&self) -> PyPhi {
787 PyPhi(self.0.phi.clone())
788 }
789 fn __repr__(&self) -> String {
790 format!("{:?}", self.0)
791 }
792 fn __str__(&self) -> String {
793 format!("{}", self.0)
794 }
795}
796
797#[pyclass(name = "PolAngle", module = "laddu", from_py_object)]
810#[derive(Clone, Serialize, Deserialize)]
811pub struct PyPolAngle(pub PolAngle);
812
813#[pymethods]
814impl PyPolAngle {
815 #[new]
816 fn new(reaction: PyReaction, pol_angle: String) -> Self {
817 Self(PolAngle::new(reaction.0.clone(), pol_angle))
818 }
819 fn value(&self, event: &PyEvent) -> PyResult<f64> {
832 let metadata = event
833 .metadata_opt()
834 .ok_or_else(|| PyValueError::new_err(
835 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
836 ))?;
837 let mut variable = self.0.clone();
838 variable.bind(metadata).map_err(PyErr::from)?;
839 let dataset =
840 Dataset::new_with_metadata(vec![event.event.data_arc()], event.event.metadata_arc());
841 let event_view = dataset.event_view(0);
842 Ok(variable.value(&event_view))
843 }
844 fn value_on<'py>(
857 &self,
858 py: Python<'py>,
859 dataset: &PyDataset,
860 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
861 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
862 Ok(PyArray1::from_vec(py, values))
863 }
864 fn __eq__(&self, value: f64) -> PyVariableExpression {
865 PyVariableExpression(self.0.eq(value))
866 }
867 fn __lt__(&self, value: f64) -> PyVariableExpression {
868 PyVariableExpression(self.0.lt(value))
869 }
870 fn __gt__(&self, value: f64) -> PyVariableExpression {
871 PyVariableExpression(self.0.gt(value))
872 }
873 fn __le__(&self, value: f64) -> PyVariableExpression {
874 PyVariableExpression(self.0.le(value))
875 }
876 fn __ge__(&self, value: f64) -> PyVariableExpression {
877 PyVariableExpression(self.0.ge(value))
878 }
879 fn __repr__(&self) -> String {
880 format!("{:?}", self.0)
881 }
882 fn __str__(&self) -> String {
883 format!("{}", self.0)
884 }
885}
886
887#[pyclass(name = "PolMagnitude", module = "laddu", from_py_object)]
902#[derive(Clone, Serialize, Deserialize)]
903pub struct PyPolMagnitude(pub PolMagnitude);
904
905#[pymethods]
906impl PyPolMagnitude {
907 #[new]
908 fn new(pol_magnitude: String) -> Self {
909 Self(PolMagnitude::new(pol_magnitude))
910 }
911 fn value(&self, event: &PyEvent) -> PyResult<f64> {
924 let metadata = event
925 .metadata_opt()
926 .ok_or_else(|| PyValueError::new_err(
927 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
928 ))?;
929 let mut variable = self.0.clone();
930 variable.bind(metadata).map_err(PyErr::from)?;
931 let dataset =
932 Dataset::new_with_metadata(vec![event.event.data_arc()], event.event.metadata_arc());
933 let event_view = dataset.event_view(0);
934 Ok(variable.value(&event_view))
935 }
936 fn value_on<'py>(
949 &self,
950 py: Python<'py>,
951 dataset: &PyDataset,
952 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
953 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
954 Ok(PyArray1::from_vec(py, values))
955 }
956 fn __eq__(&self, value: f64) -> PyVariableExpression {
957 PyVariableExpression(self.0.eq(value))
958 }
959 fn __lt__(&self, value: f64) -> PyVariableExpression {
960 PyVariableExpression(self.0.lt(value))
961 }
962 fn __gt__(&self, value: f64) -> PyVariableExpression {
963 PyVariableExpression(self.0.gt(value))
964 }
965 fn __le__(&self, value: f64) -> PyVariableExpression {
966 PyVariableExpression(self.0.le(value))
967 }
968 fn __ge__(&self, value: f64) -> PyVariableExpression {
969 PyVariableExpression(self.0.ge(value))
970 }
971 fn __repr__(&self) -> String {
972 format!("{:?}", self.0)
973 }
974 fn __str__(&self) -> String {
975 format!("{}", self.0)
976 }
977}
978
979#[pyclass(name = "Polarization", module = "laddu", skip_from_py_object)]
999#[derive(Clone)]
1000pub struct PyPolarization(pub Polarization);
1001#[pymethods]
1002impl PyPolarization {
1003 #[new]
1004 #[pyo3(signature=(reaction, *, pol_magnitude, pol_angle))]
1005 fn new(reaction: PyReaction, pol_magnitude: String, pol_angle: String) -> PyResult<Self> {
1006 if pol_magnitude == pol_angle {
1007 return Err(PyValueError::new_err(
1008 "`pol_magnitude` and `pol_angle` must reference distinct auxiliary columns",
1009 ));
1010 }
1011 let polarization = Polarization::new(reaction.0.clone(), pol_magnitude, pol_angle);
1012 Ok(PyPolarization(polarization))
1013 }
1014 #[getter]
1021 fn pol_magnitude(&self) -> PyPolMagnitude {
1022 PyPolMagnitude(self.0.pol_magnitude.clone())
1023 }
1024 #[getter]
1031 fn pol_angle(&self) -> PyPolAngle {
1032 PyPolAngle(self.0.pol_angle.clone())
1033 }
1034 fn __repr__(&self) -> String {
1035 format!("{:?}", self.0)
1036 }
1037 fn __str__(&self) -> String {
1038 format!("{}", self.0)
1039 }
1040}
1041
1042#[pyclass(name = "Mandelstam", module = "laddu", from_py_object)]
1071#[derive(Clone, Serialize, Deserialize)]
1072pub struct PyMandelstam(pub Mandelstam);
1073
1074#[pymethods]
1075impl PyMandelstam {
1076 #[new]
1077 fn new(reaction: PyReaction, channel: &str) -> PyResult<Self> {
1078 Ok(Self(Mandelstam::new(reaction.0.clone(), channel.parse()?)))
1079 }
1080 fn value(&self, event: &PyEvent) -> PyResult<f64> {
1093 let metadata = event
1094 .metadata_opt()
1095 .ok_or_else(|| PyValueError::new_err(
1096 "This event is not associated with metadata; supply `p4_names`/`aux_names` when constructing it or evaluate via a Dataset.",
1097 ))?;
1098 let mut variable = self.0.clone();
1099 variable.bind(metadata).map_err(PyErr::from)?;
1100 let dataset =
1101 Dataset::new_with_metadata(vec![event.event.data_arc()], event.event.metadata_arc());
1102 let event_view = dataset.event_view(0);
1103 Ok(variable.value(&event_view))
1104 }
1105 fn value_on<'py>(
1118 &self,
1119 py: Python<'py>,
1120 dataset: &PyDataset,
1121 ) -> PyResult<Bound<'py, PyArray1<f64>>> {
1122 let values = self.0.value_on(&dataset.0).map_err(PyErr::from)?;
1123 Ok(PyArray1::from_vec(py, values))
1124 }
1125 fn __eq__(&self, value: f64) -> PyVariableExpression {
1126 PyVariableExpression(self.0.eq(value))
1127 }
1128 fn __lt__(&self, value: f64) -> PyVariableExpression {
1129 PyVariableExpression(self.0.lt(value))
1130 }
1131 fn __gt__(&self, value: f64) -> PyVariableExpression {
1132 PyVariableExpression(self.0.gt(value))
1133 }
1134 fn __le__(&self, value: f64) -> PyVariableExpression {
1135 PyVariableExpression(self.0.le(value))
1136 }
1137 fn __ge__(&self, value: f64) -> PyVariableExpression {
1138 PyVariableExpression(self.0.ge(value))
1139 }
1140 fn __repr__(&self) -> String {
1141 format!("{:?}", self.0)
1142 }
1143 fn __str__(&self) -> String {
1144 format!("{}", self.0)
1145 }
1146}
1147
1148#[typetag::serde]
1149impl Variable for PyVariable {
1150 fn bind(&mut self, metadata: &DatasetMetadata) -> LadduResult<()> {
1151 match self {
1152 PyVariable::Mass(mass) => mass.0.bind(metadata),
1153 PyVariable::CosTheta(cos_theta) => cos_theta.0.bind(metadata),
1154 PyVariable::Phi(phi) => phi.0.bind(metadata),
1155 PyVariable::PolAngle(pol_angle) => pol_angle.0.bind(metadata),
1156 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.bind(metadata),
1157 PyVariable::Mandelstam(mandelstam) => mandelstam.0.bind(metadata),
1158 }
1159 }
1160
1161 fn value_on(&self, dataset: &Dataset) -> LadduResult<Vec<f64>> {
1162 match self {
1163 PyVariable::Mass(mass) => mass.0.value_on(dataset),
1164 PyVariable::CosTheta(cos_theta) => cos_theta.0.value_on(dataset),
1165 PyVariable::Phi(phi) => phi.0.value_on(dataset),
1166 PyVariable::PolAngle(pol_angle) => pol_angle.0.value_on(dataset),
1167 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value_on(dataset),
1168 PyVariable::Mandelstam(mandelstam) => mandelstam.0.value_on(dataset),
1169 }
1170 }
1171
1172 fn value(&self, event: &NamedEventView<'_>) -> f64 {
1173 match self {
1174 PyVariable::Mass(mass) => mass.0.value(event),
1175 PyVariable::CosTheta(cos_theta) => cos_theta.0.value(event),
1176 PyVariable::Phi(phi) => phi.0.value(event),
1177 PyVariable::PolAngle(pol_angle) => pol_angle.0.value(event),
1178 PyVariable::PolMagnitude(pol_magnitude) => pol_magnitude.0.value(event),
1179 PyVariable::Mandelstam(mandelstam) => mandelstam.0.value(event),
1180 }
1181 }
1182}