From 938eb1cdc43a6af21adfadded29a5c60fc4435d7 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Sun, 5 Oct 2025 19:40:06 -0300 Subject: [PATCH 1/9] start print tests --- test/jump_tests.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 4a5bb00..7776ff4 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1675,3 +1675,8 @@ function test_jump_errors() ) return end + +function test_print() + + return +end \ No newline at end of file From f5daebec56e74369d848e93381251e80ac0f0164 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Sun, 5 Oct 2025 19:53:08 -0300 Subject: [PATCH 2/9] add test --- test/jump_tests.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 7776ff4..888eab0 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1677,6 +1677,13 @@ function test_jump_errors() end function test_print() - + model = Model(() -> POI.Optimizer(GLPK.Optimizer())) + @variable(model, p in MOI.Parameter(1.0)) + @variable(model, x) + @constraint(model, con, x >= p) + @objective(model, Min, x) + filename = tempdir() * "/test.lp" + write_to_file(model, filename) + readlines(filename) |> println return end \ No newline at end of file From ae4e9089cf993089df38a66057b23bde587f80ea Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 7 Oct 2025 04:41:17 -0300 Subject: [PATCH 3/9] fix ListOfConstraintIndices --- src/MOI_wrapper.jl | 21 +++++++++++---------- test/jump_tests.jl | 8 ++++---- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index b9a0ff4..f54e0d1 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -805,6 +805,7 @@ function MOI.modify( c::MOI.ConstraintIndex{F,S}, chg::MOI.ScalarCoefficientChange{T}, ) where {F,S,T} + # TODO - outer or inner? if haskey(model.quadratic_constraint_cache, c) || haskey(model.affine_constraint_cache, c) error( @@ -1365,15 +1366,15 @@ function MOI.get( model::Optimizer, ::MOI.ListOfConstraintAttributesSet{F,S}, ) where {F,S} - if F <: MOI.ScalarQuadraticFunction - error( - "MOI.ListOfConstraintAttributesSet is not implemented for ScalarQuadraticFunction in ParametricOptInterface.", - ) - elseif F <: MOI.VectorQuadraticFunction - error( - "MOI.ListOfConstraintAttributesSet is not implemented for VectorQuadraticFunction in ParametricOptInterface.", - ) - end + # if F <: MOI.ScalarQuadraticFunction + # error( + # "MOI.ListOfConstraintAttributesSet is not implemented for ScalarQuadraticFunction in ParametricOptInterface.", + # ) + # elseif F <: MOI.VectorQuadraticFunction + # error( + # "MOI.ListOfConstraintAttributesSet is not implemented for VectorQuadraticFunction in ParametricOptInterface.", + # ) + # end return MOI.get(model.optimizer, MOI.ListOfConstraintAttributesSet{F,S}()) end @@ -1404,7 +1405,7 @@ function MOI.get( model::Optimizer, ::MOI.ListOfConstraintIndices{F,S}, ) where {S,F} - list = collect(values(model.constraint_outer_to_inner[F, S])) + list = collect(keys(model.constraint_outer_to_inner[F, S])) sort!(list, lt = (x, y) -> (x.value < y.value)) return list end diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 888eab0..755a5c6 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1677,13 +1677,13 @@ function test_jump_errors() end function test_print() - model = Model(() -> POI.Optimizer(GLPK.Optimizer())) + model = direct_model(POI.Optimizer(HiGHS.Optimizer())) @variable(model, p in MOI.Parameter(1.0)) @variable(model, x) - @constraint(model, con, x >= p) - @objective(model, Min, x) + @constraint(model, con, x >= p + p * p + p * x) + @objective(model, Min, 1 + 2x) filename = tempdir() * "/test.lp" write_to_file(model, filename) - readlines(filename) |> println + @test readlines(filename) == ["minimize", "obj: 1 + 2 x", "subject to", "c1: 1 x - 1 p + [ -1 p * x - 1 p ^ 2 ] >= 0", "Bounds", "x free", "p = 1", "End"] return end \ No newline at end of file From 42be2ce5c56396f494db62cd684b16558560b7d5 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Tue, 7 Oct 2025 23:41:42 -0300 Subject: [PATCH 4/9] fix attributes conditionals and printing --- src/MOI_wrapper.jl | 96 +++++++++++++++++++++++++++++------ src/ParametricOptInterface.jl | 3 ++ test/jump_tests.jl | 46 ++++++++++++++--- 3 files changed, 123 insertions(+), 22 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index f54e0d1..ed4bb87 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -566,8 +566,11 @@ function MOI.supports( # We can't tell at type-time whether the constraints will be quadratic or # lowered to affine, so we return the conservative choice for supports of # needing to support names for both quadratic and affine constraints. - return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) && - MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S}) + if MOI.supports_constraint(model.optimizer, F, S) + return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) && + MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S}) + end + return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S}) end function MOI.supports( @@ -579,8 +582,14 @@ function MOI.supports( # We can't tell at type-time whether the constraints will be quadratic or # lowered to affine, so we return the conservative choice for supports of # needing to support names for both quadratic and affine constraints. - return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) && - MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S}) + # TODO: + # switch to only check support name for the case of linear + # is a solver does not support quadratic constraints it will fain in add_ + if MOI.supports_constraint(model.optimizer, F, S) + return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{F,S}) && + MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S}) + end + return MOI.supports(model.optimizer, attr, MOI.ConstraintIndex{G,S}) end function MOI.set( @@ -805,9 +814,9 @@ function MOI.modify( c::MOI.ConstraintIndex{F,S}, chg::MOI.ScalarCoefficientChange{T}, ) where {F,S,T} - # TODO - outer or inner? - if haskey(model.quadratic_constraint_cache, c) || - haskey(model.affine_constraint_cache, c) + if haskey(model.quadratic_outer_to_inner, c) || + haskey(model.vector_quadratic_outer_to_inner, c) || + haskey(model.affine_outer_to_inner, c) error( "Parametric constraint cannot be modified in ParametricOptInterface, because it would conflict with parameter updates. You can update the parameters instead.", ) @@ -1362,19 +1371,50 @@ function MOI.get(model::Optimizer, ::MOI.ListOfVariableAttributesSet) return MOI.get(model.optimizer, MOI.ListOfVariableAttributesSet()) end +function MOI.get( + model::Optimizer, + ::MOI.ListOfConstraintAttributesSet{F,S}, +) where {T,F<:MOI.ScalarQuadraticFunction{T},S} + if MOI.supports_constraint(model.optimizer, F, S) + # in this case we cant tell if the constraint will be quadratic or + # lowered to affine + if model.warn_quad_affine_ambiguous + println( + "MOI.ListOfConstraintAttributesSet is not supported for ScalarQuadraticFunction in ParametricOptInterface, an empty list will be returned. This message can be suppressed by setting `POI._WarnIfQuadraticOfAffineFunctionAmbiguous` to false.", + ) + end + return [] + end + return MOI.get( + model.optimizer, + MOI.ListOfConstraintAttributesSet{MOI.ScalarAffineFunction{T},S}(), + ) +end + +function MOI.get( + model::Optimizer, + ::MOI.ListOfConstraintAttributesSet{F,S}, +) where {T,F<:MOI.VectorQuadraticFunction{T},S} + if MOI.supports_constraint(model.optimizer, F, S) + # in this case we cant tell if the constraint will be quadratic or + # lowered to affine + if model.warn_quad_affine_ambiguous + println( + "MOI.ListOfConstraintAttributesSet is not supported for VectorQuadraticFunction in ParametricOptInterface, an empty list will be returned. This message can be suppressed by setting `POI._WarnIfQuadraticOfAffineFunctionAmbiguous` to false.", + ) + end + return [] + end + return MOI.get( + model.optimizer, + MOI.ListOfConstraintAttributesSet{MOI.VectorAffineFunction{T},S}(), + ) +end + function MOI.get( model::Optimizer, ::MOI.ListOfConstraintAttributesSet{F,S}, ) where {F,S} - # if F <: MOI.ScalarQuadraticFunction - # error( - # "MOI.ListOfConstraintAttributesSet is not implemented for ScalarQuadraticFunction in ParametricOptInterface.", - # ) - # elseif F <: MOI.VectorQuadraticFunction - # error( - # "MOI.ListOfConstraintAttributesSet is not implemented for VectorQuadraticFunction in ParametricOptInterface.", - # ) - # end return MOI.get(model.optimizer, MOI.ListOfConstraintAttributesSet{F,S}()) end @@ -2115,3 +2155,27 @@ end function MOI.Utilities.final_touch(model::Optimizer, index_map) return MOI.Utilities.final_touch(model.optimizer, index_map) end + +""" + _WarnIfQuadraticOfAffineFunctionAmbiguous + +Some attributes such as `MOI.ListOfConstraintAttributesSet` are ambiguous +when the model contains parametric quadratic functions that can be lowered +to affine functions. This attribute can be set to `false` to skip the warning +when such ambiguity arises. The default value is `true`. +""" +struct _WarnIfQuadraticOfAffineFunctionAmbiguous <: + MOI.AbstractOptimizerAttribute end + +function MOI.set( + model::Optimizer, + ::_WarnIfQuadraticOfAffineFunctionAmbiguous, + value::Bool, +) + model.warn_quad_affine_ambiguous = value + return +end + +function MOI.get(model::Optimizer, ::_WarnIfQuadraticOfAffineFunctionAmbiguous) + return model.warn_quad_affine_ambiguous +end diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 4c08b86..5c2080a 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -179,6 +179,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer parameters_in_conflict::Set{MOI.VariableIndex} + warn_quad_affine_ambiguous::Bool + # extension data ext::Dict{Symbol,Any} function Optimizer{T}( @@ -243,6 +245,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer ONLY_CONSTRAINTS, save_original_objective_and_constraints, Set{MOI.VariableIndex}(), + true, Dict{Symbol,Any}(), ) end diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 755a5c6..d3cad95 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1659,20 +1659,45 @@ function test_jump_errors() backend(model1), MOI.NLPBlock(), ) - @test_throws ErrorException MOI.get( + + MOI.get( backend(model1), MOI.ListOfConstraintAttributesSet{ MOI.VectorQuadraticFunction{Float64}, - MOI.Nonpositives, + MOI.Nonnegatives, }(), ) - @test_throws ErrorException MOI.get( + + MOI.get( backend(model1), MOI.ListOfConstraintAttributesSet{ MOI.ScalarQuadraticFunction{Float64}, - MOI.EqualTo{Float64}, + MOI.LessThan{Float64}, }(), ) + + MOI.set( + backend(model1), + POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), + false, + ) + + MOI.get( + backend(model1), + MOI.ListOfConstraintAttributesSet{ + MOI.VectorQuadraticFunction{Float64}, + MOI.Nonnegatives, + }(), + ) + + MOI.get( + backend(model1), + MOI.ListOfConstraintAttributesSet{ + MOI.ScalarQuadraticFunction{Float64}, + MOI.LessThan{Float64}, + }(), + ) + return end @@ -1684,6 +1709,15 @@ function test_print() @objective(model, Min, 1 + 2x) filename = tempdir() * "/test.lp" write_to_file(model, filename) - @test readlines(filename) == ["minimize", "obj: 1 + 2 x", "subject to", "c1: 1 x - 1 p + [ -1 p * x - 1 p ^ 2 ] >= 0", "Bounds", "x free", "p = 1", "End"] + @test readlines(filename) == [ + "minimize", + "obj: 1 + 2 x", + "subject to", + "c1: 1 x - 1 p + [ -1 p * x - 1 p ^ 2 ] >= 0", + "Bounds", + "x free", + "p = 1", + "End", + ] return -end \ No newline at end of file +end From 26d7992e96bd3e34559d6836d54dc2e738575a01 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Wed, 8 Oct 2025 00:02:25 -0300 Subject: [PATCH 5/9] fix con name --- test/jump_tests.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 67d7bdd..0be81d6 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1713,7 +1713,7 @@ function test_print() "minimize", "obj: 1 + 2 x", "subject to", - "c1: 1 x - 1 p + [ -1 p * x - 1 p ^ 2 ] >= 0", + "con: 1 x - 1 p + [ -1 p * x - 1 p ^ 2 ] >= 0", "Bounds", "x free", "p = 1", From 01130d9f94c6ff33eea30dc6075b4d381ab3aad3 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Wed, 8 Oct 2025 00:40:28 -0300 Subject: [PATCH 6/9] add more tests --- test/jump_tests.jl | 65 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 0be81d6..84ca0d5 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1654,14 +1654,60 @@ function test_jump_errors() SCS.Optimizer(), ) optimizer1 = POI.Optimizer(cached1) - model1 = direct_model(optimizer1) + model = direct_model(optimizer1) @test_throws MOI.UnsupportedAttribute MOI.get( - backend(model1), + backend(model), MOI.NLPBlock(), ) MOI.get( - backend(model1), + backend(model), + MOI.ListOfConstraintAttributesSet{ + MOI.VectorQuadraticFunction{Float64}, + MOI.Nonnegatives, + }(), + ) + + MOI.get( + backend(model), + MOI.ListOfConstraintAttributesSet{ + MOI.ScalarQuadraticFunction{Float64}, + MOI.LessThan{Float64}, + }(), + ) + + MOI.set( + backend(model), + POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), + false, + ) + + @test MOI.get( + backend(model), + POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), + ) == false + + MOI.get( + backend(model), + MOI.ListOfConstraintAttributesSet{ + MOI.VectorQuadraticFunction{Float64}, + MOI.Nonnegatives, + }(), + ) + + MOI.get( + backend(model), + MOI.ListOfConstraintAttributesSet{ + MOI.ScalarQuadraticFunction{Float64}, + MOI.LessThan{Float64}, + }(), + ) + + model = Model(() -> ParametricOptInterface.Optimizer(Ipopt.Optimizer())) + + + MOI.get( + backend(model), MOI.ListOfConstraintAttributesSet{ MOI.VectorQuadraticFunction{Float64}, MOI.Nonnegatives, @@ -1669,7 +1715,7 @@ function test_jump_errors() ) MOI.get( - backend(model1), + backend(model), MOI.ListOfConstraintAttributesSet{ MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}, @@ -1677,13 +1723,18 @@ function test_jump_errors() ) MOI.set( - backend(model1), + backend(model), POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), false, ) + @test MOI.get( + backend(model), + POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), + ) == false + MOI.get( - backend(model1), + backend(model), MOI.ListOfConstraintAttributesSet{ MOI.VectorQuadraticFunction{Float64}, MOI.Nonnegatives, @@ -1691,7 +1742,7 @@ function test_jump_errors() ) MOI.get( - backend(model1), + backend(model), MOI.ListOfConstraintAttributesSet{ MOI.ScalarQuadraticFunction{Float64}, MOI.LessThan{Float64}, From 4c7066893fc7d6e972ed147bce2b67b79ba4b721 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Wed, 8 Oct 2025 00:44:41 -0300 Subject: [PATCH 7/9] fmt --- test/jump_tests.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 84ca0d5..72bddda 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1705,7 +1705,6 @@ function test_jump_errors() model = Model(() -> ParametricOptInterface.Optimizer(Ipopt.Optimizer())) - MOI.get( backend(model), MOI.ListOfConstraintAttributesSet{ From 59e5d64073c7d5c7e6c95d3dcef7c0f28b3bb3e7 Mon Sep 17 00:00:00 2001 From: joaquimg Date: Wed, 8 Oct 2025 01:52:32 -0300 Subject: [PATCH 8/9] fix tests --- test/jump_tests.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 72bddda..3bfb1b8 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1703,9 +1703,9 @@ function test_jump_errors() }(), ) - model = Model(() -> ParametricOptInterface.Optimizer(Ipopt.Optimizer())) + model = direct_model(POI.Optimizer(Ipopt.Optimizer())) - MOI.get( + @test_throws MOI.GetAttributeNotAllowed MOI.get( backend(model), MOI.ListOfConstraintAttributesSet{ MOI.VectorQuadraticFunction{Float64}, @@ -1732,7 +1732,7 @@ function test_jump_errors() POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), ) == false - MOI.get( + @test_throws MOI.GetAttributeNotAllowed MOI.get( backend(model), MOI.ListOfConstraintAttributesSet{ MOI.VectorQuadraticFunction{Float64}, From 560a1907bfd95c861bc58328d08349e83efbd38c Mon Sep 17 00:00:00 2001 From: joaquimg Date: Thu, 30 Oct 2025 09:55:59 -0300 Subject: [PATCH 9/9] Add tests --- src/MOI_wrapper.jl | 2 +- test/jump_tests.jl | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index f91a2dd..def730e 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -813,7 +813,7 @@ end function MOI.modify( model::Optimizer, c::MOI.ConstraintIndex{F,S}, - chg::MOI.ScalarCoefficientChange{T}, + chg::Union{MOI.ScalarConstantChange{T},MOI.ScalarCoefficientChange{T}}, ) where {F,S,T} if haskey(model.quadratic_outer_to_inner, c) || haskey(model.vector_quadratic_outer_to_inner, c) || diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 3bfb1b8..89353a2 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1771,3 +1771,46 @@ function test_print() ] return end + +function test_set_normalized_coefficient() + model = direct_model(POI.Optimizer(HiGHS.Optimizer())) + @variable(model, p in MOI.Parameter(1.0)) + @variable(model, x) + @constraint(model, con, x >= p) + @constraint(model, con1, x >= 1) + @constraint(model, con2, x >= x * p) + @test_throws ErrorException set_normalized_coefficient(con, x, 2.0) + set_normalized_coefficient(con1, x, 2.0) + @test_throws ErrorException set_normalized_coefficient(con2, x, 2.0) + return +end + +function test_ListOfConstraintAttributesSet() + cached = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + MOI.Utilities.AUTOMATIC, + ) + optimizer = POI.Optimizer(cached) + model = direct_model(optimizer) + @variable(model, p in MOI.Parameter(1.0)) + @variable(model, x) + @constraint(model, con, [x * p] in MOI.Nonnegatives(1)) + ret = get_attribute( + model, + MOI.ListOfConstraintAttributesSet{ + MOI.VectorQuadraticFunction{Float64}, + MOI.Nonnegatives, + }(), + ) + @test ret == [] + set_attribute(model, POI._WarnIfQuadraticOfAffineFunctionAmbiguous(), false) + ret = get_attribute( + model, + MOI.ListOfConstraintAttributesSet{ + MOI.VectorQuadraticFunction{Float64}, + MOI.Nonnegatives, + }(), + ) + @test ret == [] + return +end