Skip to content

Commit

Permalink
made the genome pluggable
Browse files Browse the repository at this point in the history
  • Loading branch information
kelindar committed Sep 26, 2020
1 parent 8626dde commit b8da552
Show file tree
Hide file tree
Showing 6 changed files with 345 additions and 331 deletions.
166 changes: 82 additions & 84 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,85 +1,83 @@
# Genetic Algorithm

![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/kelindar/evolve)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/kelindar/evolve)](https://pkg.go.dev/github.com/kelindar/evolve)
[![Go Report Card](https://goreportcard.com/badge/github.com/kelindar/evolve)](https://goreportcard.com/report/github.com/kelindar/evolve)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Coverage Status](https://coveralls.io/repos/github/kelindar/evolve/badge.svg)](https://coveralls.io/github/kelindar/evolve)

This repository contains a simple implementation of a genetic algorithm for evolving arbitrary `[]byte` genomes. Under the hood, it uses a simple random binary crossover and mutation to do the trick. There's a double-buffering in place to prevent unnecessary allocations and a relatively simple API around it.


## Usage

In order to use this, we first need to create a "phenotype" representation which contains the dna `[]byte`. It should implement the `Evolver` interface which contains `Genome()` and `Evolve()` methods, in the example here we are creating a simple text which contains the binary representation of the text itself.

```go
// Text represents a text with a dna (text itself in this case)
type text struct {
dna []byte
}

// Genome returns the genome
func (t *text) Genome() []byte {
return t.dna
}

// Evolve updates the genome
func (t *text) Evolve(v []byte) {
t.dna = v
}

// String returns a string representation
func (t *text) String() string {
return string(t.dna)
}
```
Next, we'll need a fitness function to evaluate how good a genome is. In this example we're creating a fitness function for an abritrary string which simply returns a `func(Evolver) float32`
```go
// fitnessFor returns a fitness function for a string
func fitnessFor(text string) evolve.Fitness {
target := []byte(text)
return func(v evolve.Evolver) float32 {
var score float32
for i, v := range v.Genome() {
if v == target[i] {
score++
}
}
return score / float32(len(target))
}
}
```

Finally, we can wire everything together by using `New()` function to create a population, and evolve it by repeatedly calling `Evolve()` method as shown below.

```go
func main() {
const target = "Hello World"
const n = 200

// Create a fitness function
fit := fitnessFor(target)

// Create a population
population := make([]evolve.Evolver, 0, n)
for i := 0; i < n; i++ {
population = append(population, new(text))
}

// Create a population
pop := evolve.New(population, fit, len(target))

// Evolve over many generations
for i := 0 ; i < 100000; i++ {
pop.Evolve()
}

// Get the fittest member of the population
fittest := pop.Fittest()
}
```

## License

# Genetic Algorithm

![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/kelindar/evolve)
[![PkgGoDev](https://pkg.go.dev/badge/github.com/kelindar/evolve)](https://pkg.go.dev/github.com/kelindar/evolve)
[![Go Report Card](https://goreportcard.com/badge/github.com/kelindar/evolve)](https://goreportcard.com/report/github.com/kelindar/evolve)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![Coverage Status](https://coveralls.io/repos/github/kelindar/evolve/badge.svg)](https://coveralls.io/github/kelindar/evolve)

This repository contains a simple implementation of a genetic algorithm for evolving arbitrary types. There's a double-buffering in place to prevent unnecessary allocations and a relatively simple API around it.

It also provides a `binary` package for evolving `[]byte` genomes. Under the hood, it uses a simple random binary crossover and mutation to do the trick.


## Usage

In order to use this, we first need to create a "phenotype" representation which contains the dna. In this example we're using the `binary` package in order to evolve a string. It should implement the `Evolver` interface which contains `Genome()` and `Evolve()` methods, in the example here we are creating a simple text which contains the binary representation of the text itself.

```go
// Text represents a text with a dna (text itself in this case)
type text struct {
dna evolve.Genome
}

// Genome returns the genome
func (t *text) Genome() []byte {
return t.dna
}

// Evolve updates the genome
func (t *text) Evolve(v []byte) {
t.dna = v
}
```
Next, we'll need a fitness function to evaluate how good a genome is. In this example we're creating a fitness function for an abritrary string which simply returns a `func(Evolver) float32`
```go
// fitnessFor returns a fitness function for a string
func fitnessFor(text string) evolve.Fitness {
target := []byte(text)
return func(v evolve.Evolver) float32 {
var score float32
genome := v.Genome().(*binary.Genome)
for i, v := range *genome {
if v == target[i] {
score++
}
}
return score / float32(len(target))
}
}
```

Finally, we can wire everything together by using `New()` function to create a population, and evolve it by repeatedly calling `Evolve()` method as shown below.

```go
func main() {
const target = "Hello World"
const n = 200

// Create a fitness function
fit := fitnessFor(target)

// Create a population
population := make([]evolve.Evolver, 0, n)
for i := 0; i < n; i++ {
population = append(population, new(text))
}

// Create a population
pop := evolve.New(population, fit, len(target))

// Evolve over many generations
for i := 0 ; i < 100000; i++ {
pop.Evolve()
}

// Get the fittest member of the population
fittest := pop.Fittest()
}
```

## License

Tile is licensed under the [MIT License](LICENSE.md).
50 changes: 50 additions & 0 deletions binary/genome.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Roman Atachiants and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

package binary

import (
crand "crypto/rand"
mrand "math/rand"

"github.com/kelindar/evolve"
)

// Genome represents a binary genome
type Genome []byte

// Crossover implements a random binary crossover
func (g *Genome) Crossover(p1, p2 evolve.Genome) {
v1, v2 := *p1.(*Genome), *p2.(*Genome)
dst := *g
n := len(v1)
for i := 0; i < n; i++ {
r := randByte()
dst[i] = (v1[i] & byte(r)) ^ (v2[i] & (^byte(r)))
}
}

// Mutate mutates a random gene
func (g Genome) Mutate() {
const rate = 0.01
if mrand.Float32() >= rate {
return
}

i := mrand.Int31n(int32(len(g)))
g[i] = randByte()
}

// Make creates a function for a random genome string
func Make(length int) func() evolve.Genome {
return func() evolve.Genome {
v := make(Genome, length)
crand.Read(v)
return &v
}
}

// randByte generates a random byte
func randByte() byte {
return byte(mrand.Int31n(256))
}
8 changes: 4 additions & 4 deletions crossover_test.go → binary/genome_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Roman Atachiants and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

package evolve
// Copyright (c) Roman Atachiants and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

package binary
24 changes: 0 additions & 24 deletions crossover.go

This file was deleted.

Loading

0 comments on commit b8da552

Please sign in to comment.