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
evograd/core/maximize.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Maximisation function for EvoGrad optimisation.
|
|
3
|
+
|
|
4
|
+
This module provides a maximisation interface that internally
|
|
5
|
+
converts to minimisation by negating the objective function.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from evograd.core.problem import Problem
|
|
9
|
+
>>> from evograd.core.maximize import maximize
|
|
10
|
+
>>> from evograd.core.termination import MaxEvaluations
|
|
11
|
+
>>> from evograd.algorithms import GA
|
|
12
|
+
>>>
|
|
13
|
+
>>> # Define problem (we want to MAXIMIZE this)
|
|
14
|
+
>>> problem = Problem(
|
|
15
|
+
... objective=lambda x: torch.sin(x).sum(dim=-1), # Want to maximize
|
|
16
|
+
... n_var=30,
|
|
17
|
+
... xl=-3.14,
|
|
18
|
+
... xu=3.14,
|
|
19
|
+
... )
|
|
20
|
+
>>>
|
|
21
|
+
>>> # Create algorithm
|
|
22
|
+
>>> algorithm = GA(pop_size=100)
|
|
23
|
+
>>>
|
|
24
|
+
>>> # Run maximisation
|
|
25
|
+
>>> result = maximize(
|
|
26
|
+
... problem,
|
|
27
|
+
... algorithm,
|
|
28
|
+
... termination=MaxEvaluations(10000),
|
|
29
|
+
... seed=42,
|
|
30
|
+
... )
|
|
31
|
+
>>>
|
|
32
|
+
>>> # Note: result.best_fitness is the ACTUAL fitness (not negated)
|
|
33
|
+
>>> print(f"Best (max) fitness: {result.best_fitness}")
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
from typing import TYPE_CHECKING, List, Optional, Union
|
|
39
|
+
|
|
40
|
+
import torch
|
|
41
|
+
from torch import Tensor
|
|
42
|
+
|
|
43
|
+
from evograd.utils.callbacks import Callback
|
|
44
|
+
from evograd.core.minimize import minimize
|
|
45
|
+
from evograd.core.problem import Problem
|
|
46
|
+
from evograd.core.result import Result
|
|
47
|
+
from evograd.core.termination import Termination
|
|
48
|
+
|
|
49
|
+
if TYPE_CHECKING:
|
|
50
|
+
from evograd.core.algorithm import Algorithm
|
|
51
|
+
|
|
52
|
+
__all__ = [
|
|
53
|
+
"maximize",
|
|
54
|
+
]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class _NegatedProblem(Problem):
|
|
58
|
+
"""
|
|
59
|
+
Wrapper that negates the objective function for maximisation.
|
|
60
|
+
|
|
61
|
+
This is an internal class used by maximize() to convert
|
|
62
|
+
maximisation to minimisation.
|
|
63
|
+
|
|
64
|
+
Instead of calling super().__init__() with all parameters,
|
|
65
|
+
we directly copy the relevant attributes from the original
|
|
66
|
+
problem to avoid parameter validation issues.
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
def __init__(self, problem: Problem) -> None:
|
|
70
|
+
# Initialize nn.Module directly (skip Problem.__init__)
|
|
71
|
+
torch.nn.Module.__init__(self)
|
|
72
|
+
|
|
73
|
+
# Copy all relevant attributes from original problem
|
|
74
|
+
self.n_var = problem.n_var
|
|
75
|
+
self.n_obj = problem.n_obj
|
|
76
|
+
self._objective = None # We override _evaluate
|
|
77
|
+
self.name = f"Negated({problem.name})" if problem.name else "Negated"
|
|
78
|
+
self.device = problem.device
|
|
79
|
+
self.dtype = problem.dtype
|
|
80
|
+
|
|
81
|
+
# Register bounds as buffers (copy from original)
|
|
82
|
+
self.register_buffer("xl", problem.xl.clone())
|
|
83
|
+
self.register_buffer("xu", problem.xu.clone())
|
|
84
|
+
|
|
85
|
+
# Copy constraint info
|
|
86
|
+
self._constraints = problem._constraints.copy() if hasattr(problem, '_constraints') else []
|
|
87
|
+
self.n_ieq_constr = problem.n_ieq_constr
|
|
88
|
+
self.n_eq_constr = problem.n_eq_constr
|
|
89
|
+
self.n_constr = problem.n_constr
|
|
90
|
+
|
|
91
|
+
# Store reference to original problem
|
|
92
|
+
self._original_problem = problem
|
|
93
|
+
|
|
94
|
+
def _evaluate(self, x: Tensor) -> Tensor:
|
|
95
|
+
"""Negate the original objective."""
|
|
96
|
+
return -self._original_problem._evaluate(x)
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def original_problem(self) -> Problem:
|
|
100
|
+
"""Access the original (non-negated) problem."""
|
|
101
|
+
return self._original_problem
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class _NegatedResult(Result):
|
|
105
|
+
"""
|
|
106
|
+
Result wrapper that negates fitness values back.
|
|
107
|
+
|
|
108
|
+
Used to return the actual (non-negated) fitness values
|
|
109
|
+
to the user after maximisation.
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
@classmethod
|
|
113
|
+
def from_minimization_result(cls, result: Result) -> Result:
|
|
114
|
+
"""Create a maximisation result from a minimisation result."""
|
|
115
|
+
# Negate fitness values
|
|
116
|
+
best_fitness = -result.best_fitness
|
|
117
|
+
|
|
118
|
+
fitness = result.fitness
|
|
119
|
+
if fitness is not None:
|
|
120
|
+
fitness = -fitness
|
|
121
|
+
|
|
122
|
+
# Negate history values
|
|
123
|
+
history = result.history.copy()
|
|
124
|
+
if "best_fitness" in history:
|
|
125
|
+
history["best_fitness"] = [-f for f in history["best_fitness"]]
|
|
126
|
+
if "mean_fitness" in history:
|
|
127
|
+
history["mean_fitness"] = [-f for f in history["mean_fitness"]]
|
|
128
|
+
|
|
129
|
+
return Result(
|
|
130
|
+
best_solution=result.best_solution,
|
|
131
|
+
best_fitness=best_fitness,
|
|
132
|
+
population=result.population,
|
|
133
|
+
fitness=fitness,
|
|
134
|
+
n_evals=result.n_evals,
|
|
135
|
+
n_gen=result.n_gen,
|
|
136
|
+
success=result.success,
|
|
137
|
+
termination_reason=result.termination_reason,
|
|
138
|
+
history=history,
|
|
139
|
+
hyperparams=result.hyperparams,
|
|
140
|
+
algorithm_state=result.algorithm_state,
|
|
141
|
+
problem_name=result.problem_name,
|
|
142
|
+
algorithm_name=result.algorithm_name,
|
|
143
|
+
start_time=result.start_time,
|
|
144
|
+
end_time=result.end_time,
|
|
145
|
+
elapsed_time=result.elapsed_time,
|
|
146
|
+
device=result.device,
|
|
147
|
+
extra=result.extra,
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def maximize(
|
|
152
|
+
problem: Problem,
|
|
153
|
+
algorithm: Algorithm,
|
|
154
|
+
termination: Optional[Termination] = None,
|
|
155
|
+
seed: Optional[int] = None,
|
|
156
|
+
verbose: bool = True,
|
|
157
|
+
callback: Optional[Union[Callback, List[Callback]]] = None,
|
|
158
|
+
copy_algorithm: bool = False,
|
|
159
|
+
save_history: bool = True,
|
|
160
|
+
initialize: bool = True,
|
|
161
|
+
# Differentiable mode options
|
|
162
|
+
optimizer: Optional[torch.optim.Optimizer] = None,
|
|
163
|
+
lr_pop: Optional[float] = None,
|
|
164
|
+
lr_hyper: Optional[float] = None,
|
|
165
|
+
grad_clip_pop: Optional[float] = None,
|
|
166
|
+
grad_clip_hyper: Optional[float] = None,
|
|
167
|
+
scheduler: Optional[str] = None,
|
|
168
|
+
scheduler_patience: int = 50,
|
|
169
|
+
scheduler_factor: float = 0.5,
|
|
170
|
+
min_lr: float = 1e-6,
|
|
171
|
+
) -> Result:
|
|
172
|
+
"""
|
|
173
|
+
Maximise an objective function using a population-based algorithm.
|
|
174
|
+
|
|
175
|
+
This function converts maximisation to minimisation by negating
|
|
176
|
+
the objective, runs the optimisation, and then negates the results
|
|
177
|
+
back to return actual fitness values.
|
|
178
|
+
|
|
179
|
+
Args:
|
|
180
|
+
problem: Problem instance defining the objective function,
|
|
181
|
+
bounds, and constraints. The objective will be MAXIMISED.
|
|
182
|
+
algorithm: Algorithm instance (e.g., GA, DE, PSO, CMAES).
|
|
183
|
+
Will be initialized inside this function.
|
|
184
|
+
termination: When to stop optimisation. Must be a Termination
|
|
185
|
+
instance (e.g., MaxEvaluations(10000)). If None, uses
|
|
186
|
+
default (10000 evaluations).
|
|
187
|
+
seed: Random seed for reproducibility.
|
|
188
|
+
verbose: If True, print progress during optimisation.
|
|
189
|
+
callback: Single Callback or list of Callbacks for monitoring.
|
|
190
|
+
copy_algorithm: If True, create a copy of the algorithm.
|
|
191
|
+
save_history: If True (default), save convergence history.
|
|
192
|
+
initialize: If True (default), initialize the algorithm with the
|
|
193
|
+
problem. Set to False to continue optimization with an already
|
|
194
|
+
initialized algorithm (e.g., when switching problems at runtime
|
|
195
|
+
while preserving population state and hyperparameters).
|
|
196
|
+
The algorithm must have been previously initialized. When False,
|
|
197
|
+
the termination budget is additive (e.g., MaxEvaluations(500)
|
|
198
|
+
will run 500 more evaluations from the current state).
|
|
199
|
+
|
|
200
|
+
# Differentiable mode options:
|
|
201
|
+
optimizer: PyTorch optimizer for gradient-based updates.
|
|
202
|
+
lr_pop: Learning rate for population updates.
|
|
203
|
+
lr_hyper: Learning rate for hyperparameter updates.
|
|
204
|
+
grad_clip_pop: Maximum gradient norm for population clipping.
|
|
205
|
+
grad_clip_hyper: Maximum gradient norm for hyperparameter clipping.
|
|
206
|
+
scheduler: Learning rate scheduler type.
|
|
207
|
+
scheduler_patience: Generations before reducing LR.
|
|
208
|
+
scheduler_factor: Factor to multiply LR when reducing.
|
|
209
|
+
min_lr: Minimum learning rate.
|
|
210
|
+
|
|
211
|
+
Returns:
|
|
212
|
+
Result object with:
|
|
213
|
+
- best_solution: Best solution found
|
|
214
|
+
- best_fitness: Best (maximum) fitness value
|
|
215
|
+
- population: Final population
|
|
216
|
+
- fitness: Final fitness values (actual, not negated)
|
|
217
|
+
- history: Convergence history (with actual fitness values)
|
|
218
|
+
|
|
219
|
+
Example:
|
|
220
|
+
>>> from evograd.core.problem import Problem
|
|
221
|
+
>>> from evograd.core.maximize import maximize
|
|
222
|
+
>>> from evograd.core.termination import MaxEvaluations
|
|
223
|
+
>>> from evograd.algorithms import GA
|
|
224
|
+
>>>
|
|
225
|
+
>>> # Maximize a function
|
|
226
|
+
>>> problem = Problem(
|
|
227
|
+
... objective=lambda x: torch.sin(x).sum(dim=-1),
|
|
228
|
+
... n_var=10,
|
|
229
|
+
... xl=-3.14,
|
|
230
|
+
... xu=3.14,
|
|
231
|
+
... )
|
|
232
|
+
>>>
|
|
233
|
+
>>> result = maximize(problem, GA(pop_size=100), termination=MaxEvaluations(10000), seed=42)
|
|
234
|
+
>>> print(f"Maximum value: {result.best_fitness}")
|
|
235
|
+
|
|
236
|
+
Note:
|
|
237
|
+
The returned fitness values are the ACTUAL values (not negated).
|
|
238
|
+
If you're looking for minimum fitness, use minimize() instead.
|
|
239
|
+
"""
|
|
240
|
+
# Wrap problem to negate objective
|
|
241
|
+
negated_problem = _NegatedProblem(problem)
|
|
242
|
+
|
|
243
|
+
# Run minimisation on negated problem
|
|
244
|
+
result = minimize(
|
|
245
|
+
problem=negated_problem,
|
|
246
|
+
algorithm=algorithm,
|
|
247
|
+
termination=termination,
|
|
248
|
+
seed=seed,
|
|
249
|
+
verbose=verbose,
|
|
250
|
+
callback=callback,
|
|
251
|
+
copy_algorithm=copy_algorithm,
|
|
252
|
+
save_history=save_history,
|
|
253
|
+
initialize=initialize,
|
|
254
|
+
optimizer=optimizer,
|
|
255
|
+
lr_pop=lr_pop,
|
|
256
|
+
lr_hyper=lr_hyper,
|
|
257
|
+
grad_clip_pop=grad_clip_pop,
|
|
258
|
+
grad_clip_hyper=grad_clip_hyper,
|
|
259
|
+
scheduler=scheduler,
|
|
260
|
+
scheduler_patience=scheduler_patience,
|
|
261
|
+
scheduler_factor=scheduler_factor,
|
|
262
|
+
min_lr=min_lr,
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
# Fix problem name in result
|
|
266
|
+
result.problem_name = problem.name
|
|
267
|
+
|
|
268
|
+
# Negate fitness values back to actual values
|
|
269
|
+
return _NegatedResult.from_minimization_result(result)
|