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,350 @@
1
+ """
2
+ CEC 2017 Data Loader.
3
+
4
+ Loads rotation matrices, shift vectors, and shuffle permutations from data.pkl.
5
+ Also provides utilities to generate random transforms if data file is not available.
6
+
7
+ The data.pkl file contains pre-computed transforms for reproducibility with
8
+ the official CEC 2017 benchmark suite.
9
+ """
10
+
11
+ import os
12
+ import pickle
13
+ from typing import Dict, Optional, Tuple
14
+
15
+ import torch
16
+ from torch import Tensor
17
+
18
+
19
+ # =============================================================================
20
+ # DATA LOADING
21
+ # =============================================================================
22
+
23
+ _DATA_LOADED = False
24
+ _DATA_CACHE: Dict = {}
25
+
26
+ # Supported dimensions
27
+ SUPPORTED_DIMS = [2, 10, 20, 30, 50, 100]
28
+
29
+
30
+ def _load_data():
31
+ """Load data from pickle file if available."""
32
+ global _DATA_LOADED, _DATA_CACHE
33
+
34
+ if _DATA_LOADED:
35
+ return
36
+
37
+ data_path = os.path.join(os.path.dirname(__file__), 'data.pkl')
38
+
39
+ if os.path.exists(data_path):
40
+ try:
41
+ with open(data_path, 'rb') as f:
42
+ _DATA_CACHE = pickle.load(f)
43
+ _DATA_LOADED = True
44
+ except Exception as e:
45
+ print(f"Warning: Could not load CEC 2017 data file: {e}")
46
+ print("Using randomly generated transforms instead.")
47
+ _DATA_LOADED = False
48
+ else:
49
+ _DATA_LOADED = False
50
+
51
+
52
+ def data_available() -> bool:
53
+ """Check if official CEC 2017 data is available."""
54
+ _load_data()
55
+ return _DATA_LOADED and len(_DATA_CACHE) > 0
56
+
57
+
58
+ # =============================================================================
59
+ # ROTATION MATRICES
60
+ # =============================================================================
61
+
62
+ def get_rotation(func_num: int, n_var: int, seed: Optional[int] = None) -> Tensor:
63
+ """
64
+ Get rotation matrix for a simple/hybrid function (f1-f20).
65
+
66
+ Args:
67
+ func_num: Function number (1-20, 0-indexed internally).
68
+ n_var: Number of variables (2, 10, 20, 30, 50, or 100).
69
+ seed: Random seed for generating matrix if data not available.
70
+
71
+ Returns:
72
+ Rotation matrix of shape [n_var, n_var].
73
+ """
74
+ _load_data()
75
+
76
+ func_idx = func_num - 1 # Convert to 0-indexed
77
+
78
+ if _DATA_LOADED and n_var in SUPPORTED_DIMS:
79
+ key = f'M_D{n_var}'
80
+ if key in _DATA_CACHE:
81
+ mat = _DATA_CACHE[key][func_idx]
82
+ return torch.tensor(mat, dtype=torch.float32)
83
+
84
+ # Generate random orthogonal matrix
85
+ return generate_rotation_matrix(n_var, seed=seed)
86
+
87
+
88
+ def get_rotation_cf(func_num: int, comp_idx: int, n_var: int, seed: Optional[int] = None) -> Tensor:
89
+ """
90
+ Get rotation matrix for a composition function (f21-f30).
91
+
92
+ Args:
93
+ func_num: Function number (21-30, 0-indexed internally as 0-9).
94
+ comp_idx: Component index within the composition (0-9).
95
+ n_var: Number of variables (2, 10, 20, 30, 50, or 100).
96
+ seed: Random seed for generating matrix if data not available.
97
+
98
+ Returns:
99
+ Rotation matrix of shape [n_var, n_var].
100
+ """
101
+ _load_data()
102
+
103
+ func_idx = func_num - 21 # Convert to 0-indexed for composition functions
104
+
105
+ if _DATA_LOADED and n_var in SUPPORTED_DIMS:
106
+ # Try both key formats
107
+ key = f'M_cf_D{n_var}' if n_var > 2 else f'M_cf_d{n_var}'
108
+ if key not in _DATA_CACHE:
109
+ key = f'M_cf_D{n_var}'
110
+ if key in _DATA_CACHE:
111
+ mat = _DATA_CACHE[key][func_idx][comp_idx]
112
+ return torch.tensor(mat, dtype=torch.float32)
113
+
114
+ # Generate random orthogonal matrix
115
+ return generate_rotation_matrix(n_var, seed=seed)
116
+
117
+
118
+ # =============================================================================
119
+ # SHIFT VECTORS
120
+ # =============================================================================
121
+
122
+ def get_shift(func_num: int, n_var: int, seed: Optional[int] = None) -> Tensor:
123
+ """
124
+ Get shift vector for a simple/hybrid function (f1-f20).
125
+
126
+ Args:
127
+ func_num: Function number (1-20, 0-indexed internally).
128
+ n_var: Number of variables.
129
+ seed: Random seed for generating vector if data not available.
130
+
131
+ Returns:
132
+ Shift vector of shape [n_var].
133
+ """
134
+ _load_data()
135
+
136
+ func_idx = func_num - 1 # Convert to 0-indexed
137
+
138
+ if _DATA_LOADED and 'shift' in _DATA_CACHE:
139
+ shift = _DATA_CACHE['shift'][func_idx][:n_var]
140
+ return torch.tensor(shift, dtype=torch.float32)
141
+
142
+ # Generate random shift vector within bounds
143
+ return generate_shift_vector(n_var, -80.0, 80.0, seed=seed)
144
+
145
+
146
+ def get_shift_cf(func_num: int, comp_idx: int, n_var: int, seed: Optional[int] = None) -> Tensor:
147
+ """
148
+ Get shift vector for a composition function (f21-f30).
149
+
150
+ Args:
151
+ func_num: Function number (21-30, 0-indexed internally as 0-9).
152
+ comp_idx: Component index within the composition (0-9).
153
+ n_var: Number of variables.
154
+ seed: Random seed for generating vector if data not available.
155
+
156
+ Returns:
157
+ Shift vector of shape [n_var].
158
+ """
159
+ _load_data()
160
+
161
+ func_idx = func_num - 21 # Convert to 0-indexed
162
+
163
+ if _DATA_LOADED and 'shift_cf' in _DATA_CACHE:
164
+ shift = _DATA_CACHE['shift_cf'][func_idx][comp_idx][:n_var]
165
+ return torch.tensor(shift, dtype=torch.float32)
166
+
167
+ # Generate random shift vector
168
+ return generate_shift_vector(n_var, -80.0, 80.0, seed=seed)
169
+
170
+
171
+ # =============================================================================
172
+ # SHUFFLE VECTORS
173
+ # =============================================================================
174
+
175
+ def get_shuffle(func_num: int, n_var: int, seed: Optional[int] = None) -> Tensor:
176
+ """
177
+ Get shuffle permutation for a hybrid function (f11-f20).
178
+
179
+ Args:
180
+ func_num: Function number (11-20, 0-indexed internally as 0-9).
181
+ n_var: Number of variables (10, 30, 50, or 100).
182
+ seed: Random seed for generating permutation if data not available.
183
+
184
+ Returns:
185
+ Shuffle permutation of shape [n_var] (0-indexed).
186
+ """
187
+ _load_data()
188
+
189
+ func_idx = func_num - 11 # Convert to 0-indexed for hybrid functions
190
+
191
+ if _DATA_LOADED and n_var in [10, 30, 50, 100]:
192
+ key = f'shuffle_D{n_var}'
193
+ if key in _DATA_CACHE:
194
+ shuffle = _DATA_CACHE[key][func_idx]
195
+ return torch.tensor(shuffle, dtype=torch.long)
196
+
197
+ # Generate random permutation
198
+ return generate_shuffle(n_var, seed=seed)
199
+
200
+
201
+ def get_shuffle_cf(func_num: int, comp_idx: int, n_var: int, seed: Optional[int] = None) -> Tensor:
202
+ """
203
+ Get shuffle permutation for composition function f29 or f30.
204
+
205
+ Args:
206
+ func_num: Function number (29 or 30).
207
+ comp_idx: Component index (0-2).
208
+ n_var: Number of variables (10, 30, 50, or 100).
209
+ seed: Random seed for generating permutation if data not available.
210
+
211
+ Returns:
212
+ Shuffle permutation of shape [n_var] (0-indexed).
213
+ """
214
+ _load_data()
215
+
216
+ func_idx = func_num - 29 # 0 for f29, 1 for f30
217
+
218
+ if _DATA_LOADED and n_var in [10, 30, 50, 100]:
219
+ key = f'shuffle_cf_D{n_var}'
220
+ if key in _DATA_CACHE:
221
+ shuffle = _DATA_CACHE[key][func_idx][comp_idx]
222
+ return torch.tensor(shuffle, dtype=torch.long)
223
+
224
+ # Generate random permutation
225
+ return generate_shuffle(n_var, seed=seed)
226
+
227
+
228
+ # =============================================================================
229
+ # GENERATION UTILITIES
230
+ # =============================================================================
231
+
232
+ def generate_rotation_matrix(n: int, seed: Optional[int] = None) -> Tensor:
233
+ """
234
+ Generate a random orthogonal rotation matrix using QR decomposition.
235
+
236
+ Args:
237
+ n: Dimension of the matrix.
238
+ seed: Optional random seed.
239
+
240
+ Returns:
241
+ Orthogonal matrix of shape [n, n].
242
+ """
243
+ if seed is not None:
244
+ torch.manual_seed(seed)
245
+
246
+ A = torch.randn(n, n)
247
+ Q, R = torch.linalg.qr(A)
248
+
249
+ # Ensure proper rotation (det = 1)
250
+ d = torch.diag(R)
251
+ ph = torch.sign(d)
252
+ Q = Q * ph.unsqueeze(0)
253
+
254
+ return Q
255
+
256
+
257
+ def generate_shift_vector(
258
+ n: int,
259
+ xl: float = -80.0,
260
+ xu: float = 80.0,
261
+ seed: Optional[int] = None,
262
+ ) -> Tensor:
263
+ """
264
+ Generate a random shift vector within bounds.
265
+
266
+ Args:
267
+ n: Dimension of the vector.
268
+ xl: Lower bound.
269
+ xu: Upper bound.
270
+ seed: Optional random seed.
271
+
272
+ Returns:
273
+ Shift vector of shape [n].
274
+ """
275
+ if seed is not None:
276
+ torch.manual_seed(seed)
277
+
278
+ return xl + (xu - xl) * torch.rand(n)
279
+
280
+
281
+ def generate_shuffle(n: int, seed: Optional[int] = None) -> Tensor:
282
+ """
283
+ Generate a random permutation.
284
+
285
+ Args:
286
+ n: Length of permutation.
287
+ seed: Optional random seed.
288
+
289
+ Returns:
290
+ Permutation of shape [n] (0-indexed).
291
+ """
292
+ if seed is not None:
293
+ torch.manual_seed(seed)
294
+
295
+ return torch.randperm(n)
296
+
297
+
298
+ # =============================================================================
299
+ # TRANSFORM UTILITIES
300
+ # =============================================================================
301
+
302
+ def shift_rotate(x: Tensor, shift: Tensor, rotation: Tensor) -> Tensor:
303
+ """
304
+ Apply shift and rotation transformation.
305
+
306
+ z = R @ (x - shift)
307
+
308
+ Args:
309
+ x: Input tensor of shape [..., n_var].
310
+ shift: Shift vector of shape [n_var].
311
+ rotation: Rotation matrix of shape [n_var, n_var].
312
+
313
+ Returns:
314
+ Transformed tensor of shape [..., n_var].
315
+ """
316
+ shifted = x - shift
317
+ return shifted @ rotation.T
318
+
319
+
320
+ def shuffle_and_partition(
321
+ x: Tensor,
322
+ shuffle: Tensor,
323
+ partitions: list,
324
+ ) -> list:
325
+ """
326
+ Apply shuffle permutation and partition into parts.
327
+
328
+ Args:
329
+ x: Input tensor of shape [..., n_var].
330
+ shuffle: Permutation of shape [n_var] (0-indexed).
331
+ partitions: List of partition fractions (should sum to 1.0).
332
+
333
+ Returns:
334
+ List of tensors, one per partition.
335
+ """
336
+ nx = x.shape[-1]
337
+
338
+ # Apply shuffle
339
+ x_shuffled = x[..., shuffle]
340
+
341
+ # Partition
342
+ parts = []
343
+ start = 0
344
+ for i, p in enumerate(partitions[:-1]):
345
+ end = start + int(torch.ceil(torch.tensor(p * nx)).item())
346
+ parts.append(x_shuffled[..., start:end])
347
+ start = end
348
+ parts.append(x_shuffled[..., start:])
349
+
350
+ return parts