piegy 2.1.0__cp38-cp38-win32.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- piegy/C_core/piegyc.cp38-win32.pyd +0 -0
- piegy/__init__.py +56 -0
- piegy/__version__.py +31 -0
- piegy/analysis.py +222 -0
- piegy/data_tools.py +127 -0
- piegy/figures.py +491 -0
- piegy/simulation.py +500 -0
- piegy/simulation_py.py +817 -0
- piegy/test_var.py +583 -0
- piegy/tools/__init__.py +15 -0
- piegy/tools/figure_tools.py +238 -0
- piegy/tools/file_tools.py +29 -0
- piegy/videos.py +303 -0
- piegy-2.1.0.dist-info/LICENSE.txt +28 -0
- piegy-2.1.0.dist-info/METADATA +112 -0
- piegy-2.1.0.dist-info/RECORD +18 -0
- piegy-2.1.0.dist-info/WHEEL +5 -0
- piegy-2.1.0.dist-info/top_level.txt +1 -0
piegy/simulation.py
ADDED
@@ -0,0 +1,500 @@
|
|
1
|
+
'''
|
2
|
+
Main Module of Stochastic Simulation
|
3
|
+
-------------------------------------------
|
4
|
+
|
5
|
+
Contains all the necessary tools to build a model and run models based on Gillespie Algorithm.
|
6
|
+
|
7
|
+
Class & Functions:
|
8
|
+
- model: creates a stochastic mode. Run the model by ``simulation.run(mod)``
|
9
|
+
- run: runs stochastic simulation on a model.
|
10
|
+
- demo_model: returns a demo model.
|
11
|
+
- UV_expected_val: calculates the expected population of U, V at steady state, assuming no migration and any stochastic process.
|
12
|
+
- check_overflow_func: check whether an overflow might happen in simulation. This is usually done automatically when init-ing a model.
|
13
|
+
'''
|
14
|
+
|
15
|
+
|
16
|
+
import numpy as np
|
17
|
+
import os
|
18
|
+
import ctypes
|
19
|
+
from ctypes import c_size_t, c_uint32, c_int32, c_int64, c_double, c_bool, c_char_p, c_char
|
20
|
+
import numpy as np
|
21
|
+
from numpy.ctypeslib import ndpointer
|
22
|
+
|
23
|
+
|
24
|
+
# path to the C shared libary
|
25
|
+
C_LIB_PATH = os.path.join(os.path.dirname(__file__), 'C_core', 'piegyc.so')
|
26
|
+
|
27
|
+
# check whether overflow / too large values might be encountered
|
28
|
+
# these values are considered as exponents in exp()
|
29
|
+
EXP_OVERFLOW_BOUND = 709 # where exp(x) reaches overflow bound
|
30
|
+
EXP_TOO_LARGE_BOUND = 88 # where exp(x) reaches 1e20
|
31
|
+
|
32
|
+
|
33
|
+
'''
|
34
|
+
The C core
|
35
|
+
'''
|
36
|
+
|
37
|
+
class model_c(ctypes.Structure):
|
38
|
+
'''
|
39
|
+
The C-cored model
|
40
|
+
'''
|
41
|
+
|
42
|
+
_fields_ = [
|
43
|
+
('N', c_size_t),
|
44
|
+
('M', c_size_t),
|
45
|
+
('maxtime', c_double),
|
46
|
+
('record_itv', c_double),
|
47
|
+
('sim_time', c_size_t),
|
48
|
+
('boundary', c_bool),
|
49
|
+
|
50
|
+
('I', ctypes.POINTER(c_uint32)),
|
51
|
+
('X', ctypes.POINTER(c_double)),
|
52
|
+
('P', ctypes.POINTER(c_double)),
|
53
|
+
|
54
|
+
('print_pct', c_int32),
|
55
|
+
('seed', c_int32),
|
56
|
+
|
57
|
+
('data_empty', c_bool),
|
58
|
+
('max_record', c_size_t),
|
59
|
+
('arr_size', c_size_t),
|
60
|
+
('compress_itv', c_uint32),
|
61
|
+
|
62
|
+
('U1d', ctypes.POINTER(c_double)),
|
63
|
+
('V1d', ctypes.POINTER(c_double)),
|
64
|
+
('Upi_1d', ctypes.POINTER(c_double)),
|
65
|
+
('Vpi_1d', ctypes.POINTER(c_double)),
|
66
|
+
]
|
67
|
+
def get_array(self, name):
|
68
|
+
"""Return internal data as NumPy array, e.g. .get_array('U')"""
|
69
|
+
ptr = getattr(self, name)
|
70
|
+
return np.ctypeslib.as_array(ptr, shape=(self.arr_size,))
|
71
|
+
|
72
|
+
lib = ctypes.CDLL(C_LIB_PATH, winmode = 0)
|
73
|
+
lib.mod_init.argtypes = [
|
74
|
+
ctypes.POINTER(model_c), c_size_t, c_size_t,
|
75
|
+
c_double, c_double, c_size_t, c_bool,
|
76
|
+
ndpointer(dtype=np.uint32, flags="C_CONTIGUOUS"),
|
77
|
+
ndpointer(dtype=np.float64, flags="C_CONTIGUOUS"),
|
78
|
+
ndpointer(dtype=np.float64, flags="C_CONTIGUOUS"),
|
79
|
+
c_int32, c_int32
|
80
|
+
]
|
81
|
+
lib.mod_init.restype = c_bool
|
82
|
+
|
83
|
+
lib.mod_free_py.argtypes = [ctypes.POINTER(model_c)]
|
84
|
+
lib.mod_free_py.restype = None
|
85
|
+
|
86
|
+
lib.run.argtypes = [ctypes.POINTER(model_c), ctypes.POINTER(c_char), c_size_t]
|
87
|
+
lib.run.restype = None
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
'''
|
93
|
+
For access by Python
|
94
|
+
'''
|
95
|
+
|
96
|
+
class model:
|
97
|
+
'''
|
98
|
+
Store model data and input parameters.
|
99
|
+
Initialize a model object to run models.
|
100
|
+
|
101
|
+
Public Class Functions:
|
102
|
+
|
103
|
+
__init__:
|
104
|
+
Create a model object. Also initialize data storage.
|
105
|
+
|
106
|
+
__str__:
|
107
|
+
Print model object in a nice way.
|
108
|
+
|
109
|
+
copy:
|
110
|
+
Return a deep copy of self. Can choose whether to copy data as well. Default is to copy.
|
111
|
+
|
112
|
+
clear_data:
|
113
|
+
clear all data stored, set U, V, Upi, Vpi to zero arrays
|
114
|
+
|
115
|
+
change_maxtime:
|
116
|
+
Changes maxtime of self. Update data storage as well.
|
117
|
+
|
118
|
+
set_seed:
|
119
|
+
Set a new seed.
|
120
|
+
|
121
|
+
compress_data:
|
122
|
+
compress data by only storing average values
|
123
|
+
'''
|
124
|
+
|
125
|
+
def __init__(self, N, M, maxtime, record_itv, sim_time, boundary, I, X, P, print_pct = 25, seed = None, check_overflow = True):
|
126
|
+
|
127
|
+
self.check_valid_input(N, M, maxtime, record_itv, sim_time, boundary, I, X, P, print_pct, seed, check_overflow)
|
128
|
+
|
129
|
+
self.N = N # int, N x M is spatial dimension
|
130
|
+
self.M = M # int, can't be 1. If want to make 1D space, use N = 1. And this model doesn't work for 1x1 space (causes NaN)
|
131
|
+
self.maxtime = maxtime # float or int, run model for how long time
|
132
|
+
self.record_itv = record_itv # float, record data every record_itv of time
|
133
|
+
self.sim_time = sim_time # int, run this many of rounds (of single_test)
|
134
|
+
self.boundary = boundary # bool, the N x M space have boundary or not (i.e., zero-flux (True) or periodical (False))
|
135
|
+
self.I = np.array(I) # N x M x 2 np.array, initial population. Two init-popu for every patch (U and V)
|
136
|
+
self.X = np.array(X) # N x M x 4 np.array, matrices. The '4' comes from 2x2 matrix flattened to 1D
|
137
|
+
self.P = np.array(P) # N x M x 6 np.array, 'patch variables', i.e., mu1&2, w1&2, kappa1&2
|
138
|
+
if print_pct == None:
|
139
|
+
self.print_pct = -1
|
140
|
+
else:
|
141
|
+
self.print_pct = print_pct # int, print how much percent is done, need to be non-zero
|
142
|
+
if seed == None:
|
143
|
+
self.seed = -1 # non-negative int, seed for random number generation
|
144
|
+
else:
|
145
|
+
self.seed = seed
|
146
|
+
self.check_overflow = check_overflow
|
147
|
+
|
148
|
+
if check_overflow:
|
149
|
+
check_overflow_func(self)
|
150
|
+
|
151
|
+
self.init_storage() # initialize storage bins. Put in a separate function because might want to change maxtime
|
152
|
+
# and that doesn't need to initialze the whole object again
|
153
|
+
|
154
|
+
|
155
|
+
def init_storage(self):
|
156
|
+
# initialize storage bins
|
157
|
+
self.data_empty = True # whether data storage bins are empty. model.run will refuse to run (raise error) if not empty.
|
158
|
+
self.max_record = int(self.maxtime / self.record_itv) # int, how many data points to store sin total
|
159
|
+
self.compress_itv = 1 # int, intended to reduce size of data (if not 1). Updated by compress_data function
|
160
|
+
# if set to an int, say 20, mod will take average over every 20 data points and save them as new data.
|
161
|
+
# May be used over and over again to recursively reduce data size.
|
162
|
+
# Default is 1, not to take average.
|
163
|
+
self.U = None # initialized by simulation.run or data_tools.read_data
|
164
|
+
self.V = None
|
165
|
+
self.Upi = None
|
166
|
+
self.Vpi = None
|
167
|
+
|
168
|
+
|
169
|
+
def check_valid_input(self, N, M, maxtime, record_itv, sim_time, boundary, I, X, P, print_pct, seed, check_overflow):
|
170
|
+
# check whether the inputs are valid
|
171
|
+
|
172
|
+
if (N < 1) or (M < 1):
|
173
|
+
raise ValueError('N < 1 or M < 1')
|
174
|
+
if (N == 1) and (M == 1):
|
175
|
+
raise ValueError('Model fails for 1x1 space')
|
176
|
+
if (M == 1):
|
177
|
+
raise ValueError('Please set N = 1 for 1D space.')
|
178
|
+
if maxtime <= 0:
|
179
|
+
raise ValueError('Please set a positive number for maxtime')
|
180
|
+
if record_itv <= 0:
|
181
|
+
raise ValueError('Please set a positive number for record_itv')
|
182
|
+
if sim_time <= 0:
|
183
|
+
raise ValueError('Please set a positive number for sim_time')
|
184
|
+
if type(boundary) != bool:
|
185
|
+
raise TypeError('boundary not a bool. Please use True for zero-flux (with boundary) or False for periodical (no boundary)')
|
186
|
+
|
187
|
+
if (type(I) != list) and (type(I) != np.ndarray):
|
188
|
+
raise TypeError('Please set I as a list or np.ndarray')
|
189
|
+
if np.array(I).shape != (N, M, 2):
|
190
|
+
raise ValueError('Please set I as a N x M x 2 shape list or array. 2 is for init values of U, V at every patch')
|
191
|
+
|
192
|
+
if (type(X) != list) and (type(X) != np.ndarray):
|
193
|
+
raise TypeError('Please set X as a list or np.ndarray')
|
194
|
+
if np.array(X).shape != (N, M, 4):
|
195
|
+
raise ValueError('Please set X as a N x M x 4 shape list or array. 4 is for the flattened 2x2 payoff matrix')
|
196
|
+
|
197
|
+
if (type(P) != list) and (type(P) != np.ndarray):
|
198
|
+
raise TypeError('Please set P as a list or np.ndarray')
|
199
|
+
if np.array(P).shape != (N, M, 6):
|
200
|
+
raise ValueError('Please set P as a N x M x 6 shape list or array. 6 is for mu1, mu2, w1, w2, kappa1, kappa2')
|
201
|
+
|
202
|
+
if not ((print_pct == None) or (isinstance(print_pct, int) and (print_pct >= -1))):
|
203
|
+
# if not the two acceptable values: None or >= -1 int
|
204
|
+
raise ValueError('Please use an int > 0 for print_pct or None for not printing progress.')
|
205
|
+
|
206
|
+
if not ((seed == None) or (isinstance(seed, int) and (seed >= -1))):
|
207
|
+
raise ValueError('Please use a non-negative int as seed, or use None for no seed.')
|
208
|
+
|
209
|
+
if not isinstance(check_overflow, bool):
|
210
|
+
raise ValueError('Please use a bool for check_overflow')
|
211
|
+
|
212
|
+
|
213
|
+
def check_valid_data(self, data_empty, max_record, compress_itv):
|
214
|
+
# check whether a set of data is valid, used when reading a saved model
|
215
|
+
if type(data_empty) != bool:
|
216
|
+
raise TypeError('data_empty not a bool')
|
217
|
+
|
218
|
+
if type(max_record) != int:
|
219
|
+
raise TypeError('max_record not an int')
|
220
|
+
if max_record < 0:
|
221
|
+
raise ValueError('max_record < 0')
|
222
|
+
|
223
|
+
if type(compress_itv) != int:
|
224
|
+
raise TypeError('compress_itv not an int')
|
225
|
+
if compress_itv < 0:
|
226
|
+
raise ValueError('compress_itv < 0')
|
227
|
+
|
228
|
+
|
229
|
+
def __str__(self):
|
230
|
+
# print this mod in a nice format
|
231
|
+
|
232
|
+
self_str = ''
|
233
|
+
self_str += 'N = ' + str(self.N) + '\n'
|
234
|
+
self_str += 'M = ' + str(self.M) + '\n'
|
235
|
+
self_str += 'maxtime = ' + str(self.maxtime) + '\n'
|
236
|
+
self_str += 'record_itv = ' + str(self.record_itv) + '\n'
|
237
|
+
self_str += 'sim_time = ' + str(self.sim_time) + '\n'
|
238
|
+
self_str += 'boundary = ' + str(self.boundary) + '\n'
|
239
|
+
self_str += 'print_pct = ' + str(self.print_pct) + '\n'
|
240
|
+
self_str += 'seed = ' + str(self.seed) + '\n'
|
241
|
+
self_str += 'check_overflow = ' + str(self.check_overflow) + '\n'
|
242
|
+
self_str += 'data_empty = ' + str(self.data_empty) + '\n'
|
243
|
+
self_str += 'compress_itv = ' + str(self.compress_itv) + '\n'
|
244
|
+
self_str += '\n'
|
245
|
+
|
246
|
+
# check whether I, X, P all same (compare all patches to (0, 0))
|
247
|
+
I_same = True
|
248
|
+
X_same = True
|
249
|
+
P_same = True
|
250
|
+
for i in range(self.N):
|
251
|
+
for j in range(self.M):
|
252
|
+
for k in range(2):
|
253
|
+
if self.I[i][j][k] != self.I[0][0][k]:
|
254
|
+
I_same = False
|
255
|
+
for k in range(4):
|
256
|
+
if self.X[i][j][k] != self.X[0][0][k]:
|
257
|
+
X_same = False
|
258
|
+
for k in range(6):
|
259
|
+
if self.P[i][j][k] != self.P[0][0][k]:
|
260
|
+
P_same = False
|
261
|
+
|
262
|
+
if I_same:
|
263
|
+
self_str += 'I all same: ' + str(self.I[0][0]) + '\n'
|
264
|
+
else:
|
265
|
+
self_str += 'I:\n'
|
266
|
+
for i in range(self.N):
|
267
|
+
for j in range(self.M):
|
268
|
+
self_str += str(self.I[i][j]) + ' '
|
269
|
+
self_str += '\n'
|
270
|
+
self_str += '\n'
|
271
|
+
|
272
|
+
if X_same:
|
273
|
+
self_str += 'X all same: ' + str(self.X[0][0]) + '\n'
|
274
|
+
else:
|
275
|
+
self_str += 'X:\n'
|
276
|
+
for i in range(self.N):
|
277
|
+
for j in range(self.M):
|
278
|
+
self_str += str(self.X[i][j]) + ' '
|
279
|
+
self_str += '\n'
|
280
|
+
self_str += '\n'
|
281
|
+
|
282
|
+
if P_same:
|
283
|
+
self_str += 'P all same: ' + str(self.P[0][0]) + '\n'
|
284
|
+
else:
|
285
|
+
self_str += 'P:\n'
|
286
|
+
for i in range(self.N):
|
287
|
+
for j in range(self.M):
|
288
|
+
self_str += str(self.P[i][j]) + ' '
|
289
|
+
self_str += '\n'
|
290
|
+
|
291
|
+
return self_str
|
292
|
+
|
293
|
+
|
294
|
+
def copy(self, copy_data = True):
|
295
|
+
# return deep copy of self
|
296
|
+
# copy_data decides whether to copy data as well
|
297
|
+
if type(copy_data) != bool:
|
298
|
+
raise TypeError('Please give a bool as argument: whether to copy data or not')
|
299
|
+
|
300
|
+
sim2 = model(N = self.N, M = self.M, maxtime = self.maxtime, record_itv = self.record_itv, sim_time = self.sim_time, boundary = self.boundary,
|
301
|
+
I = np.copy(self.I), X = np.copy(self.X), P = np.copy(self.P),
|
302
|
+
print_pct = self.print_pct, seed = self.seed, check_overflow = self.check_overflow)
|
303
|
+
|
304
|
+
if copy_data:
|
305
|
+
# copy data as well
|
306
|
+
sim2.set_data(self.data_empty, self.max_record, self.compress_itv, self.U, self.V, self.Upi, self.Vpi)
|
307
|
+
|
308
|
+
return sim2
|
309
|
+
|
310
|
+
|
311
|
+
def calculate_ave(self):
|
312
|
+
# get the average value over sim_time many models
|
313
|
+
if self.sim_time != 1:
|
314
|
+
for i in range(self.N):
|
315
|
+
for j in range(self.M):
|
316
|
+
for t in range(self.max_record):
|
317
|
+
self.U[i][j][t] /= self.sim_time
|
318
|
+
self.V[i][j][t] /= self.sim_time
|
319
|
+
self.Upi[i][j][t] /= self.sim_time
|
320
|
+
self.Vpi[i][j][t] /= self.sim_time
|
321
|
+
|
322
|
+
|
323
|
+
def change_maxtime(self, maxtime):
|
324
|
+
# change maxtime
|
325
|
+
if (type(maxtime) != float) and (type(maxtime) != int):
|
326
|
+
raise TypeError('Please pass in a float or int as the new maxtime.')
|
327
|
+
if maxtime <= 0:
|
328
|
+
raise ValueError('Please use a positive maxtime.')
|
329
|
+
self.maxtime = maxtime
|
330
|
+
self.init_storage()
|
331
|
+
|
332
|
+
|
333
|
+
def set_seed(self, seed):
|
334
|
+
# set seed
|
335
|
+
self.seed = seed
|
336
|
+
|
337
|
+
|
338
|
+
def clear_data(self):
|
339
|
+
# clear all data stored, set all to 0
|
340
|
+
self.init_storage()
|
341
|
+
|
342
|
+
|
343
|
+
def set_data(self, data_empty, max_record, compress_itv, U, V, Upi, Vpi):
|
344
|
+
# set data to the given data values
|
345
|
+
# copies are made
|
346
|
+
self.check_valid_data(data_empty, max_record, compress_itv)
|
347
|
+
|
348
|
+
self.data_empty = data_empty
|
349
|
+
self.max_record = max_record
|
350
|
+
self.compress_itv = compress_itv
|
351
|
+
self.U = np.copy(U)
|
352
|
+
self.V = np.copy(V)
|
353
|
+
self.Upi = np.copy(Upi)
|
354
|
+
self.Vpi = np.copy(Vpi)
|
355
|
+
|
356
|
+
|
357
|
+
def compress_data(self, compress_itv = 5):
|
358
|
+
# compress data by only storing average values
|
359
|
+
if self.data_empty:
|
360
|
+
raise RuntimeError('Model has empty data. Cannot compress')
|
361
|
+
|
362
|
+
if type(compress_itv) != int:
|
363
|
+
raise TypeError('Please use an int as compress_itv')
|
364
|
+
if compress_itv < 1:
|
365
|
+
raise ValueError('Please use record_itv >= 1')
|
366
|
+
if compress_itv == 1:
|
367
|
+
return
|
368
|
+
|
369
|
+
self.compress_itv *= compress_itv # may be reduced over and over again
|
370
|
+
self.max_record = int(self.max_record / compress_itv) # number of data points after reducing
|
371
|
+
|
372
|
+
U_reduced = np.zeros((self.N, self.M, self.max_record), dtype = np.float64)
|
373
|
+
V_reduced = np.zeros((self.N, self.M, self.max_record), dtype = np.float64)
|
374
|
+
Upi_reduced = np.zeros((self.N, self.M, self.max_record), dtype = np.float64)
|
375
|
+
Vpi_reduced = np.zeros((self.N, self.M, self.max_record), dtype = np.float64)
|
376
|
+
|
377
|
+
for i in range(self.N):
|
378
|
+
for j in range(self.M):
|
379
|
+
for k in range(self.max_record):
|
380
|
+
lower = k * compress_itv # upper and lower bound of current record_itv
|
381
|
+
upper = lower + compress_itv
|
382
|
+
U_reduced[i][j][k] = np.mean(self.U[i, j, lower : upper])
|
383
|
+
V_reduced[i][j][k] = np.mean(self.V[i, j, lower : upper])
|
384
|
+
Upi_reduced[i][j][k] = np.mean(self.Upi[i, j, lower : upper])
|
385
|
+
Vpi_reduced[i][j][k] = np.mean(self.Vpi[i, j, lower : upper])
|
386
|
+
|
387
|
+
self.U = U_reduced
|
388
|
+
self.V = V_reduced
|
389
|
+
self.Upi = Upi_reduced
|
390
|
+
self.Vpi = Vpi_reduced
|
391
|
+
|
392
|
+
|
393
|
+
|
394
|
+
def run(mod, message = ""):
|
395
|
+
'''
|
396
|
+
C-cored simulation
|
397
|
+
'''
|
398
|
+
|
399
|
+
if not mod.data_empty:
|
400
|
+
raise ValueError('mod has non-empty data.')
|
401
|
+
|
402
|
+
msg_len = len(message)
|
403
|
+
msg_bytes = message.encode('utf-8')
|
404
|
+
msg_buffer = ctypes.create_string_buffer(msg_bytes, msg_len)
|
405
|
+
|
406
|
+
I = np.ascontiguousarray(mod.I.flatten(), dtype = np.uint32)
|
407
|
+
X = np.ascontiguousarray(mod.X.flatten(), dtype = np.float64)
|
408
|
+
P = np.ascontiguousarray(mod.P.flatten(), dtype = np.float64)
|
409
|
+
|
410
|
+
mod_c = model_c()
|
411
|
+
success = lib.mod_init(ctypes.byref(mod_c),
|
412
|
+
mod.N, mod.M, mod.maxtime, mod.record_itv, mod.sim_time, mod.boundary,
|
413
|
+
I, X, P, mod.print_pct, mod.seed)
|
414
|
+
if not success:
|
415
|
+
raise RuntimeError('mod_init failed')
|
416
|
+
|
417
|
+
lib.run(ctypes.byref(mod_c), msg_buffer, msg_len)
|
418
|
+
|
419
|
+
mod.set_data(False, mod.max_record, 1, mod_c.get_array('U1d').reshape(mod.N, mod.M, mod.max_record),
|
420
|
+
mod_c.get_array('V1d').reshape(mod.N, mod.M, mod.max_record),
|
421
|
+
mod_c.get_array('Upi_1d').reshape(mod.N, mod.M, mod.max_record),
|
422
|
+
mod_c.get_array('Vpi_1d').reshape(mod.N, mod.M, mod.max_record))
|
423
|
+
|
424
|
+
lib.mod_free_py(ctypes.byref(mod_c))
|
425
|
+
del mod_c
|
426
|
+
|
427
|
+
|
428
|
+
|
429
|
+
def demo_model():
|
430
|
+
'''
|
431
|
+
Returns a demo model.model object
|
432
|
+
'''
|
433
|
+
|
434
|
+
N = 10 # Number of rows
|
435
|
+
M = 10 # Number of cols
|
436
|
+
maxtime = 30 # how long you want the model to run
|
437
|
+
record_itv = 0.1 # how often to record data.
|
438
|
+
sim_time = 1 # repeat model to reduce randomness
|
439
|
+
boundary = True # boundary condition.
|
440
|
+
|
441
|
+
# initial population for the N x M patches.
|
442
|
+
I = [[[44, 22] for _ in range(M)] for _ in range(N)]
|
443
|
+
|
444
|
+
# flattened payoff matrices, total resource is 0.4, cost of fighting is 0.1
|
445
|
+
X = [[[-1, 4, 0, 2] for _ in range(M)] for _ in range(N)]
|
446
|
+
|
447
|
+
# patch variables
|
448
|
+
P = [[[0.5, 0.5, 1, 1, 0.001, 0.001] for _ in range(M)] for _ in range(N)]
|
449
|
+
|
450
|
+
print_pct = 25 # print progress
|
451
|
+
seed = 36 # seed for random number generation
|
452
|
+
|
453
|
+
# create a model object
|
454
|
+
mod = model(N, M, maxtime, record_itv, sim_time, boundary, I, X, P,
|
455
|
+
print_pct = print_pct, seed = seed)
|
456
|
+
|
457
|
+
return mod
|
458
|
+
|
459
|
+
|
460
|
+
|
461
|
+
def UV_expected_val(mod):
|
462
|
+
'''
|
463
|
+
Calculate expected U & V population and payoff based on matrices, assume no migration and any stochastic process.
|
464
|
+
To differentiate from UV_expected in figures.py: this one return arrays (values).
|
465
|
+
'''
|
466
|
+
|
467
|
+
U_expected = np.zeros((mod.N, mod.M)) # expected U population
|
468
|
+
V_expected = np.zeros((mod.N, mod.M)) # expected V population
|
469
|
+
pi_expected = np.zeros((mod.N, mod.M)) # expected payoff, which are equal for U and V
|
470
|
+
|
471
|
+
for i in range(mod.N):
|
472
|
+
for j in range(mod.M):
|
473
|
+
# say matrix = [a, b, c, d]
|
474
|
+
# U_proportion = (d - b) / (a - b - c + d)
|
475
|
+
U_prop = (mod.X[i][j][3] - mod.X[i][j][1]) / (mod.X[i][j][0] - mod.X[i][j][1] - mod.X[i][j][2] + mod.X[i][j][3])
|
476
|
+
# equilibrium payoff, U_payoff = V_payoff
|
477
|
+
eq_payoff = U_prop * mod.X[i][j][0] + (1 - U_prop) * mod.X[i][j][1]
|
478
|
+
|
479
|
+
# payoff / kappa * proportion
|
480
|
+
U_expected[i][j] = eq_payoff / mod.P[i][j][4] * U_prop
|
481
|
+
V_expected[i][j] = eq_payoff / mod.P[i][j][5] * (1 - U_prop)
|
482
|
+
pi_expected[i][j] = eq_payoff
|
483
|
+
|
484
|
+
return U_expected, V_expected, pi_expected
|
485
|
+
|
486
|
+
|
487
|
+
|
488
|
+
def check_overflow_func(mod):
|
489
|
+
_, _, pi_expected = UV_expected_val(mod)
|
490
|
+
for i in range(mod.N):
|
491
|
+
for j in range(mod.M):
|
492
|
+
w1_pi = pi_expected[i][j] * mod.P[i][j][2] # w1 * U_pi
|
493
|
+
w2_pi = pi_expected[i][j] * mod.P[i][j][3] # w2 * V_pi
|
494
|
+
if ((w1_pi > EXP_OVERFLOW_BOUND) or (w2_pi > EXP_OVERFLOW_BOUND)):
|
495
|
+
print("Warning: might cause overflow. \n\t w1, w2, or payoff matrix values too large")
|
496
|
+
return
|
497
|
+
if ((w1_pi > EXP_TOO_LARGE_BOUND) or (w2_pi > EXP_TOO_LARGE_BOUND)):
|
498
|
+
print("Warning: might encounter large values > 3e38 in simulation. \n\t w1, w2, or payoff matrix values too large")
|
499
|
+
return
|
500
|
+
|