use num_bigint::BigInt;
use num_rational::Ratio;
use crate::poly::generic::GenPoly;
use crate::poly::ratfn::RationalFn;
use crate::poly::traits::{Field, Ring};
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct TowerHermiteResult {
pub g_numer: GenPoly<RationalFn>,
pub g_denom: GenPoly<RationalFn>,
pub h_numer: GenPoly<RationalFn>,
pub h_denom: GenPoly<RationalFn>,
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub enum TowerLogTerm {
Constant {
coeff: Ratio<BigInt>,
argument: GenPoly<RationalFn>,
},
NonConstant {
coeff: RationalFn,
argument: GenPoly<RationalFn>,
},
}
#[derive(Clone, Debug)]
#[allow(dead_code)]
pub struct TowerLogPartResult {
pub terms: Vec<TowerLogTerm>,
pub is_non_elementary: bool,
}
pub fn tower_hermite_reduce(
a: &GenPoly<RationalFn>,
d: &GenPoly<RationalFn>,
) -> TowerHermiteResult {
assert!(
!d.is_zero(),
"tower_hermite_reduce: denominator must be nonzero"
);
let d_monic = d.make_monic();
let lc = d.leading_coeff().unwrap();
let inv_lc = Field::inv(lc);
let a_scaled = a.scale(&inv_lc);
let (poly_part, a_proper) = a_scaled.div_rem(&d_monic);
let d_prime = d_monic.derivative();
let mut d_minus = GenPoly::gcd(&d_monic, &d_prime);
let d_star = d_monic.div(&d_minus);
if d_minus.degree().unwrap_or(0) == 0 {
let poly_integral = integrate_tower_poly(&poly_part);
return TowerHermiteResult {
g_numer: poly_integral,
g_denom: GenPoly::one(),
h_numer: a_proper,
h_denom: d_monic,
};
}
let mut g_numer: GenPoly<RationalFn> = GenPoly::zero();
let mut g_denom: GenPoly<RationalFn> = GenPoly::one();
let mut a_curr = a_proper;
while d_minus.degree().unwrap_or(0) > 0 {
let d_minus_prime = d_minus.derivative();
let d_minus_2 = GenPoly::gcd(&d_minus, &d_minus_prime);
let d_minus_star = d_minus.div(&d_minus_2);
let neg_dstar_dminus_prime = d_star.mul(&d_minus_prime).neg();
let lhs_coeff = neg_dstar_dminus_prime.div(&d_minus);
let (s, t, gcd_val) = GenPoly::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.mul(&scale);
let _c_full = t.mul(&scale);
let (_, b) = b_full.div_rem(&d_minus_star);
let b_times_lhs = b.mul(&lhs_coeff);
let numerator_for_c = a_curr.sub(&b_times_lhs);
let c = numerator_for_c.div(&d_minus_star);
g_numer = g_numer.mul(&d_minus).add(&b.mul(&g_denom));
g_denom = g_denom.mul(&d_minus);
let g_gcd = GenPoly::gcd(&g_numer, &g_denom);
if g_gcd.degree().unwrap_or(0) > 0 {
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.sub(&b_prime.mul(&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 = Field::inv(lc);
g_numer = g_numer.scale(&inv);
g_denom = g_denom.scale(&inv);
}
if !poly_part.is_zero() {
let poly_integral = integrate_tower_poly(&poly_part);
g_numer = g_numer.add(&poly_integral.mul(&g_denom));
}
TowerHermiteResult {
g_numer,
g_denom,
h_numer: a_curr,
h_denom: d_star,
}
}
fn integrate_tower_poly(p: &GenPoly<RationalFn>) -> GenPoly<RationalFn> {
if p.is_zero() {
return GenPoly::zero();
}
let coeffs = p.coeffs();
let mut result = vec![RationalFn::from_int(0)]; for (k, c) in coeffs.iter().enumerate() {
let k_plus_1 = RationalFn::from_int((k + 1) as i64);
let scaled = Field::div(c, &k_plus_1);
result.push(scaled);
}
GenPoly::from_coeffs(result)
}
pub fn tower_logarithmic_part(
a: &GenPoly<RationalFn>,
d: &GenPoly<RationalFn>,
) -> TowerLogPartResult {
assert!(
!d.is_zero(),
"tower_logarithmic_part: denominator must be nonzero"
);
if a.is_zero() {
return TowerLogPartResult {
terms: vec![],
is_non_elementary: false,
};
}
let d_prime = d.derivative();
if d.degree() == Some(1) {
let a_val = a.coeff(0);
let d_lc = d.leading_coeff().unwrap().clone();
let coeff = Field::div(&a_val, &d_lc);
if Ring::is_zero(&coeff) {
return TowerLogPartResult {
terms: vec![],
is_non_elementary: false,
};
}
if coeff.is_constant_rational() {
return TowerLogPartResult {
terms: vec![TowerLogTerm::Constant {
coeff: coeff.to_rational().unwrap(),
argument: d.make_monic(),
}],
is_non_elementary: false,
};
} else {
return TowerLogPartResult {
terms: vec![TowerLogTerm::NonConstant {
coeff,
argument: d.make_monic(),
}],
is_non_elementary: true,
};
}
}
let r_poly = GenPoly::<RationalFn>::resultant_poly(d, a, &d_prime);
if r_poly.is_zero() {
return TowerLogPartResult {
terms: vec![],
is_non_elementary: false,
};
}
let mut terms = Vec::new();
let mut is_non_elementary = false;
if let Some(deg) = r_poly.degree() {
if deg == 1 {
let a0 = r_poly.coeff(0);
let a1 = r_poly.coeff(1);
let root = Field::div(&Ring::neg(&a0), &a1);
if Ring::is_zero(&root) {
} else if root.is_constant_rational() {
let c = root.to_rational().unwrap();
let c_rf = RationalFn::from_rational(c.clone());
let a_minus_c_dprime = a.sub(&d_prime.scale(&c_rf));
let v = GenPoly::gcd(d, &a_minus_c_dprime);
if v.degree().unwrap_or(0) > 0 {
terms.push(TowerLogTerm::Constant {
coeff: c,
argument: v.make_monic(),
});
}
} else {
is_non_elementary = true;
terms.push(TowerLogTerm::NonConstant {
coeff: root,
argument: d.clone(),
});
}
} else {
let max_denom = 12i64;
let max_numer = 10i64;
let mut found_roots: Vec<Ratio<BigInt>> = Vec::new();
for denom in 1..=max_denom {
for numer in (-max_numer * denom)..=(max_numer * denom) {
if numer == 0 {
continue; }
let c = Ratio::new(BigInt::from(numer), BigInt::from(denom));
if found_roots.contains(&c) {
continue;
}
let c_rf = RationalFn::from_rational(c.clone());
let r_at_c = r_poly.eval(&c_rf);
if Ring::is_zero(&r_at_c) {
found_roots.push(c.clone());
let a_minus_c_dprime = a.sub(&d_prime.scale(&c_rf));
let v = GenPoly::gcd(d, &a_minus_c_dprime);
if v.degree().unwrap_or(0) > 0 {
terms.push(TowerLogTerm::Constant {
coeff: c,
argument: v.make_monic(),
});
}
}
}
}
let found_degree: usize = terms
.iter()
.filter_map(|t| match t {
TowerLogTerm::Constant { argument, .. } => argument.degree(),
TowerLogTerm::NonConstant { argument, .. } => argument.degree(),
})
.sum();
if found_degree < d.degree().unwrap_or(0) {
}
}
}
TowerLogPartResult {
terms,
is_non_elementary,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::poly::dense::Poly;
fn r(n: i64, d: i64) -> Ratio<BigInt> {
Ratio::new(BigInt::from(n), BigInt::from(d))
}
fn rf_int(n: i64) -> RationalFn {
RationalFn::from_int(n)
}
fn rf(numer: &[i64], denom: &[i64]) -> RationalFn {
let n = Poly::from_coeffs(numer.iter().map(|&c| r(c, 1)).collect());
let d = Poly::from_coeffs(denom.iter().map(|&c| r(c, 1)).collect());
RationalFn::new(n, d)
}
fn rp(cs: &[RationalFn]) -> GenPoly<RationalFn> {
GenPoly::from_coeffs(cs.to_vec())
}
#[test]
fn hermite_already_squarefree() {
let a = rp(&[rf_int(1)]); let d = rp(&[rf_int(-1), rf_int(0), rf_int(1)]);
let result = tower_hermite_reduce(&a, &d);
assert!(
result.h_denom.degree().unwrap_or(0) >= 2,
"h_denom should have degree ≥ 2"
);
}
#[test]
fn hermite_repeated_factor() {
let a = rp(&[rf_int(1)]); let d = rp(&[rf_int(0), rf_int(0), rf_int(1)]);
let result = tower_hermite_reduce(&a, &d);
assert!(
result.h_numer.is_zero(),
"1/θ² should have no log remainder, got h = {}/{}",
result.h_numer,
result.h_denom
);
}
#[test]
fn hermite_with_ratfn_coefficients() {
let inv_x = rf(&[1], &[0, 1]); let a = rp(&[inv_x]); let d = rp(&[rf_int(0), rf_int(0), rf_int(1)]);
let result = tower_hermite_reduce(&a, &d);
assert!(
result.h_numer.is_zero(),
"(1/x)/θ² should have no log remainder"
);
}
#[test]
fn hermite_ftc_verification() {
let a = rp(&[rf_int(1)]); let d = rp(&[rf_int(1), rf_int(2), rf_int(1)]);
let result = tower_hermite_reduce(&a, &d);
let gn_prime = result.g_numer.derivative();
let gd_prime = result.g_denom.derivative();
let dg_numer = gn_prime
.mul(&result.g_denom)
.sub(&result.g_numer.mul(&gd_prime));
let dg_denom = result.g_denom.mul(&result.g_denom);
let sum_numer = dg_numer
.mul(&result.h_denom)
.add(&result.h_numer.mul(&dg_denom));
let sum_denom = dg_denom.mul(&result.h_denom);
let lhs = sum_numer.mul(&d);
let rhs = a.mul(&sum_denom);
let diff = lhs.sub(&rhs);
assert!(diff.is_zero(), "FTC verification failed: d/dθ(g) + h ≠ A/D");
}
#[test]
fn rt_one_over_theta() {
let a = rp(&[rf_int(1)]); let d = rp(&[rf_int(0), rf_int(1)]);
let result = tower_logarithmic_part(&a, &d);
assert!(!result.is_non_elementary, "1/θ should be elementary");
assert_eq!(result.terms.len(), 1, "should have 1 log term");
match &result.terms[0] {
TowerLogTerm::Constant { coeff, .. } => {
assert_eq!(*coeff, r(1, 1), "coefficient should be 1");
}
_ => panic!("expected Constant log term"),
}
}
#[test]
fn rt_inv_x_over_theta() {
let inv_x = rf(&[1], &[0, 1]); let a = rp(&[inv_x]); let d = rp(&[rf_int(0), rf_int(1)]);
let result = tower_logarithmic_part(&a, &d);
assert!(
result.is_non_elementary,
"1/(x·θ) should flag as non-elementary at the tower level"
);
}
#[test]
fn rt_one_over_theta_squared_minus_1() {
let a = rp(&[rf_int(1)]); let d = rp(&[rf_int(-1), rf_int(0), rf_int(1)]);
let result = tower_logarithmic_part(&a, &d);
assert!(!result.is_non_elementary, "1/(θ²-1) should be elementary");
let constant_count = result
.terms
.iter()
.filter(|t| matches!(t, TowerLogTerm::Constant { .. }))
.count();
assert!(
constant_count >= 1,
"should have at least 1 constant log term, got {constant_count}"
);
}
#[test]
fn rt_zero_numerator() {
let a = GenPoly::<RationalFn>::zero();
let d = rp(&[rf_int(-1), rf_int(0), rf_int(1)]); let result = tower_logarithmic_part(&a, &d);
assert!(result.terms.is_empty());
assert!(!result.is_non_elementary);
}
#[test]
fn integrate_tower_poly_basic() {
let p = rp(&[rf_int(1), rf_int(2), rf_int(3)]);
let integral = integrate_tower_poly(&p);
assert_eq!(integral.degree(), Some(3));
assert!(Ring::is_zero(&integral.coeff(0))); assert_eq!(integral.coeff(1), rf_int(1)); assert_eq!(integral.coeff(2), rf_int(1)); assert_eq!(integral.coeff(3), rf_int(1)); }
#[test]
fn integrate_tower_poly_with_ratfn_coeff() {
let inv_x = rf(&[1], &[0, 1]); let p = rp(&[<RationalFn as Ring>::zero(), inv_x]);
let integral = integrate_tower_poly(&p);
assert_eq!(integral.degree(), Some(2));
let c2 = integral.coeff(2);
assert!(!c2.is_constant_rational(), "1/(2x) is not constant");
}
#[test]
fn integrate_tower_poly_zero() {
let p = GenPoly::<RationalFn>::zero();
let integral = integrate_tower_poly(&p);
assert!(integral.is_zero());
}
#[test]
fn combined_hermite_and_rt() {
let d = rp(&[rf_int(1), rf_int(-1), rf_int(-1), rf_int(1)]);
let a = rp(&[rf_int(1)]);
let hr = tower_hermite_reduce(&a, &d);
let h_deg = hr.h_denom.degree().unwrap_or(0);
assert!(
h_deg <= 2,
"after Hermite, h_denom degree should be ≤ 2, got {h_deg}"
);
if !hr.h_numer.is_zero() {
let rt = tower_logarithmic_part(&hr.h_numer, &hr.h_denom);
assert!(
!rt.is_non_elementary,
"1/((θ-1)²(θ+1)) should be elementary"
);
}
}
}