From 842e2756e7e8ac730aa2538984caf2c2f6990674 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 12:16:38 -0400 Subject: [PATCH 01/14] fix key vector quadratic --- src/MOI_wrapper.jl | 85 +++++++++++++++++++++++++++++------ src/ParametricOptInterface.jl | 4 ++ test/jump_tests.jl | 55 +++++++++++++++++++++-- 3 files changed, 127 insertions(+), 17 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index a3c8aa63..51f89418 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -125,7 +125,9 @@ function MOI.is_empty(model::Optimizer) isempty(model.affine_constraint_cache_set) && # quad ctr model.last_quad_add_added == 0 && + model.last_vec_quad_add_added == 0 && isempty(model.quadratic_outer_to_inner) && + isempty(model.vector_quadratic_outer_to_inner) && isempty(model.quadratic_constraint_cache) && isempty(model.quadratic_constraint_cache_set) && isempty(model.vector_quadratic_constraint_cache) && @@ -162,7 +164,9 @@ function MOI.empty!(model::Optimizer{T}) where {T} empty!(model.affine_constraint_cache_set) # quad ctr model.last_quad_add_added = 0 + model.last_vec_quad_add_added = 0 empty!(model.quadratic_outer_to_inner) + empty!(model.vector_quadratic_outer_to_inner) empty!(model.quadratic_constraint_cache) empty!(model.quadratic_constraint_cache_set) empty!(model.vector_quadratic_constraint_cache) @@ -315,6 +319,15 @@ function _add_to_constraint_map!( return end +function _add_to_constraint_map!( + model::Optimizer, + ci::MOI.ConstraintIndex{F,S}, +) where {F<:MOI.VectorQuadraticFunction,S} + model.last_vec_quad_add_added += 1 + model.constraint_outer_to_inner[ci] = ci + return +end + function MOI.supports( model::Optimizer, attr::MOI.AbstractVariableAttribute, @@ -510,6 +523,25 @@ function MOI.set( return end +function MOI.set( + model::Optimizer, + attr::MOI.ConstraintName, + c::MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}, + name::String, +) where {T,S<:MOI.AbstractSet} + if haskey(model.vector_quadratic_outer_to_inner, c) + MOI.set( + model.optimizer, + attr, + model.vector_quadratic_outer_to_inner[c], + name, + ) + else + MOI.set(model.optimizer, attr, c, name) + end + return +end + function MOI.set( model::Optimizer, attr::MOI.ConstraintName, @@ -546,6 +578,22 @@ function MOI.get( end end +function MOI.get( + model::Optimizer, + attr::MOI.ConstraintName, + c::MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}, +) where {T,S<:MOI.AbstractSet} + if haskey(model.vector_quadratic_outer_to_inner, c) + return MOI.get( + model.optimizer, + attr, + model.vector_quadratic_outer_to_inner[c], + ) + else + return MOI.get(model.optimizer, attr, c) + end +end + function MOI.get( model::Optimizer, attr::MOI.ConstraintName, @@ -606,13 +654,21 @@ function MOI.get( return _original_function( model.quadratic_constraint_cache[inner_ci], ) - elseif haskey(model.vector_quadratic_constraint_cache, inner_ci) + else + return convert( + MOI.ScalarQuadraticFunction{T}, + MOI.get(model.optimizer, attr, inner_ci), + ) + end + elseif haskey(model.vector_quadratic_outer_to_inner, ci) + inner_ci = model.vector_quadratic_outer_to_inner[ci] + if haskey(model.vector_quadratic_constraint_cache, inner_ci) return _original_function( model.vector_quadratic_constraint_cache[inner_ci], ) else return convert( - MOI.ScalarQuadraticFunction{T}, + MOI.VectorQuadraticFunction{T}, MOI.get(model.optimizer, attr, inner_ci), ) end @@ -655,8 +711,8 @@ function MOI.get( if haskey(model.quadratic_outer_to_inner, ci) inner_ci = model.quadratic_outer_to_inner[ci] return model.quadratic_constraint_cache_set[inner_ci] - elseif haskey(model.vector_quadratic_constraint_cache, ci) - inner_ci = model.vector_quadratic_constraint_cache[ci] + elseif haskey(model.vector_quadratic_outer_to_inner, ci) + inner_ci = model.vector_quadratic_outer_to_inner[ci] return model.vector_quadratic_constraint_cache_set[inner_ci] elseif haskey(model.affine_outer_to_inner, ci) inner_ci = model.affine_outer_to_inner[ci] @@ -956,22 +1012,22 @@ function _add_constraint_with_parameters_on_function( if !_is_vector_affine(func) fq = func inner_ci = MOI.add_constraint(model.optimizer, fq, set) - model.last_quad_add_added += 1 + model.last_vec_quad_add_added += 1 outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}( - model.last_quad_add_added, + model.last_vec_quad_add_added, ) - model.quadratic_outer_to_inner[outer_ci] = inner_ci + model.vector_quadratic_outer_to_inner[outer_ci] = inner_ci model.constraint_outer_to_inner[outer_ci] = inner_ci else fa = MOI.VectorAffineFunction(func.affine_terms, func.constants) inner_ci = MOI.add_constraint(model.optimizer, fa, set) - model.last_quad_add_added += 1 + model.last_vec_quad_add_added += 1 outer_ci = MOI.ConstraintIndex{MOI.VectorQuadraticFunction{T},S}( - model.last_quad_add_added, + model.last_vec_quad_add_added, ) # This part is used to remember that ci came from a quadratic function # It is particularly useful because sometimes the constraint mutates - model.quadratic_outer_to_inner[outer_ci] = inner_ci + model.vector_quadratic_outer_to_inner[outer_ci] = inner_ci model.constraint_outer_to_inner[outer_ci] = inner_ci end model.vector_quadratic_constraint_cache[inner_ci] = pf @@ -995,10 +1051,11 @@ function MOI.delete( model::Optimizer, c::MOI.ConstraintIndex{F,S}, ) where {F<:MOI.VectorQuadraticFunction,S<:MOI.AbstractSet} - ci_inner = model.constraint_outer_to_inner[c] - if haskey(model.quadratic_constraint_cache, ci_inner) - delete!(model.quadratic_constraint_cache, ci_inner) - delete!(model.quadratic_constraint_cache_set, ci_inner) + if haskey(model.vector_quadratic_outer_to_inner, c) + ci_inner = model.vector_quadratic_outer_to_inner[c] + delete!(model.vector_quadratic_outer_to_inner, c) + delete!(model.vector_quadratic_constraint_cache, ci_inner) + delete!(model.vector_quadratic_constraint_cache_set, ci_inner) MOI.delete(model.optimizer, ci_inner) else MOI.delete(model.optimizer, c) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index d9b386ed..c1ba6942 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -130,9 +130,11 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer # quadratic constraitn data last_quad_add_added::Int64 + last_vec_quad_add_added::Int64 # Store the map for SQFs (some might be transformed into SAF) # for instance p*p + var -> ScalarAffine(var) quadratic_outer_to_inner::DoubleDict{MOI.ConstraintIndex} + vector_quadratic_outer_to_inner::DoubleDict{MOI.ConstraintIndex} # Clever cache of data (inner key) quadratic_constraint_cache::DoubleDict{ParametricQuadraticFunction{T}} # Store original constraint set (inner key) @@ -212,6 +214,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer DoubleDict{MOI.AbstractScalarSet}(), # quadratic constraint 0, + 0, + DoubleDict{MOI.ConstraintIndex}(), DoubleDict{MOI.ConstraintIndex}(), DoubleDict{ParametricQuadraticFunction{T}}(), DoubleDict{MOI.AbstractScalarSet}(), diff --git a/test/jump_tests.jl b/test/jump_tests.jl index f9d36410..b412341d 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1305,7 +1305,7 @@ function test_jump_psd_cone_with_parameter_pv() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 1 / 3 atol = 1e-5 - # delete(model, con) + delete(model, con) end function test_jump_psd_cone_with_parameter_pp() @@ -1336,7 +1336,7 @@ function test_jump_psd_cone_with_parameter_pp() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 9.0 atol = 1e-5 - # delete(model, con) + delete(model, con) end function test_jump_psd_cone_with_parameter_p() @@ -1363,5 +1363,54 @@ function test_jump_psd_cone_with_parameter_p() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 3.0 atol = 1e-5 - # delete(model, con) + delete(model, con) end + +# p=1.0 +# model = JuMP.Model(SCS.Optimizer) +# @variable(model, x) +# @constraint( +# model, +# con, +# [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) +# ) +# @objective(model, Min, x) +# @test is_valid(model, con) +# optimize!(model) +# @test value(x) ≈ 0.803845 atol = 1e-5 +# p=3.0 +# model = JuMP.Model(SCS.Optimizer) +# @variable(model, x) +# @constraint( +# model, +# con, +# [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) +# ) +# @objective(model, Min, x) +# @test is_valid(model, con) +# optimize!(model) +# @test value(x) ≈ 0.416888 atol = 1e-5 +# function test_jump_psd_cone_with_parameter_quadratic() +# cached = MOI.Bridges.full_bridge_optimizer( +# MOI.Utilities.CachingOptimizer( +# MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), +# SCS.Optimizer(), +# ), +# Float64, +# ) +# optimizer = POI.Optimizer(cached) +# model = direct_model(optimizer) +# @variable(model, x) +# @variable(model, p in MOI.Parameter(1.0)) +# @constraint( +# model, +# con, +# [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) +# ) +# @objective(model, Min, x) +# @test is_valid(model, con) +# optimize!(model) +# @test value(x) ≈ 0.803845 atol = 1e-5 +# end + + \ No newline at end of file From 44d0eb4a9c83180f4d36cd2ae9c29aee6523a260 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 13:54:34 -0400 Subject: [PATCH 02/14] add new tests and fix affine cache --- src/parametric_functions.jl | 10 +-- test/jump_tests.jl | 129 +++++++++++++++++++++++++++++------- 2 files changed, 112 insertions(+), 27 deletions(-) diff --git a/src/parametric_functions.jl b/src/parametric_functions.jl index 47d57cb6..08dc78ba 100644 --- a/src/parametric_functions.jl +++ b/src/parametric_functions.jl @@ -705,10 +705,12 @@ function _parametric_affine_terms( base + term.scalar_term.coefficient * model.parameters[p_idx_val] end - for (term, coef) in f.affine_data - output_idx = term.output_index - var = term.scalar_term.variable - param_terms_dict[(var, output_idx)] = coef + # TODO: check if affine data should only contains variables that appear in pv + for (var, coef) in f.affine_data + if !haskey(param_terms_dict, var) + param_terms_dict[var] = zero(T) + end + param_terms_dict[var] += coef end return param_terms_dict diff --git a/test/jump_tests.jl b/test/jump_tests.jl index b412341d..48b8af3d 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1390,27 +1390,110 @@ end # @test is_valid(model, con) # optimize!(model) # @test value(x) ≈ 0.416888 atol = 1e-5 -# function test_jump_psd_cone_with_parameter_quadratic() -# cached = MOI.Bridges.full_bridge_optimizer( -# MOI.Utilities.CachingOptimizer( -# MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), -# SCS.Optimizer(), -# ), -# Float64, -# ) -# optimizer = POI.Optimizer(cached) -# model = direct_model(optimizer) -# @variable(model, x) -# @variable(model, p in MOI.Parameter(1.0)) -# @constraint( -# model, -# con, -# [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) -# ) -# @objective(model, Min, x) -# @test is_valid(model, con) -# optimize!(model) -# @test value(x) ≈ 0.803845 atol = 1e-5 -# end +function test_jump_psd_cone_with_parameter_px_x_px() + cached = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ), + Float64, + ) + optimizer = POI.Optimizer(cached) + model = direct_model(optimizer) + @variable(model, x) + @variable(model, p in MOI.Parameter(1.0)) + @constraint( + model, + con, + [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) + @objective(model, Min, x) + @test is_valid(model, con) + optimize!(model) + @test value(x) ≈ 0.803845 atol = 1e-5 + set_parameter_value(p, 3.0) + optimize!(model) + @test value(x) ≈ 0.416888 atol = 1e-5 + delete(model, con) +end - \ No newline at end of file +function test_jump_psd_cone_with_parameter_pp_x_px() + cached = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ), + Float64, + ) + optimizer = POI.Optimizer(cached) + model = direct_model(optimizer) + @variable(model, x) + @variable(model, p in MOI.Parameter(1.0)) + @constraint( + model, + con, + [p * p, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) + @objective(model, Min, x) + @test is_valid(model, con) + optimize!(model) + @test value(x) ≈ 0.7499854 atol = 1e-5 + set_parameter_value(p, 3.0) + optimize!(model) + @test value(x) ≈ 0.0971795 atol = 1e-5 + delete(model, con) +end + +function test_jump_psd_cone_with_parameter_p_x_px() + cached = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ), + Float64, + ) + optimizer = POI.Optimizer(cached) + model = direct_model(optimizer) + @variable(model, x) + @variable(model, p in MOI.Parameter(1.0)) + @constraint( + model, + con, + [p, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) + @objective(model, Min, x) + @test is_valid(model, con) + optimize!(model) + @test value(x) ≈ 0.7499854 atol = 1e-5 + set_parameter_value(p, 3.0) + optimize!(model) + @test value(x) ≈ 0.0971795 atol = 1e-5 + delete(model, con) +end + +function test_jump_psd_cone_with_parameter_p_x_pp() + cached = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ), + Float64, + ) + optimizer = POI.Optimizer(cached) + model = direct_model(optimizer) + @variable(model, x) + @variable(model, p in MOI.Parameter(1.0)) + @constraint( + model, + con, + [p, (2 * x - 3), p * 3 * p] in MOI.PositiveSemidefiniteConeTriangle(2) + ) + @objective(model, Min, x) + @test is_valid(model, con) + optimize!(model) + @test value(x) ≈ 0.633969 atol = 1e-5 + set_parameter_value(p, 3.0) + optimize!(model) + @test value(x) ≈ -2.9999734 atol = 1e-5 + delete(model, con) +end \ No newline at end of file From a4f780cb90872291c66ef2c0ab748afc3bf8ed37 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 14:05:25 -0400 Subject: [PATCH 03/14] format --- test/jump_tests.jl | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 48b8af3d..88cca465 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1305,7 +1305,7 @@ function test_jump_psd_cone_with_parameter_pv() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 1 / 3 atol = 1e-5 - delete(model, con) + return delete(model, con) end function test_jump_psd_cone_with_parameter_pp() @@ -1336,7 +1336,7 @@ function test_jump_psd_cone_with_parameter_pp() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 9.0 atol = 1e-5 - delete(model, con) + return delete(model, con) end function test_jump_psd_cone_with_parameter_p() @@ -1363,9 +1363,9 @@ function test_jump_psd_cone_with_parameter_p() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 3.0 atol = 1e-5 - delete(model, con) + return delete(model, con) end - + # p=1.0 # model = JuMP.Model(SCS.Optimizer) # @variable(model, x) @@ -1405,7 +1405,8 @@ function test_jump_psd_cone_with_parameter_px_x_px() @constraint( model, con, - [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) + [p * x, (2 * x - 3), p * 3 * x] in + MOI.PositiveSemidefiniteConeTriangle(2) ) @objective(model, Min, x) @test is_valid(model, con) @@ -1414,9 +1415,9 @@ function test_jump_psd_cone_with_parameter_px_x_px() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 0.416888 atol = 1e-5 - delete(model, con) + return delete(model, con) end - + function test_jump_psd_cone_with_parameter_pp_x_px() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( @@ -1432,7 +1433,8 @@ function test_jump_psd_cone_with_parameter_pp_x_px() @constraint( model, con, - [p * p, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) + [p * p, (2 * x - 3), p * 3 * x] in + MOI.PositiveSemidefiniteConeTriangle(2) ) @objective(model, Min, x) @test is_valid(model, con) @@ -1441,7 +1443,7 @@ function test_jump_psd_cone_with_parameter_pp_x_px() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ 0.0971795 atol = 1e-5 - delete(model, con) + return delete(model, con) end function test_jump_psd_cone_with_parameter_p_x_px() @@ -1467,8 +1469,8 @@ function test_jump_psd_cone_with_parameter_p_x_px() @test value(x) ≈ 0.7499854 atol = 1e-5 set_parameter_value(p, 3.0) optimize!(model) - @test value(x) ≈ 0.0971795 atol = 1e-5 - delete(model, con) + @test value(x) ≈ 0.236506 atol = 1e-5 + return delete(model, con) end function test_jump_psd_cone_with_parameter_p_x_pp() @@ -1495,5 +1497,5 @@ function test_jump_psd_cone_with_parameter_p_x_pp() set_parameter_value(p, 3.0) optimize!(model) @test value(x) ≈ -2.9999734 atol = 1e-5 - delete(model, con) -end \ No newline at end of file + return delete(model, con) +end From d6f53a19dea02d1772132cfca6e5b56ef5623dda Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 14:15:03 -0400 Subject: [PATCH 04/14] test constraint name set and get --- test/moi_tests.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/moi_tests.jl b/test/moi_tests.jl index 31590440..81af74f8 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -2044,4 +2044,9 @@ function test_psd_cone_with_parameter() MOI.optimize!(model) @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 / 3 atol = 1e-5 + + # test constraint name + @test MOI.get(model, MOI.ConstraintName(), c_index) == "" + MOI.set(model, MOI.ConstraintName(), c_index, "psd_cone") + @test MOI.get(model, MOI.ConstraintName(), c_index) == "psd_cone" end From 48a6217648e26c0431e58bfd36784305c0d47e67 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 14:30:06 -0400 Subject: [PATCH 05/14] add no parameter problem --- test/jump_tests.jl | 60 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 4 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 88cca465..6cbf7b9e 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1390,7 +1390,7 @@ end # @test is_valid(model, con) # optimize!(model) # @test value(x) ≈ 0.416888 atol = 1e-5 -function test_jump_psd_cone_with_parameter_px_x_px() +function test_jump_psd_cone_with_parameter_pv_v_pv() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), @@ -1418,7 +1418,7 @@ function test_jump_psd_cone_with_parameter_px_x_px() return delete(model, con) end -function test_jump_psd_cone_with_parameter_pp_x_px() +function test_jump_psd_cone_with_parameter_pp_v_pv() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), @@ -1446,7 +1446,7 @@ function test_jump_psd_cone_with_parameter_pp_x_px() return delete(model, con) end -function test_jump_psd_cone_with_parameter_p_x_px() +function test_jump_psd_cone_with_parameter_p_v_pv() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), @@ -1473,7 +1473,7 @@ function test_jump_psd_cone_with_parameter_p_x_px() return delete(model, con) end -function test_jump_psd_cone_with_parameter_p_x_pp() +function test_jump_psd_cone_with_parameter_p_v_pp() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), @@ -1499,3 +1499,55 @@ function test_jump_psd_cone_with_parameter_p_x_pp() @test value(x) ≈ -2.9999734 atol = 1e-5 return delete(model, con) end + +p = 1.0 +model = JuMP.Model(SCS.Optimizer) +@variable(model, x) +@constraint( + model, + con, + [x, x * (x - 1), x] in MOI.PositiveSemidefiniteConeTriangle(2) +) +@objective(model, Min, x) +@test is_valid(model, con) +optimize!(model) +@test value(x) ≈ 0.50000 atol = 1e-5 + +function test_jump_psd_cone_with_parameter_v_and_vv() + cached = MOI.Bridges.full_bridge_optimizer( + MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ), + Float64, + ) + optimizer = POI.Optimizer(cached) + model = direct_model(optimizer) + @variable(model, x) + @variable(model, p in MOI.Parameter(1.0)) + @constraint( + model, + con, + [x, (x - 1), x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) + @test is_valid(model, con) + optimize!(model) + @test value(x) ≈ 0.50000 atol = 1e-5 + delete(model, con) + unregister(model, :con) + @test_throws MOI.UnsupportedConstraint @constraint( + model, + con, + [x, x * (x - 1), x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) + @test_throws MOI.UnsupportedConstraint @constraint( + model, + con, + [x * x, (x - 1), x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) + @test_throws MOI.UnsupportedConstraint @constraint( + model, + con, + [x, (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) +end From f85c25d342b3d190e9d64901cc1fe59cb65e16a5 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 14:30:35 -0400 Subject: [PATCH 06/14] fix test 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 6cbf7b9e..9d349b8f 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1513,7 +1513,7 @@ model = JuMP.Model(SCS.Optimizer) optimize!(model) @test value(x) ≈ 0.50000 atol = 1e-5 -function test_jump_psd_cone_with_parameter_v_and_vv() +function test_jump_psd_cone_without_parameter_v_and_vv() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), From e5bab59360aade2e24d23b0fa05e20af78d9d6c9 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 14:30:48 -0400 Subject: [PATCH 07/14] fix comment --- test/jump_tests.jl | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 9d349b8f..557e4ffd 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1500,19 +1500,6 @@ function test_jump_psd_cone_with_parameter_p_v_pp() return delete(model, con) end -p = 1.0 -model = JuMP.Model(SCS.Optimizer) -@variable(model, x) -@constraint( - model, - con, - [x, x * (x - 1), x] in MOI.PositiveSemidefiniteConeTriangle(2) -) -@objective(model, Min, x) -@test is_valid(model, con) -optimize!(model) -@test value(x) ≈ 0.50000 atol = 1e-5 - function test_jump_psd_cone_without_parameter_v_and_vv() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( From 7c1dba7dbf8a0e46e14ffbfb3ed4c169a41e50a6 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 14:33:15 -0400 Subject: [PATCH 08/14] force has_parameter and !_is_vector_affine --- test/jump_tests.jl | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 557e4ffd..676a7759 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1537,4 +1537,9 @@ function test_jump_psd_cone_without_parameter_v_and_vv() con, [x, (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) ) + @test_throws MOI.UnsupportedConstraint @constraint( + model, + con, + [x, p * (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) + ) end From 7ba75202992610bc325339522aee8696c6f38d6b Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 15:54:35 -0400 Subject: [PATCH 09/14] update coverage --- src/parametric_functions.jl | 85 +++++++++++++++++----------------- src/update_parameters.jl | 91 +++++++++++++++++++------------------ 2 files changed, 90 insertions(+), 86 deletions(-) diff --git a/src/parametric_functions.jl b/src/parametric_functions.jl index 08dc78ba..d5932358 100644 --- a/src/parametric_functions.jl +++ b/src/parametric_functions.jl @@ -716,48 +716,49 @@ function _parametric_affine_terms( return param_terms_dict end -function _delta_parametric_affine_terms( - model, - f::ParametricVectorQuadraticFunction{T}, -) where {T} - delta_terms = Dict{Tuple{Int,MOI.VariableIndex},T}() - - # Handle parameter-variable quadratic terms (px) that become affine (x) when p is updated - for term in f.pv - p_idx_val = p_idx(term.scalar_term.variable_1) - var = term.scalar_term.variable_2 - output_idx = term.output_index - - if haskey(model.updated_parameters, p_idx_val) && - !isnan(model.updated_parameters[p_idx_val]) - old_param_val = model.parameters[p_idx_val] - new_param_val = model.updated_parameters[p_idx_val] - delta_coef = - term.scalar_term.coefficient * (new_param_val - old_param_val) - - key = (output_idx, var) - current_delta = get(delta_terms, key, zero(T)) - delta_terms[key] = current_delta + delta_coef - end - end - - # Handle parameter-only affine terms - for term in f.p - p_idx_val = p_idx(term.scalar_term.variable) - output_idx = term.output_index - - if haskey(model.updated_parameters, p_idx_val) && - !isnan(model.updated_parameters[p_idx_val]) - old_param_val = model.parameters[p_idx_val] - new_param_val = model.updated_parameters[p_idx_val] - - # This becomes a constant change, not an affine term change - # We'll handle this in the constant update function - end - end - - return delta_terms -end +# TODO: USED once we update _update_vector_quadratic_constraints! +# function _delta_parametric_affine_terms( +# model, +# f::ParametricVectorQuadraticFunction{T}, +# ) where {T} +# delta_terms = Dict{Tuple{Int,MOI.VariableIndex},T}() + +# # Handle parameter-variable quadratic terms (px) that become affine (x) when p is updated +# for term in f.pv +# p_idx_val = p_idx(term.scalar_term.variable_1) +# var = term.scalar_term.variable_2 +# output_idx = term.output_index + +# if haskey(model.updated_parameters, p_idx_val) && +# !isnan(model.updated_parameters[p_idx_val]) +# old_param_val = model.parameters[p_idx_val] +# new_param_val = model.updated_parameters[p_idx_val] +# delta_coef = +# term.scalar_term.coefficient * (new_param_val - old_param_val) + +# key = (output_idx, var) +# current_delta = get(delta_terms, key, zero(T)) +# delta_terms[key] = current_delta + delta_coef +# end +# end + +# # Handle parameter-only affine terms +# for term in f.p +# p_idx_val = p_idx(term.scalar_term.variable) +# output_idx = term.output_index + +# if haskey(model.updated_parameters, p_idx_val) && +# !isnan(model.updated_parameters[p_idx_val]) +# old_param_val = model.parameters[p_idx_val] +# new_param_val = model.updated_parameters[p_idx_val] + +# # This becomes a constant change, not an affine term change +# # We'll handle this in the constant update function +# end +# end + +# return delta_terms +# end function _update_cache!( f::ParametricVectorQuadraticFunction{T}, diff --git a/src/update_parameters.jl b/src/update_parameters.jl index 9fc00e62..c14db5bd 100644 --- a/src/update_parameters.jl +++ b/src/update_parameters.jl @@ -330,58 +330,61 @@ function _update_vector_quadratic_constraints!(model::Optimizer) return end -function _delta_parametric_constant( - model, - f::ParametricVectorQuadraticFunction{T}, -) where {T} - delta_constants = zeros(T, length(f.current_constant)) - - # Handle parameter-only affine terms - for term in f.p - p_idx_val = p_idx(term.scalar_term.variable) - output_idx = term.output_index - - if !isnan(model.updated_parameters[p_idx_val]) - old_param_val = model.parameters[p_idx_val] - new_param_val = model.updated_parameters[p_idx_val] - delta_constants[output_idx] += - term.scalar_term.coefficient * (new_param_val - old_param_val) - end - end +# TODO: USED once we update _update_vector_quadratic_constraints! +# function _delta_parametric_constant( +# model, +# f::ParametricVectorQuadraticFunction{T}, +# ) where {T} +# delta_constants = zeros(T, length(f.current_constant)) - # Handle parameter-parameter quadratic terms - for term in f.pp - idx = term.output_index - var1 = term.scalar_term.variable_1 - var2 = term.scalar_term.variable_2 - p1 = p_idx(var1) - p2 = p_idx(var2) - - if !isnan(model.updated_parameters[p1]) || - !isnan(model.updated_parameters[p2]) - old_val1 = model.parameters[p1] - old_val2 = model.parameters[p2] - new_val1 = - !isnan(model.updated_parameters[p1]) ? - model.updated_parameters[p1] : old_val1 - new_val2 = - !isnan(model.updated_parameters[p2]) ? - model.updated_parameters[p2] : old_val2 - - coef = term.scalar_term.coefficient / (var1 == var2 ? 2 : 1) - delta_constants[idx] += - coef * (new_val1 * new_val2 - old_val1 * old_val2) - end - end +# # Handle parameter-only affine terms +# for term in f.p +# p_idx_val = p_idx(term.scalar_term.variable) +# output_idx = term.output_index - return delta_constants -end +# if !isnan(model.updated_parameters[p_idx_val]) +# old_param_val = model.parameters[p_idx_val] +# new_param_val = model.updated_parameters[p_idx_val] +# delta_constants[output_idx] += +# term.scalar_term.coefficient * (new_param_val - old_param_val) +# end +# end +# # Handle parameter-parameter quadratic terms +# for term in f.pp +# idx = term.output_index +# var1 = term.scalar_term.variable_1 +# var2 = term.scalar_term.variable_2 +# p1 = p_idx(var1) +# p2 = p_idx(var2) + +# if !isnan(model.updated_parameters[p1]) || +# !isnan(model.updated_parameters[p2]) +# old_val1 = model.parameters[p1] +# old_val2 = model.parameters[p2] +# new_val1 = +# !isnan(model.updated_parameters[p1]) ? +# model.updated_parameters[p1] : old_val1 +# new_val2 = +# !isnan(model.updated_parameters[p2]) ? +# model.updated_parameters[p2] : old_val2 + +# coef = term.scalar_term.coefficient / (var1 == var2 ? 2 : 1) +# delta_constants[idx] += +# coef * (new_val1 * new_val2 - old_val1 * old_val2) +# end +# end + +# return delta_constants +# end + +# TODO: Update once MOI.VectorConstantChange is implemented function _update_vector_quadratic_constraints!( model::Optimizer, vector_quadratic_constraint_cache_inner::DoubleDictInner{F,S,V}, ) where {F,S,V} for (inner_ci, pf) in vector_quadratic_constraint_cache_inner + # delta_constants = _delta_parametric_constant(model, pf) # if !iszero(delta_constants) # pf.current_constant .+= delta_constants From 9b15cf2ee4508400a4997ace63951d0fb2783e04 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 16:25:45 -0400 Subject: [PATCH 10/14] remove useless function --- src/MOI_wrapper.jl | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 51f89418..9fadeddb 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -319,15 +319,6 @@ function _add_to_constraint_map!( return end -function _add_to_constraint_map!( - model::Optimizer, - ci::MOI.ConstraintIndex{F,S}, -) where {F<:MOI.VectorQuadraticFunction,S} - model.last_vec_quad_add_added += 1 - model.constraint_outer_to_inner[ci] = ci - return -end - function MOI.supports( model::Optimizer, attr::MOI.AbstractVariableAttribute, From b4335d0f6bd77b029ff3c1331a21b496276851dc Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 16:56:21 -0400 Subject: [PATCH 11/14] update tests coverage --- test/jump_tests.jl | 76 +++++++++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 676a7759..59417a9c 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1366,30 +1366,6 @@ function test_jump_psd_cone_with_parameter_p() return delete(model, con) end -# p=1.0 -# model = JuMP.Model(SCS.Optimizer) -# @variable(model, x) -# @constraint( -# model, -# con, -# [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) -# ) -# @objective(model, Min, x) -# @test is_valid(model, con) -# optimize!(model) -# @test value(x) ≈ 0.803845 atol = 1e-5 -# p=3.0 -# model = JuMP.Model(SCS.Optimizer) -# @variable(model, x) -# @constraint( -# model, -# con, -# [p * x, (2 * x - 3), p * 3 * x] in MOI.PositiveSemidefiniteConeTriangle(2) -# ) -# @objective(model, Min, x) -# @test is_valid(model, con) -# optimize!(model) -# @test value(x) ≈ 0.416888 atol = 1e-5 function test_jump_psd_cone_with_parameter_pv_v_pv() cached = MOI.Bridges.full_bridge_optimizer( MOI.Utilities.CachingOptimizer( @@ -1543,3 +1519,55 @@ function test_jump_psd_cone_without_parameter_v_and_vv() [x, p * (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) ) end + +function test_variable_and_constraint_not_registered() + cached1 = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ) + optimizer1 = POI.Optimizer(cached1) + cached2 = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ) + optimizer2 = POI.Optimizer(cached2) + model = direct_model(optimizer1) + model2 = direct_model(optimizer2) + set_silent(model) + set_silent(model2) + @variable(model, x) + @variable(model, p in MOI.Parameter(1.0)) + @variable(model2, p2 in MOI.Parameter(1.0)) + @constraint(model, con, [x - p] in MOI.Nonnegatives(1)) + @test_throws ErrorException("Variable not in the model") MOI.set( + backend(model2), + MOI.VariablePrimalStart(), + index(x), + 1.0, + ) + @test_throws ErrorException("Variable not in the model") MOI.get( + backend(model2), + MOI.VariablePrimalStart(), + index(x), + ) + @test_throws ErrorException("Parameter not in the model") MOI.get( + backend(model2), + MOI.ConstraintFunction(), + index(ParameterRef(p)), + ) + @test_throws ErrorException("Parameter not in the model") MOI.get( + backend(model2), + MOI.ConstraintSet(), + index(ParameterRef(p)), + ) + @test_throws ErrorException("Variable not in the model") MOI.set( + backend(model2), + MOI.ObjectiveFunction{MOI.VariableIndex}(), + index(x), + ) + @test_throws ErrorException("Cannot use a parameter as objective function alone") MOI.set( + backend(model2), + MOI.ObjectiveFunction{MOI.VariableIndex}(), + index(p), + ) +end From 61d092184e86a68cc95cfd168f854228dcf2c4ba Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 17:05:03 -0400 Subject: [PATCH 12/14] format and rm useless functions --- src/MOI_wrapper.jl | 18 ------------------ test/jump_tests.jl | 4 +++- 2 files changed, 3 insertions(+), 19 deletions(-) diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 9fadeddb..eee208b0 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -547,16 +547,6 @@ function MOI.set( return end -function MOI.set( - model::Optimizer, - attr::MOI.ConstraintName, - c::MOI.ConstraintIndex, - name::String, -) - MOI.set(model.optimizer, attr, c, name) - return -end - function MOI.get( model::Optimizer, attr::MOI.ConstraintName, @@ -585,14 +575,6 @@ function MOI.get( end end -function MOI.get( - model::Optimizer, - attr::MOI.ConstraintName, - c::MOI.ConstraintIndex, -) - return MOI.get(model.optimizer, attr, c) -end - function MOI.get( model::Optimizer, attr::MOI.ConstraintName, diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 59417a9c..05cde2b8 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1565,7 +1565,9 @@ function test_variable_and_constraint_not_registered() MOI.ObjectiveFunction{MOI.VariableIndex}(), index(x), ) - @test_throws ErrorException("Cannot use a parameter as objective function alone") MOI.set( + @test_throws ErrorException( + "Cannot use a parameter as objective function alone", + ) MOI.set( backend(model2), MOI.ObjectiveFunction{MOI.VariableIndex}(), index(p), From 236fd9aacfb30e2c7b6fad47a4d348c888427b2e Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 17:13:03 -0400 Subject: [PATCH 13/14] improve tests to force coverage improvement --- 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 05cde2b8..414f1fd4 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1501,17 +1501,17 @@ function test_jump_psd_cone_without_parameter_v_and_vv() @test_throws MOI.UnsupportedConstraint @constraint( model, con, - [x, x * (x - 1), x] in MOI.PositiveSemidefiniteConeTriangle(2) + [p * p, x * (x - 1), p] in MOI.PositiveSemidefiniteConeTriangle(2) ) @test_throws MOI.UnsupportedConstraint @constraint( model, con, - [x * x, (x - 1), x] in MOI.PositiveSemidefiniteConeTriangle(2) + [x * x, (x - 1), p] in MOI.PositiveSemidefiniteConeTriangle(2) ) @test_throws MOI.UnsupportedConstraint @constraint( model, con, - [x, (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) + [p, (x - 1), x * x] in MOI.PositiveSemidefiniteConeTriangle(2) ) @test_throws MOI.UnsupportedConstraint @constraint( model, From 06412ad13ef629d57521161d87995e0a9f17bdb2 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 18 Aug 2025 17:31:59 -0400 Subject: [PATCH 14/14] fix typo --- test/jump_tests.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jump_tests.jl b/test/jump_tests.jl index 414f1fd4..f3e3f6f3 100644 --- a/test/jump_tests.jl +++ b/test/jump_tests.jl @@ -1537,6 +1537,7 @@ function test_variable_and_constraint_not_registered() set_silent(model2) @variable(model, x) @variable(model, p in MOI.Parameter(1.0)) + @variable(model, p1 in MOI.Parameter(1.0)) @variable(model2, p2 in MOI.Parameter(1.0)) @constraint(model, con, [x - p] in MOI.Nonnegatives(1)) @test_throws ErrorException("Variable not in the model") MOI.set( @@ -1553,12 +1554,12 @@ function test_variable_and_constraint_not_registered() @test_throws ErrorException("Parameter not in the model") MOI.get( backend(model2), MOI.ConstraintFunction(), - index(ParameterRef(p)), + index(ParameterRef(p1)), ) @test_throws ErrorException("Parameter not in the model") MOI.get( backend(model2), MOI.ConstraintSet(), - index(ParameterRef(p)), + index(ParameterRef(p1)), ) @test_throws ErrorException("Variable not in the model") MOI.set( backend(model2),