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 printing of SDP constraints #1857

Merged
merged 7 commits into from
Feb 14, 2019
Merged
Show file tree
Hide file tree
Changes from 4 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
20 changes: 16 additions & 4 deletions docs/src/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,8 @@ julia> @variable(model, x)
x

julia> @SDconstraint(model, [x 2x; 3x 4x] >= ones(2, 2))
[x - 1, 3 x - 1, 2 x - 1, 4 x - 1] ∈ MathOptInterface.PositiveSemidefiniteConeSquare(2)
x - 1 2 x - 1
3 x - 1 4 x - 1 ∈ PSDCone()
```

Solvers supporting such constraints usually expect to be given a matrix that
Expand All @@ -518,14 +519,25 @@ follows:
julia> using LinearAlgebra

julia> @constraint(model, Symmetric([x 2x; 2x 4x] - ones(2, 2)) in PSDCone())
[x - 1, 2 x - 1, 4 x - 1] ∈ MathOptInterface.PositiveSemidefiniteConeTriangle(2)
x - 1 2 x - 1
2 x - 1 4 x - 1 ∈ PSDCone()
```

Note that the lower triangular entries are silently ignored even if they are
different so use it with caution:
```jldoctest con_psd
julia> @constraint(model, Symmetric([x 2x; 3x 4x]) in PSDCone())
[x, 2 x, 4 x] ∈ MathOptInterface.PositiveSemidefiniteConeTriangle(2)
julia> cref = @constraint(model, Symmetric([x 2x; 3x 4x]) in PSDCone())
x 2 x
2 x 4 x ∈ PSDCone()

julia> jump_function(constraint_object(cref))
3-element Array{GenericAffExpr{Float64,VariableRef},1}:
x
2 x
4 x

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeTriangle(2)
```

## Constraint modifications
Expand Down
3 changes: 2 additions & 1 deletion docs/src/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ used to reshape the result computed in [`value`](@ref) and [`dual`](@ref).
```@docs
AbstractShape
shape
reshape_result
reshape_vector
reshape_set
dual_shape
ScalarShape
VectorShape
Expand Down
6 changes: 4 additions & 2 deletions src/constraints.jl
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@ end

jump_function(constraint::ScalarConstraint) = constraint.func
moi_set(constraint::ScalarConstraint) = constraint.set
reshape_set(set::MOI.AbstractScalarSet, ::ScalarShape) = set
shape(::ScalarConstraint) = ScalarShape()

function constraint_object(ref::ConstraintRef{Model, _MOICON{FuncType, SetType}}) where
Expand Down Expand Up @@ -342,6 +343,7 @@ end

jump_function(constraint::VectorConstraint) = constraint.func
moi_set(constraint::VectorConstraint) = constraint.set
reshape_set(set::MOI.AbstractVectorSet, ::VectorShape) = set
shape(c::VectorConstraint) = c.shape
function constraint_object(ref::ConstraintRef{Model, _MOICON{FuncType, SetType}}) where
{FuncType <: MOI.AbstractVectorFunction, SetType <: MOI.AbstractVectorSet}
Expand Down Expand Up @@ -442,7 +444,7 @@ evaluation of `2x + 3y`.
```
"""
function value(cref::ConstraintRef{Model, <:_MOICON})
return reshape_result(_constraint_primal(cref), cref.shape)
return reshape_vector(_constraint_primal(cref), cref.shape)
end

# Returns the value of MOI.ConstraintPrimal in a type-stable way
Expand Down Expand Up @@ -475,7 +477,7 @@ Use `has_dual` to check if a result exists before asking for values.
See also [`shadow_price`](@ref).
"""
function dual(cref::ConstraintRef{Model, <:_MOICON})
return reshape_result(_constraint_dual(cref), dual_shape(cref.shape))
return reshape_vector(_constraint_dual(cref), dual_shape(cref.shape))
end

