use rug::Rational;
use super::poly_rde::{
degree, poly_add, poly_deriv, poly_mul, poly_one, poly_scale, poly_zero, trim, QPoly,
};
pub fn poly_sub(a: &QPoly, b: &QPoly) -> QPoly {
poly_add(a, &poly_scale(b, &Rational::from(-1)))
}
fn coeff(p: &QPoly, i: i64) -> Rational {
if i < 0 {
return Rational::from(0);
}
p.get(i as usize)
.cloned()
.unwrap_or_else(|| Rational::from(0))
}
pub fn poly_divrem(a: &QPoly, b: &QPoly) -> (QPoly, QPoly) {
let b = trim(b.clone());
let bd = degree(&b);
debug_assert!(bd >= 0, "poly_divrem: division by zero polynomial");
let lcb = b[bd as usize].clone();
let mut r = trim(a.clone());
let ad = degree(&r);
if ad < bd {
return (poly_zero(), r);
}
let mut q = vec![Rational::from(0); (ad - bd + 1) as usize];
loop {
let rd = degree(&r);
if rd < bd {
break;
}
let shift = (rd - bd) as usize;
let factor = r[rd as usize].clone() / lcb.clone();
q[shift] += factor.clone();
for (i, bc) in b.iter().enumerate() {
r[shift + i] -= factor.clone() * bc.clone();
}
r = trim(r);
if r.is_empty() {
break;
}
}
(trim(q), trim(r))
}
pub fn poly_monic(p: &QPoly) -> QPoly {
let p = trim(p.clone());
let d = degree(&p);
if d < 0 {
return p;
}
let lc = p[d as usize].clone();
poly_scale(&p, &(Rational::from(1) / lc))
}
pub fn poly_gcd(a: &QPoly, b: &QPoly) -> QPoly {
let mut a = trim(a.clone());
let mut b = trim(b.clone());
while !b.is_empty() {
let (_, r) = poly_divrem(&a, &b);
a = b;
b = r;
}
poly_monic(&a)
}
pub fn poly_div_exact(a: &QPoly, b: &QPoly) -> QPoly {
let (q, r) = poly_divrem(a, b);
debug_assert!(trim(r).is_empty(), "poly_div_exact: nonzero remainder");
q
}
fn poly_pow(p: &QPoly, n: u32) -> QPoly {
let mut acc = poly_one();
for _ in 0..n {
acc = poly_mul(&acc, p);
}
acc
}
fn polys_equal(a: &QPoly, b: &QPoly) -> bool {
trim(a.clone()) == trim(b.clone())
}
fn solve_linear_system(
mut mat: Vec<Vec<Rational>>,
mut rhs: Vec<Rational>,
cols: usize,
) -> Option<Vec<Rational>> {
let rows = mat.len();
let mut pivot_row_of_col: Vec<Option<usize>> = vec![None; cols];
let mut row = 0usize;
for col in 0..cols {
if row >= rows {
break;
}
let Some(sel) = (row..rows).find(|&r| mat[r][col] != 0) else {
continue;
};
mat.swap(row, sel);
rhs.swap(row, sel);
let piv = mat[row][col].clone();
for cell in mat[row][col..cols].iter_mut() {
*cell /= piv.clone();
}
rhs[row] /= piv.clone();
let pivot_row = mat[row].clone();
let pivot_rhs = rhs[row].clone();
for r in 0..rows {
if r != row && mat[r][col] != 0 {
let factor = mat[r][col].clone();
for (cell, pv) in mat[r][col..cols]
.iter_mut()
.zip(pivot_row[col..cols].iter())
{
*cell -= factor.clone() * pv.clone();
}
rhs[r] -= factor.clone() * pivot_rhs.clone();
}
}
pivot_row_of_col[col] = Some(row);
row += 1;
}
for r in 0..rows {
if mat[r].iter().all(|v| *v == 0) && rhs[r] != 0 {
return None;
}
}
let mut x = vec![Rational::from(0); cols];
for (col, pr) in pivot_row_of_col.iter().enumerate() {
if let Some(pr) = pr {
x[col] = rhs[*pr].clone();
}
}
Some(x)
}
pub fn solve_rational_rde(f: &QPoly, c_num: &QPoly, c_den: &QPoly) -> Option<(QPoly, QPoly)> {
let c_num = trim(c_num.clone());
let c_den = trim(c_den.clone());
if c_num.is_empty() {
return Some((poly_zero(), poly_one()));
}
if c_den.is_empty() {
return None; }
let g = poly_gcd(&c_num, &c_den);
let big_c = poly_div_exact(&c_num, &g);
let b_raw = poly_div_exact(&c_den, &g);
let bd = degree(&b_raw);
let scale = Rational::from(1) / b_raw[bd as usize].clone();
let big_b = poly_scale(&b_raw, &scale);
let big_c = poly_scale(&big_c, &scale);
let bprime = poly_deriv(&big_b);
let e_poly = poly_gcd(&big_b, &bprime);
let g_poly = poly_div_exact(&big_b, &e_poly);
let eprime = poly_deriv(&e_poly);
let ge = poly_mul(&g_poly, &e_poly); let gep = poly_mul(&g_poly, &eprime); let gef = poly_mul(&ge, f); let target = poly_mul(&big_c, &e_poly);
let deg_b = degree(&big_b);
let deg_c = degree(&big_c);
let deg_e = degree(&e_poly).max(0);
let deg_f = degree(f).max(0);
let poly_part = (deg_c - deg_b).max(0);
let dbound = (deg_e + poly_part.max(deg_f) + 2).max(0) as usize;
let cols = dbound + 1;
let max_deg = (degree(&gef) + dbound as i64)
.max(degree(&ge) + dbound as i64)
.max(degree(&gep) + dbound as i64)
.max(degree(&target))
.max(0) as usize;
let n_rows = max_deg + 1;
let mut mat = vec![vec![Rational::from(0); cols]; n_rows];
for (d, row) in mat.iter_mut().enumerate() {
let d = d as i64;
for (j, cell) in row.iter_mut().enumerate() {
let jj = j as i64;
let mut v = Rational::from(jj) * coeff(&ge, d - jj + 1);
v -= coeff(&gep, d - jj);
v += coeff(&gef, d - jj);
*cell = v;
}
}
let rhs: Vec<Rational> = (0..n_rows).map(|d| coeff(&target, d as i64)).collect();
let solution = solve_linear_system(mat, rhs, cols)?;
let n_poly = trim(solution);
let np = poly_deriv(&n_poly);
let lhs = poly_mul(
&poly_add(
&poly_sub(&poly_mul(&np, &e_poly), &poly_mul(&n_poly, &eprime)),
&poly_mul(&poly_mul(f, &n_poly), &e_poly),
),
&big_b,
);
let rhs_check = poly_mul(&big_c, &poly_mul(&e_poly, &e_poly));
if !polys_equal(&lhs, &rhs_check) {
return None;
}
if n_poly.is_empty() {
return Some((poly_zero(), poly_one()));
}
let gve = poly_gcd(&n_poly, &e_poly);
let num = poly_div_exact(&n_poly, &gve);
let den = poly_monic(&poly_div_exact(&e_poly, &gve));
Some((num, den))
}
use crate::kernel::{ExprData, ExprId, ExprPool};
pub fn expr_to_qrational(expr: ExprId, var: ExprId, pool: &ExprPool) -> Option<(QPoly, QPoly)> {
if expr == var {
return Some((vec![Rational::from(0), Rational::from(1)], poly_one()));
}
match pool.get(expr) {
ExprData::Integer(n) => Some((vec![Rational::from(n.0.to_i64()?)], poly_one())),
ExprData::Rational(r) => Some((vec![r.0.clone()], poly_one())),
ExprData::Add(args) => {
let mut acc = (poly_zero(), poly_one());
for a in &args {
let term = expr_to_qrational(*a, var, pool)?;
acc = rat_add(&acc, &term);
}
Some(acc)
}
ExprData::Mul(args) => {
let mut acc = (poly_one(), poly_one());
for a in &args {
let factor = expr_to_qrational(*a, var, pool)?;
acc = rat_mul(&acc, &factor);
}
Some(acc)
}
ExprData::Pow { base, exp } => {
let n = match pool.get(exp) {
ExprData::Integer(n) => n.0.to_i64()?,
_ => return None,
};
let (bn, bd) = expr_to_qrational(base, var, pool)?;
if n >= 0 {
Some((poly_pow(&bn, n as u32), poly_pow(&bd, n as u32)))
} else {
let m = (-n) as u32;
if trim(bn.clone()).is_empty() {
return None; }
Some((poly_pow(&bd, m), poly_pow(&bn, m)))
}
}
_ => None,
}
}
fn rat_add(a: &(QPoly, QPoly), b: &(QPoly, QPoly)) -> (QPoly, QPoly) {
let num = poly_add(&poly_mul(&a.0, &b.1), &poly_mul(&b.0, &a.1));
let den = poly_mul(&a.1, &b.1);
(num, den)
}
fn rat_mul(a: &(QPoly, QPoly), b: &(QPoly, QPoly)) -> (QPoly, QPoly) {
(poly_mul(&a.0, &b.0), poly_mul(&a.1, &b.1))
}
#[cfg(test)]
mod tests {
use super::*;
fn rat(n: i64) -> Rational {
Rational::from(n)
}
#[test]
fn rational_elementary_exp_x() {
let f = vec![rat(1)]; let c_num = vec![rat(-1), rat(1)]; let c_den = vec![rat(0), rat(0), rat(1)]; let sol = solve_rational_rde(&f, &c_num, &c_den).expect("elementary");
assert_eq!(trim(sol.0.clone()), vec![rat(1)], "numerator should be 1");
assert_eq!(trim(sol.1.clone()), vec![rat(0), rat(1)], "denominator x");
}
#[test]
fn rational_nonelementary_x2_over_x_plus_1() {
let f = vec![rat(1)];
let c_num = vec![rat(0), rat(0), rat(1)]; let c_den = vec![rat(1), rat(1)]; assert!(
solve_rational_rde(&f, &c_num, &c_den).is_none(),
"x²/(x+1)·exp(x) must be certified non-elementary"
);
}
#[test]
fn rational_nonelementary_one_over_x() {
let f = vec![rat(1)];
let c_num = vec![rat(1)]; let c_den = vec![rat(0), rat(1)]; assert!(solve_rational_rde(&f, &c_num, &c_den).is_none());
}
#[test]
fn rational_nonelementary_one_over_x2() {
let f = vec![rat(1)];
let c_num = vec![rat(1)];
let c_den = vec![rat(0), rat(0), rat(1)]; assert!(solve_rational_rde(&f, &c_num, &c_den).is_none());
}
#[test]
fn rational_reduces_to_polynomial_case() {
let f = vec![rat(0), rat(2)]; let c_num = vec![rat(0), rat(1)]; let c_den = poly_one();
let sol = solve_rational_rde(&f, &c_num, &c_den).expect("elementary");
assert_eq!(trim(sol.0), vec![Rational::from((1, 2))]);
assert_eq!(trim(sol.1), vec![rat(1)]);
}
#[test]
fn divrem_gcd_basic() {
let a = vec![rat(-1), rat(0), rat(1)];
let b = vec![rat(1), rat(1)];
let (q, r) = poly_divrem(&a, &b);
assert_eq!(trim(q), vec![rat(-1), rat(1)]); assert!(trim(r).is_empty());
let c = vec![rat(1), rat(-2), rat(1)];
let g = poly_gcd(&a, &c);
assert_eq!(trim(g), vec![rat(-1), rat(1)]);
}
#[test]
fn qrational_parse() {
use crate::kernel::{Domain, ExprPool};
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let num = pool.add(vec![x, pool.integer(-1_i32)]);
let den = pool.pow(x, pool.integer(-2_i32));
let expr = pool.mul(vec![num, den]);
let (n, d) = expr_to_qrational(expr, x, &pool).expect("parse");
let lhs = poly_mul(&n, &vec![rat(0), rat(0), rat(1)]);
let rhs = poly_mul(&d, &vec![rat(-1), rat(1)]);
assert!(polys_equal(&lhs, &rhs), "n={n:?} d={d:?}");
}
}