Skip to content

Commit bd964c6

Browse files
Merge pull request #88 from Heuro-labs/develop
Fix tau adaptive
2 parents b3fb419 + f7064c0 commit bd964c6

File tree

11 files changed

+212
-126
lines changed

11 files changed

+212
-126
lines changed

HISTORY.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@
22
History
33
=======
44

5+
0.7.1 (2019-03-23)
6+
------------------
7+
8+
Changed
9+
+++++++
10+
- Refactor ``tau_adaptive``
11+
- Rename ``direct_naive`` to ``direct``
12+
13+
Fixed
14+
+++++
15+
- SSA part of ``tau_adaptive``
16+
- Bug in linux compatibility of ``tau_adaptive``
17+
518
0.7.0 (2019-02-02)
619
------------------
720

codecov.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
coverage:
22
ignore:
3-
- "pyssa/direct_naive.py"
3+
- "pyssa/direct.py"
44
- "pyssa/utils.py"
55
- "pyssa/tau_leaping.py"
66
- "pyssa/tau_adaptive.py"

docs/tutorial.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ The results of the simulation can be retrieved by accessing the ``Results`` obje
7474

7575
.. code-block:: python
7676
77-
<Results n_rep=10 algorithm=direct_naive seed=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>
77+
<Results n_rep=10 algorithm=direct seed=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]>
7878
7979
The ``Results`` object provides abstractions for easy retrieval and iteration over the simulation results. For example you can iterate over every run of the simulation using ::
8080

@@ -112,7 +112,7 @@ Algorithms
112112

113113
The ``Simulation`` class currently supports the following algorithms:
114114

115-
1. Direct naive
115+
1. Direct
116116
2. Tau leaping
117117

118118
You can change the algorithm used to perform a simulation using the ``simulation`` flag

pyssa/direct_naive.py renamed to pyssa/direct.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
Naive implementation of the Gillespie algorithm (direct method) in Numba
2+
Implementation of the direct method.
33
"""
44

55
from typing import Tuple
@@ -11,7 +11,7 @@
1111

1212

1313
@njit(nogil=True, cache=False)
14-
def direct_naive(
14+
def direct(
1515
react_stoic: np.ndarray,
1616
prod_stoic: np.ndarray,
1717
init_state: np.ndarray,

pyssa/simulation.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
import matplotlib.pyplot as plt
1212
import matplotlib.lines as mlines
1313

14-
from .direct_naive import direct_naive
14+
from .direct import direct
1515
from .tau_leaping import tau_leaping
1616
from .tau_adaptive import tau_adaptive
1717
from .results import Results
@@ -135,7 +135,7 @@ def simulate(
135135
seed: Optional[List[int]] = None,
136136
n_rep: int = 1,
137137
n_procs: int = 1,
138-
algorithm: str = "direct_naive",
138+
algorithm: str = "direct",
139139
**kwargs,
140140
):
141141
"""
@@ -164,7 +164,7 @@ def simulate(
164164
The default value is 1
165165
algorithm : str, optional
166166
The algorithm to be used to run the simulation
167-
The default value is "direct_naive"
167+
The default value is "direct"
168168
169169
Returns
170170
-------
@@ -194,7 +194,7 @@ def simulate(
194194
raise TypeError("max_iter should be of type int")
195195

196196
algo_args = []
197-
if algorithm == "direct_naive":
197+
if algorithm == "direct":
198198
for index in range(n_rep):
199199
algo_args.append(
200200
(
@@ -209,7 +209,7 @@ def simulate(
209209
self._chem_flag,
210210
)
211211
)
212-
algo = direct_naive
212+
algo = direct
213213
elif algorithm == "tau_leaping":
214214
if "tau" in kwargs.keys():
215215
tau = kwargs["tau"]

pyssa/tau_adaptive.py

Lines changed: 116 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from numba import njit, jit
77
import numpy as np
88
from .utils import get_kstoc, roulette_selection, HIGH, TINY
9-
from .direct_naive import direct_naive
9+
from .direct import direct
1010

1111

1212
@njit(nogil=True, cache=False)
@@ -68,6 +68,102 @@ def get_HOR(react_stoic: np.ndarray):
6868
return HOR
6969

7070

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+
71167
@njit(nogil=True, cache=False)
72168
def tau_adaptive(
73169
react_stoic: np.ndarray,
@@ -159,107 +255,47 @@ def tau_adaptive(
159255

160256
M = nr
161257
N = ns
162-
L = np.zeros(M, dtype=np.int64)
163-
K = np.zeros(M, dtype=np.int64)
164258
vis = np.zeros(M, dtype=np.int64)
165259
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)
170260
skip_flag = False
171261

172262
while ite < max_iter:
173263

174264
# If negatives are not detected in step 6, perform steps 1 and 2.
175265
# Else skip to step 3.
176266
if not skip_flag:
177-
178267
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)
187271
prop_sum = np.sum(prop)
188-
if prop_sum < 1e-30:
272+
if prop_sum < TINY:
189273
status = 3
190274
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
200275

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)
244278

245279
# 3. For small taup, do SSA
246280
# -------------------------
247281
skip_flag = False
248282
if taup < 10 / prop_sum:
249-
t_ssa, x_ssa, status = direct_naive(
283+
t_ssa, x_ssa, status = direct(
250284
react_stoic,
251285
prod_stoic,
252286
x[ite - 1, :],
253287
k_det,
254288
max_t=max_t - t[ite - 1],
255-
max_iter=min(100, max_iter - ite),
289+
max_iter=min(101, max_iter - ite),
256290
volume=volume,
257291
seed=seed,
258292
chem_flag=chem_flag,
259293
)
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:]
263299
ite += len_simulation
264300
if status == 3 or status == 2:
265301
return t, x, status
@@ -274,27 +310,7 @@ def tau_adaptive(
274310

275311
# 5. Leap
276312
# -------
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)
298314

299315
# 6. Handle negatives, update and exit conditions
300316
# -----------------------------------------------
@@ -308,11 +324,11 @@ def tau_adaptive(
308324
if np.any(x_new < 0):
309325
taup = taup / 2
310326
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
316332

317333
# Exit conditions
318334
if t[ite - 1] > max_t:

pyssa/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def roulette_selection(prop_list: np.ndarray, Xt: np.ndarray) -> Tuple[int, int]
8383
choice : int
8484
Index of the chosen reaction.
8585
status : int
86-
Status of the simulation as described in `direct_naive`.
86+
Status of the simulation as described in `direct`.
8787
"""
8888
prop0 = np.sum(prop_list) # Sum of propensities
8989
# choice = 0

0 commit comments

Comments
 (0)