Skip to content

Commit

Permalink
Merge pull request #28 from psrenergy/px/sense
Browse files Browse the repository at this point in the history
Add optimization sense tools
  • Loading branch information
pedromxavier authored Dec 15, 2022
2 parents b77ca1a + 8298885 commit ca29034
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 11 deletions.
4 changes: 2 additions & 2 deletions src/interface/fallback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
11 changes: 11 additions & 0 deletions src/interface/generic.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
46 changes: 41 additions & 5 deletions src/interface/interface.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions src/library/sampleset.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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])
Expand Down Expand Up @@ -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
29 changes: 25 additions & 4 deletions src/model/model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
77 changes: 77 additions & 0 deletions test/unit/interface/generic.jl
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions test/unit/unit.jl
Original file line number Diff line number Diff line change
@@ -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")
Expand All @@ -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()
Expand Down

2 comments on commit ca29034

@pedromxavier
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/74206

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v0.5.0 -m "<description of version>" ca2903451ba97007fc577bb91b7cc109c9a47c05
git push origin v0.5.0

Please sign in to comment.