From 9ffa344fae699fb5859b97d3eaedd4d4170f3ef9 Mon Sep 17 00:00:00 2001 From: Ahmed Gad Date: Sat, 7 Sep 2024 12:55:42 -0400 Subject: [PATCH 01/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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/11] 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'],