The Ikaros Programming Interface

Christian Balkenius

Introduction

This document describes the programming interface of the Ikaros system and contains information about how to write and run simulations with the system. Ikaros is an environment developed as a standard method for simulating system-level models of the brain. Ikaros consists of a kernel that handles initialization and scheduling, and an extensible set of modules that encapsulates an algorithm or a model of a brain region. Modules can also be used as drivers that communicates with the file system or to access hardware devices such as as cameras or robots.

Modules communicates with each other through input and output connections that are represented as one or two-dimensional arrays of floats.

Ikaros modules must be written in ANSI C++ and must be platform independent as far as this is possible. This means that modules that are models of brain areas should not contain any platform specific code although modules that interface to the file system of hardware obviously must be platform specific.

The rest of this document describes the kernel structure and how a module is coded and added to the system. A more formal documentation is also available.

Module Structure

An Ikaros module can be conceptualized as a box with a number of inputs and outputs. Each output contains an array of floats. When an output is connected to an input, the output will be copied to the input once every cycle.

A module

Writing an Ikaros Module

A module is typically described by three files. A header file that declares the class for the module, an implementation file that defines the functions and an IKC-file that declares the inputs and outputs of the module together with its parameters.

The basic declaration of a simple module that only copies a scaled version of its input to its output is illustrated below. This module will be called ExampleModule. First we need to write the header file that we will call ExampleModule.h. This file declares the class ExampleModule and starts with the declaration of a Create() function. This function always looks as this example. It is followed by the member variables of the module to hold pointers to the input and output arrays as well as the size of the input and output and the scale parameter.

After the variables comes the creator and destructor functions. The constructor just calls the default constructor Module(). Since this example does not allocate any additional data structures, the destructor is empty. We could have omitted the destructor, but it is good practice to include it anyway since it is needed if we later decide to subclass this class.

Finally, the two main functions of the module are declared. The function Init() initializes out variables and the function Tick() will be called repeatedly during execution and is used for the main work of the module.

#ifndef ExampleModule_
#define ExampleModule_

class ExampleModule: public Module
{
public:
   static Module * Create(Parameter * p)  { return new ExampleModule(p); }

   float * input;
   float * output;
   int size;
   float scale;

   ExampleModule(Parameter * p) : Module(p) {}
   virtual ˜ExampleModule () {}

   void Init();
   void Tick();
};

#endif

The #ifndef and #define in the beginning of the file are used to make sure that the header file is only included once. The trialing underscore is needed to distinguish this variable from the name of the module. By using the name of the module followed by an underscore it is possible to use search and replace on the name. Remember however that the underscore is critical.

Once the header file is in place, it is necessary to write an IKC-file for this module class. This file contains information about the inputs and outputs and possibly parameters that the module uses.

<?xml version="1.0"?>
<group>
   <input name="INPUT" />
   <output name="OUTPUT" size_set="INPUT" />

   <parameter name="scale" type="float" default="1.0" />
   
   <module class="ExampleModule" />
</group>

The IKC-file above shows the minimal description of our module. The group element is used to group the different elements that describe the module. There is one input element that describes that we have one input calles INPUT. The output element describes that we have one output called OUTPUT. The size_set attribute in the output element specifies that we want the output to have the same size as the input. If we had wanted the output to have the fixed size, we could instead have used the attribute size to set it directly. (There are several other ways to set the sizes of outputs either in the IKC-file or in the source code, but we will not discuss them here). The element parameter declares that the module has one parameter that is calles scale. The final element module associates the IKC-file with the compiled code for the class ExampleModule.

In a real IKC-file, you should add several additional elements such as a description of the module, an example, and descriptions of the different inputs, outputs and parameters.

There are six functions that can be declared in ExampleModule.cc to create a new module: (1) the creator function that defines the inputs and outputs, (2) the destructor function that deallocates memory, (3) the static function Create() that is used to create a module, (4) the function Init() that allocates memory for the module, (5) the function Tick() which does the actual work of the module, and (6) the optional function SetSizes() that can be used to set the size of outputs in code instead of using the IKC-file. For most modules, it is sufficient to declare the Init() and Tick() functions.

