weavatrix_graph/topology/
core.rs1use 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 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}