rust_igraph/algorithms/layout/
mds.rs1use crate::algorithms::paths::floyd_warshall::floyd_warshall_distances;
10use crate::core::{Graph, IgraphError, IgraphResult};
11
12pub fn layout_mds(graph: &Graph, dist: Option<&[Vec<f64>]>) -> IgraphResult<Vec<[f64; 2]>> {
50 let n = graph.vcount() as usize;
51 if n == 0 {
52 return Ok(Vec::new());
53 }
54 if n == 1 {
55 return Ok(vec![[0.0, 0.0]]);
56 }
57
58 if let Some(d) = dist {
59 if d.len() != n {
60 return Err(IgraphError::InvalidArgument(format!(
61 "distance matrix rows ({}) must equal vertex count ({})",
62 d.len(),
63 n
64 )));
65 }
66 for row in d {
67 if row.len() != n {
68 return Err(IgraphError::InvalidArgument(format!(
69 "distance matrix columns ({}) must equal vertex count ({})",
70 row.len(),
71 n
72 )));
73 }
74 }
75 }
76
77 let dist_matrix = if let Some(d) = dist {
79 d.to_vec()
80 } else {
81 let fw = floyd_warshall_distances(graph, None)?;
83 fw.into_iter()
84 .map(|row| {
85 row.into_iter()
86 .map(|v| v.unwrap_or(f64::INFINITY))
87 .collect()
88 })
89 .collect()
90 };
91
92 let connected = dist_matrix
94 .iter()
95 .all(|row| row.iter().all(|&v| v.is_finite()));
96
97 if connected {
98 mds_single(n, &dist_matrix)
99 } else {
100 mds_disconnected(n, &dist_matrix)
101 }
102}
103
104fn mds_single(n: usize, dist: &[Vec<f64>]) -> IgraphResult<Vec<[f64; 2]>> {
105 if n == 1 {
106 return Ok(vec![[0.0, 0.0]]);
107 }
108 if n == 2 {
109 return Ok(vec![[0.0, 0.0], [dist[0][1].max(1.0), 0.0]]);
110 }
111
112 let mut b: Vec<Vec<f64>> = vec![vec![0.0; n]; n];
114 for i in 0..n {
115 for j in 0..n {
116 b[i][j] = dist[i][j] * dist[i][j];
117 }
118 }
119
120 let mut row_means = vec![0.0_f64; n];
122 for i in 0..n {
123 let sum: f64 = b[i].iter().sum();
124 row_means[i] = sum / n as f64;
125 }
126
127 let grand_mean: f64 = row_means.iter().sum::<f64>() / n as f64;
128
129 for i in 0..n {
130 for j in 0..n {
131 b[i][j] = -0.5 * (b[i][j] - row_means[i] - row_means[j] + grand_mean);
132 }
133 }
134
135 let (eigenvalues, eigenvectors) = top_k_eigen_symmetric(&b, 2);
137
138 let mut pos = vec![[0.0_f64; 2]; n];
140 for dim in 0..2 {
141 let scale = eigenvalues[dim].abs().sqrt();
142 for i in 0..n {
143 pos[i][dim] = scale * eigenvectors[dim][i];
144 }
145 }
146
147 Ok(pos)
148}
149
150fn mds_disconnected(n: usize, dist: &[Vec<f64>]) -> IgraphResult<Vec<[f64; 2]>> {
151 let mut component = vec![usize::MAX; n];
153 let mut comp_id = 0;
154 let mut components: Vec<Vec<usize>> = Vec::new();
155
156 for start in 0..n {
157 if component[start] != usize::MAX {
158 continue;
159 }
160 let mut members = Vec::new();
161 let mut queue = std::collections::VecDeque::new();
162 queue.push_back(start);
163 component[start] = comp_id;
164 while let Some(v) = queue.pop_front() {
165 members.push(v);
166 for j in 0..n {
167 if component[j] == usize::MAX && dist[v][j].is_finite() && dist[v][j] > 0.0 {
168 component[j] = comp_id;
169 queue.push_back(j);
170 }
171 }
172 }
173 components.push(members);
174 comp_id += 1;
175 }
176
177 let mut pos = vec![[0.0_f64; 2]; n];
179 let mut x_offset = 0.0_f64;
180
181 for comp in &components {
182 let cn = comp.len();
183 if cn == 1 {
184 pos[comp[0]] = [x_offset, 0.0];
185 x_offset += 2.0;
186 continue;
187 }
188
189 let mut sub_dist = vec![vec![0.0_f64; cn]; cn];
191 for (li, &vi) in comp.iter().enumerate() {
192 for (lj, &vj) in comp.iter().enumerate() {
193 sub_dist[li][lj] = dist[vi][vj];
194 }
195 }
196
197 let sub_pos = mds_single(cn, &sub_dist)?;
198
199 let mut min_x = f64::INFINITY;
201 let mut max_x = f64::NEG_INFINITY;
202 for p in &sub_pos {
203 if p[0] < min_x {
204 min_x = p[0];
205 }
206 if p[0] > max_x {
207 max_x = p[0];
208 }
209 }
210
211 for (li, &vi) in comp.iter().enumerate() {
213 pos[vi][0] = sub_pos[li][0] - min_x + x_offset;
214 pos[vi][1] = sub_pos[li][1];
215 }
216
217 x_offset += (max_x - min_x) + 2.0;
218 }
219
220 Ok(pos)
221}
222
223fn top_k_eigen_symmetric(matrix: &[Vec<f64>], k: usize) -> (Vec<f64>, Vec<Vec<f64>>) {
227 let n = matrix.len();
228 let k = k.min(n);
229 let max_iter = 300;
230 let tol = 1e-10;
231
232 let mut eigenvalues = Vec::with_capacity(k);
233 let mut eigenvectors: Vec<Vec<f64>> = Vec::with_capacity(k);
234
235 let mut work: Vec<Vec<f64>> = matrix.to_vec();
237
238 let mut rng = SplitMix64::new(31415);
239
240 for _ev in 0..k {
241 let mut v: Vec<f64> = (0..n).map(|_| rng.next_uniform() - 0.5).collect();
243 normalize(&mut v);
244
245 let mut eigenvalue = 0.0_f64;
246
247 for _iter in 0..max_iter {
248 let mut w = vec![0.0_f64; n];
250 for i in 0..n {
251 let mut sum = 0.0_f64;
252 for j in 0..n {
253 sum += work[i][j] * v[j];
254 }
255 w[i] = sum;
256 }
257
258 let new_eigenvalue: f64 = v.iter().zip(w.iter()).map(|(a, b)| a * b).sum();
260
261 normalize(&mut w);
262
263 let diff: f64 = v.iter().zip(w.iter()).map(|(a, b)| (a - b) * (a - b)).sum();
265 v = w;
266 eigenvalue = new_eigenvalue;
267
268 if diff < tol {
269 break;
270 }
271 }
272
273 for i in 0..n {
275 for j in 0..n {
276 work[i][j] -= eigenvalue * v[i] * v[j];
277 }
278 }
279
280 eigenvalues.push(eigenvalue);
281 eigenvectors.push(v);
282 }
283
284 (eigenvalues, eigenvectors)
285}
286
287fn normalize(v: &mut [f64]) {
288 let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
289 if norm > 0.0 {
290 for x in v.iter_mut() {
291 *x /= norm;
292 }
293 }
294}
295
296struct SplitMix64 {
301 state: u64,
302}
303
304impl SplitMix64 {
305 fn new(seed: u64) -> Self {
306 Self { state: seed }
307 }
308
309 fn next_u64(&mut self) -> u64 {
310 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
311 let mut z = self.state;
312 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
313 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
314 z ^ (z >> 31)
315 }
316
317 fn next_uniform(&mut self) -> f64 {
318 (self.next_u64() >> 11) as f64 / ((1u64 << 53) as f64)
319 }
320}
321
322#[cfg(test)]
327mod tests {
328 use super::*;
329
330 #[test]
331 fn test_mds_empty() {
332 let g = Graph::with_vertices(0);
333 let pos = layout_mds(&g, None).unwrap();
334 assert!(pos.is_empty());
335 }
336
337 #[test]
338 fn test_mds_single() {
339 let g = Graph::with_vertices(1);
340 let pos = layout_mds(&g, None).unwrap();
341 assert_eq!(pos.len(), 1);
342 assert!(pos[0][0].abs() < 1e-10 && pos[0][1].abs() < 1e-10);
343 }
344
345 #[test]
346 fn test_mds_two_vertices() {
347 let mut g = Graph::with_vertices(2);
348 g.add_edge(0, 1).unwrap();
349 let pos = layout_mds(&g, None).unwrap();
350 assert_eq!(pos.len(), 2);
351 assert!(pos[0][0].is_finite() && pos[1][0].is_finite());
352 let dx = pos[0][0] - pos[1][0];
354 let dy = pos[0][1] - pos[1][1];
355 assert!((dx * dx + dy * dy).sqrt() > 0.1);
356 }
357
358 #[test]
359 fn test_mds_path() {
360 let mut g = Graph::with_vertices(5);
361 for i in 0..4 {
362 g.add_edge(i, i + 1).unwrap();
363 }
364 let pos = layout_mds(&g, None).unwrap();
365 assert_eq!(pos.len(), 5);
366 for p in &pos {
367 assert!(p[0].is_finite() && p[1].is_finite());
368 }
369 }
370
371 #[test]
372 fn test_mds_cycle() {
373 let mut g = Graph::with_vertices(6);
374 for i in 0..6 {
375 g.add_edge(i, (i + 1) % 6).unwrap();
376 }
377 let pos = layout_mds(&g, None).unwrap();
378 assert_eq!(pos.len(), 6);
379 for p in &pos {
380 assert!(p[0].is_finite() && p[1].is_finite());
381 }
382 }
383
384 #[test]
385 fn test_mds_with_dist_matrix() {
386 let g = Graph::with_vertices(3);
387 let dist = vec![
388 vec![0.0, 1.0, 2.0],
389 vec![1.0, 0.0, 1.0],
390 vec![2.0, 1.0, 0.0],
391 ];
392 let pos = layout_mds(&g, Some(&dist)).unwrap();
393 assert_eq!(pos.len(), 3);
394 for p in &pos {
395 assert!(p[0].is_finite() && p[1].is_finite());
396 }
397 }
398
399 #[test]
400 fn test_mds_wrong_dist_size() {
401 let g = Graph::with_vertices(3);
402 let dist = vec![vec![0.0, 1.0], vec![1.0, 0.0]];
403 assert!(layout_mds(&g, Some(&dist)).is_err());
404 }
405
406 #[test]
407 fn test_mds_disconnected() {
408 let mut g = Graph::with_vertices(4);
409 g.add_edge(0, 1).unwrap();
410 g.add_edge(2, 3).unwrap();
411 let pos = layout_mds(&g, None).unwrap();
412 assert_eq!(pos.len(), 4);
413 for p in &pos {
414 assert!(p[0].is_finite() && p[1].is_finite());
415 }
416 let comp0_max_x = pos[0][0].max(pos[1][0]);
418 let comp1_min_x = pos[2][0].min(pos[3][0]);
419 assert!(comp1_min_x > comp0_max_x - 0.1 || comp0_max_x > comp1_min_x - 0.1);
420 }
421
422 #[test]
423 fn test_mds_deterministic() {
424 let mut g = Graph::with_vertices(4);
425 g.add_edge(0, 1).unwrap();
426 g.add_edge(1, 2).unwrap();
427 g.add_edge(2, 3).unwrap();
428 g.add_edge(3, 0).unwrap();
429 let pos1 = layout_mds(&g, None).unwrap();
430 let pos2 = layout_mds(&g, None).unwrap();
431 for i in 0..4 {
432 assert!((pos1[i][0] - pos2[i][0]).abs() < 1e-8);
433 assert!((pos1[i][1] - pos2[i][1]).abs() < 1e-8);
434 }
435 }
436
437 #[test]
438 fn test_mds_distance_preservation() {
439 let mut g = Graph::with_vertices(4);
441 g.add_edge(0, 1).unwrap();
442 g.add_edge(1, 2).unwrap();
443 g.add_edge(2, 3).unwrap();
444 let pos = layout_mds(&g, None).unwrap();
445
446 let d01 = ((pos[0][0] - pos[1][0]).powi(2) + (pos[0][1] - pos[1][1]).powi(2)).sqrt();
448 let d03 = ((pos[0][0] - pos[3][0]).powi(2) + (pos[0][1] - pos[3][1]).powi(2)).sqrt();
449 assert!(d01 < d03, "d01={d01} should be < d03={d03}");
450 }
451
452 #[test]
453 fn test_mds_complete_graph() {
454 let mut g = Graph::with_vertices(4);
455 for i in 0..4u32 {
456 for j in (i + 1)..4 {
457 g.add_edge(i, j).unwrap();
458 }
459 }
460 let pos = layout_mds(&g, None).unwrap();
461 assert_eq!(pos.len(), 4);
462 for p in &pos {
463 assert!(p[0].is_finite() && p[1].is_finite());
464 }
465 }
466}