diff --git a/Project.toml b/Project.toml index 8dbb0d3..da75854 100644 --- a/Project.toml +++ b/Project.toml @@ -1,17 +1,17 @@ -name = "ToQUBO" -uuid = "9a412ddf-83fa-43b6-9748-7843c851aa65" +name = "ToQUBO" +uuid = "9a412ddf-83fa-43b6-9748-7843c851aa65" authors = ["pedromxavier ", "pedroripper ", "joaquimg ", "AndradeTiago ", "bernalde "] -version = "0.1.7" +version = "0.1.8-dev" [deps] -MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" PseudoBooleanOptimization = "c8fa9a04-bc42-452d-8558-dc51757be744" -QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" -Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" -TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" [compat] -MathOptInterface = "1" +MathOptInterface = "1" PseudoBooleanOptimization = "0.2" -QUBOTools = "0.9" -julia = "1.9" +QUBOTools = "0.9" +julia = "1.9" diff --git a/src/ToQUBO.jl b/src/ToQUBO.jl index 5d0cb56..ba87638 100644 --- a/src/ToQUBO.jl +++ b/src/ToQUBO.jl @@ -27,9 +27,6 @@ const GT{T} = MOI.GreaterThan{T} const VI = MOI.VariableIndex const CI{F,S} = MOI.ConstraintIndex{F,S} -# Library -include("error.jl") - # Encoding Module include("encoding/encoding.jl") diff --git a/src/attributes/compiler.jl b/src/attributes/compiler.jl index 5a714e7..b1775c2 100644 --- a/src/attributes/compiler.jl +++ b/src/attributes/compiler.jl @@ -20,6 +20,60 @@ abstract type CompilerAttribute <: MOI.AbstractOptimizerAttribute end MOI.supports(::Optimizer, ::A) where {A<:CompilerAttribute} = true +@doc raw""" + CompilationTime() +""" +struct CompilationTime <: CompilerAttribute end + +MOI.is_set_by_optimize(::CompilationTime) = true + +function MOI.get(model::Optimizer, ::CompilationTime)::Union{Float64,Nothing} + return get(model.compiler_settings, :compilation_time, nothing) +end + +function MOI.set(model::Optimizer, ::CompilationTime, t::Any) + model.compiler_settings[:compilation_time] = convert(Float64, t) + + return nothing +end + +function MOI.set(model::Optimizer, ::CompilationTime, ::Nothing) + delete!(model.compiler_settings, :compilation_time) + + return nothing +end + +function compilation_time(model::Optimizer)::Union{Float64,Nothing} + return MOI.get(model, CompilationTime()) +end + +@doc raw""" + CompilationStatus() +""" +struct CompilationStatus <: CompilerAttribute end + +MOI.is_set_by_optimize(::CompilationStatus) = true + +function MOI.get(model::Optimizer, ::CompilationStatus) + return get(model.compiler_settings, :compilation_status, MOI.OPTIMIZE_NOT_CALLED) +end + +function MOI.set(model::Optimizer, ::CompilationStatus, status::MOI.TerminationStatusCode) + model.compiler_settings[:compilation_status] = status + + return nothing +end + +function MOI.set(model::Optimizer, ::CompilationStatus, ::Nothing) + delete!(model.compiler_settings, :compilation_status) + + return nothing +end + +function compilation_status(model::Optimizer)::MOI.TerminationStatusCode + return MOI.get(model, CompilationStatus()) +end + @doc raw""" Warnings() """ @@ -108,7 +162,7 @@ When set, this boolean flag guarantees that every coefficient in the final formu struct Discretize <: CompilerAttribute end function MOI.get(model::Optimizer, ::Discretize)::Bool - return get(model.compiler_settings, :discretize, false) + return get(model.compiler_settings, :discretize, true) end function MOI.set(model::Optimizer, ::Discretize, flag::Bool) @@ -161,11 +215,7 @@ function MOI.get(model::Optimizer, ::QuadratizationMethod) return get(model.compiler_settings, :quadratization_method, PBO.DEFAULT()) end -function MOI.set( - model::Optimizer, - ::QuadratizationMethod, - method::PBO.QuadratizationMethod, -) +function MOI.set(model::Optimizer, ::QuadratizationMethod, method::PBO.QuadratizationMethod) model.compiler_settings[:quadratization_method] = method return nothing @@ -248,7 +298,11 @@ function MOI.get( model::Optimizer, ::DefaultVariableEncodingMethod, )::Encoding.VariableEncodingMethod - return get(model.compiler_settings, :default_variable_encoding_method, Encoding.Binary()) + return get( + model.compiler_settings, + :default_variable_encoding_method, + Encoding.Binary(), + ) end function MOI.set( @@ -503,18 +557,13 @@ function MOI.get(model::Optimizer{T}, ::VariableEncodingPenaltyHint, vi::VI) whe end end -function MOI.set( - model::Optimizer{T}, - ::VariableEncodingPenaltyHint, - vi::VI, - ρ, -) where {T} +function MOI.set(model::Optimizer{T}, ::VariableEncodingPenaltyHint, vi::VI, ρ) where {T} attr = :variable_encoding_penalty_hint if !haskey(model.variable_settings, attr) model.variable_settings[attr] = Dict{VI,Any}() end - + model.variable_settings[attr][vi] = convert(T, ρ) return nothing @@ -588,7 +637,8 @@ end function MOI.get(model::Optimizer{T}, ::ConstraintEncodingPenaltyHint, ci::CI) where {T} attr = :constraint_encoding_penalty_hint - if !haskey(model.constraint_settings, attr) || !haskey(model.constraint_settings[attr], ci) + if !haskey(model.constraint_settings, attr) || + !haskey(model.constraint_settings[attr], ci) return nothing else return model.constraint_settings[attr][ci]::T @@ -606,7 +656,7 @@ function MOI.set( if !haskey(model.constraint_settings, attr) model.constraint_settings[attr] = Dict{CI,Any}() end - + model.constraint_settings[attr][ci] = convert(T, ρ) return nothing @@ -661,4 +711,295 @@ function MOI.set( return nothing end +@doc raw""" + SlackVariableEncodingMethod() + +Sets the encoding method for slack variables generated by constraints. +""" +struct SlackVariableEncodingMethod <: CompilerConstraintAttribute end + +function slack_variable_encoding_method(model::Optimizer, ci::CI)::Encoding.VariableEncodingMethod + e = MOI.get(model, SlackVariableEncodingMethod(), ci) + + if isnothing(e) + return MOI.get(model, DefaultVariableEncodingMethod()) + else + return e + end +end + +function MOI.get( + model::Optimizer, + ::SlackVariableEncodingMethod, + ci::CI, +)::Union{Encoding.VariableEncodingMethod,Nothing} + attr = :slack_variable_encoding_method + + if !haskey(model.constraint_settings, attr) || + !haskey(model.constraint_settings[attr], ci) + return nothing + else + return model.constraint_settings[attr][ci] + end +end + +function MOI.set( + model::Optimizer, + ::SlackVariableEncodingMethod, + ci::CI, + e::Encoding.VariableEncodingMethod, +) + attr = :slack_variable_encoding_method + + if !haskey(model.constraint_settings, attr) + model.constraint_settings[attr] = Dict{CI,Any}() + end + + model.constraint_settings[attr][ci] = e + + return nothing +end + +function MOI.set( + model::Optimizer, + ::SlackVariableEncodingMethod, + ci::CI, + ::Nothing, +) + attr = :slack_variable_encoding_method + + if haskey(model.constraint_settings, attr) + delete!(model.constraint_settings[attr], ci) + end + + return nothing +end + +@doc raw""" + SlackVariableEncodingATol() + +Sets the tolerance for slack variables generated by constraints. +""" +struct SlackVariableEncodingATol <: CompilerConstraintAttribute end + +function slack_variable_encoding_atol(model::Optimizer, ci::CI) + return MOI.get(model, SlackVariableEncodingATol(), ci) +end + +function MOI.get( + model::Optimizer{T}, + ::SlackVariableEncodingATol, + ci::CI, +)::Union{T,Nothing} where {T} + attr = :slack_variable_encoding_atol + + if !haskey(model.constraint_settings, attr) || + !haskey(model.constraint_settings[attr], ci) + return nothing + else + return model.constraint_settings[attr][ci]::T + end +end + +function MOI.set( + model::Optimizer{T}, + ::SlackVariableEncodingATol, + ci::CI, + τ::T, +)::Nothing where {T} + attr = :slack_variable_encoding_atol + + if !haskey(model.constraint_settings, attr) + model.constraint_settings[attr] = Dict{CI,Any}() + end + + model.constraint_settings[attr][ci] = τ + + return nothing +end + +function MOI.set( + model::Optimizer, + ::SlackVariableEncodingATol, + ci::CI, + ::Nothing, +) + attr = :slack_variable_encoding_atol + + if haskey(model.constraint_settings, attr) + delete!(model.constraint_settings[attr], ci) + end + + return nothing +end + +@doc raw""" + SlackVariableEncodingBits() + +Sets the number of bits for slack variables generated by constraints. +""" +struct SlackVariableEncodingBits <: CompilerConstraintAttribute end + +function slack_variable_encoding_bits(model::Optimizer, ci::CI) + return MOI.get(model, SlackVariableEncodingBits(), ci) +end + +function MOI.get( + model::Optimizer, + ::SlackVariableEncodingBits, + ci::CI, +)::Union{Integer,Nothing} + attr = :slack_variable_encoding_bits + + if !haskey(model.constraint_settings, attr) || + !haskey(model.constraint_settings[attr], ci) + return nothing + else + return model.constraint_settings[attr][ci] + end +end + +function MOI.set( + model::Optimizer, + ::SlackVariableEncodingBits, + ci::CI, + n::Integer, +)::Nothing + attr = :slack_variable_encoding_bits + + if !haskey(model.constraint_settings, attr) + model.constraint_settings[attr] = Dict{CI,Any}() + end + + model.constraint_settings[attr][ci] = n + + return nothing +end + +function MOI.set( + model::Optimizer, + ::SlackVariableEncodingBits, + ci::CI, + ::Nothing, +) + attr = :slack_variable_encoding_bits + + if haskey(model.constraint_settings, attr) + delete!(model.constraint_settings[attr], ci) + end + + return nothing +end + +@doc raw""" + SlackVariableEncodingPenaltyHint() + +Allows the user to hint the penalty factor used for encoding slack variables. +""" +struct SlackVariableEncodingPenaltyHint <: CompilerConstraintAttribute end + +function slack_variable_encoding_penalty_hint(model::Optimizer, ci::CI) + return MOI.get(model, SlackVariableEncodingPenaltyHint(), ci) +end + +function MOI.get( + model::Optimizer{T}, + ::SlackVariableEncodingPenaltyHint, + ci::CI, +)::Union{T,Nothing} where {T} + attr = :slack_variable_encoding_penalty_hint + + if !haskey(model.constraint_settings, attr) || + !haskey(model.constraint_settings[attr], ci) + return nothing + else + return model.constraint_settings[attr][ci]::T + end +end + +function MOI.set( + model::Optimizer{T}, + ::SlackVariableEncodingPenaltyHint, + ci::CI, + ρ::T, +)::Nothing where {T} + attr = :slack_variable_encoding_penalty_hint + + if !haskey(model.constraint_settings, attr) + model.constraint_settings[attr] = Dict{CI,Any}() + end + + model.constraint_settings[attr][ci] = ρ + + return nothing +end + +function MOI.set( + model::Optimizer, + ::SlackVariableEncodingPenaltyHint, + ci::CI, + ::Nothing, +) + attr = :slack_variable_encoding_penalty_hint + + if haskey(model.constraint_settings, attr) + delete!(model.constraint_settings[attr], ci) + end + + return nothing +end + +@doc raw""" + SlackVariableEncodingPenalty() + +Allows the user to retrieve the penalty factor used for encoding slack variables. +""" +struct SlackVariableEncodingPenalty <: CompilerConstraintAttribute end + +MOI.is_set_by_optimize(::SlackVariableEncodingPenalty) = true + +function slack_variable_encoding_penalty(model::Optimizer, ci::CI) + # TODO + # return MOI.get(model, SlackVariableEncodingPenalty(), ci) + return MOI.get(model, SlackVariableEncodingPenalty(), ci) +end + +function MOI.get( + model::Optimizer{T}, + ::SlackVariableEncodingPenalty, + ci::CI, +)::Union{T,Nothing} where {T} + # TODO + # vi = Virtual.source(model.slack[ci]) + + # return MOI.get(model, VariableEncodingPenalty(), vi) + return get(model.η, ci, nothing) +end + +function MOI.set( + model::Optimizer{T}, + ::SlackVariableEncodingPenalty, + ci::CI, + η::Any, +)::Nothing where {T} + # TODO: This doesn't work since slack variables have no source! + # vi = Virtual.source(model.slack[ci]) + + # MOI.set(model, VariableEncodingPenalty(), vi, η) + model.η[ci] = convert(T, η) + + return nothing +end + +function MOI.set( + model::Optimizer{T}, + ::SlackVariableEncodingPenalty, + ci::CI, + ::Nothing, +)::Nothing where {T} + delete!(model.η, ci) + + return nothing +end + end # module Attributes diff --git a/src/attributes/solver.jl b/src/attributes/solver.jl index 214d348..0b459dd 100644 --- a/src/attributes/solver.jl +++ b/src/attributes/solver.jl @@ -55,13 +55,7 @@ end function MOI.get( model::Virtual.Model, - attr::Union{ - MOI.SolveTimeSec, - MOI.PrimalStatus, - MOI.DualStatus, - MOI.TerminationStatus, - MOI.RawStatusString, - }, + attr::MOI.SolveTimeSec, ) if !isnothing(model.optimizer) return MOI.get(model.optimizer, attr) @@ -72,13 +66,7 @@ end function MOI.supports( model::Virtual.Model, - attr::Union{ - MOI.SolveTimeSec, - MOI.PrimalStatus, - MOI.DualStatus, - MOI.TerminationStatus, - MOI.RawStatusString, - }, + attr::MOI.SolveTimeSec, ) if !isnothing(model.optimizer) return MOI.supports(model.optimizer, attr) @@ -87,6 +75,59 @@ function MOI.supports( end end +function MOI.get( + model::Virtual.Model, + attr::MOI.RawStatusString, +) + if !isnothing(model.optimizer) && MOI.supports(model.optimizer, attr) + return MOI.get(model.optimizer, attr) + else + return get(model.moi_settings, :raw_status_string, "") + end +end + +function MOI.set( + model::Virtual.Model, + ::MOI.RawStatusString, + value::AbstractString, +) + model.moi_settings[:raw_status_string] = String(value) + + return nothing +end + +function MOI.supports(::Virtual.Model, ::MOI.RawStatusString) + return true +end + +function MOI.get(model::Virtual.Model, attr::MOI.TerminationStatus) + if !isnothing(model.optimizer) + return MOI.get(model.optimizer, attr) + else + return MOI.get(model, Attributes.CompilationStatus()) + end +end + +function MOI.supports(::Virtual.Model, ::MOI.TerminationStatus) + return true +end + +function MOI.get(model::Virtual.Model, attr::Union{MOI.PrimalStatus, MOI.DualStatus}) + if !isnothing(model.optimizer) + return MOI.get(model.optimizer, attr) + else + return MOI.NO_SOLUTION + end +end + +function MOI.supports(model::Virtual.Model, attr::Union{MOI.PrimalStatus, MOI.DualStatus}) + if !isnothing(model.optimizer) + return MOI.supports(model.optimizer, attr) + else + return true + end +end + function MOI.get(model::Virtual.Model, rc::MOI.ResultCount) if isnothing(model.optimizer) return 0 diff --git a/src/compiler/build.jl b/src/compiler/build.jl index dbcdc2a..e860ce8 100644 --- a/src/compiler/build.jl +++ b/src/compiler/build.jl @@ -12,11 +12,15 @@ function build!(model::Virtual.Model{T}, arch::AbstractArchitecture) where {T} end function objective_function(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} - empty!(model.H) + Base.empty!(model.H) # Calculate an upper bound on the number of terms - num_terms = - length(model.f) + sum(length, model.g; init = 0) + sum(length, model.h; init = 0) + num_terms = +( + length(model.f), + sum(length, model.g; init = 0), + sum(length, model.h; init = 0), + sum(length, model.s; init = 0), + ) sizehint!(model.H, num_terms) @@ -25,7 +29,7 @@ function objective_function(model::Virtual.Model{T}, ::AbstractArchitecture) whe end for (ci, g) in model.g - ρ = model.ρ[ci] + ρ = MOI.get(model, Attributes.ConstraintEncodingPenalty(), ci) for (ω, c) in g model.H[ω] += ρ * c @@ -33,13 +37,21 @@ function objective_function(model::Virtual.Model{T}, ::AbstractArchitecture) whe end for (vi, h) in model.h - θ = model.θ[vi] + θ = MOI.get(model, Attributes.VariableEncodingPenalty(), vi) for (ω, c) in h model.H[ω] += θ * c end end + for (ci, s) in model.s + η = MOI.get(model, Attributes.SlackVariableEncodingPenalty(), ci) + + for (ω, c) in s + model.H[ω] += η * c + end + end + return nothing end @@ -115,8 +127,13 @@ function output!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} # have this condition here. # HINT: When debugging this, a good place to start is to check if the 'Quadratize' # flag is set or not. If missing, it should mean that some constraint might induce - # PBFs of higher degree without calling 'MOI.set(model, Quadratize(), true)'. - compilation_error("Quadratization failed") + # PBFs of higher degree without calling + # MOI.set(model, AttributesQuadratize(), true) + compilation_error!( + model, + "Fatal: Quadratization failed"; + status="Failure in quadratization", + ) end end diff --git a/src/compiler/compiler.jl b/src/compiler/compiler.jl index dfe147a..f51548d 100644 --- a/src/compiler/compiler.jl +++ b/src/compiler/compiler.jl @@ -1,29 +1,28 @@ module Compiler # Imports -using MathOptInterface -const MOI = MathOptInterface +import MathOptInterface as MOI +import MathOptInterface: empty! +import PseudoBooleanOptimization as PBO -import QUBOTools: PBO import QUBOTools: AbstractArchitecture, GenericArchitecture -import ..Encoding: - Encoding, VariableEncodingMethod, Mirror, Unary, Binary, Arithmetic, OneHot, DomainWall - -import ..Virtual: Virtual, encoding, expansion, penaltyfn - import ..Attributes +import ..Encoding +import ..Virtual # Constants -const VI = MOI.VariableIndex -const SAT{T} = MOI.ScalarAffineTerm{T} -const SAF{T} = MOI.ScalarAffineFunction{T} -const SQT{T} = MOI.ScalarQuadraticTerm{T} -const SQF{T} = MOI.ScalarQuadraticFunction{T} -const EQ{T} = MOI.EqualTo{T} -const LT{T} = MOI.LessThan{T} -const GT{T} = MOI.GreaterThan{T} - +const VI = MOI.VariableIndex +const CI{F,S} = MOI.ConstraintIndex{F,S} +const SAT{T} = MOI.ScalarAffineTerm{T} +const SAF{T} = MOI.ScalarAffineFunction{T} +const SQT{T} = MOI.ScalarQuadraticTerm{T} +const SQF{T} = MOI.ScalarQuadraticFunction{T} +const EQ{T} = MOI.EqualTo{T} +const LT{T} = MOI.LessThan{T} +const GT{T} = MOI.GreaterThan{T} + +include("error.jl") include("analysis.jl") include("interface.jl") include("parse.jl") @@ -64,9 +63,6 @@ function compile!(model::Virtual.Model{T}, arch::AbstractArchitecture) where {T} # Add Regular Constraints constraints!(model, arch) - # Add Encoding Constraints - encoding_constraints!(model, arch) - # Compute penalties penalties!(model, arch) @@ -81,16 +77,16 @@ function reset!(model::Virtual.Model, ::AbstractArchitecture = GenericArchitectu MOI.empty!(model.target_model) # Virtual Variables - empty!(model.variables) - empty!(model.source) - empty!(model.target) + Base.empty!(model.variables) + Base.empty!(model.source) + Base.empty!(model.target) # PBF/IR - empty!(model.f) - empty!(model.g) - empty!(model.h) - empty!(model.ρ) - empty!(model.θ) + Base.empty!(model.f) + Base.empty!(model.g) + Base.empty!(model.h) + Base.empty!(model.ρ) + Base.empty!(model.θ) return nothing end diff --git a/src/compiler/constraints.jl b/src/compiler/constraints.jl index 424096a..a01e1e0 100644 --- a/src/compiler/constraints.jl +++ b/src/compiler/constraints.jl @@ -1,8 +1,17 @@ +function constraints!(model::Virtual.Model, arch::AbstractArchitecture) + for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) + constraints!(model, F, S, arch) + end + + return nothing +end + function constraints!(model::Virtual.Model, ::Type{F}, ::Type{S}, arch::AbstractArchitecture) where {F,S} for ci in MOI.get(model, MOI.ListOfConstraintIndices{F,S}()) f = MOI.get(model, MOI.ConstraintFunction(), ci) s = MOI.get(model, MOI.ConstraintSet(), ci) - g = constraint(model, f, s, arch) + + g = constraint(model, ci, f, s, arch) if !isnothing(g) model.g[ci] = g @@ -12,14 +21,6 @@ function constraints!(model::Virtual.Model, ::Type{F}, ::Type{S}, arch::Abstract return nothing end -function constraints!(model::Virtual.Model, arch::AbstractArchitecture) - for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) - constraints!(model, F, S, arch) - end - - return nothing -end - @doc raw""" constraint( ::Virtual.Model{T}, @@ -38,6 +39,7 @@ This method skips bound constraints over variables. """ function constraint( ::Virtual.Model{T}, + ::CI, ::VI, ::Union{MOI.ZeroOne,MOI.Integer,MOI.Interval{T},LT{T},GT{T}}, ::AbstractArchitecture, @@ -73,6 +75,7 @@ into """ function constraint( model::Virtual.Model{T}, + ::CI, f::SAF{T}, s::EQ{T}, arch::AbstractArchitecture, @@ -80,7 +83,9 @@ function constraint( # Scalar Affine Equality Constraint: g(x) = a'x - b = 0 g = _parse(model, f, s, arch) - PBO.discretize!(g) + if Attributes.discretize(model) + PBO.discretize!(g) + end # Bounds & Slack Variable l, u = PBO.bounds(g) @@ -125,6 +130,7 @@ by adding a slack variable ``z``. """ function constraint( model::Virtual.Model{T}, + ci::CI, f::SAF{T}, s::LT{T}, arch::AbstractArchitecture, @@ -132,7 +138,9 @@ function constraint( # Scalar Affine Inequality Constraint: g(x) = a'x - b ≤ 0 g = _parse(model, f, s, arch) - PBO.discretize!(g) + if Attributes.discretize(model) + PBO.discretize!(g) + end # Bounds & Slack Variable l, u = PBO.bounds(g) @@ -148,10 +156,12 @@ function constraint( end # Slack Variable - x = nothing - e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) S = (zero(T), abs(l)) - z = Encoding.encode!(model, x, e, S) + z = if Attributes.discretize(model) + variable_ℤ!(model, ci, S) + else + variable_ℝ!(model, ci, S) + end for (ω, c) in Virtual.expansion(z) g[ω] += c @@ -160,6 +170,74 @@ function constraint( return g^2 end +@doc raw""" + constraint( + model::Virtual.Model{T}, + f::SAF{T}, + s::GT{T}, + ::AbstractArchitecture + ) where {T} + +Turns constraints of the form + +```math +\begin{array}{rl} +\text{s.t} & \mathbf{a}'\mathbf{x} - b \ge 0 +\end{array} +``` + +into + +```math +\left\Vert(\mathbf{x})\right\Vert_{\left\lbrace{0}\right\rbrace} = (\mathbf{a}'\mathbf{x} - b - z)^{2} + +``` + +by adding a slack variable ``z``. +""" +function constraint( + model::Virtual.Model{T}, + ci::CI, + f::SAF{T}, + s::GT{T}, + arch::AbstractArchitecture, +) where {T} + # Scalar Affine Inequality Constraint: g(x) = a'x - b ≥ 0 + g = _parse(model, f, s, arch) + + if Attributes.discretize(model) + PBO.discretize!(g) + end + + # Bounds & Slack Variable + l, u = PBO.bounds(g) + + if l > zero(T) # Always feasible + @warn """ + Always-feasible constraint detected: + $(f) ≥ $(s.lower) + """ + return nothing + elseif u < zero(T) # Infeasible + @warn "Infeasible constraint detected" + end + + # Slack Variable + S = (zero(T), abs(u)) + z = if Attributes.discretize(model) + variable_ℤ!(model, ci, S) + else + variable_ℝ!(model, ci, S) + end + + for (ω, c) in Virtual.expansion(z) + g[ω] -= c + end + + return g^2 +end + + @doc raw""" constraint( model::Virtual.Model{T}, @@ -186,6 +264,7 @@ into """ function constraint( model::Virtual.Model{T}, + ::CI, f::SQF{T}, s::EQ{T}, arch::AbstractArchitecture, @@ -193,7 +272,9 @@ function constraint( # Scalar Quadratic Equality Constraint: g(x) = x' Q x + a' x - b = 0 g = _parse(model, f, s, arch) - PBO.discretize!(g) + if Attributes.discretize(model) + PBO.discretize!(g) + end # Bounds & Slack Variable l, u = PBO.bounds(g) @@ -205,7 +286,10 @@ function constraint( """ return nothing elseif l > zero(T) # Infeasible - @warn "Infeasible constraint detected" + @warn """ + Infeasible constraint detected: + $(f) ≤ $(s.value) + """ end # Tell the compiler that quadratization is necessary @@ -242,6 +326,7 @@ by adding a slack variable ``z``. """ function constraint( model::Virtual.Model{T}, + ci::CI, f::SQF{T}, s::LT{T}, arch::AbstractArchitecture, @@ -249,7 +334,9 @@ function constraint( # Scalar Quadratic Inequality Constraint: g(x) = x' Q x + a' x - b ≤ 0 g = _parse(model, f, s, arch) - PBO.discretize!(g) + if Attributes.discretize(model) + PBO.discretize!(g) + end # Bounds & Slack Variable l, u = PBO.bounds(g) @@ -261,14 +348,19 @@ function constraint( """ return nothing elseif l > zero(T) # Infeasible - @warn "Infeasible constraint detected" + @warn """ + Infeasible constraint detected: + $(f) ≤ $(s.upper) + """ end # Slack Variable - x = nothing - e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) S = (zero(T), abs(l)) - z = Encoding.encode!(model, x, e, S) + z = if Attributes.discretize(model) + variable_ℤ!(model, ci, S) + else + variable_ℝ!(model, ci, S) + end for (ω, c) in Virtual.expansion(z) g[ω] += c @@ -280,6 +372,76 @@ function constraint( return g^2 end +@doc raw""" + constraint( + model::Virtual.Model{T}, + f::SQF{T}, + s::GT{T}, + arch::AbstractArchitecture, + ) where {T} + +Turns constraints of the form + +```math +\begin{array}{rl} +\text{s.t} & \mathbf{x}'\mathbf{Q}\mathbf{x} + \mathbf{a}'\mathbf{x} - b \geq 0 +\end{array} +``` + +into + +```math +\left\Vert(\mathbf{x})\right\Vert_{\left\lbrace{0}\right\rbrace} = (\mathbf{x}'\mathbf{Q}\mathbf{x} + \mathbf{a}'\mathbf{x} - b - z)^{2} + +``` + +by adding a slack variable ``z``. +""" +function constraint( + model::Virtual.Model{T}, + ci::CI, + f::SQF{T}, + s::GT{T}, + arch::AbstractArchitecture, +) where {T} + # Scalar Quadratic Inequality Constraint: g(x) = x' Q x + a' x - b ≥ 0 + g = _parse(model, f, s, arch) + + if Attributes.discretize(model) + PBO.discretize!(g) + end + + # Bounds & Slack Variable + l, u = PBO.bounds(g) + + if l > zero(T) # Always feasible + @warn """ + Always-feasible constraint detected: + $(f) ≥ $(s.upper) + """ + return nothing + elseif u < zero(T) # Infeasible + @warn "Infeasible constraint detected" + end + + # Slack Variable + S = (zero(T), abs(u)) + z = if Attributes.discretize(model) + variable_ℤ!(model, ci, S) + else + variable_ℝ!(model, ci, S) + end + + for (ω, c) in Virtual.expansion(z) + g[ω] -= c + end + + # Tell the compiler that quadratization is necessary + MOI.set(model, Attributes.Quadratize(), true) + + return g^2 +end + @doc raw""" constraint( model::Virtual.Model{T}, @@ -290,53 +452,60 @@ end """ function constraint( model::Virtual.Model{T}, + ci::CI, x::MOI.VectorOfVariables, ::MOI.SOS1{T}, ::AbstractArchitecture, ) where {T} # Special Ordered Set of Type 1: ∑ x ≤ min x g = PBO.PBF{VI,T}() + h = PBO.PBF{VI,T}() for xi in x.variables vi = model.source[xi] - if !(encoding(vi) isa Mirror) - error("Currently, ToQUBO only supports SOS1 on binary variables") - end + if Virtual.encoding(vi) isa Encoding.Mirror + for (ω, _) in Virtual.expansion(vi) + g[ω] = one(T) + end + elseif Virtual.encoding(vi) isa Encoding.OneHot || Virtual.encoding(vi) isa Encoding.DomainWall + ξ = Virtual.expansion(vi) + a = ξ[nothing] - for (ωi, _) in Virtual.expansion(vi) - g[ωi] = one(T) - end - end + ω, c = argmin(p -> abs(last(p) + a), ξ) - # Slack variable - x = nothing - e = Encoding.Mirror{T}() - z = Encoding.encode!(model, x, e) + if !((c + a) ≈ zero(T)) + @warn "Variable $(xi) is always non-zero" + end - for (ω, c) in Virtual.expansion(z) - g[ω] += c - end + g[ω] = one(T) + else + ξ = Virtual.expansion(vi) - g[nothing] += -one(T) + # Slack variable + e = Encoding.Mirror{T}() + w = Encoding.encode!(model, ci, e) + χ = w * ξ^2 - return g^2 -end + for (ω, c) in χ + h[ω] += c + end -function encoding_constraints!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} - for v in model.variables - x = Virtual.source(v) + g[w] = one(T) - if isnothing(x) - continue + # Tell the compiler that quadratization is necessary + MOI.set(model, Attributes.Quadratize(), true) end + end - χ = Virtual.penaltyfn(v) + # Slack variable + z = variable_𝔹!(model, ci) - if !isnothing(χ) - model.h[x] = χ - end + for (ω, c) in Virtual.expansion(z) + g[ω] += c end - return nothing + g[nothing] += -one(T) + + return g^2 + h end diff --git a/src/compiler/error.jl b/src/compiler/error.jl new file mode 100644 index 0000000..0799640 --- /dev/null +++ b/src/compiler/error.jl @@ -0,0 +1,37 @@ +""" + CompilationError(msg::Union{Nothing, String}) + +This error indicates any failure during QUBO formulation +""" +struct CompilationError <: Exception + msg::Union{String,Nothing} + + function CompilationError(msg::Union{Nothing,String} = nothing) + return new(msg) + end +end + +function Base.showerror(io::IO, e::CompilationError) + if isnothing(e.msg) + print(io, "The current model can't be converted to QUBO") + else + print(io, e.msg) + end +end + +function compilation_error(msg::Union{Nothing,String} = nothing) + throw(CompilationError(msg)) + + return nothing +end + +function compilation_error!(model::Virtual.Model, msg::Union{Nothing,String} = nothing; status::AbstractString = "") + # Update model status + MOI.set(model, Attributes.CompilationStatus(), MOI.OTHER_ERROR) + MOI.set(model, MOI.RawStatusString(), status) + + # Throw error + compilation_error(msg) + + return nothing +end diff --git a/src/compiler/parse.jl b/src/compiler/parse.jl index 4060e4b..201226f 100644 --- a/src/compiler/parse.jl +++ b/src/compiler/parse.jl @@ -17,7 +17,7 @@ function parse!( vi::VI, ::AbstractArchitecture, ) where {T} - empty!(g) + Base.empty!(g) for (ω, c) in expansion(model.source[vi]) g[ω] += c @@ -32,7 +32,7 @@ function parse!( f::SAF{T}, ::AbstractArchitecture, ) where {T} - empty!(g) + Base.empty!(g) sizehint!(g, length(f.terms) + 1) @@ -41,7 +41,7 @@ function parse!( x = a.variable v = model.source[x] - for (ω, d) in expansion(v) + for (ω, d) in Virtual.expansion(v) g[ω] += c * d end end @@ -79,13 +79,28 @@ function parse!( return nothing end +function parse!( + model::Virtual.Model{T}, + g::PBO.PBF{VI,T}, + f::SAF{T}, + s::GT{T}, + arch::AbstractArchitecture, +) where {T} + parse!(model, g, f, arch) + + g[nothing] -= s.lower + + return nothing +end + + function parse!( model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::SQF{T}, ::AbstractArchitecture, ) where {T} - empty!(g) + Base.empty!(g) sizehint!(g, length(f.quadratic_terms) + length(f.affine_terms) + 1) @@ -103,8 +118,8 @@ function parse!( c /= 2 end - for (ωi, di) in expansion(vi) - for (ωj, dj) in expansion(vj) + for (ωi, di) in Virtual.expansion(vi) + for (ωj, dj) in Virtual.expansion(vj) g[union(ωi, ωj)] += c * di * dj end end @@ -115,7 +130,7 @@ function parse!( x = a.variable v = model.source[x] - for (ω, d) in expansion(v) + for (ω, d) in Virtual.expansion(v) g[ω] += c * d end end @@ -152,3 +167,18 @@ function parse!( return nothing end + +function parse!( + model::Virtual.Model{T}, + g::PBO.PBF{VI,T}, + f::SQF{T}, + s::GT{T}, + arch::AbstractArchitecture, +) where {T} + parse!(model, g, f, arch) + + g[nothing] -= s.lower + + return nothing +end + diff --git a/src/compiler/penalties.jl b/src/compiler/penalties.jl index 2209df4..4ff65c2 100644 --- a/src/compiler/penalties.jl +++ b/src/compiler/penalties.jl @@ -1,6 +1,6 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} # Adjust Sign - s = MOI.get(model, MOI.ObjectiveSense()) === MOI.MAX_SENSE ? -1 : 1 + σ = MOI.get(model, MOI.ObjectiveSense()) === MOI.MAX_SENSE ? -1 : 1 β = one(T) # TODO: This should be made a parameter too? Yes! δ = PBO.maxgap(model.f) @@ -10,10 +10,10 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} if isnothing(ρ) ϵ = PBO.mingap(g) - ρ = s * (δ / ϵ + β) + ρ = σ * (δ / ϵ + β) end - model.ρ[ci] = ρ + MOI.set(model, Attributes.ConstraintEncodingPenalty(), ci, ρ) end for (vi, h) in model.h @@ -21,10 +21,21 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} if isnothing(θ) ϵ = PBO.mingap(h) - θ = s * (δ / ϵ + β) + θ = σ * (δ / ϵ + β) end - model.θ[vi] = θ + MOI.set(model, Attributes.VariableEncodingPenalty(), vi, θ) + end + + for (ci, s) in model.s + η = Attributes.slack_variable_encoding_penalty_hint(model, ci) + + if isnothing(η) + ϵ = PBO.mingap(s) + η = σ * (δ / ϵ + β) + end + + MOI.set(model, Attributes.SlackVariableEncodingPenalty(), ci, η) end return nothing diff --git a/src/compiler/variables.jl b/src/compiler/variables.jl index 06235b3..2036533 100644 --- a/src/compiler/variables.jl +++ b/src/compiler/variables.jl @@ -71,12 +71,17 @@ function variables!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} end end - # Encode Variables if Attributes.stable_compilation(model) sort!(Ω; by = x -> x.value) end - + + # Encode Variables for x in Ω + # If variable was already encoded, skip + if haskey(model.source, x) + continue + end + if haskey(ℤ, x) variable_ℤ!(model, x, ℤ[x]) elseif haskey(ℝ, x) @@ -89,29 +94,37 @@ function variables!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} return nothing end -function variable_𝔹!(model::Virtual.Model{T}, x::VI) where {T} - Encoding.encode!(model, x, Mirror{T}()) - - return nothing +function variable_𝔹!(model::Virtual.Model{T}, i::Union{VI,CI}) where {T} + return Encoding.encode!(model, i, Encoding.Mirror{T}()) end -function variable_ℤ!(model::Virtual.Model{T}, x::VI, (a, b)::Tuple{T,T}) where {T} +function variable_ℤ!(model::Virtual.Model{T}, vi::VI, (a, b)::Tuple{T,T}) where {T} if isnothing(a) || isnothing(b) - error("Unbounded variable $(x) ∈ ℤ") + error("Unbounded variable $(vi) ∈ ℤ") else - let e = Attributes.variable_encoding_method(model, x) + let e = Attributes.variable_encoding_method(model, vi) S = (a, b) - Encoding.encode!(model, x, e, S) + return Encoding.encode!(model, vi, e, S) end end +end - return nothing +function variable_ℤ!(model::Virtual.Model{T}, ci::CI, (a, b)::Tuple{T,T}) where {T} + if isnothing(a) || isnothing(b) + error("Unbounded variable $(ci) ∈ ℤ") + else + let e = Attributes.slack_variable_encoding_method(model, ci) + S = (a, b) + + return Encoding.encode!(model, ci, e, S) + end + end end -function variable_ℝ!(model::Virtual.Model{T}, x::VI, (a, b)::Tuple{T,T}) where {T} +function variable_ℝ!(model::Virtual.Model{T}, vi::VI, (a, b)::Tuple{T,T}) where {T} if isnothing(a) || isnothing(b) - error("Unbounded variable $(x) ∈ ℝ") + error("Unbounded variable $(vi) ∈ ℝ") else # TODO: Solve this bit-guessing magic??? (DONE) # IDEA: @@ -122,23 +135,40 @@ function variable_ℝ!(model::Virtual.Model{T}, x::VI, (a, b)::Tuple{T,T}) where # # For 𝔼[|xᵢ - x̂|] ≤ τ we have # N ≥ log₂(1 + |b - a| / 4τ) - # + # # where τ is the (absolute) tolerance # TODO: Add τ as parameter (DONE) # TODO: Move this comment to the documentation - let e = Attributes.variable_encoding_method(model, x) - n = Attributes.variable_encoding_bits(model, x) + let e = Attributes.variable_encoding_method(model, vi) + n = Attributes.variable_encoding_bits(model, vi) S = (a, b) if !isnothing(n) - Encoding.encode!(model, x, e, S, n) + return Encoding.encode!(model, vi, e, S, n) else - tol = Attributes.variable_encoding_atol(model, x) + tol = Attributes.variable_encoding_atol(model, vi) - Encoding.encode!(model, x, e, S; tol) + return Encoding.encode!(model, vi, e, S; tol) end end end +end - return nothing +function variable_ℝ!(model::Virtual.Model{T}, ci::CI, (a, b)::Tuple{T,T}) where {T} + if isnothing(a) || isnothing(b) + error("Unbounded slack variable $(ci) ∈ ℝ") + else + let e = Attributes.slack_variable_encoding_method(model, ci) + n = Attributes.slack_variable_encoding_bits(model, ci) + S = (a, b) + + if !isnothing(n) + return Encoding.encode!(model, ci, e, S, n) + else + tol = Attributes.slack_variable_encoding_atol(model, ci) + + return Encoding.encode!(model, ci, e, S; tol) + end + end + end end diff --git a/src/encoding/encoding.jl b/src/encoding/encoding.jl index 8bacb40..9397f33 100644 --- a/src/encoding/encoding.jl +++ b/src/encoding/encoding.jl @@ -8,7 +8,7 @@ const VI = MOI.VariableIndex include("interface.jl") include("extras.jl") -include("variables/interval/mirror.jl") +include("variables/mirror.jl") include("variables/interval/bounded.jl") include("variables/interval/unary.jl") include("variables/interval/binary.jl") diff --git a/src/encoding/variables/interval/binary.jl b/src/encoding/variables/interval/binary.jl index 9777dc5..d9852d0 100644 --- a/src/encoding/variables/interval/binary.jl +++ b/src/encoding/variables/interval/binary.jl @@ -37,7 +37,12 @@ function encode(var::Function, e::Binary{T}, S::Tuple{T,T}; tol::Union{T,Nothing a, b = integer_interval(S) - @assert b > a + if a == b + y = VI[] + ξ = PBO.PBF{VI,T}(a) + + return (y, ξ, nothing) + end M = trunc(Int, b - a) N = ceil(Int, log2(M + 1)) @@ -56,7 +61,7 @@ function encode(var::Function, e::Binary{T}, S::Tuple{T,T}; tol::Union{T,Nothing ) end - return (y, ξ, nothing) # No penalty function + return (y, ξ, nothing) end function encoding_bits(::Binary{T}, S::Tuple{T,T}, tol::T) where {T} diff --git a/src/encoding/variables/interval/mirror.jl b/src/encoding/variables/mirror.jl similarity index 100% rename from src/encoding/variables/interval/mirror.jl rename to src/encoding/variables/mirror.jl diff --git a/src/encoding/variables/set/domain_wall.jl b/src/encoding/variables/set/domain_wall.jl index 1c231f4..4d72a75 100644 --- a/src/encoding/variables/set/domain_wall.jl +++ b/src/encoding/variables/set/domain_wall.jl @@ -14,6 +14,8 @@ where ``\mathbf{y} \in \mathbb{B}^{n + 1}``. """ struct DomainWall{T} <: SetVariableEncodingMethod end +DomainWall() = DomainWall{Float64}() + # Arbitrary set function encode(var::Function, e::DomainWall{T}, γ::AbstractVector{T}) where {T} p = length(γ) diff --git a/src/encoding/variables/set/one_hot.jl b/src/encoding/variables/set/one_hot.jl index d3b0546..3bcde3b 100644 --- a/src/encoding/variables/set/one_hot.jl +++ b/src/encoding/variables/set/one_hot.jl @@ -21,6 +21,8 @@ is added to the objective function. """ struct OneHot{T} <: SetVariableEncodingMethod end +OneHot() = OneHot{Float64}() + # Arbitrary set function encode(var::Function, e::OneHot{T}, γ::AbstractVector{T}) where {T} p = length(γ) @@ -97,7 +99,11 @@ function encode( a, b = S - Γ = collect(range(a, b; length = p)) + Γ = if p == 1 + T[(a + b) / 2] + else + collect(T, range(a, b; length = p)) + end return encode(var, e, Γ) end diff --git a/src/error.jl b/src/error.jl deleted file mode 100644 index f72319a..0000000 --- a/src/error.jl +++ /dev/null @@ -1,24 +0,0 @@ -""" - QUBOCompilationError(msg::Union{Nothing, String}) - -This error indicates any failure during QUBO formulation -""" -struct QUBOCompilationError <: Exception - msg::Union{String,Nothing} - - function QUBOCompilationError(msg::Union{Nothing,String} = nothing) - return new(msg) - end -end - -function Base.showerror(io::IO, e::QUBOCompilationError) - if isnothing(e.msg) - print(io, "The current model can't be converted to QUBO") - else - print(io, e.msg) - end -end - -function compilation_error(msg::Union{Nothing,String} = nothing) - throw(QUBOCompilationError(msg)) -end \ No newline at end of file diff --git a/src/model/prequbo.jl b/src/model/prequbo.jl index bf35966..77797bd 100644 --- a/src/model/prequbo.jl +++ b/src/model/prequbo.jl @@ -11,6 +11,3 @@ MOIU.@model( false, # is optimizer? ) -# Drop Generic Constraint Support -MOI.supports_constraint(::PreQUBOModel{T}, ::Type{SAF{T}}, ::Type{GT{T}}) where {T} = false -MOI.supports_constraint(::PreQUBOModel{T}, ::Type{SQF{T}}, ::Type{GT{T}}) where {T} = false diff --git a/src/virtual/encoding.jl b/src/virtual/encoding.jl index 16d5199..fb58101 100644 --- a/src/virtual/encoding.jl +++ b/src/virtual/encoding.jl @@ -1,8 +1,19 @@ function Encoding.encode!(model::Model{T}, v::Variable{T}) where {T} x = source(v) + χ = penaltyfn(v) - if !isnothing(x) + if x isa VI model.source[x] = v + + if !isnothing(χ) + model.h[x] = χ + end + elseif x isa CI + model.slack[x] = v + + if !isnothing(χ) + model.s[x] = χ + end end for y in target(v) @@ -16,7 +27,11 @@ function Encoding.encode!(model::Model{T}, v::Variable{T}) where {T} return v end -function Encoding.encode!(model::Model{T}, x::Union{VI,Nothing}, e::VariableEncodingMethod) where {T} +function Encoding.encode!( + model::Model{T}, + x::Union{VI,CI,Nothing}, + e::VariableEncodingMethod, +) where {T} y, ξ, χ = Encoding.encode(e) do (nv::Union{Integer,Nothing} = nothing) if isnothing(nv) return MOI.add_variable(model.target_model) @@ -32,7 +47,7 @@ end function Encoding.encode!( model::Model{T}, - x::Union{VI,Nothing}, + x::Union{VI,CI,Nothing}, e::VariableEncodingMethod, γ::AbstractVector{T}, ) where {T} @@ -51,7 +66,7 @@ end function encode!( model::Model{T}, - x::Union{VI,Nothing}, + x::Union{VI,CI,Nothing}, e::VariableEncodingMethod, S::Tuple{T,T}; tol::Union{T,Nothing} = nothing, @@ -71,7 +86,7 @@ end function Encoding.encode!( model::Model{T}, - x::Union{VI,Nothing}, + x::Union{VI,CI,Nothing}, e::VariableEncodingMethod, S::Tuple{T,T}, n::Integer, diff --git a/src/virtual/model.jl b/src/virtual/model.jl index 0b4717b..1748004 100644 --- a/src/virtual/model.jl +++ b/src/virtual/model.jl @@ -3,76 +3,85 @@ This Virtual Model links the final QUBO formulation to the original one, allowing variable value retrieving and other features. """ -mutable struct Model{T} <: MOI.AbstractOptimizer - # Underlying Optimizer # - optimizer::Union{MOI.AbstractOptimizer,Nothing} +mutable struct Model{T,O} <: MOI.AbstractOptimizer + # Underlying Optimizer + optimizer::O - # MathOptInterface Bridges # + # MathOptInterface Bridges bridge_model::MOIB.LazyBridgeOptimizer{PreQUBOModel{T}} - # Virtual Model Interface # + # Virtual Model Interface source_model::PreQUBOModel{T} target_model::QUBOModel{T} variables::Vector{Variable{T}} source::Dict{VI,Variable{T}} target::Dict{VI,Variable{T}} + slack::Dict{CI,Variable{T}} - # PBO/PBF IR # + # PBO/PBF IR f::PBO.PBF{VI,T} # Objective Function - g::Dict{CI,PBO.PBF{VI,T}} # Constraint Functions - h::Dict{VI,PBO.PBF{VI,T}} # Variable Functions - ρ::Dict{CI,T} # Constraint Penalties - θ::Dict{VI,T} # Variable Penalties + g::Dict{CI,PBO.PBF{VI,T}} # Constraint Penalty Functions + ρ::Dict{CI,T} # Constraint Penalty Factors + h::Dict{VI,PBO.PBF{VI,T}} # Variable Penalty Functions + θ::Dict{VI,T} # Variable Penalty Factors + s::Dict{CI,PBO.PBF{VI,T}} # Slack Penalty Functions + η::Dict{CI,T} # Slack Penalty Factors H::PBO.PBF{VI,T} # Final Objective Function # Settings compiler_settings::Dict{Symbol,Any} variable_settings::Dict{Symbol,Dict{VI,Any}} constraint_settings::Dict{Symbol,Dict{CI,Any}} + moi_settings::Dict{Symbol,Any} - function Model{T}( - constructor::Union{Type{O},Function}; - kws..., - ) where {T,O<:MOI.AbstractOptimizer} - optimizer = constructor() + function Model{T}(constructor::Any; kws...) where {T} + optimizer = constructor()::MOI.AbstractOptimizer + + return Model{T,typeof(optimizer)}(optimizer; kws...) + end - return Model{T}(optimizer; kws...) + function Model{T}(::Nothing = nothing; kws...) where {T} + return Model{T,Nothing}(nothing; kws...) end - function Model{T}( - optimizer::Union{O,Nothing} = nothing; + function Model{T,O}( + optimizer::O = nothing; kws..., - ) where {T,O<:MOI.AbstractOptimizer} + ) where {T,O<:Union{MOI.AbstractOptimizer,Nothing}} source_model = PreQUBOModel{T}() target_model = QUBOModel{T}() bridge_model = MOIB.full_bridge_optimizer(source_model, T) - new{T}( - # Underlying Optimizer # + new{T,O}( + # Underlying Optimizer optimizer, - # MathOptInterface Bridges # + # MathOptInterface Bridges bridge_model, # Virtual Model Interface source_model, target_model, - Vector{Variable{T}}(), - Dict{VI,Variable{T}}(), - Dict{VI,Variable{T}}(), + Vector{Variable{T}}(), # variables + Dict{VI,Variable{T}}(), # source + Dict{VI,Variable{T}}(), # target + Dict{CI,Variable{T}}(), # slack # PBO/PBF IR PBO.PBF{VI,T}(), # Objective Function - Dict{CI,PBO.PBF{VI,T}}(), # Constraint Functions - Dict{VI,PBO.PBF{VI,T}}(), # Variable Functions - Dict{CI,T}(), # Constraint Penalties - Dict{VI,T}(), # Variable Penalties + Dict{CI,PBO.PBF{VI,T}}(), # Constraint Penalty Functions + Dict{CI,T}(), # Constraint Penalty Factors + Dict{VI,PBO.PBF{VI,T}}(), # Variable Penalty Functions + Dict{VI,T}(), # Variable Penalty Factors + Dict{CI,PBO.PBF{VI,T}}(), # Slack Penalty Functions + Dict{CI,T}(), # Slack Penalty Factors PBO.PBF{VI,T}(), # Final Objective Function # Settings Dict{Symbol,Any}(), Dict{Symbol,Dict{VI,Any}}(), Dict{Symbol,Dict{CI,Any}}(), + Dict{Symbol,Any}(), ) end end diff --git a/src/virtual/variable.jl b/src/virtual/variable.jl index 4d3e4cc..98d3f95 100644 --- a/src/virtual/variable.jl +++ b/src/virtual/variable.jl @@ -4,14 +4,14 @@ """ struct Variable{T} e::VariableEncodingMethod - x::Union{VI,Nothing} # Source variable (if there is one) + x::Union{VI,CI,Nothing} # Source variable or constraint (if any) y::Vector{VI} # Target variables ξ::PBO.PBF{VI,T} # Expansion function χ::Union{PBO.PBF{VI,T},Nothing} # Penalty function (i.e. ‖gᵢ(x)‖ₛ for g(i) ∈ S) function Variable{T}( e::VariableEncodingMethod, - x::Union{VI,Nothing}, + x::Union{VI,CI,Nothing}, y::Vector{VI}, ξ::PBO.PBF{VI,T}, χ::Union{PBO.PBF{VI,T},Nothing}, diff --git a/src/wrapper.jl b/src/wrapper.jl index 22c2be6..2109be9 100644 --- a/src/wrapper.jl +++ b/src/wrapper.jl @@ -28,10 +28,16 @@ function MOI.optimize!(model::Optimizer) index_map = MOIU.identity_index_map(model.source_model) # De facto JuMP to QUBO Compilation - ToQUBO.Compiler.compile!(model) + let t = @elapsed ToQUBO.Compiler.compile!(model) + MOI.set(model, Attributes.CompilationStatus(), MOI.LOCALLY_SOLVED) + MOI.set(model, Attributes.CompilationTime(), t) + end if !isnothing(model.optimizer) MOI.optimize!(model.optimizer, model.target_model) + MOI.set(model, MOI.RawStatusString(), MOI.get(model.optimizer, MOI.RawStatusString())) + else + MOI.set(model, MOI.RawStatusString(), "Compilation complete without an internal solver") end return (index_map, false) @@ -138,7 +144,7 @@ MOI.supports_constraint( MOI.supports_constraint( ::Optimizer{T}, ::Type{<:Union{SAF{T},SQF{T}}}, - ::Type{<:Union{MOI.EqualTo{T},MOI.LessThan{T}}}, + ::Type{<:Union{MOI.EqualTo{T},MOI.LessThan{T},MOI.GreaterThan{T}}}, ) where {T} = true MOI.supports_constraint( diff --git a/test/integration/examples/continuous/continuous.jl b/test/integration/examples/continuous/continuous.jl index 9bfb295..f40c27a 100644 --- a/test/integration/examples/continuous/continuous.jl +++ b/test/integration/examples/continuous/continuous.jl @@ -1,7 +1,9 @@ include("continuous_1.jl") +include("continuous_2.jl") function test_continuous() @testset "Continuous Variables" verbose = true begin test_continuous_1() + test_continuous_2() end end diff --git a/test/integration/examples/continuous/continuous_2.jl b/test/integration/examples/continuous/continuous_2.jl new file mode 100644 index 0000000..896982f --- /dev/null +++ b/test/integration/examples/continuous/continuous_2.jl @@ -0,0 +1,111 @@ +""" + +min f(x) = x₁ + 2x₂ + 3x₃ + s.t. x₁ + x₂ + x₃ ≥ 4 + x₁, x₂, x₃ ∈ [0, 2] ⊂ ℤ + + +QUBO formulation: + +x₁ ↤ x₁,₁ + x₁,₂ +x₂ ↤ x₂,₁ + x₂,₂ +x₃ ↤ x₃,₁ + x₃,₂ + +min f(x) = x₁,₁ + x₁,₂ + 2 (x₂,₁ + x₂,₂) + 3 (x₃,₁ + x₃,₂) + s.t. x₁,₁ + x₁,₂ + x₂,₁ + x₂,₂ + x₃,₁ + x₃,₂ ≥ 4 + x₁,₁, x₁,₂, x₂,₁, x₂,₂, x₃,₁, x₃,₂ ∈ 𝔹 + +Adding a slack variable s ∈ [0, 2]: + +min f(x) = x₁,₁ + x₁,₂ + 2 (x₂,₁ + x₂,₂) + 3 (x₃,₁ + x₃,₂) + s.t. x₁,₁ + x₁,₂ + x₂,₁ + x₂,₂ + x₃,₁ + x₃,₂ - s - 4 = 0 + x₁,₁, x₁,₂, x₂,₁, x₂,₂, x₃,₁, x₃,₂ ∈ 𝔹 + s ∈ [0, 2] ⊂ ℤ + +Encoding s using binary variables: + +min f(x) = x₁,₁ + x₁,₂ + 2 (x₂,₁ + x₂,₂) + 3 (x₃,₁ + x₃,₂) + s.t. x₁,₁ + x₁,₂ + x₂,₁ + x₂,₂ + x₃,₁ + x₃,₂ - 4 - s₁ - 2 s₂ = 0 + x₁,₁, x₁,₂, x₂,₁, x₂,₂, x₃,₁, x₃,₂, s₁, s₂ ∈ 𝔹 + +Moving the constraint to the objective as a penalty: + +min f(x) = x₁,₁ + x₁,₂ + 2 (x₂,₁ + x₂,₂) + 3 (x₃,₁ + x₃,₂) + ρ (x₁,₁ + x₁,₂ + x₂,₁ + x₂,₂ + x₃,₁ + x₃,₂ - 4 - s₁ - 2 s₂)² + s.t. x₁,₁, x₁,₂, x₂,₁, x₂,₂, x₃,₁, x₃,₂, s₁, s₂ ∈ 𝔹 + + (x₁,₁ + x₁,₂ + x₂,₁ + x₂,₂ + x₃,₁ + x₃,₂ - 4 - s₁ - s₂)^2 = + - 7 x₁,₁ - 7 x₁,₂ - 7 x₂,₁ - 7 x₂,₂ - 7 x₃,₁ - 7 x₃,₂ + 9 s₁ + 9 s₂ + x₁,₁ x₁,₂ + x₁,₁ x₂,₁ + x₁,₁ x₂,₂ + x₁,₁ x₃,₁ + x₁,₁ x₃,₂ - 2 s₁ x₁,₁ - 2 s₂ x₁,₁ + + x₁,₂ x₁,₁ + x₁,₂ x₂,₁ + x₁,₂ x₂,₂ + x₁,₂ x₃,₁ + x₁,₂ x₃,₂ - 2 s₁ x₁,₂ - 2 s₂ x₁,₂ + + x₂,₁ x₁,₁ + x₂,₁ x₁,₂ + x₂,₁ x₂,₂ + x₂,₁ x₃,₁ + x₂,₁ x₃,₂ - 2 s₁ x₂,₁ - 2 s₂ x₂,₁ + + x₂,₂ x₁,₁ + x₂,₂ x₁,₂ + x₂,₂ x₂,₁ + x₂,₂ x₃,₁ + x₂,₂ x₃,₂ - 2 s₁ x₂,₂ - 2 s₂ x₂,₂ + + x₃,₁ x₁,₁ + x₃,₁ x₁,₂ + x₃,₁ x₂,₁ + x₃,₁ x₂,₂ + x₃,₁ x₃,₂ - 2 s₁ x₃,₁ - 2 s₂ x₃,₁ + + x₃,₂ x₁,₁ + x₃,₂ x₁,₂ + x₃,₂ x₂,₁ + x₃,₂ x₂,₂ + x₃,₂ x₃,₁ - 2 s₁ x₃,₂ - 2 s₂ x₃,₂ + + + 2 s₁ s₂ + 16 + +""" +function test_continuous_2() + @testset "Greater than constraint penalty hint" begin + ρ̄ = 3.0 + ᾱ = 1.0 + β̄ = 16ρ̄ + + F̄ = [ + 1 0 0 0 0 0 0 0 + 0 1 0 0 0 0 0 0 + 0 0 2 0 0 0 0 0 + 0 0 0 2 0 0 0 0 + 0 0 0 0 3 0 0 0 + 0 0 0 0 0 3 0 0 + 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 + ] + + Ḡ = [ + -7 2 2 2 2 2 -2 -2 + 0 -7 2 2 2 2 -2 -2 + 0 0 -7 2 2 2 -2 -2 + 0 0 0 -7 2 2 -2 -2 + 0 0 0 0 -7 2 -2 -2 + 0 0 0 0 0 -7 -2 -2 + 0 0 0 0 0 0 9 2 + 0 0 0 0 0 0 0 9 + ] + + Q̄ = F̄ + ρ̄ * Ḡ + + x̄ = [2.0, 2.0, 0.0] + ȳ = 6 + + model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) + + @variable(model, 0 <= x[1:3] <= 2, Int) + @constraint(model, c, sum(x) >= 4) + @objective(model, Min, sum(i * x[i] for i = 1:3)) + + set_attribute(c, ToQUBO.Attributes.ConstraintEncodingPenaltyHint(), ρ̄) + + optimize!(model) + + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + ρ = get_attribute(c, ToQUBO.Attributes.ConstraintEncodingPenalty()) + + Q̂ = Q + diagm(L) + + @test n == 8 + @test ρ ≈ ρ̄ + @test α ≈ ᾱ + @test β ≈ β̄ + @test Q̂ ≈ Q̄ + + # Solutions + x̂ = value.(x) + ŷ = objective_value(model) + + @test x̂ ≈ x̄ + @test ŷ ≈ ȳ + end + + return nothing +end diff --git a/test/integration/examples/quadratic/quadratic.jl b/test/integration/examples/quadratic/quadratic.jl index 949f2e1..f95a6a3 100644 --- a/test/integration/examples/quadratic/quadratic.jl +++ b/test/integration/examples/quadratic/quadratic.jl @@ -1,9 +1,11 @@ include("quadratic_1.jl") +include("quadratic_2.jl") include("primes.jl") function test_quadratic() @testset "Quadratic Programs" verbose = true begin test_quadratic_1() + test_quadratic_2() test_primes() end end diff --git a/test/integration/examples/quadratic/quadratic_2.jl b/test/integration/examples/quadratic/quadratic_2.jl new file mode 100644 index 0000000..a1c942b --- /dev/null +++ b/test/integration/examples/quadratic/quadratic_2.jl @@ -0,0 +1,34 @@ +QUBOTools.PBO.varshow(v::VI) = QUBOTools.PBO.varshow(v.value) + + +""" +min x₁ + x₂ + st x₁² + x₂² >= 1 + x₁, x₂ ∈ {0, 1} + +min x₁ + x₂ + ρ (x₁² + x₂² - 1 + s)² + st x₁, x₂ ∈ {0, 1} + s ∈ [0, 1] + +""" +function test_quadratic_2() + model = Model(() -> ToQUBO.Optimizer()) + + @variable(model, x[1:2], Bin) + @objective(model, Min, x[1] + x[2]) + @constraint(model, c, x[1] * x[2] >= 1) + + optimize!(model) + + n, l, q, α, β = QUBOTools.qubo(model, :dense) + + Q = q + diagm(l) + + @show n + @show Q + + @test termination_status(model) === MOI.LOCALLY_SOLVED + @test get_attribute(model, Attributes.CompilationStatus()) === MOI.LOCALLY_SOLVED + + return nothing +end diff --git a/test/integration/interface.jl b/test/integration/interface.jl index ad8a2cd..aad9f3d 100644 --- a/test/integration/interface.jl +++ b/test/integration/interface.jl @@ -89,16 +89,16 @@ function test_interface_moi() # max x1 + x2 + x3 # st x1 + x2 <= 1 (c1) # x2 + x3 <= 1 (c2) - # x1 ∈ {0, 1} - # x2 ∈ {0, 1} - # x3 ∈ {0, 1} + # 0 <= x1 <= 1 + # 0 <= x2 <= 1 + # 0 <= x3 <= 1 model = MOI.instantiate( () -> ToQUBO.Optimizer(RandomSampler.Optimizer); with_bridge_type = Float64, ) - x, _ = MOI.add_constrained_variables(model, fill(MOI.ZeroOne(), 3)) + x, _ = MOI.add_constrained_variables(model, fill(MOI.Interval{Float64}(0.0, 1.0), 3)) MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) @@ -150,6 +150,8 @@ function test_interface_moi() end # Solver Attributes + @test MOI.get(model, MOI.RawSolver()) isa RandomSampler.Optimizer + @test MOI.get(model, RandomSampler.RandomSeed()) === nothing MOI.set(model, RandomSampler.RandomSeed(), 13) @test MOI.get(model, RandomSampler.RandomSeed()) == 13 @@ -177,9 +179,9 @@ function test_interface_moi() MOI.set(model, Attributes.Optimization(), 3) @test MOI.get(model, Attributes.Optimization()) === 3 - @test MOI.get(model, Attributes.Discretize()) === false - MOI.set(model, Attributes.Discretize(), true) @test MOI.get(model, Attributes.Discretize()) === true + MOI.set(model, Attributes.Discretize(), false) + @test MOI.get(model, Attributes.Discretize()) === false @test MOI.get(model, Attributes.Quadratize()) === false MOI.set(model, Attributes.Quadratize(), true) @@ -205,10 +207,10 @@ function test_interface_moi() @test MOI.get(model, Attributes.VariableEncodingMethod(), x[1]) === nothing @test MOI.get(model, Attributes.VariableEncodingMethod(), x[2]) === nothing - MOI.set(model, Attributes.VariableEncodingMethod(), x[1], Encoding.Arithmetic()) + MOI.set(model, Attributes.VariableEncodingMethod(), x[1], Encoding.OneHot()) MOI.set(model, Attributes.VariableEncodingMethod(), x[2], Encoding.Arithmetic()) - @test MOI.get(model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.Arithmetic + @test MOI.get(model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.OneHot @test MOI.get(model, Attributes.VariableEncodingMethod(), x[2]) isa Encoding.Arithmetic # Variable Encoding ATol @@ -233,11 +235,11 @@ function test_interface_moi() @test MOI.get(model, Attributes.VariableEncodingBits(), x[1]) === nothing @test MOI.get(model, Attributes.VariableEncodingBits(), x[2]) === nothing - MOI.set(model, Attributes.VariableEncodingBits(), x[1], 1) - MOI.set(model, Attributes.VariableEncodingBits(), x[2], 2) + MOI.set(model, Attributes.VariableEncodingBits(), x[1], 10) + MOI.set(model, Attributes.VariableEncodingBits(), x[2], 20) - @test MOI.get(model, Attributes.VariableEncodingBits(), x[1]) == 1 - @test MOI.get(model, Attributes.VariableEncodingBits(), x[2]) == 2 + @test MOI.get(model, Attributes.VariableEncodingBits(), x[1]) == 10 + @test MOI.get(model, Attributes.VariableEncodingBits(), x[2]) == 20 # Variable Encoding Penalty @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[1]) === nothing @@ -261,76 +263,119 @@ function test_interface_moi() @test_throws Exception MOI.get(model, Attributes.ConstraintEncodingPenalty(), c[1]) @test_throws Exception MOI.get(model, Attributes.ConstraintEncodingPenalty(), c[2]) - # Call to MOI.optimize! - MOI.optimize!(model) - - let virtual_model = model.model.optimizer - @test MOI.get(virtual_model, Attributes.Architecture()) isa SuperArchitecture - @test MOI.get(virtual_model, Attributes.Architecture()).super === true - - - - @test MOI.get(virtual_model, Attributes.Optimization()) === 3 - - - @test MOI.get(virtual_model, Attributes.Discretize()) === true - - - @test MOI.get(virtual_model, Attributes.Quadratize()) === true - - - @test MOI.get(virtual_model, Attributes.Warnings()) === false - - - @test MOI.get(virtual_model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG - - - @test MOI.get(virtual_model, Attributes.StableQuadratization()) === true - + # Slack Variable Attributes + @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[1]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[2]) === nothing - @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary + MOI.set(model, Attributes.SlackVariableEncodingMethod(), c[1], Encoding.DomainWall()) - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.Arithmetic + @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[1]) isa Encoding.DomainWall - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[2]) isa Encoding.Arithmetic + @test MOI.get(model, Attributes.SlackVariableEncodingATol(), c[1]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingATol(), c[2]) === nothing - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[3]) === nothing + MOI.set(model, Attributes.SlackVariableEncodingATol(), c[1], 1 / 2) + @test MOI.get(model, Attributes.SlackVariableEncodingATol(), c[1]) ≈ 1 / 2 - @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingATol()) ≈ 1E-6 + @test MOI.get(model, Attributes.SlackVariableEncodingBits(), c[1]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingBits(), c[2]) === nothing - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[1]) ≈ 1 / 2 + MOI.set(model, Attributes.SlackVariableEncodingBits(), c[2], 1) - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[2]) ≈ 1 / 3 + @test MOI.get(model, Attributes.SlackVariableEncodingBits(), c[2]) == 1 - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[3]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingPenaltyHint(), c[1]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingPenaltyHint(), c[2]) === nothing + MOI.set(model, Attributes.SlackVariableEncodingPenaltyHint(), c[1], -100.0) - @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingBits()) == 3 + @test MOI.get(model, Attributes.SlackVariableEncodingPenaltyHint(), c[1]) == -100.0 - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[1]) == 1 + @test_throws Exception MOI.get(model, Attributes.SlackVariableEncodingPenalty(), c[1]) + @test_throws Exception MOI.get(model, Attributes.SlackVariableEncodingPenalty(), c[2]) - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[2]) == 2 - - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[3]) === nothing - - - @test MOI.get(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[1]) == -1.0 - - @test MOI.get(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[2]) === nothing - - @test MOI.get(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[3]) === nothing - - - @test MOI.get(virtual_model, Attributes.ConstraintEncodingPenaltyHint(), c[1]) == -10.0 - - @test MOI.get(virtual_model, Attributes.ConstraintEncodingPenaltyHint(), c[2]) === nothing - - - @test MOI.get(virtual_model, Attributes.ConstraintEncodingPenalty(), c[1]) == -10.0 - - @test MOI.get(virtual_model, Attributes.ConstraintEncodingPenalty(), c[2]) == -4.0 + # Call to MOI.optimize! + MOI.optimize!(model) + let virtual_model = model.model.optimizer + # MOI Attributes + @test MOI.get(model, MOI.ResultCount()) > 0 + @test MOI.get(model, MOI.SolveTimeSec()) > 0.0 + @test MOI.get(model, MOI.TerminationStatus()) isa MOI.TerminationStatusCode + @test MOI.get(model, MOI.RawStatusString()) isa String + + # MOI Variable Attributes + @test MOI.get(model, MOI.PrimalStatus()) isa MOI.ResultStatusCode + @test MOI.get(model, MOI.DualStatus()) isa MOI.ResultStatusCode + + @test MOI.get(model, MOI.VariablePrimal(), x[1]) >= 0.0 + @test MOI.get(model, MOI.VariablePrimal(), x[2]) >= 0.0 + @test MOI.get(model, MOI.VariablePrimal(), x[3]) >= 0.0 + + # ToQUBO Attribtues + @test MOI.get(model, Attributes.Optimization()) == 3 + @test Attributes.optimization(virtual_model) == 3 + + @test MOI.get(model, Attributes.Discretize()) === false + @test Attributes.discretize(virtual_model) === false + + @test MOI.get(model, Attributes.Quadratize()) === true + @test Attributes.quadratize(virtual_model) === true + + @test MOI.get(model, Attributes.Warnings()) === false + @test Attributes.warnings(virtual_model) === false + + @test MOI.get(model, Attributes.Architecture()) isa SuperArchitecture + @test MOI.get(model, Attributes.Architecture()).super === true + @test Attributes.architecture(virtual_model) isa SuperArchitecture + @test Attributes.architecture(virtual_model).super === true + + @test MOI.get(model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG + @test MOI.get(model, Attributes.StableQuadratization()) === true + + @test MOI.get(model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary + @test MOI.get(model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.OneHot + @test MOI.get(model, Attributes.VariableEncodingMethod(), x[2]) isa Encoding.Arithmetic + @test MOI.get(model, Attributes.VariableEncodingMethod(), x[3]) === nothing + + @test MOI.get(model, Attributes.DefaultVariableEncodingATol()) ≈ 1E-6 + @test MOI.get(model, Attributes.VariableEncodingATol(), x[1]) ≈ 1 / 2 + @test MOI.get(model, Attributes.VariableEncodingATol(), x[2]) ≈ 1 / 3 + @test MOI.get(model, Attributes.VariableEncodingATol(), x[3]) === nothing + + @test MOI.get(model, Attributes.DefaultVariableEncodingBits()) == 3 + @test MOI.get(model, Attributes.VariableEncodingBits(), x[1]) == 10 + @test MOI.get(model, Attributes.VariableEncodingBits(), x[2]) == 20 + @test MOI.get(model, Attributes.VariableEncodingBits(), x[3]) === nothing + + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[1]) == -1.0 + @test Attributes.variable_encoding_penalty_hint(virtual_model, x[1]) == -1.0 + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[2]) === nothing + @test Attributes.variable_encoding_penalty_hint(virtual_model, x[2]) === nothing + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[3]) === nothing + @test Attributes.variable_encoding_penalty_hint(virtual_model, x[3]) === nothing + + @test MOI.get(model, Attributes.VariableEncodingPenalty(), x[1]) == -1.0 + @test Attributes.variable_encoding_penalty(virtual_model, x[1]) == -1.0 + @test MOI.get(model, Attributes.VariableEncodingPenalty(), x[2]) === nothing + @test Attributes.variable_encoding_penalty(virtual_model, x[2]) === nothing + @test MOI.get(model, Attributes.VariableEncodingPenalty(), x[3]) === nothing + @test Attributes.variable_encoding_penalty(virtual_model, x[3]) === nothing + + @test MOI.get(model, Attributes.ConstraintEncodingPenaltyHint(), c[1]) == -10.0 + @test MOI.get(model, Attributes.ConstraintEncodingPenaltyHint(), c[2]) === nothing + + @test MOI.get(model, Attributes.ConstraintEncodingPenalty(), c[1]) == -10.0 + @test MOI.get(model, Attributes.ConstraintEncodingPenalty(), c[2]) <= 0.0 + + @test MOI.get(model, Attributes.SlackVariableEncodingPenalty(), c[1]) == -100.0 + + @test MOI.get(model, Attributes.CompilationStatus()) === MOI.LOCALLY_SOLVED + @test Attributes.compilation_status(virtual_model) === MOI.LOCALLY_SOLVED + + @test MOI.get(model, Attributes.CompilationTime()) > 0.0 + @test Attributes.compilation_time(virtual_model) > 0.0 end end end @@ -381,7 +426,7 @@ function test_interface_jump() end end end - + @testset "Attributes" begin let # Create Model # max x1 + x2 + x3 @@ -400,16 +445,16 @@ function test_interface_jump() # MOI Attributes @test JuMP.num_variables(model) == 3 - + # @test JuMP.time_limit_sec(model) === nothing # JuMP.set_time_limit_sec(model, 1.0) # @test JuMP.time_limit_sec(model) == 1.0 - + # Solver Attributes @test JuMP.get_attribute(model, RandomSampler.RandomSeed()) === nothing JuMP.set_attribute(model, RandomSampler.RandomSeed(), 13) @test JuMP.get_attribute(model, RandomSampler.RandomSeed()) == 13 - + @test JuMP.get_attribute(model, RandomSampler.NumberOfReads()) == 1_000 JuMP.set_attribute(model, RandomSampler.NumberOfReads(), 13) @test JuMP.get_attribute(model, RandomSampler.NumberOfReads()) == 13 @@ -433,9 +478,9 @@ function test_interface_jump() JuMP.set_attribute(model, Attributes.Optimization(), 3) @test JuMP.get_attribute(model, Attributes.Optimization()) == 3 - @test JuMP.get_attribute(model, Attributes.Discretize()) === false - JuMP.set_attribute(model, Attributes.Discretize(), true) @test JuMP.get_attribute(model, Attributes.Discretize()) === true + JuMP.set_attribute(model, Attributes.Discretize(), false) + @test JuMP.get_attribute(model, Attributes.Discretize()) === false @test JuMP.get_attribute(model, Attributes.Quadratize()) === false JuMP.set_attribute(model, Attributes.Quadratize(), true) @@ -503,9 +548,6 @@ function test_interface_jump() @test JuMP.get_attribute(x[1], Attributes.VariableEncodingPenaltyHint()) == -1.0 - @test_throws Exception JuMP.get_attribute(x[1], Attributes.VariableEncodingPenalty()) - @test_throws Exception JuMP.get_attribute(x[2], Attributes.VariableEncodingPenalty()) - # ToQUBO Constraint Attributes @test JuMP.get_attribute(c[1], Attributes.ConstraintEncodingPenaltyHint()) === nothing @test JuMP.get_attribute(c[2], Attributes.ConstraintEncodingPenaltyHint()) === nothing @@ -514,16 +556,13 @@ function test_interface_jump() @test JuMP.get_attribute(c[1], Attributes.ConstraintEncodingPenaltyHint()) == -10.0 - @test_throws Exception JuMP.get_attribute(c[1], Attributes.ConstraintEncodingPenalty()) - @test_throws Exception JuMP.get_attribute(c[2], Attributes.ConstraintEncodingPenalty()) - JuMP.optimize!(model) @test JuMP.get_attribute(model, Attributes.Architecture()) isa SuperArchitecture @test JuMP.get_attribute(model, Attributes.Architecture()).super === true @test JuMP.get_attribute(model, Attributes.Optimization()) === 3 - @test JuMP.get_attribute(model, Attributes.Discretize()) === true + @test JuMP.get_attribute(model, Attributes.Discretize()) === false @test JuMP.get_attribute(model, Attributes.Quadratize()) === true @test JuMP.get_attribute(model, Attributes.Warnings()) === false diff --git a/test/unit/compiler/compiler.jl b/test/unit/compiler/compiler.jl index 5aec856..79bcf6f 100644 --- a/test/unit/compiler/compiler.jl +++ b/test/unit/compiler/compiler.jl @@ -1,8 +1,10 @@ include("constraints.jl") +include("error.jl") function test_compiler() @testset "□ Compiler" verbose = true begin test_compiler_constraints() + test_compiler_error() end return nothing diff --git a/test/unit/compiler/constraints.jl b/test/unit/compiler/constraints.jl index b27c204..b09dac1 100644 --- a/test/unit/compiler/constraints.jl +++ b/test/unit/compiler/constraints.jl @@ -19,7 +19,8 @@ function test_compiler_constraints_quadratic() 0.0, ) s = MOI.EqualTo{Float64}(b) - g = ToQUBO.Compiler.constraint(model, f, s, arch) + c = MOI.add_constraint(model.source_model, f, s) + g = ToQUBO.Compiler.constraint(model, c, f, s, arch) h = ToQUBO.PBO.PBF{VI,Float64}() diff --git a/test/unit/compiler/error.jl b/test/unit/compiler/error.jl new file mode 100644 index 0000000..1713d2c --- /dev/null +++ b/test/unit/compiler/error.jl @@ -0,0 +1,23 @@ +function test_compiler_error() + @testset "Compilation Error" begin + model = ToQUBO.Optimizer() + + @test_throws( + ToQUBO.Compiler.CompilationError, + ToQUBO.Compiler.compilation_error!( + model, + "Test Message"; + status = "Testing Compilation Error", + ) + ) + + @test MOI.get(model, Attributes.CompilationStatus()) == MOI.OTHER_ERROR + @test MOI.get(model, MOI.RawStatusString()) == "Testing Compilation Error" + + let e = ToQUBO.Compiler.CompilationError("Test Message") + @show sprint(Base.showerror, e) + end + end + + return nothing +end \ No newline at end of file