Octave Wiki | RecentChanges

CodaTypes

DaCodaAlFine - CodaTypes

Octave Type System Muthiah Annamalai<gnumuthu@users.sf.net> Licensed under GNU Free Documentation License..

Abstract

This document explains the type system employed within GNU Octave, and how one can extend it to get specific functionality. It will be of most use to people who want to extend GNU Octave for language bindings to other API's like GSL, Gd-Octave, Octave-Vtk, Octave-Gtk. It however doesnot deter the inquisitive from hacking into the guts of GNU Octave, and aims to help such efforts with the important understanding of the Octave Type system.

Introduction

GNU Octave is written in C++ and provides an interpreted environment for scientific computing, and supports extensions on itself, by the use of dynamically loaded modules,and shared libraries. In this we explain the GNU Octave type system, and how one can extend it to get specific functionality. We shall round up with an example of custom extension.

GNU Octave has a type system that helps in reflection, meaning the interpreter of Octave language, can query the type at runtime to find its type information. Also this makes the Octave language dynamic typed like Python, Perl or JavaScript?. What we are going to do is essentially see what this type system is used for, and how we can customize/create our own types specific to each application.

Requirements

It is assumed that you have GNU Octave compiled with dynamic loading support, shared libraries, and have the GNU Octave development header files in your system. As is the case generally, you will have all these; if not, please get your copy from www.octave.org. Any version Octave-2.1.50 or greater will suffice. Later the better.

Programmers are expected to have some introduction to C++, and GNU Octave language, as it would ease your learning curve. Also experience with the beast [GNU Octave] itself is welcome, if not mandatory.

If you would like to learn more about GNU Octave and how to use it, an excellent place to start is the Octave manual, by John W Eaton. It's available at http://www.octave.org/ There is also an online manual available at http://www.gnu.org/manual/octave/index.html. Alternatively, you can try the built-in octave tutorial. To start the tutorial, type [user @ host]$ info octave.

There are a number of things you must have before you install GNU Octave, including GNU/Linux, with several dependencies. For a comprehensive list of libraries needed to compile octave extensions, just see the file `[user @ host]$cat /usr/bin/mkoctfile` or run `[user @ host]$ldd /usr/lib/liboctave*` to see the list of dependencies.

Need for a Type System

As discussed in the introduction, GNU Octave needs a type system for supporting the following features.

Implementing Octave the interpreted language, that allows us to perform scientific computation in a matrix based environment, for which we must use an interpreter. Within one interpreter, we can allow for the user to create variables, get/set their properties, and change their values. A serious user of GNU Octave will have noticed that Octave interpreter has a built-in command called typeinfo() which can be used to get the typeid of the object's current value. Executing the following piece of code in GNU Octave will give you the type of x, currently which is 'matrix'.

eg: >x=[1 2 3]; typeinfo(x); 
     ans="matrix" 
    >x=1; typeinfo(x); 
     ans="scalar" 
    >x="hello"; typeinfo(x); 
     ans="string" 

Object Hierarchy is supported in GNU Octave with the help of this type system. You can inherit a type from the octave_value, the canonical holder, and implement its virtual functions, so that we have a brand new type to work with. However, what you can do with your type is limited by what Octave interpreter allows you to. Its a bit restrictive, but all the same it works only to keep Octave what it is, and not morph into other avatars. eg: we can inherit from the matrix type to create an image object, and define our own affine transforms for it, as its member functions, overload operators like +,-,=,%,* which perform intuitive functions on the object from the interpreter itself.

RTTI in GNU Octave

As many experienced C++ programmers will tell you, RTTI [ Run Time Type identification] is a very powerful feature that will allow you to make the object hierachies interoperate. For this we must implement, RTTI using Octave, or use the one provided by C++.FIXME Personally I was successful with the RTTI present in GNU Octave only, and C++ RTTI failed me, for reasons I couldnt understand.

