use super::{SimilarityEdge, build_adjacency};
const MAX_NODES: usize = 3;
const MAX_EDGES: usize = 3;
fn constrained_node_count() -> usize {
let node_count: usize = kani::any();
kani::assume(node_count <= MAX_NODES);
node_count
}
fn constrained_edges(node_count: usize) -> Vec<SimilarityEdge> {
let active_count: usize = kani::any();
kani::assume(active_count <= MAX_EDGES);
let mut edges = Vec::new();
let mut seen = [[false; MAX_NODES]; MAX_NODES];
for _ in 0..active_count {
let left: usize = kani::any();
let right: usize = kani::any();
let weight: u64 = kani::any();
kani::assume(left < right);
kani::assume(right < node_count);
kani::assume(weight > 0);
kani::assume(!seen[left][right]);
seen[left][right] = true;
edges.push(SimilarityEdge::new(left, right, weight));
}
edges
}
fn symbolic_adjacency() -> (usize, Vec<SimilarityEdge>, Vec<Vec<(usize, u64)>>) {
let node_count = constrained_node_count();
let edges = constrained_edges(node_count);
let adjacency = build_adjacency(node_count, &edges);
(node_count, edges, adjacency)
}
fn incident_degree(edges: &[SimilarityEdge], node: usize) -> usize {
edges
.iter()
.filter(|e| e.left == node || e.right == node)
.count()
}
fn is_edge_in_input(edges: &[SimilarityEdge], node: usize, neighbour: usize, weight: u64) -> bool {
edges.iter().any(|e| {
(e.left == node && e.right == neighbour && e.weight == weight)
|| (e.right == node && e.left == neighbour && e.weight == weight)
})
}
#[kani::proof]
#[kani::unwind(7)]
fn verify_build_adjacency_length() {
let (node_count, _edges, adjacency) = symbolic_adjacency();
assert_eq!(adjacency.len(), node_count);
}
#[kani::proof]
#[kani::unwind(7)]
fn verify_build_adjacency_preserves_edges() {
let (node_count, edges, adjacency) = symbolic_adjacency();
kani::assume(node_count > 0);
for edge in &edges {
let forward_found = adjacency[edge.left]
.iter()
.any(|&(neighbour, weight)| neighbour == edge.right && weight == edge.weight);
assert!(forward_found);
let reverse_found = adjacency[edge.right]
.iter()
.any(|&(neighbour, weight)| neighbour == edge.left && weight == edge.weight);
assert!(reverse_found);
}
}
#[kani::proof]
#[kani::unwind(7)]
fn verify_build_adjacency_indices_in_bounds() {
let (node_count, _edges, adjacency) = symbolic_adjacency();
for bucket in &adjacency {
for &(neighbour, _weight) in bucket {
assert!(neighbour < node_count);
}
}
}
#[kani::proof]
#[kani::unwind(7)]
fn verify_build_adjacency_symmetry() {
let (node_count, _edges, adjacency) = symbolic_adjacency();
kani::assume(node_count > 0);
for (node, bucket) in adjacency.iter().enumerate() {
for &(neighbour, weight) in bucket {
let mirror_found =
adjacency[neighbour]
.iter()
.any(|&(mirror_neighbour, mirror_weight)| {
mirror_neighbour == node && mirror_weight == weight
});
assert!(mirror_found);
}
}
}
#[kani::proof]
#[kani::unwind(7)]
fn verify_build_adjacency_no_spurious_edges() {
let (node_count, edges, adjacency) = symbolic_adjacency();
kani::assume(node_count > 0);
for (node, bucket) in adjacency.iter().enumerate() {
assert_eq!(bucket.len(), incident_degree(&edges, node));
for &(neighbour, weight) in bucket {
assert!(is_edge_in_input(&edges, node, neighbour, weight));
}
}
}
#[kani::proof]
#[kani::unwind(7)]
fn verify_build_adjacency_sorted_neighbours() {
let (_node_count, _edges, adjacency) = symbolic_adjacency();
for bucket in &adjacency {
for window in bucket.windows(2) {
assert!(window[0].0 <= window[1].0);
}
}
}