From 9ffa344fae699fb5859b97d3eaedd4d4170f3ef9 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sat, 7 Sep 2024 12:55:42 -0400 Subject: [PATCH 01/79] Fix Typo --- docs/source/pygad_more.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/source/pygad_more.rst b/docs/source/pygad_more.rst index 69fa342..7e0d748 100644 --- a/docs/source/pygad_more.rst +++ b/docs/source/pygad_more.rst @@ -632,6 +632,7 @@ After running the code again, it will find the same result. 0.04872203136549972 Continue without Losing Progress +================================ In `PyGAD 2.18.0 `__, @@ -2000,7 +2001,7 @@ future. These instances attributes can save the solutions: To configure PyGAD for non-deterministic problems, we have to disable saving the previous solutions. This is by setting these parameters: -1. ``keep_elisitm=0`` +1. ``keep_elitism=0`` 2. ``keep_parents=0`` From e17b999284f81094144a1aae2733e053eefb5207 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sat, 21 Sep 2024 10:40:10 -0400 Subject: [PATCH 02/79] No gradients before torch predictions --- pygad/torchga/__init__.py | 2 +- pygad/torchga/torchga.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pygad/torchga/__init__.py b/pygad/torchga/__init__.py index 7e51570..b7b98f5 100644 --- a/pygad/torchga/__init__.py +++ b/pygad/torchga/__init__.py @@ -1,3 +1,3 @@ from .torchga import * -__version__ = "1.3.0" +__version__ = "1.4.0" diff --git a/pygad/torchga/torchga.py b/pygad/torchga/torchga.py index cff6d2e..e552d3d 100644 --- a/pygad/torchga/torchga.py +++ b/pygad/torchga/torchga.py @@ -44,9 +44,10 @@ def predict(model, solution, data): _model = copy.deepcopy(model) _model.load_state_dict(model_weights_dict) - predictions = _model(data) + with torch.no_grad(): + predictions = _model(data) - return predictions + return predictions class TorchGA: From d51b4d8cd46b1dcf81e44bb2678fbb2fd4b457d1 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Fri, 6 Dec 2024 14:30:53 -0500 Subject: [PATCH 03/79] Clean code --- pygad/helper/unique.py | 40 +++++++++++++--------------------------- 1 file changed, 13 insertions(+), 27 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 1a0ba63..02e3874 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -171,7 +171,7 @@ def unique_int_gene_from_range(self, max_val, mutation_by_replacement, gene_type, - step=None): + step=1): """ Finds a unique integer value for the gene. @@ -182,38 +182,24 @@ def unique_int_gene_from_range(self, max_val: Maximum value of the range to sample a number randomly. mutation_by_replacement: Identical to the self.mutation_by_replacement attribute. gene_type: Exactly the same as the self.gene_type attribute. - + step: Defaults to 1. + Returns: selected_value: The new value of the gene. It may be identical to the original gene value in case there are no possible unique values for the gene. """ + # The gene_type is of the form [type, precision] if self.gene_type_single == True: - if step is None: - # all_gene_values = numpy.arange(min_val, - # max_val, - # dtype=gene_type[0]) - all_gene_values = numpy.asarray(numpy.arange(min_val, max_val), - dtype=gene_type[0]) - else: - # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) - # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. - all_gene_values = numpy.asarray(numpy.arange(min_val, - max_val, - step), - dtype=gene_type[0]) + dtype = gene_type[0] else: - if step is None: - # all_gene_values = numpy.arange(min_val, - # max_val, - # dtype=gene_type[gene_index][0]) - all_gene_values = numpy.asarray(numpy.arange(min_val, - max_val), - dtype=gene_type[gene_index][0]) - else: - all_gene_values = numpy.asarray(numpy.arange(min_val, - max_val, - step), - dtype=gene_type[gene_index][0]) + dtype = gene_type[gene_index][0] + + # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) + # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. + all_gene_values = numpy.asarray(numpy.arange(min_val, + max_val, + step), + dtype=dtype) if mutation_by_replacement: pass From aa49af716c62fe1dfba5f43a0714e53564229cbe Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sat, 7 Dec 2024 12:23:34 -0500 Subject: [PATCH 04/79] Refine comment --- pygad/pygad.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygad/pygad.py b/pygad/pygad.py index dad0be8..1c0fe2b 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -83,7 +83,7 @@ def __init__(self, init_range_high: The upper value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20. # It is OK to set the value of any of the 2 parameters ('init_range_low' and 'init_range_high') to be equal, higher or lower than the other parameter (i.e. init_range_low is not needed to be lower than init_range_high). - gene_type: The type of the gene. It is assigned to any of these types (int, float, numpy.int8, numpy.int16, numpy.int32, numpy.int64, numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64, numpy.float16, numpy.float32, numpy.float64) and forces all the genes to be of that type. + gene_type: The type of the gene. It is assigned to any of these types (int, numpy.int8, numpy.int16, numpy.int32, numpy.int64, numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64, float, numpy.float16, numpy.float32, numpy.float64) and forces all the genes to be of that type. parent_selection_type: Type of parent selection. keep_parents: If 0, this means no parent in the current population will be used in the next population. If -1, this means all parents in the current population will be used in the next population. If set to a value > 0, then the specified value refers to the number of parents in the current population to be used in the next population. Some parent selection operators such as rank selection, favor population diversity and therefore keeping the parents in the next generation can be beneficial. However, some other parent selection operators, such as roulette wheel selection (RWS), have higher selection pressure and keeping more than one parent in the next generation can seriously harm population diversity. This parameter have an effect only when the keep_elitism parameter is 0. Thanks to Prof. Fernando Jiménez Barrionuevo (http://webs.um.es/fernan) for editing this sentence. From 93337d24ad6d77175c7a3ea3071069f4b7ecfe44 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 8 Dec 2024 13:47:51 -0500 Subject: [PATCH 05/79] Edit documentation string --- pygad/helper/unique.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 02e3874..355e0e3 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -174,18 +174,19 @@ def unique_int_gene_from_range(self, step=1): """ - Finds a unique integer value for the gene. - - solution: A solution with duplicate values. - gene_index: Index of the gene to find a unique value. - min_val: Minimum value of the range to sample a number randomly. - max_val: Maximum value of the range to sample a number randomly. - mutation_by_replacement: Identical to the self.mutation_by_replacement attribute. - gene_type: Exactly the same as the self.gene_type attribute. - step: Defaults to 1. + Finds a unique integer value for a specific gene in a solution. + + Args: + solution (list): A solution containing genes, potentially with duplicate values. + gene_index (int): The index of the gene for which to find a unique value. + min_val (int): The minimum value of the range to sample a number randomly. + max_val (int): The maximum value of the range to sample a number randomly. + mutation_by_replacement (bool): Indicates if mutation is performed by replacement. + gene_type (type): The data type of the gene (e.g., int, float). + step (int, optional): The step size for generating candidate values. Defaults to 1. Returns: - selected_value: The new value of the gene. It may be identical to the original gene value in case there are no possible unique values for the gene. + int: The new value of the gene. If no unique value can be found, the original gene value is returned. """ # The gene_type is of the form [type, precision] From 7f292ca33699f4990fcacad95c5dca786293a04f Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 8 Dec 2024 13:54:25 -0500 Subject: [PATCH 06/79] Edit documentation string --- pygad/helper/unique.py | 124 +++++++++++++++++++++++------------------ 1 file changed, 71 insertions(+), 53 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 355e0e3..72e0773 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -16,23 +16,24 @@ def solve_duplicate_genes_randomly(self, mutation_by_replacement, gene_type, num_trials=10): - """ - Solves the duplicates in a solution by randomly selecting new values for the duplicating genes. + Resolves duplicates in a solution by randomly selecting new values for the duplicate genes. - solution: A solution with duplicate values. - min_val: Minimum value of the range to sample a number randomly. - max_val: Maximum value of the range to sample a number randomly. - mutation_by_replacement: Identical to the self.mutation_by_replacement attribute. - gene_type: Exactly the same as the self.gene_type attribute. - num_trials: Maximum number of trials to change the gene value to solve the duplicates. + Args: + solution (list): A solution containing genes, potentially with duplicate values. + min_val (int): The minimum value of the range to sample a number randomly. + max_val (int): The maximum value of the range to sample a number randomly. + mutation_by_replacement (bool): Indicates if mutation is performed by replacement. + gene_type (type): The data type of the gene (e.g., int, float). + num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. Returns: - new_solution: Solution after trying to solve its duplicates. If no duplicates solved, then it is identical to the passed solution parameter. - not_unique_indices: Indices of the genes with duplicate values. - num_unsolved_duplicates: Number of unsolved duplicates. + tuple: + list: The updated solution after attempting to resolve duplicates. If no duplicates are resolved, the solution remains unchanged. + list: The indices of genes that still have duplicate values. + int: The number of duplicates that could not be resolved. """ - + new_solution = solution.copy() _, unique_gene_indices = numpy.unique(solution, return_index=True) @@ -113,17 +114,20 @@ def solve_duplicate_genes_by_space(self, build_initial_pop=False): """ - Solves the duplicates in a solution by selecting values for the duplicating genes from the gene space. - - solution: A solution with duplicate values. - gene_type: Exactly the same as the self.gene_type attribute. - num_trials: Maximum number of trials to change the gene value to solve the duplicates. - + Resolves duplicates in a solution by selecting new values for the duplicate genes from the gene space. + + Args: + solution (list): A solution containing genes, potentially with duplicate values. + gene_type (type): The data type of the gene (e.g., int, float). + num_trials (int): The maximum number of attempts to resolve duplicates by selecting values from the gene space. + Returns: - new_solution: Solution after trying to solve its duplicates. If no duplicates solved, then it is identical to the passed solution parameter. - not_unique_indices: Indices of the genes with duplicate values. - num_unsolved_duplicates: Number of unsolved duplicates. + tuple: + list: The updated solution after attempting to resolve duplicates. If no duplicates are resolved, the solution remains unchanged. + list: The indices of genes that still have duplicate values. + int: The number of duplicates that could not be resolved. """ + new_solution = solution.copy() _, unique_gene_indices = numpy.unique(solution, return_index=True) @@ -236,18 +240,20 @@ def unique_genes_by_space(self, build_initial_pop=False): """ - Loops through all the duplicating genes to find unique values that from their gene spaces to solve the duplicates. - For each duplicating gene, a call to the unique_gene_by_space() function is made. - - new_solution: A solution with duplicate values. - gene_type: Exactly the same as the self.gene_type attribute. - not_unique_indices: Indices with duplicating values. - num_trials: Maximum number of trials to change the gene value to solve the duplicates. - + Iterates through all duplicate genes to find unique values from their gene spaces and resolve duplicates. + For each duplicate gene, a call is made to the `unique_gene_by_space()` function. + + Args: + new_solution (list): A solution containing genes with duplicate values. + gene_type (type): The data type of the gene (e.g., int, float). + not_unique_indices (list): The indices of genes with duplicate values. + num_trials (int): The maximum number of attempts to resolve duplicates for each gene. + Returns: - new_solution: Solution after trying to solve all of its duplicates. If no duplicates solved, then it is identical to the passed solution parameter. - not_unique_indices: Indices of the genes with duplicate values. - num_unsolved_duplicates: Number of unsolved duplicates. + tuple: + list: The updated solution after attempting to resolve all duplicates. If no duplicates are resolved, the solution remains unchanged. + list: The indices of genes that still have duplicate values. + int: The number of duplicates that could not be resolved. """ num_unsolved_duplicates = 0 @@ -283,15 +289,15 @@ def unique_gene_by_space(self, build_initial_pop=False): """ - Returns a unique gene value for a single gene based on its value space to solve the duplicates. - - solution: A solution with duplicate values. - gene_idx: The index of the gene that duplicates its value with another gene. - gene_type: Exactly the same as the self.gene_type attribute. - + Returns a unique value for a specific gene based on its value space to resolve duplicates. + + Args: + solution (list): A solution containing genes with duplicate values. + gene_idx (int): The index of the gene that has a duplicate value. + gene_type (type): The data type of the gene (e.g., int, float). + Returns: - A unique value, if exists, for the gene. - """ + Any: A unique value for the gene, if one exists; otherwise, the original gene value. """ if self.gene_space_nested: if type(self.gene_space[gene_idx]) in [numpy.ndarray, list, tuple]: @@ -572,11 +578,14 @@ def find_two_duplicates(self, solution, gene_space_unpacked): """ - Returns the first occurrence of duplicate genes. - It returns: - The index of a gene with a duplicate value. - The value of the gene. + Identifies the first occurrence of a duplicate gene in the solution. + + Returns: + tuple: + int: The index of the first gene with a duplicate value. + Any: The value of the duplicate gene. """ + for gene in set(solution): gene_indices = numpy.where(numpy.array(solution) == gene)[0] if len(gene_indices) == 1: @@ -594,13 +603,15 @@ def unpack_gene_space(self, range_max, num_values_from_inf_range=100): """ - Unpack the gene_space for the purpose of selecting a value that solves the duplicates. - This is by replacing each range by a list of values. - It accepts: - range_min: The range minimum value. - range_min: The range maximum value. - num_values_from_inf_range: For infinite range of float values, a fixed number of values equal to num_values_from_inf_range is selected using the numpy.linspace() function. - It returns the unpacked gene space. + Unpacks the gene space for selecting a value to resolve duplicates by converting ranges into lists of values. + + Args: + range_min (float or int): The minimum value of the range. + range_max (float or int): The maximum value of the range. + num_values_from_inf_range (int): The number of values to generate for an infinite range of float values using `numpy.linspace()`. + + Returns: + list: A list representing the unpacked gene space. """ # Copy the gene_space to keep it isolated form the changes. @@ -740,8 +751,15 @@ def solve_duplicates_deeply(self, """ Sometimes it is impossible to solve the duplicate genes by simply selecting another value for either genes. This function solve the duplicates between 2 genes by searching for a third gene that can make assist in the solution. - It returns: - The solution after solving the duplicates or the None if duplicates cannot be solved. + + Args: + solution (list): The current solution containing genes, potentially with duplicates. + gene_idx1 (int): The index of the first gene involved in the duplication. + gene_idx2 (int): The index of the second gene involved in the duplication. + assist_gene_idx (int): The index of the third gene used to assist in resolving the duplication. + + Returns: + list or None: The updated solution with duplicates resolved, or `None` if the duplicates cannot be resolved. """ # gene_space_unpacked = self.unpack_gene_space() From a13953b347c1058e80a154bc1a2daf4e3ccd82a8 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 8 Dec 2024 17:08:25 -0500 Subject: [PATCH 07/79] Clean the code --- pygad/helper/unique.py | 440 ++++++++++++++--------------------------- 1 file changed, 150 insertions(+), 290 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 72e0773..4187b5f 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -38,68 +38,52 @@ def solve_duplicate_genes_randomly(self, _, unique_gene_indices = numpy.unique(solution, return_index=True) not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) - + num_unsolved_duplicates = 0 if len(not_unique_indices) > 0: for duplicate_index in not_unique_indices: for trial_index in range(num_trials): if self.gene_type_single == True: - if gene_type[0] in pygad.GA.supported_int_types: - temp_val = self.unique_int_gene_from_range(solution=new_solution, - gene_index=duplicate_index, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=mutation_by_replacement, - gene_type=gene_type) - else: - temp_val = numpy.random.uniform(low=min_val, - high=max_val, - size=1)[0] - if mutation_by_replacement: - pass - else: - temp_val = new_solution[duplicate_index] + temp_val + dtype = gene_type else: - if gene_type[duplicate_index][0] in pygad.GA.supported_int_types: - temp_val = self.unique_int_gene_from_range(solution=new_solution, - gene_index=duplicate_index, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=mutation_by_replacement, - gene_type=gene_type) + dtype = gene_type[duplicate_index] + + if dtype[0] in pygad.GA.supported_int_types: + temp_val = self.unique_int_gene_from_range(solution=new_solution, + gene_index=duplicate_index, + min_val=min_val, + max_val=max_val, + mutation_by_replacement=mutation_by_replacement, + gene_type=gene_type) + else: + temp_val = numpy.random.uniform(low=min_val, + high=max_val, + size=1)[0] + if mutation_by_replacement: + pass else: - temp_val = numpy.random.uniform(low=min_val, - high=max_val, - size=1)[0] - if mutation_by_replacement: - pass - else: - temp_val = new_solution[duplicate_index] + temp_val + temp_val = new_solution[duplicate_index] + temp_val # Similar to the round_genes() method in the pygad module, # Create a round_gene() method to round a single gene. - if self.gene_type_single == True: - if not gene_type[1] is None: - temp_val = numpy.round(gene_type[0](temp_val), - gene_type[1]) - else: - temp_val = gene_type[0](temp_val) + if not dtype[1] is None: + temp_val = numpy.round(dtype[0](temp_val), + dtype[1]) else: - if not gene_type[duplicate_index][1] is None: - temp_val = numpy.round(gene_type[duplicate_index][0](temp_val), - gene_type[duplicate_index][1]) - else: - temp_val = gene_type[duplicate_index][0](temp_val) + temp_val = dtype[0](temp_val) if temp_val in new_solution and trial_index == (num_trials - 1): num_unsolved_duplicates = num_unsolved_duplicates + 1 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.") elif temp_val in new_solution: + # Keep trying in the other remaining trials. continue else: + # Unique gene value found. new_solution[duplicate_index] = temp_val break - + + # TODO Move this code outside the loops. # Update the list of duplicate indices after each iteration. _, unique_gene_indices = numpy.unique(new_solution, return_index=True) not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) @@ -167,7 +151,7 @@ def solve_duplicate_genes_by_space(self, return new_solution, not_unique_indices, len(not_unique_indices) return new_solution, not_unique_indices, num_unsolved_duplicates - + def unique_int_gene_from_range(self, solution, gene_index, @@ -194,10 +178,7 @@ def unique_int_gene_from_range(self, """ # The gene_type is of the form [type, precision] - if self.gene_type_single == True: - dtype = gene_type[0] - else: - dtype = gene_type[gene_index][0] + dtype = gene_type[0] # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. @@ -205,26 +186,23 @@ def unique_int_gene_from_range(self, max_val, step), dtype=dtype) - + + # If mutation is by replacement, do not add the current gene value into the list. + # This is to avoid replacing the value by itself again. We are doing nothing in this case. if mutation_by_replacement: pass else: all_gene_values = all_gene_values + solution[gene_index] + # After adding solution[gene_index] to the list, we have to change the data type again. # TODO: The gene data type is converted twine. One above and one here. - if self.gene_type_single == True: - # Note that we already know that the data type is integer. - all_gene_values = numpy.asarray(all_gene_values, - dtype=gene_type[0]) - else: - # Note that we already know that the data type is integer. - all_gene_values = numpy.asarray(all_gene_values, - gene_type[gene_index][0]) + all_gene_values = numpy.asarray(all_gene_values, + dtype) values_to_select_from = list(set(list(all_gene_values)) - set(solution)) if len(values_to_select_from) == 0: - # If there is no values, then keep the current gene value. + # If there are no values, then keep the current gene value. if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but there is no enough values to prevent duplicates.") selected_value = solution[gene_index] else: @@ -314,131 +292,68 @@ def unique_gene_by_space(self, # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. elif curr_gene_space is None: if self.gene_type_single == True: - if gene_type[0] in pygad.GA.supported_int_types: - if build_initial_pop == True: - # If we are building the initial population, then use the range of the initial population. - min_val = self.init_range_low - max_val = self.init_range_high - else: - # If we are NOT building the initial population, then use the range of the random mutation. - min_val = self.random_mutation_min_val - max_val = self.random_mutation_max_val - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=True, - gene_type=gene_type) + dtype = gene_type + else: + dtype = gene_type[gene_idx] + + if dtype[0] in pygad.GA.supported_int_types: + if build_initial_pop == True: + # If we are building the initial population, then use the range of the initial population. + min_val = self.init_range_low + max_val = self.init_range_high else: - if build_initial_pop == True: - low = self.init_range_low - high = self.init_range_high - else: - low = self.random_mutation_min_val - high = self.random_mutation_max_val + # If we are NOT building the initial population, then use the range of the random mutation. + min_val = self.random_mutation_min_val + max_val = self.random_mutation_max_val - value_from_space = numpy.random.uniform(low=low, - high=high, - size=1)[0] - # TODO: Remove check for mutation_by_replacement when solving duplicates. Just replace the gene by the selected value from space. - # if self.mutation_by_replacement: - # pass - # else: - # value_from_space = solution[gene_idx] + value_from_space + value_from_space = self.unique_int_gene_from_range(solution=solution, + gene_index=gene_idx, + min_val=min_val, + max_val=max_val, + mutation_by_replacement=True, + gene_type=dtype) else: - if gene_type[gene_idx][0] in pygad.GA.supported_int_types: - if build_initial_pop == True: - min_val = self.init_range_low - max_val = self.init_range_high - else: - min_val = self.random_mutation_min_val - max_val = self.random_mutation_max_val - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=True, - gene_type=gene_type) + if build_initial_pop == True: + low = self.init_range_low + high = self.init_range_high else: - if build_initial_pop == True: - low = self.init_range_low - high = self.init_range_high - else: - low = self.random_mutation_min_val - high = self.random_mutation_max_val + low = self.random_mutation_min_val + high = self.random_mutation_max_val + + value_from_space = numpy.random.uniform(low=low, + high=high, + size=1)[0] - value_from_space = numpy.random.uniform(low=low, - high=high, - size=1)[0] - # TODO: Remove check for mutation_by_replacement when solving duplicates. Just replace the gene by the selected value from space. - # if self.mutation_by_replacement: - # pass - # else: - # value_from_space = solution[gene_idx] + value_from_space - elif type(curr_gene_space) is dict: if self.gene_type_single == True: - if gene_type[0] in pygad.GA.supported_int_types: - if 'step' in curr_gene_space.keys(): - step = curr_gene_space['step'] - else: - step = None - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=curr_gene_space['low'], - max_val=curr_gene_space['high'], - step=step, - mutation_by_replacement=True, - gene_type=gene_type) + dtype = gene_type + else: + dtype = gene_type[gene_idx] + + # Use index 0 to return the type from the list (e.g. [int, None] or [float, 2]). + if dtype[0] in pygad.GA.supported_int_types: + if 'step' in curr_gene_space.keys(): + step = curr_gene_space['step'] else: - if 'step' in curr_gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], - stop=curr_gene_space['high'], - step=curr_gene_space['step']), - size=1) - else: - value_from_space = numpy.random.uniform(low=curr_gene_space['low'], - high=curr_gene_space['high'], - size=1)[0] - # TODO: Remove check for mutation_by_replacement when solving duplicates. Just replace the gene by the selected value from space. - # if self.mutation_by_replacement: - # pass - # else: - # value_from_space = solution[gene_idx] + value_from_space + step = None + + value_from_space = self.unique_int_gene_from_range(solution=solution, + gene_index=gene_idx, + min_val=curr_gene_space['low'], + max_val=curr_gene_space['high'], + step=step, + mutation_by_replacement=True, + gene_type=dtype) else: - # Use index 0 to return the type from the list (e.g. [int, None] or [float, 2]). - if gene_type[gene_idx][0] in pygad.GA.supported_int_types: - if 'step' in curr_gene_space.keys(): - step = curr_gene_space['step'] - else: - step = None - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=curr_gene_space['low'], - max_val=curr_gene_space['high'], - step=step, - mutation_by_replacement=True, - gene_type=gene_type) + if 'step' in curr_gene_space.keys(): + value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], + stop=curr_gene_space['high'], + step=curr_gene_space['step']), + size=1) else: - if 'step' in curr_gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], - stop=curr_gene_space['high'], - step=curr_gene_space['step']), - size=1) - else: - value_from_space = numpy.random.uniform(low=curr_gene_space['low'], - high=curr_gene_space['high'], - size=1)[0] - # TODO: Remove check for mutation_by_replacement when solving duplicates. Just replace the gene by the selected value from space. - # if self.mutation_by_replacement: - # pass - # else: - # value_from_space = solution[gene_idx] + value_from_space - + value_from_space = numpy.random.uniform(low=curr_gene_space['low'], + high=curr_gene_space['high'], + size=1)[0] else: # Selecting a value randomly based on the current gene's space in the 'gene_space' attribute. # If the gene space has only 1 value, then select it. The old and new values of the gene are identical. @@ -473,66 +388,34 @@ def unique_gene_by_space(self, # Selecting a value randomly from the global gene space in the 'gene_space' attribute. if type(self.gene_space) is dict: if self.gene_type_single == True: - if gene_type[0] in pygad.GA.supported_int_types: - if 'step' in self.gene_space.keys(): - step = self.gene_space['step'] - else: - step = None - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=self.gene_space['low'], - max_val=self.gene_space['high'], - step=step, - mutation_by_replacement=True, - gene_type=gene_type) + dtype = gene_type + else: + dtype = gene_type[gene_idx] + + if dtype[0] in pygad.GA.supported_int_types: + if 'step' in self.gene_space.keys(): + step = self.gene_space['step'] else: - # When the gene_space is assigned a dict object, then it specifies the lower and upper limits of all genes in the space. - if 'step' in self.gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=1) - else: - value_from_space = numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=1)[0] - # TODO: Remove check for mutation_by_replacement when solving duplicates. Just replace the gene by the selected value from space. - # if self.mutation_by_replacement: - # pass - # else: - # value_from_space = solution[gene_idx] + value_from_space + step = None + + value_from_space = self.unique_int_gene_from_range(solution=solution, + gene_index=gene_idx, + min_val=self.gene_space['low'], + max_val=self.gene_space['high'], + step=step, + mutation_by_replacement=True, + gene_type=dtype) else: - if gene_type[gene_idx][0] in pygad.GA.supported_int_types: - if 'step' in self.gene_space.keys(): - step = self.gene_space['step'] - else: - step = None - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=self.gene_space['low'], - max_val=self.gene_space['high'], - step=step, - mutation_by_replacement=True, - gene_type=gene_type) + # When the gene_space is assigned a dict object, then it specifies the lower and upper limits of all genes in the space. + if 'step' in self.gene_space.keys(): + value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], + stop=self.gene_space['high'], + step=self.gene_space['step']), + size=1) else: - # When the gene_space is assigned a dict object, then it specifies the lower and upper limits of all genes in the space. - if 'step' in self.gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=1) - else: - value_from_space = numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=1)[0] - # TODO: Remove check for mutation_by_replacement when solving duplicates. Just replace the gene by the selected value from space. - # if self.mutation_by_replacement: - # pass - # else: - # value_from_space = solution[gene_idx] + value_from_space - + value_from_space = numpy.random.uniform(low=self.gene_space['low'], + high=self.gene_space['high'], + size=1)[0] else: # If the space type is not of type dict, then a value is randomly selected from the gene_space attribute. # Remove all the genes in the current solution from the gene_space. @@ -560,17 +443,15 @@ def unique_gene_by_space(self, # Similar to the round_genes() method in the pygad module, # Create a round_gene() method to round a single gene. if self.gene_type_single == True: - if not gene_type[1] is None: - value_from_space = numpy.round(gene_type[0](value_from_space), - gene_type[1]) - else: - value_from_space = gene_type[0](value_from_space) + dtype = gene_type else: - if not gene_type[gene_idx][1] is None: - value_from_space = numpy.round(gene_type[gene_idx][0](value_from_space), - gene_type[gene_idx][1]) - else: - value_from_space = gene_type[gene_idx][0](value_from_space) + dtype = gene_type[gene_idx] + + if not dtype[1] is None: + value_from_space = numpy.round(dtype[0](value_from_space), + dtype[1]) + else: + value_from_space = dtype[0](value_from_space) return value_from_space @@ -672,47 +553,30 @@ def unpack_gene_space(self, elif type(space) is dict: # Create a list of values using the dict range. # Use numpy.linspace() - if self.gene_type_single == True: # self.gene_type_single - if self.gene_type[0] in pygad.GA.supported_int_types: - if 'step' in space.keys(): - step = space['step'] - else: - step = 1 - - gene_space_unpacked[space_idx] = numpy.arange(start=space['low'], - stop=space['high'], - step=step) + if self.gene_type_single == True: + dtype = self.gene_type + else: + dtype = self.gene_type[gene_idx] + + if dtype[0] in pygad.GA.supported_int_types: + if 'step' in space.keys(): + step = space['step'] else: - if 'step' in space.keys(): - gene_space_unpacked[space_idx] = numpy.arange(start=space['low'], - stop=space['high'], - step=space['step']) - else: - gene_space_unpacked[space_idx] = numpy.linspace(start=space['low'], - stop=space['high'], - num=num_values_from_inf_range, - endpoint=False) + step = 1 + + gene_space_unpacked[space_idx] = numpy.arange(start=space['low'], + stop=space['high'], + step=step) else: - if self.gene_type[space_idx][0] in pygad.GA.supported_int_types: - if 'step' in space.keys(): - step = space['step'] - else: - step = 1 - + if 'step' in space.keys(): gene_space_unpacked[space_idx] = numpy.arange(start=space['low'], stop=space['high'], - step=step) + step=space['step']) else: - if 'step' in space.keys(): - gene_space_unpacked[space_idx] = numpy.arange(start=space['low'], - stop=space['high'], - step=space['step']) - else: - gene_space_unpacked[space_idx] = numpy.linspace(start=space['low'], - stop=space['high'], - num=num_values_from_inf_range, - endpoint=False) - + gene_space_unpacked[space_idx] = numpy.linspace(start=space['low'], + stop=space['high'], + num=num_values_from_inf_range, + endpoint=False) elif type(space) in [numpy.ndarray, list, tuple]: # list/tuple/numpy.ndarray # Convert all to list @@ -727,22 +591,18 @@ def unpack_gene_space(self, size=1)[0] gene_space_unpacked[space_idx][idx] = random_value - if self.gene_type_single == True: # self.gene_type_single - # Change the data type. - gene_space_unpacked[space_idx] = numpy.array(gene_space_unpacked[space_idx], - dtype=self.gene_type[0]) - if not self.gene_type[1] is None: - # Round the values for float (non-int) data types. - gene_space_unpacked[space_idx] = numpy.round(gene_space_unpacked[space_idx], - self.gene_type[1]) + if self.gene_type_single == True: + dtype = self.gene_type else: - # Change the data type. - gene_space_unpacked[space_idx] = numpy.array(gene_space_unpacked[space_idx], - self.gene_type[space_idx][0]) - if not self.gene_type[space_idx][1] is None: - # Round the values for float (non-int) data types. - gene_space_unpacked[space_idx] = numpy.round(gene_space_unpacked[space_idx], - self.gene_type[space_idx][1]) + dtype = self.gene_type[gene_idx] + + # Change the data type. + gene_space_unpacked[space_idx] = numpy.array(gene_space_unpacked[space_idx], + dtype=dtype[0]) + if not dtype[1] is None: + # Round the values for float (non-int) data types. + gene_space_unpacked[space_idx] = numpy.round(gene_space_unpacked[space_idx], + dtype[1]) return gene_space_unpacked From f492bb3787a3b776205f51d325476d416f5c1218 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 8 Dec 2024 17:18:21 -0500 Subject: [PATCH 08/79] Replace gene_idx by space_idx --- pygad/helper/unique.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 4187b5f..daf643c 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -556,7 +556,7 @@ def unpack_gene_space(self, if self.gene_type_single == True: dtype = self.gene_type else: - dtype = self.gene_type[gene_idx] + dtype = self.gene_type[space_idx] if dtype[0] in pygad.GA.supported_int_types: if 'step' in space.keys(): @@ -594,7 +594,7 @@ def unpack_gene_space(self, if self.gene_type_single == True: dtype = self.gene_type else: - dtype = self.gene_type[gene_idx] + dtype = self.gene_type[space_idx] # Change the data type. gene_space_unpacked[space_idx] = numpy.array(gene_space_unpacked[space_idx], From c6949e1c94d4571368f9bea6d8236042d403d9ea Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 8 Dec 2024 18:14:52 -0500 Subject: [PATCH 09/79] Refactor code to remove duplicate sections --- pygad/helper/unique.py | 212 +++++++++++++++++++++++++++-------------- 1 file changed, 140 insertions(+), 72 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index daf643c..8eb0505 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -25,7 +25,7 @@ def solve_duplicate_genes_randomly(self, max_val (int): The maximum value of the range to sample a number randomly. mutation_by_replacement (bool): Indicates if mutation is performed by replacement. gene_type (type): The data type of the gene (e.g., int, float). - num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. + num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. Only works for floating-point gene types. Returns: tuple: @@ -42,53 +42,48 @@ def solve_duplicate_genes_randomly(self, num_unsolved_duplicates = 0 if len(not_unique_indices) > 0: for duplicate_index in not_unique_indices: - for trial_index in range(num_trials): - if self.gene_type_single == True: - dtype = gene_type - else: - dtype = gene_type[duplicate_index] - - if dtype[0] in pygad.GA.supported_int_types: - temp_val = self.unique_int_gene_from_range(solution=new_solution, - gene_index=duplicate_index, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=mutation_by_replacement, - gene_type=gene_type) - else: - temp_val = numpy.random.uniform(low=min_val, - high=max_val, - size=1)[0] - if mutation_by_replacement: + if self.gene_type_single == True: + dtype = gene_type + else: + dtype = gene_type[duplicate_index] + + if dtype[0] in pygad.GA.supported_int_types: + temp_val = self.unique_int_gene_from_range(solution=new_solution, + gene_index=duplicate_index, + min_val=min_val, + max_val=max_val, + mutation_by_replacement=mutation_by_replacement, + gene_type=gene_type) + else: + temp_val = self.unique_float_gene_from_range(solution=new_solution, + gene_index=duplicate_index, + min_val=min_val, + max_val=max_val, + mutation_by_replacement=mutation_by_replacement, + gene_type=gene_type, + num_trials=num_trials) + """ + temp_val = numpy.random.uniform(low=min_val, + high=max_val, + size=1)[0] + if mutation_by_replacement: pass - else: + else: temp_val = new_solution[duplicate_index] + temp_val + """ + + if temp_val in new_solution: + num_unsolved_duplicates = num_unsolved_duplicates + 1 + 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.") + else: + # Unique gene value found. + new_solution[duplicate_index] = temp_val + + # Update the list of duplicate indices after each iteration. + _, unique_gene_indices = numpy.unique(new_solution, return_index=True) + not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) + # self.logger.info("not_unique_indices INSIDE", not_unique_indices) - # Similar to the round_genes() method in the pygad module, - # Create a round_gene() method to round a single gene. - if not dtype[1] is None: - temp_val = numpy.round(dtype[0](temp_val), - dtype[1]) - else: - temp_val = dtype[0](temp_val) - - if temp_val in new_solution and trial_index == (num_trials - 1): - num_unsolved_duplicates = num_unsolved_duplicates + 1 - 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.") - elif temp_val in new_solution: - # Keep trying in the other remaining trials. - continue - else: - # Unique gene value found. - new_solution[duplicate_index] = temp_val - break - - # TODO Move this code outside the loops. - # Update the list of duplicate indices after each iteration. - _, unique_gene_indices = numpy.unique(new_solution, return_index=True) - not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) - # self.logger.info("not_unique_indices INSIDE", not_unique_indices) - return new_solution, not_unique_indices, num_unsolved_duplicates def solve_duplicate_genes_by_space(self, @@ -167,14 +162,14 @@ def unique_int_gene_from_range(self, Args: solution (list): A solution containing genes, potentially with duplicate values. gene_index (int): The index of the gene for which to find a unique value. - min_val (int): The minimum value of the range to sample a number randomly. - max_val (int): The maximum value of the range to sample a number randomly. + min_val (int): The minimum value of the range to sample an integer randomly. + max_val (int): The maximum value of the range to sample an integer randomly. mutation_by_replacement (bool): Indicates if mutation is performed by replacement. - gene_type (type): The data type of the gene (e.g., int, float). + gene_type (type): The data type of the gene (e.g., int, int8, uint16, etc). step (int, optional): The step size for generating candidate values. Defaults to 1. Returns: - int: The new value of the gene. If no unique value can be found, the original gene value is returned. + int: The new integer value of the gene. If no unique value can be found, the original gene value is returned. """ # The gene_type is of the form [type, precision] @@ -194,22 +189,86 @@ def unique_int_gene_from_range(self, else: all_gene_values = all_gene_values + solution[gene_index] - # After adding solution[gene_index] to the list, we have to change the data type again. - # TODO: The gene data type is converted twine. One above and one here. - all_gene_values = numpy.asarray(all_gene_values, - dtype) + # After adding solution[gene_index] to the list, we have to change the data type again. + all_gene_values = numpy.asarray(all_gene_values, + dtype) values_to_select_from = list(set(list(all_gene_values)) - set(solution)) if len(values_to_select_from) == 0: # If there are no values, then keep the current gene value. - if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but there is no enough values to prevent duplicates.") selected_value = solution[gene_index] else: selected_value = random.choice(values_to_select_from) + + selected_value = dtype[0](selected_value) return selected_value + def unique_float_gene_from_range(self, + solution, + gene_index, + min_val, + max_val, + mutation_by_replacement, + gene_type, + num_trials=10): + + """ + Finds a unique floating-point value for a specific gene in a solution. + + Args: + solution (list): A solution containing genes, potentially with duplicate values. + gene_index (int): The index of the gene for which to find a unique value. + min_val (int): The minimum value of the range to sample a floating-point number randomly. + max_val (int): The maximum value of the range to sample a floating-point number randomly. + mutation_by_replacement (bool): Indicates if mutation is performed by replacement. + gene_type (type): The data type of the gene (e.g., float, float16, float32, etc). + num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. + + Returns: + int: The new floating-point value of the gene. If no unique value can be found, the original gene value is returned. + """ + + # The gene_type is of the form [type, precision] + dtype = gene_type + + for trial_index in range(num_trials): + temp_val = numpy.random.uniform(low=min_val, + high=max_val, + size=1)[0] + + # If mutation is by replacement, do not add the current gene value into the list. + # This is to avoid replacing the value by itself again. We are doing nothing in this case. + if mutation_by_replacement: + pass + else: + temp_val = temp_val + solution[gene_index] + + if not dtype[1] is None: + # Precision is available and we have to round the number. + # Convert the data type and round the number. + temp_val = numpy.round(dtype[0](temp_val), + dtype[1]) + else: + # There is no precision and rounding the number is not needed. The type is [type, None] + # Just convert the data type. + temp_val = dtype[0](temp_val) + + if temp_val in solution and trial_index == (num_trials - 1): + # If there are no values, then keep the current gene value. + if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but cannot find a value to prevent duplicates.") + selected_value = solution[gene_index] + elif temp_val in solution: + # Keep trying in the other remaining trials. + continue + else: + # Unique gene value found. + selected_value = temp_val + break + + return selected_value + def unique_genes_by_space(self, new_solution, gene_type, @@ -225,7 +284,7 @@ def unique_genes_by_space(self, new_solution (list): A solution containing genes with duplicate values. gene_type (type): The data type of the gene (e.g., int, float). not_unique_indices (list): The indices of genes with duplicate values. - num_trials (int): The maximum number of attempts to resolve duplicates for each gene. + num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. Returns: tuple: @@ -236,22 +295,18 @@ def unique_genes_by_space(self, num_unsolved_duplicates = 0 for duplicate_index in not_unique_indices: - for trial_index in range(num_trials): - temp_val = self.unique_gene_by_space(solution=new_solution, - gene_idx=duplicate_index, - gene_type=gene_type, - build_initial_pop=build_initial_pop) - - if temp_val in new_solution and trial_index == (num_trials - 1): - # self.logger.info("temp_val, duplicate_index", temp_val, duplicate_index, new_solution) - num_unsolved_duplicates = num_unsolved_duplicates + 1 - if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {new_solution[duplicate_index]}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.") - elif temp_val in new_solution: - continue - else: - new_solution[duplicate_index] = temp_val - # self.logger.info("SOLVED", duplicate_index) - break + temp_val = self.unique_gene_by_space(solution=new_solution, + gene_idx=duplicate_index, + gene_type=gene_type, + build_initial_pop=build_initial_pop, + num_trials=num_trials) + + if temp_val in new_solution: + # self.logger.info("temp_val, duplicate_index", temp_val, duplicate_index, new_solution) + num_unsolved_duplicates = num_unsolved_duplicates + 1 + if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {new_solution[duplicate_index]}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.") + else: + new_solution[duplicate_index] = temp_val # Update the list of duplicate indices after each iteration. _, unique_gene_indices = numpy.unique(new_solution, return_index=True) @@ -264,7 +319,8 @@ def unique_gene_by_space(self, solution, gene_idx, gene_type, - build_initial_pop=False): + build_initial_pop=False, + num_trials=10): """ Returns a unique value for a specific gene based on its value space to resolve duplicates. @@ -273,6 +329,7 @@ def unique_gene_by_space(self, solution (list): A solution containing genes with duplicate values. gene_idx (int): The index of the gene that has a duplicate value. gene_type (type): The data type of the gene (e.g., int, float). + num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. Returns: Any: A unique value for the gene, if one exists; otherwise, the original gene value. """ @@ -320,9 +377,20 @@ def unique_gene_by_space(self, low = self.random_mutation_min_val high = self.random_mutation_max_val + """ value_from_space = numpy.random.uniform(low=low, high=high, size=1)[0] + """ + + value_from_space = self.unique_float_gene_from_range(solution=solution, + gene_index=gene_idx, + min_val=low, + max_val=high, + mutation_by_replacement=True, + gene_type=dtype, + num_trials=num_trials) + elif type(curr_gene_space) is dict: if self.gene_type_single == True: From b760c3ea506dc1ae0604c72de8aaeddc91b70ccd Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 8 Dec 2024 18:25:12 -0500 Subject: [PATCH 10/79] Fix a bug --- pygad/helper/unique.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 8eb0505..3da2d71 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -173,14 +173,14 @@ def unique_int_gene_from_range(self, """ # The gene_type is of the form [type, precision] - dtype = gene_type[0] + dtype = gene_type # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. all_gene_values = numpy.asarray(numpy.arange(min_val, max_val, step), - dtype=dtype) + dtype=dtype[0]) # If mutation is by replacement, do not add the current gene value into the list. # This is to avoid replacing the value by itself again. We are doing nothing in this case. @@ -191,7 +191,7 @@ def unique_int_gene_from_range(self, # After adding solution[gene_index] to the list, we have to change the data type again. all_gene_values = numpy.asarray(all_gene_values, - dtype) + dtype[0]) values_to_select_from = list(set(list(all_gene_values)) - set(solution)) From 01eee8503b2251cda46b6744341b12045cf8bfe8 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 8 Dec 2024 19:25:51 -0500 Subject: [PATCH 11/79] Return first element of numpy.random.choice() --- pygad/helper/unique.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 3da2d71..c9b097f 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -282,7 +282,7 @@ def unique_genes_by_space(self, Args: new_solution (list): A solution containing genes with duplicate values. - gene_type (type): The data type of the gene (e.g., int, float). + gene_type (type): The data type of the all the genes (e.g., int, float). not_unique_indices (list): The indices of genes with duplicate values. num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. @@ -417,7 +417,7 @@ def unique_gene_by_space(self, value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], stop=curr_gene_space['high'], step=curr_gene_space['step']), - size=1) + size=1)[0] else: value_from_space = numpy.random.uniform(low=curr_gene_space['low'], high=curr_gene_space['high'], @@ -479,7 +479,7 @@ def unique_gene_by_space(self, value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], stop=self.gene_space['high'], step=self.gene_space['step']), - size=1) + size=1)[0] else: value_from_space = numpy.random.uniform(low=self.gene_space['low'], high=self.gene_space['high'], From 516ac20ba99504432f206086cc9651d1b0174c5e Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Mon, 9 Dec 2024 12:06:33 -0500 Subject: [PATCH 12/79] Read the requirements from the .txt file --- setup.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b9f88de..bbef371 100644 --- a/setup.py +++ b/setup.py @@ -3,11 +3,15 @@ with open("README.md", "r") as fh: long_description = fh.read() +# Read the requirements from the requirements.txt file +with open("requirements.txt", "r") as f: + requirements = f.read().splitlines() + setuptools.setup( name="pygad", version="3.3.1", author="Ahmed Fawzy Gad", - install_requires=["numpy", "matplotlib", "cloudpickle",], + install_requires=requirements, author_email="ahmed.f.gad@gmail.com", description="PyGAD: A Python Library for Building the Genetic Algorithm and Training Machine Learning Algoithms (Keras & PyTorch).", long_description=long_description, From 1a0c67fcc181cbfa5128d5d395676776e28cdf35 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Mon, 9 Dec 2024 12:12:44 -0500 Subject: [PATCH 13/79] Read the requirements from the .txt file --- setup.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index bbef371..9e79e05 100644 --- a/setup.py +++ b/setup.py @@ -1,10 +1,15 @@ import setuptools +import os with open("README.md", "r") as fh: long_description = fh.read() +# Dynamically read requirements.txt +this_dir = os.path.abspath(os.path.dirname(__file__)) +requirements_path = os.path.join(this_dir, "requirements.txt") + # Read the requirements from the requirements.txt file -with open("requirements.txt", "r") as f: +with open(requirements_path, "r") as f: requirements = f.read().splitlines() setuptools.setup( From 138333ade96f2baf40face76e7c6e69721d5221c Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Mon, 9 Dec 2024 12:15:29 -0500 Subject: [PATCH 14/79] Hardcode the requirements --- setup.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/setup.py b/setup.py index 9e79e05..b9f88de 100644 --- a/setup.py +++ b/setup.py @@ -1,22 +1,13 @@ import setuptools -import os with open("README.md", "r") as fh: long_description = fh.read() -# Dynamically read requirements.txt -this_dir = os.path.abspath(os.path.dirname(__file__)) -requirements_path = os.path.join(this_dir, "requirements.txt") - -# Read the requirements from the requirements.txt file -with open(requirements_path, "r") as f: - requirements = f.read().splitlines() - setuptools.setup( name="pygad", version="3.3.1", author="Ahmed Fawzy Gad", - install_requires=requirements, + install_requires=["numpy", "matplotlib", "cloudpickle",], author_email="ahmed.f.gad@gmail.com", description="PyGAD: A Python Library for Building the Genetic Algorithm and Training Machine Learning Algoithms (Keras & PyTorch).", long_description=long_description, From 2aa059d5c6532374a53b5e60b6bc51b1864aaac1 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Mon, 9 Dec 2024 12:31:14 -0500 Subject: [PATCH 15/79] Install the requirements in GitHub actions --- .github/workflows/main_py310.yml | 5 +++++ .github/workflows/main_py311.yml | 5 +++++ .github/workflows/main_py312.yml | 5 +++++ .github/workflows/main_py37.yml | 5 +++++ .github/workflows/main_py38.yml | 5 +++++ .github/workflows/main_py39.yml | 5 +++++ 6 files changed, 30 insertions(+) diff --git a/.github/workflows/main_py310.yml b/.github/workflows/main_py310.yml index 508dcf4..9602f17 100644 --- a/.github/workflows/main_py310.yml +++ b/.github/workflows/main_py310.yml @@ -20,6 +20,11 @@ jobs: with: python-version: '3.10' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build PyGAD from the Repository run: | python3 -m pip install --upgrade build diff --git a/.github/workflows/main_py311.yml b/.github/workflows/main_py311.yml index ce5a196..d468243 100644 --- a/.github/workflows/main_py311.yml +++ b/.github/workflows/main_py311.yml @@ -20,6 +20,11 @@ jobs: with: python-version: '3.11' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build PyGAD from the Repository run: | python3 -m pip install --upgrade build diff --git a/.github/workflows/main_py312.yml b/.github/workflows/main_py312.yml index 560d501..87bd648 100644 --- a/.github/workflows/main_py312.yml +++ b/.github/workflows/main_py312.yml @@ -28,6 +28,11 @@ jobs: with: python-version: '3.12.0-beta.2' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build PyGAD from the Repository run: | python3 -m pip install --upgrade build diff --git a/.github/workflows/main_py37.yml b/.github/workflows/main_py37.yml index 6268472..037086e 100644 --- a/.github/workflows/main_py37.yml +++ b/.github/workflows/main_py37.yml @@ -20,6 +20,11 @@ jobs: with: python-version: '3.7' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build PyGAD from the Repository run: | python3 -m pip install --upgrade build diff --git a/.github/workflows/main_py38.yml b/.github/workflows/main_py38.yml index d8902dc..602f917 100644 --- a/.github/workflows/main_py38.yml +++ b/.github/workflows/main_py38.yml @@ -20,6 +20,11 @@ jobs: with: python-version: '3.8' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build PyGAD from the Repository run: | python3 -m pip install --upgrade build diff --git a/.github/workflows/main_py39.yml b/.github/workflows/main_py39.yml index e4e0ef1..c6b61fc 100644 --- a/.github/workflows/main_py39.yml +++ b/.github/workflows/main_py39.yml @@ -20,6 +20,11 @@ jobs: with: python-version: '3.9' + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build PyGAD from the Repository run: | python3 -m pip install --upgrade build From 9910cbd43e79b57a82f3da2e7b87f3c9caf22b99 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 10 Dec 2024 09:59:09 -0500 Subject: [PATCH 16/79] Create scorecard.yml --- .github/workflows/scorecard.yml | 73 +++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 .github/workflows/scorecard.yml diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..30d789a --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,73 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '42 5 * * 2' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action?tab=readme-ov-file#authentication-with-fine-grained-pat-optional. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard (optional). + # Commenting out will disable upload of results to your repo's Code Scanning dashboard + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@v3 + with: + sarif_file: results.sarif From fca6058cb712a24b1f761febe0a56bbcafcd3154 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 10 Dec 2024 10:01:03 -0500 Subject: [PATCH 17/79] Reformat docstring --- pygad/utils/crossover.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/pygad/utils/crossover.py b/pygad/utils/crossover.py index e6c489e..d7cd86e 100644 --- a/pygad/utils/crossover.py +++ b/pygad/utils/crossover.py @@ -13,11 +13,15 @@ def __init__(): def single_point_crossover(self, parents, offspring_size): """ - Applies the single-point crossover. It selects a point randomly at which crossover takes place between the pairs of parents. - It accepts 2 parameters: - -parents: The parents to mate for producing the offspring. - -offspring_size: The size of the offspring to produce. - It returns an array the produced offspring. + Applies single-point crossover between pairs of parents. + This function selects a random point at which crossover occurs between the parents, generating offspring. + + Parameters: + parents (array-like): The parents to mate for producing the offspring. + offspring_size (int): The number of offspring to produce. + + Returns: + array-like: An array containing the produced offspring. """ if self.gene_type_single == True: From a9d09b41dbb340dd3ae597acb9cebc721ca0dcf6 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 10 Dec 2024 10:02:56 -0500 Subject: [PATCH 18/79] Add OSSF and StackOverFlow badges --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index cd84391..e2ede3a 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Check documentation of the [PyGAD](https://pygad.readthedocs.io/en/latest). -[![Downloads](https://pepy.tech/badge/pygad)](https://pepy.tech/project/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) +[![Downloads](https://pepy.tech/badge/pygad)](https://pepy.tech/project/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)]( +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) ![PYGAD-LOGO](https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png) From bb26d3c39f859a068c8f83e398f814794c5ef300 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 10 Dec 2024 10:06:32 -0500 Subject: [PATCH 19/79] Add spaces between badges --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e2ede3a..0a39235 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ Check documentation of the [PyGAD](https://pygad.readthedocs.io/en/latest). -[![Downloads](https://pepy.tech/badge/pygad)](https://pepy.tech/project/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)]( -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) +[![Downloads](https://pepy.tech/badge/pygad)](https://pepy.tech/project/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)]( +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) ![PYGAD-LOGO](https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png) From 55776d2bae3a36d041d0552102f64dcae0d88c3c Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 10 Dec 2024 10:08:52 -0500 Subject: [PATCH 20/79] Add Conda downloads badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 0a39235..c3c20cf 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,8 @@ Check documentation of the [PyGAD](https://pygad.readthedocs.io/en/latest). -[![Downloads](https://pepy.tech/badge/pygad)](https://pepy.tech/project/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)]( +[![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)]( +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)]( 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) ![PYGAD-LOGO](https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png) From a49d9089702d107b86d431fe091c796286054f56 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 10 Dec 2024 10:14:41 -0500 Subject: [PATCH 21/79] Add paper DOI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c3c20cf..fde5c6d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Check documentation of the [PyGAD](https://pygad.readthedocs.io/en/latest). [![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)]( 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)]( -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) +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) ![PYGAD-LOGO](https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png) From 27812998ab2dce951ecaee418748ec5bb09f9d4d Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 10 Dec 2024 10:18:17 -0500 Subject: [PATCH 22/79] Add Conda downloads badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fde5c6d..75cd596 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Check documentation of the [PyGAD](https://pygad.readthedocs.io/en/latest). -[![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)]( +[![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)]( 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)]( 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) From 67ff07f174678be1d4c352e01f122d4cfc7945d3 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 12 Dec 2024 16:56:22 -0500 Subject: [PATCH 23/79] Add paper DOI badge --- pygad/helper/unique.py | 383 ++++++++++++++++++++--------------------- 1 file changed, 187 insertions(+), 196 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index c9b097f..8b523f3 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -16,75 +16,66 @@ def solve_duplicate_genes_randomly(self, mutation_by_replacement, gene_type, num_trials=10): - """ - Resolves duplicates in a solution by randomly selecting new values for the duplicate genes. - - Args: - solution (list): A solution containing genes, potentially with duplicate values. - min_val (int): The minimum value of the range to sample a number randomly. - max_val (int): The maximum value of the range to sample a number randomly. - mutation_by_replacement (bool): Indicates if mutation is performed by replacement. - gene_type (type): The data type of the gene (e.g., int, float). - num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. Only works for floating-point gene types. + """ + Resolves duplicates in a solution by randomly selecting new values for the duplicate genes. - Returns: - tuple: - list: The updated solution after attempting to resolve duplicates. If no duplicates are resolved, the solution remains unchanged. - list: The indices of genes that still have duplicate values. - int: The number of duplicates that could not be resolved. - """ + Args: + solution (list): A solution containing genes, potentially with duplicate values. + min_val (int): The minimum value of the range to sample a number randomly. + max_val (int): The maximum value of the range to sample a number randomly. + mutation_by_replacement (bool): Indicates if mutation is performed by replacement. + gene_type (type): The data type of the gene (e.g., int, float). + num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. Only works for floating-point gene types. - new_solution = solution.copy() - - _, unique_gene_indices = numpy.unique(solution, return_index=True) - not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) + Returns: + tuple: + list: The updated solution after attempting to resolve duplicates. If no duplicates are resolved, the solution remains unchanged. + list: The indices of genes that still have duplicate values. + int: The number of duplicates that could not be resolved. + """ - num_unsolved_duplicates = 0 - if len(not_unique_indices) > 0: - for duplicate_index in not_unique_indices: - if self.gene_type_single == True: - dtype = gene_type - else: - dtype = gene_type[duplicate_index] + new_solution = solution.copy() - if dtype[0] in pygad.GA.supported_int_types: - temp_val = self.unique_int_gene_from_range(solution=new_solution, - gene_index=duplicate_index, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=mutation_by_replacement, - gene_type=gene_type) - else: - temp_val = self.unique_float_gene_from_range(solution=new_solution, - gene_index=duplicate_index, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=mutation_by_replacement, - gene_type=gene_type, - num_trials=num_trials) - """ - temp_val = numpy.random.uniform(low=min_val, - high=max_val, - size=1)[0] - if mutation_by_replacement: - pass - else: - temp_val = new_solution[duplicate_index] + temp_val - """ + _, unique_gene_indices = numpy.unique(solution, return_index=True) + not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) - if temp_val in new_solution: - num_unsolved_duplicates = num_unsolved_duplicates + 1 - 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.") - else: - # Unique gene value found. - new_solution[duplicate_index] = temp_val + num_unsolved_duplicates = 0 + if len(not_unique_indices) > 0: + for duplicate_index in not_unique_indices: + if self.gene_type_single == True: + dtype = gene_type + else: + dtype = gene_type[duplicate_index] + + if dtype[0] in pygad.GA.supported_int_types: + temp_val = self.unique_int_gene_from_range(solution=new_solution, + gene_index=duplicate_index, + min_val=min_val, + max_val=max_val, + mutation_by_replacement=mutation_by_replacement, + gene_type=gene_type) + else: + temp_val = self.unique_float_gene_from_range(solution=new_solution, + gene_index=duplicate_index, + min_val=min_val, + max_val=max_val, + mutation_by_replacement=mutation_by_replacement, + gene_type=gene_type, + num_trials=num_trials) + + if temp_val in new_solution: + num_unsolved_duplicates = num_unsolved_duplicates + 1 + 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.") + else: + # Unique gene value found. + new_solution[duplicate_index] = temp_val - # Update the list of duplicate indices after each iteration. - _, unique_gene_indices = numpy.unique(new_solution, return_index=True) - not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) - # self.logger.info("not_unique_indices INSIDE", not_unique_indices) + # Update the list of duplicate indices after each iteration. + _, unique_gene_indices = numpy.unique(new_solution, return_index=True) + not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) + # self.logger.info("not_unique_indices INSIDE", not_unique_indices) - return new_solution, not_unique_indices, num_unsolved_duplicates + return new_solution, not_unique_indices, num_unsolved_duplicates def solve_duplicate_genes_by_space(self, solution, @@ -92,60 +83,60 @@ def solve_duplicate_genes_by_space(self, num_trials=10, build_initial_pop=False): - """ - Resolves duplicates in a solution by selecting new values for the duplicate genes from the gene space. + """ + Resolves duplicates in a solution by selecting new values for the duplicate genes from the gene space. - Args: - solution (list): A solution containing genes, potentially with duplicate values. - gene_type (type): The data type of the gene (e.g., int, float). - num_trials (int): The maximum number of attempts to resolve duplicates by selecting values from the gene space. + Args: + solution (list): A solution containing genes, potentially with duplicate values. + gene_type (type): The data type of the gene (e.g., int, float). + num_trials (int): The maximum number of attempts to resolve duplicates by selecting values from the gene space. - Returns: - tuple: - list: The updated solution after attempting to resolve duplicates. If no duplicates are resolved, the solution remains unchanged. - list: The indices of genes that still have duplicate values. - int: The number of duplicates that could not be resolved. - """ + Returns: + tuple: + list: The updated solution after attempting to resolve duplicates. If no duplicates are resolved, the solution remains unchanged. + list: The indices of genes that still have duplicate values. + int: The number of duplicates that could not be resolved. + """ - new_solution = solution.copy() - - _, unique_gene_indices = numpy.unique(solution, return_index=True) - not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) - # self.logger.info("not_unique_indices OUTSIDE", not_unique_indices) - - # First try to solve the duplicates. - # For a solution like [3 2 0 0], the indices of the 2 duplicating genes are 2 and 3. - # The next call to the find_unique_value() method tries to change the value of the gene with index 3 to solve the duplicate. - if len(not_unique_indices) > 0: - new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(new_solution=new_solution, - gene_type=gene_type, - not_unique_indices=not_unique_indices, - num_trials=10, - build_initial_pop=build_initial_pop) - else: - return new_solution, not_unique_indices, len(not_unique_indices) + new_solution = solution.copy() + + _, unique_gene_indices = numpy.unique(solution, return_index=True) + not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) + # self.logger.info("not_unique_indices OUTSIDE", not_unique_indices) + + # First try to solve the duplicates. + # For a solution like [3 2 0 0], the indices of the 2 duplicating genes are 2 and 3. + # The next call to the find_unique_value() method tries to change the value of the gene with index 3 to solve the duplicate. + if len(not_unique_indices) > 0: + new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(new_solution=new_solution, + gene_type=gene_type, + not_unique_indices=not_unique_indices, + num_trials=10, + build_initial_pop=build_initial_pop) + else: + return new_solution, not_unique_indices, len(not_unique_indices) - # Do another try if there exist duplicate genes. - # If there are no possible values for the gene 3 with index 3 to solve the duplicate, try to change the value of the other gene with index 2. - if len(not_unique_indices) > 0: - not_unique_indices = set(numpy.where(new_solution == new_solution[list(not_unique_indices)[0]])[0]) - set([list(not_unique_indices)[0]]) - new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(new_solution=new_solution, - gene_type=gene_type, - not_unique_indices=not_unique_indices, - num_trials=10, - build_initial_pop=build_initial_pop) - else: - # DEEP-DUPLICATE-REMOVAL-NEEDED - # Search by this phrase to find where deep duplicates removal should be applied. - - # If there exist duplicate genes, then changing either of the 2 duplicating genes (with indices 2 and 3) will not solve the problem. - # This problem can be solved by randomly changing one of the non-duplicating genes that may make a room for a unique value in one the 2 duplicating genes. - # For example, if gene_space=[[3, 0, 1], [4, 1, 2], [0, 2], [3, 2, 0]] and the solution is [3 2 0 0], then the values of the last 2 genes duplicate. - # There are no possible changes in the last 2 genes to solve the problem. But it could be solved by changing the second gene from 2 to 4. - # As a result, any of the last 2 genes can take the value 2 and solve the duplicates. - return new_solution, not_unique_indices, len(not_unique_indices) + # Do another try if there exist duplicate genes. + # If there are no possible values for the gene 3 with index 3 to solve the duplicate, try to change the value of the other gene with index 2. + if len(not_unique_indices) > 0: + not_unique_indices = set(numpy.where(new_solution == new_solution[list(not_unique_indices)[0]])[0]) - set([list(not_unique_indices)[0]]) + new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(new_solution=new_solution, + gene_type=gene_type, + not_unique_indices=not_unique_indices, + num_trials=10, + build_initial_pop=build_initial_pop) + else: + # DEEP-DUPLICATE-REMOVAL-NEEDED + # Search by this phrase to find where deep duplicates removal should be applied. + + # If there exist duplicate genes, then changing either of the 2 duplicating genes (with indices 2 and 3) will not solve the problem. + # This problem can be solved by randomly changing one of the non-duplicating genes that may make a room for a unique value in one the 2 duplicating genes. + # For example, if gene_space=[[3, 0, 1], [4, 1, 2], [0, 2], [3, 2, 0]] and the solution is [3 2 0 0], then the values of the last 2 genes duplicate. + # There are no possible changes in the last 2 genes to solve the problem. But it could be solved by changing the second gene from 2 to 4. + # As a result, any of the last 2 genes can take the value 2 and solve the duplicates. + return new_solution, not_unique_indices, len(not_unique_indices) - return new_solution, not_unique_indices, num_unsolved_duplicates + return new_solution, not_unique_indices, num_unsolved_duplicates def unique_int_gene_from_range(self, solution, @@ -156,54 +147,54 @@ def unique_int_gene_from_range(self, gene_type, step=1): - """ - Finds a unique integer value for a specific gene in a solution. + """ + Finds a unique integer value for a specific gene in a solution. - Args: - solution (list): A solution containing genes, potentially with duplicate values. - gene_index (int): The index of the gene for which to find a unique value. - min_val (int): The minimum value of the range to sample an integer randomly. - max_val (int): The maximum value of the range to sample an integer randomly. - mutation_by_replacement (bool): Indicates if mutation is performed by replacement. - gene_type (type): The data type of the gene (e.g., int, int8, uint16, etc). - step (int, optional): The step size for generating candidate values. Defaults to 1. + Args: + solution (list): A solution containing genes, potentially with duplicate values. + gene_index (int): The index of the gene for which to find a unique value. + min_val (int): The minimum value of the range to sample an integer randomly. + max_val (int): The maximum value of the range to sample an integer randomly. + mutation_by_replacement (bool): Indicates if mutation is performed by replacement. + gene_type (type): The data type of the gene (e.g., int, int8, uint16, etc). + step (int, optional): The step size for generating candidate values. Defaults to 1. - Returns: - int: The new integer value of the gene. If no unique value can be found, the original gene value is returned. - """ + Returns: + int: The new integer value of the gene. If no unique value can be found, the original gene value is returned. + """ - # The gene_type is of the form [type, precision] - dtype = gene_type + # The gene_type is of the form [type, precision] + dtype = gene_type - # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) - # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. - all_gene_values = numpy.asarray(numpy.arange(min_val, - max_val, - step), - dtype=dtype[0]) + # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) + # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. + all_gene_values = numpy.asarray(numpy.arange(min_val, + max_val, + step), + dtype=dtype[0]) - # If mutation is by replacement, do not add the current gene value into the list. - # This is to avoid replacing the value by itself again. We are doing nothing in this case. - if mutation_by_replacement: - pass - else: - all_gene_values = all_gene_values + solution[gene_index] + # If mutation is by replacement, do not add the current gene value into the list. + # This is to avoid replacing the value by itself again. We are doing nothing in this case. + if mutation_by_replacement: + pass + else: + all_gene_values = all_gene_values + solution[gene_index] - # After adding solution[gene_index] to the list, we have to change the data type again. - all_gene_values = numpy.asarray(all_gene_values, - dtype[0]) + # After adding solution[gene_index] to the list, we have to change the data type again. + all_gene_values = numpy.asarray(all_gene_values, + dtype[0]) - values_to_select_from = list(set(list(all_gene_values)) - set(solution)) + values_to_select_from = list(set(list(all_gene_values)) - set(solution)) - if len(values_to_select_from) == 0: - # If there are no values, then keep the current gene value. - selected_value = solution[gene_index] - else: - selected_value = random.choice(values_to_select_from) + if len(values_to_select_from) == 0: + # If there are no values, then keep the current gene value. + selected_value = solution[gene_index] + else: + selected_value = random.choice(values_to_select_from) - selected_value = dtype[0](selected_value) + selected_value = dtype[0](selected_value) - return selected_value + return selected_value def unique_float_gene_from_range(self, solution, @@ -214,60 +205,60 @@ def unique_float_gene_from_range(self, gene_type, num_trials=10): - """ - Finds a unique floating-point value for a specific gene in a solution. + """ + Finds a unique floating-point value for a specific gene in a solution. - Args: - solution (list): A solution containing genes, potentially with duplicate values. - gene_index (int): The index of the gene for which to find a unique value. - min_val (int): The minimum value of the range to sample a floating-point number randomly. - max_val (int): The maximum value of the range to sample a floating-point number randomly. - mutation_by_replacement (bool): Indicates if mutation is performed by replacement. - gene_type (type): The data type of the gene (e.g., float, float16, float32, etc). - num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. + Args: + solution (list): A solution containing genes, potentially with duplicate values. + gene_index (int): The index of the gene for which to find a unique value. + min_val (int): The minimum value of the range to sample a floating-point number randomly. + max_val (int): The maximum value of the range to sample a floating-point number randomly. + mutation_by_replacement (bool): Indicates if mutation is performed by replacement. + gene_type (type): The data type of the gene (e.g., float, float16, float32, etc). + num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. - Returns: - int: The new floating-point value of the gene. If no unique value can be found, the original gene value is returned. - """ + Returns: + int: The new floating-point value of the gene. If no unique value can be found, the original gene value is returned. + """ - # The gene_type is of the form [type, precision] - dtype = gene_type + # The gene_type is of the form [type, precision] + dtype = gene_type - for trial_index in range(num_trials): - temp_val = numpy.random.uniform(low=min_val, - high=max_val, - size=1)[0] + for trial_index in range(num_trials): + temp_val = numpy.random.uniform(low=min_val, + high=max_val, + size=1)[0] - # If mutation is by replacement, do not add the current gene value into the list. - # This is to avoid replacing the value by itself again. We are doing nothing in this case. - if mutation_by_replacement: - pass - else: - temp_val = temp_val + solution[gene_index] + # If mutation is by replacement, do not add the current gene value into the list. + # This is to avoid replacing the value by itself again. We are doing nothing in this case. + if mutation_by_replacement: + pass + else: + temp_val = temp_val + solution[gene_index] - if not dtype[1] is None: - # Precision is available and we have to round the number. - # Convert the data type and round the number. - temp_val = numpy.round(dtype[0](temp_val), - dtype[1]) - else: - # There is no precision and rounding the number is not needed. The type is [type, None] - # Just convert the data type. - temp_val = dtype[0](temp_val) - - if temp_val in solution and trial_index == (num_trials - 1): - # If there are no values, then keep the current gene value. - if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but cannot find a value to prevent duplicates.") - selected_value = solution[gene_index] - elif temp_val in solution: - # Keep trying in the other remaining trials. - continue - else: - # Unique gene value found. - selected_value = temp_val - break + if not dtype[1] is None: + # Precision is available and we have to round the number. + # Convert the data type and round the number. + temp_val = numpy.round(dtype[0](temp_val), + dtype[1]) + else: + # There is no precision and rounding the number is not needed. The type is [type, None] + # Just convert the data type. + temp_val = dtype[0](temp_val) + + if temp_val in solution and trial_index == (num_trials - 1): + # If there are no values, then keep the current gene value. + if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but cannot find a value to prevent duplicates.") + selected_value = solution[gene_index] + elif temp_val in solution: + # Keep trying in the other remaining trials. + continue + else: + # Unique gene value found. + selected_value = temp_val + break - return selected_value + return selected_value def unique_genes_by_space(self, new_solution, From 82fa0f8e25cb2cc05cc047c2914f0b993257848e Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 7 Jan 2025 12:37:53 -0500 Subject: [PATCH 24/79] Create the plot_pareto_front_curve() method to plot the pareto front curve --- pygad/visualize/plot.py | 101 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/pygad/visualize/plot.py b/pygad/visualize/plot.py index 7dffc4b..3ddeaff 100644 --- a/pygad/visualize/plot.py +++ b/pygad/visualize/plot.py @@ -384,3 +384,104 @@ def plot_genes(self, matplotlib.pyplot.show() return fig + + def plot_pareto_front_curve(self, + title="Pareto Front Curve", + xlabel="Objective 1", + ylabel="Objective 2", + linewidth=3, + font_size=14, + label="Pareto Front", + color="#FF6347", + color_fitness="#4169E1", + grid=True, + alpha=0.7, + marker="o", + save_dir=None): + """ + Creates, shows, and returns the pareto front curve. Can only be used with multi-objective problems. + It only works with 2 objectives. + It also works only after completing at least 1 generation. If no generation is completed, an exception is raised. + + Accepts the following: + title: Figure title. + xlabel: Label on the X-axis. + ylabel: Label on the Y-axis. + linewidth: Line width of the plot. Defaults to 3. + font_size: Font size for the labels and title. Defaults to 14. + label: The label used for the legend. + color: Color of the plot. + color_fitness: Color of the fitness points. + grid: Either True or False to control the visibility of the grid. + alpha: The transparency of the pareto front curve. + marker: The marker of the fitness points. + save_dir: Directory to save the figure. + + Returns the figure. + """ + + if self.generations_completed < 1: + self.logger.error("The plot_pareto_front_curve() method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") + raise RuntimeError("The plot_pareto_front_curve() method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") + + if type(self.best_solutions_fitness[0]) in [list, tuple, numpy.ndarray] and len(self.best_solutions_fitness[0]) > 1: + # Multi-objective optimization problem. + if len(self.best_solutions_fitness[0]) == 2: + # Only 2 objectives. Proceed. + pass + else: + # More than 2 objectives. + self.logger.error(f"The plot_pareto_front_curve() method only supports 2 objectives but there are {self.best_solutions_fitness[0]} objectives.") + raise RuntimeError(f"The plot_pareto_front_curve() method only supports 2 objectives but there are {self.best_solutions_fitness[0]} objectives.") + else: + # Single-objective optimization problem. + self.logger.error("The plot_pareto_front_curve() method only works with multi-objective optimization problems.") + raise RuntimeError("The plot_pareto_front_curve() method only works with multi-objective optimization problems.") + + # Plot the pareto front curve. + remaining_set = list(zip(range(0, self.last_generation_fitness.shape[0]), self.last_generation_fitness)) + dominated_set, non_dominated_set = self.get_non_dominated_set(remaining_set) + + # Extract the fitness values (objective values) of the non-dominated solutions for plotting. + pareto_front_x = [self.last_generation_fitness[item[0]][0] for item in dominated_set] + pareto_front_y = [self.last_generation_fitness[item[0]][1] for item in dominated_set] + + # Sort the Pareto front solutions (optional but can make the plot cleaner) + sorted_pareto_front = sorted(zip(pareto_front_x, pareto_front_y)) + + # Plotting + fig = matplotlib.pyplot.figure() + # First, plot the scatter of all points (population) + all_points_x = [self.last_generation_fitness[i][0] for i in range(self.sol_per_pop)] + all_points_y = [self.last_generation_fitness[i][1] for i in range(self.sol_per_pop)] + matplotlib.pyplot.scatter(all_points_x, + all_points_y, + marker=marker, + color=color_fitness, + label='Fitness', + alpha=1.0) + + # Then, plot the Pareto front as a curve + pareto_front_x_sorted, pareto_front_y_sorted = zip(*sorted_pareto_front) + matplotlib.pyplot.plot(pareto_front_x_sorted, + pareto_front_y_sorted, + marker=marker, + label=label, + alpha=alpha, + color=color, + linewidth=linewidth) + + matplotlib.pyplot.title(title, fontsize=font_size) + matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) + matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) + matplotlib.pyplot.legend() + + matplotlib.pyplot.grid(grid) + + if not save_dir is None: + matplotlib.pyplot.savefig(fname=save_dir, + bbox_inches='tight') + + matplotlib.pyplot.show() + + return fig From 84559cea33e4d2b5ced27fb2f65646f6abf98291 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 7 Jan 2025 12:59:00 -0500 Subject: [PATCH 25/79] Document the plot_pareto_front_curve() method. --- docs/source/visualize.rst | 142 +++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 41 deletions(-) diff --git a/docs/source/visualize.rst b/docs/source/visualize.rst index 45dc1e4..629d4df 100644 --- a/docs/source/visualize.rst +++ b/docs/source/visualize.rst @@ -10,11 +10,15 @@ visualization in PyGAD. This section discusses the different options to visualize the results in PyGAD through these methods: -1. ``plot_fitness()``: Create plots for the fitness. +1. ``plot_fitness()``: Creates plots for the fitness. -2. ``plot_genes()``: Create plots for the genes. +2. ``plot_genes()``: Creates plots for the genes. -3. ``plot_new_solution_rate()``: Create plots for the new solution rate. +3. ``plot_new_solution_rate()``: Creates plots for the new solution + rate. + +4. ``plot_pareto_front_curve()``: Creates plots for the pareto front for + multi-objective problems. In the following code, the ``save_solutions`` flag is set to ``True`` which means all solutions are saved in the ``solutions`` attribute. The @@ -87,7 +91,7 @@ This method accepts the following parameters: 9. ``save_dir``: Directory to save the figure. -.. _plottypeplot: +.. _plottype=plot: ``plot_type="plot"`` ~~~~~~~~~~~~~~~~~~~~ @@ -101,10 +105,9 @@ line connecting the fitness values across all generations: ga_instance.plot_fitness() # ga_instance.plot_fitness(plot_type="plot") -.. image:: https://user-images.githubusercontent.com/16560492/122472609-d02f5280-cf8e-11eb-88a7-f9366ff6e7c6.png - :alt: +|image1| -.. _plottypescatter: +.. _plottype=scatter: ``plot_type="scatter"`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -117,10 +120,9 @@ these dots can be changed using the ``linewidth`` parameter. ga_instance.plot_fitness(plot_type="scatter") -.. image:: https://user-images.githubusercontent.com/16560492/122473159-75e2c180-cf8f-11eb-942d-31279b286dbd.png - :alt: +|image2| -.. _plottypebar: +.. _plottype=bar: ``plot_type="bar"`` ~~~~~~~~~~~~~~~~~~~ @@ -132,8 +134,7 @@ bar graph with each individual fitness represented as a bar. ga_instance.plot_fitness(plot_type="bar") -.. image:: https://user-images.githubusercontent.com/16560492/122473340-b7736c80-cf8f-11eb-89c5-4f7db3b653cc.png - :alt: +|image3| New Solution Rate ================= @@ -174,7 +175,7 @@ in the ``plot_fitness()`` method (it also have 3 possible values for 8. ``save_dir``: Directory to save the figure. -.. _plottypeplot-2: +.. _plottype=plot-2: ``plot_type="plot"`` ~~~~~~~~~~~~~~~~~~~~ @@ -192,10 +193,9 @@ first generation is always equal to the number of solutions in the population (i.e. the value assigned to the ``sol_per_pop`` parameter in the constructor of the ``pygad.GA`` class) which is 10 in this example. -.. image:: https://user-images.githubusercontent.com/16560492/122475815-3322e880-cf93-11eb-9648-bf66f823234b.png - :alt: +|image4| -.. _plottypescatter-2: +.. _plottype=scatter-2: ``plot_type="scatter"`` ~~~~~~~~~~~~~~~~~~~~~~~ @@ -207,10 +207,9 @@ The previous graph can be represented as scattered points by setting ga_instance.plot_new_solution_rate(plot_type="scatter") -.. image:: https://user-images.githubusercontent.com/16560492/122476108-adec0380-cf93-11eb-80ac-7588bf90492f.png - :alt: +|image5| -.. _plottypebar-2: +.. _plottype=bar-2: ``plot_type="bar"`` ~~~~~~~~~~~~~~~~~~~ @@ -222,8 +221,7 @@ vertical bar. ga_instance.plot_new_solution_rate(plot_type="bar") -.. image:: https://user-images.githubusercontent.com/16560492/122476173-c2c89700-cf93-11eb-9e77-d39737cd3a96.png - :alt: +|image6| Genes ===== @@ -307,13 +305,13 @@ solutions in the population or from just the best solutions. An exception is raised if: -- ``solutions="all"`` while ``save_solutions=False`` in the constructor - of the ``pygad.GA`` class. . +- ``solutions="all"`` while ``save_solutions=False`` in the constructor + of the ``pygad.GA`` class. . -- ``solutions="best"`` while ``save_best_solutions=False`` in the - constructor of the ``pygad.GA`` class. . +- ``solutions="best"`` while ``save_best_solutions=False`` in the + constructor of the ``pygad.GA`` class. . -.. _graphtypeplot: +.. _graphtype=plot: ``graph_type="plot"`` ~~~~~~~~~~~~~~~~~~~~~ @@ -322,7 +320,7 @@ When ``graph_type="plot"``, then the figure creates a normal graph where the relationship between the gene values and the generation numbers is represented as a continuous plot, scattered points, or bars. -.. _plottypeplot-3: +.. _plottype=plot-3: ``plot_type="plot"`` ^^^^^^^^^^^^^^^^^^^^ @@ -345,8 +343,7 @@ of the next graph) lasted for 83 generations. ga_instance.plot_genes(graph_type="plot", plot_type="plot") -.. image:: https://user-images.githubusercontent.com/16560492/122477158-4a62d580-cf95-11eb-8c93-9b6e74cb814c.png - :alt: +|image7| As the default value for the ``solutions`` parameter is ``"all"``, then the following method calls generate the same plot. @@ -365,7 +362,7 @@ the following method calls generate the same plot. plot_type="plot", solutions="all") -.. _plottypescatter-3: +.. _plottype=scatter-3: ``plot_type="scatter"`` ^^^^^^^^^^^^^^^^^^^^^^^ @@ -381,10 +378,9 @@ scatter plot. plot_type="scatter", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122477273-73836600-cf95-11eb-828f-f357c7b0f815.png - :alt: +|image8| -.. _plottypebar-3: +.. _plottype=bar-3: ``plot_type="bar"`` ^^^^^^^^^^^^^^^^^^^ @@ -397,10 +393,9 @@ scatter plot. plot_type="bar", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122477370-99106f80-cf95-11eb-8643-865b55e6b844.png - :alt: +|image9| -.. _graphtypeboxplot: +.. _graphtype=boxplot: ``graph_type="boxplot"`` ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -419,10 +414,9 @@ figure as the default value for the ``solutions`` parameter is ga_instance.plot_genes(graph_type="boxplot", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122479260-beeb4380-cf98-11eb-8f08-23707929b12c.png - :alt: +|image10| -.. _graphtypehistogram: +.. _graphtype=histogram: ``graph_type="histogram"`` ~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -442,8 +436,74 @@ figure as the default value for the ``solutions`` parameter is ga_instance.plot_genes(graph_type="histogram", solutions='all') -.. image:: https://user-images.githubusercontent.com/16560492/122477314-8007be80-cf95-11eb-9c95-da3f49204151.png - :alt: +|image11| All the previous figures can be created for only the best solutions by setting ``solutions="best"``. + +Pareto Front +============ + +.. _plotparetofrontcurve: + +``plot_pareto_front_curve()`` +----------------------------- + +The ``plot_pareto_front_curve()`` method creates the Pareto front curve +for multi-objective optimization problems. It creates, shows, and +returns a figure that shows the Pareto front curve and points +representing the fitness. It only works when 2 objectives are used. + +It works only after completing at least 1 generation. If no generation +is completed (at least 1), an exception is raised. + +This method accepts the following parameters: + +1. ``title``: Title of the figure. + +2. ``xlabel``: X-axis label. + +3. ``ylabel``: Y-axis label. + +4. ``linewidth``: Line width of the plot. Defaults to ``3``. + +5. ``font_size``: Font size for the labels and title. Defaults to + ``14``. + +6. ``label``: The label used for the legend. + +7. ``color``: Color of the plot which defaults to the royal blue color + ``#FF6347``. + +8. ``color_fitness``: Color of the fitness points which defaults to the + tomato red color ``#4169E1``. + +9. ``grid``: Either ``True`` or ``False`` to control the visibility of + the grid. + +10. ``alpha``: The transparency of the pareto front curve. + +11. ``marker``: The marker of the fitness points. + +12. ``save_dir``: Directory to save the figure. + +This is an example of calling the ``plot_pareto_front_curve()`` method. + +.. code:: python + + ga_instance.plot_pareto_front_curve() + +|image12| + +.. |image1| image:: https://user-images.githubusercontent.com/16560492/122472609-d02f5280-cf8e-11eb-88a7-f9366ff6e7c6.png +.. |image2| image:: https://user-images.githubusercontent.com/16560492/122473159-75e2c180-cf8f-11eb-942d-31279b286dbd.png +.. |image3| image:: https://user-images.githubusercontent.com/16560492/122473340-b7736c80-cf8f-11eb-89c5-4f7db3b653cc.png +.. |image4| image:: https://user-images.githubusercontent.com/16560492/122475815-3322e880-cf93-11eb-9648-bf66f823234b.png +.. |image5| image:: https://user-images.githubusercontent.com/16560492/122476108-adec0380-cf93-11eb-80ac-7588bf90492f.png +.. |image6| image:: https://user-images.githubusercontent.com/16560492/122476173-c2c89700-cf93-11eb-9e77-d39737cd3a96.png +.. |image7| image:: https://user-images.githubusercontent.com/16560492/122477158-4a62d580-cf95-11eb-8c93-9b6e74cb814c.png +.. |image8| image:: https://user-images.githubusercontent.com/16560492/122477273-73836600-cf95-11eb-828f-f357c7b0f815.png +.. |image9| image:: https://user-images.githubusercontent.com/16560492/122477370-99106f80-cf95-11eb-8643-865b55e6b844.png +.. |image10| image:: https://user-images.githubusercontent.com/16560492/122479260-beeb4380-cf98-11eb-8f08-23707929b12c.png +.. |image11| image:: https://user-images.githubusercontent.com/16560492/122477314-8007be80-cf95-11eb-9c95-da3f49204151.png +.. |image12| image:: https://github.com/user-attachments/assets/606d853c-7370-41a0-8ddb-857a4c6c7fb9 From 58a2ecbcb31a9a4dddaa2a2687c790967e1b0293 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 7 Jan 2025 13:11:11 -0500 Subject: [PATCH 26/79] Remove the delay_after_gen parameter --- pygad/pygad.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/pygad/pygad.py b/pygad/pygad.py index beb0a4d..bedca73 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -1,7 +1,6 @@ import numpy import random import cloudpickle -import time import warnings import concurrent.futures import inspect @@ -58,7 +57,6 @@ def __init__(self, on_mutation=None, on_generation=None, on_stop=None, - delay_after_gen=0.0, save_best_solutions=False, save_solutions=False, suppress_warnings=False, @@ -114,8 +112,6 @@ def __init__(self, on_generation: Accepts a function/method to be called after each generation. If function, then it must accept a single parameter representing the instance of the genetic algorithm. If the function returned "stop", then the run() method stops without completing the other generations. If method, then it must accept 2 parameters where the second one refers to the method's object. Added in PyGAD 2.6.0. on_stop: Accepts a function/method to be called only once exactly before the genetic algorithm stops or when it completes all the generations. If function, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one is a list of fitness values of the last population's solutions. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. - delay_after_gen: Added in PyGAD 2.4.0 and deprecated in PyGAD 3.3.0. It accepts a non-negative number specifying the number of seconds to wait after a generation completes and before going to the next generation. It defaults to 0.0 which means no delay after the generation. - save_best_solutions: Added in PyGAD 2.9.0 and its type is bool. If True, then the best solution in each generation is saved into the 'best_solutions' attribute. Use this parameter with caution as it may cause memory overflow when either the number of generations or the number of genes is large. save_solutions: Added in PyGAD 2.15.0 and its type is bool. If True, then all solutions in each generation are saved into the 'solutions' attribute. Use this parameter with caution as it may cause memory overflow when either the number of generations, number of genes, or number of solutions in population is large. @@ -1133,19 +1129,6 @@ def __init__(self, else: self.on_stop = None - # Validate delay_after_gen - if type(delay_after_gen) in GA.supported_int_float_types: - if not self.suppress_warnings: - warnings.warn("The 'delay_after_gen' parameter is deprecated starting from PyGAD 3.3.0. To delay or pause the evolution after each generation, assign a callback function/method to the 'on_generation' parameter to adds some time delay.") - if delay_after_gen >= 0.0: - self.delay_after_gen = delay_after_gen - else: - self.valid_parameters = False - raise ValueError(f"The value passed to the 'delay_after_gen' parameter must be a non-negative number. The value passed is ({delay_after_gen}) of type {type(delay_after_gen)}.") - else: - self.valid_parameters = False - raise TypeError(f"The value passed to the 'delay_after_gen' parameter must be of type int or float but {type(delay_after_gen)} found.") - # Validate save_best_solutions if type(save_best_solutions) is bool: if save_best_solutions == True: @@ -2064,8 +2047,6 @@ def run(self): if stop_run: break - time.sleep(self.delay_after_gen) - # Save the fitness of the last generation. if self.save_solutions: # self.solutions.extend(self.population.copy()) @@ -2535,11 +2516,6 @@ def print_params_summary(): if not print_step_parameters: print_mutation_params() - if self.delay_after_gen != 0: - m = f"Post-Generation Delay: {self.delay_after_gen}" - self.logger.info(m) - summary_output = summary_output + m + "\n" - if not print_step_parameters: print_on_generation_params() From 13e5713ad796060917cd85a2452337f26d8b502d Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 7 Jan 2025 16:53:55 -0500 Subject: [PATCH 27/79] Update previous_generation_fitness once GA completes --- pygad/pygad.py | 82 +++++++++++++++++------------- pygad/visualize/plot.py | 107 +++++++++++++++++++++++----------------- 2 files changed, 108 insertions(+), 81 deletions(-) diff --git a/pygad/pygad.py b/pygad/pygad.py index bedca73..436237b 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -1211,7 +1211,7 @@ def __init__(self, self.valid_parameters = False raise ValueError(f"In the 'stop_criteria' parameter, the supported stop words are {self.supported_stop_words} but '{stop_word}' found.") - if number.replace(".", "").isnumeric(): + if number.replace(".", "").replace("-", "").isnumeric(): number = float(number) else: self.valid_parameters = False @@ -1306,6 +1306,8 @@ def __init__(self, # A list holding the offspring after applying mutation in the last generation. self.last_generation_offspring_mutation = None # Holds the fitness values of one generation before the fitness values saved in the last_generation_fitness attribute. Added in PyGAD 2.16.2. + # They are used inside the cal_pop_fitness() method to fetch the fitness of the parents in one generation before the latest generation. + # This is to avoid re-calculating the fitness for such parents again. self.previous_generation_fitness = None # Added in PyGAD 2.18.0. A NumPy array holding the elitism of the current generation according to the value passed in the 'keep_elitism' parameter. It works only if the 'keep_elitism' parameter has a non-zero value. self.last_generation_elitism = None @@ -1640,14 +1642,12 @@ def cal_pop_fitness(self): # 'last_generation_parents_as_list' is the list version of 'self.last_generation_parents' # It is used to return the parent index using the 'in' membership operator of Python lists. This is much faster than using 'numpy.where()'. if self.last_generation_parents is not None: - last_generation_parents_as_list = [ - list(gen_parent) for gen_parent in self.last_generation_parents] + last_generation_parents_as_list = self.last_generation_parents.tolist() # 'last_generation_elitism_as_list' is the list version of 'self.last_generation_elitism' # It is used to return the elitism index using the 'in' membership operator of Python lists. This is much faster than using 'numpy.where()'. if self.last_generation_elitism is not None: - last_generation_elitism_as_list = [ - list(gen_elitism) for gen_elitism in self.last_generation_elitism] + last_generation_elitism_as_list = self.last_generation_elitism.tolist() pop_fitness = ["undefined"] * len(self.population) if self.parallel_processing is None: @@ -1659,6 +1659,12 @@ def cal_pop_fitness(self): # Make sure that both the solution and 'self.solutions' are of type 'list' not 'numpy.ndarray'. # if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(self.solutions == numpy.array(sol), axis=1))) # if (self.save_solutions) and (len(self.solutions) > 0) and (numpy.any(numpy.all(numpy.equal(self.solutions, numpy.array(sol)), axis=1))) + + # Make sure self.best_solutions is a list of lists before proceeding. + # Because the second condition expects that best_solutions is a list of lists. + if type(self.best_solutions) is numpy.ndarray: + self.best_solutions = self.best_solutions.tolist() + if (self.save_solutions) and (len(self.solutions) > 0) and (list(sol) in self.solutions): solution_idx = self.solutions.index(list(sol)) fitness = self.solutions_fitness[solution_idx] @@ -1867,13 +1873,13 @@ def run(self): # self.best_solutions: Holds the best solution in each generation. if type(self.best_solutions) is numpy.ndarray: - self.best_solutions = list(self.best_solutions) + self.best_solutions = self.best_solutions.tolist() # self.best_solutions_fitness: A list holding the fitness value of the best solution for each generation. if type(self.best_solutions_fitness) is numpy.ndarray: self.best_solutions_fitness = list(self.best_solutions_fitness) # self.solutions: Holds the solutions in each generation. if type(self.solutions) is numpy.ndarray: - self.solutions = list(self.solutions) + self.solutions = self.solutions.tolist() # self.solutions_fitness: Holds the fitness of the solutions in each generation. if type(self.solutions_fitness) is numpy.ndarray: self.solutions_fitness = list(self.solutions_fitness) @@ -1913,34 +1919,8 @@ def run(self): self.best_solutions.append(list(best_solution)) for generation in range(generation_first_idx, generation_last_idx): - if not (self.on_fitness is None): - on_fitness_output = self.on_fitness(self, - self.last_generation_fitness) - if on_fitness_output is None: - pass - else: - if type(on_fitness_output) in [tuple, list, numpy.ndarray, range]: - on_fitness_output = numpy.array(on_fitness_output) - if on_fitness_output.shape == self.last_generation_fitness.shape: - self.last_generation_fitness = on_fitness_output - else: - raise ValueError(f"Size mismatch between the output of on_fitness() {on_fitness_output.shape} and the expected fitness output {self.last_generation_fitness.shape}.") - else: - raise ValueError(f"The output of on_fitness() is expected to be tuple/list/range/numpy.ndarray but {type(on_fitness_output)} found.") - - # Appending the fitness value of the best solution in the current generation to the best_solutions_fitness attribute. - self.best_solutions_fitness.append(best_solution_fitness) - - # Appending the solutions in the current generation to the solutions list. - if self.save_solutions: - # self.solutions.extend(self.population.copy()) - population_as_list = self.population.copy() - population_as_list = [list(item) - for item in population_as_list] - self.solutions.extend(population_as_list) - - self.solutions_fitness.extend(self.last_generation_fitness) + self.run_loop_head(best_solution_fitness) # Call the 'run_select_parents()' method to select the parents. # It edits these 2 instance attributes: @@ -1964,7 +1944,6 @@ def run(self): # 1) population: A NumPy array of the population of solutions/chromosomes. self.run_update_population() - # The generations_completed attribute holds the number of the last completed generation. self.generations_completed = generation + 1 @@ -2078,6 +2057,9 @@ def run(self): # Converting the 'best_solutions' list into a NumPy array. self.best_solutions = numpy.array(self.best_solutions) + # Update previous_generation_fitness because it is used to get the fitness of the parents. + self.previous_generation_fitness = self.last_generation_fitness.copy() + # Converting the 'solutions' list into a NumPy array. # self.solutions = numpy.array(self.solutions) except Exception as ex: @@ -2085,6 +2067,35 @@ def run(self): # sys.exit(-1) raise ex + def run_loop_head(self, best_solution_fitness): + if not (self.on_fitness is None): + on_fitness_output = self.on_fitness(self, + self.last_generation_fitness) + + if on_fitness_output is None: + pass + else: + if type(on_fitness_output) in [tuple, list, numpy.ndarray, range]: + on_fitness_output = numpy.array(on_fitness_output) + if on_fitness_output.shape == self.last_generation_fitness.shape: + self.last_generation_fitness = on_fitness_output + else: + raise ValueError(f"Size mismatch between the output of on_fitness() {on_fitness_output.shape} and the expected fitness output {self.last_generation_fitness.shape}.") + else: + raise ValueError(f"The output of on_fitness() is expected to be tuple/list/range/numpy.ndarray but {type(on_fitness_output)} found.") + + # Appending the fitness value of the best solution in the current generation to the best_solutions_fitness attribute. + self.best_solutions_fitness.append(best_solution_fitness) + + # Appending the solutions in the current generation to the solutions list. + if self.save_solutions: + # self.solutions.extend(self.population.copy()) + population_as_list = self.population.copy() + population_as_list = [list(item) for item in population_as_list] + self.solutions.extend(population_as_list) + + self.solutions_fitness.extend(self.last_generation_fitness) + 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. @@ -2333,6 +2344,7 @@ def best_solution(self, pop_fitness=None): -best_solution_fitness: Fitness value of the best solution. -best_match_idx: Index of the best solution in the current population. """ + try: if pop_fitness is None: # If the 'pop_fitness' parameter is not passed, then we have to call the 'cal_pop_fitness()' method to calculate the fitness of all solutions in the lastest population. diff --git a/pygad/visualize/plot.py b/pygad/visualize/plot.py index 3ddeaff..3265de3 100644 --- a/pygad/visualize/plot.py +++ b/pygad/visualize/plot.py @@ -3,9 +3,18 @@ """ import numpy -import matplotlib.pyplot +# import matplotlib.pyplot import pygad +def get_matplotlib(): + # Importing matplotlib.pyplot at the module scope causes performance issues. + # This causes matplotlib.pyplot to be imported once pygad is imported. + # An efficient approach is to import matplotlib.pyplot only when needed. + # Inside each function, call get_matplotlib() to return the library object. + # If a function called get_matplotlib() once, then the library object is reused. + import matplotlib.pyplot as matplt + return matplt + class Plot: def __init__(): @@ -43,7 +52,9 @@ def plot_fitness(self, self.logger.error("The plot_fitness() (i.e. plot_result()) method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") raise RuntimeError("The plot_fitness() (i.e. plot_result()) method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") - fig = matplotlib.pyplot.figure() + matplt = get_matplotlib() + + fig = matplt.figure() if type(self.best_solutions_fitness[0]) in [list, tuple, numpy.ndarray] and len(self.best_solutions_fitness[0]) > 1: # Multi-objective optimization problem. if type(linewidth) in pygad.GA.supported_int_float_types: @@ -70,18 +81,18 @@ def plot_fitness(self, # Return the fitness values for the current objective function across all best solutions acorss all generations. fitness = numpy.array(self.best_solutions_fitness)[:, objective_idx] if plot_type == "plot": - matplotlib.pyplot.plot(fitness, + matplt.plot(fitness, linewidth=current_linewidth, color=current_color, label=current_label) elif plot_type == "scatter": - matplotlib.pyplot.scatter(range(len(fitness)), + matplt.scatter(range(len(fitness)), fitness, linewidth=current_linewidth, color=current_color, label=current_label) elif plot_type == "bar": - matplotlib.pyplot.bar(range(len(fitness)), + matplt.bar(range(len(fitness)), fitness, linewidth=current_linewidth, color=current_color, @@ -89,29 +100,29 @@ def plot_fitness(self, else: # Single-objective optimization problem. if plot_type == "plot": - matplotlib.pyplot.plot(self.best_solutions_fitness, + matplt.plot(self.best_solutions_fitness, linewidth=linewidth, color=color) elif plot_type == "scatter": - matplotlib.pyplot.scatter(range(len(self.best_solutions_fitness)), + matplt.scatter(range(len(self.best_solutions_fitness)), self.best_solutions_fitness, linewidth=linewidth, color=color) elif plot_type == "bar": - matplotlib.pyplot.bar(range(len(self.best_solutions_fitness)), + matplt.bar(range(len(self.best_solutions_fitness)), self.best_solutions_fitness, linewidth=linewidth, color=color) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) # Create a legend out of the labels. - matplotlib.pyplot.legend() + matplt.legend() if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig @@ -166,21 +177,23 @@ def plot_new_solution_rate(self, generation_num_unique_solutions = len_after - len_before num_unique_solutions_per_generation.append(generation_num_unique_solutions) - fig = matplotlib.pyplot.figure() + matplt = get_matplotlib() + + fig = matplt.figure() if plot_type == "plot": - matplotlib.pyplot.plot(num_unique_solutions_per_generation, linewidth=linewidth, color=color) + matplt.plot(num_unique_solutions_per_generation, linewidth=linewidth, color=color) elif plot_type == "scatter": - matplotlib.pyplot.scatter(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) + matplt.scatter(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) elif plot_type == "bar": - matplotlib.pyplot.bar(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) + matplt.bar(range(self.generations_completed), num_unique_solutions_per_generation, linewidth=linewidth, color=color) + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig @@ -251,7 +264,7 @@ def plot_genes(self, if num_cols == 0: figsize = (10, 8) # There is only a single gene - fig, ax = matplotlib.pyplot.subplots(num_rows, figsize=figsize) + fig, ax = matplt.subplots(num_rows, figsize=figsize) if plot_type == "plot": ax.plot(solutions_to_plot[:, 0], linewidth=linewidth, color=fill_color) elif plot_type == "scatter": @@ -260,7 +273,7 @@ def plot_genes(self, ax.bar(range(self.generations_completed + 1), solutions_to_plot[:, 0], linewidth=linewidth, color=fill_color) ax.set_xlabel(0, fontsize=font_size) else: - fig, axs = matplotlib.pyplot.subplots(num_rows, num_cols) + fig, axs = matplt.subplots(num_rows, num_cols) if num_cols == 1 and num_rows == 1: fig.set_figwidth(5 * num_cols) @@ -297,10 +310,10 @@ def plot_genes(self, gene_idx += 1 fig.suptitle(title, fontsize=font_size, y=1.001) - matplotlib.pyplot.tight_layout() + matplt.tight_layout() elif graph_type == "boxplot": - fig = matplotlib.pyplot.figure(1, figsize=(0.7*self.num_genes, 6)) + fig = matplt.figure(1, figsize=(0.7*self.num_genes, 6)) # Create an axes instance ax = fig.add_subplot(111) @@ -323,10 +336,10 @@ def plot_genes(self, for cap in boxeplots['caps']: cap.set(color=color, linewidth=linewidth) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) - matplotlib.pyplot.tight_layout() + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) + matplt.tight_layout() elif graph_type == "histogram": # num_rows will always be >= 1 @@ -337,12 +350,12 @@ def plot_genes(self, if num_cols == 0: figsize = (10, 8) # There is only a single gene - fig, ax = matplotlib.pyplot.subplots(num_rows, + fig, ax = matplt.subplots(num_rows, figsize=figsize) ax.hist(solutions_to_plot[:, 0], color=fill_color) ax.set_xlabel(0, fontsize=font_size) else: - fig, axs = matplotlib.pyplot.subplots(num_rows, num_cols) + fig, axs = matplt.subplots(num_rows, num_cols) if num_cols == 1 and num_rows == 1: fig.set_figwidth(4 * num_cols) @@ -375,13 +388,13 @@ def plot_genes(self, gene_idx += 1 fig.suptitle(title, fontsize=font_size, y=1.001) - matplotlib.pyplot.tight_layout() + matplt.tight_layout() if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig @@ -449,12 +462,14 @@ def plot_pareto_front_curve(self, # Sort the Pareto front solutions (optional but can make the plot cleaner) sorted_pareto_front = sorted(zip(pareto_front_x, pareto_front_y)) + matplt = get_matplotlib() + # Plotting - fig = matplotlib.pyplot.figure() + fig = matplt.figure() # First, plot the scatter of all points (population) all_points_x = [self.last_generation_fitness[i][0] for i in range(self.sol_per_pop)] all_points_y = [self.last_generation_fitness[i][1] for i in range(self.sol_per_pop)] - matplotlib.pyplot.scatter(all_points_x, + matplt.scatter(all_points_x, all_points_y, marker=marker, color=color_fitness, @@ -463,7 +478,7 @@ def plot_pareto_front_curve(self, # Then, plot the Pareto front as a curve pareto_front_x_sorted, pareto_front_y_sorted = zip(*sorted_pareto_front) - matplotlib.pyplot.plot(pareto_front_x_sorted, + matplt.plot(pareto_front_x_sorted, pareto_front_y_sorted, marker=marker, label=label, @@ -471,17 +486,17 @@ def plot_pareto_front_curve(self, color=color, linewidth=linewidth) - matplotlib.pyplot.title(title, fontsize=font_size) - matplotlib.pyplot.xlabel(xlabel, fontsize=font_size) - matplotlib.pyplot.ylabel(ylabel, fontsize=font_size) - matplotlib.pyplot.legend() + matplt.title(title, fontsize=font_size) + matplt.xlabel(xlabel, fontsize=font_size) + matplt.ylabel(ylabel, fontsize=font_size) + matplt.legend() - matplotlib.pyplot.grid(grid) + matplt.grid(grid) if not save_dir is None: - matplotlib.pyplot.savefig(fname=save_dir, + matplt.savefig(fname=save_dir, bbox_inches='tight') - matplotlib.pyplot.show() + matplt.show() return fig From 39841bacfd82a12aae7d5e864c58721a3811bcbe Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 7 Jan 2025 18:35:24 -0500 Subject: [PATCH 28/79] PyGAD 3.4.0 1. The `delay_after_gen` parameter is removed from the `pygad.GA` class constructor. As a result, it is no longer an attribute of the `pygad.GA` class instances. To add a delay after each generation, apply it inside the `on_generation` callback. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/283 2. In the `single_point_crossover()` method of the `pygad.utils.crossover.Crossover` class, all the random crossover points are returned before the `for` loop. This is by calling the `numpy.random.randint()` function only once before the loop to generate all the K points (where K is the offspring size). This is compared to calling the `numpy.random.randint()` function inside the `for` loop K times, once for each individual offspring. 3. Bug fix in the `examples/example_custom_operators.py` script. https://github.com/ahmedfgad/GeneticAlgorithmPython/pull/285 4. While making prediction using the `pygad.torchga.predict()` function, no gradients are calculated. 5. The `gene_type` parameter of the `pygad.helper.unique.Unique.unique_int_gene_from_range()` method accepts the type of the current gene only instead of the full gene_type list. 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. 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. 8. Refactoring the `pygad/helper/unique.py` script to remove duplicate codes and reformatting the docstrings. 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 10. Fix a bug converting a nested NumPy array to a nested list. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/300 11. 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 12. 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 13. Make sure `self.best_solutions` is a list of lists inside the `cal_pop_fitness` method. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/293 14. Fix a bug where the `cal_pop_fitness()` method was using the `previous_generation_fitness` attribute to return the parents fitness. This instance attribute was not using the fitness of the latest population, instead the fitness of the population before the last one. The issue is solved by updating the `previous_generation_fitness` attribute to the latest population fitness before the GA completes. https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/291 --- docs/source/conf.py | 4 +- docs/source/pygad.rst | 1219 +++++++++++++-------------- docs/source/releases.rst | 323 ++++--- examples/example_multi_objective.py | 1 + pyproject.toml | 2 +- setup.py | 2 +- 6 files changed, 808 insertions(+), 743 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index baef4b6..3ae40c9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,11 +18,11 @@ # -- Project information ----------------------------------------------------- project = 'PyGAD' -copyright = '2024, Ahmed Fawzy Gad' +copyright = '2025, Ahmed Fawzy Gad' author = 'Ahmed Fawzy Gad' # The full version, including alpha/beta/rc tags -release = '3.3.1' +release = '3.4.0' master_doc = 'index' diff --git a/docs/source/pygad.rst b/docs/source/pygad.rst index 9c4179e..df84336 100644 --- a/docs/source/pygad.rst +++ b/docs/source/pygad.rst @@ -29,401 +29,391 @@ algorithm to different types of applications. The ``pygad.GA`` class constructor supports the following parameters: -- ``num_generations``: Number of generations. - -- ``num_parents_mating``: Number of solutions to be selected as - parents. - -- ``fitness_func``: Accepts a function/method and returns the fitness - value(s) of the solution. If a function is passed, then it must - accept 3 parameters (1. the instance of the ``pygad.GA`` class, 2. a - single solution, and 3. its index in the population). If method, then - it accepts a fourth parameter representing the method's class - instance. Check the `Preparing the fitness_func - Parameter `__ - section for information about creating such a function. In `PyGAD - 3.2.0 `__, - multi-objective optimization is supported. To consider the problem as - multi-objective, just return a ``list``, ``tuple``, or - ``numpy.ndarray`` from the fitness function. - -- ``fitness_batch_size=None``: A new optional parameter called - ``fitness_batch_size`` is supported to calculate the fitness function - in batches. If it is assigned the value ``1`` or ``None`` (default), - then the normal flow is used where the fitness function is called for - each individual solution. If the ``fitness_batch_size`` parameter is - assigned a value satisfying this condition - ``1 < fitness_batch_size <= sol_per_pop``, then the solutions are - grouped into batches of size ``fitness_batch_size`` and the fitness - function is called once for each batch. Check the `Batch Fitness - Calculation `__ - section for more details and examples. Added in from `PyGAD - 2.19.0 `__. - -- ``initial_population``: A user-defined initial population. It is - useful when the user wants to start the generations with a custom - initial population. It defaults to ``None`` which means no initial - population is specified by the user. In this case, - `PyGAD `__ creates an initial - population using the ``sol_per_pop`` and ``num_genes`` parameters. An - exception is raised if the ``initial_population`` is ``None`` while - any of the 2 parameters (``sol_per_pop`` or ``num_genes``) is also - ``None``. Introduced in `PyGAD - 2.0.0 `__ - and higher. - -- ``sol_per_pop``: Number of solutions (i.e. chromosomes) within the - population. This parameter has no action if ``initial_population`` - parameter exists. - -- ``num_genes``: Number of genes in the solution/chromosome. This - parameter is not needed if the user feeds the initial population to - the ``initial_population`` parameter. - -- ``gene_type=float``: Controls the gene type. It can be assigned to a - single data type that is applied to all genes or can specify the data - type of each individual gene. It defaults to ``float`` which means - all genes are of ``float`` data type. Starting from `PyGAD - 2.9.0 `__, - the ``gene_type`` parameter can be assigned to a numeric value of any - of these types: ``int``, ``float``, and - ``numpy.int/uint/float(8-64)``. Starting from `PyGAD - 2.14.0 `__, - it can be assigned to a ``list``, ``tuple``, or a ``numpy.ndarray`` - which hold a data type for each gene (e.g. - ``gene_type=[int, float, numpy.int8]``). This helps to control the - data type of each individual gene. In `PyGAD - 2.15.0 `__, - a precision for the ``float`` data types can be specified (e.g. - ``gene_type=[float, 2]``. - -- ``init_range_low=-4``: The lower value of the random range from which - the gene values in the initial population are selected. - ``init_range_low`` defaults to ``-4``. Available in `PyGAD - 1.0.20 `__ - and higher. This parameter has no action if the - ``initial_population`` parameter exists. - -- ``init_range_high=4``: The upper value of the random range from which - the gene values in the initial population are selected. - ``init_range_high`` defaults to ``+4``. Available in `PyGAD - 1.0.20 `__ - and higher. This parameter has no action if the - ``initial_population`` parameter exists. - -- ``parent_selection_type="sss"``: The parent selection type. Supported - types are ``sss`` (for steady-state selection), ``rws`` (for roulette - wheel selection), ``sus`` (for stochastic universal selection), - ``rank`` (for rank selection), ``random`` (for random selection), and - ``tournament`` (for tournament selection). A custom parent selection - function can be passed starting from `PyGAD - 2.16.0 `__. - Check the `User-Defined Crossover, Mutation, and Parent Selection - Operators `__ - section for more details about building a user-defined parent - selection function. - -- ``keep_parents=-1``: Number of parents to keep in the current - population. ``-1`` (default) means to keep all parents in the next - population. ``0`` means keep no parents in the next population. A - value ``greater than 0`` means keeps the specified number of parents - in the next population. Note that the value assigned to - ``keep_parents`` cannot be ``< - 1`` or greater than the number of - solutions within the population ``sol_per_pop``. Starting from `PyGAD - 2.18.0 `__, - this parameter have an effect only when the ``keep_elitism`` - parameter is ``0``. Starting from `PyGAD - 2.20.0 `__, - the parents' fitness from the last generation will not be re-used if - ``keep_parents=0``. - -- ``keep_elitism=1``: Added in `PyGAD - 2.18.0 `__. - It can take the value ``0`` or a positive integer that satisfies - (``0 <= keep_elitism <= sol_per_pop``). It defaults to ``1`` which - means only the best solution in the current generation is kept in the - next generation. If assigned ``0``, this means it has no effect. If - assigned a positive integer ``K``, then the best ``K`` solutions are - kept in the next generation. It cannot be assigned a value greater - than the value assigned to the ``sol_per_pop`` parameter. If this - parameter has a value different than ``0``, then the ``keep_parents`` - parameter will have no effect. - -- ``K_tournament=3``: In case that the parent selection type is - ``tournament``, the ``K_tournament`` specifies the number of parents - participating in the tournament selection. It defaults to ``3``. - -- ``crossover_type="single_point"``: Type of the crossover operation. - Supported types are ``single_point`` (for single-point crossover), - ``two_points`` (for two points crossover), ``uniform`` (for uniform - crossover), and ``scattered`` (for scattered crossover). Scattered - crossover is supported from PyGAD - `2.9.0 `__ - and higher. It defaults to ``single_point``. A custom crossover - function can be passed starting from `PyGAD - 2.16.0 `__. - Check the `User-Defined Crossover, Mutation, and Parent Selection - Operators `__ - section for more details about creating a user-defined crossover - function. Starting from `PyGAD - 2.2.2 `__ - and higher, if ``crossover_type=None``, then the crossover step is - bypassed which means no crossover is applied and thus no offspring - will be created in the next generations. The next generation will use - the solutions in the current population. - -- ``crossover_probability=None``: The probability of selecting a parent - for applying the crossover operation. Its value must be between 0.0 - and 1.0 inclusive. For each parent, a random value between 0.0 and - 1.0 is generated. If this random value is less than or equal to the - value assigned to the ``crossover_probability`` parameter, then the - parent is selected. Added in `PyGAD - 2.5.0 `__ - and higher. - -- ``mutation_type="random"``: Type of the mutation operation. Supported - types are ``random`` (for random mutation), ``swap`` (for swap - mutation), ``inversion`` (for inversion mutation), ``scramble`` (for - scramble mutation), and ``adaptive`` (for adaptive mutation). It - defaults to ``random``. A custom mutation function can be passed - starting from `PyGAD - 2.16.0 `__. - Check the `User-Defined Crossover, Mutation, and Parent Selection - Operators `__ - section for more details about creating a user-defined mutation - function. Starting from `PyGAD - 2.2.2 `__ - and higher, if ``mutation_type=None``, then the mutation step is - bypassed which means no mutation is applied and thus no changes are - applied to the offspring created using the crossover operation. The - offspring will be used unchanged in the next generation. ``Adaptive`` - mutation is supported starting from `PyGAD - 2.10.0 `__. - For more information about adaptive mutation, go the the `Adaptive - Mutation `__ - section. For example about using adaptive mutation, check the `Use - Adaptive Mutation in - PyGAD `__ - section. - -- ``mutation_probability=None``: The probability of selecting a gene - for applying the mutation operation. Its value must be between 0.0 - and 1.0 inclusive. For each gene in a solution, a random value - between 0.0 and 1.0 is generated. If this random value is less than - or equal to the value assigned to the ``mutation_probability`` - parameter, then the gene is selected. If this parameter exists, then - there is no need for the 2 parameters ``mutation_percent_genes`` and - ``mutation_num_genes``. Added in `PyGAD - 2.5.0 `__ - and higher. - -- ``mutation_by_replacement=False``: An optional bool parameter. It - works only when the selected type of mutation is random - (``mutation_type="random"``). In this case, - ``mutation_by_replacement=True`` means replace the gene by the - randomly generated value. If False, then it has no effect and random - mutation works by adding the random value to the gene. Supported in - `PyGAD - 2.2.2 `__ - and higher. Check the changes in `PyGAD - 2.2.2 `__ - under the Release History section for an example. - -- ``mutation_percent_genes="default"``: Percentage of genes to mutate. - It defaults to the string ``"default"`` which is later translated - into the integer ``10`` which means 10% of the genes will be mutated. - It must be ``>0`` and ``<=100``. Out of this percentage, the number - of genes to mutate is deduced which is assigned to the - ``mutation_num_genes`` parameter. The ``mutation_percent_genes`` - parameter has no action if ``mutation_probability`` or - ``mutation_num_genes`` exist. Starting from `PyGAD - 2.2.2 `__ - and higher, this parameter has no action if ``mutation_type`` is - ``None``. - -- ``mutation_num_genes=None``: Number of genes to mutate which defaults - to ``None`` meaning that no number is specified. The - ``mutation_num_genes`` parameter has no action if the parameter - ``mutation_probability`` exists. Starting from `PyGAD - 2.2.2 `__ - and higher, this parameter has no action if ``mutation_type`` is - ``None``. - -- ``random_mutation_min_val=-1.0``: For ``random`` mutation, the - ``random_mutation_min_val`` parameter specifies the start value of - the range from which a random value is selected to be added to the - gene. It defaults to ``-1``. Starting from `PyGAD - 2.2.2 `__ - and higher, this parameter has no action if ``mutation_type`` is - ``None``. - -- ``random_mutation_max_val=1.0``: For ``random`` mutation, the - ``random_mutation_max_val`` parameter specifies the end value of the - range from which a random value is selected to be added to the gene. - It defaults to ``+1``. Starting from `PyGAD - 2.2.2 `__ - and higher, this parameter has no action if ``mutation_type`` is - ``None``. - -- ``gene_space=None``: It is used to specify the possible values for - each gene in case the user wants to restrict the gene values. It is - useful if the gene space is restricted to a certain range or to - discrete values. It accepts a ``list``, ``range``, or - ``numpy.ndarray``. When all genes have the same global space, specify - their values as a ``list``/``tuple``/``range``/``numpy.ndarray``. For - example, ``gene_space = [0.3, 5.2, -4, 8]`` restricts the gene values - to the 4 specified values. If each gene has its own space, then the - ``gene_space`` parameter can be nested like - ``[[0.4, -5], [0.5, -3.2, 8.2, -9], ...]`` where the first sublist - determines the values for the first gene, the second sublist for the - second gene, and so on. If the nested list/tuple has a ``None`` - value, then the gene's initial value is selected randomly from the - range specified by the 2 parameters ``init_range_low`` and - ``init_range_high`` and its mutation value is selected randomly from - the range specified by the 2 parameters ``random_mutation_min_val`` - and ``random_mutation_max_val``. ``gene_space`` is added in `PyGAD - 2.5.0 `__. - Check the `Release History of PyGAD - 2.5.0 `__ - section of the documentation for more details. In `PyGAD - 2.9.0 `__, - NumPy arrays can be assigned to the ``gene_space`` parameter. In - `PyGAD - 2.11.0 `__, - the ``gene_space`` parameter itself or any of its elements can be - assigned to a dictionary to specify the lower and upper limits of the - genes. For example, ``{'low': 2, 'high': 4}`` means the minimum and - maximum values are 2 and 4, respectively. In `PyGAD - 2.15.0 `__, - a new key called ``"step"`` is supported to specify the step of - moving from the start to the end of the range specified by the 2 - existing keys ``"low"`` and ``"high"``. - -- ``on_start=None``: Accepts a function/method to be called only once - before the genetic algorithm starts its evolution. If function, then - it must accept a single parameter representing the instance of the - genetic algorithm. If method, then it must accept 2 parameters where - the second one refers to the method's object. Added in `PyGAD - 2.6.0 `__. - -- ``on_fitness=None``: Accepts a function/method to be called after - calculating the fitness values of all solutions in the population. If - function, then it must accept 2 parameters: 1) a list of all - solutions' fitness values 2) the instance of the genetic algorithm. - If method, then it must accept 3 parameters where the third one - refers to the method's object. Added in `PyGAD - 2.6.0 `__. - -- ``on_parents=None``: Accepts a function/method to be called after - selecting the parents that mates. If function, then it must accept 2 - parameters: 1) the selected parents 2) the instance of the genetic - algorithm If method, then it must accept 3 parameters where the third - one refers to the method's object. Added in `PyGAD - 2.6.0 `__. - -- ``on_crossover=None``: Accepts a function to be called each time the - crossover operation is applied. This function must accept 2 - parameters: the first one represents the instance of the genetic - algorithm and the second one represents the offspring generated using - crossover. Added in `PyGAD - 2.6.0 `__. - -- ``on_mutation=None``: Accepts a function to be called each time the - mutation operation is applied. This function must accept 2 - parameters: the first one represents the instance of the genetic - algorithm and the second one represents the offspring after applying - the mutation. Added in `PyGAD - 2.6.0 `__. - -- ``on_generation=None``: Accepts a function to be called after each - generation. This function must accept a single parameter representing - the instance of the genetic algorithm. If the function returned the - string ``stop``, then the ``run()`` method stops without completing - the other generations. Added in `PyGAD - 2.6.0 `__. - -- ``on_stop=None``: Accepts a function to be called only once exactly - before the genetic algorithm stops or when it completes all the - generations. This function must accept 2 parameters: the first one - represents the instance of the genetic algorithm and the second one - is a list of fitness values of the last population's solutions. Added - in `PyGAD - 2.6.0 `__. - -- ``delay_after_gen=0.0``: It accepts a non-negative number specifying - the time in seconds to wait after a generation completes and before - going to the next generation. It defaults to ``0.0`` which means no - delay after the generation. Available in `PyGAD - 2.4.0 `__ - and higher. - -- ``save_best_solutions=False``: When ``True``, then the best solution - after each generation is saved into an attribute named - ``best_solutions``. If ``False`` (default), then no solutions are - saved and the ``best_solutions`` attribute will be empty. Supported - in `PyGAD - 2.9.0 `__. - -- ``save_solutions=False``: If ``True``, then all solutions in each - generation are appended into an attribute called ``solutions`` which - is NumPy array. Supported in `PyGAD - 2.15.0 `__. - -- ``suppress_warnings=False``: A bool parameter to control whether the - warning messages are printed or not. It defaults to ``False``. - -- ``allow_duplicate_genes=True``: Added in `PyGAD - 2.13.0 `__. - If ``True``, then a solution/chromosome may have duplicate gene - values. If ``False``, then each gene will have a unique value in its - solution. - -- ``stop_criteria=None``: Some criteria to stop the evolution. Added in - `PyGAD - 2.15.0 `__. - Each criterion is passed as ``str`` which has a stop word. The - current 2 supported words are ``reach`` and ``saturate``. ``reach`` - stops the ``run()`` method if the fitness value is equal to or - greater than a given fitness value. An example for ``reach`` is - ``"reach_40"`` which stops the evolution if the fitness is >= 40. - ``saturate`` means stop the evolution if the fitness saturates for a - given number of consecutive generations. An example for ``saturate`` - is ``"saturate_7"`` which means stop the ``run()`` method if the - fitness does not change for 7 consecutive generations. - -- ``parallel_processing=None``: Added in `PyGAD - 2.17.0 `__. - If ``None`` (Default), this means no parallel processing is applied. - It can accept a list/tuple of 2 elements [1) Can be either - ``'process'`` or ``'thread'`` to indicate whether processes or - threads are used, respectively., 2) The number of processes or - threads to use.]. For example, - ``parallel_processing=['process', 10]`` applies parallel processing - with 10 processes. If a positive integer is assigned, then it is used - as the number of threads. For example, ``parallel_processing=5`` uses - 5 threads which is equivalent to - ``parallel_processing=["thread", 5]``. For more information, check - the `Parallel Processing in - PyGAD `__ - section. - -- ``random_seed=None``: Added in `PyGAD - 2.18.0 `__. - It defines the random seed to be used by the random function - generators (we use random functions in the NumPy and random modules). - This helps to reproduce the same results by setting the same random - seed (e.g. ``random_seed=2``). If given the value ``None``, then it - has no effect. - -- ``logger=None``: Accepts an instance of the ``logging.Logger`` class - to log the outputs. Any message is no longer printed using - ``print()`` but logged. If ``logger=None``, then a logger is created - that uses ``StreamHandler`` to logs the messages to the console. - Added in `PyGAD - 3.0.0 `__. - Check the `Logging - Outputs `__ - for more information. +- ``num_generations``: Number of generations. + +- ``num_parents_mating``: Number of solutions to be selected as parents. + +- ``fitness_func``: Accepts a function/method and returns the fitness + value(s) of the solution. If a function is passed, then it must accept + 3 parameters (1. the instance of the ``pygad.GA`` class, 2. a single + solution, and 3. its index in the population). If method, then it + accepts a fourth parameter representing the method's class instance. + Check the `Preparing the fitness_func + Parameter `__ + section for information about creating such a function. In `PyGAD + 3.2.0 `__, + multi-objective optimization is supported. To consider the problem as + multi-objective, just return a ``list``, ``tuple``, or + ``numpy.ndarray`` from the fitness function. + +- ``fitness_batch_size=None``: A new optional parameter called + ``fitness_batch_size`` is supported to calculate the fitness function + in batches. If it is assigned the value ``1`` or ``None`` (default), + then the normal flow is used where the fitness function is called for + each individual solution. If the ``fitness_batch_size`` parameter is + assigned a value satisfying this condition + ``1 < fitness_batch_size <= sol_per_pop``, then the solutions are + grouped into batches of size ``fitness_batch_size`` and the fitness + function is called once for each batch. Check the `Batch Fitness + Calculation `__ + section for more details and examples. Added in from `PyGAD + 2.19.0 `__. + +- ``initial_population``: A user-defined initial population. It is + useful when the user wants to start the generations with a custom + initial population. It defaults to ``None`` which means no initial + population is specified by the user. In this case, + `PyGAD `__ creates an initial + population using the ``sol_per_pop`` and ``num_genes`` parameters. An + exception is raised if the ``initial_population`` is ``None`` while + any of the 2 parameters (``sol_per_pop`` or ``num_genes``) is also + ``None``. Introduced in `PyGAD + 2.0.0 `__ + and higher. + +- ``sol_per_pop``: Number of solutions (i.e. chromosomes) within the + population. This parameter has no action if ``initial_population`` + parameter exists. + +- ``num_genes``: Number of genes in the solution/chromosome. This + parameter is not needed if the user feeds the initial population to + the ``initial_population`` parameter. + +- ``gene_type=float``: Controls the gene type. It can be assigned to a + single data type that is applied to all genes or can specify the data + type of each individual gene. It defaults to ``float`` which means all + genes are of ``float`` data type. Starting from `PyGAD + 2.9.0 `__, + the ``gene_type`` parameter can be assigned to a numeric value of any + of these types: ``int``, ``float``, and + ``numpy.int/uint/float(8-64)``. Starting from `PyGAD + 2.14.0 `__, + it can be assigned to a ``list``, ``tuple``, or a ``numpy.ndarray`` + which hold a data type for each gene (e.g. + ``gene_type=[int, float, numpy.int8]``). This helps to control the + data type of each individual gene. In `PyGAD + 2.15.0 `__, + a precision for the ``float`` data types can be specified (e.g. + ``gene_type=[float, 2]``. + +- ``init_range_low=-4``: The lower value of the random range from which + the gene values in the initial population are selected. + ``init_range_low`` defaults to ``-4``. Available in `PyGAD + 1.0.20 `__ + and higher. This parameter has no action if the ``initial_population`` + parameter exists. + +- ``init_range_high=4``: The upper value of the random range from which + the gene values in the initial population are selected. + ``init_range_high`` defaults to ``+4``. Available in `PyGAD + 1.0.20 `__ + and higher. This parameter has no action if the ``initial_population`` + parameter exists. + +- ``parent_selection_type="sss"``: The parent selection type. Supported + types are ``sss`` (for steady-state selection), ``rws`` (for roulette + wheel selection), ``sus`` (for stochastic universal selection), + ``rank`` (for rank selection), ``random`` (for random selection), and + ``tournament`` (for tournament selection). A custom parent selection + function can be passed starting from `PyGAD + 2.16.0 `__. + Check the `User-Defined Crossover, Mutation, and Parent Selection + Operators `__ + section for more details about building a user-defined parent + selection function. + +- ``keep_parents=-1``: Number of parents to keep in the current + population. ``-1`` (default) means to keep all parents in the next + population. ``0`` means keep no parents in the next population. A + value ``greater than 0`` means keeps the specified number of parents + in the next population. Note that the value assigned to + ``keep_parents`` cannot be ``< - 1`` or greater than the number of + solutions within the population ``sol_per_pop``. Starting from `PyGAD + 2.18.0 `__, + this parameter have an effect only when the ``keep_elitism`` parameter + is ``0``. Starting from `PyGAD + 2.20.0 `__, + the parents' fitness from the last generation will not be re-used if + ``keep_parents=0``. + +- ``keep_elitism=1``: Added in `PyGAD + 2.18.0 `__. + It can take the value ``0`` or a positive integer that satisfies + (``0 <= keep_elitism <= sol_per_pop``). It defaults to ``1`` which + means only the best solution in the current generation is kept in the + next generation. If assigned ``0``, this means it has no effect. If + assigned a positive integer ``K``, then the best ``K`` solutions are + kept in the next generation. It cannot be assigned a value greater + than the value assigned to the ``sol_per_pop`` parameter. If this + parameter has a value different than ``0``, then the ``keep_parents`` + parameter will have no effect. + +- ``K_tournament=3``: In case that the parent selection type is + ``tournament``, the ``K_tournament`` specifies the number of parents + participating in the tournament selection. It defaults to ``3``. + +- ``crossover_type="single_point"``: Type of the crossover operation. + Supported types are ``single_point`` (for single-point crossover), + ``two_points`` (for two points crossover), ``uniform`` (for uniform + crossover), and ``scattered`` (for scattered crossover). Scattered + crossover is supported from PyGAD + `2.9.0 `__ + and higher. It defaults to ``single_point``. A custom crossover + function can be passed starting from `PyGAD + 2.16.0 `__. + Check the `User-Defined Crossover, Mutation, and Parent Selection + Operators `__ + section for more details about creating a user-defined crossover + function. Starting from `PyGAD + 2.2.2 `__ + and higher, if ``crossover_type=None``, then the crossover step is + bypassed which means no crossover is applied and thus no offspring + will be created in the next generations. The next generation will use + the solutions in the current population. + +- ``crossover_probability=None``: The probability of selecting a parent + for applying the crossover operation. Its value must be between 0.0 + and 1.0 inclusive. For each parent, a random value between 0.0 and 1.0 + is generated. If this random value is less than or equal to the value + assigned to the ``crossover_probability`` parameter, then the parent + is selected. Added in `PyGAD + 2.5.0 `__ + and higher. + +- ``mutation_type="random"``: Type of the mutation operation. Supported + types are ``random`` (for random mutation), ``swap`` (for swap + mutation), ``inversion`` (for inversion mutation), ``scramble`` (for + scramble mutation), and ``adaptive`` (for adaptive mutation). It + defaults to ``random``. A custom mutation function can be passed + starting from `PyGAD + 2.16.0 `__. + Check the `User-Defined Crossover, Mutation, and Parent Selection + Operators `__ + section for more details about creating a user-defined mutation + function. Starting from `PyGAD + 2.2.2 `__ + and higher, if ``mutation_type=None``, then the mutation step is + bypassed which means no mutation is applied and thus no changes are + applied to the offspring created using the crossover operation. The + offspring will be used unchanged in the next generation. ``Adaptive`` + mutation is supported starting from `PyGAD + 2.10.0 `__. + For more information about adaptive mutation, go the the `Adaptive + Mutation `__ + section. For example about using adaptive mutation, check the `Use + Adaptive Mutation in + PyGAD `__ + section. + +- ``mutation_probability=None``: The probability of selecting a gene for + applying the mutation operation. Its value must be between 0.0 and 1.0 + inclusive. For each gene in a solution, a random value between 0.0 and + 1.0 is generated. If this random value is less than or equal to the + value assigned to the ``mutation_probability`` parameter, then the + gene is selected. If this parameter exists, then there is no need for + the 2 parameters ``mutation_percent_genes`` and + ``mutation_num_genes``. Added in `PyGAD + 2.5.0 `__ + and higher. + +- ``mutation_by_replacement=False``: An optional bool parameter. It + works only when the selected type of mutation is random + (``mutation_type="random"``). In this case, + ``mutation_by_replacement=True`` means replace the gene by the + randomly generated value. If False, then it has no effect and random + mutation works by adding the random value to the gene. Supported in + `PyGAD + 2.2.2 `__ + and higher. Check the changes in `PyGAD + 2.2.2 `__ + under the Release History section for an example. + +- ``mutation_percent_genes="default"``: Percentage of genes to mutate. + It defaults to the string ``"default"`` which is later translated into + the integer ``10`` which means 10% of the genes will be mutated. It + must be ``>0`` and ``<=100``. Out of this percentage, the number of + genes to mutate is deduced which is assigned to the + ``mutation_num_genes`` parameter. The ``mutation_percent_genes`` + parameter has no action if ``mutation_probability`` or + ``mutation_num_genes`` exist. Starting from `PyGAD + 2.2.2 `__ + and higher, this parameter has no action if ``mutation_type`` is + ``None``. + +- ``mutation_num_genes=None``: Number of genes to mutate which defaults + to ``None`` meaning that no number is specified. The + ``mutation_num_genes`` parameter has no action if the parameter + ``mutation_probability`` exists. Starting from `PyGAD + 2.2.2 `__ + and higher, this parameter has no action if ``mutation_type`` is + ``None``. + +- ``random_mutation_min_val=-1.0``: For ``random`` mutation, the + ``random_mutation_min_val`` parameter specifies the start value of the + range from which a random value is selected to be added to the gene. + It defaults to ``-1``. Starting from `PyGAD + 2.2.2 `__ + and higher, this parameter has no action if ``mutation_type`` is + ``None``. + +- ``random_mutation_max_val=1.0``: For ``random`` mutation, the + ``random_mutation_max_val`` parameter specifies the end value of the + range from which a random value is selected to be added to the gene. + It defaults to ``+1``. Starting from `PyGAD + 2.2.2 `__ + and higher, this parameter has no action if ``mutation_type`` is + ``None``. + +- ``gene_space=None``: It is used to specify the possible values for + each gene in case the user wants to restrict the gene values. It is + useful if the gene space is restricted to a certain range or to + discrete values. It accepts a ``list``, ``range``, or + ``numpy.ndarray``. When all genes have the same global space, specify + their values as a ``list``/``tuple``/``range``/``numpy.ndarray``. For + example, ``gene_space = [0.3, 5.2, -4, 8]`` restricts the gene values + to the 4 specified values. If each gene has its own space, then the + ``gene_space`` parameter can be nested like + ``[[0.4, -5], [0.5, -3.2, 8.2, -9], ...]`` where the first sublist + determines the values for the first gene, the second sublist for the + second gene, and so on. If the nested list/tuple has a ``None`` value, + then the gene's initial value is selected randomly from the range + specified by the 2 parameters ``init_range_low`` and + ``init_range_high`` and its mutation value is selected randomly from + the range specified by the 2 parameters ``random_mutation_min_val`` + and ``random_mutation_max_val``. ``gene_space`` is added in `PyGAD + 2.5.0 `__. + Check the `Release History of PyGAD + 2.5.0 `__ + section of the documentation for more details. In `PyGAD + 2.9.0 `__, + NumPy arrays can be assigned to the ``gene_space`` parameter. In + `PyGAD + 2.11.0 `__, + the ``gene_space`` parameter itself or any of its elements can be + assigned to a dictionary to specify the lower and upper limits of the + genes. For example, ``{'low': 2, 'high': 4}`` means the minimum and + maximum values are 2 and 4, respectively. In `PyGAD + 2.15.0 `__, + a new key called ``"step"`` is supported to specify the step of moving + from the start to the end of the range specified by the 2 existing + keys ``"low"`` and ``"high"``. + +- ``on_start=None``: Accepts a function/method to be called only once + before the genetic algorithm starts its evolution. If function, then + it must accept a single parameter representing the instance of the + genetic algorithm. If method, then it must accept 2 parameters where + the second one refers to the method's object. Added in `PyGAD + 2.6.0 `__. + +- ``on_fitness=None``: Accepts a function/method to be called after + calculating the fitness values of all solutions in the population. If + function, then it must accept 2 parameters: 1) a list of all + solutions' fitness values 2) the instance of the genetic algorithm. If + method, then it must accept 3 parameters where the third one refers to + the method's object. Added in `PyGAD + 2.6.0 `__. + +- ``on_parents=None``: Accepts a function/method to be called after + selecting the parents that mates. If function, then it must accept 2 + parameters: 1) the selected parents 2) the instance of the genetic + algorithm If method, then it must accept 3 parameters where the third + one refers to the method's object. Added in `PyGAD + 2.6.0 `__. + +- ``on_crossover=None``: Accepts a function to be called each time the + crossover operation is applied. This function must accept 2 + parameters: the first one represents the instance of the genetic + algorithm and the second one represents the offspring generated using + crossover. Added in `PyGAD + 2.6.0 `__. + +- ``on_mutation=None``: Accepts a function to be called each time the + mutation operation is applied. This function must accept 2 parameters: + the first one represents the instance of the genetic algorithm and the + second one represents the offspring after applying the mutation. Added + in `PyGAD + 2.6.0 `__. + +- ``on_generation=None``: Accepts a function to be called after each + generation. This function must accept a single parameter representing + the instance of the genetic algorithm. If the function returned the + string ``stop``, then the ``run()`` method stops without completing + the other generations. Added in `PyGAD + 2.6.0 `__. + +- ``on_stop=None``: Accepts a function to be called only once exactly + before the genetic algorithm stops or when it completes all the + generations. This function must accept 2 parameters: the first one + represents the instance of the genetic algorithm and the second one is + a list of fitness values of the last population's solutions. Added in + `PyGAD + 2.6.0 `__. + +- ``save_best_solutions=False``: When ``True``, then the best solution + after each generation is saved into an attribute named + ``best_solutions``. If ``False`` (default), then no solutions are + saved and the ``best_solutions`` attribute will be empty. Supported in + `PyGAD + 2.9.0 `__. + +- ``save_solutions=False``: If ``True``, then all solutions in each + generation are appended into an attribute called ``solutions`` which + is NumPy array. Supported in `PyGAD + 2.15.0 `__. + +- ``suppress_warnings=False``: A bool parameter to control whether the + warning messages are printed or not. It defaults to ``False``. + +- ``allow_duplicate_genes=True``: Added in `PyGAD + 2.13.0 `__. + If ``True``, then a solution/chromosome may have duplicate gene + values. If ``False``, then each gene will have a unique value in its + solution. + +- ``stop_criteria=None``: Some criteria to stop the evolution. Added in + `PyGAD + 2.15.0 `__. + Each criterion is passed as ``str`` which has a stop word. The current + 2 supported words are ``reach`` and ``saturate``. ``reach`` stops the + ``run()`` method if the fitness value is equal to or greater than a + given fitness value. An example for ``reach`` is ``"reach_40"`` which + stops the evolution if the fitness is >= 40. ``saturate`` means stop + the evolution if the fitness saturates for a given number of + consecutive generations. An example for ``saturate`` is + ``"saturate_7"`` which means stop the ``run()`` method if the fitness + does not change for 7 consecutive generations. + +- ``parallel_processing=None``: Added in `PyGAD + 2.17.0 `__. + If ``None`` (Default), this means no parallel processing is applied. + It can accept a list/tuple of 2 elements [1) Can be either + ``'process'`` or ``'thread'`` to indicate whether processes or threads + are used, respectively., 2) The number of processes or threads to + use.]. For example, ``parallel_processing=['process', 10]`` applies + parallel processing with 10 processes. If a positive integer is + assigned, then it is used as the number of threads. For example, + ``parallel_processing=5`` uses 5 threads which is equivalent to + ``parallel_processing=["thread", 5]``. For more information, check the + `Parallel Processing in + PyGAD `__ + section. + +- ``random_seed=None``: Added in `PyGAD + 2.18.0 `__. + It defines the random seed to be used by the random function + generators (we use random functions in the NumPy and random modules). + This helps to reproduce the same results by setting the same random + seed (e.g. ``random_seed=2``). If given the value ``None``, then it + has no effect. + +- ``logger=None``: Accepts an instance of the ``logging.Logger`` class + to log the outputs. Any message is no longer printed using ``print()`` + but logged. If ``logger=None``, then a logger is created that uses + ``StreamHandler`` to logs the messages to the console. Added in `PyGAD + 3.0.0 `__. + Check the `Logging + Outputs `__ + for more information. The user doesn't have to specify all of such parameters while creating an instance of the GA class. A very important parameter you must care @@ -448,25 +438,25 @@ parameter is not correct, an exception is thrown. Plotting Methods in ``pygad.GA`` Class -------------------------------------- -- ``plot_fitness()``: Shows how the fitness evolves by generation. +- ``plot_fitness()``: Shows how the fitness evolves by generation. -- ``plot_genes()``: Shows how the gene value changes for each - generation. +- ``plot_genes()``: Shows how the gene value changes for each + generation. -- ``plot_new_solution_rate()``: Shows the number of new solutions - explored in each solution. +- ``plot_new_solution_rate()``: Shows the number of new solutions + explored in each solution. Class Attributes ---------------- -- ``supported_int_types``: A list of the supported types for the - integer numbers. +- ``supported_int_types``: A list of the supported types for the integer + numbers. -- ``supported_float_types``: A list of the supported types for the - floating-point numbers. +- ``supported_float_types``: A list of the supported types for the + floating-point numbers. -- ``supported_int_float_types``: A list of the supported types for all - numbers. It just concatenates the previous 2 lists. +- ``supported_int_float_types``: A list of the supported types for all + numbers. It just concatenates the previous 2 lists. .. _other-instance-attributes--methods: @@ -483,92 +473,92 @@ The next 2 subsections list such attributes and methods. Other Attributes ~~~~~~~~~~~~~~~~ -- ``generations_completed``: Holds the number of the last completed - generation. - -- ``population``: A NumPy array holding the initial population. - -- ``valid_parameters``: Set to ``True`` when all the parameters passed - in the ``GA`` class constructor are valid. - -- ``run_completed``: Set to ``True`` only after the ``run()`` method - completes gracefully. - -- ``pop_size``: The population size. - -- ``best_solutions_fitness``: A list holding the fitness values of the - best solutions for all generations. - -- ``best_solution_generation``: The generation number at which the best - fitness value is reached. It is only assigned the generation number - after the ``run()`` method completes. Otherwise, its value is -1. - -- ``best_solutions``: A NumPy array holding the best solution per each - generation. It only exists when the ``save_best_solutions`` parameter - in the ``pygad.GA`` class constructor is set to ``True``. - -- ``last_generation_fitness``: The fitness values of the solutions in - the last generation. `Added in PyGAD - 2.12.0 `__. - -- ``previous_generation_fitness``: At the end of each generation, the - fitness of the most recent population is saved in the - ``last_generation_fitness`` attribute. The fitness of the population - exactly preceding this most recent population is saved in the - ``last_generation_fitness`` attribute. This - ``previous_generation_fitness`` attribute is used to fetch the - pre-calculated fitness instead of calling the fitness function for - already explored solutions. `Added in PyGAD - 2.16.2 `__. - -- ``last_generation_parents``: The parents selected from the last - generation. `Added in PyGAD - 2.12.0 `__. - -- ``last_generation_offspring_crossover``: The offspring generated - after applying the crossover in the last generation. `Added in PyGAD - 2.12.0 `__. - -- ``last_generation_offspring_mutation``: The offspring generated after - applying the mutation in the last generation. `Added in PyGAD - 2.12.0 `__. - -- ``gene_type_single``: A flag that is set to ``True`` if the - ``gene_type`` parameter is assigned to a single data type that is - applied to all genes. If ``gene_type`` is assigned a ``list``, - ``tuple``, or ``numpy.ndarray``, then the value of - ``gene_type_single`` will be ``False``. `Added in PyGAD - 2.14.0 `__. - -- ``last_generation_parents_indices``: This attribute holds the indices - of the selected parents in the last generation. Supported in `PyGAD - 2.15.0 `__. - -- ``last_generation_elitism``: This attribute holds the elitism of the - last generation. It is effective only if the ``keep_elitism`` - parameter has a non-zero value. Supported in `PyGAD - 2.18.0 `__. - -- ``last_generation_elitism_indices``: This attribute holds the indices - of the elitism of the last generation. It is effective only if the - ``keep_elitism`` parameter has a non-zero value. Supported in `PyGAD - 2.19.0 `__. - -- ``logger``: This attribute holds the logger from the ``logging`` - module. Supported in `PyGAD - 3.0.0 `__. - -- ``gene_space_unpacked``: This is the unpacked version of the - ``gene_space`` parameter. For example, ``range(1, 5)`` is unpacked to - ``[1, 2, 3, 4]``. For an infinite range like - ``{'low': 2, 'high': 4}``, then it is unpacked to a limited number of - values (e.g. 100). Supported in `PyGAD - 3.1.0 `__. - -- ``pareto_fronts``: A new instance attribute named ``pareto_fronts`` - added to the ``pygad.GA`` instances that holds the pareto fronts when - solving a multi-objective problem. Supported in `PyGAD - 3.2.0 `__. +- ``generations_completed``: Holds the number of the last completed + generation. + +- ``population``: A NumPy array holding the initial population. + +- ``valid_parameters``: Set to ``True`` when all the parameters passed + in the ``GA`` class constructor are valid. + +- ``run_completed``: Set to ``True`` only after the ``run()`` method + completes gracefully. + +- ``pop_size``: The population size. + +- ``best_solutions_fitness``: A list holding the fitness values of the + best solutions for all generations. + +- ``best_solution_generation``: The generation number at which the best + fitness value is reached. It is only assigned the generation number + after the ``run()`` method completes. Otherwise, its value is -1. + +- ``best_solutions``: A NumPy array holding the best solution per each + generation. It only exists when the ``save_best_solutions`` parameter + in the ``pygad.GA`` class constructor is set to ``True``. + +- ``last_generation_fitness``: The fitness values of the solutions in + the last generation. `Added in PyGAD + 2.12.0 `__. + +- ``previous_generation_fitness``: At the end of each generation, the + fitness of the most recent population is saved in the + ``last_generation_fitness`` attribute. The fitness of the population + exactly preceding this most recent population is saved in the + ``last_generation_fitness`` attribute. This + ``previous_generation_fitness`` attribute is used to fetch the + pre-calculated fitness instead of calling the fitness function for + already explored solutions. `Added in PyGAD + 2.16.2 `__. + +- ``last_generation_parents``: The parents selected from the last + generation. `Added in PyGAD + 2.12.0 `__. + +- ``last_generation_offspring_crossover``: The offspring generated after + applying the crossover in the last generation. `Added in PyGAD + 2.12.0 `__. + +- ``last_generation_offspring_mutation``: The offspring generated after + applying the mutation in the last generation. `Added in PyGAD + 2.12.0 `__. + +- ``gene_type_single``: A flag that is set to ``True`` if the + ``gene_type`` parameter is assigned to a single data type that is + applied to all genes. If ``gene_type`` is assigned a ``list``, + ``tuple``, or ``numpy.ndarray``, then the value of + ``gene_type_single`` will be ``False``. `Added in PyGAD + 2.14.0 `__. + +- ``last_generation_parents_indices``: This attribute holds the indices + of the selected parents in the last generation. Supported in `PyGAD + 2.15.0 `__. + +- ``last_generation_elitism``: This attribute holds the elitism of the + last generation. It is effective only if the ``keep_elitism`` + parameter has a non-zero value. Supported in `PyGAD + 2.18.0 `__. + +- ``last_generation_elitism_indices``: This attribute holds the indices + of the elitism of the last generation. It is effective only if the + ``keep_elitism`` parameter has a non-zero value. Supported in `PyGAD + 2.19.0 `__. + +- ``logger``: This attribute holds the logger from the ``logging`` + module. Supported in `PyGAD + 3.0.0 `__. + +- ``gene_space_unpacked``: This is the unpacked version of the + ``gene_space`` parameter. For example, ``range(1, 5)`` is unpacked to + ``[1, 2, 3, 4]``. For an infinite range like + ``{'low': 2, 'high': 4}``, then it is unpacked to a limited number of + values (e.g. 100). Supported in `PyGAD + 3.1.0 `__. + +- ``pareto_fronts``: A new instance attribute named ``pareto_fronts`` + added to the ``pygad.GA`` instances that holds the pareto fronts when + solving a multi-objective problem. Supported in `PyGAD + 3.2.0 `__. Note that the attributes with names starting with ``last_generation_`` are updated after each generation. @@ -576,55 +566,55 @@ are updated after each generation. Other Methods ~~~~~~~~~~~~~ -- ``cal_pop_fitness()``: A method that calculates the fitness values - for all solutions within the population by calling the function - passed to the ``fitness_func`` parameter for each solution. +- ``cal_pop_fitness()``: A method that calculates the fitness values for + all solutions within the population by calling the function passed to + the ``fitness_func`` parameter for each solution. -- ``crossover()``: Refers to the method that applies the crossover - operator based on the selected type of crossover in the - ``crossover_type`` property. +- ``crossover()``: Refers to the method that applies the crossover + operator based on the selected type of crossover in the + ``crossover_type`` property. -- ``mutation()``: Refers to the method that applies the mutation - operator based on the selected type of mutation in the - ``mutation_type`` property. +- ``mutation()``: Refers to the method that applies the mutation + operator based on the selected type of mutation in the + ``mutation_type`` property. -- ``select_parents()``: Refers to a method that selects the parents - based on the parent selection type specified in the - ``parent_selection_type`` attribute. +- ``select_parents()``: Refers to a method that selects the parents + based on the parent selection type specified in the + ``parent_selection_type`` attribute. -- ``adaptive_mutation_population_fitness()``: Returns the average - fitness value used in the adaptive mutation to filter the solutions. +- ``adaptive_mutation_population_fitness()``: Returns the average + fitness value used in the adaptive mutation to filter the solutions. -- ``summary()``: Prints a Keras-like summary of the PyGAD lifecycle. - This helps to have an overview of the architecture. Supported in - `PyGAD - 2.19.0 `__. - Check the `Print Lifecycle - Summary `__ - section for more details and examples. +- ``summary()``: Prints a Keras-like summary of the PyGAD lifecycle. + This helps to have an overview of the architecture. Supported in + `PyGAD + 2.19.0 `__. + Check the `Print Lifecycle + Summary `__ + section for more details and examples. -- 4 methods with names starting with ``run_``. Their purpose is to keep - the main loop inside the ``run()`` method clean. The details inside - the loop are moved to 4 individual methods. Generally, any method - with a name starting with ``run_`` is meant to be called by PyGAD - from inside the ``run()`` method. Supported in `PyGAD - 3.3.1 `__. +- 4 methods with names starting with ``run_``. Their purpose is to keep + the main loop inside the ``run()`` method clean. The details inside + the loop are moved to 4 individual methods. Generally, any method with + a name starting with ``run_`` is meant to be called by PyGAD from + inside the ``run()`` method. Supported in `PyGAD + 3.3.1 `__. - 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. + 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. + 2. ``run_crossover()``: Apply crossover and call the callable + ``on_crossover()`` if defined. - 3. ``run_mutation()``: Apply mutation and call the callable - ``on_mutation()`` if defined. + 3. ``run_mutation()``: Apply mutation and call the callable + ``on_mutation()`` if defined. - 4. ``run_update_population()``: Update the ``population`` attribute - after completing the processes of crossover and mutation. + 4. ``run_update_population()``: Update the ``population`` attribute + after completing the processes of crossover and mutation. The next sections discuss the methods available in the ``pygad.GA`` class. @@ -639,13 +629,13 @@ saved in the instance attribute named ``population``. Accepts the following parameters: -- ``low``: The lower value of the random range from which the gene - values in the initial population are selected. It defaults to -4. - Available in PyGAD 1.0.20 and higher. +- ``low``: The lower value of the random range from which the gene + values in the initial population are selected. It defaults to -4. + Available in PyGAD 1.0.20 and higher. -- ``high``: The upper value of the random range from which the gene - values in the initial population are selected. It defaults to -4. - Available in PyGAD 1.0.20. +- ``high``: The upper value of the random range from which the gene + values in the initial population are selected. It defaults to -4. + Available in PyGAD 1.0.20. This method assigns the values of the following 3 instance attributes: @@ -731,20 +721,20 @@ the ``pygad.GA`` class constructor. After the generation completes, the following takes place: -- The ``population`` attribute is updated by the new population. +- The ``population`` attribute is updated by the new population. -- The ``generations_completed`` attribute is assigned by the number of - the last completed generation. +- The ``generations_completed`` attribute is assigned by the number of + the last completed generation. -- If there is a callback function assigned to the ``on_generation`` - attribute, then it will be called. +- If there is a callback function assigned to the ``on_generation`` + attribute, then it will be called. After the ``run()`` method completes, the following takes place: -- The ``best_solution_generation`` is assigned the generation number at - which the best fitness value is reached. +- The ``best_solution_generation`` is assigned the generation number at + which the best fitness value is reached. -- The ``run_completed`` attribute is set to ``True``. +- The ``run_completed`` attribute is set to ``True``. Parent Selection Methods ------------------------ @@ -754,10 +744,10 @@ module has several methods for selecting the parents that will mate to produce the offspring. All of such methods accept the same parameters which are: -- ``fitness``: The fitness values of the solutions in the current - population. +- ``fitness``: The fitness values of the solutions in the current + population. -- ``num_parents``: The number of parents to be selected. +- ``num_parents``: The number of parents to be selected. All of such methods return an array of the selected parents. @@ -831,9 +821,9 @@ The ``Crossover`` class in the ``pygad.utils.crossover`` module supports several methods for applying crossover between the selected parents. All of these methods accept the same parameters which are: -- ``parents``: The parents to mate for producing the offspring. +- ``parents``: The parents to mate for producing the offspring. -- ``offspring_size``: The size of the offspring to produce. +- ``offspring_size``: The size of the offspring to produce. All of such methods return an array of the produced offspring. @@ -878,7 +868,7 @@ The ``Mutation`` class in the ``pygad.utils.mutation`` module supports several methods for applying mutation. All of these methods accept the same parameter which is: -- ``offspring``: The offspring to mutate. +- ``offspring``: The offspring to mutate. All of such methods return an array of the mutated offspring. @@ -942,19 +932,19 @@ algorithm. It accepts the following parameters: -- ``pop_fitness=None``: An optional parameter that accepts a list of - the fitness values of the solutions in the population. If ``None``, - then the ``cal_pop_fitness()`` method is called to calculate the - fitness values of the population. +- ``pop_fitness=None``: An optional parameter that accepts a list of the + fitness values of the solutions in the population. If ``None``, then + the ``cal_pop_fitness()`` method is called to calculate the fitness + values of the population. It returns the following: -- ``best_solution``: Best solution in the current population. +- ``best_solution``: Best solution in the current population. -- ``best_solution_fitness``: Fitness value of the best solution. +- ``best_solution_fitness``: Fitness value of the best solution. -- ``best_match_idx``: Index of the best solution in the current - population. +- ``best_match_idx``: Index of the best solution in the current + population. .. _plotfitness: @@ -1008,8 +998,8 @@ Saves the genetic algorithm instance Accepts the following parameter: -- ``filename``: Name of the file to save the instance. No extension is - needed. +- ``filename``: Name of the file to save the instance. No extension is + needed. Functions in ``pygad`` ====================== @@ -1029,8 +1019,8 @@ be called by the pygad module as follows: ``pygad.load(filename)``. Accepts the following parameter: -- ``filename``: Name of the file holding the saved instance of the - genetic algorithm. No extension is needed. +- ``filename``: Name of the file holding the saved instance of the + genetic algorithm. No extension is needed. Returns the genetic algorithm instance. @@ -1076,16 +1066,16 @@ solution with a low value. The fitness function is where the user can decide whether the optimization problem is single-objective or multi-objective. -- If the fitness function returns a numeric value, then the problem is - single-objective. The numeric data types supported by PyGAD are - listed in the ``supported_int_float_types`` variable of the - ``pygad.GA`` class. +- If the fitness function returns a numeric value, then the problem is + single-objective. The numeric data types supported by PyGAD are listed + in the ``supported_int_float_types`` variable of the ``pygad.GA`` + class. -- If the fitness function returns a ``list``, ``tuple``, or - ``numpy.ndarray``, then the problem is multi-objective. Even if there - is only one element, the problem is still considered multi-objective. - Each element represents the fitness value of its corresponding - objective. +- If the fitness function returns a ``list``, ``tuple``, or + ``numpy.ndarray``, then the problem is multi-objective. Even if there + is only one element, the problem is still considered multi-objective. + Each element represents the fitness value of its corresponding + objective. Using a user-defined fitness function allows the user to freely use PyGAD to solve any problem by passing the appropriate fitness @@ -1268,8 +1258,7 @@ generations. ga_instance.plot_fitness() -.. image:: https://user-images.githubusercontent.com/16560492/78830005-93111d00-79e7-11ea-9d8e-a8d8325a6101.png - :alt: +|image1| Information about the Best Solution ----------------------------------- @@ -1277,11 +1266,11 @@ Information about the Best Solution The following information about the best solution in the last population is returned using the ``best_solution()`` method. -- Solution +- Solution -- Fitness value of the solution +- Fitness value of the solution -- Index of the solution within the population +- Index of the solution within the population .. code:: python @@ -1340,8 +1329,7 @@ instance of the ``pygad.GA`` class. Note that PyGAD stops when either all generations are completed or when the function passed to the ``on_generation`` parameter returns the string ``stop``. -.. image:: https://user-images.githubusercontent.com/16560492/220486073-c5b6089d-81e4-44d9-a53c-385f479a7273.jpg - :alt: +|image2| The next code implements all the callback functions to trace the execution of the genetic algorithm. Each callback function prints its @@ -1604,8 +1592,7 @@ This is the figure created by the ``plot_fitness()`` method. The fitness of the first objective has the green color. The blue color is used for the second objective fitness. -.. image:: https://github.com/ahmedfgad/GeneticAlgorithmPython/assets/16560492/7896f8d8-01c5-4ff9-8d15-52191c309b63 - :alt: +|image3| Reproducing Images ------------------ @@ -1620,29 +1607,29 @@ For more information about this project, read this tutorial titled Python `__ available at these links: -- `Heartbeat `__: - https://heartbeat.fritz.ai/reproducing-images-using-a-genetic-algorithm-with-python-91fc701ff84 +- `Heartbeat `__: + https://heartbeat.fritz.ai/reproducing-images-using-a-genetic-algorithm-with-python-91fc701ff84 -- `LinkedIn `__: - https://www.linkedin.com/pulse/reproducing-images-using-genetic-algorithm-python-ahmed-gad +- `LinkedIn `__: + https://www.linkedin.com/pulse/reproducing-images-using-genetic-algorithm-python-ahmed-gad Project Steps ~~~~~~~~~~~~~ The steps to follow in order to reproduce an image are as follows: -- Read an image +- Read an image -- Prepare the fitness function +- Prepare the fitness function -- Create an instance of the pygad.GA class with the appropriate - parameters +- Create an instance of the pygad.GA class with the appropriate + parameters -- Run PyGAD +- Run PyGAD -- Plot results +- Plot results -- Calculate some statistics +- Calculate some statistics The next sections discusses the code of each of these steps. @@ -1663,8 +1650,7 @@ to the next code. Here is the read image. -.. image:: https://user-images.githubusercontent.com/16560492/36948808-f0ac882e-1fe8-11e8-8d07-1307e3477fd0.jpg - :alt: +|image4| Based on the chromosome representation used in the example, the pixel values can be either in the 0-255, 0-1, or any other ranges. @@ -1779,8 +1765,7 @@ generations can be viewed in a plot using the ``plot_fitness()`` method. Here is the plot after 20,000 generations. -.. image:: https://user-images.githubusercontent.com/16560492/82232124-77762c00-992e-11ea-9fc6-14a1cd7a04ff.png - :alt: +|image5| Calculate Some Statistics ~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -1807,14 +1792,12 @@ Evolution by Generation The solution reached after the 20,000 generations is shown below. -.. image:: https://user-images.githubusercontent.com/16560492/82232405-e0f63a80-992e-11ea-984f-b6ed76465bd1.png - :alt: +|image6| After more generations, the result can be enhanced like what shown below. -.. image:: https://user-images.githubusercontent.com/16560492/82232345-cf149780-992e-11ea-8390-bf1a57a19de7.png - :alt: +|image7| The results can also be enhanced by changing the parameters passed to the constructor of the ``pygad.GA`` class. @@ -1824,38 +1807,31 @@ Here is how the image is evolved from generation 0 to generation Generation 0 -.. image:: https://user-images.githubusercontent.com/16560492/36948589-b47276f0-1fe5-11e8-8efe-0cd1a225ea3a.png - :alt: +|image8| Generation 1,000 -.. image:: https://user-images.githubusercontent.com/16560492/36948823-16f490ee-1fe9-11e8-97db-3e8905ad5440.png - :alt: +|image9| Generation 2,500 -.. image:: https://user-images.githubusercontent.com/16560492/36948832-3f314b60-1fe9-11e8-8f4a-4d9a53b99f3d.png - :alt: +|image10| Generation 4,500 -.. image:: https://user-images.githubusercontent.com/16560492/36948837-53d1849a-1fe9-11e8-9b36-e9e9291e347b.png - :alt: +|image11| Generation 7,000 -.. image:: https://user-images.githubusercontent.com/16560492/36948852-66f1b176-1fe9-11e8-9f9b-460804e94004.png - :alt: +|image12| Generation 8,000 -.. image:: https://user-images.githubusercontent.com/16560492/36948865-7fbb5158-1fe9-11e8-8c04-8ac3c1f7b1b1.png - :alt: +|image13| Generation 20,000 -.. image:: https://user-images.githubusercontent.com/16560492/82232405-e0f63a80-992e-11ea-984f-b6ed76465bd1.png - :alt: +|image14| Clustering ---------- @@ -1886,3 +1862,18 @@ for how the genetic algorithm plays CoinTex: https://blog.paperspace.com/building-agent-for-cointex-using-genetic-algorithm. Check also this `YouTube video `__ showing the genetic algorithm while playing CoinTex. + +.. |image1| image:: https://user-images.githubusercontent.com/16560492/78830005-93111d00-79e7-11ea-9d8e-a8d8325a6101.png +.. |image2| image:: https://user-images.githubusercontent.com/16560492/220486073-c5b6089d-81e4-44d9-a53c-385f479a7273.jpg +.. |image3| image:: https://github.com/ahmedfgad/GeneticAlgorithmPython/assets/16560492/7896f8d8-01c5-4ff9-8d15-52191c309b63 +.. |image4| image:: https://user-images.githubusercontent.com/16560492/36948808-f0ac882e-1fe8-11e8-8d07-1307e3477fd0.jpg +.. |image5| image:: https://user-images.githubusercontent.com/16560492/82232124-77762c00-992e-11ea-9fc6-14a1cd7a04ff.png +.. |image6| image:: https://user-images.githubusercontent.com/16560492/82232405-e0f63a80-992e-11ea-984f-b6ed76465bd1.png +.. |image7| image:: https://user-images.githubusercontent.com/16560492/82232345-cf149780-992e-11ea-8390-bf1a57a19de7.png +.. |image8| image:: https://user-images.githubusercontent.com/16560492/36948589-b47276f0-1fe5-11e8-8efe-0cd1a225ea3a.png +.. |image9| image:: https://user-images.githubusercontent.com/16560492/36948823-16f490ee-1fe9-11e8-97db-3e8905ad5440.png +.. |image10| image:: https://user-images.githubusercontent.com/16560492/36948832-3f314b60-1fe9-11e8-8f4a-4d9a53b99f3d.png +.. |image11| image:: https://user-images.githubusercontent.com/16560492/36948837-53d1849a-1fe9-11e8-9b36-e9e9291e347b.png +.. |image12| image:: https://user-images.githubusercontent.com/16560492/36948852-66f1b176-1fe9-11e8-9f9b-460804e94004.png +.. |image13| image:: https://user-images.githubusercontent.com/16560492/36948865-7fbb5158-1fe9-11e8-8c04-8ac3c1f7b1b1.png +.. |image14| image:: https://user-images.githubusercontent.com/16560492/82232405-e0f63a80-992e-11ea-984f-b6ed76465bd1.png diff --git a/docs/source/releases.rst b/docs/source/releases.rst index 5ad1ec0..fddddf9 100644 --- a/docs/source/releases.rst +++ b/docs/source/releases.rst @@ -1,8 +1,7 @@ Release History =============== -.. image:: https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png - :alt: +|image1| .. _pygad-1017: @@ -1551,6 +1550,81 @@ Release Date 17 February 2024 Methods `__ section for more information. +.. _pygad-340: + +PyGAD 3.4.0 +----------- + +Release Date 07 January 2025 + +1. The ``delay_after_gen`` parameter is removed from the ``pygad.GA`` + class constructor. As a result, it is no longer an attribute of the + ``pygad.GA`` class instances. To add a delay after each generation, + apply it inside the ``on_generation`` callback. + https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/283 + +2. In the ``single_point_crossover()`` method of the + ``pygad.utils.crossover.Crossover`` class, all the random crossover + points are returned before the ``for`` loop. This is by calling the + ``numpy.random.randint()`` function only once before the loop to + generate all the K points (where K is the offspring size). This is + compared to calling the ``numpy.random.randint()`` function inside + the ``for`` loop K times, once for each individual offspring. + +3. Bug fix in the ``examples/example_custom_operators.py`` script. + https://github.com/ahmedfgad/GeneticAlgorithmPython/pull/285 + +4. While making prediction using the ``pygad.torchga.predict()`` + function, no gradients are calculated. + +5. The ``gene_type`` parameter of the + ``pygad.helper.unique.Unique.unique_int_gene_from_range()`` method + accepts the type of the current gene only instead of the full + gene_type list. + +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. + +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. + +8. Refactoring the ``pygad/helper/unique.py`` script to remove + duplicate codes and reformatting the docstrings. + +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 + +10. Fix a bug converting a nested NumPy array to a nested list. + https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/300 + +11. 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 + +12. 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 + +13. Make sure ``self.best_solutions`` is a list of lists inside the + ``cal_pop_fitness`` method. + https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/293 + +14. Fix a bug where the ``cal_pop_fitness()`` method was using the + ``previous_generation_fitness`` attribute to return the parents + fitness. This instance attribute was not using the fitness of the + latest population, instead the fitness of the population before the + last one. The issue is solved by updating the + ``previous_generation_fitness`` attribute to the latest population + fitness before the GA completes. + https://github.com/ahmedfgad/GeneticAlgorithmPython/issues/291 + PyGAD Projects at GitHub ======================== @@ -1721,11 +1795,11 @@ section for more contact details. Within your message, please send the following details: -- Project title +- Project title -- Brief description +- Brief description -- Preferably, a link that directs the readers to your project +- Preferably, a link that directs the readers to your project Tutorials about PyGAD ===================== @@ -1851,7 +1925,7 @@ discussion includes building Keras models using either the Sequential Model or the Functional API, building an initial population of Keras model parameters, creating an appropriate fitness function, and more. -|image1| +|image2| `Train PyTorch Models Using Genetic Algorithm with PyGAD `__ --------------------------------------------------------------------------------------------------------------------------------------------- @@ -1871,7 +1945,7 @@ PyTorch models. It’s very easy to use, but there are a few tricky steps. So, in this tutorial, we’ll explore how to use PyGAD to train PyTorch models. -|image2| +|image3| `A Guide to Genetic ‘Learning’ Algorithms for Optimization `__ ------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1896,7 +1970,7 @@ simple pour résoudre le OpenAI CartPole Jeu. Dans cet article, nous allons former un simple réseau de neurones pour résoudre le OpenAI CartPole . J'utiliserai PyTorch et PyGAD . -|image3| +|image4| Spanish ------- @@ -1914,7 +1988,7 @@ resolver el Juego OpenAI CartPole. En este articulo, entrenaremos una red neuronal simple para resolver el OpenAI CartPole . Usare PyTorch y PyGAD . -|image4| +|image5| Korean ------ @@ -1922,7 +1996,7 @@ Korean `[PyGAD] Python 에서 Genetic Algorithm 을 사용해보기 `__ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -|image5| +|image6| 파이썬에서 genetic algorithm을 사용하는 패키지들을 다 사용해보진 않았지만, 확장성이 있어보이고, 시도할 일이 있어서 살펴봤다. @@ -1971,7 +2045,7 @@ Keras modellerini oluşturmayı, Keras model parametrelerinin ilk popülasyonunu oluşturmayı, uygun bir uygunluk işlevi oluşturmayı ve daha fazlasını içerir. -|image6| +|image7| Hungarian --------- @@ -2003,7 +2077,7 @@ hálózatunk 386 állítható paraméterrel rendelkezik, ezért a DNS-ünk itt populációnk egy 10x386 elemű mátrix lesz. Ezt adjuk át az 51. sorban az initial_population paraméterben. -|image7| +|image8| Russian ------- @@ -2032,77 +2106,75 @@ PyGAD разрабатывали на Python 3.7.3. Зависимости вк из изкейсов использования инструмента — оптимизация весов, которые удовлетворяют заданной функции. -|image8| +|image9| Research Papers using PyGAD =========================== A number of research papers used PyGAD and here are some of them: -- Alberto Meola, Manuel Winkler, Sören Weinrich, Metaheuristic - optimization of data preparation and machine learning hyperparameters - for prediction of dynamic methane production, Bioresource Technology, - Volume 372, 2023, 128604, ISSN 0960-8524. +- Alberto Meola, Manuel Winkler, Sören Weinrich, Metaheuristic + optimization of data preparation and machine learning hyperparameters + for prediction of dynamic methane production, Bioresource Technology, + Volume 372, 2023, 128604, ISSN 0960-8524. -- Jaros, Marta, and Jiri Jaros. "Performance-Cost Optimization of - Moldable Scientific Workflows." +- Jaros, Marta, and Jiri Jaros. "Performance-Cost Optimization of + Moldable Scientific Workflows." -- Thorat, Divya. "Enhanced genetic algorithm to reduce makespan of - multiple jobs in map-reduce application on serverless platform". - Diss. Dublin, National College of Ireland, 2020. +- Thorat, Divya. "Enhanced genetic algorithm to reduce makespan of + multiple jobs in map-reduce application on serverless platform". Diss. + Dublin, National College of Ireland, 2020. -- Koch, Chris, and Edgar Dobriban. "AttenGen: Generating Live - Attenuated Vaccine Candidates using Machine Learning." (2021). +- Koch, Chris, and Edgar Dobriban. "AttenGen: Generating Live Attenuated + Vaccine Candidates using Machine Learning." (2021). -- Bhardwaj, Bhavya, et al. "Windfarm optimization using Nelder-Mead and - Particle Swarm optimization." *2021 7th International Conference on - Electrical Energy Systems (ICEES)*. IEEE, 2021. +- Bhardwaj, Bhavya, et al. "Windfarm optimization using Nelder-Mead and + Particle Swarm optimization." *2021 7th International Conference on + Electrical Energy Systems (ICEES)*. IEEE, 2021. -- Bernardo, Reginald Christian S. and J. Said. “Towards a - model-independent reconstruction approach for late-time Hubble data.” - (2021). +- Bernardo, Reginald Christian S. and J. Said. “Towards a + model-independent reconstruction approach for late-time Hubble data.” + (2021). -- Duong, Tri Dung, Qian Li, and Guandong Xu. "Prototype-based - Counterfactual Explanation for Causal Classification." *arXiv - preprint arXiv:2105.00703* (2021). +- Duong, Tri Dung, Qian Li, and Guandong Xu. "Prototype-based + Counterfactual Explanation for Causal Classification." *arXiv preprint + arXiv:2105.00703* (2021). -- Farrag, Tamer Ahmed, and Ehab E. Elattar. "Optimized Deep Stacked - Long Short-Term Memory Network for Long-Term Load Forecasting." *IEEE - Access* 9 (2021): 68511-68522. +- Farrag, Tamer Ahmed, and Ehab E. Elattar. "Optimized Deep Stacked Long + Short-Term Memory Network for Long-Term Load Forecasting." *IEEE + Access* 9 (2021): 68511-68522. -- Antunes, E. D. O., Caetano, M. F., Marotta, M. A., Araujo, A., - Bondan, L., Meneguette, R. I., & Rocha Filho, G. P. (2021, August). - Soluções Otimizadas para o Problema de Localização de Máxima - Cobertura em Redes Militarizadas 4G/LTE. In *Anais do XXVI Workshop - de Gerência e Operação de Redes e Serviços* (pp. 152-165). SBC. +- Antunes, E. D. O., Caetano, M. F., Marotta, M. A., Araujo, A., Bondan, + L., Meneguette, R. I., & Rocha Filho, G. P. (2021, August). Soluções + Otimizadas para o Problema de Localização de Máxima Cobertura em Redes + Militarizadas 4G/LTE. In *Anais do XXVI Workshop de Gerência e + Operação de Redes e Serviços* (pp. 152-165). SBC. -- M. Yani, F. Ardilla, A. A. Saputra and N. Kubota, "Gradient-Free Deep - Q-Networks Reinforcement learning: Benchmark and Evaluation," *2021 - IEEE Symposium Series on Computational Intelligence (SSCI)*, 2021, - pp. 1-5, doi: 10.1109/SSCI50451.2021.9659941. +- M. Yani, F. Ardilla, A. A. Saputra and N. Kubota, "Gradient-Free Deep + Q-Networks Reinforcement learning: Benchmark and Evaluation," *2021 + IEEE Symposium Series on Computational Intelligence (SSCI)*, 2021, pp. + 1-5, doi: 10.1109/SSCI50451.2021.9659941. -- Yani, Mohamad, and Naoyuki Kubota. "Deep Convolutional Networks with - Genetic Algorithm for Reinforcement Learning Problem." +- Yani, Mohamad, and Naoyuki Kubota. "Deep Convolutional Networks with + Genetic Algorithm for Reinforcement Learning Problem." -- Mahendra, Muhammad Ihza, and Isman Kurniawan. "Optimizing - Convolutional Neural Network by Using Genetic Algorithm for COVID-19 - Detection in Chest X-Ray Image." *2021 International Conference on - Data Science and Its Applications (ICoDSA)*. IEEE, 2021. +- Mahendra, Muhammad Ihza, and Isman Kurniawan. "Optimizing + Convolutional Neural Network by Using Genetic Algorithm for COVID-19 + Detection in Chest X-Ray Image." *2021 International Conference on + Data Science and Its Applications (ICoDSA)*. IEEE, 2021. -- Glibota, Vjeko. *Umjeravanje mikroskopskog prometnog modela primjenom - genetskog algoritma*. Diss. University of Zagreb. Faculty of - Transport and Traffic Sciences. Division of Intelligent Transport - Systems and Logistics. Department of Intelligent Transport Systems, - 2021. +- Glibota, Vjeko. *Umjeravanje mikroskopskog prometnog modela primjenom + genetskog algoritma*. Diss. University of Zagreb. Faculty of Transport + and Traffic Sciences. Division of Intelligent Transport Systems and + Logistics. Department of Intelligent Transport Systems, 2021. -- Zhu, Mingda. *Genetic Algorithm-based Parameter Identification for - Ship Manoeuvring Model under Wind Disturbance*. MS thesis. NTNU, - 2021. +- Zhu, Mingda. *Genetic Algorithm-based Parameter Identification for + Ship Manoeuvring Model under Wind Disturbance*. MS thesis. NTNU, 2021. -- Abdalrahman, Ahmed, and Weihua Zhuang. "Dynamic pricing for - differentiated pev charging services using deep reinforcement - learning." *IEEE Transactions on Intelligent Transportation Systems* - (2020). +- Abdalrahman, Ahmed, and Weihua Zhuang. "Dynamic pricing for + differentiated pev charging services using deep reinforcement + learning." *IEEE Transactions on Intelligent Transportation Systems* + (2020). More Links ========== @@ -2125,19 +2197,19 @@ titled `Genetic Algorithm Implementation in Python `__ available at these links: -- `LinkedIn `__ +- `LinkedIn `__ -- `Towards Data - Science `__ +- `Towards Data + Science `__ -- `KDnuggets `__ +- `KDnuggets `__ `This tutorial `__ is prepared based on a previous version of the project but it still a good resource to start with coding the genetic algorithm. -|image9| +|image10| Tutorial: Introduction to Genetic Algorithm ------------------------------------------- @@ -2147,14 +2219,14 @@ Get started with the genetic algorithm by reading the tutorial titled Algorithm `__ which is available at these links: -- `LinkedIn `__ +- `LinkedIn `__ -- `Towards Data - Science `__ +- `Towards Data + Science `__ -- `KDnuggets `__ +- `KDnuggets `__ -|image10| +|image11| Tutorial: Build Neural Networks in Python ----------------------------------------- @@ -2165,14 +2237,14 @@ Classification of the Fruits360 Image Dataset `__ available at these links: -- `LinkedIn `__ +- `LinkedIn `__ -- `Towards Data - Science `__ +- `Towards Data + Science `__ -- `KDnuggets `__ +- `KDnuggets `__ -|image11| +|image12| Tutorial: Optimize Neural Networks with Genetic Algorithm --------------------------------------------------------- @@ -2183,14 +2255,14 @@ Genetic Algorithm with Python `__ available at these links: -- `LinkedIn `__ +- `LinkedIn `__ -- `Towards Data - Science `__ +- `Towards Data + Science `__ -- `KDnuggets `__ +- `KDnuggets `__ -|image12| +|image13| Tutorial: Building CNN in Python -------------------------------- @@ -2200,21 +2272,21 @@ titled `Building Convolutional Neural Network using NumPy from Scratch `__ available at these links: -- `LinkedIn `__ +- `LinkedIn `__ -- `Towards Data - Science `__ +- `Towards Data + Science `__ -- `KDnuggets `__ +- `KDnuggets `__ -- `Chinese Translation `__ +- `Chinese Translation `__ `This tutorial `__) is prepared based on a previous version of the project but it still a good resource to start with coding CNNs. -|image13| +|image14| Tutorial: Derivation of CNN from FCNN ------------------------------------- @@ -2224,14 +2296,14 @@ Get started with the genetic algorithm by reading the tutorial titled Step-By-Step `__ which is available at these links: -- `LinkedIn `__ +- `LinkedIn `__ -- `Towards Data - Science `__ +- `Towards Data + Science `__ -- `KDnuggets `__ +- `KDnuggets `__ -|image14| +|image15| Book: Practical Computer Vision Applications Using Deep Learning with CNNs -------------------------------------------------------------------------- @@ -2244,69 +2316,70 @@ learning, genetic algorithm, and more. Find the book at these links: -- `Amazon `__ +- `Amazon `__ -- `Springer `__ +- `Springer `__ -- `Apress `__ +- `Apress `__ -- `O'Reilly `__ +- `O'Reilly `__ -- `Google Books `__ +- `Google Books `__ -.. image:: https://user-images.githubusercontent.com/16560492/78830077-ae7c2800-79e7-11ea-980b-53b6bd879eeb.jpg - :alt: +|image16| Contact Us ========== -- E-mail: ahmed.f.gad@gmail.com +- E-mail: ahmed.f.gad@gmail.com -- `LinkedIn `__ +- `LinkedIn `__ -- `Amazon Author Page `__ +- `Amazon Author Page `__ -- `Heartbeat `__ +- `Heartbeat `__ -- `Paperspace `__ +- `Paperspace `__ -- `KDnuggets `__ +- `KDnuggets `__ -- `TowardsDataScience `__ +- `TowardsDataScience `__ -- `GitHub `__ +- `GitHub `__ -.. image:: https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png - :alt: +|image17| Thank you for using `PyGAD `__ :) -.. |image1| image:: https://user-images.githubusercontent.com/16560492/111009628-2b372500-8362-11eb-90cf-01b47d831624.png +.. |image1| image:: https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png +.. |image2| image:: https://user-images.githubusercontent.com/16560492/111009628-2b372500-8362-11eb-90cf-01b47d831624.png :target: https://blog.paperspace.com/train-keras-models-using-genetic-algorithm-with-pygad -.. |image2| image:: https://user-images.githubusercontent.com/16560492/111009678-5457b580-8362-11eb-899a-39e2f96984df.png +.. |image3| image:: https://user-images.githubusercontent.com/16560492/111009678-5457b580-8362-11eb-899a-39e2f96984df.png :target: https://neptune.ai/blog/train-pytorch-models-using-genetic-algorithm-with-pygad -.. |image3| image:: https://user-images.githubusercontent.com/16560492/111009275-3178d180-8361-11eb-9e86-7fb1519acde7.png +.. |image4| image:: https://user-images.githubusercontent.com/16560492/111009275-3178d180-8361-11eb-9e86-7fb1519acde7.png :target: https://www.hebergementwebs.com/nouvelles/comment-les-algorithmes-genetiques-peuvent-rivaliser-avec-la-descente-de-gradient-et-le-backprop -.. |image4| image:: https://user-images.githubusercontent.com/16560492/111009257-232ab580-8361-11eb-99a5-7226efbc3065.png +.. |image5| image:: https://user-images.githubusercontent.com/16560492/111009257-232ab580-8361-11eb-99a5-7226efbc3065.png :target: https://www.hebergementwebs.com/noticias/como-los-algoritmos-geneticos-pueden-competir-con-el-descenso-de-gradiente-y-el-backprop -.. |image5| image:: https://user-images.githubusercontent.com/16560492/108586306-85bd0280-731b-11eb-874c-7ac4ce1326cd.jpg +.. |image6| image:: https://user-images.githubusercontent.com/16560492/108586306-85bd0280-731b-11eb-874c-7ac4ce1326cd.jpg :target: https://data-newbie.tistory.com/m/685 -.. |image6| image:: https://user-images.githubusercontent.com/16560492/108586601-85be0200-731d-11eb-98a4-161c75a1f099.jpg +.. |image7| image:: https://user-images.githubusercontent.com/16560492/108586601-85be0200-731d-11eb-98a4-161c75a1f099.jpg :target: https://erencan34.medium.com/pygad-ile-genetik-algoritmay%C4%B1-kullanarak-keras-modelleri-nas%C4%B1l-e%C4%9Fitilir-cf92639a478c -.. |image7| image:: https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png - :target: https://thebojda.medium.com/tensorflow-alapoz%C3%B3-10-24f7767d4a2c .. |image8| image:: https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png + :target: https://thebojda.medium.com/tensorflow-alapoz%C3%B3-10-24f7767d4a2c +.. |image9| image:: https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png :target: https://neurohive.io/ru/frameworki/pygad-biblioteka-dlya-implementacii-geneticheskogo-algoritma -.. |image9| image:: https://user-images.githubusercontent.com/16560492/78830052-a3c19300-79e7-11ea-8b9b-4b343ea4049c.png +.. |image10| image:: https://user-images.githubusercontent.com/16560492/78830052-a3c19300-79e7-11ea-8b9b-4b343ea4049c.png :target: https://www.linkedin.com/pulse/genetic-algorithm-implementation-python-ahmed-gad -.. |image10| image:: https://user-images.githubusercontent.com/16560492/82078259-26252d00-96e1-11ea-9a02-52a99e1054b9.jpg +.. |image11| image:: https://user-images.githubusercontent.com/16560492/82078259-26252d00-96e1-11ea-9a02-52a99e1054b9.jpg :target: https://www.linkedin.com/pulse/introduction-optimization-genetic-algorithm-ahmed-gad -.. |image11| image:: https://user-images.githubusercontent.com/16560492/82078281-30472b80-96e1-11ea-8017-6a1f4383d602.jpg +.. |image12| image:: https://user-images.githubusercontent.com/16560492/82078281-30472b80-96e1-11ea-8017-6a1f4383d602.jpg :target: https://www.linkedin.com/pulse/artificial-neural-network-implementation-using-numpy-fruits360-gad -.. |image12| image:: https://user-images.githubusercontent.com/16560492/82078300-376e3980-96e1-11ea-821c-aa6b8ceb44d4.jpg +.. |image13| image:: https://user-images.githubusercontent.com/16560492/82078300-376e3980-96e1-11ea-821c-aa6b8ceb44d4.jpg :target: https://www.linkedin.com/pulse/artificial-neural-networks-optimization-using-genetic-ahmed-gad -.. |image13| image:: https://user-images.githubusercontent.com/16560492/82431022-6c3a1200-9a8e-11ea-8f1b-b055196d76e3.png +.. |image14| image:: https://user-images.githubusercontent.com/16560492/82431022-6c3a1200-9a8e-11ea-8f1b-b055196d76e3.png :target: https://www.linkedin.com/pulse/building-convolutional-neural-network-using-numpy-from-ahmed-gad -.. |image14| image:: https://user-images.githubusercontent.com/16560492/82431369-db176b00-9a8e-11ea-99bd-e845192873fc.png +.. |image15| image:: https://user-images.githubusercontent.com/16560492/82431369-db176b00-9a8e-11ea-99bd-e845192873fc.png :target: https://www.linkedin.com/pulse/derivation-convolutional-neural-network-from-fully-connected-gad +.. |image16| image:: https://user-images.githubusercontent.com/16560492/78830077-ae7c2800-79e7-11ea-980b-53b6bd879eeb.jpg +.. |image17| image:: https://user-images.githubusercontent.com/16560492/101267295-c74c0180-375f-11eb-9ad0-f8e37bd796ce.png diff --git a/examples/example_multi_objective.py b/examples/example_multi_objective.py index b0ff1a2..048248e 100644 --- a/examples/example_multi_objective.py +++ b/examples/example_multi_objective.py @@ -54,6 +54,7 @@ def on_generation(ga_instance): ga_instance.run() ga_instance.plot_fitness(label=['Obj 1', 'Obj 2']) +ga_instance.plot_pareto_front_curve() # Returning the details of the best solution. solution, solution_fitness, solution_idx = ga_instance.best_solution(ga_instance.last_generation_fitness) diff --git a/pyproject.toml b/pyproject.toml index 7817470..aed6e1d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ build-backend = "setuptools.build_meta" [project] name = "pygad" -version = "3.3.1" +version = "3.4.0" description = "PyGAD: A Python Library for Building the Genetic Algorithm and Training Machine Learning Algoithms (Keras & PyTorch)." readme = {file = "README.md", content-type = "text/markdown"} requires-python = ">=3" diff --git a/setup.py b/setup.py index b9f88de..d9ec309 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pygad", - version="3.3.1", + version="3.4.0", author="Ahmed Fawzy Gad", install_requires=["numpy", "matplotlib", "cloudpickle",], author_email="ahmed.f.gad@gmail.com", From fd0e18f8fc79b8dd2df49420be8f70ffef1c7483 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 6 Feb 2025 15:44:54 -0500 Subject: [PATCH 29/79] Fix bugs with stop_criteria --- docs/source/pygad_more.rst | 4871 ++++++++++++++++++----------------- pygad/pygad.py | 43 +- tests/test_stop_criteria.py | 60 + 3 files changed, 2538 insertions(+), 2436 deletions(-) diff --git a/docs/source/pygad_more.rst b/docs/source/pygad_more.rst index 7e0d748..a992b1e 100644 --- a/docs/source/pygad_more.rst +++ b/docs/source/pygad_more.rst @@ -1,2417 +1,2454 @@ -More About PyGAD -================ - -Multi-Objective Optimization -============================ - -In `PyGAD -3.2.0 `__, -the library supports multi-objective optimization using the -non-dominated sorting genetic algorithm II (NSGA-II). The code is -exactly similar to the regular code used for single-objective -optimization except for 1 difference. It is the return value of the -fitness function. - -In single-objective optimization, the fitness function returns a single -numeric value. In this example, the variable ``fitness`` is expected to -be a numeric value. - -.. code:: python - - def fitness_func(ga_instance, solution, solution_idx): - ... - return fitness - -But in multi-objective optimization, the fitness function returns any of -these data types: - -1. ``list`` - -2. ``tuple`` - -3. ``numpy.ndarray`` - -.. code:: python - - def fitness_func(ga_instance, solution, solution_idx): - ... - return [fitness1, fitness2, ..., fitnessN] - -Whenever the fitness function returns an iterable of these data types, -then the problem is considered multi-objective. This holds even if there -is a single element in the returned iterable. - -Other than the fitness function, everything else could be the same in -both single and multi-objective problems. - -But it is recommended to use one of these 2 parent selection operators -to solve multi-objective problems: - -1. ``nsga2``: This selects the parents based on non-dominated sorting - and crowding distance. - -2. ``tournament_nsga2``: This selects the parents using tournament - selection which uses non-dominated sorting and crowding distance to - rank the solutions. - -This is a multi-objective optimization example that optimizes these 2 -linear functions: - -1. ``y1 = f(w1:w6) = w1x1 + w2x2 + w3x3 + w4x4 + w5x5 + 6wx6`` - -2. ``y2 = f(w1:w6) = w1x7 + w2x8 + w3x9 + w4x10 + w5x11 + 6wx12`` - -Where: - -1. ``(x1,x2,x3,x4,x5,x6)=(4,-2,3.5,5,-11,-4.7)`` and ``y=50`` - -2. ``(x7,x8,x9,x10,x11,x12)=(-2,0.7,-9,1.4,3,5)`` and ``y=30`` - -The 2 functions use the same parameters (weights) ``w1`` to ``w6``. - -The goal is to use PyGAD to find the optimal values for such weights -that satisfy the 2 functions ``y1`` and ``y2``. - -.. code:: python - - import pygad - import numpy - - """ - Given these 2 functions: - y1 = f(w1:w6) = w1x1 + w2x2 + w3x3 + w4x4 + w5x5 + 6wx6 - y2 = f(w1:w6) = w1x7 + w2x8 + w3x9 + w4x10 + w5x11 + 6wx12 - where (x1,x2,x3,x4,x5,x6)=(4,-2,3.5,5,-11,-4.7) and y=50 - and (x7,x8,x9,x10,x11,x12)=(-2,0.7,-9,1.4,3,5) and y=30 - 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. - This is a multi-objective optimization problem. - - PyGAD considers the problem as multi-objective if the fitness function returns: - 1) List. - 2) Or tuple. - 3) Or numpy.ndarray. - """ - - function_inputs1 = [4,-2,3.5,5,-11,-4.7] # Function 1 inputs. - function_inputs2 = [-2,0.7,-9,1.4,3,5] # Function 2 inputs. - desired_output1 = 50 # Function 1 output. - desired_output2 = 30 # Function 2 output. - - def fitness_func(ga_instance, solution, solution_idx): - output1 = numpy.sum(solution*function_inputs1) - output2 = numpy.sum(solution*function_inputs2) - fitness1 = 1.0 / (numpy.abs(output1 - desired_output1) + 0.000001) - fitness2 = 1.0 / (numpy.abs(output2 - desired_output2) + 0.000001) - return [fitness1, fitness2] - - num_generations = 100 - num_parents_mating = 10 - - sol_per_pop = 20 - num_genes = len(function_inputs1) - - ga_instance = pygad.GA(num_generations=num_generations, - num_parents_mating=num_parents_mating, - sol_per_pop=sol_per_pop, - num_genes=num_genes, - fitness_func=fitness_func, - parent_selection_type='nsga2') - - ga_instance.run() - - ga_instance.plot_fitness(label=['Obj 1', 'Obj 2']) - - solution, solution_fitness, solution_idx = ga_instance.best_solution(ga_instance.last_generation_fitness) - print(f"Parameters of the best solution : {solution}") - print(f"Fitness value of the best solution = {solution_fitness}") - - prediction = numpy.sum(numpy.array(function_inputs1)*solution) - print(f"Predicted output 1 based on the best solution : {prediction}") - prediction = numpy.sum(numpy.array(function_inputs2)*solution) - print(f"Predicted output 2 based on the best solution : {prediction}") - -This is the result of the print statements. The predicted outputs are -close to the desired outputs. - -.. code:: - - Parameters of the best solution : [ 0.79676439 -2.98823386 -4.12677662 5.70539445 -2.02797016 -1.07243922] - Fitness value of the best solution = [ 1.68090829 349.8591915 ] - Predicted output 1 based on the best solution : 50.59491545442283 - Predicted output 2 based on the best solution : 29.99714270722312 - -This is the figure created by the ``plot_fitness()`` method. The fitness -of the first objective has the green color. The blue color is used for -the second objective fitness. - -.. image:: https://github.com/ahmedfgad/GeneticAlgorithmPython/assets/16560492/7896f8d8-01c5-4ff9-8d15-52191c309b63 - :alt: - -.. _limit-the-gene-value-range-using-the-genespace-parameter: - -Limit the Gene Value Range using the ``gene_space`` Parameter -============================================================= - -In `PyGAD -2.11.0 `__, -the ``gene_space`` parameter supported a new feature to allow -customizing the range of accepted values for each gene. Let's take a -quick review of the ``gene_space`` parameter to build over it. - -The ``gene_space`` parameter allows the user to feed the space of values -of each gene. This way the accepted values for each gene is retracted to -the user-defined values. Assume there is a problem that has 3 genes -where each gene has different set of values as follows: - -1. Gene 1: ``[0.4, 12, -5, 21.2]`` - -2. Gene 2: ``[-2, 0.3]`` - -3. Gene 3: ``[1.2, 63.2, 7.4]`` - -Then, the ``gene_space`` for this problem is as given below. Note that -the order is very important. - -.. code:: python - - gene_space = [[0.4, 12, -5, 21.2], - [-2, 0.3], - [1.2, 63.2, 7.4]] - -In case all genes share the same set of values, then simply feed a -single list to the ``gene_space`` parameter as follows. In this case, -all genes can only take values from this list of 6 values. - -.. code:: python - - gene_space = [33, 7, 0.5, 95. 6.3, 0.74] - -The previous example restricts the gene values to just a set of fixed -number of discrete values. In case you want to use a range of discrete -values to the gene, then you can use the ``range()`` function. For -example, ``range(1, 7)`` means the set of allowed values for the gene -are ``1, 2, 3, 4, 5, and 6``. You can also use the ``numpy.arange()`` or -``numpy.linspace()`` functions for the same purpose. - -The previous discussion only works with a range of discrete values not -continuous values. In `PyGAD -2.11.0 `__, -the ``gene_space`` parameter can be assigned a dictionary that allows -the gene to have values from a continuous range. - -Assuming you want to restrict the gene within this half-open range [1 to -5) where 1 is included and 5 is not. Then simply create a dictionary -with 2 items where the keys of the 2 items are: - -1. ``'low'``: The minimum value in the range which is 1 in the example. - -2. ``'high'``: The maximum value in the range which is 5 in the example. - -The dictionary will look like that: - -.. code:: python - - {'low': 1, - 'high': 5} - -It is not acceptable to add more than 2 items in the dictionary or use -other keys than ``'low'`` and ``'high'``. - -For a 3-gene problem, the next code creates a dictionary for each gene -to restrict its values in a continuous range. For the first gene, it can -take any floating-point value from the range that starts from 1 -(inclusive) and ends at 5 (exclusive). - -.. code:: python - - gene_space = [{'low': 1, 'high': 5}, {'low': 0.3, 'high': 1.4}, {'low': -0.2, 'high': 4.5}] - -.. _more-about-the-genespace-parameter: - -More about the ``gene_space`` Parameter -======================================= - -The ``gene_space`` parameter customizes the space of values of each -gene. - -Assuming that all genes have the same global space which include the -values 0.3, 5.2, -4, and 8, then those values can be assigned to the -``gene_space`` parameter as a list, tuple, or range. Here is a list -assigned to this parameter. By doing that, then the gene values are -restricted to those assigned to the ``gene_space`` parameter. - -.. code:: python - - gene_space = [0.3, 5.2, -4, 8] - -If some genes have different spaces, then ``gene_space`` should accept a -nested list or tuple. In this case, the elements could be: - -1. Number (of ``int``, ``float``, or ``NumPy`` data types): A single - value to be assigned to the gene. This means this gene will have the - same value across all generations. - -2. ``list``, ``tuple``, ``numpy.ndarray``, or any range like ``range``, - ``numpy.arange()``, or ``numpy.linspace``: It holds the space for - each individual gene. But this space is usually discrete. That is - there is a set of finite values to select from. - -3. ``dict``: To sample a value for a gene from a continuous range. The - dictionary must have 2 mandatory keys which are ``"low"`` and - ``"high"`` in addition to an optional key which is ``"step"``. A - random value is returned between the values assigned to the items - with ``"low"`` and ``"high"`` keys. If the ``"step"`` exists, then - this works as the previous options (i.e. discrete set of values). - -4. ``None``: A gene with its space set to ``None`` is initialized - randomly from the range specified by the 2 parameters - ``init_range_low`` and ``init_range_high``. For mutation, its value - is mutated based on a random value from the range specified by the 2 - parameters ``random_mutation_min_val`` and - ``random_mutation_max_val``. If all elements in the ``gene_space`` - parameter are ``None``, the parameter will not have any effect. - -Assuming that a chromosome has 2 genes and each gene has a different -value space. Then the ``gene_space`` could be assigned a nested -list/tuple where each element determines the space of a gene. - -According to the next code, the space of the first gene is ``[0.4, -5]`` -which has 2 values and the space for the second gene is -``[0.5, -3.2, 8.8, -9]`` which has 4 values. - -.. code:: python - - gene_space = [[0.4, -5], [0.5, -3.2, 8.2, -9]] - -For a 2 gene chromosome, if the first gene space is restricted to the -discrete values from 0 to 4 and the second gene is restricted to the -values from 10 to 19, then it could be specified according to the next -code. - -.. code:: python - - gene_space = [range(5), range(10, 20)] - -The ``gene_space`` can also be assigned to a single range, as given -below, where the values of all genes are sampled from the same range. - -.. code:: python - - gene_space = numpy.arange(15) - -The ``gene_space`` can be assigned a dictionary to sample a value from a -continuous range. - -.. code:: python - - gene_space = {"low": 4, "high": 30} - -A step also can be assigned to the dictionary. This works as if a range -is used. - -.. code:: python - - gene_space = {"low": 4, "high": 30, "step": 2.5} - -.. - - Setting a ``dict`` like ``{"low": 0, "high": 10}`` in the - ``gene_space`` means that random values from the continuous range [0, - 10) are sampled. Note that ``0`` is included but ``10`` is not - included while sampling. Thus, the maximum value that could be - returned is less than ``10`` like ``9.9999``. But if the user decided - to round the genes using, for example, ``[float, 2]``, then this - value will become 10. So, the user should be careful to the inputs. - -If a ``None`` is assigned to only a single gene, then its value will be -randomly generated initially using the ``init_range_low`` and -``init_range_high`` parameters in the ``pygad.GA`` class's constructor. -During mutation, the value are sampled from the range defined by the 2 -parameters ``random_mutation_min_val`` and ``random_mutation_max_val``. -This is an example where the second gene is given a ``None`` value. - -.. code:: python - - gene_space = [range(5), None, numpy.linspace(10, 20, 300)] - -If the user did not assign the initial population to the -``initial_population`` parameter, the initial population is created -randomly based on the ``gene_space`` parameter. Moreover, the mutation -is applied based on this parameter. - -.. _how-mutation-works-with-the-genespace-parameter: - -How Mutation Works with the ``gene_space`` Parameter? ------------------------------------------------------ - -Mutation changes based on whether the ``gene_space`` has a continuous -range or discrete set of values. - -If a gene has its **static/discrete space** defined in the -``gene_space`` parameter, then mutation works by replacing the gene -value by a value randomly selected from the gene space. This happens for -both ``int`` and ``float`` data types. - -For example, the following ``gene_space`` has the static space -``[1, 2, 3]`` defined for the first gene. So, this gene can only have a -value out of these 3 values. - -.. code:: python - - Gene space: [[1, 2, 3], - None] - Solution: [1, 5] - -For a solution like ``[1, 5]``, then mutation happens for the first gene -by simply replacing its current value by a randomly selected value -(other than its current value if possible). So, the value 1 will be -replaced by either 2 or 3. - -For the second gene, its space is set to ``None``. So, traditional -mutation happens for this gene by: - -1. Generating a random value from the range defined by the - ``random_mutation_min_val`` and ``random_mutation_max_val`` - parameters. - -2. Adding this random value to the current gene's value. - -If its current value is 5 and the random value is ``-0.5``, then the new -value is 4.5. If the gene type is integer, then the value will be -rounded. - -On the other hand, if a gene has a **continuous space** defined in the -``gene_space`` parameter, then mutation occurs by adding a random value -to the current gene value. - -For example, the following ``gene_space`` has the continuous space -defined by the dictionary ``{'low': 1, 'high': 5}``. This applies to all -genes. So, mutation is applied to one or more selected genes by adding a -random value to the current gene value. - -.. code:: python - - Gene space: {'low': 1, 'high': 5} - Solution: [1.5, 3.4] - -Assuming ``random_mutation_min_val=-1`` and -``random_mutation_max_val=1``, then a random value such as ``0.3`` can -be added to the gene(s) participating in mutation. If only the first -gene is mutated, then its new value changes from ``1.5`` to -``1.5+0.3=1.8``. Note that PyGAD verifies that the new value is within -the range. In the worst scenarios, the value will be set to either -boundary of the continuous range. For example, if the gene value is 1.5 -and the random value is -0.55, then the new value is 0.95 which smaller -than the lower boundary 1. Thus, the gene value will be rounded to 1. - -If the dictionary has a step like the example below, then it is -considered a discrete range and mutation occurs by randomly selecting a -value from the set of values. In other words, no random value is added -to the gene value. - -.. code:: python - - Gene space: {'low': 1, 'high': 5, 'step': 0.5} - -Stop at Any Generation -====================== - -In `PyGAD -2.4.0 `__, -it is possible to stop the genetic algorithm after any generation. All -you need to do it to return the string ``"stop"`` in the callback -function ``on_generation``. When this callback function is implemented -and assigned to the ``on_generation`` parameter in the constructor of -the ``pygad.GA`` class, then the algorithm immediately stops after -completing its current generation. Let's discuss an example. - -Assume that the user wants to stop algorithm either after the 100 -generations or if a condition is met. The user may assign a value of 100 -to the ``num_generations`` parameter of the ``pygad.GA`` class -constructor. - -The condition that stops the algorithm is written in a callback function -like the one in the next code. If the fitness value of the best solution -exceeds 70, then the string ``"stop"`` is returned. - -.. code:: python - - def func_generation(ga_instance): - if ga_instance.best_solution()[1] >= 70: - return "stop" - -Stop Criteria -============= - -In `PyGAD -2.15.0 `__, -a new parameter named ``stop_criteria`` is added to the constructor of -the ``pygad.GA`` class. It helps to stop the evolution based on some -criteria. It can be assigned to one or more criterion. - -Each criterion is passed as ``str`` that consists of 2 parts: - -1. Stop word. - -2. Number. - -It takes this form: - -.. code:: python - - "word_num" - -The current 2 supported words are ``reach`` and ``saturate``. - -The ``reach`` word stops the ``run()`` method if the fitness value is -equal to or greater than a given fitness value. An example for ``reach`` -is ``"reach_40"`` which stops the evolution if the fitness is >= 40. - -``saturate`` stops the evolution if the fitness saturates for a given -number of consecutive generations. An example for ``saturate`` is -``"saturate_7"`` which means stop the ``run()`` method if the fitness -does not change for 7 consecutive generations. - -Here is an example that stops the evolution if either the fitness value -reached ``127.4`` or if the fitness saturates for ``15`` generations. - -.. code:: python - - import pygad - import numpy - - equation_inputs = [4, -2, 3.5, 8, 9, 4] - desired_output = 44 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution * equation_inputs) - - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - - return fitness - - ga_instance = pygad.GA(num_generations=200, - sol_per_pop=10, - num_parents_mating=4, - num_genes=len(equation_inputs), - fitness_func=fitness_func, - stop_criteria=["reach_127.4", "saturate_15"]) - - ga_instance.run() - print(f"Number of generations passed is {ga_instance.generations_completed}") - -Elitism Selection -================= - -In `PyGAD -2.18.0 `__, -a new parameter called ``keep_elitism`` is supported. It accepts an -integer to define the number of elitism (i.e. best solutions) to keep in -the next generation. This parameter defaults to ``1`` which means only -the best solution is kept in the next generation. - -In the next example, the ``keep_elitism`` parameter in the constructor -of the ``pygad.GA`` class is set to 2. Thus, the best 2 solutions in -each generation are kept in the next generation. - -.. code:: python - - import numpy - import pygad - - function_inputs = [4,-2,3.5,5,-11,-4.7] - desired_output = 44 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution*function_inputs) - fitness = 1.0 / numpy.abs(output - desired_output) - return fitness - - ga_instance = pygad.GA(num_generations=2, - num_parents_mating=3, - fitness_func=fitness_func, - num_genes=6, - sol_per_pop=5, - keep_elitism=2) - - ga_instance.run() - -The value passed to the ``keep_elitism`` parameter must satisfy 2 -conditions: - -1. It must be ``>= 0``. - -2. It must be ``<= sol_per_pop``. That is its value cannot exceed the - number of solutions in the current population. - -In the previous example, if the ``keep_elitism`` parameter is set equal -to the value passed to the ``sol_per_pop`` parameter, which is 5, then -there will be no evolution at all as in the next figure. This is because -all the 5 solutions are used as elitism in the next generation and no -offspring will be created. - -.. code:: python - - ... - - ga_instance = pygad.GA(..., - sol_per_pop=5, - keep_elitism=5) - - ga_instance.run() - -.. image:: https://user-images.githubusercontent.com/16560492/189273225-67ffad41-97ab-45e1-9324-429705e17b20.png - :alt: - -Note that if the ``keep_elitism`` parameter is effective (i.e. is -assigned a positive integer, not zero), then the ``keep_parents`` -parameter will have no effect. Because the default value of the -``keep_elitism`` parameter is 1, then the ``keep_parents`` parameter has -no effect by default. The ``keep_parents`` parameter is only effective -when ``keep_elitism=0``. - -Random Seed -=========== - -In `PyGAD -2.18.0 `__, -a new parameter called ``random_seed`` is supported. Its value is used -as a seed for the random function generators. - -PyGAD uses random functions in these 2 libraries: - -1. NumPy - -2. random - -The ``random_seed`` parameter defaults to ``None`` which means no seed -is used. As a result, different random numbers are generated for each -run of PyGAD. - -If this parameter is assigned a proper seed, then the results will be -reproducible. In the next example, the integer 2 is used as a random -seed. - -.. code:: python - - import numpy - import pygad - - function_inputs = [4,-2,3.5,5,-11,-4.7] - desired_output = 44 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution*function_inputs) - fitness = 1.0 / numpy.abs(output - desired_output) - return fitness - - ga_instance = pygad.GA(num_generations=2, - num_parents_mating=3, - fitness_func=fitness_func, - sol_per_pop=5, - num_genes=6, - random_seed=2) - - ga_instance.run() - best_solution, best_solution_fitness, best_match_idx = ga_instance.best_solution() - print(best_solution) - print(best_solution_fitness) - -This is the best solution found and its fitness value. - -.. code:: - - [ 2.77249188 -4.06570662 0.04196872 -3.47770796 -0.57502138 -3.22775267] - 0.04872203136549972 - -After running the code again, it will find the same result. - -.. code:: - - [ 2.77249188 -4.06570662 0.04196872 -3.47770796 -0.57502138 -3.22775267] - 0.04872203136549972 - -Continue without Losing Progress -================================ - -In `PyGAD -2.18.0 `__, -and thanks for `Felix Bernhard `__ for -opening `this GitHub -issue `__, -the values of these 4 instance attributes are no longer reset after each -call to the ``run()`` method. - -1. ``self.best_solutions`` - -2. ``self.best_solutions_fitness`` - -3. ``self.solutions`` - -4. ``self.solutions_fitness`` - -This helps the user to continue where the last run stopped without -losing the values of these 4 attributes. - -Now, the user can save the model by calling the ``save()`` method. - -.. code:: python - - import pygad - - def fitness_func(ga_instance, solution, solution_idx): - ... - return fitness - - ga_instance = pygad.GA(...) - - ga_instance.run() - - ga_instance.plot_fitness() - - ga_instance.save("pygad_GA") - -Then the saved model is loaded by calling the ``load()`` function. After -calling the ``run()`` method over the loaded instance, then the data -from the previous 4 attributes are not reset but extended with the new -data. - -.. code:: python - - import pygad - - def fitness_func(ga_instance, solution, solution_idx): - ... - return fitness - - loaded_ga_instance = pygad.load("pygad_GA") - - loaded_ga_instance.run() - - loaded_ga_instance.plot_fitness() - -The plot created by the ``plot_fitness()`` method will show the data -collected from both the runs. - -Note that the 2 attributes (``self.best_solutions`` and -``self.best_solutions_fitness``) only work if the -``save_best_solutions`` parameter is set to ``True``. Also, the 2 -attributes (``self.solutions`` and ``self.solutions_fitness``) only work -if the ``save_solutions`` parameter is ``True``. - -Change Population Size during Runtime -===================================== - -Starting from `PyGAD -3.3.0 `__, -the population size can changed during runtime. In other words, the -number of solutions/chromosomes and number of genes can be changed. - -The user has to carefully arrange the list of *parameters* and *instance -attributes* that have to be changed to keep the GA consistent before and -after changing the population size. Generally, change everything that -would be used during the GA evolution. - - CAUTION: If the user failed to change a parameter or an instance - attributes necessary to keep the GA running after the population size - changed, errors will arise. - -These are examples of the parameters that the user should decide whether -to change. The user should check the `list of -parameters `__ -and decide what to change. - -1. ``population``: The population. It *must* be changed. - -2. ``num_offspring``: The number of offspring to produce out of the - crossover and mutation operations. Change this parameter if the - number of offspring have to be changed to be consistent with the new - population size. - -3. ``num_parents_mating``: The number of solutions to select as parents. - Change this parameter if the number of parents have to be changed to - be consistent with the new population size. - -4. ``fitness_func``: If the way of calculating the fitness changes after - the new population size, then the fitness function have to be - changed. - -5. ``sol_per_pop``: The number of solutions per population. It is not - critical to change it but it is recommended to keep this number - consistent with the number of solutions in the ``population`` - parameter. - -These are examples of the instance attributes that might be changed. The -user should check the `list of instance -attributes `__ -and decide what to change. - -1. All the ``last_generation_*`` parameters - - 1. ``last_generation_fitness``: A 1D NumPy array of fitness values of - the population. - - 2. ``last_generation_parents`` and - ``last_generation_parents_indices``: Two NumPy arrays: 2D array - representing the parents and 1D array of the parents indices. - - 3. ``last_generation_elitism`` and - ``last_generation_elitism_indices``: Must be changed if - ``keep_elitism != 0``. The default value of ``keep_elitism`` is 1. - Two NumPy arrays: 2D array representing the elitism and 1D array - of the elitism indices. - -2. ``pop_size``: The population size. - -Prevent Duplicates in Gene Values -================================= - -In `PyGAD -2.13.0 `__, -a new bool parameter called ``allow_duplicate_genes`` is supported to -control whether duplicates are supported in the chromosome or not. In -other words, whether 2 or more genes might have the same exact value. - -If ``allow_duplicate_genes=True`` (which is the default case), genes may -have the same value. If ``allow_duplicate_genes=False``, then no 2 genes -will have the same value given that there are enough unique values for -the genes. - -The next code gives an example to use the ``allow_duplicate_genes`` -parameter. A callback generation function is implemented to print the -population after each generation. - -.. code:: python - - import pygad - - def fitness_func(ga_instance, solution, solution_idx): - return 0 - - def on_generation(ga): - print("Generation", ga.generations_completed) - print(ga.population) - - ga_instance = pygad.GA(num_generations=5, - sol_per_pop=5, - num_genes=4, - mutation_num_genes=3, - random_mutation_min_val=-5, - random_mutation_max_val=5, - num_parents_mating=2, - fitness_func=fitness_func, - gene_type=int, - on_generation=on_generation, - allow_duplicate_genes=False) - ga_instance.run() - -Here are the population after the 5 generations. Note how there are no -duplicate values. - -.. code:: python - - Generation 1 - [[ 2 -2 -3 3] - [ 0 1 2 3] - [ 5 -3 6 3] - [-3 1 -2 4] - [-1 0 -2 3]] - Generation 2 - [[-1 0 -2 3] - [-3 1 -2 4] - [ 0 -3 -2 6] - [-3 0 -2 3] - [ 1 -4 2 4]] - Generation 3 - [[ 1 -4 2 4] - [-3 0 -2 3] - [ 4 0 -2 1] - [-4 0 -2 -3] - [-4 2 0 3]] - Generation 4 - [[-4 2 0 3] - [-4 0 -2 -3] - [-2 5 4 -3] - [-1 2 -4 4] - [-4 2 0 -3]] - Generation 5 - [[-4 2 0 -3] - [-1 2 -4 4] - [ 3 4 -4 0] - [-1 0 2 -2] - [-4 2 -1 1]] - -The ``allow_duplicate_genes`` parameter is configured with use with the -``gene_space`` parameter. Here is an example where each of the 4 genes -has the same space of values that consists of 4 values (1, 2, 3, and 4). - -.. code:: python - - import pygad - - def fitness_func(ga_instance, solution, solution_idx): - return 0 - - def on_generation(ga): - print("Generation", ga.generations_completed) - print(ga.population) - - ga_instance = pygad.GA(num_generations=1, - sol_per_pop=5, - num_genes=4, - num_parents_mating=2, - fitness_func=fitness_func, - gene_type=int, - gene_space=[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]], - on_generation=on_generation, - allow_duplicate_genes=False) - ga_instance.run() - -Even that all the genes share the same space of values, no 2 genes -duplicate their values as provided by the next output. - -.. code:: python - - Generation 1 - [[2 3 1 4] - [2 3 1 4] - [2 4 1 3] - [2 3 1 4] - [1 3 2 4]] - Generation 2 - [[1 3 2 4] - [2 3 1 4] - [1 3 2 4] - [2 3 4 1] - [1 3 4 2]] - Generation 3 - [[1 3 4 2] - [2 3 4 1] - [1 3 4 2] - [3 1 4 2] - [3 2 4 1]] - Generation 4 - [[3 2 4 1] - [3 1 4 2] - [3 2 4 1] - [1 2 4 3] - [1 3 4 2]] - Generation 5 - [[1 3 4 2] - [1 2 4 3] - [2 1 4 3] - [1 2 4 3] - [1 2 4 3]] - -You should care of giving enough values for the genes so that PyGAD is -able to find alternatives for the gene value in case it duplicates with -another gene. - -There might be 2 duplicate genes where changing either of the 2 -duplicating genes will not solve the problem. For example, if -``gene_space=[[3, 0, 1], [4, 1, 2], [0, 2], [3, 2, 0]]`` and the -solution is ``[3 2 0 0]``, then the values of the last 2 genes -duplicate. There are no possible changes in the last 2 genes to solve -the problem. - -This problem can be solved by randomly changing one of the -non-duplicating genes that may make a room for a unique value in one the -2 duplicating genes. For example, by changing the second gene from 2 to -4, then any of the last 2 genes can take the value 2 and solve the -duplicates. The resultant gene is then ``[3 4 2 0]``. But this option is -not yet supported in PyGAD. - -Solve Duplicates using a Third Gene ------------------------------------ - -When ``allow_duplicate_genes=False`` and a user-defined ``gene_space`` -is used, it sometimes happen that there is no room to solve the -duplicates between the 2 genes by simply replacing the value of one gene -by another gene. In `PyGAD -3.1.0 `__, -the duplicates are solved by looking for a third gene that will help in -solving the duplicates. The following examples explain how it works. - -Example 1: - -Let's assume that this gene space is used and there is a solution with 2 -duplicate genes with the same value 4. - -.. code:: python - - Gene space: [[2, 3], - [3, 4], - [4, 5], - [5, 6]] - Solution: [3, 4, 4, 5] - -By checking the gene space, the second gene can have the values -``[3, 4]`` and the third gene can have the values ``[4, 5]``. To solve -the duplicates, we have the value of any of these 2 genes. - -If the value of the second gene changes from 4 to 3, then it will be -duplicate with the first gene. If we are to change the value of the -third gene from 4 to 5, then it will duplicate with the fourth gene. As -a conclusion, trying to just selecting a different gene value for either -the second or third genes will introduce new duplicating genes. - -When there are 2 duplicate genes but there is no way to solve their -duplicates, then the solution is to change a third gene that makes a -room to solve the duplicates between the 2 genes. - -In our example, duplicates between the second and third genes can be -solved by, for example,: - -- Changing the first gene from 3 to 2 then changing the second gene - from 4 to 3. - -- Or changing the fourth gene from 5 to 6 then changing the third gene - from 4 to 5. - -Generally, this is how to solve such duplicates: - -1. For any duplicate gene **GENE1**, select another value. - -2. Check which other gene **GENEX** has duplicate with this new value. - -3. Find if **GENEX** can have another value that will not cause any more - duplicates. If so, go to step 7. - -4. If all the other values of **GENEX** will cause duplicates, then try - another gene **GENEY**. - -5. Repeat steps 3 and 4 until exploring all the genes. - -6. If there is no possibility to solve the duplicates, then there is not - way to solve the duplicates and we have to keep the duplicate value. - -7. If a value for a gene **GENEM** is found that will not cause more - duplicates, then use this value for the gene **GENEM**. - -8. Replace the value of the gene **GENE1** by the old value of the gene - **GENEM**. This solves the duplicates. - -This is an example to solve the duplicate for the solution -``[3, 4, 4, 5]``: - -1. Let's use the second gene with value 4. Because the space of this - gene is ``[3, 4]``, then the only other value we can select is 3. - -2. The first gene also have the value 3. - -3. The first gene has another value 2 that will not cause more - duplicates in the solution. Then go to step 7. - -4. Skip. - -5. Skip. - -6. Skip. - -7. The value of the first gene 3 will be replaced by the new value 2. - The new solution is [2, 4, 4, 5]. - -8. Replace the value of the second gene 4 by the old value of the first - gene which is 3. The new solution is [2, 3, 4, 5]. The duplicate is - solved. - -Example 2: - -.. code:: python - - Gene space: [[0, 1], - [1, 2], - [2, 3], - [3, 4]] - Solution: [1, 2, 2, 3] - -The quick summary is: - -- Change the value of the first gene from 1 to 0. The solution becomes - [0, 2, 2, 3]. - -- Change the value of the second gene from 2 to 1. The solution becomes - [0, 1, 2, 3]. The duplicate is solved. - -.. _more-about-the-genetype-parameter: - -More about the ``gene_type`` Parameter -====================================== - -The ``gene_type`` parameter allows the user to control the data type for -all genes at once or each individual gene. In `PyGAD -2.15.0 `__, -the ``gene_type`` parameter also supports customizing the precision for -``float`` data types. As a result, the ``gene_type`` parameter helps to: - -1. Select a data type for all genes with or without precision. - -2. Select a data type for each individual gene with or without - precision. - -Let's discuss things by examples. - -Data Type for All Genes without Precision ------------------------------------------ - -The data type for all genes can be specified by assigning the numeric -data type directly to the ``gene_type`` parameter. This is an example to -make all genes of ``int`` data types. - -.. code:: python - - gene_type=int - -Given that the supported numeric data types of PyGAD include Python's -``int`` and ``float`` in addition to all numeric types of ``NumPy``, -then any of these types can be assigned to the ``gene_type`` parameter. - -If no precision is specified for a ``float`` data type, then the -complete floating-point number is kept. - -The next code uses an ``int`` data type for all genes where the genes in -the initial and final population are only integers. - -.. code:: python - - import pygad - import numpy - - equation_inputs = [4, -2, 3.5, 8, -2] - desired_output = 2671.1234 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution * equation_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - return fitness - - ga_instance = pygad.GA(num_generations=10, - sol_per_pop=5, - num_parents_mating=2, - num_genes=len(equation_inputs), - fitness_func=fitness_func, - gene_type=int) - - print("Initial Population") - print(ga_instance.initial_population) - - ga_instance.run() - - print("Final Population") - print(ga_instance.population) - -.. code:: python - - Initial Population - [[ 1 -1 2 0 -3] - [ 0 -2 0 -3 -1] - [ 0 -1 -1 2 0] - [-2 3 -2 3 3] - [ 0 0 2 -2 -2]] - - Final Population - [[ 1 -1 2 2 0] - [ 1 -1 2 2 0] - [ 1 -1 2 2 0] - [ 1 -1 2 2 0] - [ 1 -1 2 2 0]] - -Data Type for All Genes with Precision --------------------------------------- - -A precision can only be specified for a ``float`` data type and cannot -be specified for integers. Here is an example to use a precision of 3 -for the ``float`` data type. In this case, all genes are of type -``float`` and their maximum precision is 3. - -.. code:: python - - gene_type=[float, 3] - -The next code uses prints the initial and final population where the -genes are of type ``float`` with precision 3. - -.. code:: python - - import pygad - import numpy - - equation_inputs = [4, -2, 3.5, 8, -2] - desired_output = 2671.1234 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution * equation_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - - return fitness - - ga_instance = pygad.GA(num_generations=10, - sol_per_pop=5, - num_parents_mating=2, - num_genes=len(equation_inputs), - fitness_func=fitness_func, - gene_type=[float, 3]) - - print("Initial Population") - print(ga_instance.initial_population) - - ga_instance.run() - - print("Final Population") - print(ga_instance.population) - -.. code:: python - - Initial Population - [[-2.417 -0.487 3.623 2.457 -2.362] - [-1.231 0.079 -1.63 1.629 -2.637] - [ 0.692 -2.098 0.705 0.914 -3.633] - [ 2.637 -1.339 -1.107 -0.781 -3.896] - [-1.495 1.378 -1.026 3.522 2.379]] - - Final Population - [[ 1.714 -1.024 3.623 3.185 -2.362] - [ 0.692 -1.024 3.623 3.185 -2.362] - [ 0.692 -1.024 3.623 3.375 -2.362] - [ 0.692 -1.024 4.041 3.185 -2.362] - [ 1.714 -0.644 3.623 3.185 -2.362]] - -Data Type for each Individual Gene without Precision ----------------------------------------------------- - -In `PyGAD -2.14.0 `__, -the ``gene_type`` parameter allows customizing the gene type for each -individual gene. This is by using a ``list``/``tuple``/``numpy.ndarray`` -with number of elements equal to the number of genes. For each element, -a type is specified for the corresponding gene. - -This is an example for a 5-gene problem where different types are -assigned to the genes. - -.. code:: python - - gene_type=[int, float, numpy.float16, numpy.int8, float] - -This is a complete code that prints the initial and final population for -a custom-gene data type. - -.. code:: python - - import pygad - import numpy - - equation_inputs = [4, -2, 3.5, 8, -2] - desired_output = 2671.1234 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution * equation_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - return fitness - - ga_instance = pygad.GA(num_generations=10, - sol_per_pop=5, - num_parents_mating=2, - num_genes=len(equation_inputs), - fitness_func=fitness_func, - gene_type=[int, float, numpy.float16, numpy.int8, float]) - - print("Initial Population") - print(ga_instance.initial_population) - - ga_instance.run() - - print("Final Population") - print(ga_instance.population) - -.. code:: python - - Initial Population - [[0 0.8615522360026828 0.7021484375 -2 3.5301821368185866] - [-3 2.648189378595294 -3.830078125 1 -0.9586271572917742] - [3 3.7729827570110714 1.2529296875 -3 1.395741994211889] - [0 1.0490687178053282 1.51953125 -2 0.7243617940450235] - [0 -0.6550158436937226 -2.861328125 -2 1.8212734549263097]] - - Final Population - [[3 3.7729827570110714 2.055 0 0.7243617940450235] - [3 3.7729827570110714 1.458 0 -0.14638754050305036] - [3 3.7729827570110714 1.458 0 0.0869406120516778] - [3 3.7729827570110714 1.458 0 0.7243617940450235] - [3 3.7729827570110714 1.458 0 -0.14638754050305036]] - -Data Type for each Individual Gene with Precision -------------------------------------------------- - -The precision can also be specified for the ``float`` data types as in -the next line where the second gene precision is 2 and last gene -precision is 1. - -.. code:: python - - gene_type=[int, [float, 2], numpy.float16, numpy.int8, [float, 1]] - -This is a complete example where the initial and final populations are -printed where the genes comply with the data types and precisions -specified. - -.. code:: python - - import pygad - import numpy - - equation_inputs = [4, -2, 3.5, 8, -2] - desired_output = 2671.1234 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution * equation_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - return fitness - - ga_instance = pygad.GA(num_generations=10, - sol_per_pop=5, - num_parents_mating=2, - num_genes=len(equation_inputs), - fitness_func=fitness_func, - gene_type=[int, [float, 2], numpy.float16, numpy.int8, [float, 1]]) - - print("Initial Population") - print(ga_instance.initial_population) - - ga_instance.run() - - print("Final Population") - print(ga_instance.population) - -.. code:: python - - Initial Population - [[-2 -1.22 1.716796875 -1 0.2] - [-1 -1.58 -3.091796875 0 -1.3] - [3 3.35 -0.107421875 1 -3.3] - [-2 -3.58 -1.779296875 0 0.6] - [2 -3.73 2.65234375 3 -0.5]] - - Final Population - [[2 -4.22 3.47 3 -1.3] - [2 -3.73 3.47 3 -1.3] - [2 -4.22 3.47 2 -1.3] - [2 -4.58 3.47 3 -1.3] - [2 -3.73 3.47 3 -1.3]] - -Parallel Processing in PyGAD -============================ - -Starting from `PyGAD -2.17.0 `__, -parallel processing becomes supported. This section explains how to use -parallel processing in PyGAD. - -According to the `PyGAD -lifecycle `__, -parallel processing can be parallelized in only 2 operations: - -1. Population fitness calculation. - -2. Mutation. - -The reason is that the calculations in these 2 operations are -independent (i.e. each solution/chromosome is handled independently from -the others) and can be distributed across different processes or -threads. - -For the mutation operation, it does not do intensive calculations on the -CPU. Its calculations are simple like flipping the values of some genes -from 0 to 1 or adding a random value to some genes. So, it does not take -much CPU processing time. Experiments proved that parallelizing the -mutation operation across the solutions increases the time instead of -reducing it. This is because running multiple processes or threads adds -overhead to manage them. Thus, parallel processing cannot be applied on -the mutation operation. - -For the population fitness calculation, parallel processing can help -make a difference and reduce the processing time. But this is -conditional on the type of calculations done in the fitness function. If -the fitness function makes intensive calculations and takes much -processing time from the CPU, then it is probably that parallel -processing will help to cut down the overall time. - -This section explains how parallel processing works in PyGAD and how to -use parallel processing in PyGAD - -How to Use Parallel Processing in PyGAD ---------------------------------------- - -Starting from `PyGAD -2.17.0 `__, -a new parameter called ``parallel_processing`` added to the constructor -of the ``pygad.GA`` class. - -.. code:: python - - import pygad - ... - ga_instance = pygad.GA(..., - parallel_processing=...) - ... - -This parameter allows the user to do the following: - -1. Enable parallel processing. - -2. Select whether processes or threads are used. - -3. Specify the number of processes or threads to be used. - -These are 3 possible values for the ``parallel_processing`` parameter: - -1. ``None``: (Default) It means no parallel processing is used. - -2. A positive integer referring to the number of threads to be used - (i.e. threads, not processes, are used. - -3. ``list``/``tuple``: If a list or a tuple of exactly 2 elements is - assigned, then: - - 1. The first element can be either ``'process'`` or ``'thread'`` to - specify whether processes or threads are used, respectively. - - 2. The second element can be: - - 1. A positive integer to select the maximum number of processes or - threads to be used - - 2. ``0`` to indicate that 0 processes or threads are used. It - means no parallel processing. This is identical to setting - ``parallel_processing=None``. - - 3. ``None`` to use the default value as calculated by the - ``concurrent.futures module``. - -These are examples of the values assigned to the ``parallel_processing`` -parameter: - -- ``parallel_processing=4``: Because the parameter is assigned a - positive integer, this means parallel processing is activated where 4 - threads are used. - -- ``parallel_processing=["thread", 5]``: Use parallel processing with 5 - threads. This is identical to ``parallel_processing=5``. - -- ``parallel_processing=["process", 8]``: Use parallel processing with - 8 processes. - -- ``parallel_processing=["process", 0]``: As the second element is - given the value 0, this means do not use parallel processing. This is - identical to ``parallel_processing=None``. - -Examples --------- - -The examples will help you know the difference between using processes -and threads. Moreover, it will give an idea when parallel processing -would make a difference and reduce the time. These are dummy examples -where the fitness function is made to always return 0. - -The first example uses 10 genes, 5 solutions in the population where -only 3 solutions mate, and 9999 generations. The fitness function uses a -``for`` loop with 100 iterations just to have some calculations. In the -constructor of the ``pygad.GA`` class, ``parallel_processing=None`` -means no parallel processing is used. - -.. code:: python - - import pygad - import time - - def fitness_func(ga_instance, solution, solution_idx): - for _ in range(99): - pass - return 0 - - ga_instance = pygad.GA(num_generations=9999, - num_parents_mating=3, - sol_per_pop=5, - num_genes=10, - fitness_func=fitness_func, - suppress_warnings=True, - parallel_processing=None) - - if __name__ == '__main__': - t1 = time.time() - - ga_instance.run() - - t2 = time.time() - print("Time is", t2-t1) - -When parallel processing is not used, the time it takes to run the -genetic algorithm is ``1.5`` seconds. - -In the comparison, let's do a second experiment where parallel -processing is used with 5 threads. In this case, it take ``5`` seconds. - -.. code:: python - - ... - ga_instance = pygad.GA(..., - parallel_processing=5) - ... - -For the third experiment, processes instead of threads are used. Also, -only 99 generations are used instead of 9999. The time it takes is -``99`` seconds. - -.. code:: python - - ... - ga_instance = pygad.GA(num_generations=99, - ..., - parallel_processing=["process", 5]) - ... - -This is the summary of the 3 experiments: - -1. No parallel processing & 9999 generations: 1.5 seconds. - -2. Parallel processing with 5 threads & 9999 generations: 5 seconds - -3. Parallel processing with 5 processes & 99 generations: 99 seconds - -Because the fitness function does not need much CPU time, the normal -processing takes the least time. Running processes for this simple -problem takes 99 compared to only 5 seconds for threads because managing -processes is much heavier than managing threads. Thus, most of the CPU -time is for swapping the processes instead of executing the code. - -In the second example, the loop makes 99999999 iterations and only 5 -generations are used. With no parallelization, it takes 22 seconds. - -.. code:: python - - import pygad - import time - - def fitness_func(ga_instance, solution, solution_idx): - for _ in range(99999999): - pass - return 0 - - ga_instance = pygad.GA(num_generations=5, - num_parents_mating=3, - sol_per_pop=5, - num_genes=10, - fitness_func=fitness_func, - suppress_warnings=True, - parallel_processing=None) - - if __name__ == '__main__': - t1 = time.time() - ga_instance.run() - t2 = time.time() - print("Time is", t2-t1) - -It takes 15 seconds when 10 processes are used. - -.. code:: python - - ... - ga_instance = pygad.GA(..., - parallel_processing=["process", 10]) - ... - -This is compared to 20 seconds when 10 threads are used. - -.. code:: python - - ... - ga_instance = pygad.GA(..., - parallel_processing=["thread", 10]) - ... - -Based on the second example, using parallel processing with 10 processes -takes the least time because there is much CPU work done. Generally, -processes are preferred over threads when most of the work in on the -CPU. Threads are preferred over processes in some situations like doing -input/output operations. - -*Before releasing* `PyGAD -2.17.0 `__\ *,* -`László -Fazekas `__ -*wrote an article to parallelize the fitness function with PyGAD. Check -it:* `How Genetic Algorithms Can Compete with Gradient Descent and -Backprop `__. - -Print Lifecycle Summary -======================= - -In `PyGAD -2.19.0 `__, -a new method called ``summary()`` is supported. It prints a Keras-like -summary of the PyGAD lifecycle showing the steps, callback functions, -parameters, etc. - -This method accepts the following parameters: - -- ``line_length=70``: An integer representing the length of the single - line in characters. - -- ``fill_character=" "``: A character to fill the lines. - -- ``line_character="-"``: A character for creating a line separator. - -- ``line_character2="="``: A secondary character to create a line - separator. - -- ``columns_equal_len=False``: The table rows are split into - equal-sized columns or split subjective to the width needed. - -- ``print_step_parameters=True``: Whether to print extra parameters - about each step inside the step. If ``print_step_parameters=False`` - and ``print_parameters_summary=True``, then the parameters of each - step are printed at the end of the table. - -- ``print_parameters_summary=True``: Whether to print parameters - summary at the end of the table. If ``print_step_parameters=False``, - then the parameters of each step are printed at the end of the table - too. - -This is a quick example to create a PyGAD example. - -.. code:: python - - import pygad - import numpy - - function_inputs = [4,-2,3.5,5,-11,-4.7] - desired_output = 44 - - def genetic_fitness(solution, solution_idx): - output = numpy.sum(solution*function_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - return fitness - - def on_gen(ga): - pass - - def on_crossover_callback(a, b): - pass - - ga_instance = pygad.GA(num_generations=100, - num_parents_mating=10, - sol_per_pop=20, - num_genes=len(function_inputs), - on_crossover=on_crossover_callback, - on_generation=on_gen, - parallel_processing=2, - stop_criteria="reach_10", - fitness_batch_size=4, - crossover_probability=0.4, - fitness_func=genetic_fitness) - -Then call the ``summary()`` method to print the summary with the default -parameters. Note that entries for the crossover and generation callback -function are created because their callback functions are implemented -through the ``on_crossover_callback()`` and ``on_gen()``, respectively. - -.. code:: python - - ga_instance.summary() - -.. code:: bash - - ---------------------------------------------------------------------- - PyGAD Lifecycle - ====================================================================== - Step Handler Output Shape - ====================================================================== - Fitness Function genetic_fitness() (1) - Fitness batch size: 4 - ---------------------------------------------------------------------- - Parent Selection steady_state_selection() (10, 6) - Number of Parents: 10 - ---------------------------------------------------------------------- - Crossover single_point_crossover() (10, 6) - Crossover probability: 0.4 - ---------------------------------------------------------------------- - On Crossover on_crossover_callback() None - ---------------------------------------------------------------------- - Mutation random_mutation() (10, 6) - Mutation Genes: 1 - Random Mutation Range: (-1.0, 1.0) - Mutation by Replacement: False - Allow Duplicated Genes: True - ---------------------------------------------------------------------- - On Generation on_gen() None - Stop Criteria: [['reach', 10.0]] - ---------------------------------------------------------------------- - ====================================================================== - Population Size: (20, 6) - Number of Generations: 100 - Initial Population Range: (-4, 4) - Keep Elitism: 1 - Gene DType: [, None] - Parallel Processing: ['thread', 2] - Save Best Solutions: False - Save Solutions: False - ====================================================================== - -We can set the ``print_step_parameters`` and -``print_parameters_summary`` parameters to ``False`` to not print the -parameters. - -.. code:: python - - ga_instance.summary(print_step_parameters=False, - print_parameters_summary=False) - -.. code:: bash - - ---------------------------------------------------------------------- - PyGAD Lifecycle - ====================================================================== - Step Handler Output Shape - ====================================================================== - Fitness Function genetic_fitness() (1) - ---------------------------------------------------------------------- - Parent Selection steady_state_selection() (10, 6) - ---------------------------------------------------------------------- - Crossover single_point_crossover() (10, 6) - ---------------------------------------------------------------------- - On Crossover on_crossover_callback() None - ---------------------------------------------------------------------- - Mutation random_mutation() (10, 6) - ---------------------------------------------------------------------- - On Generation on_gen() None - ---------------------------------------------------------------------- - ====================================================================== - -Logging Outputs -=============== - -In `PyGAD -3.0.0 `__, -the ``print()`` statement is no longer used and the outputs are printed -using the `logging `__ -module. A a new parameter called ``logger`` is supported to accept the -user-defined logger. - -.. code:: python - - import logging - - logger = ... - - ga_instance = pygad.GA(..., - logger=logger, - ...) - -The default value for this parameter is ``None``. If there is no logger -passed (i.e. ``logger=None``), then a default logger is created to log -the messages to the console exactly like how the ``print()`` statement -works. - -Some advantages of using the the -`logging `__ module -instead of the ``print()`` statement are: - -1. The user has more control over the printed messages specially if - there is a project that uses multiple modules where each module - prints its messages. A logger can organize the outputs. - -2. Using the proper ``Handler``, the user can log the output messages to - files and not only restricted to printing it to the console. So, it - is much easier to record the outputs. - -3. The format of the printed messages can be changed by customizing the - ``Formatter`` assigned to the Logger. - -This section gives some quick examples to use the ``logging`` module and -then gives an example to use the logger with PyGAD. - -Logging to the Console ----------------------- - -This is an example to create a logger to log the messages to the -console. - -.. code:: python - - import logging - - # Create a logger - logger = logging.getLogger(__name__) - - # Set the logger level to debug so that all the messages are printed. - logger.setLevel(logging.DEBUG) - - # Create a stream handler to log the messages to the console. - stream_handler = logging.StreamHandler() - - # Set the handler level to debug. - stream_handler.setLevel(logging.DEBUG) - - # Create a formatter - formatter = logging.Formatter('%(message)s') - - # Add the formatter to handler. - stream_handler.setFormatter(formatter) - - # Add the stream handler to the logger - logger.addHandler(stream_handler) - -Now, we can log messages to the console with the format specified in the -``Formatter``. - -.. code:: python - - logger.debug('Debug message.') - logger.info('Info message.') - logger.warning('Warn message.') - logger.error('Error message.') - logger.critical('Critical message.') - -The outputs are identical to those returned using the ``print()`` -statement. - -.. code:: - - Debug message. - Info message. - Warn message. - Error message. - Critical message. - -By changing the format of the output messages, we can have more -information about each message. - -.. code:: python - - formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') - -This is a sample output. - -.. code:: python - - 2023-04-03 18:46:27 DEBUG: Debug message. - 2023-04-03 18:46:27 INFO: Info message. - 2023-04-03 18:46:27 WARNING: Warn message. - 2023-04-03 18:46:27 ERROR: Error message. - 2023-04-03 18:46:27 CRITICAL: Critical message. - -Note that you may need to clear the handlers after finishing the -execution. This is to make sure no cached handlers are used in the next -run. If the cached handlers are not cleared, then the single output -message may be repeated. - -.. code:: python - - logger.handlers.clear() - -Logging to a File ------------------ - -This is another example to log the messages to a file named -``logfile.txt``. The formatter prints the following about each message: - -1. The date and time at which the message is logged. - -2. The log level. - -3. The message. - -4. The path of the file. - -5. The lone number of the log message. - -.. code:: python - - import logging - - level = logging.DEBUG - name = 'logfile.txt' - - logger = logging.getLogger(name) - logger.setLevel(level) - - file_handler = logging.FileHandler(name, 'a+', 'utf-8') - file_handler.setLevel(logging.DEBUG) - file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s - %(pathname)s:%(lineno)d', datefmt='%Y-%m-%d %H:%M:%S') - file_handler.setFormatter(file_format) - logger.addHandler(file_handler) - -This is how the outputs look like. - -.. code:: python - - 2023-04-03 18:54:03 DEBUG: Debug message. - c:\users\agad069\desktop\logger\example2.py:46 - 2023-04-03 18:54:03 INFO: Info message. - c:\users\agad069\desktop\logger\example2.py:47 - 2023-04-03 18:54:03 WARNING: Warn message. - c:\users\agad069\desktop\logger\example2.py:48 - 2023-04-03 18:54:03 ERROR: Error message. - c:\users\agad069\desktop\logger\example2.py:49 - 2023-04-03 18:54:03 CRITICAL: Critical message. - c:\users\agad069\desktop\logger\example2.py:50 - -Consider clearing the handlers if necessary. - -.. code:: python - - logger.handlers.clear() - -Log to Both the Console and a File ----------------------------------- - -This is an example to create a single Logger associated with 2 handlers: - -1. A file handler. - -2. A stream handler. - -.. code:: python - - import logging - - level = logging.DEBUG - name = 'logfile.txt' - - logger = logging.getLogger(name) - logger.setLevel(level) - - file_handler = logging.FileHandler(name,'a+','utf-8') - file_handler.setLevel(logging.DEBUG) - file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s - %(pathname)s:%(lineno)d', datefmt='%Y-%m-%d %H:%M:%S') - file_handler.setFormatter(file_format) - logger.addHandler(file_handler) - - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.INFO) - console_format = logging.Formatter('%(message)s') - console_handler.setFormatter(console_format) - logger.addHandler(console_handler) - -When a log message is executed, then it is both printed to the console -and saved in the ``logfile.txt``. - -Consider clearing the handlers if necessary. - -.. code:: python - - logger.handlers.clear() - -PyGAD Example -------------- - -To use the logger in PyGAD, just create your custom logger and pass it -to the ``logger`` parameter. - -.. code:: python - - import logging - import pygad - import numpy - - level = logging.DEBUG - name = 'logfile.txt' - - logger = logging.getLogger(name) - logger.setLevel(level) - - file_handler = logging.FileHandler(name,'a+','utf-8') - file_handler.setLevel(logging.DEBUG) - file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') - file_handler.setFormatter(file_format) - logger.addHandler(file_handler) - - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.INFO) - console_format = logging.Formatter('%(message)s') - console_handler.setFormatter(console_format) - logger.addHandler(console_handler) - - equation_inputs = [4, -2, 8] - desired_output = 2671.1234 - - def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution * equation_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - return fitness - - def on_generation(ga_instance): - ga_instance.logger.info(f"Generation = {ga_instance.generations_completed}") - ga_instance.logger.info(f"Fitness = {ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1]}") - - ga_instance = pygad.GA(num_generations=10, - sol_per_pop=40, - num_parents_mating=2, - keep_parents=2, - num_genes=len(equation_inputs), - fitness_func=fitness_func, - on_generation=on_generation, - logger=logger) - ga_instance.run() - - logger.handlers.clear() - -By executing this code, the logged messages are printed to the console -and also saved in the text file. - -.. code:: python - - 2023-04-03 19:04:27 INFO: Generation = 1 - 2023-04-03 19:04:27 INFO: Fitness = 0.00038086960368076276 - 2023-04-03 19:04:27 INFO: Generation = 2 - 2023-04-03 19:04:27 INFO: Fitness = 0.00038214871408010853 - 2023-04-03 19:04:27 INFO: Generation = 3 - 2023-04-03 19:04:27 INFO: Fitness = 0.0003832795907974678 - 2023-04-03 19:04:27 INFO: Generation = 4 - 2023-04-03 19:04:27 INFO: Fitness = 0.00038398612055017196 - 2023-04-03 19:04:27 INFO: Generation = 5 - 2023-04-03 19:04:27 INFO: Fitness = 0.00038442348890867516 - 2023-04-03 19:04:27 INFO: Generation = 6 - 2023-04-03 19:04:27 INFO: Fitness = 0.0003854406039137763 - 2023-04-03 19:04:27 INFO: Generation = 7 - 2023-04-03 19:04:27 INFO: Fitness = 0.00038646083174063284 - 2023-04-03 19:04:27 INFO: Generation = 8 - 2023-04-03 19:04:27 INFO: Fitness = 0.0003875169193024936 - 2023-04-03 19:04:27 INFO: Generation = 9 - 2023-04-03 19:04:27 INFO: Fitness = 0.0003888816727311021 - 2023-04-03 19:04:27 INFO: Generation = 10 - 2023-04-03 19:04:27 INFO: Fitness = 0.000389832593101348 - -Solve Non-Deterministic Problems -================================ - -PyGAD can be used to solve both deterministic and non-deterministic -problems. Deterministic are those that return the same fitness for the -same solution. For non-deterministic problems, a different fitness value -would be returned for the same solution. - -By default, PyGAD settings are set to solve deterministic problems. -PyGAD can save the explored solutions and their fitness to reuse in the -future. These instances attributes can save the solutions: - -1. ``solutions``: Exists if ``save_solutions=True``. - -2. ``best_solutions``: Exists if ``save_best_solutions=True``. - -3. ``last_generation_elitism``: Exists if ``keep_elitism`` > 0. - -4. ``last_generation_parents``: Exists if ``keep_parents`` > 0 or - ``keep_parents=-1``. - -To configure PyGAD for non-deterministic problems, we have to disable -saving the previous solutions. This is by setting these parameters: - -1. ``keep_elitism=0`` - -2. ``keep_parents=0`` - -3. ``keep_solutions=False`` - -4. ``keep_best_solutions=False`` - -.. code:: python - - import pygad - ... - ga_instance = pygad.GA(..., - keep_elitism=0, - keep_parents=0, - save_solutions=False, - save_best_solutions=False, - ...) - -This way PyGAD will not save any explored solution and thus the fitness -function have to be called for each individual solution. - -Reuse the Fitness instead of Calling the Fitness Function -========================================================= - -It may happen that a previously explored solution in generation X is -explored again in another generation Y (where Y > X). For some problems, -calling the fitness function takes much time. - -For deterministic problems, it is better to not call the fitness -function for an already explored solutions. Instead, reuse the fitness -of the old solution. PyGAD supports some options to help you save time -calling the fitness function for a previously explored solution. - -The parameters explored in this section can be set in the constructor of -the ``pygad.GA`` class. - -The ``cal_pop_fitness()`` method of the ``pygad.GA`` class checks these -parameters to see if there is a possibility of reusing the fitness -instead of calling the fitness function. - -.. _1-savesolutions: - -1. ``save_solutions`` ---------------------- - -It defaults to ``False``. If set to ``True``, then the population of -each generation is saved into the ``solutions`` attribute of the -``pygad.GA`` instance. In other words, every single solution is saved in -the ``solutions`` attribute. - -.. _2-savebestsolutions: - -2. ``save_best_solutions`` --------------------------- - -It defaults to ``False``. If ``True``, then it only saves the best -solution in every generation. - -.. _3-keepelitism: - -3. ``keep_elitism`` -------------------- - -It accepts an integer and defaults to 1. If set to a positive integer, -then it keeps the elitism of one generation available in the next -generation. - -.. _4-keepparents: - -4. ``keep_parents`` -------------------- - -It accepts an integer and defaults to -1. It set to ``-1`` or a positive -integer, then it keeps the parents of one generation available in the -next generation. - -Why the Fitness Function is not Called for Solution at Index 0? -=============================================================== - -PyGAD has a parameter called ``keep_elitism`` which defaults to 1. This -parameter defines the number of best solutions in generation **X** to -keep in the next generation **X+1**. The best solutions are just copied -from generation **X** to generation **X+1** without making any change. - -.. code:: python - - ga_instance = pygad.GA(..., - keep_elitism=1, - ...) - -The best solutions are copied at the beginning of the population. If -``keep_elitism=1``, this means the best solution in generation X is kept -in the next generation X+1 at index 0 of the population. If -``keep_elitism=2``, this means the 2 best solutions in generation X are -kept in the next generation X+1 at indices 0 and 1 of the population of -generation 1. - -Because the fitness of these best solutions are already calculated in -generation X, then their fitness values will not be recalculated at -generation X+1 (i.e. the fitness function will not be called for these -solutions again). Instead, their fitness values are just reused. This is -why you see that no solution with index 0 is passed to the fitness -function. - -To force calling the fitness function for each solution in every -generation, consider setting ``keep_elitism`` and ``keep_parents`` to 0. -Moreover, keep the 2 parameters ``save_solutions`` and -``save_best_solutions`` to their default value ``False``. - -.. code:: python - - ga_instance = pygad.GA(..., - keep_elitism=0, - keep_parents=0, - save_solutions=False, - save_best_solutions=False, - ...) - -Batch Fitness Calculation -========================= - -In `PyGAD -2.19.0 `__, -a new optional parameter called ``fitness_batch_size`` is supported. A -new optional parameter called ``fitness_batch_size`` is supported to -calculate the fitness function in batches. Thanks to `Linan -Qiu `__ for opening the `GitHub issue -#136 `__. - -Its values can be: - -- ``1`` or ``None``: If the ``fitness_batch_size`` parameter is - assigned the value ``1`` or ``None`` (default), then the normal flow - is used where the fitness function is called for each individual - solution. That is if there are 15 solutions, then the fitness - function is called 15 times. - -- ``1 < fitness_batch_size <= sol_per_pop``: If the - ``fitness_batch_size`` parameter is assigned a value satisfying this - condition ``1 < fitness_batch_size <= sol_per_pop``, then the - solutions are grouped into batches of size ``fitness_batch_size`` and - the fitness function is called once for each batch. In this case, the - fitness function must return a list/tuple/numpy.ndarray with a length - equal to the number of solutions passed. - -.. _example-without-fitnessbatchsize-parameter: - -Example without ``fitness_batch_size`` Parameter ------------------------------------------------- - -This is an example where the ``fitness_batch_size`` parameter is given -the value ``None`` (which is the default value). This is equivalent to -using the value ``1``. In this case, the fitness function will be called -for each solution. This means the fitness function ``fitness_func`` will -receive only a single solution. This is an example of the passed -arguments to the fitness function: - -.. code:: - - solution: [ 2.52860734, -0.94178795, 2.97545704, 0.84131987, -3.78447118, 2.41008358] - solution_idx: 3 - -The fitness function also must return a single numeric value as the -fitness for the passed solution. - -As we have a population of ``20`` solutions, then the fitness function -is called 20 times per generation. For 5 generations, then the fitness -function is called ``20*5 = 100`` times. In PyGAD, the fitness function -is called after the last generation too and this adds additional 20 -times. So, the total number of calls to the fitness function is -``20*5 + 20 = 120``. - -Note that the ``keep_elitism`` and ``keep_parents`` parameters are set -to ``0`` to make sure no fitness values are reused and to force calling -the fitness function for each individual solution. - -.. code:: python - - import pygad - import numpy - - function_inputs = [4,-2,3.5,5,-11,-4.7] - desired_output = 44 - - number_of_calls = 0 - - def fitness_func(ga_instance, solution, solution_idx): - global number_of_calls - number_of_calls = number_of_calls + 1 - output = numpy.sum(solution*function_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - return fitness - - ga_instance = pygad.GA(num_generations=5, - num_parents_mating=10, - sol_per_pop=20, - fitness_func=fitness_func, - fitness_batch_size=None, - # fitness_batch_size=1, - num_genes=len(function_inputs), - keep_elitism=0, - keep_parents=0) - - ga_instance.run() - print(number_of_calls) - -.. code:: - - 120 - -.. _example-with-fitnessbatchsize-parameter: - -Example with ``fitness_batch_size`` Parameter ---------------------------------------------- - -This is an example where the ``fitness_batch_size`` parameter is used -and assigned the value ``4``. This means the solutions will be grouped -into batches of ``4`` solutions. The fitness function will be called -once for each patch (i.e. called once for each 4 solutions). - -This is an example of the arguments passed to it: - -.. code:: python - - solutions: - [[ 3.1129432 -0.69123589 1.93792414 2.23772968 -1.54616001 -0.53930799] - [ 3.38508121 0.19890812 1.93792414 2.23095014 -3.08955597 3.10194128] - [ 2.37079504 -0.88819803 2.97545704 1.41742256 -3.95594055 2.45028256] - [ 2.52860734 -0.94178795 2.97545704 0.84131987 -3.78447118 2.41008358]] - solutions_indices: - [16, 17, 18, 19] - -As we have 20 solutions, then there are ``20/4 = 5`` patches. As a -result, the fitness function is called only 5 times per generation -instead of 20. For each call to the fitness function, it receives a -batch of 4 solutions. - -As we have 5 generations, then the function will be called ``5*5 = 25`` -times. Given the call to the fitness function after the last generation, -then the total number of calls is ``5*5 + 5 = 30``. - -.. code:: python - - import pygad - import numpy - - function_inputs = [4,-2,3.5,5,-11,-4.7] - desired_output = 44 - - number_of_calls = 0 - - def fitness_func_batch(ga_instance, solutions, solutions_indices): - global number_of_calls - number_of_calls = number_of_calls + 1 - batch_fitness = [] - for solution in solutions: - output = numpy.sum(solution*function_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - batch_fitness.append(fitness) - return batch_fitness - - ga_instance = pygad.GA(num_generations=5, - num_parents_mating=10, - sol_per_pop=20, - fitness_func=fitness_func_batch, - fitness_batch_size=4, - num_genes=len(function_inputs), - keep_elitism=0, - keep_parents=0) - - ga_instance.run() - print(number_of_calls) - -.. code:: - - 30 - -When batch fitness calculation is used, then we saved ``120 - 30 = 90`` -calls to the fitness function. - -Use Functions and Methods to Build Fitness and Callbacks -======================================================== - -In PyGAD 2.19.0, it is possible to pass user-defined functions or -methods to the following parameters: - -1. ``fitness_func`` - -2. ``on_start`` - -3. ``on_fitness`` - -4. ``on_parents`` - -5. ``on_crossover`` - -6. ``on_mutation`` - -7. ``on_generation`` - -8. ``on_stop`` - -This section gives 2 examples to assign these parameters user-defined: - -1. Functions. - -2. Methods. - -Assign Functions ----------------- - -This is a dummy example where the fitness function returns a random -value. Note that the instance of the ``pygad.GA`` class is passed as the -last parameter of all functions. - -.. code:: python - - import pygad - import numpy - - def fitness_func(ga_instanse, solution, solution_idx): - return numpy.random.rand() - - def on_start(ga_instanse): - print("on_start") - - def on_fitness(ga_instanse, last_gen_fitness): - print("on_fitness") - - def on_parents(ga_instanse, last_gen_parents): - print("on_parents") - - def on_crossover(ga_instanse, last_gen_offspring): - print("on_crossover") - - def on_mutation(ga_instanse, last_gen_offspring): - print("on_mutation") - - def on_generation(ga_instanse): - print("on_generation\n") - - def on_stop(ga_instanse, last_gen_fitness): - print("on_stop") - - ga_instance = pygad.GA(num_generations=5, - num_parents_mating=4, - sol_per_pop=10, - num_genes=2, - on_start=on_start, - on_fitness=on_fitness, - on_parents=on_parents, - on_crossover=on_crossover, - on_mutation=on_mutation, - on_generation=on_generation, - on_stop=on_stop, - fitness_func=fitness_func) - - ga_instance.run() - -Assign Methods --------------- - -The next example has all the method defined inside the class ``Test``. -All of the methods accept an additional parameter representing the -method's object of the class ``Test``. - -All methods accept ``self`` as the first parameter and the instance of -the ``pygad.GA`` class as the last parameter. - -.. code:: python - - import pygad - import numpy - - class Test: - def fitness_func(self, ga_instanse, solution, solution_idx): - return numpy.random.rand() - - def on_start(self, ga_instanse): - print("on_start") - - def on_fitness(self, ga_instanse, last_gen_fitness): - print("on_fitness") - - def on_parents(self, ga_instanse, last_gen_parents): - print("on_parents") - - def on_crossover(self, ga_instanse, last_gen_offspring): - print("on_crossover") - - def on_mutation(self, ga_instanse, last_gen_offspring): - print("on_mutation") - - def on_generation(self, ga_instanse): - print("on_generation\n") - - def on_stop(self, ga_instanse, last_gen_fitness): - print("on_stop") - - ga_instance = pygad.GA(num_generations=5, - num_parents_mating=4, - sol_per_pop=10, - num_genes=2, - on_start=Test().on_start, - on_fitness=Test().on_fitness, - on_parents=Test().on_parents, - on_crossover=Test().on_crossover, - on_mutation=Test().on_mutation, - on_generation=Test().on_generation, - on_stop=Test().on_stop, - fitness_func=Test().fitness_func) - - ga_instance.run() +More About PyGAD +================ + +Multi-Objective Optimization +============================ + +In `PyGAD +3.2.0 `__, +the library supports multi-objective optimization using the +non-dominated sorting genetic algorithm II (NSGA-II). The code is +exactly similar to the regular code used for single-objective +optimization except for 1 difference. It is the return value of the +fitness function. + +In single-objective optimization, the fitness function returns a single +numeric value. In this example, the variable ``fitness`` is expected to +be a numeric value. + +.. code:: python + + def fitness_func(ga_instance, solution, solution_idx): + ... + return fitness + +But in multi-objective optimization, the fitness function returns any of +these data types: + +1. ``list`` + +2. ``tuple`` + +3. ``numpy.ndarray`` + +.. code:: python + + def fitness_func(ga_instance, solution, solution_idx): + ... + return [fitness1, fitness2, ..., fitnessN] + +Whenever the fitness function returns an iterable of these data types, +then the problem is considered multi-objective. This holds even if there +is a single element in the returned iterable. + +Other than the fitness function, everything else could be the same in +both single and multi-objective problems. + +But it is recommended to use one of these 2 parent selection operators +to solve multi-objective problems: + +1. ``nsga2``: This selects the parents based on non-dominated sorting + and crowding distance. + +2. ``tournament_nsga2``: This selects the parents using tournament + selection which uses non-dominated sorting and crowding distance to + rank the solutions. + +This is a multi-objective optimization example that optimizes these 2 +linear functions: + +1. ``y1 = f(w1:w6) = w1x1 + w2x2 + w3x3 + w4x4 + w5x5 + 6wx6`` + +2. ``y2 = f(w1:w6) = w1x7 + w2x8 + w3x9 + w4x10 + w5x11 + 6wx12`` + +Where: + +1. ``(x1,x2,x3,x4,x5,x6)=(4,-2,3.5,5,-11,-4.7)`` and ``y=50`` + +2. ``(x7,x8,x9,x10,x11,x12)=(-2,0.7,-9,1.4,3,5)`` and ``y=30`` + +The 2 functions use the same parameters (weights) ``w1`` to ``w6``. + +The goal is to use PyGAD to find the optimal values for such weights +that satisfy the 2 functions ``y1`` and ``y2``. + +.. code:: python + + import pygad + import numpy + + """ + Given these 2 functions: + y1 = f(w1:w6) = w1x1 + w2x2 + w3x3 + w4x4 + w5x5 + 6wx6 + y2 = f(w1:w6) = w1x7 + w2x8 + w3x9 + w4x10 + w5x11 + 6wx12 + where (x1,x2,x3,x4,x5,x6)=(4,-2,3.5,5,-11,-4.7) and y=50 + and (x7,x8,x9,x10,x11,x12)=(-2,0.7,-9,1.4,3,5) and y=30 + 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. + This is a multi-objective optimization problem. + + PyGAD considers the problem as multi-objective if the fitness function returns: + 1) List. + 2) Or tuple. + 3) Or numpy.ndarray. + """ + + function_inputs1 = [4,-2,3.5,5,-11,-4.7] # Function 1 inputs. + function_inputs2 = [-2,0.7,-9,1.4,3,5] # Function 2 inputs. + desired_output1 = 50 # Function 1 output. + desired_output2 = 30 # Function 2 output. + + def fitness_func(ga_instance, solution, solution_idx): + output1 = numpy.sum(solution*function_inputs1) + output2 = numpy.sum(solution*function_inputs2) + fitness1 = 1.0 / (numpy.abs(output1 - desired_output1) + 0.000001) + fitness2 = 1.0 / (numpy.abs(output2 - desired_output2) + 0.000001) + return [fitness1, fitness2] + + num_generations = 100 + num_parents_mating = 10 + + sol_per_pop = 20 + num_genes = len(function_inputs1) + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=num_parents_mating, + sol_per_pop=sol_per_pop, + num_genes=num_genes, + fitness_func=fitness_func, + parent_selection_type='nsga2') + + ga_instance.run() + + ga_instance.plot_fitness(label=['Obj 1', 'Obj 2']) + + solution, solution_fitness, solution_idx = ga_instance.best_solution(ga_instance.last_generation_fitness) + print(f"Parameters of the best solution : {solution}") + print(f"Fitness value of the best solution = {solution_fitness}") + + prediction = numpy.sum(numpy.array(function_inputs1)*solution) + print(f"Predicted output 1 based on the best solution : {prediction}") + prediction = numpy.sum(numpy.array(function_inputs2)*solution) + print(f"Predicted output 2 based on the best solution : {prediction}") + +This is the result of the print statements. The predicted outputs are +close to the desired outputs. + +.. code:: + + Parameters of the best solution : [ 0.79676439 -2.98823386 -4.12677662 5.70539445 -2.02797016 -1.07243922] + Fitness value of the best solution = [ 1.68090829 349.8591915 ] + Predicted output 1 based on the best solution : 50.59491545442283 + Predicted output 2 based on the best solution : 29.99714270722312 + +This is the figure created by the ``plot_fitness()`` method. The fitness +of the first objective has the green color. The blue color is used for +the second objective fitness. + +|image1| + +.. _limit-the-gene-value-range-using-the-genespace-parameter: + +Limit the Gene Value Range using the ``gene_space`` Parameter +============================================================= + +In `PyGAD +2.11.0 `__, +the ``gene_space`` parameter supported a new feature to allow +customizing the range of accepted values for each gene. Let's take a +quick review of the ``gene_space`` parameter to build over it. + +The ``gene_space`` parameter allows the user to feed the space of values +of each gene. This way the accepted values for each gene is retracted to +the user-defined values. Assume there is a problem that has 3 genes +where each gene has different set of values as follows: + +1. Gene 1: ``[0.4, 12, -5, 21.2]`` + +2. Gene 2: ``[-2, 0.3]`` + +3. Gene 3: ``[1.2, 63.2, 7.4]`` + +Then, the ``gene_space`` for this problem is as given below. Note that +the order is very important. + +.. code:: python + + gene_space = [[0.4, 12, -5, 21.2], + [-2, 0.3], + [1.2, 63.2, 7.4]] + +In case all genes share the same set of values, then simply feed a +single list to the ``gene_space`` parameter as follows. In this case, +all genes can only take values from this list of 6 values. + +.. code:: python + + gene_space = [33, 7, 0.5, 95. 6.3, 0.74] + +The previous example restricts the gene values to just a set of fixed +number of discrete values. In case you want to use a range of discrete +values to the gene, then you can use the ``range()`` function. For +example, ``range(1, 7)`` means the set of allowed values for the gene +are ``1, 2, 3, 4, 5, and 6``. You can also use the ``numpy.arange()`` or +``numpy.linspace()`` functions for the same purpose. + +The previous discussion only works with a range of discrete values not +continuous values. In `PyGAD +2.11.0 `__, +the ``gene_space`` parameter can be assigned a dictionary that allows +the gene to have values from a continuous range. + +Assuming you want to restrict the gene within this half-open range [1 to +5) where 1 is included and 5 is not. Then simply create a dictionary +with 2 items where the keys of the 2 items are: + +1. ``'low'``: The minimum value in the range which is 1 in the example. + +2. ``'high'``: The maximum value in the range which is 5 in the example. + +The dictionary will look like that: + +.. code:: python + + {'low': 1, + 'high': 5} + +It is not acceptable to add more than 2 items in the dictionary or use +other keys than ``'low'`` and ``'high'``. + +For a 3-gene problem, the next code creates a dictionary for each gene +to restrict its values in a continuous range. For the first gene, it can +take any floating-point value from the range that starts from 1 +(inclusive) and ends at 5 (exclusive). + +.. code:: python + + gene_space = [{'low': 1, 'high': 5}, {'low': 0.3, 'high': 1.4}, {'low': -0.2, 'high': 4.5}] + +.. _more-about-the-genespace-parameter: + +More about the ``gene_space`` Parameter +======================================= + +The ``gene_space`` parameter customizes the space of values of each +gene. + +Assuming that all genes have the same global space which include the +values 0.3, 5.2, -4, and 8, then those values can be assigned to the +``gene_space`` parameter as a list, tuple, or range. Here is a list +assigned to this parameter. By doing that, then the gene values are +restricted to those assigned to the ``gene_space`` parameter. + +.. code:: python + + gene_space = [0.3, 5.2, -4, 8] + +If some genes have different spaces, then ``gene_space`` should accept a +nested list or tuple. In this case, the elements could be: + +1. Number (of ``int``, ``float``, or ``NumPy`` data types): A single + value to be assigned to the gene. This means this gene will have the + same value across all generations. + +2. ``list``, ``tuple``, ``numpy.ndarray``, or any range like ``range``, + ``numpy.arange()``, or ``numpy.linspace``: It holds the space for + each individual gene. But this space is usually discrete. That is + there is a set of finite values to select from. + +3. ``dict``: To sample a value for a gene from a continuous range. The + dictionary must have 2 mandatory keys which are ``"low"`` and + ``"high"`` in addition to an optional key which is ``"step"``. A + random value is returned between the values assigned to the items + with ``"low"`` and ``"high"`` keys. If the ``"step"`` exists, then + this works as the previous options (i.e. discrete set of values). + +4. ``None``: A gene with its space set to ``None`` is initialized + randomly from the range specified by the 2 parameters + ``init_range_low`` and ``init_range_high``. For mutation, its value + is mutated based on a random value from the range specified by the 2 + parameters ``random_mutation_min_val`` and + ``random_mutation_max_val``. If all elements in the ``gene_space`` + parameter are ``None``, the parameter will not have any effect. + +Assuming that a chromosome has 2 genes and each gene has a different +value space. Then the ``gene_space`` could be assigned a nested +list/tuple where each element determines the space of a gene. + +According to the next code, the space of the first gene is ``[0.4, -5]`` +which has 2 values and the space for the second gene is +``[0.5, -3.2, 8.8, -9]`` which has 4 values. + +.. code:: python + + gene_space = [[0.4, -5], [0.5, -3.2, 8.2, -9]] + +For a 2 gene chromosome, if the first gene space is restricted to the +discrete values from 0 to 4 and the second gene is restricted to the +values from 10 to 19, then it could be specified according to the next +code. + +.. code:: python + + gene_space = [range(5), range(10, 20)] + +The ``gene_space`` can also be assigned to a single range, as given +below, where the values of all genes are sampled from the same range. + +.. code:: python + + gene_space = numpy.arange(15) + +The ``gene_space`` can be assigned a dictionary to sample a value from a +continuous range. + +.. code:: python + + gene_space = {"low": 4, "high": 30} + +A step also can be assigned to the dictionary. This works as if a range +is used. + +.. code:: python + + gene_space = {"low": 4, "high": 30, "step": 2.5} + +.. + + Setting a ``dict`` like ``{"low": 0, "high": 10}`` in the + ``gene_space`` means that random values from the continuous range [0, + 10) are sampled. Note that ``0`` is included but ``10`` is not + included while sampling. Thus, the maximum value that could be + returned is less than ``10`` like ``9.9999``. But if the user decided + to round the genes using, for example, ``[float, 2]``, then this + value will become 10. So, the user should be careful to the inputs. + +If a ``None`` is assigned to only a single gene, then its value will be +randomly generated initially using the ``init_range_low`` and +``init_range_high`` parameters in the ``pygad.GA`` class's constructor. +During mutation, the value are sampled from the range defined by the 2 +parameters ``random_mutation_min_val`` and ``random_mutation_max_val``. +This is an example where the second gene is given a ``None`` value. + +.. code:: python + + gene_space = [range(5), None, numpy.linspace(10, 20, 300)] + +If the user did not assign the initial population to the +``initial_population`` parameter, the initial population is created +randomly based on the ``gene_space`` parameter. Moreover, the mutation +is applied based on this parameter. + +.. _how-mutation-works-with-the-genespace-parameter: + +How Mutation Works with the ``gene_space`` Parameter? +----------------------------------------------------- + +Mutation changes based on whether the ``gene_space`` has a continuous +range or discrete set of values. + +If a gene has its **static/discrete space** defined in the +``gene_space`` parameter, then mutation works by replacing the gene +value by a value randomly selected from the gene space. This happens for +both ``int`` and ``float`` data types. + +For example, the following ``gene_space`` has the static space +``[1, 2, 3]`` defined for the first gene. So, this gene can only have a +value out of these 3 values. + +.. code:: python + + Gene space: [[1, 2, 3], + None] + Solution: [1, 5] + +For a solution like ``[1, 5]``, then mutation happens for the first gene +by simply replacing its current value by a randomly selected value +(other than its current value if possible). So, the value 1 will be +replaced by either 2 or 3. + +For the second gene, its space is set to ``None``. So, traditional +mutation happens for this gene by: + +1. Generating a random value from the range defined by the + ``random_mutation_min_val`` and ``random_mutation_max_val`` + parameters. + +2. Adding this random value to the current gene's value. + +If its current value is 5 and the random value is ``-0.5``, then the new +value is 4.5. If the gene type is integer, then the value will be +rounded. + +On the other hand, if a gene has a **continuous space** defined in the +``gene_space`` parameter, then mutation occurs by adding a random value +to the current gene value. + +For example, the following ``gene_space`` has the continuous space +defined by the dictionary ``{'low': 1, 'high': 5}``. This applies to all +genes. So, mutation is applied to one or more selected genes by adding a +random value to the current gene value. + +.. code:: python + + Gene space: {'low': 1, 'high': 5} + Solution: [1.5, 3.4] + +Assuming ``random_mutation_min_val=-1`` and +``random_mutation_max_val=1``, then a random value such as ``0.3`` can +be added to the gene(s) participating in mutation. If only the first +gene is mutated, then its new value changes from ``1.5`` to +``1.5+0.3=1.8``. Note that PyGAD verifies that the new value is within +the range. In the worst scenarios, the value will be set to either +boundary of the continuous range. For example, if the gene value is 1.5 +and the random value is -0.55, then the new value is 0.95 which smaller +than the lower boundary 1. Thus, the gene value will be rounded to 1. + +If the dictionary has a step like the example below, then it is +considered a discrete range and mutation occurs by randomly selecting a +value from the set of values. In other words, no random value is added +to the gene value. + +.. code:: python + + Gene space: {'low': 1, 'high': 5, 'step': 0.5} + +Stop at Any Generation +====================== + +In `PyGAD +2.4.0 `__, +it is possible to stop the genetic algorithm after any generation. All +you need to do it to return the string ``"stop"`` in the callback +function ``on_generation``. When this callback function is implemented +and assigned to the ``on_generation`` parameter in the constructor of +the ``pygad.GA`` class, then the algorithm immediately stops after +completing its current generation. Let's discuss an example. + +Assume that the user wants to stop algorithm either after the 100 +generations or if a condition is met. The user may assign a value of 100 +to the ``num_generations`` parameter of the ``pygad.GA`` class +constructor. + +The condition that stops the algorithm is written in a callback function +like the one in the next code. If the fitness value of the best solution +exceeds 70, then the string ``"stop"`` is returned. + +.. code:: python + + def func_generation(ga_instance): + if ga_instance.best_solution()[1] >= 70: + return "stop" + +Stop Criteria +============= + +In `PyGAD +2.15.0 `__, +a new parameter named ``stop_criteria`` is added to the constructor of +the ``pygad.GA`` class. It helps to stop the evolution based on some +criteria. It can be assigned to one or more criterion. + +Each criterion is passed as ``str`` that consists of 2 parts: + +1. Stop word. + +2. Number. + +It takes this form: + +.. code:: python + + "word_num" + +The current 2 supported words are ``reach`` and ``saturate``. + +The ``reach`` word stops the ``run()`` method if the fitness value is +equal to or greater than a given fitness value. An example for ``reach`` +is ``"reach_40"`` which stops the evolution if the fitness is >= 40. + +``saturate`` stops the evolution if the fitness saturates for a given +number of consecutive generations. An example for ``saturate`` is +``"saturate_7"`` which means stop the ``run()`` method if the fitness +does not change for 7 consecutive generations. + +Here is an example that stops the evolution if either the fitness value +reached ``127.4`` or if the fitness saturates for ``15`` generations. + +.. code:: python + + import pygad + import numpy + + equation_inputs = [4, -2, 3.5, 8, 9, 4] + desired_output = 44 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution * equation_inputs) + + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + + return fitness + + ga_instance = pygad.GA(num_generations=200, + sol_per_pop=10, + num_parents_mating=4, + num_genes=len(equation_inputs), + fitness_func=fitness_func, + stop_criteria=["reach_127.4", "saturate_15"]) + + ga_instance.run() + print(f"Number of generations passed is {ga_instance.generations_completed}") + +Multi-Objective Stop Criteria +----------------------------- + +When multi-objective is used, then there are 2 options to use the +``stop_criteria`` parameter with the ``reach`` keyword: + +1. Pass a single value to use along the ``reach`` keyword to use across + all the objectives. + +2. Pass multiple values along the ``reach`` keyword. But the number of + values must equal the number of objectives. + +For the ``saturate`` keyword, it is independent to the number of +objectives. + +Suppose there are 3 objectives, this is a working example. It stops when +the fitness value of the 3 objectives reach or exceed 10, 20, and 30, +respectively. + +.. code:: python + + stop_criteria='reach_10_20_30' + +More than one criterion can be used together. In this case, pass the +``stop_criteria`` parameter as an iterable. This is an example. It stops +when either of these 2 conditions hold: + +1. The fitness values of the 3 objectives reach or exceed 10, 20, and + 30, respectively. + +2. The fitness values of the 3 objectives reach or exceed 90, -5.7, and + 10, respectively. + +.. code:: python + + stop_criteria=['reach_10_20_30', 'reach_90_-5.7_10'] + +Elitism Selection +================= + +In `PyGAD +2.18.0 `__, +a new parameter called ``keep_elitism`` is supported. It accepts an +integer to define the number of elitism (i.e. best solutions) to keep in +the next generation. This parameter defaults to ``1`` which means only +the best solution is kept in the next generation. + +In the next example, the ``keep_elitism`` parameter in the constructor +of the ``pygad.GA`` class is set to 2. Thus, the best 2 solutions in +each generation are kept in the next generation. + +.. code:: python + + import numpy + import pygad + + function_inputs = [4,-2,3.5,5,-11,-4.7] + desired_output = 44 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution*function_inputs) + fitness = 1.0 / numpy.abs(output - desired_output) + return fitness + + ga_instance = pygad.GA(num_generations=2, + num_parents_mating=3, + fitness_func=fitness_func, + num_genes=6, + sol_per_pop=5, + keep_elitism=2) + + ga_instance.run() + +The value passed to the ``keep_elitism`` parameter must satisfy 2 +conditions: + +1. It must be ``>= 0``. + +2. It must be ``<= sol_per_pop``. That is its value cannot exceed the + number of solutions in the current population. + +In the previous example, if the ``keep_elitism`` parameter is set equal +to the value passed to the ``sol_per_pop`` parameter, which is 5, then +there will be no evolution at all as in the next figure. This is because +all the 5 solutions are used as elitism in the next generation and no +offspring will be created. + +.. code:: python + + ... + + ga_instance = pygad.GA(..., + sol_per_pop=5, + keep_elitism=5) + + ga_instance.run() + +|image2| + +Note that if the ``keep_elitism`` parameter is effective (i.e. is +assigned a positive integer, not zero), then the ``keep_parents`` +parameter will have no effect. Because the default value of the +``keep_elitism`` parameter is 1, then the ``keep_parents`` parameter has +no effect by default. The ``keep_parents`` parameter is only effective +when ``keep_elitism=0``. + +Random Seed +=========== + +In `PyGAD +2.18.0 `__, +a new parameter called ``random_seed`` is supported. Its value is used +as a seed for the random function generators. + +PyGAD uses random functions in these 2 libraries: + +1. NumPy + +2. random + +The ``random_seed`` parameter defaults to ``None`` which means no seed +is used. As a result, different random numbers are generated for each +run of PyGAD. + +If this parameter is assigned a proper seed, then the results will be +reproducible. In the next example, the integer 2 is used as a random +seed. + +.. code:: python + + import numpy + import pygad + + function_inputs = [4,-2,3.5,5,-11,-4.7] + desired_output = 44 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution*function_inputs) + fitness = 1.0 / numpy.abs(output - desired_output) + return fitness + + ga_instance = pygad.GA(num_generations=2, + num_parents_mating=3, + fitness_func=fitness_func, + sol_per_pop=5, + num_genes=6, + random_seed=2) + + ga_instance.run() + best_solution, best_solution_fitness, best_match_idx = ga_instance.best_solution() + print(best_solution) + print(best_solution_fitness) + +This is the best solution found and its fitness value. + +.. code:: + + [ 2.77249188 -4.06570662 0.04196872 -3.47770796 -0.57502138 -3.22775267] + 0.04872203136549972 + +After running the code again, it will find the same result. + +.. code:: + + [ 2.77249188 -4.06570662 0.04196872 -3.47770796 -0.57502138 -3.22775267] + 0.04872203136549972 + +Continue without Losing Progress +================================ + +In `PyGAD +2.18.0 `__, +and thanks for `Felix Bernhard `__ for +opening `this GitHub +issue `__, +the values of these 4 instance attributes are no longer reset after each +call to the ``run()`` method. + +1. ``self.best_solutions`` + +2. ``self.best_solutions_fitness`` + +3. ``self.solutions`` + +4. ``self.solutions_fitness`` + +This helps the user to continue where the last run stopped without +losing the values of these 4 attributes. + +Now, the user can save the model by calling the ``save()`` method. + +.. code:: python + + import pygad + + def fitness_func(ga_instance, solution, solution_idx): + ... + return fitness + + ga_instance = pygad.GA(...) + + ga_instance.run() + + ga_instance.plot_fitness() + + ga_instance.save("pygad_GA") + +Then the saved model is loaded by calling the ``load()`` function. After +calling the ``run()`` method over the loaded instance, then the data +from the previous 4 attributes are not reset but extended with the new +data. + +.. code:: python + + import pygad + + def fitness_func(ga_instance, solution, solution_idx): + ... + return fitness + + loaded_ga_instance = pygad.load("pygad_GA") + + loaded_ga_instance.run() + + loaded_ga_instance.plot_fitness() + +The plot created by the ``plot_fitness()`` method will show the data +collected from both the runs. + +Note that the 2 attributes (``self.best_solutions`` and +``self.best_solutions_fitness``) only work if the +``save_best_solutions`` parameter is set to ``True``. Also, the 2 +attributes (``self.solutions`` and ``self.solutions_fitness``) only work +if the ``save_solutions`` parameter is ``True``. + +Change Population Size during Runtime +===================================== + +Starting from `PyGAD +3.3.0 `__, +the population size can changed during runtime. In other words, the +number of solutions/chromosomes and number of genes can be changed. + +The user has to carefully arrange the list of *parameters* and *instance +attributes* that have to be changed to keep the GA consistent before and +after changing the population size. Generally, change everything that +would be used during the GA evolution. + + CAUTION: If the user failed to change a parameter or an instance + attributes necessary to keep the GA running after the population size + changed, errors will arise. + +These are examples of the parameters that the user should decide whether +to change. The user should check the `list of +parameters `__ +and decide what to change. + +1. ``population``: The population. It *must* be changed. + +2. ``num_offspring``: The number of offspring to produce out of the + crossover and mutation operations. Change this parameter if the + number of offspring have to be changed to be consistent with the new + population size. + +3. ``num_parents_mating``: The number of solutions to select as parents. + Change this parameter if the number of parents have to be changed to + be consistent with the new population size. + +4. ``fitness_func``: If the way of calculating the fitness changes after + the new population size, then the fitness function have to be + changed. + +5. ``sol_per_pop``: The number of solutions per population. It is not + critical to change it but it is recommended to keep this number + consistent with the number of solutions in the ``population`` + parameter. + +These are examples of the instance attributes that might be changed. The +user should check the `list of instance +attributes `__ +and decide what to change. + +1. All the ``last_generation_*`` parameters + + 1. ``last_generation_fitness``: A 1D NumPy array of fitness values of + the population. + + 2. ``last_generation_parents`` and + ``last_generation_parents_indices``: Two NumPy arrays: 2D array + representing the parents and 1D array of the parents indices. + + 3. ``last_generation_elitism`` and + ``last_generation_elitism_indices``: Must be changed if + ``keep_elitism != 0``. The default value of ``keep_elitism`` is 1. + Two NumPy arrays: 2D array representing the elitism and 1D array + of the elitism indices. + +2. ``pop_size``: The population size. + +Prevent Duplicates in Gene Values +================================= + +In `PyGAD +2.13.0 `__, +a new bool parameter called ``allow_duplicate_genes`` is supported to +control whether duplicates are supported in the chromosome or not. In +other words, whether 2 or more genes might have the same exact value. + +If ``allow_duplicate_genes=True`` (which is the default case), genes may +have the same value. If ``allow_duplicate_genes=False``, then no 2 genes +will have the same value given that there are enough unique values for +the genes. + +The next code gives an example to use the ``allow_duplicate_genes`` +parameter. A callback generation function is implemented to print the +population after each generation. + +.. code:: python + + import pygad + + def fitness_func(ga_instance, solution, solution_idx): + return 0 + + def on_generation(ga): + print("Generation", ga.generations_completed) + print(ga.population) + + ga_instance = pygad.GA(num_generations=5, + sol_per_pop=5, + num_genes=4, + mutation_num_genes=3, + random_mutation_min_val=-5, + random_mutation_max_val=5, + num_parents_mating=2, + fitness_func=fitness_func, + gene_type=int, + on_generation=on_generation, + allow_duplicate_genes=False) + ga_instance.run() + +Here are the population after the 5 generations. Note how there are no +duplicate values. + +.. code:: python + + Generation 1 + [[ 2 -2 -3 3] + [ 0 1 2 3] + [ 5 -3 6 3] + [-3 1 -2 4] + [-1 0 -2 3]] + Generation 2 + [[-1 0 -2 3] + [-3 1 -2 4] + [ 0 -3 -2 6] + [-3 0 -2 3] + [ 1 -4 2 4]] + Generation 3 + [[ 1 -4 2 4] + [-3 0 -2 3] + [ 4 0 -2 1] + [-4 0 -2 -3] + [-4 2 0 3]] + Generation 4 + [[-4 2 0 3] + [-4 0 -2 -3] + [-2 5 4 -3] + [-1 2 -4 4] + [-4 2 0 -3]] + Generation 5 + [[-4 2 0 -3] + [-1 2 -4 4] + [ 3 4 -4 0] + [-1 0 2 -2] + [-4 2 -1 1]] + +The ``allow_duplicate_genes`` parameter is configured with use with the +``gene_space`` parameter. Here is an example where each of the 4 genes +has the same space of values that consists of 4 values (1, 2, 3, and 4). + +.. code:: python + + import pygad + + def fitness_func(ga_instance, solution, solution_idx): + return 0 + + def on_generation(ga): + print("Generation", ga.generations_completed) + print(ga.population) + + ga_instance = pygad.GA(num_generations=1, + sol_per_pop=5, + num_genes=4, + num_parents_mating=2, + fitness_func=fitness_func, + gene_type=int, + gene_space=[[1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4], [1, 2, 3, 4]], + on_generation=on_generation, + allow_duplicate_genes=False) + ga_instance.run() + +Even that all the genes share the same space of values, no 2 genes +duplicate their values as provided by the next output. + +.. code:: python + + Generation 1 + [[2 3 1 4] + [2 3 1 4] + [2 4 1 3] + [2 3 1 4] + [1 3 2 4]] + Generation 2 + [[1 3 2 4] + [2 3 1 4] + [1 3 2 4] + [2 3 4 1] + [1 3 4 2]] + Generation 3 + [[1 3 4 2] + [2 3 4 1] + [1 3 4 2] + [3 1 4 2] + [3 2 4 1]] + Generation 4 + [[3 2 4 1] + [3 1 4 2] + [3 2 4 1] + [1 2 4 3] + [1 3 4 2]] + Generation 5 + [[1 3 4 2] + [1 2 4 3] + [2 1 4 3] + [1 2 4 3] + [1 2 4 3]] + +You should care of giving enough values for the genes so that PyGAD is +able to find alternatives for the gene value in case it duplicates with +another gene. + +There might be 2 duplicate genes where changing either of the 2 +duplicating genes will not solve the problem. For example, if +``gene_space=[[3, 0, 1], [4, 1, 2], [0, 2], [3, 2, 0]]`` and the +solution is ``[3 2 0 0]``, then the values of the last 2 genes +duplicate. There are no possible changes in the last 2 genes to solve +the problem. + +This problem can be solved by randomly changing one of the +non-duplicating genes that may make a room for a unique value in one the +2 duplicating genes. For example, by changing the second gene from 2 to +4, then any of the last 2 genes can take the value 2 and solve the +duplicates. The resultant gene is then ``[3 4 2 0]``. But this option is +not yet supported in PyGAD. + +Solve Duplicates using a Third Gene +----------------------------------- + +When ``allow_duplicate_genes=False`` and a user-defined ``gene_space`` +is used, it sometimes happen that there is no room to solve the +duplicates between the 2 genes by simply replacing the value of one gene +by another gene. In `PyGAD +3.1.0 `__, +the duplicates are solved by looking for a third gene that will help in +solving the duplicates. The following examples explain how it works. + +Example 1: + +Let's assume that this gene space is used and there is a solution with 2 +duplicate genes with the same value 4. + +.. code:: python + + Gene space: [[2, 3], + [3, 4], + [4, 5], + [5, 6]] + Solution: [3, 4, 4, 5] + +By checking the gene space, the second gene can have the values +``[3, 4]`` and the third gene can have the values ``[4, 5]``. To solve +the duplicates, we have the value of any of these 2 genes. + +If the value of the second gene changes from 4 to 3, then it will be +duplicate with the first gene. If we are to change the value of the +third gene from 4 to 5, then it will duplicate with the fourth gene. As +a conclusion, trying to just selecting a different gene value for either +the second or third genes will introduce new duplicating genes. + +When there are 2 duplicate genes but there is no way to solve their +duplicates, then the solution is to change a third gene that makes a +room to solve the duplicates between the 2 genes. + +In our example, duplicates between the second and third genes can be +solved by, for example,: + +- Changing the first gene from 3 to 2 then changing the second gene from + 4 to 3. + +- Or changing the fourth gene from 5 to 6 then changing the third gene + from 4 to 5. + +Generally, this is how to solve such duplicates: + +1. For any duplicate gene **GENE1**, select another value. + +2. Check which other gene **GENEX** has duplicate with this new value. + +3. Find if **GENEX** can have another value that will not cause any more + duplicates. If so, go to step 7. + +4. If all the other values of **GENEX** will cause duplicates, then try + another gene **GENEY**. + +5. Repeat steps 3 and 4 until exploring all the genes. + +6. If there is no possibility to solve the duplicates, then there is not + way to solve the duplicates and we have to keep the duplicate value. + +7. If a value for a gene **GENEM** is found that will not cause more + duplicates, then use this value for the gene **GENEM**. + +8. Replace the value of the gene **GENE1** by the old value of the gene + **GENEM**. This solves the duplicates. + +This is an example to solve the duplicate for the solution +``[3, 4, 4, 5]``: + +1. Let's use the second gene with value 4. Because the space of this + gene is ``[3, 4]``, then the only other value we can select is 3. + +2. The first gene also have the value 3. + +3. The first gene has another value 2 that will not cause more + duplicates in the solution. Then go to step 7. + +4. Skip. + +5. Skip. + +6. Skip. + +7. The value of the first gene 3 will be replaced by the new value 2. + The new solution is [2, 4, 4, 5]. + +8. Replace the value of the second gene 4 by the old value of the first + gene which is 3. The new solution is [2, 3, 4, 5]. The duplicate is + solved. + +Example 2: + +.. code:: python + + Gene space: [[0, 1], + [1, 2], + [2, 3], + [3, 4]] + Solution: [1, 2, 2, 3] + +The quick summary is: + +- Change the value of the first gene from 1 to 0. The solution becomes + [0, 2, 2, 3]. + +- Change the value of the second gene from 2 to 1. The solution becomes + [0, 1, 2, 3]. The duplicate is solved. + +.. _more-about-the-genetype-parameter: + +More about the ``gene_type`` Parameter +====================================== + +The ``gene_type`` parameter allows the user to control the data type for +all genes at once or each individual gene. In `PyGAD +2.15.0 `__, +the ``gene_type`` parameter also supports customizing the precision for +``float`` data types. As a result, the ``gene_type`` parameter helps to: + +1. Select a data type for all genes with or without precision. + +2. Select a data type for each individual gene with or without + precision. + +Let's discuss things by examples. + +Data Type for All Genes without Precision +----------------------------------------- + +The data type for all genes can be specified by assigning the numeric +data type directly to the ``gene_type`` parameter. This is an example to +make all genes of ``int`` data types. + +.. code:: python + + gene_type=int + +Given that the supported numeric data types of PyGAD include Python's +``int`` and ``float`` in addition to all numeric types of ``NumPy``, +then any of these types can be assigned to the ``gene_type`` parameter. + +If no precision is specified for a ``float`` data type, then the +complete floating-point number is kept. + +The next code uses an ``int`` data type for all genes where the genes in +the initial and final population are only integers. + +.. code:: python + + import pygad + import numpy + + equation_inputs = [4, -2, 3.5, 8, -2] + desired_output = 2671.1234 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution * equation_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + + ga_instance = pygad.GA(num_generations=10, + sol_per_pop=5, + num_parents_mating=2, + num_genes=len(equation_inputs), + fitness_func=fitness_func, + gene_type=int) + + print("Initial Population") + print(ga_instance.initial_population) + + ga_instance.run() + + print("Final Population") + print(ga_instance.population) + +.. code:: python + + Initial Population + [[ 1 -1 2 0 -3] + [ 0 -2 0 -3 -1] + [ 0 -1 -1 2 0] + [-2 3 -2 3 3] + [ 0 0 2 -2 -2]] + + Final Population + [[ 1 -1 2 2 0] + [ 1 -1 2 2 0] + [ 1 -1 2 2 0] + [ 1 -1 2 2 0] + [ 1 -1 2 2 0]] + +Data Type for All Genes with Precision +-------------------------------------- + +A precision can only be specified for a ``float`` data type and cannot +be specified for integers. Here is an example to use a precision of 3 +for the ``float`` data type. In this case, all genes are of type +``float`` and their maximum precision is 3. + +.. code:: python + + gene_type=[float, 3] + +The next code uses prints the initial and final population where the +genes are of type ``float`` with precision 3. + +.. code:: python + + import pygad + import numpy + + equation_inputs = [4, -2, 3.5, 8, -2] + desired_output = 2671.1234 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution * equation_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + + return fitness + + ga_instance = pygad.GA(num_generations=10, + sol_per_pop=5, + num_parents_mating=2, + num_genes=len(equation_inputs), + fitness_func=fitness_func, + gene_type=[float, 3]) + + print("Initial Population") + print(ga_instance.initial_population) + + ga_instance.run() + + print("Final Population") + print(ga_instance.population) + +.. code:: python + + Initial Population + [[-2.417 -0.487 3.623 2.457 -2.362] + [-1.231 0.079 -1.63 1.629 -2.637] + [ 0.692 -2.098 0.705 0.914 -3.633] + [ 2.637 -1.339 -1.107 -0.781 -3.896] + [-1.495 1.378 -1.026 3.522 2.379]] + + Final Population + [[ 1.714 -1.024 3.623 3.185 -2.362] + [ 0.692 -1.024 3.623 3.185 -2.362] + [ 0.692 -1.024 3.623 3.375 -2.362] + [ 0.692 -1.024 4.041 3.185 -2.362] + [ 1.714 -0.644 3.623 3.185 -2.362]] + +Data Type for each Individual Gene without Precision +---------------------------------------------------- + +In `PyGAD +2.14.0 `__, +the ``gene_type`` parameter allows customizing the gene type for each +individual gene. This is by using a ``list``/``tuple``/``numpy.ndarray`` +with number of elements equal to the number of genes. For each element, +a type is specified for the corresponding gene. + +This is an example for a 5-gene problem where different types are +assigned to the genes. + +.. code:: python + + gene_type=[int, float, numpy.float16, numpy.int8, float] + +This is a complete code that prints the initial and final population for +a custom-gene data type. + +.. code:: python + + import pygad + import numpy + + equation_inputs = [4, -2, 3.5, 8, -2] + desired_output = 2671.1234 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution * equation_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + + ga_instance = pygad.GA(num_generations=10, + sol_per_pop=5, + num_parents_mating=2, + num_genes=len(equation_inputs), + fitness_func=fitness_func, + gene_type=[int, float, numpy.float16, numpy.int8, float]) + + print("Initial Population") + print(ga_instance.initial_population) + + ga_instance.run() + + print("Final Population") + print(ga_instance.population) + +.. code:: python + + Initial Population + [[0 0.8615522360026828 0.7021484375 -2 3.5301821368185866] + [-3 2.648189378595294 -3.830078125 1 -0.9586271572917742] + [3 3.7729827570110714 1.2529296875 -3 1.395741994211889] + [0 1.0490687178053282 1.51953125 -2 0.7243617940450235] + [0 -0.6550158436937226 -2.861328125 -2 1.8212734549263097]] + + Final Population + [[3 3.7729827570110714 2.055 0 0.7243617940450235] + [3 3.7729827570110714 1.458 0 -0.14638754050305036] + [3 3.7729827570110714 1.458 0 0.0869406120516778] + [3 3.7729827570110714 1.458 0 0.7243617940450235] + [3 3.7729827570110714 1.458 0 -0.14638754050305036]] + +Data Type for each Individual Gene with Precision +------------------------------------------------- + +The precision can also be specified for the ``float`` data types as in +the next line where the second gene precision is 2 and last gene +precision is 1. + +.. code:: python + + gene_type=[int, [float, 2], numpy.float16, numpy.int8, [float, 1]] + +This is a complete example where the initial and final populations are +printed where the genes comply with the data types and precisions +specified. + +.. code:: python + + import pygad + import numpy + + equation_inputs = [4, -2, 3.5, 8, -2] + desired_output = 2671.1234 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution * equation_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + + ga_instance = pygad.GA(num_generations=10, + sol_per_pop=5, + num_parents_mating=2, + num_genes=len(equation_inputs), + fitness_func=fitness_func, + gene_type=[int, [float, 2], numpy.float16, numpy.int8, [float, 1]]) + + print("Initial Population") + print(ga_instance.initial_population) + + ga_instance.run() + + print("Final Population") + print(ga_instance.population) + +.. code:: python + + Initial Population + [[-2 -1.22 1.716796875 -1 0.2] + [-1 -1.58 -3.091796875 0 -1.3] + [3 3.35 -0.107421875 1 -3.3] + [-2 -3.58 -1.779296875 0 0.6] + [2 -3.73 2.65234375 3 -0.5]] + + Final Population + [[2 -4.22 3.47 3 -1.3] + [2 -3.73 3.47 3 -1.3] + [2 -4.22 3.47 2 -1.3] + [2 -4.58 3.47 3 -1.3] + [2 -3.73 3.47 3 -1.3]] + +Parallel Processing in PyGAD +============================ + +Starting from `PyGAD +2.17.0 `__, +parallel processing becomes supported. This section explains how to use +parallel processing in PyGAD. + +According to the `PyGAD +lifecycle `__, +parallel processing can be parallelized in only 2 operations: + +1. Population fitness calculation. + +2. Mutation. + +The reason is that the calculations in these 2 operations are +independent (i.e. each solution/chromosome is handled independently from +the others) and can be distributed across different processes or +threads. + +For the mutation operation, it does not do intensive calculations on the +CPU. Its calculations are simple like flipping the values of some genes +from 0 to 1 or adding a random value to some genes. So, it does not take +much CPU processing time. Experiments proved that parallelizing the +mutation operation across the solutions increases the time instead of +reducing it. This is because running multiple processes or threads adds +overhead to manage them. Thus, parallel processing cannot be applied on +the mutation operation. + +For the population fitness calculation, parallel processing can help +make a difference and reduce the processing time. But this is +conditional on the type of calculations done in the fitness function. If +the fitness function makes intensive calculations and takes much +processing time from the CPU, then it is probably that parallel +processing will help to cut down the overall time. + +This section explains how parallel processing works in PyGAD and how to +use parallel processing in PyGAD + +How to Use Parallel Processing in PyGAD +--------------------------------------- + +Starting from `PyGAD +2.17.0 `__, +a new parameter called ``parallel_processing`` added to the constructor +of the ``pygad.GA`` class. + +.. code:: python + + import pygad + ... + ga_instance = pygad.GA(..., + parallel_processing=...) + ... + +This parameter allows the user to do the following: + +1. Enable parallel processing. + +2. Select whether processes or threads are used. + +3. Specify the number of processes or threads to be used. + +These are 3 possible values for the ``parallel_processing`` parameter: + +1. ``None``: (Default) It means no parallel processing is used. + +2. A positive integer referring to the number of threads to be used + (i.e. threads, not processes, are used. + +3. ``list``/``tuple``: If a list or a tuple of exactly 2 elements is + assigned, then: + + 1. The first element can be either ``'process'`` or ``'thread'`` to + specify whether processes or threads are used, respectively. + + 2. The second element can be: + + 1. A positive integer to select the maximum number of processes or + threads to be used + + 2. ``0`` to indicate that 0 processes or threads are used. It + means no parallel processing. This is identical to setting + ``parallel_processing=None``. + + 3. ``None`` to use the default value as calculated by the + ``concurrent.futures module``. + +These are examples of the values assigned to the ``parallel_processing`` +parameter: + +- ``parallel_processing=4``: Because the parameter is assigned a + positive integer, this means parallel processing is activated where 4 + threads are used. + +- ``parallel_processing=["thread", 5]``: Use parallel processing with 5 + threads. This is identical to ``parallel_processing=5``. + +- ``parallel_processing=["process", 8]``: Use parallel processing with 8 + processes. + +- ``parallel_processing=["process", 0]``: As the second element is given + the value 0, this means do not use parallel processing. This is + identical to ``parallel_processing=None``. + +Examples +-------- + +The examples will help you know the difference between using processes +and threads. Moreover, it will give an idea when parallel processing +would make a difference and reduce the time. These are dummy examples +where the fitness function is made to always return 0. + +The first example uses 10 genes, 5 solutions in the population where +only 3 solutions mate, and 9999 generations. The fitness function uses a +``for`` loop with 100 iterations just to have some calculations. In the +constructor of the ``pygad.GA`` class, ``parallel_processing=None`` +means no parallel processing is used. + +.. code:: python + + import pygad + import time + + def fitness_func(ga_instance, solution, solution_idx): + for _ in range(99): + pass + return 0 + + ga_instance = pygad.GA(num_generations=9999, + num_parents_mating=3, + sol_per_pop=5, + num_genes=10, + fitness_func=fitness_func, + suppress_warnings=True, + parallel_processing=None) + + if __name__ == '__main__': + t1 = time.time() + + ga_instance.run() + + t2 = time.time() + print("Time is", t2-t1) + +When parallel processing is not used, the time it takes to run the +genetic algorithm is ``1.5`` seconds. + +In the comparison, let's do a second experiment where parallel +processing is used with 5 threads. In this case, it take ``5`` seconds. + +.. code:: python + + ... + ga_instance = pygad.GA(..., + parallel_processing=5) + ... + +For the third experiment, processes instead of threads are used. Also, +only 99 generations are used instead of 9999. The time it takes is +``99`` seconds. + +.. code:: python + + ... + ga_instance = pygad.GA(num_generations=99, + ..., + parallel_processing=["process", 5]) + ... + +This is the summary of the 3 experiments: + +1. No parallel processing & 9999 generations: 1.5 seconds. + +2. Parallel processing with 5 threads & 9999 generations: 5 seconds + +3. Parallel processing with 5 processes & 99 generations: 99 seconds + +Because the fitness function does not need much CPU time, the normal +processing takes the least time. Running processes for this simple +problem takes 99 compared to only 5 seconds for threads because managing +processes is much heavier than managing threads. Thus, most of the CPU +time is for swapping the processes instead of executing the code. + +In the second example, the loop makes 99999999 iterations and only 5 +generations are used. With no parallelization, it takes 22 seconds. + +.. code:: python + + import pygad + import time + + def fitness_func(ga_instance, solution, solution_idx): + for _ in range(99999999): + pass + return 0 + + ga_instance = pygad.GA(num_generations=5, + num_parents_mating=3, + sol_per_pop=5, + num_genes=10, + fitness_func=fitness_func, + suppress_warnings=True, + parallel_processing=None) + + if __name__ == '__main__': + t1 = time.time() + ga_instance.run() + t2 = time.time() + print("Time is", t2-t1) + +It takes 15 seconds when 10 processes are used. + +.. code:: python + + ... + ga_instance = pygad.GA(..., + parallel_processing=["process", 10]) + ... + +This is compared to 20 seconds when 10 threads are used. + +.. code:: python + + ... + ga_instance = pygad.GA(..., + parallel_processing=["thread", 10]) + ... + +Based on the second example, using parallel processing with 10 processes +takes the least time because there is much CPU work done. Generally, +processes are preferred over threads when most of the work in on the +CPU. Threads are preferred over processes in some situations like doing +input/output operations. + +*Before releasing* `PyGAD +2.17.0 `__\ *,* +`László +Fazekas `__ +*wrote an article to parallelize the fitness function with PyGAD. Check +it:* `How Genetic Algorithms Can Compete with Gradient Descent and +Backprop `__. + +Print Lifecycle Summary +======================= + +In `PyGAD +2.19.0 `__, +a new method called ``summary()`` is supported. It prints a Keras-like +summary of the PyGAD lifecycle showing the steps, callback functions, +parameters, etc. + +This method accepts the following parameters: + +- ``line_length=70``: An integer representing the length of the single + line in characters. + +- ``fill_character=" "``: A character to fill the lines. + +- ``line_character="-"``: A character for creating a line separator. + +- ``line_character2="="``: A secondary character to create a line + separator. + +- ``columns_equal_len=False``: The table rows are split into equal-sized + columns or split subjective to the width needed. + +- ``print_step_parameters=True``: Whether to print extra parameters + about each step inside the step. If ``print_step_parameters=False`` + and ``print_parameters_summary=True``, then the parameters of each + step are printed at the end of the table. + +- ``print_parameters_summary=True``: Whether to print parameters summary + at the end of the table. If ``print_step_parameters=False``, then the + parameters of each step are printed at the end of the table too. + +This is a quick example to create a PyGAD example. + +.. code:: python + + import pygad + import numpy + + function_inputs = [4,-2,3.5,5,-11,-4.7] + desired_output = 44 + + def genetic_fitness(solution, solution_idx): + output = numpy.sum(solution*function_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + + def on_gen(ga): + pass + + def on_crossover_callback(a, b): + pass + + ga_instance = pygad.GA(num_generations=100, + num_parents_mating=10, + sol_per_pop=20, + num_genes=len(function_inputs), + on_crossover=on_crossover_callback, + on_generation=on_gen, + parallel_processing=2, + stop_criteria="reach_10", + fitness_batch_size=4, + crossover_probability=0.4, + fitness_func=genetic_fitness) + +Then call the ``summary()`` method to print the summary with the default +parameters. Note that entries for the crossover and generation callback +function are created because their callback functions are implemented +through the ``on_crossover_callback()`` and ``on_gen()``, respectively. + +.. code:: python + + ga_instance.summary() + +.. code:: bash + + ---------------------------------------------------------------------- + PyGAD Lifecycle + ====================================================================== + Step Handler Output Shape + ====================================================================== + Fitness Function genetic_fitness() (1) + Fitness batch size: 4 + ---------------------------------------------------------------------- + Parent Selection steady_state_selection() (10, 6) + Number of Parents: 10 + ---------------------------------------------------------------------- + Crossover single_point_crossover() (10, 6) + Crossover probability: 0.4 + ---------------------------------------------------------------------- + On Crossover on_crossover_callback() None + ---------------------------------------------------------------------- + Mutation random_mutation() (10, 6) + Mutation Genes: 1 + Random Mutation Range: (-1.0, 1.0) + Mutation by Replacement: False + Allow Duplicated Genes: True + ---------------------------------------------------------------------- + On Generation on_gen() None + Stop Criteria: [['reach', 10.0]] + ---------------------------------------------------------------------- + ====================================================================== + Population Size: (20, 6) + Number of Generations: 100 + Initial Population Range: (-4, 4) + Keep Elitism: 1 + Gene DType: [, None] + Parallel Processing: ['thread', 2] + Save Best Solutions: False + Save Solutions: False + ====================================================================== + +We can set the ``print_step_parameters`` and +``print_parameters_summary`` parameters to ``False`` to not print the +parameters. + +.. code:: python + + ga_instance.summary(print_step_parameters=False, + print_parameters_summary=False) + +.. code:: bash + + ---------------------------------------------------------------------- + PyGAD Lifecycle + ====================================================================== + Step Handler Output Shape + ====================================================================== + Fitness Function genetic_fitness() (1) + ---------------------------------------------------------------------- + Parent Selection steady_state_selection() (10, 6) + ---------------------------------------------------------------------- + Crossover single_point_crossover() (10, 6) + ---------------------------------------------------------------------- + On Crossover on_crossover_callback() None + ---------------------------------------------------------------------- + Mutation random_mutation() (10, 6) + ---------------------------------------------------------------------- + On Generation on_gen() None + ---------------------------------------------------------------------- + ====================================================================== + +Logging Outputs +=============== + +In `PyGAD +3.0.0 `__, +the ``print()`` statement is no longer used and the outputs are printed +using the `logging `__ +module. A a new parameter called ``logger`` is supported to accept the +user-defined logger. + +.. code:: python + + import logging + + logger = ... + + ga_instance = pygad.GA(..., + logger=logger, + ...) + +The default value for this parameter is ``None``. If there is no logger +passed (i.e. ``logger=None``), then a default logger is created to log +the messages to the console exactly like how the ``print()`` statement +works. + +Some advantages of using the the +`logging `__ module +instead of the ``print()`` statement are: + +1. The user has more control over the printed messages specially if + there is a project that uses multiple modules where each module + prints its messages. A logger can organize the outputs. + +2. Using the proper ``Handler``, the user can log the output messages to + files and not only restricted to printing it to the console. So, it + is much easier to record the outputs. + +3. The format of the printed messages can be changed by customizing the + ``Formatter`` assigned to the Logger. + +This section gives some quick examples to use the ``logging`` module and +then gives an example to use the logger with PyGAD. + +Logging to the Console +---------------------- + +This is an example to create a logger to log the messages to the +console. + +.. code:: python + + import logging + + # Create a logger + logger = logging.getLogger(__name__) + + # Set the logger level to debug so that all the messages are printed. + logger.setLevel(logging.DEBUG) + + # Create a stream handler to log the messages to the console. + stream_handler = logging.StreamHandler() + + # Set the handler level to debug. + stream_handler.setLevel(logging.DEBUG) + + # Create a formatter + formatter = logging.Formatter('%(message)s') + + # Add the formatter to handler. + stream_handler.setFormatter(formatter) + + # Add the stream handler to the logger + logger.addHandler(stream_handler) + +Now, we can log messages to the console with the format specified in the +``Formatter``. + +.. code:: python + + logger.debug('Debug message.') + logger.info('Info message.') + logger.warning('Warn message.') + logger.error('Error message.') + logger.critical('Critical message.') + +The outputs are identical to those returned using the ``print()`` +statement. + +.. code:: + + Debug message. + Info message. + Warn message. + Error message. + Critical message. + +By changing the format of the output messages, we can have more +information about each message. + +.. code:: python + + formatter = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + +This is a sample output. + +.. code:: python + + 2023-04-03 18:46:27 DEBUG: Debug message. + 2023-04-03 18:46:27 INFO: Info message. + 2023-04-03 18:46:27 WARNING: Warn message. + 2023-04-03 18:46:27 ERROR: Error message. + 2023-04-03 18:46:27 CRITICAL: Critical message. + +Note that you may need to clear the handlers after finishing the +execution. This is to make sure no cached handlers are used in the next +run. If the cached handlers are not cleared, then the single output +message may be repeated. + +.. code:: python + + logger.handlers.clear() + +Logging to a File +----------------- + +This is another example to log the messages to a file named +``logfile.txt``. The formatter prints the following about each message: + +1. The date and time at which the message is logged. + +2. The log level. + +3. The message. + +4. The path of the file. + +5. The lone number of the log message. + +.. code:: python + + import logging + + level = logging.DEBUG + name = 'logfile.txt' + + logger = logging.getLogger(name) + logger.setLevel(level) + + file_handler = logging.FileHandler(name, 'a+', 'utf-8') + file_handler.setLevel(logging.DEBUG) + file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s - %(pathname)s:%(lineno)d', datefmt='%Y-%m-%d %H:%M:%S') + file_handler.setFormatter(file_format) + logger.addHandler(file_handler) + +This is how the outputs look like. + +.. code:: python + + 2023-04-03 18:54:03 DEBUG: Debug message. - c:\users\agad069\desktop\logger\example2.py:46 + 2023-04-03 18:54:03 INFO: Info message. - c:\users\agad069\desktop\logger\example2.py:47 + 2023-04-03 18:54:03 WARNING: Warn message. - c:\users\agad069\desktop\logger\example2.py:48 + 2023-04-03 18:54:03 ERROR: Error message. - c:\users\agad069\desktop\logger\example2.py:49 + 2023-04-03 18:54:03 CRITICAL: Critical message. - c:\users\agad069\desktop\logger\example2.py:50 + +Consider clearing the handlers if necessary. + +.. code:: python + + logger.handlers.clear() + +Log to Both the Console and a File +---------------------------------- + +This is an example to create a single Logger associated with 2 handlers: + +1. A file handler. + +2. A stream handler. + +.. code:: python + + import logging + + level = logging.DEBUG + name = 'logfile.txt' + + logger = logging.getLogger(name) + logger.setLevel(level) + + file_handler = logging.FileHandler(name,'a+','utf-8') + file_handler.setLevel(logging.DEBUG) + file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s - %(pathname)s:%(lineno)d', datefmt='%Y-%m-%d %H:%M:%S') + file_handler.setFormatter(file_format) + logger.addHandler(file_handler) + + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + console_format = logging.Formatter('%(message)s') + console_handler.setFormatter(console_format) + logger.addHandler(console_handler) + +When a log message is executed, then it is both printed to the console +and saved in the ``logfile.txt``. + +Consider clearing the handlers if necessary. + +.. code:: python + + logger.handlers.clear() + +PyGAD Example +------------- + +To use the logger in PyGAD, just create your custom logger and pass it +to the ``logger`` parameter. + +.. code:: python + + import logging + import pygad + import numpy + + level = logging.DEBUG + name = 'logfile.txt' + + logger = logging.getLogger(name) + logger.setLevel(level) + + file_handler = logging.FileHandler(name,'a+','utf-8') + file_handler.setLevel(logging.DEBUG) + file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s', datefmt='%Y-%m-%d %H:%M:%S') + file_handler.setFormatter(file_format) + logger.addHandler(file_handler) + + console_handler = logging.StreamHandler() + console_handler.setLevel(logging.INFO) + console_format = logging.Formatter('%(message)s') + console_handler.setFormatter(console_format) + logger.addHandler(console_handler) + + equation_inputs = [4, -2, 8] + desired_output = 2671.1234 + + def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution * equation_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + + def on_generation(ga_instance): + ga_instance.logger.info(f"Generation = {ga_instance.generations_completed}") + ga_instance.logger.info(f"Fitness = {ga_instance.best_solution(pop_fitness=ga_instance.last_generation_fitness)[1]}") + + ga_instance = pygad.GA(num_generations=10, + sol_per_pop=40, + num_parents_mating=2, + keep_parents=2, + num_genes=len(equation_inputs), + fitness_func=fitness_func, + on_generation=on_generation, + logger=logger) + ga_instance.run() + + logger.handlers.clear() + +By executing this code, the logged messages are printed to the console +and also saved in the text file. + +.. code:: python + + 2023-04-03 19:04:27 INFO: Generation = 1 + 2023-04-03 19:04:27 INFO: Fitness = 0.00038086960368076276 + 2023-04-03 19:04:27 INFO: Generation = 2 + 2023-04-03 19:04:27 INFO: Fitness = 0.00038214871408010853 + 2023-04-03 19:04:27 INFO: Generation = 3 + 2023-04-03 19:04:27 INFO: Fitness = 0.0003832795907974678 + 2023-04-03 19:04:27 INFO: Generation = 4 + 2023-04-03 19:04:27 INFO: Fitness = 0.00038398612055017196 + 2023-04-03 19:04:27 INFO: Generation = 5 + 2023-04-03 19:04:27 INFO: Fitness = 0.00038442348890867516 + 2023-04-03 19:04:27 INFO: Generation = 6 + 2023-04-03 19:04:27 INFO: Fitness = 0.0003854406039137763 + 2023-04-03 19:04:27 INFO: Generation = 7 + 2023-04-03 19:04:27 INFO: Fitness = 0.00038646083174063284 + 2023-04-03 19:04:27 INFO: Generation = 8 + 2023-04-03 19:04:27 INFO: Fitness = 0.0003875169193024936 + 2023-04-03 19:04:27 INFO: Generation = 9 + 2023-04-03 19:04:27 INFO: Fitness = 0.0003888816727311021 + 2023-04-03 19:04:27 INFO: Generation = 10 + 2023-04-03 19:04:27 INFO: Fitness = 0.000389832593101348 + +Solve Non-Deterministic Problems +================================ + +PyGAD can be used to solve both deterministic and non-deterministic +problems. Deterministic are those that return the same fitness for the +same solution. For non-deterministic problems, a different fitness value +would be returned for the same solution. + +By default, PyGAD settings are set to solve deterministic problems. +PyGAD can save the explored solutions and their fitness to reuse in the +future. These instances attributes can save the solutions: + +1. ``solutions``: Exists if ``save_solutions=True``. + +2. ``best_solutions``: Exists if ``save_best_solutions=True``. + +3. ``last_generation_elitism``: Exists if ``keep_elitism`` > 0. + +4. ``last_generation_parents``: Exists if ``keep_parents`` > 0 or + ``keep_parents=-1``. + +To configure PyGAD for non-deterministic problems, we have to disable +saving the previous solutions. This is by setting these parameters: + +1. ``keep_elitism=0`` + +2. ``keep_parents=0`` + +3. ``keep_solutions=False`` + +4. ``keep_best_solutions=False`` + +.. code:: python + + import pygad + ... + ga_instance = pygad.GA(..., + keep_elitism=0, + keep_parents=0, + save_solutions=False, + save_best_solutions=False, + ...) + +This way PyGAD will not save any explored solution and thus the fitness +function have to be called for each individual solution. + +Reuse the Fitness instead of Calling the Fitness Function +========================================================= + +It may happen that a previously explored solution in generation X is +explored again in another generation Y (where Y > X). For some problems, +calling the fitness function takes much time. + +For deterministic problems, it is better to not call the fitness +function for an already explored solutions. Instead, reuse the fitness +of the old solution. PyGAD supports some options to help you save time +calling the fitness function for a previously explored solution. + +The parameters explored in this section can be set in the constructor of +the ``pygad.GA`` class. + +The ``cal_pop_fitness()`` method of the ``pygad.GA`` class checks these +parameters to see if there is a possibility of reusing the fitness +instead of calling the fitness function. + +.. _1-savesolutions: + +1. ``save_solutions`` +--------------------- + +It defaults to ``False``. If set to ``True``, then the population of +each generation is saved into the ``solutions`` attribute of the +``pygad.GA`` instance. In other words, every single solution is saved in +the ``solutions`` attribute. + +.. _2-savebestsolutions: + +2. ``save_best_solutions`` +-------------------------- + +It defaults to ``False``. If ``True``, then it only saves the best +solution in every generation. + +.. _3-keepelitism: + +3. ``keep_elitism`` +------------------- + +It accepts an integer and defaults to 1. If set to a positive integer, +then it keeps the elitism of one generation available in the next +generation. + +.. _4-keepparents: + +4. ``keep_parents`` +------------------- + +It accepts an integer and defaults to -1. It set to ``-1`` or a positive +integer, then it keeps the parents of one generation available in the +next generation. + +Why the Fitness Function is not Called for Solution at Index 0? +=============================================================== + +PyGAD has a parameter called ``keep_elitism`` which defaults to 1. This +parameter defines the number of best solutions in generation **X** to +keep in the next generation **X+1**. The best solutions are just copied +from generation **X** to generation **X+1** without making any change. + +.. code:: python + + ga_instance = pygad.GA(..., + keep_elitism=1, + ...) + +The best solutions are copied at the beginning of the population. If +``keep_elitism=1``, this means the best solution in generation X is kept +in the next generation X+1 at index 0 of the population. If +``keep_elitism=2``, this means the 2 best solutions in generation X are +kept in the next generation X+1 at indices 0 and 1 of the population of +generation 1. + +Because the fitness of these best solutions are already calculated in +generation X, then their fitness values will not be recalculated at +generation X+1 (i.e. the fitness function will not be called for these +solutions again). Instead, their fitness values are just reused. This is +why you see that no solution with index 0 is passed to the fitness +function. + +To force calling the fitness function for each solution in every +generation, consider setting ``keep_elitism`` and ``keep_parents`` to 0. +Moreover, keep the 2 parameters ``save_solutions`` and +``save_best_solutions`` to their default value ``False``. + +.. code:: python + + ga_instance = pygad.GA(..., + keep_elitism=0, + keep_parents=0, + save_solutions=False, + save_best_solutions=False, + ...) + +Batch Fitness Calculation +========================= + +In `PyGAD +2.19.0 `__, +a new optional parameter called ``fitness_batch_size`` is supported. A +new optional parameter called ``fitness_batch_size`` is supported to +calculate the fitness function in batches. Thanks to `Linan +Qiu `__ for opening the `GitHub issue +#136 `__. + +Its values can be: + +- ``1`` or ``None``: If the ``fitness_batch_size`` parameter is assigned + the value ``1`` or ``None`` (default), then the normal flow is used + where the fitness function is called for each individual solution. + That is if there are 15 solutions, then the fitness function is called + 15 times. + +- ``1 < fitness_batch_size <= sol_per_pop``: If the + ``fitness_batch_size`` parameter is assigned a value satisfying this + condition ``1 < fitness_batch_size <= sol_per_pop``, then the + solutions are grouped into batches of size ``fitness_batch_size`` and + the fitness function is called once for each batch. In this case, the + fitness function must return a list/tuple/numpy.ndarray with a length + equal to the number of solutions passed. + +.. _example-without-fitnessbatchsize-parameter: + +Example without ``fitness_batch_size`` Parameter +------------------------------------------------ + +This is an example where the ``fitness_batch_size`` parameter is given +the value ``None`` (which is the default value). This is equivalent to +using the value ``1``. In this case, the fitness function will be called +for each solution. This means the fitness function ``fitness_func`` will +receive only a single solution. This is an example of the passed +arguments to the fitness function: + +.. code:: + + solution: [ 2.52860734, -0.94178795, 2.97545704, 0.84131987, -3.78447118, 2.41008358] + solution_idx: 3 + +The fitness function also must return a single numeric value as the +fitness for the passed solution. + +As we have a population of ``20`` solutions, then the fitness function +is called 20 times per generation. For 5 generations, then the fitness +function is called ``20*5 = 100`` times. In PyGAD, the fitness function +is called after the last generation too and this adds additional 20 +times. So, the total number of calls to the fitness function is +``20*5 + 20 = 120``. + +Note that the ``keep_elitism`` and ``keep_parents`` parameters are set +to ``0`` to make sure no fitness values are reused and to force calling +the fitness function for each individual solution. + +.. code:: python + + import pygad + import numpy + + function_inputs = [4,-2,3.5,5,-11,-4.7] + desired_output = 44 + + number_of_calls = 0 + + def fitness_func(ga_instance, solution, solution_idx): + global number_of_calls + number_of_calls = number_of_calls + 1 + output = numpy.sum(solution*function_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + + ga_instance = pygad.GA(num_generations=5, + num_parents_mating=10, + sol_per_pop=20, + fitness_func=fitness_func, + fitness_batch_size=None, + # fitness_batch_size=1, + num_genes=len(function_inputs), + keep_elitism=0, + keep_parents=0) + + ga_instance.run() + print(number_of_calls) + +.. code:: + + 120 + +.. _example-with-fitnessbatchsize-parameter: + +Example with ``fitness_batch_size`` Parameter +--------------------------------------------- + +This is an example where the ``fitness_batch_size`` parameter is used +and assigned the value ``4``. This means the solutions will be grouped +into batches of ``4`` solutions. The fitness function will be called +once for each patch (i.e. called once for each 4 solutions). + +This is an example of the arguments passed to it: + +.. code:: python + + solutions: + [[ 3.1129432 -0.69123589 1.93792414 2.23772968 -1.54616001 -0.53930799] + [ 3.38508121 0.19890812 1.93792414 2.23095014 -3.08955597 3.10194128] + [ 2.37079504 -0.88819803 2.97545704 1.41742256 -3.95594055 2.45028256] + [ 2.52860734 -0.94178795 2.97545704 0.84131987 -3.78447118 2.41008358]] + solutions_indices: + [16, 17, 18, 19] + +As we have 20 solutions, then there are ``20/4 = 5`` patches. As a +result, the fitness function is called only 5 times per generation +instead of 20. For each call to the fitness function, it receives a +batch of 4 solutions. + +As we have 5 generations, then the function will be called ``5*5 = 25`` +times. Given the call to the fitness function after the last generation, +then the total number of calls is ``5*5 + 5 = 30``. + +.. code:: python + + import pygad + import numpy + + function_inputs = [4,-2,3.5,5,-11,-4.7] + desired_output = 44 + + number_of_calls = 0 + + def fitness_func_batch(ga_instance, solutions, solutions_indices): + global number_of_calls + number_of_calls = number_of_calls + 1 + batch_fitness = [] + for solution in solutions: + output = numpy.sum(solution*function_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + batch_fitness.append(fitness) + return batch_fitness + + ga_instance = pygad.GA(num_generations=5, + num_parents_mating=10, + sol_per_pop=20, + fitness_func=fitness_func_batch, + fitness_batch_size=4, + num_genes=len(function_inputs), + keep_elitism=0, + keep_parents=0) + + ga_instance.run() + print(number_of_calls) + +.. code:: + + 30 + +When batch fitness calculation is used, then we saved ``120 - 30 = 90`` +calls to the fitness function. + +Use Functions and Methods to Build Fitness and Callbacks +======================================================== + +In PyGAD 2.19.0, it is possible to pass user-defined functions or +methods to the following parameters: + +1. ``fitness_func`` + +2. ``on_start`` + +3. ``on_fitness`` + +4. ``on_parents`` + +5. ``on_crossover`` + +6. ``on_mutation`` + +7. ``on_generation`` + +8. ``on_stop`` + +This section gives 2 examples to assign these parameters user-defined: + +1. Functions. + +2. Methods. + +Assign Functions +---------------- + +This is a dummy example where the fitness function returns a random +value. Note that the instance of the ``pygad.GA`` class is passed as the +last parameter of all functions. + +.. code:: python + + import pygad + import numpy + + def fitness_func(ga_instanse, solution, solution_idx): + return numpy.random.rand() + + def on_start(ga_instanse): + print("on_start") + + def on_fitness(ga_instanse, last_gen_fitness): + print("on_fitness") + + def on_parents(ga_instanse, last_gen_parents): + print("on_parents") + + def on_crossover(ga_instanse, last_gen_offspring): + print("on_crossover") + + def on_mutation(ga_instanse, last_gen_offspring): + print("on_mutation") + + def on_generation(ga_instanse): + print("on_generation\n") + + def on_stop(ga_instanse, last_gen_fitness): + print("on_stop") + + ga_instance = pygad.GA(num_generations=5, + num_parents_mating=4, + sol_per_pop=10, + num_genes=2, + on_start=on_start, + on_fitness=on_fitness, + on_parents=on_parents, + on_crossover=on_crossover, + on_mutation=on_mutation, + on_generation=on_generation, + on_stop=on_stop, + fitness_func=fitness_func) + + ga_instance.run() + +Assign Methods +-------------- + +The next example has all the method defined inside the class ``Test``. +All of the methods accept an additional parameter representing the +method's object of the class ``Test``. + +All methods accept ``self`` as the first parameter and the instance of +the ``pygad.GA`` class as the last parameter. + +.. code:: python + + import pygad + import numpy + + class Test: + def fitness_func(self, ga_instanse, solution, solution_idx): + return numpy.random.rand() + + def on_start(self, ga_instanse): + print("on_start") + + def on_fitness(self, ga_instanse, last_gen_fitness): + print("on_fitness") + + def on_parents(self, ga_instanse, last_gen_parents): + print("on_parents") + + def on_crossover(self, ga_instanse, last_gen_offspring): + print("on_crossover") + + def on_mutation(self, ga_instanse, last_gen_offspring): + print("on_mutation") + + def on_generation(self, ga_instanse): + print("on_generation\n") + + def on_stop(self, ga_instanse, last_gen_fitness): + print("on_stop") + + ga_instance = pygad.GA(num_generations=5, + num_parents_mating=4, + sol_per_pop=10, + num_genes=2, + on_start=Test().on_start, + on_fitness=Test().on_fitness, + on_parents=Test().on_parents, + on_crossover=Test().on_crossover, + on_mutation=Test().on_mutation, + on_generation=Test().on_generation, + on_stop=Test().on_stop, + fitness_func=Test().fitness_func) + + ga_instance.run() + +.. |image1| image:: https://github.com/ahmedfgad/GeneticAlgorithmPython/assets/16560492/7896f8d8-01c5-4ff9-8d15-52191c309b63 +.. |image2| image:: https://user-images.githubusercontent.com/16560492/189273225-67ffad41-97ab-45e1-9324-429705e17b20.png diff --git a/pygad/pygad.py b/pygad/pygad.py index 436237b..88a2d9d 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -1147,6 +1147,21 @@ def __init__(self, self.valid_parameters = False raise TypeError(f"The value passed to the 'save_solutions' parameter must be of type bool but {type(save_solutions)} found.") + def validate_multi_stop_criteria(self, stop_word, number): + if stop_word == 'reach': + pass + else: + self.valid_parameters = False + raise ValueError(f"Passing multiple numbers following the keyword in the 'stop_criteria' parameter is expected only with the 'reach' keyword but the keyword ({stop_word}) found.") + + for idx, num in enumerate(number): + if num.replace(".", "").replace("-", "").isnumeric(): + number[idx] = float(num) + else: + self.valid_parameters = False + raise ValueError(f"The value(s) following the stop word in the 'stop_criteria' parameter must be numeric but the value ({num}) of type {type(num)} found.") + return number + self.stop_criteria = [] self.supported_stop_words = ["reach", "saturate"] if stop_criteria is None: @@ -1168,7 +1183,7 @@ def __init__(self, if len(criterion) == 2: # There is only a single number. number = number[0] - if number.replace(".", "").isnumeric(): + if number.replace(".", "").replace("-", "").isnumeric(): number = float(number) else: self.valid_parameters = False @@ -1176,21 +1191,8 @@ def __init__(self, self.stop_criteria.append([stop_word, number]) elif len(criterion) > 2: - if stop_word == 'reach': - pass - else: - self.valid_parameters = False - raise ValueError(f"Passing multiple numbers following the keyword in the 'stop_criteria' parameter is expected only with the 'reach' keyword but the keyword ({stop_word}) found.") - - for idx, num in enumerate(number): - if num.replace(".", "").isnumeric(): - number[idx] = float(num) - else: - self.valid_parameters = False - raise ValueError(f"The value(s) following the stop word in the 'stop_criteria' parameter must be numeric but the value ({num}) of type {type(num)} found.") - + number = validate_multi_stop_criteria(self, stop_word, number) self.stop_criteria.append([stop_word] + number) - else: self.valid_parameters = False raise ValueError(f"For format of a single criterion in the 'stop_criteria' parameter is 'word_number' but '{stop_criteria}' found.") @@ -1201,10 +1203,11 @@ def __init__(self, for idx, val in enumerate(stop_criteria): if type(val) is str: criterion = val.split("_") + stop_word = criterion[0] + number = criterion[1:] if len(criterion) == 2: - stop_word = criterion[0] - number = criterion[1] - + # There is only a single number. + number = number[0] if stop_word in self.supported_stop_words: pass else: @@ -1218,7 +1221,9 @@ def __init__(self, raise ValueError(f"The value following the stop word in the 'stop_criteria' parameter must be a number but the value ({number}) of type {type(number)} found.") self.stop_criteria.append([stop_word, number]) - + elif len(criterion) > 2: + number = validate_multi_stop_criteria(self, stop_word, number) + self.stop_criteria.append([stop_word] + number) else: self.valid_parameters = False raise ValueError(f"The format of a single criterion in the 'stop_criteria' parameter is 'word_number' but {criterion} found.") diff --git a/tests/test_stop_criteria.py b/tests/test_stop_criteria.py index c950be2..3ee1737 100644 --- a/tests/test_stop_criteria.py +++ b/tests/test_stop_criteria.py @@ -144,6 +144,43 @@ def test_number_calls_fitness_function_parallel_processing_process_5_patch_4_mul fitness_batch_size=4, parallel_processing=['process', 5]) +# Stop Criteria +def test_number_calls_fitness_function_multi_objective_stop_criteria_str_single_value(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_10') + +def test_number_calls_fitness_function_multi_objective_stop_criteria_str(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_10_20') + +def test_number_calls_fitness_function_multi_objective_stop_criteria_str_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_-1.0_0.5') + +def test_number_calls_fitness_function_multi_objective_stop_criteria_list(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_10_20', 'reach_5_2']) + +def test_number_calls_fitness_function_multi_objective_stop_criteria_list_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_-1.0_0.5', 'reach_5_-2.8']) + +def test_number_calls_fitness_function_single_objective_stop_criteria_str(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_10') + +def test_number_calls_fitness_function_single_objective_stop_criteria_str_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_-1.7') + +def test_number_calls_fitness_function_single_objective_stop_criteria_list(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_10', 'reach_5']) + +def test_number_calls_fitness_function_single_objective_stop_criteria_list_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_-1.5', 'reach_-2.8']) + if __name__ == "__main__": print() test_number_calls_fitness_function_no_parallel_processing() @@ -172,3 +209,26 @@ def test_number_calls_fitness_function_parallel_processing_process_5_patch_4_mul print() test_number_calls_fitness_function_parallel_processing_process_5_patch_4_multi_objective() print() + + #### Multi-Objective Stop Criteria + test_number_calls_fitness_function_multi_objective_stop_criteria_str_single_value() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_str() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_str_decimal() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_list() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_list_decimal() + print() + + #### Single-Objective Stop Criteria + test_number_calls_fitness_function_single_objective_stop_criteria_str() + print() + test_number_calls_fitness_function_single_objective_stop_criteria_str_decimal() + print() + test_number_calls_fitness_function_single_objective_stop_criteria_list() + print() + test_number_calls_fitness_function_single_objective_stop_criteria_list_decimal() + print() + From 22b579f92dd8c6e9b3b0ffba94ca51a4530a0a97 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 6 Feb 2025 15:54:43 -0500 Subject: [PATCH 30/79] Call get_matplotlib() --- pygad/visualize/plot.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pygad/visualize/plot.py b/pygad/visualize/plot.py index 3265de3..ab6bd25 100644 --- a/pygad/visualize/plot.py +++ b/pygad/visualize/plot.py @@ -235,6 +235,8 @@ def plot_genes(self, self.logger.error("The plot_genes() method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") raise RuntimeError("The plot_genes() method can only be called after completing at least 1 generation but ({self.generations_completed}) is completed.") + matplt = get_matplotlib() + if type(solutions) is str: if solutions == 'all': if self.save_solutions: From 6a5ef12968bad43803c8f79f4694dbaa3ce62c39 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sat, 8 Mar 2025 11:45:45 -0500 Subject: [PATCH 31/79] Link to optimization gadget --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 75cd596..08bd7e4 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ [PyGAD](https://pypi.org/project/pygad) is an open-source easy-to-use Python 3 library for building the genetic algorithm and optimizing machine learning algorithms. It supports Keras and PyTorch. PyGAD supports optimizing both single-objective and multi-objective problems. +> Try the [Optimization Gadget](https://optimgadget.com), a free cloud-based tool powered by PyGAD. It simplifies optimization by reducing or eliminating the need for coding while providing insightful visualizations. + Check documentation of the [PyGAD](https://pygad.readthedocs.io/en/latest). [![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)]( From a8cf03ac645931baec1355e58227ac470ee96bc0 Mon Sep 17 00:00:00 2001 From: ekn1503 <39153577+ekn1503@users.noreply.github.com> Date: Fri, 25 Apr 2025 18:15:59 +0200 Subject: [PATCH 32/79] Update cnn.py NEP 34: https://numpy.org/neps/nep-0034-infer-dtype-is-object.html --- pygad/cnn/cnn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygad/cnn/cnn.py b/pygad/cnn/cnn.py index 1bd9033..82dc8de 100644 --- a/pygad/cnn/cnn.py +++ b/pygad/cnn/cnn.py @@ -143,7 +143,7 @@ def layers_weights_as_matrix(model, vector_weights): # Currently, the weights of the layers are in the reverse order. In other words, the weights of the first layer are at the last index of the 'network_weights' list while the weights of the last layer are at the first index. # Reversing the 'network_weights' list to order the layers' weights according to their location in the network architecture (i.e. the weights of the first layer appears at index 0 of the list). network_weights.reverse() - return numpy.array(network_weights) + return numpy.array(network_weights, dtype=object) # NEP 34: https://numpy.org/neps/nep-0034-infer-dtype-is-object.html def layers_weights_as_vector(model, initial=True): From 4f35bd068ec9ef93729d1c28e1f81bde12f3b8c8 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Mon, 23 Jun 2025 14:02:53 -0400 Subject: [PATCH 33/79] Optimize float unique gene --- pygad/helper/unique.py | 90 ++++++++++++++++++++++++------------------ 1 file changed, 52 insertions(+), 38 deletions(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 8b523f3..cf4d863 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -184,13 +184,9 @@ def unique_int_gene_from_range(self, all_gene_values = numpy.asarray(all_gene_values, dtype[0]) - values_to_select_from = list(set(list(all_gene_values)) - set(solution)) - - if len(values_to_select_from) == 0: - # If there are no values, then keep the current gene value. - selected_value = solution[gene_index] - else: - selected_value = random.choice(values_to_select_from) + selected_value = self.select_unique_value(gene_values=all_gene_values, + solution=solution, + gene_index=gene_index) selected_value = dtype[0](selected_value) @@ -224,39 +220,57 @@ def unique_float_gene_from_range(self, # The gene_type is of the form [type, precision] dtype = gene_type - for trial_index in range(num_trials): - temp_val = numpy.random.uniform(low=min_val, - high=max_val, - size=1)[0] + # We cannot have a list of all values out of a continous range. + # Solution is to create a subset (e.g. 100) of all the values. + some_gene_values = numpy.random.uniform(low=min_val, + high=max_val, + size=100) - # If mutation is by replacement, do not add the current gene value into the list. - # This is to avoid replacing the value by itself again. We are doing nothing in this case. - if mutation_by_replacement: - pass - else: - temp_val = temp_val + solution[gene_index] + # If mutation is by replacement, do not add the current gene value into the list. + # This is to avoid replacing the value by itself again. We are doing nothing in this case. + if mutation_by_replacement: + pass + else: + some_gene_values = some_gene_values + solution[gene_index] + + if not dtype[1] is None: + # Precision is available and we have to round the number. + # Convert the data type and round the number. + some_gene_values = numpy.round(numpy.asarray(some_gene_values, + dtype[0]), + dtype[1]) + else: + # There is no precision and rounding the number is not needed. The type is [type, None] + # Just convert the data type. + some_gene_values = numpy.asarray(some_gene_values, + dtype[0]) + + selected_value = self.select_unique_value(gene_values=some_gene_values, + solution=solution, + gene_index=gene_index) + return selected_value - if not dtype[1] is None: - # Precision is available and we have to round the number. - # Convert the data type and round the number. - temp_val = numpy.round(dtype[0](temp_val), - dtype[1]) - else: - # There is no precision and rounding the number is not needed. The type is [type, None] - # Just convert the data type. - temp_val = dtype[0](temp_val) - - if temp_val in solution and trial_index == (num_trials - 1): - # If there are no values, then keep the current gene value. - if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but cannot find a value to prevent duplicates.") - selected_value = solution[gene_index] - elif temp_val in solution: - # Keep trying in the other remaining trials. - continue - else: - # Unique gene value found. - selected_value = temp_val - break + def select_unique_value(self, gene_values, solution, gene_index): + + """ + Select a unique value (if possible) from a list of gene values. + + Args: + gene_values (NumPy Array): An array of values from which a unique value should be selected. + solution (list): A solution containing genes, potentially with duplicate values. + + Returns: + selected_gene: The new (hopefully unique) value of the gene. If no unique value can be found, the original gene value is returned. + """ + + values_to_select_from = list(set(list(some_gene_values)) - set(solution)) + + if len(values_to_select_from) == 0: + # If there are no values, then keep the current gene value. + if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but cannot find a value to prevent duplicates.") + selected_value = solution[gene_index] + else: + selected_value = random.choice(values_to_select_from) return selected_value From 2ba9c3b4813c2426085821be1158fe2edf6a2dd1 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Mon, 23 Jun 2025 14:07:37 -0400 Subject: [PATCH 34/79] Rename variable --- pygad/helper/unique.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index cf4d863..a74a9c0 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -263,7 +263,7 @@ def select_unique_value(self, gene_values, solution, gene_index): selected_gene: The new (hopefully unique) value of the gene. If no unique value can be found, the original gene value is returned. """ - values_to_select_from = list(set(list(some_gene_values)) - set(solution)) + values_to_select_from = list(set(list(gene_values)) - set(solution)) if len(values_to_select_from) == 0: # If there are no values, then keep the current gene value. From 1070ae7ee6aad0786d7b2ea777780a9bba07bc40 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Mon, 23 Jun 2025 14:09:29 -0400 Subject: [PATCH 35/79] Use Ubuntu latest for Py3.7 workflow --- .github/workflows/main_py37.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_py37.yml b/.github/workflows/main_py37.yml index 037086e..427aaec 100644 --- a/.github/workflows/main_py37.yml +++ b/.github/workflows/main_py37.yml @@ -8,7 +8,7 @@ on: jobs: job_id_1: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest name: PyTest Workflow Job steps: From f11b0ad795bcc8cf79252ed1b21856bdfd4cb4d6 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 26 Jun 2025 09:33:12 -0400 Subject: [PATCH 36/79] Refactor the code --- pygad/pygad.py | 36 ++++++- pygad/utils/mutation.py | 232 ++++++++++++++++------------------------ 2 files changed, 123 insertions(+), 145 deletions(-) diff --git a/pygad/pygad.py b/pygad/pygad.py index 88a2d9d..1a34b9a 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -49,6 +49,7 @@ def __init__(self, random_mutation_min_val=-1.0, random_mutation_max_val=1.0, gene_space=None, + gene_constraint=None, allow_duplicate_genes=True, on_start=None, on_fitness=None, @@ -104,6 +105,8 @@ def __init__(self, gene_space: It accepts a list of all possible values of the gene. This list is used in the mutation step. Should be used only if the gene space is a set of discrete values. No need for the 2 parameters (random_mutation_min_val and random_mutation_max_val) if the parameter gene_space exists. Added in PyGAD 2.5.0. In PyGAD 2.11.0, the gene_space can be assigned a dict. + gene_constraint: It accepts a list of constraints for the genes. Each constraint is a Python function. Added in PyGAD 3.5.0. + on_start: Accepts a function/method to be called only once before the genetic algorithm starts its evolution. If function, then it must accept a single parameter representing the instance of the genetic algorithm. If method, then it must accept 2 parameters where the second one refers to the method's object. Added in PyGAD 2.6.0. on_fitness: Accepts a function/method to be called after calculating the fitness values of all solutions in the population. If function, then it must accept 2 parameters: 1) a list of all solutions' fitness values 2) the instance of the genetic algorithm. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. on_parents: Accepts a function/method to be called after selecting the parents that mates. If function, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one represents the selected parents. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. @@ -558,13 +561,38 @@ def __init__(self, self.random_mutation_min_val = random_mutation_min_val self.random_mutation_max_val = random_mutation_max_val + # Validate that gene_constraint is a list or tuple and every element inside it is either None or callable. + if gene_constraint: + if type(gene_constraint) in [list, tuple]: + for constraint_idx, item in enumerate(gene_constraint): + # Check whether the element is None or a callable. + if item and callable(item): + if item.__code__.co_argcount == 1: + # Every callable is valid if it receives a single argument. + # This argument represents the solution. + pass + else: + self.valid_parameters = False + 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).") + else: + self.valid_parameters = False + 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.") + else: + self.valid_parameters = False + raise TypeError(f"The expected type of the 'gene_constraint' parameter is either list or tuple. But the value {gene_constraint} of type {type(gene_constraint)} found.") + else: + # It is None. + pass + + self.gene_constraint = gene_constraint + # Validating the number of parents to be selected for mating (num_parents_mating) if num_parents_mating <= 0: self.valid_parameters = False raise ValueError(f"The number of parents mating (num_parents_mating) parameter must be > 0 but ({num_parents_mating}) found. \nThe following parameters must be > 0: \n1) Population size (i.e. number of solutions per population) (sol_per_pop).\n2) Number of selected parents in the mating pool (num_parents_mating).\n") # Validating the number of parents to be selected for mating: num_parents_mating - if (num_parents_mating > self.sol_per_pop): + if num_parents_mating > self.sol_per_pop: self.valid_parameters = False raise ValueError(f"The number of parents to select for mating ({num_parents_mating}) cannot be greater than the number of solutions in the population ({self.sol_per_pop}) (i.e., num_parents_mating must always be <= sol_per_pop).\n") @@ -572,11 +600,11 @@ def __init__(self, # crossover: Refers to the method that applies the crossover operator based on the selected type of crossover in the crossover_type property. # Validating the crossover type: crossover_type - if (crossover_type is None): + if crossover_type is None: self.crossover = None elif inspect.ismethod(crossover_type): # Check if the crossover_type is a method that accepts 4 paramaters. - if (crossover_type.__code__.co_argcount == 4): + if crossover_type.__code__.co_argcount == 4: # The crossover method assigned to the crossover_type parameter is validated. self.crossover = crossover_type else: @@ -584,7 +612,7 @@ def __init__(self, raise ValueError(f"When 'crossover_type' is assigned to a method, then this crossover method must accept 4 parameters:\n1) Expected to be the 'self' object.\n2) The selected parents.\n3) The size of the offspring to be produced.\n4) The instance from the pygad.GA class.\n\nThe passed crossover method named '{crossover_type.__code__.co_name}' accepts {crossover_type.__code__.co_argcount} parameter(s).") elif callable(crossover_type): # Check if the crossover_type is a function that accepts 2 paramaters. - if (crossover_type.__code__.co_argcount == 3): + if crossover_type.__code__.co_argcount == 3: # The crossover function assigned to the crossover_type parameter is validated. self.crossover = crossover_type else: diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index d0ca1b4..4309269 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -10,7 +10,7 @@ class Mutation: - def __init__(): + def __init__(self): pass def random_mutation(self, offspring): @@ -43,6 +43,24 @@ def random_mutation(self, offspring): return offspring + def get_mutation_range(self, gene_index): + + """ + Returns the minimum and maximum values of the mutation range. + It accepts a single parameter: + -gene_index: The index of the gene to mutate. Only used if the gene has a specific mutation range + It returns the minimum and maximum values of the mutation range. + """ + + # We can use either random_mutation_min_val or random_mutation_max_val. + if type(self.random_mutation_min_val) in self.supported_int_float_types: + range_min = self.random_mutation_min_val + range_max = self.random_mutation_max_val + else: + range_min = self.random_mutation_min_val[gene_index] + range_max = self.random_mutation_max_val[gene_index] + return range_min, range_max + def mutation_by_space(self, offspring): """ @@ -57,12 +75,7 @@ def mutation_by_space(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), self.mutation_num_genes)) for gene_idx in mutation_indices: - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) if self.gene_space_nested: # Returning the current gene space from the 'gene_space' attribute. @@ -201,12 +214,7 @@ def mutation_probs_by_space(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) if probs[gene_idx] <= self.mutation_probability: if self.gene_space_nested: @@ -292,6 +300,52 @@ def mutation_probs_by_space(self, offspring): num_trials=10) return offspring + + def change_random_mutation_value_dtype(self, random_value, gene_index): + """ + Change the data type of the random value used to apply mutation. + It accepts 2 parameters: + -random_value: The random value to change its data type. + -gene_index: The index of the target gene. + It returns the new value after changing the data type. + """ + + # If the mutation_by_replacement attribute is True, then the random value replaces the current gene value. + if self.mutation_by_replacement: + if self.gene_type_single == True: + random_value = self.gene_type[0](random_value) + else: + random_value = self.gene_type[gene_index][0](random_value) + if type(random_value) is numpy.ndarray: + random_value = random_value[0] + # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. + else: + if self.gene_type_single == True: + random_value = self.gene_type[0](offspring[offspring_idx, gene_index] + random_value) + else: + random_value = self.gene_type[gene_index][0](offspring[offspring_idx, gene_index] + random_value) + if type(random_value) is numpy.ndarray: + random_value = random_value[0] + return random_value + + def round_random_mutation_value(self, random_value, gene_index): + """ + Round the random value used to apply mutation. + It accepts 2 parameters: + -random_value: The random value to round its value. + -gene_index: The index of the target gene. Only used if nested gene_type is used. + It returns the new value after being rounded. + """ + + # Round the gene + if self.gene_type_single == True: + if not self.gene_type[1] is None: + random_value = numpy.round(random_value, self.gene_type[1]) + else: + if not self.gene_type[gene_index][1] is None: + random_value = numpy.round(random_value, self.gene_type[gene_index][1]) + return random_value + def mutation_randomly(self, offspring): """ @@ -306,41 +360,17 @@ def mutation_randomly(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), self.mutation_num_genes)) for gene_idx in mutation_indices: - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) # Generating a random value. random_value = numpy.random.uniform(low=range_min, high=range_max, size=1)[0] - # If the mutation_by_replacement attribute is True, then the random value replaces the current gene value. - if self.mutation_by_replacement: - if self.gene_type_single == True: - random_value = self.gene_type[0](random_value) - else: - random_value = self.gene_type[gene_idx][0](random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] - # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. - else: - if self.gene_type_single == True: - random_value = self.gene_type[0](offspring[offspring_idx, gene_idx] + random_value) - else: - random_value = self.gene_type[gene_idx][0](offspring[offspring_idx, gene_idx] + random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] + # Change the random mutation value data type. + random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) - # Round the gene - if self.gene_type_single == True: - if not self.gene_type[1] is None: - random_value = numpy.round(random_value, self.gene_type[1]) - else: - if not self.gene_type[gene_idx][1] is None: - random_value = numpy.round(random_value, self.gene_type[gene_idx][1]) + # Round the gene. + random_value = self.round_random_mutation_value(random_value, gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -363,47 +393,23 @@ def mutation_probs_randomly(self, offspring): It returns an array of the mutated offspring. """ - # Random mutation changes one or more gene in each offspring randomly. + # Random mutation changes one or more genes in each offspring randomly. for offspring_idx in range(offspring.shape[0]): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) if probs[gene_idx] <= self.mutation_probability: # Generating a random value. random_value = numpy.random.uniform(low=range_min, high=range_max, size=1)[0] - # If the mutation_by_replacement attribute is True, then the random value replaces the current gene value. - if self.mutation_by_replacement: - if self.gene_type_single == True: - random_value = self.gene_type[0](random_value) - else: - random_value = self.gene_type[gene_idx][0](random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] - # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. - else: - if self.gene_type_single == True: - random_value = self.gene_type[0](offspring[offspring_idx, gene_idx] + random_value) - else: - random_value = self.gene_type[gene_idx][0](offspring[offspring_idx, gene_idx] + random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] + # Change the random mutation value data type. + random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) - # Round the gene - if self.gene_type_single == True: - if not self.gene_type[1] is None: - random_value = numpy.round(random_value, self.gene_type[1]) - else: - if not self.gene_type[gene_idx][1] is None: - random_value = numpy.round(random_value, self.gene_type[gene_idx][1]) + # Round the gene. + random_value = self.round_random_mutation_value(random_value, gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -701,12 +707,7 @@ def adaptive_mutation_by_space(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), adaptive_mutation_num_genes)) for gene_idx in mutation_indices: - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) if self.gene_space_nested: # Returning the current gene space from the 'gene_space' attribute. @@ -780,7 +781,7 @@ def adaptive_mutation_by_space(self, offspring): high=range_max, size=1)[0] - # Assinging the selected value from the space to the gene. + # Assigning the selected value from the space to the gene. if self.gene_type_single == True: if not self.gene_type[1] is None: offspring[offspring_idx, gene_idx] = numpy.round(self.gene_type[0](value_from_space), @@ -844,40 +845,17 @@ def adaptive_mutation_randomly(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), adaptive_mutation_num_genes)) for gene_idx in mutation_indices: - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) # Generating a random value. random_value = numpy.random.uniform(low=range_min, high=range_max, size=1)[0] - # If the mutation_by_replacement attribute is True, then the random value replaces the current gene value. - if self.mutation_by_replacement: - if self.gene_type_single == True: - random_value = self.gene_type[0](random_value) - else: - random_value = self.gene_type[gene_idx][0](random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] - # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. - else: - if self.gene_type_single == True: - random_value = self.gene_type[0](offspring[offspring_idx, gene_idx] + random_value) - else: - random_value = self.gene_type[gene_idx][0](offspring[offspring_idx, gene_idx] + random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] + # Change the random mutation value data type. + random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) - if self.gene_type_single == True: - if not self.gene_type[1] is None: - random_value = numpy.round(random_value, self.gene_type[1]) - else: - if not self.gene_type[gene_idx][1] is None: - random_value = numpy.round(random_value, self.gene_type[gene_idx][1]) + # Round the gene. + random_value = self.round_random_mutation_value(random_value, gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -936,12 +914,7 @@ def adaptive_mutation_probs_by_space(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) if probs[gene_idx] <= adaptive_mutation_probability: if self.gene_space_nested: @@ -1079,41 +1052,18 @@ def adaptive_mutation_probs_randomly(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - if type(self.random_mutation_min_val) in self.supported_int_float_types: - range_min = self.random_mutation_min_val - range_max = self.random_mutation_max_val - else: - range_min = self.random_mutation_min_val[gene_idx] - range_max = self.random_mutation_max_val[gene_idx] + range_min, range_max = self.get_mutation_range(gene_idx) if probs[gene_idx] <= adaptive_mutation_probability: # Generating a random value. random_value = numpy.random.uniform(low=range_min, high=range_max, size=1)[0] - # If the mutation_by_replacement attribute is True, then the random value replaces the current gene value. - if self.mutation_by_replacement: - if self.gene_type_single == True: - random_value = self.gene_type[0](random_value) - else: - random_value = self.gene_type[gene_idx][0](random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] - # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. - else: - if self.gene_type_single == True: - random_value = self.gene_type[0](offspring[offspring_idx, gene_idx] + random_value) - else: - random_value = self.gene_type[gene_idx][0](offspring[offspring_idx, gene_idx] + random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] + # Change the random mutation value data type. + random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) - if self.gene_type_single == True: - if not self.gene_type[1] is None: - random_value = numpy.round(random_value, self.gene_type[1]) - else: - if not self.gene_type[gene_idx][1] is None: - random_value = numpy.round(random_value, self.gene_type[gene_idx][1]) + # Round the gene. + random_value = self.round_random_mutation_value(random_value, gene_idx) offspring[offspring_idx, gene_idx] = random_value From 4d1743804df7889bdda18bbe7b65594c6870051a Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 26 Jun 2025 09:34:50 -0400 Subject: [PATCH 37/79] Refactor the code --- pygad/pygad.py | 2 +- pygad/utils/mutation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pygad/pygad.py b/pygad/pygad.py index 1a34b9a..833dd92 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -569,7 +569,7 @@ def __init__(self, if item and callable(item): if item.__code__.co_argcount == 1: # Every callable is valid if it receives a single argument. - # This argument represents the solution. + # This argument represents the solution pass else: self.valid_parameters = False diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index 4309269..fcd772e 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -352,7 +352,7 @@ def mutation_randomly(self, offspring): Applies the random mutation the mutation probability. For each gene, if its probability is <= that mutation probability, then it will be mutated randomly. It accepts a single parameter: -offspring: The offspring to mutate. - It returns an array of the mutated offspring. + It returns an array of the mutated offspring """ # Random mutation changes one or more genes in each offspring randomly. From 44a9070f2f1af69b2dd25e73525d5021877490f2 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 26 Jun 2025 09:38:29 -0400 Subject: [PATCH 38/79] Refactor the code --- pygad/pygad.py | 2 +- pygad/utils/mutation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pygad/pygad.py b/pygad/pygad.py index 833dd92..1a34b9a 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -569,7 +569,7 @@ def __init__(self, if item and callable(item): if item.__code__.co_argcount == 1: # Every callable is valid if it receives a single argument. - # This argument represents the solution + # This argument represents the solution. pass else: self.valid_parameters = False diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index fcd772e..4309269 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -352,7 +352,7 @@ def mutation_randomly(self, offspring): Applies the random mutation the mutation probability. For each gene, if its probability is <= that mutation probability, then it will be mutated randomly. It accepts a single parameter: -offspring: The offspring to mutate. - It returns an array of the mutated offspring + It returns an array of the mutated offspring. """ # Random mutation changes one or more genes in each offspring randomly. From d06837b352516fdcf6e1d4f7948d2b49ce146519 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 29 Jun 2025 13:54:09 -0400 Subject: [PATCH 39/79] Generate constrained random values --- example.py | 28 +++ pygad/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 227 bytes pygad/__pycache__/pygad.cpython-310.pyc | Bin 0 -> 82308 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 255 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 0 -> 16272 bytes pygad/helper/unique.py | 3 +- pygad/pygad.py | 30 +-- .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 354 bytes .../__pycache__/crossover.cpython-310.pyc | Bin 0 -> 6073 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 0 -> 24993 bytes pygad/utils/__pycache__/nsga2.cpython-310.pyc | Bin 0 -> 7299 bytes .../parent_selection.cpython-310.pyc | Bin 0 -> 14577 bytes pygad/utils/mutation.py | 198 +++++++++++++++--- .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 259 bytes .../__pycache__/plot.cpython-310.pyc | Bin 0 -> 13852 bytes 15 files changed, 213 insertions(+), 46 deletions(-) create mode 100644 example.py create mode 100644 pygad/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/__pycache__/pygad.cpython-310.pyc create mode 100644 pygad/helper/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/helper/__pycache__/unique.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/crossover.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/mutation.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/nsga2.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/parent_selection.cpython-310.pyc create mode 100644 pygad/visualize/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/visualize/__pycache__/plot.cpython-310.pyc diff --git a/example.py b/example.py new file mode 100644 index 0000000..1299a46 --- /dev/null +++ b/example.py @@ -0,0 +1,28 @@ +import pygad +import numpy + +function_inputs = [4,-2,3.5,5,-11,-4.7] +desired_output = 44 + +def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution*function_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + +num_genes = len(function_inputs) + +ga_instance = pygad.GA(num_generations=100, + num_parents_mating=10, + sol_per_pop=20, + num_genes=num_genes, + mutation_num_genes=6, + fitness_func=fitness_func, + # suppress_warnings=True, + random_mutation_min_val=4, + random_mutation_max_val=10, + mutation_by_replacement=True, + gene_type=int, + # mutation_probability=0.4, + gene_constraint=[lambda x: x[0]>=8,None,None,None,None,None]) + +ga_instance.run() diff --git a/pygad/__pycache__/__init__.cpython-310.pyc b/pygad/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..38c074e3eb591001514bf5fd8106cdef041639fa GIT binary patch literal 227 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 literal 0 HcmV?d00001 diff --git a/pygad/helper/__pycache__/__init__.cpython-310.pyc b/pygad/helper/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc306e5f101ca37b1c45133fc964cb603d422952 GIT binary patch literal 255 zcmYjKOA5j;6in&|(Yo^h-FDGpcOoL5z>OOzl~9|IM%pB%sZej^jig&wUcr^Gir~PT z$GkVpsOfZ!0Qdc#FJM19@ec>Y2Bw+;QABZpN*rU1s1rN1w?lsa2_NL+c34YsuGw`E zg;6-)`n}lI4Nqy9vqCdtx~O(S$c33oC`rf-nLgY;^g7UG0%kApxUhMB^t4eYmf0%# zge06vW<{13Tp2Kwy0N*E!Pgw5nT5!o_gk0{BBZc{gt}=*t5VUrU~3GGRafhxk@E&Q CxJ6R{ literal 0 HcmV?d00001 diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad216d8e6c08a8dfe5d8f31b4c0fd979a06d7ce4 GIT binary patch literal 16272 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 literal 0 HcmV?d00001 diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index a74a9c0..94ea8d5 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -316,7 +316,6 @@ def unique_genes_by_space(self, # Update the list of duplicate indices after each iteration. _, unique_gene_indices = numpy.unique(new_solution, return_index=True) not_unique_indices = set(range(len(new_solution))) - set(unique_gene_indices) - # self.logger.info("not_unique_indices INSIDE", not_unique_indices) return new_solution, not_unique_indices, num_unsolved_duplicates @@ -551,7 +550,7 @@ def find_two_duplicates(self, # This means there is no way to solve the duplicates between the genes. # Because the space of the duplicates genes only has a single value and there is no alternatives. return None, gene - + def unpack_gene_space(self, range_min, range_max, diff --git a/pygad/pygad.py b/pygad/pygad.py index 1a34b9a..2cc5c01 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -564,24 +564,30 @@ def __init__(self, # Validate that gene_constraint is a list or tuple and every element inside it is either None or callable. if gene_constraint: if type(gene_constraint) in [list, tuple]: - for constraint_idx, item in enumerate(gene_constraint): - # Check whether the element is None or a callable. - if item and callable(item): - if item.__code__.co_argcount == 1: - # Every callable is valid if it receives a single argument. - # This argument represents the solution. + if len(gene_constraint) == self.num_genes: + for constraint_idx, item in enumerate(gene_constraint): + # Check whether the element is None or a callable. + if item is None: pass + elif item and callable(item): + if item.__code__.co_argcount == 1: + # Every callable is valid if it receives a single argument. + # This argument represents the solution. + pass + else: + self.valid_parameters = False + 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).") else: self.valid_parameters = False - 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).") - else: - self.valid_parameters = False - 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.") + 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.") + else: + self.valid_parameters = False + raise ValueError(f"The number of constrains ({len(gene_constraint)}) in the 'gene_constraint' parameter must be equal to the number of genes ({self.num_genes}).") else: self.valid_parameters = False - raise TypeError(f"The expected type of the 'gene_constraint' parameter is either list or tuple. But the value {gene_constraint} of type {type(gene_constraint)} found.") + raise TypeError(f"The expected type of the 'gene_constraint' parameter is either a list or tuple. But the value {gene_constraint} of type {type(gene_constraint)} found.") else: - # It is None. + # gene_constraint is None and not used. pass self.gene_constraint = gene_constraint diff --git a/pygad/utils/__pycache__/__init__.cpython-310.pyc b/pygad/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b5a31185773287ac709257526cea352409186d5 GIT binary patch literal 354 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 vPNsMo)umnTfQ^}WfAJv- zpY$%uo6aOJx(0AikTQm%qGCZXG$IdU>?0JTkBHb4cFFC44+J))VW)%7eO&FpejKms zGD*If#-9D~u3P-q-SJrOI~{C^J&2gv*`FcnC#x&EE~^dnv3buw$pY|E5DD_S!C3xf ziMfhIo|!1rOp1c$yw*VFo}Qm1VMn9!*mc<8#oi=SM7@FN3LS8qW|Wh|728oPL&b_R zO@}B72jpU9J%B$ast4p1)Fc71P@*PvdpE$S?)f_jDh!)}cPjwpB5D<*hyY%x72bH^ zoEBNwdG$zaHYb%wPf@Wz9ke<9kXPBRB1~t5jRLkJp{33^FIxc BmEiyY literal 0 HcmV?d00001 diff --git a/pygad/utils/__pycache__/parent_selection.cpython-310.pyc b/pygad/utils/__pycache__/parent_selection.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..491ba741c1657f376ed2ec7c603513cac8318feb GIT binary patch literal 14577 zcmeHOU5p%8R<6J9uCD%>p7D%hJ0V+}SXp=Q46(C>BzTi}<81IQjLh0uyd8(yn!eRD zHSVrz->P;pqiO-0tXPEdfY1tr5E{Rc2LvxjAR+O@0}@Z{!=e&GNFYHZ9uSZFkno** z>!-V?y?A#Mb|2jK+^)KHf6l#i&v(Cb?&+}AvJ`y&>E_MdKjez?Z*(*J(Q)$%{-Vc` zFr}w3m1#q@r@pQ*of+>b%y?JpX-vDWbSsBF>&BjEj}LZTwl#_UVYD@NxfjGvJvXZCM|pe`UK*>Fh_t%`5ne zZXxlMo(dT?lJPyIr%MTY2CJ|t?klXuOng^aomu#Zm@q12c$I`PkT3<6)a?%xPIFi$-)HQ_TbLkw)86&t zy~&O}=HWhOC%Ppj?$)jsc(LDihr1z1fzkLN-V1|GS$NYG)0mePlYDD&lDEbOsp&X= z;KzC$$ixJ5ehqN@oE< z5e^2?m_v>&t7zeBY`guwH;yCwBDLU-JTw`-SQMbHHZ$Q~v~SEvde zpoi33f5J&~?QuAs3|$&=DYs}en2elEX4x<@c05iE=0m{%cJgWVSXm!Zb1$CoU{TP^ zf$ehc9u!guRl_W2EtX9P5okDm!2JHqIPx0BfXX@_sA(;miW?u2u5wC{TVe6R*xT+l z(sg>(PEpqhqFwi5+98#6sEk7^Y3QXD3>HdO@kZnLAhpU#N-NQxJND9gQ6;U0JEY!e zg}cG7mm2+$YKn1otEmCB8}J$gNgFd$nzl1ZAqTqHc&GptiXnU#09jsz=l{o!cKr3 zooSCK_kaP=S@j6%J@rvVXnG~7995y)8q?p`Zt2tNv^F(&m1#Y$Wr*;?)QZ)8lMrCq zKyGEvT13q4!480^(OPB%$8S#L9CnDM|gP znY8Yys-h}^dgr-2=MocAwvu{=C{_2d2S*gV)Z5FEU`6?{$0WmTl6^K z;mE!~5V38aD>biJxm|kXx&1wWmjtX5uY90Iwz^8?c!(rF1d?#t9=VC+11+rpq<13z z2xSqVYETKS zDqdO;ej68+uc- z)HU^#Y65>O)liM!Y-@*)FXGR9Q**d8$4UMO?Gx@W6+Gl5&_WYDMBg_Uc!A&{f{`!- zJVXZ%IaVSDPoZacQrkCKB~eiq90s^leP5p%aea=T=)^_9Pim}|p^61m0cXi`z)zmV zy&)Kh>3#?6pxA>m;z;IwrLJ?6CW3GI`5xJtKcl+a>NYxF|>AQpPdNrSu9MZFaI)}(@S%C@Lb z9l172mHH}L6Wh1dq|N)3*uG8e&|acGSX5=jIR&E8k`fWokNyQoxpDsvT|XG9CuKTD z`!iCnV;eULV*mWKf|7M?-qpB~sPC%iqseMeuIg_q1C^Pq{+^jsuV7=J){@#$D{d#X zch!69)J%+{6>Rb*QGAtTGvpn!n3bttCsV&H2kQ58i4OB%m8gbL!iM{o|CbV$WvAZs6DnE}k%QunqX_L)AAZkhlf3#mlv=Q!N z!Ms(Qs^3~3#JdO@ZRZaskZe@&y>2z-uXb~v}sCB6inK>H1wmm zACAT^eVbaQZcZwP&o8Q6xo}Fg+qyIiVZBE$;UaHjySpYi!2yvUk0YA9X4}#ToeZGUDQ&IN*4p|x4V(Pg2br=tq@pP*OpO1+O^g@04+Oz> zZ}1g?Ke%QZHgGji>k9Npcny85mBgDY78W6?= zf@tK7fDuw5GX&^m7Tf;M!ywW2t)m8v5DU2m@DcPt7$Hwh8;O-vl7=usM!2quYlV*p zy2}7enatY((^*b*lHOSvi%)*`V*AI?cJcKVThO0nO$%okW zllL)5qd-j!_Z>eHJmdSYgeQfEeGk5j9k1_B;P4?Y3+w>$b!66BtVE!IP1-OhsB?=5UJFUjb5zGdpRl=Uz+C6~~R04da0%M>CZAXcuc3OFaoF5e! z5y(R?z$3~BYdKA5#45n78|W2IQQX(jPc<`RENV|kWrBGfD-k#3Gcr9AbD#@%=-|V|NclTtMZ_{X&5mklYx-kQUsB;V zl$of3wZ!8nL157ZGN-O2Y@x1yg-Q5i=>WEQ(1Cq5h8FU0x_>(hP1l8`b^nu^u#ZDR9E91E0G>}Cxx-xP>CB}(v3Y2m6_`rc z^l2Ms1K!__YS@cHkTg}HWPyZgv8rovxA8Gf0PFH-U)N?xZ# z7(l;7R|H<^+RU+tgM%@gz+_GrEDQkW=)UkKS&p!g1fyA9U^M(y>STe*T&27(Q}Pug zz149DzYdn4!(hz0LEr1O20o8sIC8~<1?tn29-jQ+G7j{36z6N(Db{vrQ~f8pjYxn+ zCe+c60jp-J8vYp{X`1noRmm-|rf{~-Fr}?Uru2hFm;+#%I`SORWJTv-2?Gi~3D;{4 zN6NycieY!b5o-P*yxaPYvXKA#Sn?*>L1xN)%X7U|hEV*3&AEliC0x#aTfM zo&8mKo7R#lAeNZaiGe0OPPKSFbLaj!yh}CmEKTbK$oRGZxsU=WT*tQ+9>ZD_;2V5% z?uo&F3r|pHieox>ugqP*E}&A>Ms6!9RGu&6$f%`02)b3f_CkeF>Oy*rk&|3 zd}SSYzdHC{#rJBm3a}Kt!>lZ6m_3;#twlyn1!+j2;x0y9}f? z46r{eZ3MQK++^mhtS$SClUN9|v=%Y4tV6^PpoIW27;vxp0eCac__L4zcysMO58sCK zch}Alq9luNh{%JbRmHh~aXN>CJ?@DHxW6-@9$YkwfC8E+PUHs6c0e z^1MEGf#Z;b%)qsdvcf zz->B6%#Rmm!WwRb*(;oT)kQ%_SJo1vkX#;PCeM^S7P%_J5*AA$IDz?t0f;SyCd^Ka z5KK-*-jfag9p2Y2P2R|M=gK3@BHpv*faPE@LAe~|`-2!uL=ZLt+{JS(W&#{r3^enF z!NueoEF=_CP;+$bfEis{d#PAd=|M#kQ7L=w-OT4ek{|0R6Cb8neqIc@^Y^i2W=hso zJQUM`Rr>!O!jc=CM($E@TLZF4Jg%YKc{;YBTBV%|q> zL>Kw&M_DiVQ4#e<-sELES!(N?DU58PfcKl00+(T%{v^V27Crp#8z7W7u&ihBI7eWb z(7D5zJTj29D-4DQHV&wVJ|F~m7uexA_DB99={^qEV}DbI-~cRz-9Ge2fGdmeAL|{Z zhTcqxtg?uC%8O(FnE>e##6^0SZzP3JC3iIBtr5(69HxtK=%Fh>BDwq)g_-T@Jm4yb z;OFDn+aT%TCh&o@?hZmG;g4{GJTwZ8F*kN?3QB@g<3g7fhjp*~y0=%=t|JRqdyJsHY*fTY#=hUEX(~9Udt!3S%r=!VuEW(zs zd}BvAB}DjClv-b-uruL^OgFGEI>nYM0-Z2sLE6*StbkE@93m}=7eGa2h{S3RWCcAJ zrz8AVs6@&9Hx`+H>971cN~Wjor+4DWL>!*S0jUX>mfWYp_2|)zI69g^-UbuB#t44O26<4%|Q;)zZvr zQ-@1zT4B!{GE#QTdgXT6a7sIXrC z27-de{uEj-i8ieBQmVP6E3J%=~-gQWDlm4S5#wD*QX~s=OAbQcs9e zsRq+2d={1ovGrzt?u3)5oUIoDwP5yZNt4_);PBcn;PmUxZJb4k)}MzDJNP`>IFVSH zO+mN_;eG5!?CnosiPfklvNh2El5t0G3X5XBuqaIT%?Gw9wn5mh z6ombHM%Z7M(be|lJiOYzdeuIc?K9m4+d?ugtTx(a7~*)dP~xS8Ua+iOLm%1#L}zSa zuSl4b7EaD(B`2rNRB~_$_nDa^W^xqXr5$iGhW`&gCCFGNWl^otj$vNpb8P%sFEelO za>-?vSpYXd%f-PQVw7X|HfRI=CLwDl>y!vx4wr}qmp@U+%`yAyv@D7d<+hj`Xeq$P?6Vjv7+y0Zt4(He=lMLq2b8QYml?MggCUqQ7DC z(1EZ&6Z;9;G8c+n&c0x!4~>#6uAWxxEG_;qW9uIpCKPQ`9Es(M^n(nO+1dZ8FiO6h z86^)bkx#-B+2+^4$GRI2U}12Id*s(Cxk1SrlzfenU!&wEB_fJ}2)r~4#KD0e{X|A( zc=R!!|2*^OZ=z&x1KWwiy^)KbOa;67&sutGIpddmi{Qr{xl?82n+Wv!Y^gYuPtqDz zyQr{{ZxG<&G7_J%`7M(0ElPf!5{DAKNJPZdium!6Tc1M@@rf%x%|J?rE;b*Qb(CLM~w!1C*2^j@NI!FxkSkoN5rdA0c)qD)s$YYt4tzT?QKT&lRN#nikc1%3&0rbJ`_7>a4EY5xm&ZWiqT literal 0 HcmV?d00001 diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index 4309269..cb58039 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -8,6 +8,8 @@ import pygad import concurrent.futures +import warnings + class Mutation: def __init__(self): @@ -43,7 +45,7 @@ def random_mutation(self, offspring): return offspring - def get_mutation_range(self, gene_index): + def get_random_mutation_range(self, gene_index): """ Returns the minimum and maximum values of the mutation range. @@ -75,7 +77,7 @@ def mutation_by_space(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), self.mutation_num_genes)) for gene_idx in mutation_indices: - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) if self.gene_space_nested: # Returning the current gene space from the 'gene_space' attribute. @@ -214,7 +216,7 @@ def mutation_probs_by_space(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) if probs[gene_idx] <= self.mutation_probability: if self.gene_space_nested: @@ -301,12 +303,17 @@ def mutation_probs_by_space(self, offspring): return offspring - def change_random_mutation_value_dtype(self, random_value, gene_index): + def change_random_mutation_value_dtype(self, + random_value, + gene_index, + gene_value): + # TODO Instead of passing offspring and offspring_idx, only pass offspring[offspring_idx, gene_index] """ Change the data type of the random value used to apply mutation. It accepts 2 parameters: -random_value: The random value to change its data type. -gene_index: The index of the target gene. + -gene_value: The gene value before mutation. It returns the new value after changing the data type. """ @@ -321,9 +328,9 @@ def change_random_mutation_value_dtype(self, random_value, gene_index): # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. else: if self.gene_type_single == True: - random_value = self.gene_type[0](offspring[offspring_idx, gene_index] + random_value) + random_value = self.gene_type[0](gene_value + random_value) else: - random_value = self.gene_type[gene_index][0](offspring[offspring_idx, gene_index] + random_value) + random_value = self.gene_type[gene_index][0](gene_value + random_value) if type(random_value) is numpy.ndarray: random_value = random_value[0] return random_value @@ -346,6 +353,132 @@ def round_random_mutation_value(self, random_value, gene_index): random_value = numpy.round(random_value, self.gene_type[gene_index][1]) return random_value + def mutation_generate_random_value(self, + range_min, + range_max, + gene_value, + gene_idx, + num_values=1): + """ + Randomly generate values to use for applying mutation. + It accepts: + -range_min: The minimum value in the range from which a value is selected. + -range_max: The maximum value in the range from which a value is selected. + -gene_value: The original gene value before applying mutation. + -gene_idx: The index of the gene in the solution. + -num_values: The number of random valus to generate. + If num_values=1, it returns a single numeric value. If num_values>1, it returns an array with number of values equal to num_values. + """ + + # Generating a random value. + random_value = numpy.random.uniform(low=range_min, + high=range_max, + size=num_values) + + # Change the random mutation value data type. + for idx, val in enumerate(random_value): + random_value[idx] = self.change_random_mutation_value_dtype(random_value[idx], + gene_idx, + gene_value) + + # Round the gene. + random_value[idx] = self.round_random_mutation_value(random_value[idx], gene_idx) + + # Rounding different values could return the same value multiple times. + # For example, 2.8 and 2.7 will be 3.0. + # Use the unique() function to avoid any duplicates. + random_value = numpy.unique(random_value) + + if num_values == 1: + random_value = random_value[0] + + return random_value + + def mutation_filter_values_by_constraint(self, + random_values, + solution, + gene_idx): + + """ + Filter the random values generated for mutation based on whether they meet the gene constraint in the gene_constraint parameter. + It accepts: + -random_values: The random values to filter. + -solution: The solution containing the target gene. + -gene_idx: The index of the gene in the solution. + It returns None if no values satisfy the constraint. Otherwise, an array of values that satisfy the constraint is returned. + """ + + # A list of the indices where the random values satisfy the constraint. + filtered_values_indices = [] + # A temporary solution to avoid changing the original solution. + solution_tmp = solution.copy() + # Loop through the random values to filter the ones satisfying the constraint. + for value_idx, random_value in enumerate(random_values): + solution_tmp[gene_idx] = random_value + # Check if the constraint is satisfied. + if self.gene_constraint[gene_idx](solution_tmp): + # The current value satisfies the constraint. + filtered_values_indices.append(value_idx) + + # After going through all the values, check if any value satisfies the constraint. + if len(filtered_values_indices) > 0: + # At least one value was found that meets the gene constraint. + pass + else: + # No value found for the current gene that satisfies the constraint. + if not self.suppress_warnings: + warnings.warn(f"No value found for the gene at index {gene_idx} at generation {self.generations_completed+1} that satisfies its gene constraint.") + return None + + filtered_values = random_values[filtered_values_indices] + + return filtered_values + + def mutation_process_random_value(self, + range_min, + range_max, + solution, + gene_idx): + + """ + Randomly generate constrained values to use for applying mutation. + It accepts: + -range_min: The minimum value in the range from which a value is selected. + -range_max: The maximum value in the range from which a value is selected. + -solution: The solution where the target gene exists. + -gene_idx: The index of the gene in the solution. + It returns either a single numeric value or multiple values based on whether a gene constraint exists in the gene_constraint parameter. + """ + + # Check if the gene has a constraint. + if self.gene_constraint and self.gene_constraint[gene_idx]: + # Generate random values to use for mutation. + random_values = self.mutation_generate_random_value(range_min=range_min, + range_max=range_max, + gene_value=solution[gene_idx], + gene_idx=gene_idx, + num_values=100) + # Filter the values that satisfy the constraint. + random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values, + solution=solution, + gene_idx=gene_idx) + if random_values_filtered is None: + # No value found that satisfy the constraint. + # Keep the old value. + random_value = solution[gene_idx] + else: + # Select a value randomly from the list of values satisfying the constraint. + random_value = numpy.random.choice(random_values_filtered, size=1)[0] + # The gene does not have a constraint. + else: + random_value = self.mutation_generate_random_value(range_min=range_min, + range_max=range_max, + gene_value=solution[gene_idx], + gene_idx=gene_idx, + num_values=1) + # Even that its name is singular, it might have a multiple values. + return random_value + def mutation_randomly(self, offspring): """ @@ -357,20 +490,18 @@ def mutation_randomly(self, offspring): # Random mutation changes one or more genes in each offspring randomly. for offspring_idx in range(offspring.shape[0]): - mutation_indices = numpy.array(random.sample(range(0, self.num_genes), self.mutation_num_genes)) + # Return the indices of the genes to mutate. + mutation_indices = numpy.array(random.sample(range(0, self.num_genes), + self.mutation_num_genes)) for gene_idx in mutation_indices: - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) - # Generating a random value. - random_value = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - # Change the random mutation value data type. - random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) - - # Round the gene. - random_value = self.round_random_mutation_value(random_value, gene_idx) + # Generate one or more random values that meet the gene constraint if exists. + random_value = self.mutation_process_random_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -395,21 +526,20 @@ def mutation_probs_randomly(self, offspring): # Random mutation changes one or more genes in each offspring randomly. for offspring_idx in range(offspring.shape[0]): + # The mutation probabilities for the current offspring. probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) + # A gene is mutated only if its mutation probability is less than or equal to the threshold. if probs[gene_idx] <= self.mutation_probability: - # Generating a random value. - random_value = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - # Change the random mutation value data type. - random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) - # Round the gene. - random_value = self.round_random_mutation_value(random_value, gene_idx) + # Generate one or more random values that meet the gene constraint if exists. + random_value = self.mutation_process_random_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -707,7 +837,7 @@ def adaptive_mutation_by_space(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), adaptive_mutation_num_genes)) for gene_idx in mutation_indices: - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) if self.gene_space_nested: # Returning the current gene space from the 'gene_space' attribute. @@ -845,14 +975,16 @@ def adaptive_mutation_randomly(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), adaptive_mutation_num_genes)) for gene_idx in mutation_indices: - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) # Generating a random value. random_value = numpy.random.uniform(low=range_min, high=range_max, size=1)[0] # Change the random mutation value data type. - random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) + random_value = self.change_random_mutation_value_dtype(random_value, + gene_idx, + offspring[offspring_idx, gene_idx]) # Round the gene. random_value = self.round_random_mutation_value(random_value, gene_idx) @@ -914,7 +1046,7 @@ def adaptive_mutation_probs_by_space(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) if probs[gene_idx] <= adaptive_mutation_probability: if self.gene_space_nested: @@ -1052,7 +1184,7 @@ def adaptive_mutation_probs_randomly(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - range_min, range_max = self.get_mutation_range(gene_idx) + range_min, range_max = self.get_random_mutation_range(gene_idx) if probs[gene_idx] <= adaptive_mutation_probability: # Generating a random value. @@ -1060,7 +1192,9 @@ def adaptive_mutation_probs_randomly(self, offspring): high=range_max, size=1)[0] # Change the random mutation value data type. - random_value = self.change_random_mutation_value_dtype(random_value, gene_idx) + random_value = self.change_random_mutation_value_dtype(random_value, + gene_idx, + offspring[offspring_idx, gene_idx]) # Round the gene. random_value = self.round_random_mutation_value(random_value, gene_idx) diff --git a/pygad/visualize/__pycache__/__init__.cpython-310.pyc b/pygad/visualize/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c367b5cc1c47805adcca3da71281298d99ffd67 GIT binary patch literal 259 zcmYjKOA5j;6ijLb@q-%=&}|njx>Q6wfg3kc3b8gJM$#rEsZej^jig&wUcr^GisHbV z$GkVpsL`m80Jq(a&tczN@edEg8pck5D56-R0w)+F>LniSY*FYx;DbWk_*yD=>CWO= zJl%wyWUOnR(fBNEl}T}7rp;;wgq)eNLY0JEk@4a7rstk6Qt$^+z=b*3V!5;F zJ16C=WKQInlLwJArR7nfFYaZ zJEy9vtL^s0M4Jr{X{&GFpL6d$_uRj8zjNHm<#Y+3{PEvgys{`se@}_{CxOIs_&OB? zmNX|>vZXZSIhlWzImJp?$+x7r#3ji}S?RYVE4`)6B`xJushrt-M!&jb8><_Ord3(5 zJ8iR3-?S^Mjh1Vyw5)c+HkM4sSh3xumgN|3%cu$4bWsBoPDS5ApVDlN!tO^#M8Wq3 ze4PRU@F6v%6}cnHk|&{8WmXVTS@DZ2rb{g~>hqP=4T|~c;I}OMX)bi{N+Jd7d)KV< z*TAK5##pSoOYM1MRkW7vn(JI+?yoJ{P1~*4%*J9%fM8{H13Wj+1jJ{|npWYbAmWDw~aBhpeN_ji%7CCFQXtRZ`nwU*G5T1rc68BNtR zP1khmgf(Pk-;(E&S`KCTUYYfvHEb17lG2LWh&CFQP`#3N2sIzF#;kFar?m-fQk&xP z!`cz;sCG7R1^ogp_3nv9j0l(uA7bv0LC9$HAOOTu@vy%MyRL{8dEf-1M1Umga< zB`>)scamOmyWl0Mg+)1DrzBCYP3nPa#oNB3NyyD0x7e0M-OHezjF;IiSZHydHi$aj zhO0@QY9&$s&Aqicsyh;IXG^{yz4l5c>t(#`q}0iIN&d}yD*q0<`t~S|_Ks{Nt>oK^ zmt9amSMbtS>bfHS*wdFwUct+IId9lX-%85T+BZ>_LD^rT>=5@>*pj#87*#5yQbF|{ zP>t7TeKZ~_i9mUb%v3C@H)eGv78a_*}~2acL0(4nu}vM5Bo4WvZrxF>rl zOTVSypwX8nJSCQz^b)<)_S79Zj5J7EM$4ZE^gVY_Y7bPUs2s4upKZMaLOYdGzd6;t3ebqtUq&qgtC3uE1Ev~9y) zZ#C9!%b4FV=PrhovlYuR>Q1+oZ4s}AeY0k-@-{`0Fzb$ORrF|Z&)29F$2{@cYBX9m(AoK@m_*># z-G+T08$1{&r7C>{Hyh@>-8gT&Og|V1H}=&t=FK{;i>?&ucViIY&ki7L)SLE=y5%mR zQA7=n8?6Oyna^+Ig1un28^n3bc&xvhg;vw8I+zA9ji^CU3{G%uW&sn2N#uc5`dT{o zcrY872-9fP9rqEpjTv~P*~aBkXYIE#_A^J^SKWtm)|4z((t;*YiA zjrq1~%)IW{jfG0@1aYe2L19~+a^DG1VYO>k41>#Z!q=jW<)?U+_$i(ZKNGGgKY`_R zR}mwi7o!O7%DzhbiT046a_fsr?p?)CEi}y)+aH>rZ{38-P+PK{@=^cLK#u%$qqVqb z3qNHG(Gq_4O1p{8VZTTz*0cO{Fynrb_Juz-Z#&q(twx*mw`wR-KS}!$JkW;pQ@kVn zOt>ZeM8j_SDOL-8<i4CNwzF)!jA)qj+N$0#6C`dV}* z`C4=Wuz&^WTE5y{l!6pBKY>=~a?x>8ty?$ea<#S)HpapeH&^T?qbcoCgmW4SE~e_S zmY0n$)Nyp@+(w`u$;zsnR#fG*Tu{=oA(v!D&LVb%W2#b;#}cZdf@%U`8m087{yVx)&A zLp>DwAoS1_^iXXPx+ru(w8J`D1sZ9_)3#*jYD36XOQfykC^jy2^7z$|%D)Zm3>ux* z8TPbo-5v6Vw`6L2?b)z)0eV<=Lh2Nu=VrYkm(932^f=;;Z0Di*j)of7b#G*O*wb0x zO0&LoJk+;5loYzWMHHlIkzPnz-MgSD4x&K*2y0jxYe^%^qpV?dQzh20f|P3MZCU&w zY8-+-+tsT^w#Qhn8fCre@1WF2XYt3KrFWBx2_$F56lESpP#zb@DEwq5ZE6_=@a2qo23 zTU1wDtG*T^Gc8M;B#N}Tx{9lvwF)s!6-n`l@v-Y}{yddFfuLLxPf~nWIjccmpn@+_ zKnjsKk6^CQZr0b@b~MAz+zBWo)!n(wZBHwj1LV|w_3@pqLu;wd+jyHm1#w(cxPBr~ zOAR3FKs`02@>#AKTXeJKQ-SlYuEh* zp1idznCX6wAQAyVw*ke`wfYf2P}WU(I-r#_fRwLzI$zgkQ7_#(r$q$u2$o6mXKZQ^zKPQd9X#vwDPGwS7|SaEyYDtW^J#O4*ZC#*vxS#K0- z#~|JSBcW`}8gtWDb_-*O^_c1Q;2e#5cn@t3?7=O$linzSPf)*J>k#Bb^CANb9Q8_* zQm=onL=aj`nokDO1Z^#(C7I@yrz}8nJ(>2^Bbgo!WQuyf5Y{8feq_CnmdEUs`p3NX zNb_AQ3+`!@<19Irp_dONiuUt7!i^5~1?3W>UF(c_v<3>-Q{d%~z}ZqX`fsDRL*5vT z_&ySNDw4qWV)O@-!qHF)ib%xj9#j%t-Udj+D_QcBQm1rVUi*D~x4j|&J^}|PtV!Ah zSdR?xVQsz#NOFpI;U8c}9R-4anB-BCtRo8w?JcO=#qDF(QJff1vzH(6a*!S>y(qqkkJoKzmC zV!UVq5)Eks1fJ=rGz8TN*|mwPrzBO3NYVeq-h#a@07aS&rxkx{eg@xvTJU`|@diFW zhG*!aFjtob0Q#JNP(&XvDzO?O`amz-7V59;!u1#GjYfd}ku|2#fT1Ub@W(sc2jb_w zK*oKTjO>L4VBciQ=#Aj%z}V13w@HTF$pNhitk3V_I(nz!nH{s)Ohf6P4&}3Ao3Nz; z$HzWu#DN@Gx26dVo~At?KAP#2pu8TPVZs1MR)BuSKY{1{o6&2UPp41gNiSycINE%D zKSubFjYR(RDG2n`Aaeue{Z#*Jo1w5%T`Vkc59tD5SOgI?R`HW0{>?WI$^uc}p9iAo zJ*5xK$mYw$LNr)o)x@5$*>2irLJNd|TjEr}3fWLzkE(TGg=#mg%I1arT3cyjwXsDI z@F2mB{SENu@3vBw=kih0T*43pym;tSdM*&)A&9eU%!>m>uUpX@8MVgPEgLl62L zvI=abF43j8Gl}@(@}IfyPbB~C*|H)i?aLc}COk#{ae+VIdv=EqfSneCt8lzYvsJ+H zv#X|CTdD$UU~>v8*^YPxL}-cu4mMb*uthr8ZQ=Nt72B#~fBMOqx$5{@bUKygfIf%1 zi$S0NO8ihGep>JautP|D4EK3>5bhJeH7AIY9Gb*)6r7^qECmG8`=h*A`xlCz-<3{7 z6@WvJpMc2+?ArB)uMu3rn-w;eP~(V!LJTgktw#seR1FP1S&Cy)cnE$jOfA`D{R*I{ zl(S^QRD~vC=p`FbmQ7>ThPlydy9_<~Lx82KVV~k>Xb`ClvWff2MNzlJ^OO^`ZPsi) zWp}|(ev)J@dLY%21AtU>dha}+(|cD&Fjpi?Mz9z$nk&YZYO6|Dkgt-!1RNcL82EC; z6{2=gI4+Pq^6@J%YR8lmrtVPQGCy$G%F{+RsaO1D-*JwpnhSU z`Y@Z!DtY{6l@U+`U!=K}fEOLSq!mThe>epgt;@v-TfWjCPJr8hm$5MCSVH+AnU3=@ z6_o@$5JVROZVA}NhA&5e8%ylo$YtDtP@yjha(H;5E!OROLtly95zfUEj{=g5QNtPQ z;>9N(d;Cd69zXZQ7hgPgReY7^T5i>7Yj;0%%#aN&fv@won4NbbP!6#H-dV^^h5lG+ zH{+_y8vA2ia`j#b`(yRzBmp};(wq+4Om%608%~VBlb{C_V6|Pj1gwSheZZWXc8A=o zl|UJMy_gq;;#=?^>*U^%0Ket#$NKu+SB} zBG(^ri?G6tdZXJTu)>v?dWfjqEcyVn#H}3S{Oq3mF~C?Qf?8l5RIQwqw}x+}F}5LZ zYJ#|wP{MZju+mnIyZF|bo z`0<&wMqs7;2ef_I%UYvWDQF|~ek$QY^3lBEwbvQl5 z{h8Q1gSC=$hwf<7welE<$>XI3Z&V}cQL#w}8iz*D*41L0ZcOZ1C zpneeVc6GlYd;$-dU!nQo8#iFN%KNv<`}b3M)!;sncF5y+bM`>2@i`It_&7%Vlwj=~ z^NBYhCdb&!#!ryVpF4Q#fmL+i(G_>^Bd7GhC#^<<=zeG{q`H$f?2xf@5mv0%hB|an zNgh1MC-Fz&x0E&b<=>V0WdL`pad;ZItg;5~q1`nPw=1P`)=$=5dj-^Nu4H2$+xhe_ zk#`iifj#qAVB`DL{V{3tY)?_xJJFRdHCk{tb)G?_Tlr&*LGF$Lx<5&PE#cyu6uU(M zc^CC8lHyxP{x#^tuOhOiJ9_Al$i7duViuX!6B?ei z|E|RQsE@}C8Sn$Pe~n@T*#0iD{p(an*2?frhc{S~{&9hJcA#XlM^}oEigHT)2GR2~ z=r<`wgA3g2cH6{{4tyJ(Ao>R>c!&Zra|dsu8H%x8Jh53{4b0^9e+i zTCJ6O6Yd9iirDy93x|E;Vg~Y`pepS0#-{RJ2JpwJ)S;mF3Yq+fR|t=lh6#r>0;swR zIB183z%n62x}OvFiiv+ZXf85;xqfzwdqyyq?_0+=ccnMxMuQPn<77Sa_*@o%2>b`= zo~YjJOKr>@ir1;$1R_EHgS)af!t7v0^CIC5IKIzm`aaf-a}7h)PtarLv@)V7%2Z-Z z&L;3!N#x~`#E6_vz{;MOz%!%kR!6RN>Ho>9@icPo@`%ln8p1h^HxdiGnvM z_+1M4bnFE*O@bg70yvzXN1#fo0#`$RYxP3?Kb7mf6vg!aQhu^OCrIi4A^oEA%AgWO x&i_02RZjVtDmG!QS`D`C!^A~~0%BzlkG}e)V5HAr=|~PuT~bn$ Date: Sun, 29 Jun 2025 13:59:55 -0400 Subject: [PATCH 40/79] Generate constrained random values --- pygad/utils/mutation.py | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index cb58039..eae575b 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -447,7 +447,7 @@ def mutation_process_random_value(self, -range_max: The maximum value in the range from which a value is selected. -solution: The solution where the target gene exists. -gene_idx: The index of the gene in the solution. - It returns either a single numeric value or multiple values based on whether a gene constraint exists in the gene_constraint parameter. + It returns a single numeric value the satisfies the gene constraint if exists in the gene_constraint parameter. """ # Check if the gene has a constraint. @@ -497,7 +497,7 @@ def mutation_randomly(self, offspring): range_min, range_max = self.get_random_mutation_range(gene_idx) - # Generate one or more random values that meet the gene constraint if exists. + # Generate a random value fpr mutation that meet the gene constraint if exists. random_value = self.mutation_process_random_value(range_min=range_min, range_max=range_max, solution=offspring[offspring_idx], @@ -535,7 +535,7 @@ def mutation_probs_randomly(self, offspring): # A gene is mutated only if its mutation probability is less than or equal to the threshold. if probs[gene_idx] <= self.mutation_probability: - # Generate one or more random values that meet the gene constraint if exists. + # Generate a random value fpr mutation that meet the gene constraint if exists. random_value = self.mutation_process_random_value(range_min=range_min, range_max=range_max, solution=offspring[offspring_idx], @@ -977,17 +977,11 @@ def adaptive_mutation_randomly(self, offspring): range_min, range_max = self.get_random_mutation_range(gene_idx) - # Generating a random value. - random_value = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - # Change the random mutation value data type. - random_value = self.change_random_mutation_value_dtype(random_value, - gene_idx, - offspring[offspring_idx, gene_idx]) - - # Round the gene. - random_value = self.round_random_mutation_value(random_value, gene_idx) + # Generate a random value fpr mutation that meet the gene constraint if exists. + random_value = self.mutation_process_random_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -1187,17 +1181,11 @@ def adaptive_mutation_probs_randomly(self, offspring): range_min, range_max = self.get_random_mutation_range(gene_idx) if probs[gene_idx] <= adaptive_mutation_probability: - # Generating a random value. - random_value = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - # Change the random mutation value data type. - random_value = self.change_random_mutation_value_dtype(random_value, - gene_idx, - offspring[offspring_idx, gene_idx]) - - # Round the gene. - random_value = self.round_random_mutation_value(random_value, gene_idx) + # Generate a random value fpr mutation that meet the gene constraint if exists. + random_value = self.mutation_process_random_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value From c2dc95a0d121c67993e08c12d11ec18770258ce6 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 29 Jun 2025 14:01:47 -0400 Subject: [PATCH 41/79] Use Python 3.13 and remove Python 3.7 workflow --- .github/workflows/{main_py37.yml => main_py313.yml} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename .github/workflows/{main_py37.yml => main_py313.yml} (89%) diff --git a/.github/workflows/main_py37.yml b/.github/workflows/main_py313.yml similarity index 89% rename from .github/workflows/main_py37.yml rename to .github/workflows/main_py313.yml index 427aaec..f2d96cc 100644 --- a/.github/workflows/main_py37.yml +++ b/.github/workflows/main_py313.yml @@ -1,4 +1,4 @@ -name: PyGAD PyTest / Python 3.7 +name: PyGAD PyTest / Python 3.13 on: push: @@ -15,10 +15,10 @@ jobs: - name: Checkout Pre-Built Action uses: actions/checkout@v3 - - name: Setup Python 3.7 + - name: Setup Python 3.13 uses: actions/setup-python@v4 with: - python-version: '3.7' + python-version: '3.13' - name: Install Dependencies run: | From a6d8456eaaaa60b0d354dc27c13bccbc3a27147e Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 29 Jun 2025 14:09:25 -0400 Subject: [PATCH 42/79] Allow manually triggering the workflows --- .github/workflows/main_py310.yml | 2 ++ .github/workflows/main_py311.yml | 2 ++ .github/workflows/main_py312.yml | 11 ++++++----- .github/workflows/main_py313.yml | 2 ++ .github/workflows/main_py38.yml | 2 ++ .github/workflows/main_py39.yml | 2 ++ 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main_py310.yml b/.github/workflows/main_py310.yml index 9602f17..9e701d1 100644 --- a/.github/workflows/main_py310.yml +++ b/.github/workflows/main_py310.yml @@ -5,6 +5,8 @@ on: branches: - github-actions # - master + # Manually trigger the workflow. + workflow_dispatch: jobs: job_id_1: diff --git a/.github/workflows/main_py311.yml b/.github/workflows/main_py311.yml index d468243..0ca059b 100644 --- a/.github/workflows/main_py311.yml +++ b/.github/workflows/main_py311.yml @@ -5,6 +5,8 @@ on: branches: - github-actions # - master + # Manually trigger the workflow. + workflow_dispatch: jobs: job_id_1: diff --git a/.github/workflows/main_py312.yml b/.github/workflows/main_py312.yml index 87bd648..41b0531 100644 --- a/.github/workflows/main_py312.yml +++ b/.github/workflows/main_py312.yml @@ -7,12 +7,13 @@ name: PyGAD PyTest / Python 3.12 # But Python 3.12 does not support distutils. # Let's wait until setuptools changes its dependencies. -# on: -# push: -# branches: - # - github-actions +on: + push: + branches: + - github-actions # - master -on: workflow_dispatch + # Manually trigger the workflow. + workflow_dispatch: jobs: job_id_1: diff --git a/.github/workflows/main_py313.yml b/.github/workflows/main_py313.yml index f2d96cc..0e0301c 100644 --- a/.github/workflows/main_py313.yml +++ b/.github/workflows/main_py313.yml @@ -5,6 +5,8 @@ on: branches: - github-actions # - master + # Manually trigger the workflow. + workflow_dispatch: jobs: job_id_1: diff --git a/.github/workflows/main_py38.yml b/.github/workflows/main_py38.yml index 602f917..7838c64 100644 --- a/.github/workflows/main_py38.yml +++ b/.github/workflows/main_py38.yml @@ -5,6 +5,8 @@ on: branches: - github-actions # - master + # Manually trigger the workflow. + workflow_dispatch: jobs: job_id_1: diff --git a/.github/workflows/main_py39.yml b/.github/workflows/main_py39.yml index c6b61fc..be32ab4 100644 --- a/.github/workflows/main_py39.yml +++ b/.github/workflows/main_py39.yml @@ -5,6 +5,8 @@ on: branches: - github-actions # - master + # Manually trigger the workflow. + workflow_dispatch: jobs: job_id_1: From e346488f37c0bf5537a7d0f8a4cae5ef6f012dc3 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 29 Jun 2025 14:12:46 -0400 Subject: [PATCH 43/79] Change Python 3.12 version --- .github/workflows/main_py312.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main_py312.yml b/.github/workflows/main_py312.yml index 41b0531..3527711 100644 --- a/.github/workflows/main_py312.yml +++ b/.github/workflows/main_py312.yml @@ -27,7 +27,7 @@ jobs: - name: Setup Python 3.12 uses: actions/setup-python@v4 with: - python-version: '3.12.0-beta.2' + python-version: '3.12' - name: Install Dependencies run: | From 01a614b02fb58eba5a044b93e552813c6993a76c Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 29 Jun 2025 14:17:15 -0400 Subject: [PATCH 44/79] Remove comments --- .github/workflows/main_py312.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.github/workflows/main_py312.yml b/.github/workflows/main_py312.yml index 3527711..91cc504 100644 --- a/.github/workflows/main_py312.yml +++ b/.github/workflows/main_py312.yml @@ -1,12 +1,5 @@ name: PyGAD PyTest / Python 3.12 -# Cannot install packages in Python 3.12. -# The reason is that we use pip for installing packages. -# pip uses setuptools for the installation. -# setuptools depends on distutils. -# But Python 3.12 does not support distutils. -# Let's wait until setuptools changes its dependencies. - on: push: branches: From 13055365d272e943239cbeb863d29f001aaeaa8a Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sun, 29 Jun 2025 16:13:27 -0400 Subject: [PATCH 45/79] Apply constraints in initial population --- example.py | 4 +- pygad/__pycache__/pygad.cpython-310.pyc | Bin 82308 -> 82822 bytes pygad/helper/__init__.py | 3 +- .../__pycache__/__init__.cpython-310.pyc | Bin 255 -> 282 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 0 -> 4934 bytes pygad/helper/misc.py | 122 ++++++++++++++++++ pygad/pygad.py | 71 +++++----- .../__pycache__/mutation.cpython-310.pyc | Bin 24993 -> 23567 bytes pygad/utils/mutation.py | 93 +++---------- 9 files changed, 187 insertions(+), 106 deletions(-) create mode 100644 pygad/helper/__pycache__/misc.cpython-310.pyc create mode 100644 pygad/helper/misc.py diff --git a/example.py b/example.py index 1299a46..362c5be 100644 --- a/example.py +++ b/example.py @@ -17,6 +17,8 @@ def fitness_func(ga_instance, solution, solution_idx): num_genes=num_genes, mutation_num_genes=6, fitness_func=fitness_func, + init_range_low=4, + init_range_high=10, # suppress_warnings=True, random_mutation_min_val=4, random_mutation_max_val=10, @@ -25,4 +27,4 @@ def fitness_func(ga_instance, solution, solution_idx): # mutation_probability=0.4, gene_constraint=[lambda x: x[0]>=8,None,None,None,None,None]) -ga_instance.run() +# ga_instance.run() diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc index 7601436849897624340125710ba38120fab2889f..2980c4a9d5177a6b87596a24482887043d8938bb 100644 GIT binary patch delta 11186 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 H0|yfTb+;L5 diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..93bfc50a82ee733f7e59e3c39fefa46328137788 GIT binary patch literal 4934 zcmeHLOOG5y60TQQS2r`p@*-d`Ldj#b>RrzO5<>gaB$;zyXjEIbg{JyNtcX!7Y zuIbl&@vp0v^*s$XFBgM%@n61!y9%M(ccOsVg)(bI`hH?#fxYGc1&zt?WIkd2V|ByNS); zuq|uM+UrxPE0vW@BQ9y2#c7o?o(Y!n6Vv~aCzUK&K5HZ@&2^k-Oz~_k4|**(G~-bu z3tckK$~c=Nlm%BDVUy~5FGe=^DY9Xl33+mzQ8rAsGe9qy&JljiVrXmZu)k-?l2xS? zEN&bza+EBXD`XVUkgD{YbG=dcrheBVjqdZx)+?QOwWpVbte=8wB2U9kFhul1M62E_ ztD?x2mIABP;Vj9y4rxmHALj5An)4l$mNJ>u<8^TAky$&b?YsQsJ4@}N3V!^jxPF8p zX8UX&>qRwXh01TrNS8;ZxQ|GWjw7DTa|MC4SfYNj{YH47N0h`)wkh$WG%llqVp;DY z_Tl#8j4wM>gqP4<9{aX$2j2Mp*beMd))el+11CPPq!sNOl;ns1eMzEbb<7i1;`u@acxuY2X(+Sxvy{ZvP`Hk1 zqw6`Ea2d_%iyJ9_kn}wy)ry8_x{Dy5#)M|&DH;u=!oPFVUD-e{U)XnD?Fr|ub2ik2 z)$q3cq4oLIBj+qQ8v)h))o>N;Su1blt_H%r>(Tfv;N3UF?#;JO^X%3YZG980hTqy} zL+JIsaMYWtp%?%aAq%|q`Ns%VpOH_^ZU)NG&QV-TmIH}$1!7Q=wJ12ln1GUG){w9U zzb&L|;*Sm709^;KMj2qws4_Bart*{>FXCtcKx^NFh-88-EydcD0nk=0Z}IaSQa^-R z13kue<1T<>9%tBD^rCfYDiH$7cu^!vpnMOxTOJx!v3MZBw`(u+q#_{Nn%yJnbQvmH zBs`L-%=C5k9-Ae6PJ{tu(`5g60bZ6!QV0FhCBVL)!s3$2j878~2U3^i`oK_*lDq_o z;Vq~pEt6goi8s0omf$&y)A z>#u}cnrRiosA1K%wmDl*Z4|PqAPL?RKa{S`9kBN+B4i2~InR@0zAV|if?X})?Nmk_ z{A_}wbB(IGU?2tLXfqoNm04cR7nBqVXX}0IG#*CCkcQ8qDiX?dFVZ->E|?peUcUY6 zHS)1z`~D;XSIk+?Oh!aD=e)BuCu3YGzGTN>&5LW0w<;_RaoPw?4qt_Om+ADySVBHbzp^#p)W5mrt{^#F>&!9!jeH)_8LO+Bo#7+Wu`{d`J1Uyr0& z6+%S>DkqPBYinj&Ji?jqu$u_2y6qXROr4CS~W6A%rE$3F|q!8us@E21D5 zlrsWRxcsYMCknIdl5LU$s+wz70x-(i(v&#uY^^v>gd4@%pPC;L2jN@yZSaUVK#E%^ z@o>b^F>ON|-G~kg&7q?^)G&Dxml~hBCLL|FzuxMPXs3zEISTn-(~iGJZ1p0rO}#`7 zp|tuV8qm`2Cf1g9&=r7@oH%L%vHGQ+pJs7_qe*+xpgLTG*9DH)ws@^GtNubtpFC$d zY&1);O$|6-X|flK2F=$7TQ5F`vhry(z9EQ)7W|)lpL+g%&prK9PYb`U>63|V7#BAp zg2}KB!Z5=x6=7J9!?5`s2>qQfB(iVbsK+6y{y+`IO1(@CeTZ)~^!cRdw;Cdwl170A z%DUn_J^s;o?b5%^yNO>9!%*Z=7^-I=QU}LeWjI!r>RB2N%;&oi-B4{Tna{J}t;WH3 T=)6!8Nqbh{1^5aN>?_VMgTM3= literal 0 HcmV?d00001 diff --git a/pygad/helper/misc.py b/pygad/helper/misc.py new file mode 100644 index 0000000..1052c3f --- /dev/null +++ b/pygad/helper/misc.py @@ -0,0 +1,122 @@ +""" +The pygad.helper.helper module has some generic helper methods. +""" + +import numpy +import warnings +import random +import pygad + +class Helper: + + def get_random_mutation_range(self, gene_index): + + """ + Returns the minimum and maximum values of the mutation range. + It accepts a single parameter: + -gene_index: The index of the gene to get its range. Only used if the gene has a specific mutation range + It returns the minimum and maximum values of the gene mutation range. + """ + + # We can use either random_mutation_min_val or random_mutation_max_val. + if type(self.random_mutation_min_val) in self.supported_int_float_types: + range_min = self.random_mutation_min_val + range_max = self.random_mutation_max_val + else: + range_min = self.random_mutation_min_val[gene_index] + range_max = self.random_mutation_max_val[gene_index] + return range_min, range_max + + def get_initial_population_range(self, gene_index): + + """ + Returns the minimum and maximum values of the initial population range. + It accepts a single parameter: + -gene_index: The index of the gene to get its range. Only used if the gene has a specific range + It returns the minimum and maximum values of the gene initial population range. + """ + + # We can use either init_range_low or init_range_high. + if type(self.init_range_low) in self.supported_int_float_types: + range_min = self.init_range_low + range_max = self.init_range_high + else: + range_min = self.init_range_low[gene_index] + range_max = self.init_range_high[gene_index] + return range_min, range_max + + def generate_gene_random_value(self, + range_min, + range_max, + gene_value, + gene_idx, + mutation_by_replacement, + num_values=1): + """ + Randomly generate one or more values for the gene. + It accepts: + -range_min: The minimum value in the range from which a value is selected. + -range_max: The maximum value in the range from which a value is selected. + -gene_value: The original gene value before applying mutation. + -gene_idx: The index of the gene in the solution. + -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. + -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. + If num_values=1, it returns a single numeric value. If num_values>1, it returns an array with number of values equal to num_values. + """ + + # Generating a random value. + random_value = numpy.asarray(numpy.random.uniform(low=range_min, + high=range_max, + size=num_values), + dtype=object) + + # Change the random mutation value data type. + for idx, val in enumerate(random_value): + random_value[idx] = self.change_random_mutation_value_dtype(random_value[idx], + gene_idx, + gene_value, + mutation_by_replacement=mutation_by_replacement) + + # Round the gene. + random_value[idx] = self.round_random_mutation_value(random_value[idx], gene_idx) + + # Rounding different values could return the same value multiple times. + # For example, 2.8 and 2.7 will be 3.0. + # Use the unique() function to avoid any duplicates. + random_value = numpy.unique(random_value) + + if num_values == 1: + random_value = random_value[0] + + return random_value + + def get_valid_gene_constraint_values(self, + range_min, + range_max, + gene_value, + gene_idx, + mutation_by_replacement, + solution, + num_values=100): + """ + Randomly generate values for the gene that satisfy the constraint. + It accepts: + -range_min: The minimum value in the range from which a value is selected. + -range_max: The maximum value in the range from which a value is selected. + -gene_value: The original gene value before applying mutation. + -gene_idx: The index of the gene in the solution. + -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. + -solution: The solution in which the gene exists. + -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. + If num_values=1, it returns a single numeric value. If num_values>1, it returns an array with number of values equal to num_values. + """ + random_values = self.generate_gene_random_value(range_min=range_min, + range_max=range_max, + gene_value=gene_value, + gene_idx=gene_idx, + mutation_by_replacement=mutation_by_replacement, + num_values=num_values) + random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values, + solution=solution, + gene_idx=gene_idx) + return random_values_filtered diff --git a/pygad/pygad.py b/pygad/pygad.py index 2cc5c01..b163960 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -15,6 +15,7 @@ class GA(utils.parent_selection.ParentSelection, utils.mutation.Mutation, utils.nsga2.NSGA2, helper.unique.Unique, + helper.misc.Helper, visualize.plot.Plot): supported_int_types = [int, numpy.int8, numpy.int16, numpy.int32, numpy.int64, @@ -435,7 +436,8 @@ def __init__(self, high=self.init_range_high, allow_duplicate_genes=allow_duplicate_genes, mutation_by_replacement=True, - gene_type=self.gene_type) + gene_type=self.gene_type, + gene_constraint=gene_constraint) else: self.valid_parameters = False raise TypeError(f"The expected type of both the sol_per_pop and num_genes parameters is int but {type(sol_per_pop)} and {type(num_genes)} found.") @@ -1377,12 +1379,18 @@ def initialize_population(self, high, allow_duplicate_genes, mutation_by_replacement, - gene_type): + gene_type, + gene_constraint): """ Creates an initial population randomly as a NumPy array. The array is saved in the instance attribute named 'population'. - low: The lower value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20 and higher. - high: The upper value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20. + It accepts: + -low: The lower value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20 and higher. + -high: The upper value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20. + -allow_duplicate_genes: Whether duplicate genes are allowed or not. + -mutation_by_replacement: Whether mutation by replacement is enabled or not. + -gene_type: The data type of the genes. + -gene_constraint: The constraints of the genes. This method assigns the values of the following 3 instance attributes: 1. pop_size: Size of the population. @@ -1397,10 +1405,11 @@ def initialize_population(self, if self.gene_space is None: # Creating the initial population randomly. if self.gene_type_single == True: + # A NumPy array holding the initial population. self.population = numpy.asarray(numpy.random.uniform(low=low, high=high, size=self.pop_size), - dtype=self.gene_type[0]) # A NumPy array holding the initial population. + dtype=self.gene_type[0]) else: # Create an empty population of dtype=object to support storing mixed data types within the same array. self.population = numpy.zeros( @@ -1408,12 +1417,7 @@ def initialize_population(self, # Loop through the genes, randomly generate the values of a single gene across the entire population, and add the values of each gene to the population. for gene_idx in range(self.num_genes): - if type(self.init_range_low) in self.supported_int_float_types: - range_min = self.init_range_low - range_max = self.init_range_high - else: - range_min = self.init_range_low[gene_idx] - range_max = self.init_range_high[gene_idx] + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) # A vector of all values of this single gene across all solutions in the population. gene_values = numpy.asarray(numpy.random.uniform(low=range_min, @@ -1423,6 +1427,30 @@ def initialize_population(self, # Adding the current gene values to the population. self.population[:, gene_idx] = gene_values + # Enforce the gene constraints as much as possible. + if gene_constraint is None: + pass + else: + # Note that gene_constraint is not validated yet. + # We have to set it as a propery of the pygad.GA instance to retrieve without passing it as an additional parameter. + self.gene_constraint = gene_constraint + for solution in self.population: + for gene_idx in range(self.num_genes): + # Check that a constraint is available for the gene and that the current value does not satisfy that constraint + if self.gene_constraint[gene_idx]: + print(gene_idx, solution[gene_idx]) + if not self.gene_constraint[gene_idx](solution): + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) + # While initializing the population, we follow a mutation by replacement approach. So, the gene value is not needed. + random_values_filtered = self.get_valid_gene_constraint_values(range_min=range_min, + range_max=range_max, + gene_value=None, + gene_idx=gene_idx, + mutation_by_replacement=True, + solution=solution, + num_values=100) + print(gene_idx, random_values_filtered) + if allow_duplicate_genes == False: for solution_idx in range(self.population.shape[0]): # self.logger.info("Before", self.population[solution_idx]) @@ -1444,12 +1472,7 @@ def initialize_population(self, for sol_idx in range(self.sol_per_pop): for gene_idx in range(self.num_genes): - if type(self.init_range_low) in self.supported_int_float_types: - range_min = self.init_range_low - range_max = self.init_range_high - else: - range_min = self.init_range_low[gene_idx] - range_max = self.init_range_high[gene_idx] + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) if self.gene_space[gene_idx] is None: @@ -1524,12 +1547,7 @@ def initialize_population(self, for sol_idx in range(self.sol_per_pop): for gene_idx in range(self.num_genes): - if type(self.init_range_low) in self.supported_int_float_types: - range_min = self.init_range_low - range_max = self.init_range_high - else: - range_min = self.init_range_low[gene_idx] - range_max = self.init_range_high[gene_idx] + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) if type(self.gene_space[gene_idx]) in [numpy.ndarray, list, tuple, range]: # Convert to list because tuple and range do not have copy(). @@ -1584,12 +1602,7 @@ def initialize_population(self, # Replace all the None values with random values using the init_range_low, init_range_high, and gene_type attributes. for gene_idx, curr_gene_space in enumerate(self.gene_space): - if type(self.init_range_low) in self.supported_int_float_types: - range_min = self.init_range_low - range_max = self.init_range_high - else: - range_min = self.init_range_low[gene_idx] - range_max = self.init_range_high[gene_idx] + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) if curr_gene_space is None: self.gene_space[gene_idx] = numpy.asarray(numpy.random.uniform(low=range_min, diff --git a/pygad/utils/__pycache__/mutation.cpython-310.pyc b/pygad/utils/__pycache__/mutation.cpython-310.pyc index 107cdc8275ecb5294e25c929e5d214c9960973c7..16be5abd1b90d84bae82148d5a5b5611dc2bfb79 100644 GIT binary patch delta 4277 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 Tc@_Z&mjjKzeX)i=V?_T4&DpS4 delta 5318 zcmbtYYiu0V6`nhW1ZG1DLz}p1;Ij=wa&Y@DciH^`_ z+VUhh6``#({-ks&O512VJY%$jcEYoPcF}HlHqsv23(qFHf%d_(nfB9-@NA(b9e`&m z9i*G!8K;}+7I?PNAvz4tcDj{X@a&-5=yrH^(jBvsHR>DF1-E>O{8A~YAdU=whfu5u z09W#)Me+mzlpm2;E4~$FK&7a7vZtivSuw*S-BQ38RCUZE$q^z+5wr3;Zo37Rb4+)^ zHnZtmI$Ow^$s9Ga$z`DXvgRYnOu=@{d@7hyaFcF2pEFr9H_yjr$9Zo@-=TRsXPa)` z%;Km$Y97mFE|>+!re-==CMGN-9W!YkDyH8SK&ORV>w4 z-3v>$-wDFed^RD(ffEvNu>{h4&QfyDPULLIwJB=>K0g%QqOmwqPef0XA^vW3 zIl?;NopmBJ8yg`$e<3zbCd+?}{Y^XFjYER~Gpq+mJc?BXVH@G~#9eF?ayJ858`xH) zEre|dI}lJLW+32wg(NJ@FHQB4I6oHeD(yw?K7{=U2N3Q+7(zIRFoAF~+45Q+={H^#~B0^w>_@PO0<{|iVz6Jns zA+ceM5lYU)i1SIamn(gD%wnz^$rZAg1R$9OGvWQL%`mYAdK8%s z5@FuNJsBplYHB%}GHc5Z?Al?b`IP~)ZPdJO;0`fBBJt9rX?FovR1OG^HSP0-WCjnY z?NvJ>2vrffU^K}J)rI1%!0^L1EE96E8F**Mkf|4P>GK8K3J12UL{bTcr*RI3mmjGe z$I!UGlBUbP0z$0`E5E;*F;`WPsP{37-h{c#XqAaf^lQyp1fDW6u61Y;&5#YE$`^;H z3lo(zc=PS%FkDxE=u#p06E5`_Pxa1>w?UL?&TX!KB}AdJy&zEqI0jWaC7`vhYAb|D z6dVHlP6g*MJOi8~RDQQNF1I(CNq5zdqR60T`3HSlqhWRnknDbZ-`3wl_VXkCL$~?u zl`F$!Coz}LE@f;;z!k;%4fT3(*cRMDD3^bszpK}eIr&Th>te#qF8N_$?*wR>-N)bP zKT0n1@Wu_KpAT)EO4Ke1d%%4pwi^*|ASlR6SjX|*Qz>Mh%UV%`84y{>r3ywDgo6s% zX(*Ui40)kh$yF91XYt=}9Bif#Hn2>vEh9Nx*3EB{SsR=Z96o6}>D+wAMlZA(6k;JQ z1P)Z>jGKmguh#stNi5+|>>pjQD@##H<{YTVWyy5T<$oEty`y2(Bg|tP$rGzW(+&o3j z*2+`$9zpedSr<3+LzVslh~zTnm>_?PIyel{oNAd|`?H$rYc4-G)fW}@mZAr3KODK894zk|eV+u%tL-i`6Q{tPBr9^mlNiHE zc#!C!{E)LyNTo71b8uZ~{ZnDck75Wqn3v~m<^qR*Wo(e#Uw&hZ=}mZ1i-=Cnl36#O z7(dl<3-V-N9vy>2@)=A^FGIih%J>lZMfvSBgqe zkEPNsoaZD`h1^^qFXR3DCMS2}sB$sAJ0upJgp_4N;w$!^NETD*d+h^)b!+f zL4O3rxC`OtZSyNIWrI>K-W?(2R(|lLNuK5ppWMiQb9AwExEO|RtkDav5a`$<#i*+- zMi-mBh!>?YRWBUV-&WqErQA5FMrCyU|^_s5ABm#6yi^&l=Xtq?M!g%w?@?-KCJl0 z`~REIKJ&AvRD1x|R4Tk)WdFd`@&3Cc{>)To@Kt#9e|7jZ>h=2|!sFaO5xrOF_+C6{ zP4W1wUjIW_dlgmwYySA%-TdeS|0FZQ`JV^pf39?oaQtY^@jb%}L-VDdBkV;8g2-a5 z5(HXZ5HzeC1dTTcf+ng%5WEO3-+WyVzz3A=N_%bZJ07cZn8LJ*2t9X2~8Y2<8O>QN78f6Fp=-NJ`@4v71 zT(tOir+fK#CK@9%Q1wLd>*8;o+Id=dXawbrA=E;>)@V{3h$3LC!OkNT5S9@{UHB1F zm@^qhEki$IWq#=NKnc}pNbtd<82v`8R5D_QZJ2Ur1, it returns an array with number of values equal to num_values. - """ - - # Generating a random value. - random_value = numpy.random.uniform(low=range_min, - high=range_max, - size=num_values) - - # Change the random mutation value data type. - for idx, val in enumerate(random_value): - random_value[idx] = self.change_random_mutation_value_dtype(random_value[idx], - gene_idx, - gene_value) - - # Round the gene. - random_value[idx] = self.round_random_mutation_value(random_value[idx], gene_idx) - - # Rounding different values could return the same value multiple times. - # For example, 2.8 and 2.7 will be 3.0. - # Use the unique() function to avoid any duplicates. - random_value = numpy.unique(random_value) - - if num_values == 1: - random_value = random_value[0] - - return random_value - def mutation_filter_values_by_constraint(self, random_values, solution, @@ -427,7 +368,7 @@ def mutation_filter_values_by_constraint(self, else: # No value found for the current gene that satisfies the constraint. if not self.suppress_warnings: - warnings.warn(f"No value found for the gene at index {gene_idx} at generation {self.generations_completed+1} that satisfies its gene constraint.") + warnings.warn(f"No value found for the gene at index {gene_idx} that satisfies its gene constraint.") return None filtered_values = random_values[filtered_values_indices] @@ -453,11 +394,12 @@ def mutation_process_random_value(self, # Check if the gene has a constraint. if self.gene_constraint and self.gene_constraint[gene_idx]: # Generate random values to use for mutation. - random_values = self.mutation_generate_random_value(range_min=range_min, - range_max=range_max, - gene_value=solution[gene_idx], - gene_idx=gene_idx, - num_values=100) + random_values = self.generate_gene_random_value(range_min=range_min, + range_max=range_max, + gene_value=solution[gene_idx], + gene_idx=gene_idx, + mutation_by_replacement=self.mutation_by_replacement, + num_values=100) # Filter the values that satisfy the constraint. random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values, solution=solution, @@ -471,11 +413,12 @@ def mutation_process_random_value(self, random_value = numpy.random.choice(random_values_filtered, size=1)[0] # The gene does not have a constraint. else: - random_value = self.mutation_generate_random_value(range_min=range_min, - range_max=range_max, - gene_value=solution[gene_idx], - gene_idx=gene_idx, - num_values=1) + random_value = self.generate_gene_random_value(range_min=range_min, + range_max=range_max, + gene_value=solution[gene_idx], + gene_idx=gene_idx, + mutation_by_replacement=self.mutation_by_replacement, + num_values=1) # Even that its name is singular, it might have a multiple values. return random_value From c5d35dceda521427c2531b8feb041b25bfae1dc3 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Tue, 1 Jul 2025 17:10:50 -0400 Subject: [PATCH 46/79] Apply constraints with duplicates --- example.py | 13 +- pygad/__pycache__/pygad.cpython-310.pyc | Bin 82822 -> 83006 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 4934 -> 6193 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 16272 -> 16314 bytes pygad/helper/misc.py | 72 +- pygad/helper/unique.py | 115 ++-- pygad/pygad.py | 19 +- .../__pycache__/crossover.cpython-310.pyc | Bin 6073 -> 6100 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 23567 -> 23864 bytes pygad/utils/crossover.py | 9 +- pygad/utils/mutation.py | 16 +- test_gene_constraint.py | 164 +++++ tests/test_gene_constraint.py | 635 ++++++++++++++++++ 13 files changed, 965 insertions(+), 78 deletions(-) create mode 100644 test_gene_constraint.py create mode 100644 tests/test_gene_constraint.py diff --git a/example.py b/example.py index 362c5be..405a273 100644 --- a/example.py +++ b/example.py @@ -17,14 +17,17 @@ def fitness_func(ga_instance, solution, solution_idx): num_genes=num_genes, mutation_num_genes=6, fitness_func=fitness_func, - init_range_low=4, - init_range_high=10, + init_range_low=1, + init_range_high=100, # suppress_warnings=True, - random_mutation_min_val=4, - random_mutation_max_val=10, + random_mutation_min_val=1, + random_mutation_max_val=100, mutation_by_replacement=True, gene_type=int, + allow_duplicate_genes=False, # mutation_probability=0.4, - gene_constraint=[lambda x: x[0]>=8,None,None,None,None,None]) + # gene_constraint=[lambda x: x[0]>=8,None,None,None,None,None], + gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], + ) # ga_instance.run() diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc index 2980c4a9d5177a6b87596a24482887043d8938bb..f45cda16f87b541fb82f89ff8cda156ec09927ca 100644 GIT binary patch delta 7556 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 KTf&;zw*LpMDt18t delta 7615 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 z zVFbDg(NR|-ufWyI5T+qaM<~%|&SUvAFyq{bj7xH+8|)sbhEFwR9=k6g9aZx+b0d3W z;dfwe)O3Bo?{%@L1->slio!?USS@gp+PtmXT06b&wvfMdVxPkEaD`Sep9QB?!;;&j z=`LMmX3UnDL3DWLY0u4PIjN*h`I}g6BpY|YJHwSZCXe)ddkDDvVFbG3A3=(4H@d{=;-Cv*00s1Zq9a9lqW-uJ z1%E_Xi*O5oD@X5-*dy>18x1^Nb1q=hp%pJ%z>2ccP>_L;hcE-7NK+TE)ht_kZ2`M+ z5xU_@;`Jz*G?yrFA;i_Lq%^gj4yL&>OPHZ7=x8AWMkHMY`bSj?$_zNy3sf{6Uq~U2Z?l@VkP_n1@K2-4oe0-x^A@uOSyVlh eIESjI3aJ|HzQwFD3zr_VvtFxObTMmX%Krkxbc(kC diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc index 93bfc50a82ee733f7e59e3c39fefa46328137788..be22fbdc0f82cd32d6d079863eccce5f0f7e54ae 100644 GIT binary patch literal 6193 zcmeHLTaVku73NJ8#a*wrP8)meqM5pFS^-|QL2tk!P3RVcV;N+vc3Qb3KXa%XvE>nxy+n1=ljU~(xuSC z=gmKe@zoz$)<3AT{BhB_jbHIAG{V}qge{!Z-naF?v+wACci+|j-oE#NC0yY>wuJY{ z+3yJF&sM*4dMEsNEZOvA#KmAN)2UR=ADiT&N+lcff))8hvXRWBN@8Yav573lxhMwV z6PUN|#ndi;TK8__uC(BmWn6k}!6og;z9%}uN59kS_PSzA1fuuY-uHVUdY8q9*hH@@ zF2T|5+W)=wE_RlTkPYg;hhK3M&CEK)Hau`th%5WdnPHK1HM4gt)9X9DXIoatn$JhF ztW;L861yOH$ys?al`J3X8?^>&q1zWsdDFwVl?90sB*yp0KKrf+a&Hc|6uJ&=Dbz@}q3w@``8b zWs6$O{Qcu;*X?MeN;2`mkL#O3HC{ zz@{oclyO>xfw`ZSz$K=Ieb>>Q%c#xQ<$rrcLDRojjvXL6jCziU>B#U+Ep1Ct; z<_=fW2KRLWwPSso?>#SxCP|h|stMznU=x0VMdz3=;Rrn_INi?4rL2-obs&|TN zI?Yun1x`*G4bz;L5s4Jfkm1K9^SP%w2(I$c)IGguQrBMcW1X+2>CvPeN}6>KwBQX4 zf3byTJM?Ye4!rPbXb1KwTgtX)3O+aS)CRO6ao+#>iG$246P~hZKCROK-4xOr*j7Mf z_18$8moK&NDJlo`7EPrfj%1YPM~Z@0UtYR7PDW$3jl1j7|1L$pF%&KK)acqJfp4QZ z{lQYmpEvsnf@;N74d);7t&5+AroZC?HO88G!aldpf>|J(9jmktokJJ(=jWw&*ul@A z*{D8U;hwu@hVagvv);@Xoyt--vo~gb*`4(s*$+CZpIw7QaM+vCxG%cr?pZI}YBU9h zq1NS#;M^@YjGl6nWZ$>`dgH!x7M^Xuqf3wuci`8|opnU-+@t>6@b%NJ`N(IEx%cpQ zrT6LOS?@DjdlUj>vssVUfqoAVy}=*g__cBWNFQtf7!?A=OMoh>O|HNzlw?gs9HKw3 zM9(UOYd~tEj5BR;bY6tCGN7tfrRT7r$|vk-oB%VqF~$<`7?}c>q*$2J_}r-F$Bo+M zs{IYrYTVMk8NEQiQIg^O>e6VOK9JY~$@p}do`6KO<#)|Pttt_XgBSTjv-ToSD?*kv z>9)8YoJ2}aQy$BS%*s7>hYeFcB0d6mF3JAU7_?3zNOOz=`&km9O_UiwNWn=Ex-2gT zn$al93ji8M!DJ@q^O{6vVv?<0yGC}sk9B)hZIm~iFqM!wH`&Q zi(n!LvceIVEkV6B?iS;`N(DQREC#hmgjDeNE>DXkwAGQ%stF+nz}bWd{R8L#P$MUR zOFDJ*q8fiC+$veA5*XE_t;uc97NSNWtEM!Awhl`9|KO(~I>yka7xdQ*CBh7@351wCok)oM|CFsl|YtIBm|kR|f2TIpkFek_?yMT4vHmV4zx5 z(=m>m#eRAbl!r~C+tekd}F1WjLr_%~!%u{FVrai#bP(|%kC zseD)=((rkqhPeNt_s(03-Rj$#>L|jVz73j=jl(@coqKmd>pj}4y4xII5P28)SbG`| zuN#@G^K-s-s?;?a-&Vx6>yD=Jwe!K9`n6S7X82eK?t!a!d7+n9`*;ij8pE8-YTu+{ z?Z@LhiQ!I_C5Yan_VWXJNT_=f;u;|8oAFq~xDi=!LcO_>egdd(sJzO=1=6Oc)IO9y ztYm-F5W#Q3dv%4DU>;t~q<|79LNQg}(?g5RRXZd=PYd;C*;+* z{Q}eKAGUBmOj3~LhWpZ~TiEVztfA++*JRJN4-WuSE->LRD0CWbN*68D68oGBC(}{-WdcIfctWA#kmF`|CjSe z$KLvdA35hyr2qf` delta 1132 zcmb_c%TLrm81M8k?Y>vQeF#cL&=d$lBr%F|kqeQ7uM-iQ%9N$Jba7gQC9@&%aqz%_ zOya>r0v8j#8REfs_u$QoCcS&|FHkRjQ&?Cy`!)ULd(Ag9zhBc2!(Yy4Yne<^0(bN>-NV&`Ew%xreUsZRIB)mP>p~ zebpAJfm@x6k`>o?ZON|;M_*>aVWxl1AQJSx7cMJvgw2(6a1Fr72<2)Ku zAroai-wSQ(_!ZZ#)>bGM$y`fv-mA=01F|%809pP(Ka0vhRh>t#*eMxKQbfzLkIIMB~pON zNEOV1Lf)Edp-3ZPeMvY)o=+8fX$KHP5rCD5G}vva9_dctQ0mkp404d#BLAM9yPTv6 z(U}sE2G|!nK38Py2~SpOIW8UEi#KxRRI6Y+x$e|3GzC2X=??xYH#sF#ZJJO`#Ib7q z;D8cRtd{~xfJ&emZWxe7%wU)a{S?qE0VFHVYjyW&KCj+B!qfla_CgD{_>c*D4o*pX z1&G_zK5iAd4G~*1wfL<<|G;tbjxQOE*9jQl?+aZ=2>Vv(E6+eFC;{LO88sjL7h`^+ zcqxZs5iLXoU6#&gihb)jF|1vHCE%ifLm6T(D{?Y`pga(fAsMpKCYO?b4?K7XX8-^I diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc index ad216d8e6c08a8dfe5d8f31b4c0fd979a06d7ce4..fde934ea73a4094bf2a032d6a4a9e47f81e65091 100644 GIT binary patch delta 4436 zcmbtXYiwLc6~1%d_wLNv}`w?n`Afk-Ob$V zJj&f}D+eK!Mv63lfVSXBAT?E0gjPa4iUf#CJSq@>FBSZY6cvG>0+gf_&YA1j8^@F% z*xIvaX3m^BbIy0poV{BAVLeqzCT$7+#t+al$`8JqQb=93G}=pbYFs9hs%6qXYEc__ z%_1~Q6Vw4-r%9T+EKM3#ES;t;Fl$T zV&Ecws^rmJF*hr270d9MhjRR>+(CYOz~&!n|LiA`jav#J>R!CAN2Xtyo()PBkD&v& zZR~o`0!EA&x_~8+I7pH}BHNptDZ~byHEyI*r`@0+G}7Qwl^4w~XT|;%wz4#rtuK;| z{CVq3ZDXLz%(TfpM3o(45{K}NeO$YV`x;f-RCjC!F0ZjQ@Fh-W>wt@o4_K)LqWLJJ8cp+V&< zX3Y#uN@gWC0AYvK!;&R=DcBoBXK{y+R{%h3O$!fDLK8Ot{*;;(>Qn>8)*J!e0#NIX z;g%VtelTxF7GUMHTbL<%i)``YLi9!aA~Qw;y%>jAcnXmpsea&A^9I`i12%$WCz4%A z1QJk+?FMpPi4sCfjL6vAMje+4E~`;mEKgNDKVZd@7eoNr00C5@F>aQYVp#c67Pla% zV}Tg+@nY2OyVLGeP>4YuPv&j56l#QVeI?`yd!uBtHa|+m>RcLYS_S~t56WJGkB|3n z$>@qqbRsYQ>$gnH1OV%5^SplBkxjh=#q^IHjX&S9t&T83{|cy7fe}#2EkXsk`=A0q zArN>3iVQ&MZvsjZjq?IdrZ6%agIzU4!zREYZ^fr!l^8dS{d|kKsxgNj%aTr;I z)4{y{KaliEa0uH2Pl5Oo4E?!0Xag|!d-(XB$t|cB$No83(f_ z)L5NbK$8@$uoq6TmUh?KyMa>fEO!CzzGsV2N)uNU>d@pB8828fygv!)o@Pl{Pk~gt z{$7y53s48H{{cveSuiQ<|1Id9@1yBZ1~j(3rd%UL^4V6_1E2kF>< zS4o{cj>8q5i1X-1{9fir@2Pv$uos&=xqRP({`>qnf`0ET_lN_U4nn7Lb7(l-fED!3 zTd{BW_|RY?itzoQB360nXvkP%mj~{A1`P zc^}g;As{WKBMf^5hS?cEJ<4?DLZqH4ojwC)Gfr+?!>1u`vhYD&=(PAx^X&s-j9Hr} z@FbU>bFVAxF_d{6$-%~r`~X?o34CODw8&WTlBhP37R*+sU533Z>qXMX*KZimv&cTc z4{bQUAKx{Gbuvd58>nW=~bv%u2OO6t$?EKRMRDCem1C z)}#5BriXZW3F50auA5wsM=afV={Dl7`|xb*Zb!Opd13zyEc;mTbRcGXmUPIr+M<}m zgcg#&zHujh-Pm}9j}1>YULF1s5iQ$6Xd*@ZMNhHqc!U-^3kx!YLR4IgN7OEQ6~9`X za-qS1ud^^%3sa6sH51dDyNjG(n3+u$N&Q8MR|$UI;E! z3KRlUngUUOg#UH(ZoXwpmYitp-!e@)-vGo(^Tsu)rUq@HI!EBw#{2x;t%Ib&|Gc&L z=(htI`@BGbiuQh>A`ibX%u&hv>cWaCvU>vbbV0#LLOr!Z8C*X@6KyNAF*2r`{ zTCG64ctqqg69IE&%k|&Y$AQ7*x2>f9jLQ2#nHi_)#kvt6~ zGN((Ph3&DW8rZx7b}r{I@%LHT)GC@xScIYM40@PmOG}#ih|<5g#8JOe_M64fEX`F${>PCn*-`kNk+H^}otIQSR>R*O z&8-rw0I5AXUGjse%1m|aCaC=YK2neL+&2DVx{E(MX7av0o9Y)~vMDPs!4PUwq5{ni zYwn;?BdYzmKbJ zak4fU;71Bb2_y3|3sF-191$jc0fxWesWG>8GjJ0Cs~AxeY%~AL*!ub=6n_ZGVufUn z;t(4dhV_RD+byc3?(8^Syzyx#$@gPy0MKwqD_nphVhgbavKF097 delta 4359 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 diff --git a/pygad/helper/misc.py b/pygad/helper/misc.py index 1052c3f..1437e27 100644 --- a/pygad/helper/misc.py +++ b/pygad/helper/misc.py @@ -9,12 +9,27 @@ class Helper: + def get_gene_dtype(self, gene_index): + + """ + Returns the data type of the gene by its index. + It accepts a single parameter: + -gene_index: The index of the gene to get its data type. Only used if each gene has its own data type. + It returns the data type of the gene. + """ + + if self.gene_type_single == True: + dtype = self.gene_type + else: + dtype = self.gene_type[gene_index] + return dtype + def get_random_mutation_range(self, gene_index): """ Returns the minimum and maximum values of the mutation range. It accepts a single parameter: - -gene_index: The index of the gene to get its range. Only used if the gene has a specific mutation range + -gene_index: The index of the gene to get its range. Only used if the gene has a specific mutation range. It returns the minimum and maximum values of the gene mutation range. """ @@ -51,7 +66,8 @@ def generate_gene_random_value(self, gene_value, gene_idx, mutation_by_replacement, - num_values=1): + num_values=1, + step=1): """ Randomly generate one or more values for the gene. It accepts: @@ -60,15 +76,39 @@ def generate_gene_random_value(self, -gene_value: The original gene value before applying mutation. -gene_idx: The index of the gene in the solution. -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. - -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. - If num_values=1, it returns a single numeric value. If num_values>1, it returns an array with number of values equal to num_values. + -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For flaot data types, a None value returns only a single value. + -step (int, optional): The step size for generating candidate values. Defaults to 1. Only used with genes of an integer data type. + + It returns, + -A single numeric value if num_values=1. Or + -An array with number of values equal to num_values if num_values>1. """ - # Generating a random value. - random_value = numpy.asarray(numpy.random.uniform(low=range_min, - high=range_max, - size=num_values), - dtype=object) + gene_type = self.get_gene_dtype(gene_index=gene_idx) + if gene_type[0] in pygad.GA.supported_int_types: + random_value = numpy.asarray(numpy.arange(range_min, + range_max, + step=step), + dtype=gene_type[0]) + if num_values is None: + # Keep all the values. + pass + else: + if num_values >= len(random_value): + # Number of values is larger than or equal to the number of elements in random_value. + # Makes no sense to create a larger sample out of the population because it just creates redundant values. + pass + else: + # Set replace=True to avoid selecting the same value more than once. + random_value = numpy.random.choice(random_value, + size=num_values, + replace=False) + else: + # Generating a random value. + random_value = numpy.asarray(numpy.random.uniform(low=range_min, + high=range_max, + size=num_values), + dtype=object) # Change the random mutation value data type. for idx, val in enumerate(random_value): @@ -97,7 +137,8 @@ def get_valid_gene_constraint_values(self, gene_idx, mutation_by_replacement, solution, - num_values=100): + num_values=100, + step=1): """ Randomly generate values for the gene that satisfy the constraint. It accepts: @@ -108,14 +149,21 @@ def get_valid_gene_constraint_values(self, -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. -solution: The solution in which the gene exists. -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. - If num_values=1, it returns a single numeric value. If num_values>1, it returns an array with number of values equal to num_values. + -step (int, optional): The step size for generating candidate values. Defaults to 1. Only used with genes of an integer data type. + + It returns, + -A single numeric value if num_values=1. Or + -An array with number of values equal to num_values if num_values>1. Or + -None if no value found that satisfies the constraint. """ random_values = self.generate_gene_random_value(range_min=range_min, range_max=range_max, gene_value=gene_value, gene_idx=gene_idx, mutation_by_replacement=mutation_by_replacement, - num_values=num_values) + num_values=num_values, + step=step) + # It returns None if no value found that satisfies the constraint. random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values, solution=solution, gene_idx=gene_idx) diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 94ea8d5..f074d06 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -15,7 +15,7 @@ def solve_duplicate_genes_randomly(self, max_val, mutation_by_replacement, gene_type, - num_trials=10): + num_values=100): """ Resolves duplicates in a solution by randomly selecting new values for the duplicate genes. @@ -25,7 +25,7 @@ def solve_duplicate_genes_randomly(self, max_val (int): The maximum value of the range to sample a number randomly. mutation_by_replacement (bool): Indicates if mutation is performed by replacement. gene_type (type): The data type of the gene (e.g., int, float). - num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. Only works for floating-point gene types. + num_values (int): The maximum number of random values to generate to find a unique value. Returns: tuple: @@ -42,10 +42,7 @@ def solve_duplicate_genes_randomly(self, num_unsolved_duplicates = 0 if len(not_unique_indices) > 0: for duplicate_index in not_unique_indices: - if self.gene_type_single == True: - dtype = gene_type - else: - dtype = gene_type[duplicate_index] + dtype = self.get_gene_dtype(gene_index=duplicate_index) if dtype[0] in pygad.GA.supported_int_types: temp_val = self.unique_int_gene_from_range(solution=new_solution, @@ -61,7 +58,7 @@ def solve_duplicate_genes_randomly(self, max_val=max_val, mutation_by_replacement=mutation_by_replacement, gene_type=gene_type, - num_trials=num_trials) + num_values=num_values) if temp_val in new_solution: num_unsolved_duplicates = num_unsolved_duplicates + 1 @@ -163,9 +160,34 @@ def unique_int_gene_from_range(self, int: The new integer value of the gene. If no unique value can be found, the original gene value is returned. """ - # The gene_type is of the form [type, precision] - dtype = gene_type + if self.gene_constraint and self.gene_constraint[gene_index]: + # A unique value is created out of the values that satisfy the constraint. + # num_values=None to return all the values. + random_values = self.get_valid_gene_constraint_values(range_min=min_val, + range_max=max_val, + gene_value=solution[gene_index], + gene_idx=gene_index, + mutation_by_replacement=mutation_by_replacement, + solution=solution, + num_values=None, + step=step) + # If there is no value satisfying the constraint, then return the current gene value. + if random_values is None: + return solution[gene_index] + else: + pass + else: + # There is no constraint for the current gene. Return the same range. + # num_values=None to return all the values. + random_values = self.generate_gene_random_value(range_min=min_val, + range_max=max_val, + gene_value=solution[gene_index], + gene_idx=gene_index, + mutation_by_replacement=mutation_by_replacement, + num_values=None, + step=step) + """ # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. all_gene_values = numpy.asarray(numpy.arange(min_val, @@ -183,12 +205,14 @@ def unique_int_gene_from_range(self, # After adding solution[gene_index] to the list, we have to change the data type again. all_gene_values = numpy.asarray(all_gene_values, dtype[0]) + """ - selected_value = self.select_unique_value(gene_values=all_gene_values, + selected_value = self.select_unique_value(gene_values=random_values, solution=solution, gene_index=gene_index) - selected_value = dtype[0](selected_value) + # The gene_type is of the form [type, precision] + selected_value = gene_type[0](selected_value) return selected_value @@ -199,7 +223,7 @@ def unique_float_gene_from_range(self, max_val, mutation_by_replacement, gene_type, - num_trials=10): + num_values=100): """ Finds a unique floating-point value for a specific gene in a solution. @@ -211,20 +235,44 @@ def unique_float_gene_from_range(self, max_val (int): The maximum value of the range to sample a floating-point number randomly. mutation_by_replacement (bool): Indicates if mutation is performed by replacement. gene_type (type): The data type of the gene (e.g., float, float16, float32, etc). - num_trials (int): The maximum number of attempts to resolve duplicates by changing the gene values. + num_values (int): The maximum number of random values to generate to find a unique value. Returns: int: The new floating-point value of the gene. If no unique value can be found, the original gene value is returned. """ + if self.gene_constraint and self.gene_constraint[gene_index]: + # A unique value is created out of the values that satisfy the constraint. + random_values = self.get_valid_gene_constraint_values(range_min=min_val, + range_max=max_val, + gene_value=solution[gene_index], + gene_idx=gene_index, + mutation_by_replacement=mutation_by_replacement, + solution=solution, + num_values=num_values) + # If there is no value satisfying the constraint, then return the current gene value. + if random_values is None: + return solution[gene_index] + else: + pass + else: + # There is no constraint for the current gene. Return the same range. + random_values = self.generate_gene_random_value(range_min=min_val, + range_max=max_val, + gene_value=solution[gene_index], + gene_idx=gene_index, + mutation_by_replacement=mutation_by_replacement, + num_values=num_values) + + """ # The gene_type is of the form [type, precision] dtype = gene_type # We cannot have a list of all values out of a continous range. - # Solution is to create a subset (e.g. 100) of all the values. + # Solution is to create a subset (e.g. 100) of some random values out of the range. some_gene_values = numpy.random.uniform(low=min_val, high=max_val, - size=100) + size=num_values) # If mutation is by replacement, do not add the current gene value into the list. # This is to avoid replacing the value by itself again. We are doing nothing in this case. @@ -244,8 +292,9 @@ def unique_float_gene_from_range(self, # Just convert the data type. some_gene_values = numpy.asarray(some_gene_values, dtype[0]) + """ - selected_value = self.select_unique_value(gene_values=some_gene_values, + selected_value = self.select_unique_value(gene_values=random_values, solution=solution, gene_index=gene_index) return selected_value @@ -267,7 +316,7 @@ def select_unique_value(self, gene_values, solution, gene_index): if len(values_to_select_from) == 0: # If there are no values, then keep the current gene value. - if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but cannot find a value to prevent duplicates.") + if not self.suppress_warnings: warnings.warn(f"'allow_duplicate_genes=False' but cannot find a unique value for the gene at index {gene_index}.") selected_value = solution[gene_index] else: selected_value = random.choice(values_to_select_from) @@ -352,10 +401,7 @@ def unique_gene_by_space(self, value_from_space = curr_gene_space # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. elif curr_gene_space is None: - if self.gene_type_single == True: - dtype = gene_type - else: - dtype = gene_type[gene_idx] + dtype = self.get_gene_dtype(gene_index=gene_idx) if dtype[0] in pygad.GA.supported_int_types: if build_initial_pop == True: @@ -393,14 +439,11 @@ def unique_gene_by_space(self, max_val=high, mutation_by_replacement=True, gene_type=dtype, - num_trials=num_trials) + num_values=num_trials) elif type(curr_gene_space) is dict: - if self.gene_type_single == True: - dtype = gene_type - else: - dtype = gene_type[gene_idx] + dtype = self.get_gene_dtype(gene_index=gene_idx) # Use index 0 to return the type from the list (e.g. [int, None] or [float, 2]). if dtype[0] in pygad.GA.supported_int_types: @@ -459,10 +502,7 @@ def unique_gene_by_space(self, else: # Selecting a value randomly from the global gene space in the 'gene_space' attribute. if type(self.gene_space) is dict: - if self.gene_type_single == True: - dtype = gene_type - else: - dtype = gene_type[gene_idx] + dtype = self.get_gene_dtype(gene_index=gene_idx) if dtype[0] in pygad.GA.supported_int_types: if 'step' in self.gene_space.keys(): @@ -514,10 +554,7 @@ def unique_gene_by_space(self, # Similar to the round_genes() method in the pygad module, # Create a round_gene() method to round a single gene. - if self.gene_type_single == True: - dtype = gene_type - else: - dtype = gene_type[gene_idx] + dtype = self.get_gene_dtype(gene_index=gene_idx) if not dtype[1] is None: value_from_space = numpy.round(dtype[0](value_from_space), @@ -625,10 +662,7 @@ def unpack_gene_space(self, elif type(space) is dict: # Create a list of values using the dict range. # Use numpy.linspace() - if self.gene_type_single == True: - dtype = self.gene_type - else: - dtype = self.gene_type[space_idx] + dtype = self.get_gene_dtype(gene_index=gene_idx) if dtype[0] in pygad.GA.supported_int_types: if 'step' in space.keys(): @@ -663,10 +697,7 @@ def unpack_gene_space(self, size=1)[0] gene_space_unpacked[space_idx][idx] = random_value - if self.gene_type_single == True: - dtype = self.gene_type - else: - dtype = self.gene_type[space_idx] + dtype = self.get_gene_dtype(gene_index=space_idx) # Change the data type. gene_space_unpacked[space_idx] = numpy.array(gene_space_unpacked[space_idx], diff --git a/pygad/pygad.py b/pygad/pygad.py index b163960..6765169 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -491,7 +491,7 @@ def __init__(self, max_val=self.init_range_high, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) else: self.initial_population[initial_solution_idx], _, _ = self.solve_duplicate_genes_by_space(solution=initial_solution, gene_type=self.gene_type, @@ -1427,6 +1427,9 @@ def initialize_population(self, # Adding the current gene values to the population. self.population[:, gene_idx] = gene_values + # Round the randomly generated values. + self.population = self.round_genes(self.population) + # Enforce the gene constraints as much as possible. if gene_constraint is None: pass @@ -1434,14 +1437,13 @@ def initialize_population(self, # Note that gene_constraint is not validated yet. # We have to set it as a propery of the pygad.GA instance to retrieve without passing it as an additional parameter. self.gene_constraint = gene_constraint - for solution in self.population: + for sol_idx, solution in enumerate(self.population): for gene_idx in range(self.num_genes): # Check that a constraint is available for the gene and that the current value does not satisfy that constraint if self.gene_constraint[gene_idx]: - print(gene_idx, solution[gene_idx]) if not self.gene_constraint[gene_idx](solution): range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) - # While initializing the population, we follow a mutation by replacement approach. So, the gene value is not needed. + # While initializing the population, we follow a mutation by replacement approach. So, the original gene value is not needed. random_values_filtered = self.get_valid_gene_constraint_values(range_min=range_min, range_max=range_max, gene_value=None, @@ -1449,7 +1451,11 @@ def initialize_population(self, mutation_by_replacement=True, solution=solution, num_values=100) - print(gene_idx, random_values_filtered) + if random_values_filtered is None: + if not self.suppress_warnings: + warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} while creating the initial population.") + else: + self.population[sol_idx, gene_idx] = random.choice(random_values_filtered) if allow_duplicate_genes == False: for solution_idx in range(self.population.shape[0]): @@ -1459,7 +1465,7 @@ def initialize_population(self, max_val=high, mutation_by_replacement=True, gene_type=gene_type, - num_trials=10) + num_values=100) # self.logger.info("After", self.population[solution_idx]) elif self.gene_space_nested: @@ -2010,7 +2016,6 @@ def run(self): if self.save_best_solutions: self.best_solutions.append(list(best_solution)) - # Note: Any code that has loop-dependant statements (e.g. continue, break, etc) must be kept inside the loop of the 'run()' method. It can be moved to another method to clean the run() method. # If the on_generation attribute is not None, then cal the callback function after the generation. if not (self.on_generation is None): diff --git a/pygad/utils/__pycache__/crossover.cpython-310.pyc b/pygad/utils/__pycache__/crossover.cpython-310.pyc index d2b64d32f7e0d65392c4128076b1f0e3053c69b6..339bfd606ceb0b92b7110057f508de92953b19f0 100644 GIT binary patch delta 598 zcmdm~e?^}+pO=@50SIhuQ!@HC@=j!C=1Jk5ypUOxk#F;M=HHBrDU&y|dMlSD=9H!u zzXT~=$y_7|q>4m=#4WD8(%krxqRhmc;?1ILoJ@=?o8>vOnIwUR^fJdV*D%+x*)Y^{ z*K!y7)G!A#X!31d%(a-&u}A`_SyQq|3S^Kph>!shAR~)pL4*R3xW!tPT9jX0BnJ|Z z2MMqwr)K8dV$RGfncTx;tfmc;&;b!5AVL>NXz~>4f!O*W!T>}VPQJ~fGg**Vgv|`Z zHJ_}=tHBt!*^4(G*_EtNSE@l>d5C{4qZ!zhE5&fT67I4hMUdG_AVOjCDnT6!;+zIl ze~ULgH7~U|zBs?MC^@xwvH-8B0LY=YI6&g@nJE=uCks!u5fWmYvDsh93fh#45J!jtEVO923Jetl>F delta 611 zcmcbjzf+$#pO=@50SInHM5Om@AaRSeDzzxTxJU*hAPW*;Nlwkoxy78B zS5hPo5>x;ZlPB{S%ZPw@+CV~+r$`6H)&&uIAVPog8y+1g0}#^?L>PexV-R5iB1|V6 z@oF#zZI0%RXOc!Zm=)?^HHd=~D}er&*!-4%GNZ{#G2G6EI~CzRuvb8i494e3pi^$~ zrl;nm7RMLomlh?b78l`i@+}UKy76o7Zwi;zM6N=WY|JLc$Gv=ggUN z&YU?jv+wVaoi9j*R$gA}f-g1BQahn56%R_?Fp9#zzIo-*_U z(Y)ETT z-nHV+OL=$R!-_WCYu>zY;w>6AeY zo>rf-Qe{tQAt5zaF;1ab6(_Ub(2#qzMZfs>UGM@)l53B7?tYY zEvmZMJy|cdI|+J%|S#L;y55&!ab-*5l&be zY9|p3h&gc&vP_9b!3gnN4V^|2nHQHsJ!C=L2|Ys=#c!ck0?xIGp*vpl0*X=d0k*8g z?(;=uOQ25hd9=PJe(vq^EutDh#6?xO{pcjB&cadDoMH}4Tn;YZT1pto`OA9P|2-j0 zYz?#zs9?7}aW$Rc2}ez2(yP!GXOoqfH;G6g(&F!d=ZIt-8QjJ2&f)$u7|t^YjONb* zY_Fa&6N!w_d6c1zX1ST+Mzti~`CVsW@lQo_wzGA-jh+rJ9 z<=4?n5gmv=#3u;n7TrRv2XR|`GaY>DL?2H#H*ez9r-(6xg}8&bD~4to556^9;G%;I7L>HnLvG*ZB+s^J&J^u_H3x%V0*k`g0&L_moyeSdq z>4~rABe7}BFoSS@5S-=lp>ljw{B?we;0Wgi+(Zp;3&*43*c`qs#uqwTkAf<@7%Q6o7Yj*XwO;Cvg(z$8PMzc49mHq$xiMv~HbNO&kgiQPK)IDaOs%%?8`4 zX6?dZ143=3ULsoN00*ih5NHc72li50fdrMfRF!(@$(4!_r(O^sQKa_5dmE)HD3Q97 zzxU?Ndoyp|%=4A2qHnO;HR&hyR$z}8))MxAzeq^ zvL6)z+1YAW4#4JY#JtuVv@1!}$IRcnRf(keS%14P?bH_vm9n-_ z%9M-8Yuip_3zb|(qwlgLzwSR@KWl@KjZfuz;CYP_DlRFDHba<~44WYtGL@F3Ma594 zToG6lEgiz5-LOvzWqIB0})X~Yzk0KtY4uORj!b^>a3rAjel<*&yiHi`Dm4UZIaqv}NvK+Mik{8XsN za~Lf_L>Tb`&Z+!|ke5vIyP@qQ$sdMx5w|%QE{nv+tGy8t;J169Bhy@syy3SBI)ahX zBVfgt18~WFFY=D)oB?%&`OtcVlfJNH8r5BhxA=~}z|K)rt#c$$v(C2V!Y**RX@1ZL z>HKBhfBH5dS$^Z$z%xg|UXy2YT7k`4YQCTyhjPidWCH^0$8v}~Z|i@F$mT@ z;HiTc&NKu@vljt1yFORR=LzzeX&0o_7a;z6c8oE3gQdIDgtkW xVO7}LM@QPiOav9f^c?_IP(@Wz{lfdIQ>ALR+N4I+etuylrJTl)3I1><{x9J{R*wJx diff --git a/pygad/utils/crossover.py b/pygad/utils/crossover.py index d7cd86e..a09edc3 100644 --- a/pygad/utils/crossover.py +++ b/pygad/utils/crossover.py @@ -70,12 +70,11 @@ def single_point_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, num_trials=10) - return offspring @@ -143,7 +142,7 @@ def two_points_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, @@ -210,7 +209,7 @@ def uniform_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, @@ -274,7 +273,7 @@ def scattered_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index 676d0fb..8535902 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -379,7 +379,8 @@ def mutation_process_random_value(self, range_min, range_max, solution, - gene_idx): + gene_idx, + num_values=100): """ Randomly generate constrained values to use for applying mutation. @@ -388,6 +389,7 @@ def mutation_process_random_value(self, -range_max: The maximum value in the range from which a value is selected. -solution: The solution where the target gene exists. -gene_idx: The index of the gene in the solution. + -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. It returns a single numeric value the satisfies the gene constraint if exists in the gene_constraint parameter. """ @@ -399,7 +401,7 @@ def mutation_process_random_value(self, gene_value=solution[gene_idx], gene_idx=gene_idx, mutation_by_replacement=self.mutation_by_replacement, - num_values=100) + num_values=num_values) # Filter the values that satisfy the constraint. random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values, solution=solution, @@ -440,7 +442,7 @@ def mutation_randomly(self, offspring): range_min, range_max = self.get_random_mutation_range(gene_idx) - # Generate a random value fpr mutation that meet the gene constraint if exists. + # Generate a random value for mutation that meet the gene constraint if exists. random_value = self.mutation_process_random_value(range_min=range_min, range_max=range_max, solution=offspring[offspring_idx], @@ -454,7 +456,7 @@ def mutation_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) return offspring @@ -492,7 +494,7 @@ def mutation_probs_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) return offspring def swap_mutation(self, offspring): @@ -934,7 +936,7 @@ def adaptive_mutation_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) return offspring def adaptive_mutation_probs_by_space(self, offspring): @@ -1138,5 +1140,5 @@ def adaptive_mutation_probs_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_trials=10) + num_values=100) return offspring diff --git a/test_gene_constraint.py b/test_gene_constraint.py new file mode 100644 index 0000000..28e290a --- /dev/null +++ b/test_gene_constraint.py @@ -0,0 +1,164 @@ +import pygad +import random +import numpy + +num_generations = 1 + +initial_population = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + +def population_gene_constraint(gene_space=None, + gene_type=float, + num_genes=10, + mutation_by_replacement=False, + random_mutation_min_val=-1, + random_mutation_max_val=1, + init_range_low=-4, + init_range_high=4, + random_seed=123, + crossover_type='single_point', + initial_population=None, + parent_selection_type='sss', + multi_objective=False, + gene_constraint=None, + allow_duplicate_genes=True): + + def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + + def fitness_func_no_batch_multi(ga, solution, idx): + return [random.random(), random.random()] + + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + num_genes=num_genes, + gene_space=gene_space, + gene_type=gene_type, + initial_population=initial_population, + parent_selection_type=parent_selection_type, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + allow_duplicate_genes=allow_duplicate_genes, + mutation_by_replacement=mutation_by_replacement, + random_seed=random_seed, + crossover_type=crossover_type, + gene_constraint=gene_constraint, + save_solutions=True, + suppress_warnings=True) + + ga_instance.run() + + return ga_instance + +#### Single-Objective +def test_initial_population_int_by_replacement(): + gene_constraint=[lambda x: x[0]>=8,lambda x: x[1]>=8,lambda x: 5>=x[2]>=1,lambda x: 5>x[3]>3,lambda x: x[4]<2] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=0, + init_range_high=10, + num_genes=5, + gene_type=int, + mutation_by_replacement=True) + initial_population = ga_instance.initial_population + # print(initial_population) + + assert numpy.all(initial_population[:, 0] >= 8), "Not all values in column 0 are >= 98" + + assert numpy.all(initial_population[:, 1] >= 8), "Not all values in column 1 are >= 98" + + assert numpy.all(initial_population[:, 2] >= 1), "Not all values in column 2 are >= 1" + assert numpy.all((initial_population[:, 2] >= 1) & (initial_population[:, 2] <= 5)), "Not all values in column 2 between 1 and 5 (inclusive)" + + assert numpy.all(initial_population[:, 3] == 4), "Not all values in column 3 between 3 and 5 (exclusive)" + + assert numpy.all(initial_population[:, 4] < 2), "Not all values in column 4 < 2" + +def test_initial_population_int_by_replacement_no_duplicates(): + gene_constraint=[lambda x: x[0]>=5,lambda x: x[1]>=5,lambda x: x[2]>=5,lambda x: x[3]>=5,lambda x: x[4]>=5] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=1, + init_range_high=10, + random_mutation_min_val=1, + random_mutation_max_val=10, + gene_type=int, + num_genes=5, + mutation_by_replacement=True, + allow_duplicate_genes=False) + + num_duplicates = 0 + for idx, solution in enumerate(ga_instance.solutions): + num = len(solution) - len(set(solution)) + if num != 0: + print(solution, idx) + num_duplicates += num + + assert num_duplicates == 0 + +def test_initial_population_float_by_replacement_no_duplicates(): + gene_constraint=[lambda x: x[0]>=5,lambda x: x[1]>=5,lambda x: x[2]>=5,lambda x: x[3]>=5,lambda x: x[4]>=5] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=1, + init_range_high=10, + gene_type=[float, 1], + num_genes=5, + crossover_type=None, + mutation_by_replacement=False, + allow_duplicate_genes=False) + + num_duplicates = 0 + for idx, solution in enumerate(ga_instance.solutions): + num = len(solution) - len(set(solution)) + if num != 0: + print(solution, idx) + num_duplicates += num + + assert num_duplicates == 0 + +def test_initial_population_float_by_replacement_no_duplicates2(): + gene_constraint=[lambda x: x[0]>=1,lambda x: x[1]>=1,lambda x: x[2]>=1,lambda x: x[3]>=1,lambda x: x[4]>=1] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=1, + init_range_high=2, + gene_type=[float, 1], + num_genes=5, + crossover_type=None, + mutation_by_replacement=False, + allow_duplicate_genes=False) + + num_duplicates = 0 + for idx, solution in enumerate(ga_instance.solutions): + num = len(solution) - len(set(solution)) + if num != 0: + print(solution, idx) + num_duplicates += num + + assert num_duplicates == 0 + +if __name__ == "__main__": + #### Single-objective + print() + test_initial_population_int_by_replacement() + print() + test_initial_population_int_by_replacement_no_duplicates() + print() + test_initial_population_float_by_replacement_no_duplicates() + print() + test_initial_population_float_by_replacement_no_duplicates2() + print() diff --git a/tests/test_gene_constraint.py b/tests/test_gene_constraint.py new file mode 100644 index 0000000..34a18b9 --- /dev/null +++ b/tests/test_gene_constraint.py @@ -0,0 +1,635 @@ +import pygad +import random +import numpy + +num_generations = 1 + +initial_population = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + +def population_gene_constraint(gene_space=None, + gene_type=float, + num_genes=10, + mutation_by_replacement=False, + random_mutation_min_val=-1, + random_mutation_max_val=1, + init_range_low=-4, + init_range_high=4, + random_seed=123, + initial_population=None, + parent_selection_type='sss', + multi_objective=False, + gene_constraint=None): + + def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + + def fitness_func_no_batch_multi(ga, solution, idx): + return [random.random(), random.random()] + + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + num_genes=num_genes, + gene_space=gene_space, + gene_type=gene_type, + initial_population=initial_population, + parent_selection_type=parent_selection_type, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + allow_duplicate_genes=False, + mutation_by_replacement=mutation_by_replacement, + random_seed=random_seed, + gene_constraint=gene_constraint, + save_solutions=True, + suppress_warnings=True) + + ga_instance.run() + + return ga_instance + +#### Single-Objective +def test_number_duplicates_default(): + gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,None,None,None,None,None,None,None,None] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=98, + init_range_high=98) + initial_population = ga_instance.initial_population + assert initial_population[:, 0] >= 98 + assert initial_population[:, 1] >= 98 + +def test_number_duplicates_default_initial_population(): + num_duplicates = population_gene_constraint(initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type_initial_population(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + initial_population=initial_population, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_initial_population(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement_initial_population(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space(): + num_duplicates = population_gene_constraint(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space_initial_population(): + num_duplicates = population_gene_constraint(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=range(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space_initial_population(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=range(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space_initial_population(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space(): + num_duplicates = population_gene_constraint(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_initial_population(): + num_duplicates = population_gene_constraint(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + + +# def test_number_duplicates_nested_gene_space_nested_gene_type(): + """ + This example causes duplicate genes that can only be solved by changing the values of a chain of genes. + Let's explain it using this solution: [0, 2, 3, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 6 at indices 5 and 6. + According to the gene space, none of these genes can has a different value that solves the duplicates. + -If the value of the gene at index 5 is changed from 6 to 5, then it causes another duplicate with the gene at index 4. + -If the value of the gene at index 6 is changed from 6 to 7, then it causes another duplicate with the gene at index 7. + The solution is to change a chain of genes that make a room to solve the duplicates between the 2 genes. + 1) Change the second gene from 2 to 1. + 2) Change the third gene from 3 to 2. + 3) Change the fourth gene from 4 to 3. + 4) Change the fifth gene from 5 to 4. + 5) Change the sixth gene from 6 to 5. This solves the duplicates. + But this is NOT SUPPORTED yet. + We support changing only a single gene that makes a room to solve the duplicates. + + Let's explain it using this solution: [1, 2, 2, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 2 at indices 1 and 2. + This is how the duplicates are solved: + 1) Change the first gene from 1 to 0. + 2) Change the second gene from 2 to 1. This solves the duplicates. + The result is [0, 1, 2, 4, 5, 6, 6, 7, 8, 9] + """ + # num_duplicates = population_gene_constraint(gene_space=[[0, 1], + # [1, 2], + # [2, 3], + # [3, 4], + # [4, 5], + # [5, 6], + # [6, 7], + # [7, 8], + # [8, 9], + # [9, 10]], + # gene_type=[int, int, int, int, int, int, int, int, int, int], + # num_genes=10) + + # assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_nested_gene_type_initial_population(): + num_duplicates = population_gene_constraint(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=[int, int, int, int, int, int, int, int, int, int], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +#### Multi-Objective +def test_number_duplicates_default_multi_objective(): + num_duplicates = population_gene_constraint() + + assert num_duplicates == 0 + +def test_number_duplicates_default_initial_population_multi_objective(): + num_duplicates = population_gene_constraint(initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type_multi_objective(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type_initial_population_multi_objective(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + initial_population=initial_population, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_initial_population_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement_initial_population_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = population_gene_constraint(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space_multi_objective(): + num_duplicates = population_gene_constraint(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space_initial_population_multi_objective(): + num_duplicates = population_gene_constraint(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space_multi_objective(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=range(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space_initial_population_multi_objective(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=range(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space_multi_objective(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space_initial_population_multi_objective(): + num_genes = 10 + num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_multi_objective(): + num_duplicates = population_gene_constraint(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_initial_population_multi_objective(): + num_duplicates = population_gene_constraint(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + + +# def test_number_duplicates_nested_gene_space_nested_gene_type_multi_objective(): + """ + This example causes duplicate genes that can only be solved by changing the values of a chain of genes. + Let's explain it using this solution: [0, 2, 3, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 6 at indices 5 and 6. + According to the gene space, none of these genes can has a different value that solves the duplicates. + -If the value of the gene at index 5 is changed from 6 to 5, then it causes another duplicate with the gene at index 4. + -If the value of the gene at index 6 is changed from 6 to 7, then it causes another duplicate with the gene at index 7. + The solution is to change a chain of genes that make a room to solve the duplicates between the 2 genes. + 1) Change the second gene from 2 to 1. + 2) Change the third gene from 3 to 2. + 3) Change the fourth gene from 4 to 3. + 4) Change the fifth gene from 5 to 4. + 5) Change the sixth gene from 6 to 5. This solves the duplicates. + But this is NOT SUPPORTED yet. + We support changing only a single gene that makes a room to solve the duplicates. + + Let's explain it using this solution: [1, 2, 2, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 2 at indices 1 and 2. + This is how the duplicates are solved: + 1) Change the first gene from 1 to 0. + 2) Change the second gene from 2 to 1. This solves the duplicates. + The result is [0, 1, 2, 4, 5, 6, 6, 7, 8, 9] + """ + # num_duplicates = population_gene_constraint(gene_space=[[0, 1], + # [1, 2], + # [2, 3], + # [3, 4], + # [4, 5], + # [5, 6], + # [6, 7], + # [7, 8], + # [8, 9], + # [9, 10]], + # gene_type=[int, int, int, int, int, int, int, int, int, int], + # num_genes=10) + + # assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_nested_gene_type_initial_population_multi_objective(): + num_duplicates = population_gene_constraint(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=[int, int, int, int, int, int, int, int, int, int], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +if __name__ == "__main__": + #### Single-objective + print() + test_number_duplicates_default() + print() + + """ + test_number_duplicates_default_initial_population() + print() + + test_number_duplicates_float_gene_type() + print() + test_number_duplicates_float_gene_type_initial_population() + print() + + test_number_duplicates_int_gene_type() + print() + test_number_duplicates_int_gene_type_initial_population() + print() + + test_number_duplicates_int_gene_type_replacement() + print() + test_number_duplicates_int_gene_type_replacement_initial_population() + print() + + test_number_duplicates_single_gene_space() + print() + test_number_duplicates_single_gene_space_initial_population() + print() + + test_number_duplicates_single_range_gene_space() + print() + test_number_duplicates_single_range_gene_space_initial_population() + print() + + test_number_duplicates_single_numpy_range_gene_space() + print() + test_number_duplicates_single_numpy_range_gene_space_initial_population() + print() + + test_number_duplicates_nested_gene_space() + print() + test_number_duplicates_nested_gene_space_initial_population() + print() + + # This example causes duplicates that can only be solved by changing a chain of genes. + # test_number_duplicates_nested_gene_space_nested_gene_type() + # print() + test_number_duplicates_nested_gene_space_nested_gene_type_initial_population() + print() + + #### Multi-objective + print() + test_number_duplicates_default_initial_population_multi_objective() + print() + + test_number_duplicates_float_gene_type_multi_objective() + print() + test_number_duplicates_float_gene_type_initial_population_multi_objective() + print() + + test_number_duplicates_int_gene_type_multi_objective() + print() + test_number_duplicates_int_gene_type_initial_population_multi_objective() + print() + + test_number_duplicates_int_gene_type_replacement_multi_objective() + print() + test_number_duplicates_int_gene_type_replacement_initial_population_multi_objective() + print() + + test_number_duplicates_single_gene_space_multi_objective() + print() + test_number_duplicates_single_gene_space_initial_population_multi_objective() + print() + + test_number_duplicates_single_range_gene_space_multi_objective() + print() + test_number_duplicates_single_range_gene_space_initial_population_multi_objective() + print() + + test_number_duplicates_single_numpy_range_gene_space_multi_objective() + print() + test_number_duplicates_single_numpy_range_gene_space_initial_population_multi_objective() + print() + + test_number_duplicates_nested_gene_space_multi_objective() + print() + test_number_duplicates_nested_gene_space_initial_population_multi_objective() + print() + + # This example causes duplicates that can only be solved by changing a chain of genes. + # test_number_duplicates_nested_gene_space_nested_gene_type_multi_objective() + # print() + test_number_duplicates_nested_gene_space_nested_gene_type_initial_population_multi_objective() + print() + """ + + From 6a3b58b58348093561f806d35e931fa3e161238a Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Wed, 2 Jul 2025 22:48:44 -0400 Subject: [PATCH 47/79] Apply constraints on gene space --- .DS_Store | Bin 0 -> 8196 bytes example.py | 12 +- pygad/.DS_Store | Bin 0 -> 6148 bytes pygad/__init__.py | 2 +- pygad/__pycache__/__init__.cpython-310.pyc | Bin 227 -> 205 bytes pygad/__pycache__/pygad.cpython-310.pyc | Bin 83006 -> 82985 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 282 -> 260 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 6193 -> 13628 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 16314 -> 16294 bytes pygad/helper/misc.py | 337 +++- pygad/helper/unique.py | 110 +- pygad/pygad.py | 6 +- pygad/utils/.DS_Store | Bin 0 -> 6148 bytes pygad/utils/__init__.py | 2 +- .../__pycache__/__init__.cpython-310.pyc | Bin 354 -> 332 bytes .../__pycache__/crossover.cpython-310.pyc | Bin 6100 -> 6079 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 23864 -> 17721 bytes pygad/utils/__pycache__/nsga2.cpython-310.pyc | Bin 7299 -> 7277 bytes .../parent_selection.cpython-310.pyc | Bin 14577 -> 14555 bytes pygad/utils/crossover.py | 8 +- pygad/utils/mutation.py | 516 +----- .../__pycache__/__init__.cpython-310.pyc | Bin 259 -> 237 bytes .../__pycache__/plot.cpython-310.pyc | Bin 13852 -> 13830 bytes setup.py | 2 +- test_gene_constraint.py | 75 +- test_gene_space.py | 1605 +++++++++++++++++ tests/test_gene_constraint.py | 658 ++----- tests/test_gene_space.py | 2 +- 28 files changed, 2214 insertions(+), 1121 deletions(-) create mode 100644 .DS_Store create mode 100644 pygad/.DS_Store create mode 100644 pygad/utils/.DS_Store create mode 100644 test_gene_space.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..ccee05d899388080d4c4fe26cc7300ddc72623e8 GIT binary patch literal 8196 zcmeHMPj4GV6n~Q@vE9&S>$*uOsOu6zJK0C)HbTsR;E*WNkt0g&J~GwXD{Nz_v*YGj&8NW3Vv1-44 zlW2j6@~DzpGiXjJa$eRGWg;0Vf->k62@2MI;kJCyIw5Ta3<3rLgMdN6AYc&qKOlfR zTbBG8_r9JrtwF#b@LD1u&j%Y-(n>;ynyRA%jY}$4+P^p+ilTDs1ZP zfhDReNimpK$Gk1YK`RLzYO2+VX?0?WXO?6rOnHZ%t;C5{G&QY3z#woE0hzl`(+V9> zOpi19y9}j|QE&gFic+4WHl$pNQhC0V`z<4Po;dB3LtW~T4}KB!;G_;~rflakIvF2q zC*vbWcWE7KU!zZ`Mr-5N+0%^llw1LIu%`C7$8y?3g6!pkr#U+*m)2E_4Sx{*MnUF=X*eW zqbH}LZ5~FTO)I0**m1(p2|B!b@X&W!+}{YQB92A3;khEv4>7CEqPrP#X|nwOUmDeN z<)aa^s2PjOn!&H>(uhkas|Oq$E4-|0Iy^USFK@CL5*Yq8IPe0I)^fUcVf70J< znw8i!c7rXmJM2Stmwn2%S%>+o%l5Q^iP5XRHtvZH&`-VUy;>OfBIsaLYFt$NzegeB z_X|Xti-=!D6!UsD>%&>PL{Eg zXzJKn<_uD2McQvl@>Mx2MnFcH4P;RruS0H_5KuZe)@|BMZ>RBBDmT#>sN=iU^je@?Do?tis0U-9N&YL+Ms literal 0 HcmV?d00001 diff --git a/example.py b/example.py index 405a273..752575c 100644 --- a/example.py +++ b/example.py @@ -23,11 +23,15 @@ def fitness_func(ga_instance, solution, solution_idx): random_mutation_min_val=1, random_mutation_max_val=100, mutation_by_replacement=True, - gene_type=int, + gene_type=[float, 1], + save_solutions=True, allow_duplicate_genes=False, - # mutation_probability=0.4, - # gene_constraint=[lambda x: x[0]>=8,None,None,None,None,None], + # gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)), + gene_space=[range(10), {"low": 1, "high": 5}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))], gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], ) -# ga_instance.run() +ga_instance.run() + +print(ga_instance.gene_space_unpacked) +print(ga_instance.population) diff --git a/pygad/.DS_Store b/pygad/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..98be63892770443c647aa222a56a6882b24d65b7 GIT binary patch literal 6148 zcmeHK&x_MQ6n@hmo5mvapy)B+b-rD* zS9lV<`lsmk-b^8BtVfY$UYYsk$M+^P-^*k=M5Ow&(E(A1h+0UDja$h6VBF8CVl~^s z&Pv8eXi7u;9@4nNb`4Ger@;TF0Po$l?Ae4eD(>IK^HiTjsgA&4)(fQLUt%b2yvDSk zGYXL|DCTjtyuRbpFf4f0!OSSDvPxq&>0HLFFxA_wSu})M<^9~^?aBO7e3xRHl%HOU z|Hh5x`Pi!PH5#W$KJ9jYs9L?z-19u&YkMCC7kWOJhSOQz4=3-q_DbtC+LXiSbrR2q zojcETHVu<3o`^#-ia~k*Cdo#6-q*8il#B1cbohSDA9n67mj_4P{owHFYCl*WJ?7$* z!_~@f-F@)r_~r02{gmlzOdqE32^PC;aRXmaC@ueLFv(J#oui$7Ji262Ab8GVmc@~< ze=7vAwj+E+P75+GBrqd63btM)31X4&;n>ia0l!3*87K~Ak=>*j_z9(ym**;h+`+sV zVJs|tD~LW_L6RKOgb*g7&lH!%;Xsylq6t2GoB~A!_;3i27<(4i2Dypb z;@TiuVD68A$lx-kz&};sH;9NK_5c6? literal 0 HcmV?d00001 diff --git a/pygad/__init__.py b/pygad/__init__.py index 17959b7..53ccec2 100644 --- a/pygad/__init__.py +++ b/pygad/__init__.py @@ -1,3 +1,3 @@ from .pygad import * # Relative import. -__version__ = "3.3.1" +__version__ = "3.5.0" diff --git a/pygad/__pycache__/__init__.cpython-310.pyc b/pygad/__pycache__/__init__.cpython-310.pyc index 38c074e3eb591001514bf5fd8106cdef041639fa..a939de9e2e698a7f89fc506e58abc4558a6692ed 100644 GIT binary patch delta 86 zcmaFNc$SejpO=@50SLA`r)Jns0hl9}w7lb&CcS(1?(P+5|ZpQm3?2~sripb`LN#vJSb delta 108 zcmX@h_?VG5pO=@50SInHM5H@SOD)MvPRvQqF9IsaEvPKX$j{S-DAr9(F3HT#gQ=akS_uGMvnEae diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc index f45cda16f87b541fb82f89ff8cda156ec09927ca..7b6c592df0cde0e632c0bc20361189d07465f9c7 100644 GIT binary patch delta 4726 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 Nnq$$Pj-#&|zXqir^|Al} delta 1447 zcmZ`(&yU+w5RRS1akBa0F70NMY`bpCR&^m^m;P8()RvVHg;EM?FBGk0dF>a+ZDNP9 z!)_2#gn&Pwyes04;J_-!f_g$i6{I3Pe`;ShYEf$Ier|PIq@nh*7rC7pU%~^2DxGOl7+ZjU>%ByTJ zVlaE5d|To_<&IB{tjL9I&=Vz>`ONfuhwX{H6HP|!iwvkJ^5-4r(za_kdPjG?s6XD( zM-bd)b`)-pCWvoSV&vJ@zzqNi!_g$_2Yx3ZcPu;d0{?GrKbGGfdZFDNO+>ZkvdD~; zW6ARD`^qCp6*Z4uV~%5?D)teb4QKhG=qjtWxCK;z^}L;8DK0 zcqadggzMS2ElHB@%^t4o%O!t2G~crZV;1rsmsm5$8W zmp8ilo1pXIGZ%YvRQG!Ne0OD|Thb36Ljr~BpaKjDM;5UWVg=mZe8rFv@ZM*l0Bkg{ zY=%S4;w?okzEv~M;aBsGIxN##19FeZ$Kw<7wJrW(``A?EQ5t7butE#Knh3>;`-Mj_ zUnsbCf}v3Ce&E?CHTJz;00;y-WCl@Wn9jh8m}uMmxY0?9IUGk8c_3+&84p^tAQ<}& zO$1LR$~46h3ZDM%m>Fe=Cw~OXg+;14acSIfp(c)X_TWVPYTh|$P(qH-@_|BIcOz+sD^OLu>c0<{I!DNsMq6qWR~ zLZODiU`&&V>4psFktUu#oLA2qL<|o6ZSG7=8$aX-EYFXOh16tTr_nsAyGhugI9#No zm;Rw|Cg(FJJq-T&xwVB)kQ|b2l_r0DZmV?@Ya{0-_Lbqn;acTa&aX|$&hQk4It980 zT%s`lStK7cVKN3v2#;b=GpelC)J1t&mP&ufZ^%;h_n2WQqF|a%V4Ehiktoz%3w+Oa XLpY6FSfxNmp$7g;Qc6YMywLs&K_jS2 diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc index fde934ea73a4094bf2a032d6a4a9e47f81e65091..71e7049cad614f07d97c66d4d7636bed3a4f7a2a 100644 GIT binary patch delta 700 zcmZuuOK1~O6z!cfpC)Zc>BO|@C;d>Qd|dID}^pZ5f|c4;>P!)NI(bXygPFa=bn4ttLBrYUmqIs z_R(jvh2-MlOaEhz`|Q9gz6ANmc{raX)eIGhmU0{6?+zXxj21{|Fb%ln+8^s>zW_Dq zES;AW6_xRu6Cr#&;UrhxUm>$0Y+1;(1WB%;az!c&>&U9wx*_C|hHRyjQ!Bcu*mCXS zo-ON|VC*rGj8p@e0(m&_)axIqP^l@|NMr@R%x^#exA;TQ@P*(t{5%+f77ho-$#L-h zKcUIN=p~50ckN1Q16k%84Qj^!!Xm386h6?`N?suLE- z(d+{UM6iDH5loYj=zKrSko4(Oz;^?SmLZjp*wE-vrCTY+Yc%$|_!Mm6Z}B4-AnipT z7coU@S?Fhz6$TlKidI%iG-{d=*qt~B<77RN;z%N#= z%of5uw&kI6G-nZa2GH*q@?in3|U7=^-ccNpE4Y1==`uQF0)B=)UN^EUgu n#h`l)*J=HB|bc{m_#rR75d#*=DE7+GKZ~-Po3@ z0S_MZpz@vs@uEl(56YbMs0T&-1q&j05P~O9DqfsPwSMg3*_rowX5Qz0m`Al2wKh8z zi#CaG?zmA3Jb%{qkiaL-$vBBv)rxjWFIOq*t$n!BaT4RhNxT({;)j%qqp{C;KXkJt zx-E10SXdzo=Zr)7qHd(rRMGTG)uoif>y}Mbjz?SIHAmgkJpr^;9xefNFq&mJCTHY zd^7R*zZv*O$1w=A88I!J#dn?EFoN~YbdZEYyqD`uMkjy+KIv-obr$y!@x zc11I-Wtl^fGDBY2;n9k-mCnB&e)Y@d#)NG%Hv=d5liX4>)N$wJnfRIvB(n|cxH{QW zxFYr>Ac(7Ckq$K)Fhz<=5=JFh0{mt%6J1M3SidYqBuFzXEy?!A)V5cmZ0jw#j7id! zBz~PtDVOE8CP9^OUceh1oO;#LXtUa=p);Mt_;i2!0_i3Y{Xq^$Y^v>;$GPdHp9}fl AIRF3v diff --git a/pygad/helper/misc.py b/pygad/helper/misc.py index 1437e27..42a68bf 100644 --- a/pygad/helper/misc.py +++ b/pygad/helper/misc.py @@ -9,6 +9,97 @@ class Helper: + def change_gene_value_dtype(self, + random_value, + gene_index, + gene_value, + mutation_by_replacement): + """ + Change the data type of the random value used to apply mutation. + It accepts 2 parameters: + -random_value: The random value to change its data type. + -gene_index: The index of the target gene. + -gene_value: The gene value before mutation. Only used if mutation_by_replacement=False and gene_type_single=False. + -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. + It returns the new value after changing the data type. + """ + + # If the mutation_by_replacement attribute is True, then the random value replaces the current gene value. + if mutation_by_replacement: + if self.gene_type_single == True: + random_value = self.gene_type[0](random_value) + else: + random_value = self.gene_type[gene_index][0](random_value) + if type(random_value) is numpy.ndarray: + random_value = random_value[0] + # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. + else: + if self.gene_type_single == True: + random_value = self.gene_type[0](gene_value + random_value) + else: + random_value = self.gene_type[gene_index][0](gene_value + random_value) + if type(random_value) is numpy.ndarray: + random_value = random_value[0] + return random_value + + def round_gene_value(self, random_value, gene_index): + """ + Round the random value used to apply mutation. + It accepts 2 parameters: + -random_value: The random value to round its value. + -gene_index: The index of the target gene. Only used if nested gene_type is used. + It returns the new value after being rounded. + """ + + # Round the gene + if self.gene_type_single == True: + if not self.gene_type[1] is None: + random_value = numpy.round(random_value, self.gene_type[1]) + else: + if not self.gene_type[gene_index][1] is None: + random_value = numpy.round(random_value, self.gene_type[gene_index][1]) + return random_value + + def filter_gene_values_by_constraint(self, + values, + solution, + gene_idx): + + """ + Filter the random values generated for mutation based on whether they meet the gene constraint in the gene_constraint parameter. + It accepts: + -values: The values to filter. + -solution: The solution containing the target gene. + -gene_idx: The index of the gene in the solution. + It returns None if no values satisfy the constraint. Otherwise, an array of values that satisfy the constraint is returned. + """ + + # A list of the indices where the random values satisfy the constraint. + filtered_values_indices = [] + # A temporary solution to avoid changing the original solution. + solution_tmp = solution.copy() + # Loop through the random values to filter the ones satisfying the constraint. + for value_idx, value in enumerate(values): + solution_tmp[gene_idx] = value + # Check if the constraint is satisfied. + if self.gene_constraint[gene_idx](solution_tmp): + # The current value satisfies the constraint. + filtered_values_indices.append(value_idx) + + # After going through all the values, check if any value satisfies the constraint. + if len(filtered_values_indices) > 0: + # At least one value was found that meets the gene constraint. + pass + else: + # No value found for the current gene that satisfies the constraint. + if not self.suppress_warnings: + warnings.warn(f"No value found for the gene at index {gene_idx} that satisfies its gene constraint.") + return None + + filtered_values = values[filtered_values_indices] + + return filtered_values + def get_gene_dtype(self, gene_index): """ @@ -60,14 +151,146 @@ def get_initial_population_range(self, gene_index): range_max = self.init_range_high[gene_index] return range_min, range_max - def generate_gene_random_value(self, - range_min, - range_max, - gene_value, - gene_idx, - mutation_by_replacement, - num_values=1, - step=1): + def generate_gene_value_from_space(self, + gene_value, + gene_idx, + mutation_by_replacement, + sample_size=1): + """ + Generate/select one or more values for the gene from the gene space. + It accepts: + -gene_value: The original gene value before applying mutation. + -gene_idx: The index of the gene in the solution. + -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. + -sample_size: The number of random values to generate. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For float data types, a None value returns only a single value. + -step (int, optional): The step size for generating candidate values. Defaults to 1. Only used with genes of an integer data type. + + It returns, + -A single numeric value if sample_size=1. Or + -An array with number of maximum number of values equal to sample_size if sample_size>1. + """ + + range_min, range_max = self.get_random_mutation_range(gene_idx) + + if self.gene_space_nested: + # Returning the current gene space from the 'gene_space' attribute. + # It is used to determine the way of selecting the next gene value: + # 1) List/NumPy Array: Whether it has only one value, multiple values, or one of its values is None. + # 2) Fixed Numeric Value + # 3) None + # 4) Dict: Whether the dict has the key `step` or not. + if type(self.gene_space[gene_idx]) in [numpy.ndarray, list]: + # Get the gene space from the `gene_space_unpacked` property because it undergoes data type change and rounded. + curr_gene_space = self.gene_space_unpacked[gene_idx].copy() + elif type(self.gene_space[gene_idx]) in pygad.GA.supported_int_float_types: + # Get the gene space from the `gene_space_unpacked` property because it undergoes data type change and rounded. + curr_gene_space = self.gene_space_unpacked[gene_idx] + else: + curr_gene_space = self.gene_space[gene_idx] + + if type(curr_gene_space) in pygad.GA.supported_int_float_types: + # If the gene space is simply a single numeric value (e.g. 5), use it as the new gene value. + value_from_space = curr_gene_space + elif curr_gene_space is None: + # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. + rand_val = numpy.random.uniform(low=range_min, + high=range_max, + size=sample_size) + if mutation_by_replacement: + value_from_space = rand_val + else: + value_from_space = gene_value + rand_val + elif type(curr_gene_space) is dict: + # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. + # The gene's space of type dict specifies the lower and upper limits of a gene. + if 'step' in curr_gene_space.keys(): + # When the `size` parameter is used, the numpy.random.choice() and numpy.random.uniform() functions return a NumPy array as the output even if the array has a single value (i.e. size=1). + # We have to return the output at index 0 to force a numeric value to be returned not an object of type numpy.ndarray. + # If numpy.ndarray is returned, then it will cause an issue later while using the set() function. + # Randomly select a value from a discrete range. + value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], + stop=curr_gene_space['high'], + step=curr_gene_space['step']), + size=sample_size) + else: + value_from_space = numpy.random.uniform(low=curr_gene_space['low'], + high=curr_gene_space['high'], + size=sample_size) + else: + # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. + # If the gene space has only 1 value, then select it. The old and new values of the gene are identical. + if len(curr_gene_space) == 1: + value_from_space = curr_gene_space + else: + # If the gene space has more than 1 value, then select a new one that is different from the current value. + # To avoid selecting the current gene value again, remove it from the current gene space and do the selection. + value_from_space = list(set(curr_gene_space) - set([gene_value])) + + """ + if len(values_to_select_from) == 0: + # After removing the current gene value from the space, there are no more values. + # Then keep the current gene value. + value_from_space = gene_value + else: + value_from_space = random.choice(values_to_select_from) + """ + else: + # Selecting a value randomly from the global gene space in the 'gene_space' attribute. + # The gene's space of type dict specifies the lower and upper limits of a gene. + if type(self.gene_space) is dict: + # When the gene_space is assigned a dict object, then it specifies the lower and upper limits of all genes in the space. + if 'step' in self.gene_space.keys(): + value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], + stop=self.gene_space['high'], + step=self.gene_space['step']), + size=sample_size) + else: + value_from_space = numpy.random.uniform(low=self.gene_space['low'], + high=self.gene_space['high'], + size=sample_size) + else: + # If the space type is not of type dict, then a value is randomly selected from the gene_space attribute. + # To avoid selecting the current gene value again, remove it from the current gene space and do the selection. + value_from_space = list(set(self.gene_space) - set([gene_value])) + + """ + if len(values_to_select_from) == 0: + # After removing the current gene value from the space, there are no more values. + # Then keep the current gene value. + value_from_space = gene_value + else: + value_from_space = random.choice(values_to_select_from) + """ + + if len(value_from_space) == 0: + # After removing the current gene value from the space, there are no more values. + # Then keep the current gene value. + value_from_space = gene_value + elif sample_size == 1: + value_from_space = random.choice(value_from_space) + + # The gene space might be [None, 1, 7]. + # It might happen that the value None is selected. + # In this case, generate a random value out of the mutation range. + if value_from_space is None: + # TODO: Return index 0. + # TODO: Check if this if statement is necessary. + value_from_space = numpy.random.uniform(low=range_min, + high=range_max, + size=sample_size) + else: + value_from_space = numpy.array(value_from_space) + + return value_from_space + + def generate_gene_value_randomly(self, + range_min, + range_max, + gene_value, + gene_idx, + mutation_by_replacement, + sample_size=1, + step=1): """ Randomly generate one or more values for the gene. It accepts: @@ -76,12 +299,12 @@ def generate_gene_random_value(self, -gene_value: The original gene value before applying mutation. -gene_idx: The index of the gene in the solution. -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. - -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For flaot data types, a None value returns only a single value. + -sample_size: The number of random values to generate. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For float data types, a None value returns only a single value. -step (int, optional): The step size for generating candidate values. Defaults to 1. Only used with genes of an integer data type. It returns, - -A single numeric value if num_values=1. Or - -An array with number of values equal to num_values if num_values>1. + -A single numeric value if sample_size=1. Or + -An array with number of values equal to sample_size if sample_size>1. """ gene_type = self.get_gene_dtype(gene_index=gene_idx) @@ -90,46 +313,84 @@ def generate_gene_random_value(self, range_max, step=step), dtype=gene_type[0]) - if num_values is None: + if sample_size is None: # Keep all the values. pass else: - if num_values >= len(random_value): + if sample_size >= len(random_value): # Number of values is larger than or equal to the number of elements in random_value. # Makes no sense to create a larger sample out of the population because it just creates redundant values. pass else: # Set replace=True to avoid selecting the same value more than once. random_value = numpy.random.choice(random_value, - size=num_values, + size=sample_size, replace=False) else: # Generating a random value. random_value = numpy.asarray(numpy.random.uniform(low=range_min, high=range_max, - size=num_values), + size=sample_size), dtype=object) # Change the random mutation value data type. for idx, val in enumerate(random_value): - random_value[idx] = self.change_random_mutation_value_dtype(random_value[idx], - gene_idx, - gene_value, - mutation_by_replacement=mutation_by_replacement) + random_value[idx] = self.change_gene_value_dtype(random_value[idx], + gene_idx, + gene_value, + mutation_by_replacement=mutation_by_replacement) # Round the gene. - random_value[idx] = self.round_random_mutation_value(random_value[idx], gene_idx) + random_value[idx] = self.round_gene_value(random_value[idx], gene_idx) # Rounding different values could return the same value multiple times. # For example, 2.8 and 2.7 will be 3.0. # Use the unique() function to avoid any duplicates. random_value = numpy.unique(random_value) - if num_values == 1: + if sample_size == 1: random_value = random_value[0] return random_value + def generate_gene_value(self, + gene_value, + gene_idx, + mutation_by_replacement, + range_min=None, + range_max=None, + sample_size=1, + step=1): + """ + Generate one or more values for the gene either randomly or from the gene space. It acts as a router. + It accepts: + -gene_value: The original gene value before applying mutation. + -gene_idx: The index of the gene in the solution. + -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. + -range_min (int, optional): The minimum value in the range from which a value is selected. It must be passed for generating the gene value randomly because we cannot decide whether it is the range for the initial population (init_range_low and init_range_high) or mutation (random_mutation_min_val and random_mutation_max_val). + -range_max (int, optional): The maximum value in the range from which a value is selected. It must be passed for generating the gene value randomly because we cannot decide whether it is the range for the initial population (init_range_low and init_range_high) or mutation (random_mutation_min_val and random_mutation_max_val). + -sample_size: The number of random values to generate/select and return. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For float data types, a None value returns only a single value. + -step (int, optional): The step size for generating candidate values. Defaults to 1. Only used with genes of an integer data type. + + It returns, + -A single numeric value if sample_size=1. Or + -An array with number of values equal to sample_size if sample_size>1. + """ + if self.gene_space is None: + output = self.generate_gene_value_randomly(range_min=range_min, + range_max=range_max, + gene_value=gene_value, + gene_idx=gene_idx, + mutation_by_replacement=mutation_by_replacement, + sample_size=sample_size, + step=step) + else: + output = self.generate_gene_value_from_space(gene_value=gene_value, + gene_idx=gene_idx, + mutation_by_replacement=mutation_by_replacement, + sample_size=sample_size) + return output + def get_valid_gene_constraint_values(self, range_min, range_max, @@ -137,10 +398,11 @@ def get_valid_gene_constraint_values(self, gene_idx, mutation_by_replacement, solution, - num_values=100, + sample_size=100, step=1): """ - Randomly generate values for the gene that satisfy the constraint. + Generate/select values for the gene that satisfy the constraint. The values could be generated randomly or from the gene space. + The number of returned values is at its maximum equal to the sample_size parameter. It accepts: -range_min: The minimum value in the range from which a value is selected. -range_max: The maximum value in the range from which a value is selected. @@ -148,23 +410,24 @@ def get_valid_gene_constraint_values(self, -gene_idx: The index of the gene in the solution. -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. -solution: The solution in which the gene exists. - -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. + -sample_size: The number of values to generate or select. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. -step (int, optional): The step size for generating candidate values. Defaults to 1. Only used with genes of an integer data type. It returns, - -A single numeric value if num_values=1. Or - -An array with number of values equal to num_values if num_values>1. Or + -A single numeric value if sample_size=1. Or + -An array with number of values equal to sample_size if sample_size>1. Or -None if no value found that satisfies the constraint. """ - random_values = self.generate_gene_random_value(range_min=range_min, - range_max=range_max, - gene_value=gene_value, - gene_idx=gene_idx, - mutation_by_replacement=mutation_by_replacement, - num_values=num_values, - step=step) + # Either generate the values randomly or from the gene space. + values = self.generate_gene_value(range_min=range_min, + range_max=range_max, + gene_value=gene_value, + gene_idx=gene_idx, + mutation_by_replacement=mutation_by_replacement, + sample_size=sample_size, + step=step) # It returns None if no value found that satisfies the constraint. - random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values, - solution=solution, - gene_idx=gene_idx) - return random_values_filtered + values_filtered = self.filter_gene_values_by_constraint(values=values, + solution=solution, + gene_idx=gene_idx) + return values_filtered diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index f074d06..4b3edb1 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -15,7 +15,7 @@ def solve_duplicate_genes_randomly(self, max_val, mutation_by_replacement, gene_type, - num_values=100): + sample_size=100): """ Resolves duplicates in a solution by randomly selecting new values for the duplicate genes. @@ -25,7 +25,7 @@ def solve_duplicate_genes_randomly(self, max_val (int): The maximum value of the range to sample a number randomly. mutation_by_replacement (bool): Indicates if mutation is performed by replacement. gene_type (type): The data type of the gene (e.g., int, float). - num_values (int): The maximum number of random values to generate to find a unique value. + sample_size (int): The maximum number of random values to generate to find a unique value. Returns: tuple: @@ -58,7 +58,7 @@ def solve_duplicate_genes_randomly(self, max_val=max_val, mutation_by_replacement=mutation_by_replacement, gene_type=gene_type, - num_values=num_values) + sample_size=sample_size) if temp_val in new_solution: num_unsolved_duplicates = num_unsolved_duplicates + 1 @@ -162,14 +162,14 @@ def unique_int_gene_from_range(self, if self.gene_constraint and self.gene_constraint[gene_index]: # A unique value is created out of the values that satisfy the constraint. - # num_values=None to return all the values. + # sample_size=None to return all the values. random_values = self.get_valid_gene_constraint_values(range_min=min_val, range_max=max_val, gene_value=solution[gene_index], gene_idx=gene_index, mutation_by_replacement=mutation_by_replacement, solution=solution, - num_values=None, + sample_size=None, step=step) # If there is no value satisfying the constraint, then return the current gene value. if random_values is None: @@ -178,14 +178,14 @@ def unique_int_gene_from_range(self, pass else: # There is no constraint for the current gene. Return the same range. - # num_values=None to return all the values. - random_values = self.generate_gene_random_value(range_min=min_val, - range_max=max_val, - gene_value=solution[gene_index], - gene_idx=gene_index, - mutation_by_replacement=mutation_by_replacement, - num_values=None, - step=step) + # sample_size=None to return all the values. + random_values = self.generate_gene_value(range_min=min_val, + range_max=max_val, + gene_value=solution[gene_index], + gene_idx=gene_index, + mutation_by_replacement=mutation_by_replacement, + sample_size=None, + step=step) """ # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) @@ -223,7 +223,7 @@ def unique_float_gene_from_range(self, max_val, mutation_by_replacement, gene_type, - num_values=100): + sample_size=100): """ Finds a unique floating-point value for a specific gene in a solution. @@ -235,7 +235,7 @@ def unique_float_gene_from_range(self, max_val (int): The maximum value of the range to sample a floating-point number randomly. mutation_by_replacement (bool): Indicates if mutation is performed by replacement. gene_type (type): The data type of the gene (e.g., float, float16, float32, etc). - num_values (int): The maximum number of random values to generate to find a unique value. + sample_size (int): The maximum number of random values to generate to find a unique value. Returns: int: The new floating-point value of the gene. If no unique value can be found, the original gene value is returned. @@ -243,58 +243,28 @@ def unique_float_gene_from_range(self, if self.gene_constraint and self.gene_constraint[gene_index]: # A unique value is created out of the values that satisfy the constraint. - random_values = self.get_valid_gene_constraint_values(range_min=min_val, - range_max=max_val, - gene_value=solution[gene_index], - gene_idx=gene_index, - mutation_by_replacement=mutation_by_replacement, - solution=solution, - num_values=num_values) + values = self.get_valid_gene_constraint_values(range_min=min_val, + range_max=max_val, + gene_value=solution[gene_index], + gene_idx=gene_index, + mutation_by_replacement=mutation_by_replacement, + solution=solution, + sample_size=sample_size) # If there is no value satisfying the constraint, then return the current gene value. - if random_values is None: + if values is None: return solution[gene_index] else: pass else: # There is no constraint for the current gene. Return the same range. - random_values = self.generate_gene_random_value(range_min=min_val, - range_max=max_val, - gene_value=solution[gene_index], - gene_idx=gene_index, - mutation_by_replacement=mutation_by_replacement, - num_values=num_values) - - """ - # The gene_type is of the form [type, precision] - dtype = gene_type - - # We cannot have a list of all values out of a continous range. - # Solution is to create a subset (e.g. 100) of some random values out of the range. - some_gene_values = numpy.random.uniform(low=min_val, - high=max_val, - size=num_values) - - # If mutation is by replacement, do not add the current gene value into the list. - # This is to avoid replacing the value by itself again. We are doing nothing in this case. - if mutation_by_replacement: - pass - else: - some_gene_values = some_gene_values + solution[gene_index] - - if not dtype[1] is None: - # Precision is available and we have to round the number. - # Convert the data type and round the number. - some_gene_values = numpy.round(numpy.asarray(some_gene_values, - dtype[0]), - dtype[1]) - else: - # There is no precision and rounding the number is not needed. The type is [type, None] - # Just convert the data type. - some_gene_values = numpy.asarray(some_gene_values, - dtype[0]) - """ - - selected_value = self.select_unique_value(gene_values=random_values, + values = self.generate_gene_value(range_min=min_val, + range_max=max_val, + gene_value=solution[gene_index], + gene_idx=gene_index, + mutation_by_replacement=mutation_by_replacement, + sample_size=sample_size) + + selected_value = self.select_unique_value(gene_values=values, solution=solution, gene_index=gene_index) return selected_value @@ -439,7 +409,7 @@ def unique_gene_by_space(self, max_val=high, mutation_by_replacement=True, gene_type=dtype, - num_values=num_trials) + sample_size=num_trials) elif type(curr_gene_space) is dict: @@ -532,8 +502,10 @@ def unique_gene_by_space(self, # If the space type is not of type dict, then a value is randomly selected from the gene_space attribute. # Remove all the genes in the current solution from the gene_space. # This only leaves the unique values that could be selected for the gene. - values_to_select_from = list(set(self.gene_space) - set(solution)) - + + # Before using the gene_space, use gene_space_unpacked instead of gene_space to make sure the numbers has the right data type and its values are rounded. + values_to_select_from = list(set(self.gene_space_unpacked) - set(solution)) + if len(values_to_select_from) == 0: if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but the gene space does not have enough values to prevent duplicates.") value_from_space = solution[gene_idx] @@ -591,14 +563,14 @@ def find_two_duplicates(self, def unpack_gene_space(self, range_min, range_max, - num_values_from_inf_range=100): + sample_size_from_inf_range=100): """ Unpacks the gene space for selecting a value to resolve duplicates by converting ranges into lists of values. Args: range_min (float or int): The minimum value of the range. range_max (float or int): The maximum value of the range. - num_values_from_inf_range (int): The number of values to generate for an infinite range of float values using `numpy.linspace()`. + sample_size_from_inf_range (int): The number of values to generate for an infinite range of float values using `numpy.linspace()`. Returns: list: A list representing the unpacked gene space. @@ -621,7 +593,7 @@ def unpack_gene_space(self, else: gene_space_unpacked = numpy.linspace(start=self.gene_space['low'], stop=self.gene_space['high'], - num=num_values_from_inf_range, + num=sample_size_from_inf_range, endpoint=False) if self.gene_type_single == True: @@ -662,7 +634,7 @@ def unpack_gene_space(self, elif type(space) is dict: # Create a list of values using the dict range. # Use numpy.linspace() - dtype = self.get_gene_dtype(gene_index=gene_idx) + dtype = self.get_gene_dtype(gene_index=space_idx) if dtype[0] in pygad.GA.supported_int_types: if 'step' in space.keys(): @@ -681,7 +653,7 @@ def unpack_gene_space(self, else: gene_space_unpacked[space_idx] = numpy.linspace(start=space['low'], stop=space['high'], - num=num_values_from_inf_range, + num=sample_size_from_inf_range, endpoint=False) elif type(space) in [numpy.ndarray, list, tuple]: # list/tuple/numpy.ndarray diff --git a/pygad/pygad.py b/pygad/pygad.py index 6765169..12afb2c 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -491,7 +491,7 @@ def __init__(self, max_val=self.init_range_high, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) else: self.initial_population[initial_solution_idx], _, _ = self.solve_duplicate_genes_by_space(solution=initial_solution, gene_type=self.gene_type, @@ -1450,7 +1450,7 @@ def initialize_population(self, gene_idx=gene_idx, mutation_by_replacement=True, solution=solution, - num_values=100) + sample_size=100) if random_values_filtered is None: if not self.suppress_warnings: warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} while creating the initial population.") @@ -1465,7 +1465,7 @@ def initialize_population(self, max_val=high, mutation_by_replacement=True, gene_type=gene_type, - num_values=100) + sample_size=100) # self.logger.info("After", self.population[solution_idx]) elif self.gene_space_nested: diff --git a/pygad/utils/.DS_Store b/pygad/utils/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a364f0c0bd6445a262c5f5901cffdae298ec52af GIT binary patch literal 6148 zcmeHKu};G<5Pc3s3JOCd7Dj&o609syRR#toCMIY@gd&9o5sJi?f8+!B6nN*eDw+gF zgsQvf{Orp;J9)O^7=W9t?@xg#fRrwn9IzNMxi3DjBu4g$F7#311-HCT@HEJ_z<*Rg z<}M3!R$+zj^Y?aHH^s7UmdudlaYaA>48|I8hyu@O(DG{Vijpmv_df5v$II}DHP^|4 zUXi&PW4W6iD`j4b2eQsa^FMm|M|_e6pQt2H)?~|CO)q{MH7R?&uxVxrm;$E24l2Ms zTP)2KtuzHp0aIY3fP5c3T`-T>DTYr6i*N)WP8bfxy8J8($4AT~c8a`1b4n#D)x{%* zQ##{5=H(GPMWw^V!-tC_yLduzVRZKILpofpXr(D&3T!K|r_HJC{}(@>|F=ojG6hV5 zol?L}=GXHXUn%abgO`)N*3<9kVv^S>ZYZpTt(dv86(7@sG47>8%p-P+jL_^yAjn{a JDe$KXd;wTAS1$kn literal 0 HcmV?d00001 diff --git a/pygad/utils/__init__.py b/pygad/utils/__init__.py index b39eda1..7d30650 100644 --- a/pygad/utils/__init__.py +++ b/pygad/utils/__init__.py @@ -3,4 +3,4 @@ from pygad.utils import mutation from pygad.utils import nsga2 -__version__ = "1.2.1" \ No newline at end of file +__version__ = "1.3.0" \ No newline at end of file diff --git a/pygad/utils/__pycache__/__init__.cpython-310.pyc b/pygad/utils/__pycache__/__init__.cpython-310.pyc index 8b5a31185773287ac709257526cea352409186d5..d759c90d10eaed53530be5207e3626eadf84f7ae 100644 GIT binary patch delta 93 zcmaFFbcTsHpO=@50SMkZr)Dge$UBSKSkGYMDmxoD{m|mnqGJ8TjNH_e^u!c>m(=3y tlKcXF_td=9lFVesob>#n%#w`UfXb4L{5<`FN|2(`lFXc9{mE908UUY*AUyy8 delta 115 zcmX@Z^oWT!pO=@50SInHM5HgB$UBSKNY8NMD!ZVV6swr@#1sWxh4jpljM5~9f};Ga z)Z~)ln1V{6cuaa~UTR5Za$-(;ei2YXZb4;9Mt+_yM6qsSa!F=>9!za%NoG!Q%w!ct F4FKilDVG2M diff --git a/pygad/utils/__pycache__/crossover.cpython-310.pyc b/pygad/utils/__pycache__/crossover.cpython-310.pyc index 339bfd606ceb0b92b7110057f508de92953b19f0..c8c3626066c9e541fac87f78cc0a88154c574e2b 100644 GIT binary patch delta 107 zcmcbjzh9p(pO=@50SH>^Q!^@UHu4!U+PLe77N-^!>nCR9rlzDPrs%t*7H5~_7wEgE z=B1WoCOhV&=NDy`WaI`^mSp7T=@(Rj6qS}_<`nC1p2V2W!oyvhm|KvO8eg1QmAaXQ H?SdcxbH61W delta 128 zcmdn5e?^}!pO=@50SIhuQ!@H&H}V-V2F0dW#iS>uDCjDrXO?7?CMgsYDKbrX|IGV}9bYD-HpbBbd&*D$8DaC7CA S=Ej#L=9H!uZ+_2uK@b4qLoS&B diff --git a/pygad/utils/__pycache__/mutation.cpython-310.pyc b/pygad/utils/__pycache__/mutation.cpython-310.pyc index 006918879918afe8d76fca9a509e262d43824423..20719fc735f915a36ed7f9729ebc6a9c79e768a4 100644 GIT binary patch delta 4794 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 diff --git a/pygad/utils/__pycache__/nsga2.cpython-310.pyc b/pygad/utils/__pycache__/nsga2.cpython-310.pyc index 0f95b902c7b3663fe0d142179bd5b50b851402a2..02233250a6afff8ddaf1b670402d0548dbcac56a 100644 GIT binary patch delta 592 zcmX|;O-~b16o$QZ`e7+hXIjL5sFg+-i427dfute9YMQVhE_5?7Aq{iWnb^+IJEOwd zJqbB07n+dp54tsm#H}k=WWt`6Kfs;$y@bx5*_gw20k3PiVq1EL9kNtz4K|d6J>$6gp%hsP-T82f|B z8n;>ZZq?Cz_o(G)Q8;Kh1wYz%!=WGaV(vQhU28CBe4$;YnLhrS53;ODIsMxsQ_hfN z`G6035DQI4H%LP*(=Xj-i}X+b%nTYCb>oFho2fm)CWR|>W*GTZ8UIGEML&(sls&PR zs8CvB%Vd{Ui)a>$6H9gOqVp2Dmc&~#CD!FT$qN2SUIsU3#XXp~D^Ue%z+>PEFa;EV zI{hiFWSWo-%9Q);IR)h?vw=i&d|f`!Zq1P~PhMr=VFQ+0S#66+a&j@b!+B-RTa%%A zUzAa#PML#J(>#EB22_BjKu03U@S~UqZjx3%CrNd8#5~L`K#gCA+LSPJq6pof?dn`z lePRJI7D(8>c!@v__nJo4lcJ7Tor#mgF3r}qS(o-|_J8xGfW-g+ delta 616 zcmZ{hyH5f^5XN0O!6zaQ5j^#Lfrn!7fkDyA_=t%X#sZbZAY6_!a7Xq|jg5EW#_$e*7IC90iCSwl~kuHZZ^OR8ij*-Sy!C>V;RDFkAqqQB50r^YZpECMQ_U zE0ZWo`E>g-9)v6JzAV>Bt^|{T2|WILzjk;6xZiDXJ{E_p*hdG@Wc3W7vStgS5)ng) zNyH|r1^owuC|42I=mM%FKy0Tj)Y$UKrE3NFHu%Gn`tbD%AvECZCOM%S7+AAyv*W>A Xmk(pBmf+*R>RgHy diff --git a/pygad/utils/__pycache__/parent_selection.cpython-310.pyc b/pygad/utils/__pycache__/parent_selection.cpython-310.pyc index 491ba741c1657f376ed2ec7c603513cac8318feb..487dcd97223a51570e8593debde5f8d75e678df2 100644 GIT binary patch delta 89 zcmexZc)O4nCR9rlzDPrs%t*7H5~_7wEgE p=B1WoCOhV&=NDy`WaI`^mSp7T=@(Rj6qS}_<`nC1KE|kR4gf|VADRFF delta 111 zcmcaz__2^LpO=@50SInHM5NCs+Q^s27?hG?6_cKrqM)mgo>`Jnnxs%rl%JKFTv8lU zPze-|Nl(p7Ey+wy%t_BL0xHNYs4U6I&(nn{)=f+<$;{7#sVyzZ%qfo9yn#{M900{E BDaZf- diff --git a/pygad/utils/crossover.py b/pygad/utils/crossover.py index a09edc3..8789135 100644 --- a/pygad/utils/crossover.py +++ b/pygad/utils/crossover.py @@ -70,7 +70,7 @@ def single_point_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, @@ -142,7 +142,7 @@ def two_points_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, @@ -209,7 +209,7 @@ def uniform_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, @@ -273,7 +273,7 @@ def scattered_crossover(self, parents, offspring_size): max_val=self.random_mutation_max_val, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index 8535902..dbb4a6a 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -59,111 +59,10 @@ def mutation_by_space(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), self.mutation_num_genes)) for gene_idx in mutation_indices: - range_min, range_max = self.get_random_mutation_range(gene_idx) - - if self.gene_space_nested: - # Returning the current gene space from the 'gene_space' attribute. - if type(self.gene_space[gene_idx]) in [numpy.ndarray, list]: - curr_gene_space = self.gene_space[gene_idx].copy() - else: - curr_gene_space = self.gene_space[gene_idx] - - # If the gene space has only a single value, use it as the new gene value. - if type(curr_gene_space) in pygad.GA.supported_int_float_types: - value_from_space = curr_gene_space - # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. - elif curr_gene_space is None: - rand_val = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - if self.mutation_by_replacement: - value_from_space = rand_val - else: - value_from_space = offspring[offspring_idx, gene_idx] + rand_val - elif type(curr_gene_space) is dict: - # The gene's space of type dict specifies the lower and upper limits of a gene. - if 'step' in curr_gene_space.keys(): - # The numpy.random.choice() and numpy.random.uniform() functions return a NumPy array as the output even if the array has a single value. - # We have to return the output at index 0 to force a numeric value to be returned not an object of type numpy.ndarray. - # If numpy.ndarray is returned, then it will cause an issue later while using the set() function. - # Randomly select a value from a discrete range. - value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], - stop=curr_gene_space['high'], - step=curr_gene_space['step']), - size=1)[0] - else: - # Return the current gene value. - value_from_space = offspring[offspring_idx, gene_idx] - # Generate a random value to be added to the current gene value. - rand_val = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - # The objective is to have a new gene value that respects the gene_space boundaries. - # The next if-else block checks if adding the random value keeps the new gene value within the gene_space boundaries. - temp_val = value_from_space + rand_val - if temp_val < curr_gene_space['low']: - # Restrict the new value to be > curr_gene_space['low'] - # If subtracting the random value makes the new gene value outside the boundaries [low, high), then use the lower boundary the gene value. - if curr_gene_space['low'] <= value_from_space - rand_val < curr_gene_space['high']: - # Because subtracting the random value keeps the new gene value within the boundaries [low, high), then use such a value as the gene value. - temp_val = value_from_space - rand_val - else: - # Because subtracting the random value makes the new gene value outside the boundaries [low, high), then use the lower boundary as the gene value. - temp_val = curr_gene_space['low'] - elif temp_val >= curr_gene_space['high']: - # Restrict the new value to be < curr_gene_space['high'] - # If subtracting the random value makes the new gene value outside the boundaries [low, high), then use such a value as the gene value. - if curr_gene_space['low'] <= value_from_space - rand_val < curr_gene_space['high']: - # Because subtracting the random value keeps the new value within the boundaries [low, high), then use such a value as the gene value. - temp_val = value_from_space - rand_val - else: - # Because subtracting the random value makes the new gene value outside the boundaries [low, high), then use the lower boundary as the gene value. - temp_val = curr_gene_space['low'] - value_from_space = temp_val - else: - # Selecting a value randomly based on the current gene's space in the 'gene_space' attribute. - # If the gene space has only 1 value, then select it. The old and new values of the gene are identical. - if len(curr_gene_space) == 1: - value_from_space = curr_gene_space[0] - # If the gene space has more than 1 value, then select a new one that is different from the current value. - else: - values_to_select_from = list(set(curr_gene_space) - set([offspring[offspring_idx, gene_idx]])) - - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) - else: - # Selecting a value randomly from the global gene space in the 'gene_space' attribute. - if type(self.gene_space) is dict: - # When the gene_space is assigned a dict object, then it specifies the lower and upper limits of all genes in the space. - if 'step' in self.gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=1)[0] - else: - # If the space type is not of type dict, then a value is randomly selected from the gene_space attribute. - values_to_select_from = list(set(self.gene_space) - set([offspring[offspring_idx, gene_idx]])) + value_from_space = self.mutation_process_gene_value(solution=offspring[offspring_idx], + gene_idx=gene_idx) - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) - # value_from_space = random.choice(self.gene_space) - - if value_from_space is None: - # TODO: Return index 0. - # TODO: Check if this if statement is necessary. - value_from_space = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - - # Assinging the selected value from the space to the gene. + # Before assigning the selected value from the space to the gene, change its data type and round it. if self.gene_type_single == True: if not self.gene_type[1] is None: offspring[offspring_idx, gene_idx] = numpy.round(self.gene_type[0](value_from_space), @@ -174,7 +73,6 @@ def mutation_by_space(self, offspring): if not self.gene_type[gene_idx][1] is None: offspring[offspring_idx, gene_idx] = numpy.round(self.gene_type[gene_idx][0](value_from_space), self.gene_type[gene_idx][1]) - else: offspring[offspring_idx, gene_idx] = self.gene_type[gene_idx][0](value_from_space) @@ -198,71 +96,9 @@ def mutation_probs_by_space(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - range_min, range_max = self.get_random_mutation_range(gene_idx) - if probs[gene_idx] <= self.mutation_probability: - if self.gene_space_nested: - # Returning the current gene space from the 'gene_space' attribute. - if type(self.gene_space[gene_idx]) in [numpy.ndarray, list]: - curr_gene_space = self.gene_space[gene_idx].copy() - else: - curr_gene_space = self.gene_space[gene_idx] - - # If the gene space has only a single value, use it as the new gene value. - if type(curr_gene_space) in pygad.GA.supported_int_float_types: - value_from_space = curr_gene_space - # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. - elif curr_gene_space is None: - rand_val = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - if self.mutation_by_replacement: - value_from_space = rand_val - else: - value_from_space = offspring[offspring_idx, gene_idx] + rand_val - elif type(curr_gene_space) is dict: - # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. - if 'step' in curr_gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], - stop=curr_gene_space['high'], - step=curr_gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=curr_gene_space['low'], - high=curr_gene_space['high'], - size=1)[0] - else: - # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. - # If the gene space has only 1 value, then select it. The old and new values of the gene are identical. - if len(curr_gene_space) == 1: - value_from_space = curr_gene_space[0] - # If the gene space has more than 1 value, then select a new one that is different from the current value. - else: - values_to_select_from = list(set(curr_gene_space) - set([offspring[offspring_idx, gene_idx]])) - - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) - else: - # Selecting a value randomly from the global gene space in the 'gene_space' attribute. - if type(self.gene_space) is dict: - if 'step' in self.gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=1)[0] - else: - values_to_select_from = list(set(self.gene_space) - set([offspring[offspring_idx, gene_idx]])) - - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) + value_from_space = self.mutation_process_gene_value(solution=offspring[offspring_idx], + gene_idx=gene_idx) # Assigning the selected value from the space to the gene. if self.gene_type_single == True: @@ -284,145 +120,53 @@ def mutation_probs_by_space(self, offspring): num_trials=10) return offspring - def change_random_mutation_value_dtype(self, - random_value, - gene_index, - gene_value, - mutation_by_replacement): - """ - Change the data type of the random value used to apply mutation. - It accepts 2 parameters: - -random_value: The random value to change its data type. - -gene_index: The index of the target gene. - -gene_value: The gene value before mutation. Only used if mutation_by_replacement=False and gene_type_single=False. - -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. - It returns the new value after changing the data type. - """ + def mutation_process_gene_value(self, + solution, + gene_idx, + range_min=None, + range_max=None, + sample_size=100): - # If the mutation_by_replacement attribute is True, then the random value replaces the current gene value. - if mutation_by_replacement: - if self.gene_type_single == True: - random_value = self.gene_type[0](random_value) - else: - random_value = self.gene_type[gene_index][0](random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] - # If the mutation_by_replacement attribute is False, then the random value is added to the gene value. - else: - if self.gene_type_single == True: - random_value = self.gene_type[0](gene_value + random_value) - else: - random_value = self.gene_type[gene_index][0](gene_value + random_value) - if type(random_value) is numpy.ndarray: - random_value = random_value[0] - return random_value - - def round_random_mutation_value(self, random_value, gene_index): - """ - Round the random value used to apply mutation. - It accepts 2 parameters: - -random_value: The random value to round its value. - -gene_index: The index of the target gene. Only used if nested gene_type is used. - It returns the new value after being rounded. """ - - # Round the gene - if self.gene_type_single == True: - if not self.gene_type[1] is None: - random_value = numpy.round(random_value, self.gene_type[1]) - else: - if not self.gene_type[gene_index][1] is None: - random_value = numpy.round(random_value, self.gene_type[gene_index][1]) - return random_value - - def mutation_filter_values_by_constraint(self, - random_values, - solution, - gene_idx): - - """ - Filter the random values generated for mutation based on whether they meet the gene constraint in the gene_constraint parameter. - It accepts: - -random_values: The random values to filter. - -solution: The solution containing the target gene. - -gene_idx: The index of the gene in the solution. - It returns None if no values satisfy the constraint. Otherwise, an array of values that satisfy the constraint is returned. - """ - - # A list of the indices where the random values satisfy the constraint. - filtered_values_indices = [] - # A temporary solution to avoid changing the original solution. - solution_tmp = solution.copy() - # Loop through the random values to filter the ones satisfying the constraint. - for value_idx, random_value in enumerate(random_values): - solution_tmp[gene_idx] = random_value - # Check if the constraint is satisfied. - if self.gene_constraint[gene_idx](solution_tmp): - # The current value satisfies the constraint. - filtered_values_indices.append(value_idx) - - # After going through all the values, check if any value satisfies the constraint. - if len(filtered_values_indices) > 0: - # At least one value was found that meets the gene constraint. - pass - else: - # No value found for the current gene that satisfies the constraint. - if not self.suppress_warnings: - warnings.warn(f"No value found for the gene at index {gene_idx} that satisfies its gene constraint.") - return None - - filtered_values = random_values[filtered_values_indices] - - return filtered_values - - def mutation_process_random_value(self, - range_min, - range_max, - solution, - gene_idx, - num_values=100): - - """ - Randomly generate constrained values to use for applying mutation. + Generate/select values for the gene that satisfy the constraint. The values could be generated randomly or from the gene space. It accepts: -range_min: The minimum value in the range from which a value is selected. -range_max: The maximum value in the range from which a value is selected. -solution: The solution where the target gene exists. -gene_idx: The index of the gene in the solution. - -num_values: The number of random valus to generate. It tries to generate a number of values up to a maximum of num_values. But it is not always guranteed because the total number of values might not be enough or the random generator creates duplicate random values. + -sample_size: The number of random values to generate from which a value is selected. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. It returns a single numeric value the satisfies the gene constraint if exists in the gene_constraint parameter. """ # Check if the gene has a constraint. if self.gene_constraint and self.gene_constraint[gene_idx]: - # Generate random values to use for mutation. - random_values = self.generate_gene_random_value(range_min=range_min, - range_max=range_max, - gene_value=solution[gene_idx], - gene_idx=gene_idx, - mutation_by_replacement=self.mutation_by_replacement, - num_values=num_values) - # Filter the values that satisfy the constraint. - random_values_filtered = self.mutation_filter_values_by_constraint(random_values=random_values, - solution=solution, - gene_idx=gene_idx) - if random_values_filtered is None: + # Generate values that meet the gene constraint. Select more than 1 value. + # This method: 1) generates or selects the values 2) filters the values according to the constraint. + values = self.get_valid_gene_constraint_values(range_min=range_min, + range_max=range_max, + gene_value=solution[gene_idx], + gene_idx=gene_idx, + mutation_by_replacement=self.mutation_by_replacement, + solution=solution, + sample_size=sample_size) + if values is None: # No value found that satisfy the constraint. # Keep the old value. - random_value = solution[gene_idx] + value_selected = solution[gene_idx] else: # Select a value randomly from the list of values satisfying the constraint. - random_value = numpy.random.choice(random_values_filtered, size=1)[0] - # The gene does not have a constraint. + # If size is used with numpy.random.choice(), it returns an array even if it has a single value. To return a numeric value, not an array, then return index 0. + value_selected = numpy.random.choice(values, size=1)[0] else: - random_value = self.generate_gene_random_value(range_min=range_min, - range_max=range_max, - gene_value=solution[gene_idx], - gene_idx=gene_idx, - mutation_by_replacement=self.mutation_by_replacement, - num_values=1) + # The gene does not have a constraint. Just select a single value. + value_selected = self.generate_gene_value(range_min=range_min, + range_max=range_max, + gene_value=solution[gene_idx], + gene_idx=gene_idx, + mutation_by_replacement=self.mutation_by_replacement, + sample_size=1) # Even that its name is singular, it might have a multiple values. - return random_value + return value_selected def mutation_randomly(self, offspring): @@ -443,10 +187,10 @@ def mutation_randomly(self, offspring): range_min, range_max = self.get_random_mutation_range(gene_idx) # Generate a random value for mutation that meet the gene constraint if exists. - random_value = self.mutation_process_random_value(range_min=range_min, - range_max=range_max, - solution=offspring[offspring_idx], - gene_idx=gene_idx) + random_value = self.mutation_process_gene_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -456,7 +200,7 @@ def mutation_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) return offspring @@ -481,10 +225,10 @@ def mutation_probs_randomly(self, offspring): if probs[gene_idx] <= self.mutation_probability: # Generate a random value fpr mutation that meet the gene constraint if exists. - random_value = self.mutation_process_random_value(range_min=range_min, - range_max=range_max, - solution=offspring[offspring_idx], - gene_idx=gene_idx) + random_value = self.mutation_process_gene_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -494,7 +238,7 @@ def mutation_probs_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) return offspring def swap_mutation(self, offspring): @@ -692,8 +436,6 @@ def adaptive_mutation_population_fitness(self, offspring): else: raise ValueError(f"The fitness function should return a number or an iterable (list, tuple, or numpy.ndarray) but the value ({sol_fitness}) of type {type(sol_fitness)} found.") - - if len(fitness.shape) > 1: # TODO This is a multi-objective optimization problem. # Calculate the average of each objective's fitness across all solutions in the population. @@ -782,79 +524,8 @@ def adaptive_mutation_by_space(self, offspring): mutation_indices = numpy.array(random.sample(range(0, self.num_genes), adaptive_mutation_num_genes)) for gene_idx in mutation_indices: - range_min, range_max = self.get_random_mutation_range(gene_idx) - - if self.gene_space_nested: - # Returning the current gene space from the 'gene_space' attribute. - if type(self.gene_space[gene_idx]) in [numpy.ndarray, list]: - curr_gene_space = self.gene_space[gene_idx].copy() - else: - curr_gene_space = self.gene_space[gene_idx] - - # If the gene space has only a single value, use it as the new gene value. - if type(curr_gene_space) in pygad.GA.supported_int_float_types: - value_from_space = curr_gene_space - # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. - elif curr_gene_space is None: - rand_val = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - if self.mutation_by_replacement: - value_from_space = rand_val - else: - value_from_space = offspring[offspring_idx, gene_idx] + rand_val - elif type(curr_gene_space) is dict: - # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. - if 'step' in curr_gene_space.keys(): - # The numpy.random.choice() and numpy.random.uniform() functions return a NumPy array as the output even if the array has a single value. - # We have to return the output at index 0 to force a numeric value to be returned not an object of type numpy.ndarray. - # If numpy.ndarray is returned, then it will cause an issue later while using the set() function. - value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], - stop=curr_gene_space['high'], - step=curr_gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=curr_gene_space['low'], - high=curr_gene_space['high'], - size=1)[0] - else: - # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. - # If the gene space has only 1 value, then select it. The old and new values of the gene are identical. - if len(curr_gene_space) == 1: - value_from_space = curr_gene_space[0] - # If the gene space has more than 1 value, then select a new one that is different from the current value. - else: - values_to_select_from = list(set(curr_gene_space) - set([offspring[offspring_idx, gene_idx]])) - - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) - else: - # Selecting a value randomly from the global gene space in the 'gene_space' attribute. - if type(self.gene_space) is dict: - if 'step' in self.gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=1)[0] - else: - values_to_select_from = list(set(self.gene_space) - set([offspring[offspring_idx, gene_idx]])) - - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) - - - if value_from_space is None: - value_from_space = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] + value_from_space = self.mutation_process_gene_value(solution=offspring[offspring_idx], + gene_idx=gene_idx) # Assigning the selected value from the space to the gene. if self.gene_type_single == True: @@ -923,10 +594,10 @@ def adaptive_mutation_randomly(self, offspring): range_min, range_max = self.get_random_mutation_range(gene_idx) # Generate a random value fpr mutation that meet the gene constraint if exists. - random_value = self.mutation_process_random_value(range_min=range_min, - range_max=range_max, - solution=offspring[offspring_idx], - gene_idx=gene_idx) + random_value = self.mutation_process_gene_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -936,7 +607,7 @@ def adaptive_mutation_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) return offspring def adaptive_mutation_probs_by_space(self, offspring): @@ -985,81 +656,12 @@ def adaptive_mutation_probs_by_space(self, offspring): probs = numpy.random.random(size=offspring.shape[1]) for gene_idx in range(offspring.shape[1]): - range_min, range_max = self.get_random_mutation_range(gene_idx) - if probs[gene_idx] <= adaptive_mutation_probability: - if self.gene_space_nested: - # Returning the current gene space from the 'gene_space' attribute. - if type(self.gene_space[gene_idx]) in [numpy.ndarray, list]: - curr_gene_space = self.gene_space[gene_idx].copy() - else: - curr_gene_space = self.gene_space[gene_idx] - - # If the gene space has only a single value, use it as the new gene value. - if type(curr_gene_space) in pygad.GA.supported_int_float_types: - value_from_space = curr_gene_space - # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. - elif curr_gene_space is None: - rand_val = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - if self.mutation_by_replacement: - value_from_space = rand_val - else: - value_from_space = offspring[offspring_idx, gene_idx] + rand_val - elif type(curr_gene_space) is dict: - # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. - if 'step' in curr_gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], - stop=curr_gene_space['high'], - step=curr_gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=curr_gene_space['low'], - high=curr_gene_space['high'], - size=1)[0] - else: - # Selecting a value randomly from the current gene's space in the 'gene_space' attribute. - # If the gene space has only 1 value, then select it. The old and new values of the gene are identical. - if len(curr_gene_space) == 1: - value_from_space = curr_gene_space[0] - # If the gene space has more than 1 value, then select a new one that is different from the current value. - else: - values_to_select_from = list(set(curr_gene_space) - set([offspring[offspring_idx, gene_idx]])) - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) - else: - # Selecting a value randomly from the global gene space in the 'gene_space' attribute. - if type(self.gene_space) is dict: - if 'step' in self.gene_space.keys(): - # The numpy.random.choice() and numpy.random.uniform() functions return a NumPy array as the output even if the array has a single value. - # We have to return the output at index 0 to force a numeric value to be returned not an object of type numpy.ndarray. - # If numpy.ndarray is returned, then it will cause an issue later while using the set() function. - value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=1)[0] - else: - values_to_select_from = list(set(self.gene_space) - set([offspring[offspring_idx, gene_idx]])) - - if len(values_to_select_from) == 0: - value_from_space = offspring[offspring_idx, gene_idx] - else: - value_from_space = random.choice(values_to_select_from) + value_from_space = self.mutation_process_gene_value(solution=offspring[offspring_idx], + gene_idx=gene_idx) - if value_from_space is None: - value_from_space = numpy.random.uniform(low=range_min, - high=range_max, - size=1)[0] - - # Assinging the selected value from the space to the gene. + # Assigning the selected value from the space to the gene. if self.gene_type_single == True: if not self.gene_type[1] is None: offspring[offspring_idx, gene_idx] = numpy.round(self.gene_type[0](value_from_space), @@ -1127,10 +729,10 @@ def adaptive_mutation_probs_randomly(self, offspring): if probs[gene_idx] <= adaptive_mutation_probability: # Generate a random value fpr mutation that meet the gene constraint if exists. - random_value = self.mutation_process_random_value(range_min=range_min, - range_max=range_max, - solution=offspring[offspring_idx], - gene_idx=gene_idx) + random_value = self.mutation_process_gene_value(range_min=range_min, + range_max=range_max, + solution=offspring[offspring_idx], + gene_idx=gene_idx) offspring[offspring_idx, gene_idx] = random_value @@ -1140,5 +742,5 @@ def adaptive_mutation_probs_randomly(self, offspring): max_val=range_max, mutation_by_replacement=self.mutation_by_replacement, gene_type=self.gene_type, - num_values=100) + sample_size=100) return offspring diff --git a/pygad/visualize/__pycache__/__init__.cpython-310.pyc b/pygad/visualize/__pycache__/__init__.cpython-310.pyc index 4c367b5cc1c47805adcca3da71281298d99ffd67..473b3f831e39a308d92cef5e00424e43e5c2b1cb 100644 GIT binary patch delta 89 zcmZo>ddtY0&&$ij00cJ1F&X9)dFvd!^h1kNi;DFVGjdZ?(i2nkT~dp)OY#f!-Ba^Y pOEQxkbJFvRGD|XY11d{0^7HfyDnW|MGK)(Sb26(^^(Q`50sz%>9`OJG delta 111 zcmaFM*v!P6&&$ij00cK8BGN4<^45hVq*%qIC#ERqDx_zYWRxZ;6cpuWr6!jY#}rfo z#beS_^HNJPlM{2&^NWBAatkU;GV=3uA&PYqlS?x5^I&SrGK)(Sb26(^V7%Q6lNW-VXDp!OM^Ua1jq?WppKfihJ&6|1eee=F? zK6X0XbUN{b{FY|lv50xlaW6|UW$Js5>pS-5lvjg94jyD*ZIfPqt>!nl$#|YxWwThK z#r!KK-}YTEf6=U%zFm09DY?w{t@4{S-*PK?uLh?bn^&o0@0)pHXk)rABNYxZ{oz8H`93I5^^WmQ;-J$rvXn(2s+eB+;ytH?N)fu#!a6+1M|4Z_6%>0z$r#O&#V*~ zO2GyJNAkh)SUNM%%}U5+*<1^|D$+a*&jM853*u(aSEnLaKL|nA@N24 z-7cvsko4ioGVPM(vvKJi=-i^7iIARe`;JLcK8%J#Yi`w7Jtaw6EE{#Q2D2m0J?H_nt`^Dn;z-86{6&al^0Kfr~c}PjacMbKd zFF;g@X8nmQ~`T{&HG{v8oh^d@h~OWdo1( z45gYwYDyfN4HsbD1T<;V{}`GeLzM|hE80n}`xTS(FwPBFV=C_jaernj77h{d_Uu|X zI8|w*;_mFNdK|td04OQLQ)VfEx@w1%*4A=y_B>R|Z3$8(-}6KN0d3 ziNnH_LMe|{4Hp3t2vj5%S$oGHz_JTAh#LuZ2sRN5R$UH@vDC-!Kiw2&_D@O_z#*vR4gCt}_`1Pm+9Kxz0ba*DSw6Y-`8Z zV5`cg5@oKke;TXMCa872_2;N^ik*+28|y(pufU$dV@1~qe35}i0?RA~d#yZDbV%9Y zjKx<(#vkLi6Q?lYG|fdlJ7)idNOG!1{uC)W(UU{Tu6 zeoL$+8XHvW&nGWOlwa6woP3FPP?%!dWEe)<^f$AJtm#D3`+P|v~C z*k3(EwJ~9Gm3W@v6lfIV!@!Y);Iru*3m97z(9-mY6a4bto`mBm05?dN1lGGMj`c>x z(0$jg$nvnSdTWF2@IMV4Iji{Oee7PkuYNH-n^L;jTSK?hoEY7ClkEPkJQCmvlk{%MW;D)7>_FdQqg&TEl(Gn#8tBmVectK981- zf<+vkv+Eq9QL-Z!K5^{A;g|QCg*62{3!DL_ffe>( z=9` z8$2mBtJ|dNRsvaKo*;#Sez+!my(Xts$+qS5c9U8K`W!~Ww4{3GkdnF+iES0CS!Rfmb z-N^nSa2vP_gl<^70{0bReB9E)mS+ap!E|Qi19&iHua({re_XMS&117O~ePq9A>Jszt`anY=1OK5Ac^sT%JtY+< zWF9z%?}vHhIaPV?CHBMY6*cS}?800wY?JaLRrbZ)H%gqj^Zm8(Ep>^6xttcG=dqgv zungd3(@sF{UxsN3`VvN723`T0qW%q)ZG@sF<+LPy8=*IVcN&vqCw>>-H-YyAUZa;_ z-eL#yXQw(~J_D=**MYAAska7HjK$cC3s>i3!qg%>s_O+euj^sSCg7KKf&18O_Ul6C EZvX_gFaQ7m diff --git a/setup.py b/setup.py index d9ec309..2d9478c 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setuptools.setup( name="pygad", - version="3.4.0", + version="3.5.0", author="Ahmed Fawzy Gad", install_requires=["numpy", "matplotlib", "cloudpickle",], author_email="ahmed.f.gad@gmail.com", diff --git a/test_gene_constraint.py b/test_gene_constraint.py index 28e290a..43cf38a 100644 --- a/test_gene_constraint.py +++ b/test_gene_constraint.py @@ -61,7 +61,7 @@ def fitness_func_no_batch_multi(ga, solution, idx): crossover_type=crossover_type, gene_constraint=gene_constraint, save_solutions=True, - suppress_warnings=True) + suppress_warnings=False) ga_instance.run() @@ -73,21 +73,19 @@ def test_initial_population_int_by_replacement(): ga_instance = population_gene_constraint(gene_constraint=gene_constraint, init_range_low=0, init_range_high=10, + random_mutation_min_val=0, + random_mutation_max_val=10, num_genes=5, gene_type=int, mutation_by_replacement=True) initial_population = ga_instance.initial_population # print(initial_population) - assert numpy.all(initial_population[:, 0] >= 8), "Not all values in column 0 are >= 98" - - assert numpy.all(initial_population[:, 1] >= 8), "Not all values in column 1 are >= 98" - + assert numpy.all(initial_population[:, 0] >= 8), "Not all values in column 0 are >= 8" + assert numpy.all(initial_population[:, 1] >= 8), "Not all values in column 1 are >= 8" assert numpy.all(initial_population[:, 2] >= 1), "Not all values in column 2 are >= 1" assert numpy.all((initial_population[:, 2] >= 1) & (initial_population[:, 2] <= 5)), "Not all values in column 2 between 1 and 5 (inclusive)" - assert numpy.all(initial_population[:, 3] == 4), "Not all values in column 3 between 3 and 5 (exclusive)" - assert numpy.all(initial_population[:, 4] < 2), "Not all values in column 4 < 2" def test_initial_population_int_by_replacement_no_duplicates(): @@ -111,6 +109,47 @@ def test_initial_population_int_by_replacement_no_duplicates(): assert num_duplicates == 0 + initial_population = ga_instance.initial_population + # print(initial_population) + + assert numpy.all(initial_population[:, 0] >= 5), "Not all values in column 0 >= 5" + assert numpy.all(initial_population[:, 1] >= 5), "Not all values in column 1 >= 5" + assert numpy.all(initial_population[:, 2] >= 5), "Not all values in column 2 >= 5" + assert numpy.all(initial_population[:, 3] >= 5), "Not all values in column 3 >= 5" + assert numpy.all(initial_population[:, 4] >= 5), "Not all values in column 4 >= 5" + +def test_initial_population_int_by_replacement_no_duplicates2(): + gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,lambda x: 20= 98), "Not all values in column 0 are >= 98" + assert numpy.all(initial_population[:, 1] >= 98), "Not all values in column 1 are >= 98" + assert numpy.all((initial_population[:, 2] > 20) & (initial_population[:, 2] < 40)), "Not all values in column 2 between 20 and 40 (exclusive)" + assert numpy.all(initial_population[:, 3] < 40), "Not all values in column 3 < 40" + assert numpy.all(initial_population[:, 4] < 50), "Not all values in column 4 < 50" + assert numpy.all(initial_population[:, 5] < 100), "Not all values in column 4 < 100" + def test_initial_population_float_by_replacement_no_duplicates(): gene_constraint=[lambda x: x[0]>=5,lambda x: x[1]>=5,lambda x: x[2]>=5,lambda x: x[3]>=5,lambda x: x[4]>=5] ga_instance = population_gene_constraint(gene_constraint=gene_constraint, @@ -131,6 +170,15 @@ def test_initial_population_float_by_replacement_no_duplicates(): assert num_duplicates == 0 + initial_population = ga_instance.initial_population + # print(initial_population) + + assert numpy.all(initial_population[:, 0] >= 5), "Not all values in column 0 >= 5" + assert numpy.all(initial_population[:, 1] >= 5), "Not all values in column 1 >= 5" + assert numpy.all(initial_population[:, 2] >= 5), "Not all values in column 2 >= 5" + assert numpy.all(initial_population[:, 3] >= 5), "Not all values in column 3 >= 5" + assert numpy.all(initial_population[:, 4] >= 5), "Not all values in column 4 >= 5" + def test_initial_population_float_by_replacement_no_duplicates2(): gene_constraint=[lambda x: x[0]>=1,lambda x: x[1]>=1,lambda x: x[2]>=1,lambda x: x[3]>=1,lambda x: x[4]>=1] ga_instance = population_gene_constraint(gene_constraint=gene_constraint, @@ -151,13 +199,24 @@ def test_initial_population_float_by_replacement_no_duplicates2(): assert num_duplicates == 0 + initial_population = ga_instance.initial_population + # print(initial_population) + + assert numpy.all(initial_population[:, 0] >= 1), "Not all values in column 0 >= 1" + assert numpy.all(initial_population[:, 1] >= 1), "Not all values in column 1 >= 1" + assert numpy.all(initial_population[:, 2] >= 1), "Not all values in column 2 >= 1" + assert numpy.all(initial_population[:, 3] >= 1), "Not all values in column 3 >= 1" + assert numpy.all(initial_population[:, 4] >= 1), "Not all values in column 4 >= 1" + if __name__ == "__main__": #### Single-objective print() test_initial_population_int_by_replacement() - print() + print() test_initial_population_int_by_replacement_no_duplicates() print() + test_initial_population_int_by_replacement_no_duplicates2() + print() test_initial_population_float_by_replacement_no_duplicates() print() test_initial_population_float_by_replacement_no_duplicates2() diff --git a/test_gene_space.py b/test_gene_space.py new file mode 100644 index 0000000..2e83bf6 --- /dev/null +++ b/test_gene_space.py @@ -0,0 +1,1605 @@ +""" +This script is identical to the test_gene_space_allow_duplicate_genes.py script except for: + Setting allow_duplicate_genes=True instead of False. +""" + +import pygad +import random +import numpy + +num_generations = 100 + +initial_population = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + +# Test single gene space with nested gene type. + +def number_respect_gene_space(gene_space=None, + gene_type=float, + num_genes=10, + mutation_by_replacement=False, + random_mutation_min_val=-1, + random_mutation_max_val=1, + init_range_low=-4, + init_range_high=4, + mutation_type="random", + mutation_percent_genes="default", + mutation_probability=None, + initial_population=None, + parent_selection_type='sss', + multi_objective=False): + + def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + + def fitness_func_no_batch_multi(ga, solution, idx): + return [random.random(), random.random()] + + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + num_genes=num_genes, + gene_space=gene_space, + gene_type=gene_type, + initial_population=initial_population, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + allow_duplicate_genes=True, + mutation_by_replacement=mutation_by_replacement, + parent_selection_type=parent_selection_type, + save_solutions=True, + mutation_type=mutation_type, + mutation_percent_genes=mutation_percent_genes, + mutation_probability=mutation_probability, + suppress_warnings=True, + random_seed=2) + + ga_instance.run() + ga_instance.solutions = numpy.array(ga_instance.solutions, + dtype=object) + + # gene_space_unpacked = ga_instance.unpack_gene_space(num_values_from_inf_range=100) + num_outside = 0 + if ga_instance.gene_space_nested == True: + for gene_idx in range(ga_instance.num_genes): + + if type(ga_instance.init_range_low) in ga_instance.supported_int_float_types: + range_min_init = ga_instance.init_range_low + range_max_init = ga_instance.init_range_high + else: + range_min_init = ga_instance.init_range_low[gene_idx] + range_max_init = ga_instance.init_range_high[gene_idx] + if type(ga_instance.random_mutation_min_val) in ga_instance.supported_int_float_types: + range_min_mutation = ga_instance.random_mutation_min_val + range_max_mutation = ga_instance.random_mutation_max_val + else: + range_min_mutation = ga_instance.random_mutation_min_val[gene_idx] + range_max_mutation = ga_instance.random_mutation_max_val[gene_idx] + + all_gene_values = ga_instance.solutions[:, gene_idx] + if type(ga_instance.gene_space[gene_idx]) in [list, tuple, range, numpy.ndarray]: + current_gene_space = list(ga_instance.gene_space[gene_idx]) + # print("current_gene_space", current_gene_space) + for val_idx, val in enumerate(all_gene_values): + if None in current_gene_space: + if (val in current_gene_space) or (val >= range_min_init and val < range_max_init) or (val >= range_min_mutation and val < range_max_mutation): + pass + else: + # print("###########") + # print(gene_idx, val) + # print(current_gene_space) + # print(range_min_mutation, range_max_mutation) + # print("\n\n") + num_outside += 1 + elif val in current_gene_space: + # print("val, current_gene_space", val, current_gene_space) + pass + else: + # print(gene_idx, val, current_gene_space) + num_outside += 1 + elif type(ga_instance.gene_space[gene_idx]) is dict: + if not "step" in ga_instance.gene_space[gene_idx].keys(): + for val in all_gene_values: + if val >= ga_instance.gene_space[gene_idx]["low"] and val < ga_instance.gene_space[gene_idx]["high"]: + pass + else: + print(gene_idx, val, current_gene_space) + num_outside += 1 + else: + gene_space_values = numpy.arange(ga_instance.gene_space[gene_idx]["low"], + ga_instance.gene_space[gene_idx]["high"], + ga_instance.gene_space[gene_idx]["step"]) + for val in all_gene_values: + if val in gene_space_values: + pass + else: + num_outside += 1 + elif type(ga_instance.gene_space[gene_idx]) in ga_instance.supported_int_float_types: + for val in all_gene_values: + if val == ga_instance.gene_space[gene_idx]: + pass + else: + num_outside += 1 + elif ga_instance.gene_space[gene_idx] is None: + for val in all_gene_values: + # print(val) + if (val >= range_min_init and val < range_max_init) or (val >= range_min_mutation and val < range_max_mutation): + pass + else: + # print("###########") + # print(gene_idx, val) + # print(ga_instance.gene_space[gene_idx]) + # print(range_min_init, range_max_init) + # print(range_min_mutation, range_max_mutation) + # print("\n\n") + num_outside += 1 + else: + for gene_idx in range(ga_instance.num_genes): + + if type(ga_instance.init_range_low) in ga_instance.supported_int_float_types: + range_min_init = ga_instance.init_range_low + range_max_init = ga_instance.init_range_high + else: + range_min_init = ga_instance.init_range_low[gene_idx] + range_max_init = ga_instance.init_range_high[gene_idx] + if type(ga_instance.random_mutation_min_val) in ga_instance.supported_int_float_types: + range_min_mutation = ga_instance.random_mutation_min_val + range_max_mutation = ga_instance.random_mutation_max_val + else: + range_min_mutation = ga_instance.random_mutation_min_val[gene_idx] + range_max_mutation = ga_instance.random_mutation_max_val[gene_idx] + + all_gene_values = ga_instance.solutions[:, gene_idx] + # print("all_gene_values", gene_idx, all_gene_values) + if type(ga_instance.gene_space) in [list, tuple, range, numpy.ndarray]: + current_gene_space = list(ga_instance.gene_space) + for val in all_gene_values: + if None in current_gene_space: + if (val in current_gene_space) or (val >= range_min_init and val < range_max_init) or (val >= range_min_mutation and val < range_max_mutation): + pass + else: + # print("###########") + # print(gene_idx, val) + # print(current_gene_space) + # print(range_min_mutation, range_max_mutation) + # print("\n\n") + num_outside += 1 + elif val in current_gene_space: + pass + else: + num_outside += 1 + elif type(ga_instance.gene_space) is dict: + if not "step" in ga_instance.gene_space.keys(): + for val in all_gene_values: + if val >= ga_instance.gene_space["low"] and val < ga_instance.gene_space["high"]: + pass + else: + num_outside += 1 + else: + gene_space_values = numpy.arange(ga_instance.gene_space["low"], + ga_instance.gene_space["high"], + ga_instance.gene_space["step"]) + for val in all_gene_values: + if val in gene_space_values: + pass + else: + num_outside += 1 + + print(f"Number of values outside the range is: {num_outside}.") + return num_outside, ga_instance + +#### Single-Objective +def test_gene_space_range(): + num_outside, _ = number_respect_gene_space(gene_space=range(10)) + + assert num_outside == 0 + +def test_gene_space_numpy_arange(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10)) + + assert num_outside == 0 + +def test_gene_space_list(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10))) + + assert num_outside == 0 + +def test_gene_space_list_None(): + num_outside, _ = number_respect_gene_space(gene_space=[30, None, 40, 50, None, 60, 70, None, None, None]) + + assert num_outside == 0 + +def test_gene_space_numpy(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10)))) + + assert num_outside == 0 + +def test_gene_space_dict_without_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}) + + assert num_outside == 0 + +def test_gene_space_dict_with_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}) + + assert num_outside == 0 + +def test_gene_space_list_single_value(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5]) + + assert num_outside == 0 + +def test_gene_space_range_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_numpy_arange_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_list_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_numpy_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_dict_without_step_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_dict_with_step_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_list_single_value_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_nested_gene_space_range(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[range(0, 10), + range(10, 20), + range(20, 30), + range(30, 40), + range(40, 50), + range(50, 60), + range(60, 70), + range(70, 80), + range(80, 90), + range(90, 100)]) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}]) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_float_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + gene_type=[float, 3]) + + assert num_outside == 0 + +def test_nested_gene_space_dict_with_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10, "step": 1}, + {"low": 10, "high": 20, "step": 1.5}, + {"low": 20, "high": 30, "step": 2}, + {"low": 30, "high": 40, "step": 2.5}, + {"low": 40, "high": 50, "step": 3}, + {"low": 50, "high": 60, "step": 3.5}, + {"low": 60, "high": 70, "step": 4}, + {"low": 70, "high": 80, "step": 4.5}, + {"low": 80, "high": 90, "step": 5}, + {"low": 90, "high": 100, "step": 5.5}]) + + assert num_outside == 0 + + +def test_nested_gene_space_numpy_arange(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[numpy.arange(0, 10), + numpy.arange(10, 20), + numpy.arange(20, 30), + numpy.arange(30, 40), + numpy.arange(40, 50), + numpy.arange(50, 60), + numpy.arange(60, 70), + numpy.arange(70, 80), + numpy.arange(80, 90), + numpy.arange(90, 100)]) + + assert num_outside == 0 + +def test_nested_gene_space_list(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [-10, 10, 20, 30, 40, 50, 60, 70, 80, 90], + [-11, 11, 22, 33, 44, 55, 66, 77, 88, 99], + [-100, 100, 200, 300, 400, 500, 600, 700, 800, 900], + [-4.1, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], + [-5.1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], + [-10.5, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9], + [-15, 15, 25, 35, 45, 55, 65, 75, 85, 95], + [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + [40, 41, 42, 43, 44, 45, 46, 47, 48, 49]]) + + assert num_outside == 0 + +def test_nested_gene_space_list2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]]) + + assert num_outside == 0 + +def test_nested_gene_space_list3_None(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, None], + [1, 2], + [2, None], + [3, 4], + [None, 5], + None, + [None, 7], + [None, None], + [8, 9], + None], + mutation_by_replacement=True) + + assert num_outside == 0 + +def test_nested_gene_space_list4_None_custom_mutation_range(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, None], + [1, 2], + [2, None], + [3, 4], + [None, 5], + None, + [None, 7], + [None, None], + [8, 9], + None], + random_mutation_min_val=20, + random_mutation_max_val=40, + mutation_by_replacement=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=int, + mutation_by_replacement=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], int, numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 00, "high": 10, "step": 1}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_single_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[float, 4]) + + assert num_outside == 0 + +def test_nested_gene_space_single_gene_type_adaptive_mutation(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + # Due to rounding the genes, a gene at index 4 will have a value of 10 (outside the dict range) if [float, 2] is used. + gene_type=[float, 4], + mutation_percent_genes=[70, 50], + mutation_type="adaptive") + + assert num_outside == 0 + +def test_nested_gene_space_single_gene_type_adaptive_mutation_probability(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + # Due to rounding the genes, a gene at index 4 will have a value of 10 (outside the dict range) if [float, 2] is used. + gene_type=[float, 4], + mutation_probability=[0.7, 0.5], + mutation_type="adaptive") + + assert num_outside == 0 + +def test_nested_gene_space_nested_gene_type_adaptive_mutation(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_percent_genes=[70, 50], + mutation_type="adaptive") + + assert num_outside == 0 + +def test_nested_gene_space_nested_gene_type_adaptive_mutation_probability(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.7, 0.5], + mutation_type="adaptive") + assert num_outside == 0 + +#### Multi-Objective +def test_gene_space_range_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_arange_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_None_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=[30, None, 40, 50, None, 60, 70, None, None, None], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_dict_without_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_dict_with_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_single_value_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_range_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_arange_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_dict_without_step_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_dict_with_step_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_single_value_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_range_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[range(0, 10), + range(10, 20), + range(20, 30), + range(30, 40), + range(40, 50), + range(50, 60), + range(60, 70), + range(70, 80), + range(80, 90), + range(90, 100)], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_float_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + gene_type=[float, 3], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_dict_with_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10, "step": 1}, + {"low": 10, "high": 20, "step": 1.5}, + {"low": 20, "high": 30, "step": 2}, + {"low": 30, "high": 40, "step": 2.5}, + {"low": 40, "high": 50, "step": 3}, + {"low": 50, "high": 60, "step": 3.5}, + {"low": 60, "high": 70, "step": 4}, + {"low": 70, "high": 80, "step": 4.5}, + {"low": 80, "high": 90, "step": 5}, + {"low": 90, "high": 100, "step": 5.5}], + multi_objective=True) + + assert num_outside == 0 + + +def test_nested_gene_space_numpy_arange_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[numpy.arange(0, 10), + numpy.arange(10, 20), + numpy.arange(20, 30), + numpy.arange(30, 40), + numpy.arange(40, 50), + numpy.arange(50, 60), + numpy.arange(60, 70), + numpy.arange(70, 80), + numpy.arange(80, 90), + numpy.arange(90, 100)], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_list_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [-10, 10, 20, 30, 40, 50, 60, 70, 80, 90], + [-11, 11, 22, 33, 44, 55, 66, 77, 88, 99], + [-100, 100, 200, 300, 400, 500, 600, 700, 800, 900], + [-4.1, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], + [-5.1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], + [-10.5, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9], + [-15, 15, 25, 35, 45, 55, 65, 75, 85, 95], + [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + [40, 41, 42, 43, 44, 45, 46, 47, 48, 49]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_list2_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_list3_None_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, None], + [1, 2], + [2, None], + [3, 4], + [None, 5], + None, + [None, 7], + [None, None], + [8, 9], + None], + mutation_by_replacement=True, + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_list4_None_custom_mutation_range_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, None], + [1, 2], + [2, None], + [3, 4], + [None, 5], + None, + [None, 7], + [None, None], + [8, 9], + None], + random_mutation_min_val=20, + random_mutation_max_val=40, + mutation_by_replacement=True, + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=int, + mutation_by_replacement=True, + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], int, numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 00, "high": 10, "step": 1}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[float, 4], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_single_gene_type_adaptive_mutation_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + # Due to rounding the genes, a gene at index 4 will have a value of 10 (outside the dict range) if [float, 2] is used. + gene_type=[float, 4], + mutation_percent_genes=[70, 50], + mutation_type="adaptive", + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_single_gene_type_adaptive_mutation_probability_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + # Due to rounding the genes, a gene at index 4 will have a value of 10 (outside the dict range) if [float, 2] is used. + gene_type=[float, 4], + mutation_probability=[0.7, 0.5], + mutation_type="adaptive", + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_nested_gene_type_adaptive_mutation_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_percent_genes=[70, 50], + mutation_type="adaptive", + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_nested_gene_type_adaptive_mutation_probability_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.7, 0.5], + mutation_type="adaptive", + multi_objective=True) + assert num_outside == 0 + +#### Multi-Objective NSGA-II Parent Selection +def test_gene_space_range_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_numpy_arange_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_None_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=[30, None, 40, 50, None, 60, 70, None, None, None], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_numpy_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_dict_without_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_dict_with_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_single_value_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_range_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_numpy_arange_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_numpy_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_dict_without_step_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_dict_with_step_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_single_value_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_range_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[range(0, 10), + range(10, 20), + range(20, 30), + range(30, 40), + range(40, 50), + range(50, 60), + range(60, 70), + range(70, 80), + range(80, 90), + range(90, 100)], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_float_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + gene_type=[float, 3], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_dict_with_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10, "step": 1}, + {"low": 10, "high": 20, "step": 1.5}, + {"low": 20, "high": 30, "step": 2}, + {"low": 30, "high": 40, "step": 2.5}, + {"low": 40, "high": 50, "step": 3}, + {"low": 50, "high": 60, "step": 3.5}, + {"low": 60, "high": 70, "step": 4}, + {"low": 70, "high": 80, "step": 4.5}, + {"low": 80, "high": 90, "step": 5}, + {"low": 90, "high": 100, "step": 5.5}], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + + +def test_nested_gene_space_numpy_arange_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[numpy.arange(0, 10), + numpy.arange(10, 20), + numpy.arange(20, 30), + numpy.arange(30, 40), + numpy.arange(40, 50), + numpy.arange(50, 60), + numpy.arange(60, 70), + numpy.arange(70, 80), + numpy.arange(80, 90), + numpy.arange(90, 100)], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_list_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [-10, 10, 20, 30, 40, 50, 60, 70, 80, 90], + [-11, 11, 22, 33, 44, 55, 66, 77, 88, 99], + [-100, 100, 200, 300, 400, 500, 600, 700, 800, 900], + [-4.1, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], + [-5.1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], + [-10.5, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9], + [-15, 15, 25, 35, 45, 55, 65, 75, 85, 95], + [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + [40, 41, 42, 43, 44, 45, 46, 47, 48, 49]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_list2_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_list3_None_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, None], + [1, 2], + [2, None], + [3, 4], + [None, 5], + None, + [None, 7], + [None, None], + [8, 9], + None], + mutation_by_replacement=True, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_list4_None_custom_mutation_range_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, None], + [1, 2], + [2, None], + [3, 4], + [None, 5], + None, + [None, 7], + [None, None], + [8, 9], + None], + random_mutation_min_val=20, + random_mutation_max_val=40, + mutation_by_replacement=True, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=int, + mutation_by_replacement=True, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], int, numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 00, "high": 10, "step": 1}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[float, 4], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_single_gene_type_adaptive_mutation_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + # Due to rounding the genes, a gene at index 4 will have a value of 10 (outside the dict range) if [float, 2] is used. + gene_type=[float, 4], + mutation_percent_genes=[70, 50], + mutation_type="adaptive", + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_single_gene_type_adaptive_mutation_probability_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + # Due to rounding the genes, a gene at index 4 will have a value of 10 (outside the dict range) if [float, 2] is used. + gene_type=[float, 4], + mutation_probability=[0.7, 0.5], + mutation_type="adaptive", + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_nested_gene_type_adaptive_mutation_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_percent_genes=[70, 50], + mutation_type="adaptive", + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_nested_gene_type_adaptive_mutation_probability_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.7, 0.5], + mutation_type="adaptive", + multi_objective=True, + parent_selection_type='nsga2') + assert num_outside == 0 + + + + +if __name__ == "__main__": + #### Single-objective + print() + test_gene_space_range() + print() + test_gene_space_range_nested_gene_type() + print() + + test_gene_space_numpy_arange() + print() + test_gene_space_numpy_arange_nested_gene_type() + print() + + test_gene_space_list() + print() + test_gene_space_list_None() + print() + test_gene_space_list_nested_gene_type() + print() + + test_gene_space_list_single_value() + print() + test_gene_space_list_single_value_nested_gene_type() + print() + + test_gene_space_numpy() + print() + test_gene_space_numpy_nested_gene_type() + print() + + test_gene_space_dict_without_step() + print() + test_gene_space_dict_without_step_nested_gene_type() + print() + + test_gene_space_dict_with_step() + print() + test_gene_space_dict_with_step_nested_gene_type() + print() + + test_nested_gene_space_range() + print() + + test_nested_gene_space_dict_without_step() + print() + + test_nested_gene_space_dict_without_step_float_gene_type() + print() + + test_nested_gene_space_dict_with_step() + print() + + test_nested_gene_space_numpy_arange() + print() + + test_nested_gene_space_list() + print() + + test_nested_gene_space_list2() + print() + + test_nested_gene_space_list3_None() + print() + + test_nested_gene_space_list4_None_custom_mutation_range() + print() + + test_nested_gene_space_mix() + print() + + test_nested_gene_space_mix_nested_gene_type() + print() + + test_nested_gene_space_mix_initial_population() + print() + + test_nested_gene_space_mix_initial_population_single_gene_type() + print() + + test_nested_gene_space_single_gene_type_adaptive_mutation() + print() + test_nested_gene_space_single_gene_type_adaptive_mutation_probability() + print() + + test_nested_gene_space_nested_gene_type_adaptive_mutation() + print() + test_nested_gene_space_nested_gene_type_adaptive_mutation_probability() + print() + + #### Multi-objective + print() + test_gene_space_range_multi_objective() + print() + test_gene_space_range_nested_gene_type_multi_objective() + print() + + test_gene_space_numpy_arange_multi_objective() + print() + test_gene_space_numpy_arange_nested_gene_type_multi_objective() + print() + + test_gene_space_list_multi_objective() + print() + test_gene_space_list_None_multi_objective() + print() + test_gene_space_list_nested_gene_type_multi_objective() + print() + + test_gene_space_list_single_value_multi_objective() + print() + test_gene_space_list_single_value_nested_gene_type_multi_objective() + print() + + test_gene_space_numpy_multi_objective() + print() + test_gene_space_numpy_nested_gene_type_multi_objective() + print() + + test_gene_space_dict_without_step_multi_objective() + print() + test_gene_space_dict_without_step_nested_gene_type_multi_objective() + print() + + test_gene_space_dict_with_step_multi_objective() + print() + test_gene_space_dict_with_step_nested_gene_type_multi_objective() + print() + + test_nested_gene_space_range_multi_objective() + print() + + test_nested_gene_space_dict_without_step_multi_objective() + print() + + test_nested_gene_space_dict_without_step_float_gene_type_multi_objective() + print() + + test_nested_gene_space_dict_with_step_multi_objective() + print() + + test_nested_gene_space_numpy_arange_multi_objective() + print() + + test_nested_gene_space_list_multi_objective() + print() + + test_nested_gene_space_list2_multi_objective() + print() + + test_nested_gene_space_list3_None_multi_objective() + print() + + test_nested_gene_space_list4_None_custom_mutation_range_multi_objective() + print() + + test_nested_gene_space_mix_multi_objective() + print() + + test_nested_gene_space_mix_nested_gene_type_multi_objective() + print() + + test_nested_gene_space_mix_initial_population_multi_objective() + print() + + test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective() + print() + + test_nested_gene_space_single_gene_type_adaptive_mutation_multi_objective() + print() + test_nested_gene_space_single_gene_type_adaptive_mutation_probability_multi_objective() + print() + + test_nested_gene_space_nested_gene_type_adaptive_mutation_multi_objective() + print() + test_nested_gene_space_nested_gene_type_adaptive_mutation_probability_multi_objective() + print() + + + #### Multi-objective NSGA-II Parent Selection + print() + test_gene_space_range_multi_objective_nsga2() + print() + test_gene_space_range_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_numpy_arange_multi_objective_nsga2() + print() + test_gene_space_numpy_arange_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_list_multi_objective_nsga2() + print() + test_gene_space_list_None_multi_objective_nsga2() + print() + test_gene_space_list_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_list_single_value_multi_objective_nsga2() + print() + test_gene_space_list_single_value_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_numpy_multi_objective_nsga2() + print() + test_gene_space_numpy_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_dict_without_step_multi_objective_nsga2() + print() + test_gene_space_dict_without_step_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_dict_with_step_multi_objective_nsga2() + print() + test_gene_space_dict_with_step_nested_gene_type_multi_objective_nsga2() + print() + + test_nested_gene_space_range_multi_objective_nsga2() + print() + + test_nested_gene_space_dict_without_step_multi_objective_nsga2() + print() + + test_nested_gene_space_dict_without_step_float_gene_type_multi_objective_nsga2() + print() + + test_nested_gene_space_dict_with_step_multi_objective_nsga2() + print() + + test_nested_gene_space_numpy_arange_multi_objective_nsga2() + print() + + test_nested_gene_space_list_multi_objective_nsga2() + print() + + test_nested_gene_space_list2_multi_objective_nsga2() + print() + + test_nested_gene_space_list3_None_multi_objective_nsga2() + print() + + test_nested_gene_space_list4_None_custom_mutation_range_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_nested_gene_type_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_initial_population_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective_nsga2() + print() + + test_nested_gene_space_single_gene_type_adaptive_mutation_multi_objective_nsga2() + print() + test_nested_gene_space_single_gene_type_adaptive_mutation_probability_multi_objective_nsga2() + print() + + test_nested_gene_space_nested_gene_type_adaptive_mutation_multi_objective_nsga2() + print() + test_nested_gene_space_nested_gene_type_adaptive_mutation_probability_multi_objective_nsga2() + print() diff --git a/tests/test_gene_constraint.py b/tests/test_gene_constraint.py index 34a18b9..43cf38a 100644 --- a/tests/test_gene_constraint.py +++ b/tests/test_gene_constraint.py @@ -24,10 +24,12 @@ def population_gene_constraint(gene_space=None, init_range_low=-4, init_range_high=4, random_seed=123, + crossover_type='single_point', initial_population=None, parent_selection_type='sss', multi_objective=False, - gene_constraint=None): + gene_constraint=None, + allow_duplicate_genes=True): def fitness_func_no_batch_single(ga, solution, idx): return random.random() @@ -53,583 +55,169 @@ def fitness_func_no_batch_multi(ga, solution, idx): init_range_high=init_range_high, random_mutation_min_val=random_mutation_min_val, random_mutation_max_val=random_mutation_max_val, - allow_duplicate_genes=False, + allow_duplicate_genes=allow_duplicate_genes, mutation_by_replacement=mutation_by_replacement, random_seed=random_seed, + crossover_type=crossover_type, gene_constraint=gene_constraint, save_solutions=True, - suppress_warnings=True) + suppress_warnings=False) ga_instance.run() return ga_instance #### Single-Objective -def test_number_duplicates_default(): - gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,None,None,None,None,None,None,None,None] +def test_initial_population_int_by_replacement(): + gene_constraint=[lambda x: x[0]>=8,lambda x: x[1]>=8,lambda x: 5>=x[2]>=1,lambda x: 5>x[3]>3,lambda x: x[4]<2] ga_instance = population_gene_constraint(gene_constraint=gene_constraint, - init_range_low=98, - init_range_high=98) + init_range_low=0, + init_range_high=10, + random_mutation_min_val=0, + random_mutation_max_val=10, + num_genes=5, + gene_type=int, + mutation_by_replacement=True) initial_population = ga_instance.initial_population - assert initial_population[:, 0] >= 98 - assert initial_population[:, 1] >= 98 + # print(initial_population) -def test_number_duplicates_default_initial_population(): - num_duplicates = population_gene_constraint(initial_population=initial_population) - - assert num_duplicates == 0 - -def test_number_duplicates_float_gene_type(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_type=float, - num_genes=num_genes, - init_range_low=0, - init_range_high=1, - random_mutation_min_val=0, - random_mutation_max_val=1) - - assert num_duplicates == 0 - -def test_number_duplicates_float_gene_type_initial_population(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_type=float, - num_genes=num_genes, - init_range_low=0, - init_range_high=1, - initial_population=initial_population, - random_mutation_min_val=0, - random_mutation_max_val=1) - - assert num_duplicates == 0 - -def test_number_duplicates_int_gene_type(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=False, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) - - assert num_duplicates == 0 - -def test_number_duplicates_int_gene_type_initial_population(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=False, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - initial_population=initial_population, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) - - assert num_duplicates == 0 - -def test_number_duplicates_int_gene_type_replacement(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=True, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) - - assert num_duplicates == 0 - -def test_number_duplicates_int_gene_type_replacement_initial_population(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=True, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - initial_population=initial_population, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) + assert numpy.all(initial_population[:, 0] >= 8), "Not all values in column 0 are >= 8" + assert numpy.all(initial_population[:, 1] >= 8), "Not all values in column 1 are >= 8" + assert numpy.all(initial_population[:, 2] >= 1), "Not all values in column 2 are >= 1" + assert numpy.all((initial_population[:, 2] >= 1) & (initial_population[:, 2] <= 5)), "Not all values in column 2 between 1 and 5 (inclusive)" + assert numpy.all(initial_population[:, 3] == 4), "Not all values in column 3 between 3 and 5 (exclusive)" + assert numpy.all(initial_population[:, 4] < 2), "Not all values in column 4 < 2" - assert num_duplicates == 0 - -def test_number_duplicates_single_gene_space(): - num_duplicates = population_gene_constraint(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - num_genes=10) - - assert num_duplicates == 0 - -def test_number_duplicates_single_gene_space_initial_population(): - num_duplicates = population_gene_constraint(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - num_genes=10, - initial_population=initial_population) - - assert num_duplicates == 0 - -def test_number_duplicates_single_range_gene_space(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_space=range(num_genes), - num_genes=num_genes) - - assert num_duplicates == 0 - -def test_number_duplicates_single_range_gene_space_initial_population(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_space=range(num_genes), - num_genes=num_genes, - initial_population=initial_population) - - assert num_duplicates == 0 - -def test_number_duplicates_single_numpy_range_gene_space(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), - num_genes=num_genes) - - assert num_duplicates == 0 - -def test_number_duplicates_single_numpy_range_gene_space_initial_population(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), - num_genes=num_genes, - initial_population=initial_population) - - assert num_duplicates == 0 - -def test_number_duplicates_nested_gene_space(): - num_duplicates = population_gene_constraint(gene_space=[[0, 1], - [1, 2], - [2, 3], - [3, 4], - [4, 5], - [5, 6], - [6, 7], - [7, 8], - [8, 9], - [9, 10]], - gene_type=int, - num_genes=10) - - assert num_duplicates == 0 - -def test_number_duplicates_nested_gene_space_initial_population(): - num_duplicates = population_gene_constraint(gene_space=[[0, 1], - [1, 2], - [2, 3], - [3, 4], - [4, 5], - [5, 6], - [6, 7], - [7, 8], - [8, 9], - [9, 10]], - gene_type=int, - num_genes=10, - initial_population=initial_population) - - assert num_duplicates == 0 - - -# def test_number_duplicates_nested_gene_space_nested_gene_type(): - """ - This example causes duplicate genes that can only be solved by changing the values of a chain of genes. - Let's explain it using this solution: [0, 2, 3, 4, 5, 6, 6, 7, 8, 9] - It has 2 genes with the value 6 at indices 5 and 6. - According to the gene space, none of these genes can has a different value that solves the duplicates. - -If the value of the gene at index 5 is changed from 6 to 5, then it causes another duplicate with the gene at index 4. - -If the value of the gene at index 6 is changed from 6 to 7, then it causes another duplicate with the gene at index 7. - The solution is to change a chain of genes that make a room to solve the duplicates between the 2 genes. - 1) Change the second gene from 2 to 1. - 2) Change the third gene from 3 to 2. - 3) Change the fourth gene from 4 to 3. - 4) Change the fifth gene from 5 to 4. - 5) Change the sixth gene from 6 to 5. This solves the duplicates. - But this is NOT SUPPORTED yet. - We support changing only a single gene that makes a room to solve the duplicates. - - Let's explain it using this solution: [1, 2, 2, 4, 5, 6, 6, 7, 8, 9] - It has 2 genes with the value 2 at indices 1 and 2. - This is how the duplicates are solved: - 1) Change the first gene from 1 to 0. - 2) Change the second gene from 2 to 1. This solves the duplicates. - The result is [0, 1, 2, 4, 5, 6, 6, 7, 8, 9] - """ - # num_duplicates = population_gene_constraint(gene_space=[[0, 1], - # [1, 2], - # [2, 3], - # [3, 4], - # [4, 5], - # [5, 6], - # [6, 7], - # [7, 8], - # [8, 9], - # [9, 10]], - # gene_type=[int, int, int, int, int, int, int, int, int, int], - # num_genes=10) - - # assert num_duplicates == 0 - -def test_number_duplicates_nested_gene_space_nested_gene_type_initial_population(): - num_duplicates = population_gene_constraint(gene_space=[[0, 1], - [1, 2], - [2, 3], - [3, 4], - [4, 5], - [5, 6], - [6, 7], - [7, 8], - [8, 9], - [9, 10]], - gene_type=[int, int, int, int, int, int, int, int, int, int], - num_genes=10, - initial_population=initial_population) - - assert num_duplicates == 0 - -#### Multi-Objective -def test_number_duplicates_default_multi_objective(): - num_duplicates = population_gene_constraint() - - assert num_duplicates == 0 - -def test_number_duplicates_default_initial_population_multi_objective(): - num_duplicates = population_gene_constraint(initial_population=initial_population) - - assert num_duplicates == 0 - -def test_number_duplicates_float_gene_type_multi_objective(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_type=float, - num_genes=num_genes, - init_range_low=0, - init_range_high=1, - random_mutation_min_val=0, - random_mutation_max_val=1) - - assert num_duplicates == 0 - -def test_number_duplicates_float_gene_type_initial_population_multi_objective(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_type=float, - num_genes=num_genes, - init_range_low=0, - init_range_high=1, - initial_population=initial_population, - random_mutation_min_val=0, - random_mutation_max_val=1) - - assert num_duplicates == 0 - -def test_number_duplicates_int_gene_type_multi_objective(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=False, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) - - assert num_duplicates == 0 - -def test_number_duplicates_int_gene_type_initial_population_multi_objective(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=False, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - initial_population=initial_population, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) - - assert num_duplicates == 0 +def test_initial_population_int_by_replacement_no_duplicates(): + gene_constraint=[lambda x: x[0]>=5,lambda x: x[1]>=5,lambda x: x[2]>=5,lambda x: x[3]>=5,lambda x: x[4]>=5] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=1, + init_range_high=10, + random_mutation_min_val=1, + random_mutation_max_val=10, + gene_type=int, + num_genes=5, + mutation_by_replacement=True, + allow_duplicate_genes=False) -def test_number_duplicates_int_gene_type_replacement_multi_objective(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=True, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) + num_duplicates = 0 + for idx, solution in enumerate(ga_instance.solutions): + num = len(solution) - len(set(solution)) + if num != 0: + print(solution, idx) + num_duplicates += num assert num_duplicates == 0 -def test_number_duplicates_int_gene_type_replacement_initial_population_multi_objective(): - num_genes = 10 - init_range_low = 0 - init_range_high = init_range_low + num_genes - random_mutation_min_val = 0 - random_mutation_max_val = random_mutation_min_val + num_genes - num_duplicates = population_gene_constraint(gene_type=int, - mutation_by_replacement=True, - num_genes=num_genes, - init_range_low=init_range_low, - init_range_high=init_range_high, - initial_population=initial_population, - random_mutation_min_val=random_mutation_min_val, - random_mutation_max_val=random_mutation_max_val) - - assert num_duplicates == 0 + initial_population = ga_instance.initial_population + # print(initial_population) -def test_number_duplicates_single_gene_space_multi_objective(): - num_duplicates = population_gene_constraint(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], - num_genes=10) + assert numpy.all(initial_population[:, 0] >= 5), "Not all values in column 0 >= 5" + assert numpy.all(initial_population[:, 1] >= 5), "Not all values in column 1 >= 5" + assert numpy.all(initial_population[:, 2] >= 5), "Not all values in column 2 >= 5" + assert numpy.all(initial_population[:, 3] >= 5), "Not all values in column 3 >= 5" + assert numpy.all(initial_population[:, 4] >= 5), "Not all values in column 4 >= 5" - assert num_duplicates == 0 +def test_initial_population_int_by_replacement_no_duplicates2(): + gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,lambda x: 20= 98), "Not all values in column 0 are >= 98" + assert numpy.all(initial_population[:, 1] >= 98), "Not all values in column 1 are >= 98" + assert numpy.all((initial_population[:, 2] > 20) & (initial_population[:, 2] < 40)), "Not all values in column 2 between 20 and 40 (exclusive)" + assert numpy.all(initial_population[:, 3] < 40), "Not all values in column 3 < 40" + assert numpy.all(initial_population[:, 4] < 50), "Not all values in column 4 < 50" + assert numpy.all(initial_population[:, 5] < 100), "Not all values in column 4 < 100" - assert num_duplicates == 0 +def test_initial_population_float_by_replacement_no_duplicates(): + gene_constraint=[lambda x: x[0]>=5,lambda x: x[1]>=5,lambda x: x[2]>=5,lambda x: x[3]>=5,lambda x: x[4]>=5] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=1, + init_range_high=10, + gene_type=[float, 1], + num_genes=5, + crossover_type=None, + mutation_by_replacement=False, + allow_duplicate_genes=False) -def test_number_duplicates_single_numpy_range_gene_space_multi_objective(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), - num_genes=num_genes) + num_duplicates = 0 + for idx, solution in enumerate(ga_instance.solutions): + num = len(solution) - len(set(solution)) + if num != 0: + print(solution, idx) + num_duplicates += num assert num_duplicates == 0 -def test_number_duplicates_single_numpy_range_gene_space_initial_population_multi_objective(): - num_genes = 10 - num_duplicates = population_gene_constraint(gene_space=numpy.arange(num_genes), - num_genes=num_genes, - initial_population=initial_population) - - assert num_duplicates == 0 + initial_population = ga_instance.initial_population + # print(initial_population) -def test_number_duplicates_nested_gene_space_multi_objective(): - num_duplicates = population_gene_constraint(gene_space=[[0, 1], - [1, 2], - [2, 3], - [3, 4], - [4, 5], - [5, 6], - [6, 7], - [7, 8], - [8, 9], - [9, 10]], - gene_type=int, - num_genes=10) + assert numpy.all(initial_population[:, 0] >= 5), "Not all values in column 0 >= 5" + assert numpy.all(initial_population[:, 1] >= 5), "Not all values in column 1 >= 5" + assert numpy.all(initial_population[:, 2] >= 5), "Not all values in column 2 >= 5" + assert numpy.all(initial_population[:, 3] >= 5), "Not all values in column 3 >= 5" + assert numpy.all(initial_population[:, 4] >= 5), "Not all values in column 4 >= 5" - assert num_duplicates == 0 +def test_initial_population_float_by_replacement_no_duplicates2(): + gene_constraint=[lambda x: x[0]>=1,lambda x: x[1]>=1,lambda x: x[2]>=1,lambda x: x[3]>=1,lambda x: x[4]>=1] + ga_instance = population_gene_constraint(gene_constraint=gene_constraint, + init_range_low=1, + init_range_high=2, + gene_type=[float, 1], + num_genes=5, + crossover_type=None, + mutation_by_replacement=False, + allow_duplicate_genes=False) -def test_number_duplicates_nested_gene_space_initial_population_multi_objective(): - num_duplicates = population_gene_constraint(gene_space=[[0, 1], - [1, 2], - [2, 3], - [3, 4], - [4, 5], - [5, 6], - [6, 7], - [7, 8], - [8, 9], - [9, 10]], - gene_type=int, - num_genes=10, - initial_population=initial_population) + num_duplicates = 0 + for idx, solution in enumerate(ga_instance.solutions): + num = len(solution) - len(set(solution)) + if num != 0: + print(solution, idx) + num_duplicates += num assert num_duplicates == 0 + initial_population = ga_instance.initial_population + # print(initial_population) -# def test_number_duplicates_nested_gene_space_nested_gene_type_multi_objective(): - """ - This example causes duplicate genes that can only be solved by changing the values of a chain of genes. - Let's explain it using this solution: [0, 2, 3, 4, 5, 6, 6, 7, 8, 9] - It has 2 genes with the value 6 at indices 5 and 6. - According to the gene space, none of these genes can has a different value that solves the duplicates. - -If the value of the gene at index 5 is changed from 6 to 5, then it causes another duplicate with the gene at index 4. - -If the value of the gene at index 6 is changed from 6 to 7, then it causes another duplicate with the gene at index 7. - The solution is to change a chain of genes that make a room to solve the duplicates between the 2 genes. - 1) Change the second gene from 2 to 1. - 2) Change the third gene from 3 to 2. - 3) Change the fourth gene from 4 to 3. - 4) Change the fifth gene from 5 to 4. - 5) Change the sixth gene from 6 to 5. This solves the duplicates. - But this is NOT SUPPORTED yet. - We support changing only a single gene that makes a room to solve the duplicates. - - Let's explain it using this solution: [1, 2, 2, 4, 5, 6, 6, 7, 8, 9] - It has 2 genes with the value 2 at indices 1 and 2. - This is how the duplicates are solved: - 1) Change the first gene from 1 to 0. - 2) Change the second gene from 2 to 1. This solves the duplicates. - The result is [0, 1, 2, 4, 5, 6, 6, 7, 8, 9] - """ - # num_duplicates = population_gene_constraint(gene_space=[[0, 1], - # [1, 2], - # [2, 3], - # [3, 4], - # [4, 5], - # [5, 6], - # [6, 7], - # [7, 8], - # [8, 9], - # [9, 10]], - # gene_type=[int, int, int, int, int, int, int, int, int, int], - # num_genes=10) - - # assert num_duplicates == 0 - -def test_number_duplicates_nested_gene_space_nested_gene_type_initial_population_multi_objective(): - num_duplicates = population_gene_constraint(gene_space=[[0, 1], - [1, 2], - [2, 3], - [3, 4], - [4, 5], - [5, 6], - [6, 7], - [7, 8], - [8, 9], - [9, 10]], - gene_type=[int, int, int, int, int, int, int, int, int, int], - num_genes=10, - initial_population=initial_population) - - assert num_duplicates == 0 + assert numpy.all(initial_population[:, 0] >= 1), "Not all values in column 0 >= 1" + assert numpy.all(initial_population[:, 1] >= 1), "Not all values in column 1 >= 1" + assert numpy.all(initial_population[:, 2] >= 1), "Not all values in column 2 >= 1" + assert numpy.all(initial_population[:, 3] >= 1), "Not all values in column 3 >= 1" + assert numpy.all(initial_population[:, 4] >= 1), "Not all values in column 4 >= 1" if __name__ == "__main__": #### Single-objective print() - test_number_duplicates_default() - print() - - """ - test_number_duplicates_default_initial_population() - print() - - test_number_duplicates_float_gene_type() - print() - test_number_duplicates_float_gene_type_initial_population() - print() - - test_number_duplicates_int_gene_type() - print() - test_number_duplicates_int_gene_type_initial_population() - print() - - test_number_duplicates_int_gene_type_replacement() - print() - test_number_duplicates_int_gene_type_replacement_initial_population() - print() - - test_number_duplicates_single_gene_space() - print() - test_number_duplicates_single_gene_space_initial_population() - print() - - test_number_duplicates_single_range_gene_space() - print() - test_number_duplicates_single_range_gene_space_initial_population() + test_initial_population_int_by_replacement() print() - - test_number_duplicates_single_numpy_range_gene_space() - print() - test_number_duplicates_single_numpy_range_gene_space_initial_population() - print() - - test_number_duplicates_nested_gene_space() - print() - test_number_duplicates_nested_gene_space_initial_population() - print() - - # This example causes duplicates that can only be solved by changing a chain of genes. - # test_number_duplicates_nested_gene_space_nested_gene_type() - # print() - test_number_duplicates_nested_gene_space_nested_gene_type_initial_population() - print() - - #### Multi-objective - print() - test_number_duplicates_default_initial_population_multi_objective() - print() - - test_number_duplicates_float_gene_type_multi_objective() - print() - test_number_duplicates_float_gene_type_initial_population_multi_objective() - print() - - test_number_duplicates_int_gene_type_multi_objective() - print() - test_number_duplicates_int_gene_type_initial_population_multi_objective() + test_initial_population_int_by_replacement_no_duplicates() print() - - test_number_duplicates_int_gene_type_replacement_multi_objective() - print() - test_number_duplicates_int_gene_type_replacement_initial_population_multi_objective() - print() - - test_number_duplicates_single_gene_space_multi_objective() - print() - test_number_duplicates_single_gene_space_initial_population_multi_objective() - print() - - test_number_duplicates_single_range_gene_space_multi_objective() - print() - test_number_duplicates_single_range_gene_space_initial_population_multi_objective() + test_initial_population_int_by_replacement_no_duplicates2() print() - - test_number_duplicates_single_numpy_range_gene_space_multi_objective() - print() - test_number_duplicates_single_numpy_range_gene_space_initial_population_multi_objective() - print() - - test_number_duplicates_nested_gene_space_multi_objective() + test_initial_population_float_by_replacement_no_duplicates() print() - test_number_duplicates_nested_gene_space_initial_population_multi_objective() + test_initial_population_float_by_replacement_no_duplicates2() print() - - # This example causes duplicates that can only be solved by changing a chain of genes. - # test_number_duplicates_nested_gene_space_nested_gene_type_multi_objective() - # print() - test_number_duplicates_nested_gene_space_nested_gene_type_initial_population_multi_objective() - print() - """ - - diff --git a/tests/test_gene_space.py b/tests/test_gene_space.py index b633880..2e83bf6 100644 --- a/tests/test_gene_space.py +++ b/tests/test_gene_space.py @@ -201,7 +201,7 @@ def fitness_func_no_batch_multi(ga, solution, idx): else: num_outside += 1 - print(f"Number of outside range is {num_outside}.") + print(f"Number of values outside the range is: {num_outside}.") return num_outside, ga_instance #### Single-Objective From 4073ffc9bae60183072b94289ff38a2e5c284421 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 3 Jul 2025 19:39:23 -0400 Subject: [PATCH 48/79] Clean repo --- .DS_Store | Bin 8196 -> 0 bytes pygad/.DS_Store | Bin 6148 -> 0 bytes pygad/__pycache__/__init__.cpython-310.pyc | Bin 205 -> 0 bytes pygad/__pycache__/pygad.cpython-310.pyc | Bin 82985 -> 0 bytes .../helper/__pycache__/__init__.cpython-310.pyc | Bin 260 -> 0 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 13628 -> 0 bytes pygad/helper/__pycache__/unique.cpython-310.pyc | Bin 16294 -> 0 bytes pygad/utils/.DS_Store | Bin 6148 -> 0 bytes .../utils/__pycache__/__init__.cpython-310.pyc | Bin 332 -> 0 bytes .../utils/__pycache__/crossover.cpython-310.pyc | Bin 6079 -> 0 bytes .../utils/__pycache__/mutation.cpython-310.pyc | Bin 17721 -> 0 bytes pygad/utils/__pycache__/nsga2.cpython-310.pyc | Bin 7277 -> 0 bytes .../parent_selection.cpython-310.pyc | Bin 14555 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 237 -> 0 bytes .../visualize/__pycache__/plot.cpython-310.pyc | Bin 13830 -> 0 bytes 15 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .DS_Store delete mode 100644 pygad/.DS_Store delete mode 100644 pygad/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/__pycache__/pygad.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/misc.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/unique.cpython-310.pyc delete mode 100644 pygad/utils/.DS_Store delete mode 100644 pygad/utils/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/crossover.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/mutation.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/nsga2.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/parent_selection.cpython-310.pyc delete mode 100644 pygad/visualize/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/visualize/__pycache__/plot.cpython-310.pyc diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index ccee05d899388080d4c4fe26cc7300ddc72623e8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8196 zcmeHMPj4GV6n~Q@vE9&S>$*uOsOu6zJK0C)HbTsR;E*WNkt0g&J~GwXD{Nz_v*YGj&8NW3Vv1-44 zlW2j6@~DzpGiXjJa$eRGWg;0Vf->k62@2MI;kJCyIw5Ta3<3rLgMdN6AYc&qKOlfR zTbBG8_r9JrtwF#b@LD1u&j%Y-(n>;ynyRA%jY}$4+P^p+ilTDs1ZP zfhDReNimpK$Gk1YK`RLzYO2+VX?0?WXO?6rOnHZ%t;C5{G&QY3z#woE0hzl`(+V9> zOpi19y9}j|QE&gFic+4WHl$pNQhC0V`z<4Po;dB3LtW~T4}KB!;G_;~rflakIvF2q zC*vbWcWE7KU!zZ`Mr-5N+0%^llw1LIu%`C7$8y?3g6!pkr#U+*m)2E_4Sx{*MnUF=X*eW zqbH}LZ5~FTO)I0**m1(p2|B!b@X&W!+}{YQB92A3;khEv4>7CEqPrP#X|nwOUmDeN z<)aa^s2PjOn!&H>(uhkas|Oq$E4-|0Iy^USFK@CL5*Yq8IPe0I)^fUcVf70J< znw8i!c7rXmJM2Stmwn2%S%>+o%l5Q^iP5XRHtvZH&`-VUy;>OfBIsaLYFt$NzegeB z_X|Xti-=!D6!UsD>%&>PL{Eg zXzJKn<_uD2McQvl@>Mx2MnFcH4P;RruS0H_5KuZe)@|BMZ>RBBDmT#>sN=iU^je@?Do?tis0U-9N&YL+Ms diff --git a/pygad/.DS_Store b/pygad/.DS_Store deleted file mode 100644 index 98be63892770443c647aa222a56a6882b24d65b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK&x_MQ6n@hmo5mvapy)B+b-rD* zS9lV<`lsmk-b^8BtVfY$UYYsk$M+^P-^*k=M5Ow&(E(A1h+0UDja$h6VBF8CVl~^s z&Pv8eXi7u;9@4nNb`4Ger@;TF0Po$l?Ae4eD(>IK^HiTjsgA&4)(fQLUt%b2yvDSk zGYXL|DCTjtyuRbpFf4f0!OSSDvPxq&>0HLFFxA_wSu})M<^9~^?aBO7e3xRHl%HOU z|Hh5x`Pi!PH5#W$KJ9jYs9L?z-19u&YkMCC7kWOJhSOQz4=3-q_DbtC+LXiSbrR2q zojcETHVu<3o`^#-ia~k*Cdo#6-q*8il#B1cbohSDA9n67mj_4P{owHFYCl*WJ?7$* z!_~@f-F@)r_~r02{gmlzOdqE32^PC;aRXmaC@ueLFv(J#oui$7Ji262Ab8GVmc@~< ze=7vAwj+E+P75+GBrqd63btM)31X4&;n>ia0l!3*87K~Ak=>*j_z9(ym**;h+`+sV zVJs|tD~LW_L6RKOgb*g7&lH!%;Xsylq6t2GoB~A!_;3i27<(4i2Dypb z;@TiuVD68A$lx-kz&};sH;9NK_5c6? diff --git a/pygad/__pycache__/__init__.cpython-310.pyc b/pygad/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index a939de9e2e698a7f89fc506e58abc4558a6692ed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 205 zcmd1j<>g`kg6+F^GcG?{L(7F4DurrhF=k1tCtD$dN$i;rK)P{ayU2_}9y=!X^qCG-lIYq X;;_lhPbtkwwF6mS%mO4h7&w>!VCOT1 diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc deleted file mode 100644 index 7b6c592df0cde0e632c0bc20361189d07465f9c7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 82985 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 diff --git a/pygad/helper/__pycache__/__init__.cpython-310.pyc b/pygad/helper/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index b92603d2b45f76f85219da6837cef6d3d8aed1e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260 zcmYjJK~BRk5VW17sv!A8uerp)6@*#|skaIVxa4A0^(wZA?O;1dffw0_n=;J|0Z(@hGvZuR!EXa+7m-tN|R=&S<6soZ6@;bJyJL>bxz7soTp&K|g@^sorNu^R*D4;DdHnh#%sM6eK!s`CGt% z@09_GulxzZdvpWef`zCLo4#{iM`fO;sGQ~h{rud|h0s<;M1& diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc deleted file mode 100644 index 9cc35c6db4750e286f51fbde72bf842208382105..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 13628 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 Nnq$$Pj-#&|zXqir^|Al} diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc deleted file mode 100644 index 71e7049cad614f07d97c66d4d7636bed3a4f7a2a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16294 zcmeHOU5p%8R<6IUuCA%>8IS*FY{#jrHyd|w#)*^7hOCptBs;+y;$31JWru`zxvOTT zXL_o7s;WJnQ8g@b#AXqe0{a45MH)XKWQ16xl@JmVPk@9363<11kdQ#p!V3`(nItyf zIk&32x@X2eNtTV!cB^mK&%Ni~I``ajzw=W;wQ5TEy!?*0c=^vI>EGz2_)&543;0D( zqj04;$(3EDEzimPugod@uga54nn~zA4YC z6|GWo54*Zs#+g47*DQA$TsfNR7nuj|xfo2dTo~s#j%iw6z%MdnNlN5R>4wxR z$MTYrFzc@?uPZ&HSBceFi%W@ojlZ#)RIbTWQqOeN-WaZ%*JM{+*21O4d{KI1wr3^M zlAc(uc3tjOld7vuOL2M0SgKr+!mr_;nUoVNQ4%%Ll9F4xuJ>v_wHYg3PjpwmUhdVB zT2e+kFBx;o2c_N^>RFK+S2t^}kyt;F+)5%R*0h8p^PSQSS(a9hQJcg%jU#@RGKtOI zo&_z;WNcmq)5e_tz#N)(_IcG~5s3ye64@=^c1Uu%aVzlc%jvrU|y=L6< z7i`~Kv#&VqE~IHb2<;fcIU;2Q$OBrMREOE`^?}{Gw@@lya@rWeS}R^GbS1_xGemr)3C*qZ^mk8===}J56r|Pir5)90Y9)|E%xw z+Qq!g+bdaRTB*+GIG%4o{WzJT5p@c(@~Rhi zLw`hEVkj_gSb|83NOr{hb~|_?aH20)&U}oCbmG`s>9D|}<50-@KzgDX`|P~!2ZbxP z1C5wfbWgCD^l66P3gnVhWOI>3#~tR*@QCn`ttM6nxWQ5kQci3~ajV^it{zg#JIOE+ z1Bb=a4H(;BW6DQwT}*hGSG%R8&!0VXliPEcl~oS0D5 z!Q!%^uDYP$fG)KWT7Z-obOb3GsGCl=&1;hCX0n`fV5UBvYR#ato|Z5z_|wIXnW~W& zrzNJAs%_6tYYSf7VE(woptQuhcdEQ}Cfyr#JDnhe#9~@vUY=2UND$&H+-yD!RvLof zk?bC?Nt#!Z~|0Hwsq<}mp#b{GSDlE)a0lz=x3 zS4%YDjneCCuSB@UFDIxc>e9|%XmuHs}MM=?Y;c@3^w3U!xHCRH#~`>dMW%OU7#H?d;~>m4kkL?bGh4U z)0V*dPy_o=y%O%Ds{5%pK*d2Srl^=kk?t=j=!kY_-s95J+M*YFsmAEq43ANL-k!PR z$LaJ5DtIst)6tixpb>?~sUVdsipOxYn^w*Hdj)l(M^PC2Nvm4=K%3S+s_B;Ylst5$ zW-52lpgHAm0#us@iu^+glzyy0zk>rPCMkS~%0!tKM>3mVGMz&;!nkwgqzp63fEqJg zxhsVa!mz^K^HN3fYiKJBwtfq)ufjw#Dnws}Lc={6eQK`)*IVR!NO#M38iFK&(+XR+7et>T0o zz~l*hhpb03ej~cWMjK5XZBj7B2q!lIidtkiWng*6e$ktEy6u>ElP3zIxZ|dPp=9<6 zoB{b{2{A9oOKxKcMU^cB^h_4Ze9-kXD+}0ep@lJvB_XH)-V5WUXlk9S8)=16aRai| zKXk5UMh!Pj4S`tQtHJ_d-Nz7DBW~2oA$jA%uTb#}6+ce}8!J>Rd=|w`H63FrOff2$ zH*7>{={VnoyCRB17>03*vBv~yTdsiUgIjbJzLIor#(hsz5M$jdcIX^Z^Tm2mX%k-D z$)@6A&Q-to`XG3Glq=BKa6=>|E1&v)r z{Sl3g+A5rF1XG8#HEHOQ^UfVKG#gb|507;MJjl-!_5FIyM)(In^OJC*-B&N4I~C5O z&%A1+zxn*hQ#&g1pO%*m!avaGBkX2fk~>aGiG9{;{Z1;muf5OJ^}8tUuR^(m&!McPr~^{>O;*KWw-tgbnKhfS>SH9yaO{Q+hbvwa*6=S~nst;=n%E-Y<_ zeXhAvGlva#^7I#WQ2Y3~F1$DPnJ{$LflbaJ&OnDmPE3L5M1adafEI&^7TMJa(2(zM z!*DS7TG^jfM4``|9kEY72%gQssyV(GbiDa41uDeWSkJbTpCBM&>vH4#BM6TKYom^@ z=VswS@E)-*ci}8{eDW0U;D7JOaA>yRH3yo5v)E4MnYs}^1u+z6Cs~}KO~pf0d;vwO z3-pod&BXu?Z!FMbbni43ylx4+r2EDEHsYWmIM*O2#EvWO4C+rnIHMX4rmB`QrC^O| z<}JXEL)m)DXk_SvX*X%<==X8p$~`?%5x{%2SH^O|f3tT z-ysn9C~P|wfjAv?+7j8AW!K<9T%~8m zdR&GJjh8JJu1;Y@IMGy4Q4^hAV-@GG)WQ-xi+BZ)IQjL%pu4bWV>$w9RqmlIF}P3j!zd&O}%WJ4bGOBuI$ zLFeZA@%klu9`O*eHwxy$cE4NEv3xH}^j_k8Jd(S++vr*H5CEKMcZqRr_iguwOSTji z#&B5Y9^#P$56`D^<&p8m^F_}+V}H}9XlM{FiwNwsKEWN_NlF zJ-hJg)alPrF>vsWpqo>;dz^HwjRS4UD42r!M}}@F9P>~(=AmpHxhP^Dg`p2zjzjvF zo}Adi`gaRzj&$zZa`-SH3|VgQyit&035gdmv>`O_VT338cQ(sN)zJO^m7b1vI$9vj zV#!496A8j2Z_EDgLSK_NFG1Te_~#TPWsto{Nf}hvLfzM)<2Asfv#3*r$4*+R^-P~M z{5YRY^h_M@A?q@!BqsE|!D$;rKX1FhZ7DsY*0Zv1^D4F~)aMf44FI&@CtW=Dr&e#`^<26Ly+j)kkI!QcUu(>8=5 zQfU1T;OhRR11Jxo?w|$JbjPl%uH{y*BUcAKl>PtY+Ec}vxL-ppasSbG z2||)M!o3>`kX8A-G`KF#>yyr##SkF*gtM&(KtrgK3?tJ9=ZyHqFFO%31dxW*f>ScH z1UQPnb3Vtw5}|&!!w|Z9KbFG1z!0LzP^9HvEpG7JJ?EUVL5X@!0dw&~4Nw_Tqt88p z8>)6!OhfcOA0UKAvLg_o8)y7=N(9>`6FGQ8X3{)F0U3)|k@ypR)edkcTx&(1NYvTE zg*-!a^Ut}EYy{r6FN95# zRUk)!X>h2jF5?RFqi(98=V-)7laMTFu55&v$1@C!{DC9Slj?c1sRZ>zU29ArEsTg zm>>b=3gxZ!t%+!+{!qGy<(LUHka~)YmsI0y7?FX*HyG!nW#46Q0Na~siIJ%YejX+s z9zl^#59iGYe+rR{!b4Q)0jgA^_^HU7Y0#{W7tU#d(}S7AJWuJNI08A#ceo*9Ozbpg zS5T?}{$tXYz4a*kI(0cqUFr@8B+@cu7g}tEW3!`Zjv}||!I6ZKsNs4@-CGxt0TI1L zAOI?E8LTS76!hKtL?M8?1JF#zo82%JekEpTI>FPzHnt!cK1ySyu&GGP8ptNg^-H)h zSIHIQsZl4`FX8q@f{8i`WA7n(pJHkgsv#RlpP7JpuG3G|@jpnLF+ViQI+AO2O07|J zeQ-yS%`K~fmZ~M2{DyX zWZ6_q8MRPaD$$!%CXmh}XkvV5MPFlUqB4myI1>6aYbClXXdbA57RCZnHe{vrPd>75twizxOX+3HUM8c}0ZKD3RkzANjjOL?^OEp{^?{MHJi*bbT`KAumhaq~l46EFTOfi4%0PiRs0eal0; zq8PwHyWPXS$eRRN;w<91N4l?cc;6iCrXur#%#m}j)Hrv7Qz@Rrm9!-E4SBVo^pD|B zX#NI{<}@1BoW<%ZGx|A5I7gXoh;KBUHas>yTsB$YZDi4;d&yJNh}VLFOD;MLBot8^ zhh-s4f&^2Z;Gc3^xn<~m|JDvon?*M3Eljyhx^4cAfemO%Ns)}wJ)u7MG*qh1xM^02H3cf00wh40qfF@ZP3 zwYao7p4GrL;7qIsJfp@VEiKS$QR4}sK&t{PO?EDLJ4tU+QuET-P_x=c+rgk(#KgJB);i-dlWNN5%YtyD*7W^Z45T#7`?`T`Ks`2Rd}w(XqK{GyrGs)Z>!6c3iV$> zjj)Q>(R0@gR z0&=wcd4a!*=8#aEg;)=sn6xXu7Qm(-CFVf$wUCd(1EacPSb`x)+a~s20;SISOdF~A zWC_9rUEtJ?#)WuK;Eae};T;Qj#c3Y|EtFSa2RU+{%Dt?6uPU?G#j(CeYPe=Qx zn4{v?sCXSkYUEFzPVnwT_!sYFkPQ$vsTR8-FVPWgZ2+g84n~(&JPefC9rh7*c#*30 zv2%EVPHA%#;Pwq1)yKuw*iV?5JGA?RhrD-Aac(ycP9XoB#bO7YD%`@)fWr5>+%hCj7)IT;$3~F!J08arMZ)>j@f1$~fst(Aj-JZl%`nMGAdFyXX?BWDF z3G6Deg{uS>Ep-wl0bG-_%dJ~AjliWYTenP2z+LO(1Dd5CQES{kK~(`a0c8~vfZ9a= z)Y`bMdy$b2Ql30xpW|B8b5EEzskpn*-i{+1{==;b%{ds2e8 z4zcg-CDedd-#B1=P2Qxs+IOXSpa{IxXAu6Rpz{KUluu*tg;svydopx70@64>4Qi#H z{uaEn*!kM{r)b(NC}Kz6EWJhOwzRBtF-<3ffC>7MpOdZ}MP&&|$#l60I zkt1xS(-P53RP-z77&pVYkFP#TBS(JE1Rl1prOyX=1+ujQl-oim6Xi;7|MJ^j{7xjT z{9)7>ak4b(!l*7Nb6C9aGGr>9^puB}5O9Hn81a?dxo}|-9p#}g@-g8RpP@cL-iy~f z&$myBwjwv%37dPCqzY5M(*KZ#ARA&Ye(%#>gVij(VUVBwEeUHAdg1q! zRalGzL*}NEDl?C#@)dZs3h(joZY*j&20E#Enl{*Gu+n+{AJNPE)YFu-aVk?*{1^oz zDD!yCoNfn*Ge^(kAaB0wfWr=G4h9yk;uM1#MfQCebYMUwq7x=wmi)UD%aPPncLJw)L$)o9brNwHHE@1`mk!(+sbA5+aWDtJV{ zj-x-th$4!(nUl0l7`F}hEhb8ns!=uIwJ_vEWDshTih&JBQK_7OpF{aC?ZC#-JM;2f zT@FdxqvMnKo5iU>9<$=DgKBCt8osmQH5#eeXoy$Facngjt6irp?u0XF8h)7yN)ly% zS9pewXwV@!fkNhAKu7dMg$d!nB0nn4J~^5qx@1U-VQwie!sYXk@}l{H@|yX9@nz)* z&OcP1pi=rx<0a*=`LVSgQp>uYmKzN>Xf_%G20es}0*o+dp*lNtn;NIZQ$0^IA6Y0g M$-r(--$~_v0TMZc82|tP diff --git a/pygad/utils/.DS_Store b/pygad/utils/.DS_Store deleted file mode 100644 index a364f0c0bd6445a262c5f5901cffdae298ec52af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHKu};G<5Pc3s3JOCd7Dj&o609syRR#toCMIY@gd&9o5sJi?f8+!B6nN*eDw+gF zgsQvf{Orp;J9)O^7=W9t?@xg#fRrwn9IzNMxi3DjBu4g$F7#311-HCT@HEJ_z<*Rg z<}M3!R$+zj^Y?aHH^s7UmdudlaYaA>48|I8hyu@O(DG{Vijpmv_df5v$II}DHP^|4 zUXi&PW4W6iD`j4b2eQsa^FMm|M|_e6pQt2H)?~|CO)q{MH7R?&uxVxrm;$E24l2Ms zTP)2KtuzHp0aIY3fP5c3T`-T>DTYr6i*N)WP8bfxy8J8($4AT~c8a`1b4n#D)x{%* zQ##{5=H(GPMWw^V!-tC_yLduzVRZKILpofpXr(D&3T!K|r_HJC{}(@>|F=ojG6hV5 zol?L}=GXHXUn%abgO`)N*3<9kVv^S>ZYZpTt(dv86(7@sG47>8%p-P+jL_^yAjn{a JDe$KXd;wTAS1$kn diff --git a/pygad/utils/__pycache__/__init__.cpython-310.pyc b/pygad/utils/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index d759c90d10eaed53530be5207e3626eadf84f7ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 332 zcmYk2F;B!G6vu(Kch$Q~{2E=#sf&X#8jWto#N5I{NFgneLIc#Kei~Q5QdcJ@cPAf> zgOBj%_udQtM{v8{F@pHMRoy$BpDFnt1d?mm;Q~Y=i4E#8!#FVkCX6hc0cR{*I0w#I z4qUK8uAWh~{({@6!t==>yt0u8WmFTjvrs!~ybHm-Di2B7k5T+J3L9E+_EwzM=k@-v zTCvSAwL;co)F$}!GM^>MlN2EHhI6n9;LG_3^6S({@^eoQ0UA_vy^`QT?^Jk+ZlDj! xs;HYA(>kwX*FR0MbCxcH=rV|MuC0!o*TXcIm%WptQCApTL;AWc+TyfI@(W3FR22XK diff --git a/pygad/utils/__pycache__/crossover.cpython-310.pyc b/pygad/utils/__pycache__/crossover.cpython-310.pyc deleted file mode 100644 index c8c3626066c9e541fac87f78cc0a88154c574e2b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6079 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 diff --git a/pygad/utils/__pycache__/mutation.cpython-310.pyc b/pygad/utils/__pycache__/mutation.cpython-310.pyc deleted file mode 100644 index 20719fc735f915a36ed7f9729ebc6a9c79e768a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17721 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 diff --git a/pygad/utils/__pycache__/parent_selection.cpython-310.pyc b/pygad/utils/__pycache__/parent_selection.cpython-310.pyc deleted file mode 100644 index 487dcd97223a51570e8593debde5f8d75e678df2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14555 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 diff --git a/pygad/visualize/__pycache__/__init__.cpython-310.pyc b/pygad/visualize/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 473b3f831e39a308d92cef5e00424e43e5c2b1cb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 237 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 m)~eND+nysX=wjf-qj>by=Yx^Hh@~Sqbkrz From 47b33dd230e8a62e01fcd9377beea032cac0df97 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Thu, 3 Jul 2025 19:46:28 -0400 Subject: [PATCH 49/79] Initialize population with gene constraints --- .DS_Store | Bin 8196 -> 8196 bytes example.py | 24 +- pygad/__pycache__/pygad.cpython-310.pyc | Bin 82985 -> 82172 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 13628 -> 15042 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 16294 -> 16386 bytes pygad/helper/misc.py | 209 +++++++++++------- pygad/helper/unique.py | 17 +- pygad/pygad.py | 105 +++------ .../__pycache__/crossover.cpython-310.pyc | Bin 6079 -> 6079 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 17721 -> 17721 bytes 10 files changed, 196 insertions(+), 159 deletions(-) diff --git a/.DS_Store b/.DS_Store index ccee05d899388080d4c4fe26cc7300ddc72623e8..e558d3d00f9ff8bdc903a1b088a6bd7b4b7c1fb6 100644 GIT binary patch delta 680 zcmZp1XmOa}&nUVvU^hRb=w==PQC3+Yh7yKUhGHNY&yWseYW&Z87je6q%!0(=re>d1QtzX$N;OTPXW3sks$@F5Tqd+s5l>Jg+7BjP%+58B@CGi z$qbG_L(_pWML=E&SaSf-S`aHAs7jv%hx= 90 + ga_instance = pygad.GA(num_generations=100, num_parents_mating=10, sol_per_pop=20, @@ -23,15 +29,21 @@ def fitness_func(ga_instance, solution, solution_idx): random_mutation_min_val=1, random_mutation_max_val=100, mutation_by_replacement=True, - gene_type=[float, 1], - save_solutions=True, - allow_duplicate_genes=False, + # gene_type=[float, 1], + gene_type=[float, [float, 5], [float, 4], [float, 3], [float, 2], [float, 1]], + # save_solutions=True, + # allow_duplicate_genes=False, # gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)), - gene_space=[range(10), {"low": 1, "high": 5}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))], - gene_constraint=[lambda x: x[0]>=98,lambda x: x[1]>=98,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], + gene_space=[range(100), {"low": 1, "high": 100}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))], + # gene_space=numpy.linspace(1, 100, 200), + gene_constraint=[const,lambda x: x[1]>=90,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], ) +# print(ga_instance.initial_population) + ga_instance.run() -print(ga_instance.gene_space_unpacked) +# print(ga_instance.gene_space_unpacked) print(ga_instance.population) + +print(calls) \ No newline at end of file diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc index 7b6c592df0cde0e632c0bc20361189d07465f9c7..056b0f92c11a60af8a17c549a1b6b8bdec5360e5 100644 GIT binary patch delta 14024 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 Tm>lKw!>}9EPpuTK;@tlQScTWP delta 14488 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 tcWT{12_W30+sV$@US~bC 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 GZTS!7NiI_W delta 2752 zcmai0U2GiH6`nhPduMiccD=TD?bxx$j+1yC<;RF0*|8J|C6pF9kmRKSv06Nn^=>z_ z%bnQ}r{gY&meL2JqAMX(Dq%lHt@`3e5PuR9iclLQp5~#-TSX-hNKmyEaeL04-Hn|F zG1eZP``vTTJ@-53p6d_W@6@ekHfu`oo!s&6_w*;e%TR1B@>tSmIsZv@~9M%mpWvfMC7c5Py|pH1~immnuJPSZA)w>5)1maiKF}b zj+BU$q?hjIt3R{L-{U=h6gPL2%E!EHOz~$1Qv9m%0Kch^Z+&K*lDTP`^c*8_0!QO} z)QLybaINLVn%}+@+Ls2d6QT~%@)432qmgv{-;1xt$0g} z$d!pC0Y|>a%?c)>_o0r-5s5huN+fl;~n6za_Wq4$!qR>Ian%Kfo z7)8iHf!Bg%`@#Y(&I_|0IXMhyfK#Sv5#V1fY!``2u9Uj$@3xoQ-qwFJAIN&Ft~6U~ z{G;4y8#jz~ap)kXL9@{gYfV3uc8zs~O`C$|2W-y}=@L-Yd2m}5=U z$(^BFRWD2e7t__PmkZZPiecL050l?ns&xIR8$-FCDfBE z<$Z@#9!;#Q9K~cE1K}vK7K9ZRLYoVlEp`uLgw5l89))O-!^{Ca9uV~$v??oJIk~Dy z=_TDz;Nm8-6j<{&CdUT`ahcXyt5)1te)J(W!#|pwUo0W21YQ_xNs~2`E1L$K?L~nN zGEN7Ti>-!N4r*6Cb`arNe!`yLRn)(*_oaUiQ^7AmScCkUy>zmus?sBYvU12SVox@$uA{H>+w-)J4tc?5Z+N2(1WE}wE_ z!ET&lUaJ92vLgTk<#=R4xszv8s4t*c1Q8Dg?N+PFpf$pi6PB?v21(0HRRX~vdk~G0 zS9To5i4Mvo5V78Tw(hNjYzUDYih_XKe(kxo=VTJ<6MAuWcM=#rC*lA}qs6WoD>e6W ztU$Zl*#vN691Db1La)q!Yp40Uvs9l34uX^Xli3B`yj9dqjT_D(vb=S#GfX=_yeoA1 zy95NS@_vkK8O13Oj&ZjN>J|+e^^XC3w8K_A#L#KCi&^}}{0Ldy`h5O9y>lAfDQpZ^ zjibPVgrq%*`XuVTf7KozEqYL-|6j5qivX?_>_%yb%AlsPTgfBjhT7aN+SI zBq;}Ir?@o=M_pdtF_PJt{!Es?y|BO2&##CysUy6)4Bt4xRKl4!%vr>4ze+;GXOKbP zmau+h}EiTQ8&CwOawc339XznIKd7jq82#cULXC`D1m N=<)xy;v0+8{{w`Ua@YU> diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc index 71e7049cad614f07d97c66d4d7636bed3a4f7a2a..7b9d5415fb61338c565519f031ec4ab21335c548 100644 GIT binary patch delta 1929 zcmZ{kT})g>6o7Xw_ul>gYiVJD1>8S+`3GB8=$5qR@-c)>t^po7M9)Z?1e%z z3yt)_7n+)hzEqQvs7Z|v;@VdqY_y3cKAD)_G(4ETG^T2ziEqR?vxWtvBz$}3%(>^x z`I-4_`Q2;Cm3KOACib^@Z*uC>^>x>K$mL~A(4Aoa#a{Xe8eLu@wo%hWqU0c^X_+`w zaTS%Ex|s+oh-obICLAR~$wg{P?wo}P#Nji+ivPNaQ?2otbnCQj+RiM`vM(IOOdP~T z+(aajDy_;TPY!%Lx9f?l%B$v*hj7d;@cO z(i#iTi#JT`{oy@5!?f0w-}JAG9$pDvc3#|mS{E3wZ}U$t1X{+qyKN1E$km4M;&*_q z3s7y%u6z!&-5&aF;BB-?gTVy4L6?F_bct>R<7k*RhcslT*F%Hoclu4J1#QrcP#3yS z9pUrNTl#_92Z?J9mCU&hXdzrg7wMz$2{cF#MS^{8x}^KroVO6cXIeo_Mk}bENhO!= zBd!&s*31Yu5x&c88g8QEmvkZ0W`9UTRpj~~>F1GC=%4c6k)H(C{(DN8@v-6$#ULFI zgM!#C$XwO#)ZlT@y$pDb;p#YDZ||lT+8>PJMz%I=a3_+*LMpA{1E4VH)D*_4MGP%8 zBz>-s)o?A7u^-S3XaY0 z%KzGVt+Q>U6TXC33flqI6nKKCm@Z*0=CgBpCZFryJ%ul^?YHRT&WEU>{C(G-sP+g` z7*ZuEm`26SX84Dm06I#a^!P(Nn{Mo%%;eOhekGq&S?ZZ|O4o{ka(D07QWK9}JSynyKUYQ>yMzv@-uq#3DT#j{nhChat#t)Uhh_*>`#g0CR zwI1Xmf0S&oY^JEE^RtEFpQ-!U#B$Z!Wr&XqdtRT?Fg&4iTBR58A)aG+A~0bd%w(Jd zC7&Y|B%bs%lMIQ~TU$`9l|=ZX#BDp+X2Q$Ib6W)Z+p%yXoIw$=4vww>c*ILgD$|Z( z%?mH|n1U)-K^Vh*bZxkOq!%osfK~v^D~5*!!#&3^HkcnR4-z=rHd&Q|P-nazjnhbc UD8wH#KHBk$sRZKPG1wucmVD;;y!-CE zzc;?{68UIdD*62$2m4ICr=EZE^J~&;L~3L|Q9I7Qng}0~(e(wxGwPTftN0AZf?)U* zeuGr}hF@t2ICR&7xZqxO&=I!gFrk3ph3%aGCQG$=u}Doum;$Vg@v^!q_eGvSf+6 zx?Ik$&Mj>@XfJ&1F6|j+fs28?Tsz@89}$VqJmgXNz5PsvmB0{!dGSNq_!wZQs{&)43tF4CgKB>5h06!!& z@)(@zI!-e1c2~@u(4Bgi&1eK(PK4YhW5Fv#`wV{RN_s7WSNKKl3ZdUXI5|%KFrQA| z;8>s2-3f;0y2Fty2HnI!CKj(1o>#XT=s0Q*B21a@bo+>4@%ma>g{_{@5Vpf|>MP|X zl{TYE2%!VjfVNUDm#D5P`9e|8Kebev)AP7dqd_zZ!_PfuI&hvS_SfQ-!n|(ri|U$2 zPoO#mv%SHVPNcOC+pV|S;Kf`Ue9#*S*004K?c4569_z-<_?DnvgnA}+p7%3t#;ui> zR`f!tIIw%_9$?F-;Y8myX*Hw!e=b;JKAWu-o}NarT4H36B4Fs%s?IVdH&&;(dN*MrGs04MJ(CjbP_poKCh?zaZe?1@ zK~p@EbPClZ;c%|A)uw&So#h!?DrowAX}LW7EiC3{|Fyc2Yd=}DqASewCyvFjiXjLz z&mPsGQIOdQEih&|3q?hJPIgy=bmO9JE5pEWHiQmkj_N2si+~}}#~FMI7l+k7{fv#t zq&mgi8108Yhr7@2N29|CwTYv6`6*sRimj%06eVQnY{xXzQ()2Ce>xgUqJtAXx4BWc WFp`bi6KapG-3T^Q#W#N5eEP{1qEsV diff --git a/pygad/utils/__pycache__/mutation.cpython-310.pyc b/pygad/utils/__pycache__/mutation.cpython-310.pyc index 20719fc735f915a36ed7f9729ebc6a9c79e768a4..98d68747cea98df122ed33db92fbf6877a4ed058 100644 GIT binary patch delta 22 ccmdnl#kjMJkvpH4mx}=iST3e* Date: Thu, 3 Jul 2025 21:34:18 -0400 Subject: [PATCH 50/79] Optimize code --- example2.py | 39 ++ pygad/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 227 bytes pygad/__pycache__/pygad.cpython-310.pyc | Bin 82172 -> 79681 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 282 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 15042 -> 15104 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 16386 -> 14559 bytes pygad/helper/misc.py | 7 +- pygad/helper/unique.py | 242 +------------ pygad/pygad.py | 335 ++++-------------- .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 354 bytes .../__pycache__/crossover.cpython-310.pyc | Bin 6079 -> 6101 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 17721 -> 17743 bytes pygad/utils/__pycache__/nsga2.cpython-310.pyc | Bin 0 -> 7299 bytes .../parent_selection.cpython-310.pyc | Bin 0 -> 14577 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 259 bytes .../__pycache__/plot.cpython-310.pyc | Bin 0 -> 13852 bytes 16 files changed, 138 insertions(+), 485 deletions(-) create mode 100644 example2.py create mode 100644 pygad/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/helper/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/nsga2.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/parent_selection.cpython-310.pyc create mode 100644 pygad/visualize/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/visualize/__pycache__/plot.cpython-310.pyc diff --git a/example2.py b/example2.py new file mode 100644 index 0000000..7c2ed72 --- /dev/null +++ b/example2.py @@ -0,0 +1,39 @@ +import pygad +import numpy + +function_inputs = [4,-2,3.5,5,-11,-4.7] +desired_output = 44 + +def fitness_func(ga_instance, solution, solution_idx): + output = numpy.sum(solution*function_inputs) + fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) + return fitness + +num_genes = len(function_inputs) + +ga_instance = pygad.GA(num_generations=100, + num_parents_mating=10, + sol_per_pop=20, + num_genes=num_genes, + mutation_num_genes=6, + fitness_func=fitness_func, + init_range_low=1, + init_range_high=100, + # suppress_warnings=True, + random_mutation_min_val=1, + random_mutation_max_val=100, + mutation_by_replacement=True, + gene_type=[float, 1], + save_solutions=True, + allow_duplicate_genes=False, + # gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)), + gene_space=[range(0, 100), {"low": 0, "high": 100, 'step': 1}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))], + gene_constraint=[lambda x: x[0]>=95,lambda x: x[1]>=95,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], + ) + +print(ga_instance.initial_population) + +# ga_instance.run() + +# print(ga_instance.gene_space_unpacked) +# print(ga_instance.population) diff --git a/pygad/__pycache__/__init__.cpython-310.pyc b/pygad/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a806e2b81e67ec11b18b41756b81ec76d7a3f16d GIT binary patch literal 227 zcmd1j<>g`k0=Ca-8BRd@F^GcG?{L(7F4DurrhF=k1tCtD$dN$i;rK)P{ayU2_}AprC7xP6)5N` zq-U07lqM+@6y;~7CYKb)fHlUXr{<-WWF{x(q~{j_733CFmSp7T=|U9iCMK5vjfAO< jkI&4@EQycTE2zB1VUwGmQks)$2Xa&~3y|Po;9vp(wJ1Em literal 0 HcmV?d00001 diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc index 056b0f92c11a60af8a17c549a1b6b8bdec5360e5..30531f3315210e50eefcc49ccfa8eac695eafbd2 100644 GIT binary patch delta 7126 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 delta 9430 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 diff --git a/pygad/helper/__pycache__/__init__.cpython-310.pyc b/pygad/helper/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..dbca10c72f3f1a7f967cab21311c43ceb8412f0c GIT binary patch literal 282 zcmYjKL2AP=5R_y)gx2|kuRhqcJ(g1Xf?je67zg24!d9^*RgweFAM%iVAy4r&r+gu& ztP%oQ*x6Zjc1A50F9_QG7AjSW{frY)W^)7nPKk~QVG%R90HWkZT-f3=(w>JU6^h)WL8!MEg^ngs{4<0US z_F2M5k|m9B?XXF-QGdL4w!Ar&4VT=By6PHbK+x;awMu4paaPq1EFao?YDuY(!cm&* WahiYiiVYqA#t>#_05OX3oLm7Ma!TO< literal 0 HcmV?d00001 diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc index 6b6e06bf35b16d2f17de2cf7ee25c50fb5b66c9d..e07f6cd83500112a3cb79c51df3c2e08087f648a 100644 GIT binary patch delta 1432 zcmah|&u<$=7@gT4-nCsjv7I<^g&M1^gbfLiMGMp|X(CD?P(ejhB*3b>co%zJvtErS zX$xZo5>!qR&_P@(gu?-p8aFxQ+R9sYwbr!k;#{%m^499RMW1!sPJ{Pq{xzuAnvUmi zw_$afT?T^p{cF6{_2x$6Ijh0luD7dgIUV0&wc+ntCz%W4YgT8~sk`>oKo4m!bjN?u z%%>ngnObLLCRdf$k7ngplMiO+p%|oH&*gPyc}=I@>0V(KERO9fw%lfmJuSbe=H&hS zVfw86Gyn4Xb%hY2+H{q$lpt*)sI6?#n$p+%G0zZWKn2~7-eFx~KXMhGnxF-;Nx9yR zapOH@jj$)7YGQQ|-zI&-%Ri(`w3F=vpgOgy0)-BIJv8bYAU42mS9VSqebc)wj4)UO zj!CuCLKiWi+3J?sj|+ue6migeFH{kqBEY_lJjS4v8@48l!T2_TBR>guX8^O{rxyrd zEJUq9)@@@;1ygfN0gI2LgZ9ft2bx}w=P;rjzpSvEVZ1ivpAZ!660qBdrnnQX)wsbY z!d6!(cJ?xWh>$Oof< zxV}H)Utz=~MqK}QM(ERoK9iS~&nbIs)D^G-&)WwCy-DoU*XkzSphAUy4#DhTBCvj( zrv@2eZbO&C#A}1vu_)l)#hISY^}$r6Bkc?h&50~J2y zK*LWr(0c9%4j^}Y_B^bz1%R>|=sj*RjtgC%EnG==6{lGhWzPTv8T>2Od%o3h z>Yf80Hk+3p&Mec4{Aq?Kj-l{4z}xbL;-$owz|3q`Nvp%1;*{|cJBjNQ0K6BGYXGmE zEi3FakicM$-vMiO3f@3+2qh}=F*b*M8Q~QfFCCdYkMsz_s|c?noRdqX!!!ScUB=?rZQe=cp9Xa7eE`m*+4E@hRyh-U8~T#%nt4vz0xMaSH(%+v3Nzg6zU z-mIaK${t5`2>~-f(-)D?B2Tg`@;#GBqKCUi#;8N5Kt6bGiQbcoCwy9$w@xe^MW@l6 h@UBL;b^%!!Gd#?ax#|L)kZ)AyW8vJE<)_t?e*^ttWK{qF delta 1314 zcmah|OKej|6rFh=zvq7%5*xFKO+kvCf}X-hG-*NwMG29hN)#1|C?#0DM{LJDTjNP6 z(ztGkx=a;mmZie(rfk6r#0u06E0DTD!V9D>SyWwiRjK!mpPLjR;l=UMopaAU_s02d z>$lBBEgp{$`0V_CvsB%Dn7D7ue&Kx8@L0nsmTr0OdeL<*c#RESt2^_a@A2}DbJg`4 zEA!H=#XG!I^PTz~6qUSc-DA!pvl>0H65c9SH@&s8`%TYObfw!GdTr2S-K(QZG<6&X z_7Y{k%-2}aU-#CkwcE^*&r+jwQoc%^rl(~reX&*52oa=B1ntmOt!Xwxf>bDbz%}^R z1vlkxCR)&OikUbKujoKx={9K=rw>nl0OZ zA}p6~5_Sc~#42%NijXi|ZAWj0g~padxM};p3SETL1lTWuNdh{4aCKp|lN|yhZ>#Ph zz>mPs$PhqNp`IabXh#Q6Yexfz?}Ir?)e2<&fwte^!xh9Mufgjx9LH+7W6FkuL8Sb%r_7dZW#o9xi2WGv~-mfo>f}>emCz# zkQd)@uYq-!`AsUk+-9}(8HVa-TuL))b++20CSjE}leL4P7{>}P$O-lal^ zea7H;a1nUV^H_UG*d5qX*mw!3jRyhu{&2Q$a-*FJbp89;DjakzlR~_hGzp31a=Gr? zOx%?JjAaf_!|#=S22jv@W`h?Q?->pFk6^P{qijZ=$zIpf?3BEpP1ED@adv?^lID2i z1a_VT=*d>@dT19|Vz~P@m$nw5?;yTX0N`lF41h1^3L2XM(zBRXuNH53Y^pCALy3+& z$v#0ohcGYa^G8N6BRzs}5#bWTf^6kK&i)sHWw1Dcpqv(w&B|x_3-nsoEZnu{J|G2s z!FZobL1*XD>?*>td@*@wXwNDb^Y^4fJKgaUPeWH$&`4+FsLmr`CRlnE`2_MP8%Dlo z@=kQs)i*{RI<3f=sdMzX{C28Nzm|VZojr<9{X3mP_9?7%Q6!z|V*Xy-or*1-0$4=a&C0U5mP)MBSmzGiyplMN3s6vWa;*IUZ-gRf! zO#}Hsk&2KYLe<>3zzs!F56E{ANR$&2SB3-kA|Y`=s89s&&8AKo1#py4Z{~e(=FR)t z&3A>Lu1D;K2F(XQ?Nq*SdGqtgkBXqvd&-5R9v~|0lK?^x0eK{Z5UyPe>_>mj#bmMSKhqdy0~5(vGcE!8$d?NaCj_Yk3*?nJs0 z0TRQ-QG_OhW`xHOTIjtX|yXrXQC$)t*}&^V`8fi zRungLrNRVF=+2 z0;eL23?U@R%gN<~cO(tHUXjIsm?|O~?Nd$C$rVbX+f8 zEvQQ&97pH`Ug zai04Ko)JZ8L^wh}?(E!1p;*8E@PY!09`gLySBGf0S5rN>)DhAMUUi{#AQaEQUQ~e0 zr9kkv(vhyo4fGg04qxV20IR-52n?bt9P=d6m0bU-fNNK9WssDduoYNSR+XGs^^tZ@ zHw_0_4PY5~ln-oaO98fTQC(G#oAYl;YXLVn?NfExD2p&H=qKGJy4Dt>Z%DoLZii0S zdnV~SVM>2U&CoCOH#fA@?vI=YFQ>@!fCQ@r3S}H+ULzAI9he}z_@6}uriwN4($6js zyPS0@2IN`Y*RF>p?*f>eIJCH42F)l&v>$}VCVDNN7UbG=-yI=~8K2T+ui9`{FnPIP z5|haWA;t>90)&>Ww;nsn`;vQ!M5QiP(%-Pad#Ux6x@)=6_j5E z0Q2J8rk-Hho@?~>lT8E1kiiGu3n=l|1+1L+uo$-lSK<04bH!nb0ezJ>>34(4@+IVB zZ!nqIRV&BC0E|k-v~nzHTGg^i4A&%WNK7Ea5pYD3Bmk4$YNce7DcqREjX>2ZF4&~3 zN4;RyhajVuG70%Ia%d$Jmoc^Q(a7{_{P{d+^qb5%@jamy95Q4fMVH2|ZV)`VuMQ4K zy1+uV3)%C=rl;l8Wf`<*pnl0sq$vb%^F9wVDDm(tfW&0To_SLb?j_vgV%xGKJiguy zXLl!a3GK_Mjd?Y(&jE)?z~XK|0&gI51p!@REp&aXMf_W!?~j+@%kkTIMjt>W&TT#o zc^eQM1_bMua8IwJv`()~9Ph#Cs6J8E{t#ah1?_=2q}>X+%G4cO-W<7NdtwnX&M^RHWg*_vur#Q@7n8` z&7Ik#PG_}g!xK^<=&Daqk&T2Moi61d1;BR0Y5KYA;4d!FOVx_L~0ssCHbP9d<>oMLn!bsrMI;bYyc;N z2tx?N2qOr2fM~GcR?jyau^+#5cBSOk%kxg8UaUCGiIiH^4+G%NPZgO8HD`^LKLOg0sgC8jwJY>a$d5# z9Zd7E#twi)k%t3QrUj#K!oNSqe>AX1p@ufl{^P)D<;?$DVBMEv1?~U1@hsS8p8P>4PYc;6vlq=)>%P9@xXAeSG|TN_Q}cC zgUdg%-U`XskDf9`8{L8u#q$Kx_8@diYeOXV0(Y7~o-7zBBm5_mM_T9@dX2mA@5z!B zN{iAp!gd4%Ml_`H(OFaiHPFt<7bLbB4&6FuRbP{un!8SEYM`gVVKWvJizZ0#4z;F! zjksSAHJdaf7J_avq&B@uns6Ljcs7M_3WQIEimfbBb_9g_nhC&k({wfXX2dMpG-13c zBq0qGp=m3jVXIf;1G5@zg9z$=ZAcFq{DM1C7 zM*V*Tn^(u|Oh~UwcJ@7ajS$KG86=FbA zN-GX7%wNLULj#O-j2C`41lc{g!OGhrZgG@1>a{ux{8zA_?gV>-!UF-{DMBbMPIOMm zqvH5ua1-PB!=5;HqSbQMZn#3d6Vh98Ekgu;0jhn?hgNgPL9FDSDggQ3!cFBRut~tu zzXEyHg7X4Z5$ab6jDWYcjW+dAe@*H#;(4LJ{8cJRrUgI#?zS3#e$Np9X>x`ipV-bz z+h65>9-ieND|vo<``37&=J~;03I5)WldWS+gPVlGEhC*^1`g8*_*g`8&AS*WmFhwT z9tLq$>d3>Uf+g=u#m^63pEu-4^@B1C@U`;l!Un_psz88Jz2w}G*-=#c0>W{COGk|_ zdkxEX0*k*rHt7Tv#}aR|<(;!|f*pu;5gH4t>e|km)}=YCQuZy+t*uyP%dff%HOCTf z$&_`X((rd%cB5Xa&X)tnx5~`1>Yndc&(@r&OG~{gwbqTt%h?{tyIug+FF2OtdX0rj zY}dB}&#E)$g5w4~#-|EfqfO{t+)I>P2OemfZH7%nW|uDBh)8$sGGpZx@tj3!(5Sa+ zPTaGMV<@&Dj3JC7OaMgbs#^_8@W3rNB}_x6XO0<(hU3StwA={FLDh3hov!U6jPje< z&M^y(pWL z3NR(3!G4-0sg6-_uw&G?lTGx)%;>S1R*@Uq98G{Na0aY_rP%E)@Dck#5e1e_(=@P@ ze=BP0oF#!mB_I?PEa75G6f&GVXs&$G zYZm#hcO9UUw4E!o)t1OiH&HL6DTN(_5BnlOB)g5}$Z%Y{?p593gt%l1B+j(B7id>F zzJ^{w6JzW(H1R3|9;mpJ7pGAA5&O|r&6&&UH2h+m` zA9c*w>nMZqh>yW(7!@*cG}c?~A|IU@Q$<|({+Uq~3%tmSGrLVuvlxXUzc%wZ`V4K~ zoGGj1Cf~R3jcgsQc00QB{9F6RUuXdFZ*Wnw3ow9&6|KfMfsG@v$we4Ns<`k9i4B=y zPXM#X$!yMCLWP=`Zke?P1Dsw&5C`H6jM|hh%+^Sj-<+K-%p%J|z$<`Z|H!bxWO#I# zSlz?X8NPRJ>*KgHL!yRxhaQE)@CiL+-llJuw~eRh7#1. """ - range_min, range_max = self.get_random_mutation_range(gene_idx) + if gene_value is None: + # Use the initial population range. + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) + else: + # Use the mutation range. + range_min, range_max = self.get_random_mutation_range(gene_idx) if self.gene_space_nested: # Returning the current gene space from the 'gene_space' attribute. diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 8b3e522..91b1f79 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -120,25 +120,13 @@ def solve_duplicate_genes_by_space(self, else: return new_solution, not_unique_indices, len(not_unique_indices) - # Do another try if there exist duplicate genes. - # If there are no possible values for the gene 3 with index 3 to solve the duplicate, try to change the value of the other gene with index 2. - if len(not_unique_indices) > 0: - not_unique_indices = set(numpy.where(new_solution == new_solution[list(not_unique_indices)[0]])[0]) - set([list(not_unique_indices)[0]]) - new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(new_solution=new_solution, - gene_type=gene_type, - not_unique_indices=not_unique_indices, - num_trials=10, - build_initial_pop=build_initial_pop) - else: - # DEEP-DUPLICATE-REMOVAL-NEEDED - # Search by this phrase to find where deep duplicates removal should be applied. - - # If there exist duplicate genes, then changing either of the 2 duplicating genes (with indices 2 and 3) will not solve the problem. - # This problem can be solved by randomly changing one of the non-duplicating genes that may make a room for a unique value in one the 2 duplicating genes. - # For example, if gene_space=[[3, 0, 1], [4, 1, 2], [0, 2], [3, 2, 0]] and the solution is [3 2 0 0], then the values of the last 2 genes duplicate. - # There are no possible changes in the last 2 genes to solve the problem. But it could be solved by changing the second gene from 2 to 4. - # As a result, any of the last 2 genes can take the value 2 and solve the duplicates. - return new_solution, not_unique_indices, len(not_unique_indices) + # DEEP-DUPLICATE-REMOVAL-NEEDED + # Search by this phrase to find where deep duplicates removal should be applied. + # If there exist duplicate genes, then changing either of the 2 duplicating genes (with indices 2 and 3) will not solve the problem. + # This problem can be solved by randomly changing one of the non-duplicating genes that may make a room for a unique value in one the 2 duplicating genes. + # For example, if gene_space=[[3, 0, 1], [4, 1, 2], [0, 2], [3, 2, 0]] and the solution is [3 2 0 0], then the values of the last 2 genes duplicate. + # There are no possible changes in the last 2 genes to solve the problem. But it could be solved by changing the second gene from 2 to 4. + # As a result, any of the last 2 genes can take the value 2 and solve the duplicates. return new_solution, not_unique_indices, num_unsolved_duplicates @@ -194,26 +182,6 @@ def unique_int_gene_from_range(self, sample_size=None, step=step) - """ - # For non-integer steps, the numpy.arange() function returns zeros if the dtype parameter is set to an integer data type. So, this returns zeros if step is non-integer and dtype is set to an int data type: numpy.arange(min_val, max_val, step, dtype=gene_type[0]) - # To solve this issue, the data type casting will not be handled inside numpy.arange(). The range is generated by numpy.arange() and then the data type is converted using the numpy.asarray() function. - all_gene_values = numpy.asarray(numpy.arange(min_val, - max_val, - step), - dtype=dtype[0]) - - # If mutation is by replacement, do not add the current gene value into the list. - # This is to avoid replacing the value by itself again. We are doing nothing in this case. - if mutation_by_replacement: - pass - else: - all_gene_values = all_gene_values + solution[gene_index] - - # After adding solution[gene_index] to the list, we have to change the data type again. - all_gene_values = numpy.asarray(all_gene_values, - dtype[0]) - """ - selected_value = self.select_unique_value(gene_values=random_values, solution=solution, gene_index=gene_index) @@ -352,196 +320,22 @@ def unique_gene_by_space(self, build_initial_pop=False, num_trials=10): - """ - Returns a unique value for a specific gene based on its value space to resolve duplicates. - - Args: - solution (list): A solution containing genes with duplicate values. - gene_idx (int): The index of the gene that has a duplicate value. - gene_type (type): The data type of the gene (e.g., int, float). - num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. - - Returns: - Any: A unique value for the gene, if one exists; otherwise, the original gene value. """ - - if self.gene_space_nested: - if type(self.gene_space[gene_idx]) in [numpy.ndarray, list, tuple]: - # Return the current gene space from the 'gene_space' attribute. - curr_gene_space = list(self.gene_space[gene_idx]).copy() - else: - # Return the entire gene space from the 'gene_space' attribute. - # curr_gene_space = list(self.gene_space[gene_idx]).copy() - curr_gene_space = self.gene_space[gene_idx] - - # If the gene space has only a single value, use it as the new gene value. - if type(curr_gene_space) in pygad.GA.supported_int_float_types: - value_from_space = curr_gene_space - # If the gene space is None, apply mutation by adding a random value between the range defined by the 2 parameters 'random_mutation_min_val' and 'random_mutation_max_val'. - elif curr_gene_space is None: - dtype = self.get_gene_dtype(gene_index=gene_idx) - - if dtype[0] in pygad.GA.supported_int_types: - if build_initial_pop == True: - # If we are building the initial population, then use the range of the initial population. - min_val = self.init_range_low - max_val = self.init_range_high - else: - # If we are NOT building the initial population, then use the range of the random mutation. - min_val = self.random_mutation_min_val - max_val = self.random_mutation_max_val - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=min_val, - max_val=max_val, - mutation_by_replacement=True, - gene_type=dtype) - else: - if build_initial_pop == True: - low = self.init_range_low - high = self.init_range_high - else: - low = self.random_mutation_min_val - high = self.random_mutation_max_val - - """ - value_from_space = numpy.random.uniform(low=low, - high=high, - size=1)[0] - """ - - value_from_space = self.unique_float_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=low, - max_val=high, - mutation_by_replacement=True, - gene_type=dtype, - sample_size=num_trials) - - - elif type(curr_gene_space) is dict: - dtype = self.get_gene_dtype(gene_index=gene_idx) - - # Use index 0 to return the type from the list (e.g. [int, None] or [float, 2]). - if dtype[0] in pygad.GA.supported_int_types: - if 'step' in curr_gene_space.keys(): - step = curr_gene_space['step'] - else: - step = None - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=curr_gene_space['low'], - max_val=curr_gene_space['high'], - step=step, - mutation_by_replacement=True, - gene_type=dtype) - else: - if 'step' in curr_gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=curr_gene_space['low'], - stop=curr_gene_space['high'], - step=curr_gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=curr_gene_space['low'], - high=curr_gene_space['high'], - size=1)[0] - else: - # Selecting a value randomly based on the current gene's space in the 'gene_space' attribute. - # If the gene space has only 1 value, then select it. The old and new values of the gene are identical. - if len(curr_gene_space) == 1: - value_from_space = curr_gene_space[0] - if not self.suppress_warnings: warnings.warn(f"You set 'allow_duplicate_genes=False' but the space of the gene with index {gene_idx} has only a single value. Thus, duplicates are possible.") - # If the gene space has more than 1 value, then select a new one that is different from the current value. - else: - values_to_select_from = list(set(curr_gene_space) - set(solution)) - - if len(values_to_select_from) == 0: - # DEEP-DUPLICATE-REMOVAL-NEEDED - # Search by this phrase to find where deep duplicates removal should be applied. - - # Reaching this block means there is no value in the gene space of this gene to solve the duplicates. - # To solve the duplicate between the 2 genes, the solution is to change the value of a third gene that makes a room to solve the duplicate. - - if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but the gene space does not have enough values to prevent duplicates.") - - solution2 = self.solve_duplicates_deeply(solution) - if solution2 is None: - # Cannot solve duplicates. At the moment, we are changing the value of a third gene to solve the duplicates between 2 genes. - # Maybe a 4th, 5th, 6th, or even more genes need to be changed to solve the duplicates. - pass - else: - solution = solution2 - value_from_space = solution[gene_idx] - - else: - value_from_space = random.choice(values_to_select_from) - else: - # Selecting a value randomly from the global gene space in the 'gene_space' attribute. - if type(self.gene_space) is dict: - dtype = self.get_gene_dtype(gene_index=gene_idx) - - if dtype[0] in pygad.GA.supported_int_types: - if 'step' in self.gene_space.keys(): - step = self.gene_space['step'] - else: - step = None - - value_from_space = self.unique_int_gene_from_range(solution=solution, - gene_index=gene_idx, - min_val=self.gene_space['low'], - max_val=self.gene_space['high'], - step=step, - mutation_by_replacement=True, - gene_type=dtype) - else: - # When the gene_space is assigned a dict object, then it specifies the lower and upper limits of all genes in the space. - if 'step' in self.gene_space.keys(): - value_from_space = numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=1)[0] - else: - value_from_space = numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=1)[0] - else: - # If the space type is not of type dict, then a value is randomly selected from the gene_space attribute. - # Remove all the genes in the current solution from the gene_space. - # This only leaves the unique values that could be selected for the gene. - - # Before using the gene_space, use gene_space_unpacked instead of gene_space to make sure the numbers has the right data type and its values are rounded. - values_to_select_from = list(set(self.gene_space_unpacked[gene_idx]) - set(solution)) - - if len(values_to_select_from) == 0: - if not self.suppress_warnings: warnings.warn("You set 'allow_duplicate_genes=False' but the gene space does not have enough values to prevent duplicates.") - value_from_space = solution[gene_idx] - else: - value_from_space = random.choice(values_to_select_from) + """ + Returns a unique value for a specific gene based on its value space to resolve duplicates. - if value_from_space is None: - if build_initial_pop == True: - low = self.init_range_low - high = self.init_range_high - else: - low = self.random_mutation_min_val - high = self.random_mutation_max_val + Args: + solution (list): A solution containing genes with duplicate values. + gene_idx (int): The index of the gene that has a duplicate value. + gene_type (type): The data type of the gene (e.g., int, float). + num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. - value_from_space = numpy.random.uniform(low=low, - high=high, - size=1)[0] + Returns: + Any: A unique value for the gene, if one exists; otherwise, the original gene value. + """ - # Similar to the round_genes() method in the pygad module, - # Create a round_gene() method to round a single gene. - dtype = self.get_gene_dtype(gene_index=gene_idx) - if not dtype[1] is None: - value_from_space = numpy.round(dtype[0](value_from_space), - dtype[1]) - else: - value_from_space = dtype[0](value_from_space) - return value_from_space + return value_from_space def find_two_duplicates(self, solution, diff --git a/pygad/pygad.py b/pygad/pygad.py index df44926..7accdfa 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -460,15 +460,16 @@ def __init__(self, for initial_solution_idx, initial_solution in enumerate(self.initial_population): if self.gene_space is None: self.initial_population[initial_solution_idx], _, _ = self.solve_duplicate_genes_randomly(solution=initial_solution, - min_val=self.init_range_low, - max_val=self.init_range_high, - mutation_by_replacement=self.mutation_by_replacement, - gene_type=self.gene_type, - sample_size=100) + min_val=self.init_range_low, + max_val=self.init_range_high, + mutation_by_replacement=True, + gene_type=self.gene_type, + sample_size=100) else: self.initial_population[initial_solution_idx], _, _ = self.solve_duplicate_genes_by_space(solution=initial_solution, - gene_type=self.gene_type, - num_trials=10) + gene_type=self.gene_type, + sample_size=100, + mutation_by_replacement=True) # Change the data type and round all genes within the initial population. self.initial_population = self.change_population_dtype_and_round(initial_population) @@ -1374,272 +1375,86 @@ def initialize_population(self, # The population will have sol_per_pop chromosome where each chromosome has num_genes genes. self.pop_size = (self.sol_per_pop, self.num_genes) - if self.gene_space is None: - # Create the initial population randomly. - - # Create an empty population. - self.population = numpy.zeros(shape=self.pop_size) + # There are 4 steps to build the initial population: + # 1) Generate the population. + # 2) Change the data type and round the values. + # 3) Check for the constraints. + # 4) Solve duplicates if not allowed. - # Loop through the genes, randomly generate the values of a single gene across the entire population, and add the values of each gene to the population. - for gene_idx in range(self.num_genes): - range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) + # Create an empty population. + self.population = numpy.zeros(shape=self.pop_size) - # A vector of all values of this single gene across all solutions in the population. - gene_values = numpy.random.uniform(low=range_min, - high=range_max, - size=self.pop_size[0]) - # Adding the current gene values to the population. - self.population[:, gene_idx] = gene_values + # 1) Create the initial population either randomly or using the gene space. + if self.gene_space is None: + # Create the initial population randomly. - # Change the data type and round all genes within the initial population. - self.population = self.change_population_dtype_and_round(self.population) + # Set gene_value=None to consider generating values for the initial population instead of generating values for mutation. + # Loop through the genes, randomly generate the values of a single gene at a time, and insert the values of each gene to the population. + for sol_idx in range(self.sol_per_pop): + for gene_idx in range(self.num_genes): + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) + self.population[sol_idx, gene_idx] = self.generate_gene_value_randomly(range_min=range_min, + range_max=range_max, + gene_idx=gene_idx, + mutation_by_replacement=True, + gene_value=None, + sample_size=1, + step=1) - # Enforce the gene constraints as much as possible. - if gene_constraint is None: - pass - else: - # Note that gene_constraint is not validated yet. - # We have to set it as a property of the pygad.GA instance to retrieve without passing it as an additional parameter. - self.gene_constraint = gene_constraint - for sol_idx, solution in enumerate(self.population): - for gene_idx in range(self.num_genes): - # Check that a constraint is available for the gene and that the current value does not satisfy that constraint - if self.gene_constraint[gene_idx]: - if not self.gene_constraint[gene_idx](solution): - range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) - # While initializing the population, we follow a mutation by replacement approach. So, the original gene value is not needed. - random_values_filtered = self.get_valid_gene_constraint_values(range_min=range_min, - range_max=range_max, - gene_value=None, - gene_idx=gene_idx, - mutation_by_replacement=True, - solution=solution, - sample_size=100) - if random_values_filtered is None: - if not self.suppress_warnings: - warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} while creating the initial population.") - else: - self.population[sol_idx, gene_idx] = random.choice(random_values_filtered) + else: + # Generate the initial population using the gene_space. + for sol_idx in range(self.sol_per_pop): + for gene_idx in range(self.num_genes): + self.population[sol_idx, gene_idx] = self.generate_gene_value_from_space(gene_idx=gene_idx, + mutation_by_replacement=True, + gene_value=None, + sample_size=1) + + # 2) Change the data type and round all genes within the initial population. + self.population = self.change_population_dtype_and_round(self.population) + + # 3) Enforce the gene constraints as much as possible. + if gene_constraint is None: + pass + else: + # Note that gene_constraint is not validated yet. + # We have to set it as a property of the pygad.GA instance to retrieve without passing it as an additional parameter. + self.gene_constraint = gene_constraint + for sol_idx, solution in enumerate(self.population): + for gene_idx in range(self.num_genes): + # Check that a constraint is available for the gene and that the current value does not satisfy that constraint + if self.gene_constraint[gene_idx]: + if not self.gene_constraint[gene_idx](solution): + range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) + # While initializing the population, we follow a mutation by replacement approach. So, the original gene value is not needed. + values_filtered = self.get_valid_gene_constraint_values(range_min=range_min, + range_max=range_max, + gene_value=None, + gene_idx=gene_idx, + mutation_by_replacement=True, + solution=solution, + sample_size=100) + if values_filtered is None: + if not self.suppress_warnings: + warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} while creating the initial population.") + else: + self.population[sol_idx, gene_idx] = random.choice(values_filtered) - if allow_duplicate_genes == False: - for solution_idx in range(self.population.shape[0]): - # self.logger.info("Before", self.population[solution_idx]) + # 4) Solve duplicate genes. + if allow_duplicate_genes == False: + for solution_idx in range(self.population.shape[0]): + if self.gene_space is None: self.population[solution_idx], _, _ = self.solve_duplicate_genes_randomly(solution=self.population[solution_idx], min_val=self.init_range_low, max_val=self.init_range_high, mutation_by_replacement=True, gene_type=gene_type, sample_size=100) - - elif self.gene_space_nested: - if self.gene_type_single == True: - # Reaching this block means: - # 1) gene_space is nested (gene_space_nested is True). - # 2) gene_type is not nested (gene_type_single is True). - self.population = numpy.zeros(shape=self.pop_size, - dtype=self.gene_type[0]) - for sol_idx in range(self.sol_per_pop): - for gene_idx in range(self.num_genes): - - range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) - - if self.gene_space[gene_idx] is None: - - # The above problem is solved by keeping the None value in the gene_space parameter. This forces PyGAD to generate this value for each solution. - self.population[sol_idx, gene_idx] = numpy.asarray(numpy.random.uniform(low=range_min, - high=range_max, - size=1), - dtype=self.gene_type[0])[0] - elif type(self.gene_space[gene_idx]) in [numpy.ndarray, list, tuple, range]: - # Check if the gene space has None values. If any, then replace it with randomly generated values according to the 3 attributes init_range_low, init_range_high, and gene_type. - if type(self.gene_space[gene_idx]) is range: - temp_gene_space = self.gene_space[gene_idx] - else: - # Convert to list because tuple and range do not have copy(). - # We copy the gene_space to a temp variable to keep its original value. - # In the next for loop, the gene_space is changed. - # Later, the gene_space is restored to its original value using the temp variable. - temp_gene_space = list(self.gene_space[gene_idx]).copy() - - for idx, val in enumerate(self.gene_space[gene_idx]): - if val is None: - self.gene_space[gene_idx][idx] = numpy.asarray(numpy.random.uniform(low=range_min, - high=range_max, - size=1), - dtype=self.gene_type[0])[0] - # Find the difference between the current gene space and the current values in the solution. - unique_gene_values = list(set(self.gene_space[gene_idx]).difference( - set(self.population[sol_idx, :gene_idx]))) - if len(unique_gene_values) > 0: - self.population[sol_idx, gene_idx] = random.choice(unique_gene_values) - else: - # If there is no unique values, then we have to select a duplicate value. - self.population[sol_idx, gene_idx] = random.choice( - self.gene_space[gene_idx]) - - self.population[sol_idx, gene_idx] = self.gene_type[0]( - self.population[sol_idx, gene_idx]) - # Restore the gene_space from the temp_gene_space variable. - self.gene_space[gene_idx] = list( - temp_gene_space).copy() - elif type(self.gene_space[gene_idx]) is dict: - if 'step' in self.gene_space[gene_idx].keys(): - self.population[sol_idx, gene_idx] = numpy.asarray(numpy.random.choice(numpy.arange(start=self.gene_space[gene_idx]['low'], - stop=self.gene_space[gene_idx]['high'], - step=self.gene_space[gene_idx]['step']), - size=1), - dtype=self.gene_type[0])[0] - else: - self.population[sol_idx, gene_idx] = numpy.asarray(numpy.random.uniform(low=self.gene_space[gene_idx]['low'], - high=self.gene_space[gene_idx]['high'], - size=1), - dtype=self.gene_type[0])[0] - elif type(self.gene_space[gene_idx]) in GA.supported_int_float_types: - self.population[sol_idx, gene_idx] = self.gene_space[gene_idx] - else: - # There is no more options. - pass - else: - # Reaching this block means: - # 1) gene_space is nested (gene_space_nested is True). - # 2) gene_type is nested (gene_type_single is False). - self.population = numpy.zeros(shape=self.pop_size, - dtype=object) - for sol_idx in range(self.sol_per_pop): - for gene_idx in range(self.num_genes): - - range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) - - if type(self.gene_space[gene_idx]) in [numpy.ndarray, list, tuple, range]: - # Convert to list because tuple and range do not have copy(). - # We copy the gene_space to a temp variable to keep its original value. - # In the next for loop, the gene_space is changed. - # Later, the gene_space is restored to its original value using the temp variable. - temp_gene_space = list(self.gene_space[gene_idx]).copy() - - # Check if the gene space has None values. If any, then replace it with randomly generated values according to the 3 attributes init_range_low, init_range_high, and gene_type. - for idx, val in enumerate(self.gene_space[gene_idx]): - if val is None: - self.gene_space[gene_idx][idx] = numpy.asarray(numpy.random.uniform(low=range_min, - high=range_max, - size=1), - dtype=self.gene_type[gene_idx][0])[0] - - self.population[sol_idx, gene_idx] = random.choice(self.gene_space[gene_idx]) - self.population[sol_idx, gene_idx] = self.gene_type[gene_idx][0](self.population[sol_idx, gene_idx]) - # Restore the gene_space from the temp_gene_space variable. - self.gene_space[gene_idx] = temp_gene_space.copy() - elif type(self.gene_space[gene_idx]) is dict: - if 'step' in self.gene_space[gene_idx].keys(): - self.population[sol_idx, gene_idx] = numpy.asarray(numpy.random.choice(numpy.arange(start=self.gene_space[gene_idx]['low'], - stop=self.gene_space[gene_idx]['high'], - step=self.gene_space[gene_idx]['step']), - size=1), - dtype=self.gene_type[gene_idx][0])[0] - else: - self.population[sol_idx, gene_idx] = numpy.asarray(numpy.random.uniform(low=self.gene_space[gene_idx]['low'], - high=self.gene_space[gene_idx]['high'], - size=1), - dtype=self.gene_type[gene_idx][0])[0] - elif self.gene_space[gene_idx] is None: - temp_gene_value = numpy.asarray(numpy.random.uniform(low=range_min, - high=range_max, - size=1), - dtype=self.gene_type[gene_idx][0])[0] - - self.population[sol_idx, gene_idx] = temp_gene_value.copy() - elif type(self.gene_space[gene_idx]) in GA.supported_int_float_types: - self.population[sol_idx, gene_idx] = self.gene_space[gene_idx] - else: - # There is no more options. - pass - else: - # Handle the non-nested gene_space. It can be assigned a numeric value, list, numpy.ndarray, or a dict. - if self.gene_type_single == True: - # Reaching this block means: - # 1) gene_space is not nested (gene_space_nested is False). - # 2) gene_type is not nested (gene_type_single is True). - - # Replace all the None values with random values using the init_range_low, init_range_high, and gene_type attributes. - for gene_idx, curr_gene_space in enumerate(self.gene_space): - - range_min, range_max = self.get_initial_population_range(gene_index=gene_idx) - - if curr_gene_space is None: - self.gene_space[gene_idx] = numpy.asarray(numpy.random.uniform(low=range_min, - high=range_max, - size=1), - dtype=self.gene_type[0])[0] - - # Creating the initial population by randomly selecting the genes' values from the values inside the 'gene_space' parameter. - if type(self.gene_space) is dict: - if 'step' in self.gene_space.keys(): - self.population = numpy.asarray(numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=self.pop_size), - dtype=self.gene_type[0]) - else: - self.population = numpy.asarray(numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=self.pop_size), - dtype=self.gene_type[0]) # A NumPy array holding the initial population. - else: - self.population = numpy.asarray(numpy.random.choice(self.gene_space, - size=self.pop_size), - dtype=self.gene_type[0]) # A NumPy array holding the initial population. - else: - # Reaching this block means: - # 1) gene_space is not nested (gene_space_nested is False). - # 2) gene_type is nested (gene_type_single is False). - - # Creating the initial population by randomly selecting the genes' values from the values inside the 'gene_space' parameter. - if type(self.gene_space) is dict: - # Create an empty population of dtype=object to support storing mixed data types within the same array. - self.population = numpy.zeros(shape=self.pop_size, - dtype=object) - # Loop through the genes, randomly generate the values of a single gene across the entire population, and add the values of each gene to the population. - for gene_idx in range(self.num_genes): - # Generate the values of the current gene across all solutions. - # A vector of all values of this single gene across all solutions in the population. - if 'step' in self.gene_space.keys(): - gene_values = numpy.asarray(numpy.random.choice(numpy.arange(start=self.gene_space['low'], - stop=self.gene_space['high'], - step=self.gene_space['step']), - size=self.pop_size[0]), - dtype=self.gene_type[gene_idx][0]) - else: - gene_values = numpy.asarray(numpy.random.uniform(low=self.gene_space['low'], - high=self.gene_space['high'], - size=self.pop_size[0]), - dtype=self.gene_type[gene_idx][0]) - # Adding the current gene values to the population. - self.population[:, gene_idx] = gene_values - else: - # Reaching this block means that the gene_space is not None or dict. - # It can be either range, numpy.ndarray, or list. - - # Create an empty population of dtype=object to support storing mixed data types within the same array. - self.population = numpy.zeros(shape=self.pop_size, dtype=object) - # Loop through the genes, randomly generate the values of a single gene across the entire population, and add the values of each gene to the population. - for gene_idx in range(self.num_genes): - # A vector of all values of this single gene across all solutions in the population. - gene_values = numpy.asarray(numpy.random.choice(self.gene_space, - size=self.pop_size[0]), - dtype=self.gene_type[gene_idx][0]) - # Adding the current gene values to the population. - self.population[:, gene_idx] = gene_values - - if self.gene_space is None: - pass - else: - if allow_duplicate_genes == False: - for sol_idx in range(self.population.shape[0]): - self.population[sol_idx], _, _ = self.solve_duplicate_genes_by_space(solution=self.population[sol_idx], + self.population[sol_idx], _, _ = self.solve_duplicate_genes_by_space(solution=self.population[solution_idx], gene_type=self.gene_type, - num_trials=10, - build_initial_pop=True) + sample_size=100, + mutation_by_replacement=True) # Keeping the initial population in the initial_population attribute. self.initial_population = self.population.copy() diff --git a/pygad/utils/__pycache__/__init__.cpython-310.pyc b/pygad/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d55b00c9a1268d2a87884daecf7c3448c7ced177 GIT binary patch literal 354 zcmYk2y-vj-6vv@ow|ehQe1xtY+FW%p#u%T##f^{`Jnnxs%rl%JKFTv8lU zPze-|Nl(p7Ey+wy%t_BL0xHNYs4U6I&(nn{)=f+<$;{7#sVyzZ%qfo9T*K%r1^_w# BC@}y4 delta 89 zcmcbrzh9p(pO=@50SJoDr)5;yY~(Xyv~kxDElw>e)=$jHO-)HpOwo5qEzT~$;b_;EXl~v(=Vt5DJm_=%qiC2Jc-d+3;-fP9{K

=g-xmUe+xmubIUP zsF~!mbWL%THZ1$<==CcEyJv-_IEO|m$d9?zuVh)jPI7EGM6SelAk?@1cl?c z_kn}p5SZeyTyU6O6$XgH&ZPwVxv=2zCs91Xk$hMyr}(ht$?Jwm)6Q_oG^&!9diV|9 z12P~BrfoGmy34!Wf~`d)KUUKjkY=wV!PqH2l)5@E=B0T*-oKWkTDhpx)3!%^*iFu` zN75%RHm5VJ5&g2e>r@lF?9&+2bAe?krdc? zLSXmfQL@V3#INEclJZm4R8Q3Ac9-K+2^d2_}^jE zBPfSBQs9pOR`K*UI1KK9yI>QHfPEka#sNOou>C4A=^;t>V=5eLe_u>$JD>CMf1NL@ z91V8ZwH0RXQ-j^wqG1|Voo=$H$_QDvKPVsFmz`ju=-Zx7-;T8Zy*sL5@IMLhR(I_IQ6g(6!GAA!yy!BGVTNDJZ{u9o3wGg6+d-(9?&G&uZytkWm^1e>`YQ4SP z+r%e*Dp(2ru5X=;r1dMdNo_rARm^-bo7a=3ecP$c>f>hBbV|9i7_-3y=&yv(w|1PYB;MJtjy`vg5MSe+r==H~<_1M%ew(Fd1c2u@K(~ zU6;HDvZq-@Rpc=dwxpa{D$@j;RtI{LxJvY|`^0tNEw|#SWm&OdK;kMKPW|<5tlNTnVpjCs)}A?W-4lbb`Ga{4Qtw<*e<@ zl=63Jk~boI2)V}J9DVE4+*b#M@bYAu_M>1xK-*30i1z{taEINChDnaSh>np3_AUB= zEOIOM%#(5r+Bf$1a(Fxh_5rIv7QLvyA@1nx{>tB-Zy3XY&L-@oaga3GN8<=t=0A)to}4@7Agnq^4gRsI ze>cRf3jFuDPoZwIn~AM)8*C-9F?0^;i@-GCj=h3t5m*A2fd=4VtMV@PJXs;Pm_IfC E6FaNFrT_o{ diff --git a/pygad/utils/__pycache__/nsga2.cpython-310.pyc b/pygad/utils/__pycache__/nsga2.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0f95b902c7b3663fe0d142179bd5b50b851402a2 GIT binary patch literal 7299 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 vPNsMo)umnTfQ^}WfAJv- zpY$%uo6aOJx(0AikTQm%qGCZXG$IdU>?0JTkBHb4cFFC44+J))VW)%7eO&FpejKms zGD*If#-9D~u3P-q-SJrOI~{C^J&2gv*`FcnC#x&EE~^dnv3buw$pY|E5DD_S!C3xf ziMfhIo|!1rOp1c$yw*VFo}Qm1VMn9!*mc<8#oi=SM7@FN3LS8qW|Wh|728oPL&b_R zO@}B72jpU9J%B$ast4p1)Fc71P@*PvdpE$S?)f_jDh!)}cPjwpB5D<*hyY%x72bH^ zoEBNwdG$zaHYb%wPf@Wz9ke<9kXPBRB1~t5jRLkJp{33^FIxc BmEiyY literal 0 HcmV?d00001 diff --git a/pygad/utils/__pycache__/parent_selection.cpython-310.pyc b/pygad/utils/__pycache__/parent_selection.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..491ba741c1657f376ed2ec7c603513cac8318feb GIT binary patch literal 14577 zcmeHOU5p%8R<6J9uCD%>p7D%hJ0V+}SXp=Q46(C>BzTi}<81IQjLh0uyd8(yn!eRD zHSVrz->P;pqiO-0tXPEdfY1tr5E{Rc2LvxjAR+O@0}@Z{!=e&GNFYHZ9uSZFkno** z>!-V?y?A#Mb|2jK+^)KHf6l#i&v(Cb?&+}AvJ`y&>E_MdKjez?Z*(*J(Q)$%{-Vc` zFr}w3m1#q@r@pQ*of+>b%y?JpX-vDWbSsBF>&BjEj}LZTwl#_UVYD@NxfjGvJvXZCM|pe`UK*>Fh_t%`5ne zZXxlMo(dT?lJPyIr%MTY2CJ|t?klXuOng^aomu#Zm@q12c$I`PkT3<6)a?%xPIFi$-)HQ_TbLkw)86&t zy~&O}=HWhOC%Ppj?$)jsc(LDihr1z1fzkLN-V1|GS$NYG)0mePlYDD&lDEbOsp&X= z;KzC$$ixJ5ehqN@oE< z5e^2?m_v>&t7zeBY`guwH;yCwBDLU-JTw`-SQMbHHZ$Q~v~SEvde zpoi33f5J&~?QuAs3|$&=DYs}en2elEX4x<@c05iE=0m{%cJgWVSXm!Zb1$CoU{TP^ zf$ehc9u!guRl_W2EtX9P5okDm!2JHqIPx0BfXX@_sA(;miW?u2u5wC{TVe6R*xT+l z(sg>(PEpqhqFwi5+98#6sEk7^Y3QXD3>HdO@kZnLAhpU#N-NQxJND9gQ6;U0JEY!e zg}cG7mm2+$YKn1otEmCB8}J$gNgFd$nzl1ZAqTqHc&GptiXnU#09jsz=l{o!cKr3 zooSCK_kaP=S@j6%J@rvVXnG~7995y)8q?p`Zt2tNv^F(&m1#Y$Wr*;?)QZ)8lMrCq zKyGEvT13q4!480^(OPB%$8S#L9CnDM|gP znY8Yys-h}^dgr-2=MocAwvu{=C{_2d2S*gV)Z5FEU`6?{$0WmTl6^K z;mE!~5V38aD>biJxm|kXx&1wWmjtX5uY90Iwz^8?c!(rF1d?#t9=VC+11+rpq<13z z2xSqVYETKS zDqdO;ej68+uc- z)HU^#Y65>O)liM!Y-@*)FXGR9Q**d8$4UMO?Gx@W6+Gl5&_WYDMBg_Uc!A&{f{`!- zJVXZ%IaVSDPoZacQrkCKB~eiq90s^leP5p%aea=T=)^_9Pim}|p^61m0cXi`z)zmV zy&)Kh>3#?6pxA>m;z;IwrLJ?6CW3GI`5xJtKcl+a>NYxF|>AQpPdNrSu9MZFaI)}(@S%C@Lb z9l172mHH}L6Wh1dq|N)3*uG8e&|acGSX5=jIR&E8k`fWokNyQoxpDsvT|XG9CuKTD z`!iCnV;eULV*mWKf|7M?-qpB~sPC%iqseMeuIg_q1C^Pq{+^jsuV7=J){@#$D{d#X zch!69)J%+{6>Rb*QGAtTGvpn!n3bttCsV&H2kQ58i4OB%m8gbL!iM{o|CbV$WvAZs6DnE}k%QunqX_L)AAZkhlf3#mlv=Q!N z!Ms(Qs^3~3#JdO@ZRZaskZe@&y>2z-uXb~v}sCB6inK>H1wmm zACAT^eVbaQZcZwP&o8Q6xo}Fg+qyIiVZBE$;UaHjySpYi!2yvUk0YA9X4}#ToeZGUDQ&IN*4p|x4V(Pg2br=tq@pP*OpO1+O^g@04+Oz> zZ}1g?Ke%QZHgGji>k9Npcny85mBgDY78W6?= zf@tK7fDuw5GX&^m7Tf;M!ywW2t)m8v5DU2m@DcPt7$Hwh8;O-vl7=usM!2quYlV*p zy2}7enatY((^*b*lHOSvi%)*`V*AI?cJcKVThO0nO$%okW zllL)5qd-j!_Z>eHJmdSYgeQfEeGk5j9k1_B;P4?Y3+w>$b!66BtVE!IP1-OhsB?=5UJFUjb5zGdpRl=Uz+C6~~R04da0%M>CZAXcuc3OFaoF5e! z5y(R?z$3~BYdKA5#45n78|W2IQQX(jPc<`RENV|kWrBGfD-k#3Gcr9AbD#@%=-|V|NclTtMZ_{X&5mklYx-kQUsB;V zl$of3wZ!8nL157ZGN-O2Y@x1yg-Q5i=>WEQ(1Cq5h8FU0x_>(hP1l8`b^nu^u#ZDR9E91E0G>}Cxx-xP>CB}(v3Y2m6_`rc z^l2Ms1K!__YS@cHkTg}HWPyZgv8rovxA8Gf0PFH-U)N?xZ# z7(l;7R|H<^+RU+tgM%@gz+_GrEDQkW=)UkKS&p!g1fyA9U^M(y>STe*T&27(Q}Pug zz149DzYdn4!(hz0LEr1O20o8sIC8~<1?tn29-jQ+G7j{36z6N(Db{vrQ~f8pjYxn+ zCe+c60jp-J8vYp{X`1noRmm-|rf{~-Fr}?Uru2hFm;+#%I`SORWJTv-2?Gi~3D;{4 zN6NycieY!b5o-P*yxaPYvXKA#Sn?*>L1xN)%X7U|hEV*3&AEliC0x#aTfM zo&8mKo7R#lAeNZaiGe0OPPKSFbLaj!yh}CmEKTbK$oRGZxsU=WT*tQ+9>ZD_;2V5% z?uo&F3r|pHieox>ugqP*E}&A>Ms6!9RGu&6$f%`02)b3f_CkeF>Oy*rk&|3 zd}SSYzdHC{#rJBm3a}Kt!>lZ6m_3;#twlyn1!+j2;x0y9}f? z46r{eZ3MQK++^mhtS$SClUN9|v=%Y4tV6^PpoIW27;vxp0eCac__L4zcysMO58sCK zch}Alq9luNh{%JbRmHh~aXN>CJ?@DHxW6-@9$YkwfC8E+PUHs6c0e z^1MEGf#Z;b%)qsdvcf zz->B6%#Rmm!WwRb*(;oT)kQ%_SJo1vkX#;PCeM^S7P%_J5*AA$IDz?t0f;SyCd^Ka z5KK-*-jfag9p2Y2P2R|M=gK3@BHpv*faPE@LAe~|`-2!uL=ZLt+{JS(W&#{r3^enF z!NueoEF=_CP;+$bfEis{d#PAd=|M#kQ7L=w-OT4ek{|0R6Cb8neqIc@^Y^i2W=hso zJQUM`Rr>!O!jc=CM($E@TLZF4Jg%YKc{;YBTBV%|q> zL>Kw&M_DiVQ4#e<-sELES!(N?DU58PfcKl00+(T%{v^V27Crp#8z7W7u&ihBI7eWb z(7D5zJTj29D-4DQHV&wVJ|F~m7uexA_DB99={^qEV}DbI-~cRz-9Ge2fGdmeAL|{Z zhTcqxtg?uC%8O(FnE>e##6^0SZzP3JC3iIBtr5(69HxtK=%Fh>BDwq)g_-T@Jm4yb z;OFDn+aT%TCh&o@?hZmG;g4{GJTwZ8F*kN?3QB@g<3g7fhjp*~y0=%=t|JRqdyJsHY*fTY#=hUEX(~9Udt!3S%r=!VuEW(zs zd}BvAB}DjClv-b-uruL^OgFGEI>nYM0-Z2sLE6*StbkE@93m}=7eGa2h{S3RWCcAJ zrz8AVs6@&9Hx`+H>971cN~Wjor+4DWL>!*S0jUX>mfWYp_2|)zI69g^-UbuB#t44O26<4%|Q;)zZvr zQ-@1zT4B!{GE#QTdgXT6a7sIXrC z27-de{uEj-i8ieBQmVP6E3J%=~-gQWDlm4S5#wD*QX~s=OAbQcs9e zsRq+2d={1ovGrzt?u3)5oUIoDwP5yZNt4_);PBcn;PmUxZJb4k)}MzDJNP`>IFVSH zO+mN_;eG5!?CnosiPfklvNh2El5t0G3X5XBuqaIT%?Gw9wn5mh z6ombHM%Z7M(be|lJiOYzdeuIc?K9m4+d?ugtTx(a7~*)dP~xS8Ua+iOLm%1#L}zSa zuSl4b7EaD(B`2rNRB~_$_nDa^W^xqXr5$iGhW`&gCCFGNWl^otj$vNpb8P%sFEelO za>-?vSpYXd%f-PQVw7X|HfRI=CLwDl>y!vx4wr}qmp@U+%`yAyv@D7d<+hj`Xeq$P?6Vjv7+y0Zt4(He=lMLq2b8QYml?MggCUqQ7DC z(1EZ&6Z;9;G8c+n&c0x!4~>#6uAWxxEG_;qW9uIpCKPQ`9Es(M^n(nO+1dZ8FiO6h z86^)bkx#-B+2+^4$GRI2U}12Id*s(Cxk1SrlzfenU!&wEB_fJ}2)r~4#KD0e{X|A( zc=R!!|2*^OZ=z&x1KWwiy^)KbOa;67&sutGIpddmi{Qr{xl?82n+Wv!Y^gYuPtqDz zyQr{{ZxG<&G7_J%`7M(0ElPf!5{DAKNJPZdium!6Tc1M@@rf%x%|J?rE;b*Qb(CLM~w!1C*2^j@NI!FxkSkoN5rdA0c)qD)s$YYt4tzT?QKT&lRN#nikc1%3&0rbJ`_7>a4EY5xm&ZWiqT literal 0 HcmV?d00001 diff --git a/pygad/visualize/__pycache__/__init__.cpython-310.pyc b/pygad/visualize/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..4c367b5cc1c47805adcca3da71281298d99ffd67 GIT binary patch literal 259 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 Date: Sat, 5 Jul 2025 10:26:59 -0400 Subject: [PATCH 51/79] More tests --- .DS_Store | Bin 8196 -> 10244 bytes example2.py | 4 +- pygad/.DS_Store | Bin 0 -> 8196 bytes pygad/__pycache__/__init__.cpython-310.pyc | Bin 227 -> 0 bytes pygad/__pycache__/pygad.cpython-310.pyc | Bin 79681 -> 0 bytes pygad/helper/.DS_Store | Bin 0 -> 6148 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 282 -> 0 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 15104 -> 0 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 14559 -> 0 bytes pygad/helper/misc.py | 10 +- pygad/helper/unique.py | 91 +++++--- pygad/pygad.py | 10 +- pygad/utils/.DS_Store | Bin 0 -> 6148 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 354 -> 0 bytes .../__pycache__/crossover.cpython-310.pyc | Bin 6101 -> 0 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 17743 -> 0 bytes pygad/utils/__pycache__/nsga2.cpython-310.pyc | Bin 7299 -> 0 bytes .../parent_selection.cpython-310.pyc | Bin 14577 -> 0 bytes pygad/utils/crossover.py | 16 +- pygad/utils/mutation.py | 16 +- pygad/visualize/.DS_Store | Bin 0 -> 6148 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 259 -> 0 bytes .../__pycache__/plot.cpython-310.pyc | Bin 13852 -> 0 bytes test_gene_type.py | 214 ++++++++++++++++++ tests/test_gene_type.py | 214 ++++++++++++++++++ 25 files changed, 531 insertions(+), 44 deletions(-) create mode 100644 pygad/.DS_Store delete mode 100644 pygad/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/__pycache__/pygad.cpython-310.pyc create mode 100644 pygad/helper/.DS_Store delete mode 100644 pygad/helper/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/misc.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/unique.cpython-310.pyc create mode 100644 pygad/utils/.DS_Store delete mode 100644 pygad/utils/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/crossover.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/mutation.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/nsga2.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/parent_selection.cpython-310.pyc create mode 100644 pygad/visualize/.DS_Store delete mode 100644 pygad/visualize/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/visualize/__pycache__/plot.cpython-310.pyc create mode 100644 test_gene_type.py create mode 100644 tests/test_gene_type.py diff --git a/.DS_Store b/.DS_Store index e558d3d00f9ff8bdc903a1b088a6bd7b4b7c1fb6..64240825e515832f8bfe22b202781f0bfd9a3891 100644 GIT binary patch delta 240 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50$SA%sU^hRb_+%b|Zb5E_RE7$M zM21|30)`wQ+i0?bs0b5N>gEoCx2)2F3?)FZVjvmMkPc+!0dYK#SIJN?xj;ai*B5Bf z$Nyl!z_7VUY%1f%hIV!#24@~ delta 106 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aMAD7rCVH$S83WFCR;&1VJfv2K1V uHi=P?8K?vZB)EZuD@gUm!tczJ`Befr7$IgbOpfQ7y4hH?f)S{ppAi6078YIr diff --git a/example2.py b/example2.py index 7c2ed72..0bbcd75 100644 --- a/example2.py +++ b/example2.py @@ -28,12 +28,12 @@ def fitness_func(ga_instance, solution, solution_idx): allow_duplicate_genes=False, # gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)), gene_space=[range(0, 100), {"low": 0, "high": 100, 'step': 1}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))], - gene_constraint=[lambda x: x[0]>=95,lambda x: x[1]>=95,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], + gene_constraint=[lambda x: x[0]>=70,lambda x: x[1]>=70,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], ) print(ga_instance.initial_population) -# ga_instance.run() +ga_instance.run() # print(ga_instance.gene_space_unpacked) # print(ga_instance.population) diff --git a/pygad/.DS_Store b/pygad/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..8a926cf11cdeec628aff003864eb85651f231ff9 GIT binary patch literal 8196 zcmeHM&x_MQ6n@hmn}#A(P~2m{>!Mvz*kv!Jt_KfZtSF*VlQ!&zjY(mQ$R2xi>>#}AQ z+jA4B;7=6Mg!*{hqtO~$Iz$0cKok%KL;+FYZ%_cwY;BD#&wVp0wJ0D8{Fe&o{lP=g z*s-`Y$Xf?0P62>5bhDw2Jb-;XiyezggY1fHn(RSYS7lQSW!y3DX*#fDacR)FlQQn4 z?46a(P!!)Cejd|Fbqq=^3Wx&h3h3Ou$vJyONg;o~cp9gJAWj1?^q1-x%F$0Yw1}!S zfJahdeJio*bVB|nrg~js)|k0=MfIb^+o4kmDaHvyNaNz&E8*|B!7Llu^?V6NagrMR0H0%1~H~QU+G>wDHwjaEV!dbs{>v5V){3r>>><|q@ zP~N_Zl3_aQrqg7Yv2V`|xNgJkx9-g6`-km4@8Hp5&zm1U(9MSji-p^`efR#+^Zr@< zAxS@B`Y>hpIJ#lpuj1wTSCv*KJikXxA7BaFGFmxAchIV8y- zjSpcW`bc3>934p2&joUW$Mq0t!@^2p$@71;IY>hkxS9f0L*Lc;|C8JLM-Jy5kz$Kui;dSG%8 Nz%ocB3j9$8egOm*Q~Lk_ literal 0 HcmV?d00001 diff --git a/pygad/__pycache__/__init__.cpython-310.pyc b/pygad/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index a806e2b81e67ec11b18b41756b81ec76d7a3f16d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 227 zcmd1j<>g`k0=Ca-8BRd@F^GcG?{L(7F4DurrhF=k1tCtD$dN$i;rK)P{ayU2_}AprC7xP6)5N` zq-U07lqM+@6y;~7CYKb)fHlUXr{<-WWF{x(q~{j_733CFmSp7T=|U9iCMK5vjfAO< jkI&4@EQycTE2zB1VUwGmQks)$2Xa&~3y|Po;9vp(wJ1Em diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc deleted file mode 100644 index 30531f3315210e50eefcc49ccfa8eac695eafbd2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79681 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 RZ|T5%2pA#RBm=*|z&j>5H7WoA literal 0 HcmV?d00001 diff --git a/pygad/helper/__pycache__/__init__.cpython-310.pyc b/pygad/helper/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index dbca10c72f3f1a7f967cab21311c43ceb8412f0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 282 zcmYjKL2AP=5R_y)gx2|kuRhqcJ(g1Xf?je67zg24!d9^*RgweFAM%iVAy4r&r+gu& ztP%oQ*x6Zjc1A50F9_QG7AjSW{frY)W^)7nPKk~QVG%R90HWkZT-f3=(w>JU6^h)WL8!MEg^ngs{4<0US z_F2M5k|m9B?XXF-QGdL4w!Ar&4VT=By6PHbK+x;awMu4paaPq1EFao?YDuY(!cm&* WahiYiiVYqA#t>#_05OX3oLm7Ma!TO< diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc deleted file mode 100644 index e07f6cd83500112a3cb79c51df3c2e08087f648a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 15104 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 A4FCWD diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc deleted file mode 100644 index 97787498c12bf3454252376ecbfe5f919e842039..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14559 zcmeHOU5p#ob)G*ChqK%tt^Rf;MH$CQyfh@*QX0o`)=mUFn+R<-MkKX{s(>DGhPzym zGu%5vt#&aCg33*t2LWxL3IqmL4@IHA^r1jepgx3~ z^F_TMv^V>{(+$0T$9LA<$dM16t{<<5UgX4~6NR0dzT@?Kop#fWeP_)N{K#-L8jt+LvrviKR!=0~{eE0~3HRja7p2~YF%_tdL;#i*3L zlb-37ab;92@03^ZY}8BKYRapkZhF(+%zMh!a@Km*o5NkJQmPo<=e$GSJgzFDLl1*#mJ8!NggR}@oA{4jRuv^tca^)!pd70k zTEa_zN4usCtU)E#VTUju^`vrJT~G$Lrw^uZ-@dJS`VB+;D6!vCet30IO_U8Y zsd~m8bub;56YaK&JJwA_d@a!iGl@Q!4Xi{d-l!(! zq?*hmvx$+EywV+WFc%Q*>Eivw^vpZu!CW$zl=0qnGUb(zD}yPtn~844v%7Pil~jMO zc$Gw@o=~$tDBV?6W%GG@C#f!@#&a$cdp&N>c*9PnS~?bY%Kb0wwe4h|ANb5l(TJBK zryV#haaccYhkY3$yhZrx(V45Vze9hOpI+*9%?AG1SM1ciTY& z_noD7K&0t_;a0ob@5&)NVT%WhX|4Hun%r&=N6mGDe)qaBhVz{u>)zT!*1h%XlkLYY zEp+4hc0>5Rj@$IRn60yPJq$Y-{>8xKGmd>}Io+%=ooL7kEPdDac#B4Fj*ps_un}+f zd}oQuY&|?Tb{)Q(tpzo6mi+o!{S5X$KI61Hp&Qr6X+^wFji|kYwuQybRv}wGP?QXl zGYI4rkryPWP;a#%rrb;*$u39Nw-sX} z-8lBUJ!V*REF`awxF@PR7h6sc7VbDMBxlypJHu=;q$&I^_>x3vbDdbn8(*FA5n+<; zCQb)dgSi-_+}MfYcBccGJ|?{P<6$C(6_&FbE$r}&K?@MQX4vm|m_h7Z9}RmntjqV* zv?_Q-8yJv(OF(2QxM?$6@U5DeTKN9UxRB%csY4zMEjuw8C_lvC6{2%S=I3 zs77*HOi@uqkxmu3=R+FC{qrksyMs;JpC?$Z5P5mzd92R;IcIA&}~D z5VbwpZ_gvK(G9VQnRJ3MBjLn^q8<*H198;{1s8Pb9VrD!h(Sh>kb$`A^*ekfscpu~ z`3lr)hf<>%_O{a!wgq3N)U2r<`EgoeYN_7wgLG!ij~l!`9xYI6aJd-ud%aMAS+OH= zgO3@X&nPW13`n)_Tu2X(x1u+r^n~n*-($Kh5q2B0$5YueNpJG$AtDE^jv8C8Ajuk~ z*05$!jb;Vu-jbWR&;E&N+Qcg4ek13WRBfb(vwpe9jOWvNFl1vmSUv~oajX{Z;oX~< z`iPIy*Xj+oXRn54NmoTkntSGN6I@TnN?(!n0N+ z?vB2T!djfy7F0v4s+L++XEZ}KRZFwfIn7kH;h*&@vpl-eRP(-NS?U*5?Z>u0uP>;! zzVoFk41em6?)O7FU@OfM%KSrj=m@`w|45+7;8AG;9+?14lklj_@W=u*vNC9N7I4H& zN&r2UXCwx|OzE0FC=s{{%DA7kC0IdMNreH1>E73#7GOLLu5gI!C(d+1fBYX~4d#0~ zp3gwN(g}$LBvABd?V4#dHU#$SG(rQT^~|K;qu1%ap04O#B zlypWuwRsf~qUDv#Rp$iZPitU3mhf+*I>l6&VpK3#*eo*A zS$+x!L=+2{dU1-e$AmN6o<#DaM|2n7kMwZ1`o3%+$9h;Q;2Z<<%`pK@AZ82wr}ctOtRK+IXW#*UblpzVrOJnB+CxBPa>}=N}UlGSFF2 z{vj%{600%Ha*9gC0H9=a19lp*mKZR*)f<}lB7~tHosNxN1c?$IzJL7#oqYd*y@)mp zN=fOG5}Ui_#JH=N_PcLyI~Rm-w}DJ9AZ|caA}6;%b|RtW#9*Rzc3lE8Yr;2!W_aIW{^ z{B;BJ{O;pVAI4;8x8MZ_mV@)xN!2=_mO*ZFX;hLuDI6-Eqv8uFQd5GD)NHPY@L<z4MgEGSo7=vsU8ZZgrq9XGSk+m0L+Uba_nFL6p zTCD9N1e=s`reHuOWsa( zRP@AT$5+MuN4;RWK{;=qocP$Of!De%u|14S_^oAl zcdx%^bZk~&3-~UqxS>atdpQF+0MBQKVn$up;}{IN#NW+Un@@CZn=jSgby^5;khxK? z7WR78ijLqXIipV!xZ{yL-kC;o$$k28l|3eCwbvIt94?tsI2q${nkNV_j{G;jmZOer zH{LIrcis7ZK;g|$+>jyF356|DSX9`PedXA*jg*bC?Em{{84F&x>sZRM`N)1WcA>E| zjRcsOrDBc>N=CTM-o9ETJ+j|vnvksr`t)j3Qpk=w3dP8Sw?zHbs5nc-$d^~AOD@QR zCw>pbERaCtpkgdo1xwKU*n-K&aR!a!4BF1AD>BYd82ZSeI3|B-Ne(dhVigWLGWawK zDI`EO$cRuW^CC9c;4>kEGlP$wv3rgTJ`O>Q+M_iYd?cx@#H9P|oGZuD;OmYD6*x#d z32^8@xf8Cq>u!V)Bk~y9@PSAiB*TFF2ZSF5eVzuAJh8ZId$)>0WUq6MXwk^XcGFemzk6XJ>3 zYDd0|=I`T`s?+l2pCL>XZ-M7m#kWxkvO-{hyg_w#-F%a36H!ujGkuF55aLPac#jws zWgdE~;Gy47ysXf>Z&ERFs{a7@mg!Wp(Q@RejF!^Q$-Sq17&U0>Af10`W(2xu2vLAS z8Y%!;^?@F%yBhuhc6LqvcHYg$WioNuUFm7Ppun0i1 z)j9a>^_He7szU#ll+BZyhNsg#cy)}O`@_}0=uyT`tIhZcZpF5UVkc}i`$G5u1@T*B z=YhF83-#y6^i!(Jq18Ng6y*T88wLoEIgG?j%&7!1q}0hQhH-2*I>ItwG=o)Ng$m-3 z&tY7or7f_ppO)CN;s6j_ozLS5t10Pzl>7y6BXHsk-%AY|XIiC_V(Nr%*d5A2c=&P} zZU^8r06NWFj1vr(9wt!Qh_}L#`4I)gfE0zQRuTJw>kxr${?{IA4=i&C<@b5DSu}}B ze-fuLm%XD)-yz3u;2%UJ89?15Zu(o8#EAdEOxEEoTm+x-)xZe8Ieb#uFk_>o0qEE0 z>24XRfJX4r@VSLI-vNKov&vls?JmZp4V$h#0}&tkrpOO{!?Z?Ho{slt(S}m-#@pz~ zge>1N1~iVUXJLffXg7_Rl(I47Rq(cbN8_h6-W0CXzACoj(k>+eQVZO>OV16=(cqDm z*694`@fc<0w|?-;&Cjig8=ZO7M3{g?H!QdF3c( zR=opf1wZ>MtTeqxJM;_e5bD{Jzs#Os2hj5hdOn6I-87Ob?Zl=zabL%8($mw}p^Dre zTuq}ta&t@tnm0wMn%J)j?^cEPYKnF#>%|{?^OZeqnNp$s_t0W$j9Rv<8NJuDQ8Qn~ z3oV_;ce^+Wvk79qMfhJy7zHr zha&X~iwCkt$I_ulc}nw}0!VHqt@cGnlv;oY2=a%L+uY*cL)S2jo%lNGc(7j6q3@Us%{4@nNn3mWtj_n zAGg{tvL8-T0?;wqkYBmsIefdP}9W~dPu%a5JhHa?Nk=F;~A!hSPOBz zfgE+@y@^G7fi#KuE)}fi%7G^}+&mM4&cYaP_}fuxy1byNl`mR4$0W&5fK=3At>cUD z6CEMy1gEzl2&}oiuX#Kq=u)cI6(y^Gl z-T~C++C0^%O%)4+477ctg6SbiReGnH*iT4-{{v{*hYZ!$W}w86P@Wq8C}RzO52}X6 zkwfznyJW&kXFiIZgc)y^}&o`!yYx&>I2)55z6W($Be3eN}u8WDIRG&&@|)0oL<#z-BfM#Pj6cf zwI9R%zA!|xv|xp)HDMeMKLpE(H32P@=s!@f+EW1y04R3xD}hA2OTQ7&f2Jh(aRyY^ zVFDibg#i24Z>zi1*7&K?0uaHEA1ng35BxIJHw62eKU1L=;J-(G8PrMx^L;fj zp*%bICg0- z3~k5q{9U-u06+_dmXG{N+#n`~m?ue0pUdm$D0duE8a6VV$NB(yKi=~Fz&S79m3eBe zXs(kmVt*82+1Cgj4OYnw1bN4C&C zE!FBTr|Qj`TFg>rg+B7RrS>vSuNiiGZ#d}Xm(V@9o6uB{8nSTzT64n zm5<&)C4c?E0Sf_|qk)MCml)I(pUtBDXD8qmo_-yp5VxqA$x2D)6kaAWtaMwbr6uOp zT4iKU%C3|1he&kj^)5Y}mzt{hG}W;%JVt-l8l_QdIww;jQCj 0: - new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(new_solution=new_solution, + new_solution, not_unique_indices, num_unsolved_duplicates = self.unique_genes_by_space(solution=new_solution, gene_type=gene_type, not_unique_indices=not_unique_indices, - num_trials=10, + sample_size=sample_size, + mutation_by_replacement=mutation_by_replacement, build_initial_pop=build_initial_pop) else: return new_solution, not_unique_indices, len(not_unique_indices) @@ -260,8 +261,11 @@ def select_unique_value(self, gene_values, solution, gene_index): values_to_select_from = list(set(list(gene_values)) - set(solution)) if len(values_to_select_from) == 0: + print("@@@@@@@@") + print(solution) + print(gene_values) # If there are no values, then keep the current gene value. - if not self.suppress_warnings: warnings.warn(f"'allow_duplicate_genes=False' but cannot find a unique value for the gene at index {gene_index}.") + if not self.suppress_warnings: warnings.warn(f"'allow_duplicate_genes=False' but cannot find a unique value for the gene at index {gene_index} with value {solution[gene_index]}.") selected_value = solution[gene_index] else: selected_value = random.choice(values_to_select_from) @@ -269,10 +273,11 @@ def select_unique_value(self, gene_values, solution, gene_index): return selected_value def unique_genes_by_space(self, - new_solution, + solution, gene_type, - not_unique_indices, - num_trials=10, + not_unique_indices, + mutation_by_replacement, + sample_size=100, build_initial_pop=False): """ @@ -280,10 +285,10 @@ def unique_genes_by_space(self, For each duplicate gene, a call is made to the `unique_gene_by_space()` function. Args: - new_solution (list): A solution containing genes with duplicate values. + solution (list): A solution containing genes with duplicate values. gene_type (type): The data type of the all the genes (e.g., int, float). not_unique_indices (list): The indices of genes with duplicate values. - num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. + sample_size (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. Returns: tuple: @@ -294,31 +299,33 @@ def unique_genes_by_space(self, num_unsolved_duplicates = 0 for duplicate_index in not_unique_indices: - temp_val = self.unique_gene_by_space(solution=new_solution, + temp_val = self.unique_gene_by_space(solution=solution, gene_idx=duplicate_index, gene_type=gene_type, - build_initial_pop=build_initial_pop, - num_trials=num_trials) + mutation_by_replacement=mutation_by_replacement, + sample_size=sample_size, + build_initial_pop=build_initial_pop) - if temp_val in new_solution: - # self.logger.info("temp_val, duplicate_index", temp_val, duplicate_index, new_solution) + if temp_val in solution: + # self.logger.info("temp_val, duplicate_index", temp_val, duplicate_index, solution) num_unsolved_duplicates = num_unsolved_duplicates + 1 - if not self.suppress_warnings: warnings.warn(f"Failed to find a unique value for gene with index {duplicate_index} whose value is {new_solution[duplicate_index]}. Consider adding more values in the gene space or use a wider range for initial population or random mutation.") + 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.") else: - new_solution[duplicate_index] = temp_val + solution[duplicate_index] = temp_val # Update the list of duplicate indices after each iteration. - _, unique_gene_indices = numpy.unique(new_solution, return_index=True) - not_unique_indices = set(range(len(new_solution))) - set(unique_gene_indices) + _, unique_gene_indices = numpy.unique(solution, return_index=True) + not_unique_indices = set(range(len(solution))) - set(unique_gene_indices) - return new_solution, not_unique_indices, num_unsolved_duplicates + return solution, not_unique_indices, num_unsolved_duplicates def unique_gene_by_space(self, solution, gene_idx, - gene_type, - build_initial_pop=False, - num_trials=10): + gene_type, + mutation_by_replacement, + sample_size=100, + build_initial_pop=False): """ Returns a unique value for a specific gene based on its value space to resolve duplicates. @@ -327,15 +334,47 @@ def unique_gene_by_space(self, solution (list): A solution containing genes with duplicate values. gene_idx (int): The index of the gene that has a duplicate value. gene_type (type): The data type of the gene (e.g., int, float). - num_trials (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. + sample_size (int): The maximum number of attempts to resolve duplicates for each gene. Only works for floating-point numbers. Returns: Any: A unique value for the gene, if one exists; otherwise, the original gene value. """ + # When gene_value is None, this forces the gene value generators to select a value for use by the initial population. + # Otherwise, it considers selecting a value for mutation. + if build_initial_pop: + gene_value = None + else: + gene_value = solution[gene_idx] + if self.gene_constraint and self.gene_constraint[gene_idx]: + # A unique value is created out of the values that satisfy the constraint. + values = self.get_valid_gene_constraint_values(range_min=None, + range_max=None, + gene_value=gene_value, + gene_idx=gene_idx, + mutation_by_replacement=mutation_by_replacement, + solution=solution, + sample_size=sample_size) + # If there is no value satisfying the constraint, then return the current gene value. + if values is None: + return solution[gene_idx] + else: + pass + else: + # There is no constraint for the current gene. Return the same range. + values = self.generate_gene_value(range_min=None, + range_max=None, + gene_value=gene_value, + gene_idx=gene_idx, + mutation_by_replacement=mutation_by_replacement, + sample_size=sample_size) - return value_from_space + selected_value = self.select_unique_value(gene_values=values, + solution=solution, + gene_index=gene_idx) + + return selected_value def find_two_duplicates(self, solution, diff --git a/pygad/pygad.py b/pygad/pygad.py index 7accdfa..6960f67 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -469,7 +469,8 @@ def __init__(self, self.initial_population[initial_solution_idx], _, _ = self.solve_duplicate_genes_by_space(solution=initial_solution, gene_type=self.gene_type, sample_size=100, - mutation_by_replacement=True) + mutation_by_replacement=True, + build_initial_pop=True) # Change the data type and round all genes within the initial population. self.initial_population = self.change_population_dtype_and_round(initial_population) @@ -1382,7 +1383,7 @@ def initialize_population(self, # 4) Solve duplicates if not allowed. # Create an empty population. - self.population = numpy.zeros(shape=self.pop_size) + self.population = numpy.zeros(shape=self.pop_size, dtype=object) # 1) Create the initial population either randomly or using the gene space. if self.gene_space is None: @@ -1436,7 +1437,7 @@ def initialize_population(self, sample_size=100) if values_filtered is None: if not self.suppress_warnings: - warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} while creating the initial population.") + warnings.warn(f"No value satisfied the constraint for the gene at index {gene_idx} with value {solution[gene_idx]} while creating the initial population.") else: self.population[sol_idx, gene_idx] = random.choice(values_filtered) @@ -1454,7 +1455,8 @@ def initialize_population(self, self.population[sol_idx], _, _ = self.solve_duplicate_genes_by_space(solution=self.population[solution_idx], gene_type=self.gene_type, sample_size=100, - mutation_by_replacement=True) + mutation_by_replacement=True, + build_initial_pop=True) # Keeping the initial population in the initial_population attribute. self.initial_population = self.population.copy() diff --git a/pygad/utils/.DS_Store b/pygad/utils/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..a364f0c0bd6445a262c5f5901cffdae298ec52af GIT binary patch literal 6148 zcmeHKu};G<5Pc3s3JOCd7Dj&o609syRR#toCMIY@gd&9o5sJi?f8+!B6nN*eDw+gF zgsQvf{Orp;J9)O^7=W9t?@xg#fRrwn9IzNMxi3DjBu4g$F7#311-HCT@HEJ_z<*Rg z<}M3!R$+zj^Y?aHH^s7UmdudlaYaA>48|I8hyu@O(DG{Vijpmv_df5v$II}DHP^|4 zUXi&PW4W6iD`j4b2eQsa^FMm|M|_e6pQt2H)?~|CO)q{MH7R?&uxVxrm;$E24l2Ms zTP)2KtuzHp0aIY3fP5c3T`-T>DTYr6i*N)WP8bfxy8J8($4AT~c8a`1b4n#D)x{%* zQ##{5=H(GPMWw^V!-tC_yLduzVRZKILpofpXr(D&3T!K|r_HJC{}(@>|F=ojG6hV5 zol?L}=GXHXUn%abgO`)N*3<9kVv^S>ZYZpTt(dv86(7@sG47>8%p-P+jL_^yAjn{a JDe$KXd;wTAS1$kn literal 0 HcmV?d00001 diff --git a/pygad/utils/__pycache__/__init__.cpython-310.pyc b/pygad/utils/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index d55b00c9a1268d2a87884daecf7c3448c7ced177..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 354 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 Ch~BdR diff --git a/pygad/utils/__pycache__/nsga2.cpython-310.pyc b/pygad/utils/__pycache__/nsga2.cpython-310.pyc deleted file mode 100644 index 0f95b902c7b3663fe0d142179bd5b50b851402a2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7299 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 vPNsMo)umnTfQ^}WfAJv- zpY$%uo6aOJx(0AikTQm%qGCZXG$IdU>?0JTkBHb4cFFC44+J))VW)%7eO&FpejKms zGD*If#-9D~u3P-q-SJrOI~{C^J&2gv*`FcnC#x&EE~^dnv3buw$pY|E5DD_S!C3xf ziMfhIo|!1rOp1c$yw*VFo}Qm1VMn9!*mc<8#oi=SM7@FN3LS8qW|Wh|728oPL&b_R zO@}B72jpU9J%B$ast4p1)Fc71P@*PvdpE$S?)f_jDh!)}cPjwpB5D<*hyY%x72bH^ zoEBNwdG$zaHYb%wPf@Wz9ke<9kXPBRB1~t5jRLkJp{33^FIxc BmEiyY diff --git a/pygad/utils/__pycache__/parent_selection.cpython-310.pyc b/pygad/utils/__pycache__/parent_selection.cpython-310.pyc deleted file mode 100644 index 491ba741c1657f376ed2ec7c603513cac8318feb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14577 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 diff --git a/pygad/utils/crossover.py b/pygad/utils/crossover.py index 8789135..aa954f0 100644 --- a/pygad/utils/crossover.py +++ b/pygad/utils/crossover.py @@ -74,7 +74,9 @@ def single_point_crossover(self, parents, offspring_size): else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring @@ -146,7 +148,9 @@ def two_points_crossover(self, parents, offspring_size): else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring def uniform_crossover(self, parents, offspring_size): @@ -213,7 +217,9 @@ def uniform_crossover(self, parents, offspring_size): else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring @@ -277,5 +283,7 @@ def scattered_crossover(self, parents, offspring_size): else: offspring[k], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[k], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index dbb4a6a..d023365 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -79,7 +79,9 @@ def mutation_by_space(self, offspring): if self.allow_duplicate_genes == False: offspring[offspring_idx], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[offspring_idx], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring def mutation_probs_by_space(self, offspring): @@ -117,7 +119,9 @@ def mutation_probs_by_space(self, offspring): if self.allow_duplicate_genes == False: offspring[offspring_idx], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[offspring_idx], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring def mutation_process_gene_value(self, @@ -544,7 +548,9 @@ def adaptive_mutation_by_space(self, offspring): if self.allow_duplicate_genes == False: offspring[offspring_idx], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[offspring_idx], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring def adaptive_mutation_randomly(self, offspring): @@ -678,7 +684,9 @@ def adaptive_mutation_probs_by_space(self, offspring): if self.allow_duplicate_genes == False: offspring[offspring_idx], _, _ = self.solve_duplicate_genes_by_space(solution=offspring[offspring_idx], gene_type=self.gene_type, - num_trials=10) + sample_size=100, + mutation_by_replacement=self.mutation_by_replacement, + build_initial_pop=False) return offspring def adaptive_mutation_probs_randomly(self, offspring): diff --git a/pygad/visualize/.DS_Store b/pygad/visualize/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..d6d23e9a0eb73b7451abf68d89614969ed97732f GIT binary patch literal 6148 zcmeHKF-`+P474Err&K)!M}G6zxW8l$m|3?dfTBRyB$>f?Ll==6r_L@kOERb3P=I&*|hm0QBev= z0V(jU0KX3nPV5SY#Q1a|@c7R48Igu@+$Dgm=3U{Ch!vP86_`}7D~2Z>`BrsZ;gFbg z^XQD{RA;Xnibr?kTa=r3iHcG{3XByv&gqi-|0VpF`~R4vnG}!$|4IR$t#8&Vyi)D0 wlb7RO+u--`FGFpVBa99Km~bmT-K%RHqpm9)5`&I>(1AJ#P#2jL_zML-0WRSuwg3PC literal 0 HcmV?d00001 diff --git a/pygad/visualize/__pycache__/__init__.cpython-310.pyc b/pygad/visualize/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index 4c367b5cc1c47805adcca3da71281298d99ffd67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 259 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 Date: Sat, 5 Jul 2025 16:11:24 -0400 Subject: [PATCH 52/79] Fix selecting a unique value from gene space --- .DS_Store | Bin 10244 -> 10244 bytes example2.py | 52 +- pygad/__pycache__/__init__.cpython-310.pyc | Bin 0 -> 205 bytes pygad/__pycache__/pygad.cpython-310.pyc | Bin 0 -> 79860 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 260 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 0 -> 16011 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 0 -> 14713 bytes pygad/helper/misc.py | 27 +- pygad/helper/unique.py | 15 +- pygad/pygad.py | 274 +- .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 332 bytes .../__pycache__/crossover.cpython-310.pyc | Bin 0 -> 6116 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 0 -> 17807 bytes pygad/utils/__pycache__/nsga2.cpython-310.pyc | Bin 0 -> 7277 bytes .../parent_selection.cpython-310.pyc | Bin 0 -> 14555 bytes pygad/utils/mutation.py | 1 + .../__pycache__/__init__.cpython-310.pyc | Bin 0 -> 237 bytes .../__pycache__/plot.cpython-310.pyc | Bin 0 -> 13830 bytes test_adaptive_mutation.py | 2393 +++++++++++++++++ test_allow_duplicate_genes.py | 634 +++++ test_crossover_mutation.py | 287 ++ test_gene_space_allow_duplicate_genes.py | 1139 ++++++++ test_lifecycle_callbacks_calls.py | 247 ++ test_number_fitness_function_calls.py | 597 ++++ test_save_solutions.py | 1289 +++++++++ test_stop_criteria.py | 234 ++ tests/test_save_solutions.py | 2 +- 27 files changed, 7022 insertions(+), 169 deletions(-) create mode 100644 pygad/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/__pycache__/pygad.cpython-310.pyc create mode 100644 pygad/helper/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/helper/__pycache__/misc.cpython-310.pyc create mode 100644 pygad/helper/__pycache__/unique.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/crossover.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/mutation.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/nsga2.cpython-310.pyc create mode 100644 pygad/utils/__pycache__/parent_selection.cpython-310.pyc create mode 100644 pygad/visualize/__pycache__/__init__.cpython-310.pyc create mode 100644 pygad/visualize/__pycache__/plot.cpython-310.pyc create mode 100644 test_adaptive_mutation.py create mode 100644 test_allow_duplicate_genes.py create mode 100644 test_crossover_mutation.py create mode 100644 test_gene_space_allow_duplicate_genes.py create mode 100644 test_lifecycle_callbacks_calls.py create mode 100644 test_number_fitness_function_calls.py create mode 100644 test_save_solutions.py create mode 100644 test_stop_criteria.py diff --git a/.DS_Store b/.DS_Store index 64240825e515832f8bfe22b202781f0bfd9a3891..24f42ca2d5ca16fd106016873d8cb448533b0e22 100644 GIT binary patch delta 1184 zcmZn(XbG6$&nUYwU^hRb>|`E+4Qz~-3=E7hn-2-Nv*=1PlrW?+6a&e4hD3%GAT9v1 zGa1T&qVWv545dI`B3LA!A#ZY-sH&+fkcZHb!;r&}&rl9jn*vl@0OVydB!hK;^rr(= z=7CgCULl~WFNLHtnV|@%uNVmP!FCm4vF(JI>SRt)TQ-KvK=*!}JVDf8a*e1k@5ldO zzyJaoljn%a`+}TKq(d0=fR3#6%*jtq%E?axnjio)I}&6*%+$#%#7yd$Qjvv#$`p~@ z4)S9f&{xR}l|Y;WWW|I1401ve&?(6b*+73G#EP+5%HYeuz|;)06ljAgk`48Fz`)D} z8kPzSrg)%bnLtT!K!OcU1Ip$BtpP<8Fj@#&1q?qFtHhA3Dh4_j64{`r$N@)qWT!3>oQ#SA43sSLRc`V3(V`3yM>r9gHnP*k76g&`3rmk4Bq z0{N*7MPSiHh77QZ6oz!5TnbnnNPRX?Z9YQ*knavun+L=t44Dkc430oE(}6NYKwb%h zC`fYv(ApB9G5J7c`ap*WO`anrFQ^C9TnTiZAJlnZQC6_%WDaqY$v;E|C*PFd-n>h~ UmwjWyA;!(@3QVlS*_%9!068mdtpET3 diff --git a/example2.py b/example2.py index 0bbcd75..7383b7c 100644 --- a/example2.py +++ b/example2.py @@ -1,35 +1,24 @@ import pygad import numpy +import random -function_inputs = [4,-2,3.5,5,-11,-4.7] -desired_output = 44 +def fitness_func(ga, solution, idx): + return random.random() -def fitness_func(ga_instance, solution, solution_idx): - output = numpy.sum(solution*function_inputs) - fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001) - return fitness - -num_genes = len(function_inputs) - -ga_instance = pygad.GA(num_generations=100, - num_parents_mating=10, - sol_per_pop=20, - num_genes=num_genes, - mutation_num_genes=6, - fitness_func=fitness_func, - init_range_low=1, - init_range_high=100, - # suppress_warnings=True, +ga_instance = pygad.GA(num_generations=1, + num_parents_mating=5, + sol_per_pop=10, + num_genes=10, + random_seed=123, + # mutation_type=None, + # crossover_type=None, random_mutation_min_val=1, random_mutation_max_val=100, - mutation_by_replacement=True, - gene_type=[float, 1], - save_solutions=True, + fitness_func=fitness_func, + gene_space=[30, None, 40, 50, None, 60, 70, None, None, None], + gene_type=int, allow_duplicate_genes=False, - # gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)), - gene_space=[range(0, 100), {"low": 0, "high": 100, 'step': 1}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))], - gene_constraint=[lambda x: x[0]>=70,lambda x: x[1]>=70,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], - ) + save_solutions=True) print(ga_instance.initial_population) @@ -37,3 +26,16 @@ def fitness_func(ga_instance, solution, solution_idx): # print(ga_instance.gene_space_unpacked) # print(ga_instance.population) + +""" +gene_space=[[0, 0], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], +""" diff --git a/pygad/__pycache__/__init__.cpython-310.pyc b/pygad/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a939de9e2e698a7f89fc506e58abc4558a6692ed GIT binary patch literal 205 zcmd1j<>g`kg6+F^GcG?{L(7F4DurrhF=k1tCtD$dN$i;rK)P{ayU2_}9y=!X^qCG-lIYq X;;_lhPbtkwwF6mS%mO4h7&w>!VCOT1 literal 0 HcmV?d00001 diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9f65c82877625b9ca5ff5549f774de1f75f5d3d6 GIT binary patch literal 79860 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 YpY{L9C&2-+={NkdPB&9rtDIc_fA%lhZ~y=R literal 0 HcmV?d00001 diff --git a/pygad/helper/__pycache__/__init__.cpython-310.pyc b/pygad/helper/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b92603d2b45f76f85219da6837cef6d3d8aed1e0 GIT binary patch literal 260 zcmYjJK~BRk5VW17sv!A8uerp)6@*#|skaIVxa4A0^(wZA?O;1dffw0_n=;J|0Z(@hGvZuR!EXa+7m-tN|R=&S<6soZ6@;bJyJL>bxz7soTp&K|g@^sorNu^R*D4;DdHnh#%sM6eK!s`CGt% z@09_GulxzZdvpWef`zCLo4#{iM`fO;sGQ~h{rud|h0s<;M1& literal 0 HcmV?d00001 diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1a1090339cce542c6c646c8ec72c6b5da7524d7 GIT binary patch literal 16011 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 Cip3oO literal 0 HcmV?d00001 diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..85a309de1f512292eff5678e0244ad6d7f8fae20 GIT binary patch literal 14713 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 D5SIny literal 0 HcmV?d00001 diff --git a/pygad/helper/misc.py b/pygad/helper/misc.py index db462af..f9a26e8 100644 --- a/pygad/helper/misc.py +++ b/pygad/helper/misc.py @@ -216,6 +216,7 @@ def get_initial_population_range(self, gene_index): def generate_gene_value_from_space(self, gene_idx, mutation_by_replacement, + solution=None, gene_value=None, sample_size=1): """ @@ -223,6 +224,7 @@ def generate_gene_value_from_space(self, It accepts: -gene_idx: The index of the gene in the solution. -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. + -solution (iterable, optional): The solution where we need to generate a gene. Needed if you are selecting a single value (sample_size=1) to select a value that respects the allow_duplicate_genes parameter instead of selecting a value randomly. If None, then the gene value is selected randomly. -gene_value (int, optional): The original gene value before applying mutation. Needed if you are calling this method to apply mutation. If None, then a sample is created from the gene space without being summed to the gene value. -sample_size (int, optional): The number of random values to generate. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For float data types, a None value returns only a single value. @@ -314,10 +316,13 @@ def generate_gene_value_from_space(self, high=self.gene_space['high'], size=sample_size) else: - # Change the data type and round the generated values. - # Pass a copy of the gene_space to avoid changing its value. + curr_gene_space = list(self.gene_space).copy() + for idx in range(len(curr_gene_space)): + if curr_gene_space[idx] is None: + curr_gene_space[idx] = numpy.random.uniform(low=range_min, + high=range_max) curr_gene_space = self.change_gene_dtype_and_round(gene_index=gene_idx, - gene_value=self.gene_space) + gene_value=curr_gene_space) if gene_value is None: # Just generate the value(s) without being added to the gene value specially when initializing the population. @@ -334,8 +339,19 @@ def generate_gene_value_from_space(self, # After removing the current gene value from the space, there are no more values. # Then keep the current gene value. value_from_space = gene_value + if sample_size > 1: + value_from_space = numpy.array([gene_value]) elif sample_size == 1: - value_from_space = random.choice(value_from_space) + if self.allow_duplicate_genes == True: + # Select a value randomly from the current gene space. + value_from_space = random.choice(value_from_space) + else: + # We must check if the selected value will respect the allow_duplicate_genes parameter. + # Instead of selecting a value randomly, we have to select a value that will be unique if allow_duplicate_genes=False. + # Only select a value from the current gene space that is, hopefully, unique. + value_from_space = self.select_unique_value(gene_values=value_from_space, + solution=solution, + gene_index=gene_idx) # The gene space might be [None, 1, 7]. # It might happen that the value None is selected. @@ -425,6 +441,7 @@ def generate_gene_value(self, gene_value, gene_idx, mutation_by_replacement, + solution=None, range_min=None, range_max=None, sample_size=1, @@ -435,6 +452,7 @@ def generate_gene_value(self, -gene_value: The original gene value before applying mutation. -gene_idx: The index of the gene in the solution. -mutation_by_replacement: A flag indicating whether mutation by replacement is enabled or not. The reason is to make this helper method usable while generating the initial population. In this case, mutation_by_replacement does not matter and should be considered False. + -solution (iterable, optional): The solution where we need to generate a gene. Needed if you are selecting a single value (sample_size=1) to select a value that respects the allow_duplicate_genes parameter instead of selecting a value randomly. If None, then the gene value is selected randomly. -range_min (int, optional): The minimum value in the range from which a value is selected. It must be passed for generating the gene value randomly because we cannot decide whether it is the range for the initial population (init_range_low and init_range_high) or mutation (random_mutation_min_val and random_mutation_max_val). -range_max (int, optional): The maximum value in the range from which a value is selected. It must be passed for generating the gene value randomly because we cannot decide whether it is the range for the initial population (init_range_low and init_range_high) or mutation (random_mutation_min_val and random_mutation_max_val). -sample_size: The number of random values to generate/select and return. It tries to generate a number of values up to a maximum of sample_size. But it is not always guaranteed because the total number of values might not be enough or the random generator creates duplicate random values. For int data types, it could be None to keep all the values. For float data types, a None value returns only a single value. @@ -456,6 +474,7 @@ def generate_gene_value(self, output = self.generate_gene_value_from_space(gene_value=gene_value, gene_idx=gene_idx, mutation_by_replacement=mutation_by_replacement, + solution=solution, sample_size=sample_size) return output diff --git a/pygad/helper/unique.py b/pygad/helper/unique.py index 6cbd44c..327a8bb 100644 --- a/pygad/helper/unique.py +++ b/pygad/helper/unique.py @@ -120,7 +120,7 @@ def solve_duplicate_genes_by_space(self, build_initial_pop=build_initial_pop) else: return new_solution, not_unique_indices, len(not_unique_indices) - + # DEEP-DUPLICATE-REMOVAL-NEEDED # Search by this phrase to find where deep duplicates removal should be applied. # If there exist duplicate genes, then changing either of the 2 duplicating genes (with indices 2 and 3) will not solve the problem. @@ -179,6 +179,7 @@ def unique_int_gene_from_range(self, range_max=max_val, gene_value=solution[gene_index], gene_idx=gene_index, + solution=solution, mutation_by_replacement=mutation_by_replacement, sample_size=None, step=step) @@ -237,6 +238,7 @@ def unique_float_gene_from_range(self, range_max=max_val, gene_value=solution[gene_index], gene_idx=gene_index, + solution=solution, mutation_by_replacement=mutation_by_replacement, sample_size=sample_size) @@ -253,17 +255,15 @@ def select_unique_value(self, gene_values, solution, gene_index): Args: gene_values (NumPy Array): An array of values from which a unique value should be selected. solution (list): A solution containing genes, potentially with duplicate values. + gene_index (int): The index of the gene for which to find a unique value. Returns: selected_gene: The new (hopefully unique) value of the gene. If no unique value can be found, the original gene value is returned. """ values_to_select_from = list(set(list(gene_values)) - set(solution)) - + if len(values_to_select_from) == 0: - print("@@@@@@@@") - print(solution) - print(gene_values) # If there are no values, then keep the current gene value. if not self.suppress_warnings: warnings.warn(f"'allow_duplicate_genes=False' but cannot find a unique value for the gene at index {gene_index} with value {solution[gene_index]}.") selected_value = solution[gene_index] @@ -367,6 +367,7 @@ def unique_gene_by_space(self, range_max=None, gene_value=gene_value, gene_idx=gene_idx, + solution=solution, mutation_by_replacement=mutation_by_replacement, sample_size=sample_size) @@ -438,6 +439,10 @@ def unpack_gene_space(self, if self.gene_type_single == True: # Change the data type. + for idx in range(len(gene_space_unpacked)): + if gene_space_unpacked[idx] is None: + gene_space_unpacked[idx] = numpy.random.uniform(low=range_min, + high=range_max) gene_space_unpacked = numpy.array(gene_space_unpacked, dtype=self.gene_type[0]) if not self.gene_type[1] is None: diff --git a/pygad/pygad.py b/pygad/pygad.py index 6960f67..727e9e4 100644 --- a/pygad/pygad.py +++ b/pygad/pygad.py @@ -73,7 +73,7 @@ def __init__(self, num_parents_mating: Number of solutions to be selected as parents in the mating pool. fitness_func: Accepts a function/method and returns the fitness value of the solution. In PyGAD 2.20.0, a third parameter is passed referring to the 'pygad.GA' instance. If method, then it must accept 4 parameters where the fourth one refers to the method's object. - fitness_batch_size: Added in PyGAD 2.19.0. Supports calculating the fitness in batches. If the value is 1 or None, then the fitness function is called for each invidiaul solution. If given another value X where X is neither 1 nor None (e.g. X=3), then the fitness function is called once for each X (3) solutions. + fitness_batch_size: Added in PyGAD 2.19.0. Supports calculating the fitness in batches. If the value is 1 or None, then the fitness function is called for each individual solution. If given another value X where X is neither 1 nor None (e.g. X=3), then the fitness function is called once for each X (3) solutions. initial_population: A user-defined initial population. It is useful when the user wants to start the generations with a custom initial population. It defaults to None which means no initial population is specified by the user. In this case, PyGAD creates an initial population using the 'sol_per_pop' and 'num_genes' parameters. An exception is raised if the 'initial_population' is None while any of the 2 parameters ('sol_per_pop' or 'num_genes') is also None. sol_per_pop: Number of solutions in the population. @@ -81,20 +81,20 @@ def __init__(self, init_range_low: The lower value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20 and higher. init_range_high: The upper value of the random range from which the gene values in the initial population are selected. It defaults to -4. Available in PyGAD 1.0.20. - # It is OK to set the value of any of the 2 parameters ('init_range_low' and 'init_range_high') to be equal, higher or lower than the other parameter (i.e. init_range_low is not needed to be lower than init_range_high). + # It is OK to set the value of the 2 parameters ('init_range_low' and 'init_range_high') to be equal, higher or lower than the other parameter (i.e. init_range_low is not needed to be lower than init_range_high). gene_type: The type of the gene. It is assigned to any of these types (int, numpy.int8, numpy.int16, numpy.int32, numpy.int64, numpy.uint, numpy.uint8, numpy.uint16, numpy.uint32, numpy.uint64, float, numpy.float16, numpy.float32, numpy.float64) and forces all the genes to be of that type. parent_selection_type: Type of parent selection. - keep_parents: If 0, this means no parent in the current population will be used in the next population. If -1, this means all parents in the current population will be used in the next population. If set to a value > 0, then the specified value refers to the number of parents in the current population to be used in the next population. Some parent selection operators such as rank selection, favor population diversity and therefore keeping the parents in the next generation can be beneficial. However, some other parent selection operators, such as roulette wheel selection (RWS), have higher selection pressure and keeping more than one parent in the next generation can seriously harm population diversity. This parameter have an effect only when the keep_elitism parameter is 0. Thanks to Prof. Fernando Jiménez Barrionuevo (http://webs.um.es/fernan) for editing this sentence. + keep_parents: If 0, this means no parent in the current population will be used in the next population. If -1, this means all parents in the current population will be used in the next population. If set to a value > 0, then the specified value refers to the number of parents in the current population to be used in the next population. Some parent selection operators such as rank selection, favor population diversity and therefore keeping the parents in the next generation can be beneficial. However, some other parent selection operators, such as roulette wheel selection (RWS), have higher selection pressure and keeping more than one parent in the next generation can seriously harm population diversity. This parameter have an effect only when the keep_elitism parameter is 0. Thanks to Prof. Fernando Jiménez (http://webs.um.es/fernan) for editing this sentence. K_tournament: When the value of 'parent_selection_type' is 'tournament', the 'K_tournament' parameter specifies the number of solutions from which a parent is selected randomly. - keep_elitism: Added in PyGAD 2.18.0. It can take the value 0 or a positive integer that satisfies (0 <= keep_elitism <= sol_per_pop). It defaults to 1 which means only the best solution in the current generation is kept in the next generation. If assigned 0, this means it has no effect. If assigned a positive integer K, then the best K solutions are kept in the next generation. It cannot be assigned a value greater than the value assigned to the sol_per_pop parameter. If this parameter has a value different than 0, then the keep_parents parameter will have no effect. + keep_elitism: Added in PyGAD 2.18.0. It can take the value 0 or a positive integer that satisfies (0 <= keep_elitism <= sol_per_pop). It defaults to 1 which means only the best solution in the current generation is kept in the next generation. If assigned 0, this means it has no effect. If assigned a positive integer K, then the best K solutions are kept in the next generation. It cannot be assigned a value greater than the value assigned to the sol_per_pop parameter. If this parameter has a value different from 0, then the keep_parents parameter will have no effect. - crossover_type: Type of the crossover opreator. If crossover_type=None, then the crossover step is bypassed which means no crossover is applied and thus no offspring will be created in the next generations. The next generation will use the solutions in the current population. + crossover_type: Type of the crossover operator. If crossover_type=None, then the crossover step is bypassed which means no crossover is applied and thus no offspring will be created in the next generations. The next generation will use the solutions in the current population. crossover_probability: The probability of selecting a solution for the crossover operation. If the solution probability is <= crossover_probability, the solution is selected. The value must be between 0 and 1 inclusive. - mutation_type: Type of the mutation opreator. If mutation_type=None, then the mutation step is bypassed which means no mutation is applied and thus no changes are applied to the offspring created using the crossover operation. The offspring will be used unchanged in the next generation. + mutation_type: Type of the mutation operator. If mutation_type=None, then the mutation step is bypassed which means no mutation is applied and thus no changes are applied to the offspring created using the crossover operation. The offspring will be used unchanged in the next generation. mutation_probability: The probability of selecting a gene for the mutation operation. If the gene probability is <= mutation_probability, the gene is selected. It accepts either a single value for fixed mutation or a list/tuple/numpy.ndarray of 2 values for adaptive mutation. The values must be between 0 and 1 inclusive. If specified, then no need for the 2 parameters mutation_percent_genes and mutation_num_genes. mutation_by_replacement: An optional bool parameter. It works only when the selected type of mutation is random (mutation_type="random"). In this case, setting mutation_by_replacement=True means replace the gene by the randomly generated value. If False, then it has no effect and random mutation works by adding the random value to the gene. @@ -108,13 +108,13 @@ def __init__(self, gene_constraint: It accepts a list of constraints for the genes. Each constraint is a Python function. Added in PyGAD 3.5.0. - on_start: Accepts a function/method to be called only once before the genetic algorithm starts its evolution. If function, then it must accept a single parameter representing the instance of the genetic algorithm. If method, then it must accept 2 parameters where the second one refers to the method's object. Added in PyGAD 2.6.0. - on_fitness: Accepts a function/method to be called after calculating the fitness values of all solutions in the population. If function, then it must accept 2 parameters: 1) a list of all solutions' fitness values 2) the instance of the genetic algorithm. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. - on_parents: Accepts a function/method to be called after selecting the parents that mates. If function, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one represents the selected parents. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. - on_crossover: Accepts a function/method to be called each time the crossover operation is applied. If function, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one represents the offspring generated using crossover. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. - on_mutation: Accepts a function/method to be called each time the mutation operation is applied. If function, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one represents the offspring after applying the mutation. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. - on_generation: Accepts a function/method to be called after each generation. If function, then it must accept a single parameter representing the instance of the genetic algorithm. If the function returned "stop", then the run() method stops without completing the other generations. If method, then it must accept 2 parameters where the second one refers to the method's object. Added in PyGAD 2.6.0. - on_stop: Accepts a function/method to be called only once exactly before the genetic algorithm stops or when it completes all the generations. If function, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one is a list of fitness values of the last population's solutions. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. + on_start: Accepts a function/method to be called only once before the genetic algorithm starts its evolution. If functioned, then it must accept a single parameter representing the instance of the genetic algorithm. If method, then it must accept 2 parameters where the second one refers to the method's object. Added in PyGAD 2.6.0. + on_fitness: Accepts a function/method to be called after calculating the fitness values of all solutions in the population. If functioned, then it must accept 2 parameters: 1) a list of all solutions' fitness values 2) the instance of the genetic algorithm. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. + on_parents: Accepts a function/method to be called after selecting the parents that mates. If functioned, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one represents the selected parents. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. + on_crossover: Accepts a function/method to be called each time the crossover operation is applied. If functioned, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one represents the offspring generated using crossover. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. + on_mutation: Accepts a function/method to be called each time the mutation operation is applied. If functioned, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one represents the offspring after applying the mutation. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. + on_generation: Accepts a function/method to be called after each generation. If functioned, then it must accept a single parameter representing the instance of the genetic algorithm. If the function returned "stop", then the run() method stops without completing the other generations. If method, then it must accept 2 parameters where the second one refers to the method's object. Added in PyGAD 2.6.0. + on_stop: Accepts a function/method to be called only once exactly before the genetic algorithm stops or when it completes all the generations. If functioned, then it must accept 2 parameters: the first one represents the instance of the genetic algorithm and the second one is a list of fitness values of the last population's solutions. If method, then it must accept 3 parameters where the third one refers to the method's object. Added in PyGAD 2.6.0. save_best_solutions: Added in PyGAD 2.9.0 and its type is bool. If True, then the best solution in each generation is saved into the 'best_solutions' attribute. Use this parameter with caution as it may cause memory overflow when either the number of generations or the number of genes is large. save_solutions: Added in PyGAD 2.15.0 and its type is bool. If True, then all solutions in each generation are saved into the 'solutions' attribute. Use this parameter with caution as it may cause memory overflow when either the number of generations, number of genes, or number of solutions in population is large. @@ -175,7 +175,7 @@ def __init__(self, numpy.random.seed(self.random_seed) random.seed(self.random_seed) - # If suppress_warnings is bool and its valud is False, then print warning messages. + # If suppress_warnings is bool and its value is False, then print warning messages. if type(suppress_warnings) is bool: self.suppress_warnings = suppress_warnings else: @@ -288,13 +288,6 @@ def __init__(self, self.valid_parameters = False raise TypeError(f"Type mismatch between the 2 parameters 'init_range_low' {type(init_range_low)} and 'init_range_high' {type(init_range_high)}.") elif type(init_range_low) in [list, tuple, numpy.ndarray]: - # The self.num_genes attribute is not created yet. - # if len(init_range_low) == self.num_genes: - # pass - # else: - # self.valid_parameters = False - # raise ValueError(f"The length of the 'init_range_low' parameter is {len(init_range_low)} which is different from the number of genes {self.num_genes}.") - # Get the number of genes before validating the num_genes parameter. if num_genes is None: if initial_population is None: @@ -410,7 +403,7 @@ def __init__(self, if initial_population is None: if (sol_per_pop is None) or (num_genes is None): self.valid_parameters = False - raise TypeError("Error creating the initial population:\n\nWhen the parameter 'initial_population' is None, then the 2 parameters 'sol_per_pop' and 'num_genes' cannot be None too.\nThere are 2 options to prepare the initial population:\n1) Assinging the initial population to the 'initial_population' parameter. In this case, the values of the 2 parameters sol_per_pop and num_genes will be deduced.\n2) Assign integer values to the 'sol_per_pop' and 'num_genes' parameters so that PyGAD can create the initial population automatically.") + raise TypeError("Error creating the initial population:\n\nWhen the parameter 'initial_population' is None, then the 2 parameters 'sol_per_pop' and 'num_genes' cannot be None too.\nThere are 2 options to prepare the initial population:\n1) Assigning the initial population to the 'initial_population' parameter. In this case, the values of the 2 parameters sol_per_pop and num_genes will be deduced.\n2) Assign integer values to the 'sol_per_pop' and 'num_genes' parameters so that PyGAD can create the initial population automatically.") elif (type(sol_per_pop) is int) and (type(num_genes) is int): # Validating the number of solutions in the population (sol_per_pop) if sol_per_pop <= 0: @@ -455,6 +448,9 @@ def __init__(self, self.valid_parameters = False raise TypeError(f"The values in the initial population can be integers or floats but the value ({initial_population[row_idx][col_idx]}) of type {type(initial_population[row_idx][col_idx])} found.") + # Change the data type and round all genes within the initial population. + self.initial_population = self.change_population_dtype_and_round(initial_population) + # Check if duplicates are allowed. If not, then solve any existing duplicates in the passed initial population. if self.allow_duplicate_genes == False: for initial_solution_idx, initial_solution in enumerate(self.initial_population): @@ -472,9 +468,6 @@ def __init__(self, mutation_by_replacement=True, build_initial_pop=True) - # Change the data type and round all genes within the initial population. - self.initial_population = self.change_population_dtype_and_round(initial_population) - # A NumPy array holding the initial population. self.population = self.initial_population.copy() # Number of genes in the solution. @@ -589,7 +582,7 @@ def __init__(self, if crossover_type is None: self.crossover = None elif inspect.ismethod(crossover_type): - # Check if the crossover_type is a method that accepts 4 paramaters. + # Check if the crossover_type is a method that accepts 4 parameters. if crossover_type.__code__.co_argcount == 4: # The crossover method assigned to the crossover_type parameter is validated. self.crossover = crossover_type @@ -597,7 +590,7 @@ def __init__(self, self.valid_parameters = False raise ValueError(f"When 'crossover_type' is assigned to a method, then this crossover method must accept 4 parameters:\n1) Expected to be the 'self' object.\n2) The selected parents.\n3) The size of the offspring to be produced.\n4) The instance from the pygad.GA class.\n\nThe passed crossover method named '{crossover_type.__code__.co_name}' accepts {crossover_type.__code__.co_argcount} parameter(s).") elif callable(crossover_type): - # Check if the crossover_type is a function that accepts 2 paramaters. + # Check if the crossover_type is a function that accepts 2 parameters. if crossover_type.__code__.co_argcount == 3: # The crossover function assigned to the crossover_type parameter is validated. self.crossover = crossover_type @@ -609,13 +602,13 @@ def __init__(self, raise TypeError(f"The expected type of the 'crossover_type' parameter is either callable or str but {type(crossover_type)} found.") else: # type crossover_type is str crossover_type = crossover_type.lower() - if (crossover_type == "single_point"): + if crossover_type == "single_point": self.crossover = self.single_point_crossover - elif (crossover_type == "two_points"): + elif crossover_type == "two_points": self.crossover = self.two_points_crossover - elif (crossover_type == "uniform"): + elif crossover_type == "uniform": self.crossover = self.uniform_crossover - elif (crossover_type == "scattered"): + elif crossover_type == "scattered": self.crossover = self.scattered_crossover else: self.valid_parameters = False @@ -627,7 +620,7 @@ def __init__(self, if crossover_probability is None: self.crossover_probability = None elif type(crossover_probability) in GA.supported_int_float_types: - if crossover_probability >= 0 and crossover_probability <= 1: + if 0 <= crossover_probability <= 1: self.crossover_probability = crossover_probability else: self.valid_parameters = False @@ -642,7 +635,7 @@ def __init__(self, if mutation_type is None: self.mutation = None elif inspect.ismethod(mutation_type): - # Check if the mutation_type is a method that accepts 3 paramater. + # Check if the mutation_type is a method that accepts 3 parameters. if (mutation_type.__code__.co_argcount == 3): # The mutation method assigned to the mutation_type parameter is validated. self.mutation = mutation_type @@ -650,7 +643,7 @@ def __init__(self, self.valid_parameters = False raise ValueError(f"When 'mutation_type' is assigned to a method, then it must accept 3 parameters:\n1) Expected to be the 'self' object.\n2) The offspring to be mutated.\n3) The instance from the pygad.GA class.\n\nThe passed mutation method named '{mutation_type.__code__.co_name}' accepts {mutation_type.__code__.co_argcount} parameter(s).") elif callable(mutation_type): - # Check if the mutation_type is a function that accepts 2 paramater. + # Check if the mutation_type is a function that accepts 2 parameters. if (mutation_type.__code__.co_argcount == 2): # The mutation function assigned to the mutation_type parameter is validated. self.mutation = mutation_type @@ -662,15 +655,15 @@ def __init__(self, raise TypeError(f"The expected type of the 'mutation_type' parameter is either callable or str but {type(mutation_type)} found.") else: # type mutation_type is str mutation_type = mutation_type.lower() - if (mutation_type == "random"): + if mutation_type == "random": self.mutation = self.random_mutation - elif (mutation_type == "swap"): + elif mutation_type == "swap": self.mutation = self.swap_mutation - elif (mutation_type == "scramble"): + elif mutation_type == "scramble": self.mutation = self.scramble_mutation - elif (mutation_type == "inversion"): + elif mutation_type == "inversion": self.mutation = self.inversion_mutation - elif (mutation_type == "adaptive"): + elif mutation_type == "adaptive": self.mutation = self.adaptive_mutation else: self.valid_parameters = False @@ -682,10 +675,10 @@ def __init__(self, if not (self.mutation_type is None): if mutation_probability is None: self.mutation_probability = None - elif (mutation_type != "adaptive"): + elif mutation_type != "adaptive": # Mutation probability is fixed not adaptive. if type(mutation_probability) in GA.supported_int_float_types: - if mutation_probability >= 0 and mutation_probability <= 1: + if 0 <= mutation_probability <= 1: self.mutation_probability = mutation_probability else: self.valid_parameters = False @@ -699,7 +692,7 @@ def __init__(self, if len(mutation_probability) == 2: for el in mutation_probability: if type(el) in GA.supported_int_float_types: - if el >= 0 and el <= 1: + if 0 <= el <= 1: pass else: self.valid_parameters = False @@ -709,7 +702,7 @@ def __init__(self, raise TypeError(f"Unexpected type for a value assigned to the 'mutation_probability' parameter. A numeric value is expected but ({el}) of type {type(el)} found.") if mutation_probability[0] < mutation_probability[1]: if not self.suppress_warnings: - warnings.warn(f"The first element in the 'mutation_probability' parameter is {mutation_probability[0]} which is smaller than the second element {mutation_probability[1]}. This means the mutation rate for the high-quality solutions is higher than the mutation rate of the low-quality ones. This causes high disruption in the high qualitiy solutions while making little changes in the low quality solutions. Please make the first element higher than the second element.") + warnings.warn(f"The first element in the 'mutation_probability' parameter is {mutation_probability[0]} which is smaller than the second element {mutation_probability[1]}. This means the mutation rate for the high-quality solutions is higher than the mutation rate of the low-quality ones. This causes high disruption in the high quality solutions while making little changes in the low quality solutions. Please make the first element higher than the second element.") self.mutation_probability = mutation_probability else: self.valid_parameters = False @@ -724,7 +717,7 @@ def __init__(self, if not (self.mutation_type is None): if mutation_num_genes is None: # The mutation_num_genes parameter does not exist. Checking whether adaptive mutation is used. - if (mutation_type != "adaptive"): + if mutation_type != "adaptive": # The percent of genes to mutate is fixed not adaptive. if mutation_percent_genes == 'default'.lower(): mutation_percent_genes = 10 @@ -740,7 +733,7 @@ def __init__(self, mutation_num_genes = 1 elif type(mutation_percent_genes) in GA.supported_int_float_types: - if (mutation_percent_genes <= 0 or mutation_percent_genes > 100): + if mutation_percent_genes <= 0 or mutation_percent_genes > 100: self.valid_parameters = False raise ValueError(f"The percentage of selected genes for mutation (mutation_percent_genes) must be > 0 and <= 100 but ({mutation_percent_genes}) found.\n") else: @@ -768,7 +761,7 @@ def __init__(self, mutation_percent_genes, dtype=numpy.uint32) for idx, el in enumerate(mutation_percent_genes): if type(el) in GA.supported_int_float_types: - if (el <= 0 or el > 100): + if el <= 0 or el > 100: self.valid_parameters = False raise ValueError(f"The values assigned to the 'mutation_percent_genes' must be > 0 and <= 100 but ({mutation_percent_genes}) found.\n") else: @@ -785,8 +778,8 @@ def __init__(self, mutation_num_genes[idx] = 1 if mutation_percent_genes[0] < mutation_percent_genes[1]: if not self.suppress_warnings: - warnings.warn(f"The first element in the 'mutation_percent_genes' parameter is ({mutation_percent_genes[0]}) which is smaller than the second element ({mutation_percent_genes[1]}).\nThis means the mutation rate for the high-quality solutions is higher than the mutation rate of the low-quality ones. This causes high disruption in the high qualitiy solutions while making little changes in the low quality solutions.\nPlease make the first element higher than the second element.") - # At this point outside the loop, all values of the parameter 'mutation_percent_genes' are validated. Eveyrthing is OK. + warnings.warn(f"The first element in the 'mutation_percent_genes' parameter is ({mutation_percent_genes[0]}) which is smaller than the second element ({mutation_percent_genes[1]}).\nThis means the mutation rate for the high-quality solutions is higher than the mutation rate of the low-quality ones. This causes high disruption in the high quality solutions while making little changes in the low quality solutions.\nPlease make the first element higher than the second element.") + # At this point outside the loop, all values of the parameter 'mutation_percent_genes' are validated. Everything is OK. else: self.valid_parameters = False raise ValueError(f"When mutation_type='adaptive', then the 'mutation_percent_genes' parameter must have only 2 elements but ({len(mutation_percent_genes)}) element(s) found.") @@ -795,13 +788,13 @@ def __init__(self, self.valid_parameters = False raise TypeError(f"Unexpected type of the 'mutation_percent_genes' parameter. When mutation_type='adaptive', then the 'mutation_percent_genes' parameter should exist and assigned a list/tuple/numpy.ndarray with 2 values but ({mutation_percent_genes}) found.") # The mutation_num_genes parameter exists. Checking whether adaptive mutation is used. - elif (mutation_type != "adaptive"): + elif mutation_type != "adaptive": # Number of genes to mutate is fixed not adaptive. if type(mutation_num_genes) in GA.supported_int_types: - if (mutation_num_genes <= 0): + if mutation_num_genes <= 0: self.valid_parameters = False raise ValueError(f"The number of selected genes for mutation (mutation_num_genes) cannot be <= 0 but ({mutation_num_genes}) found. If you do not want to use mutation, please set mutation_type=None\n") - elif (mutation_num_genes > self.num_genes): + elif mutation_num_genes > self.num_genes: self.valid_parameters = False raise ValueError(f"The number of selected genes for mutation (mutation_num_genes), which is ({mutation_num_genes}), cannot be greater than the number of genes ({self.num_genes}).\n") else: @@ -813,10 +806,10 @@ def __init__(self, if len(mutation_num_genes) == 2: for el in mutation_num_genes: if type(el) in GA.supported_int_types: - if (el <= 0): + if el <= 0: self.valid_parameters = False raise ValueError(f"The values assigned to the 'mutation_num_genes' cannot be <= 0 but ({el}) found. If you do not want to use mutation, please set mutation_type=None\n") - elif (el > self.num_genes): + elif el > self.num_genes: self.valid_parameters = False raise ValueError(f"The values assigned to the 'mutation_num_genes' cannot be greater than the number of genes ({self.num_genes}) but ({el}) found.\n") else: @@ -825,8 +818,8 @@ def __init__(self, # At this point of the loop, the current value assigned to the parameter 'mutation_num_genes' is validated. if mutation_num_genes[0] < mutation_num_genes[1]: if not self.suppress_warnings: - warnings.warn(f"The first element in the 'mutation_num_genes' parameter is {mutation_num_genes[0]} which is smaller than the second element {mutation_num_genes[1]}. This means the mutation rate for the high-quality solutions is higher than the mutation rate of the low-quality ones. This causes high disruption in the high qualitiy solutions while making little changes in the low quality solutions. Please make the first element higher than the second element.") - # At this point outside the loop, all values of the parameter 'mutation_num_genes' are validated. Eveyrthing is OK. + warnings.warn(f"The first element in the 'mutation_num_genes' parameter is {mutation_num_genes[0]} which is smaller than the second element {mutation_num_genes[1]}. This means the mutation rate for the high-quality solutions is higher than the mutation rate of the low-quality ones. This causes high disruption in the high quality solutions while making little changes in the low quality solutions. Please make the first element higher than the second element.") + # At this point outside the loop, all values of the parameter 'mutation_num_genes' are validated. Everything is OK. else: self.valid_parameters = False raise ValueError(f"When mutation_type='adaptive', then the 'mutation_num_genes' parameter must have only 2 elements but ({len(mutation_num_genes)}) element(s) found.") @@ -849,18 +842,18 @@ def __init__(self, # select_parents: Refers to a method that selects the parents based on the parent selection type specified in the parent_selection_type attribute. # Validating the selected type of parent selection: parent_selection_type if inspect.ismethod(parent_selection_type): - # Check if the parent_selection_type is a method that accepts 4 paramaters. - if (parent_selection_type.__code__.co_argcount == 4): - # population: Added in PyGAD 2.16.0. It should used only to support custom parent selection functions. Otherwise, it should be left to None to retirve the population by self.population. + # Check if the parent_selection_type is a method that accepts 4 parameters. + if parent_selection_type.__code__.co_argcount == 4: + # population: Added in PyGAD 2.16.0. It should use only to support custom parent selection functions. Otherwise, it should be left to None to retrieve the population by self.population. # The parent selection method assigned to the parent_selection_type parameter is validated. self.select_parents = parent_selection_type else: self.valid_parameters = False raise ValueError(f"When 'parent_selection_type' is assigned to a method, then it must accept 4 parameters:\n1) Expected to be the 'self' object.\n2) The fitness values of the current population.\n3) The number of parents needed.\n4) The instance from the pygad.GA class.\n\nThe passed parent selection method named '{parent_selection_type.__code__.co_name}' accepts {parent_selection_type.__code__.co_argcount} parameter(s).") elif callable(parent_selection_type): - # Check if the parent_selection_type is a function that accepts 3 paramaters. - if (parent_selection_type.__code__.co_argcount == 3): - # population: Added in PyGAD 2.16.0. It should used only to support custom parent selection functions. Otherwise, it should be left to None to retirve the population by self.population. + # Check if the parent_selection_type is a function that accepts 3 parameters. + if parent_selection_type.__code__.co_argcount == 3: + # population: Added in PyGAD 2.16.0. It should use only to support custom parent selection functions. Otherwise, it should be left to None to retrieve the population by self.population. # The parent selection function assigned to the parent_selection_type parameter is validated. self.select_parents = parent_selection_type else: @@ -872,33 +865,33 @@ def __init__(self, raise TypeError(f"The expected type of the 'parent_selection_type' parameter is either callable or str but {type(parent_selection_type)} found.") else: parent_selection_type = parent_selection_type.lower() - if (parent_selection_type == "sss"): + if parent_selection_type == "sss": self.select_parents = self.steady_state_selection - elif (parent_selection_type == "rws"): + elif parent_selection_type == "rws": self.select_parents = self.roulette_wheel_selection - elif (parent_selection_type == "sus"): + elif parent_selection_type == "sus": self.select_parents = self.stochastic_universal_selection - elif (parent_selection_type == "random"): + elif parent_selection_type == "random": self.select_parents = self.random_selection - elif (parent_selection_type == "tournament"): + elif parent_selection_type == "tournament": self.select_parents = self.tournament_selection - elif (parent_selection_type == "tournament_nsga2"): # Supported in PyGAD >= 3.2 + elif parent_selection_type == "tournament_nsga2": # Supported in PyGAD >= 3.2 self.select_parents = self.tournament_selection_nsga2 - elif (parent_selection_type == "nsga2"): # Supported in PyGAD >= 3.2 + elif parent_selection_type == "nsga2": # Supported in PyGAD >= 3.2 self.select_parents = self.nsga2_selection - elif (parent_selection_type == "rank"): + elif parent_selection_type == "rank": self.select_parents = self.rank_selection else: self.valid_parameters = False raise TypeError(f"Undefined parent selection type: {parent_selection_type}. \nThe assigned value to the 'parent_selection_type' parameter does not refer to one of the supported parent selection techniques which are: \n-sss (steady state selection)\n-rws (roulette wheel selection)\n-sus (stochastic universal selection)\n-rank (rank selection)\n-random (random selection)\n-tournament (tournament selection)\n-tournament_nsga2: (Tournament selection for NSGA-II)\n-nsga2: (NSGA-II parent selection).\n") # For tournament selection, validate the K value. - if (parent_selection_type == "tournament"): - if (K_tournament > self.sol_per_pop): + if parent_selection_type == "tournament": + if K_tournament > self.sol_per_pop: K_tournament = self.sol_per_pop if not self.suppress_warnings: warnings.warn(f"K of the tournament selection ({K_tournament}) should not be greater than the number of solutions within the population ({self.sol_per_pop}).\nK will be clipped to be equal to the number of solutions in the population (sol_per_pop).\n") - elif (K_tournament <= 0): + elif K_tournament <= 0: self.valid_parameters = False raise ValueError(f"K of the tournament selection cannot be <=0 but ({K_tournament}) found.\n") @@ -908,7 +901,7 @@ def __init__(self, if not (type(keep_parents) in GA.supported_int_types): self.valid_parameters = False raise TypeError(f"Incorrect type of the value assigned to the keep_parents parameter. The value ({keep_parents}) of type {type(keep_parents)} found but an integer is expected.") - elif (keep_parents > self.sol_per_pop or keep_parents > self.num_parents_mating or keep_parents < -1): + elif keep_parents > self.sol_per_pop or keep_parents > self.num_parents_mating or keep_parents < -1: self.valid_parameters = False raise ValueError(f"Incorrect value to the keep_parents parameter: {keep_parents}. \nThe assigned value to the keep_parent parameter must satisfy the following conditions: \n1) Less than or equal to sol_per_pop\n2) Less than or equal to num_parents_mating\n3) Greater than or equal to -1.") @@ -922,7 +915,7 @@ def __init__(self, if not (type(keep_elitism) in GA.supported_int_types): self.valid_parameters = False raise TypeError(f"Incorrect type of the value assigned to the keep_elitism parameter. The value ({keep_elitism}) of type {type(keep_elitism)} found but an integer is expected.") - elif (keep_elitism > self.sol_per_pop or keep_elitism < 0): + elif keep_elitism > self.sol_per_pop or keep_elitism < 0: self.valid_parameters = False raise ValueError(f"Incorrect value to the keep_elitism parameter: {keep_elitism}. \nThe assigned value to the keep_elitism parameter must satisfy the following conditions: \n1) Less than or equal to sol_per_pop\n2) Greater than or equal to 0.") @@ -931,13 +924,13 @@ def __init__(self, # Validate keep_parents. if self.keep_elitism == 0: # Keep all parents in the next population. - if (self.keep_parents == -1): + if self.keep_parents == -1: self.num_offspring = self.sol_per_pop - self.num_parents_mating # Keep no parents in the next population. - elif (self.keep_parents == 0): + elif self.keep_parents == 0: self.num_offspring = self.sol_per_pop # Keep the specified number of parents in the next population. - elif (self.keep_parents > 0): + elif self.keep_parents > 0: self.num_offspring = self.sol_per_pop - self.keep_parents else: self.num_offspring = self.sol_per_pop - self.keep_elitism @@ -946,15 +939,15 @@ def __init__(self, # In PyGAD 2.19.0, a method can be passed to the fitness function. If function is passed, then it accepts 2 parameters. If method, then it accepts 3 parameters. # In PyGAD 2.20.0, a new parameter is passed referring to the instance of the `pygad.GA` class. So, the function accepts 3 parameters and the method accepts 4 parameters. if inspect.ismethod(fitness_func): - # If the fitness is calculated through a method, not a function, then there is a fourth 'self` paramaters. - if (fitness_func.__code__.co_argcount == 4): + # If the fitness is calculated through a method, not a function, then there is a fourth 'self` parameters. + if fitness_func.__code__.co_argcount == 4: self.fitness_func = fitness_func else: self.valid_parameters = False raise ValueError(f"In PyGAD 2.20.0, if a method is used to calculate the fitness value, then it must accept 4 parameters\n1) Expected to be the 'self' object.\n2) The instance of the 'pygad.GA' class.\n3) A solution to calculate its fitness value.\n4) The solution's index within the population.\n\nThe passed fitness method named '{fitness_func.__code__.co_name}' accepts {fitness_func.__code__.co_argcount} parameter(s).") elif callable(fitness_func): - # Check if the fitness function accepts 2 paramaters. - if (fitness_func.__code__.co_argcount == 3): + # Check if the fitness function accepts 2 parameters. + if fitness_func.__code__.co_argcount == 3: self.fitness_func = fitness_func else: self.valid_parameters = False @@ -978,16 +971,16 @@ def __init__(self, # Check if the on_start exists. if not (on_start is None): if inspect.ismethod(on_start): - # Check if the on_start method accepts 2 paramaters. - if (on_start.__code__.co_argcount == 2): + # Check if the on_start method accepts 2 parameters. + if on_start.__code__.co_argcount == 2: self.on_start = on_start else: self.valid_parameters = False raise ValueError(f"The method assigned to the on_start parameter must accept only 2 parameters:\n1) Expected to be the 'self' object.\n2) The instance of the genetic algorithm.\nThe passed method named '{on_start.__code__.co_name}' accepts {on_start.__code__.co_argcount} parameter(s).") # Check if the on_start is a function. elif callable(on_start): - # Check if the on_start function accepts only a single paramater. - if (on_start.__code__.co_argcount == 1): + # Check if the on_start function accepts only a single parameter. + if on_start.__code__.co_argcount == 1: self.on_start = on_start else: self.valid_parameters = False @@ -1003,16 +996,16 @@ def __init__(self, if not (on_fitness is None): # Check if the on_fitness is a method. if inspect.ismethod(on_fitness): - # Check if the on_fitness method accepts 3 paramaters. - if (on_fitness.__code__.co_argcount == 3): + # Check if the on_fitness method accepts 3 parameters. + if on_fitness.__code__.co_argcount == 3: self.on_fitness = on_fitness else: self.valid_parameters = False raise ValueError(f"The method assigned to the on_fitness parameter must accept 3 parameters:\n1) Expected to be the 'self' object.\n2) The instance of the genetic algorithm.3) The fitness values of all solutions.\nThe passed method named '{on_fitness.__code__.co_name}' accepts {on_fitness.__code__.co_argcount} parameter(s).") # Check if the on_fitness is a function. elif callable(on_fitness): - # Check if the on_fitness function accepts 2 paramaters. - if (on_fitness.__code__.co_argcount == 2): + # Check if the on_fitness function accepts 2 parameters. + if on_fitness.__code__.co_argcount == 2: self.on_fitness = on_fitness else: self.valid_parameters = False @@ -1027,16 +1020,16 @@ def __init__(self, if not (on_parents is None): # Check if the on_parents is a method. if inspect.ismethod(on_parents): - # Check if the on_parents method accepts 3 paramaters. - if (on_parents.__code__.co_argcount == 3): + # Check if the on_parents method accepts 3 parameters. + if on_parents.__code__.co_argcount == 3: self.on_parents = on_parents else: self.valid_parameters = False raise ValueError(f"The method assigned to the on_parents parameter must accept 3 parameters:\n1) Expected to be the 'self' object.\n2) The instance of the genetic algorithm.\n3) The fitness values of all solutions.\nThe passed method named '{on_parents.__code__.co_name}' accepts {on_parents.__code__.co_argcount} parameter(s).") # Check if the on_parents is a function. elif callable(on_parents): - # Check if the on_parents function accepts 2 paramaters. - if (on_parents.__code__.co_argcount == 2): + # Check if the on_parents function accepts 2 parameters. + if on_parents.__code__.co_argcount == 2: self.on_parents = on_parents else: self.valid_parameters = False @@ -1051,16 +1044,16 @@ def __init__(self, if not (on_crossover is None): # Check if the on_crossover is a method. if inspect.ismethod(on_crossover): - # Check if the on_crossover method accepts 3 paramaters. - if (on_crossover.__code__.co_argcount == 3): + # Check if the on_crossover method accepts 3 parameters. + if on_crossover.__code__.co_argcount == 3: self.on_crossover = on_crossover else: self.valid_parameters = False raise ValueError(f"The method assigned to the on_crossover parameter must accept 3 parameters:\n1) Expected to be the 'self' object.\n2) The instance of the genetic algorithm.\n2) The offspring generated using crossover.\nThe passed method named '{on_crossover.__code__.co_name}' accepts {on_crossover.__code__.co_argcount} parameter(s).") # Check if the on_crossover is a function. elif callable(on_crossover): - # Check if the on_crossover function accepts 2 paramaters. - if (on_crossover.__code__.co_argcount == 2): + # Check if the on_crossover function accepts 2 parameters. + if on_crossover.__code__.co_argcount == 2: self.on_crossover = on_crossover else: self.valid_parameters = False @@ -1075,16 +1068,16 @@ def __init__(self, if not (on_mutation is None): # Check if the on_mutation is a method. if inspect.ismethod(on_mutation): - # Check if the on_mutation method accepts 3 paramaters. - if (on_mutation.__code__.co_argcount == 3): + # Check if the on_mutation method accepts 3 parameters. + if on_mutation.__code__.co_argcount == 3: self.on_mutation = on_mutation else: self.valid_parameters = False raise ValueError(f"The method assigned to the on_mutation parameter must accept 3 parameters:\n1) Expected to be the 'self' object.\n2) The instance of the genetic algorithm.\n2) The offspring after applying the mutation operation.\nThe passed method named '{on_mutation.__code__.co_name}' accepts {on_mutation.__code__.co_argcount} parameter(s).") # Check if the on_mutation is a function. elif callable(on_mutation): - # Check if the on_mutation function accepts 2 paramaters. - if (on_mutation.__code__.co_argcount == 2): + # Check if the on_mutation function accepts 2 parameters. + if on_mutation.__code__.co_argcount == 2: self.on_mutation = on_mutation else: self.valid_parameters = False @@ -1099,16 +1092,16 @@ def __init__(self, if not (on_generation is None): # Check if the on_generation is a method. if inspect.ismethod(on_generation): - # Check if the on_generation method accepts 2 paramaters. - if (on_generation.__code__.co_argcount == 2): + # Check if the on_generation method accepts 2 parameters. + if on_generation.__code__.co_argcount == 2: self.on_generation = on_generation else: self.valid_parameters = False raise ValueError(f"The method assigned to the on_generation parameter must accept 2 parameters:\n1) Expected to be the 'self' object.\n2) The instance of the genetic algorithm.\nThe passed method named '{on_generation.__code__.co_name}' accepts {on_generation.__code__.co_argcount} parameter(s).") # Check if the on_generation is a function. elif callable(on_generation): - # Check if the on_generation function accepts only a single paramater. - if (on_generation.__code__.co_argcount == 1): + # Check if the on_generation function accepts only a single parameter. + if on_generation.__code__.co_argcount == 1: self.on_generation = on_generation else: self.valid_parameters = False @@ -1123,16 +1116,16 @@ def __init__(self, if not (on_stop is None): # Check if the on_stop is a method. if inspect.ismethod(on_stop): - # Check if the on_stop method accepts 3 paramaters. - if (on_stop.__code__.co_argcount == 3): + # Check if the on_stop method accepts 3 parameters. + if on_stop.__code__.co_argcount == 3: self.on_stop = on_stop else: self.valid_parameters = False raise ValueError(f"The method assigned to the on_stop parameter must accept 3 parameters:\n1) Expected to be the 'self' object.\n2) The instance of the genetic algorithm.\n2) A list of the fitness values of the solutions in the last population.\nThe passed method named '{on_stop.__code__.co_name}' accepts {on_stop.__code__.co_argcount} parameter(s).") # Check if the on_stop is a function. elif callable(on_stop): - # Check if the on_stop function accepts 2 paramaters. - if (on_stop.__code__.co_argcount == 2): + # Check if the on_stop function accepts 2 parameters. + if on_stop.__code__.co_argcount == 2: self.on_stop = on_stop else: self.valid_parameters = False @@ -1212,7 +1205,7 @@ def validate_multi_stop_criteria(self, stop_word, number): raise ValueError(f"For format of a single criterion in the 'stop_criteria' parameter is 'word_number' but '{stop_criteria}' found.") elif type(stop_criteria) in [list, tuple, numpy.ndarray]: - # Remove duplicate criterira by converting the list to a set then back to a list. + # Remove duplicate criteria by converting the list to a set then back to a list. stop_criteria = list(set(stop_criteria)) for idx, val in enumerate(stop_criteria): if type(val) is str: @@ -1262,7 +1255,7 @@ def validate_multi_stop_criteria(self, stop_word, number): if parallel_processing[0] in ["process", "thread"]: if (type(parallel_processing[1]) in GA.supported_int_types and parallel_processing[1] > 0) or (parallel_processing[1] == 0) or (parallel_processing[1] is None): if parallel_processing[1] == 0: - # If the number of processes/threads is 0, this means no parallel processing is used. It is equivelant to setting parallel_processing=None. + # If the number of processes/threads is 0, this means no parallel processing is used. It is equivalent to setting parallel_processing=None. self.parallel_processing = None else: # Whether the second value is None or a positive integer. @@ -1289,7 +1282,7 @@ def validate_multi_stop_criteria(self, stop_word, number): # The number of completed generations. self.generations_completed = 0 - # At this point, all necessary parameters validation is done successfully and we are sure that the parameters are valid. + # At this point, all necessary parameters validation is done successfully, and we are sure that the parameters are valid. # Set to True when all the parameters passed in the GA class constructor are valid. self.valid_parameters = True @@ -1383,7 +1376,7 @@ def initialize_population(self, # 4) Solve duplicates if not allowed. # Create an empty population. - self.population = numpy.zeros(shape=self.pop_size, dtype=object) + self.population = numpy.empty(shape=self.pop_size, dtype=object) # 1) Create the initial population either randomly or using the gene space. if self.gene_space is None: @@ -1409,18 +1402,20 @@ def initialize_population(self, self.population[sol_idx, gene_idx] = self.generate_gene_value_from_space(gene_idx=gene_idx, mutation_by_replacement=True, gene_value=None, + solution=self.population[sol_idx], sample_size=1) # 2) Change the data type and round all genes within the initial population. self.population = self.change_population_dtype_and_round(self.population) + # Note that gene_constraint is not validated yet. + # We have to set it as a property of the pygad.GA instance to retrieve without passing it as an additional parameter. + self.gene_constraint = gene_constraint + # 3) Enforce the gene constraints as much as possible. - if gene_constraint is None: + if self.gene_constraint is None: pass else: - # Note that gene_constraint is not validated yet. - # We have to set it as a property of the pygad.GA instance to retrieve without passing it as an additional parameter. - self.gene_constraint = gene_constraint for sol_idx, solution in enumerate(self.population): for gene_idx in range(self.num_genes): # Check that a constraint is available for the gene and that the current value does not satisfy that constraint @@ -1452,11 +1447,14 @@ def initialize_population(self, gene_type=gene_type, sample_size=100) else: - self.population[sol_idx], _, _ = self.solve_duplicate_genes_by_space(solution=self.population[solution_idx], - gene_type=self.gene_type, - sample_size=100, - mutation_by_replacement=True, - build_initial_pop=True) + self.population[solution_idx], _, _ = self.solve_duplicate_genes_by_space(solution=self.population[solution_idx].copy(), + gene_type=self.gene_type, + sample_size=100, + mutation_by_replacement=True, + build_initial_pop=True) + + # Change the data type and round all genes within the initial population. + self.population = self.change_population_dtype_and_round(self.population) # Keeping the initial population in the initial_population attribute. self.initial_population = self.population.copy() @@ -1475,11 +1473,15 @@ def cal_pop_fitness(self): # It is used to return the parent index using the 'in' membership operator of Python lists. This is much faster than using 'numpy.where()'. if self.last_generation_parents is not None: last_generation_parents_as_list = self.last_generation_parents.tolist() + else: + last_generation_parents_as_list = [] # 'last_generation_elitism_as_list' is the list version of 'self.last_generation_elitism' # It is used to return the elitism index using the 'in' membership operator of Python lists. This is much faster than using 'numpy.where()'. if self.last_generation_elitism is not None: last_generation_elitism_as_list = self.last_generation_elitism.tolist() + else: + last_generation_elitism_as_list = [] pop_fitness = ["undefined"] * len(self.population) if self.parallel_processing is None: @@ -1790,7 +1792,7 @@ def run(self): if self.save_best_solutions: self.best_solutions.append(list(best_solution)) - # Note: Any code that has loop-dependant statements (e.g. continue, break, etc) must be kept inside the loop of the 'run()' method. It can be moved to another method to clean the run() method. + # Note: Any code that has loop-dependant statements (e.g. continue, break, etc.) must be kept inside the loop of the 'run()' method. It can be moved to another method to clean the run() method. # If the on_generation attribute is not None, then cal the callback function after the generation. if not (self.on_generation is None): r = self.on_generation(self) @@ -1830,6 +1832,10 @@ def run(self): reach_fitness_value = criterion[obj_idx + 1] elif len(criterion[1:]) == 1: reach_fitness_value = criterion[1] + else: + # Unexpected to be reached, but it is safer to handle it. + self.valid_parameters = False + raise ValueError(f"The number of values does not equal the number of objectives.") if max(self.last_generation_fitness[:, obj_idx]) >= reach_fitness_value: pass @@ -1838,7 +1844,7 @@ def run(self): break elif criterion[0] == "saturate": criterion[1] = int(criterion[1]) - if (self.generations_completed >= criterion[1]): + if self.generations_completed >= criterion[1]: # Single-objective problem. if type(self.last_generation_fitness[0]) in GA.supported_int_float_types: if (self.best_solutions_fitness[self.generations_completed - criterion[1]] - self.best_solutions_fitness[self.generations_completed - 1]) == 0: @@ -1929,7 +1935,7 @@ def run_loop_head(self, best_solution_fitness): 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. + This method must be only called 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. The objective of the 'run_select_parents()' method is to select the parents and call the callable on_parents() if defined. @@ -1994,7 +2000,7 @@ def run_select_parents(self, call_on_parents=True): 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}.") + raise ValueError(f"Size mismatch between the parents returned by on_parents() {on_parents_selected_parents.shape} and the expected parents shape {self.last_generation_parents.shape}.") 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)}.") @@ -2005,6 +2011,7 @@ def run_select_parents(self, call_on_parents=True): 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: + # Add this new instance attribute. 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}.") @@ -2016,7 +2023,7 @@ def run_select_parents(self, call_on_parents=True): def run_crossover(self): """ - This method must be only callled from inside the run() method. It is not meant for use by the user. + This method must be only called 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. The objective of the 'run_crossover()' method is to apply crossover and call the callable on_crossover() if defined. @@ -2083,7 +2090,7 @@ def run_crossover(self): def run_mutation(self): """ - This method must be only callled from inside the run() method. It is not meant for use by the user. + This method must be only called 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. The objective of the 'run_mutation()' method is to apply mutation and call the callable on_mutation() if defined. @@ -2133,7 +2140,7 @@ def run_mutation(self): def run_update_population(self): """ - This method must be only callled from inside the run() method. It is not meant for use by the user. + This method must be only called 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. The objective of the 'run_update_population()' method is to update the 'population' attribute after completing the processes of crossover and mutation. @@ -2148,13 +2155,13 @@ def run_update_population(self): # Update the population attribute according to the offspring generated. if self.keep_elitism == 0: # If the keep_elitism parameter is 0, then the keep_parents parameter will be used to decide if the parents are kept in the next generation. - if (self.keep_parents == 0): + if self.keep_parents == 0: self.population = self.last_generation_offspring_mutation - elif (self.keep_parents == -1): + elif self.keep_parents == -1: # Creating the new population based on the parents and offspring. self.population[0:self.last_generation_parents.shape[0],:] = self.last_generation_parents self.population[self.last_generation_parents.shape[0]:, :] = self.last_generation_offspring_mutation - elif (self.keep_parents > 0): + elif self.keep_parents > 0: parents_to_keep, _ = self.steady_state_selection(self.last_generation_fitness, num_parents=self.keep_parents) self.population[0:parents_to_keep.shape[0],:] = parents_to_keep @@ -2250,7 +2257,7 @@ def line_separator(line_length=line_length, line_character=line_character): def create_row(columns, line_length=line_length, fill_character=fill_character, split_percentages=None): filled_columns = [] - if split_percentages == None: + if split_percentages is None: split_percentages = [int(100/len(columns))] * 3 columns_lengths = [int((split_percentages[idx] * line_length) / 100) for idx in range(len(split_percentages))] @@ -2259,7 +2266,6 @@ def create_row(columns, line_length=line_length, fill_character=fill_character, extra_characters = columns_lengths[column_idx] - \ current_column_length filled_column = column + fill_character * extra_characters - filled_column = column + fill_character * extra_characters filled_columns.append(filled_column) return "".join(filled_columns) diff --git a/pygad/utils/__pycache__/__init__.cpython-310.pyc b/pygad/utils/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..d759c90d10eaed53530be5207e3626eadf84f7ae GIT binary patch literal 332 zcmYk2F;B!G6vu(Kch$Q~{2E=#sf&X#8jWto#N5I{NFgneLIc#Kei~Q5QdcJ@cPAf> zgOBj%_udQtM{v8{F@pHMRoy$BpDFnt1d?mm;Q~Y=i4E#8!#FVkCX6hc0cR{*I0w#I z4qUK8uAWh~{({@6!t==>yt0u8WmFTjvrs!~ybHm-Di2B7k5T+J3L9E+_EwzM=k@-v zTCvSAwL;co)F$}!GM^>MlN2EHhI6n9;LG_3^6S({@^eoQ0UA_vy^`QT?^Jk+ZlDj! xs;HYA(>kwX*FR0MbCxcH=rV|MuC0!o*TXcIm%WptQCApTL;AWc+TyfI@(W3FR22XK literal 0 HcmV?d00001 diff --git a/pygad/utils/__pycache__/crossover.cpython-310.pyc b/pygad/utils/__pycache__/crossover.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..1ca79420f7efcb810833950f6909b1f08e3f3424 GIT binary patch literal 6116 zcmeHLOK%)m6|SnTS68>4j$^kmnaR{ZCKHp`CgCN80EuRlVNeu}W(F1(s8sGMyUVVw z>fE{=$EI3}=(5w!L(B@WRj2R0 zb?&+6KI;3|C(!k6HYw62g>A4%{-__VHinx~_# zd4^}=SNEEpgXMWyLRWj(7^~dOYX|b%(ocRn)?lgW=_8pv5WO_fhb$Wp<+08!-s1A3 zcB+%!j(mu1SdtFp19@!lnbbHy9bS2Q@4&Dnx`&GrKeqK+{E}M=BuXKl2jYH)^Z*C zX*1WTdslCPj5(H zyO+?uP-#Phba6P_54B@@4V=6g_xIRb%P#(I4u@gjCw3A9NCE<`aO=X(PnPs#REGx=yH3vAN2Y)%WH_uA&MJ#e{iZ^q0XGJr>q#-OolTV3f_>k5%!{ zs$cW=eV>n5bj;O>hu7?zku9orFOIm2FSl^1Fy&1KsI599OnEG4n#cEs{u5DaWQ{Q1 z&D4Id)z4IN_|9XQwW0WgK;YIBkNW7DnZzMrBaSj-5Jb*BH$=g`FA9r;5y#5Mk+ZSq zFn<`jJ%4~5&ziI;4h@#I5_d2ReX%5uHFg?S*#e0!iFt_ZG{LYZU?^}yXBZEcyIH3) zfv$AiG7YN;z|#D|knaf_nI3O!;|OJXet>L*7o_}1$ z$-N2pvcX9Vx6;QRbTJa=1$*h zlF2)nM$3?7Mu64oVNR{w&=n`}?w6ITq9QvJ(qY6%SvTJfY!j?GmGv3COWudDX1nsN zVydRB%lc;>)l%l=SLAl%f~?4GnOfV5DO>VvV@^5WIImcWg_>IYEArewEklJaJ)M6k z4^(~Mp3u?s!H=S&Vcl zHAwFWy_vj|ngzXm4C>I!o}f3wGb)nN%NpqIcF~7s_6s5;&|5QYjxEre@#r*KG|(Fe zuXtn9I^b=mZXG@Jg5EUJ1HECMX;aXfUZXc6cr`6r3VPG1UiS{fS+xiqEANxHPiAqv zRZ!s^QQ^;I_BvMQ3|3J>c^2h)5FD(cw1B6JXAw`G4t3CBYuvhf8|`1Hv~xOKtkK~_ zFQ>z38XXcPT+PWVnUL11{VuO4?ubeW5B3zM2&kjrerQvscMj$qV4^baCOFh!2=(<(;*98Jv; z%p)LL&k-De8083}=QDDI3q_7#0z|_q6R771>aiTbB$zF7gu=SW5loRIlr2q8vx2V3 zeBK18B3no;%2GrQ^DFpg=5>nSsRT&>c} z=j9E#UJjJ^PXfwO6d<4;)N%3_0&L$!z)z9YWuyZ+SlRK2Q^=fSCqi+GbqgG`kw@g| zgYOmDL%0V}@q{+nA&p)<4Df!FA-4$lBq07Dg3M!RDIoMXWIpbm44D;7*07Oa^z#SE zY(~V_P7;q8V$u_FFf&B}j!1PXBAzY|*d#LjS^6%3B(Qbrft?H#mplcB8DAKe80qMSh9wI*hT15g?sEes>`iu5f}RoX}(lAb*9?034HvO;NwRSc{KH6@PVjHo|QYFS?Y6= zTW$Z<33N;u{m!yUSF{oCupB3|94EiUM!D@ccSmlRzhN59^;;yqP2zhbZjhJ+jG{zQ zBclr*ilq}ul}*W1%!YZ(e7Ai|yjfN=!*RU0=Qw#3EjWPsOf#05*K?P?i|;^6$gC<+ I_CKfm8&v7}9smFU literal 0 HcmV?d00001 diff --git a/pygad/utils/__pycache__/mutation.cpython-310.pyc b/pygad/utils/__pycache__/mutation.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f20aa55358a2d2329bd29bb5919132aac613e094 GIT binary patch literal 17807 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 literal 0 HcmV?d00001 diff --git a/pygad/utils/__pycache__/parent_selection.cpython-310.pyc b/pygad/utils/__pycache__/parent_selection.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..487dcd97223a51570e8593debde5f8d75e678df2 GIT binary patch literal 14555 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 literal 0 HcmV?d00001 diff --git a/pygad/utils/mutation.py b/pygad/utils/mutation.py index d023365..df9a78c 100644 --- a/pygad/utils/mutation.py +++ b/pygad/utils/mutation.py @@ -167,6 +167,7 @@ def mutation_process_gene_value(self, range_max=range_max, gene_value=solution[gene_idx], gene_idx=gene_idx, + solution=solution, mutation_by_replacement=self.mutation_by_replacement, sample_size=1) # Even that its name is singular, it might have a multiple values. diff --git a/pygad/visualize/__pycache__/__init__.cpython-310.pyc b/pygad/visualize/__pycache__/__init__.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..473b3f831e39a308d92cef5e00424e43e5c2b1cb GIT binary patch literal 237 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 m)~eND+nysX=wjf-qj>by=Yx^Hh@~Sqbkrz literal 0 HcmV?d00001 diff --git a/test_adaptive_mutation.py b/test_adaptive_mutation.py new file mode 100644 index 0000000..afac8d8 --- /dev/null +++ b/test_adaptive_mutation.py @@ -0,0 +1,2393 @@ +import pygad +import random +import numpy + +num_generations = 1 + +initial_population = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + +#### Define the fitness functions in the top-level of the module so that they are picklable and usable in the process-based parallel processing works. +#### If the functions are defined inside a class/method/function, they are not picklable and this error is raised: AttributeError: Can't pickle local object +#### Process-based parallel processing must have the used functions picklable. +def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + +def fitness_func_batch_single(ga, soluions, idxs): + return numpy.random.uniform(size=len(soluions)) + +def fitness_func_no_batch_multi(ga, solution, idx): + return [random.random(), random.random()] + +def fitness_func_batch_multi(ga, soluions, idxs): + f = [] + for sol in soluions: + f.append([random.random(), random.random()]) + return f + +def output_adaptive_mutation(gene_space=None, + gene_type=float, + num_genes=10, + mutation_by_replacement=False, + random_mutation_min_val=-1, + random_mutation_max_val=1, + init_range_low=-4, + init_range_high=4, + initial_population=None, + mutation_probability=None, + mutation_num_genes=None, + fitness_batch_size=None, + mutation_type="adaptive", + parent_selection_type='sss', + parallel_processing=None, + multi_objective=False): + + if fitness_batch_size is None or (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size == 1): + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + elif (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size > 1): + if multi_objective == True: + fitness_func = fitness_func_batch_multi + else: + fitness_func = fitness_func_batch_single + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + num_genes=num_genes, + gene_space=gene_space, + gene_type=gene_type, + initial_population=initial_population, + init_range_low=init_range_low, + init_range_high=init_range_high, + parent_selection_type=parent_selection_type, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + allow_duplicate_genes=True, + mutation_by_replacement=mutation_by_replacement, + save_solutions=True, + ## Use a static 'mutation_probability'. + ## An ambigius error in GitHub actions happen when using mutation_num_genes and mutation_probability. I do not know the reason. + # mutation_num_genes=mutation_num_genes, + mutation_probability=[0.2, 0.1], + mutation_type=mutation_type, + suppress_warnings=True, + fitness_batch_size=fitness_batch_size, + parallel_processing=parallel_processing, + random_seed=1) + + ga_instance.run() + + return None, ga_instance + +def test_adaptive_mutation(): + result, ga_instance = output_adaptive_mutation() + + # assert result == True + +def test_adaptive_mutation_gene_space(): + result, ga_instance = output_adaptive_mutation(gene_space=range(10)) + + # assert result == True + +def test_adaptive_mutation_int_gene_type(): + result, ga_instance = output_adaptive_mutation(gene_type=int) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type(): + result, ga_instance = output_adaptive_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type(): + result, ga_instance = output_adaptive_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + # assert result == True + +def test_adaptive_mutation_initial_population(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=1) + +def test_adaptive_mutation_fitness_batch_size_2(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=2) + +def test_adaptive_mutation_fitness_batch_size_3(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=3) + +def test_adaptive_mutation_fitness_batch_size_4(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=4) + +def test_adaptive_mutation_fitness_batch_size_5(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=5) + +def test_adaptive_mutation_fitness_batch_size_6(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=6) + +def test_adaptive_mutation_fitness_batch_size_7(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=7) + +def test_adaptive_mutation_fitness_batch_size_8(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=8) + +def test_adaptive_mutation_fitness_batch_size_9(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=9) + +def test_adaptive_mutation_fitness_batch_size_10(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=10) + +#### Single-Objective Mutation Probability +def test_adaptive_mutation_mutation_probability(): + result, ga_instance = output_adaptive_mutation(mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_probability(): + result, ga_instance = output_adaptive_mutation(gene_space=range(10), + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_probability(): + result, ga_instance = output_adaptive_mutation(gene_type=int, + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_probability(): + result, ga_instance = output_adaptive_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_probability(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_probability=[0.2, 0.1]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_probability(): + result, ga_instance = output_adaptive_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_probability(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_probability(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=1, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=2, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=3, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=4, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=5, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=6, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=7, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=8, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=9, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_probability(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=10, + mutation_probability=[0.2, 0.1]) + + +#### Single-Objective Mutation Number of Genes +def test_adaptive_mutation_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(gene_space=range(10), + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(gene_type=int, + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_num_genes=[6, 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_num_genes(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=1, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=2, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=3, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=4, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=5, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=6, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=7, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=8, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=9, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=10, + mutation_num_genes=[6, 4]) + +#### Multi-Objective Mutation Probability +def test_adaptive_mutation_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=range(10), + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=int, + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_probability=[0.2, 0.1]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_probability_multi_objective(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_multi_objective(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=1, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=2, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=3, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=4, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=5, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=6, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=7, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=8, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=9, + mutation_probability=[0.2, 0.1]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_probability_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=10, + mutation_probability=[0.2, 0.1]) + +#### Multi-Objective Mutation Number of Genes +def test_adaptive_mutation_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=range(10), + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=int, + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_num_genes=[6, 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_num_genes_multi_objective(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes_multi_objective(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=1, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=2, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=3, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=4, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=5, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=6, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=7, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=8, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=9, + mutation_num_genes=[6, 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes_multi_objective(): + result, ga_instance = output_adaptive_mutation(multi_objective=True,fitness_batch_size=10, + mutation_num_genes=[6, 4]) + +######## Parallel Processing +#### #### Threads + +#### Single-Objective Mutation Probability +def test_adaptive_mutation_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space=range(10), + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_type=int, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_probability_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=1, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=2, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=3, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=4, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=5, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=6, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=7, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=8, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=9, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_probability_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=10, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + +#### Single-Objective Mutation Number of Genes +def test_adaptive_mutation_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space=range(10), + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_type=int, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_num_genes_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=1, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=2, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=3, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=4, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=5, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=6, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=7, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=8, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=9, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=10, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +#### Multi-Objective Mutation Probability +def test_adaptive_mutation_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=range(10), + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=int, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_probability_multi_objective_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_multi_objective_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=1, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=2, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=3, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=4, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=5, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=6, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=7, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=8, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=9, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_probability_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=10, + mutation_probability=[0.2, 0.1], + parallel_processing=['thread', 4]) + +#### Multi-Objective Mutation Number of Genes +def test_adaptive_mutation_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=range(10), + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=int, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_num_genes_multi_objective_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes_multi_objective_parallel_processing_threads(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=1, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=2, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=3, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=4, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=5, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=6, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=7, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=8, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=9, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes_multi_objective_parallel_processing_threads(): + result, ga_instance = output_adaptive_mutation(multi_objective=True,fitness_batch_size=10, + mutation_num_genes=[6, 4], + parallel_processing=['thread', 4]) + + +#### #### Processes + +#### Single-Objective Mutation Probability +def test_adaptive_mutation_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space=range(10), + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_type=int, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_probability_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=1, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=2, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=3, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=4, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=5, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=6, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=7, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=8, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=9, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_probability_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=10, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + +#### Single-Objective Mutation Number of Genes +def test_adaptive_mutation_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space=range(10), + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_type=int, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_num_genes_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=1, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=2, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=3, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=4, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=5, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=6, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=7, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=8, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=9, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(fitness_batch_size=10, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +#### Multi-Objective Mutation Probability +def test_adaptive_mutation_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=range(10), + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=int, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_probability_multi_objective_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_multi_objective_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=1, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=2, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=3, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=4, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=5, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=6, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=7, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=8, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=9, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_probability_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=10, + mutation_probability=[0.2, 0.1], + parallel_processing=['process', 4]) + +#### Multi-Objective Mutation Number of Genes +def test_adaptive_mutation_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=range(10), + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_int_gene_type_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=int, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_gene_space_gene_type_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space={"low": 0, "high": 10}, + gene_type=[float, 2], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + # assert result == True + +def test_adaptive_mutation_nested_gene_type_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_nested_gene_space_nested_gene_type_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_mutation_num_genes_multi_objective_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes_multi_objective_parallel_processing_processes(): + global initial_population + result, ga_instance = output_adaptive_mutation(multi_objective=True, + initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + # assert result == True + +def test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=1, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=2, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=3, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=4, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=5, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=6, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=7, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=8, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True, + fitness_batch_size=9, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + +def test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes_multi_objective_parallel_processing_processes(): + result, ga_instance = output_adaptive_mutation(multi_objective=True,fitness_batch_size=10, + mutation_num_genes=[6, 4], + parallel_processing=['process', 4]) + + +if __name__ == "__main__": + #### Single-objective mutation_probability + print() + test_adaptive_mutation_mutation_probability() + print() + + test_adaptive_mutation_int_gene_type_mutation_probability() + print() + + test_adaptive_mutation_gene_space_mutation_probability() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_probability() + print() + + test_adaptive_mutation_nested_gene_space_mutation_probability() + print() + + test_adaptive_mutation_nested_gene_type_mutation_probability() + print() + + test_adaptive_mutation_initial_population_mutation_probability() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_probability() + print() + + #### Single-objective mutation_num_genes + print() + test_adaptive_mutation_mutation_num_genes() + print() + + test_adaptive_mutation_int_gene_type_mutation_num_genes() + print() + + test_adaptive_mutation_gene_space_mutation_num_genes() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_num_genes() + print() + + test_adaptive_mutation_nested_gene_space_mutation_num_genes() + print() + + test_adaptive_mutation_nested_gene_type_mutation_num_genes() + print() + + test_adaptive_mutation_initial_population_mutation_num_genes() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes() + print() + + #### Multi-objective mutation_probability + print() + test_adaptive_mutation_mutation_probability() + print() + + test_adaptive_mutation_int_gene_type_mutation_probability() + print() + + test_adaptive_mutation_gene_space_mutation_probability() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_probability() + print() + + test_adaptive_mutation_nested_gene_space_mutation_probability() + print() + + test_adaptive_mutation_nested_gene_type_mutation_probability() + print() + + test_adaptive_mutation_initial_population_mutation_probability() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_probability() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_probability() + print() + + + + ######## Parallel Processing + #### #### Threads + #### Single-objective mutation_probability + print() + test_adaptive_mutation_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_int_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_gene_space_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_nested_gene_space_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_nested_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_initial_population_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_probability_parallel_processing_threads() + print() + + #### Single-objective mutation_num_genes + print() + test_adaptive_mutation_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_int_gene_type_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_gene_space_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_nested_gene_space_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_nested_gene_type_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_initial_population_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes_parallel_processing_threads() + print() + + #### Multi-objective mutation_probability + print() + test_adaptive_mutation_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_int_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_gene_space_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_nested_gene_space_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_nested_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_initial_population_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_probability_parallel_processing_threads() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_probability_parallel_processing_threads() + print() + + #### #### Processes + #### Single-objective mutation_probability + print() + test_adaptive_mutation_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_int_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_gene_space_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_nested_gene_space_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_nested_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_initial_population_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_probability_parallel_processing_processes() + print() + + #### Single-objective mutation_num_genes + print() + test_adaptive_mutation_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_int_gene_type_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_gene_space_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_nested_gene_space_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_nested_gene_type_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_initial_population_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_num_genes_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_num_genes_parallel_processing_processes() + print() + + #### Multi-objective mutation_probability + print() + test_adaptive_mutation_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_int_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_gene_space_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_gene_space_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_nested_gene_space_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_nested_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_initial_population_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_initial_population_nested_gene_type_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_1_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_2_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_3_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_4_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_5_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_6_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_7_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_8_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_9_mutation_probability_parallel_processing_processes() + print() + + test_adaptive_mutation_fitness_batch_size_10_mutation_probability_parallel_processing_processes() + print() + + diff --git a/test_allow_duplicate_genes.py b/test_allow_duplicate_genes.py new file mode 100644 index 0000000..63037f3 --- /dev/null +++ b/test_allow_duplicate_genes.py @@ -0,0 +1,634 @@ +import pygad +import random +import numpy + +num_generations = 1 + +initial_population = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + +def number_duplicate_genes(gene_space=None, + gene_type=float, + num_genes=10, + mutation_by_replacement=False, + random_mutation_min_val=-1, + random_mutation_max_val=1, + init_range_low=-4, + init_range_high=4, + random_seed=123, + initial_population=None, + parent_selection_type='sss', + multi_objective=False): + + def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + + def fitness_func_no_batch_multi(ga, solution, idx): + return [random.random(), random.random()] + + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + num_genes=num_genes, + gene_space=gene_space, + gene_type=gene_type, + initial_population=initial_population, + parent_selection_type=parent_selection_type, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + allow_duplicate_genes=False, + mutation_by_replacement=mutation_by_replacement, + random_seed=random_seed, + save_solutions=True, + suppress_warnings=True) + + ga_instance.run() + + num_duplicates = 0 + for solution in ga_instance.solutions: + num = len(solution) - len(set(solution)) + if num != 0: + print(solution) + num_duplicates += num + + print(f"Number of duplicates is {num_duplicates}.") + return num_duplicates + +#### Single-Objective +def test_number_duplicates_default(): + num_duplicates = number_duplicate_genes() + + assert num_duplicates == 0 + +def test_number_duplicates_default_initial_population(): + num_duplicates = number_duplicate_genes(initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type_initial_population(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + initial_population=initial_population, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_initial_population(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement_initial_population(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space(): + num_duplicates = number_duplicate_genes(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space_initial_population(): + num_duplicates = number_duplicate_genes(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=range(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space_initial_population(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=range(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=numpy.arange(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space_initial_population(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=numpy.arange(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space(): + num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_initial_population(): + num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + + +# def test_number_duplicates_nested_gene_space_nested_gene_type(): + """ + This example causes duplicate genes that can only be solved by changing the values of a chain of genes. + Let's explain it using this solution: [0, 2, 3, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 6 at indices 5 and 6. + According to the gene space, none of these genes can has a different value that solves the duplicates. + -If the value of the gene at index 5 is changed from 6 to 5, then it causes another duplicate with the gene at index 4. + -If the value of the gene at index 6 is changed from 6 to 7, then it causes another duplicate with the gene at index 7. + The solution is to change a chain of genes that make a room to solve the duplicates between the 2 genes. + 1) Change the second gene from 2 to 1. + 2) Change the third gene from 3 to 2. + 3) Change the fourth gene from 4 to 3. + 4) Change the fifth gene from 5 to 4. + 5) Change the sixth gene from 6 to 5. This solves the duplicates. + But this is NOT SUPPORTED yet. + We support changing only a single gene that makes a room to solve the duplicates. + + Let's explain it using this solution: [1, 2, 2, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 2 at indices 1 and 2. + This is how the duplicates are solved: + 1) Change the first gene from 1 to 0. + 2) Change the second gene from 2 to 1. This solves the duplicates. + The result is [0, 1, 2, 4, 5, 6, 6, 7, 8, 9] + """ + # num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + # [1, 2], + # [2, 3], + # [3, 4], + # [4, 5], + # [5, 6], + # [6, 7], + # [7, 8], + # [8, 9], + # [9, 10]], + # gene_type=[int, int, int, int, int, int, int, int, int, int], + # num_genes=10) + + # assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_nested_gene_type_initial_population(): + num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=[int, int, int, int, int, int, int, int, int, int], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +#### Multi-Objective +def test_number_duplicates_default_multi_objective(): + num_duplicates = number_duplicate_genes() + + assert num_duplicates == 0 + +def test_number_duplicates_default_initial_population_multi_objective(): + num_duplicates = number_duplicate_genes(initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type_multi_objective(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_float_gene_type_initial_population_multi_objective(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_type=float, + num_genes=num_genes, + init_range_low=0, + init_range_high=1, + initial_population=initial_population, + random_mutation_min_val=0, + random_mutation_max_val=1) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_initial_population_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=False, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_int_gene_type_replacement_initial_population_multi_objective(): + num_genes = 10 + init_range_low = 0 + init_range_high = init_range_low + num_genes + random_mutation_min_val = 0 + random_mutation_max_val = random_mutation_min_val + num_genes + num_duplicates = number_duplicate_genes(gene_type=int, + mutation_by_replacement=True, + num_genes=num_genes, + init_range_low=init_range_low, + init_range_high=init_range_high, + initial_population=initial_population, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space_multi_objective(): + num_duplicates = number_duplicate_genes(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_single_gene_space_initial_population_multi_objective(): + num_duplicates = number_duplicate_genes(gene_space=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space_multi_objective(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=range(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_range_gene_space_initial_population_multi_objective(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=range(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space_multi_objective(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=numpy.arange(num_genes), + num_genes=num_genes) + + assert num_duplicates == 0 + +def test_number_duplicates_single_numpy_range_gene_space_initial_population_multi_objective(): + num_genes = 10 + num_duplicates = number_duplicate_genes(gene_space=numpy.arange(num_genes), + num_genes=num_genes, + initial_population=initial_population) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_multi_objective(): + num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10) + + assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_initial_population_multi_objective(): + num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=int, + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + + +# def test_number_duplicates_nested_gene_space_nested_gene_type_multi_objective(): + """ + This example causes duplicate genes that can only be solved by changing the values of a chain of genes. + Let's explain it using this solution: [0, 2, 3, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 6 at indices 5 and 6. + According to the gene space, none of these genes can has a different value that solves the duplicates. + -If the value of the gene at index 5 is changed from 6 to 5, then it causes another duplicate with the gene at index 4. + -If the value of the gene at index 6 is changed from 6 to 7, then it causes another duplicate with the gene at index 7. + The solution is to change a chain of genes that make a room to solve the duplicates between the 2 genes. + 1) Change the second gene from 2 to 1. + 2) Change the third gene from 3 to 2. + 3) Change the fourth gene from 4 to 3. + 4) Change the fifth gene from 5 to 4. + 5) Change the sixth gene from 6 to 5. This solves the duplicates. + But this is NOT SUPPORTED yet. + We support changing only a single gene that makes a room to solve the duplicates. + + Let's explain it using this solution: [1, 2, 2, 4, 5, 6, 6, 7, 8, 9] + It has 2 genes with the value 2 at indices 1 and 2. + This is how the duplicates are solved: + 1) Change the first gene from 1 to 0. + 2) Change the second gene from 2 to 1. This solves the duplicates. + The result is [0, 1, 2, 4, 5, 6, 6, 7, 8, 9] + """ + # num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + # [1, 2], + # [2, 3], + # [3, 4], + # [4, 5], + # [5, 6], + # [6, 7], + # [7, 8], + # [8, 9], + # [9, 10]], + # gene_type=[int, int, int, int, int, int, int, int, int, int], + # num_genes=10) + + # assert num_duplicates == 0 + +def test_number_duplicates_nested_gene_space_nested_gene_type_initial_population_multi_objective(): + num_duplicates = number_duplicate_genes(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + gene_type=[int, int, int, int, int, int, int, int, int, int], + num_genes=10, + initial_population=initial_population) + + assert num_duplicates == 0 + +if __name__ == "__main__": + #### Single-objective + print() + test_number_duplicates_default() + print() + test_number_duplicates_default_initial_population() + print() + + test_number_duplicates_float_gene_type() + print() + test_number_duplicates_float_gene_type_initial_population() + print() + + test_number_duplicates_int_gene_type() + print() + test_number_duplicates_int_gene_type_initial_population() + print() + + test_number_duplicates_int_gene_type_replacement() + print() + test_number_duplicates_int_gene_type_replacement_initial_population() + print() + + test_number_duplicates_single_gene_space() + print() + test_number_duplicates_single_gene_space_initial_population() + print() + + test_number_duplicates_single_range_gene_space() + print() + test_number_duplicates_single_range_gene_space_initial_population() + print() + + test_number_duplicates_single_numpy_range_gene_space() + print() + test_number_duplicates_single_numpy_range_gene_space_initial_population() + print() + + test_number_duplicates_nested_gene_space() + print() + test_number_duplicates_nested_gene_space_initial_population() + print() + + # This example causes duplicates that can only be solved by changing a chain of genes. + # test_number_duplicates_nested_gene_space_nested_gene_type() + # print() + test_number_duplicates_nested_gene_space_nested_gene_type_initial_population() + print() + + #### Multi-objective + print() + test_number_duplicates_default_initial_population_multi_objective() + print() + + test_number_duplicates_float_gene_type_multi_objective() + print() + test_number_duplicates_float_gene_type_initial_population_multi_objective() + print() + + test_number_duplicates_int_gene_type_multi_objective() + print() + test_number_duplicates_int_gene_type_initial_population_multi_objective() + print() + + test_number_duplicates_int_gene_type_replacement_multi_objective() + print() + test_number_duplicates_int_gene_type_replacement_initial_population_multi_objective() + print() + + test_number_duplicates_single_gene_space_multi_objective() + print() + test_number_duplicates_single_gene_space_initial_population_multi_objective() + print() + + test_number_duplicates_single_range_gene_space_multi_objective() + print() + test_number_duplicates_single_range_gene_space_initial_population_multi_objective() + print() + + test_number_duplicates_single_numpy_range_gene_space_multi_objective() + print() + test_number_duplicates_single_numpy_range_gene_space_initial_population_multi_objective() + print() + + test_number_duplicates_nested_gene_space_multi_objective() + print() + test_number_duplicates_nested_gene_space_initial_population_multi_objective() + print() + + # This example causes duplicates that can only be solved by changing a chain of genes. + # test_number_duplicates_nested_gene_space_nested_gene_type_multi_objective() + # print() + test_number_duplicates_nested_gene_space_nested_gene_type_initial_population_multi_objective() + print() + + diff --git a/test_crossover_mutation.py b/test_crossover_mutation.py new file mode 100644 index 0000000..acc3894 --- /dev/null +++ b/test_crossover_mutation.py @@ -0,0 +1,287 @@ +import pygad +import random +import numpy + +num_generations = 1 + +initial_population = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + +def output_crossover_mutation(gene_space=None, + gene_type=float, + num_genes=10, + mutation_by_replacement=False, + random_mutation_min_val=-1, + random_mutation_max_val=1, + init_range_low=-4, + init_range_high=4, + initial_population=None, + crossover_probability=None, + mutation_probability=None, + crossover_type=None, + mutation_type=None, + parent_selection_type='sss', + multi_objective=False): + + def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + + def fitness_func_no_batch_multi(ga, solution, idx): + return [random.random(), random.random()] + + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + num_genes=num_genes, + gene_space=gene_space, + gene_type=gene_type, + parent_selection_type=parent_selection_type, + initial_population=initial_population, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + allow_duplicate_genes=True, + mutation_by_replacement=mutation_by_replacement, + save_solutions=True, + crossover_probability=crossover_probability, + mutation_probability=mutation_probability, + crossover_type=crossover_type, + mutation_type=mutation_type, + suppress_warnings=True, + random_seed=1) + + ga_instance.run() + + comparison_result = [] + for solution_idx, solution in enumerate(ga_instance.population): + if list(solution) in ga_instance.initial_population.tolist(): + comparison_result.append(True) + else: + comparison_result.append(False) + + comparison_result = numpy.array(comparison_result) + result = numpy.all(comparison_result == True) + + print(f"Comparison result is {result}") + return result, ga_instance + +def test_no_crossover_no_mutation(): + result, ga_instance = output_crossover_mutation() + + assert result == True + +def test_no_crossover_no_mutation_gene_space(): + result, ga_instance = output_crossover_mutation(gene_space=range(10)) + + assert result == True + +def test_no_crossover_no_mutation_int_gene_type(): + result, ga_instance = output_crossover_mutation(gene_type=int) + + assert result == True + + +def test_no_crossover_no_mutation_gene_space_gene_type(): + result, ga_instance = output_crossover_mutation(gene_space={"low": 0, "high": 10}, + gene_type=[float, 2]) + + assert result == True + + +def test_no_crossover_no_mutation_nested_gene_space(): + result, ga_instance = output_crossover_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]]) + assert result == True + +def test_no_crossover_no_mutation_nested_gene_type(): + result, ga_instance = output_crossover_mutation(gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert result == True + +def test_no_crossover_no_mutation_nested_gene_space_nested_gene_type(): + result, ga_instance = output_crossover_mutation(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert result == True + +def test_no_crossover_no_mutation_initial_population(): + global initial_population + result, ga_instance = output_crossover_mutation(initial_population=initial_population) + + assert result == True + +def test_no_crossover_no_mutation_initial_population_nested_gene_type(): + global initial_population + result, ga_instance = output_crossover_mutation(initial_population=initial_population, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert result == True + +def test_crossover_no_mutation_zero_crossover_probability(): + global initial_population + result, ga_instance = output_crossover_mutation(crossover_type="single_point", + crossover_probability=0.0) + + assert result == True + +def test_zero_crossover_probability_zero_mutation_probability(): + global initial_population + result, ga_instance = output_crossover_mutation(crossover_type="single_point", + crossover_probability=0.0, + mutation_type="random", + mutation_probability=0.0) + + assert result == True + +def test_random_mutation_manual_call(): + result, ga_instance = output_crossover_mutation(mutation_type="random", + random_mutation_min_val=888, + random_mutation_max_val=999) + ga_instance.mutation_num_genes = 9 + + temp_offspring = numpy.array(initial_population[0:1]) + offspring = ga_instance.random_mutation(offspring=temp_offspring.copy()) + + comp = offspring - temp_offspring + comp_sorted = sorted(comp.copy()) + comp_sorted = numpy.abs(numpy.unique(comp_sorted)) + + # The other 1 added to include the last value in the range. + assert len(comp_sorted) in range(1, 1 + 1 + ga_instance.mutation_num_genes) + assert comp_sorted[0] == 0 + +def test_random_mutation_manual_call2(): + result, ga_instance = output_crossover_mutation(mutation_type="random", + random_mutation_min_val=888, + random_mutation_max_val=999) + ga_instance.mutation_num_genes = 10 + + temp_offspring = numpy.array(initial_population[0:1]) + offspring = ga_instance.random_mutation(offspring=temp_offspring.copy()) + + comp = offspring - temp_offspring + comp_sorted = sorted(comp.copy()) + comp_sorted = numpy.abs(numpy.unique(comp_sorted)) + + # The other 1 added to include the last value in the range. + assert len(comp_sorted) in range(1, 1 + 1 + ga_instance.mutation_num_genes) + # assert comp_sorted[0] == 0 + +def test_random_mutation_manual_call3(): + # Use random_mutation_min_val & random_mutation_max_val as numbers. + random_mutation_min_val = 888 + random_mutation_max_val = 999 + result, ga_instance = output_crossover_mutation(mutation_type="random", + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + mutation_by_replacement=True) + ga_instance.mutation_num_genes = 10 + + temp_offspring = numpy.array(initial_population[0:1]) + offspring = ga_instance.random_mutation(offspring=temp_offspring.copy()) + + comp = offspring + comp_sorted = sorted(comp.copy()) + comp_sorted = numpy.abs(numpy.unique(comp)) + + value_space = list(range(random_mutation_min_val, random_mutation_max_val)) + for value in comp_sorted: + assert value in value_space + +def test_random_mutation_manual_call4(): + # Use random_mutation_min_val & random_mutation_max_val as lists. + random_mutation_min_val = [888]*10 + random_mutation_max_val = [999]*10 + result, ga_instance = output_crossover_mutation(mutation_type="random", + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + mutation_by_replacement=True) + ga_instance.mutation_num_genes = 10 + + temp_offspring = numpy.array(initial_population[0:1]) + offspring = ga_instance.random_mutation(offspring=temp_offspring.copy()) + + comp = offspring + comp_sorted = sorted(comp.copy()) + comp_sorted = numpy.abs(numpy.unique(comp)) + + value_space = list(range(random_mutation_min_val[0], random_mutation_max_val[0])) + for value in comp_sorted: + assert value in value_space + +if __name__ == "__main__": + #### Single-objective + print() + test_no_crossover_no_mutation() + print() + + test_no_crossover_no_mutation_int_gene_type() + print() + + test_no_crossover_no_mutation_gene_space() + print() + + test_no_crossover_no_mutation_gene_space_gene_type() + print() + + test_no_crossover_no_mutation_nested_gene_space() + print() + + test_no_crossover_no_mutation_nested_gene_type() + print() + + test_no_crossover_no_mutation_initial_population() + print() + + test_no_crossover_no_mutation_initial_population_nested_gene_type() + print() + + test_crossover_no_mutation_zero_crossover_probability() + print() + + test_zero_crossover_probability_zero_mutation_probability() + print() + + test_random_mutation_manual_call() + print() + + test_random_mutation_manual_call2() + print() + + test_random_mutation_manual_call3() + print() + + test_random_mutation_manual_call4() + print() diff --git a/test_gene_space_allow_duplicate_genes.py b/test_gene_space_allow_duplicate_genes.py new file mode 100644 index 0000000..c35f388 --- /dev/null +++ b/test_gene_space_allow_duplicate_genes.py @@ -0,0 +1,1139 @@ +""" +This script is identical to the test_gene_space.py script except for: + Setting allow_duplicate_genes=False instead of True. +""" + +import pygad +import random +import numpy + +num_generations = 100 + +initial_population = [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]] + +# Test single gene space with nested gene type. + +def number_respect_gene_space(gene_space=None, + gene_type=float, + num_genes=10, + mutation_by_replacement=False, + random_mutation_min_val=-1, + random_mutation_max_val=1, + init_range_low=-4, + init_range_high=4, + initial_population=None, + parent_selection_type='sss', + multi_objective=False): + + def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + + def fitness_func_no_batch_multi(ga, solution, idx): + return [random.random(), random.random()] + + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + parent_selection_type=parent_selection_type, + num_genes=num_genes, + gene_space=gene_space, + gene_type=gene_type, + initial_population=initial_population, + init_range_low=init_range_low, + init_range_high=init_range_high, + random_mutation_min_val=random_mutation_min_val, + random_mutation_max_val=random_mutation_max_val, + allow_duplicate_genes=False, + mutation_by_replacement=mutation_by_replacement, + save_solutions=True, + suppress_warnings=True, + random_seed=2) + + ga_instance.run() + ga_instance.solutions = numpy.array(ga_instance.solutions, + dtype=object) + + # gene_space_unpacked = ga_instance.unpack_gene_space(num_values_from_inf_range=100) + num_outside = 0 + if ga_instance.gene_space_nested == True: + for gene_idx in range(ga_instance.num_genes): + all_gene_values = ga_instance.solutions[:, gene_idx] + if type(ga_instance.gene_space[gene_idx]) in [list, tuple, range, numpy.ndarray]: + current_gene_space = list(ga_instance.gene_space[gene_idx]) + for val in all_gene_values: + if val in current_gene_space: + # print(val, current_gene_space) + pass + else: + # print(gene_idx, val, current_gene_space) + num_outside += 1 + elif type(ga_instance.gene_space[gene_idx]) is dict: + if not "step" in ga_instance.gene_space[gene_idx].keys(): + for val in all_gene_values: + if val >= ga_instance.gene_space[gene_idx]["low"] and val < ga_instance.gene_space[gene_idx]["high"]: + pass + else: + num_outside += 1 + else: + gene_space_values = numpy.arange(ga_instance.gene_space[gene_idx]["low"], + ga_instance.gene_space[gene_idx]["high"], + ga_instance.gene_space[gene_idx]["step"]) + for val in all_gene_values: + if val in gene_space_values: + pass + else: + num_outside += 1 + elif type(ga_instance.gene_space[gene_idx]) in ga_instance.supported_int_float_types: + for val in all_gene_values: + if val == ga_instance.gene_space[gene_idx]: + pass + else: + num_outside += 1 + else: + for gene_idx in range(ga_instance.num_genes): + all_gene_values = ga_instance.solutions[:, gene_idx] + # print("all_gene_values", gene_idx, all_gene_values) + if type(ga_instance.gene_space) in [list, tuple, range, numpy.ndarray]: + current_gene_space = list(ga_instance.gene_space) + for val in all_gene_values: + if val in current_gene_space: + pass + else: + num_outside += 1 + elif type(ga_instance.gene_space) is dict: + if not "step" in ga_instance.gene_space.keys(): + for val in all_gene_values: + if val >= ga_instance.gene_space["low"] and val < ga_instance.gene_space["high"]: + pass + else: + num_outside += 1 + else: + gene_space_values = numpy.arange(ga_instance.gene_space["low"], + ga_instance.gene_space["high"], + ga_instance.gene_space["step"]) + for val in all_gene_values: + if val in gene_space_values: + pass + else: + num_outside += 1 + + print(f"Number of outside range is {num_outside}.") + return num_outside, ga_instance + +#### Single-Objective +def test_gene_space_range(): + num_outside, _ = number_respect_gene_space(gene_space=range(10)) + + assert num_outside == 0 + +def test_gene_space_numpy_arange(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10)) + + assert num_outside == 0 + +def test_gene_space_list(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10))) + + assert num_outside == 0 + +def test_gene_space_numpy(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10)))) + + assert num_outside == 0 + +def test_gene_space_dict_without_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}) + + assert num_outside == 0 + +def test_gene_space_dict_with_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}) + + assert num_outside == 0 + +def test_gene_space_list_single_value(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5]) + + assert num_outside == 0 + +def test_gene_space_range_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_numpy_arange_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_list_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_numpy_nested_gene_type(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_dict_without_step_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + assert num_outside == 0 + +def test_gene_space_dict_with_step_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_gene_space_list_single_value_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_nested_gene_space_range(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[range(0, 10), + range(10, 20), + range(20, 30), + range(30, 40), + range(40, 50), + range(50, 60), + range(60, 70), + range(70, 80), + range(80, 90), + range(90, 100)]) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}]) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_float_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + gene_type=[float, 3]) + + assert num_outside == 0 + +def test_nested_gene_space_dict_with_step(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10, "step": 1}, + {"low": 10, "high": 20, "step": 1.5}, + {"low": 20, "high": 30, "step": 2}, + {"low": 30, "high": 40, "step": 2.5}, + {"low": 40, "high": 50, "step": 3}, + {"low": 50, "high": 60, "step": 3.5}, + {"low": 60, "high": 70, "step": 4}, + {"low": 70, "high": 80, "step": 4.5}, + {"low": 80, "high": 90, "step": 5}, + {"low": 90, "high": 100, "step": 5.5}]) + + assert num_outside == 0 + + +def test_nested_gene_space_numpy_arange(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[numpy.arange(0, 10), + numpy.arange(10, 20), + numpy.arange(20, 30), + numpy.arange(30, 40), + numpy.arange(40, 50), + numpy.arange(50, 60), + numpy.arange(60, 70), + numpy.arange(70, 80), + numpy.arange(80, 90), + numpy.arange(90, 100)]) + + assert num_outside == 0 + +def test_nested_gene_space_list(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [-10, 10, 20, 30, 40, 50, 60, 70, 80, 90], + [-11, 11, 22, 33, 44, 55, 66, 77, 88, 99], + [-100, 100, 200, 300, 400, 500, 600, 700, 800, 900], + [-4.1, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], + [-5.1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], + [-10.5, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9], + [-15, 15, 25, 35, 45, 55, 65, 75, 85, 95], + [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + [40, 41, 42, 43, 44, 45, 46, 47, 48, 49]]) + + assert num_outside == 0 + +def test_nested_gene_space_list2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]]) + + assert num_outside == 0 + +def test_nested_gene_space_mix(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=int) + + assert num_outside == 0 + +def test_nested_gene_space_mix_nested_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 00, "high": 10, "step": 1}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]]) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_single_gene_type(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[float, 4]) + + assert num_outside == 0 + +#### Multi-Objective +def test_gene_space_range_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_arange_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_dict_without_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_dict_with_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_single_value_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_range_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_arange_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_nested_gene_type_multi_objective(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_dict_without_step_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + assert num_outside == 0 + +def test_gene_space_dict_with_step_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_list_single_value_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_range_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[range(0, 10), + range(10, 20), + range(20, 30), + range(30, 40), + range(40, 50), + range(50, 60), + range(60, 70), + range(70, 80), + range(80, 90), + range(90, 100)], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_float_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + gene_type=[float, 3], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_dict_with_step_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10, "step": 1}, + {"low": 10, "high": 20, "step": 1.5}, + {"low": 20, "high": 30, "step": 2}, + {"low": 30, "high": 40, "step": 2.5}, + {"low": 40, "high": 50, "step": 3}, + {"low": 50, "high": 60, "step": 3.5}, + {"low": 60, "high": 70, "step": 4}, + {"low": 70, "high": 80, "step": 4.5}, + {"low": 80, "high": 90, "step": 5}, + {"low": 90, "high": 100, "step": 5.5}], + multi_objective=True) + + assert num_outside == 0 + + +def test_nested_gene_space_numpy_arange_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[numpy.arange(0, 10), + numpy.arange(10, 20), + numpy.arange(20, 30), + numpy.arange(30, 40), + numpy.arange(40, 50), + numpy.arange(50, 60), + numpy.arange(60, 70), + numpy.arange(70, 80), + numpy.arange(80, 90), + numpy.arange(90, 100)], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_list_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [-10, 10, 20, 30, 40, 50, 60, 70, 80, 90], + [-11, 11, 22, 33, 44, 55, 66, 77, 88, 99], + [-100, 100, 200, 300, 400, 500, 600, 700, 800, 900], + [-4.1, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], + [-5.1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], + [-10.5, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9], + [-15, 15, 25, 35, 45, 55, 65, 75, 85, 95], + [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + [40, 41, 42, 43, 44, 45, 46, 47, 48, 49]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_list2_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=int, + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_nested_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 00, "high": 10, "step": 1}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[float, 4], + multi_objective=True) + + assert num_outside == 0 + +#### Multi-Objective NSGA-II Parent Selection +def test_gene_space_range_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_numpy_arange_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_numpy_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_dict_without_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_dict_with_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_single_value_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_range_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=range(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True) + + assert num_outside == 0 + +def test_gene_space_numpy_arange_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.arange(10), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=list(range(10)), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_numpy_nested_gene_type_multi_objective_nsga2(): + num_outside, _ = number_respect_gene_space(gene_space=numpy.array(list(range(10))), + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_dict_without_step_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + assert num_outside == 0 + +def test_gene_space_dict_with_step_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space={"low": 0, "high": 10, "step": 2}, + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_gene_space_list_single_value_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[5], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_range_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[range(0, 10), + range(10, 20), + range(20, 30), + range(30, 40), + range(40, 50), + range(50, 60), + range(60, 70), + range(70, 80), + range(80, 90), + range(90, 100)], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_dict_without_step_float_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10}, + {"low": 10, "high": 20}, + {"low": 20, "high": 30}, + {"low": 30, "high": 40}, + {"low": 40, "high": 50}, + {"low": 50, "high": 60}, + {"low": 60, "high": 70}, + {"low": 70, "high": 80}, + {"low": 80, "high": 90}, + {"low": 90, "high": 100}], + gene_type=[float, 3], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_dict_with_step_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[{"low": 0, "high": 10, "step": 1}, + {"low": 10, "high": 20, "step": 1.5}, + {"low": 20, "high": 30, "step": 2}, + {"low": 30, "high": 40, "step": 2.5}, + {"low": 40, "high": 50, "step": 3}, + {"low": 50, "high": 60, "step": 3.5}, + {"low": 60, "high": 70, "step": 4}, + {"low": 70, "high": 80, "step": 4.5}, + {"low": 80, "high": 90, "step": 5}, + {"low": 90, "high": 100, "step": 5.5}], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + + +def test_nested_gene_space_numpy_arange_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[numpy.arange(0, 10), + numpy.arange(10, 20), + numpy.arange(20, 30), + numpy.arange(30, 40), + numpy.arange(40, 50), + numpy.arange(50, 60), + numpy.arange(60, 70), + numpy.arange(70, 80), + numpy.arange(80, 90), + numpy.arange(90, 100)], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_list_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + [-10, 10, 20, 30, 40, 50, 60, 70, 80, 90], + [-11, 11, 22, 33, 44, 55, 66, 77, 88, 99], + [-100, 100, 200, 300, 400, 500, 600, 700, 800, 900], + [-4.1, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9], + [-5.1, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9], + [-10.5, 10.1, 10.2, 10.3, 10.4, 10.5, 10.6, 10.7, 10.8, 10.9], + [-15, 15, 25, 35, 45, 55, 65, 75, 85, 95], + [30, 31, 32, 33, 34, 35, 36, 37, 38, 39], + [40, 41, 42, 43, 44, 45, 46, 47, 48, 49]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_list2_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1], + [1, 2], + [2, 3], + [3, 4], + [4, 5], + [5, 6], + [6, 7], + [7, 8], + [8, 9], + [9, 10]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=int, + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_nested_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4], + numpy.arange(5, 10), + range(10, 15), + {"low": 15, "high": 20}, + {"low": 20, "high": 30, "step": 2}, + None, + numpy.arange(30, 35), + numpy.arange(35, 40), + numpy.arange(40, 45), + [45, 46, 47, 48, 49]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 00, "high": 10, "step": 1}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[int, float, numpy.float64, [float, 3], [float, 4], numpy.int16, [numpy.float32, 1], int, float, [float, 3]], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + +def test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective_nsga2(): + num_outside, ga_instance = number_respect_gene_space(gene_space=[[0, 1, 2, 3, 4, 5, 6, 7, 8, 9], + numpy.arange(0, 10), + range(0, 10), + {"low": 0, "high": 10}, + {"low": 0, "high": 10}, + range(0, 10), + numpy.arange(0, 10), + numpy.arange(0, 10), + {"low": 0, "high": 10}, + [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]], + gene_type=[float, 4], + multi_objective=True, + parent_selection_type='nsga2') + + assert num_outside == 0 + + +if __name__ == "__main__": + #### Single-objective + print() + test_gene_space_range() + print() + test_gene_space_range_nested_gene_type() + print() + + test_gene_space_numpy_arange() + print() + test_gene_space_numpy_arange_nested_gene_type() + print() + + test_gene_space_list() + print() + test_gene_space_list_nested_gene_type() + print() + + test_gene_space_list_single_value() + print() + test_gene_space_list_single_value_nested_gene_type() + print() + + test_gene_space_numpy() + print() + test_gene_space_numpy_nested_gene_type() + print() + + test_gene_space_dict_without_step() + print() + test_gene_space_dict_without_step_nested_gene_type() + print() + + test_gene_space_dict_with_step() + print() + test_gene_space_dict_with_step_nested_gene_type() + print() + + test_nested_gene_space_range() + print() + + test_nested_gene_space_dict_without_step() + print() + + test_nested_gene_space_dict_without_step_float_gene_type() + print() + + test_nested_gene_space_dict_with_step() + print() + + test_nested_gene_space_numpy_arange() + print() + + test_nested_gene_space_list() + print() + + test_nested_gene_space_list2() + print() + + test_nested_gene_space_mix() + print() + + test_nested_gene_space_mix_nested_gene_type() + print() + + test_nested_gene_space_mix_initial_population() + print() + + test_nested_gene_space_mix_initial_population_single_gene_type() + print() + + + + #### Multi-objective + print() + test_gene_space_range_multi_objective() + print() + test_gene_space_range_nested_gene_type_multi_objective() + print() + + test_gene_space_numpy_arange_multi_objective() + print() + test_gene_space_numpy_arange_nested_gene_type_multi_objective() + print() + + test_gene_space_list_multi_objective() + print() + test_gene_space_list_nested_gene_type_multi_objective() + print() + + test_gene_space_list_single_value_multi_objective() + print() + test_gene_space_list_single_value_nested_gene_type_multi_objective() + print() + + test_gene_space_numpy_multi_objective() + print() + test_gene_space_numpy_nested_gene_type_multi_objective() + print() + + test_gene_space_dict_without_step_multi_objective() + print() + test_gene_space_dict_without_step_nested_gene_type_multi_objective() + print() + + test_gene_space_dict_with_step_multi_objective() + print() + test_gene_space_dict_with_step_nested_gene_type_multi_objective() + print() + + test_nested_gene_space_range_multi_objective() + print() + + test_nested_gene_space_dict_without_step_multi_objective() + print() + + test_nested_gene_space_dict_without_step_float_gene_type_multi_objective() + print() + + test_nested_gene_space_dict_with_step_multi_objective() + print() + + test_nested_gene_space_numpy_arange_multi_objective() + print() + + test_nested_gene_space_list_multi_objective() + print() + + test_nested_gene_space_list2_multi_objective() + print() + + test_nested_gene_space_mix_multi_objective() + print() + + test_nested_gene_space_mix_nested_gene_type_multi_objective() + print() + + test_nested_gene_space_mix_initial_population_multi_objective() + print() + + test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective() + print() + + + #### Multi-objective NSGA-II Parent Selection + print() + test_gene_space_range_multi_objective_nsga2() + print() + test_gene_space_range_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_numpy_arange_multi_objective_nsga2() + print() + test_gene_space_numpy_arange_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_list_multi_objective_nsga2() + print() + test_gene_space_list_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_list_single_value_multi_objective_nsga2() + print() + test_gene_space_list_single_value_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_numpy_multi_objective_nsga2() + print() + test_gene_space_numpy_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_dict_without_step_multi_objective_nsga2() + print() + test_gene_space_dict_without_step_nested_gene_type_multi_objective_nsga2() + print() + + test_gene_space_dict_with_step_multi_objective_nsga2() + + print() + test_gene_space_dict_with_step_nested_gene_type_multi_objective_nsga2() + print() + + test_nested_gene_space_range_multi_objective_nsga2() + print() + + test_nested_gene_space_dict_without_step_multi_objective_nsga2() + print() + + test_nested_gene_space_dict_without_step_float_gene_type_multi_objective_nsga2() + print() + + test_nested_gene_space_dict_with_step_multi_objective_nsga2() + print() + + test_nested_gene_space_numpy_arange_multi_objective_nsga2() + print() + + test_nested_gene_space_list_multi_objective_nsga2() + print() + + test_nested_gene_space_list2_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_nested_gene_type_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_initial_population_multi_objective_nsga2() + print() + + test_nested_gene_space_mix_initial_population_single_gene_type_multi_objective_nsga2() + print() + + diff --git a/test_lifecycle_callbacks_calls.py b/test_lifecycle_callbacks_calls.py new file mode 100644 index 0000000..b3159e8 --- /dev/null +++ b/test_lifecycle_callbacks_calls.py @@ -0,0 +1,247 @@ +import pygad +import random + +num_generations = 100 + +def number_lifecycle_callback_functions_calls(stop_criteria=None, + on_generation_stop=None, + crossover_type="single_point", + mutation_type="random"): + actual_num_callbacks_calls = 0 + + def fitness_func(ga_instanse, solution, solution_idx): + return random.random() + + def on_start(ga_instance): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_fitness(ga_instance, population_fitness): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_parents(ga_instance, selected_parents): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_crossover(ga_instance, offspring_crossover): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_mutation(ga_instance, offspring_mutation): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_generation(ga_instance): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + if on_generation_stop: + if ga_instance.generations_completed == on_generation_stop: + return "stop" + + def on_stop(ga_instance, last_population_fitness): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=fitness_func, + sol_per_pop=10, + num_genes=5, + crossover_type=crossover_type, + mutation_type=mutation_type, + on_start=on_start, + on_fitness=on_fitness, + on_parents=on_parents, + on_crossover=on_crossover, + on_mutation=on_mutation, + on_generation=on_generation, + on_stop=on_stop, + stop_criteria=stop_criteria, + suppress_warnings=True) + + ga_instance.run() + + # The total number is: + # 1 [for on_start()] + + # num_generations [for on_fitness()] + + # num_generations [for on_parents()] + + # num_generations [for on_crossover()] + + # num_generations [for on_mutation()] + + # num_generations [for on_generation()] + + # 1 [for on_stop()] + # = 1 + num_generations * 5 + 1 + + # Use 'generations_completed' instead of 'num_generations' because the evolution may stops in the on_generation() callback. + expected_num_callbacks_calls = 1 + ga_instance.generations_completed * 5 + 1 + + print(f"Expected {expected_num_callbacks_calls}.") + print(f"Actual {actual_num_callbacks_calls}.") + return actual_num_callbacks_calls, expected_num_callbacks_calls + +def number_lifecycle_callback_methods_calls(stop_criteria=None, + on_generation_stop=None, + crossover_type="single_point", + mutation_type="random"): + actual_num_callbacks_calls = 0 + + class Callbacks: + def fitness_func(self, ga_instanse, solution, solution_idx): + return 1 + + def on_start(self, ga_instance): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_fitness(self, ga_instance, population_fitness): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_parents(self, ga_instance, selected_parents): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_crossover(self, ga_instance, offspring_crossover): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_mutation(self, ga_instance, offspring_mutation): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + def on_generation(self, ga_instance): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + if on_generation_stop: + if ga_instance.generations_completed == on_generation_stop: + return "stop" + + def on_stop(self, ga_instance, last_population_fitness): + nonlocal actual_num_callbacks_calls + actual_num_callbacks_calls = actual_num_callbacks_calls + 1 + + Callbacks_obj = Callbacks() + ga_instance = pygad.GA(num_generations=num_generations, + num_parents_mating=5, + fitness_func=Callbacks_obj.fitness_func, + sol_per_pop=10, + num_genes=5, + crossover_type=crossover_type, + mutation_type=mutation_type, + on_start=Callbacks_obj.on_start, + on_fitness=Callbacks_obj.on_fitness, + on_parents=Callbacks_obj.on_parents, + on_crossover=Callbacks_obj.on_crossover, + on_mutation=Callbacks_obj.on_mutation, + on_generation=Callbacks_obj.on_generation, + on_stop=Callbacks_obj.on_stop, + stop_criteria=stop_criteria, + suppress_warnings=True) + + ga_instance.run() + + # The total number is: + # 1 [for on_start()] + + # num_generations [for on_fitness()] + + # num_generations [for on_parents()] + + # num_generations [for on_crossover()] + + # num_generations [for on_mutation()] + + # num_generations [for on_generation()] + + # 1 [for on_stop()] + # = 1 + num_generations * 5 + 1 + + # Use 'generations_completed' instead of 'num_generations' because the evolution may stops in the on_generation() callback. + expected_num_callbacks_calls = 1 + ga_instance.generations_completed * 5 + 1 + + print(f"Expected {expected_num_callbacks_calls}.") + print(f"Actual {actual_num_callbacks_calls}.") + return actual_num_callbacks_calls, expected_num_callbacks_calls + +def test_number_lifecycle_callback_functions_calls(): + actual, expected = number_lifecycle_callback_functions_calls() + + assert actual == expected + +def test_number_lifecycle_callback_functions_calls_stop_criteria(): + actual, expected = number_lifecycle_callback_functions_calls(on_generation_stop=30) + + assert actual == expected + +def test_number_lifecycle_callback_methods_calls(): + actual, expected = number_lifecycle_callback_methods_calls() + + assert actual == expected + +def test_number_lifecycle_callback_methods_calls_stop_criteria(): + actual, expected = number_lifecycle_callback_methods_calls(on_generation_stop=30) + + assert actual == expected + +def test_number_lifecycle_callback_functions_calls_no_crossover(): + actual, expected = number_lifecycle_callback_functions_calls(crossover_type=None) + + assert actual == expected + +def test_number_lifecycle_callback_functions_calls_no_mutation(): + actual, expected = number_lifecycle_callback_functions_calls(mutation_type=None) + + assert actual == expected + +def test_number_lifecycle_callback_functions_calls_no_crossover_no_mutation(): + actual, expected = number_lifecycle_callback_functions_calls(crossover_type=None, + mutation_type=None) + + assert actual == expected + +def test_number_lifecycle_callback_methods_calls_no_crossover(): + actual, expected = number_lifecycle_callback_methods_calls(crossover_type=None) + + assert actual == expected + +def test_number_lifecycle_callback_methods_calls_no_mutation(): + actual, expected = number_lifecycle_callback_methods_calls(mutation_type=None) + + assert actual == expected + +def test_number_lifecycle_callback_methods_calls_no_crossover_no_mutation(): + actual, expected = number_lifecycle_callback_methods_calls(crossover_type=None, + mutation_type=None) + + assert actual == expected + +if __name__ == "__main__": + print() + test_number_lifecycle_callback_functions_calls() + print() + + test_number_lifecycle_callback_functions_calls_stop_criteria() + print() + + test_number_lifecycle_callback_methods_calls() + print() + + test_number_lifecycle_callback_methods_calls_stop_criteria() + print() + + test_number_lifecycle_callback_functions_calls_no_crossover() + print() + + test_number_lifecycle_callback_functions_calls_no_crossover() + print() + + test_number_lifecycle_callback_functions_calls_no_mutation() + print() + + test_number_lifecycle_callback_functions_calls_no_crossover_no_mutation() + print() + + test_number_lifecycle_callback_methods_calls_no_crossover() + print() + + test_number_lifecycle_callback_methods_calls_no_mutation() + print() + + test_number_lifecycle_callback_methods_calls_no_crossover_no_mutation() + print() diff --git a/test_number_fitness_function_calls.py b/test_number_fitness_function_calls.py new file mode 100644 index 0000000..916a88f --- /dev/null +++ b/test_number_fitness_function_calls.py @@ -0,0 +1,597 @@ +import pygad +import random +import numpy + +actual_num_fitness_calls_default_keep = 0 +actual_num_fitness_calls_no_keep = 0 +actual_num_fitness_calls_keep_elitism = 0 +actual_num_fitness_calls_keep_parents = 0 + +num_generations = 100 +sol_per_pop = 10 +num_parents_mating = 5 + +# TODO: Calculate the number when fitness_batch_size is used. + +def number_calls_fitness_function(keep_elitism=1, + keep_parents=-1, + mutation_type="random", + mutation_percent_genes="default", + parent_selection_type='sss', + multi_objective=False, + fitness_batch_size=None): + + actual_num_fitness_calls = 0 + def fitness_func_no_batch_single(ga, solution, idx): + nonlocal actual_num_fitness_calls + actual_num_fitness_calls = actual_num_fitness_calls + 1 + return random.random() + + def fitness_func_no_batch_multi(ga_instance, solution, solution_idx): + nonlocal actual_num_fitness_calls + actual_num_fitness_calls = actual_num_fitness_calls + 1 + return [random.random(), random.random()] + + def fitness_func_batch_single(ga_instance, solution, solution_idx): + nonlocal actual_num_fitness_calls + actual_num_fitness_calls = actual_num_fitness_calls + 1 + f = [] + for sol in solution: + f.append(random.random()) + return f + + def fitness_func_batch_multi(ga_instance, solution, solution_idx): + nonlocal actual_num_fitness_calls + actual_num_fitness_calls = actual_num_fitness_calls + 1 + f = [] + for sol in solution: + f.append([random.random(), random.random()]) + return f + + if fitness_batch_size is None or (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size == 1): + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + elif (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size > 1): + if multi_objective == True: + fitness_func = fitness_func_batch_multi + else: + fitness_func = fitness_func_batch_single + + ga_optimizer = pygad.GA(num_generations=num_generations, + sol_per_pop=sol_per_pop, + num_genes=6, + num_parents_mating=num_parents_mating, + fitness_func=fitness_func, + mutation_type=mutation_type, + parent_selection_type=parent_selection_type, + mutation_percent_genes=mutation_percent_genes, + keep_elitism=keep_elitism, + keep_parents=keep_parents, + suppress_warnings=True, + fitness_batch_size=fitness_batch_size) + + ga_optimizer.run() + + if fitness_batch_size is None: + if keep_elitism == 0: + if keep_parents == 0: + # 10 (for initial population) + 100*10 (for other generations) = 1010 + expected_num_fitness_calls = sol_per_pop + num_generations * sol_per_pop + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * sol_per_pop + elif keep_parents == -1: + # 10 (for initial population) + 100*num_parents_mating (for other generations) + expected_num_fitness_calls = sol_per_pop + num_generations * (sol_per_pop - num_parents_mating) + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * (sol_per_pop - num_parents_mating) + else: + # 10 (for initial population) + 100*keep_parents (for other generations) + expected_num_fitness_calls = sol_per_pop + num_generations * (sol_per_pop - keep_parents) + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * (sol_per_pop - keep_parents) + else: + # 10 (for initial population) + 100*keep_elitism (for other generations) + expected_num_fitness_calls = sol_per_pop + num_generations * (sol_per_pop - keep_elitism) + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * (sol_per_pop - keep_elitism) + else: + if keep_elitism == 0: + if keep_parents == 0: + # 10 (for initial population) + 100*10 (for other generations) = 1010 + expected_num_fitness_calls = int(numpy.ceil(sol_per_pop/fitness_batch_size)) + num_generations * int(numpy.ceil(sol_per_pop/fitness_batch_size)) + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * int(numpy.ceil(sol_per_pop/fitness_batch_size)) + elif keep_parents == -1: + # 10 (for initial population) + 100*num_parents_mating (for other generations) + expected_num_fitness_calls = int(numpy.ceil(sol_per_pop/fitness_batch_size)) + num_generations * int(numpy.ceil((sol_per_pop - num_parents_mating)/fitness_batch_size)) + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * int(numpy.ceil((sol_per_pop - num_parents_mating)/fitness_batch_size)) + else: + # 10 (for initial population) + 100*keep_parents (for other generations) + expected_num_fitness_calls = int(numpy.ceil(sol_per_pop/fitness_batch_size)) + num_generations * int(numpy.ceil((sol_per_pop - keep_parents)/fitness_batch_size)) + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * int(numpy.ceil((sol_per_pop - keep_parents)/fitness_batch_size)) + else: + # 10 (for initial population) + 100*keep_elitism (for other generations) + expected_num_fitness_calls = int(numpy.ceil(sol_per_pop/fitness_batch_size)) + num_generations * int(numpy.ceil((sol_per_pop - keep_elitism)/fitness_batch_size)) + if mutation_type == "adaptive": + expected_num_fitness_calls += num_generations * int(numpy.ceil((sol_per_pop - keep_elitism)/fitness_batch_size)) + + print(f"Expected number of fitness function calls is {expected_num_fitness_calls}.") + print(f"Actual number of fitness function calls is {actual_num_fitness_calls}.") + return actual_num_fitness_calls, expected_num_fitness_calls + +def test_number_calls_fitness_function_default_keep(): + actual, expected = number_calls_fitness_function() + assert actual == expected + +def test_number_calls_fitness_function_no_keep(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0) + assert actual == expected + +def test_number_calls_fitness_function_keep_elitism(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=0) + assert actual == expected + +def test_number_calls_fitness_function_keep_parents(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=4) + assert actual == expected + +def test_number_calls_fitness_function_both_keep(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_adaptive_mutation(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5]) + assert actual == expected + +def test_number_calls_fitness_function_default_adaptive_mutation(): + actual, expected = number_calls_fitness_function(mutation_type="adaptive", + mutation_percent_genes=[10, 5]) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_adaptive_mutation(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5]) + assert actual == expected + +#### Multi Objective +def test_number_calls_fitness_function_no_keep_multi_objective(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_keep_elitism_multi_objective(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=0, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_keep_parents_multi_objective(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=4, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_multi_objective(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_default_adaptive_mutation_multi_objective(): + actual, expected = number_calls_fitness_function(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert actual == expected + +#### Multi Objective NSGA-II Parent Selection +def test_number_calls_fitness_function_no_keep_multi_objective_nsga2(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_keep_elitism_multi_objective_nsga2(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=0, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_keep_parents_multi_objective_nsga2(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=4, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_multi_objective_nsga2(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective_nsga2(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_default_adaptive_mutation_multi_objective_nsga2(): + actual, expected = number_calls_fitness_function(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective_nsga2(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert actual == expected + + +######## Batch Fitness Calculation +#### Single Objective +def test_number_calls_fitness_function_no_keep_batch_1(): + actual, expected = number_calls_fitness_function(fitness_batch_size=1) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_batch_4(): + actual, expected = number_calls_fitness_function(fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_batch_9(): + actual, expected = number_calls_fitness_function(fitness_batch_size=9) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_batch_10(): + actual, expected = number_calls_fitness_function(fitness_batch_size=10) + assert actual == expected + +def test_number_calls_fitness_function_keep_elitism_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=0, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_keep_parents_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=4, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_adaptive_mutation_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_default_adaptive_mutation_batch_4(): + actual, expected = number_calls_fitness_function(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_adaptive_mutation_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + fitness_batch_size=4) + assert actual == expected + +#### Multi Objective +def test_number_calls_fitness_function_no_keep_multi_objective_batch_1(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=1) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_multi_objective_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_multi_objective_batch_9(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=9) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_multi_objective_batch_10(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=10) + assert actual == expected + +def test_number_calls_fitness_function_keep_elitism_multi_objective_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=0, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_keep_parents_multi_objective_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=4, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_multi_objective_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_default_adaptive_mutation_multi_objective_batch_4(): + actual, expected = number_calls_fitness_function(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +#### Multi Objective NSGA-II Parent Selection +def test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_1(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=1) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_9(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=9) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_10(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + multi_objective=True, + fitness_batch_size=10) + assert actual == expected + +def test_number_calls_fitness_function_keep_elitism_multi_objective_nsga2_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=0, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_keep_parents_multi_objective_nsga2_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=4, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_multi_objective_nsga2_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective_nsga2_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_default_adaptive_mutation_multi_objective_nsga2_batch_4(): + actual, expected = number_calls_fitness_function(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + +def test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective_nsga2_batch_4(): + actual, expected = number_calls_fitness_function(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert actual == expected + + +if __name__ == "__main__": + #### Single-objective + print() + test_number_calls_fitness_function_default_keep() + print() + test_number_calls_fitness_function_no_keep() + print() + test_number_calls_fitness_function_keep_elitism() + print() + test_number_calls_fitness_function_keep_parents() + print() + test_number_calls_fitness_function_both_keep() + print() + test_number_calls_fitness_function_no_keep_adaptive_mutation() + print() + test_number_calls_fitness_function_default_adaptive_mutation() + print() + test_number_calls_fitness_function_both_keep_adaptive_mutation() + print() + + #### Multi-Objective + print() + test_number_calls_fitness_function_no_keep_multi_objective() + print() + test_number_calls_fitness_function_keep_elitism_multi_objective() + print() + test_number_calls_fitness_function_keep_parents_multi_objective() + print() + test_number_calls_fitness_function_both_keep_multi_objective() + print() + test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective() + print() + test_number_calls_fitness_function_default_adaptive_mutation_multi_objective() + print() + test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective() + print() + + #### Multi-Objective NSGA-II Parent Selection + print() + test_number_calls_fitness_function_no_keep_multi_objective_nsga2() + print() + test_number_calls_fitness_function_keep_elitism_multi_objective_nsga2() + print() + test_number_calls_fitness_function_keep_parents_multi_objective_nsga2() + print() + test_number_calls_fitness_function_both_keep_multi_objective_nsga2() + print() + test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective_nsga2() + print() + test_number_calls_fitness_function_default_adaptive_mutation_multi_objective_nsga2() + print() + test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective_nsga2() + print() + + + ######## Batch Fitness + #### Single-objective + print() + test_number_calls_fitness_function_no_keep_batch_1() + print() + test_number_calls_fitness_function_no_keep_batch_4() + print() + test_number_calls_fitness_function_no_keep_batch_9() + print() + test_number_calls_fitness_function_no_keep_batch_10() + print() + test_number_calls_fitness_function_keep_elitism_batch_4() + print() + test_number_calls_fitness_function_keep_parents_batch_4() + print() + test_number_calls_fitness_function_both_keep_batch_4() + print() + test_number_calls_fitness_function_no_keep_adaptive_mutation_batch_4() + print() + test_number_calls_fitness_function_default_adaptive_mutation_batch_4() + print() + test_number_calls_fitness_function_both_keep_adaptive_mutation_batch_4() + print() + + #### Multi-Objective + print() + test_number_calls_fitness_function_no_keep_multi_objective_batch_1() + print() + test_number_calls_fitness_function_no_keep_multi_objective_batch_4() + print() + test_number_calls_fitness_function_no_keep_multi_objective_batch_9() + print() + test_number_calls_fitness_function_no_keep_multi_objective_batch_10() + print() + test_number_calls_fitness_function_keep_elitism_multi_objective_batch_4() + print() + test_number_calls_fitness_function_keep_parents_multi_objective_batch_4() + print() + test_number_calls_fitness_function_both_keep_multi_objective_batch_4() + print() + test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective_batch_4() + print() + test_number_calls_fitness_function_default_adaptive_mutation_multi_objective_batch_4() + print() + test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective_batch_4() + print() + + #### Multi-Objective NSGA-II Parent Selection + print() + test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_1() + print() + test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_4() + print() + test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_9() + print() + test_number_calls_fitness_function_no_keep_multi_objective_nsga2_batch_10() + print() + test_number_calls_fitness_function_keep_elitism_multi_objective_nsga2_batch_4() + print() + test_number_calls_fitness_function_keep_parents_multi_objective_nsga2_batch_4() + print() + test_number_calls_fitness_function_both_keep_multi_objective_nsga2_batch_4() + print() + test_number_calls_fitness_function_no_keep_adaptive_mutation_multi_objective_nsga2_batch_4() + print() + test_number_calls_fitness_function_default_adaptive_mutation_multi_objective_nsga2_batch_4() + print() + test_number_calls_fitness_function_both_keep_adaptive_mutation_multi_objective_nsga2_batch_4() + print() + diff --git a/test_save_solutions.py b/test_save_solutions.py new file mode 100644 index 0000000..537f4ea --- /dev/null +++ b/test_save_solutions.py @@ -0,0 +1,1289 @@ +import pygad +import random + +num_generations = 100 +sol_per_pop = 10 +num_parents_mating = 5 + +# TODO Verify that each entry in 'solutions_fitness' and 'best_solutions_fitness' has values equal to the number of objectives. + +def number_saved_solutions(keep_elitism=1, + keep_parents=-1, + mutation_type="random", + mutation_percent_genes="default", + parent_selection_type='sss', + multi_objective=False, + fitness_batch_size=None, + save_solutions=False, + save_best_solutions=False): + + def fitness_func_no_batch_single(ga, solution, idx): + return random.random() + + def fitness_func_no_batch_multi(ga_instance, solution, solution_idx): + return [random.random(), random.random()] + + def fitness_func_batch_single(ga_instance, solution, solution_idx): + f = [] + for sol in solution: + f.append(random.random()) + return f + + def fitness_func_batch_multi(ga_instance, solution, solution_idx): + f = [] + for sol in solution: + f.append([random.random(), random.random()]) + return f + + if fitness_batch_size is None or (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size == 1): + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + elif (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size > 1): + if multi_objective == True: + fitness_func = fitness_func_batch_multi + else: + fitness_func = fitness_func_batch_single + + ga_optimizer = pygad.GA(num_generations=num_generations, + sol_per_pop=sol_per_pop, + num_genes=6, + num_parents_mating=num_parents_mating, + fitness_func=fitness_func, + mutation_type=mutation_type, + parent_selection_type=parent_selection_type, + mutation_percent_genes=mutation_percent_genes, + keep_elitism=keep_elitism, + keep_parents=keep_parents, + suppress_warnings=True, + fitness_batch_size=fitness_batch_size, + save_best_solutions=save_best_solutions, + save_solutions=save_solutions) + + ga_optimizer.run() + + if save_solutions == True: + expected_num_solutions = sol_per_pop + num_generations * sol_per_pop + else: + expected_num_solutions = 0 + + if save_best_solutions == True: + expected_num_best_solutions = 1 + num_generations + else: + expected_num_best_solutions = 0 + + print(f"Expected number of solutions is {expected_num_solutions}.") + print(f"Actual number of solutions is {len(ga_optimizer.solutions)}.") + print(f"Expected number of best solutions is {expected_num_best_solutions}.") + print(f"Actual number of best solutions is {len(ga_optimizer.best_solutions)}.") + return expected_num_solutions, len(ga_optimizer.solutions), len(ga_optimizer.solutions_fitness), expected_num_best_solutions, len(ga_optimizer.best_solutions) + +#### Single Objective +def test_save_solutions_default_keep(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions() + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions() + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_save_solutions(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(save_solutions=True, + save_best_solutions=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_save_solutions(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + save_solutions=True, + save_best_solutions=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_save_solutions(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_save_solutions(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + save_solutions=True, + save_best_solutions=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5]) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_save_solutions(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_default_adaptive_mutation(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(mutation_type="adaptive", + mutation_percent_genes=[10, 5]) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5]) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_save_solutions(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + + +#### Multi Objective +def test_save_solutions_default_keep_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_save_solutions_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(save_solutions=True, + save_best_solutions=True, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_save_solutions_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + save_solutions=True, + save_best_solutions=True, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_save_solutions_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_save_solutions_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + save_solutions=True, + save_best_solutions=True, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_default_adaptive_mutation_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + + +#### Multi Objective NSGA-II Parent Selection +def test_save_solutions_default_keep_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_save_solutions_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_save_solutions_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_save_solutions_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_save_solutions_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_default_adaptive_mutation_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective_nsga2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2') + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +######## Batch Fitness +#### Single Objective +def test_save_solutions_no_keep_batch_1(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=1) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=2) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_3(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=3) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_5(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=5) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_6(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=6) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_7(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=7) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_8(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=8) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_9(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=9) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_batch_10(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(fitness_batch_size=10) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + + +def test_save_solutions_no_keep_save_solutions_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(save_solutions=True, + save_best_solutions=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_save_solutions_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + save_solutions=True, + save_best_solutions=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_save_solutions_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_save_solutions_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + save_solutions=True, + save_best_solutions=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_save_solutions_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_default_adaptive_mutation_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_save_solutions_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + + +#### Multi Objective +def test_save_solutions_no_keep_multi_objective_batch_1(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=1) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=2) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_3(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=3) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_5(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=5) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_6(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=6) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_7(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=7) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_8(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=8) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_9(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=9) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_batch_10(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + fitness_batch_size=10) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_save_solutions_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(save_solutions=True, + save_best_solutions=True, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_save_solutions_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_save_solutions_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_save_solutions_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_default_adaptive_mutation_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + + +#### Multi Objective NSGA-II Parent Selection +def test_save_solutions_no_keep_multi_objective_nsga2_batch_1(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=1) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_2(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=2) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_3(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=3) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_5(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=5) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_6(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=6) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_7(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=7) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_8(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=8) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_9(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=9) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_multi_objective_nsga2_batch_10(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=10) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_save_solutions_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_elitism_save_solutions_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=0, + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_keep_parents_save_solutions_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=4, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_save_solutions_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=0, + keep_parents=0, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_default_adaptive_mutation_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + +def test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective_nsga2_batch_4(): + expected_solutions, actual_solutions, actual_solutions_fitness, expected_best_solutions, actual_best_solutions = number_saved_solutions(keep_elitism=3, + keep_parents=4, + mutation_type="adaptive", + mutation_percent_genes=[10, 5], + save_solutions=True, + save_best_solutions=True, + multi_objective=True, + parent_selection_type='nsga2', + fitness_batch_size=4) + assert expected_solutions == actual_solutions + assert expected_solutions == actual_solutions_fitness + assert expected_best_solutions == actual_best_solutions + + + + +if __name__ == "__main__": + #### Single Objective + print() + test_save_solutions_default_keep() + print() + test_save_solutions_no_keep() + print() + test_save_solutions_no_keep_save_solutions() + print() + test_save_solutions_keep_elitism() + print() + test_save_solutions_keep_elitism_save_solutions() + print() + test_save_solutions_keep_parents() + print() + test_save_solutions_keep_parents_save_solutions() + print() + test_save_solutions_both_keep() + print() + test_save_solutions_both_keep_save_solutions() + print() + test_save_solutions_no_keep_adaptive_mutation() + print() + test_save_solutions_no_keep_adaptive_mutation_save_solutions() + print() + test_save_solutions_default_adaptive_mutation() + print() + test_save_solutions_both_keep_adaptive_mutation() + print() + test_save_solutions_both_keep_adaptive_mutation_save_solutions() + print() + + #### Multi-Objective + print() + test_save_solutions_default_keep_multi_objective() + print() + test_save_solutions_no_keep_multi_objective() + print() + test_save_solutions_no_keep_save_solutions_multi_objective() + print() + test_save_solutions_keep_elitism_multi_objective() + print() + test_save_solutions_keep_elitism_save_solutions_multi_objective() + print() + test_save_solutions_keep_parents_multi_objective() + print() + test_save_solutions_keep_parents_save_solutions_multi_objective() + print() + test_save_solutions_both_keep_multi_objective() + print() + test_save_solutions_both_keep_save_solutions_multi_objective() + print() + test_save_solutions_no_keep_adaptive_mutation_multi_objective() + print() + test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective() + print() + test_save_solutions_default_adaptive_mutation_multi_objective() + print() + test_save_solutions_both_keep_adaptive_mutation_multi_objective() + print() + test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective() + print() + + + #### Multi-Objective NSGA-II Parent Selection + print() + test_save_solutions_default_keep_multi_objective_nsga2() + print() + test_save_solutions_no_keep_multi_objective_nsga2() + print() + test_save_solutions_no_keep_save_solutions_multi_objective_nsga2() + print() + test_save_solutions_keep_elitism_multi_objective_nsga2() + print() + test_save_solutions_keep_elitism_save_solutions_multi_objective_nsga2() + print() + test_save_solutions_keep_parents_multi_objective_nsga2() + print() + test_save_solutions_keep_parents_save_solutions_multi_objective_nsga2() + print() + test_save_solutions_both_keep_multi_objective_nsga2() + print() + test_save_solutions_both_keep_save_solutions_multi_objective_nsga2() + print() + test_save_solutions_no_keep_adaptive_mutation_multi_objective_nsga2() + print() + test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective_nsga2() + print() + test_save_solutions_default_adaptive_mutation_multi_objective_nsga2() + print() + test_save_solutions_both_keep_adaptive_mutation_multi_objective_nsga2() + print() + test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective_nsga2() + print() + + ######## Batch Fitness Calculation + #### Single Objective + print() + test_save_solutions_no_keep_batch_1() + print() + test_save_solutions_no_keep_batch_2() + print() + test_save_solutions_no_keep_batch_3() + print() + test_save_solutions_no_keep_batch_4() + print() + test_save_solutions_no_keep_batch_5() + print() + test_save_solutions_no_keep_batch_6() + print() + test_save_solutions_no_keep_batch_7() + print() + test_save_solutions_no_keep_batch_8() + print() + test_save_solutions_no_keep_batch_9() + print() + test_save_solutions_no_keep_batch_10() + print() + test_save_solutions_no_keep_save_solutions_batch_4() + print() + test_save_solutions_keep_elitism_batch_4() + print() + test_save_solutions_keep_elitism_save_solutions_batch_4() + print() + test_save_solutions_keep_parents_batch_4() + print() + test_save_solutions_keep_parents_save_solutions_batch_4() + print() + test_save_solutions_both_keep_batch_4() + print() + test_save_solutions_both_keep_save_solutions_batch_4() + print() + test_save_solutions_no_keep_adaptive_mutation_batch_4() + print() + test_save_solutions_no_keep_adaptive_mutation_save_solutions_batch_4() + print() + test_save_solutions_default_adaptive_mutation_batch_4() + print() + test_save_solutions_both_keep_adaptive_mutation_batch_4() + print() + test_save_solutions_both_keep_adaptive_mutation_save_solutions_batch_4() + print() + + #### Multi-Objective + print() + test_save_solutions_no_keep_multi_objective_batch_1() + print() + test_save_solutions_no_keep_multi_objective_batch_2() + print() + test_save_solutions_no_keep_multi_objective_batch_3() + print() + test_save_solutions_no_keep_multi_objective_batch_4() + print() + test_save_solutions_no_keep_multi_objective_batch_5() + print() + test_save_solutions_no_keep_multi_objective_batch_6() + print() + test_save_solutions_no_keep_multi_objective_batch_7() + print() + test_save_solutions_no_keep_multi_objective_batch_8() + print() + test_save_solutions_no_keep_multi_objective_batch_9() + print() + test_save_solutions_no_keep_multi_objective_batch_10() + print() + test_save_solutions_no_keep_save_solutions_multi_objective_batch_4() + print() + test_save_solutions_keep_elitism_multi_objective_batch_4() + print() + test_save_solutions_keep_elitism_save_solutions_multi_objective_batch_4() + print() + test_save_solutions_keep_parents_multi_objective_batch_4() + print() + test_save_solutions_keep_parents_save_solutions_multi_objective_batch_4() + print() + test_save_solutions_both_keep_multi_objective_batch_4() + print() + test_save_solutions_both_keep_save_solutions_multi_objective_batch_4() + print() + test_save_solutions_no_keep_adaptive_mutation_multi_objective_batch_4() + print() + test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective_batch_4() + print() + test_save_solutions_default_adaptive_mutation_multi_objective_batch_4() + print() + test_save_solutions_both_keep_adaptive_mutation_multi_objective_batch_4() + print() + test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective_batch_4() + print() + + + #### Multi-Objective NSGA-II Parent Selection + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_1() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_2() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_3() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_4() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_5() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_6() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_7() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_8() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_9() + print() + test_save_solutions_no_keep_multi_objective_nsga2_batch_10() + print() + test_save_solutions_no_keep_save_solutions_multi_objective_nsga2_batch_4() + print() + test_save_solutions_keep_elitism_multi_objective_nsga2_batch_4() + print() + test_save_solutions_keep_elitism_save_solutions_multi_objective_nsga2_batch_4() + print() + test_save_solutions_keep_parents_multi_objective_nsga2_batch_4() + print() + test_save_solutions_keep_parents_save_solutions_multi_objective_nsga2_batch_4() + print() + test_save_solutions_both_keep_multi_objective_nsga2_batch_4() + print() + test_save_solutions_both_keep_save_solutions_multi_objective_nsga2_batch_4() + print() + test_save_solutions_no_keep_adaptive_mutation_multi_objective_nsga2_batch_4() + print() + test_save_solutions_no_keep_adaptive_mutation_save_solutions_multi_objective_nsga2_batch_4() + print() + test_save_solutions_default_adaptive_mutation_multi_objective_nsga2_batch_4() + print() + test_save_solutions_both_keep_adaptive_mutation_multi_objective_nsga2_batch_4() + print() + test_save_solutions_both_keep_adaptive_mutation_save_solutions_multi_objective_nsga2_batch_4() + print() diff --git a/test_stop_criteria.py b/test_stop_criteria.py new file mode 100644 index 0000000..3ee1737 --- /dev/null +++ b/test_stop_criteria.py @@ -0,0 +1,234 @@ +import pygad +import numpy + +actual_num_fitness_calls_default_keep = 0 +actual_num_fitness_calls_no_keep = 0 +actual_num_fitness_calls_keep_elitism = 0 +actual_num_fitness_calls_keep_parents = 0 + +num_generations = 50 +sol_per_pop = 10 +num_parents_mating = 5 + +function_inputs1 = [4,-2,3.5,5,-11,-4.7] # Function 1 inputs. +function_inputs2 = [-2,0.7,-9,1.4,3,5] # Function 2 inputs. +desired_output1 = 50 # Function 1 output. +desired_output2 = 30 # Function 2 output. + +#### Define the fitness functions in the top-level of the module so that they are picklable and usable in the process-based parallel processing works. +#### If the functions are defined inside a class/method/function, they are not picklable and this error is raised: AttributeError: Can't pickle local object +#### Process-based parallel processing must have the used functions picklable. +def fitness_func_batch_multi(ga_instance, solution, solution_idx): + f = [] + for sol in solution: + output1 = numpy.sum(sol*function_inputs1) + output2 = numpy.sum(sol*function_inputs2) + fitness1 = 1.0 / (numpy.abs(output1 - desired_output1) + 0.000001) + fitness2 = 1.0 / (numpy.abs(output2 - desired_output2) + 0.000001) + f.append([fitness1, fitness2]) + return f + +def fitness_func_no_batch_multi(ga_instance, solution, solution_idx): + output1 = numpy.sum(solution*function_inputs1) + output2 = numpy.sum(solution*function_inputs2) + fitness1 = 1.0 / (numpy.abs(output1 - desired_output1) + 0.000001) + fitness2 = 1.0 / (numpy.abs(output2 - desired_output2) + 0.000001) + return [fitness1, fitness2] + +def fitness_func_batch_single(ga_instance, solution, solution_idx): + f = [] + for sol in solution: + output = numpy.sum(solution*function_inputs1) + fitness = 1.0 / (numpy.abs(output - desired_output1) + 0.000001) + f.append(fitness) + return f + +def fitness_func_no_batch_single(ga_instance, solution, solution_idx): + output = numpy.sum(solution*function_inputs1) + fitness = 1.0 / (numpy.abs(output - desired_output1) + 0.000001) + return fitness + + +def multi_objective_problem(keep_elitism=1, + keep_parents=-1, + fitness_batch_size=None, + stop_criteria=None, + parent_selection_type='sss', + mutation_type="random", + mutation_percent_genes="default", + multi_objective=False, + parallel_processing=None): + + if fitness_batch_size is None or (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size == 1): + if multi_objective == True: + fitness_func = fitness_func_no_batch_multi + else: + fitness_func = fitness_func_no_batch_single + elif (type(fitness_batch_size) in pygad.GA.supported_int_types and fitness_batch_size > 1): + if multi_objective == True: + fitness_func = fitness_func_batch_multi + else: + fitness_func = fitness_func_batch_single + + ga_optimizer = pygad.GA(num_generations=num_generations, + sol_per_pop=sol_per_pop, + num_genes=6, + num_parents_mating=num_parents_mating, + fitness_func=fitness_func, + fitness_batch_size=fitness_batch_size, + mutation_type=mutation_type, + mutation_percent_genes=mutation_percent_genes, + keep_elitism=keep_elitism, + keep_parents=keep_parents, + stop_criteria=stop_criteria, + parent_selection_type=parent_selection_type, + parallel_processing=parallel_processing, + suppress_warnings=True) + + ga_optimizer.run() + + return None + +def test_number_calls_fitness_function_no_parallel_processing(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=None, + parallel_processing=None) + +def test_number_calls_fitness_function_parallel_processing_thread_1(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=None, + parallel_processing=['thread', 1]) + +def test_number_calls_fitness_function_parallel_processing_thread_2(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=None, + parallel_processing=['thread', 2]) + +def test_number_calls_fitness_function_parallel_processing_thread_5(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=None, + parallel_processing=['thread', 5]) + +def test_number_calls_fitness_function_parallel_processing_thread_5_patch_4(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=4, + parallel_processing=['thread', 5]) + +def test_number_calls_fitness_function_parallel_processing_thread_5_patch_4_multi_objective(): + multi_objective_problem(multi_objective=True, + fitness_batch_size=4, + parallel_processing=['thread', 5]) + +def test_number_calls_fitness_function_parallel_processing_process_1(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=None, + parallel_processing=['process', 1]) + +def test_number_calls_fitness_function_parallel_processing_process_2(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=None, + parallel_processing=['process', 2]) + +def test_number_calls_fitness_function_parallel_processing_process_5(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=None, + parallel_processing=['process', 5]) + +def test_number_calls_fitness_function_parallel_processing_process_5_patch_4(): + multi_objective_problem(multi_objective=False, + fitness_batch_size=4, + parallel_processing=['process', 5]) + +def test_number_calls_fitness_function_parallel_processing_process_5_patch_4_multi_objective(): + multi_objective_problem(multi_objective=True, + fitness_batch_size=4, + parallel_processing=['process', 5]) + +# Stop Criteria +def test_number_calls_fitness_function_multi_objective_stop_criteria_str_single_value(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_10') + +def test_number_calls_fitness_function_multi_objective_stop_criteria_str(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_10_20') + +def test_number_calls_fitness_function_multi_objective_stop_criteria_str_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_-1.0_0.5') + +def test_number_calls_fitness_function_multi_objective_stop_criteria_list(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_10_20', 'reach_5_2']) + +def test_number_calls_fitness_function_multi_objective_stop_criteria_list_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_-1.0_0.5', 'reach_5_-2.8']) + +def test_number_calls_fitness_function_single_objective_stop_criteria_str(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_10') + +def test_number_calls_fitness_function_single_objective_stop_criteria_str_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria='reach_-1.7') + +def test_number_calls_fitness_function_single_objective_stop_criteria_list(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_10', 'reach_5']) + +def test_number_calls_fitness_function_single_objective_stop_criteria_list_decimal(): + multi_objective_problem(multi_objective=True, + stop_criteria=['reach_-1.5', 'reach_-2.8']) + +if __name__ == "__main__": + print() + test_number_calls_fitness_function_no_parallel_processing() + print() + + #### Thread-based Parallel Processing + test_number_calls_fitness_function_parallel_processing_thread_1() + print() + test_number_calls_fitness_function_parallel_processing_thread_2() + print() + test_number_calls_fitness_function_parallel_processing_thread_5() + print() + test_number_calls_fitness_function_parallel_processing_thread_5_patch_4() + print() + test_number_calls_fitness_function_parallel_processing_thread_5_patch_4_multi_objective() + print() + + #### Thread-based Parallel Processing + test_number_calls_fitness_function_parallel_processing_process_1() + print() + test_number_calls_fitness_function_parallel_processing_process_2() + print() + test_number_calls_fitness_function_parallel_processing_process_5() + print() + test_number_calls_fitness_function_parallel_processing_process_5_patch_4() + print() + test_number_calls_fitness_function_parallel_processing_process_5_patch_4_multi_objective() + print() + + #### Multi-Objective Stop Criteria + test_number_calls_fitness_function_multi_objective_stop_criteria_str_single_value() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_str() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_str_decimal() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_list() + print() + test_number_calls_fitness_function_multi_objective_stop_criteria_list_decimal() + print() + + #### Single-Objective Stop Criteria + test_number_calls_fitness_function_single_objective_stop_criteria_str() + print() + test_number_calls_fitness_function_single_objective_stop_criteria_str_decimal() + print() + test_number_calls_fitness_function_single_objective_stop_criteria_list() + print() + test_number_calls_fitness_function_single_objective_stop_criteria_list_decimal() + print() + diff --git a/tests/test_save_solutions.py b/tests/test_save_solutions.py index 45216e1..537f4ea 100644 --- a/tests/test_save_solutions.py +++ b/tests/test_save_solutions.py @@ -5,7 +5,7 @@ sol_per_pop = 10 num_parents_mating = 5 -# TODO Verify that the each entry in 'solutions_fitness' and 'best_solutions_fitness' have values equal to the number of objectives. +# TODO Verify that each entry in 'solutions_fitness' and 'best_solutions_fitness' has values equal to the number of objectives. def number_saved_solutions(keep_elitism=1, keep_parents=-1, From ce702a9025b2ec195b4e3fabe4eb64d0ab3cfe1e Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sat, 5 Jul 2025 16:57:02 -0400 Subject: [PATCH 53/79] Fix selecting a unique value from gene space --- example2.py | 12 ++++++------ pygad/__pycache__/pygad.cpython-310.pyc | Bin 79860 -> 79860 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 16011 -> 16051 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 14713 -> 14713 bytes pygad/helper/misc.py | 2 ++ pygad/pygad.py | 2 +- test_gene_space_allow_duplicate_genes.py | 3 ++- 7 files changed, 11 insertions(+), 8 deletions(-) diff --git a/example2.py b/example2.py index 7383b7c..88d815c 100644 --- a/example2.py +++ b/example2.py @@ -9,14 +9,14 @@ def fitness_func(ga, solution, idx): num_parents_mating=5, sol_per_pop=10, num_genes=10, - random_seed=123, + random_seed=2, # mutation_type=None, # crossover_type=None, - random_mutation_min_val=1, - random_mutation_max_val=100, + # random_mutation_min_val=1, + # random_mutation_max_val=100, fitness_func=fitness_func, - gene_space=[30, None, 40, 50, None, 60, 70, None, None, None], - gene_type=int, + gene_space=[5], + gene_type=float, allow_duplicate_genes=False, save_solutions=True) @@ -25,7 +25,7 @@ def fitness_func(ga, solution, idx): ga_instance.run() # print(ga_instance.gene_space_unpacked) -# print(ga_instance.population) +print(ga_instance.population) """ gene_space=[[0, 0], diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc index 9f65c82877625b9ca5ff5549f774de1f75f5d3d6..383d6f076c02e0685a34aa3d3e692a7c81286854 100644 GIT binary patch delta 1248 zcmZvaYiJZ_6o&Vm*^BO&M1nCPB)UHZ2!<&fqz$x1D20l(vpQblHHyZ>yR{+2yJ?K8S#j4(lW6*#-86-efqCZ4Iq$iA zmqg5-h}qqtv^2L(KA+SbbYEz31s&lk5vnm;TY5G8Vcu%}Qq_K!;hY_t}~&E3&Qitps0{1jWK z2Y-`wQYEDCTbB%;G3vW_WnN;*_Y%5Yl(F4nl*F>LuCh6#`8jiCZ09H(Hg5MW!db)H zw-M*fy1rRXT&HFQdvS}ZR0MF7eo!$TH>~R_mc!W7XSaL~%yZ=a$whB7ZZddx#9xdX zR-GFfaKWnAFvl+IwP#=*E?OUsM%<1D<7#())_hj3XZRS+j3(NUh4K0^wl6ap87++C zj3Fy%v<_t9`ABq7{dinNr?uV#fwbgy2c-U2MRZa}0SXWy*93f~b#jp>j2L}00k!D1 zFcEo5UR?TgRqWzW1tYB#eb8@+stn+c{A93om}2(IfS`x-guvcw6XG&&Q&ATa~C@GutBuX8L;AR!epG zQ?2}|R{M0h4(&aQa~N@IF2-dfXzvsR^0=WsLcHThFQcDfgTy}+odORn(UC#9KK$g2 zawtw`e0T$sj{4;eMd*$nJ1QMgA6C42>Kt60J>*0pGI#Aro2@2Y8cv%6rC-4)`8xYW zGF$bQ>OJs^_w?gb%pLPw%5+7nke@cSX8UGoo76iMMY83j5=_sG$w=~M>0<^ChBlR; zqPU6um5if|GR?f*aND7BZs>x7&(m_w!GkTX_van3WwONPQLc}+7*u?#WV zBR{&EUBhk{CvdS;pSLu68m?qNm0sN?9&+#zqnPoSUQI&?2dJtHA88Hjy2dz1`^%7{ zB`@~_c4+i@IkK&1Wq1p;QpXGgo$VZGqly6h1^3v#FA;QXShKo%%g}94r|$xon3*oe l((}YzR;MxAs40Ma9HGttDm9g-u4TWS=9J?Dq;uQae*rETTQ&dy delta 1251 zcmZvadq`Ay6vyX#@65Gkv=Px1(QPbj=W0%xHC7Az3JJ=DCD_7+jUp?dn)atnt$*|& zcA+BJxC@8ajg1JFg~HmadvQy8S!QZax3#k9Wvi<>LgV%rH~XC#GY~J_&pr2??>Xo9 z`<-7nWDkez&Okzf(RYp;HR6J4ws-7OT_e`5J#1M|%p;VBYU30ve6Uy+NEZ_w_zj@=lM+ z&#-lN^fy{3Rzmvr#>wD0<6!^Jq!yO^B%#|yA=_<6ekj#`O`67zp=5JKC>RgRxE1*V z=MC@BI$SjO56#nXgIW|Ez+Y6Z!jE?PS;cJJw63dI4m4Rg4lHq`-{OLQGujz^9HN79 z)7tNZ3-oCs?A9R{KC;V#oo=i~v-Mdbp4-u2TMp*iERqW(YF=LSNALAv>%SJUKRGSUYJK^SWg#+2h(f*gA>Kyte8|l{k z>2f^?)3Fb61cOFtLnZ~haB3rkw|jATPDJW;wY1v3v?{R4vp`f-m5Yj%^F3l@O}oc! z^`#D9td%d;>WD7);TmRf8Y4_CxoE>69hd=s1~=3vh&LP=V%Q)7#%t=G0S{hLd>(4F zzF6+rJiI_H)%)b})X_a3wv=d6T&;NZ*bpXg>X>oV$=p>FZMMou($H)Qlwk$&^3{e# zG+Xr+=&#`w!*pmS7AE&gnXZVhvwF<2gg+oxGsA1z3gw z`aynl7rVwiD^B2Iu|BUTaTczm_DZiFCmwU~2_u)$MT4^tz;P-s#OJ;Sc3o#&U|eK0 zF)lHh8PU7`h^=_qR)kcmuMjVR+tf7&W!gOs+@)ead|40Jekf7qSi5pX#m2FNaMO=| qOiOahh3Xk%A*&M@cd6NrOw`jqew4WEypJIJ9rSS#K7pIt2HyfV?OW&o diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc index b1a1090339cce542c6c646c8ec72c6b5da7524d7..84001db4713ac660e627b647b17e21e1115f5973 100644 GIT binary patch delta 114 zcmeCK-CWC?&&$ij00a#aGc$f|&&$ij00g0}nHi}Yc@xE%{xEGW5dY1zxnF7~2jiy6n#!A5SeUC+C;u~* m+Wb$ske#t~bAfgu7vql2mrNtr86R%ewiRV$RGI8(R|EiD0~;#< diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc index 85a309de1f512292eff5678e0244ad6d7f8fae20..2ce6182865844b3581722349379ad89a1ddd51fc 100644 GIT binary patch delta 19 Zcmexa^s|U7pO=@50SGcDZsf|h1OP+Y1 Date: Sat, 5 Jul 2025 17:07:02 -0400 Subject: [PATCH 54/79] Fix selecting a unique value from gene space --- pygad/__pycache__/pygad.cpython-310.pyc | Bin 79860 -> 79900 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 16051 -> 16104 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 14713 -> 14743 bytes pygad/helper/misc.py | 3 +++ pygad/helper/unique.py | 2 +- pygad/pygad.py | 3 ++- test_gene_space_allow_duplicate_genes.py | 2 +- 7 files changed, 7 insertions(+), 3 deletions(-) diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc index 383d6f076c02e0685a34aa3d3e692a7c81286854..2b3bea3e37f373faecdf8496f49983404c3f6fe1 100644 GIT binary patch delta 1878 zcmZvc3v5$W7{~Ab-rn2wwUx208|=lS(6R>`j}GVJ1DS#Y43eOkAWpl%kTG`KVZd7$ z5))9DfH&qyfVde5A;Cl`SW!km zh}V=fL2acOvNY2w_-JI(6tC2X*U8+Y}b0@?RQnM&ixb8t)BUW3}#SD~z-ErpRJXyeU}IR%OOc{q*@_ z3=Ae*m@HX#aB;cht6iuDgKu_}0HNS#zf3k^Pw@AvWn$(oDzKGM6vG6vzUFvIpAjxc zNZ%eV#NOb=a1n4oPw8BO7QM04fzN|mI$OoOeKFyFvc8WEBN=1KJ~HyjAM{PPRwf>! zkPRGegF2S%Hoc(Bk#@2lUr zEUT2@!5Owu!Z@4`os{q@*fVw*5Dw+bm?954NBPea&JgHgs22#QL+i}2Kt93;xuIqY ziVZw%M*_U^QSN#@RBA_;0ekfmw=$B;NKr+|BOE8RvU$nKO5H>D3BmzF8=-|mZ|F@z zLnxSxqrleCatAWwaE)!s!W0~4*Rrr2VOEumi8vA3mW|P{MYhGmrQ1X`%&rW<5ScdQ z9J36C-x8+8*9mvn2Sc$0w;6^Z38z9S!!QlF%)G;~JH4AnxFvNZ1(p)-5$+TIWKTG` zf<)o^jmh88*WRAsjqB) zwCrX#(nX5^?oK6HH*3woaol6;bKyy+;_f*05zYKX;I9fn-SeaJdQ~|7cG@eiD;&Ep;!W_}N^zY%g$vO()QY7ATc`7T+L~4rKNt#=P zy95I*=K-5gh*xljt>u4DTFKS#vm*f|i`MC(A+5jN$d^!4p)TFQhK|7=#|`dt8`a-v z{t6+7aFunBK`p*ym18k6e=oUyCeQ^}={l>22}cNY>7rNYSF&tuc@Z4!##p=$Uq~HC zN3NY+8IN-1BF%TOQV;T-m&m@%Q7+G`m|MN*|J!8etuywi2U&?^y3->sR@D|wam0fR be9NwRFx4W{BA1X~VNVs|Y1pZQdSv_qw)X%< delta 1802 zcmZ`(3s6*L6u#fx-3#opfWXQ_5CkEvh=Ax)YBu@G_oU8P5rkU{BP1;RyU@s7G;?Yy zqO@E6Q`!v7G&arDly12`up}Rdg89zKrtF1?NeN{tO8?n4%gJ`_>*aZvWFGQar($R4%Q>45=ug-sB&_KZ@2f+tSmk z=T*(7DnBLGf%4XG+IEl4Vge)y&E!?eoYg<%B zxE7=ZJ7rCA8nqB-urs7FsNSrZ{d%9^N4U3ohBZYx&6=`7a$2+y(hDo}ZBsKKRj>9$ zHAQP?%3rQ%CQT`orwDI#tp=y{NqHSupm3$!<_y#CKM=M0qF@g9vn`?$sXRtJu_C)c zWDjpt3JrnS6sbP6KD7FX7{vuVaZ*!!Gbu(0rcwMO8cqLY-?ks3rhuk6!6`41)M1XF zy~htMGU(YI?Z9dEG{kcRF=5i#jYApRgH&zL2A=p5p{c?@2d&L zR{h4cMfhBQaB#V4pK!19?i!q^;3)5x@g`32Mg#0P&i5KHK{+Pkb$PEk;(f73+ym2o zF+S&8YQh{-(n(>nQv}Be=sL463H+k*Lc~v-alzCke3`y>3nm#w5@Q6MrUSxNoY`aM*V=8QHLUNoyDIp3J(rH3g&mCv%GDrNCq9rsz3>KE5RdwZkux?LQ9l z6RgDHDJf>Ir;hoireXnbmcKL_+Y|Z)M>ViDM4d@6K=3=kW$s8rDth^|X;_M@{7f2> z|K``+ksq6mH04sT&<^^hr(-?P&rjZkcQBwY+?5y+PolVg&Hru&M&k?C-0cuQOf&W~cUT(}q2Eem&G1AsU#0vzM2xMBwAADIZ z9zq}ACSH@xK>PANW7wE0CWLfz2JZuE>y)^-%fYaP+M}cyNLS4MV zj&aJjWPit-?T9ypi3;D~yX{DdBvVcBe?yryK>y8-L~P^X`Iv7p&{}HAZ{(%amEs^Ebc6xGDe{Ij2g}q9!Z8;&Kk~O22J5a=gHY-GMhI^-DKm6;_!xo z$=!;Qd{taFaG)tOd70uhHr9fo%)FAx)k!;nnjiusugP5G3S!5B zh{(ysrkadlljoY6Fz(%a$@CzbWIRNT21pvL31o&QQ&HmNH8y%crMGST8J|towk={z cnmo@oNFWlV5Nuc?h?O$Q&Vf;LvZGxQ0LXo0yZ`_I delta 349 zcmaD+ySbJxpO=@50SFo33ODE zB#_XQo_s;^+T^uLYZ$jpj#8e^#m36S$i&F@kBfctf8}U)(NK^|dl10`BIH0s_~d+T zQ^xYmOSLa@GKNkrH(O)1p z2zIGILE`A_RwoCEyZ^yS?-lJkJomhx_eJiys;{bXj>QvoE}};@ry-YCHQlsPAlzWY z^?3Fo8#ggo4>(uouJpvAWb*+(;A|nl0YAqv6X~s%PR53EAUvQeF%SAhHUc0LNs!NF*rmf7A43<2qSjl_=KtoQ6-@ zWS1V9d9*_Z<|ZnIBlBEJ)#1?E7!8`U^lg{rWy~5)CWb^x(4PX70eQd)LnJz$UB5IG qvIr#^P@?x%MmhyOqtDio9<<)Q-3W^+FkTVrI|5>F`KDKvxXsyxmdY`WdUmn zQw{TMh7{%+AT7xNWUgWb=jifD8#f9wsFw4n`Ihu74~{n_tWFax%JY{;4*bBfu9ZUE~KO zZZYST=HB8+%}XiB&&(?c(PS#}2Fdw=2u2{G$ylTXVuK6>O2Rc3v4I4bp{gf8(Qo2+ z0gBvWFU`wL%P-0;@|av|;LPYgd8dIPqvz(=2FYv!DIkM%;6&==ICELmOd#Kz2`G^T zWN5M#d4lAHK!g{Fa0U^vlNXqaGkQ+mVJ^d#01`-?e9K&gEd#{Np3Gu#L% Date: Sun, 6 Jul 2025 12:55:38 -0400 Subject: [PATCH 55/79] Fix selecting a unique value from gene space --- .DS_Store | Bin 10244 -> 8196 bytes example.py | 49 - example2.py | 41 - pygad/.DS_Store | Bin 8196 -> 10244 bytes pygad/__pycache__/__init__.cpython-310.pyc | Bin 205 -> 0 bytes pygad/__pycache__/pygad.cpython-310.pyc | Bin 79900 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 260 -> 0 bytes pygad/helper/__pycache__/misc.cpython-310.pyc | Bin 16104 -> 0 bytes .../helper/__pycache__/unique.cpython-310.pyc | Bin 14743 -> 0 bytes pygad/helper/misc.py | 3 - pygad/helper/unique.py | 14 +- pygad/pygad.py | 1 - .../__pycache__/__init__.cpython-310.pyc | Bin 332 -> 0 bytes .../__pycache__/crossover.cpython-310.pyc | Bin 6116 -> 0 bytes .../__pycache__/mutation.cpython-310.pyc | Bin 17807 -> 0 bytes pygad/utils/__pycache__/nsga2.cpython-310.pyc | Bin 7277 -> 0 bytes .../parent_selection.cpython-310.pyc | Bin 14555 -> 0 bytes .../__pycache__/__init__.cpython-310.pyc | Bin 237 -> 0 bytes .../__pycache__/plot.cpython-310.pyc | Bin 13830 -> 0 bytes test_adaptive_mutation.py | 2393 ----------------- test_allow_duplicate_genes.py | 634 ----- test_crossover_mutation.py | 287 -- test_gene_constraint.py | 223 -- test_gene_space.py | 1605 ----------- test_gene_space_allow_duplicate_genes.py | 1140 -------- test_gene_type.py | 214 -- test_lifecycle_callbacks_calls.py | 247 -- test_number_fitness_function_calls.py | 597 ---- test_save_solutions.py | 1289 --------- test_stop_criteria.py | 234 -- .../test_gene_space_allow_duplicate_genes.py | 7 +- 31 files changed, 13 insertions(+), 8965 deletions(-) delete mode 100644 example.py delete mode 100644 example2.py delete mode 100644 pygad/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/__pycache__/pygad.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/misc.cpython-310.pyc delete mode 100644 pygad/helper/__pycache__/unique.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/crossover.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/mutation.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/nsga2.cpython-310.pyc delete mode 100644 pygad/utils/__pycache__/parent_selection.cpython-310.pyc delete mode 100644 pygad/visualize/__pycache__/__init__.cpython-310.pyc delete mode 100644 pygad/visualize/__pycache__/plot.cpython-310.pyc delete mode 100644 test_adaptive_mutation.py delete mode 100644 test_allow_duplicate_genes.py delete mode 100644 test_crossover_mutation.py delete mode 100644 test_gene_constraint.py delete mode 100644 test_gene_space.py delete mode 100644 test_gene_space_allow_duplicate_genes.py delete mode 100644 test_gene_type.py delete mode 100644 test_lifecycle_callbacks_calls.py delete mode 100644 test_number_fitness_function_calls.py delete mode 100644 test_save_solutions.py delete mode 100644 test_stop_criteria.py diff --git a/.DS_Store b/.DS_Store index 24f42ca2d5ca16fd106016873d8cb448533b0e22..fcaff950d423a13d205b6cdde9babb188a7afd17 100644 GIT binary patch delta 128 zcmZn(XmOBWU|?W$DortDU;r^WfEYvza8E20o2aMAD6lbLH$S7mWFCR=&1VHf85xZx zr%SqS))0EgH2J0k_vBp?YZ#3-r%P^N5@ZJI0|E(dAmIu!Vq@WV=E?jjfgFqwgBd2r P^Gu!0D;Wvav6cw{v&kEZ literal 10244 zcmeHM&2Jk;6n~TE!*1HfNu7iSMKX$zdMIgHft08RTqh9<6cWWDBs67Nuh;I9^^VzH z=YtR_5+}Iw2k-}QhktjEP88 zI`w-*B_hh9GA&%gqetQIT+fxhxMvzvK%PjFx8h2x=E~YRZrz~~&EhTPmO>^KqGL90Iv@vRHo&aoMDoq^FtlJEzjSs1N>9MG5y)9b6U#iE}>l{hZ;uN;VykNiAxVi}oz&;dvQhByCPq?EbN1O6bJq5Q zc6}K{z+}tLVGZKQK5nlph)%dQyEV_p6~9c7lrOKjya$DcxGPZPqp&d*3J& z7o``Pj%WKyz^U6{+4;cn>Ta2`d(t0`TN-cO%jp_LfAW9`KQ5#E4Rh*EQc}l{0Vt9-%r@ey7`*jT!}-i5mC~UhKAFDmFan+4-6BX4H|#{`eYZ z?J8{{r#`2z=^Of%zM~)L7y6C<6oaBDro~+`FCL1w#Ut^)*c46Sik8@m6!djN%^9C4 zVjnMwQ14ZJ&y`*iy)1D<&Hpy}$ofx^X%^OrSyTxx+;m2$wc^J_X69)fS@w{Y@MRfN zt!xgdRU;`;fE}AYr4Bi&)xw(xYk{(PKw;XV%I&0l4xBdlJMg;4ileeS$$tyHHe^CF zIKxrfXgjorUIYGi@MfwW+-zQ?F|~$GgZoUQi?vVEK6KTUPcC#@N+auUg40vnJ$&W$ zC8c>Pq1jUL2;jL?zWV>nTaJ+UBN3s=gwC!v20Y}^GAcfNbyNg;c*xO9X&dXQEdZt_ zboSf>I3g(>x1Xbf!)h&8|6+hY}%B!;gfG;n+ykh36fseO;pj6AL> z6n25@Ss%~q^ZO#A)b~XqO0Om?c$h~ms=)@wya9V*Jcn3s17AH@H#Gtp0gZr0;Bp~wB^qvt z@Bf!C{{R1SfucvN5x8Usi2kMOQU!zVUJ+!+&v)%5)Q?fQFyBc;l?Wc{Iv!MA$K!>s v<8Pxf?;KMuXqRJp5>fP^{O5lL;CHYxtk3`5= 90 - -ga_instance = pygad.GA(num_generations=100, - num_parents_mating=10, - sol_per_pop=20, - num_genes=num_genes, - mutation_num_genes=6, - fitness_func=fitness_func, - init_range_low=1, - init_range_high=100, - # suppress_warnings=True, - random_mutation_min_val=1, - random_mutation_max_val=100, - mutation_by_replacement=True, - # gene_type=[float, 1], - gene_type=[float, [float, 5], [float, 4], [float, 3], [float, 2], [float, 1]], - # save_solutions=True, - # allow_duplicate_genes=False, - # gene_space=numpy.unique(numpy.random.uniform(1, 100, size=100)), - gene_space=[range(100), {"low": 1, "high": 100}, 2.5891221, [1,2,3,4], None, numpy.unique(numpy.random.uniform(1, 100, size=4))], - # gene_space=numpy.linspace(1, 100, 200), - gene_constraint=[const,lambda x: x[1]>=90,lambda x: x[2]<98,lambda x: x[3]<98,lambda x: x[4]<98,lambda x: x[5]<98], - ) - -# print(ga_instance.initial_population) - -ga_instance.run() - -# print(ga_instance.gene_space_unpacked) -print(ga_instance.population) - -print(calls) \ No newline at end of file diff --git a/example2.py b/example2.py deleted file mode 100644 index 88d815c..0000000 --- a/example2.py +++ /dev/null @@ -1,41 +0,0 @@ -import pygad -import numpy -import random - -def fitness_func(ga, solution, idx): - return random.random() - -ga_instance = pygad.GA(num_generations=1, - num_parents_mating=5, - sol_per_pop=10, - num_genes=10, - random_seed=2, - # mutation_type=None, - # crossover_type=None, - # random_mutation_min_val=1, - # random_mutation_max_val=100, - fitness_func=fitness_func, - gene_space=[5], - gene_type=float, - allow_duplicate_genes=False, - save_solutions=True) - -print(ga_instance.initial_population) - -ga_instance.run() - -# print(ga_instance.gene_space_unpacked) -print(ga_instance.population) - -""" -gene_space=[[0, 0], - [1, 2], - [2, 3], - [3, 4], - [4, 5], - [5, 6], - [6, 7], - [7, 8], - [8, 9], - [9, 10]], -""" diff --git a/pygad/.DS_Store b/pygad/.DS_Store index 8a926cf11cdeec628aff003864eb85651f231ff9..3e6d027cc6e2afc8222e9c82ed6348bdefdf5e7b 100644 GIT binary patch delta 441 zcmZp1XbF&DU|?W$DortDU{C-uIe-{M3-C-V6q~50$SA!rU^hRb^kg1^cy?xnWQIJ3 zyvcioni!2HI|w@qLq*Dhi}G^v^U{GD87F55=uXZS;D)Pm6-{SIgy=+3H%UO7lZ62! zhhnBETnt6^E`fazJ16fE^x|h{$Yw}oC<5v&2AKf2%UK#t2E`0!A$3+JRNDl>d}LLd z)rBM&8I3kO2&(`okS`~%5K-P(x|L0k8R$PCP~ZjjD zC+7&sPo5{tJGowX9izcyJ(1qY>qJ#HZxj<{1aqAyb4fgevh*dDH=pLQVHRWt8VUpw d+(5z=Wd6p&@640=RRTE}A+|DXj^~-f3;g`kg6+F^GcG?{L(7F4DurrhF=k1tCtD$dN$i;rK)P{ayU2_}9y=!X^qCG-lIYq X;;_lhPbtkwwF6mS%mO4h7&w>!VCOT1 diff --git a/pygad/__pycache__/pygad.cpython-310.pyc b/pygad/__pycache__/pygad.cpython-310.pyc deleted file mode 100644 index 2b3bea3e37f373faecdf8496f49983404c3f6fe1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79900 zcmeFa349#KbtXO+27>`GxWQB8h&ljJBzVb^Op&%JQKDrjv@B7UJeD#Zq6Zg&0oXGT z#c{({Ov$3+D5mTrP8=%+?PMKo?)4^4cAdjlj?E_C&F#r1&avK&lHDA3Hk&Yx`TxFG zM|aNwfRr3(fB#ULMt619t5>gHy?XD}tLlaB?sNqI{>%Gjrhni|vB;nDA^4APUx#0L z1s5mcMCT$o^*5S}%5y9ilfUs?T>d6 z`_5cvG;%EBv^mMAB2Kc{G21oUjcaDM2iM-&K3w}}2a4(0!Py~Phi5kwJ7+h}ZaUYI z+w8PEsiz{jEl%{o$XLfqz%p`tELv+@s?5xlag&@b&Mg*Q+;u!YQ(h{}%~XpM%oY51 z10G(7UwLaZ5-CR5vRtefFD7Q&@Jr&?j$aDD4*b&ib>i2BUw1Jx)tT!l_Ts(|zkd7% zkk*zPEcO%!H$-wnPE`LFbK=EbC&6|SYovDImv%aF?Q*&$S4OU;1mE&M{XBVt)8q6$ z70nGheNI1Gy}=o92Jv^JGvo~8?-|f!0bDeWNa%7zwoEwpHZO2H*XvbK`&W?0PrX%C*ac**U;r$Mw@ZMVN z*pcHeef9F?%P%EBv0A)bF297Q%P)a+k(c63>nhKbrsj(I#f6ztrIvIHC1+v&?pnK3 zoGdKORi=;!|Ni>t&CP31OpK{CxgN%~6Tdt0EC2aOBywS(`iI`A6J3h9dn=KZXeD+r zx;*NQuEZ;m*~G9`nZhw@6DlEVC1-9o|k=VkPa3@vhWY z>GVbcsq0+Ky|t9CbUX1%#v45s{oaU^cp-i+`mU%q>P7NB-UzO}-iTmwU#Yv&H`~85 z;6-M838xag&v@6Li&h4K@q~9hN)45|oi_X(2J|GL>YlT?+EuEb%R^21 z4M^Rr^L~%bn^LKPyql4CqrBgQcNOn5b-&OiwBH4I*DUWw`fk&Xsc7Ze*&Q1qj{`^d8qgH) zP@+fW5jsfO3(?b2rx&!?bovV#7oo$c7|QLoY1b{WUEf8lR<6Go0cL%Y;}NGHDK}h< z2-f#G1Hq@#-4^pfmHxev7h=RX0B1RQP>hztK|R^T0mvRC>_j zwN<4Ds9@lb3Nbesf^#VZ=Mt#kL%mcQZ3We)U*qE$pn@4=+ff*D8ehcYF^%gL$RXKcUUJ4_0iyfhcx_@{fcKSDL zD@SC6j-qCaVw6qj(cRIs>{n6tuWi|}=4DIUZH{I=vQyPy^W3ERM(S2PkpfSQc^$a! z^wOFu;_e+DX56V5Qg4z`wF`3a186g3qR54~JBu9XC6w4L{W}ZDX=fMv4Qb;{E1y&; z(%VuZ1aBb(@4SU4WnDf4nl6i{P}w(y%6`1A?8lqn@`E9G9~X-3s%&+3dui!Kp$5wV z{~%8!++We08+X5~IXCWp-crBF=)ekgJwak>|++tbjx zA49E3m0nG_A3zzuR}=2rSSHk~3HSAcW!rzVw+C%_l{b#-Eh;bWZdGt_x8ntjBxnx? zE_*lOKC5y{?#mZaz*gtA(m$*NJpssOR7Ym_K!T3px6^sua~w;1HUG4^Q(jWFz5Hd( zT?zLeY>SSFG|YHO^wvJe%r^HSKnD)y38gzuMUlS2Vt$90MBR64U9DZeAF=O`3fJ6a z-UFNcz-$v>90N@-_u*;(sW_i)nY+~+z&*yd`1^V-v63i%0BJXSn<*b=Z}K)d_dJ(? zCi(le6>k8}XaP#ytFXtNrVXB!m@~d H2l?P7HH7Vs%?{D8&r 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 AB>(^b diff --git a/pygad/helper/__pycache__/__init__.cpython-310.pyc b/pygad/helper/__pycache__/__init__.cpython-310.pyc deleted file mode 100644 index b92603d2b45f76f85219da6837cef6d3d8aed1e0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 260 zcmYjJK~BRk5VW17sv!A8uerp)6@*#|skaIVxa4A0^(wZA?O;1dffw0_n=;J|0Z(@hGvZuR!EXa+7m-tN|R=&S<6soZ6@;bJyJL>bxz7soTp&K|g@^sorNu^R*D4;DdHnh#%sM6eK!s`CGt% z@09_GulxzZdvpWef`zCLo4#{iM`fO;sGQ~h{rud|h0s<;M1& diff --git a/pygad/helper/__pycache__/misc.cpython-310.pyc b/pygad/helper/__pycache__/misc.cpython-310.pyc deleted file mode 100644 index 123c531aadb87af95284cd52653b805ece4622da..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16104 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 diff --git a/pygad/helper/__pycache__/unique.cpython-310.pyc b/pygad/helper/__pycache__/unique.cpython-310.pyc deleted file mode 100644 index 9a4f0d878facb06a20e518256ff42f50bd215673..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14743 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