1use crate::graph::Graph;
4use std::cmp::Ordering;
5use std::collections::{BinaryHeap, HashMap, HashSet};
6
7struct State {
9 dist: f64,
10 node: usize,
11}
12
13impl PartialEq for State {
14 fn eq(&self, other: &Self) -> bool {
15 self.dist == other.dist
16 }
17}
18impl Eq for State {}
19impl PartialOrd for State {
20 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
21 Some(self.cmp(other))
22 }
23}
24impl Ord for State {
25 fn cmp(&self, other: &Self) -> Ordering {
26 other
28 .dist
29 .partial_cmp(&self.dist)
30 .unwrap_or(Ordering::Equal)
31 }
32}
33
34pub fn dijkstra(graph: &Graph, source: usize) -> Vec<f64> {
37 let mut dist = vec![f64::INFINITY; graph.n_nodes];
38 dist[source] = 0.0;
39 let mut heap = BinaryHeap::new();
40 heap.push(State {
41 dist: 0.0,
42 node: source,
43 });
44 while let Some(State { dist: d, node }) = heap.pop() {
45 if d > dist[node] {
46 continue;
47 }
48 for (next, w) in graph.neighbors(node) {
49 let nd = d + w;
50 if nd < dist[next] {
51 dist[next] = nd;
52 heap.push(State {
53 dist: nd,
54 node: next,
55 });
56 }
57 }
58 }
59 dist
60}
61
62pub fn shortest_path_route(
66 graph: &Graph,
67 source: usize,
68 target: usize,
69) -> Option<(Vec<usize>, Vec<usize>, f64)> {
70 dijkstra_combined(graph, graph, 0.0, source, target).map(|(p, e, c, _)| (p, e, c))
71}
72
73fn shortest_path_blocked(
77 graph: &Graph,
78 source: usize,
79 target: usize,
80 blocked_nodes: &HashSet<usize>,
81 blocked_edges: &HashSet<(usize, usize)>,
82) -> (Vec<usize>, f64) {
83 let mut dist = vec![f64::INFINITY; graph.n_nodes];
84 let mut prev = vec![usize::MAX; graph.n_nodes];
85 dist[source] = 0.0;
86 let mut heap = BinaryHeap::new();
87 heap.push(State {
88 dist: 0.0,
89 node: source,
90 });
91 while let Some(State { dist: d, node }) = heap.pop() {
92 if node == target {
93 break;
94 }
95 if d > dist[node] {
96 continue;
97 }
98 for (next, w) in graph.neighbors(node) {
99 if blocked_nodes.contains(&next) || blocked_edges.contains(&(node, next)) {
100 continue;
101 }
102 let nd = d + w;
103 if nd < dist[next] {
104 dist[next] = nd;
105 prev[next] = node;
106 heap.push(State {
107 dist: nd,
108 node: next,
109 });
110 }
111 }
112 }
113 if dist[target].is_infinite() {
114 return (Vec::new(), f64::INFINITY);
115 }
116 let mut path = vec![target];
117 let mut cur = target;
118 while cur != source {
119 cur = prev[cur];
120 path.push(cur);
121 }
122 path.reverse();
123 (path, dist[target])
124}
125
126fn edge_argmin_map(from: &[usize], to: &[usize], weight: &[f64]) -> HashMap<(usize, usize), usize> {
129 let mut m: HashMap<(usize, usize), usize> = HashMap::new();
130 for i in 0..from.len() {
131 let key = (from[i], to[i]);
132 match m.entry(key) {
133 std::collections::hash_map::Entry::Occupied(mut e) => {
134 if weight[i] < weight[*e.get()] {
135 e.insert(i);
136 }
137 }
138 std::collections::hash_map::Entry::Vacant(e) => {
139 e.insert(i);
140 }
141 }
142 }
143 m
144}
145
146fn path_cost(path: &[usize], amap: &HashMap<(usize, usize), usize>, weight: &[f64]) -> f64 {
147 let mut c = 0.0;
148 for w in path.windows(2) {
149 c += amap
150 .get(&(w[0], w[1]))
151 .map(|&i| weight[i])
152 .unwrap_or(f64::INFINITY);
153 }
154 c
155}
156
157fn path_edges(path: &[usize], amap: &HashMap<(usize, usize), usize>) -> Vec<usize> {
159 path.windows(2)
160 .filter_map(|w| amap.get(&(w[0], w[1])).copied())
161 .collect()
162}
163
164pub struct Route {
167 pub path: Vec<usize>,
168 pub edges: Vec<usize>,
169 pub cost: f64,
170}
171
172pub fn k_shortest_paths(
177 graph: &Graph,
178 from: &[usize],
179 to: &[usize],
180 weight: &[f64],
181 source: usize,
182 target: usize,
183 k: usize,
184) -> Vec<Route> {
185 let amap = edge_argmin_map(from, to, weight);
186 let empty_n: HashSet<usize> = HashSet::new();
187 let empty_e: HashSet<(usize, usize)> = HashSet::new();
188
189 let mut accepted: Vec<(Vec<usize>, f64)> = Vec::new();
190 let (first, first_cost) = shortest_path_blocked(graph, source, target, &empty_n, &empty_e);
191 if first.is_empty() {
192 return Vec::new();
193 }
194 accepted.push((first, first_cost));
195
196 let mut candidates: Vec<(Vec<usize>, f64)> = Vec::new();
197 while accepted.len() < k {
198 let last = &accepted[accepted.len() - 1].0;
199 for i in 0..last.len() - 1 {
200 let spur_node = last[i];
201 let root_path = &last[0..=i];
202
203 let mut blocked_edges: HashSet<(usize, usize)> = HashSet::new();
204 for (p, _) in &accepted {
205 if p.len() > i && &p[0..=i] == root_path {
206 blocked_edges.insert((p[i], p[i + 1]));
207 }
208 }
209 let mut blocked_nodes: HashSet<usize> = HashSet::new();
210 for &n in &root_path[0..root_path.len() - 1] {
211 blocked_nodes.insert(n);
212 }
213
214 let (spur, _) =
215 shortest_path_blocked(graph, spur_node, target, &blocked_nodes, &blocked_edges);
216 if spur.is_empty() {
217 continue;
218 }
219 let mut total = root_path[0..root_path.len() - 1].to_vec();
220 total.extend_from_slice(&spur);
221 let cost = path_cost(&total, &amap, weight);
222 if !candidates.iter().any(|(p, _)| *p == total)
223 && !accepted.iter().any(|(p, _)| *p == total)
224 {
225 candidates.push((total, cost));
226 }
227 }
228 if candidates.is_empty() {
229 break;
230 }
231 candidates.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal));
232 accepted.push(candidates.remove(0));
233 }
234 accepted
235 .into_iter()
236 .map(|(path, cost)| {
237 let edges = path_edges(&path, &amap);
238 Route { path, edges, cost }
239 })
240 .collect()
241}
242
243pub fn distance_matrix(graph: &Graph, sources: &[usize], targets: &[usize]) -> Vec<f64> {
245 let mut out = Vec::with_capacity(sources.len() * targets.len());
246 for &s in sources {
247 let dist = dijkstra(graph, s);
248 for &t in targets {
249 out.push(dist[t]);
250 }
251 }
252 out
253}
254
255#[inline]
257fn arc(cost: &[f64], n: usize, i: usize, j: usize) -> f64 {
258 cost[i * n + j]
259}
260
261fn tour_cost(cost: &[f64], n: usize, tour: &[usize], closed: bool) -> f64 {
264 let mut c = 0.0;
265 for w in tour.windows(2) {
266 c += arc(cost, n, w[0], w[1]);
267 }
268 if closed && tour.len() > 1 {
269 c += arc(cost, n, tour[tour.len() - 1], tour[0]);
270 }
271 c
272}
273
274fn nearest_neighbor_tour(cost: &[f64], n: usize, start: usize, end: Option<usize>) -> Vec<usize> {
277 let mut visited = vec![false; n];
278 let mut tour = Vec::with_capacity(n);
279 tour.push(start);
280 visited[start] = true;
281 let reserved = match end {
282 Some(e) if e != start => {
283 visited[e] = true;
284 1
285 }
286 _ => 0,
287 };
288 while tour.len() < n - reserved {
289 let last = *tour.last().unwrap();
290 let mut best = usize::MAX;
291 let mut best_cost = f64::INFINITY;
292 for (j, &seen) in visited.iter().enumerate() {
293 if !seen && (best == usize::MAX || arc(cost, n, last, j) < best_cost) {
294 best = j;
295 best_cost = arc(cost, n, last, j);
296 }
297 }
298 visited[best] = true;
299 tour.push(best);
300 }
301 if let Some(e) = end {
302 if e != start {
303 tour.push(e);
304 }
305 }
306 tour
307}
308
309fn local_search(cost: &[f64], n: usize, tour: &mut Vec<usize>, closed: bool, fix_end: bool) -> f64 {
316 let len = tour.len();
317 let mut best_cost = tour_cost(cost, n, tour, closed);
318 if len < 3 {
319 return best_cost;
320 }
321 let tail = if fix_end { 1 } else { 0 };
322 loop {
323 let mut improved = false;
324 for seg_len in 1..=3usize.min(len - 2) {
326 if 1 + seg_len + tail > len {
327 break;
328 }
329 for i in 1..=(len - seg_len - tail) {
330 let segment: Vec<usize> = tour[i..i + seg_len].to_vec();
331 let mut rest: Vec<usize> = Vec::with_capacity(len - seg_len);
332 rest.extend_from_slice(&tour[..i]);
333 rest.extend_from_slice(&tour[i + seg_len..]);
334 let last_slot = rest.len() - tail;
335 for j in 1..=last_slot {
336 for reversed in [false, true] {
337 if reversed && seg_len == 1 {
338 continue;
339 }
340 let mut cand = Vec::with_capacity(len);
341 cand.extend_from_slice(&rest[..j]);
342 if reversed {
343 cand.extend(segment.iter().rev());
344 } else {
345 cand.extend_from_slice(&segment);
346 }
347 cand.extend_from_slice(&rest[j..]);
348 let c = tour_cost(cost, n, &cand, closed);
349 if c < best_cost {
350 best_cost = c;
351 *tour = cand;
352 improved = true;
353 }
354 }
355 }
356 }
357 }
358 let hi = len - tail;
360 for i in 1..hi {
361 for j in (i + 1)..hi {
362 let mut cand = tour.clone();
363 cand.swap(i, j);
364 let c = tour_cost(cost, n, &cand, closed);
365 if c < best_cost {
366 best_cost = c;
367 *tour = cand;
368 improved = true;
369 }
370 let mut cand = tour.clone();
371 cand[i..=j].reverse();
372 let c = tour_cost(cost, n, &cand, closed);
373 if c < best_cost {
374 best_cost = c;
375 *tour = cand;
376 improved = true;
377 }
378 }
379 }
380 if !improved {
381 break;
382 }
383 }
384 best_cost
385}
386
387struct Rng(u64);
390
391impl Rng {
392 fn next(&mut self) -> u64 {
393 let mut x = self.0;
394 x ^= x << 13;
395 x ^= x >> 7;
396 x ^= x << 17;
397 self.0 = x;
398 x
399 }
400
401 fn range(&mut self, lo: usize, hi: usize) -> usize {
403 lo + (self.next() % (hi - lo) as u64) as usize
404 }
405}
406
407fn double_bridge(rng: &mut Rng, tour: &[usize]) -> Vec<usize> {
412 let len = tour.len();
413 if len < 4 {
414 return tour.to_vec();
415 }
416 let mut cuts = [rng.range(1, len), rng.range(1, len), rng.range(1, len)];
417 cuts.sort_unstable();
418 let [a, b, c] = cuts;
419 if a == b || b == c {
420 return tour.to_vec();
421 }
422 let mut out = Vec::with_capacity(len);
423 out.extend_from_slice(&tour[..a]);
424 out.extend_from_slice(&tour[b..c]);
425 out.extend_from_slice(&tour[a..b]);
426 out.extend_from_slice(&tour[c..]);
427 out
428}
429
430pub fn tsp_tour(
438 cost: &[f64],
439 n: usize,
440 start: usize,
441 end: Option<usize>,
442 closed: bool,
443 improve: bool,
444) -> Vec<usize> {
445 if n == 0 {
446 return Vec::new();
447 }
448 if n == 1 {
449 return vec![start];
450 }
451 let cost: Vec<f64> = cost
455 .iter()
456 .map(|&c| if c.is_finite() { c } else { 1e15 })
457 .collect();
458 let end = if closed { None } else { end };
459 let mut tour = nearest_neighbor_tour(&cost, n, start, end);
460 if !improve {
461 return tour;
462 }
463 let fix_end = end.is_some();
464 let mut best_cost = local_search(&cost, n, &mut tour, closed, fix_end);
465
466 let kicks = (100_000_000 / (n * n * n).max(1)).clamp(25, 400);
469 let patience = (kicks / 4).max(20);
470 let mut rng = Rng(0x9E3779B97F4A7C15);
471 let mut since_improved = 0usize;
472 for _ in 0..kicks {
473 let mut cand = double_bridge(&mut rng, &tour);
474 let c = local_search(&cost, n, &mut cand, closed, fix_end);
475 if c < best_cost {
476 best_cost = c;
477 tour = cand;
478 since_improved = 0;
479 } else {
480 since_improved += 1;
481 if since_improved >= patience {
482 break;
483 }
484 }
485 }
486 tour
487}
488
489pub struct ParetoRoute {
493 pub path: Vec<usize>,
494 pub edges: Vec<usize>,
495 pub cost: f64,
496 pub resource: f64,
497}
498
499struct Label {
504 node: usize,
505 edge: usize,
506 pred: usize,
507}
508
509pub fn csr_edge_order(from: &[usize], n_nodes: usize) -> Vec<usize> {
513 let m = from.len();
514 let mut degree = vec![0usize; n_nodes + 1];
515 for &u in from {
516 degree[u + 1] += 1;
517 }
518 for i in 0..n_nodes {
519 degree[i + 1] += degree[i];
520 }
521 let mut cursor = degree;
522 let mut order = vec![0usize; m];
523 for e in 0..m {
524 let pos = cursor[from[e]];
525 order[pos] = e;
526 cursor[from[e]] += 1;
527 }
528 order
529}
530
531struct BiState {
533 cost: f64,
534 resource: f64,
535 label: usize,
536}
537
538impl PartialEq for BiState {
539 fn eq(&self, other: &Self) -> bool {
540 self.cost == other.cost && self.resource == other.resource
541 }
542}
543impl Eq for BiState {}
544impl PartialOrd for BiState {
545 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
546 Some(self.cmp(other))
547 }
548}
549impl Ord for BiState {
550 fn cmp(&self, other: &Self) -> Ordering {
551 other
553 .cost
554 .partial_cmp(&self.cost)
555 .unwrap_or(Ordering::Equal)
556 .then(
557 other
558 .resource
559 .partial_cmp(&self.resource)
560 .unwrap_or(Ordering::Equal),
561 )
562 }
563}
564
565#[allow(clippy::too_many_arguments)] pub fn pareto_paths(
581 from: &[usize],
582 to: &[usize],
583 w_cost: &[f64],
584 w_res: &[f64],
585 n_nodes: usize,
586 source: usize,
587 target: usize,
588 budget: f64,
589 max_routes: usize,
590) -> Vec<ParetoRoute> {
591 if max_routes == 0 {
592 return Vec::new();
593 }
594 if source == target {
595 return vec![ParetoRoute {
596 path: vec![source],
597 edges: Vec::new(),
598 cost: 0.0,
599 resource: 0.0,
600 }];
601 }
602 let g_cost = Graph::from_edges(from, to, w_cost, n_nodes);
606 let g_res = Graph::from_edges(from, to, w_res, n_nodes);
607
608 let rev_res = Graph::from_edges(to, from, w_res, n_nodes);
611 let lb_res = dijkstra(&rev_res, target);
612 let edge_order = csr_edge_order(from, n_nodes);
613
614 let mut arena: Vec<Label> = Vec::new();
615 let mut heap: BinaryHeap<BiState> = BinaryHeap::new();
616 let mut min_res = vec![f64::INFINITY; n_nodes];
620 let mut routes: Vec<ParetoRoute> = Vec::new();
621
622 if lb_res[source].is_finite() && lb_res[source] <= budget {
623 arena.push(Label {
624 node: source,
625 edge: usize::MAX,
626 pred: usize::MAX,
627 });
628 heap.push(BiState {
629 cost: 0.0,
630 resource: 0.0,
631 label: 0,
632 });
633 }
634
635 while let Some(BiState {
636 cost,
637 resource,
638 label,
639 }) = heap.pop()
640 {
641 let node = arena[label].node;
642 if min_res[node] <= resource {
643 continue; }
645 min_res[node] = resource;
646 if node == target {
647 let mut path = Vec::new();
648 let mut edges = Vec::new();
649 let mut cur = label;
650 while cur != usize::MAX {
651 path.push(arena[cur].node);
652 if arena[cur].edge != usize::MAX {
653 edges.push(edge_order[arena[cur].edge]);
654 }
655 cur = arena[cur].pred;
656 }
657 path.reverse();
658 edges.reverse();
659 routes.push(ParetoRoute {
660 path,
661 edges,
662 cost,
663 resource,
664 });
665 if routes.len() >= max_routes {
666 break;
667 }
668 continue;
669 }
670 let start = g_cost.offsets[node];
671 let end = g_cost.offsets[node + 1];
672 for e in start..end {
673 let next = g_cost.targets[e];
674 let nc = cost + g_cost.weights[e];
675 let nr = resource + g_res.weights[e];
676 if !lb_res[next].is_finite()
679 || nr + lb_res[next] > budget
680 || min_res[next] <= nr
681 || min_res[target] <= nr + lb_res[next]
682 {
683 continue;
684 }
685 arena.push(Label {
686 node: next,
687 edge: e,
688 pred: label,
689 });
690 heap.push(BiState {
691 cost: nc,
692 resource: nr,
693 label: arena.len() - 1,
694 });
695 }
696 }
697 routes
698}
699
700pub struct LagrangianRoute {
705 pub path: Vec<usize>,
706 pub edges: Vec<usize>,
707 pub cost: f64,
708 pub resource: f64,
709 pub lower_bound: f64,
710 pub lambda: f64,
711 pub iterations: usize,
712}
713
714fn dijkstra_combined(
720 g1: &Graph,
721 g2: &Graph,
722 lambda: f64,
723 source: usize,
724 target: usize,
725) -> Option<(Vec<usize>, Vec<usize>, f64, f64)> {
726 let n = g1.n_nodes;
727 let mut dist = vec![f64::INFINITY; n];
728 let mut pred_node = vec![usize::MAX; n];
729 let mut pred_edge = vec![usize::MAX; n];
730 dist[source] = 0.0;
731 let mut heap = BinaryHeap::new();
732 heap.push(State {
733 dist: 0.0,
734 node: source,
735 });
736 while let Some(State { dist: d, node }) = heap.pop() {
737 if node == target {
738 break;
739 }
740 if d > dist[node] {
741 continue;
742 }
743 for e in g1.offsets[node]..g1.offsets[node + 1] {
744 let next = g1.targets[e];
745 let nd = d + g1.weights[e] + lambda * g2.weights[e];
746 if nd < dist[next] {
747 dist[next] = nd;
748 pred_node[next] = node;
749 pred_edge[next] = e;
750 heap.push(State {
751 dist: nd,
752 node: next,
753 });
754 }
755 }
756 }
757 if dist[target].is_infinite() {
758 return None;
759 }
760 let mut path = vec![target];
761 let mut edges = Vec::new();
762 let (mut w1, mut w2) = (0.0, 0.0);
763 let mut cur = target;
764 while cur != source {
765 let e = pred_edge[cur];
766 edges.push(e);
767 w1 += g1.weights[e];
768 w2 += g2.weights[e];
769 cur = pred_node[cur];
770 path.push(cur);
771 }
772 path.reverse();
773 edges.reverse();
774 Some((path, edges, w1, w2))
775}
776
777#[allow(clippy::too_many_arguments)] pub fn csp_lagrangian(
792 from: &[usize],
793 to: &[usize],
794 w_cost: &[f64],
795 w_res: &[f64],
796 n_nodes: usize,
797 source: usize,
798 target: usize,
799 budget: f64,
800 max_iter: usize,
801) -> Option<LagrangianRoute> {
802 if budget < 0.0 {
803 return None;
804 }
805 if source == target {
806 return Some(LagrangianRoute {
807 path: vec![source],
808 edges: Vec::new(),
809 cost: 0.0,
810 resource: 0.0,
811 lower_bound: 0.0,
812 lambda: 0.0,
813 iterations: 0,
814 });
815 }
816 let g_cost = Graph::from_edges(from, to, w_cost, n_nodes);
817 let g_res = Graph::from_edges(from, to, w_res, n_nodes);
818 let edge_order = csr_edge_order(from, n_nodes);
819 let to_input =
820 |slots: Vec<usize>| -> Vec<usize> { slots.into_iter().map(|s| edge_order[s]).collect() };
821
822 let (p0, e0, c0, r0) = dijkstra_combined(&g_cost, &g_res, 0.0, source, target)?;
824 if r0 <= budget {
825 return Some(LagrangianRoute {
826 path: p0,
827 edges: to_input(e0),
828 cost: c0,
829 resource: r0,
830 lower_bound: c0,
831 lambda: 0.0,
832 iterations: 1,
833 });
834 }
835 let (pr, er, rmin, c_of_rmin) = dijkstra_combined(&g_res, &g_cost, 0.0, source, target)?;
838 if rmin > budget {
839 return None;
840 }
841
842 let mut inf = (p0, e0, c0, r0); let mut feas = (pr, er, c_of_rmin, rmin); let mut best = feas.clone(); let mut best_lb = c0; let mut best_lambda = 0.0;
847 let mut iterations = 2usize;
848
849 for _ in 0..max_iter {
850 let lam = (feas.2 - inf.2) / (inf.3 - feas.3);
852 if !lam.is_finite() || lam <= 0.0 {
853 break;
854 }
855 let (p, e, c, r) = match dijkstra_combined(&g_cost, &g_res, lam, source, target) {
856 Some(x) => x,
857 None => break,
858 };
859 iterations += 1;
860 let lb = c + lam * (r - budget); if lb > best_lb {
862 best_lb = lb;
863 best_lambda = lam;
864 }
865 let tie = inf.2 + lam * inf.3;
867 if (c + lam * r) >= tie - 1e-9 * (1.0 + tie.abs()) {
868 break;
869 }
870 if r <= budget {
871 feas = (p, e, c, r);
872 if feas.2 < best.2 {
873 best = feas.clone();
874 }
875 } else {
876 inf = (p, e, c, r);
877 }
878 }
879
880 let lower_bound = best_lb.min(best.2); Some(LagrangianRoute {
882 path: best.0,
883 edges: to_input(best.1),
884 cost: best.2,
885 resource: best.3,
886 lower_bound,
887 lambda: best_lambda,
888 iterations,
889 })
890}
891
892#[cfg(test)]
893mod tests {
894 use super::*;
895
896 fn line_graph() -> Graph {
897 Graph::from_edges(&[0, 1, 2, 0], &[1, 2, 3, 3], &[1.0, 1.0, 1.0, 10.0], 4)
899 }
900
901 #[test]
902 fn dijkstra_distances() {
903 let g = line_graph();
904 let d = dijkstra(&g, 0);
905 assert_eq!(d, vec![0.0, 1.0, 2.0, 3.0]);
906 }
907
908 #[test]
909 fn path_prefers_shorter_route() {
910 let g = line_graph();
911 let (path, slots, cost) = shortest_path_route(&g, 0, 3).unwrap();
912 assert_eq!(path, vec![0, 1, 2, 3]);
913 assert_eq!(slots.len(), 3);
914 assert!((cost - 3.0).abs() < 1e-9);
915 let order = csr_edge_order(&[0, 1, 2, 0], 4);
917 let edges: Vec<usize> = slots.iter().map(|&s| order[s]).collect();
918 assert_eq!(edges, vec![0, 1, 2]);
919 }
920
921 #[test]
922 fn unreachable_is_none() {
923 let g = Graph::from_edges(&[0], &[1], &[1.0], 3);
924 assert!(shortest_path_route(&g, 0, 2).is_none());
925 }
926
927 #[test]
928 fn shortest_route_traverses_the_cheaper_parallel_edge() {
929 let from = vec![0, 0, 1];
931 let to = vec![1, 1, 2];
932 let w = vec![5.0, 1.0, 1.0];
933 let g = Graph::from_edges(&from, &to, &w, 3);
934 let (path, slots, cost) = shortest_path_route(&g, 0, 2).unwrap();
935 assert_eq!(path, vec![0, 1, 2]);
936 assert!((cost - 2.0).abs() < 1e-9);
937 let order = csr_edge_order(&from, 3);
938 let edges: Vec<usize> = slots.iter().map(|&s| order[s]).collect();
939 assert_eq!(edges, vec![1, 2]);
940 }
941
942 #[test]
943 fn yen_finds_two_distinct_paths() {
944 let from = vec![0, 1, 0, 2];
946 let to = vec![1, 3, 2, 3];
947 let w = vec![1.0, 1.0, 2.0, 2.0];
948 let g = Graph::from_edges(&from, &to, &w, 4);
949 let paths = k_shortest_paths(&g, &from, &to, &w, 0, 3, 2);
950 assert_eq!(paths.len(), 2);
951 assert_eq!(paths[0].path, vec![0, 1, 3]);
952 assert_eq!(paths[0].edges, vec![0, 1]);
953 assert!((paths[0].cost - 2.0).abs() < 1e-9);
954 assert_eq!(paths[1].path, vec![0, 2, 3]);
955 assert_eq!(paths[1].edges, vec![2, 3]);
956 assert!((paths[1].cost - 4.0).abs() < 1e-9);
957 }
958
959 #[test]
960 fn yen_caps_at_available_paths() {
961 let from = vec![0, 1];
962 let to = vec![1, 2];
963 let w = vec![1.0, 1.0];
964 let g = Graph::from_edges(&from, &to, &w, 3);
965 let paths = k_shortest_paths(&g, &from, &to, &w, 0, 2, 5);
966 assert_eq!(paths.len(), 1);
967 }
968
969 #[test]
970 fn distance_matrix_shape_and_values() {
971 let g = line_graph();
972 let dm = distance_matrix(&g, &[0, 1], &[2, 3]);
973 assert_eq!(dm, vec![2.0, 3.0, 1.0, 2.0]);
974 }
975
976 fn line_costs() -> Vec<f64> {
978 let xs: [f64; 4] = [0.0, 1.0, 2.0, 10.0];
979 let mut c = Vec::with_capacity(16);
980 for i in 0..4 {
981 for j in 0..4 {
982 c.push((xs[i] - xs[j]).abs());
983 }
984 }
985 c
986 }
987
988 #[test]
989 fn tsp_closed_tour_is_a_permutation_returning_home() {
990 let c = line_costs();
991 let tour = tsp_tour(&c, 4, 0, None, true, true);
992 let mut sorted = tour.clone();
993 sorted.sort_unstable();
994 assert_eq!(sorted, vec![0, 1, 2, 3]);
995 assert_eq!(tour[0], 0);
996 assert!((tour_cost(&c, 4, &tour, true) - 20.0).abs() < 1e-9);
998 }
999
1000 #[test]
1001 fn tsp_open_route_respects_fixed_end() {
1002 let c = line_costs();
1003 let tour = tsp_tour(&c, 4, 3, Some(0), false, true);
1004 assert_eq!(tour[0], 3);
1005 assert_eq!(tour[3], 0);
1006 assert!((tour_cost(&c, 4, &tour, false) - 10.0).abs() < 1e-9);
1008 }
1009
1010 #[test]
1011 fn or_opt_fixes_a_greedy_mistake() {
1012 let c = vec![
1017 0.0, 1.0, 2.0, 50.0, 1.0, 0.0, 100.0, 2.0, 2.0, 100.0, 0.0, 1.0, 50.0, 3.0, 100.0, 0.0,
1021 ];
1022 let nn = tsp_tour(&c, 4, 0, None, false, false);
1023 let improved = tsp_tour(&c, 4, 0, None, false, true);
1024 assert_eq!(nn, vec![0, 1, 3, 2]);
1025 assert!((tour_cost(&c, 4, &nn, false) - 103.0).abs() < 1e-9);
1026 assert_eq!(improved, vec![0, 2, 3, 1]);
1027 assert!((tour_cost(&c, 4, &improved, false) - 6.0).abs() < 1e-9);
1028 }
1029
1030 #[test]
1031 fn tsp_handles_asymmetric_costs() {
1032 let c = vec![0.0, 1.0, 10.0, 10.0, 0.0, 1.0, 1.0, 10.0, 0.0];
1036 let tour = tsp_tour(&c, 3, 0, None, true, true);
1037 assert_eq!(tour, vec![0, 1, 2]);
1038 }
1039
1040 fn brute_force(cost: &[f64], n: usize, closed: bool) -> f64 {
1043 fn go(
1044 cost: &[f64],
1045 n: usize,
1046 tour: &mut Vec<usize>,
1047 rest: &mut Vec<usize>,
1048 closed: bool,
1049 best: &mut f64,
1050 ) {
1051 if rest.is_empty() {
1052 let c = tour_cost(cost, n, tour, closed);
1053 if c < *best {
1054 *best = c;
1055 }
1056 return;
1057 }
1058 for i in 0..rest.len() {
1059 let x = rest.remove(i);
1060 tour.push(x);
1061 go(cost, n, tour, rest, closed, best);
1062 tour.pop();
1063 rest.insert(i, x);
1064 }
1065 }
1066 let mut best = f64::INFINITY;
1067 let mut tour = vec![0usize];
1068 let mut rest: Vec<usize> = (1..n).collect();
1069 go(cost, n, &mut tour, &mut rest, closed, &mut best);
1070 best
1071 }
1072
1073 #[test]
1074 fn ils_matches_brute_force_on_random_asymmetric_instances() {
1075 let n = 7;
1076 for seed in 1..=8u64 {
1077 let mut rng = Rng(seed);
1078 let mut cost = vec![0.0; n * n];
1079 for i in 0..n {
1080 for j in 0..n {
1081 if i != j {
1082 cost[i * n + j] = (rng.next() % 1000) as f64 + 1.0;
1083 }
1084 }
1085 }
1086 for closed in [true, false] {
1087 let tour = tsp_tour(&cost, n, 0, None, closed, true);
1088 let got = tour_cost(&cost, n, &tour, closed);
1089 let best = brute_force(&cost, n, closed);
1090 assert!(
1091 (got - best).abs() < 1e-9,
1092 "seed {seed} closed {closed}: got {got}, optimum {best}"
1093 );
1094 }
1095 }
1096 }
1097
1098 #[test]
1099 fn ils_improves_on_nearest_neighbor_at_fifty_stops() {
1100 let n = 50;
1101 let mut rng = Rng(7);
1102 let mut cost = vec![0.0; n * n];
1103 for i in 0..n {
1104 for j in 0..n {
1105 if i != j {
1106 cost[i * n + j] = (rng.next() % 1000) as f64 + 1.0;
1107 }
1108 }
1109 }
1110 let nn = tsp_tour(&cost, n, 0, None, true, false);
1111 let ils = tsp_tour(&cost, n, 0, None, true, true);
1112 assert!(tour_cost(&cost, n, &ils, true) < tour_cost(&cost, n, &nn, true));
1113 }
1114
1115 fn diamond() -> (Vec<usize>, Vec<usize>, Vec<f64>, Vec<f64>) {
1118 let from = vec![0, 1, 0, 2, 0];
1119 let to = vec![1, 3, 2, 3, 3];
1120 let cost = vec![1.0, 1.0, 3.0, 3.0, 10.0];
1121 let res = vec![5.0, 5.0, 0.0, 0.0, 2.0];
1122 (from, to, cost, res)
1123 }
1124
1125 #[test]
1126 fn pareto_front_on_diamond() {
1127 let (from, to, cost, res) = diamond();
1128 let routes = pareto_paths(&from, &to, &cost, &res, 4, 0, 3, f64::INFINITY, usize::MAX);
1129 let vals: Vec<(f64, f64)> = routes.iter().map(|r| (r.cost, r.resource)).collect();
1130 assert_eq!(vals, vec![(2.0, 10.0), (6.0, 0.0)]);
1131 assert_eq!(routes[0].path, vec![0, 1, 3]);
1132 assert_eq!(routes[1].path, vec![0, 2, 3]);
1133 }
1134
1135 #[test]
1136 fn budget_picks_the_cheapest_feasible_route() {
1137 let (from, to, cost, res) = diamond();
1138 let routes = pareto_paths(&from, &to, &cost, &res, 4, 0, 3, 5.0, 1);
1140 assert_eq!(routes.len(), 1);
1141 assert_eq!(routes[0].path, vec![0, 2, 3]);
1142 let routes = pareto_paths(&from, &to, &cost, &res, 4, 0, 3, 2.0, usize::MAX);
1145 let vals: Vec<(f64, f64)> = routes.iter().map(|r| (r.cost, r.resource)).collect();
1146 assert_eq!(vals, vec![(6.0, 0.0)]);
1147 }
1148
1149 #[test]
1150 fn infeasible_budget_and_unreachable_target_are_empty() {
1151 let (from, to, cost, res) = diamond();
1152 assert!(pareto_paths(&from, &to, &cost, &res, 4, 0, 3, -1.0, usize::MAX).is_empty());
1153 assert!(
1155 pareto_paths(&from, &to, &cost, &res, 5, 0, 4, f64::INFINITY, usize::MAX).is_empty()
1156 );
1157 }
1158
1159 #[test]
1160 fn pareto_trivial_and_capped() {
1161 let (from, to, cost, res) = diamond();
1162 let same = pareto_paths(&from, &to, &cost, &res, 4, 2, 2, f64::INFINITY, usize::MAX);
1163 assert_eq!(same.len(), 1);
1164 assert_eq!(same[0].path, vec![2]);
1165 let capped = pareto_paths(&from, &to, &cost, &res, 4, 0, 3, f64::INFINITY, 1);
1166 assert_eq!(capped.len(), 1);
1167 assert_eq!(capped[0].path, vec![0, 1, 3]);
1168 }
1169
1170 fn enumerate_values(
1172 from: &[usize],
1173 to: &[usize],
1174 cost: &[f64],
1175 res: &[f64],
1176 n: usize,
1177 s: usize,
1178 t: usize,
1179 ) -> Vec<(f64, f64)> {
1180 fn go(
1181 edges: &[(usize, usize, f64, f64)],
1182 t: usize,
1183 node: usize,
1184 seen: &mut Vec<bool>,
1185 c: f64,
1186 r: f64,
1187 out: &mut Vec<(f64, f64)>,
1188 ) {
1189 if node == t {
1190 out.push((c, r));
1191 return;
1192 }
1193 for &(u, v, wc, wr) in edges {
1194 if u == node && !seen[v] {
1195 seen[v] = true;
1196 go(edges, t, v, seen, c + wc, r + wr, out);
1197 seen[v] = false;
1198 }
1199 }
1200 }
1201 let edges: Vec<(usize, usize, f64, f64)> = (0..from.len())
1202 .map(|i| (from[i], to[i], cost[i], res[i]))
1203 .collect();
1204 let mut seen = vec![false; n];
1205 seen[s] = true;
1206 let mut out = Vec::new();
1207 go(&edges, t, s, &mut seen, 0.0, 0.0, &mut out);
1208 out
1209 }
1210
1211 fn pareto_filter(mut vals: Vec<(f64, f64)>) -> Vec<(f64, f64)> {
1214 vals.sort_by(|a, b| {
1215 a.0.partial_cmp(&b.0)
1216 .unwrap()
1217 .then(a.1.partial_cmp(&b.1).unwrap())
1218 });
1219 let mut front: Vec<(f64, f64)> = Vec::new();
1220 for (c, r) in vals {
1221 if front.iter().all(|&(fc, fr)| !(fc <= c && fr <= r)) {
1222 front.push((c, r));
1223 }
1224 }
1225 front
1226 }
1227
1228 #[test]
1229 fn pareto_matches_brute_force_on_random_graphs() {
1230 for seed in 1..=12u64 {
1231 let mut rng = Rng(seed);
1232 let n = 6usize;
1233 let mut from = Vec::new();
1234 let mut to = Vec::new();
1235 let mut cost = Vec::new();
1236 let mut res = Vec::new();
1237 for u in 0..n {
1238 for v in 0..n {
1239 if u != v && rng.next() % 100 < 45 {
1240 from.push(u);
1241 to.push(v);
1242 cost.push((rng.next() % 20) as f64 + 1.0);
1243 res.push(if rng.next() % 2 == 0 {
1245 0.0
1246 } else {
1247 (rng.next() % 10) as f64
1248 });
1249 }
1250 }
1251 }
1252 let all = enumerate_values(&from, &to, &cost, &res, n, 0, n - 1);
1253 let want = pareto_filter(all.clone());
1254 let got: Vec<(f64, f64)> = pareto_paths(
1255 &from,
1256 &to,
1257 &cost,
1258 &res,
1259 n,
1260 0,
1261 n - 1,
1262 f64::INFINITY,
1263 usize::MAX,
1264 )
1265 .iter()
1266 .map(|r| (r.cost, r.resource))
1267 .collect();
1268 assert_eq!(got, want, "seed {seed}: front mismatch");
1269
1270 for budget in [0.0, 3.0, 7.0, 15.0] {
1272 let best = all
1273 .iter()
1274 .filter(|&&(_, r)| r <= budget)
1275 .map(|&(c, _)| c)
1276 .fold(f64::INFINITY, f64::min);
1277 let solved = pareto_paths(&from, &to, &cost, &res, n, 0, n - 1, budget, 1);
1278 match solved.first() {
1279 Some(r) => {
1280 assert!(
1281 (r.cost - best).abs() < 1e-9 && r.resource <= budget,
1282 "seed {seed} budget {budget}: got ({}, {}), optimum {best}",
1283 r.cost,
1284 r.resource
1285 );
1286 }
1287 None => assert!(
1288 best.is_infinite(),
1289 "seed {seed} budget {budget}: solver found nothing, optimum {best}"
1290 ),
1291 }
1292 }
1293 }
1294 }
1295
1296 #[test]
1297 fn lagrangian_shortcuts_when_unconstrained_is_feasible() {
1298 let (from, to, cost, res) = diamond();
1299 let r = csp_lagrangian(&from, &to, &cost, &res, 4, 0, 3, 100.0, 50).unwrap();
1300 assert_eq!(r.path, vec![0, 1, 3]);
1301 assert_eq!((r.cost, r.resource, r.lower_bound), (2.0, 10.0, 2.0));
1302 assert_eq!(r.iterations, 1);
1303 }
1304
1305 #[test]
1306 fn lagrangian_finds_the_optimum_with_a_duality_gap() {
1307 let (from, to, cost, res) = diamond();
1308 let r = csp_lagrangian(&from, &to, &cost, &res, 4, 0, 3, 5.0, 50).unwrap();
1311 assert_eq!(r.path, vec![0, 2, 3]);
1312 assert_eq!((r.cost, r.resource), (6.0, 0.0));
1313 assert!(r.lower_bound <= 6.0 && r.lower_bound >= 2.0);
1314 assert!((r.lambda - 0.4).abs() < 1e-9);
1315 }
1316
1317 #[test]
1318 fn lagrangian_infeasible_and_unreachable_are_none() {
1319 let (from, to, cost, res) = diamond();
1320 assert!(csp_lagrangian(&from, &to, &cost, &res, 4, 0, 1, 1.0, 50).is_none());
1322 assert!(csp_lagrangian(&from, &to, &cost, &res, 5, 0, 4, 100.0, 50).is_none());
1323 assert!(csp_lagrangian(&from, &to, &cost, &res, 4, 0, 3, -1.0, 50).is_none());
1324 }
1325
1326 #[test]
1327 fn lagrangian_is_sandwiched_by_the_exact_solver_on_random_graphs() {
1328 for seed in 1..=12u64 {
1329 let mut rng = Rng(seed * 7 + 1);
1330 let n = 7usize;
1331 let mut from = Vec::new();
1332 let mut to = Vec::new();
1333 let mut cost = Vec::new();
1334 let mut res = Vec::new();
1335 for u in 0..n {
1336 for v in 0..n {
1337 if u != v && rng.next() % 100 < 40 {
1338 from.push(u);
1339 to.push(v);
1340 cost.push((rng.next() % 20) as f64 + 1.0);
1341 res.push(if rng.next() % 2 == 0 {
1342 0.0
1343 } else {
1344 (rng.next() % 10) as f64
1345 });
1346 }
1347 }
1348 }
1349 for budget in [0.0, 2.0, 5.0, 9.0, 20.0] {
1350 let exact = pareto_paths(&from, &to, &cost, &res, n, 0, n - 1, budget, 1);
1351 let lagr = csp_lagrangian(&from, &to, &cost, &res, n, 0, n - 1, budget, 50);
1352 match (exact.first(), lagr) {
1353 (Some(e), Some(l)) => {
1354 assert!(
1355 l.resource <= budget,
1356 "seed {seed} budget {budget}: infeasible Lagrangian answer"
1357 );
1358 assert!(
1359 l.lower_bound <= e.cost + 1e-9 && e.cost <= l.cost + 1e-9,
1360 "seed {seed} budget {budget}: lb {} / exact {} / lagr {}",
1361 l.lower_bound,
1362 e.cost,
1363 l.cost
1364 );
1365 }
1366 (None, None) => {}
1367 (e, l) => panic!(
1368 "seed {seed} budget {budget}: feasibility disagreement (exact {:?}, lagr {:?})",
1369 e.map(|x| x.cost),
1370 l.map(|x| x.cost)
1371 ),
1372 }
1373 }
1374 }
1375 }
1376
1377 #[test]
1378 fn pareto_paths_are_consistent_with_their_values() {
1379 let (from, to, cost, res) = diamond();
1380 let amap = edge_argmin_map(&from, &to, &cost);
1381 for r in pareto_paths(&from, &to, &cost, &res, 4, 0, 3, f64::INFINITY, usize::MAX) {
1382 assert!((path_cost(&r.path, &amap, &cost) - r.cost).abs() < 1e-9);
1383 }
1384 }
1385
1386 #[test]
1387 fn returned_edges_are_the_traversed_ones() {
1388 let from = vec![0, 0, 1];
1392 let to = vec![1, 1, 2];
1393 let cost = vec![1.0, 5.0, 1.0];
1394 let res = vec![5.0, 0.0, 0.0];
1395 let routes = pareto_paths(&from, &to, &cost, &res, 3, 0, 2, f64::INFINITY, usize::MAX);
1396 let vals: Vec<(f64, f64)> = routes.iter().map(|r| (r.cost, r.resource)).collect();
1397 assert_eq!(vals, vec![(2.0, 5.0), (6.0, 0.0)]);
1398 assert_eq!(routes[0].edges, vec![0, 2]);
1399 assert_eq!(routes[1].edges, vec![1, 2]);
1400 let l = csp_lagrangian(&from, &to, &cost, &res, 3, 0, 2, 0.0, 50).unwrap();
1401 assert_eq!(l.edges, vec![1, 2]);
1402 assert_eq!((l.cost, l.resource), (6.0, 0.0));
1403 }
1404
1405 #[test]
1406 fn returned_edges_align_with_paths_and_values() {
1407 let (from, to, cost, res) = diamond();
1408 let check = |path: &[usize], edges: &[usize], want_c: f64, want_r: f64| {
1409 assert_eq!(edges.len() + 1, path.len());
1410 let (mut c, mut r) = (0.0, 0.0);
1411 for (i, &e) in edges.iter().enumerate() {
1412 assert_eq!(from[e], path[i]);
1413 assert_eq!(to[e], path[i + 1]);
1414 c += cost[e];
1415 r += res[e];
1416 }
1417 assert!((c - want_c).abs() < 1e-9 && (r - want_r).abs() < 1e-9);
1418 };
1419 for rt in pareto_paths(&from, &to, &cost, &res, 4, 0, 3, f64::INFINITY, usize::MAX) {
1420 check(&rt.path, &rt.edges, rt.cost, rt.resource);
1421 }
1422 let l = csp_lagrangian(&from, &to, &cost, &res, 4, 0, 3, 5.0, 50).unwrap();
1423 check(&l.path, &l.edges, l.cost, l.resource);
1424 }
1425
1426 #[test]
1427 fn tsp_trivial_sizes() {
1428 assert_eq!(tsp_tour(&[0.0], 1, 0, None, true, true), vec![0]);
1429 let c = vec![0.0, 2.0, 3.0, 0.0];
1430 assert_eq!(tsp_tour(&c, 2, 1, None, false, true), vec![1, 0]);
1431 }
1432}