Skip to content

Commit 412ad2e

Browse files
committed
Improve quadratic support in MOIU
1 parent 19d85b0 commit 412ad2e

File tree

4 files changed

+219
-56
lines changed

4 files changed

+219
-56
lines changed

src/Utilities/constraints.jl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
function add_scalar_constraint(model::MOI.ModelLike, func::MOI.SingleVariable,
2+
set::MOI.AbstractScalarSet)
3+
return MOI.addconstraint!(model, func, set)
4+
end
15
function add_scalar_constraint(model::MOI.ModelLike,
26
func::Union{MOI.ScalarAffineFunction{T},
37
MOI.ScalarQuadraticFunction{T}},

src/Utilities/functions.jl

Lines changed: 147 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,39 @@ 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.ScalarAffineTerm(t1.coefficient + t2.coefficient,
200+
t1.variable_index)
210201
end
211202

212203
"""
213-
coefficient(t::Union{MOI.ScalarAffineTerm, MOI.VectorAffineTerm})
204+
unsafe_add(t1::MOI.VectorAffineTerm, t2::MOI.VectorAffineTerm)
214205
215-
Finds the coefficient associated with the term `t`.
206+
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`.
216207
"""
217-
coefficient(t::MOI.ScalarAffineTerm) = t.coefficient
218-
coefficient(t::MOI.VectorAffineTerm) = t.scalar_term.coefficient
219-
208+
function unsafe_add(t1::VT, t2::VT) where VT <: Union{MOI.VectorAffineTerm,
209+
MOI.VectorQuadraticTerm}
210+
scalar_term = unsafe_add(t1.scalar_term, t2.scalar_term)
211+
return MOI.VectorAffineTerm(t1.output_index, scalar_term)
212+
end
220213

221214
"""
222-
iscanonical(f::Union{ScalarAffineFunction, VectorAffineFunction})
215+
iscanonical(f::Union{ScalarAffineFunction, ScalarQuadraticFunction
216+
VectorAffineFunction, VectorQuadraticTerm})
223217
224218
Returns a Bool indicating whether the function is in canonical form.
225219
See [`canonical`](@ref).
226220
"""
227-
function iscanonical(f::Union{SAF, VAF})
228-
is_strictly_sorted(f.terms, termindices, t -> !iszero(coefficient(t)))
221+
function iscanonical(f::Union{SAF, VAF, SQF, VQF})
222+
is_strictly_sorted(f.terms, MOI.term_indices,
223+
t -> !iszero(MOI.coefficient(t)))
229224
end
230225

231226
"""
@@ -266,16 +261,31 @@ If `x` (resp. `y`, `z`) is `VariableIndex(1)` (resp. 2, 3).
266261
The canonical representation of `ScalarAffineFunction([y, x, z, x, z], [2, 1, 3, -2, -3], 5)` is `ScalarAffineFunction([x, y], [-1, 2], 5)`.
267262
268263
"""
269-
canonical(f::Union{SAF, VAF}) = canonicalize!(copy(f))
264+
canonical(f::Union{SAF, VAF, SQF, VQF}) = canonicalize!(copy(f))
270265

271266
"""
272267
canonicalize!(f::Union{ScalarAffineFunction, VectorAffineFunction})
273268
274-
Convert a function to canonical form in-place, without allocating a copy to hold the result.
275-
See [`canonical`](@ref).
269+
Convert a function to canonical form in-place, without allocating a copy to hold
270+
the result. See [`canonical`](@ref).
276271
"""
277272
function canonicalize!(f::Union{SAF, VAF})
278-
sort_and_compress!(f.terms, termindices, t -> !iszero(coefficient(t)), unsafe_add)
273+
sort_and_compress!(f.terms, MOI.term_indices,
274+
t -> !iszero(MOI.coefficient(t)), unsafe_add)
275+
return f
276+
end
277+
278+
"""
279+
canonicalize!(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})
280+
281+
Convert a function to canonical form in-place, without allocating a copy to hold
282+
the result. See [`canonical`](@ref).
283+
"""
284+
function canonicalize!(f::Union{SQF, VQF})
285+
sort_and_compress!(f.affine_terms, MOI.term_indices,
286+
t -> !iszero(MOI.coefficient(t)), unsafe_add)
287+
sort_and_compress!(f.quadratic_terms, MOI.term_indices,
288+
t -> !iszero(MOI.coefficient(t)), unsafe_add)
279289
return f
280290
end
281291

@@ -355,7 +365,7 @@ isapprox_zero(α::AbstractFloat, tol) = -tol < α < tol
355365
isapprox_zero::Union{Integer, Rational}, tol) = iszero(α)
356366
function isapprox_zero(t::Union{MOI.ScalarAffineTerm,
357367
MOI.ScalarQuadraticTerm}, tol)
358-
isapprox_zero(t.coefficient, tol)
368+
isapprox_zero(MOI.coefficient(t), tol)
359369
end
360370

