checkpoint 2025-08-22
This commit is contained in:
30
README.md
30
README.md
@@ -7,36 +7,36 @@
|
||||
- Create a custom to_graph method which displays a maxflow graph
|
||||
|
||||
TODOs
|
||||
- [ ] Implementation of Algos:
|
||||
- [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
|
||||
- [ ] Goldberg-Tarjan/Preflow-Push
|
||||
- [ ] Step-by-step implementation for all algos
|
||||
- [x] Goldberg-Tarjan/Preflow-Push
|
||||
- [x] Step-by-step implementation for all algos
|
||||
- [x] Ford-Fulkerson
|
||||
- [x] Edmonds-Karp
|
||||
- [x] Dinic
|
||||
- [ ] Goldberg-Tarjan/Preflow-Push
|
||||
- [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"#
|
||||
- [ ] 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
|
||||
- [ ] Handle residual edges properly
|
||||
- [x] Handle residual edges properly
|
||||
- [x] Add display option for residual edges
|
||||
- [x] Only show active flows (filter all edges with f=0)
|
||||
- [ ] Show normal vs residual graph
|
||||
- [x] Show normal vs residual graph -> not needed, residual graph is shown by default
|
||||
- [x] When increasing flows, handle residual path
|
||||
- [ ] Add unit tests
|
||||
- [ ] With small problems to prove correctness
|
||||
- [ ] For Benchmarking the algos
|
||||
- [ ] Check validity of the generated flows (incoming flows = outgoing flows; flow <= capacity)
|
||||
- [ ] Add info display pane
|
||||
- [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)
|
||||
- [ ] number of steps
|
||||
- [ ] implement PartialEq for StableGraph (not really possible -> how do you compare graphs?)
|
||||
- [ ] Step-by-step vizualisation with choosable time delay
|
||||
- [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)
|
||||
|
||||
@@ -97,13 +97,11 @@ impl MaxflowAlgorithm for GoldbergTarjan {
|
||||
// if the distance labels were not already computed, compute them now
|
||||
if self.distance_label == None {
|
||||
self.compute_distances();
|
||||
//println!("distance labeling: {:?}", self.distance_label);
|
||||
}
|
||||
|
||||
// Main loop: while there is an active node, continue
|
||||
if let Some((node, excess)) = self.active_node() {
|
||||
// find all outgoing edges with available capacity
|
||||
//println!("active node {:?} source {:?} sink {:?}", node, self.source, self.sink);
|
||||
let valid_edges: Vec<petgraph::prelude::EdgeIndex> = self.residual_graph
|
||||
.edges_directed(node, Direction::Outgoing)
|
||||
.filter(|e| (e.weight().1 - e.weight().0) > 0 )
|
||||
@@ -112,20 +110,11 @@ impl MaxflowAlgorithm for GoldbergTarjan {
|
||||
.collect::<Vec<_>>();
|
||||
if valid_edges.len() > 0 {
|
||||
// node has valid edges -> push (move min[excess, capacity] to first valid edge)
|
||||
//println!("push");
|
||||
let edge = valid_edges[0];
|
||||
// let (source, target) = self.residual_graph.edge_endpoints(edge).expect("edge not connected");
|
||||
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);
|
||||
|
||||
// // increase flow of the forward edge with the calculated bottleneck value
|
||||
// let weight: &mut (u64, u64) = self.residual_graph.edge_weight_mut(edge).expect("edge not found");
|
||||
// (*weight).0 += additional_flow;
|
||||
// // 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 += additional_flow;
|
||||
} else {
|
||||
// node has no valid edges -> relabel (increase node distance label to min[neighbors] + 1)
|
||||
let neighbors = self.residual_graph
|
||||
@@ -134,8 +123,8 @@ impl MaxflowAlgorithm for GoldbergTarjan {
|
||||
.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();
|
||||
//println!("relabel {} + 1 (index: {:?})", lowest_distance, node);
|
||||
self.distance_label.as_mut().expect("distance labelling not present").insert(node, lowest_distance + 1);
|
||||
}
|
||||
false
|
||||
|
||||
62
src/graph.rs
62
src/graph.rs
@@ -76,6 +76,8 @@ pub trait FlowGraph {
|
||||
|
||||
fn reset_flow(&mut self);
|
||||
|
||||
fn prune_zero(&self) -> StableGraph<(f32, f32), (u64, u64)>;
|
||||
|
||||
fn valid_flow(&self, source: NodeIndex, sink: NodeIndex) -> bool;
|
||||
|
||||
fn saturated_cut(&self, source: NodeIndex, sink: NodeIndex) -> bool;
|
||||
@@ -153,6 +155,19 @@ impl FlowGraph for StableGraph<(f32, f32), (u64, u64)> {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -199,7 +214,6 @@ impl FlowGraph for StableGraph<(f32, f32), (u64, u64)> {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: assert that incoming flows at sink are equal to outgoing flows at source (otherwise return 0)
|
||||
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();
|
||||
@@ -207,7 +221,9 @@ impl FlowGraph for StableGraph<(f32, f32), (u64, u64)> {
|
||||
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;
|
||||
assert!(total_flow_sink == total_flow_source);
|
||||
if total_flow_sink != total_flow_source {
|
||||
return 0;
|
||||
}
|
||||
return total_flow_source;
|
||||
}
|
||||
|
||||
@@ -233,22 +249,38 @@ impl FlowGraph for StableGraph<(f32, f32), (u64, u64)> {
|
||||
self.edge_weight(edge).expect("edge not found").1 - self.edge_weight(edge).expect("edge not found").0
|
||||
}
|
||||
|
||||
fn add_flow(&mut self, edge: EdgeIndex, flow: u64) {
|
||||
// search for reverse edge and subtract existing flow if possible
|
||||
let (source, target) = self.edge_endpoints(edge).expect("edge not connected");
|
||||
let residual_edge = self.find_edge(target, source).expect("residual edge not found");
|
||||
let residual_weight: &mut (u64, u64) = self.edge_weight_mut(residual_edge).expect("residual edge not found");
|
||||
let forward_flow = i128::from(flow) - i128::from((*residual_weight).0);
|
||||
// subtract minimum of additional_flow and residual_weight
|
||||
if residual_weight.0 > 0 {
|
||||
(*residual_weight).0 -= min(residual_weight.0, flow);
|
||||
// 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);
|
||||
}
|
||||
assert!((*residual_weight).0 <= (*residual_weight).1);
|
||||
|
||||
// add remaining flow to forward edge
|
||||
let forward_weight: &mut (u64, u64) = self.edge_weight_mut(edge).expect("edge not found");
|
||||
if forward_flow > 0 {
|
||||
(*forward_weight).0 += u64::try_from(forward_flow).expect("error casting flow to u64");
|
||||
// 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);
|
||||
}
|
||||
assert!((*forward_weight).0 <= (*forward_weight).1);
|
||||
}
|
||||
}
|
||||
|
||||
10
src/main.rs
10
src/main.rs
@@ -27,6 +27,7 @@ pub struct MaxflowApp {
|
||||
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,
|
||||
@@ -48,6 +49,7 @@ impl MaxflowApp {
|
||||
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,
|
||||
@@ -61,6 +63,8 @@ impl MaxflowApp {
|
||||
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
|
||||
@@ -120,21 +124,25 @@ impl MaxflowApp {
|
||||
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
|
||||
}
|
||||
@@ -234,6 +242,8 @@ impl App for MaxflowApp {
|
||||
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")
|
||||
|
||||
@@ -44,7 +44,6 @@ impl MaxflowProblem {
|
||||
// select source (s) and sink (t)
|
||||
let (s,t) = nodes.choose_multiple(&mut rng, 2).copied().collect_tuple::<(NodeIndex, NodeIndex)>().expect("not enough nodes");
|
||||
|
||||
// TODO: this probably takes forever...
|
||||
// iterate over all possible edges
|
||||
let mut possible_edges: Vec<(NodeIndex, NodeIndex, f32)> = Vec::new();
|
||||
for pair in nodes.clone().into_iter().combinations(2) {
|
||||
@@ -141,4 +140,9 @@ impl MaxflowProblem {
|
||||
_ => false
|
||||
}
|
||||
}
|
||||
|
||||
fn intersects_poly(a: (f32, f32), b: (f32, f32), c: (f32, f32), d: (f32, f32)) -> bool {
|
||||
let mut intersects = Self::intersects(a, b, c, d);
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,6 @@ fn solve_problem_all_algos(problem: &MaxflowProblem) -> (f64, f64, f64, f64) {
|
||||
|
||||
// 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();
|
||||
println!("total flows: {:?}", total_flows);
|
||||
assert!(total_flows.iter().all(|&x| x == total_flows[0]), "algorithms return different total flows");
|
||||
|
||||
return durations.into_iter().collect_tuple().unwrap();
|
||||
|
||||
@@ -20,7 +20,7 @@ fn generate_small_maxflow_example() -> (StableGraph<(f32, f32), (u64, u64)>, Sta
|
||||
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,6));
|
||||
g.add_edge(four, three, (0,5)); // changed capacity to 5 to enforce a single optimal solution
|
||||
g.add_edge(four, t, (0,10));
|
||||
|
||||
// TODO:
|
||||
@@ -49,7 +49,7 @@ fn generate_small_maxflow_example() -> (StableGraph<(f32, f32), (u64, u64)>, Sta
|
||||
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,6));
|
||||
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);
|
||||
@@ -57,11 +57,12 @@ fn generate_small_maxflow_example() -> (StableGraph<(f32, f32), (u64, u64)>, Sta
|
||||
|
||||
fn graph_equal(one: StableGraph<(f32, f32), (u64, u64)>, other: StableGraph<(f32, f32), (u64, u64)>) -> bool {
|
||||
// ensure all edges have the same weights
|
||||
println!("Graph 1: {:?}", one);
|
||||
println!("Graph 2: {:?}", other);
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -70,6 +71,9 @@ fn graph_equal(one: StableGraph<(f32, f32), (u64, u64)>, other: StableGraph<(f32
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -85,5 +89,38 @@ fn test_ford_fulkerson() {
|
||||
let mut algo = FordFulkerson::from_problem(&problem);
|
||||
let solution = algo.run();
|
||||
|
||||
assert!(graph_equal(solution, m.residual()));
|
||||
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()));
|
||||
}
|
||||
Reference in New Issue
Block a user