git-subtree-dir: vendor/ruvector git-subtree-split: b64c21726f2bb37286d9ee36a7869fef60cc6900
268 lines
8.2 KiB
Rust
268 lines
8.2 KiB
Rust
use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion, Throughput};
|
|
use rand::Rng;
|
|
use sparse_persistent_homology::*;
|
|
|
|
/// Generate random points in d-dimensional space
|
|
fn generate_random_points(n: usize, d: usize) -> Vec<Vec<f32>> {
|
|
let mut rng = rand::thread_rng();
|
|
(0..n)
|
|
.map(|_| (0..d).map(|_| rng.gen_range(0.0..1.0)).collect())
|
|
.collect()
|
|
}
|
|
|
|
/// Generate random filtration for testing
|
|
fn generate_random_filtration(n_vertices: usize) -> Filtration {
|
|
let mut filt = Filtration::new();
|
|
let mut rng = rand::thread_rng();
|
|
|
|
// Add vertices
|
|
for i in 0..n_vertices {
|
|
filt.add_simplex(vec![i], rng.gen_range(0.0..1.0));
|
|
}
|
|
|
|
// Add edges
|
|
for i in 0..n_vertices {
|
|
for j in (i + 1)..n_vertices {
|
|
if rng.gen_bool(0.3) {
|
|
// 30% edge probability
|
|
filt.add_simplex(vec![i, j], rng.gen_range(0.0..1.0));
|
|
}
|
|
}
|
|
}
|
|
|
|
filt
|
|
}
|
|
|
|
/// Benchmark distance matrix computation (scalar vs SIMD)
|
|
fn bench_distance_matrix(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("distance_matrix");
|
|
|
|
for n in [10, 50, 100, 200].iter() {
|
|
let points = generate_random_points(*n, 50);
|
|
|
|
group.throughput(Throughput::Elements(*n as u64 * (*n as u64 - 1) / 2));
|
|
group.bench_with_input(BenchmarkId::new("scalar", n), &points, |b, points| {
|
|
b.iter(|| simd_filtration::euclidean_distance_matrix_scalar(black_box(points)));
|
|
});
|
|
|
|
group.bench_with_input(BenchmarkId::new("auto", n), &points, |b, points| {
|
|
b.iter(|| simd_filtration::euclidean_distance_matrix(black_box(points)));
|
|
});
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark apparent pairs identification
|
|
fn bench_apparent_pairs(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("apparent_pairs");
|
|
|
|
for n in [10, 20, 50, 100].iter() {
|
|
let filt = generate_random_filtration(*n);
|
|
|
|
group.throughput(Throughput::Elements(filt.len() as u64));
|
|
group.bench_with_input(BenchmarkId::new("standard", n), &filt, |b, filt| {
|
|
b.iter(|| apparent_pairs::identify_apparent_pairs(black_box(filt)));
|
|
});
|
|
|
|
group.bench_with_input(BenchmarkId::new("fast", n), &filt, |b, filt| {
|
|
b.iter(|| apparent_pairs::identify_apparent_pairs_fast(black_box(filt)));
|
|
});
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark sparse boundary matrix reduction
|
|
fn bench_matrix_reduction(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("matrix_reduction");
|
|
|
|
for n in [10, 20, 30, 50].iter() {
|
|
// Create a simple chain complex
|
|
let mut boundaries = vec![vec![]; *n]; // n vertices
|
|
let mut dimensions = vec![0; *n];
|
|
|
|
// Add edges
|
|
for i in 0..(n - 1) {
|
|
boundaries.push(vec![i, i + 1]);
|
|
dimensions.push(1);
|
|
}
|
|
|
|
group.throughput(Throughput::Elements(boundaries.len() as u64));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("standard_reduction", n),
|
|
&(*n, boundaries.clone(), dimensions.clone()),
|
|
|b, (_, boundaries, dimensions)| {
|
|
b.iter(|| {
|
|
let mut matrix = SparseBoundaryMatrix::from_filtration(
|
|
boundaries.clone(),
|
|
dimensions.clone(),
|
|
vec![],
|
|
);
|
|
black_box(matrix.reduce())
|
|
});
|
|
},
|
|
);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("cohomology_reduction", n),
|
|
&(*n, boundaries.clone(), dimensions.clone()),
|
|
|b, (_, boundaries, dimensions)| {
|
|
b.iter(|| {
|
|
let mut matrix = SparseBoundaryMatrix::from_filtration(
|
|
boundaries.clone(),
|
|
dimensions.clone(),
|
|
vec![],
|
|
);
|
|
black_box(matrix.reduce_cohomology())
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark persistence landscape computation
|
|
fn bench_persistence_landscape(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("persistence_landscape");
|
|
|
|
for n in [10, 50, 100, 200].iter() {
|
|
let features: Vec<_> = (0..*n)
|
|
.map(|i| streaming_homology::PersistenceFeature {
|
|
birth: i as f64 * 0.01,
|
|
death: (i as f64 + 5.0) * 0.01,
|
|
dimension: 1,
|
|
})
|
|
.collect();
|
|
|
|
group.throughput(Throughput::Elements(*n as u64));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("landscape", n),
|
|
&features,
|
|
|b, features| {
|
|
b.iter(|| {
|
|
persistence_vectors::PersistenceLandscape::from_features(black_box(features), 5)
|
|
});
|
|
},
|
|
);
|
|
|
|
group.bench_with_input(
|
|
BenchmarkId::new("persistence_image", n),
|
|
&features,
|
|
|b, features| {
|
|
b.iter(|| {
|
|
persistence_vectors::PersistenceImage::from_features(
|
|
black_box(features),
|
|
32,
|
|
0.1,
|
|
)
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark topological attention mechanism
|
|
fn bench_topological_attention(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("topological_attention");
|
|
|
|
for n in [10, 50, 100, 200].iter() {
|
|
let features: Vec<_> = (0..*n)
|
|
.map(|i| streaming_homology::PersistenceFeature {
|
|
birth: i as f64 * 0.01,
|
|
death: (i as f64 + 5.0) * 0.01,
|
|
dimension: 1,
|
|
})
|
|
.collect();
|
|
|
|
let activations: Vec<f64> = (0..*n).map(|i| i as f64 * 0.1).collect();
|
|
|
|
group.throughput(Throughput::Elements(*n as u64));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("compute_weights", n),
|
|
&features,
|
|
|b, features| {
|
|
b.iter(|| {
|
|
topological_attention::TopologicalAttention::from_features(black_box(features))
|
|
});
|
|
},
|
|
);
|
|
|
|
let attention = topological_attention::TopologicalAttention::from_features(&features);
|
|
group.bench_with_input(
|
|
BenchmarkId::new("apply_attention", n),
|
|
&activations,
|
|
|b, activations| {
|
|
b.iter(|| attention.apply(black_box(activations)));
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark streaming persistence updates
|
|
fn bench_streaming_persistence(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("streaming_persistence");
|
|
|
|
for window_size in [50, 100, 200].iter() {
|
|
group.throughput(Throughput::Elements(1));
|
|
group.bench_with_input(
|
|
BenchmarkId::new("update", window_size),
|
|
window_size,
|
|
|b, &window_size| {
|
|
let mut streaming = StreamingPersistence::new(window_size);
|
|
let point = vec![0.5_f32; 10];
|
|
let mut t = 0.0;
|
|
|
|
b.iter(|| {
|
|
streaming.update(black_box(point.clone()), black_box(t));
|
|
t += 0.01;
|
|
});
|
|
},
|
|
);
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
/// Benchmark Betti number computation
|
|
fn bench_betti_numbers(c: &mut Criterion) {
|
|
let mut group = c.benchmark_group("betti_numbers");
|
|
|
|
for n in [10, 20, 50, 100].iter() {
|
|
let mut boundaries = vec![vec![]; *n];
|
|
let mut dimensions = vec![0; *n];
|
|
|
|
for i in 0..(n - 1) {
|
|
boundaries.push(vec![i, i + 1]);
|
|
dimensions.push(1);
|
|
}
|
|
|
|
let matrix = SparseBoundaryMatrix::from_filtration(boundaries, dimensions, vec![]);
|
|
|
|
group.throughput(Throughput::Elements(*n as u64));
|
|
group.bench_with_input(BenchmarkId::new("fast", n), &matrix, |b, matrix| {
|
|
b.iter(|| betti::compute_betti_fast(black_box(matrix), 2));
|
|
});
|
|
}
|
|
|
|
group.finish();
|
|
}
|
|
|
|
criterion_group!(
|
|
benches,
|
|
bench_distance_matrix,
|
|
bench_apparent_pairs,
|
|
bench_matrix_reduction,
|
|
bench_persistence_landscape,
|
|
bench_topological_attention,
|
|
bench_streaming_persistence,
|
|
bench_betti_numbers,
|
|
);
|
|
|
|
criterion_main!(benches);
|