1use crate::InternalBallisticInputs as BallisticInputs;
2
3pub fn compute_stability_coefficient(
15 inputs: &BallisticInputs,
16 atmo_params: (f64, f64, f64, f64),
17) -> f64 {
18 if inputs.twist_rate == 0.0 || inputs.bullet_length == 0.0 || inputs.bullet_diameter == 0.0 {
20 return 0.0;
21 }
22
23 const MILLER_CONST: f64 = 30.0;
25 const VEL_REF_FPS: f64 = 2800.0;
26 const TEMP_REF_K: f64 = 288.15; const PRESS_REF_HPA: f64 = 1013.25;
28
29 let twist_rate_m = inputs.twist_rate.abs() * 0.0254; let twist_calibers = twist_rate_m / inputs.bullet_diameter;
33 let length_calibers = inputs.bullet_length / inputs.bullet_diameter;
34
35 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;
42 let geom_term = twist_calibers.powi(2)
43 * diameter_inches.powi(3)
44 * length_calibers
45 * (1.0 + length_calibers.powi(2));
46
47 if geom_term == 0.0 {
48 return 0.0;
49 }
50
51 let (_, temp_c, current_press_hpa, _) = atmo_params;
53 let temp_k = temp_c + 273.15;
54
55 let density_correction = (temp_k / TEMP_REF_K) * (PRESS_REF_HPA / current_press_hpa);
58
59 let velocity_correction = (velocity_fps / VEL_REF_FPS).powf(1.0 / 3.0);
61
62 (mass_term / geom_term) * velocity_correction * density_correction
65}
66
67pub fn compute_spin_drift(
78 time_s: f64,
79 stability: f64,
80 twist_rate: f64,
81 is_twist_right: bool,
82) -> f64 {
83 compute_spin_drift_with_decay(time_s, stability, twist_rate, is_twist_right, None)
84}
85
86pub fn compute_spin_drift_with_decay(
88 time_s: f64,
89 stability: f64,
90 twist_rate: f64,
91 is_twist_right: bool,
92 decay_factor: Option<f64>, ) -> f64 {
94 if stability == 0.0 || time_s <= 0.0 || twist_rate == 0.0 {
95 return 0.0;
96 }
97
98 let sign = if is_twist_right { 1.0 } else { -1.0 };
99
100 let scaling_factor = 1.25;
103 let base_drift = sign * scaling_factor * (stability + 1.2) * time_s.powf(1.83);
104
105 let effective_drift = if let Some(decay) = decay_factor {
107 base_drift * decay.max(0.0).min(1.0)
108 } else {
109 base_drift
110 };
111
112 effective_drift * 0.0254
114}
115
116#[cfg(test)]
117mod tests {
118 use super::*;
119
120 fn create_test_inputs() -> BallisticInputs {
121 BallisticInputs {
122 muzzle_velocity: 823.0, bc_value: 0.5,
124 bullet_mass: 0.0109, bullet_diameter: 0.00782, bullet_length: 0.033, twist_rate: 10.0,
128 ..Default::default()
129 }
130 }
131
132 #[test]
133 fn test_compute_stability_coefficient() {
134 let inputs = create_test_inputs();
135 let atmo_params = (0.0, 15.0, 1013.25, 1.0); let stability = compute_stability_coefficient(&inputs, atmo_params);
138
139 println!("Computed stability: {}", stability);
141
142 assert!(stability > 0.0);
144 assert!(stability < 10.0); assert!(stability > 1.0);
148 assert!(stability < 3.0);
149 }
150
151 #[test]
152 fn test_compute_stability_coefficient_zero_cases() {
153 let mut inputs = create_test_inputs();
154 let atmo_params = (0.0, 15.0, 1013.25, 1.0);
155
156 inputs.twist_rate = 0.0;
158 assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
159
160 inputs = create_test_inputs();
162 inputs.bullet_length = 0.0;
163 assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
164
165 inputs = create_test_inputs();
167 inputs.bullet_diameter = 0.0;
168 assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
169 }
170
171 #[test]
172 fn test_compute_stability_coefficient_atmospheric_effects() {
173 let inputs = create_test_inputs();
174
175 let standard_atmo = (0.0, 15.0, 1013.25, 1.0);
177 let standard_stability = compute_stability_coefficient(&inputs, standard_atmo);
178
179 let high_alt_atmo = (3000.0, 5.0, 900.0, 1.0);
181 let high_alt_stability = compute_stability_coefficient(&inputs, high_alt_atmo);
182
183 assert!(high_alt_stability > standard_stability);
185
186 let hot_atmo = (0.0, 35.0, 1013.25, 1.0);
188 let hot_stability = compute_stability_coefficient(&inputs, hot_atmo);
189
190 assert!(hot_stability > standard_stability);
192 }
193
194 #[test]
195 fn test_compute_spin_drift() {
196 let time_s = 1.5;
197 let stability = 2.0;
198 let twist_rate = 10.0;
199
200 let drift_right = compute_spin_drift(time_s, stability, twist_rate, true);
202 assert!(drift_right > 0.0); let drift_left = compute_spin_drift(time_s, stability, twist_rate, false);
206 assert!(drift_left < 0.0); assert!((drift_left + drift_right).abs() < 1e-10); assert!(drift_right.abs() < 0.25); }
212
213 #[test]
214 fn test_compute_spin_drift_zero_cases() {
215 assert_eq!(compute_spin_drift(1.5, 0.0, 10.0, true), 0.0);
217
218 assert_eq!(compute_spin_drift(0.0, 2.0, 10.0, true), 0.0);
220
221 assert_eq!(compute_spin_drift(-1.0, 2.0, 10.0, true), 0.0);
223
224 assert_eq!(compute_spin_drift(1.5, 2.0, 0.0, true), 0.0);
226 }
227
228 #[test]
229 fn test_compute_spin_drift_scaling() {
230 let stability = 2.0;
231 let twist_rate = 10.0;
232
233 let drift_1s = compute_spin_drift(1.0, stability, twist_rate, true);
235 let drift_2s = compute_spin_drift(2.0, stability, twist_rate, true);
236
237 assert!(drift_2s > drift_1s);
239
240 let drift_low_stability = compute_spin_drift(1.5, 1.0, twist_rate, true);
242 let drift_high_stability = compute_spin_drift(1.5, 3.0, twist_rate, true);
243
244 assert!(drift_high_stability > drift_low_stability);
246 }
247}