diff --git a/src/interface/fallback.jl b/src/interface/fallback.jl index d7464707..769fc09f 100644 --- a/src/interface/fallback.jl +++ b/src/interface/fallback.jl @@ -31,9 +31,9 @@ qubo(model, args...) = qubo(backend(model), args...) ising(model, args...) = ising(backend(model), args...) # ~*~ Solution queries ~*~ # -state(model, args...) = state(backend(model), args...) +state(model, args...) = state(backend(model), args...) value(model, args...) = value(backend(model), args...) -reads(model, args...) = reads(backend(model), args...) +reads(model, args...) = reads(backend(model), args...) # ~*~ Data queries ~*~ # domain_size(model) = domain_size(backend(model)) diff --git a/src/interface/generic.jl b/src/interface/generic.jl index b9f83697..06330a79 100644 --- a/src/interface/generic.jl +++ b/src/interface/generic.jl @@ -332,3 +332,14 @@ function ising(Q::SparseMatrixCSC{T}, α::T = one(T), β::T = zero(T)) where {T} return (h, J, α, β) end + +swap_sense(::Nothing) = nothing +swap_sense(target::Symbol, model::AbstractModel) = swap_sense(Sense(target), model) + +function swap_sense(L::Dict{Int,T}) where {T} + return Dict{Int,T}(i => -c for (i, c) in L) +end + +function swap_sense(Q::Dict{Tuple{Int,Int},T}) where {T} + return Dict{Tuple{Int,Int},T}(ij => -c for (ij, c) in Q) +end \ No newline at end of file diff --git a/src/interface/interface.jl b/src/interface/interface.jl index 4fcfec70..8a51005c 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -92,10 +92,10 @@ Returns a string representing the variable domain. """ function domain_name end @doc raw""" - swap_domain(source, target, model) - swap_domain(source, target, state) - swap_domain(source, target, states) - swap_domain(source, target, sampleset) + swap_domain(target, model::AbstractModel) + swap_domain(source, target, ψ::Vector{U}) + swap_domain(source, target, Ψ::Vector{Vector{U}}) + swap_domain(source, target, ω::SampleSet) Returns a new object, switching its domain from `source` to `target`. """ function swap_domain end @@ -110,11 +110,47 @@ Returns a new object, switching its domain from `source` to `target`. """ function offset end +@enum Sense begin + Min + Max +end + +function QUBOTools.Sense(s::Symbol) + if s === :min + return Min + elseif s === :max + return Max + else + error("Unknown optimization sense '$s'") + end +end + +QUBOTools.Sense(s::Sense) = s + @doc raw""" - sense(model)::Symbol + sense(model)::Sense """ function sense end +@doc raw""" + swap_sense(target::Sense, model::AbstractModel) + swap_sense(target::Symbol, model::AbstractModel) + +```math +\begin{array}{ll} + \min_{s} \alpha [f(s) + \beta] &\equiv \max_{s} -\alpha [f(s) + \beta] \\ + &\equiv \max_{s} \alpha [-f(s) - \beta] \\ +\end{array} +``` + +The linear terms, quadratic terms and constant offset of a model have its signs reversed. + + swap_sense(s::Sample) + swap_sense(ω::SampleSet) + +Reveses the sign of the objective value. +""" function swap_sense end + @doc raw""" id(model) """ function id end diff --git a/src/library/sampleset.jl b/src/library/sampleset.jl index 45eef888..4dd91df6 100644 --- a/src/library/sampleset.jl +++ b/src/library/sampleset.jl @@ -170,6 +170,10 @@ function swap_domain(::A, ::B, s::Sample{T,U}) where {A<:𝔻,B<:𝔻,T,U} return Sample{T,U}(swap_domain(A(), B(), state(s)), value(s), reads(s)) end +function swap_sense(s::Sample{T,U}) where {T,U} + return Sample{T,U}(state(s), -value(s), reads(s)) +end + state(ω::AbstractSampleSet, i::Integer) = state(ω[i]) state(ω::AbstractSampleSet, i::Integer, j::Integer) = state(ω[i], j) value(ω::AbstractSampleSet, i::Integer) = value(ω[i]) @@ -256,4 +260,8 @@ metadata(ω::SampleSet) = ω.metadata function swap_domain(::A, ::B, ω::SampleSet{T,U}) where {A<:𝔻,B<:𝔻,T,U} return SampleSet{T,U}(ω.bits, swap_domain.(A(), B(), ω), deepcopy(metadata(ω))) +end + +function swap_sense(ω::SampleSet{T,U}) where {T,U} + return SampleSet{T,U}(ω.bits, swap_sense.(ω), deepcopy(metadata(ω))) end \ No newline at end of file diff --git a/src/model/model.jl b/src/model/model.jl index ab958235..c3cbb3a5 100644 --- a/src/model/model.jl +++ b/src/model/model.jl @@ -23,7 +23,7 @@ By choosing `V = MOI.VariableIndex` and `T` matching `Optimizer{T}` the hard wor scale::T offset::T # ~*~ Sense ~*~ - sense::Symbol + sense::Sense # ~*~ Metadata ~*~ id::Union{Int,Nothing} version::Union{VersionNumber,Nothing} @@ -42,7 +42,7 @@ By choosing `V = MOI.VariableIndex` and `T` matching `Optimizer{T}` the hard wor scale::Union{T,Nothing} = nothing, offset::Union{T,Nothing} = nothing, # ~*~ Sense ~*~ - sense::Union{Symbol,Nothing} = nothing, + sense::Union{Sense,Symbol,Nothing} = nothing, # ~*~ Metadata ~*~ id::Union{Integer,Nothing} = nothing, version::Union{VersionNumber,Nothing} = nothing, @@ -58,7 +58,7 @@ By choosing `V = MOI.VariableIndex` and `T` matching `Optimizer{T}` the hard wor variable_inv, something(scale, one(T)), something(offset, zero(T)), - something(sense, :min), + Sense(something(sense, :min)), id, version, description, @@ -124,7 +124,7 @@ function Base.empty!(model::Model{D,V,T,U}) where {D,V,T,U} # ~*~ Attributes ~*~ # model.scale = one(T) model.offset = zero(T) - model.sense = :min + model.sense = Sense(:min) model.id = nothing model.version = nothing model.description = nothing @@ -205,6 +205,27 @@ function swap_domain(::X, ::Y, model::Model{X,V,T,U}) where {X,Y,V,T,U} ) end +function swap_sense(target::Sense, model::Model{D,V,T,U}) where {D,V,T,U} + if sense(model) === target + return model + else + return Model{D,V,T,U}( + swap_sense(linear_terms(model)), + swap_sense(quadratic_terms(model)), + copy(variable_map(model)), + copy(variable_inv(model)); + scale = scale(model), + offset = -offset(model), + sense = target, + id = id(model), + version = version(model), + description = description(model), + metadata = deepcopy(metadata(model)), + sampleset = swap_sense(sampleset(model)), + ) + end +end + function Base.copy!(target::Model{D,V,T,U}, source::Model{D,V,T,U}) where {D,V,T,U} target.linear_terms = copy(source.linear_terms) target.quadratic_terms = copy(source.quadratic_terms) diff --git a/test/unit/interface/generic.jl b/test/unit/interface/generic.jl new file mode 100644 index 00000000..cef79c56 --- /dev/null +++ b/test/unit/interface/generic.jl @@ -0,0 +1,77 @@ +function test_swap_sense() + T = Float64 + + @test isnothing(QUBOTools.swap_sense(nothing)) + + dict1 = Dict{Int,Float64}( + 5 => 10, + 6 => 11, + 7 => 12 + ) + + swapped_dict1 = Dict{Int,Float64}( + 5 => -10, + 6 => -11, + 7 => -12 + ) + + @test QUBOTools.swap_sense(dict1) == swapped_dict1 + + dict2 = Dict{Tuple{Int,Int}, Float64}( + (1,1) => 1.0, + (2,2) => 2.0, + (3,3) => 3.0 + ) + + swapped_dict2 = Dict{Tuple{Int,Int}, Float64}( + (1,1) => -1.0, + (2,2) => -2.0, + (3,3) => -3.0 + ) + + @test QUBOTools.swap_sense(dict2) == swapped_dict2 + + + sampletest = QUBOTools.Sample([0,1], 5.0, 3) + swappedsample = QUBOTools.swap_sense(sampletest) + + @test QUBOTools.value(sampletest) == - QUBOTools.value(swappedsample) + + + V = Symbol + U = Int + T = Float64 + + bool_states = [[0, 1], [0, 0], [1, 0], [1, 1]] + + reads = [ 2, 1, 3, 4] + values = [ 0.0, 2.0, 4.0, 6.0] + bool_samples1 = [QUBOTools.Sample(s...) for s in zip(bool_states, values, reads)] + bool_samples2 = [QUBOTools.Sample(s...) for s in zip(bool_states, -values, reads)] + + model1 = QUBOTools.Model{𝔹,V,T,U}( + Dict{V,T}(:x => 1.0, :y => -1.0), + Dict{Tuple{V,V},T}((:x, :y) => 2.0); + scale = 2.0, + offset = 1.0, + id = 1, + version = v"0.1.0", + description = "This is a Bool ModelWrapper", + metadata = Dict{String,Any}( + "meta" => "data", + "type" => "bool", + ), + sampleset = QUBOTools.SampleSet(bool_samples1), + ) + + + swappedmodel1 = QUBOTools.swap_sense(:max, model1) + + @test QUBOTools.offset(swappedmodel1) == - QUBOTools.offset(model1) + @test first(QUBOTools.qubo(swappedmodel1,Matrix)) == - first(QUBOTools.qubo(model1,Matrix)) + +end + +function test_generic() + test_swap_sense() +end \ No newline at end of file diff --git a/test/unit/unit.jl b/test/unit/unit.jl index cc7c28b4..0190615f 100644 --- a/test/unit/unit.jl +++ b/test/unit/unit.jl @@ -1,5 +1,6 @@ include("library/library.jl") include("interface/interface.jl") +include("interface/generic.jl") include("models/models.jl") include("formats/formats.jl") include("analysis/analysis.jl") @@ -9,6 +10,7 @@ function test_unit() @testset "◈ Unit Tests ◈" verbose = true begin test_library() test_interface() + test_generic() test_models() test_formats() test_analysis()