use super::functions::*;
use std::f64::consts::PI;
#[allow(dead_code)]
pub struct HpAdaptiveSpectral {
pub num_elements: usize,
pub degrees: Vec<usize>,
pub breakpoints: Vec<f64>,
pub local_solutions: Vec<Vec<f64>>,
}
#[allow(dead_code)]
impl HpAdaptiveSpectral {
pub fn uniform(a: f64, b: f64, num_elements: usize, p: usize) -> Self {
let h = (b - a) / num_elements as f64;
let breakpoints: Vec<f64> = (0..=num_elements).map(|i| a + i as f64 * h).collect();
let degrees = vec![p; num_elements];
let local_solutions = vec![vec![0.0f64; p + 1]; num_elements];
Self {
num_elements,
degrees,
breakpoints,
local_solutions,
}
}
pub fn total_dof(&self) -> usize {
self.degrees.iter().map(|&p| p + 1).sum()
}
pub fn h_refine(&mut self, k: usize) {
if k >= self.num_elements {
return;
}
let mid = 0.5 * (self.breakpoints[k] + self.breakpoints[k + 1]);
let deg = self.degrees[k];
let sol = self.local_solutions[k].clone();
self.breakpoints.insert(k + 1, mid);
self.degrees.remove(k);
self.degrees.insert(k, deg);
self.degrees.insert(k + 1, deg);
self.local_solutions.remove(k);
self.local_solutions.insert(k, sol.clone());
self.local_solutions.insert(k + 1, sol);
self.num_elements += 1;
}
pub fn p_refine(&mut self, k: usize) {
if k >= self.num_elements {
return;
}
self.degrees[k] += 1;
self.local_solutions[k].push(0.0);
}
pub fn element_size(&self, k: usize) -> f64 {
if k >= self.num_elements {
return 0.0;
}
self.breakpoints[k + 1] - self.breakpoints[k]
}
}
pub struct RadialBasisFunction {
pub function_type: String,
pub shape_param: f64,
}
impl RadialBasisFunction {
pub fn new(function_type: impl Into<String>, shape_param: f64) -> Self {
Self {
function_type: function_type.into(),
shape_param,
}
}
pub fn phi(&self, r: f64) -> f64 {
let eps = self.shape_param;
match self.function_type.to_lowercase().as_str() {
"gaussian" => (-(eps * r).powi(2)).exp(),
"multiquadric" => (1.0 + (eps * r).powi(2)).sqrt(),
"inverse_multiquadric" => 1.0 / (1.0 + (eps * r).powi(2)).sqrt(),
"thin_plate" => {
if r < 1e-14 {
0.0
} else {
r * r * r.ln()
}
}
_ => (-(eps * r).powi(2)).exp(),
}
}
pub fn interpolate(&self, centers: &[f64], values: &[f64], query: &[f64]) -> Vec<f64> {
let n = centers.len();
assert_eq!(values.len(), n);
let mut phi_mat = vec![vec![0.0_f64; n]; n];
for i in 0..n {
for j in 0..n {
phi_mat[i][j] = self.phi((centers[i] - centers[j]).abs());
}
}
let coeffs = solve_linear(&phi_mat, values);
query
.iter()
.map(|&x| {
coeffs
.iter()
.zip(centers.iter())
.map(|(&c, &xi)| c * self.phi((x - xi).abs()))
.sum()
})
.collect()
}
pub fn rbf_fd_stencil(&self, center: f64, neighbors: &[f64]) -> Vec<f64> {
let n = neighbors.len();
let mut phi_mat = vec![vec![0.0_f64; n]; n];
for i in 0..n {
for j in 0..n {
phi_mat[i][j] = self.phi((neighbors[i] - neighbors[j]).abs());
}
}
let rhs: Vec<f64> = neighbors
.iter()
.map(|&xj| {
let r = (center - xj).abs();
let sign = if center >= xj { 1.0 } else { -1.0 };
let dphi = self.dphi_dr(r);
dphi * sign
})
.collect();
solve_linear(&phi_mat, &rhs)
}
fn dphi_dr(&self, r: f64) -> f64 {
let eps = self.shape_param;
match self.function_type.to_lowercase().as_str() {
"gaussian" => -2.0 * eps * eps * r * (-(eps * r).powi(2)).exp(),
"multiquadric" => eps * eps * r / (1.0 + (eps * r).powi(2)).sqrt(),
"inverse_multiquadric" => -eps * eps * r / (1.0 + (eps * r).powi(2)).powf(1.5),
"thin_plate" => {
if r < 1e-14 {
0.0
} else {
r * (1.0 + 2.0 * r.ln())
}
}
_ => -2.0 * eps * eps * r * (-(eps * r).powi(2)).exp(),
}
}
}
pub struct SpectralDecomposition {
pub matrix: Vec<Vec<f64>>,
}
impl SpectralDecomposition {
pub fn new(matrix: Vec<Vec<f64>>) -> Self {
Self { matrix }
}
pub fn eigenvalues(&self) -> Vec<f64> {
let n = self.matrix.len();
if n == 0 {
return vec![];
}
if n == 1 {
return vec![self.matrix[0][0]];
}
if n == 2 {
let a = self.matrix[0][0];
let b = self.matrix[0][1];
let c = self.matrix[1][0];
let d = self.matrix[1][1];
let tr = a + d;
let det = a * d - b * c;
let disc = (tr * tr / 4.0 - det).max(0.0);
return vec![tr / 2.0 + disc.sqrt(), tr / 2.0 - disc.sqrt()];
}
(0..n).map(|i| self.matrix[i][i]).collect()
}
pub fn eigenvectors(&self) -> Vec<Vec<f64>> {
let n = self.matrix.len();
(0..n)
.map(|i| {
let mut row = vec![0.0_f64; n];
if i < n {
row[i] = 1.0;
}
row
})
.collect()
}
pub fn is_symmetric(&self) -> bool {
let n = self.matrix.len();
for i in 0..n {
if self.matrix[i].len() != n {
return false;
}
for j in 0..n {
if (self.matrix[i][j] - self.matrix[j][i]).abs() > 1e-12 {
return false;
}
}
}
true
}
}
#[allow(dead_code)]
pub struct SpectralDiffMatrix {
pub degree: usize,
pub matrix: Vec<Vec<f64>>,
pub nodes: Vec<f64>,
}
#[allow(dead_code)]
impl SpectralDiffMatrix {
pub fn chebyshev(degree: usize) -> Self {
let nodes = chebyshev_gauss_lobatto_nodes(degree);
let matrix = chebyshev_diff_matrix(degree);
Self {
degree,
matrix,
nodes,
}
}
pub fn apply(&self, u: &[f64]) -> Vec<f64> {
apply_diff_matrix(&self.matrix, u)
}
pub fn spectral_radius(&self) -> f64 {
self.matrix
.iter()
.map(|row| row.iter().map(|&v| v.abs()).sum::<f64>())
.fold(0.0_f64, f64::max)
}
pub fn apply_second(&self, u: &[f64]) -> Vec<f64> {
let du = self.apply(u);
self.apply(&du)
}
pub fn num_points(&self) -> usize {
self.degree + 1
}
}
#[allow(dead_code)]
pub struct SpectralRadiusEstimator {
pub matrix: Vec<Vec<f64>>,
}
#[allow(dead_code)]
impl SpectralRadiusEstimator {
pub fn new(matrix: Vec<Vec<f64>>) -> Self {
Self { matrix }
}
pub fn power_iteration(&self, max_iter: usize, tol: f64) -> f64 {
let n = self.matrix.len();
if n == 0 {
return 0.0;
}
let mut v: Vec<f64> = (0..n).map(|i| if i == 0 { 1.0 } else { 0.0 }).collect();
let mut lambda = 1.0f64;
for _ in 0..max_iter {
let w: Vec<f64> = (0..n)
.map(|i| {
self.matrix[i]
.iter()
.zip(v.iter())
.map(|(&a, &x)| a * x)
.sum()
})
.collect();
let norm: f64 = w.iter().map(|&x| x * x).sum::<f64>().sqrt();
if norm < 1e-15 {
break;
}
let new_lambda = norm;
if (new_lambda - lambda).abs() < tol {
lambda = new_lambda;
break;
}
lambda = new_lambda;
v = w.iter().map(|&x| x / norm).collect();
}
lambda
}
pub fn gershgorin_bound(&self) -> f64 {
self.matrix
.iter()
.map(|row| row.iter().map(|&v| v.abs()).sum::<f64>())
.fold(0.0_f64, f64::max)
}
}
pub struct ExponentialConvergence {
pub smoothness: u32,
pub error: f64,
}
impl ExponentialConvergence {
pub fn new(smoothness: u32, error: f64) -> Self {
Self { smoothness, error }
}
pub fn spectral_accuracy(&self) -> bool {
self.smoothness >= 3
}
pub fn algebraic_vs_spectral(&self, n: usize) -> (f64, f64) {
let k = self.smoothness as f64;
let alg = 1.0 / (n as f64).powf(k);
let spec = (-0.5 * n as f64).exp();
(alg, spec)
}
}
pub struct OrthogonalPolynomials {
pub family: String,
}
impl OrthogonalPolynomials {
pub fn new(family: impl Into<String>) -> Self {
Self {
family: family.into(),
}
}
pub fn three_term_recurrence(&self, n: usize) -> (f64, f64, f64) {
match self.family.to_lowercase().as_str() {
"chebyshev" => {
if n == 0 {
(2.0, 0.0, 1.0)
} else {
(2.0, 0.0, 1.0)
}
}
"legendre" => {
let nn = n as f64;
let alpha = (2.0 * nn + 1.0) / (nn + 1.0);
let beta = 0.0;
let gamma = nn / (nn + 1.0);
(alpha, beta, gamma)
}
"hermite" => (1.0, 0.0, n as f64),
"laguerre" => {
let nn = n as f64;
(
-(1.0 / (nn + 1.0)),
(2.0 * nn + 1.0) / (nn + 1.0),
nn / (nn + 1.0),
)
}
_ => (1.0, 0.0, 1.0),
}
}
pub fn gauss_quadrature_weights(&self, n: usize) -> Vec<f64> {
match self.family.to_lowercase().as_str() {
"chebyshev" => {
let w = PI / (n + 1) as f64;
vec![w; n + 1]
}
_ => {
let (_nodes, weights) = gauss_legendre_nodes_weights(n + 1);
weights
}
}
}
pub fn zeros(&self, n: usize) -> Vec<f64> {
match self.family.to_lowercase().as_str() {
"chebyshev" => gauss_chebyshev_nodes(n + 1),
_ => {
let (nodes, _) = gauss_legendre_nodes_weights(n + 1);
nodes
}
}
}
}
#[allow(non_snake_case)]
pub struct FourierSpectralMethod {
pub N: usize,
pub domain_length: f64,
}
impl FourierSpectralMethod {
#[allow(non_snake_case)]
pub fn new(N: usize, domain_length: f64) -> Self {
Self { N, domain_length }
}
pub fn spectral_differentiation_matrix(&self) -> Vec<Vec<f64>> {
let n = self.N;
let l = self.domain_length;
let mut d = vec![vec![0.0_f64; n]; n];
for j in 0..n {
for k in 0..n {
if j != k {
let diff = (j as isize - k as isize) as f64;
let arg = PI * diff / n as f64;
d[j][k] = (PI / l) * (if (j + k) % 2 == 0 { 1.0 } else { -1.0 }) / arg.tan();
}
}
}
d
}
pub fn fft_solve_poisson(&self, rhs: &[f64]) -> Vec<f64> {
let n = self.N;
assert_eq!(rhs.len(), n, "rhs length must equal N");
let mut spectrum: Vec<Complex> = rhs.iter().map(|&v| Complex::new(v, 0.0)).collect();
fft_inplace(&mut spectrum);
let mut sol_spec = spectrum.clone();
sol_spec[0] = Complex::zero();
for k in 1..n {
let kk = if k <= n / 2 { k } else { n - k };
let wave_num = 2.0 * PI * kk as f64 / self.domain_length;
let eig = wave_num * wave_num;
sol_spec[k].re = spectrum[k].re / eig;
sol_spec[k].im = spectrum[k].im / eig;
}
ifft(&mut sol_spec);
sol_spec.iter().map(|c| c.re / n as f64).collect()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Complex {
pub re: f64,
pub im: f64,
}
impl Complex {
pub fn new(re: f64, im: f64) -> Self {
Self { re, im }
}
pub fn zero() -> Self {
Self { re: 0.0, im: 0.0 }
}
pub fn one() -> Self {
Self { re: 1.0, im: 0.0 }
}
pub fn exp_i(theta: f64) -> Self {
Self {
re: theta.cos(),
im: theta.sin(),
}
}
pub fn norm_sq(self) -> f64 {
self.re * self.re + self.im * self.im
}
pub fn abs(self) -> f64 {
self.norm_sq().sqrt()
}
pub fn conj(self) -> Self {
Self {
re: self.re,
im: -self.im,
}
}
}
pub struct SpectralElementMethod {
pub num_elements: usize,
pub poly_degree: usize,
}
impl SpectralElementMethod {
pub fn new(num_elements: usize, poly_degree: usize) -> Self {
Self {
num_elements,
poly_degree,
}
}
pub fn local_stiffness(&self) -> Vec<Vec<f64>> {
let p = self.poly_degree;
let d_mat = chebyshev_diff_matrix(p);
let sz = d_mat.len();
let mut k = vec![vec![0.0_f64; sz]; sz];
for i in 0..sz {
for j in 0..sz {
let mut s = 0.0;
for l in 0..sz {
s += d_mat[l][i] * d_mat[l][j];
}
k[i][j] = s;
}
}
k
}
pub fn assembly(&self) -> usize {
if self.num_elements == 0 {
return 0;
}
self.num_elements * self.poly_degree + 1
}
}
pub struct PseudospectralMethod {
pub grid_type: String,
pub num_modes: usize,
}
impl PseudospectralMethod {
pub fn new(grid_type: impl Into<String>, num_modes: usize) -> Self {
Self {
grid_type: grid_type.into(),
num_modes,
}
}
pub fn aliasing_condition(&self) -> usize {
(3 * self.num_modes + 1) / 2
}
pub fn dealiasing_rule(&self) -> usize {
self.aliasing_condition() - self.num_modes
}
}
#[allow(dead_code)]
pub struct BarycentricInterpolator {
pub nodes: Vec<f64>,
pub values: Vec<f64>,
pub bary_weights: Vec<f64>,
}
#[allow(dead_code)]
impl BarycentricInterpolator {
pub fn chebyshev(degree: usize, f: &dyn Fn(f64) -> f64) -> Self {
let nodes = chebyshev_gauss_lobatto_nodes(degree);
let values: Vec<f64> = nodes.iter().map(|&x| f(x)).collect();
let m = nodes.len();
let bary_weights: Vec<f64> = (0..m)
.map(|j| {
let sign = if j % 2 == 0 { 1.0 } else { -1.0 };
if j == 0 || j == m - 1 {
sign * 0.5
} else {
sign
}
})
.collect();
Self {
nodes,
values,
bary_weights,
}
}
pub fn eval(&self, x: f64) -> f64 {
for (j, &xj) in self.nodes.iter().enumerate() {
if (x - xj).abs() < 1e-14 {
return self.values[j];
}
}
let num: f64 = self
.nodes
.iter()
.zip(self.values.iter())
.zip(self.bary_weights.iter())
.map(|((&xj, &fj), &wj)| wj * fj / (x - xj))
.sum();
let den: f64 = self
.nodes
.iter()
.zip(self.bary_weights.iter())
.map(|(&xj, &wj)| wj / (x - xj))
.sum();
num / den
}
pub fn max_error(&self, f: &dyn Fn(f64) -> f64, test_points: &[f64]) -> f64 {
test_points
.iter()
.map(|&x| (self.eval(x) - f(x)).abs())
.fold(0.0_f64, f64::max)
}
}
pub struct ChebychevExpansion {
pub degree: usize,
pub domain: (f64, f64),
pub coefficients: Vec<f64>,
}
impl ChebychevExpansion {
pub fn new(degree: usize, domain: (f64, f64), coefficients: Vec<f64>) -> Self {
Self {
degree,
domain,
coefficients,
}
}
pub fn evaluate_at(&self, x: f64) -> f64 {
let (a, b) = self.domain;
let xi = 2.0 * (x - a) / (b - a) - 1.0;
clenshaw_eval(&self.coefficients, xi)
}
pub fn derivative_coefficients(&self) -> Vec<f64> {
let n = self.coefficients.len();
if n == 0 {
return vec![];
}
let mut d = vec![0.0_f64; n];
if n >= 2 {
d[n - 1] = 0.0;
if n >= 3 {
d[n - 2] = 2.0 * (n as f64 - 1.0) * self.coefficients[n - 1];
}
for k in (0..n.saturating_sub(2)).rev() {
d[k] = 2.0 * (k as f64 + 1.0) * self.coefficients[k + 1]
+ if k + 2 < n { d[k + 2] } else { 0.0 };
}
if n > 0 {
d[0] /= 2.0;
}
}
let (a, b) = self.domain;
let scale = 2.0 / (b - a);
d.iter().map(|&v| scale * v).collect()
}
pub fn quadrature_points(&self) -> Vec<f64> {
let (a, b) = self.domain;
chebyshev_gauss_lobatto_nodes(self.degree)
.into_iter()
.map(|xi| 0.5 * ((b - a) * xi + a + b))
.collect()
}
}
#[allow(dead_code)]
pub struct GaussLobattoRule {
pub degree: usize,
pub nodes: Vec<f64>,
pub weights: Vec<f64>,
}
#[allow(dead_code)]
impl GaussLobattoRule {
pub fn new(degree: usize) -> Self {
let n = degree;
let m = n + 1;
let mut nodes = vec![0.0f64; m];
let mut weights = vec![0.0f64; m];
nodes[0] = -1.0;
nodes[n] = 1.0;
for j in 1..n {
nodes[j] = -(std::f64::consts::PI * j as f64 / n as f64).cos();
}
for j in 0..m {
let pn = spec2_legendre_p(n, nodes[j]);
weights[j] = 2.0 / (n as f64 * (n + 1) as f64 * pn * pn);
}
weights[0] = 2.0 / (n as f64 * (n + 1) as f64);
weights[n] = weights[0];
Self {
degree,
nodes,
weights,
}
}
pub fn integrate<F: Fn(f64) -> f64>(&self, f: F) -> f64 {
self.nodes
.iter()
.zip(self.weights.iter())
.map(|(&x, &w)| w * f(x))
.sum()
}
pub fn num_points(&self) -> usize {
self.degree + 1
}
}