def run_evolution(use_palette=False):
    # get the start time
    start_time = time.time()
    # initialize new population
    population = Population(POPULATION_SIZE, ELITE_SIZE, MUTATIONS_NUMBER,
                            use_palette)
    # report the state of population
    report(population, 0, time.time() - start_time)

    generations = np.array([])
    fitness = np.array([])

    # develop the population declared number of times
    for generation in range(GENERATIONS_NUMBER):
        # get the start time
        start_time = time.time()
        # perform the selection, crossover and mutation over the population
        population.selection()
        population.crossover()
        population.mutation()
        # report the state of population
        report(population, generation + 1, time.time() - start_time)

        generations = np.append(generations, generation)
        fitness = np.append(fitness, get_fitness(population))
        if generation % 20 == 0:
            plot_fitness(generations, fitness)

    # resultant individual will be the fittest one in the list of individuals
    result = Image.fromarray(
        utils.restore_image(population.population[0]['individual']))
    return result
def report(population, generation, runtime):
    """
    Method that report current information about the state of the population and saves the fittest individual.
    :param population: current state of population.
    :param generation: generation sequence number.
    :param runtime: execution time of program on this generation.
    """
    # get the fittest individual
    fittest = population.population[0]

    # create directory if needed
    try:
        # try to save a RGB image
        result = Image.fromarray(utils.restore_image(fittest['individual']))
        result = result.convert('RGB')
        generation_str = str(generation)
        nulls = ''
        for i in range(5 - len(generation_str)):
            nulls += '0'
        filename = f'documents/output/{FILE_NAME}/{nulls + generation_str}.jpeg'
        os.makedirs(os.path.dirname(filename), exist_ok=True)
        result.save(filename)
        status = 'Saved'
    except Exception:
        status = 'Not saved'

    print(
        f"Generation #{generation}: Fitness achieved is {get_fitness(population)}, runtime is {runtime} seconds, {status}"
    )
def _multiprocessing_mutation(individual):
    """
    Method used for multiprocessing computations at the mutation stage.
    Mutate the chromosome of individual. Restore image from chromosome and calculates fitness of individual.
    :param individual: member of population.
    :return: dictionary comprising of individual and its fitness.
    """
    individual['individual'].mutate()
    individual_image = utils.restore_image(individual['individual'])
    individual_fitness = _calculate_fitness(individual_image)
    individual['fitness'] = individual_fitness
    return individual
def _multiprocessing_init(individual):
    """
    Method used for multiprocessing computations at the initial stage.
    Restore image from the chromosome of individual and calculates its fitness.
    :param individual: member of population.
    :return: dictionary comprising of individual and its fitness.
    """
    individual_image = utils.restore_image(individual)
    individual_fitness = _calculate_fitness(individual_image)
    population_entry = {
        'individual': individual,
        'fitness': individual_fitness
    }
    return population_entry
def _multiprocessing_crossover(parents, number_mutations):
    # unpack parents of the spring
    parent1, parent2 = parents

    # observation showed that passing random genes to an offspring yields better in terms of fitness function result
    # rather than using a crossover point
    offspring_chromosome = []
    for i in range(GENES_NUMBER):
        # randomly choose gene either from first or second parent
        gene = choice([
            parent1['individual'].chromosome[i],
            parent2['individual'].chromosome[i]
        ])
        offspring_chromosome.append(gene)

    offspring = Individual(offspring_chromosome, number_mutations,
                           parent1['individual'].use_palette)
    offspring_image = utils.restore_image(offspring)
    offspring_fitness = _calculate_fitness(offspring_image)
    offspring_entry = {'individual': offspring, 'fitness': offspring_fitness}
    return offspring_entry