use crate::CfdScalar;
use crate::tensor_bridge::{dequantize, gradient, laplacian, quantize};
use alloc::format;
use alloc::vec::Vec;
use deep_causality_algebra::ConjugateScalar;
use deep_causality_physics::PhysicsError;
use deep_causality_tensor::{
CausalTensor, CausalTensorTrain, CausalTensorTrainOperator, TensorTrain, TensorTrainOperator,
Truncation,
};
pub fn ideal_gas_pressure<R: CfdScalar>(rho: R, mom: R, energy: R, gamma: R) -> R {
let half = R::from_f64(0.5).unwrap_or_else(R::one);
(gamma - R::one()) * (energy - half * mom * mom / rho)
}
pub struct CompressibleEuler1d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
l: usize,
dx: R,
gamma: R,
cfl: R,
grad: CausalTensorTrainOperator<R>,
lap: CausalTensorTrainOperator<R>,
trunc: Truncation<R>,
}
pub type EulerState<R> = (Vec<R>, Vec<R>, Vec<R>);
impl<R> CompressibleEuler1d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
pub fn new(
l: usize,
dx: R,
gamma: R,
cfl: R,
trunc: Truncation<R>,
) -> Result<Self, PhysicsError> {
if !dx.is_finite() || dx <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"CompressibleEuler1d: grid spacing dx must be finite and positive, got {dx:?}"
)));
}
if !gamma.is_finite() || gamma <= R::one() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"CompressibleEuler1d: ratio of specific heats gamma must be finite and > 1, got {gamma:?}"
)));
}
if !cfl.is_finite() || cfl <= R::zero() || cfl > R::one() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"CompressibleEuler1d: CFL number must be finite and in (0, 1], got {cfl:?}"
)));
}
let grad = gradient::<R>(l, dx, &trunc)?;
let lap = laplacian::<R>(l, dx, &trunc)?;
Ok(Self {
l,
dx,
gamma,
cfl,
grad,
lap,
trunc,
})
}
fn flux_and_speed(
&self,
rho: &[R],
mom: &[R],
energy: &[R],
) -> Result<(EulerState<R>, R), PhysicsError> {
let n = rho.len();
let mut f1 = Vec::with_capacity(n);
let mut f2 = Vec::with_capacity(n);
let mut f3 = Vec::with_capacity(n);
let mut s_max = R::zero();
for i in 0..n {
let r = rho[i];
if r <= R::zero() || !r.is_finite() {
return Err(PhysicsError::PhysicalInvariantBroken(
"compressible Euler: density must stay positive".into(),
));
}
let u = mom[i] / r;
let p = ideal_gas_pressure(r, mom[i], energy[i], self.gamma);
super::require_positive_pressure(p, i)?;
let c = (self.gamma * p / r).sqrt();
f1.push(mom[i]);
f2.push(mom[i] * u + p);
f3.push((energy[i] + p) * u);
let speed = u.abs() + c;
if speed > s_max {
s_max = speed;
}
}
Ok(((f1, f2, f3), s_max))
}
fn update_component(
&self,
u: &CausalTensorTrain<R>,
f: &CausalTensorTrain<R>,
dt: R,
diss_coeff: R,
) -> Result<CausalTensorTrain<R>, PhysicsError> {
let df = self.grad.apply(f, &self.trunc)?;
let lap_u = self.lap.apply(u, &self.trunc)?;
let neg = R::zero() - R::one();
let rate = df.scale(neg).add(&lap_u.scale(diss_coeff))?;
Ok(u.add(&rate.scale(dt))?.round(&self.trunc)?)
}
pub fn run(&self, state0: &EulerState<R>, t_final: R) -> Result<EulerState<R>, PhysicsError> {
let n = 1usize << self.l;
for buf in [&state0.0, &state0.1, &state0.2] {
if buf.len() != n {
return Err(PhysicsError::DimensionMismatch(format!(
"state length {} does not match grid 2^{}",
buf.len(),
self.l
)));
}
}
let to_tt = |v: &[R]| -> Result<CausalTensorTrain<R>, PhysicsError> {
quantize(&CausalTensor::new(v.to_vec(), alloc::vec![n])?, &self.trunc)
};
let mut rho = to_tt(&state0.0)?;
let mut mom = to_tt(&state0.1)?;
let mut energy = to_tt(&state0.2)?;
let half = R::from_f64(0.5).unwrap_or_else(R::one);
let mut t = R::zero();
let mut guard = 0usize;
let max_steps = 1_000_000usize;
while t < t_final && guard < max_steps {
guard += 1;
let rd = dequantize(&rho)?;
let md = dequantize(&mom)?;
let ed = dequantize(&energy)?;
let ((f1, f2, f3), s_max) =
self.flux_and_speed(rd.as_slice(), md.as_slice(), ed.as_slice())?;
if s_max <= R::zero() || !s_max.is_finite() {
return Err(PhysicsError::NumericalInstability(
"compressible Euler: non-physical wave speed".into(),
));
}
let mut dt = self.cfl * self.dx / s_max;
if t + dt > t_final {
dt = t_final - t;
}
let diss = half * s_max * self.dx;
let f1t = to_tt(&f1)?;
let f2t = to_tt(&f2)?;
let f3t = to_tt(&f3)?;
rho = self.update_component(&rho, &f1t, dt, diss)?;
mom = self.update_component(&mom, &f2t, dt, diss)?;
energy = self.update_component(&energy, &f3t, dt, diss)?;
t += dt;
}
Ok((
dequantize(&rho)?.as_slice().to_vec(),
dequantize(&mom)?.as_slice().to_vec(),
dequantize(&energy)?.as_slice().to_vec(),
))
}
pub fn gamma(&self) -> R {
self.gamma
}
}