1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct AgentNode {
12 pub id: String,
13 pub capabilities: Vec<String>,
14 pub load: f64,
16}
17
18impl AgentNode {
19 pub fn new(id: impl Into<String>, capabilities: Vec<String>, load: f64) -> Self {
20 Self {
21 id: id.into(),
22 capabilities,
23 load,
24 }
25 }
26}
27
28#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct CommEdge {
31 pub from: usize,
32 pub to: usize,
33 pub bandwidth: f64,
35 pub latency: f64,
37}
38
39impl CommEdge {
40 pub fn new(from: usize, to: usize, bandwidth: f64, latency: f64) -> Self {
41 Self {
42 from,
43 to,
44 bandwidth,
45 latency,
46 }
47 }
48}
49
50#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct FleetGraph {
53 pub agents: Vec<AgentNode>,
54 pub edges: Vec<CommEdge>,
55}
56
57impl FleetGraph {
58 pub fn new() -> Self {
60 Self {
61 agents: Vec::new(),
62 edges: Vec::new(),
63 }
64 }
65
66 pub fn add_agent(&mut self, agent: AgentNode) -> usize {
68 let idx = self.agents.len();
69 self.agents.push(agent);
70 idx
71 }
72
73 pub fn add_edge(&mut self, edge: CommEdge) {
75 assert!(edge.from < self.agents.len(), "edge.from out of bounds");
76 assert!(edge.to < self.agents.len(), "edge.to out of bounds");
77 self.edges.push(edge);
78 }
79
80 pub fn node_count(&self) -> usize {
82 self.agents.len()
83 }
84
85 pub fn edge_count(&self) -> usize {
87 self.edges.len()
88 }
89
90 pub fn len(&self) -> usize {
92 self.agents.len()
93 }
94
95 pub fn is_empty(&self) -> bool {
97 self.agents.is_empty()
98 }
99
100 pub fn adjacency_matrix(&self) -> Vec<Vec<f64>> {
103 let n = self.node_count();
104 let mut adj = vec![vec![0.0; n]; n];
105 for edge in &self.edges {
106 adj[edge.from][edge.to] += edge.bandwidth;
107 }
108 adj
109 }
110
111 pub fn undirected_adjacency(&self) -> Vec<Vec<f64>> {
113 let n = self.node_count();
114 let mut adj = vec![vec![0.0; n]; n];
115 for edge in &self.edges {
116 let w = edge.bandwidth;
117 adj[edge.from][edge.to] = f64::max(adj[edge.from][edge.to], w);
118 adj[edge.to][edge.from] = f64::max(adj[edge.to][edge.from], w);
119 }
120 adj
121 }
122
123 pub fn degree_matrix(&self) -> Vec<Vec<f64>> {
126 let adj = self.undirected_adjacency();
127 let n = self.node_count();
128 let mut deg = vec![vec![0.0; n]; n];
129 for i in 0..n {
130 let mut d = 0.0;
131 for j in 0..n {
132 d += adj[i][j];
133 }
134 deg[i][i] = d;
135 }
136 deg
137 }
138
139 pub fn neighbors(&self, i: usize) -> Vec<usize> {
141 let mut neighbors = Vec::new();
142 for edge in &self.edges {
143 if edge.from == i {
144 neighbors.push(edge.to);
145 }
146 if edge.to == i {
147 neighbors.push(edge.from);
148 }
149 }
150 neighbors.sort();
151 neighbors.dedup();
152 neighbors
153 }
154
155 pub fn is_connected(&self) -> bool {
157 if self.node_count() == 0 {
158 return true;
159 }
160 let n = self.node_count();
161 let adj = self.undirected_adjacency();
162 let mut visited = vec![false; n];
163 let mut stack = vec![0usize];
164 visited[0] = true;
165 while let Some(node) = stack.pop() {
166 for j in 0..n {
167 if adj[node][j] > 0.0 && !visited[j] {
168 visited[j] = true;
169 stack.push(j);
170 }
171 }
172 }
173 visited.iter().all(|&v| v)
174 }
175
176 pub fn connected_components(&self) -> Vec<Vec<usize>> {
178 let n = self.node_count();
179 if n == 0 {
180 return Vec::new();
181 }
182 let adj = self.undirected_adjacency();
183 let mut visited = vec![false; n];
184 let mut components = Vec::new();
185 for start in 0..n {
186 if visited[start] {
187 continue;
188 }
189 let mut component = Vec::new();
190 let mut stack = vec![start];
191 visited[start] = true;
192 while let Some(node) = stack.pop() {
193 component.push(node);
194 for j in 0..n {
195 if adj[node][j] > 0.0 && !visited[j] {
196 visited[j] = true;
197 stack.push(j);
198 }
199 }
200 }
201 components.push(component);
202 }
203 components
204 }
205
206 pub fn from_github_org(
210 repos: Vec<(String, Vec<String>)>,
211 dependencies: Vec<(String, String, f64)>,
212 ) -> Self {
213 let mut graph = Self::new();
214 let mut name_to_idx = std::collections::HashMap::new();
216 for (name, caps) in &repos {
217 let idx = graph.add_agent(AgentNode::new(name.as_str(), caps.clone(), 0.0));
218 name_to_idx.insert(name.clone(), idx);
219 }
220 for (from_name, to_name, weight) in dependencies {
221 if let (Some(&from), Some(&to)) =
222 (name_to_idx.get(&from_name), name_to_idx.get(&to_name))
223 {
224 graph.add_edge(CommEdge::new(from, to, weight, 1.0));
225 }
226 }
227 graph
228 }
229
230 pub fn complete(n: usize) -> Self {
232 let mut graph = Self::new();
233 for i in 0..n {
234 graph.add_agent(AgentNode::new(
235 format!("agent-{i}"),
236 vec!["general".into()],
237 0.0,
238 ));
239 }
240 for i in 0..n {
241 for j in 0..n {
242 if i != j {
243 graph.add_edge(CommEdge::new(i, j, 1.0, 1.0));
244 }
245 }
246 }
247 graph
248 }
249
250 pub fn path(n: usize) -> Self {
252 let mut graph = Self::new();
253 for i in 0..n {
254 graph.add_agent(AgentNode::new(
255 format!("agent-{i}"),
256 vec!["general".into()],
257 0.0,
258 ));
259 }
260 for i in 0..n.saturating_sub(1) {
261 graph.add_edge(CommEdge::new(i, i + 1, 1.0, 1.0));
262 graph.add_edge(CommEdge::new(i + 1, i, 1.0, 1.0));
263 }
264 graph
265 }
266
267 pub fn cycle(n: usize) -> Self {
269 let mut graph = Self::path(n);
270 if n > 2 {
271 graph.add_edge(CommEdge::new(n - 1, 0, 1.0, 1.0));
272 graph.add_edge(CommEdge::new(0, n - 1, 1.0, 1.0));
273 }
274 graph
275 }
276
277 pub fn star(n: usize) -> Self {
279 let mut graph = Self::new();
280 for i in 0..n {
281 graph.add_agent(AgentNode::new(
282 format!("agent-{i}"),
283 vec!["general".into()],
284 0.0,
285 ));
286 }
287 for i in 1..n {
288 graph.add_edge(CommEdge::new(0, i, 1.0, 1.0));
289 graph.add_edge(CommEdge::new(i, 0, 1.0, 1.0));
290 }
291 graph
292 }
293
294 pub fn barbell(k: usize) -> Self {
296 let n = 2 * k;
297 let mut graph = Self::new();
298 for i in 0..n {
299 graph.add_agent(AgentNode::new(
300 format!("agent-{i}"),
301 vec!["general".into()],
302 0.0,
303 ));
304 }
305 for i in 0..k {
307 for j in 0..k {
308 if i != j {
309 graph.add_edge(CommEdge::new(i, j, 1.0, 1.0));
310 }
311 }
312 }
313 for i in k..n {
315 for j in k..n {
316 if i != j {
317 graph.add_edge(CommEdge::new(i, j, 1.0, 1.0));
318 }
319 }
320 }
321 graph.add_edge(CommEdge::new(k - 1, k, 1.0, 1.0));
323 graph.add_edge(CommEdge::new(k, k - 1, 1.0, 1.0));
324 graph
325 }
326
327 pub fn two_cliques(k: usize) -> Self {
329 let n = 2 * k;
330 let mut graph = Self::new();
331 for i in 0..n {
332 graph.add_agent(AgentNode::new(
333 format!("agent-{i}"),
334 vec!["general".into()],
335 0.0,
336 ));
337 }
338 for i in 0..k {
340 for j in 0..k {
341 if i != j {
342 graph.add_edge(CommEdge::new(i, j, 1.0, 1.0));
343 }
344 }
345 }
346 for i in k..n {
348 for j in k..n {
349 if i != j {
350 graph.add_edge(CommEdge::new(i, j, 1.0, 1.0));
351 }
352 }
353 }
354 graph
355 }
356
357 pub fn random(n: usize, p: f64, seed: u64) -> Self {
359 let mut graph = Self::new();
360 for i in 0..n {
361 graph.add_agent(AgentNode::new(
362 format!("agent-{i}"),
363 vec!["general".into()],
364 0.0,
365 ));
366 }
367 let mut rng = seed;
369 let next_rand = |rng: &mut u64| {
370 *rng = rng.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
371 (*rng >> 33) as f64 / (1u64 << 31) as f64
372 };
373 for i in 0..n {
374 for j in 0..n {
375 if i != j && next_rand(&mut rng) < p {
376 graph.add_edge(CommEdge::new(i, j, 1.0, 1.0));
377 graph.add_edge(CommEdge::new(j, i, 1.0, 1.0));
378 }
379 }
380 }
381 graph
382 }
383}
384
385impl Default for FleetGraph {
386 fn default() -> Self {
387 Self::new()
388 }
389}