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::{discriminant, radical_integral_basis, squarefree_factors};
use super::vanhoeij::integral_basis;
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))
}
pub fn hermite_reduce_general(
f_coeffs: &[QPoly],
integrand: &AlgElem,
) -> Option<(AlgElem, AlgElem)> {
let ext = AlgExtension::new(f_coeffs);
let n = ext.degree() as usize;
if n < 2 {
return None;
}
let basis = integral_basis(f_coeffs)?;
let disc = discriminant(f_coeffs);
let mut cur = pad(integrand, n);
let mut g = ext.from_int(0);
let denom_total = |e: &AlgElem| -> i64 {
to_w_coords(&basis, e, n)
.map(|cs| cs.iter().map(|c| degree(c.denom()).max(0)).sum())
.unwrap_or(0)
};
let cap = 4 * (denom_total(&cur) as usize) + 8;
for _ in 0..cap {
let coords = to_w_coords(&basis, &cur, n)?;
let (d_poly, a_polys) = common_denominator(&coords);
let sqf = squarefree_factors(&d_poly);
let normal = sqf
.iter()
.enumerate()
.rev()
.find(|(k, p)| *k + 1 >= 2 && degree(p) >= 1 && degree(&poly_gcd(p, &disc)) <= 0)
.map(|(k, p)| (p.clone(), k + 1));
let term = if let Some((v, m)) = normal {
let vm = poly_pow(&v, m as u32);
let u = poly_div_exact(&d_poly, &vm);
let s = poly_scale(
&poly_mul(&u, &poly_deriv(&v)),
&Rational::from((m - 1) as i64),
);
let s_inv = mod_inverse(&s, &v)?;
let mut b_w = vec![RatFn::int(0); n];
for (i, ai) in a_polys.iter().enumerate() {
let bi = poly_mod(&poly_mul(ai, &s_inv), &v);
b_w[i] = RatFn::from_poly(&poly_scale(&bi, &Rational::from(-1)));
}
let b_power = w_to_power(&basis, &b_w, &ext, n);
let inv_vm1 = RatFn::new(vec![Rational::from(1)], poly_pow(&v, (m - 1) as u32));
Some(scale_elem(&b_power, &inv_vm1))
} else if let Some((v, m)) = sqf
.iter()
.enumerate()
.rev()
.find(|(k, p)| *k + 1 >= 2 && degree(p) >= 1)
.map(|(k, p)| (p.clone(), k + 1))
{
let vm = poly_pow(&v, m as u32);
let u = poly_div_exact(&d_poly, &vm);
lazy_solve_b(&ext, &basis, &a_polys, &u, &v, m, n).map(|b_power| {
let inv_vm1 = RatFn::new(vec![Rational::from(1)], poly_pow(&v, (m - 1) as u32));
scale_elem(&b_power, &inv_vm1)
})
} else {
None
};
let Some(term) = term else {
break; };
if ext.elem_eq(&term, &ext.from_int(0)) {
break; }
let next = ext.sub(&cur, &ext.derivation(&term));
g = ext.add(&g, &term);
if denom_total(&next) >= denom_total(&cur) {
cur = next;
break;
}
cur = next;
}
let h = cur;
let hcoords = to_w_coords(&basis, &h, n)?;
for c in &hcoords {
let den = c.denom();
let g_sq = poly_gcd(den, &poly_deriv(den));
if degree(&poly_gcd(&g_sq, &disc)) < degree(&g_sq) {
return None;
}
}
let lhs = ext.add(&ext.derivation(&g), &h);
if !ext.elem_eq(&lhs, integrand) {
return None;
}
Some((g, h))
}
fn pad(e: &AlgElem, n: usize) -> AlgElem {
let mut v = e.clone();
while v.len() < n {
v.push(RatFn::int(0));
}
v
}
fn to_w_coords(basis: &[AlgElem], elem: &AlgElem, n: usize) -> Option<Vec<RatFn>> {
let f = RationalFunctionField;
let comp = |e: &AlgElem, r: usize| e.get(r).cloned().unwrap_or_else(|| RatFn::int(0));
let mut m: Vec<Vec<RatFn>> = (0..n)
.map(|r| {
let mut row: Vec<RatFn> = (0..n).map(|i| comp(&basis[i], r)).collect();
row.push(comp(elem, r));
row
})
.collect();
for col in 0..n {
let sel = (col..n).find(|&r| !f.eq(&m[r][col], &f.zero()))?;
m.swap(col, sel);
let inv = f.inv(&m[col][col])?;
for v in m[col].iter_mut() {
*v = f.mul(v, &inv);
}
for r in 0..n {
if r != col && !f.eq(&m[r][col], &f.zero()) {
let factor = m[r][col].clone();
#[allow(clippy::needless_range_loop)]
for c in 0..=n {
let sub = f.mul(&factor, &m[col][c].clone());
m[r][c] = f.sub(&m[r][c], &sub);
}
}
}
}
Some((0..n).map(|i| m[i][n].clone()).collect())
}
fn w_to_power(basis: &[AlgElem], coords: &[RatFn], ext: &AlgExtension, n: usize) -> AlgElem {
let mut acc = ext.from_int(0);
for i in 0..n {
acc = ext.add(&acc, &scale_elem(&basis[i], &coords[i]));
}
acc
}
fn scale_elem(elem: &AlgElem, s: &RatFn) -> AlgElem {
let f = RationalFunctionField;
elem.iter().map(|c| f.mul(s, c)).collect()
}
fn common_denominator(coords: &[RatFn]) -> (QPoly, Vec<QPoly>) {
let mut d = vec![Rational::from(1)];
for c in coords {
d = poly_lcm(&d, c.denom());
}
let a = coords
.iter()
.map(|c| poly_mul(c.numer(), &poly_div_exact(&d, c.denom())))
.collect();
(d, a)
}
fn poly_lcm(a: &QPoly, b: &QPoly) -> QPoly {
if degree(a) < 0 || degree(b) < 0 {
return vec![Rational::from(1)];
}
poly_div_exact(&poly_mul(a, b), &poly_gcd(a, b))
}
fn lazy_solve_b(
ext: &AlgExtension,
basis: &[AlgElem],
a_polys: &[QPoly],
u: &QPoly,
v: &QPoly,
m: usize,
n: usize,
) -> Option<AlgElem> {
let dv = degree(v) as usize;
if dv < 1 {
return None;
}
let nunk = n * dv;
let mv1 = poly_scale(&poly_deriv(v), &Rational::from((m - 1) as i64)); let u_rf = RatFn::from_poly(u);
let v_rf = RatFn::from_poly(v);
let mv1_rf = RatFn::from_poly(&mv1);
let mut cols: Vec<Vec<Rational>> = Vec::with_capacity(nunk);
for i in 0..n {
for p in 0..dv {
let mut b_w = vec![RatFn::int(0); n];
b_w[i] = RatFn::from_poly(&monomial_q(p));
let b_power = w_to_power(basis, &b_w, ext, n);
let db = ext.derivation(&b_power);
let v_db = scale_elem(&db, &v_rf); let mv1_b = scale_elem(&b_power, &mv1_rf); let inner = ext.sub(&mv1_b, &v_db);
let t_elem = scale_elem(&inner, &u_rf); let tcoords = to_w_coords(basis, &t_elem, n)?;
let mut col = Vec::with_capacity(nunk);
for c in &tcoords {
let r = ratfn_mod_v(c, v)?; for k in 0..dv {
col.push(r.get(k).cloned().unwrap_or_else(|| Rational::from(0)));
}
}
cols.push(col);
}
}
let mut rhs = Vec::with_capacity(nunk);
for j in 0..n {
let aj = a_polys.get(j).cloned().unwrap_or_default();
let r = poly_mod(&poly_scale(&aj, &Rational::from(-1)), v);
for k in 0..dv {
rhs.push(r.get(k).cloned().unwrap_or_else(|| Rational::from(0)));
}
}
let mut mat = vec![vec![Rational::from(0); nunk]; nunk];
for (unk, col) in cols.iter().enumerate() {
for (eq, val) in col.iter().enumerate() {
mat[eq][unk] = val.clone();
}
}
let sol = gauss_solve_q(mat, rhs, nunk)?;
let mut b_w = vec![RatFn::int(0); n];
for (i, slot) in b_w.iter_mut().enumerate() {
let poly: QPoly = (0..dv).map(|p| sol[i * dv + p].clone()).collect();
*slot = RatFn::from_poly(&trim(poly));
}
Some(w_to_power(basis, &b_w, ext, n))
}
fn ratfn_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 monomial_q(p: usize) -> QPoly {
let mut v = vec![Rational::from(0); p + 1];
v[p] = Rational::from(1);
v
}
fn gauss_solve_q(
mut mat: Vec<Vec<Rational>>,
mut rhs: Vec<Rational>,
n: usize,
) -> Option<Vec<Rational>> {
let nrows = mat.len();
let mut pivot_of_col = vec![None; n];
let mut row = 0usize;
for col in 0..n {
if row >= nrows {
break;
}
let Some(sel) = (row..nrows).find(|&r| mat[r][col] != 0) else {
continue;
};
mat.swap(row, sel);
rhs.swap(row, sel);
let piv = mat[row][col].clone();
for v in mat[row].iter_mut() {
*v /= ϖ
}
rhs[row] /= ϖ
let pr = mat[row].clone();
let pb = rhs[row].clone();
for r in 0..nrows {
if r != row && mat[r][col] != 0 {
let f = mat[r][col].clone();
for (dst, pv) in mat[r].iter_mut().zip(pr.iter()) {
*dst -= f.clone() * pv;
}
rhs[r] -= f * &pb;
}
}
pivot_of_col[col] = Some(row);
row += 1;
}
for r in 0..nrows {
if mat[r].iter().all(|v| *v == 0) && rhs[r] != 0 {
return None; }
}
let mut x = vec![Rational::from(0); n];
for (col, pr) in pivot_of_col.iter().enumerate() {
if let Some(r) = pr {
x[col] = rhs[*r].clone();
}
}
Some(x)
}
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 general_curve_exact_derivative_reduces_to_zero() {
let f_coeffs = [qp(&[0, 0, 0, -1]), qp(&[1]), qp(&[1])];
let ext = AlgExtension::new(&f_coeffs);
let g0 = vec![RatFn::int(0), rf(&[1], &[-1, 1])]; let f = ext.derivation(&g0);
let (g, h) = hermite_reduce_general(&f_coeffs, &f).expect("reduce");
assert!(h.iter().all(|c| c.numer().is_empty()), "h = {h:?}");
assert!(ext.elem_eq(&ext.derivation(&g), &f));
}
#[test]
fn general_curve_double_pole_reduces() {
let f_coeffs = [qp(&[0, 0, 0, -1]), qp(&[1]), qp(&[1])]; let ext = AlgExtension::new(&f_coeffs);
let f = vec![RatFn::int(0), rf(&[1], &[1, -2, 1])];
let (g, h) = hermite_reduce_general(&f_coeffs, &f).expect("reduce");
assert!(!g.iter().all(|c| c.numer().is_empty()));
assert!(ext.elem_eq(&ext.add(&ext.derivation(&g), &h), &f));
for hi in &h {
let den = hi.denom();
assert!(degree(&poly_gcd(den, &poly_deriv(den))) <= 0);
}
}
#[test]
fn general_off_branch_pole_cubic_radical() {
let f_coeffs = [qp(&[0, -1]), qp(&[]), qp(&[]), qp(&[1])]; let ext = AlgExtension::new(&f_coeffs);
let f = vec![RatFn::int(0), rf(&[1], &[1, -2, 1])]; let (g, h) = hermite_reduce_general(&f_coeffs, &f).expect("reduce");
assert!(!g.iter().all(|c| c.numer().is_empty()));
assert!(ext.elem_eq(&ext.add(&ext.derivation(&g), &h), &f));
for hi in &h {
let den = hi.denom();
assert!(degree(&poly_gcd(den, &poly_deriv(den))) <= 0);
}
}
#[test]
fn general_branch_locus_pole_reduces() {
let f_coeffs = [qp(&[0, -1]), qp(&[]), qp(&[1])]; let integrand = vec![RatFn::int(0), rf(&[1], &[0, 0, 0, 1])]; let (g, h) = hermite_reduce_general(&f_coeffs, &integrand).expect("reduce");
assert!(h.iter().all(|c| c.numer().is_empty()), "h = {h:?}");
assert_eq!(g[1], RatFn::new(qp(&[-2]), qp(&[0, 0, 3]))); }
#[test]
fn general_branch_locus_cubic_radical() {
let f_coeffs = [qp(&[0, -1]), qp(&[]), qp(&[]), qp(&[1])]; let integrand = vec![RatFn::int(0), RatFn::int(0), rf(&[1], &[0, 0, 0, 0, 1])]; let (g, h) = hermite_reduce_general(&f_coeffs, &integrand).expect("reduce");
assert!(h.iter().all(|c| c.numer().is_empty()), "h = {h:?}");
assert_eq!(g[2], RatFn::new(qp(&[-3]), qp(&[0, 0, 0, 7]))); }
#[test]
fn general_branch_simple_pole_untouched() {
let f_coeffs = [qp(&[0, -1]), qp(&[]), qp(&[1])]; let ext = AlgExtension::new(&f_coeffs);
let f = vec![RatFn::int(0), rf(&[1], &[0, -1, 1])]; let (g, h) = hermite_reduce_general(&f_coeffs, &f).expect("reduce");
assert!(ext.elem_eq(&ext.add(&ext.derivation(&g), &h), &f));
}
#[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])));
}
}