1use crate::boundary::BoundaryCondition;
11use crate::discretize::FDM;
12use crate::grid::Grid1D;
13use numra_core::Scalar;
14use numra_ode::OdeSystem;
15
16pub trait PdeSystem<S: Scalar> {
23 fn rhs(&self, t: S, x: S, u: S, du_dx: S, d2u_dx2: S) -> S;
32
33 fn n_components(&self) -> usize {
35 1
36 }
37}
38
39#[derive(Clone)]
46pub struct MOLSystem<
47 S: Scalar,
48 P: PdeSystem<S>,
49 BcL: BoundaryCondition<S>,
50 BcR: BoundaryCondition<S>,
51> {
52 pde: P,
54 grid: Grid1D<S>,
56 bc_left: BcL,
58 bc_right: BcR,
60}
61
62impl<S: Scalar, P: PdeSystem<S>, BcL: BoundaryCondition<S>, BcR: BoundaryCondition<S>>
63 MOLSystem<S, P, BcL, BcR>
64{
65 pub fn new(pde: P, grid: Grid1D<S>, bc_left: BcL, bc_right: BcR) -> Self {
67 Self {
68 pde,
69 grid,
70 bc_left,
71 bc_right,
72 }
73 }
74
75 pub fn grid(&self) -> &Grid1D<S> {
77 &self.grid
78 }
79
80 pub fn n_interior(&self) -> usize {
82 self.grid.n_interior()
83 }
84
85 pub fn build_full_solution(&self, t: S, u_interior: &[S]) -> Vec<S> {
89 let n = self.grid.len();
90 let mut u_full = vec![S::ZERO; n];
91
92 if self.bc_left.is_dirichlet() {
94 u_full[0] = self.bc_left.value(t).unwrap();
95 } else {
96 let dx = self.grid.dx_uniform();
98 u_full[0] = self.bc_left.apply(t, u_interior[0], dx);
99 }
100
101 for (i, &val) in u_interior.iter().enumerate() {
103 u_full[i + 1] = val;
104 }
105
106 if self.bc_right.is_dirichlet() {
108 u_full[n - 1] = self.bc_right.value(t).unwrap();
109 } else {
110 let dx = self.grid.dx_uniform();
111 u_full[n - 1] = self.bc_right.apply(t, u_interior[u_interior.len() - 1], dx);
112 }
113
114 u_full
115 }
116}
117
118impl<S: Scalar, P: PdeSystem<S>, BcL: BoundaryCondition<S>, BcR: BoundaryCondition<S>> OdeSystem<S>
119 for MOLSystem<S, P, BcL, BcR>
120{
121 fn dim(&self) -> usize {
122 self.n_interior() * self.pde.n_components()
123 }
124
125 fn rhs(&self, t: S, y: &[S], dydt: &mut [S]) {
126 let n_interior = self.n_interior();
127 let _n_full = self.grid.len();
128 let dx = self.grid.dx_uniform();
129
130 let u_full = self.build_full_solution(t, y);
132 let points = self.grid.points();
133
134 for i in 0..n_interior {
136 let idx_full = i + 1; let x = points[idx_full];
139 let u = u_full[idx_full];
140
141 let du_dx = FDM::d1_central(&u_full, dx, idx_full);
143 let d2u_dx2 = FDM::d2_central(&u_full, dx, idx_full);
144
145 dydt[i] = self.pde.rhs(t, x, u, du_dx, d2u_dx2);
147 }
148 }
149}
150
151#[allow(dead_code)]
155#[derive(Clone, Debug)]
156pub struct HeatEquationPde<S: Scalar> {
157 pub alpha: S,
159}
160
161#[allow(dead_code)]
162impl<S: Scalar> HeatEquationPde<S> {
163 pub fn new(alpha: S) -> Self {
164 Self { alpha }
165 }
166}
167
168impl<S: Scalar> PdeSystem<S> for HeatEquationPde<S> {
169 fn rhs(&self, _t: S, _x: S, _u: S, _du_dx: S, d2u_dx2: S) -> S {
170 self.alpha * d2u_dx2
171 }
172}
173
174#[allow(dead_code)]
178#[derive(Clone)]
179pub struct DiffusionReactionPde<S: Scalar, F>
180where
181 F: Fn(S) -> S + Clone,
182{
183 pub diffusion: S,
185 pub reaction: F,
187}
188
189#[allow(dead_code)]
190impl<S: Scalar, F: Fn(S) -> S + Clone> DiffusionReactionPde<S, F> {
191 pub fn new(diffusion: S, reaction: F) -> Self {
192 Self {
193 diffusion,
194 reaction,
195 }
196 }
197}
198
199impl<S: Scalar, F: Fn(S) -> S + Clone> PdeSystem<S> for DiffusionReactionPde<S, F> {
200 fn rhs(&self, _t: S, _x: S, u: S, _du_dx: S, d2u_dx2: S) -> S {
201 self.diffusion * d2u_dx2 + (self.reaction)(u)
202 }
203}
204
205#[allow(dead_code)]
209#[derive(Clone, Debug)]
210pub struct AdvectionDiffusionPde<S: Scalar> {
211 pub diffusion: S,
213 pub velocity: S,
215}
216
217#[allow(dead_code)]
218impl<S: Scalar> AdvectionDiffusionPde<S> {
219 pub fn new(diffusion: S, velocity: S) -> Self {
220 Self {
221 diffusion,
222 velocity,
223 }
224 }
225}
226
227impl<S: Scalar> PdeSystem<S> for AdvectionDiffusionPde<S> {
228 fn rhs(&self, _t: S, _x: S, _u: S, du_dx: S, d2u_dx2: S) -> S {
229 self.diffusion * d2u_dx2 - self.velocity * du_dx
230 }
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use crate::boundary::DirichletBC;
237 use numra_ode::{DoPri5, Solver, SolverOptions};
238
239 #[test]
240 fn test_mol_system_dim() {
241 let grid = Grid1D::uniform(0.0, 1.0, 11);
242 let pde = HeatEquationPde::new(0.01_f64);
243 let bc_left = DirichletBC::new(1.0);
244 let bc_right = DirichletBC::new(0.0);
245
246 let mol = MOLSystem::new(pde, grid, bc_left, bc_right);
247 assert_eq!(mol.dim(), 9); }
249
250 #[test]
251 fn test_heat_equation_dirichlet() {
252 let grid = Grid1D::uniform(0.0, 1.0, 21);
257 let pde = HeatEquationPde::new(0.01_f64);
258 let bc_left = DirichletBC::new(1.0);
259 let bc_right = DirichletBC::new(0.0);
260
261 let mol = MOLSystem::new(pde, grid.clone(), bc_left, bc_right);
262
263 let u0: Vec<f64> = grid.interior_points().iter().map(|&x| 1.0 - x).collect();
265
266 let options = SolverOptions::default().rtol(1e-6).atol(1e-9);
267 let result = DoPri5::solve(&mol, 0.0, 1.0, &u0, &options).unwrap();
268
269 let y_final = result.y_final().unwrap();
271 for (i, &y) in y_final.iter().enumerate() {
272 let x = grid.interior_points()[i];
273 let expected = 1.0 - x;
274 assert!(
275 (y - expected).abs() < 0.1,
276 "At x={}: y={}, expected={}",
277 x,
278 y,
279 expected
280 );
281 }
282 }
283
284 #[test]
285 fn test_diffusion_reaction() {
286 let grid = Grid1D::uniform(0.0, 1.0, 21);
288 let pde = DiffusionReactionPde::new(0.01_f64, |u: f64| u * (1.0 - u));
289 let bc_left = DirichletBC::new(1.0);
290 let bc_right = DirichletBC::new(0.0);
291
292 let mol = MOLSystem::new(pde, grid.clone(), bc_left, bc_right);
293
294 let u0: Vec<f64> = grid
295 .interior_points()
296 .iter()
297 .map(|&x| if x < 0.3 { 1.0 } else { 0.0 })
298 .collect();
299
300 let options = SolverOptions::default().rtol(1e-6);
301 let result = DoPri5::solve(&mol, 0.0, 0.5, &u0, &options).unwrap();
302
303 assert!(result.success);
304 let y_final = result.y_final().unwrap();
306 for &y in &y_final {
307 assert!(
308 (-0.1..=1.1).contains(&y),
309 "Solution out of expected range: {}",
310 y
311 );
312 }
313 }
314}