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 = 0.075; let base_drift = sign * scaling_factor * (stability + 1.2) * time_s.powf(1.83);
106
107 let effective_drift = if let Some(decay) = decay_factor {
109 base_drift * decay.max(0.0).min(1.0)
110 } else {
111 base_drift
112 };
113
114 effective_drift * 0.0254
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 fn create_test_inputs() -> BallisticInputs {
123 BallisticInputs {
124 muzzle_velocity: 823.0, bc_value: 0.5,
126 bullet_mass: 0.0109, bullet_diameter: 0.00782, bullet_length: 0.033, twist_rate: 10.0,
130 ..Default::default()
131 }
132 }
133
134 #[test]
135 fn test_compute_stability_coefficient() {
136 let inputs = create_test_inputs();
137 let atmo_params = (0.0, 15.0, 1013.25, 1.0); let stability = compute_stability_coefficient(&inputs, atmo_params);
140
141 println!("Computed stability: {}", stability);
143
144 assert!(stability > 0.0);
146 assert!(stability < 10.0); assert!(stability > 1.0);
150 assert!(stability < 3.0);
151 }
152
153 #[test]
154 fn test_compute_stability_coefficient_zero_cases() {
155 let mut inputs = create_test_inputs();
156 let atmo_params = (0.0, 15.0, 1013.25, 1.0);
157
158 inputs.twist_rate = 0.0;
160 assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
161
162 inputs = create_test_inputs();
164 inputs.bullet_length = 0.0;
165 assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
166
167 inputs = create_test_inputs();
169 inputs.bullet_diameter = 0.0;
170 assert_eq!(compute_stability_coefficient(&inputs, atmo_params), 0.0);
171 }
172
173 #[test]
174 fn test_compute_stability_coefficient_atmospheric_effects() {
175 let inputs = create_test_inputs();
176
177 let standard_atmo = (0.0, 15.0, 1013.25, 1.0);
179 let standard_stability = compute_stability_coefficient(&inputs, standard_atmo);
180
181 let high_alt_atmo = (3000.0, 5.0, 900.0, 1.0);
183 let high_alt_stability = compute_stability_coefficient(&inputs, high_alt_atmo);
184
185 assert!(high_alt_stability > standard_stability);
187
188 let hot_atmo = (0.0, 35.0, 1013.25, 1.0);
190 let hot_stability = compute_stability_coefficient(&inputs, hot_atmo);
191
192 assert!(hot_stability > standard_stability);
194 }
195
196 #[test]
197 fn test_compute_spin_drift() {
198 let time_s = 1.5;
199 let stability = 2.0;
200 let twist_rate = 10.0;
201
202 let drift_right = compute_spin_drift(time_s, stability, twist_rate, true);
204 assert!(drift_right > 0.0); let drift_left = compute_spin_drift(time_s, stability, twist_rate, false);
208 assert!(drift_left < 0.0); assert!((drift_left + drift_right).abs() < 1e-10); assert!(drift_right.abs() < 0.1); }
214
215 #[test]
216 fn test_compute_spin_drift_zero_cases() {
217 assert_eq!(compute_spin_drift(1.5, 0.0, 10.0, true), 0.0);
219
220 assert_eq!(compute_spin_drift(0.0, 2.0, 10.0, true), 0.0);
222
223 assert_eq!(compute_spin_drift(-1.0, 2.0, 10.0, true), 0.0);
225
226 assert_eq!(compute_spin_drift(1.5, 2.0, 0.0, true), 0.0);
228 }
229
230 #[test]
231 fn test_compute_spin_drift_scaling() {
232 let stability = 2.0;
233 let twist_rate = 10.0;
234
235 let drift_1s = compute_spin_drift(1.0, stability, twist_rate, true);
237 let drift_2s = compute_spin_drift(2.0, stability, twist_rate, true);
238
239 assert!(drift_2s > drift_1s);
241
242 let drift_low_stability = compute_spin_drift(1.5, 1.0, twist_rate, true);
244 let drift_high_stability = compute_spin_drift(1.5, 3.0, twist_rate, true);
245
246 assert!(drift_high_stability > drift_low_stability);
248 }
249}