Compare commits
2 Commits
ba3cd9591e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b67d74b31 | |||
| 5e064c73ed |
37
README.md
37
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)
|
||||
@@ -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:
|
||||
No distance label found
|
||||
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)
|
||||
@@ -82,7 +82,6 @@ impl Dinic {
|
||||
visited.visit(neighbor);
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(neighbor);
|
||||
// TODO: is this right?
|
||||
if neighbor == self.sink {
|
||||
return Some(new_path);
|
||||
} else {
|
||||
@@ -113,15 +112,7 @@ impl MaxflowAlgorithm for Dinic {
|
||||
|
||||
// increase flow with bottleneck capacity along the augmenting path
|
||||
for edge in edges {
|
||||
// get source and target of edge
|
||||
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;
|
||||
self.residual_graph.add_flow(edge, bottleneck_capacity);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
|
||||
@@ -65,15 +65,7 @@ impl MaxflowAlgorithm for EdmondsKarp {
|
||||
|
||||
// increase flow with bottleneck capacity along the augmenting path
|
||||
for edge in edges {
|
||||
// get source and target of edge
|
||||
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;
|
||||
self.residual_graph.add_flow(edge, bottleneck_capacity);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
|
||||
@@ -37,7 +37,6 @@ impl FordFulkerson {
|
||||
visited.visit(neighbor);
|
||||
let mut new_path = path.clone();
|
||||
new_path.push(neighbor);
|
||||
// TODO: is this right?
|
||||
if neighbor == self.sink {
|
||||
return Some(new_path);
|
||||
} else {
|
||||
@@ -65,15 +64,7 @@ impl MaxflowAlgorithm for FordFulkerson {
|
||||
|
||||
// increase flow with bottleneck capacity along the augmenting path
|
||||
for edge in edges {
|
||||
// get source and target of edge
|
||||
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;
|
||||
self.residual_graph.add_flow(edge, bottleneck_capacity);
|
||||
}
|
||||
false
|
||||
} else {
|
||||
|
||||
@@ -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
|
||||
|
||||
80
src/graph.rs
80
src/graph.rs
@@ -76,9 +76,9 @@ 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;
|
||||
|
||||
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:
|
||||
// - 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
|
||||
@@ -183,23 +196,6 @@ impl FlowGraph for StableGraph<(f32, f32), (u64, u64)> {
|
||||
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 {
|
||||
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 +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 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 +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
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,9 +30,9 @@ impl<E: Clone> From<EdgeProps<E>> for CustomEdgeShape {
|
||||
label_text: props.label,
|
||||
|
||||
width: 1.,
|
||||
tip_size: 10.,
|
||||
tip_angle: std::f32::consts::TAU / 30.,
|
||||
curve_size: 15.,
|
||||
tip_size: 6.,
|
||||
tip_angle: std::f32::consts::TAU / 20.,
|
||||
curve_size: 10.,
|
||||
loop_size: 3.,
|
||||
}
|
||||
}
|
||||
|
||||
11
src/main.rs
11
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
|
||||
}
|
||||
@@ -159,7 +167,6 @@ impl App for MaxflowApp {
|
||||
ui.add(egui::DragValue::new(&mut self.node_count).range(2..=50));
|
||||
ui.label("maximum capacity");
|
||||
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() {
|
||||
self.problem = random_generator::MaxflowProblem::new_planar_graph(self.node_count, self.max_capacity);
|
||||
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!("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")
|
||||
|
||||
@@ -26,7 +26,7 @@ impl MaxflowProblem {
|
||||
// check if node is too close to another node (distance < 40)
|
||||
let mut too_close = true;
|
||||
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 {
|
||||
too_close = false;
|
||||
x = rng.gen_range(0.0..=400.0);
|
||||
@@ -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) {
|
||||
|
||||
@@ -12,13 +12,7 @@ fn solve_problem_all_algos(problem: &MaxflowProblem) -> (f64, f64, f64, f64) {
|
||||
let mut durations = vec![0.0; 4];
|
||||
for (i,a) in instances.iter_mut().enumerate() {
|
||||
let now = Instant::now();
|
||||
match a {
|
||||
MaxflowAlgorithmEnum::GoldbergTarjan(_) => { a.run(); },
|
||||
_ => {
|
||||
a.run();
|
||||
}
|
||||
}
|
||||
//a.run();
|
||||
a.run();
|
||||
durations[i] = now.elapsed().as_secs_f64();
|
||||
|
||||
// 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
|
||||
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();
|
||||
@@ -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 {
|
||||
let mut response = String::new();
|
||||
|
||||
|
||||
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();
|
||||
let mut problems: Vec<_> = Vec::new();
|
||||
for _ in 0..problem_count {
|
||||
@@ -61,10 +55,11 @@ pub fn run_small_tests() -> String {
|
||||
let test_tuples = [
|
||||
// (number of problems, number of nodes, max. capacity)
|
||||
(10, 10, 10),
|
||||
(10, 10, 100),
|
||||
(10, 50, 10),
|
||||
(10, 50, 100),
|
||||
(10, 100, 10),
|
||||
(10, 100, 100),
|
||||
(10, 200, 10),
|
||||
(10, 200, 100)
|
||||
(10, 100, 100)
|
||||
];
|
||||
return run_tests(test_tuples.into());
|
||||
}
|
||||
@@ -75,13 +70,12 @@ pub fn run_large_tests() -> String {
|
||||
(10, 100, 10),
|
||||
(10, 100, 100),
|
||||
(10, 100, 1000),
|
||||
(10, 500, 10),
|
||||
(10, 500, 100),
|
||||
(10, 500, 1000),
|
||||
(10, 1000, 10),
|
||||
(10, 1000, 100),
|
||||
(10, 1000, 1000),
|
||||
(10, 10000, 10)
|
||||
(10, 200, 10),
|
||||
(10, 200, 100),
|
||||
(10, 200, 1000),
|
||||
(10, 300, 10),
|
||||
(10, 300, 100),
|
||||
(10, 300, 1000)
|
||||
];
|
||||
return run_tests(test_tuples.into());
|
||||
}
|
||||
54
tests.txt
Normal file
54
tests.txt
Normal 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
|
||||
|
||||
@@ -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(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:
|
||||
// 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();
|
||||
m.update_edge(s, one, (10, 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(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 +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 {
|
||||
// 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 +53,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 +71,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