use rug::{Integer, Rational};
use super::super::risch::number_field::KElem;
use super::super::risch::poly_rde::QPoly;
use super::find_order::{find_order_placed, FindOrder};
use super::jacobian_torsion::{find_order_genus_ge2_alg, AlgPlace};
use super::residues::{PlacedResidue, Residue};
pub(crate) fn qbasis_decompose(residues: &[KElem], dim: usize) -> (usize, Vec<Vec<Rational>>) {
if dim == 0 || residues.is_empty() {
return (0, residues.iter().map(|_| Vec::new()).collect());
}
let vecs: Vec<Vec<Rational>> = residues
.iter()
.map(|r| {
(0..dim)
.map(|j| r.get(j).cloned().unwrap_or_else(|| Rational::from(0)))
.collect()
})
.collect();
let mut m = vecs.clone();
let nrows = m.len();
let mut pivots: Vec<usize> = Vec::new();
let mut row = 0usize;
for col in 0..dim {
if row >= nrows {
break;
}
let Some(sel) = (row..nrows).find(|&r| m[r][col] != 0) else {
continue;
};
m.swap(row, sel);
let piv = m[row][col].clone();
for v in m[row].iter_mut() {
*v /= ϖ
}
let pr = m[row].clone();
for (r, row_vec) in m.iter_mut().enumerate().take(nrows) {
if r != row && row_vec[col] != 0 {
let f = row_vec[col].clone();
for (dst, pv) in row_vec.iter_mut().zip(pr.iter()) {
*dst -= f.clone() * pv;
}
}
}
pivots.push(col);
row += 1;
}
let coords = vecs
.iter()
.map(|v| pivots.iter().map(|&p| v[p].clone()).collect())
.collect();
(pivots.len(), coords)
}
pub(crate) fn trager_log_criterion(
n: usize,
a: &QPoly,
places: &[PlacedResidue],
residues: &[KElem],
dim: usize,
) -> FindOrder {
if places.len() != residues.len() || places.is_empty() {
return FindOrder::NotDecided;
}
let (ncomp, coords) = qbasis_decompose(residues, dim);
if ncomp == 0 {
return FindOrder::Principal { order: 1 };
}
let mut lcm_order: u32 = 1;
for k in 0..ncomp {
let div_k: Vec<PlacedResidue> = places
.iter()
.zip(&coords)
.map(|(pl, c)| PlacedResidue {
residue: Residue {
point: pl.residue.point.clone(),
at_infinity: pl.residue.at_infinity,
sheet: pl.residue.sheet,
ramification: pl.residue.ramification,
value: c[k].clone(),
},
y_coord: pl.y_coord.clone(),
})
.collect();
match find_order_placed(n, a, &div_k) {
FindOrder::NonElementary => return FindOrder::NonElementary,
FindOrder::Principal { order } => lcm_order = lcm_u32(lcm_order, order),
FindOrder::NotDecided => return FindOrder::NotDecided,
}
}
FindOrder::Principal { order: lcm_order }
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn trager_log_criterion_alg(
n: usize,
a: &QPoly,
rat_places: &[PlacedResidue],
rat_residues: &[KElem],
alg_places: &[AlgPlace],
alg_residues: &[KElem],
dim: usize,
) -> FindOrder {
if rat_places.len() != rat_residues.len() || alg_places.len() != alg_residues.len() {
return FindOrder::NotDecided;
}
let nr = rat_residues.len();
let all_res: Vec<KElem> = rat_residues.iter().chain(alg_residues).cloned().collect();
if all_res.is_empty() {
return FindOrder::NotDecided;
}
let (ncomp, coords) = qbasis_decompose(&all_res, dim);
if ncomp == 0 {
return FindOrder::Principal { order: 1 };
}
let mut lcm_order: u32 = 1;
for k in 0..ncomp {
let mut l = Integer::from(1);
for c in &coords {
l = l.lcm(c[k].denom());
}
let rat_div: Vec<PlacedResidue> = rat_places
.iter()
.zip(&coords[..nr])
.map(|(pl, c)| PlacedResidue {
residue: Residue {
point: pl.residue.point.clone(),
at_infinity: pl.residue.at_infinity,
sheet: pl.residue.sheet,
ramification: pl.residue.ramification,
value: c[k].clone() * Rational::from(l.clone()),
},
y_coord: pl.y_coord.clone(),
})
.collect();
let alg_div: Vec<AlgPlace> = alg_places
.iter()
.zip(&coords[nr..])
.map(|(ap, c)| AlgPlace {
minpoly: ap.minpoly.clone(),
x_coord: ap.x_coord.clone(),
y_coord: ap.y_coord.clone(),
coeff: (c[k].clone() * Rational::from(l.clone())).numer().clone(),
orbit: ap.orbit,
})
.collect();
match find_order_genus_ge2_alg(n, a, &rat_div, &alg_div) {
FindOrder::NonElementary => return FindOrder::NonElementary,
FindOrder::Principal { order } => lcm_order = lcm_u32(lcm_order, order),
FindOrder::NotDecided => return FindOrder::NotDecided,
}
}
FindOrder::Principal { order: lcm_order }
}
fn lcm_u32(a: u32, b: u32) -> u32 {
if a == 0 || b == 0 {
return 0;
}
a / gcd_u32(a, b) * b
}
fn gcd_u32(mut a: u32, mut b: u32) -> u32 {
while b != 0 {
let t = b;
b = a % b;
a = t;
}
a
}
#[cfg(test)]
mod tests {
use super::*;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
fn ke(cs: &[i64]) -> KElem {
cs.iter().map(|&c| Rational::from(c)).collect()
}
fn place(point: i64, y: i64, inf: bool) -> PlacedResidue {
PlacedResidue {
residue: Residue {
point: Rational::from(point),
at_infinity: inf,
sheet: 0,
ramification: 1,
value: Rational::from(0),
},
y_coord: Rational::from(y),
}
}
#[test]
fn decompose_quadratic_field() {
let res = [ke(&[0, 1]), ke(&[0, -1]), ke(&[3, 0])];
let (ncomp, coords) = qbasis_decompose(&res, 2);
assert_eq!(ncomp, 2); assert_eq!(coords[0], vec![Rational::from(0), Rational::from(1)]); assert_eq!(coords[1], vec![Rational::from(0), Rational::from(-1)]); assert_eq!(coords[2], vec![Rational::from(3), Rational::from(0)]); }
#[test]
fn decompose_one_dimensional() {
let res = [ke(&[0, 1]), ke(&[0, -1])];
let (ncomp, coords) = qbasis_decompose(&res, 2);
assert_eq!(ncomp, 1);
assert_eq!(coords[0], vec![Rational::from(1)]);
assert_eq!(coords[1], vec![Rational::from(-1)]);
}
#[test]
fn criterion_nonelementary_via_component() {
let a = qp(&[-2, 0, 0, 1]); let places = [place(3, 5, false), place(0, 0, true)];
let residues = [ke(&[0, 1]), ke(&[0, -1])]; assert_eq!(
trager_log_criterion(2, &a, &places, &residues, 2),
FindOrder::NonElementary
);
}
#[test]
fn criterion_principal_via_component() {
let a = qp(&[1, 0, 0, 1]); let places = [place(-1, 0, false), place(0, 0, true)];
let residues = [ke(&[0, 1]), ke(&[0, -1])]; assert_eq!(
trager_log_criterion(2, &a, &places, &residues, 2),
FindOrder::Principal { order: 2 }
);
}
#[test]
fn criterion_alg_place_principal() {
let a = qp(&[-2, 0, 1, -2, 0, 1]); let rat_places = [place(-1, 0, false)]; let rat_residues = [ke(&[0, -1])]; let alg_places = [AlgPlace {
minpoly: qp(&[-2, 0, 1]), x_coord: qp(&[0, 1]), y_coord: Vec::new(), coeff: Integer::from(0), orbit: true, }];
let alg_residues = [ke(&[0, 1])]; let res = trager_log_criterion_alg(
2,
&a,
&rat_places,
&rat_residues,
&alg_places,
&alg_residues,
2,
);
assert!(matches!(res, FindOrder::Principal { .. }), "got {res:?}");
}
#[test]
fn criterion_two_components_principal() {
let a = qp(&[1, 0, 0, 1]); let places = [place(2, 3, false), place(-1, 0, false), place(0, 0, true)];
let residues = [ke(&[1, 0]), ke(&[0, 1]), ke(&[-1, -1])]; assert_eq!(
trager_log_criterion(2, &a, &places, &residues, 2),
FindOrder::Principal { order: 6 }
);
}
}