Skip to content

Commit

Permalink
Merge pull request #62 from oxfordcontrol/pg/box_constraint
Browse files Browse the repository at this point in the history
Box constraint support

- Add documentation for box
- Update MOIWrapper
  • Loading branch information
migarstka committed Mar 14, 2019
2 parents de04ee7 + b584ef9 commit 6a4d93e
Show file tree
Hide file tree
Showing 11 changed files with 237 additions and 118 deletions.
1 change: 1 addition & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ COSMO.warm_start_dual!
COSMO.Constraint
COSMO.ZeroSet
COSMO.Nonnegatives
COSMO.Box
COSMO.SecondOrderCone
COSMO.PsdCone
COSMO.PsdConeTriangle
Expand Down
11 changes: 6 additions & 5 deletions docs/src/guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,12 @@ The COSMO interface expects constraints to have the form ``A_i x + b_i \in \math

Convex Set | Description
--- | ---
ZeroSet(dim) | The set ``\{ 0 \}^{dim}`` that contains the origin
Nonnegatives(dim) | The nonnegative orthant ``\{ x \in \mathbb{R}^{dim} : x_i \ge 0, \forall i=1,\dots,\mathrm{dim} \}``
SecondOrderCone(dim) | The second-order (Lorenz) cone ``\{ (t,x) \in \mathbb{R}^{dim} : \|x\|_2 \leq t \}``
PsdCone(dim) | The vectorized positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the positive semidefinite matrix ``X``, i.e. ``X \in \mathbb{S}^{\sqrt{dim}}_+ \Rightarrow \text{vec}(X) = x \in \mathcal{S}_+^{dim}``
PsdConeTriangle(dim) | The vectorized positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the upper triangular part of the positive semidefinite matrix ``X``, i.e. ``X \in \mathbb{S}^{d}_+ \Rightarrow \text{svec}(X) = x \in \mathcal{S}_+^{dim}`` where ``d=\sqrt{1/4 + 2 \text{dim}} - 1/2``
ZeroSet | The set ``\{ 0 \}^{dim}`` that contains the origin
Nonnegatives | The nonnegative orthant ``\{ x \in \mathbb{R}^{dim} : x_i \ge 0, \forall i=1,\dots,\mathrm{dim} \}``
Box(u, l) | The hyperbox ``\{ x \in \mathbb{R}^{dim} : l \leq x \leq u\}`` with vectors ``l \in \mathbb{R}^{dim} \cup \{-\infty\}`` and ``u \in \mathbb{R}^{dim} \cup \{+\infty\}``
SecondOrderCone | The second-order (Lorenz) cone ``\{ (t,x) \in \mathbb{R}^{dim} : \|x\|_2 \leq t \}``
PsdCone | The vectorized positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the positive semidefinite matrix ``X``, i.e. ``X \in \mathbb{S}^{\sqrt{dim}}_+ \Rightarrow \text{vec}(X) = x \in \mathcal{S}_+^{dim}``
PsdConeTriangle | The vectorized positive semidefinite cone ``\mathcal{S}_+^{dim}``. ``x`` is the vector obtained by stacking the columns of the upper triangular part of the positive semidefinite matrix ``X``, i.e. ``X \in \mathbb{S}^{d}_+ \Rightarrow \text{svec}(X) = x \in \mathcal{S}_+^{dim}`` where ``d=\sqrt{1/4 + 2 \text{dim}} - 1/2``

The constructor for a constraint expects a matrix `A`, a vector `b` and a `convex_set`.

