use g_math::fixed_point::{FixedPoint, FixedVector, FixedMatrix};
use g_math::fixed_point::imperative::lie_group::*;
use g_math::fixed_point::imperative::manifold::*;
use g_math::fixed_point::imperative::curvature::*;
use g_math::fixed_point::imperative::projective::*;
fn fp(s: &str) -> FixedPoint {
if s.starts_with('-') { -FixedPoint::from_str(&s[1..]) }
else { FixedPoint::from_str(s) }
}
fn tol() -> FixedPoint {
#[cfg(table_format = "q16_16")]
{ fp("0.01") } #[cfg(not(table_format = "q16_16"))]
{ fp("0.001") }
}
fn tight() -> FixedPoint {
#[cfg(table_format = "q16_16")]
{ fp("0.01") }
#[cfg(table_format = "q32_32")]
{ fp("0.0001") }
#[cfg(not(any(table_format = "q16_16", table_format = "q32_32")))]
{ fp("0.000000001") }
}
fn assert_fp(got: FixedPoint, exp: FixedPoint, tol: FixedPoint, name: &str) {
let d = (got - exp).abs();
assert!(d < tol, "{}: got {}, expected {}, diff={}", name, got, exp, d);
}
#[test]
fn test_gln_exp_mpmath() {
let gl = GLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("0.5"), fp("0.1"), fp("0.2"), fp("-0.3")]);
let g = gl.lie_exp(&xi).unwrap();
assert_fp(g.get(0, 0), fp("1.6615875516813432893"), tol(), "GL exp mpmath [0,0]");
assert_fp(g.get(0, 1), fp("0.1138625723808770567"), tol(), "GL exp mpmath [0,1]");
assert_fp(g.get(1, 0), fp("0.2277251447617541135"), tol(), "GL exp mpmath [1,0]");
assert_fp(g.get(1, 1), fp("0.7506869726343268984"), tol(), "GL exp mpmath [1,1]");
}
#[test]
fn test_gln_inverse_mpmath() {
let gl = GLn { n: 2 };
let g = FixedMatrix::from_slice(2, 2, &[fp("2"), fp("1"), fp("1"), fp("3")]);
let g_inv = gl.group_inverse(&g).unwrap();
assert_fp(g_inv.get(0, 0), fp("0.6"), tol(), "GL inv mpmath [0,0]");
assert_fp(g_inv.get(0, 1), fp("-0.2"), tol(), "GL inv mpmath [0,1]");
assert_fp(g_inv.get(1, 0), fp("-0.2"), tol(), "GL inv mpmath [1,0]");
assert_fp(g_inv.get(1, 1), fp("0.4"), tol(), "GL inv mpmath [1,1]");
}
#[test]
fn test_on_exp_mpmath() {
let o3 = On { n: 3 };
let xi = FixedVector::from_slice(&[fp("0.5"), fp("0.3"), fp("0.7")]);
let g = o3.lie_exp(&xi).unwrap();
assert_fp(g.get(0, 0), fp("0.8414377968733187047"), tol(), "O(3) exp mpmath [0,0]");
assert_fp(g.get(0, 1), fp("0.3357121957015680886"), tol(), "O(3) exp mpmath [0,1]");
assert_fp(g.get(0, 2), fp("0.4234144017982946449"), tol(), "O(3) exp mpmath [0,2]");
assert_fp(g.get(1, 1), fp("0.6548940284889877914"), tol(), "O(3) exp mpmath [1,1]");
assert_fp(g.get(2, 2), fp("0.7295115358427201702"), tol(), "O(3) exp mpmath [2,2]");
}
#[test]
fn test_sln_exp_mpmath() {
let sl = SLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("1"), fp("0.5"), fp("-0.3")]);
let g = sl.lie_exp(&xi).unwrap();
assert_fp(g.get(0, 0), fp("2.6037809875877481045"), tol(), "SL exp mpmath [0,0]");
assert_fp(g.get(0, 1), fp("0.5739053999421328102"), tol(), "SL exp mpmath [0,1]");
assert_fp(g.get(1, 0), fp("-0.3443432399652796734"), tol(), "SL exp mpmath [1,0]");
assert_fp(g.get(1, 1), fp("0.3081593878192168635"), tol(), "SL exp mpmath [1,1]");
let det = g.get(0, 0) * g.get(1, 1) - g.get(0, 1) * g.get(1, 0);
assert_fp(det, fp("1"), tol(), "SL exp det=1 (mpmath)");
}
#[test]
fn test_moebius_complex_mpmath() {
let z = FixedPoint::ZERO;
let one = FixedPoint::one();
let mc = MoebiusComplex::new(
(fp("2"), z), (one, z), (one, z), (fp("2"), z)
);
let result = mc.apply((z, one)).unwrap(); assert_fp(result.0, fp("0.8"), tol(), "Möbius(i) re = 0.8 (mpmath)");
assert_fp(result.1, fp("0.6"), tol(), "Möbius(i) im = 0.6 (mpmath)");
}
#[test]
fn test_gln_exp_large_entries_mpmath() {
let gl = GLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("3"), fp("-1"), fp("2"), fp("-3")]);
let g = gl.lie_exp(&xi).unwrap();
assert_fp(g.get(0, 0), fp("15.032829041806324390"), tol(), "GL large exp [0,0]");
assert_fp(g.get(0, 1), fp("-2.6501126583129985229"), tol(), "GL large exp [0,1]");
assert_fp(g.get(1, 0), fp("5.3002253166259970459"), tol(), "GL large exp [1,0]");
assert_fp(g.get(1, 1), fp("-0.8678469080716667474"), tol(), "GL large exp [1,1]");
}
#[test]
fn test_sln_exp_large_mpmath() {
let sl = SLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("5"), fp("2"), fp("-3")]);
let g = sl.lie_exp(&xi).unwrap();
assert_fp(g.get(0, 0), fp("83.918720100582232469"), tol(), "SL large exp [0,0]");
assert_fp(g.get(0, 1), fp("17.930726283430598968"), tol(), "SL large exp [0,1]");
assert_fp(g.get(1, 0), fp("-26.896089425145898452"), tol(), "SL large exp [1,0]");
assert_fp(g.get(1, 1), fp("-5.7349113165707623710"), tol(), "SL large exp [1,1]");
let det = g.get(0, 0) * g.get(1, 1) - g.get(0, 1) * g.get(1, 0);
assert_fp(det, fp("1"), tol(), "SL large exp det=1 (mpmath)");
}
#[test]
fn test_on_near_pi_rotation_mpmath() {
let o3 = On { n: 3 };
let xi = FixedVector::from_slice(&[fp("2.9"), fp("0.5"), fp("0.3")]);
let g = o3.lie_exp(&xi).unwrap();
assert_fp(g.get(0, 0), fp("-0.96280279222731531"), tol(), "O(3) near-π [0,0]");
assert_fp(g.get(1, 1), fp("-0.92653853740556352"), tol(), "O(3) near-π [1,1]");
assert_fp(g.get(2, 2), fp("0.92293845850377745"), tol(), "O(3) near-π [2,2]");
let qtq = &g.transpose() * &g;
let id = FixedMatrix::identity(3);
for i in 0..3 { for j in 0..3 {
assert_fp(qtq.get(i, j), id.get(i, j), tol(),
&format!("O(3) near-π QᵀQ[{},{}]", i, j));
}}
}
#[test]
fn test_gln_exp_log_roundtrip_mpmath() {
let gl = GLn { n: 2 };
let a = FixedMatrix::from_slice(2, 2, &[fp("1.1"), fp("0.2"), fp("0.3"), fp("1.4")]);
let log_a = gl.lie_log(&a).unwrap();
let exp_log_a = gl.lie_exp(&log_a).unwrap();
for i in 0..2 { for j in 0..2 {
assert_fp(exp_log_a.get(i, j), a.get(i, j), tol(),
&format!("GL exp(log(A))[{},{}]", i, j));
}}
}
#[test]
fn test_sln_bracket_traceless() {
let sl = SLn { n: 3 };
let xi = FixedVector::from_slice(&[
fp("1"), fp("-2"),
fp("0.5"), fp("-0.3"), fp("0.7"), fp("1.1"), fp("-0.9"), fp("0.4"),
]);
let eta = FixedVector::from_slice(&[
fp("-1"), fp("0.5"),
fp("0.8"), fp("-0.6"), fp("0.2"), fp("-0.4"), fp("1.3"), fp("-0.7"),
]);
let bracket = sl.bracket(&xi, &eta);
let bracket_mat = sl.hat_sln(&bracket);
assert_fp(bracket_mat.trace(), fp("0"), tol(), "SL(3) [A,B] traceless");
}
#[test]
#[cfg(not(any(table_format = "q16_16", table_format = "q32_32")))]
fn test_ugod_matmul_intermediate_overflow() {
let gl = GLn { n: 2 };
let large = fp("1000000000"); let a = FixedMatrix::from_slice(2, 2, &[large, large, large, large]);
let b = FixedMatrix::from_slice(2, 2, &[large, large, large, large]);
let ab = gl.compose(&a, &b);
let expected = fp("2000000000000000000");
assert_fp(ab.get(0, 0), expected, fp("1"),
"UGOD: matmul intermediate overflow absorbed by compute tier");
}
#[test]
#[cfg(not(any(table_format = "q16_16", table_format = "q32_32")))]
fn test_ugod_dot_product_many_terms() {
let n = 64;
let val = fp("100000000"); let a = FixedVector::from_slice(&vec![val; n]);
let b = FixedVector::from_slice(&vec![val; n]);
let result = a.dot_precise(&b); let expected = fp("640000000000000000");
assert_fp(result, expected, fp("1"),
"UGOD: 64-element dot at compute tier = 1 ULP");
}
#[test]
fn test_ugod_transcendental_chain_persistence() {
use g_math::canonical::{gmath, evaluate};
let expr = gmath("0.5").exp().sin(); let result_sv = evaluate(&expr).unwrap();
let result_str = format!("{}", result_sv);
let result_fp = fp(&result_str);
let expected = fp("0.9969653876139675347");
assert_fp(result_fp, expected, fp("0.0001"),
"UGOD: sin(exp(x)) FASC chain persistence via LazyExpr");
}
#[test]
fn test_ugod_cholesky_fused_compute_tier() {
use g_math::fixed_point::imperative::decompose::cholesky_decompose;
let a = FixedMatrix::from_slice(2, 2, &[fp("4"), fp("2"), fp("2"), fp("5")]);
let chol = cholesky_decompose(&a).unwrap();
assert_fp(chol.l.get(0, 0), fp("2"), tol(), "UGOD Cholesky L[0,0] fused compute tier");
assert_fp(chol.l.get(1, 0), fp("1"), tol(), "UGOD Cholesky L[1,0] fused compute tier");
assert_fp(chol.l.get(1, 1), fp("2"), tol(), "UGOD Cholesky L[1,1] fused compute tier");
}
#[test]
#[cfg(not(table_format = "q16_16"))]
fn test_ugod_exp_intermediate_overflow_succeeds() {
let result = fp("20").try_exp();
assert!(result.is_ok(), "exp(20) should succeed via tier N+1");
let val = result.unwrap();
assert_fp(val, fp("485165195.40979027355"), fp("1"),
"UGOD: exp(20) computed at tier N+1 matches mpmath");
}
#[test]
fn test_ugod_exp_overflow_detected_at_downscale() {
let result = fp("44").try_exp();
match result {
Ok(v) => {
#[cfg(not(any(table_format = "q16_16", table_format = "q32_32")))]
assert!(v > fp("12000000000000000000"),
"exp(44) should be ~1.28e19, got {}", v);
#[cfg(any(table_format = "q16_16", table_format = "q32_32"))]
println!(" exp(44) returned Ok({}) on small profile (native compute overflow)", v);
}
Err(e) => {
println!(" exp(44) overflow detected by UGOD: {:?}", e);
}
}
}
#[test]
fn test_ugod_fasc_chain_vs_imperative_precision() {
use g_math::canonical::{gmath, evaluate};
use g_math::canonical::set_gmath_mode;
set_gmath_mode("binary:binary").ok();
let expr = (gmath("0.7").sin() + gmath("0.3")).cos();
let fasc_result = evaluate(&expr).unwrap();
use g_math::canonical::reset_gmath_mode;
reset_gmath_mode();
let fasc_str = format!("{}", fasc_result);
let fasc_val = FixedPoint::from_str(
fasc_str.trim_matches(|c: char| !c.is_ascii_digit() && c != '.' && c != '-')
);
let imp_val = (fp("0.7").sin() + fp("0.3")).cos();
let _expected = imp_val;
let diff = (fasc_val - imp_val).abs();
println!("\nFASC chain vs imperative:");
println!(" FASC: {}", fasc_val);
println!(" Imperative: {}", imp_val);
println!(" diff: {}", diff);
assert!(diff < fp("0.001"),
"FASC and imperative should agree: diff={}", diff);
}
#[test]
fn test_gln_singular_returns_error() {
let gl = GLn { n: 2 };
let singular = FixedMatrix::from_slice(2, 2, &[fp("1"), fp("2"), fp("2"), fp("4")]); let result = gl.group_inverse(&singular);
assert!(result.is_err(), "GL inverse of singular should return Err via UGOD");
}
#[test]
fn test_sln_singular_inverse_returns_error() {
let sl = SLn { n: 2 };
let singular = FixedMatrix::from_slice(2, 2, &[fp("0"), fp("0"), fp("0"), fp("0")]);
let result = sl.group_inverse(&singular);
assert!(result.is_err(), "SL inverse of zero matrix should Err via UGOD");
}
#[test]
#[cfg(not(any(table_format = "q16_16", table_format = "q32_32")))]
fn test_gln_exp_overflow_large_input() {
let gl = GLn { n: 2 };
let large = FixedVector::from_slice(&[fp("40"), fp("0"), fp("0"), fp("40")]);
let result = gl.lie_exp(&large);
match result {
Ok(g) => {
assert!(g.get(0, 0) > fp("200000000000000000"), "exp(40) should be ~2.35e17");
assert_fp(g.get(0, 1), fp("0"), tol(), "exp(diag) off-diagonal = 0");
}
Err(_) => {
}
}
}
#[test]
fn test_sln_log_of_near_singular_returns_error() {
let sl = SLn { n: 2 };
let near_singular = FixedMatrix::from_slice(2, 2, &[
fp("0.001"), fp("0"),
fp("0"), fp("0.001"),
]);
let result = sl.lie_log(&near_singular);
match result {
Ok(xi) => {
let m = sl.hat_sln(&xi);
assert_fp(m.trace(), fp("0"), tol(), "SL log result traceless even for det≠1");
}
Err(_) => { }
}
}
#[test]
fn test_stiefel_non_orthogonal_input() {
let st = StiefelManifold { k: 1, n: 3 };
let base = FixedVector::from_slice(&[fp("1"), fp("1"), fp("1")]);
let tangent = FixedVector::from_slice(&[fp("0.1"), fp("-0.1"), fp("0")]);
let result = st.exp_map(&base, &tangent).unwrap();
let rm = stiefel_vec_to_mat_pub(&result, 3, 1);
let qtq = &rm.transpose() * &rm;
assert_fp(qtq.get(0, 0), fp("1"), tol(), "Stiefel QR retraction normalizes");
}
#[test]
fn test_geodesic_christoffel_overflow_handled() {
let metric = SphereMetric { radius: fp("0.01") }; let p = FixedVector::from_slice(&[fp("1"), fp("0.5")]);
let v = FixedVector::from_slice(&[fp("0.001"), fp("0.001")]);
let result = geodesic_integrate(&metric, &p, &v, fp("0.01"), 10);
assert!(result.is_ok(), "Geodesic on high-curvature manifold should not panic");
}
#[test]
fn test_gln_compose_uses_compute_tier() {
let gl = GLn { n: 2 };
let a = FixedMatrix::from_slice(2, 2, &[fp("1.1"), fp("0.2"), fp("0.3"), fp("1.4")]);
let b = FixedMatrix::from_slice(2, 2, &[fp("0.9"), fp("-0.1"), fp("-0.2"), fp("1.3")]);
let ab = gl.compose(&a, &b);
assert_fp(ab.get(0, 0), fp("0.95"), fp("0.0001"), "tier N+1 matmul [0,0]");
assert_fp(ab.get(0, 1), fp("0.15"), fp("0.0001"), "tier N+1 matmul [0,1]");
assert_fp(ab.get(1, 0), fp("-0.01"), fp("0.0001"), "tier N+1 matmul [1,0]");
assert_fp(ab.get(1, 1), fp("1.79"), fp("0.0001"), "tier N+1 matmul [1,1]");
}
#[test]
fn test_parallel_transport_contraction_at_compute_tier() {
let metric = EuclideanMetric { dim: 3 };
let curve = vec![
FixedVector::from_slice(&[fp("0"), fp("0"), fp("0")]),
FixedVector::from_slice(&[fp("0.001"), fp("0.002"), fp("0.003")]),
FixedVector::from_slice(&[fp("0.002"), fp("0.004"), fp("0.006")]),
];
let v = FixedVector::from_slice(&[fp("1"), fp("0"), fp("0")]);
let result = parallel_transport_ode(&metric, &curve, &v, 0).unwrap();
assert_fp(result[0], fp("1"), tight(), "compute-tier PT: flat v[0]=1");
assert_fp(result[1], fp("0"), tight(), "compute-tier PT: flat v[1]=0");
assert_fp(result[2], fp("0"), tight(), "compute-tier PT: flat v[2]=0");
}
#[test]
fn test_sln_exp_det_precision() {
let sl = SLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("0.7"), fp("0.3"), fp("-0.5")]);
let g = sl.lie_exp(&xi).unwrap();
let det = g.get(0, 0) * g.get(1, 1) - g.get(0, 1) * g.get(1, 0);
let det_err = (det - FixedPoint::one()).abs();
assert!(det_err < fp("0.001"),
"SL det error {} — tier N+1 should keep det≈1 to high precision", det_err);
}
#[test]
fn test_gln_identity() {
let gl = GLn { n: 2 };
let id = gl.identity_element();
assert_fp(id.get(0, 0), fp("1"), tight(), "GL(2) id[0,0]");
assert_fp(id.get(0, 1), fp("0"), tight(), "GL(2) id[0,1]");
}
#[test]
fn test_gln_hat_vee_roundtrip() {
let gl = GLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("1"), fp("2"), fp("3"), fp("4")]);
let m = gl.hat_gln(&xi);
let xi2 = gl.vee_gln(&m);
for i in 0..4 { assert_fp(xi2[i], xi[i], tight(), &format!("GL hat/vee[{}]", i)); }
}
#[test]
fn test_gln_exp_identity() {
let gl = GLn { n: 2 };
let zero = FixedVector::from_slice(&[fp("0"); 4]);
let result = gl.lie_exp(&zero).unwrap();
let id = FixedMatrix::identity(2);
for i in 0..2 { for j in 0..2 {
assert_fp(result.get(i, j), id.get(i, j), tol(), &format!("GL exp(0)[{},{}]", i, j));
}}
}
#[test]
fn test_gln_compose_inverse() {
let gl = GLn { n: 2 };
let g = FixedMatrix::from_slice(2, 2, &[fp("2"), fp("1"), fp("1"), fp("3")]);
let g_inv = gl.group_inverse(&g).unwrap();
let product = gl.compose(&g, &g_inv);
let id = FixedMatrix::identity(2);
for i in 0..2 { for j in 0..2 {
assert_fp(product.get(i, j), id.get(i, j), tol(), &format!("GL g*g⁻¹[{},{}]", i, j));
}}
}
#[test]
fn test_on_exp_orthogonal() {
let o4 = On { n: 4 };
let xi = FixedVector::from_slice(&[fp("0.1"), fp("0.2"), fp("0.3"), fp("0.4"), fp("0.5"), fp("0.6")]);
let g = o4.lie_exp(&xi).unwrap();
let qtq = &g.transpose() * &g;
let id = FixedMatrix::identity(4);
for i in 0..4 { for j in 0..4 {
assert_fp(qtq.get(i, j), id.get(i, j), tol(), &format!("O(4) QᵀQ[{},{}]", i, j));
}}
}
#[test]
fn test_on_inverse_is_transpose() {
let o3 = On { n: 3 };
let xi = FixedVector::from_slice(&[fp("0.5"), fp("0.3"), fp("0.7")]);
let g = o3.lie_exp(&xi).unwrap();
let g_inv = o3.group_inverse(&g).unwrap();
let gt = g.transpose();
for i in 0..3 { for j in 0..3 {
assert_fp(g_inv.get(i, j), gt.get(i, j), tight(), &format!("O(3) inv=transpose[{},{}]", i, j));
}}
}
#[test]
fn test_sln_hat_traceless() {
let sl = SLn { n: 3 };
let xi = FixedVector::from_slice(&[
fp("1"), fp("2"), fp("0.1"), fp("0.2"), fp("0.3"), fp("0.4"), fp("0.5"), fp("0.6"), ]);
let m = sl.hat_sln(&xi);
assert_fp(m.trace(), fp("0"), tight(), "SL(3) hat traceless");
}
#[test]
fn test_sln_hat_vee_roundtrip() {
let sl = SLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("1"), fp("0.5"), fp("-0.3")]);
let m = sl.hat_sln(&xi);
let xi2 = sl.vee_sln(&m);
for i in 0..3 { assert_fp(xi2[i], xi[i], tight(), &format!("SL hat/vee[{}]", i)); }
}
#[test]
fn test_sln_exp_det_one() {
let sl = SLn { n: 2 };
let xi = FixedVector::from_slice(&[fp("1"), fp("0.5"), fp("-0.3")]);
let g = sl.lie_exp(&xi).unwrap();
let det = g.get(0, 0) * g.get(1, 1) - g.get(0, 1) * g.get(1, 0);
assert_fp(det, fp("1"), tol(), "SL(2) exp det=1");
}
#[test]
fn test_sln_project_traceless() {
let m = FixedMatrix::from_slice(2, 2, &[fp("3"), fp("1"), fp("2"), fp("5")]);
let proj = SLn::project_traceless(&m);
assert_fp(proj.trace(), fp("0"), tight(), "project_traceless trace=0");
}
#[test]
fn test_parallel_transport_flat() {
let metric = EuclideanMetric { dim: 2 };
let curve = vec![
FixedVector::from_slice(&[fp("0"), fp("0")]),
FixedVector::from_slice(&[fp("1"), fp("0")]),
FixedVector::from_slice(&[fp("2"), fp("0")]),
];
let v = FixedVector::from_slice(&[fp("0"), fp("1")]);
let result = parallel_transport_ode(&metric, &curve, &v, 0).unwrap();
assert_fp(result[0], fp("0"), tol(), "flat PT[0]");
assert_fp(result[1], fp("1"), tol(), "flat PT[1]");
}
#[test]
fn test_geodesic_flat_straight_line() {
let metric = EuclideanMetric { dim: 2 };
let p = FixedVector::from_slice(&[fp("0"), fp("0")]);
let v = FixedVector::from_slice(&[fp("1"), fp("0.5")]);
let points = geodesic_integrate(&metric, &p, &v, fp("1"), 100).unwrap();
let final_pt = points.last().unwrap();
assert_fp(final_pt[0], fp("1"), tol(), "flat geodesic x");
assert_fp(final_pt[1], fp("0.5"), tol(), "flat geodesic y");
}
#[test]
fn test_parallel_transport_hyperbolic_preserves_norm() {
let metric = HyperbolicMetric;
let curve: Vec<FixedVector> = (0..101).map(|i| {
let x = fp("0.005") * FixedPoint::from_int(i); FixedVector::from_slice(&[x, fp("1")])
}).collect();
let v = FixedVector::from_slice(&[fp("1"), fp("0")]);
let result = parallel_transport_ode(&metric, &curve, &v, 0).unwrap();
let initial_norm = (v[0] * v[0] + v[1] * v[1]).sqrt();
let final_norm = (result[0] * result[0] + result[1] * result[1]).sqrt();
let drift = (final_norm - initial_norm).abs();
assert!(drift < fp("0.05"),
"H² PT norm drift {} should be < 0.05 (Euler on short curve)", drift);
}
#[test]
fn test_stiefel_dimension() {
let st = StiefelManifold { k: 2, n: 4 };
assert_eq!(st.dimension(), 5);
}
#[test]
fn test_stiefel_exp_preserves_orthonormality() {
let st = StiefelManifold { k: 2, n: 3 };
let q = FixedMatrix::from_slice(3, 2, &[
fp("1"), fp("0"),
fp("0"), fp("1"),
fp("0"), fp("0"),
]);
let base = stiefel_mat_to_vec_pub(&q);
let delta = FixedMatrix::from_slice(3, 2, &[
fp("0"), fp("0"),
fp("0"), fp("0"),
fp("0.1"), fp("0.2"),
]);
let tangent = stiefel_mat_to_vec_pub(&delta);
let result_vec = st.exp_map(&base, &tangent).unwrap();
let result_mat = stiefel_vec_to_mat_pub(&result_vec, 3, 2);
let qtq = &result_mat.transpose() * &result_mat;
let id2 = FixedMatrix::identity(2);
for i in 0..2 { for j in 0..2 {
assert_fp(qtq.get(i, j), id2.get(i, j), tol(),
&format!("Stiefel QᵀQ[{},{}]", i, j));
}}
}
#[test]
fn test_stiefel_log_map() {
let st = StiefelManifold { k: 1, n: 3 };
let q1 = FixedVector::from_slice(&[fp("1"), fp("0"), fp("0")]);
let q2 = FixedVector::from_slice(&[fp("0"), fp("1"), fp("0")]);
let log_v = st.log_map(&q1, &q2).unwrap();
let norm = st.norm(&q1, &log_v);
assert!(norm > fp("0.1"), "Stiefel log should give nonzero tangent");
}
fn stiefel_mat_to_vec_pub(m: &FixedMatrix) -> FixedVector {
let len = m.rows() * m.cols();
let mut v = FixedVector::new(len);
let mut idx = 0;
for c in 0..m.cols() {
for r in 0..m.rows() {
v[idx] = m.get(r, c);
idx += 1;
}
}
v
}
fn stiefel_vec_to_mat_pub(v: &FixedVector, n: usize, k: usize) -> FixedMatrix {
let mut m = FixedMatrix::new(n, k);
let mut idx = 0;
for c in 0..k {
for r in 0..n {
m.set(r, c, v[idx]);
idx += 1;
}
}
m
}
#[test]
fn test_product_dimension() {
let pm = ProductManifold::new(
Box::new(EuclideanSpace { dim: 3 }), 3,
Box::new(Sphere { dim: 2 }), 3, );
assert_eq!(pm.dimension(), 5);
}
#[test]
fn test_product_exp_log_roundtrip() {
let pm = ProductManifold::new(
Box::new(EuclideanSpace { dim: 2 }), 2,
Box::new(EuclideanSpace { dim: 3 }), 3,
);
let base = FixedVector::from_slice(&[fp("1"), fp("2"), fp("3"), fp("4"), fp("5")]);
let tangent = FixedVector::from_slice(&[fp("0.1"), fp("0.2"), fp("0.3"), fp("0.4"), fp("0.5")]);
let q = pm.exp_map(&base, &tangent).unwrap();
let log_v = pm.log_map(&base, &q).unwrap();
for i in 0..5 {
assert_fp(log_v[i], tangent[i], tol(), &format!("product roundtrip[{}]", i));
}
}
#[test]
fn test_product_distance() {
let pm = ProductManifold::new(
Box::new(EuclideanSpace { dim: 1 }), 1,
Box::new(EuclideanSpace { dim: 1 }), 1,
);
let p = FixedVector::from_slice(&[fp("0"), fp("0")]);
let q = FixedVector::from_slice(&[fp("3"), fp("4")]);
let d = pm.distance(&p, &q).unwrap();
assert_fp(d, fp("5"), tol(), "product distance = 5");
}
#[test]
fn test_product_parallel_transport() {
let pm = ProductManifold::new(
Box::new(EuclideanSpace { dim: 2 }), 2,
Box::new(EuclideanSpace { dim: 2 }), 2,
);
let base = FixedVector::from_slice(&[fp("0"), fp("0"), fp("0"), fp("0")]);
let target = FixedVector::from_slice(&[fp("1"), fp("1"), fp("1"), fp("1")]);
let tangent = FixedVector::from_slice(&[fp("0.5"), fp("-0.5"), fp("1"), fp("-1")]);
let result = pm.parallel_transport(&base, &target, &tangent).unwrap();
for i in 0..4 {
assert_fp(result[i], tangent[i], tight(), &format!("product PT[{}]", i));
}
}
#[test]
fn test_moebius_circle_preserving() {
let m = Moebius::new(fp("2"), fp("1"), fp("1"), fp("2"));
let _w1 = m.apply(fp("1")).unwrap(); let _w2 = m.apply(fp("-1")).unwrap(); let _w3 = m.apply(fp("0")).unwrap();
let z = FixedPoint::ZERO;
let one = FixedPoint::one();
let mc = MoebiusComplex::new(
(fp("2"), z), (one, z), (one, z), (fp("2"), z)
);
let w1c = mc.apply((one, z)).unwrap(); let w2c = mc.apply((z, one)).unwrap(); let w3c = mc.apply((-one, z)).unwrap(); let w4c = mc.apply((z, -one)).unwrap();
let a = (w1c.0 - w3c.0, w1c.1 - w3c.1); let b = (w2c.0 - w4c.0, w2c.1 - w4c.1); let c = (w1c.0 - w4c.0, w1c.1 - w4c.1); let d = (w2c.0 - w3c.0, w2c.1 - w3c.1);
let num = complex_mul_pub(a, b);
let den = complex_mul_pub(c, d);
let den_sq = den.0 * den.0 + den.1 * den.1;
if !den_sq.is_zero() {
let _cr_re = (num.0 * den.0 + num.1 * den.1) / den_sq;
let cr_im = (num.1 * den.0 - num.0 * den.1) / den_sq;
assert_fp(cr_im, fp("0"), tol(), "Möbius circle-preserving: CR imaginary ≈ 0");
}
}
fn complex_mul_pub(a: (FixedPoint, FixedPoint), b: (FixedPoint, FixedPoint)) -> (FixedPoint, FixedPoint) {
(a.0 * b.0 - a.1 * b.1, a.0 * b.1 + a.1 * b.0)
}
#[test]
fn test_moebius_maps_real_line() {
let m = Moebius::new(fp("3"), fp("1"), fp("2"), fp("5"));
let t0 = m.apply(fp("0")).unwrap();
assert_fp(t0, fp("0.2"), tol(), "T(0) = b/d");
let x_pole = fp("-2.5");
let denom = fp("2") * x_pole + fp("5"); assert_fp(denom.abs(), fp("0"), tol(), "T(-d/c) → ∞ (denom=0)");
}