pub mod dijkstra;
pub mod error;
pub mod neg_cycle;
pub mod parametric;
pub mod network_oracle;
pub mod optscaling_oracle;
pub mod min_cycle_ratio;
pub mod utils;
pub use error::NetOptimError;
pub use utils::*;
#[cfg(test)]
mod integration_tests;
#[cfg(feature = "std")]
pub mod logging;
use petgraph::prelude::*;
use petgraph::algo::{FloatMeasure, NegativeCycle};
use petgraph::visit::{
IntoEdges, IntoNodeIdentifiers, NodeCount, NodeIndexable, VisitMap, Visitable,
};
#[derive(Debug, Clone)]
pub struct Paths<NodeId, EdgeWeight> {
pub distances: Vec<EdgeWeight>,
pub predecessors: Vec<Option<NodeId>>,
}
pub fn bellman_ford<G>(
g: G,
source: G::NodeId,
) -> Result<Paths<G::NodeId, G::EdgeWeight>, NegativeCycle>
where
G: NodeCount + IntoNodeIdentifiers + IntoEdges + NodeIndexable,
G::EdgeWeight: FloatMeasure,
{
let ix = |i| g.to_index(i);
let (distances, predecessors) = bellman_ford_initialize_relax(g, source);
for i in g.node_identifiers() {
for edge in g.edges(i) {
let j = edge.target();
let w = *edge.weight();
if distances[ix(i)] + w < distances[ix(j)] {
return Err(NegativeCycle(()));
}
}
}
Ok(Paths {
distances,
predecessors,
})
}
pub fn find_negative_cycle<G>(g: G, source: G::NodeId) -> Option<Vec<G::NodeId>>
where
G: NodeCount + IntoNodeIdentifiers + IntoEdges + NodeIndexable + Visitable,
G::EdgeWeight: FloatMeasure,
{
let ix = |i| g.to_index(i);
let mut path = Vec::<G::NodeId>::new();
let (distance, predecessor) = bellman_ford_initialize_relax(g, source);
'outer: for i in g.node_identifiers() {
for edge in g.edges(i) {
let j = edge.target();
let w = *edge.weight();
if distance[ix(i)] + w < distance[ix(j)] {
let start = j;
let mut node = start;
let mut visited = g.visit_map();
loop {
let ancestor = match predecessor[ix(node)] {
Some(predecessor_node) => predecessor_node,
None => node, };
if ancestor == start {
path.push(ancestor);
break;
}
else if visited.is_visited(&ancestor) {
let pos = path
.iter()
.position(|&p| p == ancestor)
.expect("we should always have a position");
path = path[pos..path.len()].to_vec();
break;
}
path.push(ancestor);
visited.visit(ancestor);
node = ancestor;
}
break 'outer;
}
}
}
if !path.is_empty() {
path.reverse();
Some(path)
} else {
None
}
}
#[inline(always)]
fn bellman_ford_initialize_relax<G>(
g: G,
source: G::NodeId,
) -> (Vec<G::EdgeWeight>, Vec<Option<G::NodeId>>)
where
G: NodeCount + IntoNodeIdentifiers + IntoEdges + NodeIndexable,
G::EdgeWeight: FloatMeasure,
{
let mut predecessor = vec![None; g.node_bound()];
let mut distance = vec![<_>::infinite(); g.node_bound()];
let ix = |i| g.to_index(i);
distance[ix(source)] = <_>::zero();
for _ in 1..g.node_count() {
let mut did_update = false;
for i in g.node_identifiers() {
for edge in g.edges(i) {
let j = edge.target();
let w = *edge.weight();
if distance[ix(i)] + w < distance[ix(j)] {
distance[ix(j)] = distance[ix(i)] + w;
predecessor[ix(j)] = Some(i);
did_update = true;
}
}
}
if !did_update {
break;
}
}
(distance, predecessor)
}
#[cfg(test)]
mod tests {
use super::*;
use petgraph::Graph;
#[test]
fn test_bellman_ford_negative_cycle() {
let graph_with_neg_cycle =
Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 0, -3.0)]);
let result = bellman_ford(&graph_with_neg_cycle, NodeIndex::new(0));
assert!(result.is_err());
}
#[test]
fn test_bellman_ford_no_edges() {
let mut graph = Graph::<(), f32, Directed>::new();
let n0 = graph.add_node(());
let result = bellman_ford(&graph, n0);
assert!(result.is_ok());
let paths = result.unwrap();
assert_eq!(paths.distances, vec![0.0]);
assert_eq!(paths.predecessors, vec![None]);
}
#[test]
fn test_bellman_ford_disconnected_components() {
let mut graph = Graph::<(), f32, Directed>::new();
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1.0);
let result = bellman_ford(&graph, n0);
assert!(result.is_ok());
let paths = result.unwrap();
assert_eq!(paths.distances.len(), 3);
assert_eq!(paths.distances[n0.index()], 0.0);
assert_eq!(paths.distances[n1.index()], 1.0);
assert!(paths.distances[n2.index()].is_infinite());
assert_eq!(paths.predecessors, vec![None, Some(n0), None]);
}
#[test]
fn test_find_negative_cycle_exists() {
let graph_with_neg_cycle =
Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 0, -3.0)]);
let result = find_negative_cycle(&graph_with_neg_cycle, NodeIndex::new(0));
assert!(result.is_some());
let cycle = result.unwrap();
assert_eq!(cycle.len(), 3);
assert!(cycle.contains(&NodeIndex::new(0)));
assert!(cycle.contains(&NodeIndex::new(1)));
assert!(cycle.contains(&NodeIndex::new(2)));
}
#[test]
fn test_find_negative_cycle_none() {
let graph = Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)]);
let result = find_negative_cycle(&graph, NodeIndex::new(0));
assert!(result.is_none());
}
#[test]
fn test_find_negative_cycle_unreachable() {
let graph =
Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (2, 3, -1.0), (3, 2, -1.0)]);
let result = find_negative_cycle(&graph, NodeIndex::new(0));
assert!(result.is_none());
}
use crate::neg_cycle::NegCycleFinder;
use crate::parametric::{MaxParametricSolver, ParametricAPI};
use num::rational::Ratio;
use petgraph::graph::{DiGraph, EdgeReference};
#[test]
fn test_neg_cycle_multiple_neg_cycles() {
let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
(0, 1, Ratio::new(1, 1)),
(1, 0, Ratio::new(-2, 1)), (2, 3, Ratio::new(1, 1)),
(3, 2, Ratio::new(-3, 1)), ]);
let mut ncf = NegCycleFinder::new(&digraph);
let mut dist = [
Ratio::new(0, 1),
Ratio::new(0, 1),
Ratio::new(0, 1),
Ratio::new(0, 1),
];
let result = ncf.howard(&mut dist, |e| *e.weight());
assert!(result.is_some());
let cycle = result.unwrap();
let cycle_weight: Ratio<i32> = cycle.iter().map(|e| *e.weight()).sum();
assert!(cycle_weight < Ratio::new(0, 1));
}
#[test]
fn test_neg_cycle_not_reachable() {
let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
(0, 1, Ratio::new(1, 1)),
(2, 3, Ratio::new(-1, 1)),
(3, 2, Ratio::new(-1, 1)),
]);
let mut ncf = NegCycleFinder::new(&digraph);
let mut dist = [
Ratio::new(0, 1),
Ratio::new(0, 1),
Ratio::new(0, 1),
Ratio::new(0, 1),
];
let result = ncf.howard(&mut dist, |e| *e.weight());
assert!(result.is_some());
}
struct TestParametricAPI;
impl ParametricAPI<(), Ratio<i32>> for TestParametricAPI {
fn distance(&self, ratio: &Ratio<i32>, edge: &EdgeReference<Ratio<i32>>) -> Ratio<i32> {
*edge.weight() - *ratio
}
fn zero_cancel(&self, cycle: &[EdgeReference<Ratio<i32>>]) -> Ratio<i32> {
let mut sum_a = Ratio::new(0, 1);
let mut sum_b = Ratio::new(0, 1);
for edge in cycle {
sum_a += *edge.weight();
sum_b += Ratio::new(1, 1);
}
sum_a / sum_b
}
}
#[test]
fn test_max_parametric_solver_no_neg_cycle() {
let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
(0, 1, Ratio::new(1, 1)),
(1, 2, Ratio::new(1, 1)),
(2, 0, Ratio::new(1, 1)),
]);
let mut solver = MaxParametricSolver::new(&digraph, TestParametricAPI);
let mut dist = [Ratio::new(0, 1), Ratio::new(0, 1), Ratio::new(0, 1)];
let mut ratio = Ratio::new(0, 1);
let cycle = solver.run(&mut dist, &mut ratio);
assert!(cycle.is_empty());
}
#[test]
fn test_max_parametric_solver_multiple_neg_cycles() {
let digraph = DiGraph::<(), Ratio<i32>>::from_edges([
(0, 1, Ratio::new(1, 1)),
(1, 0, Ratio::new(-2, 1)), (2, 3, Ratio::new(1, 1)),
(3, 2, Ratio::new(-4, 1)), ]);
let mut solver = MaxParametricSolver::new(&digraph, TestParametricAPI);
let mut dist = [
Ratio::new(0, 1),
Ratio::new(0, 1),
Ratio::new(0, 1),
Ratio::new(0, 1),
];
let mut ratio = Ratio::new(0, 1);
let cycle = solver.run(&mut dist, &mut ratio);
assert!(!cycle.is_empty());
assert_eq!(ratio, Ratio::new(-3, 2));
}
#[test]
fn test_bellman_ford_neg_cycle() {
let graph_with_neg_cycle =
Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 0, -3.0)]);
let result = bellman_ford(&graph_with_neg_cycle, NodeIndex::new(0));
assert!(result.is_err());
}
#[test]
fn test_bellman_ford_no_edge() {
let mut graph = Graph::<(), f32, Directed>::new();
let n0 = graph.add_node(());
let result = bellman_ford(&graph, n0);
assert!(result.is_ok());
let paths = result.unwrap();
assert_eq!(paths.distances, vec![0.0]);
assert_eq!(paths.predecessors, vec![None]);
}
#[test]
fn test_bellman_ford_disconnected() {
let mut graph = Graph::<(), f32, Directed>::new();
let n0 = graph.add_node(());
let n1 = graph.add_node(());
let n2 = graph.add_node(());
graph.add_edge(n0, n1, 1.0);
let result = bellman_ford(&graph, n0);
assert!(result.is_ok());
let paths = result.unwrap();
assert_eq!(paths.distances.len(), 3);
assert_eq!(paths.distances[n0.index()], 0.0);
assert_eq!(paths.distances[n1.index()], 1.0);
assert!(paths.distances[n2.index()].is_infinite());
assert_eq!(paths.predecessors, vec![None, Some(n0), None]);
}
#[test]
fn test_find_negative_cycle_multiple() {
let graph_with_neg_cycle = Graph::<(), f32, Directed>::from_edges([
(0, 1, 1.0),
(1, 0, -2.0),
(2, 3, 1.0),
(3, 2, -3.0),
]);
let result = find_negative_cycle(&graph_with_neg_cycle, NodeIndex::new(0));
assert!(result.is_some());
}
#[test]
fn test_find_negative_cycle_no_neg_cycle() {
let graph = Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (1, 2, 1.0), (2, 3, 1.0)]);
let result = find_negative_cycle(&graph, NodeIndex::new(0));
assert!(result.is_none());
}
#[test]
fn test_find_negative_cycle_unreachable_neg_cycle() {
let graph =
Graph::<(), f32, Directed>::from_edges([(0, 1, 1.0), (2, 3, -1.0), (3, 2, -1.0)]);
let result = find_negative_cycle(&graph, NodeIndex::new(0));
assert!(result.is_none());
}
}