361371
"""
@@ -453,11 +463,11 @@ function removevariable(f::VVF, vi)
453463
VVF(_rmvar(f.variables, vi))
454464
end
455465
function removevariable(f::Union{SAF, VAF}, vi)
456-
typeof(f)(_rmvar(f.terms, vi), _constant(f))
466+
typeof(f)(_rmvar(f.terms, vi), MOI._constant(f))
457467
end
458468
function removevariable(f::Union{SQF, VQF}, vi)
459469
terms = _rmvar.((f.affine_terms, f.quadratic_terms), Ref(vi))
460-
typeof(f)(terms..., _constant(f))
470+
typeof(f)(terms..., MOI._constant(f))
461471
end
462472

463473
"""
@@ -592,6 +602,11 @@ function operate_term(::typeof(*), α::T, t::MOI.ScalarQuadraticTerm{T}) where T
592602
MOI.ScalarQuadraticTerm* t.coefficient, t.variable_index_1,
593603
t.variable_index_2)
594604
end
605+
function operate_term(::typeof(*), t1::MOI.ScalarAffineTerm,
606+
t2::MOI.ScalarAffineTerm)
607+
MOI.ScalarQuadraticTerm(t1.coefficient * t2.coefficient, t1.variable_index,
608+
t2.variable_index)
609+
end
595610

596611
function operate_term(::typeof(/), t::MOI.ScalarAffineTerm{T}, α::T) where T
597612
MOI.ScalarAffineTerm(t.coefficient / α, t.variable_index)
@@ -641,6 +656,12 @@ function promote_operation(::Union{typeof(+), typeof(-)}, ::Type{T},
641656
end
642657

643658
## operate!
659+
# + with at least 3 arguments
660+
function operate!(op::typeof(+), ::Type{T}, f, g, h, args...) where T
661+
operate!(op, T, f, g)
662+
return operate!(+, T, f, h, args...)
663+
end
664+
644665
# Scalar Variable +/- ...
645666
function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T},
646667
f::MOI.SingleVariable,
@@ -697,7 +718,27 @@ function operate!(op::Union{typeof(+), typeof(-)}, ::Type{T},
697718
end
698719

699720
## operate
721+
# + with at least 3 arguments, can use in-place as the user cannot use
722+
# intermediate results
723+
function operate(op::typeof(+), ::Type{T}, f, g, h, args...) where T
724+
return operate!(+, T, operate(+, T, f, g), h, args...)
725+
end
726+
727+
# Scalar number +/- ...
728+
function operate(op::typeof(+), ::Type{T}, α::T, f::ScalarLike{T}) where T
729+
return operate(op, T, f, α)
730+
end
731+
function operate(op::typeof(-), ::Type{T}, α::T, f::ScalarLike{T}) where T
732+
return operate!(+, T, operate(-, T, f), α)
733+
end
734+
700735
# Scalar Variable +/- ...
736+
function operate(op::Union{typeof(+), typeof(-)}, ::Type{T},
737+
f::MOI.SingleVariable, α::T) where T
738+
return MOI.ScalarAffineFunction{T}([MOI.ScalarAffineTerm(one(T),
739+
f.variable)],
740+
op(α))
741+
end
701742
function operate(op::Union{typeof(+), typeof(-)}, ::Type{T},
702743
f::MOI.SingleVariable,
703744
g::MOI.SingleVariable) where T
@@ -745,14 +786,20 @@ end
745786
function Base.:+(args::ScalarLike{T}...) where T
746787
return operate(+, T, args...)
747788
end
748-
function Base.:+(f::ScalarLike{T}, g::T) where T
749-
return operate(+, T, f, g)
789+
function Base.:+::T, f::ScalarLike{T}) where T
790+
return operate(+, T, α, f)
791+
end
792+
function Base.:+(f::ScalarLike{T}, α::T) where T
793+
return operate(+, T, f, α)
750794
end
751795
function Base.:-(args::ScalarLike{T}...) where T
752796
return operate(-, T, args...)
753797
end
754-
function Base.:-(f::ScalarLike{T}, g::T) where T
755-
return operate(-, T, f, g)
798+
function Base.:-(f::ScalarLike{T}, α::T) where T
799+
return operate(-, T, f, α)
800+
end
801+
function Base.:-::T, f::ScalarLike{T}) where T
802+
return operate(-, T, α, f)
756803
end
757804

