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,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
|