use super::sample_grid_3d;
use crate::CfdScalar;
use crate::alias::physical_gradient_3_d::PhysicalGradient3d;
use crate::tensor_bridge::{gradient_x_3d, gradient_y_3d, gradient_z_3d, quantize_3d};
use crate::traits::MetricProvider3d;
use deep_causality_algebra::{ConjugateScalar, RealField};
use deep_causality_physics::PhysicsError;
use deep_causality_tensor::{
CausalTensorTrain, CausalTensorTrainOperator, TensorTrain, TensorTrainOperator, Truncation,
};
pub struct BodyFittedCoordinate3d<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
lx: usize,
ly: usize,
lz: usize,
r0: R,
dr: R,
theta0: R,
dtheta: R,
phi0: R,
dphi: R,
g_xi: CausalTensorTrainOperator<R>,
g_eta: CausalTensorTrainOperator<R>,
g_zeta: CausalTensorTrainOperator<R>,
dxi_dx: CausalTensorTrain<R>,
dxi_dy: CausalTensorTrain<R>,
deta_dx: CausalTensorTrain<R>,
deta_dy: CausalTensorTrain<R>,
deta_dz: CausalTensorTrain<R>,
dzeta_dx: CausalTensorTrain<R>,
dzeta_dy: CausalTensorTrain<R>,
dzeta_dz: CausalTensorTrain<R>,
jacobian: CausalTensorTrain<R>,
trunc: Truncation<R>,
}
impl<R> BodyFittedCoordinate3d<R>
where
R: CfdScalar + ConjugateScalar<Real = R> + RealField,
{
#[allow(clippy::too_many_arguments)]
pub fn new(
lx: usize,
ly: usize,
lz: usize,
r0: R,
dr: R,
theta0: R,
dtheta: R,
phi0: R,
dphi: R,
trunc: Truncation<R>,
) -> Result<Self, PhysicsError> {
if r0 <= R::zero() || dr <= R::zero() || dtheta <= R::zero() || dphi <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"BodyFittedCoordinate3d requires r0 > 0, dr > 0, dθ > 0, dφ > 0".into(),
));
}
if theta0 <= R::zero() || theta0 + dtheta >= R::pi() {
return Err(PhysicsError::PhysicalInvariantBroken(
"polar range must lie strictly inside (0, π) to avoid the spherical poles".into(),
));
}
let lift = |n: usize| {
R::from_usize(n)
.ok_or_else(|| PhysicsError::NumericalInstability("from_usize failed".into()))
};
let dxi = R::one() / lift(1usize << lx)?;
let deta = R::one() / lift(1usize << ly)?;
let dzeta = R::one() / lift(1usize << lz)?;
let g_xi = gradient_x_3d::<R>(lx, ly, lz, dxi, &trunc)?;
let g_eta = gradient_y_3d::<R>(lx, ly, lz, deta, &trunc)?;
let g_zeta = gradient_z_3d::<R>(lx, ly, lz, dzeta, &trunc)?;
let phi_at = |xi: R| phi0 + xi * dphi;
let theta_at = |eta: R| theta0 + eta * dtheta;
let radius_at = |zeta: R| r0 + zeta * dr;
let dxi_dx = encode(lx, ly, lz, &trunc, |xi, eta, zeta| {
-phi_at(xi).sin() / (radius_at(zeta) * theta_at(eta).sin() * dphi)
})?;
let dxi_dy = encode(lx, ly, lz, &trunc, |xi, eta, zeta| {
phi_at(xi).cos() / (radius_at(zeta) * theta_at(eta).sin() * dphi)
})?;
let deta_dx = encode(lx, ly, lz, &trunc, |xi, eta, zeta| {
theta_at(eta).cos() * phi_at(xi).cos() / (radius_at(zeta) * dtheta)
})?;
let deta_dy = encode(lx, ly, lz, &trunc, |xi, eta, zeta| {
theta_at(eta).cos() * phi_at(xi).sin() / (radius_at(zeta) * dtheta)
})?;
let deta_dz = encode(lx, ly, lz, &trunc, |_xi, eta, zeta| {
-theta_at(eta).sin() / (radius_at(zeta) * dtheta)
})?;
let dzeta_dx = encode(lx, ly, lz, &trunc, |xi, eta, _zeta| {
theta_at(eta).sin() * phi_at(xi).cos() / dr
})?;
let dzeta_dy = encode(lx, ly, lz, &trunc, |xi, eta, _zeta| {
theta_at(eta).sin() * phi_at(xi).sin() / dr
})?;
let dzeta_dz = encode(lx, ly, lz, &trunc, |_xi, eta, _zeta| {
theta_at(eta).cos() / dr
})?;
let jacobian = encode(lx, ly, lz, &trunc, |_xi, eta, zeta| {
let r = radius_at(zeta);
r * r * theta_at(eta).sin() * dr * dtheta * dphi
})?;
Ok(Self {
lx,
ly,
lz,
r0,
dr,
theta0,
dtheta,
phi0,
dphi,
g_xi,
g_eta,
g_zeta,
dxi_dx,
dxi_dy,
deta_dx,
deta_dy,
deta_dz,
dzeta_dx,
dzeta_dy,
dzeta_dz,
jacobian,
trunc,
})
}
pub fn r0(&self) -> R {
self.r0
}
pub fn dr(&self) -> R {
self.dr
}
pub fn theta0(&self) -> R {
self.theta0
}
pub fn dtheta(&self) -> R {
self.dtheta
}
pub fn phi0(&self) -> R {
self.phi0
}
pub fn dphi(&self) -> R {
self.dphi
}
pub fn position(&self, xi: R, eta: R, zeta: R) -> (R, R, R) {
let phi = self.phi0 + xi * self.dphi;
let theta = self.theta0 + eta * self.dtheta;
let r = self.r0 + zeta * self.dr;
(
r * theta.sin() * phi.cos(),
r * theta.sin() * phi.sin(),
r * theta.cos(),
)
}
}
impl<R> MetricProvider3d<R> for BodyFittedCoordinate3d<R>
where
R: CfdScalar + ConjugateScalar<Real = R> + RealField,
{
fn dims(&self) -> (usize, usize, usize) {
(self.lx, self.ly, self.lz)
}
fn sample<F>(&self, f: F) -> Result<CausalTensorTrain<R>, PhysicsError>
where
F: Fn(R, R, R) -> R,
{
quantize_3d(&sample_grid_3d(self.lx, self.ly, self.lz, f)?, &self.trunc)
}
fn physical_gradient(
&self,
u: &CausalTensorTrain<R>,
) -> Result<PhysicalGradient3d<R>, PhysicsError> {
let t = &self.trunc;
let du_dxi = self.g_xi.apply(u, t)?;
let du_deta = self.g_eta.apply(u, t)?;
let du_dzeta = self.g_zeta.apply(u, t)?;
let du_dx = self
.dxi_dx
.hadamard_rounded(&du_dxi, t)?
.add(&self.deta_dx.hadamard_rounded(&du_deta, t)?)?
.add(&self.dzeta_dx.hadamard_rounded(&du_dzeta, t)?)?
.round(t)?;
let du_dy = self
.dxi_dy
.hadamard_rounded(&du_dxi, t)?
.add(&self.deta_dy.hadamard_rounded(&du_deta, t)?)?
.add(&self.dzeta_dy.hadamard_rounded(&du_dzeta, t)?)?
.round(t)?;
let du_dz = self
.deta_dz
.hadamard_rounded(&du_deta, t)?
.add(&self.dzeta_dz.hadamard_rounded(&du_dzeta, t)?)?
.round(t)?;
Ok((du_dx, du_dy, du_dz))
}
fn jacobian(&self) -> &CausalTensorTrain<R> {
&self.jacobian
}
}
fn encode<R, F>(
lx: usize,
ly: usize,
lz: usize,
trunc: &Truncation<R>,
f: F,
) -> Result<CausalTensorTrain<R>, PhysicsError>
where
R: CfdScalar + ConjugateScalar<Real = R>,
F: Fn(R, R, R) -> R,
{
quantize_3d(&sample_grid_3d(lx, ly, lz, f)?, trunc)
}