#[derive(Debug, Clone)]
pub struct MixedElement {
pub n_vel_nodes: usize,
pub n_pres_nodes: usize,
pub vel_dofs_per_node: usize,
pub stab_param: f64,
pub penalty: f64,
}
impl MixedElement {
pub fn new(
n_vel_nodes: usize,
n_pres_nodes: usize,
vel_dofs_per_node: usize,
stab_param: f64,
penalty: f64,
) -> Self {
Self {
n_vel_nodes,
n_pres_nodes,
vel_dofs_per_node,
stab_param,
penalty,
}
}
pub fn n_vel_dofs(&self) -> usize {
self.n_vel_nodes * self.vel_dofs_per_node
}
pub fn n_pres_dofs(&self) -> usize {
self.n_pres_nodes
}
}
pub fn pressure_stabilization(
h_elem: f64,
alpha: f64,
n_pres: usize,
grad_p: &[[f64; 2]],
weight: f64,
) -> Vec<f64> {
assert_eq!(grad_p.len(), n_pres);
let mut s = vec![0.0f64; n_pres * n_pres];
let coeff = alpha * h_elem * h_elem * weight;
for i in 0..n_pres {
for j in 0..n_pres {
let dot = grad_p[i][0] * grad_p[j][0] + grad_p[i][1] * grad_p[j][1];
s[i * n_pres + j] += coeff * dot;
}
}
s
}
pub fn inf_sup_check(b_matrix: &[f64], n_pres: usize, n_vel_dofs: usize) -> f64 {
assert_eq!(b_matrix.len(), n_pres * n_vel_dofs);
let m = n_pres;
let n = n_vel_dofs;
let mut bbt = vec![0.0f64; m * m];
for i in 0..m {
for j in 0..m {
let mut s = 0.0;
for k in 0..n {
s += b_matrix[i * n + k] * b_matrix[j * n + k];
}
bbt[i * m + j] = s;
}
}
let mut min_diag = f64::MAX;
for i in 0..m {
let diag = bbt[i * m + i];
let mut row_sum = 0.0;
for j in 0..m {
if i != j {
row_sum += bbt[i * m + j].abs();
}
}
let lower = diag - row_sum;
if lower < min_diag {
min_diag = lower;
}
}
if min_diag < 0.0 { 0.0 } else { min_diag.sqrt() }
}
pub fn stokes_element_matrix(
viscosity: f64,
grad_v: &[[f64; 2]],
_grad_p: &[[f64; 2]],
phi_p: &[f64],
div_v: &[f64],
weight: f64,
dim: usize,
) -> (Vec<f64>, Vec<f64>) {
let n_vel_nodes = grad_v.len();
let n_pres_nodes = phi_p.len();
let n_vel_dofs = n_vel_nodes * dim;
let n_pres_dofs = n_pres_nodes;
let mut k = vec![0.0f64; n_vel_dofs * n_vel_dofs];
for i in 0..n_vel_nodes {
for j in 0..n_vel_nodes {
let dot = grad_v[i][0] * grad_v[j][0] + grad_v[i][1] * grad_v[j][1];
let val = viscosity * weight * dot;
for d in 0..dim {
let row = i * dim + d;
let col = j * dim + d;
k[row * n_vel_dofs + col] += val;
}
}
}
let mut b = vec![0.0f64; n_pres_dofs * n_vel_dofs];
let _ = div_v; for i in 0..n_pres_nodes {
for (j, grad_vj) in grad_v.iter().enumerate().take(n_vel_nodes) {
for (d, &gvjd) in grad_vj.iter().enumerate().take(dim) {
let col = j * dim + d;
b[i * n_vel_dofs + col] += -weight * phi_p[i] * gvjd;
}
}
}
(k, b)
}
pub fn incompressibility_penalty(
k: &mut [f64],
b: &[f64],
penalty: f64,
n_vel_dofs: usize,
n_pres_dofs: usize,
) {
for i in 0..n_vel_dofs {
for j in 0..n_vel_dofs {
let mut s = 0.0;
for p in 0..n_pres_dofs {
s += b[p * n_vel_dofs + i] * b[p * n_vel_dofs + j];
}
k[i * n_vel_dofs + j] += penalty * s;
}
}
}
pub fn bubble_function(lam: &[f64; 3]) -> f64 {
lam[0] * lam[1] * lam[2]
}
pub fn bubble_function_grad(lam: &[f64; 3]) -> [f64; 3] {
[lam[1] * lam[2], lam[0] * lam[2], lam[0] * lam[1]]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mixed_element_new() {
let e = MixedElement::new(6, 3, 2, 0.1, 1e6);
assert_eq!(e.n_vel_nodes, 6);
assert_eq!(e.n_pres_nodes, 3);
}
#[test]
fn test_mixed_element_dofs() {
let e = MixedElement::new(6, 3, 2, 0.1, 1e6);
assert_eq!(e.n_vel_dofs(), 12);
assert_eq!(e.n_pres_dofs(), 3);
}
#[test]
fn test_mixed_element_3d() {
let e = MixedElement::new(10, 4, 3, 0.05, 1e8);
assert_eq!(e.n_vel_dofs(), 30);
assert_eq!(e.n_pres_dofs(), 4);
}
#[test]
fn test_mixed_element_stab_param() {
let e = MixedElement::new(6, 3, 2, 0.25, 1e6);
assert!((e.stab_param - 0.25).abs() < 1e-14);
}
#[test]
fn test_mixed_element_penalty() {
let e = MixedElement::new(6, 3, 2, 0.1, 1e9);
assert!((e.penalty - 1e9).abs() < 1.0);
}
#[test]
fn test_pressure_stab_size() {
let grad_p = [[1.0, 0.0], [0.0, 1.0], [-1.0, -1.0]];
let s = pressure_stabilization(0.1, 0.1, 3, &grad_p, 0.5);
assert_eq!(s.len(), 9);
}
#[test]
fn test_pressure_stab_symmetry() {
let grad_p = [[1.0, 2.0], [3.0, 4.0], [-1.0, 0.5]];
let s = pressure_stabilization(0.2, 0.1, 3, &grad_p, 1.0);
for i in 0..3 {
for j in 0..3 {
let diff = (s[i * 3 + j] - s[j * 3 + i]).abs();
assert!(diff < 1e-14, "S not symmetric at ({i},{j})");
}
}
}
#[test]
fn test_pressure_stab_positive_semidefinite() {
let grad_p = [[1.0, 0.0], [0.0, 1.0]];
let s = pressure_stabilization(1.0, 1.0, 2, &grad_p, 1.0);
assert!(s[0] >= 0.0);
assert!(s[3] >= 0.0);
}
#[test]
fn test_pressure_stab_zero_alpha() {
let grad_p = [[1.0, 0.0], [0.0, 1.0]];
let s = pressure_stabilization(1.0, 0.0, 2, &grad_p, 1.0);
for &v in &s {
assert!(v.abs() < 1e-14);
}
}
#[test]
fn test_pressure_stab_scales_with_h_squared() {
let grad_p = [[1.0, 0.0]];
let s1 = pressure_stabilization(1.0, 1.0, 1, &grad_p, 1.0);
let s2 = pressure_stabilization(2.0, 1.0, 1, &grad_p, 1.0);
let ratio = s2[0] / s1[0];
assert!((ratio - 4.0).abs() < 1e-10);
}
#[test]
fn test_inf_sup_identity_like() {
let b = vec![1.0, 0.0, 0.0, 1.0];
let val = inf_sup_check(&b, 2, 2);
assert!(val >= 0.0);
}
#[test]
fn test_inf_sup_non_negative() {
let b = vec![1.0, 2.0, 3.0, 0.5, 1.0, 0.0];
let val = inf_sup_check(&b, 2, 3);
assert!(val >= 0.0);
}
#[test]
fn test_inf_sup_zero_matrix() {
let b = vec![0.0; 6];
let val = inf_sup_check(&b, 2, 3);
assert!(val >= 0.0);
}
#[test]
fn test_inf_sup_1x1() {
let b = vec![3.0];
let val = inf_sup_check(&b, 1, 1);
assert!((val - 3.0).abs() < 1e-10);
}
#[test]
fn test_inf_sup_larger() {
let b: Vec<f64> = vec![1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0];
let val = inf_sup_check(&b, 3, 4);
assert!(val >= 0.0);
}
#[test]
fn test_stokes_k_size() {
let grad_v = [[1.0, 0.0], [0.0, 1.0], [-1.0, -1.0]];
let grad_p = [[1.0, 0.0], [-1.0, 0.0]];
let phi_p = [0.5, 0.5];
let div_v = [1.0, 1.0, -2.0];
let (k, _b) = stokes_element_matrix(1.0, &grad_v, &grad_p, &phi_p, &div_v, 1.0, 2);
assert_eq!(k.len(), 36);
}
#[test]
fn test_stokes_b_size() {
let grad_v = [[1.0, 0.0], [0.0, 1.0], [-1.0, -1.0]];
let grad_p = [[1.0, 0.0], [-1.0, 0.0]];
let phi_p = [0.5, 0.5];
let div_v = [1.0, 1.0, -2.0];
let (_k, b) = stokes_element_matrix(1.0, &grad_v, &grad_p, &phi_p, &div_v, 1.0, 2);
assert_eq!(b.len(), 12);
}
#[test]
fn test_stokes_k_symmetry() {
let grad_v = [[1.0, 2.0], [3.0, 4.0], [-1.0, 0.5]];
let grad_p = [[1.0, 0.0]];
let phi_p = [1.0];
let div_v = [3.0, 7.0, -0.5];
let (k, _) = stokes_element_matrix(1.0, &grad_v, &grad_p, &phi_p, &div_v, 1.0, 2);
let n = 6;
for i in 0..n {
for j in 0..n {
let diff = (k[i * n + j] - k[j * n + i]).abs();
assert!(diff < 1e-12, "K not symmetric at ({i},{j})");
}
}
}
#[test]
fn test_stokes_viscosity_scaling() {
let grad_v = [[1.0, 0.0], [0.0, 1.0]];
let phi_p = [1.0];
let grad_p = [[0.5, 0.5]];
let div_v = [1.0, 1.0];
let (k1, _) = stokes_element_matrix(1.0, &grad_v, &grad_p, &phi_p, &div_v, 1.0, 2);
let (k2, _) = stokes_element_matrix(2.0, &grad_v, &grad_p, &phi_p, &div_v, 1.0, 2);
for (a, b) in k1.iter().zip(k2.iter()) {
assert!((b - 2.0 * a).abs() < 1e-12);
}
}
#[test]
fn test_stokes_k_positive_diagonal() {
let grad_v = [[1.0, 0.0], [0.0, 1.0]];
let phi_p = [1.0];
let grad_p = [[0.5, 0.5]];
let div_v = [1.0, 1.0];
let (k, _) = stokes_element_matrix(1.0, &grad_v, &grad_p, &phi_p, &div_v, 1.0, 2);
let n = 4;
for i in 0..n {
assert!(k[i * n + i] >= 0.0, "Negative diagonal at {i}");
}
}
#[test]
fn test_penalty_increases_k() {
let mut k = vec![1.0; 4];
let b = vec![1.0, 0.0, 0.0, 1.0]; let k_before = k.clone();
incompressibility_penalty(&mut k, &b, 1.0, 2, 2);
assert!(k[0] >= k_before[0]);
}
#[test]
fn test_penalty_zero_no_change() {
let mut k = vec![2.0, 0.0, 0.0, 2.0];
let k_orig = k.clone();
let b = vec![1.0, 0.0, 0.0, 1.0];
incompressibility_penalty(&mut k, &b, 0.0, 2, 2);
for (a, b) in k.iter().zip(k_orig.iter()) {
assert!((a - b).abs() < 1e-14);
}
}
#[test]
fn test_penalty_symmetry_preserved() {
let mut k = vec![1.0, 0.5, 0.5, 1.0];
let b = vec![1.0, 2.0, 3.0, 4.0]; incompressibility_penalty(&mut k, &b, 1.0, 2, 2);
let diff = (k[1] - k[2]).abs();
assert!(diff < 1e-12, "Symmetry broken after penalty");
}
#[test]
fn test_bubble_centroid() {
let lam = [1.0 / 3.0; 3];
let val = bubble_function(&lam);
let expected = (1.0 / 3.0f64).powi(3);
assert!((val - expected).abs() < 1e-14);
}
#[test]
fn test_bubble_vertex_zero() {
assert!(bubble_function(&[1.0, 0.0, 0.0]).abs() < 1e-14);
assert!(bubble_function(&[0.0, 1.0, 0.0]).abs() < 1e-14);
assert!(bubble_function(&[0.0, 0.0, 1.0]).abs() < 1e-14);
}
#[test]
fn test_bubble_positive_interior() {
let lam = [0.5, 0.3, 0.2];
let val = bubble_function(&lam);
assert!(val > 0.0);
}
#[test]
fn test_bubble_grad_centroid() {
let lam = [1.0 / 3.0; 3];
let grad = bubble_function_grad(&lam);
let expected = (1.0 / 3.0f64).powi(2);
for &g in &grad {
assert!((g - expected).abs() < 1e-14);
}
}
#[test]
fn test_bubble_grad_nonzero_interior() {
let lam = [0.6, 0.3, 0.1];
let grad = bubble_function_grad(&lam);
assert!((grad[0] - lam[1] * lam[2]).abs() < 1e-14);
assert!((grad[1] - lam[0] * lam[2]).abs() < 1e-14);
assert!((grad[2] - lam[0] * lam[1]).abs() < 1e-14);
}
#[test]
fn test_bubble_grad_vertex() {
let lam = [1.0, 0.0, 0.0];
let grad = bubble_function_grad(&lam);
assert!(grad[0].abs() < 1e-14);
assert!(grad[1].abs() < 1e-14);
assert!(grad[2].abs() < 1e-14);
}
}