mc_rtc::Configuration general purpose configuration

This page provides information about the following topics:

  1. How to read data from an mc_rtc::Configuration object
  2. How to write data to an mc_rtc::Configuration object
  3. Which C++/Python types are supported out of the box
  4. How to write your own function to load/save C++ objects from/to an mc_rtc::Configuration object

In every example on this page, we assume that config is a C++ object of type mc_rtc::Configuration in C++ examples and a Python object of type mc_rtc.Configuration in Python examples.

Data source

mc_rtc::Configuration provides an abstraction over a JSONor a YAML object to easily manipulate the object and retrieve C++/Python data from it.

To load data into the configuration object you can use the following:

// Load from a file, assume it's JSON data
auto config = mc_rtc::Configuration("myfile.conf");
// Load from a YAML file, the extension must be yml or yaml
auto config = mc_rtc::Configuration("myfile.yaml");
// Load from already loaded JSON data
auto config = mc_rtc::Configuration::fromData(data);
// Load from already loaded YAML data
auto config = mc_rtc::Configuration::fromYAMLData(data);

Read data from the configuration

Several methods are proposed to access the data in the configuration, you can mix them in your program depending on the most appropriate. The three methods are:

  1. Strict access: the key must exist and match the type you are retrieving
  2. Optional strict access: a way to work around the existence rquirement of the strict access approach
  3. Default access: completely relax the constraints of strict access

Strict access

The first method we present will throw if the configuration entries you are accessing do not exist in the configuration file that was loaded or if the stored type does not fit the value you are trying to retrieve.

Accessing an entry is then done in this way:

Eigen::Vector3d v = config("MyVector3d");

You can also access a sub-section of the configuration:

Eigen::VectorXd v = config("section")("MyVectorXd");

And those can be nested:

std::vector<std::string> v = config("section")("sub1")("sub2")("SomeStrings");

Optional strict access

This is simply done as so:

if(conf.has("MyQuaternion"))
{
  Eigen::Quaterniond q = config("MyQuaternion");
}

The above code only throws if the MyQuaternion entry is part of the configuration but does not contain the required data to obtain an Eigen::Quaterniond object (see the previous section).

Default access method

This method can be used safely to retrieve configuration entries. It will not throw if the configuration entry does not exist or does not match the required type.

Accessing an entry is done in this way:

bool b = false;
config("MyBool", b);

You can also simplify the code above like so:

bool b = config("MyBool", false);

If the entry does not exist or does not match the required type, the provided variable is simply not modified. Otherwise, the value stored in the configuration is copied into the provided variable. This is particularly important to understand when retrieving a vector:

std::vector<double> v = {1., 2., 3.};
config("MyVector", v); // if MyVector is a valid entry, the initial content of v is lost

Similarly to the previous method, you can access sub-sections of the configuration:

double d = 1.0;
config("section")("sub1")("sub2")("MyDouble", d);

However, the previous code will throw if any of the section/sub-section does not exist in the configuration file.

Sub-section access

The strict/default access methods presented above can be applied to access a sub-section of an mc_rtc::Configuration object, e.g.

// throws if conf is not an object or section does not exist in conf
auto sub = config("section");

// returns an empty object if the call above would fail
auto sub = config("section", mc_rtc::Configuration{});

Note that copies are shallow so any changes you make to a sub-section can be seen from the parent.

Python differences

In Python, we cannot deduce the type of the requested element so one has to provide a Python type or value to retrieve data:

# c is an mc_rtc.Configuration object
c = config("key")
# retrieve a bool
b = config("b", bool)
# v is an eigen.Vector3d object
v = config("v", eigen.Vector3d)
# retrieve a bool with a default value of False
b = config("b", False)
# retrieve a list of eigen.Vector3d objects
vlist = config("vlist", [eigen.Vector3d])
# retrieve a list of float with a default value
flist = config("flist", [0., 1., 2.])

Write data to an mc_rtc::Configuration object

Here are some writing examples that demonstrate the difference in object or array APIs.

// -- Objects --

// Create a new empty object with the section key
auto section = conf.add("section");

// Add data to the section
section.add("v1", Eigen::Vector3d{1,2,3});
section.add("isFixed", true);
// Here data is of a type supported by mc_rtc::Configuration
section.add("data", data);

// -- Arrays --

// Creates a new empty array
auto array = conf.array("array");
// Creates a new empty array and reserves memory for 10 elements
auto array10 = conf.array("array10", 10);

// Add data to the array
array.push(Eigen::Vector3d{1,2,3});
array.push(true);
array.push(data);

The Python API is identical.

Supported types

As you saw in the previous examples, the Configuration object can be used to retrieve various types of object. The following table shows the supported types and JSON requirements.

Type JSON requirement
bool Either a boolean (true/false) or an integer (0 is false, anything else is true)
int An integer
unsigned int An unsigned integer or an integer that is ≥ 0
double A numeric entry
std::string A string entry
Eigen::Vector3d An array of size 3 composed of numeric elements
Eigen::Quaterniond An array of size 4 composed of numeric elements, the returned quaternion is normalized.
Eigen::Vector6d An array of size 6 composed of numeric elements
Eigen::VectorXd An array of any size composed of numeric elements
Eigen::Matrix3d An array of size 9 composed of numeric elements. Two alternatives representations are supported to support rotation It can also be an array of size 3 representing RPY angles or an array of size 4 representing a quaternion
std::vector<T> where T is any of the above types An array for which each element satisfies the requirements of type T
std::array<T, N> where T is any of the above types and `N` a fixed size An array of size N for which each elements satisfies the requirements of type T
std::pair<U, V> where U and V are any of the above types An array of size 2 whose elements satisfy the requirements of types U and `V` respectively
std::map<std::string, T> where T is any of the above types A map indexed by string whose elements satisfy the requirements of types T

We support a lot more types, including tasks and constraints, for each object that can be loaded by mc_rtc you can find a documentation on this website.

Types’ composability

The generic types supported by the configuration can be composed in any way you can imagine. For example, the following example is perfectly valid:

std::vector<std::pair<std::vector<Eigen::Vector3d>, std::vector<double>> inequalities = config("inequalities");

Support for your own types

This feature allows you to specialize mc_rtc::Configuration to support loading/save to/from your own type. This feature is a C++ only feature for now.

Non-intrusive approach

You can add a specialization to the ConfigurationLoader template in the mc_rtc namespace of the following form:

template<>
struct ConfigurationLoader<MyType>
{
  static MyType load(const mc_rtc::Configuration & config);

  static mc_rtc::Configuration save(const MyType & object);
};

This feature is used extensively in mc_rtc to provide support for many SpaceVecAlg, RBDyn and mc_rbdyn types. All declarations can be found in mc_rbdyn/configuration_io.h.

The saving function supports optional arguments so, the following is a valid specialization:

template<>
struct ConfigurationLoader<MyType>
{
  static MyType load(const mc_rtc::Configuration & config);

  static mc_rtc::Configuration save(const MyType & object, bool verbose = false);
};

Intrusive approach

You can also implement methods in your object, mc_rtc will then pick these methods automatically:

struct Foo
{
  // ...

  // This will be called to load your object from a Configuration object
  static Foo fromConfiguration(const mc_rtc::Configuration & in);

  // This will be called save your object to a Configuration object
  mc_rtc::Configuration toConfiguration() const;

  // This also supports arguments
  mc_rtc::Configuration toConfiguration(bool verbose) const;
};