CodeReclaimers / neat-python

Python implementation of the NEAT neuroevolution algorithm
BSD 3-Clause "New" or "Revised" License
1.42k stars 495 forks source link

TypeError: '>' not supported between instances of 'NoneType' and 'NoneType' #148

Open OodavidsinoO opened 5 years ago

OodavidsinoO commented 5 years ago
Traceback (most recent call last):

  File "<ipython-input-3-420187bbc878>", line 1, in <module>
    runfile('D:/CartPoleNEAT/CartPole.py', wdir='D:/CartPoleNEAT')

  File "c:\programdata\miniconda\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 704, in runfile
    execfile(filename, namespace)

  File "c:\programdata\miniconda\lib\site-packages\spyder_kernels\customize\spydercustomize.py", line 108, in execfile
    exec(compile(f.read(), filename, 'exec'), namespace)

  File "D:/CartPoleNEAT/CartPole.py", line 71, in <module>
    run()

  File "D:/CartPoleNEAT/CartPole.py", line 45, in run
    pop.run(eval_genomes, 10)

  File "c:\programdata\miniconda\lib\site-packages\neat\population.py", line 94, in run

TypeError: '>' not supported between instances of 'NoneType' and 'NoneType'

Code For CartPole.py:
# -*- coding: utf-8 -*-
import neat
import gym
import Visualize
import numpy as np

GAME = 'CartPole-v0'
env = gym.make(GAME).unwrapped
CONFIG = "./Config"
EP_STEP = 300 # maximum episode steps
GENERATION_EP = 10 # evaluate by the minimum of 10-episode rewards
TRAINING = True # training or testing
CHECKPOINT = 9 # test on th.is checkpoint

def eval_genomes(genomes, config):
   #Fitness
    for genome_id,genome in genomes:
       net = neat.nn.FeedForwardNetwork.create(genome, config)
       ep_r = []
       for ep in range(GENERATION_EP):
           accumulative_r = 0.
           observation = env.reset()
           for t in range(EP_STEP):
               action_values = net.activate(observation)
               action = np.argmax(action_values)
               observation_, reward, done, _ = env.step(action)
               accumulative_r += reward
               if done:
                   break
               observation = observation_
           ep_r.append(accumulative_r)
    genome.fitness = np.min(ep_r)/float(EP_STEP)

def run():
    config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction, neat.DefaultSpeciesSet, neat.DefaultStagnation, CONFIG)
    pop = neat.Population(config)

    #Record History
    stats = neat.StatisticsReporter()
    pop.add_reporter(stats)
    pop.add_reporter(neat.StdOutReporter(True))
    pop.add_reporter(neat.Checkpointer(5))

    pop.run(eval_genomes, 10)

    #Visualize Training
    Visualize.plot_stats(stats, ylong = False, view = True)
    Visualize.plot_species(stats, view = True)

def evaluation():
    #Show the best one
    p = neat.Checkpointer.restore_checkpoint('neat-checkpoint-%i' % CHECKPOINT)
    winner = p.run(eval_genomes, 1)     # find the winner in restored population

    # show winner net
    node_names = {-1: 'In0', -2: 'In1', -3: 'In3', -4: 'In4', 0: 'act1', 1: 'act2'}
    Visualize.draw_net(p.config, winner, True, node_names=node_names)

    net = neat.nn.FeedForwardNetwork.create(winner, p.config)
    while True:
        s = env.reset()
        while True:
            env.render()
            a = np.argmax(net.activate(s))
            s, r, done, _ = env.step(a)
            if done: break

if __name__ == '__main__':
    if TRAINING:
        run()
    else:
        evaluation()

END OF CODE
afzalmushtaque commented 5 years ago

The line "genome.fitness = np.min(ep_r)/float(EP_STEP)" should be indented one step to the right.

patvdleer commented 5 years ago

I'm running into the same issue but due to threading and processing the genomes are passed by value instead of reference.

patvdleer commented 5 years ago

I made some nasty stuff, still testing if this all works.

import gzip
import multiprocessing as mp
import os
import pickle
import random
import signal
import sys
import threading
import time
from multiprocessing import current_process
from queue import Empty

