Skip to content

Planner Design Framework

Lukas Schmid edited this page Jan 9, 2020 · 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 the page 'Planner Structure'. 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 in cfg/planners/example_config.yaml. 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:
      ...

Adding 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:
    // default constructor with an associated planner interface
    MyModule(PlannerI &planner);

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

protected:    
    // 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){
        // Make sure to propagate the setup through the inheritance chain
        ModuleBase::setupFromParamMap(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); 
    }

    // guarantee 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 validation checks through the inheritance chain
        return ModuleBase::checkParamsValid(error_message);
    }

    // params
    int p_my_param_;
};

// Initialize registration 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){
        // passing 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()->createModule<ModuleBase>(args, verbose_modules_);
    }

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

Please make sure the code and all parameters are human readable. Also add new modules to the Code Index of the repo with a brief description. For inspiration consider looking through some of the default modules.

Adding Custom Packages

If your new modules do not seem to fit with any of the existing packages, other dependencies are required or you want to deploy the planner in a specific application, consider grouping them into a separate package.

  • Hint: if the package consists only of additional modules that are to be used from other packages, add a short initialization function that prevents the linker from 'optimizing the package away', since the application will not explicitly depend on it.

Add a short my_module_package/initialization/my_module_package.h/cpp:

namespace active_3d_planning {
    namespace initialize {
        // force the linker to include this lib
        void my_module_package();
    } // namespace initialize
} // namespace active_3d_planning

In app_my_cool_application call the initialization function of all module-only-packages to register them to the factory:

active_3d_planning::initialize::mav_package();
active_3d_planning::initialize::voxblox_package();
active_3d_planning::initialize::my_module_package();

// instantiate and run the planner ...
  • Add some documentation

Add a short description of the package in the readme and add dependencies.