1use crate::error::GraphError;
10use crate::graph::Graph;
11use crate::mst::{MstWeight, checked_mst_add};
12use crate::unionfind::UnionFind;
13use core::fmt::Display;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct SpanningTree<W> {
18 pub edges: Vec<usize>,
20 pub total_weight: W,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq)]
26pub struct MstCertificate {
27 pub edge_ids: Vec<usize>,
29 pub total_weight_repr: String,
31}
32
33impl<W: Display> SpanningTree<W> {
34 pub fn certificate(&self) -> MstCertificate {
36 MstCertificate {
37 edge_ids: self.edges.clone(),
38 total_weight_repr: format!("{}", self.total_weight),
39 }
40 }
41}
42
43fn invalid(msg: &str) -> GraphError {
44 GraphError::CertificateInvalid(msg.to_string())
45}
46
47fn tree_path_max<W: Ord + Clone>(
50 n: usize,
51 tree_adj: &[Vec<(usize, W)>],
52 u: usize,
53 v: usize,
54) -> Option<W> {
55 let mut max_w: Vec<Option<W>> = vec![None; n];
56 let mut visited = vec![false; n];
57 let mut queue = std::collections::VecDeque::new();
58 visited[u] = true;
59 queue.push_back(u);
60 while let Some(x) = queue.pop_front() {
61 if x == v {
62 return max_w[x].clone();
63 }
64 for (y, w) in &tree_adj[x] {
65 if !visited[*y] {
66 visited[*y] = true;
67 let cand = match &max_w[x] {
68 Some(m) if m >= w => m.clone(),
69 _ => w.clone(),
70 };
71 max_w[*y] = Some(cand);
72 queue.push_back(*y);
73 }
74 }
75 }
76 None
77}
78
79pub fn verify_mst<N, W>(graph: &Graph<N, W>, cert: &MstCertificate) -> Result<(), GraphError>
81where
82 W: MstWeight + Display,
83{
84 graph.validate()?;
85 if graph.is_directed() {
86 return Err(GraphError::WrongGraphKind(
87 "MST certificate requires an undirected graph".to_string(),
88 ));
89 }
90 let n = graph.node_count();
91
92 let mut tree_edges = Vec::with_capacity(cert.edge_ids.len());
94 for &id in &cert.edge_ids {
95 let e = graph
96 .edges
97 .get(id)
98 .ok_or_else(|| invalid("unknown edge id"))?;
99 if e.is_self_loop() {
100 return Err(invalid("self-loop in tree"));
101 }
102 tree_edges.push(e);
103 }
104
105 let expected = n.saturating_sub(1);
107 if tree_edges.len() != expected {
108 return Err(invalid("wrong edge count for a spanning tree"));
109 }
110
111 let mut uf = UnionFind::new(n);
113 for e in &tree_edges {
114 if !uf.union(e.source, e.target) {
115 return Err(invalid("tree contains a cycle"));
116 }
117 }
118 if n > 0 {
119 let root = uf.find(0);
120 for i in 1..n {
121 if uf.find(i) != root {
122 return Err(invalid("tree does not span the graph"));
123 }
124 }
125 }
126
127 let mut total = W::default();
129 for e in &tree_edges {
130 total = checked_mst_add(&total, &e.weight)?;
131 }
132 if format!("{total}") != cert.total_weight_repr {
133 return Err(invalid("total weight mismatch"));
134 }
135
136 let tree_ids: std::collections::HashSet<usize> = cert.edge_ids.iter().copied().collect();
139 let mut tree_adj: Vec<Vec<(usize, W)>> = vec![Vec::new(); n];
140 for e in &tree_edges {
141 tree_adj[e.source].push((e.target, e.weight.clone()));
142 tree_adj[e.target].push((e.source, e.weight.clone()));
143 }
144 for e in &graph.edges {
145 if tree_ids.contains(&e.id) || e.is_self_loop() {
146 continue;
147 }
148 if let Some(path_max) = tree_path_max(n, &tree_adj, e.source, e.target)
149 && e.weight < path_max
150 {
151 return Err(invalid("not minimal: a cheaper spanning tree exists"));
152 }
153 }
154 Ok(())
155}
156
157#[derive(Debug, Clone, PartialEq, Eq)]
159pub struct ShortestPathCertificate {
160 pub source: usize,
162 pub predecessors: Vec<Option<usize>>,
165}
166
167fn edge_weight<N>(graph: &Graph<N, i64>, from: usize, to: usize) -> Option<i64> {
169 let undirected = !graph.is_directed();
170 graph
171 .edges
172 .iter()
173 .filter(|e| {
174 (e.source == from && e.target == to)
175 || (undirected && e.source == to && e.target == from)
176 })
177 .map(|e| e.weight)
178 .min()
179}
180
181fn tree_dist<N>(
184 graph: &Graph<N, i64>,
185 cert: &ShortestPathCertificate,
186 v: usize,
187 memo: &mut [Option<Option<i64>>],
188 visiting: &mut [bool],
189) -> Result<Option<i64>, GraphError> {
190 if v == cert.source {
191 return Ok(Some(0));
192 }
193 if let Some(d) = memo[v] {
194 return Ok(d);
195 }
196 let result = match cert.predecessors[v] {
197 None => None,
198 Some(u) => {
199 if u >= cert.predecessors.len() {
200 return Err(invalid("predecessor out of range"));
201 }
202 if visiting[v] {
203 return Err(invalid("predecessor cycle"));
204 }
205 visiting[v] = true;
206 let du = tree_dist(graph, cert, u, memo, visiting)?;
207 visiting[v] = false;
208 match du {
209 None => return Err(invalid("predecessor points outside the tree")),
210 Some(d_u) => {
211 let w = edge_weight(graph, u, v)
212 .ok_or_else(|| invalid("predecessor edge missing"))?;
213 Some(
214 d_u.checked_add(w)
215 .ok_or_else(|| invalid("shortest-path distance overflow"))?,
216 )
217 }
218 }
219 }
220 };
221 memo[v] = Some(result);
222 Ok(result)
223}
224
225pub fn verify_shortest_paths<N>(
227 graph: &Graph<N, i64>,
228 cert: &ShortestPathCertificate,
229) -> Result<(), GraphError> {
230 graph.validate()?;
231 let n = graph.node_count();
232 if cert.source >= n {
233 return Err(invalid("source out of range"));
234 }
235 if cert.predecessors.len() != n {
236 return Err(invalid("predecessor length mismatch"));
237 }
238 if cert.predecessors[cert.source].is_some() {
239 return Err(invalid("source must have no predecessor"));
240 }
241 for (node, pred) in cert.predecessors.iter().enumerate() {
242 if let Some(parent) = pred
243 && *parent >= n
244 {
245 return Err(invalid(&format!(
246 "predecessor out of range for node {node}"
247 )));
248 }
249 }
250
251 let mut memo: Vec<Option<Option<i64>>> = vec![None; n];
252 let mut visiting = vec![false; n];
253 let mut dist = vec![None; n];
254 for (v, slot) in dist.iter_mut().enumerate() {
255 *slot = tree_dist(graph, cert, v, &mut memo, &mut visiting)?;
256 }
257
258 let undirected = !graph.is_directed();
261 for e in &graph.edges {
262 let mut arcs = vec![(e.source, e.target)];
263 if undirected {
264 arcs.push((e.target, e.source));
265 }
266 for (a, b) in arcs {
267 if let Some(da) = dist[a] {
268 match dist[b] {
269 None => return Err(invalid("reachable node missing from tree")),
270 Some(db) => {
271 let nd = da
272 .checked_add(e.weight)
273 .ok_or_else(|| invalid("shortest-path relaxation overflow"))?;
274 if db > nd {
275 return Err(invalid("edge violates shortest-path relaxation"));
276 }
277 }
278 }
279 }
280 }
281 }
282 Ok(())
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288 use crate::edge::Directedness;
289 use crate::path::bellman_ford;
290
291 fn weighted() -> Graph<u8, i64> {
292 let mut g = Graph::with_nodes(vec![0, 1, 2, 3], Directedness::Directed);
293 g.add_edge(0, 1, 1).unwrap();
294 g.add_edge(1, 2, 2).unwrap();
295 g.add_edge(0, 2, 5).unwrap();
296 g.add_edge(2, 3, 1).unwrap();
297 g
298 }
299
300 #[test]
301 fn valid_shortest_path_cert_verifies() {
302 let g = weighted();
303 let (res, _) = bellman_ford(&g, 0).unwrap();
304 let cert = ShortestPathCertificate {
305 source: 0,
306 predecessors: res.predecessors,
307 };
308 assert!(verify_shortest_paths(&g, &cert).is_ok());
309 }
310
311 #[test]
312 fn tampered_shortest_path_cert_rejected() {
313 let g = weighted();
314 let (res, _) = bellman_ford(&g, 0).unwrap();
315 let mut preds = res.predecessors;
316 preds[2] = Some(0);
319 let cert = ShortestPathCertificate {
320 source: 0,
321 predecessors: preds,
322 };
323 assert!(verify_shortest_paths(&g, &cert).is_err());
324 }
325
326 #[test]
327 fn predecessor_out_of_range_is_invalid_certificate() {
328 let mut g: Graph<u8, i64> = Graph::with_nodes(vec![0, 1], Directedness::Directed);
329 g.add_edge(0, 1, 1).unwrap();
330 let cert = ShortestPathCertificate {
331 source: 0,
332 predecessors: vec![None, Some(2)],
333 };
334
335 assert!(matches!(
336 verify_shortest_paths(&g, &cert),
337 Err(GraphError::CertificateInvalid(_))
338 ));
339 }
340
341 #[test]
342 fn shortest_path_certificate_rejects_distance_overflow() {
343 let mut max_graph: Graph<u8, i64> =
344 Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
345 max_graph.add_edge(0, 1, i64::MAX).unwrap();
346 max_graph.add_edge(1, 2, 1).unwrap();
347 let max_cert = ShortestPathCertificate {
348 source: 0,
349 predecessors: vec![None, Some(0), Some(1)],
350 };
351 assert!(matches!(
352 verify_shortest_paths(&max_graph, &max_cert),
353 Err(GraphError::CertificateInvalid(_))
354 ));
355
356 let mut min_graph: Graph<u8, i64> =
357 Graph::with_nodes(vec![0, 1, 2], Directedness::Directed);
358 min_graph.add_edge(0, 1, i64::MIN).unwrap();
359 min_graph.add_edge(1, 2, -1).unwrap();
360 let min_cert = ShortestPathCertificate {
361 source: 0,
362 predecessors: vec![None, Some(0), Some(1)],
363 };
364 assert!(matches!(
365 verify_shortest_paths(&min_graph, &min_cert),
366 Err(GraphError::CertificateInvalid(_))
367 ));
368 }
369
370 #[test]
371 fn mst_certificate_rejects_directed_graph() {
372 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1], Directedness::Directed);
373 g.add_edge(0, 1, 1).unwrap();
374 let cert = MstCertificate {
375 edge_ids: vec![0],
376 total_weight_repr: "1".to_string(),
377 };
378
379 assert!(matches!(
380 verify_mst(&g, &cert),
381 Err(GraphError::WrongGraphKind(_))
382 ));
383 }
384
385 #[test]
386 fn mst_certificate_rejects_total_weight_overflow() {
387 let mut g: Graph<u8, u64> = Graph::with_nodes(vec![0, 1, 2], Directedness::Undirected);
388 g.add_edge(0, 1, u64::MAX).unwrap();
389 g.add_edge(1, 2, 1).unwrap();
390 let cert = MstCertificate {
391 edge_ids: vec![0, 1],
392 total_weight_repr: "0".to_string(),
393 };
394
395 assert!(matches!(
396 verify_mst(&g, &cert),
397 Err(GraphError::WeightOverflow(_))
398 ));
399 }
400}