mod blended;
mod body_fitted_3d;
mod cartesian;
mod cartesian_3d;
pub use crate::traits::MetricProvider;
pub use crate::traits::MetricProvider3d;
pub use blended::{BlendedMap, BlendedMapConfig, BlendedMapConfigBuilder};
pub use body_fitted_3d::BodyFittedCoordinate3d;
pub use cartesian::CartesianIdentity;
pub use cartesian_3d::CartesianIdentity3d;
use crate::CfdScalar;
use crate::tensor_bridge::{gradient_x, gradient_y, quantize_2d};
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 BodyFittedCoordinate<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
lx: usize,
ly: usize,
r0: R,
dr: R,
theta0: R,
dtheta: R,
g_xi: CausalTensorTrainOperator<R>,
g_eta: CausalTensorTrainOperator<R>,
dxi_dx: CausalTensorTrain<R>,
deta_dx: CausalTensorTrain<R>,
dxi_dy: CausalTensorTrain<R>,
deta_dy: CausalTensorTrain<R>,
jacobian: CausalTensorTrain<R>,
trunc: Truncation<R>,
}
pub(crate) fn sample_grid<R, F>(lx: usize, ly: usize, f: F) -> Result<CausalTensor<R>, PhysicsError>
where
R: CfdScalar,
F: Fn(R, R) -> R,
{
let nx = 1usize << lx;
let ny = 1usize << ly;
let nxr = R::from_usize(nx)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_usize(nx) failed".into()))?;
let nyr = R::from_usize(ny)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_usize(ny) failed".into()))?;
let mut data = vec![R::zero(); nx * ny];
for i in 0..nx {
let xi = R::from_usize(i)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_usize(i) failed".into()))?
/ nxr;
for j in 0..ny {
let eta = R::from_usize(j)
.ok_or_else(|| PhysicsError::NumericalInstability("from_usize(j) failed".into()))?
/ nyr;
data[i * ny + j] = f(xi, eta);
}
}
Ok(CausalTensor::new(data, vec![nx, ny])?)
}
pub(crate) fn sample_grid_3d<R, F>(
lx: usize,
ly: usize,
lz: usize,
f: F,
) -> Result<CausalTensor<R>, PhysicsError>
where
R: CfdScalar,
F: Fn(R, R, R) -> R,
{
let (nx, ny, nz) = (1usize << lx, 1usize << ly, 1usize << lz);
let lift = |n: usize, what: &str| {
R::from_usize(n).ok_or_else(|| {
PhysicsError::NumericalInstability(alloc::format!("R::from_usize({what}) failed"))
})
};
let (nxr, nyr, nzr) = (lift(nx, "nx")?, lift(ny, "ny")?, lift(nz, "nz")?);
let mut data = vec![R::zero(); nx * ny * nz];
for i in 0..nx {
let xi = lift(i, "i")? / nxr;
for j in 0..ny {
let eta = lift(j, "j")? / nyr;
for k in 0..nz {
let zeta = lift(k, "k")? / nzr;
data[(i * ny + j) * nz + k] = f(xi, eta, zeta);
}
}
}
Ok(CausalTensor::new(data, vec![nx, ny, nz])?)
}
impl<R> BodyFittedCoordinate<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
pub fn new(
lx: usize,
ly: usize,
r0: R,
dr: R,
theta0: R,
dtheta: R,
trunc: Truncation<R>,
) -> Result<Self, PhysicsError> {
if r0 <= R::zero() || dr <= R::zero() || dtheta <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"BodyFittedCoordinate requires r0 > 0, dr > 0, dtheta > 0".into(),
));
}
let nx = 1usize << lx;
let ny = 1usize << ly;
let dxi = R::one()
/ R::from_usize(nx).ok_or_else(|| {
PhysicsError::NumericalInstability("from_usize(nx) failed".into())
})?;
let deta = R::one()
/ R::from_usize(ny).ok_or_else(|| {
PhysicsError::NumericalInstability("from_usize(ny) failed".into())
})?;
let g_xi = gradient_x::<R>(lx, ly, dxi, &trunc)?;
let g_eta = gradient_y::<R>(lx, ly, deta, &trunc)?;
let theta_at = |xi: R| theta0 + xi * dtheta;
let radius_at = |eta: R| r0 + eta * dr;
let dxi_dx = quantize_2d(
&sample_grid(lx, ly, |xi, eta| {
-theta_at(xi).sin() / (radius_at(eta) * dtheta)
})?,
&trunc,
)?;
let deta_dx = quantize_2d(
&sample_grid(lx, ly, |xi, _eta| theta_at(xi).cos() / dr)?,
&trunc,
)?;
let dxi_dy = quantize_2d(
&sample_grid(lx, ly, |xi, eta| {
theta_at(xi).cos() / (radius_at(eta) * dtheta)
})?,
&trunc,
)?;
let deta_dy = quantize_2d(
&sample_grid(lx, ly, |xi, _eta| theta_at(xi).sin() / dr)?,
&trunc,
)?;
let jacobian = quantize_2d(
&sample_grid(lx, ly, |_xi, eta| radius_at(eta) * dtheta * dr)?,
&trunc,
)?;
Ok(Self {
lx,
ly,
r0,
dr,
theta0,
dtheta,
g_xi,
g_eta,
dxi_dx,
deta_dx,
dxi_dy,
deta_dy,
jacobian,
trunc,
})
}
fn theta_of(&self, xi: R) -> R {
self.theta0 + xi * self.dtheta
}
pub fn position(&self, xi: R, eta: R) -> (R, R) {
let theta = self.theta_of(xi);
let r = self.r0 + eta * self.dr;
(r * theta.cos(), r * theta.sin())
}
pub fn jacobian(&self) -> &CausalTensorTrain<R> {
&self.jacobian
}
pub fn sample<F>(&self, f: F) -> Result<CausalTensorTrain<R>, PhysicsError>
where
F: Fn(R, R) -> R,
{
quantize_2d(&sample_grid(self.lx, self.ly, f)?, &self.trunc)
}
pub fn physical_gradient(
&self,
u: &CausalTensorTrain<R>,
) -> Result<(CausalTensorTrain<R>, CausalTensorTrain<R>), PhysicsError> {
let du_dxi = self.g_xi.apply(u, &self.trunc)?;
let du_deta = self.g_eta.apply(u, &self.trunc)?;
let du_dx = self
.dxi_dx
.hadamard_rounded(&du_dxi, &self.trunc)?
.add(&self.deta_dx.hadamard_rounded(&du_deta, &self.trunc)?)?
.round(&self.trunc)?;
let du_dy = self
.dxi_dy
.hadamard_rounded(&du_dxi, &self.trunc)?
.add(&self.deta_dy.hadamard_rounded(&du_deta, &self.trunc)?)?
.round(&self.trunc)?;
Ok((du_dx, du_dy))
}
}
impl<R> MetricProvider<R> for BodyFittedCoordinate<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
fn dims(&self) -> (usize, usize) {
(self.lx, self.ly)
}
fn sample<F>(&self, f: F) -> Result<CausalTensorTrain<R>, PhysicsError>
where
F: Fn(R, R) -> R,
{
BodyFittedCoordinate::sample(self, f)
}
fn physical_gradient(
&self,
u: &CausalTensorTrain<R>,
) -> Result<(CausalTensorTrain<R>, CausalTensorTrain<R>), PhysicsError> {
BodyFittedCoordinate::physical_gradient(self, u)
}
fn jacobian(&self) -> &CausalTensorTrain<R> {
BodyFittedCoordinate::jacobian(self)
}
}