jabsim
A jax-based package for simulating ODE models of biological systems,
where all variables are non-negative. This enforcement of non-negativity, which neither scipy.solve_ivp nor diffrax
solvers can give you, is why you may need jabsim.
This is achieved by clamping the state variables to zero whenever they are negative
jabsim is powered by the jax package for high-performance computing and parallelisation.
This means that jabsim simulations can be jit-compiled and parallelised on a GPU or TPU using jax.vmap or shard mapping.
How to use jabsim
- Make an ODE function which calculates the derivative $\frac{dx}{dt}$ from the arguments
t, x, parin this order.- What do the arguments stand for?
tis the time at this point in the simulationxis the state vector at this time pointargsis a tuple of extra arguments passed on to the ODE function.
- IMPORTANT: if you don't know jax, there are a few differences to keep in mind:
- If you normally use
numpyfunctions in your ODE, importjax.numpyasjnpand use it instead ofnp. jax withjax.numpywill get automatically installed as a dependency of jabsim when you install it. - Be careful using loops and if-statements. If you're an amateur programmer, just avoid doing all that. Otherwise, have a look at the JAX documentation .
- If you normally use
- What do the arguments stand for?
- Import
jabsimand calljabsim.simto simulate. The arguments are as follows:par: a list, array or dict of model parameters as in your ODE functionmodel_ode: the ODE function you createdx0: the initial state vector as a 1Dnp.arrayorjnp.arraytf: tuple only. The ODE will be simulated for time points betweentf[0]andtf[1](inclusive).savetimestep: interval between the time points at which the trajectory is savedsimulator: string specifyingthe simulation method to use"euler": Euler simulator."rk4": Runge-Kutta 4th order simulator. Slower per ODE integration step but more accurate, hence allowing larger steps for the same accuracy.
ode_steps_in_savetimestep: number of ODE integration steps within one timestep- e.g. if
savetimestep=0.5hours andode_step_in_savetimesteps=100, there will be 100 integration steps per 0.5 hour, so the ODE integration step size will be 0.5/100=0.05 hours. - high
ode_steps_in_savetimestepnumber increases accuracy but increases runtimes
- e.g. if
return_numpy: ifTrue(by default, it is) the output will be in thenp.arrayformat, otherwise it will bejnp.array.
- Running
jabsim.sim()will return the arraystsandxsasnp.arrayorjnp.array, as well as a boolean valuesuccess.ts: array of timepoints betweentf[0]andtf[1]withsavetimestephours, seconds or whatever units you are using between each two consecutive pointxs: system trajectory saved as an array at the time points ints- axis 0 for time, axis 1 for entries in the state vector (i.e.xs.shape[0]=len(ts)).success: boolean value;Trueif no entry inxsisnanorinf,Falseotherwise.
Notes
- In practice, 500 steps per hour (e.g.
savetimestep=0.5, ode_steps_in_savetimestep=250orsavetimestep=1.0, ode_steps_in_savetimestep=500) works well for the RK4 solver. For the Euler solver, 1e4 steps per hours is reasonably good. - For benchmarking, you can also set
simulator="scipy"to simulate your ODE withscipy.solve_ivp(but without any of the delicious jax features of the solvers above). In that case, don't use the argumentsode_steps_in_savetimestepandsavetimestep. Instead, you can optionally specify:solver: string describing any solver which may be used withscipy.solve_ivp. By default, we havesolver="LSODA".tols: dictionary of relative and absolute tolerances for the scipy solver. By default,tols={'rtol': 1e-6, 'atol': 1e-9}.dt0: starting integration step size. By default,dt0=0.1.
- If you want to make use of jax parallelisation, make sure to set
return_numpy=Falseso that the solver would operate withjnp.arrayobjects only.
Example
Let us integrate a simple one-dimensional ODE $\frac{dx}{dt} = a x^2$. For the initial condition $x_0=1$ and $a=0.4$,
this has the analytical solution $x = \frac{1}{1-0.4t}$. This means we can verify that for savetimestep=0.5,
jabsim.sim() produces ts=np.array([0, 0.5, 1.0]) and xs=np.array([1.0, 1.25, 1.66666667]).
All entries in xs are finite, hence success=True.
# import jabsim
import jabsim
# import jax.numpy for numpy operations
import jax.numpy as jnp
# our model ODE function returning a list of one element
def model_ode(t, x, args):
# unpack args - get the dictiory of parameters
par, = args
# use jnp to square x
# (here you could just as well use x[0]**2, we just want to make a point)
x_squared = jnp.square(x[0])
# return dx/dt as a list - with one entry for a one-dimensional ODE
return [par['a'] * x_squared]
# our dictionary of paramneters
par = {'a': 0.4}
ts, xs, success = jabsim.sim(
model_ode=model_ode,
args=(par,),
x0=jnp.array([1.0]),
tf=(0.0, 1.0),
savetimestep=0.5,
simulator='rk4',
ode_steps_in_savetimestep=10,
)
# print the timne
print(ts)
print(xs)
print(success)
Citation
If you find this package useful in your work, please cite the paper below: code for its Showcase 2 served as jabsim's direct ideological precursor.
@article{Gallup2024,
author = {Gallup, Olivia and Sechkar, Kirill and Towers, Sebastian and Steel, Harrison},
title = {Computational Synthetic Biology Enabled through JAX: A Showcase},
journal = {ACS Synth. Biol.},
volume = {13},
number = {9},
pages = {3046},
year = {2024},
doi = {10.1021/acssynbio.4c00307}
}
The original JAX package should be cited as:
@software{jax2018github,
author = {James Bradbury and Roy Frostig and Peter Hawkins and Matthew James Johnson and Yash Katariya and Chris Leary and Dougal Maclaurin and George Necula and Adam Paszke and Jake Vander{P}las and Skye Wanderman-{M}ilne and Qiao Zhang},
title = {{JAX}: composable transformations of {P}ython+{N}um{P}y programs},
url = {http://github.com/jax-ml/jax},
version = {0.3.13},
year = {2018},
}
Release files for jabsim 0.1.4
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| jabsim-0.1.4.tar.gz | 7.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| jabsim-0.1.4-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 15.5 kB
Release files / jabsim-0.1.4.tar.gz
| Download URL | jabsim-0.1.4.tar.gz |
|---|---|
| Size | 7.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e121d11d850aef631a70e09b383862962dd84fe80c3a7cbf6996fa5542e1b1fe
|
|
BLAKE2b-256 checksum How to use checksums |
ef42717d60d3b089715f4e6f54a8e285b973e8cd49b7f3d09dabf4ef299e4963
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency logRelease files / jabsim-0.1.4-py3-none-any.whl
| Download URL | jabsim-0.1.4-py3-none-any.whl |
|---|---|
| Size | 7.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e28e06dc20dbddf0343dc1ef568a1f217f4d8af24dd00503ef691c49e46b5bac
|
|
BLAKE2b-256 checksum How to use checksums |
c8367a46ca58ac88a7ae3860d187c46c2f6b894e9836d9ec9d8f0dea183d08aa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 9, 2026.
Transparency log