diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 6d9e520c..70f98805 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -247,12 +247,13 @@ function MOI.get(model::Optimizer, tp::Type{MOI.VariableIndex}, attr::String) return MOI.get(model.optimizer, tp, attr) end -function MOI.add_variable(model::Optimizer) +function _add_variable(model::Optimizer, inner_vi) _next_variable_index!(model) - return MOI.Utilities.CleverDicts.add_item( - model.variables, - MOI.add_variable(model.optimizer), - ) + return MOI.Utilities.CleverDicts.add_item(model.variables, inner_vi) +end + +function MOI.add_variable(model::Optimizer) + return _add_variable(model, MOI.add_variable(model.optimizer)) end function MOI.supports_add_constrained_variable( @@ -269,6 +270,20 @@ function MOI.supports_add_constrained_variables( return MOI.supports_add_constrained_variables(model.optimizer, MOI.Reals) end +function MOI.supports_add_constrained_variable( + model::Optimizer, + ::Type{S}, +) where {S<:MOI.AbstractScalarSet} + return MOI.supports_add_constrained_variable(model.optimizer, S) +end + +function MOI.supports_add_constrained_variables( + model::Optimizer, + ::Type{S}, +) where {S<:MOI.AbstractVectorSet} + return MOI.supports_add_constrained_variables(model.optimizer, S) +end + function _assert_parameter_is_finite(set::MOI.Parameter{T}) where {T} if !isfinite(set.value) throw( @@ -296,6 +311,27 @@ function MOI.add_constrained_variable( return p, cp end +function MOI.add_constrained_variable( + model::Optimizer, + set::MOI.AbstractScalarSet, +) + inner_vi, inner_ci = MOI.add_constrained_variable(model.optimizer, set) + outer_vi = _add_variable(model, inner_vi) + outer_ci = + MOI.ConstraintIndex{MOI.VariableIndex,typeof(set)}(outer_vi.value) + model.constraint_outer_to_inner[outer_ci] = inner_ci + return outer_vi, outer_ci +end + +function MOI.add_constrained_variables( + model::Optimizer, + set::MOI.AbstractVectorSet, +) + inner_vis, inner_ci = MOI.add_constrained_variables(model.optimizer, set) + _add_to_constraint_map!(model, inner_ci) + return _add_variable.(model, inner_vis), inner_ci +end + function _add_to_constraint_map!(model::Optimizer, ci) model.constraint_outer_to_inner[ci] = ci return diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index c1ba6942..32710300 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -180,12 +180,11 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer # extension data ext::Dict{Symbol,Any} - function Optimizer( + function Optimizer{T}( optimizer::OT; evaluate_duals::Bool = true, save_original_objective_and_constraints::Bool = true, - ) where {OT} - T = Float64 + ) where {T,OT} return new{T,OT}( optimizer, MOI.Utilities.CleverDicts.CleverDict{ParameterIndex,T}( @@ -248,6 +247,8 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer end end +Optimizer(args...; kws...) = Optimizer{Float64}(args...; kws...) + function _next_variable_index!(model::Optimizer) return model.last_variable_index_added += 1 end diff --git a/test/moi_tests.jl b/test/moi_tests.jl index 524f9914..0993bbe2 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -2097,7 +2097,7 @@ function test_psd_cone_with_parameter() model = POI.Optimizer(cached) MOI.set(model, MOI.Silent(), true) x = MOI.add_variable(model) - p = first.(MOI.add_constrained_variable.(model, MOI.Parameter(1.0)),) + p = first.(MOI.add_constrained_variable.(model, MOI.Parameter(1.0))) # Set objective: minimize x obj_func = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(1.0, x)], 0.0) @@ -2142,3 +2142,63 @@ function test_copy_model() MOI.optimize!(poi) @test MOI.get(poi, MOI.VariablePrimal(), x) ≈ 1.0 end + +function test_constrained_variable_glpk() + optimizer = POI.Optimizer(GLPK.Optimizer()) + MOI.set(optimizer, MOI.Silent(), true) + set = MOI.LessThan(1.0) + @test MOI.supports_add_constrained_variable(optimizer, typeof(set)) + x, c = MOI.add_constrained_variable(optimizer, set) + @test x.value == c.value + @test c isa MOI.ConstraintIndex{typeof(x),typeof(set)} + @test MOI.get(optimizer, MOI.ConstraintFunction(), c) ≈ x + @test MOI.get(optimizer, MOI.ConstraintSet(), c) == set + @test MOI.supports(optimizer, MOI.VariableName(), typeof(x)) + MOI.set(optimizer, MOI.VariableName(), x, "vname") + @test MOI.get(optimizer, MOI.VariableName(), x) == "vname" + MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) + obj_func = 1.0 * x + MOI.set(optimizer, MOI.ObjectiveFunction{typeof(obj_func)}(), obj_func) + MOI.optimize!(optimizer) + @test MOI.get(optimizer, MOI.ConstraintDual(), c) ≈ -1 atol = ATOL + @test MOI.get(optimizer, MOI.VariablePrimal(), x) ≈ 1 atol = ATOL + return +end + +include("no_free_model.jl") + +function test_constrained_variable_no_free() + optimizer = POI.Optimizer(NoFreeVariablesModel{Float64}()) + set = MOI.LessThan(1.0) + @test MOI.supports_add_constrained_variable(optimizer, typeof(set)) + x, c = MOI.add_constrained_variable(optimizer, set) + @test c isa MOI.ConstraintIndex{typeof(x),typeof(set)} + @test x.value == c.value + @test MOI.get(optimizer, MOI.ConstraintFunction(), c) ≈ x + @test MOI.get(optimizer, MOI.ConstraintSet(), c) == set + @test MOI.supports(optimizer, MOI.VariableName(), typeof(x)) + MOI.set(optimizer, MOI.VariableName(), x, "vname") + @test MOI.get(optimizer, MOI.VariableName(), x) == "vname" + MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MAX_SENSE) + obj_func = 1.0 * x + MOI.set(optimizer, MOI.ObjectiveFunction{typeof(obj_func)}(), obj_func) + return +end + +function test_constrained_variables() + optimizer = POI.Optimizer{Int}(NoFreeVariablesModel{Int}()) + set = MOI.Nonnegatives(2) + @test MOI.supports_add_constrained_variables(optimizer, typeof(set)) + x, c = MOI.add_constrained_variables(optimizer, set) + @test c isa MOI.ConstraintIndex{MOI.VectorOfVariables,typeof(set)} + @test MOI.get(optimizer, MOI.ConstraintFunction(), c) ≈ + MOI.VectorOfVariables(x) + @test MOI.get(optimizer, MOI.ConstraintSet(), c) == set + @test MOI.supports(optimizer, MOI.VariableName(), eltype(x)) + MOI.set(optimizer, MOI.VariableName(), x[1], "vname") + @test MOI.get(optimizer, MOI.VariableName(), x[1]) == "vname" + MOI.set(optimizer, MOI.ObjectiveSense(), MOI.MIN_SENSE) + obj_func = 1 * x[1] + 1 * x[2] + MOI.set(optimizer, MOI.ObjectiveFunction{typeof(obj_func)}(), obj_func) + return +end diff --git a/test/no_free_model.jl b/test/no_free_model.jl new file mode 100644 index 00000000..c172a222 --- /dev/null +++ b/test/no_free_model.jl @@ -0,0 +1,40 @@ +MOI.Utilities.@model( + NoFreeVariablesModel, + (), + (), + (MOI.Nonnegatives,), + (), + (), + (), + (MOI.VectorOfVariables,), + (), +) + +function MOI.supports_constraint( + ::NoFreeVariablesModel, + ::Type{MOI.VectorOfVariables}, + ::Type{MOI.Reals}, +) + return false +end + +function MOI.supports_add_constrained_variable( + ::NoFreeVariablesModel{T}, + ::Type{MOI.LessThan{T}}, +) where {T} + return true +end + +function MOI.supports_add_constrained_variables( + ::NoFreeVariablesModel, + ::Type{MOI.Nonnegatives}, +) + return true +end + +function MOI.supports_add_constrained_variables( + ::NoFreeVariablesModel, + ::Type{MOI.Reals}, +) + return false +end