use rug::Rational;
use super::alg_field::{AlgElem, AlgExtension, RatFn};
use super::poly_rde::{poly_mul, poly_one, trim, QPoly};
use super::rational_rde::{poly_div_exact, poly_gcd};
const DEG_CAP: usize = 6;
pub(crate) fn solve_alg_rde(e: &AlgExtension, f: &RatFn, g: &AlgElem) -> Option<AlgElem> {
let d = e.degree() as usize;
if d == 0 {
return None;
}
let dens = candidate_denominators(e, f, g, d);
for den in &dens {
for ncap in 0..=DEG_CAP {
if let Some(y) = solve_with_denominator(e, f, g, den, ncap, d) {
return Some(y);
}
}
}
None
}
fn candidate_denominators(e: &AlgExtension, f: &RatFn, 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());
}
}
}
base = poly_lcm(&base, f.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: &RatFn,
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 f_elem = e.constant(f.clone());
let cols: Vec<AlgElem> = elems
.iter()
.map(|m| e.add(&e.derivation(m), &e.mul(&f_elem, 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_elem, &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());
}
}