Skip to main content

numra_pde/
equations.rs

1//! Common PDE equations.
2//!
3//! Author: Moussa Leblouba
4//! Date: 3 February 2026
5//! Modified: 2 May 2026
6
7use crate::mol::PdeSystem;
8use numra_core::Scalar;
9
10/// 1D Heat (diffusion) equation: ∂u/∂t = α ∂²u/∂x².
11#[derive(Clone, Debug)]
12pub struct HeatEquation1D<S: Scalar> {
13    /// Thermal diffusivity α
14    pub alpha: S,
15}
16
17impl<S: Scalar> HeatEquation1D<S> {
18    pub fn new(alpha: S) -> Self {
19        Self { alpha }
20    }
21}
22
23impl<S: Scalar> PdeSystem<S> for HeatEquation1D<S> {
24    fn rhs(&self, _t: S, _x: S, _u: S, _du_dx: S, d2u_dx2: S) -> S {
25        self.alpha * d2u_dx2
26    }
27}
28
29/// 1D Diffusion-Reaction equation: ∂u/∂t = D ∂²u/∂x² + f(u).
30///
31/// Common examples:
32/// - Fisher equation: f(u) = u(1-u)
33/// - Allen-Cahn: f(u) = u - u³
34/// - Logistic: f(u) = ru(1 - u/K)
35#[derive(Clone)]
36pub struct DiffusionReaction1D<S: Scalar, R>
37where
38    R: Fn(S) -> S + Clone,
39{
40    /// Diffusion coefficient D
41    pub diffusion: S,
42    /// Reaction term f(u)
43    pub reaction: R,
44}
45
46impl<S: Scalar, R: Fn(S) -> S + Clone> DiffusionReaction1D<S, R> {
47    pub fn new(diffusion: S, reaction: R) -> Self {
48        Self {
49            diffusion,
50            reaction,
51        }
52    }
53
54    /// Create Fisher equation: D ∂²u/∂x² + ru(1-u).
55    pub fn fisher(diffusion: S, growth_rate: S) -> DiffusionReaction1D<S, impl Fn(S) -> S + Clone>
56    where
57        S: Copy,
58    {
59        let r = growth_rate;
60        DiffusionReaction1D::new(diffusion, move |u: S| r * u * (S::ONE - u))
61    }
62
63    /// Create Allen-Cahn equation: D ∂²u/∂x² + u - u³.
64    pub fn allen_cahn(diffusion: S) -> DiffusionReaction1D<S, impl Fn(S) -> S + Clone>
65    where
66        S: Copy,
67    {
68        DiffusionReaction1D::new(diffusion, |u: S| u - u * u * u)
69    }
70}
71
72impl<S: Scalar, R: Fn(S) -> S + Clone> PdeSystem<S> for DiffusionReaction1D<S, R> {
73    fn rhs(&self, _t: S, _x: S, u: S, _du_dx: S, d2u_dx2: S) -> S {
74        self.diffusion * d2u_dx2 + (self.reaction)(u)
75    }
76}
77
78/// 1D Advection-Diffusion equation: ∂u/∂t = D ∂²u/∂x² - v ∂u/∂x.
79///
80/// Infrastructure for advection-diffusion problems.
81#[allow(dead_code)]
82#[derive(Clone, Debug)]
83pub struct AdvectionDiffusion1D<S: Scalar> {
84    /// Diffusion coefficient
85    pub diffusion: S,
86    /// Advection velocity
87    pub velocity: S,
88}
89
90#[allow(dead_code)]
91impl<S: Scalar> AdvectionDiffusion1D<S> {
92    pub fn new(diffusion: S, velocity: S) -> Self {
93        Self {
94            diffusion,
95            velocity,
96        }
97    }
98}
99
100impl<S: Scalar> PdeSystem<S> for AdvectionDiffusion1D<S> {
101    fn rhs(&self, _t: S, _x: S, _u: S, du_dx: S, d2u_dx2: S) -> S {
102        self.diffusion * d2u_dx2 - self.velocity * du_dx
103    }
104}
105
106/// 1D Burgers' equation: ∂u/∂t + u ∂u/∂x = ν ∂²u/∂x².
107///
108/// Infrastructure for Burgers' equation problems.
109#[allow(dead_code)]
110#[derive(Clone, Debug)]
111pub struct Burgers1D<S: Scalar> {
112    /// Viscosity coefficient
113    pub viscosity: S,
114}
115
116#[allow(dead_code)]
117impl<S: Scalar> Burgers1D<S> {
118    pub fn new(viscosity: S) -> Self {
119        Self { viscosity }
120    }
121}
122
123impl<S: Scalar> PdeSystem<S> for Burgers1D<S> {
124    fn rhs(&self, _t: S, _x: S, u: S, du_dx: S, d2u_dx2: S) -> S {
125        self.viscosity * d2u_dx2 - u * du_dx
126    }
127}
128
129/// 1D Wave equation (first-order form):
130/// ∂u/∂t = v
131/// ∂v/∂t = c² ∂²u/∂x²
132///
133/// For MOL, we treat this as a system of equations.
134///
135/// Infrastructure for wave equation problems.
136#[allow(dead_code)]
137#[derive(Clone, Debug)]
138pub struct Wave1D<S: Scalar> {
139    /// Wave speed c
140    pub wave_speed: S,
141}
142
143#[allow(dead_code)]
144impl<S: Scalar> Wave1D<S> {
145    pub fn new(wave_speed: S) -> Self {
146        Self { wave_speed }
147    }
148}
149
150// Note: Wave equation requires special handling as a system
151// Implementation would need the PdeSystem trait to support systems
152
153/// Heat equation with source term: ∂u/∂t = α ∂²u/∂x² + Q(x,t).
154///
155/// Infrastructure for heat equation with source term problems.
156#[allow(dead_code)]
157#[derive(Clone)]
158pub struct HeatWithSource1D<S: Scalar, Q>
159where
160    Q: Fn(S, S) -> S + Clone,
161{
162    /// Thermal diffusivity
163    pub alpha: S,
164    /// Source term Q(x, t)
165    pub source: Q,
166}
167
168#[allow(dead_code)]
169impl<S: Scalar, Q: Fn(S, S) -> S + Clone> HeatWithSource1D<S, Q> {
170    pub fn new(alpha: S, source: Q) -> Self {
171        Self { alpha, source }
172    }
173}
174
175impl<S: Scalar, Q: Fn(S, S) -> S + Clone> PdeSystem<S> for HeatWithSource1D<S, Q> {
176    fn rhs(&self, t: S, x: S, _u: S, _du_dx: S, d2u_dx2: S) -> S {
177        self.alpha * d2u_dx2 + (self.source)(x, t)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_heat_equation() {
187        let heat = HeatEquation1D::new(0.1_f64);
188        // ∂²u/∂x² = 2 (for u = x²)
189        let rhs = heat.rhs(0.0, 0.5, 0.25, 1.0, 2.0);
190        assert!((rhs - 0.2).abs() < 1e-10);
191    }
192
193    #[test]
194    fn test_fisher_equation() {
195        // Fisher equation manually: D=0.1, r=1.0
196        let fisher = DiffusionReaction1D::new(0.1_f64, |u: f64| u * (1.0 - u));
197        // At u = 0.5: reaction = 0.5 * (1 - 0.5) = 0.25
198        // d2u = 2, diffusion = 0.1 * 2 = 0.2
199        // Total = 0.2 + 0.25 = 0.45
200        let rhs = fisher.rhs(0.0, 0.5, 0.5, 0.0, 2.0);
201        assert!((rhs - 0.45).abs() < 1e-10);
202    }
203
204    #[test]
205    fn test_burgers_equation() {
206        let burgers = Burgers1D::new(0.1_f64);
207        // At u = 1, du/dx = 2, d2u/dx2 = 0
208        // RHS = 0.1 * 0 - 1 * 2 = -2
209        let rhs = burgers.rhs(0.0, 0.5, 1.0, 2.0, 0.0);
210        assert!((rhs - (-2.0)).abs() < 1e-10);
211    }
212
213    #[test]
214    fn test_heat_with_source() {
215        let heat = HeatWithSource1D::new(0.1_f64, |x: f64, _t: f64| x * x);
216        // At x = 0.5: source = 0.25
217        // d2u = 2, diffusion = 0.1 * 2 = 0.2
218        // Total = 0.2 + 0.25 = 0.45
219        let rhs = heat.rhs(0.0, 0.5, 0.0, 0.0, 2.0);
220        assert!((rhs - 0.45).abs() < 1e-10);
221    }
222}