import neat
from neat import Population, CompleteExtinctionException
from neat.six_util import itervalues, iteritems
from tqdm import tqdm

from slither import SlitherIO

class MyPopulation(Population):
    def run(self, fitness_function, n=None):
        if self.config.no_fitness_termination and (n is None):
            raise RuntimeError("Cannot have no generational limit with no fitness termination")

        k = 0
        while n is None or k < n:
            k += 1

            self.reporters.start_generation(self.generation)

            # Evaluate all genomes using the user-provided function.

            # ToDo - MY EDIT HERE
            # fitness_function(list(iteritems(self.population)), self.config)
            self.population = fitness_function(list(iteritems(self.population)), self.config)
            self.species.species[1].members = self.population
            # ToDo - MY EDIT HERE

            # Gather and report statistics.
            best = None
            for g in itervalues(self.population):
                if best is None or g.fitness > best.fitness:
                    best = g
            self.reporters.post_evaluate(self.config, self.population, self.species, best)

            # Track the best genome ever seen.
            if self.best_genome is None or best.fitness > self.best_genome.fitness:
                self.best_genome = best

            if not self.config.no_fitness_termination:
                # End if the fitness threshold is reached.
                fv = self.fitness_criterion(g.fitness for g in itervalues(self.population))
                if fv >= self.config.fitness_threshold:
                    self.reporters.found_solution(self.config, self.generation, best)
                    break

            # Create the next generation from the current generation.
            self.population = self.reproduction.reproduce(self.config, self.species,
                                                          self.config.pop_size, self.generation)

            # Check for complete extinction.
            if not self.species.species:
                self.reporters.complete_extinction()

                # If requested by the user, create a completely new population,
                # otherwise raise an exception.
                if self.config.reset_on_extinction:
                    self.population = self.reproduction.create_new(self.config.genome_type,
                                                                   self.config.genome_config,
                                                                   self.config.pop_size)
                else:
                    raise CompleteExtinctionException()

            # Divide the new population into species.
            self.species.speciate(self.config, self.population, self.generation)

            self.reporters.end_generation(self.config, self.population, self.species)

            self.generation += 1

        if self.config.no_fitness_termination:
            self.reporters.found_solution(self.config, self.generation, self.best_genome)

        return self.best_genome

    run.__doc__ = Population.run.__doc__

def load(filename, klass=MyPopulation):
    with gzip.open(filename) as f:
        generation, config, population, species_set, rndstate = pickle.load(f)
        random.setstate(rndstate)
        return klass(config, (population, species_set, generation))

def runner(config, fitness_function, generations=5):
    pop = MyPopulation(config)
    stats = neat.StatisticsReporter()
    checkpoint = neat.Checkpointer(1, 60)

    from natsort import natsorted
    import glob
    files = natsorted(glob.glob('%s/neat-checkpoint-*' % os.path.dirname(__file__)))
    if len(files) > 0:
        # pop = checkpoint.restore_checkpoint(files[-1])
        pop = load(filename=files[-1])
        pop.generation += 1

    pop.add_reporter(stats)
    pop.add_reporter(neat.StdOutReporter(True))
    pop.add_reporter(checkpoint)

    while not event.is_set():
        best_genome = pop.run(fitness_function, generations)
        print(best_genome.fitness)
        stats.save()
        checkpoint.save_checkpoint(
            config,
            pop.population,
            pop.species,
            pop.generation
        )

def dispatcher(genomes, config):
    qi = mp.JoinableQueue()
    qo = mp.JoinableQueue()

    processes = []
    # n_processes = mp.cpu_count()
    n_processes = mp.cpu_count() * 2
    # n_processes = int(mp.cpu_count() / 2)

    for i in range(n_processes):
        p = threading.Thread(target=do_agent, args=(qi, qo, event, config), name="Agent-%i" % i)
        processes.append(p)

    for genome in tqdm(genomes, desc="genomes           "):
        genome[1].fitness = 0
        qi.put(genome)

    for p in tqdm(processes, desc="processes started "):
        # the sleep seems to fix the premature finish of the workers
        # due to the queue not being ready yet
        time.sleep(0.1)
        p.start()

    for p in tqdm(processes, desc="processes joined  "):
        p.join()

    # override the old one, due to pass by value the fitness isn't accounted for
    new_population = {}
    while not qo.empty():
        n, genome = qo.get()
        new_population[n] = genome
        qo.task_done()

    qi.join()
    qo.join()

    return new_population

