mirror of
https://github.com/ruvnet/RuView
synced 2026-07-31 18:51:42 +00:00
589 lines
21 KiB
Rust
589 lines
21 KiB
Rust
//! Solver Benchmark Results in RVF
|
|
//!
|
|
//! Demonstrates storing, querying, and comparing iterative solver benchmark
|
|
//! results using RVF vector stores. Benchmark parameters (algorithm, matrix
|
|
//! size, density, tolerance) and timing data (wall time, iterations,
|
|
//! convergence rate) are recorded as metadata on per-run embedding vectors.
|
|
//!
|
|
//! This enables similarity search across benchmark runs: find the closest
|
|
//! historical benchmark to a new problem, discover which algorithm performs
|
|
//! best for specific problem characteristics, and aggregate statistics
|
|
//! across runs.
|
|
//!
|
|
//! Features:
|
|
//! - Benchmark parameter encoding as vector embeddings
|
|
//! - Metadata-rich timing and convergence records
|
|
//! - Multi-algorithm comparison on the same problem
|
|
//! - Filtered queries by algorithm and problem characteristics
|
|
//! - Aggregation across benchmark runs
|
|
//!
|
|
//! RVF segments used: VEC_SEG, MANIFEST_SEG
|
|
//!
|
|
//! Run: cargo run --example solver_benchmark
|
|
|
|
use rvf_runtime::{
|
|
FilterExpr, MetadataEntry, MetadataValue, QueryOptions, RvfOptions, RvfStore, SearchResult,
|
|
};
|
|
use rvf_runtime::filter::FilterValue;
|
|
use rvf_runtime::options::DistanceMetric;
|
|
use tempfile::TempDir;
|
|
|
|
/// Simple LCG-based pseudo-random number generator for deterministic results.
|
|
fn lcg_next(state: &mut u64) -> u64 {
|
|
*state = state.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
|
|
*state >> 33
|
|
}
|
|
|
|
/// Simple LCG-based pseudo-random f64 in [0, 1).
|
|
fn lcg_f64(state: &mut u64) -> f64 {
|
|
lcg_next(state) as f64 / u32::MAX as f64
|
|
}
|
|
|
|
/// Solver algorithms for benchmarking.
|
|
const ALGORITHMS: &[&str] = &["cg", "gmres", "bicgstab", "jacobi", "sor"];
|
|
|
|
/// Problem categories based on matrix structure.
|
|
const PROBLEM_TYPES: &[&str] = &[
|
|
"laplacian",
|
|
"elasticity",
|
|
"diffusion",
|
|
"convection",
|
|
"helmholtz",
|
|
];
|
|
|
|
/// A single benchmark run result.
|
|
struct BenchmarkRun {
|
|
/// Run identifier.
|
|
run_id: u64,
|
|
/// Solver algorithm name.
|
|
algorithm: &'static str,
|
|
/// Problem type / matrix structure.
|
|
problem_type: &'static str,
|
|
/// Matrix dimension (N x N).
|
|
matrix_size: u64,
|
|
/// Matrix density (0.0 to 1.0).
|
|
density: f64,
|
|
/// Solver tolerance.
|
|
tolerance: f64,
|
|
/// Wall clock time in microseconds.
|
|
wall_time_us: u64,
|
|
/// Number of iterations to converge.
|
|
iterations: u64,
|
|
/// Final residual norm.
|
|
final_residual: f64,
|
|
/// Convergence rate (residual ratio between iterations).
|
|
convergence_rate: f64,
|
|
/// Whether the solver converged within max iterations.
|
|
converged: bool,
|
|
}
|
|
|
|
/// Encode benchmark parameters as a fixed-size embedding vector.
|
|
///
|
|
/// The embedding captures the problem characteristics so that similar
|
|
/// benchmarks (same problem type, similar size/density) are nearby in
|
|
/// vector space.
|
|
fn benchmark_to_embedding(run: &BenchmarkRun, dim: usize) -> Vec<f32> {
|
|
let mut embedding = vec![0.0f32; dim];
|
|
|
|
// Feature 0: log2(matrix_size) normalized to [0, 1]
|
|
embedding[0] = ((run.matrix_size as f64).log2() / 20.0) as f32;
|
|
|
|
// Feature 1: density
|
|
embedding[1] = run.density as f32;
|
|
|
|
// Feature 2: log10(tolerance) normalized
|
|
embedding[2] = (run.tolerance.log10().abs() / 16.0) as f32;
|
|
|
|
// Feature 3: algorithm one-hot hash (spread across features 3-7)
|
|
let algo_idx = ALGORITHMS.iter().position(|&a| a == run.algorithm).unwrap_or(0);
|
|
embedding[3 + algo_idx] = 1.0;
|
|
|
|
// Features 8-12: problem type one-hot hash
|
|
let prob_idx = PROBLEM_TYPES
|
|
.iter()
|
|
.position(|&p| p == run.problem_type)
|
|
.unwrap_or(0);
|
|
embedding[8 + prob_idx] = 1.0;
|
|
|
|
// Feature 13: log2(wall_time_us) normalized
|
|
embedding[13] = ((run.wall_time_us as f64 + 1.0).log2() / 30.0) as f32;
|
|
|
|
// Feature 14: log2(iterations) normalized
|
|
embedding[14] = ((run.iterations as f64 + 1.0).log2() / 16.0) as f32;
|
|
|
|
// Feature 15: convergence rate
|
|
embedding[15] = run.convergence_rate as f32;
|
|
|
|
// Feature 16: converged flag
|
|
embedding[16] = if run.converged { 1.0 } else { 0.0 };
|
|
|
|
// Feature 17: log10(final_residual) normalized
|
|
embedding[17] = (run.final_residual.log10().abs() / 16.0) as f32;
|
|
|
|
// Features 18+: interaction terms (size * density, size * tolerance, etc.)
|
|
embedding[18] = (embedding[0] * embedding[1]).min(1.0);
|
|
embedding[19] = (embedding[0] * embedding[2]).min(1.0);
|
|
embedding[20] = (embedding[1] * embedding[15]).min(1.0);
|
|
|
|
embedding
|
|
}
|
|
|
|
/// Simulate a benchmark run with deterministic pseudo-random results.
|
|
fn simulate_benchmark(
|
|
run_id: u64,
|
|
algorithm: &'static str,
|
|
problem_type: &'static str,
|
|
matrix_size: u64,
|
|
density: f64,
|
|
tolerance: f64,
|
|
seed: u64,
|
|
) -> BenchmarkRun {
|
|
let mut state = seed.wrapping_add(run_id * 31 + 7);
|
|
|
|
// Base iteration count depends on algorithm and problem size
|
|
let algo_factor = match algorithm {
|
|
"cg" => 0.5,
|
|
"gmres" => 0.7,
|
|
"bicgstab" => 0.6,
|
|
"jacobi" => 2.0,
|
|
"sor" => 1.2,
|
|
_ => 1.0,
|
|
};
|
|
|
|
// Problem difficulty factor
|
|
let prob_factor = match problem_type {
|
|
"laplacian" => 0.8,
|
|
"elasticity" => 1.5,
|
|
"diffusion" => 1.0,
|
|
"convection" => 1.8,
|
|
"helmholtz" => 2.0,
|
|
_ => 1.0,
|
|
};
|
|
|
|
let base_iters = (matrix_size as f64).sqrt() * algo_factor * prob_factor;
|
|
let noise = 0.8 + lcg_f64(&mut state) * 0.4; // 0.8 to 1.2
|
|
let iterations = (base_iters * noise * (1.0 / tolerance).log10()) as u64;
|
|
let iterations = iterations.max(1).min(10000);
|
|
|
|
// Convergence rate: closer to 1.0 means slower convergence
|
|
let base_rate = match algorithm {
|
|
"cg" => 0.85,
|
|
"gmres" => 0.80,
|
|
"bicgstab" => 0.82,
|
|
"jacobi" => 0.95,
|
|
"sor" => 0.90,
|
|
_ => 0.90,
|
|
};
|
|
let convergence_rate = base_rate + lcg_f64(&mut state) * 0.05;
|
|
|
|
// Determine if solver converged
|
|
let converged = convergence_rate < 0.99 && iterations < 10000;
|
|
|
|
// Final residual
|
|
let final_residual = if converged {
|
|
tolerance * (0.1 + lcg_f64(&mut state) * 0.9)
|
|
} else {
|
|
tolerance * (10.0 + lcg_f64(&mut state) * 100.0)
|
|
};
|
|
|
|
// Wall time: proportional to iterations * matrix_size * density
|
|
let time_factor = iterations as f64 * matrix_size as f64 * density;
|
|
let wall_time_us = (time_factor * 0.01 * (0.8 + lcg_f64(&mut state) * 0.4)) as u64;
|
|
|
|
BenchmarkRun {
|
|
run_id,
|
|
algorithm,
|
|
problem_type,
|
|
matrix_size,
|
|
density,
|
|
tolerance,
|
|
wall_time_us,
|
|
iterations,
|
|
final_residual,
|
|
convergence_rate,
|
|
converged,
|
|
}
|
|
}
|
|
|
|
fn main() {
|
|
println!("=== Solver Benchmark Results in RVF ===\n");
|
|
|
|
let embed_dim = 64;
|
|
let num_runs = 150;
|
|
|
|
// ====================================================================
|
|
// 1. Generate benchmark runs
|
|
// ====================================================================
|
|
println!("--- 1. Generate Benchmark Runs ---");
|
|
|
|
let matrix_sizes: Vec<u64> = vec![64, 128, 256, 512, 1024];
|
|
let densities: Vec<f64> = vec![0.01, 0.05, 0.10, 0.20];
|
|
let tolerances: Vec<f64> = vec![1e-6, 1e-8, 1e-10, 1e-12];
|
|
|
|
let mut runs: Vec<BenchmarkRun> = Vec::with_capacity(num_runs);
|
|
let mut lcg_state: u64 = 42;
|
|
|
|
for run_id in 0..num_runs as u64 {
|
|
let algo = ALGORITHMS[lcg_next(&mut lcg_state) as usize % ALGORITHMS.len()];
|
|
let prob = PROBLEM_TYPES[lcg_next(&mut lcg_state) as usize % PROBLEM_TYPES.len()];
|
|
let size = matrix_sizes[lcg_next(&mut lcg_state) as usize % matrix_sizes.len()];
|
|
let dens = densities[lcg_next(&mut lcg_state) as usize % densities.len()];
|
|
let tol = tolerances[lcg_next(&mut lcg_state) as usize % tolerances.len()];
|
|
|
|
runs.push(simulate_benchmark(run_id, algo, prob, size, dens, tol, 42));
|
|
}
|
|
|
|
let converged_count = runs.iter().filter(|r| r.converged).count();
|
|
println!(" Generated {} benchmark runs", num_runs);
|
|
println!(" Converged: {} / {}", converged_count, num_runs);
|
|
|
|
// Algorithm distribution
|
|
println!("\n Algorithm distribution:");
|
|
for &algo in ALGORITHMS {
|
|
let count = runs.iter().filter(|r| r.algorithm == algo).count();
|
|
println!(" {:>10}: {}", algo, count);
|
|
}
|
|
|
|
// Problem type distribution
|
|
println!("\n Problem type distribution:");
|
|
for &prob in PROBLEM_TYPES {
|
|
let count = runs.iter().filter(|r| r.problem_type == prob).count();
|
|
println!(" {:>12}: {}", prob, count);
|
|
}
|
|
|
|
// ====================================================================
|
|
// 2. Create RVF store and ingest benchmark data
|
|
// ====================================================================
|
|
println!("\n--- 2. Store Benchmarks in RVF ---");
|
|
|
|
let tmp_dir = TempDir::new().expect("failed to create temp dir");
|
|
let store_path = tmp_dir.path().join("solver_benchmarks.rvf");
|
|
|
|
let options = RvfOptions {
|
|
dimension: embed_dim as u16,
|
|
metric: DistanceMetric::L2,
|
|
..Default::default()
|
|
};
|
|
|
|
let mut store = RvfStore::create(&store_path, options).expect("failed to create store");
|
|
println!(" Store created: {} dims, L2 metric", embed_dim);
|
|
|
|
// Build embeddings and metadata for all runs
|
|
let mut embeddings: Vec<Vec<f32>> = Vec::with_capacity(num_runs);
|
|
let mut metadata: Vec<MetadataEntry> = Vec::new();
|
|
|
|
// Metadata field IDs:
|
|
// 0 = algorithm (string)
|
|
// 1 = problem_type (string)
|
|
// 2 = matrix_size (u64)
|
|
// 3 = wall_time_us (u64)
|
|
// 4 = iterations (u64)
|
|
// 5 = converged (u64: 0 or 1)
|
|
// 6 = convergence_rate_fixed (u64: rate * 1e6)
|
|
// 7 = density_fixed (u64: density * 1e6)
|
|
for run in &runs {
|
|
embeddings.push(benchmark_to_embedding(run, embed_dim));
|
|
|
|
metadata.push(MetadataEntry {
|
|
field_id: 0,
|
|
value: MetadataValue::String(run.algorithm.to_string()),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 1,
|
|
value: MetadataValue::String(run.problem_type.to_string()),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 2,
|
|
value: MetadataValue::U64(run.matrix_size),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 3,
|
|
value: MetadataValue::U64(run.wall_time_us),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 4,
|
|
value: MetadataValue::U64(run.iterations),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 5,
|
|
value: MetadataValue::U64(if run.converged { 1 } else { 0 }),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 6,
|
|
value: MetadataValue::U64((run.convergence_rate * 1e6) as u64),
|
|
});
|
|
metadata.push(MetadataEntry {
|
|
field_id: 7,
|
|
value: MetadataValue::U64((run.density * 1e6) as u64),
|
|
});
|
|
}
|
|
|
|
let vec_refs: Vec<&[f32]> = embeddings.iter().map(|v| v.as_slice()).collect();
|
|
let ids: Vec<u64> = (0..num_runs as u64).collect();
|
|
|
|
let ingest = store
|
|
.ingest_batch(&vec_refs, &ids, Some(&metadata))
|
|
.expect("ingest failed");
|
|
println!(
|
|
" Ingested {} benchmark records (rejected: {})",
|
|
ingest.accepted, ingest.rejected
|
|
);
|
|
|
|
// ====================================================================
|
|
// 3. Compare algorithms on the same problem class
|
|
// ====================================================================
|
|
println!("\n--- 3. Algorithm Comparison: Laplacian Problems ---");
|
|
|
|
// Find benchmarks for laplacian problems
|
|
let laplacian_runs: Vec<&BenchmarkRun> = runs
|
|
.iter()
|
|
.filter(|r| r.problem_type == "laplacian" && r.converged)
|
|
.collect();
|
|
|
|
println!(" Laplacian runs (converged): {}", laplacian_runs.len());
|
|
|
|
// Per-algorithm statistics for laplacian problems
|
|
println!("\n Per-algorithm performance on laplacian problems:");
|
|
println!(
|
|
" {:>10} {:>6} {:>12} {:>10} {:>10}",
|
|
"Algorithm", "Runs", "Avg Time(us)", "Avg Iters", "Avg Rate"
|
|
);
|
|
println!(
|
|
" {:->10} {:->6} {:->12} {:->10} {:->10}",
|
|
"", "", "", "", ""
|
|
);
|
|
|
|
for &algo in ALGORITHMS {
|
|
let algo_runs: Vec<&&BenchmarkRun> = laplacian_runs
|
|
.iter()
|
|
.filter(|r| r.algorithm == algo)
|
|
.collect();
|
|
if algo_runs.is_empty() {
|
|
continue;
|
|
}
|
|
let count = algo_runs.len();
|
|
let avg_time =
|
|
algo_runs.iter().map(|r| r.wall_time_us).sum::<u64>() as f64 / count as f64;
|
|
let avg_iters =
|
|
algo_runs.iter().map(|r| r.iterations).sum::<u64>() as f64 / count as f64;
|
|
let avg_rate = algo_runs.iter().map(|r| r.convergence_rate).sum::<f64>() / count as f64;
|
|
|
|
println!(
|
|
" {:>10} {:>6} {:>12.0} {:>10.1} {:>10.4}",
|
|
algo, count, avg_time, avg_iters, avg_rate
|
|
);
|
|
}
|
|
|
|
// ====================================================================
|
|
// 4. Query: find similar benchmarks to a new problem
|
|
// ====================================================================
|
|
println!("\n--- 4. Find Similar Benchmarks (Nearest Neighbor) ---");
|
|
|
|
// Simulate a new problem and find the most relevant historical benchmarks
|
|
let new_problem = BenchmarkRun {
|
|
run_id: 999,
|
|
algorithm: "cg",
|
|
problem_type: "diffusion",
|
|
matrix_size: 256,
|
|
density: 0.05,
|
|
tolerance: 1e-8,
|
|
wall_time_us: 0,
|
|
iterations: 0,
|
|
final_residual: 0.0,
|
|
convergence_rate: 0.0,
|
|
converged: false,
|
|
};
|
|
|
|
let query_embedding = benchmark_to_embedding(&new_problem, embed_dim);
|
|
let k = 10;
|
|
|
|
let results = store
|
|
.query(&query_embedding, k, &QueryOptions::default())
|
|
.expect("query failed");
|
|
|
|
println!(" New problem: CG on diffusion, 256x256, density=0.05, tol=1e-8");
|
|
println!(" Top-{} most similar historical benchmarks:", k);
|
|
print_benchmark_results(&results, &runs);
|
|
|
|
// ====================================================================
|
|
// 5. Filter: best-performing algorithm for large matrices
|
|
// ====================================================================
|
|
println!("\n--- 5. Best Algorithm for Large Matrices (size >= 512) ---");
|
|
|
|
// Filter for large matrices that converged
|
|
let filter_large = FilterExpr::And(vec![
|
|
FilterExpr::Ge(2, FilterValue::U64(512)),
|
|
FilterExpr::Eq(5, FilterValue::U64(1)), // converged == true
|
|
]);
|
|
let opts_large = QueryOptions {
|
|
filter: Some(filter_large),
|
|
..Default::default()
|
|
};
|
|
|
|
// Query with a "fast algorithm" embedding (low iteration count features)
|
|
let mut fast_query = vec![0.0f32; embed_dim];
|
|
fast_query[0] = 0.5; // large matrix
|
|
fast_query[14] = 0.1; // low iterations (we want fast)
|
|
fast_query[16] = 1.0; // converged
|
|
|
|
let results_large = store
|
|
.query(&fast_query, k, &opts_large)
|
|
.expect("query failed");
|
|
|
|
println!(" Large matrix benchmarks (converged, size >= 512):");
|
|
if results_large.is_empty() {
|
|
println!(" No matching benchmarks found.");
|
|
} else {
|
|
print_benchmark_results(&results_large, &runs);
|
|
|
|
// Verify all results have matrix_size >= 512 and converged
|
|
for r in &results_large {
|
|
let run = &runs[r.id as usize];
|
|
assert!(
|
|
run.matrix_size >= 512,
|
|
"Run {} has size {} < 512",
|
|
r.id,
|
|
run.matrix_size
|
|
);
|
|
assert!(run.converged, "Run {} did not converge", r.id);
|
|
}
|
|
println!(" All results verified: size >= 512, converged = true.");
|
|
}
|
|
|
|
// ====================================================================
|
|
// 6. Filter by specific algorithm: CG results only
|
|
// ====================================================================
|
|
println!("\n--- 6. CG Algorithm Results ---");
|
|
|
|
let filter_cg = FilterExpr::Eq(0, FilterValue::String("cg".to_string()));
|
|
let opts_cg = QueryOptions {
|
|
filter: Some(filter_cg),
|
|
..Default::default()
|
|
};
|
|
|
|
let results_cg = store
|
|
.query(&query_embedding, k, &opts_cg)
|
|
.expect("query failed");
|
|
|
|
println!(" CG benchmarks most similar to new problem:");
|
|
print_benchmark_results(&results_cg, &runs);
|
|
|
|
for r in &results_cg {
|
|
let run = &runs[r.id as usize];
|
|
assert_eq!(
|
|
run.algorithm, "cg",
|
|
"Run {} has algorithm {} instead of cg",
|
|
r.id, run.algorithm
|
|
);
|
|
}
|
|
println!(" All results verified: algorithm == cg.");
|
|
|
|
// ====================================================================
|
|
// 7. Aggregation: overall benchmark statistics
|
|
// ====================================================================
|
|
println!("\n--- 7. Aggregate Benchmark Statistics ---");
|
|
|
|
let total_converged = runs.iter().filter(|r| r.converged).count();
|
|
let total_time: u64 = runs.iter().map(|r| r.wall_time_us).sum();
|
|
let avg_time = total_time as f64 / num_runs as f64;
|
|
let avg_iters =
|
|
runs.iter().map(|r| r.iterations).sum::<u64>() as f64 / num_runs as f64;
|
|
let avg_rate =
|
|
runs.iter().map(|r| r.convergence_rate).sum::<f64>() / num_runs as f64;
|
|
|
|
let fastest_run = runs
|
|
.iter()
|
|
.filter(|r| r.converged)
|
|
.min_by_key(|r| r.wall_time_us);
|
|
let slowest_run = runs
|
|
.iter()
|
|
.filter(|r| r.converged)
|
|
.max_by_key(|r| r.wall_time_us);
|
|
|
|
println!(" Total runs: {}", num_runs);
|
|
println!(" Converged: {} ({:.1}%)", total_converged, total_converged as f64 / num_runs as f64 * 100.0);
|
|
println!(" Avg wall time: {:.0} us", avg_time);
|
|
println!(" Avg iterations: {:.1}", avg_iters);
|
|
println!(" Avg convergence rate: {:.4}", avg_rate);
|
|
|
|
if let Some(fast) = fastest_run {
|
|
println!(
|
|
" Fastest converged: run {} ({} on {} {}, {} us, {} iters)",
|
|
fast.run_id, fast.algorithm, fast.problem_type, fast.matrix_size,
|
|
fast.wall_time_us, fast.iterations
|
|
);
|
|
}
|
|
if let Some(slow) = slowest_run {
|
|
println!(
|
|
" Slowest converged: run {} ({} on {} {}, {} us, {} iters)",
|
|
slow.run_id, slow.algorithm, slow.problem_type, slow.matrix_size,
|
|
slow.wall_time_us, slow.iterations
|
|
);
|
|
}
|
|
|
|
// Per-algorithm summary
|
|
println!("\n Per-algorithm convergence rates:");
|
|
println!(
|
|
" {:>10} {:>6} {:>10} {:>10}",
|
|
"Algorithm", "Runs", "Conv%", "AvgRate"
|
|
);
|
|
println!(" {:->10} {:->6} {:->10} {:->10}", "", "", "", "");
|
|
for &algo in ALGORITHMS {
|
|
let algo_runs: Vec<&BenchmarkRun> = runs.iter().filter(|r| r.algorithm == algo).collect();
|
|
let count = algo_runs.len();
|
|
if count == 0 {
|
|
continue;
|
|
}
|
|
let conv = algo_runs.iter().filter(|r| r.converged).count();
|
|
let conv_pct = conv as f64 / count as f64 * 100.0;
|
|
let rate = algo_runs.iter().map(|r| r.convergence_rate).sum::<f64>() / count as f64;
|
|
println!(
|
|
" {:>10} {:>6} {:>9.1}% {:>10.4}",
|
|
algo, count, conv_pct, rate
|
|
);
|
|
}
|
|
|
|
// ====================================================================
|
|
// Summary
|
|
// ====================================================================
|
|
println!("\n=== Solver Benchmark Summary ===\n");
|
|
println!(" Benchmark runs: {}", num_runs);
|
|
println!(" Algorithms tested: {}", ALGORITHMS.len());
|
|
println!(" Problem types: {}", PROBLEM_TYPES.len());
|
|
println!(" Embedding dimension: {}", embed_dim);
|
|
println!(" Store vectors: {}", ingest.accepted);
|
|
println!(" Convergence rate: {:.1}%", total_converged as f64 / num_runs as f64 * 100.0);
|
|
|
|
let status = store.status();
|
|
println!(" Store file size: {} bytes", status.file_size);
|
|
println!(" Store epoch: {}", status.current_epoch);
|
|
|
|
store.close().expect("failed to close store");
|
|
println!("\nDone.");
|
|
}
|
|
|
|
fn print_benchmark_results(results: &[SearchResult], runs: &[BenchmarkRun]) {
|
|
println!(
|
|
" {:>4} {:>10} {:>10} {:>12} {:>6} {:>8} {:>10} {:>8}",
|
|
"ID", "Algorithm", "Problem", "Distance", "Size", "Time(us)", "Iters", "Conv"
|
|
);
|
|
println!(
|
|
" {:->4} {:->10} {:->10} {:->12} {:->6} {:->8} {:->10} {:->8}",
|
|
"", "", "", "", "", "", "", ""
|
|
);
|
|
for r in results {
|
|
let run = &runs[r.id as usize];
|
|
println!(
|
|
" {:>4} {:>10} {:>10} {:>12.6} {:>6} {:>8} {:>10} {:>8}",
|
|
r.id,
|
|
run.algorithm,
|
|
run.problem_type,
|
|
r.distance,
|
|
run.matrix_size,
|
|
run.wall_time_us,
|
|
run.iterations,
|
|
if run.converged { "yes" } else { "no" }
|
|
);
|
|
}
|
|
}
|