1#![allow(clippy::needless_range_loop, clippy::ptr_arg)]
2#![allow(dead_code)]
12
13use std::f64::consts::PI;
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub struct GridSize {
22 pub nx: usize,
24 pub ny: usize,
26}
27
28impl GridSize {
29 pub fn new(nx: usize, ny: usize) -> Self {
31 Self { nx, ny }
32 }
33
34 pub fn n_elem(&self) -> usize {
36 self.nx * self.ny
37 }
38
39 pub fn row_col(&self, idx: usize) -> (usize, usize) {
41 (idx / self.nx, idx % self.nx)
42 }
43}
44
45#[derive(Debug, Clone, Copy)]
47pub struct MaterialParams {
48 pub e_solid: f64,
50 pub e_void: f64,
52 pub nu: f64,
54}
55
56impl MaterialParams {
57 pub fn new(e_solid: f64, e_void: f64, nu: f64) -> Self {
59 Self {
60 e_solid,
61 e_void,
62 nu,
63 }
64 }
65}
66
67impl Default for MaterialParams {
68 fn default() -> Self {
69 Self {
70 e_solid: 1.0,
71 e_void: 1e-9,
72 nu: 0.3,
73 }
74 }
75}
76
77#[derive(Debug, Clone)]
86pub struct SiMP {
87 pub penalty: f64,
89 pub material: MaterialParams,
91 pub volume_fraction: f64,
93 pub filter_radius: f64,
95 pub grid: GridSize,
97 pub density: Vec<f64>,
99}
100
101impl SiMP {
102 pub fn new(
104 grid: GridSize,
105 volume_fraction: f64,
106 penalty: f64,
107 filter_radius: f64,
108 material: MaterialParams,
109 ) -> Self {
110 let n = grid.n_elem();
111 let density = vec![volume_fraction; n];
112 Self {
113 penalty,
114 material,
115 volume_fraction,
116 filter_radius,
117 grid,
118 density,
119 }
120 }
121
122 pub fn interpolated_stiffness(&self, rho: f64) -> f64 {
126 let rho_clamped = rho.clamp(0.0, 1.0);
127 self.material.e_void
128 + rho_clamped.powf(self.penalty) * (self.material.e_solid - self.material.e_void)
129 }
130
131 pub fn stiffness_sensitivity(&self, rho: f64) -> f64 {
135 let rho_clamped = rho.clamp(1e-12, 1.0);
136 self.penalty
137 * rho_clamped.powf(self.penalty - 1.0)
138 * (self.material.e_solid - self.material.e_void)
139 }
140
141 pub fn stiffness_field(&self) -> Vec<f64> {
143 self.density
144 .iter()
145 .map(|&r| self.interpolated_stiffness(r))
146 .collect()
147 }
148
149 pub fn sensitivity_field(&self) -> Vec<f64> {
151 self.density
152 .iter()
153 .map(|&r| self.stiffness_sensitivity(r))
154 .collect()
155 }
156
157 pub fn compliance_sensitivity(&self, element_strain_energy: &[f64]) -> Vec<f64> {
161 self.density
162 .iter()
163 .zip(element_strain_energy.iter())
164 .map(|(&rho, &se)| {
165 let rho_c = rho.clamp(1e-12, 1.0);
166 -self.penalty
167 * rho_c.powf(self.penalty - 1.0)
168 * (self.material.e_solid - self.material.e_void)
169 * se
170 })
171 .collect()
172 }
173
174 pub fn current_volume(&self) -> f64 {
176 let sum: f64 = self.density.iter().sum();
177 sum / self.density.len() as f64
178 }
179
180 pub fn is_valid(&self) -> bool {
182 self.density.iter().all(|&r| (0.0..=1.0).contains(&r))
183 }
184}
185
186#[derive(Debug, Clone)]
194pub struct FilteringMethods {
195 pub radius: f64,
197 pub grid: GridSize,
199 neighbor_weights: Vec<Vec<(usize, f64)>>,
201}
202
203impl FilteringMethods {
204 pub fn new(radius: f64, grid: GridSize) -> Self {
206 let neighbor_weights = Self::compute_neighbors(radius, grid);
207 Self {
208 radius,
209 grid,
210 neighbor_weights,
211 }
212 }
213
214 fn compute_neighbors(radius: f64, grid: GridSize) -> Vec<Vec<(usize, f64)>> {
215 let n = grid.n_elem();
216 let mut result = vec![Vec::new(); n];
217 for i in 0..n {
218 let (ri, ci) = grid.row_col(i);
219 for j in 0..n {
220 let (rj, cj) = grid.row_col(j);
221 let dist =
222 ((ri as f64 - rj as f64).powi(2) + (ci as f64 - cj as f64).powi(2)).sqrt();
223 if dist < radius {
224 result[i].push((j, radius - dist));
225 }
226 }
227 }
228 result
229 }
230
231 pub fn sensitivity_filter(&self, density: &[f64], sensitivity: &[f64]) -> Vec<f64> {
235 let n = self.grid.n_elem();
236 let mut filtered = vec![0.0; n];
237 for e in 0..n {
238 let mut num = 0.0f64;
239 let mut denom = 0.0f64;
240 for &(f, h) in &self.neighbor_weights[e] {
241 num += h * density[f] * sensitivity[f];
242 denom += h * density[f];
243 }
244 let rho_e = density[e];
245 let denom_safe = denom.max(1e-12 * rho_e.max(1e-12));
246 filtered[e] = num / denom_safe;
247 }
248 filtered
249 }
250
251 pub fn density_filter(&self, density: &[f64]) -> Vec<f64> {
255 let n = self.grid.n_elem();
256 let mut filtered = vec![0.0; n];
257 for e in 0..n {
258 let mut num = 0.0f64;
259 let mut denom = 0.0f64;
260 for &(f, h) in &self.neighbor_weights[e] {
261 num += h * density[f];
262 denom += h;
263 }
264 filtered[e] = if denom > 0.0 { num / denom } else { density[e] };
265 }
266 filtered
267 }
268
269 pub fn heaviside_projection(&self, density: &[f64], beta: f64, eta: f64) -> Vec<f64> {
273 let denom = (beta * eta).tanh() + (beta * (1.0 - eta)).tanh();
274 density
275 .iter()
276 .map(|&rho| ((beta * eta).tanh() + (beta * (rho - eta)).tanh()) / denom.max(1e-12))
277 .collect()
278 }
279
280 pub fn heaviside_sensitivity(&self, density: &[f64], beta: f64, eta: f64) -> Vec<f64> {
282 let denom = (beta * eta).tanh() + (beta * (1.0 - eta)).tanh();
283 density
284 .iter()
285 .map(|&rho| beta * (1.0 - (beta * (rho - eta)).tanh().powi(2)) / denom.max(1e-12))
286 .collect()
287 }
288
289 pub fn filter_sensitivity_chain(&self, sensitivity: &[f64], density: &[f64]) -> Vec<f64> {
291 let n = self.grid.n_elem();
293 let mut out = vec![0.0; n];
294 for e in 0..n {
295 let denom: f64 = self.neighbor_weights[e]
296 .iter()
297 .map(|&(_, h)| h)
298 .sum::<f64>()
299 .max(1e-12);
300 for &(f, h) in &self.neighbor_weights[e] {
301 out[f] += sensitivity[e] * h / denom;
302 }
303 }
304 let _ = density; out
306 }
307}
308
309#[derive(Debug, Clone)]
318pub struct TopOptSolver {
319 pub simp: SiMP,
321 pub filter: FilteringMethods,
323 pub move_limit: f64,
325 pub damping: f64,
327 pub tolerance: f64,
329 pub max_iter: usize,
331 pub compliance_history: Vec<f64>,
333}
334
335impl TopOptSolver {
336 pub fn new(simp: SiMP, filter: FilteringMethods) -> Self {
338 Self {
339 simp,
340 filter,
341 move_limit: 0.2,
342 damping: 0.5,
343 tolerance: 1e-4,
344 max_iter: 200,
345 compliance_history: Vec::new(),
346 }
347 }
348
349 pub fn with_move_limit(mut self, m: f64) -> Self {
351 self.move_limit = m;
352 self
353 }
354
355 pub fn with_damping(mut self, d: f64) -> Self {
357 self.damping = d;
358 self
359 }
360
361 pub fn with_tolerance(mut self, t: f64) -> Self {
363 self.tolerance = t;
364 self
365 }
366
367 pub fn with_max_iter(mut self, n: usize) -> Self {
369 self.max_iter = n;
370 self
371 }
372
373 pub fn oc_update(
377 &self,
378 density: &[f64],
379 sensitivity: &[f64],
380 volume_target: f64,
381 ) -> (Vec<f64>, f64) {
382 let n = density.len();
383 let mut lo = 0.0f64;
385 let mut hi = 1e9f64;
386 let mut new_density = vec![0.0f64; n];
387 for _ in 0..50 {
388 let lmid = 0.5 * (lo + hi);
389 for e in 0..n {
390 let be = (-sensitivity[e] / lmid).max(0.0);
391 let candidate = density[e] * be.powf(self.damping);
392 let lower = (density[e] - self.move_limit).max(0.0);
393 let upper = (density[e] + self.move_limit).min(1.0);
394 new_density[e] = candidate.clamp(lower, upper);
395 }
396 let vol: f64 = new_density.iter().sum::<f64>() / n as f64;
397 if vol > volume_target {
398 lo = lmid;
399 } else {
400 hi = lmid;
401 }
402 }
403 let lmid = 0.5 * (lo + hi);
404 (new_density, lmid)
405 }
406
407 pub fn compute_compliance(&self, strain_energy: &[f64]) -> f64 {
411 strain_energy.iter().sum()
412 }
413
414 pub fn run_mock(&mut self, load: f64) -> Vec<f64> {
419 let n = self.simp.grid.n_elem();
420 let vf = self.simp.volume_fraction;
421 let mut density = self.simp.density.clone();
422 self.compliance_history.clear();
423
424 for _iter in 0..self.max_iter {
425 let se: Vec<f64> = density
427 .iter()
428 .map(|&rho| self.simp.interpolated_stiffness(rho) * load * load / n as f64)
429 .collect();
430 let compliance = self.compute_compliance(&se);
431 self.compliance_history.push(compliance);
432
433 let raw_sens = self.simp.compliance_sensitivity(&se);
435 let filtered_sens = self.filter.sensitivity_filter(&density, &raw_sens);
437 let (new_density, _lm) = self.oc_update(&density, &filtered_sens, vf);
439
440 let change: f64 = density
442 .iter()
443 .zip(new_density.iter())
444 .map(|(a, b)| (a - b).abs())
445 .fold(0.0f64, f64::max);
446 density = new_density;
447 if change < self.tolerance {
448 break;
449 }
450 }
451
452 self.simp.density = density.clone();
453 density
454 }
455
456 pub fn volume_constraint_satisfied(&self, density: &[f64], tol: f64) -> bool {
458 let vol: f64 = density.iter().sum::<f64>() / density.len() as f64;
459 (vol - self.simp.volume_fraction).abs() < tol
460 }
461}
462
463#[derive(Debug, Clone)]
469pub struct LoadCase {
470 pub loads: Vec<f64>,
472 pub weight: f64,
474}
475
476impl LoadCase {
477 pub fn new(loads: Vec<f64>, weight: f64) -> Self {
479 Self { loads, weight }
480 }
481}
482
483#[derive(Debug, Clone)]
487pub struct MultiLoadTopOpt {
488 pub simp: SiMP,
490 pub filter: FilteringMethods,
492 pub load_cases: Vec<LoadCase>,
494 pub move_limit: f64,
496 pub damping: f64,
498 pub compliance_history: Vec<f64>,
500}
501
502impl MultiLoadTopOpt {
503 pub fn new(simp: SiMP, filter: FilteringMethods, load_cases: Vec<LoadCase>) -> Self {
505 Self {
506 simp,
507 filter,
508 load_cases,
509 move_limit: 0.2,
510 damping: 0.5,
511 compliance_history: Vec::new(),
512 }
513 }
514
515 pub fn weighted_compliance(&self, density: &[f64]) -> f64 {
517 let n = density.len();
518 self.load_cases
519 .iter()
520 .map(|lc| {
521 let c: f64 = density
522 .iter()
523 .zip(lc.loads.iter())
524 .map(|(&rho, &f)| self.simp.interpolated_stiffness(rho) * f * f / n as f64)
525 .sum();
526 lc.weight * c
527 })
528 .sum()
529 }
530
531 pub fn aggregated_sensitivity(&self, density: &[f64]) -> Vec<f64> {
533 let n = density.len();
534 let mut agg = vec![0.0f64; n];
535 for lc in &self.load_cases {
536 for (e, (&rho, &f)) in density.iter().zip(lc.loads.iter()).enumerate() {
537 let rho_c = rho.clamp(1e-12, 1.0);
538 let se = self.simp.interpolated_stiffness(rho) * f * f / n as f64;
539 let dsens = -self.simp.penalty
540 * rho_c.powf(self.simp.penalty - 1.0)
541 * (self.simp.material.e_solid - self.simp.material.e_void)
542 * se;
543 agg[e] += lc.weight * dsens;
544 }
545 }
546 agg
547 }
548
549 pub fn run(&mut self, max_iter: usize, tol: f64) -> Vec<f64> {
551 let n = self.simp.grid.n_elem();
552 let vf = self.simp.volume_fraction;
553 let mut density = self.simp.density.clone();
554 self.compliance_history.clear();
555
556 for _iter in 0..max_iter {
557 let c = self.weighted_compliance(&density);
558 self.compliance_history.push(c);
559
560 let raw_sens = self.aggregated_sensitivity(&density);
561 let filtered_sens = self.filter.sensitivity_filter(&density, &raw_sens);
562
563 let mut lo = 0.0f64;
565 let mut hi = 1e9f64;
566 let mut new_density = vec![0.0f64; n];
567 for _ in 0..50 {
568 let lmid = 0.5 * (lo + hi);
569 for e in 0..n {
570 let be = (-filtered_sens[e] / lmid).max(0.0);
571 let candidate = density[e] * be.powf(self.damping);
572 let lower = (density[e] - self.move_limit).max(0.0);
573 let upper = (density[e] + self.move_limit).min(1.0);
574 new_density[e] = candidate.clamp(lower, upper);
575 }
576 let vol: f64 = new_density.iter().sum::<f64>() / n as f64;
577 if vol > vf {
578 lo = lmid;
579 } else {
580 hi = lmid;
581 }
582 }
583
584 let change: f64 = density
585 .iter()
586 .zip(new_density.iter())
587 .map(|(a, b)| (a - b).abs())
588 .fold(0.0f64, f64::max);
589 density = new_density;
590 if change < tol {
591 break;
592 }
593 }
594
595 self.simp.density = density.clone();
596 density
597 }
598}
599
600#[derive(Debug, Clone)]
610pub struct LevelSetTopOpt {
611 pub grid: GridSize,
613 pub phi: Vec<f64>,
615 pub volume_fraction: f64,
617 pub dt: f64,
619 pub material: MaterialParams,
621 pub compliance_history: Vec<f64>,
623}
624
625impl LevelSetTopOpt {
626 pub fn new(grid: GridSize, volume_fraction: f64, dt: f64, material: MaterialParams) -> Self {
628 let nn = (grid.nx + 1) * (grid.ny + 1);
629 let cx = grid.nx as f64 * 0.5;
630 let cy = grid.ny as f64 * 0.5;
631 let r = (grid.nx.min(grid.ny) as f64) * 0.4;
632 let phi: Vec<f64> = (0..nn)
633 .map(|idx| {
634 let row = idx / (grid.nx + 1);
635 let col = idx % (grid.nx + 1);
636 r - ((col as f64 - cx).powi(2) + (row as f64 - cy).powi(2)).sqrt()
637 })
638 .collect();
639 Self {
640 grid,
641 phi,
642 volume_fraction,
643 dt,
644 material,
645 compliance_history: Vec::new(),
646 }
647 }
648
649 pub fn element_density(&self) -> Vec<f64> {
651 let nx = self.grid.nx;
652 let ny = self.grid.ny;
653 let nx1 = nx + 1;
654 (0..nx * ny)
655 .map(|e| {
656 let row = e / nx;
657 let col = e % nx;
658 let n0 = row * nx1 + col;
660 let n1 = n0 + 1;
661 let n2 = n0 + nx1;
662 let n3 = n2 + 1;
663 let avg = 0.25 * (self.phi[n0] + self.phi[n1] + self.phi[n2] + self.phi[n3]);
664 Self::heaviside(avg, 1.0)
665 })
666 .collect()
667 }
668
669 fn heaviside(x: f64, eps: f64) -> f64 {
671 if x > eps {
672 1.0
673 } else if x < -eps {
674 1e-3
675 } else {
676 let val = 1e-3 + (1.0 - 1e-3) * (x / eps + (x * PI / eps).sin() / PI) * 0.5;
677 val.clamp(1e-3, 1.0)
678 }
679 }
680
681 fn dirac(x: f64, eps: f64) -> f64 {
683 if x.abs() > eps {
684 0.0
685 } else {
686 (1.0 - 1e-3) * (1.0 + (x * PI / eps).cos()) / (2.0 * eps)
687 }
688 }
689
690 pub fn mock_compliance(&self, velocity: &[f64]) -> f64 {
692 velocity.iter().map(|v| v.powi(2)).sum::<f64>() / velocity.len() as f64
693 }
694
695 pub fn hj_update(&mut self, velocity: &[f64]) {
699 let nx = self.grid.nx;
700 let ny = self.grid.ny;
701 let nx1 = nx + 1;
702 let mut new_phi = self.phi.clone();
703 for row in 0..=ny {
704 for col in 0..=nx {
705 let idx = row * nx1 + col;
706 let vn = if idx < velocity.len() {
707 velocity[idx]
708 } else {
709 0.0
710 };
711 let dpx = if col < nx {
713 self.phi[idx + 1] - self.phi[idx]
714 } else {
715 self.phi[idx] - self.phi[idx - 1]
716 };
717 let dpy = if row < ny {
718 self.phi[idx + nx1] - self.phi[idx]
719 } else {
720 self.phi[idx] - self.phi[idx - nx1]
721 };
722 let grad_mag = (dpx * dpx + dpy * dpy).sqrt();
723 new_phi[idx] = self.phi[idx] - self.dt * vn * grad_mag;
724 }
725 }
726 self.phi = new_phi;
727 }
728
729 pub fn reinitialize(&mut self, n_iter: usize) {
731 for _ in 0..n_iter {
732 let old = self.phi.clone();
733 let nx = self.grid.nx;
734 let nx1 = nx + 1;
735 let ny = self.grid.ny;
736 for row in 1..ny {
737 for col in 1..nx {
738 let idx = row * nx1 + col;
739 let sign = if old[idx] > 0.0 { 1.0 } else { -1.0 };
740 let dx = 0.5 * ((old[idx + 1] - old[idx - 1]).powi(2)).sqrt().max(0.1);
741 let dy = 0.5 * ((old[idx + nx1] - old[idx - nx1]).powi(2)).sqrt().max(0.1);
742 let grad = (dx * dx + dy * dy).sqrt();
743 self.phi[idx] -= 0.1 * sign * (grad - 1.0);
744 }
745 }
746 }
747 }
748
749 pub fn run_mock(&mut self, n_iter: usize) {
751 self.compliance_history.clear();
752 for _i in 0..n_iter {
753 let density = self.element_density();
754 let compliance: f64 = density
755 .iter()
756 .map(|&r| self.material.e_solid * r)
757 .sum::<f64>()
758 / density.len() as f64;
759 self.compliance_history.push(compliance);
760
761 let nn = (self.grid.nx + 1) * (self.grid.ny + 1);
763 let velocity: Vec<f64> = (0..nn)
764 .map(|idx| {
765 let phi_val = self.phi[idx];
766 -Self::dirac(phi_val, 1.0)
767 })
768 .collect();
769 self.hj_update(&velocity);
770 }
771 }
772}
773
774#[derive(Debug, Clone)]
783pub struct ManufacturingFilters {
784 pub grid: GridSize,
786 pub min_member_size: f64,
788 pub max_overhang_angle: f64,
790 pub build_direction: usize,
792}
793
794impl ManufacturingFilters {
795 pub fn new(grid: GridSize, min_member_size: f64, max_overhang_angle: f64) -> Self {
797 Self {
798 grid,
799 min_member_size,
800 max_overhang_angle,
801 build_direction: 0,
802 }
803 }
804
805 pub fn with_build_direction(mut self, dir: usize) -> Self {
807 self.build_direction = dir;
808 self
809 }
810
811 pub fn minimum_length_scale(&self, density: &[f64], beta: f64) -> Vec<f64> {
815 let nx = self.grid.nx;
816 let ny = self.grid.ny;
817 let r = self.min_member_size * 0.5;
818 density
819 .iter()
820 .enumerate()
821 .map(|(e, &rho)| {
822 let (row, col) = self.grid.row_col(e);
823 let mut count = 0;
825 let mut total = 0;
826 for dr in -(r as i64)..=(r as i64) {
827 for dc in -(r as i64)..=(r as i64) {
828 if (dr as f64 * dr as f64 + dc as f64 * dc as f64).sqrt() > r {
829 continue;
830 }
831 let nr = row as i64 + dr;
832 let nc = col as i64 + dc;
833 if nr < 0 || nr >= ny as i64 || nc < 0 || nc >= nx as i64 {
834 continue;
835 }
836 let ni = nr as usize * nx + nc as usize;
837 if density[ni] > 0.5 {
838 count += 1;
839 }
840 total += 1;
841 }
842 }
843 let frac = if total > 0 {
844 count as f64 / total as f64
845 } else {
846 rho
847 };
848 1.0 / (1.0 + (-beta * (frac - 0.5)).exp())
850 })
851 .collect()
852 }
853
854 pub fn overhang_filter(&self, density: &[f64]) -> Vec<f64> {
859 let nx = self.grid.nx;
860 let ny = self.grid.ny;
861 let tan_limit = self.max_overhang_angle.tan();
862 density
863 .iter()
864 .enumerate()
865 .map(|(e, &rho)| {
866 let (row, col) = self.grid.row_col(e);
867 match self.build_direction {
869 0 => {
870 if row == 0 {
871 return rho; }
873 let support_l = if col > 0 {
875 density[(row - 1) * nx + col - 1]
876 } else {
877 0.0
878 };
879 let support_c = density[(row - 1) * nx + col];
880 let support_r = if col + 1 < nx {
881 density[(row - 1) * nx + col + 1]
882 } else {
883 0.0
884 };
885 let max_support = support_l.max(support_c).max(support_r);
886 if max_support < 0.3 && rho > 0.5 {
888 rho * (1.0 - (1.0 - tan_limit).max(0.0))
889 } else {
890 rho
891 }
892 }
893 1 => {
894 if row + 1 >= ny {
895 return rho;
896 }
897 let support = density[(row + 1) * nx + col];
898 if support < 0.3 && rho > 0.5 {
899 rho * 0.7
900 } else {
901 rho
902 }
903 }
904 2 => {
905 if col == 0 {
906 return rho;
907 }
908 let support = density[row * nx + col - 1];
909 if support < 0.3 && rho > 0.5 {
910 rho * 0.7
911 } else {
912 rho
913 }
914 }
915 _ => {
916 if col + 1 >= nx {
917 return rho;
918 }
919 let support = density[row * nx + col + 1];
920 if support < 0.3 && rho > 0.5 {
921 rho * 0.7
922 } else {
923 rho
924 }
925 }
926 }
927 })
928 .collect()
929 }
930
931 pub fn casting_filter(&self, density: &[f64]) -> Vec<f64> {
936 let nx = self.grid.nx;
937 let ny = self.grid.ny;
938 let mut result = density.to_vec();
939 match self.build_direction {
940 0 => {
941 for row in 1..ny {
943 for col in 0..nx {
944 let below = result[(row - 1) * nx + col];
945 let cur = result[row * nx + col];
946 result[row * nx + col] = cur.max(below * 0.0); let _ = (below, cur);
950 }
951 }
952 }
953 _ => {
954 for col in 1..nx {
956 for row in 0..ny {
957 let prev = result[row * nx + col - 1];
958 result[row * nx + col] = result[row * nx + col].max(prev * 0.0);
959 }
960 }
961 }
962 }
963 result
964 }
965
966 pub fn extrusion_filter(&self, density: &[f64], extrude_dir: usize) -> Vec<f64> {
971 let nx = self.grid.nx;
972 let ny = self.grid.ny;
973 let mut result = density.to_vec();
974 if extrude_dir == 0 {
975 for col in 0..nx {
977 let max_val: f64 = (0..ny)
978 .map(|r| density[r * nx + col])
979 .fold(0.0f64, f64::max);
980 for row in 0..ny {
981 result[row * nx + col] = max_val;
982 }
983 }
984 } else {
985 for row in 0..ny {
987 let max_val: f64 = (0..nx)
988 .map(|c| density[row * nx + c])
989 .fold(0.0f64, f64::max);
990 for col in 0..nx {
991 result[row * nx + col] = max_val;
992 }
993 }
994 }
995 result
996 }
997
998 pub fn milling_filter(&self, density: &[f64], threshold: f64) -> Vec<f64> {
1002 let nx = self.grid.nx;
1003 let ny = self.grid.ny;
1004 let n = nx * ny;
1005 let mut accessible = vec![false; n];
1006 let mut queue = std::collections::VecDeque::new();
1007
1008 for row in 0..ny {
1010 for col in 0..nx {
1011 if row == 0 || row == ny - 1 || col == 0 || col == nx - 1 {
1012 let e = row * nx + col;
1013 if density[e] < threshold {
1014 accessible[e] = true;
1015 queue.push_back(e);
1016 }
1017 }
1018 }
1019 }
1020
1021 while let Some(e) = queue.pop_front() {
1023 let (row, col) = (e / nx, e % nx);
1024 let neighbors = [
1025 if row > 0 {
1026 Some((row - 1) * nx + col)
1027 } else {
1028 None
1029 },
1030 if row + 1 < ny {
1031 Some((row + 1) * nx + col)
1032 } else {
1033 None
1034 },
1035 if col > 0 {
1036 Some(row * nx + col - 1)
1037 } else {
1038 None
1039 },
1040 if col + 1 < nx {
1041 Some(row * nx + col + 1)
1042 } else {
1043 None
1044 },
1045 ];
1046 for nbr in neighbors.into_iter().flatten() {
1047 if !accessible[nbr] && density[nbr] < threshold {
1048 accessible[nbr] = true;
1049 queue.push_back(nbr);
1050 }
1051 }
1052 }
1053
1054 density
1056 .iter()
1057 .enumerate()
1058 .map(|(e, &rho)| {
1059 if rho < threshold && !accessible[e] {
1060 1.0
1061 } else {
1062 rho
1063 }
1064 })
1065 .collect()
1066 }
1067}
1068
1069pub fn volume_fraction(density: &[f64]) -> f64 {
1075 if density.is_empty() {
1076 return 0.0;
1077 }
1078 density.iter().sum::<f64>() / density.len() as f64
1079}
1080
1081pub fn normalize_sensitivity(sensitivity: &mut Vec<f64>) {
1083 let min_s = sensitivity.iter().copied().fold(f64::INFINITY, f64::min);
1084 let max_s = sensitivity
1085 .iter()
1086 .copied()
1087 .fold(f64::NEG_INFINITY, f64::max);
1088 let range = (max_s - min_s).max(1e-12);
1089 for s in sensitivity.iter_mut() {
1090 *s = (*s - min_s) / range - 1.0;
1091 }
1092}
1093
1094pub fn binarize(density: &[f64], threshold: f64) -> Vec<bool> {
1096 density.iter().map(|&r| r >= threshold).collect()
1097}
1098
1099pub fn count_solid(binary: &[bool]) -> usize {
1101 binary.iter().filter(|&&b| b).count()
1102}
1103
1104pub fn is_connected(density: &[f64], grid: GridSize, threshold: f64) -> bool {
1106 let nx = grid.nx;
1107 let ny = grid.ny;
1108 let n = nx * ny;
1109 let solid: Vec<bool> = density.iter().map(|&r| r >= threshold).collect();
1110 let start = solid.iter().position(|&s| s);
1111 let Some(start) = start else { return true };
1112
1113 let mut visited = vec![false; n];
1114 let mut queue = std::collections::VecDeque::new();
1115 queue.push_back(start);
1116 visited[start] = true;
1117
1118 while let Some(e) = queue.pop_front() {
1119 let (row, col) = (e / nx, e % nx);
1120 let neighbors = [
1121 if row > 0 {
1122 Some((row - 1) * nx + col)
1123 } else {
1124 None
1125 },
1126 if row + 1 < ny {
1127 Some((row + 1) * nx + col)
1128 } else {
1129 None
1130 },
1131 if col > 0 {
1132 Some(row * nx + col - 1)
1133 } else {
1134 None
1135 },
1136 if col + 1 < nx {
1137 Some(row * nx + col + 1)
1138 } else {
1139 None
1140 },
1141 ];
1142 for nbr in neighbors.into_iter().flatten() {
1143 if solid[nbr] && !visited[nbr] {
1144 visited[nbr] = true;
1145 queue.push_back(nbr);
1146 }
1147 }
1148 }
1149
1150 solid.iter().zip(visited.iter()).all(|(&s, &v)| !s || v)
1152}
1153
1154#[cfg(test)]
1159mod tests {
1160 use super::*;
1161
1162 fn small_grid() -> GridSize {
1163 GridSize::new(4, 4)
1164 }
1165
1166 fn default_material() -> MaterialParams {
1167 MaterialParams::default()
1168 }
1169
1170 #[test]
1173 fn test_grid_size_n_elem() {
1174 let g = GridSize::new(5, 3);
1175 assert_eq!(g.n_elem(), 15);
1176 }
1177
1178 #[test]
1179 fn test_grid_size_row_col() {
1180 let g = GridSize::new(5, 3);
1181 assert_eq!(g.row_col(7), (1, 2));
1182 }
1183
1184 #[test]
1187 fn test_simp_initial_density() {
1188 let simp = SiMP::new(small_grid(), 0.5, 3.0, 2.0, default_material());
1189 assert!((simp.current_volume() - 0.5).abs() < 1e-10);
1190 }
1191
1192 #[test]
1193 fn test_simp_stiffness_solid() {
1194 let simp = SiMP::new(small_grid(), 0.5, 3.0, 2.0, default_material());
1195 let e = simp.interpolated_stiffness(1.0);
1196 assert!((e - 1.0).abs() < 1e-10);
1197 }
1198
1199 #[test]
1200 fn test_simp_stiffness_void() {
1201 let simp = SiMP::new(small_grid(), 0.5, 3.0, 2.0, default_material());
1202 let e = simp.interpolated_stiffness(0.0);
1203 assert!((e - 1e-9).abs() < 1e-15);
1204 }
1205
1206 #[test]
1207 fn test_simp_stiffness_interpolation() {
1208 let simp = SiMP::new(small_grid(), 0.5, 2.0, 2.0, default_material());
1209 let e = simp.interpolated_stiffness(0.5);
1211 assert!((e - 0.25).abs() < 0.01);
1212 }
1213
1214 #[test]
1215 fn test_simp_sensitivity_positive() {
1216 let simp = SiMP::new(small_grid(), 0.5, 3.0, 2.0, default_material());
1217 let s = simp.stiffness_sensitivity(0.5);
1218 assert!(s > 0.0);
1219 }
1220
1221 #[test]
1222 fn test_simp_compliance_sensitivity_negative() {
1223 let simp = SiMP::new(small_grid(), 0.5, 3.0, 2.0, default_material());
1224 let se = vec![1.0; 16];
1225 let cs = simp.compliance_sensitivity(&se);
1226 assert!(cs.iter().all(|&s| s < 0.0));
1227 }
1228
1229 #[test]
1230 fn test_simp_is_valid() {
1231 let simp = SiMP::new(small_grid(), 0.5, 3.0, 2.0, default_material());
1232 assert!(simp.is_valid());
1233 }
1234
1235 #[test]
1236 fn test_simp_stiffness_field_length() {
1237 let grid = small_grid();
1238 let simp = SiMP::new(grid, 0.5, 3.0, 2.0, default_material());
1239 assert_eq!(simp.stiffness_field().len(), grid.n_elem());
1240 }
1241
1242 #[test]
1245 fn test_density_filter_uniform() {
1246 let grid = GridSize::new(3, 3);
1247 let filter = FilteringMethods::new(1.5, grid);
1248 let density = vec![0.5; 9];
1249 let filtered = filter.density_filter(&density);
1250 assert!(filtered.iter().all(|&r| (r - 0.5).abs() < 1e-10));
1251 }
1252
1253 #[test]
1254 fn test_density_filter_smooths() {
1255 let grid = GridSize::new(5, 5);
1256 let filter = FilteringMethods::new(2.0, grid);
1257 let mut density = vec![0.0; 25];
1258 density[12] = 1.0; let filtered = filter.density_filter(&density);
1260 assert!(filtered[11] > 0.0 || filtered[13] > 0.0);
1262 }
1263
1264 #[test]
1265 fn test_sensitivity_filter_output_length() {
1266 let grid = GridSize::new(3, 3);
1267 let filter = FilteringMethods::new(1.5, grid);
1268 let density = vec![0.5; 9];
1269 let sens = vec![-1.0; 9];
1270 let out = filter.sensitivity_filter(&density, &sens);
1271 assert_eq!(out.len(), 9);
1272 }
1273
1274 #[test]
1275 fn test_heaviside_projection_solid() {
1276 let grid = GridSize::new(2, 2);
1277 let filter = FilteringMethods::new(1.5, grid);
1278 let density = vec![1.0; 4];
1279 let proj = filter.heaviside_projection(&density, 10.0, 0.5);
1280 assert!(proj.iter().all(|&r| r > 0.9));
1281 }
1282
1283 #[test]
1284 fn test_heaviside_projection_void() {
1285 let grid = GridSize::new(2, 2);
1286 let filter = FilteringMethods::new(1.5, grid);
1287 let density = vec![0.0; 4];
1288 let proj = filter.heaviside_projection(&density, 10.0, 0.5);
1289 assert!(proj.iter().all(|&r| r < 0.1));
1290 }
1291
1292 #[test]
1293 fn test_heaviside_sensitivity_positive() {
1294 let grid = GridSize::new(2, 2);
1295 let filter = FilteringMethods::new(1.5, grid);
1296 let density = vec![0.5; 4];
1297 let sens = filter.heaviside_sensitivity(&density, 5.0, 0.5);
1298 assert!(sens.iter().all(|&s| s >= 0.0));
1299 }
1300
1301 #[test]
1302 fn test_filter_sensitivity_chain() {
1303 let grid = GridSize::new(3, 3);
1304 let filter = FilteringMethods::new(1.5, grid);
1305 let sens = vec![1.0; 9];
1306 let density = vec![0.5; 9];
1307 let out = filter.filter_sensitivity_chain(&sens, &density);
1308 assert_eq!(out.len(), 9);
1309 }
1310
1311 #[test]
1314 fn test_topopt_oc_update_volume() {
1315 let grid = GridSize::new(4, 4);
1316 let simp = SiMP::new(grid, 0.4, 3.0, 1.5, MaterialParams::default());
1317 let filter = FilteringMethods::new(1.5, grid);
1318 let solver = TopOptSolver::new(simp, filter);
1319 let density = vec![0.5; 16];
1320 let sensitivity = vec![-1.0; 16];
1321 let (new_d, _lm) = solver.oc_update(&density, &sensitivity, 0.4);
1322 let vol: f64 = new_d.iter().sum::<f64>() / 16.0;
1323 assert!((vol - 0.4).abs() < 0.05);
1324 }
1325
1326 #[test]
1327 fn test_topopt_run_mock_convergence() {
1328 let grid = GridSize::new(4, 4);
1329 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1330 let filter = FilteringMethods::new(1.5, grid);
1331 let mut solver = TopOptSolver::new(simp, filter).with_max_iter(10);
1332 let result = solver.run_mock(1.0);
1333 assert_eq!(result.len(), 16);
1334 assert!(!solver.compliance_history.is_empty());
1335 }
1336
1337 #[test]
1338 fn test_topopt_compliance_positive() {
1339 let grid = GridSize::new(4, 4);
1340 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1341 let filter = FilteringMethods::new(1.5, grid);
1342 let solver = TopOptSolver::new(simp, filter);
1343 let se = vec![1.0; 16];
1344 let c = solver.compute_compliance(&se);
1345 assert!(c > 0.0);
1346 }
1347
1348 #[test]
1349 fn test_topopt_volume_constraint() {
1350 let grid = GridSize::new(4, 4);
1351 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1352 let filter = FilteringMethods::new(1.5, grid);
1353 let solver = TopOptSolver::new(simp, filter);
1354 let density = vec![0.5; 16];
1355 assert!(solver.volume_constraint_satisfied(&density, 0.01));
1356 }
1357
1358 #[test]
1359 fn test_topopt_with_damping() {
1360 let grid = GridSize::new(4, 4);
1361 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1362 let filter = FilteringMethods::new(1.5, grid);
1363 let solver = TopOptSolver::new(simp, filter).with_damping(0.3);
1364 assert!((solver.damping - 0.3).abs() < 1e-10);
1365 }
1366
1367 #[test]
1370 fn test_multiload_weighted_compliance() {
1371 let grid = GridSize::new(4, 4);
1372 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1373 let filter = FilteringMethods::new(1.5, grid);
1374 let lc1 = LoadCase::new(vec![1.0; 16], 0.5);
1375 let lc2 = LoadCase::new(vec![0.5; 16], 0.5);
1376 let solver = MultiLoadTopOpt::new(simp, filter, vec![lc1, lc2]);
1377 let density = vec![0.5; 16];
1378 let c = solver.weighted_compliance(&density);
1379 assert!(c > 0.0);
1380 }
1381
1382 #[test]
1383 fn test_multiload_aggregated_sensitivity_len() {
1384 let grid = GridSize::new(4, 4);
1385 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1386 let filter = FilteringMethods::new(1.5, grid);
1387 let lc = LoadCase::new(vec![1.0; 16], 1.0);
1388 let solver = MultiLoadTopOpt::new(simp, filter, vec![lc]);
1389 let density = vec![0.5; 16];
1390 let sens = solver.aggregated_sensitivity(&density);
1391 assert_eq!(sens.len(), 16);
1392 }
1393
1394 #[test]
1395 fn test_multiload_run() {
1396 let grid = GridSize::new(4, 4);
1397 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1398 let filter = FilteringMethods::new(1.5, grid);
1399 let lc = LoadCase::new(vec![1.0; 16], 1.0);
1400 let mut solver = MultiLoadTopOpt::new(simp, filter, vec![lc]);
1401 let result = solver.run(5, 1e-3);
1402 assert_eq!(result.len(), 16);
1403 }
1404
1405 #[test]
1406 fn test_multiload_equal_weight_symmetry() {
1407 let grid = GridSize::new(2, 2);
1408 let simp = SiMP::new(grid, 0.5, 3.0, 1.5, MaterialParams::default());
1409 let filter = FilteringMethods::new(1.5, grid);
1410 let lc1 = LoadCase::new(vec![1.0; 4], 0.5);
1411 let lc2 = LoadCase::new(vec![1.0; 4], 0.5);
1412 let solver = MultiLoadTopOpt::new(simp.clone(), filter.clone(), vec![lc1, lc2]);
1413 let lc_single = LoadCase::new(vec![1.0; 4], 1.0);
1414 let solver2 = MultiLoadTopOpt::new(simp, filter, vec![lc_single]);
1415 let d = vec![0.5; 4];
1416 let c1 = solver.weighted_compliance(&d);
1417 let c2 = solver2.weighted_compliance(&d);
1418 assert!((c1 - c2).abs() < 1e-10);
1419 }
1420
1421 #[test]
1424 fn test_levelset_init_phi_length() {
1425 let grid = GridSize::new(4, 4);
1426 let ls = LevelSetTopOpt::new(grid, 0.5, 0.1, MaterialParams::default());
1427 assert_eq!(ls.phi.len(), 25); }
1429
1430 #[test]
1431 fn test_levelset_element_density_length() {
1432 let grid = GridSize::new(4, 4);
1433 let ls = LevelSetTopOpt::new(grid, 0.5, 0.1, MaterialParams::default());
1434 assert_eq!(ls.element_density().len(), 16);
1435 }
1436
1437 #[test]
1438 fn test_levelset_element_density_range() {
1439 let grid = GridSize::new(4, 4);
1440 let ls = LevelSetTopOpt::new(grid, 0.5, 0.1, MaterialParams::default());
1441 let d = ls.element_density();
1442 assert!(d.iter().all(|&r| (0.0..=1.0).contains(&r)));
1443 }
1444
1445 #[test]
1446 fn test_levelset_hj_update_changes_phi() {
1447 let grid = GridSize::new(4, 4);
1448 let mut ls = LevelSetTopOpt::new(grid, 0.5, 0.1, MaterialParams::default());
1449 let phi_before = ls.phi.clone();
1450 let velocity = vec![1.0; 25];
1451 ls.hj_update(&velocity);
1452 let changed = phi_before
1453 .iter()
1454 .zip(ls.phi.iter())
1455 .any(|(a, b)| (a - b).abs() > 1e-10);
1456 assert!(changed);
1457 }
1458
1459 #[test]
1460 fn test_levelset_run_mock() {
1461 let grid = GridSize::new(4, 4);
1462 let mut ls = LevelSetTopOpt::new(grid, 0.5, 0.01, MaterialParams::default());
1463 ls.run_mock(5);
1464 assert_eq!(ls.compliance_history.len(), 5);
1465 }
1466
1467 #[test]
1468 fn test_levelset_reinitialize() {
1469 let grid = GridSize::new(4, 4);
1470 let mut ls = LevelSetTopOpt::new(grid, 0.5, 0.1, MaterialParams::default());
1471 let phi_before = ls.phi.clone();
1472 ls.reinitialize(3);
1473 let _ = phi_before;
1475 }
1476
1477 #[test]
1480 fn test_mfg_min_length_scale_output_len() {
1481 let grid = GridSize::new(4, 4);
1482 let mf = ManufacturingFilters::new(grid, 2.0, PI / 4.0);
1483 let density = vec![0.5; 16];
1484 let out = mf.minimum_length_scale(&density, 10.0);
1485 assert_eq!(out.len(), 16);
1486 }
1487
1488 #[test]
1489 fn test_mfg_min_length_scale_range() {
1490 let grid = GridSize::new(4, 4);
1491 let mf = ManufacturingFilters::new(grid, 2.0, PI / 4.0);
1492 let density = vec![0.5; 16];
1493 let out = mf.minimum_length_scale(&density, 10.0);
1494 assert!(out.iter().all(|&r| (0.0..=1.0).contains(&r)));
1495 }
1496
1497 #[test]
1498 fn test_mfg_overhang_base_unchanged() {
1499 let grid = GridSize::new(4, 4);
1500 let mf = ManufacturingFilters::new(grid, 2.0, PI / 3.0);
1501 let mut density = vec![0.0; 16];
1502 for col in 0..4 {
1504 density[col] = 1.0;
1505 }
1506 let out = mf.overhang_filter(&density);
1507 assert!((out[0] - density[0]).abs() < 1e-10);
1509 }
1510
1511 #[test]
1512 fn test_mfg_casting_filter_len() {
1513 let grid = GridSize::new(4, 4);
1514 let mf = ManufacturingFilters::new(grid, 2.0, PI / 4.0);
1515 let density = vec![0.5; 16];
1516 let out = mf.casting_filter(&density);
1517 assert_eq!(out.len(), 16);
1518 }
1519
1520 #[test]
1521 fn test_mfg_extrusion_filter_x() {
1522 let grid = GridSize::new(4, 4);
1523 let mf = ManufacturingFilters::new(grid, 2.0, PI / 4.0);
1524 let mut density = vec![0.0; 16];
1525 density[0] = 1.0; let out = mf.extrusion_filter(&density, 0); for row in 0..4 {
1529 assert!((out[row * 4] - 1.0).abs() < 1e-10);
1530 }
1531 }
1532
1533 #[test]
1534 fn test_mfg_extrusion_filter_y() {
1535 let grid = GridSize::new(4, 4);
1536 let mf = ManufacturingFilters::new(grid, 2.0, PI / 4.0);
1537 let mut density = vec![0.0; 16];
1538 density[0] = 1.0; let out = mf.extrusion_filter(&density, 1); for col in 0..4 {
1542 assert!((out[col] - 1.0).abs() < 1e-10);
1543 }
1544 }
1545
1546 #[test]
1547 fn test_mfg_milling_filter_no_enclosed() {
1548 let grid = GridSize::new(4, 4);
1549 let mf = ManufacturingFilters::new(grid, 2.0, PI / 4.0);
1550 let density = vec![0.0; 16]; let out = mf.milling_filter(&density, 0.5);
1552 assert!(out.iter().all(|&r| r < 0.5)); }
1554
1555 #[test]
1556 fn test_mfg_milling_filter_fills_enclosed() {
1557 let grid = GridSize::new(5, 5);
1558 let mf = ManufacturingFilters::new(grid, 2.0, PI / 4.0);
1559 let mut density = vec![1.0; 25];
1561 density[12] = 0.0; let out = mf.milling_filter(&density, 0.5);
1563 assert!(out[12] >= 0.5);
1565 }
1566
1567 #[test]
1570 fn test_volume_fraction() {
1571 let d = vec![0.5; 10];
1572 assert!((volume_fraction(&d) - 0.5).abs() < 1e-10);
1573 }
1574
1575 #[test]
1576 fn test_volume_fraction_empty() {
1577 assert_eq!(volume_fraction(&[]), 0.0);
1578 }
1579
1580 #[test]
1581 fn test_normalize_sensitivity() {
1582 let mut s = vec![-4.0, -2.0, -1.0];
1583 normalize_sensitivity(&mut s);
1584 let max_s = s.iter().copied().fold(f64::NEG_INFINITY, f64::max);
1585 let min_s = s.iter().copied().fold(f64::INFINITY, f64::min);
1586 assert!(max_s <= 0.0);
1587 assert!(min_s >= -1.0);
1588 }
1589
1590 #[test]
1591 fn test_binarize() {
1592 let d = vec![0.3, 0.7, 0.5, 0.1];
1593 let b = binarize(&d, 0.5);
1594 assert_eq!(b, vec![false, true, true, false]);
1595 }
1596
1597 #[test]
1598 fn test_count_solid() {
1599 let b = vec![true, false, true, true];
1600 assert_eq!(count_solid(&b), 3);
1601 }
1602
1603 #[test]
1604 fn test_is_connected_uniform() {
1605 let grid = GridSize::new(3, 3);
1606 let density = vec![1.0; 9];
1607 assert!(is_connected(&density, grid, 0.5));
1608 }
1609
1610 #[test]
1611 fn test_is_connected_disconnected() {
1612 let grid = GridSize::new(3, 3);
1613 let mut density = vec![0.0; 9];
1614 density[0] = 1.0;
1615 density[8] = 1.0; assert!(!is_connected(&density, grid, 0.5));
1617 }
1618
1619 #[test]
1620 fn test_is_connected_all_void() {
1621 let grid = GridSize::new(3, 3);
1622 let density = vec![0.0; 9];
1623 assert!(is_connected(&density, grid, 0.5)); }
1625}