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  }