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.@setup
— Macro@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
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"
.
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
QUBODrivers.sample
— Functionsample(::AbstractSampler{T})::SampleSet{T} where {T}
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