use super::element::Zp;
use super::poly::PolyZp;
use super::{FiniteFieldError, FiniteFieldResult};
fn frobenius_mod(f: &PolyZp, p: u64) -> FiniteFieldResult<PolyZp> {
let modulus = f.modulus();
debug_assert_eq!(modulus, p, "prime mismatch");
if f.is_zero() {
return Err(FiniteFieldError::DivisionByZero);
}
let mut result = PolyZp::constant(1, modulus);
let x = PolyZp::x(modulus);
let mut base = x;
let mut exp = p;
while exp > 0 {
if exp & 1 == 1 {
result = result.mul_fast(&base);
let (_, rem) = result.div_rem(f)?;
result = rem;
}
if exp > 1 {
base = base.mul_fast(&base);
let (_, rem) = base.div_rem(f)?;
base = rem;
}
exp >>= 1;
}
Ok(result)
}
#[allow(clippy::needless_range_loop)]
fn berlekamp_matrix(f: &PolyZp) -> FiniteFieldResult<Vec<Vec<u64>>> {
let n = match f.degree() {
Some(d) => d,
None => return Err(FiniteFieldError::EmptyPolynomial),
};
if n == 0 {
return Ok(vec![vec![1]]);
}
let p = f.modulus();
let mut q = vec![vec![0u64; n]; n];
let x_p = frobenius_mod(f, p)?;
let mut current = PolyZp::constant(1, p);
for j in 0..n {
for i in 0..n {
q[i][j] = current.coeff(i).value();
}
if j < n - 1 {
current = current.mul_fast(&x_p);
let (_, rem) = current.div_rem(f)?;
current = rem;
}
}
Ok(q)
}
#[allow(clippy::needless_range_loop)]
fn null_space(q: &[Vec<u64>], p: u64) -> FiniteFieldResult<Vec<Vec<u64>>> {
let n = q.len();
if n == 0 {
return Ok(vec![]);
}
let mut matrix = q.to_vec();
for i in 0..n {
let old_val = matrix[i][i];
matrix[i][i] = if old_val == 0 {
p - 1
} else {
(old_val + p - 1) % p
};
}
let mut pivot_col = vec![None; n];
let mut row = 0;
for col in 0..n {
let mut pivot_row = None;
for r in row..n {
if !matrix[r][col].is_multiple_of(p) {
pivot_row = Some(r);
break;
}
}
let Some(pr) = pivot_row else {
continue;
};
if pr != row {
matrix.swap(row, pr);
}
pivot_col[row] = Some(col);
let pivot = Zp::new(matrix[row][col], p);
let pivot_inv = pivot.inverse()?;
for j in 0..n {
let val = Zp::new(matrix[row][j], p);
matrix[row][j] = (val * pivot_inv).value();
}
for r in 0..n {
if r == row {
continue;
}
let factor = Zp::new(matrix[r][col], p);
if factor.is_zero() {
continue;
}
for j in 0..n {
let row_val = Zp::new(matrix[row][j], p);
let current = Zp::new(matrix[r][j], p);
matrix[r][j] = (current - factor * row_val).value();
}
}
row += 1;
}
let mut free_vars = Vec::new();
for col in 0..n {
if !pivot_col.contains(&Some(col)) {
free_vars.push(col);
}
}
let mut basis = Vec::new();
for &free_var in &free_vars {
let mut vec = vec![0u64; n];
vec[free_var] = 1;
for r in 0..n {
if let Some(pivot_c) = pivot_col[r] {
vec[pivot_c] = if matrix[r][free_var] == 0 {
0
} else {
p - matrix[r][free_var]
};
}
}
basis.push(vec);
}
Ok(basis)
}
pub fn berlekamp_factor(f: &PolyZp) -> FiniteFieldResult<Vec<PolyZp>> {
let n = match f.degree() {
Some(d) => d,
None => return Err(FiniteFieldError::EmptyPolynomial),
};
if n == 0 {
return Ok(vec![f.clone()]);
}
if n == 1 {
let monic = f.make_monic()?;
return Ok(vec![monic]);
}
let p = f.modulus();
let f_monic = f.make_monic()?;
let q_matrix = berlekamp_matrix(&f_monic)?;
let null_basis = null_space(&q_matrix, p)?;
if null_basis.is_empty() {
return Ok(vec![f_monic]);
}
let mut factors = vec![f_monic];
for basis_vec in null_basis {
let v = PolyZp::from_coeffs(basis_vec, p);
let mut new_factors = Vec::new();
for factor in factors {
if factor.degree() == Some(1) {
new_factors.push(factor);
continue;
}
let mut found_split = false;
for c in 0..p {
let v_minus_c = v.sub(&PolyZp::constant(c, p));
let g = factor.gcd(&v_minus_c)?;
if !g.is_constant() && g.degree() != factor.degree() {
let (q, r) = factor.div_rem(&g)?;
if !r.is_zero() {
continue;
}
new_factors.push(g);
new_factors.push(q);
found_split = true;
break;
}
}
if !found_split {
new_factors.push(factor);
}
}
factors = new_factors;
if factors.iter().all(|f| f.degree() == Some(1)) {
break;
}
}
Ok(factors)
}
pub fn factor_over_zp(poly: &PolyZp) -> FiniteFieldResult<Vec<PolyZp>> {
let n = match poly.degree() {
Some(d) => d,
None => return Err(FiniteFieldError::EmptyPolynomial),
};
if n == 0 {
return Ok(vec![poly.clone()]);
}
if n == 1 {
let monic = poly.make_monic()?;
return Ok(vec![monic]);
}
let poly_monic = poly.make_monic()?;
berlekamp_factor(&poly_monic)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_frobenius_mod() {
let f = PolyZp::from_signed_coeffs(&[-1, 0, 1], 7);
let x_p = frobenius_mod(&f, 7).unwrap();
assert!(!x_p.is_zero());
}
#[test]
fn test_berlekamp_matrix_linear() {
let f = PolyZp::from_signed_coeffs(&[-1, 1], 7);
let q = berlekamp_matrix(&f).unwrap();
assert_eq!(q.len(), 1);
assert_eq!(q[0].len(), 1);
}
#[test]
fn test_null_space_trivial() {
let p = 7;
let identity = vec![vec![1]];
let basis = null_space(&identity, p).unwrap();
assert_eq!(basis.len(), 1);
}
#[test]
fn test_berlekamp_factor_x_minus_one() {
let f = PolyZp::from_signed_coeffs(&[-1, 1], 7);
let factors = berlekamp_factor(&f).unwrap();
assert_eq!(factors.len(), 1);
}
#[test]
fn test_berlekamp_factor_x_squared_minus_one() {
let f = PolyZp::from_signed_coeffs(&[-1, 0, 1], 7);
let factors = berlekamp_factor(&f).unwrap();
assert_eq!(factors.len(), 2);
for factor in &factors {
assert_eq!(factor.degree(), Some(1));
assert_eq!(factor.leading_coeff().unwrap().value(), 1);
}
}
#[test]
fn test_berlekamp_factor_x_cubed_minus_x() {
let f = PolyZp::from_signed_coeffs(&[0, -1, 0, 1], 7);
let monic = f.make_monic().unwrap();
let factors = berlekamp_factor(&monic).unwrap();
assert!(factors.len() >= 2);
}
#[test]
fn test_berlekamp_factor_x_to_fourth_minus_one() {
let f = PolyZp::from_signed_coeffs(&[-1, 0, 0, 0, 1], 7);
let factors = berlekamp_factor(&f).unwrap();
assert!(factors.len() >= 2);
}
#[test]
fn test_berlekamp_factor_x_to_sixth_minus_one() {
let f = PolyZp::from_signed_coeffs(&[-1, 0, 0, 0, 0, 0, 1], 7);
let factors = berlekamp_factor(&f).unwrap();
assert!(factors.len() >= 2);
}
#[test]
fn test_factor_over_zp_linear() {
let f = PolyZp::from_signed_coeffs(&[-1, 1], 7);
let factors = factor_over_zp(&f).unwrap();
assert_eq!(factors.len(), 1);
}
#[test]
fn test_factor_over_zp_quadratic() {
let f = PolyZp::from_signed_coeffs(&[-1, 0, 1], 7);
let factors = factor_over_zp(&f).unwrap();
assert_eq!(factors.len(), 2);
}
#[test]
fn test_factor_over_zp_cubic() {
let f = PolyZp::from_signed_coeffs(&[0, -1, 0, 1], 7);
let monic = f.make_monic().unwrap();
let factors = factor_over_zp(&monic).unwrap();
assert!(factors.len() >= 2);
}
#[test]
fn test_factor_random_degree_10() {
let coeffs: Vec<i64> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 1];
let f = PolyZp::from_signed_coeffs(&coeffs, 11);
let result = factor_over_zp(&f);
assert!(result.is_ok());
}
}