1+ # Author: Zohaib Hassan
2+
3+ from typing import Any , Callable , Dict , List , Optional , Union
4+ import numpy as np
5+ from surfaces ._array_utils import ArrayLike , get_array_namespace
6+ from surfaces .modifiers import BaseModifier
7+
8+ from ..._base_algebraic_function import AlgebraicFunction
9+
10+
11+ class ShekelFunction (AlgebraicFunction ):
12+ """Shekel 4-dimensional test function.
13+
14+ A multimodal, non-convex, continuous function. It is defined as the
15+ sum of m inverse quadratic functions.
16+
17+ The function is defined as:
18+
19+ .. math::
20+
21+ f(\\ vec{x}) = - \\ sum_{i=1}^{m} \\ left( \\ sum_{j=1}^{4} (x_j - a_{ij})^2 + c_i \\ right)^{-1}
22+
23+ where :math:`m` is the number of maxima (typically 5, 7, or 10).
24+
25+ The global minimum is located at :math:`x \\ approx (4, 4, 4, 4)` and
26+ the value depends on :math:`m`.
27+
28+ Parameters
29+ ----------
30+ m : int, default=10
31+ Number of maxima. Standard values are 5, 7, or 10.
32+ metric : str, default="score"
33+ Either "loss" (minimize) or "score" (maximize).
34+ modifiers : list of BaseModifier, optional
35+ List of modifiers to apply to function evaluations.
36+ validate : bool, default=True
37+ Whether to validate parameters against the search space.
38+
39+ Attributes
40+ ----------
41+ n_dim : int
42+ Number of dimensions (fixed at 4 for standard Shekel).
43+ default_bounds : tuple
44+ Default parameter bounds (0.0, 10.0).
45+
46+ Examples
47+ --------
48+ >>> from surfaces.test_functions import ShekelFunction
49+ >>> func = ShekelFunction(m=10)
50+ >>> result = func({"x0": 4.0, "x1": 4.0, "x2": 4.0, "x3": 4.0})
51+ >>> float(result) < -10.0
52+ True
53+ >>> len(func.search_space)
54+ 4
55+ """
56+
57+ name = "Shekel Function"
58+ _name_ = "shekel_function"
59+ __name__ = "ShekelFunction"
60+
61+ _spec = {
62+ "convex" : False ,
63+ "unimodal" : False ,
64+ "separable" : False ,
65+ "scalable" : False ,
66+ }
67+
68+ f_global = - 10.536
69+ default_bounds = (0.0 , 10.0 )
70+
71+ latex_formula = r"f(\vec{x}) = - \sum_{i=1}^{m} \left( \sum_{j=1}^{4} (x_j - a_{ij})^2 + c_i \right)^{-1}"
72+ pgfmath_formula = None
73+
74+ # Function sheet attributes
75+ tagline = (
76+ "A multimodal function with m sharp peaks. Often called 'Foxholes', "
77+ "it tests an optimizer's ability to find a global minimum among many locals."
78+ )
79+ display_bounds = (0.0 , 10.0 )
80+ display_projection = {"fixed_values" : {"x2" : 4.0 , "x3" : 4.0 }}
81+ reference = "Shekel, J. (1971). Test function for multivariate search problems."
82+ reference_url = "https://www.sfu.ca/~ssurjano/shekel.html"
83+
84+ def __init__ (
85+ self ,
86+ m : int = 10 ,
87+ objective : str = "minimize" ,
88+ modifiers : Optional [List [BaseModifier ]] = None ,
89+ memory : bool = False ,
90+ collect_data : bool = True ,
91+ callbacks : Optional [Union [Callable , List [Callable ]]] = None ,
92+ catch_errors : Optional [Dict [type , float ]] = None ,
93+ ) -> None :
94+ super ().__init__ (objective , modifiers , memory , collect_data , callbacks , catch_errors )
95+ self .n_dim = 4
96+ self .m = m
97+
98+ self .A = np .array ([
99+ [4.0 , 4.0 , 4.0 , 4.0 ],
100+ [1.0 , 1.0 , 1.0 , 1.0 ],
101+ [8.0 , 8.0 , 8.0 , 8.0 ],
102+ [6.0 , 6.0 , 6.0 , 6.0 ],
103+ [3.0 , 7.0 , 3.0 , 7.0 ],
104+ [2.0 , 9.0 , 2.0 , 9.0 ],
105+ [5.0 , 5.0 , 3.0 , 3.0 ],
106+ [8.0 , 1.0 , 8.0 , 1.0 ],
107+ [6.0 , 2.0 , 6.0 , 2.0 ],
108+ [7.0 , 3.6 , 7.0 , 3.6 ],
109+ ])
110+
111+ self .c = np .array ([0.1 , 0.2 , 0.2 , 0.4 , 0.4 , 0.6 , 0.3 , 0.7 , 0.5 , 0.5 ])
112+
113+ if m < 10 :
114+ self .A = self .A [:m ]
115+ self .c = self .c [:m ]
116+
117+ self .x_global = (4.0 , 4.0 , 4.0 , 4.0 )
118+
119+ def _create_objective_function (self ) -> None :
120+ def shekel_function (params : Dict [str , Any ]) -> float :
121+ x_input = np .array ([params [f"x{ i } " ] for i in range (self .n_dim )])
122+
123+ result = 0.0
124+ for i in range (self .m ):
125+ # (x - a_i)^T (x - a_i)
126+ diff = x_input - self .A [i ]
127+ sq_sum = np .dot (diff , diff )
128+
129+ result -= 1.0 / (sq_sum + self .c [i ])
130+
131+ return result
132+
133+ self .pure_objective_function = shekel_function
134+
135+ def _batch_objective (self , X : ArrayLike ) -> ArrayLike :
136+ """Vectorized batch evaluation.
137+
138+ Parameters
139+ ----------
140+ X : ArrayLike
141+ Array of shape (n_points, n_dim).
142+
143+ Returns
144+ -------
145+ ArrayLike
146+ Array of shape (n_points,).
147+ """
148+ xp = get_array_namespace (X )
149+
150+ A = xp .asarray (self .A )
151+ c = xp .asarray (self .c )
152+
153+ n_points = X .shape [0 ]
154+ result = xp .zeros (n_points )
155+
156+ for i in range (self .m ):
157+ diff = X - A [i ]
158+
159+ sq_sum = xp .sum (diff ** 2 , axis = 1 )
160+
161+ result -= 1.0 / (sq_sum + c [i ])
162+
163+ return result
164+
165+ def _search_space (
166+ self ,
167+ min : float = 0.0 ,
168+ max : float = 10.0 ,
169+ size : int = 10000 ,
170+ value_types : str = "array" ,
171+ ) -> Dict [str , Any ]:
172+ return super ()._create_n_dim_search_space (min , max , size = size , value_types = value_types )
0 commit comments