sim_lib_discrete_comb/
count.rs1use num_bigint::BigUint;
4
5use crate::error::CombError;
6
7pub const MAX_FACTORIAL_INPUT: u64 = 50_000;
12
13pub const MAX_PARTITION_INPUT: u64 = 10_000;
19
20fn big(n: u64) -> BigUint {
21 BigUint::from(n)
22}
23
24pub fn factorial(n: u64) -> BigUint {
26 (1..=n).fold(BigUint::from(1u32), |acc, i| acc * big(i))
27}
28
29pub fn factorial_checked(n: u64) -> Result<BigUint, CombError> {
34 if n > MAX_FACTORIAL_INPUT {
35 return Err(CombError::LimitExceeded(format!(
36 "factorial input {n} exceeds maximum {MAX_FACTORIAL_INPUT}"
37 )));
38 }
39 Ok(factorial(n))
40}
41
42pub fn falling_factorial(n: u64, k: u64) -> BigUint {
44 if k > n {
45 return BigUint::from(0u32);
46 }
47 (0..k).fold(BigUint::from(1u32), |acc, i| acc * big(n - i))
48}
49
50pub fn permutation_count(n: u64, k: u64) -> BigUint {
52 falling_factorial(n, k)
53}
54
55pub fn binomial(n: u64, k: u64) -> BigUint {
68 if k > n {
69 return BigUint::from(0u32);
70 }
71 let k = k.min(n - k);
72 let mut result = BigUint::from(1u32);
73 for i in 1..=k {
74 result = result * big(n - k + i) / big(i);
76 }
77 result
78}
79
80pub fn multinomial(parts: &[u64]) -> BigUint {
82 let total: u64 = parts.iter().sum();
83 let mut denom = BigUint::from(1u32);
84 for &p in parts {
85 denom *= factorial(p);
86 }
87 factorial(total) / denom
88}
89
90pub fn stirling2(n: u64, k: u64) -> BigUint {
93 let (n, k) = (n as usize, k as usize);
94 let mut dp = vec![BigUint::from(0u32); k + 1];
95 dp[0] = BigUint::from(1u32); for _ in 1..=n {
97 let mut next = vec![BigUint::from(0u32); k + 1];
98 for j in 1..=k {
99 next[j] = big(j as u64) * &dp[j] + &dp[j - 1];
100 }
101 dp = next;
102 }
103 if k < dp.len() {
104 dp[k].clone()
105 } else {
106 BigUint::from(0u32)
107 }
108}
109
110pub fn bell_number(n: u64) -> BigUint {
112 (0..=n).map(|k| stirling2(n, k)).sum()
113}
114
115pub fn integer_partition_count(n: u64) -> BigUint {
118 let n = n as usize;
119 let mut dp = vec![BigUint::from(0u32); n + 1];
120 dp[0] = BigUint::from(1u32);
121 for part in 1..=n {
122 for j in part..=n {
123 dp[j] = &dp[j] + &dp[j - part].clone();
124 }
125 }
126 dp[n].clone()
127}
128
129pub fn integer_partition_count_checked(n: u64) -> Result<BigUint, CombError> {
134 if n > MAX_PARTITION_INPUT {
135 return Err(CombError::LimitExceeded(format!(
136 "partition-count input {n} exceeds maximum {MAX_PARTITION_INPUT}"
137 )));
138 }
139 Ok(integer_partition_count(n))
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 fn b(n: u64) -> BigUint {
147 BigUint::from(n)
148 }
149
150 #[test]
151 fn factorial_basics() {
152 assert_eq!(factorial(0), b(1));
153 assert_eq!(factorial(5), b(120));
154 }
155
156 #[test]
157 fn binomial_and_permutations() {
158 assert_eq!(binomial(5, 2), b(10));
159 assert_eq!(binomial(10, 5), b(252));
160 assert_eq!(binomial(5, 7), b(0));
161 assert_eq!(binomial(6, 0), b(1));
162 assert_eq!(permutation_count(5, 3), b(60));
163 assert_eq!(falling_factorial(5, 2), b(20));
164 }
165
166 #[test]
167 fn multinomial_value() {
168 assert_eq!(multinomial(&[2, 1, 1]), b(12));
170 }
171
172 #[test]
173 fn stirling_bell_and_partitions() {
174 assert_eq!(stirling2(4, 2), b(7));
175 assert_eq!(bell_number(4), b(15));
176 assert_eq!(integer_partition_count(5), b(7));
177 }
178
179 #[test]
180 fn checked_counts_accept_small_inputs() {
181 assert_eq!(factorial_checked(5).unwrap(), b(120));
182 assert_eq!(integer_partition_count_checked(5).unwrap(), b(7));
183 }
184
185 #[test]
186 fn checked_counts_reject_huge_inputs() {
187 assert!(matches!(
188 factorial_checked(MAX_FACTORIAL_INPUT + 1),
189 Err(CombError::LimitExceeded(_))
190 ));
191 assert!(matches!(
192 integer_partition_count_checked(u64::MAX),
193 Err(CombError::LimitExceeded(_))
194 ));
195 }
196}