use rug::{Integer, Rational};
use super::super::risch::poly_rde::{
degree, poly_add, poly_mul, poly_scale, qpoly_to_expr, trim, QPoly,
};
use super::super::risch::rational_rde::{poly_divrem, poly_monic, poly_sub};
use crate::kernel::{ExprId, ExprPool};
#[derive(Clone, Debug)]
pub(crate) struct CoatesPlace {
pub x: Rational,
pub y: Rational,
pub coeff: Integer,
}
fn q_is_zero(p: &QPoly) -> bool {
degree(p) < 0
}
fn poly_rem(a: &QPoly, b: &QPoly) -> QPoly {
trim(poly_divrem(a, b).1)
}
fn poly_eval(p: &QPoly, x: &Rational) -> Rational {
let mut acc = Rational::from(0);
for c in p.iter().rev() {
acc = acc * x + c;
}
acc
}
fn pow_rat(r: &Rational, e: i64) -> Rational {
let mut acc = Rational::from(1);
if e >= 0 {
for _ in 0..e {
acc *= r;
}
acc
} else {
for _ in 0..(-e) {
acc *= r;
}
Rational::from(1) / acc
}
}
fn subst_lc(p: &QPoly, lc: &Rational) -> QPoly {
let mut out = p.clone();
let mut s = Rational::from(1);
for c in out.iter_mut() {
*c *= &s;
s *= lc;
}
trim(out)
}
fn linear(alpha: &Rational) -> QPoly {
trim(vec![-alpha.clone(), Rational::from(1)])
}
struct Coates<'a> {
f: &'a QPoly, g: usize,
u: QPoly,
v: QPoly,
num_x: QPoly,
den_x: QPoly,
yfac: Vec<QPoly>,
}
impl<'a> Coates<'a> {
fn new(f: &'a QPoly, g: usize) -> Self {
Coates {
f,
g,
u: vec![Rational::from(1)],
v: vec![],
num_x: vec![Rational::from(1)],
den_x: vec![Rational::from(1)],
yfac: Vec::new(),
}
}
fn reduce(&mut self) -> Option<()> {
while degree(&self.u) > self.g as i64 {
let v2 = poly_mul(&self.v, &self.v);
let num = poly_sub(self.f, &v2);
let (w, r) = poly_divrem(&num, &self.u);
if !q_is_zero(&r) {
return None; }
let w = poly_monic(&w);
self.yfac.push(self.v.clone());
self.den_x = poly_mul(&self.den_x, &w);
let nv = poly_scale(&self.v, &Rational::from(-1));
let vp = if degree(&w) <= 0 {
vec![]
} else {
poly_rem(&nv, &w)
};
self.u = w;
self.v = vp;
}
Some(())
}
fn mumford_append(&mut self, alpha: &Rational, beta: &Rational) -> Option<()> {
let u_old = self.u.clone();
let ua = poly_eval(&u_old, alpha);
let t = if ua != 0 {
(beta.clone() - poly_eval(&self.v, alpha)) / ua
} else {
if *beta == 0 {
return None;
}
let v2 = poly_mul(&self.v, &self.v);
let num = poly_sub(&v2, self.f);
let (q, r) = poly_divrem(&num, &u_old);
if !q_is_zero(&r) {
return None;
}
-poly_eval(&q, alpha) / (Rational::from(2) * beta)
};
self.u = poly_mul(&u_old, &linear(alpha));
self.v = trim(poly_add(&self.v, &poly_scale(&u_old, &t)));
Some(())
}
fn mumford_remove(&mut self, alpha: &Rational) {
let (q, _r) = poly_divrem(&self.u, &linear(alpha));
self.u = poly_monic(&trim(q));
self.v = if degree(&self.u) <= 0 {
vec![]
} else {
poly_rem(&self.v, &self.u)
};
}
fn add_point(&mut self, alpha: &Rational, beta: &Rational) -> Option<()> {
let on_u = degree(&self.u) >= 0 && poly_eval(&self.u, alpha) == 0;
if on_u {
let vat = poly_eval(&self.v, alpha);
if *beta != 0 && vat == *beta {
self.mumford_append(alpha, beta)?;
self.reduce()?;
} else {
self.mumford_remove(alpha);
self.num_x = poly_mul(&self.num_x, &linear(alpha));
}
} else {
self.mumford_append(alpha, beta)?;
self.reduce()?;
}
Some(())
}
fn apply_unit(&mut self, sign: i32, alpha: &Rational, beta: &Rational) -> Option<()> {
if sign > 0 {
self.add_point(alpha, beta)
} else {
let on_u = degree(&self.u) >= 0 && poly_eval(&self.u, alpha) == 0;
if on_u && poly_eval(&self.v, alpha) == *beta {
self.mumford_remove(alpha);
} else {
self.add_point(alpha, &(-beta.clone()))?;
self.den_x = poly_mul(&self.den_x, &linear(alpha));
}
Some(())
}
}
}
pub(crate) fn coates_hyperelliptic(
a: &QPoly,
places: &[CoatesPlace],
var: ExprId,
pool: &ExprPool,
) -> Option<ExprId> {
let a = trim(a.clone());
let d = degree(&a);
if d < 3 || d % 2 == 0 {
return None; }
let dd = d as usize;
let g = (dd - 1) / 2;
let lc = a[dd].clone();
let mut f = vec![Rational::from(0); dd + 1];
for (k, slot) in f.iter_mut().enumerate() {
let e = dd as i64 - 1 - k as i64;
*slot = a[k].clone() * pow_rat(&lc, e);
}
let f = trim(f);
let lc_g = pow_rat(&lc, g as i64);
let mut st = Coates::new(&f, g);
for pl in places {
if pl.coeff == 0 {
continue;
}
let big_x = lc.clone() * &pl.x;
let big_y = lc_g.clone() * &pl.y;
if big_y.clone() * &big_y != poly_eval(&f, &big_x) {
return None;
}
let sign = if pl.coeff < 0 { -1 } else { 1 };
let reps = pl.coeff.clone().abs().to_u64()?;
for _ in 0..reps {
st.apply_unit(sign, &big_x, &big_y)?;
}
}
if degree(&st.u) != 0 {
return None;
}
let y_expr = pool.func("sqrt", vec![qpoly_to_expr(&a, var, pool)]);
let big_y_expr = if lc_g == 1 {
y_expr
} else {
pool.mul(vec![rat_expr(&lc_g, pool), y_expr])
};
let mut num_terms: Vec<ExprId> = Vec::new();
let num_poly_x = subst_lc(&st.num_x, &lc);
if !(degree(&num_poly_x) == 0 && num_poly_x[0] == 1) {
num_terms.push(qpoly_to_expr(&num_poly_x, var, pool));
}
for vj in &st.yfac {
let vpoly_x = subst_lc(vj, &lc);
let factor = pool.add(vec![
big_y_expr,
pool.mul(vec![
pool.integer(-1_i32),
qpoly_to_expr(&vpoly_x, var, pool),
]),
]);
num_terms.push(factor);
}
let num_expr = match num_terms.len() {
0 => pool.integer(1_i32),
1 => num_terms.remove(0),
_ => pool.mul(num_terms),
};
let den_poly_x = subst_lc(&st.den_x, &lc);
let u_expr = if degree(&den_poly_x) == 0 && den_poly_x[0] == 1 {
num_expr
} else {
let den_expr = qpoly_to_expr(&den_poly_x, var, pool);
pool.mul(vec![num_expr, pool.pow(den_expr, pool.integer(-1_i32))])
};
Some(u_expr)
}
fn rat_expr(r: &Rational, pool: &ExprPool) -> ExprId {
super::super::risch::poly_rde::rational_to_expr(r, pool)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kernel::Domain;
use crate::simplify::engine::simplify;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
fn place(x: i64, y: i64, c: i64) -> CoatesPlace {
CoatesPlace {
x: Rational::from(x),
y: Rational::from(y),
coeff: Integer::from(c),
}
}
fn eval(expr: ExprId, x: ExprId, xv: f64, pool: &ExprPool) -> Option<f64> {
use crate::kernel::ExprData;
if expr == x {
return Some(xv);
}
match pool.get(expr) {
ExprData::Integer(n) => Some(n.0.to_f64()),
ExprData::Rational(r) => Some(r.0.to_f64()),
ExprData::Add(args) => args
.iter()
.try_fold(0.0, |s, &a| Some(s + eval(a, x, xv, pool)?)),
ExprData::Mul(args) => args
.iter()
.try_fold(1.0, |s, &a| Some(s * eval(a, x, xv, pool)?)),
ExprData::Pow { base, exp } => {
Some(eval(base, x, xv, pool)?.powf(eval(exp, x, xv, pool)?))
}
ExprData::Func { ref name, ref args } if args.len() == 1 => {
let v = eval(args[0], x, xv, pool)?;
match name.as_str() {
"sqrt" => Some(v.sqrt()),
"log" => Some(v.ln()),
_ => None,
}
}
_ => None,
}
}
fn eval_qp(p: &QPoly, xv: f64) -> f64 {
p.iter().rev().fold(0.0, |acc, c| acc * xv + c.to_f64())
}
fn assert_constant_ratio(u: ExprId, ref_fn: ExprId, a: &QPoly, x: ExprId, pool: &ExprPool) {
let u = simplify(u, pool).value;
let mut ratios = Vec::new();
for &xv in &[0.31_f64, 1.27, 2.73, 3.61, 4.19, 5.53] {
let av = eval_qp(a, xv);
if av <= 1e-6 {
continue;
}
let (Some(uu), Some(rr)) = (eval(u, x, xv, pool), eval(ref_fn, x, xv, pool)) else {
continue;
};
if !uu.is_finite() || !rr.is_finite() || rr.abs() < 1e-9 {
continue;
}
ratios.push(uu / rr);
}
assert!(ratios.len() >= 3, "too few sample points: {ratios:?}");
let r0 = ratios[0];
assert!(r0.abs() > 1e-9, "reference/u vanished");
for r in &ratios {
assert!(
(r - r0).abs() < 1e-6 * (1.0 + r0.abs()),
"u/ref not constant: {ratios:?}"
);
}
}
#[test]
fn recover_y_minus_v() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let roots = [-2_i64, -1, 3, 4, 5];
let mut prod = qp(&[1]);
for &r in &roots {
prod = poly_mul(&prod, &qp(&[-r, 1]));
}
let v = qp(&[1, 1]); let a = trim(poly_add(&poly_mul(&v, &v), &prod));
assert_eq!(degree(&a), 5);
let places: Vec<CoatesPlace> = roots
.iter()
.map(|&r| place(r, eval_qp(&v, r as f64) as i64, 1))
.collect();
let u = coates_hyperelliptic(&a, &places, x, &pool).expect("principal");
let y = pool.func("sqrt", vec![qpoly_to_expr(&a, x, &pool)]);
let ref_fn = pool.add(vec![
y,
pool.mul(vec![pool.integer(-1_i32), qpoly_to_expr(&v, x, &pool)]),
]);
assert_constant_ratio(u, ref_fn, &a, x, &pool);
}
#[test]
fn recover_antisymmetric_unit() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let roots = [-2_i64, -1, 3, 4, 5];
let mut prod = qp(&[1]);
for &r in &roots {
prod = poly_mul(&prod, &qp(&[-r, 1]));
}
let v = qp(&[1, 1]);
let a = trim(poly_add(&poly_mul(&v, &v), &prod));
let mut places: Vec<CoatesPlace> = Vec::new();
for &r in &roots {
let vr = eval_qp(&v, r as f64) as i64;
places.push(place(r, vr, 1)); places.push(place(r, -vr, -1)); }
let u = coates_hyperelliptic(&a, &places, x, &pool).expect("principal");
let y = pool.func("sqrt", vec![qpoly_to_expr(&a, x, &pool)]);
let vexpr = qpoly_to_expr(&v, x, &pool);
let num = pool.add(vec![y, pool.mul(vec![pool.integer(-1_i32), vexpr])]);
let den = pool.add(vec![y, vexpr]);
let ref_fn = pool.mul(vec![num, pool.pow(den, pool.integer(-1_i32))]);
assert_constant_ratio(u, ref_fn, &a, x, &pool);
}
#[test]
fn recover_rational_x_function() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let a = qp(&[2, 1, 0, 0, 0, 1]); assert_eq!(eval_qp(&a, 1.0), 4.0);
assert_eq!(eval_qp(&a, 2.0), 36.0);
let places = vec![
place(1, 2, 1),
place(1, -2, 1),
place(2, 6, -1),
place(2, -6, -1),
];
let u = coates_hyperelliptic(&a, &places, x, &pool).expect("principal");
let ref_fn = pool.mul(vec![
pool.add(vec![x, pool.integer(-1_i32)]),
pool.pow(
pool.add(vec![x, pool.integer(-2_i32)]),
pool.integer(-1_i32),
),
]);
assert_constant_ratio(u, ref_fn, &a, x, &pool);
}
#[test]
fn non_principal_returns_none() {
let pool = ExprPool::new();
let x = pool.symbol("x", Domain::Real);
let a = qp(&[1, 1, 0, 0, 0, 1]); let places = vec![place(0, 1, 1)];
assert!(coates_hyperelliptic(&a, &places, x, &pool).is_none());
}
}