graph.rs

4.5 kB · rust · 133 lines

1use super::census::{corners, edges_of};2use super::models::Cell6d;3use super::{FILL, VOID};4use mrlycore::errors::Result;5use mrlynum::graph::models::Network;6use std::collections::BTreeMap;78type Point = (i64, i64);9type Edge = (Point, Point);1011fn centroid(c: &[Point; 3]) -> Vec<f64> {12    let xs: f64 = c.iter().map(|p| p.0 as f64).sum::<f64>() / 3.0;13    let ys: f64 = c.iter().map(|p| p.1 as f64).sum::<f64>() / 3.0;14    vec![xs, ys]15}1617fn adjacency_graph(cell: &Cell6d, keep: impl Fn(u8) -> bool) -> Result<Network> {18    let inner = &cell.cell;19    let start = cell.start as i64;20    let (height, width) = (inner.height(), inner.width());21    let mut cells = Vec::new();22    for y in 0..height {23        for x in 0..width {24            if keep(inner.types().get(&[y, x])) {25                cells.push((x as i64, y as i64));26            }27        }28    }29    let mut network = Network::new(2);30    for &(x, y) in &cells {31        network.add_node(centroid(&corners(x, y, start)))?;32    }33    let mut edge_to_cells: BTreeMap<Edge, Vec<usize>> = BTreeMap::new();34    for (i, &(x, y)) in cells.iter().enumerate() {35        for edge in edges_of(&corners(x, y, start)) {36            edge_to_cells.entry(edge).or_default().push(i);37        }38    }39    for shared in edge_to_cells.values() {40        if shared.len() == 2 {41            network.add_branch(shared[0], shared[1], 1.0)?;42        }43    }44    Ok(network)45}4647/// Builds the network of filled triangles joined by shared edges.48pub fn slice_core_graph(cell: &Cell6d) -> Result<Network> {49    adjacency_graph(cell, |v| v == FILL)50}5152/// Builds the network of fill and void triangles joined by shared edges.53pub fn slice_dual_graph(cell: &Cell6d) -> Result<Network> {54    adjacency_graph(cell, |v| v == FILL || v == VOID)55}5657/// Builds the corner-and-edge network of the triangles matching the value, or of every fill and void.58pub fn slice_edge_graph(cell: &Cell6d, value: Option<u8>) -> Result<Network> {59    let inner = &cell.cell;60    let start = cell.start as i64;61    let (height, width) = (inner.height(), inner.width());62    let keep = |v: u8| match value {63        Some(target) => v == target,64        None => v == FILL || v == VOID,65    };66    let mut corner_index: BTreeMap<Point, usize> = BTreeMap::new();67    let mut network = Network::new(2);68    let mut seen: BTreeMap<Edge, bool> = BTreeMap::new();69    for y in 0..height {70        for x in 0..width {71            if !keep(inner.types().get(&[y, x])) {72                continue;73            }74            let c = corners(x as i64, y as i64, start);75            for edge in edges_of(&c) {76                if seen.contains_key(&edge) {77                    continue;78                }79                seen.insert(edge, true);80                let mut node = |p: Point, network: &mut Network| -> Result<usize> {81                    if let Some(&i) = corner_index.get(&p) {82                        return Ok(i);83                    }84                    let i = network.add_node(vec![p.0 as f64, p.1 as f64])?;85                    corner_index.insert(p, i);86                    Ok(i)87                };88                let a = node(edge.0, &mut network)?;89                let b = node(edge.1, &mut network)?;90                network.add_branch(a, b, 1.0)?;91            }92        }93    }94    Ok(network)95}9697#[cfg(test)]98mod tests {99    use super::*;100    use crate::formulas::six as formulas;101    use crate::six::geometry::blank;102    use crate::six::{Orientation, Projection};103    fn solid(radius: usize) -> Cell6d {104        Cell6d::new(105            blank(radius, Orientation::Horizontal, FILL, crate::six::GRID),106            Projection::Cut,107            Orientation::Horizontal,108            0,109        )110    }111    #[test]112    fn solid_core_counts_are_pinned() {113        let expected = [(1, 6, 6), (2, 24, 27), (3, 54, 72)];114        for (radius, nodes, branches) in expected {115            let core = slice_core_graph(&solid(radius)).unwrap();116            assert_eq!(core.nodes.len(), nodes, "r={radius}");117            assert_eq!(core.branches.len(), branches, "r={radius}");118        }119        assert_eq!(120            slice_core_graph(&solid(3)).unwrap().branches.len() as u128,121            formulas::solid_slice_core_edges(3).unwrap()122        );123    }124    #[test]125    fn dual_contains_core() {126        let s = solid(2);127        let dual = slice_dual_graph(&s).unwrap();128        let core = slice_core_graph(&s).unwrap();129        assert!(dual.nodes.len() >= core.nodes.len());130        let edge = slice_edge_graph(&s, Some(FILL)).unwrap();131        assert!(!edge.nodes.is_empty());132    }133}