use crate::constants::{
MILLIKAN_WHITE_A_COEFFICIENT, MILLIKAN_WHITE_LOG_OFFSET, MILLIKAN_WHITE_MU_OFFSET,
};
use crate::{PhysicsError, ReactionRate, Temperature, VibrationalTemperature};
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
pub fn vibrational_relaxation_kernel<R>(
t_ve: VibrationalTemperature<R>,
t_tr: Temperature<R>,
pressure_atm: R,
reduced_mass_amu: R,
theta_vib: R,
dt: R,
) -> Result<VibrationalTemperature<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
if pressure_atm <= R::zero() {
return Err(PhysicsError::Singularity(
"Pressure must be positive for Millikan–White relaxation".into(),
));
}
if reduced_mass_amu <= R::zero() || theta_vib <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"Reduced mass and vibrational temperature must be positive".into(),
));
}
if dt < R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"Timestep must be non-negative".into(),
));
}
let t = t_tr.value();
if t <= R::zero() {
return Err(PhysicsError::Singularity(
"Translational temperature must be positive".into(),
));
}
let a = R::from_f64(MILLIKAN_WHITE_A_COEFFICIENT).ok_or_else(|| {
PhysicsError::NumericalInstability(
"R::from_f64(MILLIKAN_WHITE_A_COEFFICIENT) failed".into(),
)
})?;
let b = R::from_f64(MILLIKAN_WHITE_MU_OFFSET).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(MILLIKAN_WHITE_MU_OFFSET) failed".into())
})?;
let c = R::from_f64(MILLIKAN_WHITE_LOG_OFFSET).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(MILLIKAN_WHITE_LOG_OFFSET) failed".into())
})?;
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
let quarter = R::from_f64(0.25)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.25) failed".into()))?;
let neg_third = R::from_f64(-1.0 / 3.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(-1/3) failed".into()))?;
let four_thirds = R::from_f64(4.0 / 3.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(4/3) failed".into()))?;
let a_sr = a * reduced_mass_amu.powf(half) * theta_vib.powf(four_thirds);
let exponent = a_sr * (t.powf(neg_third) - b * reduced_mass_amu.powf(quarter)) - c;
let tau = exponent.exp() / pressure_atm;
if tau <= R::zero() || !tau.is_finite() {
return Err(PhysicsError::NumericalInstability(
"Non-physical Millikan–White relaxation time".into(),
));
}
let decay = (-(dt / tau)).exp();
let t_ve_new = t - (t - t_ve.value()) * decay;
VibrationalTemperature::new(t_ve_new)
}
pub fn arrhenius_rate_kernel<R>(
temperature: Temperature<R>,
prefactor: R,
exponent: R,
activation_temp: R,
) -> Result<ReactionRate<R>, PhysicsError>
where
R: RealField,
{
let t = temperature.value();
if t <= R::zero() {
return Err(PhysicsError::Singularity(
"Temperature must be positive for an Arrhenius rate".into(),
));
}
if prefactor < R::zero() || activation_temp < R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"Arrhenius prefactor and activation temperature must be non-negative".into(),
));
}
let rate = prefactor * t.powf(exponent) * (-(activation_temp / t)).exp();
ReactionRate::new(rate)
}