Modernized GAlib  3.0.0 current
GAGenome.h
1 /* ----------------------------------------------------------------------------
2  mbwall 28jul94
3  Copyright (c) 1995 Massachusetts Institute of Technology
4 ---------------------------------------------------------------------------- */
5 
6 #pragma once
7 
8 #include <GAEvalData.h>
9 #include <gaconfig.h>
10 #include <gaerror.h>
11 #include <gaid.h>
12 
13 #include <istream>
14 #include <ostream>
15 
16 class GAGeneticAlgorithm;
17 class GAGenome;
18 
19 template <typename T1, typename T2> constexpr void SWAP(T1 &a, T2 &b)
20 {
21  auto tmp = a;
22  a = b;
23  b = tmp;
24 }
25 
26 /* ----------------------------------------------------------------------------
27 Genome
28 -------------------------------------------------------------------------------
29 
30 Deriving your own genomes:
31  For any derived class be sure to define the canonical methods: constructor,
32 copy constructor, operator=, and destructor. Make sure that you check for a
33 self-copy in your copy method (it is possible that a genome will be
34 selected to cross with itself, and self-copying is not out of the question)
35  To work properly with the GAlib, you MUST define the following:
36 
37  YourGenome( -default-args-for-your-genome )
38  YourGenome(const YourGenome&)
39  virtual ~YourGenome()
40  virtual GAGenome* clone(GAGenome::CloneMethod)
41  virtual copy(const GAGenome&)
42 
43  If your genome class defines any new properties you should to define:
44 
45  virtual int read(istream&)
46  virtual int write(ostream&) const
47  virtual int equal(const GAGenome&) const
48 
49 
50 
51 
52 
53 
54 
55 
56  When you derive a genome, don't forget to use the _evaluated flag to
57  indicate when the state of the genome has changed and an evaluation is
58  needed.
59  Assign a default crossover method so that users don't have to assign one
60  unless they want to. Do this in the constructor.
61  It is a good idea to define an identity for your genome (especially if
62  you will be using it in an environment with multiple genome types running
63  around). Use the DefineIdentity/DeclareIdentity macros (defined in id.h)
64  to do this in your class definition.
65 
66 
67 Brief overview of the member functions:
68 
69 initialize
70  Use this method to set the initial state of your genomes once they have
71  been created. This initialization is for setting up the genome's state,
72  not for doing the basic mechanics of genome class management. The
73  default behaviour of this method is to change randomly the contents of the
74  genome. If you want to bias your initial population, this is where to
75  make that happen.
76  The initializer is used to initialize the genome (duh). Notice that the
77  state of the genome is unknown - memory may or may not have been allocated,
78  and the genome may or may not have been used before. So your initializer
79  should first clean up as needed, then do its thing. The initializer may be
80  called any number of times (unlike a class constructor which is called only
81  once for a given instance).
82 
83 
84 
85 
86 
87 
88 
89 
90 mutate
91  Mutate the genome with probability as specified. What mutation means
92  depends upon the data type of the genome. For example, you could have
93  a bit string in which 50% mutation means each bit has a 50% chance of
94  getting flipped, or you could have a tree in which 50% mutation means each
95  node has a 50% chance of getting deleted, or you could have a bit string
96  in which 50% mutation means 50% of the bits ACTUALLY get flipped.
97  The mutations member returns the number of mutations since the genome
98  was initialized.
99  The mutator makes a change to the genome with likeliehood determined by the
100  mutation rate parameter. The exact meaning of mutation is up to you, as is
101  the specific meaning of the mutation rate. The function returns the number
102  of mutations that actually occurred.
103 
104 crossover
105  Genomes don't really have any clue about other genomes, so we don't make
106  the crossover a member function. Instead, each genome kind of knows how
107  to mate with other genomes to generate offspring, but they are not
108  capable of doing it themselves. The crossover member function is used to
109  set the default mating mode for the genomes - it does not actually perform
110  the crossover. This way the GA can use asexual crossover if it wants to
111  (but genomes only know how to do the default sexual crossover).
112  This also lets you do funky stuff like crossover between different data
113  types and group sex to generate new offspring.
114  We define two types of crossover: sexual and asexual. Most GAlib
115  algorithms use the sexual crossover, but both are available. Each genome
116  knows the preferred crossover method, but none is capable of reproducing.
117  The genetic algorithm must actually perform the mating because it involves
118  another genome (as parent and/or child).
119 
120 evaluator
121  Set the genome's objective function. This also sets marks the evaluated
122  flag to indicate that the genome must be re-evaluated.
123  Evaluation happens on-demand - the objective score is not calculated until
124  it is requested. Then it is cached so that it does not need to be re-
125  calculated each time it is requested. This means that any member function
126  that modifies the state of the genome must also set the evaluated flag to
127  indicate that the score must be recalculated.
128  The genome objective function is used by the GA to evaluate each member of
129  the population.
130 
131 comparator
132  This method is used to determine how similar two genomes are. If you want
133  to use a different comparison method without deriving a new class, then use
134  the comparator function to do so. For example, you may want to do phenotype-
135  based comparisons rather than genotype-based comparisons.
136  In many cases we have to compare two genomes to determine how similar or
137  different they are. In traditional GA literature this type of function is
138  referred to as a 'distance' function, probably because bit strings can be
139  compared using the Hamming distance as a measure of similarity. In GAlib, we
140  define a genome comparator function that does exactly this kind of
141  comparison.
142  If the genomes are identical, the similarity function should return a
143  value of 0.0, if completely different then return a value greater than 0.
144  The specific definition of what "the same" and what "different" mean is up
145  to you. Most of the default comparators use the genotype for the comparison,
146  but you can use the phenotype if you prefer. There is no upper limit to the
147  distance score as far as GAlib is concerned.
148  The no-op function returns a -1 to signify that the comparison failed.
149 
150 evalData
151  The evalData member is useful if you do not want to derive a new genome class
152  but want to store data with each genome. When you clone a genome, the eval
153  data also gets cloned so that each genome has its own eval data (unlike the
154  user data pointer described next which is shared by all genomes).
155 
156 userData
157  The userData member is used to provide all genomes access to the same user
158  data. This can be a pointer to anything you want. Any genome cloned from
159  another will share the same userData as the original. This means that all
160  of the genomes in a population, for example, share the same userData.
161 
162 score
163  Evaluate the 'performance' of the genome using the objective function.
164  The score is kept in the 'score' member. The 'evaluated' member tells us
165  whether or not we can trust the score. Be sure to set/unset the 'evaluated'
166  member as appropriate (eg cross and mutate change the contents of the
167  genome so they unset the 'evaluated' flag).
168  If there is no objective function, then simply return the score. This
169  allows us to use population-based evaluation methods (where the population
170  method sets the score of each genome).
171 
172 clone
173  This method allocates space for a new genome and copies the original into
174  the new space. Depending on the argument, it either copies the entire
175  original or just parts of the original. For some data types, clone contents
176  and clone attributes will do the same thing. If your data type requires
177  significant overhead for initialization, then you'll probably want to
178  distinguish between cloning contents and cloning attributes.
179 clone(cont)
180  Clone the contents of the genome. Returns a pointer to a GAGenome
181  (which actually points to a genome of the type that was cloned). This is
182  a 'deep copy' in which every part of the genome is duplicated.
183 clone(attr)
184  Clone the attributes of the genome. This method does nothing to the
185  contents of the genome. It does NOT call the initialization method. For
186  some data types this is the same thing as cloning the contents.
187 ---------------------------------------------------------------------------- */
188 
189 
190 
199 class GAGenome : public GAID
200 {
201  public:
202  GADefineIdentity("GAGenome", GAID::Genome);
203 
204  public:
205  using Evaluator = float (*)(GAGenome &);
206  using Initializer = void (*)(GAGenome &);
207  using Mutator = int (*)(GAGenome &, float);
208  using Comparator = float (*)(const GAGenome &, const GAGenome &);
209  using SexualCrossover = int (*)(const GAGenome &, const GAGenome &, GAGenome *, GAGenome *);
210  using AsexualCrossover = int (*)(const GAGenome &, GAGenome *);
211 
212  public:
213  static void NoInitializer(GAGenome &);
214  static int NoMutator(GAGenome &, float);
215  static float NoComparator(const GAGenome &, const GAGenome &);
216 
217  public:
218  enum class Dimension
219  {
220  LENGTH = 0,
221  WIDTH = 0,
222  HEIGHT = 1,
223  DEPTH = 2
224  };
225  enum class CloneMethod
226  {
227  CONTENTS = 0,
228  ATTRIBUTES = 1
229  };
230  enum
231  {
232  FIXED_SIZE = -1,
233  ANY_SIZE = -10
234  };
235 
236  public:
237  // The GNU compiler sucks. It won't recognize No*** as a member of the
238  // genome class. So we have to use 0 as the defaults then check in the
239  // constructor.
240  GAGenome(Initializer i = nullptr, Mutator m = nullptr,
241  Comparator c = nullptr);
242  GAGenome(const GAGenome &orig);
243  GAGenome &operator=(const GAGenome &arg)
244  {
245  copy(arg);
246  return *this;
247  }
248  ~GAGenome() override;
249  virtual GAGenome *clone(CloneMethod flag = CloneMethod::CONTENTS) const;
250  virtual void copy(const GAGenome &);
251 
252  virtual int read(std::istream &)
253  {
254  GAErr(GA_LOC, className(), "read", GAError::OpUndef);
255  return 0;
256  }
257  virtual int write(std::ostream &) const
258  {
259  GAErr(GA_LOC, className(), "write", GAError::OpUndef);
260  return 0;
261  }
262 
263  virtual bool equal(const GAGenome &) const
264  {
265  GAErr(GA_LOC, className(), "equal", GAError::OpUndef);
266  return true;
267  }
268  virtual bool notequal(const GAGenome &g) const
269  {
270  return (equal(g) ? false : true);
271  }
272 
273  public:
274  int nevals() const { return _neval; }
275  float score() const
276  {
277  evaluate();
278  return _score;
279  }
280  float score(float s)
281  {
282  _evaluated = true;
283  return _score = s;
284  }
285  float fitness() { return _fitness; }
286  float fitness(float f) { return _fitness = f; }
287 
288  GAGeneticAlgorithm *geneticAlgorithm() const { return ga; }
289  GAGeneticAlgorithm *geneticAlgorithm(GAGeneticAlgorithm &g)
290  {
291  return (ga = &g);
292  }
293 
294  void *userData() const { return ud; }
295  void *userData(void *u) { return (ud = u); }
296 
297  GAEvalData *evalData() const { return evd; }
298  GAEvalData *evalData(const GAEvalData &o)
299  {
300  delete evd;
301  evd = o.clone();
302  return evd;
303  }
304 
305  float evaluate(bool flag = false) const;
306  Evaluator evaluator() const { return eval; }
307  Evaluator evaluator(Evaluator f)
308  {
309  _evaluated = false;
310  return (eval = f);
311  }
312 
313  void initialize()
314  {
315  _evaluated = false;
316  _neval = 0;
317  (*init)(*this);
318  }
319  Initializer initializer() const { return init; }
320  Initializer initializer(Initializer op) { return (init = op); }
321 
322  int mutate(float p) { return ((*mutr)(*this, p)); }
323  Mutator mutator() const { return mutr; }
324  Mutator mutator(Mutator op) { return (mutr = op); }
325 
326  float compare(const GAGenome &g) const { return (*cmp)(*this, g); }
327  Comparator comparator() const { return cmp; }
328  Comparator comparator(Comparator c) { return (cmp = c); }
329 
330  SexualCrossover crossover(SexualCrossover f) { return sexcross = f; }
331  SexualCrossover sexual() const { return sexcross; }
332  AsexualCrossover crossover(AsexualCrossover f) { return asexcross = f; }
333  AsexualCrossover asexual() const { return asexcross; }
334 
335  protected:
336  float _score; // value returned by the objective function
337  float _fitness; // (possibly scaled) fitness score
338  bool _evaluated; // has this genome been evaluated?
339  unsigned int _neval; // how many evaluations since initialization?
340  GAGeneticAlgorithm *ga; // the ga that is using this genome
341  void *ud; // pointer to user data
342  Evaluator eval; // objective function
343  GAEvalData *evd; // evaluation data (specific to each genome)
344  Mutator mutr; // the mutation operator to use for mutations
345  Initializer init; // how to initialize this genome
346  Comparator cmp; // how to compare two genomes of this type
347 
348  SexualCrossover sexcross; // preferred sexual mating method
349  AsexualCrossover asexcross; // preferred asexual mating method
350 };
351 
352 inline std::ostream &operator<<(std::ostream &os, const GAGenome &genome)
353 {
354  genome.write(os);
355  return (os);
356 }
357 inline std::istream &operator>>(std::istream &is, GAGenome &genome)
358 {
359  genome.read(is);
360  return (is);
361 }
362 
363 inline bool operator==(const GAGenome &a, const GAGenome &b)
364 {
365  return a.equal(b);
366 }
367 inline bool operator!=(const GAGenome &a, const GAGenome &b)
368 {
369  return a.notequal(b);
370 }
This is the basic interface for the object that contains evaluation data.
Definition: GAEvalData.h:15
The base GA class is virtual - it defines the core data elements and parts of the interface that are ...
Definition: GABaseGA.h:89
The base genome class just defines the genome interface - how to mutate, crossover,...
Definition: GAGenome.h:200
This defines the identifiers for polymorphic classes.
Definition: gaid.h:20