Skip to main content

netoptim_rs/
lib.rs

1//! Network optimization algorithms in Rust.
2
3/// Dijkstra's shortest path algorithm implementation.
4pub mod dijkstra;
5
6/// Error types for network optimization.
7pub mod error;
8
9/// Negative cycle detection using Howard's algorithm.
10pub mod neg_cycle;
11
12/// Maximum parametric optimization.
13pub mod parametric;
14
15/// Oracle for parametric network problems (cutting-plane / feasibility).
16pub mod network_oracle;
17
18/// Oracle for optimal matrix scaling.
19pub mod optscaling_oracle;
20
21/// Minimum cost-to-time cycle ratio solver.
22pub mod min_cycle_ratio;
23
24/// Graph utility functions.
25pub mod utils;
26
27pub use error::NetOptimError;
28pub use utils::*;
29
30#[cfg(test)]
31mod integration_tests;
32
33// Logging module - only available with std feature
34#[cfg(feature = "std")]
35pub mod logging;
36
37use petgraph::prelude::*;
38
39use petgraph::algo::{FloatMeasure, NegativeCycle};
40use petgraph::visit::{
41    IntoEdges, IntoNodeIdentifiers, NodeCount, NodeIndexable, VisitMap, Visitable,
42};
43
44/// Result of shortest path algorithms.
45///
46/// Contains the distances from the source node to all other nodes,
47/// and the predecessor of each node along the shortest path.
48#[derive(Debug, Clone)]
49pub struct Paths<NodeId, EdgeWeight> {
50    pub distances: Vec<EdgeWeight>,
51    pub predecessors: Vec<Option<NodeId>>,
52}
53
54/// \[Generic\] Compute shortest paths from node `source` to all other.
55///
56/// $$ \text{dist}^{(k+1)}\[v\] = \min\left(\text{dist}^{(k)}\[v\], \min_{(u,v) \in E} (\text{dist}^{(k)}\[u\] + w(u,v))\right) $$
57///
58/// Using the [Bellman–Ford algorithm][bf]; negative edge costs are
59/// permitted, but the graph must not have a cycle of negative weights
60/// (in that case it will return an error).
61///
62/// On success, return one vec with path costs, and another one which points
63/// out the predecessor of a node along a shortest path. The vectors
64/// are indexed by the graph's node indices.
65///
66/// [bf]: https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
67///
68/// # Example
69/// ```rust
70/// use petgraph::Graph;
71/// use petgraph::algo::bellman_ford;
72/// use petgraph::prelude::*;
73///
74/// let mut g = Graph::new();
75/// let a = g.add_node(()); // node with no weight
76/// let b = g.add_node(());
77/// let c = g.add_node(());
78/// let d = g.add_node(());
79/// let edge = g.add_node(());
80/// let f = g.add_node(());
81/// g.extend_with_edges(&[
82///     (0, 1, 2.0),
83///     (0, 3, 4.0),
84///     (1, 2, 1.0),
85///     (1, 5, 7.0),
86///     (2, 4, 5.0),
87///     (4, 5, 1.0),
88///     (3, 4, 1.0),
89/// ]);
90///
91/// // Graph represented with the weight of each edge
92/// //
93/// //     2       1
94/// // a ----- b ----- c
95/// // | 4     | 7     |
96/// // d       f       | 5
97/// // | 1     | 1     |
98/// // \------ edge ------/
99///
100/// let path = bellman_ford(&g, a);
101/// assert!(path.is_ok());
102/// let path = path.unwrap();
103/// assert_eq!(path.distances, vec![    0.0,     2.0,    3.0,    4.0,     5.0,     6.0]);
104/// assert_eq!(path.predecessors, vec![None, Some(a),Some(b),Some(a), Some(d), Some(edge)]);
105///
106/// // Node f (indice 5) can be reach from a with a path costing 6.
107/// // Predecessor of f is Some(edge) which predecessor is Some(d) which predecessor is Some(a).
108/// // Thus the path from a to f is a <-> d <-> edge <-> f
109///
110/// let graph_with_neg_cycle = Graph::<(), f32, Undirected>::from_edges(&[
111///         (0, 1, -2.0),
112///         (0, 3, -4.0),
113///         (1, 2, -1.0),
114///         (1, 5, -25.0),
115///         (2, 4, -5.0),
116///         (4, 5, -25.0),
117///         (3, 4, -1.0),
118/// ]);
119///
120/// assert!(bellman_ford(&graph_with_neg_cycle, NodeIndex::new(0)).is_err());
121/// ```
122pub fn bellman_ford<G>(
123    g: G,
124    source: G::NodeId,
125) -> Result<Paths<G::NodeId, G::EdgeWeight>, NegativeCycle>
126where
127    G: NodeCount + IntoNodeIdentifiers + IntoEdges + NodeIndexable,
128    G::EdgeWeight: FloatMeasure,
129{
130    let ix = |i| g.to_index(i);
131
132    // Step 1 and Step 2: initialize and relax
133    let (distances, predecessors) = bellman_ford_initialize_relax(g, source);
134
135    // Step 3: check for negative weight cycle
136    for i in g.node_identifiers() {
137        for edge in g.edges(i) {
138            let j = edge.target();
139            let w = *edge.weight();
140            if distances[ix(i)] + w < distances[ix(j)] {
141                return Err(NegativeCycle(()));
142            }
143        }
144    }
145
146    Ok(Paths {
147        distances,
148        predecessors,
149    })
150}
151
152/// \[Generic\] Find the path of a negative cycle reachable from node `source`.
153///
154/// $$ \text{A cycle } C \text{ is negative iff } \sum_{(u,v) \in C} w(u,v) < 0 $$
155///
156/// Using the [find_negative_cycle][nc]; will search the Graph for negative cycles using
157/// [Bellman–Ford algorithm][bf]. If no negative cycle is found the function will return `None`.
158///
159/// If a negative cycle is found from source, return one vec with a path of `NodeId`s.
160///
161/// The time complexity of this algorithm should be the same as the Bellman-Ford (O(|V|·|E|)).
162///
163/// [nc]: https://blogs.asarkar.com/assets/docs/algorithms-curated/Negative-Weight%20Cycle%20Algorithms%20-%20Huang.pdf
164/// [bf]: https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm
165///
166/// # Example
167/// ```rust
168/// use petgraph::Graph;
169/// use petgraph::algo::find_negative_cycle;
170/// use petgraph::prelude::*;
171///
172/// let graph_with_neg_cycle = Graph::<(), f32, Directed>::from_edges(&[
173///         (0, 1, 1.),
174///         (0, 2, 1.),
175///         (0, 3, 1.),
176///         (1, 3, 1.),
177///         (2, 1, 1.),
178///         (3, 2, -3.),
179/// ]);
180///
181/// let path = find_negative_cycle(&graph_with_neg_cycle, NodeIndex::new(0));
182/// assert_eq!(
183///     path,
184///     Some([NodeIndex::new(1), NodeIndex::new(3), NodeIndex::new(2)].to_vec())
185/// );
186/// ```
187/// # Example: Graph with no negative cycle
188/// ```rust
189/// use petgraph::Graph;
190/// use petgraph::algo::find_negative_cycle;
191/// use petgraph::prelude::*;
192///
193/// let graph = Graph::<(), f32, Directed>::from_edges(&[
194///     (0, 1, 1.0),
195///     (1, 2, 1.0),
196///     (2, 3, 1.0),
197/// ]);
198/// let result = find_negative_cycle(&graph, NodeIndex::new(0));
199/// assert!(result.is_none());
200/// ```
201pub fn find_negative_cycle<G>(g: G, source: G::NodeId) -> Option<Vec<G::NodeId>>
202where
203    G: NodeCount + IntoNodeIdentifiers + IntoEdges + NodeIndexable + Visitable,
204    G::EdgeWeight: FloatMeasure,
205{
206    let ix = |i| g.to_index(i);
207    let mut path = Vec::<G::NodeId>::new();
208
209    // Step 1: initialize and relax
210    let (distance, predecessor) = bellman_ford_initialize_relax(g, source);
211
212    // Step 2: Check for negative weight cycle
213    'outer: for i in g.node_identifiers() {
214        for edge in g.edges(i) {
215            let j = edge.target();
216            let w = *edge.weight();
217            if distance[ix(i)] + w < distance[ix(j)] {
218                // Step 3: negative cycle found
219                let start = j;
220                let mut node = start;
221                let mut visited = g.visit_map();
222                // Go backward in the predecessor chain
223                loop {
224                    let ancestor = match predecessor[ix(node)] {
225                        Some(predecessor_node) => predecessor_node,
226                        None => node, // no predecessor, self cycle
227                    };
228                    // We have only 2 ways to find the cycle and break the loop:
229                    // 1. start is reached
230                    if ancestor == start {
231                        path.push(ancestor);
232                        break;
233                    }
234                    // 2. some node was reached twice
235                    else if visited.is_visited(&ancestor) {
236                        // Drop any node in path that is before the first ancestor
237                        let pos = path
238                            .iter()
239                            .position(|&p| p == ancestor)
240                            .expect("we should always have a position");
241                        path = path[pos..path.len()].to_vec();
242
243                        break;
244                    }
245
246                    // None of the above, some middle path node
247                    path.push(ancestor);
248                    visited.visit(ancestor);
249                    node = ancestor;
250                }
251                // We are done here
252                break 'outer;
253            }
254        }
255    }
256    if !path.is_empty() {
257        // Users will probably need to follow the path of the negative cycle
258        // so it should be in the reverse order than it was found by the algorithm.
259        path.reverse();
260        Some(path)
261    } else {
262        None
263    }
264}
265
266// Perform Step 1 and Step 2 of the Bellman-Ford algorithm.
267#[inline(always)]
268fn bellman_ford_initialize_relax<G>(
269    g: G,
270    source: G::NodeId,
271) -> (Vec<G::EdgeWeight>, Vec<Option<G::NodeId>>)
272where
273    G: NodeCount + IntoNodeIdentifiers + IntoEdges + NodeIndexable,
274    G::EdgeWeight: FloatMeasure,
275{
276    // Step 1: initialize graph
277    let mut predecessor = vec![None; g.node_bound()];
278    let mut distance = vec![<_>::infinite(); g.node_bound()];
279    let ix = |i| g.to_index(i);
280    distance[ix(source)] = <_>::zero();
281
282    // Step 2: relax edges repeatedly
283    for _ in 1..g.node_count() {
284        let mut did_update = false;
285        for i in g.node_identifiers() {
286            for edge in g.edges(i) {
287                let j = edge.target();
288                let w = *edge.weight();
289                if distance[ix(i)] + w < distance[ix(j)] {
290                    distance[ix(j)] = distance[ix(i)] + w;
291                    predecessor[ix(j)] = Some(i);
292                    did_update = true;
293                }
294            }
295        }
296        if !did_update {
297            break;
298        }
299    }
300    (distance, predecessor)
301}
302
303#[cfg(test)]
304mod tests {
305    use super::*;
306    use petgraph::Graph;
307
308    #[test]
309    fn test_bellman_ford_negative_cycle() {
310        let graph_with_neg_cycle =
311            Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 0, -3.0)]);
312        let result = bellman_ford(&graph_with_neg_cycle, NodeIndex::new(0));
313        assert!(result.is_err());
314    }
315
316    #[test]
317    fn test_bellman_ford_no_edges() {
318        let mut graph = Graph::<(), f32, Directed>::new();
319        let n0 = graph.add_node(());
320        let result = bellman_ford(&graph, n0);
321        assert!(result.is_ok());
322        let paths = result.unwrap();
323        assert_eq!(paths.distances, vec![0.0]);
324        assert_eq!(paths.predecessors, vec![None]);
325    }
326
327    #[test]
328    fn test_bellman_ford_disconnected_components() {
329        let mut graph = Graph::<(), f32, Directed>::new();
330        let n0 = graph.add_node(());
331        let n1 = graph.add_node(());
332        let n2 = graph.add_node(());
333        graph.add_edge(n0, n1, 1.0);
334
335        let result = bellman_ford(&graph, n0);
336        assert!(result.is_ok());
337        let paths = result.unwrap();
338        // Node 2 is unreachable, so its distance should be infinite
339        assert_eq!(paths.distances.len(), 3);
340        assert_eq!(paths.distances[n0.index()], 0.0);
341        assert_eq!(paths.distances[n1.index()], 1.0);
342        assert!(paths.distances[n2.index()].is_infinite());
343        assert_eq!(paths.predecessors, vec![None, Some(n0), None]);
344    }
345
346    #[test]
347    fn test_find_negative_cycle_exists() {
348        let graph_with_neg_cycle =
349            Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 0, -3.0)]);
350        let result = find_negative_cycle(&graph_with_neg_cycle, NodeIndex::new(0));
351        assert!(result.is_some());
352        let cycle = result.unwrap();
353        assert_eq!(cycle.len(), 3);
354        assert!(cycle.contains(&NodeIndex::new(0)));
355        assert!(cycle.contains(&NodeIndex::new(1)));
356        assert!(cycle.contains(&NodeIndex::new(2)));
357    }
358
359    #[test]
360    fn test_find_negative_cycle_none() {
361        let graph = Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)]);
362        let result = find_negative_cycle(&graph, NodeIndex::new(0));
363        assert!(result.is_none());
364    }
365
366    #[test]
367    fn test_find_negative_cycle_unreachable() {
368        let graph =
369            Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (2, 3, -1.0), (3, 2, -1.0)]);
370        let result = find_negative_cycle(&graph, NodeIndex::new(0));
371        assert!(result.is_none());
372    }
373    use crate::neg_cycle::NegCycleFinder;
374    use crate::parametric::{MaxParametricSolver, ParametricAPI};
375    use num::rational::Ratio;
376    use petgraph::graph::{DiGraph, EdgeReference};
377
378    #[test]
379    fn test_neg_cycle_multiple_neg_cycles() {
380        let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
381            (0, 1, Ratio::new(1, 1)),
382            (1, 0, Ratio::new(-2, 1)), // Cycle 1: 0 -> 1 -> 0 (weight -1)
383            (2, 3, Ratio::new(1, 1)),
384            (3, 2, Ratio::new(-3, 1)), // Cycle 2: 2 -> 3 -> 2 (weight -2)
385        ]);
386
387        let mut ncf = NegCycleFinder::new(&digraph);
388        let mut dist = [
389            Ratio::new(0, 1),
390            Ratio::new(0, 1),
391            Ratio::new(0, 1),
392            Ratio::new(0, 1),
393        ];
394        let result = ncf.howard(&mut dist, |e| *e.weight());
395        assert!(result.is_some());
396        let cycle = result.unwrap();
397        let cycle_weight: Ratio<i32> = cycle.iter().map(|e| *e.weight()).sum();
398        assert!(cycle_weight < Ratio::new(0, 1));
399    }
400
401    #[test]
402    fn test_neg_cycle_not_reachable() {
403        let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
404            (0, 1, Ratio::new(1, 1)),
405            (2, 3, Ratio::new(-1, 1)),
406            (3, 2, Ratio::new(-1, 1)),
407        ]);
408
409        let mut ncf = NegCycleFinder::new(&digraph);
410        let mut dist = [
411            Ratio::new(0, 1),
412            Ratio::new(0, 1),
413            Ratio::new(0, 1),
414            Ratio::new(0, 1),
415        ];
416        let result = ncf.howard(&mut dist, |e| *e.weight());
417        assert!(result.is_some());
418    }
419
420    struct TestParametricAPI;
421
422    impl ParametricAPI<(), Ratio<i32>> for TestParametricAPI {
423        fn distance(&self, ratio: &Ratio<i32>, edge: &EdgeReference<Ratio<i32>>) -> Ratio<i32> {
424            *edge.weight() - *ratio
425        }
426
427        fn zero_cancel(&self, cycle: &[EdgeReference<Ratio<i32>>]) -> Ratio<i32> {
428            let mut sum_a = Ratio::new(0, 1);
429            let mut sum_b = Ratio::new(0, 1);
430            for edge in cycle {
431                sum_a += *edge.weight();
432                sum_b += Ratio::new(1, 1);
433            }
434            sum_a / sum_b
435        }
436    }
437
438    #[test]
439    fn test_max_parametric_solver_no_neg_cycle() {
440        let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
441            (0, 1, Ratio::new(1, 1)),
442            (1, 2, Ratio::new(1, 1)),
443            (2, 0, Ratio::new(1, 1)),
444        ]);
445
446        let mut solver = MaxParametricSolver::new(&digraph, TestParametricAPI);
447        let mut dist = [Ratio::new(0, 1), Ratio::new(0, 1), Ratio::new(0, 1)];
448        let mut ratio = Ratio::new(0, 1);
449
450        let cycle = solver.run(&mut dist, &mut ratio);
451        assert!(cycle.is_empty());
452    }
453
454    #[test]
455    fn test_max_parametric_solver_multiple_neg_cycles() {
456        let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
457            (0, 1, Ratio::new(1, 1)),
458            (1, 0, Ratio::new(-2, 1)), // Cycle 1: ratio -1/1
459            (2, 3, Ratio::new(1, 1)),
460            (3, 2, Ratio::new(-4, 1)), // Cycle 2: ratio -3/1
461        ]);
462
463        let mut solver = MaxParametricSolver::new(&digraph, TestParametricAPI);
464        let mut dist = [
465            Ratio::new(0, 1),
466            Ratio::new(0, 1),
467            Ratio::new(0, 1),
468            Ratio::new(0, 1),
469        ];
470        let mut ratio = Ratio::new(0, 1);
471
472        let cycle = solver.run(&mut dist, &mut ratio);
473        assert!(!cycle.is_empty());
474        assert_eq!(ratio, Ratio::new(-3, 2));
475    }
476
477    #[test]
478    fn test_bellman_ford_neg_cycle() {
479        let graph_with_neg_cycle =
480            Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 0, -3.0)]);
481        let result = bellman_ford(&graph_with_neg_cycle, NodeIndex::new(0));
482        assert!(result.is_err());
483    }
484
485    #[test]
486    fn test_bellman_ford_no_edge() {
487        let mut graph = Graph::<(), f32, Directed>::new();
488        let n0 = graph.add_node(());
489        let result = bellman_ford(&graph, n0);
490        assert!(result.is_ok());
491        let paths = result.unwrap();
492        assert_eq!(paths.distances, vec![0.0]);
493        assert_eq!(paths.predecessors, vec![None]);
494    }
495
496    #[test]
497    fn test_bellman_ford_disconnected() {
498        let mut graph = Graph::<(), f32, Directed>::new();
499        let n0 = graph.add_node(());
500        let n1 = graph.add_node(());
501        let n2 = graph.add_node(());
502        graph.add_edge(n0, n1, 1.0);
503
504        let result = bellman_ford(&graph, n0);
505        assert!(result.is_ok());
506        let paths = result.unwrap();
507        // Node 2 is unreachable, so its distance should be infinite
508        assert_eq!(paths.distances.len(), 3);
509        assert_eq!(paths.distances[n0.index()], 0.0);
510        assert_eq!(paths.distances[n1.index()], 1.0);
511        assert!(paths.distances[n2.index()].is_infinite());
512        assert_eq!(paths.predecessors, vec![None, Some(n0), None]);
513    }
514
515    #[test]
516    fn test_find_negative_cycle_multiple() {
517        let graph_with_neg_cycle = Graph::<(), f32, Directed>::from_edges([
518            (0, 1, 1.0),
519            (1, 0, -2.0),
520            (2, 3, 1.0),
521            (3, 2, -3.0),
522        ]);
523        let result = find_negative_cycle(&graph_with_neg_cycle, NodeIndex::new(0));
524        assert!(result.is_some());
525    }
526
527    #[test]
528    fn test_find_negative_cycle_no_neg_cycle() {
529        let graph = Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)]);
530        let result = find_negative_cycle(&graph, NodeIndex::new(0));
531        assert!(result.is_none());
532    }
533
534    #[test]
535    fn test_find_negative_cycle_unreachable_neg_cycle() {
536        let graph =
537            Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (2, 3, -1.0), (3, 2, -1.0)]);
538        let result = find_negative_cycle(&graph, NodeIndex::new(0));
539        assert!(result.is_none());
540    }
541}