Skip to content

Commit

Permalink
gago now becomes eaopt
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxHalford committed Aug 2, 2018
1 parent 7902f72 commit 72a1c63
Show file tree
Hide file tree
Showing 39 changed files with 925 additions and 633 deletions.
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,4 @@ Genetic algorithms are notorious for being [embarrassingly parallel](http://www.
## Performance

1. `go test -bench . -cpuprofile=cpu.prof`
2. `go tool pprof -pdf gago.test cpu.prof > profile.pdf`
2. `go tool pprof -pdf eaopt.test cpu.prof > profile.pdf`
509 changes: 342 additions & 167 deletions README.md

Large diffs are not rendered by default.

44 changes: 3 additions & 41 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -1,59 +1,21 @@
package gago
package eaopt

import (
"testing"
)

func BenchmarkIndividualsEvaluate(b *testing.B) {
var rng = newRand()
var indis = newIndividuals(100, NewVector, newRand())
b.ResetTimer()
for i := 0; i < b.N; i++ {
var indis = newIndividuals(2000, NewVector, rng)
indis.Evaluate(false)
}
}

func BenchmarkIndividualsEvaluateParallel(b *testing.B) {
var rng = newRand()
var indis = newIndividuals(100, NewVector, newRand())
b.ResetTimer()
for i := 0; i < b.N; i++ {
var indis = newIndividuals(2000, NewVector, rng)
indis.Evaluate(true)
}
}

func BenchmarkEvolve1Pop(b *testing.B) {
var ga = newGA()
ga.Initialize()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ga.Evolve()
}
}

func BenchmarkEvolve1PopParallel(b *testing.B) {
var ga = newGA()
ga.Initialize()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ga.Evolve()
}
}

func BenchmarkEvolve2Pop(b *testing.B) {
var ga = newGA()
ga.Initialize()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ga.Evolve()
}
}

func BenchmarkEvolve2PopParallel(b *testing.B) {
var ga = newGA()
ga.Initialize()
b.ResetTimer()
for i := 0; i < b.N; i++ {
ga.Evolve()
}
}
10 changes: 5 additions & 5 deletions crossover.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gago
package eaopt

