use crate::error::{SpecialError, SpecialResult};
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Partition {
pub parts: Vec<usize>,
}
impl Partition {
pub fn new(parts: Vec<usize>) -> SpecialResult<Self> {
let mut parts = parts;
while parts.last() == Some(&0) {
parts.pop();
}
for i in 1..parts.len() {
if parts[i] > parts[i - 1] {
return Err(SpecialError::ValueError(format!(
"Partition parts must be non-increasing, but parts[{}]={} > parts[{}]={}",
i,
parts[i],
i - 1,
parts[i - 1]
)));
}
}
Ok(Partition { parts })
}
pub fn n(&self) -> usize {
self.parts.len()
}
pub fn weight(&self) -> usize {
self.parts.iter().sum()
}
pub fn conjugate(&self) -> Partition {
if self.parts.is_empty() {
return Partition { parts: vec![] };
}
let max_part = *self.parts.iter().max().unwrap_or(&0);
let mut conj_parts = Vec::with_capacity(max_part);
for j in 1..=max_part {
let count = self.parts.iter().filter(|&&p| p >= j).count();
if count > 0 {
conj_parts.push(count);
}
}
Partition { parts: conj_parts }
}
pub fn dominates(&self, other: &Partition) -> bool {
if self.weight() != other.weight() {
return false;
}
let max_len = self.parts.len().max(other.parts.len());
let mut sum_self = 0usize;
let mut sum_other = 0usize;
for k in 0..max_len {
sum_self += self.parts.get(k).copied().unwrap_or(0);
sum_other += other.parts.get(k).copied().unwrap_or(0);
if sum_self < sum_other {
return false;
}
}
true
}
pub fn is_empty(&self) -> bool {
self.parts.is_empty()
}
}
pub fn elementary_symmetric(k: usize, vars: &[f64]) -> f64 {
let n = vars.len();
if k > n {
return 0.0;
}
if k == 0 {
return 1.0;
}
let mut dp = vec![0.0f64; k + 1];
dp[0] = 1.0;
for &x in vars.iter() {
for j in (1..=k).rev() {
dp[j] += dp[j - 1] * x;
}
}
dp[k]
}
pub fn complete_homogeneous(k: usize, vars: &[f64]) -> f64 {
let n = vars.len();
if k == 0 {
return 1.0;
}
if n == 0 {
return 0.0;
}
let mut dp = vec![0.0f64; k + 1];
dp[0] = 1.0;
for &x in vars.iter() {
let old_dp = dp.clone();
for j in 1..=k {
let mut acc = old_dp[j];
let mut xp = x;
for t in 1..=j {
acc += old_dp[j - t] * xp;
xp *= x;
}
dp[j] = acc;
}
}
dp[k]
}
pub fn power_sum(k: usize, vars: &[f64]) -> f64 {
if k == 0 {
return vars.len() as f64;
}
vars.iter().map(|&x| x.powi(k as i32)).sum()
}
pub fn monomial_symmetric(partition: &Partition, vars: &[f64]) -> f64 {
let n = vars.len();
let lambda = &partition.parts;
if lambda.is_empty() {
return 1.0;
}
if lambda.len() > n {
return 0.0;
}
let mut exponents = vec![0usize; n];
for (i, &p) in lambda.iter().enumerate() {
exponents[i] = p;
}
let mut sum = 0.0f64;
let mut perm = exponents.clone();
perm.sort_unstable();
loop {
let term: f64 = vars
.iter()
.zip(perm.iter())
.map(|(&x, &e)| x.powi(e as i32))
.product();
sum += term;
if !next_permutation_ascending(&mut perm) {
break;
}
}
sum
}
fn next_permutation_ascending(arr: &mut [usize]) -> bool {
let n = arr.len();
if n <= 1 {
return false;
}
let mut i = n - 1;
loop {
if i == 0 {
return false; }
i -= 1;
if arr[i] < arr[i + 1] {
break;
}
}
let mut j = n - 1;
while arr[j] <= arr[i] {
j -= 1;
}
arr.swap(i, j);
arr[i + 1..].reverse();
true
}
pub fn schur_polynomial(partition: &Partition, vars: &[f64]) -> f64 {
if partition.is_empty() {
return 1.0;
}
let lambda = &partition.parts;
let l = lambda.len();
let max_k = lambda[0] + l + 2;
let mut h = vec![0.0f64; max_k + 1];
for k in 0..=max_k {
h[k] = complete_homogeneous(k, vars);
}
let mut mat = vec![vec![0.0f64; l]; l];
for i in 0..l {
for j in 0..l {
let idx = lambda[i] as isize - i as isize + j as isize;
if idx < 0 {
mat[i][j] = 0.0;
} else if idx as usize <= max_k {
mat[i][j] = h[idx as usize];
} else {
mat[i][j] = 0.0;
}
}
}
determinant_f64(&mat)
}
fn determinant_f64(mat: &[Vec<f64>]) -> f64 {
let n = mat.len();
if n == 0 {
return 1.0;
}
if n == 1 {
return mat[0][0];
}
let mut a = mat.to_vec();
let mut sign = 1.0f64;
for col in 0..n {
let mut max_row = col;
let mut max_val = a[col][col].abs();
for row in (col + 1)..n {
if a[row][col].abs() > max_val {
max_val = a[row][col].abs();
max_row = row;
}
}
if max_val < 1e-15 {
return 0.0; }
if max_row != col {
a.swap(col, max_row);
sign = -sign;
}
let pivot = a[col][col];
for row in (col + 1)..n {
let factor = a[row][col] / pivot;
for k in col..n {
let val = a[col][k];
a[row][k] -= factor * val;
}
}
}
let mut det = sign;
for i in 0..n {
det *= a[i][i];
}
det
}
pub fn littlewood_richardson(lambda: &Partition, mu: &Partition, nu: &Partition) -> i64 {
if nu.weight() != lambda.weight() + mu.weight() {
return 0;
}
if !contains_partition(nu, lambda) {
return 0;
}
count_lr_tableaux(nu, lambda, mu)
}
fn contains_partition(nu: &Partition, lambda: &Partition) -> bool {
if lambda.parts.len() > nu.parts.len() {
return false;
}
for (i, &lp) in lambda.parts.iter().enumerate() {
if lp > nu.parts.get(i).copied().unwrap_or(0) {
return false;
}
}
true
}
fn count_lr_tableaux(nu: &Partition, lambda: &Partition, mu: &Partition) -> i64 {
let n_rows = nu.parts.len();
let mut skew_rows: Vec<Vec<usize>> = Vec::new();
for r in 0..n_rows {
let nu_r = nu.parts.get(r).copied().unwrap_or(0);
let la_r = lambda.parts.get(r).copied().unwrap_or(0);
if nu_r > la_r {
let row_cells: Vec<usize> = (la_r..nu_r).collect();
skew_rows.push(row_cells);
} else {
skew_rows.push(vec![]);
}
}
let total_cells: usize = skew_rows.iter().map(|r| r.len()).sum();
if total_cells != mu.weight() {
return 0;
}
if total_cells == 0 {
return 1; }
let max_label = mu.parts.len();
if max_label == 0 {
return if total_cells == 0 { 1 } else { 0 };
}
let mut grid: Vec<Vec<usize>> = (0..n_rows)
.map(|r| vec![0usize; nu.parts.get(r).copied().unwrap_or(0)])
.collect();
let mut cells: Vec<(usize, usize)> = Vec::new();
for r in 0..n_rows {
let nu_r = nu.parts.get(r).copied().unwrap_or(0);
let la_r = lambda.parts.get(r).copied().unwrap_or(0);
for c in la_r..nu_r {
cells.push((r, c));
}
}
for r in 0..n_rows {
let la_r = lambda.parts.get(r).copied().unwrap_or(0);
for c in 0..la_r {
grid[r][c] = usize::MAX; }
}
let mut content = vec![0usize; max_label + 1]; let remaining: Vec<usize> = mu.parts.clone();
let count = backtrack_lr(
&cells,
0,
&mut grid,
&mut content,
&remaining,
max_label,
lambda,
nu,
);
count
}
#[allow(clippy::too_many_arguments)]
fn backtrack_lr(
cells: &[(usize, usize)],
idx: usize,
grid: &mut Vec<Vec<usize>>,
content: &mut Vec<usize>,
remaining: &[usize],
max_label: usize,
lambda: &Partition,
nu: &Partition,
) -> i64 {
if idx == cells.len() {
if is_ballot_sequence(grid, lambda, nu) {
return 1;
} else {
return 0;
}
}
let (r, c) = cells[idx];
let mut count = 0i64;
for label in 1..=max_label {
if content[label] >= remaining[label - 1] {
continue;
}
let la_r = lambda.parts.get(r).copied().unwrap_or(0);
if c > la_r {
let prev = grid[r][c - 1];
if prev != usize::MAX && label < prev {
continue;
}
}
if r > 0 {
let nr_prev = nu.parts.get(r - 1).copied().unwrap_or(0);
if c < nr_prev {
let above = grid[r - 1][c];
if above != usize::MAX && label <= above {
continue;
}
if above == usize::MAX && label == 0 {
continue; }
}
}
grid[r][c] = label;
content[label] += 1;
count += backtrack_lr(
cells,
idx + 1,
grid,
content,
remaining,
max_label,
lambda,
nu,
);
grid[r][c] = 0;
content[label] -= 1;
}
count
}
fn is_ballot_sequence(grid: &[Vec<usize>], lambda: &Partition, nu: &Partition) -> bool {
let n_rows = nu.parts.len();
let max_label = grid
.iter()
.flat_map(|r| r.iter().copied())
.filter(|&v| v != usize::MAX && v > 0)
.max()
.unwrap_or(0);
if max_label == 0 {
return true;
}
let mut prefix_count = vec![0usize; max_label + 1];
for r in 0..n_rows {
let nu_r = nu.parts.get(r).copied().unwrap_or(0);
let la_r = lambda.parts.get(r).copied().unwrap_or(0);
for c in (la_r..nu_r).rev() {
let v = grid[r][c];
if v == 0 || v == usize::MAX {
continue;
}
prefix_count[v] += 1;
for k in 1..max_label {
if prefix_count[k] < prefix_count[k + 1] {
return false;
}
}
}
}
true
}
pub fn schur_product_expansion(
lambda: &Partition,
mu: &Partition,
max_parts: usize,
) -> HashMap<Partition, i64> {
let target_weight = lambda.weight() + mu.weight();
let mut result = HashMap::new();
let partitions = generate_partitions(target_weight, max_parts);
for nu in partitions {
let coeff = littlewood_richardson(lambda, mu, &nu);
if coeff != 0 {
result.insert(nu, coeff);
}
}
result
}
pub fn generate_partitions(n: usize, max_parts: usize) -> Vec<Partition> {
let mut result = Vec::new();
let mut current = Vec::new();
gen_parts(n, n, max_parts, &mut current, &mut result);
result
}
fn gen_parts(
remaining: usize,
max_part: usize,
max_parts: usize,
current: &mut Vec<usize>,
result: &mut Vec<Partition>,
) {
if remaining == 0 {
if !current.is_empty() {
result.push(Partition {
parts: current.clone(),
});
}
return;
}
if current.len() >= max_parts {
return;
}
let limit = max_part.min(remaining);
for p in (1..=limit).rev() {
current.push(p);
gen_parts(remaining - p, p, max_parts, current, result);
current.pop();
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_partition_new_valid() {
let p = Partition::new(vec![3, 2, 1]).expect("valid partition");
assert_eq!(p.parts, vec![3, 2, 1]);
assert_eq!(p.weight(), 6);
assert_eq!(p.n(), 3);
}
#[test]
fn test_partition_new_invalid() {
let result = Partition::new(vec![2, 3, 1]);
assert!(result.is_err(), "Non-decreasing should be rejected");
}
#[test]
fn test_partition_conjugate() {
let p = Partition::new(vec![3, 2, 1]).expect("valid");
let conj = p.conjugate();
assert_eq!(conj.parts, vec![3, 2, 1]);
let p2 = Partition::new(vec![3, 3]).expect("valid");
let conj2 = p2.conjugate();
assert_eq!(conj2.parts, vec![2, 2, 2]);
}
#[test]
fn test_partition_dominates() {
let p1 = Partition::new(vec![3, 1]).expect("valid");
let p2 = Partition::new(vec![2, 2]).expect("valid");
assert!(p1.dominates(&p2));
assert!(!p2.dominates(&p1));
}
#[test]
fn test_elementary_symmetric() {
let vars = [1.0, 2.0, 3.0];
assert_relative_eq!(elementary_symmetric(0, &vars), 1.0, epsilon = 1e-12);
assert_relative_eq!(elementary_symmetric(1, &vars), 6.0, epsilon = 1e-12);
assert_relative_eq!(elementary_symmetric(2, &vars), 11.0, epsilon = 1e-12);
assert_relative_eq!(elementary_symmetric(3, &vars), 6.0, epsilon = 1e-12);
assert_relative_eq!(elementary_symmetric(4, &vars), 0.0, epsilon = 1e-12);
}
#[test]
fn test_complete_homogeneous() {
let vars = [2.0, 3.0];
assert_relative_eq!(complete_homogeneous(0, &vars), 1.0, epsilon = 1e-12);
assert_relative_eq!(complete_homogeneous(1, &vars), 5.0, epsilon = 1e-12);
assert_relative_eq!(complete_homogeneous(2, &vars), 19.0, epsilon = 1e-12);
}
#[test]
fn test_power_sum() {
let vars = [1.0, 2.0, 3.0];
assert_relative_eq!(power_sum(0, &vars), 3.0, epsilon = 1e-12);
assert_relative_eq!(power_sum(1, &vars), 6.0, epsilon = 1e-12);
assert_relative_eq!(power_sum(2, &vars), 14.0, epsilon = 1e-12);
}
#[test]
fn test_schur_partition_1_xyz() {
let vars = [1.5, 2.0, 3.5];
let lambda = Partition::new(vec![1]).expect("valid");
let s = schur_polynomial(&lambda, &vars);
let p1 = power_sum(1, &vars);
assert_relative_eq!(s, p1, epsilon = 1e-10);
}
#[test]
fn test_schur_partition_2_xy() {
let x = 2.0f64;
let y = 3.0f64;
let vars = [x, y];
let lambda = Partition::new(vec![2]).expect("valid");
let s = schur_polynomial(&lambda, &vars);
let expected = x * x + x * y + y * y;
assert_relative_eq!(s, expected, epsilon = 1e-10);
}
#[test]
fn test_schur_empty_partition() {
let vars = [1.0, 2.0];
let lambda = Partition::new(vec![]).expect("valid");
let s = schur_polynomial(&lambda, &vars);
assert_relative_eq!(s, 1.0, epsilon = 1e-12);
}
#[test]
fn test_littlewood_richardson_simple() {
let lambda = Partition::new(vec![1]).expect("valid");
let mu = Partition::new(vec![1, 1]).expect("valid");
let nu = Partition::new(vec![2, 1]).expect("valid");
let c = littlewood_richardson(&lambda, &mu, &nu);
assert_eq!(c, 1, "LR coefficient c^(2,1)_(1),(1,1) should be 1");
}
#[test]
fn test_littlewood_richardson_zero() {
let lambda = Partition::new(vec![1]).expect("valid");
let mu = Partition::new(vec![1]).expect("valid");
let nu = Partition::new(vec![3]).expect("valid");
let c = littlewood_richardson(&lambda, &mu, &nu);
assert_eq!(c, 0);
}
#[test]
fn test_littlewood_richardson_s1_s1() {
let lambda = Partition::new(vec![1]).expect("valid");
let mu = Partition::new(vec![1]).expect("valid");
let nu2 = Partition::new(vec![2]).expect("valid");
let nu11 = Partition::new(vec![1, 1]).expect("valid");
assert_eq!(littlewood_richardson(&lambda, &mu, &nu2), 1);
assert_eq!(littlewood_richardson(&lambda, &mu, &nu11), 1);
}
#[test]
fn test_monomial_symmetric() {
let vars = [1.0, 2.0, 3.0];
let p = Partition::new(vec![1]).expect("valid");
let m = monomial_symmetric(&p, &vars);
assert_relative_eq!(m, 6.0, epsilon = 1e-12);
let p11 = Partition::new(vec![1, 1]).expect("valid");
let m11 = monomial_symmetric(&p11, &vars);
let e2 = elementary_symmetric(2, &vars);
assert_relative_eq!(m11, e2, epsilon = 1e-12);
}
#[test]
fn test_generate_partitions() {
let parts = generate_partitions(4, 4);
assert_eq!(parts.len(), 5);
}
}