Skip to content

Commit 03c321f

Browse files
committed
Copied NK and StateGrid functionality to evo3.
1 parent 7c08dfa commit 03c321f

File tree

3 files changed

+514
-0
lines changed

3 files changed

+514
-0
lines changed

evo3/NK-const.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
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_CONST_H
9+
#define EMP_EVO_NK_CONST_H
10+
11+
#include <array>
12+
13+
#include "../base/assert.h"
14+
#include "../tools/BitSet.h"
15+
#include "../tools/math.h"
16+
#include "../tools/Random.h"
17+
18+
namespace emp {
19+
namespace evo {
20+
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. You can use larger values of N and K (for slightly
38+
/// reduced speed) of you use an NKLandscape object instead.
39+
40+
template <size_t N, size_t K>
41+
class NKLandscapeConst {
42+
private:
43+
static constexpr size_t state_count = emp::IntPow<size_t>(2,K+1);
44+
static constexpr size_t total_count = N * state_count;
45+
std::array< std::array<double, state_count>, N > landscape;
46+
47+
public:
48+
NKLandscapeConst() = delete; // { ; }
49+
50+
/// Build a new NKLandscapeConst using the random number generator [random]
51+
NKLandscapeConst(emp::Random & random) : landscape() {
52+
for ( std::array<double, state_count> & ltable : landscape) {
53+
for (double & pos : ltable) {
54+
pos = random.GetDouble();
55+
}
56+
}
57+
}
58+
NKLandscapeConst(const NKLandscapeConst &) = delete;
59+
~NKLandscapeConst() { ; }
60+
NKLandscapeConst & operator=(const NKLandscapeConst &) = delete;
61+
62+
/// Returns N
63+
constexpr size_t GetN() const { return N; }
64+
/// Returns K
65+
constexpr size_t GetK() const { return K; }
66+
/// Get the number of posssible states for a given site
67+
constexpr size_t GetStateCount() const { return state_count; }
68+
/// Get the total number of states possible in the landscape
69+
/// (i.e. the number of different fitness contributions in the table)
70+
constexpr size_t GetTotalCount() const { return total_count; }
71+
72+
/// Get the fitness contribution of position [n] when it (and its K neighbors) have the value
73+
/// [state]
74+
double GetFitness(size_t n, size_t state) const {
75+
emp_assert(state < state_count, state, state_count);
76+
// std::cout << n << " : " << state << " : " << landscape[n][state] << std::endl;
77+
return landscape[n][state];
78+
}
79+
80+
/// Get the fitness of a whole bitstring
81+
double GetFitness( std::array<size_t, N> states ) const {
82+
double total = landscape[0][states[0]];
83+
for (size_t i = 1; i < N; i++) total += GetFitness(i,states[i]);
84+
return total;
85+
}
86+
87+
/// Get the fitness of a whole bitstring
88+
double GetFitness(const BitSet<N> & genome) const {
89+
// Create a double-length genome to easily handle wrap-around.
90+
BitSet<N*2> genome2( genome.template Export<N*2>() );
91+
genome2 |= (genome2 << N);
92+
93+
double total = 0.0;
94+
constexpr size_t mask = emp::MaskLow<size_t>(K+1);
95+
for (size_t i = 0; i < N; i++) {
96+
const size_t cur_val = (genome2 >> (int)i).GetUInt(0) & mask;
97+
const double cur_fit = GetFitness(i, cur_val);
98+
total += cur_fit;
99+
}
100+
return total;
101+
}
102+
};
103+
104+
}
105+
}
106+
107+
#endif

evo3/NK.h

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
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

Comments
 (0)