use super::{
operators::{AnisotropicDivergence, Discretizes, Laplacian},
stencil::Stencil,
};
use crate::{
core::storage::Real,
discretization::finite_difference::CentralLaplacian,
geometry::{
cartesian::{CartesianGrid, for_each_interior},
grid::{BlockId, Grid},
},
};
#[derive(Debug, Clone, Copy)]
pub struct FiniteVolume {
pub tol: f64,
}
impl Default for FiniteVolume {
fn default() -> Self {
Self { tol: 1e-8 }
}
}
impl Discretizes<CartesianGrid<2>, AnisotropicDivergence> for FiniteVolume {
type Stencil = KarmaRappelFlux;
fn build(&self, grid: &CartesianGrid<2>, op: AnisotropicDivergence) -> KarmaRappelFlux {
let [hx, hy] = grid.spacing(BlockId(0));
debug_assert!(
(hx - hy).abs() < 1e-12 * hx.abs(),
"Karma–Rappel flux assumes isotropic spacing"
);
KarmaRappelFlux::new(op.eps4, self.tol)
}
}
impl<const D: usize> Discretizes<CartesianGrid<D>, Laplacian> for FiniteVolume {
type Stencil = CentralLaplacian;
fn build(&self, _grid: &CartesianGrid<D>, _op: Laplacian) -> CentralLaplacian {
CentralLaplacian
}
}
#[derive(Debug, Clone, Copy)]
pub struct KarmaRappelFlux {
a_bar: f64,
eps_prime: f64,
a12: f64,
tol: f64,
}
impl KarmaRappelFlux {
#[must_use]
pub fn new(eps4: f64, tol: f64) -> Self {
let a_bar = 3.0f64.mul_add(-eps4, 1.0);
let eps_prime = 3.0 * eps4 / a_bar;
Self {
a_bar,
eps_prime,
a12: 4.0 * a_bar * eps_prime,
tol,
}
}
#[inline(always)]
fn face<T: Real>(&self, dx: T, dy: T) -> (T, T) {
let dx2 = dx * dx;
let dy2 = dy * dy;
let sum = dx2 + dy2;
let mag2 = sum * sum;
if mag2 <= T::from_f64(self.tol) {
(T::from_f64(self.a_bar), T::ZERO)
} else {
let a = T::from_f64(self.a_bar)
* (T::ONE + T::from_f64(self.eps_prime) * (dx2 * dx2 + dy2 * dy2) / mag2);
let da = -(T::from_f64(self.a12)) * dx * dy * (dx2 - dy2) / mag2;
(a, da)
}
}
#[inline(always)]
#[must_use]
pub fn center_anisotropy<T: Real>(
&self,
input: &<CartesianGrid<2> as Grid>::View<'_, T>,
idx: [isize; 2],
) -> T {
let [i, j] = idx;
let half = T::from_f64(0.5);
let dx = half * (input.get([i + 1, j]) - input.get([i - 1, j]));
let dy = half * (input.get([i, j + 1]) - input.get([i, j - 1]));
self.face(dx, dy).0
}
#[inline(always)]
pub fn center_anisotropy_rotated<T: Real>(
&self,
input: &<CartesianGrid<2> as Grid>::View<'_, T>,
idx: [isize; 2],
cos0: T,
sin0: T,
) -> T {
let [i, j] = idx;
let half = T::from_f64(0.5);
let dx = half * (input.get([i + 1, j]) - input.get([i - 1, j]));
let dy = half * (input.get([i, j + 1]) - input.get([i, j - 1]));
let (rx, ry) = rotate(cos0, sin0, dx, dy);
self.face(rx, ry).0
}
pub fn apply_oriented<T: Real>(
&self,
grid: &CartesianGrid<2>,
block: BlockId,
input: <CartesianGrid<2> as Grid>::View<'_, T>,
orientation: <CartesianGrid<2> as Grid>::View<'_, T>,
output: &mut <CartesianGrid<2> as Grid>::ViewMut<'_, T>,
) {
let [hx, hy] = grid.spacing(block);
let inv_h2 = T::from_f64(1.0 / (hx * hy));
let quarter = T::from_f64(0.25);
let p = |i: isize, j: isize| input.get([i, j]);
for_each_interior(input.interior(), |[i, j]| {
let theta0 = orientation.get([i, j]).to_f64();
let (s0, c0) = theta0.sin_cos();
let (c0, s0) = (T::from_f64(c0), T::from_f64(s0));
let derx_r = p(i + 1, j) - p(i, j);
let derx_l = p(i, j) - p(i - 1, j);
let derx_t = quarter * (p(i + 1, j + 1) - p(i - 1, j + 1) + p(i + 1, j) - p(i - 1, j));
let derx_b = quarter * (p(i + 1, j) - p(i - 1, j) + p(i + 1, j - 1) - p(i - 1, j - 1));
let dery_t = p(i, j + 1) - p(i, j);
let dery_b = p(i, j) - p(i, j - 1);
let dery_r = quarter * (p(i + 1, j + 1) - p(i + 1, j - 1) + p(i, j + 1) - p(i, j - 1));
let dery_l = quarter * (p(i, j + 1) - p(i, j - 1) + p(i - 1, j + 1) - p(i - 1, j - 1));
let (rx, ry) = rotate(c0, s0, derx_r, dery_r);
let (a_r, da_r) = self.face(rx, ry);
let (rx, ry) = rotate(c0, s0, derx_l, dery_l);
let (a_l, da_l) = self.face(rx, ry);
let (rx, ry) = rotate(c0, s0, derx_t, dery_t);
let (a_t, da_t) = self.face(rx, ry);
let (rx, ry) = rotate(c0, s0, derx_b, dery_b);
let (a_b, da_b) = self.face(rx, ry);
let j_r = a_r * (a_r * derx_r - da_r * dery_r);
let j_l = a_l * (a_l * derx_l - da_l * dery_l);
let j_t = a_t * (a_t * dery_t + da_t * derx_t);
let j_b = a_b * (a_b * dery_b + da_b * derx_b);
output.set([i, j], (j_r - j_l + j_t - j_b) * inv_h2);
});
}
}
#[inline(always)]
fn rotate<T: Real>(cos0: T, sin0: T, dx: T, dy: T) -> (T, T) {
(cos0 * dx + sin0 * dy, cos0 * dy - sin0 * dx)
}
impl Stencil<CartesianGrid<2>> for KarmaRappelFlux {
fn ghost_width(&self) -> u32 {
1
}
fn apply<T: Real>(
&self,
grid: &CartesianGrid<2>,
block: BlockId,
input: <CartesianGrid<2> as Grid>::View<'_, T>,
output: &mut <CartesianGrid<2> as Grid>::ViewMut<'_, T>,
) {
let [hx, hy] = grid.spacing(block);
let inv_h2 = T::from_f64(1.0 / (hx * hy));
let quarter = T::from_f64(0.25);
let p = |i: isize, j: isize| input.get([i, j]);
for_each_interior(input.interior(), |[i, j]| {
let derx_r = p(i + 1, j) - p(i, j);
let derx_l = p(i, j) - p(i - 1, j);
let derx_t = quarter * (p(i + 1, j + 1) - p(i - 1, j + 1) + p(i + 1, j) - p(i - 1, j));
let derx_b = quarter * (p(i + 1, j) - p(i - 1, j) + p(i + 1, j - 1) - p(i - 1, j - 1));
let dery_t = p(i, j + 1) - p(i, j);
let dery_b = p(i, j) - p(i, j - 1);
let dery_r = quarter * (p(i + 1, j + 1) - p(i + 1, j - 1) + p(i, j + 1) - p(i, j - 1));
let dery_l = quarter * (p(i, j + 1) - p(i, j - 1) + p(i - 1, j + 1) - p(i - 1, j - 1));
let (a_r, da_r) = self.face(derx_r, dery_r);
let (a_l, da_l) = self.face(derx_l, dery_l);
let (a_t, da_t) = self.face(derx_t, dery_t);
let (a_b, da_b) = self.face(derx_b, dery_b);
let j_r = a_r * (a_r * derx_r - da_r * dery_r);
let j_l = a_l * (a_l * derx_l - da_l * dery_l);
let j_t = a_t * (a_t * dery_t + da_t * derx_t);
let j_b = a_b * (a_b * dery_b + da_b * derx_b);
output.set([i, j], (j_r - j_l + j_t - j_b) * inv_h2);
});
}
}