use crate::PhysicsError;
use crate::Speed;
use crate::Temperature;
use crate::{Pressure, SpecificEnthalpy, Velocity3, VelocityGradient, ViscousStress};
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
pub fn speed_of_sound_ideal_gas_kernel<R>(
gamma: R,
r_specific: R,
temperature: &Temperature<R>,
) -> Result<Speed<R>, PhysicsError>
where
R: RealField,
{
let arg = gamma * r_specific * temperature.value();
if arg <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"speed_of_sound_ideal_gas_kernel: γ·R_s·T must be positive".into(),
));
}
Speed::new(arg.sqrt())
}
pub fn specific_enthalpy_kernel<R>(cp: R, temperature: &Temperature<R>) -> SpecificEnthalpy<R>
where
R: RealField,
{
SpecificEnthalpy::new_unchecked(cp * temperature.value())
}
pub fn total_enthalpy_kernel<R>(
h: &SpecificEnthalpy<R>,
u: &Velocity3<R>,
) -> Result<SpecificEnthalpy<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
let u_raw = u.value();
let speed_sq = u_raw[0] * u_raw[0] + u_raw[1] * u_raw[1] + u_raw[2] * u_raw[2];
Ok(SpecificEnthalpy::new_unchecked(h.value() + half * speed_sq))
}
pub fn total_pressure_isentropic_kernel<R>(
p: &Pressure<R>,
mach: R,
gamma: R,
) -> Result<Pressure<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let one = R::one();
if gamma <= one {
return Err(PhysicsError::PhysicalInvariantBroken(
"total_pressure_isentropic_kernel: γ must be > 1".into(),
));
}
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
let base = one + (gamma - one) * half * mach * mach;
if base <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"total_pressure_isentropic_kernel: base of exponent must be positive".into(),
));
}
let exponent = gamma / (gamma - one);
Pressure::new(p.value() * base.powf(exponent))
}
pub fn total_temperature_isentropic_kernel<R>(
t: &Temperature<R>,
mach: R,
gamma: R,
) -> Result<Temperature<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let one = R::one();
if gamma <= one {
return Err(PhysicsError::PhysicalInvariantBroken(
"total_temperature_isentropic_kernel: γ must be > 1".into(),
));
}
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
let factor = one + (gamma - one) * half * mach * mach;
Temperature::new(t.value() * factor)
}
fn isentropic_stagnation_factor<R>(mach: R, gamma: R, ctx: &str) -> Result<R, PhysicsError>
where
R: RealField + FromPrimitive,
{
if !(mach >= R::zero() && mach.is_finite()) {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"{ctx}: Mach number must be finite and >= 0"
)));
}
if !(gamma > R::one() && gamma.is_finite()) {
return Err(PhysicsError::PhysicalInvariantBroken(format!(
"{ctx}: γ must be finite and > 1"
)));
}
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
Ok(R::one() + (gamma - R::one()) * half * mach * mach)
}
pub fn isentropic_pressure_ratio_kernel<R>(mach: R, gamma: R) -> Result<R, PhysicsError>
where
R: RealField + FromPrimitive,
{
let factor = isentropic_stagnation_factor(mach, gamma, "isentropic_pressure_ratio_kernel")?;
Ok(factor.powf(gamma / (gamma - R::one())))
}
pub fn isentropic_temperature_ratio_kernel<R>(mach: R, gamma: R) -> Result<R, PhysicsError>
where
R: RealField + FromPrimitive,
{
isentropic_stagnation_factor(mach, gamma, "isentropic_temperature_ratio_kernel")
}
pub fn isentropic_density_ratio_kernel<R>(mach: R, gamma: R) -> Result<R, PhysicsError>
where
R: RealField + FromPrimitive,
{
let factor = isentropic_stagnation_factor(mach, gamma, "isentropic_density_ratio_kernel")?;
Ok(factor.powf(R::one() / (gamma - R::one())))
}
pub fn area_mach_ratio_kernel<R>(mach: R, gamma: R) -> Result<R, PhysicsError>
where
R: RealField + FromPrimitive,
{
if !(mach > R::zero() && mach.is_finite()) {
return Err(PhysicsError::PhysicalInvariantBroken(
"area_mach_ratio_kernel: Mach number must be finite and > 0".into(),
));
}
let factor = isentropic_stagnation_factor(mach, gamma, "area_mach_ratio_kernel")?;
let two = R::from_f64(2.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(2.0) failed".into()))?;
let gp1 = gamma + R::one();
let exponent = gp1 / (two * (gamma - R::one()));
Ok((two / gp1 * factor).powf(exponent) / mach)
}
pub fn entropy_production_rate_kernel<R>(
temperature: &Temperature<R>,
tau: &ViscousStress<R>,
grad_u: &VelocityGradient<R>,
thermal_conductivity: R,
grad_temperature: &[R; 3],
) -> Result<R, PhysicsError>
where
R: RealField,
{
let t = temperature.value();
if t <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"entropy_production_rate_kernel: temperature must be > 0".into(),
));
}
if thermal_conductivity < R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"entropy_production_rate_kernel: thermal_conductivity must be ≥ 0".into(),
));
}
let tv = tau.value();
let gv = grad_u.value();
let phi = tv[0][0] * gv[0][0]
+ tv[0][1] * gv[0][1]
+ tv[0][2] * gv[0][2]
+ tv[1][0] * gv[1][0]
+ tv[1][1] * gv[1][1]
+ tv[1][2] * gv[1][2]
+ tv[2][0] * gv[2][0]
+ tv[2][1] * gv[2][1]
+ tv[2][2] * gv[2][2];
let grad_t_norm_sq = grad_temperature[0] * grad_temperature[0]
+ grad_temperature[1] * grad_temperature[1]
+ grad_temperature[2] * grad_temperature[2];
Ok(phi / t + thermal_conductivity * grad_t_norm_sq / (t * t))
}