Skip to content

Commit 06216dc

Browse files
authored
Merge pull request #742 from JuliaOpt/bl/rsoc_get
Implement getter of ConstraintFunction/Set for RSOC
2 parents 55f3aa4 + c3cc6c9 commit 06216dc

File tree

4 files changed

+100
-13
lines changed

4 files changed

+100
-13
lines changed

Diff for: src/Bridges/rsocbridge.jl

+24-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
"""
2-
RSOCBridge{T}
2+
RSOCBridge{T, F, G}
33
44
The `RotatedSecondOrderCone` is `SecondOrderCone` representable; see [1, p. 104].
55
Indeed, we have ``2tu = (t/√2 + u/√2)^2 - (t/√2 - u/√2)^2`` hence
@@ -16,13 +16,11 @@ That means in particular that the norm is of constraint primal and duals are pre
1616
1717
[1] Ben-Tal, Aharon, and Arkadi Nemirovski. *Lectures on modern convex optimization: analysis, algorithms, and engineering applications*. Society for Industrial and Applied Mathematics, 2001.
1818
"""
19-
struct RSOCBridge{T, F} <: AbstractBridge
19+
struct RSOCBridge{T, F, G} <: AbstractBridge
2020
soc::CI{F, MOI.SecondOrderCone}
2121
end
22-
function bridge_constraint(::Type{RSOCBridge{T, F}}, model,
23-
f::MOI.AbstractVectorFunction,
24-
s::MOI.RotatedSecondOrderCone) where {T, F}
25-
d = s.dimension
22+
function rotate_function(f::MOI.AbstractVectorFunction, T::Type)
23+
d = MOI.output_dimension(f)
2624
f_scalars = MOIU.eachscalar(f)
2725
t = f_scalars[1]
2826
u = f_scalars[2]
@@ -34,17 +32,22 @@ function bridge_constraint(::Type{RSOCBridge{T, F}}, model,
3432
# line
3533
y = ts - us
3634
z = MOIU.operate!(+, T, ts, us)
37-
g = MOIU.operate(vcat, T, z, y, x)
38-
soc = MOI.add_constraint(model, g, MOI.SecondOrderCone(d))
39-
RSOCBridge{T, F}(soc)
35+
return MOIU.operate(vcat, T, z, y, x)
36+
end
37+
function bridge_constraint(::Type{RSOCBridge{T, F, G}}, model,
38+
f::MOI.AbstractVectorFunction,
39+
s::MOI.RotatedSecondOrderCone) where {T, F, G}
40+
soc = MOI.add_constraint(model, rotate_function(f, T),
41+
MOI.SecondOrderCone(MOI.dimension(s)))
42+
return RSOCBridge{T, F, G}(soc)
4043
end
4144

4245
function MOI.supports_constraint(::Type{RSOCBridge{T}},
4346
::Type{<:MOI.AbstractVectorFunction},
4447
::Type{MOI.RotatedSecondOrderCone}) where T
4548
return true
4649
end
47-
function added_constraint_types(::Type{RSOCBridge{T, F}}) where {T, F}
50+
function added_constraint_types(::Type{<:RSOCBridge{T, F}}) where {T, F}
4851
return [(F, MOI.SecondOrderCone)]
4952
end
5053
function concrete_bridge_type(::Type{<:RSOCBridge{T}},
@@ -54,7 +57,7 @@ function concrete_bridge_type(::Type{<:RSOCBridge{T}},
5457
Y = MOIU.promote_operation(-, T, S, S)
5558
Z = MOIU.promote_operation(+, T, S, S)
5659
F = MOIU.promote_operation(vcat, T, Z, Y, G)
57-
RSOCBridge{T, F}
60+
RSOCBridge{T, F, G}
5861
end
5962

6063
# Attributes, Bridge acting as an model
@@ -73,6 +76,16 @@ function MOI.delete(model::MOI.ModelLike, c::RSOCBridge)
7376
end
7477

7578
# Attributes, Bridge acting as a constraint
79+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintFunction,
80+
bridge::RSOCBridge{T, F, G}) where {T, F, G}
81+
# As it is an involution, we can just reapply the same transformation
82+
func = MOI.get(model, attr, bridge.soc)
83+
return MOIU.convert_approx(G, rotate_function(func, T))
84+
end
85+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintSet, bridge::RSOCBridge)
86+
set = MOI.get(model, attr, bridge.soc)
87+
return MOI.RotatedSecondOrderCone(MOI.dimension(set))
88+
end
7689
# As the linear transformation is a symmetric involution,
7790
# the constraint primal and dual both need to be processed by reapplying the same transformation
7891
function _get(model, attr::Union{MOI.ConstraintPrimal, MOI.ConstraintDual}, c::RSOCBridge)

Diff for: src/Test/UnitTests/basic_constraint_tests.jl

+7-1
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,16 @@ const BasicConstraintTests = Dict(
6565
(MOI.VectorAffineFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_affine, 2, MOI.Nonpositives(2) ),
6666
(MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_affine, 2, MOI.Nonnegatives(2) ),
6767

68+
(MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => ( dummy_vector_affine, 3, MOI.SecondOrderCone(3) ),
69+
(MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone) => ( dummy_vector_affine, 3, MOI.RotatedSecondOrderCone(3) ),
70+
6871
(MOI.VectorQuadraticFunction{Float64}, MOI.Reals) => ( dummy_vector_quadratic, 2, MOI.Reals(2) ),
6972
(MOI.VectorQuadraticFunction{Float64}, MOI.Zeros) => ( dummy_vector_quadratic, 2, MOI.Zeros(2) ),
7073
(MOI.VectorQuadraticFunction{Float64}, MOI.Nonpositives) => ( dummy_vector_quadratic, 2, MOI.Nonpositives(2) ),
71-
(MOI.VectorQuadraticFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_quadratic, 2, MOI.Nonnegatives(2) )
74+
(MOI.VectorQuadraticFunction{Float64}, MOI.Nonnegatives) => ( dummy_vector_quadratic, 2, MOI.Nonnegatives(2) ),
75+
76+
(MOI.VectorQuadraticFunction{Float64}, MOI.SecondOrderCone) => ( dummy_vector_quadratic, 3, MOI.SecondOrderCone(3) ),
77+
(MOI.VectorQuadraticFunction{Float64}, MOI.RotatedSecondOrderCone) => ( dummy_vector_quadratic, 3, MOI.RotatedSecondOrderCone(3) )
7278
)
7379
"""
7480
basic_constraint_tests(model::MOI.ModelLike, config::TestConfig;

Diff for: src/Utilities/functions.jl

+46-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,21 @@ function Base.getindex(it::ScalarFunctionIterator{VAF{T}}, I::AbstractVector) wh
152152
append!(terms, map(t -> MOI.VectorAffineTerm(i, t), g.terms))
153153
constant[i] = g.constant
154154
end
155-
VAF(terms, constant)
155+
return VAF(terms, constant)
156+
end
157+
function Base.getindex(it::ScalarFunctionIterator{VQF{T}}, I::AbstractVector) where T
158+
affine_terms = MOI.VectorAffineTerm{T}[]
159+
quadratic_terms = MOI.VectorQuadraticTerm{T}[]
160+
constant = Vector{T}(undef, length(I))
161+
for (i, j) in enumerate(I)
162+
g = it[j]
163+
append!(affine_terms, map(t -> MOI.VectorAffineTerm(i, t),
164+
g.affine_terms))
165+
append!(quadratic_terms, map(t -> MOI.VectorQuadraticTerm(i, t),
166+
g.quadratic_terms))
167+
constant[i] = g.constant
168+
end
169+
return VQF(affine_terms, quadratic_terms, constant)
156170
end
157171

158172
"""
@@ -1465,6 +1479,20 @@ function operate(::typeof(vcat), ::Type{T},
14651479
fill_vector(constant, T, 0, 0, fill_constant, output_dim, funcs...)
14661480
return VAF(terms, constant)
14671481
end
1482+
function operate(::typeof(vcat), ::Type{T},
1483+
funcs::Union{ScalarQuadraticLike{T}, VVF, VAF{T}, VQF{T}}...) where T
1484+
num_affine_terms = sum(func -> number_of_affine_terms(T, func), funcs)
1485+
num_quadratic_terms = sum(func -> number_of_quadratic_terms(T, func), funcs)
1486+
out_dim = sum(func -> output_dim(T, func), funcs)
1487+
affine_terms = Vector{MOI.VectorAffineTerm{T}}(undef, num_affine_terms)
1488+
quadratic_terms = Vector{MOI.VectorQuadraticTerm{T}}(undef, num_quadratic_terms)
1489+
constant = zeros(T, out_dim)
1490+
fill_vector(affine_terms, T, 0, 0, fill_terms, number_of_affine_terms, funcs...)
1491+
fill_vector(quadratic_terms, T, 0, 0, fill_terms, number_of_quadratic_terms, funcs...)
1492+
fill_vector(constant, T, 0, 0, fill_constant, output_dim, funcs...)
1493+
return VQF(affine_terms, quadratic_terms, constant)
1494+
end
1495+
14681496

14691497
# Similar to `eachscalar` but faster, see
14701498
# https://github.com/JuliaOpt/MathOptInterface.jl/issues/418
@@ -1518,3 +1546,20 @@ function count_terms(dimension::I, terms::Vector{T}) where {I,T}
15181546
count_terms(counting, terms)
15191547
return counting
15201548
end
1549+
1550+
convert_approx(::Type{T}, func::T; kws...) where {T} = func
1551+
function convert_approx(::Type{MOI.SingleVariable}, func::MOI.ScalarAffineFunction{T};
1552+
tol=sqrt(eps(T))) where {T}
1553+
f = MOIU.canonical(func)
1554+
i = findfirst(t -> isapprox(t.coefficient, one(T), atol=tol), f.terms)
1555+
if abs(f.constant) > tol || i === nothing ||
1556+
any(j -> j != i && abs(f.terms[i]) > tol, eachindex(f.terms))
1557+
throw(InexactError(:convert_approx, MOI.SingleVariable, func))
1558+
end
1559+
return MOI.SingleVariable(f.terms[i].variable_index)
1560+
end
1561+
function convert_approx(::Type{MOI.VectorOfVariables}, func::MOI.VectorAffineFunction{T};
1562+
tol=sqrt(eps(T))) where {T}
1563+
return MOI.VectorOfVariables([convert_approx(MOI.SingleVariable, f, tol=tol).variable
1564+
for f in scalarize(func)])
1565+
end

Diff for: test/Bridges/rsocbridge.jl

+23
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,28 @@
1+
using Test
2+
3+
using MathOptInterface
4+
const MOI = MathOptInterface
5+
const MOIT = MathOptInterface.Test
6+
const MOIU = MathOptInterface.Utilities
7+
const MOIB = MathOptInterface.Bridges
8+
9+
include("utilities.jl")
10+
11+
include("simple_model.jl")
12+
13+
mock = MOIU.MockOptimizer(SimpleModel{Float64}())
14+
config = MOIT.TestConfig()
15+
116
@testset "RSOC" begin
217
bridged_mock = MOIB.RSOC{Float64}(mock)
18+
19+
MOIT.basic_constraint_tests(
20+
bridged_mock, config,
21+
include = [(F, S)
22+
for F in [MOI.VectorOfVariables, MOI.VectorAffineFunction{Float64},
23+
MOI.VectorQuadraticFunction{Float64}]
24+
for S in [MOI.RotatedSecondOrderCone]])
25+
326
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1/√2, 1/√2, 0.5, 1.0],
427
(MOI.SingleVariable, MOI.EqualTo{Float64}) => [-√2, -1/√2],
528
(MOI.VectorAffineFunction{Float64}, MOI.SecondOrderCone) => [[3/2, 1/2, -1.0, -1.0]])

0 commit comments

Comments
 (0)