use super::functions::*;
pub struct ChebyshevDiffMatrix {
pub n_nodes: usize,
pub nodes: Vec<f64>,
pub d_matrix: Vec<Vec<f64>>,
}
impl ChebyshevDiffMatrix {
pub fn new(n: usize) -> Self {
let nodes: Vec<f64> = (0..=n)
.map(|k| (k as f64 * std::f64::consts::PI / n as f64).cos())
.collect();
let np1 = n + 1;
let c: Vec<f64> = (0..=n)
.map(|k| if k == 0 || k == n { 2.0 } else { 1.0 })
.collect();
let mut d = vec![vec![0.0; np1]; np1];
for i in 0..np1 {
for j in 0..np1 {
if i == j {
continue;
}
let ci = c[i];
let cj = c[j];
d[i][j] =
(ci / cj) * (if (i + j) % 2 == 0 { 1.0 } else { -1.0 }) / (nodes[i] - nodes[j]);
}
}
for (i, row) in d.iter_mut().enumerate() {
let sum: f64 = row
.iter()
.enumerate()
.filter(|&(j, _)| j != i)
.map(|(_, &v)| v)
.sum();
row[i] = -sum;
}
d[0][0] = (2.0 * (n as f64) * (n as f64) + 1.0) / 6.0;
d[n][n] = -(2.0 * (n as f64) * (n as f64) + 1.0) / 6.0;
ChebyshevDiffMatrix {
n_nodes: np1,
nodes,
d_matrix: d,
}
}
pub fn differentiate(&self, u: &[f64]) -> Vec<f64> {
let n = self.n_nodes;
let mut du = vec![0.0; n];
for (i, du_i) in du.iter_mut().enumerate() {
for (j, &u_j) in u.iter().enumerate() {
*du_i += self.d_matrix[i][j] * u_j;
}
}
du
}
pub fn row_sum(&self, i: usize) -> f64 {
self.d_matrix[i].iter().sum()
}
}
pub struct SpectralElement2D {
pub degree: usize,
pub nodes_1d: Vec<f64>,
pub weights_1d: Vec<f64>,
pub d1: Vec<Vec<f64>>,
pub dims: [f64; 2],
}
impl SpectralElement2D {
pub fn new(degree: usize, dims: [f64; 2]) -> Self {
let (nodes_1d, weights_1d) = gll_nodes_weights(degree);
let d1 = spectral_derivative_matrix(&nodes_1d);
SpectralElement2D {
degree,
nodes_1d,
weights_1d,
d1,
dims,
}
}
pub fn n_nodes_1d(&self) -> usize {
self.nodes_1d.len()
}
pub fn n_nodes(&self) -> usize {
let n = self.n_nodes_1d();
n * n
}
pub fn jacobian(&self) -> f64 {
(self.dims[0] / 2.0) * (self.dims[1] / 2.0)
}
pub fn mass_matrix_diagonal(&self) -> Vec<f64> {
let n = self.n_nodes_1d();
let j = self.jacobian();
let mut m = Vec::with_capacity(n * n);
for i in 0..n {
for j_idx in 0..n {
m.push(self.weights_1d[i] * self.weights_1d[j_idx] * j);
}
}
m
}
pub fn stiffness_matrix(&self) -> Vec<Vec<f64>> {
let n = self.n_nodes_1d();
let nn = n * n;
let jac = self.jacobian();
let jx = self.dims[0] / 2.0;
let jy = self.dims[1] / 2.0;
let mut k = vec![vec![0.0; nn]; nn];
let mut kx = vec![vec![0.0; n]; n];
let mut ky = vec![vec![0.0; n]; n];
for a in 0..n {
for b in 0..n {
for q in 0..n {
kx[a][b] += self.weights_1d[q] * self.d1[q][a] * self.d1[q][b];
ky[a][b] += self.weights_1d[q] * self.d1[q][a] * self.d1[q][b];
}
}
}
for (i, kx_row) in kx.iter().enumerate() {
for (j_idx, &w_jidx) in self.weights_1d.iter().enumerate() {
let row = i * n + j_idx;
for (kk, &kx_ikk) in kx_row.iter().enumerate() {
for (l, &ky_jl) in ky[j_idx].iter().enumerate() {
let col = kk * n + l;
let term_x =
(jy / jx) * kx_ikk * w_jidx * (if j_idx == l { 1.0 } else { 0.0 });
let term_y = (jx / jy)
* self.weights_1d[i]
* (if i == kk { 1.0 } else { 0.0 })
* ky_jl;
k[row][col] = (term_x + term_y) * jac / (jx * jy);
}
}
}
}
k
}
}
pub struct SpectralConvergence {
pub nodes: Vec<f64>,
pub weights: Vec<f64>,
}
impl SpectralConvergence {
pub fn new(degree: usize) -> Self {
let (nodes, weights) = gll_nodes_weights(degree);
SpectralConvergence { nodes, weights }
}
pub fn l2_interp_error<F: Fn(f64) -> f64>(&self, f: F) -> f64 {
let nodal_values: Vec<f64> = self.nodes.iter().map(|&x| f(x)).collect();
let interp = SpectralInterpolation::new(self.nodes.clone());
let n_fine = 4 * (self.nodes.len() + 1);
let mut err_sq = 0.0;
for k in 0..n_fine {
let xi = -1.0 + (2.0 * k as f64 + 1.0) / n_fine as f64;
let u_h = interp.interpolate(&nodal_values, xi);
let err = u_h - f(xi);
err_sq += err * err;
}
(err_sq / n_fine as f64).sqrt()
}
pub fn convergence_rate<F: Fn(f64) -> f64 + Copy>(f: F, p1: usize, p2: usize) -> f64 {
let sc1 = SpectralConvergence::new(p1);
let sc2 = SpectralConvergence::new(p2);
let e1 = sc1.l2_interp_error(f).max(1e-300);
let e2 = sc2.l2_interp_error(f).max(1e-300);
(e2.ln() - e1.ln()) / ((p2 as f64).ln() - (p1 as f64).ln())
}
}
pub struct SpectralConvection {
pub element: SpectralElement1D,
pub velocity: f64,
}
impl SpectralConvection {
pub fn new(degree: usize, length: f64, velocity: f64) -> Self {
SpectralConvection {
element: SpectralElement1D::new(degree, length),
velocity,
}
}
pub fn flux_divergence(&self, u: &[f64]) -> Vec<f64> {
let du = self.element.differentiate(u);
du.iter().map(|&d| self.velocity * d).collect()
}
pub fn step_euler(&self, u: &[f64], dt: f64) -> Vec<f64> {
let rhs = self.flux_divergence(u);
u.iter()
.zip(rhs.iter())
.map(|(&un, &r)| un - dt * r)
.collect()
}
pub fn upwind_flux_left(&self, u_left: f64, u_right: f64) -> f64 {
if self.velocity >= 0.0 {
self.velocity * u_left
} else {
self.velocity * u_right
}
}
}
pub struct ChebyshevSpectral {
pub n_modes: usize,
pub nodes: Vec<f64>,
}
impl ChebyshevSpectral {
pub fn new(n_modes: usize) -> Self {
let n = n_modes;
let nodes: Vec<f64> = (0..=n)
.map(|k| (k as f64 * std::f64::consts::PI / n as f64).cos())
.collect();
ChebyshevSpectral { n_modes, nodes }
}
pub fn to_coefficients(&self, values: &[f64]) -> Vec<f64> {
let n = self.n_modes;
let np1 = n + 1;
let mut coeff = vec![0.0; np1];
for (k, coeff_k) in coeff.iter_mut().enumerate() {
let sum: f64 = values
.iter()
.enumerate()
.map(|(j, &vj)| {
let wj = if j == 0 || j == n { 0.5 } else { 1.0 };
wj * vj * (k as f64 * std::f64::consts::PI * j as f64 / n as f64).cos()
})
.sum();
*coeff_k = 2.0 / n as f64 * sum;
}
coeff
}
pub fn to_nodal(&self, coeff: &[f64]) -> Vec<f64> {
let n = self.n_modes;
let np1 = n + 1;
let mut values = vec![0.0; np1];
for (j, val_j) in values.iter_mut().enumerate() {
*val_j = coeff
.iter()
.enumerate()
.map(|(k, &ck)| {
let wk = if k == 0 || k == n { 0.5 } else { 1.0 };
wk * ck * (k as f64 * std::f64::consts::PI * j as f64 / n as f64).cos()
})
.sum();
}
values
}
pub fn differentiate(&self, values: &[f64]) -> Vec<f64> {
let n = self.n_modes;
let np1 = n + 1;
let coeff = self.to_coefficients(values);
let mut a_std = coeff.clone();
a_std[0] /= 2.0;
a_std[n] /= 2.0;
let mut da_std = vec![0.0; np1];
for k in (0..n).rev() {
let next2 = if k + 2 <= n { da_std[k + 2] } else { 0.0 };
let next1 = if k < n { a_std[k + 1] } else { 0.0 };
let val = 2.0 * (k + 1) as f64 * next1 + next2;
da_std[k] = if k == 0 { val / 2.0 } else { val };
}
let mut da = da_std;
da[0] *= 2.0;
da[n] *= 2.0;
self.to_nodal(&da)
}
pub fn dealias_filter(&self, coeff: &mut [f64], retain_fraction: f64) {
let keep = ((self.n_modes as f64 * retain_fraction) as usize).min(self.n_modes);
for c in coeff.iter_mut().skip(keep) {
*c = 0.0;
}
}
}
pub struct SpectralHelmholtz {
pub element: SpectralElement1D,
pub k: f64,
}
impl SpectralHelmholtz {
pub fn new(degree: usize, length: f64, k: f64) -> Self {
SpectralHelmholtz {
element: SpectralElement1D::new(degree, length),
k,
}
}
pub fn system_matrix(&self) -> Vec<Vec<f64>> {
let kmat = self.element.stiffness_matrix();
let mdiag = self.element.mass_matrix_diagonal();
let n = kmat.len();
let mut a = kmat;
for i in 0..n {
a[i][i] -= self.k * self.k * mdiag[i];
}
a
}
pub fn apply_dirichlet(&self, a: &mut [Vec<f64>], rhs: &mut [f64]) {
let n = a.len();
if n == 0 {
return;
}
for v in a[0].iter_mut() {
*v = 0.0;
}
a[0][0] = 1.0;
rhs[0] = 0.0;
for v in a[n - 1].iter_mut() {
*v = 0.0;
}
a[n - 1][n - 1] = 1.0;
rhs[n - 1] = 0.0;
}
}
pub struct SemEllipticSolver {
pub mesh: HpMesh1D,
pub mapping: GlobalLocalMapping,
pub k_global: Vec<Vec<f64>>,
pub rhs: Vec<f64>,
}
impl SemEllipticSolver {
pub fn new(mesh: HpMesh1D) -> Self {
let mapping = GlobalLocalMapping::from_hp_mesh(&mesh);
let ng = mapping.n_global;
let mut k_global = vec![vec![0.0; ng]; ng];
let mut rhs = vec![0.0; ng];
for e in 0..mesh.n_elem() {
let elem = SpectralElement1D::new(mesh.degrees[e], mesh.lengths[e]);
let k_loc = elem.stiffness_matrix();
for (i, k_loc_row) in k_loc.iter().enumerate() {
let gi = mapping.conn[e][i];
for (j, &k_loc_ij) in k_loc_row.iter().enumerate() {
let gj = mapping.conn[e][j];
k_global[gi][gj] += k_loc_ij;
}
}
}
Self::apply_dirichlet_bc(&mut k_global, &mut rhs, 0);
Self::apply_dirichlet_bc(&mut k_global, &mut rhs, ng - 1);
SemEllipticSolver {
mesh,
mapping,
k_global,
rhs,
}
}
fn apply_dirichlet_bc(k: &mut [Vec<f64>], rhs: &mut [f64], idx: usize) {
for v in k[idx].iter_mut() {
*v = 0.0;
}
k[idx][idx] = 1.0;
rhs[idx] = 0.0;
let n = k.len();
for i in 0..n {
if i != idx {
rhs[i] -= k[i][idx] * rhs[idx];
k[i][idx] = 0.0;
}
}
}
pub fn load_rhs<F: Fn(f64) -> f64>(&mut self, f: F) {
let ng = self.mapping.n_global;
self.rhs = vec![0.0; ng];
let mut x_offset = 0.0;
for e in 0..self.mesh.n_elem() {
let elem = SpectralElement1D::new(self.mesh.degrees[e], self.mesh.lengths[e]);
let j = elem.jacobian();
let _n_loc = elem.nodes.len();
let m_diag = elem.mass_matrix_diagonal();
for (i, (&node, &mdi)) in elem.nodes.iter().zip(m_diag.iter()).enumerate() {
let x_phys = x_offset + (node + 1.0) / 2.0 * self.mesh.lengths[e];
let gi = self.mapping.conn[e][i];
self.rhs[gi] += mdi / j * f(x_phys);
}
x_offset += self.mesh.lengths[e];
}
self.rhs[0] = 0.0;
let last = self.mapping.n_global - 1;
self.rhs[last] = 0.0;
}
pub fn solve(&self) -> Vec<f64> {
let n = self.k_global.len();
if n == 0 {
return vec![];
}
let mut a = self.k_global.clone();
let mut b = self.rhs.clone();
for col in 0..n {
let mut pivot_row = col;
let mut max_val = a[col][col].abs();
for (row, a_row) in a.iter().enumerate().skip(col + 1) {
if a_row[col].abs() > max_val {
max_val = a_row[col].abs();
pivot_row = row;
}
}
a.swap(col, pivot_row);
b.swap(col, pivot_row);
let diag = a[col][col];
if diag.abs() < 1e-14 {
continue;
}
let col_slice: Vec<f64> = a[col][col..].to_vec();
for row in col + 1..n {
let factor = a[row][col] / diag;
for (off, &cv) in col_slice.iter().enumerate() {
a[row][col + off] -= factor * cv;
}
b[row] -= factor * b[col];
}
}
let mut x = vec![0.0; n];
for i in (0..n).rev() {
let mut sum = b[i];
for j in i + 1..n {
sum -= a[i][j] * x[j];
}
x[i] = if a[i][i].abs() > 1e-14 {
sum / a[i][i]
} else {
0.0
};
}
x
}
}
pub struct SpectralMassMatrix {
pub diagonal: Vec<f64>,
}
impl SpectralMassMatrix {
pub fn from_1d_element(degree: usize, length: f64) -> Self {
let elem = SpectralElement1D::new(degree, length);
SpectralMassMatrix {
diagonal: elem.mass_matrix_diagonal(),
}
}
pub fn from_2d_element(degree: usize, dims: [f64; 2]) -> Self {
let elem = SpectralElement2D::new(degree, dims);
SpectralMassMatrix {
diagonal: elem.mass_matrix_diagonal(),
}
}
pub fn scale(&mut self, s: f64) {
for d in &mut self.diagonal {
*d *= s;
}
}
pub fn inverse(&self) -> Vec<f64> {
self.diagonal
.iter()
.map(|&m| if m.abs() > 1e-300 { 1.0 / m } else { 0.0 })
.collect()
}
}
pub struct SpectralStiffness {
pub matrix: Vec<Vec<f64>>,
pub degree: usize,
}
impl SpectralStiffness {
pub fn from_1d_element(degree: usize, length: f64) -> Self {
let elem = SpectralElement1D::new(degree, length);
SpectralStiffness {
matrix: elem.stiffness_matrix(),
degree,
}
}
pub fn get(&self, i: usize, j: usize) -> f64 {
self.matrix[i][j]
}
pub fn row_sum(&self, i: usize) -> f64 {
self.matrix[i].iter().sum()
}
}
pub struct HpMesh1D {
pub lengths: Vec<f64>,
pub degrees: Vec<usize>,
}
impl HpMesh1D {
pub fn uniform(n_elem: usize, total_length: f64, degree: usize) -> Self {
let h = total_length / n_elem as f64;
HpMesh1D {
lengths: vec![h; n_elem],
degrees: vec![degree; n_elem],
}
}
pub fn n_elem(&self) -> usize {
self.lengths.len()
}
pub fn n_dofs(&self) -> usize {
if self.lengths.is_empty() {
return 0;
}
self.degrees.iter().sum::<usize>() + 1
}
pub fn p_refine(&mut self, elem_idx: usize, delta: usize) {
if elem_idx < self.degrees.len() {
self.degrees[elem_idx] += delta;
}
}
pub fn h_refine(&mut self, elem_idx: usize) {
if elem_idx < self.lengths.len() {
let h = self.lengths[elem_idx] / 2.0;
let deg = self.degrees[elem_idx];
self.lengths.remove(elem_idx);
self.degrees.remove(elem_idx);
self.lengths.insert(elem_idx, h);
self.lengths.insert(elem_idx + 1, h);
self.degrees.insert(elem_idx, deg);
self.degrees.insert(elem_idx + 1, deg);
}
}
}
pub struct LegendrePolynomial {
pub n: usize,
}
impl LegendrePolynomial {
pub fn new(n: usize) -> Self {
LegendrePolynomial { n }
}
pub fn evaluate(&self, x: f64) -> f64 {
legendre_pn(self.n, x)
}
pub fn derivative(&self, x: f64) -> f64 {
legendre_evaluate(self.n, x).1
}
pub fn zeros(&self) -> Vec<f64> {
legendre_zeros(self.n)
}
}
pub struct SpectralBoundaryIntegral {
pub nodes: Vec<[f64; 2]>,
pub normals: Vec<[f64; 2]>,
}
impl SpectralBoundaryIntegral {
pub fn new(nodes: Vec<[f64; 2]>, normals: Vec<[f64; 2]>) -> Self {
SpectralBoundaryIntegral { nodes, normals }
}
pub fn h_matrix(&self) -> Vec<Vec<f64>> {
let n = self.nodes.len();
let mut h = vec![vec![0.0; n]; n];
for (i, h_row) in h.iter_mut().enumerate() {
for (j, h_ij) in h_row.iter_mut().enumerate() {
if i == j {
continue;
}
*h_ij = green_normal_derivative_2d(self.nodes[i], self.nodes[j], self.normals[j]);
}
}
h
}
pub fn g_matrix(&self) -> Vec<Vec<f64>> {
let n = self.nodes.len();
let mut g = vec![vec![0.0; n]; n];
for (i, g_row) in g.iter_mut().enumerate() {
for (j, g_ij) in g_row.iter_mut().enumerate() {
if i == j {
continue;
}
*g_ij = green_function_2d(self.nodes[i], self.nodes[j]);
}
}
g
}
}
pub struct GaussLobattoLegendre {
pub degree: usize,
pub nodes: Vec<f64>,
pub weights: Vec<f64>,
}
impl GaussLobattoLegendre {
pub fn new(degree: usize) -> Self {
let (nodes, weights) = gll_nodes_weights(degree);
GaussLobattoLegendre {
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 n_points(&self) -> usize {
self.nodes.len()
}
}
pub struct SpectralInterpolation {
pub nodes: Vec<f64>,
pub bary_weights: Vec<f64>,
}
impl SpectralInterpolation {
pub fn new(nodes: Vec<f64>) -> Self {
let n = nodes.len();
let mut bary_weights = vec![1.0f64; n];
for j in 0..n {
for k in 0..n {
if k == j {
continue;
}
let diff = nodes[j] - nodes[k];
if diff.abs() > 1e-300 {
bary_weights[j] /= diff;
}
}
}
SpectralInterpolation {
nodes,
bary_weights,
}
}
pub fn interpolate(&self, values: &[f64], x: f64) -> f64 {
chebyshev_interp(&self.nodes, values, x)
}
pub fn interpolate_many(&self, values: &[f64], points: &[f64]) -> Vec<f64> {
points
.iter()
.map(|&x| self.interpolate(values, x))
.collect()
}
}
pub struct GlobalLocalMapping {
pub conn: Vec<Vec<usize>>,
pub n_global: usize,
}
impl GlobalLocalMapping {
pub fn from_hp_mesh(mesh: &HpMesh1D) -> Self {
let n_elem = mesh.n_elem();
let mut conn: Vec<Vec<usize>> = Vec::with_capacity(n_elem);
let mut global_idx = 0usize;
for e in 0..n_elem {
let n_local = mesh.degrees[e] + 1;
let mut local_conn = Vec::with_capacity(n_local);
if e == 0 {
for i in 0..n_local {
local_conn.push(global_idx + i);
}
global_idx += n_local;
} else {
local_conn.push(
*conn[e - 1]
.last()
.expect("previous element connectivity is non-empty"),
);
for i in 1..n_local {
local_conn.push(global_idx + i - 1);
}
global_idx += n_local - 1;
}
conn.push(local_conn);
}
GlobalLocalMapping {
conn,
n_global: global_idx,
}
}
pub fn scatter(&self, elem: usize, u_loc: &[f64], u_glob: &mut [f64]) {
for (i, &g) in self.conn[elem].iter().enumerate() {
if g < u_glob.len() {
u_glob[g] += u_loc[i];
}
}
}
pub fn gather(&self, elem: usize, u_glob: &[f64]) -> Vec<f64> {
self.conn[elem]
.iter()
.map(|&g| if g < u_glob.len() { u_glob[g] } else { 0.0 })
.collect()
}
}
pub struct ModalNodalTransform {
pub degree: usize,
pub nodes: Vec<f64>,
pub weights: Vec<f64>,
pub vandermonde: Vec<Vec<f64>>,
}
impl ModalNodalTransform {
pub fn new(degree: usize) -> Self {
let (nodes, weights) = gll_nodes_weights(degree);
let n = nodes.len();
let mut vandermonde = vec![vec![0.0; n]; n];
for (v_row, &node_i) in vandermonde.iter_mut().zip(nodes.iter()) {
for (k, v_ik) in v_row.iter_mut().enumerate() {
let pk = legendre_pn(k, node_i);
let norm = ((2 * k + 1) as f64 / 2.0).sqrt();
*v_ik = pk * norm;
}
}
ModalNodalTransform {
degree,
nodes,
weights,
vandermonde,
}
}
pub fn nodal_to_modal(&self, u_nodal: &[f64]) -> Vec<f64> {
let n = self.nodes.len();
let mut modal = vec![0.0; n];
for (k, modal_k) in modal.iter_mut().enumerate() {
let norm_sq = 2.0 / (2 * k + 1) as f64;
let sum: f64 = self
.weights
.iter()
.zip(self.nodes.iter())
.zip(u_nodal.iter())
.map(|((&w, &node), &u)| w * legendre_pn(k, node) * u)
.sum();
*modal_k = sum / norm_sq;
}
modal
}
pub fn modal_to_nodal(&self, modal: &[f64]) -> Vec<f64> {
let n = self.nodes.len();
let mut nodal = vec![0.0; n];
for (nodal_i, &node_i) in nodal.iter_mut().zip(self.nodes.iter()) {
*nodal_i = modal
.iter()
.enumerate()
.map(|(k, &m)| m * legendre_pn(k, node_i))
.sum();
}
nodal
}
}
pub struct SpectralElement1D {
pub degree: usize,
pub nodes: Vec<f64>,
pub weights: Vec<f64>,
pub d_matrix: Vec<Vec<f64>>,
pub length: f64,
}
impl SpectralElement1D {
pub fn new(degree: usize, length: f64) -> Self {
let (nodes, weights) = gll_nodes_weights(degree);
let d_matrix = spectral_derivative_matrix(&nodes);
SpectralElement1D {
degree,
nodes,
weights,
d_matrix,
length,
}
}
pub fn jacobian(&self) -> f64 {
self.length / 2.0
}
pub fn mass_matrix_diagonal(&self) -> Vec<f64> {
let j = self.jacobian();
self.weights.iter().map(|&w| w * j).collect()
}
pub fn stiffness_matrix(&self) -> Vec<Vec<f64>> {
let n = self.nodes.len();
let j = self.jacobian();
let mut k = vec![vec![0.0; n]; n];
for (i, k_row) in k.iter_mut().enumerate() {
for (j_idx, k_ij) in k_row.iter_mut().enumerate() {
let kij: f64 = self
.weights
.iter()
.zip(self.d_matrix.iter())
.map(|(&w, d_q)| w * d_q[i] * d_q[j_idx])
.sum();
*k_ij = kij / j;
}
}
k
}
pub fn differentiate(&self, u: &[f64]) -> Vec<f64> {
let j = self.jacobian();
let mut du = vec![0.0; self.nodes.len()];
for (du_i, d_row) in du.iter_mut().zip(self.d_matrix.iter()) {
*du_i = d_row
.iter()
.zip(u.iter())
.map(|(&d, &u_j)| d * u_j)
.sum::<f64>()
/ j;
}
du
}
}