Skip to main content

scirs2_optimize/derivative_free/
mod.rs

1//! Derivative-Free Optimization Methods
2//!
3//! This module provides optimization algorithms that do not require gradient information.
4//! These methods are suitable for black-box functions, noisy objectives, or functions
5//! where gradients are unavailable or too expensive to compute.
6//!
7//! # Algorithms
8//!
9//! - [`NelderMead`]: Nelder-Mead simplex method
10//! - [`BOBYQA`]: Bound Optimization BY Quadratic Approximation
11//! - [`PatternSearch`]: Generalized Pattern Search (GPS)
12//!
13//! # References
14//!
15//! - Nelder, J.A. & Mead, R. (1965). "A simplex method for function minimization"
16//! - Powell, M.J.D. (1994). "A direct search optimization method that models the objective by quadratic interpolation"
17//! - Torczon, V. (1997). "On the convergence of pattern search algorithms"
18
19use crate::error::OptimizeResult;
20use crate::result::OptimizeResults;
21use scirs2_core::ndarray::{s, Array1, Array2};
22
23pub mod bobyqa;
24pub mod nelder_mead;
25pub mod pattern_search;
26
27pub use bobyqa::{BOBYQAOptions, BOBYQASolver};
28pub use nelder_mead::{NelderMeadOptions, NelderMeadSolver};
29pub use pattern_search::{PatternSearchOptions, PatternSearchSolver};
30
31/// Derivative-free optimization result
32#[derive(Debug, Clone)]
33pub struct DfOptResult {
34    /// Optimal solution
35    pub x: Array1<f64>,
36    /// Optimal function value
37    pub fun: f64,
38    /// Number of function evaluations
39    pub nfev: usize,
40    /// Number of iterations
41    pub nit: usize,
42    /// Whether convergence was achieved
43    pub success: bool,
44    /// Status message
45    pub message: String,
46}
47
48impl From<DfOptResult> for OptimizeResults<f64> {
49    fn from(r: DfOptResult) -> Self {
50        OptimizeResults {
51            x: r.x,
52            fun: r.fun,
53            jac: None,
54            hess: None,
55            constr: None,
56            nit: r.nit,
57            nfev: r.nfev,
58            njev: 0,
59            nhev: 0,
60            maxcv: 0,
61            message: r.message,
62            success: r.success,
63            status: if r.success { 0 } else { 1 },
64        }
65    }
66}
67
68/// Trait for derivative-free optimizers
69pub trait DerivativeFreeOptimizer {
70    /// Minimize a function starting from x0.
71    fn minimize<F>(&self, func: F, x0: &[f64]) -> OptimizeResult<DfOptResult>
72    where
73        F: Fn(&[f64]) -> f64;
74}
75
76/// Compute the centroid of the best n vertices in the simplex (excluding the worst).
77pub(crate) fn centroid(simplex: &Array2<f64>, n: usize) -> Array1<f64> {
78    let mut c = Array1::zeros(n);
79    for i in 0..n {
80        c += &simplex.slice(s![i, ..]);
81    }
82    c / n as f64
83}
84
85/// L2 norm of a slice
86pub(crate) fn norm(v: &[f64]) -> f64 {
87    v.iter().map(|x| x * x).sum::<f64>().sqrt()
88}
89
90/// Finite difference gradient (central differences)
91#[allow(dead_code)]
92pub(crate) fn finite_diff_grad<F: Fn(&[f64]) -> f64>(f: &F, x: &[f64], h: f64) -> Vec<f64> {
93    let n = x.len();
94    let mut g = vec![0.0; n];
95    let mut xp = x.to_vec();
96    let mut xm = x.to_vec();
97    for i in 0..n {
98        xp[i] += h;
99        xm[i] -= h;
100        g[i] = (f(&xp) - f(&xm)) / (2.0 * h);
101        xp[i] = x[i];
102        xm[i] = x[i];
103    }
104    g
105}
106
107/// Clip a value to bounds
108#[inline]
109pub(crate) fn clip(v: f64, lo: f64, hi: f64) -> f64 {
110    v.max(lo).min(hi)
111}
112
113#[cfg(test)]
114mod tests {
115    use super::*;
116
117    #[test]
118    fn test_centroid_2d() {
119        let mut s = Array2::zeros((3, 2));
120        s[[0, 0]] = 0.0;
121        s[[0, 1]] = 0.0;
122        s[[1, 0]] = 1.0;
123        s[[1, 1]] = 0.0;
124        s[[2, 0]] = 0.0;
125        s[[2, 1]] = 1.0;
126        // centroid of first 2 rows
127        let c = centroid(&s, 2);
128        assert!((c[0] - 0.5).abs() < 1e-12);
129        assert!((c[1] - 0.0).abs() < 1e-12);
130    }
131
132    #[test]
133    fn test_norm() {
134        let v = vec![3.0_f64, 4.0];
135        assert!((norm(&v) - 5.0).abs() < 1e-12);
136    }
137}