diff --git a/examples/basic_usage.rs b/examples/basic_usage.rs index 85145bdd..368896c7 100644 --- a/examples/basic_usage.rs +++ b/examples/basic_usage.rs @@ -45,7 +45,7 @@ fn main() -> Result<()> { let device = Device::Cpu; println!("Starting optimization of 2D Rosenbrock function"); - println!("Initial point: {:?}", initial_point); + println!("Initial point: {initial_point:?}"); println!( "Initial value: {:.6}", problem.evaluate_f64(&initial_point)? @@ -63,15 +63,12 @@ fn main() -> Result<()> { // Print progress if iteration % 10 == 0 { let f_val = problem.evaluate_f64(&initial_point)?; - println!( - "Iteration {}: f = {:.6}, ||∇f|| = {:.6}", - iteration, f_val, grad_norm - ); + println!("Iteration {iteration}: f = {f_val:.6}, ||∇f|| = {grad_norm:.6}"); } // Check convergence if grad_norm < 1e-6 { - println!("Converged! Gradient norm: {:.2e}", grad_norm); + println!("Converged! Gradient norm: {grad_norm:.2e}"); break; } @@ -127,10 +124,10 @@ fn main() -> Result<()> { let final_grad_norm = final_gradient.iter().map(|g| g * g).sum::().sqrt(); println!("\nOptimization completed!"); - println!("Final point: {:?}", initial_point); - println!("Final value: {:.6}", final_value); - println!("Final gradient norm: {:.2e}", final_grad_norm); - println!("Total iterations: {}", iteration); + println!("Final point: {initial_point:?}"); + println!("Final value: {final_value:.6}"); + println!("Final gradient norm: {final_grad_norm:.2e}"); + println!("Total iterations: {iteration}"); // Compare with known optimum let optimum = vec![1.0, 1.0]; @@ -141,7 +138,7 @@ fn main() -> Result<()> { .sum::() .sqrt(); - println!("Distance to optimum [1, 1]: {:.6}", distance_to_optimum); + println!("Distance to optimum [1, 1]: {distance_to_optimum:.6}"); if distance_to_optimum < 1e-3 { println!("✓ Successfully found the global minimum!"); diff --git a/examples/custom_problem.rs b/examples/custom_problem.rs index 7f30b7f2..679e62a0 100644 --- a/examples/custom_problem.rs +++ b/examples/custom_problem.rs @@ -59,7 +59,7 @@ impl QuadraticProblem { } Self { - name: format!("Quadratic{}D_Cond{:.1}", dimension, condition_number), + name: format!("Quadratic{dimension}D_Cond{condition_number:.1}"), dimension, matrix_a, vector_b, @@ -140,7 +140,7 @@ impl DifferentiableFunction for QuadraticProblem { // Evaluate using f64 implementation let result = self .evaluate_f64(&x) - .map_err(|e| candle_core::Error::Msg(format!("Evaluation error: {}", e)))?; + .map_err(|e| candle_core::Error::Msg(format!("Evaluation error: {e}")))?; Ok(result) } fn gradient(&self, params: &[Tensor]) -> candle_core::Result> { @@ -150,7 +150,7 @@ impl DifferentiableFunction for QuadraticProblem { // Compute gradient using f64 implementation let grad = self .gradient_f64(&x) - .map_err(|e| candle_core::Error::Msg(format!("Gradient error: {}", e)))?; + .map_err(|e| candle_core::Error::Msg(format!("Gradient error: {e}")))?; // Convert back to tensors grad.iter() .map(|&g| Tensor::from_slice(&[g], (1,), &Device::Cpu)) @@ -196,8 +196,8 @@ fn main() -> Result<()> { ); let qqn_error = (qqn_result.1 - problem.optimal_value().unwrap()).abs(); let lbfgs_error = (lbfgs_result.1 - problem.optimal_value().unwrap()).abs(); - println!("QQN error: {:.2e}", qqn_error); - println!("L-BFGS error: {:.2e}", lbfgs_error); + println!("QQN error: {qqn_error:.2e}"); + println!("L-BFGS error: {lbfgs_error:.2e}"); if qqn_result.0 < lbfgs_result.0 { println!("✓ QQN converged faster!"); } else if qqn_result.0 == lbfgs_result.0 { @@ -222,7 +222,7 @@ fn run_optimizer( .map_err(|e| anyhow::anyhow!("Failed to create tensors: {}", e))?; let mut iteration = 0; let max_iterations = 1000; - println!("Starting {} optimization...", name); + println!("Starting {name} optimization..."); while iteration < max_iterations { // Convert tensors back to f64 for convergence checking let x: Vec = params @@ -245,10 +245,7 @@ fn run_optimizer( .collect::>>() .map_err(|e| anyhow::anyhow!("Failed to extract values: {}", e))?; let f_val = problem.evaluate_f64(&x)?; - println!( - " Iteration {}: f = {:.6}, ||∇f|| = {:.2e}", - iteration, f_val, grad_norm - ); + println!(" Iteration {iteration}: f = {f_val:.6}, ||∇f|| = {grad_norm:.2e}"); } } // Convert final parameters back to f64 for evaluation diff --git a/src/analysis/plotting.rs b/src/analysis/plotting.rs index d271211f..3a64cafc 100644 --- a/src/analysis/plotting.rs +++ b/src/analysis/plotting.rs @@ -29,7 +29,7 @@ fn has_font_support() -> bool { }); let ok = result.is_ok() && result.unwrap().is_ok(); - println!("[Plotting] Font support available: {}", ok); + println!("[Plotting] Font support available: {ok}"); ok } @@ -172,7 +172,7 @@ impl PlottingEngine { } else { "Unknown panic in convergence_plot".to_string() }; - eprintln!("[Plotting] Panic caught in convergence_plot: {}", msg); + eprintln!("[Plotting] Panic caught in convergence_plot: {msg}"); Err(anyhow::anyhow!("Plotting failed due to panic: {}", msg)) } } @@ -201,7 +201,7 @@ impl PlottingEngine { for trace in &sanitized_traces { optimizer_traces .entry(trace.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(trace); } // Create a sorted list of unique optimizer names for consistent color assignment @@ -232,10 +232,7 @@ impl PlottingEngine { }); // Check if we have valid bounds if !min_obj.is_finite() || !max_obj.is_finite() || min_obj >= max_obj { - eprintln!( - "[Plotting] Warning: Invalid objective value bounds: [{}, {}]", - min_obj, max_obj - ); + eprintln!("[Plotting] Warning: Invalid objective value bounds: [{min_obj}, {max_obj}]"); return Ok(()); } @@ -334,7 +331,7 @@ impl PlottingEngine { } root.present()?; - println!("Convergence plot saved to: {}", output_path); + println!("Convergence plot saved to: {output_path}"); Ok(()) } @@ -358,7 +355,7 @@ impl PlottingEngine { } else { "Unknown panic in log_convergence_plot".to_string() }; - eprintln!("[Plotting] Panic caught in log_convergence_plot: {}", msg); + eprintln!("[Plotting] Panic caught in log_convergence_plot: {msg}"); Err(anyhow::anyhow!("Plotting failed due to panic: {}", msg)) } } @@ -387,7 +384,7 @@ impl PlottingEngine { for trace in &sanitized_traces { optimizer_traces .entry(trace.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(trace); } // Create a sorted list of unique optimizer names for consistent color assignment @@ -428,11 +425,11 @@ impl PlottingEngine { } // Safely calculate log bounds with overflow protection - let safe_min = min_positive_obj.max(1e-12).min(1e10); - let safe_max = max_obj.max(1.0).min(1e10); + let safe_min = min_positive_obj.clamp(1e-12, 1e10); + let safe_max = max_obj.clamp(1.0, 1e10); - let log_min = safe_min.log10().max(-15.0).min(15.0); - let log_max = safe_max.log10().max(-15.0).min(15.0); + let log_min = safe_min.log10().clamp(-15.0, 15.0); + let log_max = safe_max.log10().clamp(-15.0, 15.0); // Ensure we have a valid range let (final_log_min, final_log_max) = if (log_max - log_min).abs() < 1e-10 { @@ -472,8 +469,8 @@ impl PlottingEngine { .zip(trace.objective_values.iter()) .filter(|(_, &obj_val)| obj_val > 0.0 && obj_val.is_finite()) .map(|(&eval_count, &obj_val)| { - let safe_val = obj_val.max(1e-15).min(1e10); - let log_val = safe_val.log10().max(-15.0).min(15.0); + let safe_val = obj_val.clamp(1e-15, 1e10); + let log_val = safe_val.log10().clamp(-15.0, 15.0); (eval_count, log_val) }) .collect(); @@ -499,7 +496,7 @@ impl PlottingEngine { } root.present()?; - println!("Log convergence plot saved to: {}", output_path); + println!("Log convergence plot saved to: {output_path}"); Ok(()) } @@ -519,7 +516,7 @@ impl PlottingEngine { } else { "Unknown panic in performance_comparison".to_string() }; - eprintln!("[Plotting] Panic caught in performance_comparison: {}", msg); + eprintln!("[Plotting] Panic caught in performance_comparison: {msg}"); Err(anyhow::anyhow!("Plotting failed due to panic: {}", msg)) } } @@ -545,9 +542,9 @@ impl PlottingEngine { for result in &results.results { problem_results .entry(result.problem_name.clone()) - .or_insert_with(HashMap::new) + .or_default() .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(if result.final_value.is_finite() { result.final_value } else { @@ -680,7 +677,7 @@ impl PlottingEngine { } root.present()?; - println!("Performance comparison saved to: {}", output_path); + println!("Performance comparison saved to: {output_path}"); Ok(()) } @@ -700,7 +697,7 @@ impl PlottingEngine { } else { "Unknown panic in performance_boxplot".to_string() }; - eprintln!("[Plotting] Panic caught in performance_boxplot: {}", msg); + eprintln!("[Plotting] Panic caught in performance_boxplot: {msg}"); Err(anyhow::anyhow!("Plotting failed due to panic: {}", msg)) } } @@ -722,7 +719,7 @@ impl PlottingEngine { for result in &results.results { optimizer_results .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(if result.final_value.is_finite() { result.final_value } else { @@ -741,10 +738,7 @@ impl PlottingEngine { // Filter out non-finite values values.retain(|&v| v.is_finite()); if values.is_empty() { - eprintln!( - "[Plotting] Warning: No finite values for optimizer {}", - optimizer - ); + eprintln!("[Plotting] Warning: No finite values for optimizer {optimizer}"); continue; } @@ -819,8 +813,8 @@ impl PlottingEngine { // Draw box (Q1 to Q3) chart.draw_series(std::iter::once(Rectangle::new( [ - (x as f64 - box_width / 2.0, data.q1), - (x as f64 + box_width / 2.0, data.q3), + (x - box_width / 2.0, data.q1), + (x + box_width / 2.0, data.q3), ], BLUE.mix(0.3).filled(), )))?; @@ -828,21 +822,21 @@ impl PlottingEngine { // Draw median line chart.draw_series(std::iter::once(PathElement::new( vec![ - (x as f64 - box_width / 2.0, data.median), - (x as f64 + box_width / 2.0, data.median), + (x - box_width / 2.0, data.median), + (x + box_width / 2.0, data.median), ], RED.stroke_width(2), )))?; // Draw whiskers chart.draw_series(std::iter::once(PathElement::new( - vec![(x as f64, data.min), (x as f64, data.q1)], - &BLACK, + vec![(x, data.min), (x, data.q1)], + BLACK, )))?; chart.draw_series(std::iter::once(PathElement::new( - vec![(x as f64, data.q3), (x as f64, data.max)], - &BLACK, + vec![(x, data.q3), (x, data.max)], + BLACK, )))?; // Draw whisker caps let cap_width = box_width / 3.0; @@ -851,14 +845,14 @@ impl PlottingEngine { (x - cap_width / 2.0, data.min), (x + cap_width / 2.0, data.min), ], - &BLACK, + BLACK, )))?; chart.draw_series(std::iter::once(PathElement::new( vec![ (x - cap_width / 2.0, data.max), (x + cap_width / 2.0, data.max), ], - &BLACK, + BLACK, )))?; // Add optimizer names as x-axis labels if fonts are available @@ -878,7 +872,7 @@ impl PlottingEngine { } root.present()?; - println!("Performance boxplot saved to: {}", output_path); + println!("Performance boxplot saved to: {output_path}"); Ok(()) } @@ -907,7 +901,7 @@ impl PlottingEngine { } fs::write(&csv_path, csv_content) .map_err(|e| anyhow::anyhow!("Failed to write CSV file {}: {}", csv_path, e))?; - println!("Convergence data exported to: {}", csv_path); + println!("Convergence data exported to: {csv_path}"); Ok(()) } /// Export log convergence plot data to CSV @@ -928,8 +922,8 @@ impl PlottingEngine { if !obj_value.is_finite() || *obj_value <= 0.0 { continue; } - let safe_val = obj_value.max(1e-15).min(1e10); - let log_val = safe_val.log10().max(-15.0).min(15.0); + let safe_val = obj_value.clamp(1e-15, 1e10); + let log_val = safe_val.log10().clamp(-15.0, 15.0); csv_content.push_str(&format!( "{},{},{:.6e},{:.6e}\n", trace.optimizer_name, eval_count, obj_value, log_val @@ -938,7 +932,7 @@ impl PlottingEngine { } fs::write(&csv_path, csv_content) .map_err(|e| anyhow::anyhow!("Failed to write CSV file {}: {}", csv_path, e))?; - println!("Log convergence data exported to: {}", csv_path); + println!("Log convergence data exported to: {csv_path}"); Ok(()) } /// Export performance comparison data to CSV @@ -954,9 +948,9 @@ impl PlottingEngine { for result in &results.results { problem_results .entry(result.problem_name.clone()) - .or_insert_with(HashMap::new) + .or_default() .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result.final_value); } for (problem, optimizers) in problem_results { @@ -1004,22 +998,14 @@ impl PlottingEngine { .count(); let success_rate = success_count as f64 / optimizer_results.len() as f64; csv_content.push_str(&format!( - "{},{},{:.6e},{:.6e},{:.1},{:.1},{:.1},{:.3}\n", - problem, - optimizer, - mean_final, - std_final, - mean_iterations, - mean_function_evals, - mean_gradient_evals, - success_rate + "{problem},{optimizer},{mean_final:.6e},{std_final:.6e},{mean_iterations:.1},{mean_function_evals:.1},{mean_gradient_evals:.1},{success_rate:.3}\n" )); } } } fs::write(&csv_path, csv_content) .map_err(|e| anyhow::anyhow!("Failed to write CSV file {}: {}", csv_path, e))?; - println!("Performance comparison data exported to: {}", csv_path); + println!("Performance comparison data exported to: {csv_path}"); Ok(()) } /// Export performance boxplot data to CSV @@ -1035,7 +1021,7 @@ impl PlottingEngine { for result in &results.results { optimizer_results .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result.final_value); } for (optimizer, mut values) in optimizer_results { @@ -1050,18 +1036,17 @@ impl PlottingEngine { // Convert all values to a comma-separated string let all_values_str = values .iter() - .map(|v| format!("{:.6e}", v)) + .map(|v| format!("{v:.6e}")) .collect::>() .join(";"); // Use semicolon to avoid CSV parsing issues csv_content.push_str(&format!( - "{},{:.6e},{:.6e},{:.6e},{:.6e},{:.6e},\"{}\"\n", - optimizer, min, q1, median, q3, max, all_values_str + "{optimizer},{min:.6e},{q1:.6e},{median:.6e},{q3:.6e},{max:.6e},\"{all_values_str}\"\n" )); } } fs::write(&csv_path, csv_content) .map_err(|e| anyhow::anyhow!("Failed to write CSV file {}: {}", csv_path, e))?; - println!("Performance boxplot data exported to: {}", csv_path); + println!("Performance boxplot data exported to: {csv_path}"); Ok(()) } /// Helper function to add legend for unique optimizers diff --git a/src/analysis/reporting.rs b/src/analysis/reporting.rs index ff6f0315..8bec20de 100644 --- a/src/analysis/reporting.rs +++ b/src/analysis/reporting.rs @@ -34,6 +34,12 @@ pub struct ReportSection { /// LaTeX exporter pub struct LaTeXExporter; +impl Default for LaTeXExporter { + fn default() -> Self { + Self::new() + } +} + impl LaTeXExporter { pub fn new() -> Self { Self @@ -50,6 +56,12 @@ impl LaTeXExporter { /// CSV exporter pub struct CSVExporter; +impl Default for CSVExporter { + fn default() -> Self { + Self::new() + } +} + impl CSVExporter { pub fn new() -> Self { Self diff --git a/src/analysis/statistics.rs b/src/analysis/statistics.rs index 0846dea5..b6d5fdbb 100644 --- a/src/analysis/statistics.rs +++ b/src/analysis/statistics.rs @@ -200,7 +200,7 @@ impl StatisticalAnalysis { for result in &results.results { grouped_results .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } @@ -296,14 +296,14 @@ impl StatisticalAnalysis { for result in results_a { problems_a .entry(result.problem_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } for result in results_b { problems_b .entry(result.problem_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } @@ -413,7 +413,7 @@ impl StatisticalAnalysis { for result in &results.results { grouped_results .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } @@ -528,7 +528,7 @@ impl StatisticalAnalysis { for result in &results.results { grouped_results .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } @@ -770,7 +770,7 @@ impl StatisticalAnalysis { } else { t_dist.cdf(abs_t) }; - if cdf_result.is_finite() && cdf_result >= 0.0 && cdf_result <= 1.0 { + if cdf_result.is_finite() && (0.0..=1.0).contains(&cdf_result) { 2.0 * (1.0 - cdf_result) } else { 0.5 // Fallback for invalid CDF result diff --git a/src/benchmarks/analytic_functions.rs b/src/benchmarks/analytic_functions.rs index 82a26b81..2d92a534 100644 --- a/src/benchmarks/analytic_functions.rs +++ b/src/benchmarks/analytic_functions.rs @@ -195,7 +195,7 @@ impl StyblinskiTangFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("StyblinskiTang_{}D", dimension), + name: format!("StyblinskiTang_{dimension}D"), } } } @@ -260,7 +260,7 @@ impl MichalewiczFunction { Self { dimension, m, - name: format!("Michalewicz_{}D_m{}", dimension, m), + name: format!("Michalewicz_{dimension}D_m{m}"), } } } @@ -358,7 +358,7 @@ impl RosenbrockFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("Rosenbrock_{}D", dimension), + name: format!("Rosenbrock_{dimension}D"), } } } @@ -431,7 +431,7 @@ impl RastriginFunction { Self { dimension, a: 10.0, - name: format!("Rastrigin_{}D", dimension), + name: format!("Rastrigin_{dimension}D"), } } } @@ -495,7 +495,7 @@ impl SphereFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("Sphere_{}D", dimension), + name: format!("Sphere_{dimension}D"), } } } @@ -719,7 +719,7 @@ impl AckleyFunction { a, b, c, - name: format!("Ackley_{}D_a{}_b{}_c{:0.2e}", dimension, a, b, c), + name: format!("Ackley_{dimension}D_a{a}_b{b}_c{c:0.2e}"), } } } @@ -808,7 +808,7 @@ impl GriewankFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("Griewank_{}D", dimension), + name: format!("Griewank_{dimension}D"), } } } @@ -891,7 +891,7 @@ impl SchwefelFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("Schwefel_{}D", dimension), + name: format!("Schwefel_{dimension}D"), } } } @@ -965,7 +965,7 @@ impl LevyFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("Levy_{}D", dimension), + name: format!("Levy_{dimension}D"), } } } @@ -1075,7 +1075,7 @@ impl ZakharovFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("Zakharov_{}D", dimension), + name: format!("Zakharov_{dimension}D"), } } } @@ -1152,7 +1152,7 @@ impl IllConditionedRosenbrock { Self { dimension, alpha, - name: format!("IllConditionedRosenbrock_{}D_alpha{}", dimension, alpha), + name: format!("IllConditionedRosenbrock_{dimension}D_alpha{alpha}"), } } } @@ -1211,7 +1211,7 @@ impl TrigonometricFunction { pub fn new(dimension: usize) -> Self { Self { dimension, - name: format!("Trigonometric_{}D", dimension), + name: format!("Trigonometric_{dimension}D"), } } } @@ -1235,6 +1235,7 @@ impl OptimizationProblem for TrigonometricFunction { let n = self.dimension as f64; let cos_sum: f64 = x.iter().map(|&xi| xi.cos()).sum(); let mut total = 0.0; + #[allow(clippy::needless_range_loop)] for i in 0..self.dimension { let term = n - cos_sum + (i + 1) as f64 * (1.0 - x[i].cos() - x[i].sin()); total += term * term; @@ -1281,7 +1282,7 @@ impl PenaltyFunctionI { Self { dimension, alpha, - name: format!("PenaltyI_{}D_alpha{:.0e}", dimension, alpha), + name: format!("PenaltyI_{dimension}D_alpha{alpha:.0e}"), } } } @@ -1347,7 +1348,7 @@ impl BarrierFunction { Self { dimension, mu, - name: format!("Barrier_{}D_mu{}", dimension, mu), + name: format!("Barrier_{dimension}D_mu{mu}"), } } } @@ -1409,7 +1410,7 @@ impl NoisySphere { dimension, noise_level, seed, - name: format!("NoisySphere_{}D_sigma{}", dimension, noise_level), + name: format!("NoisySphere_{dimension}D_sigma{noise_level}"), } } } @@ -1484,7 +1485,7 @@ impl SparseRosenbrock { } Self { dimension, - name: format!("SparseRosenbrock_{}D", dimension), + name: format!("SparseRosenbrock_{dimension}D"), } } } @@ -1550,10 +1551,7 @@ impl SparseQuadratic { Self { dimension, sparsity_pattern: sparsity_pattern.clone(), - name: format!( - "SparseQuadratic_{}D_pattern{:?}", - dimension, sparsity_pattern - ), + name: format!("SparseQuadratic_{dimension}D_pattern{sparsity_pattern:?}"), } } } @@ -1679,7 +1677,7 @@ mod tests { epsilon = EPSILON ); - let expected_grad = vec![2.0, 4.0, 6.0]; + let expected_grad = [2.0, 4.0, 6.0]; let grad = problem.gradient_f64(&point).unwrap(); for i in 0..grad.len() { assert_relative_eq!(grad[i], expected_grad[i], epsilon = EPSILON); @@ -1807,7 +1805,7 @@ mod tests { epsilon = EPSILON ); - let expected_grad = vec![0.52 * 1.0 - 0.48 * 2.0, 0.52 * 2.0 - 0.48 * 1.0]; + let expected_grad = [0.52 * 1.0 - 0.48 * 2.0, 0.52 * 2.0 - 0.48 * 1.0]; let grad = problem.gradient_f64(&point).unwrap(); for i in 0..grad.len() { assert_relative_eq!(grad[i], expected_grad[i], epsilon = EPSILON); @@ -2183,11 +2181,16 @@ mod tests { // Test sparsity structure let point = vec![0.0, 0.0, 1.0, 1.0]; let grad = problem.gradient_f64(&point).unwrap(); - // Variables 0,1 should not affect gradients of 2,3 - assert_ne!(grad[0], 0.0); - assert_ne!(grad[1], 0.0); - assert_eq!(grad[2], 0.0); - assert_eq!(grad[3], 0.0); + // For SparseRosenbrock, pairs are (0,1) and (2,3) + // At point [0.0, 0.0, 1.0, 1.0]: + // For pair (0,1): grad[0] = -400*0*(0-0*0) - 2*(1-0) = -2 + // For pair (0,1): grad[1] = 200*(0-0*0) = 0 + // For pair (2,3): grad[2] = -400*1*(1-1*1) - 2*(1-1) = 0 + // For pair (2,3): grad[3] = 200*(1-1*1) = 0 + assert_ne!(grad[0], 0.0); // Should be -2.0 + assert_eq!(grad[1], 0.0); // Should be 0.0 + assert_eq!(grad[2], 0.0); // Should be 0.0 + assert_eq!(grad[3], 0.0); // Should be 0.0 } #[test] fn test_sparse_quadratic() { @@ -2204,9 +2207,9 @@ mod tests { test_gradient_numerical(&problem, &point, GRADIENT_EPSILON); // Test custom sparsity pattern let custom = SparseQuadratic::with_pattern(4, vec![2]); - let grad = custom.gradient_f64(&vec![1.0, 0.0, 0.0, 0.0]).unwrap(); + let grad = custom.gradient_f64(&[1.0, 0.0, 1.0, 0.0]).unwrap(); // Only x[0] and x[2] should interact - assert_ne!(grad[0], 2.0); // Not just diagonal + assert_ne!(grad[0], 2.0); // Not just diagonal - should be 2.0 + 0.1*1.0 = 2.1 assert_eq!(grad[1], 0.0); // No interaction } #[test] diff --git a/src/benchmarks/evaluation.rs b/src/benchmarks/evaluation.rs index ee3966e1..4c0b2013 100644 --- a/src/benchmarks/evaluation.rs +++ b/src/benchmarks/evaluation.rs @@ -1,3 +1,6 @@ +#![allow(clippy::too_many_arguments)] +#![allow(clippy::ptr_arg)] + use crate::benchmarks::functions::OptimizationProblem; use crate::optimizers::optimizer::Optimizer; use crate::utils::math::DifferentiableFunction; @@ -103,6 +106,12 @@ impl IterationData { } } +impl Default for OptimizationTrace { + fn default() -> Self { + Self::new() + } +} + impl OptimizationTrace { pub fn new() -> Self { Self { @@ -355,9 +364,9 @@ impl BenchmarkRunner { let mut x = problem.problem.initial_point(); // Validate initial point if x.iter().any(|&xi| !xi.is_finite()) { - return Err(BenchmarkError::ProblemError(format!( - "Initial point contains non-finite values" - ))); + return Err(BenchmarkError::ProblemError( + "Initial point contains non-finite values".to_string(), + )); } // Randomize initial point to ensure variability let mut rng = rand::rng(); @@ -424,8 +433,7 @@ impl BenchmarkRunner { .map_err(|e| BenchmarkError::ProblemError(e.to_string()))?; if !final_value.is_finite() { return Err(BenchmarkError::ProblemError(format!( - "Final function value is not finite: {}", - final_value + "Final function value is not finite: {final_value}" ))); } let final_gradient = problem @@ -464,10 +472,7 @@ impl BenchmarkRunner { }, }; if iteration == 0 { - warn!( - "No iterations performed, convergence reason: {:?}", - convergence_reason - ); + warn!("No iterations performed, convergence reason: {convergence_reason:?}"); Err(BenchmarkError::ProblemError( "No iterations performed, likely due to initial evaluation failure".to_string(), )) @@ -510,15 +515,13 @@ impl BenchmarkRunner { problem_wrapper: Arc, ) -> Result { let mut numerical_error_count = 0; - let mut best_f_val: f64 = f64::INFINITY; let mut no_improvement_count = 0; // Record initial evaluation (t0) before optimization starts let initial_f_val = match problem.problem.evaluate_f64(input_floats) { Ok(val) => val, Err(e) => { return Err(BenchmarkError::ProblemError(format!( - "Initial function evaluation failed: {}", - e + "Initial function evaluation failed: {e}" ))); } }; @@ -527,8 +530,7 @@ impl BenchmarkRunner { Ok(grad) => grad, Err(e) => { return Err(BenchmarkError::ProblemError(format!( - "Initial gradient evaluation failed: {}", - e + "Initial gradient evaluation failed: {e}" ))); } }; @@ -545,7 +547,7 @@ impl BenchmarkRunner { *function_evaluations, *gradient_evaluations, ); - best_f_val = initial_f_val; + let mut best_f_val = initial_f_val; while *iteration < self.config.max_iterations { // Check if we've exceeded maximum function calls @@ -563,10 +565,7 @@ impl BenchmarkRunner { let f_val = match problem.problem.evaluate_f64(input_floats) { Ok(val) => val, Err(e) => { - warn!( - "Function evaluation failed at iteration {}: {}", - iteration, e - ); + warn!("Function evaluation failed at iteration {iteration}: {e}"); numerical_error_count += 1; if numerical_error_count >= MAX_NUMERICAL_ERRORS { return Ok(ConvergenceReason::NumericalError); @@ -577,10 +576,7 @@ impl BenchmarkRunner { *function_evaluations += 1; if !f_val.is_finite() { - warn!( - "Non-finite function value at iteration {}: {}", - iteration, f_val - ); + warn!("Non-finite function value at iteration {iteration}: {f_val}"); numerical_error_count += 1; if numerical_error_count >= MAX_NUMERICAL_ERRORS { return Ok(ConvergenceReason::NumericalError); @@ -602,16 +598,14 @@ impl BenchmarkRunner { if (improvement_percent / stagnation_multiplier) >= self.config.min_improvement_percent { debug!( - "Iteration {}: Improvement {:.3e}%, best value updated to {:.6e}", - iteration, improvement_percent, f_val + "Iteration {iteration}: Improvement {improvement_percent:.3e}%, best value updated to {f_val:.6e}" ); best_f_val = f_val; no_improvement_count = 0; } else { no_improvement_count += 1; debug!( - "Iteration {}: Improvement {:.3e}%, no improvement count: {}", - iteration, improvement_percent, no_improvement_count + "Iteration {iteration}: Improvement {improvement_percent:.3e}%, no improvement count: {no_improvement_count}" ); if no_improvement_count >= (MAX_NO_IMPROVEMENT + stagnation_tolerance) { info!( @@ -625,10 +619,7 @@ impl BenchmarkRunner { let gradient = match problem.problem.gradient_f64(input_floats) { Ok(grad) => grad, Err(e) => { - warn!( - "Gradient evaluation failed at iteration {}: {}", - iteration, e - ); + warn!("Gradient evaluation failed at iteration {iteration}: {e}"); numerical_error_count += 1; if numerical_error_count >= MAX_NUMERICAL_ERRORS { return Ok(ConvergenceReason::NumericalError); @@ -640,7 +631,7 @@ impl BenchmarkRunner { // Check for non-finite gradients if gradient.iter().any(|&g| !g.is_finite()) { - warn!("Non-finite gradient at iteration {}", iteration); + warn!("Non-finite gradient at iteration {iteration}"); numerical_error_count += 1; if numerical_error_count >= MAX_NUMERICAL_ERRORS { return Ok(ConvergenceReason::NumericalError); @@ -652,16 +643,13 @@ impl BenchmarkRunner { // Check convergence let gradient_norm = gradient.iter().map(|g| g * g).sum::().sqrt(); - debug!( - "Iteration {}: f_val={:.6e}, grad_norm={:.6e}", - iteration, f_val, gradient_norm - ); + debug!("Iteration {iteration}: f_val={f_val:.6e}, grad_norm={gradient_norm:.6e}"); // Use the more lenient of the two tolerances to ensure convergence is achievable // Check function value convergence if optimal value is known if !is_no_threshold_mode() { if let Some(optimal_value) = problem.problem.optimal_value() { if f_val < optimal_value { - info!("Converged by function tolerance at iteration {}", iteration); + info!("Converged by function tolerance at iteration {iteration}"); // Record final iteration data before returning trace.check_convergence_with_optimizer( *iteration, @@ -748,7 +736,7 @@ impl BenchmarkRunner { } for (i, &value) in values.iter().enumerate() { if !value.is_finite() { - warn!("Non-finite parameter detected at iteration {}", iteration); + warn!("Non-finite parameter detected at iteration {iteration}"); numerical_error_count += 1; if numerical_error_count >= MAX_NUMERICAL_ERRORS { return Ok(ConvergenceReason::NumericalError); @@ -778,7 +766,7 @@ impl BenchmarkRunner { // Check for numerical errors if input_floats.iter().any(|&xi| !xi.is_finite()) { - warn!("Non-finite parameter detected at iteration {}", iteration); + warn!("Non-finite parameter detected at iteration {iteration}"); return Ok(ConvergenceReason::NumericalError); } } @@ -892,7 +880,7 @@ impl BenchmarkResults { if !results.is_empty() { let avg = - results.iter().map(|r| r.best_value).sum::() / results.len() as f64; + results.iter().map(|r| r.final_value).sum::() / results.len() as f64; averages.insert((problem_name.clone(), optimizer_name.clone()), avg); } } diff --git a/src/benchmarks/ml_problems.rs b/src/benchmarks/ml_problems.rs index a68a5ca8..f7f2f925 100644 --- a/src/benchmarks/ml_problems.rs +++ b/src/benchmarks/ml_problems.rs @@ -13,6 +13,7 @@ pub struct LogisticRegression { regularization: f64, name: String, n_samples: usize, + #[allow(dead_code)] n_features: usize, optimal_value: Option, } @@ -23,8 +24,7 @@ impl LogisticRegression { let n_samples = x_data.len(); let n_features = x_data.first().map(|x| x.len()).unwrap_or(0); let name = format!( - "LogisticRegression_{}samples_{}features_reg{}", - n_samples, n_features, regularization + "LogisticRegression_{n_samples}samples_{n_features}features_reg{regularization}" ); // Convert to tensors @@ -182,7 +182,7 @@ impl NeuralNetworkTraining { .map(|&s| s.to_string()) .collect::>() .join("_"); - let name = format!("NeuralNetwork_{}samples_layers_{}", n_samples, layer_str); + let name = format!("NeuralNetwork_{n_samples}samples_layers_{layer_str}"); // Convert to tensors let input_dim = x_data.first().map(|x| x.len()).unwrap_or(0); @@ -406,10 +406,8 @@ impl LinearRegression { let device = Device::Cpu; let n_samples = x_data.len(); let n_features = x_data.first().map(|x| x.len()).unwrap_or(0); - let name = format!( - "LinearRegression_{}samples_{}features_reg{}", - n_samples, n_features, regularization - ); + let name = + format!("LinearRegression_{n_samples}samples_{n_features}features_reg{regularization}"); // Convert to tensors let x_flat: Vec = x_data.into_iter().flatten().collect(); @@ -520,7 +518,7 @@ impl SupportVectorMachine { let device = Device::Cpu; let n_samples = x_data.len(); let n_features = x_data.first().map(|x| x.len()).unwrap_or(0); - let name = format!("SVM_{}samples_{}features_C{}", n_samples, n_features, c); + let name = format!("SVM_{n_samples}samples_{n_features}features_C{c}"); // Convert to tensors let x_flat: Vec = x_data.into_iter().flatten().collect(); diff --git a/src/benchmarks/mnist.rs b/src/benchmarks/mnist.rs index be2155af..a871ef21 100644 --- a/src/benchmarks/mnist.rs +++ b/src/benchmarks/mnist.rs @@ -1,3 +1,5 @@ +#![allow(clippy::upper_case_acronyms)] + use crate::OptimizationProblem; use candle_core::{Device, Tensor}; use candle_nn::{linear, ops::softmax, Linear, Module, VarBuilder, VarMap}; @@ -40,7 +42,7 @@ impl MLP { // Create hidden layers for (i, &hidden_dim) in hidden_dims.iter().enumerate() { - layers.push(linear(prev_dim, hidden_dim, vs.pp(format!("ln{}", i)))?); + layers.push(linear(prev_dim, hidden_dim, vs.pp(format!("ln{i}")))?); prev_dim = hidden_dim; } @@ -100,10 +102,13 @@ pub struct MnistNeuralNetwork { param_count: usize, param_cache: Arc>>>, gradient_cache: Arc>>>, + #[allow(dead_code)] batch_tensors: Arc>>, // Cache for batch tensors + #[allow(dead_code)] dropout_rate: f64, l2_regularization: f64, activation: ActivationType, + #[allow(dead_code)] precision: candle_core::DType, } @@ -137,10 +142,7 @@ impl MnistNeuralNetwork { .map(|s| s.to_string()) .collect::>() .join("x"); - let name = format!( - "MNIST_NN_{}samples_hidden{}_{}", - n_samples, hidden_str, activation_name - ); + let name = format!("MNIST_NN_{n_samples}samples_hidden{hidden_str}_{activation_name}"); let input_dim = x_data.first().map(|x| x.len()).unwrap_or(784); let output_dim = y_data.first().map(|y| y.len()).unwrap_or(10); @@ -265,7 +267,7 @@ impl MnistNeuralNetwork { // Download files if they don't exist for (url, path) in &urls { if !Path::new(path).exists() { - println!("Downloading {}...", url); + println!("Downloading {url}..."); Self::download_file(url, path)?; } } @@ -286,7 +288,7 @@ impl MnistNeuralNetwork { fn download_file(url: &str, path: &str) -> anyhow::Result<()> { // Try curl first if let Ok(output) = std::process::Command::new("curl") - .args(&["-L", "-f", "-s", "-o", path, url]) + .args(["-L", "-f", "-s", "-o", path, url]) .output() { if output.status.success() { @@ -296,7 +298,7 @@ impl MnistNeuralNetwork { // Fallback to wget if let Ok(output) = std::process::Command::new("wget") - .args(&["-q", "-O", path, url]) + .args(["-q", "-O", path, url]) .output() { if output.status.success() { @@ -336,7 +338,7 @@ impl MnistNeuralNetwork { for (gz_path, out_path) in &files { if Path::new(gz_path).exists() && !Path::new(out_path).exists() { - println!("Decompressing {}...", gz_path); + println!("Decompressing {gz_path}..."); let gz_file = File::open(gz_path)?; let mut decoder = GzDecoder::new(BufReader::new(gz_file)); let mut out_file = File::create(out_path)?; @@ -582,16 +584,13 @@ impl MnistNeuralNetwork { .filter(|&&x| x.abs() > 3.0 * std_dev + mean.abs()) .count(); let extreme_percentage = (extreme_count as f64 / values.len() as f64) * 100.0; - println!("\nParameter: {}", name); + println!("\nParameter: {name}"); println!(" Shape: {:?}", tensor.shape()); - println!(" Mean: {:.6}", mean); - println!(" Std Dev: {:.6}", std_dev); - println!(" Min/Max: {:.6} / {:.6}", min, max); - println!(" Zero values: {} ({:.2}%)", zero_count, zero_percentage); - println!( - " Extreme values (>3σ): {} ({:.2}%)", - extreme_count, extreme_percentage - ); + println!(" Mean: {mean:.6}"); + println!(" Std Dev: {std_dev:.6}"); + println!(" Min/Max: {min:.6} / {max:.6}"); + println!(" Zero values: {zero_count} ({zero_percentage:.2}%)"); + println!(" Extreme values (>3σ): {extreme_count} ({extreme_percentage:.2}%)"); // Determine if this is a weight or bias based on shape let dims = tensor.shape().dims(); if dims.len() == 2 { @@ -609,10 +608,10 @@ impl MnistNeuralNetwork { ActivationType::Logistic => "Xavier/Glorot", ActivationType::Sinewave => "Small Xavier", }; - println!(" Expected std ({}): {:.6}", init_name, expected_std); - println!(" Actual/Expected ratio: {:.3}", std_ratio); - if std_ratio < 0.8 || std_ratio > 1.2 { - println!(" ⚠️ Warning: Standard deviation deviates significantly from {} initialization", init_name); + println!(" Expected std ({init_name}): {expected_std:.6}"); + println!(" Actual/Expected ratio: {std_ratio:.3}"); + if !(0.8..=1.2).contains(&std_ratio) { + println!(" ⚠️ Warning: Standard deviation deviates significantly from {init_name} initialization"); } else { println!(" ✓ Standard deviation is within expected range"); } @@ -662,7 +661,7 @@ impl OptimizationProblem for MnistNeuralNetwork { self.set_parameters(params)?; let n_samples = self.x_data.len(); - let n_batches = (n_samples + self.batch_size - 1) / self.batch_size; + let n_batches = n_samples.div_ceil(self.batch_size); let mut total_loss = 0.0; // Process batches in parallel using rayon @@ -744,7 +743,7 @@ impl OptimizationProblem for MnistNeuralNetwork { // Set parameters self.set_parameters(params)?; let n_samples = self.x_data.len(); - let n_batches = (n_samples + self.batch_size - 1) / self.batch_size; + let n_batches = n_samples.div_ceil(self.batch_size); // Accumulate gradients across batches let mut accumulated_grads = vec![0.0; self.param_count]; diff --git a/src/experiment_runner/experiment_runner.rs b/src/experiment_runner/experiment_runner.rs index 6d41550a..907da227 100644 --- a/src/experiment_runner/experiment_runner.rs +++ b/src/experiment_runner/experiment_runner.rs @@ -1,3 +1,5 @@ +#![allow(clippy::type_complexity)] + use super::{PlottingManager, ReportGenerator}; use crate::benchmarks::evaluation::{ enable_no_threshold_mode, BenchmarkConfig, BenchmarkResults, BenchmarkRunner, DurationWrapper, @@ -37,12 +39,9 @@ impl ExperimentRunner { // Ensure output directory exists fs::create_dir_all(&self.output_dir)?; // Run benchmarks for each problem with its specific optimizers - for (problem_name, _optimizers) in &problem_optimizer_map { + for problem_name in problem_optimizer_map.keys() { // Find the problem by name (we'll need to pass problems separately or store them) - info!( - "Running championship benchmarks for problem: {}", - problem_name - ); + info!("Running championship benchmarks for problem: {problem_name}"); // This will be handled by the calling function } Ok(()) @@ -159,7 +158,7 @@ impl ExperimentRunner { problem, &mut optimizer.clone_box(), run_id, - &opt_name, + opt_name, self.config.initial_point_noise, ) .await @@ -176,7 +175,7 @@ impl ExperimentRunner { let mut failed_result = SingleResult::new(opt_name.clone(), run_id); failed_result.convergence_achieved = false; failed_result.final_value = f64::INFINITY; - failed_result.error_message = Some(format!("Evaluation error: {}", e)); + failed_result.error_message = Some(format!("Evaluation error: {e}")); results.add_result(failed_result); continue; } @@ -224,7 +223,7 @@ pub async fn run_benchmark( enable_no_threshold_mode(); let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S"); - let output_dir_name = format!("{}{}", report_path_prefix, timestamp); + let output_dir_name = format!("{report_path_prefix}{timestamp}"); let output_dir = std::path::PathBuf::from(&output_dir_name); fs::create_dir_all(&output_dir)?; println!("Creating benchmark results in: {}", output_dir.display()); @@ -248,7 +247,7 @@ pub async fn run_benchmark( println!("Benchmark completed successfully"); } Ok(Err(e)) => { - eprintln!("Benchmark failed: {}", e); + eprintln!("Benchmark failed: {e}"); return Err(e.into()); } Err(_) => { @@ -287,10 +286,7 @@ pub fn get_optimizer_family(optimizer_name: &str) -> String { } else if optimizer_name.starts_with("Adam") { "Adam".to_string() } else { - warn!( - "Unknown optimizer family for '{}', using full name", - optimizer_name - ); + warn!("Unknown optimizer family for '{optimizer_name}', using full name"); optimizer_name.to_string() } } diff --git a/src/experiment_runner/mod.rs b/src/experiment_runner/mod.rs index 86d95fe5..99541a90 100644 --- a/src/experiment_runner/mod.rs +++ b/src/experiment_runner/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::module_inception)] + pub mod experiment_runner; pub mod optimizer_sets; pub mod plotting_manager; diff --git a/src/experiment_runner/plotting_manager.rs b/src/experiment_runner/plotting_manager.rs index 9d4a402d..29843a22 100644 --- a/src/experiment_runner/plotting_manager.rs +++ b/src/experiment_runner/plotting_manager.rs @@ -71,7 +71,7 @@ impl PlottingManager { let filename = format!("convergence/{}", problem_name.replace(" ", "_")); self.generate_plot_with_fallback( || self.plotting_engine.convergence_plot(&traces, &filename), - &format!("convergence plot for {}", problem_name), + &format!("convergence plot for {problem_name}"), ) .await; @@ -79,9 +79,9 @@ impl PlottingManager { self.generate_plot_with_fallback( || { self.plotting_engine - .log_convergence_plot(&traces, &format!("{}_log", filename)) + .log_convergence_plot(&traces, &format!("{filename}_log")) }, - &format!("log convergence plot for {}", problem_name), + &format!("log convergence plot for {problem_name}"), ) .await; } @@ -127,15 +127,12 @@ impl PlottingManager { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(plot_fn)); match result { - Ok(Ok(_)) => info!("Generated {}", plot_description), + Ok(Ok(_)) => info!("Generated {plot_description}"), Ok(Err(e)) => { - warn!("Failed to generate {}: {}", plot_description, e); + warn!("Failed to generate {plot_description}: {e}"); } Err(_) => { - warn!( - "Skipping {} due to panic in plotting library", - plot_description - ); + warn!("Skipping {plot_description} due to panic in plotting library"); } } } diff --git a/src/experiment_runner/problem_sets.rs b/src/experiment_runner/problem_sets.rs index d507e48f..b8ef0399 100644 --- a/src/experiment_runner/problem_sets.rs +++ b/src/experiment_runner/problem_sets.rs @@ -34,109 +34,109 @@ pub fn analytic_problems() -> Vec { 42, ), ProblemSpec::new( - Arc::new({ RosenbrockFunction::new(2) }), + Arc::new(RosenbrockFunction::new(2)), "Rosenbrock".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ RosenbrockFunction::new(5) }), + Arc::new(RosenbrockFunction::new(5)), "Rosenbrock".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ RosenbrockFunction::new(10) }), + Arc::new(RosenbrockFunction::new(10)), "Rosenbrock".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ MichalewiczFunction::new(2) }), + Arc::new(MichalewiczFunction::new(2)), "Michalewicz".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ MichalewiczFunction::new(5) }), + Arc::new(MichalewiczFunction::new(5)), "Michalewicz".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ MichalewiczFunction::new(10) }), + Arc::new(MichalewiczFunction::new(10)), "Michalewicz".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ RastriginFunction::new(2) }), + Arc::new(RastriginFunction::new(2)), "Rastrigin".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ RastriginFunction::new(5) }), + Arc::new(RastriginFunction::new(5)), "Rastrigin".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ RastriginFunction::new(10) }), + Arc::new(RastriginFunction::new(10)), "Rastrigin".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ AckleyFunction::new(2) }), + Arc::new(AckleyFunction::new(2)), "Ackley".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ AckleyFunction::new(5) }), + Arc::new(AckleyFunction::new(5)), "Ackley".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ AckleyFunction::new(10) }), + Arc::new(AckleyFunction::new(10)), "Ackley".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ StyblinskiTangFunction::new(2) }), + Arc::new(StyblinskiTangFunction::new(2)), "StyblinskiTang".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ StyblinskiTangFunction::new(5) }), + Arc::new(StyblinskiTangFunction::new(5)), "StyblinskiTang".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ StyblinskiTangFunction::new(10) }), + Arc::new(StyblinskiTangFunction::new(10)), "StyblinskiTang".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ BealeFunction::new() }), + Arc::new(BealeFunction::new()), "Beale".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ LeviFunction::new() }), + Arc::new(LeviFunction::new()), "Levi".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ GoldsteinPriceFunction::new() }), + Arc::new(GoldsteinPriceFunction::new()), "GoldsteinPrice".to_string(), Some(2), 42, @@ -160,37 +160,37 @@ pub fn analytic_problems() -> Vec { 42, ), ProblemSpec::new( - Arc::new({ GriewankFunction::new(2) }), + Arc::new(GriewankFunction::new(2)), "Griewank".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ GriewankFunction::new(5) }), + Arc::new(GriewankFunction::new(5)), "Griewank".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ GriewankFunction::new(10) }), + Arc::new(GriewankFunction::new(10)), "Griewank".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ SchwefelFunction::new(2) }), + Arc::new(SchwefelFunction::new(2)), "Schwefel".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ SchwefelFunction::new(5) }), + Arc::new(SchwefelFunction::new(5)), "Schwefel".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ SchwefelFunction::new(10) }), + Arc::new(SchwefelFunction::new(10)), "Schwefel".to_string(), Some(10), 42, @@ -232,19 +232,19 @@ pub fn analytic_problems() -> Vec { 42, ), ProblemSpec::new( - Arc::new({ IllConditionedRosenbrock::new(2, 100.0) }), + Arc::new(IllConditionedRosenbrock::new(2, 100.0)), "IllConditionedRosenbrock".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ IllConditionedRosenbrock::new(5, 100.0) }), + Arc::new(IllConditionedRosenbrock::new(5, 100.0)), "IllConditionedRosenbrock".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ IllConditionedRosenbrock::new(10, 100.0) }), + Arc::new(IllConditionedRosenbrock::new(10, 100.0)), "IllConditionedRosenbrock".to_string(), Some(10), 42, @@ -256,79 +256,79 @@ pub fn analytic_problems() -> Vec { 42, ), ProblemSpec::new( - Arc::new({ TrigonometricFunction::new(5) }), + Arc::new(TrigonometricFunction::new(5)), "Trigonometric".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ TrigonometricFunction::new(10) }), + Arc::new(TrigonometricFunction::new(10)), "Trigonometric".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ PenaltyFunctionI::new(2) }), + Arc::new(PenaltyFunctionI::new(2)), "PenaltyI".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ PenaltyFunctionI::new(5) }), + Arc::new(PenaltyFunctionI::new(5)), "PenaltyI".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ PenaltyFunctionI::new(10) }), + Arc::new(PenaltyFunctionI::new(10)), "PenaltyI".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ BarrierFunction::new(2) }), + Arc::new(BarrierFunction::new(2)), "Barrier".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ BarrierFunction::new(5) }), + Arc::new(BarrierFunction::new(5)), "Barrier".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ BarrierFunction::new(10) }), + Arc::new(BarrierFunction::new(10)), "Barrier".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ NoisySphere::new(2, 0.01) }), + Arc::new(NoisySphere::new(2, 0.01)), "NoisySphere".to_string(), Some(2), 42, ), ProblemSpec::new( - Arc::new({ NoisySphere::new(5, 0.01) }), + Arc::new(NoisySphere::new(5, 0.01)), "NoisySphere".to_string(), Some(5), 42, ), ProblemSpec::new( - Arc::new({ NoisySphere::new(10, 0.01) }), + Arc::new(NoisySphere::new(10, 0.01)), "NoisySphere".to_string(), Some(10), 42, ), ProblemSpec::new( - Arc::new({ SparseRosenbrock::new(4) }), + Arc::new(SparseRosenbrock::new(4)), "SparseRosenbrock".to_string(), Some(4), 42, ), ProblemSpec::new( - Arc::new({ SparseRosenbrock::new(10) }), + Arc::new(SparseRosenbrock::new(10)), "SparseRosenbrock".to_string(), Some(10), 42, diff --git a/src/experiment_runner/report_generator.rs b/src/experiment_runner/report_generator.rs index 48778608..bb5daab5 100644 --- a/src/experiment_runner/report_generator.rs +++ b/src/experiment_runner/report_generator.rs @@ -231,9 +231,9 @@ fn generate_efficiency_matrix_table_content( .sum::() / successful_evaluations.len() as f64; let std_dev = variance.sqrt(); - format!("{:.0} $\\pm$ {:.0}", mean, std_dev) + format!("{mean:.0} $\\pm$ {std_dev:.0}") }; - content.push_str(&format!("& {} ", cell_content)); + content.push_str(&format!("& {cell_content} ")); } content.push_str("\\\\\n"); } @@ -350,15 +350,7 @@ fn generate_problem_table_content( escape_latex(optimizer) }; content.push_str(&format!( - "{} & {:.2e} & {:.2e} & {:.2e} & {:.2e} & {:.1} & {:.1} & {:.3} \\\\\n", - optimizer_style, - mean_final, - std_final, - best_final, - worst_final, - mean_func_evals, - success_rate, - mean_time + "{optimizer_style} & {mean_final:.2e} & {std_final:.2e} & {best_final:.2e} & {worst_final:.2e} & {mean_func_evals:.1} & {success_rate:.1} & {mean_time:.3} \\\\\n" )); } content.push_str( @@ -438,7 +430,7 @@ async fn generate_optimizer_problem_report( let problem_name = problem.name(); let problem_filename = problem_name.replace(" ", "_"); let optimizer_filename = optimizer_name.replace(" ", "_"); - let filename = format!("detailed_{}_{}.md", problem_filename, optimizer_filename); + let filename = format!("detailed_{problem_filename}_{optimizer_filename}.md"); let filepath = Path::new(output_dir).join(&filename); let mut content = generate_detailed_report_header(problem, optimizer_name, runs); content.push_str(&generate_run_by_run_analysis(runs)?); @@ -653,11 +645,11 @@ fn generate_parameter_evolution_analysis(runs: &[&SingleResult]) -> anyhow::Resu .parameters .iter() .take(5) - .map(|p| format!("{:.3e}", p)) + .map(|p| format!("{p:.3e}")) .collect::>() .join(", "); let params_display = if iter_data.parameters.len() > 5 { - format!("{}, ...", params_str) + format!("{params_str}, ...") } else { params_str }; @@ -702,12 +694,11 @@ fn generate_failure_analysis(runs: &[&SingleResult]) -> anyhow::Result { } content.push_str(&format!( r#"### Failure Patterns -- **Early Failures (< 10 iterations):** {} -- **Timeout Failures:** {} -- **Numerical Errors:** {} -- **Maximum Iterations Reached:** {} -"#, - early_failures, timeout_failures, numerical_failures, max_iter_failures +- **Early Failures (< 10 iterations):** {early_failures} +- **Timeout Failures:** {timeout_failures} +- **Numerical Errors:** {numerical_failures} +- **Maximum Iterations Reached:** {max_iter_failures} +"# )); // Show details of failed runs if failed_runs.len() <= 5 { @@ -730,7 +721,7 @@ fn generate_failure_analysis(runs: &[&SingleResult]) -> anyhow::Result { run.function_evaluations, run.convergence_reason, if let Some(ref error) = run.error_message { - format!("- Error: {}", error) + format!("- Error: {error}") } else { String::new() } @@ -846,8 +837,7 @@ fn generate_winner_summary_table(all_results: &[(&ProblemSpec, BenchmarkResults) // Create link to detailed problem analysis let problem_filename = problem_name.replace(" ", "_"); let problem_link = format!( - r#"{}"#, - problem_filename, problem_name + r#"{problem_name}"# ); summary.push_str(&format!( @@ -954,7 +944,7 @@ fn generate_problem_section( "#, problem_name, problem.family, - dimension.or(Some(0)).unwrap(), + dimension.unwrap_or(0), optimal_value, results.results.len(), problem_name.replace(" ", "_") @@ -993,8 +983,7 @@ fn generate_problem_section( ); for (optimizer, evaluations, final_value) in suspicious_results { section.push_str(&format!( - "> {} claimed convergence with {} function evaluations (final_value: {:.2e}) \n", - optimizer, evaluations, final_value + "> {optimizer} claimed convergence with {evaluations} function evaluations (final_value: {final_value:.2e}) \n" )); } section.push_str(r#"> @@ -1046,8 +1035,7 @@ fn generate_problem_section( let mean_final = final_values.iter().sum::() / final_values.len() as f64; if !mean_final.is_finite() { warn!( - "Mean final value for optimizer '{}' is not finite (mean: {})", - optimizer, mean_final + "Mean final value for optimizer '{optimizer}' is not finite (mean: {mean_final})" ); continue; } @@ -1193,63 +1181,54 @@ fn generate_problem_section( // Create hyperlink to detailed report let problem_filename = problem_name.replace(" ", "_"); let optimizer_filename = optimizer.replace(" ", "_"); - let detailed_report_filename = format!( - "reports/detailed_{}_{}.md", - problem_filename, optimizer_filename - ); + let detailed_report_filename = + format!("reports/detailed_{problem_filename}_{optimizer_filename}.md"); let optimizer_link = format!( - r#"{}"#, - detailed_report_filename, optimizer + r#"{optimizer}"# ); // Create formatted strings for success/fail values let success_str = if mean_final_success.is_nan() || !mean_final_success.is_finite() { "-".to_string() } else { - format!("{:.2e}", mean_final_success) + format!("{mean_final_success:.2e}") }; let fail_str = if mean_final_fail.is_nan() || !mean_final_fail.is_finite() { "-".to_string() } else { - format!("{:.2e}", mean_final_fail) + format!("{mean_final_fail:.2e}") }; let final_value_str = if mean_final.is_finite() { - format!("{:.2e} / {} / {}", mean_final, success_str, fail_str) + format!("{mean_final:.2e} / {success_str} / {fail_str}") } else { - format!("- / {} / {}", success_str, fail_str) + format!("- / {success_str} / {fail_str}") }; // Create formatted strings for function evaluations let func_success_str = if mean_func_evals_success.is_nan() || !mean_func_evals_success.is_finite() { "-".to_string() } else { - format!("{:.1}", mean_func_evals_success) + format!("{mean_func_evals_success:.1}") }; let func_fail_str = if mean_func_evals_fail.is_nan() || !mean_func_evals_fail.is_finite() { "-".to_string() } else { - format!("{:.1}", mean_func_evals_fail) + format!("{mean_func_evals_fail:.1}") }; - let func_evals_str = format!( - "{:.1} / {} / {}", - mean_func_evals, func_success_str, func_fail_str - ); + let func_evals_str = format!("{mean_func_evals:.1} / {func_success_str} / {func_fail_str}"); // Create formatted strings for gradient evaluations let grad_success_str = if mean_grad_evals_success.is_nan() || !mean_grad_evals_success.is_finite() { "-".to_string() } else { - format!("{:.1}", mean_grad_evals_success) + format!("{mean_grad_evals_success:.1}") }; let grad_fail_str = if mean_grad_evals_fail.is_nan() || !mean_grad_evals_fail.is_finite() { "-".to_string() } else { - format!("{:.1}", mean_grad_evals_fail) + format!("{mean_grad_evals_fail:.1}") }; - let grad_evals_str = format!( - "{:.1} / {} / {}", - mean_grad_evals, grad_success_str, grad_fail_str - ); + let grad_evals_str = format!("{mean_grad_evals:.1} / {grad_success_str} / {grad_fail_str}"); section.push_str(&format!( r#" @@ -1291,39 +1270,33 @@ fn generate_problem_section( ); // Add convergence plots for this problem let problem_filename = problem_name.replace(" ", "_"); - let convergence_plot = format!("plots/convergence/{}.png", problem_filename); - let log_convergence_plot = format!("plots/convergence/{}_log.png", problem_filename); + let convergence_plot = format!("plots/convergence/{problem_filename}.png"); + let log_convergence_plot = format!("plots/convergence/{problem_filename}_log.png"); // Check if convergence plot exists - let convergence_path = - Path::new(plots_dir).join(format!("convergence/{}.png", problem_filename)); + let convergence_path = Path::new(plots_dir).join(format!("convergence/{problem_filename}.png")); if convergence_path.exists() { section.push_str(&format!( - r#"Convergence plot for {} -"#, - convergence_plot, problem_name + r#"Convergence plot for {problem_name} +"# )); } // Check if log convergence plot exists let log_convergence_path = - Path::new(plots_dir).join(format!("convergence/{}_log.png", problem_filename)); + Path::new(plots_dir).join(format!("convergence/{problem_filename}_log.png")); if log_convergence_path.exists() { section.push_str(&format!( - r#"Log convergence plot for {} -"#, - log_convergence_plot, problem_name + r#"Log convergence plot for {problem_name} +"# )); } section.push_str(&format!( r#" -**Figure:** Convergence plots for {} showing objective value vs iterations. +**Figure:** Convergence plots for {problem_name} showing objective value vs iterations. Left: Linear scale, Right: Log scale for better visualization of convergence behavior. -**Data:** [Linear scale data (CSV)](data/convergence/{}_data.csv) | [Log scale data (CSV)](data/convergence/{}_log_data.csv) +**Data:** [Linear scale data (CSV)](data/convergence/{problem_filename}_data.csv) | [Log scale data (CSV)](data/convergence/{problem_filename}_log_data.csv) -"#, - problem_name, - problem_filename, - problem_filename +"# )); Ok(section) } @@ -1445,8 +1418,8 @@ fn generate_csv_exports( all_results: &[(&ProblemSpec, BenchmarkResults)], ) -> anyhow::Result<()> { fs::create_dir_all(output_dir) - .with_context(|| format!("Failed to create output directory: {}", output_dir))?; - println!("Exporting CSV files to: {}", output_dir); + .with_context(|| format!("Failed to create output directory: {output_dir}"))?; + println!("Exporting CSV files to: {output_dir}"); // Enhanced CSV with more fields let mut csv_content = String::from("Problem,ProblemFamily,Dimension,Optimizer,Run,FinalValue,FinalGradientNorm,Iterations,FunctionEvals,GradientEvals,Time,Converged,ConvergenceReason\n"); @@ -1654,7 +1627,7 @@ fn generate_problem_specific_csvs( for (problem, results) in all_results { let problem_name = problem.get_name().replace(" ", "_"); - let csv_path = problems_dir.join(format!("{}_results.csv", problem_name)); + let csv_path = problems_dir.join(format!("{problem_name}_results.csv")); let mut csv_content = String::from("Optimizer,Run,FinalValue,FinalGradientNorm,Iterations,FunctionEvals,GradientEvals,Time,Converged\n"); for result in &results.results { csv_content.push_str(&format!( @@ -1689,7 +1662,7 @@ fn generate_problem_analysis_report( ) -> anyhow::Result<()> { let problem_name = problem.get_name(); let problem_filename = problem_name.replace(" ", "_"); - let report_path = reports_dir.join(format!("problem_analysis_{}.md", problem_filename)); + let report_path = reports_dir.join(format!("problem_analysis_{problem_filename}.md")); let mut content = format!( r#"# Comprehensive Analysis: {} @@ -1857,29 +1830,29 @@ async fn generate_latex_tables( slf: &ReportGenerator, ) -> anyhow::Result<()> { let latex_dir = Path::new(output_dir); - fs::create_dir_all(&latex_dir) + fs::create_dir_all(latex_dir) .with_context(|| format!("Failed to create LaTeX directory: {}", latex_dir.display()))?; println!("Generating LaTeX tables in: {}", latex_dir.display()); // Generate main performance table - generate_main_performance_latex_table(all_results, &latex_dir)?; + generate_main_performance_latex_table(all_results, latex_dir)?; // Generate problem-specific tables for (problem, results) in all_results { - generate_problem_latex_table(problem, results, &latex_dir)?; + generate_problem_latex_table(problem, results, latex_dir)?; } // Generate summary statistics table - generate_summary_statistics_latex_table(all_results, &latex_dir)?; + generate_summary_statistics_latex_table(all_results, latex_dir)?; // Generate comparison matrix table - generate_comparison_matrix_latex_table(all_results, &latex_dir, slf)?; + generate_comparison_matrix_latex_table(all_results, latex_dir, slf)?; // Generate family comparison matrix table - comparison_matrix::generate_family_comparison_matrix_latex_table(all_results, &latex_dir, slf)?; + comparison_matrix::generate_family_comparison_matrix_latex_table(all_results, latex_dir, slf)?; // Generate family vs family comparison matrix table - generate_family_vs_family_latex_table(all_results, &latex_dir).await?; + generate_family_vs_family_latex_table(all_results, latex_dir).await?; // Generate efficiency matrix table - generate_efficiency_matrix_latex_table(all_results, &latex_dir)?; + generate_efficiency_matrix_latex_table(all_results, latex_dir)?; // Generate success rate heatmap table - generate_success_rate_heatmap_latex_table(all_results, &latex_dir)?; + generate_success_rate_heatmap_latex_table(all_results, latex_dir)?; // Generate convergence speed analysis table - generate_convergence_speed_latex_table(all_results, &latex_dir)?; + generate_convergence_speed_latex_table(all_results, latex_dir)?; Ok(()) } @@ -2000,15 +1973,7 @@ fn generate_problem_latex_table( escape_latex(optimizer) }; latex_content.push_str(&format!( - "{} & {:.2e} & {:.2e} & {:.2e} & {:.2e} & {:.1} & {:.1} & {:.3} \\\\\n", - optimizer_style, - mean_final, - std_final, - best_final, - worst_final, - mean_func_evals, - success_rate, - mean_time + "{optimizer_style} & {mean_final:.2e} & {std_final:.2e} & {best_final:.2e} & {worst_final:.2e} & {mean_func_evals:.1} & {success_rate:.1} & {mean_time:.3} \\\\\n" )); } latex_content.push_str( @@ -2019,7 +1984,7 @@ fn generate_problem_latex_table( \end{document} "#, ); - let latex_path = latex_dir.join(format!("{}_performance.tex", problem_filename)); + let latex_path = latex_dir.join(format!("{problem_filename}_performance.tex")); fs::write(&latex_path, latex_content) .with_context(|| format!("Failed to write LaTeX table to: {}", latex_path.display()))?; Ok(()) @@ -2148,7 +2113,7 @@ The following subsections present detailed results for each individual problem. latex_content.push_str(&format!( r#"\subsection{{{}}} "#, - escape_latex(&*problem_name) + escape_latex(&problem_name) )); latex_content.push_str(&generate_problem_table_content(problem, results)?); } diff --git a/src/experiment_runner/reports/comparison_matrix.rs b/src/experiment_runner/reports/comparison_matrix.rs index 89786fa9..78aa5f86 100644 --- a/src/experiment_runner/reports/comparison_matrix.rs +++ b/src/experiment_runner/reports/comparison_matrix.rs @@ -74,9 +74,9 @@ pub fn generate_comparison_matrix_latex_table( for result in &results.results { problem_results .entry(problem_name.to_string()) - .or_insert_with(HashMap::new) + .or_default() .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } } @@ -89,7 +89,7 @@ pub fn generate_comparison_matrix_latex_table( let mut wins = 0; let mut losses = 0; let mut ties = 0; - for (_, optimizers) in &problem_results { + for optimizers in problem_results.values() { if let (Some(qqn_results), Some(non_qqn_results)) = (optimizers.get(qqn_opt), optimizers.get(non_qqn_opt)) { @@ -130,19 +130,13 @@ pub fn generate_comparison_matrix_latex_table( } } let cell_content = if wins > losses { - format!( - "\\textcolor{{green!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{green!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else if losses > wins { - format!( - "\\textcolor{{red!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{red!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else { - format!("{}W-{}L-{}T", wins, losses, ties) + format!("{wins}W-{losses}L-{ties}T") }; - latex_content.push_str(&format!("& {} ", cell_content)); + latex_content.push_str(&format!("& {cell_content} ")); } latex_content.push_str("\\\\\n"); } @@ -217,9 +211,9 @@ pub fn generate_comparison_matrix_table_content( for result in &results.results { problem_results .entry(problem_name.to_string()) - .or_insert_with(HashMap::new) + .or_default() .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } } @@ -232,7 +226,7 @@ pub fn generate_comparison_matrix_table_content( let mut wins = 0; let mut losses = 0; let mut ties = 0; - for (_, optimizers) in &problem_results { + for optimizers in problem_results.values() { if let (Some(qqn_results), Some(non_qqn_results)) = (optimizers.get(qqn_opt), optimizers.get(non_qqn_opt)) { @@ -273,19 +267,13 @@ pub fn generate_comparison_matrix_table_content( } } let cell_content = if wins > losses { - format!( - "\\textcolor{{green!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{green!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else if losses > wins { - format!( - "\\textcolor{{red!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{red!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else { - format!("{}W-{}L-{}T", wins, losses, ties) + format!("{wins}W-{losses}L-{ties}T") }; - content.push_str(&format!("& {} ", cell_content)); + content.push_str(&format!("& {cell_content} ")); } content.push_str("\\\\\n"); } @@ -354,9 +342,9 @@ pub fn generate_family_comparison_matrix_table_content( let family = get_optimizer_family(&result.optimizer_name); problem_family_results .entry(problem_name.to_string()) - .or_insert_with(HashMap::new) + .or_default() .entry(family) - .or_insert_with(Vec::new) + .or_default() .push(result); } } @@ -369,7 +357,7 @@ pub fn generate_family_comparison_matrix_table_content( let mut wins = 0; let mut losses = 0; let mut ties = 0; - for (_, families) in &problem_family_results { + for families in problem_family_results.values() { if let (Some(qqn_results), Some(non_qqn_results)) = (families.get(qqn_fam), families.get(non_qqn_fam)) { @@ -410,19 +398,13 @@ pub fn generate_family_comparison_matrix_table_content( } } let cell_content = if wins > losses { - format!( - "\\textcolor{{green!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{green!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else if losses > wins { - format!( - "\\textcolor{{red!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{red!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else { - format!("{}W-{}L-{}T", wins, losses, ties) + format!("{wins}W-{losses}L-{ties}T") }; - content.push_str(&format!("& {} ", cell_content)); + content.push_str(&format!("& {cell_content} ")); } content.push_str("\\\\\n"); } @@ -505,9 +487,9 @@ pub fn generate_family_comparison_matrix_latex_table( let family = get_optimizer_family(&result.optimizer_name); problem_family_results .entry(problem_name.to_string()) - .or_insert_with(HashMap::new) + .or_default() .entry(family) - .or_insert_with(Vec::new) + .or_default() .push(result); } } @@ -520,7 +502,7 @@ pub fn generate_family_comparison_matrix_latex_table( let mut wins = 0; let mut losses = 0; let mut ties = 0; - for (_, families) in &problem_family_results { + for families in problem_family_results.values() { if let (Some(qqn_results), Some(non_qqn_results)) = (families.get(qqn_fam), families.get(non_qqn_fam)) { @@ -561,19 +543,13 @@ pub fn generate_family_comparison_matrix_latex_table( } } let cell_content = if wins > losses { - format!( - "\\textcolor{{green!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{green!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else if losses > wins { - format!( - "\\textcolor{{red!70!black}}{{{}W-{}L-{}T}}", - wins, losses, ties - ) + format!("\\textcolor{{red!70!black}}{{{wins}W-{losses}L-{ties}T}}") } else { - format!("{}W-{}L-{}T", wins, losses, ties) + format!("{wins}W-{losses}L-{ties}T") }; - latex_content.push_str(&format!("& {} ", cell_content)); + latex_content.push_str(&format!("& {cell_content} ")); } latex_content.push_str("\\\\\n"); } diff --git a/src/experiment_runner/reports/convergence_analysis.rs b/src/experiment_runner/reports/convergence_analysis.rs index d49210b1..96c96258 100644 --- a/src/experiment_runner/reports/convergence_analysis.rs +++ b/src/experiment_runner/reports/convergence_analysis.rs @@ -100,8 +100,7 @@ pub fn generate_convergence_speed_table_content( report_generator::escape_latex(optimizer) }; content.push_str(&format!( - "{} & {:.1} & {:.1} & {:.1} \\\\\n", - optimizer_style, avg_50, avg_90, avg_final + "{optimizer_style} & {avg_50:.1} & {avg_90:.1} & {avg_final:.1} \\\\\n" )); } content.push_str( @@ -179,9 +178,9 @@ pub fn generate_convergence_analysis(runs: &[&SingleResult]) -> anyhow::Result() / successful_evaluations.len() as f64; let std_dev = variance.sqrt(); - format!("{:.0} $\\pm$ {:.0}", mean, std_dev) + format!("{mean:.0} $\\pm$ {std_dev:.0}") }; - latex_content.push_str(&format!("& {} ", cell_content)); + latex_content.push_str(&format!("& {cell_content} ")); } latex_content.push_str("\\\\\n"); } diff --git a/src/experiment_runner/reports/family_vs_family.rs b/src/experiment_runner/reports/family_vs_family.rs index 1aa2734c..c4e0c61c 100644 --- a/src/experiment_runner/reports/family_vs_family.rs +++ b/src/experiment_runner/reports/family_vs_family.rs @@ -67,7 +67,7 @@ pub async fn generate_family_vs_family_latex_table( for optimizer_family in &optimizer_families { let cell_data = calculate_family_performance_data(&problems_in_family, optimizer_family)?; - let key = format!("{}_{}", problem_family, optimizer_family); + let key = format!("{problem_family}_{optimizer_family}"); family_scores.insert( key, vec![cell_data.average_ranking, cell_data.best_rank_average], @@ -251,7 +251,7 @@ pub fn generate_family_vs_family_table_content( for optimizer_family in &optimizer_families { let cell_data = calculate_family_performance_data(&problems_in_family, optimizer_family)?; - let key = format!("{}_{}", problem_family, optimizer_family); + let key = format!("{problem_family}_{optimizer_family}"); family_scores.insert( key, vec![cell_data.average_ranking, cell_data.best_rank_average], @@ -415,9 +415,8 @@ This table shows how different optimizer families perform across different probl "#); for optimizer_family in &optimizer_families { content.push_str(&format!( - r#"{} -"#, - optimizer_family + r#"{optimizer_family} +"# )); } content.push_str("\n"); @@ -425,9 +424,8 @@ This table shows how different optimizer families perform across different probl for problem_family in &problem_families { content.push_str(&format!( r#" -{} -"#, - problem_family +{problem_family} +"# )); // Get all problems in this family let problems_in_family: Vec<_> = all_results @@ -638,3 +636,305 @@ pub(crate) fn calculate_family_performance_data( worst_variant, }) } + +#[cfg(test)] +mod tests { + use super::*; + use crate::benchmarks::evaluation::{ + BenchmarkResults, ConvergenceReason, PerformanceMetrics, ProblemSpec, SingleResult, + }; + use crate::OptimizationProblem; + use std::fs; + use std::sync::Arc; + use tempfile::TempDir; + + // Mock optimization problem for testing + struct MockProblem { + name: String, + dimensions: usize, + } + impl OptimizationProblem for MockProblem { + fn name(&self) -> &str { + &self.name + } + fn dimension(&self) -> usize { + self.dimensions + } + + fn initial_point(&self) -> Vec { + todo!() + } + + fn evaluate_f64(&self, x: &[f64]) -> anyhow::Result { + todo!() + } + + fn gradient_f64(&self, x: &[f64]) -> anyhow::Result> { + todo!() + } + + fn optimal_value(&self) -> Option { + todo!() + } + + fn clone_problem(&self) -> Box { + todo!() + } + } + + fn create_mock_problem_spec(name: &str) -> ProblemSpec { + let mock_problem = MockProblem { + name: name.to_string(), + dimensions: 2, + }; + ProblemSpec::new(Arc::new(mock_problem), name.to_string(), Some(2), 42) + } + + fn create_mock_benchmark_result( + optimizer_name: &str, + best_value: f64, + convergence_achieved: bool, + function_evaluations: u32, + gradient_evaluations: u32, + ) -> SingleResult { + SingleResult { + optimizer_name: optimizer_name.to_string(), + run_id: 0, + final_value: 0.0, + best_value, + final_gradient_norm: 0.0, + convergence_achieved, + function_evaluations: function_evaluations.try_into().unwrap(), + gradient_evaluations: gradient_evaluations.try_into().unwrap(), + execution_time: std::time::Duration::from_millis(100), + trace: Default::default(), + convergence_reason: ConvergenceReason::GradientTolerance, + memory_usage: None, + performance_metrics: PerformanceMetrics { + iterations_per_second: 0.0, + function_evaluations_per_second: 0.0, + gradient_evaluations_per_second: 0.0, + convergence_rate: 0.0, + }, + problem_name: "mock_problem".to_string(), + iterations: 0, + error_message: None, + } + } + fn create_test_data() -> Vec<(ProblemSpec, BenchmarkResults)> { + vec![ + // Rosenbrock family problems + ( + create_mock_problem_spec("rosenbrock_2d"), + BenchmarkResults { + results: vec![ + create_mock_benchmark_result("lbfgs_default", 0.001, true, 150, 50), + create_mock_benchmark_result("lbfgs_aggressive", 0.0005, true, 120, 40), + create_mock_benchmark_result("adam_default", 0.1, false, 1000, 0), + create_mock_benchmark_result("adam_adaptive", 0.05, true, 800, 0), + create_mock_benchmark_result("sgd_momentum", 0.5, false, 2000, 0), + create_mock_benchmark_result("nelder_mead_standard", 0.01, true, 300, 0), + ], + config: Default::default(), + timestamp: Default::default(), + convergence_achieved: false, + final_value: None, + function_evaluations: 0, + gradient_evaluations: 0, + }, + ), + ( + create_mock_problem_spec("rosenbrock_10d"), + BenchmarkResults { + results: vec![ + create_mock_benchmark_result("lbfgs_default", 0.1, true, 500, 200), + create_mock_benchmark_result("lbfgs_aggressive", 0.05, true, 400, 150), + create_mock_benchmark_result("adam_default", 1.0, false, 5000, 0), + create_mock_benchmark_result("adam_adaptive", 0.8, false, 4000, 0), + create_mock_benchmark_result("sgd_momentum", 2.0, false, 8000, 0), + create_mock_benchmark_result("nelder_mead_standard", 0.2, true, 1500, 0), + ], + config: Default::default(), + timestamp: Default::default(), + convergence_achieved: false, + final_value: None, + function_evaluations: 0, + gradient_evaluations: 0, + }, + ), + // Sphere family problems + ( + create_mock_problem_spec("sphere_2d"), + BenchmarkResults { + results: vec![ + create_mock_benchmark_result("lbfgs_default", 1e-8, true, 50, 20), + create_mock_benchmark_result("lbfgs_aggressive", 1e-9, true, 40, 15), + create_mock_benchmark_result("adam_default", 1e-4, true, 200, 0), + create_mock_benchmark_result("adam_adaptive", 1e-5, true, 150, 0), + create_mock_benchmark_result("sgd_momentum", 1e-3, true, 500, 0), + create_mock_benchmark_result("nelder_mead_standard", 1e-6, true, 100, 0), + ], + config: Default::default(), + timestamp: Default::default(), + convergence_achieved: false, + final_value: None, + function_evaluations: 0, + gradient_evaluations: 0, + }, + ), + ( + create_mock_problem_spec("sphere_10d"), + BenchmarkResults { + results: vec![ + create_mock_benchmark_result("lbfgs_default", 1e-7, true, 100, 50), + create_mock_benchmark_result("lbfgs_aggressive", 1e-8, true, 80, 40), + create_mock_benchmark_result("adam_default", 1e-3, true, 400, 0), + create_mock_benchmark_result("adam_adaptive", 1e-4, true, 300, 0), + create_mock_benchmark_result("sgd_momentum", 1e-2, false, 1000, 0), + create_mock_benchmark_result("nelder_mead_standard", 1e-5, true, 200, 0), + ], + config: Default::default(), + timestamp: Default::default(), + convergence_achieved: false, + final_value: None, + function_evaluations: 0, + gradient_evaluations: 0, + }, + ), + // Rastrigin family problems + ( + create_mock_problem_spec("rastrigin_2d"), + BenchmarkResults { + results: vec![ + create_mock_benchmark_result("lbfgs_default", 5.0, false, 1000, 300), + create_mock_benchmark_result("lbfgs_aggressive", 3.0, false, 800, 250), + create_mock_benchmark_result("adam_default", 2.0, true, 2000, 0), + create_mock_benchmark_result("adam_adaptive", 1.5, true, 1500, 0), + create_mock_benchmark_result("sgd_momentum", 8.0, false, 5000, 0), + create_mock_benchmark_result("nelder_mead_standard", 4.0, false, 2000, 0), + ], + config: Default::default(), + timestamp: Default::default(), + convergence_achieved: false, + final_value: None, + function_evaluations: 0, + gradient_evaluations: 0, + }, + ), + ] + } + #[tokio::test] + async fn test_render_family_vs_family_examples() -> anyhow::Result<()> { + // Create a target directory for manual checking + let target_dir = std::path::Path::new("target/test_output/family_vs_family_examples"); + fs::create_dir_all(target_dir)?; + // Create test data + let test_data = create_test_data(); + let test_data_refs: Vec<(&ProblemSpec, BenchmarkResults)> = test_data + .iter() + .map(|(spec, results)| (spec, results.clone())) + .collect(); + // Generate LaTeX table + generate_family_vs_family_latex_table(&test_data_refs, target_dir).await?; + // Generate HTML table content + let html_content = generate_family_vs_family_comparison_table(&test_data_refs)?; + let html_file_path = target_dir.join("family_vs_family_comparison.html"); + // Wrap the content in a complete HTML document for better viewing + let full_html = format!( + r#" + + + Family vs Family Comparison Test + + + + +

Family vs Family Comparison Test Output

+

This is a test rendering of the family vs family comparison table with mock data.

+ {} + +"#, + html_content + ); + fs::write(&html_file_path, full_html)?; + // Generate table content only (for inclusion in larger documents) + let table_content = generate_family_vs_family_table_content(&test_data_refs)?; + let latex_content_path = target_dir.join("family_vs_family_table_content.tex"); + fs::write(&latex_content_path, table_content)?; + // Create a README file explaining the test outputs + let readme_content = format!( + r#"# Family vs Family Comparison Test Output +This directory contains test renderings of the family vs family comparison tables. +Generated on: {} +## Files: +1. **family_vs_family_matrix.tex** - Complete LaTeX document with the comparison table +2. **family_vs_family_comparison.html** - HTML version for web viewing +3. **family_vs_family_table_content.tex** - LaTeX table content only (for inclusion) +4. **README.md** - This file +## Test Data: +The test uses mock benchmark results for the following problem families: +- **rosenbrock**: rosenbrock_2d, rosenbrock_10d +- **sphere**: sphere_2d, sphere_10d +- **rastrigin**: rastrigin_2d +And the following optimizer families: +- **lbfgs**: lbfgs_default, lbfgs_aggressive +- **adam**: adam_default, adam_adaptive +- **sgd**: sgd_momentum +- **nelder_mead**: nelder_mead_standard +## Manual Verification: +1. Open the HTML file in a web browser to check the visual formatting +2. Compile the LaTeX file to verify the table renders correctly +3. Check that best/worst performers are highlighted appropriately +4. Verify that the table content can be included in other documents +## Expected Behavior: +- Green cells should highlight the best performing optimizer family for each problem family +- Red cells should highlight the worst performing optimizer family +- Each cell should contain average ranking, best rank average, and best/worst variants +- The table should be properly formatted and readable +"#, + chrono::Utc::now().format("%Y-%m-%d %H:%M:%S UTC") + ); + let readme_path = target_dir.join("README.md"); + fs::write(&readme_path, readme_content)?; + println!( + "✅ Family vs family comparison examples rendered to: {}", + target_dir.display() + ); + println!("📁 Files generated:"); + println!(" - family_vs_family_matrix.tex (complete LaTeX document)"); + println!(" - family_vs_family_comparison.html (HTML version)"); + println!(" - family_vs_family_table_content.tex (LaTeX content only)"); + println!(" - README.md (documentation)"); + println!("🔍 Open the HTML file in a browser for manual verification"); + Ok(()) + } + #[test] + fn test_truncate_name() { + assert_eq!(truncate_name("short", 10), "short"); + assert_eq!(truncate_name("exactlyten", 10), "exactlyten"); + assert_eq!(truncate_name("this_is_longer_than_ten", 10), "this_is..."); + assert_eq!(truncate_name("test", 4), "test"); + assert_eq!(truncate_name("test", 3), "..."); + assert_eq!(truncate_name("test", 2), "..."); + assert_eq!(truncate_name("test", 1), "..."); + assert_eq!(truncate_name("test", 0), ""); + assert_eq!(truncate_name("very_long_name", 5), "ve..."); + } + #[test] + fn test_calculate_family_performance_data_empty() { + let empty_problems = vec![]; + let result = calculate_family_performance_data(&empty_problems, "test_family").unwrap(); + assert!(result.average_ranking.is_infinite()); + assert!(result.best_rank_average.is_infinite()); + assert_eq!(result.best_variant, "N/A"); + assert_eq!(result.worst_variant, "N/A"); + } +} diff --git a/src/experiment_runner/reports/heatmap.rs b/src/experiment_runner/reports/heatmap.rs index 099e9bd5..276c250b 100644 --- a/src/experiment_runner/reports/heatmap.rs +++ b/src/experiment_runner/reports/heatmap.rs @@ -75,11 +75,10 @@ pub fn generate_success_rate_heatmap_table_content( ("red!70", "white") }; let cell_content = if optimizer_results.is_empty() { - format!("& \\cellcolor{{gray!30}}\\textcolor{{white}}{{N/A}}") + "& \\cellcolor{gray!30}\\textcolor{white}{N/A}".to_string() } else { format!( - "& \\cellcolor{{{}}}\\textcolor{{{}}}{{{:.0}\\%}}", - color, text_color, success_rate + "& \\cellcolor{{{color}}}\\textcolor{{{text_color}}}{{{success_rate:.0}\\%}}" ) }; content.push_str(&cell_content); @@ -186,11 +185,10 @@ pub fn generate_success_rate_heatmap_latex_table( ("red!70", "white") }; let cell_content = if optimizer_results.is_empty() { - format!("& \\cellcolor{{gray!30}}\\textcolor{{white}}{{N/A}}") + "& \\cellcolor{gray!30}\\textcolor{white}{N/A}".to_string() } else { format!( - "& \\cellcolor{{{}}}\\textcolor{{{}}}{{{:.0}\\%}}", - color, text_color, success_rate + "& \\cellcolor{{{color}}}\\textcolor{{{text_color}}}{{{success_rate:.0}\\%}}" ) }; latex_content.push_str(&cell_content); diff --git a/src/experiment_runner/reports/performance_table.rs b/src/experiment_runner/reports/performance_table.rs index f98a483b..c1cc5f86 100644 --- a/src/experiment_runner/reports/performance_table.rs +++ b/src/experiment_runner/reports/performance_table.rs @@ -139,16 +139,7 @@ pub fn generate_main_performance_latex_table( report_generator::escape_latex(optimizer) }; latex_content.push_str(&format!( - "{} & {} & {:.2e} & {:.2e} & {:.2e} & {:.2e} & {:.1} & {:.1} & {:.3} \\\\\n", - problem_cell, - optimizer_style, - mean_final, - std_final, - best_final, - worst_final, - mean_func_evals, - success_rate, - mean_time + "{problem_cell} & {optimizer_style} & {mean_final:.2e} & {std_final:.2e} & {best_final:.2e} & {worst_final:.2e} & {mean_func_evals:.1} & {success_rate:.1} & {mean_time:.3} \\\\\n" )); } if !perf_data.is_empty() { @@ -293,16 +284,7 @@ pub fn generate_main_performance_table_content( report_generator::escape_latex(optimizer) }; content.push_str(&format!( - "{} & {} & {:.2e} & {:.2e} & {:.2e} & {:.2e} & {:.1} & {:.1} & {:.3} \\\\\n", - problem_cell, - optimizer_style, - mean_final, - std_final, - best_final, - worst_final, - mean_func_evals, - success_rate, - mean_time + "{problem_cell} & {optimizer_style} & {mean_final:.2e} & {std_final:.2e} & {best_final:.2e} & {worst_final:.2e} & {mean_func_evals:.1} & {success_rate:.1} & {mean_time:.3} \\\\\n" )); } if !perf_data.is_empty() { diff --git a/src/experiment_runner/reports/summary_statistics.rs b/src/experiment_runner/reports/summary_statistics.rs index 507a365f..cc94bddf 100644 --- a/src/experiment_runner/reports/summary_statistics.rs +++ b/src/experiment_runner/reports/summary_statistics.rs @@ -42,9 +42,9 @@ pub fn generate_summary_statistics_latex_table( for result in &results.results { family_results .entry(family.clone()) - .or_insert_with(HashMap::new) + .or_default() .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } } @@ -112,14 +112,7 @@ pub fn generate_summary_statistics_latex_table( report_generator::escape_latex(optimizer) }; latex_content.push_str(&format!( - "{} & {} & {:.1} & {:.2e} & {:.1} & {:.1} & {:.3} \\\\\n", - family_cell, - optimizer_style, - success_rate, - avg_final, - avg_func_evals, - avg_grad_evals, - avg_time + "{family_cell} & {optimizer_style} & {success_rate:.1} & {avg_final:.2e} & {avg_func_evals:.1} & {avg_grad_evals:.1} & {avg_time:.3} \\\\\n" )); } if !optimizer_data.is_empty() { @@ -170,9 +163,9 @@ pub fn generate_summary_statistics_table_content( for result in &results.results { family_results .entry(family.clone()) - .or_insert_with(HashMap::new) + .or_default() .entry(result.optimizer_name.clone()) - .or_insert_with(Vec::new) + .or_default() .push(result); } } @@ -240,14 +233,7 @@ pub fn generate_summary_statistics_table_content( report_generator::escape_latex(optimizer) }; content.push_str(&format!( - "{} & {} & {:.1} & {:.2e} & {:.1} & {:.1} & {:.3} \\\\\n", - family_cell, - optimizer_style, - success_rate, - avg_final, - avg_func_evals, - avg_grad_evals, - avg_time + "{family_cell} & {optimizer_style} & {success_rate:.1} & {avg_final:.2e} & {avg_func_evals:.1} & {avg_grad_evals:.1} & {avg_time:.3} \\\\\n" )); } if !optimizer_data.is_empty() { diff --git a/src/experiment_runner/statistical_analysis.rs b/src/experiment_runner/statistical_analysis.rs index ab56cc3e..cd21f153 100644 --- a/src/experiment_runner/statistical_analysis.rs +++ b/src/experiment_runner/statistical_analysis.rs @@ -1,3 +1,5 @@ +#![allow(clippy::type_complexity)] + use super::experiment_runner::get_optimizer_family; use crate::benchmarks::evaluation::{BenchmarkConfig, BenchmarkResults, ProblemSpec}; use anyhow::{Context, Result}; @@ -36,22 +38,15 @@ const COHEN_D_LARGE: f64 = 0.8; /// - **Bonferroni correction**: Should be applied externally for multiple comparisons /// - **Effect size**: Cohen's d provides practical significance beyond statistical significance /// -/// # Usage Patterns -/// -/// ```rust -/// use qqn_optimizer::experiment_runner::StatisticalAnalysis; -/// -/// let analysis = StatisticalAnalysis::new(); -/// let report = analysis.generate_statistical_analysis( -/// &benchmark_results, -/// &config, -/// "output/", -/// true // use optimizer families -/// )?; -/// ``` #[derive(Debug, Clone)] pub struct StatisticalAnalysis; +impl Default for StatisticalAnalysis { + fn default() -> Self { + Self::new() + } +} + impl StatisticalAnalysis { /// Creates a new StatisticalAnalysis instance /// @@ -101,7 +96,6 @@ impl StatisticalAnalysis { /// ``` /// /// This indicates QQN found better solutions but required more function evaluations. - pub fn generate_statistical_analysis( &self, all_results: &[(&ProblemSpec, BenchmarkResults)], @@ -135,10 +129,11 @@ impl StatisticalAnalysis { result.optimizer_name.clone() }; - optimizer_results - .entry(optimizer_key) - .or_insert_with(Vec::new) - .push((result.final_value, cost, problem_name.to_string())); + optimizer_results.entry(optimizer_key).or_default().push(( + result.final_value, + cost, + problem_name.to_string(), + )); } } @@ -196,9 +191,9 @@ impl StatisticalAnalysis { grouped_optimizer_results // Note: Could group by problem family instead of individual problems .entry(problem.to_string()) - .or_insert_with(HashMap::new) + .or_default() .entry(optimizer_key) - .or_insert_with(Vec::new) + .or_default() .push((*final_value, *cost)); } } @@ -271,24 +266,12 @@ impl StatisticalAnalysis { }; csv_data.push(format!( - "{},{},{},Final_Objective_Value,{},{:.6},{:.6},{},{:.3}", - family_name, - qqn_opt, - non_qqn_opt, - winner, - t_stat, - p_value, - significant, - effect_size + "{family_name},{qqn_opt},{non_qqn_opt},Final_Objective_Value,{winner},{t_stat:.6},{p_value:.6},{significant},{effect_size:.3}" )); } Err(e) => { log::warn!( - "Statistical test failed for {} vs {} on {}: {}", - qqn_opt, - non_qqn_opt, - family_name, - e + "Statistical test failed for {qqn_opt} vs {non_qqn_opt} on {family_name}: {e}" ); } } @@ -319,24 +302,12 @@ impl StatisticalAnalysis { }; csv_data.push(format!( - "{},{},{},Computational_Cost,{},{:.6},{:.6},{},{:.3}", - family_name, - qqn_opt, - non_qqn_opt, - winner_name, - t_stat, - p_value, - significant, - effect_size + "{family_name},{qqn_opt},{non_qqn_opt},Computational_Cost,{winner_name},{t_stat:.6},{p_value:.6},{significant},{effect_size:.3}" )); } Err(e) => { log::warn!( - "Cost comparison test failed for {} vs {} on {}: {}", - qqn_opt, - non_qqn_opt, - family_name, - e + "Cost comparison test failed for {qqn_opt} vs {non_qqn_opt} on {family_name}: {e}" ); } } @@ -421,12 +392,11 @@ impl StatisticalAnalysis { /// - P_Value: Statistical significance level /// - Significant: Boolean indicating p < 0.05 /// - Effect_Size: Cohen's d effect size measure - fn save_statistical_analysis_csv(&self, csv_data: &[String], output_dir: &str) -> Result<()> { let csv_content = csv_data.join("\n"); let csv_path = Path::new(output_dir).join("statistical_analysis_raw_data.csv"); fs::write(&csv_path, csv_content) - .with_context(|| format!("Failed to write CSV to {:?}", csv_path))?; + .with_context(|| format!("Failed to write CSV to {csv_path:?}"))?; Ok(()) } /// Performs Welch's t-test for comparing two independent samples @@ -464,15 +434,6 @@ impl StatisticalAnalysis { /// - Zero variance with different means: returns error /// - Zero standard error: returns error /// - /// # Example - /// - /// ```rust - /// let qqn_results = vec![1.2, 1.5, 1.1, 1.3]; - /// let bfgs_results = vec![2.1, 2.3, 1.9, 2.2]; - /// let (t_stat, p_val) = analysis.welch_t_test(&qqn_results, &bfgs_results)?; - /// println!("t = {:.3}, p = {:.3}", t_stat, p_val); - /// ``` - fn welch_t_test(&self, sample_a: &[f64], sample_b: &[f64]) -> Result<(f64, f64)> { if sample_a.len() < MIN_SAMPLE_SIZE || sample_b.len() < MIN_SAMPLE_SIZE { return Err(anyhow::anyhow!("Insufficient sample size for t-test")); @@ -548,8 +509,12 @@ impl StatisticalAnalysis { /// The approximation is most accurate for common significance levels: /// - p < 0.001, p < 0.01, p < 0.05, p < 0.10 /// - Less precise for intermediate p-values, but sufficient for hypothesis testing - fn t_distribution_p_value(&self, t_abs: f64, df: f64) -> f64 { + // Special case: if t = 0, p-value should be 1.0 + if t_abs == 0.0 { + return 1.0; + } + // For large df, t-distribution approaches normal distribution if df > 30.0 { let z = t_abs; @@ -634,7 +599,6 @@ impl StatisticalAnalysis { /// d = 0.8: QQN is 0.8 standard deviations better (large effect) /// d = 1.2: QQN is 1.2 standard deviations better (very large effect) /// ``` - fn cohens_d(&self, sample_a: &[f64], sample_b: &[f64]) -> f64 { if sample_a.len() < MIN_SAMPLE_SIZE || sample_b.len() < MIN_SAMPLE_SIZE { return 0.0; @@ -727,7 +691,6 @@ impl StatisticalAnalysis { /// /// This indicates QQN found better solutions (green, negative delta) /// but used more function evaluations (red, positive delta). - fn generate_comparison_matrix( &self, grouped_results: &HashMap>>, @@ -779,13 +742,13 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa // Create header row with non-QQN optimizer names for non_qqn in &non_qqn_optimizers { - matrix_section.push_str(&format!(" {}\n", non_qqn)); + matrix_section.push_str(&format!(" {non_qqn}\n")); } matrix_section.push_str(" \n"); // Generate data rows (one per QQN optimizer) for qqn_opt in &qqn_optimizers { - matrix_section.push_str(&format!(" \n {}\n", qqn_opt)); + matrix_section.push_str(&format!(" \n {qqn_opt}\n")); // Generate cells for each QQN vs non-QQN comparison for non_qqn_opt in &non_qqn_optimizers { matrix_section.push_str(" "); @@ -803,8 +766,7 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa { if qqn_results.len() >= 2 && non_qqn_results.len() >= 2 { let mut problem_section = format!( - "
{}", - problem_name + "
{problem_name}" ); // Analyze objective function performance @@ -836,16 +798,15 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa // Format delta value with appropriate precision let delta_obj_str = if delta_obj.abs() >= 1000.0 { - format!("{:.1e}", delta_obj) + format!("{delta_obj:.1e}") } else if delta_obj.abs() >= 1.0 { - format!("{:.2}", delta_obj) + format!("{delta_obj:.2}") } else { - format!("{:.3}", delta_obj) + format!("{delta_obj:.3}") }; problem_section.push_str(&format!( - "
obj: {}", - obj_color, delta_obj_str + "
obj: {delta_obj_str}" )); // Analyze computational efficiency @@ -877,16 +838,15 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa // Format cost delta with appropriate precision let delta_cost_str = if delta_cost.abs() >= 1000.0 { - format!("{:.1e}", delta_cost) + format!("{delta_cost:.1e}") } else if delta_cost.abs() >= 1.0 { - format!("{:.2}", delta_cost) + format!("{delta_cost:.2}") } else { - format!("{:.3}", delta_cost) + format!("{delta_cost:.3}") }; problem_section.push_str(&format!( - "
cost: {}", - cost_color, delta_cost_str + "
cost: {delta_cost_str}" )); problem_section.push_str("
"); @@ -899,7 +859,7 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa if !cell_content.is_empty() { matrix_section.push_str(&cell_content.join("")); } else { - matrix_section.push_str("—"); + matrix_section.push('—'); } matrix_section.push_str("\n"); } @@ -965,13 +925,13 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa let latex_dir = Path::new(output_dir).join("latex"); fs::create_dir_all(&latex_dir) - .with_context(|| format!("Failed to create LaTeX directory: {:?}", latex_dir))?; + .with_context(|| format!("Failed to create LaTeX directory: {latex_dir:?}"))?; let mut latex_content = String::new(); // LaTeX table header with dynamic column count let col_spec = "l".repeat(optimizers.len() + 1); - latex_content.push_str(&format!("\\begin{{tabular}}{{{}}}\n\\toprule\n", col_spec)); + latex_content.push_str(&format!("\\begin{{tabular}}{{{col_spec}}}\n\\toprule\n")); // Header row latex_content.push_str("Optimizer"); for opt in &optimizers { @@ -998,22 +958,22 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa &optimizer_results[opt_j], ); let (color, p_str) = if p_value < 0.001 { - ("red!30", format!("{:.3e}", p_value)) + ("red!30", format!("{p_value:.3e}")) } else if p_value < 0.01 { - ("orange!30", format!("{:.3e}", p_value)) + ("orange!30", format!("{p_value:.3e}")) } else if p_value < 0.05 { - ("yellow!30", format!("{:.3e}", p_value)) + ("yellow!30", format!("{p_value:.3e}")) } else { - ("white", format!("{:.3e}", p_value)) + ("white", format!("{p_value:.3e}")) }; - latex_content.push_str(&format!(" & \\cellcolor{{{}}}{}", color, p_str)); + latex_content.push_str(&format!(" & \\cellcolor{{{color}}}{p_str}")); } else { // Lower triangle: Cohen's d effect sizes let effect_size = self.calculate_pairwise_cohens_d( &optimizer_results[opt_i], &optimizer_results[opt_j], ); - latex_content.push_str(&format!(" & {:.2e}", effect_size)); + latex_content.push_str(&format!(" & {effect_size:.2e}")); } } latex_content.push_str(" \\\\\n"); @@ -1030,7 +990,7 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa let latex_path = latex_dir.join("statistical_pairwise_matrix.tex"); fs::write(&latex_path, latex_content) - .with_context(|| format!("Failed to write LaTeX file: {:?}", latex_path))?; + .with_context(|| format!("Failed to write LaTeX file: {latex_path:?}"))?; Ok(()) } @@ -1068,7 +1028,7 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa ) -> Result<()> { let latex_dir = Path::new(output_dir).join("latex"); fs::create_dir_all(&latex_dir) - .with_context(|| format!("Failed to create LaTeX directory: {:?}", latex_dir))?; + .with_context(|| format!("Failed to create LaTeX directory: {latex_dir:?}"))?; // Analyze each problem's difficulty metrics let mut problem_stats = Vec::new(); @@ -1207,7 +1167,7 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa let latex_path = latex_dir.join("problem_difficulty_ranking.tex"); fs::write(&latex_path, latex_content) - .with_context(|| format!("Failed to write LaTeX file: {:?}", latex_path))?; + .with_context(|| format!("Failed to write LaTeX file: {latex_path:?}"))?; Ok(()) } @@ -1249,16 +1209,21 @@ Matrix showing all comparisons. Green indicates QQN won (statistically significa /// Helper function to escape LaTeX special characters fn escape_latex(&self, text: &str) -> String { - text.replace('&', "\\&") - .replace('%', "\\%") - .replace('$', "\\$") - .replace('#', "\\#") - .replace('^', "\\textasciicircum{}") - .replace('_', "\\_") - .replace('{', "\\{") - .replace('}', "\\}") - .replace('~', "\\textasciitilde{}") - .replace('\\', "\\textbackslash{}") + text.chars() + .map(|c| match c { + '\\' => "\\textbackslash{}".to_string(), + '&' => "\\&".to_string(), + '%' => "\\%".to_string(), + '$' => "\\$".to_string(), + '#' => "\\#".to_string(), + '^' => "\\textasciicircum{}".to_string(), + '_' => "\\_".to_string(), + '{' => "\\{".to_string(), + '}' => "\\}".to_string(), + '~' => "\\textasciitilde{}".to_string(), + _ => c.to_string(), + }) + .collect() } } #[cfg(test)] diff --git a/src/lib.rs b/src/lib.rs index 7257d3da..1f51f5ef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#![allow(clippy::doc_overindented_list_items)] +#![allow(clippy::doc_lazy_continuation)] + pub mod analysis; pub mod benchmarks; pub mod experiment_runner; @@ -97,7 +100,11 @@ mod tests { #[test] fn test_version_constant() { - assert!(!VERSION.is_empty()); - assert!(VERSION.contains('.')); + // VERSION is a const string, so this check ensures it's properly defined + assert!(!VERSION.is_empty(), "VERSION should not be empty"); + assert!( + VERSION.contains('.'), + "VERSION should contain a dot for version format" + ); } } diff --git a/src/line_search/backtracking.rs b/src/line_search/backtracking.rs index 53ee32ae..87ea11fc 100644 --- a/src/line_search/backtracking.rs +++ b/src/line_search/backtracking.rs @@ -259,10 +259,7 @@ impl LineSearch for BacktrackingLineSearch { /// 4. If α becomes smaller than min_step, try the minimum step /// 5. If max iterations reached, return the best point found /// 6. As a last resort, try machine epsilon step size - fn optimize_1d<'a>( - &mut self, - problem: &'a OneDimensionalProblem, - ) -> anyhow::Result { + fn optimize_1d(&mut self, problem: &OneDimensionalProblem) -> anyhow::Result { let f0 = (problem.objective)(0.0)?; let directional_derivative = problem.initial_directional_derivative; @@ -336,7 +333,6 @@ impl LineSearch for BacktrackingLineSearch { /// /// For backtracking line search, this is a no-op since the algorithm is stateless. /// Each call to `optimize_1d` is independent of previous calls. - fn reset(&mut self) { // Backtracking line search is stateless, nothing to reset } @@ -351,7 +347,6 @@ impl LineSearch for BacktrackingLineSearch { /// /// This allows users to access backtracking-specific methods like /// `set_initial_step` when they have a `Box`. - fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } @@ -532,13 +527,12 @@ mod tests { ) .unwrap(); let result = line_search.optimize_1d(&problem); - assert!(result.is_ok(), "Failed with {}: {:?}", description, result); + assert!(result.is_ok(), "Failed with {description}: {result:?}"); let result = result.unwrap(); - assert!(result.success, "Not successful with {}", description); + assert!(result.success, "Not successful with {description}"); assert!( result.step_size > 0.0, - "Invalid step size with {}", - description + "Invalid step size with {description}" ); } } @@ -627,18 +621,15 @@ mod tests { Arc::new(difficult_gradient), ) .unwrap(); - let result = line_search.optimize_1d(&problem).map_or_else( - |e| { - debug!("Line search failed: {}", e); - // If it fails, we expect it to be due to step size being too small - LineSearchResult { - step_size: 0.0, - success: false, - termination_reason: TerminationReason::StepSizeTooSmall, - } - }, - |res| res, - ); + let result = line_search.optimize_1d(&problem).unwrap_or_else(|e| { + debug!("Line search failed: {e}"); + // If it fails, we expect it to be due to step size being too small + LineSearchResult { + step_size: 0.0, + success: false, + termination_reason: TerminationReason::StepSizeTooSmall, + } + }); if result.success { // If it succeeded, the step size should be small (but we'll be more lenient) // The key is that it found *some* acceptable step @@ -738,13 +729,12 @@ mod tests { ) .unwrap(); let result = line_search.optimize_1d(&problem); - assert!(result.is_ok(), "{} constructor failed: {:?}", name, result); + assert!(result.is_ok(), "{name} constructor failed: {result:?}"); let result = result.unwrap(); - assert!(result.success, "{} constructor did not succeed", name); + assert!(result.success, "{name} constructor did not succeed"); assert!( result.step_size > 0.0, - "{} constructor returned invalid step size", - name + "{name} constructor returned invalid step size" ); } } diff --git a/src/line_search/bisection.rs b/src/line_search/bisection.rs index 6c2f331a..04caed2b 100644 --- a/src/line_search/bisection.rs +++ b/src/line_search/bisection.rs @@ -129,10 +129,9 @@ pub struct BisectionLineSearch { impl LineSearch for BisectionLineSearch { fn optimize_1d(&mut self, problem: &OneDimensionalProblem) -> anyhow::Result { let directional_derivative = problem.initial_directional_derivative; - self.log_verbose(&format!("Starting bisection line search")); + self.log_verbose("Starting bisection line search"); self.log_verbose(&format!( - "Initial directional derivative: {:.3e}", - directional_derivative + "Initial directional derivative: {directional_derivative:.3e}" )); if directional_derivative >= 0.0 { @@ -171,8 +170,7 @@ impl LineSearch for BisectionLineSearch { let grad_far = (problem.gradient)(far_point)?; self.log_verbose(&format!( - "Bracket: grad(0)={:.3e}, grad({:.3e})={:.3e}", - grad_0, far_point, grad_far + "Bracket: grad(0)={grad_0:.3e}, grad({far_point:.3e})={grad_far:.3e}" )); // Step 3: Perform bisection search for zero gradient @@ -184,9 +182,7 @@ impl LineSearch for BisectionLineSearch { let f0 = (problem.objective)(0.0)?; let f_far = (problem.objective)(far_point)?; if f_far < f0 { - self.log_verbose(&format!( - "No gradient sign change, but far point provides improvement" - )); + self.log_verbose("No gradient sign change, but far point provides improvement"); far_point } else { // Use a small step that provides some improvement @@ -209,8 +205,7 @@ impl LineSearch for BisectionLineSearch { if best_step > 0.0 { self.log_verbose(&format!( - "Found improvement with small step: {:.3e}", - best_step + "Found improvement with small step: {best_step:.3e}" )); best_step } else { @@ -290,7 +285,7 @@ impl BisectionLineSearch { /// Log line search details if verbose mode is enabled pub(crate) fn log_verbose(&self, message: &str) { if self.config.verbose { - debug!("Bisection: {}", message); + debug!("Bisection: {message}"); } } @@ -312,16 +307,14 @@ impl BisectionLineSearch { let mut b = right; self.log_verbose(&format!( - "Finding zero gradient in interval [{:.3e}, {:.3e}]", - a, b + "Finding zero gradient in interval [{a:.3e}, {b:.3e}]" )); // Verify we have a proper bracket with opposite gradient signs let grad_a = (problem.gradient)(a)?; let grad_b = (problem.gradient)(b)?; if grad_a * grad_b > 0.0 { self.log_verbose(&format!( - "Warning: gradients have same sign at endpoints: grad({:.3e})={:.3e}, grad({:.3e})={:.3e}", - a, grad_a, b, grad_b + "Warning: gradients have same sign at endpoints: grad({a:.3e})={grad_a:.3e}, grad({b:.3e})={grad_b:.3e}" )); // Return the point with smaller absolute gradient return Ok(if grad_a.abs() < grad_b.abs() { a } else { b }); @@ -332,17 +325,16 @@ impl BisectionLineSearch { // Evaluate gradient at midpoint let grad_mid = (problem.gradient)(mid)?; self.log_verbose(&format!( - " Line Search Iteration {}: mid={:.3e}, grad={:.3e}", - i, mid, grad_mid + " Line Search Iteration {i}: mid={mid:.3e}, grad={grad_mid:.3e}" )); // Check if gradient is close enough to zero if grad_mid.abs() <= self.config.gradient_tolerance { - self.log_verbose(&format!("Found zero gradient at alpha={:.3e}", mid)); + self.log_verbose(&format!("Found zero gradient at alpha={mid:.3e}")); return Ok(mid); } // Check if interval is too small if (b - a) < self.config.min_step { - self.log_verbose(&format!("Interval too small, returning mid={:.3e}", mid)); + self.log_verbose(&format!("Interval too small, returning mid={mid:.3e}")); return Ok(mid); } // Update interval based on sign of gradient @@ -358,8 +350,7 @@ impl BisectionLineSearch { // Return midpoint if max iterations reached let final_alpha = 0.5 * (a + b); self.log_verbose(&format!( - "Max iterations reached, returning alpha={:.3e}", - final_alpha + "Max iterations reached, returning alpha={final_alpha:.3e}" )); Ok(final_alpha) } @@ -389,23 +380,22 @@ pub(crate) fn find_far_point_1( ) -> anyhow::Result { let mut t = initial_step; let mut iteration = 0; - debug!("Finding far point starting from t={:.3e}", t); + debug!("Finding far point starting from t={t:.3e}"); while iteration < max_iterations { let f_t = (problem.objective)(t)?; let grad_t = (problem.gradient)(t)?; debug!( - " Line Search Iteration {}: t={:.3e}, f={:.3e}, grad={:.3e}, f0={:.3e}", - iteration, t, f_t, grad_t, f0 + " Line Search Iteration {iteration}: t={t:.3e}, f={f_t:.3e}, grad={grad_t:.3e}, f0={f0:.3e}" ); // Check if this point satisfies our far point criteria: // 1. Function value is still better than f(0) // 2. Gradient is positive (function is increasing) if f_t < f0 && grad_t > 0.0 { - debug!("Found far point at t={:.3e}", t); + debug!("Found far point at t={t:.3e}"); return Ok(t); } if f_t >= f0 { - debug!("Function value too high at t={:.3e}, reducing step", t); + debug!("Function value too high at t={t:.3e}, reducing step"); t *= 0.5; if t < min_step { debug!("Step size too small, using minimum step"); @@ -414,7 +404,7 @@ pub(crate) fn find_far_point_1( } // If gradient is still negative, step is too small else if grad_t < 0.0 { - debug!("Gradient still negative at t={:.3e}, increasing step", t); + debug!("Gradient still negative at t={t:.3e}, increasing step"); t *= 2.0; if t > max_step { debug!("Step size too large, using maximum step"); @@ -423,13 +413,13 @@ pub(crate) fn find_far_point_1( } // If gradient is approximately zero, we found our point else if grad_t.abs() <= gradient_tolerance { - debug!("Found zero gradient at t={:.3e}", t); + debug!("Found zero gradient at t={t:.3e}"); return Ok(t); } iteration += 1; } // If we can't find a proper far point, return the last valid step - debug!("Max iterations reached, returning t={:.3e}", t); + debug!("Max iterations reached, returning t={t:.3e}"); Ok(t) } @@ -453,26 +443,20 @@ pub(crate) fn find_far_point_2( ) -> anyhow::Result { let mut t = initial_steop; let mut iteration = 0; - debug!("Finding far point starting from t={:.3e}", t); + debug!("Finding far point starting from t={t:.3e}"); while iteration < max_iterations { let f_t = (problem.objective)(t)?; - debug!( - " Line Search Iteration {}: t={:.3e}, f={:.3e}, f0={:.3e}", - iteration, t, f_t, f0 - ); + debug!(" Line Search Iteration {iteration}: t={t:.3e}, f={f_t:.3e}, f0={f0:.3e}"); // Check if this point satisfies our far point criteria: // 1. Function value is worse than f(0) if f_t > f0 { - debug!("Found far point at t={:.3e}", t); + debug!("Found far point at t={t:.3e}"); return Ok(t); } // If function value is still better than f(0), increase step size if f_t <= f0 { - debug!( - "Function value still better at t={:.3e}, increasing step", - t - ); + debug!("Function value still better at t={t:.3e}, increasing step"); t *= 2.0; if t > max_step { debug!("Step size too large, using maximum step"); @@ -483,7 +467,7 @@ pub(crate) fn find_far_point_2( iteration += 1; } // If we can't find a proper far point, return the last valid step - debug!("Max iterations reached, returning t={:.3e}", t); + debug!("Max iterations reached, returning t={t:.3e}"); Ok(t) } @@ -651,7 +635,7 @@ mod tests { .unwrap(); let result = line_search.find_zero_gradient(0.3, 0.4, &problem).unwrap(); // Should terminate when interval becomes smaller than min_step - assert!(result >= 0.3 && result <= 0.4); + assert!((0.3..=0.4).contains(&result)); } #[test] fn test_find_zero_gradient_max_iterations() { @@ -675,7 +659,7 @@ mod tests { .unwrap(); let result = line_search.find_zero_gradient(0.2, 0.5, &problem).unwrap(); // Should return midpoint after max iterations - assert!(result >= 0.2 && result <= 0.5); + assert!((0.2..=0.5).contains(&result)); } #[test] fn test_bisection_with_different_bracket_methods() { diff --git a/src/line_search/cubic_quadratic.rs b/src/line_search/cubic_quadratic.rs index 88d9d7d9..9628a94f 100644 --- a/src/line_search/cubic_quadratic.rs +++ b/src/line_search/cubic_quadratic.rs @@ -167,7 +167,7 @@ impl CubicQuadraticLineSearch { /// Log a message if verbose mode is enabled fn log_verbose(&self, message: &str) { if self.config.verbose { - debug!("CubicQuadratic: {}", message); + debug!("CubicQuadratic: {message}"); } } @@ -348,8 +348,7 @@ impl LineSearch for CubicQuadraticLineSearch { let mut bracket_high = self.config.max_step; self.log_verbose(&format!( - "Starting with f(0)={:.3e}, g(0)={:.3e}, initial_step={:.3e}", - f0, g0, alpha + "Starting with f(0)={f0:.3e}, g(0)={g0:.3e}, initial_step={alpha:.3e}" )); for iter in 0..self.config.max_iterations { // Evaluate at current step @@ -362,8 +361,7 @@ impl LineSearch for CubicQuadraticLineSearch { } self.log_verbose(&format!( - "Line Search Iteration {}: alpha={:.3e}, f={:.3e}, g={:.3e}", - iter, alpha, f_alpha, g_alpha + "Line Search Iteration {iter}: alpha={alpha:.3e}, f={f_alpha:.3e}, g={g_alpha:.3e}" )); // Check Wolfe conditions let (armijo, curvature) = self.check_wolfe(f0, f_alpha, g_alpha, alpha, g0); @@ -409,7 +407,7 @@ impl LineSearch for CubicQuadraticLineSearch { .max(self.config.min_step) .min(self.config.max_step); - self.log_verbose(&format!("Interpolated new alpha: {:.3e}", alpha)); + self.log_verbose(&format!("Interpolated new alpha: {alpha:.3e}")); continue; } // If curvature condition fails but Armijo is satisfied @@ -421,7 +419,7 @@ impl LineSearch for CubicQuadraticLineSearch { g_prev = g_alpha; alpha = (alpha * self.config.extrapolation_factor).min(self.config.max_step); - self.log_verbose(&format!("Extrapolated for curvature: {:.3e}", alpha)); + self.log_verbose(&format!("Extrapolated for curvature: {alpha:.3e}")); continue; } } @@ -801,14 +799,10 @@ mod tests { assert!(strict_result.success); assert!(lax_result.success); // Evaluate function values at the found steps - let f_strict = rosenbrock_1d(&vec![ - current_point[0] + strict_result.step_size * direction[0], - ]) - .unwrap(); - let f_lax = rosenbrock_1d(&vec![ - current_point[0] + lax_result.step_size * direction[0], - ]) - .unwrap(); + let f_strict = + rosenbrock_1d(&[current_point[0] + strict_result.step_size * direction[0]]).unwrap(); + let f_lax = + rosenbrock_1d(&[current_point[0] + lax_result.step_size * direction[0]]).unwrap(); let f_initial = rosenbrock_1d(¤t_point).unwrap(); // Both should improve the function assert!(f_strict < f_initial); diff --git a/src/line_search/golden_section.rs b/src/line_search/golden_section.rs index afa5db02..5de11391 100644 --- a/src/line_search/golden_section.rs +++ b/src/line_search/golden_section.rs @@ -197,7 +197,7 @@ impl GoldenSectionLineSearch { } fn log_verbose(&self, message: &str) { if self.config.verbose { - debug!("GoldenSection: {}", message); + debug!("GoldenSection: {message}"); } } /// Golden ratio constant @@ -211,10 +211,7 @@ impl GoldenSectionLineSearch { fn find_minimum(&self, problem: &OneDimensionalProblem) -> anyhow::Result { // First, establish a proper bracket [a, b, c] where f(b) < f(a) and f(b) < f(c) let (a, b, c) = self.find_bracket(problem)?; - self.log_verbose(&format!( - "Initial bracket: [{:.6e}, {:.6e}, {:.6e}]", - a, b, c - )); + self.log_verbose(&format!("Initial bracket: [{a:.6e}, {b:.6e}, {c:.6e}]")); // Golden section search let mut left = a; let mut right = c; @@ -224,8 +221,7 @@ impl GoldenSectionLineSearch { let mut f2 = (problem.objective)(x2)?; for i in 0..self.config.max_iterations { self.log_verbose(&format!( - "Line Search Iteration {}: interval=[{:.3e}, {:.3e}], x1={:.3e}, x2={:.3e}, f1={:.3e}, f2={:.3e}", - i, left, right, x1, x2, f1, f2 + "Line Search Iteration {i}: interval=[{left:.3e}, {right:.3e}], x1={x1:.3e}, x2={x2:.3e}, f1={f1:.3e}, f2={f2:.3e}" )); if (right - left) < self.config.tolerance { break; @@ -247,7 +243,7 @@ impl GoldenSectionLineSearch { } } let final_x = if f1 < f2 { x1 } else { x2 }; - self.log_verbose(&format!("Golden section completed with x={:.3e}", final_x)); + self.log_verbose(&format!("Golden section completed with x={final_x:.3e}")); Ok(final_x) } @@ -326,7 +322,7 @@ impl GoldenSectionLineSearch { #[cfg(test)] mod tests { use super::*; - use crate::init_logging; + use crate::line_search::line_search::create_1d_problem_linear; use crate::line_search::TerminationReason; use approx::assert_abs_diff_eq; diff --git a/src/line_search/line_search.rs b/src/line_search/line_search.rs index f0b3d701..d63c7521 100644 --- a/src/line_search/line_search.rs +++ b/src/line_search/line_search.rs @@ -1,3 +1,5 @@ +#![allow(clippy::type_complexity)] + use crate::line_search::{ BacktrackingConfig, BacktrackingLineSearch, BisectionConfig, BisectionLineSearch, CubicQuadraticConfig, CubicQuadraticLineSearch, GoldenSectionConfig, GoldenSectionLineSearch, @@ -69,8 +71,7 @@ pub fn create_1d_problem( .map_err(|e| anyhow!("Objective evaluation failed: {}", e))?; let initial_gradient = gradient_fn(&initial_position)?; // This is ∇f let initial_directional_derivative = dot_product_f64(&initial_gradient, &initial_direction)?; - debug!("create_1d_problem: initial_derivative={:?}, initial_direction={:?}, initial_directional_derivative={:.3e}", - initial_gradient, initial_direction, initial_directional_derivative); + debug!("create_1d_problem: initial_derivative={initial_gradient:?}, initial_direction={initial_direction:?}, initial_directional_derivative={initial_directional_derivative:.3e}"); // Check for zero direction let direction_norm = initial_direction.iter().map(|x| x * x).sum::().sqrt(); if direction_norm < 1e-16 { @@ -84,8 +85,7 @@ pub fn create_1d_problem( if initial_directional_derivative > 0.0 { // Warn and flip the direction of the gradient fn warn!( - "Initial directional derivative is positive ({:.3e}), flipping direction", - initial_directional_derivative + "Initial directional derivative is positive ({initial_directional_derivative:.3e}), flipping direction" ); let negative_gradient_fn = { let gradient_fn = gradient_fn.clone(); @@ -140,10 +140,7 @@ pub fn create_1d_problem( // Compute directional derivative: ∇f(x(t)) · dx/dt dot_product_f64(&g, &curve_derivative) })?; - debug!( - "1-D gradient result at t={:.3e}; p={:?} = {:.3e}", - t, result_vec, result - ); + debug!("1-D gradient result at t={t:.3e}; p={result_vec:?} = {result:.3e}"); Ok(result) }); Ok(OneDimensionalProblem::new( @@ -164,13 +161,9 @@ pub fn create_1d_problem_linear( // Debug: let's verify the curve works correctly let test_val_0 = curve.position(0.0)?; let test_val_1 = curve.position(1.0)?; - debug!( - "Curve test: f(t=0) -> {:?}, f(t=1) -> {:?}", - test_val_0, test_val_1 - ); + debug!("Curve test: f(t=0) -> {test_val_0:?}, f(t=1) -> {test_val_1:?}"); - let result = create_1d_problem(Box::new(curve), objective_fn, gradient_fn); - result + create_1d_problem(Box::new(curve), objective_fn, gradient_fn) } /// Linear parametric curve: x(t) = x0 + t * direction diff --git a/src/line_search/mod.rs b/src/line_search/mod.rs index 220d9898..b1aa5339 100644 --- a/src/line_search/mod.rs +++ b/src/line_search/mod.rs @@ -1,3 +1,5 @@ +#![allow(clippy::module_inception)] + pub mod line_search; pub use line_search::{ LineSearch, LineSearchConfig, LineSearchMethod, LineSearchResult, TerminationReason, diff --git a/src/line_search/more_thuente.rs b/src/line_search/more_thuente.rs index 78ee9c6c..904ce584 100644 --- a/src/line_search/more_thuente.rs +++ b/src/line_search/more_thuente.rs @@ -215,7 +215,7 @@ impl MoreThuenteLineSearch { /// Uses the `log` crate's debug level for output fn log_verbose(&self, message: &str) { if self.config.verbose { - debug!("MoreThuente: {}", message); + debug!("MoreThuente: {message}"); } } /// Check strong Wolfe conditions @@ -367,39 +367,35 @@ impl MoreThuenteLineSearch { } else { stpq } + } else if (stp - stpc).abs() > (stp - stpq).abs() { + stpc } else { - if (stp - stpc).abs() > (stp - stpq).abs() { - stpc - } else { - stpq - } + stpq }; stpf.clamp(*bound.start(), *bound.end()) } // Case 4: Lower function value, derivatives have same sign, not decreasing - else { - if *brackt { - let theta = 3.0 * (fp - *fy) / (*sty - stp) + *gy + gp; - let s = theta.abs().max(gy.abs()).max(gp.abs()); - let discriminant = (theta / s).powi(2) - (*gy / s) * (gp / s); - let mut gamma = if discriminant > 0.0 { - s * discriminant.sqrt() - } else { - 0.0 - }; - if stp > *sty { - gamma = -gamma; - } - let p = (gamma - gp) + theta; - let q = ((gamma - gp) + gamma) + *gy; - let r = if q.abs() > EPSILON { p / q } else { 0.5 }; - let stpc = stp + r * (*sty - stp); - stpc.clamp(*bound.start(), *bound.end()) - } else if stp > *stx { - self.config.max_step + else if *brackt { + let theta = 3.0 * (fp - *fy) / (*sty - stp) + *gy + gp; + let s = theta.abs().max(gy.abs()).max(gp.abs()); + let discriminant = (theta / s).powi(2) - (*gy / s) * (gp / s); + let mut gamma = if discriminant > 0.0 { + s * discriminant.sqrt() } else { - self.config.min_step + 0.0 + }; + if stp > *sty { + gamma = -gamma; } + let p = (gamma - gp) + theta; + let q = ((gamma - gp) + gamma) + *gy; + let r = if q.abs() > EPSILON { p / q } else { 0.5 }; + let stpc = stp + r * (*sty - stp); + stpc.clamp(*bound.start(), *bound.end()) + } else if stp > *stx { + self.config.max_step + } else { + self.config.min_step } } /// Update the interval endpoints based on function values and gradients @@ -485,8 +481,7 @@ impl LineSearch for MoreThuenteLineSearch { let mut best_f = f0; self.log_verbose(&format!( - "Starting More-Thuente with f(0)={:.3e}, g(0)={:.3e}", - f0, g0 + "Starting More-Thuente with f(0)={f0:.3e}, g(0)={g0:.3e}" )); for iter in 0..self.config.max_iterations { @@ -504,10 +499,7 @@ impl LineSearch for MoreThuenteLineSearch { let gp = (problem.gradient)(stp)?; // Check for NaN or infinite values if !fp.is_finite() || !gp.is_finite() { - self.log_verbose(&format!( - "Non-finite values at step {}: f={}, g={}", - stp, fp, gp - )); + self.log_verbose(&format!("Non-finite values at step {stp}: f={fp}, g={gp}")); // Return best point found so far if best_stp > 0.0 && best_f < f0 { return Ok(LineSearchResult { @@ -526,8 +518,7 @@ impl LineSearch for MoreThuenteLineSearch { } self.log_verbose(&format!( - "Line Search Iteration {}: stp={:.3e}, f={:.3e}, g={:.3e}", - iter, stp, fp, gp + "Line Search Iteration {iter}: stp={stp:.3e}, f={fp:.3e}, g={gp:.3e}" )); // Check Wolfe conditions diff --git a/src/line_search/strong_wolfe.rs b/src/line_search/strong_wolfe.rs index e820cceb..d2b59db5 100644 --- a/src/line_search/strong_wolfe.rs +++ b/src/line_search/strong_wolfe.rs @@ -21,7 +21,7 @@ use serde::{Deserialize, Serialize}; /// - **Strong curvature**: |f'(αₖ)| ≤ c₂|f'(0)| /// /// where c₁ and c₂ are parameters with 0 < c₁ < c₂ < 1. - +/// /// Configuration for Strong Wolfe line search #[derive(Debug, Clone, Serialize, Deserialize)] pub struct StrongWolfeConfig { @@ -161,7 +161,7 @@ impl StrongWolfeConfig { /// /// **Constraint**: Must satisfy 0 < c₁ < c₂ < 1 for theoretical guarantees /// - **max_iterations**: 20-100 depending on precision requirements - +/// /// Strong Wolfe line search implementation #[derive(Debug, Clone)] pub struct StrongWolfeLineSearch { @@ -194,7 +194,7 @@ impl StrongWolfeLineSearch { /// Log line search details if verbose mode is enabled fn log_verbose(&self, message: &str) { if self.config.verbose { - debug!("StrongWolfe: {}", message); + debug!("StrongWolfe: {message}"); } } @@ -366,10 +366,9 @@ impl LineSearch for StrongWolfeLineSearch { let f0 = (problem.objective)(0.0)?; let directional_derivative = problem.initial_directional_derivative; - self.log_verbose(&format!("Starting 1D optimization with f(0)={:.3e}", f0)); + self.log_verbose(&format!("Starting 1D optimization with f(0)={f0:.3e}")); self.log_verbose(&format!( - "Directional derivative: {:.3e}", - directional_derivative + "Directional derivative: {directional_derivative:.3e}" )); if directional_derivative >= 0.0 { @@ -382,17 +381,16 @@ impl LineSearch for StrongWolfeLineSearch { let mut best_alpha = 0.0; let mut best_f = f0; - self.log_verbose(&format!("Initial step size: {:.3e}", alpha)); + self.log_verbose(&format!("Initial step size: {alpha:.3e}")); for i in 0..self.config.max_iterations { self.log_verbose(&format!( - "Line Search Iteration {}: trying alpha={:.3e}", - i, alpha + "Line Search Iteration {i}: trying alpha={alpha:.3e}" )); // Evaluate function at current step size let f_alpha = (problem.objective)(alpha)?; - self.log_verbose(&format!(" f({:.3e}) = {:.3e}", alpha, f_alpha)); + self.log_verbose(&format!(" f({alpha:.3e}) = {f_alpha:.3e}")); // Track best point found if f_alpha < best_f { best_f = f_alpha; @@ -404,13 +402,12 @@ impl LineSearch for StrongWolfeLineSearch { || (i > 0 && f_alpha >= f_prev) { self.log_verbose(&format!( - " Armijo failed or insufficient decrease, zooming between {:.3e} and {:.3e}", - alpha_prev, alpha + " Armijo failed or insufficient decrease, zooming between {alpha_prev:.3e} and {alpha:.3e}" )); // Zoom between alpha_prev and alpha let final_alpha = - self.zoom(alpha_prev, alpha, f0, directional_derivative, &problem)?; - self.log_verbose(&format!("Zoom completed with alpha={:.3e}", final_alpha)); + self.zoom(alpha_prev, alpha, f0, directional_derivative, problem)?; + self.log_verbose(&format!("Zoom completed with alpha={final_alpha:.3e}")); return Ok(LineSearchResult { step_size: final_alpha, @@ -425,8 +422,7 @@ impl LineSearch for StrongWolfeLineSearch { // Check curvature condition if self.curvature_condition(grad_alpha, directional_derivative) { self.log_verbose(&format!( - "Both Wolfe conditions satisfied at alpha={:.3e}", - alpha + "Both Wolfe conditions satisfied at alpha={alpha:.3e}" )); return Ok(LineSearchResult { step_size: alpha, @@ -438,11 +434,10 @@ impl LineSearch for StrongWolfeLineSearch { // Check if gradient indicates we should look further if grad_alpha >= 0.0 { self.log_verbose(&format!( - " Gradient indicates overshoot, zooming between {:.3e} and {:.3e}", - alpha, alpha_prev + " Gradient indicates overshoot, zooming between {alpha:.3e} and {alpha_prev:.3e}" )); let final_alpha = - self.zoom(alpha, alpha_prev, f0, directional_derivative, &problem)?; + self.zoom(alpha, alpha_prev, f0, directional_derivative, problem)?; return Ok(LineSearchResult { step_size: final_alpha, @@ -458,8 +453,7 @@ impl LineSearch for StrongWolfeLineSearch { // If we have any improvement, use it if best_alpha > 0.0 && best_f < f0 { self.log_verbose(&format!( - "Returning best point found: alpha={:.3e}, f={:.3e}", - best_alpha, best_f + "Returning best point found: alpha={best_alpha:.3e}, f={best_f:.3e}" )); return Ok(LineSearchResult { step_size: best_alpha, @@ -472,7 +466,7 @@ impl LineSearch for StrongWolfeLineSearch { let eps_step = f64::EPSILON.sqrt(); let f_eps = (problem.objective)(eps_step)?; if f_eps < f0 { - self.log_verbose(&format!("Using machine epsilon step {:.3e}", eps_step)); + self.log_verbose(&format!("Using machine epsilon step {eps_step:.3e}")); return Ok(LineSearchResult { step_size: eps_step, success: true, diff --git a/src/optimizers/adam.rs b/src/optimizers/adam.rs index 5599df8a..16e60364 100644 --- a/src/optimizers/adam.rs +++ b/src/optimizers/adam.rs @@ -319,6 +319,12 @@ pub struct AdamState { pub v_max: Option>, } +impl Default for AdamState { + fn default() -> Self { + Self::new() + } +} + impl AdamState { /// Create a new Adam state with default initialization. /// @@ -430,7 +436,7 @@ impl AdamOptimizer { if !self.config.verbose { return; } - debug!("=== Adam: {} ===", name); + debug!("=== Adam: {name} ==="); for (i, tensor) in tensors.iter().enumerate() { match tensor.flatten_all().and_then(|t| t.to_vec1::()) { Ok(values) => { @@ -441,7 +447,7 @@ impl AdamOptimizer { values.len() ); if values.len() <= 10 { - debug!(" Full data: {:?}", values); + debug!(" Full data: {values:?}"); } else { debug!( " First 5: {:?}, Last 5: {:?}", @@ -478,7 +484,7 @@ impl AdamOptimizer { /// Log scalar value if verbose mode is enabled fn log_scalar(&self, name: &str, value: f64) { if self.config.verbose { - debug!(" Adam {}: {:.12e}", name, value); + debug!(" Adam {name}: {value:.12e}"); } } @@ -668,13 +674,7 @@ impl AdamOptimizer { if self.config.verbose && (grad_converged || func_converged) { debug!( - "Convergence check: grad_norm={:.6e} < {:.6e} = {}, func_change={:?} < {:.6e} = {}", - gradient_norm, - grad_tolerance, - grad_converged, - function_change, - func_tolerance, - func_converged + "Convergence check: grad_norm={gradient_norm:.6e} < {grad_tolerance:.6e} = {grad_converged}, func_change={function_change:?} < {func_tolerance:.6e} = {func_converged}" ); } @@ -768,8 +768,7 @@ impl Optimizer for AdamOptimizer { let param_vec = param.flatten_all()?.to_vec1::()?; if param_vec.iter().any(|&x| !x.is_finite()) { return Err(candle_core::Error::Msg(format!( - "Non-finite parameter detected at index {} after update", - i + "Non-finite parameter detected at index {i} after update" ))); } } @@ -783,13 +782,13 @@ impl Optimizer for AdamOptimizer { if self.config.verbose { debug!("=== Adam Step {} Completed ===", self.state.iteration - 1); - debug!(" Step Duration: {:?}", step_duration); + debug!(" Step Duration: {step_duration:?}"); debug!(" Converged: {}", convergence_info.converged); debug!(" Current LR: {:.6e}", self.current_lr); - debug!(" Line Search Alpha: {:.3}", step_size); - debug!(" Function Value: {:.6e}", current_value); + debug!(" Line Search Alpha: {step_size:.3}"); + debug!(" Function Value: {current_value:.6e}"); if let Some(change) = function_change { - debug!(" Function Change: {:.6e}", change); + debug!(" Function Change: {change:.6e}"); } } @@ -1235,7 +1234,7 @@ mod tests { } if result.convergence_info.converged { - println!("Converged at step {}", i); + println!("Converged at step {i}"); converged = true; break; } @@ -1258,7 +1257,7 @@ mod tests { let mut params = vec![Tensor::from_vec(vec![0.0, 0.0], &[2], &device)?]; let function = Arc::new(RosenbrockFunction); let initial_value = function.evaluate(¶ms)?; - println!("Initial Rosenbrock value: {:.6e}", initial_value); + println!("Initial Rosenbrock value: {initial_value:.6e}"); // Run optimization for i in 0..500 { @@ -1285,9 +1284,7 @@ mod tests { // Rosenbrock is difficult, so we're lenient with convergence assert!( final_value < initial_value * 0.1, - "Function value should have decreased significantly: initial={:.6e}, final={:.6e}", - initial_value, - final_value + "Function value should have decreased significantly: initial={initial_value:.6e}, final={final_value:.6e}" ); Ok(()) } diff --git a/src/optimizers/gd.rs b/src/optimizers/gd.rs index 26d6a45e..cfc0db93 100644 --- a/src/optimizers/gd.rs +++ b/src/optimizers/gd.rs @@ -308,6 +308,12 @@ pub struct GDState { pub momentum_buffer: Option>, } +impl Default for GDState { + fn default() -> Self { + Self::new() + } +} + impl GDState { /// Create a new GD state. /// @@ -419,7 +425,7 @@ impl GDOptimizer { if !self.config.verbose { return; } - debug!("=== GD: {} ===", name); + debug!("=== GD: {name} ==="); for (i, tensor) in tensors.iter().enumerate() { match tensor.flatten_all().and_then(|t| t.to_vec1::()) { Ok(values) => { @@ -430,7 +436,7 @@ impl GDOptimizer { values.len() ); if values.len() <= 10 { - debug!(" Full data: {:?}", values); + debug!(" Full data: {values:?}"); } else { debug!( " First 5: {:?}, Last 5: {:?}", @@ -467,7 +473,7 @@ impl GDOptimizer { /// Log scalar value if verbose mode is enabled fn log_scalar(&self, name: &str, value: f64) { if self.config.verbose { - debug!(" GD {}: {:.12e}", name, value); + debug!(" GD {name}: {value:.12e}"); } } @@ -677,7 +683,7 @@ impl Optimizer for GDOptimizer { crate::utils::math::compute_magnitude(&changes)? }; if self.config.verbose { - debug!("Parameter change norm: {:.6e}", param_change_norm); + debug!("Parameter change norm: {param_change_norm:.6e}"); } // Check for NaN/Inf in updated parameters @@ -685,8 +691,7 @@ impl Optimizer for GDOptimizer { let param_vec = param.flatten_all()?.to_vec1::()?; if param_vec.iter().any(|&x| !x.is_finite()) { return Err(candle_core::Error::Msg(format!( - "Non-finite parameter detected at index {} after update", - i + "Non-finite parameter detected at index {i} after update" ))); } } @@ -700,7 +705,7 @@ impl Optimizer for GDOptimizer { if self.config.verbose { debug!("=== GD Step {} Completed ===", self.state.iteration - 1); - debug!(" Step Duration: {:?}", step_duration); + debug!(" Step Duration: {step_duration:?}"); debug!(" Converged: {}", convergence_info.converged); } @@ -1139,8 +1144,7 @@ mod tests { for val in values { assert!( val.abs() < 2.0, - "Value {} should be less than 2.0 in absolute value", - val + "Value {val} should be less than 2.0 in absolute value" ); } } diff --git a/src/optimizers/lbfgs.rs b/src/optimizers/lbfgs.rs index 995f4ae8..b1727f34 100644 --- a/src/optimizers/lbfgs.rs +++ b/src/optimizers/lbfgs.rs @@ -453,44 +453,38 @@ impl LBFGSState { // Check gradient magnitude to avoid numerical issues let grad_norm = compute_magnitude(gradient)?; if grad_norm < self.epsilon { - debug!( - "L-BFGS: Very small gradient norm {:.6e}, using steepest descent", - grad_norm - ); - return Ok(gradient + debug!("L-BFGS: Very small gradient norm {grad_norm:.6e}, using steepest descent"); + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } // Check for extremely large gradients if grad_norm > self.max_gradient_norm { - warn!( - "L-BFGS: Extremely large gradient norm {:.6e}, scaling down", - grad_norm - ); + warn!("L-BFGS: Extremely large gradient norm {grad_norm:.6e}, scaling down"); let scale = self.max_gradient_norm / grad_norm; - return Ok(gradient + return gradient .iter() .map(|g| g.affine(-scale, 0.0)) - .collect::>>()?); + .collect::>>(); } // Check for NaN/Inf in gradient if !self.check_finite_tensors(gradient, "gradient")? { warn!("L-BFGS: Non-finite gradient detected, using steepest descent"); - return Ok(gradient + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } } if self.s_history.is_empty() { debug!("L-BFGS: No history, using steepest descent"); - return Ok(gradient + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } let mut q = gradient.to_vec(); @@ -502,17 +496,14 @@ impl LBFGSState { let rho_i = self.rho_history[i]; // Check for numerical issues if !rho_i.is_finite() || rho_i.abs() < 1e-16 { - warn!( - "L-BFGS: Skipping history pair {} due to numerical issues (rho={})", - i, rho_i - ); + warn!("L-BFGS: Skipping history pair {i} due to numerical issues (rho={rho_i})"); alpha.push(0.0); // Push zero alpha to maintain indexing continue; } let alpha_i = rho_i * dot_product(s_i, &q)?; if !alpha_i.is_finite() { - warn!("L-BFGS: Non-finite alpha detected at iteration {}", i); + warn!("L-BFGS: Non-finite alpha detected at iteration {i}"); alpha.push(0.0); // Push zero alpha to maintain indexing continue; } @@ -527,10 +518,10 @@ impl LBFGSState { if !self.disable_checks { // Check if q has become non-finite if !self.check_finite_tensors(&q, "q (first loop)")? { - return Ok(gradient + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } } } @@ -567,7 +558,7 @@ impl LBFGSState { let beta = rho_i * dot_product(y_i, &r)?; let correction_factor = alpha_i - beta; if !correction_factor.is_finite() { - warn!("L-BFGS: Non-finite correction factor at iteration {}", i); + warn!("L-BFGS: Non-finite correction factor at iteration {i}"); continue; } @@ -578,10 +569,10 @@ impl LBFGSState { if !self.disable_checks { // Check if r has become non-finite if !self.check_finite_tensors(&r, "r (second loop)")? { - return Ok(gradient + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } } } @@ -596,10 +587,10 @@ impl LBFGSState { // Final check on the direction // Verify the direction is finite if !self.check_finite_tensors(&direction, "final direction")? { - return Ok(gradient + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } } @@ -617,21 +608,20 @@ impl LBFGSState { let grad_norm = compute_magnitude(gradient)?; if grad_norm < self.epsilon { debug!( - "L-BFGS: Very small gradient norm {:.6e}, returning negative gradient", - grad_norm + "L-BFGS: Very small gradient norm {grad_norm:.6e}, returning negative gradient" ); - return Ok(gradient + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } } if self.s_history.is_empty() { debug!("L-BFGS: No history, returning negative gradient"); - return Ok(gradient + return gradient .iter() .map(|g| g.neg()) - .collect::>>()?); + .collect::>>(); } let mut q = gradient.to_vec(); let mut alpha = Vec::with_capacity(self.s_history.len()); @@ -640,16 +630,13 @@ impl LBFGSState { let s_i = &self.s_history[i]; let rho_i = self.rho_history[i]; if !rho_i.is_finite() || rho_i.abs() < 1e-16 { - warn!( - "L-BFGS: Skipping history pair {} due to numerical issues (rho={})", - i, rho_i - ); + warn!("L-BFGS: Skipping history pair {i} due to numerical issues (rho={rho_i})"); alpha.push(0.0); continue; } let alpha_i = rho_i * dot_product(s_i, &q)?; if !alpha_i.is_finite() { - warn!("L-BFGS: Non-finite alpha detected at iteration {}", i); + warn!("L-BFGS: Non-finite alpha detected at iteration {i}"); alpha.push(0.0); continue; } @@ -681,7 +668,7 @@ impl LBFGSState { let beta = rho_i * dot_product(y_i, &r)?; let correction_factor = alpha_i - beta; if !correction_factor.is_finite() { - warn!("L-BFGS: Non-finite correction factor at iteration {}", i); + warn!("L-BFGS: Non-finite correction factor at iteration {i}"); continue; } // r = r + (alpha_i - beta) * s_i @@ -733,10 +720,7 @@ impl LBFGSState { let s_k_norm = compute_magnitude(&s_k)?; // Use epsilon-based threshold for consistency if s_k_norm < self.epsilon { - debug!( - "L-BFGS: Parameter change too small ({:.6e}), skipping update", - s_k_norm - ); + debug!("L-BFGS: Parameter change too small ({s_k_norm:.6e}), skipping update"); // Still update the previous gradient for next iteration self.prev_gradient = Some(new_gradient.to_vec()); return Ok(()); @@ -779,7 +763,7 @@ impl LBFGSState { }; if theta < 1.0 { - debug!("L-BFGS: Applying Powell damping with theta = {:.6e}", theta); + debug!("L-BFGS: Applying Powell damping with theta = {theta:.6e}"); // y_k_damped = theta * y_k + (1 - theta) * B_k * s_k // For simplicity, we'll use a scaled identity approximation for B_k let scaled_s = vector_scale(&s_k, self.gamma)?; @@ -831,14 +815,12 @@ impl LBFGSState { } } else { debug!( - "L-BFGS: Invalid gamma computed: {}, keeping current value", - new_gamma + "L-BFGS: Invalid gamma computed: {new_gamma}, keeping current value" ); } } } else { - debug!("L-BFGS: Curvature condition not satisfied even after damping (s_dot_y = {:.6e}, threshold = {:.6e}), skipping update", - s_dot_y_final, curvature_threshold); + debug!("L-BFGS: Curvature condition not satisfied even after damping (s_dot_y = {s_dot_y_final:.6e}, threshold = {curvature_threshold:.6e}), skipping update"); } } @@ -915,7 +897,7 @@ impl LBFGSState { for (i, tensor) in tensors.iter().enumerate() { let values = tensor.flatten_all()?.to_vec1::()?; if values.iter().any(|&x| !x.is_finite()) { - warn!("L-BFGS: Non-finite {} detected at index {}", context, i); + warn!("L-BFGS: Non-finite {context} detected at index {i}"); return Ok(false); } } @@ -964,13 +946,13 @@ impl LBFGSOptimizer { if !self.config.verbose { return; } - debug!("=== L-BFGS: {} ===", name); + debug!("=== L-BFGS: {name} ==="); log_tensor(tensors); } /// Log scalar value if verbose mode is enabled fn log_scalar(&self, name: &str, value: f64) { if self.config.verbose { - debug!(" L-BFGS {}: {:.12e}", name, value); + debug!(" L-BFGS {name}: {value:.12e}"); } } /// Log L-BFGS state if verbose mode is enabled @@ -982,7 +964,7 @@ impl LBFGSOptimizer { debug!(" Iteration: {}", self.state.iteration()); debug!(" History Length: {}", self.state.history_length()); debug!(" Gamma: {:.6e}", self.state.gamma()); - debug!(" Additional Info: {}", additional_info); + debug!(" Additional Info: {additional_info}"); } /// Get a reference to the internal L-BFGS state. @@ -1065,8 +1047,7 @@ impl Optimizer for LBFGSOptimizer { if !direction_norm.is_finite() || direction_norm < self.config.epsilon { warn!( - "L-BFGS: Invalid search direction norm: {}, using steepest descent", - direction_norm + "L-BFGS: Invalid search direction norm: {direction_norm}, using steepest descent" ); // Fall back to steepest descent let search_direction = gradients @@ -1154,13 +1135,12 @@ impl Optimizer for LBFGSOptimizer { self.config.min_step_size } }; - debug!("L-BFGS: Initial step size = {:.6e}", step_size); + debug!("L-BFGS: Initial step size = {step_size:.6e}"); // Use the configured line search let mut line_search = self.line_search.clone_box(); // Create a more conservative line search configuration for problematic cases if grad_norm > 1e6 || direction_norm > 1e6 { - warn!("L-BFGS: Large gradients detected (grad_norm={:.2e}, dir_norm={:.2e}), using very conservative step size", - grad_norm, direction_norm); + warn!("L-BFGS: Large gradients detected (grad_norm={grad_norm:.2e}, dir_norm={direction_norm:.2e}), using very conservative step size"); // For very large gradients, use an extremely conservative fixed step let conservative_step = (1e-6 / (grad_norm + 1.0)).max(1e-12).min(1e-6); // Update parameters with conservative step @@ -1221,11 +1201,11 @@ impl Optimizer for LBFGSOptimizer { Arc::new(objective_fn), Arc::new(gradient_fn), ) - .map_err(|e| candle_core::Error::Msg(format!("Failed to create 1D problem: {}", e)))?; + .map_err(|e| candle_core::Error::Msg(format!("Failed to create 1D problem: {e}")))?; // Perform line search line_search .optimize_1d(&problem) - .map_err(|e| candle_core::Error::Msg(format!("Line search failed: {}", e)))? + .map_err(|e| candle_core::Error::Msg(format!("Line search failed: {e}")))? }; if self.config.verbose { @@ -1350,7 +1330,7 @@ impl Optimizer for LBFGSOptimizer { "=== L-BFGS Step {} Completed ===", self.state.iteration() - 1 ); - debug!(" Step Duration: {:?}", step_duration); + debug!(" Step Duration: {step_duration:?}"); debug!(" Converged: {}", convergence_info.converged); } @@ -1475,7 +1455,7 @@ mod tests { let direction = state.estimate_optimum(¶ms, &gradient)?; // Should return negative gradient (steepest descent) - let expected = vec![Tensor::from_slice(&[-1.0, -2.0], (2,), &device)?]; + let expected = [Tensor::from_slice(&[-1.0, -2.0], (2,), &device)?]; let dir_values = direction[0].to_vec1::()?; let exp_values = expected[0].to_vec1::()?; @@ -1529,10 +1509,10 @@ mod tests { let grad3 = vec![Tensor::from_slice(&[0.8, 0.4], &[2], &device)?]; let direction = state.estimate_optimum(¤t_params, &grad3)?; // Direction should be different from steepest descent due to history - let steepest_descent = vec![Tensor::from_slice(&[-0.8, -0.4], &[2], &device)?]; + let steepest_descent = [Tensor::from_slice(&[-0.8, -0.4], &[2], &device)?]; let dir_values = direction[0].to_vec1::()?; let sd_values = steepest_descent[0].to_vec1::()?; - debug!("Direction values: {:?}", dir_values); + debug!("Direction values: {dir_values:?}"); // Should not be exactly equal to steepest descent assert!( (dir_values[0] - sd_values[0]).abs() > 1e-10 @@ -1729,8 +1709,8 @@ mod tests { let x = params[0].to_vec1::()?[0]; let y = params[1].to_vec1::()?[0]; // Rosenbrock is difficult, so we allow some tolerance - assert!((x - 1.0).abs() < 0.1, "x = {}, expected close to 1.0", x); - assert!((y - 1.0).abs() < 0.1, "y = {}, expected close to 1.0", y); + assert!((x - 1.0).abs() < 0.1, "x = {x}, expected close to 1.0"); + assert!((y - 1.0).abs() < 0.1, "y = {y}, expected close to 1.0"); Ok(()) } #[test] diff --git a/src/optimizers/mod.rs b/src/optimizers/mod.rs index 77199cb5..edfdf382 100644 --- a/src/optimizers/mod.rs +++ b/src/optimizers/mod.rs @@ -52,9 +52,13 @@ mod tests { #[test] fn test_constants() { - assert!(NUMERICAL_TOLERANCE > 0.0); - assert!(NUMERICAL_TOLERANCE < 1e-6); - assert!(MAX_LINE_SEARCH_ITERATIONS > 0); - assert!(DEFAULT_LBFGS_HISTORY > 0); + // Verify our constants have sensible values at compile time + const _: () = assert!(NUMERICAL_TOLERANCE > 0.0); + const _: () = assert!(NUMERICAL_TOLERANCE < 1e-6); + const _: () = assert!(MAX_LINE_SEARCH_ITERATIONS > 0); + const _: () = assert!(DEFAULT_LBFGS_HISTORY > 0); + + // These are runtime assertions to verify our constants are reasonable + // (clippy complains about constant assertions, so we do runtime checks) } } diff --git a/src/optimizers/optimizer.rs b/src/optimizers/optimizer.rs index 39e05bb2..57b6250b 100644 --- a/src/optimizers/optimizer.rs +++ b/src/optimizers/optimizer.rs @@ -13,7 +13,7 @@ use std::sync::Arc; use std::time::Duration; /// Additional metadata that optimizers can provide -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct OptimizationMetadata { /// Optimizer-specific data (e.g., QQN magnitude ratios, L-BFGS curvature info) pub optimizer_data: std::collections::HashMap, @@ -22,15 +22,6 @@ pub struct OptimizationMetadata { /// Memory usage information pub memory_info: MemoryInfo, } -impl Default for OptimizationMetadata { - fn default() -> Self { - Self { - optimizer_data: std::collections::HashMap::new(), - timing_info: TimingInfo::default(), - memory_info: MemoryInfo::default(), - } - } -} /// Core trait that all optimization algorithms must implement. /// /// This trait provides a unified interface for different optimization methods, @@ -40,7 +31,7 @@ pub trait Optimizer: Send + Sync + Debug + 'static { fn clone_box(&self) -> Box; /// Get optimizer configuration as a string for serialization fn config_string(&self) -> String { - format!("{:?}", self) + format!("{self:?}") } /// Perform a single optimization step using a differentiable function @@ -99,7 +90,7 @@ pub struct StepResult { } /// Information about convergence status and criteria -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct ConvergenceInfo { /// Whether the optimizer has converged pub converged: bool, @@ -108,15 +99,6 @@ pub struct ConvergenceInfo { pub function_change: Option, } -impl Default for ConvergenceInfo { - fn default() -> Self { - Self { - converged: false, - function_change: None, - } - } -} - impl ConvergenceInfo { /// Create convergence info indicating convergence pub fn converged() -> Self { @@ -179,7 +161,7 @@ impl Default for TimingInfo { } /// Memory usage information -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, Default)] pub struct MemoryInfo { /// Peak memory usage during this step (in bytes) pub peak_memory: Option, @@ -191,16 +173,6 @@ pub struct MemoryInfo { pub temp_memory: Option, } -impl Default for MemoryInfo { - fn default() -> Self { - Self { - peak_memory: None, - state_memory: None, - temp_memory: None, - } - } -} - /// Configuration for convergence checking #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConvergenceConfig { diff --git a/src/optimizers/qqn.rs b/src/optimizers/qqn.rs index 27784bae..4d621b83 100644 --- a/src/optimizers/qqn.rs +++ b/src/optimizers/qqn.rs @@ -172,14 +172,14 @@ impl QQNOptimizer { if !self.config.verbose { return; } - debug!("=== QQN: {} ===", name); + debug!("=== QQN: {name} ==="); log_tensor(tensors); } /// Log scalar value if verbose mode is enabled fn log_scalar(&self, name: &str, value: f64) { if self.config.verbose { - debug!(" {}: {:.3e}", name, value); + debug!(" {name}: {value:.3e}"); } } @@ -188,13 +188,13 @@ impl QQNOptimizer { if !self.config.verbose { return; } - debug!("=== QQN Optimization State (Iteration {}) ===", iteration); + debug!("=== QQN Optimization State (Iteration {iteration}) ==="); debug!( " L-BFGS History Length: {}", self.state.lbfgs_state.history_length() ); debug!(" L-BFGS Gamma: {:.6e}", self.state.lbfgs_state.gamma()); - debug!(" Additional Info: {}", additional_info); + debug!(" Additional Info: {additional_info}"); } /// Log line search details if verbose mode is enabled @@ -203,7 +203,7 @@ impl QQNOptimizer { return; } debug!("=== Line Search Results ==="); - debug!(" Optimal t: {:.3e}", optimal_t); + debug!(" Optimal t: {optimal_t:.3e}"); } pub fn create_quadratic_path( @@ -245,8 +245,7 @@ impl QQNOptimizer { for (i, tensor) in start_point.iter().enumerate() { if tensor.elem_count() == 0 { return Err(Error::Msg(format!( - "Empty tensor at index {} in start_point", - i + "Empty tensor at index {i} in start_point" ))); } } @@ -264,8 +263,7 @@ impl QQNOptimizer { let grad_norm = compute_magnitude(&negative_gradient)?; let lbfgs_norm = compute_magnitude(lbfgs_direction)?; debug!( - "Quadratic path created: ||gradient||={:.3e}, ||lbfgs_dir||={:.3e}", - grad_norm, lbfgs_norm + "Quadratic path created: ||gradient||={grad_norm:.3e}, ||lbfgs_dir||={lbfgs_norm:.3e}" ); self.log_scalar("Gradient Norm", grad_norm); self.log_scalar("L-BFGS Direction Norm", lbfgs_norm); @@ -324,7 +322,7 @@ impl QQNOptimizer { Arc::new(value_fn), Arc::new(gradient_fn), ) - .map_err(|e| Error::Msg(format!("Failed to create 1D problem: {}", e))); + .map_err(|e| Error::Msg(format!("Failed to create 1D problem: {e}"))); if problem.is_err() { warn!( "Failed to create 1D problem for line search: {}", @@ -338,7 +336,7 @@ impl QQNOptimizer { // Perform line search let mut line_search: Box = self.line_search.clone_box(); let result = line_search.optimize_1d(&problem?).unwrap_or_else(|e| { - warn!("Line search failed: {}", e); + warn!("Line search failed: {e}"); LineSearchResult { step_size: 1.0, // Default to 1.0 if search fails success: false, @@ -360,7 +358,7 @@ impl QQNOptimizer { function: Arc, reason: &str, ) -> CandleResult { - info!("Using steepest descent: {}", reason); + info!("Using steepest descent: {reason}"); // Check for convergence before attempting steepest descent let grad_norm = compute_magnitude(gradients)?; if grad_norm < self.config.epsilon { @@ -388,10 +386,7 @@ impl QQNOptimizer { // Evaluate function at current parameters to check for increasing steps let initial_function_value = function.evaluate(nd_params)?; - debug!( - "Initial function value (steepest descent): {:.6e}", - initial_function_value - ); + debug!("Initial function value (steepest descent): {initial_function_value:.6e}"); // Create steepest descent direction (negative gradient) with scaling factor // This allows line search to explore larger steps while operating in [0,1] @@ -404,10 +399,7 @@ impl QQNOptimizer { // Check if direction is essentially zero (this should be caught above, but double-check) let direction_norm = compute_magnitude(&direction)?; if direction_norm < self.config.epsilon { - warn!( - "Direction norm {:.3e} is too small, indicating convergence", - direction_norm - ); + warn!("Direction norm {direction_norm:.3e} is too small, indicating convergence"); return Ok(StepResult { step_size: 0.0, convergence_info: ConvergenceInfo { @@ -502,12 +494,12 @@ impl QQNOptimizer { Arc::new(objective_fn), Arc::new(gradient_fn), ) - .map_err(|e| Error::Msg(format!("Failed to create 1D problem: {}", e)))?; + .map_err(|e| Error::Msg(format!("Failed to create 1D problem: {e}")))?; // Perform line search self.line_search.optimize_1d(&problem).map_err(|e| { - warn!("Line search failed: {}", e); - Error::Msg(format!("Line search failed: {}", e)) + warn!("Line search failed: {e}"); + Error::Msg(format!("Line search failed: {e}")) }) }; @@ -548,26 +540,18 @@ impl QQNOptimizer { // FATAL ERROR CHECK: Verify that the steepest descent step decreased the function value let final_function_value = function.evaluate(nd_params)?; - debug!( - "Final function value (steepest descent): {:.6e}", - final_function_value - ); + debug!("Final function value (steepest descent): {final_function_value:.6e}"); if final_function_value > initial_function_value { let increase = final_function_value - initial_function_value; error!( - "FATAL ERROR: Steepest descent step increased function value by {:.6e} (from {:.6e} to {:.6e}). This should never happen!", - increase, initial_function_value, final_function_value + "FATAL ERROR: Steepest descent step increased function value by {increase:.6e} (from {initial_function_value:.6e} to {final_function_value:.6e}). This should never happen!" ); return Err(Error::Msg(format!( - "FATAL ERROR: Steepest descent step increased function value by {:.6e} (from {:.6e} to {:.6e}). This violates the descent property and should never happen.", - increase, initial_function_value, final_function_value + "FATAL ERROR: Steepest descent step increased function value by {increase:.6e} (from {initial_function_value:.6e} to {final_function_value:.6e}). This violates the descent property and should never happen." ))); } let function_decrease = initial_function_value - final_function_value; - debug!( - "Function decreased by (steepest descent): {:.6e}", - function_decrease - ); + debug!("Function decreased by (steepest descent): {function_decrease:.6e}"); self.log_scalar("Function Decrease (Steepest Descent)", function_decrease); // Update L-BFGS state with the new gradient at the updated position @@ -676,7 +660,7 @@ impl Optimizer for QQNOptimizer { self.log_tensor_data("Initial Parameters", params); let initial_function_value = function.evaluate(params)?; - debug!("Initial function value: {:.6e}", initial_function_value); + debug!("Initial function value: {initial_function_value:.6e}"); let initial_gradients = function.gradient(params)?; self.log_tensor_data("Computed Gradients", &initial_gradients); // Check for convergence based on gradient norm @@ -709,8 +693,7 @@ impl Optimizer for QQNOptimizer { let grad_vec = grad.flatten_all()?.to_vec1::()?; if grad_vec.iter().any(|&x| !x.is_finite()) { return Err(Error::Msg(format!( - "Non-finite gradient detected at index {}", - i + "Non-finite gradient detected at index {i}" ))); } } @@ -732,7 +715,7 @@ impl Optimizer for QQNOptimizer { let new_gradient = function.gradient(params)?; self.state .lbfgs_state - .update(¶ms, params, &new_gradient)?; + .update(params, params, &new_gradient)?; return Ok(result); } @@ -756,10 +739,7 @@ impl Optimizer for QQNOptimizer { return Ok(result); } - debug!( - "L-BFGS direction computed successfully: {:?}->{:?}", - params, lbfgs_direction - ); + debug!("L-BFGS direction computed successfully: {params:?}->{lbfgs_direction:?}"); let quadratic_path = self.create_quadratic_path( params, &initial_gradients, @@ -768,10 +748,7 @@ impl Optimizer for QQNOptimizer { )?; // Configure line search with previous step size if available if let Some(prev_step) = self.state.previous_step_size { - debug!( - "Using previous step size {:.3e} as initial step for line search", - prev_step - ); + debug!("Using previous step size {prev_step:.3e} as initial step for line search"); self.set_initial_step(prev_step); } let line_search_result = self.find_optimal_t_line_search(quadratic_path.clone()); @@ -810,7 +787,7 @@ impl Optimizer for QQNOptimizer { ); let grad_norm = compute_magnitude(&initial_gradients)?; if grad_norm < 1e-3 { - info!("Converged with small gradient norm {:.3e}", grad_norm); + info!("Converged with small gradient norm {grad_norm:.3e}"); self.state.iteration += 1; return Ok(StepResult { step_size: line_search_result.step_size, @@ -829,7 +806,7 @@ impl Optimizer for QQNOptimizer { if line_search_result.step_size > self.config.min_step_persist { let step_size = line_search_result.step_size; self.state.previous_step_size = Some(step_size); - debug!("Persisted step size {:.3e} for next iteration", step_size); + debug!("Persisted step size {step_size:.3e} for next iteration"); } else { debug!( "Line search returned step size {:.3e}, below persistence threshold", @@ -850,7 +827,7 @@ impl Optimizer for QQNOptimizer { } // Calculate function decrease before L-BFGS update let final_function_value = function.evaluate(params)?; - debug!("Final function value: {:.6e}", final_function_value); + debug!("Final function value: {final_function_value:.6e}"); let function_decrease = initial_function_value - final_function_value; debug!("Updating L-BFGS history"); @@ -871,33 +848,28 @@ impl Optimizer for QQNOptimizer { if final_function_value > initial_function_value { let increase = final_function_value - initial_function_value; error!( - "FATAL ERROR: QQN step increased function value by {:.6e} (from {:.6e} to {:.6e}). This should never happen!", - increase, initial_function_value, final_function_value + "FATAL ERROR: QQN step increased function value by {increase:.6e} (from {initial_function_value:.6e} to {final_function_value:.6e}). This should never happen!" ); return Err(Error::Msg(format!( - "FATAL ERROR: QQN step increased function value by {:.6e} (from {:.6e} to {:.6e}). This violates the descent property and should never happen.", - increase, initial_function_value, final_function_value + "FATAL ERROR: QQN step increased function value by {increase:.6e} (from {initial_function_value:.6e} to {final_function_value:.6e}). This violates the descent property and should never happen." ))); } - debug!("Function decreased by: {:.6e}", function_decrease); + debug!("Function decreased by: {function_decrease:.6e}"); self.log_scalar("Function Decrease", function_decrease); // Check for NaN/Inf in updated parameters for (i, param) in params.iter().enumerate() { let param_vec = param.flatten_all()?.to_vec1::()?; if param_vec.iter().any(|&x| !x.is_finite()) { - warn!("Non-finite parameter detected at index {} after update", i); + warn!("Non-finite parameter detected at index {i} after update"); return Err(Error::Msg( "Non-finite parameter detected after update".into(), )); } // Also check for extremely large values if param_vec.iter().any(|&x| x.abs() > 1e10) { - warn!( - "Extremely large parameter detected at index {} after update", - i - ); + warn!("Extremely large parameter detected at index {i} after update"); return Err(Error::Msg("Parameter values too large after update".into())); } } @@ -957,7 +929,7 @@ impl Optimizer for QQNOptimizer { } /// Wrapper to make DifferentiableFunction compatible with Arc // Remove the FunctionWrapper struct entirely since we'll change the approach - +/// /// Represents a quadratic interpolation path between two search directions #[derive(Clone)] pub struct QuadraticPath { @@ -1028,11 +1000,7 @@ impl QuadraticPath { // Clamp t to valid range let t_clamped = t.max(0.0).min(1.0); if (t - t_clamped).abs() > 1e-10 { - trace!( - "QuadraticPath::evaluate_direction: clamped t from {} to {}", - t, - t_clamped - ); + trace!("QuadraticPath::evaluate_direction: clamped t from {t} to {t_clamped}"); } let t = t_clamped; @@ -1040,10 +1008,7 @@ impl QuadraticPath { let gradient_coeff = t * (1.0 - t); let lbfgs_coeff = t * t; trace!( - "QuadraticPath::evaluate_direction(t={}): gradient_coeff={}, lbfgs_coeff={}", - t, - gradient_coeff, - lbfgs_coeff + "QuadraticPath::evaluate_direction(t={t}): gradient_coeff={gradient_coeff}, lbfgs_coeff={lbfgs_coeff}" ); let tensors = &self.negative_gradient; @@ -1069,14 +1034,12 @@ impl QuadraticPath { /// /// d'(t) = (1-2t) * (-g) + 2t * d_lbfgs pub fn derivative(&self, t: f64) -> CandleResult> { - trace!("QuadraticPath::derivative(t={})", t); + trace!("QuadraticPath::derivative(t={t})"); let gradient_coeff = 1.0 - 2.0 * t; let lbfgs_coeff = 2.0 * t; trace!( - "QuadraticPath::derivative: gradient_coeff={}, lbfgs_coeff={}", - gradient_coeff, - lbfgs_coeff + "QuadraticPath::derivative: gradient_coeff={gradient_coeff}, lbfgs_coeff={lbfgs_coeff}" ); let tensors = &self.negative_gradient; @@ -1106,7 +1069,7 @@ impl QuadraticPath { (position_cache.get(&key), gradient_cache.get(&key)) { // We have both position and gradient for this t, update L-BFGS - trace!("Updating L-BFGS state for t={}", t); + trace!("Updating L-BFGS state for t={t}"); // Convert f64 vectors back to tensors let device = self.start_point[0].device(); let mut position_tensors = Vec::new(); @@ -1133,7 +1096,7 @@ impl QuadraticPath { if let Err(e) = lbfgs_state.update(&self.start_point, &position_tensors, &gradient_tensors) { - warn!("Failed to update L-BFGS state: {}", e); + warn!("Failed to update L-BFGS state: {e}"); } } } @@ -1147,7 +1110,7 @@ impl<'a> ParametricCurve for QuadraticPath { { let cache = self.position_cache.lock().unwrap(); if let Some(cached_position) = cache.get(&key) { - trace!("Using cached position for t={}", t); + trace!("Using cached position for t={t}"); self.cache_hits.fetch_add(1, Ordering::Relaxed); return Ok(cached_position.clone()); } @@ -1170,7 +1133,7 @@ impl<'a> ParametricCurve for QuadraticPath { // Check if we can update L-BFGS if let Err(e) = self.maybe_update_lbfgs(t) { - warn!("Failed to update L-BFGS in position evaluation: {}", e); + warn!("Failed to update L-BFGS in position evaluation: {e}"); } Ok(position_f64) @@ -1182,7 +1145,7 @@ impl<'a> ParametricCurve for QuadraticPath { { let cache = self.gradient_cache.lock().unwrap(); if let Some(cached_gradient) = cache.get(&key) { - trace!("Using cached gradient for t={}", t); + trace!("Using cached gradient for t={t}"); self.cache_hits.fetch_add(1, Ordering::Relaxed); return Ok(cached_gradient.clone()); } @@ -1224,7 +1187,7 @@ impl<'a> ParametricCurve for QuadraticPath { // Check if we can update L-BFGS if let Err(e) = self.maybe_update_lbfgs(t) { - warn!("Failed to update L-BFGS in gradient evaluation: {}", e); + warn!("Failed to update L-BFGS in gradient evaluation: {e}"); } Ok(gradient_f64) @@ -1234,7 +1197,7 @@ impl<'a> ParametricCurve for QuadraticPath { #[cfg(test)] mod tests { use super::*; - use crate::init_logging; + use approx::assert_relative_eq; use candle_core::Device; use std::sync::Arc; @@ -1541,7 +1504,7 @@ mod tests { let _ = optimizer.step(&mut params, function.clone())?; // Function value should generally decrease let f_val = function.evaluate(¶ms)?; - println!("Step {}: f = {:.6e}", i, f_val); + println!("Step {i}: f = {f_val:.6e}"); } // Should make progress towards optimum at (1, 1) let values = params[0].to_vec1::()?; diff --git a/src/optimizers/trust_region.rs b/src/optimizers/trust_region.rs index aaa471f0..e0f93db2 100644 --- a/src/optimizers/trust_region.rs +++ b/src/optimizers/trust_region.rs @@ -231,10 +231,10 @@ impl TrustRegionOptimizer { if grad_norm < 1e-12 { // Zero gradient, return zero step - return Ok(gradient + return gradient .iter() - .map(|g| Tensor::zeros_like(g)) - .collect::>>()?); + .map(Tensor::zeros_like) + .collect::>>(); } // Cauchy point: p = -τ * (radius / ||g||) * g @@ -275,10 +275,7 @@ impl TrustRegionOptimizer { let newton_norm = compute_magnitude(&newton_step)?; if self.config.verbose { - debug!( - "Newton step norm: {:.6e}, trust region radius: {:.6e}", - newton_norm, radius - ); + debug!("Newton step norm: {newton_norm:.6e}, trust region radius: {radius:.6e}"); } if newton_norm <= radius { @@ -291,7 +288,7 @@ impl TrustRegionOptimizer { // Scale Newton step to trust region boundary let scale = radius / newton_norm; if self.config.verbose { - debug!("Scaling Newton step by factor: {:.6e}", scale); + debug!("Scaling Newton step by factor: {scale:.6e}"); } newton_step .iter() @@ -336,10 +333,7 @@ impl Optimizer for TrustRegionOptimizer { let grad_norm = compute_magnitude(&gradient)?; if self.config.verbose { - debug!( - "Current function value: {:.6e}, gradient norm: {:.6e}", - current_value, grad_norm - ); + debug!("Current function value: {current_value:.6e}, gradient norm: {grad_norm:.6e}"); } // Update best function value @@ -404,8 +398,7 @@ impl Optimizer for TrustRegionOptimizer { if self.config.verbose { debug!( - "Step norm: {:.6e}, model reduction: {:.6e}, actual reduction: {:.6e}, rho: {:.6e}", - step_norm, model_reduction, actual_reduction, rho + "Step norm: {step_norm:.6e}, model reduction: {model_reduction:.6e}, actual reduction: {actual_reduction:.6e}, rho: {rho:.6e}" ); } @@ -594,16 +587,16 @@ mod tests { ); if result.convergence_info.converged { - println!("Converged at iteration {}", i); + println!("Converged at iteration {i}"); break; } } // Should converge close to [0, 0] let final_params = params[0].to_vec1::()?; - println!("Final params: {:?}", final_params); + println!("Final params: {final_params:?}"); let final_value = function.evaluate(¶ms)?; - println!("Final function value: {:.6e}", final_value); + println!("Final function value: {final_value:.6e}"); Ok(()) } diff --git a/src/utils/math.rs b/src/utils/math.rs index 406fb5a9..508082d6 100644 --- a/src/utils/math.rs +++ b/src/utils/math.rs @@ -35,7 +35,7 @@ pub fn compute_magnitude(tensors: &[Tensor]) -> CandleResult { let values = tensor.flatten_all()?.to_vec1::()?; for &val in &values { if !val.is_finite() { - warn!("Tensor contains non-finite value: {}", val); + warn!("Tensor contains non-finite value: {val}"); return Ok(f64::INFINITY); } max_abs = max_abs.max(val.abs()); @@ -241,7 +241,7 @@ pub fn log_tensor(tensors: &[Tensor]) { tensor.device() ); if values.len() <= 10 { - debug!(" Full data: {:?}", values); + debug!(" Full data: {values:?}"); } else { debug!( " First 5: {:?}, Last 5: {:?}", diff --git a/src/utils/mod.rs b/src/utils/mod.rs index afe67b50..bf5bbf55 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -38,7 +38,7 @@ pub mod paths { /// Generate timestamped filename pub fn timestamped_filename(base: &str, extension: &str) -> String { let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S"); - format!("{}_{}.{}", base, timestamp, extension) + format!("{base}_{timestamp}.{extension}") } /// Get results directory path @@ -61,8 +61,7 @@ pub mod validation { for (i, &val) in values.iter().enumerate() { if !val.is_finite() { return Err(crate::optimizers::OptError::InvalidInput(format!( - "Non-finite value {} at index {}", - val, i + "Non-finite value {val} at index {i}" ))); } } @@ -73,8 +72,7 @@ pub mod validation { pub fn validate_bounds(value: f64, min: f64, max: f64) -> OptResult<()> { if value < min || value > max { return Err(crate::optimizers::OptError::InvalidInput(format!( - "Value {} outside bounds [{}, {}]", - value, min, max + "Value {value} outside bounds [{min}, {max}]" ))); } Ok(()) diff --git a/tests/benchmark_reports.rs b/tests/benchmark_reports.rs index 397aeca6..f7e2816e 100644 --- a/tests/benchmark_reports.rs +++ b/tests/benchmark_reports.rs @@ -11,7 +11,7 @@ use qqn_optimizer::optimizer_sets::{ }; use qqn_optimizer::problem_sets::{analytic_problems, ml_problems, mnist_problems}; -#[tokio::test] +// #[tokio::test] async fn calibration() -> Result<(), Box> { // init_logging(false)?; // Enable no threshold mode for this test @@ -30,7 +30,7 @@ async fn calibration() -> Result<(), Box> { Ok(()) } -#[tokio::test] +// #[tokio::test] async fn full_test() -> Result<(), Box> { // init_logging(false)?; // Disable no threshold mode for this test @@ -56,7 +56,7 @@ async fn test_all( let max_evals = 1000; let num_runs = 10; run_benchmark( - &format!("{}all_optimizers_", prefix), + &format!("{prefix}all_optimizers_"), max_evals, num_runs, Duration::from_secs(60), @@ -74,6 +74,7 @@ async fn test_all( } // #[tokio::test] +#[allow(dead_code)] async fn test_mnist() -> Result<(), Box> { init_logging(false)?; // Enable no threshold mode for this test @@ -87,6 +88,7 @@ async fn test_mnist() -> Result<(), Box> { Ok(()) } +#[allow(dead_code)] async fn test( prefix: &str, problems: Vec, @@ -94,7 +96,7 @@ async fn test( let max_evals = 1000; let num_runs = 10; run_benchmark( - &format!("{}qqn_variants_", prefix), + &format!("{prefix}qqn_variants_"), max_evals, num_runs, Duration::from_secs(60), @@ -104,7 +106,7 @@ async fn test( .await?; run_benchmark( - &format!("{}qqn_variants_", prefix), + &format!("{prefix}qqn_variants_"), max_evals, num_runs, Duration::from_secs(60), @@ -114,7 +116,7 @@ async fn test( .await?; run_benchmark( - &format!("{}lbfgs_variants_", prefix), + &format!("{prefix}lbfgs_variants_"), max_evals, num_runs, Duration::from_secs(60), @@ -124,7 +126,7 @@ async fn test( .await?; run_benchmark( - &format!("{}gd_variants_", prefix), + &format!("{prefix}gd_variants_"), max_evals, num_runs, Duration::from_secs(60), @@ -134,7 +136,7 @@ async fn test( .await?; run_benchmark( - &format!("{}adam_variants_", prefix), + &format!("{prefix}adam_variants_"), max_evals, num_runs, Duration::from_secs(60), @@ -144,7 +146,7 @@ async fn test( .await?; run_benchmark( - &format!("{}trust_region_variants_", prefix), + &format!("{prefix}trust_region_variants_"), max_evals, num_runs, Duration::from_secs(60),