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