use super::{jacobian_det, jacobian_inv, ref_to_physical, triangle_gauss_quadrature_3pt, HdgMesh};
use crate::error::{IntegrateError, IntegrateResult};
use scirs2_core::ndarray::{Array1, Array2};
#[derive(Debug, Clone)]
pub struct LocalHdgMatrices {
pub a_kk: Array2<f64>,
pub a_kk_inv: Array2<f64>,
pub b_k: Array2<f64>,
pub c_k: Array2<f64>,
pub f_vol: Array1<f64>,
pub schur: Array2<f64>,
pub rhs_face: Array1<f64>,
pub vertex_indices: [usize; 3],
}
fn p1_ref(xi: f64, eta: f64) -> ([f64; 3], [f64; 3], [f64; 3]) {
(
[1.0 - xi - eta, xi, eta],
[-1.0, 1.0, 0.0],
[-1.0, 0.0, 1.0],
)
}
fn phys_grad(dxi: &[f64; 3], deta: &[f64; 3], ji: &[[f64; 2]; 2]) -> [[f64; 2]; 3] {
let mut g = [[0.0_f64; 2]; 3];
for i in 0..3 {
g[i][0] = ji[0][0] * dxi[i] + ji[1][0] * deta[i];
g[i][1] = ji[0][1] * dxi[i] + ji[1][1] * deta[i];
}
g
}
fn gauss4() -> ([f64; 4], [f64; 4]) {
let a = 0.5 - 0.5 * (3.0 / 7.0 - 2.0 / 7.0 * (6.0 / 5.0_f64).sqrt()).sqrt();
let b = 0.5 + 0.5 * (3.0 / 7.0 - 2.0 / 7.0 * (6.0 / 5.0_f64).sqrt()).sqrt();
let c = 0.5 - 0.5 * (3.0 / 7.0 + 2.0 / 7.0 * (6.0 / 5.0_f64).sqrt()).sqrt();
let d = 0.5 + 0.5 * (3.0 / 7.0 + 2.0 / 7.0 * (6.0 / 5.0_f64).sqrt()).sqrt();
let w1 = 0.5 * (18.0 + (30.0_f64).sqrt()) / 36.0;
let w2 = 0.5 * (18.0 - (30.0_f64).sqrt()) / 36.0;
([a, b, c, d], [w1, w1, w2, w2])
}
fn ref_edge(lf: usize, t: f64) -> (f64, f64) {
match lf {
0 => (1.0 - t, t),
1 => (0.0, t),
2 => (t, 0.0),
_ => (0.0, 0.0),
}
}
fn elen(lf: usize, v: &[[f64; 2]; 3]) -> f64 {
let (a, b) = match lf {
0 => (v[1], v[2]),
1 => (v[0], v[2]),
2 => (v[0], v[1]),
_ => (v[0], v[1]),
};
let d = [b[0] - a[0], b[1] - a[1]];
(d[0] * d[0] + d[1] * d[1]).sqrt()
}
fn everts(lf: usize) -> [usize; 2] {
match lf {
0 => [1, 2],
1 => [0, 2],
2 => [0, 1],
_ => [0, 1],
}
}
fn inv3(m: &Array2<f64>) -> IntegrateResult<Array2<f64>> {
let (a, b, c) = (m[[0, 0]], m[[0, 1]], m[[0, 2]]);
let (d, e, f) = (m[[1, 0]], m[[1, 1]], m[[1, 2]]);
let (g, h, k) = (m[[2, 0]], m[[2, 1]], m[[2, 2]]);
let det = a * (e * k - f * h) - b * (d * k - f * g) + c * (d * h - e * g);
if det.abs() < 1e-14 {
return Err(IntegrateError::LinearSolveError(format!(
"Singular 3x3 det={det:.2e}"
)));
}
let mut inv = Array2::<f64>::zeros((3, 3));
inv[[0, 0]] = (e * k - f * h) / det;
inv[[0, 1]] = (c * h - b * k) / det;
inv[[0, 2]] = (b * f - c * e) / det;
inv[[1, 0]] = (f * g - d * k) / det;
inv[[1, 1]] = (a * k - c * g) / det;
inv[[1, 2]] = (c * d - a * f) / det;
inv[[2, 0]] = (d * h - e * g) / det;
inv[[2, 1]] = (b * g - a * h) / det;
inv[[2, 2]] = (a * e - b * d) / det;
Ok(inv)
}
pub fn local_matrices(
elem_idx: usize,
mesh: &HdgMesh,
tau: f64,
f_func: &dyn Fn(f64, f64) -> f64,
) -> IntegrateResult<LocalHdgMatrices> {
let elem = &mesh.elements[elem_idx];
let v: [[f64; 2]; 3] = [
mesh.vertices[elem[0]],
mesh.vertices[elem[1]],
mesh.vertices[elem[2]],
];
let det = jacobian_det(&v);
let ji = jacobian_inv(&v);
let mut a_grad = Array2::<f64>::zeros((3, 3));
{
let (qps, wts) = triangle_gauss_quadrature_3pt();
for (qp, &w) in qps.iter().zip(wts.iter()) {
let (_, dxi, deta) = p1_ref(qp[0], qp[1]);
let gp = phys_grad(&dxi, &deta, &ji);
let jw = det * w;
for i in 0..3 {
for j in 0..3 {
a_grad[[i, j]] += (gp[i][0] * gp[j][0] + gp[i][1] * gp[j][1]) * jw;
}
}
}
}
let mut f_vol = Array1::<f64>::zeros(3);
{
let (qps, wts) = triangle_gauss_quadrature_3pt();
for (qp, &w) in qps.iter().zip(wts.iter()) {
let phys = ref_to_physical(qp[0], qp[1], &v);
let fval = f_func(phys[0], phys[1]);
let (phi, _, _) = p1_ref(qp[0], qp[1]);
let jw = det * w;
for i in 0..3 {
f_vol[i] += fval * phi[i] * jw;
}
}
}
let (ep, ew) = gauss4();
let mut c_k = Array2::<f64>::zeros((3, 3));
let mut b_k = Array2::<f64>::zeros((3, 3));
for lf in 0..3usize {
let hf = elen(lf, &v);
let [va, vb] = everts(lf);
for (&t, &w) in ep.iter().zip(ew.iter()) {
let pa = 1.0 - t;
let pb = t;
let ds = hf * w;
c_k[[va, va]] += tau * pa * pa * ds;
c_k[[va, vb]] += tau * pa * pb * ds;
c_k[[vb, va]] += tau * pb * pa * ds;
c_k[[vb, vb]] += tau * pb * pb * ds;
let (xi, eta) = ref_edge(lf, t);
let (phi, _, _) = p1_ref(xi, eta);
for i in 0..3 {
b_k[[i, va]] += tau * phi[i] * pa * ds;
b_k[[i, vb]] += tau * phi[i] * pb * ds;
}
}
}
let mut a_hdg = Array2::<f64>::zeros((3, 3));
for i in 0..3 {
for j in 0..3 {
a_hdg[[i, j]] = a_grad[[i, j]] + c_k[[i, j]];
}
}
let a_kk_inv = inv3(&a_hdg)?;
let mut schur = c_k.clone();
let mut tmp = Array2::<f64>::zeros((3, 3));
for i in 0..3 {
for j in 0..3 {
for k in 0..3 {
tmp[[i, j]] += a_kk_inv[[i, k]] * b_k[[k, j]];
}
}
}
for i in 0..3 {
for j in 0..3 {
let mut s = 0.0_f64;
for k in 0..3 {
s += b_k[[k, i]] * tmp[[k, j]];
}
schur[[i, j]] -= s;
}
}
let mut tmpf = [0.0_f64; 3];
for i in 0..3 {
for k in 0..3 {
tmpf[i] += a_kk_inv[[i, k]] * f_vol[k];
}
}
let mut rhs_face = Array1::<f64>::zeros(3);
for i in 0..3 {
for k in 0..3 {
rhs_face[i] += b_k[[k, i]] * tmpf[k];
}
}
Ok(LocalHdgMatrices {
a_kk: a_grad, a_kk_inv, b_k,
c_k,
f_vol,
schur,
rhs_face,
vertex_indices: [elem[0], elem[1], elem[2]],
})
}
pub fn solve_local(lambda_k: &[f64; 3], local: &LocalHdgMatrices) -> Vec<f64> {
let mut rhs = local.f_vol.to_vec();
for i in 0..3 {
for j in 0..3 {
rhs[i] += local.b_k[[i, j]] * lambda_k[j];
}
}
let mut u = vec![0.0_f64; 3];
for i in 0..3 {
for k in 0..3 {
u[i] += local.a_kk_inv[[i, k]] * rhs[k];
}
}
u
}
#[cfg(test)]
mod tests {
use super::*;
fn ref_mesh() -> HdgMesh {
let vertices = vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
let elements = vec![[0, 1, 2]];
HdgMesh::new(vertices, elements)
}
#[test]
fn test_local_matrices_akk_positive_diagonal() {
let m = local_matrices(0, &ref_mesh(), 1.0, &|_, _| 0.0).unwrap();
for i in 0..3 {
assert!(m.a_kk[[i, i]] >= 0.0, "a_kk diag[{}]={}", i, m.a_kk[[i, i]]);
}
}
#[test]
fn test_akk_symmetric() {
let m = local_matrices(0, &ref_mesh(), 1.0, &|_, _| 0.0).unwrap();
for i in 0..3 {
for j in 0..3 {
assert!(
(m.a_kk[[i, j]] - m.a_kk[[j, i]]).abs() < 1e-12,
"A[{},{}]",
i,
j
);
}
}
}
#[test]
fn test_local_matrices_schur_symmetric() {
let m = local_matrices(0, &ref_mesh(), 1.0, &|_, _| 1.0).unwrap();
for i in 0..3 {
for j in 0..3 {
assert!(
(m.schur[[i, j]] - m.schur[[j, i]]).abs() < 1e-12,
"S[{},{}]={} S[{},{}]={}",
i,
j,
m.schur[[i, j]],
j,
i,
m.schur[[j, i]]
);
}
}
}
#[test]
fn test_local_matrices_ck_diagonal() {
let m = local_matrices(0, &ref_mesh(), 1.0, &|_, _| 0.0).unwrap();
for i in 0..3 {
assert!(m.c_k[[i, i]] > 0.0, "C_K diag[{}]={}", i, m.c_k[[i, i]]);
}
}
#[test]
fn test_solve_local_zero_trace() {
let m = local_matrices(0, &ref_mesh(), 1.0, &|_, _| 2.0).unwrap();
let u = solve_local(&[0.0, 0.0, 0.0], &m);
assert_eq!(u.len(), 3);
}
}