ruvector_mincut/compact/
mod.rs

1//! Compact data structures for 8KB WASM cores
2//!
3//! Optimized for agentic chip with 256 cores × 8KB each.
4//! Total budget: ~8KB per core for complete min-cut state.
5
6// Internal optimization module - docs on public API in lib.rs
7#![allow(missing_docs)]
8#![cfg_attr(not(test), no_std)]
9extern crate alloc;
10
11use core::mem::size_of;
12
13/// Maximum vertices per core (fits in 8KB budget)
14pub const MAX_VERTICES_PER_CORE: usize = 256;
15
16/// Maximum edges per core (reduced to fit in 8KB total)
17/// Budget breakdown:
18/// - Adjacency offsets: 257 * 2 = 514 bytes
19/// - Adjacency neighbors: 384 * 2 * 4 = 3072 bytes
20/// - Edges: 384 * 8 = 3072 bytes
21/// - Other fields: ~50 bytes
22/// Total: ~6.7KB (fits comfortably in 8KB)
23pub const MAX_EDGES_PER_CORE: usize = 384;
24
25/// Compact vertex ID (u16 saves 6 bytes vs u64)
26pub type CompactVertexId = u16;
27
28/// Compact edge ID (u16)
29pub type CompactEdgeId = u16;
30
31/// Bit-packed membership set (256 vertices = 32 bytes)
32/// Much smaller than RoaringBitmap for small vertex counts
33#[derive(Clone, Copy, Default)]
34#[repr(C, align(32))]
35pub struct BitSet256 {
36    /// Raw bits storage - public for SIMD access
37    pub bits: [u64; 4], // 256 bits = 32 bytes
38}
39
40impl BitSet256 {
41    pub const fn new() -> Self {
42        Self { bits: [0; 4] }
43    }
44
45    #[inline(always)]
46    pub fn insert(&mut self, v: CompactVertexId) {
47        let idx = (v / 64) as usize;
48        let bit = v % 64;
49        if idx < 4 {
50            self.bits[idx] |= 1u64 << bit;
51        }
52    }
53
54    #[inline(always)]
55    pub fn contains(&self, v: CompactVertexId) -> bool {
56        let idx = (v / 64) as usize;
57        let bit = v % 64;
58        idx < 4 && (self.bits[idx] & (1u64 << bit)) != 0
59    }
60
61    #[inline(always)]
62    pub fn remove(&mut self, v: CompactVertexId) {
63        let idx = (v / 64) as usize;
64        let bit = v % 64;
65        if idx < 4 {
66            self.bits[idx] &= !(1u64 << bit);
67        }
68    }
69
70    #[inline(always)]
71    pub fn count(&self) -> u32 {
72        self.bits.iter().map(|b| b.count_ones()).sum()
73    }
74
75    #[inline(always)]
76    pub fn union(&self, other: &Self) -> Self {
77        Self {
78            bits: [
79                self.bits[0] | other.bits[0],
80                self.bits[1] | other.bits[1],
81                self.bits[2] | other.bits[2],
82                self.bits[3] | other.bits[3],
83            ],
84        }
85    }
86
87    #[inline(always)]
88    pub fn intersection(&self, other: &Self) -> Self {
89        Self {
90            bits: [
91                self.bits[0] & other.bits[0],
92                self.bits[1] & other.bits[1],
93                self.bits[2] & other.bits[2],
94                self.bits[3] & other.bits[3],
95            ],
96        }
97    }
98
99    #[inline(always)]
100    pub fn xor(&self, other: &Self) -> Self {
101        Self {
102            bits: [
103                self.bits[0] ^ other.bits[0],
104                self.bits[1] ^ other.bits[1],
105                self.bits[2] ^ other.bits[2],
106                self.bits[3] ^ other.bits[3],
107            ],
108        }
109    }
110
111    pub fn iter(&self) -> BitSet256Iter {
112        // Initialize with the first word's value
113        BitSet256Iter {
114            set: self,
115            current: self.bits[0],
116            word_idx: 0,
117        }
118    }
119}
120
121pub struct BitSet256Iter<'a> {
122    set: &'a BitSet256,
123    current: u64,
124    word_idx: usize,
125}
126
127impl<'a> Iterator for BitSet256Iter<'a> {
128    type Item = CompactVertexId;
129
130    fn next(&mut self) -> Option<Self::Item> {
131        loop {
132            if self.current != 0 {
133                let bit = self.current.trailing_zeros();
134                self.current &= self.current - 1; // Clear lowest bit
135                return Some((self.word_idx as u16 * 64) + bit as u16);
136            }
137            self.word_idx += 1;
138            if self.word_idx >= 4 {
139                return None;
140            }
141            self.current = self.set.bits[self.word_idx];
142        }
143    }
144}
145
146/// Compact edge representation (8 bytes)
147#[derive(Clone, Copy, Default)]
148#[repr(C, packed)]
149pub struct CompactEdge {
150    pub source: CompactVertexId, // 2 bytes
151    pub target: CompactVertexId, // 2 bytes
152    pub weight: u16,             // 2 bytes (fixed-point 0.01 precision)
153    pub flags: u16,              // 2 bytes (active, in_cut, etc.)
154}
155
156impl CompactEdge {
157    pub const FLAG_ACTIVE: u16 = 0x0001;
158    pub const FLAG_IN_CUT: u16 = 0x0002;
159    pub const FLAG_TREE_EDGE: u16 = 0x0004;
160
161    #[inline(always)]
162    pub fn is_active(&self) -> bool {
163        self.flags & Self::FLAG_ACTIVE != 0
164    }
165
166    #[inline(always)]
167    pub fn is_in_cut(&self) -> bool {
168        self.flags & Self::FLAG_IN_CUT != 0
169    }
170}
171
172/// Compact witness (40 bytes total)
173#[derive(Clone, Copy, Default)]
174#[repr(C)]
175pub struct CompactWitness {
176    pub membership: BitSet256, // 32 bytes
177    pub seed: CompactVertexId, // 2 bytes
178    pub boundary_size: u16,    // 2 bytes
179    pub cardinality: u16,      // 2 bytes
180    pub hash: u16,             // 2 bytes
181}
182
183impl CompactWitness {
184    pub fn new(seed: CompactVertexId, membership: BitSet256, boundary: u16) -> Self {
185        let cardinality = membership.count() as u16;
186        let hash = Self::compute_hash(seed, &membership);
187        Self {
188            membership,
189            seed,
190            boundary_size: boundary,
191            cardinality,
192            hash,
193        }
194    }
195
196    fn compute_hash(seed: CompactVertexId, membership: &BitSet256) -> u16 {
197        let mut h = seed as u32;
198        for &word in &membership.bits {
199            h = h.wrapping_mul(31).wrapping_add(word as u32);
200        }
201        (h & 0xFFFF) as u16
202    }
203
204    #[inline(always)]
205    pub fn contains(&self, v: CompactVertexId) -> bool {
206        self.membership.contains(v)
207    }
208}
209
210/// Compact adjacency list (fixed size, ~2KB)
211#[derive(Clone)]
212#[repr(C)]
213pub struct CompactAdjacency {
214    /// Offset into neighbors array for each vertex
215    pub offsets: [u16; MAX_VERTICES_PER_CORE + 1], // 514 bytes
216    /// Packed neighbor list (vertex, edge_id)
217    pub neighbors: [(CompactVertexId, CompactEdgeId); MAX_EDGES_PER_CORE * 2], // 2048 bytes
218}
219
220impl Default for CompactAdjacency {
221    fn default() -> Self {
222        Self {
223            offsets: [0; MAX_VERTICES_PER_CORE + 1],
224            neighbors: [(0, 0); MAX_EDGES_PER_CORE * 2],
225        }
226    }
227}
228
229impl CompactAdjacency {
230    pub fn neighbors(&self, v: CompactVertexId) -> &[(CompactVertexId, CompactEdgeId)] {
231        let start = self.offsets[v as usize] as usize;
232        let end = self.offsets[v as usize + 1] as usize;
233        &self.neighbors[start..end]
234    }
235
236    pub fn degree(&self, v: CompactVertexId) -> u16 {
237        self.offsets[v as usize + 1] - self.offsets[v as usize]
238    }
239}
240
241/// Memory budget breakdown for 8KB core:
242/// - CompactAdjacency: ~3.5KB (514 + 3072 bytes)
243/// - Edge array: 384 × 8 = 3KB
244/// - CompactWitness: 40 bytes
245/// - Other fields: ~12 bytes
246/// - Stack/control: ~1.4KB
247/// Total: ~6.7KB (fits comfortably in 8KB)
248
249/// Core state for minimum cut (fits in 8KB)
250#[repr(C)]
251pub struct CompactCoreState {
252    /// Adjacency structure (~2.5KB)
253    pub adjacency: CompactAdjacency,
254    /// Edge storage (4KB)
255    pub edges: [CompactEdge; MAX_EDGES_PER_CORE],
256    /// Number of active vertices
257    pub num_vertices: u16,
258    /// Number of active edges
259    pub num_edges: u16,
260    /// Current minimum cut value
261    pub min_cut: u16,
262    /// Best witness found
263    pub best_witness: CompactWitness,
264    /// Instance range [lambda_min, lambda_max]
265    pub lambda_min: u16,
266    pub lambda_max: u16,
267    /// Core ID (0-255)
268    pub core_id: u8,
269    /// Status flags
270    pub status: u8,
271}
272
273impl CompactCoreState {
274    pub const STATUS_IDLE: u8 = 0;
275    pub const STATUS_PROCESSING: u8 = 1;
276    pub const STATUS_DONE: u8 = 2;
277    pub const STATUS_ERROR: u8 = 3;
278
279    pub const fn size() -> usize {
280        size_of::<Self>()
281    }
282}
283
284// Verify size fits in 8KB
285const _: () = assert!(
286    CompactCoreState::size() <= 8192,
287    "CompactCoreState exceeds 8KB"
288);
289
290/// Result communicated back from core (16 bytes)
291#[derive(Clone, Copy, Default)]
292#[repr(C)]
293pub struct CoreResult {
294    pub core_id: u8,
295    pub status: u8,
296    pub min_cut: u16,
297    pub witness_hash: u16,
298    pub witness_seed: CompactVertexId,
299    pub witness_cardinality: u16,
300    pub witness_boundary: u16,
301    pub padding: [u8; 4],
302}
303
304#[cfg(test)]
305mod tests {
306    use super::*;
307
308    #[test]
309    fn test_bitset256_basic() {
310        let mut bs = BitSet256::new();
311        assert!(!bs.contains(0));
312
313        bs.insert(0);
314        bs.insert(100);
315        bs.insert(255);
316
317        assert!(bs.contains(0));
318        assert!(bs.contains(100));
319        assert!(bs.contains(255));
320        assert!(!bs.contains(50));
321
322        assert_eq!(bs.count(), 3);
323    }
324
325    #[test]
326    fn test_bitset256_iter() {
327        let mut bs = BitSet256::new();
328        bs.insert(1);
329        bs.insert(64);
330        bs.insert(200);
331
332        let collected: alloc::vec::Vec<_> = bs.iter().collect();
333        assert_eq!(collected, alloc::vec![1, 64, 200]);
334    }
335
336    #[test]
337    fn test_compact_witness_size() {
338        // CompactWitness is 64 bytes due to BitSet256's 32-byte alignment
339        assert_eq!(size_of::<CompactWitness>(), 64);
340    }
341
342    #[test]
343    fn test_compact_edge_size() {
344        assert_eq!(size_of::<CompactEdge>(), 8);
345    }
346
347    #[test]
348    fn test_core_state_fits_8kb() {
349        assert!(CompactCoreState::size() <= 8192);
350        // Print actual size for debugging
351        // println!("CompactCoreState size: {} bytes", CompactCoreState::size());
352    }
353
354    #[test]
355    fn test_bitset_operations() {
356        let mut a = BitSet256::new();
357        let mut b = BitSet256::new();
358
359        a.insert(1);
360        a.insert(2);
361        b.insert(2);
362        b.insert(3);
363
364        let union = a.union(&b);
365        assert!(union.contains(1));
366        assert!(union.contains(2));
367        assert!(union.contains(3));
368
369        let inter = a.intersection(&b);
370        assert!(!inter.contains(1));
371        assert!(inter.contains(2));
372        assert!(!inter.contains(3));
373    }
374}