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:
- Clause container
- Weighted clause container
- Incremental and non-incremental SAT solvers
- Incremental and non-incremental MaxSAT solvers
- PB to CNF encoder
- CNF to PB decoder
- Propagator
- Learner
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():
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::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:
This is a template, and specific classes are instantiated for each plugin type:
Paramita::PluginParameterParamita::ClauseContainerParameterParamita::NonIncrSatSolverParameterParamita::SatSolverParameterParamita::WClauseContainerParameterParamita::NonIncrMaxSatSolverParameterParamita::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.
Parameters:
nameThe name of the parameter.valueThe value to set the parameter to.
Exception:
std::invalid_argumentIf the parameter does not exist in the space.std::invalid_argumentIf 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.
Parameters:
nameThe 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:
- Clause container
- Weighted clause container
- Incremental and non-incremental SAT solvers
- Incremental and non-incremental MaxSAT solvers
- PB to CNF encoder
- CNF to PB decoder
- Propagator
- Learner
Or see how to test a plugin