use crate::scalar::{
mul_mod_u128, Fp, Fpn, Laurent, Nimber, Rational, Scalar, Surcomplex, Surreal,
};
use std::cmp::Ordering;
pub trait ExactRoots: Scalar {
fn is_square(&self) -> bool;
fn sqrt(&self) -> Option<Self>;
}
pub trait SeriesRoots: Scalar {
fn sqrt_to_terms(&self, n: usize) -> Option<Self>;
fn nth_root_to_terms(&self, k: u128, n: usize) -> Option<Self>;
fn inv_to_terms(&self, n: usize) -> Option<Self>;
}
pub trait Ordered: Scalar {
fn sign(&self) -> Ordering;
}
fn mod_pow(mut base: u128, mut e: u128, m: u128) -> u128 {
let mut acc = 1u128 % m;
base %= m;
while e > 0 {
if e & 1 == 1 {
acc = mul_mod_u128(acc, base, m);
}
base = mul_mod_u128(base, base, m);
e >>= 1;
}
acc
}
pub(crate) fn fp_is_square(a: u128, p: u128) -> bool {
let a = a % p;
a == 0 || mod_pow(a, (p - 1) / 2, p) == 1
}
pub(crate) fn fp_sqrt(a: u128, p: u128) -> Option<u128> {
let a = a % p;
if a == 0 {
return Some(0);
}
if mod_pow(a, (p - 1) / 2, p) != 1 {
return None; }
if p % 4 == 3 {
return Some(mod_pow(a, (p + 1) / 4, p)); }
let (mut q, mut s) = (p - 1, 0u128);
while q % 2 == 0 {
q /= 2;
s += 1;
}
let mut z = 2u128;
while mod_pow(z, (p - 1) / 2, p) != p - 1 {
z += 1;
}
let mut m = s;
let mut c = mod_pow(z, q, p);
let mut t = mod_pow(a, q, p);
let mut r = mod_pow(a, q.div_ceil(2), p);
loop {
if t == 1 {
return Some(r);
}
let mut i = 1u128;
let mut t2 = mul_mod_u128(t, t, p);
while t2 != 1 {
t2 = mul_mod_u128(t2, t2, p);
i += 1;
}
let b = mod_pow(c, 1u128 << (m - i - 1), p);
m = i;
c = mul_mod_u128(b, b, p);
t = mul_mod_u128(t, c, p);
r = mul_mod_u128(r, b, p);
}
}
pub(crate) fn fq_sqrt<const P: u128, const N: usize>(a: Fpn<P, N>) -> Option<Fpn<P, N>> {
if a.is_zero() {
return Some(Fpn::zero());
}
if !a.is_square() {
return None;
}
let one = Fpn::<P, N>::one();
let (mut qodd, mut s) = (Fpn::<P, N>::field_order() - 1, 0u128);
while qodd % 2 == 0 {
qodd /= 2;
s += 1;
}
let z = Fpn::<P, N>::primitive_element(); let mut m = s;
let mut c = z ^ qodd;
let mut t = a ^ qodd;
let mut r = a ^ qodd.div_ceil(2);
loop {
if t == one {
return Some(r);
}
let mut i = 1u128;
let mut t2 = t.mul(&t);
while t2 != one {
t2 = t2.mul(&t2);
i += 1;
}
let b = c ^ (1u128 << (m - i - 1));
m = i;
c = b.mul(&b);
t = t.mul(&c);
r = r.mul(&b);
}
}
impl ExactRoots for Rational {
fn is_square(&self) -> bool {
self.sqrt().is_some()
}
fn sqrt(&self) -> Option<Self> {
Rational::sqrt(self)
}
}
impl ExactRoots for Nimber {
fn is_square(&self) -> bool {
true }
fn sqrt(&self) -> Option<Self> {
Some(Nimber(crate::scalar::nim_sqrt(self.0)))
}
}
impl<const P: u128> ExactRoots for Fp<P> {
fn is_square(&self) -> bool {
if P == 2 {
return true; }
fp_is_square(self.value(), P)
}
fn sqrt(&self) -> Option<Self> {
if self.value() == 0 {
return Some(Fp::zero());
}
if P == 2 {
return Some(*self); }
fp_sqrt(self.value(), P).map(Fp::from_u128)
}
}
impl<const P: u128, const N: usize> ExactRoots for Fpn<P, N> {
fn is_square(&self) -> bool {
Fpn::is_square(self)
}
fn sqrt(&self) -> Option<Self> {
fq_sqrt(*self)
}
}
impl ExactRoots for Surreal {
fn is_square(&self) -> bool {
ExactRoots::sqrt(self).is_some()
}
fn sqrt(&self) -> Option<Self> {
if self.is_zero() {
return Some(Surreal::zero());
}
if self.sign() != Ordering::Greater {
return None;
}
let max_terms = 12;
for n in 1..=max_terms {
let root = self.sqrt_to_terms(n)?;
if root.mul(&root) == *self {
return Some(root);
}
}
None
}
}
impl<S: ExactRoots, const K: usize> ExactRoots for Laurent<S, K> {
fn is_square(&self) -> bool {
self.sqrt().is_some()
}
fn sqrt(&self) -> Option<Self> {
if self.is_zero() {
return Some(Self::zero());
}
let v = self.valuation().expect("nonzero has a valuation");
if v % 2 != 0 {
return None; }
let unit = self.unit_coeffs();
let root_unit = if S::characteristic() == 2 {
for (i, c) in unit.iter().enumerate() {
if i % 2 == 1 && !c.is_zero() {
return None;
}
}
let mut w = Vec::with_capacity(unit.len().div_ceil(2));
let mut j = 0;
while 2 * j < unit.len() {
w.push(unit[2 * j].sqrt()?);
j += 1;
}
Laurent::<S, K>::from_coeffs(w, 0)
} else {
let two_inv = S::one().add(&S::one()).inv()?; let half = Laurent::<S, K>::from_base(two_inv);
let u0 = unit[0].clone();
let seed = u0.sqrt()?; let unit_series = Laurent::<S, K>::from_coeffs(unit.to_vec(), 0);
let mut y = Laurent::<S, K>::from_base(seed);
for _ in 0..64 {
let yi = y.inv()?;
let next = unit_series.mul(&yi).add(&y).mul(&half);
if next == y {
break;
}
y = next;
}
y
};
Some(Laurent::<S, K>::from_t_power(v / 2).mul(&root_unit))
}
}
impl<R: ExactRoots + Ordered> ExactRoots for Surcomplex<R> {
fn is_square(&self) -> bool {
self.sqrt().is_some()
}
fn sqrt(&self) -> Option<Self> {
if self.is_zero() {
return Some(Surcomplex::zero());
}
let root = if self.im.is_zero() {
match self.re.sign() {
Ordering::Greater => Surcomplex::new(self.re.sqrt()?, R::zero()),
Ordering::Less => Surcomplex::new(R::zero(), self.re.neg().sqrt()?),
Ordering::Equal => Surcomplex::zero(),
}
} else {
let half = R::one().add(&R::one()).inv()?; let norm_sq = self.re.mul(&self.re).add(&self.im.mul(&self.im));
let norm = norm_sq.sqrt()?;
let a2 = norm.add(&self.re).mul(&half);
let b2 = norm.sub(&self.re).mul(&half);
let a = a2.sqrt()?;
let mut b = b2.sqrt()?;
if self.im.sign() == Ordering::Less {
b = b.neg();
}
Surcomplex::new(a, b)
};
if root.mul(&root) == *self {
Some(root)
} else {
None
}
}
}
impl SeriesRoots for Surreal {
fn sqrt_to_terms(&self, n: usize) -> Option<Self> {
Surreal::sqrt_to_terms(self, n)
}
fn nth_root_to_terms(&self, k: u128, n: usize) -> Option<Self> {
Surreal::nth_root_to_terms(self, k, n)
}
fn inv_to_terms(&self, n: usize) -> Option<Self> {
Surreal::inv_to_terms(self, n)
}
}
impl Ordered for Rational {
fn sign(&self) -> Ordering {
Rational::sign(self)
}
}
impl Ordered for Surreal {
fn sign(&self) -> Ordering {
Surreal::sign(self)
}
}
impl<S: SeriesRoots> Surcomplex<S> {
pub fn inv_to_terms(&self, n: usize) -> Option<Surcomplex<S>> {
let norm = self.re.mul(&self.re).add(&self.im.mul(&self.im));
let ninv = norm.inv_to_terms(n)?;
Some(Surcomplex::new(
self.re.mul(&ninv),
self.im.neg().mul(&ninv),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Scalar;
#[test]
fn rational_exact_roots() {
assert!(ExactRoots::is_square(&Rational::from_int(4)));
assert_eq!(Rational::from_int(4).sqrt(), Some(Rational::from_int(2)));
assert!(!ExactRoots::is_square(&Rational::from_int(2)));
assert_eq!(ExactRoots::sqrt(&Rational::from_int(2)), None);
assert_eq!(Rational::new(9, 16).sqrt(), Some(Rational::new(3, 4)));
}
#[test]
fn nimber_is_perfect_field() {
for x in [1u128, 2, 3, 255, 1 << 40] {
let n = Nimber(x);
assert!(ExactRoots::is_square(&n));
let r = ExactRoots::sqrt(&n).unwrap();
assert_eq!(r.mul(&r), n);
}
}
#[test]
fn fp_fpn_exact_roots() {
assert!(ExactRoots::is_square(&Fp::<7>::from_int(2)));
let r = ExactRoots::sqrt(&Fp::<7>::from_int(2)).unwrap();
assert_eq!(r.mul(&r), Fp::<7>::from_int(2));
assert!(!ExactRoots::is_square(&Fp::<7>::from_int(3)));
assert_eq!(
ExactRoots::sqrt(&Fp::<2>::from_int(1)),
Some(Fp::<2>::from_int(1))
);
let g = Fpn::<2, 3>::generator();
assert!(ExactRoots::is_square(&g));
let rg = ExactRoots::sqrt(&g).unwrap();
assert_eq!(rg.mul(&rg), g);
let mut squares = 0;
for code in 0..9u128 {
let x = {
let (c0, c1) = (code % 3, code / 3);
Fpn::<3, 2>::from_coeffs(&[c0, c1])
};
match ExactRoots::sqrt(&x) {
Some(r) => {
assert_eq!(r.mul(&r), x);
squares += 1;
}
None => assert!(!ExactRoots::is_square(&x)),
}
}
assert_eq!(squares, 5); }
#[test]
fn fp_square_predicate_uses_overflow_safe_modular_powers() {
let p = (1u128 << 64) + 13;
let a = p - 2;
assert!(!fp_is_square(a, p));
assert_eq!(fp_sqrt(a, p), None);
}
#[test]
fn surreal_exact_sqrt() {
let w = Surreal::omega();
let perfect = w.add(&Surreal::one()).mul(&w.add(&Surreal::one()));
assert_eq!(ExactRoots::sqrt(&perfect), Some(w.add(&Surreal::one())));
assert!(ExactRoots::is_square(&w)); assert_eq!(ExactRoots::sqrt(&Surreal::from_int(2)), None); assert_eq!(ExactRoots::sqrt(&w.neg()), None); assert_eq!(
ExactRoots::sqrt(&Surreal::from_int(4)),
Some(Surreal::from_int(2))
);
}
#[test]
fn gaussian_sqrt() {
type G = Surcomplex<Rational>;
let g =
|re: i128, im: i128| Surcomplex::new(Rational::from_int(re), Rational::from_int(im));
let r = ExactRoots::sqrt(&g(3, 4)).unwrap();
assert_eq!(r.mul(&r), g(3, 4));
assert!(ExactRoots::is_square(&g(3, 4)));
assert_eq!(ExactRoots::sqrt(&g(-1, 0)), Some(G::i()));
assert_eq!(ExactRoots::sqrt(&g(2, 0)), None); }
#[test]
fn surcomplex_surreal_sqrt() {
let w = Surcomplex::<Surreal>::new(Surreal::omega(), Surreal::zero());
let rw = ExactRoots::sqrt(&w).unwrap();
assert_eq!(rw.mul(&rw), w);
let z = Surcomplex::<Surreal>::new(Surreal::from_int(3), Surreal::from_int(4));
let rz = ExactRoots::sqrt(&z).unwrap();
assert_eq!(rz.mul(&rz), z);
}
#[test]
fn surcomplex_lazy_inverse() {
let z = Surcomplex::<Surreal>::new(Surreal::omega().add(&Surreal::one()), Surreal::one());
assert!(z.inv().is_none()); let zi = z.inv_to_terms(8).unwrap();
let prod = z.mul(&zi);
assert_eq!(prod.re.terms()[0].1, Rational::one());
assert_eq!(prod.re.terms()[0].0, Surreal::zero());
assert!(prod.im.is_zero());
}
#[test]
fn laurent_sqrt_char0() {
type L = Laurent<Rational, 8>;
let r = |n: i128| Rational::from_int(n);
let base = Laurent::<Rational, 8>::from_coeffs(vec![r(1), r(1)], 0);
let sq = base.mul(&base);
let root = ExactRoots::sqrt(&sq).unwrap();
assert_eq!(root.mul(&root), sq);
let shifted = Laurent::<Rational, 8>::from_t_power(2).mul(&sq);
let rs = ExactRoots::sqrt(&shifted).unwrap();
assert_eq!(rs.valuation(), Some(1));
assert_eq!(rs.mul(&rs), shifted);
assert_eq!(ExactRoots::sqrt(&L::t()), None);
assert_eq!(
ExactRoots::sqrt(&Laurent::<Rational, 8>::from_base(r(2))),
None
);
}
#[test]
fn laurent_sqrt_char2() {
type L = Laurent<Fpn<2, 3>, 6>;
let g = Fpn::<2, 3>::generator();
let one = Fpn::<2, 3>::one();
let base = Laurent::<Fpn<2, 3>, 6>::from_coeffs(vec![one, g], 0); let sq = base.mul(&base); let root = ExactRoots::sqrt(&sq).unwrap();
assert_eq!(root.mul(&root), sq);
let odd = Laurent::<Fpn<2, 3>, 6>::from_coeffs(vec![one, one], 0); assert_eq!(ExactRoots::sqrt(&odd), None);
assert!(!ExactRoots::is_square(&odd));
let _ = L::one();
}
#[test]
fn exact_roots_is_generic() {
fn round_trips<S: ExactRoots>(squares: &[S]) {
for s in squares {
if let Some(r) = ExactRoots::sqrt(s) {
assert_eq!(r.mul(&r), *s);
assert!(ExactRoots::is_square(s));
}
}
}
round_trips(&[Rational::from_int(9), Rational::from_int(2)]);
round_trips(&[Nimber(7), Nimber(255)]);
round_trips(&[Fp::<11>::from_int(3), Fp::<11>::from_int(5)]);
}
}