use rug::{Integer, Rational};
use super::super::risch::poly_rde::{degree, poly_deriv, trim, QPoly};
use super::super::risch::rational_rde::poly_gcd;
use super::elliptic::{
quartic_point_model, short_weierstrass, weierstrass_from_quartic, EllipticCurve, Point,
};
use super::residues::{residue_sum, PlacedResidue, Residue};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum FindOrder {
Principal { order: u32 },
NonElementary,
NotDecided,
}
pub fn genus(n: usize, a: &QPoly) -> Option<usize> {
let a = trim(a.clone());
let m = degree(&a);
if m < 1 || n < 2 {
return None;
}
if degree(&poly_gcd(&a, &poly_deriv(&a))) > 0 {
return None;
}
let (m, n) = (m as i64, n as i64);
let g2 = m * n - m - n + 2 - gcd_i64(n, m);
if g2 < 0 || g2 % 2 != 0 {
return None;
}
Some((g2 / 2) as usize)
}
pub fn find_order(n: usize, a: &QPoly, divisor: &[Residue]) -> FindOrder {
if residue_sum(divisor) != 0 {
return FindOrder::NotDecided;
}
if divisor.iter().all(|r| r.value == 0) {
return FindOrder::Principal { order: 1 };
}
match genus(n, a) {
Some(0) => FindOrder::Principal { order: 1 },
_ => FindOrder::NotDecided,
}
}
pub(crate) fn find_order_placed(n: usize, a: &QPoly, divisor: &[PlacedResidue]) -> FindOrder {
if divisor
.iter()
.fold(Rational::from(0), |s, r| s + &r.residue.value)
!= 0
{
return FindOrder::NotDecided;
}
if divisor.iter().all(|r| r.residue.value == 0) {
return FindOrder::Principal { order: 1 };
}
match genus(n, a) {
Some(0) => FindOrder::Principal { order: 1 },
Some(1) => genus1(n, a, divisor).unwrap_or(FindOrder::NotDecided),
Some(_) => super::jacobian_torsion::find_order_genus_ge2(n, a, divisor),
None => FindOrder::NotDecided,
}
}
fn genus1(n: usize, a: &QPoly, divisor: &[PlacedResidue]) -> Option<FindOrder> {
if n != 2 {
return None;
}
let a = trim(a.clone());
#[allow(clippy::type_complexity)]
let (e, mapper): (EllipticCurve, Box<dyn Fn(&PlacedResidue) -> Option<Point>>) =
match degree(&a) {
3 => {
let (e, map) = short_weierstrass(&a)?;
let f = move |r: &PlacedResidue| -> Option<Point> {
if r.residue.at_infinity {
None
} else {
let (x, y) = map(&r.residue.point, &r.y_coord);
Some(Point::Affine(x, y))
}
};
(e, Box::new(f))
}
4 => {
if divisor
.iter()
.any(|r| r.residue.at_infinity && r.residue.value != 0)
{
return None;
}
if let Some(root) = first_rational_root(&a) {
let (e, map) = weierstrass_from_quartic(&a, &root)?;
let f = move |r: &PlacedResidue| -> Option<Point> {
if r.residue.at_infinity || r.residue.point == root {
None
} else {
let (x, y) = map(&r.residue.point, &r.y_coord);
Some(Point::Affine(x, y))
}
};
(e, Box::new(f))
} else {
let (x0, y0) = first_rational_point(&a)?;
if divisor
.iter()
.any(|r| !r.residue.at_infinity && r.residue.point == x0)
{
return None;
}
let m = quartic_point_model(&a, &x0, &y0)?;
let (e, _) = short_weierstrass(&m.c)?;
let c3 = m.c[3].clone();
let c2 = m.c.get(2).cloned().unwrap_or_else(|| Rational::from(0));
let f = move |r: &PlacedResidue| -> Option<Point> {
if r.residue.at_infinity {
return None; }
let (z, w) = m.zw(&r.residue.point, &r.y_coord)?;
let big_x = c3.clone() * &z + c2.clone() / Rational::from(3);
let big_y = c3.clone() * &w;
Some(Point::Affine(big_x, big_y))
};
(e, Box::new(f))
}
}
_ => return None,
};
let mut l = Integer::from(1);
for r in divisor {
l = l.lcm(r.residue.value.denom());
}
let int_coeffs: Vec<Integer> = divisor
.iter()
.map(|r| {
(r.residue.value.clone() * Rational::from(l.clone()))
.numer()
.clone()
})
.collect();
let mut g = Integer::from(0);
for c in &int_coeffs {
g = g.gcd(c);
}
if g == 0 {
return Some(FindOrder::Principal { order: 1 }); }
let mut s = Point::Infinity;
for (r, c) in divisor.iter().zip(&int_coeffs) {
let Some(p) = mapper(r) else {
continue; };
if !e.contains(&p) {
return None; }
let coeff = (c.clone() / &g).to_i64()?;
let term = if coeff >= 0 {
e.mul(coeff as u64, &p)
} else {
e.mul((-coeff) as u64, &e.neg(&p))
};
s = e.add(&s, &term);
}
Some(match e.order(&s) {
Some(order) => FindOrder::Principal { order },
None => FindOrder::NonElementary,
})
}
pub(super) fn first_rational_point(q: &QPoly) -> Option<(Rational, Rational)> {
let q = trim(q.clone());
if degree(&q) != 4 {
return None;
}
let evalq =
|x: &Rational| -> Rational { q.iter().rev().fold(Rational::from(0), |acc, c| acc * x + c) };
let sqrt_rat = |v: &Rational| -> Option<Rational> {
if *v < 0 {
return None;
}
let n = v.numer().clone();
let d = v.denom().clone();
let ns = n.clone().sqrt();
let ds = d.clone().sqrt();
if Integer::from(&ns * &ns) == n && Integer::from(&ds * &ds) == d {
Some(Rational::from((ns, ds)))
} else {
None
}
};
for dn in 1..=4i64 {
for nn in -8..=8i64 {
let x0 = Rational::from((nn, dn));
let v = evalq(&x0);
if v == 0 {
continue; }
if let Some(y0) = sqrt_rat(&v) {
return Some((x0, y0));
}
}
}
None
}
pub(super) fn first_rational_root(p: &QPoly) -> Option<Rational> {
let p = trim(p.clone());
if degree(&p) < 1 {
return None;
}
if p[0] == 0 {
return Some(Rational::from(0));
}
let mut den_lcm = Integer::from(1);
for c in &p {
den_lcm = den_lcm.lcm(c.denom());
}
let ints: Vec<Integer> = p
.iter()
.map(|c| {
(c.clone() * Rational::from(den_lcm.clone()))
.numer()
.clone()
})
.collect();
let a0 = ints[0].clone().abs();
let an = ints[ints.len() - 1].clone().abs();
for pn in divisors(&a0) {
for qn in &divisors(&an) {
for sign in [1i32, -1] {
let cand = Rational::from((Integer::from(sign) * pn.clone(), qn.clone()));
let mut acc = Rational::from(0);
for c in ints.iter().rev() {
acc = acc * &cand + Rational::from(c.clone());
}
if acc == 0 {
return Some(cand);
}
}
}
}
None
}
fn divisors(n: &Integer) -> Vec<Integer> {
let n = n.clone().abs();
if n == 0 {
return vec![Integer::from(1)];
}
let mut ds = Vec::new();
let mut d = Integer::from(1);
while Integer::from(&d * &d) <= n {
if n.is_divisible(&d) {
ds.push(d.clone());
let o = n.clone() / &d;
if o != d {
ds.push(o);
}
}
d += 1;
}
ds
}
fn gcd_i64(mut a: i64, mut b: i64) -> i64 {
a = a.abs();
b = b.abs();
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
#[cfg(test)]
mod tests {
use super::super::super::risch::alg_field::RatFn;
use super::super::residues::residue_divisor;
use super::*;
use rug::Rational;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
#[test]
fn genus_values() {
assert_eq!(genus(2, &qp(&[0, 1])), Some(0)); assert_eq!(genus(2, &qp(&[1, 0, 0, 1])), Some(1)); assert_eq!(genus(2, &qp(&[0, 1, 0, 0, 1])), Some(1)); assert_eq!(genus(2, &qp(&[1, 0, 0, 0, 0, 1])), Some(2)); assert_eq!(genus(3, &qp(&[0, 1])), Some(0)); assert_eq!(genus(3, &qp(&[1, 0, 1])), Some(1)); assert_eq!(genus(2, &qp(&[0, 0, 1])), None); }
#[test]
fn genus0_principal_order_one() {
let a = qp(&[0, 1]);
let h = vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[0, -1, 1]))]; let div = residue_divisor(2, &a, &h);
assert_eq!(find_order(2, &a, &div), FindOrder::Principal { order: 1 });
}
#[test]
fn empty_divisor_principal() {
let a = qp(&[0, 1]);
let h = vec![RatFn::int(0), RatFn::new(qp(&[1]), qp(&[0, 1]))]; let div = residue_divisor(2, &a, &h);
assert!(matches!(
find_order(2, &a, &div),
FindOrder::Principal { order: 1 }
));
}
fn place(point: i64, y: i64, value: i64, inf: bool, ram: u64) -> PlacedResidue {
PlacedResidue {
residue: Residue {
point: Rational::from(point),
at_infinity: inf,
sheet: 0,
ramification: ram,
value: Rational::from(value),
},
y_coord: Rational::from(y),
}
}
#[test]
fn genus1_order_two() {
let a = qp(&[1, 0, 0, 1]);
let div = [place(-1, 0, 1, false, 2), place(0, 0, -1, true, 1)];
assert_eq!(
find_order_placed(2, &a, &div),
FindOrder::Principal { order: 2 }
);
}
#[test]
fn genus1_order_six() {
let a = qp(&[1, 0, 0, 1]);
let div = [place(2, 3, 1, false, 1), place(0, 0, -1, true, 1)];
assert_eq!(
find_order_placed(2, &a, &div),
FindOrder::Principal { order: 6 }
);
}
#[test]
fn genus1_non_elementary() {
let a = qp(&[-2, 0, 0, 1]);
assert_eq!(genus(2, &a), Some(1));
let div = [place(3, 5, 1, false, 1), place(0, 0, -1, true, 1)];
assert_eq!(find_order_placed(2, &a, &div), FindOrder::NonElementary);
}
#[test]
fn genus1_incomplete_not_decided() {
let a = qp(&[1, 0, 0, 1]);
let div = [place(-1, 0, 1, false, 2)]; assert_eq!(find_order_placed(2, &a, &div), FindOrder::NotDecided);
}
#[test]
fn genus2_non_torsion_via_dispatch() {
let a = qp(&[1, 1, 0, 0, 0, 1]);
assert_eq!(genus(2, &a), Some(2));
let div = [place(0, 1, 1, false, 1), place(0, 0, -1, true, 1)];
assert_eq!(find_order_placed(2, &a, &div), FindOrder::NonElementary);
}
#[test]
fn genus2_torsion_principal_via_dispatch() {
let a = qp(&[0, -1, 0, 0, 0, 1]);
assert_eq!(genus(2, &a), Some(2));
let div = [place(0, 0, 1, false, 2), place(1, 0, -1, false, 2)];
assert_eq!(
find_order_placed(2, &a, &div),
FindOrder::Principal { order: 2 }
);
}
#[test]
fn genus2_even_degree_not_decided() {
let a = qp(&[1, 0, 0, 0, 0, 0, 1]);
assert_eq!(genus(2, &a), Some(2));
let div = [place(0, 1, 1, false, 1), place(0, -1, -1, false, 1)];
assert_eq!(find_order_placed(2, &a, &div), FindOrder::NotDecided);
}
#[test]
fn genus1_quartic_order_two() {
let a = qp(&[4, 0, -5, 0, 1]);
assert_eq!(genus(2, &a), Some(1));
let div = [place(1, 0, 1, false, 2), place(2, 0, -1, false, 2)];
assert_eq!(
find_order_placed(2, &a, &div),
FindOrder::Principal { order: 2 }
);
}
}