use serde::Deserialize;
use serde::Serialize;
use crate::numerical::matrix::Matrix;
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct FluidProperties {
pub density: f64,
pub dynamic_viscosity: f64,
pub thermal_conductivity: f64,
pub specific_heat: f64,
}
impl FluidProperties {
#[must_use]
pub const fn new(
density: f64,
dynamic_viscosity: f64,
thermal_conductivity: f64,
specific_heat: f64,
) -> Self {
Self {
density,
dynamic_viscosity,
thermal_conductivity,
specific_heat,
}
}
#[must_use]
pub const fn air() -> Self {
Self {
density: 1.204,
dynamic_viscosity: 1.825e-5,
thermal_conductivity: 0.0257,
specific_heat: 1005.0,
}
}
#[must_use]
pub const fn water() -> Self {
Self {
density: 998.2,
dynamic_viscosity: 1.002e-3,
thermal_conductivity: 0.598,
specific_heat: 4182.0,
}
}
#[must_use]
pub fn kinematic_viscosity(&self) -> f64 {
self.dynamic_viscosity / self.density
}
#[must_use]
pub fn thermal_diffusivity(&self) -> f64 {
self.thermal_conductivity / (self.density * self.specific_heat)
}
#[must_use]
pub fn prandtl_number(&self) -> f64 {
self.dynamic_viscosity * self.specific_heat / self.thermal_conductivity
}
}
#[must_use]
pub fn reynolds_number(
velocity: f64,
length: f64,
kinematic_viscosity: f64,
) -> f64 {
velocity * length / kinematic_viscosity
}
#[must_use]
pub fn mach_number(
velocity: f64,
speed_of_sound: f64,
) -> f64 {
velocity / speed_of_sound
}
#[must_use]
pub fn froude_number(
velocity: f64,
length: f64,
gravity: f64,
) -> f64 {
velocity / (gravity * length).sqrt()
}
#[must_use]
pub fn cfl_number(
velocity: f64,
dt: f64,
dx: f64,
) -> f64 {
velocity.abs() * dt / dx
}
#[must_use]
pub fn check_cfl_stability(
velocity: f64,
dt: f64,
dx: f64,
max_cfl: f64,
) -> bool {
cfl_number(velocity, dt, dx) <= max_cfl
}
#[must_use]
#[allow(clippy::suspicious_operation_groupings)]
pub fn diffusion_number(
alpha: f64,
dt: f64,
dx: f64,
) -> f64 {
alpha * dt / (dx * dx)
}
#[must_use]
pub fn solve_advection_1d(
u0: &[f64],
c: f64,
dx: f64,
dt: f64,
num_steps: usize,
) -> Vec<Vec<f64>> {
let n = u0.len();
let mut u = u0.to_vec();
let mut results = Vec::with_capacity(num_steps + 1);
results.push(u.clone());
let nu = c * dt / dx;
for _ in 0..num_steps {
let mut u_next = vec![0.0; n];
for i in 1..(n - 1) {
if c > 0.0 {
u_next[i] = nu.mul_add(-(u[i] - u[i - 1]), u[i]);
} else {
u_next[i] = nu.mul_add(-(u[i + 1] - u[i]), u[i]);
}
}
u_next[0] = u_next[n - 2];
u_next[n - 1] = u_next[1];
u = u_next;
results.push(u.clone());
}
results
}
#[must_use]
#[allow(clippy::suspicious_operation_groupings)]
pub fn solve_diffusion_1d(
u0: &[f64],
alpha: f64,
dx: f64,
dt: f64,
num_steps: usize,
) -> Vec<Vec<f64>> {
let n = u0.len();
let mut u = u0.to_vec();
let mut results = Vec::with_capacity(num_steps + 1);
results.push(u.clone());
let r = alpha * dt / (dx * dx);
for _ in 0..num_steps {
let mut u_next = vec![0.0; n];
u_next[0] = u[0];
u_next[n - 1] = u[n - 1];
for i in 1..(n - 1) {
u_next[i] = r.mul_add(2.0f64.mul_add(-u[i], u[i - 1]) + u[i + 1], u[i]);
}
u = u_next;
results.push(u.clone());
}
results
}
#[must_use]
pub fn solve_poisson_2d_jacobi(
f: &Matrix<f64>,
u0: &Matrix<f64>,
dx: f64,
dy: f64,
max_iter: usize,
tolerance: f64,
) -> Matrix<f64> {
let nx = u0.rows();
let ny = u0.cols();
let mut u = u0.clone();
let mut u_new = u0.clone();
let dx2 = dx * dx;
let dy2 = dy * dy;
for _iter in 0..max_iter {
let mut max_diff = 0.0;
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
let val = 0.5
* (dy2.mul_add(
u.get(i + 1, j) + u.get(i - 1, j),
dx2 * (u.get(i, j + 1) + u.get(i, j - 1)),
) - (dx2 * dy2 * f.get(i, j)))
/ (dx2 + dy2);
let diff = (val - u.get(i, j)).abs();
if diff > max_diff {
max_diff = diff;
}
*u_new.get_mut(i, j) = val;
}
}
u = u_new.clone();
if max_diff < tolerance {
break;
}
}
u
}
#[must_use]
pub fn solve_poisson_2d_gauss_seidel(
f: &Matrix<f64>,
u0: &Matrix<f64>,
dx: f64,
dy: f64,
max_iter: usize,
tolerance: f64,
) -> Matrix<f64> {
let nx = u0.rows();
let ny = u0.cols();
let mut u = u0.clone();
let dx2 = dx * dx;
let dy2 = dy * dy;
let factor = 2.0 * (dx2 + dy2);
for _iter in 0..max_iter {
let mut max_diff = 0.0;
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
let old_val = *u.get(i, j);
let new_val = (dx2 * dy2).mul_add(
-*f.get(i, j),
dy2.mul_add(
*u.get(i + 1, j) + *u.get(i - 1, j),
dx2 * (*u.get(i, j + 1) + *u.get(i, j - 1)),
),
) / factor;
*u.get_mut(i, j) = new_val;
let diff = (new_val - old_val).abs();
if diff > max_diff {
max_diff = diff;
}
}
}
if max_diff < tolerance {
break;
}
}
u
}
#[must_use]
pub fn solve_poisson_2d_sor(
f: &Matrix<f64>,
u0: &Matrix<f64>,
dx: f64,
dy: f64,
omega: f64,
max_iter: usize,
tolerance: f64,
) -> Matrix<f64> {
let nx = u0.rows();
let ny = u0.cols();
let mut u = u0.clone();
let dx2 = dx * dx;
let dy2 = dy * dy;
let factor = 2.0 * (dx2 + dy2);
for _iter in 0..max_iter {
let mut max_diff = 0.0;
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
let old_val = *u.get(i, j);
let gs_val = (dx2 * dy2).mul_add(
-*f.get(i, j),
dy2.mul_add(
*u.get(i + 1, j) + *u.get(i - 1, j),
dx2 * (*u.get(i, j + 1) + *u.get(i, j - 1)),
),
) / factor;
let new_val = omega.mul_add(gs_val - old_val, old_val);
*u.get_mut(i, j) = new_val;
let diff = (new_val - old_val).abs();
if diff > max_diff {
max_diff = diff;
}
}
}
if max_diff < tolerance {
break;
}
}
u
}
#[must_use]
#[allow(clippy::suspicious_operation_groupings)]
pub fn solve_advection_diffusion_1d(
u0: &[f64],
c: f64,
alpha: f64,
dx: f64,
dt: f64,
num_steps: usize,
) -> Vec<Vec<f64>> {
let n = u0.len();
let mut u = u0.to_vec();
let mut results = Vec::with_capacity(num_steps + 1);
results.push(u.clone());
let nu = c * dt / dx;
let r = alpha * dt / (dx * dx);
for _ in 0..num_steps {
let mut u_next = vec![0.0; n];
u_next[0] = u[0];
u_next[n - 1] = u[n - 1];
for i in 1..(n - 1) {
let advection = if c > 0.0 {
-nu * (u[i] - u[i - 1])
} else {
-nu * (u[i + 1] - u[i])
};
let diffusion = r * (2.0f64.mul_add(-u[i], u[i + 1]) + u[i - 1]);
u_next[i] = u[i] + advection + diffusion;
}
u = u_next;
results.push(u.clone());
}
results
}
#[must_use]
#[allow(clippy::suspicious_operation_groupings)]
pub fn solve_burgers_1d(
u0: &[f64],
nu: f64,
dx: f64,
dt: f64,
num_steps: usize,
) -> Vec<Vec<f64>> {
let n = u0.len();
let mut u = u0.to_vec();
let mut results = Vec::with_capacity(num_steps + 1);
results.push(u.clone());
let r = nu * dt / (dx * dx);
for _ in 0..num_steps {
let mut u_next = vec![0.0; n];
u_next[0] = u[0];
u_next[n - 1] = u[n - 1];
for i in 1..(n - 1) {
let advection = if u[i] > 0.0 {
u[i] * (u[i] - u[i - 1]) / dx
} else {
u[i] * (u[i + 1] - u[i]) / dx
};
let diffusion = r * (2.0f64.mul_add(-u[i], u[i + 1]) + u[i - 1]);
u_next[i] = dt.mul_add(-advection, u[i]) + diffusion;
}
u = u_next;
results.push(u.clone());
}
results
}
#[must_use]
pub fn compute_vorticity(
u: &Matrix<f64>,
v: &Matrix<f64>,
dx: f64,
dy: f64,
) -> Matrix<f64> {
let nx = u.rows();
let ny = u.cols();
let mut omega = Matrix::zeros(nx, ny);
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
let dv_dx = (*v.get(i + 1, j) - *v.get(i - 1, j)) / (2.0 * dx);
let du_dy = (*u.get(i, j + 1) - *u.get(i, j - 1)) / (2.0 * dy);
*omega.get_mut(i, j) = dv_dx - du_dy;
}
}
omega
}
#[must_use]
pub fn compute_stream_function(
omega: &Matrix<f64>,
dx: f64,
dy: f64,
max_iter: usize,
tolerance: f64,
) -> Matrix<f64> {
let nx = omega.rows();
let ny = omega.cols();
let mut neg_omega = Matrix::zeros(nx, ny);
for i in 0..nx {
for j in 0..ny {
*neg_omega.get_mut(i, j) = -*omega.get(i, j);
}
}
let psi0 = Matrix::zeros(nx, ny);
solve_poisson_2d_gauss_seidel(&neg_omega, &psi0, dx, dy, max_iter, tolerance)
}
#[must_use]
pub fn velocity_from_stream_function(
psi: &Matrix<f64>,
dx: f64,
dy: f64,
) -> (Matrix<f64>, Matrix<f64>) {
let nx = psi.rows();
let ny = psi.cols();
let mut u = Matrix::zeros(nx, ny);
let mut v = Matrix::zeros(nx, ny);
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
*u.get_mut(i, j) = (*psi.get(i, j + 1) - *psi.get(i, j - 1)) / (2.0 * dy);
*v.get_mut(i, j) = -(*psi.get(i + 1, j) - *psi.get(i - 1, j)) / (2.0 * dx);
}
}
(u, v)
}
#[must_use]
pub fn compute_divergence(
u: &Matrix<f64>,
v: &Matrix<f64>,
dx: f64,
dy: f64,
) -> Matrix<f64> {
let nx = u.rows();
let ny = u.cols();
let mut div = Matrix::zeros(nx, ny);
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
let du_dx = (*u.get(i + 1, j) - *u.get(i - 1, j)) / (2.0 * dx);
let dv_dy = (*v.get(i, j + 1) - *v.get(i, j - 1)) / (2.0 * dy);
*div.get_mut(i, j) = du_dx + dv_dy;
}
}
div
}
#[must_use]
pub fn compute_gradient(
p: &Matrix<f64>,
dx: f64,
dy: f64,
) -> (Matrix<f64>, Matrix<f64>) {
let nx = p.rows();
let ny = p.cols();
let mut dp_dx = Matrix::zeros(nx, ny);
let mut dp_dy = Matrix::zeros(nx, ny);
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
*dp_dx.get_mut(i, j) = (*p.get(i + 1, j) - *p.get(i - 1, j)) / (2.0 * dx);
*dp_dy.get_mut(i, j) = (*p.get(i, j + 1) - *p.get(i, j - 1)) / (2.0 * dy);
}
}
(dp_dx, dp_dy)
}
#[must_use]
pub fn compute_laplacian(
f: &Matrix<f64>,
dx: f64,
dy: f64,
) -> Matrix<f64> {
let nx = f.rows();
let ny = f.cols();
let mut lap = Matrix::zeros(nx, ny);
let dx2 = dx * dx;
let dy2 = dy * dy;
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
let d2f_dx2 =
(2.0f64.mul_add(-*f.get(i, j), *f.get(i + 1, j)) + *f.get(i - 1, j)) / dx2;
let d2f_dy2 =
(2.0f64.mul_add(-*f.get(i, j), *f.get(i, j + 1)) + *f.get(i, j - 1)) / dy2;
*lap.get_mut(i, j) = d2f_dx2 + d2f_dy2;
}
}
lap
}
#[must_use]
pub fn lid_driven_cavity_simple(
nx: usize,
ny: usize,
re: f64,
lid_velocity: f64,
num_steps: usize,
dt: f64,
) -> (Matrix<f64>, Matrix<f64>) {
let dx = 1.0 / (nx - 1) as f64;
let dy = 1.0 / (ny - 1) as f64;
let nu = lid_velocity / re;
let mut psi = Matrix::zeros(nx, ny);
let mut omega = Matrix::zeros(nx, ny);
for i in 0..nx {
*omega.get_mut(i, ny - 1) =
-2.0 * *psi.get(i, ny - 2) / (dy * dy) - 2.0 * lid_velocity / dy;
}
for _ in 0..num_steps {
psi = compute_stream_function(&omega, dx, dy, 100, 1e-6);
let mut omega_new = omega.clone();
for i in 1..(nx - 1) {
for j in 1..(ny - 1) {
let u = (*psi.get(i, j + 1) - *psi.get(i, j - 1)) / (2.0 * dy);
let v = -(*psi.get(i + 1, j) - *psi.get(i - 1, j)) / (2.0 * dx);
let domega_dx = (*omega.get(i + 1, j) - *omega.get(i - 1, j)) / (2.0 * dx);
let domega_dy = (*omega.get(i, j + 1) - *omega.get(i, j - 1)) / (2.0 * dy);
let d2omega_dx2 = (2.0f64.mul_add(-*omega.get(i, j), *omega.get(i + 1, j))
+ *omega.get(i - 1, j))
/ (dx * dx);
let d2omega_dy2 = (2.0f64.mul_add(-*omega.get(i, j), *omega.get(i, j + 1))
+ *omega.get(i, j - 1))
/ (dy * dy);
*omega_new.get_mut(i, j) = dt.mul_add(
nu.mul_add(d2omega_dx2 + d2omega_dy2, -(u * domega_dx + v * domega_dy)),
*omega.get(i, j),
);
}
}
omega = omega_new;
for i in 0..nx {
*omega.get_mut(i, ny - 1) =
-2.0 * *psi.get(i, ny - 2) / (dy * dy) - 2.0 * lid_velocity / dy;
*omega.get_mut(i, 0) = -2.0 * *psi.get(i, 1) / (dy * dy);
}
for j in 0..ny {
*omega.get_mut(0, j) = -2.0 * *psi.get(1, j) / (dx * dx);
*omega.get_mut(nx - 1, j) = -2.0 * *psi.get(nx - 2, j) / (dx * dx);
}
}
(psi, omega)
}
pub fn apply_dirichlet_bc(
field: &mut Matrix<f64>,
boundary_value: f64,
) {
let nx = field.rows();
let ny = field.cols();
for i in 0..nx {
*field.get_mut(i, 0) = boundary_value;
*field.get_mut(i, ny - 1) = boundary_value;
}
for j in 0..ny {
*field.get_mut(0, j) = boundary_value;
*field.get_mut(nx - 1, j) = boundary_value;
}
}
pub fn apply_neumann_bc(field: &mut Matrix<f64>) {
let nx = field.rows();
let ny = field.cols();
for i in 0..nx {
*field.get_mut(i, 0) = *field.get(i, 1);
*field.get_mut(i, ny - 1) = *field.get(i, ny - 2);
}
for j in 0..ny {
*field.get_mut(0, j) = *field.get(1, j);
*field.get_mut(nx - 1, j) = *field.get(nx - 2, j);
}
}
#[must_use]
pub fn max_velocity_magnitude(
u: &Matrix<f64>,
v: &Matrix<f64>,
) -> f64 {
let nx = u.rows();
let ny = u.cols();
let mut max_vel = 0.0;
for i in 0..nx {
for j in 0..ny {
let vel = (*u.get(i, j))
.mul_add(*u.get(i, j), *v.get(i, j) * *v.get(i, j))
.sqrt();
if vel > max_vel {
max_vel = vel;
}
}
}
max_vel
}
#[must_use]
pub fn l2_norm(field: &Matrix<f64>) -> f64 {
let nx = field.rows();
let ny = field.cols();
let mut sum = 0.0;
for i in 0..nx {
for j in 0..ny {
sum += *field.get(i, j) * *field.get(i, j);
}
}
(sum / (nx * ny) as f64).sqrt()
}
#[must_use]
pub fn max_abs(field: &Matrix<f64>) -> f64 {
let nx = field.rows();
let ny = field.cols();
let mut max_val = 0.0;
for i in 0..nx {
for j in 0..ny {
let abs_val = field.get(i, j).abs();
if abs_val > max_val {
max_val = abs_val;
}
}
}
max_val
}