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}