Skip to content

Commit

Permalink
Allow the user to choose a simulation time
Browse files Browse the repository at this point in the history
  • Loading branch information
schneiderfelipe committed Nov 28, 2020
1 parent 207354d commit 3da923f
Show file tree
Hide file tree
Showing 4 changed files with 30 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,4 +84,4 @@ We can see that the reaction went to full completion by checking the final
concentrations:

>>> y(y.t_max)
array([0.0000, 1.0000])
array([0.00, 1.00])
14 changes: 10 additions & 4 deletions overreact/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,9 @@ def _yield_kinetics(self):
# TODO(schneiderfelipe): the following is inefficient but probably OK
y0[self.model.scheme.compounds.index(name)] = quantity

y, r = api.get_y(dydt, y0=y0)
y, r = api.get_y(
dydt, y0=y0, method=self.method, rtol=self.rtol, atol=self.atol
)
conc_table = Table(
Column("no", justify="right"),
Column("compound", justify="left"),
Expand All @@ -492,9 +494,13 @@ def _yield_kinetics(self):
)
yield conc_table

# TODO(schneiderfelipe): we can get a max time now based on the
# changes through time: stop when the graph gets boring.
t = np.linspace(y.t_min, y.t_max)
eps = 1e-4
t_max = y.t_max
if np.any(r(y.t_min) > eps):
while np.all(r(t_max) < eps):
t_max *= 0.9

t = np.linspace(y.t_min, t_max)
if self.plot:
for i, name in enumerate(self.model.scheme.compounds):
if not core.is_transition_state(name):
Expand Down
24 changes: 15 additions & 9 deletions overreact/simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@
jnp = np


def get_y(dydt, y0, t_span=None, method="Radau", rtol=1e-5, atol=1e-11):
def get_y(
dydt, y0, t_span=None, method="Radau", rtol=1e-5, atol=1e-11, max_time=5 * 60
):
"""Simulate a reaction scheme from its rate function.
This uses scipy's ``solve_ivp`` under the hood.
Expand All @@ -49,6 +51,9 @@ def get_y(dydt, y0, t_span=None, method="Radau", rtol=1e-5, atol=1e-11):
normally unsuited. "Radau", "BDF" or "LSODA" are good choices.
rtol, atol : array-like
See `scipy.integrade.solve_ivp` for details.
max_time : float, optional
If `t_span` is not given, an interval will be estimated, but it can't
be larger than this parameter.
Returns
-------
Expand All @@ -71,10 +76,10 @@ def get_y(dydt, y0, t_span=None, method="Radau", rtol=1e-5, atol=1e-11):
used to produce a suitable vector of timepoints for, e.g., plotting:
>>> y.t_min, y.t_max
(0.0, 10.0)
(0.0, 5.0)
>>> t = np.linspace(y.t_min, y.t_max)
>>> t
array([ 0. , 0.20408163, ..., 9.79591837, 10. ])
array([..., 0.20408163, ..., 4.89795918, ...])
Both `y` and `r` can be used to check concentrations and rates in any
point in time. In particular, both are vectorized:
Expand All @@ -90,26 +95,27 @@ def get_y(dydt, y0, t_span=None, method="Radau", rtol=1e-5, atol=1e-11):
y0 = np.asanyarray(y0)

if t_span is None:
n_halflives = 10.0
n_halflives = 5.0 # ensure < 5% remaining material in the worst case

halflife_estimate = 1.0
if hasattr(dydt, "k"):
halflife_estimate = (
np.max(
[
np.max(y0) / 2.0, # zeroth-order half-life
np.log(2.0), # first-order half-life
1.0 / np.min(y0[np.nonzero(y0)]), # second-order half-life
np.max(y0) / 2.0, # zeroth-order halflife
np.log(2.0), # first-order halflife
1.0 / np.min(y0[np.nonzero(y0)]), # second-order halflife
]
)
/ np.min(dydt.k)
)
logger.info(f"largest halflife guess = {halflife_estimate} s")

t_span = [
0.0,
n_halflives * halflife_estimate,
min(n_halflives * halflife_estimate, max_time),
]
logger.info(f"simulation time span = {t_span} s")
logger.info(f"simulation time span = {t_span} s")

jac = None
if hasattr(dydt, "jac"):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_simulate.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def test_get_y_propagates_reaction_automatically():
y, r = simulate.get_y(simulate.get_dydt(scheme, [1.0, 1.0]), y0=y0)

assert y.t_min == 0.0
assert y.t_max == 1000.0
assert y.t_max == 300.0
assert y(y.t_min) == pytest.approx(y0)
assert y(y.t_max) == pytest.approx(
[1.668212890625, 0.6728515625, 0.341787109375], 9e-5
Expand Down Expand Up @@ -86,11 +86,11 @@ def test_get_y_conservation_in_equilibria():
t = np.linspace(y.t_min, y.t_max, num=100)

assert y.t_min == 0.0
assert y.t_max == 10.0
assert y.t_max == 5.0
assert y(y.t_min) == pytest.approx(y0)
assert y(y.t_max) == pytest.approx([0.5, 0.5], 3e-5)
assert y(y.t_max) == pytest.approx([0.5, 0.5], 5e-5)
assert r(y.t_min) == pytest.approx([-1, 1])
assert r(y.t_max) == pytest.approx([0.0, 0.0], abs=3e-5)
assert r(y.t_max) == pytest.approx([0.0, 0.0], abs=5e-5)

assert y.t_min == t[0]
assert y.t_max == t[-1]
Expand Down

0 comments on commit 3da923f

Please sign in to comment.