GNU Octave object hierarchy resembles this octave_value : This is the base type [abstract] for all the octave objects, and simply acts as a abstract data type, that allows, us to have derived classes override the base class virtual member functions to achieve our desired functionality. As such octave_value has the virtual functions delegating the actions to the derived types, and makes life hard [you have to implement every virtual functions, if you dont want a segmentation fault!] .so we use octave_base_value, which fills in default stuff for the uninteresting functions. We generally override the int_value(), string_value(), and ulong_value(),print() kind of member functions, for our functionality. Assume you want to use an Image class that is derived from the matrix class, you could override the print() method to display just the type of image, width, height, color and size information. Similarly you could override the member functions like matrix_value() to return the properly needed matrix itself.

  1. class octave_value
  2. class octave_base_value : public octave_value
  3. template <class ST> class octave_base_scalar : public octave_base_value
  4. template <class MT> class octave_base_matrix : public octave_base_value
  5. class octave_bool : public octave_base_scalar<bool>
  6. class octave_magic_colon : public octave_base_value // A type to represent `:' as used for indexing.
  7. class octave_complex : public octave_base_scalar<Complex> // Complex scalar values.
  8. class octave_cell : public octave_base_matrix<Cell>
    1. class Cell : public ArrayN? <octave_value>
    2. template <class T> class ArrayN? : public Array<T>
  9. class octave_scalar : public octave_base_scalar<double>
  10. class octave_struct : public octave_base_value
  11. class octave_list : public octave_base_value

The above hierarchy would throw light on the inheritance tree in octave classes. We find a pattern in the inheritance tree. All scalar types are derived from octave_base_scalar which is a template class, so we have octave_scalar, octave_complex derive directly from octave_base_scalar. The matrix types naturally derive from the octave_base_matrix which is also a template class. We will have complex matrix types derive from the octave_base_matrix. Similar patterns can be found in octave_list, octave_cell, octave_map, octave_struct which are left as an exercise to the reader.

Making Custom Octave Objects

This section will show you how to create a custom octave object called Box which is designed to hold a value of a pointer, and its associated typename. Basically, it is designed to prevent the user from changing the value of the pointer by wrapping the pointer value within a octave_value object so that it appears opaque to the user.

First we will have to declare the class structure in Box.cpp. Here we will inherit the class from the octave_base_value type, and override the print(), and ulong_value(). We will add a new functions called check_ptr() and save_ptr().

/*
 * This program is free software.
 * This code is a part of gd-octave
 * Code may be used or distributed under GPL.
 * (C) 2004 Muthiah Annamalai  
 *
 *   Source File : Box.cpp
 */
#include<iostream>
#include<octave/oct.h>
#include<octave/parse.h>
#include<octave/dynamic-ld.h>
#include<octave/oct-map.h>
#include<octave/oct-stream.h>
#include<octave/ov-base-scalar.h>
#include<vector>
#include<string>

using namespace std;


class Box: public octave_base_value
{
 public: 
  
  /* Well the constructor!! */
	Box(void):octave_base_value()
	{
		objptr=0;
		objtype=new string("None");
	}

	Box(octave_value box_val):octave_base_value()
	{	
		const octave_value& rep = box_val.get_rep();
		objptr=((const Box&) rep).get_ptr();
		objtype=((const Box&) rep).get_str();
	}

	Box(long int ptr,string type):octave_base_value()
	{
		objptr=ptr;
		objtype=new string(type);
	}


  ~Box(void)
    {
      if(objtype)
	delete objtype;
    }
  /* Your Home Work! */
	static void  save_ptr(int ptr,string type)
	{
		// Make a static list, and store the data.
	}	

	static bool check_ptr(Box b)
	{
		// Iterate through the static list, and see if the pointers match,
		// else, just report not found->false
		// or found->true
	}	

  /* Query Interface */
    long int get_ptr(void) const
    {
      return this->objptr;
    }


   string*  get_str(void) const 
    {
      return this->objtype;
    }
 


  void print (std::ostream& os, bool pr_as_read_syntax = false) const
	  {
		  os<<"Ptr ="<<get_ptr()<<" Str="<<*get_str()<<endl;
	  }

  bool is_constant (void) const { return true; }
  bool is_defined (void) const { return true; }

 private:
  /* Object name */
  string *objtype;

  /* store object pointer */
  long int objptr; 

  DECLARE_OCTAVE_ALLOCATOR

  DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA
};

DEFINE_OCTAVE_ALLOCATOR (Box);
DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA (Box, "Box");

