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