Source code for aptavia.tuners.genetic

from simple_pid import PID
import numpy as np
import random


[docs]class GeneticTuner(object): """A PID Tuner using a genetic algorithm. :param population_size: How many random PID-Controllers are in the population, defaults to 100 :type population_size: int, optional :param mutation_probability: The probability of a gain of a PID-Controller in the population randomly changing, defaults to 0.2 :type mutation_probability: float, optional :param population: A list of PID_Controllers that are tuned when :meth:`aptavia.tuners.GeneticTuners.step` is called. :type population: list of :class:`simple_pid.PID` :param fitness_function: A function that evaluates the fitness of one PID-Controller, defaults to None :type fitness_function: function, optional """ def __init__(self, population_size=100, mutation_probability=0.2, fitness_function=None): """Constructor method """ self.population_size = population_size self.mutation_probability = mutation_probability self.population = [PID(random.random(), random.random(), random.random()) for _ in range(population_size)] self.fitness_function = self.__fitness if fitness_function is None else fitness_function
[docs] def set_population_size(self, population_size): """Setter for population_size :param population_size: How many random PID-Controllers are in the population :type population_size: int """ self.population_size = population_size
[docs] def set_mutation_probability(self, mutation_probability): """Setter for mutation_probability :param mutation_probability: The probability of a gain of a PID-Controller in the population randomly changing :type mutation_probability: float """ self.mutation_probability = mutation_probability
[docs] def set_population(self, population): """Setter for the population :param population: A list of PID_Controllers :type population: list of :class:`simple_pid.PID` """ assert(len(population) == self.population_size) self.population = population
[docs] def set_fitness_function(self, fitness_function): """Setter for fitness_function :param fitness_function: A function that evaluates the fitness of one PID-Controller :type fitness_function: function """ self.fitness_function = fitness_function
def __fitness(self, agent): return random.random() * 100 - random.random() * 100 def __mutate(self, agent): mutated_agent_tunings = list( map( lambda K: random.random() if random.random() < self.mutation_probability else K, agent.tunings ) ) agent.tunings = mutated_agent_tunings return agent def __crossover(self, agent1, agent2): child_tunings = list(map(lambda x: (x[0] + x[1]) * 0.5, zip(agent1.tunings, agent2.tunings))) return PID(*child_tunings) def __normalize(self, x): return x / x.sum()
[docs] def step(self, num_generations=1): """Performs the genetic algorithm over a specified number of generations :param num_generations: The number of generations to tune, defaults to 1 :type num_generations: int, optional """ assert(num_generations > 0) assert(len(self.population) == self.population_size) for _ in range(num_generations): # calculate fitness of the generation population_fitness = [self.__fitness(agent) for agent in self.population] # select parents for breeding population_fitness_np = np.array(population_fitness) # change scale of distribution to [0, min+max] if there is a fitness below 0 if np.any(population_fitness_np < 0): population_fitness_np -= population_fitness_np.min() population_fitness_np += 1e-6 # make fitness distribution sum to 1 population_fitness_np = self.__normalize(population_fitness_np) parents = np.random.choice(self.population, size=( self.population_size, 2), p=population_fitness_np) # Create new population by breeding parents self.population = list(map(lambda x: self.__crossover(x[0], x[1]), parents)) # Mutate the new population self.population = list(map(self.__mutate, self.population))