diff --git a/docs/src/manual/constraints.md b/docs/src/manual/constraints.md index c2a4b29cac3..4eb08ee188e 100644 --- a/docs/src/manual/constraints.md +++ b/docs/src/manual/constraints.md @@ -202,12 +202,12 @@ julia> @variable(model, X[1:2, 1:2], Symmetric) julia> @constraint(model, X == LinearAlgebra.I) [X[1,1] - 1 X[1,2] - X[1,2] X[2,2] - 1] ∈ Zeros() + ⋯ X[2,2] - 1] ∈ Zeros() ``` -Despite the model showing the matrix in [`Zeros`](@ref), this will add only -three rows to the constraint matrix because the symmetric constraints are -redundant. In contrast, the broadcasting syntax adds four linear constraints: +This will add only three rows to the constraint matrix because the symmetric +constraints are redundant. In contrast, the broadcasting syntax adds four linear +constraints: ```jldoctest con_symmetric_zeros julia> @constraint(model, X .== LinearAlgebra.I) @@ -1462,7 +1462,7 @@ julia> Z = [X[1, 1] X[1, 2]; X[1, 2] X[2, 2]] julia> @constraint(model, LinearAlgebra.Symmetric(Z) >= 0, PSDCone()) [X[1,1] X[1,2] - X[1,2] X[2,2]] ∈ PSDCone() + ⋯ X[2,2]] ∈ PSDCone() ``` Note that the lower triangular entries are ignored even if they are @@ -1470,9 +1470,9 @@ different so use it with caution: ```jldoctest con_psd julia> @constraint(model, LinearAlgebra.Symmetric(X) >= 0, PSDCone()) [X[1,1] X[1,2] - X[1,2] X[2,2]] ∈ PSDCone() + ⋯ X[2,2]] ∈ PSDCone() ``` -(Note the `(2, 1)` element of the constraint is `X[1,2]`, not `X[2,1]`.) +(Note that no error is thrown, even though `X` is not symmetric.) ## Complementarity constraints diff --git a/src/print.jl b/src/print.jl index 4e14e51230c..aa088d9198d 100644 --- a/src/print.jl +++ b/src/print.jl @@ -908,7 +908,9 @@ function function_string( line *= " & " end if A isa LinearAlgebra.Symmetric && i > j - line *= "\\cdot" + line *= "\\cdots" + elseif A isa LinearAlgebra.UpperTriangular && i > j + line *= "\\cdots" else line *= function_string(mode, A[i, j]) end @@ -923,6 +925,20 @@ function function_string(mode, constraint::AbstractConstraint) return function_string(mode, f) end +# A special case for symmetric matrix constraints. Since the shape is +# SymmetricMatrixShape, we know that MOI has been passed the upper triangle of +# the matrix. We can make this clearer to users by printing the +# LinearAlgebra.UpperTriangular. There shouldn't be any cases in which the +# constraint function becomes ambiguous. +function function_string( + mode, + constraint::VectorConstraint{F,S,SymmetricMatrixShape}, +) where {F,S} + f = reshape_vector(jump_function(constraint), shape(constraint)) + str = function_string(mode, LinearAlgebra.UpperTriangular(f)) + return replace(str, "⋅" => "⋯") +end + function function_string(mode::MIME, p::NonlinearExpression) nlp = nonlinear_model(p.model)::MOI.Nonlinear.Model expr = nlp[index(p)] diff --git a/src/sd.jl b/src/sd.jl index 42ca64e961b..5457884faf3 100644 --- a/src/sd.jl +++ b/src/sd.jl @@ -125,8 +125,8 @@ julia> a = [x 2x; 2x x]; julia> b = [1 2; 2 4]; julia> cref = @constraint(model, Symmetric(a - b) in PSDCone()) -[x - 1 2 x - 2 - 2 x - 2 x - 4] ∈ PSDCone() +[x - 1 2 x - 2 + ⋯ x - 4] ∈ PSDCone() julia> jump_function(constraint_object(cref)) 3-element Vector{AffExpr}: diff --git a/test/test_print.jl b/test/test_print.jl index c7730f6eccc..66d0ff34fe0 100644 --- a/test/test_print.jl +++ b/test/test_print.jl @@ -605,7 +605,7 @@ Subject to con : a + b - 10 c + c1 - 2 x $le 1 a*b $le 2 [a b - b x] $inset $(PSDCone()) + ⋯ x] $inset $(PSDCone()) [a, b, c] $inset $(MOI.PositiveSemidefiniteConeTriangle(2)) [a b c x] $inset $(PSDCone()) @@ -666,7 +666,7 @@ Names registered in the model: a, a1, b, b1, c, c1, con, fi, soc, u, x, y, z"""; " & a\\times b \\leq 2\\\\\n" * " & \\begin{bmatrix}\n" * "a & b\\\\\n" * - "\\cdot & x\\\\\n" * + "\\cdots & x\\\\\n" * "\\end{bmatrix} \\in \\text{$(PSDCone())}\\\\\n" * " & [a, b, c] \\in \\text{MathOptInterface.PositiveSemidefiniteConeTriangle(2)}\\\\\n" * " & \\begin{bmatrix}\n" * @@ -1094,4 +1094,20 @@ function test_invalid_references() return end +function test_symmetric_constraint() + model = Model() + @variable(model, x[1:2, 1:2], Symmetric) + @test function_string(MIME("text/plain"), x) == + "[x[1,1] x[1,2]\n x[1,2] x[2,2]]" + @test function_string(MIME("text/latex"), x) == + "\\begin{bmatrix}\nx_{1,1} & x_{1,2}\\\\\n\\cdots & x_{2,2}\\\\\n\\end{bmatrix}" + c = @constraint(model, x in PSDCone()) + o = constraint_object(c) + @test function_string(MIME("text/plain"), o) == + "[x[1,1] x[1,2]\n ⋯ x[2,2]]" + @test function_string(MIME("text/latex"), o) == + "\\begin{bmatrix}\nx_{1,1} & x_{1,2}\\\\\n\\cdots & x_{2,2}\\\\\n\\end{bmatrix}" + return +end + end # TestPrint