use rug::{Integer, Rational};
use std::collections::{BTreeSet, HashMap};
use super::super::risch::alg_field::{AlgElem, RatFn};
use super::super::risch::number_field::{KElem, NumberField};
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::vanhoeij::{branch_ts, elem_ts, ts_add, ts_inv, ts_mul, ts_pow, TS};
use crate::poly::puiseux::{factor_over_q, puiseux_at, puiseux_at_zero, PuiseuxSeries};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Residue {
pub point: Rational,
pub at_infinity: bool,
pub sheet: usize,
pub ramification: u64,
pub value: Rational,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct PlacedResidue {
pub(crate) residue: Residue,
pub(crate) y_coord: Rational,
}
pub fn finite_residues(n: usize, a: &QPoly, h: &AlgElem) -> Vec<Residue> {
finite_residues_placed(n, a, h)
.into_iter()
.map(|p| p.residue)
.collect()
}
pub(crate) fn finite_residues_placed(n: usize, a: &QPoly, h: &AlgElem) -> Vec<PlacedResidue> {
if n < 2 {
return Vec::new();
}
let monos = curve_monomials(n, a);
let mut cands: BTreeSet<Rational> = BTreeSet::new();
for r in rational_roots(a) {
cands.insert(r);
}
for c in h {
for r in rational_roots(c.denom()) {
cands.insert(r);
}
}
let mut out = Vec::new();
for alpha in cands {
let prec = (h
.iter()
.map(|c| degree(c.denom()).max(0))
.max()
.unwrap_or(0)
+ n as i64
+ 3) as u32;
let mut seen_per_ram: HashMap<u64, usize> = HashMap::new();
for (sheet, br) in puiseux_at(&monos, &alpha, prec).iter().enumerate() {
let e = br.ramification as i64;
let idx = seen_per_ram.entry(br.ramification).or_insert(0);
let keep = *idx % (br.ramification.max(1) as usize) == 0;
*idx += 1;
if !keep {
continue;
}
let u = 2 * e + 4;
let series = elem_ts(h, &alpha, e, u, &branch_ts(br));
let coeff = series
.get(&(-e))
.cloned()
.unwrap_or_else(|| Rational::from(0));
let value = Rational::from(e) * coeff;
if value != 0 {
let y_coord = br
.terms
.iter()
.find(|(ex, _)| *ex == 0)
.map(|(_, c)| c.clone())
.unwrap_or_else(|| Rational::from(0));
out.push(PlacedResidue {
residue: Residue {
point: alpha.clone(),
at_infinity: false,
sheet,
ramification: br.ramification,
value,
},
y_coord,
});
}
}
}
out
}
pub fn puiseux_at_infinity(n: usize, a: &QPoly, prec: u32) -> Vec<PuiseuxSeries> {
let a = trim(a.clone());
let m = degree(&a);
if m < 0 {
return Vec::new();
}
let m = m as usize;
let mut monos: Vec<(u32, u32, Rational)> = Vec::new();
for (i, ai) in a.iter().enumerate() {
if *ai != 0 {
monos.push(((m - i) as u32, n as u32, ai.clone())); }
}
monos.push((m as u32, 0, Rational::from(-1))); puiseux_at_zero(&monos, prec)
}
pub fn residues_at_infinity(n: usize, a: &QPoly, h: &AlgElem) -> Vec<Residue> {
residues_at_infinity_placed(n, a, h)
.into_iter()
.map(|p| p.residue)
.collect()
}
pub(crate) fn residues_at_infinity_placed(n: usize, a: &QPoly, h: &AlgElem) -> Vec<PlacedResidue> {
let m = degree(&trim(a.clone())).max(0) as usize;
let dmax = h
.iter()
.map(|c| degree(c.numer()).max(degree(c.denom())).max(0))
.max()
.unwrap_or(0);
let prec = (dmax + (n as i64) + (m as i64) + 4) as u32;
let mut out = Vec::new();
let mut seen_per_ram: HashMap<u64, usize> = HashMap::new();
for (sheet, w_branch) in puiseux_at_infinity(n, a, prec).iter().enumerate() {
let idx = seen_per_ram.entry(w_branch.ramification).or_insert(0);
let keep = *idx % (w_branch.ramification.max(1) as usize) == 0;
*idx += 1;
if !keep {
continue;
}
let e = w_branch.ramification as i64;
let u = 2 * e + 2 * (m as i64) * e + 8;
let w_ts = branch_ts(w_branch);
let Some(y_ts) = ts_inv(&w_ts, u) else {
continue;
};
let h_ts = elem_at_infinity(h, e, u, &y_ts);
let coeff = h_ts.get(&e).cloned().unwrap_or_else(|| Rational::from(0));
let value = -Rational::from(e) * coeff;
if value != 0 {
out.push(PlacedResidue {
residue: Residue {
point: Rational::from(0),
at_infinity: true,
sheet,
ramification: w_branch.ramification,
value,
},
y_coord: Rational::from(0),
});
}
}
out
}
fn elem_at_infinity(h: &AlgElem, e: i64, u: i64, y_ts: &TS) -> TS {
let mut acc = TS::new();
for (j, coeff) in h.iter().enumerate() {
if coeff.numer().is_empty() {
continue;
}
let cj = ratfn_at_infinity(coeff, e, u);
let yj = ts_pow(y_ts, j as u32, u);
acc = ts_add(&acc, &ts_mul(&cj, &yj, u));
}
acc
}
fn ratfn_at_infinity(r: &crate::integrate::risch::alg_field::RatFn, e: i64, u: i64) -> TS {
let num = poly_at_infinity(r.numer(), e, u);
let den = poly_at_infinity(r.denom(), e, u);
match ts_inv(&den, u) {
Some(inv) => ts_mul(&num, &inv, u),
None => TS::new(),
}
}
fn poly_at_infinity(p: &QPoly, e: i64, u: i64) -> TS {
let mut ts = TS::new();
for (i, pi) in p.iter().enumerate() {
if *pi != 0 {
let exp = -e * i as i64;
if exp < u {
*ts.entry(exp).or_insert_with(|| Rational::from(0)) += pi;
}
}
}
ts.retain(|_, c| *c != 0);
ts
}
pub fn residue_divisor(n: usize, a: &QPoly, h: &AlgElem) -> Vec<Residue> {
let mut d = finite_residues(n, a, h);
d.extend(residues_at_infinity(n, a, h));
d
}
pub(crate) fn residue_divisor_placed(n: usize, a: &QPoly, h: &AlgElem) -> Vec<PlacedResidue> {
let mut d = finite_residues_placed(n, a, h);
d.extend(residues_at_infinity_placed(n, a, h));
d
}
pub fn residue_sum(divisor: &[Residue]) -> Rational {
divisor.iter().fold(Rational::from(0), |s, r| s + &r.value)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AlgResidue {
pub minpoly: QPoly,
pub conjugates: usize,
pub r0: KElem,
pub r1: KElem,
}
pub fn finite_residues_algebraic(n: usize, a: &QPoly, h: &AlgElem) -> Vec<AlgResidue> {
if n != 2 {
return Vec::new();
}
let a = trim(a.clone());
let a_c = h.first().cloned().unwrap_or_else(|| RatFn::int(0)); let b_c = h.get(1).cloned().unwrap_or_else(|| RatFn::int(0)); let a_den = if a_c.numer().is_empty() {
vec![Rational::from(1)]
} else {
a_c.denom().clone()
};
let b_den = if b_c.numer().is_empty() {
vec![Rational::from(1)]
} else {
b_c.denom().clone()
};
let d = poly_lcm(&a_den, &b_den);
if degree(&d) < 1 {
return Vec::new();
}
if degree(&poly_gcd(&d, &poly_deriv(&d))) > 0 {
return Vec::new();
}
let a_num = poly_mul(a_c.numer(), &poly_div_exact(&d, &a_den));
let b_num = poly_mul(b_c.numer(), &poly_div_exact(&d, &b_den));
let d_prime = poly_deriv(&d);
let mut out = Vec::new();
for (q, deg_q) in factor_over_q(&d) {
if degree(&poly_gcd(&q, &a)) > 0 {
continue; }
if deg_q == 1 {
let alpha = -q.first().cloned().unwrap_or_else(|| Rational::from(0)); let a_at = eval_q(&a, &alpha);
if is_rational_square(&a_at) {
continue; }
}
let nf = NumberField::new(q.clone());
let dp_alpha = nf.reduce(&d_prime);
let Some(dp_inv) = nf.inv(&dp_alpha) else {
continue; };
let r0 = nf.mul(&nf.reduce(&a_num), &dp_inv);
let r1 = nf.mul(&nf.reduce(&b_num), &dp_inv);
out.push(AlgResidue {
minpoly: q,
conjugates: deg_q,
r0,
r1,
});
}
out
}
pub fn residue_sum_complete(n: usize, a: &QPoly, h: &AlgElem) -> Rational {
let mut total = residue_sum(&finite_residues(n, a, h));
total += residue_sum(&residues_at_infinity(n, a, h));
for r in finite_residues_algebraic(n, a, h) {
let nf = NumberField::new(r.minpoly.clone());
total += Rational::from(2) * nf.trace(&r.r0);
}
total
}
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 eval_q(p: &QPoly, x: &Rational) -> Rational {
p.iter().rev().fold(Rational::from(0), |acc, c| acc * x + c)
}
fn is_rational_square(r: &Rational) -> bool {
if *r < 0 {
return false;
}
let n = r.numer().clone();
let d = r.denom().clone();
let ns = n.clone().sqrt();
let ds = d.clone().sqrt();
Integer::from(&ns * &ns) == n && Integer::from(&ds * &ds) == d
}
fn curve_monomials(n: usize, a: &QPoly) -> Vec<(u32, u32, Rational)> {
let mut m = vec![(0u32, n as u32, Rational::from(1))]; for (i, c) in a.iter().enumerate() {
if *c != 0 {
m.push((i as u32, 0, -c.clone())); }
}
m
}
fn rational_roots(p: &QPoly) -> Vec<Rational> {
let p = trim(p.clone());
if degree(&p) < 1 {
return Vec::new();
}
let lo = p.iter().position(|c| *c != 0).unwrap_or(0);
let mut roots = Vec::new();
if lo > 0 {
roots.push(Rational::from(0));
}
let psi = &p[lo..];
if psi.len() <= 1 {
return roots;
}
let mut den_lcm = Integer::from(1);
for c in psi {
den_lcm = den_lcm.lcm(c.denom());
}
let ints: Vec<Integer> = psi
.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();
for pn in divisors(&a0) {
for qn in &divisors(&an) {
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 c in ints.iter().rev() {
acc = acc * &cand + Rational::from(c.clone());
}
if acc == 0 {
roots.push(cand);
}
}
}
}
roots
}
fn divisors(n: &Integer) -> Vec<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::super::super::risch::alg_field::RatFn;
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))
}
fn r(n: i64) -> Rational {
Rational::from(n)
}
#[test]
fn algebraic_residue_values() {
let h = vec![rf(&[0, 1], &[-2, 0, 1]), rf(&[1], &[-2, 0, 1])];
let res = finite_residues_algebraic(2, &qp(&[0, 1]), &h);
assert_eq!(res.len(), 1);
let ar = &res[0];
assert_eq!(ar.minpoly, qp(&[-2, 0, 1])); assert_eq!(ar.conjugates, 2);
assert_eq!(ar.r0, vec![Rational::from((1, 2))]); assert_eq!(ar.r1, vec![r(0), Rational::from((1, 4))]); }
#[test]
fn residue_theorem_with_algebraic_places() {
let h = vec![rf(&[0, 1], &[-2, 0, 1]), rf(&[1], &[-2, 0, 1])];
assert_eq!(residue_sum_complete(2, &qp(&[0, 1]), &h), r(0));
let den = qp(&[3, -3, -1, 1]); let h2 = vec![
RatFn::new(qp(&[1]), den.clone()),
RatFn::new(qp(&[1]), den.clone()),
];
assert_eq!(residue_sum_complete(2, &qp(&[1, 1]), &h2), r(0));
}
#[test]
fn log_differential_residues() {
let h = vec![RatFn::int(0), rf(&[1], &[0, -1, 1])];
let res = finite_residues(2, &qp(&[0, 1]), &h);
let mut at1: Vec<Rational> = res
.iter()
.filter(|r| r.point == r_one())
.map(|r| r.value.clone())
.collect();
at1.sort();
assert_eq!(at1, vec![r(-1), r(1)]);
let total: Rational = res.iter().fold(r(0), |s, x| s + &x.value);
assert_eq!(total, r(0));
}
fn r_one() -> Rational {
Rational::from(1)
}
#[test]
fn infinity_residues() {
let h = vec![RatFn::int(0), rf(&[1], &[1, 0, 1])];
let res = super::residues_at_infinity(2, &qp(&[1, 0, 1]), &h);
let mut vals: Vec<Rational> = res.iter().map(|r| r.value.clone()).collect();
vals.sort();
assert_eq!(vals, vec![r(-1), r(1)]);
assert!(finite_residues(2, &qp(&[1, 0, 1]), &h).is_empty());
}
#[test]
fn residue_theorem_finite_plus_infinity() {
let a = qp(&[0, 1]);
let h = vec![RatFn::int(0), rf(&[1], &[0, -1, 1])]; let mut total = r(0);
for r0 in finite_residues(2, &a, &h) {
total += &r0.value;
}
for r0 in super::residues_at_infinity(2, &a, &h) {
total += &r0.value;
}
assert_eq!(total, r(0));
}
#[test]
fn exact_differential_no_residues() {
let h = vec![RatFn::int(0), rf(&[1], &[0, 1])];
let res = finite_residues(2, &qp(&[0, 1]), &h);
assert!(res.is_empty(), "expected no residues; got {res:?}");
}
#[test]
fn simple_pole_off_branch() {
let h = vec![RatFn::int(0), rf(&[1], &[-2, 1])];
let res = finite_residues(2, &qp(&[0, 1]), &h);
assert!(res.iter().all(|r| r.value != 0));
}
}