evograd-diff 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evograd/__init__.py +67 -0
- evograd/algorithms/__init__.py +138 -0
- evograd/algorithms/cmaes.py +1365 -0
- evograd/algorithms/de.py +895 -0
- evograd/algorithms/ga.py +532 -0
- evograd/algorithms/pso.py +648 -0
- evograd/algorithms/shade.py +1165 -0
- evograd/benchmarks/functions/__init__.py +229 -0
- evograd/benchmarks/functions/base.py +217 -0
- evograd/benchmarks/functions/cec2017/__init__.py +250 -0
- evograd/benchmarks/functions/cec2017/basic.py +413 -0
- evograd/benchmarks/functions/cec2017/composition.py +580 -0
- evograd/benchmarks/functions/cec2017/data.pkl +0 -0
- evograd/benchmarks/functions/cec2017/data.py +350 -0
- evograd/benchmarks/functions/cec2017/hybrid.py +406 -0
- evograd/benchmarks/functions/cec2017/simple.py +326 -0
- evograd/benchmarks/functions/classical.py +649 -0
- evograd/benchmarks/functions/smoothed_funnel.py +476 -0
- evograd/benchmarks/functions/transforms.py +463 -0
- evograd/benchmarks/run_benchmark_functions.py +1208 -0
- evograd/core/__init__.py +73 -0
- evograd/core/algorithm.py +778 -0
- evograd/core/maximize.py +269 -0
- evograd/core/minimize.py +740 -0
- evograd/core/problem.py +444 -0
- evograd/core/result.py +571 -0
- evograd/core/termination.py +602 -0
- evograd/operators/__init__.py +178 -0
- evograd/operators/crossover.py +1117 -0
- evograd/operators/mutation.py +1098 -0
- evograd/operators/relaxations.py +175 -0
- evograd/operators/repair.py +601 -0
- evograd/operators/sampling.py +577 -0
- evograd/operators/selection.py +981 -0
- evograd/operators/survival.py +1000 -0
- evograd/tests/__init__.py +11 -0
- evograd/tests/run_all.py +78 -0
- evograd/tests/test_core.py +528 -0
- evograd/tests/test_ga.py +572 -0
- evograd/tests/test_operators.py +662 -0
- evograd/tests/test_per_individual.py +326 -0
- evograd/tests/test_utils.py +328 -0
- evograd/utils/__init__.py +97 -0
- evograd/utils/callbacks.py +926 -0
- evograd/utils/device.py +502 -0
- evograd/utils/duplicates.py +421 -0
- evograd_diff-0.1.0.dist-info/METADATA +439 -0
- evograd_diff-0.1.0.dist-info/RECORD +50 -0
- evograd_diff-0.1.0.dist-info/WHEEL +4 -0
- evograd_diff-0.1.0.dist-info/licenses/LICENSE +201 -0
|
@@ -0,0 +1,649 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Classical benchmark functions for optimization.
|
|
3
|
+
|
|
4
|
+
This module provides standard unimodal and multimodal test functions
|
|
5
|
+
commonly used in evolutionary computation literature.
|
|
6
|
+
|
|
7
|
+
Categories:
|
|
8
|
+
- Unimodal: Sphere, Ellipsoid, Schwefel222, Cigar, Discus, Rosenbrock
|
|
9
|
+
- Multimodal: Rastrigin, Ackley, Griewank, Schwefel, Levy, Michalewicz
|
|
10
|
+
- Other: Zakharov, Dixon-Price, Powell, Trid
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from typing import Tuple
|
|
14
|
+
|
|
15
|
+
import torch
|
|
16
|
+
from torch import Tensor
|
|
17
|
+
|
|
18
|
+
from .base import BenchmarkFunction
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# =============================================================================
|
|
22
|
+
# UNIMODAL FUNCTIONS
|
|
23
|
+
# =============================================================================
|
|
24
|
+
|
|
25
|
+
class Sphere(BenchmarkFunction):
|
|
26
|
+
"""
|
|
27
|
+
Sphere function (De Jong's function 1).
|
|
28
|
+
|
|
29
|
+
f(x) = sum(x_i^2)
|
|
30
|
+
|
|
31
|
+
Properties:
|
|
32
|
+
- Unimodal, separable, convex
|
|
33
|
+
- Continuous, differentiable
|
|
34
|
+
- Global minimum: f(0,...,0) = 0
|
|
35
|
+
"""
|
|
36
|
+
name = "sphere"
|
|
37
|
+
optimal_value = 0.0
|
|
38
|
+
|
|
39
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
40
|
+
return (-100.0, 100.0)
|
|
41
|
+
|
|
42
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
43
|
+
return (x ** 2).sum(dim=-1)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class Ellipsoid(BenchmarkFunction):
|
|
47
|
+
"""
|
|
48
|
+
Ellipsoid function (Schwefel's problem 1.2 variant).
|
|
49
|
+
|
|
50
|
+
f(x) = sum(10^(6*(i-1)/(n-1)) * x_i^2)
|
|
51
|
+
|
|
52
|
+
Properties:
|
|
53
|
+
- Unimodal, separable
|
|
54
|
+
- Ill-conditioned (condition number ~10^6)
|
|
55
|
+
- Global minimum: f(0,...,0) = 0
|
|
56
|
+
"""
|
|
57
|
+
name = "ellipsoid"
|
|
58
|
+
optimal_value = 0.0
|
|
59
|
+
|
|
60
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
61
|
+
return (-100.0, 100.0)
|
|
62
|
+
|
|
63
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
64
|
+
n = x.shape[-1]
|
|
65
|
+
i = torch.arange(n, device=x.device, dtype=x.dtype)
|
|
66
|
+
weights = 10 ** (6 * i / max(n - 1, 1))
|
|
67
|
+
return (weights * x ** 2).sum(dim=-1)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class SumOfDifferentPowers(BenchmarkFunction):
|
|
71
|
+
"""
|
|
72
|
+
Sum of Different Powers function.
|
|
73
|
+
|
|
74
|
+
f(x) = sum(|x_i|^(i+1))
|
|
75
|
+
|
|
76
|
+
Properties:
|
|
77
|
+
- Unimodal, separable
|
|
78
|
+
- Different sensitivity per dimension
|
|
79
|
+
- Global minimum: f(0,...,0) = 0
|
|
80
|
+
"""
|
|
81
|
+
name = "sum_of_different_powers"
|
|
82
|
+
optimal_value = 0.0
|
|
83
|
+
|
|
84
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
85
|
+
return (-1.0, 1.0)
|
|
86
|
+
|
|
87
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
88
|
+
n = x.shape[-1]
|
|
89
|
+
i = torch.arange(1, n + 1, device=x.device, dtype=x.dtype)
|
|
90
|
+
return (torch.abs(x) ** i).sum(dim=-1)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
class Schwefel222(BenchmarkFunction):
|
|
94
|
+
"""
|
|
95
|
+
Schwefel's Problem 2.22.
|
|
96
|
+
|
|
97
|
+
f(x) = sum(|x_i|) + prod(|x_i|)
|
|
98
|
+
|
|
99
|
+
Properties:
|
|
100
|
+
- Unimodal, non-separable
|
|
101
|
+
- Global minimum: f(0,...,0) = 0
|
|
102
|
+
"""
|
|
103
|
+
name = "schwefel222"
|
|
104
|
+
optimal_value = 0.0
|
|
105
|
+
|
|
106
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
107
|
+
return (-10.0, 10.0)
|
|
108
|
+
|
|
109
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
110
|
+
abs_x = torch.abs(x)
|
|
111
|
+
return abs_x.sum(dim=-1) + abs_x.prod(dim=-1)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class Cigar(BenchmarkFunction):
|
|
115
|
+
"""
|
|
116
|
+
Cigar function.
|
|
117
|
+
|
|
118
|
+
f(x) = x_1^2 + 10^6 * sum(x_i^2) for i>1
|
|
119
|
+
|
|
120
|
+
Properties:
|
|
121
|
+
- Unimodal, separable
|
|
122
|
+
- Ill-conditioned (one narrow direction)
|
|
123
|
+
- Global minimum: f(0,...,0) = 0
|
|
124
|
+
"""
|
|
125
|
+
name = "cigar"
|
|
126
|
+
optimal_value = 0.0
|
|
127
|
+
|
|
128
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
129
|
+
return (-100.0, 100.0)
|
|
130
|
+
|
|
131
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
132
|
+
return x[..., 0] ** 2 + 1e6 * (x[..., 1:] ** 2).sum(dim=-1)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Discus(BenchmarkFunction):
|
|
136
|
+
"""
|
|
137
|
+
Discus (Tablet) function.
|
|
138
|
+
|
|
139
|
+
f(x) = 10^6 * x_1^2 + sum(x_i^2) for i>1
|
|
140
|
+
|
|
141
|
+
Properties:
|
|
142
|
+
- Unimodal, separable
|
|
143
|
+
- Ill-conditioned (one dominant direction)
|
|
144
|
+
- Global minimum: f(0,...,0) = 0
|
|
145
|
+
"""
|
|
146
|
+
name = "discus"
|
|
147
|
+
optimal_value = 0.0
|
|
148
|
+
|
|
149
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
150
|
+
return (-100.0, 100.0)
|
|
151
|
+
|
|
152
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
153
|
+
return 1e6 * x[..., 0] ** 2 + (x[..., 1:] ** 2).sum(dim=-1)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class BentCigar(BenchmarkFunction):
|
|
157
|
+
"""
|
|
158
|
+
Bent Cigar function.
|
|
159
|
+
|
|
160
|
+
f(x) = x_1^2 + 10^6 * sum(x_i^2) for i>1
|
|
161
|
+
|
|
162
|
+
Properties:
|
|
163
|
+
- Unimodal
|
|
164
|
+
- Non-separable when rotated
|
|
165
|
+
- Global minimum: f(0,...,0) = 0
|
|
166
|
+
"""
|
|
167
|
+
name = "bent_cigar"
|
|
168
|
+
optimal_value = 0.0
|
|
169
|
+
|
|
170
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
171
|
+
return (-100.0, 100.0)
|
|
172
|
+
|
|
173
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
174
|
+
return x[..., 0] ** 2 + 1e6 * (x[..., 1:] ** 2).sum(dim=-1)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class Rosenbrock(BenchmarkFunction):
|
|
178
|
+
"""
|
|
179
|
+
Rosenbrock function (Banana function).
|
|
180
|
+
|
|
181
|
+
f(x) = sum(100*(x_{i+1} - x_i^2)^2 + (1 - x_i)^2)
|
|
182
|
+
|
|
183
|
+
Properties:
|
|
184
|
+
- Unimodal (for n<=3), multimodal (for n>3)
|
|
185
|
+
- Non-separable
|
|
186
|
+
- Global minimum: f(1,...,1) = 0
|
|
187
|
+
"""
|
|
188
|
+
name = "rosenbrock"
|
|
189
|
+
optimal_value = 0.0
|
|
190
|
+
|
|
191
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
192
|
+
return (-5.0, 10.0)
|
|
193
|
+
|
|
194
|
+
def _compute_optimal_x(self) -> Tensor:
|
|
195
|
+
return torch.ones(self.n_var)
|
|
196
|
+
|
|
197
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
198
|
+
x_i = x[..., :-1]
|
|
199
|
+
x_ip1 = x[..., 1:]
|
|
200
|
+
return (100 * (x_ip1 - x_i ** 2) ** 2 + (1 - x_i) ** 2).sum(dim=-1)
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class DixonPrice(BenchmarkFunction):
|
|
204
|
+
"""
|
|
205
|
+
Dixon-Price function.
|
|
206
|
+
|
|
207
|
+
f(x) = (x_1 - 1)^2 + sum(i * (2*x_i^2 - x_{i-1})^2)
|
|
208
|
+
|
|
209
|
+
Properties:
|
|
210
|
+
- Unimodal
|
|
211
|
+
- Non-separable
|
|
212
|
+
"""
|
|
213
|
+
name = "dixon_price"
|
|
214
|
+
optimal_value = 0.0
|
|
215
|
+
|
|
216
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
217
|
+
return (-10.0, 10.0)
|
|
218
|
+
|
|
219
|
+
def _compute_optimal_x(self) -> Tensor:
|
|
220
|
+
# x_i = 2^(-(2^i - 2)/2^i) for i = 1, ..., n
|
|
221
|
+
i = torch.arange(1, self.n_var + 1, dtype=torch.float32)
|
|
222
|
+
return 2.0 ** (-(2.0 ** i - 2.0) / (2.0 ** i))
|
|
223
|
+
|
|
224
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
225
|
+
n = x.shape[-1]
|
|
226
|
+
term1 = (x[..., 0] - 1) ** 2
|
|
227
|
+
i = torch.arange(2, n + 1, device=x.device, dtype=x.dtype)
|
|
228
|
+
term2 = (i * (2 * x[..., 1:] ** 2 - x[..., :-1]) ** 2).sum(dim=-1)
|
|
229
|
+
return term1 + term2
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
class Powell(BenchmarkFunction):
|
|
233
|
+
"""
|
|
234
|
+
Powell function.
|
|
235
|
+
|
|
236
|
+
f(x) = sum of grouped terms (requires n divisible by 4)
|
|
237
|
+
|
|
238
|
+
Properties:
|
|
239
|
+
- Unimodal
|
|
240
|
+
- Non-separable
|
|
241
|
+
- Global minimum: f(0,...,0) = 0
|
|
242
|
+
"""
|
|
243
|
+
name = "powell"
|
|
244
|
+
optimal_value = 0.0
|
|
245
|
+
|
|
246
|
+
def __init__(self, n_var: int = 24, **kwargs):
|
|
247
|
+
# Ensure n_var is divisible by 4
|
|
248
|
+
n_var = max(4, (n_var // 4) * 4)
|
|
249
|
+
super().__init__(n_var=n_var, **kwargs)
|
|
250
|
+
|
|
251
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
252
|
+
return (-4.0, 5.0)
|
|
253
|
+
|
|
254
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
255
|
+
n = x.shape[-1]
|
|
256
|
+
n_groups = n // 4
|
|
257
|
+
|
|
258
|
+
result = torch.zeros(x.shape[:-1], device=x.device, dtype=x.dtype)
|
|
259
|
+
|
|
260
|
+
for j in range(n_groups):
|
|
261
|
+
i = 4 * j
|
|
262
|
+
term1 = (x[..., i] + 10 * x[..., i + 1]) ** 2
|
|
263
|
+
term2 = 5 * (x[..., i + 2] - x[..., i + 3]) ** 2
|
|
264
|
+
term3 = (x[..., i + 1] - 2 * x[..., i + 2]) ** 4
|
|
265
|
+
term4 = 10 * (x[..., i] - x[..., i + 3]) ** 4
|
|
266
|
+
result = result + term1 + term2 + term3 + term4
|
|
267
|
+
|
|
268
|
+
return result
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class Trid(BenchmarkFunction):
|
|
272
|
+
"""
|
|
273
|
+
Trid function.
|
|
274
|
+
|
|
275
|
+
f(x) = sum((x_i - 1)^2) - sum(x_i * x_{i-1})
|
|
276
|
+
|
|
277
|
+
Properties:
|
|
278
|
+
- Unimodal
|
|
279
|
+
- Non-separable
|
|
280
|
+
"""
|
|
281
|
+
name = "trid"
|
|
282
|
+
|
|
283
|
+
def __init__(self, n_var: int = 30, **kwargs):
|
|
284
|
+
super().__init__(n_var=n_var, **kwargs)
|
|
285
|
+
# Optimal value is -n(n+4)(n-1)/6
|
|
286
|
+
self.optimal_value = -n_var * (n_var + 4) * (n_var - 1) / 6
|
|
287
|
+
|
|
288
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
289
|
+
return (-self.n_var ** 2, self.n_var ** 2)
|
|
290
|
+
|
|
291
|
+
def _compute_optimal_x(self) -> Tensor:
|
|
292
|
+
# x_i = i(n + 1 - i)
|
|
293
|
+
i = torch.arange(1, self.n_var + 1, dtype=torch.float32)
|
|
294
|
+
return i * (self.n_var + 1 - i)
|
|
295
|
+
|
|
296
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
297
|
+
term1 = ((x - 1) ** 2).sum(dim=-1)
|
|
298
|
+
term2 = (x[..., 1:] * x[..., :-1]).sum(dim=-1)
|
|
299
|
+
return term1 - term2
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
# =============================================================================
|
|
303
|
+
# MULTIMODAL FUNCTIONS
|
|
304
|
+
# =============================================================================
|
|
305
|
+
|
|
306
|
+
class Rastrigin(BenchmarkFunction):
|
|
307
|
+
"""
|
|
308
|
+
Rastrigin function.
|
|
309
|
+
|
|
310
|
+
f(x) = 10*n + sum(x_i^2 - 10*cos(2*pi*x_i))
|
|
311
|
+
|
|
312
|
+
Properties:
|
|
313
|
+
- Highly multimodal (10^n local minima)
|
|
314
|
+
- Separable
|
|
315
|
+
- Regular spacing of local minima
|
|
316
|
+
- Global minimum: f(0,...,0) = 0
|
|
317
|
+
"""
|
|
318
|
+
name = "rastrigin"
|
|
319
|
+
optimal_value = 0.0
|
|
320
|
+
|
|
321
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
322
|
+
return (-5.12, 5.12)
|
|
323
|
+
|
|
324
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
325
|
+
n = x.shape[-1]
|
|
326
|
+
return 10 * n + (x ** 2 - 10 * torch.cos(2 * torch.pi * x)).sum(dim=-1)
|
|
327
|
+
|
|
328
|
+
|
|
329
|
+
class Ackley(BenchmarkFunction):
|
|
330
|
+
"""
|
|
331
|
+
Ackley function.
|
|
332
|
+
|
|
333
|
+
f(x) = -20*exp(-0.2*sqrt(mean(x^2))) - exp(mean(cos(2*pi*x))) + 20 + e
|
|
334
|
+
|
|
335
|
+
Properties:
|
|
336
|
+
- Multimodal with large nearly flat outer region
|
|
337
|
+
- Non-separable
|
|
338
|
+
- Global minimum: f(0,...,0) = 0
|
|
339
|
+
"""
|
|
340
|
+
name = "ackley"
|
|
341
|
+
optimal_value = 0.0
|
|
342
|
+
|
|
343
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
344
|
+
return (-32.768, 32.768)
|
|
345
|
+
|
|
346
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
347
|
+
n = x.shape[-1]
|
|
348
|
+
sum1 = (x ** 2).sum(dim=-1)
|
|
349
|
+
sum2 = torch.cos(2 * torch.pi * x).sum(dim=-1)
|
|
350
|
+
return (
|
|
351
|
+
-20 * torch.exp(-0.2 * torch.sqrt(sum1 / n))
|
|
352
|
+
- torch.exp(sum2 / n)
|
|
353
|
+
+ 20
|
|
354
|
+
+ torch.e
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
class Griewank(BenchmarkFunction):
|
|
359
|
+
"""
|
|
360
|
+
Griewank function.
|
|
361
|
+
|
|
362
|
+
f(x) = sum(x_i^2)/4000 - prod(cos(x_i/sqrt(i))) + 1
|
|
363
|
+
|
|
364
|
+
Properties:
|
|
365
|
+
- Multimodal with regular local minima
|
|
366
|
+
- Non-separable
|
|
367
|
+
- Global minimum: f(0,...,0) = 0
|
|
368
|
+
"""
|
|
369
|
+
name = "griewank"
|
|
370
|
+
optimal_value = 0.0
|
|
371
|
+
|
|
372
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
373
|
+
return (-600.0, 600.0)
|
|
374
|
+
|
|
375
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
376
|
+
n = x.shape[-1]
|
|
377
|
+
i = torch.arange(1, n + 1, device=x.device, dtype=x.dtype)
|
|
378
|
+
sum_term = (x ** 2).sum(dim=-1) / 4000
|
|
379
|
+
prod_term = torch.cos(x / torch.sqrt(i)).prod(dim=-1)
|
|
380
|
+
return sum_term - prod_term + 1
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
class Schwefel(BenchmarkFunction):
|
|
384
|
+
"""
|
|
385
|
+
Schwefel function.
|
|
386
|
+
|
|
387
|
+
f(x) = 418.9829*n - sum(x_i * sin(sqrt(|x_i|)))
|
|
388
|
+
|
|
389
|
+
Properties:
|
|
390
|
+
- Multimodal
|
|
391
|
+
- Separable
|
|
392
|
+
- Global optimum far from local optima
|
|
393
|
+
- Global minimum: f(420.9687,...,420.9687) ≈ 0
|
|
394
|
+
"""
|
|
395
|
+
name = "schwefel"
|
|
396
|
+
|
|
397
|
+
def __init__(self, n_var: int = 30, **kwargs):
|
|
398
|
+
super().__init__(n_var=n_var, **kwargs)
|
|
399
|
+
self.optimal_value = 0.0 # After normalization
|
|
400
|
+
|
|
401
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
402
|
+
return (-500.0, 500.0)
|
|
403
|
+
|
|
404
|
+
def _compute_optimal_x(self) -> Tensor:
|
|
405
|
+
return torch.full((self.n_var,), 420.9687)
|
|
406
|
+
|
|
407
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
408
|
+
n = x.shape[-1]
|
|
409
|
+
return 418.9829 * n - (x * torch.sin(torch.sqrt(torch.abs(x)))).sum(dim=-1)
|
|
410
|
+
|
|
411
|
+
|
|
412
|
+
class Levy(BenchmarkFunction):
|
|
413
|
+
"""
|
|
414
|
+
Levy function.
|
|
415
|
+
|
|
416
|
+
f(x) = sin^2(pi*w_1) + sum((w_i-1)^2 * (1 + 10*sin^2(pi*w_i+1)))
|
|
417
|
+
+ (w_n-1)^2 * (1 + sin^2(2*pi*w_n))
|
|
418
|
+
where w_i = 1 + (x_i - 1)/4
|
|
419
|
+
|
|
420
|
+
Properties:
|
|
421
|
+
- Multimodal
|
|
422
|
+
- Non-separable
|
|
423
|
+
- Global minimum: f(1,...,1) = 0
|
|
424
|
+
"""
|
|
425
|
+
name = "levy"
|
|
426
|
+
optimal_value = 0.0
|
|
427
|
+
|
|
428
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
429
|
+
return (-10.0, 10.0)
|
|
430
|
+
|
|
431
|
+
def _compute_optimal_x(self) -> Tensor:
|
|
432
|
+
return torch.ones(self.n_var)
|
|
433
|
+
|
|
434
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
435
|
+
w = 1 + (x - 1) / 4
|
|
436
|
+
term1 = torch.sin(torch.pi * w[..., 0]) ** 2
|
|
437
|
+
term2 = (
|
|
438
|
+
(w[..., :-1] - 1) ** 2
|
|
439
|
+
* (1 + 10 * torch.sin(torch.pi * w[..., :-1] + 1) ** 2)
|
|
440
|
+
).sum(dim=-1)
|
|
441
|
+
term3 = (w[..., -1] - 1) ** 2 * (
|
|
442
|
+
1 + torch.sin(2 * torch.pi * w[..., -1]) ** 2
|
|
443
|
+
)
|
|
444
|
+
return term1 + term2 + term3
|
|
445
|
+
|
|
446
|
+
|
|
447
|
+
class Michalewicz(BenchmarkFunction):
|
|
448
|
+
"""
|
|
449
|
+
Michalewicz function.
|
|
450
|
+
|
|
451
|
+
f(x) = -sum(sin(x_i) * sin(i*x_i^2 / pi)^(2*m))
|
|
452
|
+
|
|
453
|
+
Properties:
|
|
454
|
+
- Multimodal with n! local minima
|
|
455
|
+
- Separable
|
|
456
|
+
- Steepness controlled by parameter m
|
|
457
|
+
"""
|
|
458
|
+
name = "michalewicz"
|
|
459
|
+
|
|
460
|
+
def __init__(self, n_var: int = 30, m: float = 10.0, **kwargs):
|
|
461
|
+
super().__init__(n_var=n_var, **kwargs)
|
|
462
|
+
self.m = m
|
|
463
|
+
# Optimal value depends on n and m
|
|
464
|
+
self.optimal_value = -4.687 if n_var >= 5 else -1.8013 # Approximate
|
|
465
|
+
|
|
466
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
467
|
+
return (0.0, torch.pi)
|
|
468
|
+
|
|
469
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
470
|
+
n = x.shape[-1]
|
|
471
|
+
i = torch.arange(1, n + 1, device=x.device, dtype=x.dtype)
|
|
472
|
+
return -(
|
|
473
|
+
torch.sin(x) * torch.sin(i * x ** 2 / torch.pi) ** (2 * self.m)
|
|
474
|
+
).sum(dim=-1)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
class Zakharov(BenchmarkFunction):
|
|
478
|
+
"""
|
|
479
|
+
Zakharov function.
|
|
480
|
+
|
|
481
|
+
f(x) = sum(x_i^2) + (sum(0.5*i*x_i))^2 + (sum(0.5*i*x_i))^4
|
|
482
|
+
|
|
483
|
+
Properties:
|
|
484
|
+
- Multimodal
|
|
485
|
+
- Non-separable
|
|
486
|
+
- Global minimum: f(0,...,0) = 0
|
|
487
|
+
"""
|
|
488
|
+
name = "zakharov"
|
|
489
|
+
optimal_value = 0.0
|
|
490
|
+
|
|
491
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
492
|
+
return (-5.0, 10.0)
|
|
493
|
+
|
|
494
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
495
|
+
n = x.shape[-1]
|
|
496
|
+
i = torch.arange(1, n + 1, device=x.device, dtype=x.dtype)
|
|
497
|
+
sum1 = (x ** 2).sum(dim=-1)
|
|
498
|
+
sum2 = (0.5 * i * x).sum(dim=-1)
|
|
499
|
+
return sum1 + sum2 ** 2 + sum2 ** 4
|
|
500
|
+
|
|
501
|
+
|
|
502
|
+
class Weierstrass(BenchmarkFunction):
|
|
503
|
+
"""
|
|
504
|
+
Weierstrass function.
|
|
505
|
+
|
|
506
|
+
f(x) = sum_i(sum_k(a^k * cos(2*pi*b^k*(x_i + 0.5))))
|
|
507
|
+
- n * sum_k(a^k * cos(pi*b^k))
|
|
508
|
+
|
|
509
|
+
Properties:
|
|
510
|
+
- Multimodal
|
|
511
|
+
- Continuous but not differentiable
|
|
512
|
+
- Non-separable
|
|
513
|
+
- Global minimum: f(0,...,0) = 0
|
|
514
|
+
"""
|
|
515
|
+
name = "weierstrass"
|
|
516
|
+
optimal_value = 0.0
|
|
517
|
+
|
|
518
|
+
def __init__(self, n_var: int = 30, a: float = 0.5, b: float = 3.0, k_max: int = 20, **kwargs):
|
|
519
|
+
super().__init__(n_var=n_var, **kwargs)
|
|
520
|
+
self.a = a
|
|
521
|
+
self.b = b
|
|
522
|
+
self.k_max = k_max
|
|
523
|
+
|
|
524
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
525
|
+
return (-0.5, 0.5)
|
|
526
|
+
|
|
527
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
528
|
+
n = x.shape[-1]
|
|
529
|
+
k = torch.arange(self.k_max + 1, device=x.device, dtype=x.dtype)
|
|
530
|
+
|
|
531
|
+
a_k = self.a ** k # Shape: [k_max+1]
|
|
532
|
+
b_k = self.b ** k # Shape: [k_max+1]
|
|
533
|
+
|
|
534
|
+
# Constant term: sum_k(a^k * cos(pi * b^k))
|
|
535
|
+
const = (a_k * torch.cos(torch.pi * b_k)).sum()
|
|
536
|
+
|
|
537
|
+
# Sum over dimensions
|
|
538
|
+
# x shape: [..., n], need to compute for each dimension
|
|
539
|
+
result = torch.zeros(x.shape[:-1], device=x.device, dtype=x.dtype)
|
|
540
|
+
for d in range(n):
|
|
541
|
+
x_d = x[..., d] # Shape: [...]
|
|
542
|
+
# sum_k(a^k * cos(2*pi*b^k*(x_d + 0.5)))
|
|
543
|
+
# Broadcast: [..., 1] * [k_max+1] -> [..., k_max+1]
|
|
544
|
+
inner = 2 * torch.pi * b_k * (x_d.unsqueeze(-1) + 0.5)
|
|
545
|
+
result = result + (a_k * torch.cos(inner)).sum(dim=-1)
|
|
546
|
+
|
|
547
|
+
return result - n * const
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
class Alpine(BenchmarkFunction):
|
|
551
|
+
"""
|
|
552
|
+
Alpine function (No. 1).
|
|
553
|
+
|
|
554
|
+
f(x) = sum(|x_i * sin(x_i) + 0.1 * x_i|)
|
|
555
|
+
|
|
556
|
+
Properties:
|
|
557
|
+
- Multimodal
|
|
558
|
+
- Separable
|
|
559
|
+
- Global minimum: f(0,...,0) = 0
|
|
560
|
+
"""
|
|
561
|
+
name = "alpine"
|
|
562
|
+
optimal_value = 0.0
|
|
563
|
+
|
|
564
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
565
|
+
return (-10.0, 10.0)
|
|
566
|
+
|
|
567
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
568
|
+
return torch.abs(x * torch.sin(x) + 0.1 * x).sum(dim=-1)
|
|
569
|
+
|
|
570
|
+
|
|
571
|
+
class Salomon(BenchmarkFunction):
|
|
572
|
+
"""
|
|
573
|
+
Salomon function.
|
|
574
|
+
|
|
575
|
+
f(x) = 1 - cos(2*pi*||x||) + 0.1*||x||
|
|
576
|
+
|
|
577
|
+
Properties:
|
|
578
|
+
- Multimodal
|
|
579
|
+
- Non-separable (radially symmetric)
|
|
580
|
+
- Global minimum: f(0,...,0) = 0
|
|
581
|
+
"""
|
|
582
|
+
name = "salomon"
|
|
583
|
+
optimal_value = 0.0
|
|
584
|
+
|
|
585
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
586
|
+
return (-100.0, 100.0)
|
|
587
|
+
|
|
588
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
589
|
+
norm = torch.sqrt((x ** 2).sum(dim=-1))
|
|
590
|
+
return 1 - torch.cos(2 * torch.pi * norm) + 0.1 * norm
|
|
591
|
+
|
|
592
|
+
|
|
593
|
+
class StyblinskiTang(BenchmarkFunction):
|
|
594
|
+
"""
|
|
595
|
+
Styblinski-Tang function.
|
|
596
|
+
|
|
597
|
+
f(x) = 0.5 * sum(x_i^4 - 16*x_i^2 + 5*x_i)
|
|
598
|
+
|
|
599
|
+
Properties:
|
|
600
|
+
- Multimodal
|
|
601
|
+
- Separable
|
|
602
|
+
- Global minimum: f(-2.9035,...,-2.9035) ≈ -39.16599*n
|
|
603
|
+
"""
|
|
604
|
+
name = "styblinski_tang"
|
|
605
|
+
|
|
606
|
+
def __init__(self, n_var: int = 30, **kwargs):
|
|
607
|
+
super().__init__(n_var=n_var, **kwargs)
|
|
608
|
+
self.optimal_value = -39.16599 * n_var
|
|
609
|
+
|
|
610
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
611
|
+
return (-5.0, 5.0)
|
|
612
|
+
|
|
613
|
+
def _compute_optimal_x(self) -> Tensor:
|
|
614
|
+
return torch.full((self.n_var,), -2.903534)
|
|
615
|
+
|
|
616
|
+
def __call__(self, x: Tensor) -> Tensor:
|
|
617
|
+
return 0.5 * (x ** 4 - 16 * x ** 2 + 5 * x).sum(dim=-1)
|
|
618
|
+
|
|
619
|
+
|
|
620
|
+
# =============================================================================
|
|
621
|
+
# FUNCTION REGISTRY
|
|
622
|
+
# =============================================================================
|
|
623
|
+
|
|
624
|
+
CLASSICAL_FUNCTIONS = {
|
|
625
|
+
# Unimodal
|
|
626
|
+
"sphere": Sphere,
|
|
627
|
+
"ellipsoid": Ellipsoid,
|
|
628
|
+
"sum_of_different_powers": SumOfDifferentPowers,
|
|
629
|
+
"schwefel222": Schwefel222,
|
|
630
|
+
"cigar": Cigar,
|
|
631
|
+
"discus": Discus,
|
|
632
|
+
"bent_cigar": BentCigar,
|
|
633
|
+
"rosenbrock": Rosenbrock,
|
|
634
|
+
"dixon_price": DixonPrice,
|
|
635
|
+
"powell": Powell,
|
|
636
|
+
"trid": Trid,
|
|
637
|
+
# Multimodal
|
|
638
|
+
"rastrigin": Rastrigin,
|
|
639
|
+
"ackley": Ackley,
|
|
640
|
+
"griewank": Griewank,
|
|
641
|
+
"schwefel": Schwefel,
|
|
642
|
+
"levy": Levy,
|
|
643
|
+
"michalewicz": Michalewicz,
|
|
644
|
+
"zakharov": Zakharov,
|
|
645
|
+
"weierstrass": Weierstrass,
|
|
646
|
+
"alpine": Alpine,
|
|
647
|
+
"salomon": Salomon,
|
|
648
|
+
"styblinski_tang": StyblinskiTang,
|
|
649
|
+
}
|