# Returns the value of MOI.ConstraintPrimal in a type-stable way
Expand Down
15 changes: 13 additions & 2 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -759,8 +759,19 @@ julia> a = [x 2x
julia> b = [1 2
3 4];

julia> @SDconstraint(model, a ⪰ b)
[x - 1, -3, 2 x - 2, x - 4] ∈ MathOptInterface.PositiveSemidefiniteConeSquare(2)
julia> cref = @SDconstraint(model, a ⪰ b)
x - 1 2 x - 2
-3 x - 4 ∈ PSDCone()

julia> jump_function(constraint_object(cref))
4-element Array{GenericAffExpr{Float64,VariableRef},1}:
x - 1
-3
2 x - 2
x - 4

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeSquare(2)
```
In the set `PositiveSemidefiniteConeSquare(2)` in the last output, `Square`
means that the matrix is passed as a square matrix as the corresponding
Expand Down
101 changes: 71 additions & 30 deletions src/print.jl
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,17 @@ function model_string(print_mode, model::AbstractModel)
end
str *= eol
str *= ijl ? "\\text{Subject to} \\quad" : "Subject to" * eol
str *= constraints_string(print_mode, model, sep, eol)
constraints = constraints_string(print_mode, model)
if print_mode == REPLMode
constraints = map(str -> replace(str, '\n' => eol * sep), constraints)
end
if !isempty(constraints)
str *= sep
end
str *= join(constraints, eol * sep)
if !isempty(constraints)
str *= eol
end
if ijl
str = "\\begin{alignat*}{1}" * str * "\\end{alignat*}\n"
end
Expand Down Expand Up @@ -259,12 +269,7 @@ end
#------------------------------------------------------------------------
## VariableRef
#------------------------------------------------------------------------
function Base.show(io::IO, v::AbstractVariableRef)
print(io, function_string(REPLMode, v))
end
function Base.show(io::IO, ::MIME"text/latex", v::AbstractVariableRef)
print(io, _wrap_in_math_mode(function_string(IJuliaMode, v)))
end

function function_string(::Type{REPLMode}, v::AbstractVariableRef)
var_name = name(v)
if !isempty(var_name)
Expand All @@ -283,10 +288,9 @@ function function_string(::Type{IJuliaMode}, v::AbstractVariableRef)
end
end

Base.show(io::IO, a::GenericAffExpr) = print(io, function_string(REPLMode, a))
function Base.show(io::IO, ::MIME"text/latex", a::GenericAffExpr)
print(io, _wrap_in_math_mode(function_string(IJuliaMode, a)))
end
#------------------------------------------------------------------------
## GenericAffExpr
#------------------------------------------------------------------------

function function_string(mode, a::GenericAffExpr, show_constant=true)
# If the expression is empty, return the constant (or 0)
Expand Down Expand Up @@ -326,10 +330,6 @@ end
#------------------------------------------------------------------------
## GenericQuadExpr
#------------------------------------------------------------------------
Base.show(io::IO, q::GenericQuadExpr) = print(io, function_string(REPLMode, q))
function Base.show(io::IO, ::MIME"text/latex", q::GenericQuadExpr)
print(io, _wrap_in_math_mode(function_string(IJuliaMode, q)))
end

function function_string(mode, q::GenericQuadExpr)
length(quad_terms(q)) == 0 && return function_string(mode, q.aff)
Expand Down Expand Up @@ -398,26 +398,25 @@ function show_constraints_summary(io::IO, model::Model)
end

"""
constraints_string(print_mode, model::AbstractModel, sep, eol)::String
constraints_string(print_mode, model::AbstractModel)::Vector{String}

Return a `String` describing the constraints of the model, each on a line
starting with `sep` and ending with `eol` (which already contains `\n`).
Return a list of `String`s describing each constraints of the model.
"""
function constraints_string(print_mode, model::Model, sep, eol)
str = ""
function constraints_string(print_mode, model::Model)
strings = String[]
for (F, S) in list_of_constraint_types(model)
for cref in all_constraints(model, F, S)
con = constraint_object(cref)
str *= sep * constraint_string(print_mode, con) * eol
push!(strings, constraint_string(print_mode, con))
end
end
if model.nlp_data !== nothing
for nl_constraint in model.nlp_data.nlconstr
str *= sep * nl_constraint_string(model, print_mode, nl_constraint)
str *= eol
push!(strings,
nl_constraint_string(model, print_mode, nl_constraint))
end
end
return str
return strings
end

## Notes for extensions
Expand Down Expand Up @@ -445,10 +444,45 @@ Return a `String` representing the function `func` using print mode
"""
function function_string end

function Base.show(io::IO, f::AbstractJuMPScalar)
print(io, function_string(REPLMode, f))
end
function Base.show(io::IO, ::MIME"text/latex", f::AbstractJuMPScalar)
print(io, _wrap_in_math_mode(function_string(IJuliaMode, f)))
end

function function_string(print_mode, vector::Vector{<:AbstractJuMPScalar})
return "[" * join(function_string.(print_mode, vector), ", ") * "]"
end

function function_string(::Type{REPLMode},
A::AbstractMatrix{<:AbstractJuMPScalar})
str = sprint(show, MIME"text/plain"(), A)
# We drop the first line with the signature "m×n Array{...}:"
return str[(findfirst(isequal('\n'), str) + 1):end]
end

function function_string(print_mode::Type{IJuliaMode},
A::AbstractMatrix{<:AbstractJuMPScalar})
str = sprint(show, MIME"text/plain"(), A)
str = "\\begin{bmatrix}\n"
for i in 1:size(A, 1)
line = ""
for j in 1:size(A, 2)
if j != 1
line *= " & "
end
if A isa Symmetric && i > j
line *= "\\cdot"
else
line *= function_string(print_mode, A[i, j])
end
end
str *= line * "\\\\\n"
end
return str * "\\end{bmatrix}"
end

"""
function_string(print_mode::{<:JuMP.PrintMode},
constraint::JuMP.AbstractConstraint)
Expand All @@ -457,7 +491,8 @@ Return a `String` representing the function of the constraint `constraint`
using print mode `print_mode`.
"""
function function_string(print_mode, constraint::AbstractConstraint)
return function_string(print_mode, jump_function(constraint))
f = reshape_vector(jump_function(constraint), shape(constraint))
return function_string(print_mode, f)
end

function in_set_string(print_mode, set::MOI.LessThan)
Expand Down Expand Up @@ -486,13 +521,12 @@ in_set_string(print_mode, ::MOI.Integer) = "integer"
# regular text in math mode which looks a bit awkward.
"""
in_set_string(print_mode::Type{<:JuMP.PrintMode},
set::Union{JuMP.AbstractJuMPScalar,
Vector{<:JuMP.AbstractJuMPScalar}})
set::Union{PSDCone, MOI.AbstractSet})

Return a `String` representing the membership to the set `set` using print mode
`print_mode`.
"""
function in_set_string(print_mode, set::MOI.AbstractSet)
function in_set_string(print_mode, set::Union{PSDCone, MOI.AbstractSet})
return string(_math_symbol(print_mode, :in), " ", set)
end

Expand All @@ -504,13 +538,20 @@ Return a `String` representing the membership to the set of the constraint
`constraint` using print mode `print_mode`.
"""
function in_set_string(print_mode, constraint::AbstractConstraint)
return in_set_string(print_mode, moi_set(constraint))
set = reshape_set(moi_set(constraint), shape(constraint))
return in_set_string(print_mode, set)
end

function constraint_string(print_mode, constraint_object::AbstractConstraint)
func_str = function_string(print_mode, constraint_object)
in_set_str = in_set_string(print_mode, constraint_object)
return func_str * " " * in_set_str
if print_mode == REPLMode
lines = split(func_str, '\n')
lines[1 + div(length(lines), 2)] *= " " * in_set_str
return join(lines, '\n')
else
return func_str * " " * in_set_str
end
end
function constraint_string(print_mode, constraint_name,
constraint_object::AbstractConstraint)
Expand Down
40 changes: 34 additions & 6 deletions src/sd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,38 @@ julia> a = [ x 2x
julia> b = [1 2
2 4];

julia> @SDconstraint(model, a ⪰ b)
[x - 1, 2 x - 2, 2 x - 2, x - 4] ∈ MathOptInterface.PositiveSemidefiniteConeSquare(2)
julia> cref = @SDconstraint(model, a ⪰ b)
x - 1 2 x - 2
2 x - 2 x - 4 ∈ PSDCone()

julia> jump_function(constraint_object(cref))
4-element Array{GenericAffExpr{Float64,VariableRef},1}:
x - 1
2 x - 2
2 x - 2
x - 4

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeSquare(2)
```
We see in the output of the last command that the matrix the vectorization of the
matrix is constrained to belong to the `PositiveSemidefiniteConeSquare`.

```jldoctest PSDCone
julia> using LinearAlgebra # For Symmetric

julia> @constraint(model, Symmetric(a - b) in PSDCone())
[x - 1, 2 x - 2, x - 4] ∈ MathOptInterface.PositiveSemidefiniteConeTriangle(2)
julia> cref = @constraint(model, Symmetric(a - b) in PSDCone())
x - 1 2 x - 2
2 x - 2 x - 4 ∈ PSDCone()

julia> jump_function(constraint_object(cref))
3-element Array{GenericAffExpr{Float64,VariableRef},1}:
x - 1
2 x - 2
x - 4

julia> moi_set(constraint_object(cref))
MathOptInterface.PositiveSemidefiniteConeTriangle(2)
```
As we see in the output of the last command, the vectorization of only the upper
triangular part of the matrix is constrained to belong to the
Expand All @@ -53,7 +74,7 @@ lower-left triangular part given row by row).
struct SymmetricMatrixShape <: AbstractShape
side_dimension::Int
end
function reshape_result(vectorized_form::Vector{T}, shape::SymmetricMatrixShape) where T
function reshape_vector(vectorized_form::Vector{T}, shape::SymmetricMatrixShape) where T
matrix = Matrix{T}(undef, shape.side_dimension, shape.side_dimension)
k = 0
for j in 1:shape.side_dimension
Expand All @@ -64,6 +85,10 @@ function reshape_result(vectorized_form::Vector{T}, shape::SymmetricMatrixShape)
end
return Symmetric(matrix)
end
function reshape_set(::MOI.PositiveSemidefiniteConeTriangle,
::SymmetricMatrixShape)
return PSDCone()
end

"""
SquareMatrixShape
Expand All @@ -76,9 +101,12 @@ row).
struct SquareMatrixShape <: AbstractShape
side_dimension::Int
end
function reshape_result(vectorized_form::Vector{T}, shape::SquareMatrixShape) where T
function reshape_vector(vectorized_form::Vector{T}, shape::SquareMatrixShape) where T
return reshape(vectorized_form, shape.side_dimension, shape.side_dimension)
end
function reshape_set(::MOI.PositiveSemidefiniteConeSquare, ::SquareMatrixShape)
return PSDCone()
end

"""
function build_constraint(_error::Function, Q::Symmetric{V, M},
Expand Down
Loading