use alloc::{vec, vec::Vec};
use crate::{
boundary::BoundaryPolicy,
chernoff::{ChernoffFunction, Growth},
conservative::thomas_solve,
conservative_assemble::{assemble_conservative_csr_1d, build_faces},
error::SemiflowError,
float::SemiflowFloat,
graph_krylov::KrylovPath,
grid::Grid1D,
grid_fn::GridFn1D,
mass_operator::mass_lumped_evolve,
scratch::ScratchPool,
symmetric_operator::SymmetricOperator,
};
#[derive(Debug, Clone, Copy)]
pub struct Layer<F: SemiflowFloat = f64> {
pub thickness: F,
pub k: F,
pub rho_c: F,
}
#[derive(Debug, Clone)]
pub struct MultilayerStack<F: SemiflowFloat = f64> {
pub grid: Grid1D<F>,
pub k_nodes: Vec<F>,
pub rho_c_nodes: Vec<F>,
}
impl<F: SemiflowFloat> MultilayerStack<F> {
pub fn from_layers(layers: &[Layer<F>], target_dx: F) -> Result<Self, SemiflowError> {
if layers.is_empty() {
return Err(SemiflowError::DomainViolation {
what: "MultilayerStack::from_layers: empty layers slice",
value: 0.0,
});
}
let mut n_cells: Vec<usize> = Vec::with_capacity(layers.len());
for lay in layers {
let c = (lay.thickness / target_dx)
.round()
.to_f64()
.unwrap_or(1.0_f64)
.max(1.0_f64);
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
n_cells.push(c as usize);
}
let mut cumul = vec![0_usize];
for &c in &n_cells {
cumul.push(cumul.last().copied().unwrap_or(0) + c);
}
let total_cells = cumul.last().copied().unwrap_or(1);
let n = total_cells + 1;
let total_len = layers.iter().fold(F::zero(), |a, l| a + l.thickness);
let grid = Grid1D::new_generic(F::zero(), total_len, n)?;
let mut k_nodes = Vec::with_capacity(n);
let mut rho_c_nodes = Vec::with_capacity(n);
for i in 0..n {
let idx = cumul
.partition_point(|&c| c < i)
.saturating_sub(1)
.min(layers.len() - 1);
k_nodes.push(layers[idx].k);
rho_c_nodes.push(layers[idx].rho_c);
}
Ok(Self {
grid,
k_nodes,
rho_c_nodes,
})
}
pub fn to_stiffness_and_mass(
&self,
boundary: BoundaryPolicy<F>,
) -> Result<(SymmetricOperator<F>, Vec<F>), SemiflowError> {
let a = assemble_conservative_csr_1d(self.grid, &self.k_nodes, None, boundary)?;
Ok((a, self.rho_c_nodes.clone()))
}
}
#[allow(clippy::too_many_arguments)]
pub fn multilayer_evolve<F: SemiflowFloat>(
stack: &MultilayerStack<F>,
boundary: BoundaryPolicy<F>,
tau: F,
u0: &[F],
out: &mut [F],
path: KrylovPath,
tol: F,
scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
let (a, masses) = stack.to_stiffness_and_mass(boundary)?;
mass_lumped_evolve(&a, &masses, tau, u0, out, path, tol, scratch)
}
#[derive(Debug, Clone)]
pub struct MassWeightedConservativeChernoff<F: SemiflowFloat = f64> {
faces: Vec<F>, rho_c: Vec<F>, dx: F,
grid: Grid1D<F>,
}
impl<F: SemiflowFloat> MassWeightedConservativeChernoff<F> {
pub fn from_stack(stack: &MultilayerStack<F>) -> Result<Self, SemiflowError> {
let dx = stack.grid.dx();
let faces = build_faces(&stack.k_nodes, dx, None)?;
Ok(Self {
faces,
rho_c: stack.rho_c_nodes.clone(),
dx,
grid: stack.grid,
})
}
}
impl<F: SemiflowFloat> ChernoffFunction<F> for MassWeightedConservativeChernoff<F> {
type S = GridFn1D<F>;
#[inline]
fn order(&self) -> u32 {
2
}
#[inline]
fn growth(&self) -> Growth<F> {
Growth::contraction()
}
#[allow(clippy::similar_names)]
fn apply_into(
&self,
tau: F,
src: &GridFn1D<F>,
dst: &mut GridFn1D<F>,
_scratch: &mut ScratchPool<F>,
) -> Result<(), SemiflowError> {
if !tau.is_finite() || tau < F::zero() {
return Err(SemiflowError::DomainViolation {
what: "MassWeightedConservativeChernoff: tau must be finite and >= 0",
value: tau.to_f64().unwrap_or(f64::NAN),
});
}
let ht = tau / (F::one() + F::one());
let n = self.rho_c.len();
let dx = self.dx;
let mut sub_d = vec![F::zero(); n];
let mut diag = vec![F::zero(); n];
let mut sup_d = vec![F::zero(); n];
let mut rhs = vec![F::zero(); n];
for i in 0..n {
let t_l = if i > 0 {
self.faces[i - 1] / dx
} else {
F::zero()
};
let t_r = if i + 1 < n {
self.faces[i] / dx
} else {
F::zero()
};
let s_l = if i > 0 { src.values[i - 1] } else { F::zero() };
let s_r = if i + 1 < n {
src.values[i + 1]
} else {
F::zero()
};
sub_d[i] = -ht * t_l;
diag[i] = self.rho_c[i] + ht * (t_l + t_r);
sup_d[i] = -ht * t_r;
rhs[i] = (self.rho_c[i] - ht * (t_l + t_r)) * src.values[i]
+ ht * t_l * s_l
+ ht * t_r * s_r;
}
dst.values.resize(n, F::zero());
thomas_solve(n, &sub_d, &diag, &sup_d, &rhs, &mut dst.values)?;
dst.grid = self.grid;
Ok(())
}
}