Skip to content

Instantly share code, notes, and snippets.

@penguinpowernz
Last active December 9, 2022 08:05
Show Gist options
  • Save penguinpowernz/e1b52882d07eb74de1ac0d4b50d7948d to your computer and use it in GitHub Desktop.
Save penguinpowernz/e1b52882d07eb74de1ac0d4b50d7948d to your computer and use it in GitHub Desktop.
Genetic Algorithms in golang

Genetic Algorithms in golang

What is a genetic operator?

In the context of genetic algorithms, a genetic operator is a function that is applied to the genetic material of individuals in a population in order to create new, offspring individuals. These operators typically mimic biological processes such as crossover (also known as recombination), mutation, and selection. The purpose of these operators is to introduce variation into the population, allowing the algorithm to explore different solutions and potentially find better ones.

How would one write a genetic operator in golang?

Here is an example of how a genetic operator might be implemented in Go:

package main

import (
  "math/rand"
)

// A GeneticOperator represents a function that takes a population of individuals
// and produces a new population by applying some genetic operation to the
// individuals in the population.
type GeneticOperator func([]Individual) []Individual

// A CrossoverOperator takes two parent individuals and produces a new offspring
// individual by combining the genetic material of the parents using the
// crossover operation.
func CrossoverOperator(parent1, parent2 Individual) Individual {
  // Create a new individual with the same length as the parents.
  offspring := make(Individual, len(parent1))

  // Select a random crossover point.
  crossOverPoint := rand.Intn(len(parent1))

  // Combine the genetic material of the parents at the crossover point.
  for i := 0; i < crossOverPoint; i++ {
    offspring[i] = parent1[i]
  }
  for i := crossOverPoint; i < len(parent1); i++ {
    offspring[i] = parent2[i]
  }

  return offspring
}

// A MutationOperator takes an individual and produces a new offspring
// individual by mutating some of the genetic material of the parent.
func MutationOperator(parent Individual, mutationRate float64) Individual {
  // Create a new individual with the same length as the parent.
  offspring := make(Individual, len(parent))

  // Copy the genetic material of the parent into the offspring.
  copy(offspring, parent)

  // Mutate some of the genes in the offspring according to the mutation rate.
  for i := 0; i < len(offspring); i++ {
    if rand.Float64() < mutationRate {
      // Mutate the gene by flipping its value.
      offspring[i] = !offspring[i]
    }
  }

  return offspring
}

// A SelectionOperator takes a population of individuals and produces a new
// population by selecting a subset of the individuals using some selection
// criteria.
func SelectionOperator(pop []Individual) []Individual {
  // Create a new slice to hold the selected individuals.
  selected := make([]Individual, 0)

  // Select the fittest individuals from the population.
  for _, ind := range pop {
    if ind.Fitness() > 0.5 {
      selected = append(selected, ind)
    }
  }

  return selected
}

In this example, we have defined three different genetic operators: CrossoverOperator, MutationOperator, and SelectionOperator. The CrossoverOperator function takes two parent individuals and produces an offspring individual by combining their genetic material at a randomly selected crossover point. The MutationOperator function takes an individual and a mutation rate, and produces an offspring individual by randomly flipping some of the genes in the parent according to the mutation rate. And the SelectionOperator function takes a population of individuals and produces a new population by selecting only the fittest individuals from the original population.

What does genetic material here refer to?

In the context of a genetic algorithm, the genetic material of an individual typically refers to the values of the variables that encode a potential solution to the problem being solved by the algorithm. For example, if the problem being solved is the traveling salesmanperson problem, the genetic material of an individual might consist of a sequence of cities that represents a potential path through all of the cities in the problem. These values are often represented using a binary string, where each gene in the string corresponds to a specific variable in the solution. The genetic operators, such as crossover and mutation, then operate on this genetic material to produce new, offspring individuals.

How would one write some genetic material in golang?

Here is an example of how genetic material might be represented and manipulated in Go:

package main

// Individual represents a potential solution to a problem in the form of a
// binary string.
type Individual []bool

// NewIndividual creates a new random individual with the given length.
func NewIndividual(length int) Individual {
  ind := make(Individual, length)
  for i := 0; i < length; i++ {
    ind[i] = rand.Intn(2) == 1
  }
  return ind
}

// Fitness returns the fitness of the individual, which is a measure of how
// good a solution it is to the problem being solved by the genetic algorithm.
func (ind Individual) Fitness() float64 {
  // Calculate the fitness of the individual by evaluating its genetic material.
  // For example, if the problem being solved is the traveling salesperson
  // problem, the fitness might be calculated as the total distance of the
  // path encoded by the individual.
  // ...

  return fitness
}

// Crossover takes another individual and produces a new offspring individual
// by combining the genetic material of the two parents using the crossover
// operation.
func (ind Individual) Crossover(other Individual) Individual {
  // Create a new individual with the same length as the parents.
  offspring := make(Individual, len(ind))

  // Select a random crossover point.
  crossOverPoint := rand.Intn(len(ind))

  // Combine the genetic material of the parents at the crossover point.
  for i := 0; i < crossOverPoint; i++ {
    offspring[i] = ind[i]
  }
  for i := crossOverPoint; i < len(ind); i++ {
    offspring[i] = other[i]
  }

  return offspring
}

// Mutate mutates some of the genetic material of the individual according
// to the given mutation rate.
func (ind Individual) Mutate(mutationRate float64) {
  // Mutate some of the genes in the individual according to the mutation rate.
  for i := 0; i < len(ind); i++ {
    if rand.Float64() < mutationRate {
      // Mutate the gene by flipping its value.
      ind[i] = !ind[i]
    }
  }
}

