Composable Attribute System API#
The Composable Attribute System allows runtime composition of vehicle routing problem features without inheritance hierarchies. Instead of creating a new class for each problem variant (VRP -> CVRP -> CVRPTW), you enable attributes at runtime.
Quick Start#
#include <plugins/PluginBundle.hpp>
#include <plugins/attributes/ComposableCorePlugin/ComposableProblem.hpp>
#include <plugins/attributes/RoutingPlugin/GeoNode.hpp>
#include <plugins/attributes/CapacityPlugin/Consumer.hpp>
#include <plugins/attributes/CapacityPlugin/Stock.hpp>
#include <plugins/attributes/TimeWindowPlugin/Rendezvous.hpp>
int main() {
// Register and initialize plugins
routing::plugins::registerCorePlugins();
routing::PluginRegistry::instance().initializeAll();
// Create a composable problem
routing::ComposableProblem problem;
// Enable CVRPTW attributes
problem.enableAttributes<
routing::attributes::GeoNode,
routing::attributes::Consumer,
routing::attributes::Stock,
routing::attributes::Rendezvous,
routing::attributes::ServiceQuery
>();
// Add depot
auto* depot = problem.addDepot(0);
depot->addAttribute<routing::attributes::GeoNode>(0.0, 0.0);
depot->addAttribute<routing::attributes::Rendezvous>(0, 1000);
// Add vehicles
for (int v = 0; v < 3; ++v) {
auto* vehicle = problem.addVehicle(v);
vehicle->addAttribute<routing::attributes::Stock>(100);
}
// Add clients
auto* client = problem.addClient(1);
client->addAttribute<routing::attributes::GeoNode>(10.0, 20.0);
client->addAttribute<routing::attributes::Consumer>(15);
client->addAttribute<routing::attributes::Rendezvous>(50, 200);
client->addAttribute<routing::attributes::ServiceQuery>(10);
// Constraint generators are auto-activated based on enabled attributes
auto generators = problem.getActiveGenerators();
// Returns: RoutingConstraintGenerator, CapacityConstraintGenerator, TimeWindowConstraintGenerator
// Cleanup
routing::PluginRegistry::instance().shutdownAll();
return 0;
}
Core Classes#
ComposableProblem#
The main problem class for composable modeling.
namespace routing {
class ComposableProblem : public Problem {
public:
// Enable specific attributes for this problem
template<typename... Attrs>
void enableAttributes();
// Check if an attribute is enabled
template<typename T>
bool isAttributeEnabled() const;
// Entity management
ComposableClient* addClient(unsigned id);
ComposableVehicle* addVehicle(unsigned id);
ComposableDepot* addDepot(unsigned id);
// Access entities
std::vector<ComposableClient*> getComposableClients() const;
std::vector<ComposableVehicle*> getComposableVehicles() const;
ComposableDepot* getComposableDepot() const;
// Get active generators for enabled attributes
std::vector<IConstraintGenerator*> getActiveGenerators() const;
// Problem statistics
size_t numClients() const;
size_t numVehicles() const;
};
}
ComposableEntity#
Base class for entities with runtime attributes.
namespace routing {
class ComposableEntity : public virtual Model {
public:
// Add an attribute to this entity
template<typename T, typename... Args>
T* addAttribute(Args&&... args);
// Get an attribute (throws if not present)
template<typename T>
T& getAttribute();
template<typename T>
const T& getAttribute() const;
// Try to get an attribute (returns nullptr if not present)
template<typename T>
T* tryGetAttribute();
template<typename T>
const T* tryGetAttribute() const;
// Check if entity has an attribute
template<typename T>
bool hasAttribute() const;
// Get all attribute type IDs
std::vector<AttributeTypeId> getAttributeTypes() const;
};
// Specialized entity types
class ComposableClient : public ComposableEntity, public models::Client {};
class ComposableVehicle : public ComposableEntity, public models::Vehicle {};
class ComposableDepot : public ComposableEntity, public models::Depot {};
}
Available Attributes#
Routing Attributes#
Attribute |
Location |
Description |
|---|---|---|
|
|
Geographic coordinates (x, y) |
#include <plugins/attributes/RoutingPlugin/GeoNode.hpp>
client->addAttribute<routing::attributes::GeoNode>(x, y);
auto* geo = client->tryGetAttribute<routing::attributes::GeoNode>();
double x = geo->getX();
double y = geo->getY();
Capacity Attributes#
Attribute |
Location |
Description |
|---|---|---|
|
|
Demand at a node |
|
|
Vehicle capacity |
#include <plugins/attributes/CapacityPlugin/Consumer.hpp>
#include <plugins/attributes/CapacityPlugin/Stock.hpp>
client->addAttribute<routing::attributes::Consumer>(15); // demand of 15
vehicle->addAttribute<routing::attributes::Stock>(100); // capacity of 100
Time Window Attributes#
Attribute |
Location |
Description |
|---|---|---|
|
|
Hard time window bounds |
|
|
Service time at node |
|
|
Soft TW with penalties |
|
|
Fixed appointment times |
|
|
Service characteristics |
#include <plugins/attributes/TimeWindowPlugin/Rendezvous.hpp>
#include <plugins/attributes/TimeWindowPlugin/ServiceQuery.hpp>
client->addAttribute<routing::attributes::Rendezvous>(50, 200); // TW [50, 200]
client->addAttribute<routing::attributes::ServiceQuery>(10); // service time 10
Profit Attributes#
Attribute |
Location |
Description |
|---|---|---|
|
|
Profit for visiting node |
#include <plugins/attributes/ProfitPlugin/Profiter.hpp>
client->addAttribute<routing::attributes::Profiter>(100.0); // profit of 100
Pickup & Delivery Attributes#
Attribute |
Location |
Description |
|---|---|---|
|
|
Pickup node |
|
|
Delivery node |
#include <plugins/attributes/PickupDeliveryPlugin/Pickup.hpp>
#include <plugins/attributes/PickupDeliveryPlugin/Delivery.hpp>
pickupClient->addAttribute<routing::attributes::Pickup>(10); // pickup 10 units
deliveryClient->addAttribute<routing::attributes::Delivery>(10); // deliver 10 units
Synchronization Attributes#
Attribute |
Location |
Description |
|---|---|---|
|
|
Temporal synchronization |
#include <plugins/attributes/SyncPlugin/Synced.hpp>
// Two clients that must be visited at the same time
client1->addAttribute<routing::attributes::Synced>(0); // sync group 0
client2->addAttribute<routing::attributes::Synced>(0); // sync group 0
Constraint Generators#
Constraint generators are automatically activated when their required attributes are enabled.
Generator |
Required Attributes |
Priority |
Purpose |
|---|---|---|---|
|
|
10 |
Base routing constraints |
|
|
50 |
Vehicle capacity |
|
|
60 |
Hard time windows |
|
|
61 |
Soft time windows |
|
|
55 |
P&D precedence |
|
|
70 |
Temporal synchronization |
|
|
1000 |
Maximize profit |
How Auto-Activation Works#
When you call problem.enableAttributes<A, B, C>():
The problem registers attribute types A, B, C as enabled
The PluginRegistry is queried for all registered generators
Each generator’s
isApplicable(enabledAttrs)method is checkedMatching generators are sorted by priority (lower = earlier)
Generators are stored in
activeGenerators_
// Get active generators
auto generators = problem.getActiveGenerators();
// Manual check
for (auto* gen : generators) {
std::cout << gen->name() << " (priority: " << gen->priority() << ")" << std::endl;
}
Creating Custom Attributes#
Step 1: Define the Attribute#
// plugins/attributes/MyPlugin/MyAttribute.hpp
#pragma once
#include "core/interfaces/IAttribute.hpp"
namespace routing::attributes {
class MyAttribute : public Attribute<MyAttribute> {
public:
explicit MyAttribute(double value) : value_(value) {}
double getValue() const { return value_; }
void setValue(double v) { value_ = v; }
private:
double value_;
};
} // namespace routing::attributes
Step 2: Create a Constraint Generator (Optional)#
// plugins/attributes/MyPlugin/MyConstraintGenerator.hpp
#pragma once
#include "core/interfaces/IConstraintGenerator.hpp"
#include "MyAttribute.hpp"
namespace routing::constraints {
class MyConstraintGenerator : public IConstraintGenerator {
public:
std::string name() const override { return "MyConstraintGenerator"; }
int priority() const override { return 100; }
std::set<AttributeTypeId> requiredAttributes() const override {
return { routing::attributes::MyAttribute::staticTypeId() };
}
#ifdef CPLEX_FOUND
void addVariables(ComposableProblem* problem, IloEnv& env, IloModel& model) override {
// Add MIP variables
}
void addConstraints(ComposableProblem* problem, IloEnv& env, IloModel& model) override {
// Add MIP constraints
}
#endif
};
} // namespace routing::constraints
Step 3: Create a Plugin to Register#
// plugins/attributes/MyPlugin/MyPlugin.hpp
#pragma once
#include "core/IPlugin.hpp"
#include "core/PluginRegistry.hpp"
#include "MyConstraintGenerator.hpp"
namespace routing::plugins {
class MyPlugin : public IPlugin {
public:
std::string name() const override { return "MyPlugin"; }
PluginType type() const override { return PluginType::Attribute; }
void initialize(PluginRegistry& registry) override {
if (!registry.hasGenerator("MyConstraintGenerator")) {
registry.registerGenerator(
std::make_unique<constraints::MyConstraintGenerator>());
}
}
};
} // namespace routing::plugins
Step 4: Register in PluginBundle#
Add to plugins/PluginBundle.cpp:
#include "plugins/attributes/MyPlugin/MyPlugin.hpp"
void registerCorePlugins() {
auto& registry = PluginRegistry::instance();
// ... existing plugins ...
registry.registerPlugin(std::make_unique<MyPlugin>());
}
Problem Types with Composable Attributes#
Problem |
Enabled Attributes |
|---|---|
VRP |
|
CVRP |
|
VRPTW |
|
CVRPTW |
|
TOP |
|
TOPTW |
|
PDVRP |
|
VRPTWTD |
|
Using with Solvers#
MIP Solver#
#include <plugins/solvers/MIPSolverPlugin/MIPSolver.hpp>
routing::ComposableProblem problem;
problem.enableAttributes<GeoNode, Consumer, Stock>();
// Add entities...
routing::MIPSolver solver(&problem);
bool solved = solver.solve(60.0); // 60 second timeout
if (solved) {
auto* solution = solver.getSolution();
solution->print(std::cout);
}
Using PluginRegistry Factory#
routing::plugins::registerCorePlugins();
auto& registry = routing::PluginRegistry::instance();
registry.initializeAll();
routing::ComposableProblem problem;
// ... configure problem ...
// Create solver via registry
auto solver = registry.createSolver("mip", &problem);
solver->solve(60.0);
Best Practices#
Enable Only Needed Attributes: More attributes = more constraints = slower solving
Use tryGetAttribute for Safety: Returns nullptr if attribute not present
Check Active Generators: Verify expected generators are activated
Initialize Plugins Once: Call
registerCorePlugins()andinitializeAll()at startupShutdown at Exit: Call
shutdownAll()before program exit
Example: Complete CVRPTW Program#
See examples/cvrptw.cpp and examples/top.cpp for CLI examples using composable readers.
./build/examples/example_cvrptw -i data/CVRPTW/Solomon/10/c101.txt
./build/examples/example_top -i data/CVRP/A/A-n32-k5.vrp