diff --git a/Project.toml b/Project.toml index dd886d55..b24279f5 100644 --- a/Project.toml +++ b/Project.toml @@ -7,7 +7,7 @@ authors = [ "joaquimg ", "bernalde " ] -version = "0.5.4" +version = "0.6.0" [deps] InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" diff --git a/README.md b/README.md index 8dceff8a..da17a07c 100644 --- a/README.md +++ b/README.md @@ -108,7 +108,7 @@ flowchart TD; ``` ## Backend -The `AbstractModel{D}` abstract type is defined, where `D <: VariableDomain`. +The `AbstractModel{D}` abstract type is defined, where `D <: Domain`. Available variable domains are `BoolDomain` and `SpinDomain`, respectively, $x \in \mathbb{B} = \lbrace 0, 1 \rbrace$ and $s \in \lbrace -1, 1 \rbrace$. Conversion between domains follows the identity diff --git a/docs/src/api.md b/docs/src/api.md index 61bc1c5f..6a6b9e93 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -9,12 +9,12 @@ QUBOTools.backend ### Variable System ```@docs -QUBOTools.varcmp +QUBOTools.varlt ``` ### Variable Domains ```@docs -QUBOTools.VariableDomain +QUBOTools.Domain QUBOTools.BoolDomain QUBOTools.SpinDomain QUBOTools.UnknownDomain diff --git a/src/QUBOTools.jl b/src/QUBOTools.jl index 9accdb42..8e8edf1e 100644 --- a/src/QUBOTools.jl +++ b/src/QUBOTools.jl @@ -10,16 +10,16 @@ using InteractiveUtils: subtypes # ~*~ Variable comparison ~*~ # @doc raw""" - varcmp(x::V, y::V) where {V} + varlt(x::V, y::V) where {V} This function exists to define an arbitrary ordering for a given type and was created to address [1]. There is no predefined comparison between instances MOI's `VariableIndex` type. [1] https://github.com/jump-dev/MathOptInterface.jl/issues/1985 -""" function varcmp end +""" function varlt end -varcmp(x::V, y::V) where {V} = isless(x, y) +varlt(x::V, y::V) where {V} = isless(x, y) -const ≺ = varcmp # \prec[tab] +const ≺ = varlt # \prec[tab] const ↑ = -1 # \uparrow[tab] const ↓ = +1 # \downarrow[tab] diff --git a/src/formats/bqpjson/format.jl b/src/formats/bqpjson/format.jl index a03dfeeb..2d34999f 100644 --- a/src/formats/bqpjson/format.jl +++ b/src/formats/bqpjson/format.jl @@ -3,21 +3,41 @@ const _BQPJSON_SCHEMA = JSONSchema.Schema(JSON.parsefile(_BQPJSON_SCHEMA const _BQPJSON_VERSION_LIST = VersionNumber[v"1.0.0"] const _BQPJSON_VERSION_LATEST = _BQPJSON_VERSION_LIST[end] -_BQPJSON_VARIABLE_DOMAIN(::Type{BoolDomain}) = "boolean" -_BQPJSON_VARIABLE_DOMAIN(::Type{SpinDomain}) = "spin" +_BQPJSON_VARIABLE_DOMAIN(::BoolDomain) = "boolean" +_BQPJSON_VARIABLE_DOMAIN(::SpinDomain) = "spin" -_BQPJSON_VALIDATE_DOMAIN(x::Integer, ::Type{BoolDomain}) = (x == 0) || (x == 1) -_BQPJSON_VALIDATE_DOMAIN(s::Integer, ::Type{SpinDomain}) = (s == ↑) || (s == ↓) +_BQPJSON_VALIDATE_DOMAIN(x::Integer, ::BoolDomain) = (x == 0) || (x == 1) +_BQPJSON_VALIDATE_DOMAIN(s::Integer, ::SpinDomain) = (s == ↑) || (s == ↓) @doc raw""" - BQPJSON{D}() where {D<:VariableDomain} + BQPJSON Precise and detailed information found in the [bqpjson docs](https://bqpjson.readthedocs.io) -""" struct BQPJSON{D} <: AbstractFormat{D} end +""" struct BQPJSON <: AbstractFormat + domain::Union{BoolDomain,SpinDomain,Nothing} + indent::Int + + BQPJSON(domain::Union{Symbol,Domain}) = new(Domain(domain)) + + function BQPJSON( + dom::Union{BoolDomain,SpinDomain,Nothing} = nothing, + sty::Nothing = nothing; + indent::Integer = 0, + ) + return new(dom, indent) + end +end + +domain(fmt::BQPJSON) = fmt.domain + +supports_domain(::Type{BQPJSON}, ::Nothing) = true +supports_domain(::Type{BQPJSON}, ::BoolDomain) = true +supports_domain(::Type{BQPJSON}, ::SpinDomain) = true + +infer_format(::Val{:json}) = BQPJSON(nothing, nothing) +infer_format(::Val{:bool}, ::Val{:json}) = BQPJSON(𝔹, nothing) +infer_format(::Val{:spin}, ::Val{:json}) = BQPJSON(𝕊, nothing) -infer_format(::Val{:json}) = BQPJSON{UnknownDomain}() -infer_format(::Val{:bool}, ::Val{:json}) = BQPJSON{BoolDomain}() -infer_format(::Val{:spin}, ::Val{:json}) = BQPJSON{SpinDomain}() include("parser.jl") -include("printer.jl") \ No newline at end of file +include("printer.jl") diff --git a/src/formats/bqpjson/parser.jl b/src/formats/bqpjson/parser.jl index 1ff4d299..5aea2fd2 100644 --- a/src/formats/bqpjson/parser.jl +++ b/src/formats/bqpjson/parser.jl @@ -1,3 +1,53 @@ +function read_model(io::IO, fmt::BQPJSON) + json_data = JSON.parse(io) + report = JSONSchema.validate(_BQPJSON_SCHEMA, json_data) + + if !isnothing(report) + codec_error("Schema violation:\n$(report)") + end + + data = Dict{Symbol,Any}( + :id => json_data["id"], + :scale => json_data["scale"], + :offset => json_data["offset"], + :variable_set => Set{Int}(json_data["variable_ids"]), + :linear_terms => Dict{Int,Float64}(), + :quadratic_terms => Dict{Tuple{Int,Int},Float64}(), + :description => get(json_data, "description", nothing), + :metadata => deepcopy(json_data["metadata"]), + ) + + _parse_version!(fmt, data, json_data) + _parse_domain!(fmt, data, json_data) + _parse_terms!(fmt, data, json_data) + _parse_solutions!(fmt, data, json_data) + + target_domain = something(domain(fmt), data[:domain]) + + L, Q, α, β = swap_domain( + data[:domain], + target_domain, + data[:linear_terms], + data[:quadratic_terms], + data[:scale], + data[:offset], + ) + + return StandardModel( + L, + Q; + scale = α, + offset = β, + sense = Sense(:min), + domain = target_domain, + id = data[:id], + version = data[:version], + description = data[:description], + metadata = data[:metadata], + sampleset = data[:sampleset], + ) +end + function _parse_version!(::BQPJSON, data::Dict{Symbol,Any}, json_data::Dict{String,Any}) bqpjson_version = VersionNumber(json_data["version"]) @@ -14,9 +64,9 @@ function _parse_domain!(::BQPJSON, data::Dict{Symbol,Any}, json_data::Dict{Strin bqpjson_domain = json_data["variable_domain"] if bqpjson_domain == "boolean" - data[:domain] = BoolDomain + data[:domain] = BoolDomain() elseif bqpjson_domain == "spin" - data[:domain] = SpinDomain + data[:domain] = SpinDomain() else codec_error("Inconsistent variable domain '$variable_domain'") end @@ -110,51 +160,7 @@ function _parse_solutions!(::BQPJSON, data::Dict{Symbol,Any}, json_data::Dict{St end end - data[:sampleset] = SampleSet{Float64,Int}(samples) + data[:sampleset] = SampleSet{Float64,Int}(samples, solution_metadata) return nothing end - -function read_model(io::IO, fmt::BQPJSON{D}) where {D} - json_data = JSON.parse(io) - report = JSONSchema.validate(_BQPJSON_SCHEMA, json_data) - - if !isnothing(report) - codec_error("Schema violation:\n$(report)") - end - - data = Dict{Symbol,Any}( - :id => json_data["id"], - :scale => json_data["scale"], - :offset => json_data["offset"], - :variable_set => Set{Int}(json_data["variable_ids"]), - :linear_terms => Dict{Int,Float64}(), - :quadratic_terms => Dict{Tuple{Int,Int},Float64}(), - :description => get(json_data, "description", nothing), - :metadata => deepcopy(json_data["metadata"]), - ) - - _parse_version!(fmt, data, json_data) - _parse_domain!(fmt, data, json_data) - _parse_terms!(fmt, data, json_data) - _parse_solutions!(fmt, data, json_data) - - model = StandardModel{data[:domain]}( - data[:linear_terms], - data[:quadratic_terms]; - scale = data[:scale], - offset = data[:offset], - id = data[:id], - version = data[:version], - description = data[:description], - metadata = data[:metadata], - sampleset = data[:sampleset], - ) - - if D === UnknownDomain - return model - else - return convert(StandardModel{D}, model) - end -end - diff --git a/src/formats/bqpjson/printer.jl b/src/formats/bqpjson/printer.jl index 4b4f1def..54a69a87 100644 --- a/src/formats/bqpjson/printer.jl +++ b/src/formats/bqpjson/printer.jl @@ -1,8 +1,4 @@ -function write_model(io::IO, model::AbstractModel{D}, ::BQPJSON{UnknownDomain}) where {D} - return write_model(io, model, BQPJSON{D}()) -end - -function write_model(io::IO, model::AbstractModel{D}, ::BQPJSON{D}) where {D<:VariableDomain} +function write_model(io::IO, model::AbstractModel, fmt::BQPJSON) data = Dict{Symbol,Any}( :linear_terms => Dict{String,Any}[], :quadratic_terms => Dict{String,Any}[], @@ -10,7 +6,7 @@ function write_model(io::IO, model::AbstractModel{D}, ::BQPJSON{D}) where {D<:Va :scale => scale(model), :id => id(model), :version => version(model), - :variable_domain => _BQPJSON_VARIABLE_DOMAIN(D), + :variable_domain => _BQPJSON_VARIABLE_DOMAIN(domain(model)), :variable_ids => variables(model), :description => description(model), :metadata => metadata(model), @@ -90,7 +86,7 @@ function write_model(io::IO, model::AbstractModel{D}, ::BQPJSON{D}) where {D<:Va json_data["solutions"] = solutions end - JSON.print(io, json_data) + JSON.print(io, json_data, fmt.indent) return nothing end \ No newline at end of file diff --git a/src/formats/error.jl b/src/formats/error.jl deleted file mode 100644 index 9a5f47d7..00000000 --- a/src/formats/error.jl +++ /dev/null @@ -1,41 +0,0 @@ -struct FormatError <: Exception - msg::Union{String,Nothing} - - FormatError(msg::Union{String,Nothing}=nothing) = new(msg) -end - -function Base.showerror(io::IO, e::FormatError) - if isnothing(e.msg) - print(io, "Format Error") - else - print(io, "Format Error: $(e.msg)") - end -end - -function format_error(msg::Union{String,Nothing} = nothing) - throw(FormatError(msg)) -end - -struct SyntaxError <: Exception - msg::Union{String,Nothing} - - SyntaxError(msg::Union{String,Nothing}=nothing) = new(msg) -end - -function Base.showerror(io::IO, e::SyntaxError) - if isnothing(e.msg) - print(io, "Syntax Error") - else - print(io, "Syntax Error: $(e.msg)") - end -end - -function syntax_error(msg::Union{String,Nothing} = nothing) - throw(SyntaxError(msg)) -end - -function syntax_warning(msg::String) - @warn "Syntax Warning: $msg" - - return nothing -end \ No newline at end of file diff --git a/src/formats/formats.jl b/src/formats/formats.jl index 367b40c2..2a4ec82a 100644 --- a/src/formats/formats.jl +++ b/src/formats/formats.jl @@ -1,6 +1,3 @@ -include("error.jl") -include("interface.jl") - # ~ Supported Formats ~ # include("bqpjson/format.jl") include("hfs/format.jl") diff --git a/src/formats/hfs/chimera.jl b/src/formats/hfs/chimera.jl index b7baa654..2ad57d80 100644 --- a/src/formats/hfs/chimera.jl +++ b/src/formats/hfs/chimera.jl @@ -76,16 +76,16 @@ There is an edge from ``v_p`` to ``v_q`` if at least one of the following holds: end end -function Chimera(model::AbstractModel{D}, fmt::HFS{D}) where {D} +function Chimera(model::AbstractModel, fmt::HFS) return Chimera(model, fmt.chimera_cell_size, fmt.chimera_degree, fmt.chimera_precision) end function Chimera( - model::AbstractModel{D}, + model::AbstractModel, chimera_cell_size::Union{Integer,Nothing} = nothing, chimera_degree::Union{Integer,Nothing} = nothing, chimera_precision::Union{Integer,Nothing} = nothing, -) where {D} +) variable_set = QUBOTools.variable_set(model) linear_terms = QUBOTools.linear_terms(model) quadratic_terms = QUBOTools.quadratic_terms(model) diff --git a/src/formats/hfs/format.jl b/src/formats/hfs/format.jl index 74fa7a8a..b5822690 100644 --- a/src/formats/hfs/format.jl +++ b/src/formats/hfs/format.jl @@ -1,24 +1,28 @@ @doc raw""" - HFS{BoolDomain} + HFS This format offers a description for the setup of chimera graphs. -""" struct HFS{D<:𝔹} <: AbstractFormat{D} +""" struct HFS <: AbstractFormat chimera_cell_size::Union{Int,Nothing} chimera_precision::Union{Int,Nothing} chimera_degree::Union{Int,Nothing} - function HFS{D}(; + function HFS( + dom::BoolDomain = BoolDomain(), + sty::Nothing = nothing; chimera_cell_size::Union{Int,Nothing} = nothing, chimera_precision::Union{Int,Nothing} = nothing, chimera_degree::Union{Int,Nothing} = nothing, - ) where {D} - return new{D}(chimera_cell_size, chimera_precision, chimera_degree) + ) + return new(chimera_cell_size, chimera_precision, chimera_degree) end end -HFS(args...; kws...) = HFS{𝔹}(args...; kws...) +domain(::HFS) = BoolDomain() -infer_format(::Val{:hfs}) = HFS() +supports_domain(::Type{HFS}, ::BoolDomain) = true + +infer_format(::Val{:hfs}) = HFS(𝔹, nothing) include("chimera.jl") include("parser.jl") diff --git a/src/formats/hfs/printer.jl b/src/formats/hfs/printer.jl index adbd00dc..4d99bcbe 100644 --- a/src/formats/hfs/printer.jl +++ b/src/formats/hfs/printer.jl @@ -1,4 +1,4 @@ -function write_model(io::IO, model::AbstractModel{D}, fmt::HFS{D}) where {D} +function write_model(io::IO, model::AbstractModel, fmt::HFS) if isempty(model) return write(io, "0 0") end diff --git a/src/formats/interface.jl b/src/formats/interface.jl deleted file mode 100644 index b2603166..00000000 --- a/src/formats/interface.jl +++ /dev/null @@ -1,40 +0,0 @@ -@doc raw""" - read_model(::AbstractString, ::AbstractFormat) - read_model(::AbstractString) -""" function read_model end - -function read_model(path::AbstractString, fmt::AbstractFormat = infer_format(path)) - return open(path, "r") do fp - return read_model(fp, fmt) - end -end - -@doc raw""" -""" function read_model! end - -function read_model!(path::AbstractString, model::AbstractModel, fmt::AbstractFormat = infer_format(path)) - return open(path, "r") do fp - return read_model!(fp, model, fmt) - end -end - -function read_model!(io::IO, model::AbstractModel, fmt::AbstractFormat) - return copy!(model, read_model(io, fmt)) -end - -@doc raw""" - write_model(::AbstractString, ::AbstractModel) - write_model(::AbstractString, ::AbstractModel, ::AbstractFormat) - write_model(::IO, ::AbstractModel, ::AbstractFormat) - -""" function write_model end - -function write_model(path::AbstractString, model::AbstractModel, fmt::AbstractFormat = infer_format(path)) - open(path, "w") do fp - write_model(fp, model, fmt) - end -end - -function write_model(io::IO, model::AbstractModel{X}, fmt::AbstractFormat{Y}) where {X,Y} - return write_model(io, swap_domain(X(), Y(), model), fmt) -end \ No newline at end of file diff --git a/src/formats/minizinc/format.jl b/src/formats/minizinc/format.jl index e6c6b13f..5e0f7e67 100644 --- a/src/formats/minizinc/format.jl +++ b/src/formats/minizinc/format.jl @@ -9,11 +9,26 @@ const _MINIZINC_RE_OBJECTIVE = r"^var\s+float\s*:\s*objective\s*=\s*(.+);$" const _MINIZINC_RE_SENSE = r"^solve (minimize|maximize) objective;$" @doc raw""" -""" struct MiniZinc{D} <: AbstractFormat{D} end +""" struct MiniZinc <: AbstractFormat + domain::Union{BoolDomain,SpinDomain,Nothing} -infer_format(::Val{:mzn}) = MiniZinc{UnknownDomain}() -infer_format(::Val{:spin}, ::Val{:mzn}) = MiniZinc{SpinDomain}() -infer_format(::Val{:bool}, ::Val{:mzn}) = MiniZinc{BoolDomain}() + function MiniZinc( + dom::Union{BoolDomain,SpinDomain,Nothing} = nothing, + sty::Nothing = nothing, + ) + return new(dom) + end +end + +domain(fmt::MiniZinc) = fmt.domain + +supports_domain(::Type{MiniZinc}, ::Nothing) = true +supports_domain(::Type{MiniZinc}, ::BoolDomain) = true +supports_domain(::Type{MiniZinc}, ::SpinDomain) = true + +infer_format(::Val{:mzn}) = MiniZinc(nothing, nothing) +infer_format(::Val{:spin}, ::Val{:mzn}) = MiniZinc(𝕊, nothing) +infer_format(::Val{:bool}, ::Val{:mzn}) = MiniZinc(𝔹, nothing) include("parser.jl") include("printer.jl") \ No newline at end of file diff --git a/src/formats/minizinc/parser.jl b/src/formats/minizinc/parser.jl index 5847be03..d9559e5c 100644 --- a/src/formats/minizinc/parser.jl +++ b/src/formats/minizinc/parser.jl @@ -63,9 +63,9 @@ function _parse_domain!(::MiniZinc, data::Dict{Symbol,Any}, line::AbstractString Ω = Set{Int}([a, b]) if Ω == Set{Int}([-1, 1]) - data[:domain] = SpinDomain + data[:domain] = SpinDomain() elseif Ω == Set{Int}([0, 1]) - data[:domain] = BoolDomain + data[:domain] = BoolDomain() else syntax_error("Invalid variable set '$(Ω)'") end @@ -188,7 +188,7 @@ function _parse_sense!(::MiniZinc, data::Dict{Symbol,Any}, line::AbstractString) return true end -function read_model(io::IO, fmt::MiniZinc{D}) where {D} +function read_model(io::IO, fmt::MiniZinc) data = Dict{Symbol,Any}( :domain => nothing, :id => nothing, @@ -210,20 +210,25 @@ function read_model(io::IO, fmt::MiniZinc{D}) where {D} _parse_line!(fmt, data, line) end - model = StandardModel{data[:domain]}( + target_domain = something(domain(fmt), data[:domain]) + + L, Q, α, β = swap_domain( + data[:domain], + target_domain, data[:linear_terms], data[:quadratic_terms], + data[:scale], + data[:offset], + ) + + return StandardModel( + L, + Q, data[:variable_set]; - scale = data[:scale], - offset = data[:offset], + scale = α, + offset = β, id = data[:id], description = data[:description], metadata = data[:metadata], ) - - if D === UnknownDomain - return model - else - return convert(StandardModel{D}, model) - end end diff --git a/src/formats/minizinc/printer.jl b/src/formats/minizinc/printer.jl index ae993c1f..1a8501d9 100644 --- a/src/formats/minizinc/printer.jl +++ b/src/formats/minizinc/printer.jl @@ -16,13 +16,13 @@ function _print_metadata(io::IO, ::MiniZinc, data::Dict{Symbol,Any}) return nothing end -function _print_domain(io::IO, ::MiniZinc{BoolDomain}) +function _print_domain(io::IO, ::MiniZinc, ::BoolDomain) println(io, "set of int: Domain = {0,1};") return nothing end -function _print_domain(io::IO, ::MiniZinc{SpinDomain}) +function _print_domain(io::IO, ::MiniZinc, ::SpinDomain) println(io, "set of int: Domain = {-1,1};") return nothing @@ -64,11 +64,7 @@ function _print_objective!(io::IO, ::MiniZinc, data::Dict{Symbol,Any}) return nothing end -function write_model(io::IO, model::AbstractModel{D}, ::MiniZinc{UnknownDomain}) where {D} - return write_model(io, model, MiniZinc{D}()) -end - -function write_model(io::IO, model::AbstractModel{D}, fmt::MiniZinc{D}) where {D} +function write_model(io::IO, model::AbstractModel, fmt::MiniZinc) data = Dict{Symbol,Any}( :linear_terms => linear_terms(model), :quadratic_terms => quadratic_terms(model), @@ -85,7 +81,7 @@ function write_model(io::IO, model::AbstractModel{D}, fmt::MiniZinc{D}) where {D ) _print_metadata(io, fmt, data) - _print_domain(io, fmt) + _print_domain(io, fmt, domain(fmt)) _print_variables!(io, fmt, data) _print_objective!(io, fmt, data) diff --git a/src/formats/qubist/format.jl b/src/formats/qubist/format.jl index ce66b564..3ecfa458 100644 --- a/src/formats/qubist/format.jl +++ b/src/formats/qubist/format.jl @@ -1,11 +1,21 @@ @doc raw""" - Qubist{D<:SpinDomain} + Qubist -""" struct Qubist{D<:𝕊} <: AbstractFormat{D} end +""" struct Qubist <: AbstractFormat -Qubist(args...; kws...) = Qubist{𝕊}(args...; kws...) + function Qubist( + dom::SpinDomain = SpinDomain(), + sty::Nothing = nothing, + ) + return new() + end +end -infer_format(::Val{:qh}) = Qubist() +domain(::Qubist) = SpinDomain() + +supports_domain(::Type{Qubist}, ::SpinDomain) = true + +infer_format(::Val{:qh}) = Qubist(𝕊, nothing) include("parser.jl") include("printer.jl") \ No newline at end of file diff --git a/src/formats/qubist/parser.jl b/src/formats/qubist/parser.jl index 95058881..042791f9 100644 --- a/src/formats/qubist/parser.jl +++ b/src/formats/qubist/parser.jl @@ -69,5 +69,5 @@ function read_model(io::IO, fmt::Qubist) _parse_line!(fmt, data, line) end - return Model{SpinDomain}(data[:linear_terms], data[:quadratic_terms]) + return StandardModel(data[:linear_terms], data[:quadratic_terms]) end diff --git a/src/formats/qubist/printer.jl b/src/formats/qubist/printer.jl index 24786cda..6e37d3d2 100644 --- a/src/formats/qubist/printer.jl +++ b/src/formats/qubist/printer.jl @@ -8,7 +8,7 @@ function _print_header(io::IO, data::Dict{Symbol,Any}, ::Qubist) return nothing end -function write_model(io::IO, model::AbstractModel{D}, fmt::Qubist{D}) where {D} +function write_model(io::IO, model::AbstractModel, fmt::Qubist) data = Dict{Symbol,Any}( :domain_size => domain_size(model), :linear_size => linear_size(model), diff --git a/src/formats/qubo/format.jl b/src/formats/qubo/format.jl index ed6ff89f..c5b1270d 100644 --- a/src/formats/qubo/format.jl +++ b/src/formats/qubo/format.jl @@ -6,31 +6,40 @@ ### References [1] [qbsolv docs](https://docs.ocean.dwavesys.com/projects/qbsolv/en/latest/source/format.html) -""" struct QUBO{D<:𝔹} <: AbstractFormat{D} - style::Union{Symbol,Nothing} +""" struct QUBO <: AbstractFormat + style::Union{DWaveStyle,MQLibStyle,Nothing} comment::Union{String,Nothing} - function QUBO{D}(; - style::Union{Symbol,Nothing} = :dwave, - comment::Union{String,Nothing} = nothing, - ) where {D} - if !isnothing(style) && isnothing(comment) - if style === :dwave + function QUBO( + dom::BoolDomain = BoolDomain(), + sty::Union{DWaveStyle,MQLibStyle,Nothing} = DWaveStyle(); + comment::Union{String,Nothing} = nothing, + ) + if !isnothing(sty) && isnothing(comment) + if sty === DWaveStyle() comment = "c" - elseif style === :mqlib + elseif sty === MQLibStyle() comment = "#" - else - format_error("Unknown QUBO File style '$style'") end end - return new{D}(style, comment) + return new(sty, comment) end end -QUBO(args...; kws...) = QUBO{𝔹}(args...; kws...) +domain(::QUBO) = BoolDomain() + +supports_domain(::Type{QUBO}, ::BoolDomain) = true + +style(fmt::QUBO) = fmt.style + +supports_style(::Type{QUBO}, ::DWaveStyle) = true +supports_style(::Type{QUBO}, ::MQLibStyle) = true -infer_format(::Val{:qubo}) = QUBO() +infer_format(::Val{:qubo}) = QUBO(𝔹, nothing) +infer_format(::Val{:dwave}, ::Val{:qubo}) = QUBO(𝔹, Style(:dwave)) +infer_format(::Val{:mqlib}, ::Val{:qubo}) = QUBO(𝔹, Style(:mqlib)) +infer_format(::Val{:qbsolv}, ::Val{:qubo}) = QUBO(𝔹, Style(:dwave)) include("parser.jl") -include("printer.jl") \ No newline at end of file +include("printer.jl") diff --git a/src/formats/qubo/parser.jl b/src/formats/qubo/parser.jl index accf926c..56bf8e71 100644 --- a/src/formats/qubo/parser.jl +++ b/src/formats/qubo/parser.jl @@ -6,11 +6,7 @@ function _parse_line!(fmt::QUBO, data::Dict{Symbol,Any}, line::AbstractString) syntax_error("$line") end -function _parse_entry!(fmt::QUBO, data::Dict{Symbol,Any}, line::AbstractString, style::Symbol) - return _parse_entry!(fmt, data, line, Val(style)) -end - -function _parse_entry!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::Any) +function _parse_entry!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::Union{DWaveStyle,Nothing}) m = match(r"^([0-9]+) ([0-9]+) ([+-]?([0-9]*[.])?[0-9]+)$", line) if isnothing(m) @@ -36,7 +32,7 @@ function _parse_entry!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::A return true end -function _parse_entry!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::Val{:mqlib}) +function _parse_entry!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::MQLibStyle) m = match(r"^([0-9]+) ([0-9]+) ([+-]?([0-9]*[.])?[0-9]+)$", line) if isnothing(m) @@ -62,11 +58,11 @@ function _parse_entry!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::V return true end -function _parse_header!(fmt::QUBO, data::Dict{Symbol,Any}, line::AbstractString, style::Symbol) - return _parse_header!(fmt, data, line, Val(style)) +function _parse_header!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::Nothing) + return false end -function _parse_header!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::Val{:dwave}) +function _parse_header!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::DWaveStyle) m = match(r"^p qubo ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)$", line) if isnothing(m) @@ -80,7 +76,7 @@ function _parse_header!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, :: return true end -function _parse_header!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::Val{:mqlib}) +function _parse_header!(::QUBO, data::Dict{Symbol,Any}, line::AbstractString, ::MQLibStyle) m = match(r"^([0-9]+) ([0-9]+)$", line) if isnothing(m) @@ -155,11 +151,12 @@ function read_model(io::IO, fmt::QUBO) _parse_line!(fmt, data, line) end - return Model{BoolDomain,Int,Float64,Int}( + return StandardModel( data[:linear_terms], data[:quadratic_terms]; scale = data[:scale], offset = data[:offset], + domain = domain(fmt), id = data[:id], description = data[:description], metadata = data[:metadata], diff --git a/src/formats/qubo/printer.jl b/src/formats/qubo/printer.jl index 72940ea5..ebb1feb5 100644 --- a/src/formats/qubo/printer.jl +++ b/src/formats/qubo/printer.jl @@ -2,11 +2,7 @@ function _print_header(::IO, ::QUBO, ::Dict{Symbol,Any}, ::Nothing) return nothing end -function _print_header(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, style::Symbol) - return _print_header(io, fmt, data, Val(style)) -end - -function _print_header(io::IO, ::QUBO, data::Dict{Symbol,Any}, ::Val{:dwave}) +function _print_header(io::IO, ::QUBO, data::Dict{Symbol,Any}, ::DWaveStyle) domain_size = data[:domain_size] linear_size = data[:linear_size] quadratic_size = data[:quadratic_size] @@ -16,7 +12,7 @@ function _print_header(io::IO, ::QUBO, data::Dict{Symbol,Any}, ::Val{:dwave}) return nothing end -function _print_header(io::IO, ::QUBO, data::Dict{Symbol,Any}, ::Val{:mqlib}) +function _print_header(io::IO, ::QUBO, data::Dict{Symbol,Any}, ::MQLibStyle) domain_size = data[:domain_size] linear_size = data[:linear_size] quadratic_size = data[:quadratic_size] @@ -53,11 +49,7 @@ function _print_metadata(io::IO, ::QUBO, data::Dict{Symbol,Any}, comment::String return nothing end -function _print_entries(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, style::Symbol) - return _print_entries(io, fmt, data, Val(style)) -end - -function _print_entries(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, ::Any) +function _print_entries(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, ::Union{DWaveStyle,Nothing}) !isnothing(fmt.comment) && println(io, "$(fmt.comment) linear terms") for (i, l) in data[:linear_terms] @@ -73,7 +65,7 @@ function _print_entries(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, ::Any) return nothing end -function _print_entries(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, ::Val{:mqlib}) +function _print_entries(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, ::MQLibStyle) !isnothing(fmt.comment) && println(io, "$(fmt.comment) linear terms") for (i, l) in data[:linear_terms] @@ -89,7 +81,7 @@ function _print_entries(io::IO, fmt::QUBO, data::Dict{Symbol,Any}, ::Val{:mqlib} return nothing end -function write_model(io::IO, model::AbstractModel{D}, fmt::QUBO{D}) where {D} +function write_model(io::IO, model::AbstractModel, fmt::QUBO) data = Dict{Symbol,Any}( :linear_terms => linear_terms(model), :quadratic_terms => quadratic_terms(model), @@ -104,8 +96,8 @@ function write_model(io::IO, model::AbstractModel{D}, fmt::QUBO{D}) where {D} ) _print_metadata(io, fmt, data, fmt.comment) - _print_header(io, fmt, data, fmt.style) - _print_entries(io, fmt, data, fmt.style) + _print_header(io, fmt, data, style(fmt)) + _print_entries(io, fmt, data, style(fmt)) return nothing end diff --git a/src/interface/fallback.jl b/src/interface/fallback.jl index 4250c3d6..a616427d 100644 --- a/src/interface/fallback.jl +++ b/src/interface/fallback.jl @@ -5,7 +5,23 @@ This file contains fallback implementations by calling the model's backend. This allows for external models to define a QUBOTools-based backend and profit from these queries. """ -frontend(model) = backend(model) +# ~*~ Frontend & Backend ~*~ # +# The `frontend` implementation defaults to `backend` +# because most of the time people will not be working +# with two equivalent models as in `TwinModel`'s API. +function frontend(model) + return backend(model) +end + +function backend(::M) where {M<:AbstractModel} + error( + """ + The '$M' QUBO Model Type has an incomplete inferface. + It should either implement `backend(::$M)` or the complete `AbstractModel` API. + Run `julia> ?QUBOTools.AbstractModel` for more information. + """ + ) +end # ~*~ Data access ~*~ # model_name(model) = model_name(backend(model)) diff --git a/src/interface/generic.jl b/src/interface/generic.jl index c7fd78b2..52b73113 100644 --- a/src/interface/generic.jl +++ b/src/interface/generic.jl @@ -123,251 +123,187 @@ end adjacency(G::Set{Tuple{Int,Int}}, k::Integer) = adjacency(collect(G), k) adjacency(G::Dict{Tuple{Int,Int}}, k::Integer) = adjacency(collect(keys(G)), k) -function qubo( - h::Dict{Int,T}, - J::Dict{Tuple{Int,Int},T}, - α::T = one(T), - β::T = zero(T), -) where {T} - Q = Dict{Tuple{Int,Int},T}() - - sizehint!(Q, length(h) + length(J)) - - for (i, l) in h - β -= l - Q[(i, i)] = get(Q, (i, i), zero(T)) + 2l - end - - for ((i, j), q) in J - β += q - Q[(i, j)] = get(Q, (i, j), zero(T)) + 4q - Q[(i, i)] = get(Q, (i, i), zero(T)) - 2q - Q[(j, j)] = get(Q, (j, j), zero(T)) - 2q - end +function format(source::AbstractModel, target::AbstractModel, data::Any) + return format(sense(source), domain(source), sense(target), domain(target), data) +end - return (Q, α, β) +function format( + source_sense::Sense, + source_domain::Domain, + target_sense::Sense, + target_domain::Domain, + data::Any, +) + return data |> ( + swap_sense(source_sense, target_sense) ∘ swap_domain(source_domain, target_domain) + ) end -function qubo( - h::Vector{T}, - J::Vector{T}, - u::Vector{Int}, - v::Vector{Int}, - α::T = one(T), - β::T = zero(T), -) where {T} - n = length(h) - m = length(J) - L = zeros(T, n) - Q = zeros(T, m) +# -* Sense *- # +Base.show(io::IO, ::MinSense) = print(io, "Min") +Base.show(io::IO, ::MaxSense) = print(io, "Max") - for i = 1:n - l = h[i] +swap_sense(::Nothing) = nothing - β -= l - L[i] += 2l - end +swap_sense(::MaxSense) = Min +swap_sense(::MinSense) = Max - for k = 1:m - i = u[k] - j = v[k] - q = J[k] +function swap_sense(target::Symbol, x::Any) + return swap_sense(Sense(target), x) +end - β += q - L[i] -= 2q - L[j] -= 2q - Q[k] += 4q - end +function swap_sense(source::Symbol, target::Symbol, x::Any) + return swap_sense(Sense(source), Sense(target), x) +end - return (L, Q, u, v, α, β) +function swap_sense(target::Sense, x::Any) + return swap_sense(sense(x), target, x) end -function qubo(h::Vector{T}, J::Matrix{T}, α::T = one(T), β::T = zero(T)) where {T} - n = length(h) - Q = zeros(T, n, n) +function swap_sense(source::Sense, target::Sense, x::Any) + if source === target + return x + else + return swap_sense(x) + end +end - for i = 1:n, j = i:n - if i == j - l = h[i] +function swap_sense(L::Dict{Int,T}) where {T} + return Dict{Int,T}(i => -c for (i, c) in L) +end - β -= l - Q[i, i] += 2l - else - q = J[i, j] +function swap_sense(Q::Dict{Tuple{Int,Int},T}) where {T} + return Dict{Tuple{Int,Int},T}(ij => -c for (ij, c) in Q) +end - β += q - Q[i, i] -= 2q - Q[j, j] -= 2q - Q[i, j] += 4q - end +function swap_sense(source::Sense, target::Sense) + if source === target + return identity + else + return (x) -> swap_sense(x) end - - return (Q, α, β) end -function qubo( - h::SparseVector{T}, - J::SparseMatrixCSC{T}, - α::T = one(T), - β::T = zero(T), -) where {T} - n = length(h) - Q = spzeros(T, n, n) +# -* Format *- # +function format_types() + return subtypes(AbstractFormat) +end - for i = 1:n, j = i:n - if i == j - l = h[i] +function formats() + return [ + fmt(dom, sty) + for fmt in format_types() + for dom in domains() + for sty in styles() + if supports_domain(fmt, dom) && supports_style(fmt, sty) + ] +end - β -= l - Q[i, i] += 2l - else - q = J[i, j] +function infer_format(path::AbstractString) + pieces = reverse(split(basename(path), ".")) - β += q - Q[i, j] += 4q - Q[i, i] -= 2q - Q[j, j] -= 2q - end + if length(pieces) == 1 + format_error("Unable to infer QUBO format from file without an extension") + else + format_hint, domain_hint, _... = pieces end - return (Q, α, β) + return infer_format(Symbol(domain_hint), Symbol(format_hint)) end -function ising(Q::Dict{Tuple{Int,Int},T}, α::T = one(T), β::T = zero(T)) where {T} - h = Dict{Int,T}() - J = Dict{Tuple{Int,Int},T}() - - for ((i, j), q) in Q - if i == j - β += q / 2 - h[i] = get(h, i, zero(T)) + q / 2 - else # i < j - β += q / 4 - h[i] = get(h, i, zero(T)) + q / 4 - h[j] = get(h, j, zero(T)) + q / 4 - J[(i, j)] = get(J, (i, j), zero(T)) + q / 4 - end - end - - return (h, J, α, β) +function infer_format(domain_hint::Symbol, format_hint::Symbol) + return infer_format(Val(domain_hint), Val(format_hint)) end -function ising( - L::Vector{T}, - Q::Vector{T}, - u::Vector{Int}, - v::Vector{Int}, - α::T = one(T), - β::T = zero(T), -) where {T} - n = length(L) - m = length(Q) +function infer_format(::Val, format_hint::Val) + return infer_format(format_hint) +end - h = zeros(T, n) - J = zeros(T, m) +function infer_format(format_hint::Symbol) + return infer_format(Val(format_hint)) +end - for i = 1:n - l = L[i] - β += l / 2 - h[i] += l / 2 - end +# -* Domain *- # +Base.show(io::IO, ::BoolDomain) = print(io, "𝔹") +Base.show(io::IO, ::SpinDomain) = print(io, "𝕊") - for k = 1:m - i = u[k] - j = v[k] - q = Q[k] +function domain_name(::BoolDomain) + return "Bool" +end - β += q / 4 - h[i] += q / 4 - h[j] += q / 4 - J[k] += q / 4 - end +function domain_name(::SpinDomain) + return "Spin" +end - return (h, J, u, v, α, β) +function domain_types() + return Type[Nothing; subtypes(Domain)] end -function ising(Q::Matrix{T}, α::T = one(T), β::T = zero(T)) where {T} - n = size(Q, 1) +function domains() + return Union{Domain,Nothing}[dom() for dom in domain_types()] +end - h = zeros(T, n) - J = zeros(T, n, n) +function supports_domain(::Type{F}, ::Nothing) where {F<:AbstractFormat} + return false +end - for i = 1:n, j = i:n - q = Q[i, j] +function supports_domain(::Type{F}, ::Domain) where {F<:AbstractFormat} + return false +end - if i == j - β += q / 2 - h[i] += q / 2 - else - β += q / 4 - h[i] += q / 4 - h[j] += q / 4 - J[i, j] += q / 4 - end +function swap_domain(source::Domain, target::Domain) + if source === target + return identity + else + return (x) -> swap_domain(source, target, x) end - - return (h, J, α, β) end -function ising(Q::SparseMatrixCSC{T}, α::T = one(T), β::T = zero(T)) where {T} - n = size(Q, 1) - - h = spzeros(T, n) - J = spzeros(T, n, n) +# -* Style *- # +function supports_style(::Type{F}, ::Nothing) where {F<:AbstractFormat} + return true +end - for i = 1:n, j = i:n - q = Q[i, j] +function supports_style(::Type{F}, ::Style) where {F<:AbstractFormat} + return false +end - if i == j - β += q / 2 - h[i] += q / 2 - else - β += q / 4 - h[i] += q / 4 - h[j] += q / 4 - J[i, j] += q / 4 - end - end +function style_types() + return Type[Nothing; subtypes(Style)] +end - return (h, J, α, β) +function styles() + return Union{Style,Nothing}[sty() for sty in style_types()] end -swap_sense(::Nothing) = nothing -swap_sense(s::Sense) = s === Min ? Max : Min -swap_sense(target::Symbol, x::Any) = swap_sense(Sense(target), x) -swap_sense(source::Symbol, target::Symbol, x::Any) = swap_sense(Sense(source), Sense(target), x) +function style(::AbstractFormat) + return nothing +end -function swap_sense(target::Sense, x::Any) - return swap_sense(sense(x), target, x) +# -* I/O *- # +function Base.show(io::IO, fmt::F) where {F<:AbstractFormat} + return print(io, "$F($(domain(fmt)),$(style(fmt)))") end -function swap_sense(source::Sense, target::Sense, x::Any) - if source === target - return x - else - return swap_sense(x) +function read_model(path::AbstractString, fmt::AbstractFormat = infer_format(path)) + return open(path, "r") do fp + return read_model(fp, fmt) end end -function swap_sense(L::Dict{Int,T}) where {T} - return Dict{Int,T}(i => -c for (i, c) in L) +function read_model!(path::AbstractString, model::AbstractModel, fmt::AbstractFormat = infer_format(path)) + return open(path, "r") do fp + return read_model!(fp, model, fmt) + end end -function swap_sense(Q::Dict{Tuple{Int,Int},T}) where {T} - return Dict{Tuple{Int,Int},T}(ij => -c for (ij, c) in Q) +function read_model!(io::IO, model::AbstractModel, fmt::AbstractFormat) + return copy!(model, read_model(io, fmt)) end -function format( - source_sense::Sense, - source_domain::VariableDomain, - target_sense::Sense, - target_domain::VariableDomain, - x::Any, -) - return swap_sense( - source_sense, - target_sense, - swap_domain(source_domain, target_domain, x), - ) -end \ No newline at end of file +function write_model(path::AbstractString, model::AbstractModel, fmt::AbstractFormat = infer_format(path)) + open(path, "w") do fp + write_model(fp, model, fmt) + end +end diff --git a/src/interface/interface.jl b/src/interface/interface.jl index 385caa93..567e8412 100644 --- a/src/interface/interface.jl +++ b/src/interface/interface.jl @@ -4,70 +4,48 @@ """ @doc raw""" - VariableDomain + AbstractModel{V,T} -""" abstract type VariableDomain end +Represents an abstract QUBO Model and should support most of the queries made available +by `QUBOTools`. +``` -const 𝔻 = VariableDomain +As shown in the example above, implementing a method for the [`backend`](@ref) function +gives access to most fallback implementations. +""" abstract type AbstractModel{V,T} end @doc raw""" - domains() - -Returns the list of available known variable domains. -""" function domains end - -Base.Broadcast.broadcastable(D::VariableDomain) = Ref(D) + AbstractFormat -@doc raw""" - UnknownDomain <: VariableDomain -""" struct UnknownDomain <: VariableDomain end +""" abstract type AbstractFormat end @doc raw""" - SpinDomain <: VariableDomain - -```math -s \in \lbrace{-1, 1}\rbrace -``` -""" struct SpinDomain <: VariableDomain end + Style -const 𝕊 = SpinDomain +""" abstract type Style end @doc raw""" - BoolDomain <: VariableDomain - -```math -x \in \lbrace{0, 1}\rbrace -``` -""" struct BoolDomain <: VariableDomain end + DWaveStyle <: Style -const 𝔹 = BoolDomain +""" struct DWaveStyle <: Style end @doc raw""" - AbstractModel{D<:VariableDomain} - -Represents an abstract QUBO Model and should support most of the queries made available -by `QUBOTools`. + MQLibStyle <: Style -## Example -A common use case is to build wrappers around the [`Model`](@ref) concrete type: +""" struct MQLibStyle <: Style end -```julia -struct ModelWrapper{D} <: AbstractModel{D} - model::Model{D,Int,Float64,Int} - attrs::Dict{String,Any} -end - -QUBOTools.backend(mw::ModelWrapper) = mw.model -``` - -As shown in the example above, implementing a method for the [`backend`](@ref) function -gives access to most fallback implementations. -""" abstract type AbstractModel{D<:VariableDomain} end +Style(sty::Style) = sty +Style(sty::Symbol) = Style(Val(sty)) +Style(::Val{:dwave}) = DWaveStyle() +Style(::Val{:mqlib}) = MQLibStyle() @doc raw""" - AbstractFormat{D<:VariableDomain} + style(::AbstractFormat)::Style +""" function style end -""" abstract type AbstractFormat{D<:VariableDomain} end +@doc raw""" + supports_style(::AbstractFormat)::Bool +""" function supports_style end @doc raw""" formats() @@ -107,11 +85,54 @@ Returns a string representing the model type. """ function model_name end @doc raw""" - domain(model)::VariableDomain + Domain + +""" abstract type Domain end + +@doc raw""" + BoolDomain <: Domain + +```math +x \in \mathbb{B} = \lbrace{0, 1}\rbrace +``` +""" struct BoolDomain <: Domain end + +const 𝔹 = BoolDomain() + +@doc raw""" + SpinDomain <: Domain + +```math +s \in \mathbb{S} = \lbrace{-1, 1}\rbrace +``` +""" struct SpinDomain <: Domain end + +const 𝕊 = SpinDomain() + +QUBOTools.Domain(dom::Domain) = dom +QUBOTools.Domain(dom::Symbol) = Domain(Val(dom)) +QUBOTools.Domain(::Val{:bool}) = 𝔹 +QUBOTools.Domain(::Val{:spin}) = 𝕊 + +Base.Broadcast.broadcastable(dom::Domain) = Ref(dom) + +@doc raw""" + domain(model::AbstractModel)::Domain + domain(fmt::AbstractFormat)::Domain Returns the singleton representing the variable domain of a given model. """ function domain end +@doc raw""" + supports_domain(::Type{<:AbstractFormat}, ::Domain) +""" function supports_domain end + +@doc raw""" + domains() + +Returns the list of available known variable domains. +""" function domains end + @doc raw""" domain_name(model)::String @@ -137,22 +158,20 @@ Returns a new object, switching its domain from `source` to `target`. """ function offset end -@enum Sense begin - Min - Max -end +abstract type Sense end + +struct MinSense <: Sense end -function QUBOTools.Sense(s::Symbol) - if s === :min - return Min - elseif s === :max - return Max - else - error("Unknown optimization sense '$s'") - end -end +const Min = MinSense() -QUBOTools.Sense(s::Sense) = s +struct MaxSense <: Sense end + +const Max = MaxSense() + +Sense(s::Sense) = s +Sense(s::Symbol) = Sense(Val(s)) +Sense(::Val{:min}) = Min +Sense(::Val{:max}) = Max @doc raw""" sense(model)::Sense @@ -172,8 +191,8 @@ QUBOTools.Sense(s::Sense) = s The linear terms, quadratic terms and constant offset of a model have its signs reversed. - swap_sense(s::Sample) - swap_sense(ω::SampleSet) + swap_sense(s::Sample)::Sample + swap_sense(ω::SampleSet)::SampleSet Reveses the sign of the objective value. """ function swap_sense end @@ -437,9 +456,33 @@ If a second parameter, an integer, is present, then the set of neighbors of that format(data::Vector{Sample{T,U}}) where {T,U} format( source_sense::Sense, - source_domain::VariableDomain, + source_domain::Domain, target_sense::Sense, - target_domain::VariableDomain, + target_domain::Domain, x::Any ) -""" function format end \ No newline at end of file +""" function format end + +@doc raw""" + read_model(::AbstractString) + read_model(::AbstractString, ::AbstractFormat) +""" function read_model end + +@doc raw""" + read_model!(::AbstractModel, ::AbstractString) +""" function read_model! end + +@doc raw""" + supports_read(::Type{F}) where {F<:AbstractFormat} + """ function supports_read end + + @doc raw""" + write_model(::AbstractString, ::AbstractModel) + write_model(::AbstractString, ::AbstractModel, ::AbstractFormat) + write_model(::IO, ::AbstractModel, ::AbstractFormat) + +""" function write_model end + +@doc raw""" + supports_read(::Type{F}) where {F<:AbstractFormat} +""" function supports_write end \ No newline at end of file diff --git a/src/library/error.jl b/src/library/error.jl index fc87ba3c..06d62f01 100644 --- a/src/library/error.jl +++ b/src/library/error.jl @@ -1,7 +1,7 @@ struct CodecError <: Exception msg::Union{String,Nothing} - function CodecError(msg::Union{String,Nothing}=nothing) + function CodecError(msg::Union{String,Nothing} = nothing) new(msg) end end @@ -14,14 +14,14 @@ function Base.showerror(io::IO, e::CodecError) end end -function codec_error(msg::Union{String,Nothing}=nothing) +function codec_error(msg::Union{String,Nothing} = nothing) throw(CodecError(msg)) end struct SamplingError <: Exception msg::Union{String,Nothing} - function SamplingError(msg::Union{String,Nothing}=nothing) + function SamplingError(msg::Union{String,Nothing} = nothing) new(msg) end end @@ -34,6 +34,48 @@ function Base.showerror(io::IO, e::SamplingError) end end -function sampling_error(msg::Union{String,Nothing}=nothing) +function sampling_error(msg::Union{String,Nothing} = nothing) throw(SamplingError(msg)) +end + +struct FormatError <: Exception + msg::Union{String,Nothing} + + FormatError(msg::Union{String,Nothing} = nothing) = new(msg) +end + +function Base.showerror(io::IO, e::FormatError) + if isnothing(e.msg) + print(io, "Format Error") + else + print(io, "Format Error: $(e.msg)") + end +end + +function format_error(msg::Union{String,Nothing} = nothing) + throw(FormatError(msg)) +end + +struct SyntaxError <: Exception + msg::Union{String,Nothing} + + SyntaxError(msg::Union{String,Nothing} = nothing) = new(msg) +end + +function Base.showerror(io::IO, e::SyntaxError) + if isnothing(e.msg) + print(io, "Syntax Error") + else + print(io, "Syntax Error: $(e.msg)") + end +end + +function syntax_error(msg::Union{String,Nothing} = nothing) + throw(SyntaxError(msg)) +end + +function syntax_warning(msg::String) + @warn "Syntax Warning: $msg" + + return nothing end \ No newline at end of file diff --git a/src/library/sampleset.jl b/src/library/sampleset.jl index 9b66befc..07df8d09 100644 --- a/src/library/sampleset.jl +++ b/src/library/sampleset.jl @@ -1,10 +1,16 @@ -swap_domain(::D, ::D, ψ::Vector{U}) where {D<:𝔻,U} = ψ -swap_domain(::𝕊, ::𝔹, ψ::Vector{U}) where {U<:Integer} = (ψ .+ 1) .÷ 2 -swap_domain(::𝔹, ::𝕊, ψ::Vector{U}) where {U<:Integer} = (2 .* ψ) .- 1 -swap_domain(::D, ::D, Ψ::Vector{Vector{U}}) where {D<:𝔻,U<:Integer} = Ψ +function swap_domain(source::Domain, target::Domain, ψ::Vector{U}) where {U<:Integer} + if source === target + return copy(ψ) + else + return swap_domain(Val(source), Val(target), ψ) + end +end -function swap_domain(a::A, b::B, Ψ::Vector{Vector{U}}) where {A<:𝔻,B<:𝔻,U<:Integer} - return swap_domain.(a, b, Ψ) +swap_domain(::Val{𝔹}, ::Val{𝕊}, ψ::Vector{U}) where {U<:Integer} = (2 .* ψ) .- 1 +swap_domain(::Val{𝕊}, ::Val{𝔹}, ψ::Vector{U}) where {U<:Integer} = (ψ .+ 1) .÷ 2 + +function swap_domain(source::Domain, target::Domain, Ψ::Vector{Vector{U}}) where {U<:Integer} + return swap_domain.(source, target, Ψ) end @doc raw""" @@ -164,10 +170,8 @@ function validate(ω::AbstractSampleSet) end end -swap_domain(::D, ::D, ω::AbstractSampleSet{T,U}) where {D<:𝔻,T,U} = ω - -function swap_domain(::A, ::B, s::Sample{T,U}) where {A<:𝔻,B<:𝔻,T,U} - return Sample{T,U}(swap_domain(A(), B(), state(s)), value(s), reads(s)) +function swap_domain(source::Domain, target::Domain, s::Sample{T,U}) where {T,U} + return Sample{T,U}(swap_domain(source, target, state(s)), value(s), reads(s)) end function swap_sense(s::Sample{T,U}) where {T,U} @@ -259,10 +263,16 @@ Base.iterate(ω::SampleSet, i::Integer) = iterate(ω.data, i) metadata(ω::SampleSet) = ω.metadata -function swap_domain(::A, ::B, ω::SampleSet{T,U}) where {A<:𝔻,B<:𝔻,T,U} - return SampleSet{T,U}(Vector{Sample{T,U}}(swap_domain.(A(), B(), ω)), deepcopy(metadata(ω))) +function swap_domain(source::Domain, target::Domain, ω::SampleSet{T,U}) where {T,U} + return SampleSet{T,U}( + Vector{Sample{T,U}}(swap_domain.(source, target, ω)), + deepcopy(metadata(ω)), + ) end function swap_sense(ω::SampleSet{T,U}) where {T,U} - return SampleSet{T,U}(Vector{Sample{T,U}}(swap_sense.(ω)), deepcopy(metadata(ω))) + return SampleSet{T,U}( + Vector{Sample{T,U}}(swap_sense.(ω)), + deepcopy(metadata(ω)), + ) end \ No newline at end of file diff --git a/src/library/tools.jl b/src/library/tools.jl index fd572ada..3131438c 100644 --- a/src/library/tools.jl +++ b/src/library/tools.jl @@ -15,57 +15,66 @@ function _isapproxdict(x::Dict{K,T}, y::Dict{K,T}; kw...) where {K,T<:Real} end function swap_domain( - ::𝕊, - ::𝔹, + ::D, + ::D, L̄::Dict{Int,T}, Q̄::Dict{Tuple{Int,Int},T}, - ᾱ::Union{T,Nothing} = nothing, - β̄::Union{T,Nothing} = nothing, + α::T = one(T), + β::T = zero(T), +) where {D<:Domain,T} + L = copy(L̄) + Q = copy(Q̄) + + return (L, Q, α, β) +end + +function swap_domain( + ::SpinDomain, + ::BoolDomain, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T = one(T), + β::T = zero(T), ) where {T} - coalesce() - α = something(ᾱ, one(T)) - β = something(β̄, zero(T)) L = sizehint!(Dict{Int,T}(), length(L̄)) Q = sizehint!(Dict{Tuple{Int,Int},T}(), length(Q̄)) for (i, c) in L̄ - β -= c L[i] = get(L, i, zero(T)) + 2c + β -= c end for ((i, j), c) in Q̄ - β += c + Q[(i, j)] = get(Q, (i, j), zero(T)) + 4c L[i] = get(L, i, zero(T)) - 2c L[j] = get(L, j, zero(T)) - 2c - Q[(i, j)] = get(Q, (i, j), zero(T)) + 4c + β += c end return (L, Q, α, β) end function swap_domain( - ::𝔹, - ::𝕊, + ::BoolDomain, + ::SpinDomain, L̄::Dict{Int,T}, Q̄::Dict{Tuple{Int,Int},T}, - ᾱ::Union{T,Nothing} = nothing, - β̄::Union{T,Nothing} = nothing, + α::T = one(T), + β::T = zero(T), ) where {T} - α = something(ᾱ, one(T)) - β = something(β̄, zero(T)) L = sizehint!(Dict{Int,T}(), length(L̄)) Q = sizehint!(Dict{Tuple{Int,Int},T}(), length(Q̄)) for (i, c) in L̄ - β += c / 2 L[i] = get(L, i, zero(T)) + c / 2 + β += c / 2 end for ((i, j), c) in Q̄ - β += c / 4 + Q[(i, j)] = get(Q, (i, j), zero(T)) + c / 4 L[i] = get(L, i, zero(T)) + c / 4 L[j] = get(L, j, zero(T)) + c / 4 - Q[(i, j)] = get(Q, (i, j), zero(T)) + c / 4 + β += c / 4 end return (L, Q, α, β) @@ -76,10 +85,16 @@ function _map_terms( _quadratic_terms::Dict{Tuple{S,S},T}, variable_map::Dict{S,Int}, ) where {S,T} - linear_terms = Dict{Int,T}(variable_map[i] => l for (i, l) in _linear_terms) - quadratic_terms = Dict{Tuple{Int,Int},T}( - (variable_map[i], variable_map[j]) => q for ((i, j), q) in _quadratic_terms - ) + linear_terms = sizehint!(Dict{Int,T}(), length(_linear_terms)) + quadratic_terms = sizehint!(Dict{Tuple{Int,Int},T}(), length(_quadratic_terms)) + + for (i, l) in _linear_terms + linear_terms[variable_map[i]] = l + end + + for ((i, j), c) in _quadratic_terms + quadratic_terms[(variable_map[i], variable_map[j])] = c + end return (linear_terms, quadratic_terms) end @@ -89,10 +104,16 @@ function _inv_terms( _quadratic_terms::Dict{Tuple{Int,Int},T}, variable_inv::Dict{Int,S}, ) where {S,T} - linear_terms = Dict{S,T}(variable_inv[i] => l for (i, l) in _linear_terms) - quadratic_terms = Dict{Tuple{S,S},T}( - (variable_inv[i], variable_inv[j]) => q for ((i, j), q) in _quadratic_terms - ) + linear_terms = sizehint!(Dict{S,T}(), length(_linear_terms)) + quadratic_terms = sizehint!(Dict{Tuple{S,S},T}(), length(_quadratic_terms)) + + for (i, c) in _linear_terms + linear_terms[variable_inv[i]] = c + end + + for ((i, j), c) in _quadratic_terms + quadratic_terms[(variable_inv[i], variable_inv[j])] = c + end return (linear_terms, quadratic_terms) end @@ -152,45 +173,13 @@ function _normal_form( end function _build_mapping(variable_set::Set{V}) where {V} - variable_map = Dict{V,Int}( - v => k for (k, v) in enumerate(sort(collect(variable_set); lt = varcmp)) - ) - variable_inv = Dict{Int,V}(v => k for (k, v) in variable_map) - - return (variable_map, variable_inv) -end - -function domains() - return Type[dom for dom in subtypes(VariableDomain) if dom !== UnknownDomain] -end - -function formats() - domain_list = domains() - format_list = subtypes(AbstractFormat) + variable_map = sizehint!(Dict{V,Int}(), length(variable_set)) + variable_inv = sizehint!(Dict{Int,V}(), length(variable_map)) - return Type[fmt{dom} for dom in domain_list, fmt in format_list if (fmt{<:dom} <: fmt)] -end - -function infer_format(path::AbstractString) - pieces = reverse(split(basename(path), ".")) - - if length(pieces) == 1 - format_error("Unable to infer QUBO format from file without an extension") - else - format_hint, domain_hint, _... = pieces + for (k, v) in enumerate(sort!(collect(variable_set); lt = varlt)) + variable_map[v] = k + variable_inv[k] = v end - return infer_format(Symbol(domain_hint), Symbol(format_hint)) -end - -function infer_format(domain_hint::Symbol, format_hint::Symbol) - return infer_format(Val(domain_hint), Val(format_hint)) -end - -function infer_format(::Val, format_hint::Val) - return infer_format(format_hint) + return (variable_map, variable_inv) end - -function infer_format(format_hint::Symbol) - return infer_format(Val(format_hint)) -end \ No newline at end of file diff --git a/src/model/abstract.jl b/src/model/abstract.jl index 24271068..c897002e 100644 --- a/src/model/abstract.jl +++ b/src/model/abstract.jl @@ -1,104 +1,106 @@ model_name(::M) where {M<:AbstractModel} = "QUBO Model" -domain(::AbstractModel{D}) where {D} = D() -domain_name(::BoolDomain) = "Bool" -domain_name(::SpinDomain) = "Spin" -domain_name(model::AbstractModel) = domain_name(domain(model)) +domain_name(dom::Domain) = domain_name(Val(dom)) +domain_name(::Val{BoolDomain}) = "Bool" +domain_name(::Val{SpinDomain}) = "Spin" +domain_name(model::AbstractModel) = domain_name(domain(model)) Base.isempty(model::AbstractModel) = isempty(variable_map(model)) -function explicit_linear_terms(model::AbstractModel) - return _explicit_linear_terms( - linear_terms(model), - variable_inv(model) - ) -end +function explicit_linear_terms(model::AbstractModel{V,T}) where {V,T} + L = linear_terms(model) -function _explicit_linear_terms( - linear_terms::Dict{Int,T}, - variable_inv::Dict{Int}, -) where {T} - merge( - Dict{Int,T}(i => zero(T) for i in keys(variable_inv)), - linear_terms, - ) + return Dict{Int,T}(i => get(L, i, zero(T)) for i = 1:domain_size(model)) end function indices(model::AbstractModel) return collect(1:domain_size(model)) end -function variables(model::AbstractModel) - return sort(collect(keys(variable_map(model))); lt=varcmp) +function variables(model::AbstractModel{V,T}) where {V,T} + return V[variable_inv(model, i) for i = 1:domain_size(model)] end function variable_set(model::AbstractModel) return Set(keys(variable_map(model))) end -function variable_map(model::AbstractModel, v) - variable_map = QUBOTools.variable_map(model) +function variable_map(model::AbstractModel{V,T}, v::V) where {V,T} + mapping = variable_map(model) - if haskey(variable_map, v) - return variable_map[v] + if haskey(mapping, v) + return mapping[v] else error("Variable '$v' does not belong to the model") end end function variable_inv(model::AbstractModel, i::Integer) - variable_inv = QUBOTools.variable_inv(model) + mapping = variable_inv(model) - if haskey(variable_inv, i) - return variable_inv[i] + if haskey(mapping, i) + return mapping[i] else error("Variable index '$i' does not belong to the model") end end # ~*~ Model's Normal Forms ~*~ # -qubo(model::AbstractModel{<:BoolDomain}) = qubo(model, Dict) - -function qubo(model::AbstractModel{<:BoolDomain}, ::Type{Dict}, T::Type = Float64) +function qubo(model::AbstractModel, type::Type = Dict) n = domain_size(model) - m = quadratic_size(model) - Q = Dict{Tuple{Int,Int},T}() + L, Q, α, β = swap_domain( + domain(model), + Domain(:bool), + linear_terms(model), + quadratic_terms(model), + scale(model), + offset(model), + ) - α::T = scale(model) - β::T = offset(model) + return qubo(type, n, L, Q, α, β) +end - sizehint!(Q, m + n) +function qubo( + ::Type{Dict}, + ::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} + Q = sizehint!(Dict{Tuple{Int,Int},T}(), length(L̄) + length(Q̄)) - for (i, qi) in explicit_linear_terms(model) + for (i, qi) in L̄ Q[i, i] = qi end - for ((i, j), Qij) in quadratic_terms(model) + for ((i, j), Qij) in Q̄ Q[i, j] = Qij end return (Q, α, β) end -function qubo(model::AbstractModel{<:BoolDomain}, ::Type{Vector}, T::Type = Float64) - n = domain_size(model) - m = quadratic_size(model) - +function qubo( + ::Type{Vector}, + n::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} L = zeros(T, n) - Q = Vector{T}(undef, m) - u = Vector{Int}(undef, m) - v = Vector{Int}(undef, m) - - α::T = scale(model) - β::T = offset(model) + Q = Vector{T}(undef, length(Q̄)) + u = Vector{Int}(undef, length(Q̄)) + v = Vector{Int}(undef, length(Q̄)) - for (i, l) in linear_terms(model) - L[i] = l + for (i, c) in L̄ + L[i] = c end - for (k, ((i, j), q)) in enumerate(quadratic_terms(model)) - Q[k] = q + for (k, ((i, j), c)) in enumerate(Q̄) + Q[k] = c u[k] = i v[k] = j end @@ -106,90 +108,104 @@ function qubo(model::AbstractModel{<:BoolDomain}, ::Type{Vector}, T::Type = Floa return (L, Q, u, v, α, β) end -function qubo(model::AbstractModel{<:BoolDomain}, ::Type{Matrix}, T::Type = Float64) - n = domain_size(model) - +function qubo( + ::Type{Matrix}, + n::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} Q = zeros(T, n, n) - α::T = scale(model) - β::T = offset(model) - - for (i, l) in linear_terms(model) - Q[i, i] = l + for (i, c) in L̄ + Q[i, i] = c end - for ((i, j), q) in quadratic_terms(model) - Q[i, j] = q + for ((i, j), c) in Q̄ + Q[i, j] = c end return (Q, α, β) end -function qubo(model::AbstractModel{<:BoolDomain}, ::Type{SparseMatrixCSC}, T::Type = Float64) - n = domain_size(model) - +function qubo( + ::Type{SparseMatrixCSC}, + n::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} Q = spzeros(T, n, n) - α::T = scale(model) - β::T = offset(model) - - for (i, l) in linear_terms(model) - Q[i, i] = l + for (i, c) in L̄ + Q[i, i] = c end - for ((i, j), q) in quadratic_terms(model) - Q[i, j] = q + for ((i, j), c) in Q̄ + Q[i, j] = c end return (Q, α, β) end -function qubo(model::AbstractModel{<:SpinDomain}, args...) - return qubo(ising(model, args...)...) -end - -ising(model::AbstractModel) = ising(model, Dict, Float64) - -function ising(model::AbstractModel{<:SpinDomain}, ::Type{Dict}, T::Type = Float64) +function ising(model::AbstractModel, type::Type = Dict) n = domain_size(model) - m = quadratic_size(model) - h = Dict{Int,T}() - J = Dict{Tuple{Int,Int},T}() + L, Q, α, β = swap_domain( + domain(model), + Domain(:spin), + linear_terms(model), + quadratic_terms(model), + scale(model), + offset(model), + ) + + return ising(type, n, L, Q, α, β) +end - α::T = scale(model) - β::T = offset(model) +function ising( + ::Type{Dict}, + n::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} + h = sizehint!(Dict{Int,T}(), length(L̄)) + J = sizehint!(Dict{Tuple{Int,Int},T}(), length(Q̄)) - for (i, hi) in explicit_linear_terms(model) - h[i] = hi + for (i, c) in L̄ + h[i] = c end - for ((i, j), Jij) in quadratic_terms(model) - J[i, j] = Jij + for ((i, j), c) in Q̄ + J[i, j] = c end return (h, J, α, β) end -function ising(model::AbstractModel{<:SpinDomain}, ::Type{Vector}, T::Type = Float64) - n = domain_size(model) - m = quadratic_size(model) - +function ising( + ::Type{Vector}, + n::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} h = zeros(T, n) - J = Vector{T}(undef, m) - u = Vector{Int}(undef, m) - v = Vector{Int}(undef, m) - - α::T = scale(model) - β::T = offset(model) + J = Vector{T}(undef, length(Q̄)) + u = Vector{Int}(undef, length(Q̄)) + v = Vector{Int}(undef, length(Q̄)) - - for (i, hi) in linear_terms(model) - h[i] = hi + for (i, c) in L̄ + h[i] = c end - for (k, ((i, j), q)) in enumerate(quadratic_terms(model)) - J[k] = q + for (k, ((i, j), c)) in enumerate(Q̄) + J[k] = c u[k] = i v[k] = j end @@ -197,76 +213,76 @@ function ising(model::AbstractModel{<:SpinDomain}, ::Type{Vector}, T::Type = Flo return (h, J, u, v, α, β) end -function ising(model::AbstractModel{<:SpinDomain}, ::Type{Matrix}, T::Type = Float64) - n = domain_size(model) - +function ising( + ::Type{Matrix}, + n::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} h = zeros(T, n) J = zeros(T, n, n) - α::T = scale(model) - β::T = offset(model) - - for (i, hi) in linear_terms(model) - h[i] = hi + for (i, c) in L̄ + h[i] = c end - for ((i, j), Jij) in quadratic_terms(model) - J[i, j] = Jij + for ((i, j), c) in Q̄ + J[i, j] = c end return (h, J, α, β) end -function ising(model::AbstractModel{<:SpinDomain}, ::Type{SparseMatrixCSC}, T::Type = Float64) - n = domain_size(model) - +function ising( + ::Type{SparseMatrixCSC}, + n::Integer, + L̄::Dict{Int,T}, + Q̄::Dict{Tuple{Int,Int},T}, + α::T, + β::T, +) where {T} h = spzeros(T, n) J = spzeros(T, n, n) - α::T = scale(model) - β::T = offset(model) - - for (i, hi) in linear_terms(model) - h[i] = hi + for (i, c) in L̄ + h[i] = c end - for ((i, j), Jij) in quadratic_terms(model) - J[i, j] = Jij + for ((i, j), c) in Q̄ + J[i, j] = c end return (h, J, α, β) end -function ising(model::AbstractModel{<:BoolDomain}, args...) - return ising(qubo(model, args...)...) -end - # ~*~ Data queries ~*~ # -function QUBOTools.state(model::AbstractModel, index::Integer) - return QUBOTools.state(QUBOTools.sampleset(model), index) +function state(model::AbstractModel, index::Integer) + return state(sampleset(model), index) end -function QUBOTools.reads(model::AbstractModel) - return QUBOTools.reads(QUBOTools.sampleset(model)) +function reads(model::AbstractModel) + return reads(sampleset(model)) end -function QUBOTools.reads(model::AbstractModel, index::Integer) - return QUBOTools.reads(QUBOTools.sampleset(model), index) +function reads(model::AbstractModel, index::Integer) + return reads(sampleset(model), index) end -function QUBOTools.value(model::AbstractModel, index::Integer) - return QUBOTools.value(QUBOTools.sampleset(model), index) +function value(model::AbstractModel, index::Integer) + return value(sampleset(model), index) end -function QUBOTools.value(model::AbstractModel, ψ::Vector{U}) where {U<:Integer} - α = QUBOTools.scale(model) - e = QUBOTools.offset(model) +function value(model::AbstractModel, ψ::Vector{U}) where {U<:Integer} + α = scale(model) + e = offset(model) - for (i, l) in QUBOTools.linear_terms(model) + for (i, l) in linear_terms(model) e += ψ[i] * l end - for ((i, j), q) in QUBOTools.quadratic_terms(model) + for ((i, j), q) in quadratic_terms(model) e += ψ[i] * ψ[j] * q end @@ -274,52 +290,52 @@ function QUBOTools.value(model::AbstractModel, ψ::Vector{U}) where {U<:Integer} end # ~*~ Queries: sizes & density ~*~ # -QUBOTools.domain_size(model::AbstractModel) = length(QUBOTools.variable_map(model)) -QUBOTools.linear_size(model::AbstractModel) = length(QUBOTools.linear_terms(model)) -QUBOTools.quadratic_size(model::AbstractModel) = length(QUBOTools.quadratic_terms(model)) +domain_size(model::AbstractModel) = length(variable_map(model)) +linear_size(model::AbstractModel) = length(linear_terms(model)) +quadratic_size(model::AbstractModel) = length(quadratic_terms(model)) -function QUBOTools.density(model::AbstractModel) - n = QUBOTools.domain_size(model) +function density(model::AbstractModel) + n = domain_size(model) if n == 0 return NaN else - l = QUBOTools.linear_size(model) - q = QUBOTools.quadratic_size(model) + ls = linear_size(model) + qs = quadratic_size(model) - return (2 * q + l) / (n * n) + return (2 * qs + ls) / (n * n) end end -function QUBOTools.linear_density(model::AbstractModel) - n = QUBOTools.domain_size(model) +function linear_density(model::AbstractModel) + n = domain_size(model) if n == 0 return NaN else - l = QUBOTools.linear_size(model) + ls = linear_size(model) - return l / n + return ls / n end end -function QUBOTools.quadratic_density(model::AbstractModel) - n = QUBOTools.domain_size(model) +function quadratic_density(model::AbstractModel) + n = domain_size(model) if n <= 1 return NaN else - q = QUBOTools.quadratic_size(model) + qs = quadratic_size(model) - return (2 * q) / (n * (n - 1)) + return (2 * qs) / (n * (n - 1)) end end -function QUBOTools.adjacency(model::AbstractModel) - n = QUBOTools.domain_size(model) +function adjacency(model::AbstractModel) + n = domain_size(model) A = Dict{Int,Set{Int}}(i => Set{Int}() for i = 1:n) - for (i, j) in keys(QUBOTools.quadratic_terms(model)) + for (i, j) in keys(quadratic_terms(model)) push!(A[i], j) push!(A[j], i) end @@ -327,10 +343,10 @@ function QUBOTools.adjacency(model::AbstractModel) return A end -function QUBOTools.adjacency(model::AbstractModel, k::Integer) +function adjacency(model::AbstractModel, k::Integer) A = Set{Int}() - for (i, j) in keys(QUBOTools.quadratic_terms(model)) + for (i, j) in keys(quadratic_terms(model)) if i == k push!(A, j) elseif j == k @@ -342,34 +358,37 @@ function QUBOTools.adjacency(model::AbstractModel, k::Integer) end # ~*~ I/O ~*~ # -function Base.read(source::Any, fmt::AbstractFormat) +function Base.read(source::Union{IO,AbstractString}, fmt::AbstractFormat) return read_model(source, fmt) end -function Base.read!(source::Any, model::AbstractModel, fmt::AbstractFormat) +function Base.read!( + source::Union{IO,AbstractString}, + model::AbstractModel, + fmt::AbstractFormat, +) return read_model!(source, model, fmt) end -function Base.write(target::Any, model::AbstractModel, fmt::AbstractFormat) +function Base.write( + target::Union{IO,AbstractString}, + model::AbstractModel, + fmt::AbstractFormat, +) return write_model(target, model, fmt) end -function Base.copy!( - target::X, - source::Y, -) where {X<:AbstractModel,Y<:AbstractModel} +function Base.copy!(target::X, source::Y) where {X<:AbstractModel,Y<:AbstractModel} return copy!(target, convert(X, source)) end function Base.show(io::IO, model::AbstractModel) - s = sense(model) === Min ? "Min" : "Max" - println( io, """ - $(model_name(model)) [$(s), $(domain_name(model))] + $(model_name(model)) [$(sense(model)), $(domain(model))] ▷ Variables ……… $(domain_size(model)) - """ + """, ) if isempty(model) @@ -377,7 +396,7 @@ function Base.show(io::IO, model::AbstractModel) io, """ The model is empty. - """ + """, ) return nothing @@ -389,7 +408,7 @@ function Base.show(io::IO, model::AbstractModel) ▷ Linear ……………… $(@sprintf("%0.2f", 100.0 * linear_density(model)))% ▷ Quadratic ……… $(@sprintf("%0.2f", 100.0 * quadratic_density(model)))% ▷ Total ………………… $(@sprintf("%0.2f", 100.0 * density(model)))% - """ + """, ) end @@ -398,7 +417,7 @@ function Base.show(io::IO, model::AbstractModel) io, """ There are no solutions available. - """ + """, ) return nothing @@ -413,7 +432,7 @@ function Base.show(io::IO, model::AbstractModel) Solutions: ▷ Samples …………… $(n) ▷ Best value …… $(z) - """ + """, ) end diff --git a/src/model/model.jl b/src/model/model.jl index 9302603e..bce79196 100644 --- a/src/model/model.jl +++ b/src/model/model.jl @@ -1,10 +1,9 @@ @doc raw""" Model{ - D <: VariableDomain, V <: Any, T <: Real, U <: Integer - } <: AbstractModel{D} + } <: AbstractModel{V,T} The `Model` was designed to work as a general for all implemented interfaces. It is intended to be the core engine behind the target codecs. @@ -13,7 +12,7 @@ It is intended to be the core engine behind the target codecs. Both `V <: Any` and `T <: Real` parameters exist to support MathOptInterface/JuMP integration. By choosing `V = MOI.VariableIndex` and `T` matching `Optimizer{T}` the hard work should be done. -""" mutable struct Model{D<:VariableDomain,V<:Any,T<:Real,U<:Integer} <: AbstractModel{D} +""" mutable struct Model{V<:Any,T<:Real,U<:Integer} <: AbstractModel{V,T} # ~*~ Required data ~*~ linear_terms::Dict{Int,T} quadratic_terms::Dict{Tuple{Int,Int},T} @@ -22,8 +21,9 @@ By choosing `V = MOI.VariableIndex` and `T` matching `Optimizer{T}` the hard wor # ~*~ Factors ~*~ scale::T offset::T - # ~*~ Sense ~*~ + # ~*~ Sense & Domain ~*~ sense::Sense + domain::Union{Domain,Nothing} # ~*~ Metadata ~*~ id::Union{Int,Nothing} version::Union{VersionNumber,Nothing} @@ -32,57 +32,65 @@ By choosing `V = MOI.VariableIndex` and `T` matching `Optimizer{T}` the hard wor # ~*~ Solutions ~*~ sampleset::SampleSet{T,U} - function Model{D,V,T,U}( + function Model{V,T,U}( # ~*~ Required data ~*~ linear_terms::Dict{Int,T}, quadratic_terms::Dict{Tuple{Int,Int},T}, variable_map::Dict{V,Int}, variable_inv::Dict{Int,V}; # ~*~ Factors ~*~ - scale::Union{T,Nothing} = nothing, + scale::Union{T,Nothing} = nothing, offset::Union{T,Nothing} = nothing, - # ~*~ Sense ~*~ - sense::Union{Sense,Symbol,Nothing} = nothing, + # ~*~ Sense & Domain ~*~ + sense::Union{Sense,Symbol,Nothing} = nothing, + domain::Union{Domain,Symbol,Nothing} = nothing, # ~*~ Metadata ~*~ - id::Union{Integer,Nothing} = nothing, - version::Union{VersionNumber,Nothing} = nothing, - description::Union{String,Nothing} = nothing, + id::Union{Integer,Nothing} = nothing, + version::Union{VersionNumber,Nothing} = nothing, + description::Union{String,Nothing} = nothing, metadata::Union{Dict{String,Any},Nothing} = nothing, # ~*~ Solutions ~*~ sampleset::Union{SampleSet{T,U},Nothing} = nothing, - ) where {D,V,T,U} - new{D,V,T,U}( + ) where {V,T,U} + scale = isnothing(scale) ? one(T) : scale + offset = isnothing(offset) ? zero(T) : offset + sense = isnothing(sense) ? Sense(:min) : Sense(sense) + domain = isnothing(domain) ? nothing : Domain(domain) + sampleset = isnothing(sampleset) ? SampleSet{T,U}() : sampleset + + return new{V,T,U}( linear_terms, quadratic_terms, variable_map, variable_inv, - something(scale, one(T)), - something(offset, zero(T)), - Sense(something(sense, :min)), + scale, + offset, + sense, + domain, id, version, description, metadata, - something(sampleset, SampleSet{T,U}()), + sampleset, ) end end -function Model{D,V,T,U}( +function Model{V,T,U}( # ~*~ Required data ~*~ _linear_terms::Dict{V,T}, _quadratic_terms::Dict{Tuple{V,V},T}, _variable_set::Union{Set{V},Nothing} = nothing; kws..., -) where {D,V,T,U} +) where {V,T,U} # ~ What is happening now: There were many layers of validation - # before we got here. This call to `_normal_form` removes any re- - # dundancy by aggregating (i, j) and (j, i) terms and also ma- - # king "quadratic" terms with i == j into linear ones. Also, - # zeros are removed, improving sparsity in this last step. + # before we got here. This call to `_normal_form` removes any + # redundancy by aggregating (i, j) and (j, i) terms and also + # making "quadratic" terms with i == j into linear ones. + # Also, zeros are removed, improving sparsity in this last step. # ~ New objects are created not to disturb the original ones. _linear_terms, _quadratic_terms, variable_set = - QUBOTools._normal_form(_linear_terms, _quadratic_terms) + _normal_form(_linear_terms, _quadratic_terms) if isnothing(_variable_set) _variable_set = variable_set @@ -90,31 +98,28 @@ function Model{D,V,T,U}( error("'variable_set' is not a subset of '_variable_set'") end - variable_map, variable_inv = QUBOTools._build_mapping(_variable_set) + variable_map, variable_inv = _build_mapping(_variable_set) linear_terms, quadratic_terms = - QUBOTools._map_terms(_linear_terms, _quadratic_terms, variable_map) + _map_terms(_linear_terms, _quadratic_terms, variable_map) - return Model{D,V,T,U}(linear_terms, quadratic_terms, variable_map, variable_inv; kws...) + return Model{V,T,U}(linear_terms, quadratic_terms, variable_map, variable_inv; kws...) end -function Model{D,V,T,U}(; kws...) where {D,V,T,U} - return Model{D,V,T,U}(Dict{V,T}(), Dict{Tuple{V,V},T}(); kws...) +# ~*~ Empty Constructor ~*~ # +function Model{V,T,U}(; kws...) where {V,T,U} + return Model{V,T,U}(Dict{V,T}(), Dict{Tuple{V,V},T}(); kws...) end -function Model{D,V,T}(args...; kws...) where {D,V,T} - return Model{D,V,T,Int}(args...; kws...) +function Model{V,T}(args...; kws...) where {V,T} + return Model{V,T,Int}(args...; kws...) end -function Model{D,V}(args...; kws...) where {D,V} - return Model{D,V,Float64,Int}(args...; kws...) +function Model{V}(args...; kws...) where {V} + return Model{V,Float64,Int}(args...; kws...) end -function Model{D}(args...; kws...) where {D} - return Model{D,Int,Float64,Int}(args...; kws...) -end - -function Base.empty!(model::Model{D,V,T,U}) where {D,V,T,U} +function Base.empty!(model::Model{V,T,U}) where {V,T,U} # ~*~ Structures ~*~ # empty!(model.linear_terms) empty!(model.quadratic_terms) @@ -125,6 +130,7 @@ function Base.empty!(model::Model{D,V,T,U}) where {D,V,T,U} model.scale = one(T) model.offset = zero(T) model.sense = Sense(:min) + model.domain = nothing model.id = nothing model.version = nothing model.description = nothing @@ -138,15 +144,16 @@ function Base.isempty(model::Model) return isempty(model.variable_map) && isempty(model.variable_inv) end -function Base.copy(model::Model{D,V,T,U}) where {D,V,T,U} - return Model{D,V,T,U}( - copy(model.linear_terms), - copy(model.quadratic_terms), - copy(model.variable_map), - copy(model.variable_inv); +function Base.copy(model::Model{V,T,U}) where {V,T,U} + return Model{V,T,U}( + copy(linear_terms(model)), + copy(quadratic_terms(model)), + copy(variable_map(model)), + copy(variable_inv(model)); scale = scale(model), offset = offset(model), sense = sense(model), + domain = domain(model), id = id(model), version = version(model), description = description(model), @@ -158,6 +165,7 @@ end scale(model::Model) = model.scale offset(model::Model) = model.offset sense(model::Model) = model.sense +domain(model::Model) = model.domain linear_terms(model::Model) = model.linear_terms quadratic_terms(model::Model) = model.quadratic_terms @@ -170,21 +178,19 @@ description(model::Model) = model.description metadata(model::Model) = model.metadata sampleset(model::Model) = model.sampleset -function swap_domain(::D, ::D, model::Model{D}) where {D<:VariableDomain} - return model -end +function swap_domain(target::Domain, model::Model{V,T,U}) where {V,T,U} + source = domain(model) -function swap_domain(::X, ::Y, model::Model{X,V,T,U}) where {X,Y,V,T,U} L, Q, α, β = swap_domain( - X(), - Y(), + source, + target, linear_terms(model), quadratic_terms(model), scale(model), offset(model), ) - return Model{Y,V,T,U}( + return Model{V,T,U}( L, Q, copy(variable_map(model)), @@ -192,23 +198,25 @@ function swap_domain(::X, ::Y, model::Model{X,V,T,U}) where {X,Y,V,T,U} scale = α, offset = β, sense = sense(model), + domain = target, id = id(model), version = version(model), description = description(model), metadata = metadata(model), - sampleset = swap_domain(X(), Y(), sampleset(model)), + sampleset = swap_domain(source, target, sampleset(model)), ) end -function swap_sense(model::Model{D,V,T,U}) where {D,V,T,U} - return Model{D,V,T,U}( +function swap_sense(model::Model{V,T,U}) where {V,T,U} + return Model{V,T,U}( swap_sense(linear_terms(model)), - swap_sense(quadratic_terms(model)), + swap_sense(quadratic_terms(model)), copy(variable_map(model)), copy(variable_inv(model)); scale = scale(model), offset = -offset(model), sense = swap_sense(sense(model)), + domain = domain(model), id = id(model), version = version(model), description = description(model), @@ -217,13 +225,15 @@ function swap_sense(model::Model{D,V,T,U}) where {D,V,T,U} ) end -function Base.copy!(target::Model{D,V,T,U}, source::Model{D,V,T,U}) where {D,V,T,U} +function Base.copy!(target::Model{V,T,U}, source::Model{V,T,U}) where {V,T,U} target.linear_terms = copy(linear_terms(source)) target.quadratic_terms = copy(quadratic_terms(source)) target.variable_map = copy(variable_map(source)) target.variable_inv = copy(variable_inv(source)) target.scale = scale(source) target.offset = offset(source) + target.sense = sense(source) + target.domain = domain(source) target.id = id(source) target.version = version(source) target.description = description(source) @@ -233,12 +243,4 @@ function Base.copy!(target::Model{D,V,T,U}, source::Model{D,V,T,U}) where {D,V,T return target end -function Base.copy!(target::Model{Y,V,T,U}, source::Model{X,V,T,U}) where {X,Y,V,T,U} - return copy!(target, convert(Model{Y,V,T,U}, source)) -end - -function Base.convert(::Type{Model{Y,V,T,U}}, model::Model{X,V,T,U}) where {X,Y,V,T,U} - return swap_domain(X(), Y(), model) -end - -const StandardModel{D} = Model{D,Int,Float64,Int} \ No newline at end of file +const StandardModel = Model{Int,Float64,Int} \ No newline at end of file diff --git a/test/Project.toml b/test/Project.toml index aa9c5c76..93d60f6d 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] Printf = "de0858da-6303-5e67-8744-51eddeeeb8d7" +RecipesBase = "3cdcf5f2-1ef4-517c-9805-6587b60abb01" SparseArrays = "2f01184e-e22b-5df5-ae63-d93ebab69eaf" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" diff --git a/test/assets/assets.jl b/test/assets/assets.jl new file mode 100644 index 00000000..ac43d2df --- /dev/null +++ b/test/assets/assets.jl @@ -0,0 +1,41 @@ +""" + __data_path(::AbstractFormat, ::Val{dom}, ::Integer) +""" +function __data_path end + +function __data_path(index::Integer, path::AbstractString) + return joinpath(@__DIR__, "..", "data", @sprintf("%02d", index), path) +end + +function __data_path(fmt::QUBOTools.AbstractFormat, dom::QUBOTools.Domain, i::Integer) + return __data_path(fmt, dom, i) +end + +__data_path(::QUBOTools.BQPJSON, ::QUBOTools.BoolDomain, i::Integer) = __data_path(i, "bool.json") +__data_path(::QUBOTools.BQPJSON, ::QUBOTools.SpinDomain, i::Integer) = __data_path(i, "spin.json") +__data_path(::QUBOTools.HFS, ::QUBOTools.BoolDomain, i::Integer) = __data_path(i, "bool.hfs") +__data_path(::QUBOTools.Qubist, ::QUBOTools.SpinDomain, i::Integer) = __data_path(i, "spin.qh") +__data_path(::QUBOTools.QUBO, ::QUBOTools.BoolDomain, i::Integer) = __data_path(i, "bool.qubo") +__data_path(::QUBOTools.MiniZinc, ::QUBOTools.BoolDomain, i::Integer) = __data_path(i, "bool.mzn") +__data_path(::QUBOTools.MiniZinc, ::QUBOTools.SpinDomain, i::Integer) = __data_path(i, "spin.mzn") + +""" + __temp_path(::AbstractFormat, ::Val{dom}, ::Integer) +""" +function __temp_path end + +function __temp_path(index::Integer, path::AbstractString) + file_path = joinpath(tempname(; cleanup = true), @sprintf("%02d", index), path) + + mkpath(dirname(file_path)) + + return file_path +end + +__temp_path(::QUBOTools.BQPJSON, ::QUBOTools.BoolDomain, i::Integer) = __temp_path(i, "bool.json") +__temp_path(::QUBOTools.BQPJSON, ::QUBOTools.SpinDomain, i::Integer) = __temp_path(i, "spin.json") +__temp_path(::QUBOTools.HFS, ::QUBOTools.BoolDomain, i::Integer) = __temp_path(i, "bool.hfs") +__temp_path(::QUBOTools.Qubist, ::QUBOTools.SpinDomain, i::Integer) = __temp_path(i, "spin.qh") +__temp_path(::QUBOTools.QUBO, ::QUBOTools.BoolDomain, i::Integer) = __temp_path(i, "bool.qubo") +__temp_path(::QUBOTools.MiniZinc, ::QUBOTools.BoolDomain, i::Integer) = __temp_path(i, "bool.mzn") +__temp_path(::QUBOTools.MiniZinc, ::QUBOTools.SpinDomain, i::Integer) = __temp_path(i, "spin.mzn") \ No newline at end of file diff --git a/test/runtests.jl b/test/runtests.jl index 048311a7..90dd54b5 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,7 +1,9 @@ using Test using Printf using SparseArrays +using RecipesBase using QUBOTools + import QUBOTools: ↑, ↓, 𝔹, 𝕊 import QUBOTools: Sample, SampleSet import QUBOTools: CodecError, codec_error @@ -13,6 +15,7 @@ import QUBOTools: backend import QUBOTools: BQPJSON, HFS, MiniZinc, Qubist, QUBO # ~*~ Include test functions ~*~ +include("assets/assets.jl") include("tools/tools.jl") include("unit/unit.jl") include("integration/integration.jl") diff --git a/test/tools/info.jl b/test/tools/info.jl index 7476b860..e7b5a38f 100644 --- a/test/tools/info.jl +++ b/test/tools/info.jl @@ -1,9 +1,9 @@ -const TEST_CASES = Dict{Type,UnitRange}( - BQPJSON{𝔹} => 0:3, - BQPJSON{𝕊} => 0:3, - HFS{𝔹} => 1:0, - MiniZinc{𝔹} => 0:1, - MiniZinc{𝕊} => 0:1, - Qubist{𝕊} => 0:3, - QUBO{𝔹} => 0:2, +const TEST_CASES = Dict{Any,Any}( + BQPJSON(𝔹) => 0:3, + BQPJSON(𝕊) => 0:3, + HFS(𝔹) => 1:0, + MiniZinc(𝔹) => 0:1, + MiniZinc(𝕊) => 0:1, + Qubist(𝕊) => 0:3, + QUBO(𝔹) => 0:2, ) \ No newline at end of file diff --git a/test/tools/paths.jl b/test/tools/paths.jl index aa43837f..059379cd 100644 --- a/test/tools/paths.jl +++ b/test/tools/paths.jl @@ -1,21 +1,21 @@ const DATA_PATH = joinpath(@__DIR__, "..", "data") -const TEST_DATA_PATH = Dict{Type,Function}( - BQPJSON{𝔹} => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.json"), - BQPJSON{𝕊} => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "spin.json"), - HFS{𝔹} => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.hfs"), - MiniZinc{𝔹} => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.mzn"), - MiniZinc{𝕊} => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "spin.mzn"), - Qubist{𝕊} => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "spin.qh"), - QUBO{𝔹} => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.qubo"), +const TEST_DATA_PATH = Dict{Tuple{Type,QUBOTools.Domain},Function}( + (BQPJSON, 𝔹) => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.json"), + (BQPJSON, 𝕊) => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "spin.json"), + (HFS, 𝔹) => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.hfs"), + (MiniZinc, 𝔹) => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.mzn"), + (MiniZinc, 𝕊) => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "spin.mzn"), + (Qubist, 𝕊) => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "spin.qh"), + (QUBO, 𝔹) => (i::Integer) -> joinpath(DATA_PATH, @sprintf("%02d", i), "bool.qubo"), ) -const TEMP_DATA_PATH = Dict{Type,Function}( - BQPJSON{𝔹} => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.json", i)), - BQPJSON{𝕊} => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.spin.temp.json", i)), - HFS{𝔹} => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.hfs", i)), - MiniZinc{𝔹} => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.mzn", i)), - MiniZinc{𝕊} => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.spin.temp.mzn", i)), - Qubist{𝕊} => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.spin.temp.qh", i)), - QUBO{𝔹} => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.qubo", i)), +const TEMP_DATA_PATH = Dict{Tuple{Type,QUBOTools.Domain},Function}( + (BQPJSON, 𝔹) => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.json", i)), + (BQPJSON, 𝕊) => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.spin.temp.json", i)), + (HFS, 𝔹) => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.hfs", i)), + (MiniZinc, 𝔹) => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.mzn", i)), + (MiniZinc, 𝕊) => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.spin.temp.mzn", i)), + (Qubist, 𝕊) => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.spin.temp.qh", i)), + (QUBO, 𝔹) => (i::Integer) -> joinpath(tempdir(), @sprintf("%02d.bool.temp.qubo", i)), ) diff --git a/test/unit/analysis/analysis.jl b/test/unit/analysis/analysis.jl index aa4d5fd3..08f60d0f 100644 --- a/test/unit/analysis/analysis.jl +++ b/test/unit/analysis/analysis.jl @@ -1,5 +1,7 @@ include("metrics.jl") +include("plots.jl") function test_analysis() test_metrics() + test_plots() end \ No newline at end of file diff --git a/test/unit/analysis/plots.jl b/test/unit/analysis/plots.jl new file mode 100644 index 00000000..0e757a4e --- /dev/null +++ b/test/unit/analysis/plots.jl @@ -0,0 +1,30 @@ +function test_plots() + @testset "■ Plots ■" verbose = true begin + test_sampleset_plot() + end +end + +function test_sampleset_plot() + @testset "SampleSet" begin + ω = SampleSet([ + Sample([0, 0], 1.0, 1), + Sample([0, 1], 2.0, 2), + Sample([1, 0], 3.0, 3), + Sample([1, 1], 4.0, 4), + ]) + + let r = RecipesBase.apply_recipe(Dict{Symbol,Any}(), ω) + @test length(r) == 1 + @test length(r[].args) == 2 + + x, y = r[].args + attr = r[].plotattributes + + @test x == [1.0, 2.0, 3.0, 4.0] + @test y == [1, 2, 3, 4] + + @test attr[:ylabel] == "Frequency" + @test attr[:xlabel] == "Energy" + end + end +end \ No newline at end of file diff --git a/test/unit/formats/formats.jl b/test/unit/formats/formats.jl index 80ea77bc..e593c4c9 100644 --- a/test/unit/formats/formats.jl +++ b/test/unit/formats/formats.jl @@ -1,6 +1,6 @@ function test_formats() @testset "⦷ Formats ⦷" verbose = true begin - format_list = Type[] + list = [] for fmt in QUBOTools.formats() test_cases = get(TEST_CASES, fmt, nothing) @@ -8,25 +8,29 @@ function test_formats() if isnothing(test_cases) || isempty(test_cases) continue else - push!(format_list, fmt) + push!(list, fmt) end end - for fmt in format_list + for fmt in list + dom = QUBOTools.domain(fmt) test_cases = TEST_CASES[fmt] @testset "▷ $(fmt)" begin for i in test_cases - test_data_path = TEST_DATA_PATH[fmt](i) - temp_data_path = TEMP_DATA_PATH[fmt](i) + test_data_path = __data_path(fmt, dom, i) + temp_data_path = __temp_path(fmt, dom, i) - test_model = QUBOTools.read_model(test_data_path, fmt()) + # if QUBOTools.supports_read(typeof(fmt)) + test_model = QUBOTools.read_model(test_data_path, fmt) @test test_model isa QUBOTools.Model - QUBOTools.write_model(temp_data_path, test_model, fmt()) + # if QUBOTools.supports_write(typeof(fmt)) - temp_model = QUBOTools.read_model(temp_data_path, fmt()) + QUBOTools.write_model(temp_data_path, test_model, fmt) + + temp_model = QUBOTools.read_model(temp_data_path, fmt) @test temp_model isa QUBOTools.Model end diff --git a/test/unit/interface/generic.jl b/test/unit/interface/generic.jl index cef79c56..1af71823 100644 --- a/test/unit/interface/generic.jl +++ b/test/unit/interface/generic.jl @@ -49,11 +49,12 @@ function test_swap_sense() bool_samples1 = [QUBOTools.Sample(s...) for s in zip(bool_states, values, reads)] bool_samples2 = [QUBOTools.Sample(s...) for s in zip(bool_states, -values, reads)] - model1 = QUBOTools.Model{𝔹,V,T,U}( + model1 = QUBOTools.Model{V,T,U}( Dict{V,T}(:x => 1.0, :y => -1.0), Dict{Tuple{V,V},T}((:x, :y) => 2.0); scale = 2.0, offset = 1.0, + domain = 𝔹, id = 1, version = v"0.1.0", description = "This is a Bool ModelWrapper", diff --git a/test/unit/interface/interface.jl b/test/unit/interface/interface.jl index 12b93f4a..379a0e51 100644 --- a/test/unit/interface/interface.jl +++ b/test/unit/interface/interface.jl @@ -22,8 +22,8 @@ function test_interface_data_access(bool_model, bool_samples, spin_model, spin_s @test QUBOTools.model_name(bool_model) == "QUBO Model" @test QUBOTools.model_name(spin_model) == "QUBO Model" - @test QUBOTools.domain(bool_model) == QUBOTools.BoolDomain() - @test QUBOTools.domain(spin_model) == QUBOTools.SpinDomain() + @test QUBOTools.domain(bool_model) == BoolDomain() + @test QUBOTools.domain(spin_model) == SpinDomain() @test QUBOTools.domain_name(bool_model) == "Bool" @test QUBOTools.domain_name(spin_model) == "Spin" @@ -375,8 +375,8 @@ end function test_interface() V = Symbol - U = Int T = Float64 + U = Int bool_states = [[0, 1], [0, 0], [1, 0], [1, 1]] spin_states = [[↑, ↓], [↑, ↑], [↓, ↑], [↓, ↓]] @@ -387,13 +387,14 @@ function test_interface() spin_samples = [QUBOTools.Sample(s...) for s in zip(spin_states, values, reads)] null_model = ModelWrapper( - QUBOTools.Model{𝔹,V,T,U}( + QUBOTools.Model{V,T,U}( Dict{V,T}(), Dict{Tuple{V,V},T}(); - id = 0, - version = v"0.0.0", + id = 0, + domain = 𝔹, + version = v"0.0.0", description = "This is a Null ModelWrapper", - metadata = Dict{String,Any}( + metadata = Dict{String,Any}( "meta" => "data", "type" => "null", ), @@ -401,11 +402,12 @@ function test_interface() ) bool_model = ModelWrapper( - QUBOTools.Model{𝔹,V,T,U}( + QUBOTools.Model{V,T,U}( Dict{V,T}(:x => 1.0, :y => -1.0), Dict{Tuple{V,V},T}((:x, :y) => 2.0); scale = 2.0, offset = 1.0, + domain = 𝔹, id = 1, version = v"0.1.0", description = "This is a Bool ModelWrapper", @@ -418,11 +420,12 @@ function test_interface() ) spin_model = ModelWrapper( - QUBOTools.Model{𝕊,V,T,U}( + QUBOTools.Model{V,T,U}( Dict{V,T}(:x => 1.0), Dict{Tuple{V,V},T}((:x, :y) => 0.5); scale = 2.0, offset = 1.5, + domain = 𝕊, id = 2, version = v"0.2.0", description = "This is a Spin ModelWrapper", @@ -437,7 +440,7 @@ function test_interface() @testset "◈ Interface ◈" verbose = true begin test_interface_setup(bool_model, spin_model, null_model) test_interface_data_access(bool_model, bool_samples, spin_model, spin_samples, null_model) - test_interface_normal_forms(bool_model, spin_model) + # test_interface_normal_forms(bool_model, spin_model) test_interface_evaluation(bool_model, bool_states, spin_model, spin_states, reads, values) end end \ No newline at end of file diff --git a/test/unit/library/sampleset.jl b/test/unit/library/sampleset.jl index 9a1fb445..aaa6819c 100644 --- a/test/unit/library/sampleset.jl +++ b/test/unit/library/sampleset.jl @@ -1,5 +1,6 @@ struct SampleModel{T} end -struct QUBOModel{T} <: QUBOTools.AbstractModel{T} end + +struct QUBOModel{V,T} <: QUBOTools.AbstractModel{V,T} end value(::SampleModel{T}, ::Any) where {T} = zero(T) @@ -12,29 +13,29 @@ function test_samples() @testset "States" begin # ~ Short Circuits ~ # - @test QUBOTools.swap_domain(𝕊(), 𝕊(), ψ) == ψ - @test QUBOTools.swap_domain(𝕊(), 𝕊(), ϕ) == ϕ - @test QUBOTools.swap_domain(𝕊(), 𝕊(), Ψ) == Ψ - @test QUBOTools.swap_domain(𝕊(), 𝕊(), Φ) == Φ - @test QUBOTools.swap_domain(𝔹(), 𝔹(), ψ) == ψ - @test QUBOTools.swap_domain(𝔹(), 𝔹(), ϕ) == ϕ - @test QUBOTools.swap_domain(𝔹(), 𝔹(), Ψ) == Ψ - @test QUBOTools.swap_domain(𝔹(), 𝔹(), Φ) == Φ - - @test QUBOTools.swap_domain(𝕊(), 𝕊(), [Φ, Ψ]) == [Φ, Ψ] - @test QUBOTools.swap_domain(𝕊(), 𝕊(), [ϕ, ψ]) == [ϕ, ψ] - @test QUBOTools.swap_domain(𝔹(), 𝔹(), [Φ, Ψ]) == [Φ, Ψ] - @test QUBOTools.swap_domain(𝔹(), 𝔹(), [ϕ, ψ]) == [ϕ, ψ] + @test QUBOTools.swap_domain(𝕊, 𝕊, ψ) == ψ + @test QUBOTools.swap_domain(𝕊, 𝕊, ϕ) == ϕ + @test QUBOTools.swap_domain(𝕊, 𝕊, Ψ) == Ψ + @test QUBOTools.swap_domain(𝕊, 𝕊, Φ) == Φ + @test QUBOTools.swap_domain(𝔹, 𝔹, ψ) == ψ + @test QUBOTools.swap_domain(𝔹, 𝔹, ϕ) == ϕ + @test QUBOTools.swap_domain(𝔹, 𝔹, Ψ) == Ψ + @test QUBOTools.swap_domain(𝔹, 𝔹, Φ) == Φ + + @test QUBOTools.swap_domain(𝕊, 𝕊, [Φ, Ψ]) == [Φ, Ψ] + @test QUBOTools.swap_domain(𝕊, 𝕊, [ϕ, ψ]) == [ϕ, ψ] + @test QUBOTools.swap_domain(𝔹, 𝔹, [Φ, Ψ]) == [Φ, Ψ] + @test QUBOTools.swap_domain(𝔹, 𝔹, [ϕ, ψ]) == [ϕ, ψ] # ~ State Conversion ~ # - @test QUBOTools.swap_domain(𝔹(), 𝕊(), Φ) == ϕ - @test QUBOTools.swap_domain(𝔹(), 𝕊(), Ψ) == ψ - @test QUBOTools.swap_domain(𝕊(), 𝔹(), ϕ) == Φ - @test QUBOTools.swap_domain(𝕊(), 𝔹(), ψ) == Ψ + @test QUBOTools.swap_domain(𝔹, 𝕊, Φ) == ϕ + @test QUBOTools.swap_domain(𝔹, 𝕊, Ψ) == ψ + @test QUBOTools.swap_domain(𝕊, 𝔹, ϕ) == Φ + @test QUBOTools.swap_domain(𝕊, 𝔹, ψ) == Ψ # ~ Multiple States Conversion ~ # - @test QUBOTools.swap_domain(𝔹(), 𝕊(), [Φ, Ψ]) == [ϕ, ψ] - @test QUBOTools.swap_domain(𝕊(), 𝔹(), [ϕ, ψ]) == [Φ, Ψ] + @test QUBOTools.swap_domain(𝔹, 𝕊, [Φ, Ψ]) == [ϕ, ψ] + @test QUBOTools.swap_domain(𝕊, 𝔹, [ϕ, ψ]) == [Φ, Ψ] end @testset "Samples" begin @@ -260,12 +261,12 @@ function test_samples() @test_throws Exception value(spin_set, 5) # ~ swap_domain ~ # - @test QUBOTools.swap_domain(𝕊(), 𝕊(), bool_set) == bool_set - @test QUBOTools.swap_domain(𝔹(), 𝔹(), bool_set) == bool_set - @test QUBOTools.swap_domain(𝕊(), 𝕊(), spin_set) == spin_set - @test QUBOTools.swap_domain(𝔹(), 𝔹(), spin_set) == spin_set - @test QUBOTools.swap_domain(𝔹(), 𝕊(), bool_set) == spin_set - @test QUBOTools.swap_domain(𝕊(), 𝔹(), spin_set) == bool_set + @test QUBOTools.swap_domain(𝕊, 𝕊, bool_set) == bool_set + @test QUBOTools.swap_domain(𝔹, 𝔹, bool_set) == bool_set + @test QUBOTools.swap_domain(𝕊, 𝕊, spin_set) == spin_set + @test QUBOTools.swap_domain(𝔹, 𝔹, spin_set) == spin_set + @test QUBOTools.swap_domain(𝔹, 𝕊, bool_set) == spin_set + @test QUBOTools.swap_domain(𝕊, 𝔹, spin_set) == bool_set end end end \ No newline at end of file diff --git a/test/unit/library/tools.jl b/test/unit/library/tools.jl index 1e8484f1..ea18c6f2 100644 --- a/test/unit/library/tools.jl +++ b/test/unit/library/tools.jl @@ -75,26 +75,26 @@ function test_tools() ) # ~*~ Type inference ~*~ # - @test QUBOTools.infer_format(:bool, :json) isa QUBOTools.BQPJSON{𝔹} - @test QUBOTools.infer_format("file.bool.json") isa QUBOTools.BQPJSON{𝔹} + @test QUBOTools.infer_format(:bool, :json) isa QUBOTools.BQPJSON + @test QUBOTools.infer_format("file.bool.json") isa QUBOTools.BQPJSON - @test QUBOTools.infer_format(:spin, :json) isa QUBOTools.BQPJSON{𝕊} - @test QUBOTools.infer_format("file.spin.json") isa QUBOTools.BQPJSON{𝕊} + @test QUBOTools.infer_format(:spin, :json) isa QUBOTools.BQPJSON + @test QUBOTools.infer_format("file.spin.json") isa QUBOTools.BQPJSON - @test QUBOTools.infer_format(:hfs) isa QUBOTools.HFS{𝔹} - @test QUBOTools.infer_format("file.hfs") isa QUBOTools.HFS{𝔹} + @test QUBOTools.infer_format(:hfs) isa QUBOTools.HFS + @test QUBOTools.infer_format("file.hfs") isa QUBOTools.HFS - @test QUBOTools.infer_format(:bool, :mzn) isa QUBOTools.MiniZinc{𝔹} - @test QUBOTools.infer_format("file.bool.mzn") isa QUBOTools.MiniZinc{𝔹} + @test QUBOTools.infer_format(:bool, :mzn) isa QUBOTools.MiniZinc + @test QUBOTools.infer_format("file.bool.mzn") isa QUBOTools.MiniZinc - @test QUBOTools.infer_format(:spin, :mzn) isa QUBOTools.MiniZinc{𝕊} - @test QUBOTools.infer_format("file.spin.mzn") isa QUBOTools.MiniZinc{𝕊} + @test QUBOTools.infer_format(:spin, :mzn) isa QUBOTools.MiniZinc + @test QUBOTools.infer_format("file.spin.mzn") isa QUBOTools.MiniZinc - @test QUBOTools.infer_format(:qh) isa QUBOTools.Qubist{𝕊} - @test QUBOTools.infer_format("file.qh") isa QUBOTools.Qubist{𝕊} + @test QUBOTools.infer_format(:qh) isa QUBOTools.Qubist + @test QUBOTools.infer_format("file.qh") isa QUBOTools.Qubist - @test QUBOTools.infer_format(:qubo) isa QUBOTools.QUBO{𝔹} - @test QUBOTools.infer_format("file.qubo") isa QUBOTools.QUBO{𝔹} + @test QUBOTools.infer_format(:qubo) isa QUBOTools.QUBO + @test QUBOTools.infer_format("file.qubo") isa QUBOTools.QUBO @test_throws Exception QUBOTools.infer_format(:xyz) @test_throws Exception QUBOTools.infer_format("file") diff --git a/test/unit/models/bqpjson.jl b/test/unit/models/bqpjson.jl deleted file mode 100644 index 0935ea63..00000000 --- a/test/unit/models/bqpjson.jl +++ /dev/null @@ -1,59 +0,0 @@ -const BQPJSON_ATOL = 1E-12 - -function test_bqpjson(path::String, n::Integer) - @testset "BQPJSON" verbose = true begin - @testset "IO" verbose = true begin - @testset "BOOL" begin - for i = 0:n - bool_path = BQPJSON_BOOL_PATH(path, i) - bool_temp_path = BQPJSON_BOOL_TEMP_PATH(path, i) - try - bool_model = read(bool_path, BQPJSON) - @test bool_model isa BQPJSON{BoolDomain} - - write(bool_temp_path, bool_model) - - temp_model = read(bool_temp_path, BQPJSON) - @test temp_model isa BQPJSON{BoolDomain} - - @test _isvalidbridge( - temp_model, - bool_model, - BQPJSON{BoolDomain}, - ) - catch e - rethrow(e) - finally - rm(bool_temp_path) - end - end - end - - @testset "SPIN" begin - for i = 0:n - spin_path = BQPJSON_SPIN_PATH(path, i) - spin_temp_path = BQPJSON_SPIN_TEMP_PATH(path, i) - try - spin_model = read(spin_path, BQPJSON) - @test spin_model isa BQPJSON{SpinDomain} - - write(spin_temp_path, spin_model) - - temp_model = read(spin_temp_path, BQPJSON) - @test temp_model isa BQPJSON{SpinDomain} - - @test _isvalidbridge( - temp_model, - spin_model, - BQPJSON{BoolDomain}; - atol = 0.0, - ) - catch e - rethrow(e) - finally - rm(spin_temp_path; force = true) - end - end - end - end -end \ No newline at end of file diff --git a/test/unit/models/hfs.jl b/test/unit/models/hfs.jl deleted file mode 100644 index 466e2a39..00000000 --- a/test/unit/models/hfs.jl +++ /dev/null @@ -1,5 +0,0 @@ -function test_hfs(path::String, n::Integer) - @testset "HFS" verbose = true begin - @test true # CI's best friend - end -end \ No newline at end of file diff --git a/test/unit/models/minizinc.jl b/test/unit/models/minizinc.jl deleted file mode 100644 index 95e9c6ec..00000000 --- a/test/unit/models/minizinc.jl +++ /dev/null @@ -1,5 +0,0 @@ -function test_minizinc(path::String, n::Integer) - @testset "MiniZinc" verbose = true begin - @test true # CI's best friend - end -end \ No newline at end of file diff --git a/test/unit/models/qubist.jl b/test/unit/models/qubist.jl deleted file mode 100644 index eafcd4c7..00000000 --- a/test/unit/models/qubist.jl +++ /dev/null @@ -1,30 +0,0 @@ -function test_qubist(path::String, n::Integer) - @testset "Qubist" verbose = true begin - @testset "IO" begin - for i = 0:n - qbst_path = QUBIST_PATH(path, i) - temp_path = QUBIST_TEMP_PATH(path, i) - try - qbst_model = read(qbst_path, Qubist) - @test qbst_model isa Qubist{SpinDomain} - - write(temp_path, qbst_model) - - temp_model = read(temp_path, Qubist) - @test temp_model isa Qubist{SpinDomain} - - @test _isvalidbridge( - temp_model, - qbst_model, - Qubist{SpinDomain}; - atol=0.0 - ) - catch e - rethrow(e) - finally - rm(temp_path; force=true) - end - end - end - end -end \ No newline at end of file diff --git a/test/unit/models/qubo.jl b/test/unit/models/qubo.jl deleted file mode 100644 index 206756a9..00000000 --- a/test/unit/models/qubo.jl +++ /dev/null @@ -1,30 +0,0 @@ -function test_qubo(path::String, n::Integer) - @testset "QUBO" verbose = true begin - @testset "IO" begin - for i = 0:n - qubo_path = QUBO_PATH(path, i) - temp_path = QUBO_TEMP_PATH(path, i) - try - qubo_model = read(qubo_path, QUBO) - @test qubo_model isa QUBO{BoolDomain} - - write(temp_path, qubo_model) - - temp_model = read(temp_path, QUBO) - @test temp_model isa QUBO{BoolDomain} - - @test _isvalidbridge( - temp_model, - qubo_model, - QUBO{BoolDomain}; - atol=0.0 - ) - catch e - rethrow(e) - finally - rm(temp_path; force=true) - end - end - end - end -end \ No newline at end of file diff --git a/test/unit/models/standard.jl b/test/unit/models/standard.jl index 792ba78c..1aa7179a 100644 --- a/test/unit/models/standard.jl +++ b/test/unit/models/standard.jl @@ -48,16 +48,17 @@ function test_standard() sampleset_metadata, ) - std_model = QUBOTools.Model{𝔹,V,T,U}( + std_model = QUBOTools.Model{V,T,U}( _linear_terms, _quadratic_terms; - scale=scale, - offset=offset, - id=id, - version=version, - description=description, - metadata=metadata, - sampleset=sampleset + scale = scale, + offset = offset, + domain = 𝔹, + id = id, + version = version, + description = description, + metadata = metadata, + sampleset = sampleset ) @testset "Standard" verbose = true begin @@ -85,17 +86,16 @@ function test_standard() end @testset "Queries" verbose = true begin - @test QUBOTools.domain(std_model) == 𝔹() + @test QUBOTools.domain(std_model) == 𝔹 @test QUBOTools.domain_name(std_model) == "Bool" @test QUBOTools.domain_size(std_model) == 2 - @test QUBOTools.linear_size(std_model) == 2 + @test QUBOTools.linear_size(std_model) == 2 @test QUBOTools.quadratic_size(std_model) == 1 - @test QUBOTools.linear_density(std_model) ≈ 1.0 + @test QUBOTools.linear_density(std_model) ≈ 1.0 @test QUBOTools.quadratic_density(std_model) ≈ 1.0 - - @test QUBOTools.density(std_model) ≈ 1.0 + @test QUBOTools.density(std_model) ≈ 1.0 end @testset "Copy" verbose = true begin @@ -110,7 +110,7 @@ function test_standard() @test QUBOTools.id(model) == QUBOTools.id(std_model) end - let model = QUBOTools.Model{𝔹,V,T,U}() + let model = QUBOTools.Model{V,T,U}() copy!(model, std_model) @test QUBOTools.linear_terms(model) == QUBOTools.linear_terms(std_model) @test QUBOTools.quadratic_terms(model) == QUBOTools.quadratic_terms(std_model)