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}