From b85c797286273b93ca785a18b66dd68b3dacd1d0 Mon Sep 17 00:00:00 2001 From: pedroripper Date: Thu, 16 Nov 2023 20:34:09 -0300 Subject: [PATCH 01/15] Fix greater than constraint encoding [no ci] --- src/compiler/constraints.jl | 127 ++++++++++++++++++ src/compiler/parse.jl | 30 +++++ src/model/prequbo.jl | 3 - src/wrapper.jl | 2 +- .../examples/continuous/continuous.jl | 2 + .../examples/continuous/continuous_2.jl | 61 +++++++++ 6 files changed, 221 insertions(+), 4 deletions(-) create mode 100644 test/integration/examples/continuous/continuous_2.jl diff --git a/src/compiler/constraints.jl b/src/compiler/constraints.jl index 424096a..26f95d2 100644 --- a/src/compiler/constraints.jl +++ b/src/compiler/constraints.jl @@ -160,6 +160,69 @@ 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}, + 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) + + PBO.discretize!(g) + + # 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 + # TODO: Add slack variable encoding method constraint attribute + e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) + S = (zero(T), abs(u)) + z = Encoding.encode!(model, nothing, e, S) + + for (ω, c) in Virtual.expansion(z) + g[ω] -= c + end + + return g^2 +end + + @doc raw""" constraint( model::Virtual.Model{T}, @@ -280,6 +343,70 @@ 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}, + 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) + + PBO.discretize!(g) + + # 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 + e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) + S = (zero(T), abs(u)) + z = Encoding.encode!(model, nothing, e, S) + + 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}, diff --git a/src/compiler/parse.jl b/src/compiler/parse.jl index 4060e4b..5769935 100644 --- a/src/compiler/parse.jl +++ b/src/compiler/parse.jl @@ -79,6 +79,21 @@ 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}, @@ -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/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/wrapper.jl b/src/wrapper.jl index 22c2be6..b26b14d 100644 --- a/src/wrapper.jl +++ b/src/wrapper.jl @@ -138,7 +138,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..a8c8348 --- /dev/null +++ b/test/integration/examples/continuous/continuous_2.jl @@ -0,0 +1,61 @@ +""" + (x11 + x12 + x21 + x22 + x31 + x32 - 4 - s1 - s2)^2 + x11*x11 + x11*x12 + x11*x21 + x11*x22 + x11*x31 + x11*x32 - 4*x11 - s1*x11 - s2*x11 + + x12*x11 + x12*x12 + x12*x21 + x12*x22 + x12*x31 + x12*x32 - 4*x12 - s1*x12 - s2*x12 + + x21*x11 + x21*x12 + x21*x21 + x21*x22 + x21*x31 + x21*x32 - 4*x21 - s1*x21 - s2*x21 + + x22*x11 + x22*x12 + x22*x21 + x22*x22 + x22*x31 + x22*x32 - 4*x22 - s1*x22 - s2*x22 + + x31*x11 + x31*x12 + x31*x21 + x31*x22 + x31*x31 + x31*x32 - 4*x31 - s1*x31 - s2*x31 + + x32*x11 + x32*x12 + x32*x21 + x32*x22 + x32*x31 + x32*x32 - 4*x32 - s1*x32 - s2*x32 + + -4*x11 - 4*x12 - 4*x21 - 4*x22 - 4*x31 - 4*x32 + 16 + 4*s1 + 4*s2 + + -s1*x11 - s1*x12 - s1*x21 - s1*x22 - s1*x31 - s1*x32 + 4*s1 + s1*s1 + s1*s2 + + -s2*x11 - s2*x12 - s2*x21 - s2*x22 - s2*x31 - s2*x32 + 4*s2 + s1*s2 + s2*s2 + +""" +function test_continuous_2() + @testset "Greater than constraint penalty hint" begin + + ρ = 3.0 + + Q = [ + 1-7ρ 2ρ 2ρ 2ρ 2ρ 2ρ -2ρ -2ρ + 0 1-7ρ 2ρ 2ρ 2ρ 2ρ -2ρ -2ρ + 0 0 1-7ρ 2ρ 2ρ 2ρ -2ρ -2ρ + 0 0 0 1-7ρ 2ρ 2ρ -2ρ -2ρ + 0 0 0 0 1-7ρ 2ρ -2ρ -2ρ + 0 0 0 0 0 1-7ρ -2ρ -2ρ + 0 0 0 0 0 0 9ρ 0 + 0 0 0 0 0 0 0 9ρ + ] + + model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) + + @variable(model, 0 <= x[1:3] <= 2, Int) + @constraint(model, c, sum(x) >= 4) + @objective(model, Min, sum(x)) + + + JuMP.set_attribute(c, ToQUBO.Attributes.ConstraintEncodingPenaltyHint(), 3.0) + + + optimize!(model) + + + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) + + @test n == 8 + @test α ≈ ᾱ + @test β ≈ β̄ + @test Q̂ ≈ Q̄ + + # Solutions + x̂ = value.(x) + ŷ = objective_value(model) + + @test x̂ ≈ x̄ + @test ŷ ≈ ȳ + + end + return +end \ No newline at end of file From 503d48db7003b803d3ee1960ae3c3f16fe6a7923 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Mon, 20 Nov 2023 05:18:03 -0500 Subject: [PATCH 02/15] Add slack variable encoding attributes --- src/attributes/compiler.jl | 301 +++++++++++++++++++++++++++++++++++-- 1 file changed, 286 insertions(+), 15 deletions(-) diff --git a/src/attributes/compiler.jl b/src/attributes/compiler.jl index 5a714e7..f8c5255 100644 --- a/src/attributes/compiler.jl +++ b/src/attributes/compiler.jl @@ -161,11 +161,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 +244,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 +503,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 +583,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 +602,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 +657,279 @@ 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) + return MOI.get(model, SlackVariableEncodingMethod(), ci) +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) + return MOI.get(model, SlackVariableEncodingPenalty(), ci) +end + +function MOI.get( + model::Optimizer{T}, + ::SlackVariableEncodingPenalty, + ci::CI, +)::Union{T,Nothing} where {T} + return get(model.ρ, ci, nothing) +end + +function MOI.set( + model::Optimizer{T}, + ::SlackVariableEncodingPenalty, + ci::CI, + ρ::T, +)::Nothing where {T} + model.ρ[ci] = ρ + + return nothing +end + +function MOI.set( + model::Optimizer, + ::SlackVariableEncodingPenalty, + ci::CI, + ::Nothing, +) + delete!(model.ρ, ci) + + return nothing +end + end # module Attributes From 4c6b5311825eea4ff3d7c4c62b40f505f7c56076 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Mon, 20 Nov 2023 08:04:33 -0500 Subject: [PATCH 03/15] Fix tests --- .../examples/continuous/continuous_2.jl | 112 +++++++++++++----- 1 file changed, 81 insertions(+), 31 deletions(-) diff --git a/test/integration/examples/continuous/continuous_2.jl b/test/integration/examples/continuous/continuous_2.jl index a8c8348..896982f 100644 --- a/test/integration/examples/continuous/continuous_2.jl +++ b/test/integration/examples/continuous/continuous_2.jl @@ -1,50 +1,100 @@ """ - (x11 + x12 + x21 + x22 + x31 + x32 - 4 - s1 - s2)^2 - x11*x11 + x11*x12 + x11*x21 + x11*x22 + x11*x31 + x11*x32 - 4*x11 - s1*x11 - s2*x11 + - x12*x11 + x12*x12 + x12*x21 + x12*x22 + x12*x31 + x12*x32 - 4*x12 - s1*x12 - s2*x12 + - x21*x11 + x21*x12 + x21*x21 + x21*x22 + x21*x31 + x21*x32 - 4*x21 - s1*x21 - s2*x21 + - x22*x11 + x22*x12 + x22*x21 + x22*x22 + x22*x31 + x22*x32 - 4*x22 - s1*x22 - s2*x22 + - x31*x11 + x31*x12 + x31*x21 + x31*x22 + x31*x31 + x31*x32 - 4*x31 - s1*x31 - s2*x31 + - x32*x11 + x32*x12 + x32*x21 + x32*x22 + x32*x31 + x32*x32 - 4*x32 - s1*x32 - s2*x32 + - -4*x11 - 4*x12 - 4*x21 - 4*x22 - 4*x31 - 4*x32 + 16 + 4*s1 + 4*s2 + - -s1*x11 - s1*x12 - s1*x21 - s1*x22 - s1*x31 - s1*x32 + 4*s1 + s1*s1 + s1*s2 + - -s2*x11 - s2*x12 - s2*x21 - s2*x22 - s2*x31 - s2*x32 + 4*s2 + s1*s2 + s2*s2 + +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 - - Q = [ - 1-7ρ 2ρ 2ρ 2ρ 2ρ 2ρ -2ρ -2ρ - 0 1-7ρ 2ρ 2ρ 2ρ 2ρ -2ρ -2ρ - 0 0 1-7ρ 2ρ 2ρ 2ρ -2ρ -2ρ - 0 0 0 1-7ρ 2ρ 2ρ -2ρ -2ρ - 0 0 0 0 1-7ρ 2ρ -2ρ -2ρ - 0 0 0 0 0 1-7ρ -2ρ -2ρ - 0 0 0 0 0 0 9ρ 0 - 0 0 0 0 0 0 0 9ρ + @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(x)) + @objective(model, Min, sum(i * x[i] for i = 1:3)) + set_attribute(c, ToQUBO.Attributes.ConstraintEncodingPenaltyHint(), ρ̄) - JuMP.set_attribute(c, ToQUBO.Attributes.ConstraintEncodingPenaltyHint(), 3.0) - - 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̄ @@ -55,7 +105,7 @@ function test_continuous_2() @test x̂ ≈ x̄ @test ŷ ≈ ȳ - end - return -end \ No newline at end of file + + return nothing +end From 99544df3d7d66eb99fe493ef2732f2aa5a379c5a Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Mon, 20 Nov 2023 15:36:20 -0500 Subject: [PATCH 04/15] Update variable encoding [no ci] --- src/attributes/compiler.jl | 8 ++-- src/attributes/solver.jl | 38 +++++++++++++++--- src/virtual/encoding.jl | 50 +++++++++++++++++++++++ src/virtual/model.jl | 61 +++++++++++++++------------- test/integration/interface.jl | 76 +++++++++++++++++------------------ 5 files changed, 155 insertions(+), 78 deletions(-) diff --git a/src/attributes/compiler.jl b/src/attributes/compiler.jl index f8c5255..10cc2f2 100644 --- a/src/attributes/compiler.jl +++ b/src/attributes/compiler.jl @@ -907,16 +907,16 @@ function MOI.get( ::SlackVariableEncodingPenalty, ci::CI, )::Union{T,Nothing} where {T} - return get(model.ρ, ci, nothing) + return get(model.η, ci, nothing) end function MOI.set( model::Optimizer{T}, ::SlackVariableEncodingPenalty, ci::CI, - ρ::T, + η::T, )::Nothing where {T} - model.ρ[ci] = ρ + model.η[ci] = η return nothing end @@ -927,7 +927,7 @@ function MOI.set( ci::CI, ::Nothing, ) - delete!(model.ρ, ci) + delete!(model.η, ci) return nothing end diff --git a/src/attributes/solver.jl b/src/attributes/solver.jl index 214d348..cf247b8 100644 --- a/src/attributes/solver.jl +++ b/src/attributes/solver.jl @@ -57,9 +57,6 @@ function MOI.get( model::Virtual.Model, attr::Union{ MOI.SolveTimeSec, - MOI.PrimalStatus, - MOI.DualStatus, - MOI.TerminationStatus, MOI.RawStatusString, }, ) @@ -74,9 +71,6 @@ function MOI.supports( model::Virtual.Model, attr::Union{ MOI.SolveTimeSec, - MOI.PrimalStatus, - MOI.DualStatus, - MOI.TerminationStatus, MOI.RawStatusString, }, ) @@ -87,6 +81,38 @@ function MOI.supports( end end +function MOI.get(model::Virtual.Model, attr::MOI.TerminationStatus) + if !isnothing(model.optimizer) + return MOI.get(model.optimizer, attr) + else + return get(model.compiler_settings, :compilation_status, MOI.OTHER_LIMIT) + end +end + +function MOI.supports(model::Virtual.Model, attr::MOI.TerminationStatus) + if !isnothing(model.optimizer) + return MOI.supports(model.optimizer, attr) + else + return true + end +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/virtual/encoding.jl b/src/virtual/encoding.jl index 16d5199..d4738c3 100644 --- a/src/virtual/encoding.jl +++ b/src/virtual/encoding.jl @@ -30,6 +30,14 @@ function Encoding.encode!(model::Model{T}, x::Union{VI,Nothing}, e::VariableEnco return Encoding.encode!(model, v) end +function Encoding.encode!(model::Model{T}, c::CI, e::VariableEncodingMethod) where {T} + v = Encoding.encode!(model, nothing, e) + + model.slack[c] = v + + return v +end + function Encoding.encode!( model::Model{T}, x::Union{VI,Nothing}, @@ -49,6 +57,19 @@ function Encoding.encode!( return Encoding.encode!(model, v) end +function Encoding.encode!( + model::Model{T}, + c::CI, + e::VariableEncodingMethod, + γ::AbstractVector{T}, +) where {T} + v = Encoding.encode!(model, nothing, e, γ) + + model.slack[c] = v + + return v +end + function encode!( model::Model{T}, x::Union{VI,Nothing}, @@ -69,6 +90,21 @@ function encode!( return Encoding.encode!(model, v) end +function encode!( + model::Model{T}, + c::CI, + e::VariableEncodingMethod, + S::Tuple{T,T}; + tol::Union{T,Nothing} = nothing, +) where {T} + v = Encoding.encode!(model, nothing, e, S; tol) + + model.slack[c] = v + + return v +end + + function Encoding.encode!( model::Model{T}, x::Union{VI,Nothing}, @@ -88,3 +124,17 @@ function Encoding.encode!( return Encoding.encode!(model, v) end + +function Encoding.encode!( + model::Model{T}, + c::CI, + e::VariableEncodingMethod, + S::Tuple{T,T}, + n::Integer, +) where {T} + v = Encoding.encode!(model, nothing, e, S, n) + + model.slack[c] = v + + return v +end diff --git a/src/virtual/model.jl b/src/virtual/model.jl index 0b4717b..77fea96 100644 --- a/src/virtual/model.jl +++ b/src/virtual/model.jl @@ -3,26 +3,27 @@ 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 H::PBO.PBF{VI,T} # Final Objective Function # Settings @@ -30,43 +31,45 @@ mutable struct Model{T} <: MOI.AbstractOptimizer variable_settings::Dict{Symbol,Dict{VI,Any}} constraint_settings::Dict{Symbol,Dict{CI,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 PBO.PBF{VI,T}(), # Final Objective Function # Settings diff --git a/test/integration/interface.jl b/test/integration/interface.jl index ad8a2cd..63f9035 100644 --- a/test/integration/interface.jl +++ b/test/integration/interface.jl @@ -261,76 +261,80 @@ 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) + # Slack Variable Attributes + @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[1]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[2]) === nothing - let virtual_model = model.model.optimizer - @test MOI.get(virtual_model, Attributes.Architecture()) isa SuperArchitecture - @test MOI.get(virtual_model, Attributes.Architecture()).super === true + MOI.set(model, Attributes.SlackVariableEncodingMethod(), c[1], Encoding.Arithmetic()) + @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[1]) 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.Optimization()) === 3 + 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.Discretize()) === true + @test MOI.get(model, Attributes.SlackVariableEncodingBits(), c[1]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingBits(), c[2]) === nothing + MOI.set(model, Attributes.SlackVariableEncodingBits(), c[2], 1) - @test MOI.get(virtual_model, Attributes.Quadratize()) === true + @test MOI.get(model, Attributes.SlackVariableEncodingBits(), c[2]) == 1 + @test MOI.get(model, Attributes.SlackVariableEncodingPenaltyHint(), c[1]) === nothing + @test MOI.get(model, Attributes.SlackVariableEncodingPenaltyHint(), c[2]) === nothing - @test MOI.get(virtual_model, Attributes.Warnings()) === false + MOI.set(model, Attributes.SlackVariableEncodingPenaltyHint(), c[1], -100.0) + @test MOI.get(model, Attributes.SlackVariableEncodingPenaltyHint(), c[1]) == -100.0 - @test MOI.get(virtual_model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG + @test_throws Exception MOI.get(model, Attributes.SlackVariableEncodingPenalty(), c[1]) + @test_throws Exception MOI.get(model, Attributes.SlackVariableEncodingPenalty(), 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.StableQuadratization()) === 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 @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.Arithmetic - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[2]) isa Encoding.Arithmetic - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[3]) === nothing - @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingATol()) ≈ 1E-6 - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[1]) ≈ 1 / 2 - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[2]) ≈ 1 / 3 - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[3]) === nothing - @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingBits()) == 3 - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[1]) == 1 - @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 + @test MOI.get(model, Attributes.SlackVariableEncodingPenalty(), c[1]) == -100.0 end end end @@ -381,7 +385,7 @@ function test_interface_jump() end end end - + @testset "Attributes" begin let # Create Model # max x1 + x2 + x3 @@ -400,16 +404,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 @@ -503,9 +507,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,9 +515,6 @@ 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 From 27e6597e14dc54b047fc58b471a4129feb519f86 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Tue, 21 Nov 2023 16:08:15 -0500 Subject: [PATCH 05/15] Fix slack variable tracking --- src/attributes/compiler.jl | 28 ++++++-- src/compiler/compiler.jl | 17 ++--- src/compiler/constraints.jl | 107 +++++++++++++++++++----------- src/compiler/penalties.jl | 17 ++++- src/virtual/encoding.jl | 66 +++--------------- src/virtual/model.jl | 4 ++ src/virtual/variable.jl | 4 +- test/integration/interface.jl | 4 +- test/unit/compiler/constraints.jl | 3 +- 9 files changed, 136 insertions(+), 114 deletions(-) diff --git a/src/attributes/compiler.jl b/src/attributes/compiler.jl index 10cc2f2..6ccc02f 100644 --- a/src/attributes/compiler.jl +++ b/src/attributes/compiler.jl @@ -664,8 +664,14 @@ Sets the encoding method for slack variables generated by constraints. """ struct SlackVariableEncodingMethod <: CompilerConstraintAttribute end -function slack_variable_encoding_method(model::Optimizer, ci::CI) - return MOI.get(model, SlackVariableEncodingMethod(), ci) +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( @@ -899,6 +905,8 @@ 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 @@ -907,6 +915,10 @@ function MOI.get( ::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 @@ -914,19 +926,23 @@ function MOI.set( model::Optimizer{T}, ::SlackVariableEncodingPenalty, ci::CI, - η::T, + η::Any, )::Nothing where {T} - model.η[ci] = η + # 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, + model::Optimizer{T}, ::SlackVariableEncodingPenalty, ci::CI, ::Nothing, -) +)::Nothing where {T} delete!(model.η, ci) return nothing diff --git a/src/compiler/compiler.jl b/src/compiler/compiler.jl index dfe147a..a1bfaa4 100644 --- a/src/compiler/compiler.jl +++ b/src/compiler/compiler.jl @@ -15,14 +15,15 @@ import ..Virtual: Virtual, encoding, expansion, penaltyfn import ..Attributes # 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("analysis.jl") include("interface.jl") diff --git a/src/compiler/constraints.jl b/src/compiler/constraints.jl index 26f95d2..9dc1998 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, @@ -125,6 +128,7 @@ by adding a slack variable ``z``. """ function constraint( model::Virtual.Model{T}, + ci::CI, f::SAF{T}, s::LT{T}, arch::AbstractArchitecture, @@ -148,10 +152,9 @@ function constraint( end # Slack Variable - x = nothing - e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) + e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(l)) - z = Encoding.encode!(model, x, e, S) + z = Encoding.encode!(model, ci, e, S) for (ω, c) in Virtual.expansion(z) g[ω] += c @@ -187,6 +190,7 @@ by adding a slack variable ``z``. """ function constraint( model::Virtual.Model{T}, + ci::CI, f::SAF{T}, s::GT{T}, arch::AbstractArchitecture, @@ -210,10 +214,9 @@ function constraint( end # Slack Variable - # TODO: Add slack variable encoding method constraint attribute - e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) + e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(u)) - z = Encoding.encode!(model, nothing, e, S) + z = Encoding.encode!(model, ci, e, S) for (ω, c) in Virtual.expansion(z) g[ω] -= c @@ -249,6 +252,7 @@ into """ function constraint( model::Virtual.Model{T}, + ::CI, f::SQF{T}, s::EQ{T}, arch::AbstractArchitecture, @@ -268,7 +272,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 @@ -305,6 +312,7 @@ by adding a slack variable ``z``. """ function constraint( model::Virtual.Model{T}, + ci::CI, f::SQF{T}, s::LT{T}, arch::AbstractArchitecture, @@ -324,14 +332,16 @@ 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()) + e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(l)) - z = Encoding.encode!(model, x, e, S) + z = Encoding.encode!(model, ci, e, S) for (ω, c) in Virtual.expansion(z) g[ω] += c @@ -370,6 +380,7 @@ by adding a slack variable ``z``. """ function constraint( model::Virtual.Model{T}, + ci::CI, f::SQF{T}, s::GT{T}, arch::AbstractArchitecture, @@ -393,9 +404,9 @@ function constraint( end # Slack Variable - e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) + e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(u)) - z = Encoding.encode!(model, nothing, e, S) + z = Encoding.encode!(model, ci, e, S) for (ω, c) in Virtual.expansion(z) g[ω] -= c @@ -417,29 +428,52 @@ 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] + + ω, c = argmin(p -> abs(last(p) + a), ξ) - for (ωi, _) in Virtual.expansion(vi) - g[ωi] = one(T) + if !((c + a) ≈ zero(T)) + @warn "Variable $(xi) is always non-zero" + end + + g[ω] = one(T) + else + ξ = Virtual.expansion(vi) + + # Slack variable + e = Encoding.Mirror{T}() + w = Encoding.encode!(model, ci, e) + χ = w * ξ^2 + + for (ω, c) in χ + h[ω] += c + end + + g[w] = one(T) end end # Slack variable - x = nothing e = Encoding.Mirror{T}() - z = Encoding.encode!(model, x, e) + z = Encoding.encode!(model, ci, e) for (ω, c) in Virtual.expansion(z) g[ω] += c @@ -447,21 +481,20 @@ function constraint( g[nothing] += -one(T) - return g^2 + return g^2 + h end function encoding_constraints!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} for v in model.variables - x = Virtual.source(v) - - if isnothing(x) - continue - end - + i = Virtual.source(v) χ = Virtual.penaltyfn(v) if !isnothing(χ) - model.h[x] = χ + if i isa VI + model.h[i] = χ + elseif i isa CI + model.s[i] = χ + end end end diff --git a/src/compiler/penalties.jl b/src/compiler/penalties.jl index 2209df4..acb6431 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,7 +10,7 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} if isnothing(ρ) ϵ = PBO.mingap(g) - ρ = s * (δ / ϵ + β) + ρ = σ * (δ / ϵ + β) end model.ρ[ci] = ρ @@ -21,11 +21,22 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} if isnothing(θ) ϵ = PBO.mingap(h) - θ = s * (δ / ϵ + β) + θ = σ * (δ / ϵ + β) end model.θ[vi] = θ end + for (ci, s) in model.s + η = Attributes.slack_variable_encoding_penalty_hint(model, ci) + + if isnothing(η) + ϵ = PBO.mingap(s) + η = σ * (δ / ϵ + β) + end + + model.η[ci] = η + end + return nothing end diff --git a/src/virtual/encoding.jl b/src/virtual/encoding.jl index d4738c3..89073c4 100644 --- a/src/virtual/encoding.jl +++ b/src/virtual/encoding.jl @@ -1,8 +1,10 @@ function Encoding.encode!(model::Model{T}, v::Variable{T}) where {T} x = source(v) - if !isnothing(x) + if x isa VI model.source[x] = v + elseif x isa CI + model.slack[x] = v end for y in target(v) @@ -16,7 +18,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) @@ -30,17 +36,9 @@ function Encoding.encode!(model::Model{T}, x::Union{VI,Nothing}, e::VariableEnco return Encoding.encode!(model, v) end -function Encoding.encode!(model::Model{T}, c::CI, e::VariableEncodingMethod) where {T} - v = Encoding.encode!(model, nothing, e) - - model.slack[c] = v - - return v -end - function Encoding.encode!( model::Model{T}, - x::Union{VI,Nothing}, + x::Union{VI,CI,Nothing}, e::VariableEncodingMethod, γ::AbstractVector{T}, ) where {T} @@ -57,22 +55,9 @@ function Encoding.encode!( return Encoding.encode!(model, v) end -function Encoding.encode!( - model::Model{T}, - c::CI, - e::VariableEncodingMethod, - γ::AbstractVector{T}, -) where {T} - v = Encoding.encode!(model, nothing, e, γ) - - model.slack[c] = v - - return v -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, @@ -90,24 +75,9 @@ function encode!( return Encoding.encode!(model, v) end -function encode!( - model::Model{T}, - c::CI, - e::VariableEncodingMethod, - S::Tuple{T,T}; - tol::Union{T,Nothing} = nothing, -) where {T} - v = Encoding.encode!(model, nothing, e, S; tol) - - model.slack[c] = v - - return v -end - - function Encoding.encode!( model::Model{T}, - x::Union{VI,Nothing}, + x::Union{VI,CI,Nothing}, e::VariableEncodingMethod, S::Tuple{T,T}, n::Integer, @@ -124,17 +94,3 @@ function Encoding.encode!( return Encoding.encode!(model, v) end - -function Encoding.encode!( - model::Model{T}, - c::CI, - e::VariableEncodingMethod, - S::Tuple{T,T}, - n::Integer, -) where {T} - v = Encoding.encode!(model, nothing, e, S, n) - - model.slack[c] = v - - return v -end diff --git a/src/virtual/model.jl b/src/virtual/model.jl index 77fea96..1217a05 100644 --- a/src/virtual/model.jl +++ b/src/virtual/model.jl @@ -24,6 +24,8 @@ mutable struct Model{T,O} <: MOI.AbstractOptimizer ρ::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 @@ -70,6 +72,8 @@ mutable struct Model{T,O} <: MOI.AbstractOptimizer 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 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/test/integration/interface.jl b/test/integration/interface.jl index 63f9035..b030811 100644 --- a/test/integration/interface.jl +++ b/test/integration/interface.jl @@ -265,9 +265,9 @@ function test_interface_moi() @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[1]) === nothing @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[2]) === nothing - MOI.set(model, Attributes.SlackVariableEncodingMethod(), c[1], Encoding.Arithmetic()) + MOI.set(model, Attributes.SlackVariableEncodingMethod(), c[1], Encoding.DomainWall()) - @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[1]) isa Encoding.Arithmetic + @test MOI.get(model, Attributes.SlackVariableEncodingMethod(), c[1]) isa Encoding.DomainWall @test MOI.get(model, Attributes.SlackVariableEncodingATol(), c[1]) === nothing @test MOI.get(model, Attributes.SlackVariableEncodingATol(), c[2]) === 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}() From 71ec1e668378feb28acf4fce9028211612364cb4 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Tue, 21 Nov 2023 16:08:36 -0500 Subject: [PATCH 06/15] Add additional typeless dispatch for constructors --- src/encoding/encoding.jl | 2 +- src/encoding/variables/{interval => }/mirror.jl | 0 src/encoding/variables/set/domain_wall.jl | 2 ++ src/encoding/variables/set/one_hot.jl | 2 ++ 4 files changed, 5 insertions(+), 1 deletion(-) rename src/encoding/variables/{interval => }/mirror.jl (100%) 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/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..0a847b9 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(γ) From 69978b6352fc740ce9a275fc5812bb6ef5a58edd Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Wed, 22 Nov 2023 15:28:28 -0500 Subject: [PATCH 07/15] Fix Attribute Access --- src/ToQUBO.jl | 3 --- src/attributes/compiler.jl | 54 +++++++++++++++++++++++++++++++++++++ src/attributes/solver.jl | 43 +++++++++++++++++++---------- src/compiler/build.jl | 11 +++++--- src/compiler/compiler.jl | 30 ++++++++++----------- src/{ => compiler}/error.jl | 15 ++++++++++- src/compiler/parse.jl | 14 +++++----- src/compiler/variables.jl | 2 +- src/virtual/model.jl | 2 ++ src/wrapper.jl | 8 +++++- 10 files changed, 136 insertions(+), 46 deletions(-) rename src/{ => compiler}/error.jl (62%) 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 6ccc02f..e080571 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() """ diff --git a/src/attributes/solver.jl b/src/attributes/solver.jl index cf247b8..0b459dd 100644 --- a/src/attributes/solver.jl +++ b/src/attributes/solver.jl @@ -55,10 +55,7 @@ end function MOI.get( model::Virtual.Model, - attr::Union{ - MOI.SolveTimeSec, - MOI.RawStatusString, - }, + attr::MOI.SolveTimeSec, ) if !isnothing(model.optimizer) return MOI.get(model.optimizer, attr) @@ -69,10 +66,7 @@ end function MOI.supports( model::Virtual.Model, - attr::Union{ - MOI.SolveTimeSec, - MOI.RawStatusString, - }, + attr::MOI.SolveTimeSec, ) if !isnothing(model.optimizer) return MOI.supports(model.optimizer, attr) @@ -81,22 +75,43 @@ function MOI.supports( end end -function MOI.get(model::Virtual.Model, attr::MOI.TerminationStatus) - if !isnothing(model.optimizer) +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.compiler_settings, :compilation_status, MOI.OTHER_LIMIT) + return get(model.moi_settings, :raw_status_string, "") end end -function MOI.supports(model::Virtual.Model, attr::MOI.TerminationStatus) +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.supports(model.optimizer, attr) + return MOI.get(model.optimizer, attr) else - return true + 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) diff --git a/src/compiler/build.jl b/src/compiler/build.jl index dbcdc2a..0bd1ef8 100644 --- a/src/compiler/build.jl +++ b/src/compiler/build.jl @@ -12,7 +12,7 @@ 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 = @@ -115,8 +115,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 a1bfaa4..8876305 100644 --- a/src/compiler/compiler.jl +++ b/src/compiler/compiler.jl @@ -1,18 +1,15 @@ 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 @@ -25,6 +22,7 @@ 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") @@ -82,16 +80,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/error.jl b/src/compiler/error.jl similarity index 62% rename from src/error.jl rename to src/compiler/error.jl index f72319a..499337f 100644 --- a/src/error.jl +++ b/src/compiler/error.jl @@ -21,4 +21,17 @@ end function compilation_error(msg::Union{Nothing,String} = nothing) throw(QUBOCompilationError(msg)) -end \ No newline at end of file + + 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 5769935..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 @@ -100,7 +100,7 @@ function parse!( f::SQF{T}, ::AbstractArchitecture, ) where {T} - empty!(g) + Base.empty!(g) sizehint!(g, length(f.quadratic_terms) + length(f.affine_terms) + 1) @@ -118,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 @@ -130,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 diff --git a/src/compiler/variables.jl b/src/compiler/variables.jl index 06235b3..62dd80d 100644 --- a/src/compiler/variables.jl +++ b/src/compiler/variables.jl @@ -90,7 +90,7 @@ function variables!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} end function variable_𝔹!(model::Virtual.Model{T}, x::VI) where {T} - Encoding.encode!(model, x, Mirror{T}()) + Encoding.encode!(model, x, Encoding.Mirror{T}()) return nothing end diff --git a/src/virtual/model.jl b/src/virtual/model.jl index 1217a05..1748004 100644 --- a/src/virtual/model.jl +++ b/src/virtual/model.jl @@ -32,6 +32,7 @@ mutable struct Model{T,O} <: MOI.AbstractOptimizer 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::Any; kws...) where {T} optimizer = constructor()::MOI.AbstractOptimizer @@ -80,6 +81,7 @@ mutable struct Model{T,O} <: MOI.AbstractOptimizer Dict{Symbol,Any}(), Dict{Symbol,Dict{VI,Any}}(), Dict{Symbol,Dict{CI,Any}}(), + Dict{Symbol,Any}(), ) end end diff --git a/src/wrapper.jl b/src/wrapper.jl index b26b14d..dd50e2e 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.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, Attributes.CompilationStatus(), MOI.LOCALLY_SOLVED) + MOI.set(model, MOI.RawStatusString(), "Compilation complete without an internal solver") end return (index_map, false) From 7929d58c83586db548be8ff108903c57ac3e1687 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Wed, 22 Nov 2023 15:38:23 -0500 Subject: [PATCH 08/15] Include varaible penalty terms in the hamiltonian --- src/compiler/build.jl | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/compiler/build.jl b/src/compiler/build.jl index 0bd1ef8..37e70db 100644 --- a/src/compiler/build.jl +++ b/src/compiler/build.jl @@ -15,8 +15,12 @@ function objective_function(model::Virtual.Model{T}, ::AbstractArchitecture) whe 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) @@ -40,6 +44,14 @@ function objective_function(model::Virtual.Model{T}, ::AbstractArchitecture) whe end end + for (ci, s) in model.s + η = model.η[ci] + + for (ω, c) in s + model.H[ω] += η * c + end + end + return nothing end From af533a0f904b6a784d2dcb2d0a9dd2025562c450 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Thu, 23 Nov 2023 14:20:44 -0500 Subject: [PATCH 09/15] Add tests --- src/compiler/build.jl | 6 +-- src/compiler/compiler.jl | 3 -- src/compiler/constraints.jl | 20 ++------- src/compiler/penalties.jl | 6 +-- src/encoding/variables/set/one_hot.jl | 6 ++- src/virtual/encoding.jl | 9 ++++ src/wrapper.jl | 2 +- test/integration/interface.jl | 60 +++++++++++++++++++-------- 8 files changed, 66 insertions(+), 46 deletions(-) diff --git a/src/compiler/build.jl b/src/compiler/build.jl index 37e70db..e860ce8 100644 --- a/src/compiler/build.jl +++ b/src/compiler/build.jl @@ -29,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 @@ -37,7 +37,7 @@ 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 @@ -45,7 +45,7 @@ function objective_function(model::Virtual.Model{T}, ::AbstractArchitecture) whe end for (ci, s) in model.s - η = model.η[ci] + η = MOI.get(model, Attributes.SlackVariableEncodingPenalty(), ci) for (ω, c) in s model.H[ω] += η * c diff --git a/src/compiler/compiler.jl b/src/compiler/compiler.jl index 8876305..f51548d 100644 --- a/src/compiler/compiler.jl +++ b/src/compiler/compiler.jl @@ -63,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) diff --git a/src/compiler/constraints.jl b/src/compiler/constraints.jl index 9dc1998..0caf19f 100644 --- a/src/compiler/constraints.jl +++ b/src/compiler/constraints.jl @@ -468,6 +468,9 @@ function constraint( end g[w] = one(T) + + # Tell the compiler that quadratization is necessary + MOI.set(model, Attributes.Quadratize(), true) end end @@ -483,20 +486,3 @@ function constraint( return g^2 + h end - -function encoding_constraints!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} - for v in model.variables - i = Virtual.source(v) - χ = Virtual.penaltyfn(v) - - if !isnothing(χ) - if i isa VI - model.h[i] = χ - elseif i isa CI - model.s[i] = χ - end - end - end - - return nothing -end diff --git a/src/compiler/penalties.jl b/src/compiler/penalties.jl index acb6431..4ff65c2 100644 --- a/src/compiler/penalties.jl +++ b/src/compiler/penalties.jl @@ -13,7 +13,7 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} ρ = σ * (δ / ϵ + β) end - model.ρ[ci] = ρ + MOI.set(model, Attributes.ConstraintEncodingPenalty(), ci, ρ) end for (vi, h) in model.h @@ -24,7 +24,7 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} θ = σ * (δ / ϵ + β) end - model.θ[vi] = θ + MOI.set(model, Attributes.VariableEncodingPenalty(), vi, θ) end for (ci, s) in model.s @@ -35,7 +35,7 @@ function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} η = σ * (δ / ϵ + β) end - model.η[ci] = η + MOI.set(model, Attributes.SlackVariableEncodingPenalty(), ci, η) end return nothing diff --git a/src/encoding/variables/set/one_hot.jl b/src/encoding/variables/set/one_hot.jl index 0a847b9..3bcde3b 100644 --- a/src/encoding/variables/set/one_hot.jl +++ b/src/encoding/variables/set/one_hot.jl @@ -99,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/virtual/encoding.jl b/src/virtual/encoding.jl index 89073c4..fb58101 100644 --- a/src/virtual/encoding.jl +++ b/src/virtual/encoding.jl @@ -1,10 +1,19 @@ function Encoding.encode!(model::Model{T}, v::Variable{T}) where {T} x = source(v) + χ = penaltyfn(v) 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) diff --git a/src/wrapper.jl b/src/wrapper.jl index dd50e2e..2109be9 100644 --- a/src/wrapper.jl +++ b/src/wrapper.jl @@ -29,6 +29,7 @@ function MOI.optimize!(model::Optimizer) # De facto JuMP to QUBO Compilation let t = @elapsed ToQUBO.Compiler.compile!(model) + MOI.set(model, Attributes.CompilationStatus(), MOI.LOCALLY_SOLVED) MOI.set(model, Attributes.CompilationTime(), t) end @@ -36,7 +37,6 @@ function MOI.optimize!(model::Optimizer) MOI.optimize!(model.optimizer, model.target_model) MOI.set(model, MOI.RawStatusString(), MOI.get(model.optimizer, MOI.RawStatusString())) else - MOI.set(model, Attributes.CompilationStatus(), MOI.LOCALLY_SOLVED) MOI.set(model, MOI.RawStatusString(), "Compilation complete without an internal solver") end diff --git a/test/integration/interface.jl b/test/integration/interface.jl index b030811..506815c 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) @@ -205,10 +205,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 +233,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 @@ -297,20 +297,28 @@ function test_interface_moi() 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.Optimization()) == 3 + @test Attributes.optimization(virtual_model) == 3 @test MOI.get(virtual_model, Attributes.Discretize()) === true + @test Attributes.discretize(virtual_model) === true + @test MOI.get(virtual_model, Attributes.Quadratize()) === true + @test Attributes.quadratize(virtual_model) === true + @test MOI.get(virtual_model, Attributes.Warnings()) === false + @test Attributes.warnings(virtual_model) === false + + @test MOI.get(virtual_model, Attributes.Architecture()) isa SuperArchitecture + @test MOI.get(virtual_model, Attributes.Architecture()).super === true + @test Attributes.architecture(virtual_model) isa SuperArchitecture + @test Attributes.architecture(virtual_model).super === true @test MOI.get(virtual_model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG @test MOI.get(virtual_model, Attributes.StableQuadratization()) === true @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.Arithmetic + @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.OneHot @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[2]) isa Encoding.Arithmetic @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[3]) === nothing @@ -320,21 +328,37 @@ function test_interface_moi() @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[3]) === nothing @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingBits()) == 3 - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[1]) == 1 - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[2]) == 2 + @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[1]) == 10 + @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[2]) == 20 @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[3]) === nothing @test MOI.get(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[1]) == -1.0 + @test Attributes.variable_encoding_penalty_hint(virtual_model, x[1]) == -1.0 @test MOI.get(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[2]) === nothing + @test Attributes.variable_encoding_penalty_hint(virtual_model, x[2]) === nothing @test MOI.get(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[3]) === nothing + @test Attributes.variable_encoding_penalty_hint(virtual_model, x[3]) === nothing + + @test MOI.get(virtual_model, Attributes.VariableEncodingPenalty(), x[1]) == -1.0 + @test Attributes.variable_encoding_penalty(virtual_model, x[1]) == -1.0 + @test MOI.get(virtual_model, Attributes.VariableEncodingPenalty(), x[2]) === nothing + @test Attributes.variable_encoding_penalty(virtual_model, x[2]) === nothing + @test MOI.get(virtual_model, Attributes.VariableEncodingPenalty(), x[3]) === nothing + @test Attributes.variable_encoding_penalty(virtual_model, 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 + @test MOI.get(virtual_model, Attributes.ConstraintEncodingPenalty(), c[2]) <= 0.0 @test MOI.get(model, Attributes.SlackVariableEncodingPenalty(), c[1]) == -100.0 + + @test MOI.get(virtual_model, Attributes.CompilationStatus()) === MOI.LOCALLY_SOLVED + @test Attributes.compilation_status(virtual_model) === MOI.LOCALLY_SOLVED + + @test MOI.get(virtual_model, Attributes.CompilationTime()) > 0.0 + @test Attributes.compilation_time(virtual_model) > 0.0 end end end From e089bbcd3874a3f95ddcb4d511db2604d4e8b92f Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Fri, 24 Nov 2023 13:32:46 -0500 Subject: [PATCH 10/15] Add tests --- test/integration/interface.jl | 70 +++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/test/integration/interface.jl b/test/integration/interface.jl index 506815c..f3f24e6 100644 --- a/test/integration/interface.jl +++ b/test/integration/interface.jl @@ -297,67 +297,73 @@ function test_interface_moi() MOI.optimize!(model) let virtual_model = model.model.optimizer - @test MOI.get(virtual_model, Attributes.Optimization()) == 3 + # MOI Attributes + @test MOI.get(model, MOI.ResultCount()) > 0 + @test MOI.get(model, MOI.SolveTime()) > 0.0 + @test MOI.get(model, MOI.TerminationStatus()) isa MOI.TerminationStatusCode + + # ToQUBO Attribtues + @test MOI.get(model, Attributes.Optimization()) == 3 @test Attributes.optimization(virtual_model) == 3 - @test MOI.get(virtual_model, Attributes.Discretize()) === true + @test MOI.get(model, Attributes.Discretize()) === true @test Attributes.discretize(virtual_model) === true - @test MOI.get(virtual_model, Attributes.Quadratize()) === true + @test MOI.get(model, Attributes.Quadratize()) === true @test Attributes.quadratize(virtual_model) === true - @test MOI.get(virtual_model, Attributes.Warnings()) === false + @test MOI.get(model, Attributes.Warnings()) === false @test Attributes.warnings(virtual_model) === false - @test MOI.get(virtual_model, Attributes.Architecture()) isa SuperArchitecture - @test MOI.get(virtual_model, Attributes.Architecture()).super === true + @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(virtual_model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG - @test MOI.get(virtual_model, Attributes.StableQuadratization()) === true + @test MOI.get(model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG + @test MOI.get(model, Attributes.StableQuadratization()) === true - @test MOI.get(virtual_model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.OneHot - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[2]) isa Encoding.Arithmetic - @test MOI.get(virtual_model, Attributes.VariableEncodingMethod(), x[3]) === nothing + @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(virtual_model, Attributes.DefaultVariableEncodingATol()) ≈ 1E-6 - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[1]) ≈ 1 / 2 - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), x[2]) ≈ 1 / 3 - @test MOI.get(virtual_model, Attributes.VariableEncodingATol(), 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(virtual_model, Attributes.DefaultVariableEncodingBits()) == 3 - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[1]) == 10 - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), x[2]) == 20 - @test MOI.get(virtual_model, Attributes.VariableEncodingBits(), 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(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[1]) == -1.0 + @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(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[2]) === nothing + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[2]) === nothing @test Attributes.variable_encoding_penalty_hint(virtual_model, x[2]) === nothing - @test MOI.get(virtual_model, Attributes.VariableEncodingPenaltyHint(), x[3]) === nothing + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[3]) === nothing @test Attributes.variable_encoding_penalty_hint(virtual_model, x[3]) === nothing - @test MOI.get(virtual_model, Attributes.VariableEncodingPenalty(), x[1]) == -1.0 + @test MOI.get(model, Attributes.VariableEncodingPenalty(), x[1]) == -1.0 @test Attributes.variable_encoding_penalty(virtual_model, x[1]) == -1.0 - @test MOI.get(virtual_model, Attributes.VariableEncodingPenalty(), x[2]) === nothing + @test MOI.get(model, Attributes.VariableEncodingPenalty(), x[2]) === nothing @test Attributes.variable_encoding_penalty(virtual_model, x[2]) === nothing - @test MOI.get(virtual_model, Attributes.VariableEncodingPenalty(), x[3]) === nothing + @test MOI.get(model, Attributes.VariableEncodingPenalty(), x[3]) === nothing @test Attributes.variable_encoding_penalty(virtual_model, 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(model, Attributes.ConstraintEncodingPenaltyHint(), c[1]) == -10.0 + @test MOI.get(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]) <= 0.0 + @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(virtual_model, Attributes.CompilationStatus()) === MOI.LOCALLY_SOLVED + @test MOI.get(model, Attributes.CompilationStatus()) === MOI.LOCALLY_SOLVED @test Attributes.compilation_status(virtual_model) === MOI.LOCALLY_SOLVED - @test MOI.get(virtual_model, Attributes.CompilationTime()) > 0.0 + @test MOI.get(model, Attributes.CompilationTime()) > 0.0 @test Attributes.compilation_time(virtual_model) > 0.0 end end From e4e75a3a0df752dbc015e2ddbf94b0e72400fb45 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Fri, 24 Nov 2023 15:52:11 -0500 Subject: [PATCH 11/15] Fix attribute access --- test/integration/interface.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/integration/interface.jl b/test/integration/interface.jl index f3f24e6..cd1f919 100644 --- a/test/integration/interface.jl +++ b/test/integration/interface.jl @@ -299,8 +299,16 @@ function test_interface_moi() let virtual_model = model.model.optimizer # MOI Attributes @test MOI.get(model, MOI.ResultCount()) > 0 - @test MOI.get(model, MOI.SolveTime()) > 0.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 0.0 <= MOI.get(model, MOI.VariablePrimal(), x[1]) <= 1.0 + @test 0.0 <= MOI.get(model, MOI.VariablePrimal(), x[2]) <= 1.0 + @test 0.0 <= MOI.get(model, MOI.VariablePrimal(), x[3]) <= 1.0 # ToQUBO Attribtues @test MOI.get(model, Attributes.Optimization()) == 3 From cef016c12304c3a437243c0eeaf16114eb6678d8 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Fri, 24 Nov 2023 16:33:22 -0500 Subject: [PATCH 12/15] Add error tests --- test/unit/compiler/compiler.jl | 2 ++ test/unit/compiler/error.jl | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) create mode 100644 test/unit/compiler/error.jl 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/error.jl b/test/unit/compiler/error.jl new file mode 100644 index 0000000..68e2943 --- /dev/null +++ b/test/unit/compiler/error.jl @@ -0,0 +1,19 @@ +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" + end + + return nothing +end \ No newline at end of file From dda3f4790ed7af92c45eec99dbd572048fc8741a Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Fri, 24 Nov 2023 16:33:34 -0500 Subject: [PATCH 13/15] Rename Exception --- src/compiler/error.jl | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/compiler/error.jl b/src/compiler/error.jl index 499337f..0799640 100644 --- a/src/compiler/error.jl +++ b/src/compiler/error.jl @@ -1,17 +1,17 @@ """ - QUBOCompilationError(msg::Union{Nothing, String}) + CompilationError(msg::Union{Nothing, String}) This error indicates any failure during QUBO formulation """ -struct QUBOCompilationError <: Exception +struct CompilationError <: Exception msg::Union{String,Nothing} - function QUBOCompilationError(msg::Union{Nothing,String} = nothing) + function CompilationError(msg::Union{Nothing,String} = nothing) return new(msg) end end -function Base.showerror(io::IO, e::QUBOCompilationError) +function Base.showerror(io::IO, e::CompilationError) if isnothing(e.msg) print(io, "The current model can't be converted to QUBO") else @@ -20,7 +20,7 @@ function Base.showerror(io::IO, e::QUBOCompilationError) end function compilation_error(msg::Union{Nothing,String} = nothing) - throw(QUBOCompilationError(msg)) + throw(CompilationError(msg)) return nothing end From 34f2178e15a4198695e97ec74fd91e3a5006b8eb Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Sat, 25 Nov 2023 16:36:09 -0500 Subject: [PATCH 14/15] Bump Version --- Project.toml | 20 +++--- src/attributes/compiler.jl | 2 +- src/compiler/constraints.jl | 55 ++++++++++----- src/compiler/variables.jl | 70 +++++++++++++------ src/encoding/variables/interval/binary.jl | 9 ++- .../examples/quadratic/quadratic.jl | 2 + .../examples/quadratic/quadratic_2.jl | 34 +++++++++ test/integration/interface.jl | 21 +++--- 8 files changed, 154 insertions(+), 59 deletions(-) create mode 100644 test/integration/examples/quadratic/quadratic_2.jl 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/attributes/compiler.jl b/src/attributes/compiler.jl index e080571..b1775c2 100644 --- a/src/attributes/compiler.jl +++ b/src/attributes/compiler.jl @@ -162,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) diff --git a/src/compiler/constraints.jl b/src/compiler/constraints.jl index 0caf19f..a01e1e0 100644 --- a/src/compiler/constraints.jl +++ b/src/compiler/constraints.jl @@ -83,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) @@ -136,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) @@ -152,9 +156,12 @@ function constraint( end # Slack Variable - e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(l)) - z = Encoding.encode!(model, ci, 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 @@ -198,7 +205,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) @@ -214,9 +223,12 @@ function constraint( end # Slack Variable - e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(u)) - z = Encoding.encode!(model, ci, 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 @@ -260,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) @@ -320,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) @@ -339,9 +355,12 @@ function constraint( end # Slack Variable - e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(l)) - z = Encoding.encode!(model, ci, 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 @@ -388,7 +407,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) @@ -404,9 +425,12 @@ function constraint( end # Slack Variable - e = Attributes.slack_variable_encoding_method(model, ci) S = (zero(T), abs(u)) - z = Encoding.encode!(model, ci, 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 @@ -475,8 +499,7 @@ function constraint( end # Slack variable - e = Encoding.Mirror{T}() - z = Encoding.encode!(model, ci, e) + z = variable_𝔹!(model, ci) for (ω, c) in Virtual.expansion(z) g[ω] += c diff --git a/src/compiler/variables.jl b/src/compiler/variables.jl index 62dd80d..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, Encoding.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/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/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 cd1f919..dc34c15 100644 --- a/test/integration/interface.jl +++ b/test/integration/interface.jl @@ -177,9 +177,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) @@ -306,16 +306,17 @@ function test_interface_moi() # MOI Variable Attributes @test MOI.get(model, MOI.PrimalStatus()) isa MOI.ResultStatusCode @test MOI.get(model, MOI.DualStatus()) isa MOI.ResultStatusCode - @test 0.0 <= MOI.get(model, MOI.VariablePrimal(), x[1]) <= 1.0 - @test 0.0 <= MOI.get(model, MOI.VariablePrimal(), x[2]) <= 1.0 - @test 0.0 <= MOI.get(model, MOI.VariablePrimal(), x[3]) <= 1.0 + + @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()) === true - @test Attributes.discretize(virtual_model) === true + @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 @@ -475,9 +476,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) @@ -559,7 +560,7 @@ function test_interface_jump() @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 From a10e4c27799005c4f96469c4ecb0fc236ee2b88f Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Sun, 26 Nov 2023 02:16:21 -0500 Subject: [PATCH 15/15] Add tests --- test/integration/interface.jl | 2 ++ test/unit/compiler/error.jl | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/test/integration/interface.jl b/test/integration/interface.jl index dc34c15..aad9f3d 100644 --- a/test/integration/interface.jl +++ b/test/integration/interface.jl @@ -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 diff --git a/test/unit/compiler/error.jl b/test/unit/compiler/error.jl index 68e2943..1713d2c 100644 --- a/test/unit/compiler/error.jl +++ b/test/unit/compiler/error.jl @@ -13,6 +13,10 @@ function test_compiler_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