use symplex::factor_zassenhaus::modpoly::{Fp64, PolyIn};
use symplex::prelude::*;
fn strings(es: &[Ex]) -> Vec<String> {
es.iter().map(|e| format!("{e}")).collect()
}
fn assert_root_set(roots: &[Ex], expected: &[Ex], label: &str) {
let got = strings(roots);
assert!(
got.iter().all(|s| !s.contains("RootOf")),
"{label}: a RootOf placeholder survived: {got:?}"
);
assert_eq!(
roots.len(),
expected.len(),
"{label}: expected {} distinct roots, got {got:?}",
expected.len()
);
for e in expected {
assert!(
roots.iter().any(|r| r.equals(e) == Some(true)),
"{label}: root {e} missing from {got:?}"
);
}
for (i, r) in roots.iter().enumerate() {
for other in &roots[i + 1..] {
assert_ne!(
r.equals(other),
Some(true),
"{label}: duplicate root {r} in {got:?}"
);
}
}
}
fn poly_p(ctx: &Context, x: &Ex) -> Ex {
let f = (&(x * 3) - 7) * (&(x * 3) + 7) * (&(x * 2) - 5).powi(2) * (x - 9).powi(3);
let _ = ctx;
f.expand()
}
fn roots_p(ctx: &Context) -> Vec<Ex> {
vec![
ctx.rational(7, 3),
ctx.rational(-7, 3),
ctx.rational(5, 2),
ctx.int(9),
]
}
fn poly_q(x: &Ex) -> Ex {
((&(x * 2) + 3) * (&(x * 4) - 9).powi(2) * (x + 2).powi(3) * (&x.powi(2) - 2).powi(2)).expand()
}
fn roots_q(ctx: &Context) -> Vec<Ex> {
let sqrt2 = ctx.int(2).sqrt();
vec![
ctx.rational(-3, 2),
ctx.rational(9, 4),
ctx.int(-2),
sqrt2.clone(),
-&sqrt2,
]
}
#[test]
fn solve_four_times_p_gives_the_rational_roots() -> Result<(), SymplexError> {
let ctx = Context::new();
let x = ctx.symbol("x");
let p = poly_p(&ctx, &x);
assert_root_set(&p.solve(&x)?, &roots_p(&ctx), "p");
let four_p = (&p * 4).expand();
assert_root_set(&four_p.solve(&x)?, &roots_p(&ctx), "4Β·p");
Ok(())
}
#[test]
fn solve_minus_384_times_q_gives_the_five_distinct_roots() -> Result<(), SymplexError> {
let ctx = Context::new();
let x = ctx.symbol("x");
let q = poly_q(&x);
assert_root_set(&q.solve(&x)?, &roots_q(&ctx), "q");
let scaled = (&q * -384).expand();
assert_root_set(&scaled.solve(&x)?, &roots_q(&ctx), "β384Β·q");
Ok(())
}
#[test]
fn solve_degree_13_with_repeated_factors_and_a_complex_pair() -> Result<(), SymplexError> {
let ctx = Context::new();
let x = ctx.symbol("x");
let r = ((&x + 4).powi(2)
* (&x - 3).powi(3)
* (&x - 1).powi(3)
* (&(&x * 4) + 1).powi(3)
* (&x.powi(2) + 1)
* -10368)
.expand();
let i = ctx.i_unit();
let expected = vec![
ctx.int(-4),
ctx.int(3),
ctx.int(1),
ctx.rational(-1, 4),
i.clone(),
-&i,
];
assert_root_set(
&r.solve(&x)?,
&expected,
"β10368Β·(x+4)Β²(xβ3)Β³(xβ1)Β³(4x+1)Β³(xΒ²+1)",
);
Ok(())
}
#[test]
fn solve_root_set_is_invariant_under_a_constant_factor() -> Result<(), SymplexError> {
let ctx = Context::new();
let x = ctx.symbol("x");
let p = poly_p(&ctx, &x);
for c in [1i64, 4, -384, 1_000_000_000] {
let scaled = (&p * c).expand();
assert_root_set(&scaled.solve(&x)?, &roots_p(&ctx), &format!("{c}Β·p"));
}
let q = poly_q(&x);
for c in [1i64, 4, -384, 1_000_000_000] {
let scaled = (&q * c).expand();
assert_root_set(&scaled.solve(&x)?, &roots_q(&ctx), &format!("{c}Β·q"));
}
Ok(())
}
#[test]
fn solve_irreducible_quintic_still_yields_five_rootofs() -> Result<(), SymplexError> {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = &x.powi(5) - &x - 1;
let roots = f.solve(&x)?;
let got = strings(&roots);
assert_eq!(roots.len(), 5, "{got:?}");
for k in 0..5 {
let expected = format!("RootOf(x^5 - x - 1, {k})");
assert!(got.contains(&expected), "{expected} missing from {got:?}");
}
Ok(())
}
#[test]
fn solve_quintic_times_linear_places_rootofs_over_the_quintic_only() -> Result<(), SymplexError> {
let ctx = Context::new();
let x = ctx.symbol("x");
let f = ((&x.powi(5) - &x - 1) * (&x - 2)).expand();
let roots = f.solve(&x)?;
let got = strings(&roots);
assert_eq!(roots.len(), 6, "{got:?}");
assert!(
roots.iter().any(|r| r.equals(&ctx.int(2)) == Some(true)),
"2 missing from {got:?}"
);
for k in 0..5 {
let expected = format!("RootOf(x^5 - x - 1, {k})");
assert!(got.contains(&expected), "{expected} missing from {got:?}");
}
let g = (&f * (&x - 2) * 6).expand();
let roots = g.solve(&x)?;
let got = strings(&roots);
assert_eq!(roots.len(), 6, "{got:?}");
assert!(
got.iter().filter(|s| s.contains("RootOf")).count() == 5,
"{got:?}"
);
assert!(
got.iter()
.all(|s| !s.contains("RootOf") || s.contains("x^5 - x - 1")),
"a RootOf over something other than the quintic: {got:?}"
);
Ok(())
}
#[test]
fn solve_returns_distinct_roots() -> Result<(), SymplexError> {
let ctx = Context::new();
let x = ctx.symbol("x");
let roots = (&x - 1).powi(2).expand().solve(&x)?;
assert_root_set(&roots, &[ctx.int(1)], "(x β 1)Β²");
let f = ((&x - 1).powi(2) * (&x.powi(2) + &x * 2 + 1) * 7).expand();
assert_root_set(
&f.solve(&x)?,
&[ctx.int(1), ctx.int(-1)],
"7(x β 1)Β²(x + 1)Β²",
);
Ok(())
}
#[test]
fn fp2_roots_of_x_squared_plus_x() {
let f = PolyIn::from_coeffs(Fp64::new(2), vec![0, 1, 1]);
assert_eq!(f.roots(), vec![0, 1]);
}
#[test]
fn fp2_roots_of_irreducible_cubic_are_none() {
let f = PolyIn::from_coeffs(Fp64::new(2), vec![1, 1, 0, 1]);
assert!(f.roots().is_empty());
}
#[test]
fn fp2_roots_of_x_squared_plus_one_is_the_double_root_once() {
let f = PolyIn::from_coeffs(Fp64::new(2), vec![1, 0, 1]);
assert_eq!(f.roots(), vec![1]);
}
#[test]
fn fp2_roots_more_cases_and_odd_prime_unchanged() {
let two = Fp64::new(2);
assert_eq!(
PolyIn::from_coeffs(two, vec![0, 1, 0, 0, 1]).roots(),
vec![0, 1]
);
assert_eq!(PolyIn::from_coeffs(two, vec![0, 1]).roots(), vec![0]);
assert_eq!(PolyIn::from_coeffs(two, vec![1, 1]).roots(), vec![1]);
assert!(PolyIn::from_coeffs(two, vec![1]).roots().is_empty());
assert!(PolyIn::zero(two).roots().is_empty());
assert_eq!(
PolyIn::from_coeffs(Fp64::new(3), vec![0, 1, 1]).roots(),
vec![0, 2]
);
}