From 34f2178e15a4198695e97ec74fd91e3a5006b8eb Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Sat, 25 Nov 2023 16:36:09 -0500 Subject: [PATCH] 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