From 21d2f9fcae1f3698d2481405f9f2fee8bbee5151 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Mon, 9 Mar 2026 00:01:49 -0300 Subject: [PATCH 1/2] Fixes in cubic and constraint interpretation --- src/MOI_wrapper.jl | 10 ++++++++-- src/cubic_objective.jl | 2 +- src/update_parameters.jl | 20 -------------------- test/test_MathOptInterface.jl | 34 ++++++++++++++++++++++++++++++++++ test/test_cubic.jl | 26 ++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 23 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c4c4449d..b28bb1c6 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -135,6 +135,7 @@ function MOI.is_empty(model::Optimizer) # obj model.affine_objective_cache === nothing && model.quadratic_objective_cache === nothing && + model.cubic_objective_cache === nothing && MOI.is_empty(model.original_objective_cache) && # isempty(model.vector_affine_constraint_cache) && @@ -878,7 +879,7 @@ function _add_constraint_with_parameters_on_function( end elseif model.constraints_interpretation == ONLY_CONSTRAINTS poi_ci = MOI.add_constraint(model, pf, set) - elseif model.constraints_interpretation == BOUNDS_AND_CONSTRAINTS + else #if model.constraints_interpretation == BOUNDS_AND_CONSTRAINTS if length(affine_variable_terms(pf)) == 1 && isone(MOI.coefficient(affine_variable_terms(pf)[])) poi_ci = _add_vi_constraint(model, pf, set) @@ -1897,12 +1898,17 @@ BOUNDS_AND_CONSTRAINTS::ConstraintsInterpretationCode = 2 """ struct ConstraintsInterpretation <: MOI.AbstractOptimizerAttribute end +function MOI.get(model::Optimizer, ::ConstraintsInterpretation) + return model.constraints_interpretation +end + function MOI.set( model::Optimizer, ::ConstraintsInterpretation, value::ConstraintsInterpretationCode, ) - return model.constraints_interpretation = value + model.constraints_interpretation = value + return end # diff --git a/src/cubic_objective.jl b/src/cubic_objective.jl index 3f9c5afd..58990ab8 100644 --- a/src/cubic_objective.jl +++ b/src/cubic_objective.jl @@ -38,7 +38,7 @@ function MOI.set( "optimizer. " * "This may be due to unsupported features in the cubic " * "expression. " * - "Original error: $(e.msg)", + "Original error: $(sprint(showerror, e))", ) end diff --git a/src/update_parameters.jl b/src/update_parameters.jl index 6a96f7b6..23ab7a91 100644 --- a/src/update_parameters.jl +++ b/src/update_parameters.jl @@ -50,24 +50,14 @@ function _update_affine_constraints!( MOI.AbstractScalarSet, }, ) where {F,S<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},V} - # cis = MOI.ConstraintIndex{F,S}[] - # sets = S[] - # sizehint!(cis, length(affine_constraint_cache_inner)) - # sizehint!(sets, length(affine_constraint_cache_inner)) for (inner_ci, pf) in affine_constraint_cache_inner delta_constant = _delta_parametric_constant(model, pf) if !iszero(delta_constant) pf.current_constant += delta_constant new_set = S(pf.set_constant - pf.current_constant) - # new_set = _set_with_new_constant(set, param_constant) MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set) - # push!(cis, inner_ci) - # push!(sets, new_set) end end - # if !isempty(cis) - # MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets) - # end return end @@ -192,19 +182,12 @@ function _update_quadratic_constraints!( MOI.AbstractScalarSet, }, ) where {F,S<:Union{MOI.LessThan,MOI.GreaterThan,MOI.EqualTo},V} - # cis = MOI.ConstraintIndex{F,S}[] - # sets = S[] - # sizehint!(cis, length(quadratic_constraint_cache_inner)) - # sizehint!(sets, length(quadratic_constraint_cache_inner)) for (inner_ci, pf) in quadratic_constraint_cache_inner delta_constant = _delta_parametric_constant(model, pf) if !iszero(delta_constant) pf.current_constant += delta_constant new_set = S(pf.set_constant - pf.current_constant) - # new_set = _set_with_new_constant(set, param_constant) MOI.set(model.optimizer, MOI.ConstraintSet(), inner_ci, new_set) - # push!(cis, inner_ci) - # push!(sets, new_set) end delta_terms = _delta_parametric_affine_terms(model, pf) if !isempty(delta_terms) @@ -213,9 +196,6 @@ function _update_quadratic_constraints!( MOI.modify(model.optimizer, cis, changes) end end - # if !isempty(cis) - # MOI.set(model.optimizer, MOI.ConstraintSet(), cis, sets) - # end return end diff --git a/test/test_MathOptInterface.jl b/test/test_MathOptInterface.jl index f3bb060a..46651bce 100644 --- a/test/test_MathOptInterface.jl +++ b/test/test_MathOptInterface.jl @@ -2181,6 +2181,40 @@ function test_name_from_bound() return end +function test_constraints_interpretation_get_set() + model = POI.Optimizer(MOI.Utilities.Model{Float64}()) + # Default value + @test MOI.get(model, POI.ConstraintsInterpretation()) == + POI.ONLY_CONSTRAINTS + # Round-trip for each value + MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) + @test MOI.get(model, POI.ConstraintsInterpretation()) == POI.ONLY_BOUNDS + MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS) + @test MOI.get(model, POI.ConstraintsInterpretation()) == + POI.ONLY_CONSTRAINTS + MOI.set(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS) + @test MOI.get(model, POI.ConstraintsInterpretation()) == + POI.BOUNDS_AND_CONSTRAINTS + return +end + +function test_only_bounds_coefficient_not_one_errors() + # ONLY_BOUNDS with coefficient ≠ 1 must error. + # The function must contain a parameter so that the parametric path is taken; + # a plain ScalarAffineFunction without parameters bypasses the check entirely. + model = POI.Optimizer(MOI.Utilities.Model{Float64}()) + x = MOI.add_variable(model) + p, _ = MOI.add_constrained_variable(model, MOI.Parameter(1.0)) + MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) + # 2.0 * x + 1.0 * p: single variable term with coefficient 2.0 ≠ 1 — must throw + f = MOI.ScalarAffineFunction( + [MOI.ScalarAffineTerm(2.0, x), MOI.ScalarAffineTerm(1.0, p)], + 0.0, + ) + @test_throws ErrorException MOI.add_constraint(model, f, MOI.LessThan(5.0)) + return +end + function test_get_constraint_set() model = POI.Optimizer(MOI.Utilities.Model{Float64}()) x = MOI.add_variable(model) diff --git a/test/test_cubic.jl b/test/test_cubic.jl index 831e8738..a8bf809f 100644 --- a/test/test_cubic.jl +++ b/test/test_cubic.jl @@ -1567,6 +1567,32 @@ function test_optimize_skips_update_without_parameter_change() return end +function test_is_empty_with_cubic_objective() + # Regression test: cubic_objective_cache must be checked in MOI.is_empty. + # Before the fix, is_empty ignored this cache and returned true incorrectly. + model = POI.Optimizer(HiGHS.Optimizer()) + MOI.set(model, MOI.Silent(), true) + @test MOI.is_empty(model) + + # Set a cubic objective to populate cubic_objective_cache + x = MOI.add_variable(model) + p, _ = MOI.add_constrained_variable(model, MOI.Parameter(1.0)) + p_v = POI.v_idx(POI.p_idx(p)) + f = MOI.ScalarNonlinearFunction(:*, Any[1.0, p_v, x, x]) + MOI.set(model, MOI.ObjectiveFunction{MOI.ScalarNonlinearFunction}(), f) + @test !MOI.is_empty(model) + + # After full empty!, model must be empty + cubic_cache = model.cubic_objective_cache + MOI.empty!(model) + @test MOI.is_empty(model) + + # Restore only the cubic cache — is_empty must now return false + model.cubic_objective_cache = cubic_cache + @test !MOI.is_empty(model) + return +end + function test_jump_cubic_pvv_partial_pair_update() # Two pvv terms with different parameters: p1*x*y and p2*x*z. # Update only p1 -> delta_quadratic has only (x,y). From cd5e348966e3a4f2ae3f933bb5cbdf4a3da2bce5 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Mon, 9 Mar 2026 00:41:35 -0300 Subject: [PATCH 2/2] fix doc test --- src/MOI_wrapper.jl | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index b28bb1c6..a595277c 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1887,13 +1887,10 @@ ParametricOptInterface.Optimizer{Float64, MOIU.Model{Float64}} └ NumberOfConstraints: 0 julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_BOUNDS) -ONLY_BOUNDS::ConstraintsInterpretationCode = 1 julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.ONLY_CONSTRAINTS) -ONLY_CONSTRAINTS::ConstraintsInterpretationCode = 0 julia> MOI.set(model, POI.ConstraintsInterpretation(), POI.BOUNDS_AND_CONSTRAINTS) -BOUNDS_AND_CONSTRAINTS::ConstraintsInterpretationCode = 2 ``` """ struct ConstraintsInterpretation <: MOI.AbstractOptimizerAttribute end