use smallvec::{SmallVec, smallvec};
use std::cmp::Ordering;
use ocas_core::FastHashMap as HashMap;
use ocas_core::FastHashSet as HashSet;
use ocas_domain::Domain;
use rayon::prelude::*;
use super::GroebnerBasis;
use crate::sparse::{MonomialOrder, SparseMultivariatePolynomial, monomial_divides};
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Signature {
pub module_pos: usize,
pub monomial: SmallVec<[usize; 4]>,
}
impl Signature {
pub fn unit(module_pos: usize, n_vars: usize) -> Self {
Self {
module_pos,
monomial: smallvec![0; n_vars],
}
}
pub fn mul_monomial(&self, exp: &[usize]) -> Self {
let monomial: SmallVec<[usize; 4]> = self
.monomial
.iter()
.zip(exp.iter())
.map(|(a, b)| a + b)
.collect();
Self {
module_pos: self.module_pos,
monomial,
}
}
pub fn cmp_pot<O: MonomialOrder>(&self, other: &Self, order: &O) -> Ordering {
self.module_pos
.cmp(&other.module_pos)
.then_with(|| order.cmp(&self.monomial, &other.monomial))
}
}
#[derive(Clone)]
struct LabeledPoly<D: Domain, O: MonomialOrder> {
poly: SparseMultivariatePolynomial<D, O>,
sig: Signature,
}
impl<D: Domain, O: MonomialOrder> BasisPoly for LabeledPoly<D, O> {
fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>> {
self.poly.leading_monomial()
}
fn n_vars(&self) -> usize {
self.poly.n_vars()
}
fn n_terms(&self) -> usize {
self.poly.n_terms()
}
fn mul_monomial(&self, exp: &[usize]) -> Self {
Self {
poly: self.poly.mul_monomial(exp),
sig: self.sig.mul_monomial(exp),
}
}
}
impl<D: Domain, O: MonomialOrder> LabeledPoly<D, O> {
fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>> {
self.poly.leading_monomial()
}
}
struct SyzygySet {
lms: HashMap<usize, MonomialBucketSet>,
}
impl SyzygySet {
fn new() -> Self {
Self {
lms: HashMap::default(),
}
}
fn insert(&mut self, sig: &Signature) {
self.lms
.entry(sig.module_pos)
.or_default()
.insert(sig.monomial.clone());
}
fn contains(&self, sig: &Signature) -> bool {
self.lms
.get(&sig.module_pos)
.is_some_and(|lms| lms.any_divisor_of(&sig.monomial))
}
}
struct MonomialBucketSet {
buckets: HashMap<u64, Vec<SmallVec<[usize; 4]>>>,
}
impl Default for MonomialBucketSet {
fn default() -> Self {
Self::new()
}
}
impl MonomialBucketSet {
fn new() -> Self {
Self {
buckets: HashMap::default(),
}
}
fn insert(&mut self, m: SmallVec<[usize; 4]>) {
self.buckets.entry(support_mask(&m)).or_default().push(m);
}
fn any_divisor_of(&self, exp: &[usize]) -> bool {
let mask = support_mask(exp);
let mut sub = mask;
loop {
if let Some(ms) = self.buckets.get(&sub)
&& ms.iter().any(|m| monomial_divides(exp, m))
{
return true;
}
if sub == 0 {
break;
}
sub = (sub - 1) & mask;
}
false
}
}
struct LabeledRow<D: Domain> {
terms: Vec<(D::Element, usize)>,
sig: Signature,
}
pub fn f5<D: Domain + 'static, O: MonomialOrder>(
ideal: &[SparseMultivariatePolynomial<D, O>],
) -> GroebnerBasis<D, O> {
if ideal.is_empty() {
return GroebnerBasis { basis: vec![] };
}
if let Some(ff) =
(ideal[0].domain() as &dyn std::any::Any).downcast_ref::<ocas_domain::FiniteField>()
&& ff.prime_u64() < (1u64 << 31)
{
let prime = ff.prime_u64() as i64;
if packed_eligible(ideal) {
return f5_fp_packed(ideal, prime);
}
return f5_fp(ideal, prime);
}
let mut generators: Vec<SparseMultivariatePolynomial<D, O>> =
ideal.iter().filter(|p| !p.is_zero()).cloned().collect();
for p in &mut generators {
make_monic(p);
}
if generators.is_empty() {
return GroebnerBasis { basis: vec![] };
}
let n_vars = generators[0].n_vars();
let mut basis: Vec<LabeledPoly<D, O>> = Vec::new();
let mut pairs: Vec<CriticalPair> = Vec::new();
let mut simplifications: Vec<SimpCache<LabeledPoly<D, O>>> = Vec::new();
let mut syzygies = SyzygySet::new();
for (k, f) in generators.into_iter().enumerate() {
let sig_k = Signature::unit(k, n_vars);
let labeled = LabeledPoly {
poly: f,
sig: sig_k,
};
update_pairs(&mut basis, &mut pairs, &mut simplifications, labeled);
while !pairs.is_empty() {
let min_deg = pairs.iter().map(|p| p.degree).min().unwrap();
let selected: Vec<CriticalPair> =
pairs.extract_if(.., |p| p.degree == min_deg).collect();
let new_polys = build_and_reduce::<D, O>(&selected, &basis, &mut syzygies);
for poly in new_polys {
update_pairs(&mut basis, &mut pairs, &mut simplifications, poly);
}
}
}
let polys: Vec<SparseMultivariatePolynomial<D, O>> =
basis.into_iter().map(|lp| lp.poly).collect();
GroebnerBasis { basis: polys }.minimize().auto_reduce()
}
fn build_and_reduce<D: Domain + 'static, O: MonomialOrder>(
selected: &[CriticalPair],
basis: &[LabeledPoly<D, O>],
syzygies: &mut SyzygySet,
) -> Vec<LabeledPoly<D, O>> {
let domain = basis[0].poly.domain();
let order = basis[0].poly.order.clone();
let mut monomial_map: HashMap<SmallVec<[usize; 4]>, usize> = HashMap::default();
let mut monomial_list: Vec<SmallVec<[usize; 4]>> = Vec::new();
let mut rows: Vec<LabeledRow<D>> = Vec::new();
let mut worklist: Vec<SmallVec<[usize; 4]>> = Vec::new();
let mut seen_heads: HashSet<SmallVec<[usize; 4]>> = HashSet::default();
for pair in selected {
let i = pair.idx1;
let j = pair.idx2;
let lm_i = basis[i].leading_monomial().unwrap();
let lm_j = basis[j].leading_monomial().unwrap();
let lcm_exp = &pair.lcm;
let diff_i: SmallVec<[usize; 4]> = lcm_exp
.iter()
.zip(lm_i.iter())
.map(|(a, b)| a - b)
.collect();
let diff_j: SmallVec<[usize; 4]> = lcm_exp
.iter()
.zip(lm_j.iter())
.map(|(a, b)| a - b)
.collect();
for (idx, diff) in [(i, &diff_i), (j, &diff_j)] {
let sig = basis[idx].sig.mul_monomial(diff);
if syzygies.contains(&sig) {
continue;
}
let mult = basis[idx].poly.mul_monomial(diff);
seen_heads.insert(lcm_exp.clone());
add_poly_as_row(
&mult,
sig,
&mut rows,
&mut monomial_map,
&mut monomial_list,
&mut worklist,
);
}
}
if rows.is_empty() {
return vec![];
}
while let Some(exp) = worklist.pop() {
if let Some((bi, diff)) = find_reducer(basis, &exp) {
let sig = basis[bi].sig.mul_monomial(&diff);
if syzygies.contains(&sig) {
continue;
}
seen_heads.insert(exp.clone());
let reducer = basis[bi].poly.mul_monomial(&diff);
add_poly_as_row(
&reducer,
sig,
&mut rows,
&mut monomial_map,
&mut monomial_list,
&mut worklist,
);
}
}
if rows.is_empty() || monomial_list.is_empty() {
return vec![];
}
let ncols = monomial_list.len();
let mut col_order: Vec<usize> = (0..ncols).collect();
col_order.sort_unstable_by(|&a, &b| order.cmp(&monomial_list[b], &monomial_list[a]));
let mut col_inv = vec![0usize; ncols];
for (new_col, &old_col) in col_order.iter().enumerate() {
col_inv[old_col] = new_col;
}
for row in &mut rows {
for (_, col) in row.terms.iter_mut() {
*col = col_inv[*col];
}
row.terms.sort_unstable_by_key(|&(_, col)| col);
}
let mut sorted_monomials: Vec<SmallVec<[usize; 4]>> = vec![SmallVec::new(); ncols];
for (new_col, &old_col) in col_order.iter().enumerate() {
sorted_monomials[new_col] = monomial_list[old_col].clone();
}
rows.sort_by(|a, b| a.sig.cmp_pot::<O>(&b.sig, &order));
echelonize(&mut rows, ncols, domain);
let mut new_polys: Vec<LabeledPoly<D, O>> = Vec::new();
let basis_lm_set: HashSet<SmallVec<[usize; 4]>> = basis
.iter()
.filter_map(|lp| lp.leading_monomial().cloned())
.collect();
for row in &rows {
if row.terms.is_empty() {
syzygies.insert(&row.sig);
continue;
}
let row_lm = &sorted_monomials[row.terms[0].1];
if seen_heads.contains(row_lm) {
continue;
}
if basis_lm_set.contains(row_lm) {
continue;
}
let mut poly = basis[0].poly.zero();
for (coeff, col) in row.terms.iter().rev() {
poly.append_monomial(coeff.clone(), &sorted_monomials[*col]);
}
if poly.is_zero() {
syzygies.insert(&row.sig);
continue;
}
new_polys.push(LabeledPoly {
poly,
sig: row.sig.clone(),
});
}
new_polys
}
fn add_poly_as_row<D: Domain, O: MonomialOrder>(
poly: &SparseMultivariatePolynomial<D, O>,
sig: Signature,
rows: &mut Vec<LabeledRow<D>>,
monomial_map: &mut HashMap<SmallVec<[usize; 4]>, usize>,
monomial_list: &mut Vec<SmallVec<[usize; 4]>>,
worklist: &mut Vec<SmallVec<[usize; 4]>>,
) {
let domain = poly.domain();
let mut terms: Vec<(D::Element, usize)> = Vec::new();
for (exp, coeff) in poly.sorted_terms().iter().rev() {
if domain.is_zero(coeff) {
continue;
}
let col = *monomial_map.entry((*exp).clone()).or_insert_with(|| {
let idx = monomial_list.len();
monomial_list.push((*exp).clone());
worklist.push((*exp).clone());
idx
});
terms.push(((*coeff).clone(), col));
}
if !terms.is_empty() {
rows.push(LabeledRow { terms, sig });
}
}
fn find_reducer<D: Domain, O: MonomialOrder>(
basis: &[LabeledPoly<D, O>],
exp: &[usize],
) -> Option<(usize, SmallVec<[usize; 4]>)> {
for (i, lp) in basis.iter().enumerate() {
if let Some(lm) = lp.leading_monomial()
&& monomial_divides(exp, lm)
{
let diff: SmallVec<[usize; 4]> =
exp.iter().zip(lm.iter()).map(|(a, b)| a - b).collect();
return Some((i, diff));
}
}
None
}
fn echelonize<D: Domain>(rows: &mut Vec<LabeledRow<D>>, ncols: usize, domain: &D) {
let mut pivots: Vec<Option<usize>> = vec![None; ncols];
let mut scratch: Vec<(D::Element, usize)> = Vec::new();
for (r, row) in rows.iter_mut().enumerate() {
if row.terms.is_empty() {
continue;
}
let head_col = row.terms[0].1;
if pivots[head_col].is_none() {
let lc = row.terms[0].0.clone();
if !domain.is_one(&lc)
&& let Some(inv) = domain.inv(&lc)
{
for (c, _) in &mut row.terms {
*c = domain.mul(c, &inv);
}
}
pivots[head_col] = Some(r);
}
}
for r in 0..rows.len() {
if rows[r].terms.is_empty() {
continue;
}
if pivots[rows[r].terms[0].1] == Some(r) {
continue;
}
let mut row = std::mem::take(&mut rows[r].terms);
loop {
if row.is_empty() {
break;
}
let head_col = row[0].1;
match pivots[head_col] {
Some(pr) => {
let c = row[0].0.clone();
sub_scaled(domain, &mut row, &rows[pr].terms, &c, &mut scratch);
}
None => {
let lc = row[0].0.clone();
if !domain.is_one(&lc)
&& let Some(inv) = domain.inv(&lc)
{
for (c, _) in &mut row {
*c = domain.mul(c, &inv);
}
}
pivots[head_col] = Some(r);
break;
}
}
}
rows[r].terms = row;
}
rows.retain(|r| !r.terms.is_empty());
}
fn sub_scaled<D: Domain>(
domain: &D,
row: &mut Vec<(D::Element, usize)>,
pivot: &[(D::Element, usize)],
c: &D::Element,
scratch: &mut Vec<(D::Element, usize)>,
) {
scratch.clear();
let mut i = 1;
let mut j = 1;
while i < row.len() && j < pivot.len() {
if row[i].1 < pivot[j].1 {
scratch.push(row[i].clone());
i += 1;
} else if row[i].1 > pivot[j].1 {
let prod = domain.mul(&pivot[j].0, c);
let v = domain.sub(&domain.zero(), &prod);
if !domain.is_zero(&v) {
scratch.push((v, pivot[j].1));
}
j += 1;
} else {
let prod = domain.mul(&pivot[j].0, c);
let v = domain.sub(&row[i].0, &prod);
if !domain.is_zero(&v) {
scratch.push((v, row[i].1));
}
i += 1;
j += 1;
}
}
scratch.extend_from_slice(&row[i..]);
for (pc, pcol) in &pivot[j..] {
let prod = domain.mul(pc, c);
let v = domain.sub(&domain.zero(), &prod);
if !domain.is_zero(&v) {
scratch.push((v, *pcol));
}
}
std::mem::swap(row, scratch);
}
fn make_monic<D: Domain, O: MonomialOrder>(poly: &mut SparseMultivariatePolynomial<D, O>) {
poly.make_monic_inplace();
}
use super::f4::{
BasisPoly, CriticalPair, DivisorIndex, FpPoly, SimpCache, domain_to_i64_fp, i64_to_domain_fp,
mod_inv, monic_fp, norm_mod, support_mask, update_pairs,
};
use super::packed::{PackedMono, PackedMonomialBucketSet, PackedSig};
#[derive(Clone)]
struct LabeledFpPoly {
poly: FpPoly,
sig: Signature,
}
impl BasisPoly for LabeledFpPoly {
fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>> {
self.poly.leading_monomial()
}
fn n_vars(&self) -> usize {
self.poly.n_vars()
}
fn n_terms(&self) -> usize {
self.poly.n_terms()
}
fn mul_monomial(&self, exp: &[usize]) -> Self {
Self {
poly: self.poly.mul_monomial(exp),
sig: self.sig.mul_monomial(exp),
}
}
}
struct LabeledFpRow<S = Signature> {
terms: Vec<(i32, usize)>,
sig: S,
}
#[allow(clippy::too_many_lines)]
fn f5_fp<D: Domain + 'static, O: MonomialOrder>(
ideal: &[SparseMultivariatePolynomial<D, O>],
prime: i64,
) -> GroebnerBasis<D, O> {
let n_vars = ideal[0].n_vars();
let order = ideal[0].order.clone();
let mut generators: Vec<FpPoly> = ideal
.iter()
.filter(|p| !p.is_zero())
.map(|p| FpPoly::from_domain(p, prime))
.collect();
for p in &mut generators {
monic_fp(p, prime);
}
if generators.is_empty() {
return GroebnerBasis { basis: vec![] };
}
let mut basis: Vec<LabeledFpPoly> = Vec::new();
let mut pairs: Vec<CriticalPair> = Vec::new();
let mut simplifications: Vec<SimpCache<LabeledFpPoly>> = Vec::new();
let mut syzygies = SyzygySet::new();
let mut div_index = DivisorIndex::new();
for (k, f) in generators.into_iter().enumerate() {
let sig_k = Signature::unit(k, n_vars);
let labeled = LabeledFpPoly {
poly: f,
sig: sig_k,
};
update_pairs(&mut basis, &mut pairs, &mut simplifications, labeled);
if let Some(lm) = basis.last().and_then(|lp| lp.leading_monomial()) {
div_index.push(lm, basis.len() - 1);
}
while !pairs.is_empty() {
let min_deg = pairs.iter().map(|p| p.degree).min().unwrap();
let selected: Vec<CriticalPair> =
pairs.extract_if(.., |p| p.degree == min_deg).collect();
let new_polys = build_and_reduce_fp::<O>(
&selected, &basis, &mut syzygies, &div_index, prime, &order,
);
for poly in new_polys {
update_pairs(&mut basis, &mut pairs, &mut simplifications, poly);
if let Some(lm) = basis.last().and_then(|lp| lp.leading_monomial()) {
div_index.push(lm, basis.len() - 1);
}
}
}
}
let domain = ideal[0].domain().clone();
let basis_d: Vec<SparseMultivariatePolynomial<D, O>> = basis
.iter()
.map(|lp| lp.poly.to_domain::<D, O>(&domain, prime))
.collect();
GroebnerBasis { basis: basis_d }.minimize().auto_reduce()
}
fn build_and_reduce_fp<O: MonomialOrder>(
selected: &[CriticalPair],
basis: &[LabeledFpPoly],
syzygies: &mut SyzygySet,
div_index: &DivisorIndex,
prime: i64,
order: &O,
) -> Vec<LabeledFpPoly> {
let map_cap = selected.len() * 4;
let mut monomial_map: HashMap<SmallVec<[usize; 4]>, usize> =
HashMap::with_capacity_and_hasher(map_cap, Default::default());
let mut monomial_list: Vec<SmallVec<[usize; 4]>> = Vec::with_capacity(map_cap);
let mut rows: Vec<LabeledFpRow> = Vec::with_capacity(selected.len() * 2);
let mut worklist: Vec<SmallVec<[usize; 4]>> = Vec::new();
let mut seen_heads: HashSet<SmallVec<[usize; 4]>> = HashSet::default();
type RawPairRows = Vec<(Signature, Vec<(SmallVec<[usize; 4]>, i64)>)>;
let raw_rows: Vec<RawPairRows> = selected
.par_iter()
.map(|pair| {
let i = pair.idx1;
let j = pair.idx2;
let lm_i = basis[i].leading_monomial().unwrap();
let lm_j = basis[j].leading_monomial().unwrap();
let lcm_exp = &pair.lcm;
let diff_i: SmallVec<[usize; 4]> = lcm_exp
.iter()
.zip(lm_i.iter())
.map(|(a, b)| a - b)
.collect();
let diff_j: SmallVec<[usize; 4]> = lcm_exp
.iter()
.zip(lm_j.iter())
.map(|(a, b)| a - b)
.collect();
let mut out: RawPairRows = Vec::new();
for (idx, diff) in [(i, &diff_i), (j, &diff_j)] {
let sig = basis[idx].sig.mul_monomial(diff);
if syzygies.contains(&sig) {
continue;
}
let mult: FpPoly = basis[idx].poly.mul_monomial(diff);
let terms: Vec<(SmallVec<[usize; 4]>, i64)> = mult
.terms
.iter()
.filter(|t| t.1 != 0)
.map(|t| (t.0.clone(), t.1))
.collect();
if !terms.is_empty() {
out.push((sig, terms));
}
}
out
})
.collect();
for (pair, raw) in selected.iter().zip(raw_rows) {
let lcm_exp = &pair.lcm;
for (sig, terms) in raw {
seen_heads.insert(lcm_exp.clone());
let mut mapped: Vec<(i32, usize)> = Vec::with_capacity(terms.len());
for (exp, coeff) in terms {
let col = match monomial_map.get(&exp) {
Some(&c) => c,
None => {
let idx = monomial_list.len();
monomial_list.push(exp.clone());
worklist.push(exp.clone());
monomial_map.insert(exp, idx);
idx
}
};
mapped.push((coeff as i32, col));
}
rows.push(LabeledFpRow { terms: mapped, sig });
}
}
if rows.is_empty() {
return vec![];
}
while let Some(exp) = worklist.pop() {
if let Some((bi, diff)) = find_reducer_fp(div_index, basis, &exp) {
let sig = basis[bi].sig.mul_monomial(&diff);
if syzygies.contains(&sig) {
continue;
}
seen_heads.insert(exp.clone());
add_scaled_fppoly_as_row(
&basis[bi].poly,
&diff,
sig,
&mut rows,
&mut monomial_map,
&mut monomial_list,
&mut worklist,
);
}
}
if rows.is_empty() || monomial_list.is_empty() {
return vec![];
}
let ncols = monomial_list.len();
let mut col_order: Vec<usize> = (0..ncols).collect();
col_order.sort_unstable_by(|&a, &b| order.cmp(&monomial_list[b], &monomial_list[a]));
let mut col_inv = vec![0usize; ncols];
for (new_col, &old_col) in col_order.iter().enumerate() {
col_inv[old_col] = new_col;
}
for row in &mut rows {
for (_, col) in row.terms.iter_mut() {
*col = col_inv[*col];
}
row.terms.sort_unstable_by_key(|&(_, col)| col);
}
let mut sorted_monomials: Vec<SmallVec<[usize; 4]>> = vec![SmallVec::new(); ncols];
for (new_col, &old_col) in col_order.iter().enumerate() {
sorted_monomials[new_col] = monomial_list[old_col].clone();
}
rows.sort_by(|a, b| a.sig.cmp_pot::<O>(&b.sig, order));
echelonize_fp_labeled(&mut rows, ncols, prime);
let mut new_polys: Vec<LabeledFpPoly> = Vec::new();
let basis_lm_set: HashSet<SmallVec<[usize; 4]>> = basis
.iter()
.filter_map(|lp| lp.leading_monomial().cloned())
.collect();
for row in &rows {
if row.terms.is_empty() {
syzygies.insert(&row.sig);
continue;
}
let row_lm = &sorted_monomials[row.terms[0].1];
if seen_heads.contains(row_lm) {
continue;
}
if basis_lm_set.contains(row_lm) {
continue;
}
let mut terms: Vec<(SmallVec<[usize; 4]>, i64)> = Vec::new();
for &(c, col) in &row.terms {
let v = norm_mod(c as i64, prime);
if v != 0 {
terms.push((sorted_monomials[col].clone(), v));
}
}
if terms.is_empty() {
syzygies.insert(&row.sig);
continue;
}
new_polys.push(LabeledFpPoly {
poly: FpPoly {
terms,
n_vars: basis[0].poly.n_vars(),
},
sig: row.sig.clone(),
});
}
new_polys
}
fn add_scaled_fppoly_as_row(
poly: &FpPoly,
diff: &[usize],
sig: Signature,
rows: &mut Vec<LabeledFpRow>,
monomial_map: &mut HashMap<SmallVec<[usize; 4]>, usize>,
monomial_list: &mut Vec<SmallVec<[usize; 4]>>,
worklist: &mut Vec<SmallVec<[usize; 4]>>,
) {
let mut terms: Vec<(i32, usize)> = Vec::new();
let mut buf: SmallVec<[usize; 16]> = SmallVec::new();
for (exp, coeff) in &poly.terms {
if *coeff == 0 {
continue;
}
buf.clear();
for (v, &dv) in diff.iter().enumerate() {
buf.push(dv + exp.get(v).copied().unwrap_or(0));
}
let col = match monomial_map.get(&buf[..]) {
Some(&c) => c,
None => {
let key: SmallVec<[usize; 4]> = SmallVec::from_slice(&buf);
let idx = monomial_list.len();
monomial_list.push(key.clone());
worklist.push(key.clone());
monomial_map.insert(key, idx);
idx
}
};
terms.push((*coeff as i32, col));
}
if !terms.is_empty() {
rows.push(LabeledFpRow { terms, sig });
}
}
fn find_reducer_fp(
index: &DivisorIndex,
basis: &[LabeledFpPoly],
exp: &[usize],
) -> Option<(usize, SmallVec<[usize; 4]>)> {
let mask = support_mask(exp);
let mut best: Option<usize> = None;
let mut sub = mask;
loop {
if let Some(ids) = index.buckets.get(&sub) {
for &bi in ids {
if let Some(lm) = basis[bi].leading_monomial()
&& monomial_divides(exp, lm)
{
match best {
Some(b) if b <= bi => {}
_ => best = Some(bi),
}
}
}
}
if sub == 0 {
break;
}
sub = (sub - 1) & mask;
}
best.map(|bi| {
let lm = basis[bi].leading_monomial().unwrap();
let diff: SmallVec<[usize; 4]> =
exp.iter().zip(lm.iter()).map(|(a, b)| a - b).collect();
(bi, diff)
})
}
#[derive(Clone, Copy)]
enum PivotLoc {
Store(usize),
Row(usize),
}
fn echelonize_fp_labeled<S: Send + Sync>(rows: &mut Vec<LabeledFpRow<S>>, ncols: usize, prime: i64) {
let p = prime;
let mut pivots: Vec<Option<PivotLoc>> = vec![None; ncols];
for (r, row) in rows.iter_mut().enumerate() {
if row.terms.is_empty() {
continue;
}
let head_col = row.terms[0].1;
if pivots[head_col].is_none() {
if row.terms[0].0 != 1 {
let inv = mod_inv(row.terms[0].0 as i64, p);
for (c, _) in &mut row.terms {
*c = ((*c as i64 * inv) % p) as i32;
}
}
pivots[head_col] = Some(PivotLoc::Row(r));
}
}
let mut pivot_store: Vec<Vec<(i32, usize)>> = Vec::new();
let mut pivot_orig_row: Vec<usize> = Vec::new();
for c in 0..ncols {
if let Some(PivotLoc::Row(r)) = pivots[c] {
pivot_store.push(std::mem::take(&mut rows[r].terms));
pivot_orig_row.push(r);
pivots[c] = Some(PivotLoc::Store(pivot_store.len() - 1));
}
}
rows.par_iter_mut().for_each(|labeled| {
if labeled.terms.is_empty() {
return;
}
let mut row = std::mem::take(&mut labeled.terms);
let mut scratch: Vec<(i32, usize)> = Vec::new();
loop {
if row.is_empty() {
break;
}
let head_col = row[0].1;
match pivots[head_col] {
Some(PivotLoc::Store(si)) => {
let c = row[0].0;
sub_scaled_fp_labeled(&mut row, &pivot_store[si], c, p, &mut scratch);
}
_ => break, }
}
labeled.terms = row;
});
let mut scratch: Vec<(i32, usize)> = Vec::new();
for r in 0..rows.len() {
if rows[r].terms.is_empty() {
continue;
}
if matches!(pivots[rows[r].terms[0].1], Some(PivotLoc::Row(pr)) if pr == r) {
continue;
}
let mut row = std::mem::take(&mut rows[r].terms);
loop {
if row.is_empty() {
break;
}
let head_col = row[0].1;
match pivots[head_col] {
Some(PivotLoc::Store(si)) => {
let c = row[0].0;
sub_scaled_fp_labeled(&mut row, &pivot_store[si], c, p, &mut scratch);
}
Some(PivotLoc::Row(pr)) => {
let c = row[0].0;
sub_scaled_fp_labeled(&mut row, &rows[pr].terms, c, p, &mut scratch);
}
None => {
if row[0].0 != 1 {
let inv = mod_inv(row[0].0 as i64, p);
for (c, _) in &mut row {
*c = ((*c as i64 * inv) % p) as i32;
}
}
pivots[head_col] = Some(PivotLoc::Row(r));
break;
}
}
}
rows[r].terms = row;
}
for (si, &orig) in pivot_orig_row.iter().enumerate() {
rows[orig].terms = std::mem::take(&mut pivot_store[si]);
}
rows.retain(|r| !r.terms.is_empty());
}
fn sub_scaled_fp_labeled(
row: &mut Vec<(i32, usize)>,
pivot: &[(i32, usize)],
c: i32,
p: i64,
scratch: &mut Vec<(i32, usize)>,
) {
scratch.clear();
scratch.reserve(row.len() + pivot.len());
let mut i = 1; let mut j = 1;
while i < row.len() && j < pivot.len() {
let (rc, rcol) = row[i];
let (pc, pcol) = pivot[j];
if rcol < pcol {
scratch.push((rc, rcol));
i += 1;
} else if rcol > pcol {
let v = norm_mod(-(c as i64) * (pc as i64), p) as i32;
if v != 0 {
scratch.push((v, pcol));
}
j += 1;
} else {
let v = norm_mod(rc as i64 - (c as i64) * (pc as i64), p) as i32;
if v != 0 {
scratch.push((v, rcol));
}
i += 1;
j += 1;
}
}
scratch.extend_from_slice(&row[i..]);
for &(pc, pcol) in &pivot[j..] {
let v = norm_mod(-(c as i64) * (pc as i64), p) as i32;
if v != 0 {
scratch.push((v, pcol));
}
}
std::mem::swap(row, scratch);
}
pub(crate) fn packed_eligible<D: Domain, O: MonomialOrder>(
ideal: &[SparseMultivariatePolynomial<D, O>],
) -> bool {
ideal.iter().all(|p| p.n_vars() <= 8)
&& ideal.iter().all(|p| {
p.terms_ref()
.keys()
.all(|e| e.iter().all(|&x| x < 1 << 15))
})
}
#[derive(Debug, Clone)]
struct PackedFpPoly {
terms: Vec<(PackedMono, i64)>,
n_vars: usize,
lm_sv: Option<SmallVec<[usize; 4]>>,
}
fn lm_smallvec(terms: &[(PackedMono, i64)], n_vars: usize) -> Option<SmallVec<[usize; 4]>> {
terms
.first()
.map(|t| SmallVec::from_slice(&t.0.unpack_sv(n_vars)))
}
impl PackedFpPoly {
fn leading_monomial_packed(&self) -> Option<PackedMono> {
self.terms.first().map(|t| t.0)
}
fn from_domain<D: Domain + 'static, O: MonomialOrder>(
p: &SparseMultivariatePolynomial<D, O>,
prime: i64,
) -> Self {
let mut terms: Vec<(PackedMono, i64)> = Vec::with_capacity(p.n_terms());
for (exp, coeff) in p.sorted_terms().iter().rev() {
let c = norm_mod(domain_to_i64_fp::<D>(coeff, prime), prime);
if c != 0 {
terms.push((PackedMono::pack(exp), c));
}
}
let n_vars = p.n_vars();
let lm_sv = lm_smallvec(&terms, n_vars);
Self {
terms,
n_vars,
lm_sv,
}
}
fn to_domain<D: Domain + 'static, O: MonomialOrder>(
&self,
domain: &D,
prime: i64,
) -> SparseMultivariatePolynomial<D, O> {
let mut poly = SparseMultivariatePolynomial::new(domain.clone(), self.n_vars);
for (exp, c) in &self.terms {
poly.append_monomial(
i64_to_domain_fp::<D>(domain, *c, prime),
&exp.unpack_sv(self.n_vars),
);
}
poly
}
fn mul_monomial_packed(&self, diff: PackedMono) -> Self {
let terms: Vec<(PackedMono, i64)> = self
.terms
.iter()
.map(|(e, c)| (e.add(diff), *c))
.collect();
let n_vars = self.n_vars;
let lm_sv = lm_smallvec(&terms, n_vars);
Self {
terms,
n_vars,
lm_sv,
}
}
}
impl BasisPoly for PackedFpPoly {
fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>> {
self.lm_sv.as_ref()
}
fn n_vars(&self) -> usize {
self.n_vars
}
fn n_terms(&self) -> usize {
self.terms.len()
}
fn mul_monomial(&self, exp: &[usize]) -> Self {
self.mul_monomial_packed(PackedMono::pack(exp))
}
}
fn monic_packed(p: &mut PackedFpPoly, prime: i64) {
if let Some(&(_, lc)) = p.terms.first()
&& lc != 1
{
let inv = mod_inv(lc, prime);
for (_, c) in &mut p.terms {
*c = norm_mod(*c * inv, prime);
}
}
}
#[derive(Clone)]
struct LabeledPackedFpPoly {
poly: PackedFpPoly,
sig: PackedSig,
}
impl BasisPoly for LabeledPackedFpPoly {
fn leading_monomial(&self) -> Option<&SmallVec<[usize; 4]>> {
self.poly.leading_monomial()
}
fn n_vars(&self) -> usize {
self.poly.n_vars()
}
fn n_terms(&self) -> usize {
self.poly.n_terms()
}
fn mul_monomial(&self, exp: &[usize]) -> Self {
Self {
poly: self.poly.mul_monomial(exp),
sig: self.sig.mul_monomial(PackedMono::pack(exp)),
}
}
}
struct PackedSyzygySet {
lms: HashMap<usize, PackedMonomialBucketSet>,
}
impl PackedSyzygySet {
fn new() -> Self {
Self {
lms: HashMap::default(),
}
}
fn insert(&mut self, sig: &PackedSig) {
self.lms.entry(sig.pos).or_default().insert(sig.mono);
}
fn contains(&self, sig: &PackedSig) -> bool {
self.lms
.get(&sig.pos)
.is_some_and(|lms| lms.any_divisor_of(sig.mono))
}
}
#[allow(clippy::too_many_lines)]
fn f5_fp_packed<D: Domain + 'static, O: MonomialOrder>(
ideal: &[SparseMultivariatePolynomial<D, O>],
prime: i64,
) -> GroebnerBasis<D, O> {
let order = ideal[0].order.clone();
let mut generators: Vec<PackedFpPoly> = ideal
.iter()
.filter(|p| !p.is_zero())
.map(|p| PackedFpPoly::from_domain(p, prime))
.collect();
for p in &mut generators {
monic_packed(p, prime);
}
if generators.is_empty() {
return GroebnerBasis { basis: vec![] };
}
let mut basis: Vec<LabeledPackedFpPoly> = Vec::new();
let mut pairs: Vec<CriticalPair> = Vec::new();
let mut simplifications: Vec<SimpCache<LabeledPackedFpPoly>> = Vec::new();
let mut syzygies = PackedSyzygySet::new();
let mut div_index = DivisorIndex::new();
for (k, f) in generators.into_iter().enumerate() {
let sig_k = PackedSig::unit(k);
let labeled = LabeledPackedFpPoly {
poly: f,
sig: sig_k,
};
update_pairs(&mut basis, &mut pairs, &mut simplifications, labeled);
if let Some(lm) = basis.last().and_then(|lp| lp.leading_monomial()) {
div_index.push(lm, basis.len() - 1);
}
while !pairs.is_empty() {
let min_deg = pairs.iter().map(|p| p.degree).min().unwrap();
let selected: Vec<CriticalPair> =
pairs.extract_if(.., |p| p.degree == min_deg).collect();
let new_polys = build_and_reduce_fp_packed::<O>(
&selected, &basis, &mut syzygies, &div_index, prime, &order,
);
for poly in new_polys {
update_pairs(&mut basis, &mut pairs, &mut simplifications, poly);
if let Some(lm) = basis.last().and_then(|lp| lp.leading_monomial()) {
div_index.push(lm, basis.len() - 1);
}
}
}
}
let domain = ideal[0].domain().clone();
let basis_d: Vec<SparseMultivariatePolynomial<D, O>> = basis
.iter()
.map(|lp| lp.poly.to_domain::<D, O>(&domain, prime))
.collect();
GroebnerBasis { basis: basis_d }.minimize().auto_reduce()
}
fn build_and_reduce_fp_packed<O: MonomialOrder>(
selected: &[CriticalPair],
basis: &[LabeledPackedFpPoly],
syzygies: &mut PackedSyzygySet,
div_index: &DivisorIndex,
prime: i64,
order: &O,
) -> Vec<LabeledPackedFpPoly> {
let n_vars = basis[0].poly.n_vars();
let map_cap = selected.len() * 4;
let mut monomial_map: HashMap<PackedMono, usize> =
HashMap::with_capacity_and_hasher(map_cap, Default::default());
let mut monomial_list: Vec<PackedMono> = Vec::with_capacity(map_cap);
let mut rows: Vec<LabeledFpRow<PackedSig>> = Vec::with_capacity(selected.len() * 2);
let mut worklist: Vec<PackedMono> = Vec::new();
let mut seen_heads: HashSet<PackedMono> = HashSet::default();
type RawPairRows = Vec<(PackedSig, Vec<(PackedMono, i64)>)>;
let raw_rows: Vec<RawPairRows> = selected
.par_iter()
.map(|pair| {
let i = pair.idx1;
let j = pair.idx2;
let lm_i = basis[i].poly.leading_monomial_packed().unwrap();
let lm_j = basis[j].poly.leading_monomial_packed().unwrap();
let lcm_exp = PackedMono::pack(&pair.lcm);
let diff_i = lcm_exp.sub(lm_i);
let diff_j = lcm_exp.sub(lm_j);
let mut out: RawPairRows = Vec::new();
for (idx, diff) in [(i, diff_i), (j, diff_j)] {
let sig = basis[idx].sig.mul_monomial(diff);
if syzygies.contains(&sig) {
continue;
}
let mult: PackedFpPoly = basis[idx].poly.mul_monomial_packed(diff);
let terms: Vec<(PackedMono, i64)> = mult
.terms
.iter()
.filter(|t| t.1 != 0)
.map(|t| (t.0, t.1))
.collect();
if !terms.is_empty() {
out.push((sig, terms));
}
}
out
})
.collect();
for (pair, raw) in selected.iter().zip(raw_rows) {
let lcm_packed = PackedMono::pack(&pair.lcm);
for (sig, terms) in raw {
seen_heads.insert(lcm_packed);
let mut mapped: Vec<(i32, usize)> = Vec::with_capacity(terms.len());
for (exp, coeff) in terms {
let col = match monomial_map.get(&exp) {
Some(&c) => c,
None => {
let idx = monomial_list.len();
monomial_list.push(exp);
worklist.push(exp);
monomial_map.insert(exp, idx);
idx
}
};
mapped.push((coeff as i32, col));
}
rows.push(LabeledFpRow { terms: mapped, sig });
}
}
if rows.is_empty() {
return vec![];
}
while let Some(exp) = worklist.pop() {
if let Some((bi, diff)) = find_reducer_packed(div_index, basis, exp) {
let sig = basis[bi].sig.mul_monomial(diff);
if syzygies.contains(&sig) {
continue;
}
seen_heads.insert(exp);
add_scaled_packed_as_row(
&basis[bi].poly,
diff,
sig,
&mut rows,
&mut monomial_map,
&mut monomial_list,
&mut worklist,
);
}
}
if rows.is_empty() || monomial_list.is_empty() {
return vec![];
}
let ncols = monomial_list.len();
let mut col_order: Vec<usize> = (0..ncols).collect();
col_order.sort_unstable_by(|&a, &b| {
order.cmp(
&monomial_list[b].unpack_sv(n_vars),
&monomial_list[a].unpack_sv(n_vars),
)
});
let mut col_inv = vec![0usize; ncols];
for (new_col, &old_col) in col_order.iter().enumerate() {
col_inv[old_col] = new_col;
}
for row in &mut rows {
for (_, col) in row.terms.iter_mut() {
*col = col_inv[*col];
}
row.terms.sort_unstable_by_key(|&(_, col)| col);
}
let mut sorted_monomials: Vec<PackedMono> = vec![PackedMono(0); ncols];
for (new_col, &old_col) in col_order.iter().enumerate() {
sorted_monomials[new_col] = monomial_list[old_col];
}
rows.sort_by(|a, b| a.sig.cmp_pot::<O>(&b.sig, order, n_vars));
echelonize_fp_labeled(&mut rows, ncols, prime);
let mut new_polys: Vec<LabeledPackedFpPoly> = Vec::new();
let basis_lm_set: HashSet<PackedMono> = basis
.iter()
.filter_map(|lp| lp.poly.leading_monomial_packed())
.collect();
for row in &rows {
if row.terms.is_empty() {
syzygies.insert(&row.sig);
continue;
}
let row_lm = &sorted_monomials[row.terms[0].1];
if seen_heads.contains(row_lm) {
continue;
}
if basis_lm_set.contains(row_lm) {
continue;
}
let mut terms: Vec<(PackedMono, i64)> = Vec::new();
for &(c, col) in &row.terms {
let v = norm_mod(c as i64, prime);
if v != 0 {
terms.push((sorted_monomials[col], v));
}
}
if terms.is_empty() {
syzygies.insert(&row.sig);
continue;
}
new_polys.push(LabeledPackedFpPoly {
poly: PackedFpPoly {
n_vars,
lm_sv: lm_smallvec(&terms, n_vars),
terms,
},
sig: row.sig.clone(),
});
}
new_polys
}
fn add_scaled_packed_as_row(
poly: &PackedFpPoly,
diff: PackedMono,
sig: PackedSig,
rows: &mut Vec<LabeledFpRow<PackedSig>>,
monomial_map: &mut HashMap<PackedMono, usize>,
monomial_list: &mut Vec<PackedMono>,
worklist: &mut Vec<PackedMono>,
) {
let mut terms: Vec<(i32, usize)> = Vec::new();
for (exp, coeff) in &poly.terms {
if *coeff == 0 {
continue;
}
let key = exp.add(diff);
let col = match monomial_map.get(&key) {
Some(&c) => c,
None => {
let idx = monomial_list.len();
monomial_list.push(key);
worklist.push(key);
monomial_map.insert(key, idx);
idx
}
};
terms.push((*coeff as i32, col));
}
if !terms.is_empty() {
rows.push(LabeledFpRow { terms, sig });
}
}
fn find_reducer_packed(
index: &DivisorIndex,
basis: &[LabeledPackedFpPoly],
exp: PackedMono,
) -> Option<(usize, PackedMono)> {
let mask = exp.support_mask();
let mut best: Option<usize> = None;
let mut sub = mask;
loop {
if let Some(ids) = index.buckets.get(&sub) {
for &bi in ids {
if let Some(lm) = basis[bi].poly.leading_monomial_packed()
&& exp.divides(lm)
{
match best {
Some(b) if b <= bi => {}
_ => best = Some(bi),
}
}
}
}
if sub == 0 {
break;
}
sub = (sub - 1) & mask;
}
best.map(|bi| {
let lm = basis[bi].poly.leading_monomial_packed().unwrap();
(bi, exp.sub(lm))
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sparse::Lex;
use num_bigint::BigInt;
use ocas_domain::{FiniteField, Rational, RationalDomain};
fn r(n: i64, d: i64) -> Rational {
Rational::new(n, d)
}
#[test]
fn signature_unit_and_mul() {
let s = Signature::unit(2, 3);
assert_eq!(s.module_pos, 2);
assert_eq!(s.monomial.as_slice(), &[0, 0, 0]);
let s2 = s.mul_monomial(&[1, 2, 0]);
assert_eq!(s2.module_pos, 2);
assert_eq!(s2.monomial.as_slice(), &[1, 2, 0]);
}
#[test]
fn signature_pot_order() {
let s1 = Signature::unit(0, 2);
let s2 = Signature::unit(1, 2);
assert_eq!(s1.cmp_pot::<Lex>(&s2, &Lex), Ordering::Less);
let s3 = Signature {
module_pos: 0,
monomial: smallvec![0, 1],
};
let s4 = Signature {
module_pos: 0,
monomial: smallvec![1, 0],
};
assert_eq!(s3.cmp_pot::<Lex>(&s4, &Lex), Ordering::Less);
}
#[test]
fn syzygy_set_basic() {
let mut syz = SyzygySet::new();
let s = Signature {
module_pos: 1,
monomial: smallvec![2, 0],
};
assert!(!syz.contains(&s));
syz.insert(&s);
assert!(syz.contains(&s));
let s_mult = Signature {
module_pos: 1,
monomial: smallvec![3, 1],
};
assert!(syz.contains(&s_mult));
let s_other = Signature {
module_pos: 0,
monomial: smallvec![2, 0],
};
assert!(!syz.contains(&s_other));
}
#[test]
fn f5_linear_system() {
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 0], r(1, 1)), (vec![0, 1], r(1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 0], r(1, 1)), (vec![0, 1], r(-1, 1))],
);
let gb = f5(&[f1, f2]);
assert!(gb.is_groebner_basis());
}
#[test]
fn f5_two_variable_ideal() {
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![2, 0], r(1, 1)), (vec![0, 1], r(-1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![3, 0], r(1, 1)), (vec![1, 0], r(-1, 1))],
);
let gb = f5(&[f1, f2]);
assert!(gb.is_groebner_basis());
}
#[test]
fn f5_matches_buchberger() {
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 1], r(1, 1)), (vec![0, 0], r(-1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 0], r(1, 1)), (vec![0, 1], r(-1, 1))],
);
let gb_f5 = f5(&[f1.clone(), f2.clone()]);
let gb_buch = crate::groebner::buchberger(&[f1, f2]);
assert!(gb_f5.is_groebner_basis());
assert_eq!(gb_f5.basis.len(), gb_buch.basis.len());
}
fn cyclic_fp(n: usize, p: u32) -> Vec<SparseMultivariatePolynomial<FiniteField, Lex>> {
let field = FiniteField::new(BigInt::from(p));
let mut gens = Vec::with_capacity(n);
for k in 1..n {
let mut terms = Vec::new();
for start in 0..n {
let mut exps = vec![0usize; n];
for j in 0..k {
exps[(start + j) % n] = 1;
}
terms.push((exps, field.element(1)));
}
gens.push(SparseMultivariatePolynomial::from_terms(
field.clone(),
n,
terms,
));
}
let full_exps = vec![1usize; n];
gens.push(SparseMultivariatePolynomial::from_terms(
field.clone(),
n,
vec![
(full_exps, field.element(1)),
(vec![0usize; n], field.element(p - 1)),
],
));
gens
}
#[test]
fn f5_fp_linear_system() {
let field = FiniteField::new(BigInt::from(13));
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
field.clone(),
2,
vec![
(vec![1, 0], field.element(1)),
(vec![0, 1], field.element(1)),
],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
field.clone(),
2,
vec![
(vec![1, 0], field.element(1)),
(vec![0, 1], field.element(12)), ],
);
let gb = f5(&[f1, f2]);
assert!(gb.is_groebner_basis());
}
#[test]
fn f5_fp_cyclic_3_fp13() {
let ideal = cyclic_fp(3, 13);
let gb = f5(&ideal);
assert!(!gb.basis.is_empty());
assert!(gb.is_groebner_basis());
}
#[test]
fn f5_fp_cyclic_3_fp101() {
let ideal = cyclic_fp(3, 101);
let gb = f5(&ideal);
assert!(!gb.basis.is_empty());
assert!(gb.is_groebner_basis());
}
#[test]
fn f5_fp_matches_f4_cyclic_3() {
let ideal = cyclic_fp(3, 13);
let gb_f5 = f5(&ideal);
let gb_f4 = crate::groebner::f4::f4(&ideal);
assert!(gb_f5.is_groebner_basis());
assert!(gb_f4.is_groebner_basis());
}
#[test]
fn packed_eligible_checks() {
let ideal6 = cyclic_fp(6, 13);
assert!(packed_eligible(&ideal6));
let field = FiniteField::new(BigInt::from(13));
let mut terms = Vec::new();
for i in 0..9 {
let mut e = vec![0usize; 9];
e[i] = 1;
terms.push((e, field.element(1)));
}
let big = SparseMultivariatePolynomial::<_, Lex>::from_terms(field.clone(), 9, terms);
let p2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
field.clone(),
9,
vec![
(vec![1, 1, 0, 0, 0, 0, 0, 0, 0], field.element(1)),
(vec![0usize; 9], field.element(12)),
],
);
assert!(!packed_eligible(&[big, p2]));
let f = FiniteField::new(BigInt::from(13));
let h1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
f.clone(),
2,
vec![
(vec![70000, 0], f.element(1)),
(vec![0usize; 2], f.element(12)),
],
);
let h2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
f.clone(),
2,
vec![
(vec![0, 1], f.element(1)),
(vec![0usize; 2], f.element(12)),
],
);
assert!(!packed_eligible(&[h1, h2]));
let b1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
f.clone(),
2,
vec![
(vec![(1 << 15) - 1, 0], f.element(1)),
(vec![0usize; 2], f.element(12)),
],
);
let b2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
f.clone(),
2,
vec![
(vec![0, 1], f.element(1)),
(vec![0usize; 2], f.element(12)),
],
);
assert!(packed_eligible(&[b1, b2]));
}
}