use crate::traits::LieAlgebra;
pub const BCH_CONVERGENCE_RADIUS: f64 = 0.693;
pub const BCH_PRACTICAL_LIMIT: f64 = 2.0;
#[must_use]
pub fn bch_will_converge<A: LieAlgebra>(x: &A, y: &A) -> bool {
x.norm() + y.norm() < BCH_CONVERGENCE_RADIUS
}
#[must_use]
pub fn bch_is_practical<A: LieAlgebra>(x: &A, y: &A) -> bool {
x.norm() + y.norm() < BCH_PRACTICAL_LIMIT
}
#[must_use]
pub fn bch_second_order<A: LieAlgebra>(x: &A, y: &A) -> A {
debug_assert!(
x.norm() + y.norm() < BCH_PRACTICAL_LIMIT,
"BCH inputs exceed practical limit: ||X|| + ||Y|| = {} > {}. \
Use bch_checked() or bch_safe() for runtime-safe alternatives.",
x.norm() + y.norm(),
BCH_PRACTICAL_LIMIT
);
let xy_bracket = x.bracket(y);
let half_bracket = xy_bracket.scale(0.5);
x.add(y).add(&half_bracket)
}
#[must_use]
pub fn bch_third_order<A: LieAlgebra>(x: &A, y: &A) -> A {
debug_assert!(
x.norm() + y.norm() < BCH_PRACTICAL_LIMIT,
"BCH inputs exceed practical limit: ||X|| + ||Y|| = {} > {}. \
Use bch_checked() or bch_safe() for runtime-safe alternatives.",
x.norm() + y.norm(),
BCH_PRACTICAL_LIMIT
);
let xy_bracket = x.bracket(y); let half_bracket = xy_bracket.scale(0.5);
let x_xy = x.bracket(&xy_bracket);
let term3 = x_xy.scale(1.0 / 12.0);
let yx_bracket = xy_bracket.scale(-1.0); let y_yx = y.bracket(&yx_bracket); let term4 = y_yx.scale(1.0 / 12.0);
x.add(y).add(&half_bracket).add(&term3).add(&term4)
}
#[must_use]
pub fn bch_fourth_order<A: LieAlgebra>(x: &A, y: &A) -> A {
debug_assert!(
x.norm() + y.norm() < BCH_PRACTICAL_LIMIT,
"BCH inputs exceed practical limit: ||X|| + ||Y|| = {} > {}. \
Use bch_checked() or bch_safe() for runtime-safe alternatives.",
x.norm() + y.norm(),
BCH_PRACTICAL_LIMIT
);
let z3 = bch_third_order(x, y);
let xy = x.bracket(y);
let x_xy = x.bracket(&xy); let y_x_xy = y.bracket(&x_xy); let term4 = y_x_xy.scale(-1.0 / 24.0);
z3.add(&term4)
}
#[must_use]
pub fn bch_fifth_order<A: LieAlgebra>(x: &A, y: &A) -> A {
debug_assert!(
x.norm() + y.norm() < BCH_PRACTICAL_LIMIT,
"BCH inputs exceed practical limit: ||X|| + ||Y|| = {} > {}. \
Use bch_checked() or bch_safe() for runtime-safe alternatives.",
x.norm() + y.norm(),
BCH_PRACTICAL_LIMIT
);
let z4 = bch_fourth_order(x, y);
let xy = x.bracket(y); let yx = xy.scale(-1.0);
let x_xy = x.bracket(&xy); let y_yx = y.bracket(&yx);
let x_x_xy = x.bracket(&x_xy); let x_x_x_xy = x.bracket(&x_x_xy); let y_y_yx = y.bracket(&y_yx); let y_y_y_yx = y.bracket(&y_y_yx); let ad4 = x_x_x_xy.add(&y_y_y_yx).scale(-1.0 / 720.0);
let y_x_x_xy = y.bracket(&x_x_xy); let x_y_y_yx = x.bracket(&y_y_yx); let mixed4 = y_x_x_xy.add(&x_y_y_yx).scale(1.0 / 360.0);
let y_xy = y.bracket(&xy); let x_y_xy = x.bracket(&y_xy); let y_x_y_xy = y.bracket(&x_y_xy); let x_yx = x.bracket(&yx); let y_x_yx = y.bracket(&x_yx); let x_y_x_yx = x.bracket(&y_x_yx); let alt = y_x_y_xy.add(&x_y_x_yx).scale(1.0 / 120.0);
z4.add(&ad4).add(&mixed4).add(&alt)
}
#[must_use]
pub fn bch_error_bound<A: LieAlgebra>(x: &A, y: &A, order: usize) -> f64 {
let norm_x = x.norm();
let norm_y = y.norm();
let total_norm = norm_x + norm_y;
let coefficient = match order {
1 => 1.0, 2 => 0.5, 3 => 0.1, 4 => 0.02, 5 => 0.005, _ => 1.0 / (order as f64).powi(2), };
coefficient * total_norm.powi((order + 1) as i32)
}
pub fn bch_checked<A: LieAlgebra>(x: &A, y: &A, order: usize) -> Result<A, BchError> {
if !(2..=5).contains(&order) {
return Err(BchError::InvalidOrder(order));
}
if !bch_will_converge(x, y) {
return Err(BchError::OutsideConvergenceRadius);
}
Ok(match order {
2 => bch_second_order(x, y),
3 => bch_third_order(x, y),
4 => bch_fourth_order(x, y),
5 => bch_fifth_order(x, y),
_ => unreachable!(),
})
}
pub fn bch_safe<G>(x: &G::Algebra, y: &G::Algebra, order: usize) -> Result<G::Algebra, BchError>
where
G: crate::traits::LieGroup,
{
if !(2..=5).contains(&order) {
return Err(BchError::InvalidOrder(order));
}
if bch_will_converge(x, y) {
let z = match order {
2 => bch_second_order(x, y),
3 => bch_third_order(x, y),
4 => bch_fourth_order(x, y),
5 => bch_fifth_order(x, y),
_ => unreachable!(),
};
return Ok(z);
}
let gx = G::exp(x);
let gy = G::exp(y);
let product = gx.compose(&gy);
match G::log(&product) {
Ok(z) => Ok(z),
Err(_) => Err(BchError::LogFailed),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum BchMethod {
Series { order: usize },
DirectComposition,
}
pub fn bch_safe_with_method<G>(
x: &G::Algebra,
y: &G::Algebra,
order: usize,
) -> Result<(G::Algebra, BchMethod), BchError>
where
G: crate::traits::LieGroup,
{
if !(2..=5).contains(&order) {
return Err(BchError::InvalidOrder(order));
}
if bch_will_converge(x, y) {
let z = match order {
2 => bch_second_order(x, y),
3 => bch_third_order(x, y),
4 => bch_fourth_order(x, y),
5 => bch_fifth_order(x, y),
_ => unreachable!(),
};
return Ok((z, BchMethod::Series { order }));
}
let gx = G::exp(x);
let gy = G::exp(y);
let product = gx.compose(&gy);
match G::log(&product) {
Ok(z) => Ok((z, BchMethod::DirectComposition)),
Err(_) => Err(BchError::LogFailed),
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum BchError {
InvalidOrder(usize),
OutsideConvergenceRadius,
LogFailed,
}
impl std::fmt::Display for BchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
BchError::InvalidOrder(order) => {
write!(f, "Invalid BCH order {}: must be 2, 3, 4, or 5", order)
}
BchError::OutsideConvergenceRadius => {
write!(
f,
"BCH inputs exceed convergence radius: ||X|| + ||Y|| > log(2) ≈ 0.693. \
Use bch_safe() for automatic fallback."
)
}
BchError::LogFailed => write!(
f,
"BCH fallback failed: log() of composed element failed (possibly at cut locus)"
),
}
}
}
impl std::error::Error for BchError {}
#[must_use]
pub fn bch_split<A: LieAlgebra>(z: &A) -> (A, A) {
let half_z = z.scale(0.5);
(half_z.clone(), half_z)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sun::{SunAlgebra, SUN};
use crate::traits::{LieAlgebra, LieGroup};
use crate::{Su2Algebra, Su3Algebra, SU2, SU3};
#[test]
fn test_bch_safe_small_inputs_uses_series() {
let x = Su2Algebra([0.1, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.1, 0.0]);
let (z, method) = bch_safe_with_method::<SU2>(&x, &y, 3).unwrap();
assert_eq!(method, BchMethod::Series { order: 3 });
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product = g1.compose(&g2);
let product_bch = SU2::exp(&z);
let distance = product
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-3, "Distance: {}", distance);
}
#[test]
fn test_bch_safe_large_inputs_uses_direct() {
let x = Su2Algebra([1.0, 0.0, 0.0]);
let y = Su2Algebra([0.0, 1.0, 0.0]);
let (z, method) = bch_safe_with_method::<SU2>(&x, &y, 3).unwrap();
assert_eq!(method, BchMethod::DirectComposition);
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product = g1.compose(&g2);
let product_bch = SU2::exp(&z);
let distance = product
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-10, "Distance: {}", distance);
}
#[test]
fn test_bch_safe_correctness_across_boundary() {
let x_below = Su2Algebra([0.3, 0.0, 0.0]);
let y_below = Su2Algebra([0.0, 0.3, 0.0]);
let (z_below, method_below) = bch_safe_with_method::<SU2>(&x_below, &y_below, 4).unwrap();
assert_eq!(method_below, BchMethod::Series { order: 4 });
let x_above = Su2Algebra([0.4, 0.0, 0.0]);
let y_above = Su2Algebra([0.0, 0.4, 0.0]);
let (z_above, method_above) = bch_safe_with_method::<SU2>(&x_above, &y_above, 4).unwrap();
assert_eq!(method_above, BchMethod::DirectComposition);
let g1_below = SU2::exp(&x_below);
let g2_below = SU2::exp(&y_below);
let product_below = g1_below.compose(&g2_below);
let product_bch_below = SU2::exp(&z_below);
let distance_below = product_below
.compose(&product_bch_below.inverse())
.distance_to_identity();
assert!(
distance_below < 0.15,
"Below boundary distance: {}",
distance_below
);
let g1_above = SU2::exp(&x_above);
let g2_above = SU2::exp(&y_above);
let product_above = g1_above.compose(&g2_above);
let product_bch_above = SU2::exp(&z_above);
let distance_above = product_above
.compose(&product_bch_above.inverse())
.distance_to_identity();
assert!(
distance_above < 1e-10,
"Above boundary distance: {}",
distance_above
);
}
#[test]
fn test_bch_safe_invalid_order() {
let x = Su2Algebra([0.1, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.1, 0.0]);
assert!(matches!(
bch_safe::<SU2>(&x, &y, 1),
Err(BchError::InvalidOrder(1))
));
assert!(matches!(
bch_safe::<SU2>(&x, &y, 7),
Err(BchError::InvalidOrder(7))
));
}
#[test]
fn test_bch_safe_su3() {
let x = Su3Algebra([0.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let y = Su3Algebra([0.0, 0.8, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let (z, method) = bch_safe_with_method::<SU3>(&x, &y, 3).unwrap();
assert_eq!(method, BchMethod::DirectComposition);
let g1 = SU3::exp(&x);
let g2 = SU3::exp(&y);
let product = g1.compose(&g2);
let product_bch = SU3::exp(&z);
let distance = product
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-8, "Distance: {}", distance);
}
#[test]
fn test_bch_safe_all_orders() {
let x = Su2Algebra([0.2, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.2, 0.0]);
for order in 2..=5 {
let z = bch_safe::<SU2>(&x, &y, order).unwrap();
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product = g1.compose(&g2);
let product_bch = SU2::exp(&z);
let distance = product
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-3, "Order {}: distance = {}", order, distance);
}
}
#[test]
fn test_bch_error_display() {
let err1 = BchError::InvalidOrder(7);
assert!(err1.to_string().contains('7'));
assert!(err1.to_string().contains("2, 3, 4, or 5"));
let err2 = BchError::LogFailed;
assert!(err2.to_string().contains("cut locus"));
}
#[test]
fn test_bch_second_order_su2() {
let x = Su2Algebra([0.05, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.05, 0.0]);
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product_direct = g1.compose(&g2);
let z = bch_second_order(&x, &y);
let product_bch = SU2::exp(&z);
let distance = product_direct
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 5e-3, "Distance: {}", distance);
}
#[test]
fn test_bch_third_order_su2() {
let x = Su2Algebra([0.1, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.1, 0.0]);
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product_direct = g1.compose(&g2);
let z = bch_third_order(&x, &y);
let product_bch = SU2::exp(&z);
let distance = product_direct
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-3, "Distance: {}", distance);
}
#[test]
fn test_bch_second_order_su3() {
let x = Su3Algebra([0.05, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let y = Su3Algebra([0.0, 0.05, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let g1 = SU3::exp(&x);
let g2 = SU3::exp(&y);
let product_direct = g1.compose(&g2);
let z = bch_second_order(&x, &y);
let product_bch = SU3::exp(&z);
let distance = product_direct
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-3, "Distance: {}", distance);
}
#[test]
fn test_bch_split() {
let z = Su2Algebra([0.4, 0.3, 0.2]);
let (x, y) = bch_split(&z);
for i in 0..3 {
assert!((x.0[i] - z.0[i] / 2.0).abs() < 1e-10);
assert!((y.0[i] - z.0[i] / 2.0).abs() < 1e-10);
}
let gz = SU2::exp(&z);
let gx = SU2::exp(&x);
let gy = SU2::exp(&y);
let product = gx.compose(&gy);
let distance = gz.compose(&product.inverse()).distance_to_identity();
assert!(distance < 1e-10);
}
#[test]
fn test_bch_commutative_case() {
let x = Su2Algebra([0.2, 0.0, 0.0]);
let y = Su2Algebra([0.3, 0.0, 0.0]);
let z_second = bch_second_order(&x, &y);
let z_third = bch_third_order(&x, &y);
let x_plus_y = x.add(&y);
for i in 0..3 {
assert!((z_second.0[i] - x_plus_y.0[i]).abs() < 1e-10);
assert!((z_third.0[i] - x_plus_y.0[i]).abs() < 1e-10);
}
}
#[test]
fn test_bch_antisymmetry() {
let x = Su2Algebra([0.1, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.1, 0.0]);
let z_xy = bch_second_order(&x, &y);
let z_yx = bch_second_order(&y, &x);
let diff = z_xy.0[2] - z_yx.0[2];
assert!(diff.abs() > 0.01); }
#[test]
fn test_bch_fourth_order_su2() {
let x = Su2Algebra([0.15, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.15, 0.0]);
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product_direct = g1.compose(&g2);
let z = bch_fourth_order(&x, &y);
let product_bch = SU2::exp(&z);
let distance = product_direct
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-3, "Distance: {}", distance);
}
#[test]
fn test_bch_fifth_order_su2() {
let x = Su2Algebra([0.2, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.2, 0.0]);
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product_direct = g1.compose(&g2);
let z = bch_fifth_order(&x, &y);
let product_bch = SU2::exp(&z);
let distance = product_direct
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-3, "Distance: {}", distance);
}
#[test]
fn test_bch_fifth_order_su3() {
let x = Su3Algebra([0.15, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let y = Su3Algebra([0.0, 0.15, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]);
let g1 = SU3::exp(&x);
let g2 = SU3::exp(&y);
let product_direct = g1.compose(&g2);
let z = bch_fifth_order(&x, &y);
let product_bch = SU3::exp(&z);
let distance = product_direct
.compose(&product_bch.inverse())
.distance_to_identity();
assert!(distance < 1e-3, "Distance: {}", distance);
}
#[test]
fn test_bch_error_bounds() {
let x = Su2Algebra([0.1, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.1, 0.0]);
let error_2nd = bch_error_bound(&x, &y, 2);
let error_3rd = bch_error_bound(&x, &y, 3);
let error_4th = bch_error_bound(&x, &y, 4);
let error_5th = bch_error_bound(&x, &y, 5);
assert!(
error_5th < error_4th,
"5th order ({}) should have smaller error than 4th order ({})",
error_5th,
error_4th
);
assert!(
error_4th < error_3rd,
"4th order ({}) should have smaller error than 3rd order ({})",
error_4th,
error_3rd
);
assert!(
error_3rd < error_2nd,
"3rd order ({}) should have smaller error than 2nd order ({})",
error_3rd,
error_2nd
);
println!("Error bounds for ||X|| = ||Y|| = 0.1:");
println!(" 2nd order: {:.2e}", error_2nd);
println!(" 3rd order: {:.2e}", error_3rd);
println!(" 4th order: {:.2e}", error_4th);
println!(" 5th order: {:.2e}", error_5th);
}
#[test]
fn test_bch_convergence_order_comparison() {
let x = Su2Algebra([0.08, 0.0, 0.0]);
let y = Su2Algebra([0.0, 0.08, 0.0]);
let g1 = SU2::exp(&x);
let g2 = SU2::exp(&y);
let product_exact = g1.compose(&g2);
let z2 = bch_second_order(&x, &y);
let z3 = bch_third_order(&x, &y);
let z4 = bch_fourth_order(&x, &y);
let z5 = bch_fifth_order(&x, &y);
let dist2 = product_exact
.compose(&SU2::exp(&z2).inverse())
.distance_to_identity();
let dist3 = product_exact
.compose(&SU2::exp(&z3).inverse())
.distance_to_identity();
let dist4 = product_exact
.compose(&SU2::exp(&z4).inverse())
.distance_to_identity();
let dist5 = product_exact
.compose(&SU2::exp(&z5).inverse())
.distance_to_identity();
assert!(
dist3 <= dist2,
"3rd order ({:.2e}) should beat 2nd order ({:.2e})",
dist3,
dist2
);
assert!(
dist4 <= dist3,
"4th order ({:.2e}) should beat 3rd order ({:.2e})",
dist4,
dist3
);
assert!(
dist5 <= dist4,
"5th order ({:.2e}) should beat 4th order ({:.2e})",
dist5,
dist4
);
assert!(
dist5 < dist2,
"5th order ({:.2e}) should strictly beat 2nd order ({:.2e})",
dist5,
dist2
);
println!("BCH convergence for ||X|| = ||Y|| = 0.08:");
println!(" 2nd order error: {:.2e}", dist2);
println!(" 3rd order error: {:.2e}", dist3);
println!(" 4th order error: {:.2e}", dist4);
println!(" 5th order error: {:.2e}", dist5);
}
#[test]
fn test_bch_fourth_order_commutative() {
let x = Su2Algebra([0.2, 0.0, 0.0]);
let y = Su2Algebra([0.3, 0.0, 0.0]);
let z4 = bch_fourth_order(&x, &y);
let z5 = bch_fifth_order(&x, &y);
let x_plus_y = x.add(&y);
for i in 0..3 {
assert!(
(z4.0[i] - x_plus_y.0[i]).abs() < 1e-10,
"4th order should give X+Y when commutative"
);
assert!(
(z5.0[i] - x_plus_y.0[i]).abs() < 1e-10,
"5th order should give X+Y when commutative"
);
}
}
#[test]
fn test_bch_error_bound_scaling() {
let x_small = Su2Algebra([0.05, 0.0, 0.0]);
let y_small = Su2Algebra([0.0, 0.05, 0.0]);
let x_large = Su2Algebra([0.2, 0.0, 0.0]);
let y_large = Su2Algebra([0.0, 0.2, 0.0]);
let error_small = bch_error_bound(&x_small, &y_small, 3);
let error_large = bch_error_bound(&x_large, &y_large, 3);
let ratio = error_large / error_small;
assert!(
ratio > 200.0 && ratio < 300.0,
"Error ratio should be ~256, got {}",
ratio
);
}
#[test]
fn test_bch_vs_exp_log_sun3() {
let x = SunAlgebra::<3>::from_components(&[0.05, -0.03, 0.02, 0.04, -0.01, 0.03, -0.02, 0.01]);
let y = SunAlgebra::<3>::from_components(&[-0.02, 0.04, -0.03, 0.01, 0.05, -0.04, 0.02, -0.03]);
let direct = SUN::<3>::exp(&x).compose(&SUN::<3>::exp(&y));
let bch_z = bch_fifth_order(&x, &y);
let via_bch = SUN::<3>::exp(&bch_z);
let distance = direct.compose(&via_bch.inverse()).distance_to_identity();
assert!(
distance < 1e-8,
"SUN<3> BCH vs exp·log distance: {:.2e}",
distance
);
}
#[test]
fn test_bch_vs_exp_log_sun4() {
let mut x_coeffs = vec![0.0; 15];
let mut y_coeffs = vec![0.0; 15];
for i in 0..15 {
x_coeffs[i] = 0.03 * (i as f64 - 7.0) / 7.0;
y_coeffs[i] = -0.03 * ((14 - i) as f64 - 7.0) / 7.0;
}
let x = SunAlgebra::<4>::from_components(&x_coeffs);
let y = SunAlgebra::<4>::from_components(&y_coeffs);
let direct = SUN::<4>::exp(&x).compose(&SUN::<4>::exp(&y));
let bch_z = bch_fifth_order(&x, &y);
let via_bch = SUN::<4>::exp(&bch_z);
let distance = direct.compose(&via_bch.inverse()).distance_to_identity();
assert!(
distance < 1e-8,
"SUN<4> BCH vs exp·log distance: {:.2e}",
distance
);
}
#[test]
fn test_bch_su3_vs_sun3_consistency() {
let x_su3 = Su3Algebra::from_components(&[0.05, -0.03, 0.02, 0.04, -0.01, 0.03, -0.02, 0.01]);
let y_su3 = Su3Algebra::from_components(&[-0.02, 0.04, -0.03, 0.01, 0.05, -0.04, 0.02, -0.03]);
let x_sun = SunAlgebra::<3>::from_matrix(&x_su3.to_matrix());
let y_sun = SunAlgebra::<3>::from_matrix(&y_su3.to_matrix());
let z_su3 = bch_fifth_order(&x_su3, &y_su3);
let m_su3 = z_su3.to_matrix();
let z_sun = bch_fifth_order(&x_sun, &y_sun);
let m_sun = z_sun.to_matrix();
for r in 0..3 {
for c in 0..3 {
assert!(
(m_su3[(r, c)] - m_sun[(r, c)]).norm() < 1e-12,
"BCH matrix disagrees at ({},{}): SU3={}, SUN<3>={}",
r, c, m_su3[(r, c)], m_sun[(r, c)]
);
}
}
}
}