use rust_physics_engine::graph::core::Graph;
use rust_physics_engine::linalg::matrix::Matrix;
use rust_physics_engine::monte_carlo::Rng;
use rust_physics_engine::optimization::integer::{
bin_packing_exact_small, bin_packing_ffd, bin_packing_lower_bound, branch_and_bound,
edit_distance, knapsack_01, knapsack_branch_bound, longest_common_subsequence,
longest_increasing_subsequence, set_cover_exact_small, set_cover_greedy, subset_sum,
subset_sum_count,
};
use rust_physics_engine::optimization::lp::{simplex, LpProblem};
use rust_physics_engine::optimization::network::{
critical_path_method, lpt_makespan, max_flow_lp_check, shortest_path_lp_check,
};
fn pick(rng: &mut Rng, n: usize) -> usize {
((u128::from(rng.next_u64()) * n as u128) >> 64) as usize
}
fn random_digraph(n: usize, density: f64, rng: &mut Rng) -> Graph {
let mut g = Graph::new(n, true);
for u in 0..n {
for v in 0..n {
if u != v && rng.next_f64() < density {
g.add_edge(u, v, (rng.next_f64() * 9.0).round() + 1.0);
}
}
}
g
}
#[test]
fn prop_the_shortest_path_program_and_dijkstra_never_disagree() {
let mut rng = Rng::new(0x_5407_2001);
let mut compared = 0usize;
for _ in 0..60 {
let n = 4 + pick(&mut rng, 5);
let g = random_digraph(n, 0.45, &mut rng);
let (distances, _) = rust_physics_engine::graph::paths::dijkstra(&g, 0);
for t in 1..n {
let lp = shortest_path_lp_check(&g, 0, t).unwrap();
match (distances[t].is_finite(), lp) {
(true, Some(value)) => {
compared += 1;
assert!(
(value - distances[t]).abs() < 1e-6,
"to {t}: the program gave {value}, Dijkstra {}",
distances[t]
);
}
(false, None) => {}
(reachable, other) => panic!(
"disagreed on reachability to {t}: Dijkstra {reachable}, program {other:?}"
),
}
}
}
assert!(compared > 100, "only {compared} pairs were comparable");
}
#[test]
fn prop_the_max_flow_program_and_the_augmenting_path_search_never_disagree() {
let mut rng = Rng::new(0x_F108_2002);
for _ in 0..50 {
let n = 4 + pick(&mut rng, 4);
let g = random_digraph(n, 0.5, &mut rng);
let combinatorial = rust_physics_engine::graph::flow::max_flow(&g, 0, n - 1);
let lp = max_flow_lp_check(&g, 0, n - 1).unwrap().unwrap_or(0.0);
assert!(
(lp - combinatorial).abs() < 1e-6,
"the program gave {lp}, the augmenting-path method {combinatorial}"
);
let out: f64 = g.adj[0].iter().map(|&(_, w)| w).sum();
assert!(combinatorial <= out + 1e-9, "the flow exceeds the source's capacity");
}
}
#[test]
fn prop_the_knapsack_table_and_search_tree_always_agree() {
let mut rng = Rng::new(0x_C0FF_2003);
for _ in 0..400 {
let n = 1 + pick(&mut rng, 16);
let values: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 50)).collect();
let weights: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 25)).collect();
let capacity = 1 + (rng.next_u64() % 80);
let (table_value, table_pick) = knapsack_01(&values, &weights, capacity);
let (tree_value, tree_pick) = knapsack_branch_bound(&values, &weights, capacity);
assert_eq!(table_value, tree_value, "the two knapsack methods disagreed");
for (label, picks) in [("table", &table_pick), ("tree", &tree_pick)] {
let w: u64 = picks.iter().enumerate().filter(|(_, &t)| t).map(|(i, _)| weights[i]).sum();
let v: u64 = picks.iter().enumerate().filter(|(_, &t)| t).map(|(i, _)| values[i]).sum();
assert!(w <= capacity, "{label} overfilled the sack");
assert_eq!(v, table_value, "{label}'s selection is worth {v}");
}
}
}
#[test]
fn prop_branch_and_bound_never_beats_its_own_relaxation() {
let mut rng = Rng::new(0x_B4B0_2004);
let mut checked = 0usize;
for _ in 0..150 {
let n = 2 + pick(&mut rng, 3);
let m = 1 + pick(&mut rng, 3);
let mut a = Matrix::zeros(m, n);
for i in 0..m {
for j in 0..n {
a.set(i, j, (rng.next_f64() * 4.0).round() + 1.0);
}
}
let b: Vec<f64> = (0..m).map(|_| (rng.next_f64() * 20.0).round() + 4.0).collect();
let c: Vec<f64> = (0..n).map(|_| (rng.next_f64() * 9.0).round() + 1.0).collect();
let mut p = LpProblem::new(c, a, b, true).unwrap();
for j in 0..n {
p.bounds[j] = (0.0, 12.0);
}
let vars: Vec<usize> = (0..n).collect();
let Some((x, value)) = branch_and_bound(&p, &vars, 200_000).unwrap() else { continue };
checked += 1;
assert!(p.is_feasible(&x, 1e-6), "the integer answer is infeasible");
assert!(
x.iter().all(|v| (v - v.round()).abs() < 1e-6),
"a variable came back fractional: {x:?}"
);
let relaxed = simplex(&p).unwrap().objective().unwrap();
assert!(
value <= relaxed + 1e-6,
"the integer optimum {value} beat its relaxation {relaxed}"
);
assert!((p.objective_at(&x) - value).abs() < 1e-9);
}
assert!(checked > 100, "only {checked} of 150 programs had an integer optimum");
}
#[test]
fn prop_greedy_packing_and_covering_stay_inside_their_proven_ratios() {
let mut rng = Rng::new(0x_B1CE_2005);
for _ in 0..120 {
let n = 1 + pick(&mut rng, 9);
let sizes: Vec<f64> = (0..n).map(|_| rng.next_f64() * 0.75 + 0.05).collect();
let greedy = bin_packing_ffd(&sizes, 1.0);
let exact = bin_packing_exact_small(&sizes, 1.0);
let bound = bin_packing_lower_bound(&sizes, 1.0);
let mut seen = vec![0usize; n];
for bin in &greedy {
let load: f64 = bin.iter().map(|&i| sizes[i]).sum();
assert!(load <= 1.0 + 1e-9, "a bin holds {load}");
for &i in bin {
seen[i] += 1;
}
}
assert!(seen.iter().all(|&k| k == 1), "an item was lost or duplicated");
assert!(exact.len() >= bound, "the exact packing beat the volume bound");
assert!(greedy.len() >= exact.len(), "greedy beat the optimum");
let guarantee = 11.0 / 9.0 * exact.len() as f64 + 6.0 / 9.0;
assert!(
greedy.len() as f64 <= guarantee + 1e-9,
"{} bins exceeds the guarantee {guarantee} against {}",
greedy.len(),
exact.len()
);
let universe = 3 + pick(&mut rng, 7);
let sets: Vec<Vec<usize>> = (0..2 + pick(&mut rng, 7))
.map(|_| (0..universe).filter(|_| rng.next_f64() < 0.45).collect())
.collect();
match (set_cover_greedy(universe, &sets), set_cover_exact_small(universe, &sets)) {
(Some(g), Some(e)) => {
let mut covered = vec![false; universe];
for &i in &g {
for &v in &sets[i] {
if v < universe {
covered[v] = true;
}
}
}
assert!(covered.iter().all(|&c| c), "the greedy cover is incomplete");
let harmonic: f64 = (1..=universe).map(|k| 1.0 / k as f64).sum();
assert!(
g.len() as f64 <= harmonic * e.len() as f64 + 1e-9,
"{} sets exceeds H_n times {}",
g.len(),
e.len()
);
}
(None, None) => {}
_ => panic!("greedy and exact disagreed on whether a cover exists"),
}
}
}
#[test]
fn prop_longest_processing_time_stays_inside_its_ratio() {
let mut rng = Rng::new(0x_1B70_2006);
for _ in 0..100 {
let machines = 2 + pick(&mut rng, 3);
let n = machines + pick(&mut rng, 5);
if n > 8 {
continue;
}
let jobs: Vec<f64> = (0..n).map(|_| (rng.next_f64() * 9.0).round() + 1.0).collect();
let (makespan, assignment) = lpt_makespan(&jobs, machines);
let loads: Vec<f64> = (0..machines)
.map(|m| {
jobs.iter().enumerate().filter(|(i, _)| assignment[*i] == m).map(|(_, &d)| d).sum()
})
.collect();
assert!((loads.iter().copied().fold(0.0f64, f64::max) - makespan).abs() < 1e-9);
assert!((loads.iter().sum::<f64>() - jobs.iter().sum::<f64>()).abs() < 1e-9);
let mut best = f64::INFINITY;
let mut counter = vec![0usize; n];
loop {
let mut load = vec![0.0f64; machines];
for (i, &m) in counter.iter().enumerate() {
load[m] += jobs[i];
}
best = best.min(load.iter().copied().fold(0.0f64, f64::max));
let mut k = 0usize;
while k < n {
counter[k] += 1;
if counter[k] < machines {
break;
}
counter[k] = 0;
k += 1;
}
if k == n {
break;
}
}
let ratio = 4.0 / 3.0 - 1.0 / (3.0 * machines as f64);
assert!(makespan >= best - 1e-9, "greedy beat the optimum");
assert!(
makespan <= ratio * best + 1e-9,
"makespan {makespan} exceeds {ratio} times {best}"
);
}
}
#[test]
fn prop_the_critical_path_bounds_every_schedule() {
let mut rng = Rng::new(0x_C97A_2007);
for _ in 0..120 {
let n = 2 + pick(&mut rng, 8);
let tasks: Vec<(f64, Vec<usize>)> = (0..n)
.map(|i| {
let preds: Vec<usize> = (0..i).filter(|_| rng.next_f64() < 0.35).collect();
((rng.next_f64() * 9.0).round() + 1.0, preds)
})
.collect();
let (duration, critical, times) = critical_path_method(&tasks).unwrap();
let mut longest = vec![0.0f64; n];
for i in 0..n {
longest[i] =
tasks[i].0 + tasks[i].1.iter().map(|&p| longest[p]).fold(0.0f64, f64::max);
}
let expected = longest.iter().copied().fold(0.0f64, f64::max);
assert!((duration - expected).abs() < 1e-9, "{duration} against {expected}");
for i in 0..n {
for &p in &tasks[i].1 {
assert!(times[p].early_finish <= times[i].early_start + 1e-9);
assert!(times[i].late_start + 1e-9 >= times[p].late_finish);
}
assert_eq!(critical.contains(&i), times[i].slack().abs() < 1e-9);
assert!(times[i].slack() >= -1e-9, "negative slack at task {i}");
}
assert!(!critical.is_empty(), "no task is critical");
}
}
#[test]
fn prop_the_dynamic_programming_classics_return_what_they_claim() {
let mut rng = Rng::new(0x_D9C1_2008);
for _ in 0..200 {
let n = 1 + pick(&mut rng, 12);
let xs: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 20)).collect();
let target = rng.next_u64() % 50;
let mut brute = 0u64;
for mask in 0u32..(1u32 << n) {
let s: u64 = (0..n).filter(|k| mask & (1 << k) != 0).map(|k| xs[k]).sum();
if s == target {
brute += 1;
}
}
assert_eq!(subset_sum_count(&xs, target).to_string(), brute.to_string());
match subset_sum(&xs, target) {
Some(indices) => {
assert!(brute > 0, "found a subset where none exists");
assert_eq!(indices.iter().map(|&i| xs[i]).sum::<u64>(), target);
}
None => assert_eq!(brute, 0, "missed an existing subset"),
}
let sequence: Vec<f64> =
(0..1 + pick(&mut rng, 25)).map(|_| (rng.next_f64() * 15.0).round()).collect();
let lis = longest_increasing_subsequence(&sequence);
assert!(lis.windows(2).all(|w| w[0] < w[1] && sequence[w[0]] < sequence[w[1]]));
let m = sequence.len();
let mut best = vec![1usize; m];
for i in 1..m {
for j in 0..i {
if sequence[j] < sequence[i] && best[j] + 1 > best[i] {
best[i] = best[j] + 1;
}
}
}
assert_eq!(lis.len(), *best.iter().max().unwrap_or(&0));
let word = |rng: &mut Rng, k: usize| -> Vec<u8> {
(0..k).map(|_| b'a' + (rng.next_u64() % 3) as u8).collect()
};
let (ka, kb, kc) = (pick(&mut rng, 8), pick(&mut rng, 8), pick(&mut rng, 8));
let (a, b, c) = (word(&mut rng, ka), word(&mut rng, kb), word(&mut rng, kc));
assert_eq!(edit_distance(&a, &b), edit_distance(&b, &a));
assert!(edit_distance(&a, &b) <= edit_distance(&a, &c) + edit_distance(&c, &b));
let lcs = longest_common_subsequence(&a, &b);
let is_sub = |s: &[u8], whole: &[u8]| {
let mut it = whole.iter();
s.iter().all(|ch| it.any(|w| w == ch))
};
assert!(is_sub(&lcs, &a) && is_sub(&lcs, &b), "the subsequence is not common");
assert!(edit_distance(&a, &b) <= a.len() + b.len() - 2 * lcs.len());
}
}