laddu_python/utils/
vectors.rs

1use laddu_core::{Float, Vec3, Vec4};
2use numpy::PyArray1;
3use pyo3::{exceptions::PyTypeError, prelude::*};
4
5/// A 3-momentum vector formed from Cartesian components.
6///
7/// Parameters
8/// ----------
9/// px, py, pz : float
10///     The Cartesian components of the 3-vector
11///
12#[pyclass(name = "Vec3", module = "laddu")]
13#[derive(Clone)]
14pub struct PyVec3(pub Vec3);
15#[pymethods]
16impl PyVec3 {
17    #[new]
18    fn new(px: Float, py: Float, pz: Float) -> Self {
19        Self(Vec3::new(px, py, pz))
20    }
21    fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
22        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
23            Ok(Self(self.0 + other_vec.0))
24        } else if let Ok(other_int) = other.extract::<usize>() {
25            if other_int == 0 {
26                Ok(self.clone())
27            } else {
28                Err(PyTypeError::new_err(
29                    "Addition with an integer for this type is only defined for 0",
30                ))
31            }
32        } else {
33            Err(PyTypeError::new_err("Unsupported operand type for +"))
34        }
35    }
36    fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
37        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
38            Ok(Self(other_vec.0 + self.0))
39        } else if let Ok(other_int) = other.extract::<usize>() {
40            if other_int == 0 {
41                Ok(self.clone())
42            } else {
43                Err(PyTypeError::new_err(
44                    "Addition with an integer for this type is only defined for 0",
45                ))
46            }
47        } else {
48            Err(PyTypeError::new_err("Unsupported operand type for +"))
49        }
50    }
51    fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
52        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
53            Ok(Self(self.0 - other_vec.0))
54        } else if let Ok(other_int) = other.extract::<usize>() {
55            if other_int == 0 {
56                Ok(self.clone())
57            } else {
58                Err(PyTypeError::new_err(
59                    "Subtraction with an integer for this type is only defined for 0",
60                ))
61            }
62        } else {
63            Err(PyTypeError::new_err("Unsupported operand type for -"))
64        }
65    }
66    fn __rsub__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
67        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
68            Ok(Self(other_vec.0 - self.0))
69        } else if let Ok(other_int) = other.extract::<usize>() {
70            if other_int == 0 {
71                Ok(self.clone())
72            } else {
73                Err(PyTypeError::new_err(
74                    "Subtraction with an integer for this type is only defined for 0",
75                ))
76            }
77        } else {
78            Err(PyTypeError::new_err("Unsupported operand type for -"))
79        }
80    }
81    fn __mul__(&self, other: Float) -> Self {
82        Self(self.0 * other)
83    }
84    fn __rmul__(&self, other: Float) -> Self {
85        Self(self.0 * other)
86    }
87    fn __neg__(&self) -> PyResult<Self> {
88        Ok(Self(-self.0))
89    }
90    /// Calculate the dot product of two Vec3s.
91    ///
92    /// Parameters
93    /// ----------
94    /// other : Vec3
95    ///     A vector input with which the dot product is taken
96    ///
97    /// Returns
98    /// -------
99    /// float
100    ///     The dot product of this vector and `other`
101    ///
102    pub fn dot(&self, other: Self) -> Float {
103        self.0.dot(&other.0)
104    }
105    /// Calculate the cross product of two Vec3s.
106    ///
107    /// Parameters
108    /// ----------
109    /// other : Vec3
110    ///     A vector input with which the cross product is taken
111    ///
112    /// Returns
113    /// -------
114    /// Vec3
115    ///     The cross product of this vector and `other`
116    ///
117    fn cross(&self, other: Self) -> Self {
118        Self(self.0.cross(&other.0))
119    }
120    /// The magnitude of the 3-vector
121    ///
122    /// .. math:: |\vec{p}| = \sqrt{p_x^2 + p_y^2 + p_z^2}
123    ///
124    #[getter]
125    fn mag(&self) -> Float {
126        self.0.mag()
127    }
128    /// The squared magnitude of the 3-vector
129    ///
130    /// .. math:: |\vec{p}|^2 = p_x^2 + p_y^2 + p_z^2
131    ///
132    #[getter]
133    fn mag2(&self) -> Float {
134        self.0.mag2()
135    }
136    /// The cosine of the polar angle of this vector in spherical coordinates
137    ///
138    /// .. math:: -1 \leq \cos\theta \leq +1
139    ///
140    /// .. math:: \cos\theta = \frac{p_z}{|\vec{p}|}
141    ///
142    #[getter]
143    fn costheta(&self) -> Float {
144        self.0.costheta()
145    }
146    /// The polar angle of this vector in spherical coordinates
147    ///
148    /// .. math:: 0 \leq \theta \leq \pi
149    ///
150    /// .. math:: \theta = \arccos\left(\frac{p_z}{|\vec{p}|}\right)
151    ///
152    #[getter]
153    fn theta(&self) -> Float {
154        self.0.theta()
155    }
156    /// The azimuthal angle of this vector in spherical coordinates
157    ///
158    /// .. math:: 0 \leq \varphi \leq 2\pi
159    ///
160    /// .. math:: \varphi = \text{sgn}(p_y)\arccos\left(\frac{p_x}{\sqrt{p_x^2 + p_y^2}}\right)
161    ///
162    #[getter]
163    fn phi(&self) -> Float {
164        self.0.phi()
165    }
166    /// The normalized unit vector pointing in the direction of this vector
167    ///
168    #[getter]
169    fn unit(&self) -> Self {
170        Self(self.0.unit())
171    }
172    /// The x-component of this vector
173    ///
174    /// See Also
175    /// --------
176    /// Vec3.x
177    ///
178    #[getter]
179    fn px(&self) -> Float {
180        self.0.px()
181    }
182    /// The x-component of this vector
183    ///
184    /// See Also
185    /// --------
186    /// Vec3.px
187    ///
188    #[getter]
189    fn x(&self) -> Float {
190        self.0.x
191    }
192
193    /// The y-component of this vector
194    ///
195    /// See Also
196    /// --------
197    /// Vec3.y
198    ///
199    #[getter]
200    fn py(&self) -> Float {
201        self.0.py()
202    }
203    /// The y-component of this vector
204    ///
205    /// See Also
206    /// --------
207    /// Vec3.py
208    ///
209    #[getter]
210    fn y(&self) -> Float {
211        self.0.y
212    }
213    /// The z-component of this vector
214    ///
215    /// See Also
216    /// --------
217    /// Vec3.z
218    ///
219    #[getter]
220    fn pz(&self) -> Float {
221        self.0.pz()
222    }
223    /// The z-component of this vector
224    ///
225    /// See Also
226    /// --------
227    /// Vec3.pz
228    ///
229    #[getter]
230    fn z(&self) -> Float {
231        self.0.z
232    }
233    /// Convert a 3-vector momentum to a 4-momentum with the given mass.
234    ///
235    /// The mass-energy equivalence is used to compute the energy of the 4-momentum:
236    ///
237    /// .. math:: E = \sqrt{m^2 + p^2}
238    ///
239    /// Parameters
240    /// ----------
241    /// mass: float
242    ///     The mass of the new 4-momentum
243    ///
244    /// Returns
245    /// -------
246    /// Vec4
247    ///     A new 4-momentum with the given mass
248    ///
249    fn with_mass(&self, mass: Float) -> PyVec4 {
250        PyVec4(self.0.with_mass(mass))
251    }
252    /// Convert a 3-vector momentum to a 4-momentum with the given energy.
253    ///
254    /// Parameters
255    /// ----------
256    /// energy: float
257    ///     The mass of the new 4-momentum
258    ///
259    /// Returns
260    /// -------
261    /// Vec4
262    ///     A new 4-momentum with the given energy
263    ///
264    fn with_energy(&self, mass: Float) -> PyVec4 {
265        PyVec4(self.0.with_energy(mass))
266    }
267    /// Convert the 3-vector to a ``numpy`` array.
268    ///
269    /// Returns
270    /// -------
271    /// numpy_vec: array_like
272    ///     A ``numpy`` array built from the components of this ``Vec3``
273    ///
274    fn to_numpy<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1<Float>> {
275        PyArray1::from_vec(py, self.0.into())
276    }
277    /// Convert an  array into a 3-vector.
278    ///
279    /// Parameters
280    /// ----------
281    /// array_like
282    ///     An array containing the components of this ``Vec3``
283    ///
284    /// Returns
285    /// -------
286    /// laddu_vec: Vec3
287    ///     A copy of the input array as a ``laddu`` vector
288    ///
289    #[staticmethod]
290    fn from_array(array: Vec<Float>) -> Self {
291        Self::new(array[0], array[1], array[2])
292    }
293    fn __repr__(&self) -> String {
294        format!("{:?}", self.0)
295    }
296    fn __str__(&self) -> String {
297        format!("{}", self.0)
298    }
299}
300
301/// A 4-momentum vector formed from energy and Cartesian 3-momentum components.
302///
303/// This vector is ordered with energy as the fourth component (:math:`[p_x, p_y, p_z, E]`) and assumes a :math:`(---+)`
304/// signature
305///
306/// Parameters
307/// ----------
308/// px, py, pz : float
309///     The Cartesian components of the 3-vector
310/// e : float
311///     The energy component
312///
313///
314#[pyclass(name = "Vec4", module = "laddu")]
315#[derive(Clone)]
316pub struct PyVec4(pub Vec4);
317#[pymethods]
318impl PyVec4 {
319    #[new]
320    fn new(px: Float, py: Float, pz: Float, e: Float) -> Self {
321        Self(Vec4::new(px, py, pz, e))
322    }
323    fn __add__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
324        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
325            Ok(Self(self.0 + other_vec.0))
326        } else if let Ok(other_int) = other.extract::<usize>() {
327            if other_int == 0 {
328                Ok(self.clone())
329            } else {
330                Err(PyTypeError::new_err(
331                    "Addition with an integer for this type is only defined for 0",
332                ))
333            }
334        } else {
335            Err(PyTypeError::new_err("Unsupported operand type for +"))
336        }
337    }
338    fn __radd__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
339        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
340            Ok(Self(other_vec.0 + self.0))
341        } else if let Ok(other_int) = other.extract::<usize>() {
342            if other_int == 0 {
343                Ok(self.clone())
344            } else {
345                Err(PyTypeError::new_err(
346                    "Addition with an integer for this type is only defined for 0",
347                ))
348            }
349        } else {
350            Err(PyTypeError::new_err("Unsupported operand type for +"))
351        }
352    }
353    fn __sub__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
354        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
355            Ok(Self(self.0 - other_vec.0))
356        } else if let Ok(other_int) = other.extract::<usize>() {
357            if other_int == 0 {
358                Ok(self.clone())
359            } else {
360                Err(PyTypeError::new_err(
361                    "Subtraction with an integer for this type is only defined for 0",
362                ))
363            }
364        } else {
365            Err(PyTypeError::new_err("Unsupported operand type for -"))
366        }
367    }
368    fn __rsub__(&self, other: &Bound<'_, PyAny>) -> PyResult<Self> {
369        if let Ok(other_vec) = other.extract::<PyRef<Self>>() {
370            Ok(Self(other_vec.0 - self.0))
371        } else if let Ok(other_int) = other.extract::<usize>() {
372            if other_int == 0 {
373                Ok(self.clone())
374            } else {
375                Err(PyTypeError::new_err(
376                    "Subtraction with an integer for this type is only defined for 0",
377                ))
378            }
379        } else {
380            Err(PyTypeError::new_err("Unsupported operand type for -"))
381        }
382    }
383    fn __mul__(&self, other: Float) -> Self {
384        Self(self.0 * other)
385    }
386    fn __rmul__(&self, other: Float) -> Self {
387        Self(self.0 * other)
388    }
389    fn __neg__(&self) -> PyResult<Self> {
390        Ok(Self(-self.0))
391    }
392    /// The magnitude of the 4-vector
393    ///
394    /// .. math:: |p| = \sqrt{E^2 - (p_x^2 + p_y^2 + p_z^2)}
395    ///
396    /// See Also
397    /// --------
398    /// Vec4.m
399    ///
400    #[getter]
401    fn mag(&self) -> Float {
402        self.0.mag()
403    }
404    /// The squared magnitude of the 4-vector
405    ///
406    /// .. math:: |p|^2 = E^2 - (p_x^2 + p_y^2 + p_z^2)
407    ///
408    /// See Also
409    /// --------
410    /// Vec4.m2
411    ///
412    #[getter]
413    fn mag2(&self) -> Float {
414        self.0.mag2()
415    }
416    /// The 3-vector part of this 4-vector
417    ///
418    /// See Also
419    /// --------
420    /// Vec4.momentum
421    ///
422    #[getter]
423    fn vec3(&self) -> PyVec3 {
424        PyVec3(self.0.vec3())
425    }
426    /// Boost the given 4-momentum according to a boost velocity.
427    ///
428    /// The resulting 4-momentum is equal to the original boosted to an inertial frame with
429    /// relative velocity :math:`\beta`:
430    ///
431    /// .. math:: \left[\vec{p}'; E'\right] = \left[ \vec{p} + \left(\frac{(\gamma - 1) \vec{p}\cdot\vec{\beta}}{\beta^2} + \gamma E\right)\vec{\beta}; \gamma E + \vec{\beta}\cdot\vec{p} \right]
432    ///
433    /// Parameters
434    /// ----------
435    /// beta : Vec3
436    ///     The relative velocity needed to get to the new frame from the current one
437    ///
438    /// Returns
439    /// -------
440    /// Vec4
441    ///     The boosted 4-momentum
442    ///
443    /// See Also
444    /// --------
445    /// Vec4.beta
446    /// Vec4.gamma
447    ///
448    fn boost(&self, beta: &PyVec3) -> Self {
449        Self(self.0.boost(&beta.0))
450    }
451    /// The energy associated with this vector
452    ///
453    #[getter]
454    fn e(&self) -> Float {
455        self.0.e()
456    }
457    /// The t-component of this vector
458    ///
459    #[getter]
460    fn t(&self) -> Float {
461        self.0.t
462    }
463    /// The x-component of this vector
464    ///
465    #[getter]
466    fn px(&self) -> Float {
467        self.0.px()
468    }
469    /// The x-component of this vector
470    ///
471    #[getter]
472    fn x(&self) -> Float {
473        self.0.x
474    }
475
476    /// The y-component of this vector
477    ///
478    #[getter]
479    fn py(&self) -> Float {
480        self.0.py()
481    }
482    /// The y-component of this vector
483    ///
484    #[getter]
485    fn y(&self) -> Float {
486        self.0.y
487    }
488    /// The z-component of this vector
489    ///
490    #[getter]
491    fn pz(&self) -> Float {
492        self.0.pz()
493    }
494    /// The z-component of this vector
495    ///
496    #[getter]
497    fn z(&self) -> Float {
498        self.0.z
499    }
500    /// The 3-momentum part of this 4-momentum
501    ///
502    #[getter]
503    fn momentum(&self) -> PyVec3 {
504        PyVec3(self.0.momentum())
505    }
506    /// The relativistic gamma factor
507    ///
508    /// .. math:: \gamma = \frac{1}{\sqrt{1 - \beta^2}}
509    ///
510    /// See Also
511    /// --------
512    /// Vec4.beta
513    /// Vec4.boost
514    ///
515    #[getter]
516    fn gamma(&self) -> Float {
517        self.0.gamma()
518    }
519    /// The velocity 3-vector
520    ///
521    /// .. math:: \vec{\beta} = \frac{\vec{p}}{E}
522    ///
523    /// See Also
524    /// --------
525    /// Vec4.gamma
526    /// Vec4.boost
527    ///
528    #[getter]
529    fn beta(&self) -> PyVec3 {
530        PyVec3(self.0.beta())
531    }
532    /// The invariant mass associated with the four-momentum
533    ///
534    /// .. math:: m = \sqrt{E^2 - (p_x^2 + p_y^2 + p_z^2)}
535    ///
536    /// See Also
537    /// --------
538    /// Vec4.mag
539    ///
540    #[getter]
541    fn m(&self) -> Float {
542        self.0.m()
543    }
544    /// The square of the invariant mass associated with the four-momentum
545    ///
546    /// .. math:: m^2 = E^2 - (p_x^2 + p_y^2 + p_z^2)
547    ///
548    /// See Also
549    /// --------
550    /// Vec4.mag2
551    ///
552    #[getter]
553    fn m2(&self) -> Float {
554        self.0.m2()
555    }
556    /// Convert the 4-vector to a `numpy` array.
557    ///
558    /// Returns
559    /// -------
560    /// numpy_vec: array_like
561    ///     A ``numpy`` array built from the components of this ``Vec4``
562    ///
563    fn to_numpy<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray1<Float>> {
564        PyArray1::from_vec(py, self.0.into())
565    }
566    /// Convert an array into a 4-vector.
567    ///
568    /// Parameters
569    /// ----------
570    /// array_like
571    ///     An array containing the components of this ``Vec4``
572    ///
573    /// Returns
574    /// -------
575    /// laddu_vec: Vec4
576    ///     A copy of the input array as a ``laddu`` vector
577    ///
578    #[staticmethod]
579    fn from_array(array: Vec<Float>) -> Self {
580        Self::new(array[0], array[1], array[2], array[3])
581    }
582    fn __str__(&self) -> String {
583        format!("{}", self.0)
584    }
585    fn __repr__(&self) -> String {
586        self.0.to_p4_string()
587    }
588}