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).unwrap();
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.into_shape_with_order((u_len, 1)).unwrap();
1308 values.push(u_reshaped);
1309
1310 let info = PDESolverInfo {
1312 num_iterations: result.num_iterations,
1313 computation_time: result.computation_time,
1314 residual_norm: Some(result.residual_norm),
1315 convergence_history: result.convergence_history,
1316 method: "Spectral Method".to_string(),
1317 };
1318
1319 PDESolution {
1320 grids,
1321 values,
1322 error_estimate: None,
1323 info,
1324 }
1325 }
1326}
1327
1328#[allow(dead_code)]
1340fn fft(x: &Array1<f64>) -> Array1<scirs2_core::numeric::Complex<f64>> {
1341 let mut input: Vec<scirs2_core::numeric::Complex<f64>> = x
1343 .iter()
1344 .map(|&val| scirs2_core::numeric::Complex::new(val, 0.0))
1345 .collect();
1346
1347 fft_complex(&mut input);
1349
1350 Array1::from_vec(input)
1352}
1353
1354#[allow(dead_code)]
1356fn fft_complex(x: &mut [scirs2_core::numeric::Complex<f64>]) {
1357 let n = x.len();
1358
1359 if n <= 1 {
1360 return;
1361 }
1362
1363 if n.is_power_of_two() {
1365 fft_radix2(x);
1366 } else {
1367 fft_mixed_radix(x);
1369 }
1370}
1371
1372#[allow(dead_code)]
1374fn fft_radix2(x: &mut [scirs2_core::numeric::Complex<f64>]) {
1375 let n = x.len();
1376
1377 if n <= 1 {
1378 return;
1379 }
1380
1381 let mut j = 0;
1383 for i in 1..n {
1384 let mut bit = n >> 1;
1385 while j & bit != 0 {
1386 j ^= bit;
1387 bit >>= 1;
1388 }
1389 j ^= bit;
1390
1391 if j > i {
1392 x.swap(i, j);
1393 }
1394 }
1395
1396 let mut length = 2;
1398 while length <= n {
1399 let angle = -2.0 * PI / (length as f64);
1400 let wlen = scirs2_core::numeric::Complex::new(angle.cos(), angle.sin());
1401
1402 for i in (0..n).step_by(length) {
1403 let mut w = scirs2_core::numeric::Complex::new(1.0, 0.0);
1404
1405 for j in 0..length / 2 {
1406 let u = x[i + j];
1407 let v = x[i + j + length / 2] * w;
1408
1409 x[i + j] = u + v;
1410 x[i + j + length / 2] = u - v;
1411
1412 w *= wlen;
1413 }
1414 }
1415
1416 length <<= 1;
1417 }
1418}
1419
1420#[allow(dead_code)]
1422fn fft_mixed_radix(x: &mut [scirs2_core::numeric::Complex<f64>]) {
1423 let n = x.len();
1424
1425 if n < 32 || !n.is_power_of_two() {
1427 let input = x.to_vec();
1428 for k in 0..n {
1429 let mut sum = scirs2_core::numeric::Complex::new(0.0, 0.0);
1430 for j in 0..n {
1431 let angle = -2.0 * PI * (j as f64) * (k as f64) / (n as f64);
1432 let factor = scirs2_core::numeric::Complex::new(angle.cos(), angle.sin());
1433 sum += factor * input[j];
1434 }
1435 x[k] = sum;
1436 }
1437 } else {
1438 fft_radix2(x);
1439 }
1440}
1441
1442#[allow(dead_code)]
1450fn ifft(
1451 x: &Array1<scirs2_core::numeric::Complex<f64>>,
1452) -> Array1<scirs2_core::numeric::Complex<f64>> {
1453 let n = x.len();
1454 let mut input: Vec<scirs2_core::numeric::Complex<f64>> = x.to_vec();
1455
1456 for val in &mut input {
1458 *val = val.conj();
1459 }
1460
1461 fft_complex(&mut input);
1463
1464 let scale = 1.0 / (n as f64);
1466 for val in &mut input {
1467 *val = val.conj() * scale;
1468 }
1469
1470 Array1::from_vec(input)
1471}
1472
1473#[allow(dead_code)]
1481fn rfft(x: &Array1<f64>) -> Array1<scirs2_core::numeric::Complex<f64>> {
1482 let n = x.len();
1483 let full_fft = fft(x);
1484
1485 let rfft_size = n / 2 + 1;
1488 let mut result = Array1::zeros(rfft_size);
1489
1490 for i in 0..rfft_size {
1491 result[i] = full_fft[i];
1492 }
1493
1494 result
1495}
1496
1497#[allow(dead_code)]
1506fn irfft_with_size(x: &Array1<scirs2_core::numeric::Complex<f64>>, n: usize) -> Array1<f64> {
1507 let mut full_spectrum = Array1::zeros(n);
1509 let rfft_size = x.len();
1510
1511 for i in 0..rfft_size {
1513 full_spectrum[i] = x[i];
1514 }
1515
1516 for i in 1..n / 2 {
1518 full_spectrum[n - i] = x[i].conj();
1519 }
1520
1521 let complex_result = ifft(&full_spectrum);
1523
1524 let mut result = Array1::zeros(n);
1526 for i in 0..n {
1527 result[i] = complex_result[i].re;
1528 }
1529
1530 result
1531}
1532
1533#[allow(dead_code)]
1541fn irfft(x: &Array1<scirs2_core::numeric::Complex<f64>>) -> Array1<f64> {
1542 let n = 2 * (x.len() - 1);
1545 irfft_with_size(x, n)
1546}
1547
1548pub mod spectral_element;
1550pub use spectral_element::{
1551 QuadElement, SpectralElementMesh2D, SpectralElementOptions, SpectralElementPoisson2D,
1552 SpectralElementResult,
1553};