1use std::{
2 fmt::Display,
3 ops::{Add, Div, Mul, Sub},
4};
5
6use approx::{AbsDiffEq, RelativeEq};
7use auto_ops::{impl_op_ex, impl_op_ex_commutative};
8use laddu_expr::{Expr, P4Component, atan2, event_p4_component, vector};
9use nalgebra::{Vector3, Vector4};
10use serde::{Deserialize, Serialize};
11
12use crate::{LadduPhysicsError, LadduPhysicsResult};
13
14fn dot3<'a, T>(lhs: [&'a T; 3], rhs: [&'a T; 3]) -> T
15where
16 &'a T: Mul<&'a T, Output = T>,
17 T: Add<T, Output = T>,
18{
19 lhs[0] * rhs[0] + lhs[1] * rhs[1] + lhs[2] * rhs[2]
20}
21
22fn cross3<'a, T>(lhs: [&'a T; 3], rhs: [&'a T; 3]) -> [T; 3]
23where
24 &'a T: Mul<&'a T, Output = T>,
25 T: Sub<T, Output = T>,
26{
27 [
28 lhs[1] * rhs[2] - rhs[1] * lhs[2],
29 lhs[2] * rhs[0] - rhs[2] * lhs[0],
30 lhs[0] * rhs[1] - rhs[0] * lhs[1],
31 ]
32}
33
34fn lorentz_dot4<'a, T>(lhs: [&'a T; 4], rhs: [&'a T; 4]) -> T
35where
36 &'a T: Mul<&'a T, Output = T>,
37 T: Sub<T, Output = T>,
38{
39 lhs[0] * rhs[0] - lhs[1] * rhs[1] - lhs[2] * rhs[2] - lhs[3] * rhs[3]
40}
41
42fn boost_factor<'a, T>(gamma: &'a T, square: impl FnOnce(&T) -> T) -> T
43where
44 &'a T: Add<f64, Output = T>,
45 T: Div<T, Output = T>,
46{
47 square(gamma) / (gamma + 1.0)
48}
49
50#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
60pub struct RealVec3 {
61 pub x: f64,
63 pub y: f64,
65 pub z: f64,
67}
68
69impl Display for RealVec3 {
70 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71 write!(f, "[{:6.3}, {:6.3}, {:6.3}]", self.x, self.y, self.z)
72 }
73}
74
75impl AbsDiffEq for RealVec3 {
76 type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
77
78 fn default_epsilon() -> Self::Epsilon {
79 f64::default_epsilon()
80 }
81
82 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
83 f64::abs_diff_eq(&self.x, &other.x, epsilon)
84 && f64::abs_diff_eq(&self.y, &other.y, epsilon)
85 && f64::abs_diff_eq(&self.z, &other.z, epsilon)
86 }
87}
88impl RelativeEq for RealVec3 {
89 fn default_max_relative() -> Self::Epsilon {
90 f64::default_max_relative()
91 }
92
93 fn relative_eq(
94 &self,
95 other: &Self,
96 epsilon: Self::Epsilon,
97 max_relative: Self::Epsilon,
98 ) -> bool {
99 f64::relative_eq(&self.x, &other.x, epsilon, max_relative)
100 && f64::relative_eq(&self.y, &other.y, epsilon, max_relative)
101 && f64::relative_eq(&self.z, &other.z, epsilon, max_relative)
102 }
103}
104
105impl From<RealVec3> for Vector3<f64> {
106 fn from(value: RealVec3) -> Self {
107 Vector3::new(value.x, value.y, value.z)
108 }
109}
110
111impl From<Vector3<f64>> for RealVec3 {
112 fn from(value: Vector3<f64>) -> Self {
113 RealVec3::new(value.x, value.y, value.z)
114 }
115}
116
117impl TryFrom<Vec<f64>> for RealVec3 {
118 type Error = LadduPhysicsError;
119
120 fn try_from(value: Vec<f64>) -> Result<Self, Self::Error> {
121 if value.len() != 3 {
122 return Err(LadduPhysicsError::custom(
123 "Attempted to convert Vec<f64> to RealVec3 for Vec with len != 3",
124 ));
125 }
126 Ok(Self {
127 x: value[0],
128 y: value[1],
129 z: value[2],
130 })
131 }
132}
133
134impl From<RealVec3> for Vec<f64> {
135 fn from(value: RealVec3) -> Self {
136 vec![value.x, value.y, value.z]
137 }
138}
139
140impl From<[f64; 3]> for RealVec3 {
141 fn from(value: [f64; 3]) -> Self {
142 Self {
143 x: value[0],
144 y: value[1],
145 z: value[2],
146 }
147 }
148}
149
150impl From<RealVec3> for [f64; 3] {
151 fn from(value: RealVec3) -> Self {
152 [value.x, value.y, value.z]
153 }
154}
155
156impl Default for RealVec3 {
157 fn default() -> Self {
158 RealVec3::zero()
159 }
160}
161
162impl RealVec3 {
163 pub fn new(x: f64, y: f64, z: f64) -> Self {
165 RealVec3 { x, y, z }
166 }
167
168 pub const fn zero() -> Self {
170 RealVec3 {
171 x: 0.0,
172 y: 0.0,
173 z: 0.0,
174 }
175 }
176
177 pub const fn x() -> Self {
179 RealVec3 {
180 x: 1.0,
181 y: 0.0,
182 z: 0.0,
183 }
184 }
185
186 pub const fn y() -> Self {
188 RealVec3 {
189 x: 0.0,
190 y: 1.0,
191 z: 0.0,
192 }
193 }
194
195 pub const fn z() -> Self {
197 RealVec3 {
198 x: 0.0,
199 y: 0.0,
200 z: 1.0,
201 }
202 }
203
204 pub fn px(&self) -> f64 {
206 self.x
207 }
208
209 pub fn py(&self) -> f64 {
211 self.y
212 }
213
214 pub fn pz(&self) -> f64 {
216 self.z
217 }
218
219 pub fn with_mass(&self, mass: f64) -> RealVec4 {
221 let e = f64::sqrt(mass.powi(2) + self.mag2());
222 RealVec4::new(e, self.px(), self.py(), self.pz())
223 }
224
225 pub fn with_energy(&self, energy: f64) -> RealVec4 {
227 RealVec4::new(energy, self.px(), self.py(), self.pz())
228 }
229
230 pub fn dot(&self, other: &RealVec3) -> f64 {
232 dot3([&self.x, &self.y, &self.z], [&other.x, &other.y, &other.z])
233 }
234
235 pub fn cross(&self, other: &RealVec3) -> RealVec3 {
237 cross3([&self.x, &self.y, &self.z], [&other.x, &other.y, &other.z]).into()
238 }
239
240 pub fn mag(&self) -> f64 {
242 f64::sqrt(self.mag2())
243 }
244
245 pub fn mag2(&self) -> f64 {
247 self.dot(self)
248 }
249
250 pub fn costheta(&self) -> LadduPhysicsResult<f64> {
257 let mag = self.mag();
258 if mag <= 0.0 {
259 return Err(LadduPhysicsError::invalid_value(
260 "vector magnitude",
261 "positive when calculating cos(theta)",
262 mag,
263 ));
264 }
265 Ok(self.z / self.mag())
266 }
267
268 pub fn theta(&self) -> LadduPhysicsResult<f64> {
275 Ok(f64::acos(self.costheta()?))
276 }
277
278 pub fn phi(&self) -> f64 {
280 f64::atan2(self.y, self.x)
281 }
282
283 pub fn unit(&self) -> LadduPhysicsResult<RealVec3> {
290 let mag = self.mag();
291 if mag <= 0.0 {
292 return Err(LadduPhysicsError::invalid_value(
293 "vector magnitude",
294 "positive when constructing unit vector",
295 mag,
296 ));
297 }
298 Ok(RealVec3::new(self.x / mag, self.y / mag, self.z / mag))
299 }
300}
301
302impl<'a> std::iter::Sum<&'a RealVec3> for RealVec3 {
303 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
304 iter.fold(Self::zero(), |a, b| a + b)
305 }
306}
307impl std::iter::Sum<RealVec3> for RealVec3 {
308 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
309 iter.fold(Self::zero(), |a, b| a + b)
310 }
311}
312
313impl_op_ex!(+ |a: &RealVec3, b: &RealVec3| -> RealVec3 { RealVec3::new(a.x + b.x, a.y + b.y, a.z + b.z) });
314impl_op_ex!(-|a: &RealVec3, b: &RealVec3| -> RealVec3 {
315 RealVec3::new(a.x - b.x, a.y - b.y, a.z - b.z)
316});
317impl_op_ex!(-|a: &RealVec3| -> RealVec3 { RealVec3::new(-a.x, -a.y, -a.z) });
318impl_op_ex_commutative!(+ |a: &RealVec3, b: &f64| -> RealVec3 { RealVec3::new(a.x + b, a.y + b, a.z + b) });
319impl_op_ex_commutative!(-|a: &RealVec3, b: &f64| -> RealVec3 {
320 RealVec3::new(a.x - b, a.y - b, a.z - b)
321});
322impl_op_ex_commutative!(*|a: &RealVec3, b: &f64| -> RealVec3 {
323 RealVec3::new(a.x * b, a.y * b, a.z * b)
324});
325impl_op_ex!(/ |a: &RealVec3, b: &f64| -> RealVec3 { RealVec3::new(a.x / b, a.y / b, a.z / b) });
326
327#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
338pub struct RealVec4 {
339 pub e: f64,
341 pub px: f64,
343 pub py: f64,
345 pub pz: f64,
347}
348
349impl Display for RealVec4 {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351 write!(
352 f,
353 "[{:6.3}; {:6.3}, {:6.3}, {:6.3}]",
354 self.e, self.px, self.py, self.pz
355 )
356 }
357}
358
359impl AbsDiffEq for RealVec4 {
360 type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
361
362 fn default_epsilon() -> Self::Epsilon {
363 f64::default_epsilon()
364 }
365
366 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
367 f64::abs_diff_eq(&self.e, &other.e, epsilon)
368 && f64::abs_diff_eq(&self.px, &other.px, epsilon)
369 && f64::abs_diff_eq(&self.py, &other.py, epsilon)
370 && f64::abs_diff_eq(&self.pz, &other.pz, epsilon)
371 }
372}
373impl RelativeEq for RealVec4 {
374 fn default_max_relative() -> Self::Epsilon {
375 f64::default_max_relative()
376 }
377
378 fn relative_eq(
379 &self,
380 other: &Self,
381 epsilon: Self::Epsilon,
382 max_relative: Self::Epsilon,
383 ) -> bool {
384 f64::relative_eq(&self.e, &other.e, epsilon, max_relative)
385 && f64::relative_eq(&self.px, &other.px, epsilon, max_relative)
386 && f64::relative_eq(&self.py, &other.py, epsilon, max_relative)
387 && f64::relative_eq(&self.pz, &other.pz, epsilon, max_relative)
388 }
389}
390
391impl From<RealVec4> for Vector4<f64> {
392 fn from(value: RealVec4) -> Self {
393 let [e, px, py, pz] = value.components();
394 Vector4::new(e, px, py, pz)
395 }
396}
397
398impl From<Vector4<f64>> for RealVec4 {
399 fn from(value: Vector4<f64>) -> Self {
400 RealVec4::new(value.x, value.y, value.z, value.w)
401 }
402}
403
404impl TryFrom<Vec<f64>> for RealVec4 {
405 type Error = LadduPhysicsError;
406
407 fn try_from(value: Vec<f64>) -> Result<Self, Self::Error> {
408 if value.len() != 4 {
409 return Err(LadduPhysicsError::custom(
410 "Attempted to convert Vec<f64> to RealVec4 for Vec with len != 4",
411 ));
412 }
413 Ok(Self {
414 e: value[0],
415 px: value[1],
416 py: value[2],
417 pz: value[3],
418 })
419 }
420}
421
422impl From<RealVec4> for Vec<f64> {
423 fn from(value: RealVec4) -> Self {
424 Vec::from(value.components())
425 }
426}
427
428impl From<[f64; 4]> for RealVec4 {
429 fn from(value: [f64; 4]) -> Self {
430 Self {
431 e: value[0],
432 px: value[1],
433 py: value[2],
434 pz: value[3],
435 }
436 }
437}
438
439impl From<RealVec4> for [f64; 4] {
440 fn from(value: RealVec4) -> Self {
441 value.components()
442 }
443}
444
445impl RealVec4 {
446 pub fn new(e: f64, px: f64, py: f64, pz: f64) -> Self {
448 RealVec4 { e, px, py, pz }
449 }
450
451 pub const fn components(&self) -> [f64; 4] {
456 [self.e, self.px, self.py, self.pz]
457 }
458
459 pub fn px(&self) -> f64 {
461 self.px
462 }
463
464 pub fn py(&self) -> f64 {
466 self.py
467 }
468
469 pub fn pz(&self) -> f64 {
471 self.pz
472 }
473
474 pub fn e(&self) -> f64 {
476 self.e
477 }
478
479 pub fn momentum(&self) -> RealVec3 {
481 self.vec3()
482 }
483
484 pub fn gamma(&self) -> LadduPhysicsResult<f64> {
491 let beta = self.beta()?;
492 let b2 = beta.dot(&beta);
493 if b2 >= 1.0 {
494 return Err(LadduPhysicsError::invalid_value("|beta|^2", "< 1", b2));
495 }
496 Ok(1.0 / f64::sqrt(1.0 - b2))
497 }
498
499 pub fn beta(&self) -> LadduPhysicsResult<RealVec3> {
506 let e = self.e();
507 if e <= 0.0 {
508 return Err(LadduPhysicsError::invalid_value(
509 "four-momentum energy",
510 "positive",
511 e,
512 ));
513 }
514 Ok(self.momentum() / e)
515 }
516
517 pub fn m(&self) -> LadduPhysicsResult<f64> {
524 self.mag()
525 }
526
527 #[inline(always)]
528 pub fn m_unchecked(&self) -> f64 {
530 self.m2().sqrt()
531 }
532
533 pub fn signed_m(&self) -> LadduPhysicsResult<f64> {
540 self.signed_mag()
541 }
542
543 #[inline(always)]
544 pub fn signed_m_unchecked(&self) -> f64 {
546 self.signed_mag_unchecked()
547 }
548
549 pub fn m2(&self) -> f64 {
551 self.mag2()
552 }
553
554 pub fn dot(&self, other: &Self) -> f64 {
556 lorentz_dot4(
557 [&self.e, &self.px, &self.py, &self.pz],
558 [&other.e, &other.px, &other.py, &other.pz],
559 )
560 }
561
562 pub fn to_p4_string(&self) -> String {
564 let mass = self
565 .m()
566 .map(|m| format!("{m:.5}"))
567 .unwrap_or_else(|_| format!("{:.5}i", (-self.m2()).sqrt()));
568 format!(
569 "[e = {:.5}; p = ({:.5}, {:.5}, {:.5}); m = {}]",
570 self.e(),
571 self.px(),
572 self.py(),
573 self.pz(),
574 mass
575 )
576 }
577
578 pub fn mag(&self) -> LadduPhysicsResult<f64> {
585 let mag2 = self.mag2();
586
587 if !mag2.is_finite() {
588 return Err(LadduPhysicsError::invalid_value(
589 "magnitude squared",
590 "finite",
591 mag2,
592 ));
593 }
594
595 if mag2 < 0.0 {
596 return Err(LadduPhysicsError::invalid_value(
597 "magnitude squared",
598 "nonnegative",
599 mag2,
600 ));
601 }
602
603 Ok(mag2.sqrt())
604 }
605
606 pub fn signed_mag(&self) -> LadduPhysicsResult<f64> {
616 let mag2 = self.mag2();
617
618 if !mag2.is_finite() {
619 return Err(LadduPhysicsError::invalid_value(
620 "magnitude squared",
621 "finite",
622 mag2,
623 ));
624 }
625
626 if mag2 >= 0.0 {
627 Ok(mag2.sqrt())
628 } else {
629 Ok(-(-mag2).sqrt())
630 }
631 }
632
633 #[inline(always)]
634 pub fn signed_mag_unchecked(&self) -> f64 {
636 let mag2 = self.mag2();
637 if mag2 >= 0.0 {
638 mag2.sqrt()
639 } else {
640 -(-mag2).sqrt()
641 }
642 }
643
644 pub fn mag2(&self) -> f64 {
646 self.e * self.e - (self.px * self.px + self.py * self.py + self.pz * self.pz)
647 }
648
649 pub fn boost(&self, beta: &RealVec3) -> Self {
651 let b2 = beta.dot(beta);
652 if b2 == 0.0 {
653 return *self;
654 }
655 let gamma = 1.0 / f64::sqrt(1.0 - b2);
656 let factor = boost_factor(&gamma, |gamma| gamma * gamma);
657 let p3 = self.vec3() + beta * (factor * self.vec3().dot(beta) + gamma * self.e);
658 RealVec4::new(gamma * (self.e + beta.dot(&self.vec3())), p3.x, p3.y, p3.z)
659 }
660
661 pub fn vec3(&self) -> RealVec3 {
663 RealVec3 {
664 x: self.px,
665 y: self.py,
666 z: self.pz,
667 }
668 }
669}
670
671impl_op_ex!(+ |a: &RealVec4, b: &RealVec4| -> RealVec4 { RealVec4::new(a.e + b.e, a.px + b.px, a.py + b.py, a.pz + b.pz) });
672impl_op_ex!(-|a: &RealVec4, b: &RealVec4| -> RealVec4 {
673 RealVec4::new(a.e - b.e, a.px - b.px, a.py - b.py, a.pz - b.pz)
674});
675impl_op_ex!(-|a: &RealVec4| -> RealVec4 { RealVec4::new(a.e, -a.px, -a.py, -a.pz) });
676
677impl<'a> std::iter::Sum<&'a RealVec4> for RealVec4 {
678 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
679 iter.fold(Self::new(0.0, 0.0, 0.0, 0.0), |a, b| a + b)
680 }
681}
682
683impl std::iter::Sum<RealVec4> for RealVec4 {
684 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
685 iter.fold(Self::new(0.0, 0.0, 0.0, 0.0), |a, b| a + b)
686 }
687}
688
689#[derive(Clone, Debug, Serialize, Deserialize)]
691pub struct Vec3 {
692 pub x: Expr,
694 pub y: Expr,
696 pub z: Expr,
698}
699
700impl Vec3 {
701 pub fn new(x: impl Into<Expr>, y: impl Into<Expr>, z: impl Into<Expr>) -> Self {
703 Self {
704 x: x.into(),
705 y: y.into(),
706 z: z.into(),
707 }
708 }
709
710 pub fn zero() -> Self {
712 Self::new(0.0, 0.0, 0.0)
713 }
714
715 pub fn x() -> Self {
717 Self::new(1.0, 0.0, 0.0)
718 }
719
720 pub fn y() -> Self {
722 Self::new(0.0, 1.0, 0.0)
723 }
724
725 pub fn z() -> Self {
727 Self::new(0.0, 0.0, 1.0)
728 }
729
730 pub fn event(prefix: &str) -> Self {
732 Self::new(
733 event_p4_component(prefix, P4Component::Px),
734 event_p4_component(prefix, P4Component::Py),
735 event_p4_component(prefix, P4Component::Pz),
736 )
737 }
738
739 pub fn px(&self) -> Expr {
741 self.x.clone()
742 }
743
744 pub fn py(&self) -> Expr {
746 self.y.clone()
747 }
748
749 pub fn pz(&self) -> Expr {
751 self.z.clone()
752 }
753
754 pub fn dot(&self, other: &Self) -> Expr {
756 dot3([&self.x, &self.y, &self.z], [&other.x, &other.y, &other.z])
757 }
758
759 pub fn cross(&self, other: &Self) -> Self {
761 let [x, y, z] = cross3([&self.x, &self.y, &self.z], [&other.x, &other.y, &other.z]);
762 Self::new(x, y, z)
763 }
764
765 pub fn mag2(&self) -> Expr {
767 self.dot(self)
768 }
769
770 pub fn mag(&self) -> Expr {
772 self.mag2().sqrt()
773 }
774
775 pub fn costheta(&self) -> Expr {
777 &self.z / self.mag()
778 }
779
780 pub fn unit(&self) -> Self {
782 self / &self.mag()
783 }
784
785 pub fn phi(&self) -> Expr {
787 atan2(self.py(), self.px())
788 }
789
790 pub fn with_mass(&self, mass: impl Into<Expr>) -> Vec4 {
792 let mass = mass.into();
793 Vec4::new(
794 (mass.powi(2) + self.mag2()).sqrt(),
795 self.px(),
796 self.py(),
797 self.pz(),
798 )
799 }
800
801 pub fn with_energy(&self, energy: impl Into<Expr>) -> Vec4 {
803 Vec4::new(energy, self.px(), self.py(), self.pz())
804 }
805
806 pub fn as_expr(&self) -> Expr {
808 vector([self.x.clone(), self.y.clone(), self.z.clone()])
809 }
810
811 fn scale(&self, scalar: impl Into<Expr>) -> Self {
812 let scalar = scalar.into();
813 Self::new(&self.x * &scalar, &self.y * &scalar, &self.z * scalar)
814 }
815}
816
817impl From<RealVec3> for Vec3 {
818 fn from(value: RealVec3) -> Self {
819 Self::new(value.x, value.y, value.z)
820 }
821}
822
823impl Default for Vec3 {
824 fn default() -> Self {
825 Self::zero()
826 }
827}
828
829impl_op_ex!(+ |a: &Vec3, b: &Vec3| -> Vec3 { Vec3::new(&a.x + &b.x, &a.y + &b.y, &a.z + &b.z) });
830impl_op_ex!(-|a: &Vec3, b: &Vec3| -> Vec3 { Vec3::new(&a.x - &b.x, &a.y - &b.y, &a.z - &b.z) });
831impl_op_ex!(-|a: &Vec3| -> Vec3 { Vec3::new(-&a.x, -&a.y, -&a.z) });
832impl_op_ex!(*|a: &Vec3, b: &Expr| -> Vec3 { a.scale(b) });
833impl_op_ex!(*|a: &Expr, b: &Vec3| -> Vec3 { b.scale(a) });
834impl_op_ex!(*|a: &Vec3, b: &f64| -> Vec3 { a.scale(b) });
835impl_op_ex!(*|a: &f64, b: &Vec3| -> Vec3 { b.scale(a) });
836impl_op_ex!(/ |a: &Vec3, b: &Expr| -> Vec3 {
837 Vec3::new(&a.x / b, &a.y / b, &a.z / b)
838});
839impl_op_ex!(/ |a: &Vec3, b: &f64| -> Vec3 {
840 Vec3::new(&a.x / b, &a.y / b, &a.z / b)
841});
842
843#[derive(Clone, Debug, Serialize, Deserialize)]
845pub struct Vec4 {
846 pub e: Expr,
848 pub px: Expr,
850 pub py: Expr,
852 pub pz: Expr,
854}
855
856impl Vec4 {
857 pub fn new(
859 e: impl Into<Expr>,
860 px: impl Into<Expr>,
861 py: impl Into<Expr>,
862 pz: impl Into<Expr>,
863 ) -> Self {
864 Self {
865 e: e.into(),
866 px: px.into(),
867 py: py.into(),
868 pz: pz.into(),
869 }
870 }
871
872 pub fn event(prefix: &str) -> Self {
874 Self::new(
875 event_p4_component(prefix, P4Component::E),
876 event_p4_component(prefix, P4Component::Px),
877 event_p4_component(prefix, P4Component::Py),
878 event_p4_component(prefix, P4Component::Pz),
879 )
880 }
881
882 pub fn px(&self) -> Expr {
884 self.px.clone()
885 }
886
887 pub fn py(&self) -> Expr {
889 self.py.clone()
890 }
891
892 pub fn pz(&self) -> Expr {
894 self.pz.clone()
895 }
896
897 pub fn e(&self) -> Expr {
899 self.e.clone()
900 }
901
902 pub fn momentum(&self) -> Vec3 {
904 self.vec3()
905 }
906
907 pub fn vec3(&self) -> Vec3 {
909 Vec3::new(self.px(), self.py(), self.pz())
910 }
911
912 pub fn beta(&self) -> Vec3 {
914 self.momentum() / self.e()
915 }
916
917 pub fn gamma(&self) -> Expr {
919 1.0 / (1.0 - self.beta().mag2()).sqrt()
920 }
921
922 pub fn m2(&self) -> Expr {
924 self.mag2()
925 }
926
927 pub fn m(&self) -> Expr {
929 self.mag()
930 }
931
932 pub fn dot(&self, other: &Self) -> Expr {
934 lorentz_dot4(
935 [&self.e, &self.px, &self.py, &self.pz],
936 [&other.e, &other.px, &other.py, &other.pz],
937 )
938 }
939
940 pub fn mag2(&self) -> Expr {
942 self.dot(self)
943 }
944
945 pub fn mag(&self) -> Expr {
947 self.mag2().sqrt()
948 }
949
950 pub fn boost(&self, beta: &Vec3) -> Self {
952 let b2 = beta.dot(beta);
953 let gamma = (1.0 - &b2).sqrt();
954 let gamma = 1.0 / gamma;
955 let factor = boost_factor(&gamma, |gamma| gamma.powi(2));
956 let p3 = self.vec3() + beta * ((factor * self.vec3().dot(beta)) + &gamma * &self.e);
957 Self::new(gamma * (&self.e + beta.dot(&self.vec3())), p3.x, p3.y, p3.z)
958 }
959
960 pub fn as_expr(&self) -> Expr {
962 vector([
963 self.e.clone(),
964 self.px.clone(),
965 self.py.clone(),
966 self.pz.clone(),
967 ])
968 }
969}
970
971impl From<RealVec4> for Vec4 {
972 fn from(value: RealVec4) -> Self {
973 Self::new(value.e, value.px, value.py, value.pz)
974 }
975}
976
977impl_op_ex!(+ |a: &Vec4, b: &Vec4| -> Vec4 {
978 Vec4::new(&a.e + &b.e, &a.px + &b.px, &a.py + &b.py, &a.pz + &b.pz)
979});
980impl_op_ex!(-|a: &Vec4, b: &Vec4| -> Vec4 {
981 Vec4::new(&a.e - &b.e, &a.px - &b.px, &a.py - &b.py, &a.pz - &b.pz)
982});
983impl_op_ex!(-|a: &Vec4| -> Vec4 { Vec4::new(-&a.e, -&a.px, -&a.py, -&a.pz) });
984
985#[cfg(test)]
986mod tests {
987 use approx::{assert_abs_diff_eq, assert_relative_eq};
988 use fastrand::Rng;
989 use laddu_compile::{CompileOptions, CompiledModel};
990 use laddu_runtime::CpuBackend;
991 use nalgebra::{Vector3, Vector4};
992 use num::complex::Complex64;
993
994 use super::*;
995
996 fn evaluate(expr: laddu_expr::Expr) -> Complex64 {
997 let model =
1000 CompiledModel::from_expr_with_options(&expr, &CompileOptions::without_optimizations())
1001 .unwrap();
1002 let params = model.params().default_values();
1003 CpuBackend.prepare(&model).evaluate(¶ms).unwrap()
1004 }
1005
1006 fn evaluate_real(expr: Expr) -> f64 {
1007 evaluate(expr).re
1008 }
1009
1010 fn assert_symbolic_vec4_eq(symbolic: Vec4, real: RealVec4, epsilon: f64) {
1011 assert_relative_eq!(evaluate_real(symbolic.e), real.e, epsilon = epsilon);
1012 assert_relative_eq!(evaluate_real(symbolic.px), real.px, epsilon = epsilon);
1013 assert_relative_eq!(evaluate_real(symbolic.py), real.py, epsilon = epsilon);
1014 assert_relative_eq!(evaluate_real(symbolic.pz), real.pz, epsilon = epsilon);
1015 }
1016
1017 #[test]
1018 fn test_display() {
1019 let v3 = RealVec3::new(1.2341, -2.3452, 3.4563);
1020 assert_eq!(format!("{}", v3), "[ 1.234, -2.345, 3.456]");
1021 let v4 = RealVec4::new(4.5674, 1.2341, -2.3452, 3.4563);
1022 assert_eq!(format!("{}", v4), "[ 4.567; 1.234, -2.345, 3.456]");
1023 }
1024
1025 #[test]
1026 fn test_vec_vector_conversion() {
1027 let v = RealVec3::new(1.0, 2.0, 3.0);
1028 let vector3: Vec<f64> = v.into();
1029 assert_eq!(vector3[0], 1.0);
1030 assert_eq!(vector3[1], 2.0);
1031 assert_eq!(vector3[2], 3.0);
1032
1033 let v_from_vec: RealVec3 = vector3.try_into().unwrap();
1034 assert_eq!(v_from_vec, v);
1035
1036 let v = RealVec4::new(1.0, 2.0, 3.0, 4.0);
1037 let vector4: Vec<f64> = v.into();
1038 assert_eq!(vector4[0], 1.0);
1039 assert_eq!(vector4[1], 2.0);
1040 assert_eq!(vector4[2], 3.0);
1041 assert_eq!(vector4[3], 4.0);
1042
1043 let v_from_vec: RealVec4 = vector4.try_into().unwrap();
1044 assert_eq!(v_from_vec, v);
1045 }
1046
1047 #[test]
1048 fn test_vec_array_conversion() {
1049 let arr = [1.0, 2.0, 3.0];
1050 let v: RealVec3 = arr.into();
1051 assert_eq!(v, RealVec3::new(1.0, 2.0, 3.0));
1052
1053 let back_to_array: [f64; 3] = v.into();
1054 assert_eq!(back_to_array, arr);
1055
1056 let arr = [1.0, 2.0, 3.0, 4.0];
1057 let v: RealVec4 = arr.into();
1058 assert_eq!(v, RealVec4::new(1.0, 2.0, 3.0, 4.0));
1059
1060 let back_to_array: [f64; 4] = v.into();
1061 assert_eq!(back_to_array, arr);
1062 assert_eq!(v.components(), arr);
1063 }
1064
1065 #[test]
1066 fn test_vec_nalgebra_conversion() {
1067 let v = RealVec3::new(1.0, 2.0, 3.0);
1068 let vector3: Vector3<f64> = v.into();
1069 assert_eq!(vector3.x, 1.0);
1070 assert_eq!(vector3.y, 2.0);
1071 assert_eq!(vector3.z, 3.0);
1072
1073 let v_from_vec: RealVec3 = vector3.into();
1074 assert_eq!(v_from_vec, v);
1075
1076 let v = RealVec4::new(1.0, 2.0, 3.0, 4.0);
1077 let vector4: Vector4<f64> = v.into();
1078 assert_eq!(vector4.x, 1.0);
1079 assert_eq!(vector4.y, 2.0);
1080 assert_eq!(vector4.z, 3.0);
1081 assert_eq!(vector4.w, 4.0);
1082
1083 let v_from_vec: RealVec4 = vector4.into();
1084 assert_eq!(v_from_vec, v);
1085 }
1086
1087 #[test]
1088 fn test_vec_sums() {
1089 let vectors = [RealVec3::new(1.0, 2.0, 3.0), RealVec3::new(4.0, 5.0, 6.0)];
1090 let sum: RealVec3 = vectors.iter().sum();
1091 assert_eq!(sum, RealVec3::new(5.0, 7.0, 9.0));
1092 let sum: RealVec3 = vectors.into_iter().sum();
1093 assert_eq!(sum, RealVec3::new(5.0, 7.0, 9.0));
1094
1095 let vectors = [
1096 RealVec4::new(1.0, 2.0, 3.0, 4.0),
1097 RealVec4::new(4.0, 5.0, 6.0, 7.0),
1098 ];
1099 let sum: RealVec4 = vectors.iter().sum();
1100 assert_eq!(sum, RealVec4::new(5.0, 7.0, 9.0, 11.0));
1101 let sum: RealVec4 = vectors.into_iter().sum();
1102 assert_eq!(sum, RealVec4::new(5.0, 7.0, 9.0, 11.0));
1103 }
1104
1105 #[test]
1106 fn test_three_to_four_momentum_conversion() {
1107 let p3 = RealVec3::new(1.0, 2.0, 3.0);
1108 let target_p4 = RealVec4::new(10.0, 1.0, 2.0, 3.0);
1109 let p4_from_mass = p3.with_mass(target_p4.m().unwrap());
1110 assert_eq!(target_p4.e(), p4_from_mass.e());
1111 assert_eq!(target_p4.px(), p4_from_mass.px());
1112 assert_eq!(target_p4.py(), p4_from_mass.py());
1113 assert_eq!(target_p4.pz(), p4_from_mass.pz());
1114 let p4_from_energy = p3.with_energy(target_p4.e());
1115 assert_eq!(target_p4.e(), p4_from_energy.e());
1116 assert_eq!(target_p4.px(), p4_from_energy.px());
1117 assert_eq!(target_p4.py(), p4_from_energy.py());
1118 assert_eq!(target_p4.pz(), p4_from_energy.pz());
1119 }
1120
1121 #[test]
1122 fn test_four_momentum_basics() {
1123 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1124 assert_eq!(p.e(), 10.0);
1125 assert_eq!(p.px(), 3.0);
1126 assert_eq!(p.py(), 4.0);
1127 assert_eq!(p.pz(), 5.0);
1128 assert_eq!(p.momentum().px(), 3.0);
1129 assert_eq!(p.momentum().py(), 4.0);
1130 assert_eq!(p.momentum().pz(), 5.0);
1131 assert_relative_eq!(p.beta().unwrap().x, 0.3);
1132 assert_relative_eq!(p.beta().unwrap().y, 0.4);
1133 assert_relative_eq!(p.beta().unwrap().z, 0.5);
1134 assert_relative_eq!(p.m2(), 50.0);
1135 assert_relative_eq!(p.m().unwrap(), f64::sqrt(50.0));
1136 assert_eq!(
1137 p.to_p4_string().to_string(),
1138 "[e = 10.00000; p = (3.00000, 4.00000, 5.00000); m = 7.07107]"
1139 );
1140 assert_relative_eq!(RealVec3::x().x, 1.0);
1141 assert_relative_eq!(RealVec3::x().y, 0.0);
1142 assert_relative_eq!(RealVec3::x().z, 0.0);
1143 assert_relative_eq!(RealVec3::y().x, 0.0);
1144 assert_relative_eq!(RealVec3::y().y, 1.0);
1145 assert_relative_eq!(RealVec3::y().z, 0.0);
1146 assert_relative_eq!(RealVec3::z().x, 0.0);
1147 assert_relative_eq!(RealVec3::z().y, 0.0);
1148 assert_relative_eq!(RealVec3::z().z, 1.0);
1149 assert_relative_eq!(RealVec3::default().x, 0.0);
1150 assert_relative_eq!(RealVec3::default().y, 0.0);
1151 assert_relative_eq!(RealVec3::default().z, 0.0);
1152 }
1153
1154 #[test]
1155 fn test_three_momentum_basics() {
1156 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1157 let q = RealVec4::new(0.0, 1.2, -3.4, 7.6);
1158 let p3_view = p.momentum();
1159 let q3_view = q.momentum();
1160 assert_eq!(p3_view.px(), 3.0);
1161 assert_eq!(p3_view.py(), 4.0);
1162 assert_eq!(p3_view.pz(), 5.0);
1163 assert_relative_eq!(p3_view.mag2(), 50.0);
1164 assert_relative_eq!(p3_view.mag(), f64::sqrt(50.0));
1165 assert_relative_eq!(p3_view.costheta().unwrap(), 5.0 / f64::sqrt(50.0));
1166 assert_relative_eq!(p3_view.theta().unwrap(), f64::acos(5.0 / f64::sqrt(50.0)));
1167 assert_relative_eq!(p3_view.phi(), f64::atan2(4.0, 3.0));
1168 assert_relative_eq!(
1169 p3_view.unit().unwrap(),
1170 RealVec3::new(
1171 3.0 / f64::sqrt(50.0),
1172 4.0 / f64::sqrt(50.0),
1173 5.0 / f64::sqrt(50.0)
1174 )
1175 );
1176 assert_relative_eq!(p3_view.cross(&q3_view), RealVec3::new(47.4, -16.8, -15.0));
1177 }
1178
1179 #[test]
1180 fn test_vec_equality() {
1181 let p = RealVec3::new(1.1, 2.2, 3.3);
1182 let p2 = RealVec3::new(1.1 * 2.0, 2.2 * 2.0, 3.3 * 2.0);
1183 assert_abs_diff_eq!(p * 2.0, p2);
1184 assert_relative_eq!(p * 2.0, p2);
1185 }
1186
1187 #[test]
1188 fn test_boost_com() {
1189 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1190 let zero = p.boost(&-p.beta().unwrap()).momentum();
1191 assert_relative_eq!(zero, RealVec3::zero());
1192 }
1193
1194 #[test]
1195 fn test_boost() {
1196 let p0 = RealVec4::new(1.0, 0.0, 0.0, 0.0);
1197 assert_relative_eq!(p0.gamma().unwrap(), 1.0);
1198 let p0 = RealVec4::new(1.0, f64::sqrt(3.0) / 2.0, 0.0, 0.0);
1199 assert_relative_eq!(p0.gamma().unwrap(), 2.0);
1200 let p1 = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1201 let p2 = RealVec4::new(9.0, 3.4, 2.3, 1.2);
1202 let p1_boosted = p1.boost(&-p2.beta().unwrap());
1203 assert_relative_eq!(p1_boosted.e(), 8.157632144622882);
1204 assert_relative_eq!(p1_boosted.px(), -0.6489200627053444);
1205 assert_relative_eq!(p1_boosted.py(), 1.5316128987581492);
1206 assert_relative_eq!(p1_boosted.pz(), 3.712145860221643);
1207 }
1208
1209 #[test]
1210 fn real_and_symbolic_vector_formulas_agree_on_finite_inputs() {
1211 let mut rng = Rng::with_seed(0x5645_4354_4f52);
1212
1213 for _ in 0..32 {
1214 let a3 = RealVec3::new(
1215 rng.f64() * 10.0 - 5.0,
1216 rng.f64() * 10.0 - 5.0,
1217 rng.f64() * 10.0 - 5.0,
1218 );
1219 let b3 = RealVec3::new(
1220 rng.f64() * 10.0 - 5.0,
1221 rng.f64() * 10.0 - 5.0,
1222 rng.f64() * 10.0 - 5.0,
1223 );
1224 let a = Vec3::from(a3);
1225 let b = Vec3::from(b3);
1226 assert_relative_eq!(evaluate_real(a.dot(&b)), a3.dot(&b3), epsilon = 1e-11);
1227 let symbolic_cross = a.cross(&b);
1228 let real_cross = a3.cross(&b3);
1229 assert_relative_eq!(
1230 evaluate_real(symbolic_cross.x),
1231 real_cross.x,
1232 epsilon = 1e-11
1233 );
1234 assert_relative_eq!(
1235 evaluate_real(symbolic_cross.y),
1236 real_cross.y,
1237 epsilon = 1e-11
1238 );
1239 assert_relative_eq!(
1240 evaluate_real(symbolic_cross.z),
1241 real_cross.z,
1242 epsilon = 1e-11
1243 );
1244
1245 let p = RealVec4::new(
1246 rng.f64() * 9.0 + 1.0,
1247 rng.f64() * 10.0 - 5.0,
1248 rng.f64() * 10.0 - 5.0,
1249 rng.f64() * 10.0 - 5.0,
1250 );
1251 let q = RealVec4::new(
1252 rng.f64() * 9.0 + 1.0,
1253 rng.f64() * 10.0 - 5.0,
1254 rng.f64() * 10.0 - 5.0,
1255 rng.f64() * 10.0 - 5.0,
1256 );
1257 let symbolic_p = Vec4::from(p);
1258 let symbolic_q = Vec4::from(q);
1259 assert_relative_eq!(
1260 evaluate_real(symbolic_p.dot(&symbolic_q)),
1261 p.dot(&q),
1262 epsilon = 1e-11
1263 );
1264 assert_relative_eq!(evaluate_real(symbolic_p.m2()), p.m2(), epsilon = 1e-11);
1265
1266 let beta = RealVec3::new(
1267 rng.f64() * 0.6 - 0.3,
1268 rng.f64() * 0.6 - 0.3,
1269 rng.f64() * 0.6 - 0.3,
1270 );
1271 assert_symbolic_vec4_eq(
1272 Vec4::from(p).boost(&Vec3::from(beta)),
1273 p.boost(&beta),
1274 1e-10,
1275 );
1276 }
1277 }
1278
1279 #[test]
1280 fn vector_formula_boundaries_remain_well_defined() {
1281 let p = RealVec4::new(2.0, 0.25, -0.5, 1.0);
1282 assert_eq!(p.boost(&RealVec3::zero()), p);
1283 assert_symbolic_vec4_eq(Vec4::from(p).boost(&Vec3::zero()), p, 1e-12);
1284
1285 let near_zero = RealVec3::new(1e-12, -1e-12, 1e-12);
1286 assert_symbolic_vec4_eq(
1287 Vec4::from(p).boost(&Vec3::from(near_zero)),
1288 p.boost(&near_zero),
1289 1e-12,
1290 );
1291
1292 let near_lightlike = RealVec4::new(1.0, 1.0 - 1e-12, 0.0, 0.0);
1293 assert_relative_eq!(
1294 evaluate_real(Vec4::from(near_lightlike).m2()),
1295 near_lightlike.m2(),
1296 epsilon = 1e-15
1297 );
1298
1299 let near_light_speed = RealVec3::new(1.0 - 1e-12, 0.0, 0.0);
1300 assert_symbolic_vec4_eq(
1301 Vec4::from(p).boost(&Vec3::from(near_light_speed)),
1302 p.boost(&near_light_speed),
1303 1e-6,
1304 );
1305 }
1306
1307 #[test]
1308 fn collinear_frame_composition_matches_combined_boost() {
1309 let p = RealVec4::new(5.0, 0.4, -0.2, 1.5);
1310 let first = 0.2;
1311 let second = -0.35;
1312 let combined = (first + second) / (1.0 + first * second);
1313
1314 let sequential = p
1315 .boost(&RealVec3::new(first, 0.0, 0.0))
1316 .boost(&RealVec3::new(second, 0.0, 0.0));
1317 let direct = p.boost(&RealVec3::new(combined, 0.0, 0.0));
1318 assert_relative_eq!(sequential, direct, epsilon = 1e-12);
1319 assert_symbolic_vec4_eq(
1320 Vec4::from(p)
1321 .boost(&Vec3::new(first, 0.0, 0.0))
1322 .boost(&Vec3::new(second, 0.0, 0.0)),
1323 sequential,
1324 1e-12,
1325 );
1326 }
1327
1328 #[test]
1329 fn expression_vectors_build_and_evaluate_scalar_observables() {
1330 let p = Vec4::new(10.0, 3.0, 4.0, 5.0);
1331 assert_eq!(evaluate(p.m2()), Complex64::from(50.0));
1332 assert_eq!(evaluate(p.momentum().mag2()), Complex64::from(50.0));
1333
1334 let a = Vec3::new(1.0, 2.0, 3.0);
1335 let b = Vec3::new(4.0, 5.0, 6.0);
1336 assert_eq!(evaluate(a.dot(&b)), Complex64::from(32.0));
1337 assert_eq!(evaluate(a.cross(&b).z), Complex64::from(-3.0));
1338 }
1339}