use ocas_atom::AtomArena;
use ocas_domain::{Domain, Rational, RationalDomain};
use ocas_poly::DenseUnivariatePolynomial;
use super::rational::poly_integrate;
use crate::tower::build::{GenKind, Tower};
use crate::tower::elem::{KElem, KPoly};
type DPoly = DenseUnivariatePolynomial<RationalDomain>;
pub(crate) fn rde_solve<'a>(
_ctx: &'a AtomArena<'a>,
tower: &Tower<'a>,
level: usize,
f: &KElem,
g: &KElem,
) -> Option<KElem> {
if g.is_zero() {
return Some(KElem::zero(f.n_vars()));
}
if level == 0 {
return base_rde_kelem(f, g);
}
let top = level;
let tgen = &tower.gens[level - 1];
if f.num.degree_in(top) > 0 || f.den.degree_in(top) > 0 {
return None;
}
let n = f.n_vars();
let g_den_inv = KElem::from_poly(g.den.clone()).inv()?;
let g_poly = KPoly::from_sparse(&g.num, top).mul_kelem(&g_den_inv);
let m = g_poly.degree()?;
let mut q_coeffs = vec![KElem::zero(n); m + 1];
match tgen.kind {
GenKind::Constant | GenKind::Log => {
for j in (0..=m).rev() {
let mut rhs = g_poly.coeff_at(j);
if j < m {
let shift = q_coeffs[j + 1]
.mul_rational(&Rational::new((j + 1) as i64, 1))
.mul(&tgen.dt);
rhs = rhs.sub(&shift);
}
q_coeffs[j] = rde_solve(_ctx, tower, level - 1, f, &rhs)?;
}
}
GenKind::Exp => {
let t = KElem::var(top, n);
let du = tgen.dt.div(&t)?;
for j in (0..=m).rev() {
let fj = f.add(&du.mul_rational(&Rational::new(j as i64, 1)));
q_coeffs[j] = rde_solve(_ctx, tower, level - 1, &fj, &g_poly.coeff_at(j))?;
}
}
}
let q = KPoly {
top,
coeffs: q_coeffs,
n_vars: n,
};
Some(q.kelem())
}
fn base_rde_kelem(f: &KElem, g: &KElem) -> Option<KElem> {
let n = f.n_vars();
let fd = kelem_to_dpoly(f)?;
let gd = kelem_to_dpoly(g)?;
let q = base_rde(&fd, &gd)?;
Some(embed(&dpoly_to_kelem(&q), n))
}
fn embed(e: &KElem, n: usize) -> KElem {
if e.n_vars() == n {
return e.clone();
}
let embed_poly = |p: &crate::tower::elem::Sparse| -> crate::tower::elem::Sparse {
crate::tower::elem::Sparse::from_terms(
RationalDomain,
n,
p.terms_ref()
.iter()
.map(|(exp, c)| (exp.to_vec(), c.clone()))
.collect(),
)
};
KElem::new(embed_poly(&e.num), embed_poly(&e.den))
}
fn kelem_to_dpoly(e: &KElem) -> Option<DPoly> {
let dom = RationalDomain;
if let Some(c) = e.as_rational() {
return Some(DPoly::from_coeffs(RationalDomain, vec![c]));
}
let den_is_const = e
.den
.terms_ref()
.keys()
.all(|ex| ex.iter().all(|&k| k == 0));
if !den_is_const {
return None;
}
let dc = e.den.coeff(&vec![0; e.n_vars()]);
let dc_inv = dom.inv(&dc)?;
if e.num
.terms_ref()
.keys()
.any(|ex| ex.iter().skip(1).any(|&k| k != 0))
{
return None;
}
let deg = e.num.degree_in(0);
let mut coeffs = vec![dom.zero(); deg + 1];
for (exp, c) in e.num.terms_ref() {
coeffs[exp[0]] = dom.mul(c, &dc_inv);
}
Some(DPoly::from_coeffs(RationalDomain, coeffs))
}
fn dpoly_to_kelem(p: &DPoly) -> KElem {
let terms = p
.coeffs()
.iter()
.enumerate()
.filter(|&(_, c)| !RationalDomain.is_zero(c))
.map(|(i, c)| (vec![i], c.clone()))
.collect();
KElem::from_poly(crate::tower::elem::Sparse::from_terms(
RationalDomain,
1,
terms,
))
}
fn base_rde(f: &DPoly, g: &DPoly) -> Option<DPoly> {
let dom = RationalDomain;
if g.is_zero() {
return Some(DPoly::from_coeffs(RationalDomain, vec![]));
}
if f.is_zero() {
return Some(poly_integrate(g));
}
let mf = f.degree()?;
let mg = g.degree()?;
let mut r = g.clone();
if mf == 0 {
let c = f.lcoeff();
let mut q_coeffs = vec![dom.zero(); mg + 1];
for j in (0..=mg).rev() {
let rc = coeff_at(&r, j);
if dom.is_zero(&rc) {
continue;
}
let qj = dom.div(&rc, &c)?;
q_coeffs[j] = qj.clone();
let term = monomial(qj, j);
r = r.sub(&term.derivative().add(&term.mul_scalar(&c)));
}
if r.is_zero() {
Some(DPoly::from_coeffs(RationalDomain, q_coeffs))
} else {
None
}
} else {
let m = mg.checked_sub(mf)?;
let flc = f.lcoeff();
let mut q_coeffs = vec![dom.zero(); m + 1];
for j in (0..=m).rev() {
let rc = coeff_at(&r, j + mf);
if dom.is_zero(&rc) {
continue;
}
let qj = dom.div(&rc, &flc)?;
q_coeffs[j] = qj.clone();
let term = monomial(qj, j);
r = r.sub(&term.derivative().add(&term.mul(f)));
}
if r.is_zero() {
Some(DPoly::from_coeffs(RationalDomain, q_coeffs))
} else {
None
}
}
}
fn coeff_at(p: &DPoly, i: usize) -> Rational {
p.coeffs()
.get(i)
.cloned()
.unwrap_or_else(|| RationalDomain.zero())
}
fn monomial(c: Rational, k: usize) -> DPoly {
let mut coeffs = vec![RationalDomain.zero(); k];
coeffs.push(c);
DPoly::from_coeffs(RationalDomain, coeffs)
}
#[cfg(test)]
mod tests {
use ocas_atom::AtomArena;
use ocas_core::arena::Arena;
use super::*;
use crate::tower::build::build_tower;
use crate::tower::convert::atom_to_rational_extended;
use ocas_atom::Symbol;
fn rat(p: i64, q: i64) -> Rational {
Rational::new(p, q)
}
fn dpoly(coeffs: &[(i64, i64)]) -> DPoly {
DPoly::from_coeffs(
RationalDomain,
coeffs.iter().map(|&(p, q)| rat(p, q)).collect(),
)
}
#[test]
fn base_rde_constant_f() {
let f = dpoly(&[(1, 1)]);
let g = dpoly(&[(0, 1), (1, 1)]);
let q = base_rde(&f, &g).expect("solution");
assert_eq!(q, dpoly(&[(-1, 1), (1, 1)]));
}
#[test]
fn base_rde_polynomial_f() {
let f = dpoly(&[(0, 1), (1, 1)]);
let g = dpoly(&[(1, 1), (0, 1), (1, 1)]);
let q = base_rde(&f, &g).expect("solution");
assert_eq!(q, dpoly(&[(0, 1), (1, 1)]));
}
#[test]
fn base_rde_no_solution() {
let f = dpoly(&[(0, 1), (1, 1)]);
let g = dpoly(&[(1, 1)]);
assert!(base_rde(&f, &g).is_none());
}
#[test]
fn base_rde_zero_f_integrates() {
let f = DPoly::from_coeffs(RationalDomain, vec![]);
let g = dpoly(&[(0, 1), (2, 1)]);
let q = base_rde(&f, &g).expect("solution");
assert_eq!(q, dpoly(&[(0, 1), (0, 1), (1, 1)]));
}
#[test]
fn rde_hyperexponential_layer() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let tower = build_tower(&ctx, ctx.fun("exp", &[x]), Symbol::new("x")).unwrap();
let n = tower.n_vars();
let one = KElem::one(n);
let g = KElem::var(0, n);
let q = rde_solve(&ctx, &tower, 1, &one, &g).expect("solution");
let expect = KElem::var(0, n).sub(&KElem::one(n));
assert!(q.eq_cross(&expect));
}
#[test]
fn rde_primitive_layer() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let tower = build_tower(&ctx, ctx.fun("log", &[x]), Symbol::new("x")).unwrap();
let n = tower.n_vars();
let one = KElem::one(n);
let q = rde_solve(&ctx, &tower, 1, &one, &one).expect("solution");
assert!(q.eq_cross(&KElem::one(n)));
}
#[test]
fn rde_primitive_top_down() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let tower = build_tower(&ctx, ctx.fun("log", &[x]), Symbol::new("x")).unwrap();
let n = tower.n_vars();
let f = KElem::one(n);
let g = KElem::var(0, n)
.add(&KElem::one(n))
.mul(&KElem::var(1, n))
.add(&KElem::one(n));
let q = rde_solve(&ctx, &tower, 1, &f, &g).expect("solution");
let dq = crate::tower::build::tower_diff(&q, &tower.gens);
assert!(dq.add(&q).eq_cross(&g));
assert!(q.eq_cross(&KElem::var(0, n).mul(&KElem::var(1, n))));
}
#[test]
fn kelem_dpoly_roundtrip() {
let arena = Arena::new();
let ctx = AtomArena::new(&arena);
let x = ctx.var("x");
let expr = ctx.add(&[ctx.pow(x, ctx.num(2)), ctx.num(3)]);
let rf = atom_to_rational_extended(expr, &[x], 1).unwrap();
let e = KElem::new(rf.numerator, rf.denominator);
let d = kelem_to_dpoly(&e).unwrap();
assert_eq!(d, dpoly(&[(3, 1), (0, 1), (1, 1)]));
let back = dpoly_to_kelem(&d);
assert!(back.eq_cross(&e));
}
}