1use std::fmt::Display;
2
3use approx::{AbsDiffEq, RelativeEq};
4use auto_ops::{impl_op_ex, impl_op_ex_commutative};
5use laddu_expr::{Expr, P4Component, atan2, event_p4_component, vector};
6use nalgebra::{Vector3, Vector4};
7use serde::{Deserialize, Serialize};
8
9use crate::{LadduPhysicsError, LadduPhysicsResult};
10
11#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct RealVec3 {
22 pub x: f64,
24 pub y: f64,
26 pub z: f64,
28}
29
30impl Display for RealVec3 {
31 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
32 write!(f, "[{:6.3}, {:6.3}, {:6.3}]", self.x, self.y, self.z)
33 }
34}
35
36impl AbsDiffEq for RealVec3 {
37 type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
38
39 fn default_epsilon() -> Self::Epsilon {
40 f64::default_epsilon()
41 }
42
43 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
44 f64::abs_diff_eq(&self.x, &other.x, epsilon)
45 && f64::abs_diff_eq(&self.y, &other.y, epsilon)
46 && f64::abs_diff_eq(&self.z, &other.z, epsilon)
47 }
48}
49impl RelativeEq for RealVec3 {
50 fn default_max_relative() -> Self::Epsilon {
51 f64::default_max_relative()
52 }
53
54 fn relative_eq(
55 &self,
56 other: &Self,
57 epsilon: Self::Epsilon,
58 max_relative: Self::Epsilon,
59 ) -> bool {
60 f64::relative_eq(&self.x, &other.x, epsilon, max_relative)
61 && f64::relative_eq(&self.y, &other.y, epsilon, max_relative)
62 && f64::relative_eq(&self.z, &other.z, epsilon, max_relative)
63 }
64}
65
66impl From<RealVec3> for Vector3<f64> {
67 fn from(value: RealVec3) -> Self {
68 Vector3::new(value.x, value.y, value.z)
69 }
70}
71
72impl From<Vector3<f64>> for RealVec3 {
73 fn from(value: Vector3<f64>) -> Self {
74 RealVec3::new(value.x, value.y, value.z)
75 }
76}
77
78impl TryFrom<Vec<f64>> for RealVec3 {
79 type Error = LadduPhysicsError;
80
81 fn try_from(value: Vec<f64>) -> Result<Self, Self::Error> {
82 if value.len() != 3 {
83 return Err(LadduPhysicsError::custom(
84 "Attempted to convert Vec<f64> to RealVec3 for Vec with len != 3",
85 ));
86 }
87 Ok(Self {
88 x: value[0],
89 y: value[1],
90 z: value[2],
91 })
92 }
93}
94
95impl From<RealVec3> for Vec<f64> {
96 fn from(value: RealVec3) -> Self {
97 vec![value.x, value.y, value.z]
98 }
99}
100
101impl From<[f64; 3]> for RealVec3 {
102 fn from(value: [f64; 3]) -> Self {
103 Self {
104 x: value[0],
105 y: value[1],
106 z: value[2],
107 }
108 }
109}
110
111impl From<RealVec3> for [f64; 3] {
112 fn from(value: RealVec3) -> Self {
113 [value.x, value.y, value.z]
114 }
115}
116
117impl Default for RealVec3 {
118 fn default() -> Self {
119 RealVec3::zero()
120 }
121}
122
123impl RealVec3 {
124 pub fn new(x: f64, y: f64, z: f64) -> Self {
126 RealVec3 { x, y, z }
127 }
128
129 pub const fn zero() -> Self {
131 RealVec3 {
132 x: 0.0,
133 y: 0.0,
134 z: 0.0,
135 }
136 }
137
138 pub const fn x() -> Self {
140 RealVec3 {
141 x: 1.0,
142 y: 0.0,
143 z: 0.0,
144 }
145 }
146
147 pub const fn y() -> Self {
149 RealVec3 {
150 x: 0.0,
151 y: 1.0,
152 z: 0.0,
153 }
154 }
155
156 pub const fn z() -> Self {
158 RealVec3 {
159 x: 0.0,
160 y: 0.0,
161 z: 1.0,
162 }
163 }
164
165 pub fn px(&self) -> f64 {
167 self.x
168 }
169
170 pub fn py(&self) -> f64 {
172 self.y
173 }
174
175 pub fn pz(&self) -> f64 {
177 self.z
178 }
179
180 pub fn with_mass(&self, mass: f64) -> RealVec4 {
182 let e = f64::sqrt(mass.powi(2) + self.mag2());
183 RealVec4::new(e, self.px(), self.py(), self.pz())
184 }
185
186 pub fn with_energy(&self, energy: f64) -> RealVec4 {
188 RealVec4::new(energy, self.px(), self.py(), self.pz())
189 }
190
191 pub fn dot(&self, other: &RealVec3) -> f64 {
193 self.x * other.x + self.y * other.y + self.z * other.z
194 }
195
196 pub fn cross(&self, other: &RealVec3) -> RealVec3 {
198 RealVec3::new(
199 self.y * other.z - other.y * self.z,
200 self.z * other.x - other.z * self.x,
201 self.x * other.y - other.x * self.y,
202 )
203 }
204
205 pub fn mag(&self) -> f64 {
207 f64::sqrt(self.mag2())
208 }
209
210 pub fn mag2(&self) -> f64 {
212 self.dot(self)
213 }
214
215 pub fn costheta(&self) -> LadduPhysicsResult<f64> {
222 let mag = self.mag();
223 if mag <= 0.0 {
224 return Err(LadduPhysicsError::invalid_value(
225 "vector magnitude",
226 "positive when calculating cos(theta)",
227 mag,
228 ));
229 }
230 Ok(self.z / self.mag())
231 }
232
233 pub fn theta(&self) -> LadduPhysicsResult<f64> {
240 Ok(f64::acos(self.costheta()?))
241 }
242
243 pub fn phi(&self) -> f64 {
245 f64::atan2(self.y, self.x)
246 }
247
248 pub fn unit(&self) -> LadduPhysicsResult<RealVec3> {
255 let mag = self.mag();
256 if mag <= 0.0 {
257 return Err(LadduPhysicsError::invalid_value(
258 "vector magnitude",
259 "positive when constructing unit vector",
260 mag,
261 ));
262 }
263 Ok(RealVec3::new(self.x / mag, self.y / mag, self.z / mag))
264 }
265}
266
267impl<'a> std::iter::Sum<&'a RealVec3> for RealVec3 {
268 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
269 iter.fold(Self::zero(), |a, b| a + b)
270 }
271}
272impl std::iter::Sum<RealVec3> for RealVec3 {
273 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
274 iter.fold(Self::zero(), |a, b| a + b)
275 }
276}
277
278impl_op_ex!(+ |a: &RealVec3, b: &RealVec3| -> RealVec3 { RealVec3::new(a.x + b.x, a.y + b.y, a.z + b.z) });
279impl_op_ex!(-|a: &RealVec3, b: &RealVec3| -> RealVec3 {
280 RealVec3::new(a.x - b.x, a.y - b.y, a.z - b.z)
281});
282impl_op_ex!(-|a: &RealVec3| -> RealVec3 { RealVec3::new(-a.x, -a.y, -a.z) });
283impl_op_ex_commutative!(+ |a: &RealVec3, b: &f64| -> RealVec3 { RealVec3::new(a.x + b, a.y + b, a.z + b) });
284impl_op_ex_commutative!(-|a: &RealVec3, b: &f64| -> RealVec3 {
285 RealVec3::new(a.x - b, a.y - b, a.z - b)
286});
287impl_op_ex_commutative!(*|a: &RealVec3, b: &f64| -> RealVec3 {
288 RealVec3::new(a.x * b, a.y * b, a.z * b)
289});
290impl_op_ex!(/ |a: &RealVec3, b: &f64| -> RealVec3 { RealVec3::new(a.x / b, a.y / b, a.z / b) });
291
292#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
303pub struct RealVec4 {
304 pub e: f64,
306 pub px: f64,
308 pub py: f64,
310 pub pz: f64,
312}
313
314impl Display for RealVec4 {
315 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316 write!(
317 f,
318 "[{:6.3}; {:6.3}, {:6.3}, {:6.3}]",
319 self.e, self.px, self.py, self.pz
320 )
321 }
322}
323
324impl AbsDiffEq for RealVec4 {
325 type Epsilon = <f64 as approx::AbsDiffEq>::Epsilon;
326
327 fn default_epsilon() -> Self::Epsilon {
328 f64::default_epsilon()
329 }
330
331 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
332 f64::abs_diff_eq(&self.e, &other.e, epsilon)
333 && f64::abs_diff_eq(&self.px, &other.px, epsilon)
334 && f64::abs_diff_eq(&self.py, &other.py, epsilon)
335 && f64::abs_diff_eq(&self.pz, &other.pz, epsilon)
336 }
337}
338impl RelativeEq for RealVec4 {
339 fn default_max_relative() -> Self::Epsilon {
340 f64::default_max_relative()
341 }
342
343 fn relative_eq(
344 &self,
345 other: &Self,
346 epsilon: Self::Epsilon,
347 max_relative: Self::Epsilon,
348 ) -> bool {
349 f64::relative_eq(&self.e, &other.e, epsilon, max_relative)
350 && f64::relative_eq(&self.px, &other.px, epsilon, max_relative)
351 && f64::relative_eq(&self.py, &other.py, epsilon, max_relative)
352 && f64::relative_eq(&self.pz, &other.pz, epsilon, max_relative)
353 }
354}
355
356impl From<RealVec4> for Vector4<f64> {
357 fn from(value: RealVec4) -> Self {
358 Vector4::new(value.e, value.px, value.py, value.pz)
359 }
360}
361
362impl From<Vector4<f64>> for RealVec4 {
363 fn from(value: Vector4<f64>) -> Self {
364 RealVec4::new(value.x, value.y, value.z, value.w)
365 }
366}
367
368impl TryFrom<Vec<f64>> for RealVec4 {
369 type Error = LadduPhysicsError;
370
371 fn try_from(value: Vec<f64>) -> Result<Self, Self::Error> {
372 if value.len() != 4 {
373 return Err(LadduPhysicsError::custom(
374 "Attempted to convert Vec<f64> to RealVec4 for Vec with len != 4",
375 ));
376 }
377 Ok(Self {
378 e: value[0],
379 px: value[1],
380 py: value[2],
381 pz: value[3],
382 })
383 }
384}
385
386impl From<RealVec4> for Vec<f64> {
387 fn from(value: RealVec4) -> Self {
388 vec![value.e, value.px, value.py, value.pz]
389 }
390}
391
392impl From<[f64; 4]> for RealVec4 {
393 fn from(value: [f64; 4]) -> Self {
394 Self {
395 e: value[0],
396 px: value[1],
397 py: value[2],
398 pz: value[3],
399 }
400 }
401}
402
403impl From<RealVec4> for [f64; 4] {
404 fn from(value: RealVec4) -> Self {
405 [value.e, value.px, value.py, value.pz]
406 }
407}
408
409impl RealVec4 {
410 pub fn new(e: f64, px: f64, py: f64, pz: f64) -> Self {
412 RealVec4 { e, px, py, pz }
413 }
414
415 pub fn px(&self) -> f64 {
417 self.px
418 }
419
420 pub fn py(&self) -> f64 {
422 self.py
423 }
424
425 pub fn pz(&self) -> f64 {
427 self.pz
428 }
429
430 pub fn e(&self) -> f64 {
432 self.e
433 }
434
435 pub fn momentum(&self) -> RealVec3 {
437 self.vec3()
438 }
439
440 pub fn gamma(&self) -> LadduPhysicsResult<f64> {
447 let beta = self.beta()?;
448 let b2 = beta.dot(&beta);
449 if b2 >= 1.0 {
450 return Err(LadduPhysicsError::invalid_value("|beta|^2", "< 1", b2));
451 }
452 Ok(1.0 / f64::sqrt(1.0 - b2))
453 }
454
455 pub fn beta(&self) -> LadduPhysicsResult<RealVec3> {
462 let e = self.e();
463 if e <= 0.0 {
464 return Err(LadduPhysicsError::invalid_value(
465 "four-momentum energy",
466 "positive",
467 e,
468 ));
469 }
470 Ok(self.momentum() / e)
471 }
472
473 pub fn m(&self) -> LadduPhysicsResult<f64> {
480 self.mag()
481 }
482
483 #[inline(always)]
484 pub fn m_unchecked(&self) -> f64 {
486 self.m2().sqrt()
487 }
488
489 pub fn signed_m(&self) -> LadduPhysicsResult<f64> {
496 self.signed_mag()
497 }
498
499 #[inline(always)]
500 pub fn signed_m_unchecked(&self) -> f64 {
502 self.signed_mag_unchecked()
503 }
504
505 pub fn m2(&self) -> f64 {
507 self.mag2()
508 }
509
510 pub fn dot(&self, other: &Self) -> f64 {
512 self.e * other.e - self.px * other.px - self.py * other.py - self.pz * other.pz
513 }
514
515 pub fn to_p4_string(&self) -> String {
517 let mass = self
518 .m()
519 .map(|m| format!("{m:.5}"))
520 .unwrap_or_else(|_| format!("{:.5}i", (-self.m2()).sqrt()));
521 format!(
522 "[e = {:.5}; p = ({:.5}, {:.5}, {:.5}); m = {}]",
523 self.e(),
524 self.px(),
525 self.py(),
526 self.pz(),
527 mass
528 )
529 }
530
531 pub fn mag(&self) -> LadduPhysicsResult<f64> {
538 let mag2 = self.mag2();
539
540 if !mag2.is_finite() {
541 return Err(LadduPhysicsError::invalid_value(
542 "magnitude squared",
543 "finite",
544 mag2,
545 ));
546 }
547
548 if mag2 < 0.0 {
549 return Err(LadduPhysicsError::invalid_value(
550 "magnitude squared",
551 "nonnegative",
552 mag2,
553 ));
554 }
555
556 Ok(mag2.sqrt())
557 }
558
559 pub fn signed_mag(&self) -> LadduPhysicsResult<f64> {
569 let mag2 = self.mag2();
570
571 if !mag2.is_finite() {
572 return Err(LadduPhysicsError::invalid_value(
573 "magnitude squared",
574 "finite",
575 mag2,
576 ));
577 }
578
579 if mag2 >= 0.0 {
580 Ok(mag2.sqrt())
581 } else {
582 Ok(-(-mag2).sqrt())
583 }
584 }
585
586 #[inline(always)]
587 pub fn signed_mag_unchecked(&self) -> f64 {
589 let mag2 = self.mag2();
590 if mag2 >= 0.0 {
591 mag2.sqrt()
592 } else {
593 -(-mag2).sqrt()
594 }
595 }
596
597 pub fn mag2(&self) -> f64 {
599 self.e * self.e - (self.px * self.px + self.py * self.py + self.pz * self.pz)
600 }
601
602 pub fn boost(&self, beta: &RealVec3) -> Self {
604 let b2 = beta.dot(beta);
605 if b2 == 0.0 {
606 return *self;
607 }
608 let gamma = 1.0 / f64::sqrt(1.0 - b2);
609 let p3 = self.vec3() + beta * ((gamma - 1.0) * self.vec3().dot(beta) / b2 + gamma * self.e);
610 RealVec4::new(gamma * (self.e + beta.dot(&self.vec3())), p3.x, p3.y, p3.z)
611 }
612
613 pub fn vec3(&self) -> RealVec3 {
615 RealVec3 {
616 x: self.px,
617 y: self.py,
618 z: self.pz,
619 }
620 }
621}
622
623impl_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) });
624impl_op_ex!(-|a: &RealVec4, b: &RealVec4| -> RealVec4 {
625 RealVec4::new(a.e - b.e, a.px - b.px, a.py - b.py, a.pz - b.pz)
626});
627impl_op_ex!(-|a: &RealVec4| -> RealVec4 { RealVec4::new(a.e, -a.px, -a.py, -a.pz) });
628
629impl<'a> std::iter::Sum<&'a RealVec4> for RealVec4 {
630 fn sum<I: Iterator<Item = &'a Self>>(iter: I) -> Self {
631 iter.fold(Self::new(0.0, 0.0, 0.0, 0.0), |a, b| a + b)
632 }
633}
634
635impl std::iter::Sum<RealVec4> for RealVec4 {
636 fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
637 iter.fold(Self::new(0.0, 0.0, 0.0, 0.0), |a, b| a + b)
638 }
639}
640
641#[derive(Clone, Debug, Serialize, Deserialize)]
643pub struct Vec3 {
644 pub x: Expr,
646 pub y: Expr,
648 pub z: Expr,
650}
651
652impl Vec3 {
653 pub fn new(x: impl Into<Expr>, y: impl Into<Expr>, z: impl Into<Expr>) -> Self {
655 Self {
656 x: x.into(),
657 y: y.into(),
658 z: z.into(),
659 }
660 }
661
662 pub fn zero() -> Self {
664 Self::new(0.0, 0.0, 0.0)
665 }
666
667 pub fn x() -> Self {
669 Self::new(1.0, 0.0, 0.0)
670 }
671
672 pub fn y() -> Self {
674 Self::new(0.0, 1.0, 0.0)
675 }
676
677 pub fn z() -> Self {
679 Self::new(0.0, 0.0, 1.0)
680 }
681
682 pub fn event(prefix: &str) -> Self {
684 Self::new(
685 event_p4_component(prefix, P4Component::Px),
686 event_p4_component(prefix, P4Component::Py),
687 event_p4_component(prefix, P4Component::Pz),
688 )
689 }
690
691 pub fn px(&self) -> Expr {
693 self.x.clone()
694 }
695
696 pub fn py(&self) -> Expr {
698 self.y.clone()
699 }
700
701 pub fn pz(&self) -> Expr {
703 self.z.clone()
704 }
705
706 pub fn dot(&self, other: &Self) -> Expr {
708 &self.x * &other.x + &self.y * &other.y + &self.z * &other.z
709 }
710
711 pub fn cross(&self, other: &Self) -> Self {
713 Self::new(
714 &self.y * &other.z - &other.y * &self.z,
715 &self.z * &other.x - &other.z * &self.x,
716 &self.x * &other.y - &other.x * &self.y,
717 )
718 }
719
720 pub fn mag2(&self) -> Expr {
722 self.dot(self)
723 }
724
725 pub fn mag(&self) -> Expr {
727 self.mag2().sqrt()
728 }
729
730 pub fn costheta(&self) -> Expr {
732 &self.z / self.mag()
733 }
734
735 pub fn unit(&self) -> Self {
737 self / &self.mag()
738 }
739
740 pub fn phi(&self) -> Expr {
742 atan2(self.py(), self.px())
743 }
744
745 pub fn with_mass(&self, mass: impl Into<Expr>) -> Vec4 {
747 let mass = mass.into();
748 Vec4::new(
749 (mass.powi(2) + self.mag2()).sqrt(),
750 self.px(),
751 self.py(),
752 self.pz(),
753 )
754 }
755
756 pub fn with_energy(&self, energy: impl Into<Expr>) -> Vec4 {
758 Vec4::new(energy, self.px(), self.py(), self.pz())
759 }
760
761 pub fn as_expr(&self) -> Expr {
763 vector([self.x.clone(), self.y.clone(), self.z.clone()])
764 }
765
766 fn scale(&self, scalar: impl Into<Expr>) -> Self {
767 let scalar = scalar.into();
768 Self::new(&self.x * &scalar, &self.y * &scalar, &self.z * scalar)
769 }
770}
771
772impl From<RealVec3> for Vec3 {
773 fn from(value: RealVec3) -> Self {
774 Self::new(value.x, value.y, value.z)
775 }
776}
777
778impl Default for Vec3 {
779 fn default() -> Self {
780 Self::zero()
781 }
782}
783
784impl_op_ex!(+ |a: &Vec3, b: &Vec3| -> Vec3 { Vec3::new(&a.x + &b.x, &a.y + &b.y, &a.z + &b.z) });
785impl_op_ex!(-|a: &Vec3, b: &Vec3| -> Vec3 { Vec3::new(&a.x - &b.x, &a.y - &b.y, &a.z - &b.z) });
786impl_op_ex!(-|a: &Vec3| -> Vec3 { Vec3::new(-&a.x, -&a.y, -&a.z) });
787impl_op_ex!(*|a: &Vec3, b: &Expr| -> Vec3 { a.scale(b) });
788impl_op_ex!(*|a: &Expr, b: &Vec3| -> Vec3 { b.scale(a) });
789impl_op_ex!(*|a: &Vec3, b: &f64| -> Vec3 { a.scale(b) });
790impl_op_ex!(*|a: &f64, b: &Vec3| -> Vec3 { b.scale(a) });
791impl_op_ex!(/ |a: &Vec3, b: &Expr| -> Vec3 {
792 Vec3::new(&a.x / b, &a.y / b, &a.z / b)
793});
794impl_op_ex!(/ |a: &Vec3, b: &f64| -> Vec3 {
795 Vec3::new(&a.x / b, &a.y / b, &a.z / b)
796});
797
798#[derive(Clone, Debug, Serialize, Deserialize)]
800pub struct Vec4 {
801 pub e: Expr,
803 pub px: Expr,
805 pub py: Expr,
807 pub pz: Expr,
809}
810
811impl Vec4 {
812 pub fn new(
814 e: impl Into<Expr>,
815 px: impl Into<Expr>,
816 py: impl Into<Expr>,
817 pz: impl Into<Expr>,
818 ) -> Self {
819 Self {
820 e: e.into(),
821 px: px.into(),
822 py: py.into(),
823 pz: pz.into(),
824 }
825 }
826
827 pub fn event(prefix: &str) -> Self {
829 Self::new(
830 event_p4_component(prefix, P4Component::E),
831 event_p4_component(prefix, P4Component::Px),
832 event_p4_component(prefix, P4Component::Py),
833 event_p4_component(prefix, P4Component::Pz),
834 )
835 }
836
837 pub fn px(&self) -> Expr {
839 self.px.clone()
840 }
841
842 pub fn py(&self) -> Expr {
844 self.py.clone()
845 }
846
847 pub fn pz(&self) -> Expr {
849 self.pz.clone()
850 }
851
852 pub fn e(&self) -> Expr {
854 self.e.clone()
855 }
856
857 pub fn momentum(&self) -> Vec3 {
859 self.vec3()
860 }
861
862 pub fn vec3(&self) -> Vec3 {
864 Vec3::new(self.px(), self.py(), self.pz())
865 }
866
867 pub fn beta(&self) -> Vec3 {
869 self.momentum() / self.e()
870 }
871
872 pub fn gamma(&self) -> Expr {
874 1.0 / (1.0 - self.beta().mag2()).sqrt()
875 }
876
877 pub fn m2(&self) -> Expr {
879 self.mag2()
880 }
881
882 pub fn m(&self) -> Expr {
884 self.mag()
885 }
886
887 pub fn dot(&self, other: &Self) -> Expr {
889 &self.e * &other.e - &self.px * &other.px - &self.py * &other.py - &self.pz * &other.pz
890 }
891
892 pub fn mag2(&self) -> Expr {
894 self.dot(self)
895 }
896
897 pub fn mag(&self) -> Expr {
899 self.mag2().sqrt()
900 }
901
902 pub fn boost(&self, beta: &Vec3) -> Self {
904 let b2 = beta.dot(beta);
905 let gamma = (1.0 - &b2).sqrt();
906 let gamma = 1.0 / gamma;
907 let boost_factor = gamma.powi(2) / (&gamma + 1.0);
908 let p3 = self.vec3() + beta * ((boost_factor * self.vec3().dot(beta)) + &gamma * &self.e);
909 Self::new(gamma * (&self.e + beta.dot(&self.vec3())), p3.x, p3.y, p3.z)
910 }
911
912 pub fn as_expr(&self) -> Expr {
914 vector([
915 self.e.clone(),
916 self.px.clone(),
917 self.py.clone(),
918 self.pz.clone(),
919 ])
920 }
921}
922
923impl From<RealVec4> for Vec4 {
924 fn from(value: RealVec4) -> Self {
925 Self::new(value.e, value.px, value.py, value.pz)
926 }
927}
928
929impl_op_ex!(+ |a: &Vec4, b: &Vec4| -> Vec4 {
930 Vec4::new(&a.e + &b.e, &a.px + &b.px, &a.py + &b.py, &a.pz + &b.pz)
931});
932impl_op_ex!(-|a: &Vec4, b: &Vec4| -> Vec4 {
933 Vec4::new(&a.e - &b.e, &a.px - &b.px, &a.py - &b.py, &a.pz - &b.pz)
934});
935impl_op_ex!(-|a: &Vec4| -> Vec4 { Vec4::new(-&a.e, -&a.px, -&a.py, -&a.pz) });
936
937#[cfg(test)]
938mod tests {
939 use approx::{assert_abs_diff_eq, assert_relative_eq};
940 use laddu_compile::CompiledModel;
941 use laddu_runtime::CpuBackend;
942 use nalgebra::{Vector3, Vector4};
943 use num::complex::Complex64;
944
945 use super::*;
946
947 fn evaluate(expr: laddu_expr::Expr) -> Complex64 {
948 let model = CompiledModel::from_expr(&expr).unwrap();
949 let params = model.params().default_values();
950 CpuBackend.prepare(&model).evaluate(¶ms).unwrap()
951 }
952
953 #[test]
954 fn test_display() {
955 let v3 = RealVec3::new(1.2341, -2.3452, 3.4563);
956 assert_eq!(format!("{}", v3), "[ 1.234, -2.345, 3.456]");
957 let v4 = RealVec4::new(4.5674, 1.2341, -2.3452, 3.4563);
958 assert_eq!(format!("{}", v4), "[ 4.567; 1.234, -2.345, 3.456]");
959 }
960
961 #[test]
962 fn test_vec_vector_conversion() {
963 let v = RealVec3::new(1.0, 2.0, 3.0);
964 let vector3: Vec<f64> = v.into();
965 assert_eq!(vector3[0], 1.0);
966 assert_eq!(vector3[1], 2.0);
967 assert_eq!(vector3[2], 3.0);
968
969 let v_from_vec: RealVec3 = vector3.try_into().unwrap();
970 assert_eq!(v_from_vec, v);
971
972 let v = RealVec4::new(1.0, 2.0, 3.0, 4.0);
973 let vector4: Vec<f64> = v.into();
974 assert_eq!(vector4[0], 1.0);
975 assert_eq!(vector4[1], 2.0);
976 assert_eq!(vector4[2], 3.0);
977 assert_eq!(vector4[3], 4.0);
978
979 let v_from_vec: RealVec4 = vector4.try_into().unwrap();
980 assert_eq!(v_from_vec, v);
981 }
982
983 #[test]
984 fn test_vec_array_conversion() {
985 let arr = [1.0, 2.0, 3.0];
986 let v: RealVec3 = arr.into();
987 assert_eq!(v, RealVec3::new(1.0, 2.0, 3.0));
988
989 let back_to_array: [f64; 3] = v.into();
990 assert_eq!(back_to_array, arr);
991
992 let arr = [1.0, 2.0, 3.0, 4.0];
993 let v: RealVec4 = arr.into();
994 assert_eq!(v, RealVec4::new(1.0, 2.0, 3.0, 4.0));
995
996 let back_to_array: [f64; 4] = v.into();
997 assert_eq!(back_to_array, arr);
998 }
999
1000 #[test]
1001 fn test_vec_nalgebra_conversion() {
1002 let v = RealVec3::new(1.0, 2.0, 3.0);
1003 let vector3: Vector3<f64> = v.into();
1004 assert_eq!(vector3.x, 1.0);
1005 assert_eq!(vector3.y, 2.0);
1006 assert_eq!(vector3.z, 3.0);
1007
1008 let v_from_vec: RealVec3 = vector3.into();
1009 assert_eq!(v_from_vec, v);
1010
1011 let v = RealVec4::new(1.0, 2.0, 3.0, 4.0);
1012 let vector4: Vector4<f64> = v.into();
1013 assert_eq!(vector4.x, 1.0);
1014 assert_eq!(vector4.y, 2.0);
1015 assert_eq!(vector4.z, 3.0);
1016 assert_eq!(vector4.w, 4.0);
1017
1018 let v_from_vec: RealVec4 = vector4.into();
1019 assert_eq!(v_from_vec, v);
1020 }
1021
1022 #[test]
1023 fn test_vec_sums() {
1024 let vectors = [RealVec3::new(1.0, 2.0, 3.0), RealVec3::new(4.0, 5.0, 6.0)];
1025 let sum: RealVec3 = vectors.iter().sum();
1026 assert_eq!(sum, RealVec3::new(5.0, 7.0, 9.0));
1027 let sum: RealVec3 = vectors.into_iter().sum();
1028 assert_eq!(sum, RealVec3::new(5.0, 7.0, 9.0));
1029
1030 let vectors = [
1031 RealVec4::new(1.0, 2.0, 3.0, 4.0),
1032 RealVec4::new(4.0, 5.0, 6.0, 7.0),
1033 ];
1034 let sum: RealVec4 = vectors.iter().sum();
1035 assert_eq!(sum, RealVec4::new(5.0, 7.0, 9.0, 11.0));
1036 let sum: RealVec4 = vectors.into_iter().sum();
1037 assert_eq!(sum, RealVec4::new(5.0, 7.0, 9.0, 11.0));
1038 }
1039
1040 #[test]
1041 fn test_three_to_four_momentum_conversion() {
1042 let p3 = RealVec3::new(1.0, 2.0, 3.0);
1043 let target_p4 = RealVec4::new(10.0, 1.0, 2.0, 3.0);
1044 let p4_from_mass = p3.with_mass(target_p4.m().unwrap());
1045 assert_eq!(target_p4.e(), p4_from_mass.e());
1046 assert_eq!(target_p4.px(), p4_from_mass.px());
1047 assert_eq!(target_p4.py(), p4_from_mass.py());
1048 assert_eq!(target_p4.pz(), p4_from_mass.pz());
1049 let p4_from_energy = p3.with_energy(target_p4.e());
1050 assert_eq!(target_p4.e(), p4_from_energy.e());
1051 assert_eq!(target_p4.px(), p4_from_energy.px());
1052 assert_eq!(target_p4.py(), p4_from_energy.py());
1053 assert_eq!(target_p4.pz(), p4_from_energy.pz());
1054 }
1055
1056 #[test]
1057 fn test_four_momentum_basics() {
1058 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1059 assert_eq!(p.e(), 10.0);
1060 assert_eq!(p.px(), 3.0);
1061 assert_eq!(p.py(), 4.0);
1062 assert_eq!(p.pz(), 5.0);
1063 assert_eq!(p.momentum().px(), 3.0);
1064 assert_eq!(p.momentum().py(), 4.0);
1065 assert_eq!(p.momentum().pz(), 5.0);
1066 assert_relative_eq!(p.beta().unwrap().x, 0.3);
1067 assert_relative_eq!(p.beta().unwrap().y, 0.4);
1068 assert_relative_eq!(p.beta().unwrap().z, 0.5);
1069 assert_relative_eq!(p.m2(), 50.0);
1070 assert_relative_eq!(p.m().unwrap(), f64::sqrt(50.0));
1071 assert_eq!(
1072 p.to_p4_string().to_string(),
1073 "[e = 10.00000; p = (3.00000, 4.00000, 5.00000); m = 7.07107]"
1074 );
1075 assert_relative_eq!(RealVec3::x().x, 1.0);
1076 assert_relative_eq!(RealVec3::x().y, 0.0);
1077 assert_relative_eq!(RealVec3::x().z, 0.0);
1078 assert_relative_eq!(RealVec3::y().x, 0.0);
1079 assert_relative_eq!(RealVec3::y().y, 1.0);
1080 assert_relative_eq!(RealVec3::y().z, 0.0);
1081 assert_relative_eq!(RealVec3::z().x, 0.0);
1082 assert_relative_eq!(RealVec3::z().y, 0.0);
1083 assert_relative_eq!(RealVec3::z().z, 1.0);
1084 assert_relative_eq!(RealVec3::default().x, 0.0);
1085 assert_relative_eq!(RealVec3::default().y, 0.0);
1086 assert_relative_eq!(RealVec3::default().z, 0.0);
1087 }
1088
1089 #[test]
1090 fn test_three_momentum_basics() {
1091 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1092 let q = RealVec4::new(0.0, 1.2, -3.4, 7.6);
1093 let p3_view = p.momentum();
1094 let q3_view = q.momentum();
1095 assert_eq!(p3_view.px(), 3.0);
1096 assert_eq!(p3_view.py(), 4.0);
1097 assert_eq!(p3_view.pz(), 5.0);
1098 assert_relative_eq!(p3_view.mag2(), 50.0);
1099 assert_relative_eq!(p3_view.mag(), f64::sqrt(50.0));
1100 assert_relative_eq!(p3_view.costheta().unwrap(), 5.0 / f64::sqrt(50.0));
1101 assert_relative_eq!(p3_view.theta().unwrap(), f64::acos(5.0 / f64::sqrt(50.0)));
1102 assert_relative_eq!(p3_view.phi(), f64::atan2(4.0, 3.0));
1103 assert_relative_eq!(
1104 p3_view.unit().unwrap(),
1105 RealVec3::new(
1106 3.0 / f64::sqrt(50.0),
1107 4.0 / f64::sqrt(50.0),
1108 5.0 / f64::sqrt(50.0)
1109 )
1110 );
1111 assert_relative_eq!(p3_view.cross(&q3_view), RealVec3::new(47.4, -16.8, -15.0));
1112 }
1113
1114 #[test]
1115 fn test_vec_equality() {
1116 let p = RealVec3::new(1.1, 2.2, 3.3);
1117 let p2 = RealVec3::new(1.1 * 2.0, 2.2 * 2.0, 3.3 * 2.0);
1118 assert_abs_diff_eq!(p * 2.0, p2);
1119 assert_relative_eq!(p * 2.0, p2);
1120 }
1121
1122 #[test]
1123 fn test_boost_com() {
1124 let p = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1125 let zero = p.boost(&-p.beta().unwrap()).momentum();
1126 assert_relative_eq!(zero, RealVec3::zero());
1127 }
1128
1129 #[test]
1130 fn test_boost() {
1131 let p0 = RealVec4::new(1.0, 0.0, 0.0, 0.0);
1132 assert_relative_eq!(p0.gamma().unwrap(), 1.0);
1133 let p0 = RealVec4::new(1.0, f64::sqrt(3.0) / 2.0, 0.0, 0.0);
1134 assert_relative_eq!(p0.gamma().unwrap(), 2.0);
1135 let p1 = RealVec4::new(10.0, 3.0, 4.0, 5.0);
1136 let p2 = RealVec4::new(9.0, 3.4, 2.3, 1.2);
1137 let p1_boosted = p1.boost(&-p2.beta().unwrap());
1138 assert_relative_eq!(p1_boosted.e(), 8.157632144622882);
1139 assert_relative_eq!(p1_boosted.px(), -0.6489200627053444);
1140 assert_relative_eq!(p1_boosted.py(), 1.5316128987581492);
1141 assert_relative_eq!(p1_boosted.pz(), 3.712145860221643);
1142 }
1143
1144 #[test]
1145 fn expression_vectors_build_and_evaluate_scalar_observables() {
1146 let p = Vec4::new(10.0, 3.0, 4.0, 5.0);
1147 assert_eq!(evaluate(p.m2()), Complex64::from(50.0));
1148 assert_eq!(evaluate(p.momentum().mag2()), Complex64::from(50.0));
1149
1150 let a = Vec3::new(1.0, 2.0, 3.0);
1151 let b = Vec3::new(4.0, 5.0, 6.0);
1152 assert_eq!(evaluate(a.dot(&b)), Complex64::from(32.0));
1153 assert_eq!(evaluate(a.cross(&b).z), Complex64::from(-3.0));
1154 }
1155}