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,175 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Differentiable relaxations for discrete operations.
|
|
3
|
+
|
|
4
|
+
This module provides continuous relaxations of discrete operations
|
|
5
|
+
used throughout EvoGrad operators, enabling gradient flow through
|
|
6
|
+
categorical and Bernoulli sampling.
|
|
7
|
+
|
|
8
|
+
Functions:
|
|
9
|
+
- gumbel_softmax: Categorical sampling relaxation
|
|
10
|
+
- binary_concrete: Bernoulli sampling relaxation
|
|
11
|
+
- expand_param: Parameter expansion utility
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import torch
|
|
15
|
+
from torch import Tensor
|
|
16
|
+
from typing import Union
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"gumbel_softmax",
|
|
20
|
+
"binary_concrete",
|
|
21
|
+
"expand_param",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def gumbel_softmax(
|
|
26
|
+
logits: Tensor,
|
|
27
|
+
temperature: Union[float, Tensor] = 1.0,
|
|
28
|
+
dim: int = -1,
|
|
29
|
+
eps: float = 1e-10,
|
|
30
|
+
) -> Tensor:
|
|
31
|
+
"""
|
|
32
|
+
Gumbel-Softmax with straight-through estimator.
|
|
33
|
+
|
|
34
|
+
Provides a differentiable approximation to categorical sampling.
|
|
35
|
+
Forward pass returns hard one-hot vectors, backward pass uses
|
|
36
|
+
soft gradients through the softmax.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
logits: Unnormalized log probabilities [..., n_categories].
|
|
40
|
+
temperature: Temperature for softmax (lower = harder).
|
|
41
|
+
dim: Dimension to apply softmax.
|
|
42
|
+
eps: Small constant for numerical stability.
|
|
43
|
+
|
|
44
|
+
Returns:
|
|
45
|
+
One-hot vectors with soft gradients [..., n_categories].
|
|
46
|
+
"""
|
|
47
|
+
device = logits.device
|
|
48
|
+
|
|
49
|
+
# Ensure temperature is on correct device
|
|
50
|
+
if isinstance(temperature, Tensor):
|
|
51
|
+
temperature = temperature.to(device)
|
|
52
|
+
|
|
53
|
+
# Sample Gumbel noise
|
|
54
|
+
u = torch.rand_like(logits).clamp(eps, 1.0 - eps)
|
|
55
|
+
gumbels = -torch.log(-torch.log(u))
|
|
56
|
+
|
|
57
|
+
# Softmax with temperature
|
|
58
|
+
y_soft = torch.softmax((logits + gumbels) / temperature, dim=dim)
|
|
59
|
+
|
|
60
|
+
# Straight-through: hard forward, soft backward
|
|
61
|
+
index = y_soft.argmax(dim=dim, keepdim=True)
|
|
62
|
+
y_hard = torch.zeros_like(y_soft).scatter_(dim, index, 1.0)
|
|
63
|
+
|
|
64
|
+
return (y_hard - y_soft).detach() + y_soft
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def binary_concrete(
|
|
68
|
+
logits: Tensor,
|
|
69
|
+
temperature: Union[float, Tensor] = 1.0,
|
|
70
|
+
eps: float = 1e-10,
|
|
71
|
+
) -> Tensor:
|
|
72
|
+
"""
|
|
73
|
+
Binary-Concrete (Gumbel-Sigmoid) with straight-through estimator.
|
|
74
|
+
|
|
75
|
+
Provides a differentiable approximation to Bernoulli sampling.
|
|
76
|
+
Forward pass returns hard 0/1 values, backward pass uses
|
|
77
|
+
soft gradients through the sigmoid.
|
|
78
|
+
|
|
79
|
+
Args:
|
|
80
|
+
logits: Unnormalized log-odds [...].
|
|
81
|
+
temperature: Temperature for sigmoid (lower = harder).
|
|
82
|
+
eps: Small constant for numerical stability.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
Binary mask with soft gradients [...].
|
|
86
|
+
"""
|
|
87
|
+
device = logits.device
|
|
88
|
+
|
|
89
|
+
# Ensure temperature is on correct device
|
|
90
|
+
if isinstance(temperature, Tensor):
|
|
91
|
+
temperature = temperature.to(device)
|
|
92
|
+
|
|
93
|
+
# Sample logistic noise (difference of Gumbels)
|
|
94
|
+
u = torch.rand_like(logits).clamp(eps, 1.0 - eps)
|
|
95
|
+
noise = torch.log(u) - torch.log(1 - u)
|
|
96
|
+
|
|
97
|
+
# Sigmoid with temperature
|
|
98
|
+
y_soft = torch.sigmoid((logits + noise) / temperature)
|
|
99
|
+
|
|
100
|
+
# Straight-through: hard forward, soft backward
|
|
101
|
+
y_hard = (y_soft > 0.5).float()
|
|
102
|
+
|
|
103
|
+
return (y_hard - y_soft).detach() + y_soft
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def expand_param(
|
|
107
|
+
param: Union[Tensor, float, None],
|
|
108
|
+
default: Union[Tensor, float],
|
|
109
|
+
n_pop: int,
|
|
110
|
+
n_var: int,
|
|
111
|
+
device: torch.device,
|
|
112
|
+
dtype: torch.dtype,
|
|
113
|
+
) -> Tensor:
|
|
114
|
+
"""
|
|
115
|
+
Expand a parameter to shape [N, D] supporting four configurations.
|
|
116
|
+
|
|
117
|
+
Handles parameter expansion for per-individual and per-gene
|
|
118
|
+
parameter support in operators like SHADE.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
param: Optional override parameter. Can be:
|
|
122
|
+
- None: Use default
|
|
123
|
+
- scalar: Same value for all [N, D]
|
|
124
|
+
- [D]: Per-gene, broadcast to [N, D]
|
|
125
|
+
- [N]: Per-individual, broadcast to [N, D]
|
|
126
|
+
- [N, D]: Use as-is
|
|
127
|
+
default: Default value if param is None.
|
|
128
|
+
n_pop: Number of individuals (N).
|
|
129
|
+
n_var: Number of variables (D).
|
|
130
|
+
device: Target device.
|
|
131
|
+
dtype: Target dtype.
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Tensor of shape [N, D].
|
|
135
|
+
|
|
136
|
+
Example:
|
|
137
|
+
>>> # Per-individual CR for SHADE
|
|
138
|
+
>>> cr = torch.rand(100) # [N]
|
|
139
|
+
>>> cr_expanded = expand_param(cr, 0.9, 100, 30, device, dtype)
|
|
140
|
+
>>> # cr_expanded.shape == [100, 30]
|
|
141
|
+
"""
|
|
142
|
+
val = default if param is None else param
|
|
143
|
+
|
|
144
|
+
# Convert to tensor on correct device
|
|
145
|
+
if not isinstance(val, Tensor):
|
|
146
|
+
val = torch.tensor(val, device=device, dtype=dtype)
|
|
147
|
+
else:
|
|
148
|
+
val = val.to(device=device, dtype=dtype)
|
|
149
|
+
|
|
150
|
+
# Expand based on input shape
|
|
151
|
+
if val.dim() == 0:
|
|
152
|
+
# Scalar -> [N, D]
|
|
153
|
+
return val.expand(n_pop, n_var)
|
|
154
|
+
elif val.dim() == 1:
|
|
155
|
+
if val.shape[0] == n_var:
|
|
156
|
+
# [D] -> [N, D] (per-gene)
|
|
157
|
+
return val.unsqueeze(0).expand(n_pop, -1)
|
|
158
|
+
elif val.shape[0] == n_pop:
|
|
159
|
+
# [N] -> [N, D] (per-individual)
|
|
160
|
+
return val.unsqueeze(1).expand(-1, n_var)
|
|
161
|
+
else:
|
|
162
|
+
raise ValueError(
|
|
163
|
+
f"1D param must have size n_var={n_var} or n_pop={n_pop}, "
|
|
164
|
+
f"got {val.shape[0]}"
|
|
165
|
+
)
|
|
166
|
+
elif val.dim() == 2:
|
|
167
|
+
if val.shape == (n_pop, n_var):
|
|
168
|
+
return val
|
|
169
|
+
else:
|
|
170
|
+
raise ValueError(
|
|
171
|
+
f"2D param must have shape [{n_pop}, {n_var}], "
|
|
172
|
+
f"got {list(val.shape)}"
|
|
173
|
+
)
|
|
174
|
+
else:
|
|
175
|
+
raise ValueError(f"param must be 0D, 1D, or 2D, got {val.dim()}D")
|