From 0ef5819408dd8d6cb8b6ce818d31e5c210879c7c Mon Sep 17 00:00:00 2001 From: Jean-Francois Baffier Date: Sun, 23 Jun 2024 10:41:28 +0900 Subject: [PATCH 1/3] Add usual constraints from XCSP3 (#29) * Fix AllDifferent with param too * AllDiff and AllEq * Cardinality * Channel * Circuit * Count * Cumulative * Element * Extension * Instantiation * Intention (DistDifferent) * Maximum * Minimum * MDD * NValues * No Overlap * Ordered * Regular * Sum * Test items and fixes * Fix test items --- .JuliaFormatter.toml | 1 + Project.toml | 6 +- docs/make.jl | 28 +-- src/CBLS.jl | 54 +++- src/MOI_wrapper.jl | 16 +- src/attributes.jl | 8 +- src/constraints.jl | 416 ++----------------------------- src/constraints/all_different.jl | 65 +++++ src/constraints/all_equal.jl | 86 +++++++ src/constraints/cardinality.jl | 98 ++++++++ src/constraints/channel.jl | 80 ++++++ src/constraints/circuit.jl | 80 ++++++ src/constraints/count.jl | 173 +++++++++++++ src/constraints/cumulative.jl | 94 +++++++ src/constraints/element.jl | 82 ++++++ src/constraints/extension.jl | 201 +++++++++++++++ src/constraints/instantiation.jl | 69 +++++ src/constraints/intention.jl | 59 +++++ src/constraints/maximum.jl | 74 ++++++ src/constraints/mdd.jl | 94 +++++++ src/constraints/minimum.jl | 74 ++++++ src/constraints/n_values.jl | 88 +++++++ src/constraints/no_overlap.jl | 90 +++++++ src/constraints/ordered.jl | 83 ++++++ src/constraints/regular.jl | 83 ++++++ src/constraints/sum.jl | 86 +++++++ src/objectives.jl | 19 +- src/results.jl | 3 +- src/variables.jl | 103 ++++---- test/JuMP.jl | 13 +- test/MOI_wrapper.jl | 6 +- test/TestItemRunner.jl | 5 + test/runtests.jl | 1 + 33 files changed, 1935 insertions(+), 503 deletions(-) create mode 100644 .JuliaFormatter.toml create mode 100644 src/constraints/all_different.jl create mode 100644 src/constraints/all_equal.jl create mode 100644 src/constraints/cardinality.jl create mode 100644 src/constraints/channel.jl create mode 100644 src/constraints/circuit.jl create mode 100644 src/constraints/count.jl create mode 100644 src/constraints/cumulative.jl create mode 100644 src/constraints/element.jl create mode 100644 src/constraints/extension.jl create mode 100644 src/constraints/instantiation.jl create mode 100644 src/constraints/intention.jl create mode 100644 src/constraints/maximum.jl create mode 100644 src/constraints/mdd.jl create mode 100644 src/constraints/minimum.jl create mode 100644 src/constraints/n_values.jl create mode 100644 src/constraints/no_overlap.jl create mode 100644 src/constraints/ordered.jl create mode 100644 src/constraints/regular.jl create mode 100644 src/constraints/sum.jl create mode 100644 test/TestItemRunner.jl diff --git a/.JuliaFormatter.toml b/.JuliaFormatter.toml new file mode 100644 index 0000000..453925c --- /dev/null +++ b/.JuliaFormatter.toml @@ -0,0 +1 @@ +style = "sciml" \ No newline at end of file diff --git a/Project.toml b/Project.toml index a8e340b..fcffc56 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["Jean-Francois Baffier"] version = "0.1.13" [deps] +ConstraintCommons = "e37357d9-0691-492f-a822-e5ea6a920954" ConstraintDomains = "5800fd60-8556-4464-8d61-84ebf7a0bedb" Constraints = "30f324ab-b02d-43f0-b619-e131c61659f7" Intervals = "d8418881-c3e1-53bb-8760-2df7ec849ed5" @@ -11,6 +12,7 @@ JuMP = "4076af6c-e467-56ae-b986-b466b2749572" Lazy = "50d2b5c4-7a5e-59d5-8109-a42b560f39c0" LocalSearchSolvers = "2b10edaa-728d-4283-ac71-07e312d6ccf3" MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee" +TestItems = "1c621080-faea-4a02-84b6-bbd5e436b8fe" [compat] ConstraintDomains = "0.3" @@ -23,7 +25,9 @@ MathOptInterface = "1" julia = "1.6" [extras] +Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" [targets] -test = ["Test"] +test = ["Aqua", "Test", "TestItemRunner"] \ No newline at end of file diff --git a/docs/make.jl b/docs/make.jl index dc38de0..75d15bb 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -1,24 +1,24 @@ using CBLS using Documenter -DocMeta.setdocmeta!(CBLS, :DocTestSetup, :(using CBLS); recursive=true) +DocMeta.setdocmeta!(CBLS, :DocTestSetup, :(using CBLS); recursive = true) makedocs(; - modules=[CBLS], - authors="Jean-Francois Baffier", - repo="https://github.com/JuliaConstraints/CBLS.jl/blob/{commit}{path}#{line}", - sitename="CBLS.jl", - format=Documenter.HTML(; - prettyurls=get(ENV, "CI", "false") == "true", - canonical="https://JuliaConstraints.github.io/CBLS.jl", - assets=String[], + modules = [CBLS], + authors = "Jean-Francois Baffier", + repo = "https://github.com/JuliaConstraints/CBLS.jl/blob/{commit}{path}#{line}", + sitename = "CBLS.jl", + format = Documenter.HTML(; + prettyurls = get(ENV, "CI", "false") == "true", + canonical = "https://JuliaConstraints.github.io/CBLS.jl", + assets = String[] ), - pages=[ - "Home" => "index.md", - ], + pages = [ + "Home" => "index.md" + ] ) deploydocs(; - repo="github.com/JuliaConstraints/CBLS.jl", - devbranch="main", + repo = "github.com/JuliaConstraints/CBLS.jl", + devbranch = "main" ) diff --git a/src/CBLS.jl b/src/CBLS.jl index 4c0f00d..e636f28 100644 --- a/src/CBLS.jl +++ b/src/CBLS.jl @@ -1,12 +1,14 @@ module CBLS -using Constraints +using ConstraintCommons using ConstraintDomains +using Constraints using Intervals using JuMP using Lazy using LocalSearchSolvers using MathOptInterface +using TestItems # Const const LS = LocalSearchSolvers @@ -28,19 +30,27 @@ const VAR_TYPES = Union{MOI.ZeroOne, MOI.Integer} export DiscreteSet # Export: Constraints +export Error, Predicate + export AllDifferent export AllEqual -export AllEqualParam -export AlwaysTrue -export DistDifferent -export Eq -export Error -export LessThanParam -export MinusEqualParam +export Cardinality, CardinalityClosed, CardinalityOpen +export Channel +export Circuit +export Count, AtLeast, AtMost, Exactly +export Cumulative +export Element +export Extension, Supports, Conflicts +export Instantiation +export DistDifferent # Implementation of an intensional constraint +export Maximum +export MDDConstraint +export Minimum +export NValues +export NoOverlap, NoOverlapNoZero, NoOverlapWithZero export Ordered -export Predicate -export SequentialTasks -export SumEqualParam +export Regular +export Sum #Export: Scalar objective function export ScalarFunction @@ -49,7 +59,29 @@ export ScalarFunction include("MOI_wrapper.jl") include("attributes.jl") include("variables.jl") + +## Constraints include("constraints.jl") +include("constraints/all_different.jl") +include("constraints/all_equal.jl") +include("constraints/cardinality.jl") +include("constraints/channel.jl") +include("constraints/circuit.jl") +include("constraints/count.jl") +include("constraints/cumulative.jl") +include("constraints/element.jl") +include("constraints/extension.jl") +include("constraints/instantiation.jl") +include("constraints/intention.jl") +include("constraints/maximum.jl") +include("constraints/mdd.jl") +include("constraints/minimum.jl") +include("constraints/n_values.jl") +include("constraints/no_overlap.jl") +include("constraints/ordered.jl") +include("constraints/regular.jl") +include("constraints/sum.jl") + include("objectives.jl") include("results.jl") diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 3b00126..fca601b 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -9,10 +9,10 @@ DOCSTRING - `set`: DESCRIPTION """ function JuMP.build_variable( - ::Function, - info::JuMP.VariableInfo, - set::T, -) where {T<:MOI.AbstractScalarSet} + ::Function, + info::JuMP.VariableInfo, + set::T +) where {T <: MOI.AbstractScalarSet} return JuMP.VariableConstrainedOnCreation(JuMP.ScalarVariable(info), set) end @@ -42,7 +42,7 @@ function Optimizer(model = model(); options = Options()) solver(model, options = options), Set{Int}(), Set{Int}() - ) + ) end # forward functions from Solver @@ -102,7 +102,7 @@ struct DiscreteSet{T <: Number} <: MOI.AbstractScalarSet values::Vector{T} end DiscreteSet(values) = DiscreteSet(collect(values)) -DiscreteSet(values::T...) where {T<:Number} = DiscreteSet(collect(values)) +DiscreteSet(values::T...) where {T <: Number} = DiscreteSet(collect(values)) """ Base.copy(set::DiscreteSet) = begin @@ -118,7 +118,9 @@ DOCSTRING """ MOI.empty!(opt) = empty!(opt) - function MOI.is_valid(optimizer::Optimizer, index::CI{VI, MOI.Integer}) return index.value ∈ optimizer.int_vars end + +Base.copy(op::F) where {F <: Function} = op +Base.copy(::Nothing) = nothing diff --git a/src/attributes.jl b/src/attributes.jl index 04396fd..12490d3 100644 --- a/src/attributes.jl +++ b/src/attributes.jl @@ -8,7 +8,7 @@ MOI.supports(::Optimizer, ::MOI.NumberOfThreads) = true MOI.set(model::Optimizer, ::MOI.TimeLimitSec, value::Union{Nothing,Float64}) Set the time limit """ -function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, value::Union{Nothing,Float64}) +function MOI.set(model::Optimizer, ::MOI.TimeLimitSec, value::Union{Nothing, Float64}) set_option!(model, "time_limit", isnothing(value) ? Inf : value) end function MOI.get(model::Optimizer, ::MOI.TimeLimitSec) @@ -20,14 +20,14 @@ end MOI.set(model::Optimizer, p::MOI.RawOptimizerAttribute, value) Set a RawOptimizerAttribute to `value` """ -MOI.set(model::Optimizer, p::MOI.RawOptimizerAttribute, value) = set_option!(model, p.name, value) +MOI.set(model::Optimizer, p::MOI.RawOptimizerAttribute, value) = set_option!( + model, p.name, value) MOI.get(model::Optimizer, p::MOI.RawOptimizerAttribute) = get_option(model, p.name) - function MOI.set(model::Optimizer, ::MOI.NumberOfThreads, value) set_option!(model, "threads", isnothing(value) ? typemax(0) : value) end function MOI.get(model::Optimizer, ::MOI.NumberOfThreads) nt = get_option(model, "threads") return nt == typemax(0) ? nothing : nt -end \ No newline at end of file +end diff --git a/src/constraints.jl b/src/constraints.jl index 45716af..0e93d56 100644 --- a/src/constraints.jl +++ b/src/constraints.jl @@ -43,7 +43,8 @@ DOCSTRING - `vars`: DESCRIPTION - `set`: DESCRIPTION """ -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIError{F}) where {F <: Function} +function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, + set::MOIError{F}) where {F <: Function} cidx = constraint!(optimizer, set.f, map(x -> x.value, vars.variables)) return CI{VOV, MOIError{F}}(cidx) end @@ -94,7 +95,8 @@ function MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIPredicate{F ) where {F <: Function} return true end -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIPredicate{F}) where {F <: Function} +function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, + set::MOIPredicate{F}) where {F <: Function} err = x -> convert(Float64, !set.f(x)) cidx = constraint!(optimizer, err, map(x -> x.value, vars.variables)) return CI{VOV, MOIPredicate{F}}(cidx) @@ -116,405 +118,21 @@ struct Predicate{F <: Function} <: JuMP.AbstractVectorSet end JuMP.moi_set(set::Predicate, dim::Int) = MOIPredicate(set.f, dim) -""" - MOIAllDifferent <: MOI.AbstractVectorSet +## SECTION - Test Items +@testitem "Error and Predicate" begin + using CBLS + using JuMP -DOCSTRING -""" -struct MOIAllDifferent <: MOI.AbstractVectorSet - dimension::Int - - MOIAllDifferent(dim = 0) = new(dim) -end -MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIAllDifferent}) = true -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOIAllDifferent) - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:all_different])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIAllDifferent}(cidx) -end -Base.copy(set::MOIAllDifferent) = MOIAllDifferent(copy(set.dimension)) - -""" -Global constraint ensuring that all the values of a given configuration are unique. - -```julia -@constraint(model, X in AllDifferent()) -``` -""" -struct AllDifferent <: JuMP.AbstractVectorSet end -JuMP.moi_set(::AllDifferent, dim::Int) = MOIAllDifferent(dim) + model = Model(CBLS.Optimizer) -""" - MOIAllEqual <: MOI.AbstractVectorSet + @variable(model, 1≤X[1:4]≤4, Int) + @variable(model, 1≤Y[1:4]≤4, Int) -DOCSTRING -""" -struct MOIAllEqual <: MOI.AbstractVectorSet - dimension::Int + @constraint(model, X in Error(x -> x[1] + x[2] + x[3] + x[4] == 10)) + @constraint(model, Y in Predicate(x -> x[1] + x[2] + x[3] + x[4] == 10)) - MOIAllEqual(dim = 0) = new(dim) + optimize!(model) + @info "Error and Predicate" value.(X) value.(Y) + termination_status(model) + @info solution_summary(model) end -MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIAllEqual}) = true -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOIAllEqual) - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:all_equal])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIAllEqual}(cidx) -end - -Base.copy(set::MOIAllEqual) = MOIAllEqual(copy(set.dimension)) - -""" -Global constraint ensuring that all the values of `X` are all equal. - -```julia -@constraint(model, X in AllEqual()) -``` -""" -struct AllEqual <: JuMP.AbstractVectorSet end -JuMP.moi_set(::AllEqual, dim::Int) = MOIAllEqual(dim) - -""" - MOIEq <: MOI.AbstractVectorSet - -DOCSTRING -""" -struct MOIEq <: MOI.AbstractVectorSet - dimension::Int - - MOIEq(dim = 0) = new(dim) -end -MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIEq}) = true -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOIEq) - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:eq])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIEq}(cidx) -end - -Base.copy(set::MOIEq) = MOIEq(copy(set.dimension)) - -""" -Equality between two variables. - -```julia -@constraint(model, X in Eq()) -``` -""" -struct Eq <: JuMP.AbstractVectorSet end -JuMP.moi_set(::Eq, dim::Int) = MOIEq(dim) - -""" - MOIAlwaysTrue <: MOI.AbstractVectorSet - -DOCSTRING -""" -struct MOIAlwaysTrue <: MOI.AbstractVectorSet - dimension::Int - - MOIAlwaysTrue(dim = 0) = new(dim) -end -MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIAlwaysTrue}) = true -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOIAlwaysTrue) - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f(USUAL_CONSTRAINTS[:always_true])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIAlwaysTrue}(cidx) -end - -Base.copy(set::MOIAlwaysTrue) = MOIAlwaysTrue(copy(set.dimension)) - -""" -Always return `true`. Mainly used for testing purpose. - -```julia -@constraint(model, X in AlwaysTrue()) -``` -""" -struct AlwaysTrue <: JuMP.AbstractVectorSet end -JuMP.moi_set(::AlwaysTrue, dim::Int) = MOIAlwaysTrue(dim) - -""" - MOIOrdered <: MOI.AbstractVectorSet - -DOCSTRING -""" -struct MOIOrdered <: MOI.AbstractVectorSet - dimension::Int - - MOIOrdered(dim = 0) = new(dim) -end -MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIOrdered}) = true -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOIOrdered) - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:ordered])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIOrdered}(cidx) -end - -Base.copy(set::MOIOrdered) = MOIOrdered(copy(set.dimension)) - -""" -Global constraint ensuring that all the values of `x` are ordered. - -```julia -@constraint(model, X in Ordered()) -``` -""" -struct Ordered <: JuMP.AbstractVectorSet end -JuMP.moi_set(::Ordered, dim::Int) = MOIOrdered(dim) - - -""" - MOIDistDifferent <: MOI.AbstractVectorSet - -DOCSTRING -""" -struct MOIDistDifferent <: MOI.AbstractVectorSet - dimension::Int - - MOIDistDifferent(dim = 4) = new(dim) -end -MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIDistDifferent}) = true -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOIDistDifferent) - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:dist_different])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIDistDifferent}(cidx) -end -Base.copy(set::MOIDistDifferent) = MOIDistDifferent(copy(set.dimension)) - -""" -Local constraint ensuring that, given a vector `X` of size 4, `|X[1] - X[2]| ≠ |X[3] - X[4]|)`. - -```julia -@constraint(model, X in DistDifferent()) -``` -""" -struct DistDifferent <: JuMP.AbstractVectorSet end -JuMP.moi_set(::DistDifferent, dim::Int) = MOIDistDifferent(dim) - -""" - MOIAllEqualParam{T <: Number} <: MOI.AbstractVectorSet - -DOCSTRING - -# Arguments: -- `param::T`: DESCRIPTION -- `dimension::Int`: DESCRIPTION -- `MOIAllEqualParam(param, dim = 0) = begin - #= none:5 =# - new{typeof(param)}(param, dim) - end`: DESCRIPTION -""" -struct MOIAllEqualParam{T <: Number} <: MOI.AbstractVectorSet - param::T - dimension::Int - - MOIAllEqualParam(param, dim = 0) = new{typeof(param)}(param, dim) -end -function MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIAllEqualParam{T}} -) where {T <: Number} - return true -end -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIAllEqualParam{T} -) where T <: Number - # max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:all_equal_param])(x; param=param, dom_size=dom_size - ) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIAllEqualParam{T}}(cidx) -end - -Base.copy(set::MOIAllEqualParam) = MOIAllEqualParam(copy(set.param), copy(set.dimension)) - -""" -Global constraint ensuring that all the values of `X` are all equal to a given parameter `param`. - -```julia -@constraint(model, X in AllEqualParam(param)) -``` -""" -struct AllEqualParam{T <: Number} <: JuMP.AbstractVectorSet - param::T -end -JuMP.moi_set(set::AllEqualParam, dim::Int) = MOIAllEqualParam(set.param, dim) - -""" - MOISumEqualParam{T <: Number} <: MOI.AbstractVectorSet - -DOCSTRING - -# Arguments: -- `param::T`: DESCRIPTION -- `dimension::Int`: DESCRIPTION -- `MOISumEqualParam(param, dim = 0) = begin - #= none:5 =# - new{typeof(param)}(param, dim) - end`: DESCRIPTION -""" -struct MOISumEqualParam{T <: Number} <: MOI.AbstractVectorSet - param::T - dimension::Int - - MOISumEqualParam(param, dim = 0) = new{typeof(param)}(param, dim) -end -function MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOISumEqualParam{T}} -) where {T <: Number} - return true -end -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOISumEqualParam{T} -) where {T <: Number} - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:sum_equal_param])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOISumEqualParam{T}}(cidx) -end - -Base.copy(set::MOISumEqualParam) = MOISumEqualParam(copy(set.param), -copy(set.dimension)) - -""" -Global constraint ensuring that the sum of the values of `X` is equal to a given parameter `param`. - -```julia -@constraint(model, X in SumEqualParam(param)) -``` -""" -struct SumEqualParam{T <: Number} <: JuMP.AbstractVectorSet - param::T -end -JuMP.moi_set(set::SumEqualParam, dim::Int) = MOISumEqualParam(set.param, dim) - -""" - MOILessThanParam{T <: Number} <: MOI.AbstractVectorSet - -DOCSTRING - -# Arguments: -- `param::T`: DESCRIPTION -- `dimension::Int`: DESCRIPTION -- `MOILessThanParam(param, dim = 0) = begin - #= none:5 =# - new{typeof(param)}(param, dim) - end`: DESCRIPTION -""" -struct MOILessThanParam{T <: Number} <: MOI.AbstractVectorSet - param::T - dimension::Int - - MOILessThanParam(param, dim = 0) = new{typeof(param)}(param, dim) -end -function MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOILessThanParam{T}} -) where {T <: Number} - return true -end -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOILessThanParam{T} -) where {T <: Number} - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:less_than_param])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOILessThanParam{T}}(cidx) -end - -Base.copy(set::MOILessThanParam) = MOILessThanParam(copy(set.param), -copy(set.dimension)) - -""" -Constraint ensuring that the value of `x` is less than a given parameter `param`. - -```julia -@constraint(model, x in LessThanParam(param)) -``` -""" -struct LessThanParam{T <: Number} <: JuMP.AbstractVectorSet - param::T -end -JuMP.moi_set(set::LessThanParam, dim::Int) = MOILessThanParam(set.param, dim) - -""" - MOIMinusEqualParam{T <: Number} <: MOI.AbstractVectorSet - -DOCSTRING - -# Arguments: -- `param::T`: DESCRIPTION -- `dimension::Int`: DESCRIPTION -- `MOIMinusEqualParam(param, dim = 0) = begin - #= none:5 =# - new{typeof(param)}(param, dim) - end`: DESCRIPTION -""" -struct MOIMinusEqualParam{T <: Number} <: MOI.AbstractVectorSet - param::T - dimension::Int - - MOIMinusEqualParam(param, dim = 0) = new{typeof(param)}(param, dim) -end -function MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOIMinusEqualParam{T}} -) where {T <: Number} - return true -end -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIMinusEqualParam{T} -) where {T <: Number} - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:minus_equal_param])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOIMinusEqualParam{T}}(cidx) -end - -Base.copy(set::MOIMinusEqualParam) = MOIMinusEqualParam(copy(set.param), -copy(set.dimension)) - -""" -Constraint ensuring that the value of `x` is less than a given parameter `param`. - -```julia -@constraint(model, x in MinusEqualParam(param)) -``` -""" -struct MinusEqualParam{T <: Number} <: JuMP.AbstractVectorSet - param::T -end -JuMP.moi_set(set::MinusEqualParam, dim::Int) = MOIMinusEqualParam(set.param, dim) - - -""" - MOISequentialTasks <: MOI.AbstractVectorSet - -DOCSTRING -""" -struct MOISequentialTasks <: MOI.AbstractVectorSet - dimension::Int - - MOISequentialTasks(dim = 4) = new(dim) -end -MOI.supports_constraint(::Optimizer, ::Type{VOV}, ::Type{MOISequentialTasks}) = true -function MOI.add_constraint(optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOISequentialTasks) - #max_dom_size = max_domains_size(optimizer, map(x -> x.value, vars.variables)) - e = (x; kargs...) -> error_f( - USUAL_CONSTRAINTS[:sequential_tasks])(x; kargs...) - cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) - return CI{VOV, MOISequentialTasks}(cidx) -end -Base.copy(set::MOISequentialTasks) = MOISequentialTasks(copy(set.dimension)) - -""" -Local constraint ensuring that, given a vector `X` of size 4, `|X[1] - X[2]| ≠ |X[3] - X[4]|)`. - -```julia -@constraint(model, X in SequentialTasks()) -``` -""" -struct SequentialTasks <: JuMP.AbstractVectorSet end -JuMP.moi_set(::SequentialTasks, dim::Int) = MOISequentialTasks(dim) diff --git a/src/constraints/all_different.jl b/src/constraints/all_different.jl new file mode 100644 index 0000000..576a074 --- /dev/null +++ b/src/constraints/all_different.jl @@ -0,0 +1,65 @@ +""" + MOIAllDifferent <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIAllDifferent{T <: Number} <: MOI.AbstractVectorSet + vals::Vector{T} + dimension::Int + + MOIAllDifferent(vals, dim = 0) = new{eltype(vals)}(vals, dim) +end + +function MOI.supports_constraint( + ::Optimizer, ::Type{VOV}, ::Type{MOIAllDifferent{T}}) where {T <: Number} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIAllDifferent) + vals = isempty(set.vals) ? nothing : set.vals + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:vals => vals)) + return error_f(USUAL_CONSTRAINTS[:all_different])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIAllDifferent{eltype(set.vals)}}(cidx) +end + +Base.copy(set::MOIAllDifferent) = MOIAllDifferent(copy(set.vals), copy(set.dimension)) + +""" +Global constraint ensuring that all the values of a given configuration are unique. + +```julia +@constraint(model, X in AllDifferent()) +``` +""" +struct AllDifferent{T <: Number} <: JuMP.AbstractVectorSet + vals::Vector{T} + + AllDifferent(vals) = new{eltype(vals)}(vals) +end + +AllDifferent(; vals::Vector{T} = Vector{Number}()) where {T <: Number} = AllDifferent(vals) + +JuMP.moi_set(set::AllDifferent, dim::Int) = MOIAllDifferent(set.vals, dim) + +## SECTION - Test Items +@testitem "All Different" tags=[:usual, :constraints, :all_different] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:4]≤4, Int) + @variable(model, 0≤Y[1:4]≤2, Int) + + @constraint(model, X in AllDifferent()) + @constraint(model, Y in AllDifferent(; vals = [0])) + + optimize!(model) + @info "All Different" value.(X) value.(Y) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/all_equal.jl b/src/constraints/all_equal.jl new file mode 100644 index 0000000..d5417d1 --- /dev/null +++ b/src/constraints/all_equal.jl @@ -0,0 +1,86 @@ +""" + MOIAllEqual <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIAllEqual{F <: Function, T1 <: Number, T2 <: Union{Nothing, Number}} <: + MOI.AbstractVectorSet + op::F + pair_vars::Vector{T1} + val::T2 + dimension::Int + + function MOIAllEqual(op, pair_vars, val, dim = 0) + return new{typeof(op), eltype(pair_vars), typeof(val)}(op, pair_vars, val, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIAllEqual{F, T1, T2}}) where { + F <: Function, T1 <: Number, T2 <: Union{Nothing, Number}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIAllEqual) + function e(x; kwargs...) + new_kwargs = merge( + kwargs, Dict(:op => set.op, :pair_vars => set.pair_vars, :val => set.val)) + return error_f(USUAL_CONSTRAINTS[:all_equal])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIAllEqual{typeof(set.op), eltype(set.pair_vars), typeof(set.val)}}(cidx) +end + +function Base.copy(set::MOIAllEqual) + return MOIAllEqual( + copy(set.op), copy(set.pair_vars), copy(set.val), copy(set.dimension)) +end + +""" +Global constraint ensuring that all the values of `X` are all equal. + +```julia +@constraint(model, X in AllEqual()) +``` +""" +struct AllEqual{F <: Function, T1 <: Number, T2 <: Union{Nothing, Number}} <: + JuMP.AbstractVectorSet + op::F + pair_vars::Vector{T1} + val::T2 + + function AllEqual(op, pair_vars, val) + return new{typeof(op), eltype(pair_vars), typeof(val)}(op, pair_vars, val) + end +end + +function AllEqual(; op::F = +, pair_vars::Vector{T1} = Vector{Number}(), + val::T2 = nothing) where {F <: Function, T1 <: Number, T2 <: Union{Nothing, Number}} + return AllEqual(op, pair_vars, val) +end + +JuMP.moi_set(set::AllEqual, dim::Int) = MOIAllEqual(set.op, set.pair_vars, set.val, dim) + +@testitem "All Equal" tags=[:usual, :constraints, :all_equal] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 0≤X1[1:4]≤4, Int) + @variable(model, 0≤X2[1:4]≤4, Int) + @variable(model, 0≤X3[1:4]≤4, Int) + @variable(model, 0≤X4[1:4]≤4, Int) + + @constraint(model, X1 in AllEqual()) + @constraint(model, X2 in AllEqual(; pair_vars = [0, 1, 2, 3])) + @constraint(model, X3 in AllEqual(; op = /, val = 1, pair_vars = [1, 2, 3, 4])) + @constraint(model, X4 in AllEqual(; op = *, val = 1, pair_vars = [1, 2, 3, 4])) + + optimize!(model) + @info "All Equal" value.(X1) value.(X2) value.(X3) value.(X4) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/cardinality.jl b/src/constraints/cardinality.jl new file mode 100644 index 0000000..c2abb4e --- /dev/null +++ b/src/constraints/cardinality.jl @@ -0,0 +1,98 @@ +""" + MOICardinality <: MOI.AbstractVectorSet + +DOCSTRING +""" + +struct MOICardinality{T <: Number, V <: VecOrMat{T}} <: MOI.AbstractVectorSet + bool::Bool + vals::V + dimension::Int + + MOICardinality(bool, vals, dim = 0) = new{eltype(vals), typeof(vals)}(bool, vals, dim) +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOICardinality{T, V}}) where {T <: Number, V <: VecOrMat{T}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOICardinality) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:bool => set.bool, :vals => set.vals)) + return error_f(USUAL_CONSTRAINTS[:cardinality])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOICardinality{eltype(set.vals), typeof(set.vals)}}(cidx) +end + +function Base.copy(set::MOICardinality) + return MOICardinality(copy(set.bool), copy(set.vals), copy(set.dimension)) +end + +""" +Global constraint ensuring that the number of occurrences of each value in `X` is equal to the corresponding value in `vals`. + +```julia +@constraint(model, X in Cardinality(vals)) +``` +""" + +struct Cardinality{T <: Number, V <: VecOrMat{T}} <: JuMP.AbstractVectorSet + bool::Bool + vals::V + + Cardinality(bool, vals) = new{eltype(vals), typeof(vals)}(bool, vals) +end + +function Cardinality(; vals::VecOrMat{T}, bool::Bool = false) where {T <: Number} + return Cardinality(bool, vals) +end + +JuMP.moi_set(set::Cardinality, dim::Int) = MOICardinality(set.bool, set.vals, dim) + +struct CardinalityOpen{T <: Number, V <: VecOrMat{T}} <: JuMP.AbstractVectorSet + vals::V + + CardinalityOpen(vals) = new{eltype(vals), typeof(vals)}(vals) +end + +function CardinalityOpen(; vals::VecOrMat{T}) where {T <: Number} + return CardinalityOpen(vals) +end + +JuMP.moi_set(set::CardinalityOpen, dim::Int) = MOICardinality(false, set.vals, dim) + +struct CardinalityClosed{T <: Number, V <: VecOrMat{T}} <: JuMP.AbstractVectorSet + vals::V + + CardinalityClosed(vals) = new{eltype(vals), typeof(vals)}(vals) +end + +function CardinalityClosed(; vals::VecOrMat{T}) where {T <: Number} + return CardinalityClosed(vals) +end + +JuMP.moi_set(set::CardinalityClosed, dim::Int) = MOICardinality(true, set.vals, dim) + +@testitem "Cardinality" tags=[:usual, :constraints, :cardinality] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:4]≤10, Int) + @variable(model, 1≤Y[1:4]≤10, Int) + @variable(model, 1≤Z[1:4]≤10, Int) + + @constraint(model, X in Cardinality(; vals = [2 0 1; 5 1 3; 10 2 3])) + @constraint(model, Y in CardinalityOpen(; vals = [2 0 1; 5 1 3; 10 2 3])) + @constraint(model, Z in CardinalityClosed(; vals = [2 0 1; 5 1 3; 10 2 3])) + + optimize!(model) + @info "Cardinality" value.(X) value.(Y) value.(Z) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/channel.jl b/src/constraints/channel.jl new file mode 100644 index 0000000..012c747 --- /dev/null +++ b/src/constraints/channel.jl @@ -0,0 +1,80 @@ +""" + MOIChannel <: MOI.AbstractVectorSet + +DOCSTRING +""" + +struct MOIChannel{D <: Integer, I <: Integer} <: MOI.AbstractVectorSet + dim::D + id::I + dimension::Int + + function MOIChannel(dim, id, dim_moi = 0) + return new{typeof(dim), typeof(id)}(dim, id, dim_moi) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIChannel{D, I}}) where {D <: Integer, I <: Integer} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIChannel) + id = iszero(set.id) ? nothing : set.id + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:dim => set.dim, :id => id)) + return error_f(USUAL_CONSTRAINTS[:channel])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIChannel{typeof(set.dim), typeof(set.id)}}(cidx) +end + +function Base.copy(set::MOIChannel) + return MOIChannel(copy(set.dim), copy(set.id), copy(set.dimension)) +end + +""" +Global constraint ensuring that the values of `X` are a channel. + +```julia +@constraint(model, X in Channel()) +``` +""" + +struct Channel{D <: Integer, I <: Integer} <: JuMP.AbstractVectorSet + dim::D + id::I + + function Channel(dim, id) + return new{typeof(dim), typeof(id)}(dim, id) + end +end + +function Channel(; dim::D = 1, id::I = 0) where {D <: Integer, I <: Integer} + return Channel(dim, id) +end + +JuMP.moi_set(set::Channel, dim_moi::Int) = MOIChannel(set.dim, set.id, dim_moi) + +@testitem "Channel" tags=[:usual, :constraints, :channel] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:4]≤4, Int) + @variable(model, 1≤Y[1:10]≤5, Int) + @variable(model, 0≤Z[1:4]≤1, Int) + + @constraint(model, X in CBLS.Channel()) + @constraint(model, Y in CBLS.Channel(; dim = 2)) + @constraint(model, Z in CBLS.Channel(; id = 3)) + + optimize!(model) + + @info "Channel" value.(X) value.(Y) value.(Z) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/circuit.jl b/src/constraints/circuit.jl new file mode 100644 index 0000000..760aba3 --- /dev/null +++ b/src/constraints/circuit.jl @@ -0,0 +1,80 @@ +""" + MOICircuit <: MOI.AbstractVectorSet + +DOCSTRING +""" + +struct MOICircuit{F <: Function, T <: Number} <: MOI.AbstractVectorSet + op::F + val::T + dimension::Int + + function MOICircuit(op, val, dim_moi = 0) + return new{typeof(op), typeof(val)}(op, val, dim_moi) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOICircuit{F, T}}) where {F <: Function, T <: Number} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOICircuit) + val = iszero(set.val) ? length(vars.variables) : set.val + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:op => set.op, :val => val)) + return error_f(USUAL_CONSTRAINTS[:circuit])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOICircuit{typeof(set.op), typeof(set.val)}}(cidx) +end + +function Base.copy(set::MOICircuit) + return MOICircuit(copy(set.op), copy(set.val), copy(set.dimension)) +end + +""" +Global constraint ensuring that the values of `X` are a circuit. + +```julia +@constraint(model, X in Circuit()) +``` +""" + +struct Circuit{F <: Function, T <: Number} <: JuMP.AbstractVectorSet + op::F + val::T + + function Circuit(op, val) + return new{typeof(op), typeof(val)}(op, val) + end +end + +function Circuit(; op::F = ≥, val::T = 0) where {F <: Function, T <: Number} + return Circuit(op, val) +end + +JuMP.moi_set(set::Circuit, dim_moi::Int) = MOICircuit(set.op, set.val, dim_moi) + +@testitem "Circuit" tags=[:usual, :constraints, :circuit] begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:4]≤4, Int) + @variable(model, 1≤Y[1:4]≤4, Int) + @variable(model, 1≤Z[1:4]≤4, Int) + + @constraint(model, X in Circuit()) + @constraint(model, Y in Circuit(op = ==, val = 3)) + @constraint(model, Z in Circuit(op = >, val = 0)) + + optimize!(model) + + @info "Circuit" value.(X) value.(Y) value.(Z) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/count.jl b/src/constraints/count.jl new file mode 100644 index 0000000..b0c80d8 --- /dev/null +++ b/src/constraints/count.jl @@ -0,0 +1,173 @@ +""" + MOICount <: MOI.AbstractVectorSet + +DOCSTRING +""" + +struct MOICount{F <: Function, T1 <: Number, T2 <: Number} <: MOI.AbstractVectorSet + op::F + val::T1 + vals::Vector{T2} + dimension::Int + + function MOICount(op, val, vals, dim = 0) + new{typeof(op), typeof(val), eltype(vals)}(op, val, vals, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOICount{F, T1, T2}}) where {F <: Function, T1 <: Number, T2 <: Number} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOICount) + s = if set.op == == + :exactly + elseif set.op == ≥ + :at_least + elseif set.op == ≤ + :at_most + else + :count + end + function e(x; kwargs...) + d = Dict(:vals => set.vals, :val => set.val) + s == :count && push!(d, :op => set.op) + new_kwargs = merge(kwargs, d) + return error_f(USUAL_CONSTRAINTS[s])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOICount{typeof(set.op), typeof(set.val), eltype(set.vals)}}(cidx) +end + +function Base.copy(set::MOICount) + return MOICount( + copy(set.op), copy(set.val), copy(set.vals), copy(set.dimension)) +end + +""" +Global constraint ensuring that the number of occurrences of `val` in `X` is equal to `count`. + +```julia +@constraint(model, X in Count(count, val, vals)) +``` +""" +struct Count{F <: Function, T1 <: Number, T2 <: Number} <: JuMP.AbstractVectorSet + op::F + val::T1 + vals::Vector{T2} + + function Count(op, val, vals) + return new{typeof(op), typeof(val), eltype(vals)}(op, val, vals) + end +end + +function Count(; + op::F, val::T1, vals::Vector{T2}) where { + F <: Function, T1 <: Number, T2 <: Number} + return Count(op, val, vals) +end + +function JuMP.moi_set(set::Count, dim::Int) + return MOICount(set.op, set.val, set.vals, dim) +end + +""" +Constraint ensuring that the number of occurrences of the values in `vals` in `x` is at least `val`. + +```julia +@constraint(model, X in AtLeast(val, vals)) +``` +""" +struct AtLeast{T1 <: Number, T2 <: Number} <: JuMP.AbstractVectorSet + val::T1 + vals::Vector{T2} + + function AtLeast(val, vals) + return new{typeof(val), eltype(vals)}(val, vals) + end +end + +function AtLeast(; + val::T1, vals::Vector{T2}) where {T1 <: Number, T2 <: Number} + return AtLeast(val, vals) +end + +function JuMP.moi_set(set::AtLeast, dim::Int) + return MOICount(≥, set.val, set.vals, dim) +end + +""" +Constraint ensuring that the number of occurrences of the values in `vals` in `x` is at most `val`. + +```julia +@constraint(model, X in AtMost(val, vals)) +``` +""" +struct AtMost{T1 <: Number, T2 <: Number} <: JuMP.AbstractVectorSet + val::T1 + vals::Vector{T2} + + function AtMost(val, vals) + return new{typeof(val), eltype(vals)}(val, vals) + end +end + +function AtMost(; + val::T1, vals::Vector{T2}) where {T1 <: Number, T2 <: Number} + return AtMost(val, vals) +end + +function JuMP.moi_set(set::AtMost, dim::Int) + return MOICount(≤, set.val, set.vals, dim) +end + +""" +Constraint ensuring that the number of occurrences of the values in `vals` in `x` is exactly `val`. + +```julia +@constraint(model, X in Exactly(val, vals)) +``` +""" +struct Exactly{T1 <: Number, T2 <: Number} <: JuMP.AbstractVectorSet + val::T1 + vals::Vector{T2} + + function Exactly(val, vals) + return new{typeof(val), eltype(vals)}(val, vals) + end +end + +function Exactly(; + val::T1, vals::Vector{T2}) where {T1 <: Number, T2 <: Number} + return Exactly(val, vals) +end + +function JuMP.moi_set(set::Exactly, dim::Int) + return MOICount(==, set.val, set.vals, dim) +end + +## SECTION - Test Items +@testitem "Count" tags=[:usual, :constraints, :count] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:4]≤4, Int) + @variable(model, 1≤X_at_least[1:4]≤4, Int) + @variable(model, 1≤X_at_most[1:4]≤4, Int) + @variable(model, 1≤X_exactly[1:4]≤4, Int) + + @constraint(model, X in Count(vals = [1, 2, 3, 4], op = ≥, val = 2)) + @constraint(model, X_at_least in AtLeast(vals = [1, 2, 3, 4], val = 2)) + @constraint(model, X_at_most in AtMost(vals = [1, 2], val = 1)) + @constraint(model, X_exactly in Exactly(vals = [1, 2], val = 2)) + + optimize!(model) + @info "Count" value.(X) value.(X_at_least) value.(X_at_most) value.(X_exactly) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/cumulative.jl b/src/constraints/cumulative.jl new file mode 100644 index 0000000..62425c7 --- /dev/null +++ b/src/constraints/cumulative.jl @@ -0,0 +1,94 @@ +""" + MOICumulative{F <: Function, T1 <: Number, T2 <: Number} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOICumulative{F <: Function, T1 <: Number, T2 <: Number, V <: VecOrMat{T1}} <: + MOI.AbstractVectorSet + op::F + pair_vars::V + val::T2 + dimension::Int + + function MOICumulative(op, pair_vars, val, dim = 0) + return new{typeof(op), eltype(pair_vars), typeof(val), typeof(pair_vars)}( + op, pair_vars, val, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOICumulative{F, T1, T2, V}}) where { + F <: Function, T1 <: Number, T2 <: Number, V <: VecOrMat{T1}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOICumulative) + function e(x; kwargs...) + d = Dict(:op => set.op, :val => set.val) + !isempty(set.pair_vars) && push!(d, :pair_vars => set.pair_vars) + new_kwargs = merge(kwargs, d) + return error_f(USUAL_CONSTRAINTS[:cumulative])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, + MOICumulative{ + typeof(set.op), eltype(set.pair_vars), typeof(set.val), typeof(set.pair_vars)}}(cidx) +end + +function Base.copy(set::MOICumulative) + return MOICumulative( + copy(set.op), copy(set.pair_vars), copy(set.val), copy(set.dimension)) +end + +""" +Global constraint ensuring that the cumulative sum of the heights of the tasks is less than or equal to `val`. + +```julia +@constraint(model, X in Cumulative(; pair_vars, op, val)) +``` +""" +struct Cumulative{F <: Function, T1 <: Number, T2 <: Number, V <: VecOrMat{T1}} <: + JuMP.AbstractVectorSet + op::F + pair_vars::V + val::T2 + + function Cumulative(op, pair_vars, val) + return new{typeof(op), eltype(pair_vars), typeof(val), typeof(pair_vars)}( + op, pair_vars, val) + end +end + +function Cumulative(; op::F = ≤, pair_vars::V = Vector{Number}(), + val::T2) where {F <: Function, T1 <: Number, T2 <: Number, V <: VecOrMat{T1}} + return Cumulative(op, pair_vars, val) +end + +function JuMP.moi_set(set::Cumulative, dim::Int) + return MOICumulative(set.op, set.pair_vars, set.val, dim) +end + +## SECTION - Test Items +@testitem "Cumulative" tags=[:usual, :constraints, :cumulative] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + @variable(model, 1≤Y[1:5]≤5, Int) + @variable(model, 1≤Z[1:5]≤5, Int) + + @constraint(model, X in Cumulative(; val = 1)) + @constraint(model, + Y in Cumulative(; pair_vars = [3 2 5 4 2; 1 2 1 1 3], op = ≤, val = 5)) + @constraint(model, + Z in Cumulative(; pair_vars = [3 2 5 4 2; 1 2 1 1 3], op = <, val = 5)) + + optimize!(model) + @info "Cumulative" value.(X) value.(Y) value.(Z) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/element.jl b/src/constraints/element.jl new file mode 100644 index 0000000..ce2e177 --- /dev/null +++ b/src/constraints/element.jl @@ -0,0 +1,82 @@ +""" + MOIElement{I <: Integer, F <: Function, T <: Union{Nothing, Number}} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIElement{I <: Integer, F <: Function, T <: Union{Nothing, Number}} <: + MOI.AbstractVectorSet + id::I + op::F + val::T + dimension::Int + + function MOIElement(id, op, val, dim = 0) + return new{typeof(id), typeof(op), typeof(val)}(id, op, val, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIElement{I, F, T}}) where { + I <: Integer, F <: Function, T <: Union{Nothing, Number}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIElement) + id = iszero(set.id) ? nothing : set.id + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:id => id, :op => set.op, :val => set.val)) + return error_f(USUAL_CONSTRAINTS[:element])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIElement{typeof(set.id), typeof(set.op), typeof(set.val)}}(cidx) +end + +function Base.copy(set::MOIElement) + return MOIElement(copy(set.id), copy(set.op), copy(set.val), copy(set.dimension)) +end + +""" +Global constraint ensuring that the value of `X` at index `id` is equal to `val`. + +```julia +@constraint(model, X in Element(; id = nothing, op = ==, val = 0)) +``` +""" +struct Element{I <: Integer, F <: Function, T <: Union{Nothing, Number}} <: + JuMP.AbstractVectorSet + id::I + op::F + val::T + + function Element(; id::I = 0, op::F = ==, + val::T = 0) where {I <: Integer, F <: Function, T <: Union{Nothing, Number}} + return new{typeof(id), typeof(op), typeof(val)}(id, op, val) + end +end + +function JuMP.moi_set(set::Element, dim_moi::Int) + return MOIElement(set.id, set.op, set.val, dim_moi) +end + +## SECTION - Test Items +@testitem "Element" tags=[:usual, :constraints, :element] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + @variable(model, 1≤Y[1:5]≤5, Int) + @variable(model, 0≤Z[1:5]≤5, Int) + + @constraint(model, X in Element()) + @constraint(model, Y in Element(; id = 1, val = 1)) + @constraint(model, Z in Element(; id = 2, val = 2)) + + optimize!(model) + @info "Element" value.(X) value.(Y) value.(Z) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/extension.jl b/src/constraints/extension.jl new file mode 100644 index 0000000..60e03bd --- /dev/null +++ b/src/constraints/extension.jl @@ -0,0 +1,201 @@ +""" + MOIExtension{T <: Number, V <: Union{Vector{Vector{T}}, Tuple{Vector{T}, Vector{T}}}} <: MOI.AbstractVectorSet + + DOCSTRING +""" +struct MOIExtension{ + T <: Number, V <: Union{Vector{Vector{T}}, Tuple{Vector{T}, Vector{T}}}} <: + MOI.AbstractVectorSet + pair_vars::V + dimension::Int + + function MOIExtension(pair_vars, dim = 0) + ET = eltype(first(typeof(pair_vars) <: Tuple ? first(pair_vars) : pair_vars)) + return new{ET, typeof(pair_vars)}(pair_vars, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIExtension{T, V}}) where { + T <: Number, V <: Union{Vector{Vector{T}}, Tuple{Vector{T}, Vector{T}}}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIExtension) + pair_vars = set.pair_vars + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:pair_vars => set.pair_vars)) + return error_f(USUAL_CONSTRAINTS[:extension])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + ET = eltype(first(typeof(pair_vars) <: Tuple ? first(pair_vars) : pair_vars)) + return CI{VOV, MOIExtension{ET, typeof(pair_vars)}}(cidx) +end + +function Base.copy(set::MOIExtension) + return MOIExtension(copy(set.pair_vars), copy(set.dimension)) +end + +""" +Global constraint enforcing that the tuple `x` matches a configuration within the supports set `pair_vars[1]` or does not match any configuration within the conflicts set `pair_vars[2]`. It embodies the logic: `x ∈ pair_vars[1] || x ∉ pair_vars[2]`, providing a comprehensive way to define valid (supported) and invalid (conflicted) tuples for constraint satisfaction problems. This constraint is versatile, allowing for the explicit delineation of both acceptable and unacceptable configurations. +""" +struct Extension{T <: Number, V <: Union{Vector{Vector{T}}, Tuple{Vector{T}, Vector{T}}}} <: + JuMP.AbstractVectorSet + pair_vars::V + + function Extension(pair_vars) + ET = eltype(first(typeof(pair_vars) <: Tuple ? first(pair_vars) : pair_vars)) + return new{ET, typeof(pair_vars)}(pair_vars) + end +end + +function Extension(; + pair_vars::V) where { + T <: Number, V <: Union{Vector{Vector{T}}, Tuple{Vector{T}, Vector{T}}}} + return Extension(pair_vars) +end + +function JuMP.moi_set(set::Extension, dim::Int) + return MOIExtension(set.pair_vars, dim) +end + +""" + MOISupports{T <: Number, V <: Vector{Vector{T}}} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOISupports{T <: Number, V <: Vector{Vector{T}}} <: MOI.AbstractVectorSet + pair_vars::V + dimension::Int + + function MOISupports(pair_vars, dim = 0) + ET = eltype(first(pair_vars)) + return new{ET, typeof(pair_vars)}(pair_vars, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOISupports{T, V}}) where { + T <: Number, V <: Vector{Vector{T}}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOISupports) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:pair_vars => set.pair_vars)) + return error_f(USUAL_CONSTRAINTS[:supports])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + ET = eltype(first(set.pair_vars)) + return CI{VOV, MOISupports{ET, typeof(set.pair_vars)}}(cidx) +end + +function Base.copy(set::MOISupports) + return MOISupports(copy(set.pair_vars), copy(set.dimension)) +end + +""" +Global constraint ensuring that the tuple `x` matches a configuration listed within the support set `pair_vars`. This constraint is derived from the extension model, specifying that `x` must be one of the explicitly defined supported configurations: `x ∈ pair_vars`. It is utilized to directly declare the tuples that are valid and should be included in the solution space. + +```julia +@constraint(model, X in Supports(; pair_vars)) +``` +""" +struct Supports{T <: Number, V <: Vector{Vector{T}}} <: JuMP.AbstractVectorSet + pair_vars::V + + function Supports(; pair_vars) + ET = eltype(first(pair_vars)) + return new{ET, typeof(pair_vars)}(pair_vars) + end +end + +function JuMP.moi_set(set::Supports, dim::Int) + return MOISupports(set.pair_vars, dim) +end + +""" + MOIConflicts{T <: Number, V <: Vector{Vector{T}}} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIConflicts{T <: Number, V <: Vector{Vector{T}}} <: + MOI.AbstractVectorSet + pair_vars::V + dimension::Int + + function MOIConflicts(pair_vars, dim = 0) + ET = eltype(first(pair_vars)) + return new{ET, typeof(pair_vars)}(pair_vars, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIConflicts{T, V}}) where { + T <: Number, V <: Vector{Vector{T}}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIConflicts) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:pair_vars => set.pair_vars)) + return error_f(USUAL_CONSTRAINTS[:conflicts])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + ET = eltype(first(set.pair_vars)) + return CI{VOV, MOIConflicts{ET, typeof(set.pair_vars)}}(cidx) +end + +function Base.copy(set::MOIConflicts) + return MOIConflicts(copy(set.pair_vars), copy(set.dimension)) +end + +""" +Global constraint ensuring that the tuple `x` does not match any configuration listed within the conflict set `pair_vars`. This constraint, originating from the extension model, stipulates that `x` must avoid all configurations defined as conflicts: `x ∉ pair_vars`. It is useful for specifying tuples that are explicitly forbidden and should be excluded from the solution space. + +```julia +@constraint(model, X in Conflicts(; pair_vars)) +``` +""" +struct Conflicts{T <: Number, V <: Vector{Vector{T}}} <: JuMP.AbstractVectorSet + pair_vars::V + + function Conflicts(; pair_vars) + ET = eltype(first(pair_vars)) + return new{ET, typeof(pair_vars)}(pair_vars) + end +end + +function JuMP.moi_set(set::Conflicts, dim::Int) + return MOIConflicts(set.pair_vars, dim) +end + +## SECTION - Test Items +@testitem "Extension" tags=[:usual, :constraints, :extension] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + @variable(model, 1≤Y[1:5]≤5, Int) + @variable(model, 1≤X_Supports[1:5]≤5, Int) + @variable(model, 1≤X_Conflicts[1:5]≤5, Int) + + @constraint(model, X in Extension(; pair_vars = [[1, 2, 3, 4, 5]])) + @constraint(model, Y in Extension(; pair_vars = [[1, 2, 1, 4, 5], [1, 2, 3, 5, 5]])) + @constraint(model, X_Supports in Supports(; pair_vars = [[1, 2, 3, 4, 5]])) + @constraint(model, + X_Conflicts in Conflicts(; pair_vars = [[1, 2, 1, 4, 5], [1, 2, 3, 5, 5]])) + + optimize!(model) + @info "Extension" value.(X) value.(Y) value.(X_Supports) value.(X_Conflicts) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/instantiation.jl b/src/constraints/instantiation.jl new file mode 100644 index 0000000..ec319f3 --- /dev/null +++ b/src/constraints/instantiation.jl @@ -0,0 +1,69 @@ +""" + MOIInstantiation{T <: Number, V <: Vector{T}} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIInstantiation{T <: Number, V <: Vector{T}} <: + MOI.AbstractVectorSet + pair_vars::V + dimension::Int + + function MOIInstantiation(pair_vars, dim = 0) + return new{eltype(pair_vars), typeof(pair_vars)}(pair_vars, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIInstantiation{T, V}}) where { + T <: Number, V <: Vector{T}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIInstantiation) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:pair_vars => set.pair_vars)) + return error_f(USUAL_CONSTRAINTS[:instantiation])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIInstantiation{eltype(set.pair_vars), typeof(set.pair_vars)}}(cidx) +end + +function Base.copy(set::MOIInstantiation) + return MOIInstantiation(copy(set.pair_vars), copy(set.dimension)) +end + +""" +The instantiation constraint is a global constraint used in constraint programming that ensures that a list of variables takes on a specific set of values in a specific order. +""" +struct Instantiation{T <: Number, V <: Vector{T}} <: JuMP.AbstractVectorSet + pair_vars::V + + function Instantiation(; pair_vars) + return new{eltype(pair_vars), typeof(pair_vars)}(pair_vars) + end +end + +function JuMP.moi_set(set::Instantiation, dim::Int) + return MOIInstantiation(set.pair_vars, dim) +end + +## SECTION - Test Items +@testitem "Instantiation" tags=[:usual, :constraints, :instantiation] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤6, Int) + @variable(model, 1≤Y[1:5]≤6, Int) + + @constraint(model, X in Instantiation(; pair_vars = [1, 2, 3, 4, 5])) + @constraint(model, Y in Instantiation(; pair_vars = [1, 2, 3, 4, 6])) + + optimize!(model) + @info "Instantiation" value.(X) value.(Y) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/intention.jl b/src/constraints/intention.jl new file mode 100644 index 0000000..65a5c44 --- /dev/null +++ b/src/constraints/intention.jl @@ -0,0 +1,59 @@ +# Intention constraints emcompass any generic constraint. DistDifferent is implemented as an example of an intensional constraint. + +""" + MOIDistDifferent <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIDistDifferent <: MOI.AbstractVectorSet + dimension::Int + + function MOIDistDifferent(dim = 4) + return new(dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIDistDifferent}) + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, ::MOIDistDifferent) + function e(x; kwargs...) + return error_f(USUAL_CONSTRAINTS[:dist_different])(x) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIDistDifferent}(cidx) +end + +function Base.copy(set::MOIDistDifferent) + return MOIDistDifferent(copy(set.dimension)) +end + +""" +A constraint ensuring that the distances between marks on the ruler are unique. Specifically, it checks that the distance between `x[1]` and `x[2]`, and the distance between `x[3]` and `x[4]`, are different. This constraint is fundamental in ensuring the validity of a Golomb ruler, where no two pairs of marks should have the same distance between them. +""" +struct DistDifferent <: JuMP.AbstractVectorSet end + +function JuMP.moi_set(::DistDifferent, dim::Int) + return MOIDistDifferent(dim) +end + +## SECTION - Test Items +@testitem "Dist different (intension)" tags=[:usual, :constraints, :intension] begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:4]≤6, Int) + + @constraint(model, X in DistDifferent()) + + optimize!(model) + @info "Dist different (intension)" value.(X) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/maximum.jl b/src/constraints/maximum.jl new file mode 100644 index 0000000..1219057 --- /dev/null +++ b/src/constraints/maximum.jl @@ -0,0 +1,74 @@ +""" + MOIMaximum {F <: Function, T <: Number} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIMaximum{F <: Function, T <: Number} <: MOI.AbstractVectorSet + op::F + val::T + dimension::Int + + function MOIMaximum(op, val, dim = 0) + return new{typeof(op), typeof(val)}(op, val, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIMaximum{F, T}}) where { + F <: Function, T <: Number} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIMaximum) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:op => set.op, :val => set.val)) + return error_f(USUAL_CONSTRAINTS[:maximum])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIMaximum{typeof(set.op), typeof(set.val)}}(cidx) +end + +function Base.copy(set::MOIMaximum) + return MOIMaximum(copy(set.op), copy(set.val), copy(set.dimension)) +end + +""" +Global constraint ensuring that the maximum value in the tuple `x` satisfies the condition `op(x) val`. This constraint is useful for specifying that the maximum value in the tuple must satisfy a certain condition. + +```julia +@constraint(model, X in Maximum(; op = ==, val)) +``` +""" +struct Maximum{F <: Function, T <: Number} <: JuMP.AbstractVectorSet + op::F + val::T + + function Maximum(op, val) + return new{typeof(op), typeof(val)}(op, val) + end +end + +Maximum(; op::F = ==, val::T) where {F <: Function, T <: Number} = Maximum(op, val) + +function JuMP.moi_set(set::Maximum, dim::Int) + return MOIMaximum(set.op, set.val, dim) +end + +## SECTION - Test Items +@testitem "Maximum" tags=[:usual, :constraints, :maximum] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + + @constraint(model, X in Maximum(; op = ==, val = 5)) + + optimize!(model) + @info "Maximum" value.(X) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/mdd.jl b/src/constraints/mdd.jl new file mode 100644 index 0000000..dce5826 --- /dev/null +++ b/src/constraints/mdd.jl @@ -0,0 +1,94 @@ +""" + MOIMultivaluedDecisionDiagram{L <: ConstraintCommons.AbstractMultivaluedDecisionDiagram} <: AbstractVectorSet + +DOCSTRING +""" +struct MOIMultivaluedDecisionDiagram{L <: + ConstraintCommons.AbstractMultivaluedDecisionDiagram} <: + MOI.AbstractVectorSet + language::L + dimension::Int + + function MOIMultivaluedDecisionDiagram(language, dim = 0) + return new{typeof(language)}(language, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIMultivaluedDecisionDiagram{L}}) where {L <: + ConstraintCommons.AbstractMultivaluedDecisionDiagram} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIMultivaluedDecisionDiagram) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:language => set.language)) + return error_f(USUAL_CONSTRAINTS[:mdd])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIMultivaluedDecisionDiagram{typeof(set.language)}}(cidx) +end + +function Base.copy(set::MOIMultivaluedDecisionDiagram) + return MOIMultivaluedDecisionDiagram(deepcopy(set.language), copy(set.dimension)) +end + +""" +Multi-valued Decision Diagram (MDD) constraint. + +The MDD constraint is a constraint that can be used to model a wide range of problems. It is a directed graph where each node is labeled with a value and each edge is labeled with a value. The constraint is satisfied if there is a path from the first node to the last node such that the sequence of edge labels is a valid sequence of the value labels. + +```julia +@constraint(model, X in MDDConstraint(; language)) +``` +""" +struct MDDConstraint{L <: ConstraintCommons.AbstractMultivaluedDecisionDiagram} <: + JuMP.AbstractVectorSet + language::L + + function MDDConstraint(; language) + return new{typeof(language)}(language) + end +end + +function JuMP.moi_set(set::MDDConstraint, dim::Int) + return MOIMultivaluedDecisionDiagram(set.language, dim) +end + +## SECTION - Test Items for MDD +@testitem "MDD" tags=[:usual, :constraints, :mdd] default_imports=false begin + using CBLS + using JuMP + + import ConstraintCommons: MDD + + model = Model(CBLS.Optimizer) + + states = [ + Dict( # level x1 + (:r, 0) => :n1, + (:r, 1) => :n2, + (:r, 2) => :n3 + ), + Dict( # level x2 + (:n1, 2) => :n4, + (:n2, 2) => :n4, + (:n3, 0) => :n5 + ), + Dict( # level x3 + (:n4, 0) => :t, + (:n5, 0) => :t + ) + ] + + @variable(model, 0≤X[1:3]≤2, Int) + + @constraint(model, X in MDDConstraint(; language = MDD(states))) + + optimize!(model) + @info "MDD" value.(X) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/minimum.jl b/src/constraints/minimum.jl new file mode 100644 index 0000000..7c0ddcf --- /dev/null +++ b/src/constraints/minimum.jl @@ -0,0 +1,74 @@ +""" + MOIMinimum {F <: Function, T <: Number} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIMinimum{F <: Function, T <: Number} <: MOI.AbstractVectorSet + op::F + val::T + dimension::Int + + function MOIMinimum(op, val, dim = 0) + return new{typeof(op), typeof(val)}(op, val, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIMinimum{F, T}}) where { + F <: Function, T <: Number} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIMinimum) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:op => set.op, :val => set.val)) + return error_f(USUAL_CONSTRAINTS[:minimum])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIMinimum{typeof(set.op), typeof(set.val)}}(cidx) +end + +function Base.copy(set::MOIMinimum) + return MOIMinimum(copy(set.op), copy(set.val), copy(set.dimension)) +end + +""" +Global constraint ensuring that the minimum value in the tuple `x` satisfies the condition `op(x) val`. This constraint is useful for specifying that the minimum value in the tuple must satisfy a certain condition. + +```julia +@constraint(model, X in Minimum(; op = ==, val)) +``` +""" +struct Minimum{F <: Function, T <: Number} <: JuMP.AbstractVectorSet + op::F + val::T + + function Minimum(op, val) + return new{typeof(op), typeof(val)}(op, val) + end +end + +Minimum(; op::F = ==, val::T) where {F <: Function, T <: Number} = Minimum(op, val) + +function JuMP.moi_set(set::Minimum, dim::Int) + return MOIMinimum(set.op, set.val, dim) +end + +## SECTION - Test Items +@testitem "Minimum" tags=[:usual, :constraints, :minimum] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + + @constraint(model, X in Minimum(; op = ==, val = 3)) + + optimize!(model) + @info "Minimum" value.(X) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/n_values.jl b/src/constraints/n_values.jl new file mode 100644 index 0000000..f4f542a --- /dev/null +++ b/src/constraints/n_values.jl @@ -0,0 +1,88 @@ +""" + MOINValues{F <: Function, T1 <: Number, T2 <: Number, V <: Vector{T2}} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOINValues{F <: Function, T1 <: Number, T2 <: Number, V <: Vector{T2}} <: + MOI.AbstractVectorSet + op::F + val::T1 + vals::V + dimension::Int + + function MOINValues(op, val, vals, dim = 0) + return new{typeof(op), typeof(val), eltype(vals), typeof(vals)}(op, val, vals, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOINValues{F, T1, T2, V}}) where { + F <: Function, T1 <: Number, T2 <: Number, V <: Vector{T2}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOINValues) + vals = isempty(set.vals) ? nothing : set.vals + function e(x; kwargs...) + d = Dict(:op => set.op, :val => set.val) + isnothing(vals) && (d[:vals] = vals) + new_kwargs = merge(kwargs, d) + return error_f(USUAL_CONSTRAINTS[:nvalues])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, + MOINValues{typeof(set.op), typeof(set.val), eltype(set.vals), typeof(set.vals)}}(cidx) +end + +function Base.copy(set::MOINValues) + return MOINValues(copy(set.op), copy(set.val), copy(set.vals), copy(set.dimension)) +end + +""" +Global constraint ensuring that the number of distinct values in `X` satisfies the given condition. +""" +struct NValues{F <: Function, T1 <: Number, T2 <: Number, V <: Vector{T2}} <: + JuMP.AbstractVectorSet + op::F + val::T1 + vals::V + + function NValues(op, val, vals) + return new{typeof(op), typeof(val), eltype(vals), typeof(vals)}(op, val, vals) + end +end + +function NValues(; op::F = ==, + val::T1, + vals::V = Vector{Number}()) where { + F <: Function, T1 <: Number, T2 <: Number, V <: Vector{T2}} + return NValues(op, val, vals) +end + +function JuMP.moi_set(set::NValues, dim::Int) + vals = isnothing(set.vals) ? Vector{Number}() : set.vals + return MOINValues(set.op, set.val, vals, dim) +end + +## SECTION - Test Items +@testitem "NValues" tags=[:usual, :constraints, :nvalues] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + @variable(model, 1≤Y[1:5]≤5, Int) + @variable(model, 1≤Z[1:5]≤5, Int) + + @constraint(model, X in NValues(; op = ==, val = 5)) + @constraint(model, Y in NValues(; op = ==, val = 2)) + @constraint(model, Z in NValues(; op = <=, val = 5, vals = [1, 2])) + + optimize!(model) + @info "NValues" value.(X) value.(Y) value.(Z) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/no_overlap.jl b/src/constraints/no_overlap.jl new file mode 100644 index 0000000..9d1850c --- /dev/null +++ b/src/constraints/no_overlap.jl @@ -0,0 +1,90 @@ +""" + MOINoOverlap{I <: Integer, T <: Number, V <: Vector{T}} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOINoOverlap{I <: Integer, T <: Number, V <: Vector{T}} <: + MOI.AbstractVectorSet + bool::Bool + dim::I + pair_vars::V + dimension::Int + + function MOINoOverlap(bool, dim, pair_vars, moi_dim = 0) + return new{typeof(dim), eltype(pair_vars), typeof(pair_vars)}( + bool, dim, pair_vars, moi_dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOINoOverlap{I, T, V}}) where { + I <: Integer, T <: Number, V <: Vector{T}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOINoOverlap) + function e(x; kwargs...) + d = if isempty(set.pair_vars) + Dict(:dim => set.dim, :bool => set.bool) + else + Dict{Symbol, Any}( + :dim => set.dim, :bool => set.bool, :pair_vars => set.pair_vars) + end + new_kwargs = merge(kwargs, d) + return error_f(USUAL_CONSTRAINTS[:no_overlap])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{ + VOV, MOINoOverlap{typeof(set.dim), eltype(set.pair_vars), typeof(set.pair_vars)}}(cidx) +end + +function Base.copy(set::MOINoOverlap) + return MOINoOverlap( + copy(set.bool), copy(set.dim), copy(set.pair_vars), copy(set.dimension)) +end + +""" +Global constraint ensuring that the tuple `x` does not overlap with any configuration listed within the pair set `pair_vars`. This constraint, originating from the extension model, stipulates that `x` must avoid all configurations defined as pairs: `x ∩ pair_vars = ∅`. It is useful for specifying tuples that are explicitly forbidden and should be excluded from the solution space. + +```julia +@constraint(model, X in NoOverlap(; bool = true, dim = 1, pair_vars = nothing)) +``` +""" +struct NoOverlap{I <: Integer, T <: Number, V <: Vector{T}} <: + JuMP.AbstractVectorSet + bool::Bool + dim::I + pair_vars::V + + function NoOverlap(; bool = true, dim = 1, pair_vars = Vector{Number}()) + return new{typeof(dim), eltype(pair_vars), typeof(pair_vars)}(bool, dim, pair_vars) + end +end + +function JuMP.moi_set(set::NoOverlap, dim::Int) + return MOINoOverlap(set.bool, set.dim, set.pair_vars, dim) +end + +## SECTION - Test Items +@testitem "noOverlap" tags=[:usual, :constraints, :no_overlap] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + @variable(model, 1≤Y[1:5]≤6, Int) + @variable(model, 1≤Z[1:12]≤12, Int) + + @constraint(model, X in NoOverlap()) + @constraint(model, Y in NoOverlap(; pair_vars = [1, 1, 1, 1, 1])) + @constraint(model, + Z in NoOverlap(; pair_vars = [2, 4, 1, 4, 2, 3, 5, 1, 2, 3, 3, 2], dim = 3)) + + optimize!(model) + @info "NoOverlap" value.(X) value.(Y) value.(Z) + termination_status(model) + @info termination_status(model) +end diff --git a/src/constraints/ordered.jl b/src/constraints/ordered.jl new file mode 100644 index 0000000..43dddea --- /dev/null +++ b/src/constraints/ordered.jl @@ -0,0 +1,83 @@ +""" + MOIOrdered{F <: Function, T <: Number, V <: Vector{T}} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOIOrdered{F <: Function, T <: Number, V <: Vector{T}} <: + MOI.AbstractVectorSet + op::F + pair_vars::V + dimension::Int + + function MOIOrdered(op, pair_vars, moi_dim = 0) + return new{typeof(op), eltype(pair_vars), typeof(pair_vars)}( + op, pair_vars, moi_dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIOrdered{F, T, V}}) where { + F <: Function, T <: Number, V <: Vector{T}} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIOrdered) + function e(x; kwargs...) + d = if isempty(set.pair_vars) + Dict(:op => set.op) + else + Dict(:op => set.op, :pair_vars => set.pair_vars) + end + new_kwargs = merge(kwargs, d) + return error_f(USUAL_CONSTRAINTS[:ordered])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{ + VOV, MOIOrdered{typeof(set.op), eltype(set.pair_vars), typeof(set.pair_vars)}}(cidx) +end + +function Base.copy(set::MOIOrdered) + return MOIOrdered(copy(set.op), copy(set.pair_vars), copy(set.dimension)) +end + +""" +Global constraint ensuring that the variables are ordered according to `op`. +""" +struct Ordered{F <: Function, T <: Number, V <: Vector{T}} <: + JuMP.AbstractVectorSet + op::F + pair_vars::V + + function Ordered(op, pair_vars) + return new{typeof(op), eltype(pair_vars), typeof(pair_vars)}(op, pair_vars) + end +end + +function Ordered(; op = ≤, pair_vars = Vector{Number}()) + return Ordered(op, pair_vars) +end + +function JuMP.moi_set(set::Ordered, dim::Int) + return MOIOrdered(set.op, set.pair_vars, dim) +end + +## SECTION - Test Items +@testitem "Ordered" tags=[:usual, :constraints, :ordered] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + @variable(model, 1≤Y[1:5]≤5, Int) + + @constraint(model, X in Ordered()) + @constraint(model, Y in Ordered(; op = <)) + + optimize!(model) + @info "Ordered" value.(X) value.(Y) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/regular.jl b/src/constraints/regular.jl new file mode 100644 index 0000000..8ee907f --- /dev/null +++ b/src/constraints/regular.jl @@ -0,0 +1,83 @@ +""" + MOIRegular{L <: ConstraintCommons.AbstractAutomaton} <: AbstractVectorSet + +DOCSTRING +""" +struct MOIRegular{L <: ConstraintCommons.AbstractAutomaton} <: MOI.AbstractVectorSet + language::L + dimension::Int + + function MOIRegular(language, dim = 0) + return new{typeof(language)}(language, dim) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOIRegular{L}}) where {L <: ConstraintCommons.AbstractAutomaton} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOIRegular) + function e(x; kwargs...) + new_kwargs = merge(kwargs, Dict(:language => set.language)) + return error_f(USUAL_CONSTRAINTS[:regular])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, MOIRegular{typeof(set.language)}}(cidx) +end + +function Base.copy(set::MOIRegular) + return MOIRegular(deepcopy(set.language), copy(set.dimension)) +end + +""" +Ensures that a sequence `x` (interpreted as a word) is accepted by the regular language represented by a given automaton. This constraint verifies the compliance of `x` with the language rules encoded within the `automaton` parameter, which must be an instance of `<:AbstractAutomaton`. + +```julia +@constraint(model, X in RegularConstraint(; language)) +``` +""" +struct Regular{L <: ConstraintCommons.AbstractAutomaton} <: JuMP.AbstractVectorSet + language::L + + function Regular(; language) + return new{typeof(language)}(language) + end +end + +function JuMP.moi_set(set::Regular, dim::Int) + return MOIRegular(set.language, dim) +end + +## SECTION - Test Items for Regular +@testitem "Regular" tags=[:usual, :constraints, :regular] default_imports=false begin + using CBLS + using JuMP + + import ConstraintCommons: Automaton + + states = Dict( + (:a, 0) => :a, + (:a, 1) => :b, + (:b, 1) => :c, + (:c, 0) => :d, + (:d, 0) => :d, + (:d, 1) => :e, + (:e, 0) => :e + ) + start = :a + finish = :e + a = Automaton(states, start, finish) + + model = Model(CBLS.Optimizer) + + @variable(model, 0≤X[1:9]≤1, Int) + @constraint(model, X in Regular(; language = a)) + + optimize!(model) + @info "Regular" value.(X) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/constraints/sum.jl b/src/constraints/sum.jl new file mode 100644 index 0000000..7bbcbed --- /dev/null +++ b/src/constraints/sum.jl @@ -0,0 +1,86 @@ +""" + MOISum{F <: Function, T1 <: Number, T2 <: Number, V <: Number} <: MOI.AbstractVectorSet + +DOCSTRING +""" +struct MOISum{F <: Function, T1 <: Number, T2 <: Number, V <: Vector{T1}} <: + MOI.AbstractVectorSet + op::F + pair_vars::V + val::T2 + dimension::Int + + function MOISum(op, pair_vars, val, dimension) + return new{typeof(op), eltype(pair_vars), typeof(val), typeof(pair_vars)}( + op, pair_vars, val, dimension) + end +end + +function MOI.supports_constraint(::Optimizer, + ::Type{VOV}, + ::Type{MOISum{F, T1, T2, V}}) where {F, T1, T2, V} + return true +end + +function MOI.add_constraint( + optimizer::Optimizer, vars::MOI.VectorOfVariables, set::MOISum) + function e(x; kwargs...) + d = if isempty(set.pair_vars) + Dict(:op => set.op, :val => set.val) + else + Dict(:op => set.op, :pair_vars => set.pair_vars, :val => set.val) + end + new_kwargs = merge(kwargs, d) + return error_f(USUAL_CONSTRAINTS[:sum])(x; new_kwargs...) + end + cidx = constraint!(optimizer, e, map(x -> x.value, vars.variables)) + return CI{VOV, + MOISum{ + typeof(set.op), eltype(set.pair_vars), typeof(set.val), typeof(set.pair_vars)}}(cidx) +end + +function Base.copy(set::MOISum) + return MOISum(copy(set.op), copy(set.pair_vars), copy(set.val), copy(set.dimension)) +end + +""" +Global constraint ensuring that the sum of the variables in `x` satisfies a given condition. +""" +struct Sum{F <: Function, T1 <: Number, T2 <: Number, V <: Vector{T1}} <: + JuMP.AbstractVectorSet + op::F + pair_vars::V + val::T2 + + function Sum(op, pair_vars, val) + return new{typeof(op), eltype(pair_vars), typeof(val), typeof(pair_vars)}( + op, pair_vars, val) + end +end + +function Sum(; op = ==, pair_vars = Vector{Number}(), val) + return Sum(op, pair_vars, val) +end + +function JuMP.moi_set(set::Sum, dim::Int) + return MOISum(set.op, set.pair_vars, set.val, dim) +end + +## SECTION - Test Items for Sum +@testitem "Sum" tags=[:usual, :constraints, :sum] default_imports=false begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:5]≤5, Int) + @variable(model, 1≤Y[1:5]≤5, Int) + + @constraint(model, X in Sum(; op = ==, val = 15)) + @constraint(model, Y in Sum(; op = <=, val = 10)) + + optimize!(model) + @info "Sum" value.(X) value.(Y) + termination_status(model) + @info solution_summary(model) +end diff --git a/src/objectives.jl b/src/objectives.jl index 5bb4cc3..024aa9b 100644 --- a/src/objectives.jl +++ b/src/objectives.jl @@ -45,7 +45,10 @@ ScalarFunction(f, x::VI) = ScalarFunction(f, VOV([x])) Base.copy(func::ScalarFunction) = ScalarFunction(func.f, func.X) # supports -MOI.supports(::Optimizer, ::OF{ScalarFunction{F, V}}) where {F <: Function, V <: Union{Nothing, VI,VOV}} = true +function MOI.supports(::Optimizer, + ::OF{ScalarFunction{F, V}}) where {F <: Function, V <: Union{Nothing, VI, VOV}} + true +end # set function MOI.set(optimizer::Optimizer, ::OF, func::ScalarFunction{F, Nothing} @@ -55,17 +58,17 @@ end function MOI.set(optimizer::Optimizer, ::OF, func::ScalarFunction{F, VOV} ) where {F <: Function} # VOV, mainly for JuMP - objective_func = _ -> func.f(map(y -> get_value(optimizer,y.value), func.X.variables)) - return objective!(optimizer, objective_func) - end + objective_func = _ -> func.f(map(y -> get_value(optimizer, y.value), func.X.variables)) + return objective!(optimizer, objective_func) +end # @autodoc - function MOIU.map_indices(index_map::Function, sf::ScalarFunction{F,VOV} +function MOIU.map_indices(index_map::Function, sf::ScalarFunction{F, VOV} ) where {F <: Function} return ScalarFunction(sf.f, MOIU.map_indices(index_map, sf.X)) - end +end # @autodoc - function MOIU.map_indices(::Function, sf::ScalarFunction{F,Nothing}) where {F <: Function} - return ScalarFunction(sf.f, nothing) +function MOIU.map_indices(::Function, sf::ScalarFunction{F, Nothing}) where {F <: Function} + return ScalarFunction(sf.f, nothing) end diff --git a/src/results.jl b/src/results.jl index 71571c8..a9a24c5 100644 --- a/src/results.jl +++ b/src/results.jl @@ -32,9 +32,8 @@ function MOI.get(optimizer::Optimizer, ::MOI.VariablePrimal, vi::MOI.VariableInd end end - MOI.get(optimizer::Optimizer, ::MOI.SolveTimeSec) = time_info(optimizer)[:total_run] function MOI.get(optimizer::Optimizer, ::MOI.RawStatusString) return has_solution(optimizer) ? "Satisfying solution" : "No solutions" -end \ No newline at end of file +end diff --git a/src/variables.jl b/src/variables.jl index 6fa6298..5261d0b 100644 --- a/src/variables.jl +++ b/src/variables.jl @@ -29,7 +29,10 @@ function MOI.supports_constraint(::Optimizer, ::Type{VI}, ::Type{MOI.GreaterThan return true end -MOI.supports_constraint(::Optimizer, ::Type{VI}, ::Type{DiscreteSet{T}}) where {T <: Number} = true +function MOI.supports_constraint( + ::Optimizer, ::Type{VI}, ::Type{DiscreteSet{T}}) where {T <: Number} + true +end """ MOI.add_constraint(optimizer::Optimizer, v::VI, set::DiscreteSet{T}) where T <: Number @@ -45,55 +48,36 @@ function MOI.add_constraint(optimizer::Optimizer, v::VI, set::DiscreteSet{T} ) where {T <: Number} vidx = v.value _set_domain!(optimizer, vidx, set.values) - return CI{VI,DiscreteSet{T}}(vidx) + return CI{VI, DiscreteSet{T}}(vidx) end -# function MOI.add_constraint(optimizer::Optimizer, v::VI, lt::MOI.LessThan{T} -# ) where {T <: AbstractFloat} -# vidx = v.value -# d = make_domain(typemin(Int), lt.upper, Val(:range)) -# update_domain!(optimizer, vidx, d) -# return CI{VI,MOI.LessThan{T}}(vidx) -# end - -# function MOI.add_constraint(optimizer::Optimizer, v::VI, gt::MOI.GreaterThan{T} -# ) where {T <: AbstractFloat} -# vidx = v.value -# d = make_domain(gt.lower, typemax(Int), Val(:range)) -# update_domain!(optimizer, vidx, d) -# return CI{VI,MOI.GreaterThan{T}}(vidx) -# end - -# make_domain(a, b, ::Val{:range}) = domain(Int(a):Int(b)) -# make_domain(a, b, ::Val{:inter}) = domain((a, true), (b, true)) - function MOI.add_constraint(optimizer::Optimizer, v::VI, lt::MOI.LessThan{T} - ) where {T <: AbstractFloat} - vidx = v.value - push!(optimizer.compare_vars, vidx) - if vidx ∈ optimizer.int_vars - d = domain(Int(typemin(Int)), Int(lt.upper)) - else - a = Float64(-floatmax(Float32)) - d = domain(Interval{Open, Closed}(a, lt.upper)) - end - update_domain!(optimizer, vidx, d) - return CI{VI,MOI.LessThan{T}}(vidx) +) where {T <: AbstractFloat} + vidx = v.value + push!(optimizer.compare_vars, vidx) + if vidx ∈ optimizer.int_vars + d = domain(Int(typemin(Int)), Int(lt.upper)) + else + a = Float64(-floatmax(Float32)) + d = domain(Interval{Open, Closed}(a, lt.upper)) end + update_domain!(optimizer, vidx, d) + return CI{VI, MOI.LessThan{T}}(vidx) +end - function MOI.add_constraint(optimizer::Optimizer, v::VI, gt::MOI.GreaterThan{T} - ) where {T <: AbstractFloat} - vidx = v.value - push!(optimizer.compare_vars, vidx) - if vidx ∈ optimizer.int_vars - d = domain(Int(gt.lower):Int(typemax(Int))) - else - b = Float64(floatmax(Float32)) - d = domain(Interval{Closed, Open}(gt.lower, b)) - end - update_domain!(optimizer, vidx, d) - return CI{VI,MOI.GreaterThan{T}}(vidx) +function MOI.add_constraint(optimizer::Optimizer, v::VI, gt::MOI.GreaterThan{T} +) where {T <: AbstractFloat} + vidx = v.value + push!(optimizer.compare_vars, vidx) + if vidx ∈ optimizer.int_vars + d = domain(Int(gt.lower):Int(typemax(Int))) + else + b = Float64(floatmax(Float32)) + d = domain(Interval{Closed, Open}(gt.lower, b)) end + update_domain!(optimizer, vidx, d) + return CI{VI, MOI.GreaterThan{T}}(vidx) +end function MOI.add_constraint(optimizer::Optimizer, v::VI, i::MOI.Interval{T} ) where {T <: Real} @@ -101,14 +85,14 @@ function MOI.add_constraint(optimizer::Optimizer, v::VI, i::MOI.Interval{T} is_int = MOI.is_valid(optimizer, CI{VI, MOI.Integer}(vidx)) d = make_domain(i.lower, i.upper, Val(is_int ? :range : :inter)) _set_domain!(optimizer, vidx, d) - return CI{VI,MOI.Interval{T}}(vidx) + return CI{VI, MOI.Interval{T}}(vidx) end function MOI.add_constraint(optimizer::Optimizer, v::VI, et::MOI.EqualTo{T} ) where {T <: Number} vidx = v.value _set_domain!(optimizer, vidx, et.value) - return CI{VI,MOI.EqualTo{T}}(vidx) + return CI{VI, MOI.EqualTo{T}}(vidx) end """ @@ -124,5 +108,30 @@ function MOI.add_constraint(optimizer::Optimizer, v::VI, ::MOI.Integer) x = get_variable(optimizer, vidx) _set_domain!(optimizer, vidx, convert(RangeDomain, x.domain)) end - return MOI.ConstraintIndex{VI,MOI.Integer}(vidx) + return MOI.ConstraintIndex{VI, MOI.Integer}(vidx) +end + +# MOI.supports_constraint(::Optimizer, ::Type{VI}, ::Type{<:MOI.ZeroOne}) = true + +# function MOI.add_constraint(optimizer::Optimizer, v::VI, ::MOI.ZeroOne) +# vidx = v.value +# push!(optimizer.int_vars, vidx) +# if vidx ∈ optimizer.compare_vars +# d = domain(0:1) +# _set_domain!(optimizer, vidx, d) +# end +# return MOI.ConstraintIndex{VI, MOI.ZeroOne}(vidx) +# end + +## SECTION - Test Items +@testitem "Variable Index" begin + using CBLS + using JuMP + + model = Model(CBLS.Optimizer) + + @variable(model, 1≤X[1:4]≤4, Int) + # @variable(model, Y[1:4], Bin) + + optimize!(model) end diff --git a/test/JuMP.jl b/test/JuMP.jl index 8a16965..e549e08 100644 --- a/test/JuMP.jl +++ b/test/JuMP.jl @@ -6,7 +6,7 @@ using JuMP err = _ -> 1.0 concept = _ -> true - @variable(m, 1 ≤ X[1:10] ≤ 4, Int) + @variable(m, 1≤X[1:10]≤4, Int) @constraint(m, X in Error(err)) @constraint(m, X in Predicate(concept)) @@ -30,18 +30,17 @@ end set_time_limit_sec(model, 5.0) @test time_limit_sec(model) == 5.0 - @variable(model, 0 ≤ x ≤ 20, Int) + @variable(model, 0≤x≤20, Int) @variable(model, y in DiscreteSet(0:20)) - @constraint(model, [x,y] in Predicate(v -> 6v[1] + 8v[2] >= 100 )) - @constraint(model, [x,y] in Predicate(v -> 7v[1] + 12v[2] >= 120 )) + @constraint(model, [x, y] in Predicate(v -> 6v[1] + 8v[2] >= 100)) + @constraint(model, [x, y] in Predicate(v -> 7v[1] + 12v[2] >= 120)) objFunc = v -> 12v[1] + 20v[2] @objective(model, Min, ScalarFunction(objFunc)) optimize!(model) - @info "JuMP: basic opt" value(x) value(y) (12*value(x)+20*value(y)) solve_time(model) termination_status(model) + @info "JuMP: basic opt" value(x) value(y) (12 * value(x)+20 * value(y)) solve_time(model) termination_status(model) @info solution_summary(model) - -end \ No newline at end of file +end diff --git a/test/MOI_wrapper.jl b/test/MOI_wrapper.jl index 0106487..758b163 100644 --- a/test/MOI_wrapper.jl +++ b/test/MOI_wrapper.jl @@ -25,7 +25,7 @@ end const BRIDGED = MOI.instantiate( OPTIMIZER_CONSTRUCTOR, with_bridge_type = Float64 ) -const CONFIG = MOIT.Config(atol=1e-6, rtol=1e-6) +const CONFIG = MOIT.Config(atol = 1e-6, rtol = 1e-6) # @testset "Unit" begin # # Test all the functions included in dictionary `MOI.Test.unittests`, @@ -68,10 +68,10 @@ const CONFIG = MOIT.Config(atol=1e-6, rtol=1e-6) m1 = CBLS.Optimizer() MOI.add_variable(m1) - MOI.add_constraint(m1, VI(1), CBLS.DiscreteSet([1,2,3])) + MOI.add_constraint(m1, VI(1), CBLS.DiscreteSet([1, 2, 3])) m2 = CBLS.Optimizer() - MOI.add_constrained_variable(m2, CBLS.DiscreteSet([1,2,3])) + MOI.add_constrained_variable(m2, CBLS.DiscreteSet([1, 2, 3])) # opt = CBLS.sudoku(3, modeler = :MOI) # MOI.optimize!(opt) diff --git a/test/TestItemRunner.jl b/test/TestItemRunner.jl new file mode 100644 index 0000000..d843b4d --- /dev/null +++ b/test/TestItemRunner.jl @@ -0,0 +1,5 @@ +using TestItemRunner + +@testset "TestItemRunner" begin + @run_package_tests +end diff --git a/test/runtests.jl b/test/runtests.jl index bf16a49..8fb5049 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -4,4 +4,5 @@ using Test @testset "CBLS.jl" begin include("MOI_wrapper.jl") include("JuMP.jl") + include("TestItemRunner.jl") end From 89941ea17a43117630334eddcf0fc4f721479e8e Mon Sep 17 00:00:00 2001 From: Jean-Francois Baffier Date: Sun, 23 Jun 2024 10:43:22 +0900 Subject: [PATCH 2/3] Update Project.toml --- Project.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Project.toml b/Project.toml index fcffc56..399be22 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CBLS" uuid = "a3809bfe-37bb-4d48-a667-bac4c6be8d90" authors = ["Jean-Francois Baffier"] -version = "0.1.13" +version = "0.1.14" [deps] ConstraintCommons = "e37357d9-0691-492f-a822-e5ea6a920954" @@ -30,4 +30,4 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestItemRunner = "f8b46487-2199-4994-9208-9a1283c18c0a" [targets] -test = ["Aqua", "Test", "TestItemRunner"] \ No newline at end of file +test = ["Aqua", "Test", "TestItemRunner"] From bf33070b92f21cbb02f04265e28db602cc3b36e8 Mon Sep 17 00:00:00 2001 From: Jean-Francois Baffier Date: Sun, 23 Jun 2024 10:51:21 +0900 Subject: [PATCH 3/3] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 399be22..0335df4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "CBLS" uuid = "a3809bfe-37bb-4d48-a667-bac4c6be8d90" authors = ["Jean-Francois Baffier"] -version = "0.1.14" +version = "0.2.0" [deps] ConstraintCommons = "e37357d9-0691-492f-a822-e5ea6a920954"