toolbox_rs/
metis.rs

1use itertools::Itertools;
2use log::info;
3
4use crate::{edge::InputEdge, geometry::FPCoordinate, graph::NodeID, io::read_lines};
5
6pub enum WeightType {
7    Unit,
8    Original,
9}
10
11pub enum Direction {
12    Both = 0,
13    Forward = 1,
14    Reverse = 2,
15    Closed = 3,
16}
17
18impl TryFrom<i32> for Direction {
19    type Error = ();
20
21    fn try_from(v: i32) -> Result<Self, Self::Error> {
22        match v {
23            x if x == Direction::Both as i32 => Ok(Direction::Both),
24            x if x == Direction::Forward as i32 => Ok(Direction::Forward),
25            x if x == Direction::Reverse as i32 => Ok(Direction::Reverse),
26            x if x == Direction::Closed as i32 => Ok(Direction::Closed),
27            _ => Err(()),
28        }
29    }
30}
31
32pub fn read_graph<T: std::cmp::Eq + From<usize>>(
33    filename: &str,
34    _weight_type: WeightType,
35) -> Vec<InputEdge<T>> {
36    let mut edges = Vec::new();
37
38    let mut lines = read_lines(filename).expect("could not load graph file");
39
40    let first_line = lines.next().unwrap().unwrap();
41    let sizes = first_line
42        .get(..)
43        .unwrap_or("")
44        .split_ascii_whitespace()
45        .collect_vec();
46    let number_of_nodes = sizes[0].parse::<NodeID>().unwrap();
47
48    // load unweighted metis graph and coordinates
49    for (source, line) in lines.enumerate() {
50        let line = line.unwrap();
51        let tokens = line.get(..).unwrap_or("").split_whitespace().collect_vec();
52
53        assert!(source < number_of_nodes);
54
55        for token in tokens {
56            let target = token.parse::<NodeID>().unwrap() - 1;
57            assert!(target < number_of_nodes);
58            // avoid eigenloops
59            if source == target {
60                continue;
61            }
62
63            edges.push(InputEdge {
64                source,
65                target,
66                data: T::from(1),
67            });
68        }
69    }
70    info!("loaded {} directed edges", edges.len());
71    edges
72}
73
74pub fn read_coordinates(filename: &str) -> Vec<FPCoordinate> {
75    let mut coordinates = Vec::new();
76    for line in read_lines(filename).expect("could not load coordinates file") {
77        let line = line.unwrap();
78        let tokens = line.get(..).unwrap_or("").split_whitespace().collect_vec();
79        let lon = tokens[0].parse::<f64>().unwrap() / 100_000.;
80        let lat = tokens[1].parse::<f64>().unwrap() / 100_000.;
81        // let _z = tokens[2].parse::<f64>().unwrap();
82        coordinates.push(FPCoordinate::new_from_lat_lon(lat, lon));
83    }
84
85    coordinates
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use std::fs::write;
92    use tempfile::NamedTempFile;
93
94    #[test]
95    fn read_graph_valid() {
96        let graph_content = "4 8\n3 2\n2 3\n1 3\n1 2\n";
97        let tmp_file = NamedTempFile::new().unwrap();
98        write(tmp_file.path(), graph_content).unwrap();
99
100        let edges = read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
101
102        assert_eq!(edges.len(), 6);
103        assert_eq!(
104            edges[0],
105            InputEdge {
106                source: 0,
107                target: 2,
108                data: 1
109            }
110        );
111        assert_eq!(
112            edges[1],
113            InputEdge {
114                source: 0,
115                target: 1,
116                data: 1
117            }
118        );
119        assert_eq!(
120            edges[2],
121            InputEdge {
122                source: 1,
123                target: 2,
124                data: 1
125            }
126        );
127        assert_eq!(
128            edges[3],
129            InputEdge {
130                source: 2,
131                target: 0,
132                data: 1
133            }
134        );
135        assert_eq!(
136            edges[4],
137            InputEdge {
138                source: 3,
139                target: 0,
140                data: 1
141            }
142        );
143        assert_eq!(
144            edges[5],
145            InputEdge {
146                source: 3,
147                target: 1,
148                data: 1
149            }
150        );
151    }
152
153    #[test]
154    fn read_coordinates_valid() {
155        let coord_content = "1234567 4567890\n2345678 5678901\n";
156        let tmp_file = NamedTempFile::new().unwrap();
157        write(tmp_file.path(), coord_content).unwrap();
158
159        let coords = read_coordinates(tmp_file.path().to_str().unwrap());
160
161        assert_eq!(coords.len(), 2);
162        let (lon, lat) = coords[0].to_lon_lat_pair();
163        assert!((lat - 45.67890).abs() < 1e-5);
164        assert!((lon - 12.34567).abs() < 1e-5);
165    }
166
167    #[test]
168    #[should_panic]
169    fn read_graph_invalid_file() {
170        read_graph::<usize>("nonexistent_file.txt", WeightType::Unit);
171    }
172
173    #[test]
174    #[should_panic]
175    fn read_graph_invalid_format() {
176        let graph_content = "not a number\n1 2\n";
177        let tmp_file = NamedTempFile::new().unwrap();
178        write(tmp_file.path(), graph_content).unwrap();
179
180        read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
181    }
182
183    #[test]
184    #[should_panic]
185    fn read_graph_node_out_of_bounds() {
186        let graph_content = "2 1\n3\n1\n"; // Node 3 exceeds number_of_nodes
187        let tmp_file = NamedTempFile::new().unwrap();
188        write(tmp_file.path(), graph_content).unwrap();
189
190        read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
191    }
192
193    #[test]
194    fn read_graph_skip_eigenloops() {
195        let graph_content = "2 1\n1 1\n2\n";
196        let tmp_file = NamedTempFile::new().unwrap();
197        write(tmp_file.path(), graph_content).unwrap();
198
199        let edges = read_graph::<usize>(tmp_file.path().to_str().unwrap(), WeightType::Unit);
200
201        assert_eq!(edges.len(), 0); // Eigenloop should be skipped
202    }
203
204    #[test]
205    fn direction_try_from() {
206        // test valid input values
207        assert!(matches!(Direction::try_from(0), Ok(Direction::Both)));
208        assert!(matches!(Direction::try_from(1), Ok(Direction::Forward)));
209        assert!(matches!(Direction::try_from(2), Ok(Direction::Reverse)));
210        assert!(matches!(Direction::try_from(3), Ok(Direction::Closed)));
211
212        // test invalid input values
213        assert!(Direction::try_from(-1).is_err());
214        assert!(Direction::try_from(4).is_err());
215    }
216
217    #[test]
218    fn direction_as_i32() {
219        assert_eq!(Direction::Both as i32, 0);
220        assert_eq!(Direction::Forward as i32, 1);
221        assert_eq!(Direction::Reverse as i32, 2);
222        assert_eq!(Direction::Closed as i32, 3);
223    }
224}