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  }