diff --git a/src/JuMP.jl b/src/JuMP.jl index 4a589aa1a83..207c9989996 100644 --- a/src/JuMP.jl +++ b/src/JuMP.jl @@ -163,7 +163,7 @@ mutable struct GenericModel{T<:Real} <: AbstractModel enable_macro_timing::Bool macro_times::Dict{Tuple{LineNumberNode,String},Float64} # A cache to track common subexpressions based on their `objectid`. - subexpressions::Dict{UInt64,MOI.ScalarNonlinearFunction} + subexpressions::Base.IdDict{Any,MOI.ScalarNonlinearFunction} end value_type(::Type{GenericModel{T}}) where {T} = T @@ -278,7 +278,7 @@ function direct_generic_model( Dict{Any,MOI.ConstraintIndex}(), false, Dict{Tuple{LineNumberNode,String},Float64}(), - Dict{UInt64,MOI.ScalarNonlinearFunction}(), + Base.IdDict{Any,MOI.ScalarNonlinearFunction}(), ) end diff --git a/src/jump_moi_function.jl b/src/jump_moi_function.jl index 871788af125..7caa4f044ae 100644 --- a/src/jump_moi_function.jl +++ b/src/jump_moi_function.jl @@ -167,10 +167,9 @@ end moi_function_type(::Type{<:GenericNonlinearExpr}) = MOI.ScalarNonlinearFunction function moi_function(model::GenericModel, f::GenericNonlinearExpr{V}) where {V} - # key = objectid(f) - # if haskey(model.subexpressions, key) - # return model.subexpressions[key] - # end + if (subexpression = get(model.subexpressions, f, nothing)) !== nothing + return subexpression + end ret = MOI.ScalarNonlinearFunction(f.head, similar(f.args)) stack = Tuple{MOI.ScalarNonlinearFunction,Int,GenericNonlinearExpr{V}}[] for i in length(f.args):-1:1 @@ -182,11 +181,10 @@ function moi_function(model::GenericModel, f::GenericNonlinearExpr{V}) where {V} end while !isempty(stack) parent, i, arg = pop!(stack) - # arg_key = objectid(arg) - # if haskey(model.subexpressions, arg_key) - # parent.args[i] = model.subexpressions[arg_key] - # continue - # end + if (subexpression = get(model.subexpressions, arg, nothing)) !== nothing + parent.args[i] = subexpression + continue + end child = MOI.ScalarNonlinearFunction(arg.head, similar(arg.args)) parent.args[i] = child for j in length(arg.args):-1:1 @@ -196,9 +194,9 @@ function moi_function(model::GenericModel, f::GenericNonlinearExpr{V}) where {V} child.args[j] = moi_function(model, arg.args[j]) end end - # model.subexpressions[arg_key] = child + model.subexpressions[arg] = child end - # model.subexpressions[key] = ret + model.subexpressions[f] = ret return ret end diff --git a/test/test_nlp_expr.jl b/test/test_nlp_expr.jl index 9992269f68c..caa9e72026d 100644 --- a/test/test_nlp_expr.jl +++ b/test/test_nlp_expr.jl @@ -1390,38 +1390,38 @@ function test_custom_array() return end -# function test_scalar_nonlinear_moi_function() -# model = Model() -# @variable(model, x) -# y1 = atan(x, 2) -# y1_moi = MOI.ScalarNonlinearFunction(:atan, Any[index(x), 2]) -# y2 = y1 + y1 -# y2_moi = MOI.ScalarNonlinearFunction(:+, Any[y1_moi, y1_moi]) -# y3 = exp(y2) -# y3_moi = MOI.ScalarNonlinearFunction(:exp, Any[y2_moi]) -# # Test y1 -# @test isapprox(moi_function(y1), y1_moi) -# @test length(model.subexpressions) == 1 -# @test isapprox(moi_function(model, y1), y1_moi) -# @test length(model.subexpressions) == 1 -# @test isapprox(model.subexpressions[objectid(y1)], y1_moi) -# # Test y2 -# @test isapprox(moi_function(y2), y2_moi) -# @test length(model.subexpressions) == 2 -# @test isapprox(moi_function(model, y2), y2_moi) -# @test length(model.subexpressions) == 2 -# @test isapprox(model.subexpressions[objectid(y1)], y1_moi) -# @test isapprox(model.subexpressions[objectid(y2)], y2_moi) -# # Test y3 -# @test isapprox(moi_function(y3), y3_moi) -# @test length(model.subexpressions) == 3 -# @test isapprox(moi_function(model, y3), y3_moi) -# @test length(model.subexpressions) == 3 -# @test isapprox(model.subexpressions[objectid(y1)], y1_moi) -# @test isapprox(model.subexpressions[objectid(y2)], y2_moi) -# @test isapprox(model.subexpressions[objectid(y3)], y3_moi) -# return -# end +function test_scalar_nonlinear_moi_function() + model = Model() + @variable(model, x) + y1 = atan(x, 2) + y1_moi = MOI.ScalarNonlinearFunction(:atan, Any[index(x), 2]) + y2 = y1 + y1 + y2_moi = MOI.ScalarNonlinearFunction(:+, Any[y1_moi, y1_moi]) + y3 = exp(y2) + y3_moi = MOI.ScalarNonlinearFunction(:exp, Any[y2_moi]) + # Test y1 + @test isapprox(moi_function(y1), y1_moi) + @test length(model.subexpressions) == 1 + @test isapprox(moi_function(model, y1), y1_moi) + @test length(model.subexpressions) == 1 + @test isapprox(model.subexpressions[y1], y1_moi) + # Test y2 + @test isapprox(moi_function(y2), y2_moi) + @test length(model.subexpressions) == 2 + @test isapprox(moi_function(model, y2), y2_moi) + @test length(model.subexpressions) == 2 + @test isapprox(model.subexpressions[y1], y1_moi) + @test isapprox(model.subexpressions[y2], y2_moi) + # Test y3 + @test isapprox(moi_function(y3), y3_moi) + @test length(model.subexpressions) == 3 + @test isapprox(moi_function(model, y3), y3_moi) + @test length(model.subexpressions) == 3 + @test isapprox(model.subexpressions[y1], y1_moi) + @test isapprox(model.subexpressions[y2], y2_moi) + @test isapprox(model.subexpressions[y3], y3_moi) + return +end function test_addition_with_zero_Base_sum() model = Model() @@ -1482,4 +1482,15 @@ function test_extension_expression_bedmas_parentheses( return end +function test_issue_4203() + n = 25_000 + model = Model() + @variable(model, x) + @constraint(model, cons[i in 1:n], sin(x) * i <= 0) + @testset "$i" for i in 1:n + @test isequal_canonical(constraint_object(cons[i]).func, sin(x) * i - 0) + end + return +end + end # module