evograd-diff 0.2.0__tar.gz → 0.2.1__tar.gz
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_diff-0.2.0 → evograd_diff-0.2.1}/PKG-INFO +1 -1
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/__init__.py +1 -1
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/algorithms/cmaes.py +4 -7
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/algorithms/de.py +4 -4
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/algorithms/pso.py +3 -2
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/algorithms/shade.py +7 -8
- evograd_diff-0.2.1/evograd/benchmarks/functions/cec2017/_base.py +51 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/hybrid.py +10 -40
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/simple.py +9 -46
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/crossover.py +3 -5
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/mutation.py +6 -8
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/relaxations.py +30 -1
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/repair.py +11 -1
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/sampling.py +5 -7
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/selection.py +2 -4
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/run_all.py +21 -0
- evograd_diff-0.2.1/evograd/tests/test_de.py +62 -0
- evograd_diff-0.2.1/evograd/tests/test_pso.py +62 -0
- evograd_diff-0.2.1/evograd/tests/test_shade.py +64 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/pyproject.toml +1 -1
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/.gitignore +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/LICENSE +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/README.md +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/algorithms/__init__.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/algorithms/ga.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/__init__.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/base.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/__init__.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/basic.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/composition.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/data.pkl +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/data.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/classical.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/smoothed_funnel.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/transforms.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/run_benchmark_functions.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/core/__init__.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/core/algorithm.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/core/maximize.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/core/minimize.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/core/problem.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/core/result.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/core/termination.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/__init__.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/operators/survival.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/__init__.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/test_cmaes.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/test_core.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/test_ga.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/test_operators.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/test_per_individual.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/tests/test_utils.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/utils/__init__.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/utils/callbacks.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/utils/device.py +0 -0
- {evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/utils/duplicates.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: evograd-diff
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: PyTorch-based framework for differentiable evolutionary computation and swarm intelligence
|
|
5
5
|
Project-URL: Homepage, https://github.com/andreatangherloni/EvoGrad
|
|
6
6
|
Project-URL: Repository, https://github.com/andreatangherloni/EvoGrad
|
|
@@ -83,6 +83,7 @@ import torch.nn as nn
|
|
|
83
83
|
from torch import Tensor
|
|
84
84
|
|
|
85
85
|
from evograd.core.algorithm import Algorithm
|
|
86
|
+
from evograd.operators.relaxations import standard_normal, log_param
|
|
86
87
|
|
|
87
88
|
if TYPE_CHECKING:
|
|
88
89
|
from evograd.core.problem import Problem
|
|
@@ -386,9 +387,7 @@ class CMAES(Algorithm):
|
|
|
386
387
|
with torch.no_grad():
|
|
387
388
|
self._log_sigma.fill_(math.log(sigma_val))
|
|
388
389
|
else:
|
|
389
|
-
self._log_sigma =
|
|
390
|
-
torch.tensor(sigma_val, device=self.device, dtype=self.dtype).log()
|
|
391
|
-
)
|
|
390
|
+
self._log_sigma = log_param(sigma_val, device=self.device, dtype=self.dtype)
|
|
392
391
|
else:
|
|
393
392
|
if hasattr(self, '_sigma_buffer'):
|
|
394
393
|
self._sigma_buffer.fill_(sigma_val)
|
|
@@ -467,9 +466,7 @@ class CMAES(Algorithm):
|
|
|
467
466
|
self._to_logit(cmu).to(device=self.device, dtype=self.dtype)
|
|
468
467
|
)
|
|
469
468
|
# damps is positive, store as log
|
|
470
|
-
self._log_damps =
|
|
471
|
-
torch.tensor(damps, device=self.device, dtype=self.dtype).log()
|
|
472
|
-
)
|
|
469
|
+
self._log_damps = log_param(damps, device=self.device, dtype=self.dtype)
|
|
473
470
|
else:
|
|
474
471
|
if not hasattr(self, '_cc'):
|
|
475
472
|
self.register_buffer("_cc", torch.tensor(cc, device=self.device, dtype=self.dtype))
|
|
@@ -587,7 +584,7 @@ class CMAES(Algorithm):
|
|
|
587
584
|
N, D = self.pop_size, self.n_var
|
|
588
585
|
|
|
589
586
|
# Sample standard normal
|
|
590
|
-
z =
|
|
587
|
+
z = standard_normal(N, D, device=self.device, dtype=self.dtype)
|
|
591
588
|
|
|
592
589
|
# Transform: y = L @ z
|
|
593
590
|
L = self.L
|
|
@@ -63,6 +63,8 @@ import torch.nn as nn
|
|
|
63
63
|
from torch import Tensor
|
|
64
64
|
|
|
65
65
|
from evograd.core.algorithm import Algorithm
|
|
66
|
+
from evograd.operators.repair import clamp_to_bounds
|
|
67
|
+
from evograd.operators.relaxations import log_param
|
|
66
68
|
|
|
67
69
|
if TYPE_CHECKING:
|
|
68
70
|
from evograd.core.problem import Problem
|
|
@@ -308,9 +310,7 @@ class DE(Algorithm):
|
|
|
308
310
|
# Setup F parameter
|
|
309
311
|
if self.adaptive:
|
|
310
312
|
# Learnable F stored as log(F) for positivity
|
|
311
|
-
self._log_F =
|
|
312
|
-
torch.tensor(self._init_F, device=self.device, dtype=self.dtype).log()
|
|
313
|
-
)
|
|
313
|
+
self._log_F = log_param(self._init_F, device=self.device, dtype=self.dtype)
|
|
314
314
|
else:
|
|
315
315
|
self.register_buffer(
|
|
316
316
|
"_F_buffer",
|
|
@@ -627,7 +627,7 @@ class DE(Algorithm):
|
|
|
627
627
|
trial = self.repair(trial, self.xl, self.xu)
|
|
628
628
|
else:
|
|
629
629
|
# Default: clamp to bounds
|
|
630
|
-
trial =
|
|
630
|
+
trial = clamp_to_bounds(trial, self.xl, self.xu)
|
|
631
631
|
|
|
632
632
|
return trial
|
|
633
633
|
|
|
@@ -52,6 +52,7 @@ import torch.nn as nn
|
|
|
52
52
|
from torch import Tensor
|
|
53
53
|
|
|
54
54
|
from evograd.core.algorithm import Algorithm
|
|
55
|
+
from evograd.operators.repair import clamp_to_bounds
|
|
55
56
|
|
|
56
57
|
if TYPE_CHECKING:
|
|
57
58
|
from evograd.core.problem import Problem
|
|
@@ -357,7 +358,7 @@ class PSO(Algorithm):
|
|
|
357
358
|
pos_repaired = torch.where(above, 2 * self.xu - position, pos_repaired)
|
|
358
359
|
|
|
359
360
|
# Clamp to ensure within bounds (in case of large violations)
|
|
360
|
-
pos_repaired =
|
|
361
|
+
pos_repaired = clamp_to_bounds(pos_repaired, self.xl, self.xu)
|
|
361
362
|
|
|
362
363
|
# Reverse velocity at boundaries
|
|
363
364
|
vel_repaired = velocity.clone()
|
|
@@ -473,7 +474,7 @@ class PSO(Algorithm):
|
|
|
473
474
|
old_pos = new_pos - new_velocity # P₀
|
|
474
475
|
grad_delta = self._population.data - old_pos # −lr·∇
|
|
475
476
|
combined = new_pos + grad_delta # P₀ + v − lr·∇
|
|
476
|
-
combined =
|
|
477
|
+
combined = clamp_to_bounds(combined, self.xl, self.xu)
|
|
477
478
|
self._population.copy_(combined)
|
|
478
479
|
else:
|
|
479
480
|
self._population.copy_(new_pos)
|
|
@@ -65,6 +65,7 @@ import torch.nn as nn
|
|
|
65
65
|
from torch import Tensor
|
|
66
66
|
|
|
67
67
|
from evograd.core.algorithm import Algorithm
|
|
68
|
+
from evograd.operators.relaxations import standard_normal
|
|
68
69
|
|
|
69
70
|
if TYPE_CHECKING:
|
|
70
71
|
from evograd.core.problem import Problem
|
|
@@ -197,7 +198,7 @@ class SHADEMemory:
|
|
|
197
198
|
|
|
198
199
|
# Sample from Normal distribution
|
|
199
200
|
sigma = 0.1 # Standard SHADE scale parameter
|
|
200
|
-
CR = mu_CR + sigma *
|
|
201
|
+
CR = mu_CR + sigma * standard_normal(n, device=device, dtype=dtype)
|
|
201
202
|
|
|
202
203
|
# Truncate to [0, 1]
|
|
203
204
|
CR = torch.clamp(CR, min=0.0, max=1.0)
|
|
@@ -520,7 +521,7 @@ class SHADE(Algorithm):
|
|
|
520
521
|
|
|
521
522
|
# Reparameterized Normal for CR
|
|
522
523
|
sigma = 0.1
|
|
523
|
-
eps =
|
|
524
|
+
eps = standard_normal(N, device=self.device, dtype=self.dtype)
|
|
524
525
|
CR = mu_CR + sigma * eps
|
|
525
526
|
CR = torch.clamp(CR, min=0.0, max=1.0)
|
|
526
527
|
else:
|
|
@@ -886,8 +887,6 @@ class LSHADE(SHADE):
|
|
|
886
887
|
adaptive: bool = False,
|
|
887
888
|
differentiable: bool = False,
|
|
888
889
|
selection_temperature: float = 1.0,
|
|
889
|
-
seed: Optional[int] = None,
|
|
890
|
-
device: Optional[Union[str, torch.device]] = None,
|
|
891
890
|
dtype: torch.dtype = torch.float32,
|
|
892
891
|
) -> None:
|
|
893
892
|
self.pop_size_init = pop_size_init # Will be set in _setup if None
|
|
@@ -909,8 +908,6 @@ class LSHADE(SHADE):
|
|
|
909
908
|
differentiable=differentiable,
|
|
910
909
|
adaptive=adaptive,
|
|
911
910
|
selection_temperature=selection_temperature,
|
|
912
|
-
seed=seed,
|
|
913
|
-
device=device,
|
|
914
911
|
dtype=dtype,
|
|
915
912
|
)
|
|
916
913
|
|
|
@@ -919,8 +916,10 @@ class LSHADE(SHADE):
|
|
|
919
916
|
# Set default pop_size_init based on problem dimension
|
|
920
917
|
if self.pop_size_init is None:
|
|
921
918
|
self.pop_size_init = 18 * self.problem.n_var
|
|
922
|
-
|
|
923
|
-
|
|
919
|
+
# Always initialise the working population size (fixes target_pop_size
|
|
920
|
+
# AttributeError when pop_size_init was provided explicitly).
|
|
921
|
+
self._pop_size = self.pop_size_init
|
|
922
|
+
|
|
924
923
|
# Create success-history memory with L-SHADE archive size
|
|
925
924
|
self.memory = SHADEMemory.create(
|
|
926
925
|
H=self.memory_size,
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared base for CEC 2017 functions.
|
|
3
|
+
|
|
4
|
+
Single source of truth for the bias, shift/rotation loading, default bounds, and
|
|
5
|
+
the shift+rotate transform used by the simple and hybrid CEC 2017 families
|
|
6
|
+
(previously copy-pasted in simple.py and hybrid.py).
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Optional, Tuple
|
|
11
|
+
|
|
12
|
+
from torch import Tensor
|
|
13
|
+
|
|
14
|
+
from ..base import BenchmarkFunction
|
|
15
|
+
from . import data as cec_data
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class CEC2017Base(BenchmarkFunction):
|
|
19
|
+
"""Base for shifted+rotated CEC 2017 functions (search space [-100, 100]^D)."""
|
|
20
|
+
|
|
21
|
+
def __init__(
|
|
22
|
+
self,
|
|
23
|
+
func_num: int,
|
|
24
|
+
n_var: int = 10,
|
|
25
|
+
rotation: Optional[Tensor] = None,
|
|
26
|
+
shift: Optional[Tensor] = None,
|
|
27
|
+
seed: Optional[int] = None,
|
|
28
|
+
):
|
|
29
|
+
super().__init__(n_var=n_var, xl=-100.0, xu=100.0)
|
|
30
|
+
|
|
31
|
+
self.func_num = func_num
|
|
32
|
+
self.bias = func_num * 100.0 # F_i* = i * 100
|
|
33
|
+
|
|
34
|
+
if rotation is not None:
|
|
35
|
+
self.rotation = rotation
|
|
36
|
+
else:
|
|
37
|
+
self.rotation = cec_data.get_rotation(func_num, n_var, seed=seed)
|
|
38
|
+
|
|
39
|
+
if shift is not None:
|
|
40
|
+
self.shift = shift
|
|
41
|
+
else:
|
|
42
|
+
self.shift = cec_data.get_shift(func_num, n_var, seed=seed)
|
|
43
|
+
|
|
44
|
+
def default_bounds(self) -> Tuple[float, float]:
|
|
45
|
+
return (-100.0, 100.0)
|
|
46
|
+
|
|
47
|
+
def _shift_rotate(self, x: Tensor) -> Tensor:
|
|
48
|
+
"""Apply the shift + rotation transform (single source of truth)."""
|
|
49
|
+
shift = self.shift.to(x.device, x.dtype)
|
|
50
|
+
rotation = self.rotation.to(x.device, x.dtype)
|
|
51
|
+
return cec_data.shift_rotate(x, shift, rotation)
|
|
@@ -26,11 +26,15 @@ from torch import Tensor
|
|
|
26
26
|
from ..base import BenchmarkFunction
|
|
27
27
|
from . import basic
|
|
28
28
|
from . import data as cec_data
|
|
29
|
+
from ._base import CEC2017Base
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
class CEC2017HybridFunction(
|
|
32
|
-
"""Base class for CEC 2017 hybrid functions.
|
|
33
|
-
|
|
32
|
+
class CEC2017HybridFunction(CEC2017Base):
|
|
33
|
+
"""Base class for CEC 2017 hybrid functions (adds a shuffle permutation).
|
|
34
|
+
|
|
35
|
+
Shared shift/rotation handling is inherited from CEC2017Base (see _base.py).
|
|
36
|
+
"""
|
|
37
|
+
|
|
34
38
|
def __init__(
|
|
35
39
|
self,
|
|
36
40
|
func_num: int,
|
|
@@ -40,47 +44,13 @@ class CEC2017HybridFunction(BenchmarkFunction):
|
|
|
40
44
|
shuffle: Optional[Tensor] = None,
|
|
41
45
|
seed: Optional[int] = None,
|
|
42
46
|
):
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
Args:
|
|
47
|
-
func_num: Function number (11-20).
|
|
48
|
-
n_var: Number of variables.
|
|
49
|
-
rotation: Optional rotation matrix.
|
|
50
|
-
shift: Optional shift vector.
|
|
51
|
-
shuffle: Optional shuffle permutation.
|
|
52
|
-
seed: Random seed for generating transforms if not provided.
|
|
53
|
-
"""
|
|
54
|
-
super().__init__(n_var=n_var, xl=-100.0, xu=100.0)
|
|
55
|
-
|
|
56
|
-
self.func_num = func_num
|
|
57
|
-
self.bias = func_num * 100.0
|
|
58
|
-
|
|
59
|
-
# Load or generate transforms
|
|
60
|
-
if rotation is not None:
|
|
61
|
-
self.rotation = rotation
|
|
62
|
-
else:
|
|
63
|
-
self.rotation = cec_data.get_rotation(func_num, n_var, seed=seed)
|
|
64
|
-
|
|
65
|
-
if shift is not None:
|
|
66
|
-
self.shift = shift
|
|
67
|
-
else:
|
|
68
|
-
self.shift = cec_data.get_shift(func_num, n_var, seed=seed)
|
|
69
|
-
|
|
47
|
+
super().__init__(func_num=func_num, n_var=n_var,
|
|
48
|
+
rotation=rotation, shift=shift, seed=seed)
|
|
70
49
|
if shuffle is not None:
|
|
71
50
|
self.shuffle = shuffle
|
|
72
51
|
else:
|
|
73
52
|
self.shuffle = cec_data.get_shuffle(func_num, n_var, seed=seed)
|
|
74
|
-
|
|
75
|
-
def default_bounds(self) -> Tuple[float, float]:
|
|
76
|
-
return (-100.0, 100.0)
|
|
77
|
-
|
|
78
|
-
def _shift_rotate(self, x: Tensor) -> Tensor:
|
|
79
|
-
"""Apply shift and rotation transformation."""
|
|
80
|
-
shift = self.shift.to(x.device, x.dtype)
|
|
81
|
-
rotation = self.rotation.to(x.device, x.dtype)
|
|
82
|
-
return cec_data.shift_rotate(x, shift, rotation)
|
|
83
|
-
|
|
53
|
+
|
|
84
54
|
def _shuffle_and_partition(self, x: Tensor, partitions: List[float]) -> List[Tensor]:
|
|
85
55
|
"""Apply shuffle and partition."""
|
|
86
56
|
shuffle = self.shuffle.to(x.device)
|
|
@@ -26,54 +26,17 @@ from torch import Tensor
|
|
|
26
26
|
from ..base import BenchmarkFunction
|
|
27
27
|
from . import basic
|
|
28
28
|
from . import data as cec_data
|
|
29
|
+
from ._base import CEC2017Base
|
|
29
30
|
|
|
30
31
|
|
|
31
|
-
class CEC2017Function(
|
|
32
|
-
"""Base class for CEC 2017 functions
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
shift: Optional[Tensor] = None,
|
|
40
|
-
seed: Optional[int] = None,
|
|
41
|
-
):
|
|
42
|
-
"""
|
|
43
|
-
Initialize CEC 2017 function.
|
|
44
|
-
|
|
45
|
-
Args:
|
|
46
|
-
func_num: Function number (1-30).
|
|
47
|
-
n_var: Number of variables (2, 10, 20, 30, 50, or 100 for official data).
|
|
48
|
-
rotation: Optional rotation matrix. If None, loads from data or generates.
|
|
49
|
-
shift: Optional shift vector. If None, loads from data or generates.
|
|
50
|
-
seed: Random seed for generating transforms if not provided.
|
|
51
|
-
"""
|
|
52
|
-
super().__init__(n_var=n_var, xl=-100.0, xu=100.0)
|
|
53
|
-
|
|
54
|
-
self.func_num = func_num
|
|
55
|
-
self.bias = func_num * 100.0 # F_i* = i * 100
|
|
56
|
-
|
|
57
|
-
# Load or generate rotation matrix
|
|
58
|
-
if rotation is not None:
|
|
59
|
-
self.rotation = rotation
|
|
60
|
-
else:
|
|
61
|
-
self.rotation = cec_data.get_rotation(func_num, n_var, seed=seed)
|
|
62
|
-
|
|
63
|
-
# Load or generate shift vector
|
|
64
|
-
if shift is not None:
|
|
65
|
-
self.shift = shift
|
|
66
|
-
else:
|
|
67
|
-
self.shift = cec_data.get_shift(func_num, n_var, seed=seed)
|
|
68
|
-
|
|
69
|
-
def default_bounds(self) -> Tuple[float, float]:
|
|
70
|
-
return (-100.0, 100.0)
|
|
71
|
-
|
|
72
|
-
def _shift_rotate(self, x: Tensor) -> Tensor:
|
|
73
|
-
"""Apply shift and rotation transformation."""
|
|
74
|
-
shift = self.shift.to(x.device, x.dtype)
|
|
75
|
-
rotation = self.rotation.to(x.device, x.dtype)
|
|
76
|
-
return cec_data.shift_rotate(x, shift, rotation)
|
|
32
|
+
class CEC2017Function(CEC2017Base):
|
|
33
|
+
"""Base class for CEC 2017 simple functions (shift + rotation).
|
|
34
|
+
|
|
35
|
+
The shared __init__, default_bounds, and _shift_rotate live in
|
|
36
|
+
CEC2017Base (see _base.py).
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
pass
|
|
77
40
|
|
|
78
41
|
|
|
79
42
|
class CEC2017_F1(CEC2017Function):
|
|
@@ -71,7 +71,7 @@ import torch
|
|
|
71
71
|
import torch.nn as nn
|
|
72
72
|
from torch import Tensor
|
|
73
73
|
|
|
74
|
-
from evograd.operators.relaxations import binary_concrete, expand_param
|
|
74
|
+
from evograd.operators.relaxations import binary_concrete, expand_param, log_param
|
|
75
75
|
|
|
76
76
|
__all__ = [
|
|
77
77
|
"Crossover",
|
|
@@ -136,9 +136,7 @@ class Crossover(nn.Module, ABC):
|
|
|
136
136
|
|
|
137
137
|
# Temperature parameter (log for positivity)
|
|
138
138
|
if learn_temperature and adaptive:
|
|
139
|
-
self._log_temperature =
|
|
140
|
-
torch.tensor(temperature).log()
|
|
141
|
-
)
|
|
139
|
+
self._log_temperature = log_param(temperature)
|
|
142
140
|
else:
|
|
143
141
|
self.register_buffer(
|
|
144
142
|
"_log_temperature",
|
|
@@ -328,7 +326,7 @@ class SBXCrossover(Crossover):
|
|
|
328
326
|
|
|
329
327
|
# Eta parameter (log for positivity)
|
|
330
328
|
if learn_eta and adaptive:
|
|
331
|
-
self._log_eta =
|
|
329
|
+
self._log_eta = log_param(eta)
|
|
332
330
|
else:
|
|
333
331
|
self.register_buffer("_log_eta", torch.tensor(eta).log())
|
|
334
332
|
|
|
@@ -67,7 +67,7 @@ import torch
|
|
|
67
67
|
import torch.nn as nn
|
|
68
68
|
from torch import Tensor
|
|
69
69
|
|
|
70
|
-
from evograd.operators.relaxations import binary_concrete, expand_param
|
|
70
|
+
from evograd.operators.relaxations import binary_concrete, expand_param, standard_normal, log_param
|
|
71
71
|
|
|
72
72
|
if TYPE_CHECKING:
|
|
73
73
|
from evograd.core.problem import Problem
|
|
@@ -136,9 +136,7 @@ class Mutation(nn.Module, ABC):
|
|
|
136
136
|
|
|
137
137
|
# Temperature parameter (log for positivity)
|
|
138
138
|
if learn_temperature and adaptive:
|
|
139
|
-
self._log_temperature =
|
|
140
|
-
torch.tensor(temperature).log()
|
|
141
|
-
)
|
|
139
|
+
self._log_temperature = log_param(temperature)
|
|
142
140
|
else:
|
|
143
141
|
self.register_buffer(
|
|
144
142
|
"_log_temperature",
|
|
@@ -366,7 +364,7 @@ class PolynomialMutation(Mutation):
|
|
|
366
364
|
|
|
367
365
|
# Eta parameter (log for positivity)
|
|
368
366
|
if learn_eta and adaptive:
|
|
369
|
-
self._log_eta =
|
|
367
|
+
self._log_eta = log_param(eta)
|
|
370
368
|
else:
|
|
371
369
|
self.register_buffer("_log_eta", torch.tensor(eta).log())
|
|
372
370
|
|
|
@@ -523,7 +521,7 @@ class GaussianMutation(Mutation):
|
|
|
523
521
|
# Sigma parameter (log for positivity)
|
|
524
522
|
sigma_val = sigma if sigma is not None else sigma_frac
|
|
525
523
|
if learn_sigma and adaptive:
|
|
526
|
-
self._log_sigma =
|
|
524
|
+
self._log_sigma = log_param(sigma_val)
|
|
527
525
|
else:
|
|
528
526
|
self.register_buffer("_log_sigma", torch.tensor(sigma_val).log())
|
|
529
527
|
|
|
@@ -593,7 +591,7 @@ class GaussianMutation(Mutation):
|
|
|
593
591
|
sigma_expanded = expand_param(None, self.sigma, n_pop, n_var, device, dtype)
|
|
594
592
|
|
|
595
593
|
# Gaussian noise (reparameterised)
|
|
596
|
-
noise =
|
|
594
|
+
noise = standard_normal(n_pop, n_var, device=device, dtype=dtype) * sigma_expanded
|
|
597
595
|
|
|
598
596
|
# Apply mutation with mask
|
|
599
597
|
y = x + mask * noise
|
|
@@ -779,7 +777,7 @@ class NonUniformMutation(Mutation):
|
|
|
779
777
|
|
|
780
778
|
# B parameter (log for positivity)
|
|
781
779
|
if learn_b and adaptive:
|
|
782
|
-
self._log_b =
|
|
780
|
+
self._log_b = log_param(b)
|
|
783
781
|
else:
|
|
784
782
|
self.register_buffer("_log_b", torch.tensor(b).log())
|
|
785
783
|
|
|
@@ -13,12 +13,14 @@ Functions:
|
|
|
13
13
|
|
|
14
14
|
import torch
|
|
15
15
|
from torch import Tensor
|
|
16
|
-
from typing import Union
|
|
16
|
+
from typing import Optional, Union
|
|
17
17
|
|
|
18
18
|
__all__ = [
|
|
19
19
|
"gumbel_softmax",
|
|
20
20
|
"binary_concrete",
|
|
21
21
|
"expand_param",
|
|
22
|
+
"standard_normal",
|
|
23
|
+
"log_param",
|
|
22
24
|
]
|
|
23
25
|
|
|
24
26
|
|
|
@@ -173,3 +175,30 @@ def expand_param(
|
|
|
173
175
|
)
|
|
174
176
|
else:
|
|
175
177
|
raise ValueError(f"param must be 0D, 1D, or 2D, got {val.dim()}D")
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def standard_normal(
|
|
181
|
+
*shape: int,
|
|
182
|
+
device: Optional[torch.device] = None,
|
|
183
|
+
dtype: Optional[torch.dtype] = None,
|
|
184
|
+
generator: Optional[torch.Generator] = None,
|
|
185
|
+
) -> Tensor:
|
|
186
|
+
"""Draw standard-normal (pathwise / reparameterization) noise.
|
|
187
|
+
|
|
188
|
+
Single source for the ``torch.randn(..., device=, dtype=)`` reparameterization
|
|
189
|
+
draw, reused by CMA-ES sampling, Gaussian mutation, and the samplers.
|
|
190
|
+
"""
|
|
191
|
+
return torch.randn(*shape, device=device, dtype=dtype, generator=generator)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def log_param(
|
|
195
|
+
value: Union[float, Tensor],
|
|
196
|
+
device: Optional[torch.device] = None,
|
|
197
|
+
dtype: Optional[torch.dtype] = None,
|
|
198
|
+
) -> "torch.nn.Parameter":
|
|
199
|
+
"""Learnable positive hyperparameter stored on the log scale.
|
|
200
|
+
|
|
201
|
+
Single source for the repeated ``nn.Parameter(torch.tensor(v).log())`` idiom
|
|
202
|
+
used for sigma, F, eta, temperature, etc. Recover the value via ``.exp()``.
|
|
203
|
+
"""
|
|
204
|
+
return torch.nn.Parameter(torch.tensor(value, device=device, dtype=dtype).log())
|
|
@@ -50,9 +50,19 @@ __all__ = [
|
|
|
50
50
|
"BoundsRepair",
|
|
51
51
|
"RepairMethod",
|
|
52
52
|
"NoRepair",
|
|
53
|
+
"clamp_to_bounds",
|
|
53
54
|
]
|
|
54
55
|
|
|
55
56
|
|
|
57
|
+
def clamp_to_bounds(x: Tensor, xl: Tensor, xu: Tensor) -> Tensor:
|
|
58
|
+
"""Clamp ``x`` element-wise to the box ``[xl, xu]``.
|
|
59
|
+
|
|
60
|
+
Single source of truth for box-bounds clamping: reused by ClipRepair and by
|
|
61
|
+
the algorithms (PSO/DE) instead of repeating ``torch.clamp`` inline.
|
|
62
|
+
"""
|
|
63
|
+
return torch.clamp(x, min=xl, max=xu)
|
|
64
|
+
|
|
65
|
+
|
|
56
66
|
class RepairMethod(Enum):
|
|
57
67
|
"""Available repair methods."""
|
|
58
68
|
CLIP = "clip"
|
|
@@ -189,7 +199,7 @@ class ClipRepair(Repair):
|
|
|
189
199
|
xl: Tensor,
|
|
190
200
|
xu: Tensor,
|
|
191
201
|
) -> Tensor:
|
|
192
|
-
return
|
|
202
|
+
return clamp_to_bounds(x, xl, xu)
|
|
193
203
|
|
|
194
204
|
def __repr__(self) -> str:
|
|
195
205
|
return "ClipRepair()"
|
|
@@ -32,6 +32,8 @@ import torch
|
|
|
32
32
|
import torch.nn as nn
|
|
33
33
|
from torch import Tensor
|
|
34
34
|
|
|
35
|
+
from evograd.operators.relaxations import standard_normal
|
|
36
|
+
|
|
35
37
|
if TYPE_CHECKING:
|
|
36
38
|
from evograd.core.problem import Problem
|
|
37
39
|
|
|
@@ -335,16 +337,12 @@ class NormalSampling(Sampling):
|
|
|
335
337
|
if generator is not None:
|
|
336
338
|
# Generator may be on CPU for MPS devices
|
|
337
339
|
gen_device = generator.device
|
|
338
|
-
z =
|
|
339
|
-
|
|
340
|
-
device=gen_device,
|
|
341
|
-
dtype=dtype,
|
|
342
|
-
generator=generator,
|
|
343
|
-
)
|
|
340
|
+
z = standard_normal(n_samples, n_var, device=gen_device, dtype=dtype,
|
|
341
|
+
generator=generator)
|
|
344
342
|
if gen_device != device:
|
|
345
343
|
z = z.to(device)
|
|
346
344
|
else:
|
|
347
|
-
z =
|
|
345
|
+
z = standard_normal(n_samples, n_var, device=device, dtype=dtype)
|
|
348
346
|
|
|
349
347
|
# Transform to [0, 1] centered at 0.5 with scaled std
|
|
350
348
|
# mean=0.5, std=sigma_factor * 0.5
|
|
@@ -44,7 +44,7 @@ import torch
|
|
|
44
44
|
import torch.nn as nn
|
|
45
45
|
from torch import Tensor
|
|
46
46
|
|
|
47
|
-
from evograd.operators.relaxations import gumbel_softmax
|
|
47
|
+
from evograd.operators.relaxations import gumbel_softmax, log_param
|
|
48
48
|
|
|
49
49
|
__all__ = [
|
|
50
50
|
"Selection",
|
|
@@ -94,9 +94,7 @@ class Selection(nn.Module, ABC):
|
|
|
94
94
|
# Temperature parameter
|
|
95
95
|
if learn_temperature and adaptive:
|
|
96
96
|
# Store as log for positivity
|
|
97
|
-
self._log_temperature =
|
|
98
|
-
torch.tensor(temperature).log()
|
|
99
|
-
)
|
|
97
|
+
self._log_temperature = log_param(temperature)
|
|
100
98
|
else:
|
|
101
99
|
self.register_buffer(
|
|
102
100
|
"_log_temperature",
|
|
@@ -18,6 +18,9 @@ from tests.test_core import run_all_tests as test_core
|
|
|
18
18
|
from tests.test_operators import run_all_tests as test_operators
|
|
19
19
|
from tests.test_cmaes import run_all_tests as test_cmaes
|
|
20
20
|
from tests.test_ga import run_all_tests as test_ga
|
|
21
|
+
from tests.test_de import run_all_tests as test_de
|
|
22
|
+
from tests.test_pso import run_all_tests as test_pso
|
|
23
|
+
from tests.test_shade import run_all_tests as test_shade
|
|
21
24
|
from tests.test_per_individual import main as test_per_individual
|
|
22
25
|
|
|
23
26
|
|
|
@@ -61,6 +64,24 @@ def run_all():
|
|
|
61
64
|
print("▶"*60)
|
|
62
65
|
results['ga'] = test_ga()
|
|
63
66
|
|
|
67
|
+
# Run DE tests
|
|
68
|
+
print("\n\n" + "▶"*60)
|
|
69
|
+
print("▶ RUNNING DE TESTS")
|
|
70
|
+
print("▶"*60)
|
|
71
|
+
results['de'] = test_de()
|
|
72
|
+
|
|
73
|
+
# Run PSO tests
|
|
74
|
+
print("\n\n" + "▶"*60)
|
|
75
|
+
print("▶ RUNNING PSO TESTS")
|
|
76
|
+
print("▶"*60)
|
|
77
|
+
results['pso'] = test_pso()
|
|
78
|
+
|
|
79
|
+
# Run SHADE tests
|
|
80
|
+
print("\n\n" + "▶"*60)
|
|
81
|
+
print("▶ RUNNING SHADE TESTS")
|
|
82
|
+
print("▶"*60)
|
|
83
|
+
results['shade'] = test_shade()
|
|
84
|
+
|
|
64
85
|
# Run per-individual parameter tests
|
|
65
86
|
print("\n\n" + "▶"*60)
|
|
66
87
|
print("▶ RUNNING PER-INDIVIDUAL TESTS")
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Behavioural tests for Differential Evolution — coverage added before the refactor."""
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
from evograd.core.problem import Problem
|
|
8
|
+
from evograd.core.minimize import minimize
|
|
9
|
+
from evograd.core.termination import MaxEvaluations
|
|
10
|
+
from evograd.algorithms.de import DE
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sphere(x):
|
|
14
|
+
return (x ** 2).sum(dim=-1)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def P(n=10, lo=-5.0, hi=5.0):
|
|
18
|
+
return Problem(objective=sphere, n_var=n, xl=lo, xu=hi)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_de_converges():
|
|
22
|
+
print("\n1. DE converges on sphere...")
|
|
23
|
+
r = minimize(P(10), DE(pop_size=30), termination=MaxEvaluations(8000), seed=0, verbose=False)
|
|
24
|
+
assert r.best_fitness < 1.0, f"DE best {r.best_fitness} !< 1.0"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_de_variants():
|
|
28
|
+
print("2. DE variants run, finite fitness...")
|
|
29
|
+
for v in ("DE/rand/1/bin", "DE/best/1/bin", "DE/current-to-best/1/bin", "DE/rand/2/bin"):
|
|
30
|
+
r = minimize(P(8), DE(pop_size=20, variant=v), termination=MaxEvaluations(1500), seed=1, verbose=False)
|
|
31
|
+
assert math.isfinite(r.best_fitness), f"DE {v} non-finite fitness"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_de_four_modes():
|
|
35
|
+
print("3. DE four modes run, finite fitness...")
|
|
36
|
+
for kw in ({}, dict(adaptive=True), dict(differentiable=True), dict(adaptive=True, differentiable=True)):
|
|
37
|
+
r = minimize(P(8), DE(pop_size=20, **kw), termination=MaxEvaluations(800), seed=2, verbose=False)
|
|
38
|
+
assert math.isfinite(r.best_fitness), f"DE {kw} non-finite fitness"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_de_respects_bounds():
|
|
42
|
+
print("4. DE respects bounds...")
|
|
43
|
+
r = minimize(P(6, -2.0, 2.0), DE(pop_size=20), termination=MaxEvaluations(1500), seed=3, verbose=False)
|
|
44
|
+
x = r.best_solution
|
|
45
|
+
assert (x >= -2.0 - 1e-3).all() and (x <= 2.0 + 1e-3).all(), "DE violated bounds"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run_all_tests():
|
|
49
|
+
ok = True
|
|
50
|
+
for t in (test_de_converges, test_de_variants, test_de_four_modes, test_de_respects_bounds):
|
|
51
|
+
try:
|
|
52
|
+
t()
|
|
53
|
+
print(" PASS")
|
|
54
|
+
except Exception:
|
|
55
|
+
ok = False
|
|
56
|
+
import traceback
|
|
57
|
+
traceback.print_exc()
|
|
58
|
+
return ok
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
sys.exit(0 if run_all_tests() else 1)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""Behavioural tests for Particle Swarm Optimisation — coverage added before the refactor."""
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
from evograd.core.problem import Problem
|
|
8
|
+
from evograd.core.minimize import minimize
|
|
9
|
+
from evograd.core.termination import MaxEvaluations
|
|
10
|
+
from evograd.algorithms.pso import PSO
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sphere(x):
|
|
14
|
+
return (x ** 2).sum(dim=-1)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def P(n=10, lo=-5.0, hi=5.0):
|
|
18
|
+
return Problem(objective=sphere, n_var=n, xl=lo, xu=hi)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_pso_converges():
|
|
22
|
+
print("\n1. PSO converges on sphere...")
|
|
23
|
+
r = minimize(P(10), PSO(pop_size=30), termination=MaxEvaluations(8000), seed=0, verbose=False)
|
|
24
|
+
assert r.best_fitness < 1.0, f"PSO best {r.best_fitness} !< 1.0"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_pso_four_modes():
|
|
28
|
+
print("2. PSO four modes run, finite fitness...")
|
|
29
|
+
for kw in ({}, dict(adaptive=True), dict(differentiable=True), dict(adaptive=True, differentiable=True)):
|
|
30
|
+
r = minimize(P(8), PSO(pop_size=20, **kw), termination=MaxEvaluations(800), seed=1, verbose=False)
|
|
31
|
+
assert math.isfinite(r.best_fitness), f"PSO {kw} non-finite fitness"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_pso_per_particle_coeffs():
|
|
35
|
+
print("3. PSO per-particle adaptive coeffs run...")
|
|
36
|
+
r = minimize(P(8), PSO(pop_size=20, adaptive=True, per_particle_coeffs=True),
|
|
37
|
+
termination=MaxEvaluations(800), seed=2, verbose=False)
|
|
38
|
+
assert math.isfinite(r.best_fitness), "PSO per-particle non-finite fitness"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_pso_respects_bounds():
|
|
42
|
+
print("4. PSO respects bounds...")
|
|
43
|
+
r = minimize(P(6, -2.0, 2.0), PSO(pop_size=20), termination=MaxEvaluations(1500), seed=3, verbose=False)
|
|
44
|
+
x = r.best_solution
|
|
45
|
+
assert (x >= -2.0 - 1e-3).all() and (x <= 2.0 + 1e-3).all(), "PSO violated bounds"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def run_all_tests():
|
|
49
|
+
ok = True
|
|
50
|
+
for t in (test_pso_converges, test_pso_four_modes, test_pso_per_particle_coeffs, test_pso_respects_bounds):
|
|
51
|
+
try:
|
|
52
|
+
t()
|
|
53
|
+
print(" PASS")
|
|
54
|
+
except Exception:
|
|
55
|
+
ok = False
|
|
56
|
+
import traceback
|
|
57
|
+
traceback.print_exc()
|
|
58
|
+
return ok
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == "__main__":
|
|
62
|
+
sys.exit(0 if run_all_tests() else 1)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Behavioural tests for SHADE (and LSHADE) — coverage added before the refactor."""
|
|
2
|
+
import math
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
import torch
|
|
6
|
+
|
|
7
|
+
from evograd.core.problem import Problem
|
|
8
|
+
from evograd.core.minimize import minimize
|
|
9
|
+
from evograd.core.termination import MaxEvaluations
|
|
10
|
+
from evograd.algorithms.shade import SHADE, LSHADE
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def sphere(x):
|
|
14
|
+
return (x ** 2).sum(dim=-1)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def P(n=10, lo=-5.0, hi=5.0):
|
|
18
|
+
return Problem(objective=sphere, n_var=n, xl=lo, xu=hi)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_shade_converges():
|
|
22
|
+
print("\n1. SHADE converges on sphere...")
|
|
23
|
+
r = minimize(P(10), SHADE(pop_size=30), termination=MaxEvaluations(3000), seed=0, verbose=False)
|
|
24
|
+
assert r.best_fitness < 1.0, f"SHADE best {r.best_fitness} !< 1.0"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_shade_four_modes():
|
|
28
|
+
print("2. SHADE four modes run, finite fitness...")
|
|
29
|
+
for kw in ({}, dict(adaptive=True), dict(differentiable=True), dict(adaptive=True, differentiable=True)):
|
|
30
|
+
r = minimize(P(8), SHADE(pop_size=20, **kw), termination=MaxEvaluations(800), seed=1, verbose=False)
|
|
31
|
+
assert math.isfinite(r.best_fitness), f"SHADE {kw} non-finite fitness"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def test_shade_respects_bounds():
|
|
35
|
+
print("3. SHADE respects bounds...")
|
|
36
|
+
r = minimize(P(6, -2.0, 2.0), SHADE(pop_size=20), termination=MaxEvaluations(1200), seed=2, verbose=False)
|
|
37
|
+
x = r.best_solution
|
|
38
|
+
assert (x >= -2.0 - 1e-3).all() and (x <= 2.0 + 1e-3).all(), "SHADE violated bounds"
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_lshade_runs():
|
|
42
|
+
print("4. LSHADE runs, finite fitness, respects bounds (fixed)...")
|
|
43
|
+
r = minimize(P(10), LSHADE(pop_size_init=40, pop_size_min=4),
|
|
44
|
+
termination=MaxEvaluations(2000), seed=3, verbose=False)
|
|
45
|
+
assert math.isfinite(r.best_fitness), "LSHADE non-finite fitness"
|
|
46
|
+
x = r.best_solution
|
|
47
|
+
assert (x >= -5.0 - 1e-3).all() and (x <= 5.0 + 1e-3).all(), "LSHADE violated bounds"
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def run_all_tests():
|
|
51
|
+
ok = True
|
|
52
|
+
for t in (test_shade_converges, test_shade_four_modes, test_shade_respects_bounds, test_lshade_runs):
|
|
53
|
+
try:
|
|
54
|
+
t()
|
|
55
|
+
print(" PASS")
|
|
56
|
+
except Exception:
|
|
57
|
+
ok = False
|
|
58
|
+
import traceback
|
|
59
|
+
traceback.print_exc()
|
|
60
|
+
return ok
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
if __name__ == "__main__":
|
|
64
|
+
sys.exit(0 if run_all_tests() else 1)
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "evograd-diff"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "PyTorch-based framework for differentiable evolutionary computation and swarm intelligence"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{evograd_diff-0.2.0 → evograd_diff-0.2.1}/evograd/benchmarks/functions/cec2017/composition.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|