Compare commits
12 Commits
5680b67d95
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b67d74b31 | |||
| 5e064c73ed | |||
| ba3cd9591e | |||
| 8d28ef44be | |||
| a13c1a5001 | |||
| c21d648c8f | |||
| 3f93cb57d3 | |||
| 6b17e583e6 | |||
| 681cdee139 | |||
| 111759fbbb | |||
| fdcb646bd2 | |||
| 5d81549ac0 |
13
Cargo.lock
generated
13
Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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
101
README.md
Normal 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)
|
||||
@@ -1,13 +0,0 @@
|
||||
// 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 random_generator::MaxflowProblem;
|
||||
|
||||
mod random_generator;
|
||||
|
||||
impl FordFulkerson {
|
||||
fn solve(problem: MaxflowProblem) -> ???solution??? {
|
||||
|
||||
}
|
||||
}
|
||||
61
src/algorithms/common.rs
Normal file
61
src/algorithms/common.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/algorithms/dinic.rs
Normal file
148
src/algorithms/dinic.rs
Normal file
@@ -0,0 +1,148 @@
|
||||
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};
|
||||
|
||||
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)>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
});
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
100
src/algorithms/edmonds_karp.rs
Normal file
100
src/algorithms/edmonds_karp.rs
Normal file
@@ -0,0 +1,100 @@
|
||||
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};
|
||||
|
||||
pub struct EdmondsKarp {
|
||||
graph: StableGraph<(f32, f32), (u64, u64)>,
|
||||
source: NodeIndex,
|
||||
sink: NodeIndex,
|
||||
residual_graph: StableGraph<(f32, f32), (u64, u64)>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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]));
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
impl MaxflowAlgorithm for EdmondsKarp {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
99
src/algorithms/ford_fulkerson.rs
Normal file
99
src/algorithms/ford_fulkerson.rs
Normal file
@@ -0,0 +1,99 @@
|
||||
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};
|
||||
|
||||
pub struct FordFulkerson {
|
||||
graph: StableGraph<(f32, f32), (u64, u64)>,
|
||||
source: NodeIndex,
|
||||
sink: NodeIndex,
|
||||
residual_graph: StableGraph<(f32, f32), (u64, u64)>,
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
});
|
||||
|
||||
// increase flow with bottleneck capacity along the augmenting path
|
||||
for edge in edges {
|
||||
self.residual_graph.add_flow(edge, bottleneck_capacity);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
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 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 {
|
||||
"Ford-Fulkerson"
|
||||
}
|
||||
}
|
||||
161
src/algorithms/goldberg_tarjan.rs
Normal file
161
src/algorithms/goldberg_tarjan.rs
Normal 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"
|
||||
}
|
||||
}
|
||||
13
src/algorithms/mod.rs
Normal file
13
src/algorithms/mod.rs
Normal file
@@ -0,0 +1,13 @@
|
||||
mod common;
|
||||
mod ford_fulkerson;
|
||||
mod edmonds_karp;
|
||||
mod dinic;
|
||||
mod goldberg_tarjan;
|
||||
|
||||
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
268
src/graph.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
353
src/layout.rs
Normal file
353
src/layout.rs
Normal file
@@ -0,0 +1,353 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use egui::{
|
||||
epaint::{CubicBezierShape, QuadraticBezierShape, TextShape}, Color32, FontFamily, FontId, Pos2, Shape, Stroke, Vec2
|
||||
};
|
||||
|
||||
use petgraph::{matrix_graph::Nullable, stable_graph::IndexType, EdgeType};
|
||||
|
||||
use egui_graphs::{DisplayEdge, EdgeProps, DisplayNode, Node, DrawContext};
|
||||
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CustomEdgeShape {
|
||||
pub order: usize,
|
||||
pub selected: bool,
|
||||
|
||||
pub width: f32,
|
||||
pub tip_size: f32,
|
||||
pub tip_angle: f32,
|
||||
pub curve_size: f32,
|
||||
pub loop_size: f32,
|
||||
pub label_text: String,
|
||||
}
|
||||
|
||||
impl<E: Clone> From<EdgeProps<E>> for CustomEdgeShape {
|
||||
fn from(props: EdgeProps<E>) -> Self {
|
||||
Self {
|
||||
order: props.order,
|
||||
selected: props.selected,
|
||||
label_text: props.label,
|
||||
|
||||
width: 1.,
|
||||
tip_size: 6.,
|
||||
tip_angle: std::f32::consts::TAU / 20.,
|
||||
curve_size: 10.,
|
||||
loop_size: 3.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, Ix>>
|
||||
DisplayEdge<N, E, Ty, Ix, D> for CustomEdgeShape
|
||||
{
|
||||
// helper functions
|
||||
// copied from: https://github.com/SeverinAlexB/pknames/blob/d042b6843ee503d92ae36742bab81b14574d9ad3/cli/src/visualization/edge_vis.rs
|
||||
fn is_inside(
|
||||
&self,
|
||||
start: &Node<N, E, Ty, Ix, D>,
|
||||
end: &Node<N, E, Ty, Ix, D>,
|
||||
pos: egui::Pos2,
|
||||
) -> bool {
|
||||
if start.id() == end.id() {
|
||||
return is_inside_loop(start, self, pos);
|
||||
}
|
||||
|
||||
let pos_start = start.location();
|
||||
let pos_end = end.location();
|
||||
|
||||
if self.order == 0 {
|
||||
return is_inside_line(pos_start, pos_end, pos, self);
|
||||
}
|
||||
|
||||
is_inside_curve(start, end, self, pos)
|
||||
}
|
||||
|
||||
fn shapes(
|
||||
&mut self,
|
||||
start: &Node<N, E, Ty, Ix, D>,
|
||||
end: &Node<N, E, Ty, Ix, D>,
|
||||
ctx: &DrawContext,
|
||||
) -> Vec<egui::Shape> {
|
||||
// fixed properties
|
||||
let edge_color = match self.selected {
|
||||
true => Color32::LIGHT_BLUE,
|
||||
false => Color32::LIGHT_GRAY
|
||||
};
|
||||
let label_color = Color32::DARK_GRAY;
|
||||
//let label_visible = true;
|
||||
//let style = ctx.ctx.style().visuals.widgets.inactive;
|
||||
|
||||
// calculate coordinates
|
||||
let dir = (end.location() - start.location()).normalized();
|
||||
let start_connector_point = start.display().closest_boundary_point(dir);
|
||||
let end_connector_point = end.display().closest_boundary_point(-dir);
|
||||
let tip_end = end_connector_point;
|
||||
let edge_start = start_connector_point;
|
||||
let edge_end = end_connector_point;
|
||||
|
||||
// draw curved edge
|
||||
let stroke_edge = Stroke::new(self.width * ctx.meta.zoom, edge_color);
|
||||
let dir_perpendicular = Vec2::new(-dir.y, dir.x);
|
||||
let center_point = (edge_start + edge_end.to_vec2()).to_vec2() / 2.;
|
||||
let control_point = (center_point + dir_perpendicular * self.curve_size as f32).to_pos2();
|
||||
let tip_dir = (control_point-tip_end).normalized();
|
||||
//println!("edge_start: {:?} edge_end {:?} center_point {:?} dir_p {:?} control_point {:?}", edge_start, edge_end, center_point, dir_perpendicular, control_point);
|
||||
|
||||
let arrow_tip_dir_1 = rotate_vector(tip_dir, self.tip_angle) * self.tip_size;
|
||||
let arrow_tip_dir_2 = rotate_vector(tip_dir, -self.tip_angle) * self.tip_size;
|
||||
let tip_start_1 = tip_end + arrow_tip_dir_1;
|
||||
let tip_start_2 = tip_end + arrow_tip_dir_2;
|
||||
|
||||
let edge_end_curved = point_between(tip_start_1, tip_start_2);
|
||||
let line_curved = QuadraticBezierShape::from_points_stroke(
|
||||
[
|
||||
ctx.meta.canvas_to_screen_pos(edge_start),
|
||||
ctx.meta.canvas_to_screen_pos(control_point),
|
||||
ctx.meta.canvas_to_screen_pos(edge_end_curved),
|
||||
],
|
||||
false,
|
||||
Color32::TRANSPARENT,
|
||||
stroke_edge,
|
||||
);
|
||||
|
||||
// draw tip/arrow
|
||||
let stroke_tip = Stroke::new(0., edge_color);
|
||||
let line_curved_tip = Shape::convex_polygon(
|
||||
vec![
|
||||
ctx.meta.canvas_to_screen_pos(tip_end),
|
||||
ctx.meta.canvas_to_screen_pos(tip_start_1),
|
||||
ctx.meta.canvas_to_screen_pos(tip_start_2),
|
||||
],
|
||||
edge_color,
|
||||
stroke_tip,
|
||||
);
|
||||
|
||||
|
||||
// draw label
|
||||
let size = (::egui_graphs::node_size(start, dir) + egui_graphs::node_size(end, dir)) / 2.;
|
||||
let galley = ctx.ctx.fonts(|f| {
|
||||
f.layout_no_wrap(
|
||||
self.label_text.clone(),
|
||||
FontId::new(ctx.meta.canvas_to_screen_size(size), FontFamily::Monospace),
|
||||
label_color,
|
||||
)
|
||||
});
|
||||
|
||||
let flattened_curve = line_curved.flatten(None);
|
||||
let median = *flattened_curve.get(flattened_curve.len() / 2).unwrap();
|
||||
|
||||
let label_width = galley.rect.width();
|
||||
let label_height = galley.rect.height();
|
||||
let pos = Pos2::new(median.x - label_width / 2., median.y - label_height);
|
||||
|
||||
let label_shape = TextShape::new(pos, galley, label_color);
|
||||
|
||||
vec![line_curved.into(), line_curved_tip, label_shape.into()]
|
||||
}
|
||||
|
||||
fn update(&mut self, state: &egui_graphs::EdgeProps<E>) {
|
||||
self.order = state.order;
|
||||
self.selected = state.selected;
|
||||
self.label_text = state.label.to_string();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Everything below is copied from:
|
||||
// https://github.com/SeverinAlexB/pknames/blob/d042b6843ee503d92ae36742bab81b14574d9ad3/cli/src/visualization/edge_vis.rs
|
||||
fn shape_looped(
|
||||
node_size: f32,
|
||||
node_center: Pos2,
|
||||
stroke: Stroke,
|
||||
e: &CustomEdgeShape,
|
||||
) -> CubicBezierShape {
|
||||
let center_horizon_angle = PI / 4.;
|
||||
let y_intersect = node_center.y - node_size * center_horizon_angle.sin();
|
||||
|
||||
let edge_start = Pos2::new(
|
||||
node_center.x - node_size * center_horizon_angle.cos(),
|
||||
y_intersect,
|
||||
);
|
||||
let edge_end = Pos2::new(
|
||||
node_center.x + node_size * center_horizon_angle.cos(),
|
||||
y_intersect,
|
||||
);
|
||||
|
||||
let loop_size = node_size * (e.loop_size + e.order as f32);
|
||||
|
||||
let control_point1 = Pos2::new(node_center.x + loop_size, node_center.y - loop_size);
|
||||
let control_point2 = Pos2::new(node_center.x - loop_size, node_center.y - loop_size);
|
||||
|
||||
CubicBezierShape::from_points_stroke(
|
||||
[edge_end, control_point1, control_point2, edge_start],
|
||||
false,
|
||||
Color32::default(),
|
||||
stroke,
|
||||
)
|
||||
}
|
||||
|
||||
fn shape_curved(
|
||||
pos_start: Pos2,
|
||||
pos_end: Pos2,
|
||||
size_start: f32,
|
||||
size_end: f32,
|
||||
stroke: Stroke,
|
||||
e: &CustomEdgeShape,
|
||||
) -> QuadraticBezierShape {
|
||||
let vec = pos_end - pos_start;
|
||||
let dist: f32 = vec.length();
|
||||
let dir = vec / dist;
|
||||
|
||||
let start_node_radius_vec = Vec2::new(size_start, size_start) * dir;
|
||||
let end_node_radius_vec = Vec2::new(size_end, size_end) * dir;
|
||||
|
||||
let tip_end = pos_start + vec - end_node_radius_vec;
|
||||
|
||||
let edge_start = pos_start + start_node_radius_vec;
|
||||
let edge_end = pos_end + end_node_radius_vec;
|
||||
|
||||
let dir_perpendicular = Vec2::new(-dir.y, dir.x);
|
||||
let center_point = (edge_start + tip_end.to_vec2()).to_vec2() / 2.0;
|
||||
let control_point =
|
||||
(center_point + dir_perpendicular * e.curve_size * e.order as f32).to_pos2();
|
||||
|
||||
QuadraticBezierShape::from_points_stroke(
|
||||
[edge_start, control_point, edge_end],
|
||||
false,
|
||||
stroke.color,
|
||||
stroke,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_inside_loop<E: Clone, N: Clone, Ix: IndexType, Ty: EdgeType, D: DisplayNode<N, E, Ty, Ix>>(
|
||||
node: &Node<N, E, Ty, Ix, D>,
|
||||
e: &CustomEdgeShape,
|
||||
pos: Pos2,
|
||||
) -> bool {
|
||||
let node_size = node_size(node);
|
||||
|
||||
let shape = shape_looped(node_size, node.location(), Stroke::default(), e);
|
||||
is_point_on_cubic_bezier_curve(pos, shape, e.width)
|
||||
}
|
||||
|
||||
fn is_inside_line(pos_start: Pos2, pos_end: Pos2, pos: Pos2, e: &CustomEdgeShape) -> bool {
|
||||
let distance = distance_segment_to_point(pos_start, pos_end, pos);
|
||||
distance <= e.width
|
||||
}
|
||||
|
||||
fn is_inside_curve<
|
||||
N: Clone,
|
||||
E: Clone,
|
||||
Ty: EdgeType,
|
||||
Ix: IndexType,
|
||||
D: DisplayNode<N, E, Ty, Ix>,
|
||||
>(
|
||||
node_start: &Node<N, E, Ty, Ix, D>,
|
||||
node_end: &Node<N, E, Ty, Ix, D>,
|
||||
e: &CustomEdgeShape,
|
||||
pos: Pos2,
|
||||
) -> bool {
|
||||
let pos_start = node_start.location();
|
||||
let pos_end = node_end.location();
|
||||
|
||||
let size_start = node_size(node_start);
|
||||
let size_end = node_size(node_end);
|
||||
|
||||
let shape = shape_curved(
|
||||
pos_start,
|
||||
pos_end,
|
||||
size_start,
|
||||
size_end,
|
||||
Stroke::default(),
|
||||
e,
|
||||
);
|
||||
is_point_on_quadratic_bezier_curve(pos, shape, e.width)
|
||||
}
|
||||
|
||||
fn node_size<N: Clone, E: Clone, Ty: EdgeType, Ix: IndexType, D: DisplayNode<N, E, Ty, Ix>>(
|
||||
node: &Node<N, E, Ty, Ix, D>,
|
||||
) -> f32 {
|
||||
let left_dir = Vec2::new(-1., 0.);
|
||||
let connector_left = node.display().closest_boundary_point(left_dir);
|
||||
let connector_right = node.display().closest_boundary_point(-left_dir);
|
||||
|
||||
(connector_right.x - connector_left.x) / 2.
|
||||
}
|
||||
|
||||
/// Returns the distance from line segment `a``b` to point `c`.
|
||||
/// Adapted from https://stackoverflow.com/questions/1073336/circle-line-segment-collision-detection-algorithm
|
||||
fn distance_segment_to_point(a: Pos2, b: Pos2, point: Pos2) -> f32 {
|
||||
let ac = point - a;
|
||||
let ab = b - a;
|
||||
|
||||
let d = a + proj(ac, ab);
|
||||
|
||||
let ad = d - a;
|
||||
|
||||
let k = if ab.x.abs() > ab.y.abs() {
|
||||
ad.x / ab.x
|
||||
} else {
|
||||
ad.y / ab.y
|
||||
};
|
||||
|
||||
if k <= 0.0 {
|
||||
return hypot2(point.to_vec2(), a.to_vec2()).sqrt();
|
||||
} else if k >= 1.0 {
|
||||
return hypot2(point.to_vec2(), b.to_vec2()).sqrt();
|
||||
}
|
||||
|
||||
hypot2(point.to_vec2(), d.to_vec2()).sqrt()
|
||||
}
|
||||
|
||||
/// Calculates the square of the Euclidean distance between vectors `a` and `b`.
|
||||
fn hypot2(a: Vec2, b: Vec2) -> f32 {
|
||||
(a - b).dot(a - b)
|
||||
}
|
||||
|
||||
/// Calculates the projection of vector `a` onto vector `b`.
|
||||
fn proj(a: Vec2, b: Vec2) -> Vec2 {
|
||||
let k = a.dot(b) / b.dot(b);
|
||||
Vec2::new(k * b.x, k * b.y)
|
||||
}
|
||||
|
||||
fn is_point_on_cubic_bezier_curve(point: Pos2, curve: CubicBezierShape, width: f32) -> bool {
|
||||
is_point_on_bezier_curve(point, curve.flatten(Option::new(10.0)), width)
|
||||
}
|
||||
|
||||
fn is_point_on_quadratic_bezier_curve(
|
||||
point: Pos2,
|
||||
curve: QuadraticBezierShape,
|
||||
width: f32,
|
||||
) -> bool {
|
||||
is_point_on_bezier_curve(point, curve.flatten(Option::new(0.3)), width)
|
||||
}
|
||||
|
||||
fn is_point_on_bezier_curve(point: Pos2, curve_points: Vec<Pos2>, width: f32) -> bool {
|
||||
let mut previous_point = None;
|
||||
for p in curve_points {
|
||||
if let Some(pp) = previous_point {
|
||||
let distance = distance_segment_to_point(p, pp, point);
|
||||
if distance < width {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
previous_point = Some(p);
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// rotates vector by angle
|
||||
fn rotate_vector(vec: Vec2, angle: f32) -> Vec2 {
|
||||
let cos = angle.cos();
|
||||
let sin = angle.sin();
|
||||
Vec2::new(cos * vec.x - sin * vec.y, sin * vec.x + cos * vec.y)
|
||||
}
|
||||
|
||||
/// finds point exactly in the middle between 2 points
|
||||
fn point_between(p1: Pos2, p2: Pos2) -> Pos2 {
|
||||
let base = p1 - p2;
|
||||
let base_len = base.length();
|
||||
let dir = base / base_len;
|
||||
p1 - (base_len / 2.) * dir
|
||||
}
|
||||
5
src/lib.rs
Normal file
5
src/lib.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod algorithms;
|
||||
pub mod graph;
|
||||
pub mod random_generator;
|
||||
pub mod layout;
|
||||
pub mod testing;
|
||||
288
src/main.rs
288
src/main.rs
@@ -1,47 +1,295 @@
|
||||
use eframe::{run_native, App, CreationContext, NativeOptions, Frame};
|
||||
use egui::{CentralPanel, SidePanel, Context};
|
||||
use egui_graphs::{GraphView, Graph, SettingsStyle, LayoutRandom, LayoutStateRandom};
|
||||
|
||||
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 algorithms::{ford_fulk
|
||||
//use petgraph::stable_graph::StableGraph;
|
||||
use layout::CustomEdgeShape;
|
||||
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)>;
|
||||
|
||||
pub struct MaxflowApp {
|
||||
g: Graph<(), u64>,
|
||||
p: MaxflowProblem,
|
||||
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: 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);
|
||||
Self { g: problem.g.clone(), p: problem, node_count: 10, max_capacity: 5 }
|
||||
let problem = MaxflowProblem::new_planar_graph(10, 10);
|
||||
Self {
|
||||
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,
|
||||
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 graph = &self.current_graph;
|
||||
let mut gui_graph_lock = self.gui_graph.lock().unwrap();
|
||||
if self.display_active_flows {
|
||||
*gui_graph_lock = graph.filter_empty_flows().to_gui_graph(self.problem.s, self.problem.t);
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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::<_, _, _, _, _, _, 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.)
|
||||
.show(ctx, |ui| {
|
||||
ui.label("node count");
|
||||
ui.add(egui::DragValue::new(&mut self.node_count).range(1..=1000));
|
||||
ui.label("maximum capacity");
|
||||
ui.add(egui::DragValue::new(&mut self.max_capacity).range(1..=10));
|
||||
if ui.button("generate graph").clicked() {
|
||||
let problem = random_generator::MaxflowProblem::new(self.node_count, self.max_capacity);
|
||||
self.g = problem.g;
|
||||
}
|
||||
if ui.button("run algorithm").clicked() {
|
||||
}
|
||||
ScrollArea::vertical().show(ui, |ui| {
|
||||
CollapsingHeader::new("Graph generation")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("node count");
|
||||
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));
|
||||
if ui.button("generate graph").clicked() {
|
||||
self.problem = random_generator::MaxflowProblem::new_planar_graph(self.node_count, self.max_capacity);
|
||||
self.reset_state();
|
||||
self.update_graph();
|
||||
}
|
||||
});
|
||||
|
||||
CollapsingHeader::new("Max-flow algorithms")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
ComboBox::from_label("algorithm")
|
||||
.selected_text(self.algorithm.to_string())
|
||||
.show_ui(ui, |ui| {
|
||||
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();
|
||||
}
|
||||
});
|
||||
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.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_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());
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,66 +1,75 @@
|
||||
use petgraph::stable_graph::{StableGraph, NodeIndex};
|
||||
use petgraph::{Directed};
|
||||
use egui_graphs::{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 std::cmp::min;
|
||||
|
||||
pub struct MaxflowProblem {
|
||||
pub g: Graph<(), u64, Directed, u32, egui_graphs::DefaultNodeShape, egui_graphs::DefaultEdgeShape>,
|
||||
pub g: StableGraph<(f32, f32), (u64, u64)>,
|
||||
pub s: NodeIndex,
|
||||
pub t: NodeIndex,
|
||||
}
|
||||
|
||||
impl MaxflowProblem {
|
||||
pub fn new(num_nodes: u64, max_capacity: u64) -> Self {
|
||||
let mut graph: Graph<_, u64> = Graph::new(StableGraph::default());
|
||||
// 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();
|
||||
let mut rng = thread_rng();
|
||||
|
||||
// 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);
|
||||
let pos = Pos2::new(x,y);
|
||||
let n = graph.add_node_with_label_and_location((), String::new(), pos);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// select source (s) and sink (t)
|
||||
let s = *nodes.choose(&mut rng).expect("no nodes found");
|
||||
let t = *nodes.choose(&mut rng).expect("no nodes found");
|
||||
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::RED);
|
||||
graph.node_mut(t).expect("node index not found").set_color(Color32::GREEN);
|
||||
let (s,t) = nodes.choose_multiple(&mut rng, 2).copied().collect_tuple::<(NodeIndex, NodeIndex)>().expect("not enough nodes");
|
||||
|
||||
// iterate over all possible edges
|
||||
let mut possible_edges: Vec<(NodeIndex, NodeIndex, f32)> = Vec::new();
|
||||
for pair in nodes.clone().into_iter().combinations(2) {
|
||||
// calculate distance & append tuple (node_a, node_b, distance) to possible_edges
|
||||
let distance = Self::distance(graph.node(pair[0]).expect("node index not found").location(), graph.node(pair[1]).expect("node index not found").location());
|
||||
let distance = Self::distance(
|
||||
*graph.node_weight(pair[0]).expect("node index not found"),
|
||||
*graph.node_weight(pair[1]).expect("node index not found")
|
||||
);
|
||||
//let distance = Self::distance(graph.node(pair[0]).expect("node index not found").location(), graph.node(pair[1]).expect("node index not found").location());
|
||||
possible_edges.push((pair[0], pair[1], distance));
|
||||
}
|
||||
|
||||
// sort mapping by distance
|
||||
possible_edges.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap());
|
||||
println!("possible_edges len: {}", possible_edges.len());
|
||||
|
||||
// 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 {
|
||||
if Self::intersects(
|
||||
graph.node(node1).expect("node index not found").location(),
|
||||
graph.node(node2).expect("node index not found").location(),
|
||||
graph.node(*node3).expect("node index not found").location(),
|
||||
graph.node(*node4).expect("node index not found").location(),
|
||||
*graph.node_weight(node1).expect("node index not found"),
|
||||
*graph.node_weight(node2).expect("node index not found"),
|
||||
*graph.node_weight(*node3).expect("node index not found"),
|
||||
*graph.node_weight(*node4).expect("node index not found"),
|
||||
) {
|
||||
intersects = true;
|
||||
break;
|
||||
@@ -70,26 +79,61 @@ impl MaxflowProblem {
|
||||
// otherwise insert the edge and its residual edge
|
||||
inserted_edges.push((node1, node2));
|
||||
inserted_edges.push((node2, node1));
|
||||
let capacity: u64 = rng.gen_range(1..=max_capacity);
|
||||
graph.add_edge_with_label(node1, node2, capacity, capacity.to_string());
|
||||
graph.add_edge_with_label(node2, node1, capacity, capacity.to_string());
|
||||
let capacity1: u64 = rng.gen_range(1..=max_capacity);
|
||||
let capacity2: u64 = rng.gen_range(1..=max_capacity);
|
||||
graph.add_edge(node1, node2, (0, capacity1));
|
||||
graph.add_edge(node2, node1, (0, capacity2));
|
||||
}
|
||||
}
|
||||
|
||||
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}
|
||||
}
|
||||
|
||||
// returns the eudclidean distance between two points
|
||||
fn distance(a: Pos2, b: Pos2) -> f32 {
|
||||
let delta_x = a.x - b.x;
|
||||
let delta_y = a.y - b.y;
|
||||
fn distance(a: (f32, f32), b: (f32, f32)) -> f32 {
|
||||
let delta_x = a.0 - b.0;
|
||||
let delta_y = a.1 - b.1;
|
||||
return f32::sqrt(delta_x.powf(2.) + delta_y.powf(2.));
|
||||
}
|
||||
|
||||
// checks if the lines between a-b and c-d intersect
|
||||
fn intersects(a: Pos2, b: Pos2, c: Pos2, d: Pos2) -> bool {
|
||||
let line1 = Line::new(coord! {x: a.x, y: a.y }, coord! {x: b.x, y: b.y } );
|
||||
let line2 = Line::new(coord! {x: c.x, y: c.y }, coord! {x: d.x, y: d.y } );
|
||||
fn intersects(a: (f32, f32), b: (f32, f32), c: (f32, f32), d: (f32, f32)) -> bool {
|
||||
let line1 = Line::new(coord! {x: a.0, y: a.1 }, coord! {x: b.0, y: b.1 } );
|
||||
let line2 = Line::new(coord! {x: c.0, y: c.1 }, coord! {x: d.0, y: d.1 } );
|
||||
match line_intersection(line1, line2) {
|
||||
// only return true for 'proper' intersections which are not at the end of a line
|
||||
Some(LineIntersection::SinglePoint { is_proper: true, .. }) => true,
|
||||
|
||||
81
src/testing.rs
Normal file
81
src/testing.rs
Normal 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
54
tests.txt
Normal 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
108
tests/example_flows.rs
Normal 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
2
tests/valid_flow.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
// check capacity at each edge (incoming flows = outgoing flows)
|
||||
// check capacity isn't violated (flow <= capacity)
|
||||
Reference in New Issue
Block a user