qubosolver.solving.classical
qubosolver.solving.classical
Classical QUBO solver algorithms.
Provides CPLEX, tabu search, simulated annealing, random sampling, and brute-force solvers.
Modules:
-
brute_force–Exact brute-force QUBO solver by exhaustive enumeration.
-
cplex–QUBO solver backed by IBM CPLEX.
-
iterative_bitflip_local_search–Bit-flip local search for QUBO solutions.
-
random_sampling–Uniform random bitstring sampler for QUBO instances.
-
simulated_annealing–Simulated Annealing solver for QUBO problems.
-
tabu_search–Tabu Search solver for QUBO problems.
-
trivial_solution_search–Trivial QUBO solution detection.
brute_force
Exact brute-force QUBO solver by exhaustive enumeration.
Enumerates the 2^n binary assignments of an n-variable QUBO, evaluates
\(x^T Q x\) for each, and returns the max_bitstrings lowest-cost ones. This
is exact but scales exponentially in n; it is meant for small instances,
validation, and as a ground-truth reference for other solvers.
Enumeration proceeds in fixed-size batches so peak memory stays bounded and a
wall-clock time_limit can interrupt it between batches. When the limit is
reached the best assignments found so far are returned, so the result is a valid
(possibly non-optimal) solution rather than an error.
Functions:
-
solve–Solve a QUBO exactly by enumerating all
2^nbinary assignments.
solve
Solve a QUBO exactly by enumerating all 2^n binary assignments.
Evaluates every assignment's cost and returns the max_bitstrings
lowest-cost ones, sorted by ascending cost. Enumeration is batched; when
time_limit elapses, the best assignments found so far are returned.
Parameters:
-
instance(Instance) –The QUBO instance to solve.
-
max_bitstrings(int, default:1) –Number of lowest-cost bitstrings to return. If
max_bitstrings > 2^n, only2^nbitstrings are returned. -
time_limit(float, default:float('inf')) –Wall-clock budget in seconds. Enumeration stops between batches once the budget is exhausted and returns the best solutions found so far. Use
float("inf")for no limit.
Returns:
-
Solution–A solution with up to
max_bitstringsbitstrings, their QUBO costs, and probabilities, sorted by ascending cost.
Source code in qubosolver/solving/classical/brute_force.py
cplex
QUBO solver backed by IBM CPLEX.
Formulates the QUBO problem as a Binary Quadratic Program (BQP) and solves it with IBM CPLEX's branch-and-bound MIP engine, which guarantees an optimal solution within the given time limit.
Note
This module requires the cplex Python package (part of IBM CPLEX
Optimization Studio) to be installed. Install it with the extras
extra: pip install 'qubo-solver[extras]'.
Functions:
-
solve–Solve a QUBO instance to optimality (or time limit) using IBM CPLEX.
solve
Solve a QUBO instance to optimality (or time limit) using IBM CPLEX.
Parameters:
-
instance(Instance) –The QUBO instance to solve.
-
time_limit(float, default:float('inf')) –Wall-clock time limit for CPLEX in seconds. CPLEX returns the best feasible solution found so far when the limit is reached.
-
log_path(str, default:'') –File path where CPLEX log output (progress, warnings, errors) is written, opened in write mode (
"w"), so any existing file is overwritten. When empty (the default), logging is suppressed and no file is created.
Returns:
-
Solution–A solution containing exactly one bitstring — the best (or optimal) solution found by CPLEX.
Source code in qubosolver/solving/classical/cplex.py
iterative_bitflip_local_search
Bit-flip local search for QUBO solutions.
This module provides greedy single-bit-flip local search strategies that improve a batch of candidate bitstrings by iteratively flipping bits, stopping when no flip improves the objective, a maximum number of iterations is reached, or a shared time budget is exhausted.
The main public entry point is solve, which applies the selected
strategy to every bitstring in an existing Solution, or to a batch of
uniformly random candidates generated on the fly. It is used as a
post-processing step in Solver. "best_improvement" runs every
bitstring in lockstep as a single batched search; "first_improvement"
and "greedy_sweep" improve each bitstring independently.
Functions:
-
solve–Improve every bitstring in
startsvia single-bit-flip local search.
solve
solve(instance: Instance, *, starts: Solution | int = 1, strategy: Literal['greedy_sweep', 'best_improvement', 'first_improvement'] = 'greedy_sweep', max_iterations: int = -1, time_limit: float = float('inf')) -> Solution
Improve every bitstring in starts via single-bit-flip local search.
Bitstrings driven to the same local minimum are merged afterwards via
deduplicate.
time_limit is a global budget for the whole batch of bitstrings in
starts, not a per-bitstring limit. Once it is exhausted, any
remaining bitstrings are left unchanged, with their original cost.
Parameters:
-
instance(Instance) –The instance used to evaluate bitstring costs.
-
starts(Solution | int, default:1) –Either the
Solutionto refine, or anintgiving the number of uniformly random candidate bitstrings to draw viarandom_sampling.solve, which may return fewer than requested after deduplication. This random draw is not reproducible via a caller-suppliedrng; callers who need reproducibility should sample their ownSolution(e.g. with a seededrandom_sampling.solve) and pass it in directly. -
strategy(Literal['greedy_sweep', 'best_improvement', 'first_improvement'], default:'greedy_sweep') –Which local-search strategy to use:
"best_improvement","first_improvement", or"greedy_sweep". -
max_iterations(int, default:-1) –For
"first_improvement"and"greedy_sweep", the maximum number of accepted flips per bitstring. For"best_improvement", the maximum number of batch rounds: each round applies one flip to every bitstring that still has an improving one, so a bitstring may converge in fewer rounds than this cap, and different bitstrings can converge after different numbers of flips under the same cap. Defaults to-1, i.e. no limit. -
time_limit(float, default:float('inf')) –Maximum total time in seconds for the whole batch. Defaults to
float('inf'), i.e. no limit.
Returns:
-
Solution–A new solution with updated
bitstrings,costs,counts, andprobabilitiesreflecting the locally optimal results.
Raises:
-
ValueError–If
strategyis not one of the supported strategies. -
ValueError–If
startsbitstrings do not have lengthinstance.size.
Source code in qubosolver/solving/classical/iterative_bitflip_local_search.py
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 | |
random_sampling
Uniform random bitstring sampler for QUBO instances.
Functions:
-
solve–Sample uniformly random bitstring solutions for a QUBO instance.
solve
solve(instance: Instance, *, max_bitstrings: int = 1, rng: torch.Generator | None = None) -> Solution
Sample uniformly random bitstring solutions for a QUBO instance.
Draws max_bitstrings independent binary vectors uniformly at random,
deduplicates them (identical samples are merged and their draw count is
accumulated in counts), evaluates the QUBO cost of each unique
bitstring.
Note
Because of deduplication, the returned solution may contain fewer than
max_bitstrings bitstrings when the same random vector is drawn more
than once.
Parameters:
-
instance(Instance) –The QUBO instance whose coefficient matrix is used to evaluate bitstring costs.
-
max_bitstrings(int, default:1) –Number of random bitstrings to draw before deduplication. The returned solution may contain fewer unique bitstrings.
-
rng(torch.Generator | None, default:None) –PyTorch random number generator controlling the sampling.
Returns:
-
Solution–A solution with unique bitstrings, their QUBO costs, draw counts, and probabilities.
Source code in qubosolver/solving/classical/random_sampling.py
simulated_annealing
Simulated Annealing solver for QUBO problems.
Implements a bit-flip annealer that minimizes the quadratic objective \(E(x) = x^T Q x\) over binary vectors \(x \\in \\{0,1\\}^n\), run independently from each of a batch of starting points and merged into a single solution.
Functions:
-
solve–Run Simulated Annealing on a QUBO instance from each of a batch of starting points.
solve
solve(instance: Instance, starts: Bitstrings | int = 1, *, merge: bool = True, top_k: int = 1, max_iter: int = 1000, initial_temp: float = 5.0, final_temp: float = 0.001, cooling_rate: float | None = None, time_limit: float = float('inf'), rng: torch.Generator | None = None, stats: Literal['per_run', 'full'] = 'per_run', vectorized: bool = True) -> Solution | list[Solution]
Run Simulated Annealing on a QUBO instance from each of a batch of starting points.
For each starting bitstring, at each of max_iter steps a random bit is
proposed for flipping. The flip is always accepted when it reduces the
energy; otherwise it is accepted with probability \(\\exp(-\\Delta E / T)\).
Up to top_k unique lowest-energy bitstrings encountered during each run
are retained, along with how many iterations were spent at each one
(whether or not the proposed flip at that iteration was accepted). The
runs are independent. By default (merge=True) the per-start results
are merged into a single Solution. Pass merge=False to instead
get back the unmerged, one-per-start list.
Example
Running a single explicit start requires promoting it to a batch of
size 1 first, via torch.unsqueeze:
Passing merge=False returns the per-start results instead of a
single merged Solution:
Parameters:
-
instance(Instance) –The QUBO instance to solve. Its coefficient matrix is symmetrised internally as
(Q + Qáµ€) / 2. -
starts(Bitstrings | int, default:1) –Either a batch of initial binary solutions, a tensor of shape
(k, n)with values in{0, 1}(one independent run is performed per row), or anintgiving the number of uniformly random starts to generate. -
merge(bool, default:True) – -
top_k(int, default:1) –Maximum number of unique best solutions to keep per run, ordered by ascending energy.
-
max_iter(int, default:1000) –Number of bit-flip proposals to perform.
-
initial_temp(float, default:5.0) –Starting temperature \(T_0\). Higher values increase the probability of accepting uphill moves early in the search.
-
final_temp(float, default:0.001) –Target temperature \(T_f\) at the end of the schedule, used to derive the cooling rate when
cooling_rateisNone. Ignored whencooling_rateis provided explicitly. -
cooling_rate(float | None, default:None) –Geometric cooling factor \(\\alpha \\in (0, 1)\) such that \(T \\leftarrow \\alpha T\) at each step. When
None(default), \(\\alpha\) is derived automatically frominitial_temp,final_temp, andmax_iterso that the temperature reachesfinal_tempaftermax_itersteps. -
time_limit(float, default:float('inf')) –Wall-clock budget in seconds. The algorithm stops early when either
max_itersteps or the time limit is reached, whichever comes first. Defaults tofloat("inf")(no limit). Withvectorized=Truethis is a single budget for the whole batch of starts, which all stop at the same iteration; withvectorized=Falseeach start gets its own budget. -
rng(torch.Generator | None, default:None) –PyTorch random number generator used for bit selection and acceptance sampling. When
None(default), a new generator is created; pass an explicit generator for reproducibility across calls. Note thatvectorizedchanges the order in which draws are consumed, so a given seed produces the same result only for a fixed value ofvectorized. -
stats(Literal['per_run', 'full'], default:'per_run') –When
"per_run"(default), each run's retained bitstrings are counted as1instead of how many iterations were spent at each one, before any merging. This is mainly meant fortop_k=1together withmerge=True, where the merged count directly reflects how many of the runs converged on each bitstring. Withmerge=Trueandtop_k > 1, or whenever a bitstring is retained by more than one run,deduplicatesums those per-run1s, so counts on the merged result are generally neither1nor uniform. When"full", counts instead reflect how many iterations were spent at each bitstring. -
vectorized(bool, default:True) –When
True(default), step every start forward together so each iteration costs a handful of batched tensor operations regardless of how many starts there are -- markedly faster for large batches. WhenFalse, anneal the starts one at a time; this is the reference implementation, kept for comparison. The two run the same algorithm but differ intime_limitscope and in RNG draw order (seetime_limitandrng).
Returns:
Raises:
-
ValueError–If
top_k < 1. -
ValueError–If
initial_temp <= 0. -
ValueError–If
cooling_rateisNoneandfinal_temp <= 0. -
ValueError–If
cooling_rateis provided but not in(0, 1). -
ValueError–If
startsbitstrings do not have lengthinstance.size.
Source code in qubosolver/solving/classical/simulated_annealing.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 | |
tabu_search
Tabu Search solver for QUBO problems.
A single-neighborhood tabu search that explores bit-flip moves in parallel across multiple starting points.
Functions:
-
solve–Perform Tabu Search on a QUBO instance to find low-cost bitstrings.
solve
solve(instance: Instance, *, starts: Bitstrings | int = 1, max_iter: int = 100, tabu_tenure: int = 7, max_no_improve: int = 20, time_limit: float = float('inf')) -> Solution
Perform Tabu Search on a QUBO instance to find low-cost bitstrings.
Runs one independent search per row of starts, each exploring
single-bit-flip neighbors from its own starting point. A tabu list
prevents revisiting recently flipped bits; aspiration overrides the tabu
restriction whenever a move yields a new global best. All independent
runs share the same stopping criteria and are deduplicated before being
returned.
Parameters:
-
instance(Instance) –The QUBO instance providing the cost matrix.
-
starts(Bitstrings | int, default:1) –Either a batch of initial binary solutions, one row per independent run, each of length
n, or anintgiving the number of uniformly random starts to generate. This random draw is not reproducible via a caller-suppliedrng; callers who need reproducibility should sample their ownBitstrings(e.g. with a seededbitstrings.rand) and pass it in directly. -
max_iter(int, default:100) –Maximum number of search iterations.
-
tabu_tenure(int, default:7) –Number of iterations a bit-flip move stays tabu.
-
max_no_improve(int, default:20) –Maximum consecutive iterations without improvement before a run is considered stagnated. Search stops early when all independent runs have stagnated.
-
time_limit(float, default:float('inf')) –Wall-clock time budget in seconds. Defaults to
float('inf')(no limit).
Returns:
-
Solution–Deduplicated best bitstrings found across all runs, together with their objective values and occurrence counts, sorted by ascending cost.
Raises:
-
ValueError–If
startsbitstrings do not have lengthinstance.size.
Source code in qubosolver/solving/classical/tabu_search.py
22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | |
trivial_solution_search
Trivial QUBO solution detection.
Recognizes coefficient patterns whose optimal solution can be read off analytically without any search.
Functions:
-
solve–Solve a QUBO when the coefficient structure is trivial.
solve
Solve a QUBO when the coefficient structure is trivial.
Three patterns are recognised:
- All coefficients ≥ 0 — the all-zeros bitstring
0^nis optimal. - All coefficients ≤ 0 — the all-ones bitstring
1^nis optimal. - Diagonal matrix — each variable is independent; bits with a
negative diagonal entry are set to
1, the rest to0.
Parameters:
-
instance(Instance) –The QUBO problem whose matrix is inspected.
Returns:
-
Solution–A single-bitstring solution when a trivial case is detected, or an empty solution (no bitstrings) when none of the three patterns apply.