Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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)

‎.DS_Store renamed to ‎docs/.DS_Store

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-
forvalue_idx, valueinenumerate(values):
153-
solution_tmp[gene_idx] =value
154-
# Check if the constraint is satisfied.
155-
ifself.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+
raiseException("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)

AltStyle によって変換されたページ (->オリジナル) /