Skip to content

Commit 1051093

Browse files
committed
More operators & support of benchmarks
1 parent 75dd58d commit 1051093

22 files changed

Lines changed: 1890 additions & 10 deletions

‎docs/source/benchmarks.md‎

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Benchmark Problems
2+
3+
PyGAD ships a small collection of standard benchmark problems under `pygad.benchmarks`. Each problem is a class that can be called with the PyGAD fitness signature `(ga, solution, sol_idx)` and returns a fitness value in PyGAD's maximization format (the original minimization values are negated for you).
4+
5+
Each class also exposes the attributes you usually need to set up the GA:
6+
7+
- `num_genes`: number of decision variables.
8+
- `num_objectives`: number of objectives. `1` for single-objective problems.
9+
- `bounds`: `(low, high)` tuple of variable bounds.
10+
11+
For ZDT problems and ZDT4 / ZDT6, the class also has a `pareto_front(num_points)` method that returns reference points on the true Pareto front. Pass these to the IGD or GD indicators as the `reference_front` argument.
12+
13+
## Single-Objective Problems
14+
15+
Available in `pygad.benchmarks.classic`:
16+
17+
| Class | Global minimum | Bounds |
18+
|---|---|---|
19+
| `Sphere` | f(0, ..., 0) = 0 | `(-5.12, 5.12)` |
20+
| `Rastrigin` | f(0, ..., 0) = 0 | `(-5.12, 5.12)` |
21+
| `Rosenbrock` | f(1, ..., 1) = 0 | `(-5.0, 10.0)` |
22+
| `Griewank` | f(0, ..., 0) = 0 | `(-600.0, 600.0)` |
23+
| `Schwefel` | f(420.97, ..., 420.97) ≈ 0 | `(-500.0, 500.0)` |
24+
| `Ackley` | f(0, ..., 0) = 0 | `(-32.768, 32.768)` |
25+
| `Himmelblau` | four equal minima at f = 0 (2D only) | `(-5.0, 5.0)` |
26+
27+
## Multi-Objective Problems (ZDT family)
28+
29+
Available in `pygad.benchmarks.zdt`. All ZDT problems have two objectives and variables in `[0, 1]` (except ZDT4 which uses `[-5, 5]` for the rest of the variables).
30+
31+
| Class | Pareto front shape |
32+
|---|---|
33+
| `ZDT1` | convex |
34+
| `ZDT2` | non-convex |
35+
| `ZDT3` | disconnected (five pieces) |
36+
| `ZDT4` | convex, many local minima in the search space |
37+
| `ZDT6` | non-uniform |
38+
39+
## Many-Objective Problems (DTLZ family)
40+
41+
Available in `pygad.benchmarks.dtlz`. All DTLZ problems support an arbitrary number of objectives `M`. The number of decision variables is `M + k - 1` where `k` is a "distance" variable count.
42+
43+
| Class | Default M | Pareto front shape |
44+
|---|---|---|
45+
| `DTLZ1` | 3 | linear hyperplane (`sum(f_i) = 0.5`) |
46+
| `DTLZ2` | 3 | unit sphere first orthant |
47+
| `DTLZ3` | 3 | unit sphere with hard multimodal g-function |
48+
| `DTLZ4` | 3 | unit sphere with strong bias toward one corner |
49+
50+
## Combinatorial Problems
51+
52+
Available in `pygad.benchmarks.knapsack`. The 0/1 `Knapsack` class takes three arguments: a 1D array of item `weights`, a 1D array of item `values`, and a numeric `capacity`. A solution is a binary vector where a 1 means the item is picked. The fitness is the total value when the candidate is within the capacity, and a negative penalty scaled by how much the candidate is over the limit otherwise.
53+
54+
The class exposes `gene_space=[0, 1]` and `gene_type=int` so you can plug it directly into PyGAD:
55+
56+
```python
57+
import pygad
58+
from pygad.benchmarks.knapsack import Knapsack
59+
60+
problem = Knapsack(weights=[2, 3, 4, 5],
61+
values=[3, 4, 5, 6],
62+
capacity=5)
63+
64+
ga = pygad.GA(
65+
num_generations=50,
66+
num_parents_mating=10,
67+
fitness_func=problem,
68+
sol_per_pop=30,
69+
num_genes=problem.num_genes,
70+
gene_space=problem.gene_space,
71+
gene_type=problem.gene_type,
72+
)
73+
ga.run()
74+
```
75+
76+
## Example: SOO
77+
78+
```python
79+
import pygad
80+
from pygad.benchmarks.classic import Sphere
81+
82+
problem = Sphere(num_genes=10)
83+
84+
ga = pygad.GA(
85+
num_generations=100,
86+
num_parents_mating=10,
87+
fitness_func=problem,
88+
sol_per_pop=20,
89+
num_genes=problem.num_genes,
90+
init_range_low=problem.bounds[0],
91+
init_range_high=problem.bounds[1],
92+
crossover_type='sbx',
93+
sbx_crossover_eta=30,
94+
mutation_type='polynomial',
95+
polynomial_mutation_eta=20,
96+
)
97+
ga.run()
98+
```
99+
100+
## Example: MOO
101+
102+
```python
103+
import pygad
104+
from pygad.benchmarks.zdt import ZDT1
105+
from pygad.utils.indicators import inverted_generational_distance
106+
107+
problem = ZDT1(num_genes=10)
108+
109+
ga = pygad.GA(
110+
num_generations=200,
111+
num_parents_mating=20,
112+
fitness_func=problem,
113+
sol_per_pop=30,
114+
num_genes=problem.num_genes,
115+
init_range_low=problem.bounds[0],
116+
init_range_high=problem.bounds[1],
117+
parent_selection_type='nsga2',
118+
crossover_type='sbx',
119+
sbx_crossover_eta=30,
120+
mutation_type='polynomial',
121+
polynomial_mutation_eta=20,
122+
)
123+
ga.run()
124+
125+
# Measure how close the final population is to the true Pareto front
126+
true_front = problem.pareto_front(num_points=100)
127+
igd = inverted_generational_distance(ga.last_generation_fitness, true_front)
128+
print(f'IGD = {igd}')
129+
```