Expand Down
30 changes: 17 additions & 13 deletions src/MOIWrapper.jl
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,10 @@ function MOIU.IndexMap(dest::Optimizer, src::MOI.ModelLike)
end
# map model constraint indices to solver constraint indices. For now this is important since solver expects following
# order: {0}-variables, R+-variables, SOCP cones, psd cones
LOCs = MOI.get(src, MOI.ListOfConstraints())
#sort!(LOCs, by=x-> sortSets(x[2]))
# LOCs = MOI.get(src, MOI.ListOfConstraints())
# sort!(LOCs, by=x-> sort_sets(x[2]))
i = 0
for (F, S) in LOCs
for (F, S) in MOI.get(src, MOI.ListOfConstraints())
MOI.supports_constraint(dest, F, S) || throw(MOI.UnsupportedConstraint{F, S})
cis_src = MOI.get(src, MOI.ListOfConstraintIndices{F, S}())
for ci in cis_src
Expand Down Expand Up @@ -359,7 +359,7 @@ function processconstraints!(triplets::SparseTriplets, b::Vector, COSMOconvexSet
rows = constraint_rows(rowranges, idxmap[ci])
processConstant!(b, rows, f, s)
constant[rows] = b[rows]
if typeof(s) <: MOI.AbstractScalarSet
if typeof(s) <: MOI.AbstractScalarSet && !(typeof(s) <: MOI.Interval)
set_constant[rows] = MOIU.getconstant(s)
end
processConstraint!(triplets, f, rows, idxmap, s)
Expand Down Expand Up @@ -459,6 +459,11 @@ function processSet!(b::Vector, row::Int, cs, s::EqualTo)
nothing
end

function processSet!(b::Vector, row::Int, cs, s::MOI.Interval)
push!(cs, COSMO.Box{Float64}([s.lower], [s.upper]))
nothing
end

# process the following sets Zeros
function processSet!(b::Vector, rows::UnitRange{Int}, cs, s::Zeros)
push!(cs, COSMO.ZeroSet{Float64}(length(rows)))
Expand Down Expand Up @@ -624,19 +629,18 @@ _unshift(optimizer::Optimizer, offset, value, s::Type{<:MOI.AbstractScalarSet})
_shift(optimizer::Optimizer, offset, value, s) = value
_shift(optimizer::Optimizer, offset, value, s::Type{<:MOI.AbstractScalarSet}) = value - optimizer.set_constant[offset]

function MOI.get(optimizer::Optimizer, ::MOI.ConstraintPrimal, ci::CI{<:MOI.AbstractFunction, S}) where S <: MOI.AbstractSet
function MOI.get(optimizer::Optimizer, ::MOI.ConstraintPrimal, ci_src::CI{<:MOI.AbstractFunction, S}) where S <: MOI.AbstractSet
# offset = constroffset(optimizer, ci)
# rows = constrrows(optimizer, ci)
# _unshift(optimizer, offset, unscalecoef(rows, reorderval(optimizer.sol.slack[offset .+ rows], S), S, length(rows)), S)
# ci_dest = optimizer.idxmap[ci_src]
rows = constraint_rows(optimizer.rowranges, ci)
rows = COSMO.constraint_rows(optimizer.rowranges, ci_src)
c_primal = unscalecoef(rows, optimizer.results.s[rows], S, length(rows))
# (typeof(c_primal) <: AbstractArray && length(c_primal) == 1) && (c_primal = first(c_primal))
return _unshift(optimizer, rows, c_primal, S)
end

function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, ci::CI{<:MOI.AbstractFunction, S}) where S <: MOI.AbstractSet
rows = constraint_rows(optimizer.rowranges, ci)#optimizer.idxmap[ci])
function MOI.get(optimizer::Optimizer, ::MOI.ConstraintDual, ci_src::CI{<:MOI.AbstractFunction, S}) where S <: MOI.AbstractSet
rows = constraint_rows(optimizer.rowranges, ci_src)
return unscalecoef(rows, optimizer.results.y[rows], S, length(rows))
end

