The Ikaros Programming Interface

Christian Balkenius

This version of this article describes Ikaros version 1.0. A newer version of this article that describes version 1.2 of Ikaros is also available.

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 each is assumed to correspond to a region of the brain. 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.

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.

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 control file (IKC), 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 each module is created, it registers its inputs and outputs in the kernel using the functions AddInput and AddOutput.

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.

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

The basic declaration of a simple module is illustrated below. The only function of this module is to copy its input to its output.

class ExampleModule: public Module
{
public:
   ExampleModule(char * name, Parameter * p);

   virtual ˜ExampleModule ();

   static Module * Create(char * name, Parameter * p);

   void Init();
   void Tick();

   int		no_of_inputs;
   int		no_of_outputs;

   float 	*	input;
   float	*	output;
};

There are five functions that must be declared to create a new module. The creator function that defines the inputs and outputs, the destructor function that deallocates memory, the static function Create that is used to create a module, the function Init that allocates memory for the module, and finally the function Tick which does the actual work of the module.

In the example, there are two variables to hold the size of the input and the output and two variables for the actual input and output data.

Module Creator

A sample creator function is shown in the figure below. The creator function must declare which variables are used for input and output.

ExampleModule::ExampleModule(char * name, Parameter * p):
   Module(p)
{
   AddInput("INPUT");
   AddOutput("OUTPUT", 4);
}

In addition, a static function must be defined that is to be used to create new instances of the module class. This function has the following structure:

Module *
ExampleModule:: ExampleModule (char * name, Parameter * p)
{
   return new ExampleModule (name, p);
}

This static function is only used to give a name to the function that creates a module and simplify the interface to the kernel.

Module Initialization

The data structures of a module is 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 GetInputData and GetOutputData. It is a good idea to also let the initialization function call the functions GetInputSize and GetOutputSize once and for all and store the results in member variables. These sizes are guaranteed not to change during the simulation.

ExampleModule::Init()
{
   no_of_inputs = GetInputSize("INPUT");
   no_of_outputs = GetOutputSize("OUTPUT");

   input = GetInputArray("INPUT");     // Get pointer to the input vector
   output = GetOutputArray("OUTPUT");  // Get pointer to the output vector

   // Allocate additional memory here
}

It would be a good idea to include code to test that the size of the input is the same as the size of the output since the module will later copy the input to the output.

Module Destruction

The destructor for each module should deallocate any additional memory it has allocated in the Init function. For the simple example here, this function need not do anything. However, do not forget that in C++ a class with virtual members must have a virtual destructor. The compiler may not complain if you forget it, but strange and dangerous thing will happen in your code.

ExampleModule::˜ExampleModule()
{
   // Deallocate memory allocated in Init()
   // Do NOT deallocate the input and output vectors.
   // This is done automatically.
}

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 copy its input to the output.

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

Hopefully, most modules written for the Ikaros system will be more interesting.

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.

Finally, you need to write an IKC file that documents the featurs of the module. A minimal IKC file could look something like this:

<?xml version="1.0"?>
<group name="ExampleModule">
   <input name="INPUT" />
   <output name="OUTPUT" />
   <module class="ExampleModule" />
</group>

The IKC file for a module should also include all documentation for the module including a description of what it does and the role of each input and output.