pub mod global_system;
pub mod local_solver;
pub use global_system::{solve_hdg, HdgSolution};
pub use local_solver::LocalHdgMatrices;
use crate::error::IntegrateError;
#[derive(Debug, Clone)]
pub struct HdgConfig {
pub polynomial_degree: usize,
pub tau_stabilization: f64,
pub tol: f64,
}
impl Default for HdgConfig {
fn default() -> Self {
HdgConfig {
polynomial_degree: 1,
tau_stabilization: 1.0,
tol: 1e-10,
}
}
}
#[derive(Debug, Clone)]
pub struct HdgMesh {
pub vertices: Vec<[f64; 2]>,
pub elements: Vec<[usize; 3]>,
pub faces: Vec<[usize; 2]>,
pub boundary_faces: Vec<usize>,
}
impl HdgMesh {
pub fn new(vertices: Vec<[f64; 2]>, elements: Vec<[usize; 3]>) -> Self {
use std::collections::HashMap;
let mut face_map: HashMap<[usize; 2], usize> = HashMap::new();
let mut faces: Vec<[usize; 2]> = Vec::new();
let mut face_count: HashMap<[usize; 2], usize> = HashMap::new();
for elem in &elements {
let local_faces = [
[elem[0].min(elem[1]), elem[0].max(elem[1])],
[elem[1].min(elem[2]), elem[1].max(elem[2])],
[elem[0].min(elem[2]), elem[0].max(elem[2])],
];
for face in &local_faces {
let count = face_count.entry(*face).or_insert(0);
*count += 1;
if !face_map.contains_key(face) {
let idx = faces.len();
face_map.insert(*face, idx);
faces.push(*face);
}
}
}
let boundary_faces = faces
.iter()
.enumerate()
.filter(|(_, f)| face_count.get(*f).copied().unwrap_or(0) == 1)
.map(|(i, _)| i)
.collect();
HdgMesh {
vertices,
elements,
faces,
boundary_faces,
}
}
pub fn element_faces(&self, elem_idx: usize) -> [usize; 3] {
use std::collections::HashMap;
let mut face_map: HashMap<[usize; 2], usize> = HashMap::new();
for (i, f) in self.faces.iter().enumerate() {
face_map.insert(*f, i);
}
let elem = &self.elements[elem_idx];
let local_faces = [
[elem[0].min(elem[1]), elem[0].max(elem[1])],
[elem[1].min(elem[2]), elem[1].max(elem[2])],
[elem[0].min(elem[2]), elem[0].max(elem[2])],
];
[
face_map[&local_faces[0]],
face_map[&local_faces[1]],
face_map[&local_faces[2]],
]
}
pub fn n_faces(&self) -> usize {
self.faces.len()
}
pub fn n_elements(&self) -> usize {
self.elements.len()
}
pub fn face_midpoint(&self, face_idx: usize) -> [f64; 2] {
let f = &self.faces[face_idx];
let v0 = &self.vertices[f[0]];
let v1 = &self.vertices[f[1]];
[(v0[0] + v1[0]) * 0.5, (v0[1] + v1[1]) * 0.5]
}
pub fn is_boundary_face(&self, face_idx: usize) -> bool {
self.boundary_faces.contains(&face_idx)
}
}
#[derive(Debug, Clone)]
pub struct HdgDof {
pub n_trace_dofs: usize,
pub n_volume_dofs_per_element: usize,
pub face_to_dof: Vec<usize>,
}
impl HdgDof {
pub fn new(mesh: &HdgMesh, _degree: usize) -> Result<Self, IntegrateError> {
let n_faces = mesh.n_faces();
let face_to_dof: Vec<usize> = (0..n_faces).collect();
let n_volume_dofs_per_element = 3;
Ok(HdgDof {
n_trace_dofs: n_faces,
n_volume_dofs_per_element,
face_to_dof,
})
}
}
pub fn triangle_gauss_quadrature_3pt() -> (Vec<[f64; 2]>, Vec<f64>) {
let pts = vec![
[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 weights = vec![1.0 / 6.0, 1.0 / 6.0, 1.0 / 6.0];
(pts, weights)
}
pub fn ref_to_physical(xi: f64, eta: f64, v: &[[f64; 2]; 3]) -> [f64; 2] {
let x = v[0][0] + (v[1][0] - v[0][0]) * xi + (v[2][0] - v[0][0]) * eta;
let y = v[0][1] + (v[1][1] - v[0][1]) * xi + (v[2][1] - v[0][1]) * eta;
[x, y]
}
pub fn jacobian_det(v: &[[f64; 2]; 3]) -> f64 {
let j11 = v[1][0] - v[0][0];
let j12 = v[2][0] - v[0][0];
let j21 = v[1][1] - v[0][1];
let j22 = v[2][1] - v[0][1];
(j11 * j22 - j12 * j21).abs()
}
pub fn jacobian_inv(v: &[[f64; 2]; 3]) -> [[f64; 2]; 2] {
let j11 = v[1][0] - v[0][0];
let j12 = v[2][0] - v[0][0];
let j21 = v[1][1] - v[0][1];
let j22 = v[2][1] - v[0][1];
let det = j11 * j22 - j12 * j21;
[[j22 / det, -j12 / det], [-j21 / det, j11 / det]]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesh_construction_two_triangles() {
let vertices = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let elements = vec![[0, 1, 2], [0, 2, 3]];
let mesh = HdgMesh::new(vertices, elements);
assert_eq!(mesh.n_elements(), 2);
assert_eq!(mesh.n_faces(), 5);
assert_eq!(mesh.boundary_faces.len(), 4);
}
#[test]
fn test_mesh_element_faces() {
let vertices = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
let elements = vec![[0, 1, 2], [0, 2, 3]];
let mesh = HdgMesh::new(vertices, elements);
let faces0 = mesh.element_faces(0);
let faces1 = mesh.element_faces(1);
assert_eq!(faces0.len(), 3);
assert_eq!(faces1.len(), 3);
let shared: Vec<usize> = faces0
.iter()
.filter(|f| faces1.contains(f))
.copied()
.collect();
assert_eq!(shared.len(), 1);
}
#[test]
fn test_default_config() {
let cfg = HdgConfig::default();
assert_eq!(cfg.polynomial_degree, 1);
assert!((cfg.tau_stabilization - 1.0).abs() < 1e-15);
assert!((cfg.tol - 1e-10).abs() < 1e-20);
}
#[test]
fn test_gauss_quadrature_3pt_weight_sum() {
let (_, weights) = triangle_gauss_quadrature_3pt();
let sum: f64 = weights.iter().sum();
assert!((sum - 0.5).abs() < 1e-14);
}
#[test]
fn test_jacobian_det_unit_triangle() {
let v = [[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]];
let det = jacobian_det(&v);
assert!((det - 1.0).abs() < 1e-14);
}
}