use crate::scalar::{mod_inverse_u128, Fp, Scalar};
use std::fmt;
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
pub struct Zp<const P: u128, const K: u128>(pub u128);
impl<const P: u128, const K: u128> Zp<P, K> {
pub fn assert_supported_params() {
assert!(
Fp::<P>::modulus_is_prime() && K > 0,
"Zp<P,K> needs prime P and positive precision K, got P={P}, K={K}"
);
let mut acc = 1u128;
for _ in 0..K {
acc = acc.checked_mul(P).expect("Zp modulus exceeds u128");
assert!(
acc <= i128::MAX as u128,
"Zp<P,K> modulus must fit i128-backed embeddings, got P={P}, K={K}"
);
}
}
pub fn modulus() -> u128 {
Self::assert_supported_params();
let mut acc = 1u128;
for _ in 0..K {
acc = acc.checked_mul(P).expect("Zp modulus exceeds u128");
}
acc
}
pub fn valuation(&self) -> u128 {
Self::assert_supported_params();
if self.0 == 0 {
return K;
}
let mut n = self.0;
let mut v = 0;
while n.is_multiple_of(P) {
n /= P;
v += 1;
}
v
}
pub fn is_unit(&self) -> bool {
Self::assert_supported_params();
!self.0.is_multiple_of(P)
}
}
impl<const P: u128, const K: u128> fmt::Display for Zp<P, K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} (mod {}^{})", self.0, P, K)
}
}
impl<const P: u128, const K: u128> fmt::Debug for Zp<P, K> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<const P: u128, const K: u128> Scalar for Zp<P, K> {
fn zero() -> Self {
Self::assert_supported_params();
Zp(0)
}
fn one() -> Self {
Self::assert_supported_params();
Zp(1 % Self::modulus())
}
fn add(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
let m = Self::modulus();
Zp(self.0.checked_add(rhs.0).expect("Zp addition exceeds u128") % m)
}
fn neg(&self) -> Self {
Self::assert_supported_params();
let m = Self::modulus();
let r = self.0 % m;
if r == 0 {
Zp(0)
} else {
Zp(m - r)
}
}
fn mul(&self, rhs: &Self) -> Self {
Self::assert_supported_params();
let m = Self::modulus();
Zp(crate::scalar::mul_mod_u128(self.0, rhs.0, m))
}
fn characteristic() -> u128 {
Self::assert_supported_params();
Self::modulus()
}
fn inv(&self) -> Option<Self> {
Self::assert_supported_params();
if !self.is_unit() {
return None;
}
Some(Zp(mod_inverse_u128(self.0, Self::modulus())?))
}
fn from_int(n: i128) -> Self {
Self::assert_supported_params();
let m = Self::modulus() as i128;
Zp((((n % m) + m) % m) as u128)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::clifford::{CliffordAlgebra, Metric};
fn elems<const P: u128, const K: u128>() -> Vec<Zp<P, K>> {
(0..Zp::<P, K>::modulus()).map(Zp::<P, K>).collect()
}
fn check_ring_axioms<const P: u128, const K: u128>() {
let es = elems::<P, K>();
let zero = Zp::<P, K>::zero();
let one = Zp::<P, K>::one();
for &a in &es {
assert_eq!(a.add(&zero), a);
assert_eq!(a.add(&a.neg()), zero);
assert_eq!(a.mul(&one), a);
if a.is_unit() {
let ai = a.inv().expect("a unit must be invertible");
assert_eq!(a.mul(&ai), one);
} else {
assert!(a.inv().is_none(), "a non-unit must not invert");
}
for &b in &es {
assert_eq!(a.add(&b), b.add(&a));
assert_eq!(a.mul(&b), b.mul(&a));
for &c in &es {
assert_eq!(a.add(&b).add(&c), a.add(&b.add(&c)));
assert_eq!(a.mul(&b).mul(&c), a.mul(&b.mul(&c)));
assert_eq!(a.mul(&b.add(&c)), a.mul(&b).add(&a.mul(&c)));
}
}
}
}
#[test]
fn ring_axioms_z8_z9_z27_z16() {
check_ring_axioms::<2, 3>(); check_ring_axioms::<3, 2>(); check_ring_axioms::<3, 3>(); check_ring_axioms::<2, 4>(); }
#[test]
fn p_is_a_non_unit_the_defining_property() {
assert_eq!(Zp::<2, 3>(2).inv(), None); assert_eq!(Zp::<2, 3>(4).inv(), None);
assert_eq!(Zp::<2, 3>(0).inv(), None);
assert_eq!(Zp::<3, 3>(3).inv(), None);
assert_eq!(Zp::<2, 3>(3).inv(), Some(Zp::<2, 3>(3))); assert_eq!(Zp::<2, 3>(7).inv(), Some(Zp::<2, 3>(7))); }
#[test]
fn characteristic_is_the_modulus_not_the_prime() {
assert_eq!(Zp::<2, 3>::characteristic(), 8);
assert_eq!(Zp::<3, 3>::characteristic(), 27);
}
#[test]
fn invalid_parameters_are_rejected() {
assert!(std::panic::catch_unwind(Zp::<4, 3>::one).is_err());
assert!(std::panic::catch_unwind(Zp::<5, 0>::one).is_err());
assert!(std::panic::catch_unwind(Zp::<5, 55>::one).is_err());
}
#[test]
fn neg_reduces_out_of_range_representations_first() {
assert_eq!(Zp::<3, 2>(20).0, 20); assert_eq!(Zp::<3, 2>(20).neg().0, 7); assert_eq!(Zp::<3, 2>(9).neg(), Zp::<3, 2>(0)); }
#[test]
fn inverse_reduces_to_the_mod_p_inverse() {
let two = Zp::<3, 3>(2);
let inv = two.inv().unwrap();
assert_eq!(two.mul(&inv), Zp::<3, 3>::one());
assert_eq!(inv.0 % 3, 2);
}
#[test]
fn clifford_over_z4_runs_with_a_nonunit_scalar() {
let alg = CliffordAlgebra::new(2, Metric::diagonal(vec![Zp::<2, 2>(1), Zp::<2, 2>(2)]));
let (e0, e1) = (alg.e(0), alg.e(1));
assert_eq!(alg.mul(&e0, &e0), alg.scalar(Zp::<2, 2>(1)));
assert_eq!(alg.mul(&e1, &e1), alg.scalar(Zp::<2, 2>(2)));
let e1sq = alg.mul(&e1, &e1);
assert_eq!(alg.mul(&e1sq, &e1sq), alg.scalar(Zp::<2, 2>::zero()));
}
}