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.
Files changed (50) hide show
  1. evograd/__init__.py +67 -0
  2. evograd/algorithms/__init__.py +138 -0
  3. evograd/algorithms/cmaes.py +1365 -0
  4. evograd/algorithms/de.py +895 -0
  5. evograd/algorithms/ga.py +532 -0
  6. evograd/algorithms/pso.py +648 -0
  7. evograd/algorithms/shade.py +1165 -0
  8. evograd/benchmarks/functions/__init__.py +229 -0
  9. evograd/benchmarks/functions/base.py +217 -0
  10. evograd/benchmarks/functions/cec2017/__init__.py +250 -0
  11. evograd/benchmarks/functions/cec2017/basic.py +413 -0
  12. evograd/benchmarks/functions/cec2017/composition.py +580 -0
  13. evograd/benchmarks/functions/cec2017/data.pkl +0 -0
  14. evograd/benchmarks/functions/cec2017/data.py +350 -0
  15. evograd/benchmarks/functions/cec2017/hybrid.py +406 -0
  16. evograd/benchmarks/functions/cec2017/simple.py +326 -0
  17. evograd/benchmarks/functions/classical.py +649 -0
  18. evograd/benchmarks/functions/smoothed_funnel.py +476 -0
  19. evograd/benchmarks/functions/transforms.py +463 -0
  20. evograd/benchmarks/run_benchmark_functions.py +1208 -0
  21. evograd/core/__init__.py +73 -0
  22. evograd/core/algorithm.py +778 -0
  23. evograd/core/maximize.py +269 -0
  24. evograd/core/minimize.py +740 -0
  25. evograd/core/problem.py +444 -0
  26. evograd/core/result.py +571 -0
  27. evograd/core/termination.py +602 -0
  28. evograd/operators/__init__.py +178 -0
  29. evograd/operators/crossover.py +1117 -0
  30. evograd/operators/mutation.py +1098 -0
  31. evograd/operators/relaxations.py +175 -0
  32. evograd/operators/repair.py +601 -0
  33. evograd/operators/sampling.py +577 -0
  34. evograd/operators/selection.py +981 -0
  35. evograd/operators/survival.py +1000 -0
  36. evograd/tests/__init__.py +11 -0
  37. evograd/tests/run_all.py +78 -0
  38. evograd/tests/test_core.py +528 -0
  39. evograd/tests/test_ga.py +572 -0
  40. evograd/tests/test_operators.py +662 -0
  41. evograd/tests/test_per_individual.py +326 -0
  42. evograd/tests/test_utils.py +328 -0
  43. evograd/utils/__init__.py +97 -0
  44. evograd/utils/callbacks.py +926 -0
  45. evograd/utils/device.py +502 -0
  46. evograd/utils/duplicates.py +421 -0
  47. evograd_diff-0.1.0.dist-info/METADATA +439 -0
  48. evograd_diff-0.1.0.dist-info/RECORD +50 -0
  49. evograd_diff-0.1.0.dist-info/WHEEL +4 -0
  50. evograd_diff-0.1.0.dist-info/licenses/LICENSE +201 -0
