Implementing a Sampler

This walkthrough builds a minimal sampler that works through the same public interfaces used by package wrappers. It uses exhaustive search so the example is self-contained; a production wrapper can replace that inner loop with a call to an external library, service, or device.

Minimal module

A QUBODrivers sampler needs an optimizer type and a QUBODrivers.sample method. The QUBODrivers.@setup macro creates the optimizer type, standard MOI methods, and attribute storage.

module DemoSampler

import QUBODrivers
import QUBODrivers: MOI, QUBOTools, Sample, SampleSet

QUBODrivers.@setup Optimizer begin
    name    = "Demo Sampler"
    version = v"0.1.0"
    attributes = begin
        NumberOfReads["num_reads"]::Integer = 1
    end
end

function QUBODrivers.sample(sampler::Optimizer{T}) where {T}
    n, linear, quadratic, scale, offset = QUBOTools.qubo(
        sampler,
        :dict;
        sense = :min,
    )

    num_reads = MOI.get(sampler, NumberOfReads())
    if num_reads < 1
        error("'num_reads' must be a positive integer")
    end

    best_state = Vector{Int}()
    best_value = nothing

    for state in Iterators.product(ntuple(_ -> (0, 1), n)...)
        candidate_state = collect(state)
        candidate_value = QUBOTools.value(
            candidate_state,
            linear,
            quadratic,
            scale,
            offset,
        )

        if isnothing(best_value) || candidate_value < best_value
            best_state = candidate_state
            best_value = candidate_value
        end
    end

    samples = [
        Sample{T,Int}(copy(best_state), best_value)
        for _ in 1:num_reads
    ]

    metadata = Dict{String,Any}(
        "origin" => "Demo Sampler",
        "time"   => Dict{String,Any}("effective" => 0.0),
        "status" => "optimal",
    )

    return SampleSet{T}(samples, metadata; sense = :min, domain = :bool)
end

end

nothing

Use it from JuMP

The generated Optimizer type is an MOI.AbstractOptimizer, so it can be passed directly to JuMP.Model.

using JuMP

model = Model(DemoSampler.Optimizer)
set_optimizer_attribute(model, "num_reads", 2)

@variable(model, x[1:2], Bin)
@objective(model, Min, -x[1] - 2x[2] + 3x[1] * x[2])

optimize!(model)

(objective_value(model), round.(Int, value.(x)), result_count(model))
(-2.0, [0, 1], 1)

Implementation checklist

When adapting the example for a real sampler:

  • choose the QUBOTools representation that matches the backend, such as QUBOTools.qubo(sampler, :dict; sense = :min) or QUBOTools.ising(sampler, :dense; sense = :max);
  • read user-facing options through MOI attributes generated by @setup;
  • convert backend outputs into Sample{T,Int} entries;
  • return a SampleSet{T} with the correct sense and domain;
  • include useful metadata such as timing, backend status, and backend version;
  • run QUBODrivers.test(YourSampler.Optimizer) in the package test suite.

The built-in RandomSampler and ExactSampler implementations are compact references for these same steps.