use rug::Rational;
use super::super::risch::poly_rde::{degree, poly_add, poly_mul, poly_scale, trim, QPoly};
use super::super::risch::rational_rde::poly_divrem;
type Tow = (QPoly, QPoly);
fn qmod(a: &QPoly, q: &QPoly) -> QPoly {
trim(poly_divrem(a, q).1)
}
fn tmul(u: &Tow, v: &Tow, q: &QPoly, c: &QPoly) -> Tow {
let p0 = qmod(
&poly_add(&poly_mul(&u.0, &v.0), &poly_mul(c, &poly_mul(&u.1, &v.1))),
q,
);
let p1 = qmod(&poly_add(&poly_mul(&u.0, &v.1), &poly_mul(&u.1, &v.0)), q);
(p0, p1)
}
fn tflat(u: &Tow, d: usize) -> Vec<Rational> {
let mut v = vec![Rational::from(0); 2 * d];
for i in 0..d {
if let Some(c) = u.0.get(i) {
v[i] = c.clone();
}
if let Some(c) = u.1.get(i) {
v[d + i] = c.clone();
}
}
v
}
pub(crate) fn primitive_element(q: &QPoly, c: &QPoly) -> Option<(QPoly, QPoly, QPoly)> {
let d = degree(q);
if d < 2 {
return None;
}
let d = d as usize;
let n = 2 * d;
let one: Tow = (vec![Rational::from(1)], vec![]);
let alpha: Tow = (vec![Rational::from(0), Rational::from(1)], vec![]); let w: Tow = (vec![], vec![Rational::from(1)]);
for lambda in 0..=8i64 {
let theta: Tow = (
qmod(&poly_scale(&alpha.0, &Rational::from(lambda)), q),
vec![Rational::from(1)],
);
let mut powers: Vec<Tow> = vec![one.clone()];
let mut cur = one.clone();
for _ in 0..n {
cur = tmul(&cur, &theta, q, c);
powers.push(cur.clone());
}
let cols: Vec<Vec<Rational>> = (0..n).map(|k| tflat(&powers[k], d)).collect();
let Some(m) = solve_columns(&cols, &tflat(&powers[n], d), n) else {
continue; };
let mut mm = vec![Rational::from(0); n + 1];
mm[n] = Rational::from(1);
for (k, mk) in m.iter().enumerate() {
mm[k] = -mk.clone();
}
let a_in = solve_columns(&cols, &tflat(&alpha, d), n)?;
let w_in = solve_columns(&cols, &tflat(&w, d), n)?;
return Some((trim(mm), trim(a_in), trim(w_in)));
}
None
}
fn solve_columns(cols: &[Vec<Rational>], rhs: &[Rational], n: usize) -> Option<Vec<Rational>> {
let mut a: Vec<Vec<Rational>> = (0..n)
.map(|i| {
let mut row: Vec<Rational> = (0..n).map(|j| cols[j][i].clone()).collect();
row.push(rhs[i].clone());
row
})
.collect();
for col in 0..n {
let piv = (col..n).find(|&r| a[r][col] != 0)?;
a.swap(col, piv);
let inv = Rational::from(1) / a[col][col].clone();
for v in a[col].iter_mut() {
*v *= &inv;
}
for r in 0..n {
if r != col && a[r][col] != 0 {
let f = a[r][col].clone();
#[allow(clippy::needless_range_loop)]
for k in col..=n {
let s = f.clone() * &a[col][k];
a[r][k] -= s;
}
}
}
}
Some((0..n).map(|i| a[i][n].clone()).collect())
}
pub(crate) fn compose_mod(f: &QPoly, g: &QPoly, m: &QPoly) -> QPoly {
let mut acc: QPoly = Vec::new();
for fi in f.iter().rev() {
acc = qmod(&poly_mul(&acc, g), m);
if *fi != 0 {
acc = poly_add(&acc, &vec![fi.clone()]);
}
}
qmod(&acc, m)
}
fn rat_sqrt(r: &Rational) -> Option<Rational> {
if *r < 0 {
return None;
}
let (n, d) = (r.numer().clone(), r.denom().clone());
let (ns, ds) = (n.clone().sqrt(), d.clone().sqrt());
if rug::Integer::from(&ns * &ns) == n && rug::Integer::from(&ds * &ds) == d {
Some(Rational::from((ns, ds)))
} else {
None
}
}
fn sqrt_in_quad(u: &Rational, v: &Rational, m: &Rational) -> Option<[Rational; 2]> {
let zero = Rational::from(0);
if *v == 0 {
if let Some(a) = rat_sqrt(u) {
return Some([a, zero]);
}
if let Some(b) = rat_sqrt(&(u.clone() / m)) {
return Some([zero, b]);
}
return None;
}
let disc = u.clone() * u - v.clone() * v * m; let s = rat_sqrt(&disc)?;
for sg in [Rational::from(1), Rational::from(-1)] {
let a2 = (u.clone() + sg * &s) / Rational::from(2);
if let Some(a) = rat_sqrt(&a2) {
if a != 0 {
let b = v.clone() / (Rational::from(2) * &a);
return Some([a, b]);
}
}
}
None
}
pub(crate) fn galois_quartic(q: &QPoly, c: &QPoly) -> Option<(QPoly, QPoly, QPoly, Vec<QPoly>)> {
if degree(q) != 2 || q.get(1).map(|x| *x != 0).unwrap_or(false) {
return None; }
let m = -q[0].clone(); let (mm, a_in, w_in) = primitive_element(q, c)?;
if degree(&mm) != 4 {
return None;
}
let u = c.first().cloned().unwrap_or_else(|| Rational::from(0));
let v = c.get(1).cloned().unwrap_or_else(|| Rational::from(0));
let nrm = u.clone() * &u - v.clone() * &v * &m; if nrm == 0 {
return None;
}
let cbar = [u.clone(), -v.clone()];
let cbar_over_c = [
(u.clone() * &u + v.clone() * &v * &m) / &nrm,
Rational::from(-2) * &u * &v / &nrm,
];
let w_alpha: QPoly = if let Some(g) = sqrt_in_quad(&cbar_over_c[0], &cbar_over_c[1], &m) {
let g_at = poly_add(
&vec![g[0].clone()],
&poly_scale(&a_in, &g[1]), );
qmod(&poly_mul(&g_at, &w_in), &mm)
} else if let Some(h) = sqrt_in_quad(&cbar[0], &cbar[1], &m) {
poly_add(&vec![h[0].clone()], &poly_scale(&a_in, &h[1]))
} else {
return None; };
let theta = vec![Rational::from(0), Rational::from(1)];
let two_w = poly_scale(&w_in, &Rational::from(2));
let sigma_w = qmod(&poly_sub_q(&theta, &two_w), &mm);
let sigma_a = qmod(&poly_sub_q(&poly_add(&w_alpha, &w_in), &theta), &mm);
let sigma_aw = qmod(&poly_sub_q(&poly_sub_q(&w_in, &w_alpha), &theta), &mm);
let autos = vec![theta, sigma_w, sigma_a, sigma_aw];
for (i, pi) in autos.iter().enumerate() {
if degree(&eval_mod(&mm, pi, &mm)) >= 0 {
return None; }
for pj in autos.iter().take(i) {
if trim(pi.clone()) == trim(pj.clone()) {
return None; }
}
}
Some((mm, a_in, w_in, autos))
}
fn eval_mod(f: &QPoly, beta: &QPoly, m: &QPoly) -> QPoly {
compose_mod(f, beta, m)
}
pub(crate) fn quartic_closure(q: &QPoly, c: &QPoly) -> Option<(QPoly, QPoly, QPoly, QPoly)> {
use super::super::risch::number_field::mod_inverse;
if degree(q) != 2 || q.get(1).map(|x| *x != 0).unwrap_or(false) {
return None;
}
let m = -q[0].clone();
let (mm, a_in, w_in) = primitive_element(q, c)?; if degree(&mm) != 4 {
return None;
}
let u = c.first().cloned().unwrap_or_else(|| Rational::from(0));
let vc = c.get(1).cloned().unwrap_or_else(|| Rational::from(0));
let dprime = u.clone() * &u - vc.clone() * &vc * &m; if dprime == 0 {
return None;
}
let (ml, theta_in, s_in) = primitive_element(&mm, &vec![dprime])?;
if degree(&ml) != 8 {
return None; }
let alpha_l = compose_mod(&a_in, &theta_in, &ml);
let w_l = compose_mod(&w_in, &theta_in, &ml);
let w_inv_k = mod_inverse(&w_in, &mm)?; let w_inv_l = compose_mod(&w_inv_k, &theta_in, &ml);
let v_l = qmod(&poly_mul(&s_in, &w_inv_l), &ml);
let sq = |x: &QPoly| qmod(&poly_mul(x, x), &ml);
let c_at = |sgn: i64| {
poly_add(
&vec![u.clone()],
&poly_scale(&alpha_l, &(vc.clone() * Rational::from(sgn))),
)
};
let alpha2_ok = degree(&qmod(&poly_sub_q(&sq(&alpha_l), &vec![m.clone()]), &ml)) < 0;
let w2_ok = degree(&qmod(&poly_sub_q(&sq(&w_l), &c_at(1)), &ml)) < 0;
let v2_ok = degree(&qmod(&poly_sub_q(&sq(&v_l), &c_at(-1)), &ml)) < 0;
if !(alpha2_ok && w2_ok && v2_ok) {
return None;
}
Some((ml, alpha_l, w_l, v_l))
}
fn poly_sub_q(a: &QPoly, b: &QPoly) -> QPoly {
poly_add(a, &poly_scale(b, &Rational::from(-1)))
}
#[cfg(test)]
mod tests {
use super::*;
fn qp(cs: &[i64]) -> QPoly {
cs.iter().map(|&c| Rational::from(c)).collect()
}
fn check(q: &QPoly, c: &QPoly) {
let (m, a_in, w_in) = primitive_element(q, c).expect("primitive element");
let d = degree(q) as usize;
assert_eq!(degree(&m) as usize, 2 * d, "deg M = 2d");
let mut qa = QPoly::new();
let mut pw = vec![Rational::from(1)];
for qi in q {
if *qi != 0 {
qa = poly_add(&qa, &poly_scale(&pw, qi));
}
pw = qmod(&poly_mul(&pw, &a_in), &m);
}
assert!(degree(&qmod(&qa, &m)) < 0, "q(α(θ)) ≡ 0");
let mut ca = QPoly::new();
let mut pw2 = vec![Rational::from(1)];
for ci in c {
if *ci != 0 {
ca = poly_add(&ca, &poly_scale(&pw2, ci));
}
pw2 = qmod(&poly_mul(&pw2, &a_in), &m);
}
let w2 = qmod(&poly_mul(&w_in, &w_in), &m);
assert_eq!(trim(qmod(&ca, &m)), trim(w2), "w² ≡ c(α)");
}
#[test]
fn quartic_pure_radical() {
check(&qp(&[-2, 0, 1]), &qp(&[0, 1])); }
#[test]
fn quartic_nontrivial_sheet() {
check(&qp(&[-2, 0, 1]), &qp(&[1, 1])); }
#[test]
fn sextic_cubic_base() {
check(&qp(&[-2, 0, 0, 1]), &qp(&[0, 1])); }
#[test]
fn galois_quartic_multiquadratic() {
let (m, _a, _w, autos) = galois_quartic(&qp(&[-2, 0, 1]), &qp(&[3])).expect("Galois");
assert_eq!(m, qp(&[1, 0, -10, 0, 1])); assert_eq!(autos.len(), 4);
}
#[test]
fn non_galois_quartic_declines() {
assert!(galois_quartic(&qp(&[-2, 0, 1]), &qp(&[1, 5])).is_none());
}
#[test]
fn quartic_closure_non_galois() {
let q = qp(&[-2, 0, 1]);
let c = qp(&[1, 5]); let (ml, alpha, w, v) = quartic_closure(&q, &c).expect("degree-8 closure");
assert_eq!(degree(&ml), 8);
let sq = |x: &QPoly| qmod(&poly_mul(x, x), &ml);
assert_eq!(trim(sq(&alpha)), qp(&[2]));
let c_at = |sgn: i64| {
qmod(
&poly_add(&qp(&[1]), &poly_scale(&alpha, &Rational::from(5 * sgn))),
&ml,
)
};
assert_eq!(trim(sq(&w)), trim(c_at(1)));
assert_eq!(trim(sq(&v)), trim(c_at(-1)));
assert!(galois_quartic(&q, &c).is_none());
}
}