use scirs2_core::ndarray::{Array1, Array2, ArrayView1};
use std::f64::consts::PI;
use std::time::Instant;
use crate::gaussian::GaussLegendreQuadrature;
use crate::pde::{
BoundaryCondition, BoundaryConditionType, BoundaryLocation, Domain, PDEError, PDEResult,
PDESolution, PDESolverInfo,
};
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SpectralBasis {
Fourier,
Chebyshev,
Legendre,
}
#[derive(Debug, Clone)]
pub struct SpectralOptions {
pub basis: SpectralBasis,
pub num_modes: usize,
pub max_iterations: usize,
pub tolerance: f64,
pub save_convergence_history: bool,
pub use_real_transform: bool,
pub use_dealiasing: bool,
pub verbose: bool,
}
impl Default for SpectralOptions {
fn default() -> Self {
SpectralOptions {
basis: SpectralBasis::Fourier,
num_modes: 64,
max_iterations: 1000,
tolerance: 1e-10,
save_convergence_history: false,
use_real_transform: true,
use_dealiasing: true,
verbose: false,
}
}
}
#[derive(Debug, Clone)]
pub struct SpectralResult {
pub u: Array1<f64>,
pub coefficients: Array1<f64>,
pub grid: Array1<f64>,
pub residual_norm: f64,
pub num_iterations: usize,
pub computation_time: f64,
pub convergence_history: Option<Vec<f64>>,
}
#[allow(dead_code)]
pub fn chebyshev_diff_matrix(n: usize) -> Array2<f64> {
let mut d = Array2::zeros((n, n));
let mut x = Array1::zeros(n);
for j in 0..n {
x[j] = (j as f64 * PI / (n - 1) as f64).cos();
}
for i in 0..n {
for j in 0..n {
if i != j {
let c_i = if i == 0 || i == n - 1 { 2.0 } else { 1.0 };
let c_j = if j == 0 || j == n - 1 { 2.0 } else { 1.0 };
d[[i, j]] = c_i / c_j * (-1.0_f64).powf((i + j) as f64) / (x[i] - x[j]);
}
}
}
for i in 0..n {
let mut row_sum = 0.0;
for j in 0..n {
if i != j {
row_sum += d[[i, j]];
}
}
d[[i, i]] = -row_sum;
}
d
}
#[allow(dead_code)]
pub fn chebyshev_diff2_matrix(n: usize) -> Array2<f64> {
let d1 = chebyshev_diff_matrix(n);
d1.dot(&d1) }
#[allow(dead_code)]
pub fn chebyshev_points(n: usize) -> Array1<f64> {
let mut x = Array1::zeros(n);
for j in 0..n {
x[j] = (j as f64 * PI / (n - 1) as f64).cos();
}
x
}
#[allow(dead_code)]
pub fn chebyshev_transform(u: &ArrayView1<f64>) -> Array1<f64> {
let n = u.len();
let mut coeffs = Array1::zeros(n);
for k in 0..n {
let mut sum = 0.0;
for j in 0..n {
let _x_j = (j as f64 * PI / (n - 1) as f64).cos();
sum += u[j] * (k as f64 * j as f64 * PI / (n - 1) as f64).cos();
}
let norm = if k == 0 || k == n - 1 {
n - 1
} else {
2 * (n - 1)
};
coeffs[k] = 2.0 * sum / norm as f64;
}
coeffs
}
#[allow(dead_code)]
pub fn chebyshev_inverse_transform(coeffs: &ArrayView1<f64>) -> Array1<f64> {
let n = coeffs.len();
let mut u = Array1::zeros(n);
let _x = chebyshev_points(n);
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
sum += coeffs[k] * (k as f64 * j as f64 * PI / (n - 1) as f64).cos();
}
u[j] = sum;
}
u
}
#[allow(dead_code)]
pub fn legendre_points(n: usize) -> (Array1<f64>, Array1<f64>) {
if n <= 1 {
return (Array1::zeros(1), Array1::ones(1));
}
if n <= 12 {
let quadrature = GaussLegendreQuadrature::<f64>::new(n - 2).expect("Operation failed");
let mut points = Array1::zeros(n);
let mut weights = Array1::zeros(n);
points[0] = -1.0;
points[n - 1] = 1.0;
for i in 0..n - 2 {
points[i + 1] = quadrature.nodes[n - 3 - i]; }
let factor = 2.0 / (n as f64 * (n - 1) as f64);
weights[0] = factor;
weights[n - 1] = factor;
for i in 1..n - 1 {
let x = points[i];
let p = legendre_polynomial(n - 1, x);
weights[i] = factor / (p * p);
}
return (points, weights);
}
let mut points = Array1::zeros(n);
for i in 0..n {
points[i] = -(i as f64 * PI / (n - 1) as f64).cos();
}
for i in 1..n - 1 {
let mut x = points[i];
let mut delta;
for _ in 0..10 {
let _p = legendre_polynomial(n - 1, x);
let dp = legendre_polynomial_derivative(n - 1, x);
let h = 1e-6;
let dp_plus = legendre_polynomial_derivative(n - 1, x + h);
let dp_minus = legendre_polynomial_derivative(n - 1, x - h);
let ddp = (dp_plus - dp_minus) / (2.0 * h);
let f = (1.0 - x * x) * dp;
let df = -2.0 * x * dp + (1.0 - x * x) * ddp;
delta = f / df;
x -= delta;
if delta.abs() < 1e-12 {
break;
}
}
points[i] = x;
}
let mut weights = Array1::zeros(n);
let factor = 2.0 / (n as f64 * (n - 1) as f64);
for i in 0..n {
let x = points[i];
let p = legendre_polynomial(n - 1, x);
weights[i] = factor / (p * p);
}
(points, weights)
}
#[allow(dead_code)]
fn legendre_polynomial(n: usize, x: f64) -> f64 {
if n == 0 {
return 1.0;
}
if n == 1 {
return x;
}
let mut p_prev = 1.0; let mut p = x;
for k in 2..=n {
let k_f64 = k as f64;
let p_next = ((2.0 * k_f64 - 1.0) * x * p - (k_f64 - 1.0) * p_prev) / k_f64;
p_prev = p;
p = p_next;
}
p
}
#[allow(dead_code)]
fn legendre_polynomial_derivative(n: usize, x: f64) -> f64 {
if n == 0 {
return 0.0;
}
if n == 1 {
return 1.0;
}
let pn = legendre_polynomial(n, x);
let pn_minus_1 = legendre_polynomial(n - 1, x);
if (1.0 - x * x).abs() < 1e-10 {
if x > 0.0 {
return n as f64 * (n + 1) as f64 / 2.0;
} else {
return if n.is_multiple_of(2) { -1.0 } else { 1.0 } * n as f64 * (n + 1) as f64 / 2.0;
}
}
n as f64 * (pn_minus_1 - x * pn) / (1.0 - x * x)
}
#[allow(dead_code)]
pub fn legendre_diff_matrix(n: usize) -> Array2<f64> {
let mut d = Array2::zeros((n, n));
let (x_, weights) = legendre_points(n);
for i in 0..n {
for j in 0..n {
if i != j {
let p_i = legendre_polynomial(n - 1, x_[i]);
let p_j = legendre_polynomial(n - 1, x_[j]);
d[[i, j]] = p_i / (p_j * (x_[i] - x_[j]));
if j == 0 || j == n - 1 {
d[[i, j]] *= 2.0;
}
}
}
}
for i in 0..n {
d[[i, i]] = 0.0;
for j in 0..n {
if i != j {
d[[i, i]] -= d[[i, j]];
}
}
}
d
}
#[allow(dead_code)]
pub fn legendre_diff2_matrix(n: usize) -> Array2<f64> {
let d1 = legendre_diff_matrix(n);
d1.dot(&d1) }
#[allow(dead_code)]
pub fn legendre_transform(u: &ArrayView1<f64>) -> Array1<f64> {
let n = u.len();
let mut coeffs = Array1::zeros(n);
let (x, w) = legendre_points(n);
for k in 0..n {
let mut sum = 0.0;
for j in 0..n {
let p_k = legendre_polynomial(k, x[j]);
sum += u[j] * p_k * w[j];
}
let norm = k as f64 + 0.5;
coeffs[k] = sum * norm;
}
coeffs
}
#[allow(dead_code)]
pub fn legendre_inverse_transform(
coeffs: &ArrayView1<f64>,
x_points: Option<&ArrayView1<f64>>,
) -> Array1<f64> {
let n = coeffs.len();
let mut u = Array1::zeros(n);
let x = if let Some(points) = x_points {
points.to_owned()
} else {
legendre_points(n).0
};
for j in 0..n {
let mut sum = 0.0;
for k in 0..n {
let norm = 1.0 / (k as f64 + 0.5);
sum += coeffs[k] * norm * legendre_polynomial(k, x[j]);
}
u[j] = sum;
}
u
}
pub struct FourierSpectralSolver1D {
domain: Domain,
source_term: Box<dyn Fn(f64) -> f64 + Send + Sync>,
#[allow(dead_code)]
boundary_conditions: Vec<BoundaryCondition<f64>>,
options: SpectralOptions,
}
impl FourierSpectralSolver1D {
pub fn new(
domain: Domain,
source_term: impl Fn(f64) -> f64 + Send + Sync + 'static,
boundary_conditions: Vec<BoundaryCondition<f64>>,
options: Option<SpectralOptions>,
) -> PDEResult<Self> {
if domain.dimensions() != 1 {
return Err(PDEError::DomainError(
"Domain must be 1-dimensional for 1D Fourier spectral solver".to_string(),
));
}
let mut has_periodic = false;
for bc in &boundary_conditions {
if bc.bc_type == BoundaryConditionType::Periodic {
has_periodic = true;
break;
}
}
if !has_periodic {
return Err(PDEError::BoundaryConditions(
"Fourier spectral methods require periodic boundary _conditions".to_string(),
));
}
let mut options = options.unwrap_or_default();
options.basis = SpectralBasis::Fourier;
Ok(FourierSpectralSolver1D {
domain,
source_term: Box::new(source_term),
boundary_conditions,
options,
})
}
pub fn solve(&self) -> PDEResult<SpectralResult> {
let start_time = Instant::now();
let range = &self.domain.ranges[0];
let length = range.end - range.start;
let n = self.options.num_modes;
let mut grid = Array1::zeros(n);
for i in 0..n {
grid[i] = range.start + i as f64 * length / n as f64;
}
let mut f_values = Array1::zeros(n);
for (i, &x) in grid.iter().enumerate() {
f_values[i] = (self.source_term)(x);
}
let f_hat = if self.options.use_real_transform {
rfft(&f_values.to_owned())
} else {
fft(&f_values.to_owned())
};
let n_freq = if self.options.use_real_transform {
n / 2 + 1
} else {
n
};
let mut k = Array1::zeros(n_freq);
for i in 0..n_freq {
if i <= n / 2 {
k[i] = 2.0 * PI * i as f64 / length;
} else {
k[i] = -2.0 * PI * (n - i) as f64 / length;
}
}
let mut u_hat =
Array1::from_elem(f_hat.len(), scirs2_core::numeric::Complex::new(0.0, 0.0));
for i in 0..n_freq {
if i == 0 {
u_hat[i] = scirs2_core::numeric::Complex::new(0.0, 0.0);
} else {
u_hat[i] = -f_hat[i] / (k[i] * k[i]);
}
}
if self.options.use_dealiasing {
let cutoff = 2 * n / 3;
for i in cutoff..n_freq {
u_hat[i] = scirs2_core::numeric::Complex::new(0.0, 0.0);
}
}
let u = if self.options.use_real_transform {
irfft(&u_hat)
} else {
ifft(&u_hat).mapv(|c| c.re)
};
let mut residual = Array1::zeros(n);
let u_xx = if self.options.use_real_transform {
let mut u_xx_hat = Array1::zeros(u_hat.len());
for i in 0..n_freq {
u_xx_hat[i] = -k[i] * k[i] * u_hat[i];
}
irfft(&u_xx_hat)
} else {
let mut u_xx_hat = Array1::zeros(u_hat.len());
for i in 0..n_freq {
u_xx_hat[i] = -k[i] * k[i] * u_hat[i];
}
ifft(&u_xx_hat).mapv(|c| c.re)
};
for i in 0..n {
residual[i] = u_xx[i] - f_values[i];
}
let residual_norm = (residual.mapv(|r| r * r).sum() / n as f64).sqrt();
let computation_time = start_time.elapsed().as_secs_f64();
Ok(SpectralResult {
u,
coefficients: u_hat.mapv(|c| c.re),
grid,
residual_norm,
num_iterations: 1, computation_time,
convergence_history: None,
})
}
}
pub struct ChebyshevSpectralSolver1D {
domain: Domain,
source_term: Box<dyn Fn(f64) -> f64 + Send + Sync>,
boundary_conditions: Vec<BoundaryCondition<f64>>,
options: SpectralOptions,
}
impl ChebyshevSpectralSolver1D {
pub fn new(
domain: Domain,
source_term: impl Fn(f64) -> f64 + Send + Sync + 'static,
boundary_conditions: Vec<BoundaryCondition<f64>>,
options: Option<SpectralOptions>,
) -> PDEResult<Self> {
if domain.dimensions() != 1 {
return Err(PDEError::DomainError(
"Domain must be 1-dimensional for 1D Chebyshev spectral solver".to_string(),
));
}
if boundary_conditions.len() != 2 {
return Err(PDEError::BoundaryConditions(
"1D Chebyshev spectral solver requires exactly 2 boundary _conditions".to_string(),
));
}
let mut has_lower = false;
let mut has_upper = false;
for bc in &boundary_conditions {
match bc.location {
BoundaryLocation::Lower => has_lower = true,
BoundaryLocation::Upper => has_upper = true,
}
if bc.bc_type == BoundaryConditionType::Periodic {
return Err(PDEError::BoundaryConditions(
"Chebyshev spectral methods are not suitable for periodic boundary _conditions"
.to_string(),
));
}
}
if !has_lower || !has_upper {
return Err(PDEError::BoundaryConditions(
"Chebyshev spectral solver requires boundary _conditions at both domain endpoints"
.to_string(),
));
}
let mut options = options.unwrap_or_default();
options.basis = SpectralBasis::Chebyshev;
Ok(ChebyshevSpectralSolver1D {
domain,
source_term: Box::new(source_term),
boundary_conditions,
options,
})
}
pub fn solve(&self) -> PDEResult<SpectralResult> {
let start_time = Instant::now();
let range = &self.domain.ranges[0];
let a = range.start;
let b = range.end;
let n = self.options.num_modes;
let mut cheb_grid = Array1::zeros(n);
for j in 0..n {
cheb_grid[j] = (j as f64 * PI / (n - 1) as f64).cos();
}
let mut grid = Array1::zeros(n);
for j in 0..n {
grid[j] = a + (b - a) * (cheb_grid[j] + 1.0) / 2.0;
}
let mut f_values = Array1::zeros(n);
for j in 0..n {
f_values[j] = (self.source_term)(grid[j]);
}
let mut d2 = chebyshev_diff2_matrix(n);
let scale = 4.0 / ((b - a) * (b - a));
d2.mapv_inplace(|x| x * scale);
let mut a_matrix = d2;
let mut rhs = f_values;
for bc in &self.boundary_conditions {
match bc.location {
BoundaryLocation::Lower => {
let j = 0;
match bc.bc_type {
BoundaryConditionType::Dirichlet => {
for k in 0..n {
a_matrix[[j, k]] = 0.0;
}
a_matrix[[j, j]] = 1.0;
rhs[j] = bc.value;
}
BoundaryConditionType::Neumann => {
let d1 = chebyshev_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
}
rhs[j] = bc.value;
}
BoundaryConditionType::Robin => {
if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
let d1 = chebyshev_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
}
rhs[j] = c_coef;
}
}
_ => {
return Err(PDEError::BoundaryConditions(
"Unsupported boundary condition type for Chebyshev spectral method"
.to_string(),
))
}
}
}
BoundaryLocation::Upper => {
let j = n - 1;
match bc.bc_type {
BoundaryConditionType::Dirichlet => {
for k in 0..n {
a_matrix[[j, k]] = 0.0;
}
a_matrix[[j, j]] = 1.0;
rhs[j] = bc.value;
}
BoundaryConditionType::Neumann => {
let d1 = chebyshev_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
}
rhs[j] = bc.value;
}
BoundaryConditionType::Robin => {
if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
let d1 = chebyshev_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
}
rhs[j] = c_coef;
}
}
_ => {
return Err(PDEError::BoundaryConditions(
"Unsupported boundary condition type for Chebyshev spectral method"
.to_string(),
))
}
}
}
}
}
let u = ChebyshevSpectralSolver1D::solve_linear_system(&a_matrix, &rhs.view())?;
let coefficients = chebyshev_transform(&u.view());
let mut residual = Array1::zeros(n);
let a_times_u = a_matrix.dot(&u);
for i in 0..n {
residual[i] = a_times_u[i] - rhs[i];
}
let mut residual_norm = 0.0;
for i in 1..n - 1 {
residual_norm += residual[i] * residual[i];
}
residual_norm = (residual_norm / (n - 2) as f64).sqrt();
let computation_time = start_time.elapsed().as_secs_f64();
Ok(SpectralResult {
u,
coefficients,
grid,
residual_norm,
num_iterations: 1, computation_time,
convergence_history: None,
})
}
fn solve_linear_system(a: &Array2<f64>, b: &ArrayView1<f64>) -> PDEResult<Array1<f64>> {
let n = b.len();
let mut a_copy = a.clone();
let mut b_copy = b.to_owned();
for i in 0..n {
let mut max_val = a_copy[[i, i]].abs();
let mut max_row = i;
for k in i + 1..n {
if a_copy[[k, i]].abs() > max_val {
max_val = a_copy[[k, i]].abs();
max_row = k;
}
}
if max_val < 1e-10 {
return Err(PDEError::Other(
"Matrix is singular or nearly singular".to_string(),
));
}
if max_row != i {
for j in i..n {
let temp = a_copy[[i, j]];
a_copy[[i, j]] = a_copy[[max_row, j]];
a_copy[[max_row, j]] = temp;
}
let temp = b_copy[i];
b_copy[i] = b_copy[max_row];
b_copy[max_row] = temp;
}
for k in i + 1..n {
let factor = a_copy[[k, i]] / a_copy[[i, i]];
for j in i..n {
a_copy[[k, j]] -= factor * a_copy[[i, j]];
}
b_copy[k] -= factor * b_copy[i];
}
}
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
let mut sum = 0.0;
for j in i + 1..n {
sum += a_copy[[i, j]] * x[j];
}
x[i] = (b_copy[i] - sum) / a_copy[[i, i]];
}
Ok(x)
}
}
pub struct LegendreSpectralSolver1D {
domain: Domain,
source_term: Box<dyn Fn(f64) -> f64 + Send + Sync>,
boundary_conditions: Vec<BoundaryCondition<f64>>,
options: SpectralOptions,
}
impl LegendreSpectralSolver1D {
pub fn new(
domain: Domain,
source_term: impl Fn(f64) -> f64 + Send + Sync + 'static,
boundary_conditions: Vec<BoundaryCondition<f64>>,
options: Option<SpectralOptions>,
) -> PDEResult<Self> {
if domain.dimensions() != 1 {
return Err(PDEError::DomainError(
"Domain must be 1-dimensional for 1D Legendre spectral solver".to_string(),
));
}
if boundary_conditions.len() != 2 {
return Err(PDEError::BoundaryConditions(
"1D Legendre spectral solver requires exactly 2 boundary _conditions".to_string(),
));
}
let mut has_lower = false;
let mut has_upper = false;
for bc in &boundary_conditions {
match bc.location {
BoundaryLocation::Lower => has_lower = true,
BoundaryLocation::Upper => has_upper = true,
}
if bc.bc_type == BoundaryConditionType::Periodic {
return Err(PDEError::BoundaryConditions(
"Legendre spectral methods are not suitable for periodic boundary _conditions"
.to_string(),
));
}
}
if !has_lower || !has_upper {
return Err(PDEError::BoundaryConditions(
"Legendre spectral solver requires boundary _conditions at both domain endpoints"
.to_string(),
));
}
let mut options = options.unwrap_or_default();
options.basis = SpectralBasis::Legendre;
Ok(LegendreSpectralSolver1D {
domain,
source_term: Box::new(source_term),
boundary_conditions,
options,
})
}
pub fn solve(&self) -> PDEResult<SpectralResult> {
let start_time = Instant::now();
let range = &self.domain.ranges[0];
let a = range.start;
let b = range.end;
let n = self.options.num_modes;
let (lgb_grid_, weights) = legendre_points(n);
let mut grid = Array1::zeros(n);
for j in 0..n {
grid[j] = a + (b - a) * (lgb_grid_[j] + 1.0) / 2.0;
}
let mut f_values = Array1::zeros(n);
for j in 0..n {
f_values[j] = (self.source_term)(grid[j]);
}
let mut d2 = legendre_diff2_matrix(n);
let scale = 4.0 / ((b - a) * (b - a));
d2.mapv_inplace(|x| x * scale);
let mut a_matrix = d2;
let mut rhs = f_values;
for bc in &self.boundary_conditions {
match bc.location {
BoundaryLocation::Lower => {
let j = 0;
match bc.bc_type {
BoundaryConditionType::Dirichlet => {
for k in 0..n {
a_matrix[[j, k]] = 0.0;
}
a_matrix[[j, j]] = 1.0;
rhs[j] = bc.value;
}
BoundaryConditionType::Neumann => {
let d1 = legendre_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
}
rhs[j] = bc.value;
}
BoundaryConditionType::Robin => {
if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
let d1 = legendre_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
}
rhs[j] = c_coef;
}
}
_ => {
return Err(PDEError::BoundaryConditions(
"Unsupported boundary condition type for Legendre spectral method"
.to_string(),
))
}
}
}
BoundaryLocation::Upper => {
let j = n - 1;
match bc.bc_type {
BoundaryConditionType::Dirichlet => {
for k in 0..n {
a_matrix[[j, k]] = 0.0;
}
a_matrix[[j, j]] = 1.0;
rhs[j] = bc.value;
}
BoundaryConditionType::Neumann => {
let d1 = legendre_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = d1[[j, k]] * deriv_scale;
}
rhs[j] = bc.value;
}
BoundaryConditionType::Robin => {
if let Some([a_coef, b_coef, c_coef]) = bc.coefficients {
let d1 = legendre_diff_matrix(n);
let deriv_scale = 2.0 / (b - a);
for k in 0..n {
a_matrix[[j, k]] = a_coef + b_coef * d1[[j, k]] * deriv_scale;
}
rhs[j] = c_coef;
}
}
_ => {
return Err(PDEError::BoundaryConditions(
"Unsupported boundary condition type for Legendre spectral method"
.to_string(),
))
}
}
}
}
}
let u = LegendreSpectralSolver1D::solve_linear_system(&a_matrix, &rhs.view())?;
let coefficients = legendre_transform(&u.view());
let mut residual = Array1::zeros(n);
let a_times_u = a_matrix.dot(&u);
for i in 0..n {
residual[i] = a_times_u[i] - rhs[i];
}
let mut residual_norm = 0.0;
for i in 1..n - 1 {
residual_norm += residual[i] * residual[i];
}
residual_norm = (residual_norm / (n - 2) as f64).sqrt();
let computation_time = start_time.elapsed().as_secs_f64();
Ok(SpectralResult {
u,
coefficients,
grid,
residual_norm,
num_iterations: 1, computation_time,
convergence_history: None,
})
}
fn solve_linear_system(a: &Array2<f64>, b: &ArrayView1<f64>) -> PDEResult<Array1<f64>> {
let n = b.len();
let mut a_copy = a.clone();
let mut b_copy = b.to_owned();
for i in 0..n {
let mut max_val = a_copy[[i, i]].abs();
let mut max_row = i;
for k in i + 1..n {
if a_copy[[k, i]].abs() > max_val {
max_val = a_copy[[k, i]].abs();
max_row = k;
}
}
if max_val < 1e-10 {
return Err(PDEError::Other(
"Matrix is singular or nearly singular".to_string(),
));
}
if max_row != i {
for j in i..n {
let temp = a_copy[[i, j]];
a_copy[[i, j]] = a_copy[[max_row, j]];
a_copy[[max_row, j]] = temp;
}
let temp = b_copy[i];
b_copy[i] = b_copy[max_row];
b_copy[max_row] = temp;
}
for k in i + 1..n {
let factor = a_copy[[k, i]] / a_copy[[i, i]];
for j in i..n {
a_copy[[k, j]] -= factor * a_copy[[i, j]];
}
b_copy[k] -= factor * b_copy[i];
}
}
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
let mut sum = 0.0;
for j in i + 1..n {
sum += a_copy[[i, j]] * x[j];
}
x[i] = (b_copy[i] - sum) / a_copy[[i, i]];
}
Ok(x)
}
}
impl From<SpectralResult> for PDESolution<f64> {
fn from(result: SpectralResult) -> Self {
let grids = vec![result.grid.clone()];
let mut values = Vec::new();
let u_clone = result.u.clone();
let u_len = u_clone.len();
let u_reshaped = u_clone
.into_shape_with_order((u_len, 1))
.expect("Operation failed");
values.push(u_reshaped);
let info = PDESolverInfo {
num_iterations: result.num_iterations,
computation_time: result.computation_time,
residual_norm: Some(result.residual_norm),
convergence_history: result.convergence_history,
method: "Spectral Method".to_string(),
};
PDESolution {
grids,
values,
error_estimate: None,
info,
}
}
}
#[allow(dead_code)]
fn fft(x: &Array1<f64>) -> Array1<scirs2_core::numeric::Complex<f64>> {
let mut input: Vec<scirs2_core::numeric::Complex<f64>> = x
.iter()
.map(|&val| scirs2_core::numeric::Complex::new(val, 0.0))
.collect();
fft_complex(&mut input);
Array1::from_vec(input)
}
#[allow(dead_code)]
fn fft_complex(x: &mut [scirs2_core::numeric::Complex<f64>]) {
let n = x.len();
if n <= 1 {
return;
}
if n.is_power_of_two() {
fft_radix2(x);
} else {
fft_mixed_radix(x);
}
}
#[allow(dead_code)]
fn fft_radix2(x: &mut [scirs2_core::numeric::Complex<f64>]) {
let n = x.len();
if n <= 1 {
return;
}
let mut j = 0;
for i in 1..n {
let mut bit = n >> 1;
while j & bit != 0 {
j ^= bit;
bit >>= 1;
}
j ^= bit;
if j > i {
x.swap(i, j);
}
}
let mut length = 2;
while length <= n {
let angle = -2.0 * PI / (length as f64);
let wlen = scirs2_core::numeric::Complex::new(angle.cos(), angle.sin());
for i in (0..n).step_by(length) {
let mut w = scirs2_core::numeric::Complex::new(1.0, 0.0);
for j in 0..length / 2 {
let u = x[i + j];
let v = x[i + j + length / 2] * w;
x[i + j] = u + v;
x[i + j + length / 2] = u - v;
w *= wlen;
}
}
length <<= 1;
}
}
#[allow(dead_code)]
fn fft_mixed_radix(x: &mut [scirs2_core::numeric::Complex<f64>]) {
let n = x.len();
if n < 32 || !n.is_power_of_two() {
let input = x.to_vec();
for k in 0..n {
let mut sum = scirs2_core::numeric::Complex::new(0.0, 0.0);
for j in 0..n {
let angle = -2.0 * PI * (j as f64) * (k as f64) / (n as f64);
let factor = scirs2_core::numeric::Complex::new(angle.cos(), angle.sin());
sum += factor * input[j];
}
x[k] = sum;
}
} else {
fft_radix2(x);
}
}
#[allow(dead_code)]
fn ifft(
x: &Array1<scirs2_core::numeric::Complex<f64>>,
) -> Array1<scirs2_core::numeric::Complex<f64>> {
let n = x.len();
let mut input: Vec<scirs2_core::numeric::Complex<f64>> = x.to_vec();
for val in &mut input {
*val = val.conj();
}
fft_complex(&mut input);
let scale = 1.0 / (n as f64);
for val in &mut input {
*val = val.conj() * scale;
}
Array1::from_vec(input)
}
#[allow(dead_code)]
fn rfft(x: &Array1<f64>) -> Array1<scirs2_core::numeric::Complex<f64>> {
let n = x.len();
let full_fft = fft(x);
let rfft_size = n / 2 + 1;
let mut result = Array1::zeros(rfft_size);
for i in 0..rfft_size {
result[i] = full_fft[i];
}
result
}
#[allow(dead_code)]
fn irfft_with_size(x: &Array1<scirs2_core::numeric::Complex<f64>>, n: usize) -> Array1<f64> {
let mut full_spectrum = Array1::zeros(n);
let rfft_size = x.len();
for i in 0..rfft_size {
full_spectrum[i] = x[i];
}
for i in 1..n / 2 {
full_spectrum[n - i] = x[i].conj();
}
let complex_result = ifft(&full_spectrum);
let mut result = Array1::zeros(n);
for i in 0..n {
result[i] = complex_result[i].re;
}
result
}
#[allow(dead_code)]
fn irfft(x: &Array1<scirs2_core::numeric::Complex<f64>>) -> Array1<f64> {
let n = 2 * (x.len() - 1);
irfft_with_size(x, n)
}
pub mod spectral_element;
pub use spectral_element::{
QuadElement, SpectralElementMesh2D, SpectralElementOptions, SpectralElementPoisson2D,
SpectralElementResult,
};