Skip to main content

basin/problems/
holder_table.rs

1//! 2D Holder table function.
2//!
3//! ```text
4//! f(x, y) = −|sin(x)·cos(y)·exp(|1 − √(x²+y²)/π|)|
5//! ```
6//!
7//! Multimodal function with many local minima and four equal global minima at
8//! `(±8.05502, ±9.66459)` with `f ≈ −19.2085`, arranged at the corners of a
9//! flat "table". The nested `|·|` terms make it non-differentiable, so it is
10//! cost-only — a target for derivative-free and global solvers. Usual search
11//! domain is `x, y ∈ [-10, 10]`.
12
13use core::marker::PhantomData;
14
15use super::spec::{Dimensionality, HasSpec, ProblemSpec, Properties, Reference};
16use crate::CostFunction;
17
18/// Standard lower bound on each coordinate.
19pub const STANDARD_LOWER: f64 = -10.0;
20/// Standard upper bound on each coordinate.
21pub const STANDARD_UPPER: f64 = 10.0;
22
23/// Evaluates the Holder table function at `x`. Requires `x.len() == 2`.
24pub fn holder_table(x: &[f64]) -> f64 {
25    debug_assert_eq!(x.len(), 2);
26    let pi = core::f64::consts::PI;
27    let (a, b) = (x[0], x[1]);
28    let inner = (1.0 - (a * a + b * b).sqrt() / pi).abs();
29    -(a.sin() * b.cos() * inner.exp()).abs()
30}
31
32/// Pre-wrapped Holder table problem (fixed 2D). Cost-only: the nested `|·|`
33/// terms make it non-differentiable, so no `Gradient` impl is provided.
34pub struct HolderTable<P = Vec<f64>>(PhantomData<fn() -> P>);
35
36impl<P> HolderTable<P> {
37    /// Build a freshly typed problem instance. Pair with one of the
38    /// backend-specific impl blocks (Vec, nalgebra, ndarray, faer).
39    pub const fn new() -> Self {
40        Self(PhantomData)
41    }
42}
43
44impl<P> Default for HolderTable<P> {
45    fn default() -> Self {
46        Self::new()
47    }
48}
49
50/// Catalogue entry for this problem.
51pub static HOLDER_TABLE_SPEC: ProblemSpec = ProblemSpec {
52    name: "Holder table",
53    dim: Dimensionality::Fixed(2),
54    properties: Properties {
55        smooth: false,
56        differentiable: false,
57        convex: false,
58        unimodal: false,
59        separable: false,
60        scalable: false,
61    },
62    references: &[Reference {
63        citation: "Jamil & Yang (2013)",
64        title: "A literature survey of benchmark functions for global optimisation problems",
65        source: "International Journal of Mathematical Modelling and Numerical Optimisation, 4(2), 150–194",
66        doi: Some("10.1504/IJMMNO.2013.055204"),
67        url: Some("https://arxiv.org/abs/1308.4008"),
68    }],
69    description: "Multimodal surface with four equal global minima at \
70                  (±8.05502, ±9.66459), value ≈ −19.2085, at the corners of a \
71                  flat table. Non-differentiable (nested |·| terms); usual \
72                  search domain is x, y ∈ [-10, 10]. Cost-only, for \
73                  derivative-free / global solvers.",
74};
75
76impl<P> HasSpec for HolderTable<P> {
77    const SPEC: &'static ProblemSpec = &HOLDER_TABLE_SPEC;
78}
79
80impl CostFunction for HolderTable<Vec<f64>> {
81    type Param = Vec<f64>;
82    type Output = f64;
83    type Error = std::convert::Infallible;
84    fn cost(&self, x: &Vec<f64>) -> Result<f64, std::convert::Infallible> {
85        Ok(holder_table(x))
86    }
87}
88
89#[cfg(feature = "nalgebra")]
90mod nalgebra_impl {
91    use super::{HolderTable, holder_table};
92    use crate::CostFunction;
93    use nalgebra::DVector;
94
95    impl CostFunction for HolderTable<DVector<f64>> {
96        type Param = DVector<f64>;
97        type Output = f64;
98        type Error = std::convert::Infallible;
99        fn cost(&self, x: &DVector<f64>) -> Result<f64, std::convert::Infallible> {
100            Ok(holder_table(x.as_slice()))
101        }
102    }
103}
104
105#[cfg(feature = "ndarray")]
106mod ndarray_impl {
107    use super::{HolderTable, holder_table};
108    use crate::CostFunction;
109    use ndarray::Array1;
110
111    impl CostFunction for HolderTable<Array1<f64>> {
112        type Param = Array1<f64>;
113        type Output = f64;
114        type Error = std::convert::Infallible;
115        fn cost(&self, x: &Array1<f64>) -> Result<f64, std::convert::Infallible> {
116            Ok(holder_table(x.as_slice().expect("Array1 is contiguous")))
117        }
118    }
119}
120
121#[cfg(feature = "faer")]
122mod faer_impl {
123    use super::HolderTable;
124    use crate::CostFunction;
125    use faer::Col;
126
127    impl CostFunction for HolderTable<Col<f64>> {
128        type Param = Col<f64>;
129        type Output = f64;
130        type Error = std::convert::Infallible;
131        fn cost(&self, x: &Col<f64>) -> Result<f64, std::convert::Infallible> {
132            debug_assert_eq!(x.nrows(), 2);
133            let pi = core::f64::consts::PI;
134            let (a, b) = (x[0], x[1]);
135            let inner = (1.0 - (a * a + b * b).sqrt() / pi).abs();
136            Ok(-(a.sin() * b.cos() * inner.exp()).abs())
137        }
138    }
139}
140
141#[cfg(test)]
142mod tests {
143    use super::*;
144
145    #[test]
146    fn known_value_at_origin() {
147        // sin(0) = 0 ⇒ the product is 0 ⇒ f(0, 0) = 0.
148        assert!(holder_table(&[0.0, 0.0]).abs() < 1e-15);
149    }
150
151    #[test]
152    fn minimum_value_at_documented_optimum() {
153        let f = holder_table(&[8.05502, 9.66459]);
154        assert!((f - (-19.2085)).abs() < 1e-3, "got {f}");
155    }
156
157    #[test]
158    fn spec_is_wired_up_via_has_spec_trait() {
159        let spec = <HolderTable<Vec<f64>> as HasSpec>::SPEC;
160        assert_eq!(spec.name, "Holder table");
161        assert!(!spec.properties.differentiable);
162        assert!(matches!(spec.dim, Dimensionality::Fixed(2)));
163        assert!(!spec.references.is_empty());
164    }
165}