Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve matrix inequality support #3778

Merged
merged 5 commits into from
Aug 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions docs/src/manual/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ julia> b = [5, 6]
6

julia> @constraint(model, con_vector, A * x == b)
con_vector : [x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Zeros(2)
con_vector : [x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Zeros()

julia> @constraint(model, con_scalar, A * x .== b)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.EqualTo{Float64}}, ScalarShape}}:
Expand Down Expand Up @@ -148,15 +148,15 @@ constraint.

```jldoctest con_vector
julia> @constraint(model, A * x <= b)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonpositives(2)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Nonpositives()

julia> @constraint(model, A * x .<= b)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.LessThan{Float64}}, ScalarShape}}:
x[1] + 2 x[2] ≤ 5
3 x[1] + 4 x[2] ≤ 6

julia> @constraint(model, A * x >= b)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonnegatives(2)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Nonnegatives()

julia> @constraint(model, A * x .>= b)
2-element Vector{ConstraintRef{Model, MathOptInterface.ConstraintIndex{MathOptInterface.ScalarAffineFunction{Float64}, MathOptInterface.GreaterThan{Float64}}, ScalarShape}}:
Expand All @@ -169,18 +169,37 @@ julia> @constraint(model, A * x .>= b)
Inequalities between matrices are not supported, due to the common ambiguity
between elementwise inequalities and a [`PSDCone`](@ref) constraint.

```jldoctest symmetric_matrix
````jldoctest symmetric_matrix
odow marked this conversation as resolved.
Show resolved Hide resolved
julia> model = Model();

julia> @variable(model, x[1:2, 1:2], Symmetric);

julia> @variable(model, y[1:2, 1:2], Symmetric);

julia> @constraint(model, x >= y)
ERROR: At none:1: `@constraint(model, x >= y)`: Unsupported matrix in vector-valued set. Did you mean to use the broadcasting syntax `.>=` instead? Alternatively, perhaps you are missing a set argument like `@constraint(model, X >= 0, PSDCone())` or `@constraint(model, X >= 0, HermitianPSDCone())`.
ERROR: At none:1: `@constraint(model, x >= y)`:
The syntax `x >= y` is ambiguous for matrices because we cannot tell if
you intend a positive semidefinite constraint or an elementwise
inequality.

To create a positive semidefinite constraint, pass `PSDCone()` or
`HermitianPSDCone()`:

```julia
@constraint(model, x >= y, PSDCone())
```

To create an element-wise inequality, pass `Nonnegatives()`, or use
broadcasting:

```julia
@constraint(model, x >= y, Nonnegatives())
# or
@constraint(model, x .>= y)
```
Stacktrace:
[...]
```
````
odow marked this conversation as resolved.
Show resolved Hide resolved

Instead, use the [Set inequality syntax](@ref) to specify a set like
[`PSDCone`](@ref) or [`Nonnegatives`](@ref):
Expand Down Expand Up @@ -1257,7 +1276,7 @@ julia> @constraint(model, x in MOI.ExponentialCone())
## Set inequality syntax

For modeling convenience, the syntax `@constraint(model, x >= y, Set())` is
short-hand for `@constraint(model, x - y in Set())`.
short-hand for `@constraint(model, x - y in Set())`.

Therefore, the following calls are equivalent:
```jldoctest set_inequality
Expand Down
14 changes: 7 additions & 7 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ julia> model = Model();
julia> @variable(model, x, start = 2.0);

julia> @constraint(model, c, [2x] in Nonnegatives())
c : [2 x] ∈ MathOptInterface.Nonnegatives(1)
c : [2 x] ∈ Nonnegatives()

julia> set_dual_start_value(c, [0.0])

Expand Down Expand Up @@ -174,7 +174,7 @@ julia> model = Model();
julia> @variable(model, x, start = 2.0);

julia> @constraint(model, c, [2x] in Nonnegatives())
c : [2 x] ∈ MathOptInterface.Nonnegatives(1)
c : [2 x] ∈ Nonnegatives()

julia> set_dual_start_value(c, [0.0])

Expand Down Expand Up @@ -253,7 +253,7 @@ julia> model = Model();
julia> @variable(model, x, start = 2.0);

julia> @constraint(model, c, [2x] in Nonnegatives())
c : [2 x] ∈ MathOptInterface.Nonnegatives(1)
c : [2 x] ∈ Nonnegatives()

julia> set_start_value(c, [4.0])

Expand Down Expand Up @@ -333,7 +333,7 @@ julia> model = Model();
julia> @variable(model, x, start = 2.0);

julia> @constraint(model, c, [2x] in Nonnegatives())
c : [2 x] ∈ MathOptInterface.Nonnegatives(1)
c : [2 x] ∈ Nonnegatives()

julia> set_start_value(c, [4.0])

Expand Down Expand Up @@ -368,7 +368,7 @@ julia> model = Model();
julia> @variable(model, x);

julia> @constraint(model, c, [2x] in Nonnegatives())
c : [2 x] ∈ MathOptInterface.Nonnegatives(1)
c : [2 x] ∈ Nonnegatives()

julia> name(c)
"c"
Expand Down Expand Up @@ -404,15 +404,15 @@ julia> model = Model();
julia> @variable(model, x);

julia> @constraint(model, c, [2x] in Nonnegatives())
c : [2 x] ∈ MathOptInterface.Nonnegatives(1)
c : [2 x] ∈ Nonnegatives()