def do_agent(qi: mp.Queue, qo: mp.Queue, event: mp.Event, config):
    global DEBUG
    while not qi.empty() and not event.is_set():
        try:
            n, genome = qi.get(timeout=1)
            with SlitherIO(debug=DEBUG) as slither:
                qo.put((n, slither.run(event, config, genome),))
            qi.task_done()
        except Empty:
            pass

def handler(signum, frame):
    if current_process().name == "MainProcess":
        print('Signal handler called with signal', signum)
        event.set()
        sys.exit(0)

signal.signal(signal.SIGINT, handler)

if __name__ == '__main__':
    DEBUG = False
    event = mp.Event()

    config_file = os.path.join(os.path.dirname(__file__), 'config')
    t = threading.Thread(
        target=runner,
        args=(
            neat.Config(
                neat.DefaultGenome,
                neat.DefaultReproduction,
                neat.DefaultSpeciesSet,
                neat.DefaultStagnation,
                config_file
            ),
            dispatcher,
        ),
        name="Runner"
    )
    t.start()
    t.join()
    event.set()
MrStashley commented 4 years ago

@patvdleer could you explain how you fixed this error if you were able to? I'm having the same issue

patvdleer commented 4 years ago

ow damn, it's been a while, IIRC, there is/was? a bug which I fixed by creating my own population class extended on the package. Looking back I think I hacked something together that sets the population on self.species.species[1].members to prevent the NoneType error.

https://github.com/CodeReclaimers/neat-python/blob/master/neat/population.py

MINE

            # ToDo - MY EDIT HERE
            # fitness_function(list(iteritems(self.population)), self.config)
            self.population = fitness_function(list(iteritems(self.population)), self.config)
            self.species.species[1].members = self.population
            # ToDo - MY EDIT HERE
MrStashley commented 4 years ago

Thanks! I found another fix to the problem by using the multiprocessing pool class and using the return value of the function instead of the manager/queue class via pool.apply_async(). I guess there is an issue with the way the manager/queue class handles the genome data.

SaiEthihasChanda commented 6 months ago

@MrStashley , @patvdleer , @afzalmushtaque , @OodavidsinoO ,