758805
####################################### * ######################################
@@ -761,6 +808,14 @@ function promote_operation(::typeof(*), ::Type{T}, ::Type{T},
761808
MOI.ScalarAffineFunction{T}}}) where T
762809
return MOI.ScalarAffineFunction{T}
763810
end
811+
function promote_operation(::typeof(*), ::Type{T},
812+
::Type{<:Union{MOI.SingleVariable,
813+
MOI.ScalarAffineFunction{T}}},
814+
::Type{<:Union{MOI.SingleVariable,
815+
MOI.ScalarAffineFunction{T}}}) where T
816+
return MOI.ScalarQuadraticFunction{T}
817+
end
818+
764819

765820
function operate!(::typeof(*), ::Type{T}, f::MOI.SingleVariable, α::T) where T
766821
return operate(*, T, α, f)
@@ -785,6 +840,60 @@ function operate(::typeof(*), ::Type{T}, α::T,
785840
return operate!(*, T, copy(f), α)
786841
end
787842

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

src/functions.jl

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -289,20 +289,62 @@ function sum_dict(kvs::Vector{Pair{K, V}}) where {K, V}
289289
d
290290
end
291291

292-
# _pair transforms a *Term into a pair key => coefficient where the key groups the variables of the term and its output_index if it is not one.
293-
function _pair(t::Union{VectorAffineTerm, VectorQuadraticTerm})
294-
p = _pair(t.scalar_term)
295-
(t.output_index, p.first) => p.second
292+
"""
293+
coefficient(t::Union{ScalarAffineTerm, ScalarQuadraticTerm
294+
VectorAffineTerm, VectorQuadraticTerm})
295+
296+
Finds the coefficient associated with the term `t`.
297+
"""
298+
function coefficient end
299+
300+
function coefficient(t::Union{ScalarAffineTerm, ScalarQuadraticTerm})
301+
return t.coefficient
302+
end
303+
function coefficient(t::Union{VectorAffineTerm, VectorQuadraticTerm})
304+
return t.scalar_term.coefficient
296305
end
297-
_pair(t::ScalarAffineTerm) = t.variable_index => t.coefficient
298-
# For quadratic terms, x*y == y*x
299-
_canonicalize(v1::VariableIndex, v2::VariableIndex) = VariableIndex.(extrema((v1.value, v2.value)))
300-
_pair(t::ScalarQuadraticTerm) = _canonicalize(t.variable_index_1, t.variable_index_2) => t.coefficient
301306

302-
_dicts(f::Union{ScalarAffineFunction, VectorAffineFunction}) = (sum_dict(_pair.(f.terms)),)
307+
"""
308+
term_indices(t::Union{ScalarAffineTerm, ScalarQuadraticTerm,
309+
VectorAffineTerm, VectorQuadraticTerm})
310+
311+
Returns the indices of the input term `t` as a tuple of `Int`s.
303312
304-
_dicts(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction}) = (sum_dict(_pair.(f.affine_terms)),
305-
sum_dict(_pair.(f.quadratic_terms)))
313+
* For `t::ScalarAffineTerm`, this is a 1-tuple of the variable index.
314+
* For `t::ScalarQuadraticTerm`, this is a 2-tuple of the variable indices
315+
in non-decreasing order.
316+
* For `t::VectorAffineTerm`, this is a 2-tuple of the row/output and
317+
variable indices.
318+
* For `t::VectorQuadraticTerm`, this is a 3-tuple of the row/output and
319+
variable indices in non-decreasing order.
320+
"""
321+
term_indices(t::ScalarAffineTerm) = (t.variable_index.value,)
322+
function term_indices(t::ScalarQuadraticTerm)
323+
return minmax(t.variable_index_1.value, t.variable_index_2.value)
324+
end
325+
function term_indices(t::Union{VectorAffineTerm, VectorQuadraticTerm})
326+
return (t.output_index, term_indices(t.scalar_term)...)
327+
end
328+
329+
"""
330+
term_pair(t::Union{ScalarAffineTerm, ScalarQuadraticTerm,
331+
VectorAffineTerm, VectorQuadraticTerm})
332+
333+
Returns the pair [`term_indices`](@ref) `=>` [`coefficient`](@ref) of the term.
334+
"""
335+
function term_pair(t::Union{ScalarAffineTerm, ScalarQuadraticTerm,
336+
VectorAffineTerm, VectorQuadraticTerm})
337+
term_indices(t) => coefficient(t)
338+
end
339+
340+
function _dicts(f::Union{ScalarAffineFunction, VectorAffineFunction})
341+
return (sum_dict(term_pair.(f.terms)),)
342+
end
343+
344+
function _dicts(f::Union{ScalarQuadraticFunction, VectorQuadraticFunction})
345+
return (sum_dict(term_pair.(f.affine_terms)),
346+
sum_dict(term_pair.(f.quadratic_terms)))
347+
end
306348

307349
_constant(f::Union{ScalarAffineFunction, ScalarQuadraticFunction}) = f.constant
308350
_constant(f::Union{VectorAffineFunction, VectorQuadraticFunction}) = f.constants

0 commit comments

Comments
 (0)