8000 Accept gene values in gene_constraint callables · ahmedfgad/GeneticAlgorithmPython@8b245e6 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8b245e6

Browse files
author
Ahmed Gad
committed
Accept gene values in gene_constraint callables
1 parent cd7be47 commit 8b245e6

File tree

8 files changed

+84
-22
lines changed

8 files changed

+84
-22
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
Check documentation of the [PyGAD](https://pygad.readthedocs.io/en/latest).
88

99
[![PyPI Downloads](https://pepy.tech/badge/pygad)](https://pepy.tech/project/pygad) [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/pygad.svg?label=Conda%20Downloads)](
10-
https://anaconda.org/conda-forge/PyGAD) [![PyPI version](https://badge.fury.io/py/pygad.svg)](https://badge.fury.io/py/pygad) ![Docs](https://readthedocs.org/projects/pygad/badge) [![PyGAD PyTest / Python 3.11](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py311.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py311.yml) [![PyGAD PyTest / Python 3.10](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py310.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py310.yml) [![PyGAD PyTest / Python 3.9](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py39.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py39.yml) [![PyGAD PyTest / Python 3.8](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py38.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py38.yml) [![PyGAD PyTest / Python 3.7](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py37.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py37.yml) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Translation](https://hosted.weblate.org/widgets/weblate/-/svg-badge.svg)](https://hosted.weblate.org/engage/weblate/) [![REUSE](https://api.reuse.software/badge/github.com/WeblateOrg/weblate)](https://api.reuse.software/info/github.com/WeblateOrg/weblate) [![Stack Overflow](https://img.shields.io/badge/stackoverflow-Ask%20questions-blue.svg)](
10+
https://anaconda.org/conda-forge/PyGAD) [![PyPI version](https://badge.fury.io/py/pygad.svg)](https://badge.fury.io/py/pygad)![Docs](https://readthedocs.org/projects/pygad/badge)[![PyGAD PyTest / Python 3.13](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py313.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py313.yml) [![PyGAD PyTest / Python 3.12](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py312.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py312.yml) [![PyGAD PyTest / Python 3.11](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py311.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py311.yml) [![PyGAD PyTest / Python 3.10](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py310.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py310.yml) [![PyGAD PyTest / Python 3.9](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py39.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py39.yml) [![PyGAD PyTest / Python 3.8](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py38.yml/badge.svg)](https://github.com/ahmedfgad/GeneticAlgorithmPython/actions/workflows/main_py38.yml) [![License](https://img.shields.io/badge/License-BSD_3--Clause-blue.svg)](https://opensource.org/licenses/BSD-3-Clause) [![Translation](https://hosted.weblate.org/widgets/weblate/-/svg-badge.svg)](https://hosted.weblate.org/engage/weblate/) [![REUSE](https://api.reuse.software/badge/github.com/WeblateOrg/weblate)](https://api.reuse.software/info/github.com/WeblateOrg/weblate) [![Stack Overflow](https://img.shields.io/badge/stackoverflow-Ask%20questions-blue.svg)](
1111
https://stackoverflow.com/questions/tagged/pygad) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/ahmedfgad/GeneticAlgorithmPython/badge)](https://securityscorecards.dev/viewer/?uri=github.com/ahmedfgad/GeneticAlgorithmPython) [![DOI](https://zenodo.org/badge/DOI/10.1007/s11042-023-17167-y.svg)](https://doi.org/10.1007/s11042-023-17167-y)
1212

1313
![PYGAD-LOGO](https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png)
8 KB
Binary file not shown.

examples/example_gene_constraint.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import pygad
2+
import numpy
3+
4+
"""
5+
An example of using the gene_constraint parameter.
6+
7+
"""
8+
9+
function_inputs = [4,-2,3.5,5,-11,-4.7]
10+
desired_output = 44
11+
12+
def fitness_func(ga_instance, solution, solution_idx):
13+
output = numpy.sum(solution*function_inputs)
14+
fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001)
15+
return fitness
16+
17+
ga_instance = pygad.GA(num_generations=100,
18+
num_parents_mating=5,
19+
sol_per_pop=10,
20+
num_genes=len(function_inputs),
21+
mutation_num_genes=6,
22+
fitness_func=fitness_func,
23+
allow_duplicate_genes=False,
24+
gene_space=range(100),
25+
gene_type=int,
26+
sample_size=100,
27+
random_seed=10,
28+
gene_constraint=[lambda x, v: [val for val in v if val>=98],
29+
lambda x, v: [val for val in v if val>=98],
30+
lambda x, v: [val for val in v if 80<val<90],
31+
None,
32+
lambda x, v: [val for val in v if 20<val<40],
33+
None]
34+
)
35+
36+
ga_instance.run()
37+
print(ga_instance.population)

pygad/helper/misc.py

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,20 @@ def mutation_change_gene_dtype_and_round(self,
128128
gene_value=gene_value)
129129
return gene_value_new
130130

131+
def validate_gene_constraint_callable_output(self,
132+
selected_values,
133+
values):
134+
if type(selected_values) in [list, numpy.ndarray]:
135+
selected_values_set = set(selected_values)
136+
if selected_values_set.issubset(values):
137+
pass
138+
else:
139+
return False
140+
else:
141+
return False
142+
143+
return True
144+
131145
def filter_gene_values_by_constraint(self,
132146
values,
133147
solution,
@@ -144,30 +158,30 @@ def filter_gene_values_by_constraint(self,
144158
It returns None if no values satisfy the constraint. Otherwise, an array of values that satisfy the constraint is returned.
145159
"""
146160

147-
# A list of the indices where the random values satisfy the constraint.
148-
filtered_values_indices = []
161+
if self.gene_constraint and self.gene_constraint[gene_idx]:
162+
pass
163+
else:
164+
raise Exception(f"Either the gene at index {gene_idx} is not assigned a callable/function or the gene_constraint itself is not used.")
165+
149166
# A temporary solution to avoid changing the original solution.
150167
solution_tmp = solution.copy()
151-
# Loop through the random values to filter the ones satisfying the constraint.
152-
for value_idx, value in enumerate(values):
153-
solution_tmp[gene_idx] = value
154-
# Check if the constraint is satisfied.
155-
if self.gene_constraint[gene_idx](solution_tmp):
156-
# The current value satisfies the constraint.
157-
filtered_values_indices.append(value_idx)
168+
filtered_values = self.gene_constraint[gene_idx](solution_tmp, values.copy())
169+
result = self.validate_gene_constraint_callable_output(selected_values=filtered_values,
170+
values=values)
171+
if result:
172+
pass
173+
else:
174+
raise Exception("The output from the gene_constraint callable/function must be a list or NumPy array that is subset of the passed values (second argument).")
158175

159176
# After going through all the values, check if any value satisfies the constraint.
160-
if len(filtered_values_indices) > 0:
177+
if len(filtered_values) > 0:
161178
# At least one value was found that meets the gene constraint.
162179
pass
163180
else:
164181
# No value found for the current gene that satisfies the constraint.
165-
if not self.suppress_warnings:
166-
warnings.warn(f"No value found for the gene at index {gene_idx} with value {solution[gene_idx]} that satisfies its gene constraint.")
182+
if not self.suppress_warnings: warnings.warn(f"Failed to find a value that satisfies its gene constraint for the gene at index {gene_idx} with value {solution[gene_idx]} at generation {self.generations_completed}.")
167183
return None
168184

169-
filtered_values = values[filtered_values_indices]
170-
171185
return filtered_values
172186

173187
def get_gene_dtype(self, gene_index):

pygad/helper/unique.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ def solve_duplicate_genes_randomly(self,
6969

7070
if temp_val in new_solution:
7171
num_unsolved_duplicates = num_unsolved_duplicates + 1
72-
if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {solution[duplicate_index]}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.")
72+
if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {solution[duplicate_index]} at generation {self.generations_completed}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.")
7373
else:
7474
# Unique gene value found.
7575
new_solution[duplicate_index] = temp_val
@@ -320,7 +320,7 @@ def unique_genes_by_space(self,
320320

321321
if temp_val in solution:
322322
num_unsolved_duplicates = num_unsolved_duplicates + 1
323-
if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {solution[duplicate_index]}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.")
323+
if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {solution[duplicate_index]} at generation {self.generations_completed+1}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.")
324324
else:
325325
solution[duplicate_index] = temp_val
326326

pygad/pygad.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -545,13 +545,13 @@ def __init__(self,
545545
if item is None:
546546
pass
547547
elif item and callable(item):
548-
if item.__code__.co_argcount == 1:
549-
# Every callable is valid if it receives a single argument.
550-
# This argument represents the solution.
548+
if item.__code__.co_argcount == 2:
549+
# Every callable is valid if it receives 2 arguments.
550+
# The 2 arguments: 1) solution 2) A list or numpy.ndarray of values to check if they meet the constraint.
551551
pass
552552
else:
553553
self.valid_parameters = False
554-
raise ValueError(f"Every callable inside the gene_constraint parameter must accept a single argument representing the solution/chromosome. But the callable at index {constraint_idx} named '{item.__code__.co_name}' accepts {item.__code__.co_argcount} argument(s).")
554+
raise ValueError(f"Every callable inside the gene_constraint parameter must accept 2 arguments representing 1) The solution/chromosome where the gene exists 2) A list of NumPy array of values to check if they meet the constraint. But the callable at index {constraint_idx} named '{item.__code__.co_name}' accepts {item.__code__.co_argcount} argument(s).")
555555
else:
556556
self.valid_parameters = False
557557
raise TypeError(f"The expected type of an element in the 'gene_constraint' parameter is None or a callable (e.g. function). But {item} at index {constraint_idx} of type {type(item)} found.")
@@ -1424,7 +1424,18 @@ def initialize_population(self,
14241424
for gene_idx in range(self.num_genes):
14251425
# Check that a constraint is available for the gene and that the current value does not satisfy that constraint
14261426
if self.gene_constraint[gene_idx]:
1427-
if not self.gene_constraint[gene_idx](solution):
1427+
# Remember that the second argument to the gene constraint callable is a list/numpy.ndarray of the values to check if they meet the gene constraint.
1428+
values = [solution[gene_idx]]
1429+
filtered_values = self.gene_constraint[gene_idx](solution, values)
1430+
result = self.validate_gene_constraint_callable_output(selected_values=filtered_values,
1431+
values=values)
1432+
if result:
1433+
pass
1434+
else:
1435+
raise Exception("The output from the gene_constraint callable/function must be a list or NumPy array that is subset of the passed values (second argument).")
1436+
1437+
# Check if the gene value satisfies the gene constraint.
1438+
if len(filtered_values) == 1 and filtered_values[0] == solution[gene_idx]:
14281439
range_min, range_max = self.get_initial_population_range(gene_index=gene_idx)
14291440
# While initializing the population, we follow a mutation by replacement approach. So, the original gene value is not needed.
14301441
values_filtered = self.get_valid_gene_constraint_values(range_min=range_min,
-237 Bytes
Binary file not shown.
-13.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)
0