use rust_physics_engine::discrete::combinatorics::{
binomial_u64, catalan, catalan_mod, is_permutation, nth_permutation, permutation_compose,
permutation_cycle_type, permutation_index, permutation_inverse, permutation_order,
permutation_sign, permutation_to_cycles, random_permutation, stirling_second,
twelvefold_way,
};
use rust_physics_engine::discrete::disjoint_set::DisjointSet;
use rust_physics_engine::discrete::partitions::{
durfee_square, hook_lengths, partition_conjugate, partition_count, partitions_iter,
rsk_correspondence, standard_tableaux_count,
};
use rust_physics_engine::discrete::sequences::{
fibonacci_mod, find_linear_recurrence, linear_recurrence, linear_recurrence_mod,
pisano_period,
};
use rust_physics_engine::exact::bigint::BigInt;
use rust_physics_engine::exact::rational::Rational;
use rust_physics_engine::monte_carlo::Rng;
fn pick(rng: &mut Rng, n: u64) -> u64 {
((u128::from(rng.next_u64()) * u128::from(n)) >> 64) as u64
}
#[test]
fn prop_permutation_group_laws() {
let mut rng = Rng::new(0xC0FFEE);
for _ in 0..300 {
let n = 1 + pick(&mut rng, 9) as usize;
let a = random_permutation(n, &mut rng);
let b = random_permutation(n, &mut rng);
assert!(is_permutation(&a) && is_permutation(&b));
let ab = permutation_compose(&a, &b);
assert_eq!(permutation_sign(&ab), permutation_sign(&a) * permutation_sign(&b));
let inv = permutation_inverse(&a);
let id: Vec<usize> = (0..n).collect();
assert_eq!(permutation_compose(&a, &inv), id);
assert_eq!(permutation_compose(&inv, &a), id);
assert_eq!(permutation_cycle_type(&a), permutation_cycle_type(&inv));
assert_eq!(permutation_order(&a), permutation_order(&inv));
let g = random_permutation(n, &mut rng);
let gi = permutation_inverse(&g);
let conj = permutation_compose(&permutation_compose(&g, &a), &gi);
assert_eq!(permutation_cycle_type(&conj), permutation_cycle_type(&a));
let order = permutation_order(&a).to_i64().unwrap() as usize;
let mut acc = id.clone();
for step in 1..=order {
acc = permutation_compose(&acc, &a);
if step < order {
assert_ne!(acc, id, "returned to the identity at step {step}");
}
}
assert_eq!(acc, id);
assert_eq!(permutation_cycle_type(&a).iter().sum::<usize>(), n);
assert_eq!(permutation_to_cycles(&a).len(), permutation_cycle_type(&a).len());
}
}
#[test]
fn prop_factoradic_index_round_trips() {
let mut rng = Rng::new(7_654_321);
for _ in 0..300 {
let n = 1 + pick(&mut rng, 12) as usize;
let total = BigInt::factorial(n as u64);
let idx = BigInt::random_below(&total, &mut rng);
let p = nth_permutation(n, &idx);
assert!(is_permutation(&p));
assert_eq!(permutation_index(&p), idx);
}
let mut rng = Rng::new(99);
for _ in 0..200 {
let n = 2 + pick(&mut rng, 8) as usize;
let total = BigInt::factorial(n as u64);
let i = BigInt::random_below(&total, &mut rng);
let j = BigInt::random_below(&total, &mut rng);
let (pi, pj) = (nth_permutation(n, &i), nth_permutation(n, &j));
assert_eq!(i < j, pi < pj, "order not preserved at {i} vs {j}");
}
}
#[test]
fn prop_binomial_u64_agrees_with_bigint() {
let mut rng = Rng::new(31_337);
let max = BigInt::from_str_radix(&u64::MAX.to_string(), 10).unwrap();
for _ in 0..2_000 {
let n = pick(&mut rng, 200);
let k = pick(&mut rng, n + 1);
let exact = BigInt::binomial(n, k);
match binomial_u64(n, k) {
Some(v) => {
assert_eq!(BigInt::from_u64(v), exact, "C({n}, {k})");
}
None => assert!(exact > max, "C({n}, {k}) fits but was refused"),
}
}
}
#[test]
fn prop_catalan_mod_agrees_with_exact() {
let mut rng = Rng::new(2_718_281);
for _ in 0..200 {
let n = pick(&mut rng, 60);
let m = 1 + pick(&mut rng, 10_000);
let exact = catalan(n)
.rem_euclid(&BigInt::from_u64(m))
.to_i64()
.unwrap() as u64;
assert_eq!(catalan_mod(n, m), exact, "C({n}) mod {m}");
}
}
#[test]
fn prop_partition_conjugation_is_an_involution() {
let mut rng = Rng::new(161_803);
for _ in 0..200 {
let n = 1 + pick(&mut rng, 40);
let all: Vec<Vec<u64>> = partitions_iter(n).collect();
assert_eq!(
BigInt::from_u64(all.len() as u64),
partition_count(n),
"enumeration count disagrees at n = {n}"
);
let p = &all[pick(&mut rng, all.len() as u64) as usize];
let c = partition_conjugate(p);
assert_eq!(c.iter().sum::<u64>(), n);
assert_eq!(partition_conjugate(&c), *p);
assert_eq!(c.len() as u64, p[0]);
assert_eq!(p.len() as u64, c[0]);
assert_eq!(durfee_square(p), durfee_square(&c));
let hp = hook_lengths(p);
let hc = hook_lengths(&c);
for (i, row) in hp.iter().enumerate() {
for (j, &h) in row.iter().enumerate() {
assert_eq!(h, hc[j][i], "hook ({i}, {j}) of {p:?}");
}
}
assert_eq!(standard_tableaux_count(p), standard_tableaux_count(&c));
}
}
#[test]
fn prop_rsk_shape_is_schensted() {
let mut rng = Rng::new(1_414_213);
for _ in 0..300 {
let n = 1 + pick(&mut rng, 30) as usize;
let perm = random_permutation(n, &mut rng);
let (p, q) = rsk_correspondence(&perm);
let shape_p: Vec<usize> = p.iter().map(Vec::len).collect();
let shape_q: Vec<usize> = q.iter().map(Vec::len).collect();
assert_eq!(shape_p, shape_q);
assert!(shape_p.windows(2).all(|w| w[0] >= w[1]));
assert_eq!(shape_p.iter().sum::<usize>(), n);
let mut inc = vec![1usize; n];
let mut dec = vec![1usize; n];
for i in 0..n {
for j in 0..i {
if perm[j] < perm[i] {
inc[i] = inc[i].max(inc[j] + 1);
}
if perm[j] > perm[i] {
dec[i] = dec[i].max(dec[j] + 1);
}
}
}
assert_eq!(p[0].len(), *inc.iter().max().unwrap(), "{perm:?}");
assert_eq!(p.len(), *dec.iter().max().unwrap(), "{perm:?}");
let (pi, qi) = rsk_correspondence(&permutation_inverse(&perm));
assert_eq!(pi, q);
assert_eq!(qi, p);
}
}
#[test]
fn prop_berlekamp_massey_regenerates() {
let mut rng = Rng::new(577_215);
for _ in 0..200 {
let order = 1 + pick(&mut rng, 4) as usize;
let coeffs: Vec<i64> = (0..order)
.map(|_| pick(&mut rng, 11) as i64 - 5)
.collect();
let init: Vec<i64> = (0..order)
.map(|_| pick(&mut rng, 11) as i64 - 5)
.collect();
let terms: Vec<Rational> = (0..4 * order as u64 + 4)
.map(|i| Rational::from_int(linear_recurrence(&init, &coeffs, i)))
.collect();
let Some(found) = find_linear_recurrence(&terms) else {
assert!(terms.iter().all(Rational::is_zero) || terms.len() < 2);
continue;
};
assert!(found.len() <= order, "order {} exceeds {order}", found.len());
for i in found.len()..terms.len() {
let mut acc = Rational::zero();
for (j, cj) in found.iter().enumerate() {
acc = acc.add(&cj.mul(&terms[i - 1 - j]));
}
assert_eq!(acc, terms[i], "regeneration fails at {i} for {coeffs:?}");
}
}
}
#[test]
fn prop_linear_recurrence_mod_agrees_with_iteration() {
let mut rng = Rng::new(1_729);
for _ in 0..300 {
let order = 1 + pick(&mut rng, 3) as usize;
let coeffs: Vec<i64> = (0..order).map(|_| pick(&mut rng, 9) as i64 - 4).collect();
let init: Vec<i64> = (0..order).map(|_| pick(&mut rng, 9) as i64 - 4).collect();
let n = pick(&mut rng, 120);
let m = 1 + pick(&mut rng, 1_000_000);
let direct = linear_recurrence(&init, &coeffs, n)
.rem_euclid(&BigInt::from_u64(m))
.to_i64()
.unwrap() as u64;
assert_eq!(
linear_recurrence_mod(&init, &coeffs, n, m),
direct,
"init {init:?}, coeffs {coeffs:?}, n = {n}, m = {m}"
);
}
let mut rng = Rng::new(4_669);
for _ in 0..200 {
let n = pick(&mut rng, 500);
let m = 1 + pick(&mut rng, 5_000);
let direct = linear_recurrence(&[0, 1], &[1, 1], n)
.rem_euclid(&BigInt::from_u64(m))
.to_i64()
.unwrap() as u64;
assert_eq!(fibonacci_mod(n, m), direct, "F({n}) mod {m}");
let p = pisano_period(m);
assert_eq!(fibonacci_mod(n + p, m), direct, "period {p} for m = {m}");
}
}
#[test]
fn prop_disjoint_set_matches_transitive_closure() {
let mut rng = Rng::new(0x00D1_5C0D);
for _ in 0..40 {
let n = 2 + pick(&mut rng, 30) as usize;
let mut ds = DisjointSet::new(n);
let mut reach = vec![vec![false; n]; n];
for (i, row) in reach.iter_mut().enumerate() {
row[i] = true;
}
let mut merges = 0usize;
for _ in 0..2 * n {
let a = pick(&mut rng, n as u64) as usize;
let b = pick(&mut rng, n as u64) as usize;
if ds.union(a, b) {
merges += 1;
}
let ca: Vec<usize> = (0..n).filter(|&i| reach[i][a]).collect();
let cb: Vec<usize> = (0..n).filter(|&i| reach[i][b]).collect();
for &i in &ca {
for &j in &cb {
reach[i][j] = true;
reach[j][i] = true;
}
}
}
for i in 0..n {
for j in 0..n {
assert_eq!(ds.connected(i, j), reach[i][j], "({i}, {j}) of {n}");
}
}
assert_eq!(ds.count(), n - merges);
let sets = ds.sets();
assert_eq!(sets.len(), ds.count());
assert_eq!(sets.iter().map(Vec::len).sum::<usize>(), n);
}
}
#[test]
fn prop_twelvefold_surjections_by_inclusion_exclusion() {
let mut rng = Rng::new(8_675_309);
for _ in 0..200 {
let n = pick(&mut rng, 15);
let k = pick(&mut rng, 12);
let mut by_ie = BigInt::zero();
for j in 0..=k {
let term = BigInt::binomial(k, j).mul(&BigInt::from_u64(k - j).pow(n));
if j % 2 == 0 {
by_ie = by_ie.add(&term);
} else {
by_ie = by_ie.sub(&term);
}
}
assert_eq!(
twelvefold_way(n, k, None, Some(true), true, true),
by_ie,
"surjections from {n} onto {k}"
);
assert_eq!(BigInt::factorial(k).mul(&stirling_second(n, k)), by_ie);
}
}