From 969beef0b54061df83b84602fee51b5b7d670dc5 Mon Sep 17 00:00:00 2001 From: Chris Barrick Date: Wed, 10 Feb 2016 17:46:22 -0500 Subject: [PATCH 1/4] Introduce new API for termination conditions --- interface.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/interface.go b/interface.go index bf22390..c45f42f 100644 --- a/interface.go +++ b/interface.go @@ -1,5 +1,10 @@ package evo +import "time" + +// A ConditionFn describes a termination condition. +type ConditionFn func() bool + // An EvolveFn describes an iteration of the evolution loop. The evolve function // is called once for each member of the population, possibly in parrallel, and // is responsible for producing new Genomes given some subset of the population, @@ -35,6 +40,14 @@ type Population interface { // Stop terminates the optimization. Stop() + // Poll executes a function at some frequency for the duration of the + // current optimization. If the function returns true, the current + // optimization is halted. Use a frequency of 0 for continuous polling. + Poll(freq time.Duration, cond ConditionFn) + + // Wait blocks until the evolution terminates. + Wait() + // Stats returns various statistics about the population. Stats() Stats } From 2e9317a5c89b8f6318135facbdd7e970ac19b1ba Mon Sep 17 00:00:00 2001 From: Chris Barrick Date: Wed, 10 Feb 2016 21:11:54 -0500 Subject: [PATCH 2/4] Implement new polling API in populations --- pop/gen/generational.go | 30 ++++++++++++++++++++++++++++-- pop/graph/graph.go | 36 ++++++++++++++++++++++++++++++++++-- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/pop/gen/generational.go b/pop/gen/generational.go index 92fb819..5bb590b 100644 --- a/pop/gen/generational.go +++ b/pop/gen/generational.go @@ -31,7 +31,7 @@ func (pop *Population) Evolve(members []evo.Genome, body evo.EvolveFn) { pop.setc = make(chan chan int) pop.getc = make(chan chan int) pop.valuec = make(chan evo.Genome) - pop.stopc = make(chan chan struct{}) + pop.stopc = make(chan chan struct{}, 1) go run(*pop, body) } @@ -44,7 +44,32 @@ func (pop *Population) Stop() { close(pop.setc) close(pop.getc) close(pop.valuec) - close(pop.stopc) +} + +// Poll executes a function at some frequency for the duration of the +// current optimization. If the function returns true, the current optimization +// is halted. +func (pop *Population) Poll(freq time.Duration, cond evo.ConditionFn) { + done := pop.stopc + go func() { + for { + select { + case <-time.After(freq): + if cond() { + pop.Stop() + return + } + case ch := <-done: + done <- ch + return + } + } + }() +} + +// Wait blocks until the evolution terminates. +func (pop *Population) Wait() { + pop.stopc <- <-pop.stopc } // Stats returns statistics on the fitness of genomes in the population. @@ -195,6 +220,7 @@ func run(pop Population, body evo.EvolveFn) { } } ch <- struct{}{} + pop.stopc <- ch return } } diff --git a/pop/graph/graph.go b/pop/graph/graph.go index f04c05f..b74713c 100644 --- a/pop/graph/graph.go +++ b/pop/graph/graph.go @@ -6,7 +6,11 @@ // regular population, it is known as the diffusion model. package graph -import "github.com/cbarrick/evo" +import ( + "time" + + "github.com/cbarrick/evo" +) type Graph []node @@ -16,6 +20,7 @@ type node struct { getc chan chan evo.Genome setc chan chan evo.Genome closec chan chan struct{} + done chan struct{} } // Grid creates a new graph population arranged as a 2D grid. @@ -115,7 +120,32 @@ func (n *node) stop() { <-ch close(n.getc) close(n.setc) - close(n.closec) +} + +// Poll executes a function at some frequency for the duration of the +// current optimization. If the function returns true, the current optimization +// is halted. +func (g Graph) Poll(freq time.Duration, cond evo.ConditionFn) { + done := g[0].closec + go func() { + for { + select { + case <-time.After(freq): + if cond() { + g.Stop() + return + } + case ch := <-done: + done <- ch + return + } + } + }() +} + +// Wait blocks until the evolution terminates. +func (g Graph) Wait() { + g[0].closec <- <-g[0].closec } // get returns the genome underlying the node. @@ -162,6 +192,8 @@ func (n *node) run(body evo.EvolveFn) { subpop.Stop() } ch <- struct{}{} + n.closec <- ch + return } } From 4f01270a605eecf35125da789d4c845495904076 Mon Sep 17 00:00:00 2001 From: Chris Barrick Date: Wed, 10 Feb 2016 21:12:08 -0500 Subject: [PATCH 3/4] Update examples to use new polling API --- example/ackley/ackley_test.go | 58 ++++++++++++++++---------------- example/queens/queens_test.go | 62 ++++++++++++++++++----------------- example/tsp/tsp_test.go | 56 +++++++++++++++---------------- 3 files changed, 88 insertions(+), 88 deletions(-) diff --git a/example/ackley/ackley_test.go b/example/ackley/ackley_test.go index cf29228..abe3429 100644 --- a/example/ackley/ackley_test.go +++ b/example/ackley/ackley_test.go @@ -133,27 +133,8 @@ func TestAckley(t *testing.T) { var pop gen.Population pop.Evolve(seed, Evolve) - // Tear-down: - // Upon returning, we cleanup our resources and print the solution. - defer func() { - pop.Stop() - selector.Close() - 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 and terminate when we - // have a solution or after 200,000 evaluations. - for { + // Continuously print statistics while the optimization runs. + pop.Poll(0, func() bool { count.Lock() n := count.n count.Unlock() @@ -166,16 +147,35 @@ func TestAckley(t *testing.T) { -stats.Min(), -stats.Mean(), -stats.Max(), - stats.RSD()) + -stats.RSD()) - // We've converged once the deviation is within the precision - if stats.SD() < precision { - return - } + return false + }) - // Force stop after 200,000 fitness evaluations - if n > 200000 { - return + // Terminate after 200,000 fitness evaluations. + pop.Poll(0, func() bool { + count.Lock() + n := count.n + count.Unlock() + return n > 200000 + }) + + // Terminate if the standard deviation is low. + pop.Poll(0, func() bool { + stats := pop.Stats() + return stats.SD() < precision + }) + + pop.Wait() + selector.Close() + 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) } diff --git a/example/queens/queens_test.go b/example/queens/queens_test.go index 7b3df21..9f2c50b 100644 --- a/example/queens/queens_test.go +++ b/example/queens/queens_test.go @@ -122,25 +122,8 @@ func TestQueens(t *testing.T) { pop := graph.Ring(isl) pop.Evolve(islands, gen.Migrate(migration, delay)) - // Teardown: - defer func() { - 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) - }() - - // Termination: - // We continuously poll the population for statistics to check various - // termination conditions. - for { + // Continuously print statistics while the optimization runs. + pop.Poll(0, func() bool { count.Lock() n := count.n count.Unlock() @@ -156,19 +139,38 @@ func TestQueens(t *testing.T) { -stats.Max(), -stats.RSD()) - // We've found the solution when max is 0 - if stats.Max() == 0 { - return - } + return false + }) - // We've converged once the deviation is less than 0.01 - if stats.SD() < 1e-2 { - return - } + // Terminate when we've found the solution (when max is 0) + pop.Poll(0, func() bool { + stats := pop.Stats() + return stats.Max() == 0 + }) - // Force stop after 2,000,000 fitness evaluations - if n > 2e6 { - return + // Terminate if We've converged to a deviation is less than 0.01 + pop.Poll(0, func() bool { + stats := pop.Stats() + return stats.SD() < 1e-2 + }) + + // Terminate after 2,000,000 fitness evaluations. + pop.Poll(0, func() bool { + count.Lock() + n := count.n + count.Unlock() + return n > 2e6 + }) + + pop.Wait() + 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) } diff --git a/example/tsp/tsp_test.go b/example/tsp/tsp_test.go index 302fbb7..8309d61 100644 --- a/example/tsp/tsp_test.go +++ b/example/tsp/tsp_test.go @@ -17,7 +17,6 @@ import ( const ( dim = len(cities) // the dimension of the problem size = 256 // the size of the population - stop = 2e6 // terminate after this number of fitness evalutations ) // Global objects @@ -163,26 +162,8 @@ func TestTSP(t *testing.T) { pop = graph.Hypercube(size) pop.Evolve(seed, Evolve) - // Tear-down: - // Upon returning, we cleanup our resources and print the solution. - defer func() { - 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: - // We continuously poll the population for statistics used in the - // termination conditions. - for { + // Continuously print statistics while the optimization runs. + pop.Poll(0, func() bool { count.Lock() n := count.n count.Unlock() @@ -197,17 +178,34 @@ func TestTSP(t *testing.T) { -stats.Max(), -stats.RSD()) - // Stop when we get close. Finding the true minimum could take a while. - if -stats.Max() < best*1.1 { - return - } + return false + }) - // Force stop after some number fitness evaluations - if n > stop { - t.Fail() - return + // Stop when we get close. Finding the true minimum could take a while. + pop.Poll(0, func() bool { + stats := pop.Stats() + return -stats.Max() < best*1.1 + }) + + // Terminate after 2,000,000 fitness evaluations. + pop.Poll(0, func() bool { + count.Lock() + n := count.n + count.Unlock() + return n > 2e6 + }) + + pop.Wait() + 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) } // Best is the minimum tour of the cities. From bbb4dca49294ec4b36b1e2458b42247701181e95 Mon Sep 17 00:00:00 2001 From: Chris Barrick Date: Wed, 10 Feb 2016 19:07:51 -0500 Subject: [PATCH 4/4] go fmt --- stats.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stats.go b/stats.go index 889a8ee..ab22363 100644 --- a/stats.go +++ b/stats.go @@ -12,7 +12,7 @@ type Stats struct { max, min float64 mean float64 sumsq float64 // sum of squares of deviation from the mean - count float64 + count float64 } // Put inserts a new value into the data.