Skip to content

Commit

Permalink
PyGAD 3.3.1
Browse files Browse the repository at this point in the history
Release Date 17 February 2024
1. After the last generation and before the `run()` method completes, update the 2 instance attributes: 1) `last_generation_parents` 2) `last_generation_parents_indices`. This is to keep the list of parents up-to-date with the latest population fitness `last_generation_fitness`.  #275
2. 4 methods with names starting with `run_`. Their purpose is to keep the main loop inside the `run()` method clean. Check the [Other Methods](https://pygad.readthedocs.io/en/latest/pygad.html#other-methods) section for more information.
  • Loading branch information
ahmedfgad committed Feb 17, 2024
1 parent eae41c0 commit 990f1a1
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 41 deletions.
8 changes: 6 additions & 2 deletions docs/source/pygad.rst
Original file line number Diff line number Diff line change
Expand Up @@ -610,8 +610,12 @@ Other Methods
from inside the ``run()`` method. Supported in `PyGAD
3.3.1 <https://pygad.readthedocs.io/en/latest/releases.html#pygad-3-3-1>`__.

1. ``run_select_parents()``: Select the parents and call the callable
``on_parents()`` if defined.
1. ``run_select_parents(call_on_parents=True)``: Select the parents
and call the callable ``on_parents()`` if defined. If
``call_on_parents`` is ``True``, then the callable
``on_parents()`` is called. It must be ``False`` when the
``run_select_parents()`` method is called to update the parents at
the end of the ``run()`` method.

2. ``run_crossover()``: Apply crossover and call the callable
``on_crossover()`` if defined.
Expand Down
19 changes: 19 additions & 0 deletions docs/source/releases.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1532,6 +1532,25 @@ Release Date 29 January 2024
self.best_solution_generation = numpy.where(numpy.array(
self.best_solutions_fitness) == numpy.max(numpy.array(self.best_solutions_fitness)))[0][0]
.. _pygad-331:

PyGAD 3.3.1
-----------

Release Date 17 February 2024

1. After the last generation and before the ``run()`` method completes,
update the 2 instance attributes: 1) ``last_generation_parents`` 2)
``last_generation_parents_indices``. This is to keep the list of
parents up-to-date with the latest population fitness
``last_generation_fitness``.
https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/275

2. 4 methods with names starting with ``run_``. Their purpose is to keep
the main loop inside the ``run()`` method clean. Check the `Other
Methods <https://pygad.readthedocs.io/en/latest/pygad.html#other-methods>`__
section for more information.

PyGAD Projects at GitHub
========================

Expand Down
85 changes: 46 additions & 39 deletions pygad/pygad.py
Original file line number Diff line number Diff line change
Expand Up @@ -2067,7 +2067,8 @@ def run(self):

# Call the run_select_parents() method to update these 2 attributes according to the 'last_generation_fitness' attribute:
# 1) last_generation_parents 2) last_generation_parents_indices
self.run_select_parents()
# Set 'call_on_parents=False' to avoid calling the callable 'on_parents' because this step is not part of the cycle.
self.run_select_parents(call_on_parents=False)

