struct_compression_analyzer/brute_force/
brute_force_custom.rs1use super::{
2 find_optimal_coefficients_for_metrics_parallel, BruteForceComparisonMetrics, BruteForceConfig,
3 OptimizationResult,
4};
5use crate::results::analysis_results::AnalysisResults;
6
7#[derive(Debug, Clone)]
9pub struct CustomComparisonOptimizationResult {
10 pub baseline: OptimizationResult,
12 pub comparisons: Box<[OptimizationResult]>,
14}
15
16pub fn find_optimal_custom_result_coefficients(
25 individual_results: &mut [AnalysisResults],
26 config: Option<&BruteForceConfig>,
27) -> Vec<(String, CustomComparisonOptimizationResult)> {
28 let default_config = BruteForceConfig::default();
30 let config = config.unwrap_or(&default_config);
31
32 let mut results: Vec<(String, CustomComparisonOptimizationResult)> = Vec::new();
33
34 for (comparison_idx, comparison) in individual_results[0].custom_comparisons.iter().enumerate()
35 {
36 results.push((
37 comparison.name.clone(),
38 find_optimal_custom_result_coefficients_for_comparison(
39 comparison_idx,
40 config,
41 individual_results,
42 ),
43 ));
44 }
45
46 results
47}
48
49#[allow(clippy::needless_range_loop)]
51fn find_optimal_custom_result_coefficients_for_comparison(
52 comparison_idx: usize,
53 config: &BruteForceConfig,
54 original_results: &[AnalysisResults], ) -> CustomComparisonOptimizationResult {
56 let first_result = &original_results[0].custom_comparisons[comparison_idx];
58 let num_comparisons = first_result.group_metrics.len();
59
60 let baseline_metrics = extract_baseline_metrics(comparison_idx, original_results);
62 let baseline_best = find_optimal_coefficients_for_metrics_parallel(&baseline_metrics, config);
63
64 let mut comparison_bests = Vec::with_capacity(num_comparisons);
66
67 for group_idx in 0..num_comparisons {
69 let group_metrics =
70 extract_comparison_group_metrics(comparison_idx, group_idx, original_results);
71
72 let group_best = find_optimal_coefficients_for_metrics_parallel(&group_metrics, config);
74 comparison_bests.push(group_best);
75 }
76
77 CustomComparisonOptimizationResult {
78 baseline: baseline_best,
79 comparisons: comparison_bests.into_boxed_slice(),
80 }
81}
82
83fn extract_baseline_metrics(
86 comparison_idx: usize,
87 original_results: &[AnalysisResults], ) -> Box<[BruteForceComparisonMetrics]> {
89 original_results
90 .iter()
91 .map(|result| {
92 result.custom_comparisons[comparison_idx]
93 .baseline_metrics
94 .into()
95 })
96 .collect()
97}
98
99fn extract_comparison_group_metrics(
108 comparison_idx: usize,
109 group_idx: usize,
110 original_results: &[AnalysisResults], ) -> Box<[BruteForceComparisonMetrics]> {
112 original_results
113 .iter()
114 .map(|result| result.custom_comparisons[comparison_idx].group_metrics[group_idx].into())
115 .collect()
116}
117
118pub fn print_optimization_results<W: std::io::Write>(
125 writer: &mut W,
126 results: &[(String, CustomComparisonOptimizationResult)],
127) -> std::io::Result<()> {
128 writeln!(
129 writer,
130 "\n=== Custom Comparison Parameter Optimization Results ==="
131 )?;
132 writeln!(
133 writer,
134 "Comparison Name | Group | LZ Multiplier | Entropy Multiplier |"
135 )?;
136 writeln!(
137 writer,
138 "----------------|-------|---------------|--------------------|"
139 )?;
140
141 for (name, result) in results {
142 writeln!(
143 writer,
144 "{:<16}|{:<7}|{:<15.3}|{:<20.3}|",
145 name, "BASE", result.baseline.lz_match_multiplier, result.baseline.entropy_multiplier
146 )?;
147
148 for (i, comparison) in result.comparisons.iter().enumerate() {
149 writeln!(
150 writer,
151 "{:<16}|{:<7}|{:<15.3}|{:<20.3}|",
152 "", i, comparison.lz_match_multiplier, comparison.entropy_multiplier
153 )?;
154 }
155 }
156
157 Ok(())
158}
159
160#[cfg(test)]
161mod tests {
162 use ahash::AHashMap;
163
164 use super::*;
165 use crate::{
166 brute_force::calculate_error_for_bruteforce_metrics,
167 comparison::{
168 compare_groups::GroupComparisonResult, GroupComparisonMetrics, GroupDifference,
169 },
170 schema::Metadata,
171 };
172
173 #[allow(clippy::too_many_arguments)]
175 fn create_mock_group_comparison_result(
176 name: &str,
177 baseline_lz_matches: u64,
178 baseline_entropy: f64,
179 baseline_zstd_size: u64,
180 baseline_original_size: u64,
181 comparison_group_count: usize,
182 comparison_lz_matches: u64,
183 comparison_entropy: f64,
184 comparison_zstd_size: u64,
185 comparison_original_size: u64,
186 ) -> GroupComparisonResult {
187 let baseline_metrics = GroupComparisonMetrics {
188 lz_matches: baseline_lz_matches,
189 entropy: baseline_entropy,
190 estimated_size: 0, zstd_size: baseline_zstd_size,
192 original_size: baseline_original_size,
193 };
194
195 let mut group_names = Vec::with_capacity(comparison_group_count);
196 let mut group_metrics = Vec::with_capacity(comparison_group_count);
197 let mut differences = Vec::with_capacity(comparison_group_count);
198
199 for i in 0..comparison_group_count {
200 group_names.push(format!("group_{}", i));
201
202 let metrics = GroupComparisonMetrics {
203 lz_matches: comparison_lz_matches,
204 entropy: comparison_entropy,
205 estimated_size: 0, zstd_size: comparison_zstd_size,
207 original_size: comparison_original_size,
208 };
209
210 group_metrics.push(metrics);
211 differences.push(GroupDifference::from_metrics(&baseline_metrics, &metrics));
212 }
213
214 GroupComparisonResult {
215 name: name.to_string(),
216 description: "Test comparison".to_string(),
217 baseline_metrics,
218 group_names,
219 group_metrics,
220 differences,
221 }
222 }
223
224 #[allow(clippy::too_many_arguments)]
226 fn create_mock_analysis_results_with_custom(
227 comparison_name: &str,
228 baseline_lz_matches: u64,
229 baseline_entropy: f64,
230 baseline_zstd_size: u64,
231 baseline_original_size: u64,
232 comparison_0_group_count: usize,
233 comparison_0_lz_matches: u64,
234 comparison_0_entropy: f64,
235 comparison_0_zstd_size: u64,
236 comparison_0_original_size: u64,
237 ) -> AnalysisResults {
238 let custom_comparison = create_mock_group_comparison_result(
239 comparison_name,
240 baseline_lz_matches,
241 baseline_entropy,
242 baseline_zstd_size,
243 baseline_original_size,
244 comparison_0_group_count,
245 comparison_0_lz_matches,
246 comparison_0_entropy,
247 comparison_0_zstd_size,
248 comparison_0_original_size,
249 );
250
251 AnalysisResults {
252 schema_metadata: Metadata {
253 name: "Test Schema".to_string(),
254 description: "Test Schema Description".to_string(),
255 },
256 file_entropy: 0.0,
257 file_lz_matches: 0,
258 zstd_file_size: 0,
259 original_size: 0,
260 per_field: AHashMap::new(),
261 split_comparisons: Vec::new(),
262 custom_comparisons: vec![custom_comparison],
263 }
264 }
265
266 #[test]
267 fn can_find_optimal_custom_result_coefficients() {
268 let analysis_results1 = create_mock_analysis_results_with_custom(
270 "test_comparison",
271 100, 1.0,
273 110,
274 1000,
275 2, 210,
277 1.6,
278 230,
279 1000,
280 );
281
282 let config = BruteForceConfig::default();
283
284 let mut original_results = vec![analysis_results1];
286 let optimal_results =
287 find_optimal_custom_result_coefficients(&mut original_results, Some(&config));
288
289 assert_eq!(optimal_results.len(), 1);
291 assert_eq!(optimal_results[0].0, "test_comparison");
292
293 assert!(optimal_results[0].1.baseline.lz_match_multiplier >= config.min_lz_multiplier);
296 assert!(optimal_results[0].1.baseline.lz_match_multiplier <= config.max_lz_multiplier);
297 assert!(optimal_results[0].1.baseline.entropy_multiplier >= config.min_entropy_multiplier);
298 assert!(optimal_results[0].1.baseline.entropy_multiplier <= config.max_entropy_multiplier);
299
300 assert_eq!(optimal_results[0].1.comparisons.len(), 2);
302
303 let comparisons = &optimal_results[0].1.comparisons;
305 assert!(comparisons[0].lz_match_multiplier >= config.min_lz_multiplier);
306 assert!(comparisons[0].lz_match_multiplier <= config.max_lz_multiplier);
307 assert!(comparisons[0].entropy_multiplier >= config.min_entropy_multiplier);
308 assert!(comparisons[0].entropy_multiplier <= config.max_entropy_multiplier);
309
310 assert!(comparisons[1].lz_match_multiplier >= config.min_lz_multiplier);
312 assert!(comparisons[1].lz_match_multiplier <= config.max_lz_multiplier);
313 assert!(comparisons[1].entropy_multiplier >= config.min_entropy_multiplier);
314 assert!(comparisons[1].entropy_multiplier <= config.max_entropy_multiplier);
315
316 let baseline_metrics = extract_baseline_metrics(0, &original_results);
318 let baseline_error = calculate_error_for_bruteforce_metrics(
319 &baseline_metrics,
320 optimal_results[0].1.baseline.lz_match_multiplier,
321 optimal_results[0].1.baseline.entropy_multiplier,
322 );
323
324 assert!(
326 baseline_error < 5.0,
327 "Baseline error {} should be less than 5.0",
328 baseline_error
329 );
330
331 for i in 0..2 {
333 let group_metrics = extract_comparison_group_metrics(0, i, &original_results);
334 let group_error = calculate_error_for_bruteforce_metrics(
335 &group_metrics,
336 optimal_results[0].1.comparisons[i].lz_match_multiplier,
337 optimal_results[0].1.comparisons[i].entropy_multiplier,
338 );
339
340 assert!(
341 group_error < 5.0,
342 "Comparison group {} error {} should be less than 5.0",
343 i,
344 group_error
345 );
346 }
347 }
348
349 #[test]
350 fn handles_empty_custom_results() {
351 let analysis_results = AnalysisResults::default();
352
353 let mut original_results = vec![analysis_results];
355 let optimal_results = find_optimal_custom_result_coefficients(&mut original_results, None);
356
357 assert!(optimal_results.is_empty());
359 }
360}