use selen::prelude::*;
use std::time::Instant;
#[derive(Debug)]
struct BenchmarkResult {
name: String,
iterations: usize,
total_ms: f64,
avg_ms: f64,
min_ms: f64,
max_ms: f64,
}
impl BenchmarkResult {
fn print(&self) {
println!(
"{:<35} | {:>10.3} ms",
self.name, self.avg_ms
);
}
}
fn benchmark<F>(name: &str, iterations: usize, mut f: F) -> BenchmarkResult
where
F: FnMut() -> (),
{
let mut times = Vec::new();
for _ in 0..2 {
f();
}
for _ in 0..iterations {
let start = Instant::now();
f();
let elapsed = start.elapsed().as_secs_f64() * 1000.0;
times.push(elapsed);
}
let total_ms: f64 = times.iter().sum();
let avg_ms = total_ms / iterations as f64;
let min_ms = times.iter().cloned().fold(f64::INFINITY, f64::min);
let max_ms = times.iter().cloned().fold(0.0, f64::max);
BenchmarkResult {
name: name.to_string(),
iterations,
total_ms,
avg_ms,
min_ms,
max_ms,
}
}
fn table_small_2vars() {
let mut m = Model::default();
let x = m.int(1, 200);
let y = m.int(1, 200);
let mut tuples = Vec::new();
for i in 1..=200 {
for j in i..=200 {
tuples.push(vec![Val::int(i), Val::int(j)]);
}
}
m.table(&[x, y], tuples);
let _sol = m.solve();
}
fn table_medium_3vars() {
let mut m = Model::default();
let x = m.int(1, 50);
let y = m.int(1, 50);
let z = m.int(1, 50);
let mut tuples = Vec::new();
for i in 1..=50 {
for j in 1..=50 {
let sum = i + j;
if sum <= 100 {
tuples.push(vec![Val::int(i), Val::int(j), Val::int(sum)]);
}
}
}
m.table(&[x, y, z], tuples);
let _sol = m.solve();
}
fn table_large_tuples_2vars() {
let mut m = Model::default();
let x = m.int(1, 100);
let y = m.int(1, 100);
let mut tuples = Vec::new();
for i in 1..=100 {
for j in 1..=100 {
if (i * j) % 3 == 0 {
tuples.push(vec![Val::int(i), Val::int(j)]);
}
}
}
m.table(&[x, y], tuples);
let _sol = m.solve();
}
fn table_high_arity_5vars() {
let mut m = Model::default();
let vars = vec![m.int(1, 15), m.int(1, 15), m.int(1, 15), m.int(1, 15), m.int(1, 15)];
let mut tuples = Vec::new();
for i in 1..=15 {
for j in (1..=15).step_by(2) {
for k in 1..=3 {
tuples.push(vec![
Val::int(i),
Val::int(j),
Val::int((i + j) % 15),
Val::int(i),
Val::int((j * k) % 15),
]);
if tuples.len() >= 500 {
break;
}
}
if tuples.len() >= 500 {
break;
}
}
if tuples.len() >= 500 {
break;
}
}
m.table(&vars, tuples);
let _sol = m.solve();
}
fn table_dense_3vars() {
let mut m = Model::default();
let x = m.int(1, 50);
let y = m.int(1, 50);
let z = m.int(1, 50);
let mut tuples = Vec::new();
for i in 1..=50 {
for j in 1..=50 {
for k in 1..=50 {
if (i + j + k) % 2 == 0 {
tuples.push(vec![Val::int(i), Val::int(j), Val::int(k)]);
}
}
}
}
m.table(&[x, y, z], tuples);
let _sol = m.solve();
}
fn table_pigeon_hole() {
let mut m = Model::default();
let vars = (0..8).map(|_| m.int(0, 4)).collect::<Vec<_>>();
let mut tuples = Vec::new();
fn generate_tuples(vars: usize, holes: usize, min_in_hole_0: usize, tuples: &mut Vec<Vec<Val>>) {
fn recurse(
var_idx: usize,
vars: usize,
holes: usize,
current: &mut Vec<i32>,
count_in_0: usize,
min_in_hole_0: usize,
tuples: &mut Vec<Vec<Val>>,
) {
if var_idx == vars {
if count_in_0 >= min_in_hole_0 {
tuples.push(current.iter().map(|&x| Val::int(x)).collect());
}
return;
}
for h in 0..holes {
current.push(h as i32);
recurse(
var_idx + 1,
vars,
holes,
current,
if h == 0 { count_in_0 + 1 } else { count_in_0 },
min_in_hole_0,
tuples,
);
current.pop();
}
}
let mut current = Vec::new();
recurse(0, vars, holes, &mut current, 0, min_in_hole_0, tuples);
}
generate_tuples(8, 5, 3, &mut tuples);
m.table(&vars, tuples);
let _sol = m.solve();
}
fn table_configuration() {
let mut m = Model::default();
let cpu = m.int(1, 8);
let ram = m.int(1, 10);
let storage = m.int(1, 8);
let network = m.int(1, 6);
let mut tuples = Vec::new();
for c in 1..=8 {
for r in 1..=10 {
for s in 1..=8 {
for n in 1..=6 {
let cpu_compatible = c >= 2 || r <= 4;
let storage_compatible = s >= 3 || r <= 6;
let network_compatible = n <= 4 || c >= 5;
if cpu_compatible && storage_compatible && network_compatible {
tuples.push(vec![Val::int(c), Val::int(r), Val::int(s), Val::int(n)]);
}
}
}
}
}
m.table(&[cpu, ram, storage, network], tuples);
let _sol = m.solve();
}
fn table_sudoku_row() {
let mut m = Model::default();
let vars: Vec<_> = (0..12).map(|_| m.int(1, 12)).collect();
let mut tuples = Vec::new();
let mut perm = (1..=12).collect::<Vec<i32>>();
for _ in 0..500 {
tuples.push(perm.iter().map(|&x| Val::int(x)).collect());
perm.rotate_left(1);
}
m.table(&vars, tuples);
let _sol = m.solve();
}
fn main() {
let mut results = Vec::new();
println!("TABLE CONSTRAINT BENCHMARK (GAC - Generalized Arc Consistency)");
println!("name | ms/iter | total ms");
println!("─────────────────────┼───────────┼──────────");
results.push(benchmark("2vars_xl", 5, || table_small_2vars()));
results.last().unwrap().print();
results.push(benchmark("3vars_xl", 4, || table_medium_3vars()));
results.last().unwrap().print();
results.push(benchmark("large_tup", 3, || table_large_tuples_2vars()));
results.last().unwrap().print();
results.push(benchmark("high_arity", 3, || table_high_arity_5vars()));
results.last().unwrap().print();
results.push(benchmark("dense_xl", 2, || table_dense_3vars()));
results.last().unwrap().print();
results.push(benchmark("pigeon_6v", 3, || table_pigeon_hole()));
results.last().unwrap().print();
results.push(benchmark("config_xl", 3, || table_configuration()));
results.last().unwrap().print();
results.push(benchmark("sudoku_12", 2, || table_sudoku_row()));
results.last().unwrap().print();
let total_ms: f64 = results.iter().map(|r| r.total_ms).sum();
let avg_ms_per_iter = results.iter().map(|r| r.avg_ms).sum::<f64>() / results.len() as f64;
println!("─────────────────────┼───────────┼──────────");
println!("Total: {:.1}ms | Avg: {:.2}ms", total_ms, avg_ms_per_iter);
}