use crate::constants::{
rp1232_ei_n_activation_temp, rp1232_ei_n_exponent, rp1232_ei_n_prefactor,
rp1232_ei_o_activation_temp, rp1232_ei_o_exponent, rp1232_ei_o_prefactor,
rp1232_n2_diss_activation_temp, rp1232_n2_diss_exponent, rp1232_n2_diss_prefactor,
rp1232_n2_recomb_exponent, rp1232_n2_recomb_prefactor, rp1232_no_dr_activation_temp,
rp1232_no_dr_exponent, rp1232_no_dr_prefactor, rp1232_o2_diss_activation_temp,
rp1232_o2_diss_exponent, rp1232_o2_diss_prefactor, rp1232_o2_recomb_exponent,
rp1232_o2_recomb_prefactor,
};
use crate::kernels::hypersonic::thermochemistry::arrhenius_rate_kernel;
use crate::{
DissociationFraction, ElectronDensity, ElectronTemperature, EquilibriumConstant, PhysicsError,
ReactionRate, Temperature,
};
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
fn lift<R: RealField + FromPrimitive>(x: f64, what: &str) -> Result<R, PhysicsError> {
R::from_f64(x)
.ok_or_else(|| PhysicsError::NumericalInstability(format!("R::from_f64({what}) failed")))
}
pub fn no_dissociative_recombination_rate_kernel<R>(
electron_temperature: ElectronTemperature<R>,
) -> Result<ReactionRate<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
arrhenius_rate_kernel(
Temperature::new(electron_temperature.value())?,
rp1232_no_dr_prefactor::<R>(),
rp1232_no_dr_exponent::<R>(),
rp1232_no_dr_activation_temp::<R>(),
)
}
pub fn electron_impact_ionization_n_rate_kernel<R>(
electron_temperature: ElectronTemperature<R>,
) -> Result<ReactionRate<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
arrhenius_rate_kernel(
Temperature::new(electron_temperature.value())?,
rp1232_ei_n_prefactor::<R>(),
rp1232_ei_n_exponent::<R>(),
rp1232_ei_n_activation_temp::<R>(),
)
}
pub fn electron_impact_ionization_o_rate_kernel<R>(
electron_temperature: ElectronTemperature<R>,
) -> Result<ReactionRate<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
arrhenius_rate_kernel(
Temperature::new(electron_temperature.value())?,
rp1232_ei_o_prefactor::<R>(),
rp1232_ei_o_exponent::<R>(),
rp1232_ei_o_activation_temp::<R>(),
)
}
pub fn n2_dissociation_equilibrium_kernel<R>(
temperature: Temperature<R>,
) -> Result<EquilibriumConstant<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let kf = arrhenius_rate_kernel(
temperature,
rp1232_n2_diss_prefactor::<R>(),
rp1232_n2_diss_exponent::<R>(),
rp1232_n2_diss_activation_temp::<R>(),
)?;
let kb = arrhenius_rate_kernel(
temperature,
rp1232_n2_recomb_prefactor::<R>(),
rp1232_n2_recomb_exponent::<R>(),
R::zero(),
)?;
EquilibriumConstant::new(kf.value() / kb.value())
}
pub fn o2_dissociation_equilibrium_kernel<R>(
temperature: Temperature<R>,
) -> Result<EquilibriumConstant<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let kf = arrhenius_rate_kernel(
temperature,
rp1232_o2_diss_prefactor::<R>(),
rp1232_o2_diss_exponent::<R>(),
rp1232_o2_diss_activation_temp::<R>(),
)?;
let kb = arrhenius_rate_kernel(
temperature,
rp1232_o2_recomb_prefactor::<R>(),
rp1232_o2_recomb_exponent::<R>(),
R::zero(),
)?;
EquilibriumConstant::new(kf.value() / kb.value())
}
pub fn dissociation_equilibrium_fraction_kernel<R>(
k_eq: EquilibriumConstant<R>,
nuclei_density: R,
) -> Result<DissociationFraction<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
if nuclei_density <= R::zero() {
return Err(PhysicsError::Singularity(
"Nuclei density must be positive for the dissociation equilibrium".into(),
));
}
let k = k_eq.value();
let four = lift::<R>(4.0, "4.0")?;
let eight = lift::<R>(8.0, "8.0")?;
let atoms = (-k + (k * k + eight * k * nuclei_density).sqrt()) / four;
let x = atoms / nuclei_density;
let x = if x < R::zero() {
R::zero()
} else if x > R::one() {
R::one()
} else {
x
};
DissociationFraction::new(x)
}
pub fn finite_rate_ionization_fixed_point_kernel<R>(
production: R,
linear_coefficient: R,
loss_coefficient: R,
) -> Result<ElectronDensity<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
if production < R::zero() || linear_coefficient < R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"Production terms cannot be negative".into(),
));
}
if loss_coefficient <= R::zero() {
return Err(PhysicsError::Singularity(
"The quadratic loss coefficient must be positive (dissociative recombination is \
barrier-free)"
.into(),
));
}
let two = lift::<R>(2.0, "2.0")?;
let four = lift::<R>(4.0, "4.0")?;
let disc = linear_coefficient * linear_coefficient + four * loss_coefficient * production;
let x = (linear_coefficient + disc.sqrt()) / (two * loss_coefficient);
ElectronDensity::new(x)
}
pub fn no_associative_ionization_rate_kernel<R>(
temperature: Temperature<R>,
) -> Result<ReactionRate<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
arrhenius_rate_kernel(
temperature,
crate::constants::park_no_ionization_prefactor::<R>(),
crate::constants::park_no_ionization_exponent::<R>(),
crate::constants::park_no_ionization_activation_temp::<R>(),
)
}
pub fn zeldovich_exchange_rate_kernel<R>(
temperature: Temperature<R>,
) -> Result<ReactionRate<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
arrhenius_rate_kernel(
temperature,
crate::constants::rp1232_zeldovich_prefactor::<R>(),
crate::constants::rp1232_zeldovich_exponent::<R>(),
crate::constants::rp1232_zeldovich_activation_temp::<R>(),
)
}
pub fn park_controlling_temperature_kernel<R>(
t_translational: Temperature<R>,
t_vibrational: Temperature<R>,
q: R,
) -> Result<Temperature<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let q_in_unit_interval = q >= R::zero() && q <= R::one();
if !q_in_unit_interval {
return Err(PhysicsError::PhysicalInvariantBroken(
"The controlling-temperature exponent q must lie in [0, 1]".into(),
));
}
let t = t_translational.value();
let tv = t_vibrational.value();
Temperature::new(t.powf(q) * tv.powf(R::one() - q))
}