8000 Solve a bug in NSGA2 tournament selection of parents · ahmedfgad/GeneticAlgorithmPython@3c7ed23 · GitHub
[go: up one dir, main page]

Skip to content

Commit 3c7ed23

Browse files
author
Ahmed Gad
committed
Solve a bug in NSGA2 tournament selection of parents
1 parent c4e1164 commit 3c7ed23

22 files changed

+193
-98
lines changed

.DS_Store

8 KB
Binary file not shown.

docs/.DS_Store

6 KB
Binary file not shown.

docs/md/pygad_more.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,17 @@ Out of the range of *1000* numbers, all the 100 values might not be satisfying t
337337

338338
> PyGAD does not yet handle the **dependencies** among the genes in the `gene_constraint` parameter.
339339
>
340-
> For example, gene 0 might depend on gene 1. To efficiently enforce the constraints, the constraint for gene 1 must be enforced first (if not `None`) then the constraint for gene 0.
340+
> This is an example where gene 0 depends on gene 1. To efficiently enforce the constraints, the constraint for gene 1 must be enforced first (if not `None`) then the constraint for gene 0.
341341
>
342-
> PyGAD applies constraints sequentially, starting from the first gene to the last. To ensure correct behavior when genes depend on each other, structure your GA problem so that if gene X depends on gene Y, then gene Y appears earlier in the chromosome (solution) than gene X.
342+
> ```python
343+
> gene_constraint=
344+
> [
345+
> lambda solution,values: [val for val in values if val<solution[1]],
346+
> lambda solution,values: [val for val in values if val>10]
347+
> ]
348+
> ```
349+
>
350+
> PyGAD applies constraints sequentially, starting from the first gene to the last. To ensure correct behavior when genes depend on each other, structure your GA problem so that if gene X depends on gene Y, then gene Y appears earlier in the chromosome (solution) than gene X. As a result, its gene constraint will be earlier in the list.
343351

344352
## Full Example
345353

docs/md/releases.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -566,7 +566,7 @@ Release Date 07 January 2025
566566
6. Created a new method called `unique_float_gene_from_range()` inside the `pygad.helper.unique.Unique` class to find a unique floating-point number from a range.
567567
7. Fix a bug in the `pygad.helper.unique.Unique.unique_gene_by_space()` method to return the numeric value only instead of a NumPy array.
568568
8. Refactoring the `pygad/helper/unique.py` script to remove duplicate codes and reformatting the docstrings.
569-
9. The plot_pareto_front_curve() method added to the pygad.visualize.plot.Plot class to visualize the Pareto front for multi-objective problems. It only supports 2 objectives. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/279
569+
9. The `plot_pareto_front_curve()` method added to the pygad.visualize.plot.Plot class to visualize the Pareto front for multi-objective problems. It only supports 2 objectives. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/279
570570
11. Fix a bug converting a nested NumPy array to a nested list. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/300
571571
12. The `Matplotlib` library is only imported when a method inside the `pygad/visualize/plot.py` script is used. This is more efficient than using `import matplotlib.pyplot` at the module level as this causes it to be imported when `pygad` is imported even when it is not needed. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/292
572572
13. Fix a bug when minus sign (-) is used inside the `stop_criteria` parameter (e.g. `stop_criteria=["saturate_10", "reach_-0.5"]`). https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/296
@@ -575,9 +575,9 @@ Release Date 07 January 2025
575575

576576
## PyGAD 3.5.0
577577

578-
Release Date 07 July 2025
578+
Release Date 08 July 2025
579579

