From 3656206e563db12505ea3ae71b34c605e8ae659b Mon Sep 17 00:00:00 2001 From: joaquimg Date: Sun, 11 Oct 2026 00:23:55 -0300 Subject: [PATCH] fix the error for unsupported objective --- docs/src/changelog.md | 6 ++++++ src/cubic_objective.jl | 28 ++++++++++++++++++++++------ test/test_cubic.jl | 38 ++++++++++++++++++++++++++++++++------ 3 files changed, 60 insertions(+), 12 deletions(-) diff --git a/docs/src/changelog.md b/docs/src/changelog.md index 3dc8af88..1ee07857 100644 --- a/docs/src/changelog.md +++ b/docs/src/changelog.md @@ -16,6 +16,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 usually rejects them. The new `SolveWithoutVariables` and `NoVariablesFeasibilityTolerance` attributes control this behavior. +### Changed + +- Setting a `MOI.ScalarNonlinearFunction` objective that is not a valid cubic + polynomial, or whose parametric form the inner optimizer does not support, + now throws `MOI.UnsupportedAttribute` instead of an `ErrorException`. + ## Version 0.15.2 (March 15, 2026) ### Fixed diff --git a/src/cubic_objective.jl b/src/cubic_objective.jl index 58990ab8..7a7ffb6b 100644 --- a/src/cubic_objective.jl +++ b/src/cubic_objective.jl @@ -5,16 +5,23 @@ function MOI.set( model::Optimizer{T}, - ::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, + attr::MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}, f::MOI.ScalarNonlinearFunction, ) where {T} - # 1. Attempt to parse as cubic + # 1. Attempt to parse as cubic. A function that does not parse is an + # objective this optimizer does not support, so it is reported with the + # MOI exception for that: callers such as bridges or DiffOpt's choice of + # differentiation model can then catch it and try something else parsed = _parse_cubic_expression(f, T) if parsed === nothing - error( - "ScalarNonlinearFunction must be a valid cubic polynomial with " * - "parameters multiplying at most quadratic variable terms. " * - "Non-polynomial operations or degree > 3 are not supported.", + throw( + MOI.UnsupportedAttribute( + attr, + "ScalarNonlinearFunction must be a valid cubic polynomial " * + "with parameters multiplying at most quadratic variable " * + "terms. Non-polynomial operations or degree > 3 are not " * + "supported.", + ), ) end @@ -32,6 +39,15 @@ function MOI.set( current, ) catch e + msg = + "Failed to set cubic objective function, f = $f, on inner " * + "optimizer. Original error: $(sprint(showerror, e))" + # the inner optimizer not supporting the parametric form means this + # optimizer does not support the objective either; any other error is + # a genuine failure and keeps its plain report + if e isa MOI.UnsupportedError + throw(MOI.UnsupportedAttribute(attr, msg)) + end # rethrow the original error with the additional info of the objective function that caused it error( "Failed to set cubic objective function, f = $f, on inner " * diff --git a/test/test_cubic.jl b/test/test_cubic.jl index a8bf809f..dc3263bc 100644 --- a/test/test_cubic.jl +++ b/test/test_cubic.jl @@ -30,13 +30,17 @@ end # ============================================================================ """ -Mock optimizer that rejects ScalarQuadraticFunction objectives. +Mock optimizer that rejects ScalarQuadraticFunction objectives, with a plain +error or, if `unsupported`, with `MOI.UnsupportedAttribute`. """ struct NoQuadObjModel <: MOI.ModelLike inner::MOI.Utilities.Model{Float64} + unsupported::Bool end -NoQuadObjModel() = NoQuadObjModel(MOI.Utilities.Model{Float64}()) +function NoQuadObjModel(; unsupported::Bool = false) + return NoQuadObjModel(MOI.Utilities.Model{Float64}(), unsupported) +end MOI.add_variable(m::NoQuadObjModel) = MOI.add_variable(m.inner) MOI.is_empty(m::NoQuadObjModel) = MOI.is_empty(m.inner) @@ -46,10 +50,11 @@ function MOI.is_valid(m::NoQuadObjModel, vi::MOI.VariableIndex) end function MOI.set( - ::NoQuadObjModel, - ::MOI.ObjectiveFunction{<:MOI.ScalarQuadraticFunction}, + m::NoQuadObjModel, + attr::MOI.ObjectiveFunction{<:MOI.ScalarQuadraticFunction}, ::MOI.ScalarQuadraticFunction, ) + m.unsupported && throw(MOI.UnsupportedAttribute(attr)) return error("Quadratic objectives not supported") end @@ -865,9 +870,9 @@ function test_cubic_objective_set_invalid_expression() y = MOI.add_variable(model) z = MOI.add_variable(model) - # x*y*z (3 variables, no parameter) should error + # x*y*z (3 variables, no parameter) is not a supported objective f = MOI.ScalarNonlinearFunction(:*, Any[x, y, z]) - @test_throws ErrorException MOI.set( + @test_throws MOI.UnsupportedAttribute MOI.set( model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(), f, @@ -897,6 +902,27 @@ function test_cubic_objective_set_error_on_inner_optimizer() return end +function test_cubic_objective_set_unsupported_by_inner_optimizer() + model = POI.Optimizer(NoQuadObjModel(; unsupported = true)) + x = MOI.add_variable(model) + p, _ = MOI.add_constrained_variable(model, MOI.Parameter(2.0)) + p_v = POI.v_idx(POI.p_idx(p)) + # p * x^2 parses, but its parametric form is a quadratic objective, which + # the inner optimizer does not support, so neither does POI + f = MOI.ScalarNonlinearFunction(:*, Any[1.0, p_v, x, x]) + attr = MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}() + err = try + MOI.set(model, attr, f) + nothing + catch e + e + end + @test err isa MOI.UnsupportedAttribute + @test err.attr == attr + @test occursin("Failed to set cubic objective function", err.message) + return +end + function test_cubic_objective_get_no_cache() model = POI.Optimizer(HiGHS.Optimizer())