Antimony Tutorial

Antimony Examples

Introduction: the basics

Creating a model in Antimony is designed to be very straightforward and simple. Model elements are created and defined in text, with a simple syntax.

The most common way to use Antimony is to create a reaction network, where processes are defined wherein some elements are consumed and other elements are created. Using the language of SBML, the processes are called ‘reactions’ and the elements are called ‘species’, but any set of processes and elements may be modeled in this way. The syntax for defining a reaction in Antimony is to list the species being consumed, separated by a ‘+‘, followed by an arrow (‘->‘), followed by another list of species being created, followed by a semicolon. If this reaction has a defined mathematical rate at which this happens, that rate can be listed next:

  S1 -> S2; k1*S1

The above model defines a reaction where ‘S1‘ is converted to ‘S2‘ at a rate of ‘k1*S1‘.

This model cannot be simulated, however, because a simulator would not know what the conditions are to start the simulation. These values can be set by using an equals sign:

  S1 -> S2; k1*S1
  S1 = 10
  S2 = 0
  k1 = 0.1

The above, then, is a complete model that can be simulated by any software that understands SBML (to which Antimony models can be converted).

If you want to give your model a name, you can do that by wrapping it with the text: ‘model [name] [reactions, etc.] end‘:

# Simple UniUni reaction with first-order mass-action kinetics
model example1
  S1 -> S2; k1*S1
  S1 = 10
  S2 = 0
  k1 = 0.1
end

In subsequent examples in this tutorial, we’ll be using this syntax to name the examples, but for simple models, the name is optional. Later, when we discuss submodels, this will become more important.

There are many more complicated options in Antimony, but the above has enough power to define a wide variety of models, such as this oscillator:

model oscli
  #Reactions:
  J0:    -> S1;  J0_v0
  J1: S1 ->   ;  J1_k3*S1
  J2: S1 -> S2; (J2_k1*S1 - J2_k2*S2)*(1 + J2_c*S2^J2_q)
  J3: S2 ->   ;  J3_k2*S2

  # Species initializations:
  S1 = 0
  S2 = 1

  # Variable initializations:
  J0_v0 = 8
  J1_k3 = 0
  J2_k1 = 1
  J2_k2 = 0
  J2_c  = 1
  J2_q  = 3
  J3_k2 = 5
end

Example Antimony Scripts

Comments
Reactions
Rate Laws and Initializing Values
Boundary Species
Compartments
Assignments
Assignments in Time
Events
Function Definitions
Modular Models
Importing Files
Units
Misc


Comments

Single-line comments in Antimony can be created using the # symbol, and multi-line comments can be created by surrounding them with /* [comments] */.

/* This is an example of a multi-line
   comment for this tutorial */
model example2
  J0: S1 -> S2 + S3; k1*S1 #Mass-action kinetics
  S1 = 10  #The initial concentration of S1
  S2 = 0   #The initial concentration of S2
  S3 = 3   #The initial concentration of S3
  k1 = 0.1 #The value of the kinetic parameter from J0.
end

The names of the reaction and the model are saved in SBML, but any comments are not.


Reactions

Reactions can be created with multiple reactants and/or products, and the stoichiometries can be set by adding a number before the name of the species:

     -> S1;                 k0
  S1 -> S2;                 k1*S1
  S1 + S2 -> S3;            k2*S1*S2
  2 S1 -> S2;               k3*S1*S1
  S1 + 2 S2 -> 3 S3 + 5 S4; k4*S1*S2*S2


Rate Laws and Initializing Values

Reactions can be defined with a wide variety of rate laws

  model pathway()
    # Examples of different rate laws and initialization

    S1 -> S2; k1*S1
    S2 -> S3; k2*S2 - k3*S3
    S3 -> S4; Vm*S3/(Km + S3)
    S4 -> S5; Vm*S4^n/(Km + S4)^n

    S1 = 10
    S2 = 0
    S3 = 0
    S4 = 0
    S5 = 0
    k1 = 0.1
    k2 = 0.2
    Vm = 6.7
    Km = 1E-3
    n = 4
  end


Boundary Species

Boundary species are those species which are unaffected by the model. Usually this means they are fixed. There are two ways to declare boundary species.

1) Using a dollar sign to indicate that a particular species is fixed:

  model pathway()
    # Example of using $ to fix species

    $S1 ->  S2; k1*S1
     S2 ->  S3; k2*S2
     S3 -> $S4; k3*S3
  end

2) Using the const keyword to declare species are fixed:

  model pathway()
    # Examples of using the const keyword to fix species

    const S1, S4
    S1 -> S2; k1*S1
    S2 -> S3; k2*S2
    S3 -> S4; k3*S3
  end


Compartments

For multi-compartment models, or models where the compartment size changes over time, you can define the compartments in Antimony by using the ‘compartment’ keyword, and designate species as being in particular compartments with the ‘in’ keyword:

  model pathway()
    # Examples of different compartments

    compartment cytoplasm = 1.5, mitochondria = 2.6
    const S1 in mitochondria
    var S2 in cytoplasm
    var S3 in cytoplasm
    const S4 in cytoplasm

    S1 -> S2; k1*S1
    S2 -> S3; k2*S2
    S3 -> S4; k3*S3
  end


Assignments

