ABS3 Solver Usage
Solving an expression f using the ABS3 Solver involves the following two steps:
- Create an
ABS3Solverobject for the expressionf. - Call the
search()method with keyword arguments, which returns the obtained solution.
Solving LABS problem using the ABS3 Solver
The following program solves the Low Autocorrelation Binary Sequence (LABS) problem using the ABS3 Solver:
import pyqbpp as qbpp
size = 100
x = qbpp.var("x", shape=size)
f = qbpp.expr()
for d in range(1, size):
temp = qbpp.expr()
for i in range(size - d):
temp += (2 * x[i] - 1) * (2 * x[i + d] - 1)
f += qbpp.sqr(temp)
f.simplify_as_binary()
solver = qbpp.ABS3Solver(f)
sol = solver.search(time_limit=10.0, enable_default_callback=1)
bits = "".join("-" if sol(x[i]) == 0 else "+" for i in range(size))
print(f"{sol.energy}: {bits}")
In this program, an ABS3Solver object solver is first created for the expression f. The search() method is then called with parameters passed as keyword arguments. The time_limit option specifies the maximum search time in seconds, while enable_default_callback=1 enables a built-in callback that prints the energy and TTS of newly found best solutions during the search. This method returns the best solution found within the given time limit, which is stored in sol.
The program prints the energy of the solution and the corresponding binary sequence, where “+” represents 1 and “-“ represents 0.
This program produces output similar to the following:
TTS = 0.002s Energy = 1218
TTS = 0.002s Energy = 1170
TTS = 0.002s Energy = 994
TTS = 0.015s Energy = 958
TTS = 0.018s Energy = 922
TTS = 0.034s Energy = 874
TTS = 4.364s Energy = 834
834: -+--+---++-++-+---++-++--+++--+-+-+++++----+++-+-+---++-+--+-----+--+----++----+-+--++++++---+------
ABS3 Solver object
An ABS3Solver object is created for a given expression. The constructor converts the expression into an internal data format and loads it into host memory, and also transfers it to device memory when GPUs are available. Subsequent search() calls reuse this load, so repeated searches on the same expression incur no reloading overhead.
An optional second argument gpu controls GPU usage:
ABS3Solver(f): Automatically uses all available GPUs. If no GPU is available, falls back to CPU-only mode.ABS3Solver(f, 0): Forces CPU-only mode (no GPU is used).ABS3Solver(f, n): UsesnGPUs. The expression is loaded onto allnGPUs.
Search parameters are passed directly to search() as keyword arguments. In the example above:
time_limit=10.0: Sets the time limit to 10.0 seconds.enable_default_callback=1: Enables the built-in callback function, which prints the energy and TTS of newly obtained solutions.
ABS3 Parameters
Parameters are passed directly to the search() method as keyword arguments. In the program above, time_limit=10.0 sets the time limit to 10.0 seconds and enable_default_callback=1 enables the built-in callback function, which prints the energy of newly obtained solutions.
The return value is a solution that provides sol.energy (energy value), sol(x) (variable value lookup), sol.info (dict of solver info), and more. See QR_SOLUTION for details.
Basic Options
| Key | Type | Description |
|---|---|---|
time_limit | float | Time limit in seconds. Terminates the search when the time limit is reached |
target_energy | int | Terminates the search when the target energy is achieved |
Advanced Options
| Key | Type | Description |
|---|---|---|
enable_default_callback | int (0 or 1) | Enables the built-in callback that prints energy and TTS |
cpu_enable | int (0 or 1) | Enables/disables the CPU solver running alongside the GPU (default: 1) |
cpu_thread_count | int | Number of CPU solver threads (default: auto) |
block_count | int | Number of CUDA blocks per GPU |
thread_count | int | Number of threads per CUDA block |
topk_sols | int | Returns the top-K solutions with the best energies |
best_energy_sols | int | Max count (0 = unlimited). Returns all solutions with the best energy found |
Collecting Multiple Solutions
The ABS3 Solver can collect multiple solutions during the search. Two modes are available:
Top-K Solutions (topk_sols)
The topk_sols parameter collects the top-K solutions sorted by energy in ascending order.
sol = solver.search(topk_sols=10) # collect up to 10 best solutions
Best Energy Solutions (best_energy_sols)
The best_energy_sols parameter collects all solutions that share the best energy found. Whenever a better energy is discovered, the pool is cleared and only solutions with the new best energy are kept.
sol = solver.search(best_energy_sols=0) # collect all best-energy solutions (unlimited)
Alternatively, best_energy_sols can be set with a maximum count:
sol = solver.search(best_energy_sols=100) # collect up to 100
Note that topk_sols and best_energy_sols share the same internal pool. If both are specified, the last one takes effect.
Accessing Collected Solutions
The return value of search() is a solution that provides access to the collected solutions via the sol.sols property:
sol = solver.search(topk_sols=5)
print(f"Best energy: {sol.energy}")
print(f"Number of solutions: {len(sol.sols)}")
for s in sol.sols:
print(f"energy = {s.energy} TTS = {s.tts}s")
The returned object supports:
sol.energy— the best solution’s energysol.tts— time in seconds at which the best solution was foundsol.sols— list of collected solutions (sorted by ascending energy)sol.sols[i]— access the i-th solutionlen(sol.sols)— number of collected solutionssol.info— dict of solver info strings
Custom Callback
The built-in callback (enabled by enable_default_callback=1) simply prints the energy and TTS whenever a new best solution is found. For more control, subclass ABS3Solver and override the callback() method (no arguments).
The callback is invoked with one of the following events:
| Event value | Name | Description |
|---|---|---|
0 | Start | Called once at the beginning of search() |
1 | BestUpdated | Called whenever a new best solution is found |
2 | Timer | Called periodically at a configurable interval |
Inside callback(), the following methods are available:
self.event()— the event that triggered this callback (int: 0=Start, 1=BestUpdated, 2=Timer)self.best_sol()— returns the current best solution. Use.energy,.tts,.get(var), etc.self.timer(seconds)— set the timer interval in seconds for periodicTimercallbacks.0disables the timer (see below)self.hint(sol)— provide a hint solution to the solver during the search (see Solution Hint)self.terminate()— cooperatively abort the running search (see Aborting the Search below)
Timer Control
The Timer event is not enabled by default. To enable periodic timer callbacks, call self.timer(seconds) inside the callback() method:
self.timer(1.0)— fireTimercallbacks every 1 secondself.timer(0)— disable the timer- If
self.timer()is not called, the timer interval remains unchanged.
Typically, self.timer() is called once during the Start callback to establish the interval. It can also be called during BestUpdated or Timer callbacks to adjust or disable the timer dynamically.
Example: Custom Callback
import pyqbpp as qbpp
class MySolver(qbpp.ABS3Solver):
def callback(self):
if self.event() == 0: # Start
self.timer(1.0) # enable timer callback every 1 second
if self.event() == 1: # BestUpdated
sol = self.best_sol()
print(f"New best: energy={sol.energy} TTS={sol.tts:.3f}s")
x = qbpp.var("x", shape=8)
f = qbpp.sqr(qbpp.sum(x) - 4)
f.simplify_as_binary()
solver = MySolver(f)
sol = solver.search(time_limit=5, target_energy=0)
print(f"energy={sol.energy}")
Aborting the Search
Calling self.terminate() cooperatively aborts a running search(). The call returns immediately with the best solution found so far.
It can be called from:
- inside
callback()— inspectself.best_sol().energyand decide whether to stop - a separate thread — for external cancellation, signal handlers, watchdogs, etc.
The advantage of terminate() is that you can express stopping conditions in terms of the current best solution at runtime. Whereas target_energy locks you into a single fixed threshold up front, terminate() lets you decide on the fly:
- arbitrary energy conditions (e.g.,
energy <= 0,energy < prev_best * 0.99) - conditions combined with elapsed time (e.g., “stop if no improvement in the last 5 seconds”)
- weighted combinations of constraint violations and objective (e.g.,
onehot_violation == 0 and objective <= threshold) - external triggers (a GUI cancel button, a notification from another solver, …)
— anything you can evaluate from inside the callback (or another thread) qualifies as a stopping condition.
The stop flag is automatically cleared when search() is called again on the same instance, so the solver can be reused.
Example: Terminate on energy == 0
This example does not use target_energy. Instead the callback observes energy == 0 and calls self.terminate().
import pyqbpp as qbpp
class TerminateOnZero(qbpp.ABS3Solver):
def callback(self):
if self.event() == qbpp.ABS3Solver.EVENT_BEST_UPDATED:
sol = self.best_sol()
print(f"energy={sol.energy} tts={sol.tts:.3f}s")
if sol.energy == 0:
self.terminate() # fires only once: BestUpdated is strictly monotonic
x = qbpp.var("x", shape=10)
f = qbpp.sqr(qbpp.sum(x) - 5)
f.simplify_as_binary()
solver = TerminateOnZero(f)
# No target_energy is set; stopping is controlled entirely by terminate().
sol = solver.search(time_limit=60)
print(f"final energy={sol.energy}")
Solution Hint
A hint solution allows warm-starting a search with a previously found solution.
The simplest way is to call solver.hint(sol) before search():
solver.hint(prev_sol) # provide a hint solution for the search
sol = solver.search(time_limit=10)
The solution is written directly to the solver’s internal data structure before the search begins.
For advanced use cases such as running an external solver concurrently, you can also call self.hint(sol) inside a callback to feed solutions dynamically. In this scenario, setting up a periodic timer (e.g., self.timer(1.0)) is recommended so that the callback is invoked regularly to check for new external solutions.
Example: Providing a Hint Solution
The following example solves a factorization problem twice. The first run finds the optimal solution normally. The second run provides the first solution as a hint via solver.hint(sol1), causing the solver to converge much faster.
import pyqbpp as qbpp
p = qbpp.var("p", between=(2, 1000))
q = qbpp.var("q", between=(2, 1000))
f = qbpp.sqr(p * q - 899 * 997)
f.simplify_as_binary()
solver = qbpp.ABS3Solver(f)
# Run 1: normal search
sol1 = solver.search(target_energy=0, time_limit=10, enable_default_callback=1)
print(f"Run 1: p={sol1(p)} q={sol1(q)} energy={sol1.energy} TTS={sol1.tts:.3f}s")
# Run 2: provide previous solution as a hint
solver.hint(sol1)
sol2 = solver.search(target_energy=0, time_limit=10, enable_default_callback=1)
print(f"Run 2: p={sol2(p)} q={sol2(q)} energy={sol2.energy} TTS={sol2.tts:.3f}s")
The hint solution is written directly to the solver’s internal data structure before the search begins. The solver evaluates its energy and uses it as the initial state, then continues searching for better solutions.