use crate::CfdScalar;
use crate::tensor_bridge::{AcousticCoreInverse, dequantize, gradient, laplacian, quantize};
use alloc::format;
use alloc::vec;
use deep_causality_algebra::ConjugateScalar;
use deep_causality_physics::PhysicsError;
use deep_causality_tensor::{
CausalTensor, CausalTensorTrain, CausalTensorTrainOperator, TensorTrain, TensorTrainOperator,
Truncation,
};
pub struct AcousticImex1d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
l: usize,
dt: R,
advect: R,
kappa: R,
grad: CausalTensorTrainOperator<R>,
lap: CausalTensorTrainOperator<R>,
a0_inv: AcousticCoreInverse<R>,
c2: CausalTensorTrain<R>,
dc2: CausalTensorTrain<R>,
trunc: Truncation<R>,
}
impl<R> AcousticImex1d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
pub fn new(
l: usize,
dx: R,
advect: R,
kappa: R,
dt: R,
c2: &[R],
trunc: Truncation<R>,
) -> Result<Self, PhysicsError> {
let n = 1usize << l;
if c2.len() != n {
return Err(PhysicsError::DimensionMismatch(format!(
"c2 length {} does not match grid 2^{l}",
c2.len()
)));
}
let grad = gradient::<R>(l, dx, &trunc)?;
let lap = laplacian::<R>(l, dx, &trunc)?;
let n_r = R::from_usize(n)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_usize(n)".into()))?;
let cbar2 = c2.iter().fold(R::zero(), |a, &v| a + v) / n_r;
let s = dt * kappa * cbar2 / (dx * dx);
let a0_inv = AcousticCoreInverse::new_1d(l, s, trunc)?;
let c2_field = quantize(&CausalTensor::new(c2.to_vec(), vec![n])?, &trunc)?;
let dc2_dense: vec::Vec<R> = c2.iter().map(|&v| v - cbar2).collect();
let dc2 = quantize(&CausalTensor::new(dc2_dense, vec![n])?, &trunc)?;
Ok(Self {
l,
dt,
advect,
kappa,
grad,
lap,
a0_inv,
c2: c2_field,
dc2,
trunc,
})
}
pub fn step(&self, u: &CausalTensorTrain<R>) -> Result<CausalTensorTrain<R>, PhysicsError> {
let neg_a = R::zero() - self.advect;
let conv = self.grad.apply(u, &self.trunc)?.scale(neg_a * self.dt);
let lap_u = self.lap.apply(u, &self.trunc)?;
let rem = self
.dc2
.hadamard_rounded(&lap_u, &self.trunc)?
.scale(self.kappa * self.dt);
let rhs = u.add(&conv)?.add(&rem)?.round(&self.trunc)?;
self.a0_inv.apply(&rhs)
}
pub fn explicit_step(
&self,
u: &CausalTensorTrain<R>,
) -> Result<CausalTensorTrain<R>, PhysicsError> {
let neg_a = R::zero() - self.advect;
let conv = self.grad.apply(u, &self.trunc)?.scale(neg_a);
let lap_u = self.lap.apply(u, &self.trunc)?;
let diff = self
.c2
.hadamard_rounded(&lap_u, &self.trunc)?
.scale(self.kappa);
let rate = conv.add(&diff)?;
Ok(u.add(&rate.scale(self.dt))?.round(&self.trunc)?)
}
pub fn grid(&self) -> usize {
1usize << self.l
}
}
pub fn conservation_round<R>(
u: &CausalTensorTrain<R>,
target: R,
trunc: &Truncation<R>,
) -> Result<CausalTensorTrain<R>, PhysicsError>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
let rounded = u.round(trunc)?;
let dense = dequantize(&rounded)?;
let n = dense.as_slice().len();
let total_after = dense.as_slice().iter().fold(R::zero(), |a, &v| a + v);
let n_r = R::from_usize(n)
.ok_or_else(|| PhysicsError::NumericalInstability("from_usize(n)".into()))?;
let delta = (target - total_after) / n_r;
let offset = quantize(&CausalTensor::new(vec![delta; n], vec![n])?, trunc)?;
Ok(rounded.add(&offset)?.round(trunc)?)
}
pub fn positivity_floor<R>(
u: &CausalTensorTrain<R>,
floor: R,
trunc: &Truncation<R>,
) -> Result<CausalTensorTrain<R>, PhysicsError>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
let dense = dequantize(u)?;
let clamped: vec::Vec<R> = dense
.as_slice()
.iter()
.map(|&v| if v > floor { v } else { floor })
.collect();
let n = clamped.len();
quantize(&CausalTensor::new(clamped, vec![n])?, trunc)
}