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        let compact_node_count =
29            u32::try_from(node_count).map_err(|_| GraphError::IndexCapacityExceeded {
30                category: "nodes",
31                count: node_count,
32            })?;
33        let endpoints = edges.into_iter().collect::<Vec<_>>();
34        u32::try_from(endpoints.len()).map_err(|_| GraphError::IndexCapacityExceeded {
35            category: "edges",
36            count: endpoints.len(),
37        })?;
38        let (outgoing, incoming) = Csr::try_build_pair(node_count, &endpoints)?;
39        Ok(Self {
40            node_count: compact_node_count,
41            endpoints,
42            outgoing,
43            incoming,
44        })
45    }
46
47    pub(crate) fn try_from_usize_edges(
48        node_count: usize,
49        edges: impl IntoIterator<Item = (usize, usize)>,
50    ) -> Result<Self> {
51        let mut endpoints = Vec::new();
52        for (source, target) in edges {
53            endpoints.push(EdgeEndpoints::new(
54                compact_node(source)?,
55                compact_node(target)?,
56            ));
57        }
58        Self::try_from_edges(node_count, endpoints)
59    }
60
61    #[must_use]
62    pub const fn node_count(&self) -> usize {
63        self.node_count as usize
64    }
65
66    #[must_use]
67    pub const fn edge_count(&self) -> usize {
68        self.endpoints.len()
69    }
70
71    #[must_use]
72    pub fn contains_node(&self, node: NodeIndex) -> bool {
73        node.index() < self.node_count()
74    }
75
76    #[must_use]
77    pub fn contains_edge(&self, edge: EdgeIndex) -> bool {
78        edge.index() < self.edge_count()
79    }
80
81    #[must_use]
82    pub fn edge_endpoints(&self, edge: EdgeIndex) -> Option<EdgeEndpoints> {
83        self.endpoints.get(edge.index()).copied()
84    }
85
86    #[must_use]
87    pub fn outgoing_edges(
88        &self,
89        node: NodeIndex,
90    ) -> impl DoubleEndedIterator<Item = EdgeIndex> + ExactSizeIterator + '_ {
91        self.outgoing.get(node.index()).iter().copied()
92    }
93
94    #[must_use]
95    pub fn incoming_edges(
96        &self,
97        node: NodeIndex,
98    ) -> impl DoubleEndedIterator<Item = EdgeIndex> + ExactSizeIterator + '_ {
99        self.incoming.get(node.index()).iter().copied()
100    }
101
102    #[must_use]
103    pub fn outgoing_neighbors(
104        &self,
105        node: NodeIndex,
106    ) -> impl DoubleEndedIterator<Item = NodeIndex> + ExactSizeIterator + '_ {
107        self.outgoing_edges(node)
108            .map(|edge| self.endpoints[edge.index()].target())
109    }
110
111    #[must_use]
112    pub fn incoming_neighbors(
113        &self,
114        node: NodeIndex,
115    ) -> impl DoubleEndedIterator<Item = NodeIndex> + ExactSizeIterator + '_ {
116        self.incoming_edges(node)
117            .map(|edge| self.endpoints[edge.index()].source())
118    }
119
120    #[must_use]
121    pub fn out_degree(&self, node: NodeIndex) -> Option<usize> {
122        self.contains_node(node)
123            .then(|| self.outgoing.get(node.index()).len())
124    }
125
126    #[must_use]
127    pub fn in_degree(&self, node: NodeIndex) -> Option<usize> {
128        self.contains_node(node)
129            .then(|| self.incoming.get(node.index()).len())
130    }
131}
132
133impl GraphView for Topology {
134    type Node = NodeIndex;
135    type Edge = EdgeIndex;
136
137    fn node_count(&self) -> usize {
138        self.node_count()
139    }
140
141    fn edge_count(&self) -> usize {
142        self.edge_count()
143    }
144
145    fn contains_node(&self, node: NodeIndex) -> bool {
146        self.contains_node(node)
147    }
148
149    fn contains_edge(&self, edge: EdgeIndex) -> bool {
150        self.contains_edge(edge)
151    }
152
153    fn node_indices(&self) -> impl Iterator<Item = Self::Node> + '_ {
154        (0..self.node_count).map(NodeIndex::new)
155    }
156
157    fn edge_indices(&self) -> impl Iterator<Item = Self::Edge> + '_ {
158        (0..u32::try_from(self.edge_count()).expect("edge count was checked")).map(EdgeIndex::new)
159    }
160
161    fn edge_endpoints(&self, edge: EdgeIndex) -> Option<EdgeEndpoints> {
162        self.edge_endpoints(edge)
163    }
164
165    fn edge_references(&self) -> impl Iterator<Item = (EdgeIndex, EdgeEndpoints)> + '_ {
166        self.edge_indices().zip(self.endpoints.iter().copied())
167    }
168
169    fn outgoing_edges(&self, node: NodeIndex) -> impl Iterator<Item = EdgeIndex> + '_ {
170        self.outgoing_edges(node)
171    }
172
173    fn incoming_edges(&self, node: NodeIndex) -> impl Iterator<Item = EdgeIndex> + '_ {
174        self.incoming_edges(node)
175    }
176}
177
178impl IndexGraphView for Topology {
179    fn node_bound(&self) -> usize {
180        self.node_count()
181    }
182
183    fn edge_bound(&self) -> usize {
184        self.edge_count()
185    }
186
187    fn node_slot(node: Self::Node) -> usize {
188        node.index()
189    }
190
191    fn edge_slot(edge: Self::Edge) -> usize {
192        edge.index()
193    }
194}
195
196#[derive(Deserialize)]
197struct TopologyWire {
198    node_count: u32,
199    endpoints: Vec<EdgeEndpoints>,
200}
201
202impl<'de> Deserialize<'de> for Topology {
203    fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
204    where
205        D: Deserializer<'de>,
206    {
207        let wire = TopologyWire::deserialize(deserializer)?;
208        Self::try_from_edges(wire.node_count as usize, wire.endpoints).map_err(D::Error::custom)
209    }
210}
211
212fn compact_node(index: usize) -> Result<NodeIndex> {
213    u32::try_from(index)
214        .map(NodeIndex::new)
215        .map_err(|_| GraphError::IndexCapacityExceeded {
216            category: "node index",
217            count: index,
218        })
219}