diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 3fe05a41..b14fff12 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v2 - uses: julia-actions/setup-julia@latest with: - version: '1.6' + version: '1.9' - name: Develop ToQUBO.jl run: julia --project=docs -e 'using Pkg; Pkg.develop(path=pwd())' - uses: julia-actions/julia-buildpkg@latest diff --git a/Project.toml b/Project.toml index 02523ed3..b66eeeee 100644 --- a/Project.toml +++ b/Project.toml @@ -1,15 +1,16 @@ name = "ToQUBO" uuid = "9a412ddf-83fa-43b6-9748-7843c851aa65" authors = ["pedromxavier ", "pedroripper ", "joaquimg ", "AndradeTiago ", "bernalde "] -version = "0.1.6" +version = "0.1.7" [deps] 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" [compat] MathOptInterface = "1" -QUBOTools = "0.8.0" -julia = "1.6" +QUBOTools = "0.9" +julia = "1.9" diff --git a/docs/Project.toml b/docs/Project.toml index 8b1a1e9f..b597d71c 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,21 +1,19 @@ [deps] CSV = "336ed68f-0bac-5ca0-87d4-7b16caf5d00b" -DWaveNeal = "870cdf72-5502-4b10-839c-127ceab78f22" +DWave = "4d534982-bf11-4157-9e48-fe3a62208a50" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e" Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" -PythonPlot = "274fc56d-3b97-40fa-a1cd-1b4a50311bf9" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" ToQUBO = "9a412ddf-83fa-43b6-9748-7843c851aa65" [compat] CSV = "0.10" -DWaveNeal = "0.4" DataFrames = "1.3" -Documenter = "0.27" +Documenter = "1" JuMP = "1" MathOptInterface = "1" -ToQUBO = "0.1" diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 17717d51..00000000 --- a/docs/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# ToQUBO Documentation - -## Building -```shell -$ julia --project .\make.jl -``` \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index 101429f1..3ea2461c 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -2,46 +2,43 @@ using Documenter using ToQUBO # Set up to run docstrings with jldoctest -DocMeta.setdocmeta!( - ToQUBO, :DocTestSetup, :(using ToQUBO); recursive=true -) +DocMeta.setdocmeta!(ToQUBO, :DocTestSetup, :(using ToQUBO); recursive = true) makedocs(; - modules = [ToQUBO], - doctest = true, - clean = true, - format = Documenter.HTML( + modules = [ToQUBO], + doctest = true, + clean = true, + warnonly = [:missing_docs, :cross_references], + format = Documenter.HTML( # sidebar_sitename = false, mathengine = Documenter.KaTeX( Dict( - :macros => Dict( - raw"\set" => raw"\left\lbrace{#1}\right\rbrace" - ) + :macros => Dict(raw"\set" => raw"\left\lbrace{#1}\right\rbrace") ) ), - assets = [ + assets = [ # "assets/extra_styles.css", "assets/favicon.ico", - asset("https://tikzjax.com/v1/fonts.css"; class = :css), - asset("https://tikzjax.com/v1/tikzjax.js"; class = :js), - ], - ), + # asset("https://tikzjax.com/v1/fonts.css"; class = :css), + # asset("https://tikzjax.com/v1/tikzjax.js"; class = :js), + ] + ), sitename = "ToQUBO.jl", authors = "Pedro Maciel Xavier and Pedro Ripper and Tiago Andrade and Joaquim Dias Garcia and David E. Bernal Neira", - pages = [ - "Home" => "index.md", - "Manual" => [ + pages = [ # + "Home" => "index.md", + "Manual" => [ # "Getting Started" => "manual/1-start.md", "Running a Model" => "manual/2-model.md", "Gathering Results" => "manual/3-results.md", - "Compiler Settings" => "manual/4-settings.md", + "Compiler Settings" => "manual/4-settings.md" ], - "Examples" => [ + "Examples" => [ # "Knapsack" => "examples/knapsack.md", - "Prime Factorization" => "examples/prime_factorization.md", + "Integer Factorization" => "examples/integer_factorization.md", "Portfolio Optimization" => "examples/portfolio_optimization.md", ], - "Booklet" => [ + "Booklet" => [ # "Introduction" => "booklet/1-intro.md", "QUBO" => "booklet/2-qubo.md", "PBO" => "booklet/3-pbo.md", @@ -50,16 +47,13 @@ makedocs(; "The Compiler" => "booklet/6-compiler.md", "Solvers" => "booklet/7-solvers.md", "Appendix" => "booklet/8-appendix.md", - ], + ] ], - workdir="." + workdir = @__DIR__, ) if "--skip-deploy" ∈ ARGS @warn "Skipping deployment" else - deploydocs( - repo=raw"github.com/psrenergy/ToQUBO.jl.git", - push_preview = true - ) -end \ No newline at end of file + deploydocs(repo = raw"github.com/psrenergy/ToQUBO.jl.git", push_preview = true) +end diff --git a/docs/src/assets/fonts/Sunflower-LICENSE.md b/docs/src/assets/fonts/Sunflower-LICENSE.md new file mode 100644 index 00000000..153ffb01 --- /dev/null +++ b/docs/src/assets/fonts/Sunflower-LICENSE.md @@ -0,0 +1,26 @@ +# Sunflower Font License + +```@raw txt +MadeType +behance.com/madetype + +https://www.fontspring.com/fonts/madetype +https://creativemarket.com/MadeType +https://www.youworkforthem.com/designer/1002/madetype/ +https://crella.net/store/madetype/ +https://thehungryjpeg.com/madetype + + +END USER LICENSE AGREEMENT + +- FOR PERSONAL USE. + +- Contact me at Madetypeinfo@gmail.com before commercial using it. + +- MadeType is not liable for any damage resulting from the use ot this typeface. + +- All rights are retained by MadeType. + + +THANK YOU! +``` diff --git a/docs/src/assets/init.js b/docs/src/assets/init.js deleted file mode 100644 index f3ead049..00000000 --- a/docs/src/assets/init.js +++ /dev/null @@ -1,24 +0,0 @@ -/* Ref: -** https://discourse.julialang.org/t/use-javascript-library-with-documenter-jl/34984/8 -*/ -require.config({ - paths: { - mermaid: "https://cdnjs.cloudflare.com/ajax/libs/mermaid/9.4.0/mermaid.min" - } -}); - -require(['mermaid'], function (mermaid) { - mermaid.initialize({ - startOnLoad : true, - theme : 'neutral', - // themeVariables: { - // primaryColor: '#BB2528', - // primaryTextColor: '#fff', - // primaryBorderColor: '#7C0000', - // lineColor: '#F8B229', - // secondaryColor: '#006100', - // tertiaryColor: '#aaa', - // noteBorderColor: '#ff0', - // } - }) -}); \ No newline at end of file diff --git a/docs/src/booklet/1-intro.md b/docs/src/booklet/1-intro.md index 4f15124b..c76ad1f0 100644 --- a/docs/src/booklet/1-intro.md +++ b/docs/src/booklet/1-intro.md @@ -5,7 +5,8 @@ The target audience includes, among others, advanced users and those willing to The latter are advised to read the following sections, as they give a glimpse of the ideas employed up to now. ## Table of Contents + ```@contents Pages = ["2-qubo.md", "3-pbo.md", "4-encoding.md", "5-virtual.md", "6-compiler.md", "7-solvers.md", "8-appendix.md"] Depth = 2 -``` \ No newline at end of file +``` diff --git a/docs/src/booklet/2-qubo.md b/docs/src/booklet/2-qubo.md index 6bec1f28..c332f7dd 100644 --- a/docs/src/booklet/2-qubo.md +++ b/docs/src/booklet/2-qubo.md @@ -15,6 +15,7 @@ where ``Q \in \mathbb{R}^{n \times n}`` is symmetric and ``\mathbb{B} = \lbrace{ Note that, since ``x^{2} = x`` holds for ``x \in \mathbb{B}``, the linear terms of the objective function are stored in the main diagonal of ``Q``. ## OK, but why QUBO? + Mathematically speaking, there is a notorious equivalence between QUBO and [Max-Cut](https://en.wikipedia.org/wiki/Maximum_cut) problems, e.g. for every QUBO instance there is an information preserving Max-Cut reformulation and vice versa. This statement is followed by two immediate implications: diff --git a/docs/src/booklet/3-pbo.md b/docs/src/booklet/3-pbo.md index d7376962..a564e515 100644 --- a/docs/src/booklet/3-pbo.md +++ b/docs/src/booklet/3-pbo.md @@ -1,39 +1,25 @@ # Pseudo-Boolean Optimization + Internally, problems are represented through a Pseudo-Boolean Optimization (PBO) framework. The main goal is to represent a given problem using a Pseudo-Boolean Function (PBF) since there is an immediate correspondence between optimization over quadratic PBFs and the QUBO formalism. -```@docs -ToQUBO.PBO.PseudoBooleanFunction -``` - ## Quadratization + In order to successfully achieve a QUBO formulation, sometimes it is needed to quadratize the resulting PBF, i.e., reduce its degree until reaching the quadratic case. A quadratization is a mapping ``\mathcal{Q}: \mathscr{F} \to \mathscr{F}^{2}`` such that -```math +```math \forall f \in \mathscr{F}, \forall x \in \{0, 1\}^{n}, \min_{y} \mathcal{Q}\left\lbrace{}f\right\rbrace{}(x; y) = f(x) - ``` There are many quadratization methods available[^Dattani2019], and `ToQUBO` implements two of them for now. However, using Julia's multiple dispatch paradigm, it's possible to extend the quadratization method coverage with your own algorithms. -```@docs -ToQUBO.PBO.quadratize! -``` - [^Dattani2019]: Nikesh S. Dattani, **Quadratization in discrete optimization and quantum mechanics**, *ArXiv*, 2019 [{doi}](https://doi.org/10.48550/arXiv.1901.04405) -### Implemented Quadratization Techniques - -Currently, `ToQUBO` has two reduction algorithms, one for negative and another for positive terms. - -```@docs -ToQUBO.PBO.NTR_KZFD -ToQUBO.PBO.PTR_BG -``` +### Quadratization Methods ### Stable Quadratization @@ -41,14 +27,3 @@ The quadratization of a PBF does not guarantee that the resulting function will With said that, we have introduced the concept of Stable Quadratization, where the terms of the PBF are sorted, guaranteeing that the resulting PBF will be the same every time. We have defined it as an attribute of the compiler, with the [`ToQUBO.Attributes.StableQuadratization`](@ref) flag. - - - -### A Primer on Submodularity -A set function ``f : 2^{S} \to \mathbb{R}`` is said to be submodular if - -```math -f(X \cup Y) + f(X \cap Y) \le f(X) + f(Y) \forall X, Y \subset S -``` - -holds. diff --git a/docs/src/booklet/4-encoding.md b/docs/src/booklet/4-encoding.md index b46d64a8..66fc512f 100644 --- a/docs/src/booklet/4-encoding.md +++ b/docs/src/booklet/4-encoding.md @@ -1,9 +1,15 @@ # Encoding Methods +```@docs +ToQUBO.Encoding.encode +ToQUBO.Encoding.encode! +ToQUBO.Encoding.encodes +``` + ## Variables As you may already know, QUBO models are comprised only of binary variables. -So when we are reformulating general optimization problems, one important step is to encode variables into binary ones. +So when we are reformulating general optimization problems, one important step is to encode variables into binary ones. `ToQUBO` currently implements 6 encoding techniques. Each method introduces a different number of variables, quadratic terms and linear terms. @@ -18,29 +24,43 @@ Also, they differ in the magnitude of their coefficients ``\Delta``. | Bounded-Coefficient | ``O(n)`` | ``O(n)`` | - | ``O(1)`` | | Arithmetic Prog | ``O(\sqrt{n})`` | ``O(\sqrt{n})`` | - | ``O(\sqrt{n})`` | -### Linear Encoding +### Mirror Encoding + +```@docs +ToQUBO.Encoding.VariableEncodingMethod +ToQUBO.Encoding.Mirror +``` + +### Interval Encoding + ```@docs -ToQUBO.Unary -ToQUBO.Binary -ToQUBO.Arithmetic -ToQUBO.OneHot +ToQUBO.Encoding.IntervalVariableEncodingMethod +ToQUBO.Encoding.Unary +ToQUBO.Encoding.Binary +ToQUBO.Encoding.Arithmetic ``` +#### Bounded Coefficients + ```@docs -ToQUBO.Mirror +ToQUBO.Encoding.Bounded ``` -### Sequential Encoding +### Arbitrary Set Encoding + ```@docs -ToQUBO.DomainWall +ToQUBO.Encoding.SetVariableEncodingMethod +ToQUBO.Encoding.OneHot +ToQUBO.Encoding.DomainWall ``` -### Bounded Coefficients +### Representation Error + ```@docs -ToQUBO.Bounded +ToQUBO.Encoding.encoding_bits +ToQUBO.Encoding.encoding_points ``` -### Encoding Error Let ``\set{x_{i}}_{i \in [k]}`` be the collection of ``k`` evenly spaced samples from the discretization of an interval ``[a, b] \subseteq \mathbb{R}``. The representation error for a given point ``x`` with respect to ``\set{x_{i}}_{i \in [k]}`` is @@ -73,11 +93,4 @@ A QUBO model is unconstrained. So when `ToQUBO` is reformulating a problem, it n As constraints are introduced into the objective function, we need to make sure that they won't be violated. In order to do that, `ToQUBO` multiplies the encoded constraint by a large penalty ``\rho``, so that any violation would result in a sub-optimal solution to the problem. -Sometimes, the encoding process might introduce higher-order terms, demanding `ToQUBO` to reduce the offending polynomials back to a quadratic form. - -As of today, `ToQUBO` provides encoding for the following constraints: - -```@docs -ToQUBO.toqubo_constraint -``` - +Sometimes, the encoding process might introduce higher-order terms, demanding `ToQUBO` to reduce the offending polynomials back to a quadratic form. diff --git a/docs/src/booklet/5-virtual.md b/docs/src/booklet/5-virtual.md index 6ef0b68f..ebb97832 100644 --- a/docs/src/booklet/5-virtual.md +++ b/docs/src/booklet/5-virtual.md @@ -1,29 +1,34 @@ # Virtual Mapping + During reformulation, `ToQUBO` holds two distinct models, namely the *Source Model* and the *Target Model*. The source model is a generic `MOI` model restricted to the supported constraints. The target one is on the QUBO form used during the solving process. Both lie within a *Virtual Model*, which provides the necessary API integration and keeps all variable and constraint mapping tied together. -```@raw html - -``` - This is done in a transparent fashion for both agents since the user will mostly interact with the presented model, and the solvers will only access the generated one. ## Virtual Model + +```@docs +ToQUBO.Virtual.Model +``` + ```@docs -ToQUBO.VirtualModel +ToQUBO.Virtual.source +ToQUBO.Virtual.target ``` ## Virtual Variables + Every virtual model stores a collection of virtual variables, intended to provide a link between those in the source and those to be created in the target model. Each virtual variable stores encoding information for later expansion and evaluation. ```@docs -ToQUBO.VirtualVariable -ToQUBO.encode! -``` \ No newline at end of file +ToQUBO.Virtual.Variable +``` + +```@docs +ToQUBO.Virtual.encoding +ToQUBO.Virtual.expansion +ToQUBO.Virtual.penaltyfn +``` diff --git a/docs/src/booklet/6-compiler.md b/docs/src/booklet/6-compiler.md index 2828fd72..29fb85a7 100644 --- a/docs/src/booklet/6-compiler.md +++ b/docs/src/booklet/6-compiler.md @@ -6,13 +6,42 @@ ## Compilation Steps +### Setup + +```@docs +ToQUBO.Compiler.reset! +ToQUBO.Compiler.setup! +``` + +### Parsing + +```@docs +ToQUBO.Compiler.parse! +ToQUBO.Compiler._parse +``` + +### Reformulation + +```@docs +ToQUBO.Compiler.sense! +ToQUBO.Compiler.variable! +ToQUBO.Compiler.variables! +ToQUBO.Compiler.constraint +ToQUBO.Compiler.constraints! +ToQUBO.Compiler.objective! +ToQUBO.Compiler.penalties! +``` + +#### Copying + +```@docs +ToQUBO.Compiler.isqubo +ToQUBO.Compiler.copy! +``` + +### Hamiltonian Assembly + ```@docs -ToQUBO.toqubo_sense! -ToQUBO.toqubo_variables! -ToQUBO.toqubo_constraint -ToQUBO.toqubo_constraints! -ToQUBO.toqubo_objective! -ToQUBO.toqubo_penalties! -ToQUBO.toqubo_parse! -ToQUBO.toqubo_build! -``` \ No newline at end of file +ToQUBO.Compiler.build! +ToQUBO.Compiler.quadratize! +``` diff --git a/docs/src/booklet/7-solvers.md b/docs/src/booklet/7-solvers.md index 2f504a24..bda45e32 100644 --- a/docs/src/booklet/7-solvers.md +++ b/docs/src/booklet/7-solvers.md @@ -1,29 +1,35 @@ # QUBO Solvers ## Solvers, Annealers & Samplers + [`ToQUBO.jl`](https://github.com/psrenergy/ToQUBO.jl)'s main goal is to make use of parameterized stochastic optimization solvers, particularly those relying on non-conventional hardware such as *Quantum Annealing* and other *Ising Machines*. A few `MOI`-compliant interfaces for annealers and samplers are bundled within [`ToQUBO.jl`](https://github.com/psrenergy/ToQUBO.jl) via the [`QUBODrivers.jl`](https://github.com/psrenergy/QUBODrivers.jl) companion package. Some of them are presented below. ## Simulated Annealing + Provided by D-Wave's open-source code libraries, this [Simulated Annealing](https://en.wikipedia.org/wiki/Simulated_annealing) engine implements some of the features and configurations you would find using the Quantum API. Its adoption is recommended for basic usage, tests, and research due to its robustness, simplicity and ease of use. -The [`DWaveNeal.jl`](https://github.com/psrenergy/DWaveNeal.jl) package uses [`QUBODrivers.jl`](https://github.com/psrenergy/QUBODrivers.jl) to deliver an interface to this sampler. +The [`DWave.jl`](https://github.com/psrenergy/DWave.jl)'s `DWave.Neal` module uses [`QUBODrivers.jl`](https://github.com/psrenergy/QUBODrivers.jl) to deliver an interface to this sampler. ## Quantum Annealing + Interfacing with [D-Wave](https://www.dwavesys.com/)'s quantum annealer is one of the milestones we expect to achieve with this package. Like other proprietary optimization resources such as [Gurobi](https://gurobi.com), [FICO® Xpress](https://www.fico.com/en/products/fico-xpress-solver) and [IBM® CPLEX®](https://www.ibm.com/products/ilog-cplex-optimization-studio/cplex-optimizer), this requires licensing and extra steps are needed to get an access token. -In a first moment, for those willing to get started, the [`DWaveNeal.jl`](https://github.com/psrenergy/DWaveNeal.jl) optimizer might be enough to learn the ropes. +In a first moment, for those willing to get started, the `DWave.Neal` optimizer might be enough to learn the ropes. ## Random Sampling + This sampler is implemented for test purposes and simply assigns 0 or 1 to each variable according to a given probability bias ``0 \le p \le 1``, which defaults to ``p = 0.5``. After running the `using QUBODrivers` command, `RandomSampler.Optimizer` will be available. ## Exact Solver (Exhaustive Enumeration) + Also made to be used in tests, the `ExactSolver.Optimizer` interface runs through all possible state configurations, which implies in an exponential time complexity on the number of variables. Thus, only problems with at most ``\approxeq 20`` variables should be provided since visiting ``2^{20} \approxeq 10^{6}`` states can already take up to a few seconds. ## Mixed-Integer Quadratic Programming + The most accessible alternative to the forementioned methods are Mixed-Integer Quadratic Programming (MIQP) solvers such as [Gurobi](https://github.com/jump-dev/Gurobi.jl), [CPLEX](https://github.com/jump-dev/CPLEX.jl), [SCIP](https://github.com/scipopt/SCIP.jl) and [BARON](https://github.com/jump-dev/BARON.jl). These are not intended to be of regular use alongside [`ToQUBO.jl`](https://github.com/psrenergy/ToQUBO.jl) since providing a QUBO reformulation will usually make things harder for non-specialized solvers. Yet, there are still a few cases where they may be suitable, such as tests, benchmarks, or any other situation where global optimality is a must. diff --git a/docs/src/booklet/8-appendix.md b/docs/src/booklet/8-appendix.md index c1d5707a..435b3ae9 100644 --- a/docs/src/booklet/8-appendix.md +++ b/docs/src/booklet/8-appendix.md @@ -7,7 +7,7 @@ The ideia behind [ToQUBO.jl](https://github.com/psrenergy/ToQUBO.jl)'s logo comes from a wordplay in Portuguese and Spanish. The package's main purpose is to assemble QUBO Models, which sounds like *cubo*[^1], the translation for *cube*. -[![ToQUBO.jl Logo](../assets/logo.svg)]() +![ToQUBO.jl Logo](../assets/logo.svg) ### Colors @@ -26,10 +26,11 @@ It was converted to a SVG path using the *Google Font to Svg Path*[^4] online to [github.com/JuliaLang/julia-logo-graphics](https://github.com/JuliaLang/julia-logo-graphics/) [^3]: - [Licensed](../../assets/fonts/Sunflower-LICENSE.txt) by the authors for use in this project + [Licensed](../assets/fonts/Sunflower-LICENSE.txt) by the authors for use in this project [^4]: [danmarshall.github.io/google-font-to-svg-path](https://danmarshall.github.io/google-font-to-svg-path/) -### Web Icon [![ToQUBO.jl Icon](../../assets/favicon.ico)]() -The icon used to decorate the documentation resembles an assembled version of the cube with its blue face making up the background. \ No newline at end of file +### Web Icon ![ToQUBO.jl Icon](../assets/favicon.ico) + +The icon used to decorate the documentation resembles an assembled version of the cube with its blue face making up the background. diff --git a/docs/src/examples/prime_factorization.md b/docs/src/examples/integer_factorization.md similarity index 94% rename from docs/src/examples/prime_factorization.md rename to docs/src/examples/integer_factorization.md index 670cfc84..a8a0bf0b 100644 --- a/docs/src/examples/prime_factorization.md +++ b/docs/src/examples/integer_factorization.md @@ -7,6 +7,7 @@ often regarded as one of the major theoretical landmarks in Quantum Computing, b driving increasingly greater interest to the area. A naïve approach to model this problem can be stated as a quadratically-constrained integer program: + ```math \begin{array}{rl} \text{s.t.} & p \times q = R \\ @@ -16,19 +17,20 @@ A naïve approach to model this problem can be stated as a quadratically-constra ``` From the definition and the basics of number theory, we are able to retrieve a few assumptions about the problem's variables: + - ``p`` and ``q`` are bounded to the interval ``\left[1, R\right]`` - Moreover, it is fine to assume ``1 < p \le \sqrt{R} \le q \le R \div 2``. ```@example prime-factorization using JuMP using ToQUBO -using DWaveNeal +using DWave -function factor(R::Integer; optimizer = DWaveNeal.Optimizer) +function factor(R::Integer; optimizer = DWave.Neal.Optimizer) return factor(identity, R; optimizer) end -function factor(config!::Function, R::Integer; optimizer = DWaveNeal.Optimizer) +function factor(config!::Function, R::Integer; optimizer = DWave.Neal.Optimizer) model = Model(() -> ToQUBO.Optimizer(optimizer)) @variable(model, 1 <= p <= √R, Int) @@ -54,4 +56,4 @@ p, q = factor(15) do model end print("$p, $q") -``` \ No newline at end of file +``` diff --git a/docs/src/examples/knapsack.md b/docs/src/examples/knapsack.md index 3a40ecd1..6b77c7d0 100644 --- a/docs/src/examples/knapsack.md +++ b/docs/src/examples/knapsack.md @@ -1,5 +1,7 @@ -## Knapsack +# Knapsack + We start with some instances of the discrete [Knapsack Problem](https://en.wikipedia.org/wiki/Knapsack_problem) whose standard formulation is + ```math \begin{array}{r l} \max & \mathbf{c}\, \mathbf{x} \\ @@ -8,61 +10,60 @@ We start with some instances of the discrete [Knapsack Problem](https://en.wikip \end{array} ``` -### MathOptInterface -Using [MOI](https://github.com/jump-dev/MathOptInterface.jl) directly to build a simple model is pretty straightforward. All that one has to do is to use `MOI.instantiate` and define the model as usual. +First, consider the following items -```@example moi-knapsack -import MathOptInterface as MOI -const MOIU = MOI.Utilities +| Item (``i``) | Value (``c_{i}``) | Weight (``w_{i}``) | +|:------------:|:-----------------:|:------------------:| +| 1 | 1 | 0.3 | +| 2 | 2 | 0.5 | +| 3 | 3 | 1.0 | -using ToQUBO -using DWaveNeal # <- Your favourite Annealer / Sampler / Solver here +to be carried in a knapsack with capacity ``C = 1.6``. -# Example from https://jump.dev/MathOptInterface.jl/stable/tutorials/example/ +Writing down the data above as a linear program, we have -# Virtual QUBO Model -model = MOI.instantiate( - () -> ToQUBO.Optimizer(DWaveNeal.Optimizer), - with_bridge_type = Float64, -) +```math +\begin{array}{r l} + \max & x_{1} + 2 x_{2} + 3 x_{3} \\ + \text{s.t.} & 0.3 x_{1} + 0.5 x_{2} + x_{3} \le 1.6 \\ + ~ & \mathbf{x} \in \mathbb{B}^{3} +\end{array} +``` + +## Simple JuMP Model -n = 3; -c = [1.0, 2.0, 3.0] -w = [0.3, 0.5, 1.0] -C = 3.2; +Writing this in [JuMP](https://github.com/jump-dev/JuMP.jl) we end up with -x = MOI.add_variables(model, n); +```@example dwave-knapsack +using JuMP +using ToQUBO +using DWave -for xᵢ in x - MOI.add_constraint(model, xᵢ, MOI.ZeroOne()) -end +model = Model(() -> ToQUBO.Optimizer(DWave.Neal.Optimizer)) -MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) +@variable(model, x[1:3], Bin) +@objective(model, Max, x[1] + 2 * x[2] + 3 * x[3]) +@constraint(model, 0.3 * x[1] + 0.5 * x[2] + x[3] ≤ 1.6) -MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(c, x), 0.0), -); +optimize!(model) -MOI.add_constraint( - model, - MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(w, x), 0.0), - MOI.LessThan(C), -); +solution_summary(model) +``` -MOI.optimize!(model) +The final decision is to take items ``2`` and ``3``, i.e., ``x_{1} = 0, x_{2} = 1, x_{3} = 1``. -# Collect Solution -MOI.get.(model, MOI.VariablePrimal(), x) +```@example dwave-knapsack +value.(x) ``` -### JuMP + D-Wave Examples -We may now fill a few more knapsacks using [JuMP](https://github.com/jump-dev/JuMP.jl). We will generate uniform random costs ``\mathbf{c}`` and weights ``\mathbf{w}`` then set the knapsack's capacity ``C`` to be a fraction of the total available weight i.e. ``80\%``. +## Using DataFrames + +Now, lets fill a few more knapsacks. +First, we generate uniform random costs ``\mathbf{c}`` and weights ``\mathbf{w}`` then set the knapsack's capacity ``C`` to be a fraction of the total available weight i.e. ``80\%``. This example was inspired by [D-Wave's knapsack example repository](https://github.com/dwave-examples/knapsack). -```@setup +```@setup dwave-knapsack using CSV using DataFrames using Random @@ -71,8 +72,8 @@ using Random rng = MersenneTwister(1) df = DataFrame( - :cost => rand(rng, 1:100, 16), - :weight => rand(rng, 1:100, 16), + :cost => rand(rng, 1:100, 8), + :weight => rand(rng, 1:100, 8), ) CSV.write("knapsack.csv", df) @@ -88,9 +89,9 @@ df = CSV.read("knapsack.csv", DataFrame) ```@example dwave-knapsack using JuMP using ToQUBO -using DWaveNeal # <- Your favourite Annealer/Sampler/Solver here +using DWave -model = Model(() -> ToQUBO.Optimizer(DWaveNeal.Optimizer)) +model = Model(() -> ToQUBO.Optimizer(DWave.Neal.Optimizer)) n = size(df, 1) c = collect(Float64, df[!, :cost]) @@ -104,5 +105,7 @@ C = round(0.8 * sum(w)) optimize!(model) # Add Results as a new column -insertcols!(df, 3, :select => map((ξ) -> (ξ > 0.0) ? "Yes" : "No", value.(x))) -``` \ No newline at end of file +df[:,:select] = map(xi -> ifelse(xi > 0, "✅", "❌"), value.(x)) + +df +``` diff --git a/docs/src/examples/portfolio_optimization.md b/docs/src/examples/portfolio_optimization.md index 11132a25..afe0a305 100644 --- a/docs/src/examples/portfolio_optimization.md +++ b/docs/src/examples/portfolio_optimization.md @@ -43,16 +43,17 @@ df = DataFrames.DataFrame( ``` ## Solving + ```@example portfolio-optimization using JuMP using ToQUBO -using DWaveNeal +using DWave function solve( config!::Function, df::DataFrame, λ::Float64 = 10.; - optimizer = DWaveNeal.Optimizer + optimizer = DWave.Neal.Optimizer ) # Number of assets n = size(df, 2) @@ -80,7 +81,7 @@ function solve( return value.(x) end -function solve(df::DataFrame, λ::Float64 = 10.; optimizer = DWaveNeal.Optimizer) +function solve(df::DataFrame, λ::Float64 = 10.; optimizer = DWave.Neal.Optimizer) return solve(identity, df, λ; optimizer) end ``` @@ -93,10 +94,27 @@ end ``` ## Penalty Analysis + To finish our discussion, we are going to sketch some graphics to help our reasoning on how the penalty factor ``\lambda`` affects our investments. +```@setup portfolio-optimization +using Plots +using Measures + +# Make plots look professional +Plots.default(; + fontfamily = "Computer Modern", + plot_titlefontsize = 16, + titlefontsize = 14, + guidefontsize = 12, + legendfontsize = 10, + tickfontsize = 10, + margin = 5mm, +) +``` + ```@example portfolio-optimization -using Plots; pythonplot() +using Plots Λ = collect(0.:5.:50.) X = Dict{Symbol,Vector{Float64}}(tag => [] for tag in assets) @@ -120,4 +138,4 @@ for tag in assets end plt -``` \ No newline at end of file +``` diff --git a/docs/src/index.md b/docs/src/index.md index 1dd37f16..c054f63a 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -5,6 +5,7 @@ ## Quick Start ### Installation + ```julia julia> import Pkg @@ -12,12 +13,13 @@ julia> Pkg.add("ToQUBO") ``` ### Example + ```@example using JuMP using ToQUBO -using DWaveNeal +using DWave -model = Model(() -> ToQUBO.Optimizer(DWaveNeal.Optimizer)) +model = Model(() -> ToQUBO.Optimizer(DWave.Neal.Optimizer)) @variable(model, x[1:3], Bin) @objective(model, Max, 1.0*x[1] + 2.0*x[2] + 3.0*x[3]) @@ -29,7 +31,9 @@ solution_summary(model) ``` ## Citing ToQUBO.jl + If you use `ToQUBO.jl` in your work, we kindly ask you to include the following citation: + ```tex @software{toqubo:2023, author = {Pedro Maciel Xavier and Pedro Ripper and Tiago Andrade and Joaquim Dias Garcia and David E. Bernal Neira}, @@ -41,4 +45,4 @@ If you use `ToQUBO.jl` in your work, we kindly ask you to include the following doi = {10.5281/zenodo.7644291}, url = {https://doi.org/10.5281/zenodo.7644291} } -``` \ No newline at end of file +``` diff --git a/docs/src/manual/1-start.md b/docs/src/manual/1-start.md index 5deae6c3..060a378a 100644 --- a/docs/src/manual/1-start.md +++ b/docs/src/manual/1-start.md @@ -1,12 +1,13 @@ # Manual ## Quick Start Guide + ```@example quick-start using JuMP using ToQUBO -using DWaveNeal # <- Your favourite Annealer/Sampler/Solver here +using DWave -model = Model(() -> ToQUBO.Optimizer(DWaveNeal.Optimizer)) +model = Model(() -> ToQUBO.Optimizer(DWave.Neal.Optimizer)) @variable(model, x[1:3], Bin) @@ -20,7 +21,8 @@ solution_summary(model) ``` ## Table of Contents + ```@contents Pages = ["2-model.md", "3-results.md", "4-settings.md"] Depth = 2 -``` \ No newline at end of file +``` diff --git a/docs/src/manual/3-results.md b/docs/src/manual/3-results.md index b3e497d7..b064e9e7 100644 --- a/docs/src/manual/3-results.md +++ b/docs/src/manual/3-results.md @@ -2,4 +2,4 @@ !!! warning "Work in progress" We hope to write this part of the documentation soon. - Please come back later! \ No newline at end of file + Please come back later! diff --git a/docs/src/manual/4-settings.md b/docs/src/manual/4-settings.md index 65cba01e..0132cd83 100644 --- a/docs/src/manual/4-settings.md +++ b/docs/src/manual/4-settings.md @@ -1,16 +1,29 @@ # Compiler Settings -## Working with solver architectures ```@docs -ToQUBO.Attributes.Architecture +ToQUBO.Attributes.StableCompilation +``` + +## Compiler Messages + +```@docs +ToQUBO.Attributes.Warnings +``` + +## Compiler Optimization + +```@docs +ToQUBO.Attributes.Optimization ``` +## Working with target architectures + ```@docs -ToQUBO.AbstractArchitecture -ToQUBO.GenericArchitecture +ToQUBO.Attributes.Architecture ``` ## Quadratization + ```@docs ToQUBO.Attributes.Quadratize ToQUBO.Attributes.QuadratizationMethod @@ -18,6 +31,7 @@ ToQUBO.Attributes.StableQuadratization ``` ## Variable & Constraint Encoding + ```@docs ToQUBO.Attributes.VariableEncodingBits ToQUBO.Attributes.DefaultVariableEncodingBits @@ -30,6 +44,7 @@ ToQUBO.Attributes.ConstraintEncodingPenalty ``` ## Discretization + ```@docs ToQUBO.Attributes.Discretize ``` diff --git a/src/ToQUBO.jl b/src/ToQUBO.jl index a0d6de35..5d0cb564 100644 --- a/src/ToQUBO.jl +++ b/src/ToQUBO.jl @@ -1,21 +1,20 @@ module ToQUBO -# Base Imports & Constants -import TOML -const PROJECT_FILE_PATH = joinpath(@__DIR__, "..", "Project.toml") -const PROJECT_VERSION = VersionNumber(getindex(TOML.parsefile(PROJECT_FILE_PATH), "version")) +using MathOptInterface +const MOI = MathOptInterface -# QUBOTools +import PseudoBooleanOptimization as PBO import QUBOTools -const QUBO_NORMAL_FORM{T} = Tuple{Int,Dict{Int,T},Dict{Tuple{Int,Int},T},T,T} +# Versioning +using TOML +const __PROJECT__ = abspath(@__DIR__, "..") +const __VERSION__ = VersionNumber(getindex(TOML.parsefile(joinpath(__PROJECT__, "Project.toml")), "version")) -# External Imports -import MathOptInterface as MOI +# MOI Aliases const MOIU = MOI.Utilities const MOIB = MOI.Bridges -# MOI Aliases const SAF{T} = MOI.ScalarAffineFunction{T} const SQF{T} = MOI.ScalarQuadraticFunction{T} const SAT{T} = MOI.ScalarAffineTerm{T} @@ -25,26 +24,31 @@ const EQ{T} = MOI.EqualTo{T} const LT{T} = MOI.LessThan{T} const GT{T} = MOI.GreaterThan{T} -const VI = MOI.VariableIndex -const CI = MOI.ConstraintIndex +const VI = MOI.VariableIndex +const CI{F,S} = MOI.ConstraintIndex{F,S} # Library -include("lib/error.jl") -include("lib/pbo/PBO.jl") +include("error.jl") -# Model +# Encoding Module +include("encoding/encoding.jl") + +# Models include("model/qubo.jl") include("model/prequbo.jl") -include("model/virtual.jl") -include("model/wrapper.jl") -# Compiler & Analysis -include("compiler/compiler.jl") +# Virtual mapping module +include("virtual/virtual.jl") + +# MOI wrapper +include("wrapper.jl") # Attributes include("attributes/model.jl") include("attributes/solver.jl") -include("attributes/virtual.jl") include("attributes/compiler.jl") -end # module \ No newline at end of file +# Compiler +include("compiler/compiler.jl") + +end # module diff --git a/src/attributes/compiler.jl b/src/attributes/compiler.jl index e7ad3a6a..5a714e7a 100644 --- a/src/attributes/compiler.jl +++ b/src/attributes/compiler.jl @@ -1,259 +1,535 @@ module Attributes -import ..ToQUBO: - ToQUBO, - Unary, - Binary, - Arithmetic, - OneHot, - DomainWall, - Bounded, - GenericArchitecture - import MathOptInterface as MOI const MOIU = MOI.Utilities -const VI = MOI.VariableIndex -const CI = MOI.ConstraintIndex +const VI = MOI.VariableIndex +const CI = MOI.ConstraintIndex + +import PseudoBooleanOptimization as PBO +import QUBOTools + +import ..ToQUBO: Optimizer +import ..Encoding +import ..Virtual + +function MOIU.map_indices(::Function, e::Encoding.VariableEncodingMethod) + return e +end abstract type CompilerAttribute <: MOI.AbstractOptimizerAttribute end -export - Architecture, - Discretize, - Quadratize, - QuadratizationMethod, - StableQuadratization, - DefaultVariableEncodingATol, - DefaultVariableEncodingBits, - DefaultVariableEncodingMethod, - VariableEncodingATol, - VariableEncodingBits, - VariableEncodingMethod, - VariableEncodingPenalty, - ConstraintEncodingPenalty, - QUBONormalForm +MOI.supports(::Optimizer, ::A) where {A<:CompilerAttribute} = true + +@doc raw""" + Warnings() +""" +struct Warnings <: CompilerAttribute end + +function MOI.get(model::Optimizer, ::Warnings)::Bool + return get(model.compiler_settings, :warnings, true) +end + +function MOI.set(model::Optimizer, ::Warnings, flag::Bool) + model.compiler_settings[:warnings] = flag + + return nothing +end + +function MOI.set(model::Optimizer, ::Warnings, ::Nothing) + delete!(model.compiler_settings, :warnings) + + return nothing +end + +function warnings(model::Optimizer)::Bool + return MOI.get(model, Warnings()) +end + +@doc raw""" + Optimization() +""" +struct Optimization <: CompilerAttribute end + +function MOI.get(model::Optimizer, ::Optimization)::Integer + return get(model.compiler_settings, :optimization, 0) +end + +function MOI.set(model::Optimizer, ::Optimization, level::Integer) + @assert level >= 0 + + model.compiler_settings[:optimization] = level + + return nothing +end + +function MOI.set(model::Optimizer, ::Optimization, ::Nothing) + delete!(model.compiler_settings, :optimization) + + return nothing +end + +function optimization(model::Optimizer)::Integer + return MOI.get(model, Optimization()) +end @doc raw""" Architecture() Selects which solver architecture to use. -Defaults to [`GenericArchitecture`](@ref). -""" struct Architecture <: CompilerAttribute end +Defaults to `QUBOTools.GenericArchitecture`. +""" +struct Architecture <: CompilerAttribute end -function MOI.get(model::ToQUBO.VirtualModel, ::Architecture)::ToQUBO.AbstractArchitecture - return get(model.compiler_settings, :architecture, ToQUBO.GenericArchitecture()) +function MOI.get(model::Optimizer, ::Architecture)::QUBOTools.AbstractArchitecture + return get(model.compiler_settings, :architecture, QUBOTools.GenericArchitecture()) end -function MOI.set(model::ToQUBO.VirtualModel, ::Architecture, arch::ToQUBO.AbstractArchitecture) +function MOI.set(model::Optimizer, ::Architecture, arch::QUBOTools.AbstractArchitecture) model.compiler_settings[:architecture] = arch return nothing end +function MOI.set(model::Optimizer, ::Architecture, ::Nothing) + delete!(model.compiler_settings, :architecture) + + return nothing +end + +function architecture(model::Optimizer)::QUBOTools.AbstractArchitecture + return MOI.get(model, Architecture()) +end + @doc raw""" Discretize() When set, this boolean flag guarantees that every coefficient in the final formulation is an integer. -""" struct Discretize <: CompilerAttribute end +""" +struct Discretize <: CompilerAttribute end -function MOI.get(model::ToQUBO.VirtualModel, ::Discretize, flag::Bool)::Bool +function MOI.get(model::Optimizer, ::Discretize)::Bool return get(model.compiler_settings, :discretize, false) end -function MOI.set(model::ToQUBO.VirtualModel, ::Discretize, flag::Bool) +function MOI.set(model::Optimizer, ::Discretize, flag::Bool) model.compiler_settings[:discretize] = flag return nothing end +function MOI.set(model::Optimizer, ::Discretize, ::Nothing) + delete!(model.compiler_settings, :discretize) + + return nothing +end + +function discretize(model::Optimizer)::Bool + return MOI.get(model, Discretize()) +end + @doc raw""" Quadratize() Boolean flag to conditionally perform the quadratization step. Is automatically set by the compiler when high-order functions are generated. -""" struct Quadratize <: CompilerAttribute end +""" +struct Quadratize <: CompilerAttribute end -function MOI.get(model::ToQUBO.VirtualModel, ::Quadratize)::Bool +function MOI.get(model::Optimizer, ::Quadratize)::Bool return get(model.compiler_settings, :quadratize, false) end -function MOI.set(model::ToQUBO.VirtualModel, ::Quadratize, flag::Bool) +function MOI.set(model::Optimizer, ::Quadratize, flag::Bool) model.compiler_settings[:quadratize] = flag return nothing end +function quadratize(model::Optimizer)::Bool + return MOI.get(model, Quadratize()) +end + @doc raw""" QuadratizationMethod() Defines which quadratization method to use. Available options are defined in the `PBO` submodule. -""" struct QuadratizationMethod <: CompilerAttribute end +""" +struct QuadratizationMethod <: CompilerAttribute end -function MOI.get(model::ToQUBO.VirtualModel, ::QuadratizationMethod) - return get(model.compiler_settings, :QuadratizationMethod, ToQUBO.PBO.INFER) +function MOI.get(model::Optimizer, ::QuadratizationMethod) + return get(model.compiler_settings, :quadratization_method, PBO.DEFAULT()) end function MOI.set( - model::ToQUBO.VirtualModel, + model::Optimizer, ::QuadratizationMethod, - ::Type{method}, -) where {method<:ToQUBO.PBO.QuadratizationMethod} - model.compiler_settings[:QuadratizationMethod] = method + method::PBO.QuadratizationMethod, +) + model.compiler_settings[:quadratization_method] = method + + return nothing +end + +function MOI.set(model::Optimizer, ::QuadratizationMethod, ::Nothing) + delete!(model.compiler_settings, :quadratization_method) return nothing end +function quadratization_method(model::Optimizer) + return MOI.get(model, QuadratizationMethod()) +end + @doc raw""" StableQuadratization() When set, this boolean flag enables stable quadratization methods, thus yielding predictable results. This is intended to be used during tests or other situations where deterministic output is desired. On the other hand, usage in production is not recommended since it requires increased memory and processing resources. -""" struct StableQuadratization <: CompilerAttribute end +""" +struct StableQuadratization <: CompilerAttribute end -function MOI.get(model::ToQUBO.VirtualModel, ::StableQuadratization)::Bool +function MOI.get(model::Optimizer, ::StableQuadratization)::Bool return get(model.compiler_settings, :stable_quadratization, false) end -function MOI.set(model::ToQUBO.VirtualModel, ::StableQuadratization, flag::Bool) +function MOI.set(model::Optimizer, ::StableQuadratization, flag::Bool) model.compiler_settings[:stable_quadratization] = flag return nothing end +function MOI.set(model::Optimizer, ::StableQuadratization, ::Nothing) + delete!(model.compiler_settings, :stable_quadratization) + + return nothing +end + +function stable_quadratization(model::Optimizer)::Bool + return stable_compilation(model) || MOI.get(model, StableQuadratization()) +end + +@doc raw""" + StableCompilation() + +When set, this boolean flag enables stable reformulation methods, thus yielding predictable results. +""" +struct StableCompilation <: CompilerAttribute end + +function MOI.get(model::Optimizer, ::StableCompilation)::Bool + return get(model.compiler_settings, :stable_compilation, false) +end + +function MOI.set(model::Optimizer, ::StableCompilation, flag::Bool) + model.compiler_settings[:stable_compilation] = flag + + return nothing +end + +function MOI.set(model::Optimizer, ::StableCompilation, ::Nothing) + delete!(model.compiler_settings, :stable_compilation) + + return nothing +end + +function stable_compilation(model::Optimizer)::Bool + return MOI.get(model, StableCompilation()) +end + @doc raw""" DefaultVariableEncodingMethod() Fallback value for [`VariableEncodingMethod`](@ref). -""" struct DefaultVariableEncodingMethod <: CompilerAttribute end +""" +struct DefaultVariableEncodingMethod <: CompilerAttribute end -function MOI.get(model::ToQUBO.VirtualModel, ::DefaultVariableEncodingMethod)::ToQUBO.Encoding - return get(model.compiler_settings, :default_variable_encoding_method, ToQUBO.Binary()) +function MOI.get( + model::Optimizer, + ::DefaultVariableEncodingMethod, +)::Encoding.VariableEncodingMethod + return get(model.compiler_settings, :default_variable_encoding_method, Encoding.Binary()) end -function MOI.set(model::ToQUBO.VirtualModel, ::DefaultVariableEncodingMethod, e::ToQUBO.Encoding) +function MOI.set( + model::Optimizer, + ::DefaultVariableEncodingMethod, + e::Encoding.VariableEncodingMethod, +) model.compiler_settings[:default_variable_encoding_method] = e return nothing end +function MOI.set(model::Optimizer, ::DefaultVariableEncodingMethod, ::Nothing) + delete!(model.compiler_settings, :default_variable_encoding_method) + + return nothing +end + @doc raw""" DefaultVariableEncodingATol() Fallback value for [`VariableEncodingATol`](@ref). -""" struct DefaultVariableEncodingATol <: CompilerAttribute end +""" +struct DefaultVariableEncodingATol <: CompilerAttribute end -function MOI.get(model::ToQUBO.VirtualModel{T}, ::DefaultVariableEncodingATol)::T where {T} - return get(model.compiler_settings, :default_variable_encoding_atol, T(1E-2)) +function MOI.get(model::Optimizer{T}, ::DefaultVariableEncodingATol)::T where {T} + return get(model.compiler_settings, :default_variable_encoding_atol, T(1 / 4)) end -function MOI.set(model::ToQUBO.VirtualModel{T}, ::DefaultVariableEncodingATol, τ::T) where {T} +function MOI.set(model::Optimizer{T}, ::DefaultVariableEncodingATol, τ::T) where {T} model.compiler_settings[:default_variable_encoding_atol] = τ return nothing end +function MOI.set(model::Optimizer, ::DefaultVariableEncodingATol, ::Nothing) + delete!(model.compiler_settings, :default_variable_encoding_atol) + + return nothing +end + +@doc raw""" + DefaultVariableEncodingBits() +""" +struct DefaultVariableEncodingBits <: CompilerAttribute end + +function MOI.get(model::Optimizer, ::DefaultVariableEncodingBits)::Union{Integer,Nothing} + return get(model.compiler_settings, :default_variable_encoding_bits, nothing) +end + +function MOI.set(model::Optimizer, ::DefaultVariableEncodingBits, n::Integer) + model.compiler_settings[:default_variable_encoding_bits] = n + + return nothing +end + +function MOI.set(model::Optimizer, ::DefaultVariableEncodingBits, ::Nothing) + delete!(model.compiler_settings, :default_variable_encoding_bits) + + return nothing +end + + abstract type CompilerVariableAttribute <: MOI.AbstractVariableAttribute end +MOI.supports(::Optimizer, ::A, ::Type{VI}) where {A<:CompilerVariableAttribute} = true + @doc raw""" VariableEncodingATol() -""" struct VariableEncodingATol <: CompilerVariableAttribute end +""" +struct VariableEncodingATol <: CompilerVariableAttribute end -function MOI.get(model::ToQUBO.VirtualModel{T}, ::VariableEncodingATol, vi::VI)::T where {T} +function MOI.get( + model::Optimizer{T}, + ::VariableEncodingATol, + vi::VI, +)::Union{T,Nothing} where {T} attr = :variable_encoding_atol - if !haskey(model.variable_settings, attr) || !haskey(model.variable_settings[attr], vi) - return MOI.get(model, DefaultVariableEncodingATol()) + if haskey(model.variable_settings, attr) + return get(model.variable_settings[attr], vi, nothing) else - return model.variable_settings[attr][vi] + return nothing end end -function MOI.set(model::ToQUBO.VirtualModel{T}, ::VariableEncodingATol, vi::VI, τ::T) where {T} +function MOI.set(model::Optimizer{T}, ::VariableEncodingATol, vi::VI, τ::T) where {T} attr = :variable_encoding_atol if !haskey(model.variable_settings, attr) - model.variable_settings[attr] = Dict{VI,Any}(vi => τ) - else - model.variable_settings[attr][vi] = τ + model.variable_settings[attr] = Dict{VI,Any}() end + model.variable_settings[attr][vi] = τ + return nothing end -@doc raw""" - DefaultVariableEncodingBits() -""" struct DefaultVariableEncodingBits <: CompilerAttribute end +function MOI.set(model::Optimizer, ::VariableEncodingATol, vi::VI, ::Nothing) + attr = :variable_encoding_atol -function MOI.get(model::ToQUBO.VirtualModel, ::DefaultVariableEncodingBits)::Union{Integer,Nothing} - return get(model.compiler_settings, :default_variable_encoding_bits, nothing) + if haskey(model.variable_settings, attr) + delete!(model.variable_settings[attr], vi) + end + + return nothing end -function MOI.set(model::ToQUBO.VirtualModel, ::DefaultVariableEncodingBits, n::Union{Integer,Nothing}) - model.compiler_settings[:default_variable_encoding_bits] = n +function variable_encoding_atol(model::Optimizer{T}, vi::VI)::T where {T} + τ = MOI.get(model, VariableEncodingATol(), vi) - return nothing + if τ === nothing + return MOI.get(model, DefaultVariableEncodingATol()) + else + return τ + end end @doc raw""" VariableEncodingBits() -""" struct VariableEncodingBits <: CompilerVariableAttribute end +""" +struct VariableEncodingBits <: CompilerVariableAttribute end -function MOI.get(model::ToQUBO.VirtualModel, ::VariableEncodingBits, vi::VI)::Union{Integer,Nothing} +function MOI.get(model::Optimizer, ::VariableEncodingBits, vi::VI)::Union{Integer,Nothing} attr = :variable_encoding_bits - if !haskey(model.variable_settings, attr) || !haskey(model.variable_settings[attr], vi) - return MOI.get(model, DefaultVariableEncodingBits()) + if haskey(model.variable_settings, attr) + return get(model.variable_settings[attr], vi, nothing) else - return model.variable_settings[attr][vi] + return MOI.get(model, DefaultVariableEncodingBits()) end end -function MOI.set(model::ToQUBO.VirtualModel, ::VariableEncodingBits, vi::VI, n::Union{Integer,Nothing}) +function MOI.set(model::Optimizer, ::VariableEncodingBits, vi::VI, n::Integer) attr = :variable_encoding_bits if !haskey(model.variable_settings, attr) - model.variable_settings[attr] = Dict{VI,Any}(vi => n) - else - model.variable_settings[attr][vi] = n + model.variable_settings[attr] = Dict{VI,Any}() end + model.variable_settings[attr][vi] = n + return nothing end +function MOI.set(model::Optimizer, ::VariableEncodingBits, vi::VI, ::Nothing) + attr = :variable_encoding_bits + + if haskey(model.variable_settings, attr) + delete!(model.variable_settings[attr], vi) + end + + return nothing +end + +function variable_encoding_bits(model::Optimizer, vi::VI)::Union{Integer,Nothing} + n = MOI.get(model, VariableEncodingBits(), vi) + + if isnothing(n) + return MOI.get(model, DefaultVariableEncodingBits()) + else + return n + end +end + @doc raw""" VariableEncodingMethod() Available methods are: -- [`Binary`](@ref) (default) -- [`Unary`](@ref) -- [`Arithmetic`](@ref) -- [`OneHot`](@ref) -- [`DomainWall`](@ref) -- [`Bounded`](@ref) - -The [`Binary`](@ref), [`Unary`](@ref) and [`Arithmetic`](@ref) encodings can have their -expansion coefficients bounded by parametrizing the [`Bounded`](@ref) encoding. -""" struct VariableEncodingMethod <: CompilerVariableAttribute end - -function MOI.get(model::ToQUBO.VirtualModel, ::VariableEncodingMethod, vi::VI)::ToQUBO.Encoding +- [`Encoding.Binary`](@ref) (default) +- [`Encoding.Unary`](@ref) +- [`Encoding.Arithmetic`](@ref) +- [`Encoding.OneHot`](@ref) +- [`Encoding.DomainWall`](@ref) +- [`Encoding.Bounded`](@ref) + +The [`Encoding.Binary`](@ref), [`Encoding.Unary`](@ref) and [`Encoding.Arithmetic`](@ref) +encodings can have their expansion coefficients bounded by wrapping them with the +[`Encoding.Bounded`](@ref) method. +""" +struct VariableEncodingMethod <: CompilerVariableAttribute end + +function variable_encoding_method(model::Optimizer, vi::VI)::Encoding.VariableEncodingMethod + e = MOI.get(model, VariableEncodingMethod(), vi) + + if isnothing(e) + return MOI.get(model, DefaultVariableEncodingMethod()) + else + return e + end +end + +function MOI.get( + model::Optimizer, + ::VariableEncodingMethod, + vi::VI, +)::Union{Encoding.VariableEncodingMethod,Nothing} attr = :variable_encoding_method if !haskey(model.variable_settings, attr) || !haskey(model.variable_settings[attr], vi) - return MOI.get(model, DefaultVariableEncodingMethod()) + return nothing else return model.variable_settings[attr][vi] end end -function MOI.set(model::ToQUBO.VirtualModel, ::VariableEncodingMethod, vi::VI, e::ToQUBO.Encoding) +function MOI.set( + model::Optimizer, + ::VariableEncodingMethod, + vi::VI, + e::Encoding.VariableEncodingMethod, +) attr = :variable_encoding_method if !haskey(model.variable_settings, attr) - model.variable_settings[attr] = Dict{VI,Any}(vi => e) + model.variable_settings[attr] = Dict{VI,Any}() + end + + model.variable_settings[attr][vi] = e + + return nothing +end + +function MOI.set(model::Optimizer, ::Attributes.VariableEncodingMethod, vi::VI, ::Nothing) + attr = :variable_encoding_method + + if haskey(model.variable_settings, attr) + delete!(model.variable_settings[attr], vi) + end + + return nothing +end + +@doc raw""" + VariableEncodingPenaltyHint() + +Allows the user to set the coefficients used for encoding constraints. +""" +struct VariableEncodingPenaltyHint <: CompilerVariableAttribute end + +function variable_encoding_penalty_hint(model::Optimizer, vi::VI) + return MOI.get(model, VariableEncodingPenaltyHint(), vi) +end + +function MOI.get(model::Optimizer{T}, ::VariableEncodingPenaltyHint, vi::VI) where {T} + attr = :variable_encoding_penalty_hint + + if !haskey(model.variable_settings, attr) || !haskey(model.variable_settings[attr], vi) + return nothing else - model.variable_settings[attr][vi] = e + return model.variable_settings[attr][vi]::T + end +end + +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 +end + +function MOI.set( + model::Optimizer{T}, + ::VariableEncodingPenaltyHint, + vi::VI, + ::Nothing, +) where {T} + attr = :variable_encoding_penalty_hint + + if haskey(model.variable_settings, attr) + delete!(model.variable_settings[attr], vi) end return nothing @@ -264,94 +540,125 @@ end Allows the user to set and retrieve the coefficients used for encoding variables when additional constraints are involved. -""" struct VariableEncodingPenalty <: CompilerVariableAttribute end +""" +struct VariableEncodingPenalty <: CompilerVariableAttribute end -function MOI.get(model::ToQUBO.VirtualModel{T}, ::VariableEncodingPenalty, vi::VI) where {T} - return model.θ[vi]::T +MOI.is_set_by_optimize(::VariableEncodingPenalty) = true + +function variable_encoding_penalty(model::Optimizer, vi::VI) + return MOI.get(model, VariableEncodingPenalty(), vi) +end + +function MOI.get(model::Optimizer{T}, ::VariableEncodingPenalty, vi::VI) where {T} + return get(model.θ, vi, nothing) +end + +function MOI.set(model::Optimizer{T}, ::VariableEncodingPenalty, vi::VI, θ) where {T} + model.θ[vi] = convert(T, θ) + + return nothing end function MOI.set( - model::ToQUBO.VirtualModel{T}, + model::Optimizer{T}, ::VariableEncodingPenalty, vi::VI, - θ::T, + ::Nothing, ) where {T} - model.θ[vi] = θ + delete!(model.θ, vi) return nothing end -MOI.is_set_by_optimize(::VariableEncodingPenalty) = true - abstract type CompilerConstraintAttribute <: MOI.AbstractConstraintAttribute end +MOI.supports(::Optimizer, ::A, ::Type{<:CI}) where {A<:CompilerConstraintAttribute} = true + @doc raw""" - ConstraintEncodingPenalty() + ConstraintEncodingPenaltyHint() -Allows the user to set and retrieve the coefficients used for encoding constraints. -""" struct ConstraintEncodingPenalty <: CompilerConstraintAttribute end +Allows the user to set the coefficients used for encoding constraints. +""" +struct ConstraintEncodingPenaltyHint <: CompilerConstraintAttribute end + +function constraint_encoding_penalty_hint(model::Optimizer, ci::CI) + return MOI.get(model, ConstraintEncodingPenaltyHint(), ci) +end -function MOI.get(model::ToQUBO.VirtualModel{T}, ::ConstraintEncodingPenalty, ci::CI) where {T} - return model.ρ[ci] +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) + return nothing + else + return model.constraint_settings[attr][ci]::T + end end function MOI.set( - model::ToQUBO.VirtualModel{T}, - ::ConstraintEncodingPenalty, + model::Optimizer{T}, + ::ConstraintEncodingPenaltyHint, ci::CI, - ρ::T, + ρ::Any, ) where {T} - model.ρ[ci] = ρ + attr = :constraint_encoding_penalty_hint + + if !haskey(model.constraint_settings, attr) + model.constraint_settings[attr] = Dict{CI,Any}() + end + + model.constraint_settings[attr][ci] = convert(T, ρ) return nothing end -MOI.is_set_by_optimize(::ConstraintEncodingPenalty) = true +function MOI.set( + model::Optimizer{T}, + ::ConstraintEncodingPenaltyHint, + ci::CI, + ::Nothing, +) where {T} + attr = :constraint_encoding_penalty_hint -@doc raw""" - QUBONormalForm() -""" struct QUBONormalForm <: CompilerAttribute end + if haskey(model.constraint_settings, attr) + delete!(model.constraint_settings[attr], ci) + end -function MOI.get( - model::ToQUBO.VirtualModel{T}, - ::QUBONormalForm, -)::ToQUBO.QUBO_NORMAL_FORM{T} where {T} - target_model = model.target_model + return nothing +end - n = MOI.get(target_model, MOI.NumberOfVariables()) - F = MOI.get(target_model, MOI.ObjectiveFunctionType()) - f = MOI.get(target_model, MOI.ObjectiveFunction{F}()) +@doc raw""" + ConstraintEncodingPenalty() - linear_terms = sizehint!(Dict{Int,T}(), length(f.affine_terms)) - quadratic_terms = sizehint!(Dict{Tuple{Int,Int},T}(), length(f.quadratic_terms)) +Allows the user to retrieve the coefficients used for encoding constraints. +""" +struct ConstraintEncodingPenalty <: CompilerConstraintAttribute end - for a in f.affine_terms - c = a.coefficient - i = a.variable.value +MOI.is_set_by_optimize(::ConstraintEncodingPenalty) = true - linear_terms[i] = get(linear_terms, i, zero(T)) + c - end +function constraint_encoding_penalty(model::Optimizer, ci::CI) + return MOI.get(model, ConstraintEncodingPenalty(), ci) +end - for q in f.quadratic_terms - c = q.coefficient - i = q.variable_1.value - j = q.variable_2.value - - if i == j - linear_terms[i] = get(linear_terms, i, zero(T)) + c / 2 - elseif i > j - quadratic_terms[(j, i)] = get(quadratic_terms, (j, i), zero(T)) + c - else - quadratic_terms[(i, j)] = get(quadratic_terms, (i, j), zero(T)) + c - end - end +function MOI.get(model::Optimizer{T}, ::ConstraintEncodingPenalty, ci::CI) where {T} + return get(model.ρ, ci, nothing) +end - scale = one(T) - offset = f.constant +function MOI.set(model::Optimizer{T}, ::ConstraintEncodingPenalty, ci::CI, ρ::T) where {T} + model.ρ[ci] = ρ - return (n, linear_terms, quadratic_terms, scale, offset) + return nothing end -MOIU.map_indices(::MOIU.IndexMap, x::ToQUBO.QUBO_NORMAL_FORM{T}) where {T} = x +function MOI.set( + model::Optimizer{T}, + ::ConstraintEncodingPenalty, + ci::CI, + ::Nothing, +) where {T} + delete!(model.ρ, ci) + + return nothing +end -end # module Settings \ No newline at end of file +end # module Attributes diff --git a/src/attributes/model.jl b/src/attributes/model.jl index 044309df..b76b6037 100644 --- a/src/attributes/model.jl +++ b/src/attributes/model.jl @@ -1,7 +1,8 @@ -MOI.get(::VirtualModel, ::MOI.SolverName) = "Virtual Model" -MOI.get(::VirtualModel, ::MOI.SolverVersion) = PROJECT_VERSION +MOI.get(::Virtual.Model, ::MOI.SolverName) = "Virtual QUBO Model" +MOI.get(::Virtual.Model, ::MOI.SolverVersion) = __VERSION__ -const MOI_MODEL_ATTRIBUTE = Union{ +const SOURCE_MODEL_ATTRIBUES{T} = Union{ + MOIB.ListOfNonstandardBridges{T}, MOI.ListOfConstraintAttributesSet, MOI.ListOfConstraintIndices, MOI.ListOfConstraintTypesPresent, @@ -11,64 +12,57 @@ const MOI_MODEL_ATTRIBUTE = Union{ MOI.NumberOfConstraints, MOI.NumberOfVariables, MOI.Name, + MOI.VariableName, + MOI.ConstraintName, MOI.ObjectiveFunction, MOI.ObjectiveFunctionType, MOI.ObjectiveSense, } -function MOI.get(model::VirtualModel, attr::MOI_MODEL_ATTRIBUTE) - return MOI.get(model.source_model, attr) +function MOI.get(model::Virtual.Model{T}, attr::SOURCE_MODEL_ATTRIBUES{T}, args...) where {T} + return MOI.get(model.source_model, attr, args...) end -function MOI.set(model::VirtualModel, attr::MOI_MODEL_ATTRIBUTE, value::Any) - MOI.set(model.source_model, attr, value) +function MOI.set(model::Virtual.Model{T}, attr::SOURCE_MODEL_ATTRIBUES{T}, args::Any...) where {T} + MOI.set(model.source_model, attr, args...) return nothing end +function MOI.supports(::Virtual.Model{T}, ::SOURCE_MODEL_ATTRIBUES{T}) where {T} + return true +end + function MOI.get( - model::VirtualModel, + model::Virtual.Model, attr::Union{MOI.ConstraintFunction,MOI.ConstraintSet}, ci::MOI.ConstraintIndex, ) return MOI.get(model.source_model, attr, ci) end -function MOI.get(model::VirtualModel, attr::MOI.VariableName, x::VI) +function MOI.get(model::Virtual.Model, attr::MOI.VariableName, x::VI) return MOI.get(model.source_model, attr, x) end -function Base.show(io::IO, model::VirtualModel) - print( - io, - """ - Virtual Model - with source: - $(model.source_model) - with target: - $(model.target_model) - """, - ) -end - -function MOI.add_variable(model::VirtualModel) +function MOI.add_variable(model::Virtual.Model) return MOI.add_variable(model.source_model) end function MOI.add_constraint( - model::VirtualModel, + model::Virtual.Model, f::MOI.AbstractFunction, s::MOI.AbstractSet, ) return MOI.add_constraint(model.source_model, f, s) end - + function MOI.set( - model::VirtualModel, + model::Virtual.Model, ::MOI.ObjectiveFunction{F}, f::F, ) where {F<:MOI.AbstractFunction} MOI.set(model.source_model, MOI.ObjectiveFunction{F}(), f) return nothing -end \ No newline at end of file +end diff --git a/src/attributes/solver.jl b/src/attributes/solver.jl index f5152501..214d3488 100644 --- a/src/attributes/solver.jl +++ b/src/attributes/solver.jl @@ -1,4 +1,4 @@ -function MOI.get(model::VirtualModel, raw_attr::MOI.RawOptimizerAttribute) +function MOI.get(model::Virtual.Model, raw_attr::MOI.RawOptimizerAttribute) if !isnothing(model.optimizer) && MOI.supports(model.optimizer, raw_attr) return MOI.get(model.optimizer, raw_attr) else @@ -7,7 +7,7 @@ function MOI.get(model::VirtualModel, raw_attr::MOI.RawOptimizerAttribute) end end -function MOI.set(model::VirtualModel, raw_attr::MOI.RawOptimizerAttribute, args...) +function MOI.set(model::Virtual.Model, raw_attr::MOI.RawOptimizerAttribute, args...) if !isnothing(model.optimizer) && MOI.supports(model.optimizer, raw_attr) MOI.set(model.optimizer, raw_attr, args...) else @@ -18,7 +18,7 @@ function MOI.set(model::VirtualModel, raw_attr::MOI.RawOptimizerAttribute, args. return nothing end -function MOI.supports(model::VirtualModel, raw_attr::MOI.RawOptimizerAttribute) +function MOI.supports(model::Virtual.Model, raw_attr::MOI.RawOptimizerAttribute) if !isnothing(model.optimizer) return MOI.supports(model.optimizer, raw_attr) else @@ -27,7 +27,7 @@ function MOI.supports(model::VirtualModel, raw_attr::MOI.RawOptimizerAttribute) end end -function MOI.get(model::VirtualModel, attr::MOI.AbstractOptimizerAttribute) +function MOI.get(model::Virtual.Model, attr::MOI.AbstractOptimizerAttribute) if !isnothing(model.optimizer) && MOI.supports(model.optimizer, attr) return MOI.get(model.optimizer, attr) else @@ -35,7 +35,7 @@ function MOI.get(model::VirtualModel, attr::MOI.AbstractOptimizerAttribute) end end -function MOI.set(model::VirtualModel, attr::MOI.AbstractOptimizerAttribute, args...) +function MOI.set(model::Virtual.Model, attr::MOI.AbstractOptimizerAttribute, args...) if !isnothing(model.optimizer) && MOI.supports(model.optimizer, attr) MOI.set(model.optimizer, attr, args...) else @@ -45,7 +45,7 @@ function MOI.set(model::VirtualModel, attr::MOI.AbstractOptimizerAttribute, args return nothing end -function MOI.supports(model::VirtualModel, attr::MOI.AbstractOptimizerAttribute) +function MOI.supports(model::Virtual.Model, attr::MOI.AbstractOptimizerAttribute) if !isnothing(model.optimizer) return MOI.supports(model.optimizer, attr) else @@ -54,7 +54,7 @@ function MOI.supports(model::VirtualModel, attr::MOI.AbstractOptimizerAttribute) end function MOI.get( - model::VirtualModel, + model::Virtual.Model, attr::Union{ MOI.SolveTimeSec, MOI.PrimalStatus, @@ -71,7 +71,7 @@ function MOI.get( end function MOI.supports( - model::VirtualModel, + model::Virtual.Model, attr::Union{ MOI.SolveTimeSec, MOI.PrimalStatus, @@ -87,7 +87,7 @@ function MOI.supports( end end -function MOI.get(model::VirtualModel, rc::MOI.ResultCount) +function MOI.get(model::Virtual.Model, rc::MOI.ResultCount) if isnothing(model.optimizer) return 0 else @@ -95,9 +95,9 @@ function MOI.get(model::VirtualModel, rc::MOI.ResultCount) end end -MOI.supports(::VirtualModel, ::MOI.ResultCount) = true +MOI.supports(::Virtual.Model, ::MOI.ResultCount) = true -function MOI.get(model::VirtualModel{T}, ov::MOI.ObjectiveValue) where {T} +function MOI.get(model::Virtual.Model{T}, ov::MOI.ObjectiveValue) where {T} if isnothing(model.optimizer) return zero(T) else @@ -105,20 +105,26 @@ function MOI.get(model::VirtualModel{T}, ov::MOI.ObjectiveValue) where {T} end end -function MOI.get(model::VirtualModel{T}, vp::MOI.VariablePrimalStart, x::VI) where {T} +function MOI.get(model::Virtual.Model{T}, vp::MOI.VariablePrimalStart, x::VI) where {T} return MOI.get(model.source_model, vp, x) end -MOI.supports(::VirtualModel, ::MOI.VariablePrimalStart, ::MOI.VariableIndex) = true +MOI.supports(::Virtual.Model, ::MOI.VariablePrimalStart, ::MOI.VariableIndex) = true + +function MOI.get(model::Virtual.Model{T}, vp::MOI.VariablePrimal, x::VI) where {T} + if !haskey(model.source, x) + error("Variable '$x' not present in the model") + + return nothing + end -function MOI.get(model::VirtualModel{T}, vp::MOI.VariablePrimal, x::VI) where {T} if isnothing(model.optimizer) return zero(T) else - s = zero(T) v = model.source[x] + s = zero(T) - for (ω, c) in expansion(v) + for (ω, c) in Virtual.expansion(v) for y in ω c *= MOI.get(model.optimizer, vp, y) end @@ -130,7 +136,7 @@ function MOI.get(model::VirtualModel{T}, vp::MOI.VariablePrimal, x::VI) where {T end end -function MOI.get(model::VirtualModel, rs::MOI.RawSolver) +function MOI.get(model::Virtual.Model, rs::MOI.RawSolver) if isnothing(model.optimizer) return nothing else diff --git a/src/attributes/virtual.jl b/src/attributes/virtual.jl deleted file mode 100644 index 50ad497a..00000000 --- a/src/attributes/virtual.jl +++ /dev/null @@ -1,19 +0,0 @@ -function MOI.is_empty(model::VirtualModel) - return MOI.is_empty(model.source_model) -end - -function MOI.empty!(model::VirtualModel) - # Source Model - MOI.empty!(model.source_model) - - # Underlying Optimizer - if !isnothing(model.optimizer) - MOI.empty!(model.optimizer) - end - - return nothing -end - -function MOI.get(::VirtualModel{T}, ::MOIB.ListOfNonstandardBridges{T}) where {T} - return Type[] -end diff --git a/src/compiler/architectures.jl b/src/compiler/architectures.jl deleted file mode 100644 index d1f1e998..00000000 --- a/src/compiler/architectures.jl +++ /dev/null @@ -1,15 +0,0 @@ -@doc raw""" - AbstractArchitecture -""" abstract type AbstractArchitecture end - -@doc raw""" - GenericArchitecture() - -This type is used to reach fallback implementations for [`AbstractArchitecture`](@ref) and, therefore, -should not have any methods directely related to it. -""" struct GenericArchitecture <: AbstractArchitecture end - -@doc raw""" -""" function infer_architecture end - -infer_architecture(::Any) = GenericArchitecture() diff --git a/src/compiler/build.jl b/src/compiler/build.jl index 77d88723..dbcdc2ad 100644 --- a/src/compiler/build.jl +++ b/src/compiler/build.jl @@ -1,21 +1,22 @@ -function toqubo_build!(model::VirtualModel{T}, arch::AbstractArchitecture) where {T} +function build!(model::Virtual.Model{T}, arch::AbstractArchitecture) where {T} # Assemble Objective Function - toqubo_hamiltonian!(model, arch) + objective_function(model, arch) # Quadratization Step - toqubo_quadratize!(model, arch) + quadratize!(model, arch) # Write to MathOptInterface - toqubo_output!(model, arch) + output!(model, arch) return nothing end -function toqubo_hamiltonian!(model::VirtualModel{T}, ::AbstractArchitecture) where {T} +function objective_function(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} 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) sizehint!(model.H, num_terms) @@ -42,43 +43,56 @@ function toqubo_hamiltonian!(model::VirtualModel{T}, ::AbstractArchitecture) whe return nothing end -function toqubo_aux(model::VirtualModel, ::Nothing, ::AbstractArchitecture)::VI - target_model = model.target_model +function aux(model::Virtual.Model{T}, ::Nothing, ::AbstractArchitecture)::VI where {T} + w = Encoding.encode!(model, nothing, Encoding.Mirror{T}())::Virtual.Variable{T} - w = MOI.add_variable(target_model) - - MOI.add_constraint(target_model, w, MOI.ZeroOne()) - - return w + return first(Virtual.target(w)) end -function toqubo_aux(model::VirtualModel, n::Integer, ::AbstractArchitecture)::Vector{VI} - target_model = model.target_model - - w = MOI.add_variables(target_model, n) - - MOI.add_constraint.(target_model, w, MOI.ZeroOne()) - - return w +function aux(model::Virtual.Model, n::Integer, arch::AbstractArchitecture)::Vector{VI} + return VI[aux(model, nothing, arch) for _ = 1:n] end -function toqubo_quadratize!(model::VirtualModel, arch::AbstractArchitecture) +function quadratize!(model::Virtual.Model, arch::AbstractArchitecture) if MOI.get(model, Attributes.Quadratize()) - method = MOI.get(model, Attributes.QuadratizationMethod()) - stable = MOI.get(model, Attributes.StableQuadratization()) - - PBO.quadratize!( - model.H, - PBO.Quadratization{method}(stable), - ) do (n::Union{Integer,Nothing} = nothing) - return toqubo_aux(model, n, arch) + method = Attributes.quadratization_method(model) + stable = Attributes.stable_quadratization(model) + + quad = PBO.Quadratization(method; stable) + + if MOI.get(model, MOI.ObjectiveSense()) === MOI.MAX_SENSE + # NOTE: Here it is necessary to invert the sign of the + # Hamiltonian since PBO adopts the minimization sense + # convention. + + # TODO: Add an in-place version of 'quadratize!' that + # provides support for maximization problems. + + # IDEA: As an easy fix, just modify 'model.H' in-place. + # Support for this is expected to be provided by PBO soon. + for (ω, c) in model.H + model.H[ω] = -c + end + + PBO.quadratize!(model.H, quad) do (n::Union{Integer,Nothing} = nothing) + return aux(model, n, arch) + end + + # Take it back to the original state + for (ω, c) in model.H + model.H[ω] = -c + end + else # === MOI.MIN_SENSE || === MOI.FEASIBILITY + PBO.quadratize!(model.H, quad) do (n::Union{Integer,Nothing} = nothing) + return aux(model, n, arch) + end end end return nothing end -function toqubo_output!(model::VirtualModel{T}, ::AbstractArchitecture) where {T} +function output!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} Q = SQT{T}[] a = SAT{T}[] b = zero(T) @@ -102,7 +116,7 @@ function toqubo_output!(model::VirtualModel{T}, ::AbstractArchitecture) where {T # 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)'. - throw(QUBOError("Quadratization failed")) + compilation_error("Quadratization failed") end end diff --git a/src/compiler/compiler.jl b/src/compiler/compiler.jl index 3a9836fe..dfe147ad 100644 --- a/src/compiler/compiler.jl +++ b/src/compiler/compiler.jl @@ -1,115 +1,82 @@ +module Compiler + +# Imports +using MathOptInterface +const MOI = MathOptInterface + +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 + +# 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} + include("analysis.jl") -include("architectures.jl") include("interface.jl") include("parse.jl") +include("setup.jl") include("variables.jl") include("objective.jl") include("constraints.jl") include("penalties.jl") include("build.jl") -# toqubo: MOI.ModelLike -> QUBO.Model -toqubo( - source::MOI.ModelLike, - arch::Union{AbstractArchitecture,Nothing} = nothing, - optimizer = nothing, -) = toqubo(Float64, source, arch; optimizer) - -function toqubo( - ::Type{T}, - source::MOI.ModelLike, - arch::Union{AbstractArchitecture,Nothing} = nothing; - optimizer = nothing, -) where {T} - model = VirtualModel{T}(optimizer) - - MOI.copy_to(model, source) - - if isnothing(arch) - arch = infer_architecture(optimizer) - end +function compile!(model::Virtual.Model) + arch = MOI.get(model, Attributes.Architecture()) - toqubo!(model, arch) + compile!(model, arch) - return model + return nothing end -function toqubo!( - model::VirtualModel{T}, - arch::AbstractArchitecture = GenericArchitecture(), -) where {T} - # Cleanup - toqubo_empty!(model, arch) - +function compile!(model::Virtual.Model{T}, arch::AbstractArchitecture) where {T} if is_qubo(model.source_model) - toqubo_copy!(model, arch) + Compiler.copy!(model, arch) return nothing end - toqubo_compile!(model, arch) - - return nothing -end - -function toqubo_copy!( - model::VirtualModel{T}, - ::AbstractArchitecture, -) where {T} - source_model = model.source_model - target_model = model.target_model - - # Map Variables - for vi in MOI.get(source_model, MOI.ListOfVariableIndices()) - encode!(model, Mirror(), vi) - end - - # Copy Objective Sense - s = MOI.get(source_model, MOI.ObjectiveSense()) - - MOI.set(target_model, MOI.ObjectiveSense(), s) + # Compiler Settings + setup!(model, arch) - # Copy Objective Function - F = MOI.get(source_model, MOI.ObjectiveFunctionType()) - f = MOI.get(source_model, MOI.ObjectiveFunction{F}()) - - MOI.set(target_model, MOI.ObjectiveFunction{F}(), f) - - return nothing -end - -function toqubo_compile!( - model::VirtualModel{T}, - arch::AbstractArchitecture = GenericArchitecture(), -) where {T} # Objective Sense - toqubo_sense!(model, arch) + sense!(model, arch) # Problem Variables - toqubo_variables!(model, arch) + variables!(model, arch) # Objective Analysis - toqubo_objective!(model, arch) + objective!(model, arch) # Add Regular Constraints - toqubo_constraints!(model, arch) + constraints!(model, arch) # Add Encoding Constraints - toqubo_encoding_constraints!(model, arch) + encoding_constraints!(model, arch) # Compute penalties - toqubo_penalties!(model, arch) + penalties!(model, arch) # Build Final Model - toqubo_build!(model, arch) + build!(model, arch) return nothing end -function toqubo_empty!( - model::VirtualModel, - ::AbstractArchitecture = GenericArchitecture(), -) +function reset!(model::Virtual.Model, ::AbstractArchitecture = GenericArchitecture()) # Model MOI.empty!(model.target_model) @@ -127,3 +94,26 @@ function toqubo_empty!( return nothing end + +function Compiler.copy!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} + # Map Variables + for vi in MOI.get(model.source_model, MOI.ListOfVariableIndices()) + Encoding.encode!(model, vi, Encoding.Mirror{T}()) + end + + # Copy Objective Sense + let s = MOI.get(model.source_model, MOI.ObjectiveSense()) + MOI.set(model.target_model, MOI.ObjectiveSense(), s) + end + + # Copy Objective Function + let F = MOI.get(model.source_model, MOI.ObjectiveFunctionType()) + f = MOI.get(model.source_model, MOI.ObjectiveFunction{F}()) + + MOI.set(model.target_model, MOI.ObjectiveFunction{F}(), f) + end + + return nothing +end + +end # module Compiler diff --git a/src/compiler/constraints.jl b/src/compiler/constraints.jl index 92bf3540..424096a8 100644 --- a/src/compiler/constraints.jl +++ b/src/compiler/constraints.jl @@ -1,22 +1,28 @@ -function toqubo_constraints!(model::VirtualModel, arch::AbstractArchitecture) - for (F, S) in MOI.get(model, MOI.ListOfConstraintTypesPresent()) - 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 = toqubo_constraint(model, f, s, arch) - - if !isnothing(g) - model.g[ci] = g - 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) + + if !isnothing(g) + model.g[ci] = g end end 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""" - toqubo_constraint( - ::VirtualModel{T}, + constraint( + ::Virtual.Model{T}, ::VI, ::Union{ MOI.ZeroOne, @@ -30,8 +36,8 @@ end This method skips bound constraints over variables. """ -function toqubo_constraint( - ::VirtualModel{T}, +function constraint( + ::Virtual.Model{T}, ::VI, ::Union{MOI.ZeroOne,MOI.Integer,MOI.Interval{T},LT{T},GT{T}}, ::AbstractArchitecture, @@ -40,8 +46,8 @@ function toqubo_constraint( end @doc raw""" - toqubo_constraint( - model::VirtualModel{T}, + constraint( + model::Virtual.Model{T}, f::SAF{T}, s::EQ{T}, ::AbstractArchitecture @@ -65,14 +71,14 @@ into ``` """ -function toqubo_constraint( - model::VirtualModel{T}, +function constraint( + model::Virtual.Model{T}, f::SAF{T}, s::EQ{T}, arch::AbstractArchitecture, ) where {T} # Scalar Affine Equality Constraint: g(x) = a'x - b = 0 - g = toqubo_parse(model, f, s, arch) + g = _parse(model, f, s, arch) PBO.discretize!(g) @@ -80,7 +86,10 @@ function toqubo_constraint( l, u = PBO.bounds(g) if u < zero(T) # Always feasible - @warn "Always-feasible constraint detected" + @warn """ + Always-feasible constraint detected: + $(f) ≤ $(s.value) + """ return nothing elseif l > zero(T) # Infeasible @warn "Infeasible constraint detected" @@ -90,8 +99,8 @@ function toqubo_constraint( end @doc raw""" - toqubo_constraint( - model::VirtualModel{T}, + constraint( + model::Virtual.Model{T}, f::SAF{T}, s::LT{T}, ::AbstractArchitecture @@ -114,14 +123,14 @@ into by adding a slack variable ``z``. """ -function toqubo_constraint( - model::VirtualModel{T}, +function constraint( + model::Virtual.Model{T}, f::SAF{T}, s::LT{T}, arch::AbstractArchitecture, ) where {T} # Scalar Affine Inequality Constraint: g(x) = a'x - b ≤ 0 - g = toqubo_parse(model, f, s, arch) + g = _parse(model, f, s, arch) PBO.discretize!(g) @@ -129,17 +138,22 @@ function toqubo_constraint( l, u = PBO.bounds(g) if u < zero(T) # Always feasible - @warn "Always-feasible constraint detected" + @warn """ + Always-feasible constraint detected: + $(f) ≤ $(s.upper) + """ return nothing elseif l > zero(T) # Infeasible @warn "Infeasible constraint detected" end # Slack Variable + x = nothing e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) - z = encode!(model, e, nothing, zero(T), abs(l)) + S = (zero(T), abs(l)) + z = Encoding.encode!(model, x, e, S) - for (ω, c) in expansion(z) + for (ω, c) in Virtual.expansion(z) g[ω] += c end @@ -147,8 +161,8 @@ function toqubo_constraint( end @doc raw""" - toqubo_constraint( - model::VirtualModel{T}, + constraint( + model::Virtual.Model{T}, f::SQF{T}, s::EQ{T}, arch::AbstractArchitecture, @@ -170,14 +184,14 @@ into ``` """ -function toqubo_constraint( - model::VirtualModel{T}, +function constraint( + model::Virtual.Model{T}, f::SQF{T}, s::EQ{T}, arch::AbstractArchitecture, ) where {T} # Scalar Quadratic Equality Constraint: g(x) = x' Q x + a' x - b = 0 - g = toqubo_parse(model, f, s, arch) + g = _parse(model, f, s, arch) PBO.discretize!(g) @@ -185,7 +199,10 @@ function toqubo_constraint( l, u = PBO.bounds(g) if u < zero(T) # Always feasible - @warn "Always-feasible constraint detected" + @warn """ + Always-feasible constraint detected: + $(f) ≤ $(s.value) + """ return nothing elseif l > zero(T) # Infeasible @warn "Infeasible constraint detected" @@ -199,8 +216,8 @@ end @doc raw""" - toqubo_constraint( - model::VirtualModel{T}, + constraint( + model::Virtual.Model{T}, f::SQF{T}, s::LT{T}, arch::AbstractArchitecture, @@ -223,32 +240,37 @@ into by adding a slack variable ``z``. """ -function toqubo_constraint( - model::VirtualModel{T}, +function constraint( + model::Virtual.Model{T}, f::SQF{T}, s::LT{T}, arch::AbstractArchitecture, ) where {T} # Scalar Quadratic Inequality Constraint: g(x) = x' Q x + a' x - b ≤ 0 - g = toqubo_parse(model, f, s, arch) - + g = _parse(model, f, s, arch) + PBO.discretize!(g) # Bounds & Slack Variable l, u = PBO.bounds(g) if u < zero(T) # Always feasible - @warn "Always-feasible constraint detected" + @warn """ + Always-feasible constraint detected: + $(f) ≤ $(s.upper) + """ return nothing elseif l > zero(T) # Infeasible @warn "Infeasible constraint detected" end # Slack Variable + x = nothing e = MOI.get(model, Attributes.DefaultVariableEncodingMethod()) - z = encode!(model, e, nothing, zero(T), abs(l)) + S = (zero(T), abs(l)) + z = Encoding.encode!(model, x, e, S) - for (ω, c) in expansion(z) + for (ω, c) in Virtual.expansion(z) g[ω] += c end @@ -259,15 +281,15 @@ function toqubo_constraint( end @doc raw""" - toqubo_constraint( - model::VirtualModel{T}, + constraint( + model::Virtual.Model{T}, x::MOI.VectorOfVariables, ::MOI.SOS1{T}, ::AbstractArchitecture, ) where {T} """ -function toqubo_constraint( - model::VirtualModel{T}, +function constraint( + model::Virtual.Model{T}, x::MOI.VectorOfVariables, ::MOI.SOS1{T}, ::AbstractArchitecture, @@ -277,19 +299,22 @@ function toqubo_constraint( for xi in x.variables vi = model.source[xi] - - @assert encoding(vi) isa Mirror "Currently, SOS1 only supports binary variables" - for (ωi, _) in expansion(vi) + if !(encoding(vi) isa Mirror) + error("Currently, ToQUBO only supports SOS1 on binary variables") + end + + for (ωi, _) in Virtual.expansion(vi) g[ωi] = one(T) end end # Slack variable - e = Mirror() - z = encode!(model, e, nothing) + x = nothing + e = Encoding.Mirror{T}() + z = Encoding.encode!(model, x, e) - for (ω, c) in expansion(z) + for (ω, c) in Virtual.expansion(z) g[ω] += c end @@ -298,21 +323,20 @@ function toqubo_constraint( return g^2 end -function toqubo_encoding_constraints!( - model::VirtualModel{T}, - ::AbstractArchitecture, -) where {T} +function encoding_constraints!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} for v in model.variables - if is_aux(v) + x = Virtual.source(v) + + if isnothing(x) continue end - - h = penaltyfn(v) - if !isnothing(h) - model.h[source(v)] = h + χ = Virtual.penaltyfn(v) + + if !isnothing(χ) + model.h[x] = χ end end return nothing -end \ No newline at end of file +end diff --git a/src/compiler/interface.jl b/src/compiler/interface.jl index 434fef92..a7a4e5d6 100644 --- a/src/compiler/interface.jl +++ b/src/compiler/interface.jl @@ -8,88 +8,89 @@ For it to be true, a few conditions must be met: 2. No other constraints are allowed 3. The objective function must be of type `MOI.ScalarQuadraticFunction`, `MOI.ScalarAffineFunction` or `MOI.VariableIndex` 4. The objective sense must be either `MOI.MIN_SENSE` or `MOI.MAX_SENSE` -""" function isqubo end +""" +function isqubo end @doc raw""" - toqubo( - [T=Float64,] - source::MOI.ModelLike, - ::AbstractArchitecture; - optimizer::Union{Nothing, Type{<:MOI.AbstractOptimizer}} = nothing - ) - -Low-level interface to create a `::VirtualModel{T}` from `::MOI.ModelLike` instance. -If provided, an `::MOI.AbstractOptimizer` is attached to the model. -""" function toqubo end - -@doc raw""" - toqubo!(model::VirtualModel{T}, ::AbstractArchitecture) where {T} -""" function toqubo! end + setup!(model::Virtual.Model, ::AbstractArchitecture) +""" +function setup! end @doc raw""" - toqubo_sense!(model::VirtualModel, ::AbstractArchitecture) where {T} + sense!(model::Virtual.Model, ::AbstractArchitecture) Copies `MOI.ObjectiveSense` from `model.source_model` to `model.target_model`. -""" function toqubo_sense! end +""" +function sense! end @doc raw""" - toqubo_variables!(model::VirtualModel{T}) where {T} -""" function toqubo_variables! end + variables!(model::Virtual.Model{T}) where {T} +""" +function variables! end @doc raw""" - toqubo_variables(model::VirtualModel{T}) where {T} -""" function toqubo_variable end + variable!(model::Virtual.Model{T}) where {T} +""" +function variable! end @doc raw""" - toqubo_objective!(model::VirtualModel, ::AbstractArchitecture) -""" function toqubo_objective! end + objective!(model::Virtual.Model, ::AbstractArchitecture) +""" +function objective! end @doc raw""" - toqubo_objective(model::VirtualModel, F::VI, ::AbstractArchitecture) - toqubo_objective(model::VirtualModel{T}, F::SAF{T}, ::AbstractArchitecture) where {T} - toqubo_objective(model::VirtualModel{T}, F::SQF{T}, ::AbstractArchitecture) where {T} -""" function toqubo_objective end + constraints!(model::Virtual.Model, ::AbstractArchitecture) +""" +function constraints! end @doc raw""" - toqubo_constraints!(model::VirtualModel, ::AbstractArchitecture) -""" function toqubo_constraints! end + constraint -# """ -# toqubo_constraint +Returns the pseudo-boolean function associated to a given constraint from the source model. +""" +function constraint end -# Returns the pseudo-boolean function associated to a given constraint from the source model. -# """ -function toqubo_constraint end +@doc raw""" +""" +function _parse end @doc raw""" - toqubo_parse!( - model::VirtualModel{T}, + parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::MOI.AbstractFunction, - arch::AbstractArchitectur + arch::AbstractArchitecture ) where {T} Parses the given MOI function `f` into PBF `g`. -""" function toqubo_parse! end +""" +function parse! end @doc raw""" - toqubo_penalties!(model::VirtualModel, ::AbstractArchitecture) -""" function toqubo_penalties! end + penalties!(model::Virtual.Model, arch::AbstractArchitecture) +""" +function penalties! end @doc raw""" -""" function toqubo_penalty end + build!(model::Virtual.Model, arch::AbstractArchitecture) +""" +function build! end @doc raw""" - toqubo_build!(model::VirtualModel, ::AbstractArchitecture) -""" function toqubo_build! end + quadratize!(model::Virtual.Model, arch::AbstractArchitecture) -@doc raw""" - toqubo_quadratize!(model::VirtualModel, arch::AbstractArchitecture) +Quadratizes the objective function from a model. +""" +function quadratize! end -Quadratizes the objective function from a model +@doc raw""" + reset!(model::Virtual.Model, arch::AbstractArchitecture) -""" function toqubo_quadratize! end +""" +function reset! end @doc raw""" - toqubo_empty!(model::VirtualModel, ::AbstractArchitecture) -""" function toqubo_empty! end \ No newline at end of file + copy!(model::Virtual.Model, arch::AbstractArchitecture) + +""" +function copy! end diff --git a/src/compiler/objective.jl b/src/compiler/objective.jl index 3d34aae2..629e5d3f 100644 --- a/src/compiler/objective.jl +++ b/src/compiler/objective.jl @@ -1,4 +1,4 @@ -function toqubo_sense!(model::VirtualModel, ::AbstractArchitecture) +function sense!(model::Virtual.Model, ::AbstractArchitecture) if MOI.get(model, MOI.ObjectiveSense()) === MOI.MAX_SENSE MOI.set(model.target_model, MOI.ObjectiveSense(), MOI.MAX_SENSE) else @@ -9,11 +9,11 @@ function toqubo_sense!(model::VirtualModel, ::AbstractArchitecture) return nothing end -function toqubo_objective!(model::VirtualModel, arch::AbstractArchitecture) +function objective!(model::Virtual.Model, arch::AbstractArchitecture) F = MOI.get(model, MOI.ObjectiveFunctionType()) f = MOI.get(model, MOI.ObjectiveFunction{F}()) - toqubo_parse!(model, model.f, f, arch) + parse!(model, model.f, f, arch) return nothing end diff --git a/src/compiler/parse.jl b/src/compiler/parse.jl index 2952b2cd..4060e4b6 100644 --- a/src/compiler/parse.jl +++ b/src/compiler/parse.jl @@ -1,18 +1,18 @@ -function toqubo_parse( - model::VirtualModel{T}, +function _parse( + model::Virtual.Model{T}, f::MOI.AbstractFunction, s::MOI.AbstractSet, arch::AbstractArchitecture, ) where {T} g = PBO.PBF{VI,T}() - toqubo_parse!(model, g, f, s, arch) + parse!(model, g, f, s, arch) return g end -function toqubo_parse!( - model::VirtualModel{T}, +function parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, vi::VI, ::AbstractArchitecture, @@ -26,8 +26,8 @@ function toqubo_parse!( return nothing end -function toqubo_parse!( - model::VirtualModel{T}, +function parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::SAF{T}, ::AbstractArchitecture, @@ -51,36 +51,36 @@ function toqubo_parse!( return nothing end -function toqubo_parse!( - model::VirtualModel{T}, +function parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::SAF{T}, s::EQ{T}, arch::AbstractArchitecture, ) where {T} - toqubo_parse!(model, g, f, arch) + parse!(model, g, f, arch) g[nothing] -= s.value return nothing end -function toqubo_parse!( - model::VirtualModel{T}, +function parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::SAF{T}, s::LT{T}, arch::AbstractArchitecture, ) where {T} - toqubo_parse!(model, g, f, arch) + parse!(model, g, f, arch) g[nothing] -= s.upper return nothing end -function toqubo_parse!( - model::VirtualModel{T}, +function parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::SQF{T}, ::AbstractArchitecture, @@ -125,30 +125,30 @@ function toqubo_parse!( return nothing end -function toqubo_parse!( - model::VirtualModel{T}, +function parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::SQF{T}, s::EQ{T}, arch::AbstractArchitecture, ) where {T} - toqubo_parse!(model, g, f, arch) + parse!(model, g, f, arch) g[nothing] -= s.value return nothing end -function toqubo_parse!( - model::VirtualModel{T}, +function parse!( + model::Virtual.Model{T}, g::PBO.PBF{VI,T}, f::SQF{T}, s::LT{T}, arch::AbstractArchitecture, ) where {T} - toqubo_parse!(model, g, f, arch) + parse!(model, g, f, arch) g[nothing] -= s.upper return nothing -end \ No newline at end of file +end diff --git a/src/compiler/penalties.jl b/src/compiler/penalties.jl index d7b3225d..2209df4c 100644 --- a/src/compiler/penalties.jl +++ b/src/compiler/penalties.jl @@ -1,21 +1,31 @@ -function toqubo_penalties!(model::VirtualModel{T}, ::AbstractArchitecture) where {T} - # Invert Sign +function penalties!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} + # Adjust Sign s = MOI.get(model, MOI.ObjectiveSense()) === MOI.MAX_SENSE ? -1 : 1 β = one(T) # TODO: This should be made a parameter too? Yes! - δ = PBO.gap(model.f) + δ = PBO.maxgap(model.f) for (ci, g) in model.g - ϵ = PBO.sharpness(g) + ρ = Attributes.constraint_encoding_penalty_hint(model, ci) - model.ρ[ci] = s * (δ / ϵ + β) + if isnothing(ρ) + ϵ = PBO.mingap(g) + ρ = s * (δ / ϵ + β) + end + + model.ρ[ci] = ρ end for (vi, h) in model.h - ϵ = PBO.sharpness(h) + θ = Attributes.variable_encoding_penalty_hint(model, vi) + + if isnothing(θ) + ϵ = PBO.mingap(h) + θ = s * (δ / ϵ + β) + end - model.θ[vi] = s * (δ / ϵ + β) + model.θ[vi] = θ end return nothing -end \ No newline at end of file +end diff --git a/src/compiler/setup.jl b/src/compiler/setup.jl new file mode 100644 index 00000000..506f0969 --- /dev/null +++ b/src/compiler/setup.jl @@ -0,0 +1,9 @@ +function setup!(model::Virtual.Model, arch::AbstractArchitecture) + level = MOI.get(model, Attributes.Optimization()) + + if level >= 1 + + end + + return nothing +end \ No newline at end of file diff --git a/src/compiler/variables.jl b/src/compiler/variables.jl index 9c1296fa..06235b39 100644 --- a/src/compiler/variables.jl +++ b/src/compiler/variables.jl @@ -1,4 +1,4 @@ -function toqubo_variables!(model::VirtualModel{T}, ::AbstractArchitecture) where {T} +function variables!(model::Virtual.Model{T}, ::AbstractArchitecture) where {T} # Set of all source variables Ω = Vector{VI}(MOI.get(model, MOI.ListOfVariableIndices())) @@ -71,56 +71,74 @@ function toqubo_variables!(model::VirtualModel{T}, ::AbstractArchitecture) where end end - # Discretize Real Ones - for (x, (a, b)) in ℝ - if isnothing(a) || isnothing(b) - error("Unbounded variable $(x) ∈ ℝ") - else - # TODO: Solve this bit-guessing magic??? - # IDEA: - # Let x̂ ~ U[a, b], K = 2ᴺ, γ = [a, b] - # 𝔼[|xᵢ - x̂|] = ∫ᵧ |xᵢ - x̂| f(x̂) dx̂ - # = 1 / |b - a| ∫ᵧ |xᵢ - x̂| dx̂ - # = |b - a| / 4 (K - 1) - # - # 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 = MOI.get(model, Attributes.VariableEncodingMethod(), x) - n = MOI.get(model, Attributes.VariableEncodingBits(), x) - - if !isnothing(n) - encode!(model, e, x, a, b, n) - else - τ = MOI.get(model, Attributes.VariableEncodingATol(), x) - encode!(model, e, x, a, b, τ) - end - end - end + # Encode Variables + if Attributes.stable_compilation(model) + sort!(Ω; by = x -> x.value) end - # Discretize Integer Variables - for (x, (a, b)) in ℤ - if isnothing(a) || isnothing(b) - error("Unbounded variable $(x) ∈ ℤ") - else - let - e = MOI.get(model, Attributes.VariableEncodingMethod(), x) - encode!(model, e, x, a, b) - end + for x in Ω + if haskey(ℤ, x) + variable_ℤ!(model, x, ℤ[x]) + elseif haskey(ℝ, x) + variable_ℝ!(model, x, ℝ[x]) + else # x ∈ 𝔹 + variable_𝔹!(model, x) end end - # Mirror Boolean Variables - for x in 𝔹 - encode!(model, Mirror(), x) + return nothing +end + +function variable_𝔹!(model::Virtual.Model{T}, x::VI) where {T} + Encoding.encode!(model, x, Mirror{T}()) + + return nothing +end + +function variable_ℤ!(model::Virtual.Model{T}, x::VI, (a, b)::Tuple{T,T}) where {T} + if isnothing(a) || isnothing(b) + error("Unbounded variable $(x) ∈ ℤ") + else + let e = Attributes.variable_encoding_method(model, x) + S = (a, b) + + Encoding.encode!(model, x, e, S) + end end return nothing end -function toqubo_variable(model::VirtualModel, ::AbstractArchitecture) end \ No newline at end of file +function variable_ℝ!(model::Virtual.Model{T}, x::VI, (a, b)::Tuple{T,T}) where {T} + if isnothing(a) || isnothing(b) + error("Unbounded variable $(x) ∈ ℝ") + else + # TODO: Solve this bit-guessing magic??? (DONE) + # IDEA: + # Let x̂ ~ U[a, b], K = 2ᴺ, γ = [a, b] + # 𝔼[|xᵢ - x̂|] = ∫ᵧ |xᵢ - x̂| f(x̂) dx̂ + # = 1 / |b - a| ∫ᵧ |xᵢ - x̂| dx̂ + # = |b - a| / 4 (K - 1) + # + # 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) + S = (a, b) + + if !isnothing(n) + Encoding.encode!(model, x, e, S, n) + else + tol = Attributes.variable_encoding_atol(model, x) + + Encoding.encode!(model, x, e, S; tol) + end + end + end + + return nothing +end diff --git a/src/encoding/constraints/constraints.jl b/src/encoding/constraints/constraints.jl new file mode 100644 index 00000000..e69de29b diff --git a/src/encoding/encoding.jl b/src/encoding/encoding.jl new file mode 100644 index 00000000..8bacb40f --- /dev/null +++ b/src/encoding/encoding.jl @@ -0,0 +1,22 @@ +module Encoding + +import MathOptInterface as MOI +import PseudoBooleanOptimization as PBO + +const VI = MOI.VariableIndex + +include("interface.jl") +include("extras.jl") + +include("variables/interval/mirror.jl") +include("variables/interval/bounded.jl") +include("variables/interval/unary.jl") +include("variables/interval/binary.jl") +include("variables/interval/arithmetic.jl") + +include("variables/set/one_hot.jl") +include("variables/set/domain_wall.jl") + +# include("constraints/constraints.jl") + +end \ No newline at end of file diff --git a/src/encoding/extras.jl b/src/encoding/extras.jl new file mode 100644 index 00000000..f39ceb2a --- /dev/null +++ b/src/encoding/extras.jl @@ -0,0 +1,7 @@ +function integer_interval((a, b)::Tuple{T,T}) where {T} + if a < b + return (ceil(a), floor(b)) + else + return (ceil(b), floor(a)) + end +end diff --git a/src/encoding/interface.jl b/src/encoding/interface.jl new file mode 100644 index 00000000..0e013451 --- /dev/null +++ b/src/encoding/interface.jl @@ -0,0 +1,51 @@ +@doc raw""" + VariableEncodingMethod + +Abstract type for variable encoding methods. +""" +abstract type VariableEncodingMethod end + +Base.broadcastable(e::E) where {E<:VariableEncodingMethod} = Ref(e) + +@doc raw""" + encode(var, e::VariableEncodingMethod, x::Union{VI,Nothing}, S) +""" +function encode end + +@doc raw""" + encode!(target, source...) +""" +function encode! end + +@doc raw""" + encodes(f::AbstractPBF, S::Tuple{T,T}, tol::T) where {T} +""" +function encodes end + +@doc raw""" + encoding_bits(e::VariableEncodingMethod, S::Tuple{T,T}, tol::T) where {T} +""" +function encoding_bits end + +@doc raw""" + SetVariableEncodingMethod + +Abstract type for methods that encode variables over an arbitrary set. +""" +abstract type SetVariableEncodingMethod <: VariableEncodingMethod end + +@doc raw""" + encoding_points(e::SetVariableEncodingMethod, S::Tuple{T,T}, tol::T) where {T} +""" +function encoding_points end + +@doc raw""" + IntervalVariableEncodingMethod + +Abstract type for methods that encode variables using a linear function, e.g., + +```math +\xi(\mathbf{y}) = \beta + \sum_{i = 1}^{n} \gamma_{i} y_{i} +``` +""" +abstract type IntervalVariableEncodingMethod <: VariableEncodingMethod end diff --git a/src/encoding/variables/interval/arithmetic.jl b/src/encoding/variables/interval/arithmetic.jl new file mode 100644 index 00000000..9bba9ecb --- /dev/null +++ b/src/encoding/variables/interval/arithmetic.jl @@ -0,0 +1,95 @@ +@doc raw""" + Arithmetic{T}() + +## Integer +Let ``x \in [a, b] \subset \mathbb{Z}``, ``n = \left\lceil{ \frac{1}{2} {\sqrt{1 + 8 (b - a)}} - \frac{1}{2} }\right\rceil`` and ``\mathbf{y} \in \mathbb{B}^{n}``. + +```math +\xi{[a, b]}(\mathbf{y}) = a + \left( {b - a - \frac{n (n - 1)}{2}} \right) y_{n} + \sum_{j = 1}^{n - 1} j y_{j} +``` + +## Real +Given ``n \in \mathbb{N}`` for ``x \in [a, b] \subset \mathbb{R}``, + +```math +\xi{[a, b]}(\mathbf{y}) = a + \frac{b - a}{n (n + 1)} \sum_{j = 1}^{n} j y_{j} +``` + +### Representation error + +Given ``\tau > 0``, for the expected encoding error to be less than or equal to ``\tau``, at least + +```math +n \ge \frac{1}{2} \left[ 1 + \sqrt{3 + \frac{(b - a)}{2 \tau})} \right] +``` +""" +struct Arithmetic{T} <: IntervalVariableEncodingMethod end + +Arithmetic() = Arithmetic{Float64}() + +@doc raw""" + encode(var::Function, ::Arithmetic{T}, S::Tuple{T,T}) where {T} +""" +function encode(var::Function, e::Arithmetic{T}, S::Tuple{T,T}; tol::Union{T,Nothing} = nothing) where {T} + isnothing(tol) || return encode(var, e, S, nothing; tol) + + a, b = integer_interval(S) + + @assert b > a + + M = trunc(Int, b - a) + N = ceil(Int, (sqrt(1 + 8M) - 1) / 2) + + if N == 0 + y = VI[] + ξ = PBO.PBF{VI,T}((a + b) / 2) + else + y = var(N)::Vector{VI} + ξ = PBO.PBF{VI,T}( + [ + a + [y[i] => i for i = 1:N-1] + y[N] => M - N * (N - 1) / 2 + ], + ) + end + + return (y, ξ, nothing) # No penalty function +end + +function encoding_bits(::Arithmetic{T}, S::Tuple{T,T}, tol::T) where {T} + @assert tol > zero(T) + + a, b = S + + return ceil(Int, (1 + sqrt(3 + abs(b - a) / 2tol)) / 2) +end + +# Real (fixed) +function encode( + var::Function, + e::Arithmetic{T}, + S::Tuple{T,T}, + n::Union{Integer,Nothing}; + tol::Union{T,Nothing} = nothing, +) where {T} + @assert !(isnothing(tol) && isnothing(n)) + + if isnothing(n) + n = encoding_bits(e, S, tol) + end + + @assert n >= 0 + + a, b = S + + if n == 0 + y = Vector{VI}() + ξ = PBO.PBF{VI,T}((a + b) / 2) + else + y = var(n)::Vector{VI} + ξ = PBO.PBF{VI,T}([a; [y[i] => 2i * (b - a) / (n * (n + 1)) for i = 1:n]]) + end + + return (y, ξ, nothing) # No penalty function +end diff --git a/src/encoding/variables/interval/binary.jl b/src/encoding/variables/interval/binary.jl new file mode 100644 index 00000000..9777dc55 --- /dev/null +++ b/src/encoding/variables/interval/binary.jl @@ -0,0 +1,97 @@ +@doc raw""" + Binary{T}() + +## Integer + +Let ``x \in [a, b] \subset \mathbb{Z}``, ``n = \left\lceil \log_{2}(b - a) + 1 \right\rceil`` and ``\mathbf{y} \in \mathbb{B}^{n}``. + +```math +\xi{[a, b]}(\mathbf{y}) = a + \left(b - a - 2^{n - 1} + 1\right) y_{n} + \sum_{j = 1}^{n - 1} 2^{j - 1} y_{j} +``` + +## Real + +Given ``n \in \mathbb{N}`` for ``x \in [a, b] \subset \mathbb{R}``, + +```math +\xi{[a, b]}(\mathbf{y}) = a + \frac{b - a}{2^{n} - 1} \sum_{j = 1}^{n} 2^{j - 1} y_{j} +``` + +### Representation error + +Given ``\tau > 0``, for the expected encoding error to be less than or equal to ``\tau``, at least + +```math +n \ge \log_{2} \left[1 + \frac{b - a}{4 \tau}\right] +``` + +binary variables become necessary. +""" +struct Binary{T} <: IntervalVariableEncodingMethod end + +Binary() = Binary{Float64}() + +# Integer +function encode(var::Function, e::Binary{T}, S::Tuple{T,T}; tol::Union{T,Nothing} = nothing) where {T} + isnothing(tol) || return encode(var, e, S, nothing; tol) + + a, b = integer_interval(S) + + @assert b > a + + M = trunc(Int, b - a) + N = ceil(Int, log2(M + 1)) + + if N == 0 + y = VI[] + ξ = PBO.PBF{VI,T}((a + b) / 2) + else + y = var(N)::Vector{VI} + ξ = PBO.PBF{VI,T}( + [ + a + [y[i] => 2^(i - 1) for i = 1:N-1] + y[N] => M - 2^(N - 1) + 1 + ], + ) + end + + return (y, ξ, nothing) # No penalty function +end + +function encoding_bits(::Binary{T}, S::Tuple{T,T}, tol::T) where {T} + @assert tol > zero(T) + + a, b = S + + return ceil(Int, log2(1 + abs(b - a) / 4tol)) +end + +# Real +function encode( + var::Function, + e::Binary{T}, + S::Tuple{T,T}, + n::Union{Integer,Nothing}; + tol::Union{T,Nothing} = nothing, +) where {T} + @assert !(isnothing(tol) && isnothing(n)) + + if isnothing(n) + n = encoding_bits(e, S, tol) + end + + @assert n >= 0 + + a, b = S + + if n == 0 + y = Vector{VI}() + ξ = PBO.PBF{VI,T}((a + b) / 2) + else + y = var(n)::Vector{VI} + ξ = PBO.PBF{VI,T}([a; [y[i] => (b - a) * 2^(i - 1) / (2^n - 1) for i = 1:n]]) + end + + return (y, ξ, nothing) # No penalty function +end diff --git a/src/encoding/variables/interval/bounded.jl b/src/encoding/variables/interval/bounded.jl new file mode 100644 index 00000000..b6682b42 --- /dev/null +++ b/src/encoding/variables/interval/bounded.jl @@ -0,0 +1,117 @@ +@doc raw""" + Bounded{E,T}(μ::T) where {E<:Encoding,T} + +The bounded-coefficient encoding method[^Karimi2019] consists in limiting the +magnitude of the coefficients in the encoding expansion to a parameter ``\mu``. +This can be applied to the [`Unary`](@ref), [`Binary`](@ref) and [`Arithmetic`](@ref) +encoding methods. + +Let ``\xi[a, b] : \mathbb{B}^{n} \to [a, b]`` be an encoding function over the +closed interval ``[a, b]``. +The bounded-coefficient encoding function given ``\mu`` is defined as + +```math +\xi_{\mu}[a, b] = \xi[0, \delta](y_{1}, \dots, y_{k}) + \sum_{j = k + 1}^{r} \mu y{j} +``` + +[^Karimi2019]: + Karimi, S. & Ronagh, P. **Practical integer-to-binary mapping for quantum annealers**. *Quantum Inf Process 18, 94* (2019). [{doi}](https://doi.org/10.1007/s11128-019-2213-x) +``` + +""" +struct Bounded{E<:IntervalVariableEncodingMethod,T} <: IntervalVariableEncodingMethod + e::E + μ::T + + function Bounded(e::E, μ::T) where {E<:IntervalVariableEncodingMethod,T} + @assert μ > zero(T) + + return new{E,T}(e, μ) + end +end + +# Integer +function encode( + var::Function, + e::Bounded{E,T}, + S::Tuple{T,T}; + tol::Union{T,Nothing} = nothing, +) where {E<:IntervalVariableEncodingMethod,T} + isnothing(tol) || return encode(var, e, S, nothing; tol) + + a, b = integer_interval(S) + + m = floor(Int, e.μ) + n = trunc(Int, b - a) + r = n ÷ m + d = n - r * m + + Δ::Tuple{T,T} = (0, d + r) + + y0, ξ0, χ0 = encode(var, e.e, Δ) + + @assert isnothing(χ0) + + y1 = var(r - 1)::Vector{VI} + ξ1 = PBO.PBF{VI,T}(y1[i] => m for i = 1:(r-1)) + + y = [y0; y1] + ξ = a + ξ0 + ξ1 + + return (y, ξ, nothing) # No penalty function +end + +@doc raw""" + encode(var::Function, e::Bounded{E,T}, S::Tuple{T,T}, n::integer) where {E<:IntervalVariableEncodingMethod,T} + + +""" +function encode( + var::Function, + e::Bounded{E,T}, + S::Tuple{T,T}, + n::Union{Integer,Nothing}; + tol::Union{T,Nothing} = nothing, +) where {E<:IntervalVariableEncodingMethod,T} + @assert !(isnothing(tol) && isnothing(n)) + + a, b = S + + ℓ = abs(b - a) + r = floor(Int, ℓ / e.μ - 1) + δ = ℓ - r * e.μ + + Δ::Tuple{T,T} = (0, δ) + + yδ, ξδ, χδ = if isnothing(n) + encode(var, e.e, Δ, nothing; tol) + else + encode(var, e.e, Δ, n - r; tol) + end + + @assert isnothing(χδ) + + yμ = var(r)::Vector{VI} + ξμ = PBO.PBF{VI,T}(yμ[i] => e.μ for i = 1:r) + + y = [yδ; yμ] + ξ = a + ξδ + ξμ + + return (y, ξ, nothing) # No penalty function +end + +function encoding_bits( + e::Bounded{E,T}, + S::Tuple{T,T}, + tol::T, +) where {T,E<:IntervalVariableEncodingMethod} + a, b = S + + ℓ = abs(b - a) + r = floor(Int, ℓ / e.μ - 1) + δ = ℓ - r * e.μ + + Δ::Tuple{T,T} = (0, δ) + + return r + encoding_bits(e.e, Δ, tol) +end diff --git a/src/encoding/variables/interval/mirror.jl b/src/encoding/variables/interval/mirror.jl new file mode 100644 index 00000000..b1f9c04b --- /dev/null +++ b/src/encoding/variables/interval/mirror.jl @@ -0,0 +1,15 @@ +@doc raw""" + Mirror() + +Simply mirrors a binary variable ``x \in \mathbb{B}`` with a twin variable ``y \in \mathbb{B}``. +""" +struct Mirror{T} <: IntervalVariableEncodingMethod end + +Mirror() = Mirror{Float64}() + +function encode(var::Function, ::Mirror{T}) where {T} + y = var()::VI + f = PBO.PBF{VI,T}(y) + + return (VI[y], f, nothing) +end diff --git a/src/encoding/variables/interval/unary.jl b/src/encoding/variables/interval/unary.jl new file mode 100644 index 00000000..82e29952 --- /dev/null +++ b/src/encoding/variables/interval/unary.jl @@ -0,0 +1,95 @@ +@doc raw""" + Unary{T}() + +## Integer +Let ``x \in [a, b] \subset \mathbb{Z}``, ``n = b - a`` and ``\mathbf{y} \in \mathbb{B}^{n}``. + +```math +\xi{[a, b]}(\mathbf{y}) = a + \sum_{j = 1}^{b - a} y_{j} +``` + +## Real +Given ``n \in \mathbb{N}`` for ``x \in [a, b] \subset \mathbb{R}``, + +```math +\xi{[a, b]}(\mathbf{y}) = a + \frac{b - a}{n} \sum_{j = 1}^{n} y_{j} +``` + +### Representation error + +Given ``\tau > 0``, for the expected encoding error to be less than or equal to ``\tau``, at least + +```math +n \ge 1 + \frac{b - a}{4 \tau} +``` + +binary variables become necessary. +""" +struct Unary{T} <: IntervalVariableEncodingMethod end + +Unary() = Unary{Float64}() + +@doc raw""" + encode(var::Function, ::Unary{T}, S::Tuple{T,T}) where {T} + +Given ``S = [a, b] \subset \mathbb{Z}``, ``a < b``, let ``n = b - a`` and ``\mathbf{y} \in \mathbb{B}^{n}``. + +```math +\xi{[a, b]}(\mathbf{y}) = a + \sum_{j = 1}^{b - a} y_{j} +``` +""" +function encode(var::Function, e::Unary{T}, S::Tuple{T,T}; tol::Union{T,Nothing} = nothing) where {T} + isnothing(tol) || return encode(var, e, S, nothing; tol) + + a, b = integer_interval(S) + + @assert b > a + + N = trunc(Int, b - a) + + y = var(N)::Vector{VI} + ξ = if N == 0 + PBO.PBF{VI,T}((a + b) / 2) + else + PBO.PBF{VI,T}([a; [y[i] => one(T) for i = 1:N]]) + end + + return (y, ξ, nothing) # No penalty function +end + +function encoding_bits(::Unary{T}, S::Tuple{T,T}, tol::T) where {T} + @assert tol > zero(T) + + a, b = S + + return ceil(Int, (1 + abs(b - a) / 4tol)) +end + +# Real +function encode( + var::Function, + e::Unary{T}, + S::Tuple{T,T}, + n::Union{Integer,Nothing}; + tol::Union{T,Nothing} = nothing, +) where {T} + @assert !(isnothing(tol) && isnothing(n)) + + if isnothing(n) + n = encoding_bits(e, S, tol) + end + + @assert n >= 0 + + a, b = S + + if n == 0 + y = Vector{VI}() + ξ = PBO.PBF{VI,T}((a + b) / 2) + else + y = var(n)::Vector{VI} + ξ = PBO.PBF{VI,T}([a; [y[i] => (b - a) / n for i = 1:n]]) + end + + return (y, ξ, nothing) # No penalty function +end diff --git a/src/encoding/variables/set/domain_wall.jl b/src/encoding/variables/set/domain_wall.jl new file mode 100644 index 00000000..1c231f4e --- /dev/null +++ b/src/encoding/variables/set/domain_wall.jl @@ -0,0 +1,98 @@ +@doc raw""" + DomainWall{T}() + +The Domain Wall[^Chancellor2019] encoding method is a sequential approach that requires ``n - 1`` bits to represent ``n`` distinct values. + +```math +\xi{[\set{\gamma_{j}}_{j \in [n]}]}(\mathbf{y}) = \sum_{j = 1}^{n} \gamma_{j} (y_{j} - y_{j + 1}) ~\textrm{s.t.}~ \sum_{j = 1}^{n} y_{j} \oplus y_{j + 1} = 1, y_{1} = 1, y_{n + 1} = 0 +``` + +where ``\mathbf{y} \in \mathbb{B}^{n + 1}``. + +[^Chancellor2019]: + Nicholas Chancellor, **Domain wall encoding of discrete variables for quantum annealing and QAOA**, *Quantum Science Technology 4*, 2019. +""" +struct DomainWall{T} <: SetVariableEncodingMethod end + +# Arbitrary set +function encode(var::Function, e::DomainWall{T}, γ::AbstractVector{T}) where {T} + p = length(γ) + n = encoding_bits(e, p) + + if p == 0 + y = VI[] + ξ = PBO.PBF{VI,T}() + χ = nothing + elseif p == 1 + y = Vector{VI}() + ξ = PBO.PBF{VI,T}(γ[1]) + χ = nothing + else + y = var(n)::Vector{VI} + ξ = PBO.PBF{VI,T}([γ[1]; [y[i] => (γ[i+1] - γ[i]) for i = 1:n]]) + χ = PBO.PBF{VI,T}([[y[i] => 2 for i = 2:n]; [(y[i], y[i+1]) => -2 for i = 1:(n-1)]]) + end + + return (y, ξ, χ) +end + +# Integer +function encode( + var::Function, + e::E, + S::Tuple{T,T}; + tol::Union{T,Nothing} = nothing, +) where {T,E<:DomainWall{T}} + isnothing(tol) || return encode(var, e, S, nothing; tol) + + a, b = integer_interval(S) + + return encode(var, e, collect(a:b)) +end + +function encoding_points( + ::E, + S::Tuple{T,T}, + tol::T, +) where {T,E<:DomainWall{T}} + a, b = S + + return ceil(Int, ((b - a)^2 / 4tol) + 1) +end + +function encoding_points(::DomainWall, n::Integer) + return n + 1 +end + +function encoding_bits(e::E, S::Tuple{T,T}, tol::T) where {T,E<:DomainWall{T}} + p = encoding_points(e, S, tol) + + return encoding_bits(e, p) +end + +function encoding_bits(::DomainWall, p::Integer) + return p - 1 +end + +# Real +function encode( + var::Function, + e::E, + S::Tuple{T,T}, + n::Union{Integer,Nothing}; + tol::Union{T,Nothing} = nothing, +) where {T,E<:DomainWall{T}} + @assert !(isnothing(n) && isnothing(tol)) + + p = if isnothing(n) + encoding_points(e, S, tol) + else + encoding_points(e, n) + end + + a, b = S + + Γ = collect(range(a, b; length = p)) + + return encode(var, e, Γ) +end diff --git a/src/encoding/variables/set/one_hot.jl b/src/encoding/variables/set/one_hot.jl new file mode 100644 index 00000000..d3b0546d --- /dev/null +++ b/src/encoding/variables/set/one_hot.jl @@ -0,0 +1,103 @@ + +@doc raw""" + OneHot{T}() + +The one-hot encoding is a linear technique used to represent a variable ``x \in \set{\gamma_{j}}_{j \in [n]}``. + +The associated encoding function is combined with a constraint assuring that only one and exactly one of the expansion's variables ``y_{j}`` is activated at a time. + +```math +\xi[\set{\gamma_{j}}_{j \in [n]}](\mathbf{y}) = \sum_{j = 1}^{n} \gamma_{j} y_{j} ~\textrm{s.t.}~ \sum_{j = 1}^{n} y_{j} = 1 +``` + +When a variable is encoded following this approach, a penalty term of the form + +```math +\rho \left[ \sum_{j = 1}^{n} y_{j} - 1 \right]^{2} +``` + +is added to the objective function. + +""" +struct OneHot{T} <: SetVariableEncodingMethod end + +# Arbitrary set +function encode(var::Function, e::OneHot{T}, γ::AbstractVector{T}) where {T} + p = length(γ) + n = encoding_bits(e, p) + + if p == 0 + y = Vector{VI}() + ξ = PBO.PBF{VI,T}() + χ = nothing + elseif p == 1 + y = Vector{VI}() + ξ = PBO.PBF{VI,T}(γ[1]) + χ = nothing + else + y = var(n)::Vector{VI} + ξ = PBO.PBF{VI,T}([y[i] => γ[i] for i = 1:p]) + χ = PBO.PBF{VI,T}([y; -1])^2 + end + + return (y, ξ, χ) +end + +# Integer +function encode( + var::Function, + e::E, + S::Tuple{T,T}; + tol::Union{T,Nothing} = nothing, +) where {T,E<:OneHot{T}} + isnothing(tol) || return encode(var, e, S, nothing; tol) + + a, b = integer_interval(S) + + return encode(var, e, collect(a:b)) +end + +function encoding_points( + ::E, + S::Tuple{T,T}, + tol::T, +) where {T,E<:OneHot{T}} + a, b = S + + return ceil(Int, ((b - a)^2 / 4tol) + 1) +end + +function encoding_points(::OneHot, n::Integer) + return n +end + +function encoding_bits(e::E, S::Tuple{T,T}, tol::T) where {T,E<:OneHot{T}} + return encoding_points(e, S, tol) +end + +function encoding_bits(::OneHot, p::Integer) + return p +end + +# Real +function encode( + var::Function, + e::E, + S::Tuple{T,T}, + n::Union{Integer,Nothing}; + tol::Union{T,Nothing} = nothing, +) where {T,E<:OneHot{T}} + @assert !(isnothing(n) && isnothing(tol)) + + p = if isnothing(n) + encoding_points(e, S, tol) + else + encoding_points(e, n) + end + + a, b = S + + Γ = collect(range(a, b; length = p)) + + return encode(var, e, Γ) +end diff --git a/src/error.jl b/src/error.jl new file mode 100644 index 00000000..f72319a2 --- /dev/null +++ b/src/error.jl @@ -0,0 +1,24 @@ +""" + QUBOCompilationError(msg::Union{Nothing, String}) + +This error indicates any failure during QUBO formulation +""" +struct QUBOCompilationError <: Exception + msg::Union{String,Nothing} + + function QUBOCompilationError(msg::Union{Nothing,String} = nothing) + return new(msg) + end +end + +function Base.showerror(io::IO, e::QUBOCompilationError) + if isnothing(e.msg) + print(io, "The current model can't be converted to QUBO") + else + print(io, e.msg) + end +end + +function compilation_error(msg::Union{Nothing,String} = nothing) + throw(QUBOCompilationError(msg)) +end \ No newline at end of file diff --git a/src/lib/error.jl b/src/lib/error.jl deleted file mode 100644 index ad5c6d8a..00000000 --- a/src/lib/error.jl +++ /dev/null @@ -1,20 +0,0 @@ -""" - QUBOError(msg::Union{Nothing, String}) - -This error indicates any failure during QUBO formulation -""" -struct QUBOError <: Exception - msg::Union{String, Nothing} - - function QUBOError(msg::Union{Nothing, String} = nothing) - new(msg) - end -end - -function Base.showerror(io::IO, e::QUBOError) - if isnothing(e.msg) - print(io, "The current model can't be converted to QUBO") - else - print(io, e.msg) - end -end \ No newline at end of file diff --git a/src/lib/pbo/PBF.jl b/src/lib/pbo/PBF.jl deleted file mode 100644 index 71cc6287..00000000 --- a/src/lib/pbo/PBF.jl +++ /dev/null @@ -1,355 +0,0 @@ -import Base: isiterable - -@doc raw""" -""" -function _parseterm end - -_parseterm(::Type{S}, ::Type{T}, x::Any) where {S,T} = error("Invalid term '$(x)'") # fallback -_parseterm(::Type{S}, ::Type{T}, ::Nothing) where {S,T} = (Set{S}(), one(T)) -_parseterm(::Type{S}, ::Type{T}, x::T) where {S,T} = (Set{S}(), x) -_parseterm(::Type{S}, ::Type{T}, x::S) where {S,T} = (Set{S}([x]), one(T)) -_parseterm(::Type{S}, ::Type{T}, x::Union{Vector{S},Set{S}}) where {S,T} = - (Set{S}(x), one(T)) -_parseterm(::Type{S}, ::Type{T}, x::Pair{Nothing,T}) where {S,T} = (Set{S}(), last(x)) -_parseterm(::Type{S}, ::Type{T}, x::Tuple{Nothing,T}) where {S,T} = (Set{S}(), last(x)) -_parseterm(::Type{S}, ::Type{T}, x::Union{Pair{S,T},Tuple{S,T}}) where {S,T} = - (Set{S}([first(x)]), last(x)) -_parseterm(::Type{S}, ::Type{T}, x::Pair{<:Union{Vector{S},Set{S}},T}) where {S,T} = - (Set{S}(first(x)), last(x)) -_parseterm(::Type{S}, ::Type{T}, x::Tuple{<:Union{Vector{S},Set{S}},T}) where {S,T} = - (Set{S}(first(x)), last(x)) - -@doc raw""" - PseudoBooleanFunction{V,T}(Ω::Dict{Union{Set{V},Nothing},T}) where {V,T} - -A Pseudo-Boolean Function[^Boros2002] ``f \in \mathscr{F}`` over some field ``\mathbb{T}`` takes the form - -```math -f(\mathbf{x}) = \sum_{\omega \in \Omega\left[f\right]} c_\omega \prod_{j \in \omega} x_j -``` - -where each ``\Omega\left[{f}\right]`` is the multi-linear representation of ``f`` as a set of terms. -Each term is given by a unique set of indices ``\omega \subseteq \mathbb{S}`` related to some coefficient ``c_\omega \in \mathbb{T}``. -We say that ``\omega \in \Omega\left[{f}\right] \iff c_\omega \neq 0``. -Variables ``x_j`` are boolean, thus ``f : \mathbb{B}^{n} \to \mathbb{T}``. - -[^Boros2002]: - Endre Boros, Peter L. Hammer, **Pseudo-Boolean optimization**, *Discrete Applied Mathematics*, 2002 [{doi}](https://doi.org/10.1016/S0166-218X(01)00341-9) -""" -struct PseudoBooleanFunction{V,T} - Ω::Dict{Set{V},T} - - function PseudoBooleanFunction{V,T}(Ω::Dict{<:Union{Set{V},Nothing},T}) where {V,T} - return new{V,T}( - Dict{Set{V},T}(isnothing(ω) ? Set{V}() : ω => c for (ω, c) in Ω if !iszero(c)), - ) - end - - function PseudoBooleanFunction{V,T}(v::Vector) where {V,T} - Ω = Dict{Set{V},T}() - - for x in v - ω, a = _parseterm(V, T, x) - Ω[ω] = get(Ω, ω, zero(T)) + a - end - - return PseudoBooleanFunction{V,T}(Ω) - end - - function PseudoBooleanFunction{V,T}(x::Base.Generator) where {V,T} - return PseudoBooleanFunction{V,T}(collect(x)) - end - - function PseudoBooleanFunction{V,T}(x::Vararg{Any}) where {V,T} - return PseudoBooleanFunction{V,T}(collect(x)) - end - - function PseudoBooleanFunction{V,T}() where {V,T} - return new{V,T}(Dict{Set{V},T}()) - end -end - -# Alias -const PBF{V,T} = PseudoBooleanFunction{V,T} - -# Broadcast as scalar -Base.broadcastable(f::PBF) = Ref(f) - -# Copy -function Base.copy!(f::PBF{S,T}, g::PBF{S,T}) where {S,T} - sizehint!(f, length(g)) - copy!(f.Ω, g.Ω) - - return f -end - -function Base.copy(f::PBF{S,T}) where {S,T} - return copy!(PBF{S,T}(), f) -end - -# Iterator & Length -Base.keys(f::PBF) = keys(f.Ω) -Base.values(f::PBF) = values(f.Ω) -Base.length(f::PBF) = length(f.Ω) -Base.empty!(f::PBF) = empty!(f.Ω) -Base.isempty(f::PBF) = isempty(f.Ω) -Base.iterate(f::PBF) = iterate(f.Ω) -Base.iterate(f::PBF, i::Integer) = iterate(f.Ω, i) - -Base.haskey(f::PBF{S}, ω::Set{S}) where {S} = haskey(f.Ω, ω) -Base.haskey(f::PBF{S}, ξ::S) where {S} = haskey(f, Set{S}([ξ])) -Base.haskey(f::PBF{S}, ::Nothing) where {S} = haskey(f, Set{S}()) - -# Indexing: Get # -Base.getindex(f::PBF{S,T}, ω::Set{S}) where {S,T} = get(f.Ω, ω, zero(T)) -Base.getindex(f::PBF{S}, η::Vector{S}) where {S} = getindex(f, Set{S}(η)) -Base.getindex(f::PBF{S}, ξ::S) where {S} = getindex(f, Set{S}([ξ])) -Base.getindex(f::PBF{S}, ::Nothing) where {S} = getindex(f, Set{S}()) - -# Indexing: Set # -function Base.setindex!(f::PBF{S,T}, c::T, ω::Set{S}) where {S,T} - if !iszero(c) - setindex!(f.Ω, c, ω) - elseif haskey(f, ω) - delete!(f, ω) - end - - return c -end - -Base.setindex!(f::PBF{S,T}, c::T, η::Vector{S}) where {S,T} = setindex!(f, c, Set{S}(η)) -Base.setindex!(f::PBF{S,T}, c::T, ξ::S) where {S,T} = setindex!(f, c, Set{S}([ξ])) -Base.setindex!(f::PBF{S,T}, c::T, ::Nothing) where {S,T} = setindex!(f, c, Set{S}()) - -# Indexing: Delete # -Base.delete!(f::PBF{S}, ω::Set{S}) where {S} = delete!(f.Ω, ω) -Base.delete!(f::PBF{S}, η::Vector{S}) where {S} = delete!(f, Set{S}(η)) -Base.delete!(f::PBF{S}, k::S) where {S} = delete!(f, Set{S}([k])) -Base.delete!(f::PBF{S}, ::Nothing) where {S} = delete!(f, Set{S}()) - -# Properties -Base.size(f::PBF{S,T}) where {S,T} = (length(f),) - -function Base.sizehint!(f::PBF, n::Integer) - sizehint!(f.Ω, n) - - return f -end - -# Comparison: (==, !=, ===, !==) # -Base.:(==)(f::PBF{S,T}, g::PBF{S,T}) where {S,T} = f.Ω == g.Ω -Base.:(==)(f::PBF{S,T}, a::T) where {S,T} = isscalar(f) && (f[nothing] == a) -Base.:(!=)(f::PBF{S,T}, g::PBF{S,T}) where {S,T} = f.Ω != g.Ω -Base.:(!=)(f::PBF{S,T}, a::T) where {S,T} = !isscalar(f) || (f[nothing] != a) - -function Base.isapprox(f::PBF{S,T}, g::PBF{S,T}; kw...) where {S,T} - return (length(f) == length(g)) && - all(haskey(g, ω) && isapprox(g[ω], f[ω]; kw...) for ω in keys(f)) -end - -function Base.isapprox(f::PBF{S,T}, a::T; kw...) where {S,T} - return isscalar(f) && isapprox(f[nothing], a; kw...) -end - -function isscalar(f::PBF{S}) where {S} - return isempty(f) || (length(f) == 1 && haskey(f, nothing)) -end - -Base.zero(::Type{PBF{S,T}}) where {S,T} = PBF{S,T}() -Base.iszero(f::PBF) = isempty(f) -Base.one(::Type{PBF{S,T}}) where {S,T} = PBF{S,T}(one(T)) -Base.isone(f::PBF) = isscalar(f) && isone(f[nothing]) -Base.round(f::PBF{S,T}; kw...) where {S,T} = PBF{S,T}(ω => round(c; kw...) for (ω, c) in f) - -# Arithmetic: (+) -function Base.:(+)(f::PBF{S,T}, g::PBF{S,T}) where {S,T} - h = copy(f) - - for (ω, c) in g - h[ω] += c - end - - return h -end - -function Base.:(+)(f::PBF{S,T}, c::T) where {S,T} - if iszero(c) - copy(f) - else - g = copy(f) - - g[nothing] += c - - return g - end -end - -Base.:(+)(f::PBF{S,T}, c) where {S,T} = +(f, convert(T, c)) -Base.:(+)(c, f::PBF) = +(f, c) - -# Arithmetic: (-) -function Base.:(-)(f::PBF{S,T}) where {S,T} - return PBF{S,T}(Dict{Set{S},T}(ω => -c for (ω, c) in f)) -end - -function Base.:(-)(f::PBF{S,T}, g::PBF{S,T}) where {S,T} - h = copy(f) - - for (ω, c) in g - h[ω] -= c - end - - return h -end - -function Base.:(-)(f::PBF{S,T}, c::T) where {S,T} - if iszero(c) - copy(f) - else - g = copy(f) - - g[nothing] -= c - - return g - end -end - -function Base.:(-)(c::T, f::PBF{S,T}) where {S,T} - g = -f - - if !iszero(c) - g[nothing] += c - end - - return g -end - -Base.:(-)(c, f::PBF{S,T}) where {S,T} = -(convert(T, c), f) -Base.:(-)(f::PBF{S,T}, c) where {S,T} = -(f, convert(T, c)) - -# Arithmetic: (*) -function Base.:(*)(f::PBF{S,T}, g::PBF{S,T}) where {S,T} - h = zero(PBF{S,T}) - m = length(f) - n = length(g) - - if iszero(f) || iszero(g) # T(n) = O(1) - return h - elseif f === g # T(n) = O(n) + O(n^2 / 2) - k = collect(f) - - sizehint!(h, n^2 ÷ 2) - - for i = 1:n - ωi, ci = k[i] - - h[ωi] += ci * ci - - for j = (i+1):n - ωj, cj = k[j] - - h[union(ωi, ωj)] += 2 * ci * cj - end - end - - return h - else # T(n) = O(m n) - sizehint!(h, m * n) - - for (ωᵢ, cᵢ) in f, (ωⱼ, cⱼ) in g - h[union(ωᵢ, ωⱼ)] += cᵢ * cⱼ - end - - return h - end -end - -function Base.:(*)(f::PBF{S,T}, a::T) where {S,T} - if iszero(a) - return PBF{S,T}() - else - return PBF{S,T}(ω => c * a for (ω, c) ∈ f) - end -end - -Base.:(*)(f::PBF{S,T}, a) where {S,T} = *(f, convert(T, a)) -Base.:(*)(a, f::PBF) = *(f, a) - -# Arithmetic: (/) -function Base.:(/)(f::PBF{S,T}, a::T) where {S,T} - if iszero(a) - throw(DivideError()) - else - return PBF{S,T}(Dict(ω => c / a for (ω, c) in f)) - end -end - -Base.:(/)(f::PBF{S,T}, a) where {S,T} = /(f, convert(T, a)) - -# Arithmetic: (^) -function Base.:(^)(f::PBF{S,T}, n::Integer) where {S,T} - if n < 0 - throw(DivideError()) - elseif n == 0 - return one(PBF{S,T}) - elseif n == 1 - return copy(f) - elseif n == 2 - return f * f - else - g = f * f - - if iseven(n) - return g^(n ÷ 2) - else - return g^(n ÷ 2) * f - end - end -end - -# Arithmetic: Evaluation -function (f::PBF{S,T})(x::Dict{S,U}) where {S,T,U<:Integer} - g = PBF{S,T}() - - for (ω, c) in f - η = Set{S}() - - for j in ω - if haskey(x, j) - if iszero(x[j]) - c = zero(T) - break - end - else - push!(η, j) - end - end - - g[η] += c - end - - return g -end - -function (f::PBF{S,T})(η::Set{S}) where {S,T} - return sum(c for (ω, c) in f if ω ⊆ η; init = zero(T)) -end - -function (f::PBF{S})(x::Pair{S,U}...) where {S,U<:Integer} - return f(Dict{S,U}(x...)) -end - -function (f::PBF{S})() where {S} - return f(Dict{S,Int}()) -end - -# Type conversion -function Base.convert(U::Type{<:T}, f::PBF{<:Any,T}) where {T} - if isempty(f) - return zero(U) - elseif degree(f) == 0 - return convert(U, f[nothing]) - else - error("Can't convert non-constant Pseudo-boolean Function to scalar type '$U'") - end -end diff --git a/src/lib/pbo/PBO.jl b/src/lib/pbo/PBO.jl deleted file mode 100644 index e39470c0..00000000 --- a/src/lib/pbo/PBO.jl +++ /dev/null @@ -1,15 +0,0 @@ -module PBO - -using Random - -# Imports -import QUBOTools: varlt, qubo, variables, variable_map, variable_set, variable_inv - -include("PBF.jl") -include("tools.jl") -include("rand.jl") -include("wrapper.jl") -include("quadratization.jl") -include("print.jl") - -end # module \ No newline at end of file diff --git a/src/lib/pbo/interface.jl b/src/lib/pbo/interface.jl deleted file mode 100644 index c1586c81..00000000 --- a/src/lib/pbo/interface.jl +++ /dev/null @@ -1,91 +0,0 @@ - -@doc raw""" - gap(f::PBF{S, T}; bound::Symbol=:loose) where {S, T} - -Computes the least upper bound for the greatest variantion possible under some `` f \in \mathscr{F} `` i. e. - -```math -\begin{array}{r l} - \min & M \\ - \text{s.t.} & \left|{f(\mathbf{x}) - f(\mathbf{y})}\right| \le M ~~ \forall \mathbf{x}, \mathbf{y} \in \mathbb{B}^{n} -\end{array} -``` - -A simple approach, avaiable using the `bound=:loose` parameter, is to define -```math -M \triangleq \sum_{\omega \neq \varnothing} \left|{c_\omega}\right| -``` -""" -function gap end - -const δ = gap - -@doc raw""" - sharpness(f::PBF{S, T}; bound::Symbol=:loose, tol::T = T(1e-6)) where {S, T} -""" -function sharpness end - -const ϵ = sharpness - -@doc raw""" - derivative(f::PBF{V,T}, x::V) where {V,T} - -The partial derivate of function ``f \in \mathscr{F}`` with respect to the ``x`` variable. - -```math - \Delta_i f(\mathbf{x}) = \frac{\partial f(\mathbf{x})}{\partial \mathbf{x}_i} = - \sum_{\omega \in \Omega\left[{f}\right] \setminus \left\{{i}\right\}} - c_{\omega \cup \left\{{i}\right\}} \prod_{k \in \omega} \mathbf{x}_k -``` -""" -function derivative end - -const Δ = derivative -const ∂ = derivative - -@doc raw""" - gradient(f::PBF) - -Computes the gradient of ``f \in \mathscr{F}`` where the ``i``-th derivative is given by [`derivative`](@ref). -""" function gradient end - -const ∇ = gradient - -@doc raw""" - residual(f::PBF{S, T}, x::S) where {S, T} - -The residual of ``f \in \mathscr{F}`` with respect to ``x``. - -```math - \Theta_i f(\mathbf{x}) = f(\mathbf{x}) - \mathbf{x}_i\, \Delta_i f(\mathbf{x}) = - \sum_{\omega \in \Omega\left[{f}\right] \setminus \left\{{i}\right\}} - c_{\omega} \prod_{k \in \omega} \mathbf{x}_k -``` -""" function residual end - -const Θ = residual - -@doc raw""" - discretize(f::PBF{V,T}; tol::T) where {V,T} - -For a given function ``f \in \mathscr{F}`` written as - -```math - f\left({\mathbf{x}}\right) = \sum_{\omega \in \Omega\left[{f}\right]} c_\omega \prod_{i \in \omega} \mathbf{x}_i -``` - -computes an approximate function ``g : \mathbb{B}^{n} \to \mathbb{Z}`` such that - -```math - \text{argmin}_{\mathbf{x} \in \mathbb{B}^{n}} g\left({\mathbf{x}}\right) = \text{argmin}_{\mathbf{x} \in \mathbb{B}^{n}} f\left({\mathbf{x}}\right) -``` - -This is done by rationalizing every coefficient ``c_\omega`` according to some tolerance `tol`. - -""" function discretize end - -@doc raw""" - discretize!(f::PBF{V,T}; tol::T) where {V,T} - -In-place version of [`discretize`](@ref). -""" function discretize! end \ No newline at end of file diff --git a/src/lib/pbo/print.jl b/src/lib/pbo/print.jl deleted file mode 100644 index 2bcbc365..00000000 --- a/src/lib/pbo/print.jl +++ /dev/null @@ -1,31 +0,0 @@ -@doc raw""" -""" -function showvar end - -showvar(x::Any) = x -showvar(s::Set) = showvar.(sort(collect(s); lt=PBO.varlt)) -showvar(x::Integer, v::Symbol = :x) = join([v; Char(0x2080) .+ reverse(digits(x))]) - -function showterm(ω::Set{S}, c::T, isfirst::Bool) where {S, T} - if isfirst - "$(c)$(join(showvar(ω), "*"))" - else - if c < zero(T) - " - $(abs(c))$(join(showvar(ω), "*"))" - else - " + $(abs(c))$(join(showvar(ω), "*"))" - end - end -end - -function Base.show(io::IO, f::PBF{<:Any, T}) where T - Ω = sort!(collect(f); lt=(x,y) -> varlt(first(x), first(y))) - - print(io, - if isempty(f) - zero(T) - else - join(showterm(ω, c, isone(i)) for (i, (ω, c)) in enumerate(Ω)) - end - ) -end \ No newline at end of file diff --git a/src/lib/pbo/quadratization.jl b/src/lib/pbo/quadratization.jl deleted file mode 100644 index ceae52bd..00000000 --- a/src/lib/pbo/quadratization.jl +++ /dev/null @@ -1,202 +0,0 @@ -# :: Quadratization :: # -abstract type QuadratizationMethod end - -struct Quadratization{Q<:QuadratizationMethod} - stable::Bool - - function Quadratization{Q}(stable::Bool = false) where {Q<:QuadratizationMethod} - return new{Q}(stable) - end -end - -@doc raw""" - quadratize!(aux::Function, f::PBF{S, T}, ::Quadratization{Q}) where {S,T,Q} - -Quadratizes a given PBF in-place, i.e. applies a mapping ``\mathcal{Q} : \mathscr{F}^{k} \to \mathscr{F}^{2}``, where ``\mathcal{Q}`` is the quadratization method. - -```julia -aux(::Nothing)::S -aux(::Integer)::Vector{S} -``` -""" function quadratize! end - -@doc raw""" - Quadratization{NTR_KZFD}(stable::Bool = false) - -Negative Term Reduction NTR-KZFD (Kolmogorov & Zabih, 2004; Freedman & Drineas, 2005) - -Let ``f(\mathbf{x}) = x_{1} x_{2} \dots x_{k}``. - -```math -\mathcal{Q}\left\lbrace{f}\right\rbrace(\mathbf{x}; z) = (k - 1) z - \sum_{i = 1}^{k} x_{i} z -``` - -where ``\mathbf{x} \in \mathbb{B}^k`` - -!!! info - Introduces a new variable ``z`` and no non-submodular terms. -""" struct NTR_KZFD <: QuadratizationMethod end - -function quadratize!( - aux::Function, - f::PBF{S,T}, - ω::Set{S}, - c::T, - ::Quadratization{NTR_KZFD}, -) where {S,T} - # Degree - k = length(ω) - - # Fast-track - k < 3 && return nothing - - # Variables - s = aux()::S - - # Stabilize - # NOTE: This method is stable by construction - - # Quadratization - delete!(f, ω) - - f[s] += -c * (k - 1) - - for i ∈ ω - f[i×s] += c - end - - return nothing -end - -@doc raw""" - Quadratization{PTR_BG}(stable::Bool = false) - -Positive Term Reduction PTR-BG (Boros & Gruber, 2014) - -Let ``f(\mathbf{x}) = x_{1} x_{2} \dots x_{k}``. - -```math -\mathcal{Q}\left\lbrace{f}\right\rbrace(\mathbf{x}; \mathbf{z}) = \left[{ - \sum_{i = 1}^{k-2} z_{i} \left({ k - i - 1 + x_{i} + \sum_{j = i+1}^{k} x_{j} }\right) -}\right] + x_{k-1} x_{k} -``` -where ``\mathbf{x} \in \mathbb{B}^k`` and ``\mathbf{z} \in \mathbb{B}^{k-2}`` - -!!! info - Introduces ``k - 2`` new variables ``z_{1}, \dots, z_{k-2}`` and ``k - 1`` non-submodular terms. -""" struct PTR_BG <: QuadratizationMethod end - -function quadratize!( - aux::Function, - f::PBF{S,T}, - ω::Set{S}, - c::T, - quad::Quadratization{PTR_BG}, -) where {S,T} - # Degree - k = length(ω) - - # Fast-track - k < 3 && return nothing - - # Variables - s = aux(k - 2)::Vector{S} - b = collect(ω)::Vector{S} - - # Stabilize - quad.stable && sort!(b; lt = varlt) - - # Quadratization - delete!(f, ω) - - f[b[k]×b[k-1]] += c - - for i = 1:(k-2) - f[s[i]] += c * (k - i - 1) - - f[s[i]×b[i]] += c - - for j = (i+1):k - f[s[i]×b[j]] -= c - end - end - - return nothing -end - -@doc raw""" - Quadratization{TERM_BY_TERM}(stable::Bool = false) - -Term-by-term quadratization. Employs other inner methods, specially [`NTR_KZFD`](@ref) and [`PTR_BG`](@ref). -""" struct TERM_BY_TERM <: QuadratizationMethod end - -function quadratize!( - aux::Function, - f::PBF{S,T}, - ω::Set{S}, - c::T, - quad::Quadratization{TERM_BY_TERM}, -) where {S,T} - if c < zero(T) - quadratize!(aux, f, ω, c, Quadratization{NTR_KZFD}(quad.stable)) - else - quadratize!(aux, f, ω, c, Quadratization{PTR_BG}(quad.stable)) - end - - return nothing -end - -@doc raw""" - quadratize!(aux::Function, f::PBF{S,T}, quad::Quadratization{TERM_BY_TERM}) where {S,T} - - Receives a higher-degree pseudo-Boolean function -""" -function quadratize!( - aux::Function, - f::PBF{S,T}, - quad::Quadratization{TERM_BY_TERM}, -) where {S,T} - # Collect Terms - Ω = collect(f) - - # Stable Quadratization - quad.stable && sort!(Ω; by = first, lt = varlt) - - for (ω, c) in Ω - quadratize!(aux, f, ω, c, Quadratization{PTR_BG}(quad.stable)) - end - - return nothing -end - - -@doc raw""" - Quadratization{INFER}(stable::Bool = false) -""" struct INFER <: QuadratizationMethod end - -@doc raw""" - infer_quadratization(f::PBF) - -For a given PBF, returns whether it should be quadratized or not, based on its degree. -""" -function infer_quadratization(f::PBF, stable::Bool = false) - k = degree(f) - - if k <= 2 - return nothing - else - # Without any extra knowledge, it is better to - # quadratize term-by-term - return Quadratization{TERM_BY_TERM}(stable) - end -end - -function quadratize!(aux::Function, f::PBF, quad::Quadratization{INFER}) - quadratize!(aux, f, infer_quadratization(f, quad.stable)) - - return nothing -end - -function quadratize!(::Function, ::PBF, ::Nothing) - return nothing -end diff --git a/src/lib/pbo/rand.jl b/src/lib/pbo/rand.jl deleted file mode 100644 index f0f8850d..00000000 --- a/src/lib/pbo/rand.jl +++ /dev/null @@ -1,36 +0,0 @@ -function Base.rand( - ::Type{PBF{S,T}}, - args...; - kws... -) where {S,T} - return rand(Random.GLOBAL_RNG, PBF{S,T}, args...; kws...) -end - -function Base.rand( - rng::Random.AbstractRNG, - ::Type{PBF{S,T}}, - ω::Set{S}, - n::Integer, - r::AbstractRange{T}, - d::Integer = 2, # degree -) where {S,T} - a = first(r) - b = last(r) - f = sizehint!(PBF{S,T}(), n) - - for _ = 1:n - η = Set{S}() - - for _ = 1:d - if rand(rng) < 0.5 - push!(η, rand(ω)) - end - end - - c = a + (b - a) * rand(rng) - - f[η] += c - end - - return f -end \ No newline at end of file diff --git a/src/lib/pbo/tools.jl b/src/lib/pbo/tools.jl deleted file mode 100644 index ef1d7470..00000000 --- a/src/lib/pbo/tools.jl +++ /dev/null @@ -1,129 +0,0 @@ -# Relaxed Greatest Common Divisor -@doc raw""" - relaxed_gcd(x::T, y::T; tol::T = T(1e-6)) where {T} - -We define two real numbers ``x`` and ``y`` to be ``\tau``-comensurable if, for some ``\tau > 0`` there exists a continued fractions convergent ``p_{k} \div q_{k}`` such that - -```math - \left| {q_{k} x - p_{k} y} \right| \le \tau -``` -""" -function relaxed_gcd(x::T, y::T; tol::T = 1e-6) where {T} - x_ = abs(x) - y_ = abs(y) - - if x_ < y_ - return relaxed_gcd(y_, x_; tol = tol)::T - elseif y_ < tol - return x_ - elseif x_ < tol - return y_ - else - return (x_ / numerator(rationalize(x_ / y_; tol = tol)))::T - end -end - -function relaxed_gcd(a::AbstractArray{T}; tol::T = 1e-6) where {T} - if length(a) == 0 - return one(T) - elseif length(a) == 1 - return first(a) - else - return reduce((x, y) -> relaxed_gcd(x, y; tol = tol), a) - end -end - -# Variable Terms -varmul(x::V, y::V) where {V} = Set{V}([x, y]) -varmul(x::Set{V}, y::V) where {V} = push!(copy(x), y) -varmul(x::V, y::Set{V}) where {V} = push!(copy(y), x) -varmul(x::Set{V}, y::Set{V}) where {V} = union(x, y) - -const × = varmul # \times[tab] -const ≺ = varlt # \prec[tab] - -@doc raw""" -""" -function degree end - -degree(f::PBF) = maximum(length.(keys(f)); init = 0) - -# Gap & Penalties -@doc raw""" -""" function lowerbound end - -function lowerbound(f::PBF; bound::Symbol = :loose) - return lowerbound(f, Val(bound)) -end - -function lowerbound(f::PBF{<:Any,T}, ::Val{:loose}) where {T} - return sum(c < zero(T) || isempty(ω) ? c : zero(T) for (ω, c) in f) -end - -@doc raw""" -""" function upperbound end - -function upperbound(f::PBF; bound::Symbol = :loose) - return upperbound(f, Val(bound)) -end - -function upperbound(f::PBF{<:Any,T}, ::Val{:loose}) where {T} - return sum(c > zero(T) || isempty(ω) ? c : zero(T) for (ω, c) in f) -end - -@doc raw""" -""" function bounds end - -function bounds(f::PBF; bound::Symbol = :loose) - return (lowerbound(f; bound), upperbound(f; bound)) -end - -function gap(f::PBF; bound::Symbol = :loose) - return gap(f, Val(bound)) -end - -function gap(f::PBF{V,T}, ::Val{:loose}) where {V,T} - return sum(abs(c) for (ω, c) in f if !isempty(ω); init = zero(T)) -end - -function gap(::PBF, ::Val{:tight}) - error("Not Implemented: See [1] sec 5.1.1 Majorization") -end - -function sharpness(f::PBF{V,T}; bound::Symbol = :loose, tol::T = 1e-6) where {V,T} - return sharpness(f, Val(bound), tol) -end - -function sharpness(::PBF{V,T}, ::Val{:none}, ::T) where {V,T} - return one(T) -end - -function sharpness(f::PBF{V,T}, ::Val{:loose}, tol::T = 1E-6) where {V,T} - return relaxed_gcd(collect(values(f)); tol = tol)::T -end - -function derivative(f::PBF{V,T}, x::V) where {V,T} - return PBF{V,T}(ω => f[ω×x] for ω ∈ keys(f) if (x ∉ ω)) -end - -function gradient(f::PBF{V}, x::Vector{V}) where {V} - return derivative.(f, x) -end - -function residual(f::PBF{V,T}, x::V) where {V,T} - return PBF{V,T}(ω => c for (ω, c) ∈ keys(f) if (x ∉ ω)) -end - -function discretize(f::PBF{V,T}; tol::T = 1E-6) where {V,T} - return discretize!(copy(f); tol = tol) -end - -function discretize!(f::PBF{V,T}; tol::T = 1E-6) where {V,T} - ε = sharpness(f; bound = :loose, tol = tol) - - for (ω, c) in f - f[ω] = round(c / ε; digits = 0) - end - - return f -end \ No newline at end of file diff --git a/src/lib/pbo/wrapper.jl b/src/lib/pbo/wrapper.jl deleted file mode 100644 index b953af85..00000000 --- a/src/lib/pbo/wrapper.jl +++ /dev/null @@ -1,67 +0,0 @@ -qubo(f::PBF) = qubo(f, Dict) - -function qubo(f::PBF{S,T}, ::Type{Dict}) where {S,T} - x = variable_map(f) - Q = Dict{Tuple{Int,Int},T}() - α = one(T) - β = zero(T) - - sizehint!(Q, length(f)) - - for (ω, a) in f - η = sort([x[i] for i ∈ ω]; lt = varlt) - k = length(η) - - if k == 0 - β += a - elseif k == 1 - i, = η - Q[i, i] = a - elseif k == 2 - i, j = η - Q[i, j] = a - else - error( - DomainError, - ": Can't convert Pseudo-boolean function with degree greater than 2 to QUBO format.\nTry using 'quadratize' before conversion.", - ) - end - end - - return (Q, α, β) -end - -function qubo(f::PBF{S,T}, ::Type{Matrix}) where {S,T} - x = variable_map(f) - n = length(x) - Q = zeros(T, n, n) - α = one(T) - β = zero(T) - - for (ω, a) ∈ f - η = sort([x[i] for i ∈ ω]; lt = varlt) - k = length(η) - if k == 0 - β += a - elseif k == 1 - i, = η - Q[i, i] += a - elseif k == 2 - i, j = η - Q[i, j] += a / 2 - Q[j, i] += a / 2 - else - error( - DomainError, - ": Can't convert Pseudo-boolean function with degree greater than 2 to QUBO format.\nTry using 'quadratize' before conversion.", - ) - end - end - - return (Q, α, β) -end - -variable_map(f::PBF{S}) where {S} = Dict{S,Int}(v => i for (i, v) in enumerate(variables(f))) -variable_inv(f::PBF{S}) where {S} = Dict{Int,S}(i => v for (i, v) in enumerate(variables(f))) -variable_set(f::PBF{S}) where {S} = reduce(union!, keys(f); init=Set{S}()) -variables(f::PBF) = sort(collect(variable_set(f)); lt = varlt) diff --git a/src/model/prequbo.jl b/src/model/prequbo.jl index d10ed1e4..bf359662 100644 --- a/src/model/prequbo.jl +++ b/src/model/prequbo.jl @@ -1,4 +1,5 @@ -MOIU.@model(PreQUBOModel, # Name of model +MOIU.@model( + PreQUBOModel, # Name of model (MOI.Integer, MOI.ZeroOne), # untyped scalar sets (EQ, LT, GT), # typed scalar sets (), # untyped vector sets @@ -12,4 +13,4 @@ MOIU.@model(PreQUBOModel, # Name of model # 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 \ No newline at end of file +MOI.supports_constraint(::PreQUBOModel{T}, ::Type{SQF{T}}, ::Type{GT{T}}) where {T} = false diff --git a/src/model/qubo.jl b/src/model/qubo.jl index 926a0f4e..687f990b 100644 --- a/src/model/qubo.jl +++ b/src/model/qubo.jl @@ -82,11 +82,8 @@ function MOI.set(model::QUBOModel{T}, ::MOI.ObjectiveFunction{SAF{T}}, f::SAF{T} end function MOI.set(model::QUBOModel{T}, ::MOI.ObjectiveFunction{SQF{T}}, f::SQF{T}) where {T} - model.objective_function = SQF{T}( - copy(f.quadratic_terms), - copy(f.affine_terms), - f.constant - ) + model.objective_function = + SQF{T}(copy(f.quadratic_terms), copy(f.affine_terms), f.constant) return nothing end diff --git a/src/model/virtual.jl b/src/model/virtual.jl deleted file mode 100644 index beaf999e..00000000 --- a/src/model/virtual.jl +++ /dev/null @@ -1,791 +0,0 @@ -# Virtual Variable Encoding -abstract type Encoding end - -@doc raw""" - encode!(model::VirtualModel{T}, v::VirtualVariable{T}) where {T} - -Maps newly created virtual variable `v` within the virtual model structure. It follows these steps: - - 1. Maps `v`'s source to it in the model's `source` mapping. - 2. For every one of `v`'s targets, maps it to itself and adds a binary constraint to it. - 2. Adds `v` to the end of the model's `varvec`. -""" -function encode! end - -@doc raw""" -# Variable Expansion methods: - - Linear - - Unary - - Binary - - One Hot - - Domain Wall - -# References: - * [1] Chancellor, N. (2019). Domain wall encoding of discrete variables for quantum annealing and QAOA. _Quantum Science and Technology_, _4_(4), 045004. [{doi}](https://doi.org/10.1088/2058-9565/ab33c2) -""" -struct VirtualVariable{T} - e::Encoding - x::Union{VI,Nothing} # Source variable (if there is one) - y::Vector{VI} # Target variables - ξ::PBO.PBF{VI,T} # Expansion function - h::Union{PBO.PBF{VI,T},Nothing} # Penalty function (i.e. ‖gᵢ(x)‖ₛ for g(i) ∈ S) - - function VirtualVariable{T}( - e::Encoding, - x::Union{VI,Nothing}, - y::Vector{VI}, - ξ::PBO.PBF{VI,T}, - h::Union{PBO.PBF{VI,T},Nothing}, - ) where {T} - return new{T}(e, x, y, ξ, h) - end -end - -const VV{T} = VirtualVariable{T} - -encoding(v::VirtualVariable) = v.e -source(v::VirtualVariable) = v.x -target(v::VirtualVariable) = v.y -is_aux(v::VirtualVariable) = isnothing(source(v)) -expansion(v::VirtualVariable) = v.ξ -penaltyfn(v::VirtualVariable) = v.h - -@doc raw""" - VirtualModel{T}(optimizer::Union{Nothing, Type{<:MOI.AbstractOptimizer}} = nothing) where {T} - -This Virtual Model links the final QUBO formulation to the original one, allowing variable value retrieving and other features. -""" -struct VirtualModel{T} <: MOI.AbstractOptimizer - # Underlying Optimizer # - optimizer::Union{MOI.AbstractOptimizer,Nothing} - - # MathOptInterface Bridges # - bridge_model::MOIB.LazyBridgeOptimizer{PreQUBOModel{T}} - - # Virtual Model Interface # - source_model::PreQUBOModel{T} - target_model::QUBOModel{T} - variables::Vector{VV{T}} - source::Dict{VI,VV{T}} - target::Dict{VI,VV{T}} - - # 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 - H::PBO.PBF{VI,T} # Final Hamiltonian - - # Settings - compiler_settings::Dict{Symbol,Any} - variable_settings::Dict{Symbol,Dict{VI,Any}} - constraint_settings::Dict{Symbol,Dict{CI,Any}} - - function VirtualModel{T}( - constructor::Union{Type{O},Function}; - kws..., - ) where {T,O<:MOI.AbstractOptimizer} - optimizer = constructor() - - return VirtualModel{T}(optimizer; kws...) - end - - function VirtualModel{T}( - optimizer::Union{O,Nothing} = nothing; - kws..., - ) where {T,O<:MOI.AbstractOptimizer} - source_model = PreQUBOModel{T}() - target_model = QUBOModel{T}() - bridge_model = MOIB.full_bridge_optimizer(source_model, T) - - new{T}( - # Underlying Optimizer # - optimizer, - - # MathOptInterface Bridges # - bridge_model, - - # Virtual Model Interface - source_model, - target_model, - Vector{VV{T}}(), - Dict{VI,VV{T}}(), - Dict{VI,VV{T}}(), - - # 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 - PBO.PBF{VI,T}(), # Final Hamiltonian - - # Settings - Dict{Symbol,Any}(), - Dict{Symbol,Dict{VI,Any}}(), - Dict{Symbol,Dict{CI,Any}}(), - ) - end - -end - -VirtualModel(args...; kws...) = VirtualModel{Float64}(args...; kws...) - -function encode!(model::VirtualModel{T}, v::VV{T}) where {T} - if !is_aux(v) - let x = source(v) - model.source[x] = v - end - end - - for y in target(v) - MOI.add_constraint(model.target_model, y, MOI.ZeroOne()) - model.target[y] = v - end - - # Add variable to collection - push!(model.variables, v) - - return v -end - -@doc raw""" - LinearEncoding - -Every linear encoding ``\xi`` is of the form -```math -\xi(\mathbf{y}) = \alpha + \sum_{i = 1}^{n} \gamma_{i} y_{i} -``` - -""" -abstract type LinearEncoding <: Encoding end - -function VirtualVariable{T}( - e::LinearEncoding, - x::Union{VI,Nothing}, - y::Vector{VI}, - γ::Vector{T}, - α::T = zero(T), -) where {T} - @assert (n = length(y)) == length(γ) - - ξ = α + PBO.PBF{VI,T}(y[i] => γ[i] for i = 1:n) - - return VirtualVariable{T}(e, x, y, ξ, nothing) -end - -function encode!( - model::VirtualModel{T}, - e::LinearEncoding, - x::Union{VI,Nothing}, - γ::Vector{T}, - α::T = zero(T), -) where {T} - n = length(γ) - y = MOI.add_variables(model.target_model, n) - v = VirtualVariable{T}(e, x, y, γ, α) - - return encode!(model, v) -end - -@doc raw""" - Mirror() - -Mirrors binary variable ``x \in \mathbb{B}`` with a twin variable ``y \in \mathbb{B}``. -""" -struct Mirror <: LinearEncoding end - -function encode!(model::VirtualModel{T}, e::Mirror, x::Union{VI,Nothing}) where {T} - return encode!(model, e, x, ones(T, 1)) -end - -@doc raw""" - Linear() -""" -struct Linear <: LinearEncoding end - -function encode!( - model::VirtualModel{T}, - e::Linear, - x::Union{VI,Nothing}, - Γ::Function, - n::Integer, -) where {T} - γ = T[Γ(i) for i = 1:n] - - return encode!(model, e, x, γ, zero(T)) -end - -@doc raw""" - Unary() - -## Integer -Let ``x \in [a, b] \subset \mathbb{Z}``, ``n = b - a`` and ``\mathbf{y} \in \mathbb{B}^{n}``. - -```math -\xi{[a, b]}(\mathbf{y}) = a + \sum_{j = 1}^{b - a} y_{j} -``` - -## Real -Given ``n \in \mathbb{N}`` for ``x \in [a, b] \subset \mathbb{R}``, - -```math -\xi{[a, b]}(\mathbf{y}) = a + \frac{b - a}{n} \sum_{j = 1}^{n} y_{j} -``` - -### Encoding error -Given ``\tau > 0``, for the expected encoding error to be less than or equal to ``\tau``, at least - -```math -n \ge 1 + \frac{b - a}{4 \tau} -``` - -binary variables become necessary. - -""" -struct Unary <: LinearEncoding end - -function encode!( - model::VirtualModel{T}, - e::Unary, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - α, β = if a < b - ceil(a), floor(b) - else - ceil(b), floor(a) - end - - # assumes: β - α > 0 - M = trunc(Int, β - α) - γ = ones(T, M) - - return encode!(model, e, x, γ, α) -end - -function encode!( - model::VirtualModel{T}, - e::Unary, - x::Union{VI,Nothing}, - a::T, - b::T, - n::Integer, -) where {T} - Γ = (b - a) / n - γ = Γ * ones(T, n) - - return encode!(model, e, x, γ, a) -end - -function encode!( - model::VirtualModel{T}, - e::Unary, - x::Union{VI,Nothing}, - a::T, - b::T, - τ::T, -) where {T} - n = ceil(Int, (1 + abs(b - a) / 4τ)) - - return encode!(model, e, x, a, b, n) -end - -@doc raw""" - Binary() - -## Integer -Let ``x \in [a, b] \subset \mathbb{Z}``, ``n = \left\lceil \log_{2}(b - a) + 1 \right\rceil`` and ``\mathbf{y} \in \mathbb{B}^{n}``. - -```math -\xi{[a, b]}(\mathbf{y}) = a + \left(b - a - 2^{n - 1} + 1\right) y_{n} + \sum_{j = 1}^{n - 1} 2^{j - 1} y_{j} -``` - -## Real -Given ``n \in \mathbb{N}`` for ``x \in [a, b] \subset \mathbb{R}``, - -```math -\xi{[a, b]}(\mathbf{y}) = a + \frac{b - a}{2^{n} - 1} \sum_{j = 1}^{n} 2^{j - 1} y_{j} -``` - -### Encoding error -Given ``\tau > 0``, for the expected encoding error to be less than or equal to ``\tau``, at least - -```math -n \ge \log_{2} \left[1 + \frac{b - a}{4 \tau}\right] -``` - -binary variables become necessary. -""" -struct Binary <: LinearEncoding end - -function encode!( - model::VirtualModel{T}, - e::Binary, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - α, β = if a < b - ceil(a), floor(b) - else - ceil(b), floor(a) - end - - # assumes: β - α > 0 - M = trunc(Int, β - α) - N = ceil(Int, log2(M + 1)) - - γ = if N == 0 - T[M+1/2] - else - T[[2^i for i = 0:N-2]; [M - 2^(N - 1) + 1]] - end - - return encode!(model, e, x, γ, α) -end - -function encode!( - model::VirtualModel{T}, - e::Binary, - x::Union{VI,Nothing}, - a::T, - b::T, - n::Integer, -) where {T} - Γ = (b - a) / (2^n - 1) - γ = Γ * 2 .^ collect(T, 0:n-1) - - return encode!(model, e, x, γ, a) -end - -function encode!( - model::VirtualModel{T}, - e::Binary, - x::Union{VI,Nothing}, - a::T, - b::T, - τ::T, -) where {T} - n = ceil(Int, log2(1 + abs(b - a) / 4τ)) - - return encode!(model, e, x, a, b, n) -end - -@doc raw""" - Arithmetic() - -## Integer -Let ``x \in [a, b] \subset \mathbb{Z}``, ``n = \left\lceil{ \frac{1}{2} {\sqrt{1 + 8 (b - a)}} - \frac{1}{2} }\right\rceil`` and ``\mathbf{y} \in \mathbb{B}^{n}``. - -```math -\xi{[a, b]}(\mathbf{y}) = a + \left( {b - a - \frac{n (n - 1)}{2}} \right) y_{n} + \sum_{j = 1}^{n - 1} j y_{j} -``` - -## Real -Given ``n \in \mathbb{N}`` for ``x \in [a, b] \subset \mathbb{R}``, - -```math -\xi{[a, b]}(\mathbf{y}) = a + \frac{b - a}{n (n + 1)} \sum_{j = 1}^{n} j y_{j} -``` - -### Encoding error -Given ``\tau > 0``, for the expected encoding error to be less than or equal to ``\tau``, at least - -```math -n \ge \frac{1}{2} \left[ 1 + \sqrt{3 + \frac{(b - a)}{2 \tau})} \right] -``` - -""" -struct Arithmetic <: LinearEncoding end - -function encode!( - model::VirtualModel{T}, - e::Arithmetic, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - α, β = if a < b - ceil(a), floor(b) - else - ceil(b), floor(a) - end - - # assumes: β - α > 0 - M = trunc(Int, β - α) - N = ceil(Int, (sqrt(1 + 8M) - 1) / 2) - - γ = T[[i for i = 1:N-1]; [M - N * (N - 1) / 2]] - - return encode!(model, e, x, γ, α) -end - -function encode!( - model::VirtualModel{T}, - e::Arithmetic, - x::Union{VI,Nothing}, - a::T, - b::T, - n::Integer, -) where {T} - Γ = 2 * (b - a) / (n * (n + 1)) - γ = Γ * collect(1:n) - - return encode!(model, e, x, γ, a) -end - -function encode!( - model::VirtualModel{T}, - e::Arithmetic, - x::Union{VI,Nothing}, - a::T, - b::T, - τ::T, -) where {T} - n = ceil(Int, (1 + sqrt(3 + (b - a) / 2τ)) / 2) - - return encode!(model, e, x, a, b, n) -end - -@doc raw""" - OneHot() - -The one-hot encoding is a linear technique used to represent a variable ``x \in \set{\gamma_{j}}_{j \in [n]}``. - -The associated encoding function is combined with a constraint assuring that only one and exactly one of the expansion's variables ``y_{j}`` is activated at a time. - -```math -\xi[\set{\gamma_{j}}_{j \in [n]}](\mathbf{y}) = \sum_{j = 1}^{n} \gamma_{j} y_{j} ~\textrm{s.t.}~ \sum_{j = 1}^{n} y_{j} = 1 -``` - -When a variable is encoded following this approach, a penalty term of the form - -```math -\rho \left[ \sum_{j = 1}^{n} y_{j} - 1 \right]^{2} -``` - -is added to the objective function. - -""" -struct OneHot <: LinearEncoding end - -function VirtualVariable{T}( - e::OneHot, - x::Union{VI,Nothing}, - y::Vector{VI}, - γ::Vector{T}, - α::T = zero(T), -) where {T} - @assert (n = length(y)) == length(γ) - - ξ = α + PBO.PBF{VI,T}(y[i] => γ[i] for i = 1:n) - h = (one(T) - PBO.PBF{VI,T}(y))^2 - - return VirtualVariable{T}(e, x, y, ξ, h) -end - -function encode!( - model::VirtualModel{T}, - e::OneHot, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - α, β = if a < b - ceil(a), floor(b) - else - ceil(b), floor(a) - end - - # assumes: β - α > 0 - γ = collect(T, α:β) - - return encode!(model, e, x, γ) -end - -function encode!( - model::VirtualModel{T}, - e::OneHot, - x::Union{VI,Nothing}, - a::T, - b::T, - n::Integer, -) where {T} - Γ = (b - a) / (n - 1) - γ = a .+ Γ * collect(T, 0:n-1) - - return encode!(model, e, x, γ) -end - -function encode!( - model::VirtualModel{T}, - e::OneHot, - x::Union{VI,Nothing}, - a::T, - b::T, - τ::T, -) where {T} - n = ceil(Int, (1 + abs(b - a) / 4τ)) - - return encode!(model, e, x, a, b, n) -end - -@doc raw""" - SequentialEncoding - -A *sequential encoding* is one of the form - -```math -\xi[\set{\gamma_{j}}_{j \in [n]}](\mathbf{y}) = \sum_{j = 1}^{n} \gamma_{j} \left({y_{j + 1} \ast y_{j}}\right) -``` - -where ``\mathbf{y} \in \mathbb{B}^{n + 1}`` and ``\ast`` is a binary operator. -""" -abstract type SequentialEncoding <: Encoding end - -function encode!( - model::VirtualModel{T}, - e::SequentialEncoding, - x::Union{VI,Nothing}, - γ::Vector{T}, - α::T = zero(T), -) where {T} - n = length(γ) - y = MOI.add_variables(model.target_model, n - 1) - v = VirtualVariable{T}(e, x, y, γ, α) - - return encode!(model, v) -end - -@doc raw""" - DomainWall() - -The Domain Wall[^Chancellor2019] encoding method is a sequential approach that requires ``n - 1`` bits to represent ``n`` distinct values. - -```math -\xi{[\set{\gamma_{j}}_{j \in [n]}]}(\mathbf{y}) = \sum_{j = 1}^{n} \gamma_{j} (y_{j} - y_{j + 1}) ~\textrm{s.t.}~ \sum_{j = 1}^{n} y_{j} \oplus y_{j + 1} = 1, y_{1} = 1, y_{n + 1} = 0 -``` - -where ``\mathbf{y} \in \mathbb{B}^{n + 1}``. - -[^Chancellor2019]: - Nicholas Chancellor, **Domain wall encoding of discrete variables for quantum annealing and QAOA**, *Quantum Science Technology 4*, 2019. -""" -struct DomainWall <: SequentialEncoding end - -function VirtualVariable{T}( - e::DomainWall, - x::Union{VI,Nothing}, - y::Vector{VI}, - γ::Vector{T}, - α::T = zero(T), -) where {T} - @assert (n = length(y)) == length(γ) - 1 - - ξ = α + PBO.PBF{VI,T}(y[i] => (γ[i] - γ[i+1]) for i = 1:n) - h = 2 * (PBO.PBF{VI,T}(y[2:n]) - PBO.PBF{VI,T}([Set{VI}([y[i], y[i-1]]) for i = 2:n])) - - return VirtualVariable{T}(e, x, y, ξ, h) -end - -function encode!( - model::VirtualModel{T}, - e::DomainWall, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - α, β = if a < b - ceil(a), floor(b) - else - ceil(b), floor(a) - end - - # assumes: β - α > 0 - M = trunc(Int, β - α) - γ = α .+ collect(T, 0:M) - - return encode!(model, e, x, γ) -end - -function encode!( - model::VirtualModel{T}, - e::DomainWall, - x::Union{VI,Nothing}, - a::T, - b::T, - n::Integer, -) where {T} - Γ = (b - a) / (n - 1) - γ = a .+ Γ * collect(T, 0:n-1) - - return encode!(model, e, x, γ) -end - -@doc raw""" - Bounded{E,T}(μ::T) where {E<:Encoding,T} - -The bounded-coefficient encoding method[^Karimi2019] consists in limiting the magnitude of the coefficients in the encoding expansion to a parameter ``\mu``. - -[^Karimi2019]: - Karimi, S. & Ronagh, P. **Practical integer-to-binary mapping for quantum annealers**. *Quantum Inf Process 18, 94* (2019). [{doi}](https://doi.org/10.1007/s11128-019-2213-x) - -This can be applied to the [`Unary`](@ref), [`Binary`](@ref) and [`Arithmetic`](@ref) encoding schemas, as discussed below. - - Bounded{Unary,T}(μ::T) where {T} - -Given ``\mu > 0``, let ``x \in [a, b] \subset \mathbb{Z}`` and ``n = b - a``. - - Bounded{Binary,T}(μ::T) where {T} - -Given ``\mu > 0``, let ``x \in [a, b] \subset \mathbb{Z}`` and ``n = b - a``. - -First, - -```math -\begin{align*} - 2^{k - 1} &\le \mu \\ -\implies k &= \left\lfloor\log_{2} \mu + 1 \right\rfloor -\end{align*} -``` - -Since - -```math -\sum_{j = 1}^{k} 2^{j - 1} = \sum_{j = 0}^{k - 1} 2^{j} = 2^{k} - 1 -``` - -then, for ``r \in \mathbb{N}`` - -```math -n = 2^{k} - 1 + r \times \mu + \epsilon \implies r = \left\lfloor \frac{n - 2^{k} + 1}{\mu} \right\rfloor -``` - -and - -```math -\epsilon = n - 2^{k} + 1 - r \times \mu -``` - -Therefore, - -```math -\xi_{\mu}{[a, b]}(\mathbf{y}) = \sum_{j = 1} \gamma_{j} y_{j} -``` - -where - -```math -\gamma_{j} = \left\lbrace\begin{array}{cl} - 2^{j} & \text{if } 1 \le j \le k \\ - \mu & \text{if } k < j < r + k \\ - n - 2^k + 1 - r \times \mu & \text{otherwise} -\end{array}\right. -``` - -""" -struct Bounded{E<:LinearEncoding,T} <: LinearEncoding - μ::T - - function Bounded{E,T}(μ::T) where {E,T} - @assert !iszero(μ) - - return new{E,T}(μ) - end -end - -function Bounded{E}(μ::T) where {E,T} - return Bounded{E,T}(μ) -end - -function encode!( - model::VirtualModel{T}, - e::Bounded{Binary,T}, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - if a < b - a = ceil(a) - b = floor(b) - else - a = ceil(b) - b = floor(a) - end - - n = round(Int, b - a) - k = floor(Int, log2(e.μ) + 1) - m = 2^k - 1 - r = floor(Int, (n - m) / e.μ) - ϵ = n - m - r * e.μ - - if iszero(ϵ) - γ = T[[2^(j - 1) for j = 1:k]; [e.μ for _ = 1:r]] - else - γ = T[[2^(j - 1) for j = 1:k]; [e.μ for _ = 1:r]; [ϵ]] - end - - return encode!(model, e, x, γ, a) -end - -function encode!( - model::VirtualModel{T}, - e::Bounded{Unary,T}, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - if a < b - a = ceil(a) - b = floor(b) - else - a = ceil(b) - b = floor(a) - end - - n = round(Int, b - a) - k = ceil(Int, e.μ - 1) - r = floor(Int, (n - k) / e.μ) - ϵ = n - k + - r * e.μ - - if iszero(ϵ) - γ = T[ones(T, k); [e.μ for _ = 1:r]] - else - γ = T[ones(T, k); [e.μ for _ = 1:r]; [ϵ]] - end - - return encode!(model, e, x, γ, a) -end - -function encode!( - model::VirtualModel{T}, - e::Bounded{Arithmetic,T}, - x::Union{VI,Nothing}, - a::T, - b::T, -) where {T} - if a < b - a = ceil(a) - else - b = floor(b) - a = ceil(b) - b = floor(a) - end - - n = round(Int, b - a) - k = floor(Int, e.μ) - m = (k * (k + 1)) ÷ 2 - r = floor(Int, (n - m) / e.μ) - ϵ = n - m + - r * e.μ - - if iszero(ϵ) - γ = T[collect(T,1:k); [e.μ for _ = 1:r]] - else - γ = T[collect(T,1:k); [e.μ for _ = 1:r]; [ϵ]] - end - - return encode!(model, e, x, γ, a) -end diff --git a/src/model/wrapper.jl b/src/model/wrapper.jl deleted file mode 100644 index bab0f250..00000000 --- a/src/model/wrapper.jl +++ /dev/null @@ -1,106 +0,0 @@ -# Notes on the optimize! interface -# After `JuMP.optimize!(model)` there are a few layers before reaching -# 1. `MOI.optimize!(::VirtualModel, ::MOI.ModelLike)` -# Then, -# 2. `MOI.copy_to(::VirtualModel, ::MOI.ModelLike)` -# 3. `MOI.optimize!(::VirtualModel)` -# is called. - -function MOI.optimize!(model::VirtualModel) - index_map = MOIU.identity_index_map(model.source_model) - - # De facto JuMP to QUBO Compilation - ToQUBO.toqubo!(model) - - if !isnothing(model.optimizer) - MOI.optimize!(model.optimizer, model.target_model) - end - - return (index_map, false) -end - -function MOI.copy_to(model::VirtualModel{T}, source::MOI.ModelLike) where {T} - if !MOI.is_empty(model) - error("QUBO Model is not empty") - end - - # Copy to PreQUBOModel + Add Bridges - bridge_model = MOIB.full_bridge_optimizer(model.source_model, T) - - # Copy to source using bridges - return MOI.copy_to(bridge_model, source) # index_map -end - -# Objective Function Support -MOI.supports( - ::VirtualModel{T}, - ::MOI.ObjectiveFunction{<:Union{VI,SAF{T},SQF{T}}}, -) where {T} = true - -# Constraint Support -MOI.supports_constraint( - ::VirtualModel{T}, - ::Type{VI}, - ::Type{ - <:Union{MOI.ZeroOne,MOI.Integer,MOI.Interval{T},MOI.LessThan{T},MOI.GreaterThan{T}}, - }, -) where {T} = true - -MOI.supports_constraint( - ::VirtualModel{T}, - ::Type{<:Union{SAF{T},SQF{T}}}, - ::Type{<:Union{MOI.EqualTo{T},MOI.LessThan{T}}}, -) where {T} = true - -MOI.supports_constraint( - ::VirtualModel{T}, - ::Type{<:MOI.VectorOfVariables}, - ::Type{<:MOI.SOS1}, -) where {T} = true - -MOI.supports_add_constrained_variable( - ::VirtualModel{T}, - ::Type{ - <:Union{MOI.ZeroOne,MOI.Integer,MOI.Interval{T},MOI.LessThan{T},MOI.GreaterThan{T}}, - }, -) where {T} = true - - -PBO.showvar(x::VI) = PBO.showvar(x.value) - -PBO.varlt(x::VI, y::VI) = PBO.varlt(x.value, y.value) - -function PBO.varlt(x::Set{V}, y::Set{V}) where {V} - if length(x) == length(y) - xv = sort!(collect(x); lt = PBO.varlt) - yv = sort!(collect(y); lt = PBO.varlt) - - for (xi, yi) in zip(xv, yv) - if xi == yi - continue - else - return PBO.varlt(xi, yi) - end - end - - return false - else - return length(x) < length(y) - end -end - -const Optimizer{T} = VirtualModel{T} - -# QUBOTools -function qubo(model, type::Type = Dict) - n, L, Q, α, β = MOI.get(model, Attributes.QUBONormalForm()) - - return QUBOTools.qubo(type, n, L, Q, α, β) -end - -function ising(model, type::Type = Dict) - n, L̄, Q̄, ᾱ, β̄ = MOI.get(model, Attributes.QUBONormalForm()) - L, Q, α, β = QUBOTools.cast(QUBOTools.𝔹, QUBOTools.𝕊, L̄, Q̄, ᾱ, β̄) - - return QUBOTools.ising(type, n, L, Q, α, β) -end diff --git a/src/virtual/encoding.jl b/src/virtual/encoding.jl new file mode 100644 index 00000000..16d51995 --- /dev/null +++ b/src/virtual/encoding.jl @@ -0,0 +1,90 @@ +function Encoding.encode!(model::Model{T}, v::Variable{T}) where {T} + x = source(v) + + if !isnothing(x) + model.source[x] = v + end + + for y in target(v) + MOI.add_constraint(model.target_model, y, MOI.ZeroOne()) + model.target[y] = v + end + + # Add variable to collection + push!(model.variables, v) + + return v +end + +function Encoding.encode!(model::Model{T}, x::Union{VI,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) + else + return MOI.add_variables(model.target_model, nv) + end + end + + v = Variable{T}(e, x, y, ξ, χ) + + return Encoding.encode!(model, v) +end + +function Encoding.encode!( + model::Model{T}, + x::Union{VI,Nothing}, + e::VariableEncodingMethod, + γ::AbstractVector{T}, +) where {T} + y, ξ, χ = Encoding.encode(e, γ) do (nv::Union{Integer,Nothing} = nothing) + if isnothing(nv) + return MOI.add_variable(model.target_model) + else + return MOI.add_variables(model.target_model, nv) + end + end + + v = Variable{T}(e, x, y, ξ, χ) + + return Encoding.encode!(model, v) +end + +function encode!( + model::Model{T}, + x::Union{VI,Nothing}, + e::VariableEncodingMethod, + S::Tuple{T,T}; + tol::Union{T,Nothing} = nothing, +) where {T} + y, ξ, χ = Encoding.encode(e, S; tol) do (nv::Union{Integer,Nothing} = nothing) + if isnothing(nv) + return MOI.add_variable(model.target_model) + else + return MOI.add_variables(model.target_model, nv) + end + end + + v = Variable{T}(e, x, y, ξ, χ) + + return Encoding.encode!(model, v) +end + +function Encoding.encode!( + model::Model{T}, + x::Union{VI,Nothing}, + e::VariableEncodingMethod, + S::Tuple{T,T}, + n::Integer, +) where {T} + y, ξ, χ = Encoding.encode(e, S, n) do (nv::Union{Integer,Nothing} = nothing) + if isnothing(nv) + return MOI.add_variable(model.target_model) + else + return MOI.add_variables(model.target_model, nv) + end + end + + v = Variable{T}(e, x, y, ξ, χ) + + return Encoding.encode!(model, v) +end diff --git a/src/virtual/interface.jl b/src/virtual/interface.jl new file mode 100644 index 00000000..2148ad88 --- /dev/null +++ b/src/virtual/interface.jl @@ -0,0 +1,24 @@ +@doc raw""" + source +""" +function source end + +@doc raw""" + target +""" +function target end + +@doc raw""" + encoding +""" +function encoding end + +@doc raw""" + expansion +""" +function expansion end + +@doc raw""" + penaltyfn +""" +function penaltyfn end diff --git a/src/virtual/model.jl b/src/virtual/model.jl new file mode 100644 index 00000000..0b4717b2 --- /dev/null +++ b/src/virtual/model.jl @@ -0,0 +1,80 @@ +@doc raw""" + Model{T}(optimizer::Union{Nothing, Type{<:MOI.AbstractOptimizer}} = nothing) where {T} + +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} + + # MathOptInterface Bridges # + bridge_model::MOIB.LazyBridgeOptimizer{PreQUBOModel{T}} + + # 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}} + + # 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 + H::PBO.PBF{VI,T} # Final Objective Function + + # Settings + compiler_settings::Dict{Symbol,Any} + variable_settings::Dict{Symbol,Dict{VI,Any}} + constraint_settings::Dict{Symbol,Dict{CI,Any}} + + function Model{T}( + constructor::Union{Type{O},Function}; + kws..., + ) where {T,O<:MOI.AbstractOptimizer} + optimizer = constructor() + + return Model{T}(optimizer; kws...) + end + + function Model{T}( + optimizer::Union{O,Nothing} = nothing; + kws..., + ) where {T,O<:MOI.AbstractOptimizer} + source_model = PreQUBOModel{T}() + target_model = QUBOModel{T}() + bridge_model = MOIB.full_bridge_optimizer(source_model, T) + + new{T}( + # Underlying Optimizer # + optimizer, + + # MathOptInterface Bridges # + bridge_model, + + # Virtual Model Interface + source_model, + target_model, + Vector{Variable{T}}(), + Dict{VI,Variable{T}}(), + Dict{VI,Variable{T}}(), + + # 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 + PBO.PBF{VI,T}(), # Final Objective Function + + # Settings + Dict{Symbol,Any}(), + Dict{Symbol,Dict{VI,Any}}(), + Dict{Symbol,Dict{CI,Any}}(), + ) + end +end + +Model(args...; kws...) = Model{Float64}(args...; kws...) diff --git a/src/virtual/variable.jl b/src/virtual/variable.jl new file mode 100644 index 00000000..4d3e4cca --- /dev/null +++ b/src/virtual/variable.jl @@ -0,0 +1,28 @@ +@doc raw""" + Variable{T} + +""" +struct Variable{T} + e::VariableEncodingMethod + x::Union{VI,Nothing} # Source variable (if there is one) + 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}, + y::Vector{VI}, + ξ::PBO.PBF{VI,T}, + χ::Union{PBO.PBF{VI,T},Nothing}, + ) where {T} + return new{T}(e, x, y, ξ, χ) + end +end + +# Virtual mapping interface +source(v::Variable) = v.x +target(v::Variable) = v.y +encoding(v::Variable) = v.e +expansion(v::Variable) = v.ξ +penaltyfn(v::Variable) = v.χ \ No newline at end of file diff --git a/src/virtual/virtual.jl b/src/virtual/virtual.jl new file mode 100644 index 00000000..f9ed0bda --- /dev/null +++ b/src/virtual/virtual.jl @@ -0,0 +1,21 @@ +module Virtual + +# Imports +import MathOptInterface as MOI +import PseudoBooleanOptimization as PBO + +import ..ToQUBO: QUBOModel, PreQUBOModel +import ..Encoding: Encoding, VariableEncodingMethod, encode! + +# Constants +const MOIB = MOI.Bridges +const VI = MOI.VariableIndex +const CI{F,S} = MOI.ConstraintIndex{F,S} + +include("interface.jl") + +include("variable.jl") +include("model.jl") +include("encoding.jl") + +end # module Virtual diff --git a/src/wrapper.jl b/src/wrapper.jl new file mode 100644 index 00000000..22c2be67 --- /dev/null +++ b/src/wrapper.jl @@ -0,0 +1,169 @@ +# Notes on the optimize! interface +# After `JuMP.optimize!(model)` there are a few layers before reaching +# 1. `MOI.optimize!(::Optimizer, ::MOI.ModelLike)` +# Then, +# 2. `MOI.copy_to(::Optimizer, ::MOI.ModelLike)` +# 3. `MOI.optimize!(::Optimizer)` +# is called. +const Optimizer{T} = Virtual.Model{T} + +function MOI.is_empty(model::Optimizer) + return MOI.is_empty(model.source_model) +end + +function MOI.empty!(model::Optimizer) + MOI.empty!(model.source_model) + + Compiler.reset!(model) + + # Underlying Optimizer + if !isnothing(model.optimizer) + MOI.empty!(model.optimizer) + end + + return nothing +end + +function MOI.optimize!(model::Optimizer) + index_map = MOIU.identity_index_map(model.source_model) + + # De facto JuMP to QUBO Compilation + ToQUBO.Compiler.compile!(model) + + if !isnothing(model.optimizer) + MOI.optimize!(model.optimizer, model.target_model) + end + + return (index_map, false) +end + +function _copy_constraints!(::Type{F}, ::Type{S}, source, target, index_map) where {F,S} + for ci in MOI.get(source, MOI.ListOfConstraintIndices{F,S}()) + f = MOI.get(source, MOI.ConstraintFunction(), ci) + s = MOI.get(source, MOI.ConstraintSet(), ci) + + index_map[ci] = MOI.add_constraint(target, f, s) + end + + return nothing +end + +function _copy_constraint_attributes( + ::Type{F}, + ::Type{S}, + source, + target, + index_map, +) where {F,S} + for attr in MOI.get(source, MOI.ListOfConstraintAttributesSet{F,S}()) + for ci in MOI.get(source, MOI.ListOfConstraintIndices{F,S}()) + MOI.set(target, attr, index_map[ci], MOI.get(source, attr, ci)) + end + end + + return nothing +end + +function MOI.copy_to(model::Optimizer{T}, source::MOI.ModelLike) where {T} + if !MOI.is_empty(model) + error("QUBO Model is not empty") + end + + variable_indices = MOI.get(source, MOI.ListOfVariableIndices()) + constraint_types = MOI.get(source, MOI.ListOfConstraintTypesPresent()) + + # Build Index Map + index_map = MOIU.IndexMap() + + # Copy to PreQUBOModel + Add Bridges + bridge_model = MOIB.full_bridge_optimizer(model.source_model, T) + + # Copy Objective Function + let F = MOI.get(source, MOI.ObjectiveFunctionType()) + MOI.set( + bridge_model, + MOI.ObjectiveFunction{F}(), + MOI.get(source, MOI.ObjectiveFunction{F}()), + ) + end + + # Copy Objective Sense + MOI.set(bridge_model, MOI.ObjectiveSense(), MOI.get(source, MOI.ObjectiveSense())) + + # Copy Variables + for vi in variable_indices + index_map[vi] = MOI.add_variable(bridge_model) + end + + # Copy Constraints + for (F, S) in constraint_types + _copy_constraints!(F, S, source, bridge_model, index_map) + end + + # Copy Attributes + for attr in MOI.get(source, MOI.ListOfModelAttributesSet()) + MOI.set(model, attr, MOI.get(source, attr)) + end + + for attr in MOI.get(source, MOI.ListOfVariableAttributesSet()) + for vi in variable_indices + MOI.set(model, attr, index_map[vi], MOI.get(source, attr, vi)) + end + end + + for (F, S) in constraint_types + _copy_constraint_attributes(F, S, source, model, index_map) + end + + model.bridge_model = bridge_model + + return index_map +end + +# Objective Function Support +MOI.supports( + ::Optimizer{T}, + ::MOI.ObjectiveFunction{<:Union{VI,SAF{T},SQF{T}}}, +) where {T} = true + +# Constraint Support +MOI.supports_constraint( + ::Optimizer{T}, + ::Type{VI}, + ::Type{ + <:Union{MOI.ZeroOne,MOI.Integer,MOI.Interval{T},MOI.LessThan{T},MOI.GreaterThan{T}}, + }, +) where {T} = true + +MOI.supports_constraint( + ::Optimizer{T}, + ::Type{<:Union{SAF{T},SQF{T}}}, + ::Type{<:Union{MOI.EqualTo{T},MOI.LessThan{T}}}, +) where {T} = true + +MOI.supports_constraint( + ::Optimizer{T}, + ::Type{<:MOI.VectorOfVariables}, + ::Type{<:MOI.SOS1}, +) where {T} = true + +MOI.supports_add_constrained_variable( + ::Optimizer{T}, + ::Type{ + <:Union{MOI.ZeroOne,MOI.Integer,MOI.Interval{T},MOI.LessThan{T},MOI.GreaterThan{T}}, + }, +) where {T} = true + +function Base.show(io::IO, model::Optimizer) + print( + io, + """ + $(MOI.get(model, MOI.SolverName())) + $(model.source_model) + """, + ) +end + +function QUBOTools.backend(model::Optimizer{T}) where {T} + return QUBOTools.Model{T}(model.target_model) +end diff --git a/test/Project.toml b/test/Project.toml index 1dd74fd3..66cfb181 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,10 +1,12 @@ [deps] JuMP = "4076af6c-e467-56ae-b986-b466b2749572" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" +PseudoBooleanOptimization = "c8fa9a04-bc42-452d-8558-dc51757be744" QUBODrivers = "a3f166f7-2cd3-47b6-9e1e-6fbfe0449eb0" +QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [compat] JuMP = "1" -QUBODrivers = "0.2" +QUBODrivers = "0.3" diff --git a/test/assets/assets.jl b/test/assets/assets.jl deleted file mode 100644 index 7f22d95a..00000000 --- a/test/assets/assets.jl +++ /dev/null @@ -1,161 +0,0 @@ -module Assets -using ToQUBO: PBO - -const S = Symbol -const T = Float64 - -const f = PBO.PBF{S,T}( - Dict{Set{S},T}( - Set{S}() => 0.5, - Set{S}([:x]) => 1.0, - Set{S}([:y]) => 1.0, - Set{S}([:z]) => 1.0, - Set{S}([:x, :y]) => -2.0, - Set{S}([:x, :z]) => -2.0, - Set{S}([:y, :z]) => -2.0, - Set{S}([:x, :y, :z]) => 3.0, - ), -) -const g = PBO.PBF{S,T}(Dict{Set{S},T}(Set{S}() => 1.0)) -const h = PBO.PBF{S,T}( - Dict{Set{S},T}( - Set{S}([:x]) => 1.0, - Set{S}([:y]) => 1.0, - Set{S}([:z]) => 1.0, - Set{S}([:w]) => 1.0, - Set{S}() => 1.0, - ), -) -const p = PBO.PBF{S,T}( - Dict{Set{S},T}(Set{S}() => 0.5, Set{S}([:x]) => 1.0, Set{S}([:x, :y]) => -2.0), -) -const q = PBO.PBF{S,T}( - Dict{Set{S},T}(Set{S}() => 0.5, Set{S}([:y]) => 1.0, Set{S}([:x, :y]) => 2.0), -) -const r = PBO.PBF{S,T}(Set{S}() => 1.0, Set{S}([:z]) => -1.0) -const s = PBO.PBF{S,T}(Set{S}() => 0.0, Set{S}([:x, :y, :z]) => 3.0) -const t = PBO.PBF{S,T}(:x, :y, -1.0) -const u = PBO.PBF{S,T}(:x, :y) -const v = PBO.PBF{S,T}(:y, :z) -const w = PBO.PBF{S,T}(:x, -100.0) -const α = PBO.PBF{S,T}(:w, :x) -const β = PBO.PBF{S,T}(:x) - -x = Set{Symbol}([:x]) -y = Set{Symbol}([:y]) -z = Set{Symbol}([:z]) - -const PBF_CONSTRUCTOR_LIST = [ - (PBO.PBF{S,T}(0.0), PBO.PBF{S,T}()), - (zero(PBO.PBF{S,T}), PBO.PBF{S,T}()), - ( - f, - PBO.PBF{S,T}( - nothing => 0.5, - :x => 1.0, - :y => 1.0, - :z => 1.0, - [:x, :y] => -2.0, - [:x, :z] => -2.0, - [:y, :z] => -2.0, - [:x, :y, :z] => 3.0, - ), - ), - (g, PBO.PBF{S,T}(1.0)), - (g, one(PBO.PBF{S,T})), - (h, PBO.PBF{S,T}([:x, :y, :z, :w, nothing])), - (p, PBO.PBF{S,T}((nothing, 0.5), :x, [:x, :y] => -2.0)), - (q, PBO.PBF{S,T}(nothing => 0.5, :y, [:x, :y] => 2.0)), - (r, PBO.PBF{S,T}(nothing, :z => -1.0)), - (s, PBO.PBF{S,T}(S[] => 0.0, Set{S}([:x, :y, :z]) => 3.0)), -] - -const PBF_OPERATOR_LIST = [ - (+) => [ - (u, v) => PBO.PBF{S,T}(:x, :y => 2.0, :z), - (w, β) => PBO.PBF{S,T}(:x => 2.0, -100.0), - (u, 4.0) => PBO.PBF{S,T}(:x, :y, 4.0), - (p, q) => PBO.PBF{S,T}(1.0, :x, :y), - (q, p) => PBO.PBF{S,T}(1.0, :x, :y), - (p, q, r) => PBO.PBF{S,T}(2.0, :x => 1.0, :y => 1.0, :z => -1.0), - (r, q, p) => PBO.PBF{S,T}(2.0, :x => 1.0, :y => 1.0, :z => -1.0), - (s, 3.0) => PBO.PBF{S,T}(3.0, [:x, :y, :z] => 3.0), - (3.0, s) => PBO.PBF{S,T}(3.0, [:x, :y, :z] => 3.0), - ], - (-) => [ - (u, v) => PBO.PBF{S,T}(:x, :z => -1.0), - (β, w) => PBO.PBF{S,T}(100.0), - (u, -2.0) => PBO.PBF{S,T}(:x, :y, 2.0), - ], - (*) => [ - (u, v) => PBO.PBF{S,T}([:x, :y], [:x, :z], :y, [:y, :z]), - (α, v) => PBO.PBF{S,T}([:w, :y], [:w, :z], [:x, :y], [:x, :z]), - (β, w) => PBO.PBF{S,T}(:x => -99.0), - (u, -2.0) => PBO.PBF{S,T}(:x => -2.0, :y => -2.0), - (t, t) => PBO.PBF{S,T}([:x, :y] => 2.0, :x => -1.0, :y => -1.0, 1.0), - (p, q) => PBO.PBF{S,T}(0.25, :x => 0.5, :y => 0.5, [:x, :y] => -3.0), - (q, p) => PBO.PBF{S,T}(0.25, :x => 0.5, :y => 0.5, [:x, :y] => -3.0), - (p, -0.5) => PBO.PBF{S,T}(-0.25, :x => -0.5, [:x, :y] => 1.0), - (-0.5, p) => PBO.PBF{S,T}(-0.25, :x => -0.5, [:x, :y] => 1.0), - ], - (/) => [ - (p, 2.0) => PBO.PBF{S,T}(0.25, :x => 0.5, [:x, :y] => -1.0), - (p, 0.0) => DivideError, - ], - (^) => [ - (p, 0) => one(PBO.PBF{S,T}), - (q, 0) => one(PBO.PBF{S,T}), - (r, 0) => one(PBO.PBF{S,T}), - (s, 0) => one(PBO.PBF{S,T}), - (t, 0) => one(PBO.PBF{S,T}), - (u, 0) => one(PBO.PBF{S,T}), - (v, 0) => one(PBO.PBF{S,T}), - (w, 0) => one(PBO.PBF{S,T}), - (β, 0) => one(PBO.PBF{S,T}), - (α, 0) => one(PBO.PBF{S,T}), - (p, 1) => p, - (q, 1) => q, - (r, 1) => r, - (s, 1) => s, - (t, 1) => t, - (u, 1) => u, - (v, 1) => v, - (w, 1) => w, - (β, 1) => β, - (α, 1) => α, - (p, 2) => PBO.PBF{S,T}(0.25, :x => 2.0, [:x, :y] => -2.0), - (q, 2) => PBO.PBF{S,T}(0.25, :y => 2.0, [:x, :y] => 10.0), - (r, 2) => PBO.PBF{S,T}(1.0, :z => -1.0), - (s, 2) => PBO.PBF{S,T}([:x, :y, :z] => 9.0), - (r, 3) => PBO.PBF{S,T}(1.0, :z => -1.0), - (s, 3) => PBO.PBF{S,T}([:x, :y, :z] => 27.0), - (r, 4) => PBO.PBF{S,T}(1.0, :z => -1.0), - ], -] - -const PBF_EVALUATION_LIST = [ - "dict" => [], - "set" => [(q, x) => 0.5, (q, y) => 1.5, (q, z) => 0.5, (r, x) => 1.0, (r, y) => 1.0, (r, z) => 0.0, (s, x) => 0.0, (s, y) => 0.0, (s, z) => 0.0, (p, x) => 1.5, (p, y) => 0.5, (p, z) => 0.5], -] - -const PBF_QUBOTOOLS_LIST = [ - (PBO.variable_map) => [ - (f,) => Dict{Symbol,Int}(:x => 1, :y => 2, :z => 3), - (g,) => Dict{Symbol,Int}(), - (h,) => Dict{Symbol,Int}(:x => 2, :y => 3, :z => 4, :w => 1), - ], - (PBO.variable_inv) => [ - (f,) => Dict{Int,Symbol}(1 => :x, 2 => :y, 3 => :z), - (g,) => Dict{Int,Symbol}(), - (h,) => Dict{Int,Symbol}(2 => :x, 3 => :y, 4 => :z, 1 => :w), - ], - (PBO.variable_set) => [ - (f,) => Set{Symbol}([:x, :y, :z]), - (g,) => Set{Symbol}([]), - (h,) => Set{Symbol}([:x, :y, :z, :w]), - ], - (PBO.variables) => - [(f,) => Symbol[:x, :y, :z], (g,) => Symbol[], (h,) => Symbol[:w, :x, :y, :z]], -] - -end \ No newline at end of file diff --git a/test/examples/integer/integer.jl b/test/examples/integer/integer.jl deleted file mode 100644 index e4b52558..00000000 --- a/test/examples/integer/integer.jl +++ /dev/null @@ -1,7 +0,0 @@ -include("integer_primes.jl") - -function test_integer() - @testset "Integer Programs" verbose = true begin - test_integer_primes() - end -end \ No newline at end of file diff --git a/test/examples/integer/integer_primes.jl b/test/examples/integer/integer_primes.jl deleted file mode 100644 index 5ff1ade9..00000000 --- a/test/examples/integer/integer_primes.jl +++ /dev/null @@ -1,57 +0,0 @@ -function test_integer_primes() - @testset "Prime Factoring: 15 = 3 × 5" begin - # Problem Data # - R = 15 - a = ceil(Int, √R) - b = ceil(Int, R ÷ 2) - - # Solution Data # - ᾱ = 1 - β̄ = 49 - Q̄ = [ - -24 16 15 15 20 20 18 0 8 0 - 0 -40 60 60 -20 -20 0 40 -8 8 - 0 0 -40 98 -20 0 -18 -40 -8 -8 - 0 0 0 -40 0 -20 -18 -40 -8 -8 - 0 0 0 0 20 0 0 0 0 0 - 0 0 0 0 0 20 0 0 0 0 - 0 0 0 0 0 0 18 0 0 0 - 0 0 0 0 0 0 0 40 0 0 - 0 0 0 0 0 0 0 0 16 0 - 0 0 0 0 0 0 0 0 0 8 - ] - - - ρ̄ = 1 - p̄ = 3 - q̄ = 5 - - model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) - - @variable(model, 2 <= p <= a, Int) - @variable(model, a <= q <= b, Int) - @constraint(model, c1, p * q == R) - - set_optimizer_attribute(model, TQA.StableQuadratization(), true) - - optimize!(model) - - # Reformulation - ρ = MOI.get(model, TQA.ConstraintEncodingPenalty(), c1) - Q, α, β = ToQUBO.qubo(model, Matrix) - - @test ρ ≈ ρ̄ - @test α ≈ ᾱ - @test β ≈ β̄ - @test Q ≈ Q̄ - - # Solutions - p̂ = trunc(Int, value(p)) - q̂ = trunc(Int, value(q)) - - @test p̂ == p̄ - @test q̂ == q̄ - - return nothing - end -end \ No newline at end of file diff --git a/test/examples/logical/logical_tsp.jl b/test/examples/logical/logical_tsp.jl deleted file mode 100644 index a40f8f5e..00000000 --- a/test/examples/logical/logical_tsp.jl +++ /dev/null @@ -1,73 +0,0 @@ -function test_logical_tsp() - @testset "TSP: 16 variables" begin - # Problem Data # - n = 4 - D = [ - 0 1 5 4 - 1 0 2 6 - 5 2 0 3 - 4 6 3 0 - ] - - # Penalty Choice - ρ̄ = fill(169, 2n) - - # Solution Data - Q̄ = [ - -338 676 1 676 5 0 676 5 5 4 - 0 -675 676 681 679 5 681 682 6 6 - 0 0 -338 3 676 2 6 676 6 0 - 0 0 0 -676 681 676 681 10 681 7 - 0 0 0 0 -674 676 4 682 681 6 - 0 0 0 0 0 -338 5 5 676 3 - 0 0 0 0 0 0 -672 682 683 676 - 0 0 0 0 0 0 0 -676 682 676 - 0 0 0 0 0 0 0 0 -673 676 - 0 0 0 0 0 0 0 0 0 -338 - ] - - ᾱ = 1 - β̄ = 1352 - x̄ = Set{Matrix{Int}}([ - [0 0 0 1; 0 0 1 0; 0 1 0 0; 1 0 0 0], - [1 0 0 0; 0 0 0 1; 0 0 1 0; 0 1 0 0], - [0 1 0 0; 1 0 0 0; 0 0 0 1; 0 0 1 0], - [0 0 1 0; 0 1 0 0; 1 0 0 0; 0 0 0 1], - ]) - ȳ = 10 - - # Model # - model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) - - @variable(model, x[1:n, 1:n], Bin, Symmetric) - @objective( - model, - Min, - sum(D[i, j] * x[i, k] * x[j, (k%n)+1] for i = 1:n, j = 1:n, k = 1:n) - ) - @constraint(model, ci[i = 1:n], sum(x[i, :]) == 1) - @constraint(model, ck[k = 1:n], sum(x[:, k]) == 1) - - optimize!(model) - - # Reformulation - ρi = MOI.get.(model, TQA.ConstraintEncodingPenalty(), ci) - ρk = MOI.get.(model, TQA.ConstraintEncodingPenalty(), ck) - ρ = [ρi; ρk] - Q, α, β = ToQUBO.qubo(model, Matrix) - - @test ρ ≈ ρ̄ - @test α ≈ ᾱ - @test β ≈ β̄ - @test Q ≈ Q̄ - - # Solutions - x̂ = trunc.(Int, value.(x)) - ŷ = objective_value(model) - - @test x̂ ∈ x̄ - @test ŷ ≈ ȳ - - return nothing - end -end \ No newline at end of file diff --git a/test/examples/quadratic/quadratic_1.jl b/test/examples/quadratic/quadratic_1.jl deleted file mode 100644 index 5e45c6cc..00000000 --- a/test/examples/quadratic/quadratic_1.jl +++ /dev/null @@ -1,74 +0,0 @@ -function test_quadratic_1() - @testset "3 variables, 1 constraint" begin - # Problem Data - n = 3 - A = [ - -1 2 2 - 2 -1 2 - 2 2 -1 - ] - b = 6 - - # Penalty Choice - ρ̄ = -16 - - # Solution - Q̄ = [ - -209 740 740 32 64 128 64 -1152 -128 -256 -512 -256 -128 -256 -512 -256 0 0 0 0 - 0 -209 -412 -96 -192 -384 -192 1152 128 256 512 256 0 0 0 0 -128 -256 -512 -256 - 0 0 -209 -224 -448 -896 -448 1152 0 0 0 0 128 256 512 256 128 256 512 256 - 0 0 0 176 -64 -128 -64 0 128 0 0 0 128 0 0 0 128 0 0 0 - 0 0 0 0 320 -256 -128 0 0 256 0 0 0 256 0 0 0 256 0 0 - 0 0 0 0 0 512 -256 0 0 0 512 0 0 0 512 0 0 0 512 0 - 0 0 0 0 0 0 320 0 0 0 0 256 0 0 0 256 0 0 0 256 - 0 0 0 0 0 0 0 -1152 0 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 -128 0 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 -256 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 -512 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 -256 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 -128 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 -256 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -512 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -256 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -128 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -256 0 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -512 0 - 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -256 - ] - - ᾱ = 1 - β̄ = -576 - - x̄ = Set{Vector{Int}}([[0, 1, 1], [1, 0, 1], [1, 1, 0]]) - ȳ = 2 - - # Model - model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) - - @variable(model, x[1:n], Bin) - @objective(model, Max, x'A * x) - @constraint(model, c1, x'A * x <= b) - - set_optimizer_attribute(model, TQA.StableQuadratization(), true) - - optimize!(model) - - # Reformulation - ρ = MOI.get(model, TQA.ConstraintEncodingPenalty(), c1) - Q, α, β = ToQUBO.qubo(model, Matrix) - - @test ρ ≈ ρ̄ - @test α ≈ ᾱ - @test β ≈ β̄ - @test Q ≈ Q̄ - - # Solutions - x̂ = trunc.(Int, value.(x)) - ŷ = objective_value(model) - - @test x̂ ∈ x̄ - @test ŷ ≈ ȳ - - return nothing - end -end \ No newline at end of file diff --git a/test/integration/attributes.jl b/test/integration/attributes.jl deleted file mode 100644 index d625ce19..00000000 --- a/test/integration/attributes.jl +++ /dev/null @@ -1,37 +0,0 @@ -function test_attributes() - @testset "Attributes" begin - model = JuMP.Model(() -> ToQUBO.Optimizer(RandomSampler.Optimizer)) - - # MOI Attributes - @test MOI.get(model, MOI.NumberOfVariables()) == 0 - - @test MOI.get(model, MOI.TimeLimitSec()) |> isnothing - MOI.set(model, MOI.TimeLimitSec(), 1.0) - @test MOI.get(model, MOI.TimeLimitSec()) == 1.0 - - # ToQUBO Attributes - @test MOI.get(model, TQA.Quadratize()) === false - MOI.set(model, TQA.Quadratize(), true) - @test MOI.get(model, TQA.Quadratize()) === true - - # Solver Attributes - @test MOI.get(model, RandomSampler.RandomSeed()) |> isnothing - MOI.set(model, RandomSampler.RandomSeed(), 13) - @test MOI.get(model, RandomSampler.RandomSeed()) == 13 - - @test MOI.get(model, RandomSampler.NumberOfReads()) == 1_000 - MOI.set(model, RandomSampler.NumberOfReads(), 13) - @test MOI.get(model, RandomSampler.NumberOfReads()) == 13 - - # Raw Attributes - @test MOI.get(model, MOI.RawOptimizerAttribute("seed")) == 13 - MOI.set(model, MOI.RawOptimizerAttribute("seed"), 1_001) - @test MOI.get(model, MOI.RawOptimizerAttribute("seed")) == 1_001 - - @test MOI.get(model, MOI.RawOptimizerAttribute("num_reads")) == 13 - MOI.set(model, MOI.RawOptimizerAttribute("num_reads"), 1_001) - @test MOI.get(model, MOI.RawOptimizerAttribute("num_reads")) == 1_001 - end - - return nothing -end \ No newline at end of file diff --git a/test/examples/continuous/continuous.jl b/test/integration/examples/continuous/continuous.jl similarity index 78% rename from test/examples/continuous/continuous.jl rename to test/integration/examples/continuous/continuous.jl index c21a4d47..9bfb295b 100644 --- a/test/examples/continuous/continuous.jl +++ b/test/integration/examples/continuous/continuous.jl @@ -2,6 +2,6 @@ include("continuous_1.jl") function test_continuous() @testset "Continuous Variables" verbose = true begin - test_continuous_1() + test_continuous_1() end -end \ No newline at end of file +end diff --git a/test/examples/continuous/continuous_1.jl b/test/integration/examples/continuous/continuous_1.jl similarity index 57% rename from test/examples/continuous/continuous_1.jl rename to test/integration/examples/continuous/continuous_1.jl index 1641de33..d06a0d8c 100644 --- a/test/examples/continuous/continuous_1.jl +++ b/test/integration/examples/continuous/continuous_1.jl @@ -1,3 +1,20 @@ +""" + +Let x ∈ [0, 1]ⁿˣⁿ. + + ┌ -1 2 2 ┐ +Let A = │ 2 -1 2 │ + └ 2 2 -1 ┘ + +Each variable is encoded according to a tolerance τ = 0.1, using the binary method. + +This means that each variable will take 2 bits. + +xᵢ = -1/2 + 1/3 xᵢ,₁ + 2/3 xᵢ,₂ + +where xᵢ,ⱼ ∈ 𝔹. + +""" function test_continuous_1() @testset "9 variables ∈ [0, 1]" begin # Problem Data @@ -9,7 +26,7 @@ function test_continuous_1() ] # Solution - Q̄ = [ + Q̄ = (1/3) * [ -1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 @@ -18,10 +35,10 @@ function test_continuous_1() 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 - 0 0 0 0 0 0 0 0 0 0 0 -2 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 -1 0 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 -2 0 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 + 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 0 0 0 @@ -31,37 +48,43 @@ function test_continuous_1() ] ᾱ = 1 - β̄ = 0 + β̄ = -9/2 - x̄ = Set{Matrix{Int}}([ - [0 1 1;1 0 1;1 1 0] - ]) - ȳ = 12 + x̄ = [ + -1/2 1/2 1/2 + 1/2 -1/2 1/2 + 1/2 1/2 -1/2 + ] + ȳ = 15/2 # Model model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) - set_optimizer_attribute(model, TQA.DefaultVariableEncodingATol(), 1E-1) - - @variable(model, 0 <= x[1:n,1:n] <= 1) + @variable(model, -1/2 <= x[1:n, 1:n] <= 1/2) @objective(model, Max, sum(A .* x)) + + set_attribute(model, Attributes.DefaultVariableEncodingATol(), 0.1) + set_attribute(model, Attributes.StableCompilation(), true) optimize!(model) # Reformulation - Q, α, β = ToQUBO.qubo(model, Matrix) + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) + @test n == 18 @test α ≈ ᾱ @test β ≈ β̄ - @test Q ≈ Q̄/3 + @test Q̂ ≈ Q̄ # Solutions - x̂ = trunc.(Int, value.(x)) + x̂ = value.(x) ŷ = objective_value(model) - @test x̂ ∈ x̄ + @test x̂ ≈ x̄ @test ŷ ≈ ȳ return nothing end -end \ No newline at end of file +end diff --git a/test/examples/examples.jl b/test/integration/examples/examples.jl similarity index 74% rename from test/examples/examples.jl rename to test/integration/examples/examples.jl index b515d813..34fa4559 100644 --- a/test/examples/examples.jl +++ b/test/integration/examples/examples.jl @@ -2,16 +2,16 @@ include("qba/qba.jl") include("linear/linear.jl") include("quadratic/quadratic.jl") include("logical/logical.jl") -include("integer/integer.jl") include("continuous/continuous.jl") function test_examples() - @testset "Examples" verbose = true begin + @testset "□ Examples" verbose = true begin test_qba() test_linear() test_quadratic() test_logical() - test_integer() test_continuous() end -end \ No newline at end of file + + return nothing +end diff --git a/test/examples/linear/linear.jl b/test/integration/examples/linear/linear.jl similarity index 97% rename from test/examples/linear/linear.jl rename to test/integration/examples/linear/linear.jl index c1907ed7..ff8ebdf6 100644 --- a/test/examples/linear/linear.jl +++ b/test/integration/examples/linear/linear.jl @@ -6,4 +6,4 @@ function test_linear() test_linear1() test_linear2() end -end \ No newline at end of file +end diff --git a/test/examples/linear/linear1.jl b/test/integration/examples/linear/linear1.jl similarity index 87% rename from test/examples/linear/linear1.jl rename to test/integration/examples/linear/linear1.jl index 4fb679f8..332cc786 100644 --- a/test/examples/linear/linear1.jl +++ b/test/integration/examples/linear/linear1.jl @@ -37,13 +37,16 @@ function test_linear1() optimize!(model) # Reformulation - ρ = MOI.get(model, TQA.ConstraintEncodingPenalty(), c1) - Q, α, β = ToQUBO.qubo(model, Matrix) + ρ = get_attribute(c1, Attributes.ConstraintEncodingPenalty()) + + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) @test ρ ≈ ρ̄ @test α ≈ ᾱ @test β ≈ β̄ - @test Q ≈ Q̄ + @test Q̂ ≈ Q̄ # Solutions x̂ = trunc.(Int, value.(x)) @@ -54,4 +57,4 @@ function test_linear1() return nothing end -end \ No newline at end of file +end diff --git a/test/examples/linear/linear2.jl b/test/integration/examples/linear/linear2.jl similarity index 90% rename from test/examples/linear/linear2.jl rename to test/integration/examples/linear/linear2.jl index 6d9cabd7..3631ca7f 100644 --- a/test/examples/linear/linear2.jl +++ b/test/integration/examples/linear/linear2.jl @@ -45,13 +45,15 @@ function test_linear2() optimize!(model) # Reformulation - ρ = MOI.get.(model, TQA.ConstraintEncodingPenalty(), k) - Q, α, β = ToQUBO.qubo(model, Matrix) + ρ = get_attribute.(k, Attributes.ConstraintEncodingPenalty()) + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) @test ρ ≈ ρ̄ @test α ≈ ᾱ @test β ≈ β̄ - @test Q ≈ Q̄ + @test Q̂ ≈ Q̄ # Solutions x̂ = trunc.(Int, value.(x)) @@ -62,4 +64,4 @@ function test_linear2() return nothing end -end \ No newline at end of file +end diff --git a/test/examples/logical/logical.jl b/test/integration/examples/logical/logical.jl similarity index 71% rename from test/examples/logical/logical.jl rename to test/integration/examples/logical/logical.jl index bf41fde8..87f04c50 100644 --- a/test/examples/logical/logical.jl +++ b/test/integration/examples/logical/logical.jl @@ -2,8 +2,8 @@ include("logical_tsp.jl") include("logical_sos.jl") function test_logical() - @testset "Logical Programs" verbose=true begin + @testset "Logical Programs" verbose = true begin test_logical_sos1() test_logical_tsp() end -end \ No newline at end of file +end diff --git a/test/examples/logical/logical_sos.jl b/test/integration/examples/logical/logical_sos.jl similarity index 75% rename from test/examples/logical/logical_sos.jl rename to test/integration/examples/logical/logical_sos.jl index bc6f4a51..dfad4482 100644 --- a/test/examples/logical/logical_sos.jl +++ b/test/integration/examples/logical/logical_sos.jl @@ -13,10 +13,10 @@ function test_logical_sos1() # Solution Data Q̄ = [ - 15 -28 -28 -32 - 0 15 -28 -32 - 0 0 15 -32 - 0 0 0 16 + 15 -28 -28 -32 + 0 15 -28 -32 + 0 0 15 -32 + 0 0 0 16 ] ᾱ = 1 @@ -35,13 +35,15 @@ function test_logical_sos1() optimize!(model) # Reformulation - ρ = MOI.get(model, TQA.ConstraintEncodingPenalty(), c1) - Q, α, β = ToQUBO.qubo(model, Matrix) + ρ = get_attribute(c1, Attributes.ConstraintEncodingPenalty()) + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) @test ρ ≈ ρ̄ @test α ≈ ᾱ @test β ≈ β̄ - @test Q ≈ Q̄ + @test Q̂ ≈ Q̄ # Solutions x̂ = trunc.(Int, value.(x)) @@ -52,4 +54,4 @@ function test_logical_sos1() return nothing end -end \ No newline at end of file +end diff --git a/test/integration/examples/logical/logical_tsp.jl b/test/integration/examples/logical/logical_tsp.jl new file mode 100644 index 00000000..0f9a8dcc --- /dev/null +++ b/test/integration/examples/logical/logical_tsp.jl @@ -0,0 +1,138 @@ +""" +The graph below has the following distances between nodes: + +[1] ←---- ⃒1 ----→ [2] + ↑ ↖ ↗ ↑ + | 5 2 | + | ↘ ↙ | + 4 [3] | + | ↗ | + | 3 | + ↓ ↙ | +[4] ←--------------6 + + 1 2 3 4 +D = 1 ┌ 0 1 5 4 ┐ + 2 │ 1 0 2 6 │ + 3 │ 5 2 0 3 │ + 4 └ 4 6 3 0 ┘ + +This formulation will create 16 binary variables, xᵢₖ ∈ 𝔹, for i, k ∈ {1, 2, 3, 4}. +xᵢₖ = 1 if the i-th node is in the k-th position of the tour, and 0 otherwise. + +The objective is to minimize the total distance ∑ᵢ ∑ⱼ ∑ₖ Dᵢ,ⱼ xᵢ,ₖ xⱼ,₍ₖ₊₁₎ + +where xₙ₊₁ = x₁. + +Each node must be visited exactly once, that is, + +(ci[i]) ∑ₖ xᵢ,ₖ = 1 for i ∈ {1, 2, 3, 4}. + +One the other hand, each position must be occupied exactly once, that is, + +(ck[k]) ∑ᵢ xᵢ,ₖ = 1 for k ∈ {1, 2, 3, 4}. + +The penalty functions will be + +ci[i] => ∑ᵢ (∑ₖ xᵢ,ₖ - 1)² +ck[k] => ∑ₖ (∑ᵢ xᵢ,ₖ - 1)² + +This results in a penalty hamiltonian + +2ρ (n - ∑ₖ ∑ᵢ xᵢ,ₖ + 2 ∑ᵢ ∑ⱼ ∑ₖ xᵢ,ₖ xⱼ,ₖ) + +Since Dᵢ,ⱼ ≥ 0, δ = n ∑ᵢ ∑ⱼ Dᵢ,ⱼ. ε = 1. + +Therefore, ρ = δ / ε + 1 = 42n = 169 + +The QUBO matrix will then be + +Qᵤ,ᵤ = 2ρ(2n - 1) +Qᵤ,ᵥ = 4ρ + + +""" +function test_logical_tsp() + @testset "TSP: 16 variables" begin + # Problem Data # + m = 4 + D = [ + 0 1 5 4 + 1 0 2 6 + 5 2 0 3 + 4 6 3 0 + ] + + # Penalty Choice + ρ̄ = sum(D) * m + 1 + + # Solution Data + Q̄ = [ + -2ρ̄ 2ρ̄ 2ρ̄ 2ρ̄ 2ρ̄ 1 5 4 2ρ̄ 0 0 0 2ρ̄ 1 5 4 + 0 -2ρ̄ 2ρ̄ 2ρ̄ 1 2ρ̄ 2 6 0 2ρ̄ 0 0 1 2ρ̄ 2 6 + 0 0 -2ρ̄ 2ρ̄ 5 2 2ρ̄ 3 0 0 2ρ̄ 0 5 2 2ρ̄ 3 + 0 0 0 -2ρ̄ 4 6 3 2ρ̄ 0 0 0 2ρ̄ 4 6 3 2ρ̄ + 0 0 0 0 -2ρ̄ 2ρ̄ 2ρ̄ 2ρ̄ 2ρ̄ 1 5 4 2ρ̄ 0 0 0 + 0 0 0 0 0 -2ρ̄ 2ρ̄ 2ρ̄ 1 2ρ̄ 2 6 0 2ρ̄ 0 0 + 0 0 0 0 0 0 -2ρ̄ 2ρ̄ 5 2 2ρ̄ 3 0 0 2ρ̄ 0 + 0 0 0 0 0 0 0 -2ρ̄ 4 6 3 2ρ̄ 0 0 0 2ρ̄ + 0 0 0 0 0 0 0 0 -2ρ̄ 2ρ̄ 2ρ̄ 2ρ̄ 2ρ̄ 1 5 4 + 0 0 0 0 0 0 0 0 0 -2ρ̄ 2ρ̄ 2ρ̄ 1 2ρ̄ 2 6 + 0 0 0 0 0 0 0 0 0 0 -2ρ̄ 2ρ̄ 5 2 2ρ̄ 3 + 0 0 0 0 0 0 0 0 0 0 0 -2ρ̄ 4 6 3 2ρ̄ + 0 0 0 0 0 0 0 0 0 0 0 0 -2ρ̄ 2ρ̄ 2ρ̄ 2ρ̄ + 0 0 0 0 0 0 0 0 0 0 0 0 0 -2ρ̄ 2ρ̄ 2ρ̄ + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2ρ̄ 2ρ̄ + 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -2ρ̄ + ] + + ᾱ = 1 + β̄ = 2ρ̄ * m + x̄ = Set{Matrix{Int}}([ + [0 0 0 1; 0 0 1 0; 0 1 0 0; 1 0 0 0], + [1 0 0 0; 0 0 0 1; 0 0 1 0; 0 1 0 0], + [0 1 0 0; 1 0 0 0; 0 0 0 1; 0 0 1 0], + [0 0 1 0; 0 1 0 0; 1 0 0 0; 0 0 0 1], + ]) + ȳ = 10 + + # Model + model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) + + @variable(model, x[1:m, 1:m], Bin) + @objective( + model, + Min, + sum(D[i, j] * x[i, k] * x[j, (k%m)+1] for i = 1:m, j = 1:m, k = 1:m) + ) + @constraint(model, ci[i = 1:m], sum(x[i, :]) == 1) + @constraint(model, ck[k = 1:m], sum(x[:, k]) == 1) + + set_attribute(model, Attributes.StableCompilation(), true) + + optimize!(model) + + # Reformulation + ρi = get_attribute.(ci, Attributes.ConstraintEncodingPenalty()) + ρk = get_attribute.(ck, Attributes.ConstraintEncodingPenalty()) + ρ = [ρi; ρk] + + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) + + @test n == m^2 + @test all(ρ .≈ ρ̄) + @test α ≈ ᾱ + @test β ≈ β̄ + @test Q̂ ≈ Q̄ + + # Solutions + x̂ = trunc.(Int, value.(x)) + ŷ = objective_value(model) + + @test x̂ ∈ x̄ + @test ŷ ≈ ȳ + + return nothing + end +end diff --git a/test/examples/qba/qba.jl b/test/integration/examples/qba/qba.jl similarity index 98% rename from test/examples/qba/qba.jl rename to test/integration/examples/qba/qba.jl index a9456539..9c16e033 100644 --- a/test/examples/qba/qba.jl +++ b/test/integration/examples/qba/qba.jl @@ -8,4 +8,4 @@ function test_qba() test_qba3_1() test_qba3_2() end -end \ No newline at end of file +end diff --git a/test/examples/qba/qba2.jl b/test/integration/examples/qba/qba2.jl similarity index 75% rename from test/examples/qba/qba2.jl rename to test/integration/examples/qba/qba2.jl index abd91b7c..d2cfa691 100644 --- a/test/examples/qba/qba2.jl +++ b/test/integration/examples/qba/qba2.jl @@ -1,5 +1,5 @@ function test_qba2() - @testset "Illustrative Example" begin + @testset "2: Illustrative Example" begin # Problem Data Q̄ = [ -5 4 8 0 @@ -21,20 +21,23 @@ function test_qba2() model, Min, -5x[1] - 3x[2] - 8x[3] - 6x[4] + - 4x[1] * x[2] + - 8x[1] * x[3] + - 2x[2] * x[3] + + 4x[1] * x[2] + + 8x[1] * x[3] + + 2x[2] * x[3] + 10x[3] * x[4] ) optimize!(model) # Reformulation - Q, α, β = ToQUBO.qubo(model, Matrix) + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + Q̂ = Q + diagm(L) + + @test n == 4 @test α ≈ ᾱ @test β ≈ β̄ - @test Q ≈ Q̄ + @test Q̂ ≈ Q̄ # Solution x̂ = value.(x) @@ -45,4 +48,4 @@ function test_qba2() return nothing end -end \ No newline at end of file +end diff --git a/test/examples/qba/qba3_1.jl b/test/integration/examples/qba/qba3_1.jl similarity index 74% rename from test/examples/qba/qba3_1.jl rename to test/integration/examples/qba/qba3_1.jl index bc9fd981..8263ecbd 100644 --- a/test/examples/qba/qba3_1.jl +++ b/test/integration/examples/qba/qba3_1.jl @@ -1,13 +1,12 @@ -function test_qba3_1() - @testset "Number Partitioning" begin - #= - Quote from [1]: - - The Number Partitioning problem has numerous applications cited in the bibliography. A - common version of this problem involves partitioning a set of numbers into two subsets - such that the subset sums are as close to each other as possible. - =# +raw""" +Quote from [1]: +The Number Partitioning problem has numerous applications cited in the bibliography. A +common version of this problem involves partitioning a set of numbers into two subsets +such that the subset sums are as close to each other as possible. +""" +function test_qba3_1() + @testset "3.1: Number Partitioning" begin # Problem Data S = Int[25, 7, 13, 31, 42, 17, 21, 10] m = 8 @@ -42,11 +41,14 @@ function test_qba3_1() optimize!(model) - Q, _, c = ToQUBO.qubo(model, Matrix) - # Reformulation + n, L, Q, _, c = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) + + @test n == 8 @test c ≈ c̄ - @test Q ≈ 4Q̄ + @test Q̂ ≈ 4Q̄ # Solution x̂ = trunc.(Int, value.(x)) @@ -57,4 +59,4 @@ function test_qba3_1() return nothing end -end \ No newline at end of file +end diff --git a/test/examples/qba/qba3_2.jl b/test/integration/examples/qba/qba3_2.jl similarity index 53% rename from test/examples/qba/qba3_2.jl rename to test/integration/examples/qba/qba3_2.jl index cd184ed3..20588695 100644 --- a/test/examples/qba/qba3_2.jl +++ b/test/integration/examples/qba/qba3_2.jl @@ -1,21 +1,23 @@ -function test_qba3_2() - @testset "Max-Cut" begin - #= - Quote from [1]: - - The Max Cut problem is one of the most famous problems in combinatorial optimization. - Given an undirected graph G(V, E) with a vertex set V and an edge set E, the Max-Cut - problem seeks to partition V into two sets such that the number of edges between the - two sets (considered to be severed by the cut), is a large as possible. - - Graph G: - (1)-(2) - | | - (3)-(4) - \ / - (5) - =# +raw""" + +Quote from [1]: + +The Max Cut problem is one of the most famous problems in combinatorial optimization. +Given an undirected graph G(V, E) with a vertex set V and an edge set E, the Max-Cut +problem seeks to partition V into two sets such that the number of edges between the +two sets (considered to be severed by the cut), is a large as possible. + +Graph G: + + (1)-(2) + | | + (3)-(4) + \ / + (5) +""" +function test_qba3_2() + @testset "3.2: Max-Cut" begin ⊻(x::VariableRef, y::VariableRef) = x + y - 2 * x * y # Problem Data @@ -31,11 +33,11 @@ function test_qba3_2() # Results Q̄ = [ - 2 -2 -2 0 0 - 0 2 0 -2 0 - 0 0 3 -2 -2 - 0 0 0 3 -2 - 0 0 0 0 2 + 2 -2 -2 0 0 + 0 2 0 -2 0 + 0 0 3 -2 -2 + 0 0 0 3 -2 + 0 0 0 0 2 ] c̄ = 0 @@ -51,15 +53,18 @@ function test_qba3_2() model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) @variable(model, x[1:m], Bin) - @objective(model, Max, sum(Gᵢⱼ * (x[i] ⊻ x[j]) for ((i, j), Gᵢⱼ) in G)) + @objective(model, Max, sum(Gij * (x[i] ⊻ x[j]) for ((i, j), Gij) in G)) optimize!(model) - Q, _, c = ToQUBO.qubo(model, Matrix) - # Reformulation + n, L, Q, _, c = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) + + @test n == 5 @test c ≈ c̄ - @test Q ≈ Q̄ + @test Q̂ ≈ Q̄ # Solutions x̂ = trunc.(Int, value.(x)) @@ -70,4 +75,4 @@ function test_qba3_2() return nothing end -end \ No newline at end of file +end diff --git a/test/integration/examples/quadratic/primes.jl b/test/integration/examples/quadratic/primes.jl new file mode 100644 index 00000000..e4679e00 --- /dev/null +++ b/test/integration/examples/quadratic/primes.jl @@ -0,0 +1,158 @@ +raw""" + +Factoring R in its prime factors p, q goes as + +feasibility +st p * q == R + p ∈ [2, a] ⊂ ℤ + q ∈ [a, b] ⊂ ℤ + +where a = ⌈√R⌉ and b = R ÷ 2. + +For R = 15, we have a = 4 and b = 7. + +Thus, + +feasibility +st p * q == R + p ∈ [2, 4] ⊂ ℤ + q ∈ [4, 7] ⊂ ℤ + +We penalize the constraint using ρ = 1, since we are looking for a feasible solution. + +This yields + +min (p * q - R)² + st p ∈ [2, 4] ⊂ ℤ + q ∈ [4, 7] ⊂ ℤ + +Expanding p, q using binary variables gives us + +p = 2 + p₁ + p₂ +q = 4 + q₁ + 2q₂ + +where p₁, p₂, q₁, q₂ ∈ 𝔹. + +Therefore, our model is + +min [(2 + p₁ + p₂) * (4 + q₁ + 2q₂) - 15]² + st p₁, p₂ ∈ 𝔹 + q₁, q₂ ∈ 𝔹 + +Expanding the product we have + +[(2 + p₁ + p₂) * (4 + q₁ + 2q₂) - 15]² = + 49 + - 40 p₁ - 40 p₂ - 24 q₁ - 40 q₂ + + 32 p₁ p₂ + 15 p₁ q₁ + 40 p₁ q₂ + 15 p₂ q₁ + 40 p₂ q₂ + 16 q₁ q₂ + + 18 p₁ p₂ q₁ + 40 p₁ p₂ q₂ + 20 p₁ q₁ q₂ + 20 p₂ q₁ q₂ + + 8 p₁ p₂ q₁ q₂ + +Quadratizing this model using (PTR-BG) will require 6 auxiliary variables: w₁, w₂, w₃, w₄, w₅, w₆ ∈ 𝔹. + +𝒬{p₁ p₂ q₁}(p₁, p₂, q₁; w₁) = w₁ + p₁ w₁ - p₂ w₁ - q₁ w₁ + p₂ q₁ +𝒬{p₁ p₂ q₂}(p₁, p₂, q₂; w₂) = w₂ + p₁ w₂ - p₂ w₂ - q₂ w₂ + p₂ q₂ +𝒬{p₁ q₁ q₂}(p₁, q₁, q₂; w₃) = w₃ + p₁ w₃ - q₁ w₃ - q₂ w₃ + q₁ q₂ +𝒬{p₂ q₁ q₂}(p₂, q₁, q₂; w₄) = w₄ + p₂ w₄ - q₁ w₄ - q₂ w₄ + q₁ q₂ + +𝒬{p₁ p₂ q₁ q₂}(p₁ p₂ q₁ q₂; w₅, w₆) = q₁ q₂ + 2 w₅ + p₁ w₅ - p₂ w₅ - q₁ w₅ - q₂ w₅ + w₆ + p₂ w₆ - q₁ w₆ - q₂ w₆ + +This results in + +min 49 - 40 p₁ - 40 p₂ + 32 p₁ p₂ - 24 q₁ + 15 p₁ q₁ + 33 p₂ q₁ - 40 q₂ + + 40 p₁ q₂ + 80 p₂ q₂ + 64 q₁ q₂ + 18 w₁ + 18 p₁ w₁ - 18 p₂ w₁ - + 18 q₁ w₁ + 40 w₂ + 40 p₁ w₂ - 40 p₂ w₂ - 40 q₂ w₂ + 20 w₃ + + 20 p₁ w₃ - 20 q₁ w₃ - 20 q₂ w₃ + 20 w₄ + 20 p₂ w₄ - 20 q₁ w₄ - + 20 q₂ w₄ + 16 w₅ + 8 p₁ w₅ - 8 p₂ w₅ - 8 q₁ w₅ - 8 q₂ w₅ + 8 w₆ + + 8 p₂ w₆ - 8 q₁ w₆ - 8 q₂ w₆ + st p₁, p₂ ∈ 𝔹 + q₁, q₂ ∈ 𝔹 + w₁, w₂, w₃, w₄, w₅, w₆ ∈ 𝔹 + +whose QUBO matrix is + + p₁ p₂ q₁ q₂ w₁ w₂ w₃ w₄ w₅ w₆ +Q = p₁ ┌ -40 32 15 40 18 40 20 8 ┐ + p₂ │ -40 33 80 -18 -40 20 -8 8 │ + q₁ │ -24 64 -18 -20 -20 -8 -8 │ + q₂ │ -40 -40 -20 -20 -8 -8 │ + w₁ │ 18 │ + w₂ │ 40 │ + w₃ │ 20 │ + w₄ │ 20 │ + w₅ │ 16 │ + w₆ └ 8 ┘ + + + +## PTR-BG + +### n = 3 +𝒬{x₁ x₂ x₃}(x₁, x₂, x₃; w) = w + x₁ w - x₂ w - x₃ w + x₂ x₃ + +### n = 4 +𝒬{x₁ x₂ x₃ x₄}(x₁, x₂, x₃, x₄; w₁, w₂) = 2 w₁ + w₂ + w₁ x₁ - w₁ x₂ + w₂ x₂ - w₁ x₃ - w₂ x₃ - w₁ x₄ - w₂ x₄ + x₃ x₄ +""" +function test_primes() + @testset "Prime Factoring: 15 = 3 × 5" begin + # Problem Data # + R = 15 + a = ceil(Int, √R) + b = R ÷ 2 + + @test a == 4 + @test b == 7 + + # Solution Data # + ᾱ = 1 + β̄ = 49 + Q̄ = [ + -40 32 15 40 18 40 20 0 8 0 + 0 -40 33 80 -18 -40 0 20 -8 8 + 0 0 -24 64 -18 0 -20 -20 -8 -8 + 0 0 0 -40 0 -40 -20 -20 -8 -8 + 0 0 0 0 18 0 0 0 0 0 + 0 0 0 0 0 40 0 0 0 0 + 0 0 0 0 0 0 20 0 0 0 + 0 0 0 0 0 0 0 20 0 0 + 0 0 0 0 0 0 0 0 16 0 + 0 0 0 0 0 0 0 0 0 8 + ] + + ρ̄ = 1 + p̄ = 3 + q̄ = 5 + + model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) + + @variable(model, 2 <= p <= a, Int) + @variable(model, a <= q <= b, Int) + @constraint(model, c1, p * q == R) + + set_attribute(model, Attributes.StableQuadratization(), true) + + optimize!(model) + + # Reformulation + ρ = get_attribute(c1, Attributes.ConstraintEncodingPenalty()) + + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) + + @test n == 10 + @test ρ ≈ ρ̄ + @test α ≈ ᾱ + @test β ≈ β̄ + @test Q̂ ≈ Q̄ + + # Solutions + p̂ = trunc(Int, value(p)) + q̂ = trunc(Int, value(q)) + + @test p̂ == p̄ + @test q̂ == q̄ + + return nothing + end +end diff --git a/test/examples/quadratic/quadratic.jl b/test/integration/examples/quadratic/quadratic.jl similarity index 61% rename from test/examples/quadratic/quadratic.jl rename to test/integration/examples/quadratic/quadratic.jl index f2a15b12..949f2e18 100644 --- a/test/examples/quadratic/quadratic.jl +++ b/test/integration/examples/quadratic/quadratic.jl @@ -1,7 +1,9 @@ include("quadratic_1.jl") +include("primes.jl") function test_quadratic() @testset "Quadratic Programs" verbose = true begin - test_quadratic_1() + test_quadratic_1() + test_primes() end -end \ No newline at end of file +end diff --git a/test/integration/examples/quadratic/quadratic_1.jl b/test/integration/examples/quadratic/quadratic_1.jl new file mode 100644 index 00000000..1936be4d --- /dev/null +++ b/test/integration/examples/quadratic/quadratic_1.jl @@ -0,0 +1,176 @@ +raw""" + +Let A = ┌ -1 1 ┐ + └ 1 -1 ┘ + +The original model is + +max x' A x + st x' A x ≤ 1 + x ∈ 𝔹^2 + +that is, + +max [x₁ x₂] ┌ -1 1 ┐ ┌ x₁ ┐ + └ 1 -1 ┘ └ x₂ ┘ + st [x₁ x₂] ┌ -1 1 ┐ ┌ x₁ ┐ ≤ 1 + └ 1 -1 ┘ └ x₂ ┘ + +or, expanding the matrix multiplication, + +max -x₁ - x₂ + 2 x₁ x₂ + st -x₁ - x₂ + 2 x₁ x₂ ≤ 1 + x ∈ 𝔹^2 + +Adding a slack variable u, the reformulation is + +max -x₁ - x₂ + 2 x₁ x₂ + st -x₁ - x₂ + 2 x₁ x₂ + u = 1 + x ∈ 𝔹^2 + u ∈ [0, 3] ⊂ ℤ + +Expanding u as a sum of binary variables, u = u₁ + 2 u₂ where u₁, u₂ ∈ 𝔹. + +This results in + +max -x₁ - x₂ + 2 x₁ x₂ + st -x₁ - x₂ + 2 x₁ x₂ + u₁ + 2 u₂ = 1 + x₁, x₂ ∈ 𝔹 + u₁, u₂ ∈ 𝔹 + +The reformulation is + +max -x₁ - x₂ + 2 x₁ x₂ + ρ (-x₁ - x₂ + 2 x₁ x₂ + u₁ + 2 u₂ - 1)² + st x ∈ 𝔹^2 + +where ρ is a penalty parameter. + +Expanding the square, + +max -x₁ - x₂ + 2 x₁ x₂ + ρ ( + 3 x₁ + 3 x₂ - u₁ + - 6 x₁ x₂ - 2 x₁ u₁ - 2 x₂ u₁ - 4 x₁ u₂ - 4 x₂ u₂ + 4 u₁ u₂ + + 4 x₁ x₂ u₁ + 8 x₁ x₂ u₂ + + 1 + ) + st x₁, x₂ ∈ 𝔹 + u₁, u₂ ∈ 𝔹 + +quadratizing u₁ x₁ x₂ and u₂ x₁ x₂ using positive term reduction (PTR-BG), +which adds the auxiliary variables w₁, w₂ yields + +𝒬{x₁ x₂ u₁}(x₁, x₂, u₁; w₁) = w₁ + x₁ w₁ - x₂ w₁ - u₁ w₁ + x₂ u₁ +𝒬{x₁ x₂ u₂}(x₁, x₂, u₂; w₂) = w₂ + x₁ w₂ - x₂ w₂ - u₂ w₂ + x₂ u₂ + +and then + +max -x₁ - x₂ + 2 x₁ x₂ + ρ ( + 3 x₁ + 3 x₂ - u₁ + - 6 x₁ x₂ - 2 x₁ u₁ - 2 x₂ u₁ - 4 x₁ u₂ - 4 x₂ u₂ + 4 u₁ u₂ + + 4 [w₁ + x₁ w₁ - x₂ w₁ - u₁ w₁ + x₂ u₁] + 8 [w₂ + x₁ w₂ - x₂ w₂ - u₂ w₂ + x₂ u₂] + + 1 + ) + st x₁, x₂ ∈ 𝔹 + u₁, u₂ ∈ 𝔹 + w₁, w₂ ∈ 𝔹 + +or, in other words, + +max -x₁ - x₂ + 2 x₁ x₂ + ρ ( + 3 x₁ + 3 x₂ - u₁ + - 6 x₁ x₂ - 2 x₁ u₁ + 2 x₂ u₁ - 4 x₁ u₂ + 4 x₂ u₂ + 4 u₁ u₂ + + 4 w₁ + 4 x₁ w₁ - 4 x₂ w₁ - 4 u₁ w₁ + + 8 w₂ + 8 x₁ w₂ - 8 x₂ w₂ - 8 u₂ w₂ + + 1 + ) + st x₁, x₂ ∈ 𝔹 + u₁, u₂ ∈ 𝔹 + w₁, w₂ ∈ 𝔹 + +whose QUBO matrix is + + x₁ x₂ u₁ u₂ w₁ w₂ +Q = x₁ ┌ -1 + 3ρ 2 - 6ρ -2ρ -4ρ 4ρ 8ρ ┐ + x₂ │ -1 + 3ρ 2ρ 4ρ -4ρ -8ρ │ + u₁ │ -ρ 4ρ -4ρ │ + u₂ │ -8ρ │ + w₁ │ 4ρ │ + w₂ └ 8ρ ┘ + +not to forget its offset β = ρ. + +Let |ρ| > δ / ϵ where δ = 2 - (-2) = 4 and ϵ = 1. Then |ρ| > 4 ⟹ ρ = -5 since +this is a maximization problem. + +Possible solutions are x = [0, 0] and x = [1, 1] with objective value y = 0. + + +## PTR-BG + +𝒬{x₁ x₂ x₃}(x₁, x₂, x₃; w) = w + x₁ w - x₂ w - x₃ w + x₂ x₃ + +""" +function test_quadratic_1() + @testset "2 variables, 1 constraint" begin + # Problem Data + A = [ + -1 1 + 1 -1 + ] + b = 1 + + # Penalty Choice + ρ̄ = -5.0 + + # Solution + Q̄ = [ + -1+3ρ̄ 2-6ρ̄ -2ρ̄ -4ρ̄ 4ρ̄ 8ρ̄ + 0 -1+3ρ̄ 2ρ̄ 4ρ̄ -4ρ̄ -8ρ̄ + 0 0 -ρ̄ 4ρ̄ -4ρ̄ 0 + 0 0 0 0 0 -8ρ̄ + 0 0 0 0 4ρ̄ 0 + 0 0 0 0 0 8ρ̄ + ] + + ᾱ = 1.0 + β̄ = ρ̄ + + x̄ = Set{Vector{Int}}([[0, 0], [1, 1]]) + ȳ = 0.0 + + # Model + model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) + + @variable(model, x[1:2], Bin) + @objective(model, Max, x' * A * x) + @constraint(model, c1, x' * A * x <= b) + + set_attribute(model, Attributes.StableQuadratization(), true) + + optimize!(model) + + # Reformulation + ρ = get_attribute(c1, Attributes.ConstraintEncodingPenalty()) + + n, L, Q, α, β = QUBOTools.qubo(model, :dense) + + Q̂ = Q + diagm(L) + + @test n == 6 + @test ρ ≈ ρ̄ + @test α ≈ ᾱ + @test β ≈ β̄ + @test Q̂ ≈ Q̄ + + # Solutions + for i = 1:2 + x̂ = trunc.(Int, value.(x; result = i)) + ŷ = objective_value(model; result = i) + + @test x̂ ∈ x̄ + @test ŷ ≈ ȳ + end + + return nothing + end +end diff --git a/test/integration/integration.jl b/test/integration/integration.jl index 85856c62..95ddabe0 100644 --- a/test/integration/integration.jl +++ b/test/integration/integration.jl @@ -1,7 +1,11 @@ -include("attributes.jl") +include("interface.jl") +include("examples/examples.jl") function test_integration() - @testset "Integration" verbose = true begin - test_attributes() + @testset "⊚ Integration Tests" verbose = true begin + test_interface() + test_examples() end -end \ No newline at end of file + + return nothing +end diff --git a/test/integration/interface.jl b/test/integration/interface.jl new file mode 100644 index 00000000..ad8a2cde --- /dev/null +++ b/test/integration/interface.jl @@ -0,0 +1,565 @@ +# Assets +struct SuperArchitecture <: QUBOTools.AbstractArchitecture + super::Bool + + function SuperArchitecture(super::Bool = false) + return new(super) + end +end + +function test_interface() + @testset "□ Interface" verbose = true begin + test_interface_moi() + test_interface_jump() + end + + return nothing +end + +function test_interface_moi() + @testset "MathOptInterface" begin + @testset "Instantiate" begin + @testset "Compiler mode" begin + let + model = MOI.instantiate( + () -> ToQUBO.Optimizer{Float64}(nothing), + with_bridge_type = Float64, + ) + + @test MOI.is_empty(model) + end + end + + @testset "Optimizer mode" begin + let + model = MOI.instantiate( + () -> + ToQUBO.Optimizer{Float64}(ExactSampler.Optimizer{Float64}), + with_bridge_type = Float64, + ) + @test MOI.is_empty(model) + end + end + end + + @testset "Models" begin + @testset "Binary Knapsack" begin + let n = 3 # size + v = [1.0, 2.0, 3.0] # value + w = [0.3, 0.5, 1.0] # weight + C = 1.6 # capacity + + model = MOI.instantiate( + () -> + ToQUBO.Optimizer{Float64}(ExactSampler.Optimizer{Float64}), + with_bridge_type = Float64, + ) + + x, _ = MOI.add_constrained_variables(model, fill(MOI.ZeroOne(), n)) + + @test length(x) == length(v) == n + + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm{Float64}.(v, x), + 0.0, + ), + ) + MOI.add_constraint( + model, + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm{Float64}.(w, x), + 0.0, + ), + MOI.LessThan{Float64}(C), + ) + + MOI.optimize!(model) + + @test MOI.get.(model, MOI.VariablePrimal(), x) ≈ [0.0, 1.0, 1.0] + end + end + end + + @testset "Attributes" begin + let # Create Model + # max x1 + x2 + x3 + # st x1 + x2 <= 1 (c1) + # x2 + x3 <= 1 (c2) + # x1 ∈ {0, 1} + # x2 ∈ {0, 1} + # x3 ∈ {0, 1} + + model = MOI.instantiate( + () -> ToQUBO.Optimizer(RandomSampler.Optimizer); + with_bridge_type = Float64, + ) + + x, _ = MOI.add_constrained_variables(model, fill(MOI.ZeroOne(), 3)) + + MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) + + MOI.set( + model, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm{Float64}[ + MOI.ScalarAffineTerm{Float64}(1.0, x[1]), + MOI.ScalarAffineTerm{Float64}(1.0, x[2]), + MOI.ScalarAffineTerm{Float64}(1.0, x[3]), + ], + 0.0, + ), + ) + + c = ( + MOI.add_constraint( + model, + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm{Float64}[ + MOI.ScalarAffineTerm{Float64}(1.0, x[1]), + MOI.ScalarAffineTerm{Float64}(1.0, x[2]), + ], + 0.0, + ), + MOI.LessThan{Float64}(1.0), + ), + MOI.add_constraint( + model, + MOI.ScalarAffineFunction{Float64}( + MOI.ScalarAffineTerm{Float64}[ + MOI.ScalarAffineTerm{Float64}(1.0, x[2]), + MOI.ScalarAffineTerm{Float64}(1.0, x[3]), + ], + 0.0, + ), + MOI.LessThan{Float64}(1.0), + ) + ) + + # MOI Attributes + @test MOI.get(model, MOI.NumberOfVariables()) == 3 + + if MOI.supports(model, MOI.TimeLimitSec()) + @test MOI.get(model, MOI.TimeLimitSec()) === nothing + MOI.set(model, MOI.TimeLimitSec(), 1.0) + @test MOI.get(model, MOI.TimeLimitSec()) == 1.0 + end + + # Solver Attributes + @test MOI.get(model, RandomSampler.RandomSeed()) === nothing + MOI.set(model, RandomSampler.RandomSeed(), 13) + @test MOI.get(model, RandomSampler.RandomSeed()) == 13 + + @test MOI.get(model, RandomSampler.NumberOfReads()) == 1_000 + MOI.set(model, RandomSampler.NumberOfReads(), 13) + @test MOI.get(model, RandomSampler.NumberOfReads()) == 13 + + # Raw Solver Attributes + @test MOI.get(model, MOI.RawOptimizerAttribute("seed")) == 13 + MOI.set(model, MOI.RawOptimizerAttribute("seed"), 1_001) + @test MOI.get(model, MOI.RawOptimizerAttribute("seed")) == 1_001 + + @test MOI.get(model, MOI.RawOptimizerAttribute("num_reads")) == 13 + MOI.set(model, MOI.RawOptimizerAttribute("num_reads"), 1_001) + @test MOI.get(model, MOI.RawOptimizerAttribute("num_reads")) == 1_001 + + # ToQUBO Attributes + @test MOI.get(model, Attributes.Architecture()) isa QUBOTools.GenericArchitecture + MOI.set(model, Attributes.Architecture(), SuperArchitecture(true)) + @test MOI.get(model, Attributes.Architecture()) isa SuperArchitecture + @test MOI.get(model, Attributes.Architecture()).super === true + + @test MOI.get(model, Attributes.Optimization()) === 0 + 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 + + @test MOI.get(model, Attributes.Quadratize()) === false + MOI.set(model, Attributes.Quadratize(), true) + @test MOI.get(model, Attributes.Quadratize()) === true + + @test MOI.get(model, Attributes.Warnings()) === true + MOI.set(model, Attributes.Warnings(), false) + @test MOI.get(model, Attributes.Warnings()) === false + + @test MOI.get(model, Attributes.QuadratizationMethod()) isa PBO.DEFAULT + MOI.set(model, Attributes.QuadratizationMethod(), PBO.PTR_BG()) + @test MOI.get(model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG + + @test MOI.get(model, Attributes.StableQuadratization()) === false + MOI.set(model, Attributes.StableQuadratization(), true) + @test MOI.get(model, Attributes.StableQuadratization()) === true + + # Variable Encoding Method + @test MOI.get(model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Binary + MOI.set(model, Attributes.DefaultVariableEncodingMethod(), Encoding.Unary()) + @test MOI.get(model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary + + @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[2], Encoding.Arithmetic()) + + @test MOI.get(model, Attributes.VariableEncodingMethod(), x[1]) isa Encoding.Arithmetic + @test MOI.get(model, Attributes.VariableEncodingMethod(), x[2]) isa Encoding.Arithmetic + + # Variable Encoding ATol + @test MOI.get(model, Attributes.DefaultVariableEncodingATol()) ≈ 1 / 4 + MOI.set(model, Attributes.DefaultVariableEncodingATol(), 1E-6) + @test MOI.get(model, Attributes.DefaultVariableEncodingATol()) ≈ 1E-6 + + @test MOI.get(model, Attributes.VariableEncodingATol(), x[1]) === nothing + @test MOI.get(model, Attributes.VariableEncodingATol(), x[2]) === nothing + + MOI.set(model, Attributes.VariableEncodingATol(), x[1], 1 / 2) + MOI.set(model, Attributes.VariableEncodingATol(), x[2], 1 / 3) + + @test MOI.get(model, Attributes.VariableEncodingATol(), x[1]) ≈ 1 / 2 + @test MOI.get(model, Attributes.VariableEncodingATol(), x[2]) ≈ 1 / 3 + + # Variable Encoding Bits + @test MOI.get(model, Attributes.DefaultVariableEncodingBits()) === nothing + MOI.set(model, Attributes.DefaultVariableEncodingBits(), 3) + @test MOI.get(model, Attributes.DefaultVariableEncodingBits()) == 3 + + @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) + + @test MOI.get(model, Attributes.VariableEncodingBits(), x[1]) == 1 + @test MOI.get(model, Attributes.VariableEncodingBits(), x[2]) == 2 + + # Variable Encoding Penalty + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[1]) === nothing + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[2]) === nothing + + MOI.set(model, Attributes.VariableEncodingPenaltyHint(), x[1], -1.0) + + @test MOI.get(model, Attributes.VariableEncodingPenaltyHint(), x[1]) == -1.0 + + @test_throws Exception MOI.get(model, Attributes.VariableEncodingPenalty(), x[1]) + @test_throws Exception MOI.get(model, Attributes.VariableEncodingPenalty(), x[2]) + + # ToQUBO Constraint Attributes + @test MOI.get(model, Attributes.ConstraintEncodingPenaltyHint(), c[1]) === nothing + @test MOI.get(model, Attributes.ConstraintEncodingPenaltyHint(), c[2]) === nothing + + MOI.set(model, Attributes.ConstraintEncodingPenaltyHint(), c[1], -10.0) + + @test MOI.get(model, Attributes.ConstraintEncodingPenaltyHint(), c[1]) == -10.0 + + @test_throws Exception MOI.get(model, Attributes.ConstraintEncodingPenalty(), c[1]) + @test_throws Exception MOI.get(model, Attributes.ConstraintEncodingPenalty(), c[2]) + + # Call to MOI.optimize! + MOI.optimize!(model) + + let virtual_model = model.model.optimizer + @test MOI.get(virtual_model, Attributes.Architecture()) isa SuperArchitecture + @test MOI.get(virtual_model, Attributes.Architecture()).super === true + + + + @test MOI.get(virtual_model, Attributes.Optimization()) === 3 + + + @test MOI.get(virtual_model, Attributes.Discretize()) === true + + + @test MOI.get(virtual_model, Attributes.Quadratize()) === true + + + @test MOI.get(virtual_model, Attributes.Warnings()) === false + + + @test MOI.get(virtual_model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG + + + @test MOI.get(virtual_model, Attributes.StableQuadratization()) === true + + + @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 + + end + end + end + end + + return nothing +end + +function test_interface_jump() + @testset "JuMP" begin + @testset "Instantiate" begin + @testset "Compiler mode" begin + let model = Model(ToQUBO.Optimizer) + + @test isempty(model) + end + end + + @testset "Optimizer mode" begin + let model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) + + @test isempty(model) + end + end + end + + @testset "Models" begin + @testset "Binary Knapsack" begin + let + n = 3 # size + v = [1.0, 2.0, 3.0] # value + w = [0.3, 0.5, 1.0] # weight + C = 1.6 # capacity + + model = Model(() -> ToQUBO.Optimizer(ExactSampler.Optimizer)) + + @variable(model, x[1:n], Bin) + + @test length(x) == length(v) == n + + @objective(model, Max, v'x) + + @constraint(model, w'x <= C) + + optimize!(model) + + @test value.(x) ≈ [0.0, 1.0, 1.0] + end + end + end + + @testset "Attributes" begin + let # Create Model + # max x1 + x2 + x3 + # st x1 + x2 <= 1 (c1) + # x2 + x3 <= 1 (c2) + # x1 ∈ {0, 1} + # x2 ∈ {0, 1} + # x3 ∈ {0, 1} + model = Model(() -> ToQUBO.Optimizer(RandomSampler.Optimizer)) + + @variable(model, x[1:3], Bin) + + @objective(model, Max, sum(x)) + + @constraint(model, c[i = 1:2], x[i] + x[i + 1] <= 1) + + # 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 + + # Raw Solver Attributes + @test JuMP.get_attribute(model, "seed") == 13 + JuMP.set_attribute(model, "seed", 1_001) + @test JuMP.get_attribute(model, "seed") == 1_001 + + @test JuMP.get_attribute(model, "num_reads") == 13 + JuMP.set_attribute(model, "num_reads", 1_001) + @test JuMP.get_attribute(model, "num_reads") == 1_001 + + # ToQUBO Attributes + @test JuMP.get_attribute(model, Attributes.Architecture()) isa QUBOTools.GenericArchitecture + JuMP.set_attribute(model, Attributes.Architecture(), SuperArchitecture(true)) + @test JuMP.get_attribute(model, Attributes.Architecture()) isa SuperArchitecture + @test JuMP.get_attribute(model, Attributes.Architecture()).super === true + + @test JuMP.get_attribute(model, Attributes.Optimization()) === 0 + 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 + + @test JuMP.get_attribute(model, Attributes.Quadratize()) === false + JuMP.set_attribute(model, Attributes.Quadratize(), true) + @test JuMP.get_attribute(model, Attributes.Quadratize()) === true + + @test JuMP.get_attribute(model, Attributes.Warnings()) === true + JuMP.set_attribute(model, Attributes.Warnings(), false) + @test JuMP.get_attribute(model, Attributes.Warnings()) === false + + @test JuMP.get_attribute(model, Attributes.QuadratizationMethod()) isa PBO.DEFAULT + JuMP.set_attribute(model, Attributes.QuadratizationMethod(), PBO.PTR_BG()) + @test JuMP.get_attribute(model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG + + @test JuMP.get_attribute(model, Attributes.StableQuadratization()) === false + JuMP.set_attribute(model, Attributes.StableQuadratization(), true) + @test JuMP.get_attribute(model, Attributes.StableQuadratization()) === true + + # Variable Encoding Method + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Binary + JuMP.set_attribute(model, Attributes.DefaultVariableEncodingMethod(), Encoding.Unary()) + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingMethod()) === nothing + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingMethod()) === nothing + + JuMP.set_attribute(x[1], Attributes.VariableEncodingMethod(), Encoding.Arithmetic()) + JuMP.set_attribute(x[2], Attributes.VariableEncodingMethod(), Encoding.Arithmetic()) + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingMethod()) isa Encoding.Arithmetic + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingMethod()) isa Encoding.Arithmetic + + # Variable Encoding ATol + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingATol()) ≈ 1 / 4 + JuMP.set_attribute(model, Attributes.DefaultVariableEncodingATol(), 1E-6) + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingATol()) ≈ 1E-6 + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingATol()) === nothing + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingATol()) === nothing + + JuMP.set_attribute(x[1], Attributes.VariableEncodingATol(), 1 / 2) + JuMP.set_attribute(x[2], Attributes.VariableEncodingATol(), 1 / 3) + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingATol()) ≈ 1 / 2 + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingATol()) ≈ 1 / 3 + + # Variable Encoding Bits + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingBits()) === nothing + JuMP.set_attribute(model, Attributes.DefaultVariableEncodingBits(), 3) + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingBits()) == 3 + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingBits()) === nothing + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingBits()) === nothing + + JuMP.set_attribute(x[1], Attributes.VariableEncodingBits(), 1) + JuMP.set_attribute(x[2], Attributes.VariableEncodingBits(), 2) + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingBits()) == 1 + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingBits()) == 2 + + # Variable Encoding Penalty + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingPenaltyHint()) === nothing + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingPenaltyHint()) === nothing + + JuMP.set_attribute(x[1], Attributes.VariableEncodingPenaltyHint(), -1.0) + + @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 + + JuMP.set_attribute(c[1], Attributes.ConstraintEncodingPenaltyHint(), -10.0) + + @test JuMP.get_attribute(c[1], Attributes.ConstraintEncodingPenaltyHint()) == -10.0 + + @test_throws Exception JuMP.get_attribute(c[1], Attributes.ConstraintEncodingPenalty()) + @test_throws Exception JuMP.get_attribute(c[2], Attributes.ConstraintEncodingPenalty()) + + JuMP.optimize!(model) + + @test JuMP.get_attribute(model, Attributes.Architecture()) isa SuperArchitecture + @test JuMP.get_attribute(model, Attributes.Architecture()).super === true + + @test JuMP.get_attribute(model, Attributes.Optimization()) === 3 + @test JuMP.get_attribute(model, Attributes.Discretize()) === true + @test JuMP.get_attribute(model, Attributes.Quadratize()) === true + @test JuMP.get_attribute(model, Attributes.Warnings()) === false + + @test JuMP.get_attribute(model, Attributes.QuadratizationMethod()) isa PBO.PTR_BG + @test JuMP.get_attribute(model, Attributes.StableQuadratization()) === true + + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingMethod()) isa Encoding.Unary + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingMethod()) isa Encoding.Arithmetic + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingMethod()) isa Encoding.Arithmetic + @test JuMP.get_attribute(x[3], Attributes.VariableEncodingMethod()) === nothing + + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingATol()) ≈ 1E-6 + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingATol()) ≈ 1 / 2 + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingATol()) ≈ 1 / 3 + @test JuMP.get_attribute(x[3], Attributes.VariableEncodingATol()) === nothing + + @test JuMP.get_attribute(model, Attributes.DefaultVariableEncodingBits()) == 3 + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingBits()) == 1 + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingBits()) == 2 + @test JuMP.get_attribute(x[3], Attributes.VariableEncodingBits()) === nothing + + @test JuMP.get_attribute(x[1], Attributes.VariableEncodingPenaltyHint()) == -1.0 + @test JuMP.get_attribute(x[2], Attributes.VariableEncodingPenaltyHint()) === nothing + @test JuMP.get_attribute(x[3], Attributes.VariableEncodingPenaltyHint()) === nothing + + @test JuMP.get_attribute(c[1], Attributes.ConstraintEncodingPenaltyHint()) == -10.0 + @test JuMP.get_attribute(c[2], Attributes.ConstraintEncodingPenaltyHint()) === nothing + + @test JuMP.get_attribute(c[1], Attributes.ConstraintEncodingPenalty()) == -10.0 + @test JuMP.get_attribute(c[2], Attributes.ConstraintEncodingPenalty()) == -4.0 + end + end + end + + return nothing +end diff --git a/test/runtests.jl b/test/runtests.jl index 8e62be1e..072031f7 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,27 +4,39 @@ using JuMP const MOIU = MOI.Utilities const VI = MOI.VariableIndex const CI = MOI.ConstraintIndex - -using ToQUBO: ToQUBO, PBO using QUBODrivers using LinearAlgebra using TOML -const TQA = ToQUBO.Attributes +using ToQUBO +using ToQUBO: Attributes, Encoding + +import QUBOTools +import PseudoBooleanOptimization as PBO + +# Move this to QUBOTools / PBO +function MOIU.map_indices(::Function, arch::QUBOTools.AbstractArchitecture) + return arch +end + +function MOIU.map_indices(::Function, quad::PBO.QuadratizationMethod) + return quad +end + +function QUBOTools.backend(model::JuMP.Model) + return QUBOTools.backend(JuMP.unsafe_backend(model)) +end -include("assets/assets.jl") include("unit/unit.jl") include("integration/integration.jl") -include("examples/examples.jl") function main() - @testset ":: :: :: ToQUBO.jl :: :: ::" verbose = true begin + @testset "♡ ToQUBO.jl $(ToQUBO.__VERSION__) Test Suite ♡" verbose = true begin test_unit() test_integration() - test_examples() end return nothing end -main() # Here we go! \ No newline at end of file +main() # Here we go! diff --git a/test/unit/compiler/compiler.jl b/test/unit/compiler/compiler.jl index 8b69a083..5aec856b 100644 --- a/test/unit/compiler/compiler.jl +++ b/test/unit/compiler/compiler.jl @@ -1,7 +1,9 @@ include("constraints.jl") function test_compiler() - @testset "Compiler" verbose = true begin + @testset "□ Compiler" verbose = true begin test_compiler_constraints() end -end \ No newline at end of file + + return nothing +end diff --git a/test/unit/compiler/constraints.jl b/test/unit/compiler/constraints.jl index 512917ce..b27c2044 100644 --- a/test/unit/compiler/constraints.jl +++ b/test/unit/compiler/constraints.jl @@ -7,23 +7,23 @@ function test_compiler_constraints_quadratic() ] b = 6.0 - model = ToQUBO.VirtualModel{Float64}() - arch = ToQUBO.GenericArchitecture() + model = ToQUBO.Virtual.Model{Float64}() + arch = ToQUBO.Compiler.GenericArchitecture() x, _ = MOI.add_constrained_variables(model.source_model, repeat([MOI.ZeroOne()], n)) - - ToQUBO.toqubo_variables!(model, arch) + + ToQUBO.Compiler.variables!(model, arch) f = MOI.ScalarQuadraticFunction{Float64}( [MOI.ScalarQuadraticTerm(A[i, j], x[i], x[j]) for i = 1:n for j = 1:n if i != j], [MOI.ScalarAffineTerm(A[i, i] / 2.0, x[i]) for i = 1:n], - 0.0 + 0.0, ) s = MOI.EqualTo{Float64}(b) - g = ToQUBO.toqubo_constraint(model, f, s, arch) + g = ToQUBO.Compiler.constraint(model, f, s, arch) h = ToQUBO.PBO.PBF{VI,Float64}() - ToQUBO.toqubo_parse!(model, h, f, s, arch) + ToQUBO.Compiler.parse!(model, h, f, s, arch) @test h == PBO.PBF{VI,Float64}( -6.0, @@ -50,9 +50,9 @@ function test_compiler_constraints_quadratic() end function test_compiler_constraints() - @testset "Constraints" verbose = true begin + @testset "→ Constraints" verbose = true begin test_compiler_constraints_quadratic() end return nothing -end \ No newline at end of file +end diff --git a/test/unit/encoding/encoding.jl b/test/unit/encoding/encoding.jl new file mode 100644 index 00000000..de8e40f1 --- /dev/null +++ b/test/unit/encoding/encoding.jl @@ -0,0 +1,9 @@ +include("variables.jl") + +function test_encoding_methods() + @testset "□ Encoding" verbose = true begin + test_variable_encoding_methods() + end + + return nothing +end diff --git a/test/unit/encoding/variables.jl b/test/unit/encoding/variables.jl new file mode 100644 index 00000000..28fa1583 --- /dev/null +++ b/test/unit/encoding/variables.jl @@ -0,0 +1,694 @@ +function test_variable_encoding_methods() + @testset "→ Variables" begin + @testset "⋅ Mirror" begin + let e = ToQUBO.Encoding.Mirror{Float64}() + φ = PBO.vargen(VI) + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e) + + @test length(y) == 1 + @test y == VI.([1]) + @test ξ == PBO.PBF{VI,Float64}(y => 1.0) + @test isnothing(χ) + end + end + + @testset "⊛ Interval" begin + @testset "⋅ Unary" begin + let e = ToQUBO.Encoding.Unary{Float64}() + @testset "ℤ" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 4 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.0, + y[2] => 1.0, + y[3] => 1.0, + y[4] => 1.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℤ (bounded)" begin + let μ = 2.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 3 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.0, + y[2] => 1.0, + y[3] => 2.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (fixed)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 8 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 0.5, + y[2] => 0.5, + y[3] => 0.5, + y[4] => 0.5, + y[5] => 0.5, + y[6] => 0.5, + y[7] => 0.5, + y[8] => 0.5, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (fixed, bounded)" begin + let μ = 2.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 8 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 2 / 7, + y[2] => 2 / 7, + y[3] => 2 / 7, + y[4] => 2 / 7, + y[5] => 2 / 7, + y[6] => 2 / 7, + y[7] => 2 / 7, + y[8] => 2.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (tolerance)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 5 + + @test ToQUBO.Encoding.encoding_bits(e, S, 1 / 4) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S; tol = 1 / 4) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 4 / 5, + y[2] => 4 / 5, + y[3] => 4 / 5, + y[4] => 4 / 5, + y[5] => 4 / 5, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (tolerance, bounded)" begin + let μ = 2.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 4 + + @test ToQUBO.Encoding.encoding_bits(ê, S, 1 / 4) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S; tol = 1 / 4) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 2 / 3, + y[2] => 2 / 3, + y[3] => 2 / 3, + y[4] => 2.0, + -2.0, + ) + @test isnothing(χ) + end + end + end + end + + @testset "⋅ Binary" begin + let e = ToQUBO.Encoding.Binary{Float64}() + @testset "ℤ" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 3 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.0, + y[2] => 2.0, + y[3] => 1.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℤ (bounded)" begin + let μ = 2.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 3 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.0, + y[2] => 1.0, + y[3] => 2.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (fixed)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 8 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 4 / 255, + y[2] => 8 / 255, + y[3] => 16 / 255, + y[4] => 32 / 255, + y[5] => 64 / 255, + y[6] => 128 / 255, + y[7] => 256 / 255, + y[8] => 512 / 255, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (fixed, bounded)" begin + let μ = 1.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-1.0, 2.5) + n = 8 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.5 / 63, + y[2] => 3 / 63, + y[3] => 6 / 63, + y[4] => 12 / 63, + y[5] => 24 / 63, + y[6] => 48 / 63, + y[7] => 1.0, + y[8] => 1.0, + -1.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (tolerance)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 3 + + @test ToQUBO.Encoding.encoding_bits(e, S, 1 / 4) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S; tol = 1 / 4) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 4 / 7, + y[2] => 8 / 7, + y[3] => 16 / 7, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (tolerance, bounded)" begin + let μ = 1.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 4 + + @test ToQUBO.Encoding.encoding_bits(ê, S, 1 / 4) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S; tol = 1 / 4) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.0, + y[2] => 1.0, + y[3] => 1.0, + y[4] => 1.0, + -2.0, + ) + @test isnothing(χ) + end + end + end + end + + @testset "⋅ Arithmetic" begin + let e = ToQUBO.Encoding.Arithmetic{Float64}() + @testset "ℤ" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S) + + @test length(y) == 3 + @test y == VI.(1:3) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.0, + y[2] => 2.0, + y[3] => 1.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℤ (bounded)" begin + let μ = 2.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S) + + @test length(y) == 3 + @test y == VI.(1:3) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 1.0, + y[2] => 1.0, + y[3] => 2.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (fixed)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 8 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 8 / 72, + y[2] => 16 / 72, + y[3] => 24 / 72, + y[4] => 32 / 72, + y[5] => 40 / 72, + y[6] => 48 / 72, + y[7] => 56 / 72, + y[8] => 64 / 72, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (fixed, bounded)" begin + let μ = 1.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 8 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 2 / 30, + y[2] => 4 / 30, + y[3] => 6 / 30, + y[4] => 8 / 30, + y[5] => 10 / 30, + y[6] => 1.0, + y[7] => 1.0, + y[8] => 1.0, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (tolerance)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 4 + + @test ToQUBO.Encoding.encoding_bits(e, S, 1 / 12) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S; tol = 1 / 12) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 8 / 20, + y[2] => 16 / 20, + y[3] => 24 / 20, + y[4] => 32 / 20, + -2.0, + ) + @test isnothing(χ) + end + end + + @testset "ℝ (tolerance, bounded)" begin + let μ = 1.0 + ê = ToQUBO.Encoding.Bounded(e, μ) + φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 5 + + @test ToQUBO.Encoding.encoding_bits(ê, S, 1 / 12) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, ê, S; tol = 1 / 12) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => 2 / 6, + y[2] => 4 / 6, + y[3] => 1.0, + y[4] => 1.0, + y[5] => 1.0, + -2.0, + ) + @test isnothing(χ) + end + end + end + end + end + + @testset "⊛ Set" begin + @testset "⋅ One-Hot" begin + let e = ToQUBO.Encoding.OneHot{Float64}() + @testset "Γ ⊂ ℝ" begin + let φ = PBO.vargen(VI) + Γ = [-1.0, -0.5, 0.0, 0.5, 1.0] + n = 5 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, Γ) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => -1.0, + y[2] => -0.5, + y[3] => 0.0, + y[4] => 0.5, + y[5] => 1.0, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + 1.0 + [y[i] => -1.0 for i = 1:n] + [(y[i], y[j]) => 2.0 for i = 1:n for j = (i+1):n] + ], + ) + end + end + + @testset "ℤ" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 5 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => -2.0, + y[2] => -1.0, + y[3] => 0.0, + y[4] => 1.0, + y[5] => 2.0, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + 1.0 + [y[i] => -1.0 for i = 1:n] + [(y[i], y[j]) => 2.0 for i = 1:n for j = (i+1):n] + ], + ) + end + end + + @testset "ℝ (fixed)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 9 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => -2.0, + y[2] => -1.5, + y[3] => -1.0, + y[4] => -0.5, + y[5] => 0.0, + y[6] => 0.5, + y[7] => 1.0, + y[8] => 1.5, + y[9] => 2.0, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + 1.0 + [y[i] => -1.0 for i = 1:n] + [(y[i], y[j]) => 2.0 for i = 1:n for j = (i+1):n] + ], + ) + end + end + + @testset "ℝ (tolerance)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + τ = 1 / 4 + n = 17 + + @test ToQUBO.Encoding.encoding_points(e, S, τ) == n + @test ToQUBO.Encoding.encoding_bits(e, S, τ) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S; tol = τ) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + y[1] => -2.0, + y[2] => -1.75, + y[3] => -1.5, + y[4] => -1.25, + y[5] => -1.0, + y[6] => -0.75, + y[7] => -0.5, + y[8] => -0.25, + y[9] => 0.0, + y[10] => 0.25, + y[11] => 0.5, + y[12] => 0.75, + y[13] => 1.0, + y[14] => 1.25, + y[15] => 1.5, + y[16] => 1.75, + y[17] => 2.0, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + 1.0 + [y[i] => -1.0 for i = 1:n] + [(y[i], y[j]) => 2.0 for i = 1:n for j = (i+1):n] + ], + ) + end + end + end + end + + @testset "⋅ Domain Wall" begin + let e = ToQUBO.Encoding.DomainWall{Float64}() + @testset "Γ ⊂ ℝ" begin + let φ = PBO.vargen(VI) + Γ = [-1.0, -0.25, 0.0, 0.25, 1.0] + n = 4 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, Γ) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + -1.0, + y[1] => 0.75, + y[2] => 0.25, + y[3] => 0.25, + y[4] => 0.75, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + [y[i] => 2.0 for i = 2:n] + [(y[i], y[i+1]) => -2.0 for i = 1:(n-1)] + ], + ) + end + end + + @testset "ℤ" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 4 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + -2.0, + y[1] => 1.0, + y[2] => 1.0, + y[3] => 1.0, + y[4] => 1.0, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + [y[i] => 2.0 for i = 2:n] + [(y[i], y[i+1]) => -2.0 for i = 1:(n-1)] + ], + ) + end + end + + @testset "ℝ (fixed)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + n = 8 + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S, n) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + -2.0, + y[1] => 0.5, + y[2] => 0.5, + y[3] => 0.5, + y[4] => 0.5, + y[5] => 0.5, + y[6] => 0.5, + y[7] => 0.5, + y[8] => 0.5, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + [y[i] => 2.0 for i = 2:n] + [(y[i], y[i+1]) => -2.0 for i = 1:(n-1)] + ], + ) + end + end + + @testset "ℝ (tolerance)" begin + let φ = PBO.vargen(VI) + S = (-2.0, 2.0) + τ = 1 / 4 + n = 16 + + @test ToQUBO.Encoding.encoding_points(e, S, τ) == n + 1 + @test ToQUBO.Encoding.encoding_bits(e, S, τ) == n + + y, ξ, χ = ToQUBO.Encoding.encode(φ, e, S; tol = τ) + + @test length(y) == n + @test y == VI.(1:n) + @test ξ == PBO.PBF{VI,Float64}( + -2.0, + y[1] => 0.25, + y[2] => 0.25, + y[3] => 0.25, + y[4] => 0.25, + y[5] => 0.25, + y[6] => 0.25, + y[7] => 0.25, + y[8] => 0.25, + y[9] => 0.25, + y[10] => 0.25, + y[11] => 0.25, + y[12] => 0.25, + y[13] => 0.25, + y[14] => 0.25, + y[15] => 0.25, + y[16] => 0.25, + ) + @test χ == PBO.PBF{VI,Float64}( + [ + [y[i] => 2.0 for i = 2:n] + [(y[i], y[i+1]) => -2.0 for i = 1:(n-1)] + ], + ) + end + end + end + end + end + end + + return nothing +end \ No newline at end of file diff --git a/test/unit/interface/interface.jl b/test/unit/interface/interface.jl deleted file mode 100644 index dd4c138f..00000000 --- a/test/unit/interface/interface.jl +++ /dev/null @@ -1,9 +0,0 @@ -include("moi.jl") -include("jump.jl") - -function test_interface() - @testset "Interface" verbose = true begin - test_moi() - test_jump() - end -end \ No newline at end of file diff --git a/test/unit/interface/jump.jl b/test/unit/interface/jump.jl deleted file mode 100644 index 56a0de2f..00000000 --- a/test/unit/interface/jump.jl +++ /dev/null @@ -1,5 +0,0 @@ -function test_jump() - @testset "JuMP" verbose = true begin - @testset "Models" begin end - end -end \ No newline at end of file diff --git a/test/unit/interface/moi.jl b/test/unit/interface/moi.jl deleted file mode 100644 index e7b4f27e..00000000 --- a/test/unit/interface/moi.jl +++ /dev/null @@ -1,220 +0,0 @@ -function test_moi() - @testset "MathOptInterface" verbose = true begin - @testset "Optimizer" verbose = true begin - @testset "Attributes" begin - model = ToQUBO.Optimizer{Float64}() - - @test MOI.is_empty(model) - @test MOI.get(model, ToQUBO.Tol()) ≈ 1E-2 - - MOI.set(model, ToQUBO.Tol(), 1E-3) - @test MOI.get(model, ToQUBO.Tol()) ≈ 1E-3 - - MOI.empty!(model) - @test MOI.get(model, ToQUBO.Tol()) ≈ 1E-3 - end - @testset "Instantiate" begin - model = MOI.instantiate( - () -> ToQUBO.Optimizer{Float64}(ExactSampler.Optimizer{Float64}), - with_bridge_type = Float64, - ) - @test MOI.is_empty(model) - end - end - @testset "Models" verbose = true begin - @testset "Binary Knapsack" begin - model = MOI.instantiate( - () -> ToQUBO.Optimizer{Float64}(ExactSampler.Optimizer{Float64}), - with_bridge_type = Float64, - ) - n = 3 - x, _ = MOI.add_constrained_variables(model, repeat([MOI.ZeroOne()], n)) - v = [1.0, 2.0, 3.0] # value - w = [0.3, 0.5, 1.0] # weight - C = 3.2 # capacity - - @test length(x) == length(v) == n - - MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - MOI.set( - model, - MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - MOI.ScalarAffineFunction{Float64}( - MOI.ScalarAffineTerm{Float64}.(v, x), - 0.0, - ), - ) - MOI.add_constraint( - model, - MOI.ScalarAffineFunction{Float64}( - MOI.ScalarAffineTerm{Float64}.(w, x), - 0.0, - ), - MOI.LessThan{Float64}(C), - ) - - MOI.optimize!(model) - - @test MOI.get.(model, MOI.VariablePrimal(), x) ≈ [1.0, 1.0, 1.0] - end - end - # @test sol ≈ [2.0, 5.0, 0.0] || sol ≈ [4.0, 4.0, 0.0] - - # end - - # @testset "MOI UI - IQP" begin - - # model = MOI.instantiate( - # ()->ToQUBO.Optimizer(SimulatedAnnealer.Optimizer), - # with_bridge_type = Float64, - # ) - - # @test MOI.is_empty(model) - - # n = 3 - # x = MOI.add_variables(model, n); - # v = [1.0 2.0 3.0] # value - # w = [0.5 0.0 0.0 - # 0.0 0.0 0.5 - # 0.0 0.5 1.0] # weights - # C = 6.4 # capacity - - # # will work because ToQUBO.Optimizer will know how to expand an integer variables - # # into binaries - # for xᵢ in x - # MOI.add_constraint(model, xᵢ, MOI.Integer()) - # MOI.add_constraint(model, xᵢ, MOI.Interval{Float64}(0.0, 3.0)) - # end - - # MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - - # MOI.set( - # model, - # MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - # MOI.ScalarAffineFunction{Float64}( - # [MOI.ScalarAffineTerm{Float64}(v[i], x[i]) for i = 1:n], - # 0.0, - # ), - # ); - - # MOI.add_constraint( - # model, - # MOI.ScalarQuadraticFunction{Float64}( - # [MOI.ScalarQuadraticTerm{Float64}(w[i, j], x[i], x[j]) for i = 1:n for j = 1:n], - # [], - # 0.0, - # ), - # MOI.LessThan{Float64}(C), - # ) - - # MOI.optimize!(model) - - # sol = - - # @test sol ≈ [2.0, 2.0, 1.0] || sol ≈ [2.0, 0.0, 2.0] - - # end - - # @testset "MOI UI - LP" begin - - # model = MOI.instantiate( - # ()->ToQUBO.Optimizer(SimulatedAnnealer.Optimizer), - # with_bridge_type = Float64, - # ) - - # @test MOI.is_empty(model) == true - - # @test MOI.get(model, ToQUBO.Tol()) ≈ 1e-6 # default value - - # MOI.set(model, ToQUBO.Tol(), 1e-2) - - # @test MOI.get(model, ToQUBO.Tol()) ≈ 1e-2 - - # x = MOI.add_variables(model, 3); - # v = [1.0, 2.0, 3.0] # value - # w = [0.3, 0.5, 1.0] # weight - # C = 3.2 # capacity - - # # will work because ToQUBO.Optimizer will know how to expand integer variables - # # into binaries - # for xᵢ in x - # # MOI.add_constraint(model, xᵢ, MOI.Integer()) - # MOI.add_constraint(model, xᵢ, MOI.Interval{Float64}(0.0, 5.0)) - # end - - # MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - - # MOI.set( - # model, - # MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - # MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}.(v, x), 0.0), - # ); - - # MOI.add_constraint( - # model, - # MOI.ScalarAffineFunction{Float64}(MOI.ScalarAffineTerm{Float64}.(w, x), 0.0), - # MOI.LessThan{Float64}(C), - # ) - - # MOI.optimize!(model) - - # sol = MOI.get.(model, MOI.VariablePrimal(), x) - - # @test sol ≈ [2.142857142857143, 5.0, 0.0] - - # end - - # @testset "MOI UI - QP" begin - - # model = MOI.instantiate( - # ()->ToQUBO.Optimizer(SimulatedAnnealer.Optimizer), - # with_bridge_type = Float64, - # ) - - # @test MOI.is_empty(model) - - # n = 3 - # x = MOI.add_variables(model, n); - # v = [1.0 2.0 3.0] # value - # w = [0.5 0.0 0.0 - # 0.0 0.0 0.5 - # 0.0 0.5 1.0] # weights - # C = 6.4 # capacity - - # # will work because ToQUBO.Optimizer will know how to expand continuous variables - # # into binaries - # for xᵢ in x - # # MOI.add_constraint(model, xᵢ, MOI.Integer()) - # MOI.add_constraint(model, xᵢ, MOI.Interval{Float64}(0.0, 3.0)) - # end - - # MOI.set(model, MOI.ObjectiveSense(), MOI.MAX_SENSE) - - # MOI.set( - # model, - # MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), - # MOI.ScalarAffineFunction{Float64}( - # [MOI.ScalarAffineTerm{Float64}(v[i], x[i]) for i = 1:n], - # 0.0, - # ), - # ); - - # MOI.add_constraint( - # model, - # MOI.ScalarQuadraticFunction{Float64}( - # [MOI.ScalarQuadraticTerm{Float64}(w[i, j], x[i], x[j]) for i = 1:n for j = 1:n], - # [], - # 0.0, - # ), - # MOI.LessThan{Float64}(C), - # ) - - # MOI.optimize!(model) - - # sol = MOI.get.(model, MOI.VariablePrimal(), x) - - # @test sol ≈ [2.0, 0.0, 2.0] || sol ≈ [2.0, 2.0, 1.0] - - # end - end -end \ No newline at end of file diff --git a/test/unit/lib/lib.jl b/test/unit/lib/lib.jl deleted file mode 100644 index 3b6b1a66..00000000 --- a/test/unit/lib/lib.jl +++ /dev/null @@ -1,11 +0,0 @@ -include("version.jl") -include("pbo.jl") -include("virtual.jl") - -function test_lib() - @testset "Library" verbose = true begin - test_version() - test_pbo() - test_virtual() - end -end \ No newline at end of file diff --git a/test/unit/lib/pbo.jl b/test/unit/lib/pbo.jl deleted file mode 100644 index 57922b20..00000000 --- a/test/unit/lib/pbo.jl +++ /dev/null @@ -1,141 +0,0 @@ -function test_pbo() - @testset "Pseudo-Boolean Optimization" verbose = true begin - @testset "Constructors" begin - for (x, y) in Assets.PBF_CONSTRUCTOR_LIST - @test x == y - end - end - - @testset "Operators" verbose = true begin - @testset "$op" for (op::Function, data) in Assets.PBF_OPERATOR_LIST - for (x, y) in data - if y isa Type{<:Exception} - @test_throws y op(x...) - else - @test op(x...) == y - end - end - end - end - - @testset "Evaluation" verbose = true begin - @testset "$tag" for (tag::String, data) in Assets.PBF_EVALUATION_LIST - for ((f, x), y) in data - @test f(x) == y - end - end - end - - @testset "QUBOTools Interface" verbose = true begin - @testset "$fn" for (fn::Function, data) in Assets.PBF_QUBOTOOLS_LIST - for (x, y) in data - if y isa Type{<:Exception} - @test_throws y op(x...) - else - @test fn(x...) == y - end - end - end - end - end -end - -# function test_pbo() -# @testset "PBO" verbose = true begin -# # Definitions -# S = Symbol -# T = Float64 - -# # :: Canonical Constructor :: -# - -# @testset "QUBO" begin -# x = PBO.variable_map(p) -# Q, α, β = PBO.qubo(p, Dict) -# @test Q == Dict{Tuple{Int,Int},T}((x[:x], x[:x]) => 1.0, (x[:x], x[:y]) => -2.0) -# @test α == 1.0 -# @test β == 0.5 - -# x = PBO.variable_map(q) -# Q, α, β = PBO.qubo(q, Dict) -# @test Q == Dict{Tuple{Int,Int},T}((x[:y], x[:y]) => 1.0, (x[:x], x[:y]) => 2.0) -# @test α == 1.0 -# @test β == 0.5 - -# x = PBO.variable_map(r) -# Q, α, β = PBO.qubo(r, Dict) -# @test Q == Dict{Tuple{Int,Int},T}((x[:z], x[:z]) => -1.0) -# @test α == 1.0 -# @test β == 1.0 - -# Q, α, β = PBO.qubo(p, Matrix) -# @test Q == Matrix{T}([1.0 -1.0; -1.0 0.0]) -# @test β == 0.5 - -# Q, α, β = PBO.qubo(q, Matrix) -# @test Q == Matrix{T}([0.0 1.0; 1.0 1.0]) -# @test β == 0.5 - -# Q, α, β = PBO.qubo(r, Matrix) -# @test Q == Matrix{T}([-1.0][:, :]) -# @test β == 1.0 - -# @test_throws Exception PBO.qubo(s, Dict) -# @test_throws Exception PBO.qubo(s, Matrix) -# end - -# @testset "Evaluation" begin -# for x = 0:1, y = 0:1, z = 0:1 -# @test f(:x => x, :y => y, :z => z) == 0.5 + (x + y + z == 1.0) -# end - -# @test g() == 1.0 - -# for x = 0:1, y = 0:1, z = 0:1, w = 0:1 -# @test h(:x => x, :y => y, :z => z, :w => w) == 1.0 + x + y + z + w -# end -# end - -# @testset "Calculus" begin -# @test PBO.gap(f; bound = :loose) == -# (PBO.upperbound(f; bound = :loose) - PBO.lowerbound(f; bound = :loose)) -# @test PBO.gap(g; bound = :loose) == -# (PBO.upperbound(g; bound = :loose) - PBO.lowerbound(g; bound = :loose)) -# @test PBO.gap(h; bound = :loose) == -# (PBO.upperbound(h; bound = :loose) - PBO.lowerbound(h; bound = :loose)) -# end - -# @testset "Quadratization" begin -# function aux(n::Union{Integer,Nothing}) -# if isnothing(n) -# return :w -# else -# return [:w, :t, :u, :v][1:n] -# end -# end - -# @test PBO.quadratize(aux, p) == p -# @test PBO.quadratize(aux, q) == q -# @test PBO.quadratize(aux, r) == r -# @test PBO.quadratize(aux, s) == PBO.PBF{S,T}( -# :w => 3.0, -# [:x, :w] => 3.0, -# [:y, :w] => -3.0, -# [:z, :w] => -3.0, -# [:y, :z] => 3.0, -# ) -# end - -# @testset "Discretization" begin -# @test PBO.discretize(p; tol = 0.1) == -# PBO.PBF{S,T}(nothing => 1.0, :x => 2.0, [:x, :y] => -4.0) -# @test PBO.discretize(q; tol = 0.1) == -# PBO.PBF{S,T}(nothing => 1.0, :y => 2.0, [:x, :y] => 4.0) -# @test PBO.discretize(r; tol = 0.1) == PBO.PBF{S,T}(nothing => 1.0, :z => -1.0) -# end - -# @testset "Print" begin -# @test "$(r)" == "1.0 - 1.0z" || "$(r)" == "-1.0z + 1.0" -# @test "$(s)" == "3.0x*y*z" -# end -# end \ No newline at end of file diff --git a/test/unit/lib/version.jl b/test/unit/lib/version.jl deleted file mode 100644 index 91a77dd8..00000000 --- a/test/unit/lib/version.jl +++ /dev/null @@ -1,8 +0,0 @@ -function test_version() - @testset "Version" begin - proj_path = joinpath(@__DIR__, "..", "..", "..", "Project.toml") - proj_data = TOML.parsefile(proj_path) - - @test MOI.get(ToQUBO.Optimizer(), MOI.SolverVersion()) == VersionNumber(proj_data["version"]) - end -end \ No newline at end of file diff --git a/test/unit/lib/virtual.jl b/test/unit/lib/virtual.jl deleted file mode 100644 index 7962b58e..00000000 --- a/test/unit/lib/virtual.jl +++ /dev/null @@ -1,433 +0,0 @@ -function test_virtual() - @testset "VirtualMapping" verbose = true begin - @testset "Virtual Model" begin - model = ToQUBO.VirtualModel() - - @test MOI.is_empty(model) - end - - @testset "Encodings" verbose = true begin - @testset "Linear" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - γ = [1.0, 2.0, 3.0] - α = 1.0 - - v = ToQUBO.encode!(model, ToQUBO.Linear(), x, γ, α) - y = ToQUBO.target(v) - - @test length(y) == 3 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}( - y[1] => γ[1], - y[2] => γ[2], - y[3] => γ[3], - nothing => α, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v] - end - - @testset "Mirror" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - - v = ToQUBO.encode!(model, ToQUBO.Mirror(), x) - y = ToQUBO.target(v) - - @test length(y) == 1 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}(y[1]) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v] - end - - @testset "Unary ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - - v = ToQUBO.encode!(model, ToQUBO.Unary(), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 4 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}( - y[1] => 1.0, - y[2] => 1.0, - y[3] => 1.0, - y[4] => 1.0, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v] - end - - @testset "Unary ℝ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - n = 4 - - v = ToQUBO.encode!(model, ToQUBO.Unary(), x, a, b, n) - y = ToQUBO.target(v) - - @test length(y) == n - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => 1.0, - y[2] => 1.0, - y[3] => 1.0, - y[4] => 1.0, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v] - end - - @testset "Binary ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - - v = ToQUBO.encode!(model, ToQUBO.Binary(), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 3 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}( - y[1] => 1.0, - y[2] => 2.0, - y[3] => 1.0, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v] - end - - @testset "Binary ℝ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - n = 3 - - v = ToQUBO.encode!(model, ToQUBO.Binary(), x, a, b, n) - y = ToQUBO.target(v) - - @test length(y) == n - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => 4 / 7, - y[2] => 8 / 7, - y[3] => 16 / 7, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v] - end - - @testset "Arithmetic ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - - v = ToQUBO.encode!(model, ToQUBO.Arithmetic(), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 3 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}( - y[1] => 1.0, - y[2] => 2.0, - y[3] => 1.0, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v] - end - - @testset "Arithmetic ℝ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - n = 3 - - v = ToQUBO.encode!(model, ToQUBO.Arithmetic(), x, a, b, n) - y = ToQUBO.target(v) - - @test length(y) == n - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => 2 / 3, - y[2] => 4 / 3, - y[3] => 6 / 3, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v] - end - - @testset "One Hot" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - γ = [-1.0, -0.5, 0.0, 0.5, 1.0] - - v = ToQUBO.encode!(model, ToQUBO.OneHot(), x, γ) - y = ToQUBO.target(v) - - @test length(y) == length(γ) - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => -1.0, - y[2] => -0.5, - y[4] => 0.5, - y[5] => 1.0, - ) - @test ToQUBO.penaltyfn(v) ≈ (PBO.PBF{VI,Float64}(-1.0, y...)^2) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v, v] - end - - @testset "One Hot ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - - v = ToQUBO.encode!(model, ToQUBO.OneHot(), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 5 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => -2.0, - y[2] => -1.0, - y[4] => 1.0, - y[5] => 2.0, - ) - @test ToQUBO.penaltyfn(v) ≈ (PBO.PBF{VI,Float64}(-1.0, y...)^2) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v, v] - end - @testset "One Hot ℝ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - n = 5 - - v = ToQUBO.encode!(model, ToQUBO.OneHot(), x, a, b, n) - y = ToQUBO.target(v) - - @test length(y) == n - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => -2.0, - y[2] => -1.0, - y[4] => 1.0, - y[5] => 2.0, - ) - @test ToQUBO.penaltyfn(v) ≈ (PBO.PBF{VI,Float64}(-1.0, y...)^2) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v, v] - end - @testset "Domain Wall ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - - v = ToQUBO.encode!(model, ToQUBO.DomainWall(), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 4 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => -1.0, - y[2] => -1.0, - y[3] => -1.0, - y[4] => -1.0, - ) - @test ToQUBO.penaltyfn(v) ≈ PBO.PBF{VI,Float64}( - y[2] => 2.0, - y[3] => 2.0, - y[4] => 2.0, - [y[1], y[2]] => -2.0, - [y[2], y[3]] => -2.0, - [y[3], y[4]] => -2.0, - ) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v] - end - - @testset "Domain Wall ℝ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-2.0, 2.0) - n = 5 - - v = ToQUBO.encode!(model, ToQUBO.DomainWall(), x, a, b, n) - y = ToQUBO.target(v) - - @test length(y) == n - 1 - - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) ≈ PBO.PBF{VI,Float64}( - y[1] => -1.0, - y[2] => -1.0, - y[3] => -1.0, - y[4] => -1.0, - ) - @test ToQUBO.penaltyfn(v) ≈ PBO.PBF{VI,Float64}( - y[2] => 2.0, - y[3] => 2.0, - y[4] => 2.0, - [y[1], y[2]] => -2.0, - [y[2], y[3]] => -2.0, - [y[3], y[4]] => -2.0, - ) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v] - end - - @testset "Bounded(Unary) ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-10.0, 10.0) - - v = ToQUBO.encode!(model, ToQUBO.Bounded{ToQUBO.Unary}(5.0), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 8 - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}( - y[1] => 1.0, - y[2] => 1.0, - y[3] => 1.0, - y[4] => 1.0, - y[5] => 5.0, - y[6] => 5.0, - y[7] => 5.0, - y[8] => 1.0, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v, v, v, v, v] - end - - @testset "Bounded(Binary) ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-10.0, 10.0) - - v = ToQUBO.encode!(model, ToQUBO.Bounded{ToQUBO.Binary}(5.0), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 6 - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}( - y[1] => 1.0, - y[2] => 2.0, - y[3] => 4.0, - y[4] => 5.0, - y[5] => 5.0, - y[6] => 3.0, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v, v, v] - end - - @testset "Bounded(Arithmetic) ℤ" begin - model = ToQUBO.VirtualModel() - - x = MOI.add_variable(model.source_model) - a, b = (-10.0, 10.0) - - v = ToQUBO.encode!(model, ToQUBO.Bounded{ToQUBO.Arithmetic}(5.0), x, a, b) - y = ToQUBO.target(v) - - @test length(y) == 6 - @test ToQUBO.source(v) == x - @test ToQUBO.expansion(v) == PBO.PBF{VI,Float64}( - y[1] => 1.0, - y[2] => 2.0, - y[3] => 3.0, - y[4] => 4.0, - y[5] => 5.0, - y[6] => 5.0, - nothing => a, - ) - @test isnothing(ToQUBO.penaltyfn(v)) - - @test model.variables == [v] - @test model.source[ToQUBO.source(v)] == (v) - @test [model.target[y] for y in ToQUBO.target(v)] == [v, v, v, v, v, v] - end - end - end -end \ No newline at end of file diff --git a/test/unit/unit.jl b/test/unit/unit.jl index 4cc9bea9..73319561 100644 --- a/test/unit/unit.jl +++ b/test/unit/unit.jl @@ -1,11 +1,11 @@ -include("lib/lib.jl") include("compiler/compiler.jl") +include("encoding/encoding.jl") function test_unit() - @testset "Unit Tests" verbose = true begin - test_lib() + @testset "⊚ Unit Tests" verbose = true begin + test_encoding_methods() test_compiler() end return nothing -end \ No newline at end of file +end