julia> set_name(c, "my_constraint")

julia> name(c)
"my_constraint"

julia> c
my_constraint : [2 x] ∈ MathOptInterface.Nonnegatives(1)
my_constraint : [2 x] ∈ Nonnegatives()
```
"""
function set_name(
Expand Down
138 changes: 130 additions & 8 deletions src/macros/@constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -587,19 +587,40 @@ julia> @variable(model, x[1:2])
x[2]

julia> @constraint(model, x in Nonnegatives())
[x[1], x[2]] ∈ MathOptInterface.Nonnegatives(2)
[x[1], x[2]] ∈ Nonnegatives()

julia> A = [1 2; 3 4];

julia> b = [5, 6];

julia> @constraint(model, A * x >= b)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonnegatives(2)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Nonnegatives()
```
"""
struct Nonnegatives end

operator_to_set(::Function, ::Union{Val{:(>=)},Val{:(≥)}}) = Nonnegatives()
"""
GreaterThanZero()

A struct used to intercept when `>=` or `≥` is used in a macro via
[`operator_to_set`](@ref).

This struct is not the same as [`Nonnegatives`](@ref) so that we can disambiguate
`x >= y` and `x - y in Nonnegatives()`.

This struct is not intended for general usage, but it may be useful to some
JuMP extensions.

## Example

```jldoctest
julia> operator_to_set(error, Val(:>=))
GreaterThanZero()
```
"""
struct GreaterThanZero end

operator_to_set(::Function, ::Union{Val{:(>=)},Val{:(≥)}}) = GreaterThanZero()

"""
Nonpositives()
Expand All @@ -618,19 +639,40 @@ julia> @variable(model, x[1:2])
x[2]

julia> @constraint(model, x in Nonpositives())
[x[1], x[2]] ∈ MathOptInterface.Nonpositives(2)
[x[1], x[2]] ∈ Nonpositives()

julia> A = [1 2; 3 4];

julia> b = [5, 6];

julia> @constraint(model, A * x <= b)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Nonpositives(2)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Nonpositives()
```
"""
struct Nonpositives end

operator_to_set(::Function, ::Union{Val{:(<=)},Val{:(≤)}}) = Nonpositives()
"""
GreaterThanZero()

A struct used to intercept when `<=` or `≤` is used in a macro via
[`operator_to_set`](@ref).

This struct is not the same as [`Nonpositives`](@ref) so that we can disambiguate
`x <= y` and `x - y in Nonpositives()`.

This struct is not intended for general usage, but it may be useful to some
JuMP extensions.

## Example

```jldoctest
julia> operator_to_set(error, Val(:<=))
LessThanZero()
```
"""
struct LessThanZero end

operator_to_set(::Function, ::Union{Val{:(<=)},Val{:(≤)}}) = LessThanZero()

"""
Zeros()
Expand All @@ -649,14 +691,14 @@ julia> @variable(model, x[1:2])
x[2]

julia> @constraint(model, x in Zeros())
[x[1], x[2]] ∈ MathOptInterface.Zeros(2)
[x[1], x[2]] ∈ Zeros()

julia> A = [1 2; 3 4];

julia> b = [5, 6];

julia> @constraint(model, A * x == b)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ MathOptInterface.Zeros(2)
[x[1] + 2 x[2] - 5, 3 x[1] + 4 x[2] - 6] ∈ Zeros()
```
"""
struct Zeros end
Expand Down Expand Up @@ -792,6 +834,86 @@ function parse_constraint_call(
return parse_code, build_call
end

function build_constraint(
error_fn::Function,
f,
::GreaterThanZero,
args...;
kwargs...,
)
return build_constraint(error_fn, f, Nonnegatives(), args...; kwargs...)
end

function build_constraint(
error_fn::Function,
::Union{Matrix,LinearAlgebra.Symmetric,LinearAlgebra.Hermitian},
::GreaterThanZero,
)
return error_fn(
"""

The syntax `x >= y` is ambiguous for matrices because we cannot tell if
you intend a positive semidefinite constraint or an elementwise
inequality.

To create a positive semidefinite constraint, pass `PSDCone()` or
`HermitianPSDCone()`:

```julia
@constraint(model, x >= y, PSDCone())
```

To create an element-wise inequality, pass `Nonnegatives()`, or use
broadcasting:

```julia
@constraint(model, x >= y, Nonnegatives())
# or
@constraint(model, x .>= y)
```""",
)
end

function build_constraint(
error_fn::Function,
f,
::LessThanZero,
args...;
kwargs...,
)
return build_constraint(error_fn, f, Nonpositives(), args...; kwargs...)
end

function build_constraint(
error_fn::Function,
::Union{Matrix,LinearAlgebra.Symmetric,LinearAlgebra.Hermitian},
::LessThanZero,
)
return error_fn(
"""

The syntax `x <= y` is ambiguous for matrices because we cannot tell if
you intend a positive semidefinite constraint or an elementwise
inequality.

To create a positive semidefinite constraint, reverse the sense of the
inequality and pass `PSDCone()` or `HermitianPSDCone()`:

```julia
@constraint(model, y >= x, PSDCone())
```

To create an element-wise inequality, reverse the sense of the
inequality and pass `Nonnegatives()`, or use broadcasting:

```julia
@constraint(model, y >= x, Nonnegatives())
# or
@constraint(model, x .<= y)
```""",
)
end

function build_constraint(
error_fn::Function,
f,
Expand Down
Loading
Loading