1use serde::{Deserialize, Serialize};
7use serde_with::serde_as;
8use std::{
9 array, fmt,
10 iter::{Sum, zip},
11 ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign},
12};
13
14use approxim::approx_derive::RelativeEq;
15use rand::{
16 Rng,
17 distr::{Distribution, StandardUniform, Uniform},
18};
19
20use crate::{Cross, Error, InnerProduct, Metric, Outer, Rotate, Unit, Vector, Wedge};
21use hoomd_linear_algebra::{MatMul, matrix::Matrix};
22
23#[serde_as]
94#[derive(Clone, Copy, Debug, PartialEq, RelativeEq, Serialize, Deserialize)]
95#[approx(epsilon_type = f64)]
96pub struct Cartesian<const N: usize> {
97 #[serde_as(as = "[_; N]")]
99 #[approx(into_iter)]
100 pub coordinates: [f64; N],
101}
102
103impl<const N: usize> Default for Cartesian<N> {
104 #[inline]
114 fn default() -> Self {
115 Cartesian::from([0.0; N])
116 }
117}
118
119impl<const N: usize> From<[f64; N]> for Cartesian<N> {
120 #[inline]
129 fn from(coordinates: [f64; N]) -> Self {
130 Self { coordinates }
131 }
132}
133
134impl<const N: usize> IntoIterator for Cartesian<N> {
135 type Item = f64;
136 type IntoIter = <[f64; N] as IntoIterator>::IntoIter;
137
138 #[inline]
152 fn into_iter(self) -> Self::IntoIter {
153 self.coordinates.into_iter()
154 }
155}
156
157impl From<(f64, f64)> for Cartesian<2> {
158 #[inline]
159 fn from(coordinates: (f64, f64)) -> Self {
160 Self {
161 coordinates: coordinates.into(),
162 }
163 }
164}
165
166impl From<(f64, f64, f64)> for Cartesian<3> {
167 #[inline]
168 fn from(coordinates: (f64, f64, f64)) -> Self {
169 Self {
170 coordinates: coordinates.into(),
171 }
172 }
173}
174
175impl<const N: usize> TryFrom<Vec<f64>> for Cartesian<N> {
176 type Error = Error;
177
178 #[inline]
197 fn try_from(value: Vec<f64>) -> Result<Self, Self::Error> {
198 let coordinates = value.try_into().map_err(|_| Error::InvalidVectorLength)?;
199 Ok(Self { coordinates })
200 }
201}
202
203impl<const N: usize> TryFrom<std::ops::Range<usize>> for Cartesian<N> {
204 type Error = Error;
205
206 #[inline]
219 fn try_from(value: std::ops::Range<usize>) -> Result<Self, Self::Error> {
220 if value.len() != N {
221 return Err(Error::InvalidVectorLength);
222 }
223
224 let coordinates = array::from_fn(|i| (value.start + i) as f64);
225 Ok(Self { coordinates })
226 }
227}
228
229impl<const N: usize> TryFrom<[f64; N]> for Unit<Cartesian<N>> {
230 type Error = Error;
231
232 #[inline]
245 fn try_from(value: [f64; N]) -> Result<Self, Self::Error> {
246 Cartesian::from(value).to_unit().map(|t| t.0)
247 }
248}
249
250impl<const N: usize> Vector for Cartesian<N> {}
251
252impl<const N: usize> InnerProduct for Cartesian<N> {
253 #[inline]
254 fn dot(&self, other: &Self) -> f64 {
255 zip(self.coordinates.iter(), other.coordinates.iter())
256 .fold(0.0, |product, (&x, &y)| x.mul_add(y, product))
257 }
258
259 #[inline]
274 fn default_unit() -> Unit<Self> {
275 assert!(N >= 1);
276 let mut coordinates = [0.0; N];
277 coordinates[N - 1] = 1.0;
278 Unit(Self { coordinates })
279 }
280}
281
282impl<const N: usize> Metric for Cartesian<N> {
283 #[inline]
300 fn distance_squared(&self, other: &Self) -> f64 {
301 zip(self.coordinates.iter(), other.coordinates.iter())
302 .fold(0.0, |product, x| product + (x.0 - x.1).powi(2))
303 }
304 #[inline]
305 fn distance(&self, other: &Self) -> f64 {
306 (self.distance_squared(other)).sqrt()
307 }
308 #[inline]
321 fn n_dimensions() -> usize {
322 N
323 }
324}
325
326impl<const N: usize> Add for Cartesian<N> {
327 type Output = Self;
328
329 #[inline]
330 fn add(self, rhs: Self) -> Self {
331 let mut coordinates = [0.0; N];
332
333 for (result, (a, b)) in coordinates
334 .iter_mut()
335 .zip(self.coordinates.iter().zip(rhs.coordinates.iter()))
336 {
337 *result = a + b;
338 }
339 Self { coordinates }
340 }
341}
342
343impl<const N: usize> AddAssign for Cartesian<N> {
344 #[inline]
345 fn add_assign(&mut self, rhs: Self) {
346 for (result, a) in self.coordinates.iter_mut().zip(rhs.coordinates.iter()) {
347 *result += a;
348 }
349 }
350}
351
352impl<const N: usize> Div<f64> for Cartesian<N> {
353 type Output = Self;
354
355 #[inline]
356 fn div(self, rhs: f64) -> Self {
357 Self {
358 coordinates: self.coordinates.map(|x| x / rhs),
359 }
360 }
361}
362
363impl<const N: usize> DivAssign<f64> for Cartesian<N> {
364 #[inline]
365 fn div_assign(&mut self, rhs: f64) {
366 self.coordinates = self.coordinates.map(|x| x / rhs);
367 }
368}
369
370impl<const N: usize> Mul<f64> for Cartesian<N> {
371 type Output = Self;
372
373 #[inline]
374 fn mul(self, rhs: f64) -> Self {
375 Self {
376 coordinates: self.coordinates.map(|x| x * rhs),
377 }
378 }
379}
380
381impl<const N: usize> MulAssign<f64> for Cartesian<N> {
382 #[inline]
383 fn mul_assign(&mut self, rhs: f64) {
384 self.coordinates = self.coordinates.map(|x| x * rhs);
385 }
386}
387
388impl<const N: usize> Sub for Cartesian<N> {
389 type Output = Self;
390
391 #[inline]
392 fn sub(self, rhs: Self) -> Self {
393 let mut coordinates = [0.0; N];
394 for (result, (a, b)) in coordinates
395 .iter_mut()
396 .zip(self.coordinates.iter().zip(rhs.coordinates.iter()))
397 {
398 *result = a - b;
399 }
400 Self { coordinates }
401 }
402}
403
404impl<const N: usize> SubAssign for Cartesian<N> {
405 #[inline]
406 fn sub_assign(&mut self, rhs: Self) {
407 for (result, a) in self.coordinates.iter_mut().zip(rhs.coordinates) {
408 *result -= a;
409 }
410 }
411}
412
413impl<const N: usize> fmt::Display for Cartesian<N> {
414 #[inline]
415 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
416 write!(
417 f,
418 "[{}]",
419 self.coordinates
420 .iter()
421 .map(f64::to_string)
422 .collect::<Vec<String>>()
423 .join(", ")
424 )
425 }
426}
427
428impl<const N: usize> Neg for Cartesian<N> {
429 type Output = Self;
430 #[inline]
431 fn neg(self) -> Self::Output {
432 Self {
433 coordinates: self.coordinates.map(|x| -x),
434 }
435 }
436}
437
438impl Cross for Cartesian<3> {
439 #[inline]
440 fn cross(&self, other: &Self) -> Self {
441 Cartesian::from((
442 self[1] * other[2] - self[2] * other[1],
443 self[2] * other[0] - self[0] * other[2],
444 self[0] * other[1] - self[1] * other[0],
445 ))
446 }
447}
448
449impl Wedge for Cartesian<3> {
450 type Bivector = Cartesian<3>;
451
452 #[inline]
453 fn wedge(&self, other: &Self) -> Self::Bivector {
469 self.cross(other)
470 }
471}
472
473impl Cartesian<4> {
474 #[inline]
488 #[must_use]
489 pub fn counary_cross(vectors: &[Self; 3]) -> Self {
490 #[inline(always)]
493 fn diff_of_products(a: f64, b: f64, c: f64, d: f64) -> f64 {
494 let cd = c * d;
495 let err = (-c).mul_add(d, cd);
496 let dop = a.mul_add(b, -cd);
497 dop + err
498 }
499 let u = &vectors[0];
500 let v = &vectors[1];
501 let w = &vectors[2];
502
503 let m01 = diff_of_products(v[0], w[1], v[1], w[0]);
505 let m02 = diff_of_products(v[0], w[2], v[2], w[0]);
506 let m03 = diff_of_products(v[0], w[3], v[3], w[0]);
507 let m12 = diff_of_products(v[1], w[2], v[2], w[1]);
508 let m13 = diff_of_products(v[1], w[3], v[3], w[1]);
509 let m23 = diff_of_products(v[2], w[3], v[3], w[2]);
510
511 [
514 (u[1] * m23 - u[2] * m13 + u[3] * m12),
515 -(u[0] * m23 - u[2] * m03 + u[3] * m02),
516 (u[0] * m13 - u[1] * m03 + u[3] * m01),
517 -(u[0] * m12 - u[1] * m02 + u[2] * m01),
518 ]
519 .into()
520 }
521}
522
523impl<const N: usize> Distribution<Cartesian<N>> for StandardUniform {
524 #[inline]
541 fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Cartesian<N> {
542 let uniform = Uniform::new_inclusive(-1.0, 1.0)
543 .expect("hard-coded range should form a valid distribution");
544 Cartesian {
545 coordinates: array::from_fn(|_| uniform.sample(rng)),
546 }
547 }
548}
549
550impl<const N: usize, T> Index<T> for Cartesian<N>
551where
552 T: Into<usize> + std::slice::SliceIndex<[f64], Output = f64>,
553{
554 type Output = f64;
555 #[inline]
568 fn index(&self, index: T) -> &Self::Output {
569 &self.coordinates[index]
570 }
571}
572
573impl Cartesian<2> {
574 #[inline]
590 #[must_use]
591 pub fn perpendicular(self) -> Self {
592 Cartesian::from([-self[1], self[0]])
593 }
594}
595
596impl Wedge for Cartesian<2> {
597 type Bivector = f64;
598
599 #[inline]
616 fn wedge(&self, other: &Self) -> Self::Bivector {
617 self[0] * other[1] - self[1] * other[0]
618 }
619}
620
621impl<const N: usize> Outer for Cartesian<N> {
622 type Tensor = Matrix<N, N>;
623
624 #[inline]
625 fn outer(&self, other: &Self) -> Self::Tensor {
626 self.to_column_matrix().matmul(&other.to_row_matrix())
627 }
628}
629
630impl<const N: usize, T> IndexMut<T> for Cartesian<N>
631where
632 T: Into<usize> + std::slice::SliceIndex<[f64], Output = f64>,
633{
634 #[inline]
649 fn index_mut(&mut self, index: T) -> &mut Self::Output {
650 &mut self.coordinates[index]
651 }
652}
653
654impl<const N: usize> Sum for Cartesian<N> {
655 #[inline]
656 fn sum<I>(iter: I) -> Self
657 where
658 I: Iterator<Item = Self>,
659 {
660 iter.fold(Cartesian::default(), |acc, x| acc + x)
661 }
662}
663
664#[serde_as]
677#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
678pub struct RotationMatrix<const N: usize> {
679 #[serde_as(as = "[_; N]")]
681 pub(crate) rows: [Cartesian<N>; N],
682}
683
684impl<const N: usize> From<RotationMatrix<N>> for Matrix<N, N> {
685 #[inline]
686 fn from(value: RotationMatrix<N>) -> Self {
687 Self {
688 rows: value.rows().map(|arr| arr.coordinates),
689 }
690 }
691}
692
693impl<const N: usize> RotationMatrix<N> {
694 #[inline]
708 #[must_use]
709 pub fn rows(&self) -> [Cartesian<N>; N] {
710 self.rows
711 }
712
713 #[inline]
732 #[must_use]
733 pub fn inverted(self) -> Self {
734 Self {
735 rows: array::from_fn(|i| array::from_fn::<_, N, _>(|j| self.rows()[j][i]).into()),
736 }
737 }
738}
739
740impl<const N: usize> Default for RotationMatrix<N> {
741 #[inline]
760 fn default() -> RotationMatrix<N> {
761 RotationMatrix {
762 rows: array::from_fn(|i| array::from_fn(|j| if i == j { 1.0 } else { 0.0 }).into()),
763 }
764 }
765}
766
767impl<const N: usize> fmt::Display for RotationMatrix<N> {
768 #[inline]
769 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
770 write!(
771 f,
772 "[{}]",
773 self.rows
774 .iter()
775 .map(Cartesian::<N>::to_string)
776 .collect::<Vec<String>>()
777 .join("\n ")
778 )
779 }
780}
781
782impl<const N: usize> Rotate<Cartesian<N>> for RotationMatrix<N> {
783 type Matrix = RotationMatrix<N>;
784
785 #[inline]
786 fn rotate(&self, vector: &Cartesian<N>) -> Cartesian<N> {
818 let mut coordinates = [0.0; N];
819
820 for (result, row) in coordinates.iter_mut().zip(self.rows.iter()) {
821 *result = row.dot(vector);
822 }
823
824 Cartesian { coordinates }
825 }
826}
827
828impl<const N: usize, const K: usize> MatMul<Matrix<N, K>> for RotationMatrix<N> {
829 type Output = Matrix<N, K>;
830
831 #[inline]
832 fn matmul(&self, rhs: &Matrix<N, K>) -> Self::Output {
833 Matrix::from(*self).matmul(rhs)
834 }
835}
836
837impl<const N: usize> Cartesian<N> {
838 #[inline]
851 #[must_use]
852 pub fn to_row_matrix(self) -> Matrix<1, N> {
853 Matrix {
854 rows: [self.coordinates],
855 }
856 }
857 #[inline]
870 #[must_use]
871 pub fn to_column_matrix(self) -> Matrix<N, 1> {
872 Matrix {
873 rows: std::array::from_fn(|i| [self[i]]),
874 }
875 }
876}
877
878impl<const N: usize> From<Matrix<1, N>> for Cartesian<N> {
879 #[inline]
880 fn from(value: Matrix<1, N>) -> Self {
881 value.rows[0].into()
882 }
883}
884
885#[cfg(test)]
886mod tests {
887 use crate::{Angle, Rotation, Versor};
888
889 use super::*;
890 use approxim::assert_relative_eq;
891 use paste::paste;
892 use rand::{RngExt, SeedableRng, rngs::StdRng};
893 use rstest::rstest;
894
895 macro_rules! parameterize_vector_length {
897 ($test_body:ident, [$($dim:expr),*]) => {
900
901 $(
903 paste! {
905 #[test]
906 fn [< $test_body "_" $dim>]() {
907 const DIM: usize = $dim;
908 $test_body::<DIM>();
909 }
910 }
911 )*
912 };
913 }
914
915 fn generate_vector_pair<const N: usize>() -> (Cartesian<N>, Cartesian<N>) {
918 (
919 Cartesian::try_from(0..N).unwrap(),
920 Cartesian::try_from(N..N * 2).unwrap(),
921 )
922 }
923
924 fn index<const N: usize>() {
925 let (_, b) = generate_vector_pair::<N>();
926 assert!(zip(0..N, b.coordinates.iter()).all(|(i, &x)| b[i] == x));
927 }
928 parameterize_vector_length!(index, [2, 3, 4, 8, 16, 32]);
929
930 fn index_mut<const N: usize>() {
931 let (a, mut b) = generate_vector_pair::<N>();
932 zip(0..N, b.coordinates.iter_mut()).for_each(|(i, x)| *x = a[i]);
933 assert_eq!(a, b);
934 }
935 parameterize_vector_length!(index_mut, [2, 3, 4, 8, 16, 32]);
936
937 fn add_explicit<const N: usize>() {
938 let (a, b) = generate_vector_pair::<N>();
939 let c = a.add(b);
940
941 let addition_answer: Vec<f64> = (0..(2 * N))
942 .step_by(2)
943 .map(|x| (x + N) as f64)
944 .collect::<Vec<_>>();
945
946 assert_eq!(c, Cartesian::try_from(addition_answer).unwrap());
947 }
948 parameterize_vector_length!(add_explicit, [2, 3, 4, 8, 16, 32]);
949
950 fn add_operator<const N: usize>() {
951 let (a, b) = generate_vector_pair::<N>();
952 let c = a + b;
953
954 let addition_answer: Vec<f64> = (0..(2 * N))
955 .step_by(2)
956 .map(|x| (x + N) as f64)
957 .collect::<Vec<_>>();
958
959 assert_eq!(c, Cartesian::try_from(addition_answer).unwrap());
960 }
961 parameterize_vector_length!(add_operator, [2, 3, 4, 8, 16, 32]);
962
963 fn add_assign<const N: usize>() {
964 let (a, b) = generate_vector_pair::<N>();
965 let mut c = a;
966 c += b;
967
968 let addition_answer: Vec<f64> = (0..(2 * N))
969 .step_by(2)
970 .map(|x| (x + N) as f64)
971 .collect::<Vec<_>>();
972
973 assert_eq!(c, Cartesian::try_from(addition_answer).unwrap());
974 }
975 parameterize_vector_length!(add_assign, [2, 3, 4, 8, 16, 32]);
976
977 fn sub_operator<const N: usize>() {
978 let (a, b) = generate_vector_pair::<N>();
979 let c = a - b;
980
981 let subtraction_answer = [-(N as f64); N];
982
983 assert_eq!(c, subtraction_answer.into());
984 }
985 parameterize_vector_length!(sub_operator, [2, 3, 4, 8, 16, 32]);
986
987 fn sub_assign<const N: usize>() {
988 let (a, b) = generate_vector_pair::<N>();
989 let mut c = a;
990 c -= b;
991
992 let subtraction_answer = [-(N as f64); N];
993
994 assert_eq!(c, subtraction_answer.into());
995 }
996
997 parameterize_vector_length!(sub_assign, [2, 3, 4, 8, 16, 32]);
998
999 fn mul_operator<const N: usize>() {
1000 let (a, _) = generate_vector_pair::<N>();
1001 let b = 12.0;
1002 let c = a * b;
1003
1004 let multiplication_answer: Vec<f64> = (0..N).map(|x| (x as f64) * b).collect::<Vec<_>>();
1005
1006 assert_eq!(c, Cartesian::try_from(multiplication_answer).unwrap());
1007 }
1008 parameterize_vector_length!(mul_operator, [2, 3, 4, 8, 16, 32]);
1009
1010 fn mul_assign<const N: usize>() {
1011 let (mut a, _) = generate_vector_pair::<N>();
1012 let b = 12.0;
1013 a *= b;
1014
1015 let multiplication_answer: Vec<f64> = (0..N).map(|x| (x as f64) * b).collect::<Vec<_>>();
1016
1017 assert_eq!(a, Cartesian::try_from(multiplication_answer).unwrap());
1018 }
1019
1020 parameterize_vector_length!(mul_assign, [2, 3, 4, 8, 16, 32]);
1021
1022 fn div_operator<const N: usize>() {
1023 let (a, _) = generate_vector_pair::<N>();
1024 let b = 12.0;
1025 let c = a / b;
1026
1027 let division_answer: Vec<f64> = (0..N).map(|x| (x as f64) / b).collect::<Vec<_>>();
1028
1029 assert_eq!(c, Cartesian::try_from(division_answer).unwrap());
1030 }
1031 parameterize_vector_length!(div_operator, [2, 3, 4, 8, 16, 32]);
1032
1033 fn div_assign<const N: usize>() {
1034 let (mut a, _) = generate_vector_pair::<N>();
1035 let b = 12.0;
1036 a /= b;
1037
1038 let division_answer: Vec<f64> = (0..N).map(|x| (x as f64) / b).collect::<Vec<_>>();
1039
1040 assert_eq!(a, Cartesian::try_from(division_answer).unwrap());
1041 }
1042
1043 parameterize_vector_length!(div_assign, [2, 3, 4, 8, 16, 32]);
1044
1045 fn compute_add_ref_ref<const N: usize>(a: &Cartesian<N>, b: &Cartesian<N>) -> Cartesian<N> {
1046 *a + *b
1047 }
1048
1049 fn compute_add_ref_type<const N: usize>(a: &Cartesian<N>, b: Cartesian<N>) -> Cartesian<N> {
1050 *a + b
1051 }
1052
1053 fn compute_add_type_ref<const N: usize>(a: Cartesian<N>, b: &Cartesian<N>) -> Cartesian<N> {
1054 a + *b
1055 }
1056
1057 fn add_with_refs<const N: usize>() {
1058 let (a, b) = generate_vector_pair::<N>();
1059
1060 let addition_answer = Cartesian::try_from(
1061 (0..(2 * N))
1062 .step_by(2)
1063 .map(|x| (x + N) as f64)
1064 .collect::<Vec<_>>(),
1065 )
1066 .unwrap();
1067
1068 let c = compute_add_ref_ref(&a, &b);
1069 assert_eq!(c, addition_answer);
1070
1071 let c = compute_add_ref_type(&a, b);
1072 assert_eq!(c, addition_answer);
1073
1074 let c = compute_add_type_ref(a, &b);
1075 assert_eq!(c, addition_answer);
1076 }
1077 parameterize_vector_length!(add_with_refs, [2, 3, 4, 8, 16, 32]);
1078
1079 fn dot<const N: usize>() {
1080 let (a, b) = generate_vector_pair::<N>();
1081 let c = a.dot(&b);
1082
1083 let n = N as f64;
1084
1085 let dot_ans = (5.0 * n.powi(3) - 6.0 * n.powi(2) + n) / 6.0;
1087
1088 assert_eq!(c, dot_ans);
1089 }
1090 parameterize_vector_length!(dot, [2, 3, 4, 8, 16, 32]);
1091
1092 fn neg<const N: usize>() {
1093 let a: Cartesian<N> = Cartesian::try_from(0..N).unwrap();
1094 let b = -a;
1095 for (i, j) in zip(a.coordinates.iter(), b.coordinates.iter()) {
1096 assert_eq!(*i, -j);
1097 }
1098 }
1099 parameterize_vector_length!(neg, [2, 3, 4, 8, 16, 32]);
1100
1101 #[test]
1102 fn cross() {
1103 let (a, b) = generate_vector_pair::<3>();
1104 let c = a.cross(&b);
1105
1106 let cross_ans = Cartesian::from((-3.0, 6.0, -3.0));
1108
1109 assert_eq!(c, cross_ans);
1110
1111 let a = Cartesian::from([1.0, 0.0, 0.0]);
1112 let b = Cartesian::from([0.0, 1.0, 0.0]);
1113 assert_eq!(a.cross(&b), [0.0, 0.0, 1.0].into());
1114
1115 let a = Cartesian::from([0.0, 3.0, 0.0]);
1116 let b = Cartesian::from([0.0, 0.0, 2.0]);
1117 assert_eq!(a.cross(&b), [6.0, 0.0, 0.0].into());
1118
1119 let a = Cartesian::from([2.0, 0.0, 0.0]);
1120 let b = Cartesian::from([0.0, 0.0, 4.0]);
1121 assert_eq!(a.cross(&b), [0.0, -8.0, 0.0].into());
1122 }
1123
1124 #[test]
1125 fn display() {
1126 let a = Cartesian::from([1.5, 2.125, -3.875]);
1127 let s = format!("{a}");
1128 assert_eq!(s, "[1.5, 2.125, -3.875]");
1129
1130 let a = Cartesian::from([10.0, 20.0, 30.0, 40.0]);
1131 let s = format!("{a}");
1132 assert_eq!(s, "[10, 20, 30, 40]");
1133 }
1134
1135 #[test]
1136 fn from_2_tuple() {
1137 let a = Cartesian::from((3.0, 0.125));
1138 assert_eq!(a.coordinates, [3.0, 0.125]);
1139 }
1140
1141 #[test]
1142 fn from_3_tuple() {
1143 let a = Cartesian::from((-0.5, 2.0, 18.125));
1144 assert_eq!(a.coordinates, [-0.5, 2.0, 18.125]);
1145 }
1146
1147 fn from_vec<const N: usize>() {
1148 let mut vec = Vec::with_capacity(N);
1149
1150 assert_eq!(
1151 Cartesian::<N>::try_from(vec.clone()),
1152 Err(Error::InvalidVectorLength)
1153 );
1154
1155 for i in 0..N {
1156 vec.push(i as f64 * 0.5);
1157 }
1158 let a = Cartesian::<N>::try_from(vec.clone()).unwrap();
1159
1160 assert_eq!(vec, Vec::from(a.coordinates));
1161
1162 vec.push(1.0);
1163 assert_eq!(
1164 Cartesian::<N>::try_from(vec.clone()),
1165 Err(Error::InvalidVectorLength)
1166 );
1167 }
1168 parameterize_vector_length!(from_vec, [2, 3, 4, 8, 16, 32]);
1169
1170 fn random_in_range<const N: usize>() {
1171 let mut rng = StdRng::seed_from_u64(1);
1173 let a: Cartesian<N> = rng.random();
1174
1175 assert!(a.coordinates.iter().all(|&x| -1.0 < x && x < 1.0));
1176
1177 if N == 10_000 {
1179 assert!(a.coordinates.iter().any(|&x| x < 0.0));
1180 }
1181 }
1182
1183 parameterize_vector_length!(random_in_range, [2, 3, 4, 8, 16, 32, 10_000]);
1184
1185 #[test]
1186 fn to_unit() {
1187 let a = Cartesian::from((2.0, 0.0, 0.0));
1188 let (Unit(unit_a), _) = a
1189 .to_unit()
1190 .expect("hard-coded vector should have non-zero length");
1191 assert_eq!(unit_a, [1.0, 0.0, 0.0].into());
1192
1193 let (Unit(unit_a), _) = a.to_unit_unchecked();
1194 assert_eq!(unit_a, [1.0, 0.0, 0.0].into());
1195
1196 let Unit(unit_a) = Unit::<Cartesian<3>>::try_from([1.0, 0.0, 0.0])
1197 .expect("hard-coded vector should have non-zero length");
1198 assert_eq!(unit_a, [1.0, 0.0, 0.0].into());
1199
1200 let a = Cartesian::from((3.0, 0.0, 4.0));
1201 let (Unit(unit_a), _) = a
1202 .to_unit()
1203 .expect("hard-coded vector should have non-zero length");
1204 assert_eq!(unit_a, [3.0 / 5.0, 0.0, 4.0 / 5.0].into());
1205
1206 let (Unit(unit_a), _) = a.to_unit_unchecked();
1207 assert_eq!(unit_a, [3.0 / 5.0, 0.0, 4.0 / 5.0].into());
1208
1209 let Unit(unit_a) = Unit::<Cartesian<3>>::try_from([3.0, 0.0, 4.0])
1210 .expect("hard-coded vector should have non-zero length");
1211 assert_eq!(unit_a, [3.0 / 5.0, 0.0, 4.0 / 5.0].into());
1212
1213 let a = Cartesian::from((0.0, 0.0, 0.0));
1214 assert!(matches!(a.to_unit(), Err(Error::InvalidVectorMagnitude)));
1215 assert!(matches!(
1216 Unit::<Cartesian<3>>::try_from([0.0, 0.0, 0.0]),
1217 Err(Error::InvalidVectorMagnitude)
1218 ));
1219 }
1220
1221 #[test]
1222 fn sum() {
1223 let total: Cartesian<2> = [Cartesian::from((1.0, 2.0)), Cartesian::from((-2.0, -1.0))]
1224 .into_iter()
1225 .sum();
1226
1227 assert_eq!(total, [-1.0, 1.0].into());
1228 }
1229
1230 #[test]
1231 fn perpendicular() {
1232 let v = Cartesian::from([1.0, -4.5]);
1233 assert_eq!(v.perpendicular(), [4.5, 1.0].into());
1234 }
1235
1236 #[test]
1237 fn test_rotationmatrix_display() {
1238 let m = RotationMatrix::<3>::default();
1239 let s = format!("{m}");
1240 assert_eq!(
1241 s,
1242 "[[1, 0, 0]
1243 [0, 1, 0]
1244 [0, 0, 1]]"
1245 );
1246
1247 let m = RotationMatrix::<2>::default();
1248 let s = format!("{m}");
1249 assert_eq!(
1250 s,
1251 "[[1, 0]
1252 [0, 1]]"
1253 );
1254 }
1255
1256 #[rstest(
1257 angle => [
1258 Angle::default(),
1259 Angle::from(std::f64::consts::FRAC_PI_3),
1260 Angle::from(999.9 * std::f64::consts::PI / 1.234)
1261 ]
1262 )]
1263 fn test_rotationmatrix_invert_2d(angle: Angle) {
1264 let transposed = RotationMatrix::from(angle).inverted();
1265 let inverted = RotationMatrix::from(angle.inverted());
1266 for (a, b) in transposed.rows().iter().zip(inverted.rows().iter()) {
1267 assert_relative_eq!(a, b);
1268 }
1269 }
1270 #[rstest(
1271 versor => [
1272 Versor::default(),
1273 Versor::from_axis_angle(
1274 Unit::try_from([1.0, 0.0, 0.0]).unwrap(), std::f64::consts::PI / 1.234
1275 )
1276 ]
1277 )]
1278 fn test_rotationmatrix_invert_3d(versor: Versor) {
1279 let transposed = RotationMatrix::from(versor).inverted();
1280 let inverted = RotationMatrix::from(versor.inverted());
1281 for (a, b) in transposed.rows().iter().zip(inverted.rows().iter()) {
1282 assert_relative_eq!(a, b);
1283 }
1284 }
1285
1286 #[rstest]
1287 fn test_generalized_cross_product_combinations(
1288 #[values(1.0, -2.5, 4.0)] r1: f64,
1289 #[values(1.0, 3.0, -5.1)] r2: f64,
1290 #[values(2.0, -1.5, 0.0)] r3: f64,
1291 #[values(0.0, 33.0, -12.5, 0.04)] x: f64,
1292 #[values(0.0, 1.0, 3.0, -2.6)] y: f64,
1293 #[values(0.0, 2.0, -1.5, -0.1)] z: f64,
1294 ) {
1295 let vecs = [
1297 Cartesian::from([r1, 0.0, 0.0, 0.0]),
1298 Cartesian::from([x, r2, 0.0, 0.0]),
1299 Cartesian::from([y, z, r3, 0.0]),
1300 ];
1301
1302 let cross = Cartesian::<4>::counary_cross(&vecs);
1303
1304 for v in &vecs {
1306 assert_relative_eq!(cross.dot(v), 0.0);
1307 }
1308
1309 let expected_volume_sq = (r1 * r2 * r3).powi(2);
1312 assert_relative_eq!(cross.norm_squared(), expected_volume_sq);
1313 }
1314}