use crate::{
Density, FlowBranch, NozzleExitState, PhysicsError, Pressure, Speed, Temperature,
area_mach_ratio_kernel, isentropic_pressure_ratio_kernel, isentropic_temperature_ratio_kernel,
speed_of_sound_ideal_gas_kernel,
};
use deep_causality_algebra::RealField;
use deep_causality_num::FromPrimitive;
const INVERSE_AREA_MACH_ITERATIONS: usize = 200;
pub fn inverse_area_mach_kernel<R>(
area_ratio: R,
gamma: R,
branch: FlowBranch,
) -> Result<R, PhysicsError>
where
R: RealField + FromPrimitive,
{
let one = R::one();
if !area_ratio.is_finite() || area_ratio < one {
return Err(PhysicsError::PhysicalInvariantBroken(
"Area ratio A/A* must be finite and >= 1".into(),
));
}
if gamma <= one {
return Err(PhysicsError::PhysicalInvariantBroken(
"Ratio of specific heats must be > 1".into(),
));
}
if area_ratio == one {
return Ok(one);
}
let (mut lo, mut hi) = match branch {
FlowBranch::Subsonic => {
let tiny = R::from_f64(1.0e-9).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(1.0e-9) failed".into())
})?;
if area_mach_ratio_kernel(tiny, gamma)? < area_ratio {
return Err(PhysicsError::NumericalInstability(
"inverse_area_mach_kernel: subsonic area ratio exceeds the solve bracket"
.into(),
));
}
(tiny, one)
}
FlowBranch::Supersonic => {
let cap = R::from_f64(1.0e9).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(1.0e9) failed".into())
})?;
let two = R::from_f64(2.0).ok_or_else(|| {
PhysicsError::NumericalInstability("R::from_f64(2.0) failed".into())
})?;
let mut hi = two;
while area_mach_ratio_kernel(hi, gamma)? < area_ratio {
hi *= two;
if hi > cap {
return Err(PhysicsError::NumericalInstability(
"inverse_area_mach_kernel: supersonic bracket exceeded cap".into(),
));
}
}
(one, hi)
}
};
let half = R::from_f64(0.5)
.ok_or_else(|| PhysicsError::NumericalInstability("R::from_f64(0.5) failed".into()))?;
for _ in 0..INVERSE_AREA_MACH_ITERATIONS {
let mid = (lo + hi) * half;
if mid <= lo || mid >= hi {
break; }
let f = area_mach_ratio_kernel(mid, gamma)?;
let root_is_above = match branch {
FlowBranch::Subsonic => f > area_ratio,
FlowBranch::Supersonic => f < area_ratio,
};
if root_is_above {
lo = mid;
} else {
hi = mid;
}
}
Ok((lo + hi) * half)
}
pub fn nozzle_exit_state_kernel<R>(
chamber_pressure: Pressure<R>,
chamber_temperature: Temperature<R>,
area_ratio: R,
gamma: R,
r_specific: R,
) -> Result<NozzleExitState<R>, PhysicsError>
where
R: RealField + FromPrimitive,
{
if chamber_pressure.value() <= R::zero() {
return Err(PhysicsError::Singularity(
"Chamber pressure must be positive".into(),
));
}
if chamber_temperature.value() <= R::zero() {
return Err(PhysicsError::Singularity(
"Chamber temperature must be positive".into(),
));
}
if r_specific <= R::zero() {
return Err(PhysicsError::Singularity(
"Specific gas constant must be positive".into(),
));
}
let mach_e = inverse_area_mach_kernel(area_ratio, gamma, FlowBranch::Supersonic)?;
let p_ratio = isentropic_pressure_ratio_kernel(mach_e, gamma)?; let t_ratio = isentropic_temperature_ratio_kernel(mach_e, gamma)?; let p_e = Pressure::new(chamber_pressure.value() / p_ratio)?;
let t_e = Temperature::new(chamber_temperature.value() / t_ratio)?;
let rho_e = Density::new(p_e.value() / (r_specific * t_e.value()))?;
let a_e = speed_of_sound_ideal_gas_kernel(gamma, r_specific, &t_e)?;
let u_e = Speed::new(mach_e * a_e.value())?;
Ok(NozzleExitState::new(mach_e, p_e, t_e, rho_e, u_e))
}