forked from yaricom/goNEAT
-
Notifications
You must be signed in to change notification settings - Fork 0
/
common.go
152 lines (131 loc) · 4.65 KB
/
common.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
// The experiments package holds various experiments with NEAT.
package experiments
import (
"github.com/elmware/goNEAT/neat/genetics"
"github.com/elmware/goNEAT/neat"
"fmt"
"time"
"os"
"log"
"errors"
)
// The type of action to be applied to environment
type ActionType byte
// The supported action types
const (
// The continuous action type meaning continuous values to be applied to environment
ContinuousAction ActionType = iota
// The discrete action assumes that there are only discrete values of action (e.g. 0, 1)
DiscreteAction
)
// The interface describing evaluator for one generation of evolution.
type GenerationEvaluator interface {
// Invoked to evaluate one generation of population of organisms within given
// execution context.
GenerationEvaluate(pop *genetics.Population, epoch *Generation, context *neat.NeatContext) (err error)
}
// The interface to describe trial lifecycle observer interested to receive lifecycle notifications
type TrialRunObserver interface {
// Invoked to notify that new trial run just started before any epoch evaluation in that trial run
TrialRunStarted(trial *Trial)
}
// Returns appropriate executor type from given context
func epochExecutorForContext(context *neat.NeatContext) (genetics.PopulationEpochExecutor, error) {
switch genetics.EpochExecutorType(context.EpochExecutorType) {
case genetics.SequentialExecutorType:
return &genetics.SequentialPopulationEpochExecutor{}, nil
case genetics.ParallelExecutorType:
return &genetics.ParallelPopulationEpochExecutor{}, nil
default:
return nil, errors.New("Unsupported epoch executor type requested")
}
}
// The Experiment execution entry point
func (ex *Experiment) Execute(context *neat.NeatContext, start_genome *genetics.Genome, executor interface{}) (err error) {
if ex.Trials == nil {
ex.Trials = make(Trials, context.NumRuns)
}
var pop *genetics.Population
for run := 0; run < context.NumRuns; run++ {
trial_start_time := time.Now()
neat.InfoLog("\n>>>>> Spawning new population ")
pop, err = genetics.NewPopulation(start_genome, context)
if err != nil {
neat.InfoLog("Failed to spawn new population from start genome")
return err
} else {
neat.InfoLog("OK <<<<<")
}
neat.InfoLog(">>>>> Verifying spawned population ")
_, err = pop.Verify()
if err != nil {
neat.ErrorLog("\n!!!!! Population verification failed !!!!!")
return err
} else {
neat.InfoLog("OK <<<<<")
}
// create appropriate population's epoch executor
epoch_executor, err := epochExecutorForContext(context)
if err != nil {
return err
}
// start new trial
trial := Trial {
Id:run,
}
if trial_observer, ok := executor.(TrialRunObserver); ok {
trial_observer.TrialRunStarted(&trial) // optional
}
generation_evaluator := executor.(GenerationEvaluator) // mandatory
for generation_id := 0; generation_id < context.NumGenerations; generation_id++ {
neat.InfoLog(fmt.Sprintf(">>>>> Generation:%3d\tRun: %d\n", generation_id, run))
generation := Generation{
Id:generation_id,
TrialId:run,
}
gen_start_time := time.Now()
err = generation_evaluator.GenerationEvaluate(pop, &generation, context)
if err != nil {
neat.InfoLog(fmt.Sprintf("!!!!! Generation [%d] evaluation failed !!!!!\n", generation_id))
return err
}
generation.Executed = time.Now()
// Turnover population of organisms to the next epoch if appropriate
if !generation.Solved {
neat.DebugLog(">>>>> start next generation")
err = epoch_executor.NextEpoch(generation_id, pop, context)
if err != nil {
neat.InfoLog(fmt.Sprintf("!!!!! Epoch execution failed in generation [%d] !!!!!\n", generation_id))
return err
}
}
// Set generation duration, which also includes preparation for the next epoch
generation.Duration = generation.Executed.Sub(gen_start_time)
trial.Generations = append(trial.Generations, generation)
if generation.Solved {
// stop further evaluation if already solved
neat.InfoLog(fmt.Sprintf(">>>>> The winner organism found in [%d] generation, fitness: %f <<<<<\n",
generation_id, generation.Best.Fitness))
break
}
}
// holds trial duration
trial.Duration = time.Now().Sub(trial_start_time)
// store trial into experiment
ex.Trials[run] = trial
}
return nil
}
// To provide standard output directory syntax based on current trial
// Method checks if directory should be created
func OutDirForTrial(outDir string, trialID int) string {
dir := fmt.Sprintf("%s/%d", outDir, trialID)
if _, err := os.Stat(dir); err != nil {
// create output dir
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
log.Fatal("Failed to create output directory: ", err)
}
}
return dir
}