DEFUN_DLD(BoxMake,args,,"BoxMake(ptr,string)\n\
 makes new instances of Box objects\
to fill into the world of octave")
{
	
	if(args.length() < 2){
		cout<<"usage: BoxMake(ptr,string)";
		return octave_value();
	}
	Box *b=new Box(args(0).int_value(),args(1).string_value());
	return octave_value(b);
}



DEFUN_DLD(BoxTest,args,,"BoxTest(Boxobject)\
 tests if the Box object is present in the\
saved pointer list present")
{
	if(args.length() < 1 
	   || args(0).type_id()!=Box::static_type_id()){
		
		cout<<"usage: BoxTest(Boxobject)"<<endl;
		return octave_value(-1);
	}
	const octave_value& rep = args(0).get_rep();
	const Box& b = ((const Box &)rep);
	b.print(cout,0);
	return octave_value();
}

/*
gcc --shared -o BoxMake.oct Box.cpp -I /usr/include/octave-2.1.50/octave/ -I /usr/include/octave-2.1.50/ 
*/

Data Members of the Box class are:

1: long int objptr ;/* to hold the value of the pointer */ 2: string *objtype ;/* description where this pointer points to */

Members Functions of the Box class are: 1: long int get_ptr(void) const; 2: string* get_str(void) const; which are property accessors for the private data members. Actually the save_ptr() & check_ptr() functions are left as an exercise, as otherwise it would interfere with the comprehension of our octave_value mutation, and customization.

First you can see that the constructor for Box class has two arguments, one the pointer value, and the next the descriptive string that accompanies it. The default constructor, is elegant to include, and we initialize 0 and "None" to objptr and objtype fields.

The overridden functions from octave_base_value are 1: void print (std::ostream& os, bool pr_as_read_syntax = false) const; which is invoked if octave interpreter wants to print our object. 2: bool is_constant (void) const { return true; } 3: bool is_defined (void) const { return true; } -- FIXME -- These functions are necessary, if we want the object returned from our dynamic functions to be assignable within the octave interpreter. i.e they enable the object to be proper R-values.

The macros DECLARE_OCTAVE_ALLOCATOR [<liboctave/oct-alloc.h>] expands to

#define DECLARE_OCTAVE_ALLOCATOR \
        public: \ 
        void *operator new (size_t size, void *p) \ { return ::operator new (size, p); } \
        DECLARE_OCTAVE_ALLOCATOR_PLACEMENT_DELETE \ 
        void *operator new (size_t size) { return allocator.alloc (size); } \
        void operator delete (void *p, size_t size) { allocator.free (p, size); } \
        private: \
        static octave_allocator allocator; /* End of Macro */
These functions simply create our object, and allocate some memory.

Another macro DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA defined in [src/ov.h] expands to

#define DECLARE_OV_TYPEID_FUNCTIONS_AND_DATA \\
        public: \
        int type_id (void) const { return t_id; } \
        std::string type_name (void) const { return t_name; } \
        std::string class_name (void) const { return c_name; } \
        static int static_type_id (void) { return t_id; } \
        static void register_type (void); \
        private: \
        static int t_id; \
        static const std::string t_name; \
        static const std::string c_name;

These private variables t_id, t_name, c_name are useful in RTTI within Octave libraries and inherited types for accessing the type id with type_id(), type name with type_name(), class name with class_name(). Also the accessor helper/property functions static_type_id() and register_type() provide a clean interface to these variable.

We have in the code section, a macro DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA(type, type-name, class-name) which expands to

#define DEFINE_OV_TYPEID_FUNCTIONS_AND_DATA(t, n, c) \
 int t::t_id (-1); \
 const std::string t::t_name (n); \
 const std::string t::c_name (c); \
 void t::register_type (void) \
 { \
 t_id = octave_value_typeinfo::register_type (t::t_name, \
                                              t::c_name, \
                                              octave_value (new t ()));}

This sets the t_id,t_name,c_name private variables to the given values of type,type-name and class-name. Also it registers the octave type which we have created with the octave interpreter using the register_type() function, and gets the unique type id. This makes the derived Box class a unique Octave Value derived type.