Expand All @@ -653,19 +657,19 @@ function MOI.set(optimizer::Optimizer, a::MOI.VariablePrimalStart, vi::VI, value
end

MOI.supports(::Optimizer, a::MOI.ConstraintPrimalStart, ::Type{MOI.ConstraintIndex}) = true
function MOI.set(optimizer::Optimizer, a::MOI.ConstraintPrimalStart, ci::CI{<:MOI.AbstractFunction, S}, value) where S <: MOI.AbstractSet
function MOI.set(optimizer::Optimizer, a::MOI.ConstraintPrimalStart, ci_src::CI{<:MOI.AbstractFunction, S}, value) where S <: MOI.AbstractSet
MOI.is_empty(optimizer) && throw(MOI.CannotSetAttribute(a))
rows = constraint_rows(optimizer.rowranges, optimizer.idxmap[ci])
rows = constraint_rows(optimizer.rowranges, optimizer.idxmap[ci_src])
# this undoes the scaling and shifting that was used in get(MOI.ConstraintPrimal)
# Off-diagonal entries of slack variable of a PSDTriangle constraint has to be scaled by sqrt(2)
value = _shift(optimizer, rows, value, S)
COSMO.warm_start_slack!(optimizer.inner, _scalecoef(rows, value, false, S, length(rows), false), rows)
end

MOI.supports(::Optimizer, a::MOI.ConstraintDualStart, ::Type{MOI.ConstraintIndex}) = true
function MOI.set(optimizer::Optimizer, a::MOI.ConstraintDualStart, ci::CI{<:MOI.AbstractFunction, S}, value) where S <: MOI.AbstractSet
function MOI.set(optimizer::Optimizer, a::MOI.ConstraintDualStart, ci_src::CI{<:MOI.AbstractFunction, S}, value) where S <: MOI.AbstractSet
MOI.is_empty(optimizer) && throw(MOI.CannotSetAttribute(a))
rows = constraint_rows(optimizer.rowranges, optimizer.idxmap[ci])
rows = constraint_rows(optimizer.rowranges, optimizer.idxmap[ci_src])
# this undoes the scaling that was used in get(MOI.ConstraintDual)
# Off-diagonal entries of dual variable of a PSDTriangle constraint has to be scaled by sqrt(2)
COSMO.warm_start_dual!(optimizer.inner, _scalecoef(rows, value, false, S, length(rows), false), rows)
Expand Down
1 change: 0 additions & 1 deletion src/algebra.jl
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,3 @@ function is_neg_sem_def(X, tol)
s, U = LAPACK.syevr!('N', 'A', 'U', X, 0.0, 0.0, 0, 0, -1.0);
return s[end] <= tol
end

72 changes: 37 additions & 35 deletions src/constraint.jl
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@

"""
Constraint(A, b, convex_set, dim = 0, indices = 0:0)
Constraint(A, b, convex_set_type, dim = 0, indices = 0:0)
Creates a COSMO constraint: `Ax + b ∈ convex_set`.
By default the following convex sets are supported: `ZeroSet`, `Nonnegatives`, `SecondOrderCone`, `PsdCone`, `PsdConeTriangle`.
By default the following convex set types are supported: `ZeroSet`, `Nonnegatives`, `SecondOrderCone`, `PsdCone`, `PsdConeTriangle`.
# Examples
```jldoctest; setup = :(using COSMO)
Expand All @@ -14,6 +14,16 @@ Size of A: (2, 2)
ConvexSet: Nonnegatives{Float64}
```
For convex sets that require their own data, it is possible to pass the pass the instantiated object directly rather than the type name.
# Examples
```jldoctest; setup = :(using COSMO)
julia> Constraint([1 0;0 1], zeros(2), COSMO.Box([-1.;-1.],[1.;1.]))
Constraint
Size of A: (2, 2)
ConvexSet: Box{Float64}
```
---
The optional arguments `dim` and `indices` can be used to specify A and b for subparts of variable `x`. If `x` has dimension `dim = 4`,
then x[2] and x[3] can be constrained to the zero cone in the following way:
Expand Down Expand Up @@ -43,11 +53,12 @@ struct Constraint{T <: AbstractFloat}
function Constraint{T}(
A::AbstractMatrix{T},
b::AbstractVector{T},
set_type::Type{ <: AbstractConvexSet},
convex_set::AbstractConvexSet{T},
dim::Integer = 0,
indices::UnitRange = 0:0) where{T}

size(A, 1) != length(b) && throw(DimensionMismatch("The dimensions of matrix A and vector b don't match."))
size(A,1) != convex_set.dim && throw(DimensionMismatch("The row dimension of A doesn't match the dimension of the constraint set."))
size(b, 2) != 1 && throw(DimensionMismatch("Input b must be a vector or a scalar."))

if indices != 0:0
Expand All @@ -57,9 +68,6 @@ struct Constraint{T <: AbstractFloat}
Ac[:, indices] = A
A = Ac
end
dim = size(A, 1)
# call the appropriate set constructor
convex_set = set_type{T}(dim)
new(A, b, convex_set)
end
end
Expand All @@ -69,44 +77,38 @@ end
#the user or to DefaultFloat
Constraint(args...) = Constraint{DefaultFloat}(args...)

function Constraint{T}(A::AbstractMatrix, b::AbstractVector,
set_type::Type{ <: AbstractConvexSet},
dim::Integer = 0,
indices::UnitRange = 0:0) where{T}
Constraint{T}(AbstractMatrix{T}(A), AbstractVector{T}(b), set_type, dim, indices)
#support the case where only a Type is specified for the set, and we need to
#create an instance of the appropriate size
function Constraint{T}(
A::AbstractMatrix{T},
b::AbstractVector{T},
set_type::Type{ <: AbstractConvexSet},args...) where{T}
# call the appropriate set constructor
convex_set = set_type{T}(size(A, 1))
Constraint{T}(A, b, convex_set, args...)
end

#all others convert first o matrix / vector args
function Constraint{T}(A::Real,b::Real,
set_type::Type{ <: AbstractConvexSet},
dim::Integer = 0,
indices::UnitRange = 0:0) where{T}
Constraint{T}(reshape([A], 1, 1), [b], set_type, dim, indices)

#allow various ways of passing A and b and convert to AbstractMatrix and AbstractVector types
function Constraint{T}(A::AbstractMatrix, b::AbstractVector, args...) where{T}
Constraint{T}(AbstractMatrix{T}(A), AbstractVector{T}(b), args...)
end

function Constraint{T}(A::AbstractMatrix,
b::AbstractMatrix,
set_type::Type{ <: AbstractConvexSet},
dim::Integer = 0,
indices::UnitRange = 0:0) where {T}
#all others convert first o matrix / vector args
function Constraint{T}(A::Real,b::Real,args...) where{T}
Constraint{T}(reshape([A], 1, 1), [b], args...)
end

Constraint{T}(A, vec(b), set_type, dim, indices)
function Constraint{T}(A::AbstractMatrix,b::AbstractMatrix, args...) where {T}
Constraint{T}(A, vec(b), args...)
end

function Constraint{T}(A::Union{AbstractVector,AbstractMatrix},
b::Real,
set_type::Type{ <: AbstractConvexSet},
dim::Integer = 0,
indices::UnitRange = 0:0) where{T}
Constraint{T}(reshape(A, 1, length(A)), [b], set_type, dim, indices)
function Constraint{T}(A::Union{AbstractVector,AbstractMatrix}, b::Real, args...) where{T}
Constraint{T}(reshape(A, 1, length(A)), [b], args...)
end

function Constraint{T}(A::AbstractVector,
b::AbstractVector,
set_type::Type{ <: AbstractConvexSet},
dim::Integer = 0,
indices::UnitRange = 0:0) where{T}
Constraint{T}(reshape(A, length(A), 1), b, set_type, dim, indices)
function Constraint{T}(A::AbstractVector,b::AbstractVector,args...) where{T}
Constraint{T}(reshape(A, length(A), 1), b, args...)
end


Expand Down

0 comments on commit 6a4d93e

Please sign in to comment.