Skip to main content

murk_space/
region.rs

1//! Region specification and compiled region plans.
2
3use murk_core::Coord;
4
5/// Specifies a region of cells within a Space.
6///
7/// Used for observation gathering, propagator spatial queries,
8/// and region-scoped operations.
9#[derive(Clone, Debug, PartialEq)]
10pub enum RegionSpec {
11    /// Every cell in the space.
12    All,
13    /// Topology-aware disk: all cells within `radius` graph-distance of `center`.
14    Disk {
15        /// Center coordinate.
16        center: Coord,
17        /// Maximum graph distance from center (inclusive).
18        radius: u32,
19    },
20    /// Axis-aligned bounding box in coordinate space.
21    Rect {
22        /// Minimum corner (inclusive).
23        min: Coord,
24        /// Maximum corner (inclusive).
25        max: Coord,
26    },
27    /// BFS expansion from center to given depth.
28    Neighbours {
29        /// Center coordinate.
30        center: Coord,
31        /// BFS depth.
32        depth: u32,
33    },
34    /// Explicit list of coordinates.
35    Coords(Vec<Coord>),
36}
37
38/// Compiled region plan — precomputed for O(1) lookups during tick execution.
39///
40/// Created by [`Space::compile_region`](crate::Space::compile_region).
41/// Fields are `pub(crate)` — use accessor methods from outside the crate.
42#[derive(Clone, Debug)]
43pub struct RegionPlan {
44    /// Precomputed coordinates in canonical iteration order.
45    pub(crate) coords: Vec<Coord>,
46    /// Mapping: `coords[i]` -> flat tensor index for observation output.
47    pub(crate) tensor_indices: Vec<usize>,
48    /// Validity mask: `1` = valid cell, `0` = padding.
49    /// Length = `bounding_shape.total_elements()`.
50    pub(crate) valid_mask: Vec<u8>,
51    /// Shape of the bounding tensor that contains this region.
52    pub(crate) bounding_shape: BoundingShape,
53}
54
55impl RegionPlan {
56    /// Number of valid cells in the region (derived from `coords.len()`).
57    pub fn cell_count(&self) -> usize {
58        self.coords.len()
59    }
60
61    /// Precomputed coordinates in canonical iteration order.
62    pub fn coords(&self) -> &[Coord] {
63        &self.coords
64    }
65
66    /// Mapping: `coords[i]` -> flat tensor index for observation output.
67    pub fn tensor_indices(&self) -> &[usize] {
68        &self.tensor_indices
69    }
70
71    /// Validity mask: `1` = valid cell, `0` = padding.
72    pub fn valid_mask(&self) -> &[u8] {
73        &self.valid_mask
74    }
75
76    /// Take ownership of the valid mask, replacing it with an empty vec.
77    pub fn take_valid_mask(&mut self) -> Vec<u8> {
78        std::mem::take(&mut self.valid_mask)
79    }
80
81    /// Shape of the bounding tensor that contains this region.
82    pub fn bounding_shape(&self) -> &BoundingShape {
83        &self.bounding_shape
84    }
85
86    /// Fraction of tensor elements that are valid (non-padding).
87    pub fn valid_ratio(&self) -> f64 {
88        let total = self.bounding_shape.total_elements();
89        if total == 0 {
90            return 0.0;
91        }
92        self.valid_mask.iter().filter(|&&v| v == 1).count() as f64 / total as f64
93    }
94}
95
96/// Shape of the bounding tensor for a compiled region.
97#[derive(Clone, Debug, PartialEq)]
98pub enum BoundingShape {
99    /// N-dimensional rectangular bounding box.
100    Rect(Vec<usize>),
101}
102
103impl BoundingShape {
104    /// Total number of elements in the bounding tensor.
105    pub fn total_elements(&self) -> usize {
106        match self {
107            Self::Rect(dims) => dims.iter().product(),
108        }
109    }
110}