-
-
Notifications
You must be signed in to change notification settings - Fork 7
/
maze_obj.go
174 lines (152 loc) · 5.94 KB
/
maze_obj.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
package maze
import (
"github.com/yaricom/goNEAT/experiments"
"github.com/yaricom/goNEAT/neat/genetics"
"github.com/yaricom/goNEAT/neat"
"fmt"
"github.com/yaricom/goNEAT_NS/neatns"
"os"
)
// The maze solving experiment with objective fitness-based optimization of NEAT algorithm
type MazeObjectiveEvaluator struct {
// The output path to store execution results
OutputPath string
// The maze seed environment
Environment *Environment
// The target number of species to be maintained
NumSpeciesTarget int
// The species compatibility threshold adjustment frequency
CompatAdjustFreq int
}
// Invoked before new trial run started
func (ev MazeObjectiveEvaluator) TrialRunStarted(trial *experiments.Trial) {
trialSim = mazeSimResults{
trialID : trial.Id,
records : new(RecordStore),
archive : neatns.NewNoveltyArchive(archive_thresh, noveltyMetric),
}
}
// This method evaluates one epoch for given population and prints results into output directory if any.
func (ev MazeObjectiveEvaluator) GenerationEvaluate(pop *genetics.Population, epoch *experiments.Generation, context *neat.NeatContext) (err error) {
// Evaluate each organism on a test
for _, org := range pop.Organisms {
res, err := ev.orgEvaluate(org, pop, epoch)
if err != nil {
return err
}
if res && (epoch.Best == nil || org.Fitness > epoch.Best.Fitness) {
epoch.Solved = true
epoch.WinnerNodes = len(org.Genotype.Nodes)
epoch.WinnerGenes = org.Genotype.Extrons()
epoch.WinnerEvals = trialSim.individCounter
epoch.Best = org
}
}
// Fill statistics about current epoch
epoch.FillPopulationStatistics(pop)
// Only print to file every print_every generations
if epoch.Solved || epoch.Id % context.PrintEvery == 0 || epoch.Id == context.NumGenerations - 1 {
pop_path := fmt.Sprintf("%s/gen_%d", experiments.OutDirForTrial(ev.OutputPath, trialSim.trialID), epoch.Id)
file, err := os.Create(pop_path)
if err != nil {
neat.ErrorLog(fmt.Sprintf("Failed to dump population, reason: %s\n", err))
} else {
pop.WriteBySpecies(file)
}
}
if epoch.Solved {
// print winner organism
for _, org := range pop.Organisms {
if org.IsWinner {
// Prints the winner organism to file!
org_path := fmt.Sprintf("%s/%s_%d-%d", experiments.OutDirForTrial(ev.OutputPath, trialSim.trialID),
"maze_obj_winner", org.Phenotype.NodeCount(), org.Phenotype.LinkCount())
file, err := os.Create(org_path)
if err != nil {
neat.ErrorLog(fmt.Sprintf("Failed to dump winner organism genome, reason: %s\n", err))
} else {
org.Genotype.Write(file)
neat.InfoLog(fmt.Sprintf("Generation #%d winner dumped to: %s\n", epoch.Id, org_path))
}
break
}
}
// store recorded data points and novelty archive
ev.storeRecorded()
} else if epoch.Id == context.NumGenerations - 1 {
// the last epoch executed
ev.storeRecorded()
} else {
speciesCount := len(pop.Species)
// adjust species count by keeping it constant
if epoch.Id % ev.CompatAdjustFreq == 0 {
if speciesCount < ev.NumSpeciesTarget {
context.CompatThreshold -= 0.1
} else if speciesCount > ev.NumSpeciesTarget {
context.CompatThreshold += 0.1
}
// to avoid dropping too low
if context.CompatThreshold < 0.3 {
context.CompatThreshold = 0.3
}
}
neat.InfoLog(fmt.Sprintf("%d species -> %d organisms [compatibility threshold: %.1f, target: %d]\n",
speciesCount, len(pop.Organisms), context.CompatThreshold, ev.NumSpeciesTarget))
}
return err
}
func (ev *MazeObjectiveEvaluator) storeRecorded() {
// store recorded agents' performance
rec_path := fmt.Sprintf("%s/record.dat", experiments.OutDirForTrial(ev.OutputPath, trialSim.trialID))
rec_file, err := os.Create(rec_path)
if err == nil {
err = trialSim.records.Write(rec_file)
}
if err != nil {
neat.ErrorLog(fmt.Sprintf("Failed to store agents' data records, reason: %s\n", err))
}
// print novelty points with maximal fitness
np_path := fmt.Sprintf("%s/fittest_archive_points.txt", experiments.OutDirForTrial(ev.OutputPath, trialSim.trialID))
np_file, err := os.Create(np_path)
if err == nil {
err = trialSim.archive.PrintFittest(np_file)
}
if err != nil {
neat.ErrorLog(fmt.Sprintf("Failed to print fittest points from archive, reason: %s\n", err))
}
}
// Evaluates individual organism against maze environment and returns true if organism was able to solve maze by navigating to exit
func (ev *MazeObjectiveEvaluator) orgEvaluate(org *genetics.Organism, pop *genetics.Population, epoch *experiments.Generation) (bool, error) {
// create record to store simulation results for organism
record := AgentRecord{Generation:epoch.Id, AgentID:trialSim.individCounter}
record.SpeciesID = org.Species.Id
record.SpeciesAge = org.Species.Age
// evaluate individual organism and get novelty point holding simulation results
n_item, solved, err := mazeSimulationEvaluate(ev.Environment, org, &record, nil)
if err != nil {
return false, err
}
n_item.IndividualID = org.Genotype.Id
// assign organism fitness based on simulation results - the normalized distance between agent and maze exit
org.Fitness = n_item.Fitness
org.IsWinner = solved // store if maze was solved
org.Error = 1 - n_item.Fitness // error value consider how far we are from exit normalized to (0;1] range
if solved {
// run simulation to store solver path
pathPoints := make([]Point, ev.Environment.TimeSteps)
_, _, err := mazeSimulationEvaluate(ev.Environment, org, &record, pathPoints)
if err != nil {
neat.ErrorLog("Solver's path simulation failed\n")
return false, err
}
trialSim.records.SolverPathPoints = pathPoints
}
// add record
trialSim.records.Records = append(trialSim.records.Records, record)
// increment tested unique individuals counter
trialSim.individCounter++
// update fittest organisms list - needed for debugging output
org.Data = &genetics.OrganismData{Value:n_item} // store novelty item within organism data to avoid errors next
trialSim.archive.UpdateFittestWithOrganism(org)
return solved, nil
}