Compare commits

...

7 Commits

18 changed files with 1433 additions and 339 deletions

13
Cargo.lock generated
View File

@@ -1004,6 +1004,18 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
[[package]]
name = "enum_dispatch"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd"
dependencies = [
"once_cell",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "enumflags2"
version = "0.7.11"
@@ -1908,6 +1920,7 @@ dependencies = [
"eframe",
"egui",
"egui_graphs",
"enum_dispatch",
"geo",
"itertools 0.14.0",
"petgraph",

View File

@@ -9,6 +9,7 @@ ecolor = "0.31.1"
eframe = "0.30"
egui = "0.30"
egui_graphs = "0.23.0"
enum_dispatch = "0.3.13"
geo = "0.29.3"
itertools = "0.14.0"
petgraph = "0.6"

101
README.md Normal file
View File

@@ -0,0 +1,101 @@
# Maxflow-rs
## Idea
- Generate StableGraph (petgraph)
- run algo on that
- Create a custom to_graph method which displays a maxflow graph
TODOs
- [x] Implementation of Algos:
- [x] Ford-Fulkersson (DFS zur Berechnung des flusserhöhenden/augmentierenden Pfads)
- [x] Edmonds-Karp (BFS zur Berechnung des flusserhöhenden/augmnetierenden Pfads)
- [x] Dinic
- [x] Goldberg-Tarjan/Preflow-Push
- [x] Step-by-step implementation for all algos
- [x] Ford-Fulkerson
- [x] Edmonds-Karp
- [x] Dinic
- [x] Goldberg-Tarjan/Preflow-Push
- [x] Mark edges which have active flows with a color
- [x] Move GUI Graph conversion to a trait of StableGraph
- [ ] Add graph positioning strategy which looks "nicer"
- [x] Only insert nodes which are at least a distance of 40 away (default)
- [ ] Pseudo-random node positioning strategy
- [x] Handle residual edges properly
- [x] Add display option for residual edges
- [x] Only show active flows (filter all edges with f=0)
- [x] Show normal vs residual graph -> not needed, residual graph is shown by default
- [x] When increasing flows, handle residual path
- [x] Add unit tests
- [x] With small problems to prove correctness
- [x] For Benchmarking the algos
- [x] Check validity of the generated flows (incoming flows = outgoing flows; flow <= capacity)
- [x] Add info display pane
- [x] is valid flow? (add trait to graph)
- [x] current total flow (add trait to calculate)
- [x] number of steps (is implemented for stepwise)
- [x] implement PartialEq for StableGraph (not really possible -> how do you compare graphs?) -> implemented a prune_zero trait & compare node/edge weights for same index
- [x] Step-by-step vizualisation with choosable time delay
- [ ] Dinic: add GUI options to show Layer Graph and Blocking flow
- [ ] Goldberg-Tarjan: show distance labels at nodes
- [x] change GUI colors to accomodate color-blindness (blue-yellow rather than red-green)
- [x] remove display option for residual graph -> this should be the default
DFS: Stack
BFS: Queue
Bugs:
- [x] if I generate a new graph, the algo runs on the old graph
- [x] if I click reset & then step, the algo runs in one step
- [x] when using edmonds karp the updated flow isn't displayed correctly
- [ ] the capacity on residual edges also gets displayed when using
Residual Graph:
- parallel arcs in the same direction can be summarized into a single arc by summing up the capcities
-
TODOs:
- [x] gui colors in yellow blue (should be easy)
- [x] no residual graph option, residual graph is default (medium, bit of effort)
- [ ] do we need to delete the graph field in algos & use residual_graph instead?
- [ ] check correctness of all algos by testing a small sample#
- [ ] gui display options (medium)
- [ ] layer graph (done - TODO: check if correct) & blocking flow (for Dinic)
- [ ] goldberg-tarjan: push-relabel step - current pre-flow, flow change in every step
- [x] add time delay for algorithm (hard due to multithreading)
- [ ] fix glitchy graph display (somewhere in update_graph function)
- [ ] implement goldberg tarjan (hard)
- [x] fix residual edges: algo pushes flows back but increases flow on the residual edge instead of decreasing the reverse edge
- [ ] fix loops: somehow the algo produces loops towards the source node (not critical for calculating the total flow, but should be fixed)
- [ ] big tests sometimes fail for goldberg-tarjan (panic when reading node from distance labelling)
- [x] make MaxflowAlgorithm object-safe (remove from_problem & implement it per struct, then create a generic wrapper which creates new instances for each specific algorithm)
- [ ] add unit test environment (hard)
- [x] callable from gui
- [x] several test tuples [number of problems, node count, max capacity]
- [x] split MaxflowProblem:new into two functions (one for display, one for test environment)
- [x] apply to every algo
- [x] große testumgebung
- [x] testfunktion in seperatem thread ausführen, damit programm nicht hängt
- [ ] check for:
- [x] flusserhaltung
- [x] kapazitätsbedingungen
- [ ] saturierter schritt
- [ ] testumgebung durch änderungen der lösung automatisch überprüfen (korrektheit, optimalität)
- [x] reduce warnings
thread '<unnamed>' panicked at src/graph.rs:191:31
attempt to subtract with overflow
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at src/algorithms/goldberg_tarjan.rs:109:238:
No distance label found
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
# Code erklären
- Graph generieren (random_generator.rs)
- GUI, Custom Layout (main.rs, layout.rs)
- Graph Hilfsfunktionen (graph.rs)
- Algorithmen (ford_fulkerson.rs, edmonds_karp.rs, dinic.rs, goldberg_tarjan.rs)

61
src/algorithms/common.rs Normal file
View File

@@ -0,0 +1,61 @@
use crate::algorithms::ford_fulkerson::FordFulkerson;
use crate::algorithms::edmonds_karp::EdmondsKarp;
use crate::algorithms::dinic::Dinic;
use crate::algorithms::goldberg_tarjan::GoldbergTarjan;
use std::fmt;
use enum_dispatch::enum_dispatch;
use petgraph::stable_graph::StableGraph;
#[enum_dispatch(MaxflowAlgorithmEnum)]
pub trait MaxflowAlgorithm {
// perform a single step; returns true if the algorithm terminates
fn step(&mut self) -> bool;
// runs the whole algorithm in a single step
fn run(&mut self) -> StableGraph<(f32, f32), (u64, u64)>;
// returns the current graph
fn graph(&mut self) -> StableGraph<(f32, f32), (u64, u64)>;
// resets the current flow to the zero flow
fn reset(&mut self);
// returns the algorithm name
fn name(&self) -> &'static str;
}
pub fn available_capacity(edge: (u64, u64)) -> u64 {
edge.1 - edge.0
}
#[enum_dispatch]
pub enum MaxflowAlgorithmEnum {
FordFulkerson,
EdmondsKarp,
Dinic,
GoldbergTarjan
}
impl PartialEq for MaxflowAlgorithmEnum {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(&MaxflowAlgorithmEnum::FordFulkerson(_), &MaxflowAlgorithmEnum::FordFulkerson(_)) => true,
(&MaxflowAlgorithmEnum::EdmondsKarp(_), &MaxflowAlgorithmEnum::EdmondsKarp(_)) => true,
(&MaxflowAlgorithmEnum::Dinic(_), &MaxflowAlgorithmEnum::Dinic(_)) => true,
(&MaxflowAlgorithmEnum::GoldbergTarjan(_), &MaxflowAlgorithmEnum::GoldbergTarjan(_)) => true,
_ => false,
}
}
}
impl fmt::Display for MaxflowAlgorithmEnum {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &self {
&MaxflowAlgorithmEnum::FordFulkerson(_) => write!(f, "Ford-Fulkerson"),
&MaxflowAlgorithmEnum::EdmondsKarp(_) => write!(f, "Edmonds-Karp"),
&MaxflowAlgorithmEnum::Dinic(_) => write!(f, "Dinic"),
&MaxflowAlgorithmEnum::GoldbergTarjan(_) => write!(f, "Goldberg-Tarjan"),
}
}
}

View File