‎docs/source/pygad.md‎

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ Introduced in [PyGAD 2.0.0](https://pygad.readthedocs.io/en/latest/releases.html
5555

5656
One or more conditions that stop the evolution early. Each criterion is a string made of a stop word and a number, like `"reach_40"`.
5757

58-
Two stop words are supported:
58+
Four stop words are supported:
5959

6060
- `reach`: stop when the fitness is greater than or equal to a given value. Example: `"reach_40"` stops once the fitness is `>= 40`.
6161
- `saturate`: stop when the fitness does not change for a given number of generations. Example: `"saturate_7"` stops if the fitness stays the same for 7 generations in a row.
62+
- `time`: stop when the time spent inside `run()` is at least the given number of seconds. Example: `"time_30"` stops the run after 30 seconds.
63+
- `evaluations`: stop when the number of fitness function calls made inside `run()` reaches the given count. Example: `"evaluations_1000"` stops the run once 1000 calls have been made.
6264

63-
Added in [PyGAD 2.15.0](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-15-0).
65+
You can also pass a list of criteria; the run stops as soon as any one of them is met.
66+
67+
Added in [PyGAD 2.15.0](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-15-0). The `time` and `evaluations` keywords were added in PyGAD 3.6.0.
6468
:::
6569

6670
#### Fitness Function
@@ -251,12 +255,19 @@ The built-in types are:
251255
- `two_points`: two-point crossover.
252256
- `uniform`: uniform crossover.
253257
- `scattered`: scattered crossover (since [PyGAD 2.9.0](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-9-0)).
258+
- `sbx`: simulated binary crossover. The standard real-coded operator. Requires the `sbx_crossover_eta` parameter.
254259

255260
You can also pass your own crossover function (since [PyGAD 2.16.0](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-16-0)). See [User-Defined Crossover, Mutation, and Parent Selection Operators](https://pygad.readthedocs.io/en/latest/user_defined_operators.html#user-defined-crossover-mutation-and-parent-selection-operators).
256261

257262
If `crossover_type=None`, the crossover step is skipped and no offspring are created, so the next generation reuses the current population (since [PyGAD 2.2.2](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-2-2)).
258263
:::
259264

265+
:::{dropdown} `sbx_crossover_eta=30`: Distribution index for SBX crossover.
266+
:animate: fade-in-slide-down
267+
268+
Only used when `crossover_type` is `'sbx'`. Sets how close the children stay to the parents. A higher value means children stay closer. Must be a positive number. Defaults to `30`.
269+
:::
270+
260271
:::{dropdown} `crossover_probability=None`: Chance a parent is used for crossover.
261272
:animate: fade-in-slide-down
262273

@@ -281,12 +292,19 @@ The built-in types are:
281292
- `inversion`: inversion mutation.
282293
- `scramble`: scramble mutation.
283294
- `adaptive`: adaptive mutation (since [PyGAD 2.10.0](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-10-0)). See [Adaptive Mutation](https://pygad.readthedocs.io/en/latest/adaptive_mutation.html#adaptive-mutation) and [Use Adaptive Mutation in PyGAD](https://pygad.readthedocs.io/en/latest/adaptive_mutation.html#use-adaptive-mutation-in-pygad).
295+
- `polynomial`: polynomial mutation. The standard real-coded operator used together with SBX. Requires the `polynomial_mutation_eta` parameter.
284296

285297
You can also pass your own mutation function (since [PyGAD 2.16.0](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-16-0)). See [User-Defined Crossover, Mutation, and Parent Selection Operators](https://pygad.readthedocs.io/en/latest/user_defined_operators.html#user-defined-crossover-mutation-and-parent-selection-operators).
286298

287299
If `mutation_type=None`, the mutation step is skipped and the offspring are used unchanged (since [PyGAD 2.2.2](https://pygad.readthedocs.io/en/latest/releases.html#pygad-2-2-2)).
288300
:::
289301

302+
:::{dropdown} `polynomial_mutation_eta=20`: Distribution index for polynomial mutation.
303+
:animate: fade-in-slide-down
304+
305+
Only used when `mutation_type` is `'polynomial'`. Sets the size of the change. A higher value means a smaller change. Must be a positive number. Defaults to `20`.
306+
:::
307+
290308
:::{dropdown} `mutation_probability=None`: Per-gene chance of mutation.
291309
:animate: fade-in-slide-down
292310

‎docs/source/pygad_more.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ Print a Keras-like summary and log the outputs.
4747
Pass your own functions, methods, or classes for the fitness and callbacks.
4848
:::
4949

50+
:::{grid-item-card} Benchmark Problems
51+
:link: benchmarks
52+
:link-type: doc
53+
54+
Built-in single, multi, and many-objective benchmark problems to plug into the GA.
55+
:::
56+
5057
::::
5158

5259
:::{toctree}
@@ -58,4 +65,5 @@ generations
5865
fitness_calculation
5966
logging
6067
custom_functions
68+
benchmarks
6169
:::

‎docs/source/releases.md‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -657,7 +657,11 @@ Release Date April 8, 2026
657657
22. Two new parent selection methods are added to support NSGA-III: 1) `nsga3_selection()` for plain NSGA-III selection, and 2) `tournament_selection_nsga3()` for the tournament variant. Use them by setting `parent_selection_type` to `'nsga3'` or `'tournament_nsga3'`.
658658
23. A new parameter `nsga3_num_divisions` is added to the `pygad.GA` constructor. It is required when `parent_selection_type` is `'nsga3'` or `'tournament_nsga3'` and sets the number of divisions per objective axis used to build the structured reference points (the `p` parameter from Deb & Jain 2014). The total number of reference points is `C(M + p - 1, p)` where `M` is the number of objectives.
659659
24. When `sol_per_pop` is smaller than the number of NSGA-III reference points, PyGAD raises a warning and grows the population to match before the generational loop starts.
660-
660+
25. A new crossover operator: Simulated Binary Crossover (SBX). Use it by setting `crossover_type='sbx'`. The shape of the spread is controlled by the new `sbx_crossover_eta` parameter (default 30).
661+
26. A new mutation operator: polynomial mutation. Use it by setting `mutation_type='polynomial'`. The size of the change is controlled by the new `polynomial_mutation_eta` parameter (default 20).
662+
27. Two new stop criteria: `time_<seconds>` stops the run when the time inside `run()` is at least the given number of seconds; `evaluations_<N>` stops the run when the number of fitness function calls reaches the given count. New instance attribute `num_fitness_evaluations` counts the calls.
663+
28. A new submodule `pygad.utils.indicators` with four functions to measure the quality of a Pareto front: `hypervolume`, `inverted_generational_distance`, `generational_distance`, and `spacing`.
664+
29. A new submodule `pygad.benchmarks` with built-in benchmark problems. `pygad.benchmarks.classic` has Sphere, Rastrigin, Rosenbrock, Griewank, Schwefel, Ackley, and Himmelblau. `pygad.benchmarks.zdt` has the ZDT family (ZDT1, ZDT2, ZDT3, ZDT4, ZDT6). `pygad.benchmarks.dtlz` has DTLZ1, DTLZ2, DTLZ3, and DTLZ4. `pygad.benchmarks.knapsack` has the 0/1 Knapsack problem. Each class is callable with the PyGAD fitness signature and returns negated values (for the minimization-style problems) so PyGAD can maximize toward the original minimum.
661665
21. Instead of using repeated code for converting the data type and rounding the genes during crossover and mutation, the `change_gene_dtype_and_round()` method is called from the `pygad.helper.misc.Helper` class.
662666
22. Fix some documentation issues. https://github.com/ahmedfgad/GeneticAlgorithmPython/pull/336
663667
23. Update the documentation to reflect the recent additions and changes to the library structure.

‎docs/source/utils.md‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The submodules in the `pygad.utils` module are:
1212
4. `parent_selection`: Has the `ParentSelection` class that implements the parent selection operators.
1313
5. `nsga2`: Has the `NSGA2` class that implements the Non-Dominated Sorting Genetic Algorithm II (NSGA-II).
1414
6. `nsga3`: Has the `NSGA3` class that implements the Non-Dominated Sorting Genetic Algorithm III (NSGA-III).
15+
7. `indicators`: Has functions to measure the quality of a Pareto front: `hypervolume`, `inverted_generational_distance`, `generational_distance`, and `spacing`.
1516

1617
Note that the `pygad.GA` class extends all of these classes. So, the user can access any of the methods in such classes directly by the instance/object of the `pygad.GA` class.
1718

@@ -342,6 +343,32 @@ The `pygad.utils.nsga3` module has a class named `NSGA3` that implements NSGA-II
342343
8. `nsga3_selection()`: Top-level NSGA-III parent selection routine.
343344
9. `tournament_selection_nsga3()`: Tournament-style NSGA-III parent selection routine.
344345

346+
## `pygad.utils.indicators` Submodule
347+
348+
The `pygad.utils.indicators` module has functions to measure the quality of a Pareto front. All functions take fitness values in PyGAD's maximization format. The functions are:
349+
350+
1. `hypervolume(fitness, reference_point)`: Volume of the objective space dominated by the front. The reference point must be worse than every solution on every objective. A larger value is better.
351+
2. `inverted_generational_distance(fitness, reference_front)`: Mean distance from each reference-front point to its nearest approximation point. Reports both convergence and diversity. A smaller value is better.
352+
3. `generational_distance(fitness, reference_front)`: Mean distance from each approximation point to its nearest reference point. Reports convergence only. A smaller value is better.
353+
4. `spacing(fitness)`: Standard deviation of the distance from each solution to its nearest neighbour. A smaller value means the solutions are spread more evenly.
354+
355+
Example:
356+
357+
```python
358+
from pygad.utils.indicators import hypervolume, inverted_generational_distance
359+
360+
# After ga.run()
361+
fitness = ga.last_generation_fitness
362+
reference_point = [-10.0, -10.0] # worse than every solution
363+
hv = hypervolume(fitness, reference_point)
364+
365+
# If the true Pareto front is known
366+
from pygad.benchmarks.zdt import ZDT1
367+
problem = ZDT1()
368+
true_front = problem.pareto_front(num_points=100)
369+
igd = inverted_generational_distance(fitness, true_front)
370+
```
371+
345372
## More about the Operators
346373

347374
::::{grid} 1 2 2 2

‎pygad/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
from .pygad import * # Relative import.
22

3+
from pygad import benchmarks
4+
35
from ._version import __version__

‎pygad/benchmarks/__init__.py‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
Standard benchmark problems for PyGAD.
3+
4+
Every problem class can be called with the standard PyGAD fitness
5+
function signature (ga, solution, sol_idx) and returns a fitness in
6+
PyGAD's maximization format. The original minimization values are
7+
negated so the user can plug the problem directly into PyGAD without
8+
extra wrapping. Each class also has the attributes num_genes,
9+
num_objectives, and bounds.
10+
"""
11+
12+
from pygad.benchmarks import classic
13+
from pygad.benchmarks import zdt
14+
from pygad.benchmarks import dtlz
15+
from pygad.benchmarks import knapsack
16+
17+
__all__ = ["classic", "zdt", "dtlz", "knapsack"]

0 commit comments

Comments
 (0)