diff --git a/README.md b/README.md index ba1da03..3051d90 100644 --- a/README.md +++ b/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) diff --git a/src/algorithms/goldberg_tarjan.rs b/src/algorithms/goldberg_tarjan.rs index 0b0b1be..87024a6 100644 --- a/src/algorithms/goldberg_tarjan.rs +++ b/src/algorithms/goldberg_tarjan.rs @@ -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 = 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::>(); 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::>(); + 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 diff --git a/src/graph.rs b/src/graph.rs index 03782d4..5b18534 100644 --- a/src/graph.rs +++ b/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::>() { + 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); } } diff --git a/src/main.rs b/src/main.rs index da9b076..4c11ee0 100644 --- a/src/main.rs +++ b/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>, 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") diff --git a/src/random_generator.rs b/src/random_generator.rs index 13b9e0c..f303c54 100644 --- a/src/random_generator.rs +++ b/src/random_generator.rs @@ -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!(); + } } diff --git a/src/testing.rs b/src/testing.rs index 8c8a3dc..01b1604 100644 --- a/src/testing.rs +++ b/src/testing.rs @@ -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 = 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(); diff --git a/tests/example_flows.rs b/tests/example_flows.rs index 7d6230c..ac5051d 100644 --- a/tests/example_flows.rs +++ b/tests/example_flows.rs @@ -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::>(); 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::>(); 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())); } \ No newline at end of file