use rug::Rational;
use super::number_field::{KElem, KPoly, NumberField};
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
}
pub 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))
}
pub fn solve_rational_rde_generalized(
f_num: &QPoly,
f_den: &QPoly,
c_num: &QPoly,
c_den: &QPoly,
) -> Option<(QPoly, QPoly)> {
let f_den_t = trim(f_den.clone());
let f_num_t = trim(f_num.clone());
if f_den_t.is_empty() {
return None;
}
if degree(&f_den_t) == 0 {
let scale = Rational::from(1) / f_den_t[0].clone();
let f_poly = poly_scale(&f_num_t, &scale);
return solve_rational_rde(&f_poly, c_num, c_den);
}
let gf = poly_gcd(&f_num_t, &f_den_t);
let a_raw = poly_div_exact(&f_num_t, &gf);
let bf_raw = poly_div_exact(&f_den_t, &gf);
let bf_d = degree(&bf_raw);
debug_assert!(bf_d > 0, "f_den should have positive degree here");
let bf_lc_inv = Rational::from(1) / bf_raw[bf_d as usize].clone();
let big_a = poly_scale(&a_raw, &bf_lc_inv);
let big_bf = poly_scale(&bf_raw, &bf_lc_inv);
let c_num_t = trim(c_num.clone());
let c_den_t = trim(c_den.clone());
if c_num_t.is_empty() {
return Some((poly_zero(), poly_one()));
}
if c_den_t.is_empty() {
return None; }
let gc = poly_gcd(&c_num_t, &c_den_t);
let big_c_raw = poly_div_exact(&c_num_t, &gc);
let d_raw = poly_div_exact(&c_den_t, &gc);
let d_d = degree(&d_raw);
let d_lc_inv = if d_d >= 0 {
Rational::from(1) / d_raw[d_d as usize].clone()
} else {
Rational::from(1)
};
let big_d = poly_scale(&d_raw, &d_lc_inv);
let big_c = poly_scale(&big_c_raw, &d_lc_inv);
let dprime = poly_deriv(&big_d);
let e_poly = poly_gcd(&big_d, &dprime);
let g_poly = poly_div_exact(&big_d, &e_poly);
let eprime = poly_deriv(&e_poly);
let ge = poly_mul(&g_poly, &e_poly); let bfg = poly_mul(&big_bf, &g_poly); let bfge = poly_mul(&bfg, &e_poly); let bfgep = poly_mul(&bfg, &eprime); let age = poly_mul(&big_a, &ge); let target = poly_mul(&poly_mul(&big_bf, &big_c), &e_poly);
let deg_bf = degree(&big_bf).max(0) as usize;
let deg_e = degree(&e_poly).max(0) as usize;
let deg_c = degree(&big_c).max(0) as usize;
let deg_target = degree(&target).max(0) as usize;
let dbound = (deg_bf + deg_e + deg_c + 2).max(deg_target + 1);
let cols = dbound + 1;
let max_deg = (degree(&bfge) + dbound as i64)
.max(degree(&bfgep) + dbound as i64)
.max(degree(&age) + 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(&bfge, d - jj + 1);
v -= coeff(&bfgep, d - jj);
v += coeff(&age, 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_add(
&poly_mul(
&bfg,
&poly_sub(&poly_mul(&np, &e_poly), &poly_mul(&n_poly, &eprime)),
),
&poly_mul(&age, &n_poly),
);
if !polys_equal(&lhs, &target) {
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))
}
pub fn solve_rational_rde_k(
field: &NumberField,
f: &KPoly,
c_num: &KPoly,
c_den: &KPoly,
) -> Option<(KPoly, KPoly)> {
let c_num = NumberField::kpoly_trim(c_num.clone());
let c_den = NumberField::kpoly_trim(c_den.clone());
let one: KPoly = vec![field.from_int(1)];
if c_num.is_empty() {
return Some((Vec::new(), one));
}
if c_den.is_empty() {
return None; }
let g = field.kpoly_gcd(&c_num, &c_den)?;
let big_c = field.kpoly_div_exact(&c_num, &g)?;
let b_raw = field.kpoly_div_exact(&c_den, &g)?;
let bd = NumberField::kdeg(&b_raw);
let lead_inv = field.inv(&b_raw[bd as usize])?;
let big_b = field.kpoly_scale(&b_raw, &lead_inv);
let big_c = field.kpoly_scale(&big_c, &lead_inv);
let bprime = field.kpoly_deriv(&big_b);
let e_poly = field.kpoly_gcd(&big_b, &bprime)?;
let g_poly = field.kpoly_div_exact(&big_b, &e_poly)?;
let eprime = field.kpoly_deriv(&e_poly);
let ge = field.kpoly_mul(&g_poly, &e_poly);
let gep = field.kpoly_mul(&g_poly, &eprime);
let gef = field.kpoly_mul(&ge, f);
let target = field.kpoly_mul(&big_c, &e_poly);
let deg_b = NumberField::kdeg(&big_b);
let deg_c = NumberField::kdeg(&big_c);
let deg_e = NumberField::kdeg(&e_poly).max(0);
let deg_f = NumberField::kdeg(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 = (NumberField::kdeg(&gef) + dbound as i64)
.max(NumberField::kdeg(&ge) + dbound as i64)
.max(NumberField::kdeg(&gep) + dbound as i64)
.max(NumberField::kdeg(&target))
.max(0) as usize;
let n_rows = max_deg + 1;
let mut mat = vec![vec![NumberField::k_zero(); 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 = field.mul(&field.from_int(jj), &NumberField::kcoeff(&ge, d - jj + 1));
v = field.sub(&v, &NumberField::kcoeff(&gep, d - jj));
v = field.add(&v, &NumberField::kcoeff(&gef, d - jj));
*cell = v;
}
}
let rhs: Vec<KElem> = (0..n_rows)
.map(|d| NumberField::kcoeff(&target, d as i64))
.collect();
let solution = solve_linear_system_k(field, mat, rhs, cols)?;
let n_poly = NumberField::kpoly_trim(solution);
let np = field.kpoly_deriv(&n_poly);
let lhs = field.kpoly_mul(
&field.kpoly_add(
&field.kpoly_sub(
&field.kpoly_mul(&np, &e_poly),
&field.kpoly_mul(&n_poly, &eprime),
),
&field.kpoly_mul(&field.kpoly_mul(f, &n_poly), &e_poly),
),
&big_b,
);
let rhs_check = field.kpoly_mul(&big_c, &field.kpoly_mul(&e_poly, &e_poly));
if !NumberField::kpoly_eq(&lhs, &rhs_check) {
return None;
}
if n_poly.is_empty() {
return Some((Vec::new(), one));
}
let gve = field.kpoly_gcd(&n_poly, &e_poly)?;
let num = field.kpoly_div_exact(&n_poly, &gve)?;
let den = field.kpoly_monic(&field.kpoly_div_exact(&e_poly, &gve)?)?;
Some((num, den))
}
fn solve_linear_system_k(
field: &NumberField,
mut mat: Vec<Vec<KElem>>,
mut rhs: Vec<KElem>,
cols: usize,
) -> Option<Vec<KElem>> {
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| !NumberField::is_zero(&mat[r][col])) else {
continue;
};
mat.swap(row, sel);
rhs.swap(row, sel);
let piv_inv = field.inv(&mat[row][col])?;
for cell in mat[row][col..cols].iter_mut() {
*cell = field.mul(cell, &piv_inv);
}
rhs[row] = field.mul(&rhs[row], &piv_inv);
let pivot_row = mat[row].clone();
let pivot_rhs = rhs[row].clone();
for r in 0..rows {
if r != row && !NumberField::is_zero(&mat[r][col]) {
let factor = mat[r][col].clone();
for (cell, pv) in mat[r][col..cols]
.iter_mut()
.zip(pivot_row[col..cols].iter())
{
*cell = field.sub(cell, &field.mul(&factor, pv));
}
rhs[r] = field.sub(&rhs[r], &field.mul(&factor, &pivot_rhs));
}
}
pivot_row_of_col[col] = Some(row);
row += 1;
}
for r in 0..rows {
if mat[r].iter().all(NumberField::is_zero) && !NumberField::is_zero(&rhs[r]) {
return None;
}
}
let mut x = vec![NumberField::k_zero(); cols];
for (col, pr) in pivot_row_of_col.iter().enumerate() {
if let Some(pr) = pr {
x[col] = rhs[*pr].clone();
}
}
Some(x)
}
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:?}");
}
fn field_sqrt2() -> NumberField {
NumberField::new(vec![rat(-2), rat(0), rat(1)])
}
fn kc(field: &NumberField, n: i64) -> KElem {
field.from_int(n)
}
#[test]
fn rational_rde_k_elementary_sqrt2() {
let field = field_sqrt2();
let f: KPoly = vec![kc(&field, 1)]; let sqrt2 = vec![rat(0), rat(1)]; let c0 = field.sub(&field.neg(&sqrt2), &kc(&field, 1));
let c_num: KPoly = vec![c0, kc(&field, 1)];
let base: KPoly = vec![field.neg(&sqrt2), kc(&field, 1)]; let c_den = field.kpoly_mul(&base, &base);
let (vn, vd) = solve_rational_rde_k(&field, &f, &c_num, &c_den).expect("elementary");
assert_eq!(NumberField::kdeg(&vn), 0);
assert_eq!(trim(vn[0].clone()), vec![rat(1)]);
assert!(NumberField::kpoly_eq(&vd, &base));
}
#[test]
fn rational_rde_k_nonelementary_sqrt2() {
let field = field_sqrt2();
let f: KPoly = vec![kc(&field, 1)];
let sqrt2 = vec![rat(0), rat(1)];
let c_num: KPoly = vec![NumberField::k_zero(), NumberField::k_zero(), kc(&field, 1)]; let c_den: KPoly = vec![field.neg(&sqrt2), kc(&field, 1)]; assert!(solve_rational_rde_k(&field, &f, &c_num, &c_den).is_none());
}
#[test]
fn rational_rde_k_reduces_to_polynomial() {
let field = field_sqrt2();
let f: KPoly = vec![NumberField::k_zero(), kc(&field, 2)]; let c_num: KPoly = vec![NumberField::k_zero(), kc(&field, 1)]; let c_den: KPoly = vec![kc(&field, 1)]; let (vn, vd) = solve_rational_rde_k(&field, &f, &c_num, &c_den).expect("elementary");
assert_eq!(trim(vn[0].clone()), vec![Rational::from((1, 2))]);
assert_eq!(trim(vd[0].clone()), vec![rat(1)]);
}
#[test]
fn gen_rde_exp_inv_x_nonelementary() {
let f_num = vec![rat(-1)];
let f_den = vec![rat(0), rat(0), rat(1)]; let c_num = poly_one();
let c_den = poly_one();
assert!(
solve_rational_rde_generalized(&f_num, &f_den, &c_num, &c_den).is_none(),
"∫ exp(1/x) dx must be certified non-elementary"
);
}
#[test]
fn gen_rde_inv_x2_exp_inv_x_elementary() {
let f_num = vec![rat(-1)];
let f_den = vec![rat(0), rat(0), rat(1)]; let c_num = poly_one(); let c_den = vec![rat(0), rat(0), rat(1)]; let (vn, vd) = solve_rational_rde_generalized(&f_num, &f_den, &c_num, &c_den)
.expect("∫ (1/x²)·exp(1/x) dx must be elementary");
assert_eq!(
trim(vn.clone()),
vec![rat(-1)],
"numerator should be −1, got {vn:?}"
);
assert_eq!(
trim(vd.clone()),
poly_one(),
"denominator should be 1, got {vd:?}"
);
}
#[test]
fn gen_rde_exp_neg_inv_x2_elementary() {
let f_num = vec![rat(2)];
let f_den = vec![rat(0), rat(0), rat(0), rat(1)]; let c_num = vec![rat(2)];
let c_den = vec![rat(0), rat(0), rat(0), rat(1)]; let (vn, vd) = solve_rational_rde_generalized(&f_num, &f_den, &c_num, &c_den)
.expect("∫ (2/x³)·exp(−1/x²) dx must be elementary");
assert_eq!(
trim(vn.clone()),
poly_one(),
"numerator should be 1, got {vn:?}"
);
assert_eq!(
trim(vd.clone()),
poly_one(),
"denominator should be 1, got {vd:?}"
);
}
#[test]
fn gen_rde_falls_back_to_polynomial_f() {
let f_num = vec![rat(1)];
let f_den = poly_one(); let c_num = vec![rat(-1), rat(1)]; let c_den = vec![rat(0), rat(0), rat(1)]; let (vn, vd) = solve_rational_rde_generalized(&f_num, &f_den, &c_num, &c_den)
.expect("fallback must succeed");
assert_eq!(trim(vn), vec![rat(1)]);
assert_eq!(trim(vd), vec![rat(0), rat(1)]);
}
}