Skip to content

Commit

Permalink
Merge pull request #1857 from JuliaOpt/bl/print_matrix
Browse files Browse the repository at this point in the history
Improve printing of SDP constraints
  • Loading branch information
blegat authored Feb 14, 2019
2 parents 6ee9ed0 + c9208dd commit a2376ca
Show file tree
Hide file tree
Showing 9 changed files with 205 additions and 59 deletions.
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
108 changes: 78 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 constraint 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,52 @@ 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)
lines = split(str, '\n')
# We drop the first line with the signature "m×n Array{...}:"
lines = lines[2:end]
# We replace the first space by an opening `[`
lines[1] = '[' * lines[1][2:end]
for i in 1:length(lines)
lines[i] = lines[i] * (i == length(lines) ? ']' : ';')
end
return join(lines, '\n')
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 +498,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 +528,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 +545,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

0 comments on commit a2376ca

Please sign in to comment.