How to Write an Ikaros Module
To use the Ikaros simulator, we need to know how to write a new simulation module and add it to Ikaros. While this may look daunting at first, it is actually quite easy; most of the stuff needed to do is 'boilerplate' code that can largely be cut and pasted from other modules.
The steps needed are:
- Create an .ikc file that describes the new module.
- Create an .h and .cc file that contains the code for the module.
- Add subclassed Init (for initializing and allocating memory).
- Write the actual core code in the Tick subclassed function.
- Add the .cc and .h files to the makefile or project.
- Create a Summation_test.ikc connecting the modules you need.
- Compile and run.
Example
Let's assume a module that receives an array of values from two different inputs, sums them, then sends the summed array to the output. We'll call it 'Summation'. We will require that the two input arrays are the same size and will set the output array to the same size as the input arrays.
Create Summation.ikc
To let Ikaros know about the capabilities of your new module you need to create an .ikc file that describes the input, outputs and parameters of the module. We place all files in a subiderctory of 'UserModules' called 'Summation', just like our module. The .ikc file for the module Summation is listed below:
1 <?xml version="1.0"?> 2 3 <group name="Summation" description="adds two inputs"> 4 5 <description type="text"> 6 Module that sums its two inputs element by element. 7 Both inputs must have the same size. 8 </description> 9 10 <xample description="A simple example"> 11 <module 12 class="Summation" 13 name="Summation" 14 /> 14 </example> 15 16 <input name="INPUT1" description="The first input" /> 17 <input name="INPUT2" description="The second input" /> 18 19 <output name="OUTPUT" description="The output" size_set="INPUT1, INPUT2" /> 20 21 <module class="Summation" /> 22 23 <author> 24 <name>Jan Morén</name> 25 <email>jan.moren@lucs.lu.se</email> 26 <affiliation>Lund Univeristy Cognitive Science</affiliation> 27 <homepage>http://www.lucs.lu.se/People/Jan.Moren</homepage> 28 </author> 27 29 <files> 30 <file>Summation.h</file> 31 <file>Summation.cc</file> 32 <file>Summation.ikc</file> 33 </files> 35 </group>
The ikc file for a module is used internally by Ikaros to know about the module, but it is also used to automatically generate documentation of a module. This means that you should give a comprehensive description of what the modules does in this file. The idea is that this is the only documentation that you need to produce for a module so the information in the ikc file should be sufficient to understand the module.
Line 2 sets the name of the module and gives a short description. A longer description of the module is given in lines 5-8. Here the description in plain text but it is also possible to use XHTML here. In this case the type attribute should have been set to 'xhtml' instead of 'text'.
Lines 10-14 gives an example of how the module can be used.
Lines 16-21 describes the inputs and outputs of the module. Since the output of the module will depend on the size of the input connected to the module, we use the size_set attribute to set the sizes of the output. Since both INPUT1 and INPUT2 are listed Ikaros will also automatically check that both inputs have the same size.
If the module has had parameter, these too whould have been described here. The <module> element at line 21 defines whhat code to run when the module is used. Since we want to run the code in Summation.cc, we set the class to 'Summation'.
The final lines 23-33 adds additional information about the module such as the name of the author.
The Summation.h file
Summation.h looks like this:
1 // 2 // Summation.h This file is a part of the IKAROS project 3 // Pointless example module summing its inputs. 4 // 5 // Copyright (C) 2001-2011 Jan Morén 6 // 7 // This program is free software; you can redistribute it and/or modify 8 // it under the terms of the GNU General Public License as published by 9 // the Free Software Foundation; either version 2 of the License, or 10 // (at your option) any later version. 11 // 12 // This program is distributed in the hope that it will be useful, 13 // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 // GNU General Public License for more details. 16 // 17 // You should have received a copy of the GNU General Public License 18 // along with this program; if not, write to the Free Software 19 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 // 21 #ifndef SUMMATION_ 22 #define SUMMATION_ 23 #include "IKAROS.h" 24 class Summation: public Module 25 { 26 public: 27 28 // This is all boilerplate code to declare the needed methods 29 // for initialization of the module. Just change 'Summation' to 30 // whatever name your module has. 31 static Module * Create(Parameter * p) { return new Summation(p); } 32 33 Summation(Parameter * p) : Module(p) {}; 34 virtual ~Summation() {}; 35 36 void Init(); 37 void Tick(); 38 // Declare variable for size of input 39 40 int size; 41 44 // the input and output vectors. The storage is declared and handled by 45 // Ikaros so you do not have to. 46 47 float * input1; 48 float * input2; 49 float * output; 50 }; 51 52 #endif
So, what are we doing with this header? On line 9 we declare 'Summation' to be a subclass of Module; the module class is found in 'IKAROS.h' in the IKAROS subdirectory.
Line 31-34 declares the creator, constructor and destructor functions for the module. The creator function is used internally by Ikaros to create a module. For a simple module like Summation the constructor and destructor need not do anything at all and can be declared in the .h file.
36 and 37 declare the two member functions that are the point of the module. Init() is called as soon as all modules have been created, and is used by the module creator to find out the input and output sizes, allocate dynamic storage, and anything else that might be needed at startup. Tick() is called repeatedly during execution to perform the actual work of the module.
The rest of the header is used for whatever needs to be declared for the module to function properly.
The Summation.cc file
The Summation.cc file may look big and complex, but is actually very simple. We have here intentionally written things in a fairly verbose manner and with lots of commentary to make it easily understandable; a compact version of this would likely be under 30 lines of code. Now for the Summation.cc:
1 // 2 // Summation.cc This file is a part of the IKAROS project 3 // Pointless example module summing its inputs. 4 // 5 // Copyright (C) 2001-2011 Jan Morén 6 // 7 // This program is free software; you can redistribute it and/or modify 8 // it under the terms of the GNU General Public License as published by 9 // the Free Software Foundation; either version 2 of the License, or 10 // (at your option) any later version. 11 // 12 // This program is distributed in the hope that it will be useful, 13 // but WITHOUT ANY WARRANTY; without even the implied warranty of 14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 // GNU General Public License for more details. 16 // 17 // You should have received a copy of the GNU General Public License 18 // along with this program; if not, write to the Free Software 19 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 20 // 21 // Created: 2001-12-01 22 // 23 // 2003-01-14 and 2007-01-01 Updated for the new simulator 24 #include "Summation.h" 25 26 27 void Summation::Init() 28 { 29 size = GetInputSize("INPUT1"); 30 input1 = GetInputArray("INPUT1"); 31 input2 = GetInputArray("INPUT2"); 32 output = GetOutputArray("OUTPUT"); 33 } 34 35 void Summation::Tick() 36 { 37 int i; 38 39 // Summation in action. input1 and input2 are pointers to the current input 40 // vectors and output points to the output vector that will be sent 41 // along to whatever modules it is connected to. 42 43 for(i=0; i<size; i++) { 44 output[i] = input1[i] + input2[i]; 45 } 46 } 47 48 // Install the module. This code is executed during start-up. 49 50 static InitClass init("Summation", &Summation::Create, "Source/UserModules/Summation/");
At Line 27, the Init function is defined. This function is called when all modules have been registered, and is a good place to put any and all initializations that may be needed. In this case, we find out what the input sizes really are in lines29 (again, referred through the connection target names in the ikc file described below).
In lines 30-32, we connect our array variables with the inputs and outputs of our module. Internally, all we are doing is getting pointers to arrays defined in the simulator kernel, of course.
n lines 35-46, we have the meat of our module. Every time the Tick() function is called, our input arrays have been initialized with new values, so all we need to do is to loop through these arrays and sum them. A lot of work for little result, it may seem, but all of these preliminaries will be much the same size, no matter what the module is. The Tick() function will in a non-trivial module contain almost all the code.
Finally, at line 50 we add initialisation code that will add the module to Ikaros at start-up.
Compilation
If CMAKE is used, add the new module to the file CMakeLists.txt in the UserModule directory and compile as usual.
Runtime
To actually test the module, we need two additional components: an ikc file describing the experiment and a file with input.
The experiment IKC file
To get our module to actually do something, it needs to be connected with other modules that will give it arrays to sum and that will take the result and display or save it in one way or another. Also, many modules have parameters that may need to be set. Both these functions are done in an ikc file that describes how our simulation should be set up; if we wanted to run a set of simulation with different parameters, we would have one ikc file per variation.
Here is a depiction of how we will connect our 'Summation' module:
Here is our ikc file (let's call it 'Summation_test.ikc'):
1 <?xml version="1.0"?> 2 <group> 3 4 <module 5 class = "InputFile" 6 name = "DATA" 7 filename = "inputfile.txt" 8 iterations = 1" 6 /> 7 8 <module 9 class = "OutputFile" 10 name = "OUT" 11 filename = "outputfile.txt" 12 > 13 <column name = "OUT"/> 14 </module> 15 16 <module 17 class = "Summation" 18 name = "SUM" 19 />> 20 21 <connection sourcemodule = "DATA" source = "A" 22 targetmodule = "SUM" target = "INPUT1"/> 23 24 <connection sourcemodule = "DATA" source = "B" 25 targetmodule = "SUM" target = "INPUT2"/> 26 27 <connection sourcemodule = "SUM" source = "OUTPUT" 28 targetmodule = "OUT" target = "OUT"/> 29 30 </group>
The ikc file is composed of two main parts: the module definitions and the connections between them. Every module has its own entry with name and any parameters it needs.
To get our 'Summation' module rolling, we need the module itself, some kind of input (to get arrays to sum) and some kind of output (so we can see the result). We first add an 'InputFile' module, and call it 'DATA'. This module also needs to know what file to read ('inputfile.txt'). We have added an 'iterations' tag that tells it how many times to read the file; it defaults to 1 and is thus not really needed here.
Next, we add an output module, called 'OutputFile'. We name it 'OUT', and decide that it will write the output into 'outputfile.txt'. We add a 'column' tag with name 'OUT' which will be the target input for our output. We can add as many column tags as we wish, and also set various proprties on them such as being integer or a float; number of decimal places and so on - check the documentation for the module.
Last, we add our new module 'Summation' and call this instance 'SUM'. Had we defined any parameters to set, these would be set as tags here as well.
Note that there is nothing stopping us from adding more than one instance of a module; we could add another 'Summation' module, or multiple 'InputFile' modules, reading from different files. All that's required is that we do not name the instances the same.
Next, we need to describe how to connect these modules to one another. This is done with 'connection' elements.
A connection is described as:
<connection sourcemodule = "name1" source = "output" targetmodule = "name2" target = "input" />
'name1' and 'name2' refers to the names we gave to the modules above. the 'output' and 'input' refers to the names of the in- and outputs we have given them in the code. For our 'Summation' module, we gave them the names 'INPUT1', 'INPUT2' and 'OUTPUT' (lines 35-41 in Summation.cc above).
So, the first connection above states that output 'A' from our input module 'DATA' should go to 'INPUT1' in our module 'SUM'. Similarily, the 'B' output should go to 'INPUT2'. Finally, the result in 'OUTPUT' should go to the output module 'OUT', to its sourse 'OUT'.
Input file
We need one last thing before we can test this, we need an input file. Here's an example:
A/2 B/2 1 1 1 1 2 1 1 2 3 1 1 3 4 1 1 4 5 5 5 5 0 0 0 0 0 0 0 0
This looks slightly strange, but is the standard format for the 'InputFile' module.
The first line declares the names of all outputs (that's where the 'A' and 'B' came from above) and how wide each output is. In this case, we want both outputs to be just two values wide. We could have 'A/3 B/3' and then have two three-element arrays to add, which would require six values per row. Our output will resize appropriately as we saw earlier.
We run this with:
ikaros Summation_test.ikc
If all goes well, the simulator tells us our simulation started, then stopped.
Let's look at the file 'outputfile.txt':
T/1 OUT/2 0 0.0000 0.0000 1 0.0000 0.0000 2 2.0000 2.0000 3 3.0000 3.0000 4 4.0000 4.0000 5 5.0000 5.0000 6 10.0000 10.0000
Now, this seems wrong, doesn't it? The first column is a time step (which the 'OutputFile' module generates for us). The next one should be our summed values, but 1+1 is definitely not 0, and neither is 1+2 (or 2+1). the third value is right for the first pair, though, and the fourth is right for the second. The results have 'slipped' down two steps. Also, the last values are missing from the output. There should be two arrays of zero at the end.
This is not really wrong, though. Ikaros runs in discrete time steps, and it takes a few steps for values to propagate through the system. This is what it looks like in time:
Time | InputFile | A and B | Sum INPUT1 and INPUT2 | OutputFile |
---|---|---|---|---|
0 | 1 1 1 1 | 0 0 0 0 | 0 0 | 0 0 |
1 | 2 1 1 2 | 1 1 1 1 | 2 2 | 0 0 |
2 | 3 1 1 3 | 2 1 1 2 | 3 3 | 2 2 |
3 | 4 1 1 4 | 3 1 1 3 | 4 4 | 3 3 |
8 | 5 5 5 5 | 4 1 1 4 | 5 5 | 4 4 |
8 | 0 0 0 0 | 5 5 5 5 | 10 10 | 5 5 |
8 | 0 0 0 0 | 0 0 0 0 | 0 0 | 10 10 |
After which the input file is empty, and the simulation stops. This is not an error; this just reflects the fact that calculations aren't instantaneous. To get the last values, we pad the input with a few rows of zeroes; this might interfere with a simulation being run over multiple iterations, though (In this case the extend attribute in InputFile can be used to add the extra zeros).
This has been a walkthrough of the implementation of a (very) simple module. For a complex module all the extra work will be in the module itself; the effort required to interface it with Ikaros is just about the same as in this example. To find out more, browse through the documentation and take a look at other predefined modules available as part of the distribution.