From 8747c4f425b48455156c4029057c851ccbb55f39 Mon Sep 17 00:00:00 2001 From: Chris Barrick Date: Sat, 6 Feb 2016 03:08:06 -0500 Subject: [PATCH] Update examples for the new api --- example/ackley/ackley_test.go | 42 +++++++-------- example/queens/queens_test.go | 96 ++++++++++++++++------------------- example/tsp/tsp_test.go | 31 +++++++---- 3 files changed, 82 insertions(+), 87 deletions(-) diff --git a/example/ackley/ackley_test.go b/example/ackley/ackley_test.go index ebecc0e..cf29228 100644 --- a/example/ackley/ackley_test.go +++ b/example/ackley/ackley_test.go @@ -32,13 +32,6 @@ var ( // them to this pool. This pool returns to each member a different one of // most fit to be their replacement in the next generation. selector = sel.ElitePool(40, 280) - - // A free-list used to recycle memory. - vectors = sync.Pool{ - New: func() interface{} { - return make(real.Vector, dim) - }, - } ) // The ackley type specifies our genome. We evolve a real-valued vector that @@ -51,12 +44,6 @@ type ackley struct { once sync.Once // used to compute fitness lazily } -// Close recycles the memory of this genome to be use for new genomes. -func (ack *ackley) Close() { - vectors.Put(ack.gene) - vectors.Put(ack.steps) -} - // Returns the fitness as a string. func (ack *ackley) String() string { return fmt.Sprint(-ack.Fitness()) @@ -94,13 +81,13 @@ func (ack *ackley) Fitness() float64 { // The population calls the Evolve method of each genome, in parallel. Then, // each receiver returns a value to replace it in the next generation. A global // selector object synchronises replacement among the parallel parents. -func (ack *ackley) Evolve(suitors ...evo.Genome) evo.Genome { +func Evolve(ack evo.Genome, suitors []evo.Genome) evo.Genome { for i := 0; i < 7; i++ { // Creation: // We create the child genome from recycled memory when we can. var child ackley - child.gene = vectors.Get().(real.Vector) - child.steps = vectors.Get().(real.Vector) + child.gene = make(real.Vector, dim) + child.steps = make(real.Vector, dim) // Crossover: // Select two parents at random. @@ -136,22 +123,31 @@ func TestAckley(t *testing.T) { // Setup: // We initialize a set of 40 random solutions, // then add them to a generational population. - init := make([]evo.Genome, 40) - for i := range init { - init[i] = &ackley{ + seed := make([]evo.Genome, 40) + for i := range seed { + seed[i] = &ackley{ gene: real.Random(dim, 30), steps: real.Random(dim, 1), } } - pop := gen.New(init) - pop.Start() + var pop gen.Population + pop.Evolve(seed, Evolve) // Tear-down: // Upon returning, we cleanup our resources and print the solution. defer func() { - pop.Close() + pop.Stop() selector.Close() - fmt.Println("\nSolution:", evo.Max(pop)) + best := seed[0] + bestFit := seed[0].Fitness() + for i := range seed { + fit := seed[i].Fitness() + if fit > bestFit { + best = seed[i] + bestFit = fit + } + } + fmt.Println("\nSolution:", best) }() // Run: diff --git a/example/queens/queens_test.go b/example/queens/queens_test.go index 7166a87..7b3df21 100644 --- a/example/queens/queens_test.go +++ b/example/queens/queens_test.go @@ -2,7 +2,6 @@ package queens import ( "fmt" - "math" "math/rand" "sync" "testing" @@ -17,31 +16,24 @@ import ( // Tuneables const ( - dim = 128 // the dimension of the board - size = dim * 2 // the size of the population + dim = 128 // the dimension of the problem + size = dim * 4 // the size of the population isl = 4 // the number of islands in which to divide the population - // the delay between island communications - delay = 500 * time.Millisecond + migration = size / isl / 8 // the size of migrations + delay = 1 * time.Second // the delay between migrations ) // Global objects var ( - // Count of the number of fitness evaluations. + // counts the number of fitness evaluations count struct { sync.Mutex n int } - - // A free-list used to recycle memory. - pool = sync.Pool{ - New: func() interface{} { - return rand.Perm(dim) - }, - } ) -// The queens type is our genome type. We evolve a permuation of [0,n) +// The queens type is our genome. We evolve a permuation of [0,n) // representing the position of queens on an n x n board type queens struct { gene []int // permutation representation of an n-queens solution @@ -49,12 +41,6 @@ type queens struct { once sync.Once // used to compute fitness lazily } -// Close recycles the memory of this genome to be use for new genomes. -func (q *queens) Close() { - pool.Put(q.gene) - q.gene = nil -} - // String returns the gene contents and number of conflicts. func (q *queens) String() string { return fmt.Sprintf("%v@%v", q.gene, -q.Fitness()) @@ -86,23 +72,22 @@ func (q *queens) Fitness() float64 { return q.fitness } -// Evolve implements the inner loop of the evolutionary algorithm. -// The population calls the Evolve method of each genome, in parallel. Then, -// each receiver returns a value to replace it in the next generation. -func (q *queens) Evolve(matingPool ...evo.Genome) evo.Genome { +// Evolution implements the body of the evolution loop. +func Evolution(q evo.Genome, suitors []evo.Genome) evo.Genome { // Crossover: // We're implementing a diffusion model. For each member of the population, // we receive a small mating pool containing only our neighbors. We choose // a mate using a random binary tournament and create a child with // partially mapped crossover. - mate := sel.BinaryTournament(matingPool...).(*queens) - child := &queens{gene: pool.Get().([]int)} - perm.PMX(child.gene, q.gene, mate.gene) + mom := q.(*queens) + dad := sel.BinaryTournament(suitors...).(*queens) + child := &queens{gene: make([]int, len(mom.gene))} + perm.PMX(child.gene, mom.gene, dad.gene) // Mutation: // Perform n random swaps where n is taken from an exponential distribution. - mutationCount := math.Ceil(rand.ExpFloat64() - 0.5) - for i := float64(0); i < mutationCount; i++ { + // mutationCount := math.Ceil(rand.ExpFloat64() - 0.5) + for i := float64(0); i < 5; i++ { j := rand.Intn(len(child.gene)) k := rand.Intn(len(child.gene)) child.gene[j], child.gene[k] = child.gene[k], child.gene[j] @@ -120,33 +105,40 @@ func TestQueens(t *testing.T) { fmt.Printf("Find a solution to %d-queens\n", dim) // Setup: - // We create a random initial population and divide it into islands. Each - // island is evolved independently. The islands are grouped together into - // a wrapping population. The wrapper coordiates migrations between the - // islands according to the delay period. - init := make([]evo.Genome, size) - for i := range init { - init[i] = &queens{gene: pool.Get().([]int)} + // We create an initial set of random candidates and divide them into "islands". + // Each island is evolved independently in a generational population. + // The islands are then linked together into a graph population with + seed := make([]evo.Genome, size) + for i := range seed { + seed[i] = &queens{gene: perm.New(dim)} } islands := make([]evo.Genome, isl) islSize := size / isl for i := range islands { - islands[i] = gen.New(init[i*islSize : (i+1)*islSize]) - islands[i].(evo.Population).Start() + var island gen.Population + island.Evolve(seed[i*islSize:(i+1)*islSize], Evolution) + islands[i] = &island } - pop := graph.Ring(islands) - pop.SetDelay(delay) - pop.Start() + pop := graph.Ring(isl) + pop.Evolve(islands, gen.Migrate(migration, delay)) - // Tear-down: - // Upon returning, we cleanup our resources and print the solution. + // Teardown: defer func() { - pop.Close() - fmt.Println("\nSolution:", evo.Max(pop)) + pop.Stop() + best := seed[0] + bestFit := seed[0].Fitness() + for i := range seed { + fit := seed[i].Fitness() + if fit > bestFit { + best = seed[i] + bestFit = fit + } + } + fmt.Println("\nSolution:", best) }() - // Run: - // We continuously poll the population for statistics used in the + // Termination: + // We continuously poll the population for statistics to check various // termination conditions. for { count.Lock() @@ -154,14 +146,15 @@ func TestQueens(t *testing.T) { count.Unlock() stats := pop.Stats() - // "\x1b[2K" is the escape code to clear the line - // The fitness of minimization problems is negative + // "\x1b[2K" is the xterm escape code to clear the line + // Because this is a minimization problem, the fitness is negative. + // Thus we update the statistics accordingly. fmt.Printf("\x1b[2K\rCount: %7d | Max: %3.0f | Mean: %3.0f | Min: %3.0f | RSD: %9.2e", n, -stats.Min(), -stats.Mean(), -stats.Max(), - stats.RSD()) + -stats.RSD()) // We've found the solution when max is 0 if stats.Max() == 0 { @@ -177,8 +170,5 @@ func TestQueens(t *testing.T) { if n > 2e6 { return } - - // var blocker chan struct{} - // <-blocker } } diff --git a/example/tsp/tsp_test.go b/example/tsp/tsp_test.go index 8df40f2..302fbb7 100644 --- a/example/tsp/tsp_test.go +++ b/example/tsp/tsp_test.go @@ -121,7 +121,7 @@ func (t *tsp) TwoOpt() { // Evolve implements the inner loop of the evolutionary algorithm. // The population calls the Evolve method of each genome, in parallel. Then, // each receiver returns a value to replace it in the next generation. -func (t *tsp) Evolve(matingPool ...evo.Genome) evo.Genome { +func Evolve(current evo.Genome, matingPool []evo.Genome) evo.Genome { // Selection: // Select each parent using a simple random binary tournament mom := sel.BinaryTournament(matingPool...).(*tsp) @@ -144,8 +144,8 @@ func (t *tsp) Evolve(matingPool ...evo.Genome) evo.Genome { // Replacement: // Only replace if the child is better or equal - if t.Fitness() > child.Fitness() { - return t + if current.Fitness() > child.Fitness() { + return current } return child } @@ -156,18 +156,27 @@ func TestTSP(t *testing.T) { // Setup: // We create a random initial population // and evolve it using a generational model. - init := make([]evo.Genome, size) - for i := range init { - init[i] = &tsp{gene: pool.Get().([]int)} + seed := make([]evo.Genome, size) + for i := range seed { + seed[i] = &tsp{gene: pool.Get().([]int)} } - pop = graph.Hypercube(init) - pop.Start() + pop = graph.Hypercube(size) + pop.Evolve(seed, Evolve) // Tear-down: // Upon returning, we cleanup our resources and print the solution. defer func() { - pop.Close() - fmt.Println("\nTour:", evo.Max(pop)) + pop.Stop() + best := seed[0] + bestFit := seed[0].Fitness() + for i := range seed { + fit := seed[i].Fitness() + if fit > bestFit { + best = seed[i] + bestFit = fit + } + } + fmt.Println("\nTour:", best) }() // Run: @@ -186,7 +195,7 @@ func TestTSP(t *testing.T) { -stats.Min(), -stats.Mean(), -stats.Max(), - stats.RSD()) + -stats.RSD()) // Stop when we get close. Finding the true minimum could take a while. if -stats.Max() < best*1.1 {