Skip to content

Commit 6c17807

Browse files
committed
Rename Enhance to Evolve and add parallel methods
1 parent def1672 commit 6c17807

File tree

7 files changed

+106
-75
lines changed

7 files changed

+106
-75
lines changed

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func main() {
133133

134134
fmt.Printf("Best fitness at generation 0: %f\n", ga.Best.Fitness)
135135
for i := 1; i < 10; i++ {
136-
ga.Enhance()
136+
ga.Evolve()
137137
fmt.Printf("Best fitness at generation %d: %f\n", i, ga.Best.Fitness)
138138
}
139139
}
@@ -301,7 +301,7 @@ You have to fill in the first set of fields, the rest are generated when calling
301301
- `Migrator` and `MigFrequency` should be provided if you want to exchange individuals between populations in case of a multi-population GA. If not the populations will be run independently. Again this is an advanced concept in the genetic algorithms field that you shouldn't deal with at first.
302302
- `Speciator` will split each population in distinct species at each generation. Each specie will be evolved separately from the others, after all the species has been evolved they are regrouped.
303303
- `Logger` is optional and provides basic population statistics, you can read more about it in the [logging section](#logging-population-statistics).
304-
- `Callback` is optional will execute any piece of code you wish every time `ga.Enhance()` is called. `Callback` will also be called when `ga.Initialize()` is. Using a callback can be useful for many things:
304+
- `Callback` is optional will execute any piece of code you wish every time `ga.Evolve()` is called. `Callback` will also be called when `ga.Initialize()` is. Using a callback can be useful for many things:
305305
- Calculating specific population statistics that are not provided by the logger
306306
- Changing parameters of the GA after a certain number of generations
307307
- Monitoring for converging populations
@@ -310,13 +310,13 @@ You have to fill in the first set of fields, the rest are generated when calling
310310
- Fields populated at runtime
311311
- `Populations` is where all the current populations and individuals are kept.
312312
- `HallOfFame` contains the `NBest` individuals ever encountered. This slice is always sorted, meaning that the first element of the slice will be the best individual ever encountered.
313-
- `Age` indicates the duration the GA has spent calling the `Enhance` method.
314-
- `Generations` indicates how many times the `Enhance` method has been called.
313+
- `Age` indicates the duration the GA has spent calling the `Evolve` method.
314+
- `Generations` indicates how many times the `Evolve` method has been called.
315315

316316

317317
### Running a GA
318318

319-
Once you have implemented the `Genome` interface and instantiated a `GA` struct you are good to go. You can call the `GA`'s `Enhance` method which will apply a model once (see the [models section](#models)). It's your choice if you want to call `Enhance` method multiple by using a loop or by imposing a time limit. The `Enhance` method will return an `error` which you should handle. If your population is evolving when you call `Enhance` it's most likely because `Enhance` did not return a `nil` error.
319+
Once you have implemented the `Genome` interface and instantiated a `GA` struct you are good to go. You can call the `GA`'s `Evolve` method which will apply a model once (see the [models section](#models)). It's your choice if you want to call `Evolve` method multiple by using a loop or by imposing a time limit. The `Evolve` method will return an `error` which you should handle. If your population is evolving when you call `Evolve` it's most likely because `Evolve` did not return a `nil` error.
320320

321321
At any time you have access to the `GA`'s `Best` field which contains a `Fitness` field and a `Genome` field respectively indicating the overall best obtained solution and the parameters of that solution. Moreover, the `GA`'s`CurrentBest` field contains the best solution and parameters obtained by the current generation.
322322

benchmark_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import (
55
"testing"
66
)
77

8-
func BenchmarkEnhance1Pop(b *testing.B) {
8+
func BenchmarkEvolve1Pop(b *testing.B) {
99
ga = GA{
1010
NewGenome: NewVector,
1111
NPops: 1,
@@ -19,11 +19,11 @@ func BenchmarkEnhance1Pop(b *testing.B) {
1919
}
2020
ga.Initialize()
2121
for i := 0; i < b.N; i++ {
22-
ga.Enhance()
22+
ga.Evolve()
2323
}
2424
}
2525

26-
func BenchmarkEnhance2Pops(b *testing.B) {
26+
func BenchmarkEvolve2Pops(b *testing.B) {
2727
runtime.GOMAXPROCS(runtime.NumCPU())
2828
ga = GA{
2929
NewGenome: NewVector,
@@ -38,11 +38,11 @@ func BenchmarkEnhance2Pops(b *testing.B) {
3838
}
3939
ga.Initialize()
4040
for i := 0; i < b.N; i++ {
41-
ga.Enhance()
41+
ga.Evolve()
4242
}
4343
}
4444

45-
func BenchmarkEnhance3Pops(b *testing.B) {
45+
func BenchmarkEvolve3Pops(b *testing.B) {
4646
ga = GA{
4747
NewGenome: NewVector,
4848
NPops: 3,
@@ -56,10 +56,10 @@ func BenchmarkEnhance3Pops(b *testing.B) {
5656
}
5757
ga.Initialize()
5858
for i := 0; i < b.N; i++ {
59-
ga.Enhance()
59+
ga.Evolve()
6060
}
6161
}
62-
func BenchmarkEnhance4Pops(b *testing.B) {
62+
func BenchmarkEvolve4Pops(b *testing.B) {
6363
ga = GA{
6464
NewGenome: NewVector,
6565
NPops: 4,
@@ -73,6 +73,6 @@ func BenchmarkEnhance4Pops(b *testing.B) {
7373
}
7474
ga.Initialize()
7575
for i := 0; i < b.N; i++ {
76-
ga.Enhance()
76+
ga.Evolve()
7777
}
7878
}

ga.go

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ import (
77
"math/rand"
88
"sort"
99
"time"
10-
11-
"golang.org/x/sync/errgroup"
1210
)
1311

1412
// A GA contains population which themselves contain individuals.
@@ -104,7 +102,7 @@ func (ga GA) Initialized() bool {
104102
}
105103

106104
// Initialize each population in the GA and assign an initial fitness to each
107-
// individual in each population. Running Initialize after running Enhance will
105+
// individual in each population. Running Initialize after running Evolve will
108106
// reset the GA entirely.
109107
func (ga *GA) Initialize() {
110108
// Check the NBest field
@@ -142,10 +140,10 @@ func (ga *GA) Initialize() {
142140
}
143141
}
144142

145-
// Enhance each population in the GA. The population level operations are done
143+
// Evolve each population in the GA. The population level operations are done
146144
// in parallel with a wait group. After all the population operations have been
147145
// run, the GA level operations are run.
148-
func (ga *GA) Enhance() error {
146+
func (ga *GA) Evolve() error {
149147
var start = time.Now()
150148
ga.Generations++
151149
// Check the GA has been initialized
@@ -158,39 +156,39 @@ func (ga *GA) Enhance() error {
158156
if len(ga.Populations) > 1 && ga.Migrator != nil && ga.Generations%ga.MigFrequency == 0 {
159157
ga.Migrator.Apply(ga.Populations, ga.RNG)
160158
}
161-
var g errgroup.Group
162-
for i := range ga.Populations {
163-
i := i // https://golang.org/doc/faq#closures_and_goroutines
164-
g.Go(func() error {
165-
var err error
166-
// Apply speciation if a positive number of species has been specified
167-
if ga.Speciator != nil {
168-
err = ga.Populations[i].speciateEvolveMerge(ga.Speciator, ga.Model)
169-
if err != nil {
170-
return err
171-
}
172-
} else {
173-
// Else apply the evolution model to the entire population
174-
err = ga.Model.Apply(&ga.Populations[i])
175-
if err != nil {
176-
return err
177-
}
159+
160+
var f = func(pop *Population) error {
161+
var err error
162+
// Apply speciation if a positive number of species has been specified
163+
if ga.Speciator != nil {
164+
err = pop.speciateEvolveMerge(ga.Speciator, ga.Model)
165+
if err != nil {
166+
return err
178167
}
179-
// Evaluate and sort
180-
ga.Populations[i].Individuals.Evaluate(ga.ParallelEval)
181-
ga.Populations[i].Individuals.SortByFitness()
182-
ga.Populations[i].Age += time.Since(start)
183-
ga.Populations[i].Generations++
184-
// Log current statistics if a logger has been provided
185-
if ga.Logger != nil {
186-
ga.Populations[i].Log(ga.Logger)
168+
} else {
169+
// Else apply the evolution model to the entire population
170+
err = ga.Model.Apply(pop)
171+
if err != nil {
172+
return err
187173
}
188-
return err
189-
})
174+
}
175+
// Evaluate and sort
176+
pop.Individuals.Evaluate(ga.ParallelEval)
177+
pop.Individuals.SortByFitness()
178+
pop.Age += time.Since(start)
179+
pop.Generations++
180+
// Log current statistics if a logger has been provided
181+
if ga.Logger != nil {
182+
pop.Log(ga.Logger)
183+
}
184+
return err
190185
}
191-
if err := g.Wait(); err != nil {
186+
187+
var err = ga.Populations.Apply(f, true)
188+
if err != nil {
192189
return err
193190
}
191+
194192
// Update HallOfFame
195193
for _, pop := range ga.Populations {
196194
updateHallOfFame(ga.HallOfFame, pop.Individuals)

ga_test.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func TestValidationSpeciator(t *testing.T) {
104104
func TestApplyWithSpeciator(t *testing.T) {
105105
var speciator = ga.Speciator
106106
ga.Speciator = SpecFitnessInterval{4}
107-
if ga.Enhance() != nil {
107+
if ga.Evolve() != nil {
108108
t.Error("Calling Apply with a valid Speciator should not return an error")
109109
}
110110
ga.Speciator = speciator
@@ -280,36 +280,36 @@ func TestCallback(t *testing.T) {
280280
if counter != 1 {
281281
t.Error("Counter was not incremented by the callback at initialization")
282282
}
283-
ga.Enhance()
283+
ga.Evolve()
284284
if counter != 2 {
285285
t.Error("Counter was not incremented by the callback at enhancement")
286286
}
287287
}
288288

289-
func TestGAEnhanceModelRuntimeError(t *testing.T) {
289+
func TestGAEvolveModelRuntimeError(t *testing.T) {
290290
var model = ga.Model
291291
ga.Model = ModRuntimeError{}
292292
// Check invalid model doesn't raise error
293293
if ga.Validate() != nil {
294294
t.Errorf("Expected nil, got %s", ga.Validate())
295295
}
296-
// Enhance
297-
var err = ga.Enhance()
296+
// Evolve
297+
var err = ga.Evolve()
298298
if err == nil {
299299
t.Error("An error should have been raised")
300300
}
301301
ga.Model = model
302302
}
303303

304-
func TestGAEnhanceSpeciatorRuntimeError(t *testing.T) {
304+
func TestGAEvolveSpeciatorRuntimeError(t *testing.T) {
305305
var speciator = ga.Speciator
306306
ga.Speciator = SpecRuntimeError{}
307307
// Check invalid speciator doesn't raise error
308308
if ga.Validate() != nil {
309309
t.Errorf("Expected nil, got %s", ga.Validate())
310310
}
311-
// Enhance
312-
var err = ga.Enhance()
311+
// Evolve
312+
var err = ga.Evolve()
313313
if err == nil {
314314
t.Error("An error should have been raised")
315315
}
@@ -347,13 +347,13 @@ func TestGAConsistentResults(t *testing.T) {
347347
// Run the first GA
348348
ga1.Initialize()
349349
for i := 0; i < 20; i++ {
350-
ga1.Enhance()
350+
ga1.Evolve()
351351
}
352352

353353
// Run the second GA
354354
ga2.Initialize()
355355
for i := 0; i < 20; i++ {
356-
ga2.Enhance()
356+
ga2.Evolve()
357357
}
358358

359359
// Compare best individuals

individuals.go

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -41,31 +41,40 @@ func newIndividuals(n int, newGenome NewGenome, rng *rand.Rand) Individuals {
4141
return indis
4242
}
4343

44-
// Evaluate each individual. If parallel is true then each Individual will be
44+
// Apply a function to a slice of Individuals.
45+
func (indis Individuals) Apply(f func(indi *Individual) error, parallel bool) error {
46+
if parallel {
47+
var g errgroup.Group
48+
for i := range indis {
49+
i := i // https://golang.org/doc/faq#closures_and_goroutines
50+
g.Go(func() error {
51+
return f(&indis[i])
52+
})
53+
}
54+
return g.Wait()
55+
}
56+
var err error
57+
for i := range indis {
58+
err = f(&indis[i])
59+
if err != nil {
60+
return err
61+
}
62+
}
63+
return err
64+
}
65+
66+
// Evaluate each Individual. If parallel is true then each Individual will be
4567
// evaluated in parallel thanks to the golang.org/x/sync/errgroup package. If
4668
// not then a simple sequential loop will be used. Evaluating in parallel is
4769
// only recommended for cases where evaluating an Individual takes a "long"
4870
// time. Indeed there won't necessarily be a speed-up when evaluating in
4971
// parallel. In fact performance can be degraded if evaluating an Individual is
5072
// too cheap.
5173
func (indis Individuals) Evaluate(parallel bool) {
52-
// Evaluate sequentially
53-
if !parallel {
54-
for i := range indis {
55-
indis[i].Evaluate()
56-
}
57-
return
58-
}
59-
// Evaluate in parallel
60-
var g errgroup.Group
61-
for i := range indis {
62-
i := i // https://golang.org/doc/faq#closures_and_goroutines
63-
g.Go(func() error {
64-
indis[i].Evaluate()
65-
return nil
66-
})
67-
}
68-
g.Wait()
74+
indis.Apply(
75+
func(indi *Individual) error { indi.Evaluate(); return nil },
76+
parallel,
77+
)
6978
}
7079

7180
// Mutate each individual.

population.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import (
44
"log"
55
"math/rand"
66
"time"
7+
8+
"golang.org/x/sync/errgroup"
79
)
810

911
// A Population contains individuals. Individuals mate within a population.
@@ -44,3 +46,25 @@ func (pop Population) Log(logger *log.Logger) {
4446

4547
// Populations type is necessary for migration and speciation purposes.
4648
type Populations []Population
49+
50+
// Apply a function to a slice of Populations.
51+
func (pops Populations) Apply(f func(pop *Population) error, parallel bool) error {
52+
if parallel {
53+
var g errgroup.Group
54+
for i := range pops {
55+
i := i // https://golang.org/doc/faq#closures_and_goroutines
56+
g.Go(func() error {
57+
return f(&pops[i])
58+
})
59+
}
60+
return g.Wait()
61+
}
62+
var err error
63+
for i := range pops {
64+
err = f(&pops[i])
65+
if err != nil {
66+
return err
67+
}
68+
}
69+
return err
70+
}

setup_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func newRand() *rand.Rand {
3535
func init() {
3636
ga.Initialize()
3737
for i := 0; i < nbrGenerations; i++ {
38-
ga.Enhance()
38+
ga.Evolve()
3939
}
4040
}
4141

0 commit comments

Comments
 (0)