Skip to content

Commit 2402c51

Browse files
authored
Merge pull request #664 from JuliaOpt/jg/scalarize
Scalarize bridge
2 parents 67f2ad8 + 1c52f79 commit 2402c51

File tree

4 files changed

+211
-4
lines changed

4 files changed

+211
-4
lines changed

src/Bridges/Bridges.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ function full_bridge_optimizer(model::MOI.ModelLike, ::Type{T}) where T
4949
add_bridge(bridged_model, LessToGreaterBridge{T})
5050
add_bridge(bridged_model, NonnegToNonposBridge{T})
5151
add_bridge(bridged_model, NonposToNonnegBridge{T})
52+
add_bridge(bridged_model, ScalarizeBridge{T})
5253
add_bridge(bridged_model, VectorizeBridge{T})
5354
add_bridge(bridged_model, ScalarSlackBridge{T})
5455
add_bridge(bridged_model, VectorSlackBridge{T})
@@ -71,6 +72,8 @@ include("flip_sign_bridge.jl")
7172
@bridge NonposToNonneg NonposToNonnegBridge () () (MOI.Nonpositives,) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
7273
include("vectorizebridge.jl")
7374
@bridge Vectorize VectorizeBridge () (MOI.EqualTo, MOI.LessThan, MOI.GreaterThan,) () () (MOI.SingleVariable,) (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () ()
75+
include("scalarizebridge.jl")
76+
@bridge Scalarize ScalarizeBridge () () (MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives) () () () (MOI.VectorOfVariables,) (MOI.VectorAffineFunction, MOI.VectorQuadraticFunction)
7477
include("slackbridge.jl")
7578
@bridge ScalarSlack ScalarSlackBridge () (MOI.Interval, MOI.LessThan, MOI.GreaterThan) () () () (MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction) () ()
7679
@bridge(VectorSlack, VectorSlackBridge, (), (),

src/Bridges/scalarizebridge.jl

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
const VectorLPSet = Union{MOI.Zeros, MOI.Nonnegatives, MOI.Nonpositives}
2+
3+
scalar_set_type(::Type{<:MOI.Zeros}, T::Type) = MOI.EqualTo{T}
4+
scalar_set_type(::Type{<:MOI.Nonpositives}, T::Type) = MOI.LessThan{T}
5+
scalar_set_type(::Type{<:MOI.Nonnegatives}, T::Type) = MOI.GreaterThan{T}
6+
7+
__constant(f::Union{MOI.ScalarAffineFunction, MOI.ScalarQuadraticFunction}, T::DataType) = MOI._constant(f)
8+
__constant(f::Union{MOI.VectorAffineFunction, MOI.VectorQuadraticFunction}, T::DataType) = MOI._constant(f)
9+
__constant(f::MOI.SingleVariable, T::DataType) = zero(T)
10+
__constant(f::MOI.VectorOfVariables, T::DataType) = zeros(T, length(f.variables))
11+
12+
"""
13+
ScalarizeBridge{T}
14+
15+
Transforms a constraint `AbstractVectorFunction`-in-`S` where `S <: LPCone` to
16+
`AbstactVectorFunction`-in-`scalar_set_type(S)`.
17+
"""
18+
mutable struct ScalarizeBridge{T, F, S} <: AbstractBridge
19+
scalar_constraints::Vector{CI{F, S}}
20+
constants::Vector{T}
21+
end
22+
function ScalarizeBridge{T, F, S}(model::MOI.ModelLike,
23+
f::MOI.AbstractVectorFunction,
24+
set::VectorLPSet) where {T, F, S}
25+
dimension = MOI.output_dimension(f)
26+
constants = __constant(f, T)
27+
new_f = MOIU.scalarize(f, true)
28+
constraints = Vector{CI{F, S}}(undef, dimension)
29+
for i in 1:dimension
30+
constraints[i] = MOIU.add_scalar_constraint(model, new_f[i], S(-constants[i]))
31+
end
32+
return ScalarizeBridge{T, F, S}(constraints, constants)
33+
end
34+
35+
function MOI.supports_constraint(::Type{ScalarizeBridge{T}},
36+
::Type{<:MOI.AbstractVectorFunction},
37+
::Type{<:VectorLPSet}) where T
38+
return true
39+
end
40+
function added_constraint_types(::Type{ScalarizeBridge{T, F, S}}) where {T, F, S}
41+
return [(F, S)]
42+
end
43+
function concrete_bridge_type(::Type{<:ScalarizeBridge{T}},
44+
F::Type{<:MOI.AbstractVectorFunction},
45+
S::Type{<:VectorLPSet}) where T
46+
return ScalarizeBridge{T, MOIU.scalar_type(F), scalar_set_type(S, T)}
47+
end
48+
49+
# Attributes, Bridge acting as an model
50+
function MOI.get(bridge::ScalarizeBridge{T, F, S},
51+
::MOI.NumberOfConstraints{F, S}) where {T, F, S}
52+
return length(bridge.scalar_constraints)
53+
end
54+
function MOI.get(bridge::ScalarizeBridge{T, F, S},
55+
::MOI.ListOfConstraintIndices{F, S}) where {T, F, S}
56+
return bridge.scalar_constraints
57+
end
58+
59+
# References
60+
function MOI.delete(model::MOI.ModelLike, bridge::ScalarizeBridge)
61+
MOI.delete.(model, bridge.scalar_constraints)
62+
nothing
63+
end
64+
65+
# Attributes, Bridge acting as a constraint
66+
67+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintPrimal,
68+
bridge::ScalarizeBridge)
69+
return MOI.get.(model, attr, bridge.scalar_constraints) .+ bridge.constants
70+
end
71+
function MOI.get(model::MOI.ModelLike, attr::MOI.ConstraintDual,
72+
bridge::ScalarizeBridge)
73+
return MOI.get.(model, attr, bridge.scalar_constraints)
74+
end
75+
function MOI.modify(model::MOI.ModelLike, bridge::ScalarizeBridge{T,F,S},
76+
change::MOI.VectorConstantChange{T}) where {T,F,S}
77+
bridge.constants = change.new_constant
78+
MOI.set.(model, MOI.ConstraintSet(), bridge.scalar_constraints,
79+
S.(-change.new_constant))
80+
end
81+
function MOI.modify(model::MOI.ModelLike, bridge::ScalarizeBridge,
82+
change::MOI.MultirowChange{T}) where T
83+
for (index, value) in change.new_coefficients
84+
MOI.modify(model, bridge.scalar_constraints[index],
85+
MOI.ScalarCoefficientChange{T}(change.variable, value))
86+
end
87+
nothing
88+
end
89+
function MOI.set(model::MOI.ModelLike, ::MOI.ConstraintFunction,
90+
bridge::ScalarizeBridge{T}, func) where T
91+
old_constants = bridge.constants
92+
bridge.constants = __constant(func, T)
93+
new_func = MOIU.scalarize(func, true)
94+
MOI.set.(model, MOI.ConstraintFunction(), bridge.scalar_constraints,
95+
new_func)
96+
for i in eachindex(bridge.constants)
97+
if bridge.constants[i] != old_constants[i]
98+
MOI.set(model, MOI.ConstraintSet(), bridge.scalar_constraints,
99+
S(-bridge.constants[i]))
100+
end
101+
end
102+
end
103+
104+
# TODO implement transform

src/Utilities/functions.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1405,3 +1405,56 @@ function operate(::typeof(vcat), ::Type{T},
14051405
fill_vector(constant, T, 0, 0, fill_constant, output_dim, funcs...)
14061406
return VAF(terms, constant)
14071407
end
1408+
1409+
# Similar to `eachscalar` but faster, see
1410+
# https://github.com/JuliaOpt/MathOptInterface.jl/issues/418
1411+
function scalarize(f::MOI.VectorOfVariables, ignore_constants::Bool = false)
1412+
MOI.SingleVariable.(f.variables)
1413+
end
1414+
function scalarize(f::MOI.VectorAffineFunction{T}, ignore_constants::Bool = false) where T
1415+
dimension = MOI.output_dimension(f)
1416+
constants = ignore_constants ? zeros(T, dimension) : MOI._constant(f)
1417+
counting = count_terms(dimension, f.terms)
1418+
functions = MOI.ScalarAffineFunction{T}[
1419+
MOI.ScalarAffineFunction{T}(MOI.ScalarAffineTerm{T}[], constants[i]) for i in 1:dimension]
1420+
for i in 1:dimension
1421+
sizehint!(functions[i].terms, counting[i])
1422+
end
1423+
for term in f.terms
1424+
push!(functions[term.output_index].terms, term.scalar_term)
1425+
end
1426+
return functions
1427+
end
1428+
function scalarize(f::MOI.VectorQuadraticFunction{T}, ignore_constants::Bool = false) where T
1429+
dimension = MOI.output_dimension(f)
1430+
constants = ignore_constants ? zeros(T, dimension) : MOI._constant(f)
1431+
counting_scalars = count_terms(dimension, f.affine_terms)
1432+
counting_quadratics = count_terms(dimension, f.quadratic_terms)
1433+
functions = MOI.ScalarQuadraticFunction{T}[
1434+
MOI.ScalarQuadraticFunction{T}(MOI.ScalarAffineTerm{T}[], ScalarQuadraticTerm{T}[], constants[i]) for i in 1:dimension]
1435+
functions = MOI.ScalarQuadraticFunction.(
1436+
MOI.ScalarAffineTerm{T}[], ScalarQuadraticTerm{T}[], constants)
1437+
for i in 1:dimension
1438+
sizehint!(functions[i].affine_terms, counting_scalars[i])
1439+
sizehint!(functions[i].quadratic_terms, counting_quadratics[i])
1440+
end
1441+
for term in f.affine_terms
1442+
push!(functions[term.output_index].affine_terms, term.scalar_term)
1443+
end
1444+
for term in f.quadratic_terms
1445+
push!(functions[term.output_index].quadratic_terms, term.scalar_term)
1446+
end
1447+
return functions
1448+
end
1449+
1450+
function count_terms(counting::Vector{<:Integer}, terms::Vector{T}) where T
1451+
for term in terms
1452+
counting[term.output_index] += 1
1453+
end
1454+
return nothing
1455+
end
1456+
function count_terms(dimension::I, terms::Vector{T}) where {I,T}
1457+
counting = zeros(I, dimension)
1458+
count_terms(counting, terms)
1459+
return counting
1460+
end

test/bridge.jl

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ MOI.is_set_by_optimize(::UnknownConstraintAttribute) = true
3232
# Test deletion of bridge
3333
function test_delete_bridge(m::MOIB.AbstractBridgeOptimizer,
3434
ci::MOI.ConstraintIndex{F, S}, nvars::Int,
35-
nocs::Tuple) where {F, S}
35+
nocs::Tuple; used_bridges = 1) where {F, S}
36+
num_bridges = length(m.bridges)
3637
@test MOI.get(m, MOI.NumberOfVariables()) == nvars
3738
test_noc(m, F, S, 1)
3839
for noc in nocs
@@ -47,7 +48,7 @@ function test_delete_bridge(m::MOIB.AbstractBridgeOptimizer,
4748
@test err.index == ci
4849
end
4950
@test !MOI.is_valid(m, ci)
50-
@test isempty(m.bridges)
51+
@test length(m.bridges) == num_bridges - used_bridges
5152
test_noc(m, F, S, 0)
5253
# As the bridge has been removed, if the constraints it has created where not removed, it wouldn't be there to decrease this counter anymore
5354
@test MOI.get(m, MOI.NumberOfVariables()) == nvars
@@ -346,8 +347,8 @@ end
346347
ci = first(MOI.get(full_bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.RootDetConeTriangle}()))
347348
test_delete_bridge(full_bridged_mock, ci, 4, ((MOI.VectorAffineFunction{Float64}, MOI.RotatedSecondOrderCone, 0),
348349
(MOI.VectorAffineFunction{Float64}, MOI.GeometricMeanCone, 0),
349-
(MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0)))
350-
350+
(MOI.VectorAffineFunction{Float64}, MOI.PositiveSemidefiniteConeTriangle, 0)),
351+
used_bridges = 3)
351352
end
352353

