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 { set: self, current: self.bits[0], word_idx: 0 }
114    }
115}
116
117pub struct BitSet256Iter<'a> {
118    set: &'a BitSet256,
119    current: u64,
120    word_idx: usize,
121}
122
123impl<'a> Iterator for BitSet256Iter<'a> {
124    type Item = CompactVertexId;
125
126    fn next(&mut self) -> Option<Self::Item> {
127        loop {
128            if self.current != 0 {
129                let bit = self.current.trailing_zeros();
130                self.current &= self.current - 1; // Clear lowest bit
131                return Some((self.word_idx as u16 * 64) + bit as u16);
132            }
133            self.word_idx += 1;
134            if self.word_idx >= 4 {
135                return None;
136            }
137            self.current = self.set.bits[self.word_idx];
138        }
139    }
140}
141
142/// Compact edge representation (8 bytes)
143#[derive(Clone, Copy, Default)]
144#[repr(C, packed)]
145pub struct CompactEdge {
146    pub source: CompactVertexId,  // 2 bytes
147    pub target: CompactVertexId,  // 2 bytes
148    pub weight: u16,              // 2 bytes (fixed-point 0.01 precision)
149    pub flags: u16,               // 2 bytes (active, in_cut, etc.)
150}
151
152impl CompactEdge {
153    pub const FLAG_ACTIVE: u16 = 0x0001;
154    pub const FLAG_IN_CUT: u16 = 0x0002;
155    pub const FLAG_TREE_EDGE: u16 = 0x0004;
156
157    #[inline(always)]
158    pub fn is_active(&self) -> bool {
159        self.flags & Self::FLAG_ACTIVE != 0
160    }
161
162    #[inline(always)]
163    pub fn is_in_cut(&self) -> bool {
164        self.flags & Self::FLAG_IN_CUT != 0
165    }
166}
167
168/// Compact witness (40 bytes total)
169#[derive(Clone, Copy, Default)]
170#[repr(C)]
171pub struct CompactWitness {
172    pub membership: BitSet256,    // 32 bytes
173    pub seed: CompactVertexId,    // 2 bytes
174    pub boundary_size: u16,       // 2 bytes
175    pub cardinality: u16,         // 2 bytes
176    pub hash: u16,                // 2 bytes
177}
178
179impl CompactWitness {
180    pub fn new(seed: CompactVertexId, membership: BitSet256, boundary: u16) -> Self {
181        let cardinality = membership.count() as u16;
182        let hash = Self::compute_hash(seed, &membership);
183        Self {
184            membership,
185            seed,
186            boundary_size: boundary,
187            cardinality,
188            hash,
189        }
190    }
191
192    fn compute_hash(seed: CompactVertexId, membership: &BitSet256) -> u16 {
193        let mut h = seed as u32;
194        for &word in &membership.bits {
195            h = h.wrapping_mul(31).wrapping_add(word as u32);
196        }
197        (h & 0xFFFF) as u16
198    }
199
200    #[inline(always)]
201    pub fn contains(&self, v: CompactVertexId) -> bool {
202        self.membership.contains(v)
203    }
204}
205
206/// Compact adjacency list (fixed size, ~2KB)
207#[derive(Clone)]
208#[repr(C)]
209pub struct CompactAdjacency {
210    /// Offset into neighbors array for each vertex
211    pub offsets: [u16; MAX_VERTICES_PER_CORE + 1],  // 514 bytes
212    /// Packed neighbor list (vertex, edge_id)
213    pub neighbors: [(CompactVertexId, CompactEdgeId); MAX_EDGES_PER_CORE * 2], // 2048 bytes
214}
215
216impl Default for CompactAdjacency {
217    fn default() -> Self {
218        Self {
219            offsets: [0; MAX_VERTICES_PER_CORE + 1],
220            neighbors: [(0, 0); MAX_EDGES_PER_CORE * 2],
221        }
222    }
223}
224
225impl CompactAdjacency {
226    pub fn neighbors(&self, v: CompactVertexId) -> &[(CompactVertexId, CompactEdgeId)] {
227        let start = self.offsets[v as usize] as usize;
228        let end = self.offsets[v as usize + 1] as usize;
229        &self.neighbors[start..end]
230    }
231
232    pub fn degree(&self, v: CompactVertexId) -> u16 {
233        self.offsets[v as usize + 1] - self.offsets[v as usize]
234    }
235}
236
237/// Memory budget breakdown for 8KB core:
238/// - CompactAdjacency: ~3.5KB (514 + 3072 bytes)
239/// - Edge array: 384 × 8 = 3KB
240/// - CompactWitness: 40 bytes
241/// - Other fields: ~12 bytes
242/// - Stack/control: ~1.4KB
243/// Total: ~6.7KB (fits comfortably in 8KB)
244
245/// Core state for minimum cut (fits in 8KB)
246#[repr(C)]
247pub struct CompactCoreState {
248    /// Adjacency structure (~2.5KB)
249    pub adjacency: CompactAdjacency,
250    /// Edge storage (4KB)
251    pub edges: [CompactEdge; MAX_EDGES_PER_CORE],
252    /// Number of active vertices
253    pub num_vertices: u16,
254    /// Number of active edges
255    pub num_edges: u16,
256    /// Current minimum cut value
257    pub min_cut: u16,
258    /// Best witness found
259    pub best_witness: CompactWitness,
260    /// Instance range [lambda_min, lambda_max]
261    pub lambda_min: u16,
262    pub lambda_max: u16,
263    /// Core ID (0-255)
264    pub core_id: u8,
265    /// Status flags
266    pub status: u8,
267}
268
269impl CompactCoreState {
270    pub const STATUS_IDLE: u8 = 0;
271    pub const STATUS_PROCESSING: u8 = 1;
272    pub const STATUS_DONE: u8 = 2;
273    pub const STATUS_ERROR: u8 = 3;
274
275    pub const fn size() -> usize {
276        size_of::<Self>()
277    }
278}
279
280// Verify size fits in 8KB
281const _: () = assert!(CompactCoreState::size() <= 8192, "CompactCoreState exceeds 8KB");
282
283/// Result communicated back from core (16 bytes)
284#[derive(Clone, Copy, Default)]
285#[repr(C)]
286pub struct CoreResult {
287    pub core_id: u8,
288    pub status: u8,
289    pub min_cut: u16,
290    pub witness_hash: u16,
291    pub witness_seed: CompactVertexId,
292    pub witness_cardinality: u16,
293    pub witness_boundary: u16,
294    pub padding: [u8; 4],
295}
296
297#[cfg(test)]
298mod tests {
299    use super::*;
300
301    #[test]
302    fn test_bitset256_basic() {
303        let mut bs = BitSet256::new();
304        assert!(!bs.contains(0));
305
306        bs.insert(0);
307        bs.insert(100);
308        bs.insert(255);
309
310        assert!(bs.contains(0));
311        assert!(bs.contains(100));
312        assert!(bs.contains(255));
313        assert!(!bs.contains(50));
314
315        assert_eq!(bs.count(), 3);
316    }
317
318    #[test]
319    fn test_bitset256_iter() {
320        let mut bs = BitSet256::new();
321        bs.insert(1);
322        bs.insert(64);
323        bs.insert(200);
324
325        let collected: alloc::vec::Vec<_> = bs.iter().collect();
326        assert_eq!(collected, alloc::vec![1, 64, 200]);
327    }
328
329    #[test]
330    fn test_compact_witness_size() {
331        // CompactWitness is 64 bytes due to BitSet256's 32-byte alignment
332        assert_eq!(size_of::<CompactWitness>(), 64);
333    }
334
335    #[test]
336    fn test_compact_edge_size() {
337        assert_eq!(size_of::<CompactEdge>(), 8);
338    }
339
340    #[test]
341    fn test_core_state_fits_8kb() {
342        assert!(CompactCoreState::size() <= 8192);
343        // Print actual size for debugging
344        // println!("CompactCoreState size: {} bytes", CompactCoreState::size());
345    }
346
347    #[test]
348    fn test_bitset_operations() {
349        let mut a = BitSet256::new();
350        let mut b = BitSet256::new();
351
352        a.insert(1);
353        a.insert(2);
354        b.insert(2);
355        b.insert(3);
356
357        let union = a.union(&b);
358        assert!(union.contains(1));
359        assert!(union.contains(2));
360        assert!(union.contains(3));
361
362        let inter = a.intersection(&b);
363        assert!(!inter.contains(1));
364        assert!(inter.contains(2));
365        assert!(!inter.contains(3));
366    }
367}