use crate::scalar::{Fpn, Scalar, WittVec};
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Qq<const P: u128, const N: usize, const F: usize> {
unit: WittVec<P, N, F>,
val: i128,
}
impl<const P: u128, const N: usize, const F: usize> Qq<P, N, F> {
pub fn assert_supported_params() {
WittVec::<P, N, F>::assert_supported_params();
}
fn normalized(mut unit: WittVec<P, N, F>, mut val: i128) -> Self {
Self::assert_supported_params();
loop {
if unit.is_zero() {
return Qq {
unit: WittVec::zero(),
val: 0,
};
}
match unit.try_divide_by_p() {
Some(d) => {
unit = d;
val += 1;
}
None => return Qq { unit, val }, }
}
}
pub fn from_int(n: i128) -> Self {
Self::assert_supported_params();
Self::normalized(WittVec::from_int(n), 0)
}
pub fn from_witt(w: WittVec<P, N, F>) -> Self {
Self::assert_supported_params();
Self::normalized(w, 0)
}
pub fn from_p_power(v: i128) -> Self {
Self::assert_supported_params();
Qq {
unit: WittVec::one(),
val: v,
}
}
pub fn valuation(&self) -> Option<i128> {
Self::assert_supported_params();
if self.unit.is_zero() {
None
} else {
Some(self.val)
}
}
pub fn unit_residue(&self) -> Option<Fpn<P, F>> {
Self::assert_supported_params();
if self.unit.is_zero() {
None
} else {
Some(self.unit.residue())
}
}
pub fn unit(&self) -> WittVec<P, N, F> {
Self::assert_supported_params();
self.unit
}
}
impl<const P: u128, const N: usize, const F: usize> fmt::Display for Qq<P, N, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.unit.is_zero() {
return write!(f, "0 (Q_{}^{})", P, F);
}
if self.val == 0 {
write!(f, "{}", self.unit)
} else {
write!(f, "{}·{}^{}", self.unit, P, self.val)
}
}
}
impl<const P: u128, const N: usize, const F: usize> fmt::Debug for Qq<P, N, F> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<const P: u128, const N: usize, const F: usize> Scalar for Qq<P, N, F> {
fn zero() -> Self {
Self::assert_supported_params();
Qq {
unit: WittVec::zero(),
val: 0,
}
}
fn one() -> Self {
Self::assert_supported_params();
Qq {
unit: WittVec::one(),
val: 0,
}
}
fn add(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
if self.unit.is_zero() {
return *rhs;
}
if rhs.unit.is_zero() {
return *self;
}
let (lo, hi) = if self.val <= rhs.val {
(self, rhs)
} else {
(rhs, self)
};
let d_i128 = hi.val - lo.val;
let shifted = if d_i128 >= N as i128 {
WittVec::zero()
} else {
let d = d_i128 as usize; let p = WittVec::<P, N, F>::from_int(P as i128);
let mut s = hi.unit;
for _ in 0..d {
s = s.mul(&p);
}
s
};
Self::normalized(lo.unit.add(&shifted), lo.val)
}
fn neg(&self) -> Self {
Self::assert_supported_params();
Qq {
unit: self.unit.neg(),
val: self.val,
}
}
fn mul(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
if self.unit.is_zero() || rhs.unit.is_zero() {
return Self::zero();
}
Qq {
unit: self.unit.mul(&rhs.unit),
val: self
.val
.checked_add(rhs.val)
.expect("Qq multiplication valuation exceeds i128"),
}
}
fn characteristic() -> u128 {
Self::assert_supported_params();
0 }
fn inv(&self) -> Option<Self> {
Self::assert_supported_params();
if self.unit.is_zero() {
return None;
}
Some(Qq {
unit: self.unit.inv().expect("a Witt unit must invert"),
val: self
.val
.checked_neg()
.expect("Qq inversion valuation negation exceeds i128"),
})
}
fn is_zero(&self) -> bool {
Self::assert_supported_params();
self.unit.is_zero()
}
fn from_int(n: i128) -> Self {
Qq::<P, N, F>::from_int(n)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scalar::Qp;
type Q2 = Qq<2, 4, 1>; type Q4 = Qq<2, 4, 2>; type Q9 = Qq<3, 3, 2>;
#[test]
fn reduces_to_qp_when_residue_degree_is_one() {
for u in (1..16u128).filter(|u| u % 2 != 0) {
for v in -2i128..=2 {
let a = Qq::<2, 4, 1>::normalized(WittVec::from_int(u as i128), v);
let b = Qp::<2, 4>::from_int(u as i128).mul(&Qp::<2, 4>::from_p_power(v));
assert_eq!(a.valuation(), b.valuation());
let ai = a.inv().unwrap();
assert_eq!(ai.valuation(), b.inv().unwrap().valuation());
assert_eq!(a.mul(&ai), Q2::one());
}
}
}
#[test]
fn one_over_p_exists_and_is_a_field() {
let p = Q4::from_int(2);
let pinv = p.inv().unwrap();
assert_eq!(pinv, Q4::from_p_power(-1));
assert_eq!(p.mul(&pinv), Q4::one());
assert_eq!(pinv.valuation(), Some(-1));
assert_eq!(Q4::zero().inv(), None);
}
#[test]
fn valuation_is_additive_under_multiplication() {
let a = Q4::from_int(12); let b = Q4::from_int(20); assert_eq!(a.valuation(), Some(2));
assert_eq!(b.valuation(), Some(2));
assert_eq!(a.mul(&b).valuation(), Some(4));
assert_eq!(a.mul(&Q4::from_p_power(-1)).valuation(), Some(1));
}
#[test]
fn residue_degree_two_has_genuine_fq_residues() {
let g = WittVec::<2, 4, 2>([0, 1]); let x = Q4::from_witt(g);
assert_eq!(x.valuation(), Some(0));
let xi = x.inv().unwrap();
assert_eq!(x.mul(&xi), Q4::one());
assert_eq!(
xi.unit_residue().unwrap(),
g.residue().inv().unwrap(),
"residue map commutes with inversion"
);
}
#[test]
fn characteristic_is_zero_not_the_modulus() {
assert_eq!(Q4::characteristic(), 0);
assert_eq!(Q9::characteristic(), 0);
assert_eq!(WittVec::<2, 4, 2>::characteristic(), 16);
}
#[test]
fn multiplicative_group_is_exact() {
let units: Vec<Q9> = [[1u128, 0], [0, 1], [1, 1], [2, 1], [1, 2]]
.iter()
.flat_map(|c| (-1i128..=1).map(move |v| Qq::<3, 3, 2>::normalized(WittVec(*c), v)))
.collect();
let one = Q9::one();
for a in &units {
assert_eq!(a.mul(&one), *a);
assert_eq!(a.mul(&a.inv().unwrap()), one);
for b in &units {
assert_eq!(a.mul(b), b.mul(a));
for c in &units {
assert_eq!(a.mul(b).mul(c), a.mul(&b.mul(c)));
}
}
}
}
#[test]
fn invalid_parameters_are_rejected() {
assert!(std::panic::catch_unwind(Qq::<4, 3, 1>::one).is_err()); assert!(std::panic::catch_unwind(Qq::<2, 0, 1>::one).is_err()); assert!(std::panic::catch_unwind(Qq::<2, 3, 0>::one).is_err()); }
}