diff --git a/pygad.py b/pygad.py index 7d058150..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: @@ -47,7 +49,12 @@ 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, + auxiliary_data=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 +635,46 @@ 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 = 0 # no extra data + try: + if not desired_output.empty and not function_inputs.empty: + 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 = 1 + 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 + self.auxiliary_data = auxiliary_data + except: + pass + # 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 == 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 - 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 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)) + 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))) @@ -901,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: @@ -1156,7 +1220,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: - fitness = self.fitness_func(sol, sol_idx) + fitness = 0 + if self.fitness_function_extra_data_set == 0: + fitness = self.fitness_func(sol, sol_idx) + 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: