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