use crate::error::{IntegrateError, IntegrateResult as Result};
use scirs2_core::ndarray::Array2;
use super::super::core::{FluidBoundaryCondition, FluidState, NavierStokesParams};
use super::boundary_handling::{apply_boundary_conditions_2d, apply_pressure_boundary_conditions};
#[derive(Debug, Clone)]
pub struct NavierStokesSolver {
pub params: NavierStokesParams,
pub bc_x: (FluidBoundaryCondition, FluidBoundaryCondition),
pub bc_y: (FluidBoundaryCondition, FluidBoundaryCondition),
}
impl NavierStokesSolver {
pub fn new(
params: NavierStokesParams,
bc_x: (FluidBoundaryCondition, FluidBoundaryCondition),
bc_y: (FluidBoundaryCondition, FluidBoundaryCondition),
) -> Self {
Self { params, bc_x, bc_y }
}
pub fn solve_2d(
&self,
initial_state: FluidState,
t_final: f64,
save_interval: usize,
) -> Result<Vec<FluidState>> {
let mut states = vec![initial_state.clone()];
let mut current = initial_state;
let n_steps = (t_final / self.params.dt).ceil() as usize;
for step in 0..n_steps {
let (u_star, v_star) = self.compute_intermediate_velocity_2d(¤t)?;
let pressure = self.solve_pressure_poisson_2d(&u_star, &v_star, ¤t)?;
let (u_new, v_new) =
self.correct_velocity_2d(&u_star, &v_star, &pressure, current.dx, current.dy)?;
current.velocity = vec![u_new, v_new];
current.pressure = pressure;
current.time += self.params.dt;
if (step + 1) % save_interval == 0 {
states.push(current.clone());
}
}
Ok(states)
}
pub fn step(&self, state: &mut FluidState) -> Result<()> {
let (u_star, v_star) = self.compute_intermediate_velocity_2d(state)?;
let pressure = self.solve_pressure_poisson_2d(&u_star, &v_star, state)?;
let (u_new, v_new) =
self.correct_velocity_2d(&u_star, &v_star, &pressure, state.dx, state.dy)?;
state.velocity = vec![u_new, v_new];
state.pressure = pressure;
state.time += self.params.dt;
Ok(())
}
fn compute_intermediate_velocity_2d(
&self,
state: &FluidState,
) -> Result<(Array2<f64>, Array2<f64>)> {
let u = &state.velocity[0];
let v = &state.velocity[1];
let mut u_star = u.clone();
let mut v_star = v.clone();
if self.params.semi_lagrangian {
self.semi_lagrangian_advection_2d(u, v, &mut u_star, &mut v_star, state.dx, state.dy)?;
} else {
self.standard_advection_2d(u, v, &mut u_star, &mut v_star, state.dx, state.dy)?;
}
self.add_diffusion_2d(&mut u_star, &mut v_star, u, v, state.dx, state.dy)?;
apply_boundary_conditions_2d(&mut u_star, &mut v_star, self.bc_x, self.bc_y)?;
Ok((u_star, v_star))
}
fn semi_lagrangian_advection_2d(
&self,
u: &Array2<f64>,
v: &Array2<f64>,
u_new: &mut Array2<f64>,
v_new: &mut Array2<f64>,
dx: f64,
dy: f64,
) -> Result<()> {
let (ny, nx) = u.dim();
for j in 1..ny - 1 {
for i in 1..nx - 1 {
let x_back = i as f64 - u[[j, i]] * self.params.dt / dx;
let y_back = j as f64 - v[[j, i]] * self.params.dt / dy;
u_new[[j, i]] = Self::bilinear_interpolate(u, x_back, y_back)?;
v_new[[j, i]] = Self::bilinear_interpolate(v, x_back, y_back)?;
}
}
Ok(())
}
fn standard_advection_2d(
&self,
u: &Array2<f64>,
v: &Array2<f64>,
u_new: &mut Array2<f64>,
v_new: &mut Array2<f64>,
dx: f64,
dy: f64,
) -> Result<()> {
let (ny, nx) = u.dim();
let dt = self.params.dt;
let indices: Vec<(usize, usize)> = (1..ny - 1)
.flat_map(|j| (1..nx - 1).map(move |i| (j, i)))
.collect();
let u_updates: Vec<((usize, usize), f64)> = indices
.iter()
.map(|&(j, i)| {
let u_val = u[[j, i]];
let v_val = v[[j, i]];
let du_dx = if u_val > 0.0 {
(u[[j, i]] - u[[j, i - 1]]) / dx
} else {
(u[[j, i + 1]] - u[[j, i]]) / dx
};
let du_dy = if v_val > 0.0 {
(u[[j, i]] - u[[j - 1, i]]) / dy
} else {
(u[[j + 1, i]] - u[[j, i]]) / dy
};
let u_update = u[[j, i]] - dt * (u_val * du_dx + v_val * du_dy);
((j, i), u_update)
})
.collect();
let v_updates: Vec<((usize, usize), f64)> = indices
.iter()
.map(|&(j, i)| {
let u_val = u[[j, i]];
let v_val = v[[j, i]];
let dv_dx = if u_val > 0.0 {
(v[[j, i]] - v[[j, i - 1]]) / dx
} else {
(v[[j, i + 1]] - v[[j, i]]) / dx
};
let dv_dy = if v_val > 0.0 {
(v[[j, i]] - v[[j - 1, i]]) / dy
} else {
(v[[j + 1, i]] - v[[j, i]]) / dy
};
let v_update = v[[j, i]] - dt * (u_val * dv_dx + v_val * dv_dy);
((j, i), v_update)
})
.collect();
for ((j, i), val) in u_updates {
u_new[[j, i]] = val;
}
for ((j, i), val) in v_updates {
v_new[[j, i]] = val;
}
Ok(())
}
fn add_diffusion_2d(
&self,
u_new: &mut Array2<f64>,
v_new: &mut Array2<f64>,
u: &Array2<f64>,
v: &Array2<f64>,
dx: f64,
dy: f64,
) -> Result<()> {
let (ny, nx) = u.dim();
let nu_dt = self.params.nu * self.params.dt;
for j in 1..ny - 1 {
for i in 1..nx - 1 {
let d2u_dx2 = (u[[j, i + 1]] - 2.0 * u[[j, i]] + u[[j, i - 1]]) / (dx * dx);
let d2u_dy2 = (u[[j + 1, i]] - 2.0 * u[[j, i]] + u[[j - 1, i]]) / (dy * dy);
u_new[[j, i]] += nu_dt * (d2u_dx2 + d2u_dy2);
let d2v_dx2 = (v[[j, i + 1]] - 2.0 * v[[j, i]] + v[[j, i - 1]]) / (dx * dx);
let d2v_dy2 = (v[[j + 1, i]] - 2.0 * v[[j, i]] + v[[j - 1, i]]) / (dy * dy);
v_new[[j, i]] += nu_dt * (d2v_dx2 + d2v_dy2);
}
}
Ok(())
}
fn solve_pressure_poisson_2d(
&self,
u_star: &Array2<f64>,
v_star: &Array2<f64>,
state: &FluidState,
) -> Result<Array2<f64>> {
let (ny, nx) = u_star.dim();
let mut pressure = Array2::zeros((ny, nx));
let mut rhs = Array2::zeros((ny, nx));
for j in 1..ny - 1 {
for i in 1..nx - 1 {
let div_u = (u_star[[j, i + 1]] - u_star[[j, i - 1]]) / (2.0 * state.dx)
+ (v_star[[j + 1, i]] - v_star[[j - 1, i]]) / (2.0 * state.dy);
rhs[[j, i]] = -self.params.rho * div_u / self.params.dt;
}
}
let mut pressure_new = pressure.clone();
for _ in 0..self.params.max_pressure_iter {
let mut max_diff: f64 = 0.0;
for j in 1..ny - 1 {
for i in 1..nx - 1 {
let p_new: f64 = ((pressure[[j, i + 1]] + pressure[[j, i - 1]])
/ (state.dx * state.dx)
+ (pressure[[j + 1, i]] + pressure[[j - 1, i]]) / (state.dy * state.dy)
- rhs[[j, i]])
/ (2.0 / (state.dx * state.dx) + 2.0 / (state.dy * state.dy));
let diff = f64::abs(p_new - pressure[[j, i]]);
max_diff = max_diff.max(diff);
pressure_new[[j, i]] = p_new;
}
}
pressure.assign(&pressure_new);
apply_pressure_boundary_conditions(&mut pressure)?;
if max_diff < self.params.pressure_tol {
break;
}
}
Ok(pressure)
}
fn correct_velocity_2d(
&self,
u_star: &Array2<f64>,
v_star: &Array2<f64>,
pressure: &Array2<f64>,
dx: f64,
dy: f64,
) -> Result<(Array2<f64>, Array2<f64>)> {
let (ny, nx) = u_star.dim();
let mut u_new = u_star.clone();
let mut v_new = v_star.clone();
let dt_over_rho = self.params.dt / self.params.rho;
for j in 1..ny - 1 {
for i in 1..nx - 1 {
let dp_dx = (pressure[[j, i + 1]] - pressure[[j, i - 1]]) / (2.0 * dx);
u_new[[j, i]] = u_star[[j, i]] - dt_over_rho * dp_dx;
let dp_dy = (pressure[[j + 1, i]] - pressure[[j - 1, i]]) / (2.0 * dy);
v_new[[j, i]] = v_star[[j, i]] - dt_over_rho * dp_dy;
}
}
Ok((u_new, v_new))
}
fn bilinear_interpolate(field: &Array2<f64>, x: f64, y: f64) -> Result<f64> {
let (ny, nx) = field.dim();
let x = x.max(0.0).min((nx - 1) as f64);
let y = y.max(0.0).min((ny - 1) as f64);
let i = x.floor() as usize;
let j = y.floor() as usize;
let fx = x - i as f64;
let fy = y - j as f64;
let i = i.min(nx - 2);
let j = j.min(ny - 2);
Ok(field[[j, i]] * (1.0 - fx) * (1.0 - fy)
+ field[[j, i + 1]] * fx * (1.0 - fy)
+ field[[j + 1, i]] * (1.0 - fx) * fy
+ field[[j + 1, i + 1]] * fx * fy)
}
}