ballistics_engine/reynolds.rs
1//! Reynolds number corrections for low-velocity drag.
2//!
3//! At low velocities, viscous effects become more significant and the drag coefficient
4//! increases. This module provides corrections based on Reynolds number to improve
5//! accuracy for subsonic projectiles and end-of-trajectory calculations.
6
7/// Flow regime classification based on Reynolds number
8#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10enum FlowRegime {
11 Laminar, // Re < 2000
12 Transitional, // 2000 < Re < 5e5
13 Turbulent, // Re > 5e5
14}
15
16/// Calculate Reynolds number for a projectile
17///
18/// Re = ρ × V × L / μ
19///
20/// # Arguments
21/// * `velocity_mps` - Velocity in meters per second
22/// * `diameter_m` - Projectile diameter in meters
23/// * `air_density_kg_m3` - Air density in kg/m³
24/// * `temperature_k` - Temperature in Kelvin
25fn calculate_reynolds_number(
26 velocity_mps: f64,
27 diameter_m: f64,
28 air_density_kg_m3: f64,
29 temperature_k: f64,
30) -> f64 {
31 let mu = calculate_air_viscosity(temperature_k);
32 air_density_kg_m3 * velocity_mps * diameter_m / mu
33}
34
35/// Calculate dynamic viscosity of air using Sutherland's formula
36///
37/// # Arguments
38/// * `temperature_k` - Temperature in Kelvin
39///
40/// # Returns
41/// Dynamic viscosity in Pa·s (kg/m·s)
42fn calculate_air_viscosity(temperature_k: f64) -> f64 {
43 // Reference values
44 const T0: f64 = 273.15; // Reference temperature (K)
45 const MU0: f64 = 1.716e-5; // Reference viscosity at T0 (Pa·s)
46 const S: f64 = 110.4; // Sutherland's constant for air (K)
47
48 // Sutherland's formula
49 MU0 * (T0 + S) / (temperature_k + S) * (temperature_k / T0).powf(1.5)
50}
51
52/// Determine flow regime based on Reynolds number
53#[allow(dead_code)]
54fn get_flow_regime(reynolds_number: f64) -> FlowRegime {
55 if reynolds_number < 2000.0 {
56 FlowRegime::Laminar
57 } else if reynolds_number < 5e5 {
58 FlowRegime::Transitional
59 } else {
60 FlowRegime::Turbulent
61 }
62}
63
64/// Calculate drag coefficient correction factor based on Reynolds number
65///
66/// This correction is most significant at low velocities where viscous effects
67/// dominate. The correction factor is multiplied by the base drag coefficient.
68///
69/// # Arguments
70/// * `reynolds_number` - Reynolds number
71/// * `mach` - Mach number
72/// * `base_cd` - Base drag coefficient from standard model
73///
74/// # Returns
75/// Correction factor (multiply by base Cd)
76fn reynolds_drag_correction(reynolds_number: f64, mach: f64, _base_cd: f64) -> f64 {
77 // Only apply corrections for subsonic flow
78 if mach >= 1.0 {
79 return 1.0;
80 }
81
82 // Reference Reynolds number where standard drag curves are valid
83 const RE_REF: f64 = 1e6;
84
85 if reynolds_number >= RE_REF {
86 return 1.0;
87 }
88
89 // Low Reynolds number correction
90 if reynolds_number < 1000.0 {
91 // Very low Re - Stokes flow region
92 // Cd increases dramatically
93 let correction = 24.0 / reynolds_number + 1.0;
94 // Limit correction to reasonable values
95 correction.min(5.0)
96 } else if reynolds_number < 1e4 {
97 // Low Re - significant viscous effects
98 // Use power law interpolation
99 let n = -0.4;
100 (reynolds_number / RE_REF).powf(n)
101 } else if reynolds_number < 1e5 {
102 // Moderate Re - transitional region
103 let n = -0.2;
104 (reynolds_number / RE_REF).powf(n)
105 } else {
106 // High Re but below reference
107 let n = -0.1;
108 (reynolds_number / RE_REF).powf(n)
109 }
110}
111
112/// Calculate drag coefficient with Reynolds number correction
113///
114/// # Arguments
115/// * `velocity_mps` - Velocity in meters per second
116/// * `diameter_m` - Projectile diameter in meters
117/// * `air_density_kg_m3` - Air density in kg/m³
118/// * `temperature_k` - Temperature in Kelvin
119/// * `mach` - Mach number
120/// * `base_cd` - Base drag coefficient from standard model
121///
122/// # Returns
123/// Tuple of (corrected_cd, reynolds_number)
124fn calculate_corrected_drag(
125 velocity_mps: f64,
126 diameter_m: f64,
127 air_density_kg_m3: f64,
128 temperature_k: f64,
129 mach: f64,
130 base_cd: f64,
131) -> (f64, f64) {
132 let re = calculate_reynolds_number(velocity_mps, diameter_m, air_density_kg_m3, temperature_k);
133 let correction = reynolds_drag_correction(re, mach, base_cd);
134 let corrected_cd = base_cd * correction;
135
136 (corrected_cd, re)
137}
138
139/// Apply Reynolds number correction to drag coefficient (convenience function)
140///
141/// # Arguments
142/// * `base_cd` - Base drag coefficient from G1/G7 model
143/// * `velocity_mps` - Velocity in meters per second
144/// * `diameter_inches` - Bullet diameter in inches
145/// * `air_density_kg_m3` - Air density in kg/m³
146/// * `temperature_c` - Temperature in Celsius
147/// * `mach` - Mach number
148///
149/// # Returns
150/// Corrected drag coefficient
151pub fn apply_reynolds_correction(
152 base_cd: f64,
153 velocity_mps: f64,
154 diameter_inches: f64,
155 air_density_kg_m3: f64,
156 temperature_c: f64,
157 mach: f64,
158) -> f64 {
159 // Convert units
160 let diameter_m = diameter_inches * 0.0254; // inches to meters
161 let temperature_k = temperature_c + 273.15; // Celsius to Kelvin
162
163 // Skip correction for very high velocities
164 if velocity_mps > 1000.0 || mach > 3.0 {
165 return base_cd;
166 }
167
168 // Calculate and apply correction
169 let (corrected_cd, _) = calculate_corrected_drag(
170 velocity_mps,
171 diameter_m,
172 air_density_kg_m3,
173 temperature_k,
174 mach,
175 base_cd,
176 );
177
178 corrected_cd
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184
185 #[test]
186 fn test_air_viscosity() {
187 // Test at standard temperature (15°C = 288.15K)
188 let mu = calculate_air_viscosity(288.15);
189 assert!((mu - 1.789e-5).abs() < 1e-7);
190 }
191
192 #[test]
193 fn test_reynolds_number() {
194 let re = calculate_reynolds_number(100.0, 0.00782, 1.225, 288.15);
195 // Should be around 5.4e4
196 assert!(re > 5e4 && re < 6e4);
197 }
198
199 #[test]
200 fn test_flow_regime() {
201 assert_eq!(get_flow_regime(1000.0), FlowRegime::Laminar);
202 assert_eq!(get_flow_regime(1e5), FlowRegime::Transitional);
203 assert_eq!(get_flow_regime(1e6), FlowRegime::Turbulent);
204 }
205
206 #[test]
207 fn test_reynolds_correction() {
208 // Test no correction for supersonic
209 let correction = reynolds_drag_correction(1e5, 1.5, 0.5);
210 assert_eq!(correction, 1.0);
211
212 // Test correction for low Re
213 let correction = reynolds_drag_correction(1e4, 0.5, 0.5);
214 assert!(correction > 1.0);
215
216 // Test extreme low Re
217 let correction = reynolds_drag_correction(100.0, 0.1, 0.5);
218 // Just check that there's significant correction
219 assert!(correction > 1.0);
220 assert!(correction <= 5.0); // Should be capped
221 }
222}
223
224// Removed Python-specific function