oxgraph_hyper_bcsr/internal/view.rs
1//! Borrowed bipartite-CSR hypergraph view and its caller-facing borrowed
2//! section parameter struct.
3
4use core::fmt;
5
6use crate::{
7 error::BcsrError,
8 internal::validation::{BcsrValidation, DerivedCounts, validate_sections},
9 word::{BcsrWords, LayoutWord},
10};
11
12/// Borrowed input slices for a bipartite-CSR hypergraph view.
13///
14/// Offset slices use `OffsetWord` and decode to the incidence index width.
15/// Participant value slices use `VertexWord` and decode to the vertex index
16/// width. Vertex-major relation slices use `RelationWord` and decode to the
17/// relation index width.
18///
19/// # Performance
20///
21/// `perf: unspecified`; this is a borrowed parameter struct.
22#[derive(Clone, Copy, Debug)]
23pub struct BcsrSections<'view, OffsetWord, VertexWord, RelationWord>
24where
25 OffsetWord: LayoutWord,
26 VertexWord: LayoutWord,
27 RelationWord: LayoutWord,
28{
29 /// Hyperedge-major head offsets, length `hyperedge_count + 1`.
30 pub head_offsets: &'view [OffsetWord],
31 /// Flat vertex IDs in head sets, length `P_head`.
32 pub head_participants: &'view [VertexWord],
33 /// Hyperedge-major tail offsets, length `hyperedge_count + 1`.
34 pub tail_offsets: &'view [OffsetWord],
35 /// Flat vertex IDs in tail sets, length `P_tail`.
36 pub tail_participants: &'view [VertexWord],
37 /// Vertex-major outgoing offsets, length `vertex_count + 1`.
38 pub vertex_outgoing_offsets: &'view [OffsetWord],
39 /// Flat hyperedge IDs where the vertex is in head, length `P_outgoing`.
40 pub vertex_outgoing_hyperedges: &'view [RelationWord],
41 /// Vertex-major incoming offsets, length `vertex_count + 1`.
42 pub vertex_incoming_offsets: &'view [OffsetWord],
43 /// Flat hyperedge IDs where the vertex is in tail, length `P_incoming`.
44 pub vertex_incoming_hyperedges: &'view [RelationWord],
45}
46
47#[cfg(kani)]
48impl<'view, OffsetWord, VertexWord, RelationWord>
49 BcsrSections<'view, OffsetWord, VertexWord, RelationWord>
50where
51 OffsetWord: LayoutWord,
52 VertexWord: LayoutWord,
53 RelationWord: LayoutWord,
54{
55 /// Builds a [`BcsrSections`] from eight ordered borrowed slices.
56 ///
57 /// Exists only to deduplicate the kani proof harnesses: each `#[kani::proof]`
58 /// otherwise repeats the eight-field struct literal verbatim. Argument
59 /// order matches the field declaration order above (head, tail,
60 /// vertex-outgoing, vertex-incoming; offsets paired with their value
61 /// slice).
62 ///
63 /// # Performance
64 ///
65 /// `O(1)` field assignment.
66 #[expect(
67 clippy::too_many_arguments,
68 reason = "eight slice arguments mirror the eight BcsrSections fields in the documented declaration order"
69 )]
70 pub(crate) fn for_kani(
71 head_offsets: &'view [OffsetWord],
72 head_participants: &'view [VertexWord],
73 tail_offsets: &'view [OffsetWord],
74 tail_participants: &'view [VertexWord],
75 vertex_outgoing_offsets: &'view [OffsetWord],
76 vertex_outgoing_hyperedges: &'view [RelationWord],
77 vertex_incoming_offsets: &'view [OffsetWord],
78 vertex_incoming_hyperedges: &'view [RelationWord],
79 ) -> Self {
80 Self {
81 head_offsets,
82 head_participants,
83 tail_offsets,
84 tail_participants,
85 vertex_outgoing_offsets,
86 vertex_outgoing_hyperedges,
87 vertex_incoming_offsets,
88 vertex_incoming_hyperedges,
89 }
90 }
91}
92
93/// Borrowed bipartite compressed-sparse-row hypergraph view.
94///
95/// `BcsrHypergraph` borrows the eight section payloads supplied through
96/// [`BcsrSections`] without copying or allocating. The single [`BcsrWords`]
97/// parameter selects the word family — [`NativeWords`](crate::NativeWords)
98/// for host-order build-path views, [`LeWords`](crate::LeWords) for
99/// little-endian snapshot-backed views. Construction validates the borrowed
100/// slices according to the chosen [`BcsrValidation`] level. Once constructed,
101/// every traversal is `O(degree)` in either direction.
102///
103/// # Performance
104///
105/// Construction is `O(P_head + P_tail + P_outgoing + P_incoming)` at
106/// [`BcsrValidation::Layout`]. [`BcsrValidation::Strict`] adds an
107/// `O((P_head + P_tail) · log d)` cross-direction walk where `d` is the
108/// maximum vertex outgoing or incoming degree.
109pub struct BcsrHypergraph<'view, W: BcsrWords> {
110 /// Validated counts cached for `O(1)` access.
111 counts: DerivedCounts,
112 /// The eight borrowed sections backing this view.
113 sections: BcsrSections<'view, W::OffsetWord, W::VertexWord, W::RelationWord>,
114}
115
116// Manual impls instead of derives: the word family `W` is a type-level
117// carrier that never appears as a value, so deriving would demand spurious
118// `W: Clone` / `W: Copy` / `W: Debug` bounds.
119impl<W: BcsrWords> Clone for BcsrHypergraph<'_, W> {
120 fn clone(&self) -> Self {
121 *self
122 }
123}
124
125impl<W: BcsrWords> Copy for BcsrHypergraph<'_, W> {}
126
127impl<W: BcsrWords> fmt::Debug for BcsrHypergraph<'_, W>
128where
129 W::OffsetWord: fmt::Debug,
130 W::VertexWord: fmt::Debug,
131 W::RelationWord: fmt::Debug,
132{
133 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
134 f.debug_struct("BcsrHypergraph")
135 .field("counts", &self.counts)
136 .field("sections", &self.sections)
137 .finish()
138 }
139}
140
141impl<'view, W: BcsrWords> BcsrHypergraph<'view, W> {
142 /// Validates `sections` at [`BcsrValidation::Layout`] and returns a view.
143 ///
144 /// # Errors
145 ///
146 /// Returns [`BcsrError`] when any layout invariant fails. See
147 /// [`BcsrValidation::Layout`] for the full list.
148 ///
149 /// # Performance
150 ///
151 /// `O(P_head + P_tail + P_outgoing + P_incoming)`.
152 pub fn open(
153 sections: BcsrSections<'view, W::OffsetWord, W::VertexWord, W::RelationWord>,
154 ) -> Result<Self, BcsrError> {
155 Self::open_with(sections, BcsrValidation::Layout)
156 }
157
158 /// Validates `sections` at the requested level and returns a view.
159 ///
160 /// # Errors
161 ///
162 /// Returns [`BcsrError`] when any invariant visible at `level` fails.
163 ///
164 /// # Performance
165 ///
166 /// `O(P_head + P_tail + P_outgoing + P_incoming)` at
167 /// [`BcsrValidation::Layout`]; adds `O((P_head + P_tail) · log d)` at
168 /// [`BcsrValidation::Strict`].
169 pub fn open_with(
170 sections: BcsrSections<'view, W::OffsetWord, W::VertexWord, W::RelationWord>,
171 level: BcsrValidation,
172 ) -> Result<Self, BcsrError> {
173 let counts = validate_sections(§ions, level)?;
174 Ok(Self { counts, sections })
175 }
176
177 /// Builds a view over already-validated sections, skipping re-validation.
178 ///
179 /// The hypergraph builders validate their frozen output exactly once at
180 /// [`BcsrValidation::Strict`] inside `freeze`, so re-running the
181 /// validation walk for every frozen-wrapper `as_view` borrow would be
182 /// redundant; this constructor is crate-internal so every external input
183 /// still validates through [`Self::open`] / [`Self::open_with`].
184 ///
185 /// Counts are derived structurally from the slice lengths. Sections that
186 /// passed [`validate_sections`] have non-empty offset slices and a total
187 /// incidence count that fits `usize`, so the saturating arithmetic below
188 /// is exact for every valid input.
189 ///
190 /// # Performance
191 ///
192 /// This function is `O(1)`.
193 pub(crate) const fn from_validated_sections(
194 sections: BcsrSections<'view, W::OffsetWord, W::VertexWord, W::RelationWord>,
195 ) -> Self {
196 let p_outgoing = sections.vertex_outgoing_hyperedges.len();
197 let p_incoming = sections.vertex_incoming_hyperedges.len();
198 let counts = DerivedCounts {
199 vertex_count: sections.vertex_outgoing_offsets.len().saturating_sub(1),
200 hyperedge_count: sections.head_offsets.len().saturating_sub(1),
201 p_outgoing,
202 p_incoming,
203 total_incidences: p_outgoing.saturating_add(p_incoming),
204 };
205 Self { counts, sections }
206 }
207
208 /// Returns the number of vertices in this view.
209 ///
210 /// # Performance
211 ///
212 /// This method is `O(1)`.
213 #[must_use]
214 pub const fn vertex_count(&self) -> usize {
215 self.counts.vertex_count
216 }
217
218 /// Returns the number of hyperedges in this view.
219 ///
220 /// # Performance
221 ///
222 /// This method is `O(1)`.
223 #[must_use]
224 pub const fn hyperedge_count(&self) -> usize {
225 self.counts.hyperedge_count
226 }
227
228 /// Returns the number of outgoing incidences (`P_head == P_outgoing`).
229 ///
230 /// # Performance
231 ///
232 /// This method is `O(1)`.
233 #[must_use]
234 pub const fn outgoing_incidence_count(&self) -> usize {
235 self.counts.p_outgoing
236 }
237
238 /// Returns the number of incoming incidences (`P_tail == P_incoming`).
239 ///
240 /// # Performance
241 ///
242 /// This method is `O(1)`.
243 #[must_use]
244 pub const fn incoming_incidence_count(&self) -> usize {
245 self.counts.p_incoming
246 }
247
248 /// Returns the validated count cache.
249 pub(in crate::internal) const fn counts(&self) -> DerivedCounts {
250 self.counts
251 }
252
253 /// Returns the borrowed sections.
254 pub(in crate::internal) const fn sections(
255 &self,
256 ) -> &BcsrSections<'view, W::OffsetWord, W::VertexWord, W::RelationWord> {
257 &self.sections
258 }
259}