def gga_chromosome(self, s=None, delta=None, alpha=None):
        """

        :param s:
        :param delta:
        :param alpha:
        :return: The function creates the chromosomes of the population of a grid-based genetic algorithm
         if not given it uses the parameters that the object has itself.
        """
        s = s if s is not None else self.s
        delta = delta if delta is not None else self.delta
        alpha = alpha if alpha is not None else self.alpha
        ga_tools.check(len(s) == len(delta) and len(delta) == len(alpha),
                       "Delta, Alpha and S must have the same number of elements (equal to the number of dimensions")
        return s * delta + alpha
def blend(parents, prob, upper, lower, alpha=0.5):
    """
    The following method applies a blend crossover to a matrix of chromosomes.
    
    parents is the chromosomes matrix to apply the function
    prob is the probability to apply the recombination
    upper and lower are the upper and lower bounds of the population
    alpha is the parameter used for blend recombination
    """

    # Check the input var and shuffle the elements
    ga_tools.check(len(parents) > 0, "The population cannot be an empty matrix")

    # Shuffle the array to avoid any relation between the individuals
    parents = shuffle(parents)

    # In case the length of the parents is not 2*n we remove the last element
    if len(upper) % 2 != 0:
        upper = upper[:-1]
        lower = lower[:-1]

    # Iterate over the parents taking them two by two and store the generated children
    for i in range(0, len(parents), 2):

        # Recombine a pair of parents with probability prob
        if np.random.uniform(0, 1) <= prob:
            # Calculate the value of gamma
            gamma = (1. + 2. * alpha) * np.random.uniform(0, 1) - alpha
            # Generate the children and store them
            parents[i], parents[i + 1] = (1 - gamma) * parents[i] + gamma * parents[i + 1], \
                                         ((1 - gamma) * parents[i + 1] + gamma * parents[i]).copy()

    # Fix the chromosomes values that are out of bounds. First create a mask
    # set the masked chromosome values to 0, create a matrix with the value of the
    # bounds where the mask is 1 (true) and zero in the rest, at the end add the new
    # matrix to the parents 
    out = parents > upper
    parents[out] = 0
    parents += out.astype(int) * upper
    out = parents < lower
    parents[out] = 0
    parents += out.astype(int) * lower

    # Return the generated children
    return parents
def one_point_permutation(parents, prob):
    """
    It recombines a pair of parents to generate their childrens.
    In order to do so it splits each parent in 2 halfs from the crossover point,
    then it combinates the halfs of each of them to generate two new children.
    Now the representation of each parent is a permutation of n elements, where n
    is len(parents[i]).
    """

    # Check the input var and shuffle the elements
    ga_tools.check(len(parents) > 0, "The population cannot be an empty matrix")

    # Shuffle the array to avoid any relation between the individuals
    parents = shuffle(parents)

    def add_parent(new_parent, parent):
        """
        Private auxiliary function to minimize the code.
        It appends to a given parents the values that it doesn't
        contain yet from the other parent's tail.
        """
        return np.hstack((new_parent, [x for x in parent if x not in new_parent]))

        # Iterate over the parents taking them two by two and store the generated children

    for i in range(0, len(parents), 2):

        # Apply the crossover function with probability prob
        if np.random.uniform(0, 1) <= prob:
            # Get a random crossover point
            crossover_point = np.random.randint(len(parents[i]))

            # split parents in 2 parts in the crossover point
            parent_1 = np.hsplit(parents[i], [crossover_point])
            parent_2 = np.hsplit(parents[i + 1], [crossover_point])

            # recombine to generate their children
            parents[i] = add_parent(parent_1[0], np.hstack((parent_2[1], parent_2[0])))
            parents[i + 1] = add_parent(parent_2[0], np.hstack((parent_1[1], parent_1[0])))

    # Return the generated children as a np array
    return parents
def one_point_gga(parents_s, parents_alpha, prob=1):
    """
    It recombines a pair of parents to generate their childrens.
    In order to do so it splits each parent in 2 halfs from the crossover point,
    then it combinates the halfs of each of them to generate two new children
    """

    # Check the input var and shuffle the elements
    ga_tools.check(len(parents_s) > 0, "The population S cannot be an empty matrix")
    ga_tools.check(len(parents_s) > 0, "The population Alpha cannot be an empty matrix")

    # Iterate over the parents taking them two by two and store the generated children
    for i in range(0, len(parents_s), 2):
        # Apply the crossover function with probability prob
        if np.random.uniform(0, 1) <= prob:
            # Get the crossover point
            cp = np.random.randint(len(parents_s[i]))

            # Recombine to generate their children
            parents_s[i, cp:], parents_s[i + 1, cp:] = parents_s[i + 1, cp:], parents_s[i, cp:].copy()
            parents_alpha[i, cp:], parents_alpha[i + 1, cp:] = parents_alpha[i + 1, cp:], parents_alpha[i, cp:].copy()

    return parents_s, parents_alpha
