Skip to main content

aeon_tk/kernel/
boundary.rs

1//! Provides generalized interfaces for expressing various kinds of boundary condition.
2//!
3//! This module uses a combination of trait trickery and type transformers to
4//! create an ergonomic API for working with boundaries.
5
6use crate::geometry::{Face, FaceArray};
7
8// #[derive(Clone, Copy, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
9// pub enum Boundary {
10//     Symmetric,
11//     AntiSymmetric,
12//     Dirichlet(f64),
13//     #[default]
14//     Free,
15//     Custom,
16// }
17
18// impl Boundary {
19//     pub fn is_one_sided(self) -> bool {
20//         matches!(self, Self::Free)
21//     }
22// }
23
24// pub trait BoundaryConds<const N: usize> {
25//     fn boundary(&self, channel: usize, position: [f64; N]) -> Boundary;
26// }
27
28// #[derive(Clone, Copy, Debug, PartialEq, Default, serde::Serialize, serde::Deserialize)]
29// pub enum Penalty {
30//     #[default]
31//     Free,
32//     Radiative {
33//         target: f64,
34//         strength: f64,
35//     },
36// }
37
38// pub trait PenaltyTerms<const N: usize> {
39//     fn penalty(&self, channel: usize, position: [f64; N]) -> Penalty;
40// }
41
42/// Indicates what type of boundary condition is used along a particualr
43/// face of the domain. More specific boundary conditions are provided
44/// by the `Condition` API, but for many funtions, `Boundary` provides
45/// enough information to compute supports and apply stencils.
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
47pub enum BoundaryKind {
48    /// Symmetric Boundary condition. Function is even along axis
49    /// (so function values are reflected across the axis with the same sign)
50    Symmetric,
51    /// Antisymmetric Boundary condition. Function is even along axis
52    /// (so function values are reflected across the axis with the opposite sign,
53    /// on axis set to zero)
54    AntiSymmetric,
55    /// This boundary condition indicates that the ghost nodes have been filled manually via some external
56    /// process. This can be used to implement custom boundary conditions, and is primarily used to
57    /// fill inter-grid boundaries in the adaptive mesh refinement driver.
58    Custom,
59    /// The boundary condition is implemented via
60    Radiative,
61    #[default]
62    Free,
63    /// Boundary is set to a given value, lopsided stencils are used near the boundary. This
64    /// condition can either be strongly enforced (values of systems are set at boundary before
65    /// application) or weakly enforced (condition is applied to time derivative of system).
66    StrongDirichlet,
67    WeakDirichlet,
68}
69
70impl BoundaryKind {
71    /// Are ghost nodes are used when enforcing this kind of boundary condition?
72    pub fn needs_ghost(self) -> bool {
73        match self {
74            BoundaryKind::AntiSymmetric | BoundaryKind::Symmetric | BoundaryKind::Custom => true,
75            BoundaryKind::Radiative
76            | BoundaryKind::Free
77            | BoundaryKind::StrongDirichlet
78            | BoundaryKind::WeakDirichlet => false,
79        }
80    }
81
82    pub fn class(self) -> BoundaryClass {
83        match self {
84            BoundaryKind::AntiSymmetric | BoundaryKind::Symmetric | BoundaryKind::Custom => {
85                BoundaryClass::Ghost
86            }
87            BoundaryKind::Radiative
88            | BoundaryKind::StrongDirichlet
89            | BoundaryKind::WeakDirichlet
90            | BoundaryKind::Free => BoundaryClass::OneSided,
91        }
92    }
93}
94
95#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
96pub enum BoundaryClass {
97    #[default]
98    OneSided,
99    Ghost,
100    Periodic,
101}
102
103impl BoundaryClass {
104    pub fn has_ghost(self) -> bool {
105        matches!(self, BoundaryClass::Ghost | BoundaryClass::Periodic)
106    }
107}
108
109/// Describes a radiative boundary condition at a point on the boundary.
110#[derive(Clone, Copy, Debug)]
111pub struct RadiativeParams {
112    /// Target value for field.
113    pub target: f64,
114    /// Wavespeed of field at boundary.
115    pub speed: f64,
116}
117
118impl RadiativeParams {
119    /// Constructs a boundary condition for a wave asymptotically approaching a given value, travelling
120    /// at the speed of light (c = 1).
121    pub fn lightlike(target: f64) -> Self {
122        Self { target, speed: 1.0 }
123    }
124}
125
126#[derive(Clone, Copy, Debug)]
127pub struct DirichletParams {
128    pub target: f64,
129    pub strength: f64,
130}
131
132/// Provides specifics for enforcing boundary conditions for
133/// a particular field.
134pub trait BoundaryConds<const N: usize>: Clone {
135    fn kind(&self, _face: Face<N>) -> BoundaryKind;
136
137    fn radiative(&self, _position: [f64; N]) -> RadiativeParams {
138        RadiativeParams {
139            target: 0.0,
140            speed: 1.0,
141        }
142    }
143
144    fn dirichlet(&self, _position: [f64; N]) -> DirichletParams {
145        DirichletParams {
146            target: 0.0,
147            strength: 1.0,
148        }
149    }
150}
151
152/// Checks whether a set of boundary conditions are compatible with the given ghost flags.
153pub fn is_boundary_compatible<const N: usize, B: BoundaryConds<N>>(
154    boundary: &FaceArray<N, BoundaryClass>,
155    conditions: &B,
156) -> bool {
157    Face::iterate()
158        .map(|face| conditions.kind(face).class() == boundary[face])
159        .all(|x| x)
160}
161
162/// A generalization of `Condition<N>` for a coupled systems of scalar fields.
163pub trait SystemBoundaryConds<const N: usize>: Clone {
164    fn kind(&self, channel: usize, _face: Face<N>) -> BoundaryKind;
165
166    fn radiative(&self, _channel: usize, _position: [f64; N]) -> RadiativeParams {
167        RadiativeParams {
168            target: 0.0,
169            speed: 1.0,
170        }
171    }
172
173    fn dirichlet(&self, _channel: usize, _position: [f64; N]) -> DirichletParams {
174        DirichletParams {
175            target: 0.0,
176            strength: 1.0,
177        }
178    }
179
180    fn field(&self, channel: usize) -> FieldBoundaryConds<N, Self> {
181        FieldBoundaryConds::new(self.clone(), channel)
182    }
183}
184
185/// Transfers a set of `Conditions<N>` into a single `Condition<N>` by only applying the set of conditions
186/// to a single field.
187pub struct FieldBoundaryConds<const N: usize, C> {
188    conditions: C,
189    channel: usize,
190}
191
192impl<const N: usize, C: SystemBoundaryConds<N>> FieldBoundaryConds<N, C> {
193    pub const fn new(conditions: C, channel: usize) -> Self {
194        Self {
195            channel,
196            conditions,
197        }
198    }
199}
200
201impl<const N: usize, C: SystemBoundaryConds<N>> Clone for FieldBoundaryConds<N, C> {
202    fn clone(&self) -> Self {
203        Self {
204            conditions: self.conditions.clone(),
205            channel: self.channel,
206        }
207    }
208}
209
210impl<const N: usize, C: SystemBoundaryConds<N>> BoundaryConds<N> for FieldBoundaryConds<N, C> {
211    fn kind(&self, face: Face<N>) -> BoundaryKind {
212        self.conditions.kind(self.channel, face)
213    }
214
215    fn radiative(&self, position: [f64; N]) -> RadiativeParams {
216        self.conditions.radiative(self.channel, position)
217    }
218
219    fn dirichlet(&self, position: [f64; N]) -> DirichletParams {
220        self.conditions.dirichlet(self.channel, position)
221    }
222}
223
224// ****************************
225// Specializations ************
226// ****************************
227
228/// Transforms a single condition into a set of `Conditions<N>` where `Self::System = Scalar`.
229#[derive(Clone)]
230pub struct ScalarConditions<I>(pub I);
231
232impl<I> ScalarConditions<I> {
233    pub const fn new(inner: I) -> Self {
234        Self(inner)
235    }
236}
237
238impl<const N: usize, I: BoundaryConds<N>> SystemBoundaryConds<N> for ScalarConditions<I> {
239    fn kind(&self, channel: usize, face: Face<N>) -> BoundaryKind {
240        debug_assert!(channel == 0);
241        self.0.kind(face)
242    }
243
244    fn radiative(&self, channel: usize, position: [f64; N]) -> RadiativeParams {
245        debug_assert!(channel == 0);
246        self.0.radiative(position)
247    }
248
249    fn dirichlet(&self, channel: usize, position: [f64; N]) -> DirichletParams {
250        debug_assert!(channel == 0);
251        self.0.dirichlet(position)
252    }
253}
254
255#[derive(Clone)]
256pub struct EmptyConditions;
257
258impl<const N: usize> SystemBoundaryConds<N> for EmptyConditions {
259    fn kind(&self, _channel: usize, _face: Face<N>) -> BoundaryKind {
260        unreachable!()
261    }
262
263    fn radiative(&self, _channel: usize, _position: [f64; N]) -> RadiativeParams {
264        unreachable!()
265    }
266
267    fn dirichlet(&self, _channel: usize, _position: [f64; N]) -> DirichletParams {
268        unreachable!()
269    }
270}