github.com/RH12503/Triangula@v1.2.0/algorithm/simple.go (about)

     1  package algorithm
     2  
     3  import (
     4  	"github.com/RH12503/Triangula/algorithm/evaluator"
     5  	"github.com/RH12503/Triangula/fitness"
     6  	"github.com/RH12503/Triangula/mutation"
     7  	"github.com/RH12503/Triangula/normgeom"
     8  	"github.com/panjf2000/ants/v2"
     9  	"sort"
    10  	"time"
    11  )
    12  
    13  // An simple is a simplified genetic algorithm.
    14  // It runs in parallel, scaling well across multiple cores.
    15  // For most cases, a modifiedGenetic algorithm will be more effective.
    16  //
    17  // Firstly, a new generation is chosen based on fitness.
    18  // Secondly, the fitnesses of the new generation are calculated.
    19  // Lastly, the fitnesses are sorted in preparation for the next generation.
    20  type simple struct {
    21  	evaluator evaluator.Evaluator // Used to calculate fitnesses.
    22  	mutator   mutation.Method     // Used in newGeneration to mutate members of the population.
    23  
    24  	population    []normgeom.NormPointGroup // The population of the algorithm.
    25  	newPopulation []normgeom.NormPointGroup // Used in newGeneration.
    26  
    27  	fitnesses []FitnessData // fitnesses[i] is the fitness of population[i].
    28  
    29  	mutations [][]mutation.Mutation // Stores the mutations made in newGeneration.
    30  
    31  	best normgeom.NormPointGroup // The member of the population with the highest fitness.
    32  
    33  	cutoff int // The number of members that survive to the next generation.
    34  
    35  	stats Stats // Simple statistics relating to the algorithm.
    36  }
    37  
    38  func (s *simple) Step() {
    39  	t := time.Now() // Measure the time taken for the generation
    40  
    41  	// Fill the population with new members
    42  	s.newGeneration()
    43  
    44  	// Calculate and update the fitnesses of the new population
    45  
    46  	s.calculateFitnesses()
    47  	s.updateFitnesses()
    48  
    49  	s.stats.Generation++
    50  	s.stats.TimeForGen = time.Since(t)
    51  }
    52  
    53  // calculateFitnesses calculates the fitnesses of the current generation.
    54  func (s *simple) calculateFitnesses() {
    55  	ch := make(chan FitnessData, len(s.population))
    56  
    57  	for i, p := range s.population {
    58  		i := i
    59  		p := p
    60  		e := s.evaluator.Get(i)
    61  		ants.Submit(
    62  			func() {
    63  				fit := e.Calculate(fitness.PointsData{
    64  					Points:    p,
    65  					Mutations: s.mutations[i],
    66  				})
    67  				ch <- FitnessData{
    68  					I:       i,
    69  					Fitness: fit,
    70  				}
    71  			},
    72  		)
    73  		s.fitnesses[i].I = i
    74  	}
    75  
    76  	s.evaluator.Prepare()
    77  
    78  	done := 0
    79  	for d := range ch {
    80  		s.fitnesses[d.I].Fitness = d.Fitness
    81  		s.evaluator.Update(d.I)
    82  
    83  		done++
    84  		if done == len(s.population) {
    85  			close(ch)
    86  		}
    87  	}
    88  }
    89  
    90  // updateFitnesses prepares the members with calculated fitnesses for the next generation.
    91  func (s *simple) updateFitnesses() {
    92  	sort.Sort(s)
    93  
    94  	s.best.Set(s.population[0])
    95  	s.stats.BestFitness = s.fitnesses[0].Fitness
    96  }
    97  
    98  // newGeneration populates a generation with new members.
    99  func (s *simple) newGeneration() {
   100  	i := 0
   101  
   102  	for ; i < s.cutoff; i++ {
   103  		s.newPopulation[i].Set(s.population[i])
   104  		s.mutations[i] = s.mutations[i][:0]
   105  	}
   106  
   107  	for i < len(s.population) {
   108  		for j := 0; j < s.cutoff && i < len(s.population); j++ {
   109  			s.mutations[i] = s.mutations[i][:0] // clear all previous mutations
   110  			s.newPopulation[i].Set(s.population[j])
   111  
   112  			s.evaluator.SetBase(i, j)
   113  			s.mutator.Mutate(s.newPopulation[i], func(mut mutation.Mutation) {
   114  				s.mutations[i] = append(s.mutations[i], mut)
   115  			})
   116  			i++
   117  		}
   118  	}
   119  
   120  	s.population, s.newPopulation = s.newPopulation, s.population
   121  }
   122  
   123  func (s simple) Best() normgeom.NormPointGroup {
   124  	return s.best
   125  }
   126  
   127  func (s simple) Stats() Stats {
   128  	return s.stats
   129  }
   130  
   131  func (s simple) Len() int {
   132  	return len(s.fitnesses)
   133  }
   134  
   135  func (s simple) Less(i, j int) bool {
   136  	return s.fitnesses[i].Fitness > s.fitnesses[j].Fitness
   137  }
   138  
   139  func (s *simple) Swap(i, j int) {
   140  	s.fitnesses[i], s.fitnesses[j] = s.fitnesses[j], s.fitnesses[i]
   141  	s.population[i], s.population[j] = s.population[j], s.population[i]
   142  	s.evaluator.Swap(i, j)
   143  }
   144  
   145  // NewSimple returns a new Simple algorithm.
   146  func NewSimple(newPointGroup func() normgeom.NormPointGroup, size int, cutoff int,
   147  	newEvaluators func(n int) evaluator.Evaluator, mutator mutation.Method) *simple {
   148  	var algo simple
   149  
   150  	for i := 0; i < size; i++ {
   151  		pg := newPointGroup()
   152  		algo.population = append(algo.population, pg)
   153  		algo.newPopulation = append(algo.newPopulation, pg.Copy())
   154  	}
   155  
   156  	algo.best = algo.population[0].Copy()
   157  
   158  	algo.evaluator = newEvaluators(size)
   159  
   160  	algo.fitnesses = make([]FitnessData, len(algo.population))
   161  
   162  	algo.mutations = make([][]mutation.Mutation, len(algo.population))
   163  
   164  	algo.mutator = mutator
   165  
   166  	algo.cutoff = cutoff
   167  
   168  	algo.calculateFitnesses()
   169  
   170  	algo.updateFitnesses()
   171  
   172  	return &algo
   173  }