use std::fmt::Write;
use std::path::PathBuf;
fn output_dir() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/bench_graphs")
}
fn generate_chain(n: usize) -> String {
let mut s = String::new();
writeln!(s, "// Benchmark: {n}-node chain (degree=1)").unwrap();
writeln!(s, "input cycle: u64\n").unwrap();
writeln!(s, "n0 := hash(cycle)").unwrap();
for i in 1..n {
if i % 2 == 1 {
writeln!(s, "n{i} := mod(n{}, {})", i - 1, 1000 + i).unwrap();
} else {
writeln!(s, "n{i} := hash(n{})", i - 1).unwrap();
}
}
writeln!(s, "out := n{}", n - 1).unwrap();
s
}
fn generate_layered(n: usize) -> String {
let width = ((n as f64).sqrt()).ceil().max(3.0) as usize;
let layers = (n + width - 1) / width;
let mut s = String::new();
let mut count = 0usize;
writeln!(s, "// Benchmark: {n}-node layered DAG (degree~3, width={width}, layers={layers})").unwrap();
writeln!(s, "input cycle: u64\n").unwrap();
for j in 0..width.min(n) {
if j == 0 {
writeln!(s, "n0 := hash(cycle)").unwrap();
} else {
writeln!(s, "n{j} := hash(n{})", j - 1).unwrap();
}
count += 1;
}
for layer in 1..layers {
let layer_base = layer * width;
let prev_base = (layer - 1) * width;
let prev_count = width.min(n.saturating_sub(prev_base));
if prev_count == 0 { break; }
for j in 0..width {
let idx = layer_base + j;
if idx >= n { break; }
let src = prev_base + ((j * 3 + layer) % prev_count);
if idx % 3 == 0 {
writeln!(s, "n{idx} := hash(n{src})").unwrap();
} else {
let k = 100 + (idx % 997);
writeln!(s, "n{idx} := mod(n{src}, {k})").unwrap();
}
count += 1;
}
}
writeln!(s, "out := n{}", count - 1).unwrap();
s
}
fn generate_dense(n: usize) -> String {
let width = ((n as f64).sqrt() * 1.2).ceil().max(4.0) as usize;
let layers = (n + width - 1) / width;
let mut s = String::new();
let mut count = 0usize;
writeln!(s, "// Benchmark: {n}-node dense DAG (degree~6, width={width}, layers={layers})").unwrap();
writeln!(s, "input cycle: u64\n").unwrap();
let seed_count = width.min(n);
for j in 0..seed_count {
if j == 0 {
writeln!(s, "n0 := hash(cycle)").unwrap();
} else {
writeln!(s, "n{j} := hash(n{})", j - 1).unwrap();
}
count += 1;
}
for layer in 1..layers {
let layer_base = layer * width;
let prev_base = (layer - 1) * width;
let prev_count = width.min(n.saturating_sub(prev_base));
if prev_count == 0 { break; }
let source_count = (prev_count / 3).max(1);
for j in 0..width {
let idx = layer_base + j;
if idx >= n { break; }
let src = prev_base + (j % source_count);
match idx % 4 {
0 => writeln!(s, "n{idx} := hash(n{src})").unwrap(),
1 => writeln!(s, "n{idx} := mod(n{src}, {})", 100 + idx % 900).unwrap(),
2 => writeln!(s, "n{idx} := hash(n{src})").unwrap(),
_ => writeln!(s, "n{idx} := mod(n{src}, {})", 50 + idx % 500).unwrap(),
}
count += 1;
}
}
writeln!(s, "out := n{}", count - 1).unwrap();
s
}
pub fn generate_all() {
let dir = output_dir();
std::fs::create_dir_all(&dir).unwrap();
let configs: Vec<(usize, &str, fn(usize) -> String)> = vec![
(10, "chain", generate_chain as fn(usize) -> String),
(100, "chain", generate_chain),
(1000, "chain", generate_chain),
(10, "layered", generate_layered),
(100, "layered", generate_layered),
(1000, "layered", generate_layered),
(10, "dense", generate_dense),
(100, "dense", generate_dense),
(1000, "dense", generate_dense),
];
for (size, topo, genfn) in configs {
let source = genfn(size);
let filename = format!("{topo}_{size}.gk");
let path = dir.join(&filename);
std::fs::write(&path, &source).unwrap();
let line_count = source.lines().count();
eprintln!(" wrote {filename} ({line_count} lines)");
}
}
#[cfg(test)]
mod tests {
use super::*;
fn compile_and_verify(src: &str, expected_min_nodes: usize) {
let k = polydat::dsl::compile::compile_gk(src)
.unwrap_or_else(|e| panic!("compile failed: {e}"));
let p = k.program();
assert!(p.output_names().contains(&"out"),
"missing 'out' output");
assert!(p.node_count() >= expected_min_nodes,
"expected >= {expected_min_nodes} nodes, got {}",
p.node_count());
}
fn compile_and_eval(src: &str) {
let k = polydat::dsl::compile::compile_gk(src).unwrap();
let p = k.into_program();
let mut state = p.create_state();
for cycle in [0u64, 1, 42, 999] {
state.set_inputs(&[cycle]);
let v = state.pull(&p, "out");
assert!(!matches!(v, polydat::node::Value::None),
"output should not be None at cycle={cycle}");
}
}
#[test]
fn chain_10_compiles() { compile_and_verify(&generate_chain(10), 9); }
#[test]
fn chain_100_compiles() { compile_and_verify(&generate_chain(100), 90); }
#[test]
fn chain_1000_compiles() { compile_and_verify(&generate_chain(1000), 900); }
#[test]
fn chain_10_evals() { compile_and_eval(&generate_chain(10)); }
#[test]
fn chain_100_evals() { compile_and_eval(&generate_chain(100)); }
#[test]
fn chain_1000_evals() { compile_and_eval(&generate_chain(1000)); }
#[test]
fn layered_10_compiles() { compile_and_verify(&generate_layered(10), 9); }
#[test]
fn layered_100_compiles() { compile_and_verify(&generate_layered(100), 90); }
#[test]
fn layered_1000_compiles() { compile_and_verify(&generate_layered(1000), 900); }
#[test]
fn layered_10_evals() { compile_and_eval(&generate_layered(10)); }
#[test]
fn layered_100_evals() { compile_and_eval(&generate_layered(100)); }
#[test]
fn layered_1000_evals() { compile_and_eval(&generate_layered(1000)); }
#[test]
fn dense_10_compiles() { compile_and_verify(&generate_dense(10), 9); }
#[test]
fn dense_100_compiles() { compile_and_verify(&generate_dense(100), 90); }
#[test]
fn dense_1000_compiles() { compile_and_verify(&generate_dense(1000), 900); }
#[test]
fn dense_10_evals() { compile_and_eval(&generate_dense(10)); }
#[test]
fn dense_100_evals() { compile_and_eval(&generate_dense(100)); }
#[test]
fn dense_1000_evals() { compile_and_eval(&generate_dense(1000)); }
#[test]
#[ignore] fn write_bench_graph_files() {
generate_all();
}
}