use crate::InternalBallisticInputs as BallisticInputs;
pub fn compute_stability_coefficient(
inputs: &BallisticInputs,
atmo_params: (f64, f64, f64, f64),
) -> f64 {
if inputs.twist_rate == 0.0 || inputs.bullet_length == 0.0 || inputs.bullet_diameter == 0.0 {
return 0.0;
}
const MILLER_CONST: f64 = 30.0;
const VEL_REF_FPS: f64 = 2800.0;
const TEMP_REF_K: f64 = 288.15; const PRESS_REF_HPA: f64 = 1013.25;
let twist_rate_m = inputs.twist_rate.abs() * 0.0254; let twist_calibers = twist_rate_m / inputs.bullet_diameter;
let length_calibers = inputs.bullet_length / inputs.bullet_diameter;
let mass_grains = inputs.bullet_mass / 0.00006479891; let diameter_inches = inputs.bullet_diameter / 0.0254; let velocity_fps = inputs.muzzle_velocity * 3.28084;
let mass_term = MILLER_CONST * mass_grains;
let geom_term = twist_calibers.powi(2)
* diameter_inches.powi(3)
* length_calibers
* (1.0 + length_calibers.powi(2));
if geom_term == 0.0 {
return 0.0;
}
let (_, temp_c, current_press_hpa, _) = atmo_params;
let temp_k = temp_c + 273.15;
let density_correction = (temp_k / TEMP_REF_K) * (PRESS_REF_HPA / current_press_hpa);
let velocity_correction = (velocity_fps / VEL_REF_FPS).powf(1.0 / 3.0);
(mass_term / geom_term) * velocity_correction * density_correction
}
pub fn compute_spin_drift(
time_s: f64,
stability: f64,
twist_rate: f64,
is_twist_right: bool,
) -> f64 {
compute_spin_drift_with_decay(time_s, stability, twist_rate, is_twist_right, None)
}
pub fn compute_spin_drift_with_decay(
time_s: f64,
stability: f64,
twist_rate: f64,
is_twist_right: bool,
decay_factor: Option<f64>, ) -> f64 {
if stability == 0.0 || time_s <= 0.0 || twist_rate == 0.0 {
return 0.0;
}
let sign = if is_twist_right { 1.0 } else { -1.0 };
let scaling_factor = 1.25;
let base_drift = sign * scaling_factor * (stability + 1.2) * time_s.powf(1.83);
let effective_drift = if let Some(decay) = decay_factor {
base_drift * decay.max(0.0).min(1.0)
} else {
base_drift
};
effective_drift * 0.0254
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_inputs() -> BallisticInputs {
BallisticInputs {
muzzle_velocity: 823.0, bc_value: 0.5,
bullet_mass: 0.0109, bullet_diameter: 0.00782, bullet_length: 0.033, twist_rate: 10.0,
..Default::default()
}
}
#[test]
fn test_compute_stability_coefficient() {
let inputs = create_test_inputs();
let atmo_params = (0.0, 15.0, 1013.25, 1.0);
let stability = compute_stability_coefficient(&inputs, atmo_params);
println!("Computed stability: {}", stability);
assert!(stability > 0.0);
assert!(stability < 10.0);
assert!(stability > 1.0);
assert!(stability < 3.0);
}
#[test]
fn test_compute_stability_coefficient_zero_cases() {
let mut inputs = create_test_inputs();
let atmo_params = (0.0, 15.0, 1013.25, 1.0);
inputs.twist_rate = 0.0;
assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
inputs = create_test_inputs();
inputs.bullet_length = 0.0;
assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
inputs = create_test_inputs();
inputs.bullet_diameter = 0.0;
assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
}
#[test]
fn test_compute_stability_coefficient_atmospheric_effects() {
let inputs = create_test_inputs();
let standard_atmo = (0.0, 15.0, 1013.25, 1.0);
let standard_stability = compute_stability_coefficient(&inputs, standard_atmo);
let high_alt_atmo = (3000.0, 5.0, 900.0, 1.0);
let high_alt_stability = compute_stability_coefficient(&inputs, high_alt_atmo);
assert!(high_alt_stability > standard_stability);
let hot_atmo = (0.0, 35.0, 1013.25, 1.0);
let hot_stability = compute_stability_coefficient(&inputs, hot_atmo);
assert!(hot_stability > standard_stability);
}
#[test]
fn test_compute_spin_drift() {
let time_s = 1.5;
let stability = 2.0;
let twist_rate = 10.0;
let drift_right = compute_spin_drift(time_s, stability, twist_rate, true);
assert!(drift_right > 0.0);
let drift_left = compute_spin_drift(time_s, stability, twist_rate, false);
assert!(drift_left < 0.0); assert!((drift_left + drift_right).abs() < 1e-10);
assert!(drift_right.abs() < 0.25); }
#[test]
fn test_compute_spin_drift_zero_cases() {
assert_eq!(compute_spin_drift(1.5, 0.0, 10.0, true), 0.0);
assert_eq!(compute_spin_drift(0.0, 2.0, 10.0, true), 0.0);
assert_eq!(compute_spin_drift(-1.0, 2.0, 10.0, true), 0.0);
assert_eq!(compute_spin_drift(1.5, 2.0, 0.0, true), 0.0);
}
#[test]
fn test_compute_spin_drift_scaling() {
let stability = 2.0;
let twist_rate = 10.0;
let drift_1s = compute_spin_drift(1.0, stability, twist_rate, true);
let drift_2s = compute_spin_drift(2.0, stability, twist_rate, true);
assert!(drift_2s > drift_1s);
let drift_low_stability = compute_spin_drift(1.5, 1.0, twist_rate, true);
let drift_high_stability = compute_spin_drift(1.5, 3.0, twist_rate, true);
assert!(drift_high_stability > drift_low_stability);
}
}