use crate::sparse::monomial_lcm;
pub fn hilbert_numerator(generators: &[Vec<usize>]) -> Vec<(usize, i64)> {
use std::collections::BTreeMap;
let mut coeffs: BTreeMap<usize, i64> = BTreeMap::new();
let s = generators.len();
for mask in 1..(1u64 << s) {
let mut lcm: Option<Vec<usize>> = None;
let mut bits = 0;
for (i, g) in generators.iter().enumerate() {
if mask & (1 << i) != 0 {
bits += 1;
lcm = Some(match lcm {
None => g.clone(),
Some(prev) => monomial_lcm(&prev, g).to_vec(),
});
}
}
let deg: usize = lcm.map(|l| l.iter().sum()).unwrap_or(0);
let sign: i64 = if bits % 2 == 1 { 1 } else { -1 };
*coeffs.entry(deg).or_insert(0) += sign;
}
coeffs.into_iter().filter(|&(_, c)| c != 0).collect()
}
pub fn regularity_bound(generators: &[Vec<usize>]) -> usize {
hilbert_numerator(generators)
.iter()
.map(|&(d, _)| d)
.max()
.unwrap_or(0)
}
pub fn staircase_dimension(generators: &[Vec<usize>]) -> Option<usize> {
let sum: i64 = hilbert_numerator(generators).iter().map(|&(_, c)| c).sum();
if sum == 0 {
None
} else {
Some(sum.unsigned_abs() as usize)
}
}
#[derive(Debug, Clone)]
pub struct HilbertSeries {
pub numerator: Vec<i64>,
pub denominator_power: usize,
}
impl HilbertSeries {
pub fn hilbert_function(&self, degree: usize) -> i64 {
let n = self.denominator_power as i64;
let mut result = 0i64;
for (i, &coeff) in self.numerator.iter().enumerate() {
if i > degree {
break;
}
let k = degree - i;
let binom = binomial_general(n, k);
result += coeff * binom;
}
result
}
pub fn dimension(&self) -> usize {
let n = self.denominator_power;
let mut poly = self.numerator.clone();
for dim in 0..n {
let sum: i64 = poly.iter().sum();
if sum != 0 {
return n - dim;
}
let mut new_poly = Vec::with_capacity(poly.len().saturating_sub(1));
for (i, &c) in poly.iter().enumerate().skip(1) {
new_poly.push(c * i as i64);
}
poly = new_poly;
}
0
}
pub fn degree(&self) -> i64 {
let mut poly = self.numerator.clone();
let dim = self.dimension();
for _ in 0..(self.denominator_power - dim) {
let mut new_poly = Vec::with_capacity(poly.len().saturating_sub(1));
for (i, &c) in poly.iter().enumerate().skip(1) {
new_poly.push(c * i as i64);
}
poly = new_poly;
}
let sum: i64 = poly.iter().sum();
let factorial: i64 = (1..=(self.denominator_power - dim) as i64).product();
if factorial == 0 { sum } else { sum / factorial }
}
pub fn hilbert_polynomial(&self) -> Vec<f64> {
let dim = self.dimension();
let n = dim + 1;
let xs: Vec<f64> = (0..n).map(|d| d as f64).collect();
let ys: Vec<f64> = (0..n).map(|d| self.hilbert_function(d) as f64).collect();
let mut coeffs = vec![0.0f64; n];
for j in 0..n {
let mut basis = vec![1.0f64]; let mut denom = 1.0f64;
for m in 0..n {
if m == j {
continue;
}
let xm = xs[m];
let mut new_basis = vec![0.0f64; basis.len() + 1];
for (k, &c) in basis.iter().enumerate() {
new_basis[k] -= c * xm;
new_basis[k + 1] += c;
}
basis = new_basis;
denom *= xs[j] - xm;
}
let scale = ys[j] / denom;
for (k, &c) in basis.iter().enumerate() {
coeffs[k] += c * scale;
}
}
while coeffs.len() > 1 && coeffs.last().unwrap().abs() < 1e-10 {
coeffs.pop();
}
coeffs
}
}
pub fn hilbert_series(
gb: &crate::groebner::GroebnerBasis<ocas_domain::RationalDomain, crate::sparse::Lex>,
) -> HilbertSeries {
let n_vars = gb.basis.first().map(|p| p.n_vars()).unwrap_or(0);
let lms: Vec<Vec<usize>> = gb
.basis
.iter()
.filter_map(|p| p.leading_monomial().map(|m| m.to_vec()))
.collect();
if lms.is_empty() {
return HilbertSeries {
numerator: vec![1],
denominator_power: n_vars,
};
}
let num = hilbert_numerator(&lms);
let max_deg = num.iter().map(|&(d, _)| d).max().unwrap_or(0);
let mut numerator = vec![0i64; max_deg + 1];
numerator[0] = 1;
for (deg, coeff) in num {
numerator[deg] -= coeff;
}
HilbertSeries {
numerator,
denominator_power: n_vars,
}
}
fn binomial_general(n: i64, k: usize) -> i64 {
if k == 0 {
return 1;
}
let mut result = 1i64;
for i in 0..k {
result = result * (n + i as i64) / (i as i64 + 1);
}
result
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hilbert_numerator_single_generator() {
let coeffs = hilbert_numerator(&[vec![2]]);
assert_eq!(coeffs, vec![(2, 1)]);
}
#[test]
fn hilbert_numerator_two_generators() {
let coeffs = hilbert_numerator(&[vec![2, 0], vec![0, 2]]);
assert_eq!(coeffs, vec![(2, 2), (4, -1)]);
assert_eq!(regularity_bound(&[vec![2, 0], vec![0, 2]]), 4);
assert_eq!(staircase_dimension(&[vec![2, 0], vec![0, 2]]), Some(1));
}
#[test]
fn hilbert_numerator_linear() {
let coeffs = hilbert_numerator(&[vec![1, 0], vec![0, 1]]);
assert_eq!(coeffs, vec![(1, 2), (2, -1)]);
assert_eq!(regularity_bound(&[vec![1, 0], vec![0, 1]]), 2);
assert_eq!(staircase_dimension(&[vec![1, 0], vec![0, 1]]), Some(1));
}
#[test]
fn hilbert_series_xy_xz() {
use crate::groebner::hilbert::hilbert_series;
use crate::sparse::Lex;
use crate::{Algorithm, SparseMultivariatePolynomial, groebner_basis};
use ocas_domain::{Rational, RationalDomain};
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![2, 0], Rational::new(1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 1], Rational::new(1, 1))],
);
let gb = groebner_basis(&[f1, f2], Algorithm::F4);
let hs = hilbert_series(&gb);
assert_eq!(hs.hilbert_function(0), 1);
assert_eq!(hs.hilbert_function(1), 2);
}
#[test]
fn hilbert_series_linear_ideal() {
use crate::groebner::hilbert::hilbert_series;
use crate::sparse::Lex;
use crate::{Algorithm, SparseMultivariatePolynomial, groebner_basis};
use ocas_domain::{Rational, RationalDomain};
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 0], Rational::new(1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![0, 1], Rational::new(1, 1))],
);
let gb = groebner_basis(&[f1, f2], Algorithm::F4);
let hs = hilbert_series(&gb);
assert_eq!(hs.hilbert_function(0), 1);
assert_eq!(hs.hilbert_function(1), 0);
}
#[test]
fn hilbert_polynomial_linear_ideal() {
use crate::groebner::hilbert::hilbert_series;
use crate::sparse::Lex;
use crate::{Algorithm, SparseMultivariatePolynomial, groebner_basis};
use ocas_domain::{Rational, RationalDomain};
let d = RationalDomain;
let f1 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![1, 0], Rational::new(1, 1))],
);
let f2 = SparseMultivariatePolynomial::<_, Lex>::from_terms(
d,
2,
vec![(vec![0, 1], Rational::new(1, 1))],
);
let gb = groebner_basis(&[f1, f2], Algorithm::F4);
let hs = hilbert_series(&gb);
let hp = hs.hilbert_polynomial();
assert_eq!(hp.len(), 1);
assert!((hp[0] - 1.0).abs() < 1e-10);
}
#[test]
fn hilbert_polynomial_empty_ideal() {
let hs = HilbertSeries {
numerator: vec![1],
denominator_power: 2,
};
let hp = hs.hilbert_polynomial();
assert_eq!(hp.len(), 2);
assert!((hp[0] - 1.0).abs() < 1e-10);
assert!((hp[1] - 1.0).abs() < 1e-10);
}
#[test]
fn hilbert_polynomial_x_squared() {
let hs = HilbertSeries {
numerator: vec![1, 0, -1], denominator_power: 1,
};
let hp = hs.hilbert_polynomial();
assert_eq!(hp.len(), 1);
assert!((hp[0] - 1.0).abs() < 1e-10);
}
}