use rug::Rational;
use super::alg_field::{AlgElem, AlgExtension, RatFn};
use super::poly_rde::{degree, poly_mul, poly_one, trim, QPoly};
use super::rational_rde::{poly_div_exact, poly_gcd};
const DEG_CAP: usize = 6;
const X_DEG_SANITY_CAP: usize = 48;
pub(crate) fn alg_x_degree_bound(e: &AlgExtension, f: &AlgElem, g: &AlgElem) -> usize {
let comp_xdeg = |el: &AlgElem| -> i64 {
el.iter()
.map(|c| degree(c.numer()).max(degree(c.denom())))
.max()
.unwrap_or(0)
};
let mut base = comp_xdeg(f).max(comp_xdeg(g));
let d = e.degree() as usize;
let gen = e.generator();
for j in 0..d {
if let Some(aj) = e.pow(&gen, j as i64) {
base = base.max(comp_xdeg(&e.derivation(&aj)));
}
}
(base.max(0) + 2) as usize
}
pub(crate) fn solve_alg_rde(e: &AlgExtension, f: &RatFn, g: &AlgElem) -> Option<AlgElem> {
solve_alg_rde_general(e, &e.constant(f.clone()), g)
}
pub(crate) fn solve_alg_rde_general(e: &AlgExtension, f: &AlgElem, g: &AlgElem) -> Option<AlgElem> {
let d = e.degree() as usize;
if d == 0 {
return None;
}
let cap = alg_x_degree_bound(e, f, g).clamp(DEG_CAP, X_DEG_SANITY_CAP);
let dens = candidate_denominators(e, f, g, d);
for den in &dens {
for ncap in 0..=cap {
if let Some(y) = solve_with_denominator(e, f, g, den, ncap, d) {
return Some(y);
}
}
}
None
}
fn candidate_denominators(e: &AlgExtension, f: &AlgElem, g: &AlgElem, d: usize) -> Vec<QPoly> {
let mut base = poly_one();
let gen = e.generator();
for j in 0..d {
if let Some(aj) = e.pow(&gen, j as i64) {
for c in e.derivation(&aj) {
base = poly_lcm(&base, c.denom());
}
}
}
for c in f {
base = poly_lcm(&base, c.denom());
}
for c in g {
base = poly_lcm(&base, c.denom());
}
let base2 = poly_mul(&base, &base);
let base3 = poly_mul(&base2, &base);
let mut out = vec![poly_one(), base, base2, base3];
out.dedup_by(|a, b| trim(a.clone()) == trim(b.clone()));
out
}
fn solve_with_denominator(
e: &AlgExtension,
f: &AlgElem,
g: &AlgElem,
den: &QPoly,
ncap: usize,
d: usize,
) -> Option<AlgElem> {
let basis: Vec<(usize, usize)> = (0..d)
.flat_map(|j| (0..=ncap).map(move |m| (j, m)))
.collect();
let elems: Vec<AlgElem> = basis
.iter()
.map(|&(j, m)| {
let coeff = RatFn::new(x_pow(m), den.clone()); let mut v = vec![RatFn::int(0); d];
v[j] = coeff;
e.reduce(&v)
})
.collect();
let cols: Vec<AlgElem> = elems
.iter()
.map(|m| e.add(&e.derivation(m), &e.mul(f, m)))
.collect();
let (matrix, rhs) = extract_linear_system(&cols, g, d);
let sol = gauss_solve(matrix, rhs, basis.len())?;
let mut y = e.from_int(0);
for (idx, elem) in elems.iter().enumerate() {
if sol[idx] != 0 {
let s = e.constant(RatFn::from_poly(&vec![sol[idx].clone()]));
y = e.add(&y, &e.mul(&s, elem));
}
}
let lhs = e.add(&e.derivation(&y), &e.mul(f, &y));
if e.elem_eq(&lhs, g) {
Some(y)
} else {
None
}
}
fn extract_linear_system(
cols: &[AlgElem],
target: &AlgElem,
d: usize,
) -> (Vec<Vec<Rational>>, Vec<Rational>) {
let comp =
|a: &AlgElem, k: usize| -> RatFn { a.get(k).cloned().unwrap_or_else(|| RatFn::int(0)) };
let mut matrix: Vec<Vec<Rational>> = Vec::new();
let mut rhs: Vec<Rational> = Vec::new();
for k in 0..d {
let col_rf: Vec<RatFn> = cols.iter().map(|c| comp(c, k)).collect();
let tgt_rf = comp(target, k);
let mut d_x = poly_one();
for r in &col_rf {
d_x = poly_lcm(&d_x, r.denom());
}
d_x = poly_lcm(&d_x, tgt_rf.denom());
let s_cols: Vec<QPoly> = col_rf
.iter()
.map(|r| poly_mul(r.numer(), &poly_div_exact(&d_x, r.denom())))
.collect();
let s_tgt = poly_mul(tgt_rf.numer(), &poly_div_exact(&d_x, tgt_rf.denom()));
let max_m = s_cols
.iter()
.map(|s| s.len())
.chain(std::iter::once(s_tgt.len()))
.max()
.unwrap_or(0);
for m in 0..max_m {
matrix.push(
s_cols
.iter()
.map(|s| s.get(m).cloned().unwrap_or_else(|| Rational::from(0)))
.collect(),
);
rhs.push(s_tgt.get(m).cloned().unwrap_or_else(|| Rational::from(0)));
}
}
(matrix, rhs)
}
fn gauss_solve(
mut m: Vec<Vec<Rational>>,
mut b: Vec<Rational>,
ncols: usize,
) -> Option<Vec<Rational>> {
let nrows = m.len();
let mut pivot_row_of_col: Vec<Option<usize>> = 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 = v.clone() / piv.clone();
}
b[row] = b[row].clone() / piv.clone();
let pivot_row = m[row].clone();
let pivot_b = b[row].clone();
for r in 0..nrows {
if r != row && m[r][col] != 0 {
let factor = m[r][col].clone();
for (dst, pv) in m[r].iter_mut().zip(pivot_row.iter()) {
*dst -= factor.clone() * pv.clone();
}
b[r] -= factor * pivot_b.clone();
}
}
pivot_row_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_row_of_col.iter().enumerate() {
if let Some(r) = pr {
x[col] = b[*r].clone();
}
}
Some(x)
}
fn poly_lcm(a: &QPoly, b: &QPoly) -> QPoly {
let g = poly_gcd(a, b);
poly_div_exact(&poly_mul(a, b), &g)
}
fn x_pow(m: usize) -> QPoly {
let mut p = vec![Rational::from(0); m + 1];
p[m] = Rational::from(1);
p
}
#[cfg(test)]
mod tests {
use super::*;
use crate::integrate::risch::poly_rde::poly_scale;
fn rat(n: i64) -> Rational {
Rational::from(n)
}
#[test]
fn cyclic_sqrt_recovers_solution() {
let e = AlgExtension::radical(2, &vec![rat(0), rat(1)]); let g: AlgElem = vec![
RatFn::int(0),
RatFn::from_poly(&vec![Rational::from((3, 2))]),
];
let f = RatFn::int(0);
let y = solve_alg_rde(&e, &f, &g).expect("should solve");
assert!(e.elem_eq(&e.derivation(&y), &g));
let expected: AlgElem = vec![RatFn::int(0), RatFn::from_poly(&vec![rat(0), rat(1)])];
assert!(e.elem_eq(&y, &expected), "y = {:?}", y);
}
#[test]
fn noncyclic_compositum_pure_antiderivative() {
let q: Vec<QPoly> = vec![
poly_one(), Vec::new(), poly_scale(&vec![rat(1), rat(2)], &rat(-2)), Vec::new(), poly_one(), ];
let e = AlgExtension::new(&q);
assert_eq!(e.degree(), 4);
let alpha = e.generator();
let g = e.derivation(&alpha); let f = RatFn::int(0);
let y = solve_alg_rde(&e, &f, &g).expect("coupled RDE should solve");
assert!(
e.elem_eq(&e.derivation(&y), &g),
"D(y) must equal g; y = {y:?}"
);
}
#[test]
fn noncyclic_compositum_with_f() {
let q: Vec<QPoly> = vec![
poly_one(),
Vec::new(),
poly_scale(&vec![rat(1), rat(2)], &rat(-2)),
Vec::new(),
poly_one(),
];
let e = AlgExtension::new(&q);
let alpha = e.generator();
let f = RatFn::new(poly_one(), vec![rat(0), rat(1)]); let f_elem = e.constant(f.clone());
let g = e.add(&e.derivation(&alpha), &e.mul(&f_elem, &alpha));
let y = solve_alg_rde(&e, &f, &g).expect("coupled RDE with f should solve");
let lhs = e.add(&e.derivation(&y), &e.mul(&f_elem, &y));
assert!(e.elem_eq(&lhs, &g), "D(y)+f·y must equal g; y = {y:?}");
}
#[test]
fn unsolvable_log_returns_none() {
let q: Vec<QPoly> = vec![
poly_one(),
Vec::new(),
poly_scale(&vec![rat(1), rat(2)], &rat(-2)),
Vec::new(),
poly_one(),
];
let e = AlgExtension::new(&q);
let g = e.constant(RatFn::new(poly_one(), vec![rat(0), rat(1)]));
let f = RatFn::int(0);
assert!(solve_alg_rde(&e, &f, &g).is_none());
}
#[test]
fn nonbase_f_sqrt_coupled() {
let e = AlgExtension::radical(2, &vec![rat(0), rat(1)]); let alpha = e.generator();
let f: AlgElem = vec![RatFn::int(0), RatFn::new(poly_one(), vec![rat(0), rat(2)])];
let y_true = alpha.clone();
let g = e.add(&e.derivation(&y_true), &e.mul(&f, &y_true));
let y = solve_alg_rde_general(&e, &f, &g).expect("non-base f coupled solve");
let lhs = e.add(&e.derivation(&y), &e.mul(&f, &y));
assert!(e.elem_eq(&lhs, &g), "D(y)+f·y must equal g; y = {y:?}");
assert!(e.elem_eq(&y, &y_true), "expected y = √x; got {y:?}");
}
#[test]
fn nonbase_f_cbrt_coupled() {
let e = AlgExtension::radical(3, &vec![rat(0), rat(1)]); let f: AlgElem = vec![
RatFn::int(0),
RatFn::int(0),
RatFn::new(poly_one(), vec![rat(0), rat(1)]),
];
let y_true = e.constant(RatFn::from_poly(&vec![rat(0), rat(1)]));
let g = e.add(&e.derivation(&y_true), &e.mul(&f, &y_true));
let y = solve_alg_rde_general(&e, &f, &g).expect("non-base cbrt f coupled solve");
let lhs = e.add(&e.derivation(&y), &e.mul(&f, &y));
assert!(e.elem_eq(&lhs, &g), "D(y)+f·y must equal g; y = {y:?}");
assert!(e.elem_eq(&y, &y_true), "expected y = x; got {y:?}");
}
#[test]
fn base_f_wrapper_matches_general() {
let e = AlgExtension::radical(2, &vec![rat(0), rat(1)]); let alpha = e.generator();
let f = RatFn::new(poly_one(), vec![rat(0), rat(1)]); let f_elem = e.constant(f.clone());
let g = e.add(&e.derivation(&alpha), &e.mul(&f_elem, &alpha));
let via_wrapper = solve_alg_rde(&e, &f, &g);
let via_general = solve_alg_rde_general(&e, &f_elem, &g);
match (&via_wrapper, &via_general) {
(Some(a), Some(b)) => assert!(e.elem_eq(a, b), "wrapper vs general differ"),
(None, None) => {}
_ => panic!("wrapper vs general disagree on solvability"),
}
assert!(via_wrapper.is_some(), "base-f case should solve");
}
#[test]
fn nonbase_f_unsolvable_returns_none() {
let e = AlgExtension::radical(2, &vec![rat(0), rat(1)]); let f = e.generator(); let g = e.constant(RatFn::new(poly_one(), vec![rat(0), rat(1)])); assert!(solve_alg_rde_general(&e, &f, &g).is_none());
}
#[test]
fn high_degree_solution_recovered_by_analytic_bound() {
let e = AlgExtension::radical(2, &vec![rat(0), rat(1)]); let mut p8 = vec![rat(0); 9];
p8[8] = rat(1);
let y_true: AlgElem = vec![RatFn::int(0), RatFn::from_poly(&p8)];
let f_elem = e.constant(RatFn::int(1)); let g = e.add(&e.derivation(&y_true), &e.mul(&f_elem, &y_true));
let bound = alg_x_degree_bound(&e, &f_elem, &g);
assert!(
bound >= 8,
"analytic bound {bound} must cover the degree-8 solution"
);
assert!(
bound > DEG_CAP,
"bound {bound} must exceed the old fixed DEG_CAP {DEG_CAP}"
);
assert!(
solve_with_denominator(&e, &f_elem, &g, &poly_one(), DEG_CAP, 2).is_none(),
"degree-{DEG_CAP} ansatz must NOT contain the degree-8 solution"
);
let y = solve_alg_rde_general(&e, &f_elem, &g)
.expect("analytic bound must recover the high-degree solution");
let lhs = e.add(&e.derivation(&y), &e.mul(&f_elem, &y));
assert!(e.elem_eq(&lhs, &g), "D(y)+f·y must equal g; y = {y:?}");
assert!(e.elem_eq(&y, &y_true), "expected y = x⁸·α; got {y:?}");
}
}