Skip to content

Commit 83f4c5e

Browse files
authored
[Utilities] improve performance of canonical (#1704)
1 parent 7f7b503 commit 83f4c5e

File tree

1 file changed

+43
-78
lines changed

1 file changed

+43
-78
lines changed

src/Utilities/functions.jl

+43-78
Original file line numberDiff line numberDiff line change
@@ -701,6 +701,8 @@ function unsafe_add(
701701
return T(t1.output_index, scalar_term)
702702
end
703703

704+
is_canonical(::MOI.AbstractFunction) = false
705+
704706
is_canonical(::Union{MOI.VariableIndex,MOI.VectorOfVariables}) = true
705707

706708
"""
@@ -712,11 +714,7 @@ See [`canonical`](@ref).
712714
function is_canonical(
713715
f::Union{MOI.ScalarAffineFunction,MOI.VectorAffineFunction},
714716
)
715-
return is_strictly_sorted(
716-
f.terms,
717-
MOI.term_indices,
718-
t -> !iszero(MOI.coefficient(t)),
719-
)
717+
return _is_strictly_sorted(f.terms)
720718
end
721719

722720
"""
@@ -728,39 +726,25 @@ See [`canonical`](@ref).
728726
function is_canonical(
729727
f::Union{MOI.ScalarQuadraticFunction,MOI.VectorQuadraticFunction},
730728
)
731-
v = is_strictly_sorted(
732-
f.affine_terms,
733-
MOI.term_indices,
734-
t -> !iszero(MOI.coefficient(t)),
735-
)
736-
return v & is_strictly_sorted(
737-
f.quadratic_terms,
738-
MOI.term_indices,
739-
t -> !iszero(MOI.coefficient(t)),
740-
)
729+
return _is_strictly_sorted(f.affine_terms) &&
730+
_is_strictly_sorted(f.quadratic_terms)
741731
end
742732

743-
"""
744-
is_strictly_sorted(x::Vector, by, filter)
745-
746-
Returns `true` if `by(x[i]) < by(x[i + 1])` and `filter(x[i]) == true` for
747-
all indices i.
748-
"""
749-
function is_strictly_sorted(x::Vector, by, filter)
733+
function _is_strictly_sorted(x::Vector)
750734
if isempty(x)
751735
return true
752736
end
753-
@inbounds current_x = first(x)
754-
if !filter(current_x)
737+
@inbounds current_x = x[1]
738+
if iszero(MOI.coefficient(current_x))
755739
return false
756740
end
757-
current_fx = by(current_x)
758-
for i in eachindex(x)[2:end]
759-
@inbounds next_x = x[i]
760-
if !filter(next_x)
741+
current_fx = MOI.term_indices(current_x)
742+
@inbounds for i in 2:length(x)
743+
next_x = x[i]
744+
if iszero(MOI.coefficient(next_x))
761745
return false
762746
end
763-
next_fx = by(next_x)
747+
next_fx = MOI.term_indices(next_x)
764748
if next_fx <= current_fx
765749
return false
766750
end
@@ -796,7 +780,13 @@ If `x` (resp. `y`, `z`) is `VariableIndex(1)` (resp. 2, 3). The canonical
796780
representation of `ScalarAffineFunction([y, x, z, x, z], [2, 1, 3, -2, -3], 5)`
797781
is `ScalarAffineFunction([x, y], [-1, 2], 5)`.
798782
"""
799-
canonical(f::MOI.AbstractFunction) = canonicalize!(copy(f))
783+
function canonical(f::MOI.AbstractFunction)
784+
g = copy(f)
785+
if !is_canonical(g)
786+
canonicalize!(g)
787+
end
788+
return g
789+
end
800790

801791
canonicalize!(f::Union{MOI.VectorOfVariables,MOI.VariableIndex}) = f
802792

@@ -809,12 +799,7 @@ the result. See [`canonical`](@ref).
809799
function canonicalize!(
810800
f::Union{MOI.ScalarAffineFunction,MOI.VectorAffineFunction},
811801
)
812-
sort_and_compress!(
813-
f.terms,
814-
MOI.term_indices,
815-
t -> !iszero(MOI.coefficient(t)),
816-
unsafe_add,
817-
)
802+
_sort_and_compress!(f.terms)
818803
return f
819804
end
820805

@@ -827,60 +812,40 @@ the result. See [`canonical`](@ref).
827812
function canonicalize!(
828813
f::Union{MOI.ScalarQuadraticFunction,MOI.VectorQuadraticFunction},
829814
)
830-
sort_and_compress!(
831-
f.affine_terms,
832-
MOI.term_indices,
833-
t -> !iszero(MOI.coefficient(t)),
834-
unsafe_add,
835-
)
836-
sort_and_compress!(
837-
f.quadratic_terms,
838-
MOI.term_indices,
839-
t -> !iszero(MOI.coefficient(t)),
840-
unsafe_add,
841-
)
815+
_sort_and_compress!(f.affine_terms)
816+
_sort_and_compress!(f.quadratic_terms)
842817
return f
843818
end
844819

845820
"""
846-
sort_and_compress!(
847-
x::AbstractVector,
848-
by::Function,
849-
keep::Function,
850-
combine::Function,
851-
)
821+
_sort_and_compress!(x::Vector)
852822
853823
Sort the vector `x` in-place using `by` as the function from elements to
854824
comparable keys, then combine all entries for which `by(x[i]) == by(x[j])` using
855825
the function `x[i] = combine(x[i], x[j])`, and remove any entries for which
856826
`keep(x[i]) == false`. This may result in `x` being resized to a shorter length.
857827
"""
858-
function sort_and_compress!(x::AbstractVector, by, keep, combine)
859-
if length(x) > 0
860-
sort!(
861-
x,
862-
QuickSort,
863-
Base.Order.ord(isless, by, false, Base.Sort.Forward),
864-
)
865-
i1 = firstindex(x)
866-
for i2 in eachindex(x)[2:end]
867-
if by(x[i1]) == by(x[i2])
868-
x[i1] = combine(x[i1], x[i2])
869-
else
870-
if !keep(x[i1])
871-
x[i1] = x[i2]
872-
else
873-
x[i1+1] = x[i2]
874-
i1 += 1
875-
end
876-
end
877-
end
878-
if !keep(x[i1])
879-
i1 -= 1
828+
function _sort_and_compress!(x::Vector)
829+
if length(x) == 0
830+
return
831+
end
832+
sort!(x, QuickSort, Base.Order.ord(isless, MOI.term_indices, false))
833+
i = 1
834+
@inbounds for j in 2:length(x)
835+
if MOI.term_indices(x[i]) == MOI.term_indices(x[j])
836+
x[i] = unsafe_add(x[i], x[j])
837+
elseif iszero(MOI.coefficient(x[i]))
838+
x[i] = x[j]
839+
else
840+
x[i+1] = x[j]
841+
i += 1
880842
end
881-
resize!(x, i1)
882843
end
883-
return x
844+
if iszero(MOI.coefficient(x[i]))
845+
i -= 1
846+
end
847+
resize!(x, i)
848+
return
884849
end
885850

886851
"""

0 commit comments

Comments
 (0)