Skip to main content

oxgraph_hyper_bcsr/internal/
iter.rs

1//! Iterator types yielded by [`BcsrHypergraph`] traversal traits.
2//!
3//! [`BcsrHypergraph`]: crate::BcsrHypergraph
4
5use core::{iter::Chain, marker::PhantomData};
6
7use crate::{
8    id::{BcsrHyperedgeId, BcsrParticipantId, BcsrVertexId},
9    internal::{
10        validation::{index_to_usize_validated, usize_to_index_validated},
11        view::BcsrSections,
12    },
13    word::{LayoutIndex, LayoutWord},
14};
15
16/// Iterator over a borrowed slice of words, mapping each word's decoded
17/// index to a destination newtype via `Id::from`.
18///
19/// Used to back both [`BcsrVertexSlice`] and [`BcsrHyperedgeSlice`] (and any
20/// future `BcsrIdSlice<Word, NewtypeId>` flavor) through `pub type` aliases.
21///
22/// # Performance
23///
24/// Advancing the iterator is `O(1)` and performs no allocation.
25#[derive(Clone, Debug)]
26pub struct BcsrIdSlice<'view, Word: LayoutWord, Id> {
27    /// Remaining words for this slice.
28    inner: core::slice::Iter<'view, Word>,
29    /// Brands the iterator to the destination ID newtype without coupling
30    /// `Send` / `Sync` to it (function-return `PhantomData` is covariant).
31    _id: PhantomData<fn() -> Id>,
32}
33
34impl<'view, Word: LayoutWord, Id> BcsrIdSlice<'view, Word, Id> {
35    /// Constructs a slice iterator from a borrowed `Word` slice.
36    pub(in crate::internal) fn new(slice: &'view [Word]) -> Self {
37        Self {
38            inner: slice.iter(),
39            _id: PhantomData,
40        }
41    }
42}
43
44impl<Word: LayoutWord, Id> Iterator for BcsrIdSlice<'_, Word, Id>
45where
46    Id: From<Word::Index>,
47{
48    type Item = Id;
49
50    fn next(&mut self) -> Option<Self::Item> {
51        self.inner.next().map(|word| Id::from(word.get()))
52    }
53
54    fn size_hint(&self) -> (usize, Option<usize>) {
55        self.inner.size_hint()
56    }
57}
58
59impl<Word: LayoutWord, Id> ExactSizeIterator for BcsrIdSlice<'_, Word, Id>
60where
61    Id: From<Word::Index>,
62{
63    fn len(&self) -> usize {
64        self.inner.len()
65    }
66}
67
68impl<Word: LayoutWord, Id> DoubleEndedIterator for BcsrIdSlice<'_, Word, Id>
69where
70    Id: From<Word::Index>,
71{
72    fn next_back(&mut self) -> Option<Self::Item> {
73        self.inner.next_back().map(|word| Id::from(word.get()))
74    }
75}
76
77/// Iterator over a borrowed slice of vertex words.
78///
79/// # Performance
80///
81/// Advancing the iterator is `O(1)` and performs no allocation.
82pub type BcsrVertexSlice<'view, Word> =
83    BcsrIdSlice<'view, Word, BcsrVertexId<<Word as LayoutWord>::Index>>;
84
85/// Iterator over a borrowed slice of hyperedge words.
86///
87/// # Performance
88///
89/// Advancing the iterator is `O(1)` and performs no allocation.
90pub type BcsrHyperedgeSlice<'view, Word> =
91    BcsrIdSlice<'view, Word, BcsrHyperedgeId<<Word as LayoutWord>::Index>>;
92
93/// Chained vertex iterator yielding head participants then tail participants.
94///
95/// # Performance
96///
97/// Advancing the iterator is `O(1)` and performs no allocation.
98pub type BcsrChainedParticipants<'view, Word> =
99    Chain<BcsrVertexSlice<'view, Word>, BcsrVertexSlice<'view, Word>>;
100
101/// Chained hyperedge iterator yielding outgoing then incoming hyperedges.
102///
103/// # Performance
104///
105/// Advancing the iterator is `O(1)` and performs no allocation.
106pub type BcsrChainedHyperedges<'view, Word> =
107    Chain<BcsrHyperedgeSlice<'view, Word>, BcsrHyperedgeSlice<'view, Word>>;
108
109/// Iterator yielding incidence IDs for a contiguous incidence range.
110///
111/// # Performance
112///
113/// Advancing the iterator is `O(1)` and performs no allocation.
114#[derive(Clone, Debug)]
115pub struct BcsrParticipantSlice<IncidenceIndex> {
116    /// Next position to yield.
117    cursor: usize,
118    /// Exclusive end position.
119    end: usize,
120    /// Offset added to each position.
121    base: usize,
122    /// Logical incidence index type.
123    index: PhantomData<fn() -> IncidenceIndex>,
124}
125
126impl<IncidenceIndex> BcsrParticipantSlice<IncidenceIndex> {
127    /// Constructs a participant slice over `[start, end)` with the given `base`.
128    pub(in crate::internal) const fn new(start: usize, end: usize, base: usize) -> Self {
129        Self {
130            cursor: start,
131            end,
132            base,
133            index: PhantomData,
134        }
135    }
136}
137
138impl<IncidenceIndex: LayoutIndex> Iterator for BcsrParticipantSlice<IncidenceIndex> {
139    type Item = BcsrParticipantId<IncidenceIndex>;
140
141    fn next(&mut self) -> Option<Self::Item> {
142        if self.cursor == self.end {
143            return None;
144        }
145        let id = self.base.checked_add(self.cursor)?;
146        self.cursor += 1;
147        Some(BcsrParticipantId::new(usize_to_index_validated(id)))
148    }
149
150    fn size_hint(&self) -> (usize, Option<usize>) {
151        let len = self.end - self.cursor;
152        (len, Some(len))
153    }
154}
155
156impl<IncidenceIndex: LayoutIndex> ExactSizeIterator for BcsrParticipantSlice<IncidenceIndex> {
157    fn len(&self) -> usize {
158        self.end - self.cursor
159    }
160}
161
162impl<IncidenceIndex: LayoutIndex> DoubleEndedIterator for BcsrParticipantSlice<IncidenceIndex> {
163    fn next_back(&mut self) -> Option<Self::Item> {
164        if self.cursor == self.end {
165            return None;
166        }
167        self.end -= 1;
168        let id = self.base.checked_add(self.end)?;
169        Some(BcsrParticipantId::new(usize_to_index_validated(id)))
170    }
171}
172
173/// Chained participant-ID iterator yielding head incidences then tail
174/// incidences for a single hyperedge.
175///
176/// # Performance
177///
178/// Advancing the iterator is `O(1)` and performs no allocation.
179pub type BcsrChainedRelationIncidences<IncidenceIndex> =
180    Chain<BcsrParticipantSlice<IncidenceIndex>, BcsrParticipantSlice<IncidenceIndex>>;
181
182/// Iterator yielding incidence IDs for one vertex's incidences.
183///
184/// # Performance
185///
186/// Advancing the iterator is `O(log d_h)` and performs no allocation.
187#[derive(Clone, Debug)]
188pub struct BcsrElementIncidences<'view, OffsetWord, VertexWord, RelationWord>
189where
190    OffsetWord: LayoutWord,
191    VertexWord: LayoutWord,
192    RelationWord: LayoutWord,
193{
194    /// The vertex whose incidences we are listing.
195    vertex: usize,
196    /// `P_head`, used to offset tail incidence IDs.
197    p_head: usize,
198    /// Remaining outgoing-hyperedge words for this vertex.
199    outgoing: core::slice::Iter<'view, RelationWord>,
200    /// Remaining incoming-hyperedge words for this vertex.
201    incoming: core::slice::Iter<'view, RelationWord>,
202    /// Hyperedge-major head offsets.
203    head_offsets: &'view [OffsetWord],
204    /// Hyperedge-major head participant payload.
205    head_participants: &'view [VertexWord],
206    /// Hyperedge-major tail offsets.
207    tail_offsets: &'view [OffsetWord],
208    /// Hyperedge-major tail participant payload.
209    tail_participants: &'view [VertexWord],
210}
211
212impl<'view, OffsetWord, VertexWord, RelationWord>
213    BcsrElementIncidences<'view, OffsetWord, VertexWord, RelationWord>
214where
215    OffsetWord: LayoutWord,
216    VertexWord: LayoutWord,
217    RelationWord: LayoutWord,
218{
219    /// Constructs an element-incidences iterator for `vertex`.
220    pub(in crate::internal) fn new(
221        vertex: usize,
222        p_head: usize,
223        outgoing_slice: &'view [RelationWord],
224        incoming_slice: &'view [RelationWord],
225        sections: &BcsrSections<'view, OffsetWord, VertexWord, RelationWord>,
226    ) -> Self {
227        Self {
228            vertex,
229            p_head,
230            outgoing: outgoing_slice.iter(),
231            incoming: incoming_slice.iter(),
232            head_offsets: sections.head_offsets,
233            head_participants: sections.head_participants,
234            tail_offsets: sections.tail_offsets,
235            tail_participants: sections.tail_participants,
236        }
237    }
238
239    /// Tries to yield the next outgoing-side incidence ID.
240    fn pull_outgoing(&mut self) -> Option<BcsrParticipantId<OffsetWord::Index>> {
241        for h_word in self.outgoing.by_ref() {
242            let hyperedge = index_to_usize_validated(h_word.get());
243            if let Some(position) = locate_in_bucket(
244                self.head_offsets,
245                self.head_participants,
246                hyperedge,
247                self.vertex,
248            ) {
249                return Some(BcsrParticipantId::new(usize_to_index_validated(position)));
250            }
251        }
252        None
253    }
254
255    /// Tries to yield the next incoming-side incidence ID.
256    fn pull_incoming(&mut self) -> Option<BcsrParticipantId<OffsetWord::Index>> {
257        for h_word in self.incoming.by_ref() {
258            let hyperedge = index_to_usize_validated(h_word.get());
259            if let Some(position) = locate_in_bucket(
260                self.tail_offsets,
261                self.tail_participants,
262                hyperedge,
263                self.vertex,
264            ) {
265                let id = self.p_head.checked_add(position)?;
266                return Some(BcsrParticipantId::new(usize_to_index_validated(id)));
267            }
268        }
269        None
270    }
271}
272
273impl<OffsetWord, VertexWord, RelationWord> Iterator
274    for BcsrElementIncidences<'_, OffsetWord, VertexWord, RelationWord>
275where
276    OffsetWord: LayoutWord,
277    VertexWord: LayoutWord,
278    RelationWord: LayoutWord,
279{
280    type Item = BcsrParticipantId<OffsetWord::Index>;
281
282    fn next(&mut self) -> Option<Self::Item> {
283        if let Some(id) = self.pull_outgoing() {
284            return Some(id);
285        }
286        self.pull_incoming()
287    }
288}
289
290/// Returns the absolute position of `vertex` inside one hyperedge bucket.
291fn locate_in_bucket<OffsetWord, VertexWord>(
292    offsets: &[OffsetWord],
293    participants: &[VertexWord],
294    hyperedge: usize,
295    vertex: usize,
296) -> Option<usize>
297where
298    OffsetWord: LayoutWord,
299    VertexWord: LayoutWord,
300{
301    let bucket_start = index_to_usize_validated(offsets[hyperedge].get());
302    let bucket_end = index_to_usize_validated(offsets[hyperedge + 1].get());
303    let bucket = &participants[bucket_start..bucket_end];
304    let local = bucket
305        .binary_search_by(|word| index_to_usize_validated(word.get()).cmp(&vertex))
306        .ok()?;
307    bucket_start.checked_add(local)
308}
309
310/// Iterator over neighbor vertices reached through a sequence of hyperedges
311/// resolved against an (offsets, participants) pair.
312///
313/// Backing type for both [`BcsrSuccessorVertices`] (outgoing relations →
314/// tail buckets) and [`BcsrPredecessorVertices`] (incoming relations →
315/// head buckets). Direction is encoded by which sections the constructor
316/// receives; the struct itself is direction-neutral.
317///
318/// # Performance
319///
320/// Advancing the iterator is `O(1)` amortised and performs no allocation.
321#[derive(Clone, Debug)]
322pub struct BcsrNeighborVertices<'view, RelationWord, OffsetWord, VertexWord>
323where
324    RelationWord: LayoutWord,
325    OffsetWord: LayoutWord,
326    VertexWord: LayoutWord,
327{
328    /// Remaining hyperedges along this traversal direction.
329    relations: core::slice::Iter<'view, RelationWord>,
330    /// Remaining vertices in the current hyperedge's resolved bucket.
331    current_bucket: core::slice::Iter<'view, VertexWord>,
332    /// Hyperedge-major bucket offsets for the chosen direction (tail for
333    /// successors, head for predecessors).
334    bucket_offsets: &'view [OffsetWord],
335    /// Hyperedge-major participants for the chosen direction.
336    bucket_participants: &'view [VertexWord],
337}
338
339impl<'view, RelationWord, OffsetWord, VertexWord>
340    BcsrNeighborVertices<'view, RelationWord, OffsetWord, VertexWord>
341where
342    RelationWord: LayoutWord,
343    OffsetWord: LayoutWord,
344    VertexWord: LayoutWord,
345{
346    /// Constructs a neighbor iterator from a relation slice and the
347    /// (offsets, participants) pair for the desired direction. Use the
348    /// `tail_offsets` / `tail_participants` for successor traversal and
349    /// `head_offsets` / `head_participants` for predecessor traversal.
350    pub(in crate::internal) fn new(
351        relations: &'view [RelationWord],
352        bucket_offsets: &'view [OffsetWord],
353        bucket_participants: &'view [VertexWord],
354    ) -> Self {
355        Self {
356            relations: relations.iter(),
357            current_bucket: [].iter(),
358            bucket_offsets,
359            bucket_participants,
360        }
361    }
362
363    /// Reloads `current_bucket` from the next relation if any remain.
364    fn advance_outer(&mut self) -> bool {
365        match self.relations.next() {
366            Some(h_word) => {
367                let h_index = index_to_usize_validated(h_word.get());
368                let start = index_to_usize_validated(self.bucket_offsets[h_index].get());
369                let end = index_to_usize_validated(self.bucket_offsets[h_index + 1].get());
370                self.current_bucket = self.bucket_participants[start..end].iter();
371                true
372            }
373            None => false,
374        }
375    }
376}
377
378impl<RelationWord, OffsetWord, VertexWord> Iterator
379    for BcsrNeighborVertices<'_, RelationWord, OffsetWord, VertexWord>
380where
381    RelationWord: LayoutWord,
382    OffsetWord: LayoutWord,
383    VertexWord: LayoutWord,
384{
385    type Item = BcsrVertexId<VertexWord::Index>;
386
387    fn next(&mut self) -> Option<Self::Item> {
388        loop {
389            if let Some(word) = self.current_bucket.next() {
390                return Some(BcsrVertexId::new(word.get()));
391            }
392            if !self.advance_outer() {
393                return None;
394            }
395        }
396    }
397}
398
399/// Iterator over successor vertices of a directed-hypergraph vertex.
400///
401/// # Performance
402///
403/// Advancing the iterator is `O(1)` amortised and performs no allocation.
404pub type BcsrSuccessorVertices<'view, RelationWord, OffsetWord, VertexWord> =
405    BcsrNeighborVertices<'view, RelationWord, OffsetWord, VertexWord>;
406
407/// Iterator over predecessor vertices of a directed-hypergraph vertex.
408///
409/// # Performance
410///
411/// Advancing the iterator is `O(1)` amortised and performs no allocation.
412pub type BcsrPredecessorVertices<'view, RelationWord, OffsetWord, VertexWord> =
413    BcsrNeighborVertices<'view, RelationWord, OffsetWord, VertexWord>;