1use core::cmp::Ordering;
7
8use crate::{
9 AlgorithmControl, AlgorithmInterrupt, AlgorithmReceipt, FiniteCost, GraphError, NeverInterrupt,
10 control::WorkMeter,
11 cost::{add, compare, validate},
12};
13
14#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct LayeredCell<C> {
17 pub total_cost: Option<C>,
19 pub predecessor: Option<usize>,
21}
22
23#[derive(Clone, Debug, PartialEq, Eq)]
25pub struct LayeredCertificate<C> {
26 pub layers: Vec<Vec<LayeredCell<C>>>,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq)]
32pub struct LayeredPath<S, C> {
33 pub states: Vec<S>,
35 pub indices: Vec<usize>,
37 pub total_cost: C,
39 pub certificate: LayeredCertificate<C>,
41 pub receipt: AlgorithmReceipt,
43}
44
45pub fn layered_shortest_path<S: Clone, C: FiniteCost>(
50 layers: &[Vec<S>],
51 cost: impl Fn(&S, &S) -> C,
52) -> Result<LayeredPath<S, C>, GraphError> {
53 layered_shortest_path_with_control(
54 layers,
55 |left, right| Some(cost(left, right)),
56 &AlgorithmControl::default(),
57 &NeverInterrupt,
58 )
59}
60
61pub fn layered_shortest_path_with_control<S: Clone, C: FiniteCost>(
66 layers: &[Vec<S>],
67 transition: impl Fn(&S, &S) -> Option<C>,
68 control: &AlgorithmControl,
69 interrupt: &dyn AlgorithmInterrupt,
70) -> Result<LayeredPath<S, C>, GraphError> {
71 validate_layers(layers)?;
72 let memory = layer_cell_count(layers)?;
73 let mut meter = WorkMeter::new(control, interrupt, memory)?;
74 let certificate = build_certificate(layers, &transition, Some(&mut meter))?;
75 let (indices, total_cost) = reconstruct(&certificate)?;
76 let states = indices
77 .iter()
78 .enumerate()
79 .map(|(layer, index)| layers[layer][*index].clone())
80 .collect();
81 Ok(LayeredPath {
82 states,
83 indices,
84 total_cost,
85 certificate,
86 receipt: meter.finish(),
87 })
88}
89
90pub fn verify_layered_path<S: Clone + PartialEq, C: FiniteCost>(
93 layers: &[Vec<S>],
94 transition: impl Fn(&S, &S) -> Option<C>,
95 path: &LayeredPath<S, C>,
96) -> Result<(), GraphError> {
97 validate_layers(layers)?;
98 path.receipt.validate()?;
99 let expected = build_certificate(layers, &transition, None)?;
100 if path.certificate != expected {
101 return Err(GraphError::CertificateInvalid(
102 "layered certificate violates the Bellman recurrence or stable ties".to_owned(),
103 ));
104 }
105 let (indices, total_cost) = reconstruct(&expected)?;
106 if path.indices != indices || path.total_cost != total_cost {
107 return Err(GraphError::CertificateInvalid(
108 "layered path does not select the certified optimum".to_owned(),
109 ));
110 }
111 let states = indices
112 .iter()
113 .enumerate()
114 .map(|(layer, index)| layers[layer][*index].clone())
115 .collect::<Vec<_>>();
116 if path.states != states {
117 return Err(GraphError::CertificateInvalid(
118 "layered path states do not match their certified indices".to_owned(),
119 ));
120 }
121 verify_receipt(layers, &path.receipt)
122}
123
124fn build_certificate<S, C: FiniteCost>(
125 layers: &[Vec<S>],
126 transition: &impl Fn(&S, &S) -> Option<C>,
127 mut meter: Option<&mut WorkMeter<'_>>,
128) -> Result<LayeredCertificate<C>, GraphError> {
129 let mut table = Vec::with_capacity(layers.len());
130 let mut first = Vec::with_capacity(layers[0].len());
131 for _ in &layers[0] {
132 charge_cell(&mut meter)?;
133 first.push(LayeredCell {
134 total_cost: Some(C::zero()),
135 predecessor: None,
136 });
137 }
138 table.push(first);
139
140 for layer_index in 1..layers.len() {
141 let previous_states = &layers[layer_index - 1];
142 let current_states = &layers[layer_index];
143 let previous_cells = &table[layer_index - 1];
144 let mut current_cells = Vec::with_capacity(current_states.len());
145 for current in current_states {
146 charge_cell(&mut meter)?;
147 let mut best: Option<(C, usize)> = None;
148 for (predecessor, previous) in previous_states.iter().enumerate() {
149 charge_edge(&mut meter)?;
150 let Some(previous_cost) = &previous_cells[predecessor].total_cost else {
151 continue;
152 };
153 let Some(edge_cost) = transition(previous, current) else {
154 continue;
155 };
156 validate(&edge_cost, "layered transition")?;
157 let candidate = add(previous_cost, &edge_cost, "layered path transition")?;
158 let replace = match &best {
159 Some((best_cost, _)) => {
160 compare(&candidate, best_cost, "layered path ordering")? == Ordering::Less
161 }
162 None => true,
163 };
164 if replace {
165 best = Some((candidate, predecessor));
166 }
167 }
168 current_cells.push(match best {
169 Some((total_cost, predecessor)) => LayeredCell {
170 total_cost: Some(total_cost),
171 predecessor: Some(predecessor),
172 },
173 None => LayeredCell {
174 total_cost: None,
175 predecessor: None,
176 },
177 });
178 }
179 table.push(current_cells);
180 }
181 Ok(LayeredCertificate { layers: table })
182}
183
184fn reconstruct<C: FiniteCost>(
185 certificate: &LayeredCertificate<C>,
186) -> Result<(Vec<usize>, C), GraphError> {
187 let last = certificate.layers.last().ok_or_else(|| {
188 GraphError::Unsupported("layered path requires at least one layer".to_owned())
189 })?;
190 let mut endpoint: Option<(usize, &C)> = None;
191 for (index, cell) in last.iter().enumerate() {
192 let Some(cost) = &cell.total_cost else {
193 continue;
194 };
195 let replace = match endpoint {
196 Some((_, best)) => compare(cost, best, "layered endpoint ordering")? == Ordering::Less,
197 None => true,
198 };
199 if replace {
200 endpoint = Some((index, cost));
201 }
202 }
203 let (mut index, total_cost) = endpoint.ok_or(GraphError::Disconnected)?;
204 let mut indices = vec![0; certificate.layers.len()];
205 for layer in (0..certificate.layers.len()).rev() {
206 indices[layer] = index;
207 if layer > 0 {
208 index = certificate.layers[layer][index]
209 .predecessor
210 .ok_or_else(|| {
211 GraphError::CertificateInvalid(
212 "reachable layered cell has no backpointer".to_owned(),
213 )
214 })?;
215 }
216 }
217 Ok((indices, total_cost.clone()))
218}
219
220fn validate_layers<S>(layers: &[Vec<S>]) -> Result<(), GraphError> {
221 if layers.is_empty() {
222 return Err(GraphError::Unsupported(
223 "layered path requires at least one layer".to_owned(),
224 ));
225 }
226 if layers.iter().any(Vec::is_empty) {
227 return Err(GraphError::Disconnected);
228 }
229 layer_cell_count(layers).map(|_| ())
230}
231
232fn layer_cell_count<S>(layers: &[Vec<S>]) -> Result<usize, GraphError> {
233 layers.iter().try_fold(0usize, |total, layer| {
234 total
235 .checked_add(layer.len())
236 .ok_or_else(|| GraphError::WeightOverflow("layer cell count".to_owned()))
237 })
238}
239
240fn verify_receipt<S>(layers: &[Vec<S>], receipt: &AlgorithmReceipt) -> Result<(), GraphError> {
241 let cells = u64::try_from(layer_cell_count(layers)?)
242 .map_err(|_| GraphError::CertificateInvalid("layer cell count exceeds u64".to_owned()))?;
243 let edges = layers.windows(2).try_fold(0u64, |total, pair| {
244 let count = pair[0]
245 .len()
246 .checked_mul(pair[1].len())
247 .and_then(|count| u64::try_from(count).ok())
248 .ok_or_else(|| {
249 GraphError::CertificateInvalid("layer edge count exceeds u64".to_owned())
250 })?;
251 total
252 .checked_add(count)
253 .ok_or_else(|| GraphError::CertificateInvalid("layer edge count overflowed".to_owned()))
254 })?;
255 if receipt.cells != cells
256 || receipt.edges != edges
257 || receipt.peak_memory_cells != layer_cell_count(layers)?
258 {
259 return Err(GraphError::CertificateInvalid(
260 "layered path receipt counts do not match the input".to_owned(),
261 ));
262 }
263 Ok(())
264}
265
266fn charge_cell(meter: &mut Option<&mut WorkMeter<'_>>) -> Result<(), GraphError> {
267 if let Some(meter) = meter.as_deref_mut() {
268 meter.cell()?;
269 }
270 Ok(())
271}
272
273fn charge_edge(meter: &mut Option<&mut WorkMeter<'_>>) -> Result<(), GraphError> {
274 if let Some(meter) = meter.as_deref_mut() {
275 meter.edge()?;
276 }
277 Ok(())
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn staged_path_has_stable_backpointer_certificate() {
286 let layers = vec![vec!['a', 'b'], vec!['c', 'd'], vec!['e']];
287 let path = layered_shortest_path(&layers, |left, right| {
288 if (*left, *right) == ('a', 'd') || (*left, *right) == ('d', 'e') {
289 1_i64
290 } else {
291 5
292 }
293 })
294 .expect("layered path");
295
296 assert_eq!(path.indices, vec![0, 1, 0]);
297 assert_eq!(path.states, vec!['a', 'd', 'e']);
298 assert_eq!(path.total_cost, 2);
299 assert_eq!(path.receipt.cells, 5);
300 assert_eq!(path.receipt.edges, 6);
301 verify_layered_path(
302 &layers,
303 |left, right| {
304 Some(
305 if (*left, *right) == ('a', 'd') || (*left, *right) == ('d', 'e') {
306 1_i64
307 } else {
308 5
309 },
310 )
311 },
312 &path,
313 )
314 .expect("certificate");
315 }
316
317 #[test]
318 fn forbidden_edges_bounds_and_non_finite_costs_fail_closed() {
319 let layers = vec![vec![0, 1], vec![2], vec![3]];
320 let path = layered_shortest_path_with_control(
321 &layers,
322 |left, right| (*left != 0 || *right != 2).then_some(1_i64),
323 &AlgorithmControl::default(),
324 &NeverInterrupt,
325 )
326 .expect("reachable through state one");
327 assert_eq!(path.indices, vec![1, 0, 0]);
328
329 assert!(matches!(
330 layered_shortest_path_with_control(
331 &layers,
332 |_left, _right| Some(1_i64),
333 &AlgorithmControl::default().with_max_work(2),
334 &NeverInterrupt,
335 ),
336 Err(GraphError::ControlStopped(_))
337 ));
338 assert!(matches!(
339 layered_shortest_path(&layers, |_left, _right| f64::INFINITY),
340 Err(GraphError::NonFiniteCost(_))
341 ));
342 }
343
344 #[test]
345 fn tampered_backpointer_fails_verification() {
346 let layers = vec![vec![0, 1], vec![2, 3]];
347 let mut path = layered_shortest_path(&layers, |_left, _right| 1_i64).expect("layered path");
348 path.certificate.layers[1][0].predecessor = Some(1);
349 assert!(matches!(
350 verify_layered_path(&layers, |_left, _right| Some(1_i64), &path),
351 Err(GraphError::CertificateInvalid(_))
352 ));
353 }
354}