ballistics_engine/
cluster_bc.rs1#[derive(Debug, Clone, Copy)]
9pub struct ClusterBCDegradation {
10 centroids: [(f64, f64, f64); 4],
12}
13
14impl ClusterBCDegradation {
15 pub fn new() -> Self {
16 Self {
17 centroids: [
19 (0.605, 0.415, 0.613), (0.516, 0.324, 0.643), (0.307, 0.088, 0.336), (0.750, 0.805, 0.505), ],
24 }
25 }
26
27 pub fn predict_cluster(&self, caliber: f64, weight_gr: f64, bc_g1: f64) -> usize {
29 let caliber_norm = (caliber - 0.172) / (0.750 - 0.172);
35 let weight_norm = (weight_gr - 15.0) / (750.0 - 15.0);
36 let bc_norm = (bc_g1 - 0.05) / (1.2 - 0.05);
37
38 let mut min_distance = f64::INFINITY;
40 let mut best_cluster = 0;
41
42 for (i, &(c_cal, c_wt, c_bc)) in self.centroids.iter().enumerate() {
43 let distance = ((caliber_norm - c_cal).powi(2)
44 + (weight_norm - c_wt).powi(2)
45 + (bc_norm - c_bc).powi(2)).sqrt();
46
47 if distance < min_distance {
48 min_distance = distance;
49 best_cluster = i;
50 }
51 }
52
53 best_cluster
54 }
55
56 pub fn get_bc_multiplier(&self, velocity_fps: f64, cluster_id: usize) -> f64 {
58 match cluster_id {
59 0 => {
60 if velocity_fps > 2800.0 {
62 1.0
63 } else if velocity_fps > 2400.0 {
64 0.98 - 0.03 * (2800.0 - velocity_fps) / 400.0
65 } else if velocity_fps > 1800.0 {
66 0.95 - 0.05 * (2400.0 - velocity_fps) / 600.0
67 } else if velocity_fps > 1200.0 {
68 0.90 - 0.10 * (1800.0 - velocity_fps) / 600.0
69 } else {
70 0.80 - 0.05 * (1200.0 - velocity_fps) / 1200.0
71 }
72 },
73 1 => {
74 if velocity_fps > 3000.0 {
76 1.0
77 } else if velocity_fps > 2500.0 {
78 0.99 - 0.02 * (3000.0 - velocity_fps) / 500.0
79 } else if velocity_fps > 2000.0 {
80 0.97 - 0.03 * (2500.0 - velocity_fps) / 500.0
81 } else if velocity_fps > 1500.0 {
82 0.94 - 0.06 * (2000.0 - velocity_fps) / 500.0
83 } else {
84 0.88 - 0.08 * (1500.0 - velocity_fps) / 1500.0
85 }
86 },
87 2 => {
88 if velocity_fps > 3500.0 {
90 1.0
91 } else if velocity_fps > 3000.0 {
92 0.96 - 0.04 * (3500.0 - velocity_fps) / 500.0
93 } else if velocity_fps > 2200.0 {
94 0.92 - 0.08 * (3000.0 - velocity_fps) / 800.0
95 } else if velocity_fps > 1600.0 {
96 0.84 - 0.14 * (2200.0 - velocity_fps) / 600.0
97 } else {
98 0.70 - 0.15 * (1600.0 - velocity_fps) / 1600.0
99 }
100 },
101 3 => {
102 if velocity_fps > 2600.0 {
104 1.0
105 } else if velocity_fps > 2200.0 {
106 0.96 - 0.06 * (2600.0 - velocity_fps) / 400.0
107 } else if velocity_fps > 1700.0 {
108 0.90 - 0.10 * (2200.0 - velocity_fps) / 500.0
109 } else if velocity_fps > 1200.0 {
110 0.80 - 0.15 * (1700.0 - velocity_fps) / 500.0
111 } else {
112 0.65 - 0.10 * (1200.0 - velocity_fps) / 1200.0
113 }
114 },
115 _ => 1.0, }
117 }
118
119 pub fn get_cluster_name(&self, cluster_id: usize) -> &'static str {
121 match cluster_id {
122 0 => "Standard Long-Range",
123 1 => "Low-Drag Specialty",
124 2 => "Light Varmint/Target",
125 3 => "Heavy Magnum",
126 _ => "Unknown",
127 }
128 }
129
130 pub fn apply_correction(&self, bc: f64, caliber: f64, weight_gr: f64, velocity_fps: f64) -> f64 {
132 let cluster_id = self.predict_cluster(caliber, weight_gr, bc);
133 let multiplier = self.get_bc_multiplier(velocity_fps, cluster_id);
134 bc * multiplier
135 }
136}
137
138impl Default for ClusterBCDegradation {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn test_cluster_prediction() {
150 let cluster_bc = ClusterBCDegradation::new();
151
152 let cluster = cluster_bc.predict_cluster(0.308, 168.0, 0.475);
154 assert!(cluster <= 3, "Standard long-range should be in a valid cluster");
155
156 let cluster = cluster_bc.predict_cluster(0.224, 55.0, 0.250);
158 assert_eq!(cluster, 2);
159
160 let cluster = cluster_bc.predict_cluster(0.458, 500.0, 0.295);
162 assert_eq!(cluster, 3);
163 }
164
165 #[test]
166 fn test_bc_multiplier() {
167 let cluster_bc = ClusterBCDegradation::new();
168
169 let mult = cluster_bc.get_bc_multiplier(3000.0, 0);
171 assert!(mult > 0.95 && mult <= 1.0);
172
173 let mult = cluster_bc.get_bc_multiplier(1000.0, 0);
175 assert!(mult < 0.85);
176
177 let mult_high = cluster_bc.get_bc_multiplier(2500.0, 1);
179 let mult_low = cluster_bc.get_bc_multiplier(1500.0, 1);
180 assert!(mult_high > mult_low);
181 }
182
183 #[test]
184 fn test_apply_correction() {
185 let cluster_bc = ClusterBCDegradation::new();
186
187 let bc_original = 0.475;
189 let bc_corrected = cluster_bc.apply_correction(bc_original, 0.308, 168.0, 1500.0);
190 assert!(bc_corrected < bc_original);
191
192 let bc_corrected_high = cluster_bc.apply_correction(bc_original, 0.308, 168.0, 2800.0);
194 assert!(bc_corrected_high >= bc_original * 0.90, "High velocity should have minimal BC reduction");
195 }
196}