Building SBML models efficiently with Antimony and CellML

Now that we know how to write an Antimony model in Tellurium and use it with SBML solver, let’s explore some built-in functionality to efficiently write and use reaction-kinetics models in Antimony and CellML [1] model definition languages. Antimony and CellML models can be be attached to simulation objects exactly like SBML models, using variations in function naming conventions corresponding to the choice in model specification (e.g., for SBML model functions add_sbml_to_cell and add_sbml_to_link there are corresponding functions add_antimony_to_cell, add_cellml_to_cell, add_antimony_to_link and add_cellml_to_link). For Antimony and CellML model specification, models are translated to SBML using the libAntimony library developed by Lucian Smith and Herbert Sauro. Furthermore, models can be specified in external files and imported by CC3D steppables or within CC3D steppables as Python multi-line strings.

Say we want to implement our previous Antimony model definition of an RK model:

$X0 -> S1 ; k1*X0;
S1 -> S2 ; k2*S1*S2^h/(10 + S2^h) + k3*S1;
S2 -> $X3 ; k4*S2;

h=2;
k1 = 1.0;
k2 = 2.0;
k3 = 0.02;
k4 = 1.0;
X0 = 1;

Now say we want to attach the Antimony model definition to a cell without leaving Twedit++, but rather all within a steppable AntimonySolverSteppable. This can be accomplished using a multi-line string using proper Antimony syntax:

class AntimonySolverSteppable(SteppableBasePy):

    def __init__(self,frequency=1):
        SteppableBasePy.__init__(self,frequency)

    def start(self):

        antimony_model_string = """model rkModel()
        $X0 -> S1 ; k1*X0;
        S1  -> S2 ; k2*S1*S2^h/(10 + S2^h) + k3*S1;
        S2  -> $X3 ; k4*S2;

        h  = 2;
        k1 = 1.0;
        k2 = 2.0;
        k3 = 0.02;
        k4 = 1.0;
        X0 = 1;
        end"""

        options = {'relative': 1e-10, 'absolute': 1e-12}
        self.set_sbml_global_options(options)
        step_size = 1e-2

        for cell in self.cell_list:
            self.add_antimony_to_cell(model_string=antimony_model_string,
                                      model_name='dp',
                                      cell=cell,
                                      step_size=step_size)

    def step(self):
        self.timestep_sbml()

Compared to attaching a SBML model to cells, we see that not much has changed procedurally: we define a model (in this case, using Antimony instead of SBML), set the requisite settings for SBML Solver, and attach our model to CC3D objects using built-in functions (in this case, using add_antimony_to_cell instead of add_sbml_to_cell). The major differences here are that we have defined our model using Antimony model specification within the steppable using built-in functions, and also that we defined our model initial conditions with Antimony. In this case, SBML Solver retrieved our initial conditions from the translation of our Antimony model, rather than from explicit arguments. Were we to also pass initial conditions to SBML Solver as an argument through add_antimony_to_cell, the explicit calls would take precedence over those declared in the Antimony model string.

Now say that we’ve obtained a shared CellML model that we’d like to use in CC3D. We can load the model specification from file:

class CellMLSolverSteppable(SteppableBasePy):

    def __init__(self,frequency=1):
        SteppableBasePy.__init__(self,frequency)

    def start(self):

        cellml_model_file = 'Simulation/shared_cellml_model.txt'

        options = {'relative': 1e-10, 'absolute': 1e-12}
        self.set_sbml_global_options(options)
        step_size = 1e-2

        self.add_cellml_to_cell_ids(model_file=cellml_model_file,
                                    model_name='dp',
                                    cell_ids=list(range(1,11)),
                                    step_size=step_size)

    def step(self):
        self.timestep_sbml()

Here the shared CellML model is stored in the same Simulation directory as the CC3D project steppables, in the file shared_cellml_model.txt. We pass the path of the model file to add_cellml_to_cell_ids to attach the shared CellML model to all cells with ids in the list list(range(1,11)), just like passing a SBML model file to add_sbml_to_cell_ids. Note that if the CellML model does not specify initial conditions, then we must explicitly pass them to add_cellml_to_cell_ids.

We see that there are two ways of passing an Antimony or CellML model to SBML Solver, either as a Python multi-line string, or as a path to a file containing the model. One of these must be accomplished, where a multi-line string is passed to the keyword argument model_string, or a path to a file is passed to the keyword argument model_file. These are true for SBML, Antimony, and CellML built-in functions.

For attaching Antimony and CellML models to links, steppables have the functions add_antimony_to_link and add_cellml_to_link. Their usage is the exact same as their cell-based counterparts, with the exception that a link is passed to the keyword argument link, rather than passing a cell to the keyword argument cell,

self.add_antimony_to_link(link=link, ...)
self.add_cellml_to_link(link=link, ...)