Skip to content

Commit c8d8475

Browse files
committed
Pool operators Unit tests
1 parent 0d3cf13 commit c8d8475

File tree

12 files changed

+137
-20
lines changed

12 files changed

+137
-20
lines changed

‎mlp-ea-decentralized/common/ga/cluster.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (c *eventHandler) NotifyUpdate(node *memberlist.Node) {
133133
meta := NodeMetadata{}
134134
err := meta.Unmarshal(node.Meta)
135135
if err != nil {
136-
c.Logger.Errorf("Could not deserialize node metadata. $s", err.Error())
136+
c.Logger.Errorf("Could not deserialize node metadata. %s", err.Error())
137137
}
138138

139139
c.Logger.Infof("Node updated: %s:%d", node.Addr.String(), node.Port)

‎mlp-ea-decentralized/common/ga/poolModel.go‎

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import (
1313
"sync"
1414
"time"
1515

16-
"github.com/salvacorts/TFG-Parasitic-Metaheuristics/mlp-ea-decentralized/common/mlp"
1716
"github.com/sirupsen/logrus"
1817

1918
"github.com/dennwc/dom/net/ws"
@@ -27,6 +26,9 @@ var Log = logrus.New()
2726
// SortFunction gets an array of individuals and sorts them with a given precission
2827
type SortFunction = func(slice []eaopt.Individual, precission int) []eaopt.Individual
2928

29+
// GenomeGenerator is a function that creates genomes randomly initializaed
30+
type GenomeGenerator = func(*rand.Rand) eaopt.Genome
31+
3032
// PoolModel is a pool-based evolutionary algorithm
3133
// TODO: Wrap configuration into ConfigTypes (e.g. ClientConfig, IslandConfig, ModelConfig...)
3234
type PoolModel struct {
@@ -79,7 +81,9 @@ type PoolModel struct {
7981
}
8082

8183
// MakePool Creates a new pool with default configuration
82-
func MakePool(popSize int, grpcPort, clusterPort int, boostrapPeers []string, rnd *rand.Rand) *PoolModel {
84+
func MakePool(
85+
popSize int, grpcPort, clusterPort int, boostrapPeers []string,
86+
rnd *rand.Rand, generator GenomeGenerator) *PoolModel {
8387
pool := &PoolModel{
8488
Rnd: rnd,
8589
PopSize: popSize,
@@ -100,7 +104,7 @@ func MakePool(popSize int, grpcPort, clusterPort int, boostrapPeers []string, rn
100104
pool.cluster.Logger.SetLevel(logrus.DebugLevel)
101105

102106
for i := 0; i < pool.PopSize; i++ {
103-
indi := eaopt.NewIndividual(mlp.NewRandMLP(pool.Rnd), pool.Rnd)
107+
indi := eaopt.NewIndividual(generator(pool.Rnd), pool.Rnd)
104108
pool.population.Add(indi)
105109
}
106110

@@ -140,6 +144,10 @@ func (pool *PoolModel) Minimize() {
140144
for pool.evaluations < pool.MaxEvaluations {
141145
var offsprings []eaopt.Individual
142146

147+
// At least nOffsprings shold be available in the population
148+
// tu run this operator
149+
pool.popSemaphore.Acquire(4)
150+
143151
// take here randomly from population
144152
offsprings = pool.selection(4, 4)
145153
offsprings = pool.crossover(offsprings)
@@ -168,14 +176,10 @@ func (pool *PoolModel) Minimize() {
168176
}
169177
}
170178

171-
// Binary torunament
179+
// Selection selects nOffstrings on a tournamnet of nCandidates
172180
func (pool *PoolModel) selection(nOffstrings, nCandidates int) []eaopt.Individual {
173181
offsprings := make([]eaopt.Individual, nOffstrings)
174182

175-
// At least nOffsprings shold be available in the population
176-
// tu run this operator
177-
pool.popSemaphore.Acquire(nOffstrings)
178-
179183
for i := range offsprings {
180184
alreadySelected := make(map[string]int) // 0 means not selected
181185
candidates := make([]eaopt.Individual, nCandidates)
@@ -209,6 +213,7 @@ func (pool *PoolModel) selection(nOffstrings, nCandidates int) []eaopt.Individua
209213

210214
// TODO: Get rid of odd arrays here
211215
func (pool *PoolModel) crossover(in []eaopt.Individual) []eaopt.Individual {
216+
offsprings := make([]eaopt.Individual, len(in))
212217
for i := 0; i < len(in)-1; i += 2 {
213218
if pool.Rnd.Float64() < pool.CrossRate {
214219
o1, o2 := in[i].Clone(pool.Rnd), in[i+1].Clone(pool.Rnd)
@@ -218,12 +223,12 @@ func (pool *PoolModel) crossover(in []eaopt.Individual) []eaopt.Individual {
218223
o2.Evaluated = false
219224

220225
// Add keep best here? No, since you would have to evaluate the offsprings
221-
in[i] = o1
222-
in[i+1] = o2
226+
offsprings[i] = o1
227+
offsprings[i+1] = o2
223228
}
224229
}
225230

226-
return in
231+
return offsprings
227232
}
228233

229234
func (pool *PoolModel) mutate(in []eaopt.Individual) []eaopt.Individual {
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package ga
2+
3+
import (
4+
"math"
5+
"math/rand"
6+
"testing"
7+
8+
"github.com/salvacorts/TFG-Parasitic-Metaheuristics/mlp-ea-decentralized/common/mlp"
9+
)
10+
11+
func TestMakePool(t *testing.T) {
12+
size := 100
13+
14+
mlp.Config.FactoryCfg.MinHiddenNeurons = 2
15+
mlp.Config.FactoryCfg.MaxHiddenNeurons = 4
16+
pool := MakePool(size, 9999, 9998, []string{}, rand.New(rand.NewSource(7)), mlp.NewRandMLP)
17+
18+
if pool.GetTotalEvaluations() != 0 {
19+
t.Errorf("Evaluated individuals on initialization")
20+
}
21+
22+
if pool.BestSolution.Fitness != math.Inf(1) {
23+
t.Errorf("Best individual fitness is not +Inf")
24+
}
25+
26+
if pool.PopSize != size {
27+
t.Errorf("PopSize and size argument on costructor do noot match")
28+
}
29+
30+
if pool.population.Length() != size {
31+
t.Errorf("Population Length does not match the constructor size")
32+
}
33+
34+
if len(pool.GetPopulationSnapshot()) != pool.PopSize {
35+
t.Errorf("Population snapshot size is different form PopSize")
36+
}
37+
}
38+
39+
func TestSelection(t *testing.T) {
40+
mlp.Config.FactoryCfg.MinHiddenNeurons = 2
41+
mlp.Config.FactoryCfg.MaxHiddenNeurons = 4
42+
pool := MakePool(20, 9999, 9998, []string{}, rand.New(rand.NewSource(7)), mlp.NewRandMLP)
43+
pool.SortFunc = mlp.SortByFitnessAndNeurons
44+
45+
for i, in := range pool.GetPopulationSnapshot() {
46+
in.Fitness = float64(i)
47+
pool.population.Add(in)
48+
}
49+
50+
if pool.population.Length() != 20 {
51+
t.Errorf("Population Add operator do not overwrite existing individual")
52+
}
53+
54+
selected := pool.selection(4, 4)
55+
56+
scores := make([]float64, len(selected))
57+
for i, s := range selected {
58+
scores[i] = s.Fitness
59+
}
60+
61+
t.Logf("Selected individuals: %v", scores)
62+
if len(selected) != 4 {
63+
t.Errorf("Selection do not select the amount of offsprings specified")
64+
}
65+
66+
// If selection selected one of the 4 worst individuals it is not working
67+
// It can happen but it is very unlikely
68+
for _, s := range selected {
69+
for i := 20 - 4 - 1; i < 20; i++ {
70+
if s.Fitness == float64(i) {
71+
t.Log("[WARN] Selected one of the worst individuals. It is very unlikely")
72+
}
73+
}
74+
}
75+
76+
}
77+
78+
func TestCrossover(t *testing.T) {
79+
mlp.Config.FactoryCfg.MinHiddenNeurons = 2
80+
mlp.Config.FactoryCfg.MaxHiddenNeurons = 4
81+
pool := MakePool(20, 9999, 9998, []string{}, rand.New(rand.NewSource(7)), mlp.NewRandMLP)
82+
pool.SortFunc = mlp.SortByFitnessAndNeurons
83+
pool.CrossRate = 1
84+
85+
snap := pool.GetPopulationSnapshot()
86+
87+
offsprings := pool.crossover(snap[:4])
88+
89+
for i, o := range offsprings {
90+
if o == snap[i] {
91+
t.Errorf("Offspring is equal to parent")
92+
}
93+
}
94+
}
95+
96+
func TestGetAverageFitness(t *testing.T) {
97+
mlp.Config.FactoryCfg.MinHiddenNeurons = 2
98+
mlp.Config.FactoryCfg.MaxHiddenNeurons = 4
99+
pool := MakePool(20, 9999, 9998, []string{}, rand.New(rand.NewSource(7)), mlp.NewRandMLP)
100+
101+
sum := 0.0
102+
for i, in := range pool.GetPopulationSnapshot() {
103+
in.Fitness = float64(i)
104+
pool.population.Add(in)
105+
106+
sum += in.Fitness
107+
}
108+
109+
avg := pool.GetAverageFitness()
110+
if avg != sum/20 {
111+
t.Errorf("Average fitness is not correct. Expected %f, got %f", sum/20, avg)
112+
}
113+
114+
}

‎mlp-ea-decentralized/common/ga/tests/population_test.go‎ renamed to ‎mlp-ea-decentralized/common/ga/population_test.go‎

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,18 @@ import (
44
"testing"
55

66
"github.com/salvacorts/eaopt"
7-
8-
"github.com/salvacorts/TFG-Parasitic-Metaheuristics/mlp-ea-decentralized/common/ga"
97
)
108

119
func TestMake(t *testing.T) {
12-
population := ga.MakePopulation()
10+
population := MakePopulation()
1311

1412
if population.Length() > 0 {
1513
t.Errorf("Population is not empty at initialization")
1614
}
1715
}
1816

1917
func TestAdd(t *testing.T) {
20-
population := ga.MakePopulation()
18+
population := MakePopulation()
2119

2220
indiv := eaopt.Individual{
2321
ID: "A",
@@ -60,7 +58,7 @@ func TestAdd(t *testing.T) {
6058
}
6159

6260
func TestRemove(t *testing.T) {
63-
population := ga.MakePopulation()
61+
population := MakePopulation()
6462

6563
indiv := eaopt.Individual{
6664
ID: "A",
@@ -85,7 +83,7 @@ func TestRemove(t *testing.T) {
8583
}
8684

8785
func TestFold(t *testing.T) {
88-
population := ga.MakePopulation()
86+
population := MakePopulation()
8987

9088
indiv1 := eaopt.Individual{
9189
ID: "A",

‎mlp-ea-decentralized/common/ga/tests/poolOperators_test.go‎

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)