1#![allow(clippy::needless_range_loop)]
34
35const NONE: usize = usize::MAX;
36
37pub const CAP_INF: i64 = i64::MAX;
42
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub enum NetworkSimplexStatus {
46 Optimal,
48 Infeasible,
51 Unbounded,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
63pub struct NetworkSimplexIntSolution {
64 pub status: NetworkSimplexStatus,
66 pub cost: i64,
68 pub flows: Vec<i64>,
70}
71
72#[cfg(any(test, feature = "bench-internals"))]
75#[doc(hidden)]
76#[derive(Clone, Copy, Debug)]
77pub enum NetworkSimplexCycleArm {
78 Allocating,
80 ReusedScratch,
82}
83
84#[cfg(test)]
85#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
86struct CycleTrace {
87 pivots: usize,
88 path_calls: usize,
89 path_hops: usize,
90}
91
92#[cfg(test)]
93thread_local! {
94 static CYCLE_TRACE: std::cell::Cell<CycleTrace> =
95 const { std::cell::Cell::new(CycleTrace { pivots: 0, path_calls: 0, path_hops: 0 }) };
96}
97
98#[cfg(test)]
99fn record_path_trace(hops: usize) {
100 CYCLE_TRACE.with(|trace| {
101 let mut current = trace.get();
102 current.path_calls += 1;
103 current.path_hops += hops;
104 trace.set(current);
105 });
106}
107
108struct Simplex {
109 edge_sources: Vec<usize>,
111 edge_targets: Vec<usize>,
112 edge_capacities: Vec<i64>,
113 edge_weights: Vec<i64>,
114 edge_flow: Vec<i64>,
115 node_potentials: Vec<i64>,
117 parent: Vec<usize>,
119 parent_edge: Vec<usize>,
120 subtree_size: Vec<usize>,
121 next_node_dft: Vec<usize>,
122 prev_node_dft: Vec<usize>,
123 last_descendent_dft: Vec<usize>,
124}
125
126#[derive(Default)]
127struct CycleScratch {
128 nodes: Vec<usize>,
129 edges: Vec<usize>,
130 right_nodes: Vec<usize>,
131 right_edges: Vec<usize>,
132}
133
134impl Simplex {
135 #[inline]
136 fn reduced_cost(&self, i: usize) -> i64 {
137 let c = self.edge_weights[i] - self.node_potentials[self.edge_sources[i]]
138 + self.node_potentials[self.edge_targets[i]];
139 if self.edge_flow[i] == 0 { c } else { -c }
140 }
141
142 #[inline]
143 fn residual_capacity(&self, i: usize, p: usize) -> i64 {
144 if self.edge_sources[i] == p {
145 self.edge_capacities[i] - self.edge_flow[i]
146 } else {
147 self.edge_flow[i]
148 }
149 }
150
151 fn find_apex(&self, mut p: usize, mut q: usize) -> usize {
152 let mut size_p = self.subtree_size[p];
153 let mut size_q = self.subtree_size[q];
154 loop {
155 while size_p < size_q {
156 p = self.parent[p];
157 size_p = self.subtree_size[p];
158 }
159 while size_p > size_q {
160 q = self.parent[q];
161 size_q = self.subtree_size[q];
162 }
163 if size_p == size_q {
164 if p != q {
165 p = self.parent[p];
166 size_p = self.subtree_size[p];
167 q = self.parent[q];
168 size_q = self.subtree_size[q];
169 } else {
170 return p;
171 }
172 }
173 }
174 }
175
176 #[cfg(any(test, feature = "bench-internals"))]
178 fn trace_path(&self, mut p: usize, w: usize) -> (Vec<usize>, Vec<usize>) {
179 let mut wn = vec![p];
180 let mut we = Vec::new();
181 while p != w {
182 we.push(self.parent_edge[p]);
183 p = self.parent[p];
184 wn.push(p);
185 }
186 #[cfg(test)]
187 record_path_trace(we.len());
188 (wn, we)
189 }
190
191 #[cfg(any(test, feature = "bench-internals"))]
193 fn find_cycle(&self, i: usize, p: usize, q: usize) -> (Vec<usize>, Vec<usize>) {
194 let w = self.find_apex(p, q);
195 let (mut wn, mut we) = self.trace_path(p, w);
196 wn.reverse();
197 we.reverse();
198 if we.len() != 1 || we[0] != i {
199 we.push(i);
200 }
201 let (mut wnr, wer) = self.trace_path(q, w);
202 wnr.pop(); wn.extend(wnr);
204 we.extend(wer);
205 (wn, we)
206 }
207
208 fn trace_path_into(
209 &self,
210 mut p: usize,
211 w: usize,
212 nodes: &mut Vec<usize>,
213 edges: &mut Vec<usize>,
214 ) {
215 nodes.clear();
216 edges.clear();
217 nodes.push(p);
218 while p != w {
219 edges.push(self.parent_edge[p]);
220 p = self.parent[p];
221 nodes.push(p);
222 }
223 #[cfg(test)]
224 record_path_trace(edges.len());
225 }
226
227 fn find_cycle_into(&self, i: usize, p: usize, q: usize, scratch: &mut CycleScratch) {
230 let w = self.find_apex(p, q);
231 self.trace_path_into(p, w, &mut scratch.nodes, &mut scratch.edges);
232 scratch.nodes.reverse();
233 scratch.edges.reverse();
234 if scratch.edges.len() != 1 || scratch.edges[0] != i {
235 scratch.edges.push(i);
236 }
237 self.trace_path_into(q, w, &mut scratch.right_nodes, &mut scratch.right_edges);
238 scratch.right_nodes.pop(); scratch.nodes.extend_from_slice(&scratch.right_nodes);
240 scratch.edges.extend_from_slice(&scratch.right_edges);
241 }
242
243 fn augment_flow(&mut self, wn: &[usize], we: &[usize], f: i64) {
244 for (idx, &i) in we.iter().enumerate() {
245 let p = wn[idx];
246 if self.edge_sources[i] == p {
247 self.edge_flow[i] += f;
248 } else {
249 self.edge_flow[i] -= f;
250 }
251 }
252 }
253
254 fn find_leaving_edge(&self, wn: &[usize], we: &[usize]) -> (usize, usize, usize) {
258 let mut best_j = we[we.len() - 1];
259 let mut best_s = wn[wn.len() - 1];
260 let mut best_rc = self.residual_capacity(best_j, best_s);
261 for k in (0..we.len()).rev() {
263 let j = we[k];
264 let s = wn[k];
265 let rc = self.residual_capacity(j, s);
266 if rc < best_rc {
267 best_rc = rc;
268 best_j = j;
269 best_s = s;
270 }
271 }
272 let t = if self.edge_sources[best_j] == best_s {
273 self.edge_targets[best_j]
274 } else {
275 self.edge_sources[best_j]
276 };
277 (best_j, best_s, t)
278 }
279
280 #[inline]
281 fn process_cycle(&mut self, i: usize, mut p: usize, mut q: usize, wn: &[usize], we: &[usize]) {
282 let (j, mut s, mut t) = self.find_leaving_edge(wn, we);
283 let rc = self.residual_capacity(j, s);
284 self.augment_flow(wn, we, rc);
285 if i != j {
286 if self.parent[t] != s {
287 std::mem::swap(&mut s, &mut t);
288 }
289 let pos_i = we
291 .iter()
292 .position(|&edge| edge == i)
293 .expect("fundamental cycle must contain its entering edge");
294 let pos_j = we
295 .iter()
296 .position(|&edge| edge == j)
297 .expect("fundamental cycle must contain its leaving edge");
298 if pos_i > pos_j {
299 std::mem::swap(&mut p, &mut q);
300 }
301 self.remove_edge(s, t);
302 self.make_root(q);
303 self.add_edge(i, p, q);
304 self.update_potentials(i, p, q);
305 }
306 }
307
308 fn remove_edge(&mut self, s: usize, t: usize) {
309 let size_t = self.subtree_size[t];
310 let prev_t = self.prev_node_dft[t];
311 let last_t = self.last_descendent_dft[t];
312 let next_last_t = self.next_node_dft[last_t];
313 self.parent[t] = NONE;
314 self.parent_edge[t] = NONE;
315 self.next_node_dft[prev_t] = next_last_t;
316 self.prev_node_dft[next_last_t] = prev_t;
317 self.next_node_dft[last_t] = t;
318 self.prev_node_dft[t] = last_t;
319 let mut s = s;
320 while s != NONE {
321 self.subtree_size[s] -= size_t;
322 if self.last_descendent_dft[s] == last_t {
323 self.last_descendent_dft[s] = prev_t;
324 }
325 s = self.parent[s];
326 }
327 }
328
329 fn make_root(&mut self, q: usize) {
330 let mut ancestors = Vec::new();
331 let mut cur = q;
332 while cur != NONE {
333 ancestors.push(cur);
334 cur = self.parent[cur];
335 }
336 ancestors.reverse();
337 for w in 0..ancestors.len().saturating_sub(1) {
338 let p = ancestors[w];
339 let q = ancestors[w + 1];
340 let size_p = self.subtree_size[p];
341 let mut last_p = self.last_descendent_dft[p];
342 let prev_q = self.prev_node_dft[q];
343 let last_q = self.last_descendent_dft[q];
344 let next_last_q = self.next_node_dft[last_q];
345 self.parent[p] = q;
346 self.parent[q] = NONE;
347 self.parent_edge[p] = self.parent_edge[q];
348 self.parent_edge[q] = NONE;
349 self.subtree_size[p] = size_p - self.subtree_size[q];
350 self.subtree_size[q] = size_p;
351 self.next_node_dft[prev_q] = next_last_q;
352 self.prev_node_dft[next_last_q] = prev_q;
353 self.next_node_dft[last_q] = q;
354 self.prev_node_dft[q] = last_q;
355 if last_p == last_q {
356 self.last_descendent_dft[p] = prev_q;
357 last_p = prev_q;
358 }
359 self.prev_node_dft[p] = last_q;
360 self.next_node_dft[last_q] = p;
361 self.next_node_dft[last_p] = q;
362 self.prev_node_dft[q] = last_p;
363 self.last_descendent_dft[q] = last_p;
364 }
365 }
366
367 fn add_edge(&mut self, i: usize, p: usize, q: usize) {
368 let last_p = self.last_descendent_dft[p];
369 let next_last_p = self.next_node_dft[last_p];
370 let size_q = self.subtree_size[q];
371 let last_q = self.last_descendent_dft[q];
372 self.parent[q] = p;
373 self.parent_edge[q] = i;
374 self.next_node_dft[last_p] = q;
375 self.prev_node_dft[q] = last_p;
376 self.prev_node_dft[next_last_p] = last_q;
377 self.next_node_dft[last_q] = next_last_p;
378 let mut p = p;
379 while p != NONE {
380 self.subtree_size[p] += size_q;
381 if self.last_descendent_dft[p] == last_p {
382 self.last_descendent_dft[p] = last_q;
383 }
384 p = self.parent[p];
385 }
386 }
387
388 fn update_potentials(&mut self, i: usize, p: usize, q: usize) {
389 let d = if q == self.edge_targets[i] {
390 self.node_potentials[p] - self.edge_weights[i] - self.node_potentials[q]
391 } else {
392 self.node_potentials[p] + self.edge_weights[i] - self.node_potentials[q]
393 };
394 let l = self.last_descendent_dft[q];
396 let mut cur = q;
397 loop {
398 self.node_potentials[cur] += d;
399 if cur == l {
400 break;
401 }
402 cur = self.next_node_dft[cur];
403 }
404 }
405}
406
407fn solve_impl<const REUSE_CYCLE_SCRATCH: bool>(
411 node_demands: &[i64],
412 src: &[usize],
413 tgt: &[usize],
414 cap: &[i64], wt: &[i64],
416) -> (NetworkSimplexStatus, i64, Vec<i64>) {
417 let n = node_demands.len();
418 let edge_count = src.len();
419
420 if n == 0 {
421 return (NetworkSimplexStatus::Optimal, 0, Vec::new());
422 }
423
424 let inf = i64::MAX;
425
426 let mut sum_cap: i64 = 0;
428 for &c in cap {
429 if c < inf {
430 sum_cap += c;
431 }
432 }
433 let mut sum_w: i64 = 0;
434 for &w in wt {
435 sum_w += w.abs();
436 }
437 let mut sum_d: i64 = 0;
438 for &d in node_demands {
439 sum_d += d.abs();
440 }
441 let faux_inf = {
442 let m = sum_cap.max(sum_w).max(sum_d);
443 if m == 0 { 1 } else { 3 * m }
444 };
445
446 let total = edge_count + n;
448 let mut edge_sources = Vec::with_capacity(total);
449 let mut edge_targets = Vec::with_capacity(total);
450 let mut edge_capacities = Vec::with_capacity(total);
451 let mut edge_weights = Vec::with_capacity(total);
452 edge_sources.extend_from_slice(src);
453 edge_targets.extend_from_slice(tgt);
454 edge_capacities.extend_from_slice(cap);
455 edge_weights.extend_from_slice(wt);
456 for (i, &d) in node_demands.iter().enumerate() {
458 if d > 0 {
459 edge_sources.push(n); edge_targets.push(i);
461 } else {
462 edge_sources.push(i);
463 edge_targets.push(n); }
465 }
466 edge_weights.extend(std::iter::repeat_n(faux_inf, n));
467 edge_capacities.extend(std::iter::repeat_n(faux_inf, n));
468
469 let mut edge_flow = vec![0i64; edge_count];
471 edge_flow.extend(node_demands.iter().map(|d| d.abs()));
472 let node_potentials: Vec<i64> = node_demands
473 .iter()
474 .map(|&d| if d <= 0 { faux_inf } else { -faux_inf })
475 .collect();
476 let mut parent = vec![n; n + 1];
478 parent[n] = NONE;
479 let mut parent_edge: Vec<usize> = (edge_count..edge_count + n).collect();
481 parent_edge.push(NONE); let mut subtree_size = vec![1usize; n + 1];
484 subtree_size[n] = n + 1;
485 let mut next_node_dft = vec![0usize; n + 1];
487 for i in 0..n.saturating_sub(1) {
488 next_node_dft[i] = i + 1;
489 }
490 if n >= 1 {
491 next_node_dft[n - 1] = n; next_node_dft[n] = 0;
493 }
494 let mut prev_node_dft = vec![0usize; n + 1];
496 prev_node_dft[0] = n; for i in 1..=n {
498 prev_node_dft[i] = i - 1;
499 }
500 let mut last_descendent_dft = vec![0usize; n + 1];
502 for i in 0..n {
503 last_descendent_dft[i] = i;
504 }
505 last_descendent_dft[n] = n - 1;
506
507 let mut sx = Simplex {
508 edge_sources,
509 edge_targets,
510 edge_capacities,
511 edge_weights,
512 edge_flow,
513 node_potentials,
514 parent,
515 parent_edge,
516 subtree_size,
517 next_node_dft,
518 prev_node_dft,
519 last_descendent_dft,
520 };
521
522 if edge_count > 0 {
524 let b = {
525 let s = (edge_count as f64).sqrt().ceil() as usize;
527 s.max(1)
528 };
529 let big_m = edge_count.div_ceil(b);
530 let mut m = 0usize; let mut f = 0usize; let mut cycle_scratch = CycleScratch::default();
533 while m < big_m {
534 let l = f + b;
536 let mut best_i = usize::MAX;
538 let mut best_c = 0i64;
539 if l <= edge_count {
540 for e in f..l {
541 let c = sx.reduced_cost(e);
542 if best_i == usize::MAX || c < best_c {
543 best_c = c;
544 best_i = e;
545 }
546 }
547 f = l;
548 } else {
549 let l2 = l - edge_count;
550 for e in f..edge_count {
551 let c = sx.reduced_cost(e);
552 if best_i == usize::MAX || c < best_c {
553 best_c = c;
554 best_i = e;
555 }
556 }
557 for e in 0..l2 {
558 let c = sx.reduced_cost(e);
559 if best_i == usize::MAX || c < best_c {
560 best_c = c;
561 best_i = e;
562 }
563 }
564 f = l2;
565 }
566 let i = best_i;
567 let c = sx.reduced_cost(i);
568 if c >= 0 {
569 m += 1;
570 continue;
571 }
572 let (p, q) = if sx.edge_flow[i] == 0 {
574 (sx.edge_sources[i], sx.edge_targets[i])
575 } else {
576 (sx.edge_targets[i], sx.edge_sources[i])
577 };
578 m = 0;
579 #[cfg(test)]
581 CYCLE_TRACE.with(|trace| {
582 let mut current = trace.get();
583 current.pivots += 1;
584 trace.set(current);
585 });
586 if REUSE_CYCLE_SCRATCH {
587 sx.find_cycle_into(i, p, q, &mut cycle_scratch);
588 sx.process_cycle(i, p, q, &cycle_scratch.nodes, &cycle_scratch.edges);
589 } else {
590 #[cfg(any(test, feature = "bench-internals"))]
591 {
592 let (wn, we) = sx.find_cycle(i, p, q);
593 sx.process_cycle(i, p, q, &wn, &we);
594 }
595 #[cfg(not(any(test, feature = "bench-internals")))]
596 unreachable!("allocating cycle baseline is benchmark-only");
597 }
598 }
599 }
600
601 for i in edge_count..edge_count + n {
604 if sx.edge_flow[i] != 0 {
605 return (NetworkSimplexStatus::Infeasible, 0, Vec::new());
606 }
607 }
608 for i in 0..edge_count {
610 if sx.edge_flow[i].saturating_mul(2) >= faux_inf {
611 return (NetworkSimplexStatus::Unbounded, 0, Vec::new());
612 }
613 }
614
615 let mut cost: i64 = 0;
616 for i in 0..edge_count {
617 cost += sx.edge_weights[i] * sx.edge_flow[i];
618 }
619 let flows: Vec<i64> = sx.edge_flow[..edge_count].to_vec();
620 (NetworkSimplexStatus::Optimal, cost, flows)
621}
622
623fn solve(
624 node_demands: &[i64],
625 src: &[usize],
626 tgt: &[usize],
627 cap: &[i64],
628 wt: &[i64],
629) -> (NetworkSimplexStatus, i64, Vec<i64>) {
630 solve_impl::<true>(node_demands, src, tgt, cap, wt)
631}
632
633#[cfg(any(test, feature = "bench-internals"))]
635#[doc(hidden)]
636pub fn network_simplex_int_cycle_arm(
637 node_demands: &[i64],
638 src: &[usize],
639 tgt: &[usize],
640 cap: &[i64],
641 wt: &[i64],
642 arm: NetworkSimplexCycleArm,
643) -> NetworkSimplexIntSolution {
644 let (status, cost, flows) = match arm {
645 NetworkSimplexCycleArm::Allocating => solve_impl::<false>(node_demands, src, tgt, cap, wt),
646 NetworkSimplexCycleArm::ReusedScratch => {
647 solve_impl::<true>(node_demands, src, tgt, cap, wt)
648 }
649 };
650 NetworkSimplexIntSolution {
651 status,
652 cost,
653 flows,
654 }
655}
656
657pub fn network_simplex_int(
705 node_demands: &[i64],
706 src: &[usize],
707 tgt: &[usize],
708 cap: &[i64],
709 wt: &[i64],
710) -> NetworkSimplexIntSolution {
711 let (status, cost, flows) = solve(node_demands, src, tgt, cap, wt);
712 NetworkSimplexIntSolution {
713 status,
714 cost,
715 flows,
716 }
717}
718
719#[cfg(test)]
720mod tests {
721 use super::*;
722
723 type ProblemArrays = ([i64; 4], [usize; 4], [usize; 4], [i64; 4], [i64; 4]);
726
727 fn small_problem() -> ProblemArrays {
736 let demands = [-4i64, 0, 0, 4];
737 let src = [0usize, 0, 1, 2];
738 let tgt = [1usize, 2, 3, 3];
739 let cap = [4i64, 4, 2, 4];
740 let wt = [1i64, 2, 1, 1];
741 (demands, src, tgt, cap, wt)
742 }
743
744 type OwnedProblem = (Vec<i64>, Vec<usize>, Vec<usize>, Vec<i64>, Vec<i64>);
745
746 fn assignment_problem(side: usize) -> OwnedProblem {
747 const OFFSETS: [usize; 4] = [0, 1, 7, 19];
748
749 let mut demands = vec![-1; side];
750 demands.extend(std::iter::repeat_n(1, side));
751 let mut src = Vec::with_capacity(side * OFFSETS.len());
752 let mut tgt = Vec::with_capacity(side * OFFSETS.len());
753 let mut cap = Vec::with_capacity(side * OFFSETS.len());
754 let mut wt = Vec::with_capacity(side * OFFSETS.len());
755 for supplier in 0..side {
756 for (lane, offset) in OFFSETS.into_iter().enumerate() {
757 let sink = (supplier + offset) % side;
758 src.push(supplier);
759 tgt.push(side + sink);
760 cap.push(1);
761 wt.push(((supplier * 17 + sink * 13 + lane * 5) % 11) as i64 - 5);
762 }
763 }
764 (demands, src, tgt, cap, wt)
765 }
766
767 fn solve_with_trace<const REUSE: bool>(
768 problem: &OwnedProblem,
769 ) -> ((NetworkSimplexStatus, i64, Vec<i64>), CycleTrace) {
770 let (demands, src, tgt, cap, wt) = problem;
771 CYCLE_TRACE.with(|trace| trace.set(CycleTrace::default()));
772 let result = solve_impl::<REUSE>(demands, src, tgt, cap, wt);
773 let trace = CYCLE_TRACE.with(std::cell::Cell::get);
774 (result, trace)
775 }
776
777 fn assert_cycle_scratch_parity(problem: &OwnedProblem) -> CycleTrace {
778 let (allocating, allocating_trace) = solve_with_trace::<false>(problem);
779 let (reused, reused_trace) = solve_with_trace::<true>(problem);
780 assert_eq!(
781 reused, allocating,
782 "scratch reuse must preserve status, cost, and input-order flows"
783 );
784 assert_eq!(
785 reused_trace, allocating_trace,
786 "scratch reuse must preserve pivot and path traversal counts"
787 );
788 reused_trace
789 }
790
791 #[test]
792 fn cycle_scratch_matches_allocating_baseline() {
793 let (d, s, t, c, w) = small_problem();
794 assert_cycle_scratch_parity(&(d.to_vec(), s.to_vec(), t.to_vec(), c.to_vec(), w.to_vec()));
795 assert_cycle_scratch_parity(&(vec![-4, 4], vec![0], vec![1], vec![2], vec![1]));
796 assert_cycle_scratch_parity(&(
797 vec![-1, 0, 0, 1],
798 vec![0, 1, 0, 2],
799 vec![1, 3, 2, 3],
800 vec![CAP_INF; 4],
801 vec![1, 5, 2, 1],
802 ));
803 assert_cycle_scratch_parity(&(Vec::new(), Vec::new(), Vec::new(), Vec::new(), Vec::new()));
804 assert_cycle_scratch_parity(&assignment_problem(32));
805 assert_eq!(
806 assert_cycle_scratch_parity(&assignment_problem(64)),
807 CycleTrace {
808 pivots: 140,
809 path_calls: 280,
810 path_hops: 719,
811 },
812 "the resurrected fixture must retain its pre-edit operation profile"
813 );
814 }
815
816 #[test]
817 fn known_integer_optimum() {
818 let (d, s, t, c, w) = small_problem();
819 let sol = network_simplex_int(&d, &s, &t, &c, &w);
820 assert_eq!(sol.status, NetworkSimplexStatus::Optimal);
821 assert_eq!(sol.cost, 10, "exact integer optimum cost");
822 assert_eq!(sol.flows, vec![2, 2, 2, 2]);
824 let recomputed: i64 = w.iter().zip(&sol.flows).map(|(wi, fi)| wi * fi).sum();
826 assert_eq!(recomputed, sol.cost);
827 }
828
829 #[test]
830 fn determinism_across_repeated_runs() {
831 let (d, s, t, c, w) = small_problem();
832 let first = network_simplex_int(&d, &s, &t, &c, &w);
833 for run in 0..64 {
834 let again = network_simplex_int(&d, &s, &t, &c, &w);
835 assert_eq!(again, first, "run {run} diverged from the first solve");
836 }
837 }
838
839 #[test]
840 fn infeasible_when_capacity_insufficient() {
841 let demands = [-4i64, 4];
843 let src = [0usize];
844 let tgt = [1usize];
845 let cap = [2i64];
846 let wt = [1i64];
847 let sol = network_simplex_int(&demands, &src, &tgt, &cap, &wt);
848 assert_eq!(sol.status, NetworkSimplexStatus::Infeasible);
849 assert_eq!(sol.cost, 0);
850 assert!(sol.flows.is_empty());
851 }
852
853 #[test]
854 fn empty_graph_is_trivially_optimal() {
855 let sol = network_simplex_int(&[], &[], &[], &[], &[]);
856 assert_eq!(sol.status, NetworkSimplexStatus::Optimal);
857 assert_eq!(sol.cost, 0);
858 assert!(sol.flows.is_empty());
859 }
860
861 #[test]
862 fn infinite_capacity_shortest_path() {
863 let demands = [-1i64, 0, 0, 1];
867 let src = [0usize, 1, 0, 2];
868 let tgt = [1usize, 3, 2, 3];
869 let cap = [CAP_INF, CAP_INF, CAP_INF, CAP_INF];
870 let wt = [1i64, 5, 2, 1];
871 let sol = network_simplex_int(&demands, &src, &tgt, &cap, &wt);
872 assert_eq!(sol.status, NetworkSimplexStatus::Optimal);
873 assert_eq!(sol.cost, 3, "0->2->3 exact shortest path cost");
874 assert_eq!(sol.flows, vec![0, 0, 1, 1]);
876 }
877}