Skip to main content

weavatrix_graph/matrix/bit/
mod.rs

1use crate::Vec;
2use crate::{EdgeEndpoints, GraphError, NodeIndex, Result, Topology};
3use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
4
5#[cfg(feature = "unsafe-fast")]
6mod unsafe_fast;
7
8#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
9pub struct BitMatrix {
10    node_count: u32,
11    edge_count: usize,
12    words: Vec<u64>,
13}
14
15impl BitMatrix {
16    /// Creates an empty directed bit-packed adjacency matrix.
17    ///
18    /// # Errors
19    ///
20    /// Returns an error when its dimensions exceed index or address capacity.
21    pub fn try_new(node_count: usize) -> Result<Self> {
22        let compact = u32::try_from(node_count).map_err(|_| GraphError::IndexCapacityExceeded {
23            category: "bit matrix nodes",
24            count: node_count,
25        })?;
26        let cells = node_count
27            .checked_mul(node_count)
28            .ok_or(GraphError::ArithmeticOverflow {
29                operation: "bit matrix dimensions",
30            })?;
31        let word_count = cells
32            .checked_add(63)
33            .ok_or(GraphError::ArithmeticOverflow {
34                operation: "bit matrix word count",
35            })?
36            / 64;
37        Ok(Self {
38            node_count: compact,
39            edge_count: 0,
40            words: vec![0; word_count],
41        })
42    }
43
44    #[must_use]
45    pub const fn node_count(&self) -> usize {
46        self.node_count as usize
47    }
48
49    #[must_use]
50    pub const fn edge_count(&self) -> usize {
51        self.edge_count
52    }
53
54    #[must_use]
55    pub fn storage_bytes(&self) -> usize {
56        self.words.len() * size_of::<u64>()
57    }
58
59    #[must_use]
60    #[inline]
61    pub fn contains(&self, source: NodeIndex, target: NodeIndex) -> bool {
62        let count = self.node_count();
63        let source = source.index();
64        let target = target.index();
65        if source >= count || target >= count {
66            return false;
67        }
68        let slot = source * count + target;
69        self.words[slot / 64] & (1_u64 << (slot % 64)) != 0
70    }
71
72    /// Inserts one directed edge and reports whether it was new.
73    ///
74    /// # Errors
75    ///
76    /// Returns an error when either endpoint is outside the matrix.
77    pub fn insert(&mut self, source: NodeIndex, target: NodeIndex) -> Result<bool> {
78        let slot = self.slot(source, target)?;
79        let mask = 1_u64 << (slot % 64);
80        let word = &mut self.words[slot / 64];
81        let inserted = *word & mask == 0;
82        *word |= mask;
83        self.edge_count += usize::from(inserted);
84        Ok(inserted)
85    }
86
87    /// Inserts both directions of one undirected relation.
88    ///
89    /// # Errors
90    ///
91    /// Returns an error when either endpoint is outside the matrix.
92    pub fn insert_undirected(&mut self, left: NodeIndex, right: NodeIndex) -> Result<usize> {
93        let first = usize::from(self.insert(left, right)?);
94        let second = usize::from(left != right && self.insert(right, left)?);
95        Ok(first + second)
96    }
97
98    pub fn remove(&mut self, source: NodeIndex, target: NodeIndex) -> bool {
99        let Ok(slot) = self.slot(source, target) else {
100            return false;
101        };
102        let mask = 1_u64 << (slot % 64);
103        let word = &mut self.words[slot / 64];
104        let removed = *word & mask != 0;
105        *word &= !mask;
106        self.edge_count -= usize::from(removed);
107        removed
108    }
109
110    pub fn outgoing(&self, source: NodeIndex) -> impl Iterator<Item = NodeIndex> + '_ {
111        let valid = source.index() < self.node_count();
112        (0..self.node_count()).filter_map(move |target| {
113            let target = NodeIndex::new(u32::try_from(target).ok()?);
114            (valid && self.contains(source, target)).then_some(target)
115        })
116    }
117
118    pub fn incoming(&self, target: NodeIndex) -> impl Iterator<Item = NodeIndex> + '_ {
119        let valid = target.index() < self.node_count();
120        (0..self.node_count()).filter_map(move |source| {
121            let source = NodeIndex::new(u32::try_from(source).ok()?);
122            (valid && self.contains(source, target)).then_some(source)
123        })
124    }
125
126    pub fn edges(&self) -> impl Iterator<Item = EdgeEndpoints> + '_ {
127        self.words
128            .iter()
129            .enumerate()
130            .flat_map(|(word, bits)| SetBits::new(word * 64, *bits))
131            .filter_map(|slot| {
132                let source = u32::try_from(slot / self.node_count()).ok()?;
133                let target = u32::try_from(slot % self.node_count()).ok()?;
134                Some(EdgeEndpoints::new(
135                    NodeIndex::new(source),
136                    NodeIndex::new(target),
137                ))
138            })
139    }
140
141    /// Materializes a compact dual-CSR topology.
142    ///
143    /// # Errors
144    ///
145    /// Returns an error only if the matrix violates topology capacity.
146    pub fn to_topology(&self) -> Result<Topology> {
147        Topology::try_from_edges(self.node_count(), self.edges())
148    }
149
150    fn slot(&self, source: NodeIndex, target: NodeIndex) -> Result<usize> {
151        let count = self.node_count();
152        for node in [source, target] {
153            if node.index() >= count {
154                return Err(GraphError::InvalidNodeIndex {
155                    node: node.index(),
156                    node_count: count,
157                });
158            }
159        }
160        Ok(source.index() * count + target.index())
161    }
162}
163
164struct SetBits {
165    base: usize,
166    bits: u64,
167}
168
169impl SetBits {
170    const fn new(base: usize, bits: u64) -> Self {
171        Self { base, bits }
172    }
173}
174
175impl Iterator for SetBits {
176    type Item = usize;
177
178    fn next(&mut self) -> Option<Self::Item> {
179        if self.bits == 0 {
180            return None;
181        }
182        let offset = usize::try_from(self.bits.trailing_zeros()).unwrap_or(usize::MAX);
183        self.bits &= self.bits - 1;
184        Some(self.base + offset)
185    }
186}
187
188#[derive(Deserialize)]
189struct BitWire {
190    node_count: u32,
191    #[serde(default, rename = "edge_count")]
192    _edge_count: usize,
193    words: Vec<u64>,
194}
195
196impl<'de> Deserialize<'de> for BitMatrix {
197    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
198    where
199        D: Deserializer<'de>,
200    {
201        let wire = BitWire::deserialize(deserializer)?;
202        let mut matrix = Self::try_new(wire.node_count as usize).map_err(D::Error::custom)?;
203        if matrix.words.len() != wire.words.len() {
204            return Err(D::Error::custom("invalid bit matrix word count"));
205        }
206        matrix.words = wire.words;
207        let cells = matrix.node_count() * matrix.node_count();
208        if let Some(last) = matrix.words.last() {
209            let used = cells % 64;
210            if used != 0 && *last & !((1_u64 << used) - 1) != 0 {
211                return Err(D::Error::custom("bit matrix contains out-of-range bits"));
212            }
213        }
214        matrix.edge_count = matrix
215            .words
216            .iter()
217            .map(|word| usize::try_from(word.count_ones()).unwrap_or(usize::MAX))
218            .sum();
219        Ok(matrix)
220    }
221}