6
6
from numba import njit , jit
7
7
import numpy as np
8
8
from .utils import get_kstoc , roulette_selection , HIGH , TINY
9
- from .direct_naive import direct_naive
9
+ from .direct import direct
10
10
11
11
12
12
@njit (nogil = True , cache = False )
@@ -68,6 +68,102 @@ def get_HOR(react_stoic: np.ndarray):
68
68
return HOR
69
69
70
70
71
+ @njit (nogil = True , cache = False )
72
+ def step1 (kstoc , xt , react_stoic , v , nc ):
73
+ """ Determine critical reactions """
74
+ nr = react_stoic .shape [1 ]
75
+ ns = react_stoic .shape [0 ]
76
+ prop = np .copy (kstoc )
77
+ L = np .zeros (nr , dtype = np .int64 )
78
+ # Calculate the propensities
79
+ for ind1 in range (nr ):
80
+ for ind2 in range (ns ):
81
+ # prop = kstoc * product of (number raised to order)
82
+ prop [ind1 ] *= np .power (xt [ind2 ], react_stoic [ind2 , ind1 ])
83
+ for ind in range (nr ):
84
+ vis = v [:, ind ]
85
+ L [ind ] = np .nanmin (xt [vis < 0 ] / np .abs (vis [vis < 0 ]))
86
+ # A reaction j is critical if Lj <nc. However criticality is
87
+ # considered only for reactions with propensity greater than
88
+ # 0 (`prop > 0`).
89
+ crit = (L < nc ) * (prop > 0 )
90
+ # To get the non-critical reactions, we use the bitwise not operator.
91
+ not_crit = ~ crit
92
+ return prop , crit , not_crit
93
+
94
+
95
+ @njit (nogil = True , cache = False )
96
+ def step2 (not_crit , react_species , v , xt , HOR , prop , epsilon ):
97
+ """ 2. Generate candidate taup """
98
+ n_react_species = react_species .shape [0 ]
99
+ mup = np .zeros (n_react_species , dtype = np .float64 )
100
+ sigp = np .zeros (n_react_species , dtype = np .float64 )
101
+ tau_num = np .zeros (n_react_species , dtype = np .float64 )
102
+ if np .sum (not_crit ) == 0 :
103
+ taup = HIGH
104
+ else :
105
+ # Compute mu from eqn 32a and sig from eqn 32b
106
+ for ind , species_index in enumerate (react_species ):
107
+ this_v = v [species_index , :]
108
+ for i in range (len (not_crit )):
109
+ if not_crit [i ]:
110
+ mup [ind ] += this_v [i ] * prop [i ]
111
+ sigp [ind ] += this_v [i ] * prop [i ] * this_v [i ]
112
+ if mup [ind ] == 0 :
113
+ mup [ind ] = TINY
114
+ if sigp [ind ] == 0 :
115
+ sigp [ind ] = TINY
116
+ if HOR [species_index ] > 0 :
117
+ g = HOR [species_index ]
118
+ elif HOR [species_index ] == - 2 :
119
+ if xt [species_index ] is not 1 :
120
+ g = 1 + 2 / (xt [species_index ] - 1 )
121
+ else :
122
+ g = 2
123
+ elif HOR [species_index ] == - 3 :
124
+ if xt [species_index ] not in [1 , 2 ]:
125
+ g = 3 + 1 / (xt [species_index ] - 1 ) + 2 / (xt [species_index ] - 2 )
126
+ else :
127
+ g = 3
128
+ elif HOR [species_index ] == - 32 :
129
+ if xt [species_index ] is not 1 :
130
+ g = 3 / 2 * (2 + 1 / (xt [species_index ] - 1 ))
131
+ else :
132
+ g = 3
133
+ tau_num [ind ] = max (epsilon * xt [species_index ] / g , 1 )
134
+ taup = np .nanmin (
135
+ np .concatenate ((tau_num / np .abs (mup ), np .power (tau_num , 2 ) / np .abs (sigp )))
136
+ )
137
+ return taup
138
+
139
+
140
+ @njit (nogil = True , cache = False )
141
+ def step5 (taup , taupp , nr , not_crit , prop , xt ):
142
+ K = np .zeros (nr , dtype = np .int64 )
143
+ if taup < taupp :
144
+ tau = taup
145
+ for ind in range (nr ):
146
+ if not_crit [ind ]:
147
+ K [ind ] = np .random .poisson (prop [ind ] * tau )
148
+ else :
149
+ K [ind ] = 0
150
+ else :
151
+ tau = taupp
152
+ # Identify the only critical reaction to fire
153
+ # Send in xt to match signature of roulette_selection
154
+ temp = prop .copy ()
155
+ temp [not_crit ] = 0
156
+ j_crit , _ = roulette_selection (temp , xt )
157
+ for ind in range (nr ):
158
+ if not_crit [ind ]:
159
+ K [ind ] = np .random .poisson (prop [ind ] * tau )
160
+ elif ind == j_crit :
161
+ K [ind ] = 1
162
+ else :
163
+ K [ind ] = 0
164
+ return tau , K
165
+
166
+
71
167
@njit (nogil = True , cache = False )
72
168
def tau_adaptive (
73
169
react_stoic : np .ndarray ,
@@ -159,107 +255,47 @@ def tau_adaptive(
159
255
160
256
M = nr
161
257
N = ns
162
- L = np .zeros (M , dtype = np .int64 )
163
- K = np .zeros (M , dtype = np .int64 )
164
258
vis = np .zeros (M , dtype = np .int64 )
165
259
react_species = np .where (np .sum (react_stoic , axis = 1 ) > 0 )[0 ]
166
- n_react_species = react_species .shape [0 ]
167
- mup = np .zeros (n_react_species , dtype = np .float64 )
168
- sigp = np .zeros (n_react_species , dtype = np .float64 )
169
- tau_num = np .zeros (n_react_species , dtype = np .float64 )
170
260
skip_flag = False
171
261
172
262
while ite < max_iter :
173
263
174
264
# If negatives are not detected in step 6, perform steps 1 and 2.
175
265
# Else skip to step 3.
176
266
if not skip_flag :
177
-
178
267
xt = x [ite - 1 , :]
179
- # 1. Determine critical reactions
180
- # -------------------------------
181
- # Calculate the propensities
182
- prop = np .copy (kstoc )
183
- for ind1 in range (nr ):
184
- for ind2 in range (ns ):
185
- # prop = kstoc * product of (number raised to order)
186
- prop [ind1 ] *= np .power (x [ite - 1 , ind2 ], react_stoic [ind2 , ind1 ])
268
+
269
+ # Step 1:
270
+ prop , crit , not_crit = step1 (kstoc , xt , react_stoic , v , nc )
187
271
prop_sum = np .sum (prop )
188
- if prop_sum < 1e-30 :
272
+ if prop_sum < TINY :
189
273
status = 3
190
274
return t [:ite ], x [:ite , :], status
191
- for ind in range (M ):
192
- vis = v [:, ind ]
193
- L [ind ] = np .nanmin (xt [vis < 0 ] / np .abs (vis [vis < 0 ]))
194
- # A reaction j is critical if Lj <nc. However criticality is
195
- # considered only for reactions with propensity greater than
196
- # 0 (`prop > 0`).
197
- crit = (L < nc ) * (prop > 0 )
198
- # To get the non-critical reactions, we use the bitwise not operator.
199
- not_crit = ~ crit
200
275
201
- # 2. Generate candidate taup
202
- # --------------------------
203
- if np .sum (not_crit ) == 0 :
204
- taup = HIGH
205
- else :
206
- # Compute mu from eqn 32a and sig from eqn 32b
207
- for ind , species_index in enumerate (react_species ):
208
- this_v = v [species_index , :]
209
- for i in range (len (not_crit )):
210
- if not_crit [i ]:
211
- mup [ind ] += this_v [i ] * prop [i ]
212
- sigp [ind ] += this_v [i ] * prop [i ] * this_v [i ]
213
- if mup [ind ] == 0 :
214
- mup [ind ] = TINY
215
- if sigp [ind ] == 0 :
216
- sigp [ind ] = TINY
217
- if HOR [species_index ] > 0 :
218
- g = HOR [species_index ]
219
- elif HOR [species_index ] == - 2 :
220
- if xt [species_index ] is not 1 :
221
- g = 1 + 2 / (xt [species_index ] - 1 )
222
- else :
223
- g = 2
224
- elif HOR [species_index ] == - 3 :
225
- if xt [species_index ] not in [1 , 2 ]:
226
- g = (
227
- 3
228
- + 1 / (xt [species_index ] - 1 )
229
- + 2 / (xt [species_index ] - 2 )
230
- )
231
- else :
232
- g = 3
233
- elif HOR [species_index ] == - 32 :
234
- if xt [species_index ] is not 1 :
235
- g = 3 / 2 * (2 + 1 / (xt [species_index ] - 1 ))
236
- else :
237
- g = 3
238
- tau_num [ind ] = max (epsilon * xt [species_index ] / g , 1 )
239
- taup = np .nanmin (
240
- np .concatenate (
241
- (tau_num / np .abs (mup ), np .power (tau_num , 2 ) / np .abs (sigp ))
242
- )
243
- )
276
+ # Step 2:
277
+ taup = step2 (not_crit , react_species , v , xt , HOR , prop , epsilon )
244
278
245
279
# 3. For small taup, do SSA
246
280
# -------------------------
247
281
skip_flag = False
248
282
if taup < 10 / prop_sum :
249
- t_ssa , x_ssa , status = direct_naive (
283
+ t_ssa , x_ssa , status = direct (
250
284
react_stoic ,
251
285
prod_stoic ,
252
286
x [ite - 1 , :],
253
287
k_det ,
254
288
max_t = max_t - t [ite - 1 ],
255
- max_iter = min (100 , max_iter - ite ),
289
+ max_iter = min (101 , max_iter - ite ),
256
290
volume = volume ,
257
291
seed = seed ,
258
292
chem_flag = chem_flag ,
259
293
)
260
- len_simulation = len (t_ssa )
261
- t [ite : ite + len_simulation ] = t_ssa + t [ite - 1 ]
262
- x [ite : ite + len_simulation , :] = x_ssa
294
+ # t_ssa first element is 0. x_ssa first element is x[ite - 1, :].
295
+ # Both should be dropped while logging the results.
296
+ len_simulation = len (t_ssa ) - 1 # Since t_ssa[0] is 0
297
+ t [ite : ite + len_simulation ] = t_ssa [1 :] + t [ite - 1 ]
298
+ x [ite : ite + len_simulation , :] = x_ssa [1 :]
263
299
ite += len_simulation
264
300
if status == 3 or status == 2 :
265
301
return t , x , status
@@ -274,27 +310,7 @@ def tau_adaptive(
274
310
275
311
# 5. Leap
276
312
# -------
277
- if taup < taupp :
278
- tau = taup
279
- for ind in range (M ):
280
- if not_crit [ind ]:
281
- K [ind ] = np .random .poisson (prop [ind ] * tau )
282
- else :
283
- K [ind ] = 0
284
- else :
285
- tau = taupp
286
- # Identify the only critical reaction to fire
287
- # Send in xt to match signature of roulette_selection
288
- temp = prop .copy ()
289
- temp [not_crit ] = 0
290
- j_crit , _ = roulette_selection (temp , x [ite - 1 , :])
291
- for ind in range (M ):
292
- if not_crit [ind ]:
293
- K [ind ] = np .random .poisson (prop [ind ] * tau )
294
- elif ind == j_crit :
295
- K [ind ] = 1
296
- else :
297
- K [ind ] = 0
313
+ tau , K = step5 (taup , taupp , nr , not_crit , prop , xt )
298
314
299
315
# 6. Handle negatives, update and exit conditions
300
316
# -----------------------------------------------
@@ -308,11 +324,11 @@ def tau_adaptive(
308
324
if np .any (x_new < 0 ):
309
325
taup = taup / 2
310
326
skip_flag = True
311
-
312
- # Update states if nothing is negative
313
- x [ite , :] = x [ite - 1 , :] + vdotK
314
- t [ite ] = t [ite - 1 ] + tau
315
- ite += 1
327
+ else :
328
+ # Update states if nothing is negative
329
+ x [ite , :] = x [ite - 1 , :] + vdotK
330
+ t [ite ] = t [ite - 1 ] + tau
331
+ ite += 1
316
332
317
333
# Exit conditions
318
334
if t [ite - 1 ] > max_t :
0 commit comments