diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..7170161 --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1,2 @@ +align_assignment = true +align_pair_arrow = true diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..449ad95 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,58 @@ +name: Deployment + +on: + workflow_dispatch: + push: + branches: '*' + +jobs: + publish: + if: ${{ !(github.event_name == 'push') || contains(github.event.head_commit.message, '[deploy]') }} + runs-on: ubuntu-latest + env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v2 + - uses: julia-actions/setup-julia@v1 + with: + version: '1.10' + + # - name: Last release tag + Run main script + Git tree hash + Next release tag + # run: | + # export LAST_QUBOLIB_TAG="$(gh release view | sed -nr 's/tag:\s*(v\S*)/\1/p')" + # julia "$GITHUB_WORKSPACE/deployment/script.jl" + # export GIT_TREE_HASH=$(cat $GITHUB_WORKSPACE/deployment/tree.hash) + # echo "GIT_TREE_HASH=$GIT_TREE_HASH" >> $GITHUB_ENV + # export NEXT_QUBOLIB_TAG=$(cat $GITHUB_WORKSPACE/deployment/next.tag) + # echo "TAG=$NEXT_QUBOLIB_TAG" >> $GITHUB_ENV + + # - name: Compute SHA256 for the compressed tarball + # run: | + # SHA_256="$(sha256sum -z $GITHUB_WORKSPACE/dist/qubolib.tar.gz | cut -d " " -f 1)" + # echo "SHA_256=$SHA_256" >> $GITHUB_ENV + + # - name: Write release title + # run: | + # TITLE="QUBOLib $TAG" + # echo "TITLE=$TITLE" >> $GITHUB_ENV + + # - name: Write release notes + # run: envsubst < "$GITHUB_WORKSPACE/deployment/NOTES.md" > "$RUNNER_TEMP/NOTES.md" + + # - name: Publish release + # run: > + # gh release create $TAG + # --latest + # --notes-file "$RUNNER_TEMP/NOTES.md" + # --title "$TITLE" + # --target $GITHUB_SHA + # dist/qubolib.tar.gz + + # - name: Update Documentation + # run: | + # git config user.name "github-actions" + # git config user.email "github-actions@github.com" + # git add "./*README.md" + # git commit --allow-empty -m "Update Documentation" + # git push \ No newline at end of file diff --git a/.gitignore b/.gitignore index 29126e4..2ecd02d 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,9 @@ docs/site/ # committed for packages, but should be committed for applications that require a static # environment. Manifest.toml + +# Distribution Files +dist/ + +# PythonCall +.CondaPkg \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..78d2538 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ + +all: build + +setup: + @julia --project -e 'import Pkg; Pkg.instantiate()' + @julia --project=scripts/build -e 'import Pkg; Pkg.develop(path=@__DIR__)' + +build: + @julia --project=scripts/build ./scripts/build/build.jl + +clear: + @rm -rf ./dist + +run: setup + @julia --project=scripts/run/mqlib ./scripts/run/mqlib/run.jl + +setup-docs: + @julia --project=docs -e 'import Pkg; Pkg.develop(path=@__DIR__); Pkg.instantiate()' + +docs: + @julia --project=docs ./docs/make.jl --skip-deploy diff --git a/Project.toml b/Project.toml index ecde0f7..8b884e2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,23 +1,29 @@ -name = "QUBOLib" -uuid = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" -authors = ["pedromxavier "] +name = "QUBOLib" +uuid = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" +authors = ["pedromxavier "] version = "0.1.0" [deps] -DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" -JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" -JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" +ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" +JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +JSONSchema = "7d188eb4-7ad8-530c-ae41-71a32a6d4692" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" -LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" -LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" -Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" -ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" -QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" -SQLite = "0aa819cd-b072-5ff4-a722-6bc24af294d9" -TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" -Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" -UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f" +LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" +Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" +PseudoBooleanOptimization = "c8fa9a04-bc42-452d-8558-dc51757be744" +QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" +Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +SQLite = "0aa819cd-b072-5ff4-a722-6bc24af294d9" +SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" +TOML = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] -QUBOTools = "0.9" +QUBOTools = "0.10" diff --git a/README.md b/README.md index c3d5b32..f8b6986 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,11 @@ # QUBOLib.jl -QUBO Instances for benchmarking + +
+ + ToQUBO.jl + +
+
## Introduction diff --git a/docs/Project.toml b/docs/Project.toml index 1a6d309..e59ed1d 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,9 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterDiagrams = "a106ebf2-4182-4cba-90d4-44cd3cc36e85" +DocumenterInterLinks = "d12716ef-a0f6-4df4-a9f1-a5a34e75c656" +QUBOLib = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" [compat] -Documenter = "~0.27" +Documenter = "1" +DocumenterDiagrams = "1" diff --git a/docs/make.jl b/docs/make.jl index c8fc548..cf67934 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,29 +1,47 @@ using Documenter +using DocumenterDiagrams +using DocumenterInterLinks + using QUBOLib # Set up to run docstrings with jldoctest DocMeta.setdocmeta!(QUBOLib, :DocTestSetup, :(using QUBOLib); recursive = true) +links = InterLinks( + "QUBOTools" => "https://juliaqubo.github.io/QUBOTools.jl/dev/objects.inv", +) + makedocs(; - modules = [QUBOLib], - doctest = true, - clean = true, - format = Documenter.HTML( + modules = [QUBOLib], + doctest = true, + clean = true, + warnonly = [:missing_docs], + format = Documenter.HTML( assets = ["assets/extra_styles.css", "assets/favicon.ico"], mathengine = Documenter.KaTeX(), sidebar_sitename = false, ), sitename = "QUBOLib.jl", authors = "Pedro Maciel Xavier and David E. Bernal Neira", - pages = [ - "Home" => "index.md", - "API" => "api.md", + pages = [ + "Home" => "index.md", + "API" => "api.md", + "Manual" => [ + "Introduction" => "manual/0-intro.md", + "Basic Usage" => "manual/1-basic.md", + "Advanced Usage" => "manual/2-advanced.md", + ], + # "Booklet" => [ + # "Introduction" => "booklet/0-intro.md", + # "Library Design" => "booklet/1-design.md", + # ], ], - workdir = @__DIR__, + plugins = [links], + workdir = @__DIR__, ) if "--skip-deploy" ∈ ARGS @warn "Skipping deployment" else - deploydocs(repo = raw"github.com/pedromxavier/QUBOLib.jl.git", push_preview = true) + deploydocs(repo = raw"github.com/JuliaQUBO/QUBOLib.jl.git", push_preview = true) end diff --git a/docs/src/api.md b/docs/src/api.md index 01cc213..2d1ebd9 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -1,14 +1,73 @@ # API -## List items +## Actions + +## Path Routing + +```@docs +QUBOLib.library_path +QUBOLib.database_path +QUBOLib.archive_path +``` + +```@docs +QUBOLib.root_path +QUBOLib.dist_path +QUBOLib.build_path +QUBOLib.cache_path +``` + +## Library Index + +```@docs +QUBOLib.LibraryIndex +``` ```@docs -QUBOLib.list_collections -QUBOLib.list_instances +QUBOLib.database +QUBOLib.archive ``` -## Load instances +## Data Access + +```@docs +QUBOLib.access +``` ```@docs +QUBOLib.load_collection QUBOLib.load_instance +QUBOLib.load_solution ``` + +## Data Management + +```@docs +QUBOLib.add_collection! +QUBOLib.add_instance! +QUBOLib.add_solution! +QUBOLib.add_solver! +``` + +```@docs +QUBOLib.remove_collection! +QUBOLib.remove_instance! +QUBOLib.remove_solution! +QUBOLib.remove_solver! +``` + +## Instance Synthesis + +```@docs +QUBOLib.Synthesis.AbstractProblem +QUBOLib.Synthesis.generate +``` + +### Problem Types + +```@docs +QUBOLib.Synthesis.NAE3SAT +QUBOLib.Synthesis.XORSAT +QUBOLib.Synthesis.Wishart +QUBOLib.Synthesis.SherringtonKirkpatrick +``` \ No newline at end of file diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index 1fcdf61..0c45195 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -26,59 +26,38 @@ } + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + + QUBO + + + Lib + + + + + + + + + - \ No newline at end of file + + + diff --git a/docs/src/booklet/0-intro.md b/docs/src/booklet/0-intro.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/src/booklet/1-design.md b/docs/src/booklet/1-design.md new file mode 100644 index 0000000..d7ed966 --- /dev/null +++ b/docs/src/booklet/1-design.md @@ -0,0 +1,10 @@ +# Database Design + +In this section we discuss the decisions and specifications behind the construction of the database. + +## Models and Solutions + +The HDF5 file format is used to store the data. The data is stored in a hierarchical structure, which is a natural fit for the data. The data is stored in a tree-like structure, with the root group being the top level. The root group contains the following groups: + +- `instances`: Contains the instances of the data. +- `solutions`: Contains the solutions to the instances. diff --git a/docs/src/booklet/model.erd b/docs/src/booklet/model.erd new file mode 100644 index 0000000..02cb04f --- /dev/null +++ b/docs/src/booklet/model.erd @@ -0,0 +1,1216 @@ +{ + "$schema": "https://raw.githubusercontent.com/dineug/erd-editor/main/json-schema/schema.json", + "version": "3.0.0", + "settings": { + "width": 2000, + "height": 2000, + "scrollTop": -26.1011, + "scrollLeft": 0, + "zoomLevel": 1, + "show": 431, + "database": 4, + "databaseName": "", + "canvasType": "ERD", + "language": 1, + "tableNameCase": 4, + "columnNameCase": 2, + "bracketType": 1, + "relationshipDataTypeSync": true, + "relationshipOptimization": false, + "columnOrder": [ + 1, + 2, + 4, + 8, + 16, + 32, + 64 + ], + "maxWidthComment": -1 + }, + "doc": { + "tableIds": [ + "jDnk6FD4hm_KMqhH_RwSb", + "om5Bt9XofodPHGdyYzt3T", + "OASFdQTyDm_Da6SyshDHz", + "4n9HJGfi4-dks_pkia6Sr" + ], + "relationshipIds": [ + "1IewvhI07x-8lB--Kynv7", + "qh9ik3mw8bC1nd2N4Z70o", + "GZ2ff-WVLAglGFqqUw3Ru" + ], + "indexIds": [], + "memoIds": [] + }, + "collections": { + "tableEntities": { + "jDnk6FD4hm_KMqhH_RwSb": { + "id": "jDnk6FD4hm_KMqhH_RwSb", + "name": "Instances", + "comment": "", + "columnIds": [ + "BnMcYcvifd5DUaUS5Sc--", + "XDgzkaxhdq3b-i82F5g73", + "ckVZLXAwDboT46oYGgWYL", + "hQdMrer7qlTWkgyJVAfCs", + "RxP3TIIIbok1MkZmv7OUi", + "iLbmNmc-6HBOKz6NyOBri", + "wV3X9ebPnIbTQa8uFn3v2", + "qmP8-f9jFVYVnVq0vUvfN", + "-jWaI7mVUHOvTt3xn3iWV", + "qaRu-FTdsKrdW_bBmm52H", + "6YCxt1abfwP99p-DpTTkm", + "Eq8G3s76vrmQfIXjrl3wo", + "0q25OZ5OKv4BMpwvL5ov1" + ], + "seqColumnIds": [ + "BnMcYcvifd5DUaUS5Sc--", + "Kzk47_WZsF0o81zYQWP4q", + "XDgzkaxhdq3b-i82F5g73", + "ckVZLXAwDboT46oYGgWYL", + "hQdMrer7qlTWkgyJVAfCs", + "RxP3TIIIbok1MkZmv7OUi", + "iLbmNmc-6HBOKz6NyOBri", + "wV3X9ebPnIbTQa8uFn3v2", + "qmP8-f9jFVYVnVq0vUvfN", + "-jWaI7mVUHOvTt3xn3iWV", + "qaRu-FTdsKrdW_bBmm52H", + "6YCxt1abfwP99p-DpTTkm", + "Eq8G3s76vrmQfIXjrl3wo", + "0q25OZ5OKv4BMpwvL5ov1" + ], + "ui": { + "x": 764.5554, + "y": 11.6668, + "zIndex": 2, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1707648090631, + "createAt": 1707069090217 + } + }, + "om5Bt9XofodPHGdyYzt3T": { + "id": "om5Bt9XofodPHGdyYzt3T", + "name": "Collections", + "comment": "", + "columnIds": [ + "TEVAnjSiSxgq0HfLJ9l_g", + "hmpKJtD09UPI-St2jQC_A", + "QtFsQmxkhmwRVA8kjS_k0", + "-JAozTsTy-vLm0mbRsy30", + "6_vyUE8Sn44Qnd_Tvpn19" + ], + "seqColumnIds": [ + "TEVAnjSiSxgq0HfLJ9l_g", + "hmpKJtD09UPI-St2jQC_A", + "QtFsQmxkhmwRVA8kjS_k0", + "-JAozTsTy-vLm0mbRsy30", + "6_vyUE8Sn44Qnd_Tvpn19" + ], + "ui": { + "x": 8.6668, + "y": 13.889, + "zIndex": 41, + "widthName": 62, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1707649186522, + "createAt": 1707069338899 + } + }, + "OASFdQTyDm_Da6SyshDHz": { + "id": "OASFdQTyDm_Da6SyshDHz", + "name": "Solutions", + "comment": "Each entry has a summary of a SampleSet, that can be accessed using the solution ID", + "columnIds": [ + "l5ph0ydr5sUlb28faHClb", + "1VEX0GRBZidEVQr4ywLRC", + "hk3EuFZ1chYw6-j8AaoGX", + "DA191Kizrzt3StPuCjScS", + "Xb9AuyicnFadrB5NhhEQc", + "K1lvvk-kVz8s8dSkXEd4R" + ], + "seqColumnIds": [ + "l5ph0ydr5sUlb28faHClb", + "1VEX0GRBZidEVQr4ywLRC", + "hk3EuFZ1chYw6-j8AaoGX", + "DA191Kizrzt3StPuCjScS", + "Xb9AuyicnFadrB5NhhEQc", + "7M3a_VTNtsDkqFT6kpbWQ", + "K1lvvk-kVz8s8dSkXEd4R" + ], + "ui": { + "x": 11.7879, + "y": 196.6665, + "zIndex": 42, + "widthName": 60, + "widthComment": 457, + "color": "" + }, + "meta": { + "updateAt": 1707649086861, + "createAt": 1707170631227 + } + }, + "4n9HJGfi4-dks_pkia6Sr": { + "id": "4n9HJGfi4-dks_pkia6Sr", + "name": "Solvers", + "comment": "", + "columnIds": [ + "CvX_9IX6yQ9hMZpfmfzRw", + "1doV5DTzybkLjNQrYYwVQ" + ], + "seqColumnIds": [ + "CvX_9IX6yQ9hMZpfmfzRw", + "1doV5DTzybkLjNQrYYwVQ" + ], + "ui": { + "x": 765.1218, + "y": 395.5555, + "zIndex": 46, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1707649276439, + "createAt": 1707324293977 + } + } + }, + "tableColumnEntities": { + "BnMcYcvifd5DUaUS5Sc--": { + "id": "BnMcYcvifd5DUaUS5Sc--", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "instance", + "comment": "", + "dataType": "INTEGER", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090629, + "createAt": 1707069098971 + } + }, + "Kzk47_WZsF0o81zYQWP4q": { + "id": "Kzk47_WZsF0o81zYQWP4q", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "collection", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707069322984, + "createAt": 1707069138321 + } + }, + "ckVZLXAwDboT46oYGgWYL": { + "id": "ckVZLXAwDboT46oYGgWYL", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "min", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090630, + "createAt": 1707069333783 + } + }, + "TEVAnjSiSxgq0HfLJ9l_g": { + "id": "TEVAnjSiSxgq0HfLJ9l_g", + "tableId": "om5Bt9XofodPHGdyYzt3T", + "name": "collection", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090632, + "createAt": 1707069345774 + } + }, + "XDgzkaxhdq3b-i82F5g73": { + "id": "XDgzkaxhdq3b-i82F5g73", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "collection", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090630, + "createAt": 1707069453436 + } + }, + "QtFsQmxkhmwRVA8kjS_k0": { + "id": "QtFsQmxkhmwRVA8kjS_k0", + "tableId": "om5Bt9XofodPHGdyYzt3T", + "name": "author", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090632, + "createAt": 1707069481399 + } + }, + "-JAozTsTy-vLm0mbRsy30": { + "id": "-JAozTsTy-vLm0mbRsy30", + "tableId": "om5Bt9XofodPHGdyYzt3T", + "name": "date", + "comment": "", + "dataType": "DATE", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090632, + "createAt": 1707069493277 + } + }, + "6_vyUE8Sn44Qnd_Tvpn19": { + "id": "6_vyUE8Sn44Qnd_Tvpn19", + "tableId": "om5Bt9XofodPHGdyYzt3T", + "name": "description", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 62, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090632, + "createAt": 1707069513514 + } + }, + "l5ph0ydr5sUlb28faHClb": { + "id": "l5ph0ydr5sUlb28faHClb", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "solution", + "comment": "", + "dataType": "INTEGER", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090633, + "createAt": 1707170642655 + } + }, + "1VEX0GRBZidEVQr4ywLRC": { + "id": "1VEX0GRBZidEVQr4ywLRC", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "instance", + "comment": "", + "dataType": "INTEGER", + "default": "", + "options": 8, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090633, + "createAt": 1707170650845 + } + }, + "DA191Kizrzt3StPuCjScS": { + "id": "DA191Kizrzt3StPuCjScS", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "value", + "comment": "Best value found", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 91, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090633, + "createAt": 1707170839276 + } + }, + "Xb9AuyicnFadrB5NhhEQc": { + "id": "Xb9AuyicnFadrB5NhhEQc", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "optimal", + "comment": "", + "dataType": "BOOLEAN", + "default": "FALSE", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707649253088, + "createAt": 1707170875701 + } + }, + "hQdMrer7qlTWkgyJVAfCs": { + "id": "hQdMrer7qlTWkgyJVAfCs", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "max", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090630, + "createAt": 1707186553632 + } + }, + "RxP3TIIIbok1MkZmv7OUi": { + "id": "RxP3TIIIbok1MkZmv7OUi", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "abs_min", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090630, + "createAt": 1707186558440 + } + }, + "iLbmNmc-6HBOKz6NyOBri": { + "id": "iLbmNmc-6HBOKz6NyOBri", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "abs_max", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090630, + "createAt": 1707186565821 + } + }, + "wV3X9ebPnIbTQa8uFn3v2": { + "id": "wV3X9ebPnIbTQa8uFn3v2", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "linear_min", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090630, + "createAt": 1707186570657 + } + }, + "qmP8-f9jFVYVnVq0vUvfN": { + "id": "qmP8-f9jFVYVnVq0vUvfN", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "linear_max", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090631, + "createAt": 1707186576043 + } + }, + "-jWaI7mVUHOvTt3xn3iWV": { + "id": "-jWaI7mVUHOvTt3xn3iWV", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "quadratic_min", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 78, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090631, + "createAt": 1707186581453 + } + }, + "qaRu-FTdsKrdW_bBmm52H": { + "id": "qaRu-FTdsKrdW_bBmm52H", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "quadratic_max", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 81, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090631, + "createAt": 1707186586063 + } + }, + "6YCxt1abfwP99p-DpTTkm": { + "id": "6YCxt1abfwP99p-DpTTkm", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "density", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090631, + "createAt": 1707186620006 + } + }, + "Eq8G3s76vrmQfIXjrl3wo": { + "id": "Eq8G3s76vrmQfIXjrl3wo", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "linear_density", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 75, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090631, + "createAt": 1707186842212 + } + }, + "0q25OZ5OKv4BMpwvL5ov1": { + "id": "0q25OZ5OKv4BMpwvL5ov1", + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "name": "quadratic_density", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 96, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090631, + "createAt": 1707186858665 + } + }, + "7M3a_VTNtsDkqFT6kpbWQ": { + "id": "7M3a_VTNtsDkqFT6kpbWQ", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "solver", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707324289676, + "createAt": 1707324285001 + } + }, + "CvX_9IX6yQ9hMZpfmfzRw": { + "id": "CvX_9IX6yQ9hMZpfmfzRw", + "tableId": "4n9HJGfi4-dks_pkia6Sr", + "name": "solver", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090634, + "createAt": 1707324302797 + } + }, + "1doV5DTzybkLjNQrYYwVQ": { + "id": "1doV5DTzybkLjNQrYYwVQ", + "tableId": "4n9HJGfi4-dks_pkia6Sr", + "name": "", + "comment": "", + "dataType": "INTEGER", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707649280881, + "createAt": 1707324330727 + } + }, + "hk3EuFZ1chYw6-j8AaoGX": { + "id": "hk3EuFZ1chYw6-j8AaoGX", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "solver", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 0, + "ui": { + "keys": 2, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707648090633, + "createAt": 1707324344493 + } + }, + "K1lvvk-kVz8s8dSkXEd4R": { + "id": "K1lvvk-kVz8s8dSkXEd4R", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "timestamp", + "comment": "", + "dataType": "TIMESTAMP", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 70, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707649245677, + "createAt": 1707649086860 + } + }, + "hmpKJtD09UPI-St2jQC_A": { + "id": "hmpKJtD09UPI-St2jQC_A", + "tableId": "om5Bt9XofodPHGdyYzt3T", + "name": "name", + "comment": "", + "dataType": "TEXT", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707649198849, + "createAt": 1707649180399 + } + } + }, + "relationshipEntities": { + "1IewvhI07x-8lB--Kynv7": { + "id": "1IewvhI07x-8lB--Kynv7", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "om5Bt9XofodPHGdyYzt3T", + "columnIds": [ + "TEVAnjSiSxgq0HfLJ9l_g" + ], + "x": 375.6668, + "y": 101.889, + "direction": 2 + }, + "end": { + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "columnIds": [ + "XDgzkaxhdq3b-i82F5g73" + ], + "x": 764.5554, + "y": 103.6668, + "direction": 1 + }, + "meta": { + "updateAt": 1707069453437, + "createAt": 1707069453437 + } + }, + "qh9ik3mw8bC1nd2N4Z70o": { + "id": "qh9ik3mw8bC1nd2N4Z70o", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "columnIds": [ + "BnMcYcvifd5DUaUS5Sc--" + ], + "x": 764.5554, + "y": 287.66679999999997, + "direction": 1 + }, + "end": { + "tableId": "OASFdQTyDm_Da6SyshDHz", + "columnIds": [ + "1VEX0GRBZidEVQr4ywLRC" + ], + "x": 562.7879, + "y": 246.6665, + "direction": 2 + }, + "meta": { + "updateAt": 1707170650846, + "createAt": 1707170650846 + } + }, + "erf7FbeVDQdzYlp6kVzjr": { + "id": "erf7FbeVDQdzYlp6kVzjr", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "OASFdQTyDm_Da6SyshDHz", + "columnIds": [ + "l5ph0ydr5sUlb28faHClb" + ], + "x": 374.5657, + "y": 508.6665, + "direction": 2 + }, + "end": { + "tableId": "4n9HJGfi4-dks_pkia6Sr", + "columnIds": [ + "1doV5DTzybkLjNQrYYwVQ" + ], + "x": 618.455, + "y": 566.4444, + "direction": 1 + }, + "meta": { + "updateAt": 1707324330728, + "createAt": 1707324330728 + } + }, + "GZ2ff-WVLAglGFqqUw3Ru": { + "id": "GZ2ff-WVLAglGFqqUw3Ru", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 1, + "start": { + "tableId": "4n9HJGfi4-dks_pkia6Sr", + "columnIds": [ + "CvX_9IX6yQ9hMZpfmfzRw" + ], + "x": 765.1218, + "y": 447.5555, + "direction": 1 + }, + "end": { + "tableId": "OASFdQTyDm_Da6SyshDHz", + "columnIds": [ + "hk3EuFZ1chYw6-j8AaoGX" + ], + "x": 562.7879, + "y": 346.66650000000004, + "direction": 2 + }, + "meta": { + "updateAt": 1707324866914, + "createAt": 1707324344493 + } + } + }, + "indexEntities": {}, + "indexColumnEntities": {}, + "memoEntities": {} + }, + "lww": { + "jDnk6FD4hm_KMqhH_RwSb": [ + "tableEntities", + 1707069090204, + -1, + { + "name": 1707069096732 + } + ], + "BnMcYcvifd5DUaUS5Sc--": [ + "tableColumnEntities", + 1707069098965, + -1, + { + "name": 1707069362397, + "dataType": 1707069112971, + "default": 1707069116923, + "options(notNull)": 1707069129492, + "options(primaryKey)": 1707069127679 + } + ], + "Kzk47_WZsF0o81zYQWP4q": [ + "tableColumnEntities", + 1707069138311, + 1707069456968, + { + "name": 1707069199829, + "dataType": 1707069321526, + "options(notNull)": 1707069322968 + } + ], + "ckVZLXAwDboT46oYGgWYL": [ + "tableColumnEntities", + 1707069333768, + -1, + { + "name": 1707186534205, + "dataType": 1707186803013, + "options(notNull)": 1707186549752 + } + ], + "om5Bt9XofodPHGdyYzt3T": [ + "tableEntities", + 1707069338883, + -1, + { + "name": 1707069342816 + } + ], + "TEVAnjSiSxgq0HfLJ9l_g": [ + "tableColumnEntities", + 1707069345758, + -1, + { + "name": 1707069358701, + "options(primaryKey)": 1707069366282, + "dataType": 1707069373768 + } + ], + "XDgzkaxhdq3b-i82F5g73": [ + "tableColumnEntities", + 1707069453425, + -1, + { + "options(notNull)": 1707069453425, + "name": 1707069453425, + "dataType": 1707069453425, + "default": 1707069453425, + "comment": 1707069453425 + } + ], + "1IewvhI07x-8lB--Kynv7": [ + "relationshipEntities", + 1707069453425, + -1, + {} + ], + "QtFsQmxkhmwRVA8kjS_k0": [ + "tableColumnEntities", + 1707069481380, + -1, + { + "name": 1707069484587, + "dataType": 1707069487859, + "options(notNull)": 1707069491719 + } + ], + "-JAozTsTy-vLm0mbRsy30": [ + "tableColumnEntities", + 1707069493269, + -1, + { + "name": 1707069511089, + "dataType": 1707069500749 + } + ], + "6_vyUE8Sn44Qnd_Tvpn19": [ + "tableColumnEntities", + 1707069513508, + -1, + { + "name": 1707083064956, + "dataType": 1707083068932 + } + ], + "OASFdQTyDm_Da6SyshDHz": [ + "tableEntities", + 1707170631223, + -1, + { + "name": 1707170635235, + "comment": 1707324478021 + } + ], + "l5ph0ydr5sUlb28faHClb": [ + "tableColumnEntities", + 1707170642652, + -1, + { + "name": 1707170655326, + "dataType": 1707170658705, + "options(primaryKey)": 1707170660028, + "default": 1707170668215 + } + ], + "1VEX0GRBZidEVQr4ywLRC": [ + "tableColumnEntities", + 1707170650841, + -1, + { + "options(notNull)": 1707170650841, + "name": 1707170650841, + "dataType": 1707170650841, + "default": 1707170650841, + "comment": 1707170650841 + } + ], + "qh9ik3mw8bC1nd2N4Z70o": [ + "relationshipEntities", + 1707170650841, + -1, + {} + ], + "DA191Kizrzt3StPuCjScS": [ + "tableColumnEntities", + 1707170839266, + -1, + { + "name": 1707170843449, + "dataType": 1707186821953, + "options(notNull)": 1707170893659, + "comment": 1707324450317 + } + ], + "Xb9AuyicnFadrB5NhhEQc": [ + "tableColumnEntities", + 1707170875696, + -1, + { + "name": 1707170879276, + "dataType": 1707170885169, + "options(notNull)": 1707170888811, + "default": 1707658033708 + } + ], + "hQdMrer7qlTWkgyJVAfCs": [ + "tableColumnEntities", + 1707186553621, + -1, + { + "name": 1707186556519, + "dataType": 1707186808808, + "options(notNull)": 1707186849987 + } + ], + "RxP3TIIIbok1MkZmv7OUi": [ + "tableColumnEntities", + 1707186558428, + -1, + { + "name": 1707186561981, + "dataType": 1707186811184, + "options(notNull)": 1707186850600 + } + ], + "iLbmNmc-6HBOKz6NyOBri": [ + "tableColumnEntities", + 1707186565808, + -1, + { + "name": 1707186568799, + "dataType": 1707186816834, + "options(notNull)": 1707186851107 + } + ], + "wV3X9ebPnIbTQa8uFn3v2": [ + "tableColumnEntities", + 1707186570645, + -1, + { + "name": 1707186574330, + "dataType": 1707186816211, + "options(notNull)": 1707186851573 + } + ], + "qmP8-f9jFVYVnVq0vUvfN": [ + "tableColumnEntities", + 1707186576033, + -1, + { + "name": 1707186579966, + "dataType": 1707186815477, + "options(notNull)": 1707186852070 + } + ], + "-jWaI7mVUHOvTt3xn3iWV": [ + "tableColumnEntities", + 1707186581443, + -1, + { + "name": 1707186584645, + "dataType": 1707186814115, + "options(notNull)": 1707186852546 + } + ], + "qaRu-FTdsKrdW_bBmm52H": [ + "tableColumnEntities", + 1707186586052, + -1, + { + "name": 1707186589484, + "dataType": 1707186813438, + "options(notNull)": 1707186853040 + } + ], + "6YCxt1abfwP99p-DpTTkm": [ + "tableColumnEntities", + 1707186619999, + -1, + { + "name": 1707186796717, + "dataType": 1707186812658, + "options(notNull)": 1707186853578 + } + ], + "Eq8G3s76vrmQfIXjrl3wo": [ + "tableColumnEntities", + 1707186842209, + -1, + { + "name": 1707186846200, + "dataType": 1707186847847, + "options(notNull)": 1707186854153 + } + ], + "0q25OZ5OKv4BMpwvL5ov1": [ + "tableColumnEntities", + 1707186858662, + -1, + { + "name": 1707186861615, + "dataType": 1707186863132, + "options(notNull)": 1707186864859 + } + ], + "7M3a_VTNtsDkqFT6kpbWQ": [ + "tableColumnEntities", + 1707324284996, + 1707324492909, + { + "name": 1707324289674 + } + ], + "4n9HJGfi4-dks_pkia6Sr": [ + "tableEntities", + 1707324293973, + -1, + { + "name": 1707324298885 + } + ], + "CvX_9IX6yQ9hMZpfmfzRw": [ + "tableColumnEntities", + 1707324302793, + -1, + { + "name": 1707324305749, + "dataType": 1707324311293, + "options(notNull)": 1707324312983, + "options(primaryKey)": 1707324318906 + } + ], + "1doV5DTzybkLjNQrYYwVQ": [ + "tableColumnEntities", + 1707324330724, + -1, + { + "options(notNull)": 1707324330724, + "name": 1707658059376, + "dataType": 1707324330724, + "default": 1707324330724, + "comment": 1707324330724 + } + ], + "erf7FbeVDQdzYlp6kVzjr": [ + "relationshipEntities", + 1707324330724, + 1707324341432, + {} + ], + "hk3EuFZ1chYw6-j8AaoGX": [ + "tableColumnEntities", + 1707324344490, + -1, + { + "options(notNull)": 1707324866901, + "name": 1707324344490, + "dataType": 1707324344490, + "default": 1707324344490, + "comment": 1707324344490 + } + ], + "GZ2ff-WVLAglGFqqUw3Ru": [ + "relationshipEntities", + 1707324344490, + -1, + {} + ], + "K1lvvk-kVz8s8dSkXEd4R": [ + "tableColumnEntities", + 1707657880204, + -1, + { + "dataType": 1707658023634, + "options(notNull)": 1707657999801, + "name": 1707658026879 + } + ], + "hmpKJtD09UPI-St2jQC_A": [ + "tableColumnEntities", + 1707657966618, + -1, + { + "name": 1707657974790, + "dataType": 1707657982142, + "options(notNull)": 1707657983650 + } + ] + } +} \ No newline at end of file diff --git a/docs/src/database.md b/docs/src/database.md deleted file mode 100644 index c68e3bc..0000000 --- a/docs/src/database.md +++ /dev/null @@ -1,4 +0,0 @@ -# Database - -In this section we discuss the decisions and specifications behind the construction of the database. - diff --git a/docs/src/index.md b/docs/src/index.md index fc7cef7..0548238 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -7,46 +7,7 @@ ```julia import Pkg -Pkg.add(url="https://github.com/pedromxavier/QUBOLib.jl") +Pkg.add(url="https://github.com/JuliaQUBO/QUBOLib.jl") using QUBOLib ``` - -### Basic Example - -```@example load -using QUBOLib - -# Get code of the first registered collection -coll = first(list_collections()) - -# Get code of the first instance from that collection -inst = first(list_instances(coll)) - -# Eetrieve QUBOTools model -load_instance(coll, inst) -``` - -### Accessing the instance index database - -```@setup sql -using QUBOLib -``` - -```@example sql -using SQLite, DataFrames - -db = QUBOLib.database() - -df = DBInterface.execute( - db, - "SELECT collection, instance FROM instances WHERE size BETWEEN 100 AND 200;" -) |> DataFrame - -models = [ - load_instance(coll, inst) - for (coll, inst) in zip(df[!,:collection], df[!,:instance]) -] - -first(models) -``` diff --git a/docs/src/manual/0-intro.md b/docs/src/manual/0-intro.md new file mode 100644 index 0000000..7ec771c --- /dev/null +++ b/docs/src/manual/0-intro.md @@ -0,0 +1,21 @@ +# Introduction + +## Benchmarking Physics-Inspired Optimization Solvers + +## Mathematical Definitions + +All instances have been recast into the binary, minimization form: + +```math +\begin{array}{rll} + \displaystyle + \min_{\mathbf{x}} & \alpha \left[ \mathbf{x}' \mathbf{Q} \, \mathbf{x} + \mathbf{\ell}' \mathbf{x} + \beta \right] \\ + \textrm{s.t.} & \mathbf{x} \in \mathbb{B}^{n} \\ +\end{array} +``` + +where ``\mathbf{Q} \in \mathbb{R}^{n \times n}`` is an upper triangular matrix, ``\mathbf{\ell} \in \mathbb{R}^{n}`` is a vector, ``\alpha, \beta \in \mathbb{R}`` are scalars, and ``\mathbb{B}^{n}`` is the set of binary vectors of length ``n``. + +## Table of Contents + +1. [Basic Usage](./1-basic.md) diff --git a/docs/src/manual/1-basic.md b/docs/src/manual/1-basic.md new file mode 100644 index 0000000..f448865 --- /dev/null +++ b/docs/src/manual/1-basic.md @@ -0,0 +1,11 @@ +# Basic Usage + +## Getting Started + +```julia +using QUBOLib + +QUBOLib.access() do index + print(index) +end +``` diff --git a/docs/src/manual/2-advanced.md b/docs/src/manual/2-advanced.md new file mode 100644 index 0000000..cbdf32a --- /dev/null +++ b/docs/src/manual/2-advanced.md @@ -0,0 +1,18 @@ +# Advanced Usage + +## Adding a new collection + +## Acessing Internal Data + +One is able to acess the database and archive of a [`QUBOLib.LibraryIndex`](@ref) by recalling the [`QUBOLib.database`](@ref) and [`QUBOLib.archive`](@ref) functions. + + +```julia +using QUBOLib + +QUBOLib.access() do index + db = QUBOLib.database(index) + h5 = QUBOLib.archive(index) +end +``` + diff --git a/scripts/Project.toml b/scripts/Project.toml new file mode 100644 index 0000000..26bdf3a --- /dev/null +++ b/scripts/Project.toml @@ -0,0 +1,7 @@ +[deps] +ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" +DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +QUBOLib = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" +QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" diff --git a/scripts/build/arXiv_1903_10928_3r3x.jl b/scripts/build/arXiv_1903_10928_3r3x.jl new file mode 100644 index 0000000..0a349a0 --- /dev/null +++ b/scripts/build/arXiv_1903_10928_3r3x.jl @@ -0,0 +1,77 @@ +const ARXIV_1903_10928_3R3X_URL = "https://sites.usc.edu/itayhen/files/2019/09/3r3x.zip" + +function load_arXiv_1903_10928_3r3x!(index::QUBOLib.LibraryIndex) + @info "[arXiv_1903_10928_3r3x] Downloading instances" + + _cache_path = mkpath(abspath(QUBOLib.cache_path(index; create = true), "arXiv-1903-10928-3r3x")) + _data_path = mkpath(abspath(_cache_path, "data")) + _zip_path = abspath(_cache_path, "arXiv_1903_10928_3r3x.zip") + + # Download arXiv_1903_10928 3r3x archive + if isfile(_zip_path) + @info "[arXiv_1903_10928_3r3x] Archive already downloaded" + else + @info "[arXiv_1903_10928_3r3x] Downloading archive" + + Downloads.download(ARXIV_1903_10928_5R5X_URL, _zip_path) + end + + # Extract arXiv_1903_10928 3r3x archive + @assert run(`which unzip`, devnull, devnull).exitcode == 0 "'unzip' is required to extract QPLIB archive" + + @info "[arXiv_1903_10928_3r3x] Extracting archive" + + run(``` + unzip -qq -o -j + $_zip_path + 'instance*.txt' + -d $_data_path + ```) + + return nothing +end + +function build_arXiv_1903_10928_3r3x!(index::QUBOLib.LibraryIndex; cache::Bool = true) + if QUBOLib.has_collection(index, "arXiv-1903-10928-3r3x") + @info "[arXiv_1903_10928_3r3x] Collection already exists" + + if cache + return nothing + else + QUBOLib.remove_collection!(index, "arXiv-1903-10928-3r3x") + end + end + + QUBOLib.add_collection!( + index, + "arXiv-1903-10928-3r3x", + Dict{String,Any}( + "name" => "3-Regular 3-XORSAT (arXiv:1903.10928)", + "author" => ["Itay Hen"], + "description" => "3R3X instances for \"Equation Planting: A Tool for Benchmarking Ising Machines\"", + "year" => 2019, + "url" => ARXIV_1903_10928_3R3X_URL, + ), + ) + + load_arXiv_1903_10928_3r3x!(index) + + _data_path = abspath( + QUBOLib.cache_path(index), + "arXiv-1903-10928-3r3x", + "data", + ) + + @info "[arXiv_1903_10928_3r3x] Building index" + + for path in readdir(_data_path; join = true) + model = QUBOTools.read_model(path, QUBOTools.Qubist()) + mod_i = QUBOLib.add_instance!(index, model, "arXiv-1903-10928-3r3x") + + if isnothing(mod_i) + @warn "[arXiv_1903_10928_3r3x] Failed to read instance '$path'" + end + end + + return nothing +end diff --git a/scripts/build/arXiv_1903_10928_5r5x.jl b/scripts/build/arXiv_1903_10928_5r5x.jl new file mode 100644 index 0000000..b1179ec --- /dev/null +++ b/scripts/build/arXiv_1903_10928_5r5x.jl @@ -0,0 +1,77 @@ +const ARXIV_1903_10928_5R5X_URL = "https://sites.usc.edu/itayhen/files/2019/09/5r5x.zip" + +function load_arXiv_1903_10928_5r5x!(index::QUBOLib.LibraryIndex) + @info "[arXiv_1903_10928_5r5x] Downloading instances" + + _cache_path = mkpath(abspath(QUBOLib.cache_path(index; create = true), "arXiv-1903-10928-5r5x")) + _data_path = mkpath(abspath(_cache_path, "data")) + _zip_path = abspath(_cache_path, "arXiv_1903_10928_5r5x.zip") + + # Download arXiv_1903_10928 5r5x archive + if isfile(_zip_path) + @info "[arXiv_1903_10928_5r5x] Archive already downloaded" + else + @info "[arXiv_1903_10928_5r5x] Downloading archive" + + Downloads.download(ARXIV_1903_10928_5R5X_URL, _zip_path) + end + + # Extract arXiv_1903_10928 5r5x archive + @assert run(`which unzip`, devnull, devnull).exitcode == 0 "'unzip' is required to extract QPLIB archive" + + @info "[arXiv_1903_10928_5r5x] Extracting archive" + + run(``` + unzip -qq -o -j + $_zip_path + 'instance*.txt' + -d $_data_path + ```) + + return nothing +end + +function build_arXiv_1903_10928_5r5x!(index::QUBOLib.LibraryIndex; cache::Bool = true) + if QUBOLib.has_collection(index, "arXiv-1903-10928-5r5x") + @info "[arXiv_1903_10928_5r5x] Collection already exists" + + if cache + return nothing + else + QUBOLib.remove_collection!(index, "arXiv-1903-10928-5r5x") + end + end + + QUBOLib.add_collection!( + index, + "arXiv-1903-10928-5r5x", + Dict{String,Any}( + "name" => "5-Regular 5-XORSAT (arXiv:1903.10928)", + "author" => ["Itay Hen"], + "description" => "5R5X instances for \"Equation Planting: A Tool for Benchmarking Ising Machines\"", + "year" => 2019, + "url" => ARXIV_1903_10928_5R5X_URL, + ), + ) + + load_arXiv_1903_10928_5r5x!(index) + + _data_path = abspath( + QUBOLib.cache_path(index), + "arXiv-1903-10928-5r5x", + "data", + ) + + @info "[arXiv_1903_10928_5r5x] Building index" + + for path in readdir(_data_path; join = true) + model = QUBOTools.read_model(path, QUBOTools.Qubist()) + mod_i = QUBOLib.add_instance!(index, model, "arXiv-1903-10928-5r5x") + + if isnothing(mod_i) + @warn "[arXiv_1903_10928_5r5x] Failed to read instance '$path'" + end + end + + return nothing +end diff --git a/scripts/build/arXiv_2103_08464_3r3x.jl b/scripts/build/arXiv_2103_08464_3r3x.jl new file mode 100644 index 0000000..6019047 --- /dev/null +++ b/scripts/build/arXiv_2103_08464_3r3x.jl @@ -0,0 +1,5 @@ +function build_arXiv_2103_08464_3r3x!(index::LibraryIndex) + @info "[arXiv_2103_08464_3r3x] Building index" + + return nothing +end diff --git a/scripts/build/build.jl b/scripts/build/build.jl new file mode 100644 index 0000000..3b35ee5 --- /dev/null +++ b/scripts/build/build.jl @@ -0,0 +1,52 @@ +import QUBOLib +import QUBOTools +import Downloads + +include("arXiv_1903_10928_3r3x.jl") +include("arXiv_1903_10928_5r5x.jl") +include("qplib.jl") + +function build_standard_qubolib( + path::AbstractString = root_path(); + clear_build::Bool = false, + clear_cache::Bool = false, +) + @info "Building QUBOLib v$(QUBOLib.__version__())" + + if clear_build + QUBOLib.clear_build(path) + end + + if clear_cache + QUBOLib.clear_cache(path) + end + + close(QUBOLib.access(; path, create = true)) + + QUBOLib.access(; path) do index + build_arXiv_1903_10928_3r3x!(index) + end + + # QUBOLib.access(; path) do index + # build_arXiv_1903_10928_5r5x!(index) + # end + + QUBOLib.access(; path) do index + build_qplib!(index) + end + + return nothing +end + + +function main() + build_standard_qubolib( + QUBOLib.root_path(); + clear_build = ("--clear-build" ∈ ARGS), + clear_cache = ("--clear-cache" ∈ ARGS), + ) + + return nothing +end + +main() # Here we go! diff --git a/scripts/build/qplib.jl b/scripts/build/qplib.jl new file mode 100644 index 0000000..65ba73c --- /dev/null +++ b/scripts/build/qplib.jl @@ -0,0 +1,357 @@ +# Define codec for QPLIB format +# TODO: Make it available @ QUBOTools + +struct Format{F} <: QUBOTools.AbstractFormat + Format(F::Symbol) = new{F}() +end + +function _read_qplib_model(path::AbstractString) + return open(path, "r") do io + _read_qplib_model(io) + end +end + +function _read_qplib_line(io::IO) + line = readline(io) + + # Remove comments and strip line + return strip(only(match(r"([^#]+)", line))) +end + +function _read_qplib_float(io::IO, ∞::AbstractString) + line = _read_qplib_line(io) + + if line == ∞ + return Inf + else + return parse(Float64, line) + end +end + +function QUBOTools.read_model(io::IO, ::Format{:qplib}) + # Read the header + code = _read_qplib_line(io) + + @assert !isnothing(match(r"(QBB)", _read_qplib_line(io))) + + sense = _read_qplib_line(io) + + @assert sense ∈ ("minimize", "maximize") + + # number of variables + nv = let + line = _read_qplib_line(io) + m = match(r"([0-9]+)", line) + + if isnothing(m) + QUBOTools.syntax_error("Invalid number of variables: $(line)") + + return nothing + end + + parse(Int, m[1]) + end + + V = Set{Int}(1:nv) + L = Dict{Int,Float64}() + Q = Dict{Tuple{Int,Int},Float64}() + + # number of quadratic terms in objective + nq = let + line = _read_qplib_line(io) + m = match(r"([0-9]+)", line) + + if isnothing(m) + QUBOTools.syntax_error("Invalid number of quadratic terms: $(line)") + + return nothing + end + + parse(Int, m[1]) + end + + sizehint!(Q, nq) + + for _ = 1:nq + let + line = _read_qplib_line(io) + m = match(r"([0-9]+)\s+([0-9]+)\s+(\S+)", line) + + if isnothing(m) + QUBOTools.syntax_error("Invalid quadratic term: $(line)") + + return nothing + end + + i = parse(Int, m[1]) + j = parse(Int, m[2]) + c = parse(Float64, m[3]) + + Q[(i, j)] = c + end + end + + # default value for linear coefficients in objective + dl = parse(Float64, _read_qplib_line(io)) + + # number of non-default linear coefficients in objective + ln = parse(Int, _read_qplib_line(io)) + + sizehint!(L, ln) + + if !iszero(dl) + for i = 1:nv + L[i] = dl + end + end + + for _ = 1:ln + let line = _read_qplib_line(io) + m = match(r"([0-9]+)\s+(\S+)", line) + + if isnothing(m) + QUBOTools.syntax_error("Invalid linear coefficient: $(line)") + + return nothing + end + + i = parse(Int, m[1]) + c = parse(Float64, m[2]) + + L[i] = c + end + end + + β = parse(Float64, _read_qplib_line(io)) # objective constant + + inf = _read_qplib_line(io) # value for infinity + + @assert _read_qplib_float(io, inf) isa Float64 # default variable primal value in starting point + @assert iszero(parse(Int, _read_qplib_line(io))) # number of non-default variable primal values in starting point + + @assert _read_qplib_float(io, inf) isa Float64 # default variable bound dual value in starting point + @assert iszero(parse(Int, _read_qplib_line(io))) # number of non-default variable bound dual values in starting point + + @assert iszero(parse(Int, _read_qplib_line(io))) # number of non-default variable names + @assert iszero(parse(Int, _read_qplib_line(io))) # number of non-default constraint names + + return QUBOTools.Model{Int,Float64,Int}( + V, L, Q; + offset = β, + domain = :bool, + sense = (sense == "minimize") ? :min : :max, + description = "QPLib instance no. $code", + ) +end + +function _read_qplib_solution( + path::AbstractString, + model::QUBOTools.Model{Int,Float64,Int}, + var_map::Dict{Int,Int}, +) + return open(path, "r") do io + _read_qplib_solution!(io, model, var_map) + end +end + +function _read_qplib_solution!( + io::IO, + model::QUBOTools.Model{Int,Float64,Int}, + var_map::Dict{Int,Int}, +) + # Read the header + λ = let + m = match(r"objvar\s+([\S]+)", readline(io)) + + if isnothing(m) + QUBOTools.syntax_error("Invalid header") + end + + tryparse(Float64, m[1]) + end + + if isnothing(λ) + QUBOTools.syntax_error("Invalid objective value") + end + + n = QUBOTools.dimension(model) + ψ = zeros(Int, n) + + for line in eachline(io) + i, x = let + m = match(r"b([0-9]+)\s+([\S]+)", line) + + if isnothing(m) + QUBOTools.syntax_error("Invalid solution input") + end + + (tryparse(Int, m[1]), tryparse(Float64, m[2])) + end + + if isnothing(i) || isnothing(x) + QUBOTools.syntax_error("Invalid variable assignment") + end + + ψ[var_map[i]] = ifelse(x > 0, 1, 0) + end + + s = QUBOTools.Sample{Float64,Int}[QUBOTools.Sample{Float64,Int}(ψ, λ)] + + return QUBOTools.SampleSet{Float64,Int}( + s; + sense = QUBOTools.sense(model), + domain = :bool, + ) +end + +function _is_qplib_qubo(path::AbstractString) + @assert isfile(path) && endswith(path, ".qplib") + + return open(path, "r") do io + ____ = readline(io) + type = readline(io) + + return (type == "QBB") + end +end + +function _get_qplib_var_map(path::AbstractString, n::Integer = 1) + @assert isfile(path) && endswith(path, ".lp") + + var_set = sizehint!(Set{Int}(), n) + + open(path, "r") do io + for line in eachline(io) + for m in eachmatch(r"b([0-9]+)", line) + if !isnothing(m) + push!(var_set, parse(Int, m[1])) + end + end + end + end + + return Dict{Int,Int}(v => i for (i, v) in enumerate(sort!(collect(var_set)))) +end + +const QPLIB_URL = "http://qplib.zib.de/qplib.zip" + +function build_qplib!(index::QUBOLib.LibraryIndex; cache::Bool = true) + if QUBOLib.has_collection(index, "qplib") + @info "[qplib] Collection already exists" + + if cache + return nothing + else + QUBOLib.remove_collection!(index, "qplib") + end + end + + @info "[qplib] Building QPLIB" + + QUBOLib.add_collection!( + index, + "qplib", + Dict{String,Any}( + "name" => "QPLIB", + "author" => [ + "Fabio Furini", + "Emiliano Traversi", + "Pietro Belotti", + "Antonio Frangioni", + "Ambros Gleixner", + "Nick Gould", + "Leo Liberti", + "Andrea Lodi", + "Ruth Misener", + "Hans Mittelmann", + "Nikolaos Sahinidis", + "Stefan Vigerske", + "Angelika Wiegele" + ], + "description" => "The Quadratic Programming Library", + "year" => 2014, + "url" => "http://qplib.zib.de/", + ), + ) + + code_list = load_qplib!(index) + + @info "[qplib] Building index" + + _data_path = abspath(QUBOLib.cache_path(index; create = true), "qplib", "data") + + for code in code_list + mod_path = joinpath(_data_path, "$(code).qplib") + var_path = joinpath(_data_path, "$(code).lp") + sol_path = joinpath(_data_path, "$(code).sol") + + model = QUBOTools.read_model(mod_path, Format(:qplib)) + mod_i = QUBOLib.add_instance!(index, model, "qplib") + + if isfile(sol_path) + var_map = _get_qplib_var_map(var_path) + + sol = _read_qplib_solution(sol_path, model, var_map) + + if !isnothing(sol) + QUBOLib.add_solution!(index, mod_i, sol) + else + @warn "[qplib] Failed to read solution for instance '$code'" + end + end + end + + @info "[qplib] Done!" + + return nothing +end + +function load_qplib!(index::QUBOLib.LibraryIndex) + @assert Sys.isunix() "Processing QPLIB is only possible on Unix systems" + + _cache_path = mkpath(abspath(QUBOLib.cache_path(index; create = true), "qplib")) + _data_path = mkpath(abspath(_cache_path, "data")) + _zip_path = abspath(_cache_path, "qplib.zip") + + # Download QPLIB archive + if isfile(_zip_path) + @info "[qplib] Archive already downloaded" + else + @info "[qplib] Downloading archive" + + Downloads.download(QPLIB_URL, _zip_path) + end + + # Extract QPLIB archive + @assert run(`which unzip`, devnull, devnull).exitcode == 0 "'unzip' is required to extract QPLIB archive" + + @info "[qplib] Extracting archive" + + run(``` + unzip -qq -o -j + $_zip_path + 'qplib/html/qplib/*' + 'qplib/html/sol/*' + 'qplib/html/lp/*' + -d $_data_path + ```) + + # Remove non-QUBO instances + @info "[qplib] Removing non-QUBO instances" + + code_list = String[] + + for file_path in filter(endswith(".qplib"), readdir(_data_path; join = true)) + code = readline(file_path) + + if !_is_qplib_qubo(file_path) + rm(joinpath(_data_path, "$(code).qplib"); force = true) + rm(joinpath(_data_path, "$(code).lp"); force = true) + rm(joinpath(_data_path, "$(code).sol"); force = true) + else + push!(code_list, code) + end + end + + return code_list +end diff --git a/scripts/main.jl b/scripts/main.jl new file mode 100644 index 0000000..1f22853 --- /dev/null +++ b/scripts/main.jl @@ -0,0 +1,3 @@ +using QUBOLib + +# QUBOLib.main() diff --git a/scripts/run/dwave/dwave.jl b/scripts/run/dwave/dwave.jl new file mode 100644 index 0000000..1f2960b --- /dev/null +++ b/scripts/run/dwave/dwave.jl @@ -0,0 +1,20 @@ +using DWave + +function main() + QUBOLib.access(; path = QUBOLib.root_path(), create = false) do index + df = DBInterface.execute( + QUBOLib.database(index), + "SELECT instance FROM Instances WHERE dimension < 100 AND quadratic_density < 0.5;" + ) |> DataFrame + + codes = collect(Int, df[!, :instance]) + + @info "Running DWave Neal" + QUBOLib.run!(index, DWave.Neal.Optimizer, codes; solver = "dwave-neal") + + @info "Running DWave (Quantum)" + QUBOLib.run!(index, DWave.Optimizer, codes; solver = "dwave") + end + + return nothing +end diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index 0edad3c..561694b 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -1,35 +1,99 @@ module QUBOLib +using ArgParse using LazyArtifacts -using HDF5 -using JSON -using JSONSchema +using Downloads using JuliaFormatter using LaTeXStrings using SQLite using DataFrames -using Tar -using TOML -using Pkg using UUIDs -using QUBOTools +using JuMP +using SparseArrays using ProgressMeter -const __PROJECT__ = abspath(@__DIR__, "..") -const __VERSION__ = VersionNumber(TOML.parsefile(joinpath(__PROJECT__, "Project.toml"))["version"]) +import JSONSchema +import Tar +import TOML +import Pkg +import HDF5 +import JSON +import Random +import QUBOTools +import PseudoBooleanOptimization as PBO -function data_path()::AbstractString - return abspath(artifact"qubolib") +const __PROJECT__ = Ref{Union{String,Nothing}}(nothing) + +function __project__() + if isnothing(__PROJECT__[]) + proj_path = abspath(dirname(@__DIR__)) + + @assert isdir(proj_path) + + __PROJECT__[] = proj_path + end + + return __PROJECT__[]::String +end + +const __VERSION__ = Ref{Union{VersionNumber,Nothing}}(nothing) + +function __version__()::VersionNumber + if isnothing(__VERSION__[]) + proj_file_path = abspath(__project__(), "Project.toml") + + @assert isfile(proj_file_path) + + proj_file_data = TOML.parsefile(proj_file_path) + + __VERSION__[] = VersionNumber(proj_file_data["version"]) + end + + return __VERSION__[]::VersionNumber end -# Data management methods -include("management/index.jl") +const QUBOLIB_SQL_PATH = joinpath(@__DIR__, "assets", "qubolib.sql") +const COLLECTION_SCHEMA_PATH = joinpath(@__DIR__, "assets", "collection.schema.json") +const COLLECTION_SCHEMA = JSONSchema.Schema(JSON.parsefile(COLLECTION_SCHEMA_PATH)) + +const QUBOLIB_LOGO = """ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ ▄██████▄ ██ ██ █████▄ ▄██████▄ ┃ +┃ ██ ██ ██ ██ ██ ██ ██ ██ ┃ +┃ ██ ██ ██ ██ ██████ ██ ██ ┃ +┃ ██ ▀▀▄███ ██ ██ ██ ██ ██ ██ ┃ +┃ ▀██████▀▄▄ ▀██████▀ █████▀ ▀██████▀ ┃ +┃ ┃ +┃ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ █████▄ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ███████ ██ █████▀ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +""" + +function print_logo(io::IO = stdout) + println(io, QUBOLIB_LOGO) + + return nothing +end + +include("interface.jl") + +include("library/index.jl") +include("library/path.jl") +include("library/access.jl") + +include("library/instances.jl") +include("library/collections.jl") +include("library/solvers.jl") +include("library/solutions.jl") + +include("library/synthesis/Synthesis.jl") + +include("actions/clear.jl") +include("actions/deploy.jl") -# Public API -include("public/interface.jl") -include("public/load.jl") -include("public/list.jl") -include("public/archive.jl") -include("public/database.jl") +include("main.jl") end # module QUBOLib diff --git a/src/actions/build.jl b/src/actions/build.jl new file mode 100644 index 0000000..e69de29 diff --git a/src/actions/clear.jl b/src/actions/clear.jl new file mode 100644 index 0000000..1a002c3 --- /dev/null +++ b/src/actions/clear.jl @@ -0,0 +1,11 @@ +function clear_build(path::AbstractString = QUBOLib.root_path()) + rm(build_path(path; ifmissing = identity); force = true, recursive = true) + + return nothing +end + +function clear_cache(path::AbstractString = QUBOLib.root_path()) + rm(cache_path(path; ifmissing = identity); force = true, recursive = true) + + return nothing +end diff --git a/src/actions/deploy.jl b/src/actions/deploy.jl new file mode 100644 index 0000000..ac98cdc --- /dev/null +++ b/src/actions/deploy.jl @@ -0,0 +1,70 @@ +function deploy(path::AbstractString) + # Calculate tree hash + tree_hash = bytes2hex(Pkg.GitTools.tree_hash(build_path(path))) + + # Build tarball + temp_path = abspath(Tar.create(build_path(path))) + + # Compress tarball + run(`gzip -9 $temp_path`) + + # Move tarball + file_path = mkpath(abspath(dist_path(path), "qubolib.tar.gz")) + + rm(file_path; force = true) + + cp("$temp_path.gz", file_path; force = true) + + # Remove temporary files + rm(temp_path; force = true) + rm("$temp_path.gz"; force = true) + + # Write hash to file + write(joinpath(dist_path(path), "tree.hash"), tree_hash) + + return nothing +end + +function next_tag(path::AbstractString) + last_tag = if haskey(ENV, "LAST_QUBOLIB_TAG") + x = tryparse(VersionNumber, ENV["LAST_QUBOLIB_TAG"]) + + if isnothing(x) + @warn("Pushing tag forward") + + v"0.1.0" + else + x + end + else + last_tag_path = abspath(path, "last.tag") + + if isfile(last_tag_path) + text = read(last_tag_path, String) + + m = match(r"tag:\s*v(.*)", text) + + if isnothing(m) + @error("Tag not found in '$last_tag_path'") + + exit(1) + end + + parse(VersionNumber, m[1]) + else + @error("File '$last_tag_path' not found") + + exit(1) + end + end + + next_tag = VersionNumber( + last_tag.major, + last_tag.minor, + last_tag.patch + 1, + last_tag.prerelease, + last_tag.build, + ) + + return "v$next_tag" +end diff --git a/src/management/document.jl b/src/actions/document.jl similarity index 100% rename from src/management/document.jl rename to src/actions/document.jl diff --git a/src/actions/generate.jl b/src/actions/generate.jl new file mode 100644 index 0000000..e69de29 diff --git a/src/actions/run.jl b/src/actions/run.jl new file mode 100644 index 0000000..86e4c51 --- /dev/null +++ b/src/actions/run.jl @@ -0,0 +1,126 @@ +using QUBOLib +using JuMP +using DataFrames +using DBInterface + +# Solvers +# using DWave +# using MQLib +# using PySA +# using InfinityQ +# using AIMOpt + +function warmup!(config!, model::JuMP.Model) + Q = 2 * rand(3, 3) .- 1 + + JuMP.@variable(model, x[1:3], Bin) + JuMP.@objective(model, Min, x' * Q * x) + + config!(model) + + JuMP.optimize!(model) + + empty!(model) + + return nothing +end + +function run!( + config!::Function, + index::LibraryIndex, + optimizer, + codes::AbstractVector{U}; + kws..., +) where {U<:Integer} + model = JuMP.Model(optimizer) + + warmup!(config!, model) + + for code in codes + try + run!(index, model, code; kws...) + catch e + @error "Failed to run instance '$code': $(sprint(showerror, e))" + end + end + + return nothing +end + +function run!(index::LibraryIndex, model::JuMP.Model, code::Integer; solver::Union{Symbol,Nothing} = nothing) + n, L, Q, α, β = QUBOTools.qubo( + QUBOLib.load_instance(index, code), + :sparse; + sense = :min, + ) + + empty!(model) + + x = JuMP.@variable(model, [1:n], Bin) + + JuMP.@objective(model, Min, α * (x' * Q * x + L' * x + β)) + + config!(model) + + JuMP.optimize!(model) + + let m = JuMP.unsafe_backend(model) + if m isa QUBODrivers.AbstractSampler + sol = QUBOTools.solution(m) + + if !isnothing(solver) + let data = QUBOTools.metadata(sol) + data["solver"] = string(solver) + end + end + + QUBOLib.add_solution!(index, code, sol) + end + end + + return model +end + +function run!(index::LibraryIndex, optimizer, codes::AbstractVector{U}; kws...) where {U<:Integer} + run!(identity, index, optimizer, codes; kws...) + + return nothing +end + +function main() + QUBOLib.load_index(QUBOLib.root_path(); create = false) do index + df = DBInterface.execute( + QUBOLib.database(index), + "SELECT instance FROM Instances WHERE dimension < 100 AND quadratic_density < 0.5;" + ) |> DataFrame + + codes = collect(Int, df[!, :instance]) + + @info "Running DWave Neal" + QUBOLib.run!(index, DWave.Neal.Optimizer, codes; solver = Symbol("dwave-neal")) + + @info "Running DWave (Quantum)" + QUBOLib.run!(index, DWave.Optimizer, codes; solver = :dwave) + + @info "Running MQLib" + QUBOLib.run!(index, MQLib.Optimizer, codes; solver = :mqlib) do model + JuMP.set_silent(model) + JuMP.set_attribute(model, "heuristic", "ALKHAMIS1998") + end + + @info "Running PySA" + QUBOLib.run!(index, PySA.Optimizer, codes; solver = :pysa) do model + JuMP.set_silent(model) + end + + @info "Running InfinityQ" + QUBOLib.run!(index, InfinityQ.Optimizer, codes; solver = :infinityq) + + @info "Running AIMOpt" + QUBOLib.run!(index, AIMOpt.Optimizer, codes; solver = :aimopt) + end + + return nothing +end + +# main() # Here we go! diff --git a/src/assets/collection.schema.json b/src/assets/collection.schema.json new file mode 100644 index 0000000..bbf4020 --- /dev/null +++ b/src/assets/collection.schema.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "author": { + "type": "array", + "items": [ + { + "type": "string" + }, + { + "type": "string" + } + ] + }, + "year": { + "type": "integer" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name", + "author" + ] +} \ No newline at end of file diff --git a/src/assets/qubolib.sql b/src/assets/qubolib.sql new file mode 100644 index 0000000..897a03c --- /dev/null +++ b/src/assets/qubolib.sql @@ -0,0 +1,72 @@ +PRAGMA foreign_keys = ON; + +CREATE TABLE Collections +( + collection TEXT PRIMARY KEY, + name TEXT NOT NULL , + author TEXT NULL , + year INTEGER NULL , + description TEXT NULL , + url TEXT NULL +); + +INSERT INTO Collections + (collection, name, author, year, description, url) +VALUES + ( + 'standalone', + 'Standalone', + NULL, + NULL, + 'Standalone instances', + NULL + ); + +INSERT INTO Collections + (collection, name, author, year, description, url) +VALUES + ( + 'qubolib', + 'QUBOLib', + 'Pedro Maciel Xavier and David E. Bernal Neira', + '2024', + 'QUBOLib Synthetic Instances', + 'https://juliaqubo.github.io/QUBOLib.jl' + ); + +CREATE TABLE Instances +( + instance INTEGER PRIMARY KEY, + collection TEXT NOT NULL , + dimension INTEGER NOT NULL , + min REAL NOT NULL , + max REAL NOT NULL , + abs_min REAL NOT NULL , + abs_max REAL NOT NULL , + linear_min REAL NOT NULL , + linear_max REAL NOT NULL , + quadratic_min REAL NOT NULL , + quadratic_max REAL NOT NULL , + density REAL NOT NULL , + linear_density REAL NOT NULL , + quadratic_density REAL NOT NULL , + FOREIGN KEY (collection) REFERENCES Collections (collection) ON DELETE CASCADE +); + +CREATE TABLE Solutions +( + solution INTEGER PRIMARY KEY, + instance INTEGER NOT NULL , + solver TEXT NULL , + value REAL NOT NULL , + optimal BOOLEAN NOT NULL , + FOREIGN KEY (instance) REFERENCES Instances (instance) ON DELETE CASCADE, + FOREIGN KEY (solver) REFERENCES Solvers (solver) +); + +CREATE TABLE Solvers +( + solver TEXT PRIMARY KEY, + version TEXT NULL, + description TEXT NULL +); diff --git a/src/interface.jl b/src/interface.jl new file mode 100644 index 0000000..4c6b232 --- /dev/null +++ b/src/interface.jl @@ -0,0 +1,132 @@ +# Actions + +@doc raw""" + clear!(source::Symbol, cache::Bool = true) +""" +function clear! end + +@doc raw""" + build!(source::Symbol) +""" +function build! end + +@doc raw""" + run!(index::LibraryIndex, instance::Integer, optimizer) + run!(index::LibraryIndex, instances::Vector{U}, optimizer) where {U<:Integer} +""" +function run! end + +# Data Access + +@doc raw""" + access( + callback; + path::Union{AbstractString,Nothing} = nothing, + create::Bool = false + )::LibraryIndex + +Loads the index for an instance library. + +If `path` is not provided, the latest QUBOLib artifact will be used. + +## Example + +```julia +using QUBOLib + +QUBOLib.access() do index + print(index) # Show some information about the index +end +``` +""" +function access end + +@doc raw""" + database(index::LibraryIndex)::SQLite.DB + +Returns a pointer that grants direct access to the SQLite database of the library index. +""" +function database end + +@doc raw""" + archive(index::LibraryIndex)::HDF5.File + +Returns a pointer that grants direct access to the HDF5 archive of the library index. +""" +function archive end + +@doc raw""" + load_collection(index::LibraryIndex, code::Symbol) +""" +function load_collection end + +@doc raw""" + load_instance(index::LibraryIndex, instance::Integer) +""" +function load_instance end + +@doc raw""" + load_solution(index::LibraryIndex, solution::Integer) + load_solution(index::LibraryIndex, instance::Integer, solution::Integer) +""" +function load_solution end + +# Data Management + +@doc raw""" + add_collection!(index::LibraryIndex, code::Symbol, data::Dict{String,Any}) + +Creates a new collection in the library index. +""" +function add_collection! end + +@doc raw""" + remove_collection!(index::LibraryIndex, code::Symbol) + +Removes a collection and its contents from the library index. +""" +function remove_collection! end + +@doc raw""" + add_solver!(index::LibraryIndex, code::Symbol, data::Dict{String,Any}) + +Registers a new solver in the library index. +""" +function add_solver! end + +@doc raw""" + remove_solver!(index::LibraryIndex, code::Symbol) + +Removes a solver from the library index. +""" +function remove_solver! end + +@doc raw""" + add_instance!(index::LibraryIndex, coll::Symbol, model::QUBOTools.Model{Int,Float64,Int}) + +Adds a new instance to the library index. +""" +function add_instance! end + +@doc raw""" + remove_instance!(index::LibraryIndex, coll::Symbol, instance::Integer) + +Removes an instance from the library index. +""" +function remove_instance! end + +@doc raw""" + add_solution!(index::LibraryIndex, instance::Integer, solution::SampleSet{Float64,Int}) + +Registers a new solution for a given instance. + +The `solution` argument is a [`QUBOTools.SampleSet`](@extref), which is a collection of samples and their respective energies. +""" +function add_solution! end + +@doc raw""" + remove_solution!(index::LibraryIndex, instance::Integer, solution::Integer) + +Removes a solution from the library index. +""" +function remove_solution! end diff --git a/src/library/access.jl b/src/library/access.jl new file mode 100644 index 0000000..6eb8322 --- /dev/null +++ b/src/library/access.jl @@ -0,0 +1,90 @@ +function access(; path::AbstractString = library_path(), create::Bool = false) + ifmissing = _ -> error( + """ + There's no valid QUBOLib installation at '$path'. + Try running `QUBOLib.access` with the `create = true` keyword set in order to generate one from scratch. + """, + ) + + db = load_database(database_path(path; create, ifmissing)) + h5 = load_archive(archive_path(path; create, ifmissing)) + + if isnothing(db) || isnothing(h5) + if create + return create_index(path) + else + error("Failed to load index from '$path'") + + return nothing + end + end + + return LibraryIndex(db, h5; path) +end + +function access(callback::Any; path::AbstractString = library_path(), create::Bool = false) + index = access(; path, create) + + @assert isopen(index) + + try + return callback(index) + finally + close(index) + end +end + +function create_index(path::AbstractString) + db = create_database(database_path(path; create = true)) + h5 = create_archive(archive_path(path; create = true)) + + return LibraryIndex(db, h5; path) +end + +function load_database(path::AbstractString)::Union{SQLite.DB,Nothing} + if !isfile(path) + return nothing + else + return SQLite.DB(path) + end +end + +function create_database(path::AbstractString) + rm(path; force = true) # Remove file if it exists + + db = SQLite.DB(path) + + open(QUBOLIB_SQL_PATH) do file + for stmt in eachsplit(read(file, String), ';') + stmt = strip(stmt) + + if !isempty(stmt) + DBInterface.execute(db, stmt) + end + end + end + + return db +end + +function load_archive( + path::AbstractString; + mode::AbstractString = "cw", +)::Union{HDF5.File,Nothing} + if !isfile(path) + return nothing + else + return HDF5.h5open(path, mode) + end +end + +function create_archive(path::AbstractString) + rm(path; force = true) # remove file if it exists + + h5 = HDF5.h5open(path, "w") + + HDF5.create_group(h5, "instances") + HDF5.create_group(h5, "solutions") + + return h5 +end diff --git a/src/library/collections.jl b/src/library/collections.jl new file mode 100644 index 0000000..e15458e --- /dev/null +++ b/src/library/collections.jl @@ -0,0 +1,87 @@ +function has_collection(index::LibraryIndex, collection::AbstractString) + @assert isopen(index) + + db = QUBOLib.database(index) + df = DBInterface.execute( + db, + "SELECT COUNT(*) FROM collections WHERE collection = ?", + (String(collection),) + ) |> DataFrame + + return only(df[!, 1]) > 0 +end + +function add_collection!( + index::LibraryIndex, + collection::AbstractString, + data::Dict{String,Any} +) + @assert isopen(index) + + if has_collection(index, collection) + error("Collection '$collection' already exists") + end + + let report = JSONSchema.validate(data, COLLECTION_SCHEMA) + if !isnothing(report) + error("Invalid collection data:\n$report") + end + end + + db = QUBOLib.database(index) + + DBInterface.execute( + db, + """ + INSERT INTO collections + (collection, name, author, year, description, url) + VALUES + (?, ?, ?, ?, ?, ?) + """, + ( + String(collection), + get(data, "name", String(collection)), + haskey(data, "author") ? join(data["author"], " and ") : missing, + get(data, "year", missing), + get(data, "description", missing), + get(data, "url", missing), + ) + ) + + return nothing +end + +function remove_collection!(index::LibraryIndex, code::Symbol) + @assert isopen(index) + + if !has_collection(index, code) + error("Collection '$code' does not exist") + else + DBInterface.execute( + index.db, + "DELETE FROM collections WHERE code = ?", + (string(code),) + ) + + @info "Collection '$code' removed from index" + end + + return nothing +end + +# Queries + +function collection_size(index::LibraryIndex, collection::AbstractString) + @assert isopen(index) + @assert has_collection(index, collection) + + db = database(index) + + df = DBInterface.execute( + db, + "SELECT COUNT(*) FROM instances WHERE collection = ?;", + [String(collection)] + ) |> DataFrame + + return only(df[!, begin])::Integer +end diff --git a/src/library/index.jl b/src/library/index.jl new file mode 100644 index 0000000..931bdf2 --- /dev/null +++ b/src/library/index.jl @@ -0,0 +1,51 @@ +@doc raw""" + LibraryIndex + +The QUBOLib index is composed of two pieces: a SQLite database and an HDF5 archive. +""" +struct LibraryIndex + db::SQLite.DB + h5::HDF5.File + + path::String + + function LibraryIndex(db::SQLite.DB, h5::HDF5.File; path::AbstractString = library_path()) + return new(db, h5, path) + end +end + +function Base.isopen(index::LibraryIndex) + return isopen(index.db) && isopen(index.h5) +end + +function Base.close(index::LibraryIndex) + if isopen(index.db) + close(index.db) + end + + if isopen(index.h5) + close(index.h5) + end + + return nothing +end + +function database(index::LibraryIndex)::SQLite.DB + @assert isopen(index) + + return index.db +end + +function archive(index::LibraryIndex)::HDF5.File + @assert isopen(index) + + return index.h5 +end + +function Base.show(io::IO, index::LibraryIndex) + if isopen(index) + return println(io, "QUBOLib ■ Library Index") + else + return println(io, "QUBOLib ■ Library Index (closed)") + end +end diff --git a/src/library/instances.jl b/src/library/instances.jl new file mode 100644 index 0000000..640e374 --- /dev/null +++ b/src/library/instances.jl @@ -0,0 +1,94 @@ +function add_instance!( + index::LibraryIndex, + model::QUBOTools.Model{Int,Float64,Int}, + collection::AbstractString = "standalone", +)::Integer + @assert isopen(index) + + db = QUBOLib.database(index) + h5 = QUBOLib.archive(index) + + # Retrieve coefficients + L = map(last, QUBOTools.linear_terms(model)) + Q = map(last, QUBOTools.quadratic_terms(model)) + + query = DBInterface.execute( + db, + """ + INSERT INTO Instances ( + collection , + dimension , + min , + max , + abs_min , + abs_max , + linear_min , + linear_max , + quadratic_min , + quadratic_max , + density , + linear_density , + quadratic_density + ) + VALUES ( + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ?, + ? + ); + """, + ( + String(collection), + QUBOTools.dimension(model), + min(minimum(L), minimum(Q)), + max(maximum(L), maximum(Q)), + min(minimum(abs, L), minimum(abs, Q)), + max(maximum(abs, L), maximum(abs, Q)), + minimum(L), + maximum(L), + minimum(Q), + maximum(Q), + QUBOTools.density(model), + QUBOTools.linear_density(model), + QUBOTools.quadratic_density(model), + ), + ) + + i = DBInterface.lastrowid(query)::Integer + + group = HDF5.create_group(h5["instances"], string(i)) + + QUBOTools.write_model(group, model, QUBOTools.QUBin()) + + return i +end + +function remove_instance!(index::LibraryIndex, i::Integer) + @assert isopen(index) + + db = QUBOLib.database(index) + h5 = QUBOLib.archive(index) + + DBInterface.execute(db, "DELETE FROM Instances WHERE instance = ?;", (i,)) + + HDF5.delete_object(h5["instances"], string(i)) + + return nothing +end + +function load_instance(index::LibraryIndex, i::Integer) + @assert isopen(index) + + h5 = QUBOLib.archive(index) + + return QUBOTools.read_model(h5["instances"][string(i)], QUBOTools.QUBin()) +end diff --git a/src/library/path.jl b/src/library/path.jl new file mode 100644 index 0000000..2d96630 --- /dev/null +++ b/src/library/path.jl @@ -0,0 +1,121 @@ +raw""" + _get_path(path::AbstractString; create::Bool = false) +""" +function _get_path( + path::AbstractString; + create::Bool = false, + ifmissing::Any = path -> error("Path '$path' does not exist"), +)::AbstractString + if ispath(path) + return abspath(path) + elseif create + return abspath(mkdir(path)) + else + return ifmissing(path) + end +end + +@doc raw""" + library_path()::AbstractString + +Returns the absolute path to the QUBOLib artifact. +""" +function library_path()::AbstractString + return root_path() # switch to artifact as soon as it is released: + # return abspath(artifact"qubolib") +end + +@doc raw""" + database_path(path::AbstractString=library_path())::AbstractString + +Returns the absolute path to the database file, given a reference `path`. +""" +function database_path( + path::AbstractString = library_path(); + create::Bool = false, + ifmissing::Any = path -> error("There's no database file at '$path'"), +)::AbstractString + return abspath(build_path(path; create, ifmissing), "index.db") +end + +database_path(index::LibraryIndex; kws...) = database_path(index.path; kws...) + +@doc raw""" + archive_path(path::AbstractString=library_path())::AbstractString + +Returns the absolute path to the archive file, given a reference `path`. +""" +function archive_path( + path::AbstractString = library_path(); + create::Bool = false, + ifmissing::Any = path -> error("There's no archive file at '$path'"), +)::AbstractString + return abspath(build_path(path; create, ifmissing), "archive.h5") +end + +archive_path(index::LibraryIndex; kws...) = archive_path(index.path; kws...) + +# Functions below will be more often used when building the library, +# therefore they will point to the the project's root path by default. + +@doc raw""" + root_path()::AbstractString + +Returns the absolute path to the project's root folder. + +!!! info + The [`dist_path`](@ref), [`build_path`](@ref), and [`cache_path`](@ref) functions are + more often used when building the library, therefore they will point to the the project's + root path by default, by referencing this function. +""" +function root_path()::AbstractString + return __project__() +end + +@doc raw""" + dist_path(path::AbstractString=root_path())::AbstractString + +Returns the absolute path to the distribution folder, given a reference `path`. +The path is created if it does not exist. +""" +function dist_path( + path::AbstractString = root_path(); + create::Bool = false, + ifmissing::Any = path -> error("No distribution path at '$path'"), +)::AbstractString + return _get_path(abspath(path, "dist"); create, ifmissing) +end + +dist_path(index::LibraryIndex; kws...) = dist_path(index.path; kws...) + +@doc raw""" + build_path(path::AbstractString=root_path())::AbstractString + +Returns the absolute path to the build folder, given a reference `path`. +The path is created if it does not exist. +""" +function build_path( + path::AbstractString = root_path(); + create::Bool = false, + ifmissing::Any = path -> error("No build path at '$path'"), +)::AbstractString + return _get_path(abspath(dist_path(path; create, ifmissing), "build"); create, ifmissing) +end + +build_path(index::LibraryIndex; kws...) = build_path(index.path; kws...) + +@doc raw""" + cache_path(path::AbstractString=root_path())::AbstractString + +Returns the absolute path to the cache folder, given a reference `path`. +The path is created if it does not exist. +""" +function cache_path( + path::AbstractString = root_path(); + create::Bool = false, + ifmissing::Any = path -> error("No cache path at '$path'"), +)::AbstractString + return _get_path(abspath(dist_path(path; create, ifmissing), "cache"); create, ifmissing) +end + +cache_path(index::LibraryIndex; kws...) = cache_path(index.path; kws...) diff --git a/src/library/solutions.jl b/src/library/solutions.jl new file mode 100644 index 0000000..759177b --- /dev/null +++ b/src/library/solutions.jl @@ -0,0 +1,50 @@ +function _write_solution( + fp::P, + sol::QUBOTools.AbstractSolution, + fmt::QUBOTools.QUBin, +) where {P<:Union{HDF5.File,HDF5.Group}} + HDF5.create_group(fp, "solution") + + QUBOTools._write_solution_data(fp, sol, fmt) + + fp["solution"]["sense"] = String(QUBOTools.sense(sol)) + fp["solution"]["domain"] = String(QUBOTools.domain(sol)) + + QUBOTools._write_solution_metadata(fp, sol, fmt) + + return nothing +end + +function add_solution!(index::LibraryIndex, instance::Integer, sol::QUBOTools.SampleSet{Float64,Int})::Integer + @assert isopen(index) + @assert !isempty(sol) + + data = QUBOTools.metadata(sol) + + solver = get(data, "solver", nothing) + value = QUBOTools.value(sol, 1) + + optimal = get(data, "status", nothing) == "optimal" + + db = QUBOLib.database(index) + h5 = QUBOLib.archive(index) + + query = DBInterface.execute( + db, + """ + INSERT INTO Solutions + (instance, solver, value, optimal) + VALUES + (?, ?, ?, ?) + """, + (instance, solver, value, optimal) + ) + + i = DBInterface.lastrowid(query)::Integer + + group = HDF5.create_group(h5["solutions"], string(i)) + + _write_solution(group, sol, QUBOTools.QUBin()) + + return i +end diff --git a/src/library/solvers.jl b/src/library/solvers.jl new file mode 100644 index 0000000..3b60686 --- /dev/null +++ b/src/library/solvers.jl @@ -0,0 +1,48 @@ +function has_solver(index::LibraryIndex, solver::String) + @assert isopen(index) + + df = DBInterface.query( + index.db, + "SELECT COUNT(*) FROM solvers WHERE solver = ?", + (solver,), + ) |> DataFrame + + return (only(df[!, 1]) > 0) +end + +function add_solver!(index::LibraryIndex, solver::AbstractString, data::Dict{String,Any}) + @assert isopen(index) + + db = QUBOLib.database(index) + + DBInterface.execute( + db, + """ + INSERT INTO solvers + (solver, version, description) + VALUES + (?, ?, ?); + """, + ( + String(solver), + get(data, "version", missing), + get(data, "description", missing), + ), + ) + + return nothing +end + +function get_solvers(index::LibraryIndex) + @assert isopen(index) + + return DBInterface.query(index.db, "SELECT * FROM solvers") |> DataFrame +end + +function list_solvers(index::LibraryIndex) + @assert isopen(index) + + df = DBInterface.query(index.db, "SELECT solver FROM solvers") |> DataFrame + + return collect(df[!, 1]) +end diff --git a/src/library/synthesis/Synthesis.jl b/src/library/synthesis/Synthesis.jl new file mode 100644 index 0000000..7626a9a --- /dev/null +++ b/src/library/synthesis/Synthesis.jl @@ -0,0 +1,15 @@ +module Synthesis + +import Random +import QUBOTools +import ..QUBOLib +import PseudoBooleanOptimization as PBO + +include("interface.jl") +include("abstract.jl") +include("nae3sat.jl") +include("sherrington_kirkpatrick.jl") +include("wishart.jl") +include("xorsat.jl") + +end # module Synthesis diff --git a/src/library/synthesis/abstract.jl b/src/library/synthesis/abstract.jl new file mode 100644 index 0000000..65f0581 --- /dev/null +++ b/src/library/synthesis/abstract.jl @@ -0,0 +1,3 @@ +function generate(problem::AbstractProblem) + return generate(Random.GLOBAL_RNG, problem) +end diff --git a/src/library/synthesis/interface.jl b/src/library/synthesis/interface.jl new file mode 100644 index 0000000..005720e --- /dev/null +++ b/src/library/synthesis/interface.jl @@ -0,0 +1,12 @@ +@doc raw""" + AbstractProblem{T} +""" +abstract type AbstractProblem{T} end + +@doc raw""" + generate(problem::AbstractProblem{T}) where {T} + generate(rng, problem::AbstractProblem{T}) where {T} + +Generates a QUBO problem and returns it as a [`QUBOTools.Model`](@extref). +""" +function generate end diff --git a/src/library/synthesis/nae3sat.jl b/src/library/synthesis/nae3sat.jl new file mode 100644 index 0000000..c69b070 --- /dev/null +++ b/src/library/synthesis/nae3sat.jl @@ -0,0 +1,75 @@ +@doc raw""" + NAE3SAT{T}(m::Integer, n::Integer) + +Not-all-equal 3-SAT on ``m`` clauses and ``n`` variables. +""" +struct NAE3SAT{T} <: AbstractProblem{T} + m::Int + n::Int + ratio::Float64 + + function NAE3SAT{T}(m::Integer, n::Integer) where {T} + @assert(n >= 3, "number of variables must be at least 3") + + return new{T}(m, n, m / n) + end +end + +@doc raw""" + NAE3SAT{T}(n::Integer, ratio::Real = 2.11) + +Not-all-equal 3-SAT on ``n`` variables with number of clauses defined +by the *clause-to-variable* ratio. +""" +function NAE3SAT{T}(n::Integer, ratio::Real = 2.11) where {T} + @assert(ratio > 0, "ratio must be positive") + + return NAE3SAT{T}(trunc(Int, n * ratio), n, ratio) +end + +function generate(rng, problem::NAE3SAT{T}) where {T} + m = problem.m # number of clauses + n = problem.n # number of variables + + # Ising Interactions + h = Dict{Int,T}() + J = Dict{Tuple{Int,Int},T}() + + C = BitSet(1:n) + + c = Vector{Int}(undef, 3) + s = Vector{Int}(undef, 3) + + for _ = 1:problem.m + union!(C, 1:problem.n) + + for j = 1:3 + c[j] = pop!(C, rand(rng, C)) + end + + s .= rand(rng, (↑,↓), 3) + + for i = 1:3, j = (i+1):3 + x = (c[i], c[j]) + + J[x] = get(J, x, zero(T)) + s[i] * s[j] + end + end + + return QUBOTools.Model{Int,T,Int}( + h, + J, + domain = :spin, + metadata = Dict{String,Any}( + "origin" => "QUBOLib.jl", + "synthesis" => Dict{String,Any}( + "problem" => "Not-all-equal 3-SAT", + "parameters" => Dict{String,Any}( + "m" => problem.m, + "n" => problem.n, + "ratio" => problem.ratio, + ), + ), + ) + ) +end diff --git a/src/library/synthesis/sherrington_kirkpatrick.jl b/src/library/synthesis/sherrington_kirkpatrick.jl new file mode 100644 index 0000000..13abe6d --- /dev/null +++ b/src/library/synthesis/sherrington_kirkpatrick.jl @@ -0,0 +1,64 @@ +@doc raw""" + SherringtonKirkpatrick{T}(n::Integer, μ::T, σ::T) + +Generates a Sherrington-Kirkpatrick model in ``n`` variables. +Coefficients are normally distributed with mean ``\mu`` and variance ``\sigma``. +""" +struct SherringtonKirkpatrick{T} <: AbstractProblem{T} + n::Int + μ::T + σ::T + + function SherringtonKirkpatrick{T}(n::Integer, μ::T = zero(T), σ::T = one(T)) where {T} + return new{T}(n, μ, σ) + end +end + +function SherringtonKirkpatrick(n::Integer, μ::Float64 = 0.0, σ::Float64 = 1.0) + return SherringtonKirkpatrick{Float64}(n, μ, σ) +end + +const SK{T} = SherringtonKirkpatrick{T} + +function generate(rng, problem::SherringtonKirkpatrick{T}) where {T} + f, x = PBO.sherrington_kirkpatrick( + rng, + PBO.PBF{Int,T}, + problem.n; + μ = problem.μ, + σ = problem.σ, + ) + + model = QUBOTools.Model{Int,Float64,Int}( + f; + metadata = Dict{String,Any}( + "origin" => "QUBOLib.jl", + "synthesis" => Dict{String,Any}( + "problem" => "Sherrington-Kirkpatrick", + "parameters" => Dict{String,Any}( + "n" => problem.n, + "mu" => problem.μ, + "sigma" => problem.σ, + ), + ), + ) + ) + + if !isnothing(x) + sol = QUBOTools.SampleSet{T,Int}( + model, x; + metadata = Dict{String,Any}( + "origin" => "planted", + "status" => "optimal", + "time" => Dict{String,Any}( + "total" => NaN, + "effective" => NaN, + ), + ) + ) + + QUBOTools.attach!(model, sol) + end + + return model +end diff --git a/src/library/synthesis/wishart.jl b/src/library/synthesis/wishart.jl new file mode 100644 index 0000000..e6b1eff --- /dev/null +++ b/src/library/synthesis/wishart.jl @@ -0,0 +1,76 @@ +@doc raw""" + Wishart{T}(n::Integer, m::Integer) + +Represents the Wishart model on ``n`` variables whose ``\mathbf{W}`` matrix has +``m`` columns. + +When `true`, the `discretize` keyword limits the entries of the ``\mathbf{R}`` +matrix to ``\pm 1``. +The `precision`, on the other hand, is the amount of digits to round each entry +``R_{i,j}`` after sampling from a normal distribution ``\mathcal{N}(0, 1)``. +""" +struct Wishart{T} <: AbstractProblem{T} + n::Int + m::Int + + discretize::Bool + precision::Int + + function Wishart{T}( + n::Integer, + m::Integer; + discretize::Bool = false, + precision::Integer = 0, + ) where {T} + @assert precision >= 0 + + return new{T}(n, m, discretize, precision) + end +end + +function Wishart(n::Integer, m::Integer; discretize::Bool = false, precision::Integer = 0) + return Wishart{Float64}(n, m; discretize, precision) +end + +function generate(rng, problem::Wishart{T}) where {T} + f, x = PBO.wishart( + rng, + PBO.PBF{Int,T}, + problem.n, + problem.m; + discretize_bonds = problem.discretize, + precision = problem.precision, + ) + + model = QUBOTools.Model{Int,T,Int}( + f; + metadata = Dict{String,Any}( + "origin" => "QUBOLib.jl", + "synthesis" => Dict{String,Any}( # TODO: Add this to the Schema + "problem" => "Wishart", + "parameters" => Dict{String,Any}( + "n" => problem.n, + "m" => problem.m, + ) + ), + ), + ) + + if !isnothing(x) + sol = QUBOTools.SampleSet{T,Int}( + model, x; + metadata = Dict{String,Any}( + "origin" => "planted", + "status" => "optimal", + "time" => Dict{String,Any}( + "total" => NaN, + "effective" => NaN, + ), + ) + ) + + QUBOTools.attach!(model, sol) + end + + return model +end diff --git a/src/library/synthesis/xorsat.jl b/src/library/synthesis/xorsat.jl new file mode 100644 index 0000000..69facde --- /dev/null +++ b/src/library/synthesis/xorsat.jl @@ -0,0 +1,18 @@ +@doc raw""" + XORSAT{T}(n::Integer, r::Integer = 3, k::Integer = 3) + +``r``-regular ``k``-XORSAT on ``n`` variables. +""" +struct XORSAT{T} <: AbstractProblem{T} + n::Int + r::Int + k::Int + + function XORSAT{T}(n::Integer, r::Integer = 3, k::Integer = 3) where {T} + return new{T}(n, r, k) + end +end + +function generate(rng, problem::XORSAT{T}) where {T} + +end diff --git a/src/main.jl b/src/main.jl new file mode 100644 index 0000000..5801bdd --- /dev/null +++ b/src/main.jl @@ -0,0 +1,53 @@ +""" +""" +function main() + settings = ArgParseSettings() + + @add_arg_table! settings begin + "build" + action = :command + help = "an option with an argument" + "clear" + action = :command + help = "Clears current QUBOLib" + "deploy" + action = :command + help = "Deploys current state to target" + "generate" + action = :command + help = "Generates instances for a given problem" + "run" + action = :command + help = "another option" + end + + @add_arg_table! settings["build"] begin + "--source" + default = nothing + help = "Selects data sources to build" + end + + @add_arg_table! settings["deploy"] begin + "--target" + default = nothing + help = "Defines deployment target" + end + + QUBOLib.print_logo() + + args = parse_args(settings; as_symbols = true) + + let cmd = args[:_COMMAND_] + if cmd === :clear + QUBOLib.clear(cmd_args) + elseif cmd === :build + QUBOLib.build(cmd_args) + elseif cmd === :run + QUBOLib.run(cmd_args) + elseif cmd ===:deploy + QUBOLib.deploy(cmd_args) + end + end + + return nothing +end diff --git a/src/management/index.jl b/src/management/index.jl deleted file mode 100644 index 5f37bb0..0000000 --- a/src/management/index.jl +++ /dev/null @@ -1,494 +0,0 @@ -function create_database(path::AbstractString) - rm(path; force=true) # remove file if it exists - - db = SQLite.DB(path) - - DBInterface.execute(db, "PRAGMA foreign_keys = ON;") - - DBInterface.execute(db, "DROP TABLE IF EXISTS problems;") - - DBInterface.execute( - db, - """ - CREATE TABLE problems ( - problem TEXT PRIMARY KEY, -- Problem identifier - name TEXT NOT NULL -- Problem name - ); - """ - ) - - DBInterface.execute( - db, - """ - INSERT INTO problems (problem, name) - VALUES - ('3R3X', '3-Regular 3-XORSAT'), - ('5R5X', '5-Regular 5-XORSAT'), - ('QUBO', 'Quadratic Unconstrained Binary Optimization'); - """ - ) - - DBInterface.execute(db, "DROP TABLE IF EXISTS collections;") - - DBInterface.execute( - db, - """ - CREATE TABLE collections ( - collection TEXT PRIMARY KEY, -- Collection identifier - problem TEXT NOT NULL, -- Problem type - size INTEGER NOT NULL, -- Number of instances - FOREIGN KEY (problem) REFERENCES problems (problem) - ); - """ - ) - - DBInterface.execute(db, "DROP TABLE IF EXISTS instances;") - - DBInterface.execute( - db, - """ - CREATE TABLE instances ( - instance TEXT PRIMARY KEY, -- Instance identifier - dimension INTEGER NOT NULL, -- Number of variables - collection TEXT NOT NULL, -- Collection identifier - min REAL, -- Minimum value - max REAL, -- Maximum value - linear_min REAL, -- Minimum linear coefficient - linear_max REAL, -- Maximum linear coefficient - quadratic_min REAL, -- Minimum quadratic coefficient - quadratic_max REAL, -- Maximum quadratic coefficient - density REAL, -- Coefficient density - linear_density REAL, -- Linear coefficient density - quadratic_density REAL, -- Quadratic coefficient density - FOREIGN KEY (collection) REFERENCES collections (collection) - ); - """ - ) - - return db -end - -function create_archive(path::AbstractString) - rm(path; force=true) # remove file if it exists - - fp = HDF5.h5open(path, "w") - - HDF5.create_group(fp, "collections") - - return fp -end - -struct InstanceIndex - db::SQLite.DB - fp::HDF5.File - root_path::String - dist_path::String - list_path::String - tree_hash::Ref{String} - next_tag::Ref{String} -end - -function create_index( - root_path::AbstractString, - dist_path::AbstractString=abspath(root_path, "dist") -) - mkpath(dist_path) # create dist directory if it doesn't exist - - db = create_database(joinpath(dist_path, "index.sqlite")) - fp = create_archive(joinpath(dist_path, "archive.h5")) - - list_path = abspath(root_path, "collections") - - @assert isdir(list_path) "'$list_path' is not a directory" - - return InstanceIndex(db, fp, abspath(root_path), abspath(dist_path), list_path, Ref{String}(), Ref{String}()) -end - -function _list_collections(path::AbstractString) - return basename.(filter(isdir, readdir(path; join=true))) -end - -function _list_collections(index::InstanceIndex) - return _list_collections(index.list_path) -end - -function _list_instances(path::AbstractString, collection::AbstractString) - data_path = joinpath(path, collection, "data") - - @assert isdir(data_path) "'$data_path' is not a directory" - - return readdir(data_path; join=false) -end - -function _list_instances(index::InstanceIndex, collection::AbstractString) - return _list_instances(index.list_path, collection) -end - -const _METADATA_SCHEMA = JSONSchema.Schema(JSON.parsefile(joinpath(@__DIR__, "metadata.schema.json"))) - -function _get_metadata(path::AbstractString, collection::AbstractString; validate::Bool=true) - metapath = joinpath(path, collection, "metadata.json") - metadata = JSON.parsefile(metapath) - - if validate - report = JSONSchema.validate(_METADATA_SCHEMA, metadata) - - if !isnothing(report) - error( - """ - Invalid collection metadata for $(collection): - $(report) - """ - ) - end - end - - return metadata -end - -function _get_metadata(index::InstanceIndex, collection::AbstractString; validate::Bool=true) - return _get_metadata(index.list_path, collection; validate=validate) -end - -function _get_instance_model(path::AbstractString, collection::AbstractString, instance::AbstractString; on_read_error::Function=msg -> @warn(msg)) - model_path = abspath(path, collection, "data", instance) - - return try - QUBOTools.read_model(model_path) - catch - on_read_error("Failed to read model at '$model_path'") - - nothing - end -end - -function _get_instance_model(index::InstanceIndex, collection::AbstractString, instance::AbstractString; on_read_error::Function=msg -> @warn(msg)) - return _get_instance_model(index.list_path, collection, instance; on_read_error) -end - -function hash!(index::InstanceIndex) - index.tree_hash[] = bytes2hex(Pkg.GitTools.tree_hash(index.dist_path)) - - return nothing -end - -function deploy!(index::InstanceIndex) - hash!(index) - - # Build tarball - temp_path = abspath(Tar.create(index.dist_path)) - - # Compress tarball - run(`gzip -9 $temp_path`) - - # Move tarball - file_path = mkpath(abspath(index.dist_path, "qubolib.tar.gz")) - - rm(file_path; force = true) - - cp("$temp_path.gz", file_path; force = true) - - # Remove temporary files - rm(temp_path; force = true) - rm("$temp_path.gz"; force = true) - - return nothing -end - -function tag(path::AbstractString) - last_tag = if haskey(ENV, "LAST_QUBOLIB_TAG") - x = tryparse(VersionNumber, ENV["LAST_QUBOLIB_TAG"]) - - if isnothing(x) - @warn("Pushing tag forward") - - v"0.1.0" - else - x - end - else - last_tag_path = abspath(path, "last.tag") - - if isfile(last_tag_path) - text = read(last_tag_path, String) - - m = match(r"tag:\s*v(.*)", text) - - if isnothing(m) - @error("Tag not found in '$last_tag_path'") - - exit(1) - end - - parse(VersionNumber, m[1]) - else - @error("File '$last_tag_path' not found") - - exit(1) - end - end - - next_tag = VersionNumber( - last_tag.major, - last_tag.minor, - last_tag.patch + 1, - last_tag.prerelease, - last_tag.build, - ) - - return "v$next_tag" -end - -function tag!(index::InstanceIndex) - index.next_tag[] = tag(index.root_path) - - return nothing -end - -if !isdefined(LaTeXStrings, :latexescape) - function latexescape(s::AbstractString) - return replace( - s, - raw"\\" => raw"\textbackslash{}", - raw"&" => raw"\&", - raw"%" => raw"\%", - raw"$" => raw"\$", - raw"#" => raw"\#", - raw"_" => raw"\_", - raw"{" => raw"\{", - raw"}" => raw"\}", - raw"~" => raw"\textasciitilde{}", - raw"^" => raw"\^{}", - raw"<" => raw"\textless{}", - raw">" => raw"\textgreater{}", - ) - end -end - -if !isdefined(LaTeXStrings, :bibtexescape) - function bibtexescape(s::AbstractString) - return replace(s, - raw"\\" => raw"\textbackslash{}", - raw"&" => raw"\&", - raw"%" => raw"\%", - raw"$" => raw"\$", - raw"#" => raw"\#", - raw"_" => raw"\_", - raw"~" => raw"\textasciitilde{}", - raw"^" => raw"\^{}", - raw"<" => raw"\textless{}", - raw">" => raw"\textgreater{}", - ) - end -end - -function _bibtex_entry(data::Dict{String,Any}; indent=2) - # Replace list with author names by them joined together - data["author"] = join(pop!(data, "author", []), " and ") - - # The document type / media type defaults to @misc - doctype = pop!(data, "type", "misc") - - # Citekey: use '?' as placeholder if none is given - citekey = pop!(data, "citekey", "?") - - # Get the size of longest key to align them - keysize = maximum(length.(keys(data))) - - entries = join( - [ - (" "^indent) * "$(rpad(k, keysize)) = {$(bibtexescape(string(v)))}" - for (k, v) in data - ], - "\n", - ) - - return """ - @$doctype{$citekey, - $entries - } - """ -end - -function _problem_name(problem::AbstractString) - return _problem_name(data_path(), problem) -end - -function _problem_name(path::AbstractString, collection::AbstractString) - db = database(path::AbstractString) - - @assert isopen(db) - - df = DBInterface.execute( - db, - "SELECT problems.name - FROM problems - INNER JOIN collections ON problems.problem=collections.problem - WHERE collections.collection = ?", - [collection] - ) |> DataFrame - - close(db) - - @assert !isopen(db) - - return only(df[!, :name]) -end - -function _collection_size(collection::AbstractString) - return _collection_size(data_path(), collection::AbstractString) -end - -function _collection_size(path::AbstractString, collection::AbstractString) - db = database(path) - - @assert isopen(db) - - df = DBInterface.execute( - db, - "SELECT COUNT(*) FROM instances WHERE collection = ?;", - [collection] - ) |> DataFrame - - close(db) - - @assert !isopen(db) - - return only(df[!, begin]) -end - -function _collection_size_range(collection::AbstractString) - return _collection_size_range(data_path(), collection::AbstractString) -end - -function _collection_size_range(path::AbstractString, collection::AbstractString) - db = database(path) - - @assert isopen(db) - - df = DBInterface.execute( - db, - "SELECT MIN(size), MAX(size) FROM instances WHERE collection = ?;", - [collection] - ) |> DataFrame - - close(db) - - @assert !isopen(db) - - return (only(df[!, 1]), only(df[!, 2])) -end - -function curate(root_path::AbstractString, dist_path::AbstractString=abspath(root_path, "dist"); on_read_error::Function=msg -> @warn(msg)) - index = create_index(root_path, dist_path) - - curate!(index; on_read_error) - - return index -end - -function curate!(index::InstanceIndex; on_read_error::Function=msg -> @warn(msg)) - @assert isopen(index.db) - @assert isopen(index.fp) - - # curate collections - for collection in _list_collections(index) - # extract collection metadata - coll_metadata = _get_metadata(index, collection) - - problem = get(coll_metadata, "problem", "QUBO") - - DBInterface.execute( - index.db, - """ - INSERT INTO collections (collection, problem, size) - VALUES - (?, ?, 0); - """, - [collection, problem] - ) - - # add collection to HDF5 file - HDF5.create_group(index.fp["collections"], collection) - - @showprogress dt = 1.0 desc = "Reading instances @ '$collection'" for instance in _list_instances(index, collection) - - # Add instance to HDF5 file - HDF5.create_group(index.fp["collections"][collection], instance) - - let model = _get_instance_model(index, collection, instance; on_read_error) - isnothing(model) && continue - - dimension = QUBOTools.dimension(model) - density = QUBOTools.density(model) - linear_density = QUBOTools.linear_density(model) - quadratic_density = QUBOTools.quadratic_density(model) - - linear_min, linear_max = extrema(last, QUBOTools.linear_terms(model); init = (0, 0)) - quadratic_min, quadratic_max = extrema(last, QUBOTools.quadratic_terms(model); init = (0, 0)) - - _min = min(linear_min, quadratic_min) - _max = max(linear_max, quadratic_max) - - DBInterface.execute( - index.db, - """ - INSERT INTO instances - ( - instance, - dimension, - collection, - min, - max, - linear_min, - linear_max, - quadratic_min, - quadratic_max, - density, - linear_density, - quadratic_density - ) - VALUES - (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?); - """, - [ - instance, - dimension, - collection, - _min, - _max, - linear_min, - linear_max, - quadratic_min, - quadratic_max, - density, - linear_density, - quadratic_density, - ] - ) - - # add instance to HDF5 file - QUBOTools.write_model(index.fp["collections"][collection][instance], model, QUBOTools.QUBin()) - end - end - - DBInterface.execute( - index.db, - """ - UPDATE collections - SET size = (SELECT COUNT(*) FROM instances WHERE collection == ?) - WHERE collection = ?; - """, - [collection, collection] - ) - end - - # Close files - close(index.db) - close(index.fp) - - @assert !isopen(index.db) - @assert !isopen(index.fp) - - return nothing -end diff --git a/src/management/management.jl b/src/management/management.jl deleted file mode 100644 index e02667f..0000000 --- a/src/management/management.jl +++ /dev/null @@ -1,21 +0,0 @@ -module Management - -using ..QUBOLib -using LazyArtifacts -using HDF5 -using JSON -using JSONSchema -using JuliaFormatter -using LaTeXStrings -using SQLite -using DataFrames -using Tar -using TOML -using Pkg -using UUIDs -using QUBOTools -using ProgressMeter - -include("index.jl") - -end \ No newline at end of file diff --git a/src/management/metadata.schema.json b/src/management/metadata.schema.json deleted file mode 100644 index fba5f53..0000000 --- a/src/management/metadata.schema.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "http://json-schema.org/draft-04/schema#", - "title": "QUBO Instance Collection Metadata Schema", - "type": "object", - "properties": { - "problem": { - "type": "string", - "enum": [ - "3R3X", - "5R5X", - "QUBO" - ] - }, - "source": { - "type": "array", - "items": { - "type": "object", - "properties": { - "type": { - "type": "string", - "minLength": 1 - }, - "author": { - "type": "array", - "items": { - "type": "string", - "minLength": 1 - }, - "minLength": 1 - }, - "year": { - "type": "integer" - }, - "citekey": { - "type": "string", - "minLength": 1 - } - } - } - } - } -} \ No newline at end of file diff --git a/src/public/archive.jl b/src/public/archive.jl deleted file mode 100644 index 804c0e0..0000000 --- a/src/public/archive.jl +++ /dev/null @@ -1,16 +0,0 @@ -function archive(callback::Function, path::AbstractString) - return HDF5.h5open(callback, abspath(path, "archive.h5"), "r") -end - -function archive(path::AbstractString) - return HDF5.h5open(abspath(path, "archive.h5"), "r") -end - -function archive(callback::Function) - return archive(callback, data_path()) -end - -function archive() - return archive(data_path()) -end - \ No newline at end of file diff --git a/src/public/database.jl b/src/public/database.jl deleted file mode 100644 index 011a829..0000000 --- a/src/public/database.jl +++ /dev/null @@ -1,7 +0,0 @@ -function database(path::AbstractString) - return SQLite.DB(abspath(path, "index.sqlite")) -end - -function database() - return database(data_path()) -end diff --git a/src/public/interface.jl b/src/public/interface.jl deleted file mode 100644 index f36f7f1..0000000 --- a/src/public/interface.jl +++ /dev/null @@ -1,27 +0,0 @@ -@doc raw""" - load_instance(collection::AbstractString, instance::AbstractString) - -Loads a specific instance for a given collection as a QUBOTools model. -""" -function load_instance end - -@doc raw""" - list_collections() - -List the codes of the registered collections. -""" -function list_collections end - -@doc raw""" - list_instances(collection::AbstractString) - -Lists the codes for all instances in a collection. -""" -function list_instances end - -@doc raw""" - database() - -Returns a SQLite pointer for the instance index database. -""" -function database end \ No newline at end of file diff --git a/src/public/list.jl b/src/public/list.jl deleted file mode 100644 index 2d55c68..0000000 --- a/src/public/list.jl +++ /dev/null @@ -1,13 +0,0 @@ -function list_collections() - db = database() - df = DBInterface.execute(db, "SELECT collection FROM collections") |> DataFrame - - return collect(df[!, :collection]) -end - -function list_instances(collection::AbstractString) - db = database() - df = DBInterface.execute(db, "SELECT instance FROM instances WHERE collection = ?", [collection]) |> DataFrame - - return collect(df[!, :instance]) -end diff --git a/src/public/load.jl b/src/public/load.jl deleted file mode 100644 index 8702259..0000000 --- a/src/public/load.jl +++ /dev/null @@ -1,9 +0,0 @@ -function load_instance(collection::AbstractString, instance::AbstractString) - return load_instance(data_path(), collection, instance) -end - -function load_instance(path::AbstractString, collection::AbstractString, instance::AbstractString) - return archive(path) do fp - return QUBOTools.read_model(fp["collections"][collection][instance], QUBOTools.QUBin()) - end -end diff --git a/test/Project.toml b/test/Project.toml index 0c36332..b8092dd 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,2 +1,4 @@ [deps] +QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" +Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/collections/collectionX/data/problemx.qubo b/test/collections/collectionX/data/problemx.qubo deleted file mode 100644 index fc17c06..0000000 --- a/test/collections/collectionX/data/problemx.qubo +++ /dev/null @@ -1,12 +0,0 @@ -c id : 2 -c description : "Model 2 ~ Simple model with solutions" -c scale : 2.7 -c offset : 1.93 -p qubo 0 6 3 2 -c linear terms -1 1 0.0 -3 3 0.4 -5 5 -4.4 -c quadratic terms -1 3 -0.8 -1 5 6.0 \ No newline at end of file diff --git a/test/collections/collectionX/data/problemx2.qubo b/test/collections/collectionX/data/problemx2.qubo deleted file mode 100644 index c0ec88e..0000000 --- a/test/collections/collectionX/data/problemx2.qubo +++ /dev/null @@ -1,12 +0,0 @@ -c id : 1 -c -c scale : 1.0 -c offset : 0.0 -c description : "Model 1 ~ Simple model with linear terms" -c -p qubo 0 7 2 0 -c linear terms -2 2 1.3 -4 4 0.0 -6 6 -0.7 -c quadratic terms \ No newline at end of file diff --git a/test/collections/collectionX/metadata.json b/test/collections/collectionX/metadata.json deleted file mode 100644 index 9e26dfe..0000000 --- a/test/collections/collectionX/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/test/collections/collectionY/data/problemy.json b/test/collections/collectionY/data/problemy.json deleted file mode 100644 index 6bd5af5..0000000 --- a/test/collections/collectionY/data/problemy.json +++ /dev/null @@ -1,80 +0,0 @@ -{ - "scale": 2.7, - "id": 2, - "version": "1.0.0", - "description": "Model 2 ~ Simple model with solutions", - "linear_terms": [ - { - "id": 1, - "coeff": 0.0 - }, - { - "id": 3, - "coeff": 0.4 - }, - { - "id": 5, - "coeff": -4.4 - } - ], - "offset": 1.93, - "quadratic_terms": [ - { - "id_head": 1, - "coeff": -0.8, - "id_tail": 3 - }, - { - "id_head": 1, - "coeff": 6.0, - "id_tail": 5 - } - ], - "variable_domain": "boolean", - "variable_ids": [ - 1, - 3, - 5 - ], - "metadata": {}, - "solutions": [ - { - "evaluation": 6.291, - "id": 0, - "assignment": [ - { - "id": 1, - "value": 0 - }, - { - "id": 3, - "value": 1 - }, - { - "id": 5, - "value": 0 - } - ], - "description": "first solution" - }, - { - "evaluation": 9.531, - "id": 1, - "assignment": [ - { - "id": 1, - "value": 1 - }, - { - "id": 3, - "value": 0 - }, - { - "id": 5, - "value": 1 - } - ], - "description": "second solution" - } - ] -} \ No newline at end of file diff --git a/test/collections/collectionY/data/problemy2.json b/test/collections/collectionY/data/problemy2.json deleted file mode 100644 index 5abe1ca..0000000 --- a/test/collections/collectionY/data/problemy2.json +++ /dev/null @@ -1,76 +0,0 @@ -{ - "id": 2, - "description": "Model 2 ~ Simple model with solutions", - "linear_terms": [ - { - "coeff": 1.3, - "id": 1 - }, - { - "coeff": -0.7, - "id": 5 - } - ], - "metadata": {}, - "offset": 1.23, - "quadratic_terms": [ - { - "coeff": -0.2, - "id_head": 3, - "id_tail": 1 - }, - { - "coeff": 1.5, - "id_head": 5, - "id_tail": 1 - } - ], - "scale": 2.7, - "variable_domain": "spin", - "variable_ids": [ - 1, - 3, - 5 - ], - "version": "1.0.0", - "solutions": [ - { - "id": 0, - "description": "first solution", - "evaluation": 6.291, - "assignment": [ - { - "id": 1, - "value": -1 - }, - { - "id": 3, - "value": 1 - }, - { - "id": 5, - "value": -1 - } - ] - }, - { - "id": 1, - "description": "second solution", - "evaluation": 9.531, - "assignment": [ - { - "id": 1, - "value": 1 - }, - { - "id": 3, - "value": -1 - }, - { - "id": 5, - "value": 1 - } - ] - } - ] -} \ No newline at end of file diff --git a/test/collections/collectionY/metadata.json b/test/collections/collectionY/metadata.json deleted file mode 100644 index 9e26dfe..0000000 --- a/test/collections/collectionY/metadata.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/test/curation.jl b/test/curation.jl deleted file mode 100644 index 295d9a2..0000000 --- a/test/curation.jl +++ /dev/null @@ -1,26 +0,0 @@ -function test_curation() - @testset "□ Curation Routines" begin - ENV["LAST_QUBOLIB_TAG"] = "v1.2.3" - - let index = QUBOLib.create_index(abspath(@__DIR__)) - - @test index.root_path == abspath(@__DIR__) - @test index.list_path == abspath(@__DIR__, "collections") - @test index.dist_path == abspath(@__DIR__, "dist") - - QUBOLib.curate!(index) - - @test haskey(index.fp, "collections") - - QUBOLib.hash!(index) - - @test length(index.tree_hash[]) > 0 - - QUBOLib.tag!(index) - - @test index.next_tag[] == "v1.2.4" - end - end - - return nothing -end diff --git a/test/runtests.jl b/test/runtests.jl index d478f5c..799d59c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,11 +1,14 @@ using Test -using QUBOLib +using Statistics -include("curation.jl") +import QUBOTools +import QUBOLib + +include("synthesis.jl") function main() - @testset "♣ QUBOLib.jl «$(QUBOLib.__VERSION__)» Test Suite ♣" verbose = true begin - test_curation() + @testset "♣ QUBOLib.jl «$(QUBOLib.__version__())» Test Suite ♣" verbose = true begin + test_synthesis() end return nothing diff --git a/test/synthesis.jl b/test/synthesis.jl new file mode 100644 index 0000000..c220dc8 --- /dev/null +++ b/test/synthesis.jl @@ -0,0 +1,46 @@ +function test_synthesis() + @testset "→ Synthesis" verbose = true begin + test_wishart() + test_sherrington_kirkpatrick() + end + + return nothing +end + +function test_wishart() + @testset "⋅ Wishart" begin + let n = 100 + m = 10 + + model = QUBOLib.Synthesis.generate(QUBOLib.Synthesis.Wishart(n, m)) + + @test QUBOTools.dimension(model) == n + @test QUBOTools.density(model) ≈ 1.0 atol = 1E-8 + + let sol = QUBOTools.solution(model) + @test length(sol) > 0 + end + end + end + + return nothing +end + +function test_sherrington_kirkpatrick() + @testset "⋅ Sherrington-Kirkpatrick" begin + let n = 100 + μ = 5.0 + σ = 1E-3 + + model = QUBOLib.Synthesis.generate(QUBOLib.Synthesis.SK(n, μ, σ)) + + @test QUBOTools.dimension(model) == n + @test QUBOTools.density(model) ≈ 1.0 atol = 1E-8 + + @test mean(last, QUBOTools.linear_terms(model)) ≈ 2μ * (1 - n) atol = 10σ + @test mean(last, QUBOTools.quadratic_terms(model)) ≈ 4μ atol = 10σ + end + end + + return nothing +end