353354
@testset "Continuous Linear" begin
@@ -466,6 +467,52 @@ end
466467
MOI.Nonnegatives, 0),))
467468
end
468469

470+
@testset "Scalarize" begin
471+
bridged_mock = MOIB.Scalarize{Float64}(mock)
472+
# VectorOfVariables-in-Nonnegatives
473+
# VectorAffineFunction-in-Zeros
474+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 2.0],
475+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-3, -1])
476+
MOIT.lin1vtest(bridged_mock, config)
477+
ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}()))
478+
test_delete_bridge(bridged_mock, ci, 3,
479+
((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0),
480+
(MOI.SingleVariable, MOI.GreaterThan{Float64}, 0)))
481+
ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorOfVariables, MOI.Nonnegatives}()))
482+
test_delete_bridge(bridged_mock, ci, 3,
483+
((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0),
484+
(MOI.SingleVariable, MOI.GreaterThan{Float64}, 0)))
485+
# VectorAffineFunction-in-Nonnegatives
486+
# VectorAffineFunction-in-Zeros
487+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [1.0, 0.0, 2.0],
488+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0, 2, 0],
489+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [-3, -1])
490+
MOIT.lin1ftest(bridged_mock, config)
491+
ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Zeros}()))
492+
test_delete_bridge(bridged_mock, ci, 3,
493+
((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0),
494+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0)))
495+
ci = first(MOI.get(bridged_mock, MOI.ListOfConstraintIndices{MOI.VectorAffineFunction{Float64}, MOI.Nonnegatives}()))
496+
test_delete_bridge(bridged_mock, ci, 3,
497+
((MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}, 0),
498+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}, 0)))
499+
# VectorOfVariables-in-Nonnegatives
500+
# VectorOfVariables-in-Nonpositives
501+
# VectorOfVariables-in-Zeros
502+
# VectorAffineFunction-in-Zeros
503+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-4, -3, 16, 0],
504+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [7, 2, -4])
505+
MOIT.lin2vtest(bridged_mock, config)
506+
# VectorAffineFunction-in-Nonnegatives
507+
# VectorAffineFunction-in-Nonpositives
508+
# VectorAffineFunction-in-Zeros
509+
mock.optimize! = (mock::MOIU.MockOptimizer) -> MOIU.mock_optimize!(mock, [-4, -3, 16, 0],
510+
(MOI.ScalarAffineFunction{Float64}, MOI.GreaterThan{Float64}) => [0],
511+
(MOI.ScalarAffineFunction{Float64}, MOI.LessThan{Float64}) => [0],
512+
(MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [7, 2, -4, 7])
513+
MOIT.lin2ftest(bridged_mock, config)
514+
end
515+
469516
@testset "Vectorize" begin
470517
bridged_mock = MOIB.Vectorize{Float64}(mock)
471518

0 commit comments

Comments
 (0)