From c59d912312b7092a7f5435bc32368bfec313de1a Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Tue, 23 Jan 2024 18:58:37 -0300 Subject: [PATCH 01/23] Checkpoint --- Project.toml | 31 +-- docs/Project.toml | 5 +- docs/make.jl | 14 +- docs/src/api.md | 13 - docs/src/assets/logo.svg | 77 ++---- docs/src/booklet/0-intro.md | 0 docs/src/booklet/1-design.md | 19 ++ docs/src/{ => booklet}/database.md | 0 docs/src/index.md | 39 --- docs/src/manual/0-intro.md | 0 docs/src/manual/1-access.md | 0 docs/src/manual/2-extension.md | 0 src/QUBOLib.jl | 19 +- src/access/access.jl | 11 + src/{public => access}/archive.jl | 0 src/{public => access}/database.jl | 0 src/{public => access}/interface.jl | 9 +- src/{public => access}/list.jl | 0 src/{public => access}/load.jl | 0 src/collections/collections.jl | 25 ++ src/collections/qplib/format.jl | 77 ++++++ src/collections/qplib/metadata.json | 0 src/collections/qplib/qplib.jl | 21 ++ src/management/collection.jl | 12 + src/management/index.jl | 307 ---------------------- src/management/interface.jl | 32 +++ src/metadata.jl | 33 +++ src/{management => }/metadata.schema.json | 0 28 files changed, 304 insertions(+), 440 deletions(-) create mode 100644 docs/src/booklet/0-intro.md create mode 100644 docs/src/booklet/1-design.md rename docs/src/{ => booklet}/database.md (100%) create mode 100644 docs/src/manual/0-intro.md create mode 100644 docs/src/manual/1-access.md create mode 100644 docs/src/manual/2-extension.md create mode 100644 src/access/access.jl rename src/{public => access}/archive.jl (100%) rename src/{public => access}/database.jl (100%) rename src/{public => access}/interface.jl (81%) rename src/{public => access}/list.jl (100%) rename src/{public => access}/load.jl (100%) create mode 100644 src/collections/collections.jl create mode 100644 src/collections/qplib/format.jl create mode 100644 src/collections/qplib/metadata.json create mode 100644 src/collections/qplib/qplib.jl create mode 100644 src/management/collection.jl create mode 100644 src/management/interface.jl create mode 100644 src/metadata.jl rename src/{management => }/metadata.schema.json (100%) diff --git a/Project.toml b/Project.toml index ecde0f7..eec1626 100644 --- a/Project.toml +++ b/Project.toml @@ -1,23 +1,24 @@ -name = "QUBOLib" -uuid = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" +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" +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" 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" +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" [compat] QUBOTools = "0.9" diff --git a/docs/Project.toml b/docs/Project.toml index 1a6d309..1f1d0c6 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,5 +1,8 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +DocumenterDiagrams = "a106ebf2-4182-4cba-90d4-44cd3cc36e85" +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..576414d 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,4 +1,5 @@ using Documenter +using DocumenterDiagrams using QUBOLib # Set up to run docstrings with jldoctest @@ -16,8 +17,17 @@ makedocs(; sitename = "QUBOLib.jl", authors = "Pedro Maciel Xavier and David E. Bernal Neira", pages = [ - "Home" => "index.md", - "API" => "api.md", + "Home" => "index.md", + "API" => "api.md", + "Manual" => [ + "Introduction" => "manual/0-intro.md", + "Access" => "manual/1-access.md", + "Extension" => "manual/2-extension.md", + ], + "Booklet" => [ + "Introduction" => "booklet/0-intro.md", + "Library Design" => "booklet/1-design.md", + ], ], workdir = @__DIR__, ) diff --git a/docs/src/api.md b/docs/src/api.md index 01cc213..5932792 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -1,14 +1 @@ # API - -## List items - -```@docs -QUBOLib.list_collections -QUBOLib.list_instances -``` - -## Load instances - -```@docs -QUBOLib.load_instance -``` diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index 1fcdf61..b883a93 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -26,59 +26,30 @@ } + + + + + + - - - - - - - - - - - - - - - - - - - + + + + + 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..306c302 --- /dev/null +++ b/docs/src/booklet/1-design.md @@ -0,0 +1,19 @@ +# Library Design + +```@diagram tikz +\documentclass{standalone} +\usepackage{tikz} + +\begin{document} +\begin{tikzpicture}[ + node/.style={draw, rectangle, minimum width=2cm, minimum height=1cm, font=\large}, + ] + % Nodes + \node (A) at (0, 0) {\texttt{build()}}; + \node (B) at (2, 0) {\texttt{build(coll.code)}}; + + % Arrows + \draw[->] (A) -- node[above] {\texttt{coll.code}} (B); +\end{tikzpicture} +\end{document} +``` diff --git a/docs/src/database.md b/docs/src/booklet/database.md similarity index 100% rename from docs/src/database.md rename to docs/src/booklet/database.md diff --git a/docs/src/index.md b/docs/src/index.md index fc7cef7..8db994d 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -11,42 +11,3 @@ Pkg.add(url="https://github.com/pedromxavier/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..e69de29 diff --git a/docs/src/manual/1-access.md b/docs/src/manual/1-access.md new file mode 100644 index 0000000..e69de29 diff --git a/docs/src/manual/2-extension.md b/docs/src/manual/2-extension.md new file mode 100644 index 0000000..e69de29 diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index 0edad3c..6f21e65 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -3,6 +3,7 @@ module QUBOLib using LazyArtifacts using HDF5 using JSON +using Downloads using JSONSchema using JuliaFormatter using LaTeXStrings @@ -18,18 +19,18 @@ using ProgressMeter const __PROJECT__ = abspath(@__DIR__, "..") const __VERSION__ = VersionNumber(TOML.parsefile(joinpath(__PROJECT__, "Project.toml"))["version"]) -function data_path()::AbstractString - return abspath(artifact"qubolib") -end +# function data_path()::AbstractString +# return abspath(artifact"qubolib") +# end # Data management methods -include("management/index.jl") +# include("management/index.jl") # Public API -include("public/interface.jl") -include("public/load.jl") -include("public/list.jl") -include("public/archive.jl") -include("public/database.jl") +# include("access/interface.jl") +# include("access/load.jl") +# include("access/list.jl") +# include("access/archive.jl") +# include("access/database.jl") end # module QUBOLib diff --git a/src/access/access.jl b/src/access/access.jl new file mode 100644 index 0000000..34a819c --- /dev/null +++ b/src/access/access.jl @@ -0,0 +1,11 @@ +function access(callback::Function) + io = Index() + + try + return callback(io) + catch e + close(io) + + rethrow(e) + end +end diff --git a/src/public/archive.jl b/src/access/archive.jl similarity index 100% rename from src/public/archive.jl rename to src/access/archive.jl diff --git a/src/public/database.jl b/src/access/database.jl similarity index 100% rename from src/public/database.jl rename to src/access/database.jl diff --git a/src/public/interface.jl b/src/access/interface.jl similarity index 81% rename from src/public/interface.jl rename to src/access/interface.jl index f36f7f1..91811c4 100644 --- a/src/public/interface.jl +++ b/src/access/interface.jl @@ -24,4 +24,11 @@ function list_instances end Returns a SQLite pointer for the instance index database. """ -function database end \ No newline at end of file +function database end + +@doc raw""" + access() + +Returns an Index pointer for the library index. +""" +function access end diff --git a/src/public/list.jl b/src/access/list.jl similarity index 100% rename from src/public/list.jl rename to src/access/list.jl diff --git a/src/public/load.jl b/src/access/load.jl similarity index 100% rename from src/public/load.jl rename to src/access/load.jl diff --git a/src/collections/collections.jl b/src/collections/collections.jl new file mode 100644 index 0000000..6f491d7 --- /dev/null +++ b/src/collections/collections.jl @@ -0,0 +1,25 @@ +function get_metadata(coll::String) + return get_metadata(Symbol(coll)) +end + +function get_metadata(coll::Symbol) + return get_metadata(Val(coll)) +end + +function set_metadata(coll::Symbol, metadata::Dict{String, Any}) + return set_metadata(Val(coll), metadata) +end + +function validate_metadata(data::Dict{String, Any}) + @assert isnothing(JSONSchema.validate(data, COLLECTION_SCHEMA)) + + return nothing +end + +function load_collection!(index::Index, coll::String; cache::Bool = true) + return load_collection!(index, Symbol(coll); cache) +end + +function load_collection!(index::Index, coll::Symbol; cache::Bool = true) + return load_collection!(index, Val(coll); cache) +end diff --git a/src/collections/qplib/format.jl b/src/collections/qplib/format.jl new file mode 100644 index 0000000..8631fae --- /dev/null +++ b/src/collections/qplib/format.jl @@ -0,0 +1,77 @@ +@doc raw""" + QPLIB + +Format specification for reading QPLIB instances. +""" +struct QPLIB <: QUBOTools.AbstractFormat end + +QUBOTools.format(::Val{:qplib}) = QPLIB() + +function QUBOTools.read_model(io::IO, ::QPLIB) + # Read the header + code = readline(io) + + @assert !isnothing(match(r"(QBB)", readline(io))) + + sense = readline(io) + + @assert sense ∈ ("minimize", "maximize") + + vn = parse(Int, readline(io)) # number of variables + + V = Set{Int}(1:vn) + L = Dict{Int, Float64}() + Q = Dict{Tuple{Int, Int}, Float64}() + + qn = parse(Int, readline(io)) # number of quadratic terms in objective + + sizehint!(Q, qn) + + for _ in 1:qn + m = match(r"([0-9]+)\s+([0-9+])\s+(\S+)", readline(io)) + i = parse(Int, m[1]) + j = parse(Int, m[2]) + c = parse(Float64, m[3]) + + Q[(i, j)] = c + end + + dl = parse(Float64, readline(io)) # default value for linear coefficients in objective + ln = parse(Int, readline(io)) # number of non-default linear coefficients in objective + + sizehint!(L, ln) + + if !iszero(dl) + for i in 1:vn + L[i] = dl + end + end + + for _ in 1:ln + m = match(r"([0-9]+)\s+(\S+)", readline(io)) + i = parse(Int, m[1]) + c = parse(Float64, m[2]) + + L[i] = c + end + + β = parse(Float64, readline(io)) # objective constant + + @assert isfinite(parse(Float64, readline(io))) # value for infinity + @assert isfinite(parse(Float64, readline(io))) # default variable primal value in starting point + @assert iszero(parse(Int, readline(io))) # number of non-default variable primal values in starting point + + @assert isfinite(parse(Float64, readline(io))) # default variable bound dual value in starting point + @assert iszero(parse(Int, readline(io))) # number of non-default variable bound dual values in starting point + + @assert iszero(parse(Int, readline(io))) # number of non-default variable names + @assert iszero(parse(Int, readline(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 code '$code'", + ) +end diff --git a/src/collections/qplib/metadata.json b/src/collections/qplib/metadata.json new file mode 100644 index 0000000..e69de29 diff --git a/src/collections/qplib/qplib.jl b/src/collections/qplib/qplib.jl new file mode 100644 index 0000000..5f60dd9 --- /dev/null +++ b/src/collections/qplib/qplib.jl @@ -0,0 +1,21 @@ +const QPLIB_URL = "http://qplib.zib.de/qplib.zip" + +function metadata_path(::Val{:qplib}) + return bspath(@__DIR__, "metadata.json") +end + +function get_metadata(coll::Val{:qplib}; validate::Bool = true)::Dict{String, Any} + data = JSON.parsefile(metadata_path(coll)) + + validate && validate_metadata(data) + + return data +end + +function load_collection!(index::Index, coll::Val{:qplib}; cache::Bool = true) + cache && check_cache(index, coll) + + Downloads.download() + + return nothing +end \ No newline at end of file diff --git a/src/management/collection.jl b/src/management/collection.jl new file mode 100644 index 0000000..ea85b52 --- /dev/null +++ b/src/management/collection.jl @@ -0,0 +1,12 @@ +struct Collection{key} + name::String + + function Collection(key::Symbol; name::AbstractString = string(key)) + return new{key}(name) + end +end + +function Collection(data_path::AbstractString) + data = TOML.parsefile(data_path) + +end diff --git a/src/management/index.jl b/src/management/index.jl index 5f37bb0..8c9b8a8 100644 --- a/src/management/index.jl +++ b/src/management/index.jl @@ -1,131 +1,3 @@ -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) @@ -150,21 +22,7 @@ function _get_metadata(index::InstanceIndex, collection::AbstractString; validat 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)) @@ -245,70 +103,9 @@ function tag!(index::InstanceIndex) 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) @@ -387,108 +184,4 @@ function curate(root_path::AbstractString, dist_path::AbstractString=abspath(roo 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/interface.jl b/src/management/interface.jl new file mode 100644 index 0000000..14c33a5 --- /dev/null +++ b/src/management/interface.jl @@ -0,0 +1,32 @@ +@doc raw""" + build(; cache::Bool = true) +""" +function build end + +@doc raw""" + build!(index::Index; cache::Bool = true) + + build!(index::Index, collection::Collection; cache::Bool = true) +""" +function build! end + +@doc raw""" + cache!(index::Index) + + cache!(index::Index, collection::Collection) +""" +function cache! end + +@doc raw""" + index!(index::Index) + + index!(index::Index, collection::Collection) +""" +function index! end + +@doc raw""" + document!(index::Index) + + document!(index::Index, collection::Collection) +""" +function document! end \ No newline at end of file diff --git a/src/metadata.jl b/src/metadata.jl new file mode 100644 index 0000000..b1daf91 --- /dev/null +++ b/src/metadata.jl @@ -0,0 +1,33 @@ +function metadata_schema_path() + return abspath(@__DIR__, "metadata.schema.json") +end + +function metadata_schema() + return JSONSchema.Schema(JSON.parsefile(metadata_schema_path())) +end + +function validate_metadata(data::Dict{String, Any}) + report = JSONSchema.validate(metadata_schema(), data) + + if !isnothing(report) + error( + """ + Invalid collection metadata for $(collection): + $(report) + """ + ) + end + + return nothing +end + +function get_metadata(index::Index, collection::AbstractString; validate::Bool=true) + metapath = joinpath(index.list_path, collection, "metadata.json") + metadata = JSON.parsefile(metapath) + + if validate + validate_metadata(metadata) + end + + return metadata +end \ No newline at end of file diff --git a/src/management/metadata.schema.json b/src/metadata.schema.json similarity index 100% rename from src/management/metadata.schema.json rename to src/metadata.schema.json From 4e5e15c3fd64b388fdf040a75580e95308561f18 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Tue, 6 Feb 2024 07:45:11 -0300 Subject: [PATCH 02/23] Reformulate database --- .JuliaFormatter.toml | 2 + .gitignore | 3 + docs/src/booklet/1-design.md | 23 +- docs/src/booklet/database.md | 4 - docs/src/booklet/model.erd | 1096 +++++++++++++++++ docs/src/manual/0-intro.md | 14 + scripts/build.jl | 10 + scripts/deploy.jl | 5 + scripts/run.jl | 7 + src/QUBOLib.jl | 23 +- src/access/access.jl | 2 +- src/access/archive.jl | 16 - src/access/interface.jl | 6 + src/archive.jl | 17 + src/build.jl | 25 + src/collection.jl | 8 + .../arXiv_1903_10928_3r3x.jl | 5 + .../arXiv_1903_10928_5r5x.jl | 1 + .../arXiv_2103_08464_3r3x.jl | 1 + src/collections/collections.jl | 25 - src/collections/qplib.jl | 213 ++++ src/collections/qplib/format.jl | 77 -- src/collections/qplib/qplib.jl | 21 - src/database.jl | 75 ++ src/index.jl | 82 ++ src/logo.jl | 19 + src/management/build.jl | 27 + src/management/collection.jl | 12 - .../metadata.json => management/deploy.jl} | 0 src/management/index.jl | 29 +- src/management/interface.jl | 6 +- src/management/management.jl | 19 + src/path.jl | 30 + test/curation.jl | 26 - test/runtests.jl | 4 +- 35 files changed, 1690 insertions(+), 243 deletions(-) create mode 100644 .JuliaFormatter.toml delete mode 100644 docs/src/booklet/database.md create mode 100644 docs/src/booklet/model.erd create mode 100644 scripts/build.jl create mode 100644 scripts/deploy.jl create mode 100644 scripts/run.jl create mode 100644 src/archive.jl create mode 100644 src/build.jl create mode 100644 src/collection.jl create mode 100644 src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl create mode 100644 src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl create mode 100644 src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl delete mode 100644 src/collections/collections.jl create mode 100644 src/collections/qplib.jl delete mode 100644 src/collections/qplib/format.jl delete mode 100644 src/collections/qplib/qplib.jl create mode 100644 src/database.jl create mode 100644 src/index.jl create mode 100644 src/logo.jl create mode 100644 src/management/build.jl delete mode 100644 src/management/collection.jl rename src/{collections/qplib/metadata.json => management/deploy.jl} (100%) create mode 100644 src/path.jl delete mode 100644 test/curation.jl 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/.gitignore b/.gitignore index 29126e4..9e17a33 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,6 @@ docs/site/ # committed for packages, but should be committed for applications that require a static # environment. Manifest.toml + +# Distribution Files +dist/ diff --git a/docs/src/booklet/1-design.md b/docs/src/booklet/1-design.md index 306c302..d7ed966 100644 --- a/docs/src/booklet/1-design.md +++ b/docs/src/booklet/1-design.md @@ -1,19 +1,10 @@ -# Library Design +# Database Design -```@diagram tikz -\documentclass{standalone} -\usepackage{tikz} +In this section we discuss the decisions and specifications behind the construction of the database. -\begin{document} -\begin{tikzpicture}[ - node/.style={draw, rectangle, minimum width=2cm, minimum height=1cm, font=\large}, - ] - % Nodes - \node (A) at (0, 0) {\texttt{build()}}; - \node (B) at (2, 0) {\texttt{build(coll.code)}}; +## Models and Solutions - % Arrows - \draw[->] (A) -- node[above] {\texttt{coll.code}} (B); -\end{tikzpicture} -\end{document} -``` +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/database.md b/docs/src/booklet/database.md deleted file mode 100644 index c68e3bc..0000000 --- a/docs/src/booklet/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/booklet/model.erd b/docs/src/booklet/model.erd new file mode 100644 index 0000000..3fbf32f --- /dev/null +++ b/docs/src/booklet/model.erd @@ -0,0 +1,1096 @@ +{ + "$schema": "https://raw.githubusercontent.com/dineug/erd-editor/main/json-schema/schema.json", + "version": "3.0.0", + "settings": { + "width": 2000, + "height": 2000, + "scrollTop": 0, + "scrollLeft": -68.6095, + "zoomLevel": 0.9, + "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" + ], + "relationshipIds": [ + "1IewvhI07x-8lB--Kynv7", + "qh9ik3mw8bC1nd2N4Z70o" + ], + "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": 516.7776, + "y": 87.2223, + "zIndex": 2, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1707187008682, + "createAt": 1707069090217 + } + }, + "om5Bt9XofodPHGdyYzt3T": { + "id": "om5Bt9XofodPHGdyYzt3T", + "name": "Collections", + "comment": "", + "columnIds": [ + "TEVAnjSiSxgq0HfLJ9l_g", + "QtFsQmxkhmwRVA8kjS_k0", + "-JAozTsTy-vLm0mbRsy30", + "6_vyUE8Sn44Qnd_Tvpn19" + ], + "seqColumnIds": [ + "TEVAnjSiSxgq0HfLJ9l_g", + "QtFsQmxkhmwRVA8kjS_k0", + "-JAozTsTy-vLm0mbRsy30", + "6_vyUE8Sn44Qnd_Tvpn19" + ], + "ui": { + "x": 9.7778, + "y": 7.2223, + "zIndex": 41, + "widthName": 62, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1707186982783, + "createAt": 1707069338899 + } + }, + "D0rck5dTbcNtXaKBCbtx2": { + "id": "D0rck5dTbcNtXaKBCbtx2", + "name": "Solutions", + "comment": "", + "columnIds": [ + "x4I22QQi7V8bxjAtyUAbe", + "WwN0EvR-qwake5qpB9kHS", + "HqMBMoFAyLYdLZ9YHTIev" + ], + "seqColumnIds": [ + "x4I22QQi7V8bxjAtyUAbe", + "WwN0EvR-qwake5qpB9kHS", + "HqMBMoFAyLYdLZ9YHTIev" + ], + "ui": { + "x": 514, + "y": 215, + "zIndex": 53, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1707083522471, + "createAt": 1707083086999 + } + }, + "OASFdQTyDm_Da6SyshDHz": { + "id": "OASFdQTyDm_Da6SyshDHz", + "name": "Solutions", + "comment": "", + "columnIds": [ + "l5ph0ydr5sUlb28faHClb", + "1VEX0GRBZidEVQr4ywLRC", + "DA191Kizrzt3StPuCjScS", + "Xb9AuyicnFadrB5NhhEQc" + ], + "seqColumnIds": [ + "l5ph0ydr5sUlb28faHClb", + "1VEX0GRBZidEVQr4ywLRC", + "DA191Kizrzt3StPuCjScS", + "Xb9AuyicnFadrB5NhhEQc" + ], + "ui": { + "x": 9.5657, + "y": 376.6665, + "zIndex": 42, + "widthName": 60, + "widthComment": 60, + "color": "" + }, + "meta": { + "updateAt": 1707187002443, + "createAt": 1707170631227 + } + } + }, + "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": 1707170621699, + "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": 1707186803015, + "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": 1707170621700, + "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": 1707170621699, + "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": 1707170621700, + "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": 1707170621701, + "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": 1707170621701, + "createAt": 1707069513514 + } + }, + "x4I22QQi7V8bxjAtyUAbe": { + "id": "x4I22QQi7V8bxjAtyUAbe", + "tableId": "D0rck5dTbcNtXaKBCbtx2", + "name": "solution", + "comment": "", + "dataType": "INTEGER", + "default": "", + "options": 10, + "ui": { + "keys": 1, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707083140992, + "createAt": 1707083094173 + } + }, + "WwN0EvR-qwake5qpB9kHS": { + "id": "WwN0EvR-qwake5qpB9kHS", + "tableId": "D0rck5dTbcNtXaKBCbtx2", + "name": "instance", + "comment": "", + "dataType": "INTEGER", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707083569241, + "createAt": 1707083154433 + } + }, + "HqMBMoFAyLYdLZ9YHTIev": { + "id": "HqMBMoFAyLYdLZ9YHTIev", + "tableId": "D0rck5dTbcNtXaKBCbtx2", + "name": "", + "comment": "", + "dataType": "", + "default": "", + "options": 0, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707083522268, + "createAt": 1707083522268 + } + }, + "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": 1707170668219, + "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": 1707170650847, + "createAt": 1707170650845 + } + }, + "DA191Kizrzt3StPuCjScS": { + "id": "DA191Kizrzt3StPuCjScS", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "value", + "comment": "", + "dataType": "REAL", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707186821955, + "createAt": 1707170839276 + } + }, + "Xb9AuyicnFadrB5NhhEQc": { + "id": "Xb9AuyicnFadrB5NhhEQc", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "name": "optimal", + "comment": "", + "dataType": "BOOLEAN", + "default": "", + "options": 8, + "ui": { + "keys": 0, + "widthName": 60, + "widthComment": 60, + "widthDataType": 60, + "widthDefault": 60 + }, + "meta": { + "updateAt": 1707170888815, + "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": 1707186849990, + "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": 1707186850604, + "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": 1707186851110, + "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": 1707186851576, + "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": 1707186852073, + "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": 1707186852549, + "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": 1707186853043, + "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": 1707186853580, + "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": 1707186854156, + "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": 1707186864862, + "createAt": 1707186858665 + } + } + }, + "relationshipEntities": { + "1IewvhI07x-8lB--Kynv7": { + "id": "1IewvhI07x-8lB--Kynv7", + "identification": false, + "relationshipType": 16, + "startRelationshipType": 2, + "start": { + "tableId": "om5Bt9XofodPHGdyYzt3T", + "columnIds": [ + "TEVAnjSiSxgq0HfLJ9l_g" + ], + "x": 376.7778, + "y": 83.2223, + "direction": 2 + }, + "end": { + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "columnIds": [ + "XDgzkaxhdq3b-i82F5g73" + ], + "x": 516.7776, + "y": 179.22230000000002, + "direction": 1 + }, + "meta": { + "updateAt": 1707069453437, + "createAt": 1707069453437 + } + }, + "LeI3RuTUiDvcv6Z94DdoI": { + "id": "LeI3RuTUiDvcv6Z94DdoI", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "columnIds": [ + "BnMcYcvifd5DUaUS5Sc--" + ], + "x": 374, + "y": 151, + "direction": 2 + }, + "end": { + "tableId": "D0rck5dTbcNtXaKBCbtx2", + "columnIds": [ + "WwN0EvR-qwake5qpB9kHS" + ], + "x": 514, + "y": 279, + "direction": 1 + }, + "meta": { + "updateAt": 1707083154435, + "createAt": 1707083154435 + } + }, + "qh9ik3mw8bC1nd2N4Z70o": { + "id": "qh9ik3mw8bC1nd2N4Z70o", + "identification": false, + "relationshipType": 4, + "startRelationshipType": 2, + "start": { + "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "columnIds": [ + "BnMcYcvifd5DUaUS5Sc--" + ], + "x": 516.7776, + "y": 363.2223, + "direction": 1 + }, + "end": { + "tableId": "OASFdQTyDm_Da6SyshDHz", + "columnIds": [ + "1VEX0GRBZidEVQr4ywLRC" + ], + "x": 374.5657, + "y": 452.6665, + "direction": 2 + }, + "meta": { + "updateAt": 1707170650846, + "createAt": 1707170650846 + } + } + }, + "indexEntities": { + "CcCx9U5RiroEUiYV6Fm9l": { + "id": "CcCx9U5RiroEUiYV6Fm9l", + "name": "", + "tableId": "OASFdQTyDm_Da6SyshDHz", + "indexColumnIds": [], + "seqIndexColumnIds": [], + "unique": false, + "meta": { + "updateAt": 1707170688673, + "createAt": 1707170688673 + } + } + }, + "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 + } + ], + "D0rck5dTbcNtXaKBCbtx2": [ + "tableEntities", + 1707083086991, + 1707083569220, + { + "name": 1707083090702 + } + ], + "x4I22QQi7V8bxjAtyUAbe": [ + "tableColumnEntities", + 1707083094166, + -1, + { + "name": 1707083098422, + "dataType": 1707083139952, + "options(primaryKey)": 1707083140989 + } + ], + "WwN0EvR-qwake5qpB9kHS": [ + "tableColumnEntities", + 1707083154427, + -1, + { + "options(notNull)": 1707083154427, + "name": 1707083154427, + "dataType": 1707083154427, + "default": 1707083154427, + "comment": 1707083154427 + } + ], + "LeI3RuTUiDvcv6Z94DdoI": [ + "relationshipEntities", + 1707083154427, + 1707083569220, + {} + ], + "HqMBMoFAyLYdLZ9YHTIev": [ + "tableColumnEntities", + 1707083522248, + -1, + {} + ], + "OASFdQTyDm_Da6SyshDHz": [ + "tableEntities", + 1707170631223, + -1, + { + "name": 1707170635235 + } + ], + "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, + {} + ], + "CcCx9U5RiroEUiYV6Fm9l": [ + "indexEntities", + 1707170688666, + 1707170689897, + {} + ], + "DA191Kizrzt3StPuCjScS": [ + "tableColumnEntities", + 1707170839266, + -1, + { + "name": 1707170843449, + "dataType": 1707186821953, + "options(notNull)": 1707170893659 + } + ], + "Xb9AuyicnFadrB5NhhEQc": [ + "tableColumnEntities", + 1707170875696, + -1, + { + "name": 1707170879276, + "dataType": 1707170885169, + "options(notNull)": 1707170888811 + } + ], + "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 + } + ] + } +} \ No newline at end of file diff --git a/docs/src/manual/0-intro.md b/docs/src/manual/0-intro.md index e69de29..cf4482a 100644 --- a/docs/src/manual/0-intro.md +++ b/docs/src/manual/0-intro.md @@ -0,0 +1,14 @@ +# Introduction + +## Mathematical Definitions + +All instances have been recast into the binary, minimization form: + +```math +\begin{array}{rll} + \min_{\mathbf{x}} & \alpha \left[ \mathbf{x}' \mathbf{Q} \, \mathbf{x} + \mathbf{\ell}' \mathb{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``. diff --git a/scripts/build.jl b/scripts/build.jl new file mode 100644 index 0000000..b7ea667 --- /dev/null +++ b/scripts/build.jl @@ -0,0 +1,10 @@ +using QUBOLib + +function main() + QUBOLib.logo() + QUBOLib.build(QUBOLib.root_path(); clear_cache=("--clear_cache" ∈ ARGS)) + + return nothing +end + +main() # Here we go! diff --git a/scripts/deploy.jl b/scripts/deploy.jl new file mode 100644 index 0000000..17f6cda --- /dev/null +++ b/scripts/deploy.jl @@ -0,0 +1,5 @@ +import QUBOLib + +function deploy() + +end diff --git a/scripts/run.jl b/scripts/run.jl new file mode 100644 index 0000000..06ca7d7 --- /dev/null +++ b/scripts/run.jl @@ -0,0 +1,7 @@ +function run(instances, optimizers) + for instance in instances + for optimizer in optimizers + println("Running $(instance) with $(optimizer)") + end + end +end diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index 6f21e65..6d89dd0 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -19,18 +19,19 @@ using ProgressMeter const __PROJECT__ = abspath(@__DIR__, "..") const __VERSION__ = VersionNumber(TOML.parsefile(joinpath(__PROJECT__, "Project.toml"))["version"]) -# function data_path()::AbstractString -# return abspath(artifact"qubolib") -# end +# Standard Collection List +const COLLECTIONS = Symbol[] -# Data management methods -# include("management/index.jl") +# Library +include("logo.jl") +include("path.jl") +include("collection.jl") +include("database.jl") +include("archive.jl") +include("index.jl") +include("build.jl") -# Public API -# include("access/interface.jl") -# include("access/load.jl") -# include("access/list.jl") -# include("access/archive.jl") -# include("access/database.jl") +# Collections +include("collections/qplib.jl") end # module QUBOLib diff --git a/src/access/access.jl b/src/access/access.jl index 34a819c..e1d0dfd 100644 --- a/src/access/access.jl +++ b/src/access/access.jl @@ -1,5 +1,5 @@ function access(callback::Function) - io = Index() + io = load_index() try return callback(io) diff --git a/src/access/archive.jl b/src/access/archive.jl index 804c0e0..e69de29 100644 --- a/src/access/archive.jl +++ b/src/access/archive.jl @@ -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/access/interface.jl b/src/access/interface.jl index 91811c4..e735b2b 100644 --- a/src/access/interface.jl +++ b/src/access/interface.jl @@ -32,3 +32,9 @@ function database end Returns an Index pointer for the library index. """ function access end + +@doc raw""" + has_collection(index::Index, code::Symbol) + has_collection(index::Index, collection::Collection) +""" +function has_collection end \ No newline at end of file diff --git a/src/archive.jl b/src/archive.jl new file mode 100644 index 0000000..4046909 --- /dev/null +++ b/src/archive.jl @@ -0,0 +1,17 @@ +function _load_archive(path::AbstractString, mode::AbstractString="w") + 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 + + fp = HDF5.h5open(path, "w") + + HDF5.create_group(fp, "collections") + + return fp +end diff --git a/src/build.jl b/src/build.jl new file mode 100644 index 0000000..05e2c54 --- /dev/null +++ b/src/build.jl @@ -0,0 +1,25 @@ +function build(path::AbstractString = root_path(); clear_cache::Bool=false) + @info "Building QUBOLib v$(QUBOLib.__VERSION__)" + + if clear_cache + @info "Clearing Cache" + + rm(QUBOLib.cache_path(path); force=true, recursive=true) + end + + @info "Retrieving Library Index" + + QUBOLib.load_index(path; create=true) do index + for code in QUBOLib.COLLECTIONS + if !QUBOLib.has_collection(index, code) + QUBOLib.build!(index, code) + end + end + end +end + +function build!(index::LibraryIndex, code::Symbol) + @info "Building Collection: '$code'" + + return build!(index, Collection(code)) +end diff --git a/src/collection.jl b/src/collection.jl new file mode 100644 index 0000000..9e4528d --- /dev/null +++ b/src/collection.jl @@ -0,0 +1,8 @@ +@doc raw""" + Collection(code::Symbol) + +This wrapper type is used to allow for dispatch on the collection code. +""" +struct Collection{code} + Collection(code::Symbol) = new{code}() +end diff --git a/src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl b/src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl new file mode 100644 index 0000000..26250e7 --- /dev/null +++ b/src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl @@ -0,0 +1,5 @@ +function load!(index::LibraryIndex, ::Collection{:arXiv_1903_10928_3r3x}) + + + return nothing +end \ No newline at end of file diff --git a/src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl b/src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl new file mode 100644 index 0000000..1743633 --- /dev/null +++ b/src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl @@ -0,0 +1 @@ +function load!( ::Collection{:arXiv_1903_10928_5r5x}) \ No newline at end of file diff --git a/src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl b/src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl new file mode 100644 index 0000000..e1ad1a3 --- /dev/null +++ b/src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl @@ -0,0 +1 @@ +Collection{:arXiv_2103_08464_3r3x} \ No newline at end of file diff --git a/src/collections/collections.jl b/src/collections/collections.jl deleted file mode 100644 index 6f491d7..0000000 --- a/src/collections/collections.jl +++ /dev/null @@ -1,25 +0,0 @@ -function get_metadata(coll::String) - return get_metadata(Symbol(coll)) -end - -function get_metadata(coll::Symbol) - return get_metadata(Val(coll)) -end - -function set_metadata(coll::Symbol, metadata::Dict{String, Any}) - return set_metadata(Val(coll), metadata) -end - -function validate_metadata(data::Dict{String, Any}) - @assert isnothing(JSONSchema.validate(data, COLLECTION_SCHEMA)) - - return nothing -end - -function load_collection!(index::Index, coll::String; cache::Bool = true) - return load_collection!(index, Symbol(coll); cache) -end - -function load_collection!(index::Index, coll::Symbol; cache::Bool = true) - return load_collection!(index, Val(coll); cache) -end diff --git a/src/collections/qplib.jl b/src/collections/qplib.jl new file mode 100644 index 0000000..200a014 --- /dev/null +++ b/src/collections/qplib.jl @@ -0,0 +1,213 @@ +# Define codec for QPLIB format + +function _read_qplib_model(path::AbstractString) + return open(path, "r") do io + _read_qplib_model(io) + end +end + +function _read_qplib_model(io::IO) + # Read the header + code = readline(io) + + @assert !isnothing(match(r"(QBB)", readline(io))) + + sense = readline(io) + + @assert sense ∈ ("minimize", "maximize") + + nv = parse(Int, readline(io)) # number of variables + + V = Set{Int}(1:nv) + L = Dict{Int,Float64}() + Q = Dict{Tuple{Int,Int},Float64}() + + nq = parse(Int, readline(io)) # number of quadratic terms in objective + + sizehint!(Q, nq) + + for _ = 1:nq + m = match(r"([0-9]+)\s+([0-9+])\s+(\S+)", readline(io)) + i = parse(Int, m[1]) + j = parse(Int, m[2]) + c = parse(Float64, m[3]) + + Q[(i, j)] = c + end + + dl = parse(Float64, readline(io)) # default value for linear coefficients in objective + ln = parse(Int, readline(io)) # number of non-default linear coefficients in objective + + sizehint!(L, ln) + + if !iszero(dl) + for i = 1:nv + L[i] = dl + end + end + + for _ = 1:ln + m = match(r"([0-9]+)\s+(\S+)", readline(io)) + i = parse(Int, m[1]) + c = parse(Float64, m[2]) + + L[i] = c + end + + β = parse(Float64, readline(io)) # objective constant + + @assert isfinite(parse(Float64, readline(io))) # value for infinity + @assert isfinite(parse(Float64, readline(io))) # default variable primal value in starting point + @assert iszero(parse(Int, readline(io))) # number of non-default variable primal values in starting point + + @assert isfinite(parse(Float64, readline(io))) # default variable bound dual value in starting point + @assert iszero(parse(Int, readline(io))) # number of non-default variable bound dual values in starting point + + @assert iszero(parse(Int, readline(io))) # number of non-default variable names + @assert iszero(parse(Int, readline(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 '$code'", + ) +end + +function _read_qplib_solution(path::AbstractString, model::QUBOTools.Model{Int,Float64,Int}) + open(path, "r") do io + _read_qplib_solution!(io, model) + end + + return nothing +end + +function _read_qplib_solution!(io::IO, model::QUBOTools.Model{Int,Float64,Int}) + # Read the header + value = let + m = match(r"objvar\s+([\S]+)", readline(io)) + + if isnothing(m) + QUBOTools.syntax_error("Invalid header") + + return nothing + end + + tryparse(Float64, m[1]) + end + + if isnothing(value) + QUBOTools.syntax_error("Invalid objective value") + end + + v = Tuple{Int,Float64}[] + n = 0 + + for line in eachline(io) + i, λ = let + m = match(r"b([0-9]+)\s+([\S]+)", line) + + if isnothing(m) + QUBOTools.syntax_error("Invalid solution input") + + return nothing + end + + (tryparse(Int, m[1]), tryparse(Float64, m[2])) + end + + if isnothing(i) || isnothing(λ) + QUBOTools.syntax_error("Invalid variable assignment") + + return nothing + end + + n = max(n, i) + + push!(v, (i, λ)) + end + + ψ = zeros(Int, n) + + for (i, λ) in v + ψ[i] = ifelse(iszero(λ), 0, 1) + end + + s = QUBOTools.Sample{Float64,Int}[QUBOTools.Sample{Float64,Int}(ψ, λ)] + + sol = QUBOTools.SampleSet{Float64,Int}( + s; + sense = QUBOTools.sense(model), + domain = :bool, + ) + + QUBOTools.attach!(model, sol) +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 + +const QPLIB_URL = "http://qplib.zib.de/qplib.zip" + +function build!(index::LibraryIndex, coll::Collection{:qplib}) + load!(coll) + + @info "[qplib] Building index" + + qplib_data_path = abspath(cache_path(), "qplib", "data") + + return nothing +end + +function load!(::Collection{:qplib}) + @assert Sys.isunix() "Processing QPLIB is only possible on Unix systems" + + qplib_cache_path = mkpath(abspath(cache_path(), "qplib")) + qplib_data_path = mkpath(abspath(qplib_cache_path, "data")) + qplib_zip_path = abspath(qplib_cache_path, "qplib.zip") + + # Download QPLIB archive + if isfile(qplib_zip_path) + @info "[qplib] Archive already downloaded" + else + @info "[qplib] Downloading archive" + Downloads.download(QPLIB_URL, qplib_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 $qplib_zip_path 'qplib/html/qplib/*' 'qplib/html/sol/*' -d $qplib_data_path`, + ) + + # Remove non-QUBO instances + @info "[qplib] Removing non-QUBO instances" + + for file_path in filter(endswith(".qplib"), readdir(qplib_data_path; join = true)) + if !_is_qplib_qubo(file_path) + code = readline(file_path) + + rm(joinpath(qplib_data_path, "$(code).qplib"); force = true) + rm(joinpath(qplib_data_path, "$(code).sol"); force = true) + end + end + + return nothing +end + +# Add QPLIB to the standard collection list +push!(COLLECTIONS, :qplib) diff --git a/src/collections/qplib/format.jl b/src/collections/qplib/format.jl deleted file mode 100644 index 8631fae..0000000 --- a/src/collections/qplib/format.jl +++ /dev/null @@ -1,77 +0,0 @@ -@doc raw""" - QPLIB - -Format specification for reading QPLIB instances. -""" -struct QPLIB <: QUBOTools.AbstractFormat end - -QUBOTools.format(::Val{:qplib}) = QPLIB() - -function QUBOTools.read_model(io::IO, ::QPLIB) - # Read the header - code = readline(io) - - @assert !isnothing(match(r"(QBB)", readline(io))) - - sense = readline(io) - - @assert sense ∈ ("minimize", "maximize") - - vn = parse(Int, readline(io)) # number of variables - - V = Set{Int}(1:vn) - L = Dict{Int, Float64}() - Q = Dict{Tuple{Int, Int}, Float64}() - - qn = parse(Int, readline(io)) # number of quadratic terms in objective - - sizehint!(Q, qn) - - for _ in 1:qn - m = match(r"([0-9]+)\s+([0-9+])\s+(\S+)", readline(io)) - i = parse(Int, m[1]) - j = parse(Int, m[2]) - c = parse(Float64, m[3]) - - Q[(i, j)] = c - end - - dl = parse(Float64, readline(io)) # default value for linear coefficients in objective - ln = parse(Int, readline(io)) # number of non-default linear coefficients in objective - - sizehint!(L, ln) - - if !iszero(dl) - for i in 1:vn - L[i] = dl - end - end - - for _ in 1:ln - m = match(r"([0-9]+)\s+(\S+)", readline(io)) - i = parse(Int, m[1]) - c = parse(Float64, m[2]) - - L[i] = c - end - - β = parse(Float64, readline(io)) # objective constant - - @assert isfinite(parse(Float64, readline(io))) # value for infinity - @assert isfinite(parse(Float64, readline(io))) # default variable primal value in starting point - @assert iszero(parse(Int, readline(io))) # number of non-default variable primal values in starting point - - @assert isfinite(parse(Float64, readline(io))) # default variable bound dual value in starting point - @assert iszero(parse(Int, readline(io))) # number of non-default variable bound dual values in starting point - - @assert iszero(parse(Int, readline(io))) # number of non-default variable names - @assert iszero(parse(Int, readline(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 code '$code'", - ) -end diff --git a/src/collections/qplib/qplib.jl b/src/collections/qplib/qplib.jl deleted file mode 100644 index 5f60dd9..0000000 --- a/src/collections/qplib/qplib.jl +++ /dev/null @@ -1,21 +0,0 @@ -const QPLIB_URL = "http://qplib.zib.de/qplib.zip" - -function metadata_path(::Val{:qplib}) - return bspath(@__DIR__, "metadata.json") -end - -function get_metadata(coll::Val{:qplib}; validate::Bool = true)::Dict{String, Any} - data = JSON.parsefile(metadata_path(coll)) - - validate && validate_metadata(data) - - return data -end - -function load_collection!(index::Index, coll::Val{:qplib}; cache::Bool = true) - cache && check_cache(index, coll) - - Downloads.download() - - return nothing -end \ No newline at end of file diff --git a/src/database.jl b/src/database.jl new file mode 100644 index 0000000..d68402a --- /dev/null +++ b/src/database.jl @@ -0,0 +1,75 @@ +function _load_database(path::AbstractString) + if !isfile(path) + return nothing + else + return SQLite.DB(path) + end +end + +function _create_database(path::AbstractString) + # Remove file if it exists + rm(path; force=true) + + db = SQLite.DB(path) + + # Enable Foreign keys + DBInterface.execute(db, "PRAGMA foreign_keys = ON;") + + # Collections + DBInterface.execute(db, "DROP TABLE IF EXISTS collections;") + DBInterface.execute( + db, + """ + CREATE TABLE collections ( + collection TEXT PRIMARY KEY, -- Collection identifier + author TEXT , -- Author + description TEXT , -- Description + date DATETIME , -- Date of creation + url TEXT -- URL + ); + """ + ) + + # Instances + DBInterface.execute(db, "DROP TABLE IF EXISTS instances;") + DBInterface.execute( + db, + """ + CREATE TABLE instances ( + instance INTEGER PRIMARY KEY, -- Instance identifier + collection TEXT NOT NULL , -- Collection identifier + dimension INTEGER NOT NULL , -- Number of variables + min REAL , -- Minimum coefficient value + max REAL , -- Maximum coefficient value + abs_min REAL , -- Minimum absolute coefficient value + abs_max REAL , -- Maximum absolute coefficient 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) + ); + """ + ) + + # Solutions + DBInterface.execute(db, "DROP TABLE IF EXISTS solutions;") + DBInterface.execute( + db, + """ + CREATE TABLE solutions ( + solution INTEGER PRIMARY KEY, -- Solution identifier + instance TEXT NOT NULL , -- Instance identifier + vector TEXT NOT NULL , -- Solution state + + FOREIGN KEY (instance) REFERENCES instances (instance) + ); + """ + ) + + return db +end diff --git a/src/index.jl b/src/index.jl new file mode 100644 index 0000000..15bb85b --- /dev/null +++ b/src/index.jl @@ -0,0 +1,82 @@ +struct LibraryIndex + db::SQLite.DB + fp::HDF5.File + + collections::Vector{Symbol} + + metadata::Dict{String,Any} +end + +function LibraryIndex(db::SQLite.DB, fp::HDF5.File) + return LibraryIndex(db, fp, Symbol[], Dict{String,Any}()) +end + +function Base.isopen(index::LibraryIndex) + return isopen(index.db) && isopen(index.fp) +end + +function Base.close(index::LibraryIndex) + close(index.db) + close(index.fp) + + return nothing +end + +function _create_index(path::AbstractString) + db = _create_database(database_path(path)) + fp = _create_archive(archive_path(path)) + + return LibraryIndex(db, fp) +end + +@doc raw""" + load_index(path::AbstractString) + +Loads the library index from the given path. +""" +function load_index(path::AbstractString; create::Bool=false) + db = _load_database(database_path(path)) + fp = _load_archive(archive_path(path)) + + if isnothing(db) || isnothing(fp) + if create + @info "Creating index at '$path'" + + return _create_index(path) + else + error("Failed to load index from '$path'") + + return nothing + end + end + + return LibraryIndex(db, fp) +end + +function load_index(callback::Function, path::AbstractString=qubolib_path(); create::Bool=false) + index = load_index(path; create) + + @assert isopen(index) + + try + return callback(index) + finally + close(index) + end +end + +function has_collection(index::LibraryIndex, code::Symbol) + @assert isopen(index) + + df = DBInterface.execute( + index.db, + "SELECT COUNT(*) FROM collections WHERE collection = ?", + (code,) + ) |> DataFrame + + return only(df[!, 1]) > 0 +end + +function has_collection(index::LibraryIndex, ::Collection{code}) where {code} + return has_collection(index, code) +end diff --git a/src/logo.jl b/src/logo.jl new file mode 100644 index 0000000..c24ca40 --- /dev/null +++ b/src/logo.jl @@ -0,0 +1,19 @@ +const LOGO = """ + ██████ ██ ██ ██████ ██████ +██ ██ ██ ██ ██ ██ ██ ██ +██ ██ ██ ██ ██████ ██ ██ +██ ▄▄ ██ ██ ██ ██ ██ ██ ██ + ██████ ██████ ██████ ██████ + ▀▀ +██ ██ ██ +██ ██ +██ ██ ██████ +██ ██ ██ ██ +███████ ██ ██████ v$(__VERSION__) +""" + +function logo() + println(LOGO) + + return nothing +end diff --git a/src/management/build.jl b/src/management/build.jl new file mode 100644 index 0000000..b6ec1b6 --- /dev/null +++ b/src/management/build.jl @@ -0,0 +1,27 @@ +function build( + collections::AbstractVector, + root_path::AbstractString, + dist_path::AbstractString=abspath(root_path, "dist"); + cache::Bool=true, +) + index = create_index(root_path, dist_path) + + for coll in collections + build!(index, coll; cache) + end + + # Compute Tree hash + tree_hash = bytes2hex(Pkg.GitTools.tree_hash(dist_path)) + + return index +end + +function build!(index::LibraryIndex, coll::Collection; cache::Bool=true) + if !(cache && has_collection(index, coll)) + load!(index, coll; cache) + index!(index, coll) + document!(index, coll) + end + + return nothing +end diff --git a/src/management/collection.jl b/src/management/collection.jl deleted file mode 100644 index ea85b52..0000000 --- a/src/management/collection.jl +++ /dev/null @@ -1,12 +0,0 @@ -struct Collection{key} - name::String - - function Collection(key::Symbol; name::AbstractString = string(key)) - return new{key}(name) - end -end - -function Collection(data_path::AbstractString) - data = TOML.parsefile(data_path) - -end diff --git a/src/collections/qplib/metadata.json b/src/management/deploy.jl similarity index 100% rename from src/collections/qplib/metadata.json rename to src/management/deploy.jl diff --git a/src/management/index.jl b/src/management/index.jl index 8c9b8a8..d723ffe 100644 --- a/src/management/index.jl +++ b/src/management/index.jl @@ -25,7 +25,7 @@ end function hash!(index::InstanceIndex) - index.tree_hash[] = bytes2hex(Pkg.GitTools.tree_hash(index.dist_path)) + index.tree_hash[] = return nothing end @@ -158,30 +158,3 @@ 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 - - diff --git a/src/management/interface.jl b/src/management/interface.jl index 14c33a5..bcd4275 100644 --- a/src/management/interface.jl +++ b/src/management/interface.jl @@ -11,11 +11,11 @@ function build end function build! end @doc raw""" - cache!(index::Index) + load!(index::Index; cache::Bool = true) - cache!(index::Index, collection::Collection) + load!(index::Index, collection::Collection; cache::Bool = true) """ -function cache! end +function load! end @doc raw""" index!(index::Index) diff --git a/src/management/management.jl b/src/management/management.jl index e02667f..be83f53 100644 --- a/src/management/management.jl +++ b/src/management/management.jl @@ -18,4 +18,23 @@ using ProgressMeter include("index.jl") +function get_metadata(coll::String) + return get_metadata(Symbol(coll)) +end + +function get_metadata(coll::Symbol) + return get_metadata(Val(coll)) +end + +function set_metadata(coll::Symbol, metadata::Dict{String, Any}) + return set_metadata(Val(coll), metadata) +end + +function validate_metadata(data::Dict{String, Any}) + @assert isnothing(JSONSchema.validate(data, COLLECTION_SCHEMA)) + + return nothing +end + + end \ No newline at end of file diff --git a/src/path.jl b/src/path.jl new file mode 100644 index 0000000..03ad512 --- /dev/null +++ b/src/path.jl @@ -0,0 +1,30 @@ +function lib_path()::AbstractString + return abspath(artifact"qubolib") +end + +function database_path(path::AbstractString=lib_path())::AbstractString + return abspath(build_path(path), "index.db") +end + +function archive_path(path::AbstractString=lib_path())::AbstractString + return abspath(build_path(path), "archive.h5") +end + +# Functions below will be more often used when building the library, +# therefore they will point to the the project's root path by default. +function root_path()::AbstractString + return __PROJECT__ +end + +function dist_path(path::AbstractString=root_path())::AbstractString + return mkpath(abspath(path, "dist")) +end + +function build_path(path::AbstractString=root_path())::AbstractString + return mkpath(abspath(dist_path(path), "build")) +end + +function cache_path(path::AbstractString=root_path())::AbstractString + return mkpath(abspath(dist_path(path), "cache")) +end + 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..662c075 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,11 +1,9 @@ using Test using QUBOLib -include("curation.jl") - function main() @testset "♣ QUBOLib.jl «$(QUBOLib.__VERSION__)» Test Suite ♣" verbose = true begin - test_curation() + end return nothing From 9f8aecbe04f4983d99646f808dcb11853071c300 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Sun, 11 Feb 2024 12:18:55 -0300 Subject: [PATCH 03/23] Checkpoint --- docs/src/booklet/model.erd | 556 +++++++++++------- scripts/build.jl | 38 +- scripts/build/arXiv_1903_10928_3r3x.jl | 14 + scripts/build/arXiv_1903_10928_5r5x.jl | 8 + scripts/build/arXiv_2103_08464_3r3x.jl | 5 + {src/collections => scripts/build}/qplib.jl | 76 ++- src/QUBOLib.jl | 11 +- src/access/access.jl | 11 - src/access/archive.jl | 0 src/access/interface.jl | 40 -- src/build.jl | 25 - src/collection.jl | 8 - .../arXiv_1903_10928_3r3x.jl | 5 - .../arXiv_1903_10928_5r5x.jl | 1 - .../arXiv_2103_08464_3r3x.jl | 1 - src/database.jl | 75 --- src/index.jl | 35 +- src/{ => index}/archive.jl | 0 src/index/collection-data.schema.json | 33 ++ src/index/collections.jl | 64 ++ src/index/database.jl | 34 ++ src/index/instances.jl | 38 ++ src/index/qubolib.sql | 63 ++ src/index/solutions.jl | 34 ++ src/index/solvers.jl | 48 ++ src/interface.jl | 22 + src/metadata.jl | 33 -- src/metadata.schema.json | 42 -- 28 files changed, 804 insertions(+), 516 deletions(-) create mode 100644 scripts/build/arXiv_1903_10928_3r3x.jl create mode 100644 scripts/build/arXiv_1903_10928_5r5x.jl create mode 100644 scripts/build/arXiv_2103_08464_3r3x.jl rename {src/collections => scripts/build}/qplib.jl (78%) delete mode 100644 src/access/access.jl delete mode 100644 src/access/archive.jl delete mode 100644 src/access/interface.jl delete mode 100644 src/build.jl delete mode 100644 src/collection.jl delete mode 100644 src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl delete mode 100644 src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl delete mode 100644 src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl delete mode 100644 src/database.jl rename src/{ => index}/archive.jl (100%) create mode 100644 src/index/collection-data.schema.json create mode 100644 src/index/collections.jl create mode 100644 src/index/database.jl create mode 100644 src/index/instances.jl create mode 100644 src/index/qubolib.sql create mode 100644 src/index/solutions.jl create mode 100644 src/index/solvers.jl create mode 100644 src/interface.jl delete mode 100644 src/metadata.jl delete mode 100644 src/metadata.schema.json diff --git a/docs/src/booklet/model.erd b/docs/src/booklet/model.erd index 3fbf32f..02cb04f 100644 --- a/docs/src/booklet/model.erd +++ b/docs/src/booklet/model.erd @@ -4,9 +4,9 @@ "settings": { "width": 2000, "height": 2000, - "scrollTop": 0, - "scrollLeft": -68.6095, - "zoomLevel": 0.9, + "scrollTop": -26.1011, + "scrollLeft": 0, + "zoomLevel": 1, "show": 431, "database": 4, "databaseName": "", @@ -32,11 +32,13 @@ "tableIds": [ "jDnk6FD4hm_KMqhH_RwSb", "om5Bt9XofodPHGdyYzt3T", - "OASFdQTyDm_Da6SyshDHz" + "OASFdQTyDm_Da6SyshDHz", + "4n9HJGfi4-dks_pkia6Sr" ], "relationshipIds": [ "1IewvhI07x-8lB--Kynv7", - "qh9ik3mw8bC1nd2N4Z70o" + "qh9ik3mw8bC1nd2N4Z70o", + "GZ2ff-WVLAglGFqqUw3Ru" ], "indexIds": [], "memoIds": [] @@ -79,15 +81,15 @@ "0q25OZ5OKv4BMpwvL5ov1" ], "ui": { - "x": 516.7776, - "y": 87.2223, + "x": 764.5554, + "y": 11.6668, "zIndex": 2, "widthName": 60, "widthComment": 60, "color": "" }, "meta": { - "updateAt": 1707187008682, + "updateAt": 1707648090631, "createAt": 1707069090217 } }, @@ -97,83 +99,88 @@ "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": 9.7778, - "y": 7.2223, + "x": 8.6668, + "y": 13.889, "zIndex": 41, "widthName": 62, "widthComment": 60, "color": "" }, "meta": { - "updateAt": 1707186982783, + "updateAt": 1707649186522, "createAt": 1707069338899 } }, - "D0rck5dTbcNtXaKBCbtx2": { - "id": "D0rck5dTbcNtXaKBCbtx2", + "OASFdQTyDm_Da6SyshDHz": { + "id": "OASFdQTyDm_Da6SyshDHz", "name": "Solutions", - "comment": "", + "comment": "Each entry has a summary of a SampleSet, that can be accessed using the solution ID", "columnIds": [ - "x4I22QQi7V8bxjAtyUAbe", - "WwN0EvR-qwake5qpB9kHS", - "HqMBMoFAyLYdLZ9YHTIev" + "l5ph0ydr5sUlb28faHClb", + "1VEX0GRBZidEVQr4ywLRC", + "hk3EuFZ1chYw6-j8AaoGX", + "DA191Kizrzt3StPuCjScS", + "Xb9AuyicnFadrB5NhhEQc", + "K1lvvk-kVz8s8dSkXEd4R" ], "seqColumnIds": [ - "x4I22QQi7V8bxjAtyUAbe", - "WwN0EvR-qwake5qpB9kHS", - "HqMBMoFAyLYdLZ9YHTIev" + "l5ph0ydr5sUlb28faHClb", + "1VEX0GRBZidEVQr4ywLRC", + "hk3EuFZ1chYw6-j8AaoGX", + "DA191Kizrzt3StPuCjScS", + "Xb9AuyicnFadrB5NhhEQc", + "7M3a_VTNtsDkqFT6kpbWQ", + "K1lvvk-kVz8s8dSkXEd4R" ], "ui": { - "x": 514, - "y": 215, - "zIndex": 53, + "x": 11.7879, + "y": 196.6665, + "zIndex": 42, "widthName": 60, - "widthComment": 60, + "widthComment": 457, "color": "" }, "meta": { - "updateAt": 1707083522471, - "createAt": 1707083086999 + "updateAt": 1707649086861, + "createAt": 1707170631227 } }, - "OASFdQTyDm_Da6SyshDHz": { - "id": "OASFdQTyDm_Da6SyshDHz", - "name": "Solutions", + "4n9HJGfi4-dks_pkia6Sr": { + "id": "4n9HJGfi4-dks_pkia6Sr", + "name": "Solvers", "comment": "", "columnIds": [ - "l5ph0ydr5sUlb28faHClb", - "1VEX0GRBZidEVQr4ywLRC", - "DA191Kizrzt3StPuCjScS", - "Xb9AuyicnFadrB5NhhEQc" + "CvX_9IX6yQ9hMZpfmfzRw", + "1doV5DTzybkLjNQrYYwVQ" ], "seqColumnIds": [ - "l5ph0ydr5sUlb28faHClb", - "1VEX0GRBZidEVQr4ywLRC", - "DA191Kizrzt3StPuCjScS", - "Xb9AuyicnFadrB5NhhEQc" + "CvX_9IX6yQ9hMZpfmfzRw", + "1doV5DTzybkLjNQrYYwVQ" ], "ui": { - "x": 9.5657, - "y": 376.6665, - "zIndex": 42, + "x": 765.1218, + "y": 395.5555, + "zIndex": 46, "widthName": 60, "widthComment": 60, "color": "" }, "meta": { - "updateAt": 1707187002443, - "createAt": 1707170631227 + "updateAt": 1707649276439, + "createAt": 1707324293977 } } }, @@ -194,7 +201,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170621699, + "updateAt": 1707648090629, "createAt": 1707069098971 } }, @@ -234,7 +241,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186803015, + "updateAt": 1707648090630, "createAt": 1707069333783 } }, @@ -254,7 +261,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170621700, + "updateAt": 1707648090632, "createAt": 1707069345774 } }, @@ -274,7 +281,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170621699, + "updateAt": 1707648090630, "createAt": 1707069453436 } }, @@ -294,7 +301,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170621700, + "updateAt": 1707648090632, "createAt": 1707069481399 } }, @@ -314,7 +321,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170621701, + "updateAt": 1707648090632, "createAt": 1707069493277 } }, @@ -334,70 +341,10 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170621701, + "updateAt": 1707648090632, "createAt": 1707069513514 } }, - "x4I22QQi7V8bxjAtyUAbe": { - "id": "x4I22QQi7V8bxjAtyUAbe", - "tableId": "D0rck5dTbcNtXaKBCbtx2", - "name": "solution", - "comment": "", - "dataType": "INTEGER", - "default": "", - "options": 10, - "ui": { - "keys": 1, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1707083140992, - "createAt": 1707083094173 - } - }, - "WwN0EvR-qwake5qpB9kHS": { - "id": "WwN0EvR-qwake5qpB9kHS", - "tableId": "D0rck5dTbcNtXaKBCbtx2", - "name": "instance", - "comment": "", - "dataType": "INTEGER", - "default": "", - "options": 8, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1707083569241, - "createAt": 1707083154433 - } - }, - "HqMBMoFAyLYdLZ9YHTIev": { - "id": "HqMBMoFAyLYdLZ9YHTIev", - "tableId": "D0rck5dTbcNtXaKBCbtx2", - "name": "", - "comment": "", - "dataType": "", - "default": "", - "options": 0, - "ui": { - "keys": 0, - "widthName": 60, - "widthComment": 60, - "widthDataType": 60, - "widthDefault": 60 - }, - "meta": { - "updateAt": 1707083522268, - "createAt": 1707083522268 - } - }, "l5ph0ydr5sUlb28faHClb": { "id": "l5ph0ydr5sUlb28faHClb", "tableId": "OASFdQTyDm_Da6SyshDHz", @@ -414,7 +361,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170668219, + "updateAt": 1707648090633, "createAt": 1707170642655 } }, @@ -434,7 +381,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170650847, + "updateAt": 1707648090633, "createAt": 1707170650845 } }, @@ -442,19 +389,19 @@ "id": "DA191Kizrzt3StPuCjScS", "tableId": "OASFdQTyDm_Da6SyshDHz", "name": "value", - "comment": "", + "comment": "Best value found", "dataType": "REAL", "default": "", "options": 8, "ui": { "keys": 0, "widthName": 60, - "widthComment": 60, + "widthComment": 91, "widthDataType": 60, "widthDefault": 60 }, "meta": { - "updateAt": 1707186821955, + "updateAt": 1707648090633, "createAt": 1707170839276 } }, @@ -464,7 +411,7 @@ "name": "optimal", "comment": "", "dataType": "BOOLEAN", - "default": "", + "default": "FALSE", "options": 8, "ui": { "keys": 0, @@ -474,7 +421,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707170888815, + "updateAt": 1707649253088, "createAt": 1707170875701 } }, @@ -494,7 +441,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186849990, + "updateAt": 1707648090630, "createAt": 1707186553632 } }, @@ -514,7 +461,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186850604, + "updateAt": 1707648090630, "createAt": 1707186558440 } }, @@ -534,7 +481,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186851110, + "updateAt": 1707648090630, "createAt": 1707186565821 } }, @@ -554,7 +501,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186851576, + "updateAt": 1707648090630, "createAt": 1707186570657 } }, @@ -574,7 +521,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186852073, + "updateAt": 1707648090631, "createAt": 1707186576043 } }, @@ -594,7 +541,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186852549, + "updateAt": 1707648090631, "createAt": 1707186581453 } }, @@ -614,7 +561,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186853043, + "updateAt": 1707648090631, "createAt": 1707186586063 } }, @@ -634,7 +581,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186853580, + "updateAt": 1707648090631, "createAt": 1707186620006 } }, @@ -654,7 +601,7 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186854156, + "updateAt": 1707648090631, "createAt": 1707186842212 } }, @@ -674,9 +621,129 @@ "widthDefault": 60 }, "meta": { - "updateAt": 1707186864862, + "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": { @@ -690,8 +757,8 @@ "columnIds": [ "TEVAnjSiSxgq0HfLJ9l_g" ], - "x": 376.7778, - "y": 83.2223, + "x": 375.6668, + "y": 101.889, "direction": 2 }, "end": { @@ -699,8 +766,8 @@ "columnIds": [ "XDgzkaxhdq3b-i82F5g73" ], - "x": 516.7776, - "y": 179.22230000000002, + "x": 764.5554, + "y": 103.6668, "direction": 1 }, "meta": { @@ -708,8 +775,8 @@ "createAt": 1707069453437 } }, - "LeI3RuTUiDvcv6Z94DdoI": { - "id": "LeI3RuTUiDvcv6Z94DdoI", + "qh9ik3mw8bC1nd2N4Z70o": { + "id": "qh9ik3mw8bC1nd2N4Z70o", "identification": false, "relationshipType": 4, "startRelationshipType": 2, @@ -718,67 +785,82 @@ "columnIds": [ "BnMcYcvifd5DUaUS5Sc--" ], - "x": 374, - "y": 151, + "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": "D0rck5dTbcNtXaKBCbtx2", + "tableId": "4n9HJGfi4-dks_pkia6Sr", "columnIds": [ - "WwN0EvR-qwake5qpB9kHS" + "1doV5DTzybkLjNQrYYwVQ" ], - "x": 514, - "y": 279, + "x": 618.455, + "y": 566.4444, "direction": 1 }, "meta": { - "updateAt": 1707083154435, - "createAt": 1707083154435 + "updateAt": 1707324330728, + "createAt": 1707324330728 } }, - "qh9ik3mw8bC1nd2N4Z70o": { - "id": "qh9ik3mw8bC1nd2N4Z70o", + "GZ2ff-WVLAglGFqqUw3Ru": { + "id": "GZ2ff-WVLAglGFqqUw3Ru", "identification": false, - "relationshipType": 4, - "startRelationshipType": 2, + "relationshipType": 16, + "startRelationshipType": 1, "start": { - "tableId": "jDnk6FD4hm_KMqhH_RwSb", + "tableId": "4n9HJGfi4-dks_pkia6Sr", "columnIds": [ - "BnMcYcvifd5DUaUS5Sc--" + "CvX_9IX6yQ9hMZpfmfzRw" ], - "x": 516.7776, - "y": 363.2223, + "x": 765.1218, + "y": 447.5555, "direction": 1 }, "end": { "tableId": "OASFdQTyDm_Da6SyshDHz", "columnIds": [ - "1VEX0GRBZidEVQr4ywLRC" + "hk3EuFZ1chYw6-j8AaoGX" ], - "x": 374.5657, - "y": 452.6665, + "x": 562.7879, + "y": 346.66650000000004, "direction": 2 }, "meta": { - "updateAt": 1707170650846, - "createAt": 1707170650846 - } - } - }, - "indexEntities": { - "CcCx9U5RiroEUiYV6Fm9l": { - "id": "CcCx9U5RiroEUiYV6Fm9l", - "name": "", - "tableId": "OASFdQTyDm_Da6SyshDHz", - "indexColumnIds": [], - "seqIndexColumnIds": [], - "unique": false, - "meta": { - "updateAt": 1707170688673, - "createAt": 1707170688673 + "updateAt": 1707324866914, + "createAt": 1707324344493 } } }, + "indexEntities": {}, "indexColumnEntities": {}, "memoEntities": {} }, @@ -887,54 +969,13 @@ "dataType": 1707083068932 } ], - "D0rck5dTbcNtXaKBCbtx2": [ - "tableEntities", - 1707083086991, - 1707083569220, - { - "name": 1707083090702 - } - ], - "x4I22QQi7V8bxjAtyUAbe": [ - "tableColumnEntities", - 1707083094166, - -1, - { - "name": 1707083098422, - "dataType": 1707083139952, - "options(primaryKey)": 1707083140989 - } - ], - "WwN0EvR-qwake5qpB9kHS": [ - "tableColumnEntities", - 1707083154427, - -1, - { - "options(notNull)": 1707083154427, - "name": 1707083154427, - "dataType": 1707083154427, - "default": 1707083154427, - "comment": 1707083154427 - } - ], - "LeI3RuTUiDvcv6Z94DdoI": [ - "relationshipEntities", - 1707083154427, - 1707083569220, - {} - ], - "HqMBMoFAyLYdLZ9YHTIev": [ - "tableColumnEntities", - 1707083522248, - -1, - {} - ], "OASFdQTyDm_Da6SyshDHz": [ "tableEntities", 1707170631223, -1, { - "name": 1707170635235 + "name": 1707170635235, + "comment": 1707324478021 } ], "l5ph0ydr5sUlb28faHClb": [ @@ -966,12 +1007,6 @@ -1, {} ], - "CcCx9U5RiroEUiYV6Fm9l": [ - "indexEntities", - 1707170688666, - 1707170689897, - {} - ], "DA191Kizrzt3StPuCjScS": [ "tableColumnEntities", 1707170839266, @@ -979,7 +1014,8 @@ { "name": 1707170843449, "dataType": 1707186821953, - "options(notNull)": 1707170893659 + "options(notNull)": 1707170893659, + "comment": 1707324450317 } ], "Xb9AuyicnFadrB5NhhEQc": [ @@ -989,7 +1025,8 @@ { "name": 1707170879276, "dataType": 1707170885169, - "options(notNull)": 1707170888811 + "options(notNull)": 1707170888811, + "default": 1707658033708 } ], "hQdMrer7qlTWkgyJVAfCs": [ @@ -1091,6 +1128,89 @@ "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/scripts/build.jl b/scripts/build.jl index b7ea667..8ae7f31 100644 --- a/scripts/build.jl +++ b/scripts/build.jl @@ -1,8 +1,44 @@ +using QUBOTools using QUBOLib +# Standard Library +include("build/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 + @info "Clearing Build" + + rm(QUBOLib.build_path(path); force = true, recursive = true) + end + + if clear_cache + @info "Clearing Cache" + + rm(QUBOLib.cache_path(path); force = true, recursive = true) + end + + QUBOLib.load_index(path; create = true) do index + build_qplib!(index) + end + + return nothing +end + + function main() QUBOLib.logo() - QUBOLib.build(QUBOLib.root_path(); clear_cache=("--clear_cache" ∈ ARGS)) + + build_standard_qubolib( + QUBOLib.root_path(); + clear_build = ("--clear-build" ∈ ARGS), + clear_cache = ("--clear-cache" ∈ ARGS), + ) return nothing end diff --git a/scripts/build/arXiv_1903_10928_3r3x.jl b/scripts/build/arXiv_1903_10928_3r3x.jl new file mode 100644 index 0000000..4b54704 --- /dev/null +++ b/scripts/build/arXiv_1903_10928_3r3x.jl @@ -0,0 +1,14 @@ +function build_arXiv_1903_10928_3r3x!(index::LibraryIndex) + QUBOLib.add_collection!( + index, + :arXiv_1903_10928_3r3x, + Dict{String,Any}( + "name" => "arXiv:1903_10928", + "url" => "http://qplib.zib.de/", + ), + ) + + @info "[arXiv_1903_10928_3r3x] Building index" + + 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..4222e45 --- /dev/null +++ b/scripts/build/arXiv_1903_10928_5r5x.jl @@ -0,0 +1,8 @@ +function build_arXiv_1903_10928_5r5x!(index::LibraryIndex) + @info "[arXiv_1903_10928_5r5x] Building index" + + + + + 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/src/collections/qplib.jl b/scripts/build/qplib.jl similarity index 78% rename from src/collections/qplib.jl rename to scripts/build/qplib.jl index 200a014..06a07f2 100644 --- a/src/collections/qplib.jl +++ b/scripts/build/qplib.jl @@ -1,4 +1,5 @@ # Define codec for QPLIB format +# TODO: Make it available @ QUBOTools function _read_qplib_model(path::AbstractString) return open(path, "r") do io @@ -87,7 +88,7 @@ end function _read_qplib_solution!(io::IO, model::QUBOTools.Model{Int,Float64,Int}) # Read the header - value = let + λ = let m = match(r"objvar\s+([\S]+)", readline(io)) if isnothing(m) @@ -99,15 +100,15 @@ function _read_qplib_solution!(io::IO, model::QUBOTools.Model{Int,Float64,Int}) tryparse(Float64, m[1]) end - if isnothing(value) + if isnothing(λ) QUBOTools.syntax_error("Invalid objective value") end - v = Tuple{Int,Float64}[] - n = 0 + n = QUBOTools.dimension(model) + ψ = zeros(Int, n) for line in eachline(io) - i, λ = let + i, x = let m = match(r"b([0-9]+)\s+([\S]+)", line) if isnothing(m) @@ -119,32 +120,22 @@ function _read_qplib_solution!(io::IO, model::QUBOTools.Model{Int,Float64,Int}) (tryparse(Int, m[1]), tryparse(Float64, m[2])) end - if isnothing(i) || isnothing(λ) + if isnothing(i) || isnothing(x) QUBOTools.syntax_error("Invalid variable assignment") return nothing end - n = max(n, i) - - push!(v, (i, λ)) - end - - ψ = zeros(Int, n) - - for (i, λ) in v - ψ[i] = ifelse(iszero(λ), 0, 1) + ψ[i] = ifelse(x > 0, 1, 0) end s = QUBOTools.Sample{Float64,Int}[QUBOTools.Sample{Float64,Int}(ψ, λ)] - sol = QUBOTools.SampleSet{Float64,Int}( + return QUBOTools.SampleSet{Float64,Int}( s; sense = QUBOTools.sense(model), domain = :bool, ) - - QUBOTools.attach!(model, sol) end function _is_qplib_qubo(path::AbstractString) @@ -160,17 +151,51 @@ end const QPLIB_URL = "http://qplib.zib.de/qplib.zip" -function build!(index::LibraryIndex, coll::Collection{:qplib}) - load!(coll) +function build_qplib!(index::LibraryIndex) + if QUBOLib.has_collection(index, :qplib) + @info "[qplib] Collection already exists" + + QUBOLib.remove_collection!(index, :qplib) + end + + QUBOLib.add_collection!( + index, + :qplib, + Dict{String,Any}( + "name" => "QPLIB", + "author" => ["", ""], + "description" => "The Quadratic Programming Library", + "year" => 2014, + "url" => "http://qplib.zib.de/", + ), + ) + + code_list = _load_qplib!() @info "[qplib] Building index" qplib_data_path = abspath(cache_path(), "qplib", "data") + for code in code_list + mod_path = joinpath(qplib_data_path, "$(code).qplib") + sol_path = joinpath(qplib_data_path, "$(code).sol") + + model = _read_qplib_model(mod_path) + mod_i = QUBOLib.add_instance!(index, model, :qplib) + + if isfile(sol_path) + sol = _read_qplib_solution(sol_path, model) + + QUBOLib.add_solution!(index, mod_i, sol) + end + end + + @info "[qplib] Done!" + return nothing end -function load!(::Collection{:qplib}) +function _load_qplib!() @assert Sys.isunix() "Processing QPLIB is only possible on Unix systems" qplib_cache_path = mkpath(abspath(cache_path(), "qplib")) @@ -197,17 +222,18 @@ function load!(::Collection{:qplib}) # Remove non-QUBO instances @info "[qplib] Removing non-QUBO instances" + code_list = String[] + for file_path in filter(endswith(".qplib"), readdir(qplib_data_path; join = true)) if !_is_qplib_qubo(file_path) code = readline(file_path) + push!(code_list, code) + rm(joinpath(qplib_data_path, "$(code).qplib"); force = true) rm(joinpath(qplib_data_path, "$(code).sol"); force = true) end end - return nothing + return code_list end - -# Add QPLIB to the standard collection list -push!(COLLECTIONS, :qplib) diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index 6d89dd0..ac4e29b 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -19,19 +19,14 @@ using ProgressMeter const __PROJECT__ = abspath(@__DIR__, "..") const __VERSION__ = VersionNumber(TOML.parsefile(joinpath(__PROJECT__, "Project.toml"))["version"]) -# Standard Collection List -const COLLECTIONS = Symbol[] +export LibraryIndex -# Library include("logo.jl") include("path.jl") -include("collection.jl") -include("database.jl") -include("archive.jl") +include("interface.jl") include("index.jl") -include("build.jl") # Collections -include("collections/qplib.jl") +# include("collections/qplib.jl") end # module QUBOLib diff --git a/src/access/access.jl b/src/access/access.jl deleted file mode 100644 index e1d0dfd..0000000 --- a/src/access/access.jl +++ /dev/null @@ -1,11 +0,0 @@ -function access(callback::Function) - io = load_index() - - try - return callback(io) - catch e - close(io) - - rethrow(e) - end -end diff --git a/src/access/archive.jl b/src/access/archive.jl deleted file mode 100644 index e69de29..0000000 diff --git a/src/access/interface.jl b/src/access/interface.jl deleted file mode 100644 index e735b2b..0000000 --- a/src/access/interface.jl +++ /dev/null @@ -1,40 +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 - -@doc raw""" - access() - -Returns an Index pointer for the library index. -""" -function access end - -@doc raw""" - has_collection(index::Index, code::Symbol) - has_collection(index::Index, collection::Collection) -""" -function has_collection end \ No newline at end of file diff --git a/src/build.jl b/src/build.jl deleted file mode 100644 index 05e2c54..0000000 --- a/src/build.jl +++ /dev/null @@ -1,25 +0,0 @@ -function build(path::AbstractString = root_path(); clear_cache::Bool=false) - @info "Building QUBOLib v$(QUBOLib.__VERSION__)" - - if clear_cache - @info "Clearing Cache" - - rm(QUBOLib.cache_path(path); force=true, recursive=true) - end - - @info "Retrieving Library Index" - - QUBOLib.load_index(path; create=true) do index - for code in QUBOLib.COLLECTIONS - if !QUBOLib.has_collection(index, code) - QUBOLib.build!(index, code) - end - end - end -end - -function build!(index::LibraryIndex, code::Symbol) - @info "Building Collection: '$code'" - - return build!(index, Collection(code)) -end diff --git a/src/collection.jl b/src/collection.jl deleted file mode 100644 index 9e4528d..0000000 --- a/src/collection.jl +++ /dev/null @@ -1,8 +0,0 @@ -@doc raw""" - Collection(code::Symbol) - -This wrapper type is used to allow for dispatch on the collection code. -""" -struct Collection{code} - Collection(code::Symbol) = new{code}() -end diff --git a/src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl b/src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl deleted file mode 100644 index 26250e7..0000000 --- a/src/collections/arXiv_1903_10928_3r3x/arXiv_1903_10928_3r3x.jl +++ /dev/null @@ -1,5 +0,0 @@ -function load!(index::LibraryIndex, ::Collection{:arXiv_1903_10928_3r3x}) - - - return nothing -end \ No newline at end of file diff --git a/src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl b/src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl deleted file mode 100644 index 1743633..0000000 --- a/src/collections/arXiv_1903_10928_5r5x/arXiv_1903_10928_5r5x.jl +++ /dev/null @@ -1 +0,0 @@ -function load!( ::Collection{:arXiv_1903_10928_5r5x}) \ No newline at end of file diff --git a/src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl b/src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl deleted file mode 100644 index e1ad1a3..0000000 --- a/src/collections/arXiv_2103_08464_3r3x/arXiv_2103_08464_3r3x.jl +++ /dev/null @@ -1 +0,0 @@ -Collection{:arXiv_2103_08464_3r3x} \ No newline at end of file diff --git a/src/database.jl b/src/database.jl deleted file mode 100644 index d68402a..0000000 --- a/src/database.jl +++ /dev/null @@ -1,75 +0,0 @@ -function _load_database(path::AbstractString) - if !isfile(path) - return nothing - else - return SQLite.DB(path) - end -end - -function _create_database(path::AbstractString) - # Remove file if it exists - rm(path; force=true) - - db = SQLite.DB(path) - - # Enable Foreign keys - DBInterface.execute(db, "PRAGMA foreign_keys = ON;") - - # Collections - DBInterface.execute(db, "DROP TABLE IF EXISTS collections;") - DBInterface.execute( - db, - """ - CREATE TABLE collections ( - collection TEXT PRIMARY KEY, -- Collection identifier - author TEXT , -- Author - description TEXT , -- Description - date DATETIME , -- Date of creation - url TEXT -- URL - ); - """ - ) - - # Instances - DBInterface.execute(db, "DROP TABLE IF EXISTS instances;") - DBInterface.execute( - db, - """ - CREATE TABLE instances ( - instance INTEGER PRIMARY KEY, -- Instance identifier - collection TEXT NOT NULL , -- Collection identifier - dimension INTEGER NOT NULL , -- Number of variables - min REAL , -- Minimum coefficient value - max REAL , -- Maximum coefficient value - abs_min REAL , -- Minimum absolute coefficient value - abs_max REAL , -- Maximum absolute coefficient 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) - ); - """ - ) - - # Solutions - DBInterface.execute(db, "DROP TABLE IF EXISTS solutions;") - DBInterface.execute( - db, - """ - CREATE TABLE solutions ( - solution INTEGER PRIMARY KEY, -- Solution identifier - instance TEXT NOT NULL , -- Instance identifier - vector TEXT NOT NULL , -- Solution state - - FOREIGN KEY (instance) REFERENCES instances (instance) - ); - """ - ) - - return db -end diff --git a/src/index.jl b/src/index.jl index 15bb85b..03b606b 100644 --- a/src/index.jl +++ b/src/index.jl @@ -1,14 +1,14 @@ +include("index/database.jl") +include("index/archive.jl") + +@doc raw""" + LibraryIndex + +The QUBOLib index is composed of two parts: a SQLite database and an HDF5 archive. +""" struct LibraryIndex db::SQLite.DB fp::HDF5.File - - collections::Vector{Symbol} - - metadata::Dict{String,Any} -end - -function LibraryIndex(db::SQLite.DB, fp::HDF5.File) - return LibraryIndex(db, fp, Symbol[], Dict{String,Any}()) end function Base.isopen(index::LibraryIndex) @@ -65,18 +65,7 @@ function load_index(callback::Function, path::AbstractString=qubolib_path(); cre end end -function has_collection(index::LibraryIndex, code::Symbol) - @assert isopen(index) - - df = DBInterface.execute( - index.db, - "SELECT COUNT(*) FROM collections WHERE collection = ?", - (code,) - ) |> DataFrame - - return only(df[!, 1]) > 0 -end - -function has_collection(index::LibraryIndex, ::Collection{code}) where {code} - return has_collection(index, code) -end +include("index/collections.jl") +include("index/instances.jl") +include("index/solvers.jl") +include("index/solutions.jl") diff --git a/src/archive.jl b/src/index/archive.jl similarity index 100% rename from src/archive.jl rename to src/index/archive.jl diff --git a/src/index/collection-data.schema.json b/src/index/collection-data.schema.json new file mode 100644 index 0000000..bbf4020 --- /dev/null +++ b/src/index/collection-data.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/index/collections.jl b/src/index/collections.jl new file mode 100644 index 0000000..ea8e68e --- /dev/null +++ b/src/index/collections.jl @@ -0,0 +1,64 @@ +const COLLECTION_DATA_SCHEMA = JSONSchema.Schema( + JSON.parsefile(joinpath(@__DIR__, "collection-data.schema.json")) +) + +function has_collection(index::LibraryIndex, code::Symbol) + @assert isopen(index) + + df = DBInterface.execute( + index.db, + "SELECT COUNT(*) FROM collections WHERE collection = ?", + (code,) + ) |> DataFrame + + return only(df[!, 1]) > 0 +end + +function add_collection!( + index::LibraryIndex, + code::Symbol, + data::Dict{String,Any} +) + @assert isopen(index) + + let report = JSONSchema.validate(data, COLLECTION_DATA_SCHEMA) + if !isnothing(report) + error("Invalid collection data:\n$report") + end + end + + if has_collection(index, code) + error("Collection '$code' already exists") + else + DBInterface.execute( + index.db, + "INSERT INTO collections (collection, name) VALUES (?, ?)", + ( + code, + data["name"], + ) + ) + + @info "Collection '$code' added to index" + end + + 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 = ?", + (code,) + ) + + @info "Collection '$code' removed from index" + end + + return nothing +end diff --git a/src/index/database.jl b/src/index/database.jl new file mode 100644 index 0000000..10c9823 --- /dev/null +++ b/src/index/database.jl @@ -0,0 +1,34 @@ +const QUBOLIB_SQL_PATH = joinpath(@__DIR__, "qubolib.sql") + +function _load_database(path::AbstractString) + if !isfile(path) + return nothing + else + return SQLite.DB(path) + end +end + +function _clear_database!(db) + DBInterface.execute(db, "DROP TABLE IF EXISTS Collections;") + DBInterface.execute(db, "DROP TABLE IF EXISTS Instances;") + DBInterface.execute(db, "DROP TABLE IF EXISTS Solutions;") + DBInterface.execute(db, "DROP TABLE IF EXISTS Solvers;") + + return nothing +end + +function _create_database(path::AbstractString) + # Remove file if it exists + rm(path; force=true) + + db = SQLite.DB(path) + + # Enable Foreign keys + DBInterface.execute(db, "PRAGMA foreign_keys = ON;") + + open(QUBOLIB_SQL_PATH) do file + DBInterface.execute(db, read(file, String)) + end + + return db +end diff --git a/src/index/instances.jl b/src/index/instances.jl new file mode 100644 index 0000000..f3a65ec --- /dev/null +++ b/src/index/instances.jl @@ -0,0 +1,38 @@ +function add_instance!( + index::LibraryIndex, + coll::Symbol, + model::QUBOTools.Model{Int,Float64,Int}, +) + @assert isopen(index) + + DBInterface.execute( + index.db, + """ + INSERT INTO instances ( + collection, + dimension, + ) + VALUES ( + ?, + ? + ) + """, + (string(coll), QUBOTools.dimension(model)), + ) + + i = string(DBInterface.lastrowid(index.db)) + g = HDF5.create_group(index.h5["instances"], i) + + QUBOTools.write_model(g, model, QUBOTools.QUBin()) + + return nothing +end + +function get_instance(index::LibraryIndex, i::Integer) + @assert isopen(index) + + i = string(i) + g = HDF5.open(index.h5["instances"], i) + + return QUBOTools.read_model(g, QUBOTools.QUBin()) +end diff --git a/src/index/qubolib.sql b/src/index/qubolib.sql new file mode 100644 index 0000000..7e9f576 --- /dev/null +++ b/src/index/qubolib.sql @@ -0,0 +1,63 @@ + +CREATE TABLE Collections +( + collection TEXT NOT NULL, + name TEXT NOT NULL, + author TEXT NULL , + year INTEGER NULL , + description TEXT NULL , + url TEXT NULL , + PRIMARY KEY (collection) +); + +CREATE TABLE Instances +( + instance INTEGER NOT NULL, + collection TEXT 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, + PRIMARY KEY (instance) +); + +CREATE TABLE Solutions +( + solution INTEGER NOT NULL, + instance INTEGER NOT NULL, + solver TEXT NULL , + value REAL NOT NULL, + optimal BOOLEAN NOT NULL, + PRIMARY KEY (solution) +); + +CREATE TABLE Solvers +( + solver TEXT NOT NULL, + quantum BOOLEAN NOT NULL DEFAULT FALSE, + PRIMARY KEY (solver) +); + +ALTER TABLE Instances + ADD CONSTRAINT FK_Collections_TO_Instances + FOREIGN KEY (collection) + REFERENCES Collections (collection) + ON DELETE CASCADE; + +ALTER TABLE Solutions + ADD CONSTRAINT FK_Instances_TO_Solutions + FOREIGN KEY (instance) + REFERENCES Instances (instance) + ON DELETE CASCADE; + +ALTER TABLE Solutions + ADD CONSTRAINT FK_Solvers_TO_Solutions + FOREIGN KEY (solver) + REFERENCES Solvers (solver); diff --git a/src/index/solutions.jl b/src/index/solutions.jl new file mode 100644 index 0000000..0437905 --- /dev/null +++ b/src/index/solutions.jl @@ -0,0 +1,34 @@ +function add_solution!(index::LibraryIndex, instance::Integer, sol::SampleSet{Float64,Int}) + @assert isopen(index) + @assert !isempty(sol) + + data = QUBOTools.metadata(sol) + + if !haskey(data, "solver") + data["solver"] = "unknown" + end + + DBInterface.execute( + index.db, + """ + INSERT INTO solutions ( + instance, + value, + solver, + ) + VALUES ( + ?, + ?, + ? + ) + """, + (instance, QUBOTools.value(sol[begin]), sol.num_occurrences[1]), + ) + + i = string(DBInterface.lastrowid(index.db)) + g = HDF5.create_group(index.h5["solutions"], i) + + QUBOTools.write_sampleset(g, sol, QUBOTools.QUBin()) + + return nothing +end diff --git a/src/index/solvers.jl b/src/index/solvers.jl new file mode 100644 index 0000000..622e34d --- /dev/null +++ b/src/index/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::String, version::String, desc::String) + @assert isopen(index) + + DBInterface.execute( + index.db, + """ + INSERT INTO solvers ( + solver, + version, + description, + ) + VALUES ( + ?, + ?, + ? + ) + """, + (name, version, desc), + ) + + 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/interface.jl b/src/interface.jl new file mode 100644 index 0000000..54cd16e --- /dev/null +++ b/src/interface.jl @@ -0,0 +1,22 @@ +@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""" + 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""" + add_solution!(index::LibraryIndex, instance::Integer, sol::SampleSet{Float64,Int}) + +Adds a new solution to the library index. + +The `sol` argument is a sample set, which is a collection of samples and their respective energies. +""" +function add_solution! end diff --git a/src/metadata.jl b/src/metadata.jl deleted file mode 100644 index b1daf91..0000000 --- a/src/metadata.jl +++ /dev/null @@ -1,33 +0,0 @@ -function metadata_schema_path() - return abspath(@__DIR__, "metadata.schema.json") -end - -function metadata_schema() - return JSONSchema.Schema(JSON.parsefile(metadata_schema_path())) -end - -function validate_metadata(data::Dict{String, Any}) - report = JSONSchema.validate(metadata_schema(), data) - - if !isnothing(report) - error( - """ - Invalid collection metadata for $(collection): - $(report) - """ - ) - end - - return nothing -end - -function get_metadata(index::Index, collection::AbstractString; validate::Bool=true) - metapath = joinpath(index.list_path, collection, "metadata.json") - metadata = JSON.parsefile(metapath) - - if validate - validate_metadata(metadata) - end - - return metadata -end \ No newline at end of file diff --git a/src/metadata.schema.json b/src/metadata.schema.json deleted file mode 100644 index fba5f53..0000000 --- a/src/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 From 26abcd7fc5d7633bdb3d3635da26dcf098851fa8 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Mon, 26 Feb 2024 13:37:00 -0300 Subject: [PATCH 04/23] Add qplib --- scripts/build/qplib.jl | 196 +++++++++++++++++++++++++++++---------- src/access/load.jl | 4 +- src/index.jl | 16 ++-- src/index/archive.jl | 7 +- src/index/collections.jl | 4 +- src/index/database.jl | 7 +- src/index/instances.jl | 59 ++++++++++-- src/index/qubolib.sql | 65 +++++-------- src/index/solutions.jl | 52 ++++++++--- src/logo.jl | 10 +- 10 files changed, 285 insertions(+), 135 deletions(-) diff --git a/scripts/build/qplib.jl b/scripts/build/qplib.jl index 06a07f2..1dc2031 100644 --- a/scripts/build/qplib.jl +++ b/scripts/build/qplib.jl @@ -7,37 +7,91 @@ function _read_qplib_model(path::AbstractString) 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 _read_qplib_model(io::IO) # Read the header - code = readline(io) + code = _read_qplib_line(io) - @assert !isnothing(match(r"(QBB)", readline(io))) + @assert !isnothing(match(r"(QBB)", _read_qplib_line(io))) - sense = readline(io) + sense = _read_qplib_line(io) @assert sense ∈ ("minimize", "maximize") - nv = parse(Int, readline(io)) # number of variables + # 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}() - nq = parse(Int, readline(io)) # number of quadratic terms in objective + # 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 - m = match(r"([0-9]+)\s+([0-9+])\s+(\S+)", readline(io)) - i = parse(Int, m[1]) - j = parse(Int, m[2]) - c = parse(Float64, m[3]) + 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 - Q[(i, j)] = c + i = parse(Int, m[1]) + j = parse(Int, m[2]) + c = parse(Float64, m[3]) + + Q[(i, j)] = c + end end - dl = parse(Float64, readline(io)) # default value for linear coefficients in objective - ln = parse(Int, readline(io)) # number of non-default linear coefficients in objective + # 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) @@ -48,24 +102,35 @@ function _read_qplib_model(io::IO) end for _ = 1:ln - m = match(r"([0-9]+)\s+(\S+)", readline(io)) - i = parse(Int, m[1]) - c = parse(Float64, m[2]) + let + line = _read_qplib_line(io) + m = match(r"([0-9]+)\s+(\S+)", line) + + if isnothing(m) + QUBOTools.syntax_error("Invalid linear coefficient: $(line)") - L[i] = c + return nothing + end + + i = parse(Int, m[1]) + c = parse(Float64, m[2]) + + L[i] = c + end end - β = parse(Float64, readline(io)) # objective constant + β = parse(Float64, _read_qplib_line(io)) # objective constant - @assert isfinite(parse(Float64, readline(io))) # value for infinity - @assert isfinite(parse(Float64, readline(io))) # default variable primal value in starting point - @assert iszero(parse(Int, readline(io))) # number of non-default variable primal values in starting point + ∞ = _read_qplib_line(io) # value for infinity - @assert isfinite(parse(Float64, readline(io))) # default variable bound dual value in starting point - @assert iszero(parse(Int, readline(io))) # number of non-default variable bound dual values in starting point + @assert _read_qplib_float(io, ∞) 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 iszero(parse(Int, readline(io))) # number of non-default variable names - @assert iszero(parse(Int, readline(io))) # number of non-default constraint names + @assert _read_qplib_float(io, ∞) 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, @@ -73,28 +138,32 @@ function _read_qplib_model(io::IO) Q; offset = β, domain = :bool, - sense = (sense == "minimize") ? :min : :max, + sense = (sense == "minimize") ? :min : :max, description = "QPLib instance '$code'", ) end -function _read_qplib_solution(path::AbstractString, model::QUBOTools.Model{Int,Float64,Int}) - open(path, "r") do io - _read_qplib_solution!(io, model) +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 - - return nothing end -function _read_qplib_solution!(io::IO, model::QUBOTools.Model{Int,Float64,Int}) +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") - - return nothing end tryparse(Float64, m[1]) @@ -113,8 +182,6 @@ function _read_qplib_solution!(io::IO, model::QUBOTools.Model{Int,Float64,Int}) if isnothing(m) QUBOTools.syntax_error("Invalid solution input") - - return nothing end (tryparse(Int, m[1]), tryparse(Float64, m[2])) @@ -122,11 +189,9 @@ function _read_qplib_solution!(io::IO, model::QUBOTools.Model{Int,Float64,Int}) if isnothing(i) || isnothing(x) QUBOTools.syntax_error("Invalid variable assignment") - - return nothing end - ψ[i] = ifelse(x > 0, 1, 0) + ψ[var_map[i]] = ifelse(x > 0, 1, 0) end s = QUBOTools.Sample{Float64,Int}[QUBOTools.Sample{Float64,Int}(ψ, λ)] @@ -149,6 +214,24 @@ function _is_qplib_qubo(path::AbstractString) 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::LibraryIndex) @@ -174,19 +257,26 @@ function build_qplib!(index::LibraryIndex) @info "[qplib] Building index" - qplib_data_path = abspath(cache_path(), "qplib", "data") + qplib_data_path = abspath(QUBOLib.cache_path(), "qplib", "data") for code in code_list mod_path = joinpath(qplib_data_path, "$(code).qplib") + var_path = joinpath(qplib_data_path, "$(code).lp") sol_path = joinpath(qplib_data_path, "$(code).sol") model = _read_qplib_model(mod_path) - mod_i = QUBOLib.add_instance!(index, model, :qplib) + mod_i = QUBOLib.add_instance!(index, :qplib, model) if isfile(sol_path) - sol = _read_qplib_solution(sol_path, model) + var_map = _get_qplib_var_map(var_path) - QUBOLib.add_solution!(index, mod_i, sol) + 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 @@ -198,7 +288,7 @@ end function _load_qplib!() @assert Sys.isunix() "Processing QPLIB is only possible on Unix systems" - qplib_cache_path = mkpath(abspath(cache_path(), "qplib")) + qplib_cache_path = mkpath(abspath(QUBOLib.cache_path(), "qplib")) qplib_data_path = mkpath(abspath(qplib_cache_path, "data")) qplib_zip_path = abspath(qplib_cache_path, "qplib.zip") @@ -215,9 +305,14 @@ function _load_qplib!() @info "[qplib] Extracting archive" - run( - `unzip -qq -o -j $qplib_zip_path 'qplib/html/qplib/*' 'qplib/html/sol/*' -d $qplib_data_path`, - ) + run(``` + unzip -qq -o -j + $qplib_zip_path + 'qplib/html/qplib/*' + 'qplib/html/sol/*' + 'qplib/html/lp/*' + -d $qplib_data_path + ```) # Remove non-QUBO instances @info "[qplib] Removing non-QUBO instances" @@ -225,13 +320,14 @@ function _load_qplib!() code_list = String[] for file_path in filter(endswith(".qplib"), readdir(qplib_data_path; join = true)) - if !_is_qplib_qubo(file_path) - code = readline(file_path) - - push!(code_list, code) + code = readline(file_path) + if !_is_qplib_qubo(file_path) rm(joinpath(qplib_data_path, "$(code).qplib"); force = true) + rm(joinpath(qplib_data_path, "$(code).lp"); force = true) rm(joinpath(qplib_data_path, "$(code).sol"); force = true) + else + push!(code_list, code) end end diff --git a/src/access/load.jl b/src/access/load.jl index 8702259..fc0b02f 100644 --- a/src/access/load.jl +++ b/src/access/load.jl @@ -3,7 +3,7 @@ function load_instance(collection::AbstractString, instance::AbstractString) 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()) + return archive(path) do h5 + return QUBOTools.read_model(h5["collections"][collection][instance], QUBOTools.QUBin()) end end diff --git a/src/index.jl b/src/index.jl index 03b606b..07b6412 100644 --- a/src/index.jl +++ b/src/index.jl @@ -8,25 +8,25 @@ The QUBOLib index is composed of two parts: a SQLite database and an HDF5 archiv """ struct LibraryIndex db::SQLite.DB - fp::HDF5.File + h5::HDF5.File end function Base.isopen(index::LibraryIndex) - return isopen(index.db) && isopen(index.fp) + return isopen(index.db) && isopen(index.h5) end function Base.close(index::LibraryIndex) close(index.db) - close(index.fp) + close(index.h5) return nothing end function _create_index(path::AbstractString) db = _create_database(database_path(path)) - fp = _create_archive(archive_path(path)) + h5 = _create_archive(archive_path(path)) - return LibraryIndex(db, fp) + return LibraryIndex(db, h5) end @doc raw""" @@ -36,9 +36,9 @@ Loads the library index from the given path. """ function load_index(path::AbstractString; create::Bool=false) db = _load_database(database_path(path)) - fp = _load_archive(archive_path(path)) + h5 = _load_archive(archive_path(path)) - if isnothing(db) || isnothing(fp) + if isnothing(db) || isnothing(h5) if create @info "Creating index at '$path'" @@ -50,7 +50,7 @@ function load_index(path::AbstractString; create::Bool=false) end end - return LibraryIndex(db, fp) + return LibraryIndex(db, h5) end function load_index(callback::Function, path::AbstractString=qubolib_path(); create::Bool=false) diff --git a/src/index/archive.jl b/src/index/archive.jl index 4046909..ae04b32 100644 --- a/src/index/archive.jl +++ b/src/index/archive.jl @@ -9,9 +9,10 @@ end function _create_archive(path::AbstractString) rm(path; force=true) # remove file if it exists - fp = HDF5.h5open(path, "w") + h5 = HDF5.h5open(path, "w") - HDF5.create_group(fp, "collections") + HDF5.create_group(h5, "instances") + HDF5.create_group(h5, "solutions") - return fp + return h5 end diff --git a/src/index/collections.jl b/src/index/collections.jl index ea8e68e..a49d8fc 100644 --- a/src/index/collections.jl +++ b/src/index/collections.jl @@ -34,7 +34,7 @@ function add_collection!( index.db, "INSERT INTO collections (collection, name) VALUES (?, ?)", ( - code, + string(code), data["name"], ) ) @@ -54,7 +54,7 @@ function remove_collection!(index::LibraryIndex, code::Symbol) DBInterface.execute( index.db, "DELETE FROM collections WHERE code = ?", - (code,) + (string(code),) ) @info "Collection '$code' removed from index" diff --git a/src/index/database.jl b/src/index/database.jl index 10c9823..fc492ca 100644 --- a/src/index/database.jl +++ b/src/index/database.jl @@ -23,11 +23,12 @@ function _create_database(path::AbstractString) db = SQLite.DB(path) - # Enable Foreign keys - DBInterface.execute(db, "PRAGMA foreign_keys = ON;") + @info "Creating tables" open(QUBOLIB_SQL_PATH) do file - DBInterface.execute(db, read(file, String)) + for stmt in (split(read(file, String), ";") .|> strip |> filter(!isempty)) + DBInterface.execute(db, stmt) + end end return db diff --git a/src/index/instances.jl b/src/index/instances.jl index f3a65ec..b9ca926 100644 --- a/src/index/instances.jl +++ b/src/index/instances.jl @@ -2,30 +2,69 @@ function add_instance!( index::LibraryIndex, coll::Symbol, model::QUBOTools.Model{Int,Float64,Int}, -) +)::Integer @assert isopen(index) - DBInterface.execute( + L = map(last, QUBOTools.linear_terms(model)) + Q = map(last, QUBOTools.quadratic_terms(model)) + + q = DBInterface.execute( index.db, """ - INSERT INTO instances ( - collection, - dimension, + 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(coll), QUBOTools.dimension(model)), + ( + string(coll), + 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 = string(DBInterface.lastrowid(index.db)) - g = HDF5.create_group(index.h5["instances"], i) + i = DBInterface.lastrowid(q) + g = HDF5.create_group(index.h5["instances"], string(i)) QUBOTools.write_model(g, model, QUBOTools.QUBin()) - return nothing + return i end function get_instance(index::LibraryIndex, i::Integer) diff --git a/src/index/qubolib.sql b/src/index/qubolib.sql index 7e9f576..2997ef4 100644 --- a/src/index/qubolib.sql +++ b/src/index/qubolib.sql @@ -1,41 +1,43 @@ +PRAGMA foreign_keys = ON; CREATE TABLE Collections ( - collection TEXT NOT NULL, - name TEXT NOT NULL, - author TEXT NULL , - year INTEGER NULL , - description TEXT NULL , - url TEXT NULL , - PRIMARY KEY (collection) + collection TEXT PRIMARY KEY, + name TEXT NOT NULL , + author TEXT NULL , + year INTEGER NULL , + description TEXT NULL , + url TEXT NULL ); CREATE TABLE Instances ( - instance INTEGER NOT NULL, - collection TEXT 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, - PRIMARY KEY (instance) + 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 NOT NULL, + solution INTEGER PRIMARY KEY, instance INTEGER NOT NULL, solver TEXT NULL , value REAL NOT NULL, optimal BOOLEAN NOT NULL, - PRIMARY KEY (solution) + FOREIGN KEY (instance) REFERENCES Instances (instance) ON DELETE CASCADE, + FOREIGN KEY (solver) REFERENCES Solvers (solver) ); CREATE TABLE Solvers @@ -44,20 +46,3 @@ CREATE TABLE Solvers quantum BOOLEAN NOT NULL DEFAULT FALSE, PRIMARY KEY (solver) ); - -ALTER TABLE Instances - ADD CONSTRAINT FK_Collections_TO_Instances - FOREIGN KEY (collection) - REFERENCES Collections (collection) - ON DELETE CASCADE; - -ALTER TABLE Solutions - ADD CONSTRAINT FK_Instances_TO_Solutions - FOREIGN KEY (instance) - REFERENCES Instances (instance) - ON DELETE CASCADE; - -ALTER TABLE Solutions - ADD CONSTRAINT FK_Solvers_TO_Solutions - FOREIGN KEY (solver) - REFERENCES Solvers (solver); diff --git a/src/index/solutions.jl b/src/index/solutions.jl index 0437905..9f4c0bb 100644 --- a/src/index/solutions.jl +++ b/src/index/solutions.jl @@ -1,34 +1,62 @@ -function add_solution!(index::LibraryIndex, instance::Integer, sol::SampleSet{Float64,Int}) +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::SampleSet{Float64,Int})::Integer @assert isopen(index) @assert !isempty(sol) + if isempty(sol) + return nothing + end + data = QUBOTools.metadata(sol) - if !haskey(data, "solver") - data["solver"] = "unknown" - end + solver = get(data, "solver", nothing) + value = QUBOTools.value(sol, 1) + optimal = get(data, "status", nothing) == "optimal" - DBInterface.execute( + q = DBInterface.execute( index.db, """ - INSERT INTO solutions ( + INSERT INTO Solutions ( instance, - value, solver, + value, + optimal ) VALUES ( + ?, ?, ?, ? ) """, - (instance, QUBOTools.value(sol[begin]), sol.num_occurrences[1]), + ( + instance, + solver, + value, + optimal, + ) ) - i = string(DBInterface.lastrowid(index.db)) - g = HDF5.create_group(index.h5["solutions"], i) + i = DBInterface.lastrowid(q) + g = HDF5.create_group(index.h5["solutions"], string(i)) - QUBOTools.write_sampleset(g, sol, QUBOTools.QUBin()) + _write_solution(g, sol, QUBOTools.QUBin()) - return nothing + return i end diff --git a/src/logo.jl b/src/logo.jl index c24ca40..3e0e2db 100644 --- a/src/logo.jl +++ b/src/logo.jl @@ -5,11 +5,11 @@ const LOGO = """ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ██████ ██████ ██████ ██████ ▀▀ -██ ██ ██ -██ ██ -██ ██ ██████ -██ ██ ██ ██ -███████ ██ ██████ v$(__VERSION__) + ██ ██ ██ + ██ ██ + ██ ██ ██████ + ██ ██ ██ ██ + ███████ ██ ██████ v$(__VERSION__) """ function logo() From 46ce132ef5ed2e39053b0a8386472360d5dde635 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Tue, 27 Feb 2024 02:10:05 -0300 Subject: [PATCH 05/23] Add `.CondaPkg` to `.gitignore` --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 9e17a33..2ecd02d 100644 --- a/.gitignore +++ b/.gitignore @@ -25,3 +25,6 @@ Manifest.toml # Distribution Files dist/ + +# PythonCall +.CondaPkg \ No newline at end of file From 8fb2ca213c45ff80ebf86546d00a7e0b9035b519 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Tue, 27 Feb 2024 02:11:23 -0300 Subject: [PATCH 06/23] Add `run!` script --- Project.toml | 3 ++ docs/make.jl | 13 ++++---- scripts/{ => build}/build.jl | 2 +- scripts/run.jl | 7 ---- scripts/run/Project.toml | 9 ++++++ scripts/run/run.jl | 41 +++++++++++++++++++++++ src/QUBOLib.jl | 7 ++-- src/access/database.jl | 7 ---- src/access/list.jl | 13 -------- src/access/load.jl | 9 ------ src/index.jl | 12 +++++++ src/index/archive.jl | 2 +- src/index/instances.jl | 7 ++-- src/interface.jl | 13 ++++++++ src/run.jl | 63 ++++++++++++++++++++++++++++++++++++ 15 files changed, 156 insertions(+), 52 deletions(-) rename scripts/{ => build}/build.jl (97%) delete mode 100644 scripts/run.jl create mode 100644 scripts/run/Project.toml create mode 100644 scripts/run/run.jl delete mode 100644 src/access/database.jl delete mode 100644 src/access/list.jl delete mode 100644 src/access/load.jl create mode 100644 src/run.jl diff --git a/Project.toml b/Project.toml index eec1626..d00cfd5 100644 --- a/Project.toml +++ b/Project.toml @@ -9,13 +9,16 @@ 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" +QUBODrivers = "a3f166f7-2cd3-47b6-9e1e-6fbfe0449eb0" QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" 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" diff --git a/docs/make.jl b/docs/make.jl index 576414d..fb5c242 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -6,17 +6,18 @@ using QUBOLib DocMeta.setdocmeta!(QUBOLib, :DocTestSetup, :(using QUBOLib); recursive = true) 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 = [ + pages = [ "Home" => "index.md", "API" => "api.md", "Manual" => [ @@ -29,7 +30,7 @@ makedocs(; "Library Design" => "booklet/1-design.md", ], ], - workdir = @__DIR__, + workdir = @__DIR__, ) if "--skip-deploy" ∈ ARGS diff --git a/scripts/build.jl b/scripts/build/build.jl similarity index 97% rename from scripts/build.jl rename to scripts/build/build.jl index 8ae7f31..a1c2210 100644 --- a/scripts/build.jl +++ b/scripts/build/build.jl @@ -2,7 +2,7 @@ using QUBOTools using QUBOLib # Standard Library -include("build/qplib.jl") +include("qplib.jl") function build_standard_qubolib( path::AbstractString = root_path(); diff --git a/scripts/run.jl b/scripts/run.jl deleted file mode 100644 index 06ca7d7..0000000 --- a/scripts/run.jl +++ /dev/null @@ -1,7 +0,0 @@ -function run(instances, optimizers) - for instance in instances - for optimizer in optimizers - println("Running $(instance) with $(optimizer)") - end - end -end diff --git a/scripts/run/Project.toml b/scripts/run/Project.toml new file mode 100644 index 0000000..759b20b --- /dev/null +++ b/scripts/run/Project.toml @@ -0,0 +1,9 @@ +[deps] +DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" +DWave = "4d534982-bf11-4157-9e48-fe3a62208a50" +DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" +InfinityQ = "f014c839-c17b-44be-a366-f421804f8372" +JuMP = "4076af6c-e467-56ae-b986-b466b2749572" +MQLib = "16f11440-1623-44c9-850c-358a6c72f3c9" +PySA = "46b73cfa-376a-48ee-8926-0c45ac3f7830" +QUBOLib = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" diff --git a/scripts/run/run.jl b/scripts/run/run.jl new file mode 100644 index 0000000..c5e6fe6 --- /dev/null +++ b/scripts/run/run.jl @@ -0,0 +1,41 @@ +using QUBOLib +using JuMP +using DataFrames +using DBInterface + +# Solvers +using DWave +using MQLib +using PySA +using InfinityQ + +function main() + QUBOLib.logo() + + QUBOLib.load_index(QUBOLib.root_path(); create = false) do index + df = DBInterface.execute( + QUBOLib.database(index), + "SELECT instance FROM Instances WHERE linear_density > 0.8;" + ) |> DataFrame + + codes = collect(Int, df[!, :instance]) + + @info "Running DWave Neal" + QUBOLib.run!(index, DWave.Neal.Optimizer, codes; solver = Symbol("dwave-neal")) + + @info "Running MQLib" + QUBOLib.run!(index, MQLib.Optimizer, codes; solver = :mqlib) do model + JuMP.set_Attribute(model, "heuristic", "ALKHAMIS1998") + end + + @info "Running PySA" + QUBOLib.run!(index, PySA.Optimizer, codes; solver = :pysa) + + @info "Running InfinityQ" + QUBOLib.run!(index, InfinityQ.Optimizer, codes; solver = :infinityq) + end + + return nothing +end + +main() # Here we go! diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index ac4e29b..8a4e647 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -14,6 +14,9 @@ using TOML using Pkg using UUIDs using QUBOTools +using QUBODrivers +using JuMP +using SparseArrays using ProgressMeter const __PROJECT__ = abspath(@__DIR__, "..") @@ -25,8 +28,6 @@ include("logo.jl") include("path.jl") include("interface.jl") include("index.jl") - -# Collections -# include("collections/qplib.jl") +include("run.jl") end # module QUBOLib diff --git a/src/access/database.jl b/src/access/database.jl deleted file mode 100644 index 011a829..0000000 --- a/src/access/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/access/list.jl b/src/access/list.jl deleted file mode 100644 index 2d55c68..0000000 --- a/src/access/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/access/load.jl b/src/access/load.jl deleted file mode 100644 index fc0b02f..0000000 --- a/src/access/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 h5 - return QUBOTools.read_model(h5["collections"][collection][instance], QUBOTools.QUBin()) - end -end diff --git a/src/index.jl b/src/index.jl index 07b6412..1b69308 100644 --- a/src/index.jl +++ b/src/index.jl @@ -11,6 +11,18 @@ struct LibraryIndex h5::HDF5.File end +function database(index::LibraryIndex) + @assert isopen(index) + + return index.db +end + +function archive(index::LibraryIndex) + @assert isopen(index) + + return index.h5 +end + function Base.isopen(index::LibraryIndex) return isopen(index.db) && isopen(index.h5) end diff --git a/src/index/archive.jl b/src/index/archive.jl index ae04b32..6ff203a 100644 --- a/src/index/archive.jl +++ b/src/index/archive.jl @@ -1,4 +1,4 @@ -function _load_archive(path::AbstractString, mode::AbstractString="w") +function _load_archive(path::AbstractString, mode::AbstractString="cw") if !isfile(path) return nothing else diff --git a/src/index/instances.jl b/src/index/instances.jl index b9ca926..f9968c5 100644 --- a/src/index/instances.jl +++ b/src/index/instances.jl @@ -67,11 +67,8 @@ function add_instance!( return i end -function get_instance(index::LibraryIndex, i::Integer) +function load_instance(index::LibraryIndex, i::Integer) @assert isopen(index) - i = string(i) - g = HDF5.open(index.h5["instances"], i) - - return QUBOTools.read_model(g, QUBOTools.QUBin()) + return QUBOTools.read_model(index.h5["instances"][string(i)], QUBOTools.QUBin()) end diff --git a/src/interface.jl b/src/interface.jl index 54cd16e..eb2681e 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -5,6 +5,13 @@ Creates a new collection in the library index. """ function add_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""" add_instance!(index::LibraryIndex, coll::Symbol, model::QUBOTools.Model{Int,Float64,Int}) @@ -20,3 +27,9 @@ Adds a new solution to the library index. The `sol` argument is a sample set, which is a collection of samples and their respective energies. """ function add_solution! end + +@doc raw""" + run!(index::LibraryIndex, instance::Integer, optimizer) + run!(index::LibraryIndex, instances::Vector{U}, optimizer) where {U<:Integer} +""" +function run! end diff --git a/src/run.jl b/src/run.jl new file mode 100644 index 0000000..08a5e02 --- /dev/null +++ b/src/run.jl @@ -0,0 +1,63 @@ +function warmup!(model::JuMP.Model) + Q = 2 * rand(3, 3) .- 1 + + JuMP.@variable(model, x[1:3], Bin) + JuMP.@objective(model, Min, x' * Q * x) + JuMP.optimize!(model) + + empty!(model) + + return nothing +end + +function run!( + config!::Function, + index::LibraryIndex, + optimizer, + codes::AbstractVector{U}; + solver::Union{Symbol,Nothing} = nothing, +) where {U<:Integer} + model = JuMP.Model(optimizer) + + warmup!(model) + + for i in codes + n, L, Q, α, β = QUBOTools.qubo( + QUBOLib.load_instance(index, i), + :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, i, sol) + end + end + end + + return nothing +end + +function run!(index::LibraryIndex, optimizer, codes::AbstractVector{U}; kws...) where {U<:Integer} + run!(identity, index, optimizer, codes; kws...) + + return nothing +end From 93b73d74368f9852b118dc43bdecbe467255e633 Mon Sep 17 00:00:00 2001 From: pedromxavier Date: Thu, 7 Mar 2024 18:51:53 -0300 Subject: [PATCH 07/23] Add arXiv collections --- Makefile | 15 ++++++ scripts/build/Project.toml | 3 ++ scripts/build/arXiv_1903_10928_3r3x.jl | 65 ++++++++++++++++++++++-- scripts/build/arXiv_1903_10928_5r5x.jl | 69 +++++++++++++++++++++++++- scripts/build/build.jl | 5 ++ scripts/build/qplib.jl | 10 +++- scripts/{ => deploy}/deploy.jl | 0 scripts/run/Project.toml | 2 +- scripts/run/dwave/dwave.jl | 21 ++++++++ scripts/run/mqlib/mqlib.jl | 0 scripts/run/run.jl | 16 ++++-- src/index.jl | 4 ++ src/index/collections.jl | 2 +- src/run.jl | 61 ++++++++++++++--------- 14 files changed, 237 insertions(+), 36 deletions(-) create mode 100644 Makefile create mode 100644 scripts/build/Project.toml rename scripts/{ => deploy}/deploy.jl (100%) create mode 100644 scripts/run/dwave/dwave.jl create mode 100644 scripts/run/mqlib/mqlib.jl diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..db59758 --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ + +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-build: + @julia --project=scripts/build ./scripts/build/build.jl --clear-build + +run: setup + @julia --project=scripts/run/mqlib ./scripts/run/mqlib/run.jl diff --git a/scripts/build/Project.toml b/scripts/build/Project.toml new file mode 100644 index 0000000..4f685f8 --- /dev/null +++ b/scripts/build/Project.toml @@ -0,0 +1,3 @@ +[deps] +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 index 4b54704..5e576ab 100644 --- a/scripts/build/arXiv_1903_10928_3r3x.jl +++ b/scripts/build/arXiv_1903_10928_3r3x.jl @@ -1,14 +1,73 @@ -function build_arXiv_1903_10928_3r3x!(index::LibraryIndex) +const ARXIV_1903_10928_3R3X_URL = "https://sites.usc.edu/itayhen/files/2019/09/3r3x.zip" + +function _load_arXiv_1903_10928_3r3x!() + @info "[arXiv_1903_10928_3r3x] Downloading instances" + + arXiv_1903_10928_3r3x_cache_path = mkpath(abspath(QUBOLib.cache_path(), "arXiv_1903_10928_3r3x")) + arXiv_1903_10928_3r3x_data_path = mkpath(abspath(arXiv_1903_10928_3r3x_cache_path, "data")) + arXiv_1903_10928_3r3x_zip_path = abspath(arXiv_1903_10928_3r3x_cache_path, "arXiv_1903_10928_3r3x.zip") + + # Download arXiv_1903_10928 3r3x archive + if isfile(arXiv_1903_10928_3r3x_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, arXiv_1903_10928_3r3x_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 + $arXiv_1903_10928_3r3x_zip_path + 'instance*.txt' + -d $arXiv_1903_10928_3r3x_data_path + ```) + + return nothing +end + +function build_arXiv_1903_10928_3r3x!(index::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" => "arXiv:1903_10928", - "url" => "http://qplib.zib.de/", + "name" => "arXiv_1903_10928_3r3x", + "title" => "5R5X instances for \"Equation Planting: A Tool for Benchmarking Ising Machines\"", + "author" => ["Itay Hen"], + "description" => "The Quadratic Programming Library", + "year" => 2019, + "url" => ARXIV_1903_10928_5R5X_URL, ), ) + _load_arXiv_1903_10928_3r3x!() + + arXiv_1903_10928_3r3x_data_path = abspath(QUBOLib.cache_path(), "arXiv_1903_10928_3r3x", "data") + @info "[arXiv_1903_10928_3r3x] Building index" + for path in readdir(arXiv_1903_10928_3r3x_data_path; join=true) + model = QUBOTools.read_model(path, QUBOTools.Qubist()) + mod_i = QUBOLib.add_instance!(index, :arXiv_1903_10928_3r3x, model) + + 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 index 4222e45..d24487b 100644 --- a/scripts/build/arXiv_1903_10928_5r5x.jl +++ b/scripts/build/arXiv_1903_10928_5r5x.jl @@ -1,8 +1,73 @@ -function build_arXiv_1903_10928_5r5x!(index::LibraryIndex) +const ARXIV_1903_10928_5R5X_URL = "https://sites.usc.edu/itayhen/files/2019/09/5r5x.zip" + +function _load_arXiv_1903_10928_5r5x!() + @info "[arXiv_1903_10928_5r5x] Downloading instances" + + arXiv_1903_10928_5r5x_cache_path = mkpath(abspath(QUBOLib.cache_path(), "arXiv_1903_10928_5r5x")) + arXiv_1903_10928_5r5x_data_path = mkpath(abspath(arXiv_1903_10928_5r5x_cache_path, "data")) + arXiv_1903_10928_5r5x_zip_path = abspath(arXiv_1903_10928_5r5x_cache_path, "arXiv_1903_10928_5r5x.zip") + + # Download arXiv_1903_10928 5r5x archive + if isfile(arXiv_1903_10928_5r5x_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, arXiv_1903_10928_5r5x_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 + $arXiv_1903_10928_5r5x_zip_path + 'instance*.txt' + -d $arXiv_1903_10928_5r5x_data_path + ```) + + return nothing +end + +function build_arXiv_1903_10928_5r5x!(index::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" => "arXiv_1903_10928_5r5x", + "title" => "5R5X instances for \"Equation Planting: A Tool for Benchmarking Ising Machines\"", + "author" => ["Itay Hen"], + "description" => "The Quadratic Programming Library", + "year" => 2019, + "url" => ARXIV_1903_10928_5R5X_URL, + ), + ) + + _load_arXiv_1903_10928_5r5x!() + + arXiv_1903_10928_5r5x_data_path = abspath(QUBOLib.cache_path(), "arXiv_1903_10928_5r5x", "data") + @info "[arXiv_1903_10928_5r5x] Building index" - + for path in readdir(arXiv_1903_10928_5r5x_data_path; join=true) + model = QUBOTools.read_model(path, QUBOTools.Qubist()) + mod_i = QUBOLib.add_instance!(index, :arXiv_1903_10928_5r5x, model) + if isnothing(mod_i) + @warn "[arXiv_1903_10928_5r5x] Failed to read instance '$path'" + end + end return nothing end diff --git a/scripts/build/build.jl b/scripts/build/build.jl index a1c2210..149d31f 100644 --- a/scripts/build/build.jl +++ b/scripts/build/build.jl @@ -1,8 +1,11 @@ using QUBOTools using QUBOLib +using Downloads # Standard Library include("qplib.jl") +include("arXiv_1903_10928_3r3x.jl") +include("arXiv_1903_10928_5r5x.jl") function build_standard_qubolib( path::AbstractString = root_path(); @@ -25,6 +28,8 @@ function build_standard_qubolib( QUBOLib.load_index(path; create = true) do index build_qplib!(index) + build_arXiv_1903_10928_3r3x!(index) + build_arXiv_1903_10928_5r5x!(index) end return nothing diff --git a/scripts/build/qplib.jl b/scripts/build/qplib.jl index 1dc2031..5e6ce32 100644 --- a/scripts/build/qplib.jl +++ b/scripts/build/qplib.jl @@ -234,11 +234,17 @@ end const QPLIB_URL = "http://qplib.zib.de/qplib.zip" -function build_qplib!(index::LibraryIndex) +function build_qplib!(index::LibraryIndex; cache::Bool = true) + @info "[qplib] Building QPLIB" + if QUBOLib.has_collection(index, :qplib) @info "[qplib] Collection already exists" - QUBOLib.remove_collection!(index, :qplib) + if cache + return nothing + else + QUBOLib.remove_collection!(index, :qplib) + end end QUBOLib.add_collection!( diff --git a/scripts/deploy.jl b/scripts/deploy/deploy.jl similarity index 100% rename from scripts/deploy.jl rename to scripts/deploy/deploy.jl diff --git a/scripts/run/Project.toml b/scripts/run/Project.toml index 759b20b..e15d769 100644 --- a/scripts/run/Project.toml +++ b/scripts/run/Project.toml @@ -1,6 +1,6 @@ [deps] +AIMOpt = "a160bcaa-3679-4194-bb69-dd396f88ea15" DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" -DWave = "4d534982-bf11-4157-9e48-fe3a62208a50" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" InfinityQ = "f014c839-c17b-44be-a366-f421804f8372" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" diff --git a/scripts/run/dwave/dwave.jl b/scripts/run/dwave/dwave.jl new file mode 100644 index 0000000..fb9272f --- /dev/null +++ b/scripts/run/dwave/dwave.jl @@ -0,0 +1,21 @@ +using DWave + +function main() + QUBOLib.logo() + 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) + end + + return nothing +end diff --git a/scripts/run/mqlib/mqlib.jl b/scripts/run/mqlib/mqlib.jl new file mode 100644 index 0000000..e69de29 diff --git a/scripts/run/run.jl b/scripts/run/run.jl index c5e6fe6..a0ba963 100644 --- a/scripts/run/run.jl +++ b/scripts/run/run.jl @@ -8,6 +8,7 @@ using DWave using MQLib using PySA using InfinityQ +using AIMOpt function main() QUBOLib.logo() @@ -15,7 +16,7 @@ function main() QUBOLib.load_index(QUBOLib.root_path(); create = false) do index df = DBInterface.execute( QUBOLib.database(index), - "SELECT instance FROM Instances WHERE linear_density > 0.8;" + "SELECT instance FROM Instances WHERE dimension < 100 AND quadratic_density < 0.5;" ) |> DataFrame codes = collect(Int, df[!, :instance]) @@ -23,16 +24,25 @@ function main() @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_Attribute(model, "heuristic", "ALKHAMIS1998") + JuMP.set_silent(model) + JuMP.set_attribute(model, "heuristic", "ALKHAMIS1998") end @info "Running PySA" - QUBOLib.run!(index, PySA.Optimizer, codes; solver = :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 diff --git a/src/index.jl b/src/index.jl index 1b69308..0f5fc03 100644 --- a/src/index.jl +++ b/src/index.jl @@ -72,6 +72,10 @@ function load_index(callback::Function, path::AbstractString=qubolib_path(); cre try return callback(index) + catch e + @error("Error during index access: $(sprint(showerror, e)))") + + return nothing finally close(index) end diff --git a/src/index/collections.jl b/src/index/collections.jl index a49d8fc..84a2696 100644 --- a/src/index/collections.jl +++ b/src/index/collections.jl @@ -8,7 +8,7 @@ function has_collection(index::LibraryIndex, code::Symbol) df = DBInterface.execute( index.db, "SELECT COUNT(*) FROM collections WHERE collection = ?", - (code,) + (string(code),) ) |> DataFrame return only(df[!, 1]) > 0 diff --git a/src/run.jl b/src/run.jl index 08a5e02..11d20b1 100644 --- a/src/run.jl +++ b/src/run.jl @@ -1,8 +1,11 @@ -function warmup!(model::JuMP.Model) +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) @@ -15,45 +18,55 @@ function run!( index::LibraryIndex, optimizer, codes::AbstractVector{U}; - solver::Union{Symbol,Nothing} = nothing, + kws..., ) where {U<:Integer} model = JuMP.Model(optimizer) - warmup!(model) + warmup!(config!, model) - for i in codes - n, L, Q, α, β = QUBOTools.qubo( - QUBOLib.load_instance(index, i), - :sparse; - sense = :min, - ) + for code in codes + try + run!(index, model, code; kws...) + catch e + @error "Failed to run instance '$code': $(sprint(showerror, e))" + end + end - empty!(model) + return nothing +end - x = JuMP.@variable(model, [1:n], Bin) +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, + ) - JuMP.@objective(model, Min, α * (x' * Q * x + L' * x + β)) + empty!(model) - config!(model) + x = JuMP.@variable(model, [1:n], Bin) - JuMP.optimize!(model) + JuMP.@objective(model, Min, α * (x' * Q * x + L' * x + β)) - let m = JuMP.unsafe_backend(model) - if m isa QUBODrivers.AbstractSampler - sol = QUBOTools.solution(m) + config!(model) - if !isnothing(solver) - let data = QUBOTools.metadata(sol) - data["solver"] = string(solver) - end - end + JuMP.optimize!(model) + + let m = JuMP.unsafe_backend(model) + if m isa QUBODrivers.AbstractSampler + sol = QUBOTools.solution(m) - QUBOLib.add_solution!(index, i, sol) + if !isnothing(solver) + let data = QUBOTools.metadata(sol) + data["solver"] = string(solver) + end end + + QUBOLib.add_solution!(index, code, sol) end end - return nothing + return model end function run!(index::LibraryIndex, optimizer, codes::AbstractVector{U}; kws...) where {U<:Integer} From 56d663e34668ef44e240936e4062315e46b76a1a Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier <31925649+pedromxavier@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:54:23 -0300 Subject: [PATCH 08/23] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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 From 4e45a8795df46a5da56084530fa4aa686af9abaf Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier <31925649+pedromxavier@users.noreply.github.com> Date: Wed, 3 Apr 2024 00:56:58 -0300 Subject: [PATCH 09/23] Update logo.svg --- docs/src/assets/logo.svg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index b883a93..675fddb 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -27,7 +27,7 @@ - + @@ -50,6 +50,6 @@ - + From 71b0ebb054de48cb1abcba621a87b4b901911783 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Thu, 19 Sep 2024 14:21:47 -0400 Subject: [PATCH 10/23] Integrate instance synthesis --- Project.toml | 2 + scripts/{run => }/Project.toml | 4 +- scripts/build/Project.toml | 3 - scripts/build/build.jl | 7 +-- scripts/build/qplib.jl | 4 +- scripts/clear/clear.jl | 3 + scripts/deploy/deploy.jl | 2 +- scripts/main.jl | 51 ++++++++++++++++ scripts/run/mqlib/mqlib.jl | 0 scripts/run/run.jl | 14 ++--- src/QUBOLib.jl | 13 +++- src/{ => index}/index.jl | 12 ++-- src/interface.jl | 13 ++++ src/logo.jl | 24 ++++---- src/synthesis/abstract.jl | 3 + src/synthesis/sherrington_kirkpatrick.jl | 64 ++++++++++++++++++++ src/synthesis/wishart.jl | 76 ++++++++++++++++++++++++ test/Project.toml | 2 + test/runtests.jl | 9 ++- test/synthesis.jl | 46 ++++++++++++++ 20 files changed, 311 insertions(+), 41 deletions(-) rename scripts/{run => }/Project.toml (74%) delete mode 100644 scripts/build/Project.toml create mode 100644 scripts/clear/clear.jl create mode 100644 scripts/main.jl delete mode 100644 scripts/run/mqlib/mqlib.jl rename src/{ => index}/index.jl (91%) create mode 100644 src/synthesis/abstract.jl create mode 100644 src/synthesis/sherrington_kirkpatrick.jl create mode 100644 src/synthesis/wishart.jl create mode 100644 test/synthesis.jl diff --git a/Project.toml b/Project.toml index d00cfd5..ccf9147 100644 --- a/Project.toml +++ b/Project.toml @@ -15,8 +15,10 @@ 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" QUBODrivers = "a3f166f7-2cd3-47b6-9e1e-6fbfe0449eb0" 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" diff --git a/scripts/run/Project.toml b/scripts/Project.toml similarity index 74% rename from scripts/run/Project.toml rename to scripts/Project.toml index e15d769..a9406b6 100644 --- a/scripts/run/Project.toml +++ b/scripts/Project.toml @@ -1,9 +1,9 @@ [deps] -AIMOpt = "a160bcaa-3679-4194-bb69-dd396f88ea15" +ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" -InfinityQ = "f014c839-c17b-44be-a366-f421804f8372" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" MQLib = "16f11440-1623-44c9-850c-358a6c72f3c9" PySA = "46b73cfa-376a-48ee-8926-0c45ac3f7830" QUBOLib = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" +QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" diff --git a/scripts/build/Project.toml b/scripts/build/Project.toml deleted file mode 100644 index 4f685f8..0000000 --- a/scripts/build/Project.toml +++ /dev/null @@ -1,3 +0,0 @@ -[deps] -QUBOLib = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" -QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" diff --git a/scripts/build/build.jl b/scripts/build/build.jl index 149d31f..6b69f0f 100644 --- a/scripts/build/build.jl +++ b/scripts/build/build.jl @@ -6,6 +6,7 @@ using Downloads include("qplib.jl") include("arXiv_1903_10928_3r3x.jl") include("arXiv_1903_10928_5r5x.jl") +include("arXiv_2103_08464_3r3x.jl") function build_standard_qubolib( path::AbstractString = root_path(); @@ -36,9 +37,7 @@ function build_standard_qubolib( end -function main() - QUBOLib.logo() - +function build() build_standard_qubolib( QUBOLib.root_path(); clear_build = ("--clear-build" ∈ ARGS), @@ -48,4 +47,4 @@ function main() return nothing end -main() # Here we go! +# main() # Here we go! diff --git a/scripts/build/qplib.jl b/scripts/build/qplib.jl index 5e6ce32..f6613b1 100644 --- a/scripts/build/qplib.jl +++ b/scripts/build/qplib.jl @@ -235,8 +235,6 @@ end const QPLIB_URL = "http://qplib.zib.de/qplib.zip" function build_qplib!(index::LibraryIndex; cache::Bool = true) - @info "[qplib] Building QPLIB" - if QUBOLib.has_collection(index, :qplib) @info "[qplib] Collection already exists" @@ -247,6 +245,8 @@ function build_qplib!(index::LibraryIndex; cache::Bool = true) end end + @info "[qplib] Building QPLIB" + QUBOLib.add_collection!( index, :qplib, diff --git a/scripts/clear/clear.jl b/scripts/clear/clear.jl new file mode 100644 index 0000000..5623e38 --- /dev/null +++ b/scripts/clear/clear.jl @@ -0,0 +1,3 @@ +function clear() + +end diff --git a/scripts/deploy/deploy.jl b/scripts/deploy/deploy.jl index 17f6cda..58b3b58 100644 --- a/scripts/deploy/deploy.jl +++ b/scripts/deploy/deploy.jl @@ -1,5 +1,5 @@ import QUBOLib function deploy() - + end diff --git a/scripts/main.jl b/scripts/main.jl new file mode 100644 index 0000000..e24756b --- /dev/null +++ b/scripts/main.jl @@ -0,0 +1,51 @@ +using ArgParse +using QUBOLib + +include("build/build.jl") +# include("deploy/deploy.jl") +# include("run/run.jl") + +""" +""" +function main() + main_settings = ArgParseSettings() + + @add_arg_table! main_settings begin + "build" + help = "an option with an argument" + action = :command + "run" + help = "another option" + action = :command + "clear" + help = "clears current QUBOLib instance" + action = :command + "deploy" + help = "Deploys current state to target" + action = :command + end + + QUBOLib.logo() + + args = parse_args(main_settings; as_symbols = true) + + let cmd = args[:_COMMAND_] + @show cmd_args = args[cmd] + + return nothing + + 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 + +main() # Here we go! diff --git a/scripts/run/mqlib/mqlib.jl b/scripts/run/mqlib/mqlib.jl deleted file mode 100644 index e69de29..0000000 diff --git a/scripts/run/run.jl b/scripts/run/run.jl index a0ba963..fe208cc 100644 --- a/scripts/run/run.jl +++ b/scripts/run/run.jl @@ -4,15 +4,13 @@ using DataFrames using DBInterface # Solvers -using DWave -using MQLib -using PySA -using InfinityQ -using AIMOpt +# using DWave +# using MQLib +# using PySA +# using InfinityQ +# using AIMOpt function main() - QUBOLib.logo() - QUBOLib.load_index(QUBOLib.root_path(); create = false) do index df = DBInterface.execute( QUBOLib.database(index), @@ -48,4 +46,4 @@ function main() return nothing end -main() # Here we go! +# main() # Here we go! diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index 8a4e647..6c44c80 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -13,12 +13,15 @@ using Tar using TOML using Pkg using UUIDs -using QUBOTools using QUBODrivers using JuMP using SparseArrays using ProgressMeter +import Random +import QUBOTools +import PseudoBooleanOptimization as PBO + const __PROJECT__ = abspath(@__DIR__, "..") const __VERSION__ = VersionNumber(TOML.parsefile(joinpath(__PROJECT__, "Project.toml"))["version"]) @@ -27,7 +30,13 @@ export LibraryIndex include("logo.jl") include("path.jl") include("interface.jl") -include("index.jl") + +include("index/index.jl") + +include("synthesis/abstract.jl") +include("synthesis/sherrington_kirkpatrick.jl") +include("synthesis/wishart.jl") + include("run.jl") end # module QUBOLib diff --git a/src/index.jl b/src/index/index.jl similarity index 91% rename from src/index.jl rename to src/index/index.jl index 0f5fc03..3e10572 100644 --- a/src/index.jl +++ b/src/index/index.jl @@ -1,5 +1,5 @@ -include("index/database.jl") -include("index/archive.jl") +include("database.jl") +include("archive.jl") @doc raw""" LibraryIndex @@ -81,7 +81,7 @@ function load_index(callback::Function, path::AbstractString=qubolib_path(); cre end end -include("index/collections.jl") -include("index/instances.jl") -include("index/solvers.jl") -include("index/solutions.jl") +include("collections.jl") +include("instances.jl") +include("solvers.jl") +include("solutions.jl") diff --git a/src/interface.jl b/src/interface.jl index eb2681e..e93faa9 100644 --- a/src/interface.jl +++ b/src/interface.jl @@ -33,3 +33,16 @@ function add_solution! end run!(index::LibraryIndex, instances::Vector{U}, optimizer) where {U<:Integer} """ function run! end + +@doc raw""" + AbstractProblem{T} +""" +abstract type AbstractProblem{T} end + +@doc raw""" + generate(problem) + generate(rng, problem) + +Generates a QUBO problem and returns it as a [`QUBOTools.Model`](@ref). +""" +function generate end diff --git a/src/logo.jl b/src/logo.jl index 3e0e2db..af049a1 100644 --- a/src/logo.jl +++ b/src/logo.jl @@ -1,15 +1,17 @@ const LOGO = """ - ██████ ██ ██ ██████ ██████ -██ ██ ██ ██ ██ ██ ██ ██ -██ ██ ██ ██ ██████ ██ ██ -██ ▄▄ ██ ██ ██ ██ ██ ██ ██ - ██████ ██████ ██████ ██████ - ▀▀ - ██ ██ ██ - ██ ██ - ██ ██ ██████ - ██ ██ ██ ██ - ███████ ██ ██████ v$(__VERSION__) +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ ██████ ██ ██ ██████ ██████ ┃ +┃ ██ ██ ██ ██ ██ ██ ██ ██ ┃ +┃ ██ ██ ██ ██ ██████ ██ ██ ┃ +┃ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ┃ +┃ ██████ ██████ ██████ ██████ ┃ +┃ ▀▀ ┃ +┃ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ███████ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ """ function logo() diff --git a/src/synthesis/abstract.jl b/src/synthesis/abstract.jl new file mode 100644 index 0000000..6975d12 --- /dev/null +++ b/src/synthesis/abstract.jl @@ -0,0 +1,3 @@ +function QUBOLib.generate(problem::QUBOLib.AbstractProblem) + return QUBOLib.generate(Random.GLOBAL_RNG, problem) +end diff --git a/src/synthesis/sherrington_kirkpatrick.jl b/src/synthesis/sherrington_kirkpatrick.jl new file mode 100644 index 0000000..0afc380 --- /dev/null +++ b/src/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 QUBOLib.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/synthesis/wishart.jl b/src/synthesis/wishart.jl new file mode 100644 index 0000000..146ff6a --- /dev/null +++ b/src/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 QUBOLib.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/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/runtests.jl b/test/runtests.jl index 662c075..adcbd66 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,9 +1,14 @@ using Test -using QUBOLib +using Statistics + +import QUBOLib +import QUBOTools + +include("synthesis.jl") function main() @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..8f35304 --- /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.generate(QUBOLib.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.generate(QUBOLib.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 From af9e2709384aa68d238e544c3073011fd7da35f1 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Thu, 19 Sep 2024 16:08:45 -0400 Subject: [PATCH 11/23] Organize files --- Project.toml | 1 + scripts/clear/clear.jl | 3 -- scripts/main.jl | 50 +---------------- scripts/run/run.jl | 49 ----------------- src/QUBOLib.jl | 35 +++++++++--- {scripts/build => src/actions}/build.jl | 0 .../deploy.jl => actions/clear.jl} | 0 {scripts/deploy => src/actions}/deploy.jl | 0 src/{ => actions}/run.jl | 50 +++++++++++++++++ src/{ => interface}/interface.jl | 17 ++++++ src/library/build.jl | 27 ++++++++++ src/library/deploy.jl | 0 src/{management => library}/document.jl | 0 src/{management => library}/index.jl | 0 src/{ => library}/index/archive.jl | 0 .../index/collection-data.schema.json | 0 src/{ => library}/index/collections.jl | 0 src/{ => library}/index/database.jl | 0 src/{ => library}/index/index.jl | 0 src/{ => library}/index/instances.jl | 0 src/{ => library}/index/qubolib.sql | 0 src/{ => library}/index/solutions.jl | 0 src/{ => library}/index/solvers.jl | 0 src/{management => library}/interface.jl | 0 src/{management => library}/management.jl | 0 src/{ => library}/path.jl | 0 src/library/register.jl | 22 ++++++++ src/logo.jl | 21 -------- src/main.jl | 53 +++++++++++++++++++ src/management/build.jl | 27 ---------- 30 files changed, 200 insertions(+), 155 deletions(-) delete mode 100644 scripts/clear/clear.jl delete mode 100644 scripts/run/run.jl rename {scripts/build => src/actions}/build.jl (100%) rename src/{management/deploy.jl => actions/clear.jl} (100%) rename {scripts/deploy => src/actions}/deploy.jl (100%) rename src/{ => actions}/run.jl (55%) rename src/{ => interface}/interface.jl (81%) create mode 100644 src/library/build.jl create mode 100644 src/library/deploy.jl rename src/{management => library}/document.jl (100%) rename src/{management => library}/index.jl (100%) rename src/{ => library}/index/archive.jl (100%) rename src/{ => library}/index/collection-data.schema.json (100%) rename src/{ => library}/index/collections.jl (100%) rename src/{ => library}/index/database.jl (100%) rename src/{ => library}/index/index.jl (100%) rename src/{ => library}/index/instances.jl (100%) rename src/{ => library}/index/qubolib.sql (100%) rename src/{ => library}/index/solutions.jl (100%) rename src/{ => library}/index/solvers.jl (100%) rename src/{management => library}/interface.jl (100%) rename src/{management => library}/management.jl (100%) rename src/{ => library}/path.jl (100%) create mode 100644 src/library/register.jl delete mode 100644 src/logo.jl create mode 100644 src/main.jl delete mode 100644 src/management/build.jl diff --git a/Project.toml b/Project.toml index ccf9147..da0cd8c 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["pedromxavier "] version = "0.1.0" [deps] +ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" Downloads = "f43a241f-c20a-4ad4-852c-f6b1247861c6" HDF5 = "f67ccb44-e63f-5c2f-98bd-6dc0ccc4ba2f" diff --git a/scripts/clear/clear.jl b/scripts/clear/clear.jl deleted file mode 100644 index 5623e38..0000000 --- a/scripts/clear/clear.jl +++ /dev/null @@ -1,3 +0,0 @@ -function clear() - -end diff --git a/scripts/main.jl b/scripts/main.jl index e24756b..1f22853 100644 --- a/scripts/main.jl +++ b/scripts/main.jl @@ -1,51 +1,3 @@ -using ArgParse using QUBOLib -include("build/build.jl") -# include("deploy/deploy.jl") -# include("run/run.jl") - -""" -""" -function main() - main_settings = ArgParseSettings() - - @add_arg_table! main_settings begin - "build" - help = "an option with an argument" - action = :command - "run" - help = "another option" - action = :command - "clear" - help = "clears current QUBOLib instance" - action = :command - "deploy" - help = "Deploys current state to target" - action = :command - end - - QUBOLib.logo() - - args = parse_args(main_settings; as_symbols = true) - - let cmd = args[:_COMMAND_] - @show cmd_args = args[cmd] - - return nothing - - 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 - -main() # Here we go! +# QUBOLib.main() diff --git a/scripts/run/run.jl b/scripts/run/run.jl deleted file mode 100644 index fe208cc..0000000 --- a/scripts/run/run.jl +++ /dev/null @@ -1,49 +0,0 @@ -using QUBOLib -using JuMP -using DataFrames -using DBInterface - -# Solvers -# using DWave -# using MQLib -# using PySA -# using InfinityQ -# using AIMOpt - -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/QUBOLib.jl b/src/QUBOLib.jl index 6c44c80..51ae03e 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -1,5 +1,6 @@ module QUBOLib +using ArgParse using LazyArtifacts using HDF5 using JSON @@ -25,18 +26,40 @@ import PseudoBooleanOptimization as PBO const __PROJECT__ = abspath(@__DIR__, "..") const __VERSION__ = VersionNumber(TOML.parsefile(joinpath(__PROJECT__, "Project.toml"))["version"]) -export LibraryIndex +const QUBOLIB_LOGO = """ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ ██████ ██ ██ ██████ ██████ ┃ +┃ ██ ██ ██ ██ ██ ██ ██ ██ ┃ +┃ ██ ██ ██ ██ ██████ ██ ██ ┃ +┃ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ┃ +┃ ██████ ██████ ██████ ██████ ┃ +┃ ▀▀ ┃ +┃ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ███████ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +""" -include("logo.jl") -include("path.jl") -include("interface.jl") +function print_logo(io::IO = stdout) + println(io, QUBOLIB_LOGO) -include("index/index.jl") + return nothing +end + +include("interface/interface.jl") + +include("library/path.jl") +include("library/index/index.jl") + +include("library/register.jl") +include("library/build.jl") include("synthesis/abstract.jl") include("synthesis/sherrington_kirkpatrick.jl") include("synthesis/wishart.jl") -include("run.jl") +include("main.jl") end # module QUBOLib diff --git a/scripts/build/build.jl b/src/actions/build.jl similarity index 100% rename from scripts/build/build.jl rename to src/actions/build.jl diff --git a/src/management/deploy.jl b/src/actions/clear.jl similarity index 100% rename from src/management/deploy.jl rename to src/actions/clear.jl diff --git a/scripts/deploy/deploy.jl b/src/actions/deploy.jl similarity index 100% rename from scripts/deploy/deploy.jl rename to src/actions/deploy.jl diff --git a/src/run.jl b/src/actions/run.jl similarity index 55% rename from src/run.jl rename to src/actions/run.jl index 11d20b1..86e4c51 100644 --- a/src/run.jl +++ b/src/actions/run.jl @@ -1,3 +1,15 @@ +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 @@ -74,3 +86,41 @@ function run!(index::LibraryIndex, optimizer, codes::AbstractVector{U}; 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/interface.jl b/src/interface/interface.jl similarity index 81% rename from src/interface.jl rename to src/interface/interface.jl index e93faa9..ea33d83 100644 --- a/src/interface.jl +++ b/src/interface/interface.jl @@ -1,3 +1,20 @@ +@doc raw""" + register!(source::Symbol) + +Registers an instance source. +""" +function register! end + +@doc raw""" + load!(source::Symbol, cache::Bool = true) +""" +function load! end + +@doc raw""" + clear!(source::Symbol, cache::Bool = true) +""" +function clear! end + @doc raw""" add_collection!(index::LibraryIndex, code::Symbol, data::Dict{String,Any}) diff --git a/src/library/build.jl b/src/library/build.jl new file mode 100644 index 0000000..579f5ea --- /dev/null +++ b/src/library/build.jl @@ -0,0 +1,27 @@ +# function build( +# collections::AbstractVector, +# root_path::AbstractString, +# dist_path::AbstractString=abspath(root_path, "dist"); +# cache::Bool=true, +# ) +# index = create_index(root_path, dist_path) + +# for coll in collections +# build!(index, coll; cache) +# end + +# # Compute Tree hash +# tree_hash = bytes2hex(Pkg.GitTools.tree_hash(dist_path)) + +# return index +# end + +# function build!(index::LibraryIndex, coll::Collection; cache::Bool=true) +# if !(cache && has_collection(index, coll)) +# load!(index, coll; cache) +# index!(index, coll) +# document!(index, coll) +# end + +# return nothing +# end diff --git a/src/library/deploy.jl b/src/library/deploy.jl new file mode 100644 index 0000000..e69de29 diff --git a/src/management/document.jl b/src/library/document.jl similarity index 100% rename from src/management/document.jl rename to src/library/document.jl diff --git a/src/management/index.jl b/src/library/index.jl similarity index 100% rename from src/management/index.jl rename to src/library/index.jl diff --git a/src/index/archive.jl b/src/library/index/archive.jl similarity index 100% rename from src/index/archive.jl rename to src/library/index/archive.jl diff --git a/src/index/collection-data.schema.json b/src/library/index/collection-data.schema.json similarity index 100% rename from src/index/collection-data.schema.json rename to src/library/index/collection-data.schema.json diff --git a/src/index/collections.jl b/src/library/index/collections.jl similarity index 100% rename from src/index/collections.jl rename to src/library/index/collections.jl diff --git a/src/index/database.jl b/src/library/index/database.jl similarity index 100% rename from src/index/database.jl rename to src/library/index/database.jl diff --git a/src/index/index.jl b/src/library/index/index.jl similarity index 100% rename from src/index/index.jl rename to src/library/index/index.jl diff --git a/src/index/instances.jl b/src/library/index/instances.jl similarity index 100% rename from src/index/instances.jl rename to src/library/index/instances.jl diff --git a/src/index/qubolib.sql b/src/library/index/qubolib.sql similarity index 100% rename from src/index/qubolib.sql rename to src/library/index/qubolib.sql diff --git a/src/index/solutions.jl b/src/library/index/solutions.jl similarity index 100% rename from src/index/solutions.jl rename to src/library/index/solutions.jl diff --git a/src/index/solvers.jl b/src/library/index/solvers.jl similarity index 100% rename from src/index/solvers.jl rename to src/library/index/solvers.jl diff --git a/src/management/interface.jl b/src/library/interface.jl similarity index 100% rename from src/management/interface.jl rename to src/library/interface.jl diff --git a/src/management/management.jl b/src/library/management.jl similarity index 100% rename from src/management/management.jl rename to src/library/management.jl diff --git a/src/path.jl b/src/library/path.jl similarity index 100% rename from src/path.jl rename to src/library/path.jl diff --git a/src/library/register.jl b/src/library/register.jl new file mode 100644 index 0000000..64af1f3 --- /dev/null +++ b/src/library/register.jl @@ -0,0 +1,22 @@ +@doc raw""" + +""" +struct Registry + sources::Vector{Symbol} + + Registry() = new(Symbol[]) +end + +const GLOBAL_REG = Registry() + +function register!(reg::Registry, source::Symbol) + push!(reg.source, source) + + return nothing +end + +function register!(source::Symbol) + register!(GLOBAL_REG, source) + + return nothing +end diff --git a/src/logo.jl b/src/logo.jl deleted file mode 100644 index af049a1..0000000 --- a/src/logo.jl +++ /dev/null @@ -1,21 +0,0 @@ -const LOGO = """ -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ ██████ ██ ██ ██████ ██████ ┃ -┃ ██ ██ ██ ██ ██ ██ ██ ██ ┃ -┃ ██ ██ ██ ██ ██████ ██ ██ ┃ -┃ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ┃ -┃ ██████ ██████ ██████ ██████ ┃ -┃ ▀▀ ┃ -┃ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ██ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ██ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ███████ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ -""" - -function logo() - println(LOGO) - - return nothing -end diff --git a/src/main.jl b/src/main.jl new file mode 100644 index 0000000..4238467 --- /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" + "run" + action = :command + help = "another option" + "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" + 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/build.jl b/src/management/build.jl deleted file mode 100644 index b6ec1b6..0000000 --- a/src/management/build.jl +++ /dev/null @@ -1,27 +0,0 @@ -function build( - collections::AbstractVector, - root_path::AbstractString, - dist_path::AbstractString=abspath(root_path, "dist"); - cache::Bool=true, -) - index = create_index(root_path, dist_path) - - for coll in collections - build!(index, coll; cache) - end - - # Compute Tree hash - tree_hash = bytes2hex(Pkg.GitTools.tree_hash(dist_path)) - - return index -end - -function build!(index::LibraryIndex, coll::Collection; cache::Bool=true) - if !(cache && has_collection(index, coll)) - load!(index, coll; cache) - index!(index, coll) - document!(index, coll) - end - - return nothing -end From afd181afe56d0d1d076c5425f062996a4da113bf Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Wed, 25 Sep 2024 00:22:50 -0400 Subject: [PATCH 12/23] Add instance generators from `QUBOTools#px/instances` --- src/synthesis/nae3sat.jl | 60 ++++++++++++++++++++++++++++++++++++++++ src/synthesis/sk.jl | 33 ++++++++++++++++++++++ src/synthesis/xorsat.jl | 22 +++++++++++++++ 3 files changed, 115 insertions(+) create mode 100644 src/synthesis/nae3sat.jl create mode 100644 src/synthesis/sk.jl create mode 100644 src/synthesis/xorsat.jl diff --git a/src/synthesis/nae3sat.jl b/src/synthesis/nae3sat.jl new file mode 100644 index 0000000..6a8b19a --- /dev/null +++ b/src/synthesis/nae3sat.jl @@ -0,0 +1,60 @@ +@doc raw""" + NAE3SAT{T} + +Not-all-equal 3-SAT +""" +struct NAE3SAT{T} <: AbstractProblem{T} + m::Int + n::Int + + function NAE3SAT{T}(m::Integer, n::Integer) where {T} + @assert n >= 3 + + return new{T}(m, n) + end + + function NAE3SAT{T}(n::Integer, ratio = 2.11) where {T} + m = trunc(Int, n * ratio) + + return NAE3SAT{T}(m, n) + end +end + +function generate(rng, problem::NAE3SAT{T}, ::SpinDomain) 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 + + α = one(T) + β = zero(T) + + return (h, J, α, β) +end + +function generate(rng, problem::NAE3SAT{T}, ::BoolDomain) where {T} + return cast(𝕊 => 𝔹, generate(rng, problem, 𝕊)...) +end diff --git a/src/synthesis/sk.jl b/src/synthesis/sk.jl new file mode 100644 index 0000000..2d7f837 --- /dev/null +++ b/src/synthesis/sk.jl @@ -0,0 +1,33 @@ +@doc raw""" + SK{T} + +Sherrington-Kirkpatrick Model +""" +struct SK{T} <: AbstractProblem{T} + n::Int + + function SK{T}(n::Integer) where {T} + return new{T}(n) + end +end + +function generate(rng, problem::SK{T}, ::SpinDomain) where {T} + n = problem.n # number of variables + + # Ising Interactions + h = Dict{Int,T}() + J = sizehint!(Dict{Tuple{Int,Int},T}(), (n * (n - 1)) ÷ 2) + + for i = 1:n, j = (i+1):n + J[(i,j)] = randn(rng, T) + end + + α = one(T) + β = zero(T) + + return (h, J, α, β) +end + +function generate(rng, problem::SK{T}, ::BoolDomain; kws...) where {T} + return cast(𝕊 => 𝔹, generate(rng, problem, 𝕊)...; kws...) +end \ No newline at end of file diff --git a/src/synthesis/xorsat.jl b/src/synthesis/xorsat.jl new file mode 100644 index 0000000..c43f8ea --- /dev/null +++ b/src/synthesis/xorsat.jl @@ -0,0 +1,22 @@ +@doc raw""" + XORSAT{T} + +``r``-regular ``k``-XORSAT +""" +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(problem::XORSAT{T}, ::BoolDomain) where {T} + +end + +function generate(rng, problem::XORSAT{T}, ::SpinDomain; kws...) where {T} + return cast(𝔹 => 𝕊, generate(rng, problem, 𝔹)...; kws...) +end From 8ee5b19f7c3a43844ce6153c8909d35a648e5d7c Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Wed, 25 Sep 2024 00:45:27 -0400 Subject: [PATCH 13/23] Refactor --- src/QUBOLib.jl | 36 +++++++-- src/actions/build.jl | 18 ++--- src/actions/clear.jl | 21 ++++++ src/actions/deploy.jl | 4 +- src/interface/interface.jl | 5 ++ src/library/synthesis.jl | 15 ++++ src/library/synthesis/abstract.jl | 3 + src/library/synthesis/nae3sat.jl | 75 +++++++++++++++++++ .../synthesis/sherrington_kirkpatrick.jl | 2 +- src/{ => library}/synthesis/wishart.jl | 2 +- src/{ => library}/synthesis/xorsat.jl | 6 +- src/synthesis/abstract.jl | 3 - src/synthesis/nae3sat.jl | 60 --------------- src/synthesis/sk.jl | 33 -------- test/runtests.jl | 4 +- 15 files changed, 165 insertions(+), 122 deletions(-) create mode 100644 src/library/synthesis.jl create mode 100644 src/library/synthesis/abstract.jl create mode 100644 src/library/synthesis/nae3sat.jl rename src/{ => library}/synthesis/sherrington_kirkpatrick.jl (95%) rename src/{ => library}/synthesis/wishart.jl (96%) rename src/{ => library}/synthesis/xorsat.jl (54%) delete mode 100644 src/synthesis/abstract.jl delete mode 100644 src/synthesis/nae3sat.jl delete mode 100644 src/synthesis/sk.jl diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index 51ae03e..ef79aec 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -23,8 +23,35 @@ import Random import QUBOTools import PseudoBooleanOptimization as PBO -const __PROJECT__ = abspath(@__DIR__, "..") -const __VERSION__ = VersionNumber(TOML.parsefile(joinpath(__PROJECT__, "Project.toml"))["version"]) +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 const QUBOLIB_LOGO = """ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ @@ -55,10 +82,9 @@ include("library/index/index.jl") include("library/register.jl") include("library/build.jl") +include("library/synthesis.jl") -include("synthesis/abstract.jl") -include("synthesis/sherrington_kirkpatrick.jl") -include("synthesis/wishart.jl") +include("actions/clear.jl") include("main.jl") diff --git a/src/actions/build.jl b/src/actions/build.jl index 6b69f0f..ed9877f 100644 --- a/src/actions/build.jl +++ b/src/actions/build.jl @@ -1,12 +1,12 @@ -using QUBOTools -using QUBOLib -using Downloads - -# Standard Library -include("qplib.jl") -include("arXiv_1903_10928_3r3x.jl") -include("arXiv_1903_10928_5r5x.jl") -include("arXiv_2103_08464_3r3x.jl") +function build!(reg::Registry) + +end + +function build!(source::Symbol) + build!(Val(source)) + + return nothing +end function build_standard_qubolib( path::AbstractString = root_path(); diff --git a/src/actions/clear.jl b/src/actions/clear.jl index e69de29..da698f0 100644 --- a/src/actions/clear.jl +++ b/src/actions/clear.jl @@ -0,0 +1,21 @@ +function QUBOLib.clear!(reg::Registry = GLOBAL_REG) + for source in reg.sources + QUBOLib.clear!(source) + end + + return nothing +end + +function QUBOLib.clear!(source::Symbol) + QUBOLib.clear!(Val(source)) + + return nothing +end + +function QUBOLib.clear!(::Val{source}) where {source} + @assert source isa Symbol + + @warn "No clearing routine defined for '$(source)'" + + return nothing +end diff --git a/src/actions/deploy.jl b/src/actions/deploy.jl index 58b3b58..c0459c4 100644 --- a/src/actions/deploy.jl +++ b/src/actions/deploy.jl @@ -1,5 +1,3 @@ -import QUBOLib - -function deploy() +function QUBOLib.deploy!(target::AbstractString) end diff --git a/src/interface/interface.jl b/src/interface/interface.jl index ea33d83..6875083 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -15,6 +15,11 @@ function load! end """ function clear! end +@doc raw""" + build!(source::Symbol) +""" +function build! end + @doc raw""" add_collection!(index::LibraryIndex, code::Symbol, data::Dict{String,Any}) diff --git a/src/library/synthesis.jl b/src/library/synthesis.jl new file mode 100644 index 0000000..d2c0351 --- /dev/null +++ b/src/library/synthesis.jl @@ -0,0 +1,15 @@ +module Synthesis + +import Random +import QUBOTools +import ..QUBOLib +import ..QUBOLib: AbstractProblem, generate +import PseudoBooleanOptimization as PBO + +include("synthesis/abstract.jl") +include("synthesis/nae3sat.jl") +include("synthesis/sherrington_kirkpatrick.jl") +include("synthesis/wishart.jl") +include("synthesis/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/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/synthesis/sherrington_kirkpatrick.jl b/src/library/synthesis/sherrington_kirkpatrick.jl similarity index 95% rename from src/synthesis/sherrington_kirkpatrick.jl rename to src/library/synthesis/sherrington_kirkpatrick.jl index 0afc380..13abe6d 100644 --- a/src/synthesis/sherrington_kirkpatrick.jl +++ b/src/library/synthesis/sherrington_kirkpatrick.jl @@ -20,7 +20,7 @@ end const SK{T} = SherringtonKirkpatrick{T} -function QUBOLib.generate(rng, problem::SherringtonKirkpatrick{T}) where {T} +function generate(rng, problem::SherringtonKirkpatrick{T}) where {T} f, x = PBO.sherrington_kirkpatrick( rng, PBO.PBF{Int,T}, diff --git a/src/synthesis/wishart.jl b/src/library/synthesis/wishart.jl similarity index 96% rename from src/synthesis/wishart.jl rename to src/library/synthesis/wishart.jl index 146ff6a..e6b1eff 100644 --- a/src/synthesis/wishart.jl +++ b/src/library/synthesis/wishart.jl @@ -32,7 +32,7 @@ function Wishart(n::Integer, m::Integer; discretize::Bool = false, precision::In return Wishart{Float64}(n, m; discretize, precision) end -function QUBOLib.generate(rng, problem::Wishart{T}) where {T} +function generate(rng, problem::Wishart{T}) where {T} f, x = PBO.wishart( rng, PBO.PBF{Int,T}, diff --git a/src/synthesis/xorsat.jl b/src/library/synthesis/xorsat.jl similarity index 54% rename from src/synthesis/xorsat.jl rename to src/library/synthesis/xorsat.jl index c43f8ea..153604a 100644 --- a/src/synthesis/xorsat.jl +++ b/src/library/synthesis/xorsat.jl @@ -13,10 +13,6 @@ struct XORSAT{T} <: AbstractProblem{T} end end -function generate(problem::XORSAT{T}, ::BoolDomain) where {T} +function generate(rng, problem::XORSAT{T}) where {T} end - -function generate(rng, problem::XORSAT{T}, ::SpinDomain; kws...) where {T} - return cast(𝔹 => 𝕊, generate(rng, problem, 𝔹)...; kws...) -end diff --git a/src/synthesis/abstract.jl b/src/synthesis/abstract.jl deleted file mode 100644 index 6975d12..0000000 --- a/src/synthesis/abstract.jl +++ /dev/null @@ -1,3 +0,0 @@ -function QUBOLib.generate(problem::QUBOLib.AbstractProblem) - return QUBOLib.generate(Random.GLOBAL_RNG, problem) -end diff --git a/src/synthesis/nae3sat.jl b/src/synthesis/nae3sat.jl deleted file mode 100644 index 6a8b19a..0000000 --- a/src/synthesis/nae3sat.jl +++ /dev/null @@ -1,60 +0,0 @@ -@doc raw""" - NAE3SAT{T} - -Not-all-equal 3-SAT -""" -struct NAE3SAT{T} <: AbstractProblem{T} - m::Int - n::Int - - function NAE3SAT{T}(m::Integer, n::Integer) where {T} - @assert n >= 3 - - return new{T}(m, n) - end - - function NAE3SAT{T}(n::Integer, ratio = 2.11) where {T} - m = trunc(Int, n * ratio) - - return NAE3SAT{T}(m, n) - end -end - -function generate(rng, problem::NAE3SAT{T}, ::SpinDomain) 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 - - α = one(T) - β = zero(T) - - return (h, J, α, β) -end - -function generate(rng, problem::NAE3SAT{T}, ::BoolDomain) where {T} - return cast(𝕊 => 𝔹, generate(rng, problem, 𝕊)...) -end diff --git a/src/synthesis/sk.jl b/src/synthesis/sk.jl deleted file mode 100644 index 2d7f837..0000000 --- a/src/synthesis/sk.jl +++ /dev/null @@ -1,33 +0,0 @@ -@doc raw""" - SK{T} - -Sherrington-Kirkpatrick Model -""" -struct SK{T} <: AbstractProblem{T} - n::Int - - function SK{T}(n::Integer) where {T} - return new{T}(n) - end -end - -function generate(rng, problem::SK{T}, ::SpinDomain) where {T} - n = problem.n # number of variables - - # Ising Interactions - h = Dict{Int,T}() - J = sizehint!(Dict{Tuple{Int,Int},T}(), (n * (n - 1)) ÷ 2) - - for i = 1:n, j = (i+1):n - J[(i,j)] = randn(rng, T) - end - - α = one(T) - β = zero(T) - - return (h, J, α, β) -end - -function generate(rng, problem::SK{T}, ::BoolDomain; kws...) where {T} - return cast(𝕊 => 𝔹, generate(rng, problem, 𝕊)...; kws...) -end \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index adcbd66..799d59c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,13 +1,13 @@ using Test using Statistics -import QUBOLib import QUBOTools +import QUBOLib include("synthesis.jl") function main() - @testset "♣ QUBOLib.jl «$(QUBOLib.__VERSION__)» Test Suite ♣" verbose = true begin + @testset "♣ QUBOLib.jl «$(QUBOLib.__version__())» Test Suite ♣" verbose = true begin test_synthesis() end From be57907c9113f99ed2d5158f5457f5d9155d7014 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Wed, 25 Sep 2024 11:25:35 -0400 Subject: [PATCH 14/23] Update interface --- Makefile | 6 ++++++ Project.toml | 5 ++--- docs/make.jl | 20 ++++++++++---------- docs/src/api.md | 30 ++++++++++++++++++++++++++++++ docs/src/index.md | 2 +- src/QUBOLib.jl | 1 - src/actions/generate.jl | 0 src/interface/interface.jl | 13 ------------- src/library/index/solutions.jl | 2 +- src/library/synthesis.jl | 2 +- src/library/synthesis/interface.jl | 12 ++++++++++++ src/main.jl | 6 +++--- 12 files changed, 66 insertions(+), 33 deletions(-) create mode 100644 src/actions/generate.jl create mode 100644 src/library/synthesis/interface.jl diff --git a/Makefile b/Makefile index db59758..db7ff99 100644 --- a/Makefile +++ b/Makefile @@ -13,3 +13,9 @@ clear-build: 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 da0cd8c..8b884e2 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "QUBOLib" uuid = "c2d3eca2-2309-4628-88a9-9d4a554e7c47" -authors = ["pedromxavier "] +authors = ["pedromxavier "] version = "0.1.0" [deps] @@ -17,7 +17,6 @@ LazyArtifacts = "4af54fe1-eca0-43a8-85a7-787d91b784e3" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" PseudoBooleanOptimization = "c8fa9a04-bc42-452d-8558-dc51757be744" -QUBODrivers = "a3f166f7-2cd3-47b6-9e1e-6fbfe0449eb0" QUBOTools = "60eb5b62-0a39-4ddc-84c5-97d2adff9319" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" SQLite = "0aa819cd-b072-5ff4-a722-6bc24af294d9" @@ -27,4 +26,4 @@ Tar = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" UUIDs = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" [compat] -QUBOTools = "0.9" +QUBOTools = "0.10" diff --git a/docs/make.jl b/docs/make.jl index fb5c242..e0b0b02 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -20,15 +20,15 @@ makedocs(; pages = [ "Home" => "index.md", "API" => "api.md", - "Manual" => [ - "Introduction" => "manual/0-intro.md", - "Access" => "manual/1-access.md", - "Extension" => "manual/2-extension.md", - ], - "Booklet" => [ - "Introduction" => "booklet/0-intro.md", - "Library Design" => "booklet/1-design.md", - ], + # "Manual" => [ + # "Introduction" => "manual/0-intro.md", + # "Access" => "manual/1-access.md", + # "Extension" => "manual/2-extension.md", + # ], + # "Booklet" => [ + # "Introduction" => "booklet/0-intro.md", + # "Library Design" => "booklet/1-design.md", + # ], ], workdir = @__DIR__, ) @@ -36,5 +36,5 @@ makedocs(; 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 5932792..f2a89dc 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -1 +1,31 @@ # API + +## Actions + +### Build + +```@docs +QUBOLib.build +``` + +```@docs +QUBOLib.clear +``` + +```@docs +QUBOLib.deploy +``` + +```@docs +QUBOLib.generate +``` + +```@docs +QUBOLib.run +``` + +## Instance Synthesis + +```@docs +QUBOLib.Synthesis.AbstractProblem +``` \ No newline at end of file diff --git a/docs/src/index.md b/docs/src/index.md index 8db994d..0548238 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -7,7 +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 ``` diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index ef79aec..ff3975e 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -14,7 +14,6 @@ using Tar using TOML using Pkg using UUIDs -using QUBODrivers using JuMP using SparseArrays using ProgressMeter diff --git a/src/actions/generate.jl b/src/actions/generate.jl new file mode 100644 index 0000000..e69de29 diff --git a/src/interface/interface.jl b/src/interface/interface.jl index 6875083..e3cfd1b 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -55,16 +55,3 @@ function add_solution! end run!(index::LibraryIndex, instances::Vector{U}, optimizer) where {U<:Integer} """ function run! end - -@doc raw""" - AbstractProblem{T} -""" -abstract type AbstractProblem{T} end - -@doc raw""" - generate(problem) - generate(rng, problem) - -Generates a QUBO problem and returns it as a [`QUBOTools.Model`](@ref). -""" -function generate end diff --git a/src/library/index/solutions.jl b/src/library/index/solutions.jl index 9f4c0bb..fe3d1a4 100644 --- a/src/library/index/solutions.jl +++ b/src/library/index/solutions.jl @@ -15,7 +15,7 @@ function _write_solution( return nothing end -function add_solution!(index::LibraryIndex, instance::Integer, sol::SampleSet{Float64,Int})::Integer +function add_solution!(index::LibraryIndex, instance::Integer, sol::QUBOTools.SampleSet{Float64,Int})::Integer @assert isopen(index) @assert !isempty(sol) diff --git a/src/library/synthesis.jl b/src/library/synthesis.jl index d2c0351..5409f1b 100644 --- a/src/library/synthesis.jl +++ b/src/library/synthesis.jl @@ -3,9 +3,9 @@ module Synthesis import Random import QUBOTools import ..QUBOLib -import ..QUBOLib: AbstractProblem, generate import PseudoBooleanOptimization as PBO +include("synthesis/interface.jl") include("synthesis/abstract.jl") include("synthesis/nae3sat.jl") include("synthesis/sherrington_kirkpatrick.jl") diff --git a/src/library/synthesis/interface.jl b/src/library/synthesis/interface.jl new file mode 100644 index 0000000..ebef1a8 --- /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) + generate(rng, problem) + +Generates a QUBO problem and returns it as a [`QUBOTools.Model`](@ref). +""" +function generate end diff --git a/src/main.jl b/src/main.jl index 4238467..5801bdd 100644 --- a/src/main.jl +++ b/src/main.jl @@ -7,9 +7,6 @@ function main() "build" action = :command help = "an option with an argument" - "run" - action = :command - help = "another option" "clear" action = :command help = "Clears current QUBOLib" @@ -19,6 +16,9 @@ function main() "generate" action = :command help = "Generates instances for a given problem" + "run" + action = :command + help = "another option" end @add_arg_table! settings["build"] begin From dd6942b50db1df4999c256b8501a922a7858c1b4 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Fri, 27 Sep 2024 17:24:54 -0400 Subject: [PATCH 15/23] Update docs --- docs/Project.toml | 1 + docs/make.jl | 7 +++++++ docs/src/api.md | 21 +-------------------- docs/src/assets/logo.svg | 24 +++++++++++++++++------- src/QUBOLib.jl | 26 +++++++++++++------------- src/library/synthesis/interface.jl | 2 +- 6 files changed, 40 insertions(+), 41 deletions(-) diff --git a/docs/Project.toml b/docs/Project.toml index 1f1d0c6..e59ed1d 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,6 +1,7 @@ [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] diff --git a/docs/make.jl b/docs/make.jl index e0b0b02..abb4dfa 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,10 +1,16 @@ 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, @@ -30,6 +36,7 @@ makedocs(; # "Library Design" => "booklet/1-design.md", # ], ], + plugins = [links], workdir = @__DIR__, ) diff --git a/docs/src/api.md b/docs/src/api.md index f2a89dc..32b479c 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -4,28 +4,9 @@ ### Build -```@docs -QUBOLib.build -``` - -```@docs -QUBOLib.clear -``` - -```@docs -QUBOLib.deploy -``` - -```@docs -QUBOLib.generate -``` - -```@docs -QUBOLib.run -``` - ## Instance Synthesis ```@docs QUBOLib.Synthesis.AbstractProblem +QUBOLib.Synthesis.generate ``` \ No newline at end of file diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index 675fddb..ace0713 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -27,22 +27,31 @@ - - - - + + + + + + - + + QUBO - + Lib + @@ -50,6 +59,7 @@ - + + diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index ff3975e..ac9b389 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -53,19 +53,19 @@ function __version__()::VersionNumber end const QUBOLIB_LOGO = """ -┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ -┃ ██████ ██ ██ ██████ ██████ ┃ -┃ ██ ██ ██ ██ ██ ██ ██ ██ ┃ -┃ ██ ██ ██ ██ ██████ ██ ██ ┃ -┃ ██ ▄▄ ██ ██ ██ ██ ██ ██ ██ ┃ -┃ ██████ ██████ ██████ ██████ ┃ -┃ ▀▀ ┃ -┃ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ██ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ██ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┃ ███████ ██ ██████ ▒▒▒▒▒▒▒▒▒▒▒▒ ┃ -┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ +┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +┃ ▄██████▄ ██ ██ █████▄ ▄██████▄ ┃ +┃ ██ ██ ██ ██ ██ ██ ██ ██ ┃ +┃ ██ ██ ██ ██ ██████ ██ ██ ┃ +┃ ██ ▀▀▄███ ██ ██ ██ ██ ██ ██ ┃ +┃ ▀██████▀▄▄ ▀██████▀ █████▀ ▀██████▀ ┃ +┃ ┃ +┃ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ █████▄ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ██ ██ ██ ██ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┃ ███████ ██ █████▀ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒ ┃ +┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ """ function print_logo(io::IO = stdout) diff --git a/src/library/synthesis/interface.jl b/src/library/synthesis/interface.jl index ebef1a8..47de0b7 100644 --- a/src/library/synthesis/interface.jl +++ b/src/library/synthesis/interface.jl @@ -7,6 +7,6 @@ abstract type AbstractProblem{T} end generate(problem) generate(rng, problem) -Generates a QUBO problem and returns it as a [`QUBOTools.Model`](@ref). +Generates a QUBO problem and returns it as a [`QUBOTools.Model`](@extref). """ function generate end From af4df1deadb4fc50ac1c2bf7d13765edb854a3be Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Fri, 27 Sep 2024 23:08:10 -0400 Subject: [PATCH 16/23] Update Logo --- docs/src/assets/logo.svg | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index ace0713..0c45195 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -27,17 +27,15 @@ - - - - - - + + + + @@ -61,5 +59,5 @@ - + From 0cd9110147967aa203c5c970f4a84922ecec0578 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Sat, 28 Sep 2024 23:06:19 -0400 Subject: [PATCH 17/23] Refactor Library --- docs/make.jl | 9 +- docs/src/api.md | 63 ++++++- docs/src/manual/0-intro.md | 9 +- docs/src/manual/1-access.md | 0 docs/src/manual/1-basic.md | 11 ++ docs/src/manual/2-advanced.md | 10 ++ docs/src/manual/2-extension.md | 0 src/QUBOLib.jl | 27 +-- src/actions/clear.jl | 20 --- .../collection.schema.json} | 0 src/{library/index => assets}/qubolib.sql | 10 +- src/interface.jl | 132 ++++++++++++++ src/interface/interface.jl | 57 ------ src/library/access.jl | 80 +++++++++ src/library/deploy.jl | 0 src/library/index.jl | 169 +++--------------- src/library/index/archive.jl | 18 -- src/library/index/database.jl | 35 ---- src/library/index/index.jl | 87 --------- src/library/interface.jl | 32 ---- src/library/management.jl | 40 ----- src/library/path.jl | 85 +++++++-- src/library/register.jl | 22 --- src/library/synthesis.jl | 15 -- src/library/synthesis/Synthesis.jl | 15 ++ src/library/synthesis/interface.jl | 4 +- src/library/synthesis/xorsat.jl | 4 +- src/{library => old-library}/build.jl | 0 src/{library => old-library}/document.jl | 0 src/old-library/index.jl | 160 +++++++++++++++++ .../index/collections.jl | 4 +- .../index/instances.jl | 0 .../index/solutions.jl | 0 src/{library => old-library}/index/solvers.jl | 0 34 files changed, 605 insertions(+), 513 deletions(-) delete mode 100644 docs/src/manual/1-access.md create mode 100644 docs/src/manual/1-basic.md create mode 100644 docs/src/manual/2-advanced.md delete mode 100644 docs/src/manual/2-extension.md rename src/{library/index/collection-data.schema.json => assets/collection.schema.json} (100%) rename src/{library/index => assets}/qubolib.sql (86%) create mode 100644 src/interface.jl delete mode 100644 src/interface/interface.jl create mode 100644 src/library/access.jl delete mode 100644 src/library/deploy.jl delete mode 100644 src/library/index/archive.jl delete mode 100644 src/library/index/database.jl delete mode 100644 src/library/index/index.jl delete mode 100644 src/library/interface.jl delete mode 100644 src/library/management.jl delete mode 100644 src/library/register.jl delete mode 100644 src/library/synthesis.jl create mode 100644 src/library/synthesis/Synthesis.jl rename src/{library => old-library}/build.jl (100%) rename src/{library => old-library}/document.jl (100%) create mode 100644 src/old-library/index.jl rename src/{library => old-library}/index/collections.jl (91%) rename src/{library => old-library}/index/instances.jl (100%) rename src/{library => old-library}/index/solutions.jl (100%) rename src/{library => old-library}/index/solvers.jl (100%) diff --git a/docs/make.jl b/docs/make.jl index abb4dfa..9d15167 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -26,11 +26,10 @@ makedocs(; pages = [ "Home" => "index.md", "API" => "api.md", - # "Manual" => [ - # "Introduction" => "manual/0-intro.md", - # "Access" => "manual/1-access.md", - # "Extension" => "manual/2-extension.md", - # ], + "Manual" => [ + "Introduction" => "manual/0-intro.md", + "Basic Usage" => "manual/1-basic.md", + ], # "Booklet" => [ # "Introduction" => "booklet/0-intro.md", # "Library Design" => "booklet/1-design.md", diff --git a/docs/src/api.md b/docs/src/api.md index 32b479c..2d1ebd9 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -2,11 +2,72 @@ ## Actions -### Build +## 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.database +QUBOLib.archive +``` + +## 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/manual/0-intro.md b/docs/src/manual/0-intro.md index cf4482a..7ec771c 100644 --- a/docs/src/manual/0-intro.md +++ b/docs/src/manual/0-intro.md @@ -1,14 +1,21 @@ # Introduction +## Benchmarking Physics-Inspired Optimization Solvers + ## Mathematical Definitions All instances have been recast into the binary, minimization form: ```math \begin{array}{rll} - \min_{\mathbf{x}} & \alpha \left[ \mathbf{x}' \mathbf{Q} \, \mathbf{x} + \mathbf{\ell}' \mathb{x} + \beta \right] \\ + \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-access.md b/docs/src/manual/1-access.md deleted file mode 100644 index e69de29..0000000 diff --git a/docs/src/manual/1-basic.md b/docs/src/manual/1-basic.md new file mode 100644 index 0000000..a000785 --- /dev/null +++ b/docs/src/manual/1-basic.md @@ -0,0 +1,11 @@ +# Basic Usage + +## Getting Started + +```@example basic +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..38e1ae2 --- /dev/null +++ b/docs/src/manual/2-advanced.md @@ -0,0 +1,10 @@ +# Advanced Usage + +```julia +using QUBOLib + +QUBOLib.access() do index + db = QUBOLib.database(index) + h5 = QUBOLib.archive(index) +end +``` \ No newline at end of file diff --git a/docs/src/manual/2-extension.md b/docs/src/manual/2-extension.md deleted file mode 100644 index e69de29..0000000 diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index ac9b389..a1598b8 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -2,22 +2,22 @@ module QUBOLib using ArgParse using LazyArtifacts -using HDF5 -using JSON using Downloads -using JSONSchema using JuliaFormatter using LaTeXStrings using SQLite using DataFrames -using Tar -using TOML -using Pkg using UUIDs using JuMP using SparseArrays using ProgressMeter +import JSONSchema +import Tar +import TOML +import Pkg +import HDF5 +import JSON import Random import QUBOTools import PseudoBooleanOptimization as PBO @@ -52,6 +52,10 @@ function __version__()::VersionNumber return __VERSION__[]::VersionNumber end +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 = """ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ ┃ ▄██████▄ ██ ██ █████▄ ▄██████▄ ┃ @@ -74,16 +78,13 @@ function print_logo(io::IO = stdout) return nothing end -include("interface/interface.jl") +include("interface.jl") include("library/path.jl") -include("library/index/index.jl") - -include("library/register.jl") -include("library/build.jl") -include("library/synthesis.jl") +include("library/index.jl") +include("library/access.jl") -include("actions/clear.jl") +include("library/synthesis/Synthesis.jl") include("main.jl") diff --git a/src/actions/clear.jl b/src/actions/clear.jl index da698f0..8b13789 100644 --- a/src/actions/clear.jl +++ b/src/actions/clear.jl @@ -1,21 +1 @@ -function QUBOLib.clear!(reg::Registry = GLOBAL_REG) - for source in reg.sources - QUBOLib.clear!(source) - end - return nothing -end - -function QUBOLib.clear!(source::Symbol) - QUBOLib.clear!(Val(source)) - - return nothing -end - -function QUBOLib.clear!(::Val{source}) where {source} - @assert source isa Symbol - - @warn "No clearing routine defined for '$(source)'" - - return nothing -end diff --git a/src/library/index/collection-data.schema.json b/src/assets/collection.schema.json similarity index 100% rename from src/library/index/collection-data.schema.json rename to src/assets/collection.schema.json diff --git a/src/library/index/qubolib.sql b/src/assets/qubolib.sql similarity index 86% rename from src/library/index/qubolib.sql rename to src/assets/qubolib.sql index 2997ef4..e23ec48 100644 --- a/src/library/index/qubolib.sql +++ b/src/assets/qubolib.sql @@ -32,12 +32,12 @@ CREATE TABLE Instances CREATE TABLE Solutions ( solution INTEGER PRIMARY KEY, - instance INTEGER NOT NULL, - solver TEXT NULL , - value REAL NOT NULL, - optimal BOOLEAN NOT NULL, + 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) + FOREIGN KEY (solver) REFERENCES Solvers (solver) ); CREATE TABLE Solvers 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/interface/interface.jl b/src/interface/interface.jl deleted file mode 100644 index e3cfd1b..0000000 --- a/src/interface/interface.jl +++ /dev/null @@ -1,57 +0,0 @@ -@doc raw""" - register!(source::Symbol) - -Registers an instance source. -""" -function register! end - -@doc raw""" - load!(source::Symbol, cache::Bool = true) -""" -function load! end - -@doc raw""" - clear!(source::Symbol, cache::Bool = true) -""" -function clear! end - -@doc raw""" - build!(source::Symbol) -""" -function build! end - -@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""" - add_solver!(index::LibraryIndex, code::Symbol, data::Dict{String,Any}) - -Registers a new solver in the library index. -""" -function add_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""" - add_solution!(index::LibraryIndex, instance::Integer, sol::SampleSet{Float64,Int}) - -Adds a new solution to the library index. - -The `sol` argument is a sample set, which is a collection of samples and their respective energies. -""" -function add_solution! end - -@doc raw""" - run!(index::LibraryIndex, instance::Integer, optimizer) - run!(index::LibraryIndex, instances::Vector{U}, optimizer) where {U<:Integer} -""" -function run! end diff --git a/src/library/access.jl b/src/library/access.jl new file mode 100644 index 0000000..aee4744 --- /dev/null +++ b/src/library/access.jl @@ -0,0 +1,80 @@ +function access(; path::AbstractString=library_path(), create::Bool = false) + db = load_database(database_path(path; create)) + h5 = load_archive(archive_path(path; create)) + + 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) +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(path) + h5 = create_archive(path) + + return LibraryIndex(db, h5) +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 = 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/deploy.jl b/src/library/deploy.jl deleted file mode 100644 index e69de29..0000000 diff --git a/src/library/index.jl b/src/library/index.jl index d723ffe..6cc48a6 100644 --- a/src/library/index.jl +++ b/src/library/index.jl @@ -1,160 +1,45 @@ -function _get_metadata(path::AbstractString, collection::AbstractString; validate::Bool=true) - metapath = joinpath(path, collection, "metadata.json") - metadata = JSON.parsefile(metapath) +@doc raw""" + LibraryIndex - if validate - report = JSONSchema.validate(_METADATA_SCHEMA, metadata) - - if !isnothing(report) - error( - """ - Invalid collection metadata for $(collection): - $(report) - """ - ) - end - end - - return metadata +The QUBOLib index is composed of two pieces: a SQLite database and an HDF5 archive. +""" +struct LibraryIndex + db::SQLite.DB + h5::HDF5.File end -function _get_metadata(index::InstanceIndex, collection::AbstractString; validate::Bool=true) - return _get_metadata(index.list_path, collection; validate=validate) +function Base.isopen(index::LibraryIndex) + return isopen(index.db) && isopen(index.h5) end - - -function hash!(index::InstanceIndex) - index.tree_hash[] = - - 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) +function Base.close(index::LibraryIndex) + if isopen(index.db) + close(index.db) + end - 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 + if isopen(index.h5) + close(index.h5) 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 +function database(index::LibraryIndex)::SQLite.DB + @assert isopen(index) - - - -function _problem_name(problem::AbstractString) - return _problem_name(data_path(), problem) + return index.db 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) +function archive(index::LibraryIndex)::HDF5.File + @assert isopen(index) - return only(df[!, :name]) + return index.h5 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) +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/index/archive.jl b/src/library/index/archive.jl deleted file mode 100644 index 6ff203a..0000000 --- a/src/library/index/archive.jl +++ /dev/null @@ -1,18 +0,0 @@ -function _load_archive(path::AbstractString, mode::AbstractString="cw") - 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/index/database.jl b/src/library/index/database.jl deleted file mode 100644 index fc492ca..0000000 --- a/src/library/index/database.jl +++ /dev/null @@ -1,35 +0,0 @@ -const QUBOLIB_SQL_PATH = joinpath(@__DIR__, "qubolib.sql") - -function _load_database(path::AbstractString) - if !isfile(path) - return nothing - else - return SQLite.DB(path) - end -end - -function _clear_database!(db) - DBInterface.execute(db, "DROP TABLE IF EXISTS Collections;") - DBInterface.execute(db, "DROP TABLE IF EXISTS Instances;") - DBInterface.execute(db, "DROP TABLE IF EXISTS Solutions;") - DBInterface.execute(db, "DROP TABLE IF EXISTS Solvers;") - - return nothing -end - -function _create_database(path::AbstractString) - # Remove file if it exists - rm(path; force=true) - - db = SQLite.DB(path) - - @info "Creating tables" - - open(QUBOLIB_SQL_PATH) do file - for stmt in (split(read(file, String), ";") .|> strip |> filter(!isempty)) - DBInterface.execute(db, stmt) - end - end - - return db -end diff --git a/src/library/index/index.jl b/src/library/index/index.jl deleted file mode 100644 index 3e10572..0000000 --- a/src/library/index/index.jl +++ /dev/null @@ -1,87 +0,0 @@ -include("database.jl") -include("archive.jl") - -@doc raw""" - LibraryIndex - -The QUBOLib index is composed of two parts: a SQLite database and an HDF5 archive. -""" -struct LibraryIndex - db::SQLite.DB - h5::HDF5.File -end - -function database(index::LibraryIndex) - @assert isopen(index) - - return index.db -end - -function archive(index::LibraryIndex) - @assert isopen(index) - - return index.h5 -end - -function Base.isopen(index::LibraryIndex) - return isopen(index.db) && isopen(index.h5) -end - -function Base.close(index::LibraryIndex) - close(index.db) - close(index.h5) - - return nothing -end - -function _create_index(path::AbstractString) - db = _create_database(database_path(path)) - h5 = _create_archive(archive_path(path)) - - return LibraryIndex(db, h5) -end - -@doc raw""" - load_index(path::AbstractString) - -Loads the library index from the given path. -""" -function load_index(path::AbstractString; create::Bool=false) - db = _load_database(database_path(path)) - h5 = _load_archive(archive_path(path)) - - if isnothing(db) || isnothing(h5) - if create - @info "Creating index at '$path'" - - return _create_index(path) - else - error("Failed to load index from '$path'") - - return nothing - end - end - - return LibraryIndex(db, h5) -end - -function load_index(callback::Function, path::AbstractString=qubolib_path(); create::Bool=false) - index = load_index(path; create) - - @assert isopen(index) - - try - return callback(index) - catch e - @error("Error during index access: $(sprint(showerror, e)))") - - return nothing - finally - close(index) - end -end - -include("collections.jl") -include("instances.jl") -include("solvers.jl") -include("solutions.jl") diff --git a/src/library/interface.jl b/src/library/interface.jl deleted file mode 100644 index bcd4275..0000000 --- a/src/library/interface.jl +++ /dev/null @@ -1,32 +0,0 @@ -@doc raw""" - build(; cache::Bool = true) -""" -function build end - -@doc raw""" - build!(index::Index; cache::Bool = true) - - build!(index::Index, collection::Collection; cache::Bool = true) -""" -function build! end - -@doc raw""" - load!(index::Index; cache::Bool = true) - - load!(index::Index, collection::Collection; cache::Bool = true) -""" -function load! end - -@doc raw""" - index!(index::Index) - - index!(index::Index, collection::Collection) -""" -function index! end - -@doc raw""" - document!(index::Index) - - document!(index::Index, collection::Collection) -""" -function document! end \ No newline at end of file diff --git a/src/library/management.jl b/src/library/management.jl deleted file mode 100644 index be83f53..0000000 --- a/src/library/management.jl +++ /dev/null @@ -1,40 +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") - -function get_metadata(coll::String) - return get_metadata(Symbol(coll)) -end - -function get_metadata(coll::Symbol) - return get_metadata(Val(coll)) -end - -function set_metadata(coll::Symbol, metadata::Dict{String, Any}) - return set_metadata(Val(coll), metadata) -end - -function validate_metadata(data::Dict{String, Any}) - @assert isnothing(JSONSchema.validate(data, COLLECTION_SCHEMA)) - - return nothing -end - - -end \ No newline at end of file diff --git a/src/library/path.jl b/src/library/path.jl index 03ad512..6626748 100644 --- a/src/library/path.jl +++ b/src/library/path.jl @@ -1,30 +1,89 @@ -function lib_path()::AbstractString - return abspath(artifact"qubolib") +@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 -function database_path(path::AbstractString=lib_path())::AbstractString - return abspath(build_path(path), "index.db") +@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)::AbstractString + return abspath(build_path(path; create), "index.db") end -function archive_path(path::AbstractString=lib_path())::AbstractString - return abspath(build_path(path), "archive.h5") +@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)::AbstractString + return abspath(build_path(path; create), "archive.h5") end # 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__ + return __project__() end -function dist_path(path::AbstractString=root_path())::AbstractString - return mkpath(abspath(path, "dist")) +raw""" + _get_path(path::AbstractString; create::Bool = false) +""" +function _get_path(path::AbstractString; create::Bool=false)::AbstractString + if ispath(path) + return abspath(path) + elseif create + return abspath(mkdir(path)) + else + error("Path '$path' does not exist") + + return nothing + end end -function build_path(path::AbstractString=root_path())::AbstractString - return mkpath(abspath(dist_path(path), "build")) +@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)::AbstractString + return _get_path(abspath(path, "dist"); create) end -function cache_path(path::AbstractString=root_path())::AbstractString - return mkpath(abspath(dist_path(path), "cache")) +@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)::AbstractString + return _get_path(abspath(dist_path(path; create), "build"); create) end +@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)::AbstractString + return _get_path(abspath(dist_path(path; create), "cache"); create) +end diff --git a/src/library/register.jl b/src/library/register.jl deleted file mode 100644 index 64af1f3..0000000 --- a/src/library/register.jl +++ /dev/null @@ -1,22 +0,0 @@ -@doc raw""" - -""" -struct Registry - sources::Vector{Symbol} - - Registry() = new(Symbol[]) -end - -const GLOBAL_REG = Registry() - -function register!(reg::Registry, source::Symbol) - push!(reg.source, source) - - return nothing -end - -function register!(source::Symbol) - register!(GLOBAL_REG, source) - - return nothing -end diff --git a/src/library/synthesis.jl b/src/library/synthesis.jl deleted file mode 100644 index 5409f1b..0000000 --- a/src/library/synthesis.jl +++ /dev/null @@ -1,15 +0,0 @@ -module Synthesis - -import Random -import QUBOTools -import ..QUBOLib -import PseudoBooleanOptimization as PBO - -include("synthesis/interface.jl") -include("synthesis/abstract.jl") -include("synthesis/nae3sat.jl") -include("synthesis/sherrington_kirkpatrick.jl") -include("synthesis/wishart.jl") -include("synthesis/xorsat.jl") - -end # module Synthesis 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/interface.jl b/src/library/synthesis/interface.jl index 47de0b7..005720e 100644 --- a/src/library/synthesis/interface.jl +++ b/src/library/synthesis/interface.jl @@ -4,8 +4,8 @@ abstract type AbstractProblem{T} end @doc raw""" - generate(problem) - generate(rng, problem) + 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). """ diff --git a/src/library/synthesis/xorsat.jl b/src/library/synthesis/xorsat.jl index 153604a..69facde 100644 --- a/src/library/synthesis/xorsat.jl +++ b/src/library/synthesis/xorsat.jl @@ -1,7 +1,7 @@ @doc raw""" - XORSAT{T} + XORSAT{T}(n::Integer, r::Integer = 3, k::Integer = 3) -``r``-regular ``k``-XORSAT +``r``-regular ``k``-XORSAT on ``n`` variables. """ struct XORSAT{T} <: AbstractProblem{T} n::Int diff --git a/src/library/build.jl b/src/old-library/build.jl similarity index 100% rename from src/library/build.jl rename to src/old-library/build.jl diff --git a/src/library/document.jl b/src/old-library/document.jl similarity index 100% rename from src/library/document.jl rename to src/old-library/document.jl diff --git a/src/old-library/index.jl b/src/old-library/index.jl new file mode 100644 index 0000000..d723ffe --- /dev/null +++ b/src/old-library/index.jl @@ -0,0 +1,160 @@ +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 hash!(index::InstanceIndex) + index.tree_hash[] = + + 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 + + + + + +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 + diff --git a/src/library/index/collections.jl b/src/old-library/index/collections.jl similarity index 91% rename from src/library/index/collections.jl rename to src/old-library/index/collections.jl index 84a2696..0e5d9d9 100644 --- a/src/library/index/collections.jl +++ b/src/old-library/index/collections.jl @@ -1,6 +1,4 @@ -const COLLECTION_DATA_SCHEMA = JSONSchema.Schema( - JSON.parsefile(joinpath(@__DIR__, "collection-data.schema.json")) -) + function has_collection(index::LibraryIndex, code::Symbol) @assert isopen(index) diff --git a/src/library/index/instances.jl b/src/old-library/index/instances.jl similarity index 100% rename from src/library/index/instances.jl rename to src/old-library/index/instances.jl diff --git a/src/library/index/solutions.jl b/src/old-library/index/solutions.jl similarity index 100% rename from src/library/index/solutions.jl rename to src/old-library/index/solutions.jl diff --git a/src/library/index/solvers.jl b/src/old-library/index/solvers.jl similarity index 100% rename from src/library/index/solvers.jl rename to src/old-library/index/solvers.jl From d0541041991be4c2618b45576f504193579a57f3 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Sun, 29 Sep 2024 10:41:36 -0400 Subject: [PATCH 18/23] Enhance error messages --- src/library/access.jl | 32 +++++++++++++++++++---------- src/library/path.jl | 48 ++++++++++++++++++++++++++++++++----------- 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/src/library/access.jl b/src/library/access.jl index aee4744..7b13617 100644 --- a/src/library/access.jl +++ b/src/library/access.jl @@ -1,11 +1,18 @@ -function access(; path::AbstractString=library_path(), create::Bool = false) - db = load_database(database_path(path; create)) - h5 = load_archive(archive_path(path; create)) +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 + else error("Failed to load index from '$path'") return nothing @@ -15,7 +22,7 @@ function access(; path::AbstractString=library_path(), create::Bool = false) return LibraryIndex(db, h5) end -function access(callback::Any; path::AbstractString=library_path(), create::Bool=false) +function access(callback::Any; path::AbstractString = library_path(), create::Bool = false) index = access(; path, create) @assert isopen(index) @@ -28,8 +35,8 @@ function access(callback::Any; path::AbstractString=library_path(), create::Bool end function create_index(path::AbstractString) - db = create_database(path) - h5 = create_archive(path) + db = create_database(database_path(path; create = true)) + h5 = create_archive(archive_path(path; create = true)) return LibraryIndex(db, h5) end @@ -43,12 +50,12 @@ function load_database(path::AbstractString)::Union{SQLite.DB,Nothing} end function create_database(path::AbstractString) - rm(path; force=true) # Remove file if it exists + rm(path; force = true) # Remove file if it exists db = SQLite.DB(path) open(QUBOLIB_SQL_PATH) do file - for stmt = eachsplit(read(file, String), ';') + for stmt in eachsplit(read(file, String), ';') stmt = strip(stmt) if !isempty(stmt) @@ -60,7 +67,10 @@ function create_database(path::AbstractString) return db end -function load_archive(path::AbstractString; mode::AbstractString="cw")::Union{HDF5.File,Nothing} +function load_archive( + path::AbstractString; + mode::AbstractString = "cw", +)::Union{HDF5.File,Nothing} if !isfile(path) return nothing else @@ -69,7 +79,7 @@ function load_archive(path::AbstractString; mode::AbstractString="cw")::Union{HD end function create_archive(path::AbstractString) - rm(path; force=true) # remove file if it exists + rm(path; force = true) # remove file if it exists h5 = HDF5.h5open(path, "w") diff --git a/src/library/path.jl b/src/library/path.jl index 6626748..6636d28 100644 --- a/src/library/path.jl +++ b/src/library/path.jl @@ -13,8 +13,12 @@ end Returns the absolute path to the database file, given a reference `path`. """ -function database_path(path::AbstractString=library_path(); create::Bool=false)::AbstractString - return abspath(build_path(path; create), "index.db") +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 @doc raw""" @@ -22,8 +26,12 @@ end Returns the absolute path to the archive file, given a reference `path`. """ -function archive_path(path::AbstractString=library_path(); create::Bool=false)::AbstractString - return abspath(build_path(path; create), "archive.h5") +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 # Functions below will be more often used when building the library, @@ -46,13 +54,17 @@ end raw""" _get_path(path::AbstractString; create::Bool = false) """ -function _get_path(path::AbstractString; create::Bool=false)::AbstractString +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 - error("Path '$path' does not exist") + ifmissing(path) return nothing end @@ -64,8 +76,12 @@ end 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)::AbstractString - return _get_path(abspath(path, "dist"); create) +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 @doc raw""" @@ -74,8 +90,12 @@ end 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)::AbstractString - return _get_path(abspath(dist_path(path; create), "build"); create) +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 @doc raw""" @@ -84,6 +104,10 @@ end 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)::AbstractString - return _get_path(abspath(dist_path(path; create), "cache"); create) +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 From 6d0938f99fa585e68496d9cc547fede056a47427 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Sun, 29 Sep 2024 21:50:21 -0400 Subject: [PATCH 19/23] Refactor code --- Makefile | 4 +- docs/src/manual/2-advanced.md | 10 +- src/QUBOLib.jl | 5 + src/actions/deploy.jl | 72 +++++++- src/{old-library => actions}/document.jl | 0 src/assets/qubolib.sql | 40 ++++- src/library/collections.jl | 87 ++++++++++ .../index => library}/instances.jl | 36 +++- .../index => library}/solutions.jl | 42 ++--- src/{old-library/index => library}/solvers.jl | 26 +-- src/old-library/build.jl | 27 --- src/old-library/index.jl | 160 ------------------ src/old-library/index/collections.jl | 62 ------- .../collectionX/data/problemx.qubo | 12 -- .../collectionX/data/problemx2.qubo | 12 -- test/collections/collectionX/metadata.json | 1 - .../collectionY/data/problemy.json | 80 --------- .../collectionY/data/problemy2.json | 76 --------- test/collections/collectionY/metadata.json | 1 - 19 files changed, 262 insertions(+), 491 deletions(-) rename src/{old-library => actions}/document.jl (100%) create mode 100644 src/library/collections.jl rename src/{old-library/index => library}/instances.jl (65%) rename src/{old-library/index => library}/solutions.jl (59%) rename src/{old-library/index => library}/solvers.jl (64%) delete mode 100644 src/old-library/build.jl delete mode 100644 src/old-library/index.jl delete mode 100644 src/old-library/index/collections.jl delete mode 100644 test/collections/collectionX/data/problemx.qubo delete mode 100644 test/collections/collectionX/data/problemx2.qubo delete mode 100644 test/collections/collectionX/metadata.json delete mode 100644 test/collections/collectionY/data/problemy.json delete mode 100644 test/collections/collectionY/data/problemy2.json delete mode 100644 test/collections/collectionY/metadata.json diff --git a/Makefile b/Makefile index db7ff99..78d2538 100644 --- a/Makefile +++ b/Makefile @@ -8,8 +8,8 @@ setup: build: @julia --project=scripts/build ./scripts/build/build.jl -clear-build: - @julia --project=scripts/build ./scripts/build/build.jl --clear-build +clear: + @rm -rf ./dist run: setup @julia --project=scripts/run/mqlib ./scripts/run/mqlib/run.jl diff --git a/docs/src/manual/2-advanced.md b/docs/src/manual/2-advanced.md index 38e1ae2..2c5d5ef 100644 --- a/docs/src/manual/2-advanced.md +++ b/docs/src/manual/2-advanced.md @@ -1,5 +1,12 @@ # Advanced Usage +## Adding a new collection + +## Acessing Internal Data + +One is able to acess the database and archive of a [`LibraryIndex`](@ref) by recalling the [`QUBOLib.database`](@ref) and [`QUBOLib.archive`](@ref) functions. + + ```julia using QUBOLib @@ -7,4 +14,5 @@ QUBOLib.access() do index db = QUBOLib.database(index) h5 = QUBOLib.archive(index) end -``` \ No newline at end of file +``` + diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index a1598b8..92eca3d 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -84,6 +84,11 @@ include("library/path.jl") include("library/index.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("main.jl") diff --git a/src/actions/deploy.jl b/src/actions/deploy.jl index c0459c4..ae8e0a2 100644 --- a/src/actions/deploy.jl +++ b/src/actions/deploy.jl @@ -1,3 +1,73 @@ -function QUBOLib.deploy!(target::AbstractString) +function deploy(path::AbstractString) + # Calculate tree hash + tree_hash = bytes2hex(Pkg.GitTools.tree_hash(dist_path(path))) + + # Build tarball + temp_path = abspath(Tar.create(dist_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) + 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 diff --git a/src/old-library/document.jl b/src/actions/document.jl similarity index 100% rename from src/old-library/document.jl rename to src/actions/document.jl diff --git a/src/assets/qubolib.sql b/src/assets/qubolib.sql index e23ec48..ba981f2 100644 --- a/src/assets/qubolib.sql +++ b/src/assets/qubolib.sql @@ -4,12 +4,36 @@ CREATE TABLE Collections ( collection TEXT PRIMARY KEY, name TEXT NOT NULL , - author TEXT NULL , - year INTEGER NULL , - description TEXT NULL , - url TEXT 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, @@ -33,7 +57,7 @@ CREATE TABLE Solutions ( solution INTEGER PRIMARY KEY, instance INTEGER NOT NULL , - solver TEXT NULL , + solver TEXT NULL , value REAL NOT NULL , optimal BOOLEAN NOT NULL , FOREIGN KEY (instance) REFERENCES Instances (instance) ON DELETE CASCADE, @@ -42,7 +66,7 @@ CREATE TABLE Solutions CREATE TABLE Solvers ( - solver TEXT NOT NULL, - quantum BOOLEAN NOT NULL DEFAULT FALSE, - PRIMARY KEY (solver) + solver TEXT PRIMARY KEY, + version TEXT NULL, + description TEXT NULL, ); 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/old-library/index/instances.jl b/src/library/instances.jl similarity index 65% rename from src/old-library/index/instances.jl rename to src/library/instances.jl index f9968c5..640e374 100644 --- a/src/old-library/index/instances.jl +++ b/src/library/instances.jl @@ -1,15 +1,19 @@ function add_instance!( index::LibraryIndex, - coll::Symbol, 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)) - q = DBInterface.execute( - index.db, + query = DBInterface.execute( + db, """ INSERT INTO Instances ( collection , @@ -43,7 +47,7 @@ function add_instance!( ); """, ( - string(coll), + String(collection), QUBOTools.dimension(model), min(minimum(L), minimum(Q)), max(maximum(L), maximum(Q)), @@ -59,16 +63,32 @@ function add_instance!( ), ) - i = DBInterface.lastrowid(q) - g = HDF5.create_group(index.h5["instances"], string(i)) + i = DBInterface.lastrowid(query)::Integer + + group = HDF5.create_group(h5["instances"], string(i)) - QUBOTools.write_model(g, model, QUBOTools.QUBin()) + 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) - return QUBOTools.read_model(index.h5["instances"][string(i)], QUBOTools.QUBin()) + h5 = QUBOLib.archive(index) + + return QUBOTools.read_model(h5["instances"][string(i)], QUBOTools.QUBin()) end diff --git a/src/old-library/index/solutions.jl b/src/library/solutions.jl similarity index 59% rename from src/old-library/index/solutions.jl rename to src/library/solutions.jl index fe3d1a4..d87130b 100644 --- a/src/old-library/index/solutions.jl +++ b/src/library/solutions.jl @@ -19,44 +19,32 @@ function add_solution!(index::LibraryIndex, instance::Integer, sol::QUBOTools.Sa @assert isopen(index) @assert !isempty(sol) - if isempty(sol) - return nothing - end - data = QUBOTools.metadata(sol) solver = get(data, "solver", nothing) value = QUBOTools.value(sol, 1) + optimal = get(data, "status", nothing) == "optimal" - q = DBInterface.execute( - index.db, + db = QUBOLib.database(index) + h5 = QUBOLib.archive(index) + + query = DBInterface.execute( + db, """ - INSERT INTO Solutions ( - instance, - solver, - value, - optimal - ) - VALUES ( - ?, - ?, - ?, - ? - ) + INSERT INTO Solutions + (instance, solver, value, optimal) + VALUES + (?, ?, ?, ?) """, - ( - instance, - solver, - value, - optimal, - ) + (instance, solver, value, optimal) ) - i = DBInterface.lastrowid(q) - g = HDF5.create_group(index.h5["solutions"], string(i)) + i = DBInterface.lastrowid(query)::Integer + + group = HDF5.create_group(h5["solutions"], string(i)) - _write_solution(g, sol, QUBOTools.QUBin()) + QUBOTools.write_solution(group, sol, QUBOTools.QUBin()) return i end diff --git a/src/old-library/index/solvers.jl b/src/library/solvers.jl similarity index 64% rename from src/old-library/index/solvers.jl rename to src/library/solvers.jl index 622e34d..3b60686 100644 --- a/src/old-library/index/solvers.jl +++ b/src/library/solvers.jl @@ -10,24 +10,24 @@ function has_solver(index::LibraryIndex, solver::String) return (only(df[!, 1]) > 0) end -function add_solver!(index::LibraryIndex, solver::String, version::String, desc::String) +function add_solver!(index::LibraryIndex, solver::AbstractString, data::Dict{String,Any}) @assert isopen(index) + db = QUBOLib.database(index) + DBInterface.execute( - index.db, + db, """ - INSERT INTO solvers ( - solver, - version, - description, - ) - VALUES ( - ?, - ?, - ? - ) + INSERT INTO solvers + (solver, version, description) + VALUES + (?, ?, ?); """, - (name, version, desc), + ( + String(solver), + get(data, "version", missing), + get(data, "description", missing), + ), ) return nothing diff --git a/src/old-library/build.jl b/src/old-library/build.jl deleted file mode 100644 index 579f5ea..0000000 --- a/src/old-library/build.jl +++ /dev/null @@ -1,27 +0,0 @@ -# function build( -# collections::AbstractVector, -# root_path::AbstractString, -# dist_path::AbstractString=abspath(root_path, "dist"); -# cache::Bool=true, -# ) -# index = create_index(root_path, dist_path) - -# for coll in collections -# build!(index, coll; cache) -# end - -# # Compute Tree hash -# tree_hash = bytes2hex(Pkg.GitTools.tree_hash(dist_path)) - -# return index -# end - -# function build!(index::LibraryIndex, coll::Collection; cache::Bool=true) -# if !(cache && has_collection(index, coll)) -# load!(index, coll; cache) -# index!(index, coll) -# document!(index, coll) -# end - -# return nothing -# end diff --git a/src/old-library/index.jl b/src/old-library/index.jl deleted file mode 100644 index d723ffe..0000000 --- a/src/old-library/index.jl +++ /dev/null @@ -1,160 +0,0 @@ -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 hash!(index::InstanceIndex) - index.tree_hash[] = - - 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 - - - - - -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 - diff --git a/src/old-library/index/collections.jl b/src/old-library/index/collections.jl deleted file mode 100644 index 0e5d9d9..0000000 --- a/src/old-library/index/collections.jl +++ /dev/null @@ -1,62 +0,0 @@ - - -function has_collection(index::LibraryIndex, code::Symbol) - @assert isopen(index) - - df = DBInterface.execute( - index.db, - "SELECT COUNT(*) FROM collections WHERE collection = ?", - (string(code),) - ) |> DataFrame - - return only(df[!, 1]) > 0 -end - -function add_collection!( - index::LibraryIndex, - code::Symbol, - data::Dict{String,Any} -) - @assert isopen(index) - - let report = JSONSchema.validate(data, COLLECTION_DATA_SCHEMA) - if !isnothing(report) - error("Invalid collection data:\n$report") - end - end - - if has_collection(index, code) - error("Collection '$code' already exists") - else - DBInterface.execute( - index.db, - "INSERT INTO collections (collection, name) VALUES (?, ?)", - ( - string(code), - data["name"], - ) - ) - - @info "Collection '$code' added to index" - end - - 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 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 From 04576117bc84e110201e6c14bf6662bbe548706c Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Mon, 30 Sep 2024 01:23:25 -0400 Subject: [PATCH 20/23] Update deployment scripts --- scripts/Project.toml | 2 - scripts/build/arXiv_1903_10928_3r3x.jl | 44 +++++++------ scripts/build/arXiv_1903_10928_5r5x.jl | 42 ++++++------ scripts/build/build.jl | 52 +++++++++++++++ scripts/build/qplib.jl | 90 +++++++++++++++----------- scripts/run/dwave/dwave.jl | 7 +- src/QUBOLib.jl | 5 +- src/actions/build.jl | 50 -------------- src/actions/clear.jl | 10 +++ src/actions/deploy.jl | 15 ++--- src/assets/qubolib.sql | 2 +- src/library/access.jl | 4 +- src/library/index.jl | 6 ++ src/library/path.jl | 46 +++++++------ src/library/solutions.jl | 2 +- 15 files changed, 212 insertions(+), 165 deletions(-) create mode 100644 scripts/build/build.jl diff --git a/scripts/Project.toml b/scripts/Project.toml index a9406b6..26bdf3a 100644 --- a/scripts/Project.toml +++ b/scripts/Project.toml @@ -3,7 +3,5 @@ ArgParse = "c7e460c6-2fb9-53a9-8c5b-16f535851c63" DBInterface = "a10d1c49-ce27-4219-8d33-6db1a4562965" DataFrames = "a93c6f00-e57d-5684-b7b6-d8193f3e46c0" JuMP = "4076af6c-e467-56ae-b986-b466b2749572" -MQLib = "16f11440-1623-44c9-850c-358a6c72f3c9" -PySA = "46b73cfa-376a-48ee-8926-0c45ac3f7830" 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 index 5e576ab..0a349a0 100644 --- a/scripts/build/arXiv_1903_10928_3r3x.jl +++ b/scripts/build/arXiv_1903_10928_3r3x.jl @@ -1,18 +1,19 @@ const ARXIV_1903_10928_3R3X_URL = "https://sites.usc.edu/itayhen/files/2019/09/3r3x.zip" -function _load_arXiv_1903_10928_3r3x!() +function load_arXiv_1903_10928_3r3x!(index::QUBOLib.LibraryIndex) @info "[arXiv_1903_10928_3r3x] Downloading instances" - arXiv_1903_10928_3r3x_cache_path = mkpath(abspath(QUBOLib.cache_path(), "arXiv_1903_10928_3r3x")) - arXiv_1903_10928_3r3x_data_path = mkpath(abspath(arXiv_1903_10928_3r3x_cache_path, "data")) - arXiv_1903_10928_3r3x_zip_path = abspath(arXiv_1903_10928_3r3x_cache_path, "arXiv_1903_10928_3r3x.zip") + _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(arXiv_1903_10928_3r3x_zip_path) + 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, arXiv_1903_10928_3r3x_zip_path) + + Downloads.download(ARXIV_1903_10928_5R5X_URL, _zip_path) end # Extract arXiv_1903_10928 3r3x archive @@ -22,47 +23,50 @@ function _load_arXiv_1903_10928_3r3x!() run(``` unzip -qq -o -j - $arXiv_1903_10928_3r3x_zip_path + $_zip_path 'instance*.txt' - -d $arXiv_1903_10928_3r3x_data_path + -d $_data_path ```) return nothing end -function build_arXiv_1903_10928_3r3x!(index::LibraryIndex; cache::Bool = true) - if QUBOLib.has_collection(index, :arXiv_1903_10928_3r3x) +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) + QUBOLib.remove_collection!(index, "arXiv-1903-10928-3r3x") end end QUBOLib.add_collection!( index, - :arXiv_1903_10928_3r3x, + "arXiv-1903-10928-3r3x", Dict{String,Any}( - "name" => "arXiv_1903_10928_3r3x", - "title" => "5R5X instances for \"Equation Planting: A Tool for Benchmarking Ising Machines\"", + "name" => "3-Regular 3-XORSAT (arXiv:1903.10928)", "author" => ["Itay Hen"], - "description" => "The Quadratic Programming Library", + "description" => "3R3X instances for \"Equation Planting: A Tool for Benchmarking Ising Machines\"", "year" => 2019, - "url" => ARXIV_1903_10928_5R5X_URL, + "url" => ARXIV_1903_10928_3R3X_URL, ), ) - _load_arXiv_1903_10928_3r3x!() + load_arXiv_1903_10928_3r3x!(index) - arXiv_1903_10928_3r3x_data_path = abspath(QUBOLib.cache_path(), "arXiv_1903_10928_3r3x", "data") + _data_path = abspath( + QUBOLib.cache_path(index), + "arXiv-1903-10928-3r3x", + "data", + ) @info "[arXiv_1903_10928_3r3x] Building index" - for path in readdir(arXiv_1903_10928_3r3x_data_path; join=true) + for path in readdir(_data_path; join = true) model = QUBOTools.read_model(path, QUBOTools.Qubist()) - mod_i = QUBOLib.add_instance!(index, :arXiv_1903_10928_3r3x, model) + 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'" diff --git a/scripts/build/arXiv_1903_10928_5r5x.jl b/scripts/build/arXiv_1903_10928_5r5x.jl index d24487b..b1179ec 100644 --- a/scripts/build/arXiv_1903_10928_5r5x.jl +++ b/scripts/build/arXiv_1903_10928_5r5x.jl @@ -1,18 +1,19 @@ const ARXIV_1903_10928_5R5X_URL = "https://sites.usc.edu/itayhen/files/2019/09/5r5x.zip" -function _load_arXiv_1903_10928_5r5x!() +function load_arXiv_1903_10928_5r5x!(index::QUBOLib.LibraryIndex) @info "[arXiv_1903_10928_5r5x] Downloading instances" - arXiv_1903_10928_5r5x_cache_path = mkpath(abspath(QUBOLib.cache_path(), "arXiv_1903_10928_5r5x")) - arXiv_1903_10928_5r5x_data_path = mkpath(abspath(arXiv_1903_10928_5r5x_cache_path, "data")) - arXiv_1903_10928_5r5x_zip_path = abspath(arXiv_1903_10928_5r5x_cache_path, "arXiv_1903_10928_5r5x.zip") + _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(arXiv_1903_10928_5r5x_zip_path) + 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, arXiv_1903_10928_5r5x_zip_path) + + Downloads.download(ARXIV_1903_10928_5R5X_URL, _zip_path) end # Extract arXiv_1903_10928 5r5x archive @@ -22,47 +23,50 @@ function _load_arXiv_1903_10928_5r5x!() run(``` unzip -qq -o -j - $arXiv_1903_10928_5r5x_zip_path + $_zip_path 'instance*.txt' - -d $arXiv_1903_10928_5r5x_data_path + -d $_data_path ```) return nothing end -function build_arXiv_1903_10928_5r5x!(index::LibraryIndex; cache::Bool = true) - if QUBOLib.has_collection(index, :arXiv_1903_10928_5r5x) +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) + QUBOLib.remove_collection!(index, "arXiv-1903-10928-5r5x") end end QUBOLib.add_collection!( index, - :arXiv_1903_10928_5r5x, + "arXiv-1903-10928-5r5x", Dict{String,Any}( - "name" => "arXiv_1903_10928_5r5x", - "title" => "5R5X instances for \"Equation Planting: A Tool for Benchmarking Ising Machines\"", + "name" => "5-Regular 5-XORSAT (arXiv:1903.10928)", "author" => ["Itay Hen"], - "description" => "The Quadratic Programming Library", + "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!() + load_arXiv_1903_10928_5r5x!(index) - arXiv_1903_10928_5r5x_data_path = abspath(QUBOLib.cache_path(), "arXiv_1903_10928_5r5x", "data") + _data_path = abspath( + QUBOLib.cache_path(index), + "arXiv-1903-10928-5r5x", + "data", + ) @info "[arXiv_1903_10928_5r5x] Building index" - for path in readdir(arXiv_1903_10928_5r5x_data_path; join=true) + for path in readdir(_data_path; join = true) model = QUBOTools.read_model(path, QUBOTools.Qubist()) - mod_i = QUBOLib.add_instance!(index, :arXiv_1903_10928_5r5x, model) + 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'" 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 index f6613b1..65ba73c 100644 --- a/scripts/build/qplib.jl +++ b/scripts/build/qplib.jl @@ -1,6 +1,10 @@ # 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) @@ -24,7 +28,7 @@ function _read_qplib_float(io::IO, ∞::AbstractString) end end -function _read_qplib_model(io::IO) +function QUBOTools.read_model(io::IO, ::Format{:qplib}) # Read the header code = _read_qplib_line(io) @@ -102,8 +106,7 @@ function _read_qplib_model(io::IO) end for _ = 1:ln - let - line = _read_qplib_line(io) + let line = _read_qplib_line(io) m = match(r"([0-9]+)\s+(\S+)", line) if isnothing(m) @@ -121,25 +124,23 @@ function _read_qplib_model(io::IO) β = parse(Float64, _read_qplib_line(io)) # objective constant - ∞ = _read_qplib_line(io) # value for infinity + inf = _read_qplib_line(io) # value for infinity - @assert _read_qplib_float(io, ∞) isa Float64 # default variable primal value in starting point + @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, ∞) isa Float64 # default variable bound dual value 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 '$code'", + V, L, Q; + offset = β, + domain = :bool, + sense = (sense == "minimize") ? :min : :max, + description = "QPLib instance no. $code", ) end @@ -234,14 +235,14 @@ end const QPLIB_URL = "http://qplib.zib.de/qplib.zip" -function build_qplib!(index::LibraryIndex; cache::Bool = true) - if QUBOLib.has_collection(index, :qplib) +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) + QUBOLib.remove_collection!(index, "qplib") end end @@ -249,29 +250,43 @@ function build_qplib!(index::LibraryIndex; cache::Bool = true) QUBOLib.add_collection!( index, - :qplib, + "qplib", Dict{String,Any}( "name" => "QPLIB", - "author" => ["", ""], + "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!() + code_list = load_qplib!(index) @info "[qplib] Building index" - qplib_data_path = abspath(QUBOLib.cache_path(), "qplib", "data") + _data_path = abspath(QUBOLib.cache_path(index; create = true), "qplib", "data") for code in code_list - mod_path = joinpath(qplib_data_path, "$(code).qplib") - var_path = joinpath(qplib_data_path, "$(code).lp") - sol_path = joinpath(qplib_data_path, "$(code).sol") + mod_path = joinpath(_data_path, "$(code).qplib") + var_path = joinpath(_data_path, "$(code).lp") + sol_path = joinpath(_data_path, "$(code).sol") - model = _read_qplib_model(mod_path) - mod_i = QUBOLib.add_instance!(index, :qplib, model) + 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) @@ -291,19 +306,20 @@ function build_qplib!(index::LibraryIndex; cache::Bool = true) return nothing end -function _load_qplib!() +function load_qplib!(index::QUBOLib.LibraryIndex) @assert Sys.isunix() "Processing QPLIB is only possible on Unix systems" - qplib_cache_path = mkpath(abspath(QUBOLib.cache_path(), "qplib")) - qplib_data_path = mkpath(abspath(qplib_cache_path, "data")) - qplib_zip_path = abspath(qplib_cache_path, "qplib.zip") + _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(qplib_zip_path) + if isfile(_zip_path) @info "[qplib] Archive already downloaded" else @info "[qplib] Downloading archive" - Downloads.download(QPLIB_URL, qplib_zip_path) + + Downloads.download(QPLIB_URL, _zip_path) end # Extract QPLIB archive @@ -313,11 +329,11 @@ function _load_qplib!() run(``` unzip -qq -o -j - $qplib_zip_path + $_zip_path 'qplib/html/qplib/*' 'qplib/html/sol/*' 'qplib/html/lp/*' - -d $qplib_data_path + -d $_data_path ```) # Remove non-QUBO instances @@ -325,13 +341,13 @@ function _load_qplib!() code_list = String[] - for file_path in filter(endswith(".qplib"), readdir(qplib_data_path; join = true)) + for file_path in filter(endswith(".qplib"), readdir(_data_path; join = true)) code = readline(file_path) if !_is_qplib_qubo(file_path) - rm(joinpath(qplib_data_path, "$(code).qplib"); force = true) - rm(joinpath(qplib_data_path, "$(code).lp"); force = true) - rm(joinpath(qplib_data_path, "$(code).sol"); force = true) + 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 diff --git a/scripts/run/dwave/dwave.jl b/scripts/run/dwave/dwave.jl index fb9272f..1f2960b 100644 --- a/scripts/run/dwave/dwave.jl +++ b/scripts/run/dwave/dwave.jl @@ -1,8 +1,7 @@ using DWave function main() - QUBOLib.logo() - QUBOLib.load_index(QUBOLib.root_path(); create = false) do index + 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;" @@ -11,10 +10,10 @@ function main() codes = collect(Int, df[!, :instance]) @info "Running DWave Neal" - QUBOLib.run!(index, DWave.Neal.Optimizer, codes; solver = Symbol("dwave-neal")) + QUBOLib.run!(index, DWave.Neal.Optimizer, codes; solver = "dwave-neal") @info "Running DWave (Quantum)" - QUBOLib.run!(index, DWave.Optimizer, codes; solver = :dwave) + QUBOLib.run!(index, DWave.Optimizer, codes; solver = "dwave") end return nothing diff --git a/src/QUBOLib.jl b/src/QUBOLib.jl index 92eca3d..561694b 100644 --- a/src/QUBOLib.jl +++ b/src/QUBOLib.jl @@ -80,8 +80,8 @@ end include("interface.jl") -include("library/path.jl") include("library/index.jl") +include("library/path.jl") include("library/access.jl") include("library/instances.jl") @@ -91,6 +91,9 @@ include("library/solutions.jl") include("library/synthesis/Synthesis.jl") +include("actions/clear.jl") +include("actions/deploy.jl") + include("main.jl") end # module QUBOLib diff --git a/src/actions/build.jl b/src/actions/build.jl index ed9877f..e69de29 100644 --- a/src/actions/build.jl +++ b/src/actions/build.jl @@ -1,50 +0,0 @@ -function build!(reg::Registry) - -end - -function build!(source::Symbol) - build!(Val(source)) - - return nothing -end - -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 - @info "Clearing Build" - - rm(QUBOLib.build_path(path); force = true, recursive = true) - end - - if clear_cache - @info "Clearing Cache" - - rm(QUBOLib.cache_path(path); force = true, recursive = true) - end - - QUBOLib.load_index(path; create = true) do index - build_qplib!(index) - build_arXiv_1903_10928_3r3x!(index) - build_arXiv_1903_10928_5r5x!(index) - end - - return nothing -end - - -function build() - 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/src/actions/clear.jl b/src/actions/clear.jl index 8b13789..1a002c3 100644 --- a/src/actions/clear.jl +++ b/src/actions/clear.jl @@ -1 +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 index ae8e0a2..ac98cdc 100644 --- a/src/actions/deploy.jl +++ b/src/actions/deploy.jl @@ -1,9 +1,9 @@ function deploy(path::AbstractString) # Calculate tree hash - tree_hash = bytes2hex(Pkg.GitTools.tree_hash(dist_path(path))) + tree_hash = bytes2hex(Pkg.GitTools.tree_hash(build_path(path))) # Build tarball - temp_path = abspath(Tar.create(dist_path(path))) + temp_path = abspath(Tar.create(build_path(path))) # Compress tarball run(`gzip -9 $temp_path`) @@ -18,11 +18,14 @@ function deploy(path::AbstractString) # 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 tag(path::AbstractString) +function next_tag(path::AbstractString) last_tag = if haskey(ENV, "LAST_QUBOLIB_TAG") x = tryparse(VersionNumber, ENV["LAST_QUBOLIB_TAG"]) @@ -65,9 +68,3 @@ function tag(path::AbstractString) return "v$next_tag" end - -function tag!(index::InstanceIndex) - index.next_tag[] = tag(index.root_path) - - return nothing -end diff --git a/src/assets/qubolib.sql b/src/assets/qubolib.sql index ba981f2..897a03c 100644 --- a/src/assets/qubolib.sql +++ b/src/assets/qubolib.sql @@ -68,5 +68,5 @@ CREATE TABLE Solvers ( solver TEXT PRIMARY KEY, version TEXT NULL, - description TEXT NULL, + description TEXT NULL ); diff --git a/src/library/access.jl b/src/library/access.jl index 7b13617..6eb8322 100644 --- a/src/library/access.jl +++ b/src/library/access.jl @@ -19,7 +19,7 @@ function access(; path::AbstractString = library_path(), create::Bool = false) end end - return LibraryIndex(db, h5) + return LibraryIndex(db, h5; path) end function access(callback::Any; path::AbstractString = library_path(), create::Bool = false) @@ -38,7 +38,7 @@ 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) + return LibraryIndex(db, h5; path) end function load_database(path::AbstractString)::Union{SQLite.DB,Nothing} diff --git a/src/library/index.jl b/src/library/index.jl index 6cc48a6..931bdf2 100644 --- a/src/library/index.jl +++ b/src/library/index.jl @@ -6,6 +6,12 @@ The QUBOLib index is composed of two pieces: a SQLite database and an HDF5 archi 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) diff --git a/src/library/path.jl b/src/library/path.jl index 6636d28..2d96630 100644 --- a/src/library/path.jl +++ b/src/library/path.jl @@ -1,3 +1,20 @@ +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 @@ -21,6 +38,8 @@ function database_path( 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 @@ -34,6 +53,8 @@ function archive_path( 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. @@ -51,25 +72,6 @@ function root_path()::AbstractString return __project__() end -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 - ifmissing(path) - - return nothing - end -end - @doc raw""" dist_path(path::AbstractString=root_path())::AbstractString @@ -84,6 +86,8 @@ function dist_path( 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 @@ -98,6 +102,8 @@ function build_path( 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 @@ -111,3 +117,5 @@ function cache_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 index d87130b..759177b 100644 --- a/src/library/solutions.jl +++ b/src/library/solutions.jl @@ -44,7 +44,7 @@ function add_solution!(index::LibraryIndex, instance::Integer, sol::QUBOTools.Sa group = HDF5.create_group(h5["solutions"], string(i)) - QUBOTools.write_solution(group, sol, QUBOTools.QUBin()) + _write_solution(group, sol, QUBOTools.QUBin()) return i end From 082526185f439cdaac5ea5de18b0845ed9c7f8e8 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Mon, 30 Sep 2024 01:44:09 -0400 Subject: [PATCH 21/23] Add action for later reference --- .github/workflows/deploy.yml | 58 ++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 .github/workflows/deploy.yml 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 From a5de4e184ab7f99f5afb50ba032914de62617ece Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Mon, 30 Sep 2024 01:53:40 -0400 Subject: [PATCH 22/23] Fix tests and docs --- docs/make.jl | 5 +++-- docs/src/manual/2-advanced.md | 2 +- test/synthesis.jl | 4 ++-- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/make.jl b/docs/make.jl index 9d15167..cf67934 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -27,8 +27,9 @@ makedocs(; "Home" => "index.md", "API" => "api.md", "Manual" => [ - "Introduction" => "manual/0-intro.md", - "Basic Usage" => "manual/1-basic.md", + "Introduction" => "manual/0-intro.md", + "Basic Usage" => "manual/1-basic.md", + "Advanced Usage" => "manual/2-advanced.md", ], # "Booklet" => [ # "Introduction" => "booklet/0-intro.md", diff --git a/docs/src/manual/2-advanced.md b/docs/src/manual/2-advanced.md index 2c5d5ef..cbdf32a 100644 --- a/docs/src/manual/2-advanced.md +++ b/docs/src/manual/2-advanced.md @@ -4,7 +4,7 @@ ## Acessing Internal Data -One is able to acess the database and archive of a [`LibraryIndex`](@ref) by recalling the [`QUBOLib.database`](@ref) and [`QUBOLib.archive`](@ref) functions. +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 diff --git a/test/synthesis.jl b/test/synthesis.jl index 8f35304..c220dc8 100644 --- a/test/synthesis.jl +++ b/test/synthesis.jl @@ -12,7 +12,7 @@ function test_wishart() let n = 100 m = 10 - model = QUBOLib.generate(QUBOLib.Wishart(n, m)) + model = QUBOLib.Synthesis.generate(QUBOLib.Synthesis.Wishart(n, m)) @test QUBOTools.dimension(model) == n @test QUBOTools.density(model) ≈ 1.0 atol = 1E-8 @@ -32,7 +32,7 @@ function test_sherrington_kirkpatrick() μ = 5.0 σ = 1E-3 - model = QUBOLib.generate(QUBOLib.SK(n, μ, σ)) + model = QUBOLib.Synthesis.generate(QUBOLib.Synthesis.SK(n, μ, σ)) @test QUBOTools.dimension(model) == n @test QUBOTools.density(model) ≈ 1.0 atol = 1E-8 From 753666cffcaaecaaaee431faaeafa0a4c3c8a796 Mon Sep 17 00:00:00 2001 From: Pedro Maciel Xavier Date: Mon, 30 Sep 2024 01:54:14 -0400 Subject: [PATCH 23/23] Fix example --- docs/src/manual/1-basic.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/manual/1-basic.md b/docs/src/manual/1-basic.md index a000785..f448865 100644 --- a/docs/src/manual/1-basic.md +++ b/docs/src/manual/1-basic.md @@ -2,7 +2,7 @@ ## Getting Started -```@example basic +```julia using QUBOLib QUBOLib.access() do index