I've inherited a piece of Python code that uses DEAP, with tournament selection to manage a crossover step:
#Early in the code
toolbox.register("select", tools.selTournament, tournsize=tournsize)
#Then later...
popu = toolbox.select(popu, k=len(popu))
popu = [toolbox.clone(ind) for ind in popu]
for child1, child2 in zip(popu[::2], popu[1::2]):
toolbox.mate(child1, child2)
del child1.fitness.values, child2.fitness.values
It appears to me that this has no protection for an individual being crossed-over with itself, and I'd like to avoid that possibility.
Conceptually, this is simple enough, but I'm not sure how to implement it in Python.
Possible Solution 1: (see update below)
popu = toolbox.select(popu, k=len(popu))
for ind in range(1, len(popu), 2):
while popu[ind] == popu[ind-1]:
popu[ind] = toolbox.select(popu, k=1)
popu = [toolbox.clone(ind) for ind in popu]
for child1, child2 in zip(popu[::2], popu[1::2]):
toolbox.mate(child1, child2)
del child1.fitness.values, child2.fitness.values
*If reselecting duplicates one-by-one is possible, I think putting the deduping before the cloning is correct, but I'm not 100% sure.
Possible Solution 2, a little more brute-force:
popu = toolbox.select(popu, k=len(popu))
for ind in range(1, len(popu), 2):
while popu[ind] == popu[ind-1]:
popu[ind] = popu[random.randint(0,len(popu)-1)]
popu = [toolbox.clone(ind) for ind in popu]
for child1, child2 in zip(popu[::2], popu[1::2]):
toolbox.mate(child1, child2)
del child1.fitness.values, child2.fitness.values
Should either or both of those work (with the caveat that the reselection is occurring in the right place)?
UPDATE:
Possible solution #1 needs to be modified. Since toolbox.select returns a list, the individual popu item needs to be set to a list-item, and not the whole list itself, even when the list is length 1. Probably easier to show the solution, rather than write this out in prose...
Revised Possible Solution 1:
popu = toolbox.select(popu, k=len(popu))
for ind in range(1, len(popu), 2):
while popu[ind] == popu[ind-1]:
repopu = toolbox.select(popu, k=1)
popu[ind] = repopu[0]
popu = [toolbox.clone(ind) for ind in popu]
for child1, child2 in zip(popu[::2], popu[1::2]):
toolbox.mate(child1, child2)
del child1.fitness.values, child2.fitness.values
This at least gets us past the "it doesn't crash" stage, but is it doing what we want?