GeneticAlgorithmPython icon indicating copy to clipboard operation
GeneticAlgorithmPython copied to clipboard

FEATURE REQUEST: args to pass parameter to fitness function

Open qelloman opened this issue 3 years ago • 5 comments

First of all, thank you for the great repository.

The thing we need to improve about pygad is args for fitness function. Currently, fitness function takes only two inputs: solution and solution index. However, sometimes we also need to take extra inputs for the fitness function.

In the example of diff_evol from scipy, it has "args" to pass parameters. https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.differential_evolution.html

If you think this is a good feature, I will try to implement this by myself.

qelloman avatar Oct 21 '21 20:10 qelloman

Are there any plans to merge this in pygad?

Also *args for mutation functions would be really useful :)

huku- avatar Apr 13 '23 13:04 huku-

The pull request https://github.com/ahmedfgad/GeneticAlgorithmPython/pull/73 suggests creating a new parameter called args. Then the library just passes this parameter to each call to the fitness function without changes.

Since the passed args are static, I prefer using this solution instead of asking the library to do such stuff.

It just creates a wrapper function for the fitness function. The fitness function accepts args without having to define it as a global variable.

import pygad
import numpy

function_inputs = [4,-2,3.5,5,-11,-4.7]
desired_output = 44

def fitness_func_wrapper(ga_instanse, solution, solution_idx):
    def fitness_func(ga_instanse, solution, solution_idx, *args):
        print("args", args)
        output = numpy.sum(solution*function_inputs)
        fitness = 1.0 / (numpy.abs(output - desired_output) + 0.000001)
        return fitness
    args = [4, 6, 10]
    fitness = fitness_func(ga_instanse, solution, solution_idx, *args)
    return fitness

def on_generation(ga_instance):
    ga_instance.population[4:] = numpy.ones((6, num_genes))

num_genes = len(function_inputs)
sol_per_pop = 10

ga_instance = pygad.GA(num_generations=3,
                       num_parents_mating=5,
                       fitness_func=fitness_func_wrapper,
                       sol_per_pop=sol_per_pop,
                       num_genes=num_genes,
                       on_generation=on_generation,
                       suppress_warnings=True)

ga_instance.run()

ahmedfgad avatar Apr 27 '23 14:04 ahmedfgad

I would also be in favor of implementing this feature. In my case, the definition of the optimization case contains one integer gene, which correlates with several other parameters that have specific values for each integer value of the gene. For this case it would be great to be able to pass these correlated values into ga_instance, e.g., as a dictionary, to have access to them from fitness_func. I understand that the args argument could be used to achieve this goal.

I also tried using the fitness_func_wrapper setup proposed by you, @ahmedfgad, but it does not seem work for this specific case, since I also need to refer to dynamically updated global variables to achieve this goal, which I would prefer to circumvent.

timorichert avatar Sep 20 '23 10:09 timorichert

@timorichert,

So, your args are dynamic. You can simply create a new instance attribute to ga_instance and fetch it inside the fitness function.

If the args change after each generation, then you can edit them inside the on_generation() calback function.


def fitness_func_wrapper(ga_instanse, solution, solution_idx):
    # Access args using ga_instance.args
    fitness = ...
    return fitness

def on_generation(ga_instance):
    ga_instance.args = [...]

ga_instance = pygad.GA(...)

ga_instance.args = None

ga_instance.run()

ahmedfgad avatar Sep 20 '23 16:09 ahmedfgad

@ahmedfgad, thanks a lot for that, that did the job!

timorichert avatar Sep 21 '23 09:09 timorichert