use crate::assembly::HelmholtzProblem;
use crate::basis::PolynomialDegree;
use crate::mesh::{BoundaryFace, Mesh};
use crate::quadrature::gauss_legendre_1d;
use num_complex::Complex64;
pub struct NeumannBC {
pub tag: usize,
flux_fn: Box<dyn Fn(f64, f64, f64) -> Complex64>,
}
impl std::fmt::Debug for NeumannBC {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("NeumannBC").field("tag", &self.tag).finish()
}
}
impl Clone for NeumannBC {
fn clone(&self) -> Self {
Self {
tag: self.tag,
flux_fn: Box::new(|_, _, _| Complex64::new(0.0, 0.0)),
}
}
}
impl NeumannBC {
pub fn new<F>(tag: usize, flux_fn: F) -> Self
where
F: Fn(f64, f64, f64) -> Complex64 + 'static,
{
Self {
tag,
flux_fn: Box::new(flux_fn),
}
}
pub fn flux(&self, x: f64, y: f64, z: f64) -> Complex64 {
(self.flux_fn)(x, y, z)
}
pub fn boundary_edges<'a>(&self, mesh: &'a Mesh) -> impl Iterator<Item = &'a BoundaryFace> {
mesh.boundaries
.iter()
.filter(move |b| b.marker == self.tag as i32)
}
}
pub fn apply_neumann(
problem: &mut HelmholtzProblem,
mesh: &Mesh,
neumann_bcs: &[NeumannBC],
degree: PolynomialDegree,
) {
let quad_order = degree.degree() + 1;
let quad_1d = gauss_legendre_1d(quad_order);
for bc in neumann_bcs {
for boundary in bc.boundary_edges(mesh) {
let nodes = &boundary.nodes;
if nodes.len() == 2 {
let p0 = &mesh.nodes[nodes[0]];
let p1 = &mesh.nodes[nodes[1]];
let dx = p1.x - p0.x;
let dy = p1.y - p0.y;
let edge_len = (dx * dx + dy * dy).sqrt();
let mut contrib = [Complex64::new(0.0, 0.0); 2];
for qp in &quad_1d {
let t = 0.5 * (qp.xi() + 1.0);
let w = 0.5 * qp.weight * edge_len;
let x = p0.x + t * dx;
let y = p0.y + t * dy;
let z = p0.z + t * (p1.z - p0.z);
let h = bc.flux(x, y, z);
let n0 = 1.0 - t;
let n1 = t;
contrib[0] += h * Complex64::new(n0 * w, 0.0);
contrib[1] += h * Complex64::new(n1 * w, 0.0);
}
problem.rhs[nodes[0]] += contrib[0];
problem.rhs[nodes[1]] += contrib[1];
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::assembly::HelmholtzProblem;
use crate::mesh::unit_square_triangles;
#[test]
fn test_neumann_bc_creation() {
let bc = NeumannBC::new(3, |x, _y, _z| Complex64::new(x, 0.0));
assert_eq!(bc.tag, 3);
assert_eq!(bc.flux(2.0, 0.0, 0.0), Complex64::new(2.0, 0.0));
}
#[test]
fn test_apply_neumann() {
let mesh = unit_square_triangles(4);
let k = Complex64::new(1.0, 0.0);
let mut problem = HelmholtzProblem::assemble(&mesh, PolynomialDegree::P1, k, |_, _, _| {
Complex64::new(0.0, 0.0)
});
let original_sum: Complex64 = problem.rhs.iter().sum();
let bcs = vec![NeumannBC::new(3, |_, _, _| Complex64::new(1.0, 0.0))];
apply_neumann(&mut problem, &mesh, &bcs, PolynomialDegree::P1);
let new_sum: Complex64 = problem.rhs.iter().sum();
let diff = (new_sum - original_sum).norm();
assert!(
diff > 0.9 && diff < 1.1,
"Neumann contribution should be ~1, got {}",
diff
);
}
}