argmin_testfunctions/
ackley.rs

1// Copyright 2018-2024 argmin developers
2//
3// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
4// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5// http://opensource.org/licenses/MIT>, at your option. This file may not be
6// copied, modified, or distributed except according to those terms.
7
8//! # Ackley test function
9//!
10//! Defined as
11//!
12//! $$
13//! f(x_1, x_2, ..., x_n) = -a\exp\left(-b\sqrt{\frac{1}{n}\sum_{i=1}^{n}x_i^2}\right) -
14//! \exp\left(\frac{1}{n}\sum_{i=1}^{n}\cos(cx_i)\right) + a + \exp(1)
15//! $$
16//!
17//! where $x_i \in [-32.768,\\,32.768]$ and usually $a = 20$, $b = 0.2$ and $c = 2\pi$.
18//!
19//! The global minimum is at $f(x_1, x_2, ..., x_n) = f(0, 0, ..., 0) = 0$.
20
21use num::{Float, FromPrimitive};
22use std::f64::consts::PI;
23use std::iter::Sum;
24
25/// Ackley test function
26///
27/// Defined as
28///
29/// $$
30/// f(x_1, x_2, ..., x_n) = -a\exp\left(-b\sqrt{\frac{1}{n}\sum_{i=1}^{n}x_i^2}\right) -
31/// \exp\left(\frac{1}{n}\sum_{i=1}^{n}\cos(cx_i)\right) + a + \exp(1)
32/// $$
33///
34/// where $x_i \in [-32.768,\\,32.768]$ and usually $a = 20$, $b = 0.2$ and $c = 2\pi$.
35///
36/// The global minimum is at $f(x_1, x_2, ..., x_n) = f(0, 0, ..., 0) = 0$.
37pub fn ackley<T>(param: &[T]) -> T
38where
39    T: Float + FromPrimitive + Sum,
40{
41    ackley_abc(
42        param,
43        T::from_f64(20.0).unwrap(),
44        T::from_f64(0.2).unwrap(),
45        T::from_f64(2.0 * PI).unwrap(),
46    )
47}
48
49/// Ackley test function
50///
51/// The same as [`ackley`]; however, it allows to set the parameters $a$, $b$ and $c$.
52pub fn ackley_abc<T>(param: &[T], a: T, b: T, c: T) -> T
53where
54    T: Float + FromPrimitive + Sum,
55{
56    let num1 = T::from_f64(1.0).unwrap();
57    let n = T::from_usize(param.len()).unwrap();
58    -a * (-b * ((num1 / n) * param.iter().map(|x| x.powi(2)).sum()).sqrt()).exp()
59        - ((num1 / n) * param.iter().map(|x| (c * *x).cos()).sum()).exp()
60        + a
61        + num1.exp()
62}
63
64/// Derivative of Ackley test function
65///
66/// Calls [`ackley_abc_derivative`] with $a = 20$, $b = 0.2$ and $c = 2\pi$.
67pub fn ackley_derivative<T>(param: &[T]) -> Vec<T>
68where
69    T: Float + FromPrimitive + Sum,
70{
71    ackley_abc_derivative(
72        param,
73        T::from_f64(20.0).unwrap(),
74        T::from_f64(0.2).unwrap(),
75        T::from_f64(2.0 * PI).unwrap(),
76    )
77}
78
79/// Derivative of Ackley test function
80///
81/// The same as [`ackley_derivative`]; however, it allows to set the parameters $a$, $b$ and $c$.
82pub fn ackley_abc_derivative<T>(param: &[T], a: T, b: T, c: T) -> Vec<T>
83where
84    T: Float + FromPrimitive + Sum,
85{
86    let d = T::from_usize(param.len()).unwrap();
87    let n0 = T::from_f64(0.0).unwrap();
88    let eps = T::epsilon();
89
90    let norm = param.iter().map(|x| x.powi(2)).sum::<T>().sqrt();
91
92    let f1 = (c * (param.iter().map(|&x| (c * x).cos()).sum::<T>() / d).exp()) / d;
93    let f2 = if norm <= eps {
94        n0
95    } else {
96        (a * b * (-b * norm / d.sqrt()).exp()) / (d.sqrt() * norm)
97    };
98
99    param
100        .iter()
101        .map(|&x| ((c * x).sin() * f1) + x * f2)
102        .collect()
103}
104
105/// Derivative of Ackley test function
106///
107/// Calls [`ackley_abc_derivative_const`] with $a = 20$, $b = 0.2$ and $c = 2\pi$.
108///
109/// This is the const generics version, which requires the number of parameters to be known
110/// at compile time.
111pub fn ackley_derivative_const<const N: usize, T>(param: &[T; N]) -> [T; N]
112where
113    T: Float + FromPrimitive + Sum,
114{
115    ackley_abc_derivative_const(
116        param,
117        T::from_f64(20.0).unwrap(),
118        T::from_f64(0.2).unwrap(),
119        T::from_f64(2.0 * PI).unwrap(),
120    )
121}
122
123/// Derivative of Ackley test function
124///
125/// The same as [`ackley_derivative`]; however, it allows to set the parameters $a$, $b$ and $c$.
126///
127/// This is the const generics version, which requires the number of parameters to be known
128/// at compile time.
129pub fn ackley_abc_derivative_const<const N: usize, T>(param: &[T; N], a: T, b: T, c: T) -> [T; N]
130where
131    T: Float + FromPrimitive + Sum,
132{
133    let d = T::from_usize(param.len()).unwrap();
134    let n0 = T::from_f64(0.0).unwrap();
135    let eps = T::epsilon();
136
137    let norm = param.iter().map(|x| x.powi(2)).sum::<T>().sqrt();
138
139    let f1 = (c * (param.iter().map(|&x| (c * x).cos()).sum::<T>() / d).exp()) / d;
140    let f2 = if norm <= eps {
141        n0
142    } else {
143        (a * b * (-b * norm / d.sqrt()).exp()) / (d.sqrt() * norm)
144    };
145
146    let mut out = [n0; N];
147    param
148        .iter()
149        .zip(out.iter_mut())
150        .map(|(&x, o)| *o = ((c * x).sin() * f1) + x * f2)
151        .count();
152
153    out
154}
155
156/// Hessian of Ackley test function
157///
158/// Calls [`ackley_abc_hessian`] with $a = 20$, $b = 0.2$ and $c = 2\pi$.
159pub fn ackley_hessian<T>(param: &[T]) -> Vec<Vec<T>>
160where
161    T: Float + FromPrimitive + Sum + std::fmt::Debug,
162{
163    ackley_abc_hessian(
164        param,
165        T::from_f64(20.0).unwrap(),
166        T::from_f64(0.2).unwrap(),
167        T::from_f64(2.0 * PI).unwrap(),
168    )
169}
170
171/// Hessian of Ackley test function
172///
173/// The same as [`ackley_hessian`]; however, it allows to set the parameters $a$, $b$ and $c$.
174pub fn ackley_abc_hessian<T>(param: &[T], a: T, b: T, c: T) -> Vec<Vec<T>>
175where
176    T: Float + FromPrimitive + Sum,
177{
178    let du = param.len();
179    assert!(du >= 1);
180    let d = T::from_usize(du).unwrap();
181    let n0 = T::from_f64(0.0).unwrap();
182    let eps = T::epsilon();
183
184    // rename for convenience
185    let x = param;
186
187    let sqsum = param.iter().map(|x| x.powi(2)).sum::<T>();
188    let norm = sqsum.sqrt();
189    let nexp = (-b * norm / d.sqrt()).exp();
190
191    let f1 = -c.powi(2) * (x.iter().map(|&x| (c * x).cos()).sum::<T>() / d).exp();
192    let f2 = (a * b.powi(2) * nexp) / (d * sqsum);
193    let f3 = (a * b * nexp) / (d.sqrt() * norm.powi(3));
194    let f4 = (a * b * nexp) / (d.sqrt() * norm);
195    let f5 = (a * b.powi(2) * nexp) / (d * sqsum);
196    let f6 = (a * b * nexp) / (d.sqrt() * norm.powi(3));
197
198    let mut out = vec![vec![n0; du]; du];
199    for i in 0..du {
200        for j in 0..du {
201            if i == j {
202                out[i][j] = (c * x[i]).sin().powi(2) * f1 / d.powi(2)
203                    - (c * x[i]).cos() * f1 / d
204                    - if norm <= eps {
205                        n0
206                    } else {
207                        x[i].powi(2) * (f2 + f3) - f4
208                    };
209            } else {
210                let tmp = (c * x[i]).sin() * (c * x[j]).sin() * f1 / d.powi(2)
211                    + x[i] * x[j] * if norm <= eps { n0 } else { -f5 - f6 };
212                out[i][j] = tmp;
213                out[j][i] = tmp;
214            };
215        }
216    }
217
218    out
219}
220
221/// Hessian of Ackley test function
222///
223/// Calls [`ackley_abc_hessian`] with $a = 20$, $b = 0.2$ and $c = 2\pi$.
224///
225/// This is the const generics version, which requires the number of parameters to be known
226/// at compile time.
227pub fn ackley_hessian_const<const N: usize, T>(param: &[T; N]) -> [[T; N]; N]
228where
229    T: Float + FromPrimitive + Sum,
230{
231    ackley_abc_hessian_const(
232        param,
233        T::from_f64(20.0).unwrap(),
234        T::from_f64(0.2).unwrap(),
235        T::from_f64(2.0 * PI).unwrap(),
236    )
237}
238
239/// Hessian of Ackley test function
240///
241/// The same as [`ackley_hessian`]; however, it allows to set the parameters $a$, $b$ and $c$.
242pub fn ackley_abc_hessian_const<const N: usize, T>(param: &[T; N], a: T, b: T, c: T) -> [[T; N]; N]
243where
244    T: Float + FromPrimitive + Sum,
245{
246    assert!(N >= 1);
247    let d = T::from_usize(N).unwrap();
248    let n0 = T::from_f64(0.0).unwrap();
249    let eps = T::epsilon();
250
251    // rename for convenience
252    let x = param;
253
254    let sqsum = param.iter().map(|x| x.powi(2)).sum::<T>();
255    let norm = sqsum.sqrt();
256    let nexp = (-b * norm / d.sqrt()).exp();
257
258    let f1 = -c.powi(2) * (x.iter().map(|&x| (c * x).cos()).sum::<T>() / d).exp();
259    let f2 = (a * b.powi(2) * nexp) / (d * sqsum);
260    let f3 = (a * b * nexp) / (d.sqrt() * norm.powi(3));
261    let f4 = (a * b * nexp) / (d.sqrt() * norm);
262    let f5 = (a * b.powi(2) * nexp) / (d * sqsum);
263    let f6 = (a * b * nexp) / (d.sqrt() * norm.powi(3));
264
265    let mut out = [[n0; N]; N];
266    for i in 0..N {
267        for j in 0..N {
268            if i == j {
269                out[i][j] = (c * x[i]).sin().powi(2) * f1 / d.powi(2)
270                    - (c * x[i]).cos() * f1 / d
271                    - if norm <= eps {
272                        n0
273                    } else {
274                        x[i].powi(2) * (f2 + f3) - f4
275                    };
276            } else {
277                let tmp = (c * x[i]).sin() * (c * x[j]).sin() * f1 / d.powi(2)
278                    + x[i] * x[j] * if norm <= eps { n0 } else { -f5 - f6 };
279                out[i][j] = tmp;
280                out[j][i] = tmp;
281            };
282        }
283    }
284
285    out
286}
287
288#[cfg(test)]
289mod tests {
290    use super::*;
291    use approx::assert_relative_eq;
292    use finitediff::FiniteDiff;
293    use proptest::prelude::*;
294    use std::{f32, f64};
295
296    #[test]
297    fn test_ackley_optimum() {
298        assert_relative_eq!(
299            ackley(&[0.0_f32, 0.0_f32, 0.0_f32]),
300            0.0,
301            epsilon = f32::EPSILON * 10_f32
302        );
303        assert_relative_eq!(
304            ackley(&[0.0_f64, 0.0_f64, 0.0_f64]),
305            0.0,
306            epsilon = f64::EPSILON * 5_f64
307        );
308
309        let deriv = ackley_derivative(&[0.0_f64, 0.0_f64, 0.0_f64]);
310        for i in 0..3 {
311            assert_relative_eq!(deriv[i], 0.0, epsilon = f64::EPSILON * 3_f64);
312        }
313
314        let deriv = ackley_derivative_const(&[0.0_f64, 0.0_f64, 0.0_f64]);
315        for i in 0..3 {
316            assert_relative_eq!(deriv[i], 0.0, epsilon = f64::EPSILON * 3_f64);
317        }
318    }
319
320    proptest! {
321        #[test]
322        fn test_parameters(a in -5.0..5.0, b in -5.0..5.0, c in -5.0..5.0) {
323            let param = [a, b, c];
324            assert_relative_eq!(
325                ackley(&param),
326                ackley_abc(&param, 20.0, 0.2, 2.0 * PI),
327                epsilon = f64::EPSILON
328            );
329
330            let deriv1 = ackley_derivative(&param);
331            let deriv2 = ackley_abc_derivative(&param, 20.0, 0.2, 2.0 * PI);
332            for i in 0..3 {
333                assert_relative_eq!(deriv1[i], deriv2[i], epsilon = f64::EPSILON);
334            }
335
336            let deriv1 = ackley_derivative_const(&param);
337            let deriv2 = ackley_abc_derivative_const(&param, 20.0, 0.2, 2.0 * PI);
338            for i in 0..3 {
339                assert_relative_eq!(deriv1[i], deriv2[i], epsilon = f64::EPSILON);
340            }
341
342            let hessian1 = ackley_hessian(&param);
343            let hessian2 = ackley_abc_hessian(&param, 20.0, 0.2, 2.0 * PI);
344            for i in 0..3 {
345                for j in 0..3 {
346                    assert_relative_eq!(hessian1[i][j], hessian2[i][j], epsilon = f64::EPSILON);
347                }
348            }
349
350            let hessian1 = ackley_hessian_const(&param);
351            let hessian2 = ackley_abc_hessian_const(&param, 20.0, 0.2, 2.0 * PI);
352            for i in 0..3 {
353                for j in 0..3 {
354                    assert_relative_eq!(hessian1[i][j], hessian2[i][j], epsilon = f64::EPSILON);
355                }
356            }
357        }
358    }
359
360    proptest! {
361        #[test]
362        fn test_ackley_derivative_finitediff(a in -5.0..5.0,
363                                             b in -5.0..5.0,
364                                             c in -5.0..5.0,
365                                             d in -5.0..5.0,
366                                             e in -5.0..5.0,
367                                             f in -5.0..5.0,
368                                             g in -5.0..5.0,
369                                             h in -5.0..5.0) {
370            let param = [a, b, c, d, e, f, g, h];
371            let derivative = ackley_derivative(&param);
372            let derivative_fd = Vec::from(param).central_diff(&|x| ackley(&x));
373            // println!("1: {derivative:?} at {a}/{b}");
374            // println!("2: {derivative_fd:?} at {a}/{b}");
375            for i in 0..derivative.len() {
376                assert_relative_eq!(
377                    derivative[i],
378                    derivative_fd[i],
379                    epsilon = 1e-5,
380                    max_relative = 1e-2
381                );
382            }
383        }
384    }
385
386    proptest! {
387        #[test]
388        fn test_ackley_derivative_const_finitediff(a in -5.0..5.0,
389                                                   b in -5.0..5.0,
390                                                   c in -5.0..5.0,
391                                                   d in -5.0..5.0,
392                                                   e in -5.0..5.0,
393                                                   f in -5.0..5.0,
394                                                   g in -5.0..5.0,
395                                                   h in -5.0..5.0) {
396            let param = [a, b, c, d, e, f, g, h];
397            let derivative = ackley_derivative_const(&param);
398            let derivative_fd = Vec::from(param).central_diff(&|x| ackley(&x));
399            // println!("1: {derivative:?} at {a}/{b}");
400            // println!("2: {derivative_fd:?} at {a}/{b}");
401            for i in 0..derivative.len() {
402                assert_relative_eq!(
403                    derivative[i],
404                    derivative_fd[i],
405                    epsilon = 1e-5,
406                    max_relative = 1e-2
407                );
408            }
409        }
410    }
411
412    proptest! {
413        #[test]
414        fn test_ackley_hessian_finitediff(a in -5.0..5.0,
415                                          b in -5.0..5.0,
416                                          c in -5.0..5.0,
417                                          d in -5.0..5.0,
418                                          e in -5.0..5.0,
419                                          f in -5.0..5.0,
420                                          g in -5.0..5.0,
421                                          h in -5.0..5.0) {
422            let param = [a, b, c, d, e, f, g, h];
423            let hessian = ackley_hessian(&param);
424            let hessian_fd = Vec::from(param).central_hessian(&|x| ackley_derivative(&x));
425            // println!("1: {hessian:?} at {a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}");
426            // println!("2: {hessian_fd:?} at {a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}");
427            for i in 0..hessian.len() {
428                for j in 0..hessian.len() {
429                    assert_relative_eq!(
430                        hessian[i][j],
431                        hessian_fd[i][j],
432                        epsilon = 1e-5,
433                        max_relative = 1e-2
434                    );
435                }
436            }
437        }
438    }
439
440    proptest! {
441        #[test]
442        fn test_ackley_hessian_const_finitediff(a in -5.0..5.0,
443                                                b in -5.0..5.0,
444                                                c in -5.0..5.0,
445                                                d in -5.0..5.0,
446                                                e in -5.0..5.0,
447                                                f in -5.0..5.0,
448                                                g in -5.0..5.0,
449                                                h in -5.0..5.0) {
450            let param = [a, b, c, d, e, f, g, h];
451            let hessian = ackley_hessian_const(&param);
452            let hessian_fd = Vec::from(param).central_hessian(&|x| ackley_derivative(&x));
453            // println!("1: {hessian:?} at {a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}");
454            // println!("2: {hessian_fd:?} at {a}/{b}/{c}/{d}/{e}/{f}/{g}/{h}");
455            for i in 0..hessian.len() {
456                for j in 0..hessian.len() {
457                    assert_relative_eq!(
458                        hessian[i][j],
459                        hessian_fd[i][j],
460                        epsilon = 1e-5,
461                        max_relative = 1e-2
462                    );
463                }
464            }
465        }
466    }
467}