def two_point(parents, prob):
    """
    The following method recieves a pair of parents and the probability
    between [0,1] to apply the crossover function to them.
    It randomly sample 2 integers between 0 and the number of dimensions of the 
    parents. Then the two sub-arrays generated (one for each parent) are swapped
    generating 2 children.
    """

    # Check the input var and shuffle the elements
    ga_tools.check(len(parents) > 0, "The population cannot be an empty matrix")

    # Shuffle the array to avoid any relation between the individuals
    parents = shuffle(parents)

    # Iterate over the parents taking them two by two and store the generated children
    for i in range(0, len(parents), 2):

        # Apply the crossover function with probability prob
        if np.random.uniform(0, 1) <= prob:

            # Sample the 2 crossover points randomly
            cp1 = np.random.randint(len(parents[i]))
            cp2 = np.random.randint(len(parents[i]))

            # Avoid that cp1 and cp2 are equal
            while cp1 == cp2:
                cp2 = np.random.randint(len(parents[i]))

            # Swap if cp1 is bigger than cp2 (otherwise array slicing won't work)
            if cp1 > cp2:
                cp1, cp2 = cp2, cp1

            # Recombine to generate their children
            parents[i, cp1:cp2], parents[i + 1, cp1:cp2] = parents[i + 1, cp1:cp2], parents[i, cp1:cp2].copy()

    return parents
def worst_parents(parents, fitness, children, minimize=True):
    """
    Select the N worst fitness, where N is the number of children, out of the population
    and change them for the new generated children

    :param parents: current chromosomes
    :param fitness: fitness value of the chromosomes
    :param children: new generated chromosomes
    :param minimize: minimization problem or maximization (boolean)
    :return: return the new population
    """

    # Test that the inputs are correct
    ga_tools.check(
        len(parents) > 0, "The population cannot be an empty matrix")
    ga_tools.check(
        len(parents) == len(fitness),
        "len(parents) and len(pa_fitness) are not the same")

    # find the worst parents and replace it with the children
    parents[ga_tools.n_sort(fitness, len(children), minimize)] = children

    # return the new chromosomes
    return parents
def elitist(parents,
            pa_fitness,
            children,
            ch_fitness,
            M,
            elitism=0.5,
            replacement=True,
            minimize=True):
    """
    parents is the current chromosomes
    pa_fitness is parents fitness value
    children is the generated chromosomes
    ch_fitness is children fitness value
    M is the number of elements to select
    """
    ga_tools.check(len(parents) > 0, "The parents cannot be an empty matrix")
    ga_tools.check(
        len(parents) + len(children) >= M,
        "Number of survival chromosomes cannot be higher than the number of parents and children"
    )
    ga_tools.check(
        len(parents) == len(pa_fitness),
        "len(parents) and len(pa_fitness) are not the same")
    ga_tools.check(
        len(children) == len(ch_fitness),
        "len(children) and len(ch_fitness) are not the same")
    ga_tools.check(elitism >= 0 and elitism <= 1,
                   "elitism must be between 0 and 1")

    # Gather the parents and children and its fitness
    chromosomes = np.vstack((parents, children))
    fitness = np.hstack((pa_fitness, ch_fitness))

    # Get the number of chromosomes to sample as elitist and the rest of chromosomes
    n_elitist = int(np.ceil(M * elitism))
    n_rest = int(M - n_elitist)

    # Check that the fitness values are meaningful, if not assing equiprobability
    if np.sum(fitness) < 1e-15:
        fitness_prob = np.ones(fitness.shape) * (1.0 / len(fitness))
    else:
        # Calculate its probabilites
        fitness_prob = ga_tools.wheel_prob(fitness, minimize)

    # Get the non elitist part of the chromosomes with a probability proportional
    # to the value of the fitness
    rest_chromosomes = chromosomes[np.random.choice(np.arange(
        0, len(chromosomes)),
                                                    n_rest,
                                                    replace=replacement,
                                                    p=fitness_prob)]

    # get the elitist chromosomes
    elitist_chromosomes = chromosomes[ga_tools.n_sort(fitness, n_elitist,
                                                      minimize)]

    # print  "rest", rest_chromosomes
    # print "elitist", elitist_chromosomes
    # Group the elitist and the rest of the chromosomes together
    final_chromosomes = np.vstack((rest_chromosomes, elitist_chromosomes))

    # Shuffle the final matrix to avoid groups of elitist
    # shuffle = np.arange(len(final_chromosomes))
    # np.random.shuffle(shuffle)

    # Return the shuffled array
    return final_chromosomes