1use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
16use std::f64::consts::PI;
17use std::time::Instant;
18
19use crate::gaussian::GaussLegendreQuadrature;
23use crate::pde::{
24 BoundaryCondition, BoundaryConditionType, BoundaryLocation, Domain, PDEError, PDEResult,
25 PDESolution, PDESolverInfo,
26};
27
28#[derive(Debug, Clone, Copy, PartialEq)]
30pub enum SpectralBasis {
31 Fourier,
33
34 Chebyshev,
36
37 Legendre,
39}
40
41#[derive(Debug, Clone)]
43pub struct SpectralOptions {
44 pub basis: SpectralBasis,
46
47 pub num_modes: usize,
49
50 pub max_iterations: usize,
52
53 pub tolerance: f64,
55
56 pub save_convergence_history: bool,
58
59 pub use_real_transform: bool,
61
62 pub use_dealiasing: bool,
64
65 pub verbose: bool,
67}
68
69impl Default for SpectralOptions {
70 fn default() -> Self {
71 SpectralOptions {
72 basis: SpectralBasis::Fourier,
73 num_modes: 64,
74 max_iterations: 1000,
75 tolerance: 1e-10,
76 save_convergence_history: false,
77 use_real_transform: true,
78 use_dealiasing: true,
79 verbose: false,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
86pub struct SpectralResult {
87 pub u: Array1<f64>,
89
90 pub coefficients: Array1<f64>,
92
93 pub grid: Array1<f64>,
95
96 pub residual_norm: f64,
98
99 pub num_iterations: usize,
101
102 pub computation_time: f64,
104
105 pub convergence_history: Option<Vec<f64>>,
107}
108
109#[allow(dead_code)]
114pub fn chebyshev_diff_matrix(n: usize) -> Array2<f64> {
115 let mut d = Array2::zeros((n, n));
116
117 let mut x = Array1::zeros(n);
119 for j in 0..n {
120 x[j] = (j as f64 * PI / (n - 1) as f64).cos();
121 }
122
123 for i in 0..n {
125 for j in 0..n {
126 if i != j {
127 let c_i = if i == 0 || i == n - 1 { 2.0 } else { 1.0 };
128 let c_j = if j == 0 || j == n - 1 { 2.0 } else { 1.0 };
129
130 d[[i, j]] = c_i / c_j * (-1.0_f64).powf((i + j) as f64) / (x[i] - x[j]);
131 }
132 }
133 }
134
135 for i in 0..n {
137 let mut row_sum = 0.0;
138 for j in 0..n {
139 if i != j {
140 row_sum += d[[i, j]];
141 }
142 }
143 d[[i, i]] = -row_sum;
144 }
145
146 d
147}
148
149#[allow(dead_code)]
151pub fn chebyshev_diff2_matrix(n: usize) -> Array2<f64> {
152 let d1 = chebyshev_diff_matrix(n);
153 d1.dot(&d1) }
155
156#[allow(dead_code)]
158pub fn chebyshev_points(n: usize) -> Array1<f64> {
159 let mut x = Array1::zeros(n);
160 for j in 0..n {
161 x[j] = (j as f64 * PI / (n - 1) as f64).cos();
162 }
163 x
164}
165
166#[allow(dead_code)]
168pub fn chebyshev_transform(u: &ArrayView1<f64>) -> Array1<f64> {
169 let n = u.len();
170 let mut coeffs = Array1::zeros(n);
171
172 for k in 0..n {
174 let mut sum = 0.0;
175 for j in 0..n {
176 let _x_j = (j as f64 * PI / (n - 1) as f64).cos();
177 sum += u[j] * (k as f64 * j as f64 * PI / (n - 1) as f64).cos();
178 }
179
180 let norm = if k == 0 || k == n - 1 {
181 n - 1
182 } else {
183 2 * (n - 1)
184 };
185 coeffs[k] = 2.0 * sum / norm as f64;
186 }
187
188 coeffs
189}
190
191#[allow(dead_code)]
193pub fn chebyshev_inverse_transform(coeffs: &ArrayView1<f64>) -> Array1<f64> {
194 let n = coeffs.len();
195 let mut u = Array1::zeros(n);
196
197 let _x = chebyshev_points(n);
199
200 for j in 0..n {
202 let mut sum = 0.0;
203 for k in 0..n {
204 sum += coeffs[k] * (k as f64 * j as f64 * PI / (n - 1) as f64).cos();
205 }
206 u[j] = sum;
207 }
208
209 u
210}
211
212#[allow(dead_code)]
217pub fn legendre_points(n: usize) -> (Array1<f64>, Array1<f64>) {
218 if n <= 1 {
219 return (Array1::zeros(1), Array1::ones(1));
220 }
221
222 if n <= 12 {
225 let quadrature = GaussLegendreQuadrature::<f64>::new(n - 2).expect("Operation failed");
227
228 let mut points = Array1::zeros(n);
230 let mut weights = Array1::zeros(n);
231
232 points[0] = -1.0;
234 points[n - 1] = 1.0;
235
236 for i in 0..n - 2 {
238 points[i + 1] = quadrature.nodes[n - 3 - i]; }
240
241 let factor = 2.0 / (n as f64 * (n - 1) as f64);
244
245 weights[0] = factor;
247 weights[n - 1] = factor;
248
249 for i in 1..n - 1 {
251 let x = points[i];
252 let p = legendre_polynomial(n - 1, x);
253 weights[i] = factor / (p * p);
254 }
255
256 return (points, weights);
257 }
258
259 let mut points = Array1::zeros(n);
264 for i in 0..n {
265 points[i] = -(i as f64 * PI / (n - 1) as f64).cos();
266 }
267
268 for i in 1..n - 1 {
270 let mut x = points[i];
272 let mut delta;
273
274 for _ in 0..10 {
277 let _p = legendre_polynomial(n - 1, x);
279 let dp = legendre_polynomial_derivative(n - 1, x);
280 let h = 1e-6;
283 let dp_plus = legendre_polynomial_derivative(n - 1, x + h);
284 let dp_minus = legendre_polynomial_derivative(n - 1, x - h);
285 let ddp = (dp_plus - dp_minus) / (2.0 * h);
286
287 let f = (1.0 - x * x) * dp;
289 let df = -2.0 * x * dp + (1.0 - x * x) * ddp;
291
292 delta = f / df;
294 x -= delta;
295
296 if delta.abs() < 1e-12 {
297 break;
298 }
299 }
300
301 points[i] = x;
302 }
303
304 let mut weights = Array1::zeros(n);
306 let factor = 2.0 / (n as f64 * (n - 1) as f64);
307
308 for i in 0..n {
309 let x = points[i];
310 let p = legendre_polynomial(n - 1, x);
311 weights[i] = factor / (p * p);
312 }
313
314 (points, weights)
315}
316
317#[allow(dead_code)]
319fn legendre_polynomial(n: usize, x: f64) -> f64 {
320 if n == 0 {
321 return 1.0;
322 }
323 if n == 1 {
324 return x;
325 }
326
327 let mut p_prev = 1.0; let mut p = x; for k in 2..=n {
333 let k_f64 = k as f64;
334 let p_next = ((2.0 * k_f64 - 1.0) * x * p - (k_f64 - 1.0) * p_prev) / k_f64;
335 p_prev = p;
336 p = p_next;
337 }
338
339 p
340}
341
342#[allow(dead_code)]
344fn legendre_polynomial_derivative(n: usize, x: f64) -> f64 {
345 if n == 0 {
346 return 0.0;
347 }
348 if n == 1 {
349 return 1.0;
350 }
351
352 let pn = legendre_polynomial(n, x);
356 let pn_minus_1 = legendre_polynomial(n - 1, x);
357
358 if (1.0 - x * x).abs() < 1e-10 {
360 if x > 0.0 {
361 return n as f64 * (n + 1) as f64 / 2.0;
363 } else {
364 return if n.is_multiple_of(2) { -1.0 } else { 1.0 } * n as f64 * (n + 1) as f64 / 2.0;
366 }
367 }
368
369 n as f64 * (pn_minus_1 - x * pn) / (1.0 - x * x)
370}
371
372#[allow(dead_code)]
377pub fn legendre_diff_matrix(n: usize) -> Array2<f64> {
378 let mut d = Array2::zeros((n, n));
379
380 let (x_, weights) = legendre_points(n);
382
383 for i in 0..n {
385 for j in 0..n {
386 if i != j {
387 let p_i = legendre_polynomial(n - 1, x_[i]);
388 let p_j = legendre_polynomial(n - 1, x_[j]);
389
390 d[[i, j]] = p_i / (p_j * (x_[i] - x_[j]));
391
392 if j == 0 || j == n - 1 {
393 d[[i, j]] *= 2.0;
394 }
395 }
396 }
397 }
398
399 for i in 0..n {
401 d[[i, i]] = 0.0;
402 for j in 0..n {
403 if i != j {
404 d[[i, i]] -= d[[i, j]];
405 }
406 }
407 }
408
409 d
410}
411
412#[allow(dead_code)]
414pub fn legendre_diff2_matrix(n: usize) -> Array2<f64> {
415 let d1 = legendre_diff_matrix(n);
416 d1.dot(&d1) }
418
419#[allow(dead_code)]
421pub fn legendre_transform(u: &ArrayView1<f64>) -> Array1<f64> {
422 let n = u.len();
423 let mut coeffs = Array1::zeros(n);
424
425 let (x, w) = legendre_points(n);
427
428 for k in 0..n {
430 let mut sum = 0.0;
431 for j in 0..n {
432 let p_k = legendre_polynomial(k, x[j]);
433 sum += u[j] * p_k * w[j];
434 }
435
436 let norm = k as f64 + 0.5;
438 coeffs[k] = sum * norm;
439 }
440
441 coeffs
442}
443
444#[allow(dead_code)]
446pub fn legendre_inverse_transform(
447 coeffs: &ArrayView1<f64>,
448 x_points: Option<&ArrayView1<f64>>,
449) -> Array1<f64> {
450 let n = coeffs.len();
451 let mut u = Array1::zeros(n);
452
453 let x = if let Some(points) = x_points {
455 points.to_owned()
456 } else {
457 legendre_points(n).0
458 };
459
460 for j in 0..n {
462 let mut sum = 0.0;
463 for k in 0..n {
464 let norm = 1.0 / (k as f64 + 0.5);
466 sum += coeffs[k] * norm * legendre_polynomial(k, x[j]);
467 }
468 u[j] = sum;
469 }
470
471 u
472}
473
474pub struct FourierSpectralSolver1D {
476 domain: Domain,
478
479 source_term: Box<dyn Fn(f64) -> f64 + Send + Sync>,
481
482 #[allow(dead_code)]
484 boundary_conditions: Vec<BoundaryCondition<f64>>,
485
486 options: SpectralOptions,
488}
489
490impl FourierSpectralSolver1D {
491 pub fn new(
493 domain: Domain,
494 source_term: impl Fn(f64) -> f64 + Send + Sync + 'static,
495 boundary_conditions: Vec<BoundaryCondition<f64>>,
496 options: Option<SpectralOptions>,
497 ) -> PDEResult<Self> {
498 if domain.dimensions() != 1 {
500 return Err(PDEError::DomainError(
501 "Domain must be 1-dimensional for 1D Fourier spectral solver".to_string(),
502 ));
503 }
504
505 let mut has_periodic = false;
507 for bc in &boundary_conditions {
508 if bc.bc_type == BoundaryConditionType::Periodic {
509 has_periodic = true;
510 break;
511 }
512 }
513
514 if !has_periodic {
515 return Err(PDEError::BoundaryConditions(
516 "Fourier spectral methods require periodic boundary _conditions".to_string(),
517 ));
518 }
519
520 let mut options = options.unwrap_or_default();
521 options.basis = SpectralBasis::Fourier; Ok(FourierSpectralSolver1D {
524 domain,
525 source_term: Box::new(source_term),
526 boundary_conditions,
527 options,
528 })
529 }
530
531 pub fn solve(&self) -> PDEResult<SpectralResult> {
535 let start_time = Instant::now();
536
537 let range = &self.domain.ranges[0];
539 let length = range.end - range.start;
540 let n = self.options.num_modes;
541
542 let mut grid = Array1::zeros(n);
544 for i in 0..n {
545 grid[i] = range.start + i as f64 * length / n as f64;
546 }
547
548 let mut f_values = Array1::zeros(n);
550 for (i, &x) in grid.iter().enumerate() {
551 f_values[i] = (self.source_term)(x);
552 }
553
554 let f_hat = if self.options.use_real_transform {
556 rfft(&f_values.to_owned())
557 } else {
558 fft(&f_values.to_owned())
559 };
560
561 let n_freq = if self.options.use_real_transform {
563 n / 2 + 1
564 } else {
565 n
566 };
567 let mut k = Array1::zeros(n_freq);
568
569 for i in 0..n_freq {
570 if i <= n / 2 {
571 k[i] = 2.0 * PI * i as f64 / length;
572 } else {
573 k[i] = -2.0 * PI * (n - i) as f64 / length;
574 }
575 }
576
577 let mut u_hat =
579 Array1::from_elem(f_hat.len(), scirs2_core::numeric::Complex::new(0.0, 0.0));
580
581 for i in 0..n_freq {
582 if i == 0 {
583 u_hat[i] = scirs2_core::numeric::Complex::new(0.0, 0.0);
587 } else {
588 u_hat[i] = -f_hat[i] / (k[i] * k[i]);
590 }
591 }
592
593 if self.options.use_dealiasing {
595 let cutoff = 2 * n / 3;
596 for i in cutoff..n_freq {
597 u_hat[i] = scirs2_core::numeric::Complex::new(0.0, 0.0);
598 }
599 }
600
601 let u = if self.options.use_real_transform {
603 irfft(&u_hat)
604 } else {
605 ifft(&u_hat).mapv(|c| c.re)
606 };
607
608 let mut residual = Array1::zeros(n);
610
611 let u_xx = if self.options.use_real_transform {
613 let mut u_xx_hat = Array1::zeros(u_hat.len());
615 for i in 0..n_freq {
616 u_xx_hat[i] = -k[i] * k[i] * u_hat[i];
617 }
618 irfft(&u_xx_hat)
619 } else {
620 let mut u_xx_hat = Array1::zeros(u_hat.len());
622 for i in 0..n_freq {
623 u_xx_hat[i] = -k[i] * k[i] * u_hat[i];
624 }
625 ifft(&u_xx_hat).mapv(|c| c.re)
626 };
627
628 for i in 0..n {
630 residual[i] = u_xx[i] - f_values[i];
631 }
632
633 let residual_norm = (residual.mapv(|r| r * r).sum() / n as f64).sqrt();
635
636 let computation_time = start_time.elapsed().as_secs_f64();
637
638 Ok(SpectralResult {
639 u,
640 coefficients: u_hat.mapv(|c| c.re),
641 grid,
642 residual_norm,
643 num_iterations: 1, computation_time,
645 convergence_history: None,
646 })
647 }
648}
649
650pub struct ChebyshevSpectralSolver1D {
652 domain: Domain,
654
655 source_term: Box<dyn Fn(f64) -> f64 + Send + Sync>,
657
658 boundary_conditions: Vec<BoundaryCondition<f64>>,
660
661 options: SpectralOptions,
663}
664
665impl ChebyshevSpectralSolver1D {
666 pub fn new(
668 domain: Domain,
669 source_term: impl Fn(f64) -> f64 + Send + Sync + 'static,
670 boundary_conditions: Vec<BoundaryCondition<f64>>,
671 options: Option<SpectralOptions>,
672 ) -> PDEResult<Self> {
673 if domain.dimensions() != 1 {
675 return Err(PDEError::DomainError(
676 "Domain must be 1-dimensional for 1D Chebyshev spectral solver".to_string(),
677 ));
678 }
679
680 if boundary_conditions.len() != 2 {
682 return Err(PDEError::BoundaryConditions(
683 "1D Chebyshev spectral solver requires exactly 2 boundary _conditions".to_string(),
684 ));
685 }
686
687 let mut has_lower = false;
688 let mut has_upper = false;
689
690 for bc in &boundary_conditions {
691 match bc.location {
692 BoundaryLocation::Lower => has_lower = true,
693 BoundaryLocation::Upper => has_upper = true,
694 }
695
696 if bc.bc_type == BoundaryConditionType::Periodic {
698 return Err(PDEError::BoundaryConditions(
699 "Chebyshev spectral methods are not suitable for periodic boundary _conditions"
700 .to_string(),
701 ));
702 }
703 }
704
705 if !has_lower || !has_upper {
706 return Err(PDEError::BoundaryConditions(
707 "Chebyshev spectral solver requires boundary _conditions at both domain endpoints"
708 .to_string(),
709 ));
710 }
711
712 let mut options = options.unwrap_or_default();
713 options.basis = SpectralBasis::Chebyshev; Ok(ChebyshevSpectralSolver1D {
716 domain,
717 source_term: Box::new(source_term),
718 boundary_conditions,
719 options,
720 })
721 }
722
723 pub fn solve(&self) -> PDEResult<SpectralResult> {
727 let start_time = Instant::now();
728
729 let range = &self.domain.ranges[0];
731 let a = range.start;
732 let b = range.end;
733
734 let n = self.options.num_modes;
736
737 let mut cheb_grid = Array1::zeros(n);
739 for j in 0..n {
740 cheb_grid[j] = (j as f64 * PI / (n - 1) as f64).cos();
741 }
742
743 let mut grid = Array1::zeros(n);
745 for j in 0..n {
746 grid[j] = a + (b - a) * (cheb_grid[j] + 1.0) / 2.0;
747 }
748
749 let mut f_values = Array1::zeros(n);
751 for j in 0..n {
752 f_values[j] = (self.source_term)(grid[j]);
753 }
754
755 let mut d2 = chebyshev_diff2_matrix(n);
757
758 let scale = 4.0 / ((b - a) * (b - a));
760 d2.mapv_inplace(|x| x * scale);
761
762 let mut a_matrix = d2;
764 let mut rhs = f_values;
765
766 for bc in &self.boundary_conditions {
768 match bc.location {
769 BoundaryLocation::Lower => {
770 let j = 0; match bc.bc_type {
774 BoundaryConditionType::Dirichlet => {
775 for k in 0..n {
777 a_matrix[[j, k]] = 0.0;
778 }
779 a_matrix[[j, j]] = 1.0;
780 rhs[j] = bc.value;
781 }
782 BoundaryConditionType::Neumann => {
783 let d1 = chebyshev_diff_matrix(n);
786
787 let deriv_scale = 2.0 / (b - a);
789
790 for k in 0..n {
791 a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
792 }
793 rhs[j] = bc.value;
794 }
795 BoundaryConditionType::Robin => {
796 if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
798 let d1 = chebyshev_diff_matrix(n);
800
801 let deriv_scale = 2.0 / (b - a);
803
804 for k in 0..n {
805 a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
806 }
807 rhs[j] = c_coef;
808 }
809 }
810 _ => {
811 return Err(PDEError::BoundaryConditions(
812 "Unsupported boundary condition type for Chebyshev spectral method"
813 .to_string(),
814 ))
815 }
816 }
817 }
818 BoundaryLocation::Upper => {
819 let j = n - 1; match bc.bc_type {
823 BoundaryConditionType::Dirichlet => {
824 for k in 0..n {
826 a_matrix[[j, k]] = 0.0;
827 }
828 a_matrix[[j, j]] = 1.0;
829 rhs[j] = bc.value;
830 }
831 BoundaryConditionType::Neumann => {
832 let d1 = chebyshev_diff_matrix(n);
835
836 let deriv_scale = 2.0 / (b - a);
838
839 for k in 0..n {
840 a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
841 }
842 rhs[j] = bc.value;
843 }
844 BoundaryConditionType::Robin => {
845 if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
847 let d1 = chebyshev_diff_matrix(n);
849
850 let deriv_scale = 2.0 / (b - a);
852
853 for k in 0..n {
854 a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
855 }
856 rhs[j] = c_coef;
857 }
858 }
859 _ => {
860 return Err(PDEError::BoundaryConditions(
861 "Unsupported boundary condition type for Chebyshev spectral method"
862 .to_string(),
863 ))
864 }
865 }
866 }
867 }
868 }
869
870 let u = ChebyshevSpectralSolver1D::solve_linear_system(&a_matrix, &rhs.view())?;
872
873 let coefficients = chebyshev_transform(&u.view());
875
876 let mut residual = Array1::zeros(n);
878 let a_times_u = a_matrix.dot(&u);
879
880 for i in 0..n {
881 residual[i] = a_times_u[i] - rhs[i];
882 }
883
884 let mut residual_norm = 0.0;
886 for i in 1..n - 1 {
887 residual_norm += residual[i] * residual[i];
888 }
889 residual_norm = (residual_norm / (n - 2) as f64).sqrt();
890
891 let computation_time = start_time.elapsed().as_secs_f64();
892
893 Ok(SpectralResult {
894 u,
895 coefficients,
896 grid,
897 residual_norm,
898 num_iterations: 1, computation_time,
900 convergence_history: None,
901 })
902 }
903
904 fn solve_linear_system(a: &Array2<f64>, b: &ArrayView1<f64>) -> PDEResult<Array1<f64>> {
906 let n = b.len();
907
908 let mut a_copy = a.clone();
913 let mut b_copy = b.to_owned();
914
915 for i in 0..n {
917 let mut max_val = a_copy[[i, i]].abs();
919 let mut max_row = i;
920
921 for k in i + 1..n {
922 if a_copy[[k, i]].abs() > max_val {
923 max_val = a_copy[[k, i]].abs();
924 max_row = k;
925 }
926 }
927
928 if max_val < 1e-10 {
930 return Err(PDEError::Other(
931 "Matrix is singular or nearly singular".to_string(),
932 ));
933 }
934
935 if max_row != i {
937 for j in i..n {
938 let temp = a_copy[[i, j]];
939 a_copy[[i, j]] = a_copy[[max_row, j]];
940 a_copy[[max_row, j]] = temp;
941 }
942
943 let temp = b_copy[i];
944 b_copy[i] = b_copy[max_row];
945 b_copy[max_row] = temp;
946 }
947
948 for k in i + 1..n {
950 let factor = a_copy[[k, i]] / a_copy[[i, i]];
951
952 for j in i..n {
953 a_copy[[k, j]] -= factor * a_copy[[i, j]];
954 }
955
956 b_copy[k] -= factor * b_copy[i];
957 }
958 }
959
960 let mut x = Array1::zeros(n);
962 for i in (0..n).rev() {
963 let mut sum = 0.0;
964 for j in i + 1..n {
965 sum += a_copy[[i, j]] * x[j];
966 }
967
968 x[i] = (b_copy[i] - sum) / a_copy[[i, i]];
969 }
970
971 Ok(x)
972 }
973}
974
975pub struct LegendreSpectralSolver1D {
978 domain: Domain,
980
981 source_term: Box<dyn Fn(f64) -> f64 + Send + Sync>,
983
984 boundary_conditions: Vec<BoundaryCondition<f64>>,
986
987 options: SpectralOptions,
989}
990
991impl LegendreSpectralSolver1D {
992 pub fn new(
994 domain: Domain,
995 source_term: impl Fn(f64) -> f64 + Send + Sync + 'static,
996 boundary_conditions: Vec<BoundaryCondition<f64>>,
997 options: Option<SpectralOptions>,
998 ) -> PDEResult<Self> {
999 if domain.dimensions() != 1 {
1001 return Err(PDEError::DomainError(
1002 "Domain must be 1-dimensional for 1D Legendre spectral solver".to_string(),
1003 ));
1004 }
1005
1006 if boundary_conditions.len() != 2 {
1008 return Err(PDEError::BoundaryConditions(
1009 "1D Legendre spectral solver requires exactly 2 boundary _conditions".to_string(),
1010 ));
1011 }
1012
1013 let mut has_lower = false;
1014 let mut has_upper = false;
1015
1016 for bc in &boundary_conditions {
1017 match bc.location {
1018 BoundaryLocation::Lower => has_lower = true,
1019 BoundaryLocation::Upper => has_upper = true,
1020 }
1021
1022 if bc.bc_type == BoundaryConditionType::Periodic {
1024 return Err(PDEError::BoundaryConditions(
1025 "Legendre spectral methods are not suitable for periodic boundary _conditions"
1026 .to_string(),
1027 ));
1028 }
1029 }
1030
1031 if !has_lower || !has_upper {
1032 return Err(PDEError::BoundaryConditions(
1033 "Legendre spectral solver requires boundary _conditions at both domain endpoints"
1034 .to_string(),
1035 ));
1036 }
1037
1038 let mut options = options.unwrap_or_default();
1039 options.basis = SpectralBasis::Legendre; Ok(LegendreSpectralSolver1D {
1042 domain,
1043 source_term: Box::new(source_term),
1044 boundary_conditions,
1045 options,
1046 })
1047 }
1048
1049 pub fn solve(&self) -> PDEResult<SpectralResult> {
1053 let start_time = Instant::now();
1054
1055 let range = &self.domain.ranges[0];
1057 let a = range.start;
1058 let b = range.end;
1059
1060 let n = self.options.num_modes;
1062
1063 let (lgb_grid_, weights) = legendre_points(n);
1065
1066 let mut grid = Array1::zeros(n);
1068 for j in 0..n {
1069 grid[j] = a + (b - a) * (lgb_grid_[j] + 1.0) / 2.0;
1070 }
1071
1072 let mut f_values = Array1::zeros(n);
1074 for j in 0..n {
1075 f_values[j] = (self.source_term)(grid[j]);
1076 }
1077
1078 let mut d2 = legendre_diff2_matrix(n);
1080
1081 let scale = 4.0 / ((b - a) * (b - a));
1083 d2.mapv_inplace(|x| x * scale);
1084
1085 let mut a_matrix = d2;
1087 let mut rhs = f_values;
1088
1089 for bc in &self.boundary_conditions {
1091 match bc.location {
1092 BoundaryLocation::Lower => {
1093 let j = 0;
1095
1096 match bc.bc_type {
1097 BoundaryConditionType::Dirichlet => {
1098 for k in 0..n {
1100 a_matrix[[j, k]] = 0.0;
1101 }
1102 a_matrix[[j, j]] = 1.0;
1103 rhs[j] = bc.value;
1104 }
1105 BoundaryConditionType::Neumann => {
1106 let d1 = legendre_diff_matrix(n);
1109
1110 let deriv_scale = 2.0 / (b - a);
1112
1113 for k in 0..n {
1114 a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
1115 }
1116 rhs[j] = bc.value;
1117 }
1118 BoundaryConditionType::Robin => {
1119 if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
1121 let d1 = legendre_diff_matrix(n);
1123
1124 let deriv_scale = 2.0 / (b - a);
1126
1127 for k in 0..n {
1128 a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
1129 }
1130 rhs[j] = c_coef;
1131 }
1132 }
1133 _ => {
1134 return Err(PDEError::BoundaryConditions(
1135 "Unsupported boundary condition type for Legendre spectral method"
1136 .to_string(),
1137 ))
1138 }
1139 }
1140 }
1141 BoundaryLocation::Upper => {
1142 let j = n - 1;
1144
1145 match bc.bc_type {
1146 BoundaryConditionType::Dirichlet => {
1147 for k in 0..n {
1149 a_matrix[[j, k]] = 0.0;
1150 }
1151 a_matrix[[j, j]] = 1.0;
1152 rhs[j] = bc.value;
1153 }
1154 BoundaryConditionType::Neumann => {
1155 let d1 = legendre_diff_matrix(n);
1158
1159 let deriv_scale = 2.0 / (b - a);
1161
1162 for k in 0..n {
1163 a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
1164 }
1165 rhs[j] = bc.value;
1166 }
1167 BoundaryConditionType::Robin => {
1168 if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
1170 let d1 = legendre_diff_matrix(n);
1172
1173 let deriv_scale = 2.0 / (b - a);
1175
1176 for k in 0..n {
1177 a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
1178 }
1179 rhs[j] = c_coef;
1180 }
1181 }
1182 _ => {
1183 return Err(PDEError::BoundaryConditions(
1184 "Unsupported boundary condition type for Legendre spectral method"
1185 .to_string(),
1186 ))
1187 }
1188 }
1189 }
1190 }
1191 }
1192
1193 let u = LegendreSpectralSolver1D::solve_linear_system(&a_matrix, &rhs.view())?;
1195
1196 let coefficients = legendre_transform(&u.view());
1198
1199 let mut residual = Array1::zeros(n);
1201 let a_times_u = a_matrix.dot(&u);
1202
1203 for i in 0..n {
1204 residual[i] = a_times_u[i] - rhs[i];
1205 }
1206
1207 let mut residual_norm = 0.0;
1209 for i in 1..n - 1 {
1210 residual_norm += residual[i] * residual[i];
1211 }
1212 residual_norm = (residual_norm / (n - 2) as f64).sqrt();
1213
1214 let computation_time = start_time.elapsed().as_secs_f64();
1215
1216 Ok(SpectralResult {
1217 u,
1218 coefficients,
1219 grid,
1220 residual_norm,
1221 num_iterations: 1, computation_time,
1223 convergence_history: None,
1224 })
1225 }
1226
1227 fn solve_linear_system(a: &Array2<f64>, b: &ArrayView1<f64>) -> PDEResult<Array1<f64>> {
1229 let n = b.len();
1230
1231 let mut a_copy = a.clone();
1236 let mut b_copy = b.to_owned();
1237
1238 for i in 0..n {
1240 let mut max_val = a_copy[[i, i]].abs();
1242 let mut max_row = i;
1243
1244 for k in i + 1..n {
1245 if a_copy[[k, i]].abs() > max_val {
1246 max_val = a_copy[[k, i]].abs();
1247 max_row = k;
1248 }
1249 }
1250
1251 if max_val < 1e-10 {
1253 return Err(PDEError::Other(
1254 "Matrix is singular or nearly singular".to_string(),
1255 ));
1256 }
1257
1258 if max_row != i {
1260 for j in i..n {
1261 let temp = a_copy[[i, j]];
1262 a_copy[[i, j]] = a_copy[[max_row, j]];
1263 a_copy[[max_row, j]] = temp;
1264 }
1265
1266 let temp = b_copy[i];
1267 b_copy[i] = b_copy[max_row];
1268 b_copy[max_row] = temp;
1269 }
1270
1271 for k in i + 1..n {
1273 let factor = a_copy[[k, i]] / a_copy[[i, i]];
1274
1275 for j in i..n {
1276 a_copy[[k, j]] -= factor * a_copy[[i, j]];
1277 }
1278
1279 b_copy[k] -= factor * b_copy[i];
1280 }
1281 }
1282
1283 let mut x = Array1::zeros(n);
1285 for i in (0..n).rev() {
1286 let mut sum = 0.0;
1287 for j in i + 1..n {
1288 sum += a_copy[[i, j]] * x[j];
1289 }
1290
1291 x[i] = (b_copy[i] - sum) / a_copy[[i, i]];
1292 }
1293
1294 Ok(x)
1295 }
1296}
1297
1298impl From<SpectralResult> for PDESolution<f64> {
1299 fn from(result: SpectralResult) -> Self {
1300 let grids = vec![result.grid.clone()];
1301
1302 let mut values = Vec::new();
1304 let u_clone = result.u.clone();
1306 let u_len = u_clone.len();
1307 let u_reshaped = u_clone
1308 .into_shape_with_order((u_len, 1))
1309 .expect("Operation failed");
1310 values.push(u_reshaped);
1311
1312 let info = PDESolverInfo {
1314 num_iterations: result.num_iterations,
1315 computation_time: result.computation_time,
1316 residual_norm: Some(result.residual_norm),
1317 convergence_history: result.convergence_history,
1318 method: "Spectral Method".to_string(),
1319 };
1320
1321 PDESolution {
1322 grids,
1323 values,
1324 error_estimate: None,
1325 info,
1326 }
1327 }
1328}
1329
1330#[allow(dead_code)]
1342fn fft(x: &Array1<f64>) -> Array1<scirs2_core::numeric::Complex<f64>> {
1343 let mut input: Vec<scirs2_core::numeric::Complex<f64>> = x
1345 .iter()
1346 .map(|&val| scirs2_core::numeric::Complex::new(val, 0.0))
1347 .collect();
1348
1349 fft_complex(&mut input);
1351
1352 Array1::from_vec(input)
1354}
1355
1356#[allow(dead_code)]
1358fn fft_complex(x: &mut [scirs2_core::numeric::Complex<f64>]) {
1359 let n = x.len();
1360
1361 if n <= 1 {
1362 return;
1363 }
1364
1365 if n.is_power_of_two() {
1367 fft_radix2(x);
1368 } else {
1369 fft_mixed_radix(x);
1371 }
1372}
1373
1374#[allow(dead_code)]
1376fn fft_radix2(x: &mut [scirs2_core::numeric::Complex<f64>]) {
1377 let n = x.len();
1378
1379 if n <= 1 {
1380 return;
1381 }
1382
1383 let mut j = 0;
1385 for i in 1..n {
1386 let mut bit = n >> 1;
1387 while j & bit != 0 {
1388 j ^= bit;
1389 bit >>= 1;
1390 }
1391 j ^= bit;
1392
1393 if j > i {
1394 x.swap(i, j);
1395 }
1396 }
1397
1398 let mut length = 2;
1400 while length <= n {
1401 let angle = -2.0 * PI / (length as f64);
1402 let wlen = scirs2_core::numeric::Complex::new(angle.cos(), angle.sin());
1403
1404 for i in (0..n).step_by(length) {
1405 let mut w = scirs2_core::numeric::Complex::new(1.0, 0.0);
1406
1407 for j in 0..length / 2 {
1408 let u = x[i + j];
1409 let v = x[i + j + length / 2] * w;
1410
1411 x[i + j] = u + v;
1412 x[i + j + length / 2] = u - v;
1413
1414 w *= wlen;
1415 }
1416 }
1417
1418 length <<= 1;
1419 }
1420}
1421
1422#[allow(dead_code)]
1424fn fft_mixed_radix(x: &mut [scirs2_core::numeric::Complex<f64>]) {
1425 let n = x.len();
1426
1427 if n < 32 || !n.is_power_of_two() {
1429 let input = x.to_vec();
1430 for k in 0..n {
1431 let mut sum = scirs2_core::numeric::Complex::new(0.0, 0.0);
1432 for j in 0..n {
1433 let angle = -2.0 * PI * (j as f64) * (k as f64) / (n as f64);
1434 let factor = scirs2_core::numeric::Complex::new(angle.cos(), angle.sin());
1435 sum += factor * input[j];
1436 }
1437 x[k] = sum;
1438 }
1439 } else {
1440 fft_radix2(x);
1441 }
1442}
1443
1444#[allow(dead_code)]
1452fn ifft(
1453 x: &Array1<scirs2_core::numeric::Complex<f64>>,
1454) -> Array1<scirs2_core::numeric::Complex<f64>> {
1455 let n = x.len();
1456 let mut input: Vec<scirs2_core::numeric::Complex<f64>> = x.to_vec();
1457
1458 for val in &mut input {
1460 *val = val.conj();
1461 }
1462
1463 fft_complex(&mut input);
1465
1466 let scale = 1.0 / (n as f64);
1468 for val in &mut input {
1469 *val = val.conj() * scale;
1470 }
1471
1472 Array1::from_vec(input)
1473}
1474
1475#[allow(dead_code)]
1483fn rfft(x: &Array1<f64>) -> Array1<scirs2_core::numeric::Complex<f64>> {
1484 let n = x.len();
1485 let full_fft = fft(x);
1486
1487 let rfft_size = n / 2 + 1;
1490 let mut result = Array1::zeros(rfft_size);
1491
1492 for i in 0..rfft_size {
1493 result[i] = full_fft[i];
1494 }
1495
1496 result
1497}
1498
1499#[allow(dead_code)]
1508fn irfft_with_size(x: &Array1<scirs2_core::numeric::Complex<f64>>, n: usize) -> Array1<f64> {
1509 let mut full_spectrum = Array1::zeros(n);
1511 let rfft_size = x.len();
1512
1513 for i in 0..rfft_size {
1515 full_spectrum[i] = x[i];
1516 }
1517
1518 for i in 1..n / 2 {
1520 full_spectrum[n - i] = x[i].conj();
1521 }
1522
1523 let complex_result = ifft(&full_spectrum);
1525
1526 let mut result = Array1::zeros(n);
1528 for i in 0..n {
1529 result[i] = complex_result[i].re;
1530 }
1531
1532 result
1533}
1534
1535#[allow(dead_code)]
1543fn irfft(x: &Array1<scirs2_core::numeric::Complex<f64>>) -> Array1<f64> {
1544 let n = 2 * (x.len() - 1);
1547 irfft_with_size(x, n)
1548}
1549
1550pub mod spectral_element;
1552pub use spectral_element::{
1553 QuadElement, SpectralElementMesh2D, SpectralElementOptions, SpectralElementPoisson2D,
1554 SpectralElementResult,
1555};