use numra_core::Scalar;
pub trait BoundaryCondition<S: Scalar>: Clone {
fn apply(&self, t: S, interior_value: S, dx: S) -> S;
fn value(&self, _t: S) -> Option<S> {
None
}
fn flux(&self, _t: S) -> Option<S> {
None
}
fn is_dirichlet(&self) -> bool;
}
#[derive(Clone, Debug)]
pub struct DirichletBC<S: Scalar> {
value: DirichletValue<S>,
}
#[derive(Clone, Debug)]
enum DirichletValue<S: Scalar> {
Constant(S),
}
impl<S: Scalar> DirichletBC<S> {
pub fn new(value: S) -> Self {
Self {
value: DirichletValue::Constant(value),
}
}
}
impl<S: Scalar> BoundaryCondition<S> for DirichletBC<S> {
fn apply(&self, t: S, _interior_value: S, _dx: S) -> S {
self.value(t).unwrap()
}
fn value(&self, _t: S) -> Option<S> {
match &self.value {
DirichletValue::Constant(v) => Some(*v),
}
}
fn is_dirichlet(&self) -> bool {
true
}
}
#[derive(Clone, Debug)]
pub struct NeumannBC<S: Scalar> {
flux: S,
}
impl<S: Scalar> NeumannBC<S> {
pub fn new(flux: S) -> Self {
Self { flux }
}
pub fn zero_flux() -> Self {
Self { flux: S::ZERO }
}
}
impl<S: Scalar> BoundaryCondition<S> for NeumannBC<S> {
fn apply(&self, _t: S, interior_value: S, dx: S) -> S {
interior_value - S::from_f64(2.0) * dx * self.flux
}
fn flux(&self, _t: S) -> Option<S> {
Some(self.flux)
}
fn is_dirichlet(&self) -> bool {
false
}
}
#[derive(Clone, Debug)]
pub struct RobinBC<S: Scalar> {
pub alpha: S,
pub beta: S,
pub gamma: S,
}
impl<S: Scalar> RobinBC<S> {
pub fn new(alpha: S, beta: S, gamma: S) -> Self {
Self { alpha, beta, gamma }
}
pub fn convective(h_over_k: S, u_ambient: S) -> Self {
Self {
alpha: h_over_k,
beta: S::ONE,
gamma: h_over_k * u_ambient,
}
}
}
impl<S: Scalar> BoundaryCondition<S> for RobinBC<S> {
fn apply(&self, _t: S, interior_value: S, dx: S) -> S {
let flux_approx = (self.gamma - self.alpha * interior_value) / self.beta;
interior_value - S::from_f64(2.0) * dx * flux_approx
}
fn is_dirichlet(&self) -> bool {
self.beta.abs() < S::from_f64(1e-10)
}
}
#[derive(Clone, Debug)]
pub struct PeriodicBC;
impl<S: Scalar> BoundaryCondition<S> for PeriodicBC {
fn apply(&self, _t: S, interior_value: S, _dx: S) -> S {
interior_value
}
fn is_dirichlet(&self) -> bool {
false
}
}
#[derive(Clone)]
pub struct BoxedBC<S: Scalar> {
inner: BcType<S>,
}
#[derive(Clone)]
enum BcType<S: Scalar> {
Dirichlet(DirichletBC<S>),
Neumann(NeumannBC<S>),
Robin(RobinBC<S>),
Periodic,
}
impl<S: Scalar> BoxedBC<S> {
pub fn dirichlet(value: S) -> Self {
Self {
inner: BcType::Dirichlet(DirichletBC::new(value)),
}
}
pub fn neumann(flux: S) -> Self {
Self {
inner: BcType::Neumann(NeumannBC::new(flux)),
}
}
pub fn robin(alpha: S, beta: S, gamma: S) -> Self {
Self {
inner: BcType::Robin(RobinBC::new(alpha, beta, gamma)),
}
}
pub fn periodic() -> Self {
Self {
inner: BcType::Periodic,
}
}
}
impl<S: Scalar> BoundaryCondition<S> for BoxedBC<S> {
fn apply(&self, t: S, interior_value: S, dx: S) -> S {
match &self.inner {
BcType::Dirichlet(bc) => bc.apply(t, interior_value, dx),
BcType::Neumann(bc) => bc.apply(t, interior_value, dx),
BcType::Robin(bc) => bc.apply(t, interior_value, dx),
BcType::Periodic => interior_value,
}
}
fn value(&self, t: S) -> Option<S> {
match &self.inner {
BcType::Dirichlet(bc) => bc.value(t),
_ => None,
}
}
fn flux(&self, t: S) -> Option<S> {
match &self.inner {
BcType::Neumann(bc) => bc.flux(t),
_ => None,
}
}
fn is_dirichlet(&self) -> bool {
matches!(&self.inner, BcType::Dirichlet(_))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dirichlet_bc() {
let bc = DirichletBC::new(100.0_f64);
assert!(bc.is_dirichlet());
assert!((bc.value(0.0).unwrap() - 100.0).abs() < 1e-10);
assert!((bc.apply(0.0, 50.0, 0.1) - 100.0).abs() < 1e-10);
}
#[test]
fn test_neumann_bc() {
let bc = NeumannBC::new(10.0_f64);
assert!(!bc.is_dirichlet());
assert!((bc.flux(0.0).unwrap() - 10.0).abs() < 1e-10);
let ghost = bc.apply(0.0, 50.0, 0.1);
assert!((ghost - 48.0).abs() < 1e-10);
}
#[test]
fn test_zero_flux_bc() {
let bc = NeumannBC::<f64>::zero_flux();
assert!((bc.flux(0.0).unwrap()).abs() < 1e-10);
let ghost = bc.apply(0.0, 50.0, 0.1);
assert!((ghost - 50.0).abs() < 1e-10);
}
#[test]
fn test_boxed_bc() {
let bc = BoxedBC::dirichlet(100.0_f64);
assert!(bc.is_dirichlet());
assert!((bc.value(0.0).unwrap() - 100.0).abs() < 1e-10);
let bc = BoxedBC::neumann(10.0_f64);
assert!(!bc.is_dirichlet());
assert!((bc.flux(0.0).unwrap() - 10.0).abs() < 1e-10);
}
}