|
| 1 | +// This file is part of Empirical, https://github.com/devosoft/Empirical |
| 2 | +// Copyright (C) Michigan State University, 2016-2017. |
| 3 | +// Released under the MIT Software license; see doc/LICENSE |
| 4 | +// |
| 5 | +// |
| 6 | +// This file provides code to build NK-based algorithms. |
| 7 | + |
| 8 | +#ifndef EMP_EVO_NK_H |
| 9 | +#define EMP_EVO_NK_H |
| 10 | + |
| 11 | +#include <array> |
| 12 | + |
| 13 | +#include "../base/vector.h" |
| 14 | +#include "../tools/BitVector.h" |
| 15 | +#include "../tools/math.h" |
| 16 | +#include "../tools/memo_function.h" |
| 17 | +#include "../tools/Random.h" |
| 18 | + |
| 19 | +namespace emp { |
| 20 | +namespace evo { |
| 21 | + |
| 22 | + /// An NK Landscape is a popular tool for studying theoretical questions about evolutionary |
| 23 | + /// dynamics. It is a randomly generated fitness landscape on which bitstrings can evolve. |
| 24 | + /// NK Landscapes have two parameters: N (the length of the bitstrings) and K (epistasis). |
| 25 | + /// Since you have control over the amount of epistasis, NK Landscapes are often called |
| 26 | + /// "tunably rugged" - a useful feature, since the ruggedness of the fitness landscape is thought |
| 27 | + /// to be important to many evolutionary dynamics. For each possible value that a site and its |
| 28 | + /// K neighbors to the right can have, a random fitness contribution is chosen. |
| 29 | + /// These contributions are summed across the bitstring. So when K = 0, each site has a single |
| 30 | + /// optimal value, resulting in a single smooth fitness peak. |
| 31 | + /// |
| 32 | + /// For more information, see Kauffman and Levin, |
| 33 | + /// 1987 (Towards a general theory of adaptive walks on rugged landscapes). |
| 34 | + /// |
| 35 | + /// This object handles generating and maintaining an NK fitness landscape. |
| 36 | + /// Note: Overly large Ns and Ks currently trigger a seg-fault, caused by trying to build a table |
| 37 | + /// that is larger than will fit in memory. If you are using small values for N and K, |
| 38 | + /// you can get better performance by using an NKLandscapeConst instead. |
| 39 | + |
| 40 | + class NKLandscape { |
| 41 | + private: |
| 42 | + size_t N; |
| 43 | + size_t K; |
| 44 | + size_t state_count; |
| 45 | + size_t total_count; |
| 46 | + emp::vector< emp::vector<double> > landscape; |
| 47 | + |
| 48 | + public: |
| 49 | + NKLandscape() : N(0), K(0), state_count(0), total_count(0), landscape() { ; } |
| 50 | + NKLandscape(const NKLandscape &) = default; |
| 51 | + NKLandscape(NKLandscape &&) = default; |
| 52 | + |
| 53 | + /// N is the length of bitstrings in your population, K is the number of neighboring sites |
| 54 | + /// the affect the fitness contribution of each site (i.e. epistasis or ruggedness), random |
| 55 | + /// is the random number generator to use to generate this landscape. |
| 56 | + NKLandscape(size_t _N, size_t _K, emp::Random & random) |
| 57 | + : N(_N), K(_K) |
| 58 | + , state_count(emp::IntPow<size_t>(2,K+1)) |
| 59 | + , total_count(N * state_count) |
| 60 | + , landscape(N) |
| 61 | + { |
| 62 | + Reset(random); |
| 63 | + } |
| 64 | + ~NKLandscape() { ; } |
| 65 | + NKLandscape & operator=(const NKLandscape &) = delete; |
| 66 | + NKLandscape & operator=(NKLandscape &&) = default; |
| 67 | + |
| 68 | + /// Randomize the landscape without changing the landscape size. |
| 69 | + void Reset(emp::Random & random) { |
| 70 | + emp_assert(K < 32, K); |
| 71 | + emp_assert(K < N, K, N); |
| 72 | + |
| 73 | + // Build new landscape. |
| 74 | + for ( auto & ltable : landscape) { |
| 75 | + ltable.resize(state_count); |
| 76 | + for (double & pos : ltable) { |
| 77 | + pos = random.GetDouble(); |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + /// Configure for new values of N and K. |
| 83 | + void Config(size_t _N, size_t _K, emp::Random & random) { |
| 84 | + // Save new values. |
| 85 | + N = _N; K = _K; |
| 86 | + state_count = emp::IntPow<size_t>(2,K+1); |
| 87 | + total_count = N * state_count; |
| 88 | + landscape.resize(N); |
| 89 | + Reset(random); |
| 90 | + } |
| 91 | + |
| 92 | + /// Returns N |
| 93 | + size_t GetN() const { return N; } |
| 94 | + /// Returns K |
| 95 | + size_t GetK() const { return K; } |
| 96 | + /// Get the number of posssible states for a given site |
| 97 | + size_t GetStateCount() const { return state_count; } |
| 98 | + /// Get the total number of states possible in the landscape |
| 99 | + /// (i.e. the number of different fitness contributions in the table) |
| 100 | + size_t GetTotalCount() const { return total_count; } |
| 101 | + |
| 102 | + /// Get the fitness contribution of position [n] when it (and its K neighbors) have the value |
| 103 | + /// [state] |
| 104 | + double GetFitness(size_t n, size_t state) const { |
| 105 | + emp_assert(state < state_count, state, state_count); |
| 106 | + return landscape[n][state]; |
| 107 | + } |
| 108 | + |
| 109 | + /// Get the fitness of a whole bitstring |
| 110 | + double GetFitness( std::vector<size_t> states ) const { |
| 111 | + emp_assert(states.size() == N); |
| 112 | + double total = landscape[0][states[0]]; |
| 113 | + for (size_t i = 1; i < N; i++) total += GetFitness(i,states[i]); |
| 114 | + return total; |
| 115 | + } |
| 116 | + |
| 117 | + /// Get the fitness of a whole bitstring (pass by value so can be modified.) |
| 118 | + double GetFitness(BitVector genome) const { |
| 119 | + emp_assert(genome.GetSize() == N, genome.GetSize(), N); |
| 120 | + |
| 121 | + // Use a double-length genome to easily handle wrap-around. |
| 122 | + genome.Resize(N*2); |
| 123 | + genome |= (genome << N); |
| 124 | + |
| 125 | + double total = 0.0; |
| 126 | + size_t mask = emp::MaskLow<size_t>(K+1); |
| 127 | + for (size_t i = 0; i < N; i++) { |
| 128 | + const size_t cur_val = (genome >> i).GetUInt(0) & mask; |
| 129 | + const double cur_fit = GetFitness(i, cur_val); |
| 130 | + total += cur_fit; |
| 131 | + } |
| 132 | + return total; |
| 133 | + } |
| 134 | + }; |
| 135 | + |
| 136 | + /// The NKLandscapeMemo class is simialar to NKLandscape, but it does not pre-calculate all |
| 137 | + /// of the landscape states. Instead it determines the value of each gene combination on first |
| 138 | + /// use and memorizes it. |
| 139 | + |
| 140 | + class NKLandscapeMemo { |
| 141 | + private: |
| 142 | + const size_t N; |
| 143 | + const size_t K; |
| 144 | + mutable emp::vector< emp::memo_function<double(const BitVector &)> > landscape; |
| 145 | + emp::vector<BitVector> masks; |
| 146 | + |
| 147 | + public: |
| 148 | + NKLandscapeMemo() = delete; |
| 149 | + NKLandscapeMemo(const NKLandscapeMemo &) = delete; |
| 150 | + NKLandscapeMemo(NKLandscapeMemo &&) = default; |
| 151 | + NKLandscapeMemo(size_t _N, size_t _K, emp::Random & random) |
| 152 | + : N(_N), K(_K), landscape(N), masks(N) |
| 153 | + { |
| 154 | + // Each position in the landscape... |
| 155 | + for (size_t n = 0; n < N; n++) { |
| 156 | + // ...should have its own memo_function |
| 157 | + landscape[n] = [&random](const BitVector &){ return random.GetDouble(); }; |
| 158 | + // ...and its own mask. |
| 159 | + masks[n].Resize(N); |
| 160 | + for (size_t k = 0; k < K; k++) masks[n][(n+k)%N] = 1; |
| 161 | + } |
| 162 | + } |
| 163 | + ~NKLandscapeMemo() { ; } |
| 164 | + NKLandscapeMemo & operator=(const NKLandscapeMemo &) = delete; |
| 165 | + NKLandscapeMemo & operator=(NKLandscapeMemo &&) = default; |
| 166 | + |
| 167 | + size_t GetN() const { return N; } |
| 168 | + size_t GetK() const { return K; } |
| 169 | + |
| 170 | + double GetFitness(size_t n, const BitVector & state) const { |
| 171 | + emp_assert(state == (state & masks[n])); |
| 172 | + return landscape[n](state); |
| 173 | + } |
| 174 | + double GetFitness(const BitVector & genome) const { |
| 175 | + emp_assert(genome.GetSize() == N); |
| 176 | + |
| 177 | + // Otherwise calculate it. |
| 178 | + double total = 0.0; |
| 179 | + for (size_t n = 0; n < N; n++) { |
| 180 | + total += landscape[n](genome & masks[n]); |
| 181 | + } |
| 182 | + return total; |
| 183 | + } |
| 184 | + }; |
| 185 | + |
| 186 | +} |
| 187 | +} |
| 188 | + |
| 189 | +#endif |
0 commit comments