From a12e765bea993dadac2e83fb301b5b93e20448a5 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 22 Jun 2023 10:56:17 +1200 Subject: [PATCH 1/2] [Utilities] add operate for ScalarNonlinearFunction --- src/Utilities/functions.jl | 33 ------- src/Utilities/operate.jl | 151 ++++++++++++++++++++++++++++++++ test/Utilities/test_operate!.jl | 82 ++++++++++++++--- 3 files changed, 220 insertions(+), 46 deletions(-) diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index 8c2f3878b4..cb686f4a81 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -1292,39 +1292,6 @@ function map_terms!( return end -###################################### +/- ##################################### - -### ScalarNonlinearFunction - -function operate( - ::typeof(-), - ::Type{T}, - f::MOI.ScalarNonlinearFunction, -) where {T} - if f.head == :- && length(f.args) == 1 - # A simplification for -(-(f)) into f, but only if f is an SNF. - if f.args[1] isa MOI.ScalarNonlinearFunction - return f.args[1] - end - end - return MOI.ScalarNonlinearFunction(:-, Any[f]) -end - -function operate( - op::Union{typeof(+),typeof(-),typeof(*),typeof(/)}, - ::Type{T}, - f::MOI.ScalarNonlinearFunction, - g::Union{ - T, - MOI.VariableIndex, - MOI.ScalarAffineFunction{T}, - MOI.ScalarQuadraticFunction{T}, - MOI.ScalarNonlinearFunction, - }, -) where {T} - return MOI.ScalarNonlinearFunction(Symbol(op), Any[f, g]) -end - ### Base methods _eltype(args::Tuple) = _eltype(first(args), Base.tail(args)) diff --git a/src/Utilities/operate.jl b/src/Utilities/operate.jl index 20ddfaacb9..3042375ad4 100644 --- a/src/Utilities/operate.jl +++ b/src/Utilities/operate.jl @@ -156,6 +156,45 @@ function operate( return operate!(+, T, copy(f), g) end +function operate( + ::typeof(+), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::Union{ + T, + MOI.VariableIndex, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, + MOI.ScalarNonlinearFunction, + }, +) where {T} + return MOI.ScalarNonlinearFunction(:+, Any[f, g]) +end + +function operate( + ::typeof(+), + ::Type{T}, + f::Union{ + T, + MOI.VariableIndex, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, + MOI.ScalarNonlinearFunction, + }, + g::MOI.ScalarNonlinearFunction, +) where {T} + return MOI.ScalarNonlinearFunction(:+, Any[f, g]) +end + +function operate( + ::typeof(+), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::MOI.ScalarNonlinearFunction, +) where {T} + return MOI.ScalarNonlinearFunction(:+, Any[f, g]) +end + function operate( ::typeof(+), ::Type{T}, @@ -276,6 +315,20 @@ function operate( return operate_coefficients(-, f) end +function operate( + ::typeof(-), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, +) where {T} + if f.head == :- && length(f.args) == 1 + # A simplification for -(-(f)) into f, but only if f is an SNF. + if f.args[1] isa MOI.ScalarNonlinearFunction + return f.args[1] + end + end + return MOI.ScalarNonlinearFunction(:-, Any[f]) +end + ### 2b: operate(::typeof(-), ::Type{T}, ::F1, ::F2) function operate( @@ -352,6 +405,45 @@ function operate( return operate!(-, T, copy(f), g) end +function operate( + ::typeof(-), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::Union{ + T, + MOI.VariableIndex, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, + MOI.ScalarNonlinearFunction, + }, +) where {T} + return MOI.ScalarNonlinearFunction(:-, Any[f, g]) +end + +function operate( + ::typeof(-), + ::Type{T}, + f::Union{ + T, + MOI.VariableIndex, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, + MOI.ScalarNonlinearFunction, + }, + g::MOI.ScalarNonlinearFunction, +) where {T} + return MOI.ScalarNonlinearFunction(:-, Any[f, g]) +end + +function operate( + ::typeof(-), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::MOI.ScalarNonlinearFunction, +) where {T} + return MOI.ScalarNonlinearFunction(:-, Any[f, g]) +end + function operate( ::typeof(-), ::Type{T}, @@ -471,6 +563,15 @@ function operate( return operate!(*, T, copy(g), f) end +function operate( + ::typeof(*), + ::Type{T}, + f::T, + g::MOI.ScalarNonlinearFunction, +) where {T} + return MOI.ScalarNonlinearFunction(:*, Any[f, g]) +end + ### 3b: operate(::typeof(*), ::Type{T}, ::F, ::T) function operate( @@ -489,6 +590,15 @@ function operate( return operate(*, T, g, f) end +function operate( + ::typeof(*), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::T, +) where {T} + return MOI.ScalarNonlinearFunction(:*, Any[f, g]) +end + ### 3c: operate(::typeof(*), ::Type{T}, ::F1, ::F2) function operate( @@ -652,6 +762,15 @@ function operate( return operate!(/, T, copy(f), g) end +function operate( + ::typeof(/), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::T, +) where {T} + return MOI.ScalarNonlinearFunction(:/, Any[f, g]) +end + ### 5a: operate(::typeof(vcat), ::Type{T}, ::F...) function operate( @@ -786,6 +905,25 @@ function operate!( return MA.operate!(+, f, g) end +function operate!( + ::typeof(+), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::Union{ + T, + MOI.VariableIndex, + MOI.ScalarAffineFunction{T}, + MOI.ScalarQuadraticFunction{T}, + MOI.ScalarNonlinearFunction, + }, +) where {T} + if f.head == :+ + push!(f.args, g) + return f + end + return operate(+, T, f, g) +end + function operate!( ::typeof(+), ::Type{T}, @@ -992,6 +1130,19 @@ function operate!( return f end +function operate!( + ::typeof(*), + ::Type{T}, + f::MOI.ScalarNonlinearFunction, + g::T, +) where {T} + if f.head == :* + push!(f.args, g) + return f + end + return operate(*, T, f, g) +end + function operate!( ::typeof(*), ::Type{T}, diff --git a/test/Utilities/test_operate!.jl b/test/Utilities/test_operate!.jl index c2f7607f59..36c474db07 100644 --- a/test/Utilities/test_operate!.jl +++ b/test/Utilities/test_operate!.jl @@ -46,6 +46,16 @@ function _test_function(coefficients::Vector{NTuple{3,T}}) where {T} return MOI.Utilities.operate(vcat, T, _test_function.(coefficients)...) end +function _test_function(pair::Pair{Symbol,<:Any}) where {T} + head, arg = pair + if arg isa Vector + args = Any[_test_function(a) for a in arg] + return MOI.ScalarNonlinearFunction(head, args) + else + return MOI.ScalarNonlinearFunction(head, Any[_test_function(arg)]) + end +end + function test_operate_1a() for coef in ( (0, 0, 0), @@ -53,6 +63,7 @@ function test_operate_1a() (0, 1, 0), (0, 0, 1), (1, 1, 1), + :log => (0, 0, 0), [(0, 0, 0)], [(1, 0, 0)], [(0, 1, 0)], @@ -73,6 +84,7 @@ function test_operate_1b() (0, 1, 0), (0, 0, 1), (1, 1, 1), + :log => (0, 0, 0), [(0, 0, 0)], [(1, 0, 0)], [(0, 1, 0)], @@ -80,15 +92,19 @@ function test_operate_1b() [(1, 1, 1)], ) special_cases = Dict((0, 0, 0) => (0, 1, 0)) - for i in 1:5, j in 1:5 + for i in 1:6, j in 1:6 fi, fj = _test_function(F[i]), _test_function(F[j]) Fi = get(special_cases, F[i], F[i]) Fj = get(special_cases, F[j], F[j]) - fk = _test_function(Fi .+ Fj) + if i == 6 || j == 6 + fk = MOI.ScalarNonlinearFunction(:+, Any[fi, fj]) + else + fk = _test_function(Fi .+ Fj) + end @test MOI.Utilities.operate(+, Int, fi, fj) ≈ fk @test MOI.Utilities.operate!(+, Int, fi, fj) ≈ fk end - for i in 6:10, j in 6:10 + for i in 7:11, j in 7:11 fi, fj = _test_function(F[i]), _test_function(F[j]) k = map(zip(F[i], F[j])) do (x, y) return get(special_cases, x, x) .+ get(special_cases, y, y) @@ -99,6 +115,15 @@ function test_operate_1b() return end +function test_operate_1b_scalarnonlinearfunction_specialcase() + x = MOI.VariableIndex(1) + f = MOI.ScalarNonlinearFunction(:+, Any[x]) + g = MOI.Utilities.operate!(+, Float64, f, x) + @test g === f + @test g ≈ MOI.ScalarNonlinearFunction(:+, Any[x, x]) + return +end + function test_operate_1c() for coef in ( (0, 0, 0), @@ -130,6 +155,7 @@ function test_operate_2a() (0, 1, 0) => (0, -1, 0), (0, 0, 1) => (0, 0, -1), (1, 1, 1) => (-1, -1, -1), + (:log => (0, 0, 0)) => (:- => (:log => (0, 0, 0))), [(0, 0, 0)] => [(0, -1, 0)], [(1, 0, 0)] => [(-1, 0, 0)], [(0, 1, 0)] => [(0, -1, 0)], @@ -143,6 +169,16 @@ function test_operate_2a() return end +function test_operate_2a_scalarnonlinearfunction_specialcase() + x = MOI.VariableIndex(1) + f = MOI.ScalarNonlinearFunction(:log, Any[x]) + g = MOI.ScalarNonlinearFunction(:-, Any[f]) + h = MOI.Utilities.operate!(-, Float64, g) + @test h === f + @test h ≈MOI.ScalarNonlinearFunction(:log, Any[x]) + return +end + function test_operate_2b() F = ( (0, 0, 0), @@ -150,6 +186,7 @@ function test_operate_2b() (0, 1, 0), (0, 0, 1), (1, 1, 1), + :log => (0, 0, 0), [(0, 0, 0)], [(1, 0, 0)], [(0, 1, 0)], @@ -157,26 +194,33 @@ function test_operate_2b() [(1, 1, 1)], ) special_cases = Dict((0, 0, 0) => (0, 1, 0)) - for i in 1:5, j in 1:5 - fi, fj = _test_function(2 .* F[i]), _test_function(F[j]) - Fi = get(special_cases, 2 .* F[i], 2 .* F[i]) - Fj = get(special_cases, F[j], F[j]) - Fk = Fi .- Fj - fk = _test_function(get(special_cases, Fk, Fk)) - if (i, j) in ((1, 1), (1, 3)) - fk = zero(MOI.ScalarAffineFunction{Int}) + for i in 1:6, j in 1:6 + if i == 6 || j == 6 + fi = _test_function(F[i]) + fj = _test_function(F[j]) + fk = MOI.ScalarNonlinearFunction(:-, Any[fi, fj]) + else + fi = _test_function(2 .* F[i]) + fj = _test_function(F[j]) + Fi = get(special_cases, 2 .* F[i], 2 .* F[i]) + Fj = get(special_cases, F[j], F[j]) + Fk = Fi .- Fj + fk = _test_function(get(special_cases, Fk, Fk)) + if (i, j) in ((1, 1), (1, 3)) + fk = zero(MOI.ScalarAffineFunction{Int}) + end end @test MOI.Utilities.operate(-, Int, fi, fj) ≈ fk @test MOI.Utilities.operate!(-, Int, fi, fj) ≈ fk end - for i in 6:10, j in 6:10 + for i in 7:11, j in 7:11 F2 = [2 .* fi for fi in F[i]] fi, fj = _test_function(F2), _test_function(F[j]) k = map(zip(F2, F[j])) do (x, y) return get(special_cases, x, x) .- get(special_cases, y, y) end fk = _test_function(k) - if (i, j) in ((6, 6), (6, 8)) + if (i, j) in ((7, 7), (7, 9)) fk = MOI.VectorAffineFunction(MOI.VectorAffineTerm{Int}[], [0]) end @test MOI.Utilities.operate(-, Int, fi, fj) ≈ fk @@ -193,6 +237,7 @@ function test_operate_3a() (0, 1, 0) => (0, 3, 0), (0, 0, 1) => (0, 0, 3), (1, 1, 1) => (3, 3, 3), + (:log => (0, 0, 0)) => (:* => [(3, 0, 0), (:log => (0, 0, 0))]), [(0, 0, 0)] => [(0, 3, 0)], [(1, 0, 0)] => [(3, 0, 0)], [(0, 1, 0)] => [(0, 3, 0)], @@ -214,6 +259,7 @@ function test_operate_3b() (0, 1, 0) => (0, 3, 0), (0, 0, 1) => (0, 0, 3), (1, 1, 1) => (3, 3, 3), + (:log => (0, 0, 0)) => (:* => [(:log => (0, 0, 0)), (3, 0, 0)]), [(0, 0, 0)] => [(0, 3, 0)], [(1, 0, 0)] => [(3, 0, 0)], [(0, 1, 0)] => [(0, 3, 0)], @@ -227,6 +273,15 @@ function test_operate_3b() return end +function test_operate_3b_scalarnonlinearfunction_specialcase() + x = MOI.VariableIndex(1) + f = MOI.ScalarNonlinearFunction(:*, Any[x]) + g = MOI.Utilities.operate!(*, Float64, f, 3.0) + @test g === f + @test g ≈ MOI.ScalarNonlinearFunction(:*, Any[x, 3.0]) + return +end + function test_operate_4a() T = Float64 for (f, g) in ( @@ -235,6 +290,7 @@ function test_operate_4a() (0.0, 1.0, 0.0) => (0.0, 0.5, 0.0), (0.0, 0.0, 1.0) => (0.0, 0.0, 0.5), (1.0, 1.0, 1.0) => (0.5, 0.5, 0.5), + (:log => (0, 0, 0)) => (:/ => [(:log => (0, 0, 0)), (2.0, 0.0, 0.0)]), [(0.0, 0.0, 0.0)] => [(0.0, 0.5, 0.0)], [(1.0, 0.0, 0.0)] => [(0.5, 0.0, 0.0)], [(0.0, 1.0, 0.0)] => [(0.0, 0.5, 0.0)], From 5920bdd7f581446b56145524817f8fbd1eff5a71 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 22 Jun 2023 11:28:55 +1200 Subject: [PATCH 2/2] Fix formatting --- test/Utilities/test_operate!.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Utilities/test_operate!.jl b/test/Utilities/test_operate!.jl index 36c474db07..3f9f44d770 100644 --- a/test/Utilities/test_operate!.jl +++ b/test/Utilities/test_operate!.jl @@ -175,7 +175,7 @@ function test_operate_2a_scalarnonlinearfunction_specialcase() g = MOI.ScalarNonlinearFunction(:-, Any[f]) h = MOI.Utilities.operate!(-, Float64, g) @test h === f - @test h ≈MOI.ScalarNonlinearFunction(:log, Any[x]) + @test h ≈ MOI.ScalarNonlinearFunction(:log, Any[x]) return end