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