Skip to content

Commit c55e167

Browse files
authored
Merge pull request #481 from JuliaOpt/bl/quad
Improve quadratic support in MOIU
2 parents 73e92a4 + e0a8287 commit c55e167

File tree

9 files changed

+289
-60
lines changed

9 files changed

+289
-60
lines changed

src/Bridges/detbridge.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ Constrains ``t \\le l_1 + \\cdots + l_n`` where `n` is the length of `l` and ret
108108
function subsum(model, t::MOI.ScalarAffineFunction, l::Vector{MOI.VariableIndex}, ::Type{T}) where T
109109
n = length(l)
110110
f = MOIU.operate!(-, T, t, MOIU.operate(sum, T, l))
111-
return MOIU.add_scalar_constraint(model, f, MOI.LessThan(zero(T)))
111+
return MOIU.add_scalar_constraint(model, f, MOI.LessThan(zero(T)),
112+
allow_modify_function=true)
112113
end
113114

114115
# Attributes, Bridge acting as an model

src/Bridges/geomeanbridge.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ function GeoMeanBridge{T, F, G}(model, f::MOI.AbstractVectorFunction,
7070
# With sqrt(2)^l*t - xl1, we should scale both the ConstraintPrimal and ConstraintDual
7171
tubc = MOIU.add_scalar_constraint(model,
7272
MOIU.operate!(+, T, t, -sN * xl1),
73-
MOI.LessThan(zero(T)))
73+
MOI.LessThan(zero(T)),
74+
allow_modify_function=true)
7475

7576
socrc = Vector{CI{G, MOI.RotatedSecondOrderCone}}(undef, N-1)
7677
offset = offsetnext = 0

src/Bridges/squarepsdbridge.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ function SquarePSDBridge{T, F, G}(model::MOI.ModelLike, f::F,
9595
# functions at entries (i, j) and (j, i) are almost identical
9696
if !MOIU.isapprox_zero(diff, 1e-10)
9797
push!(sym, (i, j) => MOIU.add_scalar_constraint(model, diff,
98-
MOI.EqualTo(zero(T))))
98+
MOI.EqualTo(zero(T)),
99+
allow_modify_function=true))
99100
end
100101
end
101102
k += dim - j

src/Utilities/constraints.jl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
1+
"""
2+
add_scalar_constraint(model::MOI.ModelLike,
3+
func::MOI.AbstractScalarFunction,
4+
set::MOI.AbstractScalarSet;
5+
allow_modify_function::Bool=false)
6+
7+
Adds the scalar constraint obtained by moving the constant term in `func` to
8+
the set in `model`. If `allow_modify_function` is `true` then the function
9+
`func`, can be modified.
10+
"""
11+
function add_scalar_constraint end
12+
13+
function add_scalar_constraint(model::MOI.ModelLike, func::MOI.SingleVariable,
14+
set::MOI.AbstractScalarSet)
15+
return MOI.addconstraint!(model, func, set)
16+
end
117
function add_scalar_constraint(model::MOI.ModelLike,
218
func::Union{MOI.ScalarAffineFunction{T},
319
MOI.ScalarQuadraticFunction{T}},
4-
set::MOI.AbstractScalarSet) where T
20+
set::MOI.AbstractScalarSet;
21+
allow_modify_function::Bool=false) where T
522
set = shift_constant(set, -func.constant)
23+
if !allow_modify_function
24+
func = copy(func)
25+
end
626
func.constant = zero(T)
727
return MOI.addconstraint!(model, func, set)
828
end

src/Utilities/functions.jl