580-
1. Fix a bug when minus sign (-) is used inside the `stop_criteria` parameter for multi-objective problems. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/314
580+
1. Fix a bug when minus sign (-) is used inside the `stop_criteria` parameter for multi-objective problems. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/314 https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/323
581581
2. Fix a bug when the `stop_criteria` parameter is passed as an iterable (e.g. list) for multi-objective problems (e.g. `['reach_50_60', 'reach_20, 40']`). https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/314
582582
3. Call the `get_matplotlib()` function from the `plot_genes()` method inside the `pygad.visualize.plot.Plot` class to import the matplotlib library. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/315
583583
4. Create a new helper method called `select_unique_value()` inside the `pygad/helper/unique.py` script to select a unique gene from an array of values.
@@ -598,12 +598,14 @@ Release Date 07 July 2025
598598
11. `filter_gene_values_by_constraint()`: Receives a list of values for a gene. Then it filters such values using the gene constraint.
599599
12. `get_valid_gene_constraint_values()`: Selects one valid gene value that satisfy the gene constraint. It simply calls `generate_gene_value()` to generate some gene values then it filters such values using `filter_gene_values_by_constraint()`.
600600
9. Create a new helper method called `mutation_process_random_value()` inside the `pygad/utils/mutation.py` script that generates constrained random values for mutation. It calls either `generate_gene_value()` or `get_valid_gene_constraint_values()` based on whether the `gene_constraint` parameter is used or not.
601-
10. A new parameter called `gene_constraint` is added. It accepts a list of callables (i.e. functions) acting as constraints for the gene values. Before selecting a value for a gene, the callable is called to ensure the candidate value is valid. Check the [Gene Constraint](https://pygad.readthedocs.io/en/latest/pygad_more.html#gene-constraint) section for more information.
601+
10. A new parameter called `gene_constraint` is added. It accepts a list of callables (i.e. functions) acting as constraints for the gene values. Before selecting a value for a gene, the callable is called to ensure the candidate value is valid. Check the [Gene Constraint](https://pygad.readthedocs.io/en/latest/pygad_more.html#gene-constraint) section for more information. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/119
602602
11. A new parameter called `sample_size` is added. To select a gene value that respects a constraint, this variable defines the size of the sample from which a value is selected randomly. Useful if either `allow_duplicate_genes` or `gene_constraint` is used. An instance attribute of the same name is created in the instances of the `pygad.GA` class. Check the [sample_size Parameter](https://pygad.readthedocs.io/en/latest/pygad_more.html#sample-size-parameter) section for more information.
603603
12. Use the `sample_size` parameter instead of `num_trials` in the methods `solve_duplicate_genes_randomly()` and `unique_float_gene_from_range()` inside the `pygad/helper/unique.py` script. It is the maximum number of values to generate as the search space when looking for a unique float value out of a range.
604604
13. Fixed a bug in population initialization when `allow_duplicate_genes=False`. Previously, gene values were checked for duplicates before rounding, which could allow near-duplicates like 7.61 and 7.62 to pass. After rounding (e.g., both becoming 7.6), this resulted in unintended duplicates. The fix ensures gene values are now rounded before duplicate checks, preventing such cases.
605605
14. More tests are created.
606606
15. More examples are created.
607+
16. Edited the `sort_solutions_nsga2()` method in the `pygad/utils/nsga2.py` script to accept an optional parameter called `find_best_solution` when calling this method just to find the best solution.
608+
17. Fixed a bug while applying the non-dominated sorting in the `get_non_dominated_set()` method inside the `pygad/utils/nsga2.py` script. It was swapping the non-dominated and dominated sets. In other words, it used the non-dominated set as if it is the dominated set and vice versa. All the calls to this method were edited accordingly. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/320.
607609

608610
# PyGAD Projects at GitHub
609611

docs/md/utils.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ It has the following helper methods:
190190
The `pygad.utils.nsga2` module has a class named `NSGA2` that implements NSGA-II. The methods inside this class are:
191191

192192
1. `non_dominated_sorting()`: Returns all the pareto fronts by applying non-dominated sorting over the solutions.
193-
2. `get_non_dominated_set()`: Returns the set of non-dominated solutions from the passed solutions.
193+
2. `get_non_dominated_set()`: Returns the 2 sets of non-dominated solutions and dominated solutions from the passed solutions. Note that the Pareto front consists of the solutions in the non-dominated set.
194194
3. `crowding_distance()`: Calculates the crowding distance for all solutions in the current pareto front.
195195
4. `sort_solutions_nsga2()`: Sort the solutions. If the problem is single-objective, then the solutions are sorted by sorting the fitness values of the population. If it is multi-objective, then non-dominated sorting and crowding distance are applied to sort the solutions.
196196

example_multi_objective.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import pygad
2+
import numpy
3+
4+
"""
5+
Given these 2 functions:
6+
y1 = f(w1:w6) = w1x1 + w2x2 + w3x3 + w4x4 + w5x5 + 6wx6
7+
y2 = f(w1:w6) = w1x7 + w2x8 + w3x9 + w4x10 + w5x11 + 6wx12
8+
where (x1,x2,x3,x4,x5,x6)=(4,-2,3.5,5,-11,-4.7) and y=50
9+
and (x7,x8,x9,x10,x11,x12)=(-2,0.7,-9,1.4,3,5) and y=30
10+
What are the best values for the 6 weights (w1 to w6)? We are going to use the genetic algorithm to optimize these 2 functions.
11+
This is a multi-objective optimization problem.
12+
13+
PyGAD considers the problem as multi-objective if the fitness function returns:
14+
1) List.
15+
2) Or tuple.
16+
3) Or numpy.ndarray.
17+
"""
18+
19+
function_inputs1 = [4,-2,3.5,5,-11,-4.7] # Function 1 inputs.
20+
function_inputs2 = [-2,0.7,-9,1.4,3,5] # Function 2 inputs.
21+
desired_output1 = 50 # Function 1 output.
22+
desired_output2 = 30 # Function 2 output.
23+
24+
def fitness_func(ga_instance, solution, solution_idx):
25+
output1 = numpy.sum(solution*function_inputs1)
26+
output2 = numpy.sum(solution*function_inputs2)
27+
fitness1 = 1.0 / (numpy.abs(output1 - desired_output1) + 0.000001)
28+
fitness2 = 1.0 / (numpy.abs(output2 - desired_output2) + 0.000001)
29+
return [fitness1, fitness2]
30+
31+
num_generations = 1 # Number of generations.
32+
num_parents_mating = 5 # Number of solutions to be selected as parents in the mating pool.
33+
34+
sol_per_pop = 10 # Number of solutions in the population.
35+
num_genes = len(function_inputs1)
36+
37+
ga_instance = pygad.GA(num_generations=num_generations,
38+
num_parents_mating=num_parents_mating,
39+
sol_per_pop=sol_per_pop,
40+
num_genes=num_genes,
41+
fitness_func=fitness_func,
42+
random_seed=3,
43+
parent_selection_type='tournament_nsga2')
44+
45+
# Running the GA to optimize the parameters of the function.
46+
ga_instance.run()
47+
48+
"""
49+
ga_instance.plot_fitness(label=['Obj 1', 'Obj 2'])
50+
ga_instance.plot_pareto_front_curve()
51+
52+
# Returning the details of the best solution.
53+
solution, solution_fitness, solution_idx = ga_instance.best_solution(ga_instance.last_generation_fitness)
54+
print(f"Parameters of the best solution : {solution}")
55+
print(f"Fitness value of the best solution = {solution_fitness}")
56+
print(f"Index of the best solution : {solution_idx}")
57+
58+
prediction = numpy.sum(numpy.array(function_inputs1)*solution)
59+
print(f"Predicted output 1 based on the best solution : {prediction}")
60+
prediction = numpy.sum(numpy.array(function_inputs2)*solution)
61+
print(f"Predicted output 2 based on the best solution : {prediction}")
62+
63+
if ga_instance.best_solution_generation != -1:
64+
print(f"Best fitness value reached after {ga_instance.best_solution_generation} generations.")
65+
"""

pygad/.DS_Store

10 KB
Binary file not shown.
205 Bytes
Binary file not shown.
79.4 KB
Binary file not shown.
260 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)
0