use koru_lambda_core::DistinctionEngine;
use petgraph::graph::{Graph, NodeIndex};
use petgraph::visit::IntoNodeIdentifiers;
use rand::prelude::*;
use std::collections::HashMap;
mod graph_helpers {
use super::*;
pub fn build_graph(engine: &DistinctionEngine) -> Graph<String, (), petgraph::Undirected> {
let (distinctions, relationships) = engine.get_state_snapshot();
let mut graph = Graph::new_undirected();
let mut node_map: HashMap<String, NodeIndex> = HashMap::new();
for distinction in &distinctions {
let node_idx = graph.add_node(distinction.id().to_string());
node_map.insert(distinction.id().to_string(), node_idx);
}
for (id_a, id_b) in &relationships {
if let (Some(&idx_a), Some(&idx_b)) = (node_map.get(id_a), node_map.get(id_b)) {
graph.add_edge(idx_a, idx_b, ());
}
}
graph
}
pub fn largest_component_size(graph: &Graph<String, (), petgraph::Undirected>) -> usize {
if graph.node_count() == 0 {
return 0;
}
let mut visited = vec![false; graph.node_count()];
let mut max_component_size = 0;
for node in graph.node_identifiers() {
let node_idx = node.index();
if !visited[node_idx] {
let component_size = dfs_component_size(graph, node, &mut visited);
max_component_size = max_component_size.max(component_size);
}
}
max_component_size
}
fn dfs_component_size(
graph: &Graph<String, (), petgraph::Undirected>,
start: NodeIndex,
visited: &mut [bool],
) -> usize {
let mut stack = vec![start];
let mut size = 0;
while let Some(node) = stack.pop() {
let node_idx = node.index();
if visited[node_idx] {
continue;
}
visited[node_idx] = true;
size += 1;
for neighbor in graph.neighbors(node) {
if !visited[neighbor.index()] {
stack.push(neighbor);
}
}
}
size
}
}
#[test]
fn test_falsify_uniform_vulnerability() {
println!("\nTest: Uniform Vulnerability Falsification");
println!(" Testing for scale-free topology...");
let engine = DistinctionEngine::new();
let mut rng = StdRng::seed_from_u64(42);
println!(" Executing 3000 synthesis operations with degree bias...");
for step in 0..3000 {
let distinctions = engine.get_state_snapshot().0;
if distinctions.len() < 2 {
continue;
}
let graph = graph_helpers::build_graph(&engine);
let mut node_degrees: HashMap<String, usize> = HashMap::new();
for node_idx in graph.node_identifiers() {
let id = &graph[node_idx];
node_degrees.insert(id.clone(), graph.neighbors(node_idx).count());
}
let weights: Vec<f64> = distinctions
.iter()
.map(|d| (node_degrees.get(d.id()).copied().unwrap_or(0) + 1) as f64)
.collect();
let total_weight: f64 = weights.iter().sum();
let r1 = rng.gen::<f64>() * total_weight;
let mut cumulative = 0.0;
let mut parent1_idx = 0;
for (i, &weight) in weights.iter().enumerate() {
cumulative += weight;
if cumulative >= r1 {
parent1_idx = i;
break;
}
}
let mut parent2_idx = parent1_idx;
let mut attempts = 0;
while parent2_idx == parent1_idx && attempts < 10 {
let r2 = rng.gen::<f64>() * total_weight;
cumulative = 0.0;
for (i, &weight) in weights.iter().enumerate() {
cumulative += weight;
if cumulative >= r2 {
parent2_idx = i;
break;
}
}
attempts += 1;
}
if parent2_idx == parent1_idx && distinctions.len() > 1 {
parent2_idx = (parent1_idx + 1) % distinctions.len();
}
let parent1 = &distinctions[parent1_idx];
let parent2 = &distinctions[parent2_idx];
engine.synthesize(parent1, parent2);
if (step + 1) % 500 == 0 {
println!(
" Progress: {} operations, {} distinctions",
step + 1,
engine.distinction_count()
);
}
}
let original_graph = graph_helpers::build_graph(&engine);
let initial_size = original_graph.node_count();
println!(" Graph size: {} nodes, {} edges", initial_size, original_graph.edge_count());
assert!(
initial_size >= 100,
"Insufficient graph size for robust testing: {} nodes",
initial_size
);
println!(" Testing resilience to random failures...");
let attack_percent = 0.20;
let num_to_remove = (initial_size as f64 * attack_percent) as usize;
let mut random_graph = original_graph.clone();
let all_nodes: Vec<_> = random_graph.node_identifiers().collect();
let nodes_to_remove: Vec<_> =
all_nodes.choose_multiple(&mut rng, num_to_remove).cloned().collect();
for node in nodes_to_remove {
random_graph.remove_node(node);
}
let largest_random = graph_helpers::largest_component_size(&random_graph);
let survival_random = largest_random as f64 / initial_size as f64;
println!(
" Survival after random removal ({:.0}%): {:.2}%",
attack_percent * 100.0,
survival_random * 100.0
);
println!(" Testing vulnerability to targeted attacks...");
let mut targeted_graph = original_graph.clone();
let mut node_degrees: Vec<_> = targeted_graph
.node_identifiers()
.map(|n| (n, targeted_graph.neighbors(n).count()))
.collect();
node_degrees.sort_by(|a, b| b.1.cmp(&a.1));
let hubs_to_remove: Vec<_> = node_degrees.iter().take(num_to_remove).map(|(n, _)| *n).collect();
for node in hubs_to_remove {
targeted_graph.remove_node(node);
}
let largest_targeted = graph_helpers::largest_component_size(&targeted_graph);
let survival_targeted = largest_targeted as f64 / initial_size as f64;
println!(
" Survival after targeted hub removal ({:.0}%): {:.2}%",
attack_percent * 100.0,
survival_targeted * 100.0
);
let vulnerability_gap = survival_random - survival_targeted;
println!(" Vulnerability gap: {:.2}", vulnerability_gap);
assert!(
vulnerability_gap > 0.20,
"FALSIFIED: Uniform vulnerability observed (gap: {:.2}).\n \
Scale-free topology not detected.\n \
Random survival: {:.2}%, Targeted survival: {:.2}%",
vulnerability_gap,
survival_random * 100.0,
survival_targeted * 100.0
);
println!("\nHypothesis sustained.");
println!(" Scale-free topology exhibits differential vulnerability:");
println!(" Random attack survival: {:.1}%", survival_random * 100.0);
println!(" Targeted attack survival: {:.1}%", survival_targeted * 100.0);
println!(" Vulnerability gap: {:.1}%", vulnerability_gap * 100.0);
println!(" Hub-dependent topology confirmed through preferential attachment");
}
#[test]
fn test_falsify_random_degree_distribution() {
println!("\nTest: Random Degree Distribution Falsification");
println!(" Testing for power-law degree concentration...");
let engine = DistinctionEngine::new();
let mut rng = StdRng::seed_from_u64(123);
println!(" Executing 2000 synthesis operations with degree bias...");
for _step in 0..2000 {
let distinctions = engine.get_state_snapshot().0;
if distinctions.len() < 2 {
continue;
}
let graph = graph_helpers::build_graph(&engine);
let weights: Vec<f64> = distinctions
.iter()
.map(|d| {
let node_idx = graph.node_identifiers().find(|&n| graph[n] == d.id());
if let Some(idx) = node_idx {
(graph.neighbors(idx).count() + 1) as f64
} else {
1.0
}
})
.collect();
let total_weight: f64 = weights.iter().sum();
let r1 = rng.gen::<f64>() * total_weight;
let mut cumulative = 0.0;
let mut parent1_idx = 0;
for (i, &weight) in weights.iter().enumerate() {
cumulative += weight;
if cumulative >= r1 {
parent1_idx = i;
break;
}
}
let mut parent2_idx = parent1_idx;
let mut attempts = 0;
while parent2_idx == parent1_idx && attempts < 10 {
let r2 = rng.gen::<f64>() * total_weight;
cumulative = 0.0;
for (i, &weight) in weights.iter().enumerate() {
cumulative += weight;
if cumulative >= r2 {
parent2_idx = i;
break;
}
}
attempts += 1;
}
if parent2_idx == parent1_idx && distinctions.len() > 1 {
parent2_idx = (parent1_idx + 1) % distinctions.len();
}
engine.synthesize(&distinctions[parent1_idx], &distinctions[parent2_idx]);
}
let graph = graph_helpers::build_graph(&engine);
let total_nodes = graph.node_count();
println!(" Graph size: {} nodes", total_nodes);
let mut degrees: Vec<usize> =
graph.node_identifiers().map(|n| graph.neighbors(n).count()).collect();
degrees.sort_by(|a, b| b.cmp(a));
let top_20_percent = (total_nodes as f64 * 0.2).ceil() as usize;
let top_20_degrees: usize = degrees.iter().take(top_20_percent).sum();
let total_degree: usize = degrees.iter().sum();
let concentration = top_20_degrees as f64 / total_degree as f64;
println!(" Top 20% of nodes account for {:.1}% of connections", concentration * 100.0);
assert!(
concentration > 0.45,
"FALSIFIED: Random degree distribution detected (concentration: {:.2}).\n \
Power-law distribution not observed.\n \
Top 20% nodes account for only {:.1}% of connections (expected >45%)",
concentration,
concentration * 100.0
);
println!("\nHypothesis sustained.");
println!(" Power-law degree distribution confirmed:");
println!(" Top 20% of nodes: {:.1}% of all connections", concentration * 100.0);
println!(" Scale-free network structure verified");
}