use crate::CfdScalar;
use crate::solvers::qtt::QttIncompressible2d;
use crate::tensor_bridge::{dequantize_2d, quantize_2d};
use crate::traits::Marcher;
use alloc::format;
use deep_causality_algebra::ConjugateScalar;
use deep_causality_physics::PhysicsError;
use deep_causality_tensor::{CausalTensor, CausalTensorTrain, TensorTrain, Truncation};
pub struct QttImmersed2d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
inner: QttIncompressible2d<R>,
mask: CausalTensorTrain<R>,
ubx: R,
uby: R,
eta: R,
}
impl<R> QttImmersed2d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
lx: usize,
ly: usize,
dx: R,
dy: R,
dt: R,
nu: R,
mask: CausalTensorTrain<R>,
ubx: R,
uby: R,
eta: R,
trunc: Truncation<R>,
) -> Result<Self, PhysicsError> {
if !eta.is_finite() || eta <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(alloc::format!(
"QttImmersed2d: penalization parameter eta must be positive and finite, got {eta:?}"
)));
}
let two = R::one() + R::one();
let pen_limit = two * eta;
if dt > pen_limit {
return Err(PhysicsError::PhysicalInvariantBroken(alloc::format!(
"QttImmersed2d: dt {dt:?} exceeds the penalization explicit-stability limit 2·eta \
= {pen_limit:?} (eta {eta:?}); forward Euler on du/dt = −u/eta is unstable beyond it"
)));
}
let inner = QttIncompressible2d::new(lx, ly, dx, dy, dt, nu, trunc)?;
Ok(Self {
inner,
mask,
ubx,
uby,
eta,
})
}
pub fn mask(&self) -> &CausalTensorTrain<R> {
&self.mask
}
pub fn eta(&self) -> R {
self.eta
}
pub fn body_velocity(&self) -> (R, R) {
(self.ubx, self.uby)
}
pub fn modes(&self) -> (usize, usize) {
self.inner.modes()
}
pub fn projector(&self) -> &crate::tensor_bridge::QttProjector2d<R> {
self.inner.projector()
}
fn penalize(
&self,
a: &CausalTensorTrain<R>,
ub: R,
) -> Result<CausalTensorTrain<R>, PhysicsError> {
let t = self.inner.trunc();
let neg_inv_eta = (R::zero() - R::one()) / self.eta;
let deficit = if ub == R::zero() {
a.clone()
} else {
a.add_scalar(R::zero() - ub)?
};
Ok(self.mask.hadamard_rounded(&deficit, &t)?.scale(neg_inv_eta))
}
fn step(
&self,
u: &CausalTensorTrain<R>,
v: &CausalTensorTrain<R>,
) -> Result<(CausalTensorTrain<R>, CausalTensorTrain<R>), PhysicsError> {
let t = self.inner.trunc();
let dt = self.inner.dt();
let (ru, rv) = self.inner.rate_pair(u, v)?;
let ru = ru.add(&self.penalize(u, self.ubx)?)?.round(&t)?;
let rv = rv.add(&self.penalize(v, self.uby)?)?.round(&t)?;
let ustar = u.add(&ru.scale(dt))?.round(&t)?;
let vstar = v.add(&rv.scale(dt))?.round(&t)?;
self.inner.projector().project(&ustar, &vstar)
}
pub fn advance_scalar(
&self,
temp: &CausalTensorTrain<R>,
u: &CausalTensorTrain<R>,
v: &CausalTensorTrain<R>,
t_wall: R,
kappa: R,
) -> Result<CausalTensorTrain<R>, PhysicsError> {
let t = self.inner.trunc();
let dt = self.inner.dt();
let transport = self.inner.scalar_rate(temp, u, v, kappa)?;
let rate = transport.add(&self.penalize(temp, t_wall)?)?.round(&t)?;
Ok(temp.add(&rate.scale(dt))?.round(&t)?)
}
pub fn run(
&self,
u0: &CausalTensor<R>,
v0: &CausalTensor<R>,
steps: usize,
) -> Result<(CausalTensor<R>, CausalTensor<R>), PhysicsError> {
let (lx, ly) = self.inner.modes();
let want = [1usize << lx, 1usize << ly];
for f in [u0, v0] {
if f.shape() != want {
return Err(PhysicsError::DimensionMismatch(format!(
"field shape {:?} does not match the grid {want:?}",
f.shape()
)));
}
}
let t = self.inner.trunc();
let mut u = quantize_2d(u0, &t)?;
let mut v = quantize_2d(v0, &t)?;
for _ in 0..steps {
let (un, vn) = self.step(&u, &v)?;
u = un;
v = vn;
}
Ok((dequantize_2d(&u, lx, ly)?, dequantize_2d(&v, lx, ly)?))
}
}
impl<R> Marcher<R> for QttImmersed2d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
type State = (CausalTensorTrain<R>, CausalTensorTrain<R>);
type Ambient = ();
type Output = (CausalTensorTrain<R>, CausalTensorTrain<R>);
fn advance(
&self,
state: &Self::State,
_ambient: &Self::Ambient,
) -> Result<Self::Output, PhysicsError> {
self.step(&state.0, &state.1)
}
}