checkpoint 2025-08-22

This commit is contained in:
2025-08-22 15:28:33 +02:00
parent ba3cd9591e
commit 5e064c73ed
7 changed files with 120 additions and 49 deletions

View File

@@ -7,36 +7,36 @@
- Create a custom to_graph method which displays a maxflow graph - Create a custom to_graph method which displays a maxflow graph
TODOs TODOs
- [ ] Implementation of Algos: - [x] Implementation of Algos:
- [x] Ford-Fulkersson (DFS zur Berechnung des flusserhöhenden/augmentierenden Pfads) - [x] Ford-Fulkersson (DFS zur Berechnung des flusserhöhenden/augmentierenden Pfads)
- [x] Edmonds-Karp (BFS zur Berechnung des flusserhöhenden/augmnetierenden Pfads) - [x] Edmonds-Karp (BFS zur Berechnung des flusserhöhenden/augmnetierenden Pfads)
- [x] Dinic - [x] Dinic
- [ ] Goldberg-Tarjan/Preflow-Push - [x] Goldberg-Tarjan/Preflow-Push
- [ ] Step-by-step implementation for all algos - [x] Step-by-step implementation for all algos
- [x] Ford-Fulkerson - [x] Ford-Fulkerson
- [x] Edmonds-Karp - [x] Edmonds-Karp
- [x] Dinic - [x] Dinic
- [ ] Goldberg-Tarjan/Preflow-Push - [x] Goldberg-Tarjan/Preflow-Push
- [x] Mark edges which have active flows with a color - [x] Mark edges which have active flows with a color
- [x] Move GUI Graph conversion to a trait of StableGraph - [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) - [x] Only insert nodes which are at least a distance of 40 away (default)
- [ ] Pseudo-random node positioning strategy - [ ] Pseudo-random node positioning strategy
- [ ] Handle residual edges properly - [x] Handle residual edges properly
- [x] Add display option for residual edges - [x] Add display option for residual edges
- [x] Only show active flows (filter all edges with f=0) - [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 - [x] When increasing flows, handle residual path
- [ ] Add unit tests - [x] Add unit tests
- [ ] With small problems to prove correctness - [x] With small problems to prove correctness
- [ ] For Benchmarking the algos - [x] For Benchmarking the algos
- [ ] Check validity of the generated flows (incoming flows = outgoing flows; flow <= capacity) - [x] Check validity of the generated flows (incoming flows = outgoing flows; flow <= capacity)
- [ ] Add info display pane - [x] Add info display pane
- [x] is valid flow? (add trait to graph) - [x] is valid flow? (add trait to graph)
- [x] current total flow (add trait to calculate) - [x] current total flow (add trait to calculate)
- [ ] number of steps - [x] number of steps (is implemented for stepwise)
- [ ] implement PartialEq for StableGraph (not really possible -> how do you compare graphs?) - [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
- [ ] Step-by-step vizualisation with choosable time delay - [x] Step-by-step vizualisation with choosable time delay
- [ ] Dinic: add GUI options to show Layer Graph and Blocking flow - [ ] Dinic: add GUI options to show Layer Graph and Blocking flow
- [ ] Goldberg-Tarjan: show distance labels at nodes - [ ] Goldberg-Tarjan: show distance labels at nodes
- [x] change GUI colors to accomodate color-blindness (blue-yellow rather than red-green) - [x] change GUI colors to accomodate color-blindness (blue-yellow rather than red-green)

View File

@@ -97,13 +97,11 @@ impl MaxflowAlgorithm for GoldbergTarjan {
// if the distance labels were not already computed, compute them now // if the distance labels were not already computed, compute them now
if self.distance_label == None { if self.distance_label == None {
self.compute_distances(); self.compute_distances();
//println!("distance labeling: {:?}", self.distance_label);
} }
// Main loop: while there is an active node, continue // Main loop: while there is an active node, continue
if let Some((node, excess)) = self.active_node() { if let Some((node, excess)) = self.active_node() {
// find all outgoing edges with available capacity // 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 let valid_edges: Vec<petgraph::prelude::EdgeIndex> = self.residual_graph
.edges_directed(node, Direction::Outgoing) .edges_directed(node, Direction::Outgoing)
.filter(|e| (e.weight().1 - e.weight().0) > 0 ) .filter(|e| (e.weight().1 - e.weight().0) > 0 )
@@ -112,20 +110,11 @@ impl MaxflowAlgorithm for GoldbergTarjan {
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if valid_edges.len() > 0 { if valid_edges.len() > 0 {
// node has valid edges -> push (move min[excess, capacity] to first valid edge) // node has valid edges -> push (move min[excess, capacity] to first valid edge)
//println!("push");
let edge = valid_edges[0]; 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 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); let additional_flow = min(excess, available_capacity);
self.residual_graph.add_flow(edge, additional_flow); 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 { } else {
// node has no valid edges -> relabel (increase node distance label to min[neighbors] + 1) // node has no valid edges -> relabel (increase node distance label to min[neighbors] + 1)
let neighbors = self.residual_graph 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())) .sorted_by_key(|e| self.distance_label.as_ref().expect("distance labelling not present").get(&e.target()))
.map(|e| e.target()) .map(|e| e.target())
.collect::<Vec<_>>(); .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(); 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); self.distance_label.as_mut().expect("distance labelling not present").insert(node, lowest_distance + 1);
} }
false false

View File

@@ -76,6 +76,8 @@ pub trait FlowGraph {
fn reset_flow(&mut self); fn reset_flow(&mut self);
fn prune_zero(&self) -> StableGraph<(f32, f32), (u64, u64)>;
fn valid_flow(&self, source: NodeIndex, sink: NodeIndex) -> bool; fn valid_flow(&self, source: NodeIndex, sink: NodeIndex) -> bool;
fn saturated_cut(&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: // check if flow is valid:
// - for every edge, the flow doesn't exceed the capacity // - 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 // - 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; 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 { 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 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 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 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_source = outgoing_flows_source - incoming_flows_source;
let total_flow_sink = incoming_flows_sink - outgoing_flows_sink; 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; 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 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) { // adds the specified flow to the edge of a residual graph
// search for reverse edge and subtract existing flow if possible fn add_flow(&mut self, forward_edge: EdgeIndex, flow: u64) {
let (source, target) = self.edge_endpoints(edge).expect("edge not connected"); // search for reverse edge
let residual_edge = self.find_edge(target, source).expect("residual edge not found"); let (source, target) = self.edge_endpoints(forward_edge).expect("edge not connected");
let residual_weight: &mut (u64, u64) = self.edge_weight_mut(residual_edge).expect("residual edge not found"); let reverse_edge = self.find_edge(target, source).expect("reverse edge not found");
let forward_flow = i128::from(flow) - i128::from((*residual_weight).0);
// subtract minimum of additional_flow and residual_weight // calculate how much flow needs to be added on the reverse edge
if residual_weight.0 > 0 { let current_reverse_flow = self.edge_weight(reverse_edge).expect("reverse erge not found").0;
(*residual_weight).0 -= min(residual_weight.0, flow); // 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 // 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 { 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");
assert!((*forward_weight).0 <= (*forward_weight).1); (*forward_weight).0 += forward_flow;
assert!(forward_weight.0 <= forward_weight.1);
// increase reverse capacity
let reverse_weight: &mut (u64, u64) = self.edge_weight_mut(reverse_edge).expect("reverse edge not found");
(*reverse_weight).1 += forward_flow;
assert!(reverse_weight.0 <= reverse_weight.1);
}
} }
} }

View File

@@ -27,6 +27,7 @@ pub struct MaxflowApp {
display_active_flows: bool, display_active_flows: bool,
display_layer_graph: bool, display_layer_graph: bool,
algorithm_finished: bool, algorithm_finished: bool,
num_steps: u64,
test_thread: Option<JoinHandle<String>>, test_thread: Option<JoinHandle<String>>,
test_results: String, test_results: String,
test_results_open: bool, test_results_open: bool,
@@ -48,6 +49,7 @@ impl MaxflowApp {
display_active_flows: false, display_active_flows: false,
display_layer_graph: false, display_layer_graph: false,
algorithm_finished: false, algorithm_finished: false,
num_steps: 0,
test_thread: None, test_thread: None,
test_results: String::default(), test_results: String::default(),
test_results_open: false, test_results_open: false,
@@ -61,6 +63,8 @@ impl MaxflowApp {
fn reset_state(&mut self) { fn reset_state(&mut self) {
// undo finished run // undo finished run
self.algorithm_finished = false; self.algorithm_finished = false;
// set steps to zero
self.num_steps = 0;
// reinitialize the current graph from the problem // reinitialize the current graph from the problem
self.current_graph = self.problem.g.clone(); self.current_graph = self.problem.g.clone();
// assign new maxflow problem to the graph // assign new maxflow problem to the graph
@@ -120,21 +124,25 @@ impl MaxflowApp {
match &mut self.algorithm { match &mut self.algorithm {
MaxflowAlgorithmEnum::FordFulkerson(x) => { MaxflowAlgorithmEnum::FordFulkerson(x) => {
let finished = x.step(); let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph(); self.current_graph = x.graph();
finished finished
}, },
MaxflowAlgorithmEnum::EdmondsKarp(x) => { MaxflowAlgorithmEnum::EdmondsKarp(x) => {
let finished = x.step(); let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph(); self.current_graph = x.graph();
finished finished
}, },
MaxflowAlgorithmEnum::Dinic(x) => { MaxflowAlgorithmEnum::Dinic(x) => {
let finished = x.step(); let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph(); self.current_graph = x.graph();
finished finished
}, },
MaxflowAlgorithmEnum::GoldbergTarjan(x) => { MaxflowAlgorithmEnum::GoldbergTarjan(x) => {
let finished = x.step(); let finished = x.step();
self.num_steps += 1;
self.current_graph = x.graph(); self.current_graph = x.graph();
finished 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!("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!("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") CollapsingHeader::new("Tests")

View File

@@ -44,7 +44,6 @@ impl MaxflowProblem {
// select source (s) and sink (t) // select source (s) and sink (t)
let (s,t) = nodes.choose_multiple(&mut rng, 2).copied().collect_tuple::<(NodeIndex, NodeIndex)>().expect("not enough nodes"); 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 // 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) {
@@ -141,4 +140,9 @@ impl MaxflowProblem {
_ => false _ => 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!();
}
} }

View File

@@ -27,7 +27,6 @@ fn solve_problem_all_algos(problem: &MaxflowProblem) -> (f64, f64, f64, f64) {
// check if all algorithms return the same total 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(); 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"); assert!(total_flows.iter().all(|&x| x == total_flows[0]), "algorithms return different total flows");
return durations.into_iter().collect_tuple().unwrap(); return durations.into_iter().collect_tuple().unwrap();

View File

@@ -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(one, two, (0,2));
g.add_edge(two, four, (0,9)); g.add_edge(two, four, (0,9));
g.add_edge(three, t, (0,10)); 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)); g.add_edge(four, t, (0,10));
// TODO: // 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(one, two, (0,2));
m.update_edge(two, four, (9,9)); m.update_edge(two, four, (9,9));
m.update_edge(three, t, (9,10)); 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)); m.update_edge(four, t, (10,10));
return (g, m, s, t); 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 { fn graph_equal(one: StableGraph<(f32, f32), (u64, u64)>, other: StableGraph<(f32, f32), (u64, u64)>) -> bool {
// ensure all edges have the same weights // 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<_>>(); let edges = one.edge_indices().map(|e| e).collect::<Vec<_>>();
for edge in edges { for edge in edges {
if one.edge_weight(edge).expect("edge index not found") != other.edge_weight(edge).expect("edge index not found") { 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; 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<_>>(); let nodes = one.node_indices().map(|n| n).collect::<Vec<_>>();
for node in nodes { for node in nodes {
if one.node_weight(node).expect("node index not found") != other.node_weight(node).expect("node index not found") { 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; return false;
} }
} }
@@ -85,5 +89,38 @@ fn test_ford_fulkerson() {
let mut algo = FordFulkerson::from_problem(&problem); let mut algo = FordFulkerson::from_problem(&problem);
let solution = algo.run(); 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()));
} }