Skip to content

Results are limited to the current section: Application solving tools

Product news

Classical Solvers

Classical solvers tackle a QUBO Instance entirely on CPU, without going through the quantum pipeline. Each one is a standalone function under solving, callable directly with the instance and its own set of keyword arguments β€” no Solver/SolverConfig wiring required. They differ in the trade-off between solution quality, runtime, and whether they need a starting point.

These solvers start from scratch:

  • solving.cplex.solve β€” exact MIP solve via IBM CPLEX.
  • solving.random_sampling.solve β€” uniform random sampling baseline.

These solvers take a starting point. Because they accept a starting point, they can also be used to refine a previous solution β€” for example, post-processing a quantum solver's output:

  • solving.tabu_search.solve β€” neighborhood search with a tabu memory. See example below.
  • solving.simulated_annealing.solve β€” stochastic temperature-cooling search.
  • solving.iterative_bitflip_local_search.solve β€” greedy local search that iteratively flips bits until no single flip improves the solution.
from qubosolver import Instance, solving, matrix, bitstrings, torch_rng, analysis
instance = Instance(
matrix.tensor(
[
[-2.0, 1.0, 0.0, 1.5, 0.0],
[1.0, -1.5, 1.0, 0.0, 0.5],
[0.0, 1.0, -2.0, 1.0, 1.0],
[1.5, 0.0, 1.0, -1.0, 0.5],
[0.0, 0.5, 1.0, 0.5, -1.5],
]
)
)
starts = bitstrings.rand(5, instance.size, rng=torch_rng(15))
solution = solving.tabu_search.solve(instance, starts=starts, time_limit=10.0)
print("Tabu Search solution:")
print(analysis.to_dataframe([solution]))
Tabu Search solution:
labels bitstrings costs counts probs
0 0 10100 -4.0 5 1.0

Run the quantum pipeline (see quantum solving), then try local bitflips for refinement:

from qubosolver import (
Instance,
Solution,
LocalEmulator,
embedding,
drive_shaping,
solving,
matrix,
analysis,
)
import qoolqit
instance = Instance(
matrix.tensor(
[
[-2.0, 1.0, 0.0, 1.5, 0.0],
[1.0, -1.5, 1.0, 0.0, 0.5],
[0.0, 1.0, -2.0, 1.0, 1.0],
[1.5, 0.0, 1.0, -1.0, 0.5],
[0.0, 0.5, 1.0, 0.5, -1.5],
]
)
)
device = qoolqit.AnalogDeviceWithDMM()
backend = LocalEmulator()
register = embedding.blade.embed_for_device(instance, device)
drive = drive_shaping.proportional_diagonal.build_drive(instance, register, device=device, dmm=True)
program = solving.analog_quantum_sampling.compile(register, drive, device)
job = backend.run(program)
quantum_solution = Solution.from_results(job.results(), instance)
print("Quantum solution:")
print(analysis.to_dataframe([quantum_solution]))
# Refine the quantum solution classically.
refined_solution = solving.iterative_bitflip_local_search.solve(instance, starts=quantum_solution)
print("Refined solution:")
print(analysis.to_dataframe([refined_solution]))
Quantum solution:
labels bitstrings costs counts probs
0 0 10100 -4.0 380 0.380
1 0 10101 -3.5 170 0.170
2 0 10001 -3.5 330 0.330
3 0 01010 -2.5 4 0.004
4 0 01001 -2.0 32 0.032
5 0 11001 -2.0 70 0.070
6 0 01011 -2.0 7 0.007
7 0 10000 -2.0 5 0.005
8 0 00001 -1.5 1 0.001
9 0 10011 -0.5 1 0.001
Refined solution:
labels bitstrings costs counts probs
0 0 10100 -4.0 555 0.555
1 0 10001 -3.5 401 0.401
2 0 01010 -2.5 12 0.012
3 0 01001 -2.0 32 0.032

For full parameter details, see the classical solvers API reference.

For the common case, SolverConfig and Solver wrap solver selection through ClassicalSolvingConfig, and (optionally) the initial-solution sampling into a single call:

from qubosolver import (
Instance,
Solver,
SolverConfig,
ClassicalSolvingConfig,
matrix,
analysis,
)
from dataclasses import asdict
import pprint
instance = Instance(
matrix.tensor(
[
[-2.0, 1.0, 0.0, 1.5, 0.0],
[1.0, -1.5, 1.0, 0.0, 0.5],
[0.0, 1.0, -2.0, 1.0, 1.0],
[1.5, 0.0, 1.0, -1.0, 0.5],
[0.0, 0.5, 1.0, 0.5, -1.5],
]
)
)
classical_config = ClassicalSolvingConfig(
algorithm="tabu_search",
# algorithm="simulated_annealing",
# algorithm="cplex",
)
solver_config = SolverConfig(solving=classical_config)
solver = Solver(instance, solver_config)
solution = solver.solve()
print(pprint.pformat(asdict(solver_config.solving)))
print()
print(analysis.to_dataframe([solution]))
{'algorithm': 'tabu_search',
'max_bitstrings': 1,
'max_iter': 100,
'time_limit': inf}
labels bitstrings costs counts probs
0 0 10100 -4.0 1 1.0