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