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}