@@ -1,107 +1,148 @@
// Dinic
use std::cmp::min;
use std::collections::VecDeque;
use petgraph::{stable_graph::{EdgeReference, NodeIndex, StableGraph}, visit::{EdgeRef, VisitMap, Visitable}, Direction};
use petgraph::{stable_graph::{NodeIndex, StableGraph}, visit::{EdgeRef, VisitMap, Visitable}, Direction};
use crate::{algorithms::common::{available_capacity, MaxflowAlgorithm}, graph::FlowGraph, random_generator::MaxflowProblem};
fn available_capacity(edge: (u64, u64)) -> u64 {
edge.1 - edge.0
pub struct Dinic {
graph: StableGraph<(f32, f32), (u64, u64)>,
source: NodeIndex,
sink: NodeIndex,
residual_graph: StableGraph<(f32, f32), (u64, u64)>,
layer_graph: StableGraph<(f32, f32), (u64, u64)>,
}
// BFS
fn layer_graph(graph: StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, destination: NodeIndex) -> StableGraph<(f32, f32), (u64, u64)> {
// filter graph for edges with no remaining capacity
let mut layer_graph = graph.filter_map(|_, n| {
Some(*n)
}, |_, &e| {
match available_capacity(e) {
0 => None,
_ => Some(e),
impl Dinic {
pub fn from_problem(p: &MaxflowProblem) -> Self {
Self {
residual_graph: p.g.residual(),
graph: p.g.clone(),
layer_graph: StableGraph::default(),
source: p.s,
sink: p.t,
}
});
}
// filter graph for s-t paths
let mut visited = layer_graph.visit_map();
let mut queue = VecDeque::from([(source, 0)]);
while let Some((node, layer)) = queue.pop_front() {
//let outgoing_edges = layer_graph.edges_directed(node, Direction::Outgoing).collect::<Vec<_>>();
let outgoing_edges = layer_graph.edges_directed(node, Direction::Outgoing).map(|e| e.id()).collect::<Vec<_>>();
visited.visit(node);
for edge in outgoing_edges {
let neighbor = layer_graph.edge_endpoints(edge).expect("edge index not found").1;
// if neighbor is unvisited, this is the shortest path to neighbor -> keep the edge
if !visited.is_visited(&neighbor) {
visited.visit(neighbor);
// stop traversing this path if destination is reached
if neighbor == destination {
continue;
} else {
// add neighbor to queue if destination is not reached
queue.push_back((neighbor, layer+1));
}
} else {
// if neighbor was visited before this edge is not on the shortest path -> remove it
layer_graph.remove_edge(edge);
// BFS
fn layer_graph(&self) -> StableGraph<(f32, f32), (u64, u64)> {
// filter graph for edges with no remaining capacity
let mut layer_graph = self.residual_graph.filter_map(|_, n| {
Some(*n)
}, |_, &e| {
match available_capacity(e) {
0 => None,
_ => Some(e),
}
}
}
layer_graph
}
// Runs a depth Depth First Search (DFS) which returns an augmenting path from source to destination if possible
fn dfs(graph: &StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, destination: NodeIndex) -> Option<Vec<NodeIndex>> {
let mut visited = graph.visit_map();
let mut stack = VecDeque::from([(source, vec![source])]);
// work through the main stack
while let Some((node, path)) = stack.pop_front() {
let outgoing_edges = graph.edges_directed(node, Direction::Outgoing);
visited.visit(node);
// iterate over all outgoing edges & add neighboring nodes to the stack
for edge in outgoing_edges {
let neighbor = edge.target();
if !visited.is_visited(&neighbor) && available_capacity(*edge.weight()) > 0 {
visited.visit(neighbor);
let mut new_path = path.clone();
new_path.push(neighbor);
// TODO: is this right?
if neighbor == destination {
return Some(new_path);
} else {
stack.push_front((neighbor, new_path));
}
};
}
}
None
}
pub fn dinic(graph: StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, sink: NodeIndex) -> StableGraph<(f32, f32), (u64, u64)> {
// construct layer graph (from s to t)
let mut g_l = layer_graph(graph, source, sink);
// construct a blocking flow in the layer graph using a DFS
// continue while there are augmenting paths
while let Some(path) = dfs(&g_l, source, sink) {
// find all edges along the path
let edges: Vec<petgraph::prelude::EdgeIndex> = path.windows(2).map(|w| g_l.find_edge(w[0], w[1]).expect("edge not found")).collect();
// find bottleneck capacity along path
let increase_value = edges.iter().fold(u64::MAX, |m, x| {
let edge = g_l.edge_weight(*x).expect("edge index not found");
min(m, edge.1 - edge.0)
});
// increase flow with bottleneck capacity along the augmenting path
for edge in edges {
let weight = g_l.edge_weight_mut(edge).expect("edge not found");
(*weight).0 += increase_value;
// filter graph for s-t paths
let mut visited = layer_graph.visit_map();
let mut queue = VecDeque::from([(self.source, 0)]);
while let Some((node, layer)) = queue.pop_front() {
//let outgoing_edges = layer_graph.edges_directed(node, Direction::Outgoing).collect::<Vec<_>>();
let outgoing_edges = layer_graph.edges_directed(node, Direction::Outgoing).map(|e| e.id()).collect::<Vec<_>>();
visited.visit(node);
for edge in outgoing_edges {
let neighbor = layer_graph.edge_endpoints(edge).expect("edge index not found").1;
// if neighbor is unvisited, this is the shortest path to neighbor -> keep the edge
if !visited.is_visited(&neighbor) {
visited.visit(neighbor);
// stop traversing this path if destination is reached
if neighbor == self.sink {
continue;
} else {
// add neighbor to queue if destination is not reached
queue.push_back((neighbor, layer+1));
}
} else {
// if neighbor was visited before this edge is not on the shortest path -> remove it
layer_graph.remove_edge(edge);
}
}
}
layer_graph
}
// Runs a depth Depth First Search (DFS) which returns an augmenting path from source to destination if possible
fn dfs(&self) -> Option<Vec<NodeIndex>> {
let mut visited = self.layer_graph.visit_map();
let mut stack = VecDeque::from([(self.source, vec![self.source])]);
// work through the main stack
while let Some((node, path)) = stack.pop_front() {
let outgoing_edges = self.layer_graph.edges_directed(node, Direction::Outgoing);
visited.visit(node);
// iterate over all outgoing edges & add neighboring nodes to the stack
for edge in outgoing_edges {
let neighbor = edge.target();
if !visited.is_visited(&neighbor) && available_capacity(*edge.weight()) > 0 {
visited.visit(neighbor);
let mut new_path = path.clone();
new_path.push(neighbor);
if neighbor == self.sink {
return Some(new_path);
} else {
stack.push_front((neighbor, new_path));
}
};
}
}
None
}
}
impl MaxflowAlgorithm for Dinic {
fn step(&mut self) -> bool {
// construct layer graph (from s to t)
self.layer_graph = self.layer_graph();
// construct a blocking flow in the layer graph using a DFS
// continue while there are augmenting paths
if let Some(path) = self.dfs() {
// find all edges along the path
let edges: Vec<petgraph::prelude::EdgeIndex> = path.windows(2).map(|w| self.residual_graph.find_edge(w[0], w[1]).expect("edge not found")).collect();
// find bottleneck capacity along path
let bottleneck_capacity = edges.iter().fold(u64::MAX, |m, x| {
let edge = self.residual_graph.edge_weight(*x).expect("edge index not found");
min(m, edge.1 - edge.0)
});
// increase flow with bottleneck capacity along the augmenting path
for edge in edges {
self.residual_graph.add_flow(edge, bottleneck_capacity);
}
false
} else {
true
}
}
// add blocking flow to flow of original graph
g_l
}
fn reset(&mut self) {
// set all flows in graph to 0
for edge in self.graph.edge_indices().collect::<Vec<_>>() {
let weight = self.graph.edge_weight_mut(edge).expect("edge not found");
(*weight).0 = 0;
}
// reset the residual & layer graph as well
self.residual_graph = self.graph.residual();
self.layer_graph = StableGraph::default();
}
fn run(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
while !self.step() {
continue;
}
self.residual_graph.clone()
}
fn graph(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
self.residual_graph.clone()
}
fn name(&self) -> &'static str {
"Dinic"
}
}

View File

@@ -1,61 +1,100 @@
// Edmonds-Karp (also Ford-Fulkerson mit BFS statt DFS),
use std::cmp::min;
use std::collections::VecDeque;
use petgraph::{stable_graph::{NodeIndex, StableGraph, EdgeReference}, visit::{EdgeRef, VisitMap, Visitable}, Direction};
use petgraph::{stable_graph::{NodeIndex, StableGraph}, visit::{EdgeRef, VisitMap, Visitable}, Direction};
use crate::{algorithms::common::{available_capacity, MaxflowAlgorithm}, graph::FlowGraph, random_generator::MaxflowProblem};
fn available_capacity(edge: EdgeReference<'_, (u64, u64)>) -> u64 {
edge.weight().1 - edge.weight().0
pub struct EdmondsKarp {
graph: StableGraph<(f32, f32), (u64, u64)>,
source: NodeIndex,
sink: NodeIndex,
residual_graph: StableGraph<(f32, f32), (u64, u64)>,
}
// Runs a depth Breadth First Search (BFS) and returns an augmenting path from source to destination if possible
fn bfs(graph: &StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, destination: NodeIndex) -> Option<Vec<NodeIndex>> {
let mut visited = graph.visit_map();
let mut queue = VecDeque::from([(source, vec![source])]);
//queue.push_back((source, vec![source]));
impl EdmondsKarp {
pub fn from_problem(p: &MaxflowProblem) -> Self {
Self {
residual_graph: p.g.residual(),
graph: p.g.clone(),
source: p.s,
sink: p.t,
}
}
// work through the main queue
while let Some((node, path)) = queue.pop_front() {
let outgoing_edges = graph.edges_directed(node, Direction::Outgoing);
visited.visit(node);
// Runs a depth Breadth First Search (BFS) and returns an augmenting path from source to destination if possible
fn bfs(&mut self) -> Option<Vec<NodeIndex>> {
let mut visited = self.residual_graph.visit_map();
let mut queue = VecDeque::from([(self.source, vec![self.source])]);
//queue.push_back((source, vec![source]));
for edge in outgoing_edges {
let neighbor = edge.target();
if !visited.is_visited(&neighbor) && available_capacity(edge) > 0 {
visited.visit(neighbor);
let mut new_path = path.clone();
new_path.push(neighbor);
visited.visit(neighbor);
if neighbor == destination {
return Some(new_path);
} else {
queue.push_back((neighbor, new_path));
// work through the main queue
while let Some((node, path)) = queue.pop_front() {
let outgoing_edges = self.residual_graph.edges_directed(node, Direction::Outgoing);
visited.visit(node);
for edge in outgoing_edges {
let neighbor = edge.target();
if !visited.is_visited(&neighbor) && available_capacity(*edge.weight()) > 0 {
visited.visit(neighbor);
let mut new_path = path.clone();
new_path.push(neighbor);
visited.visit(neighbor);
if neighbor == self.sink {
return Some(new_path);
} else {
queue.push_back((neighbor, new_path));
}
}
}
}
None
}
None
}
pub fn edmonds_karp(mut graph: StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, sink: NodeIndex) -> StableGraph<(f32, f32), (u64, u64)> {
// continue while there are augmenting paths
while let Some(path) = bfs(&graph, source, sink) {
// find all edges along the path
let edges: Vec<petgraph::prelude::EdgeIndex> = path.windows(2).map(|w| graph.find_edge(w[0], w[1]).expect("edge not found")).collect();
// find bottleneck capacity along path
let increase_value = edges.iter().fold(u64::MAX, |m, x| {
let edge = graph.edge_weight(*x).expect("edge index not found");
min(m, edge.1 - edge.0)
});
impl MaxflowAlgorithm for EdmondsKarp {
// increase flow with bottleneck capacity along the augmenting path
for edge in edges {
let weight = graph.edge_weight_mut(edge).expect("edge not found");
(*weight).0 += increase_value;
fn step(&mut self) -> bool {
if let Some(path) = self.bfs() {
// find all edges along the path
let edges: Vec<petgraph::prelude::EdgeIndex> = path.windows(2).map(|w| self.residual_graph.find_edge(w[0], w[1]).expect("edge not found")).collect();
// find bottleneck capacity along path
let bottleneck_capacity = edges.iter().fold(u64::MAX, |m, x| {
let edge = self.residual_graph.edge_weight(*x).expect("edge index not found");
min(m, edge.1 - edge.0)
});
// increase flow with bottleneck capacity along the augmenting path
for edge in edges {
self.residual_graph.add_flow(edge, bottleneck_capacity);
}
false
} else {
true
}
}
// return graph with augmented flows
graph
}
fn reset(&mut self) {
// set all flows in graph to 0
for edge in self.graph.edge_indices().collect::<Vec<_>>() {
let weight = self.graph.edge_weight_mut(edge).expect("edge not found");
(*weight).0 = 0;
}
// reset the residual graph as well
self.residual_graph = self.graph.residual();
}
fn run(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
while !self.step() {
continue;
}
self.residual_graph.clone()
}
fn graph(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
self.residual_graph.clone()
}
fn name(&self) -> &'static str {
"Edmonds-Karp"
}
}

View File

@@ -1,118 +1,99 @@
// Ford-Fulkerson mit DFS zur Berechnung des flusserhöhenden Pfades,
// Edmonds-Karp (also Ford-Fulkerson mit BFS statt DFS),
// Dinic
// Goldberg-Tarjan (auch Preflow-Push oder Push-Relabel genannt).
use std::cmp::min;
use std::collections::VecDeque;
use petgraph::{stable_graph::{NodeIndex, StableGraph}, visit::{EdgeRef, VisitMap, Visitable}, Direction};
use crate::{algorithms::common::{available_capacity, MaxflowAlgorithm}, graph::FlowGraph, random_generator::MaxflowProblem};
use petgraph::{stable_graph::{EdgeReference, NodeIndex, StableGraph}, visit::{EdgeRef, VisitMap, Visitable}, Direction};
//use crate::random_generator::MaxflowProblem;
//mod random_generator;
fn available_capacity(edge: EdgeReference<'_, (u64, u64)>) -> u64 {
edge.weight().1 - edge.weight().0
pub struct FordFulkerson {
graph: StableGraph<(f32, f32), (u64, u64)>,
source: NodeIndex,
sink: NodeIndex,
residual_graph: StableGraph<(f32, f32), (u64, u64)>,
}
// Runs a depth Depth First Search (DFS) and returns an augmenting path from source to destination if possible
fn dfs(graph: &StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, destination: NodeIndex) -> Option<Vec<NodeIndex>> {
let mut visited = graph.visit_map();
let mut stack = VecDeque::from([(source, vec![source])]);
// work through the main stack
while let Some((node, path)) = stack.pop_front() {
let outgoing_edges = graph.edges_directed(node, Direction::Outgoing);
visited.visit(node);
// iterate over all outgoing edges
for edge in outgoing_edges {
let neighbor = edge.target();
if !visited.is_visited(&neighbor) && available_capacity(edge) > 0 {
visited.visit(neighbor);
let mut new_path = path.clone();
new_path.push(neighbor);
// TODO: is this right?
if neighbor == destination {
return Some(new_path);
} else {
stack.push_front((neighbor, new_path));
}
};
impl FordFulkerson {
pub fn from_problem(p: &MaxflowProblem) -> Self {
Self {
residual_graph: p.g.residual(),
graph: p.g.clone(),
source: p.s,
sink: p.t,
}
}
None
// Runs a depth Depth First Search (DFS) which returns an augmenting path from source to destination if possible
fn dfs(&mut self) -> Option<Vec<NodeIndex>> {
let mut visited = self.residual_graph.visit_map();
let mut stack = VecDeque::from([(self.source, vec![self.source])]);
// work through the main stack
while let Some((node, path)) = stack.pop_front() {
let outgoing_edges = self.residual_graph.edges_directed(node, Direction::Outgoing);
visited.visit(node);
// iterate over all outgoing edges & add neighboring nodes to the stack
for edge in outgoing_edges {
let neighbor = edge.target();
if !visited.is_visited(&neighbor) && available_capacity(*edge.weight()) > 0 {
visited.visit(neighbor);
let mut new_path = path.clone();
new_path.push(neighbor);
if neighbor == self.sink {
return Some(new_path);
} else {
stack.push_front((neighbor, new_path));
}
};
}
}
None
}
}
pub fn ford_fulkerson(mut graph: StableGraph<(f32, f32), (u64, u64)>, source: NodeIndex, sink: NodeIndex) -> StableGraph<(f32, f32), (u64, u64)> {
//let augmenting_path = dfs(graph, source, sink);
impl MaxflowAlgorithm for FordFulkerson {
fn step(&mut self) -> bool {
// continue while there are augmenting paths
if let Some(path) = self.dfs() {
// find all edges along the path
let edges: Vec<petgraph::prelude::EdgeIndex> = path.windows(2).map(|w| self.residual_graph.find_edge(w[0], w[1]).expect("edge not found")).collect();
// find bottleneck capacity along path
let bottleneck_capacity = edges.iter().fold(u64::MAX, |m, x| {
let edge = self.residual_graph.edge_weight(*x).expect("edge index not found");
min(m, edge.1 - edge.0)
});
// continue while there are augmenting paths
while let Some(path) = dfs(&graph, source, sink) {
// find all edges along the path
let edges: Vec<petgraph::prelude::EdgeIndex> = path.windows(2).map(|w| graph.find_edge(w[0], w[1]).expect("edge not found")).collect();
// find bottleneck capacity along path
let increase_value = edges.iter().fold(u64::MAX, |m, x| {
let edge = graph.edge_weight(*x).expect("edge index not found");
min(m, edge.1 - edge.0)
});
// increase flow with bottleneck capacity along the augmenting path
for edge in edges {
let weight = graph.edge_weight_mut(edge).expect("edge not found");
(*weight).0 += increase_value;
// increase flow with bottleneck capacity along the augmenting path
for edge in edges {
self.residual_graph.add_flow(edge, bottleneck_capacity);
}
false
} else {
true
}
}
// return graph with augmented flows
graph
}
//pub struct FordFulkerson {
// problem: MaxflowProblem,
// flows: Graph<(), u64, Directed, u32, DefaultNodeShape, DefaultEdgeShape>,
// alertnatively, use a map from EdgeIndex to flow value
//dfs: Dfs<N, VM>,
//}
/* impl FordFulkerson {
fn new(problem: MaxflowProblem) -> S/elf {
Self { problem: problem, flows: Graph::new(StableGraph::default()) }
}
fn graph(&mut self) -> &Graph<bool, (u64, u64)> {
&self.problem.g
}
fn flows(&mut self) -> &Graph<(), u64> {
&self.flows
}
fn dfs(&mut self, ni: NodeIndex) -> Vec<Vec<EdgeIndex>>{
// set own node to visited
self.problem.g.node_mut(ni).expect("node index not found").set_visited(true);
//self.graph().node_mut(ni).expect("node index not found").set_visited(true);
let augmenting_paths: Vec<Vec<EdgeIndex>> = Vec::new();
let neighboring_edges = self.graph().edges_directed(ni, Direction::Outgoing);
for edge in neighboring_edges {
//let neighbor = self.problem.g.node_mut(edge.target()).expect("node index not found");
//if neighbor.visited() == false {
// println!("{:?}", edge.weight());
//}
fn reset(&mut self) {
// set all flows in graph to 0
for edge in self.graph.edge_indices().collect::<Vec<_>>() {
let weight = self.graph.edge_weight_mut(edge).expect("edge not found");
(*weight).0 = 0;
}
// iterate over all unvisited! neighbors. for each:
// - check if there is capacity left
// - if neighbor = target, return path
// - else, do dfs starting from neighbor
// - if neighbor returns augmenting path add it to own list & prepend own edge
// return list of augmenting paths
augmenting_paths
// reset residual graph as well
self.residual_graph = self.graph.residual();
}
fn step(&self) {
//self.dfs(self.problem.s);
fn run(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
while !self.step() {
continue;
}
self.residual_graph.clone()
}
fn graph(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
self.residual_graph.clone()
}
} */
fn name(&self) -> &'static str {
"Ford-Fulkerson"
}
}

View File

@@ -0,0 +1,161 @@
use std::cmp::min;
use std::collections::{VecDeque, HashMap};
use itertools::Itertools;
use petgraph::{stable_graph::{NodeIndex, StableGraph}, visit::{EdgeRef, NodeRef, VisitMap, Visitable}, Direction};
use crate::{algorithms::common::{MaxflowAlgorithm}, graph::FlowGraph, random_generator::MaxflowProblem};
pub struct GoldbergTarjan {
graph: StableGraph<(f32, f32), (u64, u64)>,
source: NodeIndex,
sink: NodeIndex,
residual_graph: StableGraph<(f32, f32), (u64, u64)>,
first_iteration: bool,
pub distance_label: Option<HashMap<NodeIndex, u64>>
// psi?
}
impl GoldbergTarjan {
pub fn from_problem(p: &MaxflowProblem) -> Self {
Self {
residual_graph: p.g.residual(),
graph: p.g.clone(),
source: p.s,
sink: p.t,
first_iteration: true,
distance_label: None
}
// initialize with a valid preflow (max flow on all outgoing edges of s)
// assign height/label to every node using the labeling/height function (using BFS)
}
fn active_node(&self) -> Option<(NodeIndex, u64)> {
for node in self.residual_graph.node_indices() {
// s and t are ne
if node == self.source || node == self.sink {
continue;
}
let outgoing_flows: u64 = self.residual_graph.edges_directed(node, Direction::Outgoing).map(|e| e.weight().0).sum();
let incoming_flows: u64 = self.residual_graph.edges_directed(node, Direction::Incoming).map(|e| e.weight().0).sum();
if (incoming_flows - outgoing_flows) > 0 {
// this node has an excess flow - return node index & excess flow
return Some((node, incoming_flows - outgoing_flows));
}
}
None
}
// performs a BFS from sink to source and labels all nodes with their distance to the sink
fn compute_distances(&mut self) {
let mut distance_label = HashMap::new();
let mut visited = self.residual_graph.visit_map();
let mut queue = VecDeque::from([(self.sink, 0 as u64)]);
visited.visit(self.sink);
while let Some((node, distance)) = queue.pop_front() {
// first, visit nodes in queue
let outgoing_edges = self.residual_graph.edges_directed(node, Direction::Outgoing);
for edge in outgoing_edges {
let neighbor = edge.target();
if !visited.is_visited(&neighbor) {
visited.visit(neighbor);
distance_label.insert(neighbor, distance + 1);
queue.push_back((neighbor, distance + 1));
}
}
}
// set d(source) := |V|
distance_label.insert(self.source, self.residual_graph.node_count() as u64);
// set d(target) := 0
distance_label.insert(self.sink, 0);
assert!(distance_label.len() == self.residual_graph.node_count(), "Distance mapping is incomplete");
self.distance_label = Some(distance_label);
}
}
impl MaxflowAlgorithm for GoldbergTarjan {
fn step(&mut self) -> bool {
// Initialization: if this is the first iteration, set the current flow on all outgoing edges of the source to their capacity
if self.first_iteration {
for edge in self.residual_graph.edges_directed(self.source, Direction::Outgoing).map(|e| e.id()).collect::<Vec<_>>() {
let (source, target) = self.residual_graph.edge_endpoints(edge).expect("edge not connected");
// set all outgoing edges to their maximum capacity
let weight = self.residual_graph.edge_weight_mut(edge).expect("edge id not found");
let capacity = (*weight).1.clone();
(*weight).0 = capacity;
// increase capacity of the residual edge of the with the calculated bottleneck value
let residual_edge = self.residual_graph.find_edge(target, source).expect("residual edge not found");
let residual_weight: &mut (u64, u64) = self.residual_graph.edge_weight_mut(residual_edge).expect("edge not found");
(*residual_weight).1 += capacity;
}
self.first_iteration = false;
return false;
}
// if the distance labels were not already computed, compute them now
if self.distance_label == None {
self.compute_distances();
}
// Main loop: while there is an active node, continue
if let Some((node, excess)) = self.active_node() {
// find all outgoing edges with available capacity
let valid_edges: Vec<petgraph::prelude::EdgeIndex> = self.residual_graph
.edges_directed(node, Direction::Outgoing)
.filter(|e| (e.weight().1 - e.weight().0) > 0 )
.filter(|e| self.distance_label.as_ref().expect("distance labelling not present").get(&e.target().id()).expect("No distance label 1 found") < self.distance_label.as_ref().expect("distance labelling not present").get(&node).expect("No distance label 2 found") )
.map(|e| e.id())
.collect::<Vec<_>>();
if valid_edges.len() > 0 {
// node has valid edges -> push (move min[excess, capacity] to first valid edge)
let edge = valid_edges[0];
let available_capacity = self.residual_graph.edge_weight(edge).expect("Invalid edge index").1 - self.residual_graph.edge_weight(edge).expect("Invalid edge index").0;
let additional_flow = min(excess, available_capacity);
self.residual_graph.add_flow(edge, additional_flow);
} else {
// node has no valid edges -> relabel (increase node distance label to min[neighbors] + 1)
let neighbors = self.residual_graph
.edges_directed(node, Direction::Outgoing)
.filter(|e| (e.weight().1 - e.weight().0) > 0 )
.sorted_by_key(|e| self.distance_label.as_ref().expect("distance labelling not present").get(&e.target()))
.map(|e| e.target())
.collect::<Vec<_>>();
assert!(neighbors.len() > 0);
let lowest_distance = self.distance_label.as_ref().expect("distance labelling not present").get(&neighbors[0]).expect("No distance label found").clone();
self.distance_label.as_mut().expect("distance labelling not present").insert(node, lowest_distance + 1);
}
false
} else {
// algorithm terminates if there are no more active nodes
true
}
}
fn reset(&mut self) {
// set all flows in graph to 0
for edge in self.graph.edge_indices().collect::<Vec<_>>() {
let weight = self.graph.edge_weight_mut(edge).expect("edge not found");
(*weight).0 = 0;
}
// reset the residual & layer graph as well
self.residual_graph = self.graph.residual();
}
fn run(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
while !self.step() {
continue;
}
self.residual_graph.clone()
}
fn graph(&mut self) -> StableGraph<(f32, f32), (u64, u64)> {
self.residual_graph.clone()
}
fn name(&self) -> &'static str {
"Goldberg-Tarjan"
}
}

View File

@@ -1,7 +1,13 @@
mod edmonds_karp;
mod common;
mod ford_fulkerson;
mod edmonds_karp;
mod dinic;
mod goldberg_tarjan;
pub use ford_fulkerson::ford_fulkerson;
pub use edmonds_karp::edmonds_karp;
pub use dinic::dinic;
pub use common::MaxflowAlgorithm;
pub use common::MaxflowAlgorithmEnum;
pub use ford_fulkerson::FordFulkerson;
pub use edmonds_karp::EdmondsKarp;
pub use dinic::Dinic;
pub use goldberg_tarjan::GoldbergTarjan;

268
src/graph.rs Normal file
View File

@@ -0,0 +1,268 @@
use egui_graphs::{DefaultNodeShape, Graph, default_edge_transform, default_node_transform, to_graph_custom};
use petgraph::{algo::has_path_connecting, stable_graph::{EdgeIndex, NodeIndex, StableGraph}, visit::{EdgeRef, VisitMap, Visitable}, Directed, Direction};
use egui::{Pos2};
use std::collections::{HashMap, HashSet, VecDeque};
use std::cmp::min;
use crate::layout::CustomEdgeShape;
use egui::Color32;
// All methods which convert the graph into a GUI representations
pub trait GuiGraph {
fn to_gui_graph(&self, s: NodeIndex, t: NodeIndex) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>;
fn to_gui_graph_distance(&self, s: NodeIndex, t: NodeIndex, distance_labels: HashMap<NodeIndex, u64>) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>;
}
impl GuiGraph for StableGraph<(f32, f32), (u64, u64)> {
fn to_gui_graph(&self, s: NodeIndex, t: NodeIndex) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape> {
let mut graph = to_graph_custom(&self,
|n| {
let (x, y) = *n.payload();
default_node_transform(n);
n.set_location(Pos2::new(x, y));
n.set_label(String::from(""));
},
|e| {
let (flow, capacity): (u64, u64) = *e.payload();
default_edge_transform(e);
e.set_label(format!("{flow}:{capacity}"));
if flow > 0 {
e.set_selected(true);
}
}
);
graph.node_mut(s).expect("node index not found").set_label(String::from("s"));
graph.node_mut(t).expect("node index not found").set_label(String::from("t"));
graph.node_mut(s).expect("node index not found").set_color(Color32::BLUE);
graph.node_mut(t).expect("node index not found").set_color(Color32::ORANGE);
graph
}
fn to_gui_graph_distance(&self, s: NodeIndex, t: NodeIndex, distance_labels: HashMap<NodeIndex, u64>) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape> {
let mut graph = to_graph_custom(&self,
|n| {
let (x, y) = *n.payload();
default_node_transform(n);
n.set_location(Pos2::new(x, y));
if let Some(d) = distance_labels.get(&n.id()) {
if n.id() == s {
n.set_label(format!("s ({})", d));
} else if n.id() == t {
n.set_label(format!("t ({})", d));
} else {
n.set_label(format!("{}", d));
}
} else {
n.set_label(String::from(""));
}
},
|e| {
let (flow, capacity): (u64, u64) = *e.payload();
default_edge_transform(e);
e.set_label(format!("{flow}:{capacity}"));
if flow > 0 {
e.set_selected(true);
}
}
);
graph.node_mut(s).expect("node index not found").set_color(Color32::BLUE);
graph.node_mut(t).expect("node index not found").set_color(Color32::ORANGE);
graph
}
}
// all methods specific to MaxFlow problems
pub trait FlowGraph {
fn filter_empty_flows(&self) -> StableGraph<(f32, f32), (u64, u64)>;
fn reset_flow(&mut self);
fn prune_zero(&self) -> StableGraph<(f32, f32), (u64, u64)>;
fn valid_flow(&self, source: NodeIndex, sink: NodeIndex) -> bool;
fn total_flow(&self, source: NodeIndex, sink: NodeIndex) -> u64;
fn available_capacity(&self, edge: EdgeIndex) -> u64;
fn layer_graph(&self, source: NodeIndex, sink: NodeIndex) -> StableGraph<(f32, f32), (u64, u64)>;
fn add_flow(&mut self, edge: EdgeIndex, flow: u64);
fn residual(&self) -> StableGraph<(f32, f32), (u64, u64)>;
}
impl FlowGraph for StableGraph<(f32, f32), (u64, u64)> {
fn filter_empty_flows(&self) -> StableGraph<(f32, f32), (u64, u64)> {
self.filter_map( |_, n| {
Some(*n)
},
|_, &e| {
match e.0 {
0 => None,
_ => Some(e),
}
})
}
fn layer_graph(&self, source: NodeIndex, sink: NodeIndex) -> StableGraph<(f32, f32), (u64, u64)> {
// filter graph for edges with no remaining capacity
let mut layer_graph = self.filter_map(|_, n| {
Some(*n)
}, |_, &e| {
match e.1 - e.0 {
0 => None,
_ => Some(e),
}
});
// filter graph for s-t paths
let mut visited = layer_graph.visit_map();
let mut queue = VecDeque::from([(source, 0)]);
while let Some((node, layer)) = queue.pop_front() {
//let outgoing_edges = layer_graph.edges_directed(node, Direction::Outgoing).collect::<Vec<_>>();
let outgoing_edges = layer_graph.edges_directed(node, Direction::Outgoing).map(|e| e.id()).collect::<Vec<_>>();
visited.visit(node);
for edge in outgoing_edges {
let neighbor = layer_graph.edge_endpoints(edge).expect("edge index not found").1;
// if neighbor is unvisited, this is the shortest path to neighbor -> keep the edge
if !visited.is_visited(&neighbor) {
visited.visit(neighbor);
// stop traversing this path if destination is reached
if neighbor == sink {
continue;
} else {
// add neighbor to queue if destination is not reached
queue.push_back((neighbor, layer+1));
}
} else {
// if neighbor was visited before this edge is not on the shortest path -> remove it
layer_graph.remove_edge(edge);
}
}
}
layer_graph
}
// set all flows back to zero
fn reset_flow(&mut self) {
for edge in self.edge_indices().collect::<Vec<_>>() {
let weight = self.edge_weight_mut(edge).expect("edge not found");
(*weight).0 = 0;
}
}
// removes all edges where the current flow is zero & returns a pruned graph
fn prune_zero(&self) -> StableGraph<(f32, f32), (u64, u64)> {
let mut zero_graph = self.clone();
for edge in zero_graph.edge_indices().collect::<Vec<_>>() {
let weight = zero_graph.edge_weight(edge).expect("edge not found");
if weight.0 == 0 {
zero_graph.remove_edge(edge);
}
}
zero_graph
}
// check if flow is valid:
// - for every edge, the flow doesn't exceed the capacity
// - for every node, the amount of incoming flows is equal to the amount of outgoing flows
fn valid_flow(&self, source: NodeIndex, sink: NodeIndex) -> bool {
// capacity isn't exceeded
for edge in self.edge_indices().map(|e| e).collect::<Vec<_>>() {
if self.edge_weight(edge).expect("edge index not found").0 > self.edge_weight(edge).expect("edge index not found").1 {
return false;
}
}
// sum incoming flows = sum outgoing flows
for node in self.node_indices().map(|n| n).collect::<Vec<_>>() {
let outgoing_flows: u64 = self
.edges_directed(node, Direction::Outgoing)
.map(|e| e.weight().0)
.sum();
let incoming_flows: u64 = self
.edges_directed(node, Direction::Incoming)
.map(|e| e.weight().0)
.sum();
if incoming_flows != outgoing_flows {
if node != source && node != sink {
// println!("invalid flow at {:?} incoming flows: {} outgoing flows: {}", node, incoming_flows, outgoing_flows);
return false;
}
}
}
true
}
fn total_flow(&self, source: NodeIndex, sink: NodeIndex) -> u64 {
let outgoing_flows_source: u64 = self.edges_directed(source, Direction::Outgoing).map(|e| e.weight().0).sum();
let incoming_flows_source: u64 = self.edges_directed(source, Direction::Incoming).map(|e| e.weight().0).sum();
let outgoing_flows_sink: u64 = self.edges_directed(sink, Direction::Outgoing).map(|e| e.weight().0).sum();
let incoming_flows_sink: u64 = self.edges_directed(sink, Direction::Incoming).map(|e| e.weight().0).sum();
let total_flow_source = outgoing_flows_source - incoming_flows_source;
let total_flow_sink = incoming_flows_sink - outgoing_flows_sink;
if total_flow_sink != total_flow_source {
return 0;
}
return total_flow_source;
}
fn residual(&self) -> StableGraph<(f32, f32), (u64, u64)> {
let mut res_graph = self.clone();
for edge in res_graph.edge_indices().map(|e| e).collect::<Vec<_>>() {
let (source, target) = res_graph.edge_endpoints(edge).expect("edge not connected");
if let Some(reverse_edge) = res_graph.find_edge(target, source) {
let (flow, capacity) = *res_graph.edge_weight(edge).expect("forward edge not found");
let (reverse_flow, _reverse_capacity) = *res_graph.edge_weight(reverse_edge).expect("reverse edge not found");
// update the edge
res_graph.update_edge(source, target, (flow, capacity + reverse_flow));
} else {
// add a residual edge with a flow of 0 if the reverse edge doesn't exist
res_graph.add_edge(target, source, (0, 0));
}
}
res_graph
}
fn available_capacity(&self, edge: EdgeIndex) -> u64 {
self.edge_weight(edge).expect("edge not found").1 - self.edge_weight(edge).expect("edge not found").0
}
// adds the specified flow to the edge of a residual graph
fn add_flow(&mut self, forward_edge: EdgeIndex, flow: u64) {
// search for reverse edge
let (source, target) = self.edge_endpoints(forward_edge).expect("edge not connected");
let reverse_edge = self.find_edge(target, source).expect("reverse edge not found");
// calculate how much flow needs to be added on the reverse edge
let current_reverse_flow = self.edge_weight(reverse_edge).expect("reverse erge not found").0;
// maximum flow we can subtract from reverse edge
let reverse_flow = min(current_reverse_flow, flow);
let forward_flow = flow - reverse_flow;
if reverse_flow > 0 {
// decrease reverse flow
let reverse_weight: &mut (u64, u64) = self.edge_weight_mut(reverse_edge).expect("reverse edge not found");
(*reverse_weight).0 -= reverse_flow;
assert!(reverse_weight.0 <= reverse_weight.1);
// decrease forward capacity
let forward_weight: &mut (u64, u64) = self.edge_weight_mut(forward_edge).expect("edge not found");
(*forward_weight).1 -= reverse_flow;
assert!(forward_weight.0 <= forward_weight.1);
}
// add remaining flow to forward edge
if forward_flow > 0 {
// increase forward flow
let forward_weight: &mut (u64, u64) = self.edge_weight_mut(forward_edge).expect("edge not found");
(*forward_weight).0 += forward_flow;
assert!(forward_weight.0 <= forward_weight.1);
// increase reverse capacity
let reverse_weight: &mut (u64, u64) = self.edge_weight_mut(reverse_edge).expect("reverse edge not found");
(*reverse_weight).1 += forward_flow;
assert!(reverse_weight.0 <= reverse_weight.1);
}
}
}

View File

@@ -1,16 +1,12 @@
use std::f32::consts::PI;
use egui::{
epaint::{CubicBezierShape, QuadraticBezierShape, TextShape}, text::LayoutJob, Color32, FontFamily, FontId, Pos2, Shape, Stroke, TextFormat, Vec2
epaint::{CubicBezierShape, QuadraticBezierShape, TextShape}, Color32, FontFamily, FontId, Pos2, Shape, Stroke, Vec2
};
use petgraph::{matrix_graph::Nullable, stable_graph::IndexType, EdgeType};
use egui_graphs::{
DefaultEdgeShape, DisplayEdge, EdgeProps,
DefaultNodeShape, DisplayNode, NodeProps,
Node, DrawContext
};
use egui_graphs::{DisplayEdge, EdgeProps, DisplayNode, Node, DrawContext};
#[derive(Clone)]
@@ -34,9 +30,9 @@ impl<E: Clone> From<EdgeProps<E>> for CustomEdgeShape {
label_text: props.label,
width: 1.,
tip_size: 10.,
tip_angle: std::f32::consts::TAU / 30.,
curve_size: 15.,
tip_size: 6.,
tip_angle: std::f32::consts::TAU / 20.,
curve_size: 10.,
loop_size: 3.,
}
}

5
src/lib.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod algorithms;
pub mod graph;
pub mod random_generator;
pub mod layout;
pub mod testing;

View File

@@ -1,56 +1,151 @@
use eframe::{run_native, App, CreationContext, NativeOptions, Frame};
use egui::{CentralPanel, CollapsingHeader, ComboBox, Context, ScrollArea, SidePanel};
use egui_graphs::{DefaultEdgeShape, DefaultNodeShape, Graph, GraphView, LayoutRandom, LayoutStateRandom, SettingsStyle};
use geo::algorithm;
use petgraph::{stable_graph::{NodeIndex, StableGraph}, Directed};
use std::{fmt::Display, ptr::fn_addr_eq};
use egui::{CentralPanel, CollapsingHeader, ComboBox, Context, ScrollArea, SidePanel, Slider, Window};
use egui_graphs::{DefaultNodeShape, Graph, GraphView, LayoutRandom, LayoutStateRandom, SettingsStyle};
use petgraph::{stable_graph::{StableGraph}, Directed};
use std::{sync::{Arc, Mutex}, thread, thread::JoinHandle, time::Instant};
use random_generator::MaxflowProblem;
use layout::CustomEdgeShape;
use crate::algorithms::{ford_fulkerson, edmonds_karp, dinic};
use graph::{ResidualGraph, GuiGraph, FlowGraph};
use crate::algorithms::{MaxflowAlgorithm, MaxflowAlgorithmEnum, FordFulkerson, EdmondsKarp, Dinic, GoldbergTarjan};
use graph::{GuiGraph, FlowGraph};
use testing::{run_small_tests,run_large_tests};
mod random_generator;
mod algorithms;
mod layout;
mod graph;
mod testing;
type MaxflowFn = fn(StableGraph<(f32, f32), (u64, u64)>, NodeIndex, NodeIndex) -> StableGraph<(f32, f32), (u64, u64)>;
//type MaxflowFn = fn(StableGraph<(f32, f32), (u64, u64)>, NodeIndex, NodeIndex) -> StableGraph<(f32, f32), (u64, u64)>;
pub struct MaxflowApp {
g: Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>,
p: MaxflowProblem,
r: StableGraph<(f32, f32), (u64, u64)>,
gui_graph: Arc<Mutex<Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>>>,
problem: MaxflowProblem,
current_graph: StableGraph<(f32, f32), (u64, u64)>,
node_count: u64,
max_capacity: u64,
algorithm: MaxflowFn,
display_residual: bool,
algorithm: MaxflowAlgorithmEnum,
display_active_flows: bool,
display_layer_graph: bool,
algorithm_finished: bool,
num_steps: u64,
test_thread: Option<JoinHandle<String>>,
test_results: String,
test_results_open: bool,
delay: u64,
animation_runnning: bool,
last_update: Instant
}
impl MaxflowApp {
fn new(_: &CreationContext<'_>) -> Self {
let problem = MaxflowProblem::new(10, 10);
let problem = MaxflowProblem::new_planar_graph(10, 10);
Self {
g: problem.g.to_gui_graph(problem.s, problem.t, false),
r: StableGraph::default(),
p: problem, node_count: 10,
current_graph: problem.g.clone(),
gui_graph: Arc::new(Mutex::new(problem.g.to_gui_graph(problem.s, problem.t))),
algorithm: FordFulkerson::from_problem(&problem).into(),
problem,
node_count: 10,
max_capacity: 5,
algorithm: ford_fulkerson,
display_residual: false,
display_active_flows: false,
display_layer_graph: false,
algorithm_finished: false,
num_steps: 0,
test_thread: None,
test_results: String::default(),
test_results_open: false,
delay: 200,
animation_runnning: false,
last_update: Instant::now()
}
}
// reinitialize the current algorithm & reset its internal state
fn reset_state(&mut self) {
// undo finished run
self.algorithm_finished = false;
// set steps to zero
self.num_steps = 0;
// reinitialize the current graph from the problem
self.current_graph = self.problem.g.clone();
// assign new maxflow problem to the graph
match self.algorithm {
MaxflowAlgorithmEnum::FordFulkerson(_) => self.algorithm = FordFulkerson::from_problem(&self.problem).into(),
MaxflowAlgorithmEnum::EdmondsKarp(_) => self.algorithm = EdmondsKarp::from_problem(&self.problem).into(),
MaxflowAlgorithmEnum::Dinic(_) => self.algorithm = Dinic::from_problem(&self.problem).into(),
MaxflowAlgorithmEnum::GoldbergTarjan(_) => self.algorithm = GoldbergTarjan::from_problem(&self.problem).into(),
}
}
// display the current graph & apply display options from GUI
fn update_graph(&mut self) {
let mut graph = match self.display_residual {
true => &self.p.g.residual(),
false => &self.p.g,
};
let graph = &self.current_graph;
let mut gui_graph_lock = self.gui_graph.lock().unwrap();
if self.display_active_flows {
self.g = graph.filter_empty_flows().to_gui_graph(self.p.s, self.p.t, self.display_active_flows);
*gui_graph_lock = graph.filter_empty_flows().to_gui_graph(self.problem.s, self.problem.t);
} else {
self.g = graph.to_gui_graph(self.p.s, self.p.t, self.display_active_flows);
match &mut self.algorithm {
MaxflowAlgorithmEnum::GoldbergTarjan(g) => {
if let Some(l) = g.distance_label.clone() {
*gui_graph_lock = graph.to_gui_graph_distance(self.problem.s, self.problem.t, l);
} else {
*gui_graph_lock = graph.to_gui_graph(self.problem.s, self.problem.t);
}
},
MaxflowAlgorithmEnum::Dinic(_) => {
if self.display_layer_graph {
*gui_graph_lock = graph.layer_graph(self.problem.s, self.problem.t).to_gui_graph(self.problem.s, self.problem.t);
} else {
*gui_graph_lock = graph.to_gui_graph(self.problem.s, self.problem.t);
}
}
_ => *gui_graph_lock = graph.to_gui_graph(self.problem.s, self.problem.t)
}
}
}
fn run_algorithm(&mut self) {
match &mut self.algorithm {
MaxflowAlgorithmEnum::FordFulkerson(x) => {
self.current_graph = x.run();
},
MaxflowAlgorithmEnum::EdmondsKarp(x) => {
self.current_graph = x.run();
},
MaxflowAlgorithmEnum::Dinic(x) => {
self.current_graph = x.run();
},
MaxflowAlgorithmEnum::GoldbergTarjan(x) => {
self.current_graph = x.run();
}
}
}
fn step_algorithm(&mut self) -> bool {
match &mut self.algorithm {
MaxflowAlgorithmEnum::FordFulkerson(x) => {
let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph();
finished
},
MaxflowAlgorithmEnum::EdmondsKarp(x) => {
let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph();
finished
},
MaxflowAlgorithmEnum::Dinic(x) => {
let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph();
finished
},
MaxflowAlgorithmEnum::GoldbergTarjan(x) => {
let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph();
finished
}
}
}
}
@@ -59,7 +154,7 @@ impl App for MaxflowApp {
fn update(&mut self, ctx: &Context, _: &mut Frame) {
ctx.set_theme(egui::Theme::Light);
CentralPanel::default().show(ctx, |ui| {
ui.add(&mut GraphView::<_, _, _, _, _, CustomEdgeShape, LayoutStateRandom, LayoutRandom>::new(&mut self.g).with_styles(&SettingsStyle::default().with_labels_always(true)));
ui.add(&mut GraphView::<_, _, _, _, _, CustomEdgeShape, LayoutStateRandom, LayoutRandom>::new(&mut self.gui_graph.lock().unwrap()).with_styles(&SettingsStyle::default().with_labels_always(true)));
});
SidePanel::right("right_panel")
.min_width(200.)
@@ -69,12 +164,12 @@ impl App for MaxflowApp {
.default_open(true)
.show(ui, |ui| {
ui.label("node count");
ui.add(egui::DragValue::new(&mut self.node_count).range(2..=1000));
ui.add(egui::DragValue::new(&mut self.node_count).range(2..=50));
ui.label("maximum capacity");
ui.add(egui::DragValue::new(&mut self.max_capacity).range(1..=100));
// TODO: add generation strategy (random, pseudo-random)
if ui.button("generate graph").clicked() {
self.p = random_generator::MaxflowProblem::new(self.node_count, self.max_capacity);
self.problem = random_generator::MaxflowProblem::new_planar_graph(self.node_count, self.max_capacity);
self.reset_state();
self.update_graph();
}
});
@@ -83,43 +178,118 @@ impl App for MaxflowApp {
.default_open(true)
.show(ui, |ui| {
ComboBox::from_label("algorithm")
.selected_text(format!("{}", match self.algorithm {
_ if fn_addr_eq(self.algorithm, ford_fulkerson as MaxflowFn) => "Ford-Fulkerson",
_ if fn_addr_eq(self.algorithm, edmonds_karp as MaxflowFn) => "Edmonds-Karp",
_ if fn_addr_eq(self.algorithm, dinic as MaxflowFn) => "Dinic",
_ => "unknown"
}))
.selected_text(self.algorithm.to_string())
.show_ui(ui, |ui| {
ui.selectable_value(&mut self.algorithm, ford_fulkerson, "Ford-Fulkerson");
ui.selectable_value(&mut self.algorithm, edmonds_karp, "Edmonds-Karp");
ui.selectable_value(&mut self.algorithm, dinic, "Dinic");
let mut changed = ui.selectable_value(&mut self.algorithm, FordFulkerson::from_problem(&self.problem).into(), "Ford-Fulkerson").changed();
changed = changed || ui.selectable_value(&mut self.algorithm, EdmondsKarp::from_problem(&self.problem).into(), "Edmonds-Karp").changed();
changed = changed || ui.selectable_value(&mut self.algorithm, Dinic::from_problem(&self.problem).into(), "Dinic").changed();
changed = changed || ui.selectable_value(&mut self.algorithm, GoldbergTarjan::from_problem(&self.problem).into(), "Goldberg-Tarjan").changed();
//for algo in MaxflowAlgos::iter() {
//}
//ui.selectable_value(&mut self.algorithm, edmonds_karp, "Edmonds-Karp");
//ui.selectable_value(&mut self.algorithm, dinic, "Dinic");
if changed {
self.reset_state();
self.update_graph();
}
});
if ui.button("run algorithm").clicked() {
let max_flow_graph = ford_fulkerson(self.p.g.clone(), self.p.s, self.p.t);
self.p = MaxflowProblem::from(max_flow_graph, self.p.s, self.p.t);
self.update_graph();
}
ui.add(Slider::new(&mut self.delay, 0..=1000).text("delay").suffix("ms"));
ui.add_enabled_ui(!self.algorithm_finished, |ui| {
if ui.button("step").clicked() {
// check if algo is initialized
// if not, initialize
// run step function
self.algorithm_finished = self.step_algorithm();
self.update_graph();
}
if ui.button("run").clicked() {
self.animation_runnning = true;
}
});
if ui.button("reset").clicked() {
self.p.reset_flow();
self.reset_state();
self.update_graph();
}
// step button (disable when finished)
if self.animation_runnning {
if self.last_update.elapsed().as_millis() > self.delay as u128 {
self.algorithm_finished = self.step_algorithm();
self.update_graph();
self.animation_runnning = !self.algorithm_finished;
self.last_update = Instant::now();
}
}
});
CollapsingHeader::new("Display options")
.default_open(true)
.show(ui, |ui| {
if ui.checkbox(&mut self.display_residual, "show residual graph").changed() {
self.update_graph();
}
if ui.checkbox(&mut self.display_active_flows, "show active flows only").changed() {
self.update_graph();
}
ui.add_enabled_ui(matches!(self.algorithm, MaxflowAlgorithmEnum::Dinic(_)), |ui| {
if ui.checkbox(&mut self.display_layer_graph, "show layer graph (Dinic)").changed() {
self.update_graph();
}
});
});
CollapsingHeader::new("Properties")
.default_open(true)
.show(ui, |ui| {
ui.label(format!("is valid flow: {}", self.current_graph.valid_flow(self.problem.s, self.problem.t)));
ui.label(format!("total flow: {}", self.current_graph.total_flow(self.problem.s, self.problem.t)));
ui.label(format!("number of steps: {}", self.num_steps))
});
CollapsingHeader::new("Tests")
.default_open(true)
.show(ui, |ui| {
if ui.button("run tests (small)").clicked() {
// run tests
match &mut self.test_thread {
// other thread currently running - ignore
Some(_) => {},
None => {
self.test_thread = Some(thread::spawn(move || { run_small_tests() }));
}
};
}
if ui.button("run tests (large)").clicked() {
// run tests
match &mut self.test_thread {
// other thread currently running - ignore
Some(_) => {},
None => {
self.test_thread = Some(thread::spawn(|| { run_large_tests() }));
}
};
}
match &mut self.test_thread {
Some(t) => {
if t.is_finished() {
self.test_results = self.test_thread.take().map(JoinHandle::join).expect("No running thread found").expect("Could not join thread");
self.test_results_open = true;
}
},
None => {},
}
})
});
});
if self.test_results_open {
Window::new("Test Results")
.open(&mut self.test_results_open)
.show(ctx, |ui| {
ui.label(self.test_results.clone());
});
}
}
}

View File

@@ -1,16 +1,10 @@
use petgraph::data::Build;
use petgraph::stable_graph::{StableGraph, NodeIndex};
use petgraph::{Directed};
use egui_graphs::{default_edge_transform, default_node_transform, to_graph_custom, DefaultEdgeShape, DefaultNodeShape, Graph};
use egui::Pos2;
use rand::thread_rng;
use rand::Rng;
use rand::seq::SliceRandom;
use itertools::Itertools;
use geo::{Line, coord, line_intersection::{line_intersection, LineIntersection}};
use egui::Color32;
use crate::layout::CustomEdgeShape;
use std::cmp::min;
pub struct MaxflowProblem {
pub g: StableGraph<(f32, f32), (u64, u64)>,
@@ -19,7 +13,9 @@ pub struct MaxflowProblem {
}
impl MaxflowProblem {
pub fn new(num_nodes: u64, max_capacity: u64) -> Self {
// this function generates a maximally connected, planar graph with x/y coordinates which can be used for
// displaying a max-flow problem in a GUI. since the algorithm is rather inefficient, it only scales up to 50 nodes
pub fn new_planar_graph(num_nodes: u64, max_capacity: u64) -> Self {
// Node Type: (x,y) Edge Type: (current_flow, capacity)
let mut graph: StableGraph<(f32, f32), (u64, u64)> = StableGraph::default();
let mut nodes: Vec<NodeIndex> = Vec::new();
@@ -27,8 +23,20 @@ impl MaxflowProblem {
// generate nodes
for _i in 0..num_nodes {
let x: f32 = rng.gen_range(0.0..=400.0);
let y: f32 = rng.gen_range(0.0..=300.0);
// check if node is too close to another node (distance < 40)
let mut too_close = true;
let (mut x, mut y) = (0.0, 0.0);
// this is a poor attempt at modeling a do..while loop in Rust
while too_close {
too_close = false;
x = rng.gen_range(0.0..=400.0);
y = rng.gen_range(0.0..=300.0);
for node in &nodes {
if MaxflowProblem::distance(*graph.node_weight(*node).expect("node index not found"), (x,y)) < 40.0 {
too_close = true;
}
}
}
let n = graph.add_node((x,y));
nodes.push(n);
}
@@ -53,7 +61,7 @@ impl MaxflowProblem {
// insert the edge if possible
let mut inserted_edges: Vec<(NodeIndex, NodeIndex)> = Vec::new();
for (node1, node2, distance) in possible_edges {
for (node1, node2, _) in possible_edges {
// check for collision with already inserted edges
let mut intersects = false;
for (node3, node4) in &inserted_edges {
@@ -81,6 +89,36 @@ impl MaxflowProblem {
Self { g: graph, s: s, t: t }
}
// this function generates a fully connected random graph which can be used for solving max-flow problems
pub fn new_random_graph(num_nodes: u64, max_capacity: u64) -> Self {
let mut graph: StableGraph<(f32, f32), (u64, u64)> = StableGraph::default();
let mut nodes: Vec<NodeIndex> = Vec::new();
let mut rng = thread_rng();
for _i in 0..num_nodes {
let x = rng.gen_range(0.0..=400.0);
let y = rng.gen_range(0.0..=300.0);
let n = graph.add_node((x,y));
// choose a random number of nodes between 1 and 5 to connect to
let num_neighbors = rng.gen_range(min(nodes.len(), 1)..=min(nodes.len(), 5));
let neighbors = nodes.choose_multiple(&mut rng, num_neighbors);
for neighbor in neighbors {
// insert edges for each neighbor
let capacity1: u64 = rng.gen_range(1..=max_capacity);
let capacity2: u64 = rng.gen_range(1..=max_capacity);
graph.add_edge(n, *neighbor, (0, capacity1));
graph.add_edge(*neighbor, n, (0, capacity2));
}
nodes.push(n);
}
// choose source & sink nodes
let (s,t) = nodes.choose_multiple(&mut rng, 2).copied().collect_tuple::<(NodeIndex, NodeIndex)>().expect("not enough nodes");
Self { g: graph, s: s, t: t }
}
pub fn from(g: StableGraph<(f32, f32), (u64, u64)>, s: NodeIndex, t: NodeIndex) -> Self{
Self {g: g, s: s, t: t}
}
@@ -102,36 +140,4 @@ impl MaxflowProblem {
_ => false
}
}
// set all flows back to zero
pub fn reset_flow(&mut self) {
for edge in self.g.edge_indices().collect::<Vec<_>>() {
let weight = self.g.edge_weight_mut(edge).expect("edge not found");
(*weight).0 = 0;
}
}
pub fn to_gui_graph(&self) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape> {
let mut graph = to_graph_custom(&self.g,
|n| {
let (x, y) = *n.payload();
default_node_transform(n);
n.set_location(Pos2::new(x, y));
n.set_label(String::from(""));
},
|e| {
let (flow, capacity): (u64, u64) = *e.payload();
default_edge_transform(e);
e.set_label(format!("{flow}:{capacity}"));
if flow > 0 {
e.set_selected(true);
}
}
);
graph.node_mut(self.s).expect("node index not found").set_label(String::from("s"));
graph.node_mut(self.t).expect("node index not found").set_label(String::from("t"));
graph.node_mut(self.s).expect("node index not found").set_color(Color32::RED);
graph.node_mut(self.t).expect("node index not found").set_color(Color32::GREEN);
graph
}
}

81
src/testing.rs Normal file
View File

@@ -0,0 +1,81 @@
use itertools::Itertools;
use crate::{algorithms::{Dinic, EdmondsKarp, FordFulkerson, GoldbergTarjan, MaxflowAlgorithm, MaxflowAlgorithmEnum}, random_generator::MaxflowProblem, graph::FlowGraph};
use std::time::{Instant};
fn solve_problem_all_algos(problem: &MaxflowProblem) -> (f64, f64, f64, f64) {
let ff = FordFulkerson::from_problem(&problem);
let ek = EdmondsKarp::from_problem(&problem);
let di = Dinic::from_problem(&problem);
let gt = GoldbergTarjan::from_problem(&problem);
let mut instances:Vec<MaxflowAlgorithmEnum> = vec![ff.into(), ek.into(), di.into(), gt.into()];
let mut durations = vec![0.0; 4];
for (i,a) in instances.iter_mut().enumerate() {
let now = Instant::now();
a.run();
durations[i] = now.elapsed().as_secs_f64();
// check if the flow is valid
assert!(a.graph().valid_flow(problem.s, problem.t), "invalid flow");
}
// check if all algorithms return the same total flow
let total_flows: Vec<u64> = instances.iter_mut().map(|x| x.graph().total_flow(problem.s, problem.t)).collect();
assert!(total_flows.iter().all(|&x| x == total_flows[0]), "algorithms return different total flows");
return durations.into_iter().collect_tuple().unwrap();
}
fn run_tests(test_tuples: Vec<(u64, u64, u64)>) -> String {
let mut response = String::new();
for (problem_count, nodes, capacity) in test_tuples {
println!("Running test: {} times, {} nodes, {} capacity", problem_count, nodes, capacity);
response += format!("=== Testing with problem_count={}, nodes={}, capacity={}\n", problem_count, nodes, capacity).as_str();
let mut problems: Vec<_> = Vec::new();
for _ in 0..problem_count {
problems.push(MaxflowProblem::new_random_graph(nodes, capacity));
}
let mut durations = Vec::new();
for (_, problem) in problems.iter_mut().enumerate() {
durations.push(solve_problem_all_algos(problem));
}
let d0 = durations.iter().map(|x| x.0).sum::<f64>() / problem_count as f64;
let d1 = durations.iter().map(|x| x.1).sum::<f64>() / problem_count as f64;
let d2 = durations.iter().map(|x| x.2).sum::<f64>() / problem_count as f64;
let d3 = durations.iter().map(|x| x.3).sum::<f64>() / problem_count as f64;
response += format!("Ford-Fulkerson:\t{:02.8}s\nEdmonds-Karp:\t{:02.8}s\nDinic:\t\t\t\t\t\t{:02.8}s\nGoldberg-Tarjan:\t{:02.8}s\n\n", d0, d1, d2, d3).as_str();
}
return response
}
pub fn run_small_tests() -> String {
let test_tuples = [
// (number of problems, number of nodes, max. capacity)
(10, 10, 10),
(10, 10, 100),
(10, 50, 10),
(10, 50, 100),
(10, 100, 10),
(10, 100, 100)
];
return run_tests(test_tuples.into());
}
pub fn run_large_tests() -> String {
let test_tuples = [
// (number of problems, number of nodes, max. capacity)
(10, 100, 10),
(10, 100, 100),
(10, 100, 1000),
(10, 200, 10),
(10, 200, 100),
(10, 200, 1000),
(10, 300, 10),
(10, 300, 100),
(10, 300, 1000)
];
return run_tests(test_tuples.into());
}

54
tests.txt Normal file
View File

@@ -0,0 +1,54 @@
=== Testing with problem_count=10, nodes=100, capacity=10
Ford-Fulkerson: 0.00019723s
Edmonds-Karp: 0.00017104s
Dinic: 0.00114540s
Goldberg-Tarjan: 0.11118784s
=== Testing with problem_count=10, nodes=100, capacity=100
Ford-Fulkerson: 0.00044646s
Edmonds-Karp: 0.00030087s
Dinic: 0.00212037s
Goldberg-Tarjan: 0.15394399s
=== Testing with problem_count=10, nodes=100, capacity=1000
Ford-Fulkerson: 0.00028169s
Edmonds-Karp: 0.00017956s
Dinic: 0.00128019s
Goldberg-Tarjan: 0.11455003s
=== Testing with problem_count=10, nodes=200, capacity=10
Ford-Fulkerson: 0.00057035s
Edmonds-Karp: 0.00042638s
Dinic: 0.00270890s
Goldberg-Tarjan: 1.70483265s
=== Testing with problem_count=10, nodes=200, capacity=100
Ford-Fulkerson: 0.00121046s
Edmonds-Karp: 0.00045426s
Dinic: 0.00301752s
Goldberg-Tarjan: 1.48397911s
=== Testing with problem_count=10, nodes=200, capacity=1000
Ford-Fulkerson: 0.00183244s
Edmonds-Karp: 0.00062984s
Dinic: 0.00411386s
Goldberg-Tarjan: 1.49089764s
=== Testing with problem_count=10, nodes=300, capacity=10
Ford-Fulkerson: 0.00086230s
Edmonds-Karp: 0.00071712s
Dinic: 0.00423448s
Goldberg-Tarjan: 5.84010118s
=== Testing with problem_count=10, nodes=300, capacity=100
Ford-Fulkerson: 0.00164213s
Edmonds-Karp: 0.00040957s
Dinic: 0.00322493s
Goldberg-Tarjan: 1.97116912s
=== Testing with problem_count=10, nodes=300, capacity=1000
Ford-Fulkerson: 0.00362413s
Edmonds-Karp: 0.00094858s
Dinic: 0.00600265s
Goldberg-Tarjan: 5.16341617s

108
tests/example_flows.rs Normal file
View File

@@ -0,0 +1,108 @@
use maxflow_rs::graph::FlowGraph;
use petgraph::prelude::StableGraph;
use petgraph::stable_graph::NodeIndex;
use maxflow_rs::random_generator::MaxflowProblem;
use maxflow_rs::algorithms::{MaxflowAlgorithm, FordFulkerson, EdmondsKarp, Dinic, GoldbergTarjan};
// Example taken from: https://en.wikipedia.org/wiki/Dinic%27s_algorithm#Example
fn generate_small_maxflow_example() -> (StableGraph<(f32, f32), (u64, u64)>, StableGraph<(f32, f32), (u64, u64)>, NodeIndex, NodeIndex) {
let mut g: StableGraph<(f32, f32), (u64, u64)> = StableGraph::new();
let s = g.add_node((0., 5.));
let one = g.add_node((10., 0.));
let two = g.add_node((10., 10.));
let three = g.add_node((20., 0.));
let four = g.add_node((20., 10.));
let t = g.add_node((30., 5.));
g.add_edge(s, one, (0, 10));
g.add_edge(s, two, (0, 10));
g.add_edge(one, three, (0, 4));
g.add_edge(one, four, (0, 8));
g.add_edge(one, two, (0,2));
g.add_edge(two, four, (0,9));
g.add_edge(three, t, (0,10));
g.add_edge(four, three, (0,5)); // changed capacity to 5 to enforce a single optimal solution
g.add_edge(four, t, (0,10));
let mut m = g.clone();
m.update_edge(s, one, (10, 10));
m.update_edge(s, two, (9, 10));
m.update_edge(one, three, (4, 4));
m.update_edge(one, four, (6, 8));
m.update_edge(one, two, (0,2));
m.update_edge(two, four, (9,9));
m.update_edge(three, t, (9,10));
m.update_edge(four, three, (5,5)); // changed capacity to 5 to enforce a single optimal solution
m.update_edge(four, t, (10,10));
return (g, m, s, t);
}
fn graph_equal(one: StableGraph<(f32, f32), (u64, u64)>, other: StableGraph<(f32, f32), (u64, u64)>) -> bool {
// ensure all edges have the same weights
let edges = one.edge_indices().map(|e| e).collect::<Vec<_>>();
for edge in edges {
if one.edge_weight(edge).expect("edge index not found") != other.edge_weight(edge).expect("edge index not found") {
println!("Edge weights don't match");
println!("Graph 1: {:?}", one);
println!("Graph 2: {:?}", other);
return false;
}
}
// ensure all nodes have the same weights
let nodes = one.node_indices().map(|n| n).collect::<Vec<_>>();
for node in nodes {
if one.node_weight(node).expect("node index not found") != other.node_weight(node).expect("node index not found") {
println!("Node weights don't match");
println!("Graph 1: {:?}", one);
println!("Graph 2: {:?}", other);
return false;
}
}
true
}
#[test]
fn test_ford_fulkerson() {
let (g, m, s, t) = generate_small_maxflow_example();
let problem = MaxflowProblem::from(g, s, t);
let mut algo = FordFulkerson::from_problem(&problem);
let solution = algo.run();
assert!(graph_equal(solution.prune_zero(), m.residual().prune_zero()));
}
#[test]
fn test_edmonds_karp() {
let (g, m, s, t) = generate_small_maxflow_example();
let problem = MaxflowProblem::from(g, s, t);
let mut algo = EdmondsKarp::from_problem(&problem);
let solution = algo.run();
assert!(graph_equal(solution.prune_zero(), m.residual().prune_zero()));
}
#[test]
fn test_dinic() {
let (g, m, s, t) = generate_small_maxflow_example();
let problem = MaxflowProblem::from(g, s, t);
let mut algo = Dinic::from_problem(&problem);
let solution = algo.run();
assert!(graph_equal(solution.prune_zero(), m.residual().prune_zero()));
}
#[test]
fn test_goldberg_tarjan() {
let (g, m, s, t) = generate_small_maxflow_example();
let problem = MaxflowProblem::from(g, s, t);
let mut algo = GoldbergTarjan::from_problem(&problem);
let solution = algo.run();
assert!(graph_equal(solution.prune_zero(), m.residual().prune_zero()));
}

2
tests/valid_flow.rs Normal file
View File

@@ -0,0 +1,2 @@
// check capacity at each edge (incoming flows = outgoing flows)
// check capacity isn't violated (flow <= capacity)