SDK Solver Plugin Development Guide
[!NOTE] See Feature Implementation Status (Remaining Functionality) for the latest implementation status.
Overview
EvoSpikeNet provides a SolverPlugin mechanism that allows any combinatorial optimisation,
QAOA, or quantum-annealing backend to be swapped in as a plugin.
Typical use cases: - Classical SA / Greedy — default for real-time constrained scenarios - QAOA Simulator — prototype evaluation with classical approximation - Qiskit (IBMQ / Aer) — quantum hardware connectivity - D-Wave — quantum annealing device connectivity
Architecture
QPFCQAOABridge
└─ get_optimizer(name) # evospikenet/optimizer.py
├─ PluginFactory.SOLVER # plugin registry (checked first)
│ ├─ "classical" → ClassicalSolverPlugin (SA)
│ ├─ "qaoa_simulator" → QAOASimulatorSolverPlugin
│ ├─ "qiskit" → QiskitSolverPlugin (Aer / IBMQ)
│ └─ "dwave" → DWaveSolverPlugin (QPU / SA fallback)
└─ built-in backends (fallback)
Installation
Minimal (classical / qaoa_simulator only):
pip install -e .
Enable Qiskit backend:
pip install qiskit qiskit-aer
Enable D-Wave backend:
pip install dwave-ocean-sdk
Install the complete optional quantum backend set:
pip install -r EvoSpikeNet-Core/requirements-quantum.txt
After installation, check dependency consistency:
python -m pip check
On Python 3.14, a wheel for the symengine<0.14 range required by Qiskit 1.x may be unavailable. Confirm that the installed Qiskit version is within the Core support range before attempting live hardware access.
SolverPlugin Interface
Required methods:
| Method | Description |
|---|---|
get_metadata() |
Return PluginMetadata with plugin_type=PluginType.SOLVER |
initialize() |
Load dependencies, verify device connectivity |
activate() |
Transition plugin to active state |
deactivate() |
Release resources |
solve(problem, timeout, options) |
Run optimisation, return sorted candidate list |
solve() signature and return value:
def solve(
self,
problem: Dict[str, Any], # vehicles/stops or problem["qubo"]
timeout: float = 5.0, # seconds
options: Dict[str, Any] = None, # solver-specific parameters
) -> List[Dict[str, Any]]:
# Returns candidates sorted by ascending score (lower is better)
# Each element: {"assignment": {...}, "score": float, "meta": {...}}
QUBO input representation contract
For route allocation and other QUBO problems, problem["qubo"] may be a dense list[list[float]], a sparse dictionary {(row, column): coefficient} with zero terms omitted, or a legacy problem dictionary containing vehicles, stops, and distances. New large-scale problems should use the sparse dictionary as the canonical form and pass it directly to plugins that support it. Plugins such as Qiskit integrations that require a dense matrix must materialize it only at the solver boundary. Plugins must not mutate the input and should record the accepted representation and conversions in meta.
QuboProblem exposes to_dense() and to_sparse() for numerical-equivalence checks. Large conflict construction should be indexed by shared vehicle, time slot, or overlapping trajectory; sparse storage alone does not justify an all-pairs comparison.
Creating a Custom Solver Plugin
1. Subclass SolverPlugin
from evospikenet.plugins.solver_plugin import SolverPlugin
from evospikenet.plugins import PluginMetadata, PluginStatus, PluginType
class MyAnnealerPlugin(SolverPlugin):
def get_metadata(self) -> PluginMetadata:
return PluginMetadata(
name="my_annealer",
version="1.0.0",
plugin_type=PluginType.SOLVER,
description="Custom annealing backend",
author="Your Team",
)
def initialize(self) -> bool:
self.status = PluginStatus.INITIALIZED
return True
def activate(self) -> bool:
self.status = PluginStatus.ACTIVE
return True
def deactivate(self) -> bool:
self.status = PluginStatus.UNLOADED
return True
def solve(self, problem, timeout=5.0, options=None):
# Implement: optimise problem and return candidates
...
return [{"assignment": result, "score": cost, "meta": {"backend": "my_annealer"}}]
2. Register with PluginFactory
import evospikenet.plugin_factory as _pf_mod
from evospikenet.optimizer import get_optimizer
factory = _pf_mod._global_factory # or PluginFactory()
plugin = MyAnnealerPlugin()
plugin.initialize()
plugin.activate()
factory.register_plugin(plugin)
# Now accessible by name
opt = get_optimizer("my_annealer")
result = opt.solve(problem, timeout=5.0)
3. Allow custom module via EVOSPIKENET_PLUGIN_ALLOWLIST
export EVOSPIKENET_PLUGIN_ALLOWLIST="evospikenet.plugins,myorg.plugins"
Built-in Backend Details
classical (SimulatedAnnealing)
- Dependencies: none
- Notes: Random-swap SA. Default for real-time scenarios.
- options:
seed(int)
qaoa_simulator
- Dependencies: none
- Notes: Classical QAOA approximation using SA/SPSA/greedy.
- options:
seed(int)
qiskit
- Dependencies:
qiskit,qiskit-aer - Notes: Select the local simulator with
execution_target="aer"or IBM hardware withexecution_target="ibm_qpu". The plugin mapsproblem["qubo"]to QAOA; an IBM QPU failure is not reported as a successful local run. - Auth: Set the token environment variable selected by
token_env. The Core plugin compatibility default isIBMQ_TOKEN; application integrations should explicitly selectQISKIT_IBM_TOKEN. - options:
provider(instance),backend_name,execution_target,token_env,reps,shots
export QISKIT_IBM_TOKEN="<your_ibm_quantum_token>"
export QISKIT_IBM_INSTANCE="<instance CRN>"
export QISKIT_IBM_BACKEND="<backend ID>"
dwave
- Dependencies:
dwave-ocean-sdk - Notes: D-Wave QPU / SimulatedAnnealing / Tabu sampler. Falls back to SA when SDK is absent.
- Auth: Set
DWAVE_API_TOKENandDWAVE_API_ENDPOINTenv vars. - options:
sampler("QPU"|"SimulatedAnnealing"|"Tabu"),num_reads
export DWAVE_API_TOKEN="<your_dwave_token>"
openjij
- Dependencies:
openjij - Notes: OpenJij SA / SQA / GPU samplers. Local SA/SQA normally requires no token.
- options:
sampler(SA,SQA,GPU),num_reads,num_sweeps
fixstars_amplify
- Dependencies:
amplify - Notes: Fixstars AE, Toshiba SQBM v2, and Fujitsu DA3C/DA4 clients through the same plugin contract.
- options:
client(fixstars,toshiba_sbm,fujitsu_da3,fujitsu_da4), Toshibasolver(Qubo,Qplib,Pubo),token_env,url_env,timeout_ms - Auth: Fixstars AE uses
FIXSTARS_TOKEN; without it, the plugin falls back to classical SA.
Toshiba accepts AMPLIFY_TOSHIBA_TOKEN, or AMPLIFY_TOSHIBA_URL for an AWS-hosted endpoint. Fujitsu DA3C and DA4 use AMPLIFY_FUJITSU_TOKEN; AMPLIFY_FUJITSU_URL is optional and should only be set when the provider supplies a custom endpoint. Valid QUBO execution failures propagate as failures; do not confuse legacy VRP classical fallback with successful external execution.
Aliases and CPU Classical
get_optimizer() normalizes solver names to lowercase underscore form. fixstars, fixstars_amplify_sdk, and amplify resolve to fixstars_amplify; jij and open_jij resolve to openjij.
classical also uses the SolverPlugin-compatible interface. Applications do not need a separate CPU-only execution path.
Passing Configuration from the UAV Control API
The UAV control API stores the selected device configuration with:
PUT /api/v1/control/route-optimization/device
Content-Type: application/json
{"device_id":"qiskit-local","config":{"solver_backend":"qiskit","backend_name":"aer_simulator","execution_target":"aer","shots":256}}
The configuration is passed to the plugin factory on the next backend=auto request. Route planning also snapshots the selected device_id and non-secret solver configuration into that run's solver_config. Never store tokens in JSON or browser local storage.
Verification
PYTHONPATH=EvoSpikeNet-Core python -m pytest \
EvoSpikeNet-Core/tests/unit/test_priority2_type_safety_contracts.py -q
python -m pytest EvoSpikeNet-Core/tests/unit/test_qiskit_solver_plugin_optimization.py -q
python -m pytest future_apps/uav_swarm_control/tests/integration/test_route_optimization.py -q
Without credentials, these checks cover type annotations, plugin resolution, local samplers, and classical fallback. IBM Quantum, D-Wave QPU, and Fixstars AE live connectivity must be run separately in an environment where the corresponding credentials are injected through secrets or environment variables.
Integration with Q-PFC
Pass the backend name directly to QPFCQAOABridge:
from evospikenet.qpfc_qaoa_bridge import QPFCQAOABridge
bridge = QPFCQAOABridge(backend="classical") # or "qaoa_simulator", "qiskit", "dwave"
candidates = bridge.propose(vehicles=..., stops=..., distances_callable=...,
uncertainty={"global": 0.3}, risk=0.4, deadline=10.0)
Benchmarking
# 100 trials × 4 sizes × 2 backends
PYTHONPATH=.:EvoSpikeNet-Core python EvoSpikeNet-Core/scripts/bench_qpfc_qaoa_bulk.py --n 100
# View effect-size report
cat EvoSpikeNet-Core/bench_reports/qpfc_qaoa_effect_sizes.md
Verified Backends (September 2026)
| Backend | Verification | meta.backend |
|---|---|---|
classical |
SA, no fallback needed | simulated_annealing |
qaoa_simulator |
Classical SA/SPSA approximation | qaoa_simulator |
openjij |
Local OpenJij SASampler | openjij_sa |
fixstars_amplify |
Plugin resolution and classical fallback without token | simulated_annealing |
dwave |
Local simulated annealing sampler | dwave |
qiskit |
Local plugin and Qiskit test suite; live QPU requires credentials | qiskit |
Environment Variables / .env Configuration
Place secrets in Products/.env (already listed in .gitignore).
evospikenet.optimizer loads this file automatically at import time.
# Products/.env
FIXSTARS_TOKEN=<your_fixstars_amplify_token>
IBMQ_TOKEN=<your_ibmq_token>
DWAVE_API_TOKEN=<your_dwave_token>
DWAVE_API_ENDPOINT=<your_dwave_endpoint>