#![cfg(feature = "algebraic")]
use puremp::{Algebraic, FieldMatrix, Float, Int, Matrix, Rational, RoundingMode};
const N: RoundingMode = RoundingMode::Nearest;
const PREC: u64 = 128;
fn mat(rows: &[&[i64]]) -> Matrix<Rational> {
let data: Vec<Vec<Rational>> = rows
.iter()
.map(|r| r.iter().map(|&x| Rational::from(x)).collect())
.collect();
Matrix::from_rows(data)
}
fn charpoly_at(m: &Matrix<Rational>, x: &Algebraic) -> Algebraic {
let cp = m.characteristic_polynomial();
let coeffs = cp.coeffs();
let mut acc = Algebraic::from_int(Int::from_i64(0));
for c in coeffs.iter().rev() {
acc = acc.mul(x).add(&Algebraic::from_rational(c.clone()));
}
acc
}
fn det_shifted_f64(m: &Matrix<Rational>, lambda: &Algebraic) -> f64 {
let n = m.rows();
let lf = lambda.to_float(PREC, N);
let zero = Float::from_int(&Int::from_i64(0), PREC, N);
let mut b = Matrix::<Float>::filled(zero, n, n);
for i in 0..n {
for j in 0..n {
let mut e = Float::from_rational(m.get(i, j), PREC, N);
if i == j {
e = e.sub(&lf, PREC, N);
}
b.set(i, j, e);
}
}
FieldMatrix::determinant(&b).abs().to_f64()
}
fn assert_eigen(m: &Matrix<Rational>, evs: &[Algebraic]) {
for lambda in evs {
assert_eq!(
charpoly_at(m, lambda).signum(),
0,
"charpoly({lambda}) must be exactly zero"
);
assert!(
det_shifted_f64(m, lambda) < 1e-20,
"det(A − λ·I) must be ≈ 0 for λ = {lambda}"
);
}
}
fn alg_ints(vals: &[i64]) -> Vec<Algebraic> {
vals.iter()
.map(|&v| Algebraic::from_int(Int::from_i64(v)))
.collect()
}
#[test]
fn diagonal_matrix() {
let m = mat(&[&[2, 0, 0], &[0, 3, 0], &[0, 0, 5]]);
let evs = m.real_eigenvalues();
assert_eq!(evs, alg_ints(&[2, 3, 5]));
assert_eigen(&m, &evs);
}
#[test]
fn triangular_matrix() {
let m = mat(&[&[1, 2, 3], &[0, 4, 5], &[0, 0, 6]]);
let evs = m.real_eigenvalues();
assert_eq!(evs, alg_ints(&[1, 4, 6]));
assert_eigen(&m, &evs);
}
#[test]
fn symmetric_2x2() {
let m = mat(&[&[2, 1], &[1, 2]]);
let evs = m.real_eigenvalues();
assert_eq!(evs, alg_ints(&[1, 3]));
assert_eigen(&m, &evs);
let m = mat(&[&[0, 1], &[1, 0]]);
let evs = m.real_eigenvalues();
assert_eq!(evs, alg_ints(&[-1, 1]));
assert_eigen(&m, &evs);
}
#[test]
fn irrational_eigenvalues_sqrt2() {
let m = mat(&[&[0, 2], &[1, 0]]);
let cp = m.characteristic_polynomial();
assert_eq!(
cp.coeffs().to_vec(),
vec![Rational::from(-2), Rational::from(0), Rational::from(1)]
);
let evs = m.real_eigenvalues();
assert_eq!(evs.len(), 2);
let sqrt2 = Algebraic::from_int(Int::from_i64(2)).sqrt();
assert_eq!(evs[0], sqrt2.clone().neg()); assert_eq!(evs[1], sqrt2);
for lambda in &evs {
assert_eq!(
lambda.mul(lambda),
Algebraic::from_int(Int::from_i64(2)),
"λ² must equal 2"
);
}
assert_eigen(&m, &evs);
}
#[test]
fn companion_matrix_known_roots() {
let m = mat(&[&[0, 0, 6], &[1, 0, -11], &[0, 1, 6]]);
let cp = m.characteristic_polynomial();
assert_eq!(
cp.coeffs().to_vec(),
vec![
Rational::from(-6),
Rational::from(11),
Rational::from(-6),
Rational::from(1)
]
);
let evs = m.real_eigenvalues();
assert_eq!(evs, alg_ints(&[1, 2, 3]));
assert_eigen(&m, &evs);
}
#[test]
fn repeated_eigenvalue_multiplicity() {
let m = mat(&[&[2, 1], &[0, 2]]);
let evs = m.real_eigenvalues();
assert_eq!(evs, alg_ints(&[2])); assert_eigen(&m, &evs);
let with_mult = m.real_eigenvalues_with_multiplicity();
assert_eq!(with_mult.len(), 1);
assert_eq!(with_mult[0].0, Algebraic::from_int(Int::from_i64(2)));
assert_eq!(with_mult[0].1, 2);
let m = mat(&[&[2, 0, 0], &[0, 2, 0], &[0, 0, 5]]);
let with_mult = m.real_eigenvalues_with_multiplicity();
assert_eq!(with_mult.len(), 2);
assert_eq!(with_mult[0].0, Algebraic::from_int(Int::from_i64(2)));
assert_eq!(with_mult[0].1, 2);
assert_eq!(with_mult[1].0, Algebraic::from_int(Int::from_i64(5)));
assert_eq!(with_mult[1].1, 1);
}
#[test]
fn complex_pair_returns_no_real_eigenvalues() {
let m = mat(&[&[0, -1], &[1, 0]]);
let cp = m.characteristic_polynomial();
assert_eq!(
cp.coeffs().to_vec(),
vec![Rational::from(1), Rational::from(0), Rational::from(1)]
);
assert!(m.real_eigenvalues().is_empty());
assert!(m.real_eigenvalues_with_multiplicity().is_empty());
}
#[test]
fn mixed_real_and_complex_spectrum() {
let m = mat(&[&[7, 0, 0], &[0, 0, -1], &[0, 1, 0]]);
let evs = m.real_eigenvalues();
assert_eq!(evs, alg_ints(&[7]));
assert_eigen(&m, &evs);
let with_mult = m.real_eigenvalues_with_multiplicity();
assert_eq!(with_mult.len(), 1);
assert_eq!(with_mult[0].1, 1);
}