1use ndarray::{Array1, Array2};
21use num_complex::Complex64;
22
23use crate::core::integration::{regular_integration, singular_integration, unit_sphere_quadrature};
24use crate::core::types::{Cluster, ClusterLevel, Element, PhysicsParams};
25use math_audio_wave::special::spherical_hankel_first_kind;
26
27pub struct MlfmmSystem {
29 pub num_levels: usize,
31 pub near_matrix: Vec<NearFieldBlock>,
33 pub t_matrices: Vec<LevelMatrices>,
36 pub d_matrices: Vec<Vec<DMatrixEntry>>,
38 pub s_matrices: Vec<LevelMatrices>,
40 pub rhs: Array1<Complex64>,
42 pub num_dofs: usize,
44 cluster_levels: Vec<ClusterLevel>,
46 pub leaf_dof_indices: Vec<Vec<usize>>,
48 pub sphere_points_per_level: Vec<usize>,
50}
51
52#[derive(Debug, Clone)]
54pub struct NearFieldBlock {
55 pub source_cluster: usize,
57 pub field_cluster: usize,
59 pub coefficients: Array2<Complex64>,
61}
62
63#[derive(Debug, Clone)]
68pub struct DMatrixEntry {
69 pub source_cluster: usize,
71 pub field_cluster: usize,
73 pub level: usize,
75 pub diagonal: Array1<Complex64>,
77}
78
79#[derive(Debug, Clone)]
81pub struct LevelMatrices {
82 pub level: usize,
84 pub num_clusters: usize,
86 pub matrices: Vec<Array2<Complex64>>,
88}
89
90impl MlfmmSystem {
91 pub fn new(num_dofs: usize, num_levels: usize, cluster_levels: Vec<ClusterLevel>) -> Self {
93 let sphere_points_per_level: Vec<usize> = cluster_levels
94 .iter()
95 .map(|level| level.theta_points * level.phi_points)
96 .collect();
97
98 Self {
99 num_levels,
100 near_matrix: Vec::new(),
101 t_matrices: Vec::with_capacity(num_levels),
102 d_matrices: Vec::with_capacity(num_levels),
103 s_matrices: Vec::with_capacity(num_levels),
104 rhs: Array1::zeros(num_dofs),
105 num_dofs,
106 cluster_levels,
107 leaf_dof_indices: Vec::new(),
108 sphere_points_per_level,
109 }
110 }
111
112 pub fn matvec(&self, x: &Array1<Complex64>) -> Array1<Complex64> {
129 let mut y = Array1::zeros(self.num_dofs);
130
131 if self.num_levels == 0 {
132 return y;
133 }
134
135 let leaf_level = self.num_levels - 1;
136
137 for block in &self.near_matrix {
143 if block.source_cluster >= self.leaf_dof_indices.len()
144 || block.field_cluster >= self.leaf_dof_indices.len()
145 {
146 continue;
147 }
148
149 let src_dofs = &self.leaf_dof_indices[block.source_cluster];
150 let fld_dofs = &self.leaf_dof_indices[block.field_cluster];
151
152 let x_local: Array1<Complex64> = Array1::from_iter(fld_dofs.iter().map(|&i| x[i]));
154
155 let y_local = block.coefficients.dot(&x_local);
157
158 for (local_i, &global_i) in src_dofs.iter().enumerate() {
160 if local_i < y_local.len() {
161 y[global_i] += y_local[local_i];
162 }
163 }
164
165 if block.source_cluster != block.field_cluster {
168 let x_src: Array1<Complex64> = Array1::from_iter(src_dofs.iter().map(|&i| x[i]));
170 let y_fld = block.coefficients.t().dot(&x_src);
172 for (local_j, &global_j) in fld_dofs.iter().enumerate() {
174 if local_j < y_fld.len() {
175 y[global_j] += y_fld[local_j];
176 }
177 }
178 }
179 }
180
181 if self.num_levels > 1 && !self.t_matrices.is_empty() {
183 let multipoles = self.upward_pass(x);
185
186 let mut locals = self.translate_all_levels(&multipoles);
188
189 self.downward_pass(&mut locals);
191
192 self.evaluate_locals(&locals, leaf_level, &mut y);
194 }
195
196 y
197 }
198
199 fn upward_pass(&self, x: &Array1<Complex64>) -> Vec<Vec<Array1<Complex64>>> {
204 let mut multipoles: Vec<Vec<Array1<Complex64>>> = Vec::with_capacity(self.num_levels);
205
206 for level in 0..self.num_levels {
208 let num_clusters = if level < self.cluster_levels.len() {
209 self.cluster_levels[level].clusters.len()
210 } else {
211 0
212 };
213 let num_points = if level < self.sphere_points_per_level.len() {
214 self.sphere_points_per_level[level]
215 } else {
216 0
217 };
218 multipoles.push(
219 (0..num_clusters)
220 .map(|_| Array1::zeros(num_points))
221 .collect(),
222 );
223 }
224
225 let leaf_level = self.num_levels - 1;
226
227 if leaf_level < self.t_matrices.len() {
229 let t_level = &self.t_matrices[leaf_level];
230 for cluster_idx in 0..t_level.num_clusters {
231 if cluster_idx >= self.leaf_dof_indices.len() {
232 continue;
233 }
234 let cluster_dofs = &self.leaf_dof_indices[cluster_idx];
235 if cluster_dofs.is_empty() {
236 continue;
237 }
238 let t_mat = &t_level.matrices[cluster_idx];
239 if t_mat.is_empty() {
240 continue;
241 }
242 let x_local: Array1<Complex64> =
243 Array1::from_iter(cluster_dofs.iter().map(|&i| x[i]));
244 if x_local.len() == t_mat.ncols() {
245 multipoles[leaf_level][cluster_idx] = t_mat.dot(&x_local);
246 }
247 }
248 }
249
250 for level in (0..leaf_level).rev() {
253 if level >= self.t_matrices.len() || level >= self.cluster_levels.len() {
254 continue;
255 }
256
257 let t_level = &self.t_matrices[level];
258 let clusters = &self.cluster_levels[level].clusters;
259
260 for (cluster_idx, cluster) in clusters.iter().enumerate() {
261 if cluster_idx >= t_level.matrices.len() {
262 continue;
263 }
264
265 let t_mat = &t_level.matrices[cluster_idx];
267 if t_mat.is_empty() || cluster.sons.is_empty() {
268 continue;
269 }
270
271 let child_level = level + 1;
273 if child_level >= multipoles.len() {
274 continue;
275 }
276
277 let child_num_points = self
278 .sphere_points_per_level
279 .get(child_level)
280 .copied()
281 .unwrap_or(0);
282 let mut child_multipoles = Array1::zeros(cluster.sons.len() * child_num_points);
283
284 for (son_idx, &child_cluster_idx) in cluster.sons.iter().enumerate() {
285 if child_cluster_idx < multipoles[child_level].len() {
286 let child_mult = &multipoles[child_level][child_cluster_idx];
287 let offset = son_idx * child_num_points;
288 for (i, &val) in child_mult.iter().enumerate() {
289 if offset + i < child_multipoles.len() {
290 child_multipoles[offset + i] = val;
291 }
292 }
293 }
294 }
295
296 if child_multipoles.len() == t_mat.ncols() {
297 multipoles[level][cluster_idx] = t_mat.dot(&child_multipoles);
298 }
299 }
300 }
301
302 multipoles
303 }
304
305 fn translate_all_levels(
307 &self,
308 multipoles: &[Vec<Array1<Complex64>>],
309 ) -> Vec<Vec<Array1<Complex64>>> {
310 let mut locals: Vec<Vec<Array1<Complex64>>> = Vec::with_capacity(self.num_levels);
311
312 for level in 0..self.num_levels {
314 let num_clusters = if level < self.cluster_levels.len() {
315 self.cluster_levels[level].clusters.len()
316 } else {
317 0
318 };
319 let num_points = self
320 .sphere_points_per_level
321 .get(level)
322 .copied()
323 .unwrap_or(0);
324 locals.push(
325 (0..num_clusters)
326 .map(|_| Array1::zeros(num_points))
327 .collect(),
328 );
329 }
330
331 for (level, d_entries) in self.d_matrices.iter().enumerate() {
333 if level >= multipoles.len() || level >= locals.len() {
334 continue;
335 }
336
337 for d_entry in d_entries {
338 if d_entry.source_cluster >= multipoles[level].len()
339 || d_entry.field_cluster >= locals[level].len()
340 {
341 continue;
342 }
343
344 let src_mult = &multipoles[level][d_entry.source_cluster];
345 if src_mult.len() != d_entry.diagonal.len() {
346 continue;
347 }
348
349 for (i, (&d, &s)) in d_entry.diagonal.iter().zip(src_mult.iter()).enumerate() {
351 if i < locals[level][d_entry.field_cluster].len() {
352 locals[level][d_entry.field_cluster][i] += d * s;
353 }
354 }
355 }
356 }
357
358 locals
359 }
360
361 fn downward_pass(&self, locals: &mut [Vec<Array1<Complex64>>]) {
363 for level in 0..self.num_levels.saturating_sub(1) {
365 if level >= self.s_matrices.len() || level >= self.cluster_levels.len() {
366 continue;
367 }
368
369 let s_level = &self.s_matrices[level];
370 let clusters = &self.cluster_levels[level].clusters;
371
372 for (cluster_idx, cluster) in clusters.iter().enumerate() {
373 if cluster_idx >= s_level.matrices.len() || cluster.sons.is_empty() {
374 continue;
375 }
376
377 let s_mat = &s_level.matrices[cluster_idx];
378 if s_mat.is_empty() {
379 continue;
380 }
381
382 let parent_local = &locals[level][cluster_idx];
384 if parent_local.len() != s_mat.ncols() {
385 continue;
386 }
387
388 let child_locals = s_mat.dot(parent_local);
389 let child_level = level + 1;
390 if child_level >= locals.len() {
391 continue;
392 }
393
394 let child_num_points = self
395 .sphere_points_per_level
396 .get(child_level)
397 .copied()
398 .unwrap_or(0);
399
400 for (son_idx, &child_cluster_idx) in cluster.sons.iter().enumerate() {
402 if child_cluster_idx >= locals[child_level].len() {
403 continue;
404 }
405
406 let offset = son_idx * child_num_points;
407 for i in 0..child_num_points {
408 if offset + i < child_locals.len()
409 && i < locals[child_level][child_cluster_idx].len()
410 {
411 locals[child_level][child_cluster_idx][i] += child_locals[offset + i];
412 }
413 }
414 }
415 }
416 }
417 }
418
419 fn evaluate_locals(
421 &self,
422 locals: &[Vec<Array1<Complex64>>],
423 leaf_level: usize,
424 y: &mut Array1<Complex64>,
425 ) {
426 if leaf_level >= self.s_matrices.len() || leaf_level >= locals.len() {
427 return;
428 }
429
430 let s_level = &self.s_matrices[leaf_level];
431
432 for cluster_idx in 0..s_level.num_clusters {
433 if cluster_idx >= self.leaf_dof_indices.len() || cluster_idx >= locals[leaf_level].len()
434 {
435 continue;
436 }
437
438 let cluster_dofs = &self.leaf_dof_indices[cluster_idx];
439 if cluster_dofs.is_empty() {
440 continue;
441 }
442
443 let s_mat = &s_level.matrices[cluster_idx];
444 if s_mat.is_empty() {
445 continue;
446 }
447
448 let local_exp = &locals[leaf_level][cluster_idx];
449 if local_exp.len() != s_mat.ncols() {
450 continue;
451 }
452
453 let y_local = s_mat.dot(local_exp);
454 for (local_j, &global_j) in cluster_dofs.iter().enumerate() {
455 if local_j < y_local.len() && global_j < y.len() {
456 y[global_j] += y_local[local_j];
457 }
458 }
459 }
460 }
461
462 pub fn get_cluster(&self, level: usize, index: usize) -> Option<&Cluster> {
464 self.cluster_levels.get(level)?.clusters.get(index)
465 }
466
467 pub fn num_clusters_at_level(&self, level: usize) -> usize {
469 self.cluster_levels
470 .get(level)
471 .map(|l| l.clusters.len())
472 .unwrap_or(0)
473 }
474}
475
476pub fn build_mlfmm_system(
484 elements: &[Element],
485 nodes: &Array2<f64>,
486 cluster_levels: Vec<ClusterLevel>,
487 physics: &PhysicsParams,
488) -> MlfmmSystem {
489 let num_dofs = count_dofs(elements);
490 let num_levels = cluster_levels.len();
491
492 let mut system = MlfmmSystem::new(num_dofs, num_levels, cluster_levels.clone());
493
494 if let Some(leaf_level) = cluster_levels.last() {
496 build_leaf_dof_mappings(&mut system, elements, &leaf_level.clusters);
497 }
498
499 if let Some(leaf_level) = cluster_levels.last() {
501 build_near_field(&mut system, elements, nodes, &leaf_level.clusters, physics);
502 }
503
504 for (level_idx, level) in cluster_levels.iter().enumerate().rev() {
506 let (sphere_coords, sphere_weights) =
507 unit_sphere_quadrature(level.theta_points, level.phi_points);
508
509 let t_matrices = build_t_matrices_level(
510 elements,
511 &level.clusters,
512 physics,
513 &sphere_coords,
514 &sphere_weights,
515 level_idx,
516 &cluster_levels,
517 );
518
519 system.t_matrices.push(t_matrices);
520 }
521 system.t_matrices.reverse(); for (level_idx, level) in cluster_levels.iter().enumerate() {
525 let num_sphere_points = level.theta_points * level.phi_points;
526
527 let d_matrices = build_d_matrices_level(
528 &level.clusters,
529 physics,
530 &[], level.expansion_terms,
532 level_idx,
533 num_sphere_points,
534 );
535
536 system.d_matrices.push(d_matrices);
537 }
538
539 for (level_idx, level) in cluster_levels.iter().enumerate() {
541 let (sphere_coords, sphere_weights) =
542 unit_sphere_quadrature(level.theta_points, level.phi_points);
543
544 let s_matrices = build_s_matrices_level(
545 elements,
546 &level.clusters,
547 physics,
548 &sphere_coords,
549 &sphere_weights,
550 level_idx,
551 &cluster_levels,
552 );
553
554 system.s_matrices.push(s_matrices);
555 }
556
557 system
558}
559
560fn build_leaf_dof_mappings(
562 system: &mut MlfmmSystem,
563 elements: &[Element],
564 leaf_clusters: &[Cluster],
565) {
566 for cluster in leaf_clusters {
567 let mut dof_indices = Vec::new();
568 for &elem_idx in &cluster.element_indices {
569 let elem = &elements[elem_idx];
570 if elem.property.is_evaluation() {
571 continue;
572 }
573 dof_indices.extend(elem.dof_addresses.iter().copied());
575 }
576 system.leaf_dof_indices.push(dof_indices);
577 }
578}
579
580fn count_dofs(elements: &[Element]) -> usize {
582 elements
583 .iter()
584 .filter(|e| !e.property.is_evaluation())
585 .map(|e| e.dof_addresses.len())
586 .sum()
587}
588
589fn build_near_field(
591 system: &mut MlfmmSystem,
592 elements: &[Element],
593 nodes: &Array2<f64>,
594 leaf_clusters: &[Cluster],
595 physics: &PhysicsParams,
596) {
597 let gamma = Complex64::new(physics.gamma(), 0.0);
598 let tau = Complex64::new(physics.tau, 0.0);
599 let beta = physics.burton_miller_beta();
600
601 for (i, cluster_i) in leaf_clusters.iter().enumerate() {
603 let block = compute_near_block(
605 elements,
606 nodes,
607 &cluster_i.element_indices,
608 &cluster_i.element_indices,
609 physics,
610 gamma,
611 tau,
612 beta,
613 true,
614 );
615 system.near_matrix.push(NearFieldBlock {
616 source_cluster: i,
617 field_cluster: i,
618 coefficients: block,
619 });
620
621 for &j in &cluster_i.near_clusters {
623 if j > i {
624 let cluster_j = &leaf_clusters[j];
625 let block = compute_near_block(
626 elements,
627 nodes,
628 &cluster_i.element_indices,
629 &cluster_j.element_indices,
630 physics,
631 gamma,
632 tau,
633 beta,
634 false,
635 );
636 system.near_matrix.push(NearFieldBlock {
637 source_cluster: i,
638 field_cluster: j,
639 coefficients: block,
640 });
641 }
642 }
643 }
644}
645
646fn compute_near_block(
648 elements: &[Element],
649 nodes: &Array2<f64>,
650 source_indices: &[usize],
651 field_indices: &[usize],
652 physics: &PhysicsParams,
653 gamma: Complex64,
654 tau: Complex64,
655 beta: Complex64,
656 is_self: bool,
657) -> Array2<Complex64> {
658 let n_source = source_indices.len();
659 let n_field = field_indices.len();
660 let mut block = Array2::zeros((n_source, n_field));
661
662 for (i, &src_idx) in source_indices.iter().enumerate() {
663 let source_elem = &elements[src_idx];
664 if source_elem.property.is_evaluation() {
665 continue;
666 }
667
668 for (j, &fld_idx) in field_indices.iter().enumerate() {
669 let field_elem = &elements[fld_idx];
670 if field_elem.property.is_evaluation() {
671 continue;
672 }
673
674 let element_coords = get_element_coords(field_elem, nodes);
675
676 let result = if is_self && src_idx == fld_idx {
677 singular_integration(
679 &source_elem.center,
680 &source_elem.normal,
681 &element_coords,
682 field_elem.element_type,
683 physics,
684 None,
685 0,
686 false,
687 )
688 } else {
689 regular_integration(
691 &source_elem.center,
692 &source_elem.normal,
693 &element_coords,
694 field_elem.element_type,
695 field_elem.area,
696 physics,
697 None,
698 0,
699 false,
700 )
701 };
702
703 let coeff = result.dg_dn_integral * gamma * tau + result.d2g_dnxdny_integral * beta;
705 block[[i, j]] = coeff;
706 }
707 }
708
709 block
710}
711
712fn build_t_matrices_level(
714 elements: &[Element],
715 clusters: &[Cluster],
716 physics: &PhysicsParams,
717 sphere_coords: &[[f64; 3]],
718 sphere_weights: &[f64],
719 level_idx: usize,
720 cluster_levels: &[ClusterLevel],
721) -> LevelMatrices {
722 let k = physics.wave_number;
723 let num_sphere_points = sphere_coords.len();
724 let num_clusters = clusters.len();
725 let mut matrices = Vec::with_capacity(num_clusters);
726
727 let is_leaf_level = level_idx == cluster_levels.len() - 1;
728
729 for cluster in clusters {
730 if is_leaf_level {
731 let num_elem = cluster.element_indices.len();
733 let mut t_matrix = Array2::zeros((num_sphere_points, num_elem));
734
735 for (j, &elem_idx) in cluster.element_indices.iter().enumerate() {
736 let elem = &elements[elem_idx];
737 if elem.property.is_evaluation() {
738 continue;
739 }
740
741 for (p, coord) in sphere_coords.iter().enumerate() {
742 let diff: Vec<f64> =
743 (0..3).map(|d| elem.center[d] - cluster.center[d]).collect();
744 let s_dot_diff: f64 = (0..3).map(|d| coord[d] * diff[d]).sum();
745
746 let exp_factor =
747 Complex64::new((k * s_dot_diff).cos(), -(k * s_dot_diff).sin());
748
749 t_matrix[[p, j]] = exp_factor * sphere_weights[p];
750 }
751 }
752
753 matrices.push(t_matrix);
754 } else {
755 let num_children = cluster.sons.len();
757 let child_num_points = if level_idx + 1 < cluster_levels.len() {
758 cluster_levels[level_idx + 1].theta_points
759 * cluster_levels[level_idx + 1].phi_points
760 } else {
761 num_sphere_points
762 };
763
764 let mut t_matrix = Array2::zeros((num_sphere_points, num_children * child_num_points));
765
766 for (child_idx, &child_cluster_idx) in cluster.sons.iter().enumerate() {
768 if level_idx + 1 < cluster_levels.len() {
769 let child_cluster = &cluster_levels[level_idx + 1].clusters[child_cluster_idx];
770
771 for (p, coord) in sphere_coords.iter().enumerate() {
773 let diff: Vec<f64> = (0..3)
774 .map(|d| child_cluster.center[d] - cluster.center[d])
775 .collect();
776 let s_dot_diff: f64 = (0..3).map(|d| coord[d] * diff[d]).sum();
777
778 let exp_factor =
779 Complex64::new((k * s_dot_diff).cos(), -(k * s_dot_diff).sin());
780
781 for cp in 0..child_num_points {
783 t_matrix[[p, child_idx * child_num_points + cp]] =
784 exp_factor * sphere_weights[p] / child_num_points as f64;
785 }
786 }
787 }
788 }
789
790 matrices.push(t_matrix);
791 }
792 }
793
794 LevelMatrices {
795 level: level_idx,
796 num_clusters,
797 matrices,
798 }
799}
800
801fn build_d_matrices_level(
806 clusters: &[Cluster],
807 physics: &PhysicsParams,
808 _sphere_coords: &[[f64; 3]],
809 n_terms: usize,
810 level_idx: usize,
811 num_sphere_points: usize,
812) -> Vec<DMatrixEntry> {
813 let k = physics.wave_number;
814 let mut d_entries = Vec::new();
815
816 for (i, cluster_i) in clusters.iter().enumerate() {
817 for &j in &cluster_i.far_clusters {
818 let cluster_j = &clusters[j];
819
820 let diff: Vec<f64> = (0..3)
822 .map(|d| cluster_i.center[d] - cluster_j.center[d])
823 .collect();
824 let r = (diff[0] * diff[0] + diff[1] * diff[1] + diff[2] * diff[2]).sqrt();
825
826 if r < 1e-15 {
827 continue; }
829
830 let kr = k * r;
831
832 let h_funcs = spherical_hankel_first_kind(n_terms.max(2), kr, 1.0);
834
835 let d_value = h_funcs[0] * Complex64::new(0.0, k);
838 let diagonal = Array1::from_elem(num_sphere_points, d_value);
839
840 d_entries.push(DMatrixEntry {
841 source_cluster: i,
842 field_cluster: j,
843 level: level_idx,
844 diagonal,
845 });
846 }
847 }
848
849 d_entries
850}
851
852fn build_s_matrices_level(
854 elements: &[Element],
855 clusters: &[Cluster],
856 physics: &PhysicsParams,
857 sphere_coords: &[[f64; 3]],
858 sphere_weights: &[f64],
859 level_idx: usize,
860 cluster_levels: &[ClusterLevel],
861) -> LevelMatrices {
862 let k = physics.wave_number;
863 let num_sphere_points = sphere_coords.len();
864 let num_clusters = clusters.len();
865 let mut matrices = Vec::with_capacity(num_clusters);
866
867 let is_leaf_level = level_idx == cluster_levels.len() - 1;
868
869 for cluster in clusters {
870 if is_leaf_level {
871 let num_elem = cluster.element_indices.len();
873 let mut s_matrix = Array2::zeros((num_elem, num_sphere_points));
874
875 for (j, &elem_idx) in cluster.element_indices.iter().enumerate() {
876 let elem = &elements[elem_idx];
877 if elem.property.is_evaluation() {
878 continue;
879 }
880
881 for (p, coord) in sphere_coords.iter().enumerate() {
882 let diff: Vec<f64> =
883 (0..3).map(|d| elem.center[d] - cluster.center[d]).collect();
884 let s_dot_diff: f64 = (0..3).map(|d| coord[d] * diff[d]).sum();
885
886 let exp_factor = Complex64::new((k * s_dot_diff).cos(), (k * s_dot_diff).sin());
887
888 s_matrix[[j, p]] = exp_factor * sphere_weights[p];
889 }
890 }
891
892 matrices.push(s_matrix);
893 } else {
894 let num_children = cluster.sons.len();
896 let child_num_points = if level_idx + 1 < cluster_levels.len() {
897 cluster_levels[level_idx + 1].theta_points
898 * cluster_levels[level_idx + 1].phi_points
899 } else {
900 num_sphere_points
901 };
902
903 let mut s_matrix = Array2::zeros((num_children * child_num_points, num_sphere_points));
904
905 for (child_idx, &child_cluster_idx) in cluster.sons.iter().enumerate() {
906 if level_idx + 1 < cluster_levels.len() {
907 let child_cluster = &cluster_levels[level_idx + 1].clusters[child_cluster_idx];
908
909 for (p, coord) in sphere_coords.iter().enumerate() {
910 let diff: Vec<f64> = (0..3)
911 .map(|d| child_cluster.center[d] - cluster.center[d])
912 .collect();
913 let s_dot_diff: f64 = (0..3).map(|d| coord[d] * diff[d]).sum();
914
915 let exp_factor =
916 Complex64::new((k * s_dot_diff).cos(), (k * s_dot_diff).sin());
917
918 for cp in 0..child_num_points {
919 s_matrix[[child_idx * child_num_points + cp, p]] =
920 exp_factor * sphere_weights[p] / child_num_points as f64;
921 }
922 }
923 }
924 }
925
926 matrices.push(s_matrix);
927 }
928 }
929
930 LevelMatrices {
931 level: level_idx,
932 num_clusters,
933 matrices,
934 }
935}
936
937fn get_element_coords(element: &Element, nodes: &Array2<f64>) -> Array2<f64> {
939 let num_nodes = element.connectivity.len();
940 let mut coords = Array2::zeros((num_nodes, 3));
941
942 for (i, &node_idx) in element.connectivity.iter().enumerate() {
943 for j in 0..3 {
944 coords[[i, j]] = nodes[[node_idx, j]];
945 }
946 }
947
948 coords
949}
950
951pub fn estimate_num_levels(
955 num_elements: usize,
956 elements_per_leaf: usize,
957 min_levels: usize,
958 max_levels: usize,
959) -> usize {
960 if num_elements == 0 {
961 return min_levels;
962 }
963
964 let mut levels = 1;
966 let mut n = num_elements;
967
968 while n > elements_per_leaf && levels < max_levels {
969 n /= 8;
970 levels += 1;
971 }
972
973 levels.max(min_levels).min(max_levels)
974}
975
976pub fn build_cluster_tree(
980 elements: &[Element],
981 target_elements_per_leaf: usize,
982 physics: &PhysicsParams,
983) -> Vec<ClusterLevel> {
984 let num_elements = elements.len();
985 let num_levels = estimate_num_levels(num_elements, target_elements_per_leaf, 1, 8);
986
987 let mut levels = Vec::with_capacity(num_levels);
988
989 let (min_coords, max_coords) = compute_bounding_box(elements);
991 let root_center = [
992 (min_coords[0] + max_coords[0]) / 2.0,
993 (min_coords[1] + max_coords[1]) / 2.0,
994 (min_coords[2] + max_coords[2]) / 2.0,
995 ];
996 let root_radius = ((max_coords[0] - min_coords[0]).powi(2)
997 + (max_coords[1] - min_coords[1]).powi(2)
998 + (max_coords[2] - min_coords[2]).powi(2))
999 .sqrt()
1000 / 2.0;
1001
1002 let root_cluster = Cluster::new(Array1::from_vec(root_center.to_vec()));
1004 let all_indices: Vec<usize> = (0..num_elements).collect();
1005
1006 let mut root_level = ClusterLevel::new(1);
1008 let mut root = root_cluster;
1009 root.element_indices = all_indices;
1010 root.radius = root_radius;
1011 root.level = 0;
1012
1013 let kr = physics.wave_number * root_radius;
1015 root_level.expansion_terms = ((kr + 6.0 * kr.ln().max(1.0)) as usize).clamp(4, 30);
1016 root_level.theta_points = root_level.expansion_terms;
1017 root_level.phi_points = 2 * root_level.expansion_terms;
1018
1019 root_level.clusters.push(root);
1020 root_level.num_original = 1;
1021 root_level.max_radius = root_radius;
1022 root_level.avg_radius = root_radius;
1023 root_level.min_radius = root_radius;
1024
1025 levels.push(root_level);
1026
1027 if num_levels > 1 {
1029 subdivide_level(&mut levels, elements, 0, target_elements_per_leaf, physics);
1030 }
1031
1032 for level in &mut levels {
1034 compute_near_far_lists(&mut level.clusters, physics.wave_number);
1035 }
1036
1037 levels
1038}
1039
1040fn compute_bounding_box(elements: &[Element]) -> ([f64; 3], [f64; 3]) {
1042 let mut min_coords = [f64::MAX, f64::MAX, f64::MAX];
1043 let mut max_coords = [f64::MIN, f64::MIN, f64::MIN];
1044
1045 for elem in elements {
1046 for d in 0..3 {
1047 min_coords[d] = min_coords[d].min(elem.center[d]);
1048 max_coords[d] = max_coords[d].max(elem.center[d]);
1049 }
1050 }
1051
1052 (min_coords, max_coords)
1053}
1054
1055fn subdivide_level(
1057 levels: &mut Vec<ClusterLevel>,
1058 elements: &[Element],
1059 parent_level: usize,
1060 target_elements_per_leaf: usize,
1061 physics: &PhysicsParams,
1062) {
1063 let parent_clusters = levels[parent_level].clusters.clone();
1064 let mut child_level = ClusterLevel::new(parent_clusters.len() * 8);
1065
1066 let mut max_radius = 0.0_f64;
1067 let mut min_radius = f64::MAX;
1068 let mut sum_radius = 0.0_f64;
1069
1070 for (parent_idx, parent) in parent_clusters.iter().enumerate() {
1071 if parent.element_indices.len() <= target_elements_per_leaf {
1072 let mut leaf = parent.clone();
1074 leaf.level = parent_level + 1;
1075 leaf.father = Some(parent_idx);
1076
1077 max_radius = max_radius.max(leaf.radius);
1078 min_radius = min_radius.min(leaf.radius);
1079 sum_radius += leaf.radius;
1080
1081 let child_idx = child_level.clusters.len();
1082 levels[parent_level].clusters[parent_idx]
1083 .sons
1084 .push(child_idx);
1085 child_level.clusters.push(leaf);
1086 continue;
1087 }
1088
1089 let half_size = parent.radius / 2.0;
1091 let offsets = [
1092 [-1.0, -1.0, -1.0],
1093 [-1.0, -1.0, 1.0],
1094 [-1.0, 1.0, -1.0],
1095 [-1.0, 1.0, 1.0],
1096 [1.0, -1.0, -1.0],
1097 [1.0, -1.0, 1.0],
1098 [1.0, 1.0, -1.0],
1099 [1.0, 1.0, 1.0],
1100 ];
1101
1102 for offset in &offsets {
1103 let child_center = [
1104 parent.center[0] + offset[0] * half_size * 0.5,
1105 parent.center[1] + offset[1] * half_size * 0.5,
1106 parent.center[2] + offset[2] * half_size * 0.5,
1107 ];
1108
1109 let child_elements: Vec<usize> = parent
1111 .element_indices
1112 .iter()
1113 .filter(|&&idx| {
1114 let elem = &elements[idx];
1115 let dx = elem.center[0] - parent.center[0];
1116 let dy = elem.center[1] - parent.center[1];
1117 let dz = elem.center[2] - parent.center[2];
1118
1119 (dx * offset[0] >= 0.0) && (dy * offset[1] >= 0.0) && (dz * offset[2] >= 0.0)
1120 })
1121 .copied()
1122 .collect();
1123
1124 if child_elements.is_empty() {
1125 continue;
1126 }
1127
1128 let mut child = Cluster::new(Array1::from_vec(child_center.to_vec()));
1129 child.element_indices = child_elements;
1130 child.radius = half_size;
1131 child.level = parent_level + 1;
1132 child.father = Some(parent_idx);
1133
1134 max_radius = max_radius.max(child.radius);
1135 min_radius = min_radius.min(child.radius);
1136 sum_radius += child.radius;
1137
1138 let child_idx = child_level.clusters.len();
1139 levels[parent_level].clusters[parent_idx]
1140 .sons
1141 .push(child_idx);
1142 child_level.clusters.push(child);
1143 }
1144 }
1145
1146 let num_clusters = child_level.clusters.len();
1147 child_level.num_original = num_clusters;
1148 child_level.max_radius = max_radius;
1149 child_level.min_radius = min_radius;
1150 child_level.avg_radius = if num_clusters > 0 {
1151 sum_radius / num_clusters as f64
1152 } else {
1153 0.0
1154 };
1155
1156 let kr = physics.wave_number * child_level.avg_radius;
1158 child_level.expansion_terms = ((kr + 6.0 * kr.ln().max(1.0)) as usize).clamp(4, 30);
1159 child_level.theta_points = child_level.expansion_terms;
1160 child_level.phi_points = 2 * child_level.expansion_terms;
1161
1162 levels.push(child_level);
1163
1164 let current_level = levels.len() - 1;
1166 let should_continue = levels[current_level]
1167 .clusters
1168 .iter()
1169 .any(|c| c.element_indices.len() > target_elements_per_leaf);
1170
1171 if should_continue && current_level < 7 {
1172 subdivide_level(
1173 levels,
1174 elements,
1175 current_level,
1176 target_elements_per_leaf,
1177 physics,
1178 );
1179 }
1180}
1181
1182fn compute_near_far_lists(clusters: &mut [Cluster], wave_number: f64) {
1184 let n = clusters.len();
1185 let min_separation = 2.0; for i in 0..n {
1188 let center_i = clusters[i].center.clone();
1189 let radius_i = clusters[i].radius;
1190
1191 let mut near = Vec::new();
1192 let mut far = Vec::new();
1193
1194 for j in 0..n {
1195 if i == j {
1196 continue;
1197 }
1198
1199 let center_j = &clusters[j].center;
1200 let radius_j = clusters[j].radius;
1201
1202 let dist = ((center_i[0] - center_j[0]).powi(2)
1203 + (center_i[1] - center_j[1]).powi(2)
1204 + (center_i[2] - center_j[2]).powi(2))
1205 .sqrt();
1206
1207 let separation = dist / (radius_i + radius_j).max(1e-15);
1208
1209 let kr = wave_number * dist;
1211 let is_acoustic_far = kr > 2.0;
1212
1213 if separation > min_separation && is_acoustic_far {
1214 far.push(j);
1215 } else {
1216 near.push(j);
1217 }
1218 }
1219
1220 clusters[i].near_clusters = near;
1221 clusters[i].far_clusters = far;
1222 }
1223}
1224
1225#[cfg(test)]
1226mod tests {
1227 use super::*;
1228 use crate::core::types::{BoundaryCondition, ElementProperty, ElementType};
1229 use ndarray::array;
1230
1231 fn make_test_elements() -> (Vec<Element>, Array2<f64>) {
1232 let nodes = Array2::from_shape_vec(
1233 (4, 3),
1234 vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.5, 1.0, 0.0, 1.5, 1.0, 0.0],
1235 )
1236 .unwrap();
1237
1238 let elem0 = Element {
1239 connectivity: vec![0, 1, 2],
1240 element_type: ElementType::Tri3,
1241 property: ElementProperty::Surface,
1242 normal: array![0.0, 0.0, 1.0],
1243 node_normals: Array2::zeros((3, 3)),
1244 center: array![0.5, 1.0 / 3.0, 0.0],
1245 area: 0.5,
1246 boundary_condition: BoundaryCondition::Velocity(vec![Complex64::new(1.0, 0.0)]),
1247 group: 0,
1248 dof_addresses: vec![0],
1249 };
1250
1251 let elem1 = Element {
1252 connectivity: vec![1, 3, 2],
1253 element_type: ElementType::Tri3,
1254 property: ElementProperty::Surface,
1255 normal: array![0.0, 0.0, 1.0],
1256 node_normals: Array2::zeros((3, 3)),
1257 center: array![1.0, 2.0 / 3.0, 0.0],
1258 area: 0.5,
1259 boundary_condition: BoundaryCondition::Velocity(vec![Complex64::new(0.0, 0.0)]),
1260 group: 0,
1261 dof_addresses: vec![1],
1262 };
1263
1264 (vec![elem0, elem1], nodes)
1265 }
1266
1267 #[test]
1268 fn test_estimate_num_levels() {
1269 assert_eq!(estimate_num_levels(10, 10, 1, 8), 1);
1270 assert_eq!(estimate_num_levels(100, 10, 1, 8), 3);
1272 assert!(estimate_num_levels(1000, 10, 1, 8) >= 3);
1274 }
1275
1276 #[test]
1277 fn test_build_cluster_tree() {
1278 let (elements, _nodes) = make_test_elements();
1279 let physics = PhysicsParams::new(100.0, 343.0, 1.21, false);
1280
1281 let tree = build_cluster_tree(&elements, 10, &physics);
1282
1283 assert!(!tree.is_empty());
1284 assert!(!tree[0].clusters.is_empty());
1285 assert_eq!(tree[0].clusters[0].element_indices.len(), elements.len());
1286 }
1287
1288 #[test]
1289 fn test_build_mlfmm_system() {
1290 let (elements, nodes) = make_test_elements();
1291 let physics = PhysicsParams::new(100.0, 343.0, 1.21, false);
1292
1293 let cluster_tree = build_cluster_tree(&elements, 10, &physics);
1294 let system = build_mlfmm_system(&elements, &nodes, cluster_tree, &physics);
1295
1296 assert_eq!(system.num_dofs, 2);
1297 assert!(system.num_levels >= 1);
1298 }
1299
1300 #[test]
1301 fn test_mlfmm_matvec() {
1302 let (elements, nodes) = make_test_elements();
1303 let physics = PhysicsParams::new(100.0, 343.0, 1.21, false);
1304
1305 let cluster_tree = build_cluster_tree(&elements, 10, &physics);
1306 let system = build_mlfmm_system(&elements, &nodes, cluster_tree, &physics);
1307
1308 let x = Array1::from_vec(vec![Complex64::new(1.0, 0.0), Complex64::new(0.0, 1.0)]);
1309 let y = system.matvec(&x);
1310
1311 assert_eq!(y.len(), 2);
1312 }
1313}