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::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 = CompiledModel::from_expr(&expr).unwrap();
998 let params = model.params().default_values();
999 CpuBackend.prepare(&model).evaluate(¶ms).unwrap()
1000 }
1001
1002 fn evaluate_real(expr: Expr) -> f64 {
1003 evaluate(expr).re
1004 }
1005
1006 fn assert_symbolic_vec4_eq(symbolic: Vec4, real: RealVec4, epsilon: f64) {
1007 assert_relative_eq!(evaluate_real(symbolic.e), real.e, epsilon = epsilon);
1008 assert_relative_eq!(evaluate_real(symbolic.px), real.px, epsilon = epsilon);
1009 assert_relative_eq!(evaluate_real(symbolic.py), real.py, epsilon = epsilon);
1010 assert_relative_eq!(evaluate_real(symbolic.pz), real.pz, epsilon = epsilon);
1011 }
1012
1013 #[test]
1014 fn test_display() {
1015 let v3 = RealVec3::new(1.2341, -2.3452, 3.4563);
1016 assert_eq!(format!("{}", v3), "[ 1.234, -2.345, 3.456]");
1017 let v4 = RealVec4::new(4.5674, 1.2341, -2.3452, 3.4563);
1018 assert_eq!(format!("{}", v4), "[ 4.567; 1.234, -2.345, 3.456]");
1019 }
1020
1021 #[test]
1022 fn test_vec_vector_conversion() {
1023 let v = RealVec3::new(1.0, 2.0, 3.0);
1024 let vector3: Vec<f64> = v.into();
1025 assert_eq!(vector3[0], 1.0);
1026 assert_eq!(vector3[1], 2.0);
1027 assert_eq!(vector3[2], 3.0);
1028
1029 let v_from_vec: RealVec3 = vector3.try_into().unwrap();
1030 assert_eq!(v_from_vec, v);
1031
1032 let v = RealVec4::new(1.0, 2.0, 3.0, 4.0);
1033 let vector4: Vec<f64> = v.into();
1034 assert_eq!(vector4[0], 1.0);
1035 assert_eq!(vector4[1], 2.0);
1036 assert_eq!(vector4[2], 3.0);
1037 assert_eq!(vector4[3], 4.0);
1038
1039 let v_from_vec: RealVec4 = vector4.try_into().unwrap();
1040 assert_eq!(v_from_vec, v);
1041 }
1042
1043 #[test]
1044 fn test_vec_array_conversion() {
1045 let arr = [1.0, 2.0, 3.0];
1046 let v: RealVec3 = arr.into();
1047 assert_eq!(v, RealVec3::new(1.0, 2.0, 3.0));
1048
1049 let back_to_array: [f64; 3] = v.into();
1050 assert_eq!(back_to_array, arr);
1051
1052 let arr = [1.0, 2.0, 3.0, 4.0];
1053 let v: RealVec4 = arr.into();
1054 assert_eq!(v, RealVec4::new(1.0, 2.0, 3.0, 4.0));
1055
1056 let back_to_array: [f64; 4] = v.into();
1057 assert_eq!(back_to_array, arr);
1058 assert_eq!(v.components(), arr);
1059 }
1060
1061 #[test]
1062 fn test_vec_nalgebra_conversion() {
1063 let v = RealVec3::new(1.0, 2.0, 3.0);
1064 let vector3: Vector3<f64> = v.into();
1065 assert_eq!(vector3.x, 1.0);
1066 assert_eq!(vector3.y, 2.0);
1067 assert_eq!(vector3.z, 3.0);
1068
1069 let v_from_vec: RealVec3 = vector3.into();
1070 assert_eq!(v_from_vec, v);
1071
1072 let v = RealVec4::new(1.0, 2.0, 3.0, 4.0);
1073 let vector4: Vector4<f64> = v.into();
1074 assert_eq!(vector4.x, 1.0);
1075 assert_eq!(vector4.y, 2.0);
1076 assert_eq!(vector4.z, 3.0);
1077 assert_eq!(vector4.w, 4.0);
1078
1079 let v_from_vec: RealVec4 = vector4.into();
1080 assert_eq!(v_from_vec, v);
1081 }
1082
1083 #[test]
1084 fn test_vec_sums() {
1085 let vectors = [RealVec3::new(1.0, 2.0, 3.0), RealVec3::new(4.0, 5.0, 6.0)];
1086 let sum: RealVec3 = vectors.iter().sum();
1087 assert_eq!(sum, RealVec3::new(5.0, 7.0, 9.0));
1088 let sum: RealVec3 = vectors.into_iter().sum();
1089 assert_eq!(sum, RealVec3::new(5.0, 7.0, 9.0));
1090
1091 let vectors = [
1092 RealVec4::new(1.0, 2.0, 3.0, 4.0),
1093 RealVec4::new(4.0, 5.0, 6.0, 7.0),
1094 ];
1095 let sum: RealVec4 = vectors.iter().sum();
1096 assert_eq!(sum, RealVec4::new(5.0, 7.0, 9.0, 11.0));
1097 let sum: RealVec4 = vectors.into_iter().sum();
1098 assert_eq!(sum, RealVec4::new(5.0, 7.0, 9.0, 11.0));
1099 }
1100
1101 #[test]
1102 fn test_three_to_four_momentum_conversion() {
1103 let p3 = RealVec3::new(1.0, 2.0, 3.0);
1104 let target_p4 = RealVec4::new(10.0, 1.0, 2.0, 3.0);
1105 let p4_from_mass = p3.with_mass(target_p4.m().unwrap());
1106 assert_eq!(target_p4.e(), p4_from_mass.e());
1107 assert_eq!(target_p4.px(), p4_from_mass.px());
1108 assert_eq!(target_p4.py(), p4_from_mass.py());
1109 assert_eq!(target_p4.pz(), p4_from_mass.pz());
1110 let p4_from_energy = p3.with_energy(target_p4.e());
1111 assert_eq!(target_p4.e(), p4_from_energy.e());
1112 assert_eq!(target_p4.px(), p4_from_energy.px());
1113 assert_eq!(target_p4.py(), p4_from_energy.py());
1114 assert_eq!(target_p4.pz(), p4_from_energy.pz());
1115 }
1116
1117 #[test]
1118 fn test_four_momentum_basics() {
1119 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1120 assert_eq!(p.e(), 10.0);
1121 assert_eq!(p.px(), 3.0);
1122 assert_eq!(p.py(), 4.0);
1123 assert_eq!(p.pz(), 5.0);
1124 assert_eq!(p.momentum().px(), 3.0);
1125 assert_eq!(p.momentum().py(), 4.0);
1126 assert_eq!(p.momentum().pz(), 5.0);
1127 assert_relative_eq!(p.beta().unwrap().x, 0.3);
1128 assert_relative_eq!(p.beta().unwrap().y, 0.4);
1129 assert_relative_eq!(p.beta().unwrap().z, 0.5);
1130 assert_relative_eq!(p.m2(), 50.0);
1131 assert_relative_eq!(p.m().unwrap(), f64::sqrt(50.0));
1132 assert_eq!(
1133 p.to_p4_string().to_string(),
1134 "[e = 10.00000; p = (3.00000, 4.00000, 5.00000); m = 7.07107]"
1135 );
1136 assert_relative_eq!(RealVec3::x().x, 1.0);
1137 assert_relative_eq!(RealVec3::x().y, 0.0);
1138 assert_relative_eq!(RealVec3::x().z, 0.0);
1139 assert_relative_eq!(RealVec3::y().x, 0.0);
1140 assert_relative_eq!(RealVec3::y().y, 1.0);
1141 assert_relative_eq!(RealVec3::y().z, 0.0);
1142 assert_relative_eq!(RealVec3::z().x, 0.0);
1143 assert_relative_eq!(RealVec3::z().y, 0.0);
1144 assert_relative_eq!(RealVec3::z().z, 1.0);
1145 assert_relative_eq!(RealVec3::default().x, 0.0);
1146 assert_relative_eq!(RealVec3::default().y, 0.0);
1147 assert_relative_eq!(RealVec3::default().z, 0.0);
1148 }
1149
1150 #[test]
1151 fn test_three_momentum_basics() {
1152 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1153 let q = RealVec4::new(0.0, 1.2, -3.4, 7.6);
1154 let p3_view = p.momentum();
1155 let q3_view = q.momentum();
1156 assert_eq!(p3_view.px(), 3.0);
1157 assert_eq!(p3_view.py(), 4.0);
1158 assert_eq!(p3_view.pz(), 5.0);
1159 assert_relative_eq!(p3_view.mag2(), 50.0);
1160 assert_relative_eq!(p3_view.mag(), f64::sqrt(50.0));
1161 assert_relative_eq!(p3_view.costheta().unwrap(), 5.0 / f64::sqrt(50.0));
1162 assert_relative_eq!(p3_view.theta().unwrap(), f64::acos(5.0 / f64::sqrt(50.0)));
1163 assert_relative_eq!(p3_view.phi(), f64::atan2(4.0, 3.0));
1164 assert_relative_eq!(
1165 p3_view.unit().unwrap(),
1166 RealVec3::new(
1167 3.0 / f64::sqrt(50.0),
1168 4.0 / f64::sqrt(50.0),
1169 5.0 / f64::sqrt(50.0)
1170 )
1171 );
1172 assert_relative_eq!(p3_view.cross(&q3_view), RealVec3::new(47.4, -16.8, -15.0));
1173 }
1174
1175 #[test]
1176 fn test_vec_equality() {
1177 let p = RealVec3::new(1.1, 2.2, 3.3);
1178 let p2 = RealVec3::new(1.1 * 2.0, 2.2 * 2.0, 3.3 * 2.0);
1179 assert_abs_diff_eq!(p * 2.0, p2);
1180 assert_relative_eq!(p * 2.0, p2);
1181 }
1182
1183 #[test]
1184 fn test_boost_com() {
1185 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1186 let zero = p.boost(&-p.beta().unwrap()).momentum();
1187 assert_relative_eq!(zero, RealVec3::zero());
1188 }
1189
1190 #[test]
1191 fn test_boost() {
1192 let p0 = RealVec4::new(1.0, 0.0, 0.0, 0.0);
1193 assert_relative_eq!(p0.gamma().unwrap(), 1.0);
1194 let p0 = RealVec4::new(1.0, f64::sqrt(3.0) / 2.0, 0.0, 0.0);
1195 assert_relative_eq!(p0.gamma().unwrap(), 2.0);
1196 let p1 = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1197 let p2 = RealVec4::new(9.0, 3.4, 2.3, 1.2);
1198 let p1_boosted = p1.boost(&-p2.beta().unwrap());
1199 assert_relative_eq!(p1_boosted.e(), 8.157632144622882);
1200 assert_relative_eq!(p1_boosted.px(), -0.6489200627053444);
1201 assert_relative_eq!(p1_boosted.py(), 1.5316128987581492);
1202 assert_relative_eq!(p1_boosted.pz(), 3.712145860221643);
1203 }
1204
1205 #[test]
1206 fn real_and_symbolic_vector_formulas_agree_on_finite_inputs() {
1207 let mut rng = Rng::with_seed(0x5645_4354_4f52);
1208
1209 for _ in 0..32 {
1210 let a3 = RealVec3::new(
1211 rng.f64() * 10.0 - 5.0,
1212 rng.f64() * 10.0 - 5.0,
1213 rng.f64() * 10.0 - 5.0,
1214 );
1215 let b3 = RealVec3::new(
1216 rng.f64() * 10.0 - 5.0,
1217 rng.f64() * 10.0 - 5.0,
1218 rng.f64() * 10.0 - 5.0,
1219 );
1220 let a = Vec3::from(a3);
1221 let b = Vec3::from(b3);
1222 assert_relative_eq!(evaluate_real(a.dot(&b)), a3.dot(&b3), epsilon = 1e-11);
1223 let symbolic_cross = a.cross(&b);
1224 let real_cross = a3.cross(&b3);
1225 assert_relative_eq!(
1226 evaluate_real(symbolic_cross.x),
1227 real_cross.x,
1228 epsilon = 1e-11
1229 );
1230 assert_relative_eq!(
1231 evaluate_real(symbolic_cross.y),
1232 real_cross.y,
1233 epsilon = 1e-11
1234 );
1235 assert_relative_eq!(
1236 evaluate_real(symbolic_cross.z),
1237 real_cross.z,
1238 epsilon = 1e-11
1239 );
1240
1241 let p = RealVec4::new(
1242 rng.f64() * 9.0 + 1.0,
1243 rng.f64() * 10.0 - 5.0,
1244 rng.f64() * 10.0 - 5.0,
1245 rng.f64() * 10.0 - 5.0,
1246 );
1247 let q = RealVec4::new(
1248 rng.f64() * 9.0 + 1.0,
1249 rng.f64() * 10.0 - 5.0,
1250 rng.f64() * 10.0 - 5.0,
1251 rng.f64() * 10.0 - 5.0,
1252 );
1253 let symbolic_p = Vec4::from(p);
1254 let symbolic_q = Vec4::from(q);
1255 assert_relative_eq!(
1256 evaluate_real(symbolic_p.dot(&symbolic_q)),
1257 p.dot(&q),
1258 epsilon = 1e-11
1259 );
1260 assert_relative_eq!(evaluate_real(symbolic_p.m2()), p.m2(), epsilon = 1e-11);
1261
1262 let beta = RealVec3::new(
1263 rng.f64() * 0.6 - 0.3,
1264 rng.f64() * 0.6 - 0.3,
1265 rng.f64() * 0.6 - 0.3,
1266 );
1267 assert_symbolic_vec4_eq(
1268 Vec4::from(p).boost(&Vec3::from(beta)),
1269 p.boost(&beta),
1270 1e-10,
1271 );
1272 }
1273 }
1274
1275 #[test]
1276 fn vector_formula_boundaries_remain_well_defined() {
1277 let p = RealVec4::new(2.0, 0.25, -0.5, 1.0);
1278 assert_eq!(p.boost(&RealVec3::zero()), p);
1279 assert_symbolic_vec4_eq(Vec4::from(p).boost(&Vec3::zero()), p, 1e-12);
1280
1281 let near_zero = RealVec3::new(1e-12, -1e-12, 1e-12);
1282 assert_symbolic_vec4_eq(
1283 Vec4::from(p).boost(&Vec3::from(near_zero)),
1284 p.boost(&near_zero),
1285 1e-12,
1286 );
1287
1288 let near_lightlike = RealVec4::new(1.0, 1.0 - 1e-12, 0.0, 0.0);
1289 assert_relative_eq!(
1290 evaluate_real(Vec4::from(near_lightlike).m2()),
1291 near_lightlike.m2(),
1292 epsilon = 1e-15
1293 );
1294
1295 let near_light_speed = RealVec3::new(1.0 - 1e-12, 0.0, 0.0);
1296 assert_symbolic_vec4_eq(
1297 Vec4::from(p).boost(&Vec3::from(near_light_speed)),
1298 p.boost(&near_light_speed),
1299 1e-6,
1300 );
1301 }
1302
1303 #[test]
1304 fn collinear_frame_composition_matches_combined_boost() {
1305 let p = RealVec4::new(5.0, 0.4, -0.2, 1.5);
1306 let first = 0.2;
1307 let second = -0.35;
1308 let combined = (first + second) / (1.0 + first * second);
1309
1310 let sequential = p
1311 .boost(&RealVec3::new(first, 0.0, 0.0))
1312 .boost(&RealVec3::new(second, 0.0, 0.0));
1313 let direct = p.boost(&RealVec3::new(combined, 0.0, 0.0));
1314 assert_relative_eq!(sequential, direct, epsilon = 1e-12);
1315 assert_symbolic_vec4_eq(
1316 Vec4::from(p)
1317 .boost(&Vec3::new(first, 0.0, 0.0))
1318 .boost(&Vec3::new(second, 0.0, 0.0)),
1319 sequential,
1320 1e-12,
1321 );
1322 }
1323
1324 #[test]
1325 fn expression_vectors_build_and_evaluate_scalar_observables() {
1326 let p = Vec4::new(10.0, 3.0, 4.0, 5.0);
1327 assert_eq!(evaluate(p.m2()), Complex64::from(50.0));
1328 assert_eq!(evaluate(p.momentum().mag2()), Complex64::from(50.0));
1329
1330 let a = Vec3::new(1.0, 2.0, 3.0);
1331 let b = Vec3::new(4.0, 5.0, 6.0);
1332 assert_eq!(evaluate(a.dot(&b)), Complex64::from(32.0));
1333 assert_eq!(evaluate(a.cross(&b).z), Complex64::from(-3.0));
1334 }
1335}