1use numra_core::Scalar;
8
9pub trait BoundaryCondition<S: Scalar>: Clone {
11 fn apply(&self, t: S, interior_value: S, dx: S) -> S;
15
16 fn value(&self, _t: S) -> Option<S> {
18 None
19 }
20
21 fn flux(&self, _t: S) -> Option<S> {
23 None
24 }
25
26 fn is_dirichlet(&self) -> bool;
28}
29
30#[derive(Clone, Debug)]
32pub struct DirichletBC<S: Scalar> {
33 value: DirichletValue<S>,
35}
36
37#[derive(Clone, Debug)]
38enum DirichletValue<S: Scalar> {
39 Constant(S),
40 }
43
44impl<S: Scalar> DirichletBC<S> {
45 pub fn new(value: S) -> Self {
47 Self {
48 value: DirichletValue::Constant(value),
49 }
50 }
51}
52
53impl<S: Scalar> BoundaryCondition<S> for DirichletBC<S> {
54 fn apply(&self, t: S, _interior_value: S, _dx: S) -> S {
55 self.value(t).unwrap()
56 }
57
58 fn value(&self, _t: S) -> Option<S> {
59 match &self.value {
60 DirichletValue::Constant(v) => Some(*v),
61 }
62 }
63
64 fn is_dirichlet(&self) -> bool {
65 true
66 }
67}
68
69#[derive(Clone, Debug)]
71pub struct NeumannBC<S: Scalar> {
72 flux: S,
74}
75
76impl<S: Scalar> NeumannBC<S> {
77 pub fn new(flux: S) -> Self {
79 Self { flux }
80 }
81
82 pub fn zero_flux() -> Self {
84 Self { flux: S::ZERO }
85 }
86}
87
88impl<S: Scalar> BoundaryCondition<S> for NeumannBC<S> {
89 fn apply(&self, _t: S, interior_value: S, dx: S) -> S {
90 interior_value - S::from_f64(2.0) * dx * self.flux
94 }
95
96 fn flux(&self, _t: S) -> Option<S> {
97 Some(self.flux)
98 }
99
100 fn is_dirichlet(&self) -> bool {
101 false
102 }
103}
104
105#[derive(Clone, Debug)]
107pub struct RobinBC<S: Scalar> {
108 pub alpha: S,
110 pub beta: S,
112 pub gamma: S,
114}
115
116impl<S: Scalar> RobinBC<S> {
117 pub fn new(alpha: S, beta: S, gamma: S) -> Self {
119 Self { alpha, beta, gamma }
120 }
121
122 pub fn convective(h_over_k: S, u_ambient: S) -> Self {
125 Self {
126 alpha: h_over_k,
127 beta: S::ONE,
128 gamma: h_over_k * u_ambient,
129 }
130 }
131}
132
133impl<S: Scalar> BoundaryCondition<S> for RobinBC<S> {
134 fn apply(&self, _t: S, interior_value: S, dx: S) -> S {
135 let flux_approx = (self.gamma - self.alpha * interior_value) / self.beta;
144 interior_value - S::from_f64(2.0) * dx * flux_approx
145 }
146
147 fn is_dirichlet(&self) -> bool {
148 self.beta.abs() < S::from_f64(1e-10)
150 }
151}
152
153#[derive(Clone, Debug)]
155pub struct PeriodicBC;
156
157impl<S: Scalar> BoundaryCondition<S> for PeriodicBC {
158 fn apply(&self, _t: S, interior_value: S, _dx: S) -> S {
159 interior_value
162 }
163
164 fn is_dirichlet(&self) -> bool {
165 false
166 }
167}
168
169#[derive(Clone)]
171pub struct BoxedBC<S: Scalar> {
172 inner: BcType<S>,
173}
174
175#[derive(Clone)]
176enum BcType<S: Scalar> {
177 Dirichlet(DirichletBC<S>),
178 Neumann(NeumannBC<S>),
179 Robin(RobinBC<S>),
180 Periodic,
181}
182
183impl<S: Scalar> BoxedBC<S> {
184 pub fn dirichlet(value: S) -> Self {
185 Self {
186 inner: BcType::Dirichlet(DirichletBC::new(value)),
187 }
188 }
189
190 pub fn neumann(flux: S) -> Self {
191 Self {
192 inner: BcType::Neumann(NeumannBC::new(flux)),
193 }
194 }
195
196 pub fn robin(alpha: S, beta: S, gamma: S) -> Self {
197 Self {
198 inner: BcType::Robin(RobinBC::new(alpha, beta, gamma)),
199 }
200 }
201
202 pub fn periodic() -> Self {
203 Self {
204 inner: BcType::Periodic,
205 }
206 }
207}
208
209impl<S: Scalar> BoundaryCondition<S> for BoxedBC<S> {
210 fn apply(&self, t: S, interior_value: S, dx: S) -> S {
211 match &self.inner {
212 BcType::Dirichlet(bc) => bc.apply(t, interior_value, dx),
213 BcType::Neumann(bc) => bc.apply(t, interior_value, dx),
214 BcType::Robin(bc) => bc.apply(t, interior_value, dx),
215 BcType::Periodic => interior_value,
216 }
217 }
218
219 fn value(&self, t: S) -> Option<S> {
220 match &self.inner {
221 BcType::Dirichlet(bc) => bc.value(t),
222 _ => None,
223 }
224 }
225
226 fn flux(&self, t: S) -> Option<S> {
227 match &self.inner {
228 BcType::Neumann(bc) => bc.flux(t),
229 _ => None,
230 }
231 }
232
233 fn is_dirichlet(&self) -> bool {
234 matches!(&self.inner, BcType::Dirichlet(_))
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn test_dirichlet_bc() {
244 let bc = DirichletBC::new(100.0_f64);
245 assert!(bc.is_dirichlet());
246 assert!((bc.value(0.0).unwrap() - 100.0).abs() < 1e-10);
247 assert!((bc.apply(0.0, 50.0, 0.1) - 100.0).abs() < 1e-10);
248 }
249
250 #[test]
251 fn test_neumann_bc() {
252 let bc = NeumannBC::new(10.0_f64);
253 assert!(!bc.is_dirichlet());
254 assert!((bc.flux(0.0).unwrap() - 10.0).abs() < 1e-10);
255
256 let ghost = bc.apply(0.0, 50.0, 0.1);
260 assert!((ghost - 48.0).abs() < 1e-10);
261 }
262
263 #[test]
264 fn test_zero_flux_bc() {
265 let bc = NeumannBC::<f64>::zero_flux();
266 assert!((bc.flux(0.0).unwrap()).abs() < 1e-10);
267
268 let ghost = bc.apply(0.0, 50.0, 0.1);
270 assert!((ghost - 50.0).abs() < 1e-10);
271 }
272
273 #[test]
274 fn test_boxed_bc() {
275 let bc = BoxedBC::dirichlet(100.0_f64);
276 assert!(bc.is_dirichlet());
277 assert!((bc.value(0.0).unwrap() - 100.0).abs() < 1e-10);
278
279 let bc = BoxedBC::neumann(10.0_f64);
280 assert!(!bc.is_dirichlet());
281 assert!((bc.flux(0.0).unwrap() - 10.0).abs() < 1e-10);
282 }
283}