From 4f73dad4e65ad0148713f049dd4f71537fb87534 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 7 Sep 2023 13:40:14 +1200 Subject: [PATCH 1/7] Add support for Array arguments in NonlinearOperator --- src/nlp_expr.jl | 65 +++++++++++++++---------------------------- src/operators.jl | 11 ++++---- test/test_nlp.jl | 12 ++++---- test/test_nlp_expr.jl | 61 ++++++++++++++++++++-------------------- test/test_operator.jl | 2 +- 5 files changed, 66 insertions(+), 85 deletions(-) diff --git a/src/nlp_expr.jl b/src/nlp_expr.jl index 60763939f6a..1fb07479166 100644 --- a/src/nlp_expr.jl +++ b/src/nlp_expr.jl @@ -81,15 +81,11 @@ struct GenericNonlinearExpr{V<:AbstractVariableRef} <: AbstractJuMPScalar head::Symbol args::Vector{Any} - function GenericNonlinearExpr(head::Symbol, args::Vector{Any}) - index = findfirst(Base.Fix2(isa, AbstractJuMPScalar), args) - if index === nothing - error( - "Unable to create a nonlinear expression because it did not " * - "contain any JuMP scalars. head = $head, args = $args.", - ) - end - return new{variable_ref_type(args[index])}(head, args) + function GenericNonlinearExpr{V}( + head::Symbol, + args::Vararg{Any} + ) where {V<:AbstractVariableRef} + return new{V}(head, Any[a for a in args]) end function GenericNonlinearExpr{V}( @@ -110,15 +106,6 @@ const NonlinearExpr = GenericNonlinearExpr{VariableRef} variable_ref_type(::GenericNonlinearExpr{V}) where {V} = V -# We include this method so that we can refactor the internal representation of -# GenericNonlinearExpr without having to rewrite the method overloads. -function GenericNonlinearExpr{V}( - head::Symbol, - args..., -) where {V<:AbstractVariableRef} - return GenericNonlinearExpr{V}(head, Any[args...]) -end - const _PREFIX_OPERATORS = (:+, :-, :*, :/, :^, :||, :&&, :>, :<, :(<=), :(>=), :(==)) @@ -502,6 +489,8 @@ end moi_function(x::Number) = x +moi_function(x::AbstractArray) = moi_function.(x) + function moi_function(f::GenericNonlinearExpr{V}) where {V} ret = MOI.ScalarNonlinearFunction(f.head, similar(f.args)) stack = Tuple{MOI.ScalarNonlinearFunction,Int,GenericNonlinearExpr{V}}[] @@ -527,6 +516,10 @@ function moi_function(f::GenericNonlinearExpr{V}) where {V} return ret end +jump_function(::GenericModel{T}, x::Number) where {T} = convert(T, x) + +jump_function(model::GenericModel, x::AbstractArray) = jump_function.(model, x) + function jump_function(model::GenericModel, f::MOI.ScalarNonlinearFunction) V = variable_ref_type(typeof(model)) ret = GenericNonlinearExpr{V}(f.head, Any[]) @@ -542,8 +535,6 @@ function jump_function(model::GenericModel, f::MOI.ScalarNonlinearFunction) for child in reverse(arg.args) push!(stack, (new_ret, child)) end - elseif arg isa Number - push!(parent.args, arg) else push!(parent.args, jump_function(model, arg)) end @@ -833,33 +824,23 @@ function Base.show(io::IO, f::NonlinearOperator) return print(io, "NonlinearOperator($(f.func), :$(f.head))") end -# Fast overload for unary calls - -(f::NonlinearOperator)(x) = f.func(x) - -(f::NonlinearOperator)(x::AbstractJuMPScalar) = NonlinearExpr(f.head, Any[x]) - -# Fast overload for binary calls - -(f::NonlinearOperator)(x, y) = f.func(x, y) - -function (f::NonlinearOperator)(x::AbstractJuMPScalar, y) - return GenericNonlinearExpr(f.head, Any[x, y]) -end +variable_ref_type(::NonlinearOperator, ::Any) = nothing -function (f::NonlinearOperator)(x, y::AbstractJuMPScalar) - return GenericNonlinearExpr(f.head, Any[x, y]) +function variable_ref_type(::NonlinearOperator, x::AbstractJuMPScalar) + return variable_ref_type(x) end -function (f::NonlinearOperator)(x::AbstractJuMPScalar, y::AbstractJuMPScalar) - return GenericNonlinearExpr(f.head, Any[x, y]) +function variable_ref_type( + ::NonlinearOperator, + ::AbstractArray{T}, +) where {T<:AbstractJuMPScalar} + return variable_ref_type(T) end -# Fallback for more arguments -function (f::NonlinearOperator)(x, y, z...) - args = (x, y, z...) - if any(Base.Fix2(isa, AbstractJuMPScalar), args) - return GenericNonlinearExpr(f.head, Any[a for a in args]) +function (f::NonlinearOperator)(args::Vararg{Any,N}) where {N} + types = variable_ref_type.(Ref(f), args) + if (i = findfirst(!isnothing, types)) !== nothing + return GenericNonlinearExpr{types[i]}(f.head, args...) end return f.func(args...) end diff --git a/src/operators.jl b/src/operators.jl index 7c0ec273ae4..5310234ddc0 100644 --- a/src/operators.jl +++ b/src/operators.jl @@ -195,20 +195,19 @@ function Base.:/(lhs::GenericAffExpr, rhs::_Constant) return map_coefficients(c -> c / rhs, lhs) end -function Base.:^(lhs::AbstractVariableRef, rhs::Integer) - T = value_type(typeof(lhs)) +function Base.:^(lhs::V, rhs::Integer) where {V<:AbstractVariableRef} if rhs == 0 - return one(T) + return one(value_type(V)) elseif rhs == 1 return lhs elseif rhs == 2 return lhs * lhs else - return GenericNonlinearExpr(:^, Any[lhs, rhs]) + return GenericNonlinearExpr{V}(:^, Any[lhs, rhs]) end end -function Base.:^(lhs::GenericAffExpr{T}, rhs::Integer) where {T} +function Base.:^(lhs::GenericAffExpr{T,V}, rhs::Integer) where {T,V} if rhs == 0 return one(T) elseif rhs == 1 @@ -216,7 +215,7 @@ function Base.:^(lhs::GenericAffExpr{T}, rhs::Integer) where {T} elseif rhs == 2 return lhs * lhs else - return GenericNonlinearExpr(:^, Any[lhs, rhs]) + return GenericNonlinearExpr{V}(:^, Any[lhs, rhs]) end end diff --git a/test/test_nlp.jl b/test/test_nlp.jl index d4f34cfe56a..ff8be02a7bc 100644 --- a/test/test_nlp.jl +++ b/test/test_nlp.jl @@ -1605,7 +1605,7 @@ function test_parse_expression_nonlinearexpr_call() model = Model() @variable(model, x) @variable(model, y) - f = GenericNonlinearExpr(:ifelse, Any[x, 0, y]) + f = NonlinearExpr(:ifelse, Any[x, 0, y]) @NLexpression(model, ref, f) nlp = nonlinear_model(model) expr = :(ifelse($x, 0, $y)) @@ -1617,7 +1617,7 @@ function test_parse_expression_nonlinearexpr_or() model = Model() @variable(model, x) @variable(model, y) - f = GenericNonlinearExpr(:||, Any[x, y]) + f = NonlinearExpr(:||, Any[x, y]) @NLexpression(model, ref, f) nlp = nonlinear_model(model) expr = :($x || $y) @@ -1629,7 +1629,7 @@ function test_parse_expression_nonlinearexpr_and() model = Model() @variable(model, x) @variable(model, y) - f = GenericNonlinearExpr(:&&, Any[x, y]) + f = NonlinearExpr(:&&, Any[x, y]) @NLexpression(model, ref, f) nlp = nonlinear_model(model) expr = :($x && $y) @@ -1641,7 +1641,7 @@ function test_parse_expression_nonlinearexpr_unsupported() model = Model() @variable(model, x) @variable(model, y) - f = GenericNonlinearExpr(:foo, Any[x, y]) + f = NonlinearExpr(:foo, Any[x, y]) @test_throws( MOI.UnsupportedNonlinearOperator, @NLexpression(model, ref, f), @@ -1653,8 +1653,8 @@ function test_parse_expression_nonlinearexpr_nested_comparison() model = Model() @variable(model, x) @variable(model, y) - f = GenericNonlinearExpr(:||, Any[x, y]) - g = GenericNonlinearExpr(:&&, Any[f, x]) + f = NonlinearExpr(:||, Any[x, y]) + g = NonlinearExpr(:&&, Any[f, x]) @NLexpression(model, ref, g) nlp = nonlinear_model(model) expr = :(($x || $y) && $x) diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index 092c2691b80..c2e20c1c586 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -447,46 +447,46 @@ function test_extension_nl_macro( @variable(model, x) @test isequal_canonical( @expression(model, ifelse(x, 1, 2)), - GenericNonlinearExpr(:ifelse, Any[x, 1, 2]), + GenericNonlinearExpr{VariableRefType}(:ifelse, Any[x, 1, 2]), ) @test isequal_canonical( @expression(model, x || 1), - GenericNonlinearExpr(:||, Any[x, 1]), + GenericNonlinearExpr{VariableRefType}(:||, Any[x, 1]), ) @test isequal_canonical( @expression(model, x && 1), - GenericNonlinearExpr(:&&, Any[x, 1]), + GenericNonlinearExpr{VariableRefType}(:&&, Any[x, 1]), ) @test isequal_canonical( @expression(model, x < 0), - GenericNonlinearExpr(:<, Any[x, 0]), + GenericNonlinearExpr{VariableRefType}(:<, Any[x, 0]), ) @test isequal_canonical( @expression(model, x > 0), - GenericNonlinearExpr(:>, Any[x, 0]), + GenericNonlinearExpr{VariableRefType}(:>, Any[x, 0]), ) @test isequal_canonical( @expression(model, x <= 0), - GenericNonlinearExpr(:<=, Any[x, 0]), + GenericNonlinearExpr{VariableRefType}(:<=, Any[x, 0]), ) @test isequal_canonical( @expression(model, x >= 0), - GenericNonlinearExpr(:>=, Any[x, 0]), + GenericNonlinearExpr{VariableRefType}(:>=, Any[x, 0]), ) @test isequal_canonical( @expression(model, x == 0), - GenericNonlinearExpr(:(==), Any[x, 0]), + GenericNonlinearExpr{VariableRefType}(:(==), Any[x, 0]), ) @test isequal_canonical( @expression(model, 0 < x <= 1), - GenericNonlinearExpr( + GenericNonlinearExpr{VariableRefType}( :&&, Any[@expression(model, 0 < x), @expression(model, x <= 1)], ), ) @test isequal_canonical( @expression(model, ifelse(x > 0, x^2, sin(x))), - GenericNonlinearExpr( + GenericNonlinearExpr{VariableRefType}( :ifelse, Any[@expression(model, x > 0), x^2, sin(x)], ), @@ -501,7 +501,7 @@ function test_register_univariate() @test f isa NonlinearOperator @test sprint(show, f) == "NonlinearOperator($(f.func), :f)" @test isequal_canonical(@expression(model, f(x)), f(x)) - @test isequal_canonical(f(x), GenericNonlinearExpr(:f, Any[x])) + @test isequal_canonical(f(x), NonlinearExpr(:f, Any[x])) attrs = MOI.get(model, MOI.ListOfModelAttributesSet()) @test MOI.UserDefinedFunction(:f, 1) in attrs return @@ -522,7 +522,7 @@ function test_register_univariate_gradient() @variable(model, x) @operator(model, f, 1, x -> x^2, x -> 2 * x) @test isequal_canonical(@expression(model, f(x)), f(x)) - @test isequal_canonical(f(x), GenericNonlinearExpr(:f, Any[x])) + @test isequal_canonical(f(x), NonlinearExpr(:f, Any[x])) attrs = MOI.get(model, MOI.ListOfModelAttributesSet()) @test MOI.UserDefinedFunction(:f, 1) in attrs return @@ -533,7 +533,7 @@ function test_register_univariate_gradient_hessian() @variable(model, x) @operator(model, f, 1, x -> x^2, x -> 2 * x, x -> 2.0) @test isequal_canonical(@expression(model, f(x)), f(x)) - @test isequal_canonical(f(x), GenericNonlinearExpr(:f, Any[x])) + @test isequal_canonical(f(x), NonlinearExpr(:f, Any[x])) attrs = MOI.get(model, MOI.ListOfModelAttributesSet()) @test MOI.UserDefinedFunction(:f, 1) in attrs return @@ -545,7 +545,7 @@ function test_register_multivariate() f = (x...) -> sum(x .^ 2) @operator(model, foo, 2, f) @test isequal_canonical(@expression(model, foo(x...)), foo(x...)) - @test isequal_canonical(foo(x...), GenericNonlinearExpr(:foo, Any[x...])) + @test isequal_canonical(foo(x...), NonlinearExpr(:foo, Any[x...])) attrs = MOI.get(model, MOI.ListOfModelAttributesSet()) @test MOI.UserDefinedFunction(:foo, 2) in attrs return @@ -558,7 +558,7 @@ function test_register_multivariate_gradient() ∇f = (g, x...) -> (g .= 2 .* x) @operator(model, foo, 2, f, ∇f) @test isequal_canonical(@expression(model, foo(x...)), foo(x...)) - @test isequal_canonical(foo(x...), GenericNonlinearExpr(:foo, Any[x...])) + @test isequal_canonical(foo(x...), NonlinearExpr(:foo, Any[x...])) attrs = MOI.get(model, MOI.ListOfModelAttributesSet()) @test MOI.UserDefinedFunction(:foo, 2) in attrs return @@ -576,7 +576,7 @@ function test_register_multivariate_gradient_hessian() end @operator(model, foo, 2, f, ∇f, ∇²f) @test isequal_canonical(@expression(model, foo(x...)), foo(x...)) - @test isequal_canonical(foo(x...), GenericNonlinearExpr(:foo, Any[x...])) + @test isequal_canonical(foo(x...), NonlinearExpr(:foo, Any[x...])) attrs = MOI.get(model, MOI.ListOfModelAttributesSet()) @test MOI.UserDefinedFunction(:foo, 2) in attrs return @@ -587,7 +587,7 @@ function test_register_multivariate_many_args() @variable(model, x[1:10]) f = (x...) -> sum(x .^ 2) @operator(model, foo, 10, f) - @test isequal_canonical(foo(x...), GenericNonlinearExpr(:foo, Any[x...])) + @test isequal_canonical(foo(x...), NonlinearExpr(:foo, Any[x...])) @test foo((1:10)...) == 385 return end @@ -607,18 +607,6 @@ function test_register_errors() return end -function test_expression_no_variable() - head, args = :sin, Any[1] - @test_throws( - ErrorException( - "Unable to create a nonlinear expression because it did not " * - "contain any JuMP scalars. head = $head, args = $args.", - ), - GenericNonlinearExpr(head, args), - ) - return -end - function test_value_expression() model = Model() @variable(model, x) @@ -676,7 +664,7 @@ end function test_nonlinear_expr_owner_model() model = Model() @variable(model, x) - f = GenericNonlinearExpr(:sin, Any[x]) + f = NonlinearExpr(:sin, Any[x]) # This shouldn't happen in regular code, but let's test against it to check # we get something similar to AffExpr and QuadExpr. empty!(f.args) @@ -900,4 +888,17 @@ function test_ma_zero_in_operate!!() return end +function test_nonlinear_operator_vector_args() + model = Model() + @variable(model, x[1:2, 1:2]) + op_det = NonlinearOperator(LinearAlgebra.det, :det) + @objective(model, Min, log(op_det(x))) + @test isequal_canonical(objective_function(model), log(op_det(x))) + f = MOI.get(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}()) + g = MOI.ScalarNonlinearFunction(:det, Any[index.(x)]) + @test f ≈ MOI.ScalarNonlinearFunction(:log, Any[g]) + return +end + + end # module diff --git a/test/test_operator.jl b/test/test_operator.jl index ebda3be21b5..967fb8b666d 100644 --- a/test/test_operator.jl +++ b/test/test_operator.jl @@ -621,7 +621,7 @@ function test_complex_pow() @test y^0 == (1.0 + 0im) @test y^1 == y @test y^2 == y * y - @test isequal_canonical(y^3, GenericNonlinearExpr(:^, Any[y, 3])) + @test isequal_canonical(y^3, NonlinearExpr(:^, Any[y, 3])) return end From 3d27b58500c0a0ebf6aafa1e3a70a088a2ccf7d0 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 7 Sep 2023 13:45:47 +1200 Subject: [PATCH 2/7] Add test for inference --- test/test_nlp_expr.jl | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index c2e20c1c586..bc16d03e78c 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -900,5 +900,17 @@ function test_nonlinear_operator_vector_args() return end +function test_nonlinear_operator_inferred() + model = Model() + @variable(model, x) + @inferred op_less_than_or_equal_to(x, 1) + @test @inferred(op_less_than_or_equal_to(1, 2)) == true + @variable(model, y[1:2, 1:2]) + op_det = NonlinearOperator(LinearAlgebra.det, :det) + @inferred log(op_det(y)) + z = rand(2, 2) + @inferred log(op_det(z)) + return +end end # module From 0189e8048356d7e5ddfc049cfbe0391eb199ee73 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 7 Sep 2023 14:53:15 +1200 Subject: [PATCH 3/7] Fix --- src/nlp_expr.jl | 2 +- test/test_nlp_expr.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/nlp_expr.jl b/src/nlp_expr.jl index 1fb07479166..b83231eaf21 100644 --- a/src/nlp_expr.jl +++ b/src/nlp_expr.jl @@ -83,7 +83,7 @@ struct GenericNonlinearExpr{V<:AbstractVariableRef} <: AbstractJuMPScalar function GenericNonlinearExpr{V}( head::Symbol, - args::Vararg{Any} + args::Vararg{Any}, ) where {V<:AbstractVariableRef} return new{V}(head, Any[a for a in args]) end diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index bc16d03e78c..c5822bd6ecc 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -908,7 +908,7 @@ function test_nonlinear_operator_inferred() @variable(model, y[1:2, 1:2]) op_det = NonlinearOperator(LinearAlgebra.det, :det) @inferred log(op_det(y)) - z = rand(2, 2) + z = [2.0 1.0; 1.0 2.0] @inferred log(op_det(z)) return end From fb54aa89d4c5fb6c744cf6e9496a5c39a5358a79 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 7 Sep 2023 14:56:19 +1200 Subject: [PATCH 4/7] Move functions which could be omitted for now --- src/nlp_expr.jl | 24 +++++++++++++----------- test/test_nlp_expr.jl | 26 +++++++++++++------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/src/nlp_expr.jl b/src/nlp_expr.jl index b83231eaf21..2c200a1bf3f 100644 --- a/src/nlp_expr.jl +++ b/src/nlp_expr.jl @@ -489,8 +489,6 @@ end moi_function(x::Number) = x -moi_function(x::AbstractArray) = moi_function.(x) - function moi_function(f::GenericNonlinearExpr{V}) where {V} ret = MOI.ScalarNonlinearFunction(f.head, similar(f.args)) stack = Tuple{MOI.ScalarNonlinearFunction,Int,GenericNonlinearExpr{V}}[] @@ -518,8 +516,6 @@ end jump_function(::GenericModel{T}, x::Number) where {T} = convert(T, x) -jump_function(model::GenericModel, x::AbstractArray) = jump_function.(model, x) - function jump_function(model::GenericModel, f::MOI.ScalarNonlinearFunction) V = variable_ref_type(typeof(model)) ret = GenericNonlinearExpr{V}(f.head, Any[]) @@ -830,13 +826,6 @@ function variable_ref_type(::NonlinearOperator, x::AbstractJuMPScalar) return variable_ref_type(x) end -function variable_ref_type( - ::NonlinearOperator, - ::AbstractArray{T}, -) where {T<:AbstractJuMPScalar} - return variable_ref_type(T) -end - function (f::NonlinearOperator)(args::Vararg{Any,N}) where {N} types = variable_ref_type.(Ref(f), args) if (i = findfirst(!isnothing, types)) !== nothing @@ -1132,3 +1121,16 @@ end function LinearAlgebra.qr(::AbstractMatrix{<:AbstractJuMPScalar}) return throw(MOI.UnsupportedNonlinearOperator(:qr)) end + +# Add support for AbstractArray arguments in GenericNonlinearExpr + +function variable_ref_type( + ::NonlinearOperator, + ::AbstractArray{T}, +) where {T<:AbstractJuMPScalar} + return variable_ref_type(T) +end + +moi_function(x::AbstractArray) = moi_function.(x) + +jump_function(model::GenericModel, x::AbstractArray) = jump_function.(model, x) diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index c5822bd6ecc..c5107e7bdf9 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -888,28 +888,28 @@ function test_ma_zero_in_operate!!() return end -function test_nonlinear_operator_vector_args() - model = Model() - @variable(model, x[1:2, 1:2]) - op_det = NonlinearOperator(LinearAlgebra.det, :det) - @objective(model, Min, log(op_det(x))) - @test isequal_canonical(objective_function(model), log(op_det(x))) - f = MOI.get(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}()) - g = MOI.ScalarNonlinearFunction(:det, Any[index.(x)]) - @test f ≈ MOI.ScalarNonlinearFunction(:log, Any[g]) - return -end - function test_nonlinear_operator_inferred() model = Model() @variable(model, x) @inferred op_less_than_or_equal_to(x, 1) @test @inferred(op_less_than_or_equal_to(1, 2)) == true - @variable(model, y[1:2, 1:2]) + return +end + +function test_nonlinear_operator_vector_args() + model = Model() + @variable(model, x[1:2, 1:2]) op_det = NonlinearOperator(LinearAlgebra.det, :det) @inferred log(op_det(y)) z = [2.0 1.0; 1.0 2.0] @inferred log(op_det(z)) + @objective(model, Min, log(op_det(x))) + @test isequal_canonical(objective_function(model), log(op_det(x))) + f = MOI.get(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}()) + g = MOI.ScalarNonlinearFunction(:det, Any[index.(x)]) + h = MOI.ScalarNonlinearFunction(:log, Any[g]) + @test f ≈ h + @test moi_function(log(op_det(x))) ≈ h return end From 8c4368eaec3e27252eed7cfcd7285b8071127a32 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Thu, 7 Sep 2023 17:35:31 +1200 Subject: [PATCH 5/7] Update test/test_nlp_expr.jl --- test/test_nlp_expr.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index c5107e7bdf9..35c86e43db8 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -900,7 +900,7 @@ function test_nonlinear_operator_vector_args() model = Model() @variable(model, x[1:2, 1:2]) op_det = NonlinearOperator(LinearAlgebra.det, :det) - @inferred log(op_det(y)) + @inferred log(op_det(x)) z = [2.0 1.0; 1.0 2.0] @inferred log(op_det(z)) @objective(model, Min, log(op_det(x))) From fc522a73a76dac451814874c363f06def3a01737 Mon Sep 17 00:00:00 2001 From: odow Date: Thu, 7 Sep 2023 20:04:42 +1200 Subject: [PATCH 6/7] Remove support --- src/nlp_expr.jl | 13 ------------- test/test_nlp_expr.jl | 17 ----------------- 2 files changed, 30 deletions(-) diff --git a/src/nlp_expr.jl b/src/nlp_expr.jl index 2c200a1bf3f..6b2358b2935 100644 --- a/src/nlp_expr.jl +++ b/src/nlp_expr.jl @@ -1121,16 +1121,3 @@ end function LinearAlgebra.qr(::AbstractMatrix{<:AbstractJuMPScalar}) return throw(MOI.UnsupportedNonlinearOperator(:qr)) end - -# Add support for AbstractArray arguments in GenericNonlinearExpr - -function variable_ref_type( - ::NonlinearOperator, - ::AbstractArray{T}, -) where {T<:AbstractJuMPScalar} - return variable_ref_type(T) -end - -moi_function(x::AbstractArray) = moi_function.(x) - -jump_function(model::GenericModel, x::AbstractArray) = jump_function.(model, x) diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index 35c86e43db8..03fdd5fae2e 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -896,21 +896,4 @@ function test_nonlinear_operator_inferred() return end -function test_nonlinear_operator_vector_args() - model = Model() - @variable(model, x[1:2, 1:2]) - op_det = NonlinearOperator(LinearAlgebra.det, :det) - @inferred log(op_det(x)) - z = [2.0 1.0; 1.0 2.0] - @inferred log(op_det(z)) - @objective(model, Min, log(op_det(x))) - @test isequal_canonical(objective_function(model), log(op_det(x))) - f = MOI.get(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}()) - g = MOI.ScalarNonlinearFunction(:det, Any[index.(x)]) - h = MOI.ScalarNonlinearFunction(:log, Any[g]) - @test f ≈ h - @test moi_function(log(op_det(x))) ≈ h - return -end - end # module From cdecbb384c6e4f597a39bac8ec192a25ff0b36c8 Mon Sep 17 00:00:00 2001 From: odow Date: Fri, 8 Sep 2023 09:38:21 +1200 Subject: [PATCH 7/7] Add back inferred AbstractVariableRef constructor --- src/nlp_expr.jl | 37 ++++++++++++++++++++++++++++++------- test/test_nlp_expr.jl | 30 ++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 7 deletions(-) diff --git a/src/nlp_expr.jl b/src/nlp_expr.jl index 6b2358b2935..b76cbcd706b 100644 --- a/src/nlp_expr.jl +++ b/src/nlp_expr.jl @@ -96,6 +96,35 @@ struct GenericNonlinearExpr{V<:AbstractVariableRef} <: AbstractJuMPScalar end end +variable_ref_type(::Type{GenericNonlinearExpr}, ::Any) = nothing + +function variable_ref_type(::Type{GenericNonlinearExpr}, x::AbstractJuMPScalar) + return variable_ref_type(x) +end + +function _has_variable_ref_type(a) + return variable_ref_type(GenericNonlinearExpr, a) !== nothing +end + +function _variable_ref_type(head, args) + if (i = findfirst(_has_variable_ref_type, args)) !== nothing + V = variable_ref_type(GenericNonlinearExpr, args[i]) + return V::Type{<:AbstractVariableRef} + end + return error( + "Unable to create a nonlinear expression because it did not contain " * + "any JuMP scalars. head = `:$head`, args = `$args`.", + ) +end + +function GenericNonlinearExpr(head::Symbol, args::Vector{Any}) + return GenericNonlinearExpr{_variable_ref_type(head, args)}(head, args) +end + +function GenericNonlinearExpr(head::Symbol, args::Vararg{Any,N}) where {N} + return GenericNonlinearExpr{_variable_ref_type(head, args)}(head, args...) +end + """ NonlinearExpr @@ -820,14 +849,8 @@ function Base.show(io::IO, f::NonlinearOperator) return print(io, "NonlinearOperator($(f.func), :$(f.head))") end -variable_ref_type(::NonlinearOperator, ::Any) = nothing - -function variable_ref_type(::NonlinearOperator, x::AbstractJuMPScalar) - return variable_ref_type(x) -end - function (f::NonlinearOperator)(args::Vararg{Any,N}) where {N} - types = variable_ref_type.(Ref(f), args) + types = variable_ref_type.(GenericNonlinearExpr, args) if (i = findfirst(!isnothing, types)) !== nothing return GenericNonlinearExpr{types[i]}(f.head, args...) end diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index 03fdd5fae2e..bd309f4c1d8 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -896,4 +896,34 @@ function test_nonlinear_operator_inferred() return end +function test_generic_nonlinear_expr_infer_variable_type() + model = Model() + @variable(model, x) + @inferred GenericNonlinearExpr(:sin, x) + @inferred GenericNonlinearExpr GenericNonlinearExpr(:sin, Any[x]) + f = sin(x) + @test isequal_canonical(GenericNonlinearExpr(:sin, x), f) + @test isequal_canonical(GenericNonlinearExpr(:sin, Any[x]), f) + g = @expression(model, 1 <= x) + @inferred GenericNonlinearExpr(:<=, 1, x) + @inferred GenericNonlinearExpr GenericNonlinearExpr(:<=, Any[1, x]) + @test isequal_canonical(GenericNonlinearExpr(:<=, 1, x), g) + @test isequal_canonical(GenericNonlinearExpr(:<=, Any[1, x]), g) + @test_throws( + ErrorException( + "Unable to create a nonlinear expression because it did not " * + "contain any JuMP scalars. head = `:sin`, args = `(1,)`.", + ), + GenericNonlinearExpr(:sin, 1), + ) + @test_throws( + ErrorException( + "Unable to create a nonlinear expression because it did not " * + "contain any JuMP scalars. head = `:sin`, args = `Any[1]`.", + ), + GenericNonlinearExpr(:sin, Any[1]), + ) + return +end + end # module