forked from jump-dev/JuMP.jl
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[WIP] Handle
!=
constraint via MOI.AllDifferent(2)
``` (@v1.10) pkg> activate /repositories/JuMP.jl Activating project at `/repositories/JuMP.jl` julia> using JuMP Precompiling JuMP 1 dependency successfully precompiled in 10 seconds. 37 already precompiled. julia> model = Model(); julia> @variable(model, x[1:3]) 3-element Vector{VariableRef}: x[1] x[2] x[3] julia> @variable(model, y[1:3]) 3-element Vector{VariableRef}: y[1] y[2] y[3] julia> @constraint(model, x[1] != y[1]) x[1] != y[1] julia> @constraint(model, x .!= y) 3-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.VectorOfVariables, MathOptInterface.AllDifferent}, VectorShape}}: x[1] != y[1] x[2] != y[2] x[3] != y[3] julia> @constraint(model, x != y) ERROR: At REPL[8]:1: `@constraint(model, x != y)`: Ineqality operator with vector operands must be explicitly vectorized, use `.!=` instead of `!=`. Stacktrace: [1] error(::String, ::String) @ Base ./error.jl:44 [2] (::JuMP.Containers.var"#error_fn#98"{String})(str::String) @ JuMP.Containers ~/.julia/compiled/v1.10/JuMP/DmXqY_F8XkK.so:-1 [3] macro expansion @ /repositories/JuMP.jl/src/macros/@constraint.jl:132 [inlined] [4] macro expansion @ /repositories/JuMP.jl/src/macros.jl:393 [inlined] [5] top-level scope @ REPL[8]:1 ``` I'm not yet sure how to support the not-explicitly vectorized case. We'd need to somehow deduce (in `parse_constraint_call()`) that our arguments are vectors, and extend `parse_constraint_call()` to return `vectorized` itself. I'm not convinced this is even possible. Otherwise, we get ``` julia> @constraint(model, x != y) vectorized = false ERROR: MethodError: no method matching _build_inequality_constraint(::Bool, ::JuMP.Containers.var"#error_fn#98"{String}, ::Vector{VariableRef}, ::Vector{VariableRef}) Closest candidates are: _build_inequality_constraint(::Function, ::Bool, ::Vector{VariableRef}, ::Vector{VariableRef}) @ JuMP /repositories/JuMP.jl/src/inequality.jl:14 Stacktrace: [1] macro expansion @ /repositories/JuMP.jl/src/macros/@constraint.jl:132 [inlined] [2] macro expansion @ /repositories/JuMP.jl/src/macros.jl:393 [inlined] [3] top-level scope @ REPL[8]:1 ``` (because we should have called `@constraint.jl:123`) Missing tests, docs. As discussed in jump-dev/MathOptInterface.jl#2405
- Loading branch information
Showing
6 changed files
with
287 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this | ||
# file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
function _build_inequality_constraint( | ||
error_fn::Function, | ||
vectorized::Bool, | ||
lhs::VariableRef, | ||
rhs::VariableRef, | ||
) | ||
@assert !vectorized | ||
set = MOI.AllDifferent(2) | ||
return VectorConstraint([lhs; rhs], set) | ||
end | ||
|
||
function _build_inequality_constraint( | ||
error_fn::Function, | ||
vectorized::Bool, | ||
lhs::Vector{VariableRef}, | ||
rhs::Vector{VariableRef}, | ||
) | ||
if !vectorized | ||
error_fn( | ||
"Ineqality operator with vector operands must be explicitly " * | ||
"vectorized, use `.!=` instead of `!=`.", | ||
) | ||
end | ||
if length(lhs) != length(rhs) | ||
error_fn("Operand length mismatch, $(length(lhs)) vs $(length(rhs)).") | ||
end | ||
lhs = _desparsify(lhs) | ||
rhs = _desparsify(rhs) | ||
return _build_inequality_constraint.(error_fn, false, lhs, rhs) | ||
end | ||
|
||
function _build_inequality_constraint(error_fn::Function, ::Bool, lhs, rhs) | ||
return error_fn( | ||
"Unsupported form of inequality constraint. The left- and right-hand " * | ||
"sides must both be decision variables.", | ||
) | ||
end | ||
|
||
function parse_constraint_call( | ||
error_fn::Function, | ||
vectorized::Bool, | ||
::Val{:(!=)}, | ||
lhs, | ||
rhs, | ||
) | ||
build_call = Expr( | ||
:call, | ||
:_build_inequality_constraint, | ||
error_fn, | ||
vectorized, | ||
esc(lhs), | ||
esc(rhs), | ||
) | ||
return nothing, build_call | ||
end | ||
|
||
function constraint_string( | ||
print_mode, | ||
constraint::VectorConstraint{F,<:MOI.AllDifferent}, | ||
) where {F} | ||
set = constraint.set | ||
if set.dimension == 2 | ||
ineq_sym = JuMP._math_symbol(print_mode, :(!=)) | ||
lhs = function_string(print_mode, constraint.func[1]) | ||
rhs = function_string(print_mode, constraint.func[2]) | ||
return string(lhs, " $ineq_sym ", rhs) | ||
end | ||
|
||
# FIXME: can we just fallback to the generic handling here? | ||
ops = [function_string(print_mode, op) for op in constraint.func[1:end]] | ||
in_sym = JuMP._math_symbol(print_mode, :in) | ||
return string("[", join(ops, ", "), "] $in_sym $set") | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,199 @@ | ||
# Copyright 2017, Iain Dunning, Joey Huchette, Miles Lubin, and contributors | ||
# This Source Code Form is subject to the terms of the Mozilla Public | ||
# License, v. 2.0. If a copy of the MPL was not distributed with this | ||
# file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
||
module TestInequality | ||
|
||
using JuMP | ||
using Test | ||
|
||
include(joinpath(@__DIR__, "utilities.jl")) | ||
|
||
function test_inequality_two_int_scalars() | ||
model = Model() | ||
@variable(model, -4 <= x <= 4, Int) | ||
@variable(model, -4 <= y <= 4, Int) | ||
c = @constraint(model, x != y) | ||
ineq_sym = JuMP._math_symbol(MIME("text/plain"), :(!=)) | ||
set = MOI.AllDifferent(2) | ||
@test sprint(show, c) == "x $ineq_sym y" | ||
obj = constraint_object(c) | ||
@test obj.func == [x; y] | ||
@test obj.set == set | ||
return | ||
end | ||
|
||
function test_inequality_two_bin_scalars() | ||
model = Model() | ||
@variable(model, x, Bin) | ||
@variable(model, y, Bin) | ||
c = @constraint(model, x != y) | ||
ineq_sym = JuMP._math_symbol(MIME("text/plain"), :(!=)) | ||
set = MOI.AllDifferent(2) | ||
@test sprint(show, c) == "x $ineq_sym y" | ||
obj = constraint_object(c) | ||
@test obj.func == [x; y] | ||
@test obj.set == set | ||
return | ||
end | ||
|
||
# FIXME: should this fail? | ||
function test_inequality_two_scalars_only_one_being_int() | ||
model = Model() | ||
@variable(model, -4 <= x <= 4) | ||
@variable(model, -4 <= y <= 4, Int) | ||
c = @constraint(model, x != y) | ||
ineq_sym = JuMP._math_symbol(MIME("text/plain"), :(!=)) | ||
set = MOI.AllDifferent(2) | ||
@test sprint(show, c) == "x $ineq_sym y" | ||
obj = constraint_object(c) | ||
@test obj.func == [x; y] | ||
@test obj.set == set | ||
return | ||
end | ||
|
||
# FIXME: should this fail? | ||
function test_inequality_two_scalars_only_one_being_bin() | ||
model = Model() | ||
@variable(model, -4 <= x <= 4) | ||
@variable(model, y, Bin) | ||
c = @constraint(model, x != y) | ||
ineq_sym = JuMP._math_symbol(MIME("text/plain"), :(!=)) | ||
set = MOI.AllDifferent(2) | ||
@test sprint(show, c) == "x $ineq_sym y" | ||
obj = constraint_object(c) | ||
@test obj.func == [x; y] | ||
@test obj.set == set | ||
return | ||
end | ||
|
||
# FIXME: should this fail? | ||
function test_inequality_two_scalars_int_vs_bin() | ||
model = Model() | ||
@variable(model, -4 <= x <= 4, Int) | ||
@variable(model, y, Bin) | ||
c = @constraint(model, x != y) | ||
ineq_sym = JuMP._math_symbol(MIME("text/plain"), :(!=)) | ||
set = MOI.AllDifferent(2) | ||
@test sprint(show, c) == "x $ineq_sym y" | ||
obj = constraint_object(c) | ||
@test obj.func == [x; y] | ||
@test obj.set == set | ||
return | ||
end | ||
|
||
# FIXME: should this fail? | ||
function test_inequality_two_scalars_real_scalars() | ||
model = Model() | ||
@variable(model, -4 <= x <= 4) | ||
@variable(model, -4 <= y <= 4) | ||
c = @constraint(model, x != y) | ||
ineq_sym = JuMP._math_symbol(MIME("text/plain"), :(!=)) | ||
set = MOI.AllDifferent(2) | ||
@test sprint(show, c) == "x $ineq_sym y" | ||
obj = constraint_object(c) | ||
@test obj.func == [x; y] | ||
@test obj.set == set | ||
return | ||
end | ||
|
||
function test_inequality_two_vectors_vectorized() | ||
model = Model() | ||
@variable(model, -4 <= x[1:3] <= 4, Int) | ||
@variable(model, -4 <= y[1:3] <= 4, Int) | ||
c = @constraint(model, x .!= y) | ||
ineq_sym = JuMP._math_symbol(MIME("text/plain"), :(!=)) | ||
set = MOI.AllDifferent(2) | ||
for (i, ci) in enumerate(c) | ||
@test sprint(show, ci) == "x[$i] $ineq_sym y[$i]" | ||
obj = constraint_object(ci) | ||
@test obj.func == [x[i]; y[i]] | ||
@test obj.set == set | ||
end | ||
return | ||
end | ||
|
||
function test_inequality_two_vectors_nonvectorized() | ||
model = Model() | ||
@variable(model, -4 <= x[1:3] <= 4, Int) | ||
@variable(model, -4 <= y[1:3] <= 4, Int) | ||
@test_throws_runtime( | ||
ErrorException( | ||
"In `@constraint(model, x != y)`: Ineqality operator with " * | ||
"vector operands must be explicitly vectorized, " | ||
*"use `.!=` instead of `!=`.", | ||
), | ||
@constraint(model, x != y) | ||
) | ||
return | ||
end | ||
|
||
function test_inequality_two_vectors_nonvectorized_len_mismatch() | ||
model = Model() | ||
@variable(model, -4 <= x[1:3] <= 4, Int) | ||
@variable(model, -4 <= y[1:2] <= 4, Int) | ||
@test_throws_runtime( | ||
ErrorException( | ||
"In `@constraint(model, x != y)`: Ineqality operator with " * | ||
"vector operands must be explicitly vectorized, " | ||
*"use `.!=` instead of `!=`.", | ||
), | ||
@constraint(model, x != y) | ||
) | ||
return | ||
end | ||
|
||
function test_inequality_two_vectors_vectorized_len_mismatch() | ||
model = Model() | ||
@variable(model, -4 <= x[1:3] <= 4, Int) | ||
@variable(model, -4 <= y[1:2] <= 4, Int) | ||
@test_throws_runtime( | ||
ErrorException( | ||
"In `@constraint(model, x .!= y)`: " | ||
*"Operand length mismatch, 3 vs 2.", | ||
), | ||
@constraint(model, x .!= y) | ||
) | ||
return | ||
end | ||
|
||
function test_inequality_non_variables() | ||
model = Model() | ||
@variable(model, -4 <= x <= 4, Int) | ||
@test_throws_runtime( | ||
ErrorException( | ||
"In `@constraint(model, x != 0)`: Unsupported form of " * | ||
"inequality constraint. The left- and right-hand sides must both " * | ||
"be decision variables.", | ||
), | ||
@constraint(model, x != 0) | ||
) | ||
@test_throws_runtime( | ||
ErrorException( | ||
"In `@constraint(model, 0 != x)`: Unsupported form of " * | ||
"inequality constraint. The left- and right-hand sides must both " * | ||
"be decision variables.", | ||
), | ||
@constraint(model, 0 != x) | ||
) | ||
@test_throws_runtime( | ||
ErrorException( | ||
"In `@constraint(model, 2x != 0)`: Unsupported form of " * | ||
"inequality constraint. The left- and right-hand sides must both " * | ||
"be decision variables.", | ||
), | ||
@constraint(model, 2*x != 0) | ||
) | ||
@test_throws_runtime( | ||
ErrorException( | ||
"In `@constraint(model, x != 2x)`: Unsupported form of " * | ||
"inequality constraint. The left- and right-hand sides must both " * | ||
"be decision variables.", | ||
), | ||
@constraint(model, x != 2*x) | ||
) | ||
return | ||
end | ||
|
||
end # module |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters