From 4e0d00f310674defdaef30928747053ce06ffeca Mon Sep 17 00:00:00 2001 From: Roman Lebedev Date: Fri, 19 Jan 2024 04:21:33 +0300 Subject: [PATCH] [WIP] `parse_constraint_call()`: handle `Val{:(!=)}` 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 https://github.com/jump-dev/MathOptInterface.jl/issues/2405 --- src/JuMP.jl | 1 + src/inequality.jl | 57 +++++++++++++++++++++++++++++++++++++++ src/macros/@constraint.jl | 7 ++--- 3 files changed, 62 insertions(+), 3 deletions(-) create mode 100644 src/inequality.jl diff --git a/src/JuMP.jl b/src/JuMP.jl index cb13acbafbb..2f320f18035 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -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") diff --git a/src/inequality.jl b/src/inequality.jl new file mode 100644 index 00000000000..300b87584dc --- /dev/null +++ b/src/inequality.jl @@ -0,0 +1,57 @@ +# 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, +) + +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 diff --git a/src/macros/@constraint.jl b/src/macros/@constraint.jl index bc0feeb03a8..40df474e9f4 100644 --- a/src/macros/@constraint.jl +++ b/src/macros/@constraint.jl @@ -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 b`, where `` is one of `==`, `≥`, `>=`, `≤`, `<=` + * `a b`, where `` is one of `==`, `!=`, `≥`, `>=`, `≤`, `<=` * `l <= f <= u` or `u >= f >= l`, constraining the expression `f` to lie between `l` and `u` @@ -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` @@ -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 @@ -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(