fn dot3(a: [f64; 3], b: [f64; 3]) -> f64 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
fn sub3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
fn add3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
}
fn scale3(a: [f64; 3], s: f64) -> [f64; 3] {
[a[0] * s, a[1] * s, a[2] * s]
}
fn len3(a: [f64; 3]) -> f64 {
dot3(a, a).sqrt()
}
fn cross3(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[derive(Clone, Debug)]
pub struct TriElement {
pub nodes: [usize; 3],
pub coords: [[f64; 3]; 3],
pub degree: usize,
pub h_e: f64,
pub area: f64,
}
impl TriElement {
pub fn new(nodes: [usize; 3], coords: [[f64; 3]; 3], degree: usize) -> Self {
let e01 = sub3(coords[1], coords[0]);
let e12 = sub3(coords[2], coords[1]);
let e20 = sub3(coords[0], coords[2]);
let h_e = len3(e01).max(len3(e12)).max(len3(e20));
let cr = cross3(e01, sub3(coords[2], coords[0]));
let area = 0.5 * len3(cr).abs();
Self {
nodes,
coords,
degree,
h_e,
area,
}
}
pub fn centroid(&self) -> [f64; 3] {
let c = self.coords;
[
(c[0][0] + c[1][0] + c[2][0]) / 3.0,
(c[0][1] + c[1][1] + c[2][1]) / 3.0,
(c[0][2] + c[1][2] + c[2][2]) / 3.0,
]
}
pub fn edge_midpoint(&self, i: usize, j: usize) -> [f64; 3] {
let a = self.coords[i];
let b = self.coords[j];
scale3(add3(a, b), 0.5)
}
pub fn longest_edge(&self) -> (usize, usize) {
let l01 = len3(sub3(self.coords[1], self.coords[0]));
let l12 = len3(sub3(self.coords[2], self.coords[1]));
let l20 = len3(sub3(self.coords[0], self.coords[2]));
if l01 >= l12 && l01 >= l20 {
(0, 1)
} else if l12 >= l20 {
(1, 2)
} else {
(2, 0)
}
}
}
#[derive(Clone, Debug)]
pub struct MeshQuality {
pub aspect_ratio: f64,
pub skewness: f64,
pub min_angle: f64,
pub scaled_jacobian: f64,
}
impl MeshQuality {
pub fn compute_triangle(a: [f64; 3], b: [f64; 3], c: [f64; 3]) -> Self {
let ab = sub3(b, a);
let bc = sub3(c, b);
let ca = sub3(a, c);
let lab = len3(ab);
let lbc = len3(bc);
let lca = len3(ca);
let longest = lab.max(lbc).max(lca);
let _shortest = lab.min(lbc).min(lca);
let cr = cross3(ab, sub3(c, a));
let area = 0.5 * len3(cr).abs();
let alt_c = if lab > 1e-14 { 2.0 * area / lab } else { 0.0 };
let alt_a = if lbc > 1e-14 { 2.0 * area / lbc } else { 0.0 };
let alt_b = if lca > 1e-14 { 2.0 * area / lca } else { 0.0 };
let min_alt = alt_c.min(alt_a).min(alt_b);
let aspect_ratio = if min_alt > 1e-14 {
longest / min_alt
} else {
f64::MAX
};
let cos_a = dot3(ab, scale3(ca, -1.0)) / (lab * lca).max(1e-14);
let cos_b = dot3(scale3(ab, -1.0), bc) / (lab * lbc).max(1e-14);
let cos_c = dot3(scale3(bc, -1.0), ca) / (lbc * lca).max(1e-14);
let angle_a = cos_a.clamp(-1.0, 1.0).acos();
let angle_b = cos_b.clamp(-1.0, 1.0).acos();
let angle_c = cos_c.clamp(-1.0, 1.0).acos();
let min_angle = angle_a.min(angle_b).min(angle_c);
let optimal_angle = std::f64::consts::PI / 3.0; let skewness = (optimal_angle - min_angle) / optimal_angle;
let scaled_jacobian = if lab * lbc > 1e-14 {
2.0 * area / (lab * lbc)
} else {
0.0
};
Self {
aspect_ratio,
skewness,
min_angle,
scaled_jacobian,
}
}
pub fn is_acceptable(&self, max_aspect_ratio: f64) -> bool {
self.aspect_ratio < max_aspect_ratio && self.scaled_jacobian > 0.0
}
pub fn quality_score(&self) -> f64 {
(1.0 - self.skewness.clamp(0.0, 1.0)) * self.scaled_jacobian.clamp(0.0, 1.0)
}
}
#[derive(Clone, Debug)]
pub struct ZzErrorEstimator {
pub n_nodes: usize,
pub n_elements: usize,
pub recovered_grad: Vec<[f64; 2]>,
pub eta: Vec<f64>,
pub global_error: f64,
}
impl ZzErrorEstimator {
pub fn new(n_nodes: usize, n_elements: usize) -> Self {
Self {
n_nodes,
n_elements,
recovered_grad: vec![[0.0; 2]; n_nodes],
eta: vec![0.0; n_elements],
global_error: 0.0,
}
}
pub fn recover_gradients(&mut self, elem_grads: &[[f64; 2]], connectivity: &[[usize; 3]]) {
let mut count = vec![0usize; self.n_nodes];
for g in self.recovered_grad.iter_mut() {
*g = [0.0; 2];
}
for (e, conn) in connectivity.iter().enumerate() {
if e >= elem_grads.len() {
break;
}
for &n in conn.iter() {
if n < self.n_nodes {
self.recovered_grad[n][0] += elem_grads[e][0];
self.recovered_grad[n][1] += elem_grads[e][1];
count[n] += 1;
}
}
}
for (grad_n, &cnt) in self.recovered_grad.iter_mut().zip(count.iter()) {
let c = cnt.max(1) as f64;
grad_n[0] /= c;
grad_n[1] /= c;
}
}
pub fn compute_element_errors(
&mut self,
elem_grads: &[[f64; 2]],
connectivity: &[[usize; 3]],
areas: &[f64],
) {
let mut global_sq = 0.0;
for e in 0..self.n_elements.min(elem_grads.len()) {
let conn = connectivity[e];
let rec = [
(self.recovered_grad[conn[0].min(self.n_nodes - 1)][0]
+ self.recovered_grad[conn[1].min(self.n_nodes - 1)][0]
+ self.recovered_grad[conn[2].min(self.n_nodes - 1)][0])
/ 3.0,
(self.recovered_grad[conn[0].min(self.n_nodes - 1)][1]
+ self.recovered_grad[conn[1].min(self.n_nodes - 1)][1]
+ self.recovered_grad[conn[2].min(self.n_nodes - 1)][1])
/ 3.0,
];
let raw = elem_grads[e];
let area = if e < areas.len() { areas[e] } else { 1.0 };
let diff_sq = (rec[0] - raw[0]).powi(2) + (rec[1] - raw[1]).powi(2);
self.eta[e] = (diff_sq * area).sqrt();
global_sq += self.eta[e].powi(2);
}
self.global_error = global_sq.sqrt();
}
pub fn effectivity_index(&self, exact_error: f64) -> f64 {
self.global_error / exact_error.max(1e-14)
}
pub fn dorfer_mark(&self, theta: f64) -> Vec<usize> {
let eta_max = self.eta.iter().cloned().fold(0.0f64, f64::max);
self.eta
.iter()
.cloned()
.enumerate()
.filter(|(_, e)| *e > theta * eta_max)
.map(|(i, _)| i)
.collect()
}
}
#[derive(Clone, Debug)]
pub struct ResidualEstimator {
pub n_elements: usize,
pub r_element: Vec<f64>,
pub r_jump: Vec<f64>,
pub eta: Vec<f64>,
pub global_error: f64,
}
impl ResidualEstimator {
pub fn new(n_elements: usize) -> Self {
Self {
n_elements,
r_element: vec![0.0; n_elements],
r_jump: vec![0.0; n_elements],
eta: vec![0.0; n_elements],
global_error: 0.0,
}
}
pub fn compute(&mut self, h_e: &[f64], r_e: &[f64], j_e: &[f64]) {
let mut global_sq = 0.0;
for k in 0..self.n_elements {
let h = if k < h_e.len() { h_e[k] } else { 1.0 };
let r = if k < r_e.len() { r_e[k] } else { 0.0 };
let j = if k < j_e.len() { j_e[k] } else { 0.0 };
self.r_element[k] = h * r;
self.r_jump[k] = h.sqrt() * j;
self.eta[k] = self.r_element[k] + self.r_jump[k];
global_sq += self.eta[k].powi(2);
}
self.global_error = global_sq.sqrt();
}
pub fn max_eta(&self) -> f64 {
self.eta.iter().cloned().fold(0.0f64, f64::max)
}
pub fn fraction_exceeding(&self, tol: f64) -> f64 {
let cnt = self.eta.iter().filter(|&&e| e > tol).count();
cnt as f64 / self.n_elements as f64
}
}
#[derive(Clone, Debug)]
pub struct HRefinementResult {
pub nodes: Vec<[f64; 3]>,
pub elements: Vec<[usize; 3]>,
pub degrees: Vec<usize>,
pub n_refined: usize,
}
pub fn h_refine_longest_edge(
nodes: &[[f64; 3]],
elements: &[[usize; 3]],
degrees: &[usize],
marked: &[usize],
) -> HRefinementResult {
let mut new_nodes: Vec<[f64; 3]> = nodes.to_vec();
let mut new_elements: Vec<[usize; 3]> = Vec::new();
let mut new_degrees: Vec<usize> = Vec::new();
let mut n_refined = 0;
let mut midpoints: std::collections::HashMap<(usize, usize), usize> =
std::collections::HashMap::new();
let marked_set: std::collections::HashSet<usize> = marked.iter().cloned().collect();
for (e, &conn) in elements.iter().enumerate() {
let deg = if e < degrees.len() { degrees[e] } else { 1 };
if !marked_set.contains(&e) {
new_elements.push(conn);
new_degrees.push(deg);
continue;
}
let elem = TriElement::new(conn, [nodes[conn[0]], nodes[conn[1]], nodes[conn[2]]], deg);
let (i, j) = elem.longest_edge();
let ni = conn[i];
let nj = conn[j];
let key = (ni.min(nj), ni.max(nj));
let mid_idx = if let Some(&m) = midpoints.get(&key) {
m
} else {
let mid = scale3(add3(nodes[ni], nodes[nj]), 0.5);
let m = new_nodes.len();
new_nodes.push(mid);
midpoints.insert(key, m);
m
};
let k = 3 - i - j; let nk = conn[k];
new_elements.push([ni, mid_idx, nk]);
new_elements.push([mid_idx, nj, nk]);
new_degrees.push(deg);
new_degrees.push(deg);
n_refined += 1;
}
HRefinementResult {
nodes: new_nodes,
elements: new_elements,
degrees: new_degrees,
n_refined,
}
}
#[derive(Clone, Debug)]
pub struct PRefinement {
pub p_max: usize,
pub degrees: Vec<usize>,
}
impl PRefinement {
pub fn new(n_elements: usize, p0: usize, p_max: usize) -> Self {
Self {
p_max,
degrees: vec![p0; n_elements],
}
}
pub fn refine_marked(&mut self, marked: &[usize]) {
for &e in marked {
if e < self.degrees.len() {
self.degrees[e] = (self.degrees[e] + 1).min(self.p_max);
}
}
}
pub fn dof_per_element(p: usize) -> usize {
(p + 1) * (p + 2) / 2
}
pub fn total_interior_dof(&self) -> usize {
self.degrees.iter().map(|&p| Self::dof_per_element(p)).sum()
}
}
#[derive(Clone, Debug)]
pub enum SmoothnessIndicator {
Smooth,
NonSmooth,
}
#[derive(Clone, Debug)]
pub struct HpRefinementStrategy {
pub eta: Vec<f64>,
pub degrees: Vec<usize>,
pub smoothness: Vec<SmoothnessIndicator>,
pub theta: f64,
pub p_max: usize,
}
impl HpRefinementStrategy {
pub fn new(n_elements: usize, theta: f64, p_max: usize) -> Self {
Self {
eta: vec![0.0; n_elements],
degrees: vec![1; n_elements],
smoothness: (0..n_elements)
.map(|_| SmoothnessIndicator::Smooth)
.collect(),
theta,
p_max,
}
}
pub fn classify_smoothness(&mut self, coeff_decay: &[f64]) {
for (e, s) in self.smoothness.iter_mut().enumerate() {
let decay = if e < coeff_decay.len() {
coeff_decay[e]
} else {
1.0
};
*s = if decay > 0.5 {
SmoothnessIndicator::Smooth
} else {
SmoothnessIndicator::NonSmooth
};
}
}
pub fn mark_elements(&self) -> Vec<usize> {
let eta_max = self.eta.iter().cloned().fold(0.0f64, f64::max);
self.eta
.iter()
.cloned()
.enumerate()
.filter(|(_, e)| *e > self.theta * eta_max)
.map(|(i, _)| i)
.collect()
}
pub fn partition_refinement(&self, marked: &[usize]) -> (Vec<usize>, Vec<usize>) {
let mut h_set = Vec::new();
let mut p_set = Vec::new();
for &e in marked {
match &self.smoothness[e] {
SmoothnessIndicator::Smooth if self.degrees[e] < self.p_max => {
p_set.push(e);
}
_ => {
h_set.push(e);
}
}
}
(h_set, p_set)
}
pub fn hp_efficiency_gain(&self, h_only_dof: usize, hp_dof: usize) -> f64 {
if hp_dof == 0 {
0.0
} else {
h_only_dof as f64 / hp_dof as f64
}
}
}
const GL_POINTS_5: [f64; 5] = [
-0.906_179_845_938_664,
-0.538_469_310_105_683,
0.0,
0.538_469_310_105_683,
0.906_179_845_938_664,
];
const GL_WEIGHTS_5: [f64; 5] = [
0.236_926_885_056_189,
0.478_628_670_499_367,
0.568_888_888_888_889,
0.478_628_670_499_367,
0.236_926_885_056_189,
];
#[derive(Clone, Debug)]
pub struct AdaptiveQuadrature {
pub tol: f64,
pub max_depth: usize,
pub n_evals: usize,
}
impl AdaptiveQuadrature {
pub fn new(tol: f64, max_depth: usize) -> Self {
Self {
tol,
max_depth,
n_evals: 0,
}
}
pub fn integrate<F: Fn(f64) -> f64>(&mut self, f: &F, a: f64, b: f64) -> f64 {
self.n_evals = 0;
self.integrate_recursive(f, a, b, 0)
}
fn integrate_recursive<F: Fn(f64) -> f64>(
&mut self,
f: &F,
a: f64,
b: f64,
depth: usize,
) -> f64 {
let coarse = self.gl5(f, a, b);
if depth >= self.max_depth {
return coarse;
}
let mid = 0.5 * (a + b);
let fine = self.gl5(f, a, mid) + self.gl5(f, mid, b);
let err = (fine - coarse).abs();
if err < self.tol * (1.0 + fine.abs()) {
fine
} else {
self.integrate_recursive(f, a, mid, depth + 1)
+ self.integrate_recursive(f, mid, b, depth + 1)
}
}
fn gl5<F: Fn(f64) -> f64>(&mut self, f: &F, a: f64, b: f64) -> f64 {
let half = 0.5 * (b - a);
let center = 0.5 * (a + b);
let mut sum = 0.0;
for (&pt, &wt) in GL_POINTS_5.iter().zip(GL_WEIGHTS_5.iter()) {
let x = center + half * pt;
sum += wt * f(x);
self.n_evals += 1;
}
sum * half
}
pub fn integrate_triangle<F: Fn(f64, f64) -> f64>(&mut self, f: &F, area: f64) -> f64 {
let pts = [
(1.0 / 6.0, 1.0 / 6.0),
(2.0 / 3.0, 1.0 / 6.0),
(1.0 / 6.0, 2.0 / 3.0),
];
let w = 1.0 / 3.0;
let sum: f64 = pts.iter().map(|&(u, v)| f(u, v)).sum();
self.n_evals += 3;
sum * w * area
}
}
#[derive(Clone, Debug)]
pub struct ConvergenceEstimator {
pub history: Vec<(usize, f64)>,
}
impl ConvergenceEstimator {
pub fn new() -> Self {
Self {
history: Vec::new(),
}
}
pub fn record(&mut self, n_dof: usize, error: f64) {
self.history.push((n_dof, error));
}
pub fn convergence_rate(&self, dimension: usize) -> Option<(f64, f64)> {
let n = self.history.len();
if n < 2 {
return None;
}
let (n1, e1) = self.history[n - 2];
let (n2, e2) = self.history[n - 1];
if n1 == 0 || n2 == 0 || e1 <= 0.0 || e2 <= 0.0 {
return None;
}
let d = dimension as f64;
let rate = -(e2 / e1).ln() / ((n2 as f64 / n1 as f64).ln() / d);
let h1 = (n1 as f64).powf(-1.0 / d);
let c = e1 / h1.powf(rate);
Some((rate, c))
}
pub fn predict_error(&self, n_target: usize, dimension: usize) -> Option<f64> {
let (rate, c) = self.convergence_rate(dimension)?;
let h = (n_target as f64).powf(-1.0 / dimension as f64);
Some(c * h.powf(rate))
}
pub fn is_converged(&self, tol: f64) -> bool {
self.history.last().map(|(_, e)| *e < tol).unwrap_or(false)
}
}
impl Default for ConvergenceEstimator {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub struct RefinementIndicator {
pub n_elements: usize,
pub stress_grad: Vec<f64>,
pub energy_error: Vec<f64>,
pub indicator: Vec<f64>,
pub alpha: f64,
}
impl RefinementIndicator {
pub fn new(n_elements: usize, alpha: f64) -> Self {
Self {
n_elements,
stress_grad: vec![0.0; n_elements],
energy_error: vec![0.0; n_elements],
indicator: vec![0.0; n_elements],
alpha,
}
}
pub fn compute(&mut self) {
let sg_max = self
.stress_grad
.iter()
.cloned()
.fold(0.0f64, f64::max)
.max(1e-14);
let ee_max = self
.energy_error
.iter()
.cloned()
.fold(0.0f64, f64::max)
.max(1e-14);
for ((ind_e, &sg_e), &ee_e) in self
.indicator
.iter_mut()
.zip(self.stress_grad.iter())
.zip(self.energy_error.iter())
{
let sg_norm = sg_e / sg_max;
let ee_norm = ee_e / ee_max;
*ind_e = self.alpha * sg_norm + (1.0 - self.alpha) * ee_norm;
}
}
pub fn mark(&self, theta: f64) -> Vec<usize> {
self.indicator
.iter()
.cloned()
.enumerate()
.filter(|(_, v)| *v > theta)
.map(|(i, _)| i)
.collect()
}
pub fn compute_energy_error(
&mut self,
elem_stress: &[[f64; 2]],
rec_stress: &[[f64; 2]],
areas: &[f64],
) {
for e in 0..self.n_elements.min(elem_stress.len()) {
let area = if e < areas.len() { areas[e] } else { 1.0 };
let dx = elem_stress[e][0] - rec_stress[e][0];
let dy = elem_stress[e][1] - rec_stress[e][1];
self.energy_error[e] = (dx * dx + dy * dy) * area;
}
}
}
#[derive(Clone, Debug)]
pub struct CoarseningStrategy {
pub tol_coarsen: f64,
pub p_min: usize,
pub h_min: f64,
}
impl CoarseningStrategy {
pub fn new(tol_coarsen: f64, p_min: usize, h_min: f64) -> Self {
Self {
tol_coarsen,
p_min,
h_min,
}
}
pub fn mark_coarsen(&self, eta: &[f64], h_e: &[f64]) -> Vec<usize> {
eta.iter()
.enumerate()
.filter(|(e, err)| **err < self.tol_coarsen && *e < h_e.len() && h_e[*e] > self.h_min)
.map(|(i, _)| i)
.collect()
}
pub fn p_coarsen(&self, degrees: &mut [usize], marked: &[usize]) {
for &e in marked {
if e < degrees.len() && degrees[e] > self.p_min {
degrees[e] -= 1;
}
}
}
pub fn dof_savings(&self, degrees: &[usize], marked: &[usize]) -> usize {
marked
.iter()
.filter(|&&e| e < degrees.len() && degrees[e] > self.p_min)
.map(|&e| {
let p = degrees[e];
PRefinement::dof_per_element(p) - PRefinement::dof_per_element(p - 1)
})
.sum()
}
}
#[derive(Clone, Debug)]
pub struct RefinementTask {
pub element_idx: usize,
pub priority: f64,
}
#[derive(Clone, Debug)]
pub struct ParallelAdaptiveRefinement {
pub tasks: Vec<RefinementTask>,
pub n_threads: usize,
pub n_completed: usize,
}
impl ParallelAdaptiveRefinement {
pub fn new(n_threads: usize) -> Self {
Self {
tasks: Vec::new(),
n_threads,
n_completed: 0,
}
}
pub fn queue(&mut self, marked: &[usize], eta: &[f64]) {
for &e in marked {
let priority = if e < eta.len() { eta[e] } else { 0.0 };
self.tasks.push(RefinementTask {
element_idx: e,
priority,
});
}
self.tasks.sort_by(|a, b| {
b.priority
.partial_cmp(&a.priority)
.unwrap_or(std::cmp::Ordering::Equal)
});
}
pub fn process_all(&mut self) -> Vec<usize> {
let result: Vec<usize> = self.tasks.iter().map(|t| t.element_idx).collect();
self.n_completed += self.tasks.len();
self.tasks.clear();
result
}
pub fn amdahl_speedup(&self) -> f64 {
let p = 0.9;
let n = self.n_threads as f64;
1.0 / ((1.0 - p) + p / n)
}
}
pub fn l2_error_norm(exact: &[f64], approx: &[f64], weights: &[f64]) -> f64 {
let sum: f64 = exact
.iter()
.zip(approx.iter())
.zip(weights.iter())
.map(|((e, a), w)| w * (e - a).powi(2))
.sum();
sum.sqrt()
}
pub fn h1_seminorm_error(
grad_exact: &[[f64; 2]],
grad_approx: &[[f64; 2]],
weights: &[f64],
) -> f64 {
let sum: f64 = grad_exact
.iter()
.zip(grad_approx.iter())
.zip(weights.iter())
.map(|((ge, ga), w)| {
let dx = ge[0] - ga[0];
let dy = ge[1] - ga[1];
w * (dx * dx + dy * dy)
})
.sum();
sum.sqrt()
}
pub fn optimal_refinement_ratio(e0: f64, e_target: f64, convergence_rate: f64) -> f64 {
if e0 <= 0.0 || e_target <= 0.0 || convergence_rate <= 0.0 {
return 0.5;
}
(e_target / e0).powf(1.0 / convergence_rate)
}
#[derive(Clone, Debug)]
pub struct AdaptiveFemDriver {
pub tol: f64,
pub max_iter: usize,
pub theta_mark: f64,
pub theta_coarsen: f64,
pub iteration: usize,
pub convergence: ConvergenceEstimator,
pub use_hp: bool,
}
impl AdaptiveFemDriver {
pub fn new(
tol: f64,
max_iter: usize,
theta_mark: f64,
theta_coarsen: f64,
use_hp: bool,
) -> Self {
Self {
tol,
max_iter,
theta_mark,
theta_coarsen,
iteration: 0,
convergence: ConvergenceEstimator::new(),
use_hp,
}
}
pub fn should_stop(&self, global_error: f64) -> bool {
self.iteration >= self.max_iter || global_error < self.tol
}
pub fn record_iteration(&mut self, n_dof: usize, global_error: f64) {
self.convergence.record(n_dof, global_error);
self.iteration += 1;
}
pub fn next_tolerance(&self) -> f64 {
self.tol * self.theta_mark.sqrt()
}
pub fn estimated_remaining_iters(&self, global_error: f64) -> usize {
if global_error <= self.tol {
return 0;
}
let ratio = global_error / self.tol;
(ratio.log2() / 1.0_f64.log2()).ceil() as usize + 1
}
}
#[derive(Clone, Debug)]
pub struct RecoveredStress {
pub n_nodes: usize,
pub sigma: Vec<[f64; 3]>,
}
impl RecoveredStress {
pub fn new(n_nodes: usize) -> Self {
Self {
n_nodes,
sigma: vec![[0.0; 3]; n_nodes],
}
}
pub fn recover(&mut self, elem_stress: &[[f64; 3]], connectivity: &[[usize; 3]]) {
let mut count = vec![0usize; self.n_nodes];
for s in self.sigma.iter_mut() {
*s = [0.0; 3];
}
for (e, conn) in connectivity.iter().enumerate() {
if e >= elem_stress.len() {
break;
}
for &n in conn.iter() {
if n < self.n_nodes {
self.sigma[n][0] += elem_stress[e][0];
self.sigma[n][1] += elem_stress[e][1];
self.sigma[n][2] += elem_stress[e][2];
count[n] += 1;
}
}
}
for (sigma_n, &cnt) in self.sigma.iter_mut().zip(count.iter()) {
let c = cnt.max(1) as f64;
sigma_n[0] /= c;
sigma_n[1] /= c;
sigma_n[2] /= c;
}
}
pub fn von_mises(&self, n: usize) -> f64 {
if n >= self.n_nodes {
return 0.0;
}
let s = self.sigma[n];
let vm_sq = s[0] * s[0] - s[0] * s[1] + s[1] * s[1] + 3.0 * s[2] * s[2];
vm_sq.max(0.0).sqrt()
}
}
#[derive(Clone, Debug)]
pub struct AnisotropicMetric {
pub h1: f64,
pub h2: f64,
pub angle: f64,
}
impl AnisotropicMetric {
pub fn new(h1: f64, h2: f64, angle: f64) -> Self {
Self { h1, h2, angle }
}
pub fn metric_tensor(&self) -> [f64; 4] {
let ca = self.angle.cos();
let sa = self.angle.sin();
let l1 = 1.0 / self.h1.powi(2).max(1e-20);
let l2 = 1.0 / self.h2.powi(2).max(1e-20);
let m00 = l1 * ca * ca + l2 * sa * sa;
let m01 = (l1 - l2) * ca * sa;
let m11 = l1 * sa * sa + l2 * ca * ca;
[m00, m01, m01, m11]
}
pub fn edge_length_metric(&self, e: [f64; 2]) -> f64 {
let m = self.metric_tensor();
let me0 = m[0] * e[0] + m[1] * e[1];
let me1 = m[2] * e[0] + m[3] * e[1];
(e[0] * me0 + e[1] * me1).max(0.0).sqrt()
}
pub fn anisotropy_ratio(&self) -> f64 {
self.h1 / self.h2.max(1e-14)
}
}
pub fn interpolation_error_estimate(
h_e: f64,
degree: usize,
seminorm_p1: f64,
constant_c: f64,
) -> f64 {
constant_c * h_e.powi((degree + 1) as i32) * seminorm_p1
}
pub fn element_size_from_area(area: f64) -> f64 {
(2.0 * area).sqrt()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tri_element_area() {
let coords = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let e = TriElement::new([0, 1, 2], coords, 1);
assert!((e.area - 0.5).abs() < 1e-10);
}
#[test]
fn test_tri_element_centroid() {
let coords = [[0.0, 0.0, 0.0], [3.0, 0.0, 0.0], [0.0, 3.0, 0.0]];
let e = TriElement::new([0, 1, 2], coords, 1);
let c = e.centroid();
assert!((c[0] - 1.0).abs() < 1e-10);
assert!((c[1] - 1.0).abs() < 1e-10);
}
#[test]
fn test_tri_element_h_e() {
let coords = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let e = TriElement::new([0, 1, 2], coords, 1);
assert!(e.h_e > 1.0 && e.h_e < 2.0); }
#[test]
fn test_mesh_quality_equilateral() {
let s = 3.0_f64.sqrt() / 2.0;
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [0.5, s, 0.0];
let q = MeshQuality::compute_triangle(a, b, c);
assert!(q.aspect_ratio < 1.2);
assert!(q.quality_score() > 0.8);
}
#[test]
fn test_mesh_quality_is_acceptable() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [0.5, 0.5, 0.0];
let q = MeshQuality::compute_triangle(a, b, c);
assert!(q.is_acceptable(10.0));
}
#[test]
fn test_mesh_quality_degenerate() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [2.0, 0.0, 0.0]; let q = MeshQuality::compute_triangle(a, b, c);
assert!(!q.is_acceptable(10.0));
}
#[test]
fn test_zz_estimator_recover_uniform() {
let mut zz = ZzErrorEstimator::new(3, 2);
let grads = [[1.0f64, 0.0], [1.0, 0.0]];
let conn = [[0usize, 1, 2], [0, 1, 2]];
zz.recover_gradients(&grads, &conn);
assert!((zz.recovered_grad[0][0] - 1.0).abs() < 1e-10);
}
#[test]
fn test_zz_estimator_element_errors_zero_for_exact() {
let mut zz = ZzErrorEstimator::new(3, 2);
let grads = [[1.0f64, 0.0], [1.0, 0.0]];
let conn = [[0usize, 1, 2], [0, 1, 2]];
let areas = [0.5, 0.5];
zz.recover_gradients(&grads, &conn);
zz.compute_element_errors(&grads, &conn, &areas);
assert!(zz.global_error < 1e-10);
}
#[test]
fn test_zz_dorfer_mark() {
let mut zz = ZzErrorEstimator::new(4, 4);
zz.eta = vec![0.1, 0.5, 0.2, 0.9];
let marked = zz.dorfer_mark(0.5);
assert!(marked.contains(&3));
assert!(marked.contains(&1));
}
#[test]
fn test_residual_estimator_compute() {
let mut est = ResidualEstimator::new(3);
let h = [0.1, 0.1, 0.1];
let r = [1.0, 2.0, 3.0];
let j = [0.5, 0.5, 0.5];
est.compute(&h, &r, &j);
assert!(est.global_error > 0.0);
assert!(est.max_eta() == est.eta[2]);
}
#[test]
fn test_h_refine_longest_edge() {
let nodes: Vec<[f64; 3]> = vec![[0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let elements: Vec<[usize; 3]> = vec![[0, 1, 2]];
let degrees = vec![1usize];
let result = h_refine_longest_edge(&nodes, &elements, °rees, &[0]);
assert_eq!(result.n_refined, 1);
assert!(result.nodes.len() > 3);
assert_eq!(result.elements.len(), 2);
}
#[test]
fn test_h_refine_unmarked_unchanged() {
let nodes: Vec<[f64; 3]> = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let elements: Vec<[usize; 3]> = vec![[0, 1, 2]];
let degrees = vec![1usize];
let result = h_refine_longest_edge(&nodes, &elements, °rees, &[]);
assert_eq!(result.n_refined, 0);
assert_eq!(result.elements.len(), 1);
}
#[test]
fn test_p_refinement_increase() {
let mut pr = PRefinement::new(4, 1, 5);
pr.refine_marked(&[0, 2]);
assert_eq!(pr.degrees[0], 2);
assert_eq!(pr.degrees[1], 1);
assert_eq!(pr.degrees[2], 2);
}
#[test]
fn test_p_refinement_cap() {
let mut pr = PRefinement::new(2, 5, 5);
pr.refine_marked(&[0]);
assert_eq!(pr.degrees[0], 5); }
#[test]
fn test_p_refinement_dof() {
assert_eq!(PRefinement::dof_per_element(1), 3);
assert_eq!(PRefinement::dof_per_element(2), 6);
}
#[test]
fn test_hp_strategy_partition() {
let mut hp = HpRefinementStrategy::new(4, 0.5, 5);
hp.eta = vec![0.1, 0.9, 0.3, 0.8];
hp.smoothness[1] = SmoothnessIndicator::Smooth;
hp.smoothness[3] = SmoothnessIndicator::NonSmooth;
let marked = hp.mark_elements();
let (h_set, p_set) = hp.partition_refinement(&marked);
assert!(!h_set.is_empty() || !p_set.is_empty());
}
#[test]
fn test_adaptive_quadrature_x2() {
let mut aq = AdaptiveQuadrature::new(1e-10, 20);
let result = aq.integrate(&|x: f64| x * x, 0.0, 1.0);
assert!((result - 1.0 / 3.0).abs() < 1e-8);
}
#[test]
fn test_adaptive_quadrature_triangle() {
let mut aq = AdaptiveQuadrature::new(1e-10, 20);
let result = aq.integrate_triangle(&|_u, _v| 1.0, 0.5);
assert!((result - 0.5).abs() < 1e-10);
}
#[test]
fn test_convergence_estimator_rate() {
let mut ce = ConvergenceEstimator::new();
ce.record(100, 0.1);
ce.record(400, 0.05);
let (rate, _) = ce.convergence_rate(2).unwrap();
assert!(rate > 0.0);
}
#[test]
fn test_convergence_estimator_is_converged() {
let mut ce = ConvergenceEstimator::new();
ce.record(100, 1e-10);
assert!(ce.is_converged(1e-8));
}
#[test]
fn test_convergence_estimator_insufficient() {
let ce = ConvergenceEstimator::new();
assert!(ce.convergence_rate(2).is_none());
}
#[test]
fn test_refinement_indicator_mark() {
let mut ri = RefinementIndicator::new(4, 0.5);
ri.stress_grad = vec![0.1, 0.9, 0.2, 0.8];
ri.energy_error = vec![0.1, 0.9, 0.2, 0.8];
ri.compute();
let marked = ri.mark(0.5);
assert!(!marked.is_empty());
}
#[test]
fn test_coarsening_strategy_mark() {
let cs = CoarseningStrategy::new(0.01, 1, 0.01);
let eta = vec![0.001, 0.1, 0.005, 0.2];
let h = vec![0.05, 0.05, 0.05, 0.05];
let marked = cs.mark_coarsen(&eta, &h);
assert!(marked.contains(&0));
assert!(marked.contains(&2));
assert!(!marked.contains(&1));
}
#[test]
fn test_coarsening_strategy_p_coarsen() {
let cs = CoarseningStrategy::new(0.01, 1, 0.01);
let mut degrees = vec![3, 2, 3, 1];
cs.p_coarsen(&mut degrees, &[0, 2]);
assert_eq!(degrees[0], 2);
assert_eq!(degrees[3], 1); }
#[test]
fn test_parallel_adaptive_queue_process() {
let mut par = ParallelAdaptiveRefinement::new(4);
par.queue(&[0, 2, 3], &[0.1, 0.5, 0.3, 0.9]);
let processed = par.process_all();
assert_eq!(processed.len(), 3);
assert_eq!(par.n_completed, 3);
}
#[test]
fn test_parallel_adaptive_speedup() {
let par = ParallelAdaptiveRefinement::new(4);
let s = par.amdahl_speedup();
assert!(s > 1.0 && s < 5.0);
}
#[test]
fn test_l2_error_norm() {
let exact = [1.0, 2.0, 3.0];
let approx = [1.0, 2.0, 3.0];
let weights = [1.0, 1.0, 1.0];
assert!(l2_error_norm(&exact, &approx, &weights) < 1e-14);
}
#[test]
fn test_l2_error_norm_nonzero() {
let exact = [2.0, 2.0];
let approx = [1.0, 1.0];
let weights = [1.0, 1.0];
let err = l2_error_norm(&exact, &approx, &weights);
assert!((err - 2.0_f64.sqrt()).abs() < 1e-10);
}
#[test]
fn test_h1_seminorm() {
let ge = [[1.0f64, 0.0], [1.0, 0.0]];
let ga = [[0.9, 0.0], [0.9, 0.0]];
let w = [1.0, 1.0];
let err = h1_seminorm_error(&ge, &ga, &w);
assert!((err - (2.0 * 0.01_f64).sqrt()).abs() < 1e-10);
}
#[test]
fn test_optimal_refinement_ratio() {
let ratio = optimal_refinement_ratio(0.1, 0.05, 2.0);
assert!((ratio - 0.5_f64.sqrt()).abs() < 1e-10);
}
#[test]
fn test_adaptive_fem_driver_stop() {
let driver = AdaptiveFemDriver::new(1e-6, 10, 0.5, 0.1, true);
assert!(driver.should_stop(1e-10));
assert!(!driver.should_stop(1e-3));
}
#[test]
fn test_adaptive_fem_driver_record() {
let mut driver = AdaptiveFemDriver::new(1e-6, 10, 0.5, 0.1, false);
driver.record_iteration(100, 0.01);
assert_eq!(driver.iteration, 1);
assert_eq!(driver.convergence.history.len(), 1);
}
#[test]
fn test_recovered_stress_von_mises() {
let mut rs = RecoveredStress::new(2);
rs.sigma[0] = [100.0, 50.0, 30.0];
let vm = rs.von_mises(0);
assert!(vm > 0.0);
}
#[test]
fn test_anisotropic_metric_isotropic() {
let m = AnisotropicMetric::new(0.1, 0.1, 0.0);
let mt = m.metric_tensor();
assert!((mt[0] - mt[3]).abs() < 1e-10);
assert!(mt[1].abs() < 1e-10);
}
#[test]
fn test_anisotropic_metric_edge_length() {
let m = AnisotropicMetric::new(0.1, 0.1, 0.0);
let len = m.edge_length_metric([0.1, 0.0]);
assert!((len - 1.0).abs() < 1e-8);
}
#[test]
fn test_interpolation_error_estimate() {
let err = interpolation_error_estimate(0.1, 1, 10.0, 1.0);
assert!((err - 0.01 * 10.0).abs() < 1e-12);
}
#[test]
fn test_element_size_from_area() {
let h = element_size_from_area(0.5);
assert!((h - 1.0).abs() < 1e-10);
}
#[test]
fn test_zz_effectivity_index() {
let mut zz = ZzErrorEstimator::new(3, 2);
zz.global_error = 0.1;
let eff = zz.effectivity_index(0.1);
assert!((eff - 1.0).abs() < 1e-12);
}
#[test]
fn test_residual_fraction_exceeding() {
let mut est = ResidualEstimator::new(4);
est.eta = vec![0.1, 0.5, 0.2, 0.9];
let frac = est.fraction_exceeding(0.4);
assert!((frac - 0.5).abs() < 1e-10);
}
}