import (
"math/rand"
Expand Down Expand Up @@ -51,24 +51,24 @@ func gnx(p1, p2 Slice, indexes []int) {
// each parent's genome and the mirroring segments are switched. n determines
// the number of crossovers (aka mirroring segments) to perform. n has to be
// equal or lower than the number of genes in each parent.
func CrossGNX(p1 Slice, p2 Slice, n int, rng *rand.Rand) {
func CrossGNX(p1 Slice, p2 Slice, n uint, rng *rand.Rand) {
var indexes = randomInts(n, 1, p1.Len(), rng)
sort.Ints(indexes)
gnx(p1, p2, indexes)
}

// CrossGNXInt calls CrossGNX on two int slices.
func CrossGNXInt(s1 []int, s2 []int, n int, rng *rand.Rand) {
func CrossGNXInt(s1 []int, s2 []int, n uint, rng *rand.Rand) {
CrossGNX(IntSlice(s1), IntSlice(s2), n, rng)
}

// CrossGNXFloat64 calls CrossGNX on two float64 slices.
func CrossGNXFloat64(s1 []float64, s2 []float64, n int, rng *rand.Rand) {
func CrossGNXFloat64(s1 []float64, s2 []float64, n uint, rng *rand.Rand) {
CrossGNX(Float64Slice(s1), Float64Slice(s2), n, rng)
}

// CrossGNXString calls CrossGNX on two string slices.
func CrossGNXString(s1 []string, s2 []string, n int, rng *rand.Rand) {
func CrossGNXString(s1 []string, s2 []string, n uint, rng *rand.Rand) {
CrossGNX(StringSlice(s1), StringSlice(s2), n, rng)
}

Expand Down
2 changes: 1 addition & 1 deletion crossover_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gago
package eaopt

import (
"fmt"
Expand Down
121 changes: 121 additions & 0 deletions diff_evo.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package eaopt

import (
"errors"
"math/rand"
)

// An Agent is a candidate solution to a problem.
type Agent struct {
X []float64
DE *DiffEvo
}

// Evaluate the Agent by computing the value of the function at the current
// position.
func (a Agent) Evaluate() (float64, error) {
return a.DE.F(a.X), nil
}

// Mutate the Agent.
func (a *Agent) Mutate(rng *rand.Rand) {
var agents = a.DE.sampleAgents(3, rng)
for i := range a.X {
if rng.Float64() < a.DE.CRate {
a.X[i] = agents[0].X[i] + a.DE.DWeight*(agents[1].X[i]-agents[2].X[i])
}
}
}

// Crossover doesn't do anything.
func (a *Agent) Crossover(q Genome, rng *rand.Rand) {}

// Clone returns a deep copy of an Agent.
func (a Agent) Clone() Genome {
return &Agent{
X: copyFloat64s(a.X),
DE: a.DE,
}
}

// DiffEvo implements differential evolution.
type DiffEvo struct {
Min, Max float64 // Boundaries for initial values
CRate float64 // Crossover rate
DWeight float64 // Differential weight
NDims uint
F func(X []float64) float64
GA *GA
}

// NewDiffEvo instantiates and returns a DiffEvo instance after having checked
// for input errors.
func NewDiffEvo(nAgents, nSteps uint, min, max, cRate, dWeight float64, parallel bool, rng *rand.Rand) (*DiffEvo, error) {
// Check inputs
if nAgents < 4 {
return nil, errors.New("nAgents should be at least 4")
}
if nSteps == 0 {
return nil, errors.New("nSteps should be stricly higher than 0")
}
if min >= max {
return nil, errors.New("min should be stricly inferior to max")
}
if rng == nil {
rng = newRand()
}
// Instantiate a GA
var ga, err = GAConfig{
NPops: 1,
PopSize: nAgents,
NGenerations: nSteps,
HofSize: 1,
Model: ModMutationOnly{
Strict: true,
},
ParallelEval: parallel,
RNG: rand.New(rand.NewSource(rng.Int63())),
}.NewGA()
return &DiffEvo{
Min: min,
Max: max,
CRate: cRate,
DWeight: dWeight,
GA: ga,
}, err
}

// NewDefaultDiffEvo calls NewDiffEvo with default values.
func NewDefaultDiffEvo() (*DiffEvo, error) {
return NewDiffEvo(40, 30, -5, 5, 0.5, 0.2, false, newRand())
}

func (de *DiffEvo) newAgent(rng *rand.Rand) Genome {
return &Agent{
X: InitUnifFloat64(de.NDims, de.Min, de.Max, rng),
DE: de,
}
}

func (de DiffEvo) sampleAgents(k uint, rng *rand.Rand) []Agent {
var (
idxs = randomInts(k, 0, len(de.GA.Populations[0].Individuals), rng)
agents = make([]Agent, k)
)
for i, idx := range idxs {
agents[i] = *de.GA.Populations[0].Individuals[idx].Genome.(*Agent)
}
return agents
}

// Minimize finds the minimum of a given real-valued function.
func (de *DiffEvo) Minimize(f func([]float64) float64, nDims uint) ([]float64, float64, error) {
// Set the function to minimize so that the particles can access it
de.F = f
de.NDims = nDims
// Run the genetic algorithm
var err = de.GA.Minimize(de.newAgent)
// Return the best obtained vector along with the associated function value
var best = de.GA.HallOfFame[0]
return best.Genome.(*Agent).X, best.Fitness, err
}
6 changes: 3 additions & 3 deletions distance.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gago
package eaopt

import (
"fmt"
Expand Down Expand Up @@ -67,7 +67,7 @@ func calcAvgDistances(indis Individuals, dm DistanceMemoizer) map[string]float64
return avgDistances
}

func rebalanceClusters(clusters []Individuals, dm DistanceMemoizer, minPerCluster int) error {
func rebalanceClusters(clusters []Individuals, dm DistanceMemoizer, minPerCluster uint) error {
// Calculate the number of missing Individuals per cluster for each cluster
// to reach at least minPerCluster Individuals.
var missing = make([]int, len(clusters))
Expand All @@ -77,7 +77,7 @@ func rebalanceClusters(clusters []Individuals, dm DistanceMemoizer, minPerCluste
return fmt.Errorf("Cluster %d has 0 individuals", i)
}
// Calculate the number of missing Individual in the cluster to reach minPerCluster
missing[i] = minPerCluster - len(cluster)
missing[i] = int(minPerCluster) - len(cluster)
}
// Check if there are enough Individuals to rebalance the clusters.
if sumInts(missing) >= 0 {
Expand Down
14 changes: 7 additions & 7 deletions distance_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package gago
package eaopt

import (
"errors"
Expand Down Expand Up @@ -72,8 +72,8 @@ func TestRebalanceClusters(t *testing.T) {
var testCases = []struct {
clusters []Individuals
dm DistanceMemoizer
minClusterSize int
newClusterSizes []int
minClusterSize uint
newClusterSizes []uint
err error
}{
{
Expand All @@ -94,7 +94,7 @@ func TestRebalanceClusters(t *testing.T) {
},
dm: newDistanceMemoizer(l1Distance),
minClusterSize: 2,
newClusterSizes: []int{3, 2, 2},
newClusterSizes: []uint{3, 2, 2},
err: nil,
},
{
Expand All @@ -107,7 +107,7 @@ func TestRebalanceClusters(t *testing.T) {
},
dm: newDistanceMemoizer(l1Distance),
minClusterSize: 1,
newClusterSizes: []int{2, 0},
newClusterSizes: []uint{2, 0},
err: errors.New("Cluster number 2 is empty"),
},
{
Expand All @@ -122,7 +122,7 @@ func TestRebalanceClusters(t *testing.T) {
},
dm: newDistanceMemoizer(l1Distance),
minClusterSize: 2,
newClusterSizes: []int{2, 0},
newClusterSizes: []uint{2, 0},
err: errors.New("Not enough individuals to rebalance"),
},
}
Expand All @@ -136,7 +136,7 @@ func TestRebalanceClusters(t *testing.T) {
// Check new cluster sizes
if err == nil {
for j, cluster := range tc.clusters {
if len(cluster) != tc.newClusterSizes[j] {
if len(cluster) != int(tc.newClusterSizes[j]) {
t.Errorf("Wrong new cluster size in test case number %d", i)
}
}
Expand Down
Loading

0 comments on commit 72a1c63

Please sign in to comment.