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
20pub const MAX_BINOMIAL_INPUT: u64 = MAX_FACTORIAL_INPUT;
27
28fn big(n: u64) -> BigUint {
29 BigUint::from(n)
30}
31
32pub fn factorial(n: u64) -> BigUint {
34 (1..=n).fold(BigUint::from(1u32), |acc, i| acc * big(i))
35}
36
37pub fn factorial_checked(n: u64) -> Result<BigUint, CombError> {
42 if n > MAX_FACTORIAL_INPUT {
43 return Err(CombError::LimitExceeded(format!(
44 "factorial input {n} exceeds maximum {MAX_FACTORIAL_INPUT}"
45 )));
46 }
47 Ok(factorial(n))
48}
49
50pub fn falling_factorial(n: u64, k: u64) -> BigUint {
52 if k > n {
53 return BigUint::from(0u32);
54 }
55 (0..k).fold(BigUint::from(1u32), |acc, i| acc * big(n - i))
56}
57
58pub fn permutation_count(n: u64, k: u64) -> BigUint {
60 falling_factorial(n, k)
61}
62
63pub fn binomial(n: u64, k: u64) -> BigUint {
76 if k > n {
77 return BigUint::from(0u32);
78 }
79 let k = k.min(n - k);
80 let mut result = BigUint::from(1u32);
81 for i in 1..=k {
82 result = result * big(n - k + i) / big(i);
84 }
85 result
86}
87
88pub fn binomial_checked(n: u64, k: u64, max_iter: u64) -> Result<BigUint, CombError> {
94 let effective = k.min(n.saturating_sub(k));
95 if effective > max_iter {
96 return Err(CombError::LimitExceeded(format!(
97 "binomial k={effective} exceeds maximum {max_iter}"
98 )));
99 }
100 Ok(binomial(n, k))
101}
102
103pub fn multinomial(parts: &[u64]) -> BigUint {
105 let total: u64 = parts.iter().sum();
106 let mut denom = BigUint::from(1u32);
107 for &p in parts {
108 denom *= factorial(p);
109 }
110 factorial(total) / denom
111}
112
113pub fn stirling2(n: u64, k: u64) -> BigUint {
116 let (n, k) = (n as usize, k as usize);
117 let mut dp = vec![BigUint::from(0u32); k + 1];
118 dp[0] = BigUint::from(1u32); for _ in 1..=n {
120 let mut next = vec![BigUint::from(0u32); k + 1];
121 for j in 1..=k {
122 next[j] = big(j as u64) * &dp[j] + &dp[j - 1];
123 }
124 dp = next;
125 }
126 if k < dp.len() {
127 dp[k].clone()
128 } else {
129 BigUint::from(0u32)
130 }
131}
132
133pub fn bell_number(n: u64) -> BigUint {
135 (0..=n).map(|k| stirling2(n, k)).sum()
136}
137
138pub fn integer_partition_count(n: u64) -> BigUint {
141 let n = n as usize;
142 let mut dp = vec![BigUint::from(0u32); n + 1];
143 dp[0] = BigUint::from(1u32);
144 for part in 1..=n {
145 for j in part..=n {
146 dp[j] = &dp[j] + &dp[j - part].clone();
147 }
148 }
149 dp[n].clone()
150}
151
152pub fn integer_partition_count_checked(n: u64) -> Result<BigUint, CombError> {
157 if n > MAX_PARTITION_INPUT {
158 return Err(CombError::LimitExceeded(format!(
159 "partition-count input {n} exceeds maximum {MAX_PARTITION_INPUT}"
160 )));
161 }
162 Ok(integer_partition_count(n))
163}
164
165#[cfg(test)]
166mod tests {
167 use super::*;
168
169 fn b(n: u64) -> BigUint {
170 BigUint::from(n)
171 }
172
173 #[test]
174 fn factorial_basics() {
175 assert_eq!(factorial(0), b(1));
176 assert_eq!(factorial(5), b(120));
177 }
178
179 #[test]
180 fn binomial_and_permutations() {
181 assert_eq!(binomial(5, 2), b(10));
182 assert_eq!(binomial(10, 5), b(252));
183 assert_eq!(binomial(5, 7), b(0));
184 assert_eq!(binomial(6, 0), b(1));
185 assert_eq!(permutation_count(5, 3), b(60));
186 assert_eq!(falling_factorial(5, 2), b(20));
187 }
188
189 #[test]
190 fn multinomial_value() {
191 assert_eq!(multinomial(&[2, 1, 1]), b(12));
193 }
194
195 #[test]
196 fn stirling_bell_and_partitions() {
197 assert_eq!(stirling2(4, 2), b(7));
198 assert_eq!(bell_number(4), b(15));
199 assert_eq!(integer_partition_count(5), b(7));
200 }
201
202 #[test]
203 fn checked_counts_accept_small_inputs() {
204 assert_eq!(factorial_checked(5).unwrap(), b(120));
205 assert_eq!(integer_partition_count_checked(5).unwrap(), b(7));
206 assert_eq!(binomial_checked(5, 2, MAX_BINOMIAL_INPUT).unwrap(), b(10));
207 assert_eq!(binomial_checked(2, 5, MAX_BINOMIAL_INPUT).unwrap(), b(0));
209 }
210
211 #[test]
212 fn checked_counts_reject_huge_inputs() {
213 assert!(matches!(
214 factorial_checked(MAX_FACTORIAL_INPUT + 1),
215 Err(CombError::LimitExceeded(_))
216 ));
217 assert!(matches!(
218 integer_partition_count_checked(u64::MAX),
219 Err(CombError::LimitExceeded(_))
220 ));
221 assert!(matches!(
223 binomial_checked(1_000_000_000_000, 500_000_000_000, MAX_BINOMIAL_INPUT),
224 Err(CombError::LimitExceeded(_))
225 ));
226 }
227}