Experimental Solver Support: Amplify, D-Wave, OpenJij
⚠️ Experimental — PyQBPP only
The third-party solvers themselves (Fixstars Amplify, D-Wave Advantage / Leap Hybrid, OpenJij) are production tools. What is experimental here is the PyQBPP integration — the wrapper classes
AmplifySolver,DWaveSolver,DWaveHybridSolver, andOpenJijSolver. Their API may change without notice in future PyQBPP releases.They are available only from PyQBPP (Python), not from the C++ QUBO++ library. Each backend (Amplify SDK, D-Wave Ocean SDK, OpenJij) ships only as a Python package, so PyQBPP forwards models to them directly through Python — there is no C++ entry point.
Each solver requires the corresponding third-party Python package to be installed separately. PyQBPP does not depend on these packages directly; they are imported lazily when a solver is instantiated.
PyQBPP can dispatch a model to the following external solvers without any changes to your problem formulation. The solver protocol mirrors qbpp.EasySolver and qbpp.GurobiSolver:
solver = qbpp.AmplifySolver(e) # or DWaveSolver / DWaveHybridSolver / OpenJijSolver
sol = solver.search(...)
print(sol.energy, sol.info)
At a glance
| Solver | Backend | Install | Token | time_limit | HUBO |
|---|---|---|---|---|---|
AmplifySolver | Fixstars Amplify SDK (cloud — Fixstars AE, Fujitsu DA, etc.) | pip install amplify | yes (default Fixstars AE) | yes | ✅ (auto-quadratized by SDK) |
DWaveSolver | D-Wave QPU (Advantage, via Ocean SDK) | pip install dwave-ocean-sdk | yes (D-Wave Leap) | no — use num_reads | ❌ degree ≤ 2 |
DWaveHybridSolver | D-Wave Leap Hybrid Sampler | pip install dwave-ocean-sdk | yes (D-Wave Leap) | yes | ❌ degree ≤ 2 |
DWaveNealSolver | D-Wave Neal — classical SA, not a quantum solver | pip install dwave-samplers | no | no — use num_reads | ❌ degree ≤ 2 |
DWaveTabuSolver | D-Wave samplers — classical Tabu search | pip install dwave-samplers | no | no — use timeout (ms) | ❌ degree ≤ 2 |
DWaveSteepestDescentSolver | D-Wave samplers — greedy local descent | pip install dwave-samplers | no | no — use num_reads | ❌ degree ≤ 2 |
DimodExactSolver | dimod brute-force enumeration (≤ ~20 vars) | pip install dimod | no | no | ❌ degree ≤ 2 |
OpenJijSolver | OpenJij (local SA / SQA, open-source) | pip install openjij | no | no — use num_reads | ✅ via sample_hubo (SASampler) |
HobotanMikasSolver | TYTAN-SDK MIKASAmpler — HUBO-native PyTorch SA | pip install -U git+https://github.com/tytansdk/tytan (+ torch) | no | no — use shots | ✅ dense tensor |
QubovertSolver | qubovert.sim.anneal_pubo — pure-Python HUBO SA | pip install qubovert | no | no — use num_anneals | ✅ sparse PUBO |
SimulatedBifurcationSolver | Toshiba SB algorithm (PyTorch CPU/GPU) | pip install simulated-bifurcation | no | no — use timeout / max_steps | ❌ degree ≤ 2 |
CplexSolver | IBM CPLEX MIQP (commercial) | pip install cplex (license required) | no (license) | yes | ❌ degree ≤ 2 |
QiskitOptimizationSolver | IBM Qiskit Optimization (classical or QAOA / VQE) | pip install qiskit qiskit-optimization qiskit-algorithms | no | no — configure on eigensolver | ❌ degree ≤ 2 |
OrToolsCpSatSolver | Google OR-Tools CP-SAT (HUBO via Boolean encoding) | pip install ortools | no | yes | ✅ via Boolean AND |
The D-Wave QPU, D-Wave Neal, OpenJij, and TYTAN-SDK samplers do not have a wall-clock time limit concept. PyQBPP rejects time_limit=... for these solvers with a clear error rather than silently ignoring it (the underlying dimod samplers generally accept unknown kwargs without complaint).
Unified num_reads keyword
Each backend uses a different native name for “number of independent samples to draw” — D-Wave / dimod / OpenJij call it num_reads, TYTAN-SDK calls it shots, qubovert calls it num_anneals, Simulated Bifurcation calls it agents. PyQBPP accepts the unified keyword num_reads on all five and forwards it to the backend’s native parameter:
| Solver | Native key | num_reads alias |
|---|---|---|
DWaveSolver / DWaveNealSolver / DWaveTabuSolver / DWaveSteepestDescentSolver / OpenJijSolver | num_reads | (passthrough) |
HobotanMikasSolver | shots | ✅ |
QubovertSolver | num_anneals | ✅ |
SimulatedBifurcationSolver | agents | ✅ (each agent → one sample) |
The native key is still accepted; if both are passed, the native key takes precedence. This lets solver-agnostic code use a single parameter name across the entire experimental solver suite::
for cls in [qbpp.DWaveNealSolver, qbpp.OpenJijSolver,
qbpp.QubovertSolver, qbpp.HobotanMikasSolver]:
sol = cls(e).search(num_reads=200)
print(cls.__name__, sol.energy)
Platform support
All solvers on this page run on both x86_64 and aarch64 (ARM) Linux. The PyPI wheels are listed below; if you are on an unlisted Python version, pip falls back to a source build, which works for dimod / dwave-samplers / dwave-system (small Cython extensions) but is laborious for amplify and openjij/jij-cimod. Use a Python version with prebuilt wheels when possible.
| Package | Linux x86_64 | Linux aarch64 | Required Python |
|---|---|---|---|
amplify | ✅ | ✅ | 3.10+ (no aarch64 wheels for 3.9 or older) |
openjij + jij-cimod | ✅ | ✅ | 3.10–3.12 for aarch64 wheels |
dimod | ✅ | ✅ | 3.10+ for aarch64 wheels |
dwave-samplers (Neal) | ✅ | ✅ | 3.10+ for aarch64 wheels |
dwave-cloud-client, dwave-system | ✅ pure-Python | ✅ pure-Python | any |
In practice this means:
- Ubuntu 22.04 / 24.04 (default Python 3.10 / 3.12) on x86_64 or ARM: install with plain
pip install ...and you are done. - Ubuntu 20.04 (default Python 3.8): the wheels are unavailable. Either install Python 3.10+ from the deadsnakes PPA and use a venv, or move to a newer Ubuntu release.
AmplifySolver
Calls the Fixstars Amplify SDK. The default backend is the Fixstars Amplify Annealing Engine; any Amplify client (FixstarsClient, FujitsuDA4Client, LeapHybridSamplerClient, …) can be supplied via client=.
import pyqbpp as qbpp
x = qbpp.var("x", 4)
e = qbpp.sqr(x[0] + 2*x[1] + 3*x[2] + 4*x[3] - 5)
# Default: Fixstars AE
sol = qbpp.AmplifySolver(e).search(token="...", time_limit=1.0)
# Inject a different Amplify client
from amplify import FujitsuDA4Client
sol = qbpp.AmplifySolver(e, client=FujitsuDA4Client(token="...")
).search(time_limit=5.0)
Recognized search() kwargs:
| kwarg | meaning |
|---|---|
time_limit | seconds (float). Sets client.parameters.timeout. |
token | API token. Sets client.token. |
proxy / url | network overrides. |
| any other | forwarded to client.parameters.<name> if the attribute exists. |
AmplifySolver accepts polynomials of arbitrary degree. The Amplify SDK quadratizes higher-degree terms automatically when the chosen backend requires it.
DWaveSolver
Calls the D-Wave QPU (Advantage) directly via the Ocean SDK. Minor embedding is automatic (EmbeddingComposite(DWaveSampler(...))). For offline experimentation, inject any dimod-compatible sampler.
import pyqbpp as qbpp
# Live QPU
sol = qbpp.DWaveSolver(e, token="DEV-...", solver="Advantage_system6.4"
).search(num_reads=1000, chain_strength=2.0)
# Offline classical SA (use DWaveNealSolver instead — see below)
from dwave.samplers import SimulatedAnnealingSampler
sol = qbpp.DWaveSolver(e, sampler=SimulatedAnnealingSampler()
).search(num_reads=1000)
DWaveSolver requires the model to be degree ≤ 2 (BQM). Higher-degree terms must be quadratized first; trying to construct DWaveSolver from a HUBO raises a RuntimeError.
time_limit is not supported: the QPU runtime is the product of num_reads and annealing_time (microseconds per anneal). Passing time_limit=... raises a RuntimeError to prevent the wall-clock budget from being silently ignored.
DWaveHybridSolver
Calls the D-Wave Leap Hybrid Sampler, which combines classical optimization with QPU calls and handles much larger problems than the bare QPU (~10⁶ variables). Wall-clock control via time_limit (seconds).
sol = qbpp.DWaveHybridSolver(e, token="DEV-...").search(time_limit=5)
Like DWaveSolver, requires degree ≤ 2.
DWaveNealSolver
Despite the “DWave” prefix, Neal is not a quantum solver. It is a classical CPU-based simulated-annealing implementation distributed by D-Wave in the dwave-samplers package (formerly the standalone dwave-neal package). No Leap token, no network access, no D-Wave account required.
Useful as a fast classical baseline alongside OpenJijSolver::
sol = qbpp.DWaveNealSolver(e).search(num_reads=1000)
Common search() kwargs (forwarded to SimulatedAnnealingSampler.sample(bqm, **kwargs)): num_reads, num_sweeps, beta_range, beta_schedule_type. Like DWaveSolver, time_limit is rejected and degree must be ≤ 2.
OpenJijSolver
Calls OpenJij (Jij Inc., open-source Ising/QUBO sampler). The default sampler is openjij.SASampler() (Simulated Annealing); inject SQASampler() (Simulated Quantum Annealing), CSQASampler() (Continuous-time SQA), or any cloud sampler from JijZept.
HUBO support. When the model has max_degree >= 3, OpenJijSolver dispatches to SASampler.sample_hubo() instead of sample(). No quadratization needed — terms of any degree go straight to the sampler as a sparse dict. Negated literals (~x) at any degree are auto-expanded to 1 - x because OpenJij’s dict format has no native notion of negation.
sample_hubo() is currently only on openjij.SASampler. Injecting SQASampler / CSQASampler for a max_degree >= 3 problem raises a clear error.
import pyqbpp as qbpp
import openjij as oj
# QUBO via SA
x = qbpp.var("x", 4)
sol = qbpp.OpenJijSolver(qbpp.sqr(x[0]+x[1]+x[2]+x[3]-1)).search(num_reads=1000)
# HUBO degree 3 — sample_hubo() is used automatically
e = qbpp.Expr(x[0]*x[1]*x[2]) - qbpp.Expr(x[0])
sol = qbpp.OpenJijSolver(e).search(num_reads=200)
# SQA — only for QUBO; HUBO would error
sol = qbpp.OpenJijSolver(e_quad, sampler=oj.SQASampler()).search(num_reads=100)
Common search() kwargs (forwarded to the underlying sample call): num_reads, num_sweeps, beta_min, beta_max, schedule. time_limit is rejected; control runtime via num_reads / num_sweeps.
DWaveTabuSolver
Tabu-search heuristic via the dwave-samplers package. Classical, local, no token / network. Useful as a non-SA baseline alongside DWaveNealSolver and OpenJijSolver::
sol = qbpp.DWaveTabuSolver(e).search(num_reads=10, timeout=2000)
Common search() kwargs forwarded to TabuSampler.sample(): num_reads, timeout (milliseconds, per restart), tenure, num_restarts, seed, initial_states. BQM only; time_limit is rejected.
DWaveSteepestDescentSolver
Greedy local descent via dwave-samplers. Each initial state is descended monotonically to a local minimum — deterministic given the seed, fast, and a useful baseline::
sol = qbpp.DWaveSteepestDescentSolver(e).search(num_reads=100)
Common search() kwargs: num_reads, initial_states, seed, large_sparse_opt. BQM only.
DimodExactSolver
Brute-force enumeration of all 2**n assignments via dimod.ExactSolver. Feasible only for small problems (typically n <= 20); returns every assignment in the SampleSet sorted by energy. Ideal for verifying a small model or benchmarking heuristics::
sol = qbpp.DimodExactSolver(e).search()
print(sol.energy)
for s in sol.sols:
print(s.energy)
BQM only; no kwargs (the search is exhaustive).
HobotanMikasSolver
Calls TYTAN-SDK’s MIKASAmpler, a PyTorch-based simulated-annealing sampler that handles HUBO directly (no quadratization step). Despite the SDK name “TYTAN” / “Hobotan”, no token / license / network is required — MIKAS runs locally on CPU or GPU (CUDA / MPS) via PyTorch.
Install (the SDK is published only on GitHub, not PyPI)::
pip install -U git+https://github.com/tytansdk/tytan
pip install torch # CPU build; PyTorch CUDA / MPS auto-detected
Use::
import pyqbpp as qbpp
x = qbpp.var("x", 4)
e = qbpp.Expr(x[0]*x[1]*x[2]) + qbpp.Expr(x[1]*x[2]*x[3]) - qbpp.Expr(x[0])
sol = qbpp.HobotanMikasSolver(e).search(shots=100)
Common search() kwargs (forwarded to MIKASAmpler.run(hobo, **kwargs)): shots, mode ("CPU" / "GPU"), T_init, T_end, num_sweep. Like the other dimod-style solvers, time_limit is rejected; control runtime via shots / num_sweep.
Sparse HUBO is rejected. TYTAN’s HUBO format is a dense tensor of shape
(n,)*dwheren= variable count,d= max degree. PyQBPP rejects problems whosen^dexceeds 10⁸ to prevent memory blow-up. For very sparse high-degree problems preferABS3Solver(built-in, sparse, GPU-accelerated) instead.
QubovertSolver
qubovert is a pure-Python QUBO/HUBO toolkit. QubovertSolver uses qubovert.sim.anneal_pubo — classical simulated annealing on a sparse PUBO (Polynomial Unconstrained Binary Optimization) representation, supporting any degree with no tensor blow-up::
sol = qbpp.QubovertSolver(e).search(num_anneals=100)
No token, no GPU, no native deps — just pip install qubovert. Negated literals are auto-expanded via simplify_as_binary(e, all_positive=True).
Common search() kwargs (forwarded to anneal_pubo): num_anneals, anneal_duration, initial_state, seed, temperature_range, schedule. time_limit is rejected.
SimulatedBifurcationSolver
simulated-bifurcation implements Toshiba’s Simulated Bifurcation (SB) algorithm — a fast classical heuristic for QUBO/Ising, often competitive with SA on dense quadratic problems. PyTorch-based; runs on CPU or GPU::
sol = qbpp.SimulatedBifurcationSolver(e).search(agents=128, max_steps=10000)
Common search() kwargs (forwarded to sb.minimize): agents, max_steps, mode ("ballistic" / "discrete"), heated, early_stopping, timeout (seconds, internal). BQM only — HUBO is rejected (use OpenJijSolver / HobotanMikasSolver / QubovertSolver for higher-degree problems). time_limit is rejected.
CplexSolver
IBM CPLEX MIQP — the commercial sibling of Gurobi. A valid CPLEX license is required at runtime; the free Community Edition is limited to ~1000 variables. BQM only::
sol = qbpp.CplexSolver(e).search(time_limit=10.0)
Recognized search() kwargs: time_limit (s, mapped to parameters.timelimit), thread_count (parameters.threads), target_energy. Any other kwarg is forwarded to cplex.parameters.<name>.set(value) (dotted paths supported).
QiskitOptimizationSolver
IBM Qiskit Optimization — builds an qiskit_optimization.QuadraticProgram and solves it with a configurable :class:MinimumEigenOptimizer. The default eigensolver is the classical :class:NumPyMinimumEigensolver (exact — useful for verifying small models). Inject QAOA / VQE for quantum simulation::
from qiskit_algorithms import QAOA
from qiskit.primitives import Sampler
sol = qbpp.QiskitOptimizationSolver(
e, eigensolver=QAOA(Sampler(), reps=2)).search()
BQM only — Qiskit’s QuadraticProgram is quadratic by definition. For HUBO via QAOA/VQE you’d need to construct a Pauli Hamiltonian directly; that path is not yet wrapped here.
OrToolsCpSatSolver
Google OR-Tools CP-SAT — a constraint-programming engine with a SAT-solver core. CP-SAT doesn’t natively accept quadratic objectives; PyQBPP encodes each non-linear monomial ℓ_a ℓ_b ... ℓ_k (each ℓ either x_i or ~x_i) as a fresh Boolean z with z = ℓ_a ∧ ... ∧ ℓ_k and minimizes the resulting linear objective. HUBO of any degree works with the same encoding, and negated literals are handled natively via CP-SAT’s BoolVar.Not() — no all_positive expansion is applied (which would multiply each m-negation monomial into 2^m sub-terms)::
sol = qbpp.OrToolsCpSatSolver(e).search(time_limit=5.0)
Common search() kwargs: time_limit (s, mapped to parameters.max_time_in_seconds), thread_count (num_search_workers), log (boolean).
Common return type
All fourteen solvers return the standard PyQBPP SolverSol (same as EasySolverSol/ABS3SolverSol/GurobiSolverSol), so the rest of your program is solver-agnostic:
print(sol.energy) # best objective value
print(sol.tts) # time-to-best-solution (seconds)
print(sol.info["solver"]) # "AmplifySolver" / "DWaveSolver" / ...
for s in sol.sols: # additional candidate solutions
print(s.energy, s.tts)
The sol.info dict varies by solver:
AmplifySolver:amplify_version,client,execution_time,response_time,total_time, …DWaveSolver/DWaveHybridSolver:dimod_<key>for every entry in the underlyingSampleSet.info(timing, embedding context, …).OpenJijSolver:dimod_<key>likewise.