Lines changed: 153 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,12 @@ function mapvariables(varmap::Function, f::MOI.VectorOfVariables)
7474
return MOI.VectorOfVariables(varmap.(f.variables))
7575
end
7676
function mapvariables(varmap::Function, f::Union{SAF, VAF})
77-
typeof(f)(mapvariable.(varmap, f.terms), _constant(f))
77+
typeof(f)(mapvariable.(varmap, f.terms), MOI._constant(f))
7878
end
7979
function mapvariables(varmap::Function, f::Union{SQF, VQF})
8080
lin = mapvariable.(varmap, f.affine_terms)
8181
quad = mapvariable.(varmap, f.quadratic_terms)
82-
return typeof(f)(lin, quad, _constant(f))
82+
return typeof(f)(lin, quad, MOI._constant(f))
8383
end
8484
mapvariables(varmap, f::MOI.AbstractFunction) = mapvariables(vi -> varmap[vi], f)
8585
mapvariables(varmap::Function, change::Union{MOI.ScalarConstantChange, MOI.VectorConstantChange}) = change
@@ -93,9 +93,6 @@ function mapvariables(varmap, f::MOI.AbstractFunctionModification)
9393
return mapvariables(vi -> varmap[vi], f)
9494
end
9595

96-
# Constant field
97-
_constant(f::Union{SAF, SQF}) = f.constant
98-
_constant(f::Union{VAF, VQF}) = f.constants
9996
# Vector of constants
10097
constant(f::Union{SAF, SQF}) = [f.constant]
10198
constant(f::Union{VAF, VQF}) = f.constants
@@ -181,14 +178,6 @@ function Base.getindex(it::ScalarFunctionIterator{VAF{T}}, I::AbstractVector) wh
181178
VAF(terms, constant)
182179
end
183180

