diff --git a/crates/oa-core/examples/auto_layout_probe/condensed.rs b/crates/oa-core/examples/auto_layout_probe/condensed.rs index 205c95c2..e6a5fb89 100644 --- a/crates/oa-core/examples/auto_layout_probe/condensed.rs +++ b/crates/oa-core/examples/auto_layout_probe/condensed.rs @@ -54,23 +54,49 @@ fn components(arcs: &[BTreeSet], work: &mut usize) -> Result Vec<(usize, usize)> { + let mut slots = Vec::new(); + if rows == 1 { + slots.extend((0..cols).map(|col| (col, 0))); + } else if cols == 1 { + slots.extend((0..rows).map(|row| (0, row))); + } else { + slots.extend((0..cols).map(|col| (col, 0))); + slots.extend((1..rows).map(|row| (cols - 1, row))); + slots.extend((0..cols - 1).rev().map(|col| (col, rows - 1))); + slots.extend((1..rows - 1).rev().map(|row| (0, row))); + } + slots.truncate(n); + slots +} + +// Score occupied maximum-member cells, excluding unused perimeter slots. +// Heterogeneous members may occupy less than this nominal cell envelope. fn perimeter( n: usize, cell: Size, gap: Size, work: &mut usize, ) -> Result<(Vec, usize, usize), ModelError> { - let mut best: Option<((bool, f32, f32, usize, usize), usize, usize)> = None; - for rows in 2..=n.max(2) { - for cols in 2..=n.max(2) { + type GridScore = (bool, f32, f32, usize, usize); + let mut best: Option<(GridScore, usize, usize)> = None; + for rows in 1..=n { + for cols in 1..=n { charge(work, 1)?; - if 2 * rows + 2 * cols - 4 < n { + let capacity = if rows == 1 || cols == 1 { + rows * cols + } else { + 2 * rows + 2 * cols - 4 + }; + if capacity < n { continue; } - let width = cols as f32 * cell.width + (cols - 1) as f32 * gap.width; - let height = rows as f32 * cell.height + (rows - 1) as f32 * gap.height; + charge(work, capacity)?; + let slots = perimeter_slots(n, rows, cols); + let max_x = slots.iter().map(|p| p.0).max().unwrap(); + let max_y = slots.iter().map(|p| p.1).max().unwrap(); + let width = (max_x + 1) as f32 * cell.width + max_x as f32 * gap.width; + let height = (max_y + 1) as f32 * cell.height + max_y as f32 * gap.height; let aspect = (width / height).max(height / width); let key = ( aspect > 2.0, @@ -89,19 +115,7 @@ fn perimeter( } } let (_, rows, cols) = best.ok_or_else(|| error("no perimeter grid"))?; - let mut slots = Vec::new(); - for col in 0..cols { - slots.push((col, 0)); - } - for row in 1..rows { - slots.push((cols - 1, row)); - } - for col in (0..cols - 1).rev() { - slots.push((col, rows - 1)); - } - for row in (1..rows - 1).rev() { - slots.push((0, row)); - } + let slots = perimeter_slots(n, rows, cols); Ok(( slots .into_iter() @@ -548,4 +562,27 @@ mod tests { } } } + + #[test] + fn two_node_perimeter_scores_occupied_cells_instead_of_an_unused_row() { + let (positions, rows, cols) = perimeter( + 2, + Size { + width: 224.0, + height: 160.0, + }, + Size { + width: 250.0, + height: 56.0, + }, + &mut 0, + ) + .unwrap(); + assert_eq!((rows, cols), (2, 1)); + assert_eq!( + positions, + vec![Position { x: 0.0, y: 0.0 }, Position { x: 0.0, y: 216.0 }] + ); + assert!(224.0_f32 / 376.0 > 0.5); + } }