@@ -0,0 +1,580 @@
1
+ """
2
+ CEC 2017 Composition Functions (F21-F30).
3
+
4
+ Composition functions are weighted combinations of multiple basic or hybrid
5
+ functions, each with different shifts and rotations.
6
+
7
+ Reference: CEC 2017 Competition on Real-Parameter Single Objective Optimization
8
+
9
+ F21: Composition Function 1 (N=3)
10
+ F22: Composition Function 2 (N=3)
11
+ F23: Composition Function 3 (N=4)
12
+ F24: Composition Function 4 (N=4)
13
+ F25: Composition Function 5 (N=5)
14
+ F26: Composition Function 6 (N=5)
15
+ F27: Composition Function 7 (N=6)
16
+ F28: Composition Function 8 (N=6)
17
+ F29: Composition Function 9 (N=3) - Hybrid composition
18
+ F30: Composition Function 10 (N=3) - Hybrid composition
19
+ """
20
+
21
+ from typing import List, Optional, Tuple, Callable
22
+
23
+ import torch
24
+ from torch import Tensor
25
+
26
+ from ..base import BenchmarkFunction
27
+ from . import basic
28
+ from . import data as cec_data
29
+ from . import hybrid as hybrid_module
30
+
31
+
32
+ class CEC2017CompositionFunction(BenchmarkFunction):
33
+ """Base class for CEC 2017 composition functions."""
34
+
35
+ def __init__(
36
+ self,
37
+ func_num: int,
38
+ n_var: int = 10,
39
+ n_components: int = 3,
40
+ rotations: Optional[List[Tensor]] = None,
41
+ shifts: Optional[List[Tensor]] = None,
42
+ seed: Optional[int] = None,
43
+ ):
44
+ """
45
+ Initialize CEC 2017 composition function.
46
+
47
+ Args:
48
+ func_num: Function number (21-30).
49
+ n_var: Number of variables.
50
+ n_components: Number of component functions.
51
+ rotations: Optional list of rotation matrices.
52
+ shifts: Optional list of shift vectors.
53
+ seed: Random seed for generating transforms if not provided.
54
+ """
55
+ super().__init__(n_var=n_var, xl=-100.0, xu=100.0)
56
+
57
+ self.func_num = func_num
58
+ self.bias = func_num * 100.0
59
+ self.n_components = n_components
60
+
61
+ # Load or generate transforms for each component
62
+ if rotations is not None:
63
+ self.rotations = rotations
64
+ else:
65
+ self.rotations = [
66
+ cec_data.get_rotation_cf(func_num, i, n_var, seed=seed)
67
+ for i in range(n_components)
68
+ ]
69
+
70
+ if shifts is not None:
71
+ self.shifts = shifts
72
+ else:
73
+ self.shifts = [
74
+ cec_data.get_shift_cf(func_num, i, n_var, seed=seed)
75
+ for i in range(n_components)
76
+ ]
77
+
78
+ def default_bounds(self) -> Tuple[float, float]:
79
+ return (-100.0, 100.0)
80
+
81
+ def _calc_weights(self, x: Tensor, sigmas: Tensor) -> Tensor:
82
+ """
83
+ Calculate composition weights based on distance from each shift.
84
+
85
+ Args:
86
+ x: Input tensor of shape [..., n_var].
87
+ sigmas: Sigma values for each component of shape [n_components].
88
+
89
+ Returns:
90
+ Weights of shape [..., n_components].
91
+ """
92
+ batch_shape = x.shape[:-1]
93
+ nx = x.shape[-1]
94
+ n = self.n_components
95
+
96
+ weights = torch.zeros(*batch_shape, n, device=x.device, dtype=x.dtype)
97
+
98
+ for i in range(n):
99
+ shift = self.shifts[i].to(x.device, x.dtype)
100
+ x_shifted = x - shift
101
+
102
+ # Squared distance
103
+ w = (x_shifted ** 2).sum(dim=-1)
104
+
105
+ # Non-zero mask
106
+ nz_mask = w != 0
107
+
108
+ # Calculate weight: (1/sqrt(w)) * exp(-w / (2*nx*sigma^2))
109
+ sigma = sigmas[i]
110
+ weights[..., i] = torch.where(
111
+ nz_mask,
112
+ (1.0 / torch.sqrt(w + 1e-10)) * torch.exp(-w / (2.0 * nx * sigma * sigma)),
113
+ torch.tensor(float('inf'), device=x.device, dtype=x.dtype),
114
+ )
115
+
116
+ # Normalize weights
117
+ w_sum = weights.sum(dim=-1, keepdim=True)
118
+ nz_sum_mask = w_sum != 0
119
+ weights = torch.where(
120
+ nz_sum_mask.expand_as(weights),
121
+ weights / (w_sum + 1e-10),
122
+ torch.full_like(weights, 1.0 / n),
123
+ )
124
+
125
+ return weights
126
+
127
+ def _evaluate_composition(
128
+ self,
129
+ x: Tensor,
130
+ funcs: List[Callable],
131
+ sigmas: Tensor,
132
+ lambdas: Tensor,
133
+ biases: Tensor,
134
+ ) -> Tensor:
135
+ """
136
+ Evaluate composition function.
137
+
138
+ Args:
139
+ x: Input tensor of shape [..., n_var].
140
+ funcs: List of basic functions.
141
+ sigmas: Sigma values for weight calculation.
142
+ lambdas: Scaling factors for each component.
143
+ biases: Bias values for each component.
144
+
145
+ Returns:
146
+ Function values.
147
+ """
148
+ batch_shape = x.shape[:-1]
149
+ nx = x.shape[-1]
150
+ n = len(funcs)
151
+
152
+ # Move tensors to correct device
153
+ sigmas = sigmas.to(x.device, x.dtype)
154
+ lambdas = lambdas.to(x.device, x.dtype)
155
+ biases = biases.to(x.device, x.dtype)
156
+
157
+ # Calculate weights
158
+ weights = self._calc_weights(x, sigmas)
159
+
160
+ # Evaluate each component
161
+ vals = torch.zeros(*batch_shape, n, device=x.device, dtype=x.dtype)
162
+
163
+ for i in range(n):
164
+ shift = self.shifts[i].to(x.device, x.dtype)
165
+ rotation = self.rotations[i].to(x.device, x.dtype)
166
+
167
+ # Transform: R @ (x - shift)
168
+ z = cec_data.shift_rotate(x, shift, rotation)
169
+
170
+ # Evaluate function
171
+ vals[..., i] = funcs[i](z)
172
+
173
+ # Weighted sum: sum(w * (lambda * f + bias))
174
+ result = (weights * (lambdas * vals + biases)).sum(dim=-1)
175
+
176
+ return result + self.bias
177
+
178
+
179
+ class CEC2017_F21(CEC2017CompositionFunction):
180
+ """
181
+ F21: Composition Function 1 (N=3)
182
+
183
+ Components: Rosenbrock, High Conditioned Elliptic, Rastrigin
184
+
185
+ Optimal value: F21* = 2100
186
+ """
187
+ name = "cec2017_f21"
188
+ optimal_value = 2100.0
189
+
190
+ def __init__(self, n_var: int = 10, **kwargs):
191
+ super().__init__(func_num=21, n_var=n_var, n_components=3, **kwargs)
192
+
193
+ self.funcs = [
194
+ basic.rosenbrock,
195
+ basic.high_conditioned_elliptic,
196
+ basic.rastrigin,
197
+ ]
198
+ self.sigmas = torch.tensor([10.0, 20.0, 30.0])
199
+ self.lambdas = torch.tensor([1.0, 1e-6, 1.0])
200
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0])
201
+
202
+ def __call__(self, x: Tensor) -> Tensor:
203
+ return self._evaluate_composition(
204
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
205
+ )
206
+
207
+
208
+ class CEC2017_F22(CEC2017CompositionFunction):
209
+ """
210
+ F22: Composition Function 2 (N=3)
211
+
212
+ Components: Rastrigin, Griewank, Modified Schwefel
213
+
214
+ Optimal value: F22* = 2200
215
+ """
216
+ name = "cec2017_f22"
217
+ optimal_value = 2200.0
218
+
219
+ def __init__(self, n_var: int = 10, **kwargs):
220
+ super().__init__(func_num=22, n_var=n_var, n_components=3, **kwargs)
221
+
222
+ self.funcs = [
223
+ basic.rastrigin,
224
+ basic.griewank,
225
+ basic.modified_schwefel,
226
+ ]
227
+ self.sigmas = torch.tensor([10.0, 20.0, 30.0])
228
+ self.lambdas = torch.tensor([1.0, 10.0, 1.0])
229
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0])
230
+
231
+ def __call__(self, x: Tensor) -> Tensor:
232
+ return self._evaluate_composition(
233
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
234
+ )
235
+
236
+
237
+ class CEC2017_F23(CEC2017CompositionFunction):
238
+ """
239
+ F23: Composition Function 3 (N=4)
240
+
241
+ Components: Rosenbrock, Ackley, Modified Schwefel, Rastrigin
242
+
243
+ Optimal value: F23* = 2300
244
+ """
245
+ name = "cec2017_f23"
246
+ optimal_value = 2300.0
247
+
248
+ def __init__(self, n_var: int = 10, **kwargs):
249
+ super().__init__(func_num=23, n_var=n_var, n_components=4, **kwargs)
250
+
251
+ self.funcs = [
252
+ basic.rosenbrock,
253
+ basic.ackley,
254
+ basic.modified_schwefel,
255
+ basic.rastrigin,
256
+ ]
257
+ self.sigmas = torch.tensor([10.0, 20.0, 30.0, 40.0])
258
+ self.lambdas = torch.tensor([1.0, 10.0, 1.0, 1.0])
259
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0, 300.0])
260
+
261
+ def __call__(self, x: Tensor) -> Tensor:
262
+ return self._evaluate_composition(
263
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
264
+ )
265
+
266
+
267
+ class CEC2017_F24(CEC2017CompositionFunction):
268
+ """
269
+ F24: Composition Function 4 (N=4)
270
+
271
+ Components: Ackley, High Conditioned Elliptic, Griewank, Rastrigin
272
+
273
+ Optimal value: F24* = 2400
274
+ """
275
+ name = "cec2017_f24"
276
+ optimal_value = 2400.0
277
+
278
+ def __init__(self, n_var: int = 10, **kwargs):
279
+ super().__init__(func_num=24, n_var=n_var, n_components=4, **kwargs)
280
+
281
+ self.funcs = [
282
+ basic.ackley,
283
+ basic.high_conditioned_elliptic,
284
+ basic.griewank,
285
+ basic.rastrigin,
286
+ ]
287
+ self.sigmas = torch.tensor([10.0, 20.0, 30.0, 40.0])
288
+ self.lambdas = torch.tensor([1.0, 1e-6, 10.0, 1.0])
289
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0, 300.0])
290
+
291
+ def __call__(self, x: Tensor) -> Tensor:
292
+ return self._evaluate_composition(
293
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
294
+ )
295
+
296
+
297
+ class CEC2017_F25(CEC2017CompositionFunction):
298
+ """
299
+ F25: Composition Function 5 (N=5)
300
+
301
+ Components: Rastrigin, Happy Cat, Ackley, Discus, Rosenbrock
302
+
303
+ Optimal value: F25* = 2500
304
+ """
305
+ name = "cec2017_f25"
306
+ optimal_value = 2500.0
307
+
308
+ def __init__(self, n_var: int = 10, **kwargs):
309
+ super().__init__(func_num=25, n_var=n_var, n_components=5, **kwargs)
310
+
311
+ self.funcs = [
312
+ basic.rastrigin,
313
+ basic.happy_cat,
314
+ basic.ackley,
315
+ basic.discus,
316
+ basic.rosenbrock,
317
+ ]
318
+ self.sigmas = torch.tensor([10.0, 20.0, 30.0, 40.0, 50.0])
319
+ self.lambdas = torch.tensor([10.0, 1.0, 10.0, 1e-6, 1.0])
320
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0, 300.0, 400.0])
321
+
322
+ def __call__(self, x: Tensor) -> Tensor:
323
+ return self._evaluate_composition(
324
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
325
+ )
326
+
327
+
328
+ class CEC2017_F26(CEC2017CompositionFunction):
329
+ """
330
+ F26: Composition Function 6 (N=5)
331
+
332
+ Components: Expanded Schaffer's F6, Modified Schwefel, Griewank,
333
+ Rosenbrock, Rastrigin
334
+
335
+ Optimal value: F26* = 2600
336
+ """
337
+ name = "cec2017_f26"
338
+ optimal_value = 2600.0
339
+
340
+ def __init__(self, n_var: int = 10, **kwargs):
341
+ super().__init__(func_num=26, n_var=n_var, n_components=5, **kwargs)
342
+
343
+ self.funcs = [
344
+ basic.expanded_schaffers_f6,
345
+ basic.modified_schwefel,
346
+ basic.griewank,
347
+ basic.rosenbrock,
348
+ basic.rastrigin,
349
+ ]
350
+ self.sigmas = torch.tensor([10.0, 20.0, 20.0, 30.0, 40.0])
351
+ # Note: Using lambdas from the actual code, not the problem definitions
352
+ self.lambdas = torch.tensor([5e-4, 1.0, 10.0, 1.0, 10.0])
353
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0, 300.0, 400.0])
354
+
355
+ def __call__(self, x: Tensor) -> Tensor:
356
+ return self._evaluate_composition(
357
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
358
+ )
359
+
360
+
361
+ class CEC2017_F27(CEC2017CompositionFunction):
362
+ """
363
+ F27: Composition Function 7 (N=6)
364
+
365
+ Components: HGBat, Rastrigin, Modified Schwefel, Bent Cigar,
366
+ High Conditioned Elliptic, Expanded Schaffer's F6
367
+
368
+ Optimal value: F27* = 2700
369
+ """
370
+ name = "cec2017_f27"
371
+ optimal_value = 2700.0
372
+
373
+ def __init__(self, n_var: int = 10, **kwargs):
374
+ super().__init__(func_num=27, n_var=n_var, n_components=6, **kwargs)
375
+
376
+ self.funcs = [
377
+ basic.h_g_bat,
378
+ basic.rastrigin,
379
+ basic.modified_schwefel,
380
+ basic.bent_cigar,
381
+ basic.high_conditioned_elliptic,
382
+ basic.expanded_schaffers_f6,
383
+ ]
384
+ self.sigmas = torch.tensor([10.0, 20.0, 30.0, 40.0, 50.0, 60.0])
385
+ self.lambdas = torch.tensor([10.0, 10.0, 2.5, 1e-26, 1e-6, 5e-4])
386
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0, 300.0, 400.0, 500.0])
387
+
388
+ def __call__(self, x: Tensor) -> Tensor:
389
+ return self._evaluate_composition(
390
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
391
+ )
392
+
393
+
394
+ class CEC2017_F28(CEC2017CompositionFunction):
395
+ """
396
+ F28: Composition Function 8 (N=6)
397
+
398
+ Components: Ackley, Griewank, Discus, Rosenbrock, Happy Cat,
399
+ Expanded Schaffer's F6
400
+
401
+ Optimal value: F28* = 2800
402
+ """
403
+ name = "cec2017_f28"
404
+ optimal_value = 2800.0
405
+
406
+ def __init__(self, n_var: int = 10, **kwargs):
407
+ super().__init__(func_num=28, n_var=n_var, n_components=6, **kwargs)
408
+
409
+ self.funcs = [
410
+ basic.ackley,
411
+ basic.griewank,
412
+ basic.discus,
413
+ basic.rosenbrock,
414
+ basic.happy_cat,
415
+ basic.expanded_schaffers_f6,
416
+ ]
417
+ self.sigmas = torch.tensor([10.0, 20.0, 30.0, 40.0, 50.0, 60.0])
418
+ self.lambdas = torch.tensor([10.0, 10.0, 1e-6, 1.0, 1.0, 5e-4])
419
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0, 300.0, 400.0, 500.0])
420
+
421
+ def __call__(self, x: Tensor) -> Tensor:
422
+ return self._evaluate_composition(
423
+ x, self.funcs, self.sigmas, self.lambdas, self.biases_comp
424
+ )
425
+
426
+
427
+ class CEC2017_F29(CEC2017CompositionFunction):
428
+ """
429
+ F29: Composition Function 9 (N=3) - Hybrid Composition
430
+
431
+ Components: Hybrid F15, Hybrid F16, Hybrid F17
432
+
433
+ Optimal value: F29* = 2900
434
+ """
435
+ name = "cec2017_f29"
436
+ optimal_value = 2900.0
437
+
438
+ def __init__(self, n_var: int = 10, **kwargs):
439
+ seed = kwargs.get('seed', None)
440
+ super().__init__(func_num=29, n_var=n_var, n_components=3, **kwargs)
441
+
442
+ # Load shuffles for each component
443
+ self.shuffles = [
444
+ cec_data.get_shuffle_cf(29, i, n_var, seed=seed)
445
+ for i in range(3)
446
+ ]
447
+
448
+ self.sigmas = torch.tensor([10.0, 30.0, 50.0])
449
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0])
450
+ # Offsets to subtract F* added at the end of hybrid functions
451
+ self.offsets = torch.tensor([1500.0, 1600.0, 1700.0])
452
+
453
+ def __call__(self, x: Tensor) -> Tensor:
454
+ batch_shape = x.shape[:-1]
455
+ nx = x.shape[-1]
456
+ n = 3
457
+
458
+ sigmas = self.sigmas.to(x.device, x.dtype)
459
+ biases = self.biases_comp.to(x.device, x.dtype)
460
+ offsets = self.offsets.to(x.device, x.dtype)
461
+
462
+ # Calculate weights
463
+ weights = self._calc_weights(x, sigmas)
464
+
465
+ # Evaluate each hybrid function
466
+ vals = torch.zeros(*batch_shape, n, device=x.device, dtype=x.dtype)
467
+
468
+ hybrid_classes = [
469
+ hybrid_module.CEC2017_F15,
470
+ hybrid_module.CEC2017_F16,
471
+ hybrid_module.CEC2017_F17,
472
+ ]
473
+
474
+ for i in range(n):
475
+ # Create hybrid function with composition's rotation, shift, shuffle
476
+ hybrid_func = hybrid_classes[i](
477
+ n_var=nx,
478
+ rotation=self.rotations[i],
479
+ shift=self.shifts[i],
480
+ shuffle=self.shuffles[i],
481
+ )
482
+ vals[..., i] = hybrid_func(x) - offsets[i]
483
+
484
+ # Weighted sum
485
+ result = (weights * (vals + biases)).sum(dim=-1)
486
+
487
+ return result + self.bias
488
+
489
+
490
+ class CEC2017_F30(CEC2017CompositionFunction):
491
+ """
492
+ F30: Composition Function 10 (N=3) - Hybrid Composition
493
+
494
+ Components: Hybrid F15, Hybrid F18, Hybrid F19
495
+
496
+ Optimal value: F30* = 3000
497
+ """
498
+ name = "cec2017_f30"
499
+ optimal_value = 3000.0
500
+
501
+ def __init__(self, n_var: int = 10, **kwargs):
502
+ seed = kwargs.get('seed', None)
503
+ super().__init__(func_num=30, n_var=n_var, n_components=3, **kwargs)
504
+
505
+ # Load shuffles for each component
506
+ self.shuffles = [
507
+ cec_data.get_shuffle_cf(30, i, n_var, seed=seed)
508
+ for i in range(3)
509
+ ]
510
+
511
+ self.sigmas = torch.tensor([10.0, 30.0, 50.0])
512
+ self.biases_comp = torch.tensor([0.0, 100.0, 200.0])
513
+ self.offsets = torch.tensor([1500.0, 1800.0, 1900.0])
514
+
515
+ def __call__(self, x: Tensor) -> Tensor:
516
+ batch_shape = x.shape[:-1]
517
+ nx = x.shape[-1]
518
+ n = 3
519
+
520
+ sigmas = self.sigmas.to(x.device, x.dtype)
521
+ biases = self.biases_comp.to(x.device, x.dtype)
522
+ offsets = self.offsets.to(x.device, x.dtype)
523
+
524
+ # Calculate weights
525
+ weights = self._calc_weights(x, sigmas)
526
+
527
+ # Evaluate each hybrid function
528
+ vals = torch.zeros(*batch_shape, n, device=x.device, dtype=x.dtype)
529
+
530
+ hybrid_classes = [
531
+ hybrid_module.CEC2017_F15,
532
+ hybrid_module.CEC2017_F18,
533
+ hybrid_module.CEC2017_F19,
534
+ ]
535
+
536
+ for i in range(n):
537
+ hybrid_func = hybrid_classes[i](
538
+ n_var=nx,
539
+ rotation=self.rotations[i],
540
+ shift=self.shifts[i],
541
+ shuffle=self.shuffles[i],
542
+ )
543
+ vals[..., i] = hybrid_func(x) - offsets[i]
544
+
545
+ # Weighted sum
546
+ result = (weights * (vals + biases)).sum(dim=-1)
547
+
548
+ return result + self.bias
549
+
550
+
551
+ # =============================================================================
552
+ # FUNCTION REGISTRY
553
+ # =============================================================================
554
+
555
+ COMPOSITION_FUNCTIONS = {
556
+ "cec2017_f21": CEC2017_F21,
557
+ "cec2017_f22": CEC2017_F22,
558
+ "cec2017_f23": CEC2017_F23,
559
+ "cec2017_f24": CEC2017_F24,
560
+ "cec2017_f25": CEC2017_F25,
561
+ "cec2017_f26": CEC2017_F26,
562
+ "cec2017_f27": CEC2017_F27,
563
+ "cec2017_f28": CEC2017_F28,
564
+ "cec2017_f29": CEC2017_F29,
565
+ "cec2017_f30": CEC2017_F30,
566
+ }
567
+
568
+ # List for iteration
569
+ all_functions = [
570
+ CEC2017_F21,
571
+ CEC2017_F22,
572
+ CEC2017_F23,
573
+ CEC2017_F24,
574
+ CEC2017_F25,
575
+ CEC2017_F26,
576
+ CEC2017_F27,
577
+ CEC2017_F28,
578
+ CEC2017_F29,
579
+ CEC2017_F30,
580
+ ]