weavatrix_graph/traversal_cache/
core.rs1use super::adaptive::AdaptivePackedU32;
2use super::build::{build_neighbor_pair, neighbor_bits, select_storage};
3use super::elias_fano::EliasFano;
4use super::iter::NeighborIter;
5use super::packed::PackedU32;
6use crate::{NodeIndex, Topology, Vec};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum TraversalStorage {
11 #[default]
13 Auto,
14 Fast,
16 Balanced,
18 Compact,
20}
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub enum TraversalLayout {
25 Fast,
26 Balanced {
27 neighbor_bits: u8,
28 },
29 Compact {
30 neighbor_bits: u8,
31 offset_low_bits: u8,
32 },
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub struct TraversalCache {
37 node_count: usize,
38 edge_count: usize,
39 pub(super) outgoing: NeighborCsr,
40 pub(super) incoming: NeighborCsr,
41 layout: TraversalLayout,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub(super) struct NeighborCsr {
46 pub(super) offsets: OffsetStorage,
47 pub(super) neighbors: NeighborStorage,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub(super) enum OffsetStorage {
52 Direct(Vec<u32>),
53 EliasFano(EliasFano),
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub(super) enum NeighborStorage {
58 Direct(Vec<u32>),
59 Packed(PackedU32),
60 Adaptive(AdaptivePackedU32),
61}
62
63impl TraversalCache {
64 #[must_use]
65 pub fn from_topology(topology: &Topology) -> Self {
66 Self::with_storage(topology, TraversalStorage::Auto)
67 }
68
69 #[must_use]
70 pub fn with_storage(topology: &Topology, requested: TraversalStorage) -> Self {
71 let (endpoints, outgoing, incoming) = topology.traversal_parts();
72 let (out_neighbors, in_neighbors) =
73 build_neighbor_pair(endpoints, outgoing.edges(), incoming.edges());
74 let bits = neighbor_bits(topology.node_count());
75 let selected = select_storage(
76 requested,
77 topology.node_count(),
78 topology.edge_count(),
79 bits,
80 );
81 let (outgoing, layout) =
82 NeighborCsr::build(outgoing.offsets(), out_neighbors, selected, bits);
83 let (incoming, _) = NeighborCsr::build(incoming.offsets(), in_neighbors, selected, bits);
84 Self {
85 node_count: topology.node_count(),
86 edge_count: topology.edge_count(),
87 outgoing,
88 incoming,
89 layout,
90 }
91 }
92
93 #[must_use]
94 pub const fn node_count(&self) -> usize {
95 self.node_count
96 }
97
98 #[must_use]
99 pub const fn edge_count(&self) -> usize {
100 self.edge_count
101 }
102
103 #[must_use]
104 pub const fn layout(&self) -> TraversalLayout {
105 self.layout
106 }
107
108 #[must_use]
109 pub fn storage_bytes(&self) -> usize {
110 self.outgoing.storage_bytes() + self.incoming.storage_bytes()
111 }
112
113 #[must_use]
114 pub fn fast_equivalent_bytes(&self) -> usize {
115 (self.edge_count() * 2 + (self.node_count() + 1) * 2) * size_of::<u32>()
116 }
117
118 #[must_use]
119 pub fn outgoing_neighbors(&self, node: NodeIndex) -> NeighborIter<'_> {
120 self.outgoing.neighbors(node.index(), self.node_count())
121 }
122
123 #[must_use]
124 pub fn incoming_neighbors(&self, node: NodeIndex) -> NeighborIter<'_> {
125 self.incoming.neighbors(node.index(), self.node_count())
126 }
127
128 #[must_use]
129 pub fn out_degree(&self, node: NodeIndex) -> Option<usize> {
130 self.outgoing.degree(node.index(), self.node_count())
131 }
132
133 #[must_use]
134 pub fn in_degree(&self, node: NodeIndex) -> Option<usize> {
135 self.incoming.degree(node.index(), self.node_count())
136 }
137
138 pub(super) fn contains(&self, node: NodeIndex) -> bool {
139 node.index() < self.node_count()
140 }
141
142 pub(super) fn for_each_outgoing(&self, node: NodeIndex, visit: impl FnMut(NodeIndex)) {
143 self.outgoing
144 .for_each(node.index(), self.node_count(), visit);
145 }
146
147 pub(super) fn for_each_incoming(&self, node: NodeIndex, visit: impl FnMut(NodeIndex)) {
148 self.incoming
149 .for_each(node.index(), self.node_count(), visit);
150 }
151}
152
153impl NeighborCsr {
154 fn build(
155 offsets: &[u32],
156 neighbors: Vec<u32>,
157 storage: TraversalStorage,
158 bits: u8,
159 ) -> (Self, TraversalLayout) {
160 match storage {
161 TraversalStorage::Fast | TraversalStorage::Auto => (
162 Self {
163 offsets: OffsetStorage::Direct(offsets.to_vec()),
164 neighbors: NeighborStorage::Direct(neighbors),
165 },
166 TraversalLayout::Fast,
167 ),
168 TraversalStorage::Balanced => (
169 Self {
170 offsets: OffsetStorage::Direct(offsets.to_vec()),
171 neighbors: NeighborStorage::Packed(PackedU32::from_values(&neighbors, bits)),
172 },
173 TraversalLayout::Balanced {
174 neighbor_bits: bits,
175 },
176 ),
177 TraversalStorage::Compact => {
178 let offsets = EliasFano::from_monotone(offsets);
179 let offset_low_bits = offsets.low_bits();
180 let global_bytes =
181 neighbors.len().div_ceil(64) * usize::from(bits) * size_of::<u64>();
182 let neighbor_storage = if AdaptivePackedU32::estimated_storage_bytes(&neighbors)
183 < global_bytes
184 {
185 match AdaptivePackedU32::try_from_values(&neighbors) {
186 Some(values) => NeighborStorage::Adaptive(values),
187 None => NeighborStorage::Packed(PackedU32::from_values(&neighbors, bits)),
188 }
189 } else {
190 NeighborStorage::Packed(PackedU32::from_values(&neighbors, bits))
191 };
192 (
193 Self {
194 offsets: OffsetStorage::EliasFano(offsets),
195 neighbors: neighbor_storage,
196 },
197 TraversalLayout::Compact {
198 neighbor_bits: bits,
199 offset_low_bits,
200 },
201 )
202 }
203 }
204 }
205
206 fn neighbors(&self, node: usize, node_count: usize) -> NeighborIter<'_> {
207 let Some((start, end)) = self.bounds(node, node_count) else {
208 return NeighborIter::empty(&self.neighbors);
209 };
210 NeighborIter::new(&self.neighbors, start, end)
211 }
212
213 fn degree(&self, node: usize, node_count: usize) -> Option<usize> {
214 self.bounds(node, node_count)
215 .map(|(start, end)| end - start)
216 }
217
218 fn bounds(&self, node: usize, node_count: usize) -> Option<(usize, usize)> {
219 (node < node_count).then(|| {
220 (
221 self.offsets.get(node) as usize,
222 self.offsets.get(node + 1) as usize,
223 )
224 })
225 }
226
227 fn storage_bytes(&self) -> usize {
228 self.offsets.storage_bytes() + self.neighbors.storage_bytes()
229 }
230
231 fn for_each(&self, node: usize, node_count: usize, visit: impl FnMut(NodeIndex)) {
232 let Some((start, end)) = self.bounds(node, node_count) else {
233 return;
234 };
235 self.neighbors.for_each(start, end, visit);
236 }
237}
238
239impl OffsetStorage {
240 #[inline]
241 pub(super) fn get(&self, index: usize) -> u32 {
242 match self {
243 Self::Direct(values) => values[index],
244 Self::EliasFano(values) => values.get(index),
245 }
246 }
247
248 fn storage_bytes(&self) -> usize {
249 match self {
250 Self::Direct(values) => values.len() * size_of::<u32>(),
251 Self::EliasFano(values) => values.storage_bytes(),
252 }
253 }
254}
255
256impl NeighborStorage {
257 #[inline]
258 pub(super) fn get(&self, index: usize) -> u32 {
259 match self {
260 Self::Direct(values) => values[index],
261 Self::Packed(values) => values.get(index),
262 Self::Adaptive(values) => values.get(index),
263 }
264 }
265
266 fn storage_bytes(&self) -> usize {
267 match self {
268 Self::Direct(values) => values.len() * size_of::<u32>(),
269 Self::Packed(values) => values.storage_bytes(),
270 Self::Adaptive(values) => values.storage_bytes(),
271 }
272 }
273
274 fn for_each(&self, start: usize, end: usize, mut visit: impl FnMut(NodeIndex)) {
275 match self {
276 Self::Direct(values) => {
277 for &neighbor in &values[start..end] {
278 visit(NodeIndex::new(neighbor));
279 }
280 }
281 Self::Packed(values) => {
282 values.for_each(start, end, |raw| visit(NodeIndex::new(raw)));
283 }
284 Self::Adaptive(values) => {
285 values.for_each(start, end, |raw| visit(NodeIndex::new(raw)));
286 }
287 }
288 }
289}