# Save the fitness value of the best solution.
_, best_solution_fitness, _ = self.best_solution(
Expand All @@ -2093,7 +2094,7 @@ def run(self):
# sys.exit(-1)
raise ex

def run_select_parents(self):
def run_select_parents(self, call_on_parents=True):
"""
This method must be only callled from inside the run() method. It is not meant for use by the user.
Generally, any method with a name starting with 'run_' is meant to be only called by PyGAD from inside the 'run()' method.
Expand All @@ -2103,6 +2104,11 @@ def run_select_parents(self):
1) last_generation_parents: A NumPy array of the selected parents.
2) last_generation_parents_indices: A 1D NumPy array of the indices of the selected parents.
Parameters
----------
call_on_parents : bool, optional
If True, then the callable 'on_parents()' is called. The default is True.
Returns
-------
None.
Expand Down Expand Up @@ -2133,46 +2139,47 @@ def run_select_parents(self):
elif len(self.last_generation_parents_indices) != self.num_parents_mating:
raise ValueError(f"The iterable holding the selected parents indices is expected to have ({self.num_parents_mating}) values but ({len(self.last_generation_parents_indices)}) found.")

if not (self.on_parents is None):
on_parents_output = self.on_parents(self,
self.last_generation_parents)

if on_parents_output is None:
pass
elif type(on_parents_output) in [list, tuple, numpy.ndarray]:
if len(on_parents_output) == 2:
on_parents_selected_parents, on_parents_selected_parents_indices = on_parents_output
else:
raise ValueError(f"The output of on_parents() is expected to be tuple/list/numpy.ndarray of length 2 but {type(on_parents_output)} of length {len(on_parents_output)} found.")

# Validate the parents.
if on_parents_selected_parents is None:
raise ValueError("The returned outputs of on_parents() cannot be None but the first output is None.")
else:
if type(on_parents_selected_parents) in [tuple, list, numpy.ndarray]:
on_parents_selected_parents = numpy.array(on_parents_selected_parents)
if on_parents_selected_parents.shape == self.last_generation_parents.shape:
self.last_generation_parents = on_parents_selected_parents
else:
raise ValueError(f"Size mismatch between the parents retrned by on_parents() {on_parents_selected_parents.shape} and the expected parents shape {self.last_generation_parents.shape}.")
if call_on_parents:
if not (self.on_parents is None):
on_parents_output = self.on_parents(self,
self.last_generation_parents)

if on_parents_output is None:
pass
elif type(on_parents_output) in [list, tuple, numpy.ndarray]:
if len(on_parents_output) == 2:
on_parents_selected_parents, on_parents_selected_parents_indices = on_parents_output
else:
raise ValueError(f"The output of on_parents() is expected to be tuple/list/numpy.ndarray but the first output type is {type(on_parents_selected_parents)}.")

# Validate the parents indices.
if on_parents_selected_parents_indices is None:
raise ValueError("The returned outputs of on_parents() cannot be None but the second output is None.")
else:
if type(on_parents_selected_parents_indices) in [tuple, list, numpy.ndarray, range]:
on_parents_selected_parents_indices = numpy.array(on_parents_selected_parents_indices)
if on_parents_selected_parents_indices.shape == self.last_generation_parents_indices.shape:
self.last_generation_parents_indices = on_parents_selected_parents_indices
raise ValueError(f"The output of on_parents() is expected to be tuple/list/numpy.ndarray of length 2 but {type(on_parents_output)} of length {len(on_parents_output)} found.")

# Validate the parents.
if on_parents_selected_parents is None:
raise ValueError("The returned outputs of on_parents() cannot be None but the first output is None.")
else:
if type(on_parents_selected_parents) in [tuple, list, numpy.ndarray]:
on_parents_selected_parents = numpy.array(on_parents_selected_parents)
if on_parents_selected_parents.shape == self.last_generation_parents.shape:
self.last_generation_parents = on_parents_selected_parents
else:
raise ValueError(f"Size mismatch between the parents retrned by on_parents() {on_parents_selected_parents.shape} and the expected parents shape {self.last_generation_parents.shape}.")
else:
raise ValueError(f"Size mismatch between the parents indices returned by on_parents() {on_parents_selected_parents_indices.shape} and the expected crossover output {self.last_generation_parents_indices.shape}.")
raise ValueError(f"The output of on_parents() is expected to be tuple/list/numpy.ndarray but the first output type is {type(on_parents_selected_parents)}.")

# Validate the parents indices.
if on_parents_selected_parents_indices is None:
raise ValueError("The returned outputs of on_parents() cannot be None but the second output is None.")
else:
raise ValueError(f"The output of on_parents() is expected to be tuple/list/range/numpy.ndarray but the second output type is {type(on_parents_selected_parents_indices)}.")

else:
raise TypeError(f"The output of on_parents() is expected to be tuple/list/numpy.ndarray but {type(on_parents_output)} found.")
if type(on_parents_selected_parents_indices) in [tuple, list, numpy.ndarray, range]:
on_parents_selected_parents_indices = numpy.array(on_parents_selected_parents_indices)
if on_parents_selected_parents_indices.shape == self.last_generation_parents_indices.shape:
self.last_generation_parents_indices = on_parents_selected_parents_indices
else:
raise ValueError(f"Size mismatch between the parents indices returned by on_parents() {on_parents_selected_parents_indices.shape} and the expected crossover output {self.last_generation_parents_indices.shape}.")
else:
raise ValueError(f"The output of on_parents() is expected to be tuple/list/range/numpy.ndarray but the second output type is {type(on_parents_selected_parents_indices)}.")

else:
raise TypeError(f"The output of on_parents() is expected to be tuple/list/numpy.ndarray but {type(on_parents_output)} found.")

def run_crossover(self):
"""
Expand Down

0 comments on commit 990f1a1

Please sign in to comment.