oxilean_std/spectral_methods/
types.rs1use super::functions::*;
6use std::f64::consts::PI;
7
8#[allow(dead_code)]
10pub struct HpAdaptiveSpectral {
11 pub num_elements: usize,
13 pub degrees: Vec<usize>,
15 pub breakpoints: Vec<f64>,
17 pub local_solutions: Vec<Vec<f64>>,
19}
20#[allow(dead_code)]
21impl HpAdaptiveSpectral {
22 pub fn uniform(a: f64, b: f64, num_elements: usize, p: usize) -> Self {
24 let h = (b - a) / num_elements as f64;
25 let breakpoints: Vec<f64> = (0..=num_elements).map(|i| a + i as f64 * h).collect();
26 let degrees = vec![p; num_elements];
27 let local_solutions = vec![vec![0.0f64; p + 1]; num_elements];
28 Self {
29 num_elements,
30 degrees,
31 breakpoints,
32 local_solutions,
33 }
34 }
35 pub fn total_dof(&self) -> usize {
37 self.degrees.iter().map(|&p| p + 1).sum()
38 }
39 pub fn h_refine(&mut self, k: usize) {
41 if k >= self.num_elements {
42 return;
43 }
44 let mid = 0.5 * (self.breakpoints[k] + self.breakpoints[k + 1]);
45 let deg = self.degrees[k];
46 let sol = self.local_solutions[k].clone();
47 self.breakpoints.insert(k + 1, mid);
48 self.degrees.remove(k);
49 self.degrees.insert(k, deg);
50 self.degrees.insert(k + 1, deg);
51 self.local_solutions.remove(k);
52 self.local_solutions.insert(k, sol.clone());
53 self.local_solutions.insert(k + 1, sol);
54 self.num_elements += 1;
55 }
56 pub fn p_refine(&mut self, k: usize) {
58 if k >= self.num_elements {
59 return;
60 }
61 self.degrees[k] += 1;
62 self.local_solutions[k].push(0.0);
63 }
64 pub fn element_size(&self, k: usize) -> f64 {
66 if k >= self.num_elements {
67 return 0.0;
68 }
69 self.breakpoints[k + 1] - self.breakpoints[k]
70 }
71}
72pub struct RadialBasisFunction {
74 pub function_type: String,
76 pub shape_param: f64,
78}
79impl RadialBasisFunction {
80 pub fn new(function_type: impl Into<String>, shape_param: f64) -> Self {
82 Self {
83 function_type: function_type.into(),
84 shape_param,
85 }
86 }
87 pub fn phi(&self, r: f64) -> f64 {
89 let eps = self.shape_param;
90 match self.function_type.to_lowercase().as_str() {
91 "gaussian" => (-(eps * r).powi(2)).exp(),
92 "multiquadric" => (1.0 + (eps * r).powi(2)).sqrt(),
93 "inverse_multiquadric" => 1.0 / (1.0 + (eps * r).powi(2)).sqrt(),
94 "thin_plate" => {
95 if r < 1e-14 {
96 0.0
97 } else {
98 r * r * r.ln()
99 }
100 }
101 _ => (-(eps * r).powi(2)).exp(),
102 }
103 }
104 pub fn interpolate(&self, centers: &[f64], values: &[f64], query: &[f64]) -> Vec<f64> {
108 let n = centers.len();
109 assert_eq!(values.len(), n);
110 let mut phi_mat = vec![vec![0.0_f64; n]; n];
111 for i in 0..n {
112 for j in 0..n {
113 phi_mat[i][j] = self.phi((centers[i] - centers[j]).abs());
114 }
115 }
116 let coeffs = solve_linear(&phi_mat, values);
117 query
118 .iter()
119 .map(|&x| {
120 coeffs
121 .iter()
122 .zip(centers.iter())
123 .map(|(&c, &xi)| c * self.phi((x - xi).abs()))
124 .sum()
125 })
126 .collect()
127 }
128 pub fn rbf_fd_stencil(&self, center: f64, neighbors: &[f64]) -> Vec<f64> {
130 let n = neighbors.len();
131 let mut phi_mat = vec![vec![0.0_f64; n]; n];
132 for i in 0..n {
133 for j in 0..n {
134 phi_mat[i][j] = self.phi((neighbors[i] - neighbors[j]).abs());
135 }
136 }
137 let rhs: Vec<f64> = neighbors
138 .iter()
139 .map(|&xj| {
140 let r = (center - xj).abs();
141 let sign = if center >= xj { 1.0 } else { -1.0 };
142 let dphi = self.dphi_dr(r);
143 dphi * sign
144 })
145 .collect();
146 solve_linear(&phi_mat, &rhs)
147 }
148 fn dphi_dr(&self, r: f64) -> f64 {
150 let eps = self.shape_param;
151 match self.function_type.to_lowercase().as_str() {
152 "gaussian" => -2.0 * eps * eps * r * (-(eps * r).powi(2)).exp(),
153 "multiquadric" => eps * eps * r / (1.0 + (eps * r).powi(2)).sqrt(),
154 "inverse_multiquadric" => -eps * eps * r / (1.0 + (eps * r).powi(2)).powf(1.5),
155 "thin_plate" => {
156 if r < 1e-14 {
157 0.0
158 } else {
159 r * (1.0 + 2.0 * r.ln())
160 }
161 }
162 _ => -2.0 * eps * eps * r * (-(eps * r).powi(2)).exp(),
163 }
164 }
165}
166pub struct SpectralDecomposition {
168 pub matrix: Vec<Vec<f64>>,
170}
171impl SpectralDecomposition {
172 pub fn new(matrix: Vec<Vec<f64>>) -> Self {
174 Self { matrix }
175 }
176 pub fn eigenvalues(&self) -> Vec<f64> {
180 let n = self.matrix.len();
181 if n == 0 {
182 return vec![];
183 }
184 if n == 1 {
185 return vec![self.matrix[0][0]];
186 }
187 if n == 2 {
188 let a = self.matrix[0][0];
189 let b = self.matrix[0][1];
190 let c = self.matrix[1][0];
191 let d = self.matrix[1][1];
192 let tr = a + d;
193 let det = a * d - b * c;
194 let disc = (tr * tr / 4.0 - det).max(0.0);
195 return vec![tr / 2.0 + disc.sqrt(), tr / 2.0 - disc.sqrt()];
196 }
197 (0..n).map(|i| self.matrix[i][i]).collect()
198 }
199 pub fn eigenvectors(&self) -> Vec<Vec<f64>> {
201 let n = self.matrix.len();
202 (0..n)
203 .map(|i| {
204 let mut row = vec![0.0_f64; n];
205 if i < n {
206 row[i] = 1.0;
207 }
208 row
209 })
210 .collect()
211 }
212 pub fn is_symmetric(&self) -> bool {
214 let n = self.matrix.len();
215 for i in 0..n {
216 if self.matrix[i].len() != n {
217 return false;
218 }
219 for j in 0..n {
220 if (self.matrix[i][j] - self.matrix[j][i]).abs() > 1e-12 {
221 return false;
222 }
223 }
224 }
225 true
226 }
227}
228#[allow(dead_code)]
230pub struct SpectralDiffMatrix {
231 pub degree: usize,
233 pub matrix: Vec<Vec<f64>>,
235 pub nodes: Vec<f64>,
237}
238#[allow(dead_code)]
239impl SpectralDiffMatrix {
240 pub fn chebyshev(degree: usize) -> Self {
242 let nodes = chebyshev_gauss_lobatto_nodes(degree);
243 let matrix = chebyshev_diff_matrix(degree);
244 Self {
245 degree,
246 matrix,
247 nodes,
248 }
249 }
250 pub fn apply(&self, u: &[f64]) -> Vec<f64> {
252 apply_diff_matrix(&self.matrix, u)
253 }
254 pub fn spectral_radius(&self) -> f64 {
256 self.matrix
257 .iter()
258 .map(|row| row.iter().map(|&v| v.abs()).sum::<f64>())
259 .fold(0.0_f64, f64::max)
260 }
261 pub fn apply_second(&self, u: &[f64]) -> Vec<f64> {
263 let du = self.apply(u);
264 self.apply(&du)
265 }
266 pub fn num_points(&self) -> usize {
268 self.degree + 1
269 }
270}
271#[allow(dead_code)]
273pub struct SpectralRadiusEstimator {
274 pub matrix: Vec<Vec<f64>>,
276}
277#[allow(dead_code)]
278impl SpectralRadiusEstimator {
279 pub fn new(matrix: Vec<Vec<f64>>) -> Self {
281 Self { matrix }
282 }
283 pub fn power_iteration(&self, max_iter: usize, tol: f64) -> f64 {
285 let n = self.matrix.len();
286 if n == 0 {
287 return 0.0;
288 }
289 let mut v: Vec<f64> = (0..n).map(|i| if i == 0 { 1.0 } else { 0.0 }).collect();
290 let mut lambda = 1.0f64;
291 for _ in 0..max_iter {
292 let w: Vec<f64> = (0..n)
293 .map(|i| {
294 self.matrix[i]
295 .iter()
296 .zip(v.iter())
297 .map(|(&a, &x)| a * x)
298 .sum()
299 })
300 .collect();
301 let norm: f64 = w.iter().map(|&x| x * x).sum::<f64>().sqrt();
302 if norm < 1e-15 {
303 break;
304 }
305 let new_lambda = norm;
306 if (new_lambda - lambda).abs() < tol {
307 lambda = new_lambda;
308 break;
309 }
310 lambda = new_lambda;
311 v = w.iter().map(|&x| x / norm).collect();
312 }
313 lambda
314 }
315 pub fn gershgorin_bound(&self) -> f64 {
317 self.matrix
318 .iter()
319 .map(|row| row.iter().map(|&v| v.abs()).sum::<f64>())
320 .fold(0.0_f64, f64::max)
321 }
322}
323pub struct ExponentialConvergence {
325 pub smoothness: u32,
327 pub error: f64,
329}
330impl ExponentialConvergence {
331 pub fn new(smoothness: u32, error: f64) -> Self {
333 Self { smoothness, error }
334 }
335 pub fn spectral_accuracy(&self) -> bool {
337 self.smoothness >= 3
338 }
339 pub fn algebraic_vs_spectral(&self, n: usize) -> (f64, f64) {
343 let k = self.smoothness as f64;
344 let alg = 1.0 / (n as f64).powf(k);
345 let spec = (-0.5 * n as f64).exp();
346 (alg, spec)
347 }
348}
349pub struct OrthogonalPolynomials {
351 pub family: String,
353}
354impl OrthogonalPolynomials {
355 pub fn new(family: impl Into<String>) -> Self {
357 Self {
358 family: family.into(),
359 }
360 }
361 pub fn three_term_recurrence(&self, n: usize) -> (f64, f64, f64) {
365 match self.family.to_lowercase().as_str() {
366 "chebyshev" => {
367 if n == 0 {
368 (2.0, 0.0, 1.0)
369 } else {
370 (2.0, 0.0, 1.0)
371 }
372 }
373 "legendre" => {
374 let nn = n as f64;
375 let alpha = (2.0 * nn + 1.0) / (nn + 1.0);
376 let beta = 0.0;
377 let gamma = nn / (nn + 1.0);
378 (alpha, beta, gamma)
379 }
380 "hermite" => (1.0, 0.0, n as f64),
381 "laguerre" => {
382 let nn = n as f64;
383 (
384 -(1.0 / (nn + 1.0)),
385 (2.0 * nn + 1.0) / (nn + 1.0),
386 nn / (nn + 1.0),
387 )
388 }
389 _ => (1.0, 0.0, 1.0),
390 }
391 }
392 pub fn gauss_quadrature_weights(&self, n: usize) -> Vec<f64> {
394 match self.family.to_lowercase().as_str() {
395 "chebyshev" => {
396 let w = PI / (n + 1) as f64;
397 vec![w; n + 1]
398 }
399 _ => {
400 let (_nodes, weights) = gauss_legendre_nodes_weights(n + 1);
401 weights
402 }
403 }
404 }
405 pub fn zeros(&self, n: usize) -> Vec<f64> {
407 match self.family.to_lowercase().as_str() {
408 "chebyshev" => gauss_chebyshev_nodes(n + 1),
409 _ => {
410 let (nodes, _) = gauss_legendre_nodes_weights(n + 1);
411 nodes
412 }
413 }
414 }
415}
416#[allow(non_snake_case)]
418pub struct FourierSpectralMethod {
419 pub N: usize,
421 pub domain_length: f64,
423}
424impl FourierSpectralMethod {
425 #[allow(non_snake_case)]
427 pub fn new(N: usize, domain_length: f64) -> Self {
428 Self { N, domain_length }
429 }
430 pub fn spectral_differentiation_matrix(&self) -> Vec<Vec<f64>> {
434 let n = self.N;
435 let l = self.domain_length;
436 let mut d = vec![vec![0.0_f64; n]; n];
437 for j in 0..n {
438 for k in 0..n {
439 if j != k {
440 let diff = (j as isize - k as isize) as f64;
441 let arg = PI * diff / n as f64;
442 d[j][k] = (PI / l) * (if (j + k) % 2 == 0 { 1.0 } else { -1.0 }) / arg.tan();
443 }
444 }
445 }
446 d
447 }
448 pub fn fft_solve_poisson(&self, rhs: &[f64]) -> Vec<f64> {
453 let n = self.N;
454 assert_eq!(rhs.len(), n, "rhs length must equal N");
455 let mut spectrum: Vec<Complex> = rhs.iter().map(|&v| Complex::new(v, 0.0)).collect();
456 fft_inplace(&mut spectrum);
457 let mut sol_spec = spectrum.clone();
458 sol_spec[0] = Complex::zero();
459 for k in 1..n {
460 let kk = if k <= n / 2 { k } else { n - k };
461 let wave_num = 2.0 * PI * kk as f64 / self.domain_length;
462 let eig = wave_num * wave_num;
463 sol_spec[k].re = spectrum[k].re / eig;
464 sol_spec[k].im = spectrum[k].im / eig;
465 }
466 ifft(&mut sol_spec);
467 sol_spec.iter().map(|c| c.re / n as f64).collect()
468 }
469}
470#[derive(Clone, Copy, Debug, PartialEq)]
472pub struct Complex {
473 pub re: f64,
474 pub im: f64,
475}
476impl Complex {
477 pub fn new(re: f64, im: f64) -> Self {
478 Self { re, im }
479 }
480 pub fn zero() -> Self {
481 Self { re: 0.0, im: 0.0 }
482 }
483 pub fn one() -> Self {
484 Self { re: 1.0, im: 0.0 }
485 }
486 pub fn exp_i(theta: f64) -> Self {
488 Self {
489 re: theta.cos(),
490 im: theta.sin(),
491 }
492 }
493 pub fn norm_sq(self) -> f64 {
494 self.re * self.re + self.im * self.im
495 }
496 pub fn abs(self) -> f64 {
497 self.norm_sq().sqrt()
498 }
499 pub fn conj(self) -> Self {
500 Self {
501 re: self.re,
502 im: -self.im,
503 }
504 }
505}
506pub struct SpectralElementMethod {
508 pub num_elements: usize,
510 pub poly_degree: usize,
512}
513impl SpectralElementMethod {
514 pub fn new(num_elements: usize, poly_degree: usize) -> Self {
516 Self {
517 num_elements,
518 poly_degree,
519 }
520 }
521 pub fn local_stiffness(&self) -> Vec<Vec<f64>> {
525 let p = self.poly_degree;
526 let d_mat = chebyshev_diff_matrix(p);
527 let sz = d_mat.len();
528 let mut k = vec![vec![0.0_f64; sz]; sz];
529 for i in 0..sz {
530 for j in 0..sz {
531 let mut s = 0.0;
532 for l in 0..sz {
533 s += d_mat[l][i] * d_mat[l][j];
534 }
535 k[i][j] = s;
536 }
537 }
538 k
539 }
540 pub fn assembly(&self) -> usize {
544 if self.num_elements == 0 {
545 return 0;
546 }
547 self.num_elements * self.poly_degree + 1
548 }
549}
550pub struct PseudospectralMethod {
552 pub grid_type: String,
554 pub num_modes: usize,
556}
557impl PseudospectralMethod {
558 pub fn new(grid_type: impl Into<String>, num_modes: usize) -> Self {
560 Self {
561 grid_type: grid_type.into(),
562 num_modes,
563 }
564 }
565 pub fn aliasing_condition(&self) -> usize {
567 (3 * self.num_modes + 1) / 2
568 }
569 pub fn dealiasing_rule(&self) -> usize {
571 self.aliasing_condition() - self.num_modes
572 }
573}
574#[allow(dead_code)]
576pub struct BarycentricInterpolator {
577 pub nodes: Vec<f64>,
579 pub values: Vec<f64>,
581 pub bary_weights: Vec<f64>,
583}
584#[allow(dead_code)]
585impl BarycentricInterpolator {
586 pub fn chebyshev(degree: usize, f: &dyn Fn(f64) -> f64) -> Self {
588 let nodes = chebyshev_gauss_lobatto_nodes(degree);
589 let values: Vec<f64> = nodes.iter().map(|&x| f(x)).collect();
590 let m = nodes.len();
591 let bary_weights: Vec<f64> = (0..m)
592 .map(|j| {
593 let sign = if j % 2 == 0 { 1.0 } else { -1.0 };
594 if j == 0 || j == m - 1 {
595 sign * 0.5
596 } else {
597 sign
598 }
599 })
600 .collect();
601 Self {
602 nodes,
603 values,
604 bary_weights,
605 }
606 }
607 pub fn eval(&self, x: f64) -> f64 {
609 for (j, &xj) in self.nodes.iter().enumerate() {
610 if (x - xj).abs() < 1e-14 {
611 return self.values[j];
612 }
613 }
614 let num: f64 = self
615 .nodes
616 .iter()
617 .zip(self.values.iter())
618 .zip(self.bary_weights.iter())
619 .map(|((&xj, &fj), &wj)| wj * fj / (x - xj))
620 .sum();
621 let den: f64 = self
622 .nodes
623 .iter()
624 .zip(self.bary_weights.iter())
625 .map(|(&xj, &wj)| wj / (x - xj))
626 .sum();
627 num / den
628 }
629 pub fn max_error(&self, f: &dyn Fn(f64) -> f64, test_points: &[f64]) -> f64 {
631 test_points
632 .iter()
633 .map(|&x| (self.eval(x) - f(x)).abs())
634 .fold(0.0_f64, f64::max)
635 }
636}
637pub struct ChebychevExpansion {
639 pub degree: usize,
641 pub domain: (f64, f64),
643 pub coefficients: Vec<f64>,
645}
646impl ChebychevExpansion {
647 pub fn new(degree: usize, domain: (f64, f64), coefficients: Vec<f64>) -> Self {
649 Self {
650 degree,
651 domain,
652 coefficients,
653 }
654 }
655 pub fn evaluate_at(&self, x: f64) -> f64 {
659 let (a, b) = self.domain;
660 let xi = 2.0 * (x - a) / (b - a) - 1.0;
661 clenshaw_eval(&self.coefficients, xi)
662 }
663 pub fn derivative_coefficients(&self) -> Vec<f64> {
668 let n = self.coefficients.len();
669 if n == 0 {
670 return vec![];
671 }
672 let mut d = vec![0.0_f64; n];
673 if n >= 2 {
674 d[n - 1] = 0.0;
675 if n >= 3 {
676 d[n - 2] = 2.0 * (n as f64 - 1.0) * self.coefficients[n - 1];
677 }
678 for k in (0..n.saturating_sub(2)).rev() {
679 d[k] = 2.0 * (k as f64 + 1.0) * self.coefficients[k + 1]
680 + if k + 2 < n { d[k + 2] } else { 0.0 };
681 }
682 if n > 0 {
683 d[0] /= 2.0;
684 }
685 }
686 let (a, b) = self.domain;
687 let scale = 2.0 / (b - a);
688 d.iter().map(|&v| scale * v).collect()
689 }
690 pub fn quadrature_points(&self) -> Vec<f64> {
692 let (a, b) = self.domain;
693 chebyshev_gauss_lobatto_nodes(self.degree)
694 .into_iter()
695 .map(|xi| 0.5 * ((b - a) * xi + a + b))
696 .collect()
697 }
698}
699#[allow(dead_code)]
701pub struct GaussLobattoRule {
702 pub degree: usize,
704 pub nodes: Vec<f64>,
706 pub weights: Vec<f64>,
708}
709#[allow(dead_code)]
710impl GaussLobattoRule {
711 pub fn new(degree: usize) -> Self {
713 let n = degree;
714 let m = n + 1;
715 let mut nodes = vec![0.0f64; m];
716 let mut weights = vec![0.0f64; m];
717 nodes[0] = -1.0;
718 nodes[n] = 1.0;
719 for j in 1..n {
720 nodes[j] = -(std::f64::consts::PI * j as f64 / n as f64).cos();
721 }
722 for j in 0..m {
723 let pn = spec2_legendre_p(n, nodes[j]);
724 weights[j] = 2.0 / (n as f64 * (n + 1) as f64 * pn * pn);
725 }
726 weights[0] = 2.0 / (n as f64 * (n + 1) as f64);
727 weights[n] = weights[0];
728 Self {
729 degree,
730 nodes,
731 weights,
732 }
733 }
734 pub fn integrate<F: Fn(f64) -> f64>(&self, f: F) -> f64 {
736 self.nodes
737 .iter()
738 .zip(self.weights.iter())
739 .map(|(&x, &w)| w * f(x))
740 .sum()
741 }
742 pub fn num_points(&self) -> usize {
744 self.degree + 1
745 }
746}