extern crate alloc;
use alloc::boxed::Box;
use crate::{
axis::{Axis, AxisLift},
chernoff::{ChernoffFunction, Growth},
error::SemiflowError,
float::{from_f64, half, SemiflowFloat},
grid2d::Grid2D,
grid_fn::GridFn1D,
grid_fn2d::GridFn2D,
scratch::ScratchPool,
strang2d::apply_strang2d_into,
};
mod coupling;
mod stencil;
pub(crate) use coupling::MixedDerivOp;
use coupling::{BetaCoupling, ScalarCoupling};
pub(crate) use stencil::cross_stencil_scalar;
const CFL_NUMER: f64 = 4.0;
pub struct NonSeparableMixedChernoff<X, Y, F: SemiflowFloat = f64, S = GridFn2D<F>>
where
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
{
pub x: AxisLift<X, F>,
pub y: AxisLift<Y, F>,
pub grid: Grid2D<F>,
pub(super) coupling: Box<dyn MixedDerivOp<F>>,
_state: core::marker::PhantomData<S>,
}
impl<X, Y, F: SemiflowFloat, S> Clone for NonSeparableMixedChernoff<X, Y, F, S>
where
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
{
fn clone(&self) -> Self {
Self {
x: self.x.clone(),
y: self.y.clone(),
grid: self.grid,
coupling: self.coupling.clone_box(),
_state: core::marker::PhantomData,
}
}
}
impl<X, Y, F: SemiflowFloat, S> core::fmt::Debug for NonSeparableMixedChernoff<X, Y, F, S>
where
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone + core::fmt::Debug,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone + core::fmt::Debug,
{
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("NonSeparableMixedChernoff")
.field("x", &self.x)
.field("y", &self.y)
.field("coupling", &self.coupling.debug_label())
.field("grid", &format_args!("Grid2D<{}>", self.grid.nx()))
.finish()
}
}
impl<X, Y, F: SemiflowFloat> NonSeparableMixedChernoff<X, Y, F, GridFn2D<F>>
where
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
{
pub fn new(
x_inner: X,
y_inner: Y,
coupling_fn: fn(F, F) -> F,
norm_bound: f64,
grid: Grid2D<F>,
) -> Result<Self, SemiflowError> {
Self::with_scalar_c(x_inner, y_inner, coupling_fn, norm_bound, grid)
}
pub fn with_scalar_c(
x_inner: X,
y_inner: Y,
c: fn(F, F) -> F,
c_norm_bound: f64,
grid: Grid2D<F>,
) -> Result<Self, SemiflowError> {
if !c_norm_bound.is_finite() || c_norm_bound < 0.0 {
return Err(SemiflowError::DomainViolation {
what: "c_norm_bound must be finite and >= 0",
value: c_norm_bound,
});
}
let coupling = Box::new(ScalarCoupling {
c,
c_norm_bound,
is_zero: c_norm_bound == 0.0,
});
Ok(Self {
x: AxisLift::new(x_inner, Axis::X),
y: AxisLift::new(y_inner, Axis::Y),
grid,
coupling,
_state: core::marker::PhantomData,
})
}
pub fn with_beta(
x_inner: X,
y_inner: Y,
beta: fn(F, F) -> F,
beta_norm_bound: f64,
grid: Grid2D<F>,
) -> Result<Self, SemiflowError> {
if !beta_norm_bound.is_finite() || beta_norm_bound < 0.0 {
return Err(SemiflowError::DomainViolation {
what: "beta_norm_bound must be finite and >= 0",
value: beta_norm_bound,
});
}
let coupling = Box::new(BetaCoupling {
beta,
beta_norm_bound,
is_zero: beta_norm_bound == 0.0,
});
Ok(Self {
x: AxisLift::new(x_inner, Axis::X),
y: AxisLift::new(y_inner, Axis::Y),
grid,
coupling,
_state: core::marker::PhantomData,
})
}
pub(crate) fn with_coupling(
x_inner: X,
y_inner: Y,
grid: Grid2D<F>,
coupling: Box<dyn MixedDerivOp<F>>,
) -> Self {
Self {
x: AxisLift::new(x_inner, Axis::X),
y: AxisLift::new(y_inner, Axis::Y),
grid,
coupling,
_state: core::marker::PhantomData,
}
}
}
#[cfg(not(feature = "parallel"))]
impl<X, Y, F: SemiflowFloat> ChernoffFunction<F> for NonSeparableMixedChernoff<X, Y, F, GridFn2D<F>>
where
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
{
type S = GridFn2D<F>;
fn order(&self) -> u32 {
2
}
fn growth(&self) -> Growth<F> {
compute_growth(&self.x, &self.y)
}
fn apply_into(
&self,
tau: F,
src: &GridFn2D<F>,
dst: &mut GridFn2D<F>,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
if self.coupling.is_zero() {
return apply_strang2d_into(tau, src, dst, &self.x, &self.y, scratch);
}
validate_cfl(tau, self.coupling.norm_bound(), &self.grid)?;
*dst = apply_five_leg(&self.x, &self.y, &*self.coupling, tau, src, &self.grid)?;
Ok(())
}
}
#[cfg(feature = "parallel")]
impl<X, Y> ChernoffFunction<f64> for NonSeparableMixedChernoff<X, Y, f64, GridFn2D<f64>>
where
X: ChernoffFunction<f64, S = GridFn1D<f64>> + Clone + Send + Sync,
Y: ChernoffFunction<f64, S = GridFn1D<f64>> + Clone + Send + Sync,
{
type S = GridFn2D<f64>;
fn order(&self) -> u32 {
2
}
fn growth(&self) -> Growth<f64> {
compute_growth(&self.x, &self.y)
}
fn apply_into(
&self,
tau: f64,
src: &GridFn2D<f64>,
dst: &mut GridFn2D<f64>,
scratch: &mut ScratchPool<f64>,
) -> Result<(), SemiflowError> {
if self.coupling.is_zero() {
return apply_strang2d_into(tau, src, dst, &self.x, &self.y, scratch);
}
validate_cfl(tau, self.coupling.norm_bound(), &self.grid)?;
*dst = apply_five_leg(&self.x, &self.y, &*self.coupling, tau, src, &self.grid)?;
Ok(())
}
}
pub type NonSeparable2DChernoff<X, Y, F = f64> = NonSeparableMixedChernoff<X, Y, F, GridFn2D<F>>;
pub type NonSeparable2DAnisotropicChernoff<X, Y, F = f64> =
NonSeparableMixedChernoff<X, Y, F, GridFn2D<F>>;
fn validate_cfl<F: SemiflowFloat>(
tau: F,
norm_bound: f64,
grid: &Grid2D<F>,
) -> Result<(), SemiflowError> {
let tau_f64 = tau.to_f64().unwrap_or(f64::NAN);
if !tau_f64.is_finite() || tau_f64 < 0.0 {
return Err(SemiflowError::DomainViolation {
what: "tau must be finite and >= 0",
value: tau_f64,
});
}
let dx_dy = grid.x.dx().to_f64().unwrap_or(f64::NAN) * grid.y.dx().to_f64().unwrap_or(f64::NAN);
if CFL_NUMER * tau_f64 * norm_bound >= dx_dy {
return Err(SemiflowError::CflViolated {
tau: tau_f64,
dx_squared: dx_dy,
a_norm_bound: norm_bound,
});
}
Ok(())
}
fn compute_growth<X, Y, F: SemiflowFloat>(x: &AxisLift<X, F>, y: &AxisLift<Y, F>) -> Growth<F>
where
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
{
let gx = x.growth();
let gy = y.growth();
let m_mixed = from_f64::<F>(1.0 + 0.25 + 0.5 * 0.25 * 0.25);
Growth {
multiplier: gx.multiplier * gy.multiplier * m_mixed * gy.multiplier * gx.multiplier,
omega: gx.omega + gy.omega,
}
}
#[allow(clippy::too_many_lines)]
fn apply_five_leg<X, Y, F: SemiflowFloat>(
x: &AxisLift<X, F>,
y: &AxisLift<Y, F>,
coupling: &dyn MixedDerivOp<F>,
tau: F,
f: &GridFn2D<F>,
grid: &Grid2D<F>,
) -> Result<GridFn2D<F>, SemiflowError>
where
X: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
Y: ChernoffFunction<F, S = GridFn1D<F>> + Clone,
{
use crate::chernoff::ApplyChernoffExt;
let half_tau = half::<F>() * tau;
let f1 = x.apply_chernoff(half_tau, f)?;
let f2 = y.apply_chernoff(half_tau, &f1)?;
let f3 = phi_mixed_step(coupling, tau, &f2, grid);
let f4 = y.apply_chernoff(half_tau, &f3)?;
x.apply_chernoff(half_tau, &f4)
}
fn phi_mixed_step<F: SemiflowFloat>(
coupling: &dyn MixedDerivOp<F>,
tau: F,
f: &GridFn2D<F>,
grid: &Grid2D<F>,
) -> GridFn2D<F> {
let mut mf = f.clone();
coupling.apply_mixed_into(f, &mut mf, grid);
let mut m2f = f.clone();
coupling.apply_mixed_into(&mf, &mut m2f, grid);
let nx = grid.nx();
let ny = grid.ny();
let half_v = half::<F>();
let mut out = f.clone();
for j in 0..ny {
for i in 0..nx {
let k = grid.idx(i, j);
out.values[k] = out.values[k] + tau * mf.values[k] + half_v * tau * tau * m2f.values[k];
}
}
out
}
#[cfg(test)]
#[path = "tests.rs"]
mod tests;