Skip to content

Commit 6d63994

Browse files
committed
Make simulated annealing use a struct
Closes #88
1 parent edcf33f commit 6d63994

File tree

5 files changed

+98
-78
lines changed

5 files changed

+98
-78
lines changed

benches/benchmarks.rs

+9-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use std::time::Duration;
22

33
use broute::graphs::algorithms::{
4-
form_abstracted_graph, travelling_salesman, ConnectedComponents, Dijkstra,
4+
form_abstracted_graph, SimulatedAnnealing, ConnectedComponents, Dijkstra,
55
};
66
use broute::graphs::datastructures::{Digraph, NodeIndex};
77
use broute::graphs::input::{get_random_graph, load_pbf_file, load_tsplib_file, load_xgmml_file};
@@ -77,7 +77,10 @@ fn travelling_salesman_benchmark(c: &mut Criterion) {
7777
group.bench_with_input(
7878
BenchmarkId::new("DIMCAS d1291", &dimacs_g),
7979
&dimacs_g,
80-
|b, g| b.iter(|| travelling_salesman(g, false)),
80+
|b, g| b.iter(|| {
81+
let mut sa = SimulatedAnnealing::new(g);
82+
sa.run();
83+
}),
8184
);
8285

8386
let monaco_g = load_pbf_file("test_data/geofabrik/monaco-latest.osm.pbf").unwrap();
@@ -95,7 +98,10 @@ fn travelling_salesman_benchmark(c: &mut Criterion) {
9598
group.bench_with_input(
9699
BenchmarkId::new("OSM Monaco - 5 random nodes", &abstracted_graph),
97100
&abstracted_graph,
98-
|b, g| b.iter(|| travelling_salesman(g, false)),
101+
|b, g| b.iter(|| {
102+
let mut sa = SimulatedAnnealing::new(g);
103+
sa.run();
104+
}),
99105
);
100106

101107
group.finish();

src/graphs/algorithms/travelling_salesman.rs

+78-66
Original file line numberDiff line numberDiff line change
@@ -34,97 +34,109 @@ pub fn form_abstracted_graph(g: &dyn Digraph, node_ids: &Vec<NodeID>) -> AMDigra
3434
abstracted_graph
3535
}
3636

37-
fn get_potential_new_path(
38-
rng: &mut ThreadRng,
39-
g: &dyn Digraph,
40-
current_path: &GraphPath,
41-
) -> GraphPath {
42-
let mut potential_new_path = current_path.clone();
43-
44-
let node_index_to_mutate = rng.gen_range(0..(g.num_vertices() - 1));
37+
pub struct SimulatedAnnealing<'a> {
38+
g: &'a dyn Digraph,
39+
result_data: Vec<(f64, f64)>,
40+
current_path: GraphPath,
41+
path_length: f64,
42+
best_path: GraphPath,
43+
rng: ThreadRng,
44+
}
4545

46-
let reverse_or_transport: bool = rng.gen();
46+
impl<'a> SimulatedAnnealing<'a> {
47+
pub fn new(g: &'a dyn Digraph) -> Self {
48+
let mut rng = thread_rng();
4749

48-
if reverse_or_transport {
49-
let node_index_to_swap_with = if node_index_to_mutate < (g.num_vertices() - 1) {
50-
node_index_to_mutate + 1
51-
} else {
52-
0
50+
let mut current_path = GraphPath {
51+
path: (0..g.num_vertices()).map(NodeIndex).collect(),
5352
};
54-
potential_new_path
55-
.path
56-
.swap(node_index_to_mutate, node_index_to_swap_with);
57-
} else {
58-
// Cyclic permutation
59-
let node_to_move = potential_new_path.path[node_index_to_mutate];
60-
// -2 because we are looking for new position with 1 node missing
61-
let new_node_position = rng.gen_range(0..(g.num_vertices() - 2));
62-
potential_new_path.path.remove(node_index_to_mutate);
63-
potential_new_path
64-
.path
65-
.insert(new_node_position, node_to_move)
53+
current_path.path.shuffle(&mut rng);
54+
55+
let path_length = current_path.get_length_on_graph(g);
56+
57+
let best_path = current_path.clone();
58+
SimulatedAnnealing {
59+
g,
60+
result_data: vec![],
61+
current_path,
62+
path_length,
63+
best_path,
64+
rng,
65+
}
6666
}
6767

68-
potential_new_path
69-
}
70-
71-
pub fn travelling_salesman(g: &dyn Digraph, output_graph: bool) -> GraphPath {
72-
let mut result_data: Vec<(f64, f64)> = vec![];
73-
74-
let mut rng = thread_rng();
68+
pub fn run(&mut self) {
69+
let mut temp = f64::sqrt(self.g.num_vertices() as f64);
70+
let mut iterations = 0;
71+
while temp > 1e-8_f64 && iterations < (100 * self.g.num_vertices()) {
72+
let potential_new_path = self.get_potential_new_path();
73+
74+
let new_path_length = potential_new_path.get_length_on_graph(self.g);
75+
if new_path_length < self.path_length {
76+
self.current_path = potential_new_path;
77+
self.best_path.clone_from(&self.current_path);
78+
self.path_length = new_path_length;
79+
} else {
80+
// TODO: Is this between 0 and 1?
81+
if f64::exp(-f64::abs(new_path_length - self.path_length) / temp) > self.rng.gen::<f64>() {
82+
self.current_path = potential_new_path;
83+
self.path_length = new_path_length;
84+
}
85+
}
7586

76-
let mut current_path = GraphPath {
77-
path: (0..g.num_vertices()).map(NodeIndex).collect(),
78-
};
79-
current_path.path.shuffle(&mut rng);
80-
let mut path_length = current_path.get_length_on_graph(g);
87+
temp *= 0.995;
88+
iterations += 1;
89+
self.result_data.push((temp, self.path_length));
90+
}
91+
}
8192

82-
let mut best_path = current_path.clone();
93+
fn get_potential_new_path(&mut self) -> GraphPath {
94+
let mut potential_new_path = self.current_path.clone();
8395

84-
// println!("Initial state");
85-
//
86-
// println!("\t{:?}", best_path.path);
87-
// println!("\t{}", path_length);
96+
let node_index_to_mutate = self.rng.gen_range(0..(self.g.num_vertices() - 1));
8897

89-
let mut temp = f64::sqrt(g.num_vertices() as f64);
90-
let mut iterations = 0;
91-
while temp > 1e-8_f64 && iterations < (100 * g.num_vertices()) {
92-
// println!("{}", temp);
93-
let potential_new_path = get_potential_new_path(&mut rng, g, &current_path);
98+
let reverse_or_transport: bool = self.rng.gen();
9499

95-
let new_path_length = potential_new_path.get_length_on_graph(g);
96-
if new_path_length < path_length {
97-
current_path = potential_new_path;
98-
best_path.clone_from(&current_path);
99-
path_length = new_path_length;
100+
if reverse_or_transport {
101+
let node_index_to_swap_with = if node_index_to_mutate < (self.g.num_vertices() - 1) {
102+
node_index_to_mutate + 1
103+
} else {
104+
0
105+
};
106+
potential_new_path
107+
.path
108+
.swap(node_index_to_mutate, node_index_to_swap_with);
100109
} else {
101-
// TODO: Is this between 0 and 1?
102-
if f64::exp(-f64::abs(new_path_length - path_length) / temp) > rng.gen::<f64>() {
103-
current_path = potential_new_path;
104-
path_length = new_path_length;
105-
}
110+
// Cyclic permutation
111+
let node_to_move = potential_new_path.path[node_index_to_mutate];
112+
// -2 because we are looking for new position with 1 node missing
113+
let new_node_position = self.rng.gen_range(0..(self.g.num_vertices() - 2));
114+
potential_new_path.path.remove(node_index_to_mutate);
115+
potential_new_path
116+
.path
117+
.insert(new_node_position, node_to_move)
106118
}
107119

108-
temp *= 0.995;
109-
iterations += 1;
110-
result_data.push((temp, path_length));
120+
potential_new_path
121+
}
122+
123+
pub fn get_best_path(&self) -> &GraphPath {
124+
&self.best_path
111125
}
112126

113-
if output_graph {
127+
pub fn output_graph(&self) {
114128
// We create our scatter plot from the data
115129
let s1: Plot =
116-
Plot::new(result_data.clone()).line_style(LineStyle::new().colour("#DD3355"));
130+
Plot::new(self.result_data.clone()).line_style(LineStyle::new().colour("#DD3355"));
117131

118132
// The 'view' describes what set of data is drawn
119133
let v = ContinuousView::new()
120134
.add(s1)
121135
.x_label("Temperature")
122136
.y_label("Path length")
123-
.y_range(0.0, result_data[0].1 + 100.0);
137+
.y_range(0.0, self.result_data[0].1 + 100.0);
124138

125139
// A page with a single view is then saved to an SVG file
126140
Page::single(&v).save("out/tsp_test_1.svg").unwrap();
127141
}
128-
129-
best_path
130142
}

src/main.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use rocket::serde::{Deserialize, Serialize};
1010
use rocket::serde::json::Json;
1111

1212
use broute::graphs::algorithms::{
13-
ConnectedComponents, Dijkstra, form_abstracted_graph, travelling_salesman,
13+
ConnectedComponents, Dijkstra, form_abstracted_graph, SimulatedAnnealing,
1414
};
1515
use broute::graphs::datastructures::{ALDigraph, Digraph, LatLng, NodeID};
1616
use broute::graphs::input::load_pbf_file;
@@ -109,16 +109,17 @@ fn route_optimisation(
109109

110110
println!("Abstracted graph constructed");
111111

112-
let p = travelling_salesman(&abstracted_graph, false);
112+
let mut sa = SimulatedAnnealing::new(&abstracted_graph);
113+
sa.run();
113114

114115
println!("TSP ran");
115116

116117
let mut p_node_ids = vec![];
117-
for p_node_index in p.path {
118+
for p_node_index in &sa.get_best_path().path {
118119
p_node_ids.push(
119120
abstracted_graph
120121
.nodes_data()
121-
.get_node_id_by_index(&p_node_index),
122+
.get_node_id_by_index(p_node_index),
122123
)
123124
}
124125

tests/graphs/shortest_path_tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ fn simple_dijkstra_test() {
4646
#[test]
4747
fn osm_dijkstra_test() {
4848
// Load graph
49-
let g = load_pbf_file("test_data/geofabrik/monaco-latest.osm.pbf");
49+
let g = load_pbf_file("test_data/geofabrik/monaco-latest.osm.pbf").unwrap();
5050
// Get largest connected subgraph
5151
let mut cc = ConnectedComponents::new(&g);
5252
cc.run();

tests/graphs/travelling_salesman_tests.rs

+5-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use broute::graphs::algorithms::{form_abstracted_graph, travelling_salesman, ConnectedComponents};
1+
use broute::graphs::algorithms::{form_abstracted_graph, SimulatedAnnealing, ConnectedComponents};
22
use broute::graphs::datastructures::{Digraph, LatLng, NodeID, NodeIndex};
33
use broute::graphs::input::load_pbf_file;
44

@@ -19,7 +19,7 @@ fn check_graph_adjacency(
1919
#[test]
2020
fn dijkstra_travelling_salesman_integration_test() {
2121
// Load graph
22-
let g = load_pbf_file("test_data/geofabrik/monaco-latest.osm.pbf");
22+
let g = load_pbf_file("test_data/geofabrik/monaco-latest.osm.pbf").unwrap();
2323

2424
// Get largest connected subgraph
2525
let mut cc = ConnectedComponents::new(&g);
@@ -130,8 +130,9 @@ fn dijkstra_travelling_salesman_integration_test() {
130130
// Run TSP
131131
let mut path_lengths: Vec<f64> = vec![];
132132
for _ in 0..100 {
133-
let best_path = travelling_salesman(&abstracted_graph, false);
134-
path_lengths.push(best_path.get_length_on_graph(&abstracted_graph));
133+
let mut sa = SimulatedAnnealing::new(&abstracted_graph);
134+
sa.run();
135+
path_lengths.push(sa.get_best_path().get_length_on_graph(&abstracted_graph));
135136
}
136137
assert!((path_lengths.into_iter().sum::<f64>() / 100.0) < 8.0);
137138

0 commit comments

Comments
 (0)