use crate::constants::{
BOLTZMANN_CONSTANT, ELECTRON_MASS, ELEMENTARY_CHARGE, NO_IONIZATION_ENERGY_EV, PLANCK_CONSTANT,
};
use crate::{ElectronDensity, IonizationFraction, PhysicsError, Temperature};
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
pub fn saha_ionization_fraction_kernel<R>(
temperature: Temperature<R>,
total_number_density: R,
ionization_energy_ev: R,
partition_ratio: R,
) -> Result<IonizationFraction<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let t = temperature.value();
if t <= R::zero() {
return Err(PhysicsError::Singularity(
"Temperature must be positive for the Saha equation".into(),
));
}
if total_number_density <= R::zero() {
return Err(PhysicsError::Singularity(
"Total number density must be positive".into(),
));
}
if ionization_energy_ev <= R::zero() || partition_ratio <= R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"Ionization energy and partition ratio must be positive".into(),
));
}
let me_ = R::from_f64(ELECTRON_MASS).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(ELECTRON_MASS) failed".into())
})?;
let kb = R::from_f64(BOLTZMANN_CONSTANT).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(BOLTZMANN_CONSTANT) failed".into())
})?;
let h = R::from_f64(PLANCK_CONSTANT).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(PLANCK_CONSTANT) failed".into())
})?;
let e_charge = R::from_f64(ELEMENTARY_CHARGE).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(ELEMENTARY_CHARGE) failed".into())
})?;
let two = R::from_f64(2.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(2.0) failed".into()))?;
let four = R::from_f64(4.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(4.0) failed".into()))?;
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
let three_halves = R::from_f64(1.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(1.5) failed".into()))?;
let two_pi = two * R::pi();
let e_ion_j = ionization_energy_ev * e_charge;
let thermal_db = (two_pi * me_ * kb * t / (h * h)).powf(three_halves);
let k_saha = partition_ratio * thermal_db * (-(e_ion_j / (kb * t))).exp();
let x = k_saha / total_number_density;
let alpha = (-x + (x * x + four * x).sqrt()) * half;
let alpha = if alpha < R::zero() {
R::zero()
} else if alpha > R::one() {
R::one()
} else {
alpha
};
IonizationFraction::new(alpha)
}
pub fn park2t_ionization_surrogate_kernel<R>(
temperature: Temperature<R>,
total_number_density: R,
) -> Result<IonizationFraction<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
let e_ion = R::from_f64(NO_IONIZATION_ENERGY_EV).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(NO_IONIZATION_ENERGY_EV) failed".into())
})?;
let two = R::from_f64(2.0)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(2.0) failed".into()))?;
saha_ionization_fraction_kernel(temperature, total_number_density, e_ion, two)
}
pub fn electron_density_kernel<R>(
alpha: IonizationFraction<R>,
total_number_density: R,
) -> Result<ElectronDensity<R>, PhysicsError>
where
R: RealField,
{
if total_number_density < R::zero() {
return Err(PhysicsError::PhysicalInvariantBroken(
"Total number density cannot be negative".into(),
));
}
ElectronDensity::new(alpha.value() * total_number_density)
}