Skip to content

Commit

Permalink
Add support for skew symmetric variables (#2416)
Browse files Browse the repository at this point in the history
Co-authored-by: Geth, Frederik (Energy, Newcastle) <Frederik.Geth@csiro.au>
  • Loading branch information
odow and Geth, Frederik (Energy, Newcastle) authored Dec 23, 2020
1 parent 994feba commit 2b0d910
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 4 deletions.
2 changes: 2 additions & 0 deletions docs/src/reference/constraints.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ RotatedSecondOrderCone
PSDCone
SOS1
SOS2
SkewSymmetricMatrixSpace
SkewSymmetricMatrixShape
AbstractConstraint
ScalarConstraint
Expand Down
10 changes: 10 additions & 0 deletions docs/src/variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -539,6 +539,16 @@ julia> @variable(model, x[1:2, 1:2], Symmetric)
x[1,1] x[1,2]
x[1,2] x[2,2]
```

You can impose a constraint that the square matrix is skew symmetric with
[`SkewSymmetricMatrixSpace`](@ref):
```jldoctest; setup=:(model=Model())
julia> @variable(model, x[1:2, 1:2] in SkewSymmetricMatrixSpace())
2×2 Array{GenericAffExpr{Float64,VariableRef},2}:
0 x[1,2]
-x[1,2] 0
```

## Anonymous JuMP variables

In all of the above examples, we have created *named* JuMP variables. However,
Expand Down
83 changes: 82 additions & 1 deletion src/sd.jl
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
struct SymMatrixSpace end

"""
SkewSymmetricMatrixSpace()
Use in the [`@variable`](@ref) macro to constrain a matrix of variables to be
skew-symmetric.
## Examples
```jldoctest; setup=:(model = Model())
@variable(model, Q[1:2, 1:2] in SkewSymmetricMatrixSpace())
```
"""
struct SkewSymmetricMatrixSpace end

"""
PSDCone
Expand Down Expand Up @@ -96,6 +110,46 @@ function vectorize(matrix::Matrix, ::SymmetricMatrixShape)
return [matrix[i, j] for j in 1:n for i in 1:j]
end


"""
SkewSymmetricMatrixShape
Shape object for a skew symmetric square matrix of `side_dimension` rows and
columns. The vectorized form contains the entries of the upper-right triangular
part of the matrix (without the diagonal) given column by column (or
equivalently, the entries of the lower-left triangular part given row by row).
The diagonal is zero.
"""
struct SkewSymmetricMatrixShape <: AbstractShape
side_dimension::Int
end
function reshape_vector(
vectorized_form::Vector{T},
shape::SkewSymmetricMatrixShape,
) where {T}
NewType = Base.promote_type(
T,
_MA.promote_operation(-, T),
_MA.promote_operation(zero, T),
)
matrix = Matrix{NewType}(undef, shape.side_dimension, shape.side_dimension)
k = 0
for j in 1:shape.side_dimension
for i in 1:(j - 1)
k += 1
matrix[i, j] = vectorized_form[k]
matrix[j, i] = -vectorized_form[k]
end
matrix[j, j] = zero(NewType)
end
return matrix
end

function vectorize(matrix::Matrix, ::SkewSymmetricMatrixShape)
n = LinearAlgebra.checksquare(matrix)
return [matrix[i, j] for j in 1:n for i in 1:j-1]
end

"""
SquareMatrixShape
Expand Down Expand Up @@ -131,7 +185,7 @@ end
function _square_side(_error::Function, variables::Matrix)
n, m = size(variables)
if n != m
_error("Symmetric variables must be 2-dimensional.")
_error("Symmetric variables must be square. Got size ($n, $m).")
end
return n
end
Expand Down Expand Up @@ -167,6 +221,33 @@ function build_variable(_error::Function, variables::Matrix{<:ScalarVariable}, :
return VariablesConstrainedOnCreation(_vectorize_variables(_error, variables), set, shape)
end

"""
build_variable(_error::Function, variables, ::SkewSymmetricMatrixSpace)
Return a `VariablesConstrainedOnCreation` of shape [`SkewSymmetricMatrixShape`](@ref)
creating variables in `MOI.Reals`, i.e. "free" variables unless they are
constrained after their creation.
This function is used by the [`@variable`](@ref) macro as follows:
```julia
@variable(model, Q[1:2, 1:2] in SkewSymmetricMatrixSpace())
```
"""
function build_variable(
_error::Function,
variables::Matrix{<:ScalarVariable},
::SkewSymmetricMatrixSpace,
)
n = _square_side(_error, variables)
set = MOI.Reals(div(n^2 - n, 2))
shape = SkewSymmetricMatrixShape(n)
return VariablesConstrainedOnCreation(
vectorize(variables, SkewSymmetricMatrixShape(n)),
set,
shape,
)
end

"""
build_constraint(_error::Function, variables, ::PSDCone)
Expand Down
2 changes: 1 addition & 1 deletion test/constraint.jl
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ end
function test_nonsensical_SDP_constraint(ModelType, ::Any)
m = ModelType()
@test_throws_strip(
ErrorException("In `@variable(m, unequal[1:5, 1:6], PSD)`: Symmetric variables must be 2-dimensional."),
ErrorException("In `@variable(m, unequal[1:5, 1:6], PSD)`: Symmetric variables must be square. Got size (5, 6)."),
@variable(m, unequal[1:5, 1:6], PSD)
)
# Some of these errors happen at compile time, so we can't use @test_throws
Expand Down
26 changes: 24 additions & 2 deletions test/variable.jl
Original file line number Diff line number Diff line change
Expand Up @@ -482,6 +482,30 @@ function test_variable_symmetric(ModelType, ::Any)
@test y[1, 2] === y[2, 1]
end

function test_variable_skewsymmetric(ModelType, ::Any)
model = ModelType()
@variable(model, x[1:2, 1:2] in SkewSymmetricMatrixSpace())
@test x[1, 2] == -x[2, 1]
@test iszero(x[1, 1])
@test iszero(x[2, 2])
@test model[:x] === x
@test num_variables(model) == 1
@variable(model, z[1:3, 1:3] in SkewSymmetricMatrixSpace())
@test z[1, 2] == -z[2, 1]
@test z[1, 3] == -z[3, 1]
@test z[2, 3] == -z[3, 2]
@test iszero(z[1, 1])
@test iszero(z[2, 2])
@test iszero(z[3, 3])
@test model[:z] === z
@test num_variables(model) == 4
y = @variable(model, [1:3, 1:3] in SkewSymmetricMatrixSpace())
@test y[1, 2] == -y[2, 1]
@test y[2, 3] == -y[3, 2]
@test iszero(y[3, 3])
@test num_variables(model) == 7
end

function test_variables_constrained_on_creation(ModelType, ::Any)
model = ModelType()

Expand Down Expand Up @@ -763,10 +787,8 @@ end
function test_Model_value_var(ModelType, ::Any)
model = ModelType()
@variable(model, x[1:2])

vals = Dict(x[1] => 1.0, x[2] => 2.0)
f = vidx -> vals[vidx]

@test value(x[1], f) === 1.0
@test value(x[2], f) === 2.0
end
Expand Down

0 comments on commit 2b0d910

Please sign in to comment.