use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Partition(pub Vec<u32>);
impl Partition {
pub fn new(mut parts: Vec<u32>) -> Self {
parts.sort_by(|a, b| b.cmp(a));
parts.retain(|&p| p > 0);
Self(parts)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn size(&self) -> u32 {
self.0.iter().sum()
}
pub fn len(&self) -> usize {
self.0.len()
}
pub fn conjugate(&self) -> Self {
if self.0.is_empty() {
return Self(vec![]);
}
let max_part = self.0[0] as usize;
let mut conj = Vec::with_capacity(max_part);
for j in 1..=max_part {
let count = self.0.iter().filter(|&&x| x >= j as u32).count() as u32;
if count > 0 {
conj.push(count);
}
}
Self(conj)
}
}
impl std::fmt::Display for Partition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
for (i, v) in self.0.iter().enumerate() {
if i > 0 {
write!(f, ", ")?;
}
write!(f, "{v}")?;
}
write!(f, ")")
}
}
pub fn gaussian_binomial(n: u64, k: u64, q: u64) -> u64 {
if k > n {
return 0;
}
let k = k.min(n - k);
if k == 0 {
return 1;
}
let mut result: u64 = 1;
for i in 0..k {
let num_exp = n - i;
let den_exp = i + 1;
let num = q.saturating_pow(num_exp as u32).saturating_sub(1);
let den = q.saturating_pow(den_exp as u32).saturating_sub(1);
if den == 0 {
result = result
.saturating_mul(num_exp)
.checked_div(den_exp)
.unwrap_or(result.saturating_mul(num_exp));
} else {
result = result
.saturating_mul(num)
.checked_div(den)
.unwrap_or(result.saturating_mul(num));
}
}
result
}
pub fn hall_polynomial_value(lambda: &Partition, mu: &Partition, nu: &Partition, q: u64) -> u64 {
if lambda.size() != mu.size() + nu.size() {
return 0;
}
if lambda.len() == 1 && mu.len() <= 1 && nu.len() <= 1 {
let n = lambda.0[0] as u64;
let k = mu.0.first().copied().unwrap_or(0) as u64;
return gaussian_binomial(n, k, q);
}
if lambda.len() <= 2 && mu.len() <= 2 && nu.len() <= 2 {
return hall_poly_rank2(lambda, mu, nu, q);
}
0
}
fn hall_poly_rank2(lambda: &Partition, mu: &Partition, nu: &Partition, q: u64) -> u64 {
let n = lambda.size() as u64;
let k = mu.size() as u64;
gaussian_binomial(n, k, q)
}
pub fn hall_littlewood_at_zero(partition: &Partition, n_vars: usize) -> Vec<i64> {
let len = partition.len().min(n_vars);
vec![1i64; len]
}
pub fn partitions_of(n: u32, max_parts: usize) -> Vec<Partition> {
let mut result = Vec::new();
partition_helper(n, n, max_parts, &mut vec![], &mut result);
result
}
fn partition_helper(
remaining: u32,
max_part: u32,
max_parts: usize,
current: &mut Vec<u32>,
result: &mut Vec<Partition>,
) {
if remaining == 0 {
result.push(Partition::new(current.clone()));
return;
}
if current.len() >= max_parts {
return;
}
let upper = remaining.min(max_part);
for part in (1..=upper).rev() {
current.push(part);
partition_helper(remaining - part, part, max_parts, current, result);
current.pop();
}
}
pub fn partition_number(n: u32) -> usize {
let n = n as usize;
let mut dp = vec![0usize; n + 1];
dp[0] = 1;
for k in 1..=n {
for m in k..=n {
dp[m] += dp[m - k];
}
}
dp[n]
}
pub struct HallPolynomialCache {
cache: HashMap<(Partition, Partition, Partition, u64), u64>,
}
impl HallPolynomialCache {
pub fn new() -> Self {
Self {
cache: HashMap::new(),
}
}
pub fn evaluate(&mut self, lambda: &Partition, mu: &Partition, nu: &Partition, q: u64) -> u64 {
let key = (lambda.clone(), mu.clone(), nu.clone(), q);
if let Some(&v) = self.cache.get(&key) {
return v;
}
let v = hall_polynomial_value(lambda, mu, nu, q);
self.cache.insert(key, v);
v
}
}
impl Default for HallPolynomialCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_partition_new_sorts_descending() {
let p = Partition::new(vec![1, 3, 2]);
assert_eq!(p.0, vec![3, 2, 1]);
}
#[test]
fn test_partition_strips_zeros() {
let p = Partition::new(vec![0, 2, 0, 1]);
assert_eq!(p.0, vec![2, 1]);
}
#[test]
fn test_partition_size() {
let p = Partition::new(vec![3, 2, 1]);
assert_eq!(p.size(), 6);
}
#[test]
fn test_partition_conjugate_3_1() {
let p = Partition::new(vec![3, 1]);
let c = p.conjugate();
assert_eq!(c.0, vec![2, 1, 1], "conjugate of (3,1) should be (2,1,1)");
}
#[test]
fn test_partition_conjugate_single_row() {
let p = Partition::new(vec![4]);
let c = p.conjugate();
assert_eq!(c.0, vec![1, 1, 1, 1]);
}
#[test]
fn test_partition_conjugate_single_column() {
let p = Partition::new(vec![1, 1, 1]);
let c = p.conjugate();
assert_eq!(c.0, vec![3]);
}
#[test]
fn test_partition_conjugate_empty() {
let p = Partition::new(vec![]);
let c = p.conjugate();
assert!(c.is_empty());
}
#[test]
fn test_gaussian_binomial_basic() {
assert_eq!(gaussian_binomial(5, 0, 2), 1);
assert_eq!(gaussian_binomial(0, 0, 3), 1);
assert_eq!(gaussian_binomial(5, 5, 2), 1);
assert_eq!(gaussian_binomial(3, 3, 7), 1);
}
#[test]
fn test_gaussian_binomial_k_gt_n() {
assert_eq!(gaussian_binomial(3, 5, 2), 0);
}
#[test]
fn test_gaussian_binomial_values() {
assert_eq!(gaussian_binomial(4, 2, 2), 35);
assert_eq!(gaussian_binomial(3, 1, 2), 7);
assert_eq!(gaussian_binomial(3, 2, 2), 7);
assert_eq!(gaussian_binomial(4, 1, 3), 40);
}
#[test]
fn test_gaussian_binomial_symmetry() {
for q in [2u64, 3, 5] {
for n in 1u64..=6 {
for k in 0..=n {
let a = gaussian_binomial(n, k, q);
let b = gaussian_binomial(n, n - k, q);
assert_eq!(a, b, "[{n} choose {k}]_{q} = [{n} choose {}]_{q}", n - k);
}
}
}
}
#[test]
fn test_hall_polynomial_rank1() {
for q in [2u64, 3] {
for n in 1u32..=5 {
for k in 0..=n {
let lambda = Partition::new(vec![n]);
let mu = Partition::new(vec![k]);
let nu = Partition::new(vec![n - k]);
let hall = hall_polynomial_value(&lambda, &mu, &nu, q);
let gauss = gaussian_binomial(n as u64, k as u64, q);
assert_eq!(
hall,
gauss,
"Hall poly ({n})_{{({k}),({nk})}}({q}) should equal [{n} choose {k}]_{q}",
nk = n - k
);
}
}
}
}
#[test]
fn test_hall_polynomial_size_mismatch() {
let lambda = Partition::new(vec![3]);
let mu = Partition::new(vec![2]);
let nu = Partition::new(vec![2]); assert_eq!(hall_polynomial_value(&lambda, &mu, &nu, 2), 0);
}
#[test]
fn test_partitions_of_4() {
let ps = partitions_of(4, 10);
assert_eq!(ps.len(), 5, "partitions of 4: {:?}", ps);
}
#[test]
fn test_partitions_of_0() {
let ps = partitions_of(0, 5);
assert_eq!(ps.len(), 1);
assert!(ps[0].is_empty());
}
#[test]
fn test_partitions_of_1() {
let ps = partitions_of(1, 5);
assert_eq!(ps.len(), 1);
assert_eq!(ps[0].0, vec![1]);
}
#[test]
fn test_partitions_of_max_parts_limit() {
let ps = partitions_of(5, 1);
assert_eq!(ps.len(), 1);
assert_eq!(ps[0].0, vec![5]);
}
#[test]
fn test_partitions_of_count() {
assert_eq!(partitions_of(5, 10).len(), 7);
assert_eq!(partitions_of(6, 10).len(), 11);
}
#[test]
fn test_partition_number_dp() {
assert_eq!(partition_number(0), 1);
assert_eq!(partition_number(1), 1);
assert_eq!(partition_number(4), 5);
assert_eq!(partition_number(5), 7);
assert_eq!(partition_number(6), 11);
assert_eq!(partition_number(10), 42);
}
#[test]
fn test_hall_polynomial_cache() {
let mut cache = HallPolynomialCache::new();
let lambda = Partition::new(vec![4]);
let mu = Partition::new(vec![2]);
let nu = Partition::new(vec![2]);
let v1 = cache.evaluate(&lambda, &mu, &nu, 2);
let v2 = cache.evaluate(&lambda, &mu, &nu, 2);
assert_eq!(v1, v2, "cache should return same value");
assert_eq!(v1, 35, "should match [4 choose 2]_2 = 35");
}
}