#![allow(non_snake_case)]
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FleetAgent {
pub id: u64,
pub position: [f64; 2],
pub neighbors: Vec<u64>,
pub capabilities: Vec<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FleetGraph {
agents: Vec<FleetAgent>,
agent_index: HashMap<u64, usize>,
adjacency: HashMap<u64, HashSet<u64>>,
edge_count: usize,
}
impl FleetGraph {
pub fn new() -> Self {
Self {
agents: Vec::new(),
agent_index: HashMap::new(),
adjacency: HashMap::new(),
edge_count: 0,
}
}
pub fn add_agent(&mut self, id: u64, position: [f64; 2], capabilities: Vec<String>) {
if let std::collections::hash_map::Entry::Vacant(e) = self.adjacency.entry(id) {
e.insert(HashSet::new());
let idx = self.agents.len();
self.agent_index.insert(id, idx);
self.agents.push(FleetAgent { id, position, neighbors: vec![], capabilities });
}
}
pub fn add_edge(&mut self, a: u64, b: u64) {
let mut new_edge = false;
if let Some(set) = self.adjacency.get_mut(&a) {
if set.insert(b) {
new_edge = true;
if let Some(agent) = self.agents.iter_mut().find(|ag| ag.id == a) {
if !agent.neighbors.contains(&b) {
agent.neighbors.push(b);
}
}
}
}
if let Some(set) = self.adjacency.get_mut(&b) {
if set.insert(a) {
if new_edge {
self.edge_count += 1;
}
if let Some(agent) = self.agents.iter_mut().find(|ag| ag.id == b) {
if !agent.neighbors.contains(&a) {
agent.neighbors.push(a);
}
}
}
}
}
pub fn V(&self) -> usize {
self.agents.len()
}
pub fn E(&self) -> usize {
self.edge_count
}
pub fn check_laman_rigidity(&self) -> RigidityResult {
let V = self.V();
let E = self.E();
let expected_E = 2 * V - 3;
let e_ratio = if expected_E > 0 { E as f64 / expected_E as f64 } else { 1.0 };
let subgraph_check = if V < 3 {
true
} else {
let mut ok = true;
for agent in &self.agents {
let v_prime = agent.neighbors.len() + 1;
let e_prime = agent.neighbors.len();
if v_prime >= 2 && e_prime > 2 * v_prime - 3 {
ok = false;
break;
}
}
ok
};
let is_rigid = (e_ratio - 1.0).abs() < 0.05 && subgraph_check;
let H1 = if E >= V { E - V + 1 } else { 0 };
RigidityResult {
is_rigid,
V,
E,
expected_E,
h1_dimension: H1,
critical_subgraphs: vec![],
max_neighbors: self.agents.iter().map(|a| a.neighbors.len()).max().unwrap_or(0),
}
}
pub fn max_neighbors(&self) -> usize {
self.agents.iter().map(|a| a.neighbors.len()).max().unwrap_or(0)
}
pub fn get_agent(&self, id: u64) -> Option<&FleetAgent> {
self.agent_index.get(&id).and_then(|&idx| self.agents.get(idx))
}
pub fn get_neighbors(&self, id: u64) -> Vec<u64> {
self.adjacency.get(&id).map(|s| s.iter().copied().collect::<Vec<u64>>()).unwrap_or_default()
}
}
impl Default for FleetGraph {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct RigidityResult {
pub is_rigid: bool,
pub V: usize,
pub E: usize,
pub expected_E: usize,
pub h1_dimension: usize,
pub critical_subgraphs: Vec<u64>,
pub max_neighbors: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_laman_rigid_triangle() {
let mut graph = FleetGraph::new();
graph.add_agent(1, [0.0, 0.0], vec![]);
graph.add_agent(2, [1.0, 0.0], vec![]);
graph.add_agent(3, [0.5, 0.87], vec![]);
graph.add_edge(1, 2);
graph.add_edge(2, 3);
graph.add_edge(3, 1);
let result = graph.check_laman_rigidity();
assert!(result.is_rigid);
assert_eq!(result.V, 3);
assert_eq!(result.E, 3);
assert_eq!(result.max_neighbors, 2);
}
#[test]
fn test_laman_indeterminate_square() {
let mut graph = FleetGraph::new();
graph.add_agent(1, [0.0, 0.0], vec![]);
graph.add_agent(2, [1.0, 0.0], vec![]);
graph.add_agent(3, [1.0, 1.0], vec![]);
graph.add_agent(4, [0.0, 1.0], vec![]);
graph.add_edge(1, 2);
graph.add_edge(2, 3);
graph.add_edge(3, 4);
graph.add_edge(4, 1);
let result = graph.check_laman_rigidity();
assert!(!result.is_rigid);
assert_eq!(result.max_neighbors, 2);
}
#[test]
fn test_k12_complete_graph() {
let mut graph = FleetGraph::new();
for i in 0..12 {
graph.add_agent(i as u64, [i as f64, 0.0], vec![]);
}
for i in 0..12 {
for j in (i+1)..12 {
graph.add_edge(i as u64, j as u64);
}
}
let result = graph.check_laman_rigidity();
assert!(!result.is_rigid); assert_eq!(result.max_neighbors, 11);
}
#[test]
fn test_h1_dimension_cycle() {
let mut graph = FleetGraph::new();
graph.add_agent(0, [0.0, 0.0], vec![]);
graph.add_agent(1, [1.0, 0.0], vec![]);
graph.add_agent(2, [0.0, 1.0], vec![]);
graph.add_edge(0, 1);
graph.add_edge(1, 2);
graph.add_edge(2, 0);
let result = graph.check_laman_rigidity();
assert_eq!(result.h1_dimension, 1);
assert!(result.is_rigid); }
#[test]
fn test_o1_agent_lookup() {
let mut graph = FleetGraph::new();
for i in 0..100 {
graph.add_agent(i, [i as f64, 0.0], vec![format!("cap_{}", i)]);
}
let start = std::time::Instant::now();
for i in 0..100 {
let agent = graph.get_agent(i);
assert!(agent.is_some());
assert_eq!(agent.unwrap().id, i);
}
let elapsed = start.elapsed();
assert!(elapsed.as_secs_f64() < 0.001, "O(1) lookup took {}s for 100 agents", elapsed.as_secs_f64());
}
#[test]
fn test_o1_lookup_not_found() {
let mut graph = FleetGraph::new();
graph.add_agent(42, [0.0, 0.0], vec![]);
assert!(graph.get_agent(999).is_none());
}
#[test]
fn test_agent_index_consistency() {
let mut graph = FleetGraph::new();
for i in 0..50 {
graph.add_agent(i * 2, [i as f64, 0.0], vec![]);
}
for i in 0..50 {
let agent = graph.get_agent(i * 2).expect(&format!("agent {} should exist", i * 2));
assert_eq!(agent.id, i * 2);
}
}
}