Skip to main content

weavatrix_graph/topology/
core.rs

1use super::csr::Csr;
2use super::{EdgeEndpoints, EdgeIndex, GraphView, IndexGraphView, NodeIndex};
3use crate::Vec;
4use crate::{GraphError, Result};
5use serde::{Deserialize, Deserializer, Serialize, de::Error as _};
6
7#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
8pub struct Topology {
9    node_count: u32,
10    endpoints: Vec<EdgeEndpoints>,
11    #[serde(skip)]
12    outgoing: Csr,
13    #[serde(skip)]
14    incoming: Csr,
15}
16
17impl Topology {
18    /// Builds a validated directed topology with incoming and outgoing CSR.
19    ///
20    /// # Errors
21    ///
22    /// Returns an error when the graph exceeds the compact index capacity or
23    /// an endpoint is outside `node_count`.
24    pub fn try_from_edges(
25        node_count: usize,
26        edges: impl IntoIterator<Item = EdgeEndpoints>,
27    ) -> Result<Self> {
28        Self::try_from_collected(node_count, edges.into_iter().collect())
29    }
30
31    pub(super) fn try_from_collected(
32        node_count: usize,
33        endpoints: Vec<EdgeEndpoints>,
34    ) -> Result<Self> {
35        let compact_node_count = Self::validate_capacity(node_count, endpoints.len())?;
36        let (outgoing, incoming) = Csr::try_build_pair(node_count, &endpoints)?;
37        Ok(Self::from_parts(
38            compact_node_count,
39            endpoints,
40            outgoing,
41            incoming,
42        ))
43    }
44
45    pub(super) fn validate_capacity(node_count: usize, edge_count: usize) -> Result<u32> {
46        let compact_node_count =
47            u32::try_from(node_count).map_err(|_| GraphError::IndexCapacityExceeded {
48                category: "nodes",
49                count: node_count,
50            })?;
51        u32::try_from(edge_count).map_err(|_| GraphError::IndexCapacityExceeded {
52            category: "edges",
53            count: edge_count,
54        })?;
55        Ok(compact_node_count)
56    }
57
58    pub(super) fn from_parts(
59        node_count: u32,
60        endpoints: Vec<EdgeEndpoints>,
61        outgoing: Csr,
62        incoming: Csr,
63    ) -> Self {
64        Self {
65            node_count,
66            endpoints,
67            outgoing,
68            incoming,
69        }
70    }
71
72    pub(crate) fn try_from_usize_edges(
73        node_count: usize,
74        edges: impl IntoIterator<Item = (usize, usize)>,
75    ) -> Result<Self> {
76        let edges = edges.into_iter();
77        let mut endpoints = Vec::with_capacity(edges.size_hint().0);
78        for (source, target) in edges {
79            endpoints.push(EdgeEndpoints::new(
80                compact_node(source)?,
81                compact_node(target)?,
82            ));
83        }
84        Self::try_from_edges(node_count, endpoints)
85    }
86
87    #[must_use]
88    pub const fn node_count(&self) -> usize {
89        self.node_count as usize
90    }
91
92    #[must_use]
93    pub const fn edge_count(&self) -> usize {
94        self.endpoints.len()
95    }
96
97    #[must_use]
98    pub fn contains_node(&self, node: NodeIndex) -> bool {
99        node.index() < self.node_count()
100    }
101
102    #[must_use]
103    pub fn contains_edge(&self, edge: EdgeIndex) -> bool {
104        edge.index() < self.edge_count()
105    }
106
107    #[must_use]
108    pub fn edge_endpoints(&self, edge: EdgeIndex) -> Option<EdgeEndpoints> {
109        self.endpoints.get(edge.index()).copied()
110    }
111
112    #[must_use]
113    pub fn outgoing_edges(
114        &self,
115        node: NodeIndex,
116    ) -> impl DoubleEndedIterator<Item = EdgeIndex> + ExactSizeIterator + '_ {
117        self.outgoing.get(node.index()).iter().copied()
118    }
119
120    #[must_use]
121    pub fn incoming_edges(
122        &self,
123        node: NodeIndex,
124    ) -> impl DoubleEndedIterator<Item = EdgeIndex> + ExactSizeIterator + '_ {
125        self.incoming.get(node.index()).iter().copied()
126    }
127
128    #[must_use]
129    pub fn outgoing_neighbors(
130        &self,
131        node: NodeIndex,
132    ) -> impl DoubleEndedIterator<Item = NodeIndex> + ExactSizeIterator + '_ {
133        self.outgoing_edges(node)
134            .map(|edge| self.endpoints[edge.index()].target())
135    }
136
137    #[must_use]
138    pub fn incoming_neighbors(
139        &self,
140        node: NodeIndex,
141    ) -> impl DoubleEndedIterator<Item = NodeIndex> + ExactSizeIterator + '_ {
142        self.incoming_edges(node)
143            .map(|edge| self.endpoints[edge.index()].source())
144    }
145
146    #[must_use]
147    pub fn out_degree(&self, node: NodeIndex) -> Option<usize> {
148        self.contains_node(node)
149            .then(|| self.outgoing.get(node.index()).len())
150    }
151
152    #[must_use]
153    pub fn in_degree(&self, node: NodeIndex) -> Option<usize> {
154        self.contains_node(node)
155            .then(|| self.incoming.get(node.index()).len())
156    }
157
158    pub(crate) fn traversal_parts(&self) -> (&[EdgeEndpoints], &Csr, &Csr) {
159        (&self.endpoints, &self.outgoing, &self.incoming)
160    }
161}
162
163impl GraphView for Topology {
164    type Node = NodeIndex;
165    type Edge = EdgeIndex;
166
167    fn node_count(&self) -> usize {
168        self.node_count()
169    }
170
171    fn edge_count(&self) -> usize {
172        self.edge_count()
173    }
174
175    fn contains_node(&self, node: NodeIndex) -> bool {
176        self.contains_node(node)
177    }
178
179    fn contains_edge(&self, edge: EdgeIndex) -> bool {
180        self.contains_edge(edge)
181    }
182
183    fn node_indices(&self) -> impl Iterator<Item = Self::Node> + '_ {
184        (0..self.node_count).map(NodeIndex::new)
185    }
186
187    fn edge_indices(&self) -> impl Iterator<Item = Self::Edge> + '_ {
188        (0..u32::try_from(self.edge_count()).expect("edge count was checked")).map(EdgeIndex::new)
189    }
190
191    fn edge_endpoints(&self, edge: EdgeIndex) -> Option<EdgeEndpoints> {
192        self.edge_endpoints(edge)
193    }
194
195    fn edge_references(&self) -> impl Iterator<Item = (EdgeIndex, EdgeEndpoints)> + '_ {
196        self.edge_indices().zip(self.endpoints.iter().copied())
197    }
198
199    fn outgoing_edges(&self, node: NodeIndex) -> impl Iterator<Item = EdgeIndex> + '_ {
200        self.outgoing_edges(node)
201    }
202
203    fn incoming_edges(&self, node: NodeIndex) -> impl Iterator<Item = EdgeIndex> + '_ {
204        self.incoming_edges(node)
205    }
206}
207
208impl IndexGraphView for Topology {
209    fn node_bound(&self) -> usize {
210        self.node_count()
211    }
212
213    fn edge_bound(&self) -> usize {
214        self.edge_count()
215    }
216
217    fn node_slot(node: Self::Node) -> usize {
218        node.index()
219    }
220
221    fn edge_slot(edge: Self::Edge) -> usize {
222        edge.index()
223    }
224}
225
226#[derive(Deserialize)]
227struct TopologyWire {
228    node_count: u32,
229    endpoints: Vec<EdgeEndpoints>,
230}
231
232impl<'de> Deserialize<'de> for Topology {
233    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
234    where
235        D: Deserializer<'de>,
236    {
237        let wire = TopologyWire::deserialize(deserializer)?;
238        Self::try_from_edges(wire.node_count as usize, wire.endpoints).map_err(D::Error::custom)
239    }
240}
241
242fn compact_node(index: usize) -> Result<NodeIndex> {
243    u32::try_from(index)
244        .map(NodeIndex::new)
245        .map_err(|_| GraphError::IndexCapacityExceeded {
246            category: "node index",
247            count: index,
248        })
249}