use rug::Rational;
use super::super::risch::alg_field::{AlgElem, AlgExtension, RatFn, RationalFunctionField};
use super::super::risch::number_field::{mod_inverse, 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};
use super::integral_basis::{radical_integral_basis, squarefree_factors};
pub fn hermite_reduce_radical(
n: usize,
a: &QPoly,
integrand: &AlgElem,
) -> Option<(AlgElem, AlgElem)> {
if n < 2 {
return None;
}
let f = RationalFunctionField;
let basis = radical_integral_basis(n, a)?;
let ext = AlgExtension::radical(n, a);
let d: Vec<QPoly> = (0..n)
.map(|i| {
basis[i]
.get(i)
.map(|c| c.denom().clone())
.unwrap_or_else(|| vec![Rational::from(1)])
})
.collect();
let a_prime = poly_deriv(a);
let mut g_w = vec![RatFn::int(0); n];
let mut h_w = vec![RatFn::int(0); n];
for i in 0..n {
let fi = integrand.get(i).cloned().unwrap_or_else(|| RatFn::int(0));
if fi.numer().is_empty() {
continue;
}
let coord = f.mul(&fi, &RatFn::from_poly(&d[i])); let omega = omega_i(i, n, a, &a_prime, &d[i]);
let (gi, hi) = twisted_hermite(&coord, &omega)?;
g_w[i] = gi;
h_w[i] = hi;
}
let to_power = |w: &[RatFn]| -> AlgElem {
w.iter()
.enumerate()
.map(|(i, gi)| f.mul(gi, &RatFn::new(vec![Rational::from(1)], d[i].clone())))
.collect()
};
let g = to_power(&g_w);
let h = to_power(&h_w);
for hi in &h {
let den = hi.denom();
if degree(&poly_gcd(den, &poly_deriv(den))) > 0 {
return None;
}
}
let lhs = ext.add(&ext.derivation(&g), &h);
if !ext.elem_eq(&lhs, integrand) {
return None;
}
Some((g, h))
}
fn omega_i(i: usize, n: usize, a: &QPoly, a_prime: &QPoly, di: &QPoly) -> RatFn {
let f = RationalFunctionField;
let scale = RatFn::new(
vec![Rational::from(i as i64)],
vec![Rational::from(n as i64)],
);
let log_a = RatFn::new(a_prime.clone(), a.clone()); let term1 = f.mul(&scale, &log_a);
if degree(di) < 1 {
return term1; }
let log_d = RatFn::new(poly_deriv(di), di.clone()); f.add(&term1, &f.neg(&log_d))
}
fn twisted_hermite(c: &RatFn, omega: &RatFn) -> Option<(RatFn, RatFn)> {
let f = RationalFunctionField;
let mut cur = c.clone();
let mut g = RatFn::int(0);
let cap = 4 * (degree(c.denom()).max(0) as usize) + 8;
for _ in 0..cap {
let den = cur.denom().clone();
let sqf = squarefree_factors(&den);
let Some((v, m)) = sqf
.iter()
.enumerate()
.rev()
.find(|(k, p)| *k + 1 >= 2 && degree(p) >= 1)
.map(|(k, p)| (p.clone(), k + 1))
else {
break; };
let vm = poly_pow(&v, m as u32);
let u = poly_div_exact(&den, &vm);
let num = cur.numer().clone();
let au = poly_mod(&poly_mul(&num, &mod_inverse(&u, &v)?), &v);
let v_rf = RatFn::from_poly(&v);
let vp_rf = RatFn::from_poly(&poly_scale(
&poly_deriv(&v),
&Rational::from((m - 1) as i64),
));
let k_rf = f.add(&f.mul(omega, &v_rf), &f.neg(&vp_rf));
let k_mod = reduce_mod_v(&k_rf, &v)?;
let k_inv = mod_inverse(&k_mod, &v)?;
let b = poly_mod(&poly_mul(&au, &k_inv), &v);
if trim(b.clone()).is_empty() {
break; }
let term = RatFn::new(b.clone(), poly_pow(&v, (m - 1) as u32));
g = f.add(&g, &term);
let l_term = f.add(&f.derivation(&term), &f.mul(omega, &term));
let next = f.add(&cur, &f.neg(&l_term));
if degree(next.denom()) >= degree(cur.denom()) && next != RatFn::int(0) {
cur = next;
break;
}
cur = next;
}
Some((g, cur))
}
fn reduce_mod_v(r: &RatFn, v: &QPoly) -> Option<QPoly> {
let inv = mod_inverse(r.denom(), v)?;
Some(poly_mod(&poly_mul(r.numer(), &inv), v))
}
fn poly_mod(a: &QPoly, m: &QPoly) -> QPoly {
let (_, rem) = poly_divrem(a, m);
rem
}
fn poly_divrem(a: &QPoly, b: &QPoly) -> (QPoly, QPoly) {
let b = trim(b.clone());
let bd = degree(&b);
let mut r = trim(a.clone());
if bd < 0 {
return (Vec::new(), r);
}
let lc = b[bd as usize].clone();
let mut q = vec![Rational::from(0); (degree(&r) - bd + 1).max(0) as usize];
while degree(&r) >= bd && !r.is_empty() {
let rd = degree(&r);
let shift = (rd - bd) as usize;
let factor = r[rd as usize].clone() / &lc;
if (shift as i64) < q.len() as i64 {
q[shift] = factor.clone();
}
for (i, bc) in b.iter().enumerate() {
r[shift + i] -= factor.clone() * bc;
}
r = trim(r);
}
(trim(q), r)
}
fn poly_pow(p: &QPoly, e: u32) -> QPoly {
let mut acc = vec![Rational::from(1)];
for _ in 0..e {
acc = poly_mul(&acc, p);
}
acc
}
fn poly_scale(p: &QPoly, s: &Rational) -> QPoly {
p.iter().map(|c| c.clone() * s).collect()
}
#[cfg(test)]
mod tests {
use super::*;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
fn rf(num: &[i64], den: &[i64]) -> RatFn {
RatFn::new(qp(num), qp(den))
}
#[test]
fn sqrt_double_pole_fully_reduces() {
let integrand = vec![RatFn::int(0), rf(&[1], &[0, 0, 0, 1])];
let (g, h) = hermite_reduce_radical(2, &qp(&[0, 1]), &integrand).expect("reduce");
assert!(h.iter().all(|c| c.numer().is_empty()));
assert_eq!(g[1], RatFn::new(qp(&[-2]), qp(&[0, 0, 3])));
}
#[test]
fn sqrt_simple_pole_untouched() {
let integrand = vec![RatFn::int(0), rf(&[1], &[0, -1, 1])];
let (g, h) = hermite_reduce_radical(2, &qp(&[0, 1]), &integrand).expect("reduce");
assert!(g.iter().all(|c| c.numer().is_empty())); assert_eq!(h[1], rf(&[1], &[0, -1, 1])); }
#[test]
fn sqrt_mixed_reduction() {
let integrand = vec![RatFn::int(0), rf(&[1], &[0, 0, -1, 1])];
let (g, h) = hermite_reduce_radical(2, &qp(&[0, 1]), &integrand).expect("reduce");
assert!(!g.iter().all(|c| c.numer().is_empty()));
for hi in &h {
let den = hi.denom();
assert!(degree(&poly_gcd(den, &poly_deriv(den))) <= 0);
}
}
#[test]
fn cbrt_reduction() {
let integrand = vec![RatFn::int(0), RatFn::int(0), rf(&[1], &[0, 0, 0, 0, 1])];
let (g, h) = hermite_reduce_radical(3, &qp(&[0, 1]), &integrand).expect("reduce");
assert!(h.iter().all(|c| c.numer().is_empty()));
assert_eq!(g[2], RatFn::new(qp(&[-3]), qp(&[0, 0, 0, 7])));
}
}