use rug::{Integer, Rational};
use std::collections::BTreeSet;
use super::super::risch::alg_field::AlgElem;
use super::super::risch::poly_rde::{degree, trim, QPoly};
use super::vanhoeij::{branch_ts, elem_ts, ts_add, ts_inv, ts_mul, ts_pow, TS};
use crate::poly::puiseux::{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,
}
pub fn finite_residues(n: usize, a: &QPoly, h: &AlgElem) -> Vec<Residue> {
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;
for (sheet, br) in puiseux_at(&monos, &alpha, prec).iter().enumerate() {
let e = br.ramification as i64;
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 {
out.push(Residue {
point: alpha.clone(),
at_infinity: false,
sheet,
ramification: br.ramification,
value,
});
}
}
}
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> {
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();
for (sheet, w_branch) in puiseux_at_infinity(n, a, prec).iter().enumerate() {
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(Residue {
point: Rational::from(0),
at_infinity: true,
sheet,
ramification: w_branch.ramification,
value,
});
}
}
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 fn residue_sum(divisor: &[Residue]) -> Rational {
divisor.iter().fold(Rational::from(0), |s, r| s + &r.value)
}
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 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));
}
}