Skip to content

Commit

Permalink
[WIP] parse_constraint_call(): handle Val{:(!=)} via `MOI.AllDiff…
Browse files Browse the repository at this point in the history
…erent(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
LebedevRI committed Jan 19, 2024
1 parent a55edea commit c2528a8
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/JuMP.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1103,6 +1103,7 @@ include("operators.jl")
include("sd.jl")
include("sets.jl")
include("solution_summary.jl")
include("inequality.jl")

# print.jl must come last, because it uses types defined in earlier files.
include("print.jl")
Expand Down
46 changes: 46 additions & 0 deletions src/inequality.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# 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 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}
lhs = function_string(print_mode, constraint.func[1])
rhs = function_string(print_mode, constraint.func[2])
return string(lhs, " != ", rhs)
end
7 changes: 4 additions & 3 deletions src/macros/@constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ The expression `expr` may be one of following forms:
which is either a [`MOI.AbstractSet`](@ref) or one of the JuMP shortcuts like
[`SecondOrderCone`](@ref) or [`PSDCone`](@ref)
* `a <op> b`, where `<op>` is one of `==`, `≥`, `>=`, `≤`, `<=`
* `a <op> b`, where `<op>` is one of `==`, `!=`, `≥`, `>=`, `≤`, `<=`
* `l <= f <= u` or `u >= f >= l`, constraining the expression `f` to lie
between `l` and `u`
Expand Down Expand Up @@ -233,6 +233,7 @@ The entry-point for all constraint-related parsing.
JuMP currently supports the following `expr` objects:
* `lhs <= rhs`
* `lhs == rhs`
* `lhs != rhs`
* `lhs >= rhs`
* `l <= body <= u`
* `u >= body >= l`
Expand All @@ -259,7 +260,7 @@ end
function parse_constraint(error_fn::Function, arg)
return error_fn(
"Incomplete constraint specification $arg. Are you missing a " *
"comparison (<=, >=, or ==)?",
"comparison (<=, >=, == or !=)?",
)
end

Expand Down Expand Up @@ -591,7 +592,7 @@ julia> @constraint(model, A * x == b)
"""
struct Zeros end

operator_to_set(::Function, ::Val{:(==)}) = Zeros()
operator_to_set(::Function, ::Union{Val{:(==)},Val{:(!=)}}) = Zeros()

"""
parse_constraint_call(
Expand Down

0 comments on commit c2528a8

Please sign in to comment.