argmin_testfunctions/
crossintray.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//! # Cross-in-tray test function
9//!
10//! Defined as
11//!
12//! $$
13//! f(x_1, x_2) = -0.0001\left(\left|\sin(x_1)\sin(x_2)
14//!               \exp\left(\left| 100 - \frac{\sqrt{x_1^2+x_2^2}}{\pi} \right|\right)\right| +
15//!               1\right)^{0.1}
16//! $$
17//!
18//! where $x_i \in [-10,\\,10]$.
19//!
20//! The global minima are at
21//!  * $f(x_1, x_2) = f(1.34941, 1.34941) = -2.06261$.
22//!  * $f(x_1, x_2) = f(1.34941, -1.34941) = -2.06261$.
23//!  * $f(x_1, x_2) = f(-1.34941, 1.34941) = -2.06261$.
24//!  * $f(x_1, x_2) = f(-1.34941, -1.34941) = -2.06261$.
25
26use std::f64::consts::PI;
27
28use num::{Float, FromPrimitive};
29
30/// Cross-in-tray test function
31///
32/// Defined as
33///
34/// $$
35/// f(x_1, x_2) = -0.0001\left(\left|\sin(x_1)\sin(x_2)
36///               \exp\left(\left| 100 - \frac{\sqrt{x_1^2+x_2^2}}{\pi} \right|\right)\right| +
37///               1\right)^{0.1}
38/// $$
39///
40/// where $x_i \in [-10,\\,10]$.
41///
42/// The global minima are at
43///  * $f(x_1, x_2) = f(1.34941, 1.34941) = -2.06261$.
44///  * $f(x_1, x_2) = f(1.34941, -1.34941) = -2.06261$.
45///  * $f(x_1, x_2) = f(-1.34941, 1.34941) = -2.06261$.
46///  * $f(x_1, x_2) = f(-1.34941, -1.34941) = -2.06261$.
47///
48/// Note: Even if the input parameters are [`f32`], internal computations will be performed in [`f64`].
49pub fn cross_in_tray<T>(param: &[T; 2]) -> T
50where
51    T: Float + Into<f64> + FromPrimitive,
52{
53    let x1: f64 = param[0].into();
54    let x2: f64 = param[1].into();
55    T::from_f64(
56        -0.0001
57            * ((x1.sin() * x2.sin() * (100.0 - (x1.powi(2) + x2.powi(2)).sqrt() / PI).abs().exp())
58                .abs()
59                + 1.0)
60                .powf(0.1),
61    )
62    .unwrap()
63}
64
65/// Derivative of Cross-in-tray test function
66///
67/// Note: Even if the input parameters are [`f32`], internal computations will be performed in [`f64`].
68pub fn cross_in_tray_derivative<T>(param: &[T; 2]) -> [T; 2]
69where
70    T: Float + Into<f64> + FromPrimitive,
71{
72    let x1: f64 = param[0].into();
73    let x2: f64 = param[1].into();
74
75    let a = (x1.powi(2) + x2.powi(2)).sqrt();
76    let b = a / PI - 100.0;
77    let c = b.abs().exp();
78    let x2sin = x2.sin();
79    let x2sinabs = x2sin.abs();
80    let x1sin = x1.sin();
81    let x1sinabs = x1sin.abs();
82    let x1cos = x1.cos();
83    let x2cos = x2.cos();
84    let denom = PI * a * b.abs();
85
86    [
87        T::from_f64({
88            -1e-5
89                * (if denom.abs() <= f64::EPSILON {
90                    0.0
91                } else {
92                    (x2sinabs * x1 * b * c * x1sinabs) / denom
93                } + if x1sinabs <= f64::EPSILON {
94                    0.0
95                } else {
96                    (x2sinabs * x1sin * x1cos * c) / x1sinabs
97                })
98                / (x2sinabs * x1sinabs * c + 1.0).powf(0.9)
99        })
100        .unwrap(),
101        T::from_f64({
102            -1e-5
103                * (if denom.abs() <= f64::EPSILON {
104                    0.0
105                } else {
106                    (x2sinabs * x2 * b * c * x1sinabs) / denom
107                } + if x2sinabs <= f64::EPSILON {
108                    0.0
109                } else {
110                    (x1sinabs * x2sin * x2cos * c) / x2sinabs
111                })
112                / (x2sinabs * x1sinabs * c + 1.0).powf(0.9)
113        })
114        .unwrap(),
115    ]
116}
117
118/// Hessian of Cross-in-tray test function
119///
120/// This function may return [NAN][f64::NAN] or [INFINITY][f64::INFINITY].
121///
122/// Note: Even if the input parameters are [`f32`], internal computations will be performed in [`f64`].
123pub fn cross_in_tray_hessian<T>(param: &[T; 2]) -> [[T; 2]; 2]
124where
125    T: Float + Into<f64> + FromPrimitive,
126{
127    let x1: f64 = param[0].into();
128    let x2: f64 = param[1].into();
129
130    let a = (x1.powi(2) + x2.powi(2)).sqrt();
131    let b = a / PI - 100.0;
132    let c = b.abs().exp();
133    let x2sin = x2.sin();
134    let x2sinabs = x2sin.abs();
135    let x1sin = x1.sin();
136    let x1sinabs = x1sin.abs();
137    let x1cos = x1.cos();
138    let x2cos = x2.cos();
139    let denom = PI * a * b.abs();
140    let d = x1sinabs * x2sinabs * b * c / denom;
141
142    let h1 = T::from_f64({
143        9.0 * 1e-6 * (d * x1 + (x1sin * x1cos * x2sinabs * c) / x1sinabs).powi(2)
144            / (x1sinabs * x2sinabs * c + 1.0).powf(1.9)
145            - 1e-5
146                * (d - x1.powi(2)
147                    * ((x1sinabs * x2sinabs * b * c) / (PI * a.powi(3) * b.abs())
148                        - (x1sinabs * x2sinabs * c) / (PI.powi(2) * (x1.powi(2) + x2.powi(2))))
149                    - x2sinabs * x1sinabs * c
150                    + (2.0 * x1sin * x1cos * x2sinabs * x1 * b * c) / (denom * x1sinabs))
151                / (x1sinabs * x2sinabs * c + 1.0).powf(0.9)
152    })
153    .unwrap();
154
155    let h2 = T::from_f64({
156        9.0 * 1e-6 * (d * x2 + (x2sin * x2cos * x1sinabs * c) / x2sinabs).powi(2)
157            / (x1sinabs * x2sinabs * c + 1.0).powf(1.9)
158            - 1e-5
159                * (d - x2.powi(2)
160                    * ((x1sinabs * x2sinabs * b * c) / (PI * a.powi(3) * b.abs())
161                        - (x1sinabs * x2sinabs * c) / (PI.powi(2) * (x1.powi(2) + x2.powi(2))))
162                    - x2sinabs * x1sinabs * c
163                    + (2.0 * x2sin * x2cos * x1sinabs * x2 * b * c) / (denom * x2sinabs))
164                / (x1sinabs * x2sinabs * c + 1.0).powf(0.9)
165    })
166    .unwrap();
167
168    let offdiag = T::from_f64({
169        (9.0 * 1e-6
170            * (x1 * d + (x1sin * x1cos * x2sinabs * c) / x1sinabs)
171            * (x2 * d + (x2sin * x2cos * x1sinabs * c) / x2sinabs))
172            / (x1sinabs * x2sinabs * c + 1.0).powf(1.9)
173            - ((x2 * x1sin * x1cos * x2sinabs * b * c) / (PI * x1sinabs * a * b.abs())
174                - (x1 * x2 * x1sinabs * x2sinabs * b * c) / (PI * a.powi(3) * b.abs())
175                + (x1 * x2 * x1sinabs * x2sinabs * c) / (PI.powi(2) * (x1.powi(2) + x2.powi(2)))
176                + (x1 * x2sin * x2cos * x1sinabs * b * c) / (PI * a * b.abs() * x2sinabs)
177                + (x1sin * x1cos * x2sin * x2cos * c) / (x1sinabs * x2sinabs))
178                / (100_000.0 * (x1sinabs * x2sinabs * c + 1.0).powf(0.9))
179    })
180    .unwrap();
181
182    [[h1, offdiag], [offdiag, h2]]
183}
184
185#[cfg(test)]
186mod tests {
187    use super::*;
188    use approx::assert_relative_eq;
189    use finitediff::FiniteDiff;
190    use proptest::prelude::*;
191    use std::f32;
192
193    #[test]
194    fn test_cross_in_tray_optimum() {
195        // This isnt exactly a great way to test this. The function can only be computed with the
196        // use of f64; however, I only have the minimum points available in f32, which is why I use
197        // the f32 EPSILONs.
198        assert_relative_eq!(
199            cross_in_tray(&[1.34941_f64, 1.34941_f64]),
200            -2.062611870,
201            epsilon = f32::EPSILON.into()
202        );
203        assert_relative_eq!(
204            cross_in_tray(&[1.34941_f64, -1.34941_f64]),
205            -2.062611870,
206            epsilon = f32::EPSILON.into()
207        );
208        assert_relative_eq!(
209            cross_in_tray(&[-1.34941_f64, 1.34941_f64]),
210            -2.062611870,
211            epsilon = f32::EPSILON.into()
212        );
213        assert_relative_eq!(
214            cross_in_tray(&[-1.34941_f64, -1.34941_f64]),
215            -2.062611870,
216            epsilon = f32::EPSILON.into()
217        );
218        assert_relative_eq!(
219            cross_in_tray(&[1.34941_f32, 1.34941_f32]),
220            -2.062611870,
221            epsilon = f32::EPSILON.into()
222        );
223        assert_relative_eq!(
224            cross_in_tray(&[1.34941_f32, -1.34941_f32]),
225            -2.062611870,
226            epsilon = f32::EPSILON.into()
227        );
228        assert_relative_eq!(
229            cross_in_tray(&[-1.34941_f32, 1.34941_f32]),
230            -2.062611870,
231            epsilon = f32::EPSILON.into()
232        );
233        assert_relative_eq!(
234            cross_in_tray(&[-1.34941_f32, -1.34941_f32]),
235            -2.062611870,
236            epsilon = f32::EPSILON.into()
237        );
238
239        for p in [
240            [1.34941_f64, 1.34941_f64],
241            [1.34941_f64, -1.34941_f64],
242            [-1.34941_f64, 1.34941_f64],
243            [-1.34941_f64, -1.34941_f64],
244            [0.0_f64, 0.0_f64],
245        ] {
246            let deriv = cross_in_tray_derivative(&p);
247            for i in 0..2 {
248                assert_relative_eq!(deriv[i], 0.0, epsilon = 1e-6);
249            }
250        }
251    }
252
253    proptest! {
254        #[test]
255        fn test_cross_in_tray_derivative_finitediff(a in -10.0..10.0, b in -10.0..10.0) {
256            let param = [a, b];
257            let derivative = cross_in_tray_derivative(&param);
258            let derivative_fd = Vec::from(param).central_diff(&|x| cross_in_tray(&[x[0], x[1]]));
259            // println!("1: {derivative:?} at {a}/{b}");
260            // println!("2: {derivative_fd:?} at {a}/{b}");
261            for i in 0..derivative.len() {
262                assert_relative_eq!(
263                    derivative[i],
264                    derivative_fd[i],
265                    epsilon = 1e-4,
266                    max_relative = 1e-2
267                );
268            }
269        }
270    }
271
272    proptest! {
273        #[test]
274        fn test_cross_in_tray_hessian_finitediff(a in -10.0..10.0, b in -10.0..10.0) {
275            let param = [a, b];
276            let hessian = cross_in_tray_hessian(&param);
277            let hessian_fd =
278                Vec::from(param).central_hessian(&|x| cross_in_tray_derivative(&[x[0], x[1]]).to_vec());
279            let n = hessian.len();
280            // println!("1: {hessian:?} at {a}/{b}");
281            // println!("2: {hessian_fd:?} at {a}/{b}");
282            for i in 0..n {
283                assert_eq!(hessian[i].len(), n);
284                for j in 0..n {
285                    if hessian[i][j].is_finite() {
286                        assert_relative_eq!(
287                            hessian[i][j],
288                            hessian_fd[i][j],
289                            epsilon = 1e-5,
290                            max_relative = 1e-2
291                        );
292                    }
293                }
294            }
295        }
296    }
297}