Skip to content

Commit

Permalink
WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxHalford committed Mar 24, 2017
1 parent 068ea3e commit 52fb045
Show file tree
Hide file tree
Showing 13 changed files with 117 additions and 99 deletions.
3 changes: 3 additions & 0 deletions HACKME.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
## Ideas

- Speciation
- Slice interface

- Add functional tests with deterministic operators to make everything works
- Add more example usage
- Add a way to compare individuals based on their genome to improve speciation
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
</a>
<!-- Build status -->
<a href="https://travis-ci.org/MaxHalford/gago">
<img src="https://img.shields.io/travis/MaxHalford/gago.svg?style=flat-square" alt="build_status" />
<img src="https://img.shields.io/travis/MaxHalford/gago/master.svg?style=flat-square" alt="build_status" />
</a>
<!-- Test coverage -->
<a href="https://coveralls.io/github/MaxHalford/gago?branch=master">
Expand Down
26 changes: 26 additions & 0 deletions distance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package gago

type DistanceMemoizer struct {
Metric func(a, b Genome) float64
Distances map[Genome]map[Genome]float64
}

func makeDistanceMemoizer(metric func(a, b Genome) float64) DistanceMemoizer {
return DistanceMemoizer{
Metric: metric,
Distances: make(map[Genome]map[Genome]float64),
}
}

func (dm *DistanceMemoizer) getDistance(a, b Genome) float64 {
if dist, ok := dm.Distances[a][b]; ok {
return dist
}
if dist, ok := dm.Distances[b][a]; ok {
return dist
}
var dist = dm.Metric(a, b)
dm.Distances[a][b] = dist
dm.Distances[b][a] = dist
return dist
}
19 changes: 19 additions & 0 deletions distance_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package gago

import (
"math"
"testing"
)

func L1Distance(x1, x2 Genome) (dist float64) {
var g1 = x1.(Vector)
var g2 = x2.(Vector)
for i := range g1 {
dist += math.Abs(g1[i] - g2[i])
}
return
}

func TestDistanceMemoizer(t *testing.T) {
var dm = makeDistanceMemoizer(L1Distance)
}
2 changes: 1 addition & 1 deletion ga.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ func (ga *GA) Enhance() {
ga.Model.Apply(&species[k])
}
// Merge each cluster back into the original population
ga.Populations[j].Individuals = species.merge()
ga.Populations[j].Individuals = species.mergeIndividuals()
} else {
// Else apply the evolution model to the entire population
ga.Model.Apply(&ga.Populations[j])
Expand Down
12 changes: 10 additions & 2 deletions individual.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ func MakeIndividual(genome Genome) Individual {
}
}

