-
Notifications
You must be signed in to change notification settings - Fork 0
/
run_model.py
238 lines (238 loc) · 9.43 KB
/
run_model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
#
# Run Model
#
# Peter Turney, September 22, 2021
#
# The evolutionary algorithm used here is based on Whitley's
# GENITOR algorithm, with several extensions.
#
# Whitley, D., and Kauth, J. (1988). GENITOR: A different genetic
# algorithm. Proceedings of the Rocky Mountain Conference on
# Artificial Intelligence, Denver, CO. pp. 118-130.
#
# Whitley, D. (1989). The GENITOR algorithm and selective pressure.
# Proceedings of the Third International Conference on Genetic
# Algorithms (ICGA-89), pp. 116-121. California: Morgan Kaufmann.
#
import golly as g
import model_classes as mclass
import model_functions as mfunc
import model_parameters as mparam
import random as rand
import copy
import time
import pickle
#
# -----------------------------------------------------------------
# Make a file for logging the results. The filename is based on the
# date, so that log files can easily be ordered by date.
# -----------------------------------------------------------------
#
log_name = time.strftime("log-20%y-%m-%d-%Hh-%Mm-%Ss", \
time.localtime())
log_path = mparam.log_directory + "/" + log_name + ".txt"
# use "1" option so that the log file is updated with
# each new line, in case of a forced exit
log_handle = open(log_path, "w", 1)
start_time = time.strftime("Start time: 20%y-%m-%d %Hh:%Mm:%Ss\n", \
time.localtime())
mfunc.show_message(g, log_handle, start_time)
# show parameter settings
parameter_settings = mfunc.show_parameters()
mfunc.show_message(g, log_handle, "\nParameter Settings\n\n")
for setting in parameter_settings:
mfunc.show_message(g, log_handle, setting + "\n")
mfunc.show_message(g, log_handle, "\n")
#
# -----------------------------------------------------------------
# Set the random number generator seed here. If random_seed is
# negative, then Python will automatically set a random number
# seed. Note that, if random_seed is negative, then the experiment
# cannot be exactly repeated.
# -----------------------------------------------------------------
#
random_seed = mparam.random_seed
if (random_seed >= 0):
rand.seed(random_seed)
#
# -----------------------------------------------------------------
# Build the initial population. Initialize the seeds randomly.
# -----------------------------------------------------------------
#
seed_density = mparam.seed_density # density of state 1 in seed
assert seed_density > 0.0
assert seed_density < 1.0
s_xspan = mparam.s_xspan # width of seed
s_yspan = mparam.s_yspan # height of seed
pop_size = mparam.pop_size # fixed population size
#
message = "Building initial population of size: " + str(pop_size) + "\n"
mfunc.show_message(g, log_handle, message)
#
pop = mfunc.initialize_population(pop_size, s_xspan, s_yspan, \
seed_density)
#
for seed in pop:
mfunc.seed_storage(seed) # store all seeds for future analysis
#
# -----------------------------------------------------------------
# Make the seeds compete against each other, to build up a history
# of wins and losses for the initial population.
# -----------------------------------------------------------------
#
width_factor = mparam.width_factor
height_factor = mparam.height_factor
time_factor = mparam.time_factor
num_trials = mparam.num_trials
#
message = "Building a history for initial population.\n"
mfunc.show_message(g, log_handle, message)
#
# Every seed competes against every other seed (and itself)
for i in range(pop_size):
# Since mfunc.update_history updates i's score for j and j's score for i,
# we only need to calculate the lower triangle of the matrix of scores.
for j in range(i + 1):
mfunc.update_history(g, pop, i, j, width_factor, height_factor, \
time_factor, num_trials)
# While we're here, let's update the similarities.
mfunc.update_similarity(pop, i, j)
#
# -----------------------------------------------------------------
# Log the average population fitness for the initial population.
# -----------------------------------------------------------------
#
avg_fit = mfunc.average_fitness(pop)
message = "Average fitness of the initial population: {:.3f}\n".format(avg_fit)
mfunc.show_message(g, log_handle, message)
#
# -----------------------------------------------------------------
# Run the system until run_length children have been born.
# -----------------------------------------------------------------
#
# Get some parameter values from model_parameters.py.
#
run_length = mparam.run_length
tournament_size = mparam.tournament_size
elite_size = mparam.elite_size
log_directory = mparam.log_directory
experiment_type_num = mparam.experiment_type_num
max_area_first = mparam.max_area_first
max_area_last = mparam.max_area_last
#
# We add 1 to run_length so that a run_length of, say, 1000, will
# yield a range of 0, 1, ..., 1000. Then, if pop_size is, say, 100,
# the final trip through the loop will have n = 1000 and
# pop_size = 100, so ((n % pop_size) == 0) will be true, and
# the final trip will be archived.
#
for n in range(run_length + 1):
#
# Calculate the next available unique ID number for stored seeds.
# The initial random seeds use the ID numbers from range(pop_size);
# that is, from 0 to pop_size - 1. See initialize_population().
# The value of n (see immediately above) uses numbers from
# range(run_length + 1). Therefore:
#
next_unique_ID_number = pop_size + n
#
# If n (the number of children born so far) is an integer multiple
# of pop_size (the population size), then store the top elite_size
# seeds in the population, as a benchmark for measuring progress
# in evolution.
#
if ((n % pop_size) == 0): # if n divides evenly by pop_size ...
run_id_number = int(n / pop_size) # ... an integer is expected here
# Store the elite of the population for later analysis.
mfunc.archive_elite(pop, elite_size, log_directory, \
log_name, run_id_number)
#
#
# Calculate max_seed_area. The maximum seed area increases linearly
# with each new child born. The motivation for this linear limit to
# the seed area is to prevent an explosive increase in seed area,
# which causes the simulation to run extremely slowly. This limit is
# due to a lack of patience on my part; it is not intended to model
# a natural phenomenon.
#
max_area_delta = max_area_last - max_area_first
max_area_increment = max_area_delta * (n / float(run_length + 1))
max_seed_area = max_area_first + max_area_increment
#
# Run a tournament to select a seed for reproduction. Four types
# of reproduction are possible.
#
# Get a random sample of tournament_size from the population
tournament_sample = mfunc.random_sample(pop, tournament_size)
# Find the most fit member of the sample
candidate_seed = mfunc.find_best_seed(tournament_sample)
#
# Find the address of the incumbent best seed in the population.
#
incumbent_seed = mfunc.find_best_seed(pop)
#
# Update the population according to the chosen type of reproduction;
# that is, chosen according to experiment_type_num.
#
if (experiment_type_num == 1):
# uniform asexual -- note: no need for max_seed_area here
[pop, message] = mfunc.uniform_asexual(candidate_seed, \
pop, n, next_unique_ID_number)
mfunc.show_message(g, log_handle, message)
elif (experiment_type_num == 2):
# variable asexual
[pop, message] = mfunc.variable_asexual(candidate_seed, \
pop, n, max_seed_area, next_unique_ID_number)
mfunc.show_message(g, log_handle, message)
elif (experiment_type_num == 3):
# sexual
[pop, message] = mfunc.sexual(candidate_seed, pop, n, \
max_seed_area, next_unique_ID_number)
mfunc.show_message(g, log_handle, message)
else:
# symbiotic
assert experiment_type_num == 4
[pop, message] = mfunc.symbiotic(candidate_seed, pop, n, \
max_seed_area, next_unique_ID_number)
mfunc.show_message(g, log_handle, message)
#
# Compare the new best seed with the incumbent best seed.
# Note that the fitness of the incumbent will have changed
# now that the population has been updated.
#
incumbent_address = incumbent_seed.address
incumbent_fitness = incumbent_seed.fitness()
incumbent_area = incumbent_seed.xspan * incumbent_seed.yspan
#
winning_seed = mfunc.find_best_seed(pop)
winning_address = winning_seed.address
winning_fitness = winning_seed.fitness()
winning_area = winning_seed.xspan * winning_seed.yspan
#
similarity = mfunc.similarity(incumbent_seed, winning_seed)
address_change = (incumbent_address != winning_address)
fitness_change = winning_fitness - incumbent_fitness
area_change = winning_area - incumbent_area
#
message = "Incumbent vs Winner: " + \
" Similarity: {:.3f}".format(similarity) + \
" Address change: {:}".format(address_change) + \
" Fitness change: {:.3f}".format(fitness_change) + \
" Area change: {:.3f}\n".format(area_change)
#
mfunc.show_message(g, log_handle, message)
#
#
# -----------------------------------------------------------------
# Close the log file.
# -----------------------------------------------------------------
#
avg_fit = mfunc.average_fitness(pop)
message = "Average fitness of the final population: {:.3f}\n".format(avg_fit)
mfunc.show_message(g, log_handle, message)
#
end_time = time.strftime("End time: 20%y-%m-%d %Hh:%Mm:%Ss\n", time.localtime())
mfunc.show_message(g, log_handle, end_time)
log_handle.close()
#
#