math_audio_optimisation/differential_evolution.rs
1use crate::{DEConfig, DEError, DEReport, DifferentialEvolution, Result};
2use ndarray::Array1;
3
4/// Runs Differential Evolution optimization on a function.
5///
6/// This is a convenience function that mirrors SciPy's `differential_evolution` API.
7/// It creates a DE optimizer with the given bounds and configuration, then runs
8/// the optimization to find the global minimum.
9///
10/// # Arguments
11///
12/// * `func` - The objective function to minimize, mapping `&Array1<f64>` to `f64`
13/// * `bounds` - Vector of (lower, upper) bound pairs for each dimension
14/// * `config` - DE configuration (use `DEConfigBuilder` to construct)
15///
16/// # Returns
17///
18/// Returns `Ok(DEReport)` containing the optimization result on success.
19///
20/// # Errors
21///
22/// Returns `DEError::InvalidBounds` if any bound pair has upper < lower.
23///
24/// # Example
25///
26/// ```rust
27/// use math_audio_optimisation::{differential_evolution, DEConfigBuilder};
28///
29/// let config = DEConfigBuilder::new()
30/// .maxiter(50)
31/// .seed(42)
32/// .build()
33/// .expect("invalid config");
34///
35/// let result = differential_evolution(
36/// &|x| x[0].powi(2) + x[1].powi(2),
37/// &[(-5.0, 5.0), (-5.0, 5.0)],
38/// config,
39/// ).expect("optimization failed");
40///
41/// assert!(result.fun < 0.01);
42/// ```
43pub fn differential_evolution<F>(
44 func: &F,
45 bounds: &[(f64, f64)],
46 config: DEConfig,
47) -> Result<DEReport>
48where
49 F: Fn(&Array1<f64>) -> f64 + Sync,
50{
51 let n = bounds.len();
52 let mut lower = Array1::<f64>::zeros(n);
53 let mut upper = Array1::<f64>::zeros(n);
54 for (i, (lo, hi)) in bounds.iter().enumerate() {
55 lower[i] = *lo;
56 upper[i] = *hi;
57 if hi < lo {
58 return Err(DEError::InvalidBounds {
59 index: i,
60 lower: *lo,
61 upper: *hi,
62 });
63 }
64 }
65 let mut de = DifferentialEvolution::new(func, lower, upper)?;
66 *de.config_mut() = config;
67 Ok(de.solve())
68}