Skip to main content

weavatrix_graph/topology/
core.rs

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