Skip to content

Commit f9b7bbb

Browse files
committed
Integrate with JuMP
1 parent f9136f8 commit f9b7bbb

File tree

5 files changed

+196
-1
lines changed

5 files changed

+196
-1
lines changed

Project.toml

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
name = "ComplexOptInterface"
22
uuid = "25c3070e-1275-41b5-afef-2f982c87090a"
3-
repo = "https://github.com/jump-dev/ComplexOptInterface.jl.git"
43
authors = ["Benoît Legat <[email protected]>"]
4+
repo = "https://github.com/jump-dev/ComplexOptInterface.jl.git"
55
version = "0.0.3"
66

77
[deps]
8+
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
89
MathOptInterface = "b8f27783-ece8-5eb3-8dc8-9495eed66fee"
910
MutableArithmetics = "d8a4904e-b15c-11e9-3269-09a3773c0cb0"
1011

1112
[compat]
13+
JuMP = "0.23"
1214
MathOptInterface = "1"
1315
MutableArithmetics = "1"
1416
julia = "1.6"

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,23 @@
88
| [![Codecov branch][codecov-img]][codecov-url] | [<img src="https://upload.wikimedia.org/wikipedia/en/a/af/Discourse_logo.png" width="64">][discourse-url] |
99

1010
An extension of MathOptInterface to complex sets.
11+
12+
## How to use
13+
14+
Add to the JuMP model the bridges of this package with `add_all_bridges` and then you can create complex equality constraints and complex hermitian matrices:
15+
```julia
16+
using JuMP
17+
import ComplexOptInterface
18+
const COI = ComplexOptInterface
19+
20+
model = Model()
21+
COI.add_all_bridges(model)
22+
@variable(model, x[1:2, 1:2] in COI.HermitianPSDCone())
23+
@constraint(model, x[1, 1] + x[2, 2] * im == 1 + 2im)
24+
```
25+
26+
## Design considerations
27+
1128
There are two types of complex sets:
1229
1) Some sets have complex entries, i.e. `MOI.EqualTo{Complex{Float64}}`,
1330
2) Some sets have real entries even if they model a complex mathematical set, i.e.

src/ComplexOptInterface.jl

+4
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@ const MOI = MathOptInterface
99
struct HermitianPositiveSemidefiniteConeTriangle <: MOI.AbstractVectorSet
1010
side_dimension::Int
1111
end
12+
1213
function MOI.dimension(set::HermitianPositiveSemidefiniteConeTriangle)
1314
return MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(set.side_dimension)) +
1415
MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(set.side_dimension - 1))
1516
end
1617