184-
"""
185-
termindices(t::Union{MOI.ScalarAffineTerm, MOI.VectorAffineTerm})
186-
187-
Returns the indices of the input term `t` as a tuple of `Int`s. For `t::MOI.ScalarAffineTerm`, this is a 1-tuple of the variable index. For `t::MOI.VectorAffineTerm`, this is a 2-tuple of the row/output and variable indices of the term.
188-
"""
189-
termindices(t::MOI.ScalarAffineTerm) = (t.variable_index.value,)
190-
termindices(t::MOI.VectorAffineTerm) = (t.output_index, termindices(t.scalar_term)...)
191-
192181
"""
193182
unsafe_add(t1::MOI.ScalarAffineTerm, t2::MOI.ScalarAffineTerm)
194183
@@ -199,33 +188,40 @@ function unsafe_add(t1::MOI.ScalarAffineTerm, t2::MOI.ScalarAffineTerm)
199188
end
200189

201190
"""
202-
unsafe_add(t1::MOI.VectorAffineTerm, t2::MOI.VectorAffineTerm)
191+
unsafe_add(t1::MOI.ScalarQuadraticTerm, t2::MOI.ScalarQuadraticTerm)
203192
204-
Sums the coefficients of `t1` and `t2` and returns an output `MOI.VectorAffineTerm`. It is unsafe because it uses the `output_index` and `variable_index` of `t1` as the `output_index` and `variable_index` of the output term without checking that they are equal to those of `t2`.
193+
Sums the coefficients of `t1` and `t2` and returns an output
194+
`MOI.ScalarQuadraticTerm`. It is unsafe because it uses the `variable_index`'s
195+
of `t1` as the `variable_index`'s of the output without checking that they are
196+
the same (up to permutation) to those of `t2`.
205197
"""
206-
function unsafe_add(t1::MOI.VectorAffineTerm, t2::MOI.VectorAffineTerm)
207-
coefficient = t1.scalar_term.coefficient + t2.scalar_term.coefficient
208-
scalar_term = MOI.ScalarAffineTerm(coefficient, t1.scalar_term.variable_index)
209-
return MOI.VectorAffineTerm(t1.output_index, scalar_term)
198+
function unsafe_add(t1::MOI.ScalarQuadraticTerm, t2::MOI.ScalarQuadraticTerm)
199+
return MOI.ScalarQuadraticTerm(t1.coefficient + t2.coefficient,
200+
t1.variable_index_1,
201+
t1.variable_index_2)
210202
end
211203

212204
"""
213-
coefficient(t::Union{MOI.ScalarAffineTerm, MOI.VectorAffineTerm})
205+
unsafe_add(t1::MOI.VectorAffineTerm, t2::MOI.VectorAffineTerm)
214206
215-
Finds the coefficient associated with the term `t`.
207+
Sums the coefficients of `t1` and `t2` and returns an output `MOI.VectorAffineTerm`. It is unsafe because it uses the `output_index` and `variable_index` of `t1` as the `output_index` and `variable_index` of the output term without checking that they are equal to those of `t2`.
216208
"""
217-
coefficient(t::MOI.ScalarAffineTerm) = t.coefficient
218-
coefficient(t::MOI.VectorAffineTerm) = t.scalar_term.coefficient
219-
209+
function unsafe_add(t1::VT, t2::VT) where VT <: Union{MOI.VectorAffineTerm,
210+
MOI.VectorQuadraticTerm}
211+
scalar_term = unsafe_add(t1.scalar_term, t2.scalar_term)
212+
return MOI.VectorAffineTerm(t1.output_index, scalar_term)
213+
end
220214

221215
"""
222-
iscanonical(f::Union{ScalarAffineFunction, VectorAffineFunction})
216+
iscanonical(f::Union{ScalarAffineFunction, ScalarQuadraticFunction
217+
VectorAffineFunction, VectorQuadraticTerm})
223218
224219
Returns a Bool indicating whether the function is in canonical form.
225220
See [`canonical`](@ref).
226221
"""
227-
function iscanonical(f::Union{SAF, VAF})
228-
is_strictly_sorted(f.terms, termindices, t -> !iszero(coefficient(t)))
222+
function iscanonical(f::Union{SAF, VAF, SQF, VQF})
223+
is_strictly_sorted(f.terms, MOI.term_indices,
224+
t -> !iszero(MOI.coefficient(t)))
229225
end
230226

231227
"""
@@ -266,16 +262,31 @@ If `x` (resp. `y`, `z`) is `VariableIndex(1)` (resp. 2, 3).
266262
The canonical representation of `ScalarAffineFunction([y, x, z, x, z], [2, 1, 3, -2, -3], 5)` is `ScalarAffineFunction([x, y], [-1, 2], 5)`.
267263
268264
"""
269-
canonical(f::Union{SAF, VAF}) = canonicalize!(copy(f))
265+
canonical(f::Union{SAF, VAF, SQF, VQF}) = canonicalize!(copy(f))
270266

271267
"""
272268
canonicalize!(f::Union{ScalarAffineFunction, VectorAffineFunction})
273269
274-
Convert a function to canonical form in-place, without allocating a copy to hold the result.
275-
See [`canonical`](@ref).
270+
Convert a function to canonical form in-place, without allocating a copy to hold
271+
the result. See [`canonical`](@ref).
276272
"""
277273
function canonicalize!(f::Union{SAF, VAF})
278-
sort_and_compress!(f.terms, termindices, t -> !iszero(coefficient(t)), unsafe_add)
274+
sort_and_compress!(f.terms, MOI.term_indices,
275+
t -> !iszero(MOI.coefficient(t)), unsafe_add)
276+
return f
277+
end
278+
279+
"""
280+
canonicalize!(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})
281+
282+
Convert a function to canonical form in-place, without allocating a copy to hold
283+
the result. See [`canonical`](@ref).
284+
"""
285+
function canonicalize!(f::Union{SQF, VQF})
286+
sort_and_compress!(f.affine_terms, MOI.term_indices,
287+
t -> !iszero(MOI.coefficient(t)), unsafe_add)
288+
sort_and_compress!(f.quadratic_terms, MOI.term_indices,
289+
t -> !iszero(MOI.coefficient(t)), unsafe_add)
279290
return f
280291
end
281292

@@ -355,7 +366,7 @@ isapprox_zero(α::AbstractFloat, tol) = -tol < α < tol
355366
isapprox_zero::Union{Integer, Rational}, tol) = iszero(α)
356367
function isapprox_zero(t::Union{MOI.ScalarAffineTerm,
357368
MOI.ScalarQuadraticTerm}, tol)
358-
isapprox_zero(t.coefficient, tol)
369+
isapprox_zero(MOI.coefficient(t), tol)
359370
end
360371

361372
"""
@@ -453,11 +464,11 @@ function removevariable(f::VVF, vi)
453464
VVF(_rmvar(f.variables, vi))
454465
end
455466
function removevariable(f::Union{SAF, VAF}, vi)
456-
typeof(f)(_rmvar(f.terms, vi), _constant(f))
467+
typeof(f)(_rmvar(f.terms, vi), MOI._constant(f))
457468
end
458469
function removevariable(f::Union{SQF, VQF}, vi)
459470
terms = _rmvar.((f.affine_terms, f.quadratic_terms), Ref(vi))
460-
typeof(f)(terms..., _constant(f))
471+
typeof(f)(terms..., MOI._constant(f))
461472
end
462473

