Genetic Algortihm (GA)

This is a sample taken from my upcoming book about CI-based optimization. Should you be interested in this, I can start posting more, and if you have any questions on this, feel free to contact me.


import numpy as np
import matplotlib.pyplot as plt

def init_generator(num_variable, pop_size, min_val, max_val):
    '''num_variable = Choromo size or number of variables;
    pop_size = population_size'''
    return np.random.uniform(low=min_val,high=max_val,size=(pop_size,num_variable))

def selection(pop, obj_func, R, minimizing=True, method='ranking selection'):
    '''pop = population_set,
    obj_func = Objective function or cost function,
    R = number of parents you want to select,
    method = {'roulette wheel', ranking selection', 'tournament selection'}
    Roulette wheel cannot be used for minimization because of the scaling. 
    Furthermore, it also cannot be used when there are negative or null fitnesses 
    because their probability would be negative or null.'''
    if method == 'ranking selection':
        of = np.apply_along_axis(func1d=obj_func,axis=1,arr=pop)
        index = np.argsort(of)
        if minimizing:a
            return pop[index][:R]
        else:
            return pop[index[::-1]][:R]
        
    elif method == 'roulette wheel':
        try:
            if not minimizing:
                of = np.apply_along_axis(func1d=obj_func,axis=1,arr=pop)
                selection_prob = of/np.sum(of)
                pop_index = np.random.choice(a=np.arange(len(pop)),size=R,replace=True,p=selection_prob)
                return pop[pop_index]
            else:
                raise TypeError
        except:
            raise TypeError('Roulette wheel cannot be used for minimization, nor negative or null fitnesses value.')
    
    elif method == 'tournament selection':
        index=np.random.choice(a=np.arange(len(pop)),size=R,replace=True)
        return pop[index]
    
    else:
        raise NameError(f'method {method} is not defined.')

def cross_over(pop,pc,method='uniform'):
    '''method = {'one_point', 'two_points', 'uniform'}
    Note that to use two points methods you have to have at least have four variables.'''
    index = np.random.uniform(size=len(pop))
    selected_parents = pop[index<=pc]
    parents_indeces = np.random.randint(0,len(selected_parents),size=(2,len(selected_parents)//2))
    parents_1, parents_2 = selected_parents[parents_indeces[0]], selected_parents[parents_indeces[1]]
    
    if method == 'one_point':
        C = np.random.randint(pop.shape[1]-1)
        crossover_index = np.where(np.arange(pop.shape[1])<=C,0,1)
        childs_1 = np.where(crossover_index,parents_1,parents_2)
        childs_2 = np.where(crossover_index,parents_2,parents_1)
        childs = np.concatenate((childs_1,childs_2),axis=0)
        return childs
    
    elif method == 'two_points':
        if pop.shape[1]>=4:
            C1 = np.random.randint(1,pop.shape[1]/2)
            C2 = np.random.randint(pop.shape[1]/2,pop.shape[1]-1)
            crossover_index = np.where((np.arange(pop.shape[1])<C1)|(np.arange(pop.shape[1])>C2),0,1)
            childs_1 = np.where(crossover_index,parents_1,parents_2)
            childs_2 = np.where(crossover_index,parents_2,parents_1)
            childs = np.concatenate((childs_1,childs_2),axis=0)
            return childs
        else:
            raise ValueError('To use two points methods you have to have at least four variables.')
    
    elif method == 'uniform':
        crossover_index = np.random.randint(0,2,pop.shape[1])
        childs_1 = np.where(crossover_index,parents_1,parents_2)
        childs_2 = np.where(crossover_index,parents_2,parents_1)
        childs = np.concatenate((childs_1,childs_2),axis=0)
        return childs
    
    else:
        raise NameError(f'method {method} is not defined.')

def mutation(pop,pm,min_val,max_val,current_iteration,iteration,d0,method='uniform'):
    '''method = {'uniform', 'nonuniform'}'''
    indeces = np.random.uniform(size=pop.shape)<pm
    if method == 'uniform':
        mutated_values = init_generator(pop.shape[1],pop.shape[0],min_val,max_val)
        mutated_pop = np.where(indeces,mutated_values,pop)
        return mutated_pop
    
    elif method == 'nonuniform':
        mutated_values = init_generator(pop.shape[1],pop.shape[0],min_val,max_val)
        d=d0*((iteration-current_iteration)/iteration)
        mutated_pop = np.where(indeces,np.random.uniform(pop-d,pop+d),pop)
        return mutated_pop
        
    else:
        raise NameError(f'method {method} is not defined.')

def GA_algorithem(pop_size,num_variable,obj_func,R,
                  min_val,max_val,pm=.2,pc=.7, d0=2,
                  iteration=1000,minimizing=True,
                  full_result=False,selection_method='ranking selection',
                  crossover_method='uniform',mutation_method='uniform'):
    '''selection_method = {'roulette wheel', 'ranking selection', 'tournament selection'}
    crossover_method = {'one_point', 'two_points', 'uniform'}
    mutation_method = {'uniform', 'nonuniform'}'''
    results=np.zeros(iteration)
    NFE=np.zeros(iteration)
    NFE_value=0
    pop=init_generator(num_variable,pop_size,min_val,max_val)
    for i in range(iteration):
        selected_parents=selection(pop,obj_func,R,minimizing=minimizing,method=selection_method)
        if selection_method=='ranking selection':
            NFE_value+=pop_size
        childs=cross_over(selected_parents,pc,method=crossover_method)
        mutated_childs=mutation(pop,pm,min_val,max_val,current_iteration=i,
                                iteration=iteration,d0=d0,method=mutation_method)
        all_results=np.concatenate((pop,childs,mutated_childs),axis=0)
        pop=selection(all_results,obj_func,pop_size,minimizing)
        NFE_value+=len(all_results)
        results[i]=obj_func(pop[0])
        NFE[i]=NFE_value
    
    if not full_result:
        return pop[0], obj_func(pop[0])
    else:
        return pop[0], obj_func(pop[0]), results, NFE