In this example, the Individual type is defined as a slice of bool values, which represent the binary string encoding the genetic material of the individual. The NewIndividual function is used to create a new random individual with a given length. The Fitness method is used to evaluate the fitness of the individual, which is a measure of how good a solution it is to the problem being solved by the genetic algorithm. The Crossover method is used to produce an offspring individual by combining the genetic material of the current individual and another parent individual. And the Mutate method is used to randomly flip some of the genes in the individual according to the given mutation rate.

Can you give code examples in golang for evolving a genetic algorithm population 10 times?

Sure! Here is an example of how you might implement a genetic algorithm in Go that evolves a population over 10 generations:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

// Individual represents a member of the population
// in our genetic algorithm.
type Individual struct {
	chromosome []int
	fitness    float64
}

// NewIndividual creates a new individual with
// a random chromosome and a fitness score of 0.
func NewIndividual() *Individual {
	return &Individual{
		chromosome: rand.Perm(10),
		fitness:    0,
	}
}

// EvolvePopulation applies the genetic algorithm to evolve
// a population over a specified number of generations.
func EvolvePopulation(pop []*Individual, numGenerations int) []*Individual {
	for i := 0; i < numGenerations; i++ {
		// Calculate the fitness of each individual in the population
		for _, ind := range pop {
			ind.fitness = CalculateFitness(ind)
		}

		// Select the fittest individuals to breed and create the next generation
		var newPop []*Individual
		for len(newPop) < len(pop) {
			parent1 := SelectFittest(pop)
			parent2 := SelectFittest(pop)
			child := Breed(parent1, parent2)
			newPop = append(newPop, child)
		}

		// Replace the current population with the newly evolved population
		pop = newPop
	}

	return pop
}

// CalculateFitness calculates the fitness of an individual.
// In this example, the fitness is simply the sum of the values
// in the individual's chromosome.
func CalculateFitness(ind *Individual) float64 {
	sum := 0
	for _, gene := range ind.chromosome {
		sum += gene
	}
	return float64(sum)
}

// SelectFittest selects the fittest individual from a population.
// In this example, we simply return the individual with the highest fitness.
func SelectFittest(pop []*Individual) *Individual {
	var fittest *Individual
	for _, ind := range pop {
		if fittest == nil || ind.fitness > fittest.fitness {
			fittest = ind
		}
	}
	return fittest
}

// Breed creates a new child individual by combining the chromosomes
// of the two parent individuals using crossover.
func Breed(parent1, parent2 *Individual) *Individual {
	// Perform crossover to create the child's chromosome
	chromosome := make([]int, len(parent1.chromosome))
	for i := 0; i < len(chromosome); i++ {
		if rand.Float64() < 0.5 {
			chromosome[i] = parent1.chromosome[i]
		} else {
			chromosome[i] = parent2.chromosome[i]
		}
	}

	// Return the newly created child individual
	return &Individual{
		chromosome: chromosome,

Is this code subject to inbreeding? Is inbreeding beneficial in the genetic algorithm?

The code example I provided does not include any measures to prevent inbreeding, so it is possible that inbreeding could occur in the population.

Whether or not inbreeding is beneficial in a genetic algorithm is not clear-cut, as it can have both positive and negative effects. On the one hand, inbreeding can increase the likelihood that beneficial traits will be inherited by offspring, which can lead to faster convergence on a solution. However, inbreeding can also increase the likelihood of genetic defects, which can reduce the overall fitness of the population and lead to suboptimal solutions.

In general, it is best to avoid inbreeding in a genetic algorithm by implementing measures to prevent closely related individuals from breeding. For example, you could use a mating pool selection method, where individuals are chosen from the population randomly with a probability proportional to their fitness, to reduce the likelihood of inbreeding.

Can you give a golang example of such measures?

Sure! Here is an example of how you could modify the EvolvePopulation function from the previous code example to prevent inbreeding using a mating pool selection method:

// EvolvePopulation applies the genetic algorithm to evolve
// a population over a specified number of generations.
func EvolvePopulation(pop []*Individual, numGenerations int) []*Individual {
	for i := 0; i < numGenerations; i++ {
		// Calculate the fitness of each individual in the population
		for _, ind := range pop {
			ind.fitness = CalculateFitness(ind)
		}

		// Select the fittest individuals to breed and create the next generation
		var newPop []*Individual
		for len(newPop) < len(pop) {
			// Create a mating pool by randomly selecting individuals
			// with a probability proportional to their fitness
			var matingPool []*Individual
			totalFitness := 0.0
			for _, ind := range pop {
				totalFitness += ind.fitness
			}
			for _, ind := range pop {
				probability := ind.fitness / totalFitness
				n := int(probability * 100)
				for i := 0; i < n; i++ {
					matingPool = append(matingPool, ind)
				}
			}

			// Select two parents from the mating pool at random
			parent1 := matingPool[rand.Intn(len(matingPool))]
			parent2 := matingPool[rand.Intn(len(matingPool))]
			child := Breed(parent1, parent2)
			newPop = append(newPop, child)
		}

		// Replace the current population with the newly evolved population
		pop = newPop
	}

	return pop
}

In this modified version of the EvolvePopulation function, a mating pool is created by selecting individuals from the population with a probability proportional to their fitness. Then, two parents are selected at random from the mating pool to breed and create a child. This approach reduces the likelihood of inbreeding by ensuring that closely related individuals are less likely to be selected as parents.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment