1use crate::core::{Graph, IgraphError, IgraphResult};
12
13const GRID_SIZE: usize = 400;
14const RADIUS: i32 = 10;
15const HALF_VIEW: f32 = 800.0;
16const VIEW_TO_GRID: f32 = 0.25;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum DrlTemplate {
21 Default,
22 Coarsen,
23 Coarsest,
24 Refine,
25 Final,
26}
27
28#[derive(Debug, Clone, Copy)]
30struct StageParams {
31 iterations: u32,
32 temperature: f32,
33 attraction: f32,
34 damping_mult: f32,
35}
36
37#[derive(Debug, Clone)]
39pub struct DrlOptions {
40 pub edge_cut: f32,
41 pub init_iterations: u32,
42 pub init_temperature: f32,
43 pub init_attraction: f32,
44 pub init_damping_mult: f32,
45 liquid: StageParams,
46 expansion: StageParams,
47 cooldown: StageParams,
48 crunch: StageParams,
49 simmer: StageParams,
50}
51
52impl DrlOptions {
53 pub fn from_template(templ: DrlTemplate) -> Self {
55 let edge_cut: f32 = 32.0 / 40.0;
56 match templ {
57 DrlTemplate::Default => Self {
58 edge_cut,
59 init_iterations: 0,
60 init_temperature: 2000.0,
61 init_attraction: 10.0,
62 init_damping_mult: 1.0,
63 liquid: StageParams {
64 iterations: 200,
65 temperature: 2000.0,
66 attraction: 10.0,
67 damping_mult: 1.0,
68 },
69 expansion: StageParams {
70 iterations: 200,
71 temperature: 2000.0,
72 attraction: 2.0,
73 damping_mult: 1.0,
74 },
75 cooldown: StageParams {
76 iterations: 200,
77 temperature: 2000.0,
78 attraction: 1.0,
79 damping_mult: 0.1,
80 },
81 crunch: StageParams {
82 iterations: 50,
83 temperature: 250.0,
84 attraction: 1.0,
85 damping_mult: 0.25,
86 },
87 simmer: StageParams {
88 iterations: 100,
89 temperature: 250.0,
90 attraction: 0.5,
91 damping_mult: 0.0,
92 },
93 },
94 DrlTemplate::Coarsen => Self {
95 edge_cut,
96 init_iterations: 0,
97 init_temperature: 2000.0,
98 init_attraction: 10.0,
99 init_damping_mult: 1.0,
100 liquid: StageParams {
101 iterations: 200,
102 temperature: 2000.0,
103 attraction: 2.0,
104 damping_mult: 1.0,
105 },
106 expansion: StageParams {
107 iterations: 200,
108 temperature: 2000.0,
109 attraction: 10.0,
110 damping_mult: 1.0,
111 },
112 cooldown: StageParams {
113 iterations: 200,
114 temperature: 2000.0,
115 attraction: 1.0,
116 damping_mult: 0.1,
117 },
118 crunch: StageParams {
119 iterations: 50,
120 temperature: 250.0,
121 attraction: 1.0,
122 damping_mult: 0.25,
123 },
124 simmer: StageParams {
125 iterations: 100,
126 temperature: 250.0,
127 attraction: 0.5,
128 damping_mult: 0.0,
129 },
130 },
131 DrlTemplate::Coarsest => Self {
132 edge_cut,
133 init_iterations: 0,
134 init_temperature: 2000.0,
135 init_attraction: 10.0,
136 init_damping_mult: 1.0,
137 liquid: StageParams {
138 iterations: 200,
139 temperature: 2000.0,
140 attraction: 2.0,
141 damping_mult: 1.0,
142 },
143 expansion: StageParams {
144 iterations: 200,
145 temperature: 2000.0,
146 attraction: 10.0,
147 damping_mult: 1.0,
148 },
149 cooldown: StageParams {
150 iterations: 200,
151 temperature: 2000.0,
152 attraction: 1.0,
153 damping_mult: 0.1,
154 },
155 crunch: StageParams {
156 iterations: 200,
157 temperature: 250.0,
158 attraction: 1.0,
159 damping_mult: 0.25,
160 },
161 simmer: StageParams {
162 iterations: 100,
163 temperature: 250.0,
164 attraction: 0.5,
165 damping_mult: 0.0,
166 },
167 },
168 DrlTemplate::Refine => Self {
169 edge_cut,
170 init_iterations: 0,
171 init_temperature: 50.0,
172 init_attraction: 0.5,
173 init_damping_mult: 0.0,
174 liquid: StageParams {
175 iterations: 0,
176 temperature: 2000.0,
177 attraction: 2.0,
178 damping_mult: 1.0,
179 },
180 expansion: StageParams {
181 iterations: 50,
182 temperature: 500.0,
183 attraction: 0.1,
184 damping_mult: 0.25,
185 },
186 cooldown: StageParams {
187 iterations: 50,
188 temperature: 200.0,
189 attraction: 1.0,
190 damping_mult: 0.1,
191 },
192 crunch: StageParams {
193 iterations: 50,
194 temperature: 250.0,
195 attraction: 1.0,
196 damping_mult: 0.25,
197 },
198 simmer: StageParams {
199 iterations: 0,
200 temperature: 250.0,
201 attraction: 0.5,
202 damping_mult: 0.0,
203 },
204 },
205 DrlTemplate::Final => Self {
206 edge_cut,
207 init_iterations: 0,
208 init_temperature: 50.0,
209 init_attraction: 0.5,
210 init_damping_mult: 0.0,
211 liquid: StageParams {
212 iterations: 0,
213 temperature: 2000.0,
214 attraction: 2.0,
215 damping_mult: 1.0,
216 },
217 expansion: StageParams {
218 iterations: 50,
219 temperature: 50.0,
220 attraction: 0.1,
221 damping_mult: 0.25,
222 },
223 cooldown: StageParams {
224 iterations: 50,
225 temperature: 200.0,
226 attraction: 1.0,
227 damping_mult: 0.1,
228 },
229 crunch: StageParams {
230 iterations: 50,
231 temperature: 250.0,
232 attraction: 1.0,
233 damping_mult: 0.25,
234 },
235 simmer: StageParams {
236 iterations: 25,
237 temperature: 250.0,
238 attraction: 0.5,
239 damping_mult: 0.0,
240 },
241 },
242 }
243 }
244}
245
246impl Default for DrlOptions {
247 fn default() -> Self {
248 Self::from_template(DrlTemplate::Default)
249 }
250}
251
252pub fn layout_drl(
291 graph: &Graph,
292 seed: Option<&[[f64; 2]]>,
293 options: &DrlOptions,
294 weights: Option<&[f64]>,
295) -> IgraphResult<Vec<[f64; 2]>> {
296 let n = graph.vcount() as usize;
297 if n == 0 {
298 return Ok(Vec::new());
299 }
300 if n == 1 {
301 return Ok(vec![[0.0, 0.0]]);
302 }
303
304 if let Some(s) = seed {
305 if s.len() != n {
306 return Err(IgraphError::InvalidArgument(format!(
307 "seed length ({}) must equal vertex count ({})",
308 s.len(),
309 n
310 )));
311 }
312 }
313
314 if let Some(w) = weights {
315 let ne = graph.ecount();
316 if w.len() != ne {
317 return Err(IgraphError::InvalidArgument(format!(
318 "weights length ({}) must equal edge count ({})",
319 w.len(),
320 ne
321 )));
322 }
323 if ne > 0 {
324 for &wt in w {
325 if wt <= 0.0 || !wt.is_finite() {
326 return Err(IgraphError::InvalidArgument(
327 "weights must be positive and finite for DrL layout".into(),
328 ));
329 }
330 }
331 }
332 }
333
334 let mut neighbors: Vec<Vec<(usize, f32)>> = vec![Vec::new(); n];
336 for eid in 0..graph.ecount() as u32 {
337 if let Ok((src, tgt)) = graph.edge(eid) {
338 let w = weights.map_or(1.0_f32, |ws| ws[eid as usize] as f32);
339 neighbors[src as usize].push((tgt as usize, w));
340 if src != tgt {
341 neighbors[tgt as usize].push((src as usize, w));
342 }
343 }
344 }
345
346 let mut rng = SplitMix64::new(42);
348 let mut positions: Vec<[f32; 2]> = if let Some(s) = seed {
349 s.iter().map(|p| [p[0] as f32, p[1] as f32]).collect()
350 } else {
351 (0..n)
352 .map(|_| {
353 [
354 (rng.next_uniform() as f32 - 0.5) * 1000.0,
355 (rng.next_uniform() as f32 - 0.5) * 1000.0,
356 ]
357 })
358 .collect()
359 };
360
361 let mut density = DensityGrid::new();
363
364 let cut_length_end = (40000.0 * (1.0 - options.edge_cut)).max(1.0);
366 let cut_length_start = 4.0 * cut_length_end;
367 let cut_rate = (cut_length_start - cut_length_end) / 400.0;
368 let mut cut_off_length = cut_length_start;
369
370 let mut min_edges: f32 = 20.0;
371 let mut temperature;
372 let mut attraction;
373 let mut damping_mult;
374 let mut fine_density = false;
375 let mut first_add = true;
376 let mut fine_first_add = true;
377
378 let stages: [StageParams; 5] = [
380 options.liquid,
381 options.expansion,
382 options.cooldown,
383 options.crunch,
384 options.simmer,
385 ];
386
387 for (stage_idx, stage) in stages.iter().enumerate() {
388 temperature = stage.temperature;
389 attraction = stage.attraction;
390 damping_mult = stage.damping_mult;
391
392 if stage_idx == 4 {
393 fine_density = true;
394 min_edges = 99.0;
395 }
396
397 for iter in 0..stage.iterations {
398 match stage_idx {
400 1 => {
401 if attraction > 1.0 {
403 attraction -= 0.05;
404 }
405 if min_edges > 12.0 {
406 min_edges -= 0.05;
407 }
408 cut_off_length -= cut_rate;
409 if damping_mult > 0.1 {
410 damping_mult -= 0.005;
411 }
412 }
413 2 => {
414 if temperature > 50.0 {
416 temperature -= 10.0;
417 }
418 if cut_off_length > cut_length_end {
419 cut_off_length -= cut_rate * 2.0;
420 }
421 if min_edges > 1.0 {
422 min_edges -= 0.2;
423 }
424 }
425 4 if temperature > 50.0 => {
426 temperature -= 2.0;
427 }
428 _ => {}
429 }
430
431 update_all_nodes(
433 n,
434 &mut positions,
435 &neighbors,
436 &mut density,
437 temperature,
438 attraction,
439 damping_mult,
440 min_edges,
441 cut_off_length,
442 cut_length_end,
443 fine_density,
444 first_add,
445 fine_first_add,
446 stage_idx,
447 &mut rng,
448 );
449
450 first_add = false;
451 if fine_density && iter > 0 {
452 fine_first_add = false;
453 }
454 }
455
456 if stage_idx == 1 {
458 min_edges = 12.0;
459 } else if stage_idx == 2 {
460 cut_off_length = cut_length_end;
461 min_edges = 1.0;
462 }
463 }
464
465 Ok(positions
467 .iter()
468 .map(|p| [p[0] as f64, p[1] as f64])
469 .collect())
470}
471
472fn update_all_nodes(
473 n: usize,
474 positions: &mut [[f32; 2]],
475 neighbors: &[Vec<(usize, f32)>],
476 density: &mut DensityGrid,
477 temperature: f32,
478 attraction: f32,
479 damping_mult: f32,
480 min_edges: f32,
481 cut_off_length: f32,
482 cut_end: f32,
483 fine_density: bool,
484 first_add: bool,
485 fine_first_add: bool,
486 stage: usize,
487 rng: &mut SplitMix64,
488) {
489 let jump_length = 0.010 * temperature;
490 let attraction_factor = attraction * attraction * attraction * attraction * 2e-2;
491
492 for node_ind in 0..n {
493 density.subtract(positions[node_ind], first_add, fine_first_add, fine_density);
495
496 let (cx, cy) = solve_analytic(
498 node_ind,
499 positions,
500 neighbors,
501 damping_mult,
502 min_edges,
503 cut_off_length,
504 cut_end,
505 );
506 let old_pos = positions[node_ind];
507 positions[node_ind] = [cx, cy];
508 let energy_centroid = compute_energy(
509 node_ind,
510 positions,
511 neighbors,
512 density,
513 attraction_factor,
514 fine_density,
515 stage,
516 );
517
518 let rx = cx + (0.5 - rng.next_uniform() as f32) * jump_length;
520 let ry = cy + (0.5 - rng.next_uniform() as f32) * jump_length;
521 positions[node_ind] = [rx, ry];
522 let energy_random = compute_energy(
523 node_ind,
524 positions,
525 neighbors,
526 density,
527 attraction_factor,
528 fine_density,
529 stage,
530 );
531
532 positions[node_ind] = old_pos;
534 if (!fine_density && !first_add) || (!fine_first_add) {
535 density.add(positions[node_ind], fine_density);
536 }
537
538 let new_pos = if energy_centroid < energy_random {
540 [cx, cy]
541 } else {
542 [rx, ry]
543 };
544
545 density.subtract(positions[node_ind], false, false, fine_density);
547 positions[node_ind] = new_pos;
548 density.add(positions[node_ind], fine_density);
549 }
550}
551
552fn solve_analytic(
553 node_ind: usize,
554 positions: &[[f32; 2]],
555 neighbors: &[Vec<(usize, f32)>],
556 damping_mult: f32,
557 min_edges: f32,
558 cut_off_length: f32,
559 cut_end: f32,
560) -> (f32, f32) {
561 let mut total_weight: f32 = 0.0;
562 let mut x_sum: f32 = 0.0;
563 let mut y_sum: f32 = 0.0;
564
565 for &(neighbor, weight) in &neighbors[node_ind] {
566 total_weight += weight;
567 x_sum += weight * positions[neighbor][0];
568 y_sum += weight * positions[neighbor][1];
569 }
570
571 if total_weight <= 0.0 {
572 return (positions[node_ind][0], positions[node_ind][1]);
573 }
574
575 let x_cen = x_sum / total_weight;
576 let y_cen = y_sum / total_weight;
577 let damping = 1.0 - damping_mult;
578 let pos_x = damping * positions[node_ind][0] + (1.0 - damping) * x_cen;
579 let pos_y = damping * positions[node_ind][1] + (1.0 - damping) * y_cen;
580
581 if min_edges >= 99.0 || cut_end >= 39500.0 {
583 return (pos_x, pos_y);
584 }
585
586 let _ = cut_off_length;
590
591 (pos_x, pos_y)
592}
593
594fn compute_energy(
595 node_ind: usize,
596 positions: &[[f32; 2]],
597 neighbors: &[Vec<(usize, f32)>],
598 density: &DensityGrid,
599 attraction_factor: f32,
600 fine_density: bool,
601 stage: usize,
602) -> f32 {
603 let mut energy: f32 = 0.0;
604
605 for &(neighbor, weight) in &neighbors[node_ind] {
606 let dx = positions[node_ind][0] - positions[neighbor][0];
607 let dy = positions[node_ind][1] - positions[neighbor][1];
608 let mut dist = dx * dx + dy * dy;
609 if stage < 2 {
610 dist *= dist;
611 }
612 if stage == 0 {
613 dist *= dist;
614 }
615 energy += weight * attraction_factor * dist;
616 }
617
618 energy += density.get_density(positions[node_ind][0], positions[node_ind][1], fine_density);
619 energy
620}
621
622pub fn layout_drl_3d(
661 graph: &Graph,
662 seed: Option<&[[f64; 3]]>,
663 options: &DrlOptions,
664 weights: Option<&[f64]>,
665) -> IgraphResult<Vec<[f64; 3]>> {
666 let n = graph.vcount() as usize;
667 if n == 0 {
668 return Ok(Vec::new());
669 }
670 if n == 1 {
671 return Ok(vec![[0.0, 0.0, 0.0]]);
672 }
673
674 if let Some(s) = seed {
675 if s.len() != n {
676 return Err(IgraphError::InvalidArgument(format!(
677 "seed length ({}) must equal vertex count ({})",
678 s.len(),
679 n
680 )));
681 }
682 }
683
684 if let Some(w) = weights {
685 let ne = graph.ecount();
686 if w.len() != ne {
687 return Err(IgraphError::InvalidArgument(format!(
688 "weights length ({}) must equal edge count ({})",
689 w.len(),
690 ne
691 )));
692 }
693 if ne > 0 {
694 for &wt in w {
695 if wt <= 0.0 || !wt.is_finite() {
696 return Err(IgraphError::InvalidArgument(
697 "weights must be positive and finite for DrL layout".into(),
698 ));
699 }
700 }
701 }
702 }
703
704 let mut neighbors: Vec<Vec<(usize, f32)>> = vec![Vec::new(); n];
705 for eid in 0..graph.ecount() as u32 {
706 if let Ok((src, tgt)) = graph.edge(eid) {
707 let w = weights.map_or(1.0_f32, |ws| ws[eid as usize] as f32);
708 neighbors[src as usize].push((tgt as usize, w));
709 if src != tgt {
710 neighbors[tgt as usize].push((src as usize, w));
711 }
712 }
713 }
714
715 let mut rng = SplitMix64::new(42);
716 let mut positions: Vec<[f32; 3]> = if let Some(s) = seed {
717 s.iter()
718 .map(|p| [p[0] as f32, p[1] as f32, p[2] as f32])
719 .collect()
720 } else {
721 (0..n)
722 .map(|_| {
723 [
724 (rng.next_uniform() as f32 - 0.5) * 1000.0,
725 (rng.next_uniform() as f32 - 0.5) * 1000.0,
726 (rng.next_uniform() as f32 - 0.5) * 1000.0,
727 ]
728 })
729 .collect()
730 };
731
732 let mut density = DensityGrid3d::new();
733
734 let cut_length_end = (40000.0 * (1.0 - options.edge_cut)).max(1.0);
735 let cut_length_start = 4.0 * cut_length_end;
736 let cut_rate = (cut_length_start - cut_length_end) / 400.0;
737 let mut cut_off_length = cut_length_start;
738
739 let mut min_edges: f32 = 20.0;
740 let mut temperature;
741 let mut attraction;
742 let mut damping_mult;
743 let mut fine_density = false;
744 let mut first_add = true;
745 let mut fine_first_add = true;
746
747 let stages: [StageParams; 5] = [
748 options.liquid,
749 options.expansion,
750 options.cooldown,
751 options.crunch,
752 options.simmer,
753 ];
754
755 for (stage_idx, stage) in stages.iter().enumerate() {
756 temperature = stage.temperature;
757 attraction = stage.attraction;
758 damping_mult = stage.damping_mult;
759
760 if stage_idx == 4 {
761 fine_density = true;
762 min_edges = 99.0;
763 }
764
765 for iter in 0..stage.iterations {
766 match stage_idx {
767 1 => {
768 if attraction > 1.0 {
769 attraction -= 0.05;
770 }
771 if min_edges > 12.0 {
772 min_edges -= 0.05;
773 }
774 cut_off_length -= cut_rate;
775 if damping_mult > 0.1 {
776 damping_mult -= 0.005;
777 }
778 }
779 2 => {
780 if temperature > 50.0 {
781 temperature -= 10.0;
782 }
783 if cut_off_length > cut_length_end {
784 cut_off_length -= cut_rate * 2.0;
785 }
786 if min_edges > 1.0 {
787 min_edges -= 0.2;
788 }
789 }
790 4 if temperature > 50.0 => {
791 temperature -= 2.0;
792 }
793 _ => {}
794 }
795
796 update_all_nodes_3d(
797 n,
798 &mut positions,
799 &neighbors,
800 &mut density,
801 temperature,
802 attraction,
803 damping_mult,
804 min_edges,
805 cut_off_length,
806 cut_length_end,
807 fine_density,
808 first_add,
809 fine_first_add,
810 stage_idx,
811 &mut rng,
812 );
813
814 first_add = false;
815 if fine_density && iter > 0 {
816 fine_first_add = false;
817 }
818 }
819
820 if stage_idx == 1 {
821 min_edges = 12.0;
822 } else if stage_idx == 2 {
823 cut_off_length = cut_length_end;
824 min_edges = 1.0;
825 }
826 }
827
828 Ok(positions
829 .iter()
830 .map(|p| [p[0] as f64, p[1] as f64, p[2] as f64])
831 .collect())
832}
833
834#[allow(clippy::too_many_arguments)]
835fn update_all_nodes_3d(
836 n: usize,
837 positions: &mut [[f32; 3]],
838 neighbors: &[Vec<(usize, f32)>],
839 density: &mut DensityGrid3d,
840 temperature: f32,
841 attraction: f32,
842 damping_mult: f32,
843 min_edges: f32,
844 cut_off_length: f32,
845 cut_end: f32,
846 fine_density: bool,
847 first_add: bool,
848 fine_first_add: bool,
849 stage: usize,
850 rng: &mut SplitMix64,
851) {
852 let jump_length = 0.010 * temperature;
853 let attraction_factor = attraction * attraction * attraction * attraction * 2e-2;
854
855 for node_ind in 0..n {
856 density.subtract(positions[node_ind], first_add, fine_first_add, fine_density);
857
858 let (cx, cy, cz) = solve_analytic_3d(
859 node_ind,
860 positions,
861 neighbors,
862 damping_mult,
863 min_edges,
864 cut_off_length,
865 cut_end,
866 );
867 let old_pos = positions[node_ind];
868 positions[node_ind] = [cx, cy, cz];
869 let energy_centroid = compute_energy_3d(
870 node_ind,
871 positions,
872 neighbors,
873 density,
874 attraction_factor,
875 fine_density,
876 stage,
877 );
878
879 let rx = cx + (0.5 - rng.next_uniform() as f32) * jump_length;
880 let ry = cy + (0.5 - rng.next_uniform() as f32) * jump_length;
881 let rz = cz + (0.5 - rng.next_uniform() as f32) * jump_length;
882 positions[node_ind] = [rx, ry, rz];
883 let energy_random = compute_energy_3d(
884 node_ind,
885 positions,
886 neighbors,
887 density,
888 attraction_factor,
889 fine_density,
890 stage,
891 );
892
893 positions[node_ind] = old_pos;
894 if (!fine_density && !first_add) || (!fine_first_add) {
895 density.add(positions[node_ind], fine_density);
896 }
897
898 let new_pos = if energy_centroid < energy_random {
899 [cx, cy, cz]
900 } else {
901 [rx, ry, rz]
902 };
903
904 density.subtract(positions[node_ind], false, false, fine_density);
905 positions[node_ind] = new_pos;
906 density.add(positions[node_ind], fine_density);
907 }
908}
909
910fn solve_analytic_3d(
911 node_ind: usize,
912 positions: &[[f32; 3]],
913 neighbors: &[Vec<(usize, f32)>],
914 damping_mult: f32,
915 min_edges: f32,
916 cut_off_length: f32,
917 cut_end: f32,
918) -> (f32, f32, f32) {
919 let mut total_weight: f32 = 0.0;
920 let mut x_sum: f32 = 0.0;
921 let mut y_sum: f32 = 0.0;
922 let mut z_sum: f32 = 0.0;
923
924 for &(neighbor, weight) in &neighbors[node_ind] {
925 total_weight += weight;
926 x_sum += weight * positions[neighbor][0];
927 y_sum += weight * positions[neighbor][1];
928 z_sum += weight * positions[neighbor][2];
929 }
930
931 if total_weight <= 0.0 {
932 return (
933 positions[node_ind][0],
934 positions[node_ind][1],
935 positions[node_ind][2],
936 );
937 }
938
939 let x_cen = x_sum / total_weight;
940 let y_cen = y_sum / total_weight;
941 let z_cen = z_sum / total_weight;
942 let damping = 1.0 - damping_mult;
943 let pos_x = damping * positions[node_ind][0] + (1.0 - damping) * x_cen;
944 let pos_y = damping * positions[node_ind][1] + (1.0 - damping) * y_cen;
945 let pos_z = damping * positions[node_ind][2] + (1.0 - damping) * z_cen;
946
947 if min_edges >= 99.0 || cut_end >= 39500.0 {
948 return (pos_x, pos_y, pos_z);
949 }
950
951 let _ = cut_off_length;
952
953 (pos_x, pos_y, pos_z)
954}
955
956fn compute_energy_3d(
957 node_ind: usize,
958 positions: &[[f32; 3]],
959 neighbors: &[Vec<(usize, f32)>],
960 density: &DensityGrid3d,
961 attraction_factor: f32,
962 fine_density: bool,
963 stage: usize,
964) -> f32 {
965 let mut energy: f32 = 0.0;
966
967 for &(neighbor, weight) in &neighbors[node_ind] {
968 let dx = positions[node_ind][0] - positions[neighbor][0];
969 let dy = positions[node_ind][1] - positions[neighbor][1];
970 let dz = positions[node_ind][2] - positions[neighbor][2];
971 let mut dist = dx * dx + dy * dy + dz * dz;
972 if stage < 2 {
973 dist *= dist;
974 }
975 if stage == 0 {
976 dist *= dist;
977 }
978 energy += weight * attraction_factor * dist;
979 }
980
981 energy += density.get_density(
982 positions[node_ind][0],
983 positions[node_ind][1],
984 positions[node_ind][2],
985 fine_density,
986 );
987 energy
988}
989
990struct DensityGrid {
995 density: Vec<f32>,
996 fall_off: Vec<f32>,
997 bins: Vec<Vec<[f32; 2]>>,
998}
999
1000impl DensityGrid {
1001 fn new() -> Self {
1002 let mut fall_off = vec![0.0_f32; (RADIUS * 2 + 1) as usize * (RADIUS * 2 + 1) as usize];
1003 let diam = (RADIUS * 2 + 1) as usize;
1004 for i in 0..diam {
1005 for j in 0..diam {
1006 let fi = (RADIUS - (i as i32 - RADIUS).abs()) as f32 / RADIUS as f32;
1007 let fj = (RADIUS - (j as i32 - RADIUS).abs()) as f32 / RADIUS as f32;
1008 fall_off[i * diam + j] = fi * fj;
1009 }
1010 }
1011 Self {
1012 density: vec![0.0_f32; GRID_SIZE * GRID_SIZE],
1013 fall_off,
1014 bins: vec![Vec::new(); GRID_SIZE * GRID_SIZE],
1015 }
1016 }
1017
1018 fn pos_to_grid(x: f32, y: f32) -> (i32, i32) {
1019 let gx = ((x + HALF_VIEW + 0.5) * VIEW_TO_GRID) as i32;
1020 let gy = ((y + HALF_VIEW + 0.5) * VIEW_TO_GRID) as i32;
1021 (gx, gy)
1022 }
1023
1024 fn add(&mut self, pos: [f32; 2], fine_density: bool) {
1025 let (gx, gy) = Self::pos_to_grid(pos[0], pos[1]);
1026 if fine_density {
1027 if gx >= 0 && gx < GRID_SIZE as i32 && gy >= 0 && gy < GRID_SIZE as i32 {
1028 self.bins[gy as usize * GRID_SIZE + gx as usize].push(pos);
1029 }
1030 return;
1031 }
1032 let sx = gx - RADIUS;
1033 let sy = gy - RADIUS;
1034 if sx < 0 || sy < 0 || sx >= GRID_SIZE as i32 || sy >= GRID_SIZE as i32 {
1035 return;
1036 }
1037 let diam = (RADIUS * 2 + 1) as usize;
1038 for i in 0..diam {
1039 for j in 0..diam {
1040 let dy = sy as usize + i;
1041 let dx = sx as usize + j;
1042 if dy < GRID_SIZE && dx < GRID_SIZE {
1043 self.density[dy * GRID_SIZE + dx] += self.fall_off[i * diam + j];
1044 }
1045 }
1046 }
1047 }
1048
1049 fn subtract(
1050 &mut self,
1051 pos: [f32; 2],
1052 first_add: bool,
1053 fine_first_add: bool,
1054 fine_density: bool,
1055 ) {
1056 if fine_density && !fine_first_add {
1057 let (gx, gy) = Self::pos_to_grid(pos[0], pos[1]);
1058 if gx >= 0 && gx < GRID_SIZE as i32 && gy >= 0 && gy < GRID_SIZE as i32 {
1059 let idx = gy as usize * GRID_SIZE + gx as usize;
1060 if !self.bins[idx].is_empty() {
1061 self.bins[idx].remove(0);
1062 }
1063 }
1064 } else if !first_add {
1065 let (gx, gy) = Self::pos_to_grid(pos[0], pos[1]);
1066 let sx = gx - RADIUS;
1067 let sy = gy - RADIUS;
1068 if sx < 0 || sy < 0 || sx >= GRID_SIZE as i32 || sy >= GRID_SIZE as i32 {
1069 return;
1070 }
1071 let diam = (RADIUS * 2 + 1) as usize;
1072 for i in 0..diam {
1073 for j in 0..diam {
1074 let dy = sy as usize + i;
1075 let dx = sx as usize + j;
1076 if dy < GRID_SIZE && dx < GRID_SIZE {
1077 self.density[dy * GRID_SIZE + dx] -= self.fall_off[i * diam + j];
1078 }
1079 }
1080 }
1081 }
1082 }
1083
1084 fn get_density(&self, x: f32, y: f32, fine_density: bool) -> f32 {
1085 let (gx, gy) = Self::pos_to_grid(x, y);
1086 let boundary = 10_i32;
1087
1088 if gx >= GRID_SIZE as i32 - boundary
1089 || gx < boundary
1090 || gy >= GRID_SIZE as i32 - boundary
1091 || gy < boundary
1092 {
1093 return 10000.0;
1094 }
1095
1096 if fine_density {
1097 let mut d: f32 = 0.0;
1098 for i in (gy - 1)..=(gy + 1) {
1099 for j in (gx - 1)..=(gx + 1) {
1100 if i >= 0 && i < GRID_SIZE as i32 && j >= 0 && j < GRID_SIZE as i32 {
1101 let idx = i as usize * GRID_SIZE + j as usize;
1102 for p in &self.bins[idx] {
1103 let dx = x - p[0];
1104 let dy_val = y - p[1];
1105 let dist_sq = dx * dx + dy_val * dy_val;
1106 d += 1e-4 / (dist_sq + 1e-50);
1107 }
1108 }
1109 }
1110 }
1111 d
1112 } else {
1113 let val = self.density[gy as usize * GRID_SIZE + gx as usize];
1114 val * val
1115 }
1116 }
1117}
1118
1119const GRID_SIZE_3D: usize = 100;
1124const RADIUS_3D: i32 = 10;
1125const HALF_VIEW_3D: f32 = 125.0;
1126const VIEW_TO_GRID_3D: f32 = 0.4;
1127
1128struct DensityGrid3d {
1129 density: Vec<f32>,
1130 fall_off: Vec<f32>,
1131 bins: Vec<Vec<[f32; 3]>>,
1132}
1133
1134impl DensityGrid3d {
1135 fn new() -> Self {
1136 let diam = (RADIUS_3D * 2 + 1) as usize;
1137 let mut fall_off = vec![0.0_f32; diam * diam * diam];
1138 for i in 0..diam {
1139 for j in 0..diam {
1140 for k in 0..diam {
1141 let fi = (RADIUS_3D - (i as i32 - RADIUS_3D).abs()) as f32 / RADIUS_3D as f32;
1142 let fj = (RADIUS_3D - (j as i32 - RADIUS_3D).abs()) as f32 / RADIUS_3D as f32;
1143 let fk = (RADIUS_3D - (k as i32 - RADIUS_3D).abs()) as f32 / RADIUS_3D as f32;
1144 fall_off[(i * diam + j) * diam + k] = fi * fj * fk;
1145 }
1146 }
1147 }
1148 let total = GRID_SIZE_3D * GRID_SIZE_3D * GRID_SIZE_3D;
1149 Self {
1150 density: vec![0.0_f32; total],
1151 fall_off,
1152 bins: vec![Vec::new(); total],
1153 }
1154 }
1155
1156 fn pos_to_grid(x: f32, y: f32, z: f32) -> (i32, i32, i32) {
1157 let gx = ((x + HALF_VIEW_3D + 0.5) * VIEW_TO_GRID_3D) as i32;
1158 let gy = ((y + HALF_VIEW_3D + 0.5) * VIEW_TO_GRID_3D) as i32;
1159 let gz = ((z + HALF_VIEW_3D + 0.5) * VIEW_TO_GRID_3D) as i32;
1160 (gx, gy, gz)
1161 }
1162
1163 fn add(&mut self, pos: [f32; 3], fine_density: bool) {
1164 let (gx, gy, gz) = Self::pos_to_grid(pos[0], pos[1], pos[2]);
1165 if fine_density {
1166 if gx >= 0
1167 && gx < GRID_SIZE_3D as i32
1168 && gy >= 0
1169 && gy < GRID_SIZE_3D as i32
1170 && gz >= 0
1171 && gz < GRID_SIZE_3D as i32
1172 {
1173 let idx = (gz as usize * GRID_SIZE_3D + gy as usize) * GRID_SIZE_3D + gx as usize;
1174 self.bins[idx].push(pos);
1175 }
1176 return;
1177 }
1178 let sx = gx - RADIUS_3D;
1179 let sy = gy - RADIUS_3D;
1180 let sz = gz - RADIUS_3D;
1181 if sx < 0
1182 || sy < 0
1183 || sz < 0
1184 || sx >= GRID_SIZE_3D as i32
1185 || sy >= GRID_SIZE_3D as i32
1186 || sz >= GRID_SIZE_3D as i32
1187 {
1188 return;
1189 }
1190 let diam = (RADIUS_3D * 2 + 1) as usize;
1191 for i in 0..diam {
1192 for j in 0..diam {
1193 for k in 0..diam {
1194 let dz = sz as usize + i;
1195 let dy = sy as usize + j;
1196 let dx = sx as usize + k;
1197 if dz < GRID_SIZE_3D && dy < GRID_SIZE_3D && dx < GRID_SIZE_3D {
1198 let grid_idx = (dz * GRID_SIZE_3D + dy) * GRID_SIZE_3D + dx;
1199 self.density[grid_idx] += self.fall_off[(i * diam + j) * diam + k];
1200 }
1201 }
1202 }
1203 }
1204 }
1205
1206 fn subtract(
1207 &mut self,
1208 pos: [f32; 3],
1209 first_add: bool,
1210 fine_first_add: bool,
1211 fine_density: bool,
1212 ) {
1213 if fine_density && !fine_first_add {
1214 let (gx, gy, gz) = Self::pos_to_grid(pos[0], pos[1], pos[2]);
1215 if gx >= 0
1216 && gx < GRID_SIZE_3D as i32
1217 && gy >= 0
1218 && gy < GRID_SIZE_3D as i32
1219 && gz >= 0
1220 && gz < GRID_SIZE_3D as i32
1221 {
1222 let idx = (gz as usize * GRID_SIZE_3D + gy as usize) * GRID_SIZE_3D + gx as usize;
1223 if !self.bins[idx].is_empty() {
1224 self.bins[idx].remove(0);
1225 }
1226 }
1227 } else if !first_add {
1228 let (gx, gy, gz) = Self::pos_to_grid(pos[0], pos[1], pos[2]);
1229 let sx = gx - RADIUS_3D;
1230 let sy = gy - RADIUS_3D;
1231 let sz = gz - RADIUS_3D;
1232 if sx < 0
1233 || sy < 0
1234 || sz < 0
1235 || sx >= GRID_SIZE_3D as i32
1236 || sy >= GRID_SIZE_3D as i32
1237 || sz >= GRID_SIZE_3D as i32
1238 {
1239 return;
1240 }
1241 let diam = (RADIUS_3D * 2 + 1) as usize;
1242 for i in 0..diam {
1243 for j in 0..diam {
1244 for k in 0..diam {
1245 let dz = sz as usize + i;
1246 let dy = sy as usize + j;
1247 let dx = sx as usize + k;
1248 if dz < GRID_SIZE_3D && dy < GRID_SIZE_3D && dx < GRID_SIZE_3D {
1249 let grid_idx = (dz * GRID_SIZE_3D + dy) * GRID_SIZE_3D + dx;
1250 self.density[grid_idx] -= self.fall_off[(i * diam + j) * diam + k];
1251 }
1252 }
1253 }
1254 }
1255 }
1256 }
1257
1258 fn get_density(&self, x: f32, y: f32, z: f32, fine_density: bool) -> f32 {
1259 let (gx, gy, gz) = Self::pos_to_grid(x, y, z);
1260 let boundary = 10_i32;
1261
1262 if gx >= GRID_SIZE_3D as i32 - boundary
1263 || gx < boundary
1264 || gy >= GRID_SIZE_3D as i32 - boundary
1265 || gy < boundary
1266 || gz >= GRID_SIZE_3D as i32 - boundary
1267 || gz < boundary
1268 {
1269 return 10000.0;
1270 }
1271
1272 if fine_density {
1273 let mut d: f32 = 0.0;
1274 for iz in (gz - 1)..=(gz + 1) {
1275 for iy in (gy - 1)..=(gy + 1) {
1276 for ix in (gx - 1)..=(gx + 1) {
1277 if iz >= 0
1278 && iz < GRID_SIZE_3D as i32
1279 && iy >= 0
1280 && iy < GRID_SIZE_3D as i32
1281 && ix >= 0
1282 && ix < GRID_SIZE_3D as i32
1283 {
1284 let idx = (iz as usize * GRID_SIZE_3D + iy as usize) * GRID_SIZE_3D
1285 + ix as usize;
1286 for p in &self.bins[idx] {
1287 let dx = x - p[0];
1288 let dy_val = y - p[1];
1289 let dz_val = z - p[2];
1290 let dist_sq = dx * dx + dy_val * dy_val + dz_val * dz_val;
1291 d += 1e-4 / (dist_sq + 1e-50);
1292 }
1293 }
1294 }
1295 }
1296 }
1297 d
1298 } else {
1299 let idx = (gz as usize * GRID_SIZE_3D + gy as usize) * GRID_SIZE_3D + gx as usize;
1300 let val = self.density[idx];
1301 val * val
1302 }
1303 }
1304}
1305
1306struct SplitMix64 {
1311 state: u64,
1312}
1313
1314impl SplitMix64 {
1315 fn new(seed: u64) -> Self {
1316 Self { state: seed }
1317 }
1318
1319 fn next_u64(&mut self) -> u64 {
1320 self.state = self.state.wrapping_add(0x9E37_79B9_7F4A_7C15);
1321 let mut z = self.state;
1322 z = (z ^ (z >> 30)).wrapping_mul(0xBF58_476D_1CE4_E5B9);
1323 z = (z ^ (z >> 27)).wrapping_mul(0x94D0_49BB_1331_11EB);
1324 z ^ (z >> 31)
1325 }
1326
1327 fn next_uniform(&mut self) -> f64 {
1328 (self.next_u64() >> 11) as f64 / ((1u64 << 53) as f64)
1329 }
1330}
1331
1332#[cfg(test)]
1337mod tests {
1338 use super::*;
1339
1340 #[test]
1341 fn test_drl_empty() {
1342 let g = Graph::with_vertices(0);
1343 let opts = DrlOptions::default();
1344 let pos = layout_drl(&g, None, &opts, None).unwrap();
1345 assert!(pos.is_empty());
1346 }
1347
1348 #[test]
1349 fn test_drl_single() {
1350 let g = Graph::with_vertices(1);
1351 let opts = DrlOptions::default();
1352 let pos = layout_drl(&g, None, &opts, None).unwrap();
1353 assert_eq!(pos.len(), 1);
1354 assert!(pos[0][0].abs() < 1e-10 && pos[0][1].abs() < 1e-10);
1355 }
1356
1357 #[test]
1358 fn test_drl_path() {
1359 let mut g = Graph::with_vertices(5);
1360 for i in 0..4 {
1361 g.add_edge(i, i + 1).unwrap();
1362 }
1363 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1364 let pos = layout_drl(&g, None, &opts, None).unwrap();
1365 assert_eq!(pos.len(), 5);
1366 for p in &pos {
1367 assert!(p[0].is_finite() && p[1].is_finite());
1368 }
1369 }
1370
1371 #[test]
1372 fn test_drl_cycle() {
1373 let mut g = Graph::with_vertices(6);
1374 for i in 0..6 {
1375 g.add_edge(i, (i + 1) % 6).unwrap();
1376 }
1377 let opts = DrlOptions::from_template(DrlTemplate::Final);
1378 let pos = layout_drl(&g, None, &opts, None).unwrap();
1379 assert_eq!(pos.len(), 6);
1380 for p in &pos {
1381 assert!(p[0].is_finite() && p[1].is_finite());
1382 }
1383 }
1384
1385 #[test]
1386 fn test_drl_complete() {
1387 let mut g = Graph::with_vertices(4);
1388 for i in 0..4u32 {
1389 for j in (i + 1)..4 {
1390 g.add_edge(i, j).unwrap();
1391 }
1392 }
1393 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1394 let pos = layout_drl(&g, None, &opts, None).unwrap();
1395 assert_eq!(pos.len(), 4);
1396 for p in &pos {
1397 assert!(p[0].is_finite() && p[1].is_finite());
1398 }
1399 }
1400
1401 #[test]
1402 fn test_drl_with_seed() {
1403 let mut g = Graph::with_vertices(3);
1404 g.add_edge(0, 1).unwrap();
1405 g.add_edge(1, 2).unwrap();
1406 let seed = vec![[0.0, 0.0], [100.0, 0.0], [50.0, 86.6]];
1407 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1408 let pos = layout_drl(&g, Some(&seed), &opts, None).unwrap();
1409 assert_eq!(pos.len(), 3);
1410 for p in &pos {
1411 assert!(p[0].is_finite() && p[1].is_finite());
1412 }
1413 }
1414
1415 #[test]
1416 fn test_drl_seed_wrong_length() {
1417 let g = Graph::with_vertices(3);
1418 let seed = vec![[0.0, 0.0]];
1419 let opts = DrlOptions::default();
1420 assert!(layout_drl(&g, Some(&seed), &opts, None).is_err());
1421 }
1422
1423 #[test]
1424 fn test_drl_weighted() {
1425 let mut g = Graph::with_vertices(4);
1426 g.add_edge(0, 1).unwrap();
1427 g.add_edge(1, 2).unwrap();
1428 g.add_edge(2, 3).unwrap();
1429 let weights = vec![1.0, 2.0, 0.5];
1430 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1431 let pos = layout_drl(&g, None, &opts, Some(&weights)).unwrap();
1432 assert_eq!(pos.len(), 4);
1433 for p in &pos {
1434 assert!(p[0].is_finite() && p[1].is_finite());
1435 }
1436 }
1437
1438 #[test]
1439 fn test_drl_bad_weights() {
1440 let mut g = Graph::with_vertices(3);
1441 g.add_edge(0, 1).unwrap();
1442 g.add_edge(1, 2).unwrap();
1443 let opts = DrlOptions::default();
1444 let bad_w = vec![1.0, -1.0];
1445 assert!(layout_drl(&g, None, &opts, Some(&bad_w)).is_err());
1446 }
1447
1448 #[test]
1449 fn test_drl_deterministic() {
1450 let mut g = Graph::with_vertices(5);
1451 g.add_edge(0, 1).unwrap();
1452 g.add_edge(1, 2).unwrap();
1453 g.add_edge(2, 3).unwrap();
1454 g.add_edge(3, 4).unwrap();
1455 g.add_edge(4, 0).unwrap();
1456 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1457 let pos1 = layout_drl(&g, None, &opts, None).unwrap();
1458 let pos2 = layout_drl(&g, None, &opts, None).unwrap();
1459 for i in 0..5 {
1460 assert!((pos1[i][0] - pos2[i][0]).abs() < 1e-6);
1461 assert!((pos1[i][1] - pos2[i][1]).abs() < 1e-6);
1462 }
1463 }
1464
1465 #[test]
1466 fn test_drl_all_templates() {
1467 let mut g = Graph::with_vertices(4);
1468 g.add_edge(0, 1).unwrap();
1469 g.add_edge(1, 2).unwrap();
1470 g.add_edge(2, 3).unwrap();
1471 g.add_edge(3, 0).unwrap();
1472
1473 for templ in [
1474 DrlTemplate::Default,
1475 DrlTemplate::Coarsen,
1476 DrlTemplate::Coarsest,
1477 DrlTemplate::Refine,
1478 DrlTemplate::Final,
1479 ] {
1480 let opts = DrlOptions::from_template(templ);
1481 let pos = layout_drl(&g, None, &opts, None).unwrap();
1482 assert_eq!(pos.len(), 4);
1483 for p in &pos {
1484 assert!(
1485 p[0].is_finite() && p[1].is_finite(),
1486 "Non-finite position with template {templ:?}"
1487 );
1488 }
1489 }
1490 }
1491
1492 #[test]
1493 fn test_drl_disconnected() {
1494 let mut g = Graph::with_vertices(6);
1495 g.add_edge(0, 1).unwrap();
1496 g.add_edge(1, 2).unwrap();
1497 g.add_edge(3, 4).unwrap();
1498 g.add_edge(4, 5).unwrap();
1499 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1500 let pos = layout_drl(&g, None, &opts, None).unwrap();
1501 assert_eq!(pos.len(), 6);
1502 for p in &pos {
1503 assert!(p[0].is_finite() && p[1].is_finite());
1504 }
1505 }
1506
1507 #[test]
1510 fn test_drl_3d_empty() {
1511 let g = Graph::with_vertices(0);
1512 let opts = DrlOptions::default();
1513 let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
1514 assert!(pos.is_empty());
1515 }
1516
1517 #[test]
1518 fn test_drl_3d_single() {
1519 let g = Graph::with_vertices(1);
1520 let opts = DrlOptions::default();
1521 let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
1522 assert_eq!(pos.len(), 1);
1523 assert!(pos[0][0].abs() < 1e-10 && pos[0][1].abs() < 1e-10 && pos[0][2].abs() < 1e-10);
1524 }
1525
1526 #[test]
1527 fn test_drl_3d_path() {
1528 let mut g = Graph::with_vertices(5);
1529 for i in 0..4 {
1530 g.add_edge(i, i + 1).unwrap();
1531 }
1532 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1533 let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
1534 assert_eq!(pos.len(), 5);
1535 for p in &pos {
1536 assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
1537 }
1538 }
1539
1540 #[test]
1541 fn test_drl_3d_cycle() {
1542 let mut g = Graph::with_vertices(6);
1543 for i in 0..6 {
1544 g.add_edge(i, (i + 1) % 6).unwrap();
1545 }
1546 let opts = DrlOptions::from_template(DrlTemplate::Final);
1547 let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
1548 assert_eq!(pos.len(), 6);
1549 for p in &pos {
1550 assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
1551 }
1552 }
1553
1554 #[test]
1555 fn test_drl_3d_with_seed() {
1556 let mut g = Graph::with_vertices(3);
1557 g.add_edge(0, 1).unwrap();
1558 g.add_edge(1, 2).unwrap();
1559 let seed = vec![[0.0, 0.0, 0.0], [100.0, 0.0, 0.0], [50.0, 86.6, 10.0]];
1560 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1561 let pos = layout_drl_3d(&g, Some(&seed), &opts, None).unwrap();
1562 assert_eq!(pos.len(), 3);
1563 for p in &pos {
1564 assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
1565 }
1566 }
1567
1568 #[test]
1569 fn test_drl_3d_seed_wrong_length() {
1570 let g = Graph::with_vertices(3);
1571 let seed = vec![[0.0, 0.0, 0.0]];
1572 let opts = DrlOptions::default();
1573 assert!(layout_drl_3d(&g, Some(&seed), &opts, None).is_err());
1574 }
1575
1576 #[test]
1577 fn test_drl_3d_weighted() {
1578 let mut g = Graph::with_vertices(4);
1579 g.add_edge(0, 1).unwrap();
1580 g.add_edge(1, 2).unwrap();
1581 g.add_edge(2, 3).unwrap();
1582 let weights = vec![1.0, 2.0, 0.5];
1583 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1584 let pos = layout_drl_3d(&g, None, &opts, Some(&weights)).unwrap();
1585 assert_eq!(pos.len(), 4);
1586 for p in &pos {
1587 assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
1588 }
1589 }
1590
1591 #[test]
1592 fn test_drl_3d_bad_weights() {
1593 let mut g = Graph::with_vertices(3);
1594 g.add_edge(0, 1).unwrap();
1595 g.add_edge(1, 2).unwrap();
1596 let opts = DrlOptions::default();
1597 let bad_w = vec![1.0, -1.0];
1598 assert!(layout_drl_3d(&g, None, &opts, Some(&bad_w)).is_err());
1599 }
1600
1601 #[test]
1602 fn test_drl_3d_deterministic() {
1603 let mut g = Graph::with_vertices(5);
1604 g.add_edge(0, 1).unwrap();
1605 g.add_edge(1, 2).unwrap();
1606 g.add_edge(2, 3).unwrap();
1607 g.add_edge(3, 4).unwrap();
1608 g.add_edge(4, 0).unwrap();
1609 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1610 let pos1 = layout_drl_3d(&g, None, &opts, None).unwrap();
1611 let pos2 = layout_drl_3d(&g, None, &opts, None).unwrap();
1612 for i in 0..5 {
1613 assert!((pos1[i][0] - pos2[i][0]).abs() < 1e-6);
1614 assert!((pos1[i][1] - pos2[i][1]).abs() < 1e-6);
1615 assert!((pos1[i][2] - pos2[i][2]).abs() < 1e-6);
1616 }
1617 }
1618
1619 #[test]
1620 fn test_drl_3d_all_templates() {
1621 let mut g = Graph::with_vertices(4);
1622 g.add_edge(0, 1).unwrap();
1623 g.add_edge(1, 2).unwrap();
1624 g.add_edge(2, 3).unwrap();
1625 g.add_edge(3, 0).unwrap();
1626
1627 for templ in [
1628 DrlTemplate::Default,
1629 DrlTemplate::Coarsen,
1630 DrlTemplate::Coarsest,
1631 DrlTemplate::Refine,
1632 DrlTemplate::Final,
1633 ] {
1634 let opts = DrlOptions::from_template(templ);
1635 let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
1636 assert_eq!(pos.len(), 4);
1637 for p in &pos {
1638 assert!(
1639 p[0].is_finite() && p[1].is_finite() && p[2].is_finite(),
1640 "Non-finite position with template {templ:?}"
1641 );
1642 }
1643 }
1644 }
1645
1646 #[test]
1647 fn test_drl_3d_disconnected() {
1648 let mut g = Graph::with_vertices(6);
1649 g.add_edge(0, 1).unwrap();
1650 g.add_edge(1, 2).unwrap();
1651 g.add_edge(3, 4).unwrap();
1652 g.add_edge(4, 5).unwrap();
1653 let opts = DrlOptions::from_template(DrlTemplate::Refine);
1654 let pos = layout_drl_3d(&g, None, &opts, None).unwrap();
1655 assert_eq!(pos.len(), 6);
1656 for p in &pos {
1657 assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
1658 }
1659 }
1660}