From 333e0a135b25ba3f75c58e076d6911e9bbcf0c88 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 18 Feb 2026 14:44:05 +1300 Subject: [PATCH 1/4] [breaking] remove ParameterValue and ParameterDual --- benchmark/run_benchmarks.jl | 42 ++++------ benchmark/run_benchmarks_jump.jl | 12 +-- src/MOI_wrapper.jl | 75 ----------------- src/duals.jl | 64 ++++---------- test/test_JuMP.jl | 138 +++++++++++++++++-------------- test/test_MathOptInterface.jl | 27 +++--- 6 files changed, 129 insertions(+), 229 deletions(-) diff --git a/benchmark/run_benchmarks.jl b/benchmark/run_benchmarks.jl index f71a8a11..4caf25cc 100644 --- a/benchmark/run_benchmarks.jl +++ b/benchmark/run_benchmarks.jl @@ -94,9 +94,8 @@ function poi_add_saf_variables_and_parameters_ctr_parameter_update( ) model = POI.Optimizer(MOI.Utilities.Model{Float64}()) x = MOI.add_variables(model, N / 2) - y = first.( - MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))), - ) + p = MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))) + y, cy = first.(p), last.(p) for _ in 1:M MOI.add_constraint( model, @@ -107,7 +106,7 @@ function poi_add_saf_variables_and_parameters_ctr_parameter_update( MOI.GreaterThan(1.0), ) end - MOI.set.(model, POI.ParameterValue(), y, 0.5) + MOI.set.(model, MOI.ConstraintSet(), cy, MOI.Parameter(0.5)) POI.update_parameters!(model) return end @@ -169,9 +168,8 @@ end function poi_add_sqf_variables_parameters_ctr_parameter_update(N::Int, M::Int) model = POI.Optimizer(MOI.Utilities.Model{Float64}()) x = MOI.add_variables(model, N / 2) - y = first.( - MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))), - ) + p = MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))) + y, cy = first.(p), last.(p) for _ in 1:M MOI.add_constraint( model, @@ -183,7 +181,7 @@ function poi_add_sqf_variables_parameters_ctr_parameter_update(N::Int, M::Int) MOI.GreaterThan(1.0), ) end - MOI.set.(model, POI.ParameterValue(), y, 0.5) + MOI.set.(model, MOI.ConstraintSet(), cy, MOI.Parameter(0.5)) POI.update_parameters!(model) return end @@ -211,9 +209,8 @@ end function poi_add_sqf_parameters_parameters_ctr_parameter_update(N::Int, M::Int) model = POI.Optimizer(MOI.Utilities.Model{Float64}()) x = MOI.add_variables(model, N / 2) - y = first.( - MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))), - ) + p = MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))) + y, cy = first.(p), last.(p) for _ in 1:M MOI.add_constraint( model, @@ -225,7 +222,7 @@ function poi_add_sqf_parameters_parameters_ctr_parameter_update(N::Int, M::Int) MOI.GreaterThan(1.0), ) end - MOI.set.(model, POI.ParameterValue(), y, 0.5) + MOI.set.(model, MOI.ConstraintSet(), cy, MOI.Parameter(0.5)) POI.update_parameters!(model) return end @@ -281,9 +278,8 @@ function poi_add_saf_variables_and_parameters_obj_parameter_update( ) model = POI.Optimizer(MOI.Utilities.Model{Float64}()) x = MOI.add_variables(model, N / 2) - y = first.( - MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))), - ) + p = MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))) + y, cy = first.(p), last.(p) for _ in 1:M MOI.set( model, @@ -295,7 +291,7 @@ function poi_add_saf_variables_and_parameters_obj_parameter_update( ) end for _ in 1:M - MOI.set.(model, POI.ParameterValue(), y, 0.5) + MOI.set.(model, MOI.ConstraintSet(), cy, MOI.Parameter(0.5)) POI.update_parameters!(model) end return @@ -358,9 +354,8 @@ end function poi_add_sqf_variables_parameters_obj_parameter_update(N::Int, M::Int) model = POI.Optimizer(MOI.Utilities.Model{Float64}()) x = MOI.add_variables(model, N / 2) - y = first.( - MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))), - ) + p = MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))) + y, cy = first.(p), last.(p) for _ in 1:M MOI.set( model, @@ -373,7 +368,7 @@ function poi_add_sqf_variables_parameters_obj_parameter_update(N::Int, M::Int) ) end for _ in 1:M - MOI.set.(model, POI.ParameterValue(), y, 0.5) + MOI.set.(model, MOI.ConstraintSet(), cy, MOI.Parameter(0.5)) POI.update_parameters!(model) end return @@ -402,9 +397,8 @@ end function poi_add_sqf_parameters_parameters_obj_parameter_update(N::Int, M::Int) model = POI.Optimizer(MOI.Utilities.Model{Float64}()) x = MOI.add_variables(model, N / 2) - y = first.( - MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))), - ) + p = MOI.add_constrained_variable.(model, MOI.Parameter.(ones(Int(N / 2)))) + y, cy = first.(p), last.(p) for _ in 1:M MOI.set( model, @@ -417,7 +411,7 @@ function poi_add_sqf_parameters_parameters_obj_parameter_update(N::Int, M::Int) ) end for _ in 1:M - MOI.set.(model, POI.ParameterValue(), y, 0.5) + MOI.set.(model, MOI.ConstraintSet(), cy, MOI.Parameter(0.5)) POI.update_parameters!(model) end return diff --git a/benchmark/run_benchmarks_jump.jl b/benchmark/run_benchmarks_jump.jl index 6feb9d37..5449570e 100644 --- a/benchmark/run_benchmarks_jump.jl +++ b/benchmark/run_benchmarks_jump.jl @@ -131,7 +131,7 @@ function poi_add_saf_variables_and_parameters_ctr_parameter_update( @variable(model, x[i=1:Int(N/2)]) @variable(model, p[i=1:Int(N/2)] in Parameter(0)) @constraint(model, con[i=1:M], sum(x) + sum(p) >= 1) - MOI.set.(model, POI.ParameterValue(), p, 0.5) + set_parameter_value.(p, 0.5) POI.update_parameters!(backend(model)) return end @@ -189,7 +189,7 @@ function poi_add_sqf_variables_parameters_ctr_parameter_update(N::Int, M::Int) @variable(model, x[i=1:Int(N/2)]) @variable(model, p[i=1:Int(N/2)] in Parameter(1)) @constraint(model, con[i=1:M], x' * p >= 1) - MOI.set.(model, POI.ParameterValue(), p, 0.5) + set_parameter_value.(p, 0.5) POI.update_parameters!(backend(model)) return end @@ -221,7 +221,7 @@ function poi_add_sqf_parameters_parameters_ctr_parameter_update(N::Int, M::Int) @variable(model, x[i=1:Int(N/2)]) @variable(model, p[i=1:Int(N/2)] in Parameter(1)) @constraint(model, con[i=1:M], p' * p >= 1) - MOI.set.(model, POI.ParameterValue(), p, 0.5) + set_parameter_value.(p, 0.5) POI.update_parameters!(backend(model)) return end @@ -289,7 +289,7 @@ function poi_add_saf_variables_and_parameters_obj_parameter_update( @objective(model, Min, sum(x) + sum(p)) end for _ in 1:M - MOI.set.(model, POI.ParameterValue(), p, 0.5) + set_parameter_value.(p, 0.5) POI.update_parameters!(backend(model)) end return @@ -357,7 +357,7 @@ function poi_add_sqf_variables_parameters_obj_parameter_update(N::Int, M::Int) @objective(model, Min, x' * p) end for _ in 1:M - MOI.set.(model, POI.ParameterValue(), p, 0.5) + set_parameter_value.(p, 0.5) POI.update_parameters!(backend(model)) end return @@ -395,7 +395,7 @@ function poi_add_sqf_parameters_parameters_obj_parameter_update(N::Int, M::Int) @objective(model, Min, p' * p) end for _ in 1:M - MOI.set.(model, POI.ParameterValue(), p, 0.5) + set_parameter_value.(p, 0.5) POI.update_parameters!(backend(model)) end return diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index c1f5f0c0..0e605104 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -1816,81 +1816,6 @@ function MOI.get(model::Optimizer, ::ListOfParameterIndices) return collect(keys(model.parameters))::Vector{ParameterIndex} end -""" - ParameterValue <: MOI.AbstractVariableAttribute - -Attribute defined to set and get parameter values - -# Example - -```julia -MOI.set(model, POI.ParameterValue(), p, 2.0) -MOI.get(model, POI.ParameterValue(), p) -``` -""" -struct ParameterValue <: MOI.AbstractVariableAttribute end - -# We need a CachingOptimizer fallback to -# get ParameterValue working correctly on JuMP -# TODO: Think of a better solution for this - -function MOI.set( - opt::MOI.Utilities.CachingOptimizer, - ::ParameterValue, - var::MOI.VariableIndex, - val::Float64, -) - ci = - MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{Float64}}(var.value) - set = MOI.set(opt, MOI.ConstraintSet(), ci, MOI.Parameter(val)) - return nothing -end - -function MOI.set( - model::Optimizer, - ::ParameterValue, - var::MOI.VariableIndex, - val::Float64, -) - ci = - MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{Float64}}(var.value) - set = MOI.set(model, MOI.ConstraintSet(), ci, MOI.Parameter(val)) - return nothing -end - -function MOI.set( - opt::MOI.Utilities.CachingOptimizer, - ::ParameterValue, - vi::MOI.VariableIndex, - val::Real, -) - return MOI.set(opt, ParameterValue(), vi, convert(Float64, val)) -end - -function MOI.set( - model::Optimizer, - ::ParameterValue, - vi::MOI.VariableIndex, - val::Real, -) - return MOI.set(model, ParameterValue(), vi, convert(Float64, val)) -end - -function MOI.get( - opt::MOI.Utilities.CachingOptimizer, - ::ParameterValue, - var::MOI.VariableIndex, -) - ci = - MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{Float64}}(var.value) - set = MOI.get(opt, MOI.ConstraintSet(), ci) - return set.value -end - -function MOI.get(model::Optimizer, ::ParameterValue, var::MOI.VariableIndex) - return model.parameters[p_idx(var)] -end - """ ConstraintsInterpretation <: MOI.AbstractOptimizerAttribute diff --git a/src/duals.jl b/src/duals.jl index e488dbe7..7f6e593f 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -82,17 +82,14 @@ function _compute_parameters_in_ci!( cons_dual * term.coefficient end for term in pf.pp - coef = ifelse(term.variable_1 == term.variable_2, T(1 // 2), T(1)) + mult = cons_dual * term.coefficient + if term.variable_1 == term.variable_2 + mult /= 2 + end model.dual_value_of_parameters[p_val(term.variable_1)] -= - coef * - cons_dual * - term.coefficient * - MOI.get(model, ParameterValue(), term.variable_2) + mult * model.parameters[p_idx(term.variable_2)] model.dual_value_of_parameters[p_val(term.variable_2)] -= - coef * - cons_dual * - term.coefficient * - MOI.get(model, ParameterValue(), term.variable_1) + mult * model.parameters[p_idx(term.variable_1)] end return end @@ -119,30 +116,6 @@ function _update_duals_from_objective!(model::Optimizer{T}, pf) where {T} return end -""" - ParameterDual <: MOI.AbstractVariableAttribute - -Attribute defined to get the dual values associated to parameters - -# Example - -```julia -MOI.get(model, POI.ParameterValue(), p) -``` -""" -struct ParameterDual <: MOI.AbstractVariableAttribute end - -MOI.is_set_by_optimize(::ParametricOptInterface.ParameterDual) = true - -function MOI.get( - model::Optimizer{T}, - ::ParameterDual, - v::MOI.VariableIndex, -) where {T} - ci = MOI.ConstraintIndex{MOI.VariableIndex,MOI.Parameter{T}}(v.value) - return MOI.get(model, MOI.ConstraintDual(), ci) -end - function MOI.get( model::Optimizer{T}, attr::MOI.ConstraintDual, @@ -187,22 +160,15 @@ function _compute_parameters_in_ci!( model.dual_value_of_parameters[p_val(term.scalar_term.variable)] -= cons_dual[term.output_index] * term.scalar_term.coefficient end - for term in pf.pp - coef = ifelse( - term.scalar_term.variable_1 == term.scalar_term.variable_2, - T(1 // 2), - T(1), - ) - model.dual_value_of_parameters[p_val(term.scalar_term.variable_1)] -= - coef * - cons_dual[term.output_index] * - term.scalar_term.coefficient * - MOI.get(model, ParameterValue(), term.scalar_term.variable_2) - model.dual_value_of_parameters[p_val(term.scalar_term.variable_2)] -= - coef * - cons_dual[term.output_index] * - term.scalar_term.coefficient * - MOI.get(model, ParameterValue(), term.scalar_term.variable_1) + for t in pf.pp + mult = cons_dual[t.output_index] * t.scalar_term.coefficient + if t.scalar_term.variable_1 == t.scalar_term.variable_2 + mult /= 2 + end + model.dual_value_of_parameters[p_val(t.scalar_term.variable_1)] -= + mult * model.parameters[p_idx(t.scalar_term.variable_2)] + model.dual_value_of_parameters[p_val(t.scalar_term.variable_2)] -= + mult * model.parameters[p_idx(t.scalar_term.variable_1)] end return end diff --git a/test/test_JuMP.jl b/test/test_JuMP.jl index 7a1e6c22..e4d04957 100644 --- a/test/test_JuMP.jl +++ b/test/test_JuMP.jl @@ -47,7 +47,7 @@ function test_jump_direct_affine_parameters() @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL) @test isapprox.(value(y), 0, atol = ATOL) # ===== Set parameter value ===== - MOI.set(model, POI.ParameterValue(), y, 2.0) + set_parameter_value(y, 2.0) optimize!(model) @test isapprox.(value(x[1]), 0.0, atol = ATOL) @test isapprox.(value(x[2]), 2.0, atol = ATOL) @@ -71,7 +71,7 @@ function test_jump_direct_parameter_times_variable() @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL) @test isapprox.(value(y), 0, atol = ATOL) # ===== Set parameter value ===== - MOI.set(model, POI.ParameterValue(), y, 2.0) + set_parameter_value(y, 2.0) optimize!(model) @test isapprox.(value(x[1]), 0.0, atol = ATOL) @test isapprox.(value(x[2]), 2.0, atol = ATOL) @@ -94,7 +94,7 @@ function test_jump_affine_parameters() @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL) @test isapprox.(value(y), 0, atol = ATOL) # ===== Set parameter value ===== - MOI.set(model, POI.ParameterValue(), y, 2.0) + set_parameter_value(y, 2.0) optimize!(model) @test isapprox.(value(x[1]), 0.0, atol = ATOL) @test isapprox.(value(x[2]), 2.0, atol = ATOL) @@ -109,7 +109,7 @@ function test_jump_parameter_times_variable() @variable(model, y in Parameter(0.0)) @variable(model, w in Parameter(0.0)) @variable(model, z in Parameter(0.0)) - @test MOI.get(model, POI.ParameterValue(), y) == 0 + @test parameter_value(y) == 0 @constraint(model, 2 * x[1] + x[2] + y <= 4) @constraint(model, (1 + y) * x[1] + 2 * x[2] + z <= 4) @objective(model, Max, 4 * x[1] + 3 * x[2] + w) @@ -118,7 +118,7 @@ function test_jump_parameter_times_variable() @test isapprox.(value(x[2]), 4.0 / 3.0, atol = ATOL) @test isapprox.(value(y), 0, atol = ATOL) # ===== Set parameter value ===== - MOI.set(model, POI.ParameterValue(), y, 2.0) + set_parameter_value(y, 2.0) optimize!(model) @test isapprox.(value(x[1]), 0.0, atol = ATOL) @test isapprox.(value(x[2]), 2.0, atol = ATOL) @@ -307,7 +307,7 @@ function test_jump_interpret_parameteric_bounds() @test Set(result) == Set(expected) @test length(result) == length(expected) @test objective_value(model) == -2 - MOI.set(model, POI.ParameterValue(), p[1], 4.0) + set_parameter_value(p[1], 4.0) optimize!(model) @test objective_value(model) == 3 return @@ -337,7 +337,7 @@ function test_jump_interpret_parameteric_bounds_expression() @test Set(result) == Set(expected) @test length(result) == length(expected) @test objective_value(model) == -4 - MOI.set(model, POI.ParameterValue(), p[1], 4.0) + set_parameter_value(p[1], 4.0) optimize!(model) @test objective_value(model) == 11.0 return @@ -365,7 +365,7 @@ function test_jump_direct_interpret_parameteric_bounds() @test Set(result) == Set(expected) @test length(result) == length(expected) @test objective_value(model) == -2 - MOI.set(model, POI.ParameterValue(), p[1], 4.0) + set_parameter_value(p[1], 4.0) optimize!(model) @test objective_value(model) == 3 return @@ -396,7 +396,7 @@ function test_jump_direct_interpret_parameteric_bounds_no_interpretation() @test Set(result) == Set(expected) @test length(result) == length(expected) @test objective_value(model) == -2 - MOI.set(model, POI.ParameterValue(), p[1], 4.0) + set_parameter_value(p[1], 4.0) optimize!(model) @test objective_value(model) == 3 return @@ -415,7 +415,7 @@ function test_jump_direct_interpret_parameteric_bounds_change() @objective(model, Min, sum(x)) optimize!(model) @test objective_value(model) == -1 - MOI.set(model, POI.ParameterValue(), p[1], 4.0) + set_parameter_value(p[1], 4.0) optimize!(model) @test objective_value(model) == 3.5 return @@ -432,7 +432,7 @@ function test_jump_direct_interpret_parameteric_bounds_both() @objective(model, Min, sum(x)) optimize!(model) @test objective_value(model) == -1 - MOI.set(model, POI.ParameterValue(), p[1], 4.0) + set_parameter_value(p[1], 4.0) optimize!(model) @test objective_value(model) == 3.5 return @@ -474,7 +474,7 @@ function test_jump_direct_get_parameter_value() @variable(model, z, set = MOI.Parameter(10.0)) c = @constraint(model, 19.0 * x - z + 22.0 * y <= 1.0) @objective(model, Min, x + y) - @test MOI.get(model, POI.ParameterValue(), z) == 10 + @test parameter_value(z) == 10 return end @@ -486,7 +486,7 @@ function test_jump_get_parameter_value() @variable(model, z, set = MOI.Parameter(10)) c = @constraint(model, 19.0 * x - z + 22.0 * y <= 1.0) @objective(model, Min, x + y) - @test MOI.get(model, POI.ParameterValue(), z) == 10 + @test parameter_value(z) == 10 return end @@ -499,7 +499,7 @@ function test_jump_sdp_scalar_parameter() @constraint(m, LinearAlgebra.Symmetric(x .- [1+p 0; 0 1+p]) in PSDCone()) optimize!(m) @test all(isapprox.(value.(x), [1 0; 0 1], atol = ATOL)) - MOI.set(m, POI.ParameterValue(), p, 1) + set_parameter_value(p, 1) optimize!(m) @test all(isapprox.(value.(x), [2 0; 0 2], atol = ATOL)) return @@ -516,7 +516,7 @@ function test_jump_sdp_matrix_parameter() optimize!(m) @test all(isapprox.(value.(x), P1, atol = ATOL)) P2 = [1 2; 2 1] - MOI.set.(m, POI.ParameterValue(), p, P2) + set_parameter_value.(p, P2) optimize!(m) @test all(isapprox.(value.(x), P2, atol = ATOL)) return @@ -531,14 +531,14 @@ function test_jump_dual_basic() @objective(model, Min, 5 * y[1]) optimize!(model) @test 5 / 3 ≈ dual(ctr1) atol = 1e-3 - @test [-35 / 3, 0.0] ≈ MOI.get.(model, POI.ParameterDual(), x) atol = 1e-3 + @test [-35 / 3, 0.0] ≈ dual.(ParameterRef.(x)) atol = 1e-3 @test [-26 / 3, 0.0, 0.0, 0.0, 0.0, 0.0] ≈ value.(y) atol = 1e-3 @test -130 / 3 ≈ objective_value(model) atol = 1e-3 return end function test_jump_dual_multiplicative_fail() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) + model = direct_model(POI.Optimizer(HiGHS.Optimizer)) set_silent(model) @variable(model, x) @variable(model, p in Parameter(1.0)) @@ -549,7 +549,19 @@ function test_jump_dual_multiplicative_fail() MOI.ConstraintDual(), "Cannot compute the dual of a multiplicative parameter", ) - @test_throws err MOI.get(model, POI.ParameterDual(), p) + @test_throws err dual(ParameterRef(p)) + return +end + +function test_jump_dual_multiplicative_get_fallback() + model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) + set_silent(model) + @variable(model, x) + @variable(model, p in Parameter(1.0)) + @constraint(model, cons, x * p >= 3) + @objective(model, Min, 2x) + optimize!(model) + @test isapprox(dual(ParameterRef(p)), -6; atol = 1e-4) return end @@ -561,7 +573,7 @@ function test_jump_dual_objective_min() @constraint(model, cons, x >= 3 * p) @objective(model, Min, 2x + p) optimize!(model) - @test MOI.get(model, POI.ParameterDual(), p) == 7 + @test dual(ParameterRef(p)) == 7 return end @@ -573,7 +585,7 @@ function test_jump_dual_objective_max() @constraint(model, cons, x >= 3 * p) @objective(model, Max, -2x + p) optimize!(model) - @test MOI.get(model, POI.ParameterDual(), p) == 5 + @test dual(ParameterRef(p)) == 5 return end @@ -606,7 +618,7 @@ function test_jump_dual_multiple_parameters_1() @test 0.0 ≈ dual(ctr7) atol = 1e-3 @test 0.0 ≈ dual(ctr8) atol = 1e-3 @test [0.0, 0.0, -35 / 3, 0.0, 0.0, 0.0] ≈ - MOI.get.(model, POI.ParameterDual(), x) atol = 1e-3 + dual.(ParameterRef.(x)) atol = 1e-3 @test [-26 / 3, 0.0, 0.0, 0.0, 0.0, 0.0] ≈ value.(y) atol = 1e-3 @test -130 / 3 ≈ objective_value(model) atol = 1e-3 return @@ -622,13 +634,13 @@ function test_jump_duals_LessThan() optimize!(model) @test value(x) == -1.0 @test dual(cref) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -1.0 + @test dual(ParameterRef(α)) == -1.0 - MOI.set(model, POI.ParameterValue(), α, 2.0) + set_parameter_value(α, 2.0) optimize!(model) @test value(x) == 2.0 @test dual(cref) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -1.0 + @test dual(ParameterRef(α)) == -1.0 return end @@ -642,12 +654,12 @@ function test_jump_duals_EqualTo() optimize!(model) @test value(x) == -1.0 @test dual(cref) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -1.0 - MOI.set(model, POI.ParameterValue(), α, 2.0) + @test dual(ParameterRef(α)) == -1.0 + set_parameter_value(α, 2.0) optimize!(model) @test value(x) == 2.0 @test dual(cref) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -1.0 + @test dual(ParameterRef(α)) == -1.0 return end @@ -655,19 +667,19 @@ function test_jump_duals_GreaterThan() model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) set_silent(model) @variable(model, α in Parameter(1.0)) - MOI.set(model, POI.ParameterValue(), α, -1.0) + set_parameter_value(α, -1.0) @variable(model, x) cref = @constraint(model, x >= α) @objective(model, Min, x) optimize!(model) @test value(x) == -1.0 @test dual(cref) == 1.0 - @test MOI.get(model, POI.ParameterDual(), α) == 1.0 - MOI.set(model, POI.ParameterValue(), α, 2.0) + @test dual(ParameterRef(α)) == 1.0 + set_parameter_value(α, 2.0) optimize!(model) @test value(x) == 2.0 @test dual(cref) == 1.0 - @test MOI.get(model, POI.ParameterDual(), α) == 1.0 + @test dual(ParameterRef(α)) == 1.0 return end @@ -681,7 +693,7 @@ function test_jump_dual_multiple_parameters_2() optimize!(model) @test value(x) == 20.0 @test dual(cref) == 1.0 - @test MOI.get(model, POI.ParameterDual(), α[3]) == 2.0 + @test dual(ParameterRef(α[3])) == 2.0 return end @@ -695,7 +707,7 @@ function test_jump_dual_mixing_params_and_vars_1() optimize!(model) @test value(x) == 2.0 @test dual(cref) == 1 / 5 - @test MOI.get(model, POI.ParameterDual(), α[3]) == 2 / 5 + @test dual(ParameterRef(α[3])) == 2 / 5 return end @@ -709,7 +721,7 @@ function test_jump_dual_mixing_params_and_vars_2() optimize!(model) @test value(x) == 2.0 @test dual(cref) == 1 / 5 - @test MOI.get(model, POI.ParameterDual(), α[3]) == 2 / 5 + @test dual(ParameterRef(α[3])) == 2 / 5 return end @@ -723,7 +735,7 @@ function test_jump_dual_mixing_params_and_vars_3() optimize!(model) @test value(x) == 4.0 @test dual(cref) == 1 / 5 - @test MOI.get(model, POI.ParameterDual(), α[3]) == 2 / 5 + @test dual(ParameterRef(α[3])) == 2 / 5 return end @@ -731,21 +743,21 @@ function test_jump_dual_add_after_solve() model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) set_silent(model) @variable(model, α in Parameter(1.0)) - MOI.set(model, POI.ParameterValue(), α, -1.0) + set_parameter_value(α, -1.0) @variable(model, x) cref = @constraint(model, x <= α) @objective(model, Max, x) optimize!(model) @test value(x) == -1.0 @test dual(cref) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -1.0 + @test dual(ParameterRef(α)) == -1.0 @variable(model, b in Parameter(-2.0)) cref = @constraint(model, x <= b) optimize!(model) @test value(x) == -2.0 @test dual(cref) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == 0.0 - @test MOI.get(model, POI.ParameterDual(), b) == -1.0 + @test dual(ParameterRef(α)) == 0.0 + @test dual(ParameterRef(b)) == -1.0 return end @@ -760,7 +772,7 @@ function test_jump_dual_add_ctr_alaternative() optimize!(model) @test value(x) == -1.0 @test dual(cref) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -1.0 + @test dual(ParameterRef(α)) == -1.0 return end @@ -778,12 +790,12 @@ function test_jump_dual_delete_constraint() @test value(x) == -1.0 @test dual(cref1) == 0.0 @test dual(cref2) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -1.0 + @test dual(ParameterRef(α)) == -1.0 delete(model, cref2) optimize!(model) @test value(x) == -0.5 @test dual(cref1) == -1.0 - @test MOI.get(model, POI.ParameterDual(), α) == -0.5 + @test dual(ParameterRef(α)) == -0.5 return end @@ -823,9 +835,9 @@ function test_jump_dual_delete_constraint_2() end @test dual(list[1]) == 1.0 if i in [7, 4] - @test MOI.get(model, POI.ParameterDual(), α) == 0.0 + @test dual(ParameterRef(α)) == 0.0 else - @test MOI.get(model, POI.ParameterDual(), α) == 1.0 * i + @test dual(ParameterRef(α)) == 1.0 * i end con = popfirst!(list) delete(model, con) @@ -872,9 +884,9 @@ function test_jump_dual_delete_constraint_3() end @test dual(list[1])[] ≈ 1.0 atol = 1e-5 if i in [7, 4] - @test MOI.get(model, POI.ParameterDual(), α) ≈ 0.0 atol = 1e-5 + @test dual(ParameterRef(α)) ≈ 0.0 atol = 1e-5 else - @test MOI.get(model, POI.ParameterDual(), α) ≈ 1.0 * i atol = 1e-5 + @test dual(ParameterRef(α)) ≈ 1.0 * i atol = 1e-5 end con = popfirst!(list) delete(model, con) @@ -914,7 +926,7 @@ function test_jump_direct_vector_parameter_affine_nonnegatives() optimize!(model) @test isapprox.(value(x), 4.0, atol = ATOL) @test isapprox.(value(y), 3.0, atol = ATOL) - MOI.set(model, POI.ParameterValue(), t, 6) + set_parameter_value(t, 6) optimize!(model) @test isapprox.(value(x), 5.0, atol = ATOL) @test isapprox.(value(y), 4.0, atol = ATOL) @@ -942,7 +954,7 @@ function test_jump_direct_vector_parameter_affine_nonpositives() optimize!(model) @test isapprox.(value(x), 4.0, atol = ATOL) @test isapprox.(value(y), 3.0, atol = ATOL) - MOI.set(model, POI.ParameterValue(), t, 6) + set_parameter_value(t, 6) optimize!(model) @test isapprox.(value(x), 5.0, atol = ATOL) @test isapprox.(value(y), 4.0, atol = ATOL) @@ -983,7 +995,7 @@ function test_jump_direct_soc_parameters() @test value(x) ≈ -1 / √2 atol = ATOL @test value(y) ≈ 1 / √2 atol = ATOL @test value(t) ≈ 1 atol = ATOL - MOI.set(model, POI.ParameterValue(), p, 1) + set_parameter_value(p, 1) optimize!(model) @test objective_value(model) ≈ 1 - 1 / √2 atol = ATOL @test value(x) ≈ 1 - 1 / √2 atol = ATOL @@ -1020,7 +1032,7 @@ function test_jump_direct_rsoc_constraints() @test value(x) ≈ 1 / 4 atol = ATOL @test value(y) ≈ 1 / √2 atol = ATOL @test value(t) ≈ 1 atol = ATOL - MOI.set(model, POI.ParameterValue(), p, 2) + set_parameter_value(p, 2) optimize!(model) @test objective_value(model) ≈ 0.0 atol = ATOL @test value(x) ≈ 0.0 atol = ATOL @@ -1040,11 +1052,11 @@ function test_jump_quadratic_interval() optimize!(model) @test value(x) ≈ 0 atol = ATOL @test value(y) ≈ 0.4 atol = ATOL - MOI.set(model, POI.ParameterValue(), p, 20.0) + set_parameter_value(p, 20.0) optimize!(model) @test value(x) ≈ 0 atol = ATOL @test value(y) ≈ 0.2 atol = ATOL - MOI.set(model, POI.ParameterValue(), q, 6.0) + set_parameter_value(q, 6.0) optimize!(model) @test value(x) ≈ 0 atol = ATOL @test value(y) ≈ 0.3 atol = ATOL @@ -1068,11 +1080,11 @@ function test_jump_quadratic_interval_cached() optimize!(model) @test value(x) ≈ 0 atol = ATOL @test value(y) ≈ 0.4 atol = ATOL - MOI.set(model, POI.ParameterValue(), p, 20.0) + set_parameter_value(p, 20.0) optimize!(model) @test value(x) ≈ 0 atol = ATOL @test value(y) ≈ 0.2 atol = ATOL - MOI.set(model, POI.ParameterValue(), q, 6.0) + set_parameter_value(q, 6.0) optimize!(model) @test value(x) ≈ 0 atol = ATOL @test value(y) ≈ 0.3 atol = ATOL @@ -1122,8 +1134,8 @@ function test_get_duals_from_multiplicative_parameters_1() @objective(model, Min, sum(x)) optimize!(model) @test dual(c) ≈ 1.0 / 3.0 - @test MOI.get.(model, POI.ParameterDual(), p1) ≈ 2.0 / 3 - @test MOI.get.(model, POI.ParameterDual(), p2) ≈ 2.0 / 3 + @test dual.(ParameterRef.(p1)) ≈ 2.0 / 3 + @test dual.(ParameterRef.(p2)) ≈ 2.0 / 3 return end @@ -1137,8 +1149,8 @@ function test_get_duals_from_multiplicative_parameters_2() @objective(model, Min, sum(x)) optimize!(model) @test dual(c) ≈ 1.0 / 3.0 - @test MOI.get.(model, POI.ParameterDual(), p1) ≈ 2.0 / 3 - @test MOI.get.(model, POI.ParameterDual(), p2) ≈ 40.0 / 3 + @test dual.(ParameterRef.(p1)) ≈ 2.0 / 3 + @test dual.(ParameterRef.(p2)) ≈ 40.0 / 3 return end @@ -1151,7 +1163,7 @@ function test_get_duals_from_multiplicative_parameters_3() @objective(model, Min, sum(x)) optimize!(model) @test dual(c) ≈ 1.0 / 3.0 - @test MOI.get.(model, POI.ParameterDual(), p) ≈ 2 * 4.0 / 3 + @test dual.(ParameterRef.(p)) ≈ 2 * 4.0 / 3 return end @@ -1163,7 +1175,7 @@ function test_parameters_cannot_be_nan_1() @constraint(model, c, 3 * x >= p * p) @objective(model, Min, sum(x)) @test_throws AssertionError optimize!(model) - MOI.set(model, POI.ParameterValue(), p, 20.0) + set_parameter_value(p, 20.0) return end @@ -1176,7 +1188,7 @@ function test_parameters_cannot_be_nan_2() @variable(model, p in Parameter(1.0)) @constraint(model, c, 3 * x[1] + x[2] >= p * p) @objective(model, Min, sum(x)) - @test_throws AssertionError MOI.set(model, POI.ParameterValue(), p, NaN) + @test_throws AssertionError set_parameter_value(p, NaN) return end @@ -1188,7 +1200,7 @@ function test_parameter_Cannot_be_inf_1() @constraint(model, c, 3 * x >= p * p) @objective(model, Min, sum(x)) @test_throws AssertionError optimize!(model) - MOI.set(model, POI.ParameterValue(), p, 20.0) + set_parameter_value(p, 20.0) return end @@ -1201,7 +1213,7 @@ function test_parameter_Cannot_be_inf_2() @variable(model, p in Parameter(1.0)) @constraint(model, c, 3 * x[1] + x[2] >= p * p) @objective(model, Min, sum(x)) - @test_throws AssertionError MOI.set(model, POI.ParameterValue(), p, Inf) + @test_throws AssertionError set_parameter_value(p, Inf) return end diff --git a/test/test_MathOptInterface.jl b/test/test_MathOptInterface.jl index 82fc5eae..dc1e2547 100644 --- a/test/test_MathOptInterface.jl +++ b/test/test_MathOptInterface.jl @@ -699,7 +699,7 @@ function test_vector_parameter_affine_nonnegatives() @test MOI.get(model, MOI.ObjectiveValue()) ≈ 7 atol = ATOL @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 7 atol = ATOL @test MOI.get(model, MOI.ConstraintDual(), cnn) ≈ [1.0, 1.0] atol = ATOL - MOI.set(model, POI.ParameterValue(), t, 6) + MOI.set(model, MOI.ConstraintSet(), ct, MOI.Parameter(6.0)) MOI.optimize!(model) @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 5 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 4 atol = ATOL @@ -750,7 +750,7 @@ function test_vector_parameter_affine_nonpositives() @test MOI.get(model, MOI.ObjectiveValue()) ≈ 7 atol = ATOL @test MOI.get(model, MOI.DualObjectiveValue()) ≈ 7 atol = ATOL @test MOI.get(model, MOI.ConstraintDual(), cnn) ≈ [-1.0, -1.0] atol = ATOL - MOI.set(model, POI.ParameterValue(), t, 6) + MOI.set(model, MOI.ConstraintSet(), ct, MOI.Parameter(6.0)) MOI.optimize!(model) @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 5 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 4 atol = ATOL @@ -829,7 +829,7 @@ function test_vector_soc_parameters() @test MOI.get(model, MOI.VariablePrimal(), x) ≈ -1 / √2 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), y) ≈ 1 / √2 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), t) ≈ 1 atol = ATOL - MOI.set(model, POI.ParameterValue(), p, 1) + MOI.set(model, MOI.ConstraintSet(), cp, MOI.Parameter(1.0)) MOI.optimize!(model) @test MOI.get(model, MOI.ObjectiveValue()) ≈ 1 - 1 / √2 atol = ATOL @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 - 1 / √2 atol = ATOL @@ -1459,13 +1459,16 @@ function test_qp_objective_parameter_times_parameter() MOI.set(optimizer, MOI.ConstraintSet(), cz, MOI.Parameter(3.0)) MOI.optimize!(optimizer) @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 6.0, atol = ATOL) - MOI.set(optimizer, POI.ParameterValue(), y, 5) - MOI.set(optimizer, POI.ParameterValue(), z, 5.0) - @test_throws MOI.InvalidIndex MOI.set( - optimizer, - POI.ParameterValue(), - MOI.VariableIndex(10872368175), - 5.0, + MOI.set(optimizer, MOI.ConstraintSet(), cy, MOI.Parameter(5.0)) + MOI.set(optimizer, MOI.ConstraintSet(), cz, MOI.Parameter(5.0)) + @test_throws( + MOI.InvalidIndex, + MOI.set( + optimizer, + MOI.ConstraintSet(), + typeof(cy)(cy.value + 10), + MOI.Parameter(5.0), + ) ) MOI.optimize!(optimizer) @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 25.0, atol = ATOL) @@ -1994,7 +1997,7 @@ function test_psd_cone_with_parameter() model = POI.Optimizer(SCS.Optimizer; with_bridge_type = Float64) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) - p = first.(MOI.add_constrained_variable.(model, MOI.Parameter(1.0))) + p, cp = MOI.add_constrained_variable(model, MOI.Parameter(1.0)) # Set objective: minimize x obj_func = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0) @@ -2018,7 +2021,7 @@ function test_psd_cone_with_parameter() @test MOI.get(model, MOI.TerminationStatus()) == MOI.OPTIMAL @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1.0 atol = 1e-5 - MOI.set(model, POI.ParameterValue(), p, 3.0) + MOI.set(model, MOI.ConstraintSet(), cp, MOI.Parameter(3.0)) MOI.optimize!(model) @test MOI.get(model, MOI.VariablePrimal(), x) ≈ 1 / 3 atol = 1e-5 From e481a75c4032ddb7fcacceee023e8f284d72b3a3 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 18 Feb 2026 15:03:26 +1300 Subject: [PATCH 2/4] Update --- test/test_JuMP.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_JuMP.jl b/test/test_JuMP.jl index e4d04957..4c9daf8c 100644 --- a/test/test_JuMP.jl +++ b/test/test_JuMP.jl @@ -617,8 +617,8 @@ function test_jump_dual_multiple_parameters_1() dual(ctr6) atol = 1e-3 @test 0.0 ≈ dual(ctr7) atol = 1e-3 @test 0.0 ≈ dual(ctr8) atol = 1e-3 - @test [0.0, 0.0, -35 / 3, 0.0, 0.0, 0.0] ≈ - dual.(ParameterRef.(x)) atol = 1e-3 + @test [0.0, 0.0, -35 / 3, 0.0, 0.0, 0.0] ≈ dual.(ParameterRef.(x)) atol = + 1e-3 @test [-26 / 3, 0.0, 0.0, 0.0, 0.0, 0.0] ≈ value.(y) atol = 1e-3 @test -130 / 3 ≈ objective_value(model) atol = 1e-3 return From 418e447eed9da02b1120d8b15b90b8ab849c9a17 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 18 Feb 2026 15:17:36 +1300 Subject: [PATCH 3/4] Update --- src/duals.jl | 3 +-- test/test_JuMP.jl | 19 +++---------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/duals.jl b/src/duals.jl index 7f6e593f..3bf8af47 100644 --- a/src/duals.jl +++ b/src/duals.jl @@ -125,8 +125,7 @@ function MOI.get( msg = "$attr not available when evaluate_duals is set to false. Create an optimizer such as `POI.Optimizer(HiGHS.Optimizer; evaluate_duals = true)` to enable this feature." throw(MOI.GetAttributeNotAllowed(attr, msg)) elseif !_is_additive(model, cp) - msg = "Cannot compute the dual of a multiplicative parameter" - throw(MOI.GetAttributeNotAllowed(attr, msg)) + error("Cannot compute the dual of a multiplicative parameter") end return model.dual_value_of_parameters[p_val(cp)] end diff --git a/test/test_JuMP.jl b/test/test_JuMP.jl index 4c9daf8c..abfb182c 100644 --- a/test/test_JuMP.jl +++ b/test/test_JuMP.jl @@ -545,23 +545,10 @@ function test_jump_dual_multiplicative_fail() @constraint(model, cons, x * p >= 3) @objective(model, Min, 2x) optimize!(model) - err = MOI.GetAttributeNotAllowed( - MOI.ConstraintDual(), - "Cannot compute the dual of a multiplicative parameter", + @test_throws( + ErrorException("Cannot compute the dual of a multiplicative parameter"), + dual(ParameterRef(p)), ) - @test_throws err dual(ParameterRef(p)) - return -end - -function test_jump_dual_multiplicative_get_fallback() - model = Model(() -> POI.Optimizer(HiGHS.Optimizer)) - set_silent(model) - @variable(model, x) - @variable(model, p in Parameter(1.0)) - @constraint(model, cons, x * p >= 3) - @objective(model, Min, 2x) - optimize!(model) - @test isapprox(dual(ParameterRef(p)), -6; atol = 1e-4) return end From 993f0bab1b5e44c200e78ed1170c48df1f909f29 Mon Sep 17 00:00:00 2001 From: Oscar Dowson Date: Wed, 18 Feb 2026 15:47:28 +1300 Subject: [PATCH 4/4] Update --- test/test_MathOptInterface.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_MathOptInterface.jl b/test/test_MathOptInterface.jl index dc1e2547..e9e9ce65 100644 --- a/test/test_MathOptInterface.jl +++ b/test/test_MathOptInterface.jl @@ -2275,7 +2275,7 @@ function test_multiplicative_dual_error() f = 1.0 * x * p ci = MOI.add_constraint(model, f, MOI.EqualTo{Float64}(0.0)) @test_throws( - MOI.GetAttributeNotAllowed, + ErrorException("Cannot compute the dual of a multiplicative parameter"), MOI.get(model, MOI.ConstraintDual(), pc), ) return