18+
Base.copy(set::HermitianPositiveSemidefiniteConeTriangle) = set
19+
1720
function MOI.Utilities.set_dot(
1821
x::AbstractVector,
1922
y::AbstractVector,
@@ -28,5 +31,6 @@ function MOI.Utilities.set_dot(
2831
end
2932

3033
include("Bridges/Bridges.jl")
34+
include("jump.jl")
3135

3236
end # module

src/jump.jl

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import LinearAlgebra
2+
import JuMP
3+
4+
function add_all_bridges(model::JuMP.Model)
5+
JuMP.add_bridge(model, Bridges.Variable.HermitianToSymmetricPSDBridge)
6+
JuMP.add_bridge(model, Bridges.Constraint.SplitEqualToBridge)
7+
JuMP.add_bridge(model, Bridges.Constraint.SplitZeroBridge)
8+
end
9+
10+
struct HermitianPSDCone end
11+
12+
struct HermitianMatrixShape <: JuMP.AbstractShape
13+
side_dimension::Int
14+
end
15+
16+
function JuMP.vectorize(matrix::Matrix, ::HermitianMatrixShape)
17+
n = LinearAlgebra.checksquare(matrix)
18+
return vcat(
19+
JuMP.vectorize(_real.(matrix), JuMP.SymmetricMatrixShape(n)),
20+
JuMP.vectorize(_imag.(matrix[1:(end-1),2:end]), JuMP.SymmetricMatrixShape(n - 1)),
21+
)
22+
end
23+
24+
function JuMP.reshape_vector(v::Vector{T}, shape::HermitianMatrixShape) where T
25+
NewType = MA.promote_operation(MA.add_mul, T, Complex{Bool}, T)
26+
n = shape.side_dimension
27+
matrix = Matrix{NewType}(undef, n, n)
28+
real_k = 0
29+
imag_k = MOI.dimension(MOI.PositiveSemidefiniteConeTriangle(n))
30+
for j in 1:n
31+
for i in 1:(j-1)
32+
real_k += 1
33+
imag_k += 1
34+
matrix[i, j] = v[real_k] + im * v[imag_k]
35+
matrix[j, i] = v[real_k] - im * v[imag_k]
36+
end
37+
real_k += 1
38+
matrix[j, j] = v[real_k]
39+
end
40+
return matrix
41+
end
42+
43+
function _mapinfo(f::Function, v::JuMP.ScalarVariable)
44+
info = v.info
45+
return JuMP.ScalarVariable(JuMP.VariableInfo(
46+
info.has_lb,
47+
f(info.lower_bound),
48+
info.has_ub,
49+
f(info.upper_bound),
50+
info.has_fix,
51+
f(info.fixed_value),
52+
info.has_start,
53+
f(info.start),
54+
info.binary,
55+
info.integer,
56+
))
57+
end
58+
59+
_real(s::String) = s
60+
_imag(s::String) = s
61+
62+
_real(v::JuMP.ScalarVariable) = _mapinfo(real, v)
63+
_imag(v::JuMP.ScalarVariable) = _mapinfo(imag, v)
64+
_conj(v::JuMP.ScalarVariable) = _mapinfo(conj, v)
65+
function _isreal(v::JuMP.ScalarVariable)
66+
info = v.info
67+
return isreal(info.lower_bound) && isreal(info.upper_bound) && isreal(info.fixed_value) && isreal(info.start)
68+
end
69+
70+
function _vectorize_variables(_error::Function, matrix::Matrix)
71+
n = LinearAlgebra.checksquare(matrix)
72+
for j in 1:n
73+
if !_isreal(matrix[j, j])
74+
_error(
75+
"Non-real bounds or starting values for diagonal of hermitian variable.",
76+
)
77+
end
78+
for i in 1:j
79+
if matrix[i, j] != _conj(matrix[j, i])
80+
_error(
81+
"Non-conjugate bounds, integrality or starting values for hermitian variable.",
82+
)
83+
end
84+
end
85+
end
86+
JuMP.vectorize(matrix, HermitianMatrixShape(n))
87+
end
88+
89+
function JuMP.build_variable(
90+
_error::Function,
91+
variables::Matrix{<:JuMP.AbstractVariable},
92+
::HermitianPSDCone,
93+
)
94+
n = JuMP._square_side(_error, variables)
95+
set = HermitianPositiveSemidefiniteConeTriangle(n)
96+
shape = HermitianMatrixShape(n)
97+
return JuMP.VariablesConstrainedOnCreation(
98+
_vectorize_variables(_error, variables),
99+
set,
100+
shape,
101+
)
102+
end
103+

test/jump.jl

+69
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
module TestJuMP
2+
3+
using JuMP
4+
import GLPK
5+
import ComplexOptInterface
6+
const COI = ComplexOptInterface
7+
using Test
8+
9+
function test_readme_example()
10+
model = Model()
11+
COI.add_all_bridges(model)
12+
@variable(model, x[1:2, 1:2] in COI.HermitianPSDCone())
13+
@test num_variables(model) == 4
14+
v = all_variables(model)
15+
@test x[1, 1] == 1v[1]
16+
@test x[1, 2] == v[2] + v[4] * im
17+
@test x[2, 2] == 1v[3]
18+
# FIXME needs https://github.com/jump-dev/JuMP.jl/pull/2899
19+
#@test x[2, 1] == conj(x[1, 2])
20+
@constraint(model, x[1, 1] + x[2, 2] * im == 1 + 2im)
21+
end
22+
23+
function test_simple_equality()
24+
model = Model(GLPK.Optimizer)
25+
add_bridge(model, COI.Bridges.Constraint.SplitEqualToBridge)
26+
27+
@variable(model, 0 <= x[1:3] <= 1)
28+
29+
@objective(model, Max, x[1] + 2x[2] - x[3])
30+
31+
# FIXME needs https://github.com/jump-dev/JuMP.jl/pull/2895
32+
#@constraint(model, (1 + im) * x[1] + (1 + im) * x[2] + (1 - 3im) * x[3] == 1.0)
33+
aff = (1 + im) * x[1] + (1 + im) * x[2] + (1 - 3im) * x[3]
34+
@constraint(model, aff == 1.0)
35+
36+
# Optimize the model:
37+
38+
optimize!(model)
39+
40+
# Check the termination and primal status to see if we have a solution:
41+
42+
println("Termination status : ", termination_status(model))
43+
println("Primal status : ", primal_status(model))
44+
45+
# Print the solution:
46+
47+
obj_value = objective_value(model)
48+
x_values = value.(x)
49+
50+
println("Objective value : ", obj_value)
51+
println("x values : ", x_values)
52+
53+
@test obj_value 1.25
54+
@test x_values [0.0, 0.75, 0.25]
55+
end
56+
57+
function runtests()
58+
for name in names(@__MODULE__; all = true)
59+
if startswith("$name", "test_")
60+
@testset "$(name)" begin
61+
getfield(@__MODULE__, name)()
62+
end
63+
end
64+
end
65+
end
66+
67+
end
68+
69+
TestJuMP.runtests()

0 commit comments

Comments
 (0)