-
Notifications
You must be signed in to change notification settings - Fork 0
/
clump_walk_loop_exe.py
319 lines (233 loc) · 8.49 KB
/
clump_walk_loop_exe.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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
'''
Executable file for running clump walk on cluster.
Jordon D Hemingway
Updated: 15 Feb. 2021
Inputs:
csv_filename:
Name of csv file containing all input values:
nC = size of carbon grid
nO = number of 18O atoms to seed per iteration
nt = number of time steps
niter = number of iterations
D0 = initial D47 value, in permil
Deq = equilibrium D47 value, in permil
gam = power for relationship between jump probability and O-O distance
p = probability of a jump per time step
mineral = calcite or aragonite; defines O-O connectivity
'''
#import packages
import csv
# import matplotlib.pyplot as plt
import numpy as np
import os
import sys
#system inputs for executable
incsv = str(sys.argv[1])
#get cwd
cwd = os.getcwd()
#make dictionary of input values from csv file
csvpath = cwd+'/'+incsv+'.csv'
with open(csvpath, mode = 'r') as infile:
reader = csv.reader(infile)
indict = {row[0]:row[1] for row in reader}
#pop out into individual input variables
nC = int(indict.pop('nC'))
nO = int(indict.pop('nO'))
nt = int(indict.pop('nt'))
niter = int(indict.pop('niter'))
D0 = float(indict.pop('D0'))
Deq = float(indict.pop('Deq'))
gam = int(indict.pop('gam'))
p = float(indict.pop('p'))
mineral = str(indict.pop('mineral'))
#set distances based on mineralogy
if mineral in ['calcite','Calcite']:
# d = np.array([3.63, 3.80, 4.05, 4.65, 4.47]) #calcite O-O distances
#calcite O-O distances; values from Zolotoyabko et al. (2009) Mat. Sci. Eng. A
d = np.array([3.189, 3.260, 3.411])
elif mineral in ['aragonite','Aragonite']:
#aragonite O-O distances; values from Zolotoyabko et al. (2009) Mat. Sci. Eng. A
d = np.array([2.977, 3.069, 3.080, 3.225, 3.449])
elif mineral in ['dolomite','Dolomite']:
#dolomite O-O distances;
# Eugui Dolomite lattice parameter values from Reeder and Sheppard (1984) Am. Min.
d = np.array([3.007, 3.142, 3.226])
else:
raise ValueError('value of mineral must be calcite, aragonite, or dolomite!')
nd = len(d)
nj = 2*nd+1 #total number of possible jump positions
#input isotope abundances
f13C = 0.01109
#probability scaling factor for making a clump relative to stochastic
pC0 = D0/1000 + 1
pCeq = Deq/1000 + 1
#pre-allocate arrays and datatypes (alphabetical order):
#bin right-hand limits for sorting onto C grid
bins = np.zeros(nC+1, dtype = float)
#13C probability random numbers
c = np.zeros(nC, dtype = int)
#indices of 13C atoms; include buffer length
c13it = np.zeros(int(2*f13C*nC), dtype = int)
#scaling factor matrix for nearest neighbor 13C atoms
c13nn = np.zeros([nO, nj], dtype = float)
#probabilities of containing a 13C
c13r = np.zeros(nC, dtype = float)
#containiner for clump boolean for each iteration
clump = np.zeros([nO, nt], dtype = bool)
#stor total number of clumps for each iteration
Ct = np.zeros([niter, nt], dtype = int)
#final delta values
D = np.zeros([niter, nt], dtype = float)
#delta position jumps for each 18O atom
dpos = np.zeros(nO, dtype = int)
#final D values, converted to G, the fraction of reaction remaining
G = np.zeros([niter, nt], dtype = float)
#index matrix of possible delta positions
I = np.zeros([nO, nj], dtype = int)
#indicies where jump is possible
jind = np.zeros([nO, nj], dtype = int)
#probability of making a jump for each 18O atom
jump_prob = np.zeros(nO, dtype = float)
#baseline jump probabilities
mb = np.zeros([nO, nj], dtype = float)
#jump probabilities scaled to account for neighboring 13C atoms
msc = np.zeros([nO, nj], dtype = float)
#cumsum of msc
msccs = np.zeros([nO, nj], dtype = float)
#current and nearest neighbor positions
mpos = np.zeros([nO, nj], dtype = int)
#nearest neighbor operator matrix
N = np.zeros([nO, nj], dtype = int)
#new positions at t+dt
newpos = np.zeros(nO, dtype = int)
#ones array of nearest neighbors
nnones = np.ones([nO, nj], dtype = int)
#sum for normalizing probability jumps to (denom for rescaling)
normsum = np.zeros(nO, dtype = float)
#initial 18O positions on the C grid
o0 = np.zeros(nO, dtype = int)
#probabilities of initial 18O positions
o0r = np.zeros(nO, dtype = float)
#18O positions on the C grid
o18pos = np.zeros([nO, nt], dtype = int)
#probabilities of initial 18O placing
po0 = np.zeros(nC, dtype = float)
#movement probabilities
pm = np.zeros([nO, nt], dtype = float)
#all 18O positions on the C grid
pos = np.zeros(nO, dtype = int)
#-------------------------#
# 1. pre-allocate things #
#-------------------------#
#seed the base movement probability matrix
#convert d to probabilities
scf = (p/2)/np.sum(1/d**gam)
qi = scf/d**gam
#append left, no move, and right jumps
pjump = np.append(np.append(qi[::-1],1-p),qi)
nj = len(pjump)
#make baseline array of jump probabilities (to be udpated by neighbor 13C)
mb[:,:] = pjump*nnones
#make a nearest neighbor operator matrix
for n in range(nd+1):
N[:,nd+n] = n
N[:,nd-n] = -n
#--------------------------------#
# 2. loop through each iteration #
#--------------------------------#
for k in range(niter):
#---------------------#
# 2a. seed the C grids #
#---------------------#
#seed the C grid; shape [niter x nC]
c13r[:] = np.random.uniform(size = nC)
c[:] = np.where(c13r<=f13C, 1, 0)
#reset c13it for each iteration
c13it[:] = -999*np.ones(int(2*f13C*nC))
#fill c13it with positions containing a 13C
temp = np.where(c==1)[0]
c13it[:len(temp)] = temp
#----------------------------#
# 2b. seed the 18O positions #
#----------------------------#
#generate start position probabilities and store to o0r
o0r[:] = np.random.uniform(size = nO)
po0[:] = c*pC0
po0[po0 == 0] = 1
po0[:] = po0/np.sum(po0)
#sort each o entry into a bin based on probability
bins[1:] = np.cumsum(po0)
o0[:] = np.digitize(o0r, bins) - 1 #get back to python indexing
#------------------------------------------------------#
# 2c. loop through time steps and update 18O positions #
#------------------------------------------------------#
#store initial positions
o18pos[:,0] = o0
#pre-allocate 18O movement probabilities
pm[:,:] = np.random.uniform(size = [nO, nt])
#pre-allocate 3d matrix of current 18O and nearest neighbor positions
# nnones[:,:,:] = np.ones([niter, nO, nj])
#update positions for next time step
for i in range(nt-1):
#get current positions and jump probabilities
pos[:] = o18pos[:,i]
jump_prob[:] = pm[:,i]
#make matrix of current and nearest neighbor positions, adding nearest neighbor
# operator matrix
mpos[:,:] = np.outer(pos, np.ones(nj, dtype = int)) + N
mpos[mpos<0] = mpos[mpos<0] + nC #loop around when off the grid, left
mpos[mpos>=nC] = mpos[mpos>=nC] - nC #loop around when off the grid, right
#make matrix of the nearest neighbor positions occupied by 13C
# increase the probability of moving to a 13C-occupied site by a factor
# that is proportional to D47_eq
c13nn[:,:] = np.isin(mpos, c13it)*pCeq
c13nn[c13nn == 0] = 1 #fill 12C sites with unity
#scale the movement probability matrix accordingly
msc[:,:] = mb*c13nn
normsum[:] = msc.sum(axis=1)
msc[:,:] = np.divide(msc, np.outer(normsum, np.ones(nj)))
#make cumulative sum version
msccs[:,:] = np.cumsum(msc, axis=1)
#get indices where msccs >= jump probability
jind[:,:] = msccs >= np.outer(jump_prob, np.ones(nj))
#reset index matrix and set values not in jind to something very large
I[:,:] = np.arange(-nd,nd+1)*nnones
I[jind==0] = 999
#calculate delta position jumps as minimum of each row in I
dpos[:] = I[:,:].min(axis=1)
#update jumps
newpos[:] = pos + dpos
newpos[newpos<0] = newpos[newpos<0] + nC #loop around when off the grid, left
newpos[newpos>=nC] = newpos[newpos>=nC] - nC
#store results
o18pos[:,i+1] = newpos
#---------------------------------------------------------#
# 2d. calculate which 18O atoms are on a 13C through time #
#---------------------------------------------------------#
#make index of clump boolean
clump[:,:] = np.isin(o18pos, c13it)
#store number of clumps in overall array
Ct[k,:] = np.sum(clump, axis = 0)
#------------------#
# 2e. convert to D #
#------------------#
#convert to D47 and store
Rs = np.sum(c)/nC
R = Ct[k,:]/nO
D[k,:] = (R/Rs - 1)*1000
#---------------------------------#
# 3. calculate summary statistics #
#---------------------------------#
#save D47 average and std dev
Dm = np.mean(D, axis = 0)
Ds = np.std(D, axis = 0)
#convert to G
G = (D - Deq)/(D0-Deq)
Gm = np.mean(G, axis = 0)
Gs = np.std(G, axis = 0)
res = np.array([Dm,Ds,Gm,Gs])
#---------------------#
# 6. spit out results #
#---------------------#
filename = cwd +'/D_'+str(niter)+'nit_'+str(nO)+'nO_'+str(nC)+'nC_'+str(D0)+'D0_'+str(Deq)+'Deq.csv'
np.savetxt(filename, res, delimiter = ',')