The LibRoadRunner C++ Library
==============================
.. toctree::
:hidden:
:maxdepth: 1
llvm/index.rst
CVODEIntegrator.rst
Dictionary.rst
EulerIntegrator.rst
ExecutableModelFactory.rst
GillespieIntegrator.rst
Integrator.rst
IntegratorFactory.rst
NLEQ1Solver.rst
NLEQ2Solver.rst
RK45Integrator.rst
RK4Integrator.rst
SBMLValidator.rst
Solver.rst
SolverFactory.rst
SteadyStateSolver.rst
Variant.rst
rrCompiler.rst
rrConfig.rst
rrException.rst
rrExecutableModel.rst
rrFileName.rst
rrIniFile.rst
rrIniKey.rst
rrIniSection.rst
rrLogger.rst
rrNLEQ1Interface.rst
rrNLEQ2Interface.rst
rrRoadRunner.rst
rrRoadRunnerData.rst
rrRoadRunnerOptions.rst
rrSBMLModelSimulation.rst
rrSBMLReader.rst
rrSelectionRecord.rst
rrSparse.rst
RoadRunnerMap.rst
Introduction
--------------
This document describes the application programming interface (wrappers) of RoadRunner,
an open source (BSD) library for computing structural characteristics of cellular networks.
Creating a new Integrator
-------------------------
One of the key design goals of the LibRoadRunner library is extensibility. This means that the
library is designed with the idea that most internal components are loosely coupled and that
it is simple to add new solvers such as new steady state solvers or integrators. This section
will give a tutorial on creating a new integrator using the EulerIntegrator as an example.
At its simplest, an Integrator is a class which implements the Integrator interface and
is responsible for advanding a model (an object which implements the ExecutableModel interface)
forward in time.
All Integrators are created by the IntegratorFactory class, this is the only class that knows
about the existence of any Integrator objects. All integrators that the IntegratorFactory knows about
are automatically available to any code (including any Python code) that needs to create one. Each time
the RoadRunner::simulate method is called, a different integrator may be specified. In Python, this is
done with the integrator keyword, i.e.
.. code-block: python
rr.simulate(0, 10, 100, integrator="MyIntegratorName")
Or, in C++:
.. code-block:: C++
BasicDictionary d;
d.setItem("integrator", "MyIntegratorName")
rr.simulate(&d);
To create a new integrator, one first needs to create an object that implments the Integrator interface,
tell RoadRunner about it.
Implementing the Integrator interface
--------------------------------------
The IntegratorFactory is the ONLY object that creates integrators.
Integrators are created when the IntegratorFactory::New method is called,
typically by the top level RoadRunner object. New Integrators are given a pointer to an
existing ExecutableModel object which the Integrator is responsible for advancing forward in time,
and pointer to a SimulateOptions object which contains the initial set of parameters that the
Integrator may configure itself with.
The integrator will hold onto the ExecutableModel pointer, m, and when the
Integrator::integrate method is called, will advance the model object forward in time.
There are three key members of the Integrator interface that an integrator needs be implement:
* Integrator::setSimulateOptions
This method is called whenever the simulation parameters are changed via calls to the
RoadRunner::simulate method, the setSimulateOptions method is
called by RoadRunner object to inform the Integrator of any new parameter changes.
This method should be used to read any updated tuning parameters.
* Integrator::integrate
This is the actual method that should perform the time integration.
* Integrator::restart
This method is called whenever the RoadRunner object is re-started. This gives the
integrator an option to reload any internal solvers. Simple integrators like the EulerIntegrator does not
really do anything in the EulerIntegrator::restart method, but more sophisticated ones like the CVODE integrator
perform a number of tasks such as re-calclating the tollerances and so forth.
The other key thing that an Integrator needs to do is provide a
.. code-block: C++
static const Dictionary* getIntegratorOptions();
method, as in the EulerIntegrator::getIntegratorOptions. This method is used by the IntegratorFactory
to build a list of all the available tuning parameters that any integrator supports. The returned
Dictionary pointer should be statically created inside the implementation file, and should contain the
following keys / values
+-----------------------+----------------------------------------------------+
| Key | Value |
+=======================+====================================================+
| integrator | The name of your new integrator |
+-----------------------+----------------------------------------------------+
| integrator.description| A description of your new integrator |
+-----------------------+----------------------------------------------------+
| integrator.hint | A short hint for your new integrator |
+-----------------------+----------------------------------------------------+
Any additional tuning parameters should be listed in this dictionary, where each tuning parameter
should have three key/value pairs.
+---------------------------+----------------------------------------------------+
| Key | Value |
+===========================+====================================================+
| parameterName | The default value of this tuning parmeter |
+---------------------------+----------------------------------------------------+
| parameterName.description | A description of this tuning parameter |
+---------------------------+----------------------------------------------------+
| parameterName.hint | A short hint for this tuning parameter |
+---------------------------+----------------------------------------------------+
When the Integrator::setSimulateOptions method is called, the integrator should read any
parameters it expects out of the given dictionary.
Telling RoadRunner about the new Integrator
--------------------------------------------
In order for the RoadRunner::simulate method use the new integrator, it needs to know about it.
The IntegratorFactory is the only object that knows about all the integrators, and a few lines of
code need to be added in the implementation of this object so that it can construct one.
First, a new enum value needs to be added to the Integrator::IntegratorId enum. This is numeric
index of the integrator. Then the textual name of the integrator needs to be added to the
integratorNames static array in the Integrator.cpp file. Then a line needs to be added inside
the IntegratorFactory::New method which will create the new integrator, e.g. this is a series
of if statements, and a new statment needs to be created which creates an instance of the
new kind of integrator:
.. code-block::
if (opt->integrator == Integrator::GILLESPIE)
{
result = new GillespieIntegrator(m, opt);
}
else if(opt->integrator == Integrator::RK4)
{
result = new RK4Integrator(m, opt);
}
else if(opt->integrator == Integrator::EULER)
{
result = new EulerIntegrator(m, opt);
}
else
{
result = new CVODEIntegrator(m, opt);
}
Finally, the IntegratorFactory::getIntegratorOptions() method needs to be updated to also return
the dictionary that was created in the getIntegratorOptions method, e.g.
.. code-block: C++
const Dictionary* options[] = {
CVODEIntegrator::getIntegratorOptions(),
GillespieIntegrator::getIntegratorOptions(),
RK4Integrator::getIntegratorOptions(),
EulerIntegrator::getIntegratorOptions()
};
Once the IntegratorFactory is made aware of your new integrator, it is available for full introspection and
can be used by just adding the integrator="myNewIntegrator" argument whenever the
RoadRunner.simulate method is called. The EulerIntegrator was created as an example of how to create and add
a new integrator, have a look at it.
A complete example of creating an integrator
---------------------------------------------
This section includes the complete Euler integrator implemented in EulerIntegrator.h as an example of
creating a new integrator.
This class has two demo paramters which may be set via the keyword arguments to RoadRunner.simulate
in Python, or set via the Dictionary::setItem method on the dictionary that is given to the
RoadRunner::simulate method in C++. In Python, this would be:
.. code-block:: python
rr.simulate(integrator='euler', exampleParameter1=123456, exampleParameter2='some value');
print(rr.integrator)
.. code-block:
:caption: Output:
< roadrunner.EulerIntegrator() {
'this' : 0x101f28350
'exampleParameter1' : 123456
'exampleParameter2' : some value
}>
In Python, all the keyword arguments to the simulate method are packaged up and added to the
dictionary which is passed into the RoadRunner::simulate method. In C++, the equivalent code
would be:
.. code:block: C++
SimulateOptions& opt = rr.getSimulateOptions();
opt.setItem("integrator", "euler")
opt.setItem("exampleParameter1", 123456);
opt.setItem("exampleParameter2", "some value");
rr.simulate();
cout << rr.getIntegrator()->toString() << endl;
The EulerIntegrator.h file serves as a complete example of creating an new integrator.
This example was written entierly in the header file for clarity, but a real integrator
should separate the code and header files.