Skip to content

Commit 3ada40c

Browse files
authored
PySB events (#2988)
Add option to sneak general events into PySB-derived AMICI models. At least for now, this is intended only for internal use, in particular for PEtab v2 handling.
1 parent cb1f08e commit 3ada40c

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

python/sdist/amici/de_model_components.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ def get_state_update(
783783
if len(self._assignments) == 0:
784784
return None
785785

786-
x_to_x_old = dict(zip(x, x_old))
786+
x_to_x_old = dict(zip(x, x_old, strict=True))
787787

788788
def get_bolus(x_i: sp.Symbol) -> sp.Expr:
789789
"""

python/sdist/amici/pysb_import.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
SigmaY,
3636
)
3737
from .de_model import DEModel
38-
from .de_model_components import NoiseParameter, ObservableParameter
38+
from .de_model_components import Event, NoiseParameter, ObservableParameter
3939
from .import_utils import (
4040
MeasurementChannel,
4141
_default_simplify,
@@ -163,6 +163,7 @@ def pysb2amici(
163163
generate_sensitivity_code: bool = True,
164164
model_name: str | None = None,
165165
pysb_model_has_obs_and_noise: bool = False,
166+
_events: list[Event] = None,
166167
) -> amici.Model | None:
167168
r"""
168169
Generate AMICI C++ files for the provided model.
@@ -262,6 +263,7 @@ def pysb2amici(
262263
cache_simplify=cache_simplify,
263264
verbose=verbose,
264265
pysb_model_has_obs_and_noise=pysb_model_has_obs_and_noise,
266+
events=_events,
265267
)
266268
exporter = DEExporter(
267269
ode_model,
@@ -303,6 +305,7 @@ def ode_model_from_pysb_importer(
303305
verbose: int | bool = False,
304306
jax: bool = False,
305307
pysb_model_has_obs_and_noise: bool = False,
308+
events: list[Event] = None,
306309
) -> DEModel:
307310
"""
308311
Creates an :class:`amici.DEModel` instance from a :class:`pysb.Model`
@@ -360,6 +363,11 @@ def ode_model_from_pysb_importer(
360363
_process_pysb_species(model, ode)
361364
_process_pysb_parameters(model, ode, constant_parameters, jax)
362365
if compute_conservation_laws:
366+
if events:
367+
raise NotImplementedError(
368+
"Conservation law computation is not supported for models "
369+
"with events. Use `compute_conservation_laws=False`."
370+
)
363371
_process_pysb_conservation_laws(model, ode)
364372
_process_pysb_observables(
365373
model,
@@ -373,6 +381,10 @@ def ode_model_from_pysb_importer(
373381
observation_model,
374382
pysb_model_has_obs_and_noise,
375383
)
384+
385+
for event in events or []:
386+
ode.add_component(event)
387+
376388
ode._has_quadratic_nllh = all(
377389
channel.noise_distribution
378390
in ["normal", "lin-normal", "log-normal", "log10-normal"]

python/tests/test_pysb.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
import pytest
1818
import sympy as sp
1919
from amici import ParameterScaling, parameter_scaling_from_int_vector
20+
from amici.de_model_components import Event
2021
from amici.gradient_check import check_derivatives
22+
from amici.import_utils import amici_time_symbol
2123
from amici.pysb_import import pysb2amici
2224
from amici.testing import TemporaryDirectoryWinSafe, skip_on_valgrind
2325
from numpy.testing import assert_allclose
@@ -391,3 +393,47 @@ def test_energy():
391393
check_derivatives(
392394
amici_model, solver, epsilon=1e-4, rtol=1e-2, atol=1e-2
393395
)
396+
397+
398+
@skip_on_valgrind
399+
def test_pysb_event(tempdir):
400+
"""Test adding events to PySB models."""
401+
pysb.SelfExporter.cleanup() # reset pysb
402+
pysb.SelfExporter.do_export = True
403+
404+
model = pysb.Model("pysb_event_test")
405+
a = pysb.Monomer("A")
406+
pysb.Initial(a(), pysb.Parameter("a0"))
407+
pysb.Rule("deg", a() >> None, pysb.Parameter("kk", 1.0))
408+
409+
events = [
410+
Event(
411+
# note that unlike for SBML import, we must omit the real=True here
412+
identifier=sp.Symbol("event1"),
413+
name="Event1",
414+
value=amici_time_symbol - 5,
415+
assignments={sp.Symbol("__s0"): sp.Symbol("__s0") + 1000},
416+
use_values_from_trigger_time=False,
417+
)
418+
]
419+
420+
outdir = tempdir
421+
pysb2amici(
422+
model,
423+
outdir,
424+
verbose=True,
425+
observation_model=[amici.MeasurementChannel("a")],
426+
compute_conservation_laws=False,
427+
_events=events,
428+
)
429+
430+
model_module = amici.import_model_module(
431+
module_name=model.name, module_path=outdir
432+
)
433+
amici_model = model_module.get_model()
434+
assert amici_model.ne
435+
amici_model.set_timepoints([0, 4, 5])
436+
437+
np.testing.assert_allclose(
438+
amici_model.simulate().x, np.array([[0.0], [0.0], [1000.0]])
439+
)

0 commit comments

Comments
 (0)