Compare commits

..

2 Commits

Author SHA1 Message Date
5b67d74b31 checkpoint 2025-08-27 2025-08-27 00:06:12 +02:00
5e064c73ed checkpoint 2025-08-22 2025-08-22 15:28:33 +02:00
12 changed files with 196 additions and 137 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)
@@ -92,3 +92,10 @@ note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
thread '<unnamed>' panicked at src/algorithms/goldberg_tarjan.rs:109:238: thread '<unnamed>' panicked at src/algorithms/goldberg_tarjan.rs:109:238:
No distance label found No distance label found
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace 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)

View File

@@ -82,7 +82,6 @@ impl Dinic {
visited.visit(neighbor); visited.visit(neighbor);
let mut new_path = path.clone(); let mut new_path = path.clone();
new_path.push(neighbor); new_path.push(neighbor);
// TODO: is this right?
if neighbor == self.sink { if neighbor == self.sink {
return Some(new_path); return Some(new_path);
} else { } else {
@@ -113,15 +112,7 @@ impl MaxflowAlgorithm for Dinic {
// increase flow with bottleneck capacity along the augmenting path // increase flow with bottleneck capacity along the augmenting path
for edge in edges { for edge in edges {
// get source and target of edge self.residual_graph.add_flow(edge, bottleneck_capacity);
let (source, target) = self.residual_graph.edge_endpoints(edge).expect("edge not connected");
// 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 += bottleneck_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 += bottleneck_capacity;
} }
false false
} else { } else {

View File

@@ -65,15 +65,7 @@ impl MaxflowAlgorithm for EdmondsKarp {
// increase flow with bottleneck capacity along the augmenting path // increase flow with bottleneck capacity along the augmenting path
for edge in edges { for edge in edges {
// get source and target of edge self.residual_graph.add_flow(edge, bottleneck_capacity);
let (source, target) = self.residual_graph.edge_endpoints(edge).expect("edge not connected");
// 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 += bottleneck_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 += bottleneck_capacity;
} }
false false
} else { } else {

View File

@@ -37,7 +37,6 @@ impl FordFulkerson {
visited.visit(neighbor); visited.visit(neighbor);
let mut new_path = path.clone(); let mut new_path = path.clone();
new_path.push(neighbor); new_path.push(neighbor);
// TODO: is this right?
if neighbor == self.sink { if neighbor == self.sink {
return Some(new_path); return Some(new_path);
} else { } else {
@@ -65,15 +64,7 @@ impl MaxflowAlgorithm for FordFulkerson {
// increase flow with bottleneck capacity along the augmenting path // increase flow with bottleneck capacity along the augmenting path
for edge in edges { for edge in edges {
// get source and target of edge self.residual_graph.add_flow(edge, bottleneck_capacity);
let (source, target) = self.residual_graph.edge_endpoints(edge).expect("edge not connected");
// 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 += bottleneck_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 += bottleneck_capacity;
} }
false false
} else { } else {

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,9 +76,9 @@ 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 total_flow(&self, source: NodeIndex, sink: NodeIndex) -> u64; fn total_flow(&self, source: NodeIndex, sink: NodeIndex) -> u64;
@@ -153,6 +153,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
@@ -183,23 +196,6 @@ impl FlowGraph for StableGraph<(f32, f32), (u64, u64)> {
true true
} }
// checks if there exists a saturated cut in the graph (a bip)
fn saturated_cut(&self, source: NodeIndex, sink: NodeIndex) -> bool {
let mut s = HashSet::new();
let mut t = HashSet::new();
for node in self.node_indices().into_iter().collect::<Vec<_>>() {
if has_path_connecting(self, source, node, None) {
s.insert(node);
} else {
t.insert(node);
}
}
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 +203,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 +231,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");
(*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);
} }
} }

View File

@@ -30,9 +30,9 @@ impl<E: Clone> From<EdgeProps<E>> for CustomEdgeShape {
label_text: props.label, label_text: props.label,
width: 1., width: 1.,
tip_size: 10., tip_size: 6.,
tip_angle: std::f32::consts::TAU / 30., tip_angle: std::f32::consts::TAU / 20.,
curve_size: 15., curve_size: 10.,
loop_size: 3., loop_size: 3.,
} }
} }

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
} }
@@ -159,7 +167,6 @@ impl App for MaxflowApp {
ui.add(egui::DragValue::new(&mut self.node_count).range(2..=50)); ui.add(egui::DragValue::new(&mut self.node_count).range(2..=50));
ui.label("maximum capacity"); ui.label("maximum capacity");
ui.add(egui::DragValue::new(&mut self.max_capacity).range(1..=100)); ui.add(egui::DragValue::new(&mut self.max_capacity).range(1..=100));
// TODO: add generation strategy (random, pseudo-random)
if ui.button("generate graph").clicked() { if ui.button("generate graph").clicked() {
self.problem = random_generator::MaxflowProblem::new_planar_graph(self.node_count, self.max_capacity); self.problem = random_generator::MaxflowProblem::new_planar_graph(self.node_count, self.max_capacity);
self.reset_state(); self.reset_state();
@@ -234,6 +241,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

@@ -26,7 +26,7 @@ impl MaxflowProblem {
// check if node is too close to another node (distance < 40) // check if node is too close to another node (distance < 40)
let mut too_close = true; let mut too_close = true;
let (mut x, mut y) = (0.0, 0.0); let (mut x, mut y) = (0.0, 0.0);
// this is a poor attempt at modeling a do..while look in Rust // this is a poor attempt at modeling a do..while loop in Rust
while too_close { while too_close {
too_close = false; too_close = false;
x = rng.gen_range(0.0..=400.0); x = rng.gen_range(0.0..=400.0);
@@ -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) {

View File

@@ -12,13 +12,7 @@ fn solve_problem_all_algos(problem: &MaxflowProblem) -> (f64, f64, f64, f64) {
let mut durations = vec![0.0; 4]; let mut durations = vec![0.0; 4];
for (i,a) in instances.iter_mut().enumerate() { for (i,a) in instances.iter_mut().enumerate() {
let now = Instant::now(); let now = Instant::now();
match a { a.run();
MaxflowAlgorithmEnum::GoldbergTarjan(_) => { a.run(); },
_ => {
a.run();
}
}
//a.run();
durations[i] = now.elapsed().as_secs_f64(); durations[i] = now.elapsed().as_secs_f64();
// check if the flow is valid // check if the flow is valid
@@ -27,7 +21,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();
@@ -35,8 +28,9 @@ fn solve_problem_all_algos(problem: &MaxflowProblem) -> (f64, f64, f64, f64) {
fn run_tests(test_tuples: Vec<(u64, u64, u64)>) -> String { fn run_tests(test_tuples: Vec<(u64, u64, u64)>) -> String {
let mut response = String::new(); let mut response = String::new();
for (problem_count, nodes, capacity) in test_tuples { 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(); response += format!("=== Testing with problem_count={}, nodes={}, capacity={}\n", problem_count, nodes, capacity).as_str();
let mut problems: Vec<_> = Vec::new(); let mut problems: Vec<_> = Vec::new();
for _ in 0..problem_count { for _ in 0..problem_count {
@@ -61,10 +55,11 @@ pub fn run_small_tests() -> String {
let test_tuples = [ let test_tuples = [
// (number of problems, number of nodes, max. capacity) // (number of problems, number of nodes, max. capacity)
(10, 10, 10), (10, 10, 10),
(10, 10, 100),
(10, 50, 10),
(10, 50, 100),
(10, 100, 10), (10, 100, 10),
(10, 100, 100), (10, 100, 100)
(10, 200, 10),
(10, 200, 100)
]; ];
return run_tests(test_tuples.into()); return run_tests(test_tuples.into());
} }
@@ -75,13 +70,12 @@ pub fn run_large_tests() -> String {
(10, 100, 10), (10, 100, 10),
(10, 100, 100), (10, 100, 100),
(10, 100, 1000), (10, 100, 1000),
(10, 500, 10), (10, 200, 10),
(10, 500, 100), (10, 200, 100),
(10, 500, 1000), (10, 200, 1000),
(10, 1000, 10), (10, 300, 10),
(10, 1000, 100), (10, 300, 100),
(10, 1000, 1000), (10, 300, 1000)
(10, 10000, 10)
]; ];
return run_tests(test_tuples.into()); return run_tests(test_tuples.into());
} }

54
tests.txt Normal file
View 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

View File

@@ -20,27 +20,9 @@ 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:
// let mut m: StableGraph<(f32, f32), (u64, u64)> = StableGraph::new();
// let s = m.add_node((0., 5.));
// let one = m.add_node((10., 0.));
// let two = m.add_node((10., 10.));
// let three = m.add_node((20., 0.));
// let four = m.add_node((20., 10.));
// let t = m.add_node((30., 5.));
// m.add_edge(s, one, (10, 10));
// m.add_edge(s, two, (9, 10));
// m.add_edge(one, three, (4, 4));
// m.add_edge(one, four, (6, 8));
// m.add_edge(one, two, (0,2));
// m.add_edge(two, four, (9,9));
// m.add_edge(three, t, (9,10));
// m.add_edge(four, three, (5,6));
// m.add_edge(four, t, (10,10));
let mut m = g.clone(); let mut m = g.clone();
m.update_edge(s, one, (10, 10)); m.update_edge(s, one, (10, 10));
m.update_edge(s, two, (9, 10)); m.update_edge(s, two, (9, 10));
@@ -49,7 +31,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 +39,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 +53,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 +71,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()));
} }