Module Initialization

The data structures of a module are allocated and initialized in the function Init(). The input and output vectors are already allocated by the kernel and are received from the kernel using calls to GetInputArray and GetOutputArray. It is a good idea to also let the initialization function call the functions GetInputSize and GetOutputSize and store the results in member variables. These sizes are guaranteed not to change during the execution.

The init function is also where the parameters for the module are read from the IKC-file. The preferred way to do this is to bind the member variable for the parameter to the value in the IKC-file using the Bind() function. When a variable is bound to a value in this way, it can also optionally be changed in the WebUI. (The bind method replaces the old functions GetFloatValue() etc that were used in earlier versions of Ikaros).

ExampleModule::Init()
{
   Bind(scale, "scale");
   
   input = GetInputArray("INPUT");     // Get pointer to the input vector
   output = GetOutputArray("OUTPUT");  // Get pointer to the output vector
   size = GetInputSize("INPUT");
}

Doing the actual work

The real work of a module is done in the function Tick(). When this function is called, all input vectors have been updated with new data and all the module has to do is convert the input to an output. In the example below, all the module does is to copy a scaled version of its input to the output.

ExampleModule::Tick()
{
   for(int i=0; i<size; i++)
      output[i] = scale * input[i];
}

It is often useful to use the function in the math library instead of using scalar code as in the above example. For example, the multiply function could have been used which would make the code several times faster in the following example.

ExampleModule::Tick()
{
   multiply(output, input, scale, size);
}

Adding a Module to the System

To make the Ikaros environment use a new module the file main.cc or the file UserModules/UserModules.h has to be changed to include the new module. The following header file must be included:

#include "ExampleModule.h"

In addition, the following line has to be added to the function main:

k.AddClass("ExampleModule", &ExampleModule::Create,
                "Source/UserModules/ExampleModule);

This call will bind the string "ExampleModule" to the creator function for the new module class. It is this name that is used in the IKC file that described the network structure and what modules are created during the start-up of the system.

Kernel Structure

The Ikaros kernel is responsible for the creation of the all modules and their connections at startup, the scheduling during system execution, and the propagation of data between the modules. Understanding the kernel makes it easier to write modules for the system and this section describes the main responsiblities of the kernel.

Start-up

The most important aspect of the kernel is the creation sequence that occurs when the system starts up. This happens in four steps:

1. Class Registration. When the Ikaros program starts, it first registers all code for the modules contained in the system. This initialization step builds a data structure that contains pointers to a creator function for each module type and binds it to a module class name.

2. Module Creation. When the initialization has finished, the kernel reads the supplied IKC-file (Ikaros Control File), which specifies the modules to activate and gives them instance names and other parameters. The file is in XML format and is intended to be easy to edit by hand. One instance of each module specified is created for every occurrence of that module in the control file. A module can thus have multiple instantiations with different parameters.

When a module is created its corresponding IKC file is read to see what inputs and outputs the module has. This file also describes the different parameters of the module and their default values.

3. Connections. When all modules have been created, the kernel continues to read the control file and make the specified connections between modules.

4. Module Initialization. When all modules have been connected, the initialization phase starts. At this stage, the size of the input that each module will receive is known and each module is allowed to create any additional storage that it needs and initialize variables. To do this, the kernel calls the function Init for each of the created modules.

Scheduling

The scheduling mechanism of the Ikaros system is responsible for calling the code of each module instance and for calling the data propagation routine to copy data from the outputs to the inputs of the modules. The scheduling in the basic system is entirely simplistic: the function Tick is called for each module in turn to let it read its input and calculate the output. As the second step, the data propagation function is called.

Data Propagation

The data propagation is done simultaneously for all modules. The output for each module is copied to the input to which it is connected. The propagation process is also responsible for the simple data translation that is made by the system and concatenation in the case when several outputs are connected to the same input.

blog comments powered by Disqus