use alloc::rc::Rc;
use alloc::vec::Vec;
use core::fmt;
use crate::int::Int;
fn deg_of(a: &[Int]) -> Option<usize> {
a.iter().rposition(|c| !c.is_zero())
}
fn poly_trim(mut a: Vec<Int>) -> Vec<Int> {
while a.last().is_some_and(Int::is_zero) {
a.pop();
}
a
}
fn pad_to(mut a: Vec<Int>, len: usize) -> Vec<Int> {
a.truncate(len);
while a.len() < len {
a.push(Int::ZERO);
}
a
}
fn poly_sub(a: &[Int], b: &[Int], p: &Int) -> Vec<Int> {
let n = a.len().max(b.len());
let mut r = Vec::with_capacity(n);
for i in 0..n {
let x = a.get(i).cloned().unwrap_or(Int::ZERO);
let y = b.get(i).cloned().unwrap_or(Int::ZERO);
r.push(x.sub(&y).rem_euclid(p));
}
poly_trim(r)
}
fn poly_mul(a: &[Int], b: &[Int], p: &Int) -> Vec<Int> {
if a.is_empty() || b.is_empty() {
return Vec::new();
}
let mut r = alloc::vec![Int::ZERO; a.len() + b.len() - 1];
for (i, ai) in a.iter().enumerate() {
if ai.is_zero() {
continue;
}
for (j, bj) in b.iter().enumerate() {
r[i + j] = r[i + j].add(&ai.mul(bj));
}
}
poly_trim(r.into_iter().map(|c| c.rem_euclid(p)).collect())
}
fn poly_scale(a: &[Int], s: &Int, p: &Int) -> Vec<Int> {
poly_trim(a.iter().map(|c| c.mul(s).rem_euclid(p)).collect())
}
fn poly_divmod(a: &[Int], b: &[Int], p: &Int) -> (Vec<Int>, Vec<Int>) {
let db = deg_of(b).expect("poly_divmod: division by zero polynomial");
let inv_lc = b[db]
.modinv(p)
.expect("poly_divmod: leading coefficient must be invertible mod p");
let mut r = poly_trim(a.to_vec());
let rd = match deg_of(&r) {
Some(d) if d >= db => d,
_ => return (Vec::new(), r),
};
let mut q = alloc::vec![Int::ZERO; rd - db + 1];
while let Some(rd) = deg_of(&r) {
if rd < db {
break;
}
let d = rd - db;
let coef = r[rd].mul(&inv_lc).rem_euclid(p);
for (j, bj) in b.iter().enumerate() {
r[d + j] = r[d + j].sub(&coef.mul(bj)).rem_euclid(p);
}
q[d] = coef;
r = poly_trim(r);
}
(poly_trim(q), r)
}
fn poly_rem(a: &[Int], b: &[Int], p: &Int) -> Vec<Int> {
poly_divmod(a, b, p).1
}
fn poly_gcd(a: &[Int], b: &[Int], p: &Int) -> Vec<Int> {
let mut a = poly_trim(a.to_vec());
let mut b = poly_trim(b.to_vec());
while !b.is_empty() {
let r = poly_rem(&a, &b, p);
a = b;
b = r;
}
match deg_of(&a) {
None => a,
Some(d) => {
if a[d].is_one() {
a
} else {
let inv = a[d]
.modinv(p)
.expect("poly_gcd: leading coefficient invertible mod p");
poly_scale(&a, &inv, p)
}
}
}
}
fn poly_powmod(base: &[Int], e: &Int, modulus: &[Int], p: &Int) -> Vec<Int> {
let mut result = poly_rem(&[Int::ONE], modulus, p);
let mut b = poly_rem(base, modulus, p);
for i in 0..e.bit_len() {
if e.bit(i) {
result = poly_rem(&poly_mul(&result, &b, p), modulus, p);
}
b = poly_rem(&poly_mul(&b, &b, p), modulus, p);
}
result
}
fn frobenius_pow(f: &[Int], m: usize, p: &Int) -> Vec<Int> {
let x = alloc::vec![Int::ZERO, Int::ONE];
let mut h = poly_rem(&x, f, p);
for _ in 0..m {
h = poly_powmod(&h, p, f, p);
}
h
}
fn distinct_primes(k: usize) -> Vec<usize> {
let mut out: Vec<usize> = Vec::new();
for f in Int::from_u64(k as u64).factorize() {
let v = f.to_u64().expect("small factor fits in u64") as usize;
if out.last() != Some(&v) {
out.push(v);
}
}
out
}
fn is_irreducible(f: &[Int], p: &Int) -> bool {
let k = match deg_of(f) {
Some(d) if d >= 1 => d,
_ => return false,
};
if k == 1 {
return true; }
let x = alloc::vec![Int::ZERO, Int::ONE];
for q in distinct_primes(k) {
let h = frobenius_pow(f, k / q, p);
let diff = poly_sub(&h, &x, p);
let g = poly_gcd(&diff, f, p);
if deg_of(&g).is_some_and(|d| d >= 1) {
return false; }
}
let h = frobenius_pow(f, k, p);
poly_sub(&h, &x, p).is_empty()
}
fn poly_inv(a: &[Int], modulus: &[Int], p: &Int) -> Option<Vec<Int>> {
let a = poly_trim(a.to_vec());
if a.is_empty() {
return None;
}
let (mut r0, mut r1) = (modulus.to_vec(), a);
let (mut s0, mut s1): (Vec<Int>, Vec<Int>) = (Vec::new(), alloc::vec![Int::ONE]);
while !r1.is_empty() {
let (q, r) = poly_divmod(&r0, &r1, p);
let s = poly_sub(&s0, &poly_mul(&q, &s1, p), p);
r0 = r1;
r1 = r;
s0 = s1;
s1 = s;
}
if deg_of(&r0) != Some(0) {
return None;
}
let inv_c = r0[0].modinv(p)?;
Some(poly_rem(&poly_scale(&s0, &inv_c, p), modulus, p))
}
fn incr_base_p(digits: &mut [Int], p: &Int) -> bool {
for d in digits.iter_mut() {
*d = d.add(&Int::ONE);
if *d < *p {
return true;
}
*d = Int::ZERO;
}
false
}
struct FieldData {
p: Int,
k: usize,
modulus: Vec<Int>,
red: Vec<Vec<Int>>,
}
impl FieldData {
fn build(p: Int, k: usize, modulus: Vec<Int>) -> FieldData {
let mut red: Vec<Vec<Int>> = Vec::new();
if k >= 2 {
let mut xk = alloc::vec![Int::ZERO; k + 1];
xk[k] = Int::ONE;
let r0 = pad_to(poly_rem(&xk, &modulus, &p), k);
red.push(r0.clone());
let mut cur = r0;
for _ in 1..(k - 1) {
let top = cur[k - 1].clone();
let mut next = alloc::vec![Int::ZERO; k];
for i in (1..k).rev() {
next[i] = cur[i - 1].clone();
}
if !top.is_zero() {
for i in 0..k {
next[i].addmul(&top, &red[0][i]);
}
}
for c in next.iter_mut() {
*c = c.rem_euclid(&p);
}
red.push(next.clone());
cur = next;
}
}
FieldData { p, k, modulus, red }
}
fn mul_coeffs(&self, a: &[Int], b: &[Int]) -> Vec<Int> {
let k = self.k;
let p = &self.p;
if k == 1 {
return alloc::vec![a[0].mul(&b[0]).rem_euclid(p)];
}
let mut prod = alloc::vec![Int::ZERO; 2 * k - 1];
for (i, ai) in a.iter().enumerate() {
if ai.is_zero() {
continue;
}
for (j, bj) in b.iter().enumerate() {
prod[i + j].addmul(ai, bj);
}
}
self.reduce_raw(&prod)
}
fn reduce_raw(&self, prod: &[Int]) -> Vec<Int> {
let k = self.k;
let p = &self.p;
if k == 1 {
return alloc::vec![prod[0].rem_euclid(p)];
}
let mut acc: Vec<Int> = prod[..k].to_vec();
for (j, red_j) in self.red.iter().enumerate() {
let hi = prod[k + j].rem_euclid(p);
if hi.is_zero() {
continue;
}
for (ai, rj) in acc.iter_mut().zip(red_j.iter()) {
ai.addmul(&hi, rj);
}
}
for c in acc.iter_mut() {
*c = c.rem_euclid(p);
}
acc
}
}
#[derive(Clone)]
pub struct GaloisField {
data: Rc<FieldData>,
}
#[derive(Clone)]
pub struct GfElement {
field: Rc<FieldData>,
coeffs: Vec<Int>,
}
impl GaloisField {
pub fn new(p: Int, modulus: &[Int]) -> Option<GaloisField> {
if !p.is_prime_bpsw() {
return None;
}
let reduced: Vec<Int> = modulus.iter().map(|c| c.rem_euclid(&p)).collect();
if reduced.len() < 2 {
return None; }
let k = reduced.len() - 1;
if !reduced[k].is_one() {
return None; }
if !is_irreducible(&reduced, &p) {
return None;
}
Some(GaloisField {
data: Rc::new(FieldData::build(p, k, reduced)),
})
}
pub fn create(p: Int, k: usize) -> Option<GaloisField> {
if k == 0 || !p.is_prime_bpsw() {
return None;
}
let mut low = alloc::vec![Int::ZERO; k];
loop {
let mut modulus = low.clone();
modulus.push(Int::ONE); if is_irreducible(&modulus, &p) {
return Some(GaloisField {
data: Rc::new(FieldData::build(p, k, modulus)),
});
}
if !incr_base_p(&mut low, &p) {
return None; }
}
}
#[inline]
pub fn characteristic(&self) -> Int {
self.data.p.clone()
}
#[inline]
pub fn degree(&self) -> usize {
self.data.k
}
#[inline]
pub fn order(&self) -> Int {
self.data.p.pow(self.data.k as u32)
}
#[inline]
pub fn modulus(&self) -> &[Int] {
&self.data.modulus
}
pub fn element(&self, coeffs: &[Int]) -> GfElement {
let reduced: Vec<Int> = coeffs.iter().map(|c| c.rem_euclid(&self.data.p)).collect();
let residue = poly_rem(&reduced, &self.data.modulus, &self.data.p);
GfElement {
field: self.data.clone(),
coeffs: pad_to(residue, self.data.k),
}
}
#[inline]
pub fn zero(&self) -> GfElement {
GfElement {
field: self.data.clone(),
coeffs: alloc::vec![Int::ZERO; self.data.k],
}
}
pub fn one(&self) -> GfElement {
self.from_int(&Int::ONE)
}
pub fn generator(&self) -> GfElement {
self.element(&[Int::ZERO, Int::ONE])
}
pub fn from_int(&self, c: &Int) -> GfElement {
let mut coeffs = alloc::vec![Int::ZERO; self.data.k];
coeffs[0] = c.rem_euclid(&self.data.p);
GfElement {
field: self.data.clone(),
coeffs,
}
}
pub fn frobenius(&self, elem: &GfElement) -> GfElement {
assert!(
same_field(&self.data, &elem.field),
"GaloisField::frobenius: element from a different field"
);
elem.pow(&self.data.p)
}
pub fn primitive_element(&self) -> GfElement {
let one = self.one();
let n = self.order().sub(&Int::ONE); let mut primes: Vec<Int> = Vec::new();
for q in n.factorize() {
if primes.last() != Some(&q) {
primes.push(q);
}
}
let mut digits = alloc::vec![Int::ZERO; self.data.k];
while incr_base_p(&mut digits, &self.data.p) {
let cand = self.element(&digits);
if cand.is_zero() {
continue;
}
let is_primitive = primes.iter().all(|q| cand.pow(&n.div_exact(q)) != one);
if is_primitive {
return cand;
}
}
unreachable!("a primitive element always exists in a finite field");
}
}
fn same_field(a: &Rc<FieldData>, b: &Rc<FieldData>) -> bool {
Rc::ptr_eq(a, b) || (a.p == b.p && a.modulus == b.modulus)
}
impl GfElement {
pub fn field(&self) -> GaloisField {
GaloisField {
data: self.field.clone(),
}
}
#[inline]
pub fn to_coefficients(&self) -> &[Int] {
&self.coeffs
}
pub fn is_zero(&self) -> bool {
self.coeffs.iter().all(Int::is_zero)
}
pub fn is_one(&self) -> bool {
self.coeffs[0].is_one() && self.coeffs[1..].iter().all(Int::is_zero)
}
fn same_field(&self, other: &GfElement) {
assert!(
same_field(&self.field, &other.field),
"GfElement: operands from different fields"
);
}
#[inline]
fn wrap(&self, coeffs: Vec<Int>) -> GfElement {
GfElement {
field: self.field.clone(),
coeffs,
}
}
pub fn add(&self, rhs: &GfElement) -> GfElement {
self.same_field(rhs);
let p = &self.field.p;
let coeffs = (0..self.field.k)
.map(|i| self.coeffs[i].add(&rhs.coeffs[i]).rem_euclid(p))
.collect();
self.wrap(coeffs)
}
pub fn sub(&self, rhs: &GfElement) -> GfElement {
self.same_field(rhs);
let p = &self.field.p;
let coeffs = (0..self.field.k)
.map(|i| self.coeffs[i].sub(&rhs.coeffs[i]).rem_euclid(p))
.collect();
self.wrap(coeffs)
}
pub fn neg(&self) -> GfElement {
let p = &self.field.p;
let coeffs = self.coeffs.iter().map(|c| c.neg().rem_euclid(p)).collect();
self.wrap(coeffs)
}
pub fn mul(&self, rhs: &GfElement) -> GfElement {
self.same_field(rhs);
let coeffs = self.field.mul_coeffs(&self.coeffs, &rhs.coeffs);
self.wrap(coeffs)
}
pub fn inv(&self) -> Option<GfElement> {
let p = &self.field.p;
let u = poly_inv(&self.coeffs, &self.field.modulus, p)?;
Some(self.wrap(pad_to(u, self.field.k)))
}
pub fn div(&self, rhs: &GfElement) -> GfElement {
self.mul(
&rhs.inv()
.expect("GfElement::div: divisor is not invertible"),
)
}
pub fn pow(&self, exp: &Int) -> GfElement {
if exp.is_negative() {
return self
.inv()
.expect("GfElement::pow: base not invertible for a negative exponent")
.pow(&exp.abs());
}
let mut result = GfElement {
field: self.field.clone(),
coeffs: {
let mut c = alloc::vec![Int::ZERO; self.field.k];
c[0] = Int::ONE;
c
},
};
let mut base = self.clone();
for i in 0..exp.bit_len() {
if exp.bit(i) {
result = result.mul(&base);
}
base = base.mul(&base);
}
result
}
}
impl PartialEq for GfElement {
fn eq(&self, other: &Self) -> bool {
same_field(&self.field, &other.field) && self.coeffs == other.coeffs
}
}
impl Eq for GfElement {}
impl fmt::Display for GfElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
return f.write_str("0");
}
let mut first = true;
for (i, c) in self.coeffs.iter().enumerate().rev() {
if c.is_zero() {
continue;
}
if !first {
f.write_str(" + ")?;
}
first = false;
match i {
0 => write!(f, "{c}")?,
1 if c.is_one() => f.write_str("a")?,
1 => write!(f, "{c}·a")?,
_ if c.is_one() => write!(f, "a^{i}")?,
_ => write!(f, "{c}·a^{i}")?,
}
}
Ok(())
}
}
impl fmt::Debug for GfElement {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"GfElement({} in GF({}^{}))",
self, self.field.p, self.field.k
)
}
}
impl fmt::Debug for GaloisField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "GaloisField(GF({}^{}))", self.data.p, self.data.k)
}
}
macro_rules! gf_binop {
($tr:ident, $m:ident, $atr:ident, $am:ident) => {
impl core::ops::$tr for GfElement {
type Output = GfElement;
#[inline]
fn $m(self, rhs: GfElement) -> GfElement {
GfElement::$m(&self, &rhs)
}
}
impl core::ops::$tr<&GfElement> for GfElement {
type Output = GfElement;
#[inline]
fn $m(self, rhs: &GfElement) -> GfElement {
GfElement::$m(&self, rhs)
}
}
impl core::ops::$tr<GfElement> for &GfElement {
type Output = GfElement;
#[inline]
fn $m(self, rhs: GfElement) -> GfElement {
GfElement::$m(self, &rhs)
}
}
impl core::ops::$tr<&GfElement> for &GfElement {
type Output = GfElement;
#[inline]
fn $m(self, rhs: &GfElement) -> GfElement {
GfElement::$m(self, rhs)
}
}
impl core::ops::$atr<GfElement> for GfElement {
#[inline]
fn $am(&mut self, rhs: GfElement) {
*self = GfElement::$m(self, &rhs);
}
}
impl core::ops::$atr<&GfElement> for GfElement {
#[inline]
fn $am(&mut self, rhs: &GfElement) {
*self = GfElement::$m(self, rhs);
}
}
};
}
gf_binop!(Add, add, AddAssign, add_assign);
gf_binop!(Sub, sub, SubAssign, sub_assign);
gf_binop!(Mul, mul, MulAssign, mul_assign);
gf_binop!(Div, div, DivAssign, div_assign);
impl core::ops::Neg for GfElement {
type Output = GfElement;
#[inline]
fn neg(self) -> GfElement {
GfElement::neg(&self)
}
}
impl core::ops::Neg for &GfElement {
type Output = GfElement;
#[inline]
fn neg(self) -> GfElement {
GfElement::neg(self)
}
}
#[cfg(feature = "poly")]
const GF_KRONECKER_THRESHOLD: usize = 24;
#[cfg(feature = "poly")]
pub(crate) fn gf_kronecker_mul(a: &[GfElement], b: &[GfElement]) -> Option<Vec<GfElement>> {
if a.is_empty() || b.is_empty() {
return Some(Vec::new());
}
if a.len().min(b.len()) < GF_KRONECKER_THRESHOLD {
return None;
}
Some(gf_kronecker_convolve(a, b))
}
#[cfg(feature = "poly")]
pub(crate) fn gf_kronecker_convolve(a: &[GfElement], b: &[GfElement]) -> Vec<GfElement> {
use crate::poly::{kronecker_pack, kronecker_unpack};
let field = a[0].field.clone();
let k = field.k;
let bp = field.p.bit_len();
let na = a.len();
let nb = b.len();
let min_len = na.min(nb);
let num_outer = na + nb - 1;
let inner_count = (min_len as u64) * (k as u64);
let clog_in = 64 - inner_count.leading_zeros(); let k_in = 2 * bp + clog_in;
let clog_out = 64 - (min_len as u64).leading_zeros(); let k_out_u64 = 2u64 * (k as u64) * u64::from(k_in) + u64::from(clog_out);
let k_out =
u32::try_from(k_out_u64).expect("gf_kronecker_convolve: outer slot exceeds 2³² bits");
let blocks_a: Vec<Int> = a.iter().map(|e| kronecker_pack(&e.coeffs, k_in)).collect();
let blocks_b: Vec<Int> = b.iter().map(|e| kronecker_pack(&e.coeffs, k_in)).collect();
let prod = kronecker_pack(&blocks_a, k_out).mul(&kronecker_pack(&blocks_b, k_out));
let inner_slots = 2 * k - 1;
kronecker_unpack(&prod, k_out, num_outer)
.into_iter()
.map(|block| {
let raw = kronecker_unpack(&block, k_in, inner_slots);
GfElement {
field: field.clone(),
coeffs: field.reduce_raw(&raw),
}
})
.collect()
}