diff --git a/docs/source/pygad.rst b/docs/source/pygad.rst index e606353..0271ce8 100644 --- a/docs/source/pygad.rst +++ b/docs/source/pygad.rst @@ -610,8 +610,12 @@ Other Methods from inside the ``run()`` method. Supported in `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. diff --git a/docs/source/releases.rst b/docs/source/releases.rst index 2674eb1..5ad1ec0 100644 --- a/docs/source/releases.rst +++ b/docs/source/releases.rst @@ -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 `__ + section for more information. + PyGAD Projects at GitHub ======================== diff --git a/pygad/pygad.py b/pygad/pygad.py index f668f63..f10eb70 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -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( @@ -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. @@ -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. @@ -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): """