You can also initialize elements with more complicated formulas than simple numbers:

  model pathway()
    # Examples of different assignments

    A = 1.2
    k1 = 2.3 + A
    k2 = sin(0.5)
    k3 = k2/k1

    S1 -> S2; k1*S1
    S2 -> S3; k2*S2
    S3 -> S4; k3*S3
  end


Assignments in Time

If you want to define some elements as changing in time, you can either define the formula a variable equals at all points in time with a :=, or you can define how a variable changes in time with X', in which case you’ll also need to define its initial starting value. The keyword ‘time’ represents time.

  model pathway()
    # Examples of assignments that change in time

    k1 := sin(time)  #  k1 will always equal the sine of time
    k2  = 0.2
    k2' = k1         #' k2 starts at 0.2, and changes according to the value
                     #   of k1: d(k2)/dt = k1

    S1 -> S2; k1*S1
    S2 -> S3; k2*S2
  end


Events

Events are discontinuities in model simulations that change the definitions of one or more symbols at the moment when certain conditions apply. The condition is expressed as a boolean formula, and the definition changes are expressed as assignments, using the keyword ‘at‘:

at (x>5): y=3, x=r+2

In a model with this event, at any moment when x transitions from being less than or equal to 5 to being greater to five, y will be assigned the value of 3, and x will be assigned the value of r+2, using whatever value r has at that moment. The following model sees the conversion of S1 to S2 until a threshold is reached, at which point the cycle is reset.

  model reset()

    S1 -> S2; k1*S1

    E1: at (S2>9): S2=0, S1=10

    S1 = 10
    S2 = 0
    k1 = 0.5
  end

For more advanced options with events, see Antimony’s full documentation.


Function Definitions

You may create user-defined functions in a similar fashion to the way you create modules, and then use these functions in Antimony equations. These functions must be basic single equations, and act in a similar manner to macro expansions. As an example, you might define the quadratic equation and use it in a later equation as follows:

function quadratic(x, a, b, c)
  a*x^2 + b*x + c
end

model quad1
  S3 := quadratic(s1, k1, k2, k3);
end

This effectively defines S3 to always equal the equation “k1*s1^2 + k2*s1 + k3”.


Modular Models

Antimony was actually originally designed to allow the modular creation of models, and has a basic syntax set up to do so. For a full discussion of Antimony modularity, see the full documentation, but at the most basic level, you define a re-usable module with the ‘model’ syntax, followed by parentheses where you define the elements you wish to expose, then import it by using the model’s name, and the local variables you want to connect to that module

# This creates a model 'side_reaction', exposing the variables 'S' and 'k1':
model side_reaction(S, k1)
  J0: S + E -> SE; k1*k2*S*E - k2*ES;
  E = 3;
  SE = E+S;
  k2 = 0.4;
end

# In this model, 'side_reaction' is imported twice:
model full_pathway
     -> S1; k1
  S1 -> S2; k2*S1
  S2 ->   ; k3*S2

  A: side_reaction(S1, k4)
  B: side_reaction(S2, k5)

  S1 = 0
  S2 = 0
  k1 = 0.3
  k2 = 2.3
  k3 = 3.5
  k4 = 0.0004
  k5 = 1

end

In this model, ‘A’ is a submodel that creates a side-reaction of S1 with A.E and A.SE, and ‘B’ is a submodel that creates a side-reaction of S2 with B.E and B.SE. It is important to note that there is no connection between A.E and B.E (nor A.ES and B.ES): they are completely different species in the model.


Importing Files

More than one file may be used to define a set of modules in Antimony through the use of the ‘import‘ keyword. At any point in the file outside of a module definition, use the word ‘import‘ followed by the name of the file in quotation marks, and Antimony will include the modules defined in that file as if they had been cut and pasted into your file at that point. SBML files may also be included in this way:

import "models1.txt"
import "oscli.xml"

model mod2()
  A: mod1();
  B: oscli();
end

In this example, the file ‘models1.txt‘ is an Antimony file that defines the module ‘mod1‘, and the file ‘oscli.xml‘ is an SBML file that defines a model named ‘oscli‘. The Antimony module ‘mod2‘ may then use modules from either or both of the other imported files.


Units

While units do not affect the mathematics of SBML or Antimony models, you can define them in Antimony for annotation purposes by using the ‘unit‘ keyword:

  unit substance = 1e-6 mole;
  unit hour = 3600 seconds;

Adding an ‘s’ to the end of a unit name to make it plural is fine when defining a unit: ‘3600 second‘ is the same as ‘3600 seconds‘. Compound units may be created by using formulas with ‘*‘, ‘/‘, and ‘^‘. However, you must use base units when doing so (‘base units’ defined as those listed in Table 2 of the SBML Level 3 Version 1 specification, which mostly are SI and SI-derived units).

  unit micromole = 10e-6 mole / liter;
  unit daily_feeding = 1 item / 86400 seconds
  unit voltage = 1000 grams * meters^2 / seconds^-3 * ampere^-1

You may use units when defining formulas using the same syntax as above: any number may be given a unit by writing the name of the unit after the number. When defining a symbol (of any numerical type: species, parameter, compartment, etc.), you can either use the same technique to give it an initial value and a unit, or you may just define its units by using the ‘has’ keyword:

  unit foo = 100 mole/5 liter;
  x = 40 foo/3 seconds; # '40' now has units of 'foo' and '3' units of 'seconds'.
  y = 3.3 foo;          # 'y' is given units of 'foo' and an initial
                        #   value of '3.3'.
  z has foo;            # 'z' is given units of 'foo'.