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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a3d8a32ae18130a3c84dd492d4215c3d913c3b07c6b63c2eb3eb7ff1101ab7bf"
|
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]]
|
[[package]]
|
||||||
name = "enumflags2"
|
name = "enumflags2"
|
||||||
version = "0.7.11"
|
version = "0.7.11"
|
||||||
@@ -1908,6 +1920,7 @@ dependencies = [
|
|||||||
"eframe",
|
"eframe",
|
||||||
"egui",
|
"egui",
|
||||||
"egui_graphs",
|
"egui_graphs",
|
||||||
|
"enum_dispatch",
|
||||||
"geo",
|
"geo",
|
||||||
"itertools 0.14.0",
|
"itertools 0.14.0",
|
||||||
"petgraph",
|
"petgraph",
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ ecolor = "0.31.1"
|
|||||||
eframe = "0.30"
|
eframe = "0.30"
|
||||||
egui = "0.30"
|
egui = "0.30"
|
||||||
egui_graphs = "0.23.0"
|
egui_graphs = "0.23.0"
|
||||||
|
enum_dispatch = "0.3.13"
|
||||||
geo = "0.29.3"
|
geo = "0.29.3"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
petgraph = "0.6"
|
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 eframe::{run_native, App, CreationContext, NativeOptions, Frame};
|
||||||
use egui::{CentralPanel, SidePanel, Context};
|
use egui::{CentralPanel, CollapsingHeader, ComboBox, Context, ScrollArea, SidePanel, Slider, Window};
|
||||||
use egui_graphs::{GraphView, Graph, SettingsStyle, LayoutRandom, LayoutStateRandom};
|
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 random_generator::MaxflowProblem;
|
||||||
use algorithms::{ford_fulk
|
use layout::CustomEdgeShape;
|
||||||
//use petgraph::stable_graph::StableGraph;
|
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 random_generator;
|
||||||
mod algorithms;
|
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 {
|
pub struct MaxflowApp {
|
||||||
g: Graph<(), u64>,
|
gui_graph: Arc<Mutex<Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>>>,
|
||||||
p: MaxflowProblem,
|
problem: MaxflowProblem,
|
||||||
|
current_graph: StableGraph<(f32, f32), (u64, u64)>,
|
||||||
node_count: u64,
|
node_count: u64,
|
||||||
max_capacity: 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 {
|
impl MaxflowApp {
|
||||||
fn new(_: &CreationContext<'_>) -> Self {
|
fn new(_: &CreationContext<'_>) -> Self {
|
||||||
let problem = MaxflowProblem::new(10, 10);
|
let problem = MaxflowProblem::new_planar_graph(10, 10);
|
||||||
Self { g: problem.g.clone(), p: problem, node_count: 10, max_capacity: 5 }
|
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 {
|
impl App for MaxflowApp {
|
||||||
fn update(&mut self, ctx: &Context, _: &mut Frame) {
|
fn update(&mut self, ctx: &Context, _: &mut Frame) {
|
||||||
|
ctx.set_theme(egui::Theme::Light);
|
||||||
CentralPanel::default().show(ctx, |ui| {
|
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")
|
SidePanel::right("right_panel")
|
||||||
.min_width(200.)
|
.min_width(200.)
|
||||||
.show(ctx, |ui| {
|
.show(ctx, |ui| {
|
||||||
ui.label("node count");
|
ScrollArea::vertical().show(ui, |ui| {
|
||||||
ui.add(egui::DragValue::new(&mut self.node_count).range(1..=1000));
|
CollapsingHeader::new("Graph generation")
|
||||||
ui.label("maximum capacity");
|
.default_open(true)
|
||||||
ui.add(egui::DragValue::new(&mut self.max_capacity).range(1..=10));
|
.show(ui, |ui| {
|
||||||
if ui.button("generate graph").clicked() {
|
ui.label("node count");
|
||||||
let problem = random_generator::MaxflowProblem::new(self.node_count, self.max_capacity);
|
ui.add(egui::DragValue::new(&mut self.node_count).range(2..=50));
|
||||||
self.g = problem.g;
|
ui.label("maximum capacity");
|
||||||
}
|
ui.add(egui::DragValue::new(&mut self.max_capacity).range(1..=100));
|
||||||
if ui.button("run algorithm").clicked() {
|
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::stable_graph::{StableGraph, NodeIndex};
|
||||||
use petgraph::{Directed};
|
|
||||||
use egui_graphs::{Graph};
|
|
||||||
use egui::Pos2;
|
|
||||||
use rand::thread_rng;
|
use rand::thread_rng;
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use rand::seq::SliceRandom;
|
use rand::seq::SliceRandom;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use geo::{Line, coord, line_intersection::{line_intersection, LineIntersection}};
|
use geo::{Line, coord, line_intersection::{line_intersection, LineIntersection}};
|
||||||
use egui::Color32;
|
use std::cmp::min;
|
||||||
|
|
||||||
pub struct MaxflowProblem {
|
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 s: NodeIndex,
|
||||||
pub t: NodeIndex,
|
pub t: NodeIndex,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl 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
|
||||||
let mut graph: Graph<_, u64> = Graph::new(StableGraph::default());
|
// 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 nodes: Vec<NodeIndex> = Vec::new();
|
||||||
let mut rng = thread_rng();
|
let mut rng = thread_rng();
|
||||||
|
|
||||||
// generate nodes
|
// generate nodes
|
||||||
for _i in 0..num_nodes {
|
for _i in 0..num_nodes {
|
||||||
let x: f32 = rng.gen_range(0.0..=400.0);
|
// check if node is too close to another node (distance < 40)
|
||||||
let y: f32 = rng.gen_range(0.0..=300.0);
|
let mut too_close = true;
|
||||||
let pos = Pos2::new(x,y);
|
let (mut x, mut y) = (0.0, 0.0);
|
||||||
let n = graph.add_node_with_label_and_location((), String::new(), pos);
|
// 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);
|
nodes.push(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
// select source (s) and sink (t)
|
// select source (s) and sink (t)
|
||||||
let s = *nodes.choose(&mut rng).expect("no nodes found");
|
let (s,t) = nodes.choose_multiple(&mut rng, 2).copied().collect_tuple::<(NodeIndex, NodeIndex)>().expect("not enough nodes");
|
||||||
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);
|
|
||||||
|
|
||||||
// iterate over all possible edges
|
// iterate over all possible edges
|
||||||
let mut possible_edges: Vec<(NodeIndex, NodeIndex, f32)> = Vec::new();
|
let mut possible_edges: Vec<(NodeIndex, NodeIndex, f32)> = Vec::new();
|
||||||
for pair in nodes.clone().into_iter().combinations(2) {
|
for pair in nodes.clone().into_iter().combinations(2) {
|
||||||
// calculate distance & append tuple (node_a, node_b, distance) to possible_edges
|
// 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));
|
possible_edges.push((pair[0], pair[1], distance));
|
||||||
}
|
}
|
||||||
|
|
||||||
// sort mapping by distance
|
// sort mapping by distance
|
||||||
possible_edges.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap());
|
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
|
// insert the edge if possible
|
||||||
let mut inserted_edges: Vec<(NodeIndex, NodeIndex)> = Vec::new();
|
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
|
// check for collision with already inserted edges
|
||||||
let mut intersects = false;
|
let mut intersects = false;
|
||||||
for (node3, node4) in &inserted_edges {
|
for (node3, node4) in &inserted_edges {
|
||||||
if Self::intersects(
|
if Self::intersects(
|
||||||
graph.node(node1).expect("node index not found").location(),
|
*graph.node_weight(node1).expect("node index not found"),
|
||||||
graph.node(node2).expect("node index not found").location(),
|
*graph.node_weight(node2).expect("node index not found"),
|
||||||
graph.node(*node3).expect("node index not found").location(),
|
*graph.node_weight(*node3).expect("node index not found"),
|
||||||
graph.node(*node4).expect("node index not found").location(),
|
*graph.node_weight(*node4).expect("node index not found"),
|
||||||
) {
|
) {
|
||||||
intersects = true;
|
intersects = true;
|
||||||
break;
|
break;
|
||||||
@@ -70,26 +79,61 @@ impl MaxflowProblem {
|
|||||||
// otherwise insert the edge and its residual edge
|
// otherwise insert the edge and its residual edge
|
||||||
inserted_edges.push((node1, node2));
|
inserted_edges.push((node1, node2));
|
||||||
inserted_edges.push((node2, node1));
|
inserted_edges.push((node2, node1));
|
||||||
let capacity: u64 = rng.gen_range(1..=max_capacity);
|
let capacity1: u64 = rng.gen_range(1..=max_capacity);
|
||||||
graph.add_edge_with_label(node1, node2, capacity, capacity.to_string());
|
let capacity2: u64 = rng.gen_range(1..=max_capacity);
|
||||||
graph.add_edge_with_label(node2, node1, capacity, capacity.to_string());
|
graph.add_edge(node1, node2, (0, capacity1));
|
||||||
|
graph.add_edge(node2, node1, (0, capacity2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Self { g: graph, s: s, t: t }
|
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
|
// returns the eudclidean distance between two points
|
||||||
fn distance(a: Pos2, b: Pos2) -> f32 {
|
fn distance(a: (f32, f32), b: (f32, f32)) -> f32 {
|
||||||
let delta_x = a.x - b.x;
|
let delta_x = a.0 - b.0;
|
||||||
let delta_y = a.y - b.y;
|
let delta_y = a.1 - b.1;
|
||||||
return f32::sqrt(delta_x.powf(2.) + delta_y.powf(2.));
|
return f32::sqrt(delta_x.powf(2.) + delta_y.powf(2.));
|
||||||
}
|
}
|
||||||
|
|
||||||
// checks if the lines between a-b and c-d intersect
|
// checks if the lines between a-b and c-d intersect
|
||||||
fn intersects(a: Pos2, b: Pos2, c: Pos2, d: Pos2) -> bool {
|
fn intersects(a: (f32, f32), b: (f32, f32), c: (f32, f32), d: (f32, f32)) -> bool {
|
||||||
let line1 = Line::new(coord! {x: a.x, y: a.y }, coord! {x: b.x, y: b.y } );
|
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.x, y: c.y }, coord! {x: d.x, y: d.y } );
|
let line2 = Line::new(coord! {x: c.0, y: c.1 }, coord! {x: d.0, y: d.1 } );
|
||||||
match line_intersection(line1, line2) {
|
match line_intersection(line1, line2) {
|
||||||
// only return true for 'proper' intersections which are not at the end of a line
|
// only return true for 'proper' intersections which are not at the end of a line
|
||||||
Some(LineIntersection::SinglePoint { is_proper: true, .. }) => true,
|
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