github.com/yaricom/goNEAT@v0.0.0-20210507221059-e2110b885482/neat/genetics/population_epoch.go (about) 1 package genetics 2 3 import ( 4 "github.com/yaricom/goNEAT/neat" 5 "sort" 6 "fmt" 7 "errors" 8 "encoding/gob" 9 "bytes" 10 "sync" 11 ) 12 13 // The epoch executor type definition 14 type EpochExecutorType int 15 const ( 16 // The sequential executor 17 SequentialExecutorType EpochExecutorType = 0 18 // The parallel executor to perform reproduction cycle in parallel threads 19 ParallelExecutorType = 1 20 ) 21 22 // Executes epoch's turnover for population of organisms 23 type PopulationEpochExecutor interface { 24 // Turnover the population to a new generation 25 NextEpoch(generation int, population *Population, context *neat.NeatContext) error 26 } 27 28 // The epoch executor which will run execution sequentially in single thread for all species and organisms 29 type SequentialPopulationEpochExecutor struct { 30 sorted_species []*Species 31 best_species_reproduced bool 32 best_species_id int 33 } 34 35 func (ex *SequentialPopulationEpochExecutor) NextEpoch(generation int, population *Population, context *neat.NeatContext) error { 36 err := ex.prepare(generation, population, context) 37 if err != nil { 38 return err 39 } 40 err = ex.reproduce(generation, population, context) 41 if err != nil { 42 return err 43 } 44 err = ex.finalize(generation, population, context) 45 46 neat.DebugLog(fmt.Sprintf("POPULATION: >>>>> Epoch %d complete\n", generation)) 47 48 return err 49 } 50 51 func (ex *SequentialPopulationEpochExecutor) prepare(generation int, p *Population, context *neat.NeatContext) error { 52 // clear executor state from previous run 53 ex.sorted_species = nil 54 55 // Use Species' ages to modify the objective fitness of organisms in other words, make it more fair for younger 56 // species so they have a chance to take hold and also penalize stagnant species. Then adjust the fitness using 57 // the species size to "share" fitness within a species. Then, within each Species, mark for death those below 58 // survival_thresh * average 59 for _, sp := range p.Species { 60 sp.adjustFitness(context) 61 } 62 63 // find and remove species unable to produce offspring due to fitness stagnation 64 p.purgeZeroOffspringSpecies(generation) 65 66 // Stick the Species pointers into a new Species list for sorting 67 ex.sorted_species = make([]*Species, len(p.Species)) 68 copy(ex.sorted_species, p.Species) 69 70 // Sort the Species by max original fitness of its first organism 71 sort.Sort(sort.Reverse(byOrganismOrigFitness(ex.sorted_species))) 72 73 // Used in debugging to see why (if) best species dies 74 ex.best_species_id = ex.sorted_species[0].Id 75 76 if neat.LogLevel == neat.LogLevelDebug { 77 neat.DebugLog("POPULATION: >> Sorted Species START <<") 78 for _, sp := range ex.sorted_species { 79 // Print out for Debugging/viewing what's going on 80 neat.DebugLog( 81 fmt.Sprintf("POPULATION: >> Orig. fitness of Species %d (Size %d): %f, current fitness: %f, expected offspring: %d, last improved %d \n", 82 sp.Id, len(sp.Organisms), sp.Organisms[0].originalFitness, sp.Organisms[0].Fitness, sp.ExpectedOffspring, (sp.Age - sp.AgeOfLastImprovement))) 83 } 84 neat.DebugLog("POPULATION: >> Sorted Species END <<\n") 85 86 } 87 88 // Check for Population-level stagnation 89 curr_species := ex.sorted_species[0] 90 curr_species.Organisms[0].isPopulationChampion = true // DEBUG marker of the best of pop 91 if curr_species.Organisms[0].originalFitness > p.HighestFitness { 92 p.HighestFitness = curr_species.Organisms[0].originalFitness 93 p.EpochsHighestLastChanged = 0 94 neat.DebugLog(fmt.Sprintf("POPULATION: NEW POPULATION RECORD FITNESS: %f of SPECIES with ID: %d\n", p.HighestFitness, ex.best_species_id)) 95 96 } else { 97 p.EpochsHighestLastChanged += 1 98 neat.DebugLog(fmt.Sprintf(" generations since last population fitness record: %f\n", p.HighestFitness)) 99 } 100 101 // Check for stagnation - if there is stagnation, perform delta-coding 102 if p.EpochsHighestLastChanged >= context.DropOffAge + 5 { 103 // Population stagnated - trying to fix it by delta coding 104 p.deltaCoding(ex.sorted_species, context) 105 } else if context.BabiesStolen > 0 { 106 // STOLEN BABIES: The system can take expected offspring away from worse species and give them 107 // to superior species depending on the system parameter BabiesStolen (when BabiesStolen > 0) 108 p.giveBabiesToTheBest(ex.sorted_species, context) 109 } 110 111 // Kill off all Organisms marked for death. The remainder will be allowed to reproduce. 112 err := p.purgeOrganisms() 113 return err 114 } 115 116 // Do sequential reproduction cycle 117 func (ex *SequentialPopulationEpochExecutor) reproduce(generation int, p *Population, context *neat.NeatContext) error { 118 neat.DebugLog("POPULATION: Start Sequential Reproduction Cycle >>>>>") 119 120 // Perform reproduction. Reproduction is done on a per-Species basis 121 babies := make([]*Organism, 0) 122 123 for _, sp := range p.Species { 124 rep_babies, err := sp.reproduce(generation, p, ex.sorted_species, context) 125 if err != nil { 126 return err 127 } 128 if sp.Id == ex.best_species_id { 129 // store flag if best species reproduced - it will be used to determine if best species 130 // produced offspring before died 131 ex.best_species_reproduced = true 132 } 133 134 // store babies 135 babies = append(babies, rep_babies...) 136 } 137 138 // sanity check - make sure that population size keep the same 139 if len(babies) != context.PopSize { 140 return errors.New( 141 fmt.Sprintf("POPULATION: Progeny size after reproduction cycle dimished.\nExpected: [%d], but got: [%d]", 142 context.PopSize, len(babies))) 143 } 144 145 146 // speciate fresh progeny 147 err := p.speciate(babies, context) 148 149 neat.DebugLog("POPULATION: >>>>> Reproduction Complete") 150 151 return err 152 } 153 154 func (ex *SequentialPopulationEpochExecutor) finalize(generation int, p *Population, context *neat.NeatContext) error { 155 // Destroy and remove the old generation from the organisms and species 156 err := p.purgeOldGeneration(ex.best_species_id) 157 if err != nil { 158 return err 159 } 160 161 // Removes all empty Species and age ones that survive. 162 // As this happens, create master organism list for the new generation. 163 p.purgeOrAgeSpecies() 164 165 // Remove the innovations of the current generation 166 p.Innovations = make([]*Innovation, 0) 167 168 // Check to see if the best species died somehow. We don't want this to happen!!! 169 err = p.checkBestSpeciesAlive(ex.best_species_id, ex.best_species_reproduced) 170 171 // DEBUG: Checking the top organism's duplicate in the next gen 172 // This prints the champ's child to the screen 173 if neat.LogLevel == neat.LogLevelDebug && err != nil { 174 for _, curr_org := range p.Organisms { 175 if curr_org.isPopulationChampionChild { 176 neat.DebugLog( 177 fmt.Sprintf("POPULATION: At end of reproduction cycle, the child of the pop champ is: %s", 178 curr_org.Genotype)) 179 } 180 } 181 } 182 return err 183 } 184 185 // The population epoch executor with parallel reproduction cycle 186 type ParallelPopulationEpochExecutor struct { 187 sequential *SequentialPopulationEpochExecutor 188 } 189 190 func (ex *ParallelPopulationEpochExecutor) NextEpoch(generation int, population *Population, context *neat.NeatContext) error { 191 ex.sequential = &SequentialPopulationEpochExecutor{} 192 err := ex.sequential.prepare(generation, population, context) 193 if err != nil { 194 return err 195 } 196 197 // Do parallel reproduction 198 err = ex.reproduce(generation, population, context) 199 if err != nil { 200 return err 201 } 202 203 err = ex.sequential.finalize(generation, population, context) 204 205 neat.DebugLog(fmt.Sprintf("POPULATION: >>>>> Epoch %d complete\n", generation)) 206 207 return err 208 } 209 210 // Do parallel reproduction cycle 211 func (ex *ParallelPopulationEpochExecutor) reproduce(generation int, p *Population, context *neat.NeatContext) error { 212 neat.DebugLog("POPULATION: Start Parallel Reproduction Cycle >>>>>") 213 214 // Perform reproduction. Reproduction is done on a per-Species basis 215 sp_num := len(p.Species) 216 res_chan := make(chan reproductionResult, sp_num) 217 // The wait group to wait for all GO routines 218 var wg sync.WaitGroup 219 220 for _, curr_species := range p.Species { 221 wg.Add(1) 222 // run in separate GO thread 223 go func(sp *Species, generation int, p *Population, sorted_species []*Species, 224 context *neat.NeatContext, res_chan chan <- reproductionResult, wg *sync.WaitGroup) { 225 226 babies, err := sp.reproduce(generation, p, sorted_species, context) 227 res := reproductionResult{} 228 if err == nil { 229 res.species_id = sp.Id 230 231 // fill babies into result 232 var buf bytes.Buffer 233 enc := gob.NewEncoder(&buf) 234 for _, baby := range babies { 235 err = enc.Encode(baby) 236 if err != nil { 237 break 238 } 239 } 240 if err == nil { 241 res.babies = buf.Bytes() 242 res.babies_stored = len(babies) 243 } 244 } 245 res.err = err 246 247 // write result to channel and signal to wait group 248 res_chan <- res 249 wg.Done() 250 251 }(curr_species, generation, p, ex.sequential.sorted_species, context, res_chan, &wg) 252 } 253 254 // wait for reproduction results 255 wg.Wait() 256 close(res_chan) 257 258 // read reproduction results, instantiate progeny and speciate over population 259 babies := make([]*Organism, 0) 260 for result := range res_chan { 261 if result.err != nil { 262 return result.err 263 } 264 // read baby genome 265 dec := gob.NewDecoder(bytes.NewBuffer(result.babies)) 266 for i := 0; i < result.babies_stored; i++ { 267 org := Organism{} 268 err := dec.Decode(&org) 269 if err != nil { 270 return errors.New( 271 fmt.Sprintf("POPULATION: Failed to decode baby organism, reason: %s", err)) 272 } 273 babies = append(babies, &org) 274 } 275 if result.species_id == ex.sequential.best_species_id { 276 // store flag if best species reproduced - it will be used to determine if best species 277 // produced offspring before died 278 ex.sequential.best_species_reproduced = (babies != nil) 279 } 280 } 281 282 // sanity check - make sure that population size keep the same 283 if len(babies) != context.PopSize { 284 return errors.New( 285 fmt.Sprintf("POPULATION: Progeny size after reproduction cycle dimished.\nExpected: [%d], but got: [%d]", 286 context.PopSize, len(babies))) 287 } 288 289 290 // speciate fresh progeny 291 err := p.speciate(babies, context) 292 293 neat.DebugLog("POPULATION: >>>>> Reproduction Complete") 294 295 return err 296 }