use crate::CfdScalar;
use crate::tensor_bridge::quantize;
use alloc::vec;
use deep_causality_algebra::ConjugateScalar;
use deep_causality_physics::{
AVOGADRO_CONSTANT, ElectronDensity, PARK_NO_IONIZATION_ACTIVATION_TEMP,
PARK_NO_IONIZATION_EXPONENT, PARK_NO_IONIZATION_PREFACTOR, PhysicsError, Temperature,
VibrationalTemperature, arrhenius_rate_kernel, electron_density_kernel,
park2t_ionization_surrogate_kernel, plasma_frequency_kernel,
rankine_hugoniot_temperature_kernel, vibrational_relaxation_kernel,
};
use deep_causality_tensor::{CausalTensor, Truncation};
#[derive(Clone, Copy, Debug)]
pub struct PostShockState<R> {
pub t2: R,
pub n_tot2: R,
pub rho_ratio: R,
pub u_ratio: R,
pub p_ratio: R,
}
#[derive(Clone, Copy, Debug)]
pub struct StagnationOutcome<R> {
pub electron_density: R,
pub plasma_frequency: R,
pub ionization_fraction: R,
pub blackout: bool,
}
#[derive(Clone, Copy, Debug)]
pub struct Park2tClosure<R> {
pub t_ve_initial: R,
pub pressure_atm: R,
pub reduced_mass_amu: R,
pub theta_vib: R,
}
const N_ATOMIC_MASS_AMU: f64 = 14.007;
const N2_MOLECULAR_MASS_AMU: f64 = 2.0 * N_ATOMIC_MASS_AMU;
pub const REDUCED_MASS_AMU: f64 =
N2_MOLECULAR_MASS_AMU * N2_MOLECULAR_MASS_AMU / (N2_MOLECULAR_MASS_AMU + N2_MOLECULAR_MASS_AMU);
pub fn reduced_mass_amu(
relaxing_mass_amu: f64,
partner_mass_amu: f64,
relaxing_is_diatomic: bool,
) -> Result<f64, PhysicsError> {
if !relaxing_is_diatomic {
return Err(PhysicsError::PhysicalInvariantBroken(
"the relaxing species is monatomic and has no vibrational mode; it cannot be the \
Millikan–White relaxing partner (this is why μ = 7.0, the N–N atomic pair, is invalid)"
.into(),
));
}
if relaxing_mass_amu <= 0.0
|| partner_mass_amu <= 0.0
|| !relaxing_mass_amu.is_finite()
|| !partner_mass_amu.is_finite()
{
return Err(PhysicsError::PhysicalInvariantBroken(
"collision-pair masses must be strictly positive and finite".into(),
));
}
Ok(relaxing_mass_amu * partner_mass_amu / (relaxing_mass_amu + partner_mass_amu))
}
pub struct FittedNormalShock<R> {
gamma: R,
}
impl<R> FittedNormalShock<R>
where
R: CfdScalar + ConjugateScalar<Real = R>,
{
pub fn new(gamma: R) -> Result<Self, PhysicsError> {
if gamma <= R::one() {
return Err(PhysicsError::PhysicalInvariantBroken(
"ratio of specific heats must be > 1".into(),
));
}
Ok(Self { gamma })
}
pub fn post_shock(
&self,
t_inf: R,
n_tot_inf: R,
mach: R,
) -> Result<PostShockState<R>, PhysicsError> {
let g = self.gamma;
let one = R::one();
let two = R::from_f64(2.0)
.ok_or_else(|| PhysicsError::NumericalInstability("from_f64(2.0)".into()))?;
let m2 = mach * mach;
let t2 = rankine_hugoniot_temperature_kernel(Temperature::new(t_inf)?, mach, g)?.value();
let rho_ratio = (g + one) * m2 / ((g - one) * m2 + two);
let u_ratio = one / rho_ratio;
let p_ratio = (two * g * m2 - (g - one)) / (g + one);
let n_tot2 = n_tot_inf * rho_ratio;
Ok(PostShockState {
t2,
n_tot2,
rho_ratio,
u_ratio,
p_ratio,
})
}
pub fn stagnation_blackout(
&self,
post: &PostShockState<R>,
comms_band: R,
) -> Result<StagnationOutcome<R>, PhysicsError> {
let alpha = park2t_ionization_surrogate_kernel(Temperature::new(post.t2)?, post.n_tot2)?;
let n_e = electron_density_kernel(alpha, post.n_tot2)?;
let omega_p = plasma_frequency_kernel(n_e)?;
Ok(StagnationOutcome {
electron_density: n_e.value(),
plasma_frequency: omega_p.value(),
ionization_fraction: alpha.value(),
blackout: omega_p.value() > comms_band,
})
}
pub fn stagnation_line_blackout(
&self,
post: &PostShockState<R>,
residence_time: R,
comms_band: R,
) -> Result<StagnationOutcome<R>, PhysicsError> {
let t2 = Temperature::new(post.t2)?;
let alpha_eq = park2t_ionization_surrogate_kernel(t2, post.n_tot2)?.value();
let prefactor = R::from_f64(PARK_NO_IONIZATION_PREFACTOR)
.ok_or_else(|| PhysicsError::NumericalInstability("Park prefactor".into()))?;
let exponent = R::from_f64(PARK_NO_IONIZATION_EXPONENT)
.ok_or_else(|| PhysicsError::NumericalInstability("Park exponent".into()))?;
let theta_d = R::from_f64(PARK_NO_IONIZATION_ACTIVATION_TEMP)
.ok_or_else(|| PhysicsError::NumericalInstability("Park activation temp".into()))?;
let k_cgs = arrhenius_rate_kernel(t2, prefactor, exponent, theta_d)?.value();
let cm3_per_m3 = R::from_f64(1.0e-6)
.ok_or_else(|| PhysicsError::NumericalInstability("cm³→m³".into()))?;
let avogadro = R::from_f64(AVOGADRO_CONSTANT)
.ok_or_else(|| PhysicsError::NumericalInstability("Avogadro".into()))?;
let k_si = k_cgs * cm3_per_m3 / avogadro;
let tau_ion = R::one() / (k_si * post.n_tot2);
let frac = R::one() - (R::zero() - residence_time / tau_ion).exp();
let alpha = alpha_eq * frac;
let n_e = ElectronDensity::new(alpha * post.n_tot2)?;
let omega_p = plasma_frequency_kernel(n_e)?;
Ok(StagnationOutcome {
electron_density: n_e.value(),
plasma_frequency: omega_p.value(),
ionization_fraction: alpha,
blackout: omega_p.value() > comms_band,
})
}
pub fn stagnation_line_blackout_2t(
&self,
post: &PostShockState<R>,
residence_time: R,
closure: &Park2tClosure<R>,
comms_band: R,
) -> Result<StagnationOutcome<R>, PhysicsError> {
let t_tr = Temperature::new(post.t2)?;
let t_ve = vibrational_relaxation_kernel(
VibrationalTemperature::new(closure.t_ve_initial)?,
t_tr,
closure.pressure_atm,
closure.reduced_mass_amu,
closure.theta_vib,
residence_time,
)?
.value();
let t_a_val = (post.t2 * t_ve).sqrt();
let t_a = Temperature::new(t_a_val)?;
let alpha_eq = park2t_ionization_surrogate_kernel(t_a, post.n_tot2)?.value();
let prefactor = R::from_f64(PARK_NO_IONIZATION_PREFACTOR)
.ok_or_else(|| PhysicsError::NumericalInstability("Park prefactor".into()))?;
let exponent = R::from_f64(PARK_NO_IONIZATION_EXPONENT)
.ok_or_else(|| PhysicsError::NumericalInstability("Park exponent".into()))?;
let theta_d = R::from_f64(PARK_NO_IONIZATION_ACTIVATION_TEMP)
.ok_or_else(|| PhysicsError::NumericalInstability("Park activation temp".into()))?;
let k_cgs = arrhenius_rate_kernel(t_a, prefactor, exponent, theta_d)?.value();
let cm3_per_m3 = R::from_f64(1.0e-6)
.ok_or_else(|| PhysicsError::NumericalInstability("cm³→m³".into()))?;
let avogadro = R::from_f64(AVOGADRO_CONSTANT)
.ok_or_else(|| PhysicsError::NumericalInstability("Avogadro".into()))?;
let k_si = k_cgs * cm3_per_m3 / avogadro;
let tau_ion = R::one() / (k_si * post.n_tot2);
let frac = R::one() - (R::zero() - residence_time / tau_ion).exp();
let alpha = alpha_eq * frac;
let n_e = ElectronDensity::new(alpha * post.n_tot2)?;
let omega_p = plasma_frequency_kernel(n_e)?;
Ok(StagnationOutcome {
electron_density: n_e.value(),
plasma_frequency: omega_p.value(),
ionization_fraction: alpha,
blackout: omega_p.value() > comms_band,
})
}
pub fn relaxation_profile_bond(
&self,
post: &PostShockState<R>,
l: usize,
relax_length: R,
trunc: &Truncation<R>,
) -> Result<(usize, R), PhysicsError> {
let alpha_eq =
park2t_ionization_surrogate_kernel(Temperature::new(post.t2)?, post.n_tot2)?.value();
let peak = alpha_eq * post.n_tot2;
let n = 1usize << l;
let n_r = R::from_usize(n)
.ok_or_else(|| PhysicsError::NumericalInstability("from_usize(n)".into()))?;
let mut data = vec![R::zero(); n];
for (i, d) in data.iter_mut().enumerate() {
let s = R::from_usize(i)
.ok_or_else(|| PhysicsError::NumericalInstability("from_usize(i)".into()))?
/ n_r;
let frac = R::one() - (R::zero() - s / relax_length).exp();
*d = peak * frac;
}
let field = quantize(&CausalTensor::new(data, vec![n])?, trunc)?;
Ok((field.max_bond(), peak))
}
}