use rug::Rational;
use super::super::risch::poly_rde::{
degree, poly_add, poly_deriv, poly_mul, poly_scale, trim, QPoly,
};
use super::super::risch::rational_rde::{poly_divrem, poly_gcd, poly_monic, poly_sub};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum SqrtRde {
Solved(QPoly, QPoly),
NoRationalSolution,
Undecided,
}
pub(super) fn decide(a: &QPoly, b_num: &QPoly, b_den: &QPoly) -> SqrtRde {
let a = trim(a.clone());
let m = degree(&a);
if m < 1 {
return SqrtRde::Undecided;
}
let a_prime = poly_deriv(&a);
if degree(&poly_gcd(&a, &a_prime)) > 0 {
return SqrtRde::Undecided; }
let (bn, bd) = match reduce(b_num, b_den) {
Some(r) => r,
None => return SqrtRde::Undecided, };
if bn.is_empty() {
return SqrtRde::Solved(vec![], vec![Rational::from(1)]); }
let d_poly = {
let g = poly_gcd(&bd, &poly_deriv(&bd));
if degree(&g) < 0 {
vec![Rational::from(1)]
} else {
g
}
};
let delta = degree(&d_poly).max(0);
let two_a = poly_scale(&a, &Rational::from(2));
let d_sq = poly_mul(&d_poly, &d_poly);
let rhs_num = poly_mul(&poly_mul(&two_a, &d_sq), &bn);
let (rhs, rem) = poly_divrem(&rhs_num, &bd);
if !trim(rem).is_empty() {
return SqrtRde::NoRationalSolution;
}
let rhs = trim(rhs);
let deg_rhs = degree(&rhs);
let d_max = (deg_rhs - m - delta + 1).max(delta).max(0) as usize;
if d_max > 512 {
return SqrtRde::Undecided; }
let d_prime = poly_deriv(&d_poly);
let mut cols: Vec<QPoly> = Vec::with_capacity(d_max + 1);
for j in 0..=d_max {
let xj = monomial(j);
let dxj = if j == 0 {
vec![]
} else {
let mut p = monomial(j - 1);
p[j - 1] = Rational::from(j as u32);
p
};
let wronskian = poly_sub(&poly_mul(&dxj, &d_poly), &poly_mul(&xj, &d_prime));
let col = trim(poly_add(
&poly_mul(&two_a, &wronskian),
&poly_mul(&poly_mul(&a_prime, &xj), &d_poly),
));
cols.push(col);
}
let rows = cols
.iter()
.map(degree)
.chain(std::iter::once(deg_rhs))
.max()
.unwrap_or(-1);
if rows < 0 {
return SqrtRde::NoRationalSolution; }
let rows = rows as usize + 1;
let mut mat: Vec<Vec<Rational>> = (0..rows)
.map(|i| {
let mut row: Vec<Rational> = cols
.iter()
.map(|c| c.get(i).cloned().unwrap_or_else(|| Rational::from(0)))
.collect();
row.push(rhs.get(i).cloned().unwrap_or_else(|| Rational::from(0)));
row
})
.collect();
let Some(sol) = solve_exact(&mut mat, d_max + 1) else {
return SqrtRde::NoRationalSolution;
};
let n_poly = trim(sol);
let lhs = trim(poly_add(
&poly_mul(
&two_a,
&poly_sub(
&poly_mul(&poly_deriv(&n_poly), &d_poly),
&poly_mul(&n_poly, &d_prime),
),
),
&poly_mul(&poly_mul(&a_prime, &n_poly), &d_poly),
));
if lhs != rhs {
return SqrtRde::Undecided;
}
SqrtRde::Solved(n_poly, d_poly)
}
fn monomial(j: usize) -> QPoly {
let mut p = vec![Rational::from(0); j + 1];
p[j] = Rational::from(1);
p
}
fn reduce(b_num: &QPoly, b_den: &QPoly) -> Option<(QPoly, QPoly)> {
let num = trim(b_num.clone());
let den = trim(b_den.clone());
if den.is_empty() {
return None;
}
if num.is_empty() {
return Some((vec![], vec![Rational::from(1)]));
}
let g = poly_gcd(&num, &den);
let (num, _) = poly_divrem(&num, &g);
let (den, _) = poly_divrem(&den, &g);
let lc = den[degree(&den) as usize].clone();
Some((
poly_scale(&num, &(Rational::from(1) / lc)),
poly_monic(&den),
))
}
fn solve_exact(mat: &mut [Vec<Rational>], n: usize) -> Option<QPoly> {
let rows = mat.len();
let mut pivot_of_col = vec![usize::MAX; n];
let mut r = 0;
for c in 0..n {
let Some(p) = (r..rows).find(|&i| mat[i][c] != 0) else {
continue;
};
mat.swap(r, p);
let inv = Rational::from(1) / mat[r][c].clone();
for v in mat[r].iter_mut() {
*v *= &inv;
}
for i in 0..rows {
if i != r && mat[i][c] != 0 {
let f = mat[i][c].clone();
let pivot_row = mat[r].clone();
for (dst, src) in mat[i].iter_mut().zip(&pivot_row).skip(c) {
*dst -= f.clone() * src;
}
}
}
pivot_of_col[c] = r;
r += 1;
if r == rows {
break;
}
}
for row in mat.iter() {
if row[..n].iter().all(|v| *v == 0) && row[n] != 0 {
return None;
}
}
let mut sol = vec![Rational::from(0); n];
for (c, &p) in pivot_of_col.iter().enumerate() {
if p != usize::MAX {
sol[c] = mat[p][n].clone();
}
}
Some(sol)
}
#[cfg(test)]
mod tests {
use super::*;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
#[test]
fn solves_the_obvious_algebraic_primitive() {
let a = qp(&[1, 0, 0, 0, 0, 1]); let ap = poly_deriv(&a);
let two_a = poly_scale(&a, &Rational::from(2));
match decide(&a, &ap, &two_a) {
SqrtRde::Solved(n, d) => {
assert_eq!(degree(&n), 0);
assert_eq!(degree(&d), 0);
assert_eq!(
n[0].clone() / d[0].clone(),
Rational::from(1),
"expected v = 1"
);
}
other => panic!("expected Solved, got {other:?}"),
}
}
#[test]
fn solves_polynomial_weight() {
let a = qp(&[1, 0, 0, 0, 0, 1]);
let b = qp(&[0, 0, 0, 0, 5]);
match decide(&a, &b, &qp(&[1])) {
SqrtRde::Solved(n, d) => {
assert_eq!(degree(&d), 0);
let v: Vec<Rational> = n.iter().map(|c| c.clone() / d[0].clone()).collect();
assert_eq!(
v,
vec![
Rational::from((2, 3)),
Rational::from(0),
Rational::from(0),
Rational::from(0),
Rational::from(0),
Rational::from((2, 3))
]
);
}
other => panic!("expected Solved, got {other:?}"),
}
}
#[test]
fn proves_no_algebraic_primitive_for_the_quintic_first_kind() {
let a = qp(&[1, 0, 0, 0, 0, 1]);
assert_eq!(
decide(&a, &qp(&[1]), &a.clone()),
SqrtRde::NoRationalSolution
);
}
#[test]
fn proves_no_algebraic_primitive_for_the_elementary_pullback() {
let a = qp(&[1, 0, 0, 0, -1]);
assert_eq!(
decide(&a, &qp(&[0, 1]), &a.clone()),
SqrtRde::NoRationalSolution
);
}
#[test]
fn proves_no_algebraic_primitive_for_the_genus_two_first_kind() {
let a = qp(&[1, 0, 0, 0, 0, 0, -1]);
assert_eq!(
decide(&a, &qp(&[0, 1]), &a.clone()),
SqrtRde::NoRationalSolution
);
}
#[test]
fn refuses_non_squarefree_radicand() {
let a = poly_mul(&qp(&[1, 1]), &poly_mul(&qp(&[1, 1]), &qp(&[0, 1])));
assert_eq!(decide(&a, &qp(&[1]), &qp(&[1])), SqrtRde::Undecided);
}
#[test]
fn zero_weight_is_solved() {
let a = qp(&[1, 0, 0, 1]);
assert!(matches!(
decide(&a, &qp(&[]), &qp(&[1])),
SqrtRde::Solved(_, _)
));
}
#[test]
fn solves_with_a_denominator() {
let a = qp(&[1, 0, 0, 1]);
let bn = qp(&[-2, 0, 0, 1]);
let bd = poly_scale(&poly_mul(&qp(&[0, 0, 1]), &a), &Rational::from(2));
match decide(&a, &bn, &bd) {
SqrtRde::Solved(n, d) => {
assert_eq!(trim(poly_mul(&n, &qp(&[0, 1]))), trim(d));
}
other => panic!("expected Solved, got {other:?}"),
}
}
}