use crate::boundary::BoundaryCondition;
use crate::discretize::FDM;
use crate::grid::Grid1D;
use numra_core::Scalar;
use numra_ode::OdeSystem;
pub trait PdeSystem<S: Scalar> {
fn rhs(&self, t: S, x: S, u: S, du_dx: S, d2u_dx2: S) -> S;
fn n_components(&self) -> usize {
1
}
}
#[derive(Clone)]
pub struct MOLSystem<
S: Scalar,
P: PdeSystem<S>,
BcL: BoundaryCondition<S>,
BcR: BoundaryCondition<S>,
> {
pde: P,
grid: Grid1D<S>,
bc_left: BcL,
bc_right: BcR,
}
impl<S: Scalar, P: PdeSystem<S>, BcL: BoundaryCondition<S>, BcR: BoundaryCondition<S>>
MOLSystem<S, P, BcL, BcR>
{
pub fn new(pde: P, grid: Grid1D<S>, bc_left: BcL, bc_right: BcR) -> Self {
Self {
pde,
grid,
bc_left,
bc_right,
}
}
pub fn grid(&self) -> &Grid1D<S> {
&self.grid
}
pub fn n_interior(&self) -> usize {
self.grid.n_interior()
}
pub fn build_full_solution(&self, t: S, u_interior: &[S]) -> Vec<S> {
let n = self.grid.len();
let mut u_full = vec![S::ZERO; n];
if self.bc_left.is_dirichlet() {
u_full[0] = self.bc_left.value(t).unwrap();
} else {
let dx = self.grid.dx_uniform();
u_full[0] = self.bc_left.apply(t, u_interior[0], dx);
}
for (i, &val) in u_interior.iter().enumerate() {
u_full[i + 1] = val;
}
if self.bc_right.is_dirichlet() {
u_full[n - 1] = self.bc_right.value(t).unwrap();
} else {
let dx = self.grid.dx_uniform();
u_full[n - 1] = self.bc_right.apply(t, u_interior[u_interior.len() - 1], dx);
}
u_full
}
}
impl<S: Scalar, P: PdeSystem<S>, BcL: BoundaryCondition<S>, BcR: BoundaryCondition<S>> OdeSystem<S>
for MOLSystem<S, P, BcL, BcR>
{
fn dim(&self) -> usize {
self.n_interior() * self.pde.n_components()
}
fn rhs(&self, t: S, y: &[S], dydt: &mut [S]) {
let n_interior = self.n_interior();
let _n_full = self.grid.len();
let dx = self.grid.dx_uniform();
let u_full = self.build_full_solution(t, y);
let points = self.grid.points();
for i in 0..n_interior {
let idx_full = i + 1;
let x = points[idx_full];
let u = u_full[idx_full];
let du_dx = FDM::d1_central(&u_full, dx, idx_full);
let d2u_dx2 = FDM::d2_central(&u_full, dx, idx_full);
dydt[i] = self.pde.rhs(t, x, u, du_dx, d2u_dx2);
}
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct HeatEquationPde<S: Scalar> {
pub alpha: S,
}
#[allow(dead_code)]
impl<S: Scalar> HeatEquationPde<S> {
pub fn new(alpha: S) -> Self {
Self { alpha }
}
}
impl<S: Scalar> PdeSystem<S> for HeatEquationPde<S> {
fn rhs(&self, _t: S, _x: S, _u: S, _du_dx: S, d2u_dx2: S) -> S {
self.alpha * d2u_dx2
}
}
#[allow(dead_code)]
#[derive(Clone)]
pub struct DiffusionReactionPde<S: Scalar, F>
where
F: Fn(S) -> S + Clone,
{
pub diffusion: S,
pub reaction: F,
}
#[allow(dead_code)]
impl<S: Scalar, F: Fn(S) -> S + Clone> DiffusionReactionPde<S, F> {
pub fn new(diffusion: S, reaction: F) -> Self {
Self {
diffusion,
reaction,
}
}
}
impl<S: Scalar, F: Fn(S) -> S + Clone> PdeSystem<S> for DiffusionReactionPde<S, F> {
fn rhs(&self, _t: S, _x: S, u: S, _du_dx: S, d2u_dx2: S) -> S {
self.diffusion * d2u_dx2 + (self.reaction)(u)
}
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct AdvectionDiffusionPde<S: Scalar> {
pub diffusion: S,
pub velocity: S,
}
#[allow(dead_code)]
impl<S: Scalar> AdvectionDiffusionPde<S> {
pub fn new(diffusion: S, velocity: S) -> Self {
Self {
diffusion,
velocity,
}
}
}
impl<S: Scalar> PdeSystem<S> for AdvectionDiffusionPde<S> {
fn rhs(&self, _t: S, _x: S, _u: S, du_dx: S, d2u_dx2: S) -> S {
self.diffusion * d2u_dx2 - self.velocity * du_dx
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::boundary::DirichletBC;
use numra_ode::{DoPri5, Solver, SolverOptions};
#[test]
fn test_mol_system_dim() {
let grid = Grid1D::uniform(0.0, 1.0, 11);
let pde = HeatEquationPde::new(0.01_f64);
let bc_left = DirichletBC::new(1.0);
let bc_right = DirichletBC::new(0.0);
let mol = MOLSystem::new(pde, grid, bc_left, bc_right);
assert_eq!(mol.dim(), 9); }
#[test]
fn test_heat_equation_dirichlet() {
let grid = Grid1D::uniform(0.0, 1.0, 21);
let pde = HeatEquationPde::new(0.01_f64);
let bc_left = DirichletBC::new(1.0);
let bc_right = DirichletBC::new(0.0);
let mol = MOLSystem::new(pde, grid.clone(), bc_left, bc_right);
let u0: Vec<f64> = grid.interior_points().iter().map(|&x| 1.0 - x).collect();
let options = SolverOptions::default().rtol(1e-6).atol(1e-9);
let result = DoPri5::solve(&mol, 0.0, 1.0, &u0, &options).unwrap();
let y_final = result.y_final().unwrap();
for (i, &y) in y_final.iter().enumerate() {
let x = grid.interior_points()[i];
let expected = 1.0 - x;
assert!(
(y - expected).abs() < 0.1,
"At x={}: y={}, expected={}",
x,
y,
expected
);
}
}
#[test]
fn test_diffusion_reaction() {
let grid = Grid1D::uniform(0.0, 1.0, 21);
let pde = DiffusionReactionPde::new(0.01_f64, |u: f64| u * (1.0 - u));
let bc_left = DirichletBC::new(1.0);
let bc_right = DirichletBC::new(0.0);
let mol = MOLSystem::new(pde, grid.clone(), bc_left, bc_right);
let u0: Vec<f64> = grid
.interior_points()
.iter()
.map(|&x| if x < 0.3 { 1.0 } else { 0.0 })
.collect();
let options = SolverOptions::default().rtol(1e-6);
let result = DoPri5::solve(&mol, 0.0, 0.5, &u0, &options).unwrap();
assert!(result.success);
let y_final = result.y_final().unwrap();
for &y in &y_final {
assert!(
(-0.1..=1.1).contains(&y),
"Solution out of expected range: {}",
y
);
}
}
}