1#![no_std]
28
29#[cfg(feature = "build")]
30extern crate alloc;
31
32#[cfg(kani)]
33extern crate kani;
34
35#[cfg(kani)]
36mod proofs;
37
38#[cfg(feature = "build")]
39pub mod build;
40
41use core::{fmt, hash::Hash, marker::PhantomData};
42
43use oxgraph_graph::{
44 ContainsElement, ContainsRelation, EdgeTargetGraph, ElementIndex, ElementSuccessors,
45 GraphCounts, OutgoingEdgeCount, OutgoingGraph, RelationIndex, TopologyBase, TopologyCounts,
46};
47use oxgraph_layout_util::{OffsetIntegrityIssue, check_offset_section, check_value_range};
48use oxgraph_snapshot::{SectionViewError, Snapshot};
49use zerocopy::{
50 FromBytes, Immutable, IntoBytes, KnownLayout,
51 byteorder::{LE, U16, U32, U64},
52};
53
54pub const SNAPSHOT_KIND_CSR_OFFSETS_U16: u32 = 0x0001;
56pub const SNAPSHOT_KIND_CSR_OFFSETS_U32: u32 = 0x0002;
58pub const SNAPSHOT_KIND_CSR_OFFSETS_U64: u32 = 0x0003;
60
61pub const SNAPSHOT_KIND_CSR_TARGETS_U16: u32 = 0x0004;
63pub const SNAPSHOT_KIND_CSR_TARGETS_U32: u32 = 0x0005;
65pub const SNAPSHOT_KIND_CSR_TARGETS_U64: u32 = 0x0006;
67
68mod sealed {
70 pub trait CsrIndex {}
72
73 pub trait CsrSnapshotIndex {}
75
76 pub trait CsrSnapshotWord {}
78}
79
80pub trait CsrIndex:
102 sealed::CsrIndex + Copy + Eq + Ord + fmt::Debug + fmt::Display + Hash + Sized
103{
104 const ZERO: Self;
110
111 fn to_usize(self) -> Option<usize>;
122
123 #[must_use]
129 fn from_usize(value: usize) -> Option<Self>;
130}
131
132macro_rules! impl_csr_index {
134 ($index:ty) => {
135 impl sealed::CsrIndex for $index {}
136
137 impl CsrIndex for $index {
138 const ZERO: Self = 0;
139
140 fn to_usize(self) -> Option<usize> {
141 usize::try_from(self).ok()
142 }
143
144 fn from_usize(value: usize) -> Option<Self> {
145 Self::try_from(value).ok()
146 }
147 }
148 };
149}
150
151impl_csr_index!(u16);
152impl_csr_index!(u32);
153impl_csr_index!(u64);
154
155impl sealed::CsrIndex for usize {}
156
157impl CsrIndex for usize {
158 const ZERO: Self = 0;
159
160 fn to_usize(self) -> Option<usize> {
161 Some(self)
162 }
163
164 fn from_usize(value: usize) -> Option<Self> {
165 Some(value)
166 }
167}
168
169pub trait CsrSnapshotIndex: sealed::CsrSnapshotIndex + CsrIndex {
180 type LittleEndianWord: CsrSnapshotWord<Index = Self>;
186
187 const OFFSETS_KIND: u32;
193
194 const TARGETS_KIND: u32;
200
201 fn to_le_word(self) -> Self::LittleEndianWord;
207}
208
209macro_rules! impl_csr_snapshot_index {
211 ($index:ty, $little_endian:ty, $offsets_kind:expr, $targets_kind:expr) => {
212 impl sealed::CsrSnapshotIndex for $index {}
213
214 impl CsrSnapshotIndex for $index {
215 type LittleEndianWord = $little_endian;
216
217 const OFFSETS_KIND: u32 = $offsets_kind;
218 const TARGETS_KIND: u32 = $targets_kind;
219
220 fn to_le_word(self) -> Self::LittleEndianWord {
221 <$little_endian>::new(self)
222 }
223 }
224 };
225}
226
227impl_csr_snapshot_index!(
228 u16,
229 U16<LE>,
230 SNAPSHOT_KIND_CSR_OFFSETS_U16,
231 SNAPSHOT_KIND_CSR_TARGETS_U16
232);
233impl_csr_snapshot_index!(
234 u32,
235 U32<LE>,
236 SNAPSHOT_KIND_CSR_OFFSETS_U32,
237 SNAPSHOT_KIND_CSR_TARGETS_U32
238);
239impl_csr_snapshot_index!(
240 u64,
241 U64<LE>,
242 SNAPSHOT_KIND_CSR_OFFSETS_U64,
243 SNAPSHOT_KIND_CSR_TARGETS_U64
244);
245
246pub trait CsrWord: Copy + oxgraph_layout_util::ZerocopyWord {
262 type Index: CsrIndex;
268
269 fn get(self) -> Self::Index;
275}
276
277macro_rules! impl_native_csr_word {
279 ($index:ty) => {
280 impl CsrWord for $index {
281 type Index = $index;
282
283 fn get(self) -> Self::Index {
284 self
285 }
286 }
287 };
288}
289
290impl_native_csr_word!(u16);
291impl_native_csr_word!(u32);
292impl_native_csr_word!(u64);
293impl_native_csr_word!(usize);
294
295macro_rules! impl_little_endian_csr_word {
297 ($word:ty, $index:ty) => {
298 impl CsrWord for $word {
299 type Index = $index;
300
301 fn get(self) -> Self::Index {
302 Self::get(self)
303 }
304 }
305
306 impl sealed::CsrSnapshotWord for $word {}
307
308 impl CsrSnapshotWord for $word {}
309 };
310}
311
312impl_little_endian_csr_word!(U16<LE>, u16);
313impl_little_endian_csr_word!(U32<LE>, u32);
314impl_little_endian_csr_word!(U64<LE>, u64);
315
316pub trait CsrSnapshotWord:
325 sealed::CsrSnapshotWord + CsrWord + FromBytes + Immutable + IntoBytes + KnownLayout
326{
327}
328
329pub type CsrNativeGraph<'view, NodeIndex, EdgeIndex> =
338 CsrGraph<'view, NodeIndex, EdgeIndex, EdgeIndex, NodeIndex>;
339
340pub type CsrSnapshotGraph<'view, NodeIndex, EdgeIndex> = CsrGraph<
350 'view,
351 NodeIndex,
352 EdgeIndex,
353 <EdgeIndex as CsrSnapshotIndex>::LittleEndianWord,
354 <NodeIndex as CsrSnapshotIndex>::LittleEndianWord,
355>;
356
357#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
368pub struct CsrNodeId<Index>(pub Index);
369
370impl<Index> fmt::Debug for CsrNodeId<Index>
371where
372 Index: fmt::Debug,
373{
374 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
375 formatter.debug_tuple("CsrNodeId").field(&self.0).finish()
376 }
377}
378
379#[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
390pub struct CsrEdgeId<Index>(pub Index);
391
392impl<Index> fmt::Debug for CsrEdgeId<Index>
393where
394 Index: fmt::Debug,
395{
396 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
397 formatter.debug_tuple("CsrEdgeId").field(&self.0).finish()
398 }
399}
400
401#[derive(Clone, Copy, Debug)]
403struct Unchecked;
404
405#[derive(Clone, Copy, Debug)]
407struct Checked;
408
409#[derive(Clone, Copy, Debug)]
411struct NodeSlot<State, Index> {
412 raw: Index,
414 slot: usize,
416 state: PhantomData<fn() -> State>,
418}
419
420impl<Index> NodeSlot<Unchecked, Index> {
421 fn from_id(id: CsrNodeId<Index>) -> Self {
427 Self {
428 raw: id.0,
429 slot: 0,
430 state: PhantomData,
431 }
432 }
433}
434
435impl<Index> NodeSlot<Checked, Index> {
436 fn from_raw_slot(raw: Index, slot: usize) -> Self {
442 Self {
443 raw,
444 slot,
445 state: PhantomData,
446 }
447 }
448
449 const fn index(&self) -> usize {
455 self.slot
456 }
457}
458
459#[derive(Clone, Copy, Debug)]
461struct EdgeSlot<State, Index> {
462 raw: Index,
464 slot: usize,
466 state: PhantomData<fn() -> State>,
468}
469
470impl<Index> EdgeSlot<Unchecked, Index> {
471 fn from_id(id: CsrEdgeId<Index>) -> Self {
477 Self {
478 raw: id.0,
479 slot: 0,
480 state: PhantomData,
481 }
482 }
483}
484
485impl<Index> EdgeSlot<Checked, Index> {
486 fn from_raw_slot(raw: Index, slot: usize) -> Self {
492 Self {
493 raw,
494 slot,
495 state: PhantomData,
496 }
497 }
498
499 fn from_csr_range_slot(slot: usize) -> Option<Self>
511 where
512 Index: CsrIndex,
513 {
514 let raw = Index::from_usize(slot)?;
515 Some(Self::from_raw_slot(raw, slot))
516 }
517
518 fn from_csr_range_slot_unchecked(slot: usize) -> Self
530 where
531 Index: CsrIndex,
532 {
533 Self::from_csr_range_slot(slot)
534 .unwrap_or_else(|| unreachable!("checked CSR edge slot must fit index type"))
535 }
536
537 const fn index(&self) -> usize {
543 self.slot
544 }
545
546 const fn id(&self) -> CsrEdgeId<Index>
552 where
553 Index: Copy,
554 {
555 CsrEdgeId(self.raw)
556 }
557}
558
559#[derive(Clone, Copy, Debug)]
561struct EdgeRange<State, Index> {
562 start: usize,
564 end: usize,
566 state: PhantomData<fn() -> State>,
568 index: PhantomData<fn() -> Index>,
570}
571
572impl<Index> EdgeRange<Checked, Index>
573where
574 Index: CsrIndex,
575{
576 fn from_bounds(start: usize, end: usize) -> Self {
582 Self {
583 start,
584 end,
585 state: PhantomData,
586 index: PhantomData,
587 }
588 }
589
590 const fn as_range(&self) -> core::ops::Range<usize> {
596 self.start..self.end
597 }
598
599 const fn len(&self) -> usize {
605 self.end - self.start
606 }
607
608 fn next_slot(&mut self) -> Option<EdgeSlot<Checked, Index>> {
614 if self.start == self.end {
615 return None;
616 }
617
618 let slot = EdgeSlot::from_csr_range_slot_unchecked(self.start);
619 self.start += 1;
620 Some(slot)
621 }
622}
623
624#[derive(Clone, Copy, Debug)]
639pub struct CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
640where
641 NodeIndex: CsrIndex,
642 EdgeIndex: CsrIndex,
643 OffsetWord: CsrWord<Index = EdgeIndex>,
644 TargetWord: CsrWord<Index = NodeIndex>,
645{
646 node_count: NodeIndex,
648 node_bound: usize,
650 offsets: &'view [OffsetWord],
652 targets: &'view [TargetWord],
654}
655
656impl<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
657 CsrGraph<'view, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
658where
659 NodeIndex: CsrIndex,
660 EdgeIndex: CsrIndex,
661 OffsetWord: CsrWord<Index = EdgeIndex>,
662 TargetWord: CsrWord<Index = NodeIndex>,
663{
664 pub fn validate(
677 node_count: NodeIndex,
678 offsets: &'view [OffsetWord],
679 targets: &'view [TargetWord],
680 ) -> Result<Self, CsrError<NodeIndex, EdgeIndex>> {
681 let node_bound = node_count
682 .to_usize()
683 .ok_or(CsrError::NodeUsizeOverflow { value: node_count })?;
684 if node_bound.checked_add(1).is_none() {
685 return Err(CsrError::OffsetLengthOverflow { node_count });
686 }
687
688 check_offset_section(offsets, node_bound, targets.len())
689 .map_err(|issue| map_offsets_issue::<NodeIndex, EdgeIndex, _>(offsets, issue))?;
690 check_value_range(targets, node_bound).map_err(|issue| {
691 map_targets_issue::<NodeIndex, EdgeIndex, _>(targets, node_count, issue)
692 })?;
693
694 Ok(Self {
695 node_count,
696 node_bound,
697 offsets,
698 targets,
699 })
700 }
701
702 #[must_use]
708 pub const fn offsets(&self) -> &'view [OffsetWord] {
709 self.offsets
710 }
711
712 #[must_use]
718 pub const fn targets(&self) -> &'view [TargetWord] {
719 self.targets
720 }
721
722 pub fn for_each_out_target(
730 &self,
731 source: CsrNodeId<NodeIndex>,
732 mut visit: impl FnMut(CsrNodeId<NodeIndex>) -> bool,
733 ) -> bool {
734 let Some(node) = self.try_node_slot(source) else {
735 return false;
736 };
737 let Some(index) = node.raw.to_usize() else {
738 return false;
739 };
740 let Some(start) = self.offsets[index].get().to_usize() else {
741 return false;
742 };
743 let Some(end) = self.offsets[index + 1].get().to_usize() else {
744 return false;
745 };
746 for word in &self.targets[start..end] {
747 if visit(CsrNodeId(word.get())) {
748 return true;
749 }
750 }
751 false
752 }
753
754 #[must_use]
760 pub fn contains_node(&self, node: CsrNodeId<NodeIndex>) -> bool {
761 self.try_node_slot(node).is_some()
762 }
763
764 #[must_use]
770 pub fn contains_edge(&self, edge: CsrEdgeId<EdgeIndex>) -> bool {
771 self.try_edge_slot(edge).is_some()
772 }
773
774 #[must_use]
780 pub fn try_target(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<CsrNodeId<NodeIndex>> {
781 self.try_edge_slot(edge)
782 .map(|checked| self.target_node(checked))
783 }
784
785 fn try_node_slot(&self, node: CsrNodeId<NodeIndex>) -> Option<NodeSlot<Checked, NodeIndex>> {
791 self.check_node_slot(NodeSlot::from_id(node))
792 }
793
794 fn check_node_slot(
800 &self,
801 node: NodeSlot<Unchecked, NodeIndex>,
802 ) -> Option<NodeSlot<Checked, NodeIndex>> {
803 let slot = node.raw.to_usize()?;
804 if node.raw < self.node_count && slot < self.node_bound {
805 Some(NodeSlot::from_raw_slot(node.raw, slot))
806 } else {
807 None
808 }
809 }
810
811 fn checked_node_slot(&self, node: CsrNodeId<NodeIndex>) -> NodeSlot<Checked, NodeIndex> {
822 self.try_node_slot(node)
823 .unwrap_or_else(|| panic!("CSR node ID {node:?} is invalid for this graph"))
824 }
825
826 fn try_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> Option<EdgeSlot<Checked, EdgeIndex>> {
832 self.check_edge_slot(EdgeSlot::from_id(edge))
833 }
834
835 fn check_edge_slot(
841 &self,
842 edge: EdgeSlot<Unchecked, EdgeIndex>,
843 ) -> Option<EdgeSlot<Checked, EdgeIndex>> {
844 let slot = edge.raw.to_usize()?;
845 if slot < self.targets.len() {
846 Some(EdgeSlot::from_raw_slot(edge.raw, slot))
847 } else {
848 None
849 }
850 }
851
852 fn checked_edge_slot(&self, edge: CsrEdgeId<EdgeIndex>) -> EdgeSlot<Checked, EdgeIndex> {
863 self.try_edge_slot(edge)
864 .unwrap_or_else(|| panic!("CSR edge ID {edge:?} is invalid for this graph"))
865 }
866
867 fn checked_offset_slot(offset: EdgeIndex) -> usize {
880 offset
881 .to_usize()
882 .unwrap_or_else(|| unreachable!("checked CSR offset must fit usize"))
883 }
884
885 fn target_node(&self, edge: EdgeSlot<Checked, EdgeIndex>) -> CsrNodeId<NodeIndex> {
891 CsrNodeId(self.targets[edge.index()].get())
892 }
893
894 fn outgoing_range(&self, node: NodeSlot<Checked, NodeIndex>) -> EdgeRange<Checked, EdgeIndex> {
900 let index = node.index();
901 EdgeRange::from_bounds(
902 Self::checked_offset_slot(self.offsets[index].get()),
903 Self::checked_offset_slot(self.offsets[index + 1].get()),
904 )
905 }
906}
907
908impl<'view, NodeIndex, EdgeIndex>
909 CsrGraph<
910 'view,
911 NodeIndex,
912 EdgeIndex,
913 <EdgeIndex as CsrSnapshotIndex>::LittleEndianWord,
914 <NodeIndex as CsrSnapshotIndex>::LittleEndianWord,
915 >
916where
917 NodeIndex: CsrSnapshotIndex,
918 EdgeIndex: CsrSnapshotIndex,
919{
920 pub fn from_snapshot(
940 snapshot: &Snapshot<'view>,
941 ) -> Result<Self, CsrSnapshotError<NodeIndex, EdgeIndex>> {
942 let offsets_section = snapshot
943 .section(EdgeIndex::OFFSETS_KIND)
944 .ok_or(CsrSnapshotError::MissingOffsets)?;
945 let targets_section = snapshot
946 .section(NodeIndex::TARGETS_KIND)
947 .ok_or(CsrSnapshotError::MissingTargets)?;
948
949 let offsets: &'view [<EdgeIndex as CsrSnapshotIndex>::LittleEndianWord] = offsets_section
950 .try_as_slice()
951 .map_err(CsrSnapshotError::OffsetsView)?;
952 let targets: &'view [<NodeIndex as CsrSnapshotIndex>::LittleEndianWord] = targets_section
953 .try_as_slice()
954 .map_err(CsrSnapshotError::TargetsView)?;
955
956 if offsets.is_empty() {
957 return Err(CsrSnapshotError::OffsetsEmpty);
958 }
959
960 let node_count_usize = offsets.len() - 1;
961 let node_count =
962 NodeIndex::from_usize(node_count_usize).ok_or(CsrSnapshotError::NodeCountOverflow {
963 offsets_len: offsets.len(),
964 })?;
965
966 Ok(Self::validate(node_count, offsets, targets)?)
967 }
968}
969
970impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyBase
971 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
972where
973 NodeIndex: CsrIndex,
974 EdgeIndex: CsrIndex,
975 OffsetWord: CsrWord<Index = EdgeIndex>,
976 TargetWord: CsrWord<Index = NodeIndex>,
977{
978 type ElementId = CsrNodeId<NodeIndex>;
979 type RelationId = CsrEdgeId<EdgeIndex>;
980}
981
982impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> TopologyCounts
983 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
984where
985 NodeIndex: CsrIndex,
986 EdgeIndex: CsrIndex,
987 OffsetWord: CsrWord<Index = EdgeIndex>,
988 TargetWord: CsrWord<Index = NodeIndex>,
989{
990 fn element_count(&self) -> usize {
991 self.node_bound
992 }
993
994 fn relation_count(&self) -> usize {
995 self.targets.len()
996 }
997}
998
999impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> GraphCounts
1000 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1001where
1002 NodeIndex: CsrIndex,
1003 EdgeIndex: CsrIndex,
1004 OffsetWord: CsrWord<Index = EdgeIndex>,
1005 TargetWord: CsrWord<Index = NodeIndex>,
1006{
1007}
1008
1009impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ElementIndex
1010 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1011where
1012 NodeIndex: CsrIndex,
1013 EdgeIndex: CsrIndex,
1014 OffsetWord: CsrWord<Index = EdgeIndex>,
1015 TargetWord: CsrWord<Index = NodeIndex>,
1016{
1017 fn element_bound(&self) -> usize {
1018 self.node_bound
1019 }
1020
1021 fn element_index(&self, element: CsrNodeId<NodeIndex>) -> usize {
1022 self.checked_node_slot(element).index()
1023 }
1024}
1025
1026impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> RelationIndex
1027 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1028where
1029 NodeIndex: CsrIndex,
1030 EdgeIndex: CsrIndex,
1031 OffsetWord: CsrWord<Index = EdgeIndex>,
1032 TargetWord: CsrWord<Index = NodeIndex>,
1033{
1034 fn relation_bound(&self) -> usize {
1035 self.targets.len()
1036 }
1037
1038 fn relation_index(&self, relation: CsrEdgeId<EdgeIndex>) -> usize {
1039 self.checked_edge_slot(relation).index()
1040 }
1041}
1042
1043impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsElement
1044 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1045where
1046 NodeIndex: CsrIndex,
1047 EdgeIndex: CsrIndex,
1048 OffsetWord: CsrWord<Index = EdgeIndex>,
1049 TargetWord: CsrWord<Index = NodeIndex>,
1050{
1051 fn contains_element(&self, element: CsrNodeId<NodeIndex>) -> bool {
1052 self.contains_node(element)
1053 }
1054}
1055
1056impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ContainsRelation
1057 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1058where
1059 NodeIndex: CsrIndex,
1060 EdgeIndex: CsrIndex,
1061 OffsetWord: CsrWord<Index = EdgeIndex>,
1062 TargetWord: CsrWord<Index = NodeIndex>,
1063{
1064 fn contains_relation(&self, relation: CsrEdgeId<EdgeIndex>) -> bool {
1065 self.contains_edge(relation)
1066 }
1067}
1068
1069impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> EdgeTargetGraph
1070 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1071where
1072 NodeIndex: CsrIndex,
1073 EdgeIndex: CsrIndex,
1074 OffsetWord: CsrWord<Index = EdgeIndex>,
1075 TargetWord: CsrWord<Index = NodeIndex>,
1076{
1077 fn target(&self, edge: CsrEdgeId<EdgeIndex>) -> CsrNodeId<NodeIndex> {
1078 self.target_node(self.checked_edge_slot(edge))
1079 }
1080}
1081
1082impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingGraph
1083 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1084where
1085 NodeIndex: CsrIndex,
1086 EdgeIndex: CsrIndex,
1087 OffsetWord: CsrWord<Index = EdgeIndex>,
1088 TargetWord: CsrWord<Index = NodeIndex>,
1089{
1090 type OutEdges<'view>
1091 = CsrOutEdges<EdgeIndex>
1092 where
1093 Self: 'view;
1094
1095 fn outgoing_edges(&self, node: CsrNodeId<NodeIndex>) -> Self::OutEdges<'_> {
1096 CsrOutEdges {
1097 range: self.outgoing_range(self.checked_node_slot(node)),
1098 }
1099 }
1100}
1101
1102impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> OutgoingEdgeCount
1103 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1104where
1105 NodeIndex: CsrIndex,
1106 EdgeIndex: CsrIndex,
1107 OffsetWord: CsrWord<Index = EdgeIndex>,
1108 TargetWord: CsrWord<Index = NodeIndex>,
1109{
1110 fn out_degree(&self, node: CsrNodeId<NodeIndex>) -> usize {
1111 self.outgoing_range(self.checked_node_slot(node)).len()
1112 }
1113}
1114
1115impl<NodeIndex, EdgeIndex, OffsetWord, TargetWord> ElementSuccessors
1116 for CsrGraph<'_, NodeIndex, EdgeIndex, OffsetWord, TargetWord>
1117where
1118 NodeIndex: CsrIndex,
1119 EdgeIndex: CsrIndex,
1120 OffsetWord: CsrWord<Index = EdgeIndex>,
1121 TargetWord: CsrWord<Index = NodeIndex>,
1122{
1123 type Successors<'view>
1124 = CsrOutNeighbors<'view, TargetWord>
1125 where
1126 Self: 'view;
1127
1128 fn element_successors(&self, node: CsrNodeId<NodeIndex>) -> Self::Successors<'_> {
1129 let range = self.outgoing_range(self.checked_node_slot(node));
1130 CsrOutNeighbors {
1131 targets: self.targets[range.as_range()].iter(),
1132 }
1133 }
1134}
1135
1136#[derive(Clone, Debug)]
1142pub struct CsrOutEdges<Index> {
1143 range: EdgeRange<Checked, Index>,
1145}
1146
1147impl<Index> Iterator for CsrOutEdges<Index>
1148where
1149 Index: CsrIndex,
1150{
1151 type Item = CsrEdgeId<Index>;
1152
1153 fn next(&mut self) -> Option<Self::Item> {
1154 self.range.next_slot().map(|slot| slot.id())
1155 }
1156}
1157
1158impl<Index> ExactSizeIterator for CsrOutEdges<Index>
1159where
1160 Index: CsrIndex,
1161{
1162 fn len(&self) -> usize {
1163 self.range.len()
1164 }
1165}
1166
1167#[derive(Clone, Debug)]
1177pub struct CsrOutNeighbors<'view, StorageWord> {
1178 targets: core::slice::Iter<'view, StorageWord>,
1180}
1181
1182impl<StorageWord> Iterator for CsrOutNeighbors<'_, StorageWord>
1183where
1184 StorageWord: CsrWord,
1185{
1186 type Item = CsrNodeId<StorageWord::Index>;
1187
1188 fn next(&mut self) -> Option<Self::Item> {
1189 self.targets.next().map(|word| CsrNodeId(word.get()))
1190 }
1191}
1192
1193impl<StorageWord> ExactSizeIterator for CsrOutNeighbors<'_, StorageWord>
1194where
1195 StorageWord: CsrWord,
1196{
1197 fn len(&self) -> usize {
1198 self.targets.len()
1199 }
1200}
1201
1202fn map_offsets_issue<NodeIndex, EdgeIndex, OffsetWord>(
1209 offsets: &[OffsetWord],
1210 issue: OffsetIntegrityIssue,
1211) -> CsrError<NodeIndex, EdgeIndex>
1212where
1213 NodeIndex: CsrIndex,
1214 EdgeIndex: CsrIndex,
1215 OffsetWord: CsrWord<Index = EdgeIndex>,
1216{
1217 match issue {
1218 OffsetIntegrityIssue::Length { expected, actual } => {
1219 CsrError::OffsetLength { expected, actual }
1220 }
1221 OffsetIntegrityIssue::FirstNonZero { .. } => CsrError::FirstOffset {
1222 actual: offsets[0].get(),
1223 },
1224 OffsetIntegrityIssue::NonMonotonic { index, .. } => CsrError::NonMonotonicOffset {
1225 index,
1226 previous: offsets[index - 1].get(),
1227 actual: offsets[index].get(),
1228 },
1229 OffsetIntegrityIssue::FinalMismatch { value_len, .. } => CsrError::FinalOffset {
1230 final_offset: offsets[offsets.len() - 1].get(),
1231 target_len: value_len,
1232 },
1233 OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::EdgeUsizeOverflow {
1234 value: offsets[index].get(),
1235 },
1236 OffsetIntegrityIssue::ValueOutOfRange { .. } => {
1237 CsrError::EdgeUsizeOverflow {
1239 value: EdgeIndex::ZERO,
1240 }
1241 }
1242 _ => CsrError::EdgeUsizeOverflow {
1243 value: EdgeIndex::ZERO,
1244 },
1245 }
1246}
1247
1248fn map_targets_issue<NodeIndex, EdgeIndex, TargetWord>(
1252 targets: &[TargetWord],
1253 node_count: NodeIndex,
1254 issue: OffsetIntegrityIssue,
1255) -> CsrError<NodeIndex, EdgeIndex>
1256where
1257 NodeIndex: CsrIndex,
1258 EdgeIndex: CsrIndex,
1259 TargetWord: CsrWord<Index = NodeIndex>,
1260{
1261 match issue {
1262 OffsetIntegrityIssue::ValueOutOfRange { index, .. }
1263 | OffsetIntegrityIssue::UsizeOverflow { index } => CsrError::TargetOutOfRange {
1264 index,
1265 target: targets[index].get(),
1266 node_count,
1267 },
1268 _ => CsrError::TargetOutOfRange {
1269 index: 0,
1270 target: NodeIndex::ZERO,
1271 node_count,
1272 },
1273 }
1274}
1275
1276#[derive(Clone, Debug, Eq, PartialEq)]
1282pub enum CsrError<NodeIndex, EdgeIndex> {
1283 OffsetLengthOverflow {
1285 node_count: NodeIndex,
1287 },
1288 OffsetLength {
1290 expected: usize,
1292 actual: usize,
1294 },
1295 FirstOffset {
1297 actual: EdgeIndex,
1299 },
1300 NonMonotonicOffset {
1302 index: usize,
1304 previous: EdgeIndex,
1306 actual: EdgeIndex,
1308 },
1309 FinalOffset {
1311 final_offset: EdgeIndex,
1313 target_len: usize,
1315 },
1316 TargetOutOfRange {
1318 index: usize,
1320 target: NodeIndex,
1322 node_count: NodeIndex,
1324 },
1325 NodeUsizeOverflow {
1327 value: NodeIndex,
1329 },
1330 EdgeUsizeOverflow {
1332 value: EdgeIndex,
1334 },
1335}
1336
1337impl<NodeIndex, EdgeIndex> fmt::Display for CsrError<NodeIndex, EdgeIndex>
1338where
1339 NodeIndex: fmt::Display,
1340 EdgeIndex: fmt::Display,
1341{
1342 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1343 match self {
1344 Self::OffsetLengthOverflow { node_count } => {
1345 write!(
1346 formatter,
1347 "offset length overflow for node count {node_count}"
1348 )
1349 }
1350 Self::OffsetLength { expected, actual } => write!(
1351 formatter,
1352 "invalid CSR offset length: expected {expected}, got {actual}"
1353 ),
1354 Self::FirstOffset { actual } => {
1355 write!(formatter, "first CSR offset must be 0, got {actual}")
1356 }
1357 Self::NonMonotonicOffset {
1358 index,
1359 previous,
1360 actual,
1361 } => write!(
1362 formatter,
1363 "CSR offset at index {index} is not monotonic: previous {previous}, got {actual}"
1364 ),
1365 Self::FinalOffset {
1366 final_offset,
1367 target_len,
1368 } => write!(
1369 formatter,
1370 "final CSR offset {final_offset} does not match target length {target_len}"
1371 ),
1372 Self::TargetOutOfRange {
1373 index,
1374 target,
1375 node_count,
1376 } => write!(
1377 formatter,
1378 "CSR target at index {index} is out of range: target {target}, node count {node_count}"
1379 ),
1380 Self::NodeUsizeOverflow { value } => {
1381 write!(formatter, "CSR node index value {value} does not fit usize")
1382 }
1383 Self::EdgeUsizeOverflow { value } => {
1384 write!(formatter, "CSR edge index value {value} does not fit usize")
1385 }
1386 }
1387 }
1388}
1389
1390impl<NodeIndex, EdgeIndex> core::error::Error for CsrError<NodeIndex, EdgeIndex>
1391where
1392 NodeIndex: fmt::Debug + fmt::Display,
1393 EdgeIndex: fmt::Debug + fmt::Display,
1394{
1395}
1396
1397#[derive(Clone, Debug, Eq, PartialEq)]
1403pub enum CsrSnapshotError<NodeIndex, EdgeIndex> {
1404 MissingOffsets,
1406 MissingTargets,
1408 OffsetsView(SectionViewError),
1411 TargetsView(SectionViewError),
1414 OffsetsEmpty,
1417 NodeCountOverflow {
1419 offsets_len: usize,
1421 },
1422 Csr(CsrError<NodeIndex, EdgeIndex>),
1424}
1425
1426impl<NodeIndex, EdgeIndex> fmt::Display for CsrSnapshotError<NodeIndex, EdgeIndex>
1427where
1428 NodeIndex: fmt::Display,
1429 EdgeIndex: fmt::Display,
1430{
1431 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1432 match self {
1433 Self::MissingOffsets => formatter.write_str("snapshot has no CSR offsets section"),
1434 Self::MissingTargets => formatter.write_str("snapshot has no CSR targets section"),
1435 Self::OffsetsView(error) => write!(
1436 formatter,
1437 "CSR offsets section cannot be borrowed as selected little-endian index words: {error}"
1438 ),
1439 Self::TargetsView(error) => write!(
1440 formatter,
1441 "CSR targets section cannot be borrowed as selected little-endian index words: {error}"
1442 ),
1443 Self::OffsetsEmpty => formatter.write_str("CSR offsets section is empty"),
1444 Self::NodeCountOverflow { offsets_len } => write!(
1445 formatter,
1446 "derived node count from offsets length {offsets_len} does not fit selected CSR index type"
1447 ),
1448 Self::Csr(error) => write!(formatter, "CSR validation failed: {error}"),
1449 }
1450 }
1451}
1452
1453impl<NodeIndex, EdgeIndex> core::error::Error for CsrSnapshotError<NodeIndex, EdgeIndex>
1454where
1455 NodeIndex: fmt::Debug + fmt::Display,
1456 EdgeIndex: fmt::Debug + fmt::Display,
1457{
1458}
1459
1460impl<NodeIndex, EdgeIndex> From<CsrError<NodeIndex, EdgeIndex>>
1461 for CsrSnapshotError<NodeIndex, EdgeIndex>
1462{
1463 fn from(error: CsrError<NodeIndex, EdgeIndex>) -> Self {
1464 Self::Csr(error)
1465 }
1466}