Skip to content

Commit

Permalink
SDP constraint (#1122)
Browse files Browse the repository at this point in the history
* SDP constraint

* 1 -> one(T)

* Move tests from sdp to constraint

* Move fill_expr! to affexpr

* Stop using Nullable

* Remove symmetry constraints

* Add constraintobject test

* Add comment on future support for non-symmetric matrices

* eachscalar update
  • Loading branch information
blegat authored and mlubin committed Nov 18, 2017
1 parent 534ae34 commit 9cfe927
Show file tree
Hide file tree
Showing 5 changed files with 621 additions and 133 deletions.
7 changes: 7 additions & 0 deletions src/affexpr.jl
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,10 @@ function constraintobject(cref::ConstraintRef{Model}, ::Type{AffExpr}, ::Type{Se
s = MOI.get(m.instance, MOI.ConstraintSet(), cref.instanceref)::SetType
return AffExprConstraint(AffExpr(m, f), s)
end

function constraintobject(cref::ConstraintRef{Model}, ::Type{Vector{AffExpr}}, ::Type{SetType}) where {SetType <: MOI.AbstractVectorSet}
m = cref.m
f = MOI.get(m.instance, MOI.ConstraintFunction(), cref.instanceref)::MOI.VectorAffineFunction
s = MOI.get(m.instance, MOI.ConstraintSet(), cref.instanceref)::SetType
return VectorAffExprConstraint(map(f -> AffExpr(m, f), MOIU.eachscalar(f)), s)
end
82 changes: 40 additions & 42 deletions src/macros.jl
Original file line number Diff line number Diff line change
Expand Up @@ -323,8 +323,6 @@ end

constructconstraint!(q::QuadExpr, lb, ub) = error("Two-sided quadratic constraints not supported. (Try @NLconstraint instead.)")

# constructconstraint!(x::AbstractMatrix, ::PSDCone) = SDConstraint(x)

constraint_error(args, str) = error("In @constraint($(join(args,","))): ", str)

"""
Expand Down Expand Up @@ -479,46 +477,46 @@ macro constraint(args...)
end


# """
# @SDconstraint(m, x)
#
# Adds a semidefinite constraint to the `Model m`. The expression `x` must be a square, two-dimensional array.
# """
# macro SDconstraint(m, x)
# m = esc(m)
#
# if isa(x, Symbol)
# error("in @SDconstraint: Incomplete constraint specification $x. Are you missing a comparison (<= or >=)?")
# end
#
# (x.head == :block) &&
# error("Code block passed as constraint.")
# isexpr(x,:call) && length(x.args) == 3 || error("in @SDconstraint ($(string(x))): constraints must be in one of the following forms:\n" *
# " expr1 <= expr2\n" * " expr1 >= expr2")
# # Build the constraint
# # Simple comparison - move everything to the LHS
# sense = x.args[1]
# if sense == :⪰
# sense = :(>=)
# elseif sense == :⪯
# sense = :(<=)
# end
# sense,_ = _canonicalize_sense(sense)
# lhs = :()
# if sense == :(>=)
# lhs = :($(x.args[2]) - $(x.args[3]))
# elseif sense == :(<=)
# lhs = :($(x.args[3]) - $(x.args[2]))
# else
# error("Invalid sense $sense in SDP constraint")
# end
# newaff, parsecode = parseExprToplevel(lhs, :q)
# assert_validmodel(m, quote
# q = zero(AffExpr)
# $parsecode
# addconstraint($m, constructconstraint!($newaff, PSDCone()))
# end)
# end
"""
@SDconstraint(m, x)
Adds a semidefinite constraint to the `Model m`. The expression `x` must be a square, two-dimensional array.
"""
macro SDconstraint(m, x)
m = esc(m)

if isa(x, Symbol)
error("in @SDconstraint: Incomplete constraint specification $x. Are you missing a comparison (<= or >=)?")
end

(x.head == :block) &&
error("Code block passed as constraint.")
isexpr(x,:call) && length(x.args) == 3 || error("in @SDconstraint ($(string(x))): constraints must be in one of the following forms:\n" *
" expr1 <= expr2\n" * " expr1 >= expr2")
# Build the constraint
# Simple comparison - move everything to the LHS
sense = x.args[1]
if sense == :
sense = :(>=)
elseif sense == :
sense = :(<=)
end
sense,_ = _canonicalize_sense(sense)
lhs = :()
if sense == :(>=)
lhs = :($(x.args[2]) - $(x.args[3]))
elseif sense == :(<=)
lhs = :($(x.args[3]) - $(x.args[2]))
else
error("Invalid sense $sense in SDP constraint")
end
newaff, parsecode = parseExprToplevel(lhs, :q)
assert_validmodel(m, quote
q = zero(AffExpr)
$parsecode
addconstraint($m, constructconstraint!($newaff, PSDCone()))
end)
end


# """
Expand Down
12 changes: 12 additions & 0 deletions src/sd.jl
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@ function addconstraint(m::Model, c::SDVariableConstraint)
cref = MOI.addconstraint!(m.instance, MOI.VectorOfVariables([instancereference(c.Q[i, j]) for j in 1:n for i in 1:j]), MOI.PositiveSemidefiniteConeTriangle(n))
return ConstraintRef(m, cref)
end

function constructconstraint!(x::AbstractMatrix, ::PSDCone)
n = Base.LinAlg.checksquare(x)
# Support for non-symmetric matrices as done prior to JuMP v0.19
# will be added once the appropriate cone has been added in MathOptInterface
# as discussed in the following PR:
# https://github.com/JuliaOpt/JuMP.jl/pull/1122#issuecomment-344980944
@assert issymmetric(x)
aff = [x[i, j] for j in 1:n for i in 1:j]
s = MOI.PositiveSemidefiniteConeTriangle(n)
return VectorAffExprConstraint(aff, s)
end
28 changes: 28 additions & 0 deletions test/constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,32 @@
# @test JuMP.isequal_canonical(c.func, -1 + x^2)
# @test c.set == MOI.SecondOrderCone(1)
end

@testset "SDP constraint" begin
m = Model()
@variable(m, x)
@variable(m, y)

cref = @SDconstraint(m, [x 1; 1 -y] [1 x; x -2])
c = JuMP.constraintobject(cref, Vector{AffExpr}, MOI.PositiveSemidefiniteConeTriangle)
@test JuMP.isequal_canonical(c.func[1], x-1)
@test JuMP.isequal_canonical(c.func[2], 1-x)
@test JuMP.isequal_canonical(c.func[3], 2-y)
@test c.set == MOI.PositiveSemidefiniteConeTriangle(2)
end

@testset "Nonsensical SDPs" begin
m = Model()
@test_throws ErrorException @variable(m, unequal[1:5,1:6], PSD)
# Some of these errors happen at compile time, so we can't use @test_throws
@test macroexpand(:(@variable(m, notone[1:5,2:6], PSD))).head == :error
@test macroexpand(:(@variable(m, oneD[1:5], PSD))).head == :error
@test macroexpand(:(@variable(m, threeD[1:5,1:5,1:5], PSD))).head == :error
@test macroexpand(:(@variable(m, psd[2] <= rand(2,2), PSD))).head == :error
@test macroexpand(:(@variable(m, -ones(3,4) <= foo[1:4,1:4] <= ones(4,4), PSD))).head == :error
@test macroexpand(:(@variable(m, -ones(3,4) <= foo[1:4,1:4] <= ones(4,4), Symmetric))).head == :error
@test macroexpand(:(@variable(m, -ones(4,4) <= foo[1:4,1:4] <= ones(4,5), Symmetric))).head == :error
@test macroexpand(:(@variable(m, -rand(5,5) <= nonsymmetric[1:5,1:5] <= rand(5,5), Symmetric))).head == :error
end

end

0 comments on commit 9cfe927

Please sign in to comment.