1use crate::traits::{
60 AntiHermitianByConstruction, Compact, LieAlgebra, LieGroup, SemiSimple, Simple,
61 TracelessByConstruction,
62};
63use ndarray::Array2;
64use num_complex::Complex64;
65use std::fmt;
66use std::marker::PhantomData;
67use std::ops::{Add, Mul, MulAssign, Neg, Sub};
68
69#[derive(Clone, Debug, PartialEq)]
103pub struct SunAlgebra<const N: usize> {
104 pub(crate) coefficients: Vec<f64>,
107 _phantom: PhantomData<[(); N]>,
108}
109
110impl<const N: usize> SunAlgebra<N> {
111 const DIM: usize = {
113 assert!(
114 N >= 2,
115 "SU(N) requires N >= 2: SU(1) is trivial, SU(0) is undefined"
116 );
117 N * N - 1
118 };
119
120 #[must_use]
126 pub fn new(coefficients: Vec<f64>) -> Self {
127 assert_eq!(
128 coefficients.len(),
129 Self::DIM,
130 "SU({}) algebra requires {} coefficients, got {}",
131 N,
132 Self::DIM,
133 coefficients.len()
134 );
135 Self {
136 coefficients,
137 _phantom: PhantomData,
138 }
139 }
140
141 #[must_use]
143 pub fn coefficients(&self) -> &[f64] {
144 &self.coefficients
145 }
146
147 #[must_use]
166 pub fn to_matrix(&self) -> Array2<Complex64> {
167 let mut matrix = Array2::zeros((N, N));
168 let i = Complex64::new(0.0, 1.0);
169
170 let mut idx = 0;
171
172 for row in 0..N {
174 for col in (row + 1)..N {
175 let coeff = self.coefficients[idx];
176 matrix[[row, col]] += i * coeff;
177 matrix[[col, row]] += i * coeff;
178 idx += 1;
179 }
180 }
181
182 for row in 0..N {
186 for col in (row + 1)..N {
187 let coeff = self.coefficients[idx];
188 matrix[[row, col]] += Complex64::new(coeff, 0.0); matrix[[col, row]] += Complex64::new(-coeff, 0.0); idx += 1;
191 }
192 }
193
194 for k in 0..(N - 1) {
200 let coeff = self.coefficients[idx];
201
202 let k_f = k as f64;
204 let normalization = 2.0 / ((k_f + 1.0) * (k_f + 2.0));
205 let scale = normalization.sqrt();
206
207 for j in 0..=k {
209 matrix[[j, j]] += i * coeff * scale;
210 }
211 matrix[[k + 1, k + 1]] += i * coeff * scale * (-(k_f + 1.0));
213
214 idx += 1;
215 }
216
217 matrix.mapv_inplace(|z| z * 0.5);
219 matrix
220 }
221
222 #[must_use]
230 pub fn from_matrix(matrix: &Array2<Complex64>) -> Self {
231 assert_eq!(matrix.nrows(), N);
232 assert_eq!(matrix.ncols(), N);
233
234 let mut coefficients = vec![0.0; Self::DIM];
235 let mut idx = 0;
236
237 for row in 0..N {
245 for col in (row + 1)..N {
246 let val = matrix[[row, col]];
247 coefficients[idx] = val.im * 2.0;
248 idx += 1;
249 }
250 }
251
252 for row in 0..N {
257 for col in (row + 1)..N {
258 let val = matrix[[row, col]];
259 coefficients[idx] = val.re * 2.0;
260 idx += 1;
261 }
262 }
263
264 for k in 0..(N - 1) {
274 let k_f = k as f64;
275 let normalization = 2.0 / ((k_f + 1.0) * (k_f + 2.0));
276 let scale = normalization.sqrt();
277
278 let mut trace_prod = Complex64::new(0.0, 0.0);
279 for j in 0..=k {
280 trace_prod += matrix[[j, j]] * scale;
281 }
282 trace_prod += matrix[[k + 1, k + 1]] * (-(k_f + 1.0) * scale);
283
284 coefficients[idx] = trace_prod.im;
287 idx += 1;
288 }
289
290 Self::new(coefficients)
291 }
292}
293
294impl<const N: usize> Add for SunAlgebra<N> {
295 type Output = Self;
296 fn add(self, rhs: Self) -> Self {
297 let coefficients = self
298 .coefficients
299 .iter()
300 .zip(&rhs.coefficients)
301 .map(|(a, b)| a + b)
302 .collect();
303 Self::new(coefficients)
304 }
305}
306
307impl<const N: usize> Add<&SunAlgebra<N>> for SunAlgebra<N> {
308 type Output = SunAlgebra<N>;
309 fn add(self, rhs: &SunAlgebra<N>) -> SunAlgebra<N> {
310 let coefficients = self
311 .coefficients
312 .iter()
313 .zip(&rhs.coefficients)
314 .map(|(a, b)| a + b)
315 .collect();
316 Self::new(coefficients)
317 }
318}
319
320impl<const N: usize> Add<SunAlgebra<N>> for &SunAlgebra<N> {
321 type Output = SunAlgebra<N>;
322 fn add(self, rhs: SunAlgebra<N>) -> SunAlgebra<N> {
323 let coefficients = self
324 .coefficients
325 .iter()
326 .zip(&rhs.coefficients)
327 .map(|(a, b)| a + b)
328 .collect();
329 SunAlgebra::<N>::new(coefficients)
330 }
331}
332
333impl<const N: usize> Add<&SunAlgebra<N>> for &SunAlgebra<N> {
334 type Output = SunAlgebra<N>;
335 fn add(self, rhs: &SunAlgebra<N>) -> SunAlgebra<N> {
336 let coefficients = self
337 .coefficients
338 .iter()
339 .zip(&rhs.coefficients)
340 .map(|(a, b)| a + b)
341 .collect();
342 SunAlgebra::<N>::new(coefficients)
343 }
344}
345
346impl<const N: usize> Sub for SunAlgebra<N> {
347 type Output = Self;
348 fn sub(self, rhs: Self) -> Self {
349 let coefficients = self
350 .coefficients
351 .iter()
352 .zip(&rhs.coefficients)
353 .map(|(a, b)| a - b)
354 .collect();
355 Self::new(coefficients)
356 }
357}
358
359impl<const N: usize> Neg for SunAlgebra<N> {
360 type Output = Self;
361 fn neg(self) -> Self {
362 let coefficients = self.coefficients.iter().map(|x| -x).collect();
363 Self::new(coefficients)
364 }
365}
366
367impl<const N: usize> Mul<f64> for SunAlgebra<N> {
368 type Output = Self;
369 fn mul(self, scalar: f64) -> Self {
370 let coefficients = self.coefficients.iter().map(|x| x * scalar).collect();
371 Self::new(coefficients)
372 }
373}
374
375impl<const N: usize> Mul<SunAlgebra<N>> for f64 {
376 type Output = SunAlgebra<N>;
377 fn mul(self, rhs: SunAlgebra<N>) -> SunAlgebra<N> {
378 rhs * self
379 }
380}
381
382impl<const N: usize> LieAlgebra for SunAlgebra<N> {
383 const DIM: usize = {
389 assert!(
390 N >= 2,
391 "SU(N) requires N >= 2: SU(1) is trivial, SU(0) is undefined"
392 );
393 N * N - 1
394 };
395
396 fn zero() -> Self {
397 Self {
398 coefficients: vec![0.0; Self::DIM],
399 _phantom: PhantomData,
400 }
401 }
402
403 fn add(&self, other: &Self) -> Self {
404 let coefficients = self
405 .coefficients
406 .iter()
407 .zip(&other.coefficients)
408 .map(|(a, b)| a + b)
409 .collect();
410 Self::new(coefficients)
411 }
412
413 fn scale(&self, scalar: f64) -> Self {
414 let coefficients = self.coefficients.iter().map(|x| x * scalar).collect();
415 Self::new(coefficients)
416 }
417
418 fn norm(&self) -> f64 {
419 self.coefficients
420 .iter()
421 .map(|x| x.powi(2))
422 .sum::<f64>()
423 .sqrt()
424 }
425
426 fn basis_element(i: usize) -> Self {
427 assert!(
428 i < Self::DIM,
429 "Basis index {} out of range for SU({})",
430 i,
431 N
432 );
433 let mut coefficients = vec![0.0; Self::DIM];
434 coefficients[i] = 1.0;
435 Self::new(coefficients)
436 }
437
438 fn from_components(components: &[f64]) -> Self {
439 assert_eq!(
440 components.len(),
441 Self::DIM,
442 "Expected {} components for SU({}), got {}",
443 Self::DIM,
444 N,
445 components.len()
446 );
447 Self::new(components.to_vec())
448 }
449
450 fn to_components(&self) -> Vec<f64> {
451 self.coefficients.clone()
452 }
453
454 fn bracket(&self, other: &Self) -> Self {
480 let x = self.to_matrix();
481 let y = other.to_matrix();
482 let commutator = x.dot(&y) - y.dot(&x);
483 Self::from_matrix(&commutator)
484 }
485
486 #[inline]
487 fn inner(&self, other: &Self) -> f64 {
488 self.coefficients
489 .iter()
490 .zip(other.coefficients.iter())
491 .map(|(a, b)| a * b)
492 .sum()
493 }
494}
495
496#[derive(Debug, Clone, PartialEq)]
512pub struct SUN<const N: usize> {
513 pub(crate) matrix: Array2<Complex64>,
515}
516
517impl<const N: usize> SUN<N> {
518 #[must_use]
520 pub fn matrix(&self) -> &Array2<Complex64> {
521 &self.matrix
522 }
523
524 #[must_use]
526 pub fn identity() -> Self {
527 Self {
528 matrix: Array2::eye(N),
529 }
530 }
531
532 #[must_use]
534 pub fn trace(&self) -> Complex64 {
535 (0..N).map(|i| self.matrix[[i, i]]).sum()
536 }
537
538 #[must_use]
548 pub fn verify_unitarity(&self, tolerance: f64) -> bool {
549 let adjoint = self.matrix.t().mapv(|z| z.conj());
550 let product = adjoint.dot(&self.matrix);
551 let identity: Array2<Complex64> = Array2::eye(N);
552
553 let diff = &product - &identity;
554 let norm_sq: f64 = diff.iter().map(num_complex::Complex::norm_sqr).sum();
555
556 norm_sq.sqrt() < tolerance
557 }
558
559 #[must_use]
581 #[allow(clippy::many_single_char_names)] pub fn determinant(&self) -> Complex64 {
583 if N == 2 {
585 let a = self.matrix[[0, 0]];
586 let b = self.matrix[[0, 1]];
587 let c = self.matrix[[1, 0]];
588 let d = self.matrix[[1, 1]];
589 return a * d - b * c;
590 }
591
592 if N == 3 {
593 let (a, b, c, d, e, f, g, h, i) = {
595 let m = &self.matrix;
596 (
597 m[[0, 0]],
598 m[[0, 1]],
599 m[[0, 2]],
600 m[[1, 0]],
601 m[[1, 1]],
602 m[[1, 2]],
603 m[[2, 0]],
604 m[[2, 1]],
605 m[[2, 2]],
606 )
607 };
608
609 return a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
611 }
612
613 Complex64::new(1.0, 0.0)
617 }
618
619 #[must_use]
641 fn gram_schmidt_project(matrix: Array2<Complex64>) -> Array2<Complex64> {
642 let mut result: Array2<Complex64> = Array2::zeros((N, N));
643
644 for j in 0..N {
646 let mut col = matrix.column(j).to_owned();
647
648 for k in 0..j {
650 let prev_col = result.column(k);
651 let proj: Complex64 = prev_col
652 .iter()
653 .zip(col.iter())
654 .map(|(p, c)| p.conj() * c)
655 .sum();
656 for i in 0..N {
657 col[i] -= proj * prev_col[i];
658 }
659 }
660
661 let norm: f64 = col
663 .iter()
664 .map(num_complex::Complex::norm_sqr)
665 .sum::<f64>()
666 .sqrt();
667
668 debug_assert!(
670 norm > 1e-14,
671 "Gram-Schmidt: column {} is linearly dependent (norm = {:.2e}). \
672 Input matrix is rank-deficient.",
673 j,
674 norm
675 );
676
677 if norm > 1e-14 {
678 for i in 0..N {
679 result[[i, j]] = col[i] / norm;
680 }
681 }
682 }
684
685 if N <= 3 {
688 let det = if N == 2 {
689 result[[0, 0]] * result[[1, 1]] - result[[0, 1]] * result[[1, 0]]
690 } else {
691 result[[0, 0]] * (result[[1, 1]] * result[[2, 2]] - result[[1, 2]] * result[[2, 1]])
693 - result[[0, 1]]
694 * (result[[1, 0]] * result[[2, 2]] - result[[1, 2]] * result[[2, 0]])
695 + result[[0, 2]]
696 * (result[[1, 0]] * result[[2, 1]] - result[[1, 1]] * result[[2, 0]])
697 };
698
699 let det_norm = det.norm();
701 if det_norm < 1e-14 {
702 return Array2::eye(N);
704 }
705
706 let det_phase = det / det_norm;
707 let correction = (det_phase.conj()).powf(1.0 / N as f64);
708 result.mapv_inplace(|z| z * correction);
709 }
710
711 result
712 }
713
714 #[must_use]
718 pub fn distance_to_identity(&self) -> f64 {
719 let identity: Array2<Complex64> = Array2::eye(N);
720 let diff = &self.matrix - &identity;
721 diff.iter()
722 .map(num_complex::Complex::norm_sqr)
723 .sum::<f64>()
724 .sqrt()
725 }
726}
727
728impl<const N: usize> approx::AbsDiffEq for SunAlgebra<N> {
729 type Epsilon = f64;
730
731 fn default_epsilon() -> Self::Epsilon {
732 1e-10
733 }
734
735 fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
736 self.coefficients
737 .iter()
738 .zip(other.coefficients.iter())
739 .all(|(a, b)| (a - b).abs() < epsilon)
740 }
741}
742
743impl<const N: usize> approx::RelativeEq for SunAlgebra<N> {
744 fn default_max_relative() -> Self::Epsilon {
745 1e-10
746 }
747
748 fn relative_eq(
749 &self,
750 other: &Self,
751 epsilon: Self::Epsilon,
752 max_relative: Self::Epsilon,
753 ) -> bool {
754 self.coefficients
755 .iter()
756 .zip(other.coefficients.iter())
757 .all(|(a, b)| approx::RelativeEq::relative_eq(a, b, epsilon, max_relative))
758 }
759}
760
761impl<const N: usize> fmt::Display for SunAlgebra<N> {
762 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
763 write!(f, "su({})[", N)?;
764 for (i, c) in self.coefficients.iter().enumerate() {
765 if i > 0 {
766 write!(f, ", ")?;
767 }
768 write!(f, "{:.4}", c)?;
769 }
770 write!(f, "]")
771 }
772}
773
774impl<const N: usize> fmt::Display for SUN<N> {
775 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
776 let dist = self.distance_to_identity();
777 write!(f, "SU({})(d={:.4})", N, dist)
778 }
779}
780
781impl<const N: usize> Mul<&SUN<N>> for &SUN<N> {
783 type Output = SUN<N>;
784 fn mul(self, rhs: &SUN<N>) -> SUN<N> {
785 SUN {
786 matrix: self.matrix.dot(&rhs.matrix),
787 }
788 }
789}
790
791impl<const N: usize> Mul<&SUN<N>> for SUN<N> {
792 type Output = SUN<N>;
793 fn mul(self, rhs: &SUN<N>) -> SUN<N> {
794 &self * rhs
795 }
796}
797
798impl<const N: usize> MulAssign<&SUN<N>> for SUN<N> {
799 fn mul_assign(&mut self, rhs: &SUN<N>) {
800 self.matrix = self.matrix.dot(&rhs.matrix);
801 }
802}
803
804impl<const N: usize> LieGroup for SUN<N> {
805 type Algebra = SunAlgebra<N>;
806 const MATRIX_DIM: usize = {
807 assert!(
808 N >= 2,
809 "SU(N) requires N >= 2: SU(1) is trivial, SU(0) is undefined"
810 );
811 N
812 };
813
814 fn identity() -> Self {
815 Self::identity()
816 }
817
818 fn compose(&self, other: &Self) -> Self {
819 Self {
820 matrix: self.matrix.dot(&other.matrix),
821 }
822 }
823
824 fn inverse(&self) -> Self {
825 Self {
827 matrix: self.matrix.t().mapv(|z| z.conj()),
828 }
829 }
830
831 fn conjugate_transpose(&self) -> Self {
832 Self {
833 matrix: self.matrix.t().mapv(|z| z.conj()),
834 }
835 }
836
837 fn adjoint_action(&self, algebra_element: &Self::Algebra) -> Self::Algebra {
852 let x = algebra_element.to_matrix();
853 let g_inv = self.inverse();
854
855 let result = self.matrix.dot(&x).dot(&g_inv.matrix);
857
858 SunAlgebra::from_matrix(&result)
859 }
860
861 fn distance_to_identity(&self) -> f64 {
862 self.distance_to_identity()
863 }
864
865 fn exp(tangent: &Self::Algebra) -> Self {
890 let x = tangent.to_matrix();
891 let norm = matrix_frobenius_norm(&x);
892
893 let k = if norm > 0.5 {
895 (norm / 0.5).log2().ceil() as u32
896 } else {
897 0
898 };
899
900 let scale_factor = 2_f64.powi(-(k as i32));
902 let x_scaled = x.mapv(|z| z * scale_factor);
903
904 let exp_scaled = matrix_exp_taylor(&x_scaled, 15);
906
907 let mut result = exp_scaled;
918 for _ in 0..k {
919 result = result.dot(&result);
920 result = Self::gram_schmidt_project(result);
921 }
922
923 Self { matrix: result }
924 }
925
926 fn log(&self) -> crate::error::LogResult<Self::Algebra> {
927 use crate::error::LogError;
928
929 let dist = self.distance_to_identity();
937 const MAX_DISTANCE: f64 = 2.0;
938
939 if dist > MAX_DISTANCE {
940 return Err(LogError::NotNearIdentity {
941 distance: dist,
942 threshold: MAX_DISTANCE,
943 });
944 }
945
946 if dist < 1e-14 {
947 return Ok(SunAlgebra::zero());
948 }
949
950 let identity_matrix: Array2<Complex64> = Array2::eye(N);
952 let mut current = self.matrix.clone();
953 let mut num_sqrts: u32 = 0;
954 const MAX_SQRTS: u32 = 32;
955 const TARGET_NORM: f64 = 0.5;
956
957 while num_sqrts < MAX_SQRTS {
958 let x_matrix = ¤t - &identity_matrix;
959 let x_norm = matrix_frobenius_norm(&x_matrix);
960 if x_norm < TARGET_NORM {
961 break;
962 }
963 current = matrix_sqrt_db(¤t);
964 num_sqrts += 1;
965 }
966
967 let x_matrix = ¤t - &identity_matrix;
969 let log_matrix = matrix_log_taylor(&x_matrix, 30);
970
971 let scale_factor = (1_u64 << num_sqrts) as f64;
973 let log_scaled = log_matrix.mapv(|z| z * scale_factor);
974
975 Ok(SunAlgebra::from_matrix(&log_scaled))
976 }
977}
978
979fn matrix_frobenius_norm(matrix: &Array2<Complex64>) -> f64 {
981 matrix
982 .iter()
983 .map(num_complex::Complex::norm_sqr)
984 .sum::<f64>()
985 .sqrt()
986}
987
988fn matrix_exp_taylor(matrix: &Array2<Complex64>, terms: usize) -> Array2<Complex64> {
994 let n = matrix.nrows();
995 let mut result = Array2::eye(n); let mut term = Array2::eye(n); for k in 1..=terms {
999 term = term.dot(matrix).mapv(|z| z / (k as f64));
1001 result += &term;
1002 }
1003
1004 result
1005}
1006
1007fn matrix_log_taylor(matrix: &Array2<Complex64>, terms: usize) -> Array2<Complex64> {
1014 let mut result = matrix.clone(); let mut x_power = matrix.clone(); for k in 2..=terms {
1018 x_power = x_power.dot(matrix);
1020
1021 let sign = if k % 2 == 0 { -1.0 } else { 1.0 };
1023 let coefficient = sign / (k as f64);
1024
1025 result = result + x_power.mapv(|z| z * coefficient);
1027 }
1028
1029 result
1030}
1031
1032fn matrix_inverse(a: &Array2<Complex64>) -> Option<Array2<Complex64>> {
1036 let n = a.nrows();
1037 assert_eq!(n, a.ncols());
1038
1039 let mut aug = Array2::<Complex64>::zeros((n, 2 * n));
1041 for i in 0..n {
1042 for j in 0..n {
1043 aug[[i, j]] = a[[i, j]];
1044 }
1045 aug[[i, n + i]] = Complex64::new(1.0, 0.0);
1046 }
1047
1048 for col in 0..n {
1049 let mut max_norm = 0.0;
1051 let mut max_row = col;
1052 for row in col..n {
1053 let norm = aug[[row, col]].norm();
1054 if norm > max_norm {
1055 max_norm = norm;
1056 max_row = row;
1057 }
1058 }
1059 if max_norm < 1e-15 {
1060 return None; }
1062
1063 if max_row != col {
1065 for j in 0..2 * n {
1066 let tmp = aug[[col, j]];
1067 aug[[col, j]] = aug[[max_row, j]];
1068 aug[[max_row, j]] = tmp;
1069 }
1070 }
1071
1072 let pivot = aug[[col, col]];
1074 for j in 0..2 * n {
1075 aug[[col, j]] /= pivot;
1076 }
1077
1078 for row in 0..n {
1080 if row != col {
1081 let factor = aug[[row, col]];
1082 let pivot_row: Vec<Complex64> = (0..2 * n).map(|j| aug[[col, j]]).collect();
1084 for j in 0..2 * n {
1085 aug[[row, j]] -= factor * pivot_row[j];
1086 }
1087 }
1088 }
1089 }
1090
1091 let mut result = Array2::zeros((n, n));
1093 for i in 0..n {
1094 for j in 0..n {
1095 result[[i, j]] = aug[[i, n + j]];
1096 }
1097 }
1098 Some(result)
1099}
1100
1101fn matrix_sqrt_db(u: &Array2<Complex64>) -> Array2<Complex64> {
1107 let n = u.nrows();
1108 let mut y = u.clone();
1109 let mut z = Array2::<Complex64>::eye(n);
1110
1111 const MAX_ITERS: usize = 20;
1112 const TOL: f64 = 1e-14;
1113
1114 for _ in 0..MAX_ITERS {
1115 let y_inv = matrix_inverse(&y).unwrap_or_else(|| y.t().mapv(|z| z.conj()));
1116 let z_inv = matrix_inverse(&z).unwrap_or_else(|| z.t().mapv(|z| z.conj()));
1117
1118 let y_new = (&y + &z_inv).mapv(|z| z * 0.5);
1119 let z_new = (&z + &y_inv).mapv(|z| z * 0.5);
1120
1121 let diff = matrix_frobenius_norm(&(&y_new - &y));
1122 y = y_new;
1123 z = z_new;
1124
1125 if diff < TOL {
1126 break;
1127 }
1128 }
1129
1130 y
1131}
1132
1133impl<const N: usize> Compact for SUN<N> {}
1143
1144impl<const N: usize> Simple for SUN<N> {}
1146
1147impl<const N: usize> SemiSimple for SUN<N> {}
1149
1150impl<const N: usize> TracelessByConstruction for SunAlgebra<N> {}
1155
1156impl<const N: usize> AntiHermitianByConstruction for SunAlgebra<N> {}
1160
1161pub type SU2Generic = SUN<2>;
1167pub type SU3Generic = SUN<3>;
1169pub type SU4 = SUN<4>;
1171pub type SU5 = SUN<5>;
1173
1174#[cfg(test)]
1175mod tests {
1176 use super::*;
1177 use approx::assert_relative_eq;
1178 #[test]
1188 fn test_sun2_su2_basis_matrices_agree() {
1189 for k in 0..3 {
1191 let m_sun = SunAlgebra::<2>::basis_element(k).to_matrix();
1192 let i = Complex64::new(0.0, 1.0);
1194 let sigma: Array2<Complex64> = match k {
1195 0 => Array2::from_shape_vec(
1196 (2, 2),
1197 vec![
1198 Complex64::new(0.0, 0.0),
1199 Complex64::new(1.0, 0.0),
1200 Complex64::new(1.0, 0.0),
1201 Complex64::new(0.0, 0.0),
1202 ],
1203 )
1204 .unwrap(),
1205 1 => Array2::from_shape_vec(
1206 (2, 2),
1207 vec![
1208 Complex64::new(0.0, 0.0),
1209 Complex64::new(0.0, -1.0),
1210 Complex64::new(0.0, 1.0),
1211 Complex64::new(0.0, 0.0),
1212 ],
1213 )
1214 .unwrap(),
1215 2 => Array2::from_shape_vec(
1216 (2, 2),
1217 vec![
1218 Complex64::new(1.0, 0.0),
1219 Complex64::new(0.0, 0.0),
1220 Complex64::new(0.0, 0.0),
1221 Complex64::new(-1.0, 0.0),
1222 ],
1223 )
1224 .unwrap(),
1225 _ => unreachable!(),
1226 };
1227 let expected = sigma.mapv(|z| i * z * 0.5);
1228
1229 for r in 0..2 {
1230 for c in 0..2 {
1231 assert!(
1232 (m_sun[(r, c)] - expected[(r, c)]).norm() < 1e-10,
1233 "SUN<2> basis {} at ({},{}): got {}, want {}",
1234 k,
1235 r,
1236 c,
1237 m_sun[(r, c)],
1238 expected[(r, c)]
1239 );
1240 }
1241 }
1242 }
1243 }
1244
1245 #[test]
1246 fn test_sun2_su2_brackets_agree() {
1247 use crate::Su2Algebra;
1248
1249 for i in 0..3 {
1250 for j in 0..3 {
1251 let bracket_su2 = Su2Algebra::basis_element(i)
1252 .bracket(&Su2Algebra::basis_element(j))
1253 .to_components();
1254 let bracket_sun = SunAlgebra::<2>::basis_element(i)
1255 .bracket(&SunAlgebra::<2>::basis_element(j))
1256 .to_components();
1257
1258 for k in 0..3 {
1259 assert_relative_eq!(bracket_su2[k], bracket_sun[k], epsilon = 1e-10);
1260 }
1261 }
1262 }
1263 }
1264
1265 #[test]
1266 fn test_sun2_su2_exp_agrees() {
1267 use crate::{Su2Algebra, SU2};
1268
1269 let coeffs = [0.3, -0.2, 0.4];
1270 let g_su2 = SU2::exp(&Su2Algebra::new(coeffs));
1271 let g_sun = SUN::<2>::exp(&SunAlgebra::<2>::from_components(&coeffs));
1272
1273 let m_su2 = g_su2.to_matrix();
1274 let m_sun = g_sun.matrix();
1275
1276 for i in 0..2 {
1277 for j in 0..2 {
1278 assert_relative_eq!(m_su2[i][j].re, m_sun[(i, j)].re, epsilon = 1e-10);
1279 assert_relative_eq!(m_su2[i][j].im, m_sun[(i, j)].im, epsilon = 1e-10);
1280 }
1281 }
1282 }
1283
1284 #[test]
1290 fn test_sun3_su3_basis_matrices_agree() {
1291 use crate::Su3Algebra;
1292
1293 let su3_to_sun: [usize; 8] = [0, 3, 6, 1, 4, 2, 5, 7];
1295
1296 for k in 0..8 {
1297 let m_su3 = Su3Algebra::basis_element(k).to_matrix();
1298 let m_sun = SunAlgebra::<3>::basis_element(su3_to_sun[k]).to_matrix();
1299
1300 for r in 0..3 {
1301 for c in 0..3 {
1302 assert!(
1303 (m_su3[(r, c)] - m_sun[(r, c)]).norm() < 1e-10,
1304 "Basis {} (SU3) vs {} (SUN) at ({},{}): SU3={}, SUN<3>={}",
1305 k,
1306 su3_to_sun[k],
1307 r,
1308 c,
1309 m_su3[(r, c)],
1310 m_sun[(r, c)]
1311 );
1312 }
1313 }
1314 }
1315 }
1316
1317 #[test]
1321 fn test_sun3_su3_brackets_agree() {
1322 use crate::Su3Algebra;
1323
1324 let su3_to_sun: [usize; 8] = [0, 3, 6, 1, 4, 2, 5, 7];
1326
1327 for i in 0..8 {
1328 for j in 0..8 {
1329 let bracket_su3 = Su3Algebra::basis_element(i)
1330 .bracket(&Su3Algebra::basis_element(j))
1331 .to_matrix();
1332 let bracket_sun = SunAlgebra::<3>::basis_element(su3_to_sun[i])
1333 .bracket(&SunAlgebra::<3>::basis_element(su3_to_sun[j]))
1334 .to_matrix();
1335
1336 for r in 0..3 {
1337 for c in 0..3 {
1338 assert!(
1339 (bracket_su3[(r, c)] - bracket_sun[(r, c)]).norm() < 1e-10,
1340 "Bracket [e{},e{}] at ({},{}): SU3={}, SUN<3>={}",
1341 i,
1342 j,
1343 r,
1344 c,
1345 bracket_su3[(r, c)],
1346 bracket_sun[(r, c)]
1347 );
1348 }
1349 }
1350 }
1351 }
1352 }
1353
1354 #[test]
1358 fn test_sun3_su3_exp_agrees() {
1359 use crate::{Su3Algebra, SU3};
1360
1361 let su3_coeffs = [0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06];
1363 let su3_elem = Su3Algebra::from_components(&su3_coeffs);
1364 let matrix = su3_elem.to_matrix();
1365
1366 let sun_elem = SunAlgebra::<3>::from_matrix(&matrix);
1368
1369 let g_su3 = SU3::exp(&su3_elem);
1370 let g_sun = SUN::<3>::exp(&sun_elem);
1371
1372 let m_su3 = g_su3.matrix();
1373 let m_sun = g_sun.matrix();
1374
1375 for i in 0..3 {
1376 for j in 0..3 {
1377 assert!(
1378 (m_su3[(i, j)] - m_sun[(i, j)]).norm() < 1e-10,
1379 "exp disagrees at ({},{}): SU3={}, SUN<3>={}",
1380 i,
1381 j,
1382 m_su3[(i, j)],
1383 m_sun[(i, j)]
1384 );
1385 }
1386 }
1387 }
1388
1389 #[test]
1392 fn test_normalization_half_delta() {
1393 for n in [2_usize, 3, 4, 5] {
1394 let dim = n * n - 1;
1395 for a in 0..dim {
1396 let ma = match n {
1397 2 => SunAlgebra::<2>::basis_element(a).to_matrix(),
1398 3 => SunAlgebra::<3>::basis_element(a).to_matrix(),
1399 4 => SunAlgebra::<4>::basis_element(a).to_matrix(),
1400 5 => SunAlgebra::<5>::basis_element(a).to_matrix(),
1401 _ => unreachable!(),
1402 };
1403 let ma_dag = ma.t().mapv(|z| z.conj());
1404
1405 for b in a..dim {
1406 let mb = match n {
1407 2 => SunAlgebra::<2>::basis_element(b).to_matrix(),
1408 3 => SunAlgebra::<3>::basis_element(b).to_matrix(),
1409 4 => SunAlgebra::<4>::basis_element(b).to_matrix(),
1410 5 => SunAlgebra::<5>::basis_element(b).to_matrix(),
1411 _ => unreachable!(),
1412 };
1413
1414 let prod = ma_dag.dot(&mb);
1415 let mut tr = 0.0;
1416 for i in 0..n {
1417 tr += prod[(i, i)].re;
1418 }
1419
1420 let expected = if a == b { 0.5 } else { 0.0 };
1421 assert!(
1422 (tr - expected).abs() < 1e-10,
1423 "SU({}): tr(T{}†T{}) = {:.4}, want {}",
1424 n,
1425 a,
1426 b,
1427 tr,
1428 expected
1429 );
1430 }
1431 }
1432 }
1433 }
1434
1435 #[test]
1442 fn test_bch_exp_log_coherence_sun2() {
1443 use crate::bch::bch_fifth_order;
1444
1445 let x = SunAlgebra::<2>::from_components(&[0.05, -0.03, 0.04]);
1446 let y = SunAlgebra::<2>::from_components(&[-0.02, 0.04, -0.03]);
1447
1448 let direct = SUN::<2>::exp(&x).compose(&SUN::<2>::exp(&y));
1449 let bch_z = bch_fifth_order(&x, &y);
1450 let via_bch = SUN::<2>::exp(&bch_z);
1451
1452 let distance = direct.compose(&via_bch.inverse()).distance_to_identity();
1453 assert!(
1454 distance < 1e-8,
1455 "SUN<2> BCH vs exp·log distance: {:.2e}",
1456 distance
1457 );
1458 }
1459
1460 #[test]
1463 fn test_adjoint_preserves_bracket_su3() {
1464 use crate::{Su3Algebra, SU3};
1465
1466 let x = Su3Algebra::from_components(&[0.3, -0.2, 0.1, 0.15, -0.1, 0.25, -0.15, 0.05]);
1467 let y = Su3Algebra::from_components(&[-0.1, 0.25, -0.15, 0.2, 0.05, -0.1, 0.3, -0.2]);
1468 let g = SU3::exp(&Su3Algebra::from_components(&[0.4, -0.3, 0.2, 0.1, -0.2, 0.15, -0.1, 0.3]));
1469
1470 let lhs = g.adjoint_action(&x.bracket(&y));
1471 let rhs = g.adjoint_action(&x).bracket(&g.adjoint_action(&y));
1472
1473 let diff_matrix = lhs.to_matrix() - rhs.to_matrix();
1474 let mut frobenius = 0.0;
1475 for r in 0..3 {
1476 for c in 0..3 {
1477 frobenius += diff_matrix[(r, c)].norm_sqr();
1478 }
1479 }
1480 assert!(
1481 frobenius.sqrt() < 1e-10,
1482 "SU3 Ad_g([X,Y]) ≠ [Ad_g(X), Ad_g(Y)]: ||diff|| = {:.2e}",
1483 frobenius.sqrt()
1484 );
1485 }
1486
1487 #[test]
1490 fn test_su3_sun3_bracket_coefficient_roundtrip() {
1491 use crate::Su3Algebra;
1492
1493 let x_su3 = Su3Algebra::from_components(&[0.3, -0.2, 0.1, 0.15, -0.1, 0.25, -0.15, 0.05]);
1494 let y_su3 = Su3Algebra::from_components(&[-0.1, 0.25, -0.15, 0.2, 0.05, -0.1, 0.3, -0.2]);
1495
1496 let bracket_su3 = x_su3.bracket(&y_su3);
1498 let m_su3 = bracket_su3.to_matrix();
1499
1500 let x_sun = SunAlgebra::<3>::from_matrix(&x_su3.to_matrix());
1502 let y_sun = SunAlgebra::<3>::from_matrix(&y_su3.to_matrix());
1503 let bracket_sun = x_sun.bracket(&y_sun);
1504 let m_sun = bracket_sun.to_matrix();
1505
1506 for r in 0..3 {
1507 for c in 0..3 {
1508 assert!(
1509 (m_su3[(r, c)] - m_sun[(r, c)]).norm() < 1e-12,
1510 "Bracket matrix disagrees at ({},{}): SU3={}, SUN<3>={}",
1511 r, c, m_su3[(r, c)], m_sun[(r, c)]
1512 );
1513 }
1514 }
1515
1516 let roundtrip = SunAlgebra::<3>::from_matrix(&m_su3).to_matrix();
1518 for r in 0..3 {
1519 for c in 0..3 {
1520 assert!(
1521 (m_su3[(r, c)] - roundtrip[(r, c)]).norm() < 1e-12,
1522 "from_matrix roundtrip failed at ({},{})",
1523 r, c
1524 );
1525 }
1526 }
1527 }
1528
1529 #[test]
1534 fn test_su4_group_axioms() {
1535 let x = SunAlgebra::<4>::from_components(&[
1536 0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06, 0.09, -0.11, 0.07, 0.03, -0.08, 0.04,
1537 0.13,
1538 ]);
1539 let y = SunAlgebra::<4>::from_components(&[
1540 -0.05, 0.1, -0.08, 0.12, 0.06, -0.15, 0.03, 0.09, -0.07, 0.11, -0.04, 0.14, 0.02, -0.1,
1541 0.08,
1542 ]);
1543
1544 let g = SUN::<4>::exp(&x);
1545 let h = SUN::<4>::exp(&y);
1546 let e = SUN::<4>::identity();
1547
1548 assert_relative_eq!(
1550 g.compose(&e).distance_to_identity(),
1551 g.distance_to_identity(),
1552 epsilon = 1e-10
1553 );
1554
1555 assert_relative_eq!(
1557 g.compose(&g.inverse()).distance_to_identity(),
1558 0.0,
1559 epsilon = 1e-9
1560 );
1561
1562 let k = SUN::<4>::exp(&x.scale(0.7));
1564 let lhs = g.compose(&h).compose(&k);
1565 let rhs = g.compose(&h.compose(&k));
1566 assert_relative_eq!(
1567 lhs.compose(&rhs.inverse()).distance_to_identity(),
1568 0.0,
1569 epsilon = 1e-9
1570 );
1571
1572 assert!(g.verify_unitarity(1e-10));
1574 assert!(g.compose(&h).verify_unitarity(1e-10));
1575 }
1576
1577 #[test]
1578 fn test_su5_group_axioms() {
1579 let x = SunAlgebra::<5>::from_components(
1580 &(0..24)
1581 .map(|i| 0.05 * (i as f64 - 12.0).sin())
1582 .collect::<Vec<_>>(),
1583 );
1584 let g = SUN::<5>::exp(&x);
1585
1586 assert_relative_eq!(
1588 g.compose(&SUN::<5>::identity()).distance_to_identity(),
1589 g.distance_to_identity(),
1590 epsilon = 1e-10
1591 );
1592
1593 assert_relative_eq!(
1595 g.compose(&g.inverse()).distance_to_identity(),
1596 0.0,
1597 epsilon = 1e-9
1598 );
1599
1600 assert!(g.verify_unitarity(1e-10));
1602 }
1603
1604 #[test]
1609 fn test_su4_jacobi_identity() {
1610 let triples = [(0, 3, 7), (1, 5, 10), (2, 8, 14), (4, 9, 12)];
1612 for (i, j, k) in triples {
1613 let x = SunAlgebra::<4>::basis_element(i);
1614 let y = SunAlgebra::<4>::basis_element(j);
1615 let z = SunAlgebra::<4>::basis_element(k);
1616
1617 let t1 = x.bracket(&y.bracket(&z));
1618 let t2 = y.bracket(&z.bracket(&x));
1619 let t3 = z.bracket(&x.bracket(&y));
1620 let sum = t1.add(&t2).add(&t3);
1621
1622 assert!(
1623 sum.norm() < 1e-10,
1624 "Jacobi violated for SU(4) basis ({},{},{}): ||sum|| = {:.2e}",
1625 i,
1626 j,
1627 k,
1628 sum.norm()
1629 );
1630 }
1631 }
1632
1633 #[test]
1638 fn test_sun_adjoint_identity_action() {
1639 let e = SUN::<3>::identity();
1641 for i in 0..8 {
1642 let x = SunAlgebra::<3>::basis_element(i);
1643 let ad_x = e.adjoint_action(&x);
1644 for k in 0..8 {
1645 assert_relative_eq!(
1646 x.to_components()[k],
1647 ad_x.to_components()[k],
1648 epsilon = 1e-10
1649 );
1650 }
1651 }
1652 }
1653
1654 #[test]
1655 fn test_sun_adjoint_homomorphism() {
1656 let g = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(0).scale(0.5));
1658 let h = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(3).scale(0.3));
1659 let x = SunAlgebra::<3>::from_components(&[0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06]);
1660
1661 let gh = g.compose(&h);
1662 let lhs = gh.adjoint_action(&x);
1663 let rhs = g.adjoint_action(&h.adjoint_action(&x));
1664
1665 for k in 0..8 {
1666 assert_relative_eq!(
1667 lhs.to_components()[k],
1668 rhs.to_components()[k],
1669 epsilon = 1e-9
1670 );
1671 }
1672 }
1673
1674 #[test]
1675 fn test_sun_adjoint_preserves_bracket() {
1676 let g = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(2).scale(0.8));
1678 let x = SunAlgebra::<3>::basis_element(0);
1679 let y = SunAlgebra::<3>::basis_element(4);
1680
1681 let lhs = g.adjoint_action(&x.bracket(&y));
1682 let rhs = g.adjoint_action(&x).bracket(&g.adjoint_action(&y));
1683
1684 for k in 0..8 {
1685 assert_relative_eq!(
1686 lhs.to_components()[k],
1687 rhs.to_components()[k],
1688 epsilon = 1e-9
1689 );
1690 }
1691 }
1692
1693 #[test]
1694 fn test_sun4_adjoint_preserves_norm() {
1695 let x = SunAlgebra::<4>::from_components(&[
1697 0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06, 0.09, -0.11, 0.07, 0.03, -0.08, 0.04,
1698 0.13,
1699 ]);
1700 let g = SUN::<4>::exp(&SunAlgebra::<4>::basis_element(5).scale(1.2));
1701 let ad_x = g.adjoint_action(&x);
1702 assert_relative_eq!(x.norm(), ad_x.norm(), epsilon = 1e-9);
1703 }
1704
1705 #[test]
1710 fn test_su4_exp_log_roundtrip() {
1711 let x = SunAlgebra::<4>::from_components(&[
1712 0.1, -0.05, 0.08, 0.03, -0.06, 0.02, 0.04, -0.07, 0.09, -0.03, 0.01, 0.05, -0.02, 0.06,
1713 0.04,
1714 ]);
1715 let g = SUN::<4>::exp(&x);
1716 assert!(g.verify_unitarity(1e-10));
1717
1718 let x_back = SUN::<4>::log(&g).expect("log should succeed near identity");
1719 let diff: f64 = x
1720 .coefficients()
1721 .iter()
1722 .zip(x_back.coefficients().iter())
1723 .map(|(a, b)| (a - b).powi(2))
1724 .sum::<f64>()
1725 .sqrt();
1726
1727 assert!(diff < 1e-8, "SU(4) exp/log roundtrip error: {:.2e}", diff);
1728 }
1729
1730 #[test]
1731 fn test_sun_algebra_dimensions() {
1732 assert_eq!(SunAlgebra::<2>::DIM, 3); assert_eq!(SunAlgebra::<3>::DIM, 8); assert_eq!(SunAlgebra::<4>::DIM, 15); assert_eq!(SunAlgebra::<5>::DIM, 24); }
1737
1738 #[test]
1739 fn test_sun_algebra_zero() {
1740 let zero = SunAlgebra::<3>::zero();
1741 assert_eq!(zero.coefficients.len(), 8);
1742 assert!(zero.coefficients.iter().all(|&x| x == 0.0));
1743 }
1744
1745 #[test]
1746 fn test_sun_algebra_add_scale() {
1747 let x = SunAlgebra::<2>::basis_element(0);
1748 let y = SunAlgebra::<2>::basis_element(1);
1749
1750 let sum = &x + &y;
1751 assert_eq!(sum.coefficients, vec![1.0, 1.0, 0.0]);
1752
1753 let scaled = x.scale(2.5);
1754 assert_eq!(scaled.coefficients, vec![2.5, 0.0, 0.0]);
1755 }
1756
1757 #[test]
1758 fn test_sun_identity() {
1759 let id = SUN::<3>::identity();
1760 assert!(id.verify_unitarity(1e-10));
1761 assert_relative_eq!(id.distance_to_identity(), 0.0, epsilon = 1e-10);
1762 }
1763
1764 #[test]
1765 fn test_sun_exponential_preserves_unitarity() {
1766 let algebra =
1768 SunAlgebra::<3>::from_components(&[0.5, -0.3, 0.8, 0.2, -0.6, 0.4, 0.1, -0.2]);
1769 let g = SUN::<3>::exp(&algebra);
1770
1771 assert!(
1773 g.verify_unitarity(1e-10),
1774 "Exponential should preserve unitarity"
1775 );
1776 }
1777
1778 #[test]
1779 fn test_sun_exp_identity() {
1780 let zero = SunAlgebra::<4>::zero();
1781 let g = SUN::<4>::exp(&zero);
1782 assert_relative_eq!(g.distance_to_identity(), 0.0, epsilon = 1e-10);
1783 }
1784
1785 #[test]
1786 fn test_sun_group_composition() {
1787 let g1 = SUN::<2>::exp(&SunAlgebra::<2>::basis_element(0).scale(0.5));
1788 let g2 = SUN::<2>::exp(&SunAlgebra::<2>::basis_element(1).scale(0.3));
1789
1790 let product = g1.compose(&g2);
1791
1792 assert!(product.verify_unitarity(1e-10));
1793 }
1794
1795 #[test]
1796 fn test_sun_inverse() {
1797 let algebra =
1798 SunAlgebra::<3>::from_components(&[0.2, 0.3, -0.1, 0.5, -0.2, 0.1, 0.4, -0.3]);
1799 let g = SUN::<3>::exp(&algebra);
1800 let g_inv = g.inverse();
1801
1802 let product = g.compose(&g_inv);
1803
1804 assert_relative_eq!(product.distance_to_identity(), 0.0, epsilon = 1e-9);
1805 }
1806
1807 #[test]
1808 fn test_sun_adjoint_action_preserves_norm() {
1809 let g = SUN::<3>::exp(&SunAlgebra::<3>::basis_element(0).scale(1.2));
1810 let x = SunAlgebra::<3>::basis_element(2).scale(0.5);
1811
1812 let ad_x = g.adjoint_action(&x);
1813
1814 assert_relative_eq!(x.norm(), ad_x.norm(), epsilon = 1e-9);
1816 }
1817
1818 #[test]
1819 fn test_sun_exp_log_roundtrip() {
1820 let x = SunAlgebra::<3>::from_components(&[0.1, -0.2, 0.15, 0.08, -0.12, 0.05, 0.1, -0.06]);
1822 let g = SUN::<3>::exp(&x);
1823 assert!(g.verify_unitarity(1e-10));
1824
1825 let x_back = SUN::<3>::log(&g).expect("log should succeed near identity");
1826 let diff_norm: f64 = x
1827 .coefficients
1828 .iter()
1829 .zip(x_back.coefficients.iter())
1830 .map(|(a, b)| (a - b).powi(2))
1831 .sum::<f64>()
1832 .sqrt();
1833
1834 assert!(
1835 diff_norm < 1e-8,
1836 "SU(3) exp/log roundtrip error: {:.2e}",
1837 diff_norm
1838 );
1839 }
1840
1841 #[test]
1842 fn test_sun_exp_log_roundtrip_su2() {
1843 let x = SunAlgebra::<2>::from_components(&[0.3, -0.2, 0.4]);
1845 let g = SUN::<2>::exp(&x);
1846 assert!(g.verify_unitarity(1e-10));
1847
1848 let x_back = SUN::<2>::log(&g).expect("log should succeed");
1849 let diff_norm: f64 = x
1850 .coefficients
1851 .iter()
1852 .zip(x_back.coefficients.iter())
1853 .map(|(a, b)| (a - b).powi(2))
1854 .sum::<f64>()
1855 .sqrt();
1856
1857 assert!(
1858 diff_norm < 1e-8,
1859 "SU(2) exp/log roundtrip error: {:.2e}",
1860 diff_norm
1861 );
1862 }
1863
1864 #[test]
1865 fn test_sun_log_exp_roundtrip() {
1866 let x = SunAlgebra::<3>::from_components(&[0.2, 0.3, -0.1, 0.5, -0.2, 0.1, 0.4, -0.3]);
1868 let g = SUN::<3>::exp(&x);
1869
1870 let log_g = SUN::<3>::log(&g).expect("log should succeed");
1871 let g_back = SUN::<3>::exp(&log_g);
1872
1873 assert_relative_eq!(
1874 g.distance_to_identity(),
1875 g_back.distance_to_identity(),
1876 epsilon = 1e-8
1877 );
1878
1879 let product = g.compose(&g_back.inverse());
1881 assert_relative_eq!(product.distance_to_identity(), 0.0, epsilon = 1e-8);
1882 }
1883
1884 #[test]
1885 fn test_sun_jacobi_identity() {
1886 let x = SunAlgebra::<3>::basis_element(0);
1887 let y = SunAlgebra::<3>::basis_element(1);
1888 let z = SunAlgebra::<3>::basis_element(2);
1889
1890 let t1 = x.bracket(&y.bracket(&z));
1892 let t2 = y.bracket(&z.bracket(&x));
1893 let t3 = z.bracket(&x.bracket(&y));
1894 let sum = t1.add(&t2).add(&t3);
1895
1896 assert!(
1897 sum.norm() < 1e-10,
1898 "Jacobi identity violated for SU(3): ||sum|| = {:.2e}",
1899 sum.norm()
1900 );
1901 }
1902
1903 #[test]
1904 fn test_sun_bracket_antisymmetry() {
1905 let x = SunAlgebra::<4>::basis_element(0);
1906 let y = SunAlgebra::<4>::basis_element(3);
1907
1908 let xy = x.bracket(&y);
1909 let yx = y.bracket(&x);
1910
1911 for i in 0..SunAlgebra::<4>::DIM {
1913 assert_relative_eq!(xy.coefficients[i], -yx.coefficients[i], epsilon = 1e-10);
1914 }
1915 }
1916
1917 #[test]
1918 fn test_sun_bracket_bilinearity() {
1919 let x = SunAlgebra::<3>::basis_element(0);
1920 let y = SunAlgebra::<3>::basis_element(3);
1921 let z = SunAlgebra::<3>::basis_element(5);
1922 let alpha = 2.5;
1923
1924 let lhs = x.scale(alpha).add(&y).bracket(&z);
1926 let rhs = x.bracket(&z).scale(alpha).add(&y.bracket(&z));
1927 for i in 0..SunAlgebra::<3>::DIM {
1928 assert!(
1929 (lhs.coefficients[i] - rhs.coefficients[i]).abs() < 1e-14,
1930 "Left linearity failed at component {}: {} vs {}",
1931 i,
1932 lhs.coefficients[i],
1933 rhs.coefficients[i]
1934 );
1935 }
1936
1937 let lhs = z.bracket(&x.scale(alpha).add(&y));
1939 let rhs = z.bracket(&x).scale(alpha).add(&z.bracket(&y));
1940 for i in 0..SunAlgebra::<3>::DIM {
1941 assert!(
1942 (lhs.coefficients[i] - rhs.coefficients[i]).abs() < 1e-14,
1943 "Right linearity failed at component {}: {} vs {}",
1944 i,
1945 lhs.coefficients[i],
1946 rhs.coefficients[i]
1947 );
1948 }
1949 }
1950}