diff --git a/CHANGELOG.md b/CHANGELOG.md index 75299ac1..5e4f8b04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # ConstrainSolver.jl - Changelog +## v0.1.8 (15th of June 2020) +- Support for indicator constraints + - i.e. `@constraint(m, b => { x + y <= 10 })` + ## v0.1.7 (22th of May 2020) - Better feasibility and pruning in `==` - **Bugfixes:** diff --git a/Project.toml b/Project.toml index 62027e2b..e9944650 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "ConstraintSolver" uuid = "e0e52ebd-5523-408d-9ca3-7641f1cd1405" authors = ["Ole Kröger "] -version = "0.1.7" +version = "0.1.8" [deps] Formatting = "59287772-0a20-5a39-b81b-1366585eb4c0" diff --git a/docs/src/supported.md b/docs/src/supported.md index 3e2e6e40..3c24d0e6 100644 --- a/docs/src/supported.md +++ b/docs/src/supported.md @@ -43,12 +43,14 @@ It's a bit more but still not as fully featured as I would like it to be. - [X] `==` - [X] `<=` - [X] `>=` + - [X] `!=` - [X] All different - `@constraint(m, [x,y,z] in CS.AllDifferentSet())` -- [X] Support for `!=` - - [X] Supports `a != b` with `a` and `b` being single variables - - [X] Support for linear unequal constraints [#66](https://github.com/Wikunia/ConstraintSolver.jl/issues/66) - [X] `TableSet` constraint [#130](https://github.com/Wikunia/ConstraintSolver.jl/pull/130) +- [X] Indicator constraints [#167](https://github.com/Wikunia/ConstraintSolver.jl/pull/167) + - i.e `@constraint(m, b => {x + y >= 12})` + - [X] for affine inner constraints + - [X] for all types of inner constraints - [ ] Scheduling constraints - [ ] Cycle constraints diff --git a/src/ConstraintSolver.jl b/src/ConstraintSolver.jl index 44fec3ce..31394280 100644 --- a/src/ConstraintSolver.jl +++ b/src/ConstraintSolver.jl @@ -52,8 +52,8 @@ include("constraints/less_than.jl") include("constraints/svc.jl") include("constraints/equal.jl") include("constraints/not_equal.jl") - include("constraints/table.jl") +include("constraints/indicator.jl") """ add_var!(com::CS.CoM, from::Int, to::Int; fix=nothing) diff --git a/src/MOI_wrapper/MOI_wrapper.jl b/src/MOI_wrapper/MOI_wrapper.jl index f34dcad0..49d1c849 100644 --- a/src/MOI_wrapper/MOI_wrapper.jl +++ b/src/MOI_wrapper/MOI_wrapper.jl @@ -1,5 +1,6 @@ const SVF = MOI.SingleVariable const SAF = MOI.ScalarAffineFunction +const VAF = MOI.VectorAffineFunction # indices const VI = MOI.VariableIndex diff --git a/src/MOI_wrapper/constraints.jl b/src/MOI_wrapper/constraints.jl index 378e39a7..719527ed 100644 --- a/src/MOI_wrapper/constraints.jl +++ b/src/MOI_wrapper/constraints.jl @@ -3,6 +3,20 @@ JuMP constraints """ sense_to_set(::Function, ::Val{:!=}) = NotEqualTo(0.0) +""" + Support for indicator constraints with a set constraint as the right hand side +""" +function JuMP._build_indicator_constraint( + _error::Function, variable::JuMP.AbstractVariableRef, + constraint::JuMP.VectorConstraint, ::Type{MOI.IndicatorSet{A}}) where A + + variable_indices = [v.index for v in constraint.func] + set = CS.IndicatorSet{A}(variable, MOI.VectorOfVariables(variable_indices), constraint.set, 0) + vov = VariableRef[variable] + append!(vov, constraint.func) + return JuMP.VectorConstraint(vov, set) +end + ### != MOIU.shift_constant(set::NotEqualTo, value) = NotEqualTo(set.value + value) @@ -39,6 +53,29 @@ MOI.supports_constraint( ::Type{TableSetInternal}, ) = true +MOI.supports_constraint( + ::Optimizer, + ::Type{SAF{T}}, + ::Type{NotEqualTo{T}}, +) where {T<:Real} = true + +function MOI.supports_constraint( + ::Optimizer, + func::Type{VAF{T}}, + set::Type{IS}, +) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:MOI.IndicatorSet{A, ASS}} + return A == MOI.ACTIVATE_ON_ONE || A == MOI.ACTIVATE_ON_ZERO +end + +function MOI.supports_constraint( + ::Optimizer, + func::Type{MOI.VectorOfVariables}, + set::Type{IS}, +) where {A, IS<:CS.IndicatorSet{A}} + return A == MOI.ACTIVATE_ON_ONE || A == MOI.ACTIVATE_ON_ZERO +end + + function check_inbounds(model::Optimizer, aff::SAF{T}) where {T<:Real} for term in aff.terms check_inbounds(model, term.variable_index) @@ -267,12 +304,6 @@ function MOI.add_constraint( return MOI.ConstraintIndex{MOI.VectorOfVariables,TableSetInternal}(length(com.constraints)) end -MOI.supports_constraint( - ::Optimizer, - ::Type{SAF{T}}, - ::Type{NotEqualTo{T}}, -) where {T<:Real} = true - function MOI.add_constraint( model::Optimizer, func::SAF{T}, @@ -310,6 +341,96 @@ function MOI.add_constraint( return MOI.ConstraintIndex{SAF{T},NotEqualTo{T}}(length(com.constraints)) end +function MOI.add_constraint( + model::Optimizer, + func::VAF{T}, + set::IS, +) where {A, T<:Real,ASS<:MOI.AbstractScalarSet, IS<:MOI.IndicatorSet{A, ASS}} + com = model.inner + com.info.n_constraint_types.indicator += 1 + + indices = [v.scalar_term.variable_index.value for v in func.terms] + + # for normal linear constraints + inner_indices = [v.scalar_term.variable_index.value for v in func.terms if v.output_index == 2] + inner_terms = [v.scalar_term for v in func.terms if v.output_index == 2] + inner_constant = func.constants[2] + inner_set = set.set + if ASS isa Type{MOI.GreaterThan{T}} + inner_terms = [MOI.ScalarAffineTerm(-v.scalar_term.coefficient, v.scalar_term.variable_index) for v in func.terms if v.output_index == 2] + inner_constant = -inner_constant + inner_set = MOI.LessThan{T}(-set.set.lower) + end + inner_func = MOI.ScalarAffineFunction{T}(inner_terms, inner_constant) + + internals = ConstraintInternals( + length(com.constraints) + 1, + func, + MOI.IndicatorSet{A}(inner_set), + indices + ) + + lc = LinearConstraint(inner_func, inner_set, inner_indices) + # should not be used... + lc.std.idx = 0 + + con = IndicatorConstraint(internals, A, lc) + + push!(com.constraints, con) + for (i, ind) in enumerate(con.std.indices) + push!(com.subscription[ind], con.std.idx) + end + + return MOI.ConstraintIndex{VAF{T},MOI.IndicatorSet{A, ASS}}(length(com.constraints)) +end + +function MOI.add_constraint( + model::Optimizer, + vars::MOI.VectorOfVariables, + set::IS, +) where {A, T<:Real,IS<:CS.IndicatorSet{A}} + com = model.inner + com.info.n_constraint_types.indicator += 1 + + internals = ConstraintInternals( + length(com.constraints) + 1, + vars, + set, + Int[v.value for v in vars.variables] + ) + + inner_constraint = nothing + if set.set isa CS.AllDifferentSetInternal + inner_internals = ConstraintInternals( + 0, + MOI.VectorOfVariables(vars.variables[2:end]), + set.set, + Int[v.value for v in vars.variables[2:end]] + ) + + inner_constraint = AllDifferentConstraint( + inner_internals, + Int[], # pval_mapping will be filled later + Int[], # vertex_mapping => later + Int[], # vertex_mapping_bw => later + Int[], # di_ei => later + Int[], # di_ej => later + MatchingInit(), + Int[] + ) + end + + + con = IndicatorConstraint(internals, A, inner_constraint) + + push!(com.constraints, con) + for (i, ind) in enumerate(con.std.indices) + push!(com.subscription[ind], con.std.idx) + end + + return MOI.ConstraintIndex{MOI.VectorOfVariables,CS.IndicatorSet{A}}(length(com.constraints)) +end + function set_pvals!(model::CS.Optimizer) com = model.inner for constraint in com.constraints diff --git a/src/constraints/indicator.jl b/src/constraints/indicator.jl new file mode 100644 index 00000000..85a6dd6c --- /dev/null +++ b/src/constraints/indicator.jl @@ -0,0 +1,90 @@ +""" + prune_constraint!( + com::CS.CoM, + constraint::IndicatorConstraint, + fct::Union{MOI.VectorOfVariables, VAF{T}}, + set::IS; + logs = true, + ) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}} + +Prune the search space given the indicator constraint. An indicator constraint is of the form `b => {x + y == 2}`. +Where the constraint in `{ }` is currently a linear constraint. + +Return whether the search space is still feasible. +""" +function prune_constraint!( + com::CS.CoM, + constraint::IndicatorConstraint, + fct::Union{MOI.VectorOfVariables, VAF{T}}, + set::IS; + logs = true, +) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}} + indicator_var_idx = constraint.std.indices[1] + search_space = com.search_space + indicator_var = search_space[indicator_var_idx] + # still feasible but nothing to prune + !isfixed(indicator_var) && return true + # if active + CS.value(indicator_var) != Int(constraint.activate_on) && return true + inner_constraint = constraint.inner_constraint + return prune_constraint!(com, inner_constraint, inner_constraint.std.fct, inner_constraint.std.set; logs=logs) +end + +""" + still_feasible( + com::CoM, + constraint::IndicatorConstraint, + fct::Union{MOI.VectorOfVariables, VAF{T}}, + set::IS, + val::Int, + index::Int, + ) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}} + +Return whether the search space is still feasible when setting `search_space[index]` to value. +""" +function still_feasible( + com::CoM, + constraint::IndicatorConstraint, + fct::Union{MOI.VectorOfVariables, VAF{T}}, + set::IS, + val::Int, + index::Int, +) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}} + indicator_var_idx = constraint.std.indices[1] + search_space = com.search_space + indicator_var = search_space[indicator_var_idx] + # still feasible but nothing to prune + !isfixed(indicator_var) && index != indicator_var_idx && return true + # if deactivating or is deactivated + if index != indicator_var_idx + CS.value(indicator_var) != Int(constraint.activate_on) && return true + else + val != Int(constraint.activate_on) && return true + end + # if activating or activated check the inner constraint + inner_constraint = constraint.inner_constraint + return still_feasible(com, inner_constraint, inner_constraint.std.fct, inner_constraint.std.set, val, index) +end + +""" + is_solved_constraint( + constraint::IndicatorConstraint, + fct::Union{MOI.VectorOfVariables, VAF{T}}, + set::IS, + values::Vector{Int} + ) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}} + +Return whether given `values` the constraint is fulfilled. +""" +function is_solved_constraint( + constraint::IndicatorConstraint, + fct::Union{MOI.VectorOfVariables, VAF{T}}, + set::IS, + values::Vector{Int} +) where {A, T<:Real, ASS<:MOI.AbstractScalarSet, IS<:Union{IndicatorSet{A}, MOI.IndicatorSet{A, ASS}}} + if values[1] == Int(constraint.activate_on) + inner_constraint = constraint.inner_constraint + return is_solved_constraint(inner_constraint, inner_constraint.std.fct, inner_constraint.std.set, values[2:end]) + end + return true +end \ No newline at end of file diff --git a/src/hashes.jl b/src/hashes.jl index 245de85b..853b991d 100644 --- a/src/hashes.jl +++ b/src/hashes.jl @@ -20,3 +20,7 @@ end function constraint_hash(constraint::SingleVariableConstraint) return hash([string(typeof(constraint.std.set)), constraint.std.indices]) end + +function constraint_hash(constraint::IndicatorConstraint) + return hash([string(typeof(constraint.std.set)), constraint.std.indices, constraint.activate_on, constraint_hash(constraint.inner_constraint)]) +end diff --git a/src/types.jl b/src/types.jl index 60c2ae65..05452f8e 100644 --- a/src/types.jl +++ b/src/types.jl @@ -29,6 +29,7 @@ mutable struct NumberConstraintTypes notequal::Int alldifferent::Int table::Int + indicator::Int end mutable struct CSInfo @@ -81,6 +82,8 @@ end ====================== TYPES FOR CONSTRAINTS ======================================== ====================================================================================# +abstract type Constraint end + """ BoundRhsVariable idx - variable index in the lp Model @@ -144,6 +147,13 @@ mutable struct TableBacktrackInfo indices :: Vector{Int} end +struct IndicatorSet{A} <: MOI.AbstractVectorSet + ind_var :: JuMP.VariableRef + func :: MOI.VectorOfVariables + set :: MOI.AbstractVectorSet + dimension::Int +end + #==================================================================================== ====================================================================================# @@ -171,8 +181,6 @@ end ====================== CONSTRAINTS ================================================== ====================================================================================# -abstract type Constraint end - mutable struct BasicConstraint <: Constraint std::ConstraintInternals end @@ -226,6 +234,12 @@ mutable struct TableConstraint <: Constraint sum_max::Vector{Int} end +mutable struct IndicatorConstraint <: Constraint + std::ConstraintInternals + activate_on::MOI.ActivationCondition + inner_constraint::Constraint +end + #==================================================================================== ====================== OBJECTIVES ================================================== ====================================================================================# diff --git a/test/constraints/indicator.jl b/test/constraints/indicator.jl new file mode 100644 index 00000000..23124d56 --- /dev/null +++ b/test/constraints/indicator.jl @@ -0,0 +1,180 @@ +@testset "IndicatorConstraint" begin +@testset "Basic ==" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,2,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, b => {x + y == 7}) + @objective(m, Max, b) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 1.0 + @test JuMP.value(b) ≈ 1.0 + @test JuMP.value(x) ≈ 4 + @test JuMP.value(y) ≈ 3 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end + +@testset "Basic == Active on zero" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,2,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, !b => {x + y == 9}) + @objective(m, Min, b) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 1.0 + @test JuMP.value(b) ≈ 1.0 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end + +@testset "Basic == infeasible" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, b == 1) + @constraint(m, b => {x + y == 6}) + optimize!(m) + @test JuMP.termination_status(m) == MOI.INFEASIBLE + com = JuMP.backend(m).optimizer.model.inner + @test !is_solved(com) +end + +@testset "Basic !=" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,2,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, b => {x + y != 8}) + @objective(m, Max, x+y) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 8.0 + @test JuMP.value(b) ≈ 0.0 + @test JuMP.value(x) ≈ 4 + @test JuMP.value(y) ≈ 4 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end + +@testset "Basic >=" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,2,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, b => {x + y >= 6}) + @objective(m, Min, x+y) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 4.0 + @test JuMP.value(b) ≈ 0.0 + @test JuMP.value(x) ≈ 1 + @test JuMP.value(y) ≈ 3 + + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,2,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, b == 1) + @constraint(m, b => {x + y >= 6}) + @objective(m, Min, x+y) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 6.0 + @test JuMP.value(b) ≈ 1.0 + @test JuMP.value(x) ≈ 2 + @test JuMP.value(y) ≈ 4 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end + +@testset "Basic <=" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,2,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, b => {x + y <= 4}) + @objective(m, Max, x+y) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 8.0 + @test JuMP.value(b) ≈ 0.0 + @test JuMP.value(x) ≈ 4 + @test JuMP.value(y) ≈ 4 + + m = Model(CSJuMPTestOptimizer()) + @variable(m, x, CS.Integers([1,2,4])) + @variable(m, y, CS.Integers([3,4])) + @variable(m, b, Bin) + @constraint(m, b == 1) + @constraint(m, b => {x + y <= 4}) + @objective(m, Max, x+y) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 4.0 + @test JuMP.value(b) ≈ 1.0 + @test JuMP.value(x) ≈ 1 + @test JuMP.value(y) ≈ 3 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end + +@testset "Basic AllDifferent" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, 0 <= x <= 1, Int) + @variable(m, 0 <= y <= 1, Int) + @variable(m, a, Bin) + @constraint(m, x +y <= 1) + @constraint(m, [a, x] in CS.AllDifferentSet()) + @constraint(m, a => {[a,x,y] in CS.AllDifferentSet()}) + @objective(m, Max, a) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 0.0 + @test JuMP.value(a) ≈ 0.0 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end + +@testset "Basic AllDifferent achievable" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, 0 <= x <= 1, Int) + @variable(m, 0 <= y <= 1, Int) + @variable(m, a, Bin) + @constraint(m, x +y <= 1) + @constraint(m, [a, x] in CS.AllDifferentSet()) + @constraint(m, a => {[x,y] in CS.AllDifferentSet()}) + @objective(m, Max, a) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 1.0 + @test JuMP.value(a) ≈ 1.0 + @test JuMP.value(y) ≈ 1.0 + @test JuMP.value(x) ≈ 0.0 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end + +@testset "Basic AllDifferent negated" begin + m = Model(CSJuMPTestOptimizer()) + @variable(m, 0 <= x <= 1, Int) + @variable(m, 0 <= y <= 1, Int) + @variable(m, a, Bin) + @constraint(m, x +y <= 1) + @constraint(m, [a, x] in CS.AllDifferentSet()) + @constraint(m, !a => {[x,y] in CS.AllDifferentSet()}) + @objective(m, Min, a) + optimize!(m) + @test JuMP.termination_status(m) == MOI.OPTIMAL + @test JuMP.objective_value(m) ≈ 0.0 + @test JuMP.value(a) ≈ 0.0 + @test JuMP.value(y) ≈ 0.0 + @test JuMP.value(x) ≈ 1.0 + com = JuMP.backend(m).optimizer.model.inner + @test is_solved(com) +end +end \ No newline at end of file diff --git a/test/moi.jl b/test/moi.jl index 1632743c..8b60239f 100644 --- a/test/moi.jl +++ b/test/moi.jl @@ -33,6 +33,38 @@ CS.NotEqualTo{Float64}, ) + f = MOI.VectorAffineFunction( + [MOI.VectorAffineTerm(1, MOI.ScalarAffineTerm(1.0, MOI.VariableIndex(1))), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, MOI.VariableIndex(2))), + MOI.VectorAffineTerm(2, MOI.ScalarAffineTerm(1.0, MOI.VariableIndex(3))), + ], + [0.0, 0.0], + ) + indicator_set = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.LessThan(9.0)) + @test MOI.supports_constraint( + optimizer, + typeof(f), + typeof(indicator_set) + ) + indicator_set = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.GreaterThan(9.0)) + @test MOI.supports_constraint( + optimizer, + typeof(f), + typeof(indicator_set) + ) + indicator_set = MOI.IndicatorSet{MOI.ACTIVATE_ON_ONE}(MOI.EqualTo(9.0)) + @test MOI.supports_constraint( + optimizer, + typeof(f), + typeof(indicator_set) + ) + @test MOI.supports_constraint( + optimizer, + MOI.VectorOfVariables, + CS.IndicatorSet{MOI.ACTIVATE_ON_ONE} + ) + + # TimeLimit @test MOI.supports(optimizer, MOI.TimeLimitSec()) diff --git a/test/runtests.jl b/test/runtests.jl index 1bb4a6a9..a25a82fd 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -22,6 +22,7 @@ include("unit/index.jl") include("options.jl") include("moi.jl") include("constraints/table.jl") +include("constraints/indicator.jl") include("lp_solver.jl") diff --git a/test/unit/constraints/indicator.jl b/test/unit/constraints/indicator.jl new file mode 100644 index 00000000..50bf854a --- /dev/null +++ b/test/unit/constraints/indicator.jl @@ -0,0 +1,44 @@ +@testset "indicator" begin + m = Model(optimizer_with_attributes(CS.Optimizer, "no_prune" => true, "logging" => [])) + @variable(m, x, CS.Integers([-3,1,2,3])) + @variable(m, y, CS.Integers([-3,1,2,3])) + @variable(m, b, Bin) + @constraint(m, b => {x+y+1 == 5}) + optimize!(m) + com = JuMP.backend(m).optimizer.model.inner + constraint = get_constraints_by_type(com, CS.IndicatorConstraint)[1] + + @test CS.is_solved_constraint(constraint, constraint.std.fct, constraint.std.set, [1,2,2]) + @test !CS.is_solved_constraint(constraint, constraint.std.fct, constraint.std.set, [1,2,3]) + @test CS.is_solved_constraint(constraint, constraint.std.fct, constraint.std.set, [0,2,3]) + + constr_indices = constraint.std.indices + @test CS.still_feasible(com, constraint, constraint.std.fct, constraint.std.set, -3, constr_indices[2]) + @test CS.still_feasible(com, constraint, constraint.std.fct, constraint.std.set, 1, constr_indices[3]) + # not actually feasible but will not be tested fully here + CS.fix!(com, com.search_space[constr_indices[1]], 1) + # will be tested when setting the next + @test !CS.still_feasible(com, constraint, constraint.std.fct, constraint.std.set, -3, constr_indices[3]) + + # need to create a backtrack_vec to reverse pruning + dummy_backtrack_obj = CS.BacktrackObj(com) + push!(com.backtrack_vec, dummy_backtrack_obj) + # reverse previous fix + CS.reverse_pruning!(com, 1) + com.c_backtrack_idx = 1 + # now setting it to 1 should be feasible + @test CS.still_feasible(com, constraint, constraint.std.fct, constraint.std.set, -3, constr_indices[3]) + + @test CS.prune_constraint!(com, constraint, constraint.std.fct, constraint.std.set) + for ind in constr_indices[2:3] + @test sort(CS.values(com.search_space[ind])) == [-3,1,2,3] + end + @test sort(CS.values(com.search_space[1])) == [0,1] + # feasible but remove -3 + @test CS.fix!(com, com.search_space[constr_indices[1]], 1) + @test CS.prune_constraint!(com, constraint, constraint.std.fct, constraint.std.set) + for ind in constr_indices[2:3] + @test sort(CS.values(com.search_space[ind])) == [1,2,3] + end + CS.values(com.search_space[constr_indices[1]]) == [1] +end \ No newline at end of file diff --git a/test/unit/index.jl b/test/unit/index.jl index f78d3e7f..35243d89 100644 --- a/test/unit/index.jl +++ b/test/unit/index.jl @@ -16,4 +16,5 @@ end include("constraints/not_equal.jl") include("constraints/svc.jl") include("constraints/table.jl") + include("constraints/indicator.jl") end \ No newline at end of file