use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct LoopBlock {
pub id: u64,
pub x: f32, pub y: f32, pub z: f32, }
#[derive(Debug, Clone)]
pub struct LoopInfo {
pub level: u32,
pub block_ids: Vec<u64>,
pub entry_block: u64,
pub has_nested_loops: bool,
}
#[derive(Debug, Clone)]
pub struct LoopAnalysisResult {
pub loops: Vec<LoopInfo>,
pub max_depth: u32,
pub blocks_in_loops: usize,
pub blocks_outside_loops: usize,
}
pub fn detect_loops(blocks: &[LoopBlock]) -> Vec<LoopInfo> {
let mut level_blocks: HashMap<u32, Vec<&LoopBlock>> = HashMap::new();
for block in blocks {
let level = block.y as u32;
if level > 0 {
level_blocks.entry(level).or_default().push(block);
}
}
let mut loops: Vec<LoopInfo> = level_blocks
.into_iter()
.map(|(level, blocks_at_level)| {
let block_ids: Vec<u64> = blocks_at_level.iter().map(|b| b.id).collect();
let entry_block = blocks_at_level
.iter()
.min_by(|a, b| a.x.partial_cmp(&b.x).unwrap_or(std::cmp::Ordering::Equal))
.map(|b| b.id)
.unwrap_or(0);
let has_nested_loops = false;
LoopInfo {
level,
block_ids,
entry_block,
has_nested_loops,
}
})
.collect();
loops.sort_by_key(|l| l.level);
let max_level = loops.iter().map(|l| l.level).max().unwrap_or(0);
for loop_info in &mut loops {
loop_info.has_nested_loops = loop_info.level < max_level;
}
loops
}
pub fn analyze_loops(blocks: &[LoopBlock]) -> LoopAnalysisResult {
let loops = detect_loops(blocks);
let max_depth = loops.iter().map(|l| l.level).max().unwrap_or(0);
let blocks_in_loops: usize = loops.iter().map(|l| l.block_ids.len()).sum();
let blocks_outside_loops = blocks.len() - blocks_in_loops;
LoopAnalysisResult {
loops,
max_depth,
blocks_in_loops,
blocks_outside_loops,
}
}
pub fn find_innermost_loop_for_block(block_id: u64, loops: &[LoopInfo]) -> Option<&LoopInfo> {
loops
.iter()
.filter(|l| l.block_ids.contains(&block_id))
.max_by_key(|l| l.level)
}
#[inline]
pub fn is_in_loop(block: &LoopBlock) -> bool {
block.y > 0.0
}
#[inline]
pub fn get_loop_depth(block: &LoopBlock) -> u32 {
block.y as u32
}
pub fn get_blocks_at_level(blocks: &[LoopBlock], level: u32) -> Vec<&LoopBlock> {
blocks.iter().filter(|b| (b.y as u32) == level).collect()
}
pub fn calculate_loop_complexity(loops: &[LoopInfo]) -> u32 {
loops
.iter()
.map(|l| l.level * l.block_ids.len() as u32)
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_loops_simple() {
let blocks = vec![
LoopBlock {
id: 0,
x: 0.0,
y: 0.0,
z: 0.0,
}, LoopBlock {
id: 1,
x: 1.0,
y: 1.0,
z: 1.0,
}, LoopBlock {
id: 2,
x: 2.0,
y: 1.0,
z: 0.0,
}, LoopBlock {
id: 3,
x: 3.0,
y: 0.0,
z: 0.0,
}, ];
let loops = detect_loops(&blocks);
assert_eq!(loops.len(), 1, "Should detect 1 loop");
assert_eq!(loops[0].level, 1, "Loop should be at level 1");
assert_eq!(loops[0].block_ids.len(), 2, "Loop should have 2 blocks");
assert!(loops[0].block_ids.contains(&1), "Should contain header");
assert!(loops[0].block_ids.contains(&2), "Should contain body");
}
#[test]
fn test_detect_loops_nested() {
let blocks = vec![
LoopBlock {
id: 0,
x: 0.0,
y: 0.0,
z: 0.0,
}, LoopBlock {
id: 1,
x: 1.0,
y: 1.0,
z: 1.0,
}, LoopBlock {
id: 2,
x: 2.0,
y: 1.0,
z: 0.0,
}, LoopBlock {
id: 3,
x: 3.0,
y: 2.0,
z: 1.0,
}, LoopBlock {
id: 4,
x: 4.0,
y: 2.0,
z: 0.0,
}, LoopBlock {
id: 5,
x: 5.0,
y: 0.0,
z: 0.0,
}, ];
let loops = detect_loops(&blocks);
assert_eq!(loops.len(), 2, "Should detect 2 loops");
assert_eq!(loops[0].level, 1, "First loop at level 1");
assert_eq!(loops[1].level, 2, "Second loop at level 2");
assert!(
loops[0].has_nested_loops,
"Outer loop should have nested loops"
);
assert!(
!loops[1].has_nested_loops,
"Inner loop should not have nested loops"
);
}
#[test]
fn test_detect_loops_multiple_at_same_level() {
let blocks = vec![
LoopBlock {
id: 0,
x: 0.0,
y: 0.0,
z: 0.0,
}, LoopBlock {
id: 1,
x: 1.0,
y: 1.0,
z: 1.0,
}, LoopBlock {
id: 2,
x: 2.0,
y: 1.0,
z: 0.0,
}, LoopBlock {
id: 3,
x: 3.0,
y: 0.0,
z: 0.0,
}, LoopBlock {
id: 4,
x: 4.0,
y: 1.0,
z: 1.0,
}, LoopBlock {
id: 5,
x: 5.0,
y: 1.0,
z: 0.0,
}, ];
let loops = detect_loops(&blocks);
assert_eq!(loops.len(), 1, "Should detect 1 loop level");
assert_eq!(loops[0].block_ids.len(), 4, "All level-1 blocks grouped");
}
#[test]
fn test_detect_loops_no_loops() {
let blocks = vec![
LoopBlock {
id: 0,
x: 0.0,
y: 0.0,
z: 0.0,
},
LoopBlock {
id: 1,
x: 1.0,
y: 0.0,
z: 1.0,
},
LoopBlock {
id: 2,
x: 2.0,
y: 0.0,
z: 0.0,
},
];
let loops = detect_loops(&blocks);
assert_eq!(loops.len(), 0, "Should detect no loops");
}
#[test]
fn test_analyze_loops() {
let blocks = vec![
LoopBlock {
id: 0,
x: 0.0,
y: 0.0,
z: 0.0,
}, LoopBlock {
id: 1,
x: 1.0,
y: 1.0,
z: 1.0,
}, LoopBlock {
id: 2,
x: 2.0,
y: 1.0,
z: 0.0,
}, LoopBlock {
id: 3,
x: 3.0,
y: 2.0,
z: 0.0,
}, LoopBlock {
id: 4,
x: 4.0,
y: 0.0,
z: 0.0,
}, ];
let result = analyze_loops(&blocks);
assert_eq!(result.max_depth, 2, "Max depth should be 2");
assert_eq!(result.blocks_in_loops, 3, "3 blocks in loops");
assert_eq!(result.blocks_outside_loops, 2, "2 blocks outside loops");
assert_eq!(result.loops.len(), 2, "2 loop levels");
}
#[test]
fn test_is_in_loop() {
let in_loop = LoopBlock {
id: 0,
x: 1.0,
y: 1.0,
z: 0.0,
};
let not_in_loop = LoopBlock {
id: 1,
x: 0.0,
y: 0.0,
z: 0.0,
};
assert!(is_in_loop(&in_loop), "Block with y>0 should be in loop");
assert!(
!is_in_loop(¬_in_loop),
"Block with y=0 should not be in loop"
);
}
#[test]
fn test_get_loop_depth() {
let block = LoopBlock {
id: 0,
x: 0.0,
y: 3.0,
z: 0.0,
};
assert_eq!(get_loop_depth(&block), 3, "Loop depth should be 3");
}
#[test]
fn test_find_innermost_loop_for_block() {
let loops = vec![
LoopInfo {
level: 1,
block_ids: vec![1, 2, 3],
entry_block: 1,
has_nested_loops: true,
},
LoopInfo {
level: 2,
block_ids: vec![2, 3],
entry_block: 2,
has_nested_loops: false,
},
];
let innermost = find_innermost_loop_for_block(2, &loops);
assert!(innermost.is_some(), "Should find loop for block 2");
assert_eq!(innermost.unwrap().level, 2, "Should return innermost loop");
}
#[test]
fn test_get_blocks_at_level() {
let blocks = vec![
LoopBlock {
id: 0,
x: 0.0,
y: 0.0,
z: 0.0,
},
LoopBlock {
id: 1,
x: 1.0,
y: 1.0,
z: 0.0,
},
LoopBlock {
id: 2,
x: 2.0,
y: 1.0,
z: 0.0,
},
LoopBlock {
id: 3,
x: 3.0,
y: 2.0,
z: 0.0,
},
];
let level_1 = get_blocks_at_level(&blocks, 1);
assert_eq!(level_1.len(), 2, "Should have 2 blocks at level 1");
let level_2 = get_blocks_at_level(&blocks, 2);
assert_eq!(level_2.len(), 1, "Should have 1 block at level 2");
}
#[test]
fn test_loop_complexity() {
let loops = vec![
LoopInfo {
level: 1,
block_ids: vec![1, 2, 3],
entry_block: 1,
has_nested_loops: true,
},
LoopInfo {
level: 2,
block_ids: vec![2, 3],
entry_block: 2,
has_nested_loops: false,
},
];
let complexity = calculate_loop_complexity(&loops);
assert_eq!(complexity, 7, "Loop complexity should be 7");
}
#[test]
fn test_loop_entry_block() {
let blocks = vec![
LoopBlock {
id: 0,
x: 5.0,
y: 1.0,
z: 0.0,
},
LoopBlock {
id: 1,
x: 2.0,
y: 1.0,
z: 0.0,
}, LoopBlock {
id: 2,
x: 8.0,
y: 1.0,
z: 0.0,
},
];
let loops = detect_loops(&blocks);
assert_eq!(loops[0].entry_block, 1, "Entry should be block with min X");
}
}