Skip to content

Planner Design Framework

Schmluk edited this page Mar 29, 2019 · 8 revisions

Many sampling based planners make use of similar functionalities or approaches. To make building new planners as easy as possible, we provide a modular object-oriented framework for creating new planners.

Modular Configuration

All planners have a common structure as is presented above. The core components of TrajectoryGenerators and TrajectoryEvaluators are supposed to have a flexible enough interface to allow for a large variety of planners. However, they don't need to implement every single of their virtual functions. Each of these functions has different implementations encapsulated as modules, allowing reusage of common implementations. All functions, which are not explicitely overridden, will be built from these modular building blocks.

Modules are specified and configured via the ros parameter server, where the main planner and other submodules forward their namespace plus the respective module namespace to the ModuleFactory. Every module needs to specify a "type" parameter, according to which the desired classes are instantiated. They are then individually configured through the ros parameter server.

Building Planners

This object-oriented modular approach has the advantage, that planners consisting of already existing modules can be built at run time via a ros parameter configuration. No more recompiling for testing new planners! The recommended way is to load a "my_config.yaml" into the planner node. An example configuration is given here. The default namespaces for modules, relative to the planner node, are as follows:

trajectory_generator:
  type: "MyTrajectoryGenerator"
  segment_selector:
    type: "MyExpansionPolicy"
  generator_updater:
    type: "MyUpdateStrategy"
    
trajectory_evaluator:
  type: "MyTrajectoryEvaluator"
  cost_computer:
    type: "MyCost"
  value_computer:
    type: "MyValueFunc"
  next_selector:
    type: "MyNextBestSelection"
  evaluator_updater:
    type: "MyUpdateStrategy"
    
back_tracker:
  type: "DontGetStuck"

Notice that for some functionalities multiple modules can be combined using the Decorator Pattern:

evaluator_updater:
  type: "Operation1"
  following_updater:
    type: "Operation2"
    following_updater:
      ...

Contributing Custom Modules

Custom implementations for all module types can easily be added to the framework. Custom modules inherit from a base module type that specifies the interface with other classes. All base types inherit from the Module class, which contains utilities to configure and build modules through the ModuleFactory. To create a custom module follow these steps:

  • Create your module class

All module classes should be organized as follows:

class MyModule : public ModuleBase {
public:
    // override virtual functions of the base class
    bool moduleBasePureVirtualFunction(TrajectorySegment *root){
        /* do some magic here */
        return success;
    }

protected:
    // All modules need to be friends of the ModuleFactory to allow creation and setup
    friend ModuleFactory;

    // protected default constructor
    MyModule() {}
    
    // Statically register the module to the factory so that it can be constructed
    static ModuleFactory::Registration<MyModule> registration;

    // make the module configurable through the factory (required by Module class)
    void setupFromParamMap(Module::ParamMap *param_map){
        int my_param_default = 1;
        
         // Use the utility function of Module to set params
        setParam<int>(param_map, "my_param", &p_my_param_, my_param_default); 

        // Make sure to propagate the setup through the inheritance chain
        ModuleBase::setupFromParamMap(param_map);
    }

    // guarantee that parameters fulfill required constraints (optional by Module class)
    bool checkParamsValid(std::string *error_message) {
        if (p_my_param <= 0) {
            *error_message = "my_param expected > 0";
            return false;
        }

        // Make sure to propagate the validation check through the inheritance chain
        return ModuleBase::checkParamsValid(error_message);
    }

    // params
    int p_my_param_;
};

// make sure the registration is initialized with a unique name (i.e. the name of the class)
ModuleFactory::Registration<MyModule> MyModule::registration("MyModule");

If your module uses other modules, e.g. to be used in a chain of decorators, adapt these functions as follows:

class MyDecoratorModule : public ModuleBase {
public:
    bool moduleBasePureVirtualFunction(TrajectorySegment *root){
        // pass the request down the chain
        return following_module->moduleBasePureVirtualFunction(root);
    }

protected:
    void setupFromParamMap(Module::ParamMap *param_map){
        // Submodules are created using this formalism
        std::string args;   // the module args need to be specifiable
        std::string param_ns = (*param_map)["param_namespace"]; // default extends the parent namespace
        setParam<std::string>(param_map, "following_module_args", &args, param_ns + "/following_module");
        following_module_ = ModuleFactory::Instance()->createModuleBase(args, verbose_modules_, parent_);
    }

    // Modules are unique_ptrs
    std::unique_ptr<ModuleBase> following_module_;
};
  • Add some doc..?

For inspiration maybe look through some of the default modules.