use crate::params::*;
#[non_exhaustive]
pub struct CollisionResult {
pub z: f64,
pub dzdr: f64,
pub dzdth: f64,
pub dzdph: f64,
}
pub fn compute_col(
r: f64,
theta: f64,
phi: f64,
freq_mhz: f64,
p: &ModelParams,
) -> CollisionResult {
match p.col_model {
CollisionModel::Constant => constz(r, theta, phi, freq_mhz, p),
CollisionModel::SingleExponential => expz(r, theta, phi, freq_mhz, p),
_ => expz2(r, theta, phi, freq_mhz, p),
}
}
fn expz2(r: f64, _theta: f64, _phi: f64, freq_mhz: f64, p: &ModelParams) -> CollisionResult {
let h = (r - p.earth_r).max(0.0);
let omega = PIT2 * freq_mhz * 1.0e6;
if omega == 0.0 {
return CollisionResult { z: 0.0, dzdr: 0.0, dzdth: 0.0, dzdph: 0.0 };
}
let arg1 = (-p.a1 * (h - p.h1)).clamp(-20.0, 20.0);
let arg2 = (-p.a2 * (h - p.h2)).clamp(-20.0, 20.0);
let exp1 = p.nu1 * arg1.exp();
let exp2 = p.nu2 * arg2.exp();
let z = (exp1 + exp2) / omega;
let dzdr = (-p.a1 * exp1 - p.a2 * exp2) / omega;
CollisionResult {
z,
dzdr,
dzdth: 0.0,
dzdph: 0.0,
}
}
fn constz(_r: f64, _theta: f64, _phi: f64, freq_mhz: f64, p: &ModelParams) -> CollisionResult {
let omega = PIT2 * freq_mhz * 1.0e6;
let z = if omega != 0.0 { p.nu1 / omega } else { 0.0 };
CollisionResult {
z,
dzdr: 0.0,
dzdth: 0.0,
dzdph: 0.0,
}
}
fn expz(r: f64, _theta: f64, _phi: f64, freq_mhz: f64, p: &ModelParams) -> CollisionResult {
let h = (r - p.earth_r).max(0.0);
let omega = PIT2 * freq_mhz * 1.0e6;
if omega == 0.0 {
return CollisionResult { z: 0.0, dzdr: 0.0, dzdth: 0.0, dzdph: 0.0 };
}
let arg1 = (-p.a1 * (h - p.h1)).clamp(-20.0, 20.0);
let exp1 = p.nu1 * arg1.exp();
let z = exp1 / omega;
let dzdr = -p.a1 * exp1 / omega;
CollisionResult {
z,
dzdr,
dzdth: 0.0,
dzdph: 0.0,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn default_params() -> ModelParams {
ModelParams::default()
}
#[test]
fn test_expz2_at_low_altitude() {
let p = default_params();
let col = compute_col(EARTH_RADIUS + 80.0, PID2, 0.0, 10.0, &p);
assert!(col.z > 0.0, "EXPZ2 z at 80 km should be positive");
assert!(
col.dzdr < 0.0,
"EXPZ2 dzdr should be negative (decreasing with height)"
);
}
#[test]
fn test_expz2_decreases_with_altitude() {
let p = default_params();
let col_low = compute_col(EARTH_RADIUS + 80.0, PID2, 0.0, 10.0, &p);
let col_high = compute_col(EARTH_RADIUS + 300.0, PID2, 0.0, 10.0, &p);
assert!(
col_low.z > col_high.z,
"Collision frequency should decrease with altitude"
);
}
#[test]
fn test_expz2_no_angular_dependence() {
let p = default_params();
let col = compute_col(EARTH_RADIUS + 200.0, PID2, 0.0, 10.0, &p);
assert_eq!(col.dzdth, 0.0, "EXPZ2 should have no theta dependence");
assert_eq!(col.dzdph, 0.0, "EXPZ2 should have no phi dependence");
}
#[test]
fn test_constz_constant() {
let mut p = default_params();
p.col_model = CollisionModel::Constant;
let col1 = compute_col(EARTH_RADIUS + 100.0, PID2, 0.0, 10.0, &p);
let col2 = compute_col(EARTH_RADIUS + 500.0, 1.0, 1.0, 10.0, &p);
assert!((col1.z - col2.z).abs() < 1e-15, "CONSTZ should be constant");
assert_eq!(col1.dzdr, 0.0, "CONSTZ dzdr should be 0");
}
#[test]
fn test_constz_value() {
let mut p = default_params();
p.col_model = CollisionModel::Constant;
let col = compute_col(EARTH_RADIUS, PID2, 0.0, 10.0, &p);
let omega = PIT2 * 10.0e6;
let expected = p.nu1 / omega;
assert!((col.z - expected).abs() < 1e-12, "CONSTZ z = nu1/omega");
}
#[test]
fn test_expz_single_exponential() {
let mut p = default_params();
p.col_model = CollisionModel::SingleExponential;
let col = compute_col(EARTH_RADIUS + 150.0, PID2, 0.0, 10.0, &p);
assert!(col.z > 0.0, "EXPZ should be positive");
assert!(col.dzdr < 0.0, "EXPZ dzdr should be negative");
}
#[test]
fn test_expz_vs_expz2() {
let mut p1 = default_params();
p1.col_model = CollisionModel::DoubleExponential; let mut p2 = default_params();
p2.col_model = CollisionModel::SingleExponential;
let r = EARTH_RADIUS + p1.h1;
let col1 = compute_col(r, PID2, 0.0, 10.0, &p1);
let col2 = compute_col(r, PID2, 0.0, 10.0, &p2);
assert!(col1.z >= col2.z, "EXPZ2 >= EXPZ (extra term)");
}
#[test]
fn test_all_collision_models_finite() {
for model in [
CollisionModel::DoubleExponential,
CollisionModel::Constant,
CollisionModel::SingleExponential,
] {
let mut p = default_params();
p.col_model = model;
let col = compute_col(EARTH_RADIUS + 200.0, PID2, 0.0, 10.0, &p);
assert!(col.z.is_finite(), "Col model {:?} z not finite", model);
assert!(
col.dzdr.is_finite(),
"Col model {:?} dzdr not finite",
model
);
}
}
}