use super::codec::{dequantize_2d, quantize_2d};
use crate::CfdScalar;
use alloc::vec;
use deep_causality_algebra::ConjugateScalar;
use deep_causality_physics::PhysicsError;
use deep_causality_tensor::{CausalTensor, CausalTensorTrain, Truncation};
const MASK_GROSS_EXCURSION: f64 = 0.05;
fn clamp_mask_to_unit_interval<R>(
mask: CausalTensorTrain<R>,
lx: usize,
ly: usize,
trunc: &Truncation<R>,
) -> Result<CausalTensorTrain<R>, PhysicsError>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
let (nx, ny) = (1usize << lx, 1usize << ly);
let dense = dequantize_2d(&mask, lx, ly)?;
let one = R::one();
let gross = R::from_f64(MASK_GROSS_EXCURSION).unwrap_or_else(R::one);
let mut worst_below = R::zero();
let mut worst_above = R::zero();
let mut clamped = vec::Vec::with_capacity(nx * ny);
for &v in dense.as_slice() {
if v < R::zero() && (R::zero() - v) > worst_below {
worst_below = R::zero() - v;
}
if v > one && (v - one) > worst_above {
worst_above = v - one;
}
clamped.push(if v < R::zero() {
R::zero()
} else if v > one {
one
} else {
v
});
}
if worst_below > gross || worst_above > gross {
return Err(PhysicsError::PhysicalInvariantBroken(alloc::format!(
"body mask leaves [0, 1] by more than {MASK_GROSS_EXCURSION} of the range (min excess \
below 0 = {worst_below:?}, max excess above 1 = {worst_above:?}) — this is a wrong mask, \
not tensor-train rounding noise"
)));
}
let field = CausalTensor::new(clamped, vec![nx, ny])?;
quantize_2d(&field, trunc)
}
pub fn mask_from_fn<R, F>(
lx: usize,
ly: usize,
dx: R,
dy: R,
f: F,
trunc: &Truncation<R>,
) -> Result<CausalTensorTrain<R>, PhysicsError>
where
R: CfdScalar + ConjugateScalar<Real = R>,
F: Fn(R, R) -> R,
{
let (nx, ny) = (1usize << lx, 1usize << ly);
let mut data = vec![R::zero(); nx * ny];
for i in 0..nx {
let x = R::from_usize(i).expect("a lattice index lifts into every real field") * dx;
for j in 0..ny {
let y = R::from_usize(j).expect("a lattice index lifts into every real field") * dy;
data[i * ny + j] = f(x, y);
}
}
let field = CausalTensor::new(data, vec![nx, ny])?;
let mask = quantize_2d(&field, trunc)?;
clamp_mask_to_unit_interval(mask, lx, ly, trunc)
}
#[allow(clippy::too_many_arguments)]
pub fn plume_mask_2d<R>(
lx: usize,
ly: usize,
dx: R,
dy: R,
cx: R,
cy: R,
half_length: R,
max_radius: R,
smoothing: R,
trunc: &Truncation<R>,
) -> Result<CausalTensorTrain<R>, PhysicsError>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
let half = R::from_f64(0.5).expect("0.5 lifts into every real field");
let scale = if half_length < max_radius {
half_length
} else {
max_radius
};
mask_from_fn(
lx,
ly,
dx,
dy,
|x, y| {
let ex = (x - cx) / half_length;
let ey = (y - cy) / max_radius;
let dist = ((ex * ex + ey * ey).sqrt() - R::one()) * scale;
half * (R::one() - (dist / smoothing).tanh())
},
trunc,
)
}
#[allow(clippy::too_many_arguments)]
pub fn body_mask_2d<R>(
lx: usize,
ly: usize,
dx: R,
dy: R,
cx: R,
cy: R,
radius: R,
smoothing: R,
trunc: &Truncation<R>,
) -> Result<CausalTensorTrain<R>, PhysicsError>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
let half = R::from_f64(0.5).expect("0.5 lifts into every real field");
mask_from_fn(
lx,
ly,
dx,
dy,
|x, y| {
let (ex, ey) = (x - cx, y - cy);
let dist = (ex * ex + ey * ey).sqrt() - radius;
half * (R::one() - (dist / smoothing).tanh())
},
trunc,
)
}