import numpy as np
from random import shuffle
[docs]def mate(
mating_pop, individuals: list, params, crossover_type=None, mutation_type=None
):
"""Swap nodes between two partners and mutate based on standard deviation.
Parameters
----------
mating_pop : list
List of indices of individuals to mate. If None, choose from population
randomly.
Each entry should contain two indices, one for each parent.
individuals : list
List of all individuals.
params : dict
Parameters for evolution. If None, use defaults.
Returns
-------
offspring : list
The offsprings produced as a result of crossover and mutation.
"""
prob_crossover = params.get("prob_crossover", 0.8)
prob_mutation = params.get("prob_mutation", 0.3)
mut_strength = params.get("mut_strength", 1.0)
cur_gen = params.get("current_total_gen_count", 1)
total_gen = params.get("total_generations", 10)
std_dev = (5 / 3) * (1 - cur_gen / total_gen)
if std_dev < 0:
std_dev = 0
if mating_pop is None:
mating_pop = []
for i in range(len(individuals)):
mating_pop.append([i, np.random.randint(len(individuals))])
offspring = []
for mates in mating_pop:
offspring1 = np.copy(individuals[mates[0]])
offspring2 = np.copy(individuals[mates[1]])
# Crossover
for i in range(offspring1.shape[1]):
if np.random.random() < prob_crossover:
tmp = np.copy(offspring1[:, i])
offspring1[:, i] = offspring2[:, i]
offspring2[:, i] = tmp
if mutation_type == "gaussian" or mutation_type is None:
# Method : Gaussian (default)
# Take a random number of connections based on probability and mutate based
# on standard deviation, calculated once per generation.
connections = offspring1.size
mut_val = np.random.normal(0, std_dev, connections) * mut_strength
mut = np.random.choice(
connections,
np.random.binomial(connections, prob_mutation),
replace=False,
)
offspring1.ravel()[mut] += offspring1.ravel()[mut] * mut_val[mut]
mut_val = np.random.normal(0, std_dev, connections) * mut_strength
mut = np.random.choice(
connections,
np.random.binomial(connections, prob_mutation),
replace=False,
)
offspring2.ravel()[mut] += offspring2.ravel()[mut] * mut_val[mut]
elif mutation_type == "self-adapting":
# Method: Self adapting mutation
# Choose two random individuals and a random number of connections,
# mutate offspring based on current gen and connections of two randomly
# chosen individuals
# Randomly select two individuals with current match active (=non-zero)
connections = offspring1.size
select = np.asarray(individuals)[
np.random.choice(np.nonzero(np.asarray(individuals))[0], 2)
]
mut = np.random.choice(
connections,
np.random.binomial(connections, prob_mutation),
replace=False,
)
offspring1.ravel()[mut] = offspring1.ravel()[mut] + mut_strength * (
1 - cur_gen / total_gen
) * (select[1].ravel()[mut] - select[0].ravel()[mut])
select = np.asarray(individuals)[
np.random.choice(np.nonzero(np.asarray(individuals))[0], 2)
]
mut = np.random.choice(
connections,
np.random.binomial(connections, prob_mutation),
replace=False,
)
offspring2.ravel()[mut] = offspring2.ravel()[mut] + mut_strength * (
1 - cur_gen / total_gen
) * (select[1].ravel()[mut] - select[0].ravel()[mut])
else:
pass
offspring.extend((offspring1, offspring2))
return offspring
[docs]class EvoNNRecombination:
def __init__(
self,
evolver: "BaseEA",
ProC: float = 0.8,
ProM: float = 0.3,
mutation_strength: float = 1.0,
mutation_type: str = "gaussian",
):
self.evolver = evolver
self.mutation_type: str = mutation_type
self.ProC: float = ProC
self.ProM: float = ProM
self.mutation_strength: float = mutation_strength
[docs] def do(self, pop, mating_pop_ids: list = None):
cur_gen = self.evolver.__getattribute__("_current_gen_count")
total_gen = self.evolver.__getattribute__("total_gen_count")
pop_size = pop.shape[0]
if mating_pop_ids is None:
shuffled_ids = list(range(pop_size))
shuffle(shuffled_ids)
else:
shuffled_ids = mating_pop_ids
# TODO fix the need for the following
# [1,2,3,4] -> [[1,2],[3,4]]
if np.asarray(shuffled_ids).ndim == 1:
mating_pop = [
[shuffled_ids[x], shuffled_ids[x]]
for x in range(len(shuffled_ids))
if x % 2 == 0
]
elif np.asarray(shuffled_ids).ndim == 2:
mating_pop = shuffled_ids
std_dev = (5 / 3) * (1 - cur_gen / total_gen)
offspring = []
for mates in mating_pop:
offspring1 = np.copy(pop[mates[0]])
offspring2 = np.copy(pop[mates[1]])
# Crossover
for i in range(offspring1.shape[1]):
if np.random.random() < self.ProC:
tmp = np.copy(offspring1[:, i])
offspring1[:, i] = offspring2[:, i]
offspring2[:, i] = tmp
if self.mutation_type == "gaussian" or self.mutation_type is None:
# Method : Gaussian (default)
# Take a random number of connections based on probability and mutate based
# on standard deviation, calculated once per generation.
connections = offspring1.size
mut_val = (
np.random.normal(0, std_dev, connections) * self.mutation_strength
)
mut = np.random.choice(
connections,
np.random.binomial(connections, self.ProM),
replace=False,
)
offspring1.ravel()[mut] += offspring1.ravel()[mut] * mut_val[mut]
mut_val = (
np.random.normal(0, std_dev, connections) * self.mutation_strength
)
mut = np.random.choice(
connections,
np.random.binomial(connections, self.ProM),
replace=False,
)
offspring2.ravel()[mut] += offspring2.ravel()[mut] * mut_val[mut]
elif self.mutation_type == "self-adapting":
# Method: Self adapting mutation
# Choose two random individuals and a random number of connections,
# mutate offspring based on current gen and connections of two randomly
# chosen individuals
# Randomly select two individuals with current match active (=non-zero)
connections = offspring1.size
select = np.asarray(pop)[
np.random.choice(np.nonzero(np.asarray(pop))[0], 2)
]
mut = np.random.choice(
connections,
np.random.binomial(connections, self.ProM),
replace=False,
)
offspring1.ravel()[mut] = offspring1.ravel()[
mut
] + self.mutation_strength * (1 - cur_gen / total_gen) * (
select[1].ravel()[mut] - select[0].ravel()[mut]
)
select = np.asarray(pop)[
np.random.choice(np.nonzero(np.asarray(pop))[0], 2)
]
mut = np.random.choice(
connections,
np.random.binomial(connections, self.ProM),
replace=False,
)
offspring2.ravel()[mut] = offspring2.ravel()[
mut
] + self.mutation_strength * (1 - cur_gen / total_gen) * (
select[1].ravel()[mut] - select[0].ravel()[mut]
)
else:
pass
offspring.extend((offspring1, offspring2))
return np.asarray(offspring)