rust_igraph/algorithms/layout/
lgl.rs1use std::collections::VecDeque;
10
11use crate::core::{Graph, IgraphError, IgraphResult};
12
13#[derive(Debug, Clone)]
15pub struct LglParams {
16 pub maxit: u32,
18 pub maxdelta: Option<f64>,
20 pub area: Option<f64>,
22 pub coolexp: f64,
24 pub repulserad: Option<f64>,
26 pub cellsize: Option<f64>,
28 pub root: Option<u32>,
30}
31
32impl Default for LglParams {
33 fn default() -> Self {
34 Self {
35 maxit: 150,
36 maxdelta: None,
37 area: None,
38 coolexp: 1.5,
39 repulserad: None,
40 cellsize: None,
41 root: None,
42 }
43 }
44}
45
46pub fn layout_lgl(graph: &Graph, params: &LglParams) -> IgraphResult<Vec<[f64; 2]>> {
82 let n = graph.vcount() as usize;
83 if n == 0 {
84 return Ok(Vec::new());
85 }
86 if n == 1 {
87 return Ok(vec![[0.0, 0.0]]);
88 }
89
90 let root = if let Some(r) = params.root {
91 if r >= graph.vcount() {
92 return Err(IgraphError::InvalidArgument(format!(
93 "root vertex {} out of range (vcount={})",
94 r,
95 graph.vcount()
96 )));
97 }
98 r as usize
99 } else {
100 highest_degree_vertex(graph)
101 };
102
103 let maxdelta = params.maxdelta.unwrap_or(n as f64);
104 let area = params.area.unwrap_or((n * n) as f64);
105 let coolexp = params.coolexp;
106 let repulserad = params.repulserad.unwrap_or(area * n as f64);
107 let cellsize = params.cellsize.unwrap_or(area.sqrt().sqrt());
108
109 let frk = (area / n as f64).sqrt();
110
111 let adj = build_adjacency(graph, n);
113
114 let (layers, parent) = bfs_layers(n, root, &adj);
116
117 let mut pos = vec![[0.0_f64; 2]; n];
119 let mut placed = vec![false; n];
120 let mut rng = SplitMix64::new(42);
121
122 pos[root] = [0.0, 0.0];
124 placed[root] = true;
125
126 for layer in layers.iter().skip(1) {
128 for &v in layer {
129 let p = parent[v];
130 if p == usize::MAX {
131 pos[v] = [rng.next_uniform() - 0.5, rng.next_uniform() - 0.5];
132 placed[v] = true;
133 continue;
134 }
135 let angle = rng.next_uniform() * std::f64::consts::TAU;
137 let radius = frk * 0.5;
138 pos[v] = [
139 pos[p][0] + radius * angle.cos(),
140 pos[p][1] + radius * angle.sin(),
141 ];
142 placed[v] = true;
143 }
144 }
145
146 for v in 0..n {
148 if !placed[v] {
149 pos[v] = [
150 (rng.next_uniform() - 0.5) * frk * 2.0,
151 (rng.next_uniform() - 0.5) * frk * 2.0,
152 ];
153 }
154 }
155
156 let mut disp = vec![[0.0_f64; 2]; n];
158
159 for iter in 0..params.maxit {
160 let temp = maxdelta * (-coolexp * (iter as f64) / (params.maxit as f64)).exp();
162
163 disp.fill([0.0, 0.0]);
165
166 apply_repulsion_grid(n, &pos, &mut disp, frk, repulserad, cellsize);
168
169 for v in 0..n {
171 for &u in &adj[v] {
172 if u <= v {
173 continue;
174 }
175 let dx = pos[v][0] - pos[u][0];
176 let dy = pos[v][1] - pos[u][1];
177 let dist = (dx * dx + dy * dy).sqrt();
178 if dist == 0.0 {
179 continue;
180 }
181 let force = dist * dist / frk;
183 let fx = force * dx / dist;
184 let fy = force * dy / dist;
185 disp[v][0] -= fx;
186 disp[v][1] -= fy;
187 disp[u][0] += fx;
188 disp[u][1] += fy;
189 }
190 }
191
192 for v in 0..n {
194 let dx = disp[v][0];
195 let dy = disp[v][1];
196 let len = (dx * dx + dy * dy).sqrt();
197 if len > 0.0 {
198 let scale = temp.min(len) / len;
199 pos[v][0] += dx * scale;
200 pos[v][1] += dy * scale;
201 }
202 }
203 }
204
205 Ok(pos)
206}
207
208fn highest_degree_vertex(graph: &Graph) -> usize {
209 let n = graph.vcount();
210 let mut best = 0_usize;
211 let mut best_deg = 0_usize;
212 for v in 0..n {
213 if let Ok(d) = graph.degree(v) {
214 if d > best_deg {
215 best_deg = d;
216 best = v as usize;
217 }
218 }
219 }
220 best
221}
222
223fn build_adjacency(graph: &Graph, n: usize) -> Vec<Vec<usize>> {
224 let mut adj: Vec<Vec<usize>> = vec![Vec::new(); n];
225 for eid in 0..graph.ecount() as u32 {
226 if let Ok((src, tgt)) = graph.edge(eid) {
227 let s = src as usize;
228 let t = tgt as usize;
229 adj[s].push(t);
230 if s != t {
231 adj[t].push(s);
232 }
233 }
234 }
235 adj
236}
237
238fn bfs_layers(n: usize, root: usize, adj: &[Vec<usize>]) -> (Vec<Vec<usize>>, Vec<usize>) {
239 let mut visited = vec![false; n];
240 let mut parent = vec![usize::MAX; n];
241 let mut layers: Vec<Vec<usize>> = Vec::new();
242
243 let mut queue = VecDeque::new();
244 queue.push_back(root);
245 visited[root] = true;
246
247 let mut current_layer = vec![root];
248 let mut next_layer = Vec::new();
249
250 layers.push(current_layer.clone());
251
252 loop {
253 next_layer.clear();
254 for &v in ¤t_layer {
255 for &u in &adj[v] {
256 if !visited[u] {
257 visited[u] = true;
258 parent[u] = v;
259 next_layer.push(u);
260 }
261 }
262 }
263 if next_layer.is_empty() {
264 break;
265 }
266 layers.push(next_layer.clone());
267 current_layer.clone_from(&next_layer);
268 }
269
270 (layers, parent)
271}
272
273fn apply_repulsion_grid(
274 n: usize,
275 pos: &[[f64; 2]],
276 disp: &mut [[f64; 2]],
277 frk: f64,
278 repulserad: f64,
279 cellsize: f64,
280) {
281 if n <= 100 {
282 for i in 0..n {
284 for j in (i + 1)..n {
285 let dx = pos[i][0] - pos[j][0];
286 let dy = pos[i][1] - pos[j][1];
287 let dist_sq = dx * dx + dy * dy;
288 let dist = dist_sq.sqrt();
289 if dist == 0.0 || dist_sq >= repulserad {
290 continue;
291 }
292 let force = frk * frk * (1.0 / dist - dist_sq / repulserad);
294 let fx = force * dx / dist;
295 let fy = force * dy / dist;
296 disp[i][0] += fx;
297 disp[i][1] += fy;
298 disp[j][0] -= fx;
299 disp[j][1] -= fy;
300 }
301 }
302 } else {
303 grid_repulsion(n, pos, disp, frk, repulserad, cellsize);
305 }
306}
307
308fn grid_repulsion(
309 n: usize,
310 pos: &[[f64; 2]],
311 disp: &mut [[f64; 2]],
312 frk: f64,
313 repulserad: f64,
314 cellsize: f64,
315) {
316 if cellsize <= 0.0 {
317 return;
318 }
319
320 let mut min_x = f64::INFINITY;
322 let mut max_x = f64::NEG_INFINITY;
323 let mut min_y = f64::INFINITY;
324 let mut max_y = f64::NEG_INFINITY;
325 for p in pos.iter().take(n) {
326 if p[0] < min_x {
327 min_x = p[0];
328 }
329 if p[0] > max_x {
330 max_x = p[0];
331 }
332 if p[1] < min_y {
333 min_y = p[1];
334 }
335 if p[1] > max_y {
336 max_y = p[1];
337 }
338 }
339
340 let width = max_x - min_x;
341 let height = max_y - min_y;
342 if width <= 0.0 && height <= 0.0 {
343 return;
344 }
345
346 let cols = ((width / cellsize).ceil() as usize).max(1);
347 let rows = ((height / cellsize).ceil() as usize).max(1);
348
349 let max_cells = 10_000;
351 if cols.saturating_mul(rows) > max_cells {
352 brute_force_repulsion(n, pos, disp, frk, repulserad);
354 return;
355 }
356
357 let mut grid: Vec<Vec<usize>> = vec![Vec::new(); cols * rows];
359 let mut cell_of = vec![0_usize; n];
360
361 for v in 0..n {
362 let cx = ((pos[v][0] - min_x) / cellsize).floor() as usize;
363 let cy = ((pos[v][1] - min_y) / cellsize).floor() as usize;
364 let cx = cx.min(cols - 1);
365 let cy = cy.min(rows - 1);
366 let cell = cy * cols + cx;
367 grid[cell].push(v);
368 cell_of[v] = cell;
369 }
370
371 for v in 0..n {
373 let cell = cell_of[v];
374 let cy = cell / cols;
375 let cx = cell % cols;
376
377 let row_start = if cy > 0 { cy - 1 } else { 0 };
378 let row_end = (cy + 1).min(rows - 1);
379 let col_start = if cx > 0 { cx - 1 } else { 0 };
380 let col_end = (cx + 1).min(cols - 1);
381
382 for ry in row_start..=row_end {
383 for rx in col_start..=col_end {
384 let neighbor_cell = ry * cols + rx;
385 for &u in &grid[neighbor_cell] {
386 if u <= v {
387 continue;
388 }
389 let dx = pos[v][0] - pos[u][0];
390 let dy = pos[v][1] - pos[u][1];
391 let dist_sq = dx * dx + dy * dy;
392 let dist = dist_sq.sqrt();
393 if dist == 0.0 || dist_sq >= repulserad {
394 continue;
395 }
396 let force = frk * frk * (1.0 / dist - dist_sq / repulserad);
397 let fx = force * dx / dist;
398 let fy = force * dy / dist;
399 disp[v][0] += fx;
400 disp[v][1] += fy;
401 disp[u][0] -= fx;
402 disp[u][1] -= fy;
403 }
404 }
405 }
406 }
407}
408
409fn brute_force_repulsion(
410 n: usize,
411 pos: &[[f64; 2]],
412 disp: &mut [[f64; 2]],
413 frk: f64,
414 repulserad: f64,
415) {
416 for i in 0..n {
417 for j in (i + 1)..n {
418 let dx = pos[i][0] - pos[j][0];
419 let dy = pos[i][1] - pos[j][1];
420 let dist_sq = dx * dx + dy * dy;
421 let dist = dist_sq.sqrt();
422 if dist == 0.0 || dist_sq >= repulserad {
423 continue;
424 }
425 let force = frk * frk * (1.0 / dist - dist_sq / repulserad);
426 let fx = force * dx / dist;
427 let fy = force * dy / dist;
428 disp[i][0] += fx;
429 disp[i][1] += fy;
430 disp[j][0] -= fx;
431 disp[j][1] -= fy;
432 }
433 }
434}
435
436struct SplitMix64 {
441 state: u64,
442}
443
444impl SplitMix64 {
445 fn new(seed: u64) -> Self {
446 Self { state: seed }
447 }
448
449 fn next_u64(&mut self) -> u64 {
450 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
451 let mut z = self.state;
452 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
453 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
454 z ^ (z >> 31)
455 }
456
457 fn next_uniform(&mut self) -> f64 {
458 (self.next_u64() >> 11) as f64 / ((1u64 << 53) as f64)
459 }
460}
461
462#[cfg(test)]
467mod tests {
468 use super::*;
469
470 #[test]
471 fn test_lgl_empty() {
472 let g = Graph::with_vertices(0);
473 let params = LglParams::default();
474 let pos = layout_lgl(&g, ¶ms).unwrap();
475 assert!(pos.is_empty());
476 }
477
478 #[test]
479 fn test_lgl_single() {
480 let g = Graph::with_vertices(1);
481 let params = LglParams::default();
482 let pos = layout_lgl(&g, ¶ms).unwrap();
483 assert_eq!(pos.len(), 1);
484 assert!(pos[0][0].abs() < 1e-10 && pos[0][1].abs() < 1e-10);
485 }
486
487 #[test]
488 fn test_lgl_two_vertices() {
489 let mut g = Graph::with_vertices(2);
490 g.add_edge(0, 1).unwrap();
491 let params = LglParams::default();
492 let pos = layout_lgl(&g, ¶ms).unwrap();
493 assert_eq!(pos.len(), 2);
494 assert!(pos[0][0].is_finite() && pos[1][0].is_finite());
495 let dx = pos[0][0] - pos[1][0];
496 let dy = pos[0][1] - pos[1][1];
497 assert!((dx * dx + dy * dy).sqrt() > 0.01);
498 }
499
500 #[test]
501 fn test_lgl_path() {
502 let mut g = Graph::with_vertices(6);
503 for i in 0..5 {
504 g.add_edge(i, i + 1).unwrap();
505 }
506 let params = LglParams::default();
507 let pos = layout_lgl(&g, ¶ms).unwrap();
508 assert_eq!(pos.len(), 6);
509 for p in &pos {
510 assert!(p[0].is_finite() && p[1].is_finite());
511 }
512 }
513
514 #[test]
515 fn test_lgl_cycle() {
516 let mut g = Graph::with_vertices(8);
517 for i in 0..8 {
518 g.add_edge(i, (i + 1) % 8).unwrap();
519 }
520 let params = LglParams::default();
521 let pos = layout_lgl(&g, ¶ms).unwrap();
522 assert_eq!(pos.len(), 8);
523 for p in &pos {
524 assert!(p[0].is_finite() && p[1].is_finite());
525 }
526 }
527
528 #[test]
529 fn test_lgl_complete() {
530 let mut g = Graph::with_vertices(5);
531 for i in 0..5u32 {
532 for j in (i + 1)..5 {
533 g.add_edge(i, j).unwrap();
534 }
535 }
536 let params = LglParams::default();
537 let pos = layout_lgl(&g, ¶ms).unwrap();
538 assert_eq!(pos.len(), 5);
539 for p in &pos {
540 assert!(p[0].is_finite() && p[1].is_finite());
541 }
542 }
543
544 #[test]
545 fn test_lgl_with_root() {
546 let mut g = Graph::with_vertices(5);
547 g.add_edge(0, 1).unwrap();
548 g.add_edge(1, 2).unwrap();
549 g.add_edge(2, 3).unwrap();
550 g.add_edge(3, 4).unwrap();
551 let params = LglParams {
552 root: Some(2),
553 ..LglParams::default()
554 };
555 let pos = layout_lgl(&g, ¶ms).unwrap();
556 assert_eq!(pos.len(), 5);
557 for p in &pos {
558 assert!(p[0].is_finite() && p[1].is_finite());
559 }
560 }
561
562 #[test]
563 fn test_lgl_root_out_of_range() {
564 let g = Graph::with_vertices(3);
565 let params = LglParams {
566 root: Some(5),
567 ..LglParams::default()
568 };
569 assert!(layout_lgl(&g, ¶ms).is_err());
570 }
571
572 #[test]
573 fn test_lgl_disconnected() {
574 let mut g = Graph::with_vertices(6);
575 g.add_edge(0, 1).unwrap();
576 g.add_edge(1, 2).unwrap();
577 g.add_edge(3, 4).unwrap();
578 g.add_edge(4, 5).unwrap();
579 let params = LglParams::default();
580 let pos = layout_lgl(&g, ¶ms).unwrap();
581 assert_eq!(pos.len(), 6);
582 for p in &pos {
583 assert!(p[0].is_finite() && p[1].is_finite());
584 }
585 }
586
587 #[test]
588 fn test_lgl_deterministic() {
589 let mut g = Graph::with_vertices(5);
590 g.add_edge(0, 1).unwrap();
591 g.add_edge(1, 2).unwrap();
592 g.add_edge(2, 3).unwrap();
593 g.add_edge(3, 4).unwrap();
594 g.add_edge(4, 0).unwrap();
595 let params = LglParams::default();
596 let pos1 = layout_lgl(&g, ¶ms).unwrap();
597 let pos2 = layout_lgl(&g, ¶ms).unwrap();
598 for i in 0..5 {
599 assert!((pos1[i][0] - pos2[i][0]).abs() < 1e-10);
600 assert!((pos1[i][1] - pos2[i][1]).abs() < 1e-10);
601 }
602 }
603
604 #[test]
605 fn test_lgl_custom_params() {
606 let mut g = Graph::with_vertices(5);
607 g.add_edge(0, 1).unwrap();
608 g.add_edge(1, 2).unwrap();
609 g.add_edge(2, 3).unwrap();
610 g.add_edge(3, 4).unwrap();
611 let params = LglParams {
612 maxit: 50,
613 coolexp: 2.0,
614 ..LglParams::default()
615 };
616 let pos = layout_lgl(&g, ¶ms).unwrap();
617 assert_eq!(pos.len(), 5);
618 for p in &pos {
619 assert!(p[0].is_finite() && p[1].is_finite());
620 }
621 }
622
623 #[test]
624 fn test_lgl_star_topology() {
625 let mut g = Graph::with_vertices(7);
626 for i in 1..7 {
627 g.add_edge(0, i).unwrap();
628 }
629 let params = LglParams {
630 root: Some(0),
631 ..LglParams::default()
632 };
633 let pos = layout_lgl(&g, ¶ms).unwrap();
634 assert_eq!(pos.len(), 7);
635 for p in &pos {
636 assert!(p[0].is_finite() && p[1].is_finite());
637 }
638 }
639}