use super::diff_field::DifferentialField;
type FPoly<F> = Vec<<F as DifferentialField>::Elem>;
#[derive(Clone, Debug)]
pub struct RadicalExt<F: DifferentialField> {
base: F,
radicand_a: F::Elem,
n: usize,
}
impl<F: DifferentialField> RadicalExt<F> {
pub fn new(base: F, radicand_a: F::Elem, n: usize) -> Self {
assert!(n >= 1, "RadicalExt: degree n must be ≥ 1");
Self {
base,
radicand_a,
n,
}
}
pub fn base(&self) -> &F {
&self.base
}
pub fn degree(&self) -> usize {
self.n
}
pub fn radicand(&self) -> &F::Elem {
&self.radicand_a
}
fn log_deriv_a(&self) -> Option<F::Elem> {
let da = self.base.derivation(&self.radicand_a);
let inv_a = self.base.inv(&self.radicand_a)?;
Some(self.base.mul(&da, &inv_a))
}
fn base_scalar_ratio(&self, m: usize) -> F::Elem {
let num = self.base_int(m as i64);
let den = self.base_int(self.n as i64);
let den_inv = self
.base
.inv(&den)
.expect("n ≥ 1 ⇒ nonzero base integer is invertible");
self.base.mul(&num, &den_inv)
}
fn base_int(&self, m: i64) -> F::Elem {
let one = self.base.one();
let mut acc = self.base.zero();
for _ in 0..m.unsigned_abs() {
acc = self.base.add(&acc, &one);
}
if m < 0 {
self.base.neg(&acc)
} else {
acc
}
}
fn trim(&self, mut v: Vec<F::Elem>) -> Vec<F::Elem> {
while v.last().is_some_and(|c| self.base.is_zero(c)) {
v.pop();
}
v
}
fn reduce(&self, v: &[F::Elem]) -> Vec<F::Elem> {
if v.len() <= self.n {
return self.trim(v.to_vec());
}
let mut v = v.to_vec();
for k in (self.n..v.len()).rev() {
let c = v[k].clone();
if self.base.is_zero(&c) {
continue;
}
let folded = self.base.mul(&c, &self.radicand_a);
let lower = k - self.n;
v[lower] = self.base.add(&v[lower], &folded);
v[k] = self.base.zero();
}
v.truncate(self.n);
self.trim(v)
}
fn coupled_nondiagonal_rde(&self, f: &[F::Elem], g: &[F::Elem]) -> Option<Vec<F::Elem>> {
let comps = |v: &[F::Elem]| -> Vec<F::Elem> {
let mut out = vec![self.base.zero(); self.n];
for (i, c) in v.iter().take(self.n).enumerate() {
out[i] = c.clone();
}
out
};
let f_comps = comps(f);
let g_comps = comps(g);
let u = self
.base
.coupled_radical_rde(self.n, &self.radicand_a, &f_comps, &g_comps)?;
let u = self.trim(u);
let f_elem = f.to_vec();
let g_elem = self.trim(g.to_vec());
let lhs = self.add(&self.derivation(&u), &self.mul(&f_elem, &u));
if self.eq(&lhs, &g_elem) {
Some(u)
} else {
None
}
}
}
impl<F: DifferentialField> DifferentialField for RadicalExt<F> {
type Elem = Vec<F::Elem>;
fn zero(&self) -> Self::Elem {
Vec::new()
}
fn one(&self) -> Self::Elem {
vec![self.base.one()]
}
fn add(&self, a: &Self::Elem, b: &Self::Elem) -> Self::Elem {
let len = a.len().max(b.len());
let mut r = Vec::with_capacity(len);
for i in 0..len {
let ai = a.get(i).cloned().unwrap_or_else(|| self.base.zero());
let bi = b.get(i).cloned().unwrap_or_else(|| self.base.zero());
r.push(self.base.add(&ai, &bi));
}
self.trim(r)
}
fn sub(&self, a: &Self::Elem, b: &Self::Elem) -> Self::Elem {
let len = a.len().max(b.len());
let mut r = Vec::with_capacity(len);
for i in 0..len {
let ai = a.get(i).cloned().unwrap_or_else(|| self.base.zero());
let bi = b.get(i).cloned().unwrap_or_else(|| self.base.zero());
r.push(self.base.sub(&ai, &bi));
}
self.trim(r)
}
fn mul(&self, a: &Self::Elem, b: &Self::Elem) -> Self::Elem {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut raw = vec![self.base.zero(); a.len() + b.len() - 1];
for (i, ca) in a.iter().enumerate() {
if self.base.is_zero(ca) {
continue;
}
for (j, cb) in b.iter().enumerate() {
let p = self.base.mul(ca, cb);
raw[i + j] = self.base.add(&raw[i + j], &p);
}
}
self.reduce(&raw)
}
fn neg(&self, a: &Self::Elem) -> Self::Elem {
self.trim(a.iter().map(|c| self.base.neg(c)).collect())
}
fn inv(&self, a: &Self::Elem) -> Option<Self::Elem> {
let a = self.trim(a.to_vec());
if a.is_empty() {
return None;
}
let mut modulus = vec![self.base.zero(); self.n + 1];
modulus[0] = self.base.neg(&self.radicand_a);
modulus[self.n] = self.base.one();
let (g, s, _t) = self.fpoly_ext_gcd(&a, &modulus);
if g.len() != 1 || self.base.is_zero(&g[0]) {
return None;
}
let g_inv = self.base.inv(&g[0])?;
let s = self.fpoly_scale(&s, &g_inv);
Some(self.reduce(&s))
}
fn is_zero(&self, a: &Self::Elem) -> bool {
self.trim(a.to_vec()).is_empty()
}
fn eq(&self, a: &Self::Elem, b: &Self::Elem) -> bool {
let a = self.trim(a.to_vec());
let b = self.trim(b.to_vec());
a.len() == b.len() && a.iter().zip(b.iter()).all(|(x, y)| self.base.eq(x, y))
}
fn derivation(&self, a: &Self::Elem) -> Self::Elem {
if a.is_empty() {
return Vec::new();
}
let lda = match self.log_deriv_a() {
Some(v) => v,
None => {
let mut out = vec![self.base.zero(); a.len()];
if let Some(c0) = a.first() {
out[0] = self.base.derivation(c0);
}
return self.trim(out);
}
};
let mut out = Vec::with_capacity(a.len());
for (i, ci) in a.iter().enumerate() {
let dci = self.base.derivation(ci);
if i == 0 {
out.push(dci);
} else {
let scale = self.base_scalar_ratio(i); let twist = self.base.mul(ci, &self.base.mul(&scale, &lda));
out.push(self.base.add(&dci, &twist));
}
}
self.trim(out)
}
fn rational_rde(&self, f: &Self::Elem, g: &Self::Elem) -> Option<Self::Elem> {
let f_trim = self.trim(f.to_vec());
if f_trim.len() > 1 {
return self.coupled_nondiagonal_rde(f, g);
}
let f0 = f_trim
.into_iter()
.next()
.unwrap_or_else(|| self.base.zero());
let lda = self.log_deriv_a()?;
let g = self.trim(g.to_vec());
let mut u = vec![self.base.zero(); g.len()];
for (i, gi) in g.iter().enumerate() {
if self.base.is_zero(gi) {
continue;
}
let omega = if i == 0 {
f0.clone()
} else {
let scale = self.base_scalar_ratio(i);
let twist = self.base.mul(&scale, &lda);
self.base.add(&f0, &twist)
};
let ui = self.base.rational_rde(&omega, gi)?; u[i] = ui;
}
let u = self.trim(u);
let lhs = self.add(&self.derivation(&u), &self.mul(f, &u));
if self.eq(&lhs, &g) {
Some(u)
} else {
None
}
}
}
impl<F: DifferentialField> RadicalExt<F> {
fn fpoly_trim(&self, mut p: Vec<F::Elem>) -> Vec<F::Elem> {
while p.last().is_some_and(|c| self.base.is_zero(c)) {
p.pop();
}
p
}
fn fpoly_degree(&self, p: &[F::Elem]) -> i64 {
let mut d = p.len() as i64 - 1;
while d >= 0 && self.base.is_zero(&p[d as usize]) {
d -= 1;
}
d
}
fn fpoly_scale(&self, p: &[F::Elem], s: &F::Elem) -> Vec<F::Elem> {
if self.base.is_zero(s) {
return Vec::new();
}
self.fpoly_trim(p.iter().map(|c| self.base.mul(c, s)).collect())
}
fn fpoly_sub(&self, a: &[F::Elem], b: &[F::Elem]) -> Vec<F::Elem> {
let n = a.len().max(b.len());
let mut r = Vec::with_capacity(n);
for i in 0..n {
let ai = a.get(i).cloned().unwrap_or_else(|| self.base.zero());
let bi = b.get(i).cloned().unwrap_or_else(|| self.base.zero());
r.push(self.base.sub(&ai, &bi));
}
self.fpoly_trim(r)
}
fn fpoly_mul(&self, a: &[F::Elem], b: &[F::Elem]) -> Vec<F::Elem> {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut r = vec![self.base.zero(); a.len() + b.len() - 1];
for (i, ca) in a.iter().enumerate() {
if self.base.is_zero(ca) {
continue;
}
for (j, cb) in b.iter().enumerate() {
let p = self.base.mul(ca, cb);
r[i + j] = self.base.add(&r[i + j], &p);
}
}
self.fpoly_trim(r)
}
fn fpoly_divrem(&self, a: &[F::Elem], b: &[F::Elem]) -> (Vec<F::Elem>, Vec<F::Elem>) {
let b = self.fpoly_trim(b.to_vec());
let bd = self.fpoly_degree(&b);
debug_assert!(bd >= 0, "division by zero polynomial");
let lc_inv = self
.base
.inv(&b[bd as usize])
.expect("nonzero leading coefficient of a field element is invertible");
let mut r = self.fpoly_trim(a.to_vec());
let ad = self.fpoly_degree(&r);
if ad < bd {
return (Vec::new(), r);
}
let mut q = vec![self.base.zero(); (ad - bd + 1) as usize];
loop {
let rd = self.fpoly_degree(&r);
if rd < bd {
break;
}
let shift = (rd - bd) as usize;
let factor = self.base.mul(&r[rd as usize], &lc_inv);
q[shift] = self.base.add(&q[shift], &factor);
for (i, bc) in b.iter().enumerate() {
let prod = self.base.mul(&factor, bc);
r[shift + i] = self.base.sub(&r[shift + i], &prod);
}
r = self.fpoly_trim(r);
if r.is_empty() {
break;
}
}
(self.fpoly_trim(q), r)
}
fn fpoly_ext_gcd(&self, a: &[F::Elem], b: &[F::Elem]) -> (FPoly<F>, FPoly<F>, FPoly<F>) {
let (mut old_r, mut r) = (self.fpoly_trim(a.to_vec()), self.fpoly_trim(b.to_vec()));
let one = vec![self.base.one()];
let (mut old_s, mut s) = (one.clone(), Vec::new());
let (mut old_t, mut t) = (Vec::new(), one);
while !r.is_empty() {
let (q, rem) = self.fpoly_divrem(&old_r, &r);
old_r = r;
r = rem;
let ns = self.fpoly_sub(&old_s, &self.fpoly_mul(&q, &s));
old_s = s;
s = ns;
let nt = self.fpoly_sub(&old_t, &self.fpoly_mul(&q, &t));
old_t = t;
t = nt;
}
(old_r, old_s, old_t)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::integrate::risch::alg_field::RatFn;
use crate::integrate::risch::diff_field::RationalDiffField;
use crate::integrate::risch::poly_rde::QPoly;
use crate::integrate::risch::tower_field::{ExpTowerField, LogTowerField, TExpr};
use rug::Rational;
fn rat(n: i64) -> Rational {
Rational::from(n)
}
fn rf_poly(c: &[i64]) -> RatFn {
let p: QPoly = c.iter().map(|&n| rat(n)).collect();
RatFn::from_poly(&p)
}
fn sqrt_x() -> RadicalExt<RationalDiffField> {
RadicalExt::new(RationalDiffField::new(), rf_poly(&[0, 1]), 2)
}
#[test]
fn arithmetic_mod_y2_eq_x() {
let ext = sqrt_x();
let y = vec![ext.base().zero(), ext.base().one()];
let yy = ext.mul(&y, &y);
assert!(
ext.eq(&yy, &vec![rf_poly(&[0, 1])]),
"y² should reduce to x; got {yy:?}"
);
let one_plus_y = ext.add(&ext.one(), &y);
let sq = ext.mul(&one_plus_y, &one_plus_y);
let expected = vec![rf_poly(&[1, 1]), rf_poly(&[2])]; assert!(ext.eq(&sq, &expected), "got {sq:?}");
}
#[test]
fn inverse_of_y_is_y_over_x() {
let ext = sqrt_x();
let y = vec![ext.base().zero(), ext.base().one()];
let inv = ext.inv(&y).expect("y invertible");
assert!(ext.eq(&ext.mul(&y, &inv), &ext.one()));
let expected = vec![
ext.base().zero(),
RatFn::new(vec![rat(1)], vec![rat(0), rat(1)]),
];
assert!(ext.eq(&inv, &expected), "got {inv:?}");
}
#[test]
fn derivation_of_y_is_half_over_x_times_y() {
let ext = sqrt_x();
let y = vec![ext.base().zero(), ext.base().one()];
let dy = ext.derivation(&y);
let expected = vec![
ext.base().zero(),
RatFn::new(vec![rat(1)], vec![rat(0), rat(2)]), ];
assert!(ext.eq(&dy, &expected), "D(y) should be y/(2x); got {dy:?}");
}
#[test]
fn derivation_product_rule_holds() {
let ext = sqrt_x();
let y = vec![ext.base().zero(), ext.base().one()];
let d_yy = ext.derivation(&ext.mul(&y, &y));
assert!(ext.eq(&d_yy, &ext.one()), "D(y²)=D(x)=1; got {d_yy:?}");
let dy = ext.derivation(&y);
let pr = ext.add(&ext.mul(&dy, &y), &ext.mul(&y, &dy));
assert!(ext.eq(&d_yy, &pr), "product rule mismatch");
}
fn check_solvable(ext: &RadicalExt<RationalDiffField>, f: &Vec<RatFn>, u: &Vec<RatFn>) {
let g = ext.add(&ext.derivation(u), &ext.mul(f, u));
let sol = ext.rational_rde(f, &g).expect("should be solvable");
let lhs = ext.add(&ext.derivation(&sol), &ext.mul(f, &sol));
assert!(ext.eq(&lhs, &g), "RDE not satisfied; sol={sol:?}");
}
#[test]
fn rde_pure_antiderivative_per_component() {
let ext = sqrt_x();
let f = ext.zero(); let u = vec![rf_poly(&[0, 1]), rf_poly(&[0, 0, 1])];
check_solvable(&ext, &f, &u);
}
#[test]
fn rde_base_scalar_f() {
let ext = sqrt_x();
let f = vec![ext.base().one()]; let u = vec![rf_poly(&[0, 1]), rf_poly(&[1])]; check_solvable(&ext, &f, &u);
}
#[test]
fn rde_unsolvable_component_is_none() {
let ext = sqrt_x();
let f = ext.zero();
let g = vec![RatFn::new(vec![rat(1)], vec![rat(0), rat(1)])]; assert!(ext.rational_rde(&f, &g).is_none(), "log x ∉ ℚ(x) ⇒ None");
}
fn check_nondiag_solvable(
ext: &RadicalExt<RationalDiffField>,
f: &Vec<RatFn>,
u_true: &Vec<RatFn>,
) {
assert!(ext.trim(f.clone()).len() > 1, "test f must be non-diagonal");
let g = ext.add(&ext.derivation(u_true), &ext.mul(f, u_true));
let sol = ext
.rational_rde(f, &g)
.expect("non-diagonal f over ℚ(x) should now solve");
let lhs = ext.add(&ext.derivation(&sol), &ext.mul(f, &sol));
assert!(ext.eq(&lhs, &g), "coupled RDE not satisfied; sol={sol:?}");
}
#[test]
fn rde_nondiagonal_f_sqrt_x_solves() {
let ext = sqrt_x();
let inv_2x = RatFn::new(vec![rat(1)], vec![rat(0), rat(2)]); let f = vec![ext.base().zero(), inv_2x]; let u_true = vec![ext.base().zero(), ext.base().one()]; check_nondiag_solvable(&ext, &f, &u_true);
}
#[test]
fn rde_nondiagonal_f_cbrt_x_solves() {
let ext = RadicalExt::new(RationalDiffField::new(), rf_poly(&[0, 1]), 3);
let inv_3x = RatFn::new(vec![rat(1)], vec![rat(0), rat(3)]); let f = vec![ext.base().zero(), inv_3x]; let u_true = vec![ext.base().zero(), ext.base().one()]; check_nondiag_solvable(&ext, &f, &u_true);
}
#[test]
fn rde_nondiagonal_f_over_log_tower_base_solves() {
let dh_over_h = RatFn::new(vec![rat(1)], vec![rat(0), rat(1)]); let log_field = LogTowerField::new(dh_over_h);
let x = TExpr::from_ratfn(rf_poly(&[0, 1]));
let a = <LogTowerField as DifferentialField>::add(&log_field, &x, &TExpr::t()); let ext = RadicalExt::new(log_field.clone(), a.clone(), 2);
let c = <LogTowerField as DifferentialField>::inv(&log_field, &a).unwrap();
let f = vec![<LogTowerField as DifferentialField>::zero(&log_field), c];
let u_true = vec![
<LogTowerField as DifferentialField>::zero(&log_field),
<LogTowerField as DifferentialField>::one(&log_field),
]; assert!(ext.trim(f.clone()).len() > 1, "f must be non-diagonal");
let g = ext.add(&ext.derivation(&u_true), &ext.mul(&f, &u_true));
let sol = ext.rational_rde(&f, &g).expect("log-tower coupled solve");
let lhs = ext.add(&ext.derivation(&sol), &ext.mul(&f, &sol));
assert!(ext.eq(&lhs, &g), "coupled RDE not satisfied; sol={sol:?}");
assert!(ext.eq(&sol, &u_true), "expected u = y; got {sol:?}");
}
#[test]
fn rde_nondiagonal_f_over_exp_tower_base_solves() {
let exp_field = ExpTowerField::new(RatFn::int(1)); let x = TExpr::from_ratfn(rf_poly(&[0, 1]));
let a = <ExpTowerField as DifferentialField>::add(&exp_field, &x, &TExpr::t()); let ext = RadicalExt::new(exp_field.clone(), a.clone(), 2);
let c = <ExpTowerField as DifferentialField>::inv(&exp_field, &a).unwrap();
let f = vec![<ExpTowerField as DifferentialField>::zero(&exp_field), c];
let u_true = vec![
<ExpTowerField as DifferentialField>::zero(&exp_field),
<ExpTowerField as DifferentialField>::one(&exp_field),
];
let g = ext.add(&ext.derivation(&u_true), &ext.mul(&f, &u_true));
let sol = ext.rational_rde(&f, &g).expect("exp-tower coupled solve");
let lhs = ext.add(&ext.derivation(&sol), &ext.mul(&f, &sol));
assert!(ext.eq(&lhs, &g), "coupled RDE not satisfied; sol={sol:?}");
}
#[test]
fn rde_nondiagonal_f_over_tower_base_unsolvable_is_none() {
let dh_over_h = RatFn::new(vec![rat(1)], vec![rat(0), rat(1)]); let log_field = LogTowerField::new(dh_over_h);
let x = TExpr::from_ratfn(rf_poly(&[0, 1]));
let a = <LogTowerField as DifferentialField>::add(&log_field, &x, &TExpr::t());
let ext = RadicalExt::new(log_field.clone(), a, 2);
let f = vec![
<LogTowerField as DifferentialField>::zero(&log_field),
<LogTowerField as DifferentialField>::one(&log_field),
];
let inv_x = TExpr::from_ratfn(RatFn::new(vec![rat(1)], vec![rat(0), rat(1)]));
let g = vec![inv_x];
assert!(
ext.rational_rde(&f, &g).is_none(),
"unsolvable tower-base coupled case ⇒ None"
);
}
#[test]
fn radical_over_log_tower_descent() {
let dh_over_h = RatFn::new(vec![rat(1)], vec![rat(0), rat(1)]); let log_field = LogTowerField::new(dh_over_h);
let a = {
let x = TExpr::from_ratfn(rf_poly(&[0, 1]));
<LogTowerField as DifferentialField>::add(&log_field, &x, &TExpr::t())
};
let ext = RadicalExt::new(log_field.clone(), a, 2);
let f = vec![<LogTowerField as DifferentialField>::one(&log_field)];
let target = vec![
<LogTowerField as DifferentialField>::zero(&log_field),
<LogTowerField as DifferentialField>::one(&log_field),
]; let g = ext.add(&ext.derivation(&target), &ext.mul(&f, &target));
let sol = ext
.rational_rde(&f, &g)
.expect("radical-over-log descent should solve");
assert!(
ext.eq(&sol, &target),
"recovered w should be y; got {sol:?}"
);
let lhs = ext.add(&ext.derivation(&sol), &ext.mul(&f, &sol));
assert!(ext.eq(&lhs, &g), "D(w)+f·w=g must hold in-field");
}
#[test]
fn stubs_decline() {
let ext = sqrt_x();
let one = ext.one();
assert!(ext
.limited_integrate(&one, std::slice::from_ref(&one))
.is_none());
assert!(ext.param_log_deriv(&one, &one).is_none());
}
}