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