#![allow(clippy::manual_isolate_lowest_one)]
use crate::error::GeomError;
use crate::exact::bigint::BigInt;
use crate::optimization::lp::{simplex, Cmp, LpProblem, LpResult};
const INTEGRALITY_TOL: f64 = 1e-7;
pub fn branch_and_bound(
p: &LpProblem,
integer_vars: &[usize],
node_limit: usize,
) -> Result<Option<(Vec<f64>, f64)>, GeomError> {
p.validate()?;
if integer_vars.iter().any(|&j| j >= p.n()) {
return Err(GeomError::InvalidArgument("branch_and_bound: variable index out of range"));
}
let better = |a: f64, b: f64| if p.maximize { a > b } else { a < b };
let mut best: Option<(Vec<f64>, f64)> = None;
let mut stack = vec![p.clone()];
let mut nodes = 0usize;
while let Some(node) = stack.pop() {
nodes += 1;
if nodes > node_limit {
break;
}
let LpResult::Optimal { x, objective, .. } = simplex(&node)? else {
continue;
};
if let Some((_, incumbent)) = &best {
if !better(objective, *incumbent) {
continue;
}
}
let fractional = integer_vars
.iter()
.copied()
.find(|&j| (x[j] - x[j].round()).abs() > INTEGRALITY_TOL);
let Some(j) = fractional else {
let rounded: Vec<f64> = x
.iter()
.enumerate()
.map(|(k, &v)| if integer_vars.contains(&k) { v.round() } else { v })
.collect();
let value = node.objective_at(&rounded);
if best.as_ref().is_none_or(|(_, incumbent)| better(value, *incumbent)) {
best = Some((rounded, value));
}
continue;
};
let floor = x[j].floor();
for (bound, side) in [(floor, Cmp::Le), (floor + 1.0, Cmp::Ge)] {
let mut child = node.clone();
let (lo, hi) = child.bounds[j];
match side {
Cmp::Le => {
if bound < lo - INTEGRALITY_TOL {
continue;
}
child.bounds[j] = (lo, hi.min(bound));
}
Cmp::Ge => {
if bound > hi + INTEGRALITY_TOL {
continue;
}
child.bounds[j] = (lo.max(bound), hi);
}
Cmp::Eq => unreachable!("branching uses only inequalities"),
}
if child.bounds[j].0 <= child.bounds[j].1 + INTEGRALITY_TOL {
stack.push(child);
}
}
}
Ok(best)
}
pub fn gomory_cuts(
p: &LpProblem,
integer_vars: &[usize],
max_cuts: usize,
) -> Result<LpProblem, GeomError> {
p.validate()?;
if integer_vars.len() != p.n() || (0..p.n()).any(|j| !integer_vars.contains(&j)) {
return Err(GeomError::InvalidArgument(
"gomory_cuts requires every variable to be an integer variable",
));
}
if p.bounds.iter().any(|&(lo, _)| lo < 0.0) {
return Err(GeomError::InvalidArgument(
"gomory_cuts requires non-negative variables; the rounding step needs it",
));
}
let mut out = p.clone();
for _ in 0..max_cuts {
let LpResult::Optimal { x, .. } = simplex(&out)? else {
break;
};
if x.iter().all(|v| (v - v.round()).abs() <= INTEGRALITY_TOL) {
break;
}
let mut best: Option<(Vec<f64>, f64, f64)> = None;
for i in 0..out.m() {
let orientations: &[f64] = match out.constraint_types[i] {
Cmp::Le => &[1.0],
Cmp::Ge => &[-1.0],
Cmp::Eq => &[1.0, -1.0],
};
for &sign in orientations {
let row: Vec<f64> = (0..out.n()).map(|j| sign * out.a.get(i, j)).collect();
let rhs = sign * out.b[i];
let mut multipliers: Vec<f64> = vec![0.5, 1.0 / 3.0, 2.0 / 3.0, 0.25];
for &v in &row {
if v.abs() > 1e-9 {
multipliers.push(1.0 / v.abs());
}
}
for lambda in multipliers {
if !(lambda > 0.0) || !lambda.is_finite() {
continue;
}
let cut: Vec<f64> = row.iter().map(|&v| (lambda * v).floor()).collect();
let bound = (lambda * rhs).floor();
if cut.iter().all(|&v| v == 0.0) {
continue;
}
let lhs: f64 = cut.iter().zip(&x).map(|(a, b)| a * b).sum();
let violation = lhs - bound;
if violation > 1e-6
&& best.as_ref().is_none_or(|(_, _, v)| violation > *v)
{
best = Some((cut, bound, violation));
}
}
}
}
let Some((cut, bound, _)) = best else { break };
let (m, n) = (out.m(), out.n());
let mut a = crate::linalg::matrix::Matrix::zeros(m + 1, n);
for i in 0..m {
for j in 0..n {
a.set(i, j, out.a.get(i, j));
}
}
for (j, &v) in cut.iter().enumerate() {
a.set(m, j, v);
}
out.a = a;
out.b.push(bound);
out.constraint_types.push(Cmp::Le);
}
Ok(out)
}
#[must_use]
pub fn knapsack_01(values: &[u64], weights: &[u64], capacity: u64) -> (u64, Vec<bool>) {
assert!(values.len() == weights.len(), "knapsack_01 needs one weight per value");
let n = values.len();
let cap = capacity as usize;
let mut table = vec![vec![0u64; cap + 1]; n + 1];
for i in 1..=n {
let w = weights[i - 1] as usize;
for c in 0..=cap {
table[i][c] = table[i - 1][c];
if w <= c {
let with = table[i - 1][c - w] + values[i - 1];
if with > table[i][c] {
table[i][c] = with;
}
}
}
}
let mut chosen = vec![false; n];
let mut c = cap;
for i in (1..=n).rev() {
if table[i][c] != table[i - 1][c] {
chosen[i - 1] = true;
c -= weights[i - 1] as usize;
}
}
(table[n][cap], chosen)
}
#[must_use]
pub fn knapsack_unbounded(values: &[u64], weights: &[u64], capacity: u64) -> (u64, Vec<u64>) {
assert!(values.len() == weights.len(), "knapsack_unbounded needs one weight per value");
assert!(weights.iter().all(|&w| w > 0), "knapsack_unbounded requires positive weights");
let cap = capacity as usize;
let mut best = vec![0u64; cap + 1];
let mut taken = vec![usize::MAX; cap + 1];
for c in 1..=cap {
for (i, (&v, &w)) in values.iter().zip(weights).enumerate() {
let w = w as usize;
if w <= c && best[c - w] + v > best[c] {
best[c] = best[c - w] + v;
taken[c] = i;
}
}
}
let mut counts = vec![0u64; values.len()];
let mut c = cap;
while c > 0 && taken[c] != usize::MAX {
let i = taken[c];
counts[i] += 1;
c -= weights[i] as usize;
}
(best[cap], counts)
}
#[must_use]
pub fn knapsack_bounded(
values: &[u64],
weights: &[u64],
limits: &[u64],
capacity: u64,
) -> (u64, Vec<u64>) {
assert!(
values.len() == weights.len() && values.len() == limits.len(),
"knapsack_bounded needs one weight and limit per value"
);
let mut expanded_values = Vec::new();
let mut expanded_weights = Vec::new();
let mut origin = Vec::new();
let mut multiplicity = Vec::new();
for (i, ((&v, &w), &limit)) in values.iter().zip(weights).zip(limits).enumerate() {
let mut remaining = limit;
let mut piece = 1u64;
while remaining > 0 {
let take = piece.min(remaining);
expanded_values.push(v * take);
expanded_weights.push(w * take);
origin.push(i);
multiplicity.push(take);
remaining -= take;
piece *= 2;
}
}
let (best, chosen) = knapsack_01(&expanded_values, &expanded_weights, capacity);
let mut counts = vec![0u64; values.len()];
for (k, &taken) in chosen.iter().enumerate() {
if taken {
counts[origin[k]] += multiplicity[k];
}
}
(best, counts)
}
#[must_use]
pub fn knapsack_multiple(
values: &[u64],
weights: &[u64],
capacities: &[u64],
) -> (u64, Vec<Option<usize>>) {
assert!(values.len() == weights.len(), "knapsack_multiple needs one weight per value");
let n = values.len();
let mut order: Vec<usize> = (0..n).collect();
order.sort_by(|&a, &b| {
let da = values[a] as f64 / weights[a].max(1) as f64;
let db = values[b] as f64 / weights[b].max(1) as f64;
db.partial_cmp(&da).unwrap_or(std::cmp::Ordering::Equal)
});
let mut remaining: Vec<u64> = capacities.to_vec();
let mut placement = vec![None; n];
let mut total = 0u64;
for &i in &order {
if let Some(bin) = remaining.iter().position(|&r| r >= weights[i]) {
remaining[bin] -= weights[i];
placement[i] = Some(bin);
total += values[i];
}
}
(total, placement)
}
#[must_use]
pub fn knapsack_branch_bound(values: &[u64], weights: &[u64], capacity: u64) -> (u64, Vec<bool>) {
assert!(values.len() == weights.len(), "knapsack_branch_bound needs one weight per value");
let n = values.len();
if n == 0 {
return (0, Vec::new());
}
let mut order: Vec<usize> = (0..n).collect();
order.sort_by(|&a, &b| {
let da = values[a] as f64 / weights[a].max(1) as f64;
let db = values[b] as f64 / weights[b].max(1) as f64;
db.partial_cmp(&da).unwrap_or(std::cmp::Ordering::Equal)
});
let bound = |k: usize, room: u64, value: u64| -> f64 {
let mut left = room;
let mut total = value as f64;
for &i in &order[k..] {
if weights[i] <= left {
left -= weights[i];
total += values[i] as f64;
} else {
total += values[i] as f64 * left as f64 / weights[i].max(1) as f64;
break;
}
}
total
};
let mut best_value = 0u64;
let mut best_take = vec![false; n];
let mut take = vec![false; n];
struct Frame {
depth: usize,
room: u64,
value: u64,
branch: u8,
}
let mut stack = vec![Frame { depth: 0, room: capacity, value: 0, branch: 0 }];
while let Some(frame) = stack.last_mut() {
let Frame { depth, room, value, branch } = *frame;
if depth == n || branch == 2 {
if value > best_value && depth == n {
best_value = value;
best_take.copy_from_slice(&take);
}
if depth == n && branch == 0 {
}
stack.pop();
if let Some(parent) = stack.last() {
let i = order[parent.depth];
take[i] = false;
}
continue;
}
frame.branch += 1;
let i = order[depth];
let (next_room, next_value, feasible) = if branch == 0 {
(room.checked_sub(weights[i]), value + values[i], weights[i] <= room)
} else {
(Some(room), value, true)
};
if !feasible {
continue;
}
let room_left = next_room.unwrap_or(0);
if bound(depth + 1, room_left, next_value) <= best_value as f64 {
continue;
}
take[i] = branch == 0;
if next_value > best_value {
best_value = next_value;
best_take.copy_from_slice(&take);
}
stack.push(Frame { depth: depth + 1, room: room_left, value: next_value, branch: 0 });
}
(best_value, best_take)
}
#[must_use]
pub fn subset_sum(xs: &[u64], target: u64) -> Option<Vec<usize>> {
let t = target as usize;
let n = xs.len();
let mut reachable = vec![vec![false; t + 1]; n + 1];
for row in reachable.iter_mut() {
row[0] = true;
}
for i in 1..=n {
let v = xs[i - 1] as usize;
for s in 0..=t {
reachable[i][s] = reachable[i - 1][s] || (v <= s && reachable[i - 1][s - v]);
}
}
if !reachable[n][t] {
return None;
}
let mut chosen = Vec::new();
let mut s = t;
for i in (1..=n).rev() {
let v = xs[i - 1] as usize;
if !reachable[i - 1][s] {
chosen.push(i - 1);
s -= v;
}
}
chosen.reverse();
Some(chosen)
}
#[must_use]
pub fn subset_sum_count(xs: &[u64], target: u64) -> BigInt {
let t = target as usize;
let mut counts = vec![BigInt::zero(); t + 1];
counts[0] = BigInt::one();
for &v in xs {
let v = v as usize;
if v > t {
continue;
}
for s in (v..=t).rev() {
let carried = counts[s - v].clone();
counts[s] = counts[s].add(&carried);
}
}
counts[t].clone()
}
#[must_use]
pub fn partition_min_diff(xs: &[u64]) -> (u64, Vec<bool>) {
let total: u64 = xs.iter().sum();
let half = (total / 2) as usize;
let n = xs.len();
let mut reachable = vec![vec![false; half + 1]; n + 1];
for row in reachable.iter_mut() {
row[0] = true;
}
for i in 1..=n {
let v = xs[i - 1] as usize;
for s in 0..=half {
reachable[i][s] = reachable[i - 1][s] || (v <= s && reachable[i - 1][s - v]);
}
}
let best = (0..=half).rev().find(|&s| reachable[n][s]).unwrap_or(0);
let mut flags = vec![false; n];
let mut s = best;
for i in (1..=n).rev() {
let v = xs[i - 1] as usize;
if !reachable[i - 1][s] {
flags[i - 1] = true;
s -= v;
}
}
(total - 2 * best as u64, flags)
}
#[must_use]
pub fn bin_packing_ffd(sizes: &[f64], capacity: f64) -> Vec<Vec<usize>> {
assert!(capacity > 0.0, "bin_packing_ffd requires a positive capacity");
assert!(
sizes.iter().all(|&s| s <= capacity + 1e-12 && s >= 0.0),
"every item must fit in an empty bin"
);
let mut order: Vec<usize> = (0..sizes.len()).collect();
order.sort_by(|&a, &b| sizes[b].partial_cmp(&sizes[a]).unwrap_or(std::cmp::Ordering::Equal));
let mut bins: Vec<Vec<usize>> = Vec::new();
let mut room: Vec<f64> = Vec::new();
for &i in &order {
match room.iter().position(|&r| r >= sizes[i] - 1e-12) {
Some(b) => {
room[b] -= sizes[i];
bins[b].push(i);
}
None => {
room.push(capacity - sizes[i]);
bins.push(vec![i]);
}
}
}
bins
}
#[must_use]
pub fn bin_packing_lower_bound(sizes: &[f64], capacity: f64) -> usize {
assert!(capacity > 0.0, "bin_packing_lower_bound requires a positive capacity");
let total: f64 = sizes.iter().sum();
(total / capacity).ceil().max(0.0) as usize
}
#[must_use]
pub fn bin_packing_exact_small(sizes: &[f64], capacity: f64) -> Vec<Vec<usize>> {
assert!(capacity > 0.0, "bin_packing_exact_small requires a positive capacity");
assert!(sizes.len() <= 12, "bin_packing_exact_small is for instances of at most twelve items");
let n = sizes.len();
if n == 0 {
return Vec::new();
}
let lower = bin_packing_lower_bound(sizes, capacity).max(1);
for count in lower..=n {
let mut assignment = vec![usize::MAX; n];
let mut room = vec![capacity; count];
if pack(sizes, 0, &mut assignment, &mut room) {
let mut bins = vec![Vec::new(); count];
for (i, &b) in assignment.iter().enumerate() {
bins[b].push(i);
}
return bins;
}
}
(0..n).map(|i| vec![i]).collect()
}
fn pack(sizes: &[f64], i: usize, assignment: &mut Vec<usize>, room: &mut Vec<f64>) -> bool {
if i == sizes.len() {
return true;
}
for b in 0..room.len() {
if room[b] >= sizes[i] - 1e-12 {
room[b] -= sizes[i];
assignment[i] = b;
if pack(sizes, i + 1, assignment, room) {
return true;
}
room[b] += sizes[i];
assignment[i] = usize::MAX;
}
}
false
}
#[must_use]
pub fn set_cover_greedy(universe_n: usize, sets: &[Vec<usize>]) -> Option<Vec<usize>> {
let mut covered = vec![false; universe_n];
let mut chosen = Vec::new();
let mut remaining = universe_n;
while remaining > 0 {
let best = (0..sets.len())
.filter(|i| !chosen.contains(i))
.max_by_key(|&i| sets[i].iter().filter(|&&e| e < universe_n && !covered[e]).count());
let best = best?;
let gain = sets[best].iter().filter(|&&e| e < universe_n && !covered[e]).count();
if gain == 0 {
return None;
}
for &e in &sets[best] {
if e < universe_n && !covered[e] {
covered[e] = true;
remaining -= 1;
}
}
chosen.push(best);
}
Some(chosen)
}
#[must_use]
pub fn set_cover_exact_small(universe_n: usize, sets: &[Vec<usize>]) -> Option<Vec<usize>> {
assert!(sets.len() <= 20, "set_cover_exact_small is for at most twenty sets");
let m = sets.len();
let masks: Vec<u64> = sets
.iter()
.map(|s| s.iter().filter(|&&e| e < universe_n).fold(0u64, |acc, &e| acc | (1 << e)))
.collect();
let full = if universe_n >= 64 { u64::MAX } else { (1u64 << universe_n) - 1 };
for size in 0..=m {
for combination in 0u32..(1u32 << m) {
if combination.count_ones() as usize != size {
continue;
}
let mut union = 0u64;
for (i, &mask) in masks.iter().enumerate() {
if combination & (1 << i) != 0 {
union |= mask;
}
}
if union == full {
return Some((0..m).filter(|&i| combination & (1 << i) != 0).collect());
}
}
}
None
}
#[must_use]
pub fn facility_location_greedy(
open_costs: &[f64],
serve_costs: &crate::linalg::matrix::Matrix,
) -> (f64, Vec<bool>) {
let m = open_costs.len();
assert!(m > 0 && serve_costs.rows == m, "facility_location_greedy: shape mismatch");
let n = serve_costs.cols;
let mut open = vec![false; m];
let mut best_serve = vec![f64::INFINITY; n];
let mut total = f64::INFINITY;
loop {
let mut improvement: Option<(f64, usize, Vec<f64>)> = None;
for i in 0..m {
if open[i] {
continue;
}
let candidate: Vec<f64> =
(0..n).map(|j| best_serve[j].min(serve_costs.get(i, j))).collect();
let cost: f64 = open_costs[i]
+ candidate.iter().sum::<f64>()
+ (0..m).filter(|&k| open[k]).map(|k| open_costs[k]).sum::<f64>();
if cost < total && improvement.as_ref().is_none_or(|(best, _, _)| cost < *best) {
improvement = Some((cost, i, candidate));
}
}
let Some((cost, i, serve)) = improvement else { break };
open[i] = true;
best_serve = serve;
total = cost;
}
(total, open)
}
pub fn cutting_stock_column_generation(
demand: &[u64],
lengths: &[u64],
stock_length: u64,
max_rounds: usize,
) -> Result<f64, GeomError> {
if demand.len() != lengths.len() || demand.is_empty() {
return Err(GeomError::InvalidArgument("cutting_stock: one demand per length"));
}
if lengths.iter().any(|&l| l == 0 || l > stock_length) {
return Err(GeomError::InvalidArgument("cutting_stock: a piece does not fit the stock"));
}
let n = lengths.len();
let mut patterns: Vec<Vec<f64>> = (0..n)
.map(|i| {
let mut p = vec![0.0; n];
p[i] = (stock_length / lengths[i]) as f64;
p
})
.collect();
let mut value = f64::INFINITY;
for _ in 0..max_rounds {
let mut a = crate::linalg::matrix::Matrix::zeros(n, patterns.len());
for (k, pattern) in patterns.iter().enumerate() {
for (i, &count) in pattern.iter().enumerate() {
a.set(i, k, count);
}
}
let p = LpProblem {
c: vec![1.0; patterns.len()],
a,
b: demand.iter().map(|&d| d as f64).collect(),
constraint_types: vec![Cmp::Ge; n],
bounds: vec![(0.0, f64::INFINITY); patterns.len()],
maximize: false,
};
let LpResult::Optimal { objective, duals, .. } = simplex(&p)? else {
return Err(GeomError::Degenerate("cutting_stock: the relaxation has no optimum"));
};
value = objective;
let scale = 10_000.0;
let values: Vec<u64> = duals.iter().map(|&d| (d.max(0.0) * scale) as u64).collect();
let (best, counts) = knapsack_unbounded(&values, lengths, stock_length);
if best as f64 / scale <= 1.0 + 1e-6 {
break;
}
let column: Vec<f64> = counts.iter().map(|&c| c as f64).collect();
if patterns.contains(&column) {
break;
}
patterns.push(column);
}
Ok(value)
}
#[must_use]
pub fn coin_change_min(coins: &[u64], amount: u64) -> Option<Vec<u64>> {
let target = amount as usize;
let mut best = vec![usize::MAX; target + 1];
let mut used = vec![usize::MAX; target + 1];
best[0] = 0;
for s in 1..=target {
for (i, &c) in coins.iter().enumerate() {
let c = c as usize;
if c > 0 && c <= s && best[s - c] != usize::MAX && best[s - c] + 1 < best[s] {
best[s] = best[s - c] + 1;
used[s] = i;
}
}
}
if best[target] == usize::MAX {
return None;
}
let mut counts = vec![0u64; coins.len()];
let mut s = target;
while s > 0 {
let i = used[s];
counts[i] += 1;
s -= coins[i] as usize;
}
Some(counts)
}
#[must_use]
pub fn coin_change_count(coins: &[u64], amount: u64) -> BigInt {
let target = amount as usize;
let mut ways = vec![BigInt::zero(); target + 1];
ways[0] = BigInt::one();
for &c in coins {
let c = c as usize;
if c == 0 {
continue;
}
for s in c..=target {
let carried = ways[s - c].clone();
ways[s] = ways[s].add(&carried);
}
}
ways[target].clone()
}
#[must_use]
pub fn longest_increasing_subsequence(x: &[f64]) -> Vec<usize> {
let n = x.len();
if n == 0 {
return Vec::new();
}
let mut tails: Vec<usize> = Vec::new();
let mut previous = vec![usize::MAX; n];
for i in 0..n {
let mut lo = 0usize;
let mut hi = tails.len();
while lo < hi {
let mid = (lo + hi) / 2;
if x[tails[mid]] < x[i] {
lo = mid + 1;
} else {
hi = mid;
}
}
if lo > 0 {
previous[i] = tails[lo - 1];
}
if lo == tails.len() {
tails.push(i);
} else {
tails[lo] = i;
}
}
let mut out = Vec::with_capacity(tails.len());
let mut k = *tails.last().unwrap_or(&0);
while k != usize::MAX {
out.push(k);
k = previous[k];
}
out.reverse();
out
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EditOp {
Keep(usize, usize),
Substitute(usize, usize),
Delete(usize),
Insert(usize),
}
#[must_use]
pub fn edit_distance(a: &[u8], b: &[u8]) -> usize {
let (n, m) = (a.len(), b.len());
let mut previous: Vec<usize> = (0..=m).collect();
let mut current = vec![0usize; m + 1];
for i in 1..=n {
current[0] = i;
for j in 1..=m {
let cost = usize::from(a[i - 1] != b[j - 1]);
current[j] = (previous[j] + 1).min(current[j - 1] + 1).min(previous[j - 1] + cost);
}
std::mem::swap(&mut previous, &mut current);
}
previous[m]
}
#[must_use]
pub fn edit_distance_ops(a: &[u8], b: &[u8]) -> Vec<EditOp> {
let (n, m) = (a.len(), b.len());
let mut table = vec![vec![0usize; m + 1]; n + 1];
for (i, row) in table.iter_mut().enumerate() {
row[0] = i;
}
for j in 0..=m {
table[0][j] = j;
}
for i in 1..=n {
for j in 1..=m {
let cost = usize::from(a[i - 1] != b[j - 1]);
table[i][j] =
(table[i - 1][j] + 1).min(table[i][j - 1] + 1).min(table[i - 1][j - 1] + cost);
}
}
let mut ops = Vec::new();
let (mut i, mut j) = (n, m);
while i > 0 || j > 0 {
if i > 0 && j > 0 {
let cost = usize::from(a[i - 1] != b[j - 1]);
if table[i][j] == table[i - 1][j - 1] + cost {
ops.push(if cost == 0 {
EditOp::Keep(i - 1, j - 1)
} else {
EditOp::Substitute(i - 1, j - 1)
});
i -= 1;
j -= 1;
continue;
}
}
if i > 0 && table[i][j] == table[i - 1][j] + 1 {
ops.push(EditOp::Delete(i - 1));
i -= 1;
continue;
}
ops.push(EditOp::Insert(j - 1));
j -= 1;
}
ops.reverse();
ops
}
#[must_use]
pub fn longest_common_subsequence(a: &[u8], b: &[u8]) -> Vec<u8> {
let (n, m) = (a.len(), b.len());
let mut table = vec![vec![0usize; m + 1]; n + 1];
for i in 1..=n {
for j in 1..=m {
table[i][j] = if a[i - 1] == b[j - 1] {
table[i - 1][j - 1] + 1
} else {
table[i - 1][j].max(table[i][j - 1])
};
}
}
let mut out = Vec::with_capacity(table[n][m]);
let (mut i, mut j) = (n, m);
while i > 0 && j > 0 {
if a[i - 1] == b[j - 1] {
out.push(a[i - 1]);
i -= 1;
j -= 1;
} else if table[i - 1][j] >= table[i][j - 1] {
i -= 1;
} else {
j -= 1;
}
}
out.reverse();
out
}
#[must_use]
pub fn matrix_chain_order(dims: &[usize]) -> (u64, String) {
assert!(dims.len() >= 2, "matrix_chain_order needs at least one matrix");
let n = dims.len() - 1;
let mut cost = vec![vec![0u64; n]; n];
let mut split = vec![vec![0usize; n]; n];
for len in 2..=n {
for i in 0..=n - len {
let j = i + len - 1;
cost[i][j] = u64::MAX;
for k in i..j {
let c = cost[i][k]
+ cost[k + 1][j]
+ (dims[i] * dims[k + 1] * dims[j + 1]) as u64;
if c < cost[i][j] {
cost[i][j] = c;
split[i][j] = k;
}
}
}
}
fn render(split: &[Vec<usize>], i: usize, j: usize, out: &mut String) {
if i == j {
out.push_str(&format!("A{i}"));
return;
}
out.push('(');
render(split, i, split[i][j], out);
render(split, split[i][j] + 1, j, out);
out.push(')');
}
let mut rendered = String::new();
render(&split, 0, n - 1, &mut rendered);
(cost[0][n - 1], rendered)
}
#[must_use]
pub fn rod_cutting(prices: &[u64], n: usize) -> (u64, Vec<usize>) {
let mut best = vec![0u64; n + 1];
let mut first = vec![0usize; n + 1];
for length in 1..=n {
for (k, &price) in prices.iter().enumerate() {
let piece = k + 1;
if piece <= length && best[length - piece] + price > best[length] {
best[length] = best[length - piece] + price;
first[length] = piece;
}
}
}
let mut pieces = Vec::new();
let mut length = n;
while length > 0 && first[length] > 0 {
pieces.push(first[length]);
length -= first[length];
}
(best[n], pieces)
}
#[must_use]
pub fn egg_drop(eggs: usize, floors: usize) -> u64 {
if eggs == 0 || floors == 0 {
return 0;
}
let mut reach = vec![0u64; eggs + 1];
let mut drops = 0u64;
while (reach[eggs] as usize) < floors {
drops += 1;
for e in (1..=eggs).rev() {
reach[e] = reach[e] + reach[e - 1] + 1;
}
}
drops
}
#[must_use]
pub fn optimal_bst(frequencies: &[f64]) -> f64 {
let n = frequencies.len();
if n == 0 {
return 0.0;
}
let mut prefix = vec![0.0; n + 1];
for i in 0..n {
prefix[i + 1] = prefix[i] + frequencies[i];
}
let sum = |i: usize, j: usize| prefix[j + 1] - prefix[i];
let mut cost = vec![vec![0.0f64; n]; n];
for i in 0..n {
cost[i][i] = frequencies[i];
}
for len in 2..=n {
for i in 0..=n - len {
let j = i + len - 1;
cost[i][j] = f64::INFINITY;
for r in i..=j {
let left = if r > i { cost[i][r - 1] } else { 0.0 };
let right = if r < j { cost[r + 1][j] } else { 0.0 };
let c = left + right + sum(i, j);
if c < cost[i][j] {
cost[i][j] = c;
}
}
}
}
cost[0][n - 1]
}
pub fn viterbi_generic(
transition: &crate::linalg::matrix::Matrix,
emission: &crate::linalg::matrix::Matrix,
) -> Result<Vec<usize>, GeomError> {
let s = transition.rows;
if !transition.is_square() || emission.rows != s || emission.cols == 0 {
return Err(GeomError::InvalidArgument("viterbi_generic: shape mismatch"));
}
let t = emission.cols;
let mut cost = vec![vec![f64::INFINITY; s]; t];
let mut from = vec![vec![0usize; s]; t];
for i in 0..s {
cost[0][i] = emission.get(i, 0);
}
for step in 1..t {
for j in 0..s {
for i in 0..s {
let c = cost[step - 1][i] + transition.get(i, j) + emission.get(j, step);
if c < cost[step][j] {
cost[step][j] = c;
from[step][j] = i;
}
}
}
}
let mut best = 0usize;
for i in 1..s {
if cost[t - 1][i] < cost[t - 1][best] {
best = i;
}
}
let mut path = vec![0usize; t];
path[t - 1] = best;
for step in (1..t).rev() {
path[step - 1] = from[step][path[step]];
}
Ok(path)
}
pub fn exact_cover_dlx(matrix: &[Vec<bool>]) -> Result<Option<Vec<usize>>, GeomError> {
if matrix.is_empty() {
return Ok(Some(Vec::new()));
}
let cols = matrix[0].len();
if matrix.iter().any(|r| r.len() != cols) {
return Err(GeomError::InvalidArgument("exact_cover_dlx: ragged matrix"));
}
if cols > 64 {
return Err(GeomError::InvalidArgument("exact_cover_dlx: at most 64 columns"));
}
let rows: Vec<u64> = matrix
.iter()
.map(|r| r.iter().enumerate().filter(|(_, &v)| v).fold(0u64, |acc, (j, _)| acc | (1 << j)))
.collect();
let full = if cols == 64 { u64::MAX } else { (1u64 << cols) - 1 };
let mut chosen = Vec::new();
let mut used = vec![false; rows.len()];
if cover(&rows, full, 0, &mut used, &mut chosen) {
chosen.sort_unstable();
Ok(Some(chosen))
} else {
Ok(None)
}
}
fn cover(
rows: &[u64],
remaining: u64,
covered: u64,
used: &mut Vec<bool>,
chosen: &mut Vec<usize>,
) -> bool {
if covered == remaining {
return true;
}
let mut best_column = usize::MAX;
let mut best_count = usize::MAX;
for j in 0..64 {
let bit = 1u64 << j;
if bit > remaining {
break;
}
if remaining & bit == 0 || covered & bit != 0 {
continue;
}
let count = rows
.iter()
.enumerate()
.filter(|(i, &r)| !used[*i] && r & bit != 0 && r & covered == 0)
.count();
if count < best_count {
best_count = count;
best_column = j;
}
if count == 0 {
return false;
}
}
if best_column == usize::MAX {
return covered == remaining;
}
let bit = 1u64 << best_column;
for i in 0..rows.len() {
if used[i] || rows[i] & bit == 0 || rows[i] & covered != 0 {
continue;
}
used[i] = true;
chosen.push(i);
if cover(rows, remaining, covered | rows[i], used, chosen) {
return true;
}
chosen.pop();
used[i] = false;
}
false
}
#[must_use]
pub fn sudoku_solve(grid: &[[u8; 9]; 9]) -> Option<[[u8; 9]; 9]> {
let mut cells = *grid;
let (mut rows, mut cols, mut boxes) = ([0u16; 9], [0u16; 9], [0u16; 9]);
for r in 0..9 {
for c in 0..9 {
let v = cells[r][c];
if v == 0 {
continue;
}
if !(1..=9).contains(&v) {
return None;
}
let bit = 1u16 << (v - 1);
let b = (r / 3) * 3 + c / 3;
if rows[r] & bit != 0 || cols[c] & bit != 0 || boxes[b] & bit != 0 {
return None;
}
rows[r] |= bit;
cols[c] |= bit;
boxes[b] |= bit;
}
}
if fill(&mut cells, &mut rows, &mut cols, &mut boxes) {
Some(cells)
} else {
None
}
}
fn fill(
cells: &mut [[u8; 9]; 9],
rows: &mut [u16; 9],
cols: &mut [u16; 9],
boxes: &mut [u16; 9],
) -> bool {
let mut target: Option<(usize, usize, u16, u32)> = None;
for r in 0..9 {
for c in 0..9 {
if cells[r][c] != 0 {
continue;
}
let b = (r / 3) * 3 + c / 3;
let available = !(rows[r] | cols[c] | boxes[b]) & 0x1FF;
let count = available.count_ones();
if count == 0 {
return false;
}
if target.is_none_or(|(_, _, _, best)| count < best) {
target = Some((r, c, available, count));
}
}
}
let Some((r, c, available, _)) = target else { return true };
let b = (r / 3) * 3 + c / 3;
let mut options = available;
while options != 0 {
let bit = options & options.wrapping_neg();
options ^= bit;
let digit = bit.trailing_zeros() as u8 + 1;
cells[r][c] = digit;
rows[r] |= bit;
cols[c] |= bit;
boxes[b] |= bit;
if fill(cells, rows, cols, boxes) {
return true;
}
cells[r][c] = 0;
rows[r] ^= bit;
cols[c] ^= bit;
boxes[b] ^= bit;
}
false
}
#[must_use]
pub fn n_queens(n: usize) -> Vec<Vec<usize>> {
assert!(n <= 12, "n_queens is for boards up to twelve squares wide");
let mut solutions = Vec::new();
let mut placement = Vec::with_capacity(n);
queens(n, 0, 0, 0, &mut placement, &mut solutions, false);
solutions
}
#[must_use]
pub fn n_queens_count(n: usize) -> u64 {
assert!(n <= 14, "n_queens_count is for boards up to fourteen squares wide");
let mut solutions = Vec::new();
let mut placement = Vec::with_capacity(n);
queens(n, 0, 0, 0, &mut placement, &mut solutions, true) as u64
}
fn queens(
n: usize,
cols: u32,
left: u32,
right: u32,
placement: &mut Vec<usize>,
out: &mut Vec<Vec<usize>>,
count_only: bool,
) -> usize {
if placement.len() == n {
if !count_only {
out.push(placement.clone());
}
return 1;
}
let mask = if n == 32 { u32::MAX } else { (1u32 << n) - 1 };
let mut available = !(cols | left | right) & mask;
let mut found = 0usize;
while available != 0 {
let bit = available & available.wrapping_neg();
available ^= bit;
placement.push(bit.trailing_zeros() as usize);
found += queens(
n,
cols | bit,
(left | bit) << 1,
(right | bit) >> 1,
placement,
out,
count_only,
);
placement.pop();
}
found
}
#[must_use]
pub fn constraint_propagation_ac3(
domains: &[u64],
constraints: &[(usize, usize)],
) -> Option<Vec<u64>> {
let mut d = domains.to_vec();
let n = d.len();
if constraints.iter().any(|&(a, b)| a >= n || b >= n) {
return None;
}
let mut queue: Vec<(usize, usize)> = Vec::new();
for &(a, b) in constraints {
queue.push((a, b));
queue.push((b, a));
}
while let Some((a, b)) = queue.pop() {
let mut revised = false;
let mut values = d[a];
while values != 0 {
let bit = values & values.wrapping_neg();
values ^= bit;
if d[b] == bit {
d[a] &= !bit;
revised = true;
}
}
if d[a] == 0 {
return None;
}
if revised {
for &(x, y) in constraints {
if y == a && x != b {
queue.push((x, y));
}
if x == a && y != b {
queue.push((y, x));
}
}
}
}
Some(d)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::linalg::matrix::Matrix;
use crate::monte_carlo::Rng;
fn pick(rng: &mut Rng, n: usize) -> usize {
((u128::from(rng.next_u64()) * n as u128) >> 64) as usize
}
fn knapsack_brute(values: &[u64], weights: &[u64], capacity: u64) -> u64 {
let n = values.len();
let mut best = 0u64;
for mask in 0u32..(1u32 << n) {
let (mut w, mut v) = (0u64, 0u64);
for i in 0..n {
if mask & (1 << i) != 0 {
w += weights[i];
v += values[i];
}
}
if w <= capacity && v > best {
best = v;
}
}
best
}
#[test]
fn branch_and_bound_matches_exhaustive_integer_search() {
let mut rng = Rng::new(0xB4B0_0001);
let mut compared = 0usize;
for _ in 0..120 {
let n = 2 + pick(&mut rng, 2);
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() * 15.0).round() + 3.0).collect();
let c: Vec<f64> = (0..n).map(|_| (rng.next_f64() * 8.0).round() + 1.0).collect();
let mut p = LpProblem::new(c.clone(), a.clone(), b.clone(), true).unwrap();
for j in 0..n {
p.bounds[j] = (0.0, 8.0);
}
let integer_vars: Vec<usize> = (0..n).collect();
let Some((x, value)) = branch_and_bound(&p, &integer_vars, 100_000).unwrap() else {
continue;
};
compared += 1;
assert!(p.is_feasible(&x, 1e-6), "branch and bound returned {x:?}, not feasible");
assert!(
x.iter().all(|v| (v - v.round()).abs() < 1e-6),
"a variable came back fractional: {x:?}"
);
let mut best = f64::NEG_INFINITY;
let mut counter = vec![0usize; n];
loop {
let point: Vec<f64> = counter.iter().map(|&k| k as f64).collect();
if p.is_feasible(&point, 1e-9) {
best = best.max(p.objective_at(&point));
}
let mut k = 0usize;
while k < n {
counter[k] += 1;
if counter[k] <= 8 {
break;
}
counter[k] = 0;
k += 1;
}
if k == n {
break;
}
}
assert!(
(value - best).abs() < 1e-6,
"branch and bound gave {value}, exhaustive search {best}"
);
if let Some(relaxed) = simplex(&p).unwrap().objective() {
assert!(
value <= relaxed + 1e-6,
"the integer optimum {value} beat its own relaxation {relaxed}"
);
}
}
assert!(compared > 80, "only {compared} of 120 programs were comparable");
}
#[test]
fn branch_and_bound_reports_an_integer_infeasibility() {
let p = LpProblem {
c: vec![1.0],
a: Matrix::from_rows(&[&[2.0]]).unwrap(),
b: vec![1.0],
constraint_types: vec![Cmp::Eq],
bounds: vec![(0.0, 10.0)],
maximize: true,
};
assert!(simplex(&p).unwrap().objective().is_some(), "the relaxation should be feasible");
assert_eq!(branch_and_bound(&p, &[0], 10_000).unwrap(), None);
assert!(branch_and_bound(&p, &[9], 10).is_err());
}
#[test]
fn gomory_cuts_never_remove_an_integer_point() {
let mut rng = Rng::new(0x0060_0001);
for _ in 0..40 {
let n = 2usize;
let m = 2usize;
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() * 12.0).round() + 4.0).collect();
let c: Vec<f64> = (0..n).map(|_| (rng.next_f64() * 6.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, 10.0);
}
let Some((integer_point, integer_best)) =
branch_and_bound(&p, &[0, 1], 50_000).unwrap()
else {
continue;
};
let cut = gomory_cuts(&p, &[0, 1], 6).unwrap();
for a in 0..=10u32 {
for b in 0..=10u32 {
let point = [f64::from(a), f64::from(b)];
if p.is_feasible(&point, 1e-9) {
assert!(
cut.is_feasible(&point, 1e-6),
"the cuts removed the integer point {point:?}"
);
}
}
}
assert!(
cut.is_feasible(&integer_point, 1e-6),
"the cuts removed the integer optimum {integer_point:?}"
);
let before = simplex(&p).unwrap().objective().unwrap_or(f64::INFINITY);
if let Some(after) = simplex(&cut).unwrap().objective() {
assert!(after <= before + 1e-7, "the cut loosened the relaxation");
assert!(
after >= integer_best - 1e-7,
"the tightened bound {after} fell below the integer optimum {integer_best}"
);
}
}
}
#[test]
fn gomory_cuts_refuse_the_problems_the_rounding_argument_does_not_cover() {
let p = LpProblem::new(
vec![1.0, 1.0],
Matrix::from_rows(&[&[2.0, 3.0]]).unwrap(),
vec![7.0],
true,
)
.unwrap();
assert!(gomory_cuts(&p, &[0], 3).is_err(), "a continuous variable should be refused");
let mut negative = p.clone();
negative.bounds[0] = (f64::NEG_INFINITY, f64::INFINITY);
assert!(
gomory_cuts(&negative, &[0, 1], 3).is_err(),
"a free variable should be refused"
);
let integral = LpProblem::new(
vec![1.0],
Matrix::from_rows(&[&[1.0]]).unwrap(),
vec![4.0],
true,
)
.unwrap();
let same = gomory_cuts(&integral, &[0], 3).unwrap();
assert_eq!(same.m(), integral.m(), "a cut was added where none was needed");
}
#[test]
fn the_knapsack_table_and_the_search_tree_agree_with_brute_force() {
let mut rng = Rng::new(0xC0FF_0001);
for _ in 0..200 {
let n = 1 + pick(&mut rng, 12);
let values: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 40)).collect();
let weights: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 20)).collect();
let capacity = 5 + (rng.next_u64() % 60);
let (dp_value, chosen) = knapsack_01(&values, &weights, capacity);
let (bb_value, bb_chosen) = knapsack_branch_bound(&values, &weights, capacity);
let brute = knapsack_brute(&values, &weights, capacity);
assert_eq!(dp_value, brute, "the table disagreed with brute force");
assert_eq!(bb_value, brute, "branch and bound disagreed with brute force");
for (label, picks) in [("table", &chosen), ("branch and bound", &bb_chosen)] {
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: {w} > {capacity}");
assert_eq!(v, brute, "{label}'s selection is worth {v}, not {brute}");
}
}
}
#[test]
fn the_knapsack_variants_order_themselves_the_way_the_rules_imply() {
let values = [10u64, 30, 25, 50];
let weights = [5u64, 10, 6, 20];
let capacity = 30u64;
let (once, _) = knapsack_01(&values, &weights, capacity);
let (limited, counts) =
knapsack_bounded(&values, &weights, &[1, 1, 1, 1], capacity);
let (unlimited, repeats) = knapsack_unbounded(&values, &weights, capacity);
assert_eq!(limited, once, "bounded at one disagreed with 0/1");
assert!(counts.iter().all(|&c| c <= 1), "a limit of one was exceeded: {counts:?}");
assert!(unlimited >= once, "unbounded {unlimited} fell below 0/1 {once}");
let w: u64 = repeats.iter().zip(&weights).map(|(&c, &w)| c * w).sum();
let v: u64 = repeats.iter().zip(&values).map(|(&c, &v)| c * v).sum();
assert!(w <= capacity, "the unbounded pack overfilled: {w}");
assert_eq!(v, unlimited);
let mut previous = 0u64;
for limit in 1..=5u64 {
let (value, _) = knapsack_bounded(&values, &weights, &[limit; 4], capacity);
assert!(value >= previous, "raising the limit to {limit} reduced the value");
previous = value;
}
assert_eq!(previous, unlimited, "a high enough limit should reach the unbounded answer");
let (multi, placement) = knapsack_multiple(&values, &weights, &[15, 15]);
assert!(multi > 0);
for (i, spot) in placement.iter().enumerate() {
if let Some(bin) = spot {
assert!(*bin < 2, "item {i} went into bin {bin}");
}
}
for bin in 0..2 {
let load: u64 = placement
.iter()
.enumerate()
.filter(|(_, s)| **s == Some(bin))
.map(|(i, _)| weights[i])
.sum();
assert!(load <= 15, "bin {bin} holds {load}");
}
}
#[test]
fn subset_sum_finds_a_subset_and_counts_them_all() {
let mut rng = Rng::new(0x5085_0001);
for _ in 0..150 {
let n = 1 + pick(&mut rng, 12);
let xs: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 25)).collect();
let target = rng.next_u64() % 60;
let mut brute_count = 0u64;
let mut brute_found = false;
for mask in 0u32..(1u32 << n) {
let s: u64 = (0..n).filter(|i| mask & (1 << i) != 0).map(|i| xs[i]).sum();
if s == target {
brute_count += 1;
brute_found = true;
}
}
match subset_sum(&xs, target) {
Some(indices) => {
assert!(brute_found, "a subset was found where none exists");
let s: u64 = indices.iter().map(|&i| xs[i]).sum();
assert_eq!(s, target, "the reported subset sums to {s}, not {target}");
let mut sorted = indices.clone();
sorted.sort_unstable();
sorted.dedup();
assert_eq!(sorted.len(), indices.len(), "repeated index in {indices:?}");
}
None => assert!(!brute_found, "a subset exists but none was found"),
}
assert_eq!(
subset_sum_count(&xs, target).to_string(),
brute_count.to_string(),
"the count disagreed with brute force"
);
}
}
#[test]
fn the_partition_split_is_as_even_as_any_split_can_be() {
let mut rng = Rng::new(0x9A27_0001);
for _ in 0..120 {
let n = 1 + pick(&mut rng, 12);
let xs: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 30)).collect();
let total: u64 = xs.iter().sum();
let (difference, flags) = partition_min_diff(&xs);
let left: u64 =
flags.iter().enumerate().filter(|(_, &f)| f).map(|(i, _)| xs[i]).sum();
let right = total - left;
assert_eq!(
left.abs_diff(right),
difference,
"the reported flags give a gap of {}, not {difference}",
left.abs_diff(right)
);
let mut best = u64::MAX;
for mask in 0u32..(1u32 << n) {
let s: u64 = (0..n).filter(|i| mask & (1 << i) != 0).map(|i| xs[i]).sum();
best = best.min(s.abs_diff(total - s));
}
assert_eq!(difference, best, "a more even split exists");
}
}
#[test]
fn first_fit_decreasing_packs_validly_and_within_its_proven_ratio() {
let mut rng = Rng::new(0x00B1_0001);
for _ in 0..80 {
let n = 1 + pick(&mut rng, 10);
let sizes: Vec<f64> = (0..n).map(|_| rng.next_f64() * 0.7 + 0.05).collect();
let capacity = 1.0f64;
let bins = bin_packing_ffd(&sizes, capacity);
let mut seen = vec![0usize; n];
for bin in &bins {
let load: f64 = bin.iter().map(|&i| sizes[i]).sum();
assert!(load <= capacity + 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: {seen:?}");
let lower = bin_packing_lower_bound(&sizes, capacity);
assert!(bins.len() >= lower, "{} bins is below the bound {lower}", bins.len());
let exact = bin_packing_exact_small(&sizes, capacity);
assert!(exact.len() >= lower, "the exact packing beat the lower bound");
assert!(exact.len() <= bins.len(), "the exact packing used more bins than greedy");
let guarantee = 11.0 / 9.0 * exact.len() as f64 + 6.0 / 9.0;
assert!(
bins.len() as f64 <= guarantee + 1e-9,
"{} bins exceeds the guarantee {guarantee} against an optimum of {}",
bins.len(),
exact.len()
);
}
assert_eq!(bin_packing_lower_bound(&[0.4, 0.4, 0.4], 1.0), 2);
assert_eq!(bin_packing_exact_small(&[0.4, 0.4, 0.4], 1.0).len(), 2);
assert_eq!(bin_packing_exact_small(&[0.4; 4], 1.0).len(), 2);
assert_eq!(bin_packing_lower_bound(&[0.6; 3], 1.0), 2);
assert_eq!(bin_packing_exact_small(&[0.6; 3], 1.0).len(), 3);
assert!(bin_packing_ffd(&[], 1.0).is_empty());
}
#[test]
fn greedy_set_cover_covers_everything_within_its_harmonic_ratio() {
let mut rng = Rng::new(0x5E7C_0001);
for _ in 0..80 {
let universe = 3 + pick(&mut rng, 8);
let count = 2 + pick(&mut rng, 8);
let sets: Vec<Vec<usize>> = (0..count)
.map(|_| {
(0..universe).filter(|_| rng.next_f64() < 0.45).collect::<Vec<usize>>()
})
.collect();
let greedy = set_cover_greedy(universe, &sets);
let exact = set_cover_exact_small(universe, &sets);
match (&greedy, &exact) {
(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 misses an element");
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 * {} = {}",
g.len(),
e.len(),
harmonic * e.len() as f64
);
assert!(g.len() >= e.len(), "greedy beat the exact minimum");
}
(None, None) => {}
_ => panic!("greedy and exact disagreed on whether a cover exists"),
}
}
assert_eq!(set_cover_greedy(3, &[vec![0], vec![1]]), None);
assert_eq!(set_cover_exact_small(3, &[vec![0], vec![1]]), None);
}
#[test]
fn facility_location_opens_a_set_that_serves_every_client() {
let serve = Matrix::from_rows(&[
&[1.0, 9.0, 9.0],
&[9.0, 1.0, 9.0],
&[2.0, 2.0, 2.0],
])
.unwrap();
let (total, open) = facility_location_greedy(&[1.0, 1.0, 3.0], &serve);
assert!(open.iter().any(|&o| o), "no facility was opened");
assert!(total.is_finite() && total > 0.0);
let opening: f64 =
open.iter().enumerate().filter(|(_, &o)| o).map(|(i, _)| [1.0, 1.0, 3.0][i]).sum();
let serving: f64 = (0..3)
.map(|j| {
(0..3)
.filter(|&i| open[i])
.map(|i| serve.get(i, j))
.fold(f64::INFINITY, f64::min)
})
.sum();
assert!(
(total - opening - serving).abs() < 1e-9,
"reported {total} against {opening} + {serving}"
);
let open_costs = [1.0f64, 1.0, 3.0];
let best_single = (0..3)
.map(|i| open_costs[i] + (0..3).map(|j| serve.get(i, j)).sum::<f64>())
.fold(f64::INFINITY, f64::min);
assert!((best_single - 9.0).abs() < 1e-9, "the best single facility costs {best_single}");
assert!(total <= best_single + 1e-9, "greedy {total} lost to a single facility");
}
#[test]
fn column_generation_bounds_the_cutting_stock_problem() {
let value =
cutting_stock_column_generation(&[97, 610, 395], &[45, 36, 31], 100, 40).unwrap();
let total_length = 97 * 45 + 610 * 36 + 395 * 31;
let trivial = total_length as f64 / 100.0;
assert!(value >= trivial - 1e-6, "the relaxation {value} fell below the bound {trivial}");
assert!(value.is_finite() && value > 0.0);
let naive = 97.0 / 2.0 + 610.0 / 2.0 + 395.0 / 3.0;
assert!(value <= naive + 1e-6, "column generation {value} lost to the naive {naive}");
assert!(cutting_stock_column_generation(&[1], &[1, 2], 10, 5).is_err());
assert!(cutting_stock_column_generation(&[1], &[200], 100, 5).is_err());
assert!(cutting_stock_column_generation(&[], &[], 100, 5).is_err());
}
#[test]
fn coin_change_is_minimal_where_greedy_is_not() {
let counts = coin_change_min(&[1, 3, 4], 6).unwrap();
assert_eq!(counts.iter().sum::<u64>(), 2, "expected two coins, got {counts:?}");
let paid: u64 = counts.iter().zip([1u64, 3, 4]).map(|(&c, v)| c * v).sum();
assert_eq!(paid, 6);
let mut rng = Rng::new(0x0C01_0001);
for _ in 0..120 {
let k = 1 + pick(&mut rng, 4);
let coins: Vec<u64> = (0..k).map(|_| 1 + (rng.next_u64() % 12)).collect();
let amount = rng.next_u64() % 40;
match coin_change_min(&coins, amount) {
Some(counts) => {
let paid: u64 = counts.iter().zip(&coins).map(|(&c, &v)| c * v).sum();
assert_eq!(paid, amount, "the coins pay {paid}, not {amount}");
let mut best = vec![u64::MAX; amount as usize + 1];
best[0] = 0;
for s in 1..=amount as usize {
for &c in &coins {
let c = c as usize;
if c <= s && best[s - c] != u64::MAX {
best[s] = best[s].min(best[s - c] + 1);
}
}
}
assert_eq!(counts.iter().sum::<u64>(), best[amount as usize]);
}
None => {
let mut reachable = vec![false; amount as usize + 1];
reachable[0] = true;
for s in 1..=amount as usize {
reachable[s] = coins
.iter()
.any(|&c| c as usize <= s && reachable[s - c as usize]);
}
assert!(!reachable[amount as usize], "a combination exists");
}
}
}
assert_eq!(coin_change_count(&[1, 2], 3).to_string(), "2");
assert_eq!(coin_change_count(&[1, 2, 5], 11).to_string(), "11");
assert_eq!(coin_change_count(&[2], 3).to_string(), "0");
}
#[test]
fn the_longest_increasing_subsequence_is_increasing_and_longest() {
let mut rng = Rng::new(0x0011_0001);
for _ in 0..150 {
let n = pick(&mut rng, 40);
let x: Vec<f64> = (0..n).map(|_| (rng.next_f64() * 20.0).round()).collect();
let indices = longest_increasing_subsequence(&x);
if n == 0 {
assert!(indices.is_empty());
continue;
}
assert!(indices.windows(2).all(|w| w[0] < w[1]), "indices out of order");
assert!(
indices.windows(2).all(|w| x[w[0]] < x[w[1]]),
"the subsequence is not increasing"
);
let mut best = vec![1usize; n];
for i in 1..n {
for j in 0..i {
if x[j] < x[i] && best[j] + 1 > best[i] {
best[i] = best[j] + 1;
}
}
}
assert_eq!(indices.len(), *best.iter().max().unwrap_or(&0));
}
}
#[test]
fn edit_distance_is_a_metric_and_its_operations_reproduce_the_target() {
let mut rng = Rng::new(0x00ED_0001);
let word = |rng: &mut Rng, n: usize| -> Vec<u8> {
(0..n).map(|_| b'a' + (rng.below(4)) as u8).collect()
};
for _ in 0..150 {
let (la, lb, lc) = (pick(&mut rng, 9), pick(&mut rng, 9), pick(&mut rng, 9));
let a = word(&mut rng, la);
let b = word(&mut rng, lb);
let c = word(&mut rng, lc);
let d = edit_distance(&a, &b);
assert_eq!(d, edit_distance(&b, &a), "the distance is not symmetric");
assert_eq!(edit_distance(&a, &a), 0, "a sequence differs from itself");
if d == 0 {
assert_eq!(a, b, "distinct sequences at distance zero");
}
assert!(
d <= edit_distance(&a, &c) + edit_distance(&c, &b),
"the triangle inequality failed"
);
assert!(d <= a.len().max(b.len()), "the distance exceeds the longer length");
assert!(d >= a.len().abs_diff(b.len()), "the distance is below the length gap");
let ops = edit_distance_ops(&a, &b);
let mut rebuilt = Vec::new();
for op in &ops {
match *op {
EditOp::Keep(i, _) => rebuilt.push(a[i]),
EditOp::Substitute(_, j) | EditOp::Insert(j) => rebuilt.push(b[j]),
EditOp::Delete(_) => {}
}
}
assert_eq!(rebuilt, b, "replaying the edits did not reproduce b");
let cost = ops.iter().filter(|o| !matches!(o, EditOp::Keep(_, _))).count();
assert_eq!(cost, d, "the operation list costs {cost}, not {d}");
}
}
#[test]
fn the_common_subsequence_is_common_and_longest() {
let mut rng = Rng::new(0x01C5_0001);
for _ in 0..150 {
let a: Vec<u8> =
(0..pick(&mut rng, 12)).map(|_| b'a' + (rng.below(4)) as u8).collect();
let b: Vec<u8> =
(0..pick(&mut rng, 12)).map(|_| b'a' + (rng.below(4)) as u8).collect();
let lcs = longest_common_subsequence(&a, &b);
let is_sub = |s: &[u8], whole: &[u8]| -> bool {
let mut it = whole.iter();
s.iter().all(|c| it.any(|w| w == c))
};
assert!(is_sub(&lcs, &a), "{lcs:?} is not a subsequence of {a:?}");
assert!(is_sub(&lcs, &b), "{lcs:?} is not a subsequence of {b:?}");
let (n, m) = (a.len(), b.len());
let mut table = vec![vec![0usize; m + 1]; n + 1];
for i in 1..=n {
for j in 1..=m {
table[i][j] = if a[i - 1] == b[j - 1] {
table[i - 1][j - 1] + 1
} else {
table[i - 1][j].max(table[i][j - 1])
};
}
}
assert_eq!(lcs.len(), table[n][m]);
}
}
#[test]
fn the_matrix_chain_order_beats_every_parenthesisation() {
let (cost, order) = matrix_chain_order(&[40, 20, 30, 10, 30]);
assert_eq!(cost, 26_000, "got {cost} with {order}");
assert!(order.starts_with('('), "the rendering is not parenthesised: {order}");
let (cheap, _) = matrix_chain_order(&[1, 100, 1, 100]);
assert_eq!(cheap, 200, "the cheap order costs {cheap}");
fn brute(dims: &[usize], i: usize, j: usize) -> u64 {
if i == j {
return 0;
}
(i..j)
.map(|k| {
brute(dims, i, k)
+ brute(dims, k + 1, j)
+ (dims[i] * dims[k + 1] * dims[j + 1]) as u64
})
.min()
.unwrap_or(0)
}
let mut rng = Rng::new(0x003A_0001);
for _ in 0..60 {
let k = 2 + pick(&mut rng, 5);
let dims: Vec<usize> = (0..=k).map(|_| 1 + pick(&mut rng, 30)).collect();
let (table, _) = matrix_chain_order(&dims);
assert_eq!(table, brute(&dims, 0, k - 1), "the table lost to brute force");
}
assert_eq!(matrix_chain_order(&[3, 4]).0, 0);
}
#[test]
fn rod_cutting_returns_pieces_that_add_up_and_pay_out() {
let prices = [1u64, 5, 8, 9, 10, 17, 17, 20];
let (value, pieces) = rod_cutting(&prices, 8);
assert_eq!(value, 22, "got {value} from {pieces:?}");
assert_eq!(pieces.iter().sum::<usize>(), 8, "the pieces are {pieces:?}");
let paid: u64 = pieces.iter().map(|&p| prices[p - 1]).sum();
assert_eq!(paid, value);
let mut previous = 0u64;
for n in 0..=8 {
let (v, p) = rod_cutting(&prices, n);
assert!(v >= previous, "a longer rod was worth less at n = {n}");
assert_eq!(p.iter().sum::<usize>(), n, "pieces {p:?} do not total {n}");
previous = v;
}
}
#[test]
fn egg_drop_reproduces_its_known_values() {
assert_eq!(egg_drop(2, 100), 14);
assert_eq!(egg_drop(1, 37), 37);
assert_eq!(egg_drop(20, 1000), 10);
assert_eq!(egg_drop(0, 5), 0);
assert_eq!(egg_drop(3, 0), 0);
for floors in [10usize, 50, 200] {
let mut previous = u64::MAX;
for eggs in 1..=8 {
let d = egg_drop(eggs, floors);
assert!(d <= previous, "an extra egg cost more drops at {eggs}");
previous = d;
}
}
for eggs in [1usize, 2, 4] {
let mut previous = 0u64;
for floors in [1usize, 10, 100, 500] {
let d = egg_drop(eggs, floors);
assert!(d >= previous, "more floors needed fewer drops");
previous = d;
}
}
}
#[test]
fn the_optimal_search_tree_beats_every_arrangement() {
fn brute(freq: &[f64], i: usize, j: usize) -> f64 {
if i > j {
return 0.0;
}
let sum: f64 = freq[i..=j].iter().sum();
(i..=j)
.map(|r| {
let left = if r > i { brute(freq, i, r - 1) } else { 0.0 };
let right = if r < j { brute(freq, r + 1, j) } else { 0.0 };
left + right + sum
})
.fold(f64::INFINITY, f64::min)
}
let mut rng = Rng::new(0x0B57_0001);
for _ in 0..40 {
let n = 1 + pick(&mut rng, 6);
let freq: Vec<f64> = (0..n).map(|_| (rng.next_f64() * 10.0).round() + 1.0).collect();
let table = optimal_bst(&freq);
let exact = brute(&freq, 0, n - 1);
assert!(
(table - exact).abs() < 1e-9,
"the table gave {table}, brute force {exact}"
);
}
assert_eq!(optimal_bst(&[]), 0.0);
assert!((optimal_bst(&[0.7]) - 0.7).abs() < 1e-12);
let flat = optimal_bst(&[1.0, 1.0, 1.0, 1.0]);
let skewed = optimal_bst(&[3.7, 0.1, 0.1, 0.1]);
assert!(skewed < flat, "skewed {skewed} was not cheaper than flat {flat}");
}
#[test]
fn the_trellis_path_is_the_cheapest_of_them_all() {
let mut rng = Rng::new(0x1727_0001);
for _ in 0..60 {
let s = 2 + pick(&mut rng, 3);
let t = 2 + pick(&mut rng, 4);
let mut transition = Matrix::zeros(s, s);
let mut emission = Matrix::zeros(s, t);
for i in 0..s {
for j in 0..s {
transition.set(i, j, (rng.next_f64() * 9.0).round());
}
for k in 0..t {
emission.set(i, k, (rng.next_f64() * 9.0).round());
}
}
let path = viterbi_generic(&transition, &emission).unwrap();
assert_eq!(path.len(), t);
let cost = |p: &[usize]| -> f64 {
let mut acc = emission.get(p[0], 0);
for k in 1..t {
acc += transition.get(p[k - 1], p[k]) + emission.get(p[k], k);
}
acc
};
let mut best = f64::INFINITY;
let mut counter = vec![0usize; t];
loop {
best = best.min(cost(&counter));
let mut k = 0usize;
while k < t {
counter[k] += 1;
if counter[k] < s {
break;
}
counter[k] = 0;
k += 1;
}
if k == t {
break;
}
}
assert!(
(cost(&path) - best).abs() < 1e-9,
"the trellis path costs {}, the best is {best}",
cost(&path)
);
}
assert!(viterbi_generic(&Matrix::zeros(2, 3), &Matrix::zeros(2, 2)).is_err());
assert!(viterbi_generic(&Matrix::zeros(2, 2), &Matrix::zeros(3, 2)).is_err());
}
#[test]
fn exact_cover_partitions_the_columns() {
let matrix = vec![
vec![true, false, false, true, false, false, true],
vec![true, false, false, true, false, false, false],
vec![false, false, false, true, true, false, true],
vec![false, false, true, false, true, true, false],
vec![false, true, true, false, false, true, true],
vec![false, true, false, false, false, false, true],
];
let chosen = exact_cover_dlx(&matrix).unwrap().expect("a cover exists");
for col in 0..7 {
let hits = chosen.iter().filter(|&&r| matrix[r][col]).count();
assert_eq!(hits, 1, "column {col} is covered {hits} times by {chosen:?}");
}
assert_eq!(exact_cover_dlx(&[vec![true, false], vec![true, false]]).unwrap(), None);
assert_eq!(exact_cover_dlx(&[]).unwrap(), Some(Vec::new()));
assert!(exact_cover_dlx(&[vec![true], vec![true, false]]).is_err());
assert!(exact_cover_dlx(&[vec![false; 65]]).is_err());
}
#[test]
fn a_solved_sudoku_is_valid_and_keeps_its_clues() {
let puzzle = [
[5, 3, 0, 0, 7, 0, 0, 0, 0],
[6, 0, 0, 1, 9, 5, 0, 0, 0],
[0, 9, 8, 0, 0, 0, 0, 6, 0],
[8, 0, 0, 0, 6, 0, 0, 0, 3],
[4, 0, 0, 8, 0, 3, 0, 0, 1],
[7, 0, 0, 0, 2, 0, 0, 0, 6],
[0, 6, 0, 0, 0, 0, 2, 8, 0],
[0, 0, 0, 4, 1, 9, 0, 0, 5],
[0, 0, 0, 0, 8, 0, 0, 7, 9],
];
let solved = sudoku_solve(&puzzle).expect("this puzzle has a solution");
for r in 0..9 {
for c in 0..9 {
assert!((1..=9).contains(&solved[r][c]), "cell ({r}, {c}) is {}", solved[r][c]);
if puzzle[r][c] != 0 {
assert_eq!(solved[r][c], puzzle[r][c], "clue at ({r}, {c}) was changed");
}
}
}
for k in 0..9 {
let row: Vec<u8> = (0..9).map(|c| solved[k][c]).collect();
let col: Vec<u8> = (0..9).map(|r| solved[r][k]).collect();
let boxed: Vec<u8> = (0..9)
.map(|i| solved[(k / 3) * 3 + i / 3][(k % 3) * 3 + i % 3])
.collect();
for group in [row, col, boxed] {
let mut sorted = group.clone();
sorted.sort_unstable();
assert_eq!(sorted, (1..=9).collect::<Vec<u8>>(), "a group repeats: {group:?}");
}
}
let mut broken = puzzle;
broken[0][2] = 5;
assert_eq!(sudoku_solve(&broken), None);
let mut invalid = puzzle;
invalid[0][2] = 10;
assert_eq!(sudoku_solve(&invalid), None);
assert!(sudoku_solve(&[[0u8; 9]; 9]).is_some());
}
#[test]
fn n_queens_places_them_legally_and_counts_them_correctly() {
let known = [1u64, 0, 0, 2, 10, 4, 40, 92, 352, 724];
for (n, &expected) in known.iter().enumerate() {
assert_eq!(n_queens_count(n + 1), expected, "board {} has {expected}", n + 1);
}
assert!(known[5] < known[4]);
for n in 1..=8usize {
let solutions = n_queens(n);
assert_eq!(solutions.len() as u64, n_queens_count(n));
for placement in &solutions {
assert_eq!(placement.len(), n);
for i in 0..n {
assert!(placement[i] < n, "a queen left the board");
for j in i + 1..n {
assert_ne!(placement[i], placement[j], "two queens share a column");
assert_ne!(
placement[i].abs_diff(placement[j]),
j - i,
"two queens share a diagonal"
);
}
}
}
}
}
#[test]
fn arc_consistency_prunes_soundly_and_detects_contradictions() {
let domains = [0b001u64, 0b011, 0b111];
let constraints = [(0usize, 1usize), (1, 2), (0, 2)];
let reduced = constraint_propagation_ac3(&domains, &constraints).unwrap();
assert_eq!(reduced[0], 0b001);
assert_eq!(reduced[1], 0b010, "variable 1 came out {:#b}", reduced[1]);
assert_eq!(reduced[2], 0b100, "variable 2 came out {:#b}", reduced[2]);
let mut rng = Rng::new(0x0AC3_0001);
for _ in 0..200 {
let n = 2 + pick(&mut rng, 3);
let domains: Vec<u64> = (0..n).map(|_| 1 + (rng.next_u64() % 15)).collect();
let pairs: Vec<(usize, usize)> = (0..n)
.flat_map(|i| (i + 1..n).map(move |j| (i, j)))
.filter(|_| rng.next_f64() < 0.7)
.collect();
let mut solutions: Vec<Vec<u64>> = Vec::new();
let mut assignment = vec![0u64; n];
fn search(
k: usize,
domains: &[u64],
pairs: &[(usize, usize)],
assignment: &mut Vec<u64>,
out: &mut Vec<Vec<u64>>,
) {
if k == domains.len() {
out.push(assignment.clone());
return;
}
let mut values = domains[k];
while values != 0 {
let bit = values & values.wrapping_neg();
values ^= bit;
assignment[k] = bit;
if pairs
.iter()
.all(|&(a, b)| a > k || b > k || assignment[a] != assignment[b])
{
search(k + 1, domains, pairs, assignment, out);
}
}
assignment[k] = 0;
}
search(0, &domains, &pairs, &mut assignment, &mut solutions);
match constraint_propagation_ac3(&domains, &pairs) {
Some(reduced) => {
for solution in &solutions {
for (k, &v) in solution.iter().enumerate() {
assert!(
reduced[k] & v != 0,
"AC-3 pruned a value that appears in a solution"
);
}
}
for k in 0..n {
assert_eq!(reduced[k] & !domains[k], 0, "AC-3 added a value");
}
}
None => assert!(
solutions.is_empty(),
"AC-3 declared a contradiction where solutions exist"
),
}
}
assert_eq!(constraint_propagation_ac3(&[1, 2], &[(0, 5)]), None);
}
}