use rug::{Integer, Rational};
use std::collections::BTreeMap;
use super::super::risch::alg_field::{AlgElem, AlgExtension, RatFn, RationalFunctionField};
use super::super::risch::number_field::{CoeffField, KElem, NumberField};
use super::super::risch::poly_rde::{degree, poly_deriv, QPoly};
use super::super::risch::rational_rde::poly_gcd;
use super::integral_basis::{discriminant, is_integral, rational_singularities};
use crate::poly::puiseux::{
factor_over_q, puiseux_at, puiseux_at_algebraic_tower, AlgBasePuiseuxSeries, PuiseuxSeries,
};
pub(super) type TS = BTreeMap<i64, Rational>;
pub fn integral_basis(f_coeffs: &[QPoly]) -> Option<Vec<AlgElem>> {
let ext = AlgExtension::new(f_coeffs);
let n = ext.degree() as usize;
if n < 1 {
return None;
}
if let Some(p) = as_simple_radical(f_coeffs, n) {
if let Some(basis) = super::integral_basis::radical_integral_basis(n, &p) {
return Some(basis);
}
}
let monos = to_monomials(f_coeffs);
let disc = discriminant(f_coeffs);
let sing = rational_singularities(&disc);
let alg_sing = algebraic_singularities(&disc);
let mut b: Vec<AlgElem> = vec![ext.from_int(1)]; for d in 1..n {
let mut bd = ext.mul(&b[d - 1], &ext.generator()); let cap = (degree(&disc).max(0) as usize + 2) * (sing.len() + alg_sing.len()).max(1) + 4;
let mut iters = 0;
'enlarge: loop {
if iters >= cap {
break;
}
for alpha in &sing {
if let Some(cand) = try_enlarge(&ext, &b, &bd, d, alpha, &monos, n) {
if !ext.elem_eq(&cand, &bd) && is_integral(f_coeffs, &cand) {
bd = cand;
iters += 1;
continue 'enlarge;
}
}
}
for q in &alg_sing {
if let Some(cand) = try_enlarge_algebraic(&ext, &b, &bd, d, q, &monos, n) {
if !ext.elem_eq(&cand, &bd) && is_integral(f_coeffs, &cand) {
bd = cand;
iters += 1;
continue 'enlarge;
}
}
}
break;
}
b.push(bd);
}
for bi in &b {
if !is_integral(f_coeffs, bi) {
return None;
}
}
Some(b)
}
fn try_enlarge(
ext: &AlgExtension,
b: &[AlgElem],
bd: &AlgElem,
d: usize,
alpha: &Rational,
monos: &[(u32, u32, Rational)],
n: usize,
) -> Option<AlgElem> {
let dmax = max_denom_degree(b, bd);
let prec = (dmax + n + 3) as u32;
let branches = puiseux_at(monos, alpha, prec);
if branches.is_empty() {
return None;
}
let emax = branches.iter().map(|s| s.ramification).max().unwrap_or(1) as i64;
let u_bound = (prec as i64 + 3) * emax + 4;
let mut matrix: Vec<Vec<Rational>> = Vec::new();
let mut rhs: Vec<Rational> = Vec::new();
for s in &branches {
let e = s.ramification as i64;
let bts = branch_ts(s);
let bi_ts: Vec<TS> = (0..d)
.map(|i| elem_ts(&b[i], alpha, e, u_bound, &bts))
.collect();
let bd_ts = elem_ts(bd, alpha, e, u_bound, &bts);
let low = bi_ts
.iter()
.chain(std::iter::once(&bd_ts))
.filter_map(|s| s.keys().next().copied())
.min()
.unwrap_or(0);
for k in low..e {
let row: Vec<Rational> = bi_ts
.iter()
.map(|s| s.get(&k).cloned().unwrap_or_else(|| Rational::from(0)))
.collect();
let r = -bd_ts.get(&k).cloned().unwrap_or_else(|| Rational::from(0));
matrix.push(row);
rhs.push(r);
}
}
let a = gauss_solve(matrix, rhs, d)?;
let mut num = bd.clone();
for (i, ai) in a.iter().enumerate() {
if *ai != 0 {
num = ext.add(&num, &scale(&b[i], &RatFn::from_poly(&vec![ai.clone()])));
}
}
let inv_lin = RatFn::new(
vec![Rational::from(1)],
vec![-alpha.clone(), Rational::from(1)],
);
Some(scale(&num, &inv_lin))
}
fn algebraic_singularities(disc: &QPoly) -> Vec<QPoly> {
if degree(disc) < 2 {
return Vec::new();
}
let repeated = poly_gcd(disc, &poly_deriv(disc)); if degree(&repeated) < 2 {
return Vec::new();
}
factor_over_q(disc)
.into_iter()
.filter_map(|(q, deg)| {
if deg < 2 {
return None;
}
let g = poly_gcd(&repeated, &q);
if degree(&g) == deg as i64 {
Some(q)
} else {
None
}
})
.collect()
}
fn try_enlarge_algebraic(
ext: &AlgExtension,
b: &[AlgElem],
bd: &AlgElem,
d: usize,
q: &QPoly,
monos: &[(u32, u32, Rational)],
n: usize,
) -> Option<AlgElem> {
let m = degree(q) as usize; if m < 2 {
return None;
}
let nf = NumberField::new(q.clone());
let alpha = nf.reduce(&vec![Rational::from(0), Rational::from(1)]);
let dmax = max_denom_degree(b, bd);
let prec = (dmax + n + 3) as u32;
let (all_branches, _skipped) = puiseux_at_algebraic_tower(monos, q, prec);
let branches: Vec<AlgBasePuiseuxSeries> = all_branches
.into_iter()
.filter(|s| s.coeff_minpoly == *q)
.map(|s| s.branch)
.collect();
if branches.is_empty() {
return None;
}
let emax = branches.iter().map(|s| s.ramification).max().unwrap_or(1) as i64;
let u_bound = (prec as i64 + 3) * emax + 4;
let ncols = d * m;
let mut matrix: Vec<Vec<Rational>> = Vec::new();
let mut rhs: Vec<Rational> = Vec::new();
for s in &branches {
let e = s.ramification as i64;
let bts = branch_kts(&nf, s);
let xseries = x_alpha_kts(&nf, &alpha, e); let mut xpows: Vec<Kts> = Vec::with_capacity(m);
xpows.push(kts_one(&nf));
for l in 1..m {
xpows.push(kts_mul(&nf, &xpows[l - 1], &xseries, u_bound));
}
let bi_ts: Vec<Kts> = (0..d)
.map(|i| elem_kts(&nf, &b[i], &alpha, e, u_bound, &bts))
.collect();
let bd_ts = elem_kts(&nf, bd, &alpha, e, u_bound, &bts);
let mut col_ts: Vec<Kts> = Vec::with_capacity(ncols);
for bi in &bi_ts {
for xl in &xpows {
col_ts.push(kts_mul(&nf, xl, bi, u_bound));
}
}
let low = col_ts
.iter()
.chain(std::iter::once(&bd_ts))
.filter_map(|s| s.keys().next().copied())
.min()
.unwrap_or(0);
for k in low..e {
for comp in 0..m {
let row: Vec<Rational> = col_ts.iter().map(|s| kts_comp(s, k, comp)).collect();
let r = -kts_comp(&bd_ts, k, comp);
matrix.push(row);
rhs.push(r);
}
}
}
let a = gauss_solve(matrix, rhs, ncols)?;
let mut num = bd.clone();
for i in 0..d {
let ai: QPoly = (0..m).map(|l| a[i * m + l].clone()).collect();
if degree(&ai) >= 0 {
num = ext.add(&num, &scale(&b[i], &RatFn::from_poly(&ai)));
}
}
let inv_q = RatFn::new(vec![Rational::from(1)], q.clone());
Some(scale(&num, &inv_q))
}
type Kts = BTreeMap<i64, KElem>;
fn kts_one(nf: &NumberField) -> Kts {
let mut s = Kts::new();
s.insert(0, nf.from_int(1));
s
}
fn x_alpha_kts(nf: &NumberField, alpha: &KElem, e: i64) -> Kts {
let mut s = Kts::new();
if !NumberField::is_zero(alpha) {
s.insert(0, alpha.clone());
}
s.insert(e, nf.from_int(1));
s
}
fn branch_kts(nf: &NumberField, s: &AlgBasePuiseuxSeries) -> Kts {
let e = s.ramification as i64;
let mut ts = Kts::new();
for (exp, c) in &s.terms {
let k = (exp.clone() * Rational::from(e))
.numer()
.to_i64()
.unwrap_or(0);
let slot = ts.entry(k).or_insert_with(NumberField::k_zero);
*slot = nf.add(slot, c);
}
ts.retain(|_, c| !NumberField::is_zero(c));
ts
}
fn elem_kts(nf: &NumberField, b: &AlgElem, alpha: &KElem, e: i64, u: i64, bts: &Kts) -> Kts {
let mut acc = Kts::new();
for (j, coeff) in b.iter().enumerate() {
if coeff.numer().is_empty() {
continue;
}
let cj = ratfn_kts(nf, coeff, alpha, e, u);
let yj = kts_pow(nf, bts, j as u32, u);
acc = kts_add(nf, &acc, &kts_mul(nf, &cj, &yj, u));
}
acc
}
fn ratfn_kts(nf: &NumberField, r: &RatFn, alpha: &KElem, e: i64, u: i64) -> Kts {
let num = poly_kts(nf, r.numer(), alpha, e, u);
let den = poly_kts(nf, r.denom(), alpha, e, u);
match kts_inv(nf, &den, u) {
Some(inv) => kts_mul(nf, &num, &inv, u),
None => Kts::new(),
}
}
fn poly_kts(nf: &NumberField, p: &QPoly, alpha: &KElem, e: i64, u: i64) -> Kts {
let mut ts = Kts::new();
for (mexp, pm) in p.iter().enumerate() {
if *pm == 0 {
continue;
}
let pmk = nf.from_rational(pm);
for l in 0..=mexp {
let exp = e * l as i64;
if exp >= u {
break;
}
let binom = nf.from_rational(&Rational::from(binom(mexp as u32, l as u32)));
let apow = k_pow(nf, alpha, (mexp - l) as u32);
let coeff = nf.mul(&nf.mul(&pmk, &binom), &apow);
if !NumberField::is_zero(&coeff) {
let slot = ts.entry(exp).or_insert_with(NumberField::k_zero);
*slot = nf.add(slot, &coeff);
}
}
}
ts.retain(|_, c| !NumberField::is_zero(c));
ts
}
fn kts_add(nf: &NumberField, a: &Kts, b: &Kts) -> Kts {
let mut r = a.clone();
for (k, c) in b {
let slot = r.entry(*k).or_insert_with(NumberField::k_zero);
*slot = nf.add(slot, c);
}
r.retain(|_, c| !NumberField::is_zero(c));
r
}
fn kts_mul(nf: &NumberField, a: &Kts, b: &Kts, u: i64) -> Kts {
let mut r = Kts::new();
for (ka, ca) in a {
for (kb, cb) in b {
let k = ka + kb;
if k < u {
let slot = r.entry(k).or_insert_with(NumberField::k_zero);
*slot = nf.add(slot, &nf.mul(ca, cb));
}
}
}
r.retain(|_, c| !NumberField::is_zero(c));
r
}
fn kts_pow(nf: &NumberField, a: &Kts, m: u32, u: i64) -> Kts {
let mut acc = kts_one(nf);
for _ in 0..m {
acc = kts_mul(nf, &acc, a, u);
}
acc
}
fn kts_inv(nf: &NumberField, s: &Kts, u: i64) -> Option<Kts> {
let (&v0, c0) = s.iter().next()?; let inv_c0 = nf.inv(c0)?;
let kmax = (u + v0).max(1);
let mut us = vec![NumberField::k_zero(); kmax as usize + 1];
for (exp, c) in s {
let k = exp - v0;
if (0..=kmax).contains(&k) {
us[k as usize] = nf.mul(c, &inv_c0);
}
}
let mut iu = vec![NumberField::k_zero(); kmax as usize + 1];
iu[0] = nf.from_int(1);
for k in 1..=kmax as usize {
let mut acc = NumberField::k_zero();
for i in 1..=k {
acc = nf.add(&acc, &nf.mul(&us[i], &iu[k - i]));
}
iu[k] = nf.neg(&acc);
}
let mut res = Kts::new();
for (k, iuk) in iu.iter().enumerate() {
let exp = -v0 + k as i64;
if exp < u && !NumberField::is_zero(iuk) {
res.insert(exp, nf.mul(&inv_c0, iuk));
}
}
Some(res)
}
fn kts_comp(s: &Kts, k: i64, comp: usize) -> Rational {
s.get(&k)
.and_then(|c| c.get(comp))
.cloned()
.unwrap_or_else(|| Rational::from(0))
}
fn k_pow(nf: &NumberField, c: &KElem, e: u32) -> KElem {
let mut acc = nf.from_int(1);
for _ in 0..e {
acc = nf.mul(&acc, c);
}
acc
}
pub(super) fn branch_ts(s: &PuiseuxSeries) -> TS {
let e = s.ramification as i64;
let mut ts = TS::new();
for (exp, c) in &s.terms {
let k = (exp.clone() * Rational::from(e))
.numer()
.to_i64()
.unwrap_or(0);
ts.insert(k, c.clone());
}
ts
}
pub(super) fn elem_ts(b: &AlgElem, alpha: &Rational, e: i64, u: i64, bts: &TS) -> TS {
let mut acc = TS::new();
for (j, coeff) in b.iter().enumerate() {
if coeff.numer().is_empty() {
continue;
}
let cj = ratfn_ts(coeff, alpha, e, u);
let yj = ts_pow(bts, j as u32, u);
acc = ts_add(&acc, &ts_mul(&cj, &yj, u));
}
acc
}
fn ratfn_ts(r: &RatFn, alpha: &Rational, e: i64, u: i64) -> TS {
let num = poly_ts(r.numer(), alpha, e, u);
let den = poly_ts(r.denom(), alpha, e, u);
match ts_inv(&den, u) {
Some(inv) => ts_mul(&num, &inv, u),
None => TS::new(),
}
}
fn poly_ts(p: &QPoly, alpha: &Rational, e: i64, u: i64) -> TS {
let mut ts = TS::new();
for (m, pm) in p.iter().enumerate() {
if *pm == 0 {
continue;
}
for l in 0..=m {
let exp = e * l as i64;
if exp >= u {
break;
}
let coeff = pm.clone()
* Rational::from(binom(m as u32, l as u32))
* rat_pow(alpha, (m - l) as u32);
if coeff != 0 {
*ts.entry(exp).or_insert_with(|| Rational::from(0)) += &coeff;
}
}
}
ts.retain(|_, c| *c != 0);
ts
}
pub(super) fn ts_add(a: &TS, b: &TS) -> TS {
let mut r = a.clone();
for (k, c) in b {
*r.entry(*k).or_insert_with(|| Rational::from(0)) += c;
}
r.retain(|_, c| *c != 0);
r
}
pub(super) fn ts_mul(a: &TS, b: &TS, u: i64) -> TS {
let mut r = TS::new();
for (ka, ca) in a {
for (kb, cb) in b {
let k = ka + kb;
if k < u {
*r.entry(k).or_insert_with(|| Rational::from(0)) += ca.clone() * cb;
}
}
}
r.retain(|_, c| *c != 0);
r
}
pub(super) fn ts_pow(a: &TS, m: u32, u: i64) -> TS {
let mut acc = TS::new();
acc.insert(0, Rational::from(1));
for _ in 0..m {
acc = ts_mul(&acc, a, u);
}
acc
}
pub(super) fn ts_inv(s: &TS, u: i64) -> Option<TS> {
let (&v0, c0) = s.iter().next()?; let inv_c0 = Rational::from(1) / c0.clone();
let kmax = (u + v0).max(1);
let mut us = vec![Rational::from(0); kmax as usize + 1];
for (exp, c) in s {
let k = exp - v0;
if (0..=kmax).contains(&k) {
us[k as usize] = c.clone() * &inv_c0;
}
}
let mut iu = vec![Rational::from(0); kmax as usize + 1];
iu[0] = Rational::from(1);
for k in 1..=kmax as usize {
let mut acc = Rational::from(0);
for i in 1..=k {
acc += us[i].clone() * &iu[k - i];
}
iu[k] = -acc;
}
let mut res = TS::new();
for (k, iuk) in iu.iter().enumerate() {
let exp = -v0 + k as i64;
if exp < u && *iuk != 0 {
res.insert(exp, inv_c0.clone() * iuk);
}
}
Some(res)
}
fn as_simple_radical(f_coeffs: &[QPoly], n: usize) -> Option<QPoly> {
if f_coeffs.len() != n + 1 {
return None;
}
if f_coeffs[n].len() != 1 || f_coeffs[n][0] != 1 {
return None;
}
if f_coeffs[1..n].iter().any(|c| degree(c) >= 0) {
return None;
}
let p: QPoly = f_coeffs[0].iter().map(|c| -c.clone()).collect();
if degree(&p) < 0 {
return None;
}
Some(p)
}
fn to_monomials(f_coeffs: &[QPoly]) -> Vec<(u32, u32, Rational)> {
let mut out = Vec::new();
for (j, cj) in f_coeffs.iter().enumerate() {
for (i, c) in cj.iter().enumerate() {
if *c != 0 {
out.push((i as u32, j as u32, c.clone()));
}
}
}
out
}
fn max_denom_degree(b: &[AlgElem], bd: &AlgElem) -> usize {
let one = b
.iter()
.chain(std::iter::once(bd))
.flat_map(|e| e.iter())
.map(|c| degree(c.denom()).max(0) as usize)
.max()
.unwrap_or(0);
one
}
fn scale(b: &AlgElem, s: &RatFn) -> AlgElem {
let f = RationalFunctionField;
b.iter().map(|c| f.mul(s, c)).collect()
}
fn binom(n: u32, k: u32) -> Integer {
if k > n {
return Integer::from(0);
}
let mut num = Integer::from(1);
for t in 0..k {
num *= Integer::from(n - t);
}
let mut den = Integer::from(1);
for t in 1..=k {
den *= Integer::from(t);
}
num / den
}
fn rat_pow(c: &Rational, e: u32) -> Rational {
let mut acc = Rational::from(1);
for _ in 0..e {
acc *= c;
}
acc
}
fn gauss_solve(
mut m: Vec<Vec<Rational>>,
mut b: Vec<Rational>,
ncols: usize,
) -> Option<Vec<Rational>> {
let nrows = m.len();
let mut pivot_of_col = vec![None; ncols];
let mut row = 0usize;
for col in 0..ncols {
if row >= nrows {
break;
}
let Some(sel) = (row..nrows).find(|&r| m[r][col] != 0) else {
continue;
};
m.swap(row, sel);
b.swap(row, sel);
let piv = m[row][col].clone();
for v in m[row].iter_mut() {
*v /= ϖ
}
b[row] /= ϖ
let pr = m[row].clone();
let pb = b[row].clone();
for r in 0..nrows {
if r != row && m[r][col] != 0 {
let f = m[r][col].clone();
for (dst, pv) in m[r].iter_mut().zip(pr.iter()) {
*dst -= f.clone() * pv;
}
b[r] -= f * &pb;
}
}
pivot_of_col[col] = Some(row);
row += 1;
}
for r in 0..nrows {
if m[r].iter().all(|v| *v == 0) && b[r] != 0 {
return None; }
}
let mut x = vec![Rational::from(0); ncols];
for (col, pr) in pivot_of_col.iter().enumerate() {
if let Some(r) = pr {
x[col] = b[*r].clone();
}
}
Some(x)
}
#[cfg(test)]
mod tests {
use super::*;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
#[test]
fn nonsingular_power_basis() {
let basis = integral_basis(&[qp(&[-1, -1]), qp(&[]), qp(&[1])]).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(basis[0], vec![RatFn::int(1)]);
assert_eq!(basis[1], vec![RatFn::int(0), RatFn::int(1)]);
}
#[test]
fn cusp_basis() {
let basis = integral_basis(&[qp(&[0, 0, 0, -1]), qp(&[]), qp(&[1])]).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(basis[0], vec![RatFn::int(1)]);
assert_eq!(
basis[1],
vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[0, 1]))]
);
let f = [qp(&[0, 0, 0, -1]), qp(&[]), qp(&[1])];
assert!(basis.iter().all(|bi| is_integral(&f, bi)));
}
#[test]
fn nodal_cubic_basis() {
let f = [qp(&[0, 0, -1, -1]), qp(&[]), qp(&[1])];
let basis = integral_basis(&f).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(
basis[1],
vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[0, 1]))]
);
assert!(basis.iter().all(|bi| is_integral(&f, bi)));
}
#[test]
fn radical_fast_path_matches_explicit() {
let f = [qp(&[0, 0, 0, -1]), qp(&[]), qp(&[]), qp(&[]), qp(&[1])]; let via_loop = integral_basis(&f).expect("basis");
let explicit =
super::super::integral_basis::radical_integral_basis(4, &qp(&[0, 0, 0, 1])).unwrap();
assert_eq!(via_loop, explicit);
assert_eq!(via_loop.len(), 4);
assert_eq!(
via_loop[2],
vec![
RatFn::int(0),
RatFn::int(0),
RatFn::new(qp(&[1]), qp(&[0, 1]))
]
); assert!(via_loop.iter().all(|bi| is_integral(&f, bi)));
}
#[test]
fn algebraic_singular_place_sqrt2() {
let f = [qp(&[-12, -4, 12, 4, -3, -1]), qp(&[]), qp(&[1])];
let basis = integral_basis(&f).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(basis[0], vec![RatFn::int(1)]);
assert_eq!(
basis[1],
vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[-2, 0, 1]))]
);
assert!(basis.iter().all(|bi| is_integral(&f, bi)));
}
#[test]
fn algebraic_factor_simple_no_enlargement() {
let f = [qp(&[-6, -2, 3, 1]), qp(&[]), qp(&[1])];
let disc = discriminant(&f);
assert!(algebraic_singularities(&disc).is_empty());
let basis = integral_basis(&f).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(basis[0], vec![RatFn::int(1)]);
assert_eq!(basis[1], vec![RatFn::int(0), RatFn::int(1)]);
assert!(basis.iter().all(|bi| is_integral(&f, bi)));
}
#[test]
fn radical_with_irrational_branch_points() {
let f = [qp(&[2, 0, -1]), qp(&[]), qp(&[1])]; let basis = integral_basis(&f).expect("basis");
assert_eq!(basis.len(), 2);
assert_eq!(basis[0], vec![RatFn::int(1)]);
assert_eq!(basis[1], vec![RatFn::int(0), RatFn::int(1)]);
assert!(basis.iter().all(|bi| is_integral(&f, bi)));
}
#[test]
fn cubic_radical_basis() {
let f = [qp(&[0, 0, 1]), qp(&[]), qp(&[]), qp(&[1])]; let basis = integral_basis(&f).expect("basis");
assert_eq!(basis.len(), 3);
assert_eq!(basis[1], vec![RatFn::int(0), RatFn::int(1)]); assert_eq!(
basis[2],
vec![
RatFn::int(0),
RatFn::int(0),
RatFn::new(qp(&[1]), qp(&[0, 1]))
]
); assert!(basis.iter().all(|bi| is_integral(&f, bi)));
}
}