Skip to content

Implementing a Plugin

Paramita is designed to be extensible, allowing users to develop their own implementations by adhering to a well-defined set of interfaces. Any implementation that follows these interfaces can be dynamically loaded into the project.

This documentation will guide you to implement the base for any Paramita plugin.

Paramita structures the plugin interfaces in the following hierarchy:

classDiagram
  Plugin <|-- ClauseContainer
  ClauseContainer <|-- NonIncrSatSolver
  NonIncrSatSolver <|-- SatSolver
  Plugin <|-- WClauseContainer
  WClauseContainer <|-- NonIncrMaxSatSolver
  NonIncrMaxSatSolver <|-- MaxSatSolver
  Plugin <|-- Learner
  Plugin <|-- PbToCnfEncoder
  Plugin <|-- CnfToPbDecoder
  Plugin <|-- Propagator

  class Plugin{}
  class ClauseContainer{}
  class Learner{}
  class NonIncrSatSolver{}
  class PbToCnfEncoder{}
  class Propagator{}
  class SatSolver{}
  class CnfToPbDecoder{}
  class WClauseContainer{}
  class NonIncrMaxSatSolver{}
  class MaxSatSolver{}

The core idea is that each implementation is compiled as a shared library, decoupling Paramita's core from specific solvers, containers, translators, and other components. This architecture allows practitioners to rapidly prototype new ideas and share their work without waiting for official Paramita releases, facilitating experimentation and collaboration.

This page will show the implementation bits shared by all the plugins. Later on, you can move to the page for each specific plugin:

The Plugin interface

Plugin is the base interface for all Paramita plugins.

Currently it defines the contract for discovering, inspecting, and manipulating parameter spaces in a uniform way.

For starters, define a new class that inherits from Plugin:

Class Paramita::Plugin

Provides capabilities to tweak parameters from the inheriting class.

This abstract class defines a common interface to get and set parameters from/to the inheriting classes, as well as a way to peek which parameters it has.

  • #include <Plugin.hpp>

Example:

class MyPlugin : public Plugin {
    // Here we would also add constructors, destructors, and such, if needed.
};

Defining the parameter space

At the core of Plugin is the concept of a parameter space.

A parameter space describes which parameters the interface has, by defining its type, its domain, and its default value. This is represented by the following class:

Class Paramita::ParameterSpace

Manages a collection of parameters, each identified by a unique name.

  • #include <ParameterSpace.hpp>

To start implementing a configurable extension, you need to override the method get_parameter_space():

function get_parameter_space

Get the parameter space for this class.

virtual ParameterSpace Paramita::Plugin::get_parameter_space () 

Returns:

The parameter space.


Let's define some parameters for our extension:

  • A categorical parameter where we can choose between "a", "b", and "c". By default we use "a".
  • An integer parameter, where valid values are in the range between 1 and 10 (inclusive). By default we use 1.
  • A real parameter, with a domain between 0 and 2 (inclusive). By default we use 0.5.
  • A boolean parameter. By default it is set to false.
class MyPlugin : public Plugin {
    // ...
  public:
    ParameterSpace get_parameter_space() override {
        return {m_params};
}

  private:
    static ParameterSpace m_params;
};

ParameterSpace MyPlugin::m_params(std::unordered_map<std::string, const Parameter &>{
    {"my_choice", CategoricalParameter("a", {"a", "b", "c"})},
    {"my_int", IntegerParameter(1, Range<int>{1, 10})},
    {"my_float", RealParameter(.5f, Range<float>{0.f, 2.f})},
    {"my_bool", BooleanParameter(false)},
});

Parameter types

Paramita supports a wide variety of parameter types:

Class Paramita::Parameter

Base class for all parameter types.

  • #include <ParameterSpace.hpp>

Class Paramita::CategoricalParameter

Represents a categorical parameter with discrete choices.

Values for the choices are std::string.

  • #include <ParameterSpace.hpp>

Inherits the following classes: Paramita::Parameter

Class Paramita::IntegerParameter

Represents an integer-valued parameter with a range of valid values.

The domain is an inclusive Range.

  • #include <ParameterSpace.hpp>

Inherits the following classes: Paramita::Parameter

Class Paramita::BooleanParameter

Represents a boolean-valued parameter.

  • #include <ParameterSpace.hpp>

Inherits the following classes: Paramita::Parameter

It also supports using the different interfaces as a parameter:

Class Paramita::InterfaceParameter

template

Inherits the following classes: Paramita::Parameter

This is a template, and specific classes are instantiated for each plugin type:

  • Paramita::PluginParameter
  • Paramita::ClauseContainerParameter
  • Paramita::NonIncrSatSolverParameter
  • Paramita::SatSolverParameter
  • Paramita::WClauseContainerParameter
  • Paramita::NonIncrMaxSatSolverParameter
  • Paramita::PbToCnfEncoderParameter


All the valid values for the different parameters are aggregated in the ParameterType std::variant.

Reading and writing parameters

As a configurable, we also want our class to be able to change the parameters, and allow users to query the current values.

To accept new values for a parameter we will implement the set method:

function set

Set a parameter.

virtual Paramita::Plugin::set (
    const std::string & name,
    const ParameterType & value
) 

Parameters:

  • name The name of the parameter.
  • value The value to set the parameter to.

Exception:

  • std::invalid_argument If the parameter does not exist in the space.
  • std::invalid_argument If the parameter value is not valid for this name.

class MyPlugin : public Plugin {
    // ...
  public:
    void set(const std::string &name, const ParameterType &value) override {
        if (name == "my_choice" && m_params.get_parameter("my_choice").is_valid(value)) {
            m_my_choice = std::get<std::string>(value);
        } else if (name == "my_int" && m_params.get_parameter("my_int").is_valid(value)) {
            m_my_int = std::get<int>(value);
        } else if (name == "my_float" && m_params.get_parameter("my_float").is_valid(value)) {
            m_my_float = std::get<float>(value);
        } else if (name == "my_bool" && m_params.get_parameter("my_bool").is_valid(value)) {
            m_my_bool = std::get<bool>(value);
        } else {
            throw std::runtime_error("unexpected value or name");
        }
    }

  private:
    static ParameterSpace m_params;

    std::string m_my_choice{"a"};
    int m_my_int{1};
    float m_my_float{.5f};
    bool m_my_bool{false};
};

Note

Note that we are checking if the parameter is one that we know using the if/else structure, and validating its value using the parameter itself. The validation will check that the new value is in the domain we defined in our parameter space.

Note

We could also define the parameters by using the default_value() method for each parameter in m_params.

We can finish the implementation of our configurable by adding the get method:

function get

Get the parameter's value.

virtual ParameterType Paramita::Plugin::get (
    const std::string & name
) 

Parameters:

  • name The name of the parameter.

Returns:

The value of the parameter.


class MyPlugin : public Plugin {
    // ...
  public:
    ParameterType get(const std::string &name) override {
        if (name == "my_choice") {
            return m_my_choice;
        } else if (name == "my_int") {
            return m_my_int;
        } else if (name == "my_float") {
            return m_my_float;
        } else if (name == "my_bool") {
            return m_my_bool;
        } else {
            throw std::runtime_error("unexpected name");
        }
    }
    //...
};

Next steps

You can move to implement a specific plugin:

Or see how to test a plugin