use num_bigint::BigInt;
use num_rational::Ratio;
use num_traits::{One, Zero};
use crate::poly::dense::Poly;
#[derive(Clone, Debug)]
pub struct HermiteResult {
pub g_numer: Poly,
pub g_denom: Poly,
pub h_numer: Poly,
pub h_denom: Poly,
}
pub fn hermite_reduce(a: &Poly, d: &Poly) -> HermiteResult {
assert!(!d.is_zero(), "hermite_reduce: denominator must be nonzero");
let d_lc = d.coeff(d.coeffs().len() - 1);
let d_monic = d.make_monic();
let inv_lc = Ratio::one() / &d_lc;
let a_scaled = a.scale(&inv_lc);
let (poly_part, a_proper) = a_scaled.div_rem(&d_monic);
hermite_reduce_proper(&a_proper, &d_monic, &poly_part)
}
fn hermite_reduce_proper(a: &Poly, d: &Poly, poly_part: &Poly) -> HermiteResult {
let d_prime = d.derivative();
let mut d_minus = Poly::gcd(d, &d_prime);
let d_star = d.div(&d_minus);
if d_minus.degree().unwrap_or(0) == 0 {
return HermiteResult {
g_numer: integrate_poly(poly_part),
g_denom: Poly::from_int(1),
h_numer: a.clone(),
h_denom: d.clone(),
};
}
let mut g_numer = Poly::zero();
let mut g_denom = Poly::from_int(1);
let mut a_curr = a.clone();
while d_minus.degree().unwrap_or(0) > 0 {
let d_minus_prime = d_minus.derivative();
let d_minus_2 = Poly::gcd(&d_minus, &d_minus_prime);
let d_minus_star = d_minus.div(&d_minus_2);
let neg_dstar_dminus_prime = -&(&d_star * &d_minus_prime);
let lhs_coeff = neg_dstar_dminus_prime.div(&d_minus);
let (s, t, gcd_val) = Poly::extended_gcd(&lhs_coeff, &d_minus_star);
let (scale, rem) = a_curr.div_rem(&gcd_val);
if !rem.is_zero() {
break;
}
let b_full = &s * &scale;
let _c_full = &t * &scale;
let (_, b) = b_full.div_rem(&d_minus_star);
let b_times_lhs = &b * &lhs_coeff;
let numerator_for_c = &a_curr - &b_times_lhs;
let c = numerator_for_c.div(&d_minus_star);
g_numer = &(&g_numer * &d_minus) + &(&b * &g_denom);
g_denom = &g_denom * &d_minus;
let g_gcd = Poly::gcd(&g_numer, &g_denom);
if g_gcd.degree().unwrap_or(0) > 0 || !g_gcd.is_constant() {
g_numer = g_numer.div(&g_gcd);
g_denom = g_denom.div(&g_gcd);
}
let b_prime = b.derivative();
let dstar_over_dmstar = d_star.div(&d_minus_star);
a_curr = &c - &(&b_prime * &dstar_over_dmstar);
d_minus = d_minus_2;
}
if !g_denom.is_zero()
&& let Some(lc) = g_denom.leading_coeff()
&& !lc.is_one()
{
let inv = Ratio::one() / lc;
g_numer = g_numer.scale(&inv);
g_denom = g_denom.scale(&inv);
}
if !poly_part.is_zero() {
let poly_integral = integrate_poly(poly_part);
g_numer = &g_numer + &(&poly_integral * &g_denom);
}
HermiteResult {
g_numer,
g_denom,
h_numer: a_curr,
h_denom: d_star,
}
}
fn integrate_poly(p: &Poly) -> Poly {
if p.is_zero() {
return Poly::zero();
}
let coeffs = p.coeffs();
let mut result = vec![Ratio::zero()]; for (k, c) in coeffs.iter().enumerate() {
let k_plus_1 = Ratio::from_integer(BigInt::from((k + 1) as i64));
result.push(c / &k_plus_1);
}
Poly::from_coeffs(result)
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64, d: i64) -> Ratio<BigInt> {
Ratio::new(BigInt::from(n), BigInt::from(d))
}
fn verify_hermite(a: &Poly, d: &Poly, result: &HermiteResult) {
let gn_prime = result.g_numer.derivative();
let gd_prime = result.g_denom.derivative();
let dg_numer = &(&gn_prime * &result.g_denom) - &(&result.g_numer * &gd_prime);
let dg_denom = &result.g_denom * &result.g_denom;
let sum_numer = &(&dg_numer * &result.h_denom) + &(&result.h_numer * &dg_denom);
let sum_denom = &dg_denom * &result.h_denom;
let lhs = &sum_numer * d;
let rhs = &(a * &sum_denom);
let diff = &lhs - rhs;
assert!(
diff.is_zero(),
"FTC verification failed:\n d/dx(g) + h ≠ A/D\n diff = {diff}"
);
}
#[test]
fn hermite_reduce_already_squarefree() {
let a = Poly::from_int(1);
let d = Poly::from_coeffs(vec![rat(-1, 1), rat(0, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
assert_eq!(result.h_numer.degree(), Some(0));
assert!(result.h_denom.degree().unwrap_or(0) >= 2);
verify_hermite(&a, &d, &result);
}
#[test]
fn hermite_reduce_one_over_x_squared() {
let a = Poly::from_int(1);
let d = Poly::from_coeffs(vec![rat(0, 1), rat(0, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
assert!(
result.h_numer.is_zero(),
"∫ 1/x^2: log part should be zero, got h = {}/{}",
result.h_numer,
result.h_denom
);
verify_hermite(&a, &d, &result);
}
#[test]
fn hermite_reduce_one_over_x_plus_1_squared() {
let a = Poly::from_int(1);
let d = Poly::from_coeffs(vec![rat(1, 1), rat(2, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
assert!(
result.h_numer.is_zero(),
"∫ 1/(x+1)^2: log part should be zero, got h = {}/{}",
result.h_numer,
result.h_denom
);
verify_hermite(&a, &d, &result);
}
#[test]
fn hermite_reduce_one_over_x_cubed() {
let a = Poly::from_int(1);
let d = Poly::from_coeffs(vec![rat(0, 1), rat(0, 1), rat(0, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
assert!(result.h_numer.is_zero(), "∫ 1/x^3: log part should be zero");
verify_hermite(&a, &d, &result);
}
#[test]
fn hermite_reduce_repeated_quadratic() {
let a = Poly::from_int(1);
let d = Poly::from_coeffs(vec![rat(1, 1), rat(0, 1), rat(2, 1), rat(0, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
let h_deg = result.h_denom.degree().unwrap_or(0);
assert!(
h_deg <= 2,
"∫ 1/(x^2+1)^2: remainder denom should be degree ≤ 2 (square-free), got {h_deg}"
);
verify_hermite(&a, &d, &result);
}
#[test]
fn hermite_reduce_2x_plus_3_over_x_plus_1_cubed() {
let a = Poly::from_coeffs(vec![rat(3, 1), rat(2, 1)]); let d = Poly::from_coeffs(vec![rat(1, 1), rat(3, 1), rat(3, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
verify_hermite(&a, &d, &result);
}
#[test]
fn hermite_reduce_improper_fraction() {
let a = Poly::from_coeffs(vec![rat(0, 1), rat(0, 1), rat(0, 1), rat(1, 1)]); let d = Poly::from_coeffs(vec![rat(1, 1), rat(2, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
verify_hermite(&a, &d, &result);
}
#[test]
fn hermite_reduce_with_mixed_roots() {
let a = Poly::from_int(1);
let d = Poly::from_coeffs(vec![rat(0, 1), rat(0, 1), rat(-1, 1), rat(1, 1)]);
let result = hermite_reduce(&a, &d);
let h_deg = result.h_denom.degree().unwrap_or(0);
assert!(
h_deg <= 2,
"remainder denom degree should be ≤ 2, got {h_deg}"
);
verify_hermite(&a, &d, &result);
}
#[test]
fn integrate_poly_basic() {
let p = Poly::from_coeffs(vec![rat(1, 1), rat(2, 1), rat(3, 1)]);
let integral = integrate_poly(&p);
assert_eq!(integral.degree(), Some(3));
assert_eq!(integral.coeff(0), rat(0, 1));
assert_eq!(integral.coeff(1), rat(1, 1));
assert_eq!(integral.coeff(2), rat(1, 1));
assert_eq!(integral.coeff(3), rat(1, 1));
}
#[test]
fn integrate_poly_constant() {
let p = Poly::from_int(5);
let integral = integrate_poly(&p);
assert_eq!(integral.degree(), Some(1));
assert_eq!(integral.coeff(1), rat(5, 1));
}
#[test]
fn integrate_poly_zero() {
let p = Poly::zero();
let integral = integrate_poly(&p);
assert!(integral.is_zero());
}
#[test]
fn hermite_h_denom_is_squarefree() {
let test_cases: Vec<(Poly, Poly)> = vec![
(
Poly::from_int(1),
Poly::from_coeffs(vec![rat(1, 1), rat(2, 1), rat(1, 1)]),
),
(
Poly::from_int(1),
Poly::from_coeffs(vec![rat(0, 1), rat(0, 1), rat(0, 1), rat(1, 1)]),
),
(
Poly::from_coeffs(vec![rat(2, 1), rat(1, 1)]),
Poly::from_coeffs(vec![rat(0, 1), rat(0, 1), rat(1, 1), rat(-2, 1), rat(1, 1)]),
),
];
for (a, d) in &test_cases {
let result = hermite_reduce(a, d);
if !result.h_numer.is_zero() {
let h_d_prime = result.h_denom.derivative();
let g = Poly::gcd(&result.h_denom, &h_d_prime);
assert!(
g.degree().unwrap_or(0) == 0,
"h_denom should be square-free for A={a}, D={d}, got gcd(h_denom, h_denom') = {g}"
);
}
}
}
}