use alloc::format;
use alloc::vec::Vec;
use deep_causality_tensor::CausalTensor;
use deep_causality_topology::{
CellClass, ChainComplex, CutCellRegistry, LatticeCell, LatticeComplex, Manifold,
};
use crate::solvers::dec::DecNsScalar;
use deep_causality_physics::PhysicsError;
#[derive(Debug)]
pub struct DecScalarRate<'m, const D: usize, R: DecNsScalar> {
manifold: &'m Manifold<LatticeComplex<D, R>, R>,
kappa: R,
n0: usize,
pinned: Vec<usize>,
t_wall: R,
}
impl<'m, const D: usize, R: DecNsScalar> DecScalarRate<'m, D, R> {
pub fn new(
manifold: &'m Manifold<LatticeComplex<D, R>, R>,
kappa: R,
) -> Result<Self, PhysicsError> {
if manifold.metric().is_none() {
return Err(PhysicsError::TopologyError(
"DecScalarRate requires a metric-bearing manifold (Hodge star); construct it with \
CubicalReggeGeometry"
.into(),
));
}
if !kappa.is_finite() {
return Err(PhysicsError::NumericalInstability(
"DecScalarRate: diffusivity must be finite".into(),
));
}
if kappa < R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"DecScalarRate: diffusivity must be non-negative, got {kappa}"
)));
}
Ok(Self {
manifold,
kappa,
n0: manifold.complex().num_cells(0),
pinned: Vec::new(),
t_wall: R::zero(),
})
}
pub fn with_isothermal_body(
mut self,
registry: &CutCellRegistry<D, R>,
t_wall: R,
) -> Result<Self, PhysicsError> {
if !t_wall.is_finite() {
return Err(PhysicsError::NumericalInstability(
"DecScalarRate: wall temperature must be finite".into(),
));
}
let complex = self.manifold.complex();
let shape = complex.shape();
let periodic = complex.periodic();
let mut vertex_index = alloc::collections::BTreeMap::<[usize; D], usize>::new();
for (idx, vertex) in complex.iter_cells(0).enumerate() {
vertex_index.insert(*vertex.position(), idx);
}
let cells: Vec<LatticeCell<D>> = complex.iter_cells(D).collect();
let mut pinned: Vec<usize> = Vec::new();
for (&cell_id, cut) in registry.iter() {
if cut.class() == CellClass::Fluid {
continue;
}
let Some(cell) = cells.get(cell_id) else {
continue;
};
let base = *cell.position();
for corner in 0..(1usize << D) {
let mut pos = base;
let mut inside = true;
for (axis, p) in pos.iter_mut().enumerate() {
let bit = (corner >> axis) & 1;
*p += bit;
if *p >= shape[axis] {
if periodic[axis] {
*p %= shape[axis];
} else {
inside = false;
}
}
}
if inside && let Some(&v) = vertex_index.get(&pos) {
pinned.push(v);
}
}
}
pinned.sort_unstable();
pinned.dedup();
self.pinned = pinned;
self.t_wall = t_wall;
Ok(self)
}
pub fn wall_temperature(&self) -> R {
self.t_wall
}
pub fn pinned_vertices(&self) -> &[usize] {
&self.pinned
}
pub fn eval(
&self,
scalar: &CausalTensor<R>,
velocity: &CausalTensor<R>,
) -> Result<CausalTensor<R>, PhysicsError> {
if scalar.len() != self.n0 {
return Err(PhysicsError::DimensionMismatch(format!(
"DecScalarRate: expected {} scalar values (one per vertex), got {}",
self.n0,
scalar.len()
)));
}
let n1 = self.manifold.complex().num_cells(1);
if velocity.len() != n1 {
return Err(PhysicsError::DimensionMismatch(format!(
"DecScalarRate: expected {} velocity edge values, got {}",
n1,
velocity.len()
)));
}
let grad = self.manifold.exterior_derivative_of(scalar.as_slice(), 0);
let advect = self
.manifold
.interior_product(velocity, &grad, 1)
.map_err(|e| PhysicsError::TopologyError(format!("interior_product(u, dT): {e}")))?;
let lap = self.manifold.laplacian_of(scalar.as_slice(), 0);
let mut out = alloc::vec![R::zero(); self.n0];
for (i, o) in out.iter_mut().enumerate() {
*o = R::zero() - advect.as_slice()[i] - self.kappa * lap.as_slice()[i];
}
for &v in &self.pinned {
if let Some(slot) = out.get_mut(v) {
*slot = R::zero();
}
}
CausalTensor::new(out, alloc::vec![self.n0]).map_err(|e| {
PhysicsError::DimensionMismatch(format!("DecScalarRate: rate assembly: {e:?}"))
})
}
pub fn apply_wall(&self, values: &mut [R]) {
for &v in &self.pinned {
if let Some(slot) = values.get_mut(v) {
*slot = self.t_wall;
}
}
}
pub fn step(
&self,
scalar: &CausalTensor<R>,
velocity: &CausalTensor<R>,
dt: R,
) -> Result<CausalTensor<R>, PhysicsError> {
if !dt.is_finite() || dt <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"DecScalarRate: dt must be finite and positive, got {dt}"
)));
}
let rate = self.eval(scalar, velocity)?;
let mut next = scalar.as_slice().to_vec();
for (n, r) in next.iter_mut().zip(rate.as_slice()) {
*n += dt * *r;
}
self.apply_wall(&mut next);
CausalTensor::new(next, alloc::vec![self.n0]).map_err(|e| {
PhysicsError::DimensionMismatch(format!("DecScalarRate: step assembly: {e:?}"))
})
}
}