use rug::Rational;
use super::super::risch::alg_field::{AlgElem, AlgExtension, RatFn, RationalFunctionField};
use super::super::risch::number_field::CoeffField;
use super::super::risch::poly_rde::{degree, poly_deriv, poly_mul, trim, QPoly};
use super::super::risch::rational_rde::{poly_div_exact, poly_gcd};
fn trace(e: &AlgExtension, b: &AlgElem) -> RatFn {
let f = RationalFunctionField;
let n = e.degree() as usize;
let mut acc = f.zero();
let gen = e.generator();
let mut yj = e.from_int(1); for j in 0..n {
let prod = e.mul(b, &yj);
if let Some(c) = prod.get(j) {
acc = f.add(&acc, c);
}
if j + 1 < n {
yj = e.mul(&yj, &gen);
}
}
acc
}
pub fn discriminant(f_coeffs: &[QPoly]) -> QPoly {
let e = AlgExtension::new(f_coeffs);
let n = e.degree() as usize;
let gen = e.generator();
let ntr = 2 * n.saturating_sub(1) + 1;
let mut tr = vec![RatFn::int(0); ntr];
let mut ym = e.from_int(1);
for (m, slot) in tr.iter_mut().enumerate() {
*slot = trace(&e, &ym);
if m + 1 < ntr {
ym = e.mul(&ym, &gen);
}
}
let mat: Vec<Vec<RatFn>> = (0..n)
.map(|i| (0..n).map(|j| tr[i + j].clone()).collect())
.collect();
let det = ratfn_det(mat);
debug_assert!(is_polynomial(&det));
trim(det.numer().clone())
}
pub fn rational_singularities(disc: &QPoly) -> Vec<Rational> {
let disc = trim(disc.clone());
if degree(&disc) < 2 {
return Vec::new();
}
let g = poly_gcd(&disc, &poly_deriv(&disc));
rational_roots_monic(&g)
}
pub fn is_integral(f_coeffs: &[QPoly], a: &AlgElem) -> bool {
let e = AlgExtension::new(f_coeffs);
let f = RationalFunctionField;
let n = e.degree() as usize;
let mut p = vec![f.zero(); n + 1];
let mut ak = e.from_int(1);
for pk in p.iter_mut().take(n + 1).skip(1) {
ak = e.mul(&ak, a);
*pk = trace(&e, &ak);
}
let mut ek = vec![f.zero(); n + 1];
ek[0] = f.one();
for k in 1..=n {
let mut acc = f.zero();
for i in 1..=k {
let term = f.mul(&ek[k - i], &p[i]);
if i % 2 == 1 {
acc = f.add(&acc, &term);
} else {
acc = f.add(&acc, &f.neg(&term));
}
}
let inv_k = f
.inv(&RatFn::int(k as i64))
.expect("k≠0 is invertible in ℚ(x)");
ek[k] = f.mul(&acc, &inv_k);
}
ek.iter().skip(1).all(is_polynomial)
}
pub fn radical_integral_basis(n: usize, p: &QPoly) -> Option<Vec<AlgElem>> {
if n < 2 {
return None;
}
let mut f_coeffs = vec![QPoly::new(); n + 1];
f_coeffs[0] = trim(poly_scale(p, &Rational::from(-1)));
f_coeffs[n] = vec![Rational::from(1)];
let sqfree = squarefree_factors(p);
let mut basis = Vec::with_capacity(n);
for i in 0..n {
let mut di = vec![Rational::from(1)];
for (j, pj) in sqfree.iter().enumerate() {
let mult = (i * (j + 1)) / n; for _ in 0..mult {
di = poly_mul(&di, pj);
}
}
let mut w = vec![RatFn::int(0); i + 1];
w[i] = RatFn::new(vec![Rational::from(1)], di);
if !is_integral(&f_coeffs, &w) {
return None; }
basis.push(w);
}
Some(basis)
}
fn is_polynomial(r: &RatFn) -> bool {
let d = r.denom();
r.numer().is_empty() || (d.len() == 1 && d[0] == 1)
}
fn poly_scale(p: &QPoly, s: &Rational) -> QPoly {
p.iter().map(|c| c.clone() * s).collect()
}
fn ratfn_det(mut m: Vec<Vec<RatFn>>) -> RatFn {
let f = RationalFunctionField;
let n = m.len();
let mut det = f.one();
for col in 0..n {
let Some(piv) = (col..n).find(|&r| !is_zero(&m[r][col])) else {
return f.zero();
};
if piv != col {
m.swap(piv, col);
det = f.neg(&det);
}
det = f.mul(&det, &m[col][col]);
let inv = f.inv(&m[col][col]).expect("nonzero pivot invertible");
for r in (col + 1)..n {
if is_zero(&m[r][col]) {
continue;
}
let factor = f.mul(&m[r][col], &inv);
let pivot_row = m[col].clone();
for (c, pv) in pivot_row.iter().enumerate().skip(col) {
let sub = f.mul(&factor, pv);
m[r][c] = f.add(&m[r][c], &f.neg(&sub));
}
}
}
det
}
fn is_zero(r: &RatFn) -> bool {
r.numer().is_empty()
}
pub(super) fn squarefree_factors(p: &QPoly) -> Vec<QPoly> {
let p = trim(p.clone());
if degree(&p) < 1 {
return Vec::new();
}
let mut out = Vec::new();
let dp = poly_deriv(&p);
let mut a = poly_gcd(&p, &dp); let mut b = poly_div_exact(&p, &a); loop {
let c = poly_gcd(&a, &b); let factor = poly_div_exact(&b, &c); out.push(trim(factor));
if degree(&a) < 1 {
break;
}
a = poly_div_exact(&a, &c);
b = c;
if degree(&b) < 1 {
break;
}
}
out
}
fn rational_roots_monic(p: &QPoly) -> Vec<Rational> {
use rug::Integer;
let p = trim(p.clone());
if degree(&p) < 1 {
return Vec::new();
}
let mut den_lcm = Integer::from(1);
for c in &p {
den_lcm = den_lcm.lcm(c.denom());
}
let ints: Vec<Integer> = p
.iter()
.map(|c| {
(c.clone() * Rational::from(den_lcm.clone()))
.numer()
.clone()
})
.collect();
let a0 = ints[0].clone().abs();
let an = ints[ints.len() - 1].clone().abs();
let mut roots = Vec::new();
if ints[0] == 0 {
roots.push(Rational::from(0));
}
let pdiv = divisors(&a0);
let qdiv = divisors(&an);
for pn in &pdiv {
for qn in &qdiv {
for sign in [1i32, -1] {
let cand = Rational::from((Integer::from(sign) * pn.clone(), qn.clone()));
if roots.contains(&cand) {
continue;
}
let mut acc = Rational::from(0);
for a in ints.iter().rev() {
acc = acc * &cand + Rational::from(a.clone());
}
if acc == 0 {
roots.push(cand);
}
}
}
}
roots
}
fn divisors(n: &rug::Integer) -> Vec<rug::Integer> {
use rug::Integer;
let n = n.clone().abs();
if n == 0 {
return vec![Integer::from(1)];
}
let mut ds = Vec::new();
let mut d = Integer::from(1);
while Integer::from(&d * &d) <= n {
if n.is_divisible(&d) {
ds.push(d.clone());
let o = n.clone() / &d;
if o != d {
ds.push(o);
}
}
d += 1;
}
ds
}
#[cfg(test)]
mod tests {
use super::*;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
fn r(n: i64) -> Rational {
Rational::from(n)
}
#[test]
fn discriminant_sqrt() {
let d = discriminant(&[qp(&[0, -1]), qp(&[]), qp(&[1])]);
assert_eq!(degree(&d), 1);
assert!(
rational_singularities(&d).is_empty(),
"x=0 is a simple root"
);
}
#[test]
fn discriminant_cusp_singular() {
let d = discriminant(&[qp(&[0, 0, 0, -1]), qp(&[]), qp(&[1])]);
assert_eq!(degree(&d), 3);
let s = rational_singularities(&d);
assert_eq!(s, vec![r(0)]);
}
#[test]
fn is_integral_examples() {
let f = [qp(&[0, 0, 0, -1]), qp(&[]), qp(&[1])];
assert!(is_integral(&f, &vec![RatFn::int(0), RatFn::int(1)]));
let y_over_x = vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[0, 1]))];
assert!(is_integral(&f, &y_over_x));
let y_over_x2 = vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[0, 0, 1]))];
assert!(!is_integral(&f, &y_over_x2));
}
#[test]
fn radical_basis_sqrt_x_is_power_basis() {
let basis = radical_integral_basis(2, &qp(&[0, 1])).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(basis[0], vec![RatFn::int(1)]);
assert_eq!(basis[1], vec![RatFn::int(0), RatFn::int(1)]);
}
#[test]
fn radical_basis_y2_eq_x3() {
let basis = radical_integral_basis(2, &qp(&[0, 0, 0, 1])).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(
basis[1],
vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[0, 1]))]
);
}
#[test]
fn radical_basis_cbrt_x_squared() {
let basis = radical_integral_basis(3, &qp(&[0, 0, 1])).expect("basis");
assert_eq!(basis.len(), 3);
assert_eq!(
basis[2],
vec![
RatFn::int(0),
RatFn::int(0),
RatFn::new(qp(&[1]), qp(&[0, 1]))
]
);
}
}