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))
46 .sqrt();
47
48 if distance < min_distance {
49 min_distance = distance;
50 best_cluster = i;
51 }
52 }
53
54 best_cluster
55 }
56
57 pub fn get_bc_multiplier(&self, velocity_fps: f64, cluster_id: usize) -> f64 {
59 match cluster_id {
60 0 => {
61 if velocity_fps > 2800.0 {
63 1.0
64 } else if velocity_fps > 2400.0 {
65 0.98 - 0.03 * (2800.0 - velocity_fps) / 400.0
66 } else if velocity_fps > 1800.0 {
67 0.95 - 0.05 * (2400.0 - velocity_fps) / 600.0
68 } else if velocity_fps > 1200.0 {
69 0.90 - 0.10 * (1800.0 - velocity_fps) / 600.0
70 } else {
71 0.80 - 0.05 * (1200.0 - velocity_fps) / 1200.0
72 }
73 }
74 1 => {
75 if velocity_fps > 3000.0 {
77 1.0
78 } else if velocity_fps > 2500.0 {
79 0.99 - 0.02 * (3000.0 - velocity_fps) / 500.0
80 } else if velocity_fps > 2000.0 {
81 0.97 - 0.03 * (2500.0 - velocity_fps) / 500.0
82 } else if velocity_fps > 1500.0 {
83 0.94 - 0.06 * (2000.0 - velocity_fps) / 500.0
84 } else {
85 0.88 - 0.08 * (1500.0 - velocity_fps) / 1500.0
86 }
87 }
88 2 => {
89 if velocity_fps > 3500.0 {
91 1.0
92 } else if velocity_fps > 3000.0 {
93 0.96 - 0.04 * (3500.0 - velocity_fps) / 500.0
94 } else if velocity_fps > 2200.0 {
95 0.92 - 0.08 * (3000.0 - velocity_fps) / 800.0
96 } else if velocity_fps > 1600.0 {
97 0.84 - 0.14 * (2200.0 - velocity_fps) / 600.0
98 } else {
99 0.70 - 0.15 * (1600.0 - velocity_fps) / 1600.0
100 }
101 }
102 3 => {
103 if velocity_fps > 2600.0 {
105 1.0
106 } else if velocity_fps > 2200.0 {
107 0.96 - 0.06 * (2600.0 - velocity_fps) / 400.0
108 } else if velocity_fps > 1700.0 {
109 0.90 - 0.10 * (2200.0 - velocity_fps) / 500.0
110 } else if velocity_fps > 1200.0 {
111 0.80 - 0.15 * (1700.0 - velocity_fps) / 500.0
112 } else {
113 0.65 - 0.10 * (1200.0 - velocity_fps) / 1200.0
114 }
115 }
116 _ => 1.0, }
118 }
119
120 pub fn get_cluster_name(&self, cluster_id: usize) -> &'static str {
122 match cluster_id {
123 0 => "Standard Long-Range",
124 1 => "Low-Drag Specialty",
125 2 => "Light Varmint/Target",
126 3 => "Heavy Magnum",
127 _ => "Unknown",
128 }
129 }
130
131 pub fn apply_correction(
133 &self,
134 bc: f64,
135 caliber: f64,
136 weight_gr: f64,
137 velocity_fps: f64,
138 ) -> f64 {
139 let cluster_id = self.predict_cluster(caliber, weight_gr, bc);
140 let multiplier = self.get_bc_multiplier(velocity_fps, cluster_id);
141 bc * multiplier
142 }
143}
144
145impl Default for ClusterBCDegradation {
146 fn default() -> Self {
147 Self::new()
148 }
149}
150
151#[cfg(test)]
152mod tests {
153 use super::*;
154
155 #[test]
156 fn test_cluster_prediction() {
157 let cluster_bc = ClusterBCDegradation::new();
158
159 let cluster = cluster_bc.predict_cluster(0.308, 168.0, 0.475);
161 assert!(
162 cluster <= 3,
163 "Standard long-range should be in a valid cluster"
164 );
165
166 let cluster = cluster_bc.predict_cluster(0.224, 55.0, 0.250);
168 assert_eq!(cluster, 2);
169
170 let cluster = cluster_bc.predict_cluster(0.458, 500.0, 0.295);
172 assert_eq!(cluster, 3);
173 }
174
175 #[test]
176 fn test_bc_multiplier() {
177 let cluster_bc = ClusterBCDegradation::new();
178
179 let mult = cluster_bc.get_bc_multiplier(3000.0, 0);
181 assert!(mult > 0.95 && mult <= 1.0);
182
183 let mult = cluster_bc.get_bc_multiplier(1000.0, 0);
185 assert!(mult < 0.85);
186
187 let mult_high = cluster_bc.get_bc_multiplier(2500.0, 1);
189 let mult_low = cluster_bc.get_bc_multiplier(1500.0, 1);
190 assert!(mult_high > mult_low);
191 }
192
193 #[test]
194 fn test_apply_correction() {
195 let cluster_bc = ClusterBCDegradation::new();
196
197 let bc_original = 0.475;
199 let bc_corrected = cluster_bc.apply_correction(bc_original, 0.308, 168.0, 1500.0);
200 assert!(bc_corrected < bc_original);
201
202 let bc_corrected_high = cluster_bc.apply_correction(bc_original, 0.308, 168.0, 2800.0);
204 assert!(
205 bc_corrected_high >= bc_original * 0.90,
206 "High velocity should have minimal BC reduction"
207 );
208 }
209}