so im facing the same issue here.... im converting my neat training process from being able to run each game sequentially to implementing multiprocessing.... i was getting the same error ( aka the [Type Error: '>' not supported between instances of 'NoneType' and 'NoneType')

i came across this and i tried making the changes but it still doesnt help.... i understand that i could be doing smtg insanely wrong but im open to suggestions...

this is my first neat project and i rly need help with it coz i need to submit this...

here are my files... lemme know if anything else is required

neat_train.py

`import pygame from run import GameController from constants import * import pygame import neat import os import time import pickle import keyboard import math import datetime import threading import multiprocessing import concurrent.futures

class pacman_game: def init(self): self.game = GameController() self.game.startGame() self.pacman = self.game.pacman self.ghosts = self.game.ghosts # list self.score = self.game.score self.clock = pygame.time.Clock() self.turning_points = self.game.nodes.nodesLUT.keys() self.intersection_points = [[], []] self.last_position = None # Store the last position of the agent self.last_position_time = None for node in self.turning_points: self.intersection_points[0].append(node[0]) self.intersection_points[1].append(node[1])

        # (node.position.x, node.position.y)
    # print(self.intersection_points[0])
    # print(self.intersection_points[1])

# print("pacman at")
# print(self.pacman.position)

def test_ai(self):
    run = True
    # clock = pygame.time.Clock()
    self.game.startGame()
    while run:
        # clock.tick(600)

        self.game.loop()
        self.game.render()
        pygame.display.update()
        game_info = self.game.loop()
        # print(game_info.pacman_pos) #prints pacmans coordinates on the board
        # print(game_info.ghosts_pos) #prints all ghosts coordinates on the board

def test_ai(self, net):
    """
    Test the AI against a human player by passing a NEAT neural network
    """
    clock = pygame.time.Clock()
    run = True
    while run:
        game_info = self.game.loop()
        clock.tick(60)
        pacman_pos = (game_info.pacman_pos_x.x, game_info.pacman_pos_x.y)
        for node_pos in self.turning_points:
            if pacman_pos == node_pos:

                output1 = net.activate((game_info.pacman_pos_x.x, game_info.pacman_pos_x.y,
                                        game_info.ghosts_pos[0].x - game_info.pacman_pos_x.x,
                                        game_info.ghosts_pos[0].y - game_info.pacman_pos_x.y,
                                        game_info.ghosts_pos[1].x - game_info.pacman_pos_x.x,
                                        game_info.ghosts_pos[1].y - game_info.pacman_pos_x.y,
                                        game_info.ghosts_pos[2].x - game_info.pacman_pos_x.x,
                                        game_info.ghosts_pos[2].y - game_info.pacman_pos_x.y,
                                        game_info.ghosts_pos[3].x - game_info.pacman_pos_x.x,
                                        game_info.ghosts_pos[3].y - game_info.pacman_pos_x.y,
                                        game_info.score
                                        ))
                decision = output1.index(max(output1))
                if decision == 0:
                    # self.game.pacman.direction = UP
                    keyboard.press('w')
                    keyboard.release('a')
                    keyboard.release('s')
                    keyboard.release('d')
                    # print("up")

                elif decision == 1:
                    keyboard.press('s')
                    keyboard.release('d')
                    keyboard.release('a')
                    keyboard.release('w')
                    # print("down")

                elif decision == 2:
                    keyboard.press('a')
                    keyboard.release('d')
                    keyboard.release('s')
                    keyboard.release('w')
                    # print("left")

                elif decision == 3:
                    keyboard.press('d')
                    keyboard.release('s')
                    keyboard.release('a')
                    keyboard.release('w')

                    # print("right")
            else:
                pass

def train_ai(self, genome1, config):
    net1 = neat.nn.FeedForwardNetwork.create(genome1, config)
    #net2 = neat.nn.FeedForwardNetwork.create(genome2, config)

    run = True
    self.game.startGame()
    # self.pause.setPause(playerPaused=True)
    self.game.pause.setPause(playerPaused=False)
    time_stamp = None
    last_position = None  # Store the last position of the agent
    last_position_time = None

    while run:
        dt = self.clock.tick(300) / 1000.0
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                quit()
        self.pacman.update(dt)
        game_info = self.game.loop()
        self.game.render()
        pygame.display.update()

        # Check if Pac-Man's position matches any of the node positions

        # print(pacman_pos)
        valid = self.move_ai(net1, game_info, genome1)
        # print(valid)
        game_info = self.game.loop()
        self.game.render()
        pygame.display.update()
        if game_info is not None:

            valid = self.move_ai(net1, game_info, genome1)
            if valid:
                self.calculate_fitness(genome1, game_info)
            elif valid is False:
                break

def calculate_fitness(self, genome1,  game_info):
    if genome1.fitness == None:

        genome1.fitness = 0
        genome1.fitness += game_info.score / 10 + game_info.time_elapsed / 1000

    else:
        genome1.fitness += game_info.score / 10 + game_info.time_elapsed / 1000

    #genome2.fitness += game_info.score / 10 + game_info.time_elapsed / 1000
    # print(genome1.fitness)

def move_ai(self, net, game_info, genome1):
    dt = self.clock.tick(300) / 1000.0
    pacman_pos = (game_info.pacman_pos_x.x, game_info.pacman_pos_x.y)
    for node_pos in self.turning_points:
        if pacman_pos == node_pos:

            output1 = net.activate((game_info.pacman_pos_x.x, game_info.pacman_pos_x.y,
                                    game_info.ghosts_pos[0].x - game_info.pacman_pos_x.x,
                                    game_info.ghosts_pos[0].y - game_info.pacman_pos_x.y,
                                    game_info.ghosts_pos[1].x - game_info.pacman_pos_x.x,
                                    game_info.ghosts_pos[1].y - game_info.pacman_pos_x.y,
                                    game_info.ghosts_pos[2].x - game_info.pacman_pos_x.x,
                                    game_info.ghosts_pos[2].y - game_info.pacman_pos_x.y,
                                    game_info.ghosts_pos[3].x - game_info.pacman_pos_x.x,
                                    game_info.ghosts_pos[3].y - game_info.pacman_pos_x.y,
                                    game_info.score
                                    ))
            decision = output1.index(max(output1))
            if decision == 0:
                # self.game.pacman.direction = UP
                keyboard.press('w')
                keyboard.release('a')
                keyboard.release('s')
                keyboard.release('d')
                # print("up")

            elif decision == 1:
                keyboard.press('s')
                keyboard.release('d')
                keyboard.release('a')
                keyboard.release('w')
                # print("down")

            elif decision == 2:
                keyboard.press('a')
                keyboard.release('d')
                keyboard.release('s')
                keyboard.release('w')
                # print("left")

            elif decision == 3:
                keyboard.press('d')
                keyboard.release('s')
                keyboard.release('a')
                keyboard.release('w')

                # print("right")
        else:
            pass
    self.pacman.update(dt)
    pacman_pos = (game_info.pacman_pos_x.x, game_info.pacman_pos_x.y)
    # print(pacman_pos)
    current_position = (game_info.pacman_pos_x.x, game_info.pacman_pos_x.y)
    if game_info.lives < 5:
        if genome1.fitness == None:
            genome1.fitness = 0
            genome1.fitness -= 100
        else:

            genome1.fitness -= 100
        return False

    elif current_position != self.last_position:
        self.last_position = current_position
        current_time = datetime.datetime.now()
        time_string = current_time.strftime("%H%M%S")
        self.last_position_time = time_string
        # print(self.last_position_time)

        # print(last_position)
        # last_position_time = time.time()
    elif current_position == self.last_position:
        current_time = datetime.datetime.now()
        time_string = current_time.strftime("%H%M%S")
        # print(int(time_string) - int(self.last_position_time))
        if int(time_string) - int(self.last_position_time) > 1.5:
            # print('idle')
            if genome1.fitness == None:
                genome1.fitness = 0
                genome1.fitness -= 10
            else:

                genome1.fitness -= 10

            return False
        else:
            return True

            # self.calculate_fitness(genome1, genome2, game_info)
            pass

def train_ai(genome1, config): net1 = neat.nn.FeedForwardNetwork.create(genome1, config)

net2 = neat.nn.FeedForwardNetwork.create(genome2, config)

run = True
pgame = pacman_game()
pgame.game.startGame()
# self.pause.setPause(playerPaused=True)
pgame.game.pause.setPause(playerPaused=False)
time_stamp = None
last_position = None  # Store the last position of the agent
last_position_time = None

while run:
    dt = pgame.clock.tick(300) / 1000.0
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            quit()
    pgame.game.pacman.update(dt)
    game_info = pgame.game.loop()
    pgame.game.render()
    pygame.display.update()

    # Check if Pac-Man's position matches any of the node positions

    # print(pacman_pos)
    valid = pgame.move_ai(net1, game_info, genome1)
    # print(valid)
    game_info = pgame.game.loop()
    pgame.game.render()
    pygame.display.update()
    if game_info is not None:
        if genome1.fitness is None:
            genome1.fitness = 0
        valid = pgame.move_ai(net1, game_info, genome1)
        if valid:
            pgame.calculate_fitness(genome1, game_info)
        elif valid is False:
            break

def calculate_fitness(self, genome1, game_info): if genome1.fitness == None: genome1.fitness = 0 genome1.fitness += game_info.score / 10 + game_info.time_elapsed / 1000

fitness_lock = threading.Lock() ''' def eval_genomes(genomes, config): fitness_values = [] """ Run each genome against each other one time to determine the fitness. """ workercount = 0

for i, (genome_id1, genome1) in enumerate(genomes):
    print(round(i / len(genomes) * 100), end=" ")
    #genome1.fitness = 0
    for genome_id2, genome2 in genomes[min(i + 1, len(genomes) - 1):]:
        workercount +=1

print(workercount)

with concurrent.futures.ProcessPoolExecutor(max_workers=workercount) as executor:
    futures = []
    for i, (genome_id1, genome1) in enumerate(genomes):
        print(round(i / len(genomes) * 100), end=" ")
        #print(genome1)
        genome1.fitness = 1

        for genome_id2, genome2 in genomes[min(i + 1, len(genomes) - 1):]:
            genome2.fitness = 0 if genome2.fitness == None else genome2.fitness
            futures.append(executor.submit(run_game, genome1, genome2, config))

        for future in concurrent.futures.as_completed(futures):
            fitnesses = future.result()
            genome1, genome2, fitness1, fitness2 = fitnesses
            genome1.fitness += fitness1
            #genome2.fitness += fitness2
        print("look here",genome1.fitness)

print(genome1.fitness)
#return [(genome_id, genome.fitness) for genome_id, genome in genomes]

'''

def eval_genomes(genomes, config): for genome_id, genome in genomes: game = pacman_game() game.train_ai(genome, config)

'''for i, (genome_id1, genome1) in enumerate(genomes):
    print(round(i / len(genomes) * 100), end=" ")
    genome1.fitness = 0
    for genome_id2, genome2 in genomes[min(i + 1, len(genomes) - 1):]:
        genome2.fitness = 0 if genome2.fitness == None else genome2.fitness
        game = pacman_game()
        game.train_ai(genome1, genome2, config)'''

def run_game(genome1, genome2, config): game = pacman_game() game.train_ai(genome1, config) return genome1, genome2, genome1.fitness, genome2.fitness

def test_best_network(config): with open("best.pickle", "rb") as f: winner = pickle.load(f) winner_net = neat.nn.FeedForwardNetwork.create(winner, config)

with open("best.pickle", "rb") as f:

    #genome = pickle.load(f)

    # Convert loaded genome into required data structure
#genomes = [(1, genome)]
#print(genomes)

#pygame.display.set_caption("Pong")
pac_game = pacman_game()
pac_game.game.startGame()
pac_game.game.pause.setPause(playerPaused=False)
pac_game.test_ai(winner_net)

def run_neat(config_f): config = config_f p = neat.Population(config) p.add_reporter(neat.StdOutReporter(True)) stats = neat.StatisticsReporter() p.add_reporter(stats) p.add_reporter(neat.Checkpointer(1))

# Create the ParallelEvaluator with multiprocessing
for genome_id, genome in p.population.items():
    genome.fitness = 0

evaluator = neat.ParallelEvaluator(multiprocessing.cpu_count(), train_ai)

# Run the NEAT algorithm with the evaluator
winner = p.run(evaluator.evaluate, 10)

# Save the winner
with open("best.pickle", "wb") as f:
    pickle.dump(winner, f)
print("Saved the best genome.")

if name == 'main': local_dir = os.path.dirname(file) config_path = os.path.join(local_dir, 'config.txt')

config = neat.Config(neat.DefaultGenome, neat.DefaultReproduction,
                     neat.DefaultSpeciesSet, neat.DefaultStagnation,
                     config_path)

run_neat(config)
#test_best_network(config)

`

parallel.py

`""" Runs evaluation functions in parallel subprocesses in order to evaluate multiple genomes at once. """ from multiprocessing import Pool

class ParallelEvaluator(object): def init(self, num_workers, eval_function, timeout=None, maxtasksperchild=None): """ eval_function should take one argument, a tuple of (genome object, config object), and return a single float (the genome's fitness). """ self.eval_function = eval_function self.timeout = timeout self.pool = Pool(processes=num_workers, maxtasksperchild=maxtasksperchild)

def __del__(self):
    self.pool.close()
    self.pool.join()
    self.pool.terminate()

def evaluate(self, genomes, config):
    jobs = []
    for ignored_genome_id, genome in genomes:
        jobs.append(self.pool.apply_async(self.eval_function, (genome, config)))

    # assign the fitness back to each genome
    for job, (ignored_genome_id, genome) in zip(jobs, genomes):
        genome.fitness = job.get(timeout=self.timeout)

`

population.py

`"""Implements the core evolution algorithm."""

from neat.math_util import mean from neat.reporting import ReporterSet

class CompleteExtinctionException(Exception): pass

class Population(object): """ This class implements the core evolution algorithm:

  1. Evaluate fitness of all genomes.
  2. Check to see if the termination criterion is satisfied; exit if it is.
  3. Generate the next generation from the current population.
  4. Partition the new generation into species based on genetic similarity.
  5. Go to 1. """

    def init(self, config, initial_state=None): self.reporters = ReporterSet() self.config = config stagnation = config.stagnation_type(config.stagnation_config, self.reporters) self.reproduction = config.reproduction_type(config.reproduction_config, self.reporters, stagnation) if config.fitness_criterion == 'max': self.fitness_criterion = max elif config.fitness_criterion == 'min': self.fitness_criterion = min elif config.fitness_criterion == 'mean': self.fitness_criterion = mean elif not config.no_fitness_termination: raise RuntimeError( "Unexpected fitness_criterion: {0!r}".format(config.fitness_criterion))

    if initial_state is None:
        # Create a population from scratch, then partition into species.
        self.population = self.reproduction.create_new(config.genome_type,
                                                       config.genome_config,
                                                       config.pop_size)
        self.species = config.species_set_type(config.species_set_config, self.reporters)
        self.generation = 0
        self.species.speciate(config, self.population, self.generation)
    else:
        self.population, self.species, self.generation = initial_state
    
    self.best_genome = None

    def add_reporter(self, reporter): self.reporters.add(reporter)

    def remove_reporter(self, reporter): self.reporters.remove(reporter)

    def run(self, fitness_function, n=None): """ Runs NEAT's genetic algorithm for at most n generations. If n is None, run until solution is found or extinction occurs.

    The user-provided fitness_function must take only two arguments:
        1. The population as a list of (genome id, genome) tuples.
        2. The current configuration object.
    
    The return value of the fitness function is ignored, but it must assign
    a Python float to the `fitness` member of each genome.
    
    The fitness function is free to maintain external state, perform
    evaluations in parallel, etc.
    
    It is assumed that fitness_function does not modify the list of genomes,
    the genomes themselves (apart from updating the fitness member),
    or the configuration object.
    """
    
    if self.config.no_fitness_termination and (n is None):
        raise RuntimeError("Cannot have no generational limit with no fitness termination")
    
    k = 0
    while n is None or k < n:
        k += 1
    
        self.reporters.start_generation(self.generation)
    
        # Evaluate all genomes using the user-provided function.
        fitness_function(list(self.population.items()), self.config)
    
        # Gather and report statistics.
        best = None
        for g in self.population.values():
            if g.fitness is None:
                raise RuntimeError("Fitness not assigned to genome {}".format(g.key))
    
            if best is None or g.fitness > best.fitness:
                best = g
        self.reporters.post_evaluate(self.config, self.population, self.species, best)
    
        # Track the best genome ever seen.
        if self.best_genome is None or best.fitness > self.best_genome.fitness:
            self.best_genome = best
    
        if not self.config.no_fitness_termination:
            # End if the fitness threshold is reached.
            fv = self.fitness_criterion(g.fitness for g in self.population.values())
            if fv >= self.config.fitness_threshold:
                self.reporters.found_solution(self.config, self.generation, best)
                break
    
        # Create the next generation from the current generation.
        self.population = self.reproduction.reproduce(self.config, self.species,
                                                      self.config.pop_size, self.generation)
    
        # Check for complete extinction.
        if not self.species.species:
            self.reporters.complete_extinction()
    
            # If requested by the user, create a completely new population,
            # otherwise raise an exception.
            if self.config.reset_on_extinction:
                self.population = self.reproduction.create_new(self.config.genome_type,
                                                               self.config.genome_config,
                                                               self.config.pop_size)
            else:
                raise CompleteExtinctionException()
    
        # Divide the new population into species.
        self.species.speciate(self.config, self.population, self.generation)
    
        self.reporters.end_generation(self.config, self.population, self.species)
    
        self.generation += 1
    
    if self.config.no_fitness_termination:
        self.reporters.found_solution(self.config, self.generation, self.best_genome)
    
    return self.best_genome

    `