Sampler Setup

This guide aims to provide a tutorial on how to implement new sampler interfaces using QUBODrivers.jl. To get your QUBO sampler running right now, QUBODrivers.jl will require only two main ingredients: a QUBODrivers.@setup macro call and a QUBODrivers.sample method implementation.

Imports

First things first, we are going to import both QUBODrivers.jl and also MathOptInterface.jl, commonly aliased as MOI. Although not strictly necessary, we recommend that you also import QUBOTools.jlfor convenience, as it provides many useful functions for QUBO manipulation. It is readly available in the QUBODrivers module.

import QUBODrivers
import QUBODrivers: QUBOTools
import MathOptInterface as MOI

The QUBODrivers.@setup macro

QUBODrivers.@setupMacro
@setup(expr)

The @setup macro receives a begin ... end block with an attribute definition on each of the block's statements.

Sampler Attributes

All attributes must be presented as an assignment to the default value of that attribute. To create a MathOptInterface optimizer attribute, an identifier must be present on the left hand side. If a solver-specific, raw attribute is desired, its name must be given as a string, e.g. between double quotes. In the special case where an attribute could be accessed in both ways, the identifier must be followed by the parenthesised raw attribute string. In any case, the attribute type can be specified typing the type assertion operator :: followed by the type itself just before the equal sign.

For example, a list of the valid syntax variations for the number of reads attribute follows: - "num_reads" = 1_000 - "num_reads"::Integer = 1_000 - NumberOfReads = 1_000 - NumberOfReads::Integer = 1_000 - NumberOfReads("num_reads") = 1_000 - NumberOfReads("num_reads")::Integer = 1_000

Example

QUBODrivers.@setup Optimizer begin
    name       = "Super Sampler"
    version    = v"1.0.2"
    attributes = begin
        NumberOfReads["num_reads"]::Integer  = 1_000
        SuperAttribute["super_attr"]         = nothing
        MegaAttribute::Union{String,Nothing} = "mega"
    end
end
source

This macro takes two arguments: the identifier of the sampler's struct (usually Optimizer), and a begin...end block containing configuration parameters as key-value pairs.

The first parameter of the configuration block is the sampler's name, which will be used to identify it in the MOI.SolverName attribute.

The next entry is the version assignment, which is accessed by the MOI.SolverVersion attribute. In order to consistently support semantic versioning it is required that the version number comes as a v-string e.g. v"major.minor.patch".

Note

If missing, the version parameter matches the current version of QUBODrivers.jl.

A simple yet valid @setup call would look like this:

QUBODrivers.@setup Optimizer begin
    name    = "Super Sampler"
    version = v"1.0.2"
end

We expect that most users will be happy with this approach and it is likely that it will fit most use cases.

Attributes

The attributes parameter is also given by a begin...end block and contains the sampler's attributes. These attributes are used to configure the sampler's behavior and are accessed by the MOI.get method.

QUBODrivers.@setup Optimizer begin
    name    = "Super Sampler"
    version = v"1.0.2"
    attributes = begin
        NumberOfReads["num_reads"]::Integer = 1_000
        SuperAttribute::String = "super"
    end
end

The QUBODrivers.sample method

The [QUBODrivers.SampleSet] collection

A complete example

module SuperSampler

import QUBODrivers
import QUBODrivers: QUBOTools
import MathOptInterface as MOI

@doc raw"""
    SuperSampler.Optimizer

This sampler is super!
"""
QUBODrivers.@setup Optimizer begin
    name    = "Super Sampler"
    version = v"1.0.2"
    attributes = begin
        NumberOfReads["num_reads"]::Integer = 1_000
        SuperAttribute::String = "super"
    end
end

function QUBODrivers.sample(sampler::Optimizer{T}) where {T}
    # ~ Is your annealer running on the Ising Model? Have this:
    n, h, J, α, β = QUBOTools.ising(
        sampler,
        :dense; # Here we opt for a dense matrix representation
        sense = :max,
    )

    # ~ Retrieve Attributes using MathOptInterface ~ #
    num_reads  = MOI.get(sampler, NumberOfReads())
    super_attr = MOI.get(sampler, SuperAttribute())

    # ~ Do some sampling ~ #
    samples = QUBOTools.Sample{T,Int}[]

    clock = @timed for _ = 1:num_reads
        ψ = super_sample(n, h, J, super_attr)
        λ = QUBOTools.value(ψ, h, J, α, β)

        s = QUBOTools.Sample{T,Int}(ψ, λ)

        push!(samples, s)
    end

    # ~ Store some metadata ~ #
    metadata = Dict{String,Any}(
        "num_reads"  => num_reads,
        "super_attr" => super_attr,
        "time"       => clock.time,
    )

    # ~ Return a SampleSet ~ #
    return QUBOTools.SampleSet(samples, metadata; sense=:max, domain=:spin)
end

function super_sample(n, h, J, super_attr)
    # ~ Do some super sampling (using C/C++) ~ #
    ψ = ccall(
        :super_sample,
        Vector{Int},
        (
            Cint,
            Ptr{Float64},
            Ptr{Ptr{Float64}},
            Cstring
        ),
        n,
        h,
        J,
        super_attr,
    )

    return ψ
end

end # module