Dynamic Loaded Functions

Two functions BoxMake? and BoxTest? are defined which do functions of creating and testing a box expressing how we do conversions between the Box type to octave_value and vice verca. We will defer discussion of test script to the next section.

BoxMake? function creates a new Box object after checking if we have the 2 arguments. It converts the arguments 1,2 to integer and string respectively using the octave_value member functions int_value() and string_value(). Then we call the Box() constructor, and the new object we created is returned to the user as an octave_object. Finally the Box object is cast into a octave_value to be returned to the octave interpreter. If the user tries to print our object, then our own print() function gets called, and we can display relevant data for our objects string representation.Thus we have converted the Box object to the octave_value , and send it to the Octave Interpreter.

BoxTest? function is invoked by the user with a Box object created from BoxMake? function. We check the argument object, if its a type of BoxTest? using the args(0).type_id() function comparing it with Box::static_type_id(), and on success we proceed to the most important step of type casting octave_value object to a Box object which it actually is. The code magic is in this line

 const octave_value& rep = args(0).get_rep();
 const Box& b = ((const Box &)rep);

where we force the object to return its 'rep' which is actually the opaque pointer present as a union object within octave_value, to a rep object which is the actual Box object. This is necessary because octave language uses pass be value, not reference. So every copy of the object passed to our BoxTest? function has a pointer to the original Box object created within the BoxMake? function. So after our type_id() checks we are rightfully qualified to force the typecasting of the octave_value to a const Box reference. From now on we are free to call the requisite Box member functions, as and when necessary. Now we may call the check_ptr() function and return a true/false value. Thus we have converted the octave_value to the Box type , on the object received from the Octave Interpreter.

Compiling the program must be easy if you have "mkoctfile" script in your path. Just use it like

$mkoctfile Box.cpp -o BoxMake.oct && ln -s BoxMake.oct BoxTest.oct

Testing with DEFUN_DLD

Actually we compile the Box.cpp file into a shared object, so that the symbols are exported into the library without linking to the octave library. This will be recognised by the octave interpreter [after you set the extension from .so to .oct, and call the function 'myFunc' present in the file 'myFunc.oct' ]. Now the dl library with its functions like dlopen and dlsym found in [/usr/include/dlfcn.h] are used by Octave to search and load the function from you .oct file, and invoke the same with the arguments. Marshalling the arguments is easy as all the DEFUN_DLD functions take the same number of arguments, and we can access the functions arguments from the 'args' parameter. This 'args' parameter is of the type octave_value_list, which is the list of all arguments the user has passed to us. We [have to ! ;-)] do checks on the type and number of the arguments before we get down to using them. Again, our example falls short of these expectations.

We now make symbolic links of the files BoxMake?.oct and BoxTest?.oct to the same shared file, and then execute this script. ln -s BoxMake?.oct BoxTest?.oct

%Octave Testing script.
#! /usr/bin/octave -q 
% Create a Few Boxes: 
%Stress test. 
for i=1:100 
    b=BoxMake(i,"one") 
    BoxTest(b,"check") 
    typeinfo(b) 
end
%`chmod +x Box.m` before executing the program.

Do `chmod +x Box.m` and then run the program. More interesting results are obtained if you try the octave interpreter, and execute the lines one by one. Also try the typeinfo(BoxMake?(123,"BoxType?")), to see the custom type we have christened Box from the octave_value.

See Also

see also : octave-x.y.z/examples/make_int.cc in octave sources, CODA from octave-forge distribution. wiki.octave.org, www.octave.org, http://octave.sourceforge.net.

Credits

  1. OCtave Mailinglists: Thanks to the cheerful people at octave mailinglists, like David Bateman, Paul Kienzile,Etienne and JWE.
  2. Octave Forge: Thanks to Paul for maintaining octave forge.
  3. CODA : Where I learnt more Octave. octave-x.y.z/examples/make_int.cc : Thanks to JWE
  4. wiki.octave.org : Thanks to Etienne.

GNU Free Documentation License

  1. This article is © copyright Muthiah Annamalai 2004.
  2. You can freely read,modify,distribute and edit this document under the terms of the GNU Free Documentation License.
  3. GNU FDL Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA