Menu

ComponentsConfig

Tomassino Ferrauto
Prev: Creating a plugin and registering components Up: Plugins, components and resources Next: Declaring and accessing resources

Table of contents

Configuring components

This page shows how to write components that can be created and configured from configuration files. This, together with the information on how to create plugins, is the basic knowledge needed to extend FARSA and create experiments. The reference guide contains detailed description of all methods and classes used here.

NOTE: when implementing components in a plugin, remember to follow the guidelines in this page. In particular, code snippets in this page lack both the FARSA_PLUGIN_* and the FARSA_REGISTER_CLASS macros (because they are mostly taken from FARSA core source code).

Introduction

The main use of plugins in FARSA is to add new components. Almost everything in FARSA is a component: robots, sensors, motors, experiments, controllers, ... These different types of objects share one common characteristic: they can be referred in configuration files together with their parameters and they can then be created at runtime loading the configuration file.

The objects that are created at runtime have in general the same hierarchical structure of the configuration file. An object corresponds to a group of parameters in the configuration file and objects corresponding to subgroups are owned by the object corresponding to the parent group. In the remaining part of this page we will refer to the following configuration file (a reduced and simplified version of the configuration file of the KheperaDiscriminationExperiment plugin):

:::INI
[Component]
type = EvoRobotComponent

[Component/GA]
type = Evoga
evolutionType = steadyState
ngenerations = 500
mutation_rate = 0.02

[Component/GA/Experiment]
type = KheperaDiscriminationExperiment
ntrials = 25
nsteps = 600
minObjectDistanceFromWall = 0.05

[Component/GA/Experiment/Arena]
type = Arena
planeHeight = 1.0
planeWidth = 1.0

[Component/GA/Experiment/ROBOT]
type = Khepera
kinematicRobot = true

[Component/GA/Experiment/NET]
type = Evonet
nHiddens = 0

[Component/GA/Experiment/Sensor:0]
type = KheperaSampledProximityIRSensor
activeSensors = 11111100

[Component/GA/Experiment/Motor:0]
type = KheperaWheelVelocityMotor

The configuration file format is similar to INI files (it is also possible to use an XML format, but the support is not well tested). It is made up of groups that have a tree-like organization and each group has a set of parameters. The text between square brackets is the name of the group. The special character / in group names separates groups and subgroups. So, for example, Component/GA/Experiment means that Experiment is a subgroup of GA that, in turn, is a subgroup of Component. All parameters have the form name = value and they belong to the group immediately before them.

The type parameter

Before describing how to write component classes, it is important to introduce the type parameter, which is present in all groups (with the exception of groups that are only used to keep related parameters together, which are almost never used). This parameter is mandatory and specifies the component associated with the group. For example, in the configuration file above, the [Component/GA/Experiment/NET] group has a type parameter whose value is "Evonet". This means that component "Evonet" will be created and the parameters of the group will be used to configure the component. The section about the describe() method will give further information about the type parameter.

Base classes for components

All component classes are subclasses of the ParameterSettable class. They are not, however, direct subclasses. A component must not inherit directly from ParameterSettable, but instead choose between two possible parents:

  • ParameterSettableInConstructor
  • ParameterSettableWithConfigureFunction

The difference between the two lies in where configuration parameters are read: classes inheriting from ParameterSettableInConstructor read parameters in the constructor, while classes inheriting from ParameterSettableWithConfigureFunction read parameters in the special configure() function. This difference will be explained in detail in the section about component configuration. The rest of this guide applies to both classes.

The describe() method

The first method that all components need to implement is the describe() function. The aim of this function is to provide a description of all the parameters and subgroups that are needed by the component. For each parameter it is possible to specify its name, which is its type, a description, whether it is mandatory or not and so on. Also subgroups must be defined here. This information is then used by total99 to allow to graphically modify the configuration file. The following code snippet is a simplified version of the describe function of the Evoga class (corresponding to the [Component/GA] group in the configuration file above):

:::C++
class Evoga : public ParameterSettableWithConfigureFunction, ...
{
...
public:
    static void describe(QString type);
...
};

void Evoga::describe(QString type)
{
    ParameterSettableWithConfigureFunction::describe(type);

    Descriptor d = addTypeDescription(type, "Implements the genetic algorithm developed by Stefano Nolfi");

    d.describeEnum("evolutionType")
        .def("steadyState")
        .values(QStringList() << "steadyState" << "generational" << "xnes" << "specializerSteadyState")
        .props(IsMandatory)
        .help("Specify the type of evolution process to execute");

    d.describeInt("ngenerations")
        .def(100)
        .limits(0, MaxInteger)
        .help("Number of generations");

    d.describeReal("mutation_rate")
        .def(0.05)
        .limits(0, 100)
        .help("The mutation rate", "The rate at which a mutation will occur during a genotype copy; a real value below 1 (i.e. 0.12) is considered as a rate (i.e. 0.12 correspond to 12% of mutation); a value egual or above 1 is considered as a percentage of mutation (i.e. 25 correspond to 25% of mutation, or 0.25 rate of mutation)");

    d.describeSubgroup("Experiment")
        .props(IsMandatory)
        .type("EvoRobotExperiment")
        .help("The object delegated to simulate and to evaluate the fitness of an individual"); 
}

The small excerpt from the Evoga class declaration shows that the describe() function is a static method. This method is called when the component is registered, before any instance of the class is created. The type parameter of the function contains the name with which the component is registered (which is the same as the class name). This is also the name you must use as the value of type parameter in the configuration file.

The first line of the function is a call to the describe function of the parent class. This is important so that all parameters of the parent class are also associated with the present component.

The second instruction creates a Descriptor object. This object is then used to declare all the parameters and subgroups. The object is created using the addTypeDescription function of the ParameterSettable class which takes two arguments: the first one is the name of the type being described (that is the type parameter of the describe() function) and the second one is a short description of the class. A third optional parameter is a longer description of the component.

The remaining part of the function makes use of the Descriptor object to declare the parameters and subgroups. Descriptor has a set of functions named describe<type>() to declare different types of parameters. In the example above the describeEnum(), describeInt() and describeReal() functions are used. These functions take one parameter (the name of the configuration parameter) and return an object that can be used to further specify the parameter properties (default value, range, help text, ...). Refer to the API documentation for the full list of describe<type>() functions and the methods of the associated objects.

Similarly to configuration parameters, subgroups are declared using the describeSubgroup() function, which takes the name of the subgroup as the only parameter. In the source code above, the subgroup is called "Experiment". The example configuration file contains a group named [Component/GA/Experiment], that is a subgroup of [Component/GA] from which the Evoga object is created. A fundamental property of the subgroup is the type, which dictates which is the type of components that can be created from the subgroup. In the example, the type of the "Experiment" subgroup is "EvoRobotExperiment". This means that the EvoRobotExperiment component or any subclass can be used. In the configuration file the type "KheperaDiscriminationExperiment" is specified and that is actually a subclass of EvoRobotExperiment (you can check that this is actually the case by looking at the header file in the KheperaDiscriminationExperiment plugin).

The constructor and the configure() method

The describe() method allows to declare all the parameters and subgroups that a component expects to find. When the component is created, it has to read the value of those parameters and create the sub-components, i.e. the components associated with its subgroups. Depending on whether the components inherits from the ParameterSettableInConstructor class or from the ParameterSettableWithConfigureFunction class, the function in which configuration parameters are read is different. How parameters are read and sub-components are created, however, is the same in both cases. We will first show how to implement components inheriting from ParameterSettableWithConfigureFunction, then we will discuss in what components inheriting from ParameterSettableInConstructor differ.

ParameterSettableWithConfigureFunction

The following is an example of a component inheriting from ParameterSettableWithConfigureFunction. As we did above, we will use a simplified version of the configure function of the Evoga class (corresponding to the [Component/GA] group in the configuration file above):

:::C++
class Evoga : public ParameterSettableWithConfigureFunction, ...
{
...
public:
    virtual void configure(ConfigurationParameters& params, QString prefix);
...
};

void Evoga::configure(ConfigurationParameters& params, QString prefix)
{
    // Call parent class configure() function if not pure virtual!
    //ParameterSettableWithConfigureFunction::configure(params, prefix);

    evolutionType = ConfigurationHelper::getString(params, prefix + "evolutionType", "steadyState");
    if ( evolutionType != "steadyState" && evolutionType != "generational" && evolutionType != "xnes" && evolutionType != "specializerSteadyState") {
        Logger::error( "Evoga - evolutionType has been wrongly setted. It can assume only 'steadyState', 'generational', 'xnes' or 'specializerSteadyState' values" );
        evolutionType = "steadyState";
    }

    nogenerations = ConfigurationHelper::getInt(params, prefix + "ngenerations", 100);

    mutation = ConfigurationHelper::getDouble(params, prefix + "mutation_rate", 0.02);

    exp = params.getObjectFromGroup<EvoRobotExperiment>(prefix + "Experiment");
}

The small excerpt from the Evoga class declaration shows that the configure() function is a virtual method. It takes two parameters: a reference to a ConfigurationParameters object and a string. The ConfigurationParameters class is the one responsible of reading the configuration file. All parameters and subgroups are read through params, directly or using helper methods. This object contains all parameters in the file, even those relative to objects different from the one being configured. The prefix string contains the name of the group associated with the object being configured, and is then needed to access the object own parameters (as explained in detail below).

The first instruction of the function should be a call to the parent class configure() function. This is needed so that configuration parameters of the parent are correctly set. The only exception to this rule is when a class inherits directly from ParameterSettableWithConfigureFunction, as in the example above: in this case the parent function cannot be called because it is declared as pure virtual.

The rest of the function contains code to read the value of parameters and create subcomponents. All parameters are stored as strings in ConfigurationParameters, so they must in general be converted to the correct type. The class ConfigurationHelper contains a set of static functions that can be used to convert a value to the correct type and to assign a default value in case the parameter is missing or the conversion fails. All the functions are called ConfigurationHelper::get<type>() and take three parameters: the first one is the ConfigurationParameters object containing the parameters and their values, the second one is the name and path of the parameter to read and the third one is the default value. The functions take the full path to a parameter. This means that you must provide the name of the group followed by the name of the parameter. The name of the group is in prefix and you can simply append to it the name of the parameter using the + operator (as in the example above). As always, refer to the API documentation for more information.

A note on the default value. It is important to use the same default value for a parameter in both the describe() and configure() functions. At the moment the default value when reading the parameters cannot be inferred automatically from the default value declared in describe() (this will be changed in a future version of FARSA).

The example also shows that you should check errors when reading parameters. If a parameter ends up having and invalid value, you should either set it to the default and warn the used (as in the example above) or you can abort the configuration of all components throwing an exception of type UserDefinedCheckFailureException (in this case the ConfigurationHelper::throwUserConfigError() function is a convenient helper function).

The last instruction of the function shows how subcomponents can be created using the ConfigurationParameters::getObjectFromGroup() function. The function returns a pointer to a new object that is created using configuration parameters of the subgroup. More in detail, getObjectFromGroup() is a template function whose template parameter is the type of the object to return. The function creates the object and then checks whether its type is compatible with the requested one, throwing an exception if it is not. The only parameter of the function is the group from which the object is to be created: the full path is needed and, as for parameters, it can be obtained by concatenating prefix with the subgroup name. The getObjectFromGroup() function has also other parameters to customize its behaviour, refer to the API documentation for more information.

ParameterSettableInConstructor

Classes inheriting from ParameterSettableInConstructor instead of ParameterSettableWithConfigureFunction do not have any configure() function and should read configuration parameters in the constructor. Here is an example, a simplified version of the constructor of the KheperaSampledProximityIRSensor class (corresponding to the [Component/GA/Experiment/Sensor:0] group in the configuration file above):

:::C++
class KheperaSampledProximityIRSensor : public KheperaSensor
{
public:
    KheperaSampledProximityIRSensor(ConfigurationParameters& params, QString prefix);

    ...
};

KheperaSampledProximityIRSensor::KheperaSampledProximityIRSensor(ConfigurationParameters& params, QString prefix) :
    KheperaSensor(params, prefix),
    m_activeSensors(ConfigurationHelper::getBoolVector(params, prefix + "activeSensors", "11111111"))
{
    if (m_activeSensors.size() != 8) {
        ConfigurationHelper::throwUserConfigError(prefix + "activeSensors", params.getValue(prefix + "activeSensors"), "The parameter must be a list of exactly 8 elements either equal to 1 or to 0 (do not use any space to separate elements, just put them directly one after the other)");
    }
}

Class declaration shows that the constructor must take two parameters, the same parameters (with the same meaning) of the configure() function. Furthermore, the parent constructor taking the two parameters must also be called in the initialization list. The body of the constructor can be implemented similarly to the configure() function, but a parameter can be read in the initialization list, as in the example. The example also shows how to use the ConfigurationHelper::throwUserConfigError function to throw an exception if a parameter is not valid.

The postConfigureInitialization() method (optional)

There is one further method that is called when components are configured but that is not compulsory and is needed only in some special cases. The postConfigureInitialization() member function is called after all components have been created and configured, and is useful when there are initializations that require multiple components to be already created and configured. Here is an example:

:::C++
class Evoga : public ParameterSettableWithConfigureFunction, ...
{
...
public:
    virtual void postConfigureInitialization();
...
};

void Evoga::postConfigureInitialization()
{
    // Call parent class postConfigureInitialization()
    ParameterSettableWithConfigureFunction::postConfigureInitialization();

    ...
}

As shown in the example, the function takes no parameters. The only important thing to remember when implementing it, is to call the postConfigureInitialization() function of the parent class. Also note that there is not a pre-defined order in which the postConfigureInitialization() functions of different components are called.

The save() method

All subclasses of ParameterSettable have another pure virtual method: save(). This function is used in some FARSA libraries (notably the NNFW and GA libraries) to save the status of objects, but it will be removed in future FARSA releases. Being pure virtual, however, forces you to implement it anyway in case you inherit directly from ParameterSettableInConstructor or ParameterSettableWithConfigureFunction, or from an abstract subclass. You can safely create an empty implementation, as in the example below:

:::C++
class Evoga : public ParameterSettableWithConfigureFunction, ...
{
...
public:
    virtual void save(ConfigurationParameters& params, QString prefix) {}
...
};

The getUIManager() method (optional)

Components can optionally have associated GUIs and menu items. To associate one or more GUIs and/or menu items (or even custom menus) you need to implement the getUIManager() function. The function should return an instance of a subclass of ParameterSettableUI. The following code snippets are taken from the Evonet class and show how to implement both the getUIManager() method and the subclass of ParameterSettableUI

:::C++
class Evonet : public ParameterSettableWithConfigureFunction, ...
{
...
public:
    virtual ParameterSettableUI* getUIManager();
...
};

ParameterSettableUI* Evonet::getUIManager()
{
    return new EvonetUI(...);
}

class EvonetUI : public ParameterSettableUI, ...
{
...
public:
    QList<ParameterSettableUIViewer> getViewers(QWidget* parent, Qt::WindowFlags flags);

    void fillActionsMenu(QMenu* actionsMenu) {}

    void addAdditionalMenus(QMenuBar* menuBar) {}

...
};

QList<ParameterSettableUIViewer> EvonetUI::getViewers(QWidget* parent, Qt::WindowFlags flags)
{
    QList<ParameterSettableUIViewer> viewsList;

    networkDialog = new NetworkDialog(...);
    ...
    viewsList.append(ParameterSettableUIViewer(networkDialog, "Nervous System"));

    ...

    return viewsList;
}

The getUIManager() function must return a new instance of a subclass of ParameterSettableUI (it is a factory method). The returned object is then managed by total99. The default implementation of the function returns NULL, to indicate that the component has no GUI.

The EvonetUI class inherits from ParameterSettableUI and implements a number of methods. getViewers() is the one that creates and returns the GUIs associated to the component. The function parameters (parent and flags) are meant to be passed to constructors of the GUIs, which generally are QT widgets. The function returns a list of the GUIs that have been created together with their names (refer to the API documentation for more information). The returned objects are managed by total99.

In the example above the fillActionsMenu() and addAdditionalMenus() methods are empty. They can be implemented to respectively add actions that will be shown in the total99 Actions menu and to insert new menus in the menu bar of total99. Information on how to create new menu items and menus can be found in the QT library documentation.

Threading issues

One important aspect to keep in mind when dealing with GUIs and menus in FARSA is that when total99 is running there are two different threads: one managing the grafical user interface and one running the experiment/simulation. GUI objects (ParameterSettableUI subclasses and all the objects it creates) live in the former, components are created in the former but then run in the latter. This means that when exchanging data to and from the GUI or when actions are triggered from total99 menus, special care should be taken. FARSA does not impose the use of a specific synchronization and locking mechanism, so you are free to use whatever you like. There are, however, some helper classes that can be useful, like those implemented in the dataexchange.h header file (e.g. DataUploader, DataDownloader, ...) . Refer to the API documentation for a detailed description of those classes and examples of their use.

The Component class

Despite the name, this is a special kind of component, specifically the first one that is loaded by total99. Its use is described in this page.


Related

Manual: APIDoc
Manual: ComponentBaseExperiment
Manual: ComponentsPluginAndResources
Manual: Home
Manual: NewRobot
Manual: PluginsAndRegistration
Manual: Resources

Discussion

Anonymous
Anonymous

Add attachments
Cancel





MongoDB Logo MongoDB