From 55e6672aa1da525057fefdeb08fda1ed53672ad0 Mon Sep 17 00:00:00 2001 From: Dex Date: Tue, 8 Mar 2022 11:30:53 -0300 Subject: [PATCH 1/4] Altered to allow function_inputs and desired_output to be passed as parameters on initiation. --- pygad.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/pygad.py b/pygad.py index 7d058150..258653d7 100644 --- a/pygad.py +++ b/pygad.py @@ -47,7 +47,11 @@ def __init__(self, save_best_solutions=False, save_solutions=False, suppress_warnings=False, - stop_criteria=None): + stop_criteria=None, + # Initialization for data parameters on fitness function + desired_output=None, + function_inputs=None + ): """ The constructor of the GA class accepts all parameters required to create an instance of the GA class. It validates such parameters. @@ -628,14 +632,26 @@ def __init__(self, elif (self.keep_parents > 0): # Keep the specified number of parents in the next population. self.num_offspring = self.sol_per_pop - self.keep_parents + # Initialization for data parameters for fitness function + self.fitness_function_extra_data_set = False + if not desired_output.empty and not function_inputs.empty: + self.fitness_function_extra_data_set = True + self.desired_output = desired_output + self.function_inputs = function_inputs + # Check if the fitness_func is a function. if callable(fitness_func): # Check if the fitness function accepts 2 paramaters. if (fitness_func.__code__.co_argcount == 2): self.fitness_func = fitness_func + elif self.fitness_function_extra_data_set and (fitness_func.__code__.co_argcount == 4): + self.fitness_func = fitness_func else: self.valid_parameters = False - raise ValueError("The fitness function must accept 2 parameters:\n1) A solution to calculate its fitness value.\n2) The solution's index within the population.\n\nThe passed fitness function named '{funcname}' accepts {argcount} parameter(s).".format(funcname=fitness_func.__code__.co_name, argcount=fitness_func.__code__.co_argcount)) + if not self.fitness_function_extra_data_set: + raise ValueError("The fitness function must accept 2 parameters:\n1) A solution to calculate its fitness value.\n2) The solution's index within the population.\n\nThe passed fitness function named '{funcname}' accepts {argcount} parameter(s).".format(funcname=fitness_func.__code__.co_name, argcount=fitness_func.__code__.co_argcount)) + else: + raise ValueError("The fitness function with extra_data must accept 4 parameters:\n1) A solution to calculate its fitness value.\n2) The solution's index within the population\n3) The desired output.\n4) The function input\n\nThe passed fitness function named '{funcname}' accepts {argcount} parameter(s).".format(funcname=fitness_func.__code__.co_name, argcount=fitness_func.__code__.co_argcount)) else: self.valid_parameters = False raise ValueError("The value assigned to the fitness_func parameter is expected to be of type function but ({fitness_func_type}) found.".format(fitness_func_type=type(fitness_func))) @@ -1156,7 +1172,10 @@ def cal_pop_fitness(self): # Use the parent's index to return its pre-calculated fitness value. fitness = self.previous_generation_fitness[parent_idx] else: - fitness = self.fitness_func(sol, sol_idx) + if not self.fitness_function_extra_data_set: + fitness = self.fitness_func(sol, sol_idx) + else: + fitness = self.fitness_func(sol, sol_idx, self.desired_output, self.function_inputs) if type(fitness) in GA.supported_int_float_types: pass else: From b92fa919c2a4914a4b683c6cf236984e19856a0c Mon Sep 17 00:00:00 2001 From: Dex Date: Fri, 11 Mar 2022 11:07:57 -0300 Subject: [PATCH 2/4] Altered function_inputs and desired_output to accept numpy.array too --- pygad.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pygad.py b/pygad.py index 258653d7..00c691ea 100644 --- a/pygad.py +++ b/pygad.py @@ -634,10 +634,20 @@ def __init__(self, # Initialization for data parameters for fitness function self.fitness_function_extra_data_set = False - if not desired_output.empty and not function_inputs.empty: - self.fitness_function_extra_data_set = True - self.desired_output = desired_output - self.function_inputs = function_inputs + try: + if not desired_output.empty and not function_inputs.empty: + self.fitness_function_extra_data_set = True + self.desired_output = desired_output + self.function_inputs = function_inputs + except: + pass + try: + if desired_output.size > 0 and function_inputs.size > 0: + self.fitness_function_extra_data_set = True + self.desired_output = desired_output + self.function_inputs = function_inputs + except: + pass # Check if the fitness_func is a function. if callable(fitness_func): From b955d2d697a7007a367df5e4caa4ac1eaa3bd9f3 Mon Sep 17 00:00:00 2001 From: Dex Date: Thu, 31 Mar 2022 09:35:40 -0300 Subject: [PATCH 3/4] Altered added a third possible input for auxiliary data --- pygad.py | 33 ++++++++++++++++++++++++--------- 1 file changed, 24 insertions(+), 9 deletions(-) diff --git a/pygad.py b/pygad.py index 00c691ea..01ae164a 100644 --- a/pygad.py +++ b/pygad.py @@ -50,7 +50,8 @@ def __init__(self, stop_criteria=None, # Initialization for data parameters on fitness function desired_output=None, - function_inputs=None + function_inputs=None, + auxiliary_data=None ): """ @@ -633,19 +634,25 @@ def __init__(self, self.num_offspring = self.sol_per_pop - self.keep_parents # Initialization for data parameters for fitness function - self.fitness_function_extra_data_set = False + self.fitness_function_extra_data_set = 0 # no extra data try: if not desired_output.empty and not function_inputs.empty: - self.fitness_function_extra_data_set = True + self.fitness_function_extra_data_set = 1 self.desired_output = desired_output self.function_inputs = function_inputs + if not auxiliary_data.empty: + self.auxiliary_data = auxiliary_data + self.fitness_function_extra_data_set = 2 except: pass try: if desired_output.size > 0 and function_inputs.size > 0: - self.fitness_function_extra_data_set = True + self.fitness_function_extra_data_set = 1 + if auxiliary_data: + self.fitness_function_extra_data_set = 2 self.desired_output = desired_output self.function_inputs = function_inputs + self.auxiliary_data = auxiliary_data except: pass @@ -654,14 +661,18 @@ def __init__(self, # Check if the fitness function accepts 2 paramaters. if (fitness_func.__code__.co_argcount == 2): self.fitness_func = fitness_func - elif self.fitness_function_extra_data_set and (fitness_func.__code__.co_argcount == 4): + elif self.fitness_function_extra_data_set == 1 and (fitness_func.__code__.co_argcount == 4): + self.fitness_func = fitness_func + elif self.fitness_function_extra_data_set == 2 and (fitness_func.__code__.co_argcount == 5): self.fitness_func = fitness_func else: self.valid_parameters = False - if not self.fitness_function_extra_data_set: + if self.fitness_function_extra_data_set == 0: raise ValueError("The fitness function must accept 2 parameters:\n1) A solution to calculate its fitness value.\n2) The solution's index within the population.\n\nThe passed fitness function named '{funcname}' accepts {argcount} parameter(s).".format(funcname=fitness_func.__code__.co_name, argcount=fitness_func.__code__.co_argcount)) - else: + elif self.fitness_function_extra_data_set == 1: raise ValueError("The fitness function with extra_data must accept 4 parameters:\n1) A solution to calculate its fitness value.\n2) The solution's index within the population\n3) The desired output.\n4) The function input\n\nThe passed fitness function named '{funcname}' accepts {argcount} parameter(s).".format(funcname=fitness_func.__code__.co_name, argcount=fitness_func.__code__.co_argcount)) + elif self.fitness_function_extra_data_set == 2: + raise ValueError("The fitness function with extra_data and auxiliary data must accept 5 parameters:\n1) A solution to calculate its fitness value.\n2) The solution's index within the population\n3) The desired output.\n4) The function input\n\nThe passed fitness function named '{funcname}' accepts {argcount} parameter(s).".format(funcname=fitness_func.__code__.co_name, argcount=fitness_func.__code__.co_argcount)) else: self.valid_parameters = False raise ValueError("The value assigned to the fitness_func parameter is expected to be of type function but ({fitness_func_type}) found.".format(fitness_func_type=type(fitness_func))) @@ -1182,10 +1193,14 @@ def cal_pop_fitness(self): # Use the parent's index to return its pre-calculated fitness value. fitness = self.previous_generation_fitness[parent_idx] else: - if not self.fitness_function_extra_data_set: + fitness = 0 + if self.fitness_function_extra_data_set == 0: fitness = self.fitness_func(sol, sol_idx) - else: + elif self.fitness_function_extra_data_set == 1: fitness = self.fitness_func(sol, sol_idx, self.desired_output, self.function_inputs) + elif self.fitness_function_extra_data_set == 2: + fitness = self.fitness_func(sol, sol_idx, self.desired_output, self.function_inputs, self.auxiliary_data) + if type(fitness) in GA.supported_int_float_types: pass else: From 247c0642332d2d46ad9c33338f7b38d2f1c98628 Mon Sep 17 00:00:00 2001 From: Dex Date: Mon, 4 Apr 2022 10:53:19 -0300 Subject: [PATCH 4/4] Better test for parameter auxiliary_data --- pygad.py | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/pygad.py b/pygad.py index 01ae164a..31ff6aca 100644 --- a/pygad.py +++ b/pygad.py @@ -4,6 +4,8 @@ import pickle import time import warnings +from multiprocessing import Process, Queue + class GA: @@ -648,7 +650,7 @@ def __init__(self, try: if desired_output.size > 0 and function_inputs.size > 0: self.fitness_function_extra_data_set = 1 - if auxiliary_data: + if not self.test_numpy_array_empty(auxiliary_data): self.fitness_function_extra_data_set = 2 self.desired_output = desired_output self.function_inputs = function_inputs @@ -938,6 +940,31 @@ def __init__(self, self.last_generation_offspring_mutation = None # A list holding the offspring after applying mutation in the last generation. self.previous_generation_fitness = None # Holds the fitness values of one generation before the fitness values saved in the last_generation_fitness attribute. Added in PyGAD 2.26.2 + @staticmethod + def test_numpy_array_empty(np_array): + is_empty = False + test_performed = True + try: + if np_array: + is_empty = False + else: + is_empty = True + except: + test_performed = False + + if not test_performed: + test_performed = True + try: + if np_array.size == 0: + is_empty = True + else: + is_empty = False + except: + is_empty = True + test_performed = False + + return is_empty + def round_genes(self, solutions): for gene_idx in range(self.num_genes): if self.gene_type_single: