argmin_testfunctions/
holdertable.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//! # Holder table test function
9//!
10//! Defined as
11//!
12//! $$
13//! f(x_1,\\,x_2) = -\left|\sin(x_1)\cos(x_2)\exp\left(\left|1- \frac{\sqrt{x_1^2+x_2^2}}{\pi}\right|\right)\right|
14//! $$
15//!
16//! where $x_i \in [-10,\\,10]$.
17//!
18//! The global minima are at
19//!  * $f(x_1,\\,x_2) = f(8.05502,\\,9.66459) = -19.2085$.
20//!  * $f(x_1,\\,x_2) = f(8.05502,\\,-9.66459) = -19.2085$.
21//!  * $f(x_1,\\,x_2) = f(-8.05502,\\,9.66459) = -19.2085$.
22//!  * $f(x_1,\\,x_2) = f(-8.05502,\\,-9.66459) = -19.2085$.
23
24use num::{Float, FromPrimitive};
25use std::f64::consts::PI;
26
27/// Holder table test function.
28///
29/// Defined as
30///
31/// $$
32/// f(x_1,\\,x_2) = -\left|\sin(x_1)\cos(x_2)\exp\left(\left|1- \frac{\sqrt{x_1^2+x_2^2}}{\pi}\right|\right)\right|
33/// $$
34///
35/// where $x_i \in [-10,\\,10]$.
36///
37/// The global minima are at
38///  * $f(x_1,\\,x_2) = f(8.05502,\\,9.66459) = -19.2085$.
39///  * $f(x_1,\\,x_2) = f(8.05502,\\,-9.66459) = -19.2085$.
40///  * $f(x_1,\\,x_2) = f(-8.05502,\\,9.66459) = -19.2085$.
41///  * $f(x_1,\\,x_2) = f(-8.05502,\\,-9.66459) = -19.2085$.
42pub fn holder_table<T>(param: &[T; 2]) -> T
43where
44    T: Float + FromPrimitive,
45{
46    let [x1, x2] = *param;
47    let pi = T::from_f64(PI).unwrap();
48    let n1 = T::from_f64(1.0).unwrap();
49    -(x1.sin() * x2.cos() * (n1 - (x1.powi(2) + x2.powi(2)).sqrt() / pi).abs().exp()).abs()
50}
51
52/// Derivative of the Holder table test function.
53///
54/// The test function has a discontinuity at $\sqrt{x_1^2+x_2^2} = \pi$,
55/// hence the derivative can be [NaN](num::Float::nan)-valued.
56pub fn holder_table_derivative<T>(param: &[T; 2]) -> [T; 2]
57where
58    T: Float + FromPrimitive,
59{
60    let [x1, x2] = *param;
61
62    let pi = T::from_f64(PI).unwrap();
63    let n0 = T::from_f64(0.0).unwrap();
64    let n1 = T::from_f64(1.0).unwrap();
65    let eps = T::epsilon();
66
67    let x1sin = x1.sin();
68    let x2sin = x2.sin();
69    let x1cos = x1.cos();
70    let x2cos = x2.cos();
71    let x1sinabs = x1sin.abs();
72    let x2cosabs = x2cos.abs();
73    let a = (x1.powi(2) + x2.powi(2)).sqrt();
74    let b = a / pi - n1;
75    let c = b.abs();
76    let d = c.exp();
77
78    if a <= eps {
79        [n0, n0]
80    } else {
81        [
82            -(x1 * x1sinabs * x2cosabs * b * d) / (pi * a * c)
83                - if x1sinabs <= eps {
84                    n0
85                } else {
86                    (x1sin * x1cos * x2cosabs * d) / (x1sinabs)
87                },
88            -(x2 * x1sinabs * x2cosabs * b * d) / (pi * a * c)
89                + if x2cosabs <= eps {
90                    n0
91                } else {
92                    (x2sin * x2cos * x1sinabs * d) / (x2cosabs)
93                },
94        ]
95    }
96}
97
98/// Hessian of the Holder table test function.
99///
100/// The test function has a discontinuity at $\sqrt{x_1^2+x_2^2} = \pi$,
101/// hence the Hessian can be [NaN](num::Float::nan)-valued.
102pub fn holder_table_hessian<T>(param: &[T; 2]) -> [[T; 2]; 2]
103where
104    T: Float + FromPrimitive,
105{
106    let [x1, x2] = *param;
107
108    let pi = T::from_f64(PI).unwrap();
109    let n0 = T::from_f64(0.0).unwrap();
110    let n1 = T::from_f64(1.0).unwrap();
111    let n2 = T::from_f64(2.0).unwrap();
112    let eps = T::epsilon();
113
114    let x1sin = x1.sin();
115    let x2sin = x2.sin();
116    let x1cos = x1.cos();
117    let x2cos = x2.cos();
118    let x1sinabs = x1sin.abs();
119    let x2cosabs = x2cos.abs();
120    let a = (x1.powi(2) + x2.powi(2)).sqrt();
121    let b = a / pi - n1;
122    let c = b.abs();
123    let d = c.exp();
124
125    let d1 = (x1sinabs * x2cosabs * d)
126        * (if a <= eps {
127            n0
128        } else {
129            -b / (pi * a * c) + (x1.powi(2) * b) / (pi * a.powi(3) * c)
130                - (x1.powi(2)) / (pi.powi(2) * (x1.powi(2) + x2.powi(2)))
131        } + n1)
132        - if x1sinabs <= eps || a <= eps {
133            n0
134        } else {
135            (n2 * x1 * x1sin * x1cos * x2cosabs * b * d) / (pi * a * c * x1sinabs)
136        };
137
138    let d2 = (x1sinabs * x2cosabs * d)
139        * (if a <= eps {
140            n0
141        } else {
142            -b / (pi * a * c) + (x2.powi(2) * b) / (pi * a.powi(3) * c)
143                - (x2.powi(2)) / (pi.powi(2) * (x1.powi(2) + x2.powi(2)))
144        } + n1)
145        + if x2cosabs <= eps || a <= eps {
146            n0
147        } else {
148            (n2 * x2 * x2sin * x2cos * x1sinabs * b * d) / (pi * a * c * x2cosabs)
149        };
150
151    let offdiag = if a <= eps || x1sinabs <= eps || x2cosabs <= eps {
152        n0
153    } else {
154        d * (-(x2 * x1sin * x1cos * x2cosabs * b) / (pi * x1sinabs * a * c)
155            + (x1 * x2 * x1sinabs * x2cosabs * b) / (pi * a.powi(3) * c)
156            - (x1 * x2 * x1sinabs * x2cosabs) / (pi.powi(2) * (x1.powi(2) + x2.powi(2)))
157            + (x1 * x1sinabs * x2sin * x2cos * b) / (pi * a * c * x2cosabs)
158            + (x1sin * x1cos * x2sin * x2cos) / (x1sinabs * x2cosabs))
159    };
160
161    [[d1, offdiag], [offdiag, d2]]
162}
163
164#[cfg(test)]
165mod tests {
166    use super::*;
167    use approx::assert_relative_eq;
168    use finitediff::FiniteDiff;
169    use proptest::prelude::*;
170    use std::f32;
171
172    #[test]
173    fn test_holder_table_optimum() {
174        assert_relative_eq!(
175            holder_table(&[8.05502_f32, 9.66459_f32]),
176            -19.2085,
177            epsilon = f32::EPSILON
178        );
179        assert_relative_eq!(
180            holder_table(&[-8.05502_f32, 9.66459_f32]),
181            -19.2085,
182            epsilon = f32::EPSILON
183        );
184        assert_relative_eq!(
185            holder_table(&[8.05502_f32, -9.66459_f32]),
186            -19.2085,
187            epsilon = f32::EPSILON
188        );
189        assert_relative_eq!(
190            holder_table(&[-8.05502_f32, -9.66459_f32]),
191            -19.2085,
192            epsilon = f32::EPSILON
193        );
194
195        for p in [
196            [8.05502, 9.66459],
197            [8.05502, -9.66459],
198            [-8.05502, 9.66459],
199            [-8.05502, -9.66459],
200            [0.0, 0.0],
201        ] {
202            let deriv = holder_table_derivative(&p);
203            for i in 0..2 {
204                assert_relative_eq!(deriv[i], 0.0, epsilon = 1e-4);
205            }
206        }
207    }
208
209    proptest! {
210        #[test]
211        fn test_holder_table_derivative_finitediff(a in -10.0..10.0, b in -10.0..10.0) {
212            let param = [a, b];
213            let derivative = holder_table_derivative(&param);
214            let derivative_fd = Vec::from(param).central_diff(&|x| holder_table(&[x[0], x[1]]));
215            // println!("1: {derivative:?} at {a}/{b}");
216            // println!("2: {derivative_fd:?} at {a}/{b}");
217            for i in 0..derivative.len() {
218                assert_relative_eq!(
219                    derivative[i],
220                    derivative_fd[i],
221                    epsilon = 1e-5,
222                    max_relative = 1e-2
223                );
224            }
225        }
226    }
227
228    proptest! {
229        #[test]
230        fn test_holder_table_hessian_finitediff(a in -10.0..10.0, b in -10.0..10.0) {
231            let param = [a, b];
232            let hessian = holder_table_hessian(&param);
233            let hessian_fd =
234                Vec::from(param).forward_hessian(&|x| holder_table_derivative(&[x[0], x[1]]).to_vec());
235            let n = hessian.len();
236            // println!("1: {hessian:?} at {a}/{b}");
237            // println!("2: {hessian_fd:?} at {a}/{b}");
238            for i in 0..n {
239                assert_eq!(hessian[i].len(), n);
240                for j in 0..n {
241                    if hessian_fd[i][j].is_finite() {
242                        assert_relative_eq!(
243                            hessian[i][j],
244                            hessian_fd[i][j],
245                            epsilon = 1e-5,
246                            max_relative = 1e-2
247                        );
248                    }
249                }
250            }
251        }
252    }
253}