Wrapping enumerations

The problem

Ecto cells that take parameters that are enumerations should wrap those enumerations using boost::python, this vastly improves use/readability.

For instance, this cell takes its parameter strategy as an integer:

#include <ecto/ecto.hpp>
#include <iostream>

using ecto::tendrils;

struct EnumAsInt
{
  enum Strategy {
    FAST, SLOW, ADAPTIVE
  };

  static void declare_params(tendrils& p)
  {
    p.declare<int>("strategy", "How to run our algorithm");
  }

  void configure(const tendrils& p, const tendrils& i, const tendrils& o)
  {
    int n = p.get<int>("strategy");
    switch (n) {
    case FAST:
      std::cout << "running FAST\n"; break;
    case SLOW:
      std::cout << "running SLOW\n"; break;
    case ADAPTIVE:
      std::cout << "running ADAPTIVE\n"; break;
    default:
      std::cout << "BAD!  Unhandled value of enum parameter";
    }

  }
};

ECTO_CELL(ecto_examples, EnumAsInt, "EnumAsInt", 
          "Example of how *not* to handle parameters that are enums");

Which is suboptimal in several ways. First, the documentation doesn’t say what the legal values are for this parameter:

EnumAsInt

Brief doc

Example of how *not* to handle parameters that are enums

Parameters

  • strategy   type: int    not required   no default value

    How to run our algorithm

And though the cell functions correctly if used correctly, (note here that the direct call to process() is only for convenience, you’ll typically be adding the cell to a plasm and letting a scheduler do this),

#!/usr/bin/python

import ecto.ecto_examples as ecto_examples

cell = ecto_examples.EnumAsInt(strategy=1)
cell.process()

output:

No module named PySide.QtCore
running SLOW

it is easy to pass invalid values to the cell from python, and easy to forget to handle out-of-range values:

#!/usr/bin/python

import ecto.ecto_examples as ecto_examples

cell = ecto_examples.EnumAsInt(strategy=17)
cell.process()

output:

No module named PySide.QtCore
BAD!  Unhandled value of enum parameter

Solution: enum parameters as enums, and wrap your enumerations

Make sure that the parameters are of the enum type itself. In the cell, declare and get the parameter as being of the enum type,

#include <iostream>
#include <ecto/ecto.hpp>

using ecto::tendrils;

enum Strategy {
  FAST, SLOW, ADAPTIVE
};

struct EnumAsEnum
{
  static void declare_params(tendrils& p)
  {
    p.declare<Strategy>("strategy", "How to run our algorithm");
  }

  void configure(const tendrils& p, const tendrils& i, const tendrils& o)
  {
    Strategy s = p.get<Strategy>("strategy");
    switch (s) {
    case FAST:
      std::cout << "running FAST\n"; break;
    case SLOW:
      std::cout << "running SLOW\n"; break;
    case ADAPTIVE:
      std::cout << "running ADAPTIVE\n"; break;
    default:
      std::cout << "BAD!  Unhandled value of enum parameter";
    }

  }
};

ECTO_CELL(ecto_examples, EnumAsEnum, "EnumAsEnum", 
          "This is how to defined enum parameters.  Like this.");

And wrap the enum in the ECTO_DEFINE_MODULE section like so:

#include <ecto/ecto.hpp>
#include <ecto/python.hpp>

#include <boost/python.hpp>

enum Strategy {
  FAST, SLOW, ADAPTIVE
};

ECTO_DEFINE_MODULE(ecto_examples)
{
  boost::python::enum_<Strategy>("Strategy")
    .value("FAST", FAST)
    .value("SLOW", SLOW)
    .value("ADAPTIVE", ADAPTIVE)
    .export_values()
    ;
}

Of course, this enumeration should be in a header file so that you don’t have multiple copies floating around. Now a great many things will be more usable. First of all, the documentation for the module will show the correct type (and therefore values) for the parameter:

EnumAsEnum

Brief doc

This is how to defined enum parameters. Like this.

Parameters

  • strategy   type: Strategy    not required   no default value

    Legal Values: FAST (0)   SLOW (1)   ADAPTIVE (2)  

    How to run our algorithm

And if you try to pass magic numbers to cells, you get a reasonable error:

#!/usr/bin/python

import ecto_examples

cell = ecto_examples.EnumAsEnum(strategy=17)
cell.process()

output:

$ /home/vrabaud/workspace/recognition_kitchen/src/ecto/doc/kitchen/ecto/usage/advanced/../../src/enumasenum_bad.py
Traceback (most recent call last):
  File "/home/vrabaud/workspace/recognition_kitchen/src/ecto/doc/kitchen/ecto/usage/advanced/../../src/enumasenum_bad.py", line 3, in <module>
    import ecto_examples
ImportError: No module named ecto_examples

The enum type gets a reasonable docstring,

No module named PySide.QtCore
Help on class Strategy in module ecto.ecto_examples:

class Strategy(Boost.Python.enum)
 |  Method resolution order:
 |      Strategy
 |      Boost.Python.enum
 |      __builtin__.int
 |      __builtin__.object
 |  
 |  Data and other attributes defined here:
 |  
 |  ADAPTIVE = ecto.ecto_examples.Strategy.ADAPTIVE
 |  
 |  FAST = ecto.ecto_examples.Strategy.FAST
 |  
 |  SLOW = ecto.ecto_examples.Strategy.SLOW
 |  
 |  names = {'ADAPTIVE': ecto.ecto_examples.Strategy.ADAPTIVE, 'FAST': ect...
 |  
 |  values = {0: ecto.ecto_examples.Strategy.FAST, 1: ecto.ecto_examples.S...
 |  
 |  ----------------------------------------------------------------------
 |  

And the normal way to use this kind of thing would be:

#!/usr/bin/python

import ecto.ecto_examples as ecto_examples

cell = ecto_examples.EnumAsEnum(strategy=ecto_examples.ADAPTIVE)
cell.process()
No module named PySide.QtCore
running ADAPTIVE