use hyperball::lorentz::LorentzModel;
use ndarray::array;
fn main() {
let model = LorentzModel::<f64>::new(1.0);
let origin = array![1.0, 0.0, 0.0];
println!("=== Lorentz Hyperboloid Model ===\n");
let inner = model.minkowski_dot(&origin.view(), &origin.view());
println!(
"Minkowski norm of origin: {:.4} (should be -1/c = -1.0)",
inner
);
println!(
"Origin on manifold? {}\n",
model.is_on_manifold(&origin.view(), 1e-6)
);
let points_euclidean = [
array![0.0, 0.0],
array![0.5, 0.0],
array![0.0, 0.5],
array![1.0, 1.0],
array![2.0, 0.0],
];
let points_lorentz: Vec<_> = points_euclidean
.iter()
.map(|p| model.from_euclidean(&p.view()))
.collect();
println!("Euclidean -> Lorentz lifting:");
for (euc, lor) in points_euclidean.iter().zip(&points_lorentz) {
println!(
" {:>10} -> [{:.3}, {:.3}, {:.3}] on manifold? {}",
format!("[{:.1}, {:.1}]", euc[0], euc[1]),
lor[0],
lor[1],
lor[2],
model.is_on_manifold(&lor.view(), 1e-6)
);
}
println!("\nPairwise hyperbolic distances:");
println!("{:>10}", "");
for (i, pe) in points_euclidean.iter().enumerate() {
print!("{:>10}", format!("[{:.1},{:.1}]", pe[0], pe[1]));
for j in 0..=i {
let d = model.distance(&points_lorentz[i].view(), &points_lorentz[j].view());
print!(" {:>6.3}", d);
}
println!();
}
println!("\nExp/log round-trip test:");
let p = &points_lorentz[3]; let v = model.log_map(&origin.view(), &p.view());
let p_back = model.exp_map(&origin.view(), &v.view());
let err: f64 = p
.iter()
.zip(p_back.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
println!(" point: [{:.6}, {:.6}, {:.6}]", p[0], p[1], p[2]);
println!(" log(o, p): [{:.6}, {:.6}, {:.6}]", v[0], v[1], v[2]);
println!(
" exp(o, v): [{:.6}, {:.6}, {:.6}]",
p_back[0], p_back[1], p_back[2]
);
println!(" round-trip error: {:.2e}", err);
println!("\nPoincare <-> Lorentz round-trip:");
let poincare_pt = model.to_euclidean(&p.view());
let back_to_lorentz = model.from_euclidean(&poincare_pt.view());
let conv_err: f64 = p
.iter()
.zip(back_to_lorentz.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
println!(" Lorentz: [{:.6}, {:.6}, {:.6}]", p[0], p[1], p[2]);
println!(" Poincare: [{:.6}, {:.6}]", poincare_pt[0], poincare_pt[1]);
println!(
" Back: [{:.6}, {:.6}, {:.6}]",
back_to_lorentz[0], back_to_lorentz[1], back_to_lorentz[2]
);
println!(" conversion error: {:.2e}", conv_err);
println!("\nKey properties:");
println!(" - Distances grow exponentially near boundary (hyperbolic metric)");
println!(" - Lorentz model is numerically stabler than Poincare for optimization");
println!(" - Both models are isometric (same distances after conversion)");
}