// DeepCopy an individual.
func (indi Individual) DeepCopy() Individual {
// Copy returns the same exact same individual but with a different pointer.
func (indi Individual) Copy() Individual {
return MakeIndividual(indi.Genome)
}

Expand Down Expand Up @@ -66,6 +66,14 @@ func (indi *Individual) Crossover(indi2 Individual, rng *rand.Rand) (Individual,
// be called declaratively.
type Individuals []Individual

// Copy returns the same exact same slice of individuals but with a different
// pointer.
func (indis Individuals) Copy() Individuals {
var newIndis = make(Individuals, len(indis))
copy(newIndis, indis)
return newIndis
}

// Generate a slice of n new individuals.
func makeIndividuals(n int, gm GenomeMaker, rng *rand.Rand) Individuals {
var indis = make(Individuals, n)
Expand Down
14 changes: 13 additions & 1 deletion individual_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ func TestDeepCopyIndividual(t *testing.T) {
var (
genome = MakeVector(makeRandomNumberGenerator())
indi1 = MakeIndividual(genome)
indi2 = indi1.DeepCopy()
indi2 = indi1.Copy()
)
if &indi1 == &indi2 || &indi1.Genome == &indi2.Genome {
t.Error("Individual was not deep copied")
Expand Down Expand Up @@ -69,6 +69,18 @@ func TestMakeIndividuals(t *testing.T) {
}
}

func TestDeepCopyIndividuals(t *testing.T) {
var (
indis = makeIndividuals(10, MakeVector, makeRandomNumberGenerator())
newIndis = indis.Copy()
)
for i := range indis {
if &indis[i] == &newIndis[i] || &indis[i].Genome == &newIndis[i].Genome {
t.Error("Individual was not deep copied")
}
}
}

func TestEvaluateIndividuals(t *testing.T) {
var indis = makeIndividuals(10, MakeVector, makeRandomNumberGenerator())
for _, indi := range indis {
Expand Down
4 changes: 2 additions & 2 deletions models.go
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ func (mod ModSimAnn) Apply(pop *Population) {
for mod.T > mod.Tmin {
for i, indi := range pop.Individuals {
// Generate a random neighbour through mutation
var neighbour = indi.DeepCopy()
var neighbour = indi.Copy()
neighbour.Mutate(pop.rng)
neighbour.Evaluate()
if neighbour.Fitness < indi.Fitness {
Expand Down Expand Up @@ -315,7 +315,7 @@ type ModMutationOnly struct {
func (mod ModMutationOnly) Apply(pop *Population) {
var chosen, positions = mod.Selector.Apply(mod.NChosen, pop.Individuals, pop.rng)
for i, indi := range chosen {
var mutant = indi.DeepCopy()
var mutant = indi.Copy()
mutant.Mutate(pop.rng)
mutant.Evaluate()
if !mod.Strict || (mod.Strict && mutant.Fitness > indi.Fitness) {
Expand Down
3 changes: 2 additions & 1 deletion selection.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package gago
import (
"errors"
"math/rand"
"sort"
)

// Selector chooses a subset of size n from a group of individuals. The group of
Expand Down Expand Up @@ -84,7 +85,7 @@ func (sel SelRoulette) Apply(n int, indis Individuals, rng *rand.Rand) (Individu
)
for i := range selected {
var (
index = bisectLeftFloat64(rand.Float64(), weights)
index = sort.SearchFloat64s(weights, rand.Float64())
winner = indis[index]
)
indexes[i] = index
Expand Down
40 changes: 38 additions & 2 deletions speciation.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
package gago

import "math"
import (
"math"
)

// // A Speciator partitions a population into n smaller subpopulations. Each
// // subpopulation shares the same random number generator inherited from the
// // initial population.
// type Speciator interface {
// Apply(pop Population, rng *rand.Rand) Populations
// }

// // SpecKMedoids (k-medoid clustering). The implementation is based on the
// // Partitioning Around Medoids algorithm (PAM); the only variation is that the
// // initial medoids are generated deterministically by choosing the ones with
// // the lowest average dissimilarities.
// type SpecKMedoids struct {
// K int // Number of medoids
// Metric func(a, b Individual) float64 // Dissimimilarity measure
// }

// // Apply SpecKMedoids.
// func (kmed SpecKMedoids) Apply(pop Population, rng *rand.Rand) Populations {
// var (
// pops = make(Populations, kmed.K)
// dm = makeDistanceMemoizer(kmed.metric)
// )
// var indis = pop.Individuals.Copy()
// // Calculate the dissimilarity matrix
// var D = calcDissimilarityMatrix(pop.Individuals, kmed.DissMeasure)
// // Select the k individuals with the lowest average dissimiliraties
// var avgDisses = make([]float64, len(indis))
// for i := range avgs {
// avgDisses[i] = meanFloat64s(D[i])
// }
// sort.Slice(indis, func(i, j int) bool { return avgDisses[i] < avgDisses[j] })
// return pops
// }

// Speciate splits n individuals into k species based on the fitness of each
// individual where each species contains m = n/k (rounded to the closest
Expand Down Expand Up @@ -28,7 +64,7 @@ func (pop Population) speciate(k int) Populations {
}

// Merge k species each of size of n into a single slice of k*n individuals.
func (pops Populations) merge() Individuals {
func (pops Populations) mergeIndividuals() Individuals {
var indis Individuals
for _, pop := range pops {
indis = append(indis, pop.Individuals...)
Expand Down
4 changes: 2 additions & 2 deletions speciation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestSpeciation(t *testing.T) {
}
}

func TestSpeciationMerge(t *testing.T) {
func TestSpeciationMergeIndividuals(t *testing.T) {
var (
nbrIndividuals = []int{1, 2, 3}
nbrClusters = []int{1, 2, 3}
Expand All @@ -45,7 +45,7 @@ func TestSpeciationMerge(t *testing.T) {
Individuals: makeIndividuals(nbi, MakeVector, rng),
}
}
var indis = species.merge()
var indis = species.mergeIndividuals()
// Check the species of individuals
if len(indis) != nbi*nbc {
t.Error("Merge didn't work properly")
Expand Down
28 changes: 0 additions & 28 deletions util.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,31 +163,3 @@ func union(x, y set) set {
}
return u
}

// bisectLeftFloat64 searches for the index of the lowest of the values that are higher than a given
// value.
func bisectLeftFloat64(value float64, floats []float64) int {
var (
index = -1
a = 0
b = len(floats) / 2
c = len(floats) - 1
)

for a != b && b != c {
if value <= floats[b] {
index = b
c = b
b = (a + c) / 2
} else {
a = b
b = (a + c + 1) / 2
}
}

if value <= floats[b] {
index = b
}

return index
}
59 changes: 0 additions & 59 deletions util_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,62 +287,3 @@ func TestUnion(t *testing.T) {
}
}
}

func TestBisectLeftFloat64(t *testing.T) {
var testCases = []struct {
value float64
floats []float64
index int
}{
{
value: -1,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 0,
},
{
value: 0,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 0,
},
{
value: 0.1,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 1,
},
{
value: 0.2,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 1,
},
{
value: 0.3,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 2,
},
{
value: 0.5,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 3,
},
{
value: 0.9,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 5,
},
{
value: 1,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: 5,
},
{
value: 2,
floats: []float64{0, 0.2, 0.4, 0.6, 0.8, 1},
index: -1,
},
}
for _, test := range testCases {
if bisectLeftFloat64(test.value, test.floats) != test.index {
t.Error("bisectLeftFloat64 didn't work as expected")
}
}
}

0 comments on commit 52fb045

Please sign in to comment.