463474
"""
@@ -592,6 +603,11 @@ function operate_term(::typeof(*), α::T, t::MOI.ScalarQuadraticTerm{T}) where T
592603
MOI.ScalarQuadraticTerm* t.coefficient, t.variable_index_1,
593604
t.variable_index_2)
594605
end
606+
function operate_term(::typeof(*), t1::MOI.ScalarAffineTerm,
607+
t2::MOI.ScalarAffineTerm)
608+
MOI.ScalarQuadraticTerm(t1.coefficient * t2.coefficient, t1.variable_index,
609+
t2.variable_index)
610+
end
595611

596612
function operate_term(::typeof(/), t::MOI.ScalarAffineTerm{T}, α::T) where T
597613
MOI.ScalarAffineTerm(t.coefficient / α, t.variable_index)
@@ -641,6 +657,12 @@ function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T},
641657
end
642658

643659
## operate!
660+
# + with at least 3 arguments
661+
function operate!(op::typeof(+), ::Type{T}, f, g, h, args...) where T
662+
operate!(op, T, f, g)
663+
return operate!(+, T, f, h, args...)
664+
end
665+
644666
# Scalar Variable +/- ...
645667
function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T},
646668
f::MOI.SingleVariable,
@@ -667,6 +689,11 @@ function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T},
667689
f.constant = op(f.constant, g.constant)
668690
return f
669691
end
692+
function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T},
693+
f::MOI.ScalarAffineFunction{T},
694+
g::MOI.ScalarQuadraticFunction{T}) where T
695+
return operate(op, T, f, g)
696+
end
670697
# Scalar Quadratic +/-! ...
671698
function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T},
672699
f::MOI.ScalarQuadraticFunction{T},
@@ -697,7 +724,27 @@ function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T},
697724
end
698725

699726
## operate
727+
# + with at least 3 arguments, can use in-place as the user cannot use
728+
# intermediate results
729+
function operate(op::typeof(+), ::Type{T}, f, g, h, args...) where T
730+
return operate!(+, T, operate(+, T, f, g), h, args...)
731+
end
732+
733+
# Scalar number +/- ...
734+
function operate(op::typeof(+), ::Type{T}, α::T, f::ScalarLike{T}) where T
735+
return operate(op, T, f, α)
736+
end
737+
function operate(op::typeof(-), ::Type{T}, α::T, f::ScalarLike{T}) where T
738+
return operate!(+, T, operate(-, T, f), α)
739+
end
740+
700741
# Scalar Variable +/- ...
742+
function operate(op::Union{typeof(+), typeof(-)}, ::Type{T},
743+
f::MOI.SingleVariable, α::T) where T
744+
return MOI.ScalarAffineFunction{T}([MOI.ScalarAffineTerm(one(T),
745+
f.variable)],
746+
op(α))
747+
end
701748
function operate(op::Union{typeof(+), typeof(-)}, ::Type{T},
702749
f::MOI.SingleVariable,
703750
g::MOI.SingleVariable) where T
@@ -745,14 +792,20 @@ end
745792
function Base.:+(args::ScalarLike{T}...) where T
746793
return operate(+, T, args...)
747794
end
748-
function Base.:+(f::ScalarLike{T}, g::T) where T
749-
return operate(+, T, f, g)
795+
function Base.:+::T, f::ScalarLike{T}...) where T
796+
return operate(+, T, α, f...)
797+
end
798+
function Base.:+(f::ScalarLike{T}, α::T) where T
799+
return operate(+, T, f, α)
750800
end
751801
function Base.:-(args::ScalarLike{T}...) where T
752802
return operate(-, T, args...)
753803
end
754-
function Base.:-(f::ScalarLike{T}, g::T) where T
755-
return operate(-, T, f, g)
804+
function Base.:-(f::ScalarLike{T}, α::T) where T
805+
return operate(-, T, f, α)
806+
end
807+
function Base.:-::T, f::ScalarLike{T}) where T
808+
return operate(-, T, α, f)
756809
end
757810

758811
####################################### * ######################################
@@ -761,6 +814,14 @@ function promote_operation(::typeof(*), ::Type{T}, ::Type{T},
761814
MOI.ScalarAffineFunction{T}}}) where T
762815
return MOI.ScalarAffineFunction{T}
763816
end
817+
function promote_operation(::typeof(*), ::Type{T},
818+
::Type{<:Union{MOI.SingleVariable,
819+
MOI.ScalarAffineFunction{T}}},
820+
::Type{<:Union{MOI.SingleVariable,
821+
MOI.ScalarAffineFunction{T}}}) where T
822+
return MOI.ScalarQuadraticFunction{T}
823+
end
824+
764825

765826
function operate!(::typeof(*), ::Type{T}, f::MOI.SingleVariable, α::T) where T
766827
return operate(*, T, α, f)
@@ -785,6 +846,60 @@ function operate(::typeof(*), ::Type{T}, α::T,
785846
return operate!(*, T, copy(f), α)
786847
end
787848

849+
function operate(::typeof(*), ::Type{T}, f::MOI.SingleVariable,
850+
g::MOI.SingleVariable) where T
851+
return MOI.ScalarQuadraticFunction(MOI.ScalarAffineTerm{T}[],
852+
[MOI.ScalarQuadraticTerm(one(T),
853+
f.variable,
854+
g.variable)],
855+
zero(T))
856+
end
857+
858+
function operate(::typeof(*), ::Type{T}, f::MOI.ScalarAffineFunction{T},
859+
g::MOI.SingleVariable) where T
860+
aff_terms = [MOI.ScalarAffineTerm(f.constant, g.variable)]
861+
quad_terms = map(t -> MOI.ScalarQuadraticTerm(t.coefficient,
862+
t.variable_index,
863+
g.variable),
864+
f.terms)
865+
return MOI.ScalarQuadraticFunction(aff_terms, quad_terms, zero(T))
866+
end
867+
868+
function operate(::typeof(*), ::Type{T}, f::MOI.ScalarAffineFunction{T},
869+
g::MOI.ScalarAffineFunction{T}) where T
870+
nfterms = length(f.terms)
871+
ngterms = length(g.terms)
872+
quad_terms = Vector{MOI.ScalarQuadraticTerm{T}}(undef, nfterms * ngterms)
873+
k = 0
874+
for t1 in f.terms
875+
for t2 in g.terms
876+
k += 1
877+
quad_terms[k] = operate_term(*, t1, t2)
878+
end
879+
end
880+
@assert k == length(quad_terms)
881+
if iszero(f.constant)
882+
if iszero(g.constant)
883+
aff_terms = MOI.ScalarAffineTerm{T}[]
884+
else
885+
aff_terms = operate_term.(*, g.constant, f.terms)
886+
end
887+
else
888+
if iszero(g.constant)
889+
aff_terms = operate_term.(*, f.constant, g.terms)
890+
else
891+
aff_terms = Vector{MOI.ScalarAffineTerm{T}}(undef,
892+
nfterms + ngterms)
893+
map!(t -> operate_term(*, g.constant, t), aff_terms, f.terms)
894+
for i in 1:ngterms
895+
aff_terms[nfterms + i] = operate_term(*, f.constant, g.terms[i])
896+
end
897+
end
898+
end
899+
constant = f.constant * g.constant
900+
return MOI.ScalarQuadraticFunction(aff_terms, quad_terms, constant)
901+
end
902+
788903
function Base.:*(args::ScalarLike{T}...) where T
789904
return operate(*, T, args...)
790905
end

0 commit comments

Comments
 (0)