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