Skip to main content

graco/
colony.rs

1use crate::ant::Ant;
2use crate::io::{export_dot, load_graph};
3use crate::petgraph::graph::{Graph, NodeIndex};
4use crate::petgraph::prelude::*;
5use crate::petgraph::EdgeType;
6use crate::Objective;
7use std::cmp::PartialEq;
8use std::f64::EPSILON;
9use std::fmt::{Debug, Display};
10use std::marker::Send;
11use std::path::PathBuf;
12use std::sync::mpsc::channel;
13use std::thread::sleep;
14use std::time::Duration;
15use threadpool::ThreadPool;
16
17#[derive(Clone, Copy, Debug)]
18pub struct ColonyOpts<N> {
19    /// Verbose mode will output all pheromone graphs at each iteration.
20    pub verbose: bool,
21    /// Set the initial pheromone in the graph
22    pub initphe: f64,
23    /// Set the evaporation rate, between 0 and 1, lower faster: 0 is iteration-only pheromones.
24    pub evaporation: f64,
25    /// Set the maximum number of iterations
26    pub iterations: u32,
27    /// Set the percentage of ants which need to find the same path to consider the problem solved
28    pub threshold: f64,
29    /// Density constant Q of the pheromones
30    pub density: f64,
31    /// Set the relative importance of pheromones for the probabilistic function
32    pub alpha: i32,
33    /// Set the relative importance of distance for the probabilistic function
34    pub beta: i32,
35    /// Set the seed of the first ant. Each subsequent ant will have that number incremented
36    pub seed: Option<u64>,
37    /// Specify the name of the node to reach
38    pub visit_node: Option<N>,
39    /// Specify the number of nodes to reach
40    pub visit_count: Option<usize>,
41    /// Set to true if the objective is to reach a dead end
42    pub reach_dead_end: bool,
43    /// Specify the starting node name
44    pub start_node: Option<N>,
45    /// Set the probability of not following the pheromone trail
46    pub wander: f64,
47    /// Number of ants, maxed to the number of nodes in the graph
48    pub ants: usize,
49    /// Change the number of CPUs to run on (default is max available)
50    pub cpus: usize,
51}
52
53impl<N: 'static + Clone + Display + Debug + PartialEq + Send> Default for ColonyOpts<N> {
54    /// By default, the objective is to visit all nodes
55    fn default() -> Self {
56        ColonyOpts {
57            verbose: false,
58            initphe: 1.0,
59            evaporation: 0.95,
60            iterations: 100,
61            threshold: 0.9,
62            density: 3.0,
63            alpha: 1,
64            beta: 2,
65            wander: 0.15,
66            cpus: 8,
67            ants: 50,
68            seed: None,
69            start_node: None,
70            visit_node: None,
71            visit_count: None,
72            reach_dead_end: false,
73        }
74    }
75}
76
77#[derive(Debug, Clone)]
78pub struct Colony<N, Ty: Clone + EdgeType> {
79    pub viz_graph: Graph<N, f64, Ty>,
80    pub pheromone_graph: Graph<N, f64, Ty>,
81    pub opt: ColonyOpts<N>,
82    shortest_path: Vec<usize>,
83    iteration: u32,
84    init_seed: u64,
85    obj: Objective,
86    start_idx: usize,
87}
88
89impl Colony<String, Undirected> {
90    pub fn with_input_path(opt: ColonyOpts<String>, input: PathBuf) -> Self {
91        let (viz_graph, pheromone_graph) = load_graph(input, opt.initphe);
92        Self::from_options(opt, viz_graph, pheromone_graph)
93    }
94}
95
96impl<
97        N: 'static + Clone + Display + Debug + PartialEq + Send,
98        Ty: 'static + Clone + EdgeType + Send,
99    > Colony<N, Ty>
100{
101    pub fn from_options(
102        opt: ColonyOpts<N>,
103        viz_graph: Graph<N, f64, Ty>,
104        pheromone_graph: Graph<N, f64, Ty>,
105    ) -> Self {
106        let seed = match opt.seed {
107            Some(seed) => seed,
108            None => {
109                // Generate a new random number as a seed.
110                rand::random::<u64>()
111            }
112        };
113
114        info!("Seed: {}", seed);
115
116        // Interpret the objective
117        let visit_node = opt.visit_node.clone();
118        let obj = if let Some(name) = visit_node {
119            // Find the destination node index
120            let mut idx = 0;
121            let mut found = false;
122            for node in viz_graph.raw_nodes() {
123                if node.weight == name {
124                    found = true;
125                    break;
126                }
127                idx += 1;
128            }
129            if !found {
130                panic!("could not find the requested objective node");
131            }
132            Objective::ReachNodeIdx(idx)
133        } else if let Some(count) = opt.visit_count {
134            Objective::VisitNNodes(count)
135        } else if opt.reach_dead_end {
136            Objective::ReachDeadEnd
137        } else {
138            Objective::VisitAllNodes
139        };
140
141        let start_node = opt.start_node.clone();
142        let start_idx = if let Some(name) = start_node {
143            // Find the destination node index
144            let mut idx = 0;
145            let mut found = false;
146            for node in viz_graph.raw_nodes() {
147                if node.weight == name {
148                    found = true;
149                    break;
150                }
151                idx += 1;
152            }
153            if !found {
154                panic!("could not find the requested starting node");
155            }
156            idx
157        } else {
158            0
159        };
160
161        Colony {
162            viz_graph,
163            pheromone_graph,
164            opt,
165            shortest_path: Vec::new(),
166            iteration: 0,
167            init_seed: seed,
168            obj: obj,
169            start_idx,
170        }
171    }
172
173    /// The UN-normalized probability between two nodes, i.e. visibility times pheromone.
174    pub fn unnormalized_probability(&self, from: usize, to: usize) -> f64 {
175        let node1 = NodeIndex::new(from);
176        let node2 = NodeIndex::new(to);
177        match self.pheromone_graph.find_edge(node1, node2) {
178            None => 0.0,
179            Some(edge) => {
180                // This edge exists.
181                let tau = self.pheromone_graph[edge].powi(self.opt.alpha);
182                // Find the visibility of this node ("distance").
183                let viz = self.viz_graph[edge].powi(self.opt.beta);
184                tau * viz
185            }
186        }
187    }
188
189    /// Update the pheromones based on the shortest list of edges.
190    pub fn update_pheromones(&mut self, new_path: Vec<usize>) {
191        // Export the first pheromone graph if requested
192        if self.iteration == 0 && self.opt.verbose {
193            self.export();
194            export_dot(&self.viz_graph, "dotgraphs/visibility.dot");
195        }
196
197        self.shortest_path = new_path.clone();
198        // Evaporate all pheromones and increase the pheromones for the used path.
199        for edge_no in 0..self.pheromone_graph.edge_count() {
200            self.pheromone_graph[EdgeIndex::new(edge_no)] *= 1.0 - self.opt.evaporation;
201            if new_path.contains(&edge_no) {
202                self.pheromone_graph[EdgeIndex::new(edge_no)] +=
203                    self.opt.density / (new_path.len() as f64);
204            }
205        }
206        self.iteration += 1;
207
208        if self.opt.verbose {
209            self.export();
210        }
211    }
212
213    /// Let the ants explore the problem
214    pub fn explore(&mut self) -> (bool, f64, Vec<N>) {
215        // Create a threadpool for this exploration
216        let pool = ThreadPool::new(self.opt.cpus);
217
218        let min_equal_paths = ((self.opt.ants as f64) * self.opt.threshold) as u32;
219
220        while self.iteration < self.opt.iterations {
221            let (tx, rx) = channel();
222
223            for ant_no in 0..self.opt.ants {
224                let tx = tx.clone();
225                let colony = self.clone();
226                let current_seed = self.init_seed;
227                let objective = self.obj.clone();
228                let start_idx = self.start_idx;
229                pool.execute(move || {
230                    // Let's sleep to _try_ to keep the ants ordered in the same way between runs
231                    sleep(Duration::from_millis(5));
232
233                    // Initialize the n-th Ant.
234                    let mut ant = Ant::from_node(ant_no, start_idx, current_seed, objective);
235                    while ant.move_ant(&colony) {}
236                    ant.remove_cycles(&colony);
237                    tx.send(ant)
238                        .expect("channel will be there waiting for the pool");
239                });
240
241                self.init_seed += 1;
242            }
243
244            let mut shortest_path: Vec<usize> = Vec::new();
245            let mut lowest_weight = 0.0;
246            let mut equal_paths = 1;
247
248            // And wait for all ants to finish
249            rx.iter().take(self.opt.ants).for_each(|ant| {
250                if ant.number == 0 || ant.edge_weight < lowest_weight {
251                    lowest_weight = ant.edge_weight;
252                    shortest_path = ant.visited_edges.clone();
253                    equal_paths = 1; // Reset counter
254                } else if (lowest_weight - ant.edge_weight).abs() < EPSILON {
255                    // At least two ants have found the same path
256                    equal_paths += 1;
257                }
258            });
259
260            if equal_paths >= min_equal_paths && self.iteration > 0 {
261                self.shortest_path = shortest_path;
262                info!(
263                    "Stagnation reached: {:0.2}% of ants have the same path",
264                    self.opt.threshold * 100.0
265                );
266                return self.end(false);
267            }
268
269            self.update_pheromones(shortest_path);
270            info!("Completed iteration {}", self.iteration);
271        }
272        self.end(true)
273    }
274
275    fn end(&self, max_iter: bool) -> (bool, f64, Vec<N>) {
276        self.export();
277        if max_iter {
278            warn!("Max iterations reached")
279        }
280        // Compute the weight and path
281        let mut weight = 0.0;
282        let mut path = Vec::new();
283        for edge_idx in self.shortest_path.clone() {
284            let edge = EdgeIndex::new(edge_idx);
285            let (start_nidx, end_nidx) = self.viz_graph.edge_endpoints(edge).unwrap();
286            let start_node = self.viz_graph[start_nidx].clone();
287            let end_node = self.viz_graph[end_nidx].clone();
288            if path.is_empty() {
289                if start_nidx == NodeIndex::new(self.start_idx) {
290                    path.push(start_node);
291                    path.push(end_node);
292                } else {
293                    // Add in reverse order
294                    path.push(end_node);
295                    path.push(start_node);
296                }
297            } else if path.last().unwrap() == &start_node {
298                path.push(end_node);
299            } else {
300                path.push(start_node);
301            }
302
303            weight += self.viz_graph[edge];
304        }
305
306        println!(
307            "iterations: {}\nweight: {}\npath: {:?}",
308            self.iteration + 1,
309            weight,
310            path,
311        );
312
313        (!max_iter, weight, path)
314    }
315
316    fn export(&self) {
317        export_dot(
318            &self.pheromone_graph,
319            &format!("dotgraphs/pheromones_it_{}.dot", self.iteration),
320        );
321    }
322}
323
324#[test]
325fn test_colony_complete() {
326    use std::path::PathBuf;
327    use std::str::FromStr;
328    let colony_opt = ColonyOpts {
329        verbose: false,
330        initphe: 1.0,
331        evaporation: 0.95,
332        iterations: 100,
333        threshold: 1.0,
334        density: 1.0,
335        alpha: 1,
336        beta: 2,
337        seed: Some(4580147489518812583),
338        visit_node: None,
339        visit_count: None,
340        reach_dead_end: false,
341        start_node: None,
342        wander: 0.15,
343        ants: 10,
344        cpus: 1,
345    };
346
347    let mut colony =
348        Colony::with_input_path(colony_opt, PathBuf::from_str("complete_graph.txt").unwrap());
349
350    let (success, weight, path) = colony.explore();
351
352    assert!(success);
353    assert!(weight - 8.1 < EPSILON);
354    assert_eq!(path, vec!["S", "A", "D", "B", "C"]);
355}