1use std::cmp::Ordering;
4use std::collections::{BinaryHeap, HashMap, HashSet};
5
6#[derive(Debug, Clone)]
7pub struct CfgGraphNode {
8 pub id: u64,
9 pub x: f32,
10 pub y: f32,
11 pub z: f32,
12 pub successors: Vec<u64>,
13}
14
15#[derive(Debug, Clone)]
16pub struct CfgPath {
17 pub node_ids: Vec<u64>,
18 pub total_cost: f32,
19 pub heuristic_cost: f32,
20 pub actual_cost: f32,
21}
22
23#[derive(Debug, Clone, Copy, PartialEq)]
24struct AStarNode {
25 node_id: u64,
26 f_score: f32,
27 g_score: f32,
28}
29
30impl Eq for AStarNode {}
31
32impl Ord for AStarNode {
33 fn cmp(&self, other: &Self) -> Ordering {
34 other
35 .f_score
36 .partial_cmp(&self.f_score)
37 .unwrap_or(Ordering::Equal)
38 }
39}
40
41impl PartialOrd for AStarNode {
42 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
43 Some(self.cmp(other))
44 }
45}
46
47#[inline]
48fn euclidean_distance(x1: f32, y1: f32, z1: f32, x2: f32, y2: f32, z2: f32) -> f32 {
49 let dx = x2 - x1;
50 let dy = y2 - y1;
51 let dz = z2 - z1;
52 (dx * dx + dy * dy + dz * dz).sqrt()
53}
54
55pub fn astar_find_path(nodes: &[CfgGraphNode], start_id: u64, goal_id: u64) -> Option<CfgPath> {
56 eprintln!(
57 "DEBUG astar_find_path: start_id={}, goal_id={}, nodes.len()={}",
58 start_id,
59 goal_id,
60 nodes.len()
61 );
62 let node_map: HashMap<u64, &CfgGraphNode> = nodes.iter().map(|n| (n.id, n)).collect();
63 let start_node = node_map.get(&start_id)?;
64
65 let actual_goal_id = if goal_id == u64::MAX {
67 let mut farthest_id = start_id;
69 let mut max_dist = 0.0f32;
70 for (&id, node) in &node_map {
71 if id == start_id {
72 continue;
73 }
74 let dist = euclidean_distance(
75 start_node.x,
76 start_node.y,
77 start_node.z,
78 node.x,
79 node.y,
80 node.z,
81 );
82 if dist > max_dist {
83 max_dist = dist;
84 farthest_id = id;
85 }
86 }
87 eprintln!(
88 "DEBUG: start_id={}, goal_id=MAX, farthest_id={}, max_dist={}",
89 start_id, farthest_id, max_dist
90 );
91 farthest_id
92 } else {
93 goal_id
94 };
95
96 let goal_node = node_map.get(&actual_goal_id)?;
97
98 let mut open_set = BinaryHeap::new();
99 let mut in_open_set: HashSet<u64> = HashSet::new();
100 let mut closed_set: HashSet<u64> = HashSet::new();
101 let mut g_score: HashMap<u64, f32> = HashMap::new();
102 let mut came_from: HashMap<u64, u64> = HashMap::new();
103
104 let initial_h = euclidean_distance(
105 start_node.x,
106 start_node.y,
107 start_node.z,
108 goal_node.x,
109 goal_node.y,
110 goal_node.z,
111 );
112 g_score.insert(start_id, 0.0);
113 open_set.push(AStarNode {
114 node_id: start_id,
115 f_score: initial_h,
116 g_score: 0.0,
117 });
118 in_open_set.insert(start_id);
119
120 while let Some(current) = open_set.pop() {
121 let current_id = current.node_id;
122 if closed_set.contains(¤t_id) {
123 continue;
124 }
125 if current_id == actual_goal_id {
126 let mut path = vec![actual_goal_id];
127 let mut cur = actual_goal_id;
128 while let Some(&prev) = came_from.get(&cur) {
129 path.push(prev);
130 cur = prev;
131 }
132 path.reverse();
133 return Some(CfgPath {
134 node_ids: path,
135 total_cost: current.f_score,
136 heuristic_cost: initial_h,
137 actual_cost: current.g_score,
138 });
139 }
140 in_open_set.remove(¤t_id);
141 closed_set.insert(current_id);
142 let current_node = node_map.get(¤t_id)?;
143 eprintln!(
144 "DEBUG: Processing node_id={}, successors={:?}",
145 current_id, current_node.successors
146 );
147 for &succ_id in ¤t_node.successors {
148 if closed_set.contains(&succ_id) {
149 continue;
150 }
151 let succ_node = match node_map.get(&succ_id) {
152 Some(n) => n,
153 None => continue,
154 };
155 let tentative_g = current.g_score + 1.0;
156 let existing_g = g_score.get(&succ_id).copied().unwrap_or(f32::INFINITY);
157 if tentative_g < existing_g {
158 came_from.insert(succ_id, current_id);
159 g_score.insert(succ_id, tentative_g);
160 let h = euclidean_distance(
161 succ_node.x,
162 succ_node.y,
163 succ_node.z,
164 goal_node.x,
165 goal_node.y,
166 goal_node.z,
167 );
168 if !in_open_set.contains(&succ_id) {
169 open_set.push(AStarNode {
170 node_id: succ_id,
171 f_score: tentative_g + h,
172 g_score: tentative_g,
173 });
174 in_open_set.insert(succ_id);
175 }
176 }
177 }
178 }
179 None
180}
181
182pub fn astar_find_k_paths(
183 nodes: &[CfgGraphNode],
184 start_id: u64,
185 goal_id: u64,
186 k: usize,
187) -> Vec<CfgPath> {
188 let mut paths: Vec<CfgPath> = Vec::new();
189 if let Some(first) = astar_find_path(nodes, start_id, goal_id) {
190 paths.push(first);
191 } else {
192 return paths;
193 }
194 let mut candidates: Vec<CfgPath> = Vec::new();
195 for i in 0..(k - 1).min(paths.len()) {
196 let base = &paths[i];
197 for j in 0..(base.node_ids.len() - 1) {
198 let spur = base.node_ids[j];
199 let modified: Vec<CfgGraphNode> = nodes
200 .iter()
201 .map(|n| {
202 let mut m = n.clone();
203 if j > 0 && n.id == base.node_ids[j - 1] {
204 m.successors.retain(|&s| s != spur);
205 }
206 m
207 })
208 .collect();
209 if let Some(spur_path) = astar_find_path(&modified, spur, goal_id) {
210 let mut combined: Vec<u64> = base.node_ids[..=j].to_vec();
211 combined.extend_from_slice(&spur_path.node_ids[1..]);
212 let cp = CfgPath {
213 node_ids: combined,
214 total_cost: spur_path.total_cost + j as f32,
215 heuristic_cost: spur_path.heuristic_cost,
216 actual_cost: spur_path.actual_cost + j as f32,
217 };
218 if !candidates.iter().any(|p| p.node_ids == cp.node_ids) {
219 candidates.push(cp);
220 }
221 }
222 }
223 candidates.sort_by(|a, b| {
224 a.total_cost
225 .partial_cmp(&b.total_cost)
226 .unwrap_or(Ordering::Equal)
227 });
228 if let Some(best) = candidates.pop() {
229 paths.push(best);
230 }
231 }
232 paths
233}
234
235#[derive(Debug, Clone)]
236pub struct PathComplexity {
237 pub path_length: usize,
238 pub max_loop_depth: u32,
239 pub branch_count: usize,
240 pub total_spatial_distance: f32,
241 pub heuristic_efficiency: f32,
242}
243
244pub fn analyze_path_complexity(path: &CfgPath, nodes: &[CfgGraphNode]) -> PathComplexity {
245 let node_map: HashMap<u64, &CfgGraphNode> = nodes.iter().map(|n| (n.id, n)).collect();
246 let mut max_ld = 0.0f32;
247 let mut bc = 0;
248 let mut tsd = 0.0f32;
249 for i in 0..path.node_ids.len() {
250 if let Some(&n) = node_map.get(&path.node_ids[i]) {
251 max_ld = max_ld.max(n.y);
252 if n.z > 0.0 {
253 bc += 1;
254 }
255 if i > 0 {
256 if let Some(&p) = node_map.get(&path.node_ids[i - 1]) {
257 tsd += euclidean_distance(p.x, p.y, p.z, n.x, n.y, n.z);
258 }
259 }
260 }
261 }
262 PathComplexity {
263 path_length: path.node_ids.len(),
264 max_loop_depth: max_ld as u32,
265 branch_count: bc,
266 total_spatial_distance: tsd,
267 heuristic_efficiency: if path.heuristic_cost > 0.0 {
268 path.actual_cost / path.heuristic_cost
269 } else {
270 1.0
271 },
272 }
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278 #[test]
279 fn test_astar_simple() {
280 let n = vec![
281 CfgGraphNode {
282 id: 0,
283 x: 0.0,
284 y: 0.0,
285 z: 0.0,
286 successors: vec![1],
287 },
288 CfgGraphNode {
289 id: 1,
290 x: 1.0,
291 y: 0.0,
292 z: 0.0,
293 successors: vec![2],
294 },
295 CfgGraphNode {
296 id: 2,
297 x: 2.0,
298 y: 0.0,
299 z: 0.0,
300 successors: vec![3],
301 },
302 CfgGraphNode {
303 id: 3,
304 x: 3.0,
305 y: 0.0,
306 z: 0.0,
307 successors: vec![],
308 },
309 ];
310 assert_eq!(
311 astar_find_path(&n, 0, 3).unwrap().node_ids,
312 vec![0, 1, 2, 3]
313 );
314 }
315 #[test]
316 fn test_astar_branch() {
317 let n = vec![
318 CfgGraphNode {
319 id: 0,
320 x: 0.0,
321 y: 0.0,
322 z: 0.0,
323 successors: vec![1, 2],
324 },
325 CfgGraphNode {
326 id: 1,
327 x: 1.0,
328 y: 0.0,
329 z: 1.0,
330 successors: vec![3],
331 },
332 CfgGraphNode {
333 id: 2,
334 x: 1.0,
335 y: 0.0,
336 z: 0.0,
337 successors: vec![3],
338 },
339 CfgGraphNode {
340 id: 3,
341 x: 2.0,
342 y: 0.0,
343 z: 0.0,
344 successors: vec![],
345 },
346 ];
347 assert!(astar_find_path(&n, 0, 3).is_some());
348 }
349 #[test]
350 fn test_astar_nopath() {
351 let n = vec![
352 CfgGraphNode {
353 id: 0,
354 x: 0.0,
355 y: 0.0,
356 z: 0.0,
357 successors: vec![1],
358 },
359 CfgGraphNode {
360 id: 1,
361 x: 1.0,
362 y: 0.0,
363 z: 0.0,
364 successors: vec![],
365 },
366 ];
367 assert!(astar_find_path(&n, 0, 2).is_none());
368 }
369 #[test]
370 fn test_astar_kpaths() {
371 let n = vec![
372 CfgGraphNode {
373 id: 0,
374 x: 0.0,
375 y: 0.0,
376 z: 0.0,
377 successors: vec![1, 2, 3],
378 },
379 CfgGraphNode {
380 id: 1,
381 x: 1.0,
382 y: 0.0,
383 z: 0.0,
384 successors: vec![4],
385 },
386 CfgGraphNode {
387 id: 2,
388 x: 1.0,
389 y: 0.0,
390 z: 0.0,
391 successors: vec![4],
392 },
393 CfgGraphNode {
394 id: 3,
395 x: 1.0,
396 y: 0.0,
397 z: 0.0,
398 successors: vec![4],
399 },
400 CfgGraphNode {
401 id: 4,
402 x: 2.0,
403 y: 0.0,
404 z: 0.0,
405 successors: vec![],
406 },
407 ];
408 let paths = astar_find_k_paths(&n, 0, 4, 3);
409 assert!(paths.len() >= 2 && paths.len() <= 3);
410 }
411}