fix random generator capacity, add Dinic's algo, add options for filtering empty flows & displaying residual graph

This commit is contained in:
2025-03-25 18:24:09 +01:00
parent 681cdee139
commit 6b17e583e6
4 changed files with 169 additions and 9 deletions

107
src/algorithms/dinic.rs Normal file
View File

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

View File

@@ -1,5 +1,7 @@
mod edmonds_karp; mod edmonds_karp;
mod ford_fulkerson; mod ford_fulkerson;
mod dinic;
pub use ford_fulkerson::ford_fulkerson; pub use ford_fulkerson::ford_fulkerson;
pub use edmonds_karp::edmonds_karp; pub use edmonds_karp::edmonds_karp;
pub use dinic::dinic;

View File

@@ -7,26 +7,51 @@ use std::{fmt::Display, ptr::fn_addr_eq};
use random_generator::MaxflowProblem; use random_generator::MaxflowProblem;
use layout::CustomEdgeShape; use layout::CustomEdgeShape;
use crate::algorithms::{ford_fulkerson, edmonds_karp}; use crate::algorithms::{ford_fulkerson, edmonds_karp, dinic};
use graph::{ResidualGraph, GuiGraph, FlowGraph};
mod random_generator; mod random_generator;
mod algorithms; mod algorithms;
mod layout; mod layout;
mod graph;
type MaxflowFn = fn(StableGraph<(f32, f32), (u64, u64)>, NodeIndex, NodeIndex) -> StableGraph<(f32, f32), (u64, u64)>; type MaxflowFn = fn(StableGraph<(f32, f32), (u64, u64)>, NodeIndex, NodeIndex) -> StableGraph<(f32, f32), (u64, u64)>;
pub struct MaxflowApp { pub struct MaxflowApp {
g: Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>, g: Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape>,
p: MaxflowProblem, p: MaxflowProblem,
r: StableGraph<(f32, f32), (u64, u64)>,
node_count: u64, node_count: u64,
max_capacity: u64, max_capacity: u64,
algorithm: MaxflowFn, algorithm: MaxflowFn,
display_residual: bool,
display_active_flows: bool,
} }
impl MaxflowApp { impl MaxflowApp {
fn new(_: &CreationContext<'_>) -> Self { fn new(_: &CreationContext<'_>) -> Self {
let problem = MaxflowProblem::new(10, 10); let problem = MaxflowProblem::new(10, 10);
Self { g: problem.to_gui_graph(), p: problem, node_count: 10, max_capacity: 5, algorithm: ford_fulkerson } Self {
g: problem.g.to_gui_graph(problem.s, problem.t, false),
r: StableGraph::default(),
p: problem, node_count: 10,
max_capacity: 5,
algorithm: ford_fulkerson,
display_residual: false,
display_active_flows: false,
}
}
fn update_graph(&mut self) {
let mut graph = match self.display_residual {
true => &self.p.g.residual(),
false => &self.p.g,
};
if self.display_active_flows {
self.g = graph.filter_empty_flows().to_gui_graph(self.p.s, self.p.t, self.display_active_flows);
} else {
self.g = graph.to_gui_graph(self.p.s, self.p.t, self.display_active_flows);
}
} }
} }
@@ -50,7 +75,7 @@ impl App for MaxflowApp {
// TODO: add generation strategy (random, pseudo-random) // TODO: add generation strategy (random, pseudo-random)
if ui.button("generate graph").clicked() { if ui.button("generate graph").clicked() {
self.p = random_generator::MaxflowProblem::new(self.node_count, self.max_capacity); self.p = random_generator::MaxflowProblem::new(self.node_count, self.max_capacity);
self.g = self.p.to_gui_graph(); self.update_graph();
} }
}); });
@@ -61,20 +86,36 @@ impl App for MaxflowApp {
.selected_text(format!("{}", match self.algorithm { .selected_text(format!("{}", match self.algorithm {
_ if fn_addr_eq(self.algorithm, ford_fulkerson as MaxflowFn) => "Ford-Fulkerson", _ if fn_addr_eq(self.algorithm, ford_fulkerson as MaxflowFn) => "Ford-Fulkerson",
_ if fn_addr_eq(self.algorithm, edmonds_karp as MaxflowFn) => "Edmonds-Karp", _ if fn_addr_eq(self.algorithm, edmonds_karp as MaxflowFn) => "Edmonds-Karp",
_ if fn_addr_eq(self.algorithm, dinic as MaxflowFn) => "Dinic",
_ => "unknown" _ => "unknown"
})) }))
.show_ui(ui, |ui| { .show_ui(ui, |ui| {
ui.selectable_value(&mut self.algorithm, ford_fulkerson, "Ford-Fulkerson"); ui.selectable_value(&mut self.algorithm, ford_fulkerson, "Ford-Fulkerson");
ui.selectable_value(&mut self.algorithm, edmonds_karp, "Edmonds-Karp"); ui.selectable_value(&mut self.algorithm, edmonds_karp, "Edmonds-Karp");
ui.selectable_value(&mut self.algorithm, dinic, "Dinic");
}); });
if ui.button("run algorithm").clicked() { if ui.button("run algorithm").clicked() {
let max_flow_graph = ford_fulkerson(self.p.g.clone(), self.p.s, self.p.t); let max_flow_graph = ford_fulkerson(self.p.g.clone(), self.p.s, self.p.t);
self.p = MaxflowProblem::from(max_flow_graph, self.p.s, self.p.t); self.p = MaxflowProblem::from(max_flow_graph, self.p.s, self.p.t);
self.g = self.p.to_gui_graph(); self.update_graph();
}
if ui.button("reset").clicked() {
self.p.reset_flow();
self.update_graph();
} }
// reset button
// step button (disable when finished) // step button (disable when finished)
}); });
CollapsingHeader::new("Display options")
.default_open(true)
.show(ui, |ui| {
if ui.checkbox(&mut self.display_residual, "show residual graph").changed() {
self.update_graph();
}
if ui.checkbox(&mut self.display_active_flows, "show active flows only").changed() {
self.update_graph();
}
})
}); });

View File

@@ -1,3 +1,4 @@
use petgraph::data::Build;
use petgraph::stable_graph::{StableGraph, NodeIndex}; use petgraph::stable_graph::{StableGraph, NodeIndex};
use petgraph::{Directed}; use petgraph::{Directed};
use egui_graphs::{default_edge_transform, default_node_transform, to_graph_custom, DefaultEdgeShape, DefaultNodeShape, Graph}; use egui_graphs::{default_edge_transform, default_node_transform, to_graph_custom, DefaultEdgeShape, DefaultNodeShape, Graph};
@@ -70,9 +71,10 @@ 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(node1, node2, (0, capacity)); let capacity2: u64 = rng.gen_range(1..=max_capacity);
graph.add_edge(node2, node1, (0, capacity)); graph.add_edge(node1, node2, (0, capacity1));
graph.add_edge(node2, node1, (0, capacity2));
} }
} }
@@ -101,6 +103,14 @@ impl MaxflowProblem {
} }
} }
// set all flows back to zero
pub fn reset_flow(&mut self) {
for edge in self.g.edge_indices().collect::<Vec<_>>() {
let weight = self.g.edge_weight_mut(edge).expect("edge not found");
(*weight).0 = 0;
}
}
pub fn to_gui_graph(&self) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape> { pub fn to_gui_graph(&self) -> Graph<(f32, f32), (u64, u64), Directed, u32, DefaultNodeShape, CustomEdgeShape> {
let mut graph = to_graph_custom(&self.g, let mut graph = to_graph_custom(&self.g,
|n| { |n| {