,
+ _path: PhantomData,
+ _format: PhantomData,
+}
+
+pub struct FromPath
+where
+ P: AsRef,
+ NI: Idx,
+ Format: InputCapabilities,
+ Format::GraphInput: TryFrom>,
+{
+ csr_layout: CsrLayout,
+ path: P,
+ _idx: PhantomData,
+ _format: PhantomData,
+}
+
+/// A builder to create graphs in a type-safe way.
+///
+/// The builder implementation uses different states to allow staged building of
+/// graphs. Each individual state enables stage-specific methods on the builder.
+///
+/// # Examples
+///
+/// Create a directed graph from a vec of edges:
+///
+/// ```
+/// use graph_builder::prelude::*;
+///
+/// let graph: DirectedCsrGraph = GraphBuilder::new()
+/// .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)])
+/// .build();
+///
+/// assert_eq!(graph.node_count(), 4);
+/// ```
+///
+/// Create an undirected graph from a vec of edges:
+///
+/// ```
+/// use graph_builder::prelude::*;
+///
+/// let graph: UndirectedCsrGraph = GraphBuilder::new()
+/// .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)])
+/// .build();
+///
+/// assert_eq!(graph.node_count(), 4);
+/// ```
+pub struct GraphBuilder {
+ state: State,
+}
+
+impl Default for GraphBuilder {
+ fn default() -> Self {
+ GraphBuilder::new()
+ }
+}
+
+impl GraphBuilder {
+ /// Creates a new builder
+ pub fn new() -> Self {
+ Self {
+ state: Uninitialized {
+ csr_layout: CsrLayout::default(),
+ },
+ }
+ }
+
+ /// Sets the [`CsrLayout`] to use during CSR construction.
+ ///
+ /// # Examples
+ ///
+ /// Store the neighbors sorted:
+ ///
+ /// ```
+ /// use graph_builder::prelude::*;
+ ///
+ /// let graph: UndirectedCsrGraph = GraphBuilder::new()
+ /// .csr_layout(CsrLayout::Sorted)
+ /// .edges(vec![(0, 7), (0, 3), (0, 3), (0, 1)])
+ /// .build();
+ ///
+ /// assert_eq!(graph.neighbors(0).copied().collect::>(), &[1, 3, 3, 7]);
+ /// ```
+ ///
+ /// Store the neighbors sorted and deduplicated:
+ ///
+ /// ```
+ /// use graph_builder::prelude::*;
+ ///
+ /// let graph: UndirectedCsrGraph = GraphBuilder::new()
+ /// .csr_layout(CsrLayout::Deduplicated)
+ /// .edges(vec![(0, 7), (0, 3), (0, 3), (0, 1)])
+ /// .build();
+ ///
+ /// assert_eq!(graph.neighbors(0).copied().collect::>(), &[1, 3, 7]);
+ /// ```
+ #[must_use]
+ pub fn csr_layout(mut self, csr_layout: CsrLayout) -> Self {
+ self.state.csr_layout = csr_layout;
+ self
+ }
+
+ /// Create a graph from the given edge tuples.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use graph_builder::prelude::*;
+ ///
+ /// let graph: DirectedCsrGraph = GraphBuilder::new()
+ /// .edges(vec![(0, 1), (0, 2), (1, 2), (1, 3), (2, 3)])
+ /// .build();
+ ///
+ /// assert_eq!(graph.node_count(), 4);
+ /// assert_eq!(graph.edge_count(), 5);
+ /// ```
+ pub fn edges(self, edges: Edges) -> GraphBuilder>
+ where
+ NI: Idx,
+ Edges: IntoIterator- ,
+ {
+ GraphBuilder {
+ state: FromEdges {
+ csr_layout: self.state.csr_layout,
+ edges,
+ _node: PhantomData,
+ },
+ }
+ }
+
+ /// Create a graph from the given edge triplets.
+ ///
+ /// # Example
+ ///
+ /// ```
+ /// use graph_builder::prelude::*;
+ ///
+ /// let graph: DirectedCsrGraph = GraphBuilder::new()
+ /// .edges_with_values(vec![(0, 1, 0.1), (0, 2, 0.2), (1, 2, 0.3), (1, 3, 0.4), (2, 3, 0.5)])
+ /// .build();
+ ///
+ /// assert_eq!(graph.node_count(), 4);
+ /// assert_eq!(graph.edge_count(), 5);
+ /// ```
+ pub fn edges_with_values(
+ self,
+ edges: Edges,
+ ) -> GraphBuilder>
+ where
+ NI: Idx,
+ Edges: IntoIterator
- ,
+ {
+ GraphBuilder {
+ state: FromEdgesWithValues {
+ csr_layout: self.state.csr_layout,
+ edges,
+ _node: PhantomData,
+ },
+ }
+ }
+}
+
+impl GraphBuilder>
+where
+ NI: Idx,
+ Edges: IntoIterator
- ,
+{
+ pub fn node_values(
+ self,
+ node_values: I,
+ ) -> GraphBuilder>
+ where
+ I: IntoIterator
- ,
+ {
+ let edge_list = EdgeList::from(EdgeIterator(self.state.edges));
+ let node_values = node_values.into_iter().collect::>();
+
+ GraphBuilder {
+ state: FromEdgeListAndNodeValues {
+ csr_layout: self.state.csr_layout,
+ node_values,
+ edge_list,
+ },
+ }
+ }
+
+ /// Build the graph from the given vec of edges.
+ pub fn build(self) -> Graph
+ where
+ Graph: From<(EdgeList, CsrLayout)>,
+ {
+ Graph::from((
+ EdgeList::from(EdgeIterator(self.state.edges)),
+ self.state.csr_layout,
+ ))
+ }
+}
+
+impl GraphBuilder>
+where
+ NI: Idx,
+ EV: Sync,
+ Edges: IntoIterator
- ,
+{
+ pub fn node_values(
+ self,
+ node_values: I,
+ ) -> GraphBuilder>
+ where
+ I: IntoIterator
- ,
+ {
+ let edge_list = EdgeList::from(EdgeWithValueIterator(self.state.edges));
+ let node_values = node_values.into_iter().collect::>();
+
+ GraphBuilder {
+ state: FromEdgeListAndNodeValues {
+ csr_layout: self.state.csr_layout,
+ node_values,
+ edge_list,
+ },
+ }
+ }
+
+ /// Build the graph from the given vec of edges.
+ pub fn build(self) -> Graph
+ where
+ Graph: From<(EdgeList, CsrLayout)>,
+ {
+ Graph::from((
+ EdgeList::new(self.state.edges.into_iter().collect()),
+ self.state.csr_layout,
+ ))
+ }
+}
+
+impl GraphBuilder> {
+ pub fn build(self) -> Graph
+ where
+ Graph: From<(NodeValues, EdgeList, CsrLayout)>,
+ {
+ Graph::from((
+ self.state.node_values,
+ self.state.edge_list,
+ self.state.csr_layout,
+ ))
+ }
+}
+
+impl GraphBuilder>
+where
+ Path: AsRef,
+ NI: Idx,
+ Format: InputCapabilities,
+ Format::GraphInput: TryFrom>,
+{
+ /// Set the location where the graph is stored.
+ pub fn path(self, path: Path) -> GraphBuilder> {
+ GraphBuilder {
+ state: FromPath {
+ csr_layout: self.state.csr_layout,
+ path,
+ _idx: PhantomData,
+ _format: PhantomData,
+ },
+ }
+ }
+}
+
+impl GraphBuilder>
+where
+ Path: AsRef,
+ NI: Idx,
+ Format: InputCapabilities,
+ Format::GraphInput: TryFrom>,
+ super::Error: From<>>::Error>,
+{
+ /// Build the graph from the given input format and path.
+ pub fn build(self) -> Result
+ where
+ Graph: TryFrom<(Format::GraphInput, CsrLayout)>,
+ super::Error: From,
+ {
+ let input = Format::GraphInput::try_from(InputPath(self.state.path))?;
+ let graph = Graph::try_from((input, self.state.csr_layout))?;
+
+ Ok(graph)
+ }
+}
\ No newline at end of file
diff --git a/packages/graph-rust/src/graph_builder/compat.rs b/packages/graph-rust/src/graph_builder/compat.rs
new file mode 100644
index 0000000..5dcdf25
--- /dev/null
+++ b/packages/graph-rust/src/graph_builder/compat.rs
@@ -0,0 +1,109 @@
+use std::mem::MaybeUninit;
+
+pub(crate) trait MaybeUninitWriteSliceExt {
+ fn write_slice_compat<'a>(this: &'a mut [MaybeUninit], src: &[T]) -> &'a mut [T]
+ where
+ T: Copy;
+}
+
+#[cfg(not(has_maybe_uninit_write_slice))]
+impl MaybeUninitWriteSliceExt for MaybeUninit {
+ fn write_slice_compat<'a>(this: &'a mut [MaybeUninit], src: &[T]) -> &'a mut [T]
+ where
+ T: Copy,
+ {
+ // SAFETY: &[T] and &[MaybeUninit] have the same layout
+ let uninit_src: &[MaybeUninit] = unsafe { std::mem::transmute(src) };
+
+ this.copy_from_slice(uninit_src);
+
+ // SAFETY: Valid elements have just been copied into `this` so it is initialized
+ // SAFETY: similar to safety notes for `slice_get_ref`, but we have a
+ // mutable reference which is also guaranteed to be valid for writes.
+ unsafe { &mut *(this as *mut [MaybeUninit] as *mut [T]) }
+ }
+}
+
+#[cfg(has_maybe_uninit_write_slice)]
+impl MaybeUninitWriteSliceExt for MaybeUninit {
+ fn write_slice_compat<'a>(this: &'a mut [MaybeUninit], src: &[T]) -> &'a mut [T]
+ where
+ T: Copy,
+ {
+ MaybeUninit::write_slice(this, src)
+ }
+}
+
+pub(crate) trait NewUninitExt {
+ fn new_uninit_slice_compat(len: usize) -> Box<[MaybeUninit]>;
+
+ unsafe fn assume_init_compat(self) -> Box<[T]>;
+}
+
+#[cfg(not(has_new_uninit))]
+impl NewUninitExt for Box<[MaybeUninit]> {
+ fn new_uninit_slice_compat(len: usize) -> Box<[MaybeUninit]> {
+ use std::mem::ManuallyDrop;
+ use std::slice::from_raw_parts_mut;
+
+ let vec = Vec::::with_capacity(len);
+ let mut vec = ManuallyDrop::new(vec);
+
+ unsafe {
+ let slice = from_raw_parts_mut(vec.as_mut_ptr() as *mut MaybeUninit, len);
+ Box::from_raw(slice)
+ }
+ }
+
+ unsafe fn assume_init_compat(self) -> Box<[T]> {
+ unsafe { Box::from_raw(Box::into_raw(self) as *mut [T]) }
+ }
+}
+
+#[cfg(has_new_uninit)]
+impl NewUninitExt for Box<[MaybeUninit]> {
+ fn new_uninit_slice_compat(len: usize) -> Box<[MaybeUninit]> {
+ Box::<[T]>::new_uninit_slice(len)
+ }
+
+ unsafe fn assume_init_compat(self) -> Box<[T]> {
+ unsafe { self.assume_init() }
+ }
+}
+
+pub(crate) trait SlicePartitionDedupExt {
+ fn partition_dedup_compat(&mut self) -> (&mut [T], &mut [T]);
+}
+
+#[cfg(not(has_slice_partition_dedup))]
+impl SlicePartitionDedupExt for [T] {
+ fn partition_dedup_compat(&mut self) -> (&mut [T], &mut [T]) {
+ let len = self.len();
+ if len <= 1 {
+ return (self, &mut []);
+ }
+
+ let ptr = self.as_mut_ptr();
+ let mut next_read: usize = 1;
+ let mut next_write: usize = 1;
+
+ unsafe {
+ while next_read < len {
+ let ptr_read = ptr.add(next_read);
+ let prev_ptr_write = ptr.add(next_write - 1);
+ // Changed from if `!same_bucket(&mut *ptr_read, &mut *prev_ptr_write)`
+ // as the original implementation is copied from `partition_dedup_by`.
+ if *ptr_read != *prev_ptr_write {
+ if next_read != next_write {
+ let ptr_write = prev_ptr_write.offset(1);
+ core::ptr::swap(ptr_read, ptr_write);
+ }
+ next_write += 1;
+ }
+ next_read += 1;
+ }
+ }
+
+ self.split_at_mut(next_write)
+ }
+}
diff --git a/packages/graph-rust/src/graph_builder/graph/csr.rs b/packages/graph-rust/src/graph_builder/graph/csr.rs
new file mode 100644
index 0000000..ee194cd
--- /dev/null
+++ b/packages/graph-rust/src/graph_builder/graph/csr.rs
@@ -0,0 +1,873 @@
+use atomic::Atomic;
+use byte_slice_cast::{AsByteSlice, AsMutByteSlice, ToByteSlice, ToMutByteSlice};
+use std::{
+ convert::TryFrom,
+ fs::File,
+ io::{BufReader, Read, Write},
+ iter::FromIterator,
+ mem::{ManuallyDrop, MaybeUninit},
+ path::PathBuf,
+ sync::atomic::Ordering::Acquire,
+};
+
+use rayon::prelude::*;
+
+use super::super::compat::*;
+use super::super::{
+ graph_ops::{DeserializeGraphOp, SerializeGraphOp, ToUndirectedOp},
+ index::Idx,
+ input::{edgelist::Edges, Direction},
+ DirectedDegrees, DirectedNeighbors, DirectedNeighborsWithValues, Error, Graph,
+ NodeValues as NodeValuesTrait, SharedMut, UndirectedDegrees, UndirectedNeighbors,
+ UndirectedNeighborsWithValues,
+};
+
+/// Defines how the neighbor list of individual nodes are organized within the
+/// CSR target array.
+#[derive(Clone, Copy, Debug)]
+pub enum CsrLayout {
+ /// Neighbor lists are sorted and may contain duplicate target ids. This is
+ /// the default representation.
+ Sorted,
+ /// Neighbor lists are not in any particular order.
+ Unsorted,
+ /// Neighbor lists are sorted and do not contain duplicate target ids.
+ /// Self-loops, i.e., edges in the form of `(u, u)` are removed.
+ Deduplicated,
+}
+
+impl Default for CsrLayout {
+ fn default() -> Self {
+ CsrLayout::Unsorted
+ }
+}
+
+/// A Compressed-Sparse-Row data structure to represent sparse graphs.
+///
+/// The data structure is composed of two arrays: `offsets` and `targets`. For a
+/// graph with node count `n` and edge count `m`, `offsets` has exactly `n + 1`
+/// and `targets` exactly `m` entries.
+///
+/// For a given node `u`, `offsets[u]` stores the start index of the neighbor
+/// list of `u` in `targets`. The degree of `u`, i.e., the length of the
+/// neighbor list is defined by `offsets[u + 1] - offsets[u]`. The neighbor list
+/// of `u` is defined by the slice `&targets[offsets[u]..offsets[u + 1]]`.
+#[derive(Debug)]
+pub struct Csr {
+ offsets: Box<[Index]>,
+ targets: Box<[Target]>,
+}
+
+/// Represents the target of an edge and its associated value.
+#[derive(Clone, Copy, Debug)]
+#[repr(C)]
+pub struct Target {
+ pub target: NI,
+ pub value: EV,
+}
+
+impl Ord for Target {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.target.cmp(&other.target)
+ }
+}
+
+impl PartialOrd for Target {
+ fn partial_cmp(&self, other: &Self) -> Option {
+ self.target.partial_cmp(&other.target)
+ }
+}
+
+impl PartialEq for Target {
+ fn eq(&self, other: &Self) -> bool {
+ self.target.eq(&other.target)
+ }
+}
+
+impl Eq for Target {}
+
+impl Target {
+ pub fn new(target: T, value: EV) -> Self {
+ Self { target, value }
+ }
+}
+
+impl Csr {
+ pub(crate) fn new(offsets: Box<[Index]>, targets: Box<[Target]>) -> Self {
+ Self { offsets, targets }
+ }
+
+ #[inline]
+ pub(crate) fn node_count(&self) -> Index {
+ Index::new(self.offsets.len() - 1)
+ }
+
+ #[inline]
+ pub(crate) fn edge_count(&self) -> Index {
+ Index::new(self.targets.len())
+ }
+
+ #[inline]
+ pub(crate) fn degree(&self, i: Index) -> Index {
+ let from = self.offsets[i.index()];
+ let to = self.offsets[(i + Index::new(1)).index()];
+
+ to - from
+ }
+
+ #[inline]
+ pub(crate) fn targets_with_values(&self, i: Index) -> &[Target] {
+ let from = self.offsets[i.index()];
+ let to = self.offsets[(i + Index::new(1)).index()];
+
+ &self.targets[from.index()..to.index()]
+ }
+}
+
+impl Csr {
+ #[inline]
+ pub(crate) fn targets(&self, i: Index) -> &[NI] {
+ assert_eq!(
+ std::mem::size_of::>(),
+ std::mem::size_of::()
+ );
+ assert_eq!(
+ std::mem::align_of::>(),
+ std::mem::align_of::()
+ );
+ let from = self.offsets[i.index()];
+ let to = self.offsets[(i + Index::new(1)).index()];
+
+ let len = (to - from).index();
+
+ let targets = &self.targets[from.index()..to.index()];
+
+ // SAFETY: len is within bounds as it is calculated above as `to - from`.
+ // The types Target and T are verified to have the same
+ // size and alignment.
+ unsafe { std::slice::from_raw_parts(targets.as_ptr() as *const _, len) }
+ }
+}
+
+pub trait SwapCsr {
+ fn swap_csr(&mut self, csr: Csr) -> &mut Self;
+}
+
+impl From<(&'_ E, NI, Direction, CsrLayout)> for Csr
+where
+ NI: Idx,
+ EV: Copy + Send + Sync,
+ E: Edges,
+{
+ fn from(
+ (edge_list, node_count, direction, csr_layout): (&'_ E, NI, Direction, CsrLayout),
+ ) -> Self {
+ let degrees = edge_list.degrees(node_count, direction);
+ let offsets = prefix_sum_atomic(degrees);
+ let edge_count = offsets[node_count.index()].load(Acquire).index();
+ let mut targets = Vec::>::with_capacity(edge_count);
+ let targets_ptr = SharedMut::new(targets.as_mut_ptr());
+
+ // The following loop writes all targets into their correct position.
+ // The offsets are a prefix sum of all degrees, which will produce
+ // non-overlapping positions for all node values.
+ //
+ // SAFETY: for any (s, t) tuple from the same edge_list we use the
+ // prefix_sum to find a unique position for the target value, so that we
+ // only write once into each position and every thread that might run
+ // will write into different positions.
+ if matches!(direction, Direction::Outgoing | Direction::Undirected) {
+ edge_list.edges().for_each(|(s, t, v)| {
+ let offset = NI::get_and_increment(&offsets[s.index()], Acquire);
+
+ unsafe {
+ targets_ptr.add(offset.index()).write(Target::new(t, v));
+ }
+ })
+ }
+
+ if matches!(direction, Direction::Incoming | Direction::Undirected) {
+ edge_list.edges().for_each(|(s, t, v)| {
+ let offset = NI::get_and_increment(&offsets[t.index()], Acquire);
+
+ unsafe {
+ targets_ptr.add(offset.index()).write(Target::new(s, v));
+ }
+ })
+ }
+
+ // SAFETY: The previous loops iterated the input edge list once (twice
+ // for undirected) and inserted one node id for each edge. The
+ // `edge_count` is defined by the highest offset value.
+ unsafe {
+ targets.set_len(edge_count);
+ }
+ let mut offsets = ManuallyDrop::new(offsets);
+ let (ptr, len, cap) = (offsets.as_mut_ptr(), offsets.len(), offsets.capacity());
+
+ // SAFETY: NI and NI::Atomic have the same memory layout
+ let mut offsets = unsafe {
+ let ptr = ptr as *mut _;
+ Vec::from_raw_parts(ptr, len, cap)
+ };
+
+ // Each insert into the target array in the previous loops incremented
+ // the offset for the corresponding node by one. As a consequence the
+ // offset values are shifted one index to the right. We need to correct
+ // this in order to get correct offsets.
+ offsets.rotate_right(1);
+ offsets[0] = NI::zero();
+
+ let (offsets, targets) = match csr_layout {
+ CsrLayout::Unsorted => (offsets, targets),
+ CsrLayout::Sorted => {
+ sort_targets(&offsets, &mut targets);
+ (offsets, targets)
+ }
+ CsrLayout::Deduplicated => {
+ let offsets_targets = sort_and_deduplicate_targets(&offsets, &mut targets[..]);
+ offsets_targets
+ }
+ };
+
+ Csr {
+ offsets: offsets.into_boxed_slice(),
+ targets: targets.into_boxed_slice(),
+ }
+ }
+}
+
+unsafe impl ToByteSlice for Target
+where
+ NI: ToByteSlice,
+ EV: ToByteSlice,
+{
+ fn to_byte_slice + ?Sized>(slice: &S) -> &[u8] {
+ let slice = slice.as_ref();
+ let len = slice.len() * std::mem::size_of::>();
+ unsafe { std::slice::from_raw_parts(slice.as_ptr() as *const u8, len) }
+ }
+}
+
+unsafe impl ToMutByteSlice for Target
+where
+ NI: ToMutByteSlice,
+ EV: ToMutByteSlice,
+{
+ fn to_mut_byte_slice + ?Sized>(slice: &mut S) -> &mut [u8] {
+ let slice = slice.as_mut();
+ let len = slice.len() * std::mem::size_of::>();
+ unsafe { std::slice::from_raw_parts_mut(slice.as_mut_ptr() as *mut u8, len) }
+ }
+}
+
+impl Csr
+where
+ NI: Idx + ToByteSlice,
+ EV: ToByteSlice,
+{
+ fn serialize(&self, output: &mut W) -> Result<(), Error> {
+ let type_name = std::any::type_name::().as_bytes();
+ output.write_all([type_name.len()].as_byte_slice())?;
+ output.write_all(type_name)?;
+
+ let node_count = self.node_count();
+ let edge_count = self.edge_count();
+ let meta = [node_count, edge_count];
+ output.write_all(meta.as_byte_slice())?;
+
+ output.write_all(self.offsets.as_byte_slice())?;
+ output.write_all(self.targets.as_byte_slice())?;
+
+ Ok(())
+ }
+}
+
+impl Csr
+where
+ NI: Idx + ToMutByteSlice,
+ EV: ToMutByteSlice,
+{
+ fn deserialize(read: &mut R) -> Result, Error> {
+ let mut type_name_len = [0_usize; 1];
+ read.read_exact(type_name_len.as_mut_byte_slice())?;
+ let [type_name_len] = type_name_len;
+
+ let mut type_name = vec![0_u8; type_name_len];
+ read.read_exact(type_name.as_mut_byte_slice())?;
+ let type_name = String::from_utf8(type_name).expect("could not read type name");
+
+ let expected_type_name = std::any::type_name::().to_string();
+
+ if type_name != expected_type_name {
+ return Err(Error::InvalidIdType {
+ expected: expected_type_name,
+ actual: type_name,
+ });
+ }
+
+ let mut meta = [NI::zero(); 2];
+ read.read_exact(meta.as_mut_byte_slice())?;
+
+ let [node_count, edge_count] = meta;
+
+ let mut offsets = Box::new_uninit_slice_compat(node_count.index() + 1);
+ let offsets_ptr = offsets.as_mut_ptr() as *mut NI;
+ let offsets_ptr =
+ unsafe { std::slice::from_raw_parts_mut(offsets_ptr, node_count.index() + 1) };
+ read.read_exact(offsets_ptr.as_mut_byte_slice())?;
+
+ let mut targets = Box::new_uninit_slice_compat(edge_count.index());
+ let targets_ptr = targets.as_mut_ptr() as *mut Target;
+ let targets_ptr =
+ unsafe { std::slice::from_raw_parts_mut(targets_ptr, edge_count.index()) };
+ read.read_exact(targets_ptr.as_mut_byte_slice())?;
+
+ let offsets = unsafe { offsets.assume_init_compat() };
+ let targets = unsafe { targets.assume_init_compat() };
+
+ Ok(Csr::new(offsets, targets))
+ }
+}
+
+pub struct NodeValues(Box<[NV]>);
+
+impl NodeValues {
+ pub fn new(node_values: Vec) -> Self {
+ Self(node_values.into_boxed_slice())
+ }
+}
+
+impl FromIterator for NodeValues {
+ fn from_iter>(iter: T) -> Self {
+ Self(iter.into_iter().collect::>().into_boxed_slice())
+ }
+}
+
+impl NodeValues
+where
+ NV: ToByteSlice,
+{
+ fn serialize(&self, output: &mut W) -> Result<(), Error> {
+ let node_count = self.0.len();
+ let meta = [node_count];
+ output.write_all(meta.as_byte_slice())?;
+ output.write_all(self.0.as_byte_slice())?;
+ Ok(())
+ }
+}
+
+impl NodeValues
+where
+ NV: ToMutByteSlice,
+{
+ fn deserialize(read: &mut R) -> Result {
+ let mut meta = [0_usize; 1];
+ read.read_exact(meta.as_mut_byte_slice())?;
+ let [node_count] = meta;
+
+ let mut node_values = Box::new_uninit_slice_compat(node_count);
+ let node_values_ptr = node_values.as_mut_ptr() as *mut NV;
+ let node_values_slice =
+ unsafe { std::slice::from_raw_parts_mut(node_values_ptr, node_count.index()) };
+ read.read_exact(node_values_slice.as_mut_byte_slice())?;
+
+ let offsets = unsafe { node_values.assume_init_compat() };
+
+ Ok(NodeValues(offsets))
+ }
+}
+
+pub struct DirectedCsrGraph {
+ node_values: NodeValues,
+ csr_out: Csr,
+ csr_inc: Csr,
+}
+
+impl DirectedCsrGraph {
+ pub fn new(
+ node_values: NodeValues,
+ csr_out: Csr,
+ csr_inc: Csr,
+ ) -> Self {
+ let g = Self {
+ node_values,
+ csr_out,
+ csr_inc,
+ };
+
+ g
+ }
+}
+
+impl ToUndirectedOp for DirectedCsrGraph
+where
+ NI: Idx,
+ NV: Clone + Send + Sync,
+ EV: Copy + Send + Sync,
+{
+ type Undirected = UndirectedCsrGraph;
+
+ fn to_undirected(&self, layout: impl Into