libRoadRunner C++ API 1.3
This document describes the application programming interface (API) of RoadRunner, an open source (BSD) library for computing structural characteristics of cellular networks.
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.
Or, in C++:
To create a new integrator, one first needs to create an object that implments the Integrator interface, tell RoadRunner about it.
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:
The other key thing that an Integrator needs to do is provide a
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 implemtation file, and should contain the following keys / values
|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.
|parameterName||The default value of this tuning parmeter|
|parameterName.description||A description of this tuning parameter|
|integrator.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.
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:
Finally, the IntegratorFactory::getIntegratorOptions() method needs to be updated to also return the dictionary that was created in the getIntegratorOptions method, e.g.
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.
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:
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:
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.