From b8acca9c66096574ede0edb543f980c07aaecc87 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 10 Jun 2026 01:20:22 +1200 Subject: [PATCH] [Utilities] fix use of recursion in canonicalize!(::ScalarNonlinearFunction) --- src/Utilities/functions.jl | 14 +++++++++++--- test/Utilities/test_functions.jl | 13 +++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/Utilities/functions.jl b/src/Utilities/functions.jl index e88ae35416..a512d380db 100644 --- a/src/Utilities/functions.jl +++ b/src/Utilities/functions.jl @@ -1072,9 +1072,17 @@ function canonicalize!( end function canonicalize!(f::MOI.ScalarNonlinearFunction) - for (i, arg) in enumerate(f.args) - if !is_canonical(arg) - f.args[i] = canonicalize!(arg) + # Don't use recursion here. This gets called for all scalar nonlinear + # constraints. + stack = MOI.ScalarNonlinearFunction[f] + while !isempty(stack) + g = pop!(stack) + for (i, arg) in enumerate(g.args) + if arg isa MOI.ScalarNonlinearFunction + push!(stack, arg) + elseif !is_canonical(arg) + g.args[i] = canonicalize!(arg) + end end end return f diff --git a/test/Utilities/test_functions.jl b/test/Utilities/test_functions.jl index 1d59ee3773..836ad3dffa 100644 --- a/test/Utilities/test_functions.jl +++ b/test/Utilities/test_functions.jl @@ -2044,6 +2044,19 @@ function test_ScalarNonlinearFunction_is_canonical() f = MOI.ScalarNonlinearFunction(:sin, Any[f]) end @test MOI.Utilities.is_canonical(f) + @test MOI.Utilities.canonicalize!(f) isa MOI.ScalarNonlinearFunction + g = MOI.ScalarNonlinearFunction(:^, Any[1.0 * x + 1.0 * x, 2]) + for _ in 1:100_000 + g = MOI.ScalarNonlinearFunction(:sin, Any[g]) + end + @test !MOI.Utilities.is_canonical(g) + MOI.Utilities.canonicalize!(g) + h = MOI.ScalarNonlinearFunction(:^, Any[2.0 * x, 2]) + for _ in 1:100_000 + h = MOI.ScalarNonlinearFunction(:sin, Any[h]) + end + @test MOI.Utilities.is_canonical(h) + @test isapprox(g, h) return end