Plugin System API#
The Plugin System provides a modular architecture for extending the routing framework with new solvers, neighborhoods, readers, and constraint generators.
Quick Start#
#include <plugins/PluginBundle.hpp>
#include <core/PluginRegistry.hpp>
int main() {
// Register all core plugins
routing::plugins::registerCorePlugins();
// Initialize all plugins (respects dependency order)
auto& registry = routing::PluginRegistry::instance();
registry.initializeAll();
// Use registered components
auto solver = registry.createSolver("mip", problem);
solver->solve(60.0);
// Cleanup
registry.shutdownAll();
return 0;
}
Core Classes#
IPlugin#
Base interface for all plugins.
namespace routing {
enum class PluginType {
Attribute,
ConstraintGenerator,
Evaluator,
Solver,
Neighborhood,
Reader
};
class IPlugin {
public:
virtual ~IPlugin() = default;
// Unique plugin identifier
virtual std::string name() const = 0;
// Semantic version
virtual std::string version() const { return "1.0.0"; }
// Human-readable description
virtual std::string description() const { return ""; }
// Plugin category
virtual PluginType type() const = 0;
// List of plugin names this depends on
virtual std::vector<std::string> dependencies() const { return {}; }
// Called during initializeAll() - register components here
virtual void initialize(PluginRegistry& registry) = 0;
// Called during shutdownAll()
virtual void shutdown() {}
};
} // namespace routing
PluginRegistry#
Singleton registry for all plugins and components.
namespace routing {
class PluginRegistry {
public:
// Singleton access
static PluginRegistry& instance();
// Plugin lifecycle
void registerPlugin(std::unique_ptr<IPlugin> plugin);
void initializeAll();
void shutdownAll();
IPlugin* getPlugin(const std::string& name) const;
// Constraint generator management
void registerGenerator(std::unique_ptr<IConstraintGenerator> generator);
IConstraintGenerator* getGenerator(const std::string& name) const;
bool hasGenerator(const std::string& name) const;
std::vector<IConstraintGenerator*> getGenerators(
const std::set<AttributeTypeId>& enabledAttrs) const;
std::vector<std::string> availableGenerators() const;
// Solver factory
using SolverFactory = std::function<std::unique_ptr<ISolver>(Problem*)>;
void registerSolver(const std::string& name, SolverFactory factory);
std::unique_ptr<ISolver> createSolver(const std::string& name, Problem* problem) const;
std::vector<std::string> availableSolvers() const;
// Neighborhood factory
using NeighborhoodFactory = std::function<std::unique_ptr<INeighborhood>()>;
void registerNeighborhood(const std::string& name, NeighborhoodFactory factory);
std::unique_ptr<INeighborhood> createNeighborhood(const std::string& name) const;
std::vector<std::string> availableNeighborhoods() const;
// Reader factory
using ReaderFactory = std::function<std::unique_ptr<IReader>()>;
void registerReader(const std::string& formatName,
const std::vector<std::string>& extensions,
ReaderFactory factory);
std::unique_ptr<IReader> createReader(const std::string& formatName) const;
std::unique_ptr<IReader> createReaderForExtension(const std::string& extension) const;
std::vector<std::string> availableReaders() const;
// Testing support
void clear();
};
} // namespace routing
Plugin Types#
Solver Plugins#
Solvers implement the ISolver interface:
namespace routing {
class ISolver {
public:
virtual ~ISolver() = default;
virtual std::string name() const = 0;
virtual std::string description() const { return ""; }
virtual void setProblem(Problem* problem) = 0;
virtual Problem* getProblem() const = 0;
virtual void setConfiguration(Configuration* config) = 0;
virtual void setDefaultConfiguration() = 0;
virtual bool solve(double timeout = 3600) = 0;
virtual models::Solution* getSolution() const = 0;
virtual double getObjectiveValue() const = 0;
virtual bool isOptimal() const { return false; }
virtual std::string getStats() const { return ""; }
};
} // namespace routing
Available Solvers:
Name |
Aliases |
Description |
|---|---|---|
|
|
Exact MIP solver (CPLEX) |
|
|
Genetic Algorithm |
|
|
Memetic Algorithm |
|
|
Local Search |
|
- |
Variable Neighborhood Search |
|
|
Particle Swarm Optimization |
Example Plugin:
// plugins/solvers/MySolverPlugin/MySolverPlugin.hpp
#pragma once
#include "core/IPlugin.hpp"
#include "core/PluginRegistry.hpp"
#include "core/interfaces/ISolver.hpp"
namespace routing::plugins {
class MySolverWrapper : public ISolver {
public:
explicit MySolverWrapper(Problem* problem) : problem_(problem) {}
std::string name() const override { return "my_solver"; }
void setProblem(Problem* p) override { problem_ = p; }
Problem* getProblem() const override { return problem_; }
void setConfiguration(Configuration* c) override { config_ = c; }
void setDefaultConfiguration() override { /* ... */ }
bool solve(double timeout) override {
// Implement solving logic
return true;
}
models::Solution* getSolution() const override { return solution_; }
double getObjectiveValue() const override {
return solution_ ? solution_->getCost() : 0.0;
}
private:
Problem* problem_;
Configuration* config_ = nullptr;
models::Solution* solution_ = nullptr;
};
class MySolverPlugin : public IPlugin {
public:
std::string name() const override { return "MySolverPlugin"; }
PluginType type() const override { return PluginType::Solver; }
void initialize(PluginRegistry& registry) override {
registry.registerSolver("my_solver",
[](Problem* problem) -> std::unique_ptr<ISolver> {
return std::make_unique<MySolverWrapper>(problem);
});
}
};
} // namespace routing::plugins
Neighborhood Plugins#
Neighborhoods implement the INeighborhood interface:
namespace routing {
struct NeighborhoodMove {
std::string type;
double delta = 0.0;
std::vector<int> data;
bool isImproving() const { return delta < 0.0; }
};
class INeighborhood {
public:
virtual ~INeighborhood() = default;
virtual std::string name() const = 0;
virtual std::string description() const { return ""; }
// Find best move
virtual std::optional<NeighborhoodMove> explore(models::Solution& solution) = 0;
// Apply a move
virtual void apply(models::Solution& solution, const NeighborhoodMove& move) = 0;
// Convenience: explore and apply if improving
virtual bool improve(models::Solution& solution);
};
} // namespace routing
Available Neighborhoods:
Name |
Description |
|---|---|
|
2-opt edge exchange |
|
Iterated Destruction-Construction |
Reader Plugins#
Readers implement the IReader interface:
namespace routing {
class IReader {
public:
virtual ~IReader() = default;
virtual std::string formatName() const = 0;
virtual std::vector<std::string> supportedExtensions() const = 0;
virtual bool canRead(const std::string& filepath) const = 0;
virtual Problem* readFile(const std::string& filepath) = 0;
virtual std::string detectedProblemType() const { return "unknown"; }
};
} // namespace routing
Available Readers:
Name |
Extensions |
Description |
|---|---|---|
|
|
Solomon CVRPTW format |
|
|
TSPLIB/CVRPLIB format |
Usage:
// Read by format name
auto reader = registry.createReader("solomon");
auto* problem = reader->readFile("R101.txt");
// Auto-detect by extension
auto reader = registry.createReaderForExtension(".vrp");
auto* problem = reader->readFile("A-n32-k5.vrp");
Constraint Generator Plugins#
See Composable API Documentation for constraint generators.
Creating a New Plugin#
Step 1: Create Plugin Header#
// plugins/my_category/MyPlugin/MyPlugin.hpp
#pragma once
#include "core/IPlugin.hpp"
#include "core/PluginRegistry.hpp"
namespace routing::plugins {
class MyPlugin : public IPlugin {
public:
std::string name() const override { return "MyPlugin"; }
std::string version() const override { return "1.0.0"; }
std::string description() const override {
return "My custom plugin";
}
PluginType type() const override { return PluginType::Solver; }
// Optional: declare dependencies
std::vector<std::string> dependencies() const override {
return {"RoutingPlugin"}; // Initialized after RoutingPlugin
}
void initialize(PluginRegistry& registry) override {
// Register components here
}
void shutdown() override {
// Cleanup if needed
}
};
} // namespace routing::plugins
Step 2: Register in PluginBundle#
Edit plugins/PluginBundle.cpp:
#include "plugins/my_category/MyPlugin/MyPlugin.hpp"
void registerCorePlugins() {
auto& registry = PluginRegistry::instance();
// ... existing plugins ...
registry.registerPlugin(std::make_unique<MyPlugin>());
}
And add the include in plugins/PluginBundle.hpp:
#include "plugins/my_category/MyPlugin/MyPlugin.hpp"
Step 3: Build and Test#
cmake --build build --parallel
ctest --test-dir build --output-on-failure
Plugin Lifecycle#
registerCorePlugins()
Calls registry.registerPlugin() for each plugin
Stores plugins in registry
registry.initializeAll()
Resolves dependencies (topological sort)
For each plugin in order:
plugin->initialize(registry)
Plugin registers its components
Sets initialized_ = true
[Application uses registered components]
registry.shutdownAll()
For each plugin in reverse order:
plugin->shutdown()
Dependency Management#
Plugins can declare dependencies on other plugins:
std::vector<std::string> dependencies() const override {
return {"ComposableCorePlugin", "RoutingPlugin"};
}
The registry uses topological sort to ensure:
Dependencies are initialized before dependents
Shutdown happens in reverse order
Circular dependencies throw an exception
Querying Available Components#
auto& registry = routing::PluginRegistry::instance();
// List all available solvers
for (const auto& name : registry.availableSolvers()) {
std::cout << "Solver: " << name << std::endl;
}
// List all available neighborhoods
for (const auto& name : registry.availableNeighborhoods()) {
std::cout << "Neighborhood: " << name << std::endl;
}
// List all available readers
for (const auto& name : registry.availableReaders()) {
std::cout << "Reader: " << name << std::endl;
}
// List all available generators
for (const auto& name : registry.availableGenerators()) {
std::cout << "Generator: " << name << std::endl;
}
Core Plugins#
Attribute Plugins#
Plugin |
Type |
Registers |
|---|---|---|
|
Attribute |
Core composable infrastructure |
|
ConstraintGenerator |
|
|
Attribute |
|
|
Attribute |
|
|
Attribute |
|
|
Attribute |
|
|
Attribute |
|
Solver Plugins#
Plugin |
Registers |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
Neighborhood Plugins#
Plugin |
Registers |
|---|---|
|
|
|
|
Reader Plugins#
Plugin |
Registers |
Extensions |
|---|---|---|
|
|
|
|
|
|
Testing Plugins#
Example test using Google Test:
#include <gtest/gtest.h>
#include <plugins/PluginBundle.hpp>
#include <core/PluginRegistry.hpp>
class PluginTest : public ::testing::Test {
protected:
void SetUp() override {
routing::plugins::registerCorePlugins();
registry_ = &routing::PluginRegistry::instance();
registry_->initializeAll();
}
void TearDown() override {
registry_->shutdownAll();
registry_->clear();
}
routing::PluginRegistry* registry_;
};
TEST_F(PluginTest, SolversRegistered) {
auto solvers = registry_->availableSolvers();
EXPECT_NE(std::find(solvers.begin(), solvers.end(), "mip"), solvers.end());
EXPECT_NE(std::find(solvers.begin(), solvers.end(), "vns"), solvers.end());
}
TEST_F(PluginTest, CreateSolver) {
// Need a problem to create solver
routing::ComposableProblem problem;
auto solver = registry_->createSolver("mip", &problem);
EXPECT_NE(solver, nullptr);
EXPECT_EQ(solver->name(), "mip");
}
Best Practices#
One Plugin Per Component Type: Keep plugins focused
Declare Dependencies: Use
dependencies()if order mattersCheck Before Registering: Use
hasGenerator(), etc. to avoid duplicatesUse Factory Pattern: Register factories, not instances
Clean Shutdown: Implement
shutdown()if you allocate resourcesVersion Your Plugins: Use
version()for compatibility tracking
Directory Structure#
plugins/
+-- CMakeLists.txt
+-- PluginBundle.hpp # Declares registerCorePlugins()
+-- PluginBundle.cpp # Implements plugin registration
+-- attributes/
| +-- ComposableCorePlugin/
| +-- RoutingPlugin/
| +-- CapacityPlugin/
| +-- TimeWindowPlugin/
| +-- ProfitPlugin/
| +-- PickupDeliveryPlugin/
| +-- SyncPlugin/
+-- solvers/
| +-- GASolverPlugin/
| +-- LSSolverPlugin/
| +-- MASolverPlugin/
| +-- MIPSolverPlugin/
| +-- PSOSolverPlugin/
| +-- VNSSolverPlugin/
+-- neighborhoods/
| +-- TwoOptPlugin/
| +-- IDCHPlugin/
+-- readers/
+-- SolomonReaderPlugin/
+-- TSPLIBReaderPlugin/