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/algorithms/ga.py
ADDED
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Genetic Algorithm (GA) implementation for EvoGrad.
|
|
3
|
+
|
|
4
|
+
This module provides a fully differentiable Genetic Algorithm that
|
|
5
|
+
supports both classical and gradient-enabled optimisation modes.
|
|
6
|
+
|
|
7
|
+
The GA follows the standard evolutionary cycle:
|
|
8
|
+
1. Selection: Choose parents based on fitness
|
|
9
|
+
2. Crossover: Recombine parents to create offspring
|
|
10
|
+
3. Mutation: Introduce random variation
|
|
11
|
+
4. Survival: Select individuals for next generation
|
|
12
|
+
|
|
13
|
+
All operators are pluggable via dependency injection (pymoo-style),
|
|
14
|
+
allowing flexible customisation of the algorithm behavior.
|
|
15
|
+
|
|
16
|
+
Differentiable Mode:
|
|
17
|
+
When `differentiable=True`, the population is stored as nn.Parameter,
|
|
18
|
+
enabling gradient flow through the entire evolutionary cycle.
|
|
19
|
+
|
|
20
|
+
When operators have `adaptive=True`, their internal parameters
|
|
21
|
+
(temperature, eta, prob, etc.) are also learnable nn.Parameters.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> from evograd.algorithms import GA
|
|
25
|
+
>>> from evograd.core import Problem, minimize
|
|
26
|
+
>>> from evograd.operators import (
|
|
27
|
+
... FloatRandomSampling,
|
|
28
|
+
... TournamentSelection,
|
|
29
|
+
... SBXCrossover,
|
|
30
|
+
... PolynomialMutation,
|
|
31
|
+
... MergeSurvival,
|
|
32
|
+
... )
|
|
33
|
+
>>>
|
|
34
|
+
>>> # Define problem
|
|
35
|
+
>>> problem = Problem(
|
|
36
|
+
... objective=lambda x: (x**2).sum(dim=-1),
|
|
37
|
+
... n_var=30,
|
|
38
|
+
... xl=-100.0,
|
|
39
|
+
... xu=100.0,
|
|
40
|
+
... )
|
|
41
|
+
>>>
|
|
42
|
+
>>> # Create GA with operators
|
|
43
|
+
>>> ga = GA(
|
|
44
|
+
... pop_size=100,
|
|
45
|
+
... sampling=FloatRandomSampling(),
|
|
46
|
+
... selection=TournamentSelection(tournament_size=3),
|
|
47
|
+
... crossover=SBXCrossover(eta=15, prob=0.9),
|
|
48
|
+
... mutation=PolynomialMutation(eta=20),
|
|
49
|
+
... survival=MergeSurvival(elitism=True, n_elite=1),
|
|
50
|
+
... differentiable=True,
|
|
51
|
+
... )
|
|
52
|
+
>>>
|
|
53
|
+
>>> # Run optimization (pymoo-style)
|
|
54
|
+
>>> result = minimize(problem, ga, max_evals=10000, seed=42)
|
|
55
|
+
>>> print(f"Best fitness: {result.best_fitness}")
|
|
56
|
+
|
|
57
|
+
Reference:
|
|
58
|
+
Holland, J. H. (1992). Genetic Algorithms. Scientific American.
|
|
59
|
+
Goldberg, D. E. (1989). Genetic Algorithms in Search, Optimization,
|
|
60
|
+
and Machine Learning.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
from __future__ import annotations
|
|
64
|
+
|
|
65
|
+
from typing import TYPE_CHECKING, Any, Dict, Optional, Union
|
|
66
|
+
|
|
67
|
+
import torch
|
|
68
|
+
import torch.nn as nn
|
|
69
|
+
from torch import Tensor
|
|
70
|
+
|
|
71
|
+
from evograd.core.algorithm import Algorithm
|
|
72
|
+
|
|
73
|
+
if TYPE_CHECKING:
|
|
74
|
+
from evograd.core.problem import Problem
|
|
75
|
+
|
|
76
|
+
__all__ = ["GA", "ga_default", "ga_steady_state", "ga_comma"]
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class GA(Algorithm):
|
|
80
|
+
"""
|
|
81
|
+
Genetic Algorithm (GA) for continuous optimisation.
|
|
82
|
+
|
|
83
|
+
A generational evolutionary algorithm that evolves a population
|
|
84
|
+
of candidate solutions through selection, crossover, mutation,
|
|
85
|
+
and survival selection. Supports both classical and differentiable
|
|
86
|
+
operation modes.
|
|
87
|
+
|
|
88
|
+
All operators are injected via constructor (pymoo-style), enabling
|
|
89
|
+
flexible algorithm configuration.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
pop_size: Population size.
|
|
93
|
+
sampling: Operator for initial population generation.
|
|
94
|
+
Default: FloatRandomSampling()
|
|
95
|
+
selection: Parent selection operator.
|
|
96
|
+
Default: TournamentSelection(tournament_size=3)
|
|
97
|
+
crossover: Crossover/recombination operator.
|
|
98
|
+
Default: SBXCrossover(eta=15, prob=0.9)
|
|
99
|
+
mutation: Mutation operator.
|
|
100
|
+
Default: PolynomialMutation(eta=20)
|
|
101
|
+
survival: Survival selection operator.
|
|
102
|
+
Default: MergeSurvival(elitism=True, n_elite=1)
|
|
103
|
+
repair: Repair operator for constraint handling.
|
|
104
|
+
n_offsprings: Number of offspring per generation.
|
|
105
|
+
Default: pop_size (generational GA).
|
|
106
|
+
eliminate_duplicates: Duplicate handling strategy.
|
|
107
|
+
differentiable: Enable gradient flow through population.
|
|
108
|
+
adaptive: Enable learnable operator parameters.
|
|
109
|
+
dtype: Tensor dtype (default: torch.float32).
|
|
110
|
+
|
|
111
|
+
Attributes:
|
|
112
|
+
pop_size: Population size.
|
|
113
|
+
n_offsprings: Number of offspring per generation.
|
|
114
|
+
pop: Current population (property).
|
|
115
|
+
fitness: Current fitness values (property).
|
|
116
|
+
best_fitness: Best fitness found so far.
|
|
117
|
+
best_solution: Best solution found so far.
|
|
118
|
+
generation: Current generation number.
|
|
119
|
+
n_evals: Total fitness evaluations.
|
|
120
|
+
|
|
121
|
+
Example:
|
|
122
|
+
>>> # Minimal setup with defaults
|
|
123
|
+
>>> ga = GA(pop_size=50)
|
|
124
|
+
>>> ga.initialize(problem)
|
|
125
|
+
>>>
|
|
126
|
+
>>> # With custom operators
|
|
127
|
+
>>> from evograd.operators import (
|
|
128
|
+
... RouletteSelection, BlendCrossover,
|
|
129
|
+
... GaussianMutation, CommaSurvival,
|
|
130
|
+
... )
|
|
131
|
+
>>> ga = GA(
|
|
132
|
+
... pop_size=100,
|
|
133
|
+
... selection=RouletteSelection(),
|
|
134
|
+
... crossover=BlendCrossover(alpha=0.5),
|
|
135
|
+
... mutation=GaussianMutation(sigma=0.1),
|
|
136
|
+
... survival=CommaSurvival(elitism=True, n_elite=2),
|
|
137
|
+
... n_offsprings=200, # Required: >= pop_size for comma
|
|
138
|
+
... )
|
|
139
|
+
"""
|
|
140
|
+
|
|
141
|
+
def __init__(
|
|
142
|
+
self,
|
|
143
|
+
pop_size: int = 100,
|
|
144
|
+
sampling: Optional[nn.Module] = None,
|
|
145
|
+
selection: Optional[nn.Module] = None,
|
|
146
|
+
crossover: Optional[nn.Module] = None,
|
|
147
|
+
mutation: Optional[nn.Module] = None,
|
|
148
|
+
survival: Optional[nn.Module] = None,
|
|
149
|
+
repair: Optional[nn.Module] = None,
|
|
150
|
+
n_offsprings: Optional[int] = None,
|
|
151
|
+
eliminate_duplicates: bool = True,
|
|
152
|
+
differentiable: bool = True,
|
|
153
|
+
adaptive: bool = True,
|
|
154
|
+
dtype: torch.dtype = torch.float32,
|
|
155
|
+
) -> None:
|
|
156
|
+
# Create default operators if not provided
|
|
157
|
+
if selection is None:
|
|
158
|
+
from evograd.operators.selection import TournamentSelection
|
|
159
|
+
selection = TournamentSelection(
|
|
160
|
+
tournament_size=3,
|
|
161
|
+
adaptive=adaptive,
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
if crossover is None:
|
|
165
|
+
from evograd.operators.crossover import SBXCrossover
|
|
166
|
+
crossover = SBXCrossover(
|
|
167
|
+
eta=15,
|
|
168
|
+
prob=0.9,
|
|
169
|
+
adaptive=adaptive,
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
if mutation is None:
|
|
173
|
+
from evograd.operators.mutation import PolynomialMutation
|
|
174
|
+
mutation = PolynomialMutation(
|
|
175
|
+
eta=20,
|
|
176
|
+
prob=None, # Default: 1/n_var
|
|
177
|
+
adaptive=adaptive,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
if survival is None:
|
|
181
|
+
from evograd.operators.survival import MergeSurvival
|
|
182
|
+
survival = MergeSurvival(
|
|
183
|
+
n_survive=pop_size,
|
|
184
|
+
elitism=True,
|
|
185
|
+
n_elite=1,
|
|
186
|
+
adaptive=adaptive,
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
is_adaptive = selection.adaptive or crossover.adaptive or mutation.adaptive
|
|
190
|
+
|
|
191
|
+
if not is_adaptive and survival.adaptive:
|
|
192
|
+
adaptive = False
|
|
193
|
+
survival.adaptive = False
|
|
194
|
+
for p in survival.parameters():
|
|
195
|
+
p.requires_grad = False
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
# Call super().__init__() first before any attribute assignments
|
|
199
|
+
super().__init__(
|
|
200
|
+
pop_size=pop_size,
|
|
201
|
+
sampling=sampling,
|
|
202
|
+
selection=selection,
|
|
203
|
+
crossover=crossover,
|
|
204
|
+
mutation=mutation,
|
|
205
|
+
survival=survival,
|
|
206
|
+
repair=repair,
|
|
207
|
+
eliminate_duplicates=eliminate_duplicates,
|
|
208
|
+
n_offsprings=n_offsprings,
|
|
209
|
+
differentiable=differentiable,
|
|
210
|
+
adaptive=adaptive,
|
|
211
|
+
dtype=dtype,
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# =========================================================================
|
|
215
|
+
# Properties
|
|
216
|
+
# =========================================================================
|
|
217
|
+
|
|
218
|
+
@property
|
|
219
|
+
def population(self) -> Tensor:
|
|
220
|
+
"""Current population."""
|
|
221
|
+
return self._population
|
|
222
|
+
|
|
223
|
+
@property
|
|
224
|
+
def fitness(self) -> Tensor:
|
|
225
|
+
"""Current fitness values."""
|
|
226
|
+
return self.state.fitness
|
|
227
|
+
|
|
228
|
+
# =========================================================================
|
|
229
|
+
# Core GA Methods
|
|
230
|
+
# =========================================================================
|
|
231
|
+
|
|
232
|
+
def _setup(self) -> None:
|
|
233
|
+
"""
|
|
234
|
+
GA-specific setup after initialization.
|
|
235
|
+
|
|
236
|
+
Called by initialize() after population is created.
|
|
237
|
+
"""
|
|
238
|
+
# Update survival operator's n_survive if not set
|
|
239
|
+
if hasattr(self.survival, 'n_survive'):
|
|
240
|
+
if self.survival.n_survive is None:
|
|
241
|
+
self.survival.n_survive = self.pop_size
|
|
242
|
+
|
|
243
|
+
def _infill(self) -> Tensor:
|
|
244
|
+
"""
|
|
245
|
+
Generate offspring through selection, crossover, and mutation.
|
|
246
|
+
|
|
247
|
+
This implements the main GA variation operators:
|
|
248
|
+
1. Select parents based on fitness
|
|
249
|
+
2. Apply crossover to create offspring
|
|
250
|
+
3. Apply mutation to introduce variation
|
|
251
|
+
|
|
252
|
+
Returns:
|
|
253
|
+
Offspring population tensor [n_offsprings, n_var].
|
|
254
|
+
"""
|
|
255
|
+
n_offspring = self.n_offsprings
|
|
256
|
+
|
|
257
|
+
# Number of parent pairs needed
|
|
258
|
+
n_pairs = n_offspring
|
|
259
|
+
|
|
260
|
+
# 1. SELECTION: Choose parents
|
|
261
|
+
# Select 2 * n_pairs parents (to form pairs)
|
|
262
|
+
parents = self.selection(
|
|
263
|
+
self.population,
|
|
264
|
+
self.fitness,
|
|
265
|
+
n_select=2 * n_pairs,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Split into parent pairs
|
|
269
|
+
parent1 = parents[:n_pairs]
|
|
270
|
+
parent2 = parents[n_pairs:2*n_pairs]
|
|
271
|
+
|
|
272
|
+
# 2. CROSSOVER: Recombine parents
|
|
273
|
+
offspring = self.crossover(parent1, parent2)
|
|
274
|
+
|
|
275
|
+
# 3. MUTATION: Introduce variation
|
|
276
|
+
offspring = self.mutation(offspring, self.xl, self.xu)
|
|
277
|
+
|
|
278
|
+
return offspring
|
|
279
|
+
|
|
280
|
+
def _advance(self, offspring: Tensor, offspring_fitness: Tensor) -> None:
|
|
281
|
+
"""
|
|
282
|
+
Update population using survival selection.
|
|
283
|
+
|
|
284
|
+
Delegates to the survival operator to select which individuals
|
|
285
|
+
survive to the next generation.
|
|
286
|
+
|
|
287
|
+
Args:
|
|
288
|
+
offspring: Offspring population tensor.
|
|
289
|
+
offspring_fitness: Fitness values of offspring.
|
|
290
|
+
"""
|
|
291
|
+
# Use survival operator
|
|
292
|
+
survivors, survivor_fitness = self.survival(
|
|
293
|
+
self.population,
|
|
294
|
+
self.fitness,
|
|
295
|
+
offspring,
|
|
296
|
+
offspring_fitness,
|
|
297
|
+
n_survive=self.pop_size,
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# Update population
|
|
301
|
+
self._update_population(survivors, survivor_fitness)
|
|
302
|
+
|
|
303
|
+
# Update best solution tracking
|
|
304
|
+
self.state.update_best(self.population, self.state.fitness)
|
|
305
|
+
|
|
306
|
+
def _update_population(self, new_population: Tensor, new_fit: Tensor) -> None:
|
|
307
|
+
"""
|
|
308
|
+
Update population and fitness tensors.
|
|
309
|
+
|
|
310
|
+
Args:
|
|
311
|
+
new_pop: New population tensor.
|
|
312
|
+
new_fit: New fitness tensor.
|
|
313
|
+
"""
|
|
314
|
+
with torch.no_grad():
|
|
315
|
+
self._population.copy_(new_population)
|
|
316
|
+
self.state.fitness = new_fit
|
|
317
|
+
self.state.population = self._population
|
|
318
|
+
|
|
319
|
+
# =========================================================================
|
|
320
|
+
# Hyperparameter Access
|
|
321
|
+
# =========================================================================
|
|
322
|
+
|
|
323
|
+
def _get_hyperparams(self) -> Dict[str, Any]:
|
|
324
|
+
"""
|
|
325
|
+
Return current hyperparameter values.
|
|
326
|
+
|
|
327
|
+
Collects hyperparameters from all operators for logging.
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Dictionary of hyperparameter names to values.
|
|
331
|
+
"""
|
|
332
|
+
params = {
|
|
333
|
+
'pop_size': self.pop_size,
|
|
334
|
+
'n_offsprings': self.n_offsprings,
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
# Add selection parameters
|
|
338
|
+
if hasattr(self.selection, 'temperature'):
|
|
339
|
+
params['selection_temperature'] = self.selection.temperature.item()
|
|
340
|
+
if hasattr(self.selection, 'tournament_size'):
|
|
341
|
+
params['tournament_size'] = self.selection.tournament_size
|
|
342
|
+
|
|
343
|
+
# Add crossover parameters
|
|
344
|
+
if hasattr(self.crossover, 'eta'):
|
|
345
|
+
params['crossover_eta'] = self.crossover.eta.item()
|
|
346
|
+
if hasattr(self.crossover, 'prob'):
|
|
347
|
+
prob = self.crossover.prob
|
|
348
|
+
if isinstance(prob, Tensor):
|
|
349
|
+
params['crossover_prob'] = prob.mean().item()
|
|
350
|
+
else:
|
|
351
|
+
params['crossover_prob'] = prob
|
|
352
|
+
if hasattr(self.crossover, 'temperature'):
|
|
353
|
+
params['crossover_temperature'] = self.crossover.temperature.item()
|
|
354
|
+
|
|
355
|
+
# Add mutation parameters
|
|
356
|
+
if hasattr(self.mutation, 'eta'):
|
|
357
|
+
params['mutation_eta'] = self.mutation.eta.item()
|
|
358
|
+
if hasattr(self.mutation, 'prob'):
|
|
359
|
+
prob = self.mutation.prob
|
|
360
|
+
if prob is not None:
|
|
361
|
+
if isinstance(prob, Tensor):
|
|
362
|
+
params['mutation_prob'] = prob.mean().item()
|
|
363
|
+
else:
|
|
364
|
+
params['mutation_prob'] = prob
|
|
365
|
+
if hasattr(self.mutation, 'temperature'):
|
|
366
|
+
params['mutation_temperature'] = self.mutation.temperature.item()
|
|
367
|
+
|
|
368
|
+
# Add survival parameters
|
|
369
|
+
if hasattr(self.survival, 'elitism'):
|
|
370
|
+
params['elitism'] = self.survival.elitism
|
|
371
|
+
if hasattr(self.survival, 'n_elite'):
|
|
372
|
+
params['n_elite'] = self.survival.n_elite
|
|
373
|
+
if hasattr(self.survival, 'temperature'):
|
|
374
|
+
params['survival_temperature'] = self.survival.temperature.item()
|
|
375
|
+
|
|
376
|
+
return params
|
|
377
|
+
|
|
378
|
+
# =========================================================================
|
|
379
|
+
# String Representation
|
|
380
|
+
# =========================================================================
|
|
381
|
+
|
|
382
|
+
def __repr__(self) -> str:
|
|
383
|
+
survival_name = type(self.survival).__name__
|
|
384
|
+
return (
|
|
385
|
+
f"GA(pop_size={self.pop_size}, "
|
|
386
|
+
f"n_offsprings={self.n_offsprings}, "
|
|
387
|
+
f"survival={survival_name}, "
|
|
388
|
+
f"differentiable={self.differentiable})"
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# =============================================================================
|
|
393
|
+
# Convenience Factory Functions
|
|
394
|
+
# =============================================================================
|
|
395
|
+
|
|
396
|
+
def ga_default(
|
|
397
|
+
pop_size: int = 100,
|
|
398
|
+
differentiable: bool = True,
|
|
399
|
+
adaptive: bool = True,
|
|
400
|
+
**kwargs,
|
|
401
|
+
) -> GA:
|
|
402
|
+
"""
|
|
403
|
+
Create a GA with sensible default operators.
|
|
404
|
+
|
|
405
|
+
Uses:
|
|
406
|
+
- TournamentSelection (k=3)
|
|
407
|
+
- SBXCrossover (eta=15, prob=0.9)
|
|
408
|
+
- PolynomialMutation (eta=20)
|
|
409
|
+
- MergeSurvival with elitism (n_elite=1)
|
|
410
|
+
|
|
411
|
+
Args:
|
|
412
|
+
pop_size: Population size.
|
|
413
|
+
differentiable: Enable gradient flow.
|
|
414
|
+
adaptive: Enable learnable operator parameters.
|
|
415
|
+
**kwargs: Additional arguments passed to GA.
|
|
416
|
+
|
|
417
|
+
Returns:
|
|
418
|
+
Configured GA instance.
|
|
419
|
+
|
|
420
|
+
Example:
|
|
421
|
+
>>> ga = ga_default(pop_size=50)
|
|
422
|
+
>>> result = minimize(problem, ga)
|
|
423
|
+
"""
|
|
424
|
+
from evograd.operators.survival import MergeSurvival
|
|
425
|
+
|
|
426
|
+
return GA(
|
|
427
|
+
pop_size=pop_size,
|
|
428
|
+
survival=MergeSurvival(
|
|
429
|
+
n_survive=pop_size,
|
|
430
|
+
elitism=True,
|
|
431
|
+
n_elite=1,
|
|
432
|
+
adaptive=adaptive,
|
|
433
|
+
),
|
|
434
|
+
differentiable=differentiable,
|
|
435
|
+
adaptive=adaptive,
|
|
436
|
+
**kwargs,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def ga_steady_state(
|
|
441
|
+
pop_size: int = 100,
|
|
442
|
+
n_offsprings: int = 2,
|
|
443
|
+
differentiable: bool = True,
|
|
444
|
+
adaptive: bool = True,
|
|
445
|
+
**kwargs,
|
|
446
|
+
) -> GA:
|
|
447
|
+
"""
|
|
448
|
+
Create a steady-state GA.
|
|
449
|
+
|
|
450
|
+
In steady-state GA, only a few offspring are created per
|
|
451
|
+
generation and they replace the worst individuals.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
pop_size: Population size.
|
|
455
|
+
n_offsprings: Number of offspring per generation.
|
|
456
|
+
differentiable: Enable gradient flow.
|
|
457
|
+
adaptive: Enable learnable operator parameters.
|
|
458
|
+
**kwargs: Additional arguments passed to GA.
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
Configured GA instance.
|
|
462
|
+
|
|
463
|
+
Example:
|
|
464
|
+
>>> ga = ga_steady_state(pop_size=50, n_offsprings=2)
|
|
465
|
+
>>> result = minimize(problem, ga)
|
|
466
|
+
"""
|
|
467
|
+
from evograd.operators.survival import ReplaceWorstSurvival
|
|
468
|
+
|
|
469
|
+
return GA(
|
|
470
|
+
pop_size=pop_size,
|
|
471
|
+
n_offsprings=n_offsprings,
|
|
472
|
+
survival=ReplaceWorstSurvival(
|
|
473
|
+
n_survive=pop_size,
|
|
474
|
+
elitism=True,
|
|
475
|
+
n_elite=1,
|
|
476
|
+
adaptive=adaptive,
|
|
477
|
+
),
|
|
478
|
+
differentiable=differentiable,
|
|
479
|
+
adaptive=adaptive,
|
|
480
|
+
**kwargs,
|
|
481
|
+
)
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def ga_comma(
|
|
485
|
+
pop_size: int = 50,
|
|
486
|
+
n_offsprings: int = 100,
|
|
487
|
+
differentiable: bool = True,
|
|
488
|
+
adaptive: bool = True,
|
|
489
|
+
**kwargs,
|
|
490
|
+
) -> GA:
|
|
491
|
+
"""
|
|
492
|
+
Create a (μ, λ) style GA.
|
|
493
|
+
|
|
494
|
+
In (μ, λ) selection, parents are discarded and the next
|
|
495
|
+
generation is selected only from offspring. This can help
|
|
496
|
+
escape local optima but requires n_offsprings >= pop_size.
|
|
497
|
+
|
|
498
|
+
Args:
|
|
499
|
+
pop_size: Population size (μ).
|
|
500
|
+
n_offsprings: Number of offspring (λ).
|
|
501
|
+
differentiable: Enable gradient flow.
|
|
502
|
+
adaptive: Enable learnable operator parameters.
|
|
503
|
+
**kwargs: Additional arguments passed to GA.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
Configured GA instance.
|
|
507
|
+
|
|
508
|
+
Example:
|
|
509
|
+
>>> ga = ga_comma(pop_size=50, n_offsprings=100)
|
|
510
|
+
>>> result = minimize(problem, ga)
|
|
511
|
+
"""
|
|
512
|
+
from evograd.operators.survival import CommaSurvival
|
|
513
|
+
|
|
514
|
+
if n_offsprings < pop_size:
|
|
515
|
+
raise ValueError(
|
|
516
|
+
f"For (μ,λ) GA, n_offsprings ({n_offsprings}) must be >= "
|
|
517
|
+
f"pop_size ({pop_size})"
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
return GA(
|
|
521
|
+
pop_size=pop_size,
|
|
522
|
+
n_offsprings=n_offsprings,
|
|
523
|
+
survival=CommaSurvival(
|
|
524
|
+
n_survive=pop_size,
|
|
525
|
+
elitism=True, # Still keep elitism to preserve best
|
|
526
|
+
n_elite=1,
|
|
527
|
+
adaptive=adaptive,
|
|
528
|
+
),
|
|
529
|
+
differentiable=differentiable,
|
|
530
|
+
adaptive=adaptive,
|
|
531
|
+
**kwargs,
|
|
532
|
+
)
|