diff --git a/src/MOI_wrapper.jl b/src/MOI_wrapper.jl index 853fa86..fab81b3 100644 --- a/src/MOI_wrapper.jl +++ b/src/MOI_wrapper.jl @@ -99,7 +99,8 @@ function MOI.is_empty(model::Optimizer) # isempty(model.multiplicative_parameters) && isempty(model.dual_value_of_parameters) && - model.number_of_parameters_in_model == 0 + model.number_of_parameters_in_model == 0 && + isempty(model.ext) end function MOI.empty!(model::Optimizer{T}) where {T} @@ -133,6 +134,7 @@ function MOI.empty!(model::Optimizer{T}) where {T} empty!(model.dual_value_of_parameters) # model.number_of_parameters_in_model = 0 + empty!(model.ext) return end @@ -1124,24 +1126,161 @@ end # Special Attributes # +""" + ParametricObjectiveType <: MOI.AbstractModelAttribute + +A model attribute for the type `P` of the ParametricOptInterface's parametric +function type in the objective function. The value os `P` can be `Nothing` if +the objective function is not parametric. The parametric function type can be +queried using the [`ParametricObjectiveFunction{P}`](@ref) attribute. The type +`P` can be `ParametricAffineFunction{T}` or `ParametricQuadraticFunction{T}`. +""" +struct ParametricObjectiveType <: MOI.AbstractModelAttribute end + +function MOI.get(model::Optimizer{T}, ::ParametricObjectiveType) where {T} + if model.quadratic_objective_cache !== nothing + return ParametricQuadraticFunction{T} + elseif model.affine_objective_cache !== nothing + return ParametricAffineFunction{T} + end + return Nothing +end + +""" + ParametricObjectiveFunction{P} <: MOI.AbstractModelAttribute + +A model attribute for the parametric objective function of type `P`. The type +`P` can be `ParametricAffineFunction{T}` or `ParametricQuadraticFunction{T}`. +""" +struct ParametricObjectiveFunction{T} <: MOI.AbstractModelAttribute end + +function MOI.get( + model::Optimizer{T}, + ::ParametricObjectiveFunction{ParametricQuadraticFunction{T}}, +) where {T} + if model.quadratic_objective_cache === nothing + error(" + There is no parametric quadratic objective function in the model. + ") + end + return model.quadratic_objective_cache +end + +function MOI.get( + model::Optimizer{T}, + ::ParametricObjectiveFunction{ParametricAffineFunction{T}}, +) where {T} + if model.affine_objective_cache === nothing + error(" + There is no parametric affine objective function in the model. + ") + end + return model.affine_objective_cache +end + +""" + ListOfParametricConstraintTypesPresent() + +A model attribute for the list of tuples of the form `(F,S,P)`, where `F` is a +MOI function type, `S` is a set type and `P` is a ParametricOptInterface +parametric function type indicating that the attribute +[`DictOfParametricConstraintIndicesAndFunctions{F,S,P}`](@ref) returns a +non-empty dictionary. +""" +struct ListOfParametricConstraintTypesPresent <: MOI.AbstractModelAttribute end + +function MOI.get( + model::Optimizer{T}, + ::ListOfParametricConstraintTypesPresent, +) where {T} + output = Set{Tuple{DataType,DataType,DataType}}() + for (F, S) in MOI.Utilities.DoubleDicts.nonempty_outer_keys( + model.affine_constraint_cache, + ) + push!(output, (F, S, ParametricAffineFunction{T})) + end + for (F, S) in MOI.Utilities.DoubleDicts.nonempty_outer_keys( + model.vector_affine_constraint_cache, + ) + push!(output, (F, S, ParametricVectorAffineFunction{T})) + end + for (F, S) in MOI.Utilities.DoubleDicts.nonempty_outer_keys( + model.quadratic_constraint_cache, + ) + push!(output, (F, S, ParametricQuadraticFunction{T})) + end + return collect(output) +end + +""" + DictOfParametricConstraintIndicesAndFunctions{F,S,P} + +A model attribute for a dictionary mapping constraint indices to parametric +functions. The key is a constraint index with scalar function type `F` +and set type `S` and the value is a parametric function of type `P`. +""" +struct DictOfParametricConstraintIndicesAndFunctions{F,S,P} <: + MOI.AbstractModelAttribute end + +function MOI.get( + model::Optimizer, + ::DictOfParametricConstraintIndicesAndFunctions{F,S,P}, +) where {F,S,P<:ParametricAffineFunction} + return model.affine_constraint_cache[F, S] +end + +function MOI.get( + model::Optimizer, + ::DictOfParametricConstraintIndicesAndFunctions{F,S,P}, +) where {F,S,P<:ParametricVectorAffineFunction} + return model.vector_affine_constraint_cache[F, S] +end + +function MOI.get( + model::Optimizer, + ::DictOfParametricConstraintIndicesAndFunctions{F,S,P}, +) where {F,S,P<:ParametricQuadraticFunction} + return model.quadratic_constraint_cache[F, S] +end + +""" + NumberOfPureVariables + +A model attribute for the number of pure variables in the model. +""" struct NumberOfPureVariables <: MOI.AbstractModelAttribute end function MOI.get(model::Optimizer, ::NumberOfPureVariables) return length(model.variables) end +""" + ListOfPureVariableIndices + +A model attribute for the list of pure variable indices in the model. +""" struct ListOfPureVariableIndices <: MOI.AbstractModelAttribute end function MOI.get(model::Optimizer, ::ListOfPureVariableIndices) return collect(keys(model.variables))::Vector{MOI.VariableIndex} end +""" + NumberOfParameters + +A model attribute for the number of parameters in the model. +""" struct NumberOfParameters <: MOI.AbstractModelAttribute end function MOI.get(model::Optimizer, ::NumberOfParameters) return length(model.parameters) end +""" + ListOfParameterIndices + +A model attribute for the list of parameter indices in the model. +""" struct ListOfParameterIndices <: MOI.AbstractModelAttribute end function MOI.get(model::Optimizer, ::ListOfParameterIndices) diff --git a/src/ParametricOptInterface.jl b/src/ParametricOptInterface.jl index 632e672..1b94685 100644 --- a/src/ParametricOptInterface.jl +++ b/src/ParametricOptInterface.jl @@ -166,6 +166,9 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer number_of_parameters_in_model::Int64 constraints_interpretation::ConstraintsInterpretationCode save_original_objective_and_constraints::Bool + + # extension data + ext::Dict{Symbol,Any} function Optimizer( optimizer::OT; evaluate_duals::Bool = true, @@ -223,6 +226,7 @@ mutable struct Optimizer{T,OT<:MOI.ModelLike} <: MOI.AbstractOptimizer 0, ONLY_CONSTRAINTS, save_original_objective_and_constraints, + Dict{Symbol,Any}(), ) end end diff --git a/src/parametric_functions.jl b/src/parametric_functions.jl index d5236d6..ce4e4ce 100644 --- a/src/parametric_functions.jl +++ b/src/parametric_functions.jl @@ -83,6 +83,26 @@ function ParametricQuadraticFunction( ) end +function affine_parameter_terms(f::ParametricQuadraticFunction) + return f.p +end + +function affine_variable_terms(f::ParametricQuadraticFunction) + return f.v +end + +function quadratic_parameter_variable_terms(f::ParametricQuadraticFunction) + return f.pv +end + +function quadratic_parameter_parameter_terms(f::ParametricQuadraticFunction) + return f.pp +end + +function quadratic_variable_variable_terms(f::ParametricQuadraticFunction) + return f.vv +end + function _split_quadratic_terms( terms::Vector{MOI.ScalarQuadraticTerm{T}}, ) where {T} @@ -145,8 +165,12 @@ end function _original_function(f::ParametricQuadraticFunction{T}) where {T} return MOI.ScalarQuadraticFunction{T}( - vcat(f.pv, f.pp, f.vv), - vcat(f.p, f.v), + vcat( + quadratic_parameter_variable_terms(f), + quadratic_parameter_parameter_terms(f), + quadratic_variable_variable_terms(f), + ), + vcat(affine_parameter_terms(f), affine_variable_terms(f)), f.c, ) end @@ -169,13 +193,16 @@ function _parametric_constant( ) where {T} # do not add set_function here param_constant = f.c - for term in f.p + for term in affine_parameter_terms(f) param_constant += term.coefficient * model.parameters[p_idx(term.variable)] end - for term in f.pp + for term in quadratic_parameter_parameter_terms(f) param_constant += - term.coefficient * + ( + term.coefficient / + ifelse(term.variable_1 == term.variable_2, 2, 1) + ) * model.parameters[p_idx(term.variable_1)] * model.parameters[p_idx(term.variable_2)] end @@ -187,7 +214,7 @@ function _delta_parametric_constant( f::ParametricQuadraticFunction{T}, ) where {T} delta_constant = zero(T) - for term in f.p + for term in affine_parameter_terms(f) p = p_idx(term.variable) if !isnan(model.updated_parameters[p]) delta_constant += @@ -195,7 +222,7 @@ function _delta_parametric_constant( (model.updated_parameters[p] - model.parameters[p]) end end - for term in f.pp + for term in quadratic_parameter_parameter_terms(f) p1 = p_idx(term.variable_1) p2 = p_idx(term.variable_2) isnan_1 = isnan(model.updated_parameters[p1]) @@ -212,7 +239,10 @@ function _delta_parametric_constant( model.updated_parameters[p2], ) delta_constant += - term.coefficient * + ( + term.coefficient / + ifelse(term.variable_1 == term.variable_2, 2, 1) + ) * (new_1 * new_2 - model.parameters[p1] * model.parameters[p2]) end end @@ -224,9 +254,9 @@ function _parametric_affine_terms( f::ParametricQuadraticFunction{T}, ) where {T} param_terms_dict = Dict{MOI.VariableIndex,T}() - sizehint!(param_terms_dict, length(f.pv)) + sizehint!(param_terms_dict, length(quadratic_parameter_variable_terms(f))) # remember a variable may appear more than once in pv - for term in f.pv + for term in quadratic_parameter_variable_terms(f) base = get(param_terms_dict, term.variable_2, zero(T)) param_terms_dict[term.variable_2] = base + term.coefficient * model.parameters[p_idx(term.variable_1)] @@ -243,9 +273,9 @@ function _delta_parametric_affine_terms( f::ParametricQuadraticFunction{T}, ) where {T} delta_terms_dict = Dict{MOI.VariableIndex,T}() - sizehint!(delta_terms_dict, length(f.pv)) + sizehint!(delta_terms_dict, length(quadratic_parameter_variable_terms(f))) # remember a variable may appear more than once in pv - for term in f.pv + for term in quadratic_parameter_variable_terms(f) p = p_idx(term.variable_1) if !isnan(model.updated_parameters[p]) base = get(delta_terms_dict, term.variable_2, zero(T)) @@ -296,6 +326,14 @@ function ParametricAffineFunction( ) end +function affine_parameter_terms(f::ParametricAffineFunction) + return f.p +end + +function affine_variable_terms(f::ParametricAffineFunction) + return f.v +end + function _split_affine_terms(terms::Vector{MOI.ScalarAffineTerm{T}}) where {T} num_v, num_p = _count_scalar_affine_terms_types(terms) v = Vector{MOI.ScalarAffineTerm{T}}(undef, num_v) @@ -330,17 +368,23 @@ function _count_scalar_affine_terms_types( end function _original_function(f::ParametricAffineFunction{T}) where {T} - return MOI.ScalarAffineFunction{T}(vcat(f.p, f.v), f.c) + return MOI.ScalarAffineFunction{T}( + vcat(affine_parameter_terms(f), affine_variable_terms(f)), + f.c, + ) end function _current_function(f::ParametricAffineFunction{T}) where {T} - return MOI.ScalarAffineFunction{T}(f.v, f.current_constant) + return MOI.ScalarAffineFunction{T}( + affine_variable_terms(f), + f.current_constant, + ) end function _parametric_constant(model, f::ParametricAffineFunction{T}) where {T} # do not add set_function here param_constant = f.c - for term in f.p + for term in affine_parameter_terms(f) param_constant += term.coefficient * model.parameters[p_idx(term.variable)] end @@ -352,7 +396,7 @@ function _delta_parametric_constant( f::ParametricAffineFunction{T}, ) where {T} delta_constant = zero(T) - for term in f.p + for term in affine_parameter_terms(f) p = p_idx(term.variable) if !isnan(model.updated_parameters[p]) delta_constant += @@ -394,6 +438,14 @@ function ParametricVectorAffineFunction( ) end +function vector_affine_parameter_terms(f::ParametricVectorAffineFunction) + return f.p +end + +function vector_affine_variable_terms(f::ParametricVectorAffineFunction) + return f.v +end + function _split_vector_affine_terms( terms::Vector{MOI.VectorAffineTerm{T}}, ) where {T} @@ -430,11 +482,17 @@ function _count_vector_affine_terms_types( end function _original_function(f::ParametricVectorAffineFunction{T}) where {T} - return MOI.VectorAffineFunction{T}(vcat(f.p, f.v), f.c) + return MOI.VectorAffineFunction{T}( + vcat(vector_affine_parameter_terms(f), vector_affine_variable_terms(f)), + f.c, + ) end function _current_function(f::ParametricVectorAffineFunction{T}) where {T} - return MOI.VectorAffineFunction{T}(f.v, f.current_constant) + return MOI.VectorAffineFunction{T}( + vector_affine_variable_terms(f), + f.current_constant, + ) end function _parametric_constant( @@ -443,7 +501,7 @@ function _parametric_constant( ) where {T} # do not add set_function here param_constant = copy(f.c) - for term in f.p + for term in vector_affine_parameter_terms(f) param_constant[term.output_index] += term.scalar_term.coefficient * model.parameters[p_idx(term.scalar_term.variable)] @@ -456,7 +514,7 @@ function _delta_parametric_constant( f::ParametricVectorAffineFunction{T}, ) where {T} delta_constant = zeros(T, length(f.c)) - for term in f.p + for term in vector_affine_parameter_terms(f) p = p_idx(term.scalar_term.variable) if !isnan(model.updated_parameters[p]) delta_constant[term.output_index] += diff --git a/test/moi_tests.jl b/test/moi_tests.jl index 29d40bd..ed8b5f2 100644 --- a/test/moi_tests.jl +++ b/test/moi_tests.jl @@ -28,6 +28,11 @@ function test_basic_tests() for x_i in x MOI.add_constraint(optimizer, x_i, MOI.GreaterThan(0.0)) end + @test MOI.is_valid(optimizer, x[1]) + @test MOI.is_valid(optimizer, y) + @test !MOI.is_valid(optimizer, z) + @test MOI.is_valid(optimizer, cy) + @test !MOI.is_valid(optimizer, cz) @test_throws ErrorException("Cannot constrain a parameter") MOI.add_constraint( optimizer, y, @@ -99,6 +104,7 @@ function test_basic_tests() @test MOI.supports(optimizer, MOI.VariableName(), MOI.VariableIndex) @test MOI.get(optimizer, MOI.ObjectiveSense()) == MOI.MIN_SENSE @test MOI.get(optimizer, MOI.VariableName(), x[1]) == "" + @test MOI.get(optimizer, MOI.VariableName(), y) == "" # ??? @test MOI.get(optimizer, MOI.ConstraintName(), c1) == "" MOI.set(optimizer, MOI.ConstraintName(), c1, "ctr123") @test MOI.get(optimizer, MOI.ConstraintName(), c1) == "ctr123" @@ -1146,8 +1152,13 @@ function test_qp_parameter_times_variable() @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x[1]), 0.5, atol = ATOL) @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 29.25, atol = ATOL) MOI.set(optimizer, MOI.ConstraintSet(), cy, MOI.Parameter(2.0)) + # this implies: x1 + x2 + x1^2 + x1*y + y^2 <= 30 + # becomes: 2 * x1 + x2 + x1^2 <= 30 - 4 = 26 + # then x1 = 0 and x2 = 26 and obj = 26 + # is x1 = eps >= 0, x2 = 26 - 2 * eps - eps^2 and + # obj = 26 - 2 * eps - eps^2 + 2 * eps = 26 - eps ^2 (eps == 0 to maximize) MOI.optimize!(optimizer) - @test ≈(MOI.get(optimizer, MOI.ObjectiveValue()), 22.0, atol = ATOL) + @test ≈(MOI.get(optimizer, MOI.ObjectiveValue()), 26.0, atol = ATOL) return end @@ -1190,7 +1201,13 @@ function test_qp_variable_times_parameter() @test ≈(MOI.get(optimizer, MOI.VariablePrimal(), x[2]), 29.25, atol = ATOL) MOI.set(optimizer, MOI.ConstraintSet(), cy, MOI.Parameter(2.0)) MOI.optimize!(optimizer) - @test ≈(MOI.get(optimizer, MOI.ObjectiveValue()), 22.0, atol = ATOL) + # this implies: x1 + x2 + x1^2 + x1*y + y^2 <= 30 + # becomes: 2 * x1 + x2 + x1^2 <= 30 - 4 = 26 + # then x1 = 0 and x2 = 26 and obj = 26 + # is x1 = eps >= 0, x2 = 26 - 2 * eps - eps^2 and + # obj = 26 - 2 * eps - eps^2 + 2 * eps = 26 - eps ^2 (eps == 0 to maximize) + MOI.optimize!(optimizer) + @test ≈(MOI.get(optimizer, MOI.ObjectiveValue()), 26.0, atol = ATOL) return end @@ -1204,12 +1221,17 @@ function test_qp_parameter_times_parameter() a = [1.0, 1.0] c = [2.0, 1.0] x = MOI.add_variables(optimizer, 2) + # x1 >= 0, x2 >= 0 for x_i in x MOI.add_constraint(optimizer, x_i, MOI.GreaterThan(0.0)) end + # x1 <= 20 MOI.add_constraint(optimizer, x[1], MOI.LessThan(20.0)) + # y == 0 y, cy = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) + # z == 0 z, cz = MOI.add_constrained_variable(optimizer, MOI.Parameter(0.0)) + # x1 + x2 + y^2 + yz + z^2 <= 30 quad_terms = MOI.ScalarQuadraticTerm{Float64}[] push!(quad_terms, MOI.ScalarQuadraticTerm(A[1, 1], y, y)) push!(quad_terms, MOI.ScalarQuadraticTerm(A[1, 2], y, z)) @@ -1220,6 +1242,7 @@ function test_qp_parameter_times_parameter() 0.0, ) MOI.add_constraint(optimizer, constraint_function, MOI.LessThan(30.0)) + # max 2x1 + x2 obj_func = MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(c, [x[1], x[2]]), 0.0) MOI.set( @@ -1240,16 +1263,28 @@ function test_qp_parameter_times_parameter() 10.0, atol = ATOL, ) + # now x1 + x2 + y^2 + yz + z^2 <= 30 + # implies x1 + x2 <= 26 MOI.set(optimizer, MOI.ConstraintSet(), cy, MOI.Parameter(2.0)) MOI.optimize!(optimizer) - @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 42.0, atol = ATOL) + # hence x1 = 20, x2 = 6 + # and obj = 46 + @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 46.0, atol = ATOL) MOI.set(optimizer, MOI.ConstraintSet(), cz, MOI.Parameter(1.0)) + # now x1 + x2 + y^2 + yz + z^2 <= 30 + # implies x1 + x2 <= 30 - 4 - 2 - 1 = 23 + # hence x1 = 20, x2 = 3 + # and obj = 43 MOI.optimize!(optimizer) - @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 36.0, atol = ATOL) + @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 43.0, atol = ATOL) MOI.set(optimizer, MOI.ConstraintSet(), cy, MOI.Parameter(-1.0)) MOI.set(optimizer, MOI.ConstraintSet(), cz, MOI.Parameter(-1.0)) + # now x1 + x2 + y^2 + yz + z^2 <= 30 + # implies x1 + x2 <= 30 -1 -1 -1 = 27 + # hence x1 = 20, x2 = 7 + # and obj = 47 MOI.optimize!(optimizer) - @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 45.0, atol = ATOL) + @test isapprox(MOI.get(optimizer, MOI.ObjectiveValue()), 47.0, atol = ATOL) return end @@ -1676,3 +1711,191 @@ function test_duals_without_parameters() @test ≈(MOI.get(optimizer, MOI.ConstraintDual(), c3), -3.0, atol = ATOL) return end + +function test_getters() + cached = MOI.Utilities.CachingOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + SCS.Optimizer(), + ) + optimizer = POI.Optimizer(cached) + MOI.set(optimizer, MOI.Silent(), true) + x = MOI.add_variable(optimizer) + @test isempty( + MOI.get(optimizer, POI.ListOfParametricConstraintTypesPresent()), + ) + p, cp = MOI.add_constrained_variable(optimizer, MOI.Parameter(1.0)) + @test isempty( + MOI.get(optimizer, POI.ListOfParametricConstraintTypesPresent()), + ) + MOI.add_constraint(optimizer, 1.0x + 2.0p + 0.0, MOI.GreaterThan(0.0)) + T1 = ( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + POI.ParametricAffineFunction{Float64}, + ) + T2 = ( + MOI.ScalarAffineFunction{Float64}, + MOI.LessThan{Float64}, + POI.ParametricQuadraticFunction{Float64}, + ) + T3 = ( + MOI.VectorAffineFunction{Float64}, + MOI.Zeros, + POI.ParametricVectorAffineFunction{Float64}, + ) + @test MOI.get(optimizer, POI.ListOfParametricConstraintTypesPresent()) == + [T1] + @test length( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T1[1], + T1[2], + T1[3], + }(), + ), + ) == 1 + @test isempty( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T2[1], + T2[2], + T2[3], + }(), + ), + ) + @test isempty( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T3[1], + T3[2], + T3[3], + }(), + ), + ) + MOI.add_constraint(optimizer, 2.0x * p + 2.0p, MOI.LessThan(0.0)) + @test MOI.get(optimizer, POI.ListOfParametricConstraintTypesPresent()) == + [T1, T2] || + MOI.get(optimizer, POI.ListOfParametricConstraintTypesPresent()) == + [T2, T1] + @test length( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T1[1], + T1[2], + T1[3], + }(), + ), + ) == 1 + @test length( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T2[1], + T2[2], + T2[3], + }(), + ), + ) == 1 + @test isempty( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T3[1], + T3[2], + T3[3], + }(), + ), + ) + terms = [ + MOI.VectorAffineTerm(Int64(1), MOI.ScalarAffineTerm(2.0, x)), + MOI.VectorAffineTerm(Int64(2), MOI.ScalarAffineTerm(3.0, p)), + ] + f = MOI.VectorAffineFunction(terms, [4.0, 5.0]) + MOI.add_constraint(optimizer, f, MOI.Zeros(2)) + @test length( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T1[1], + T1[2], + T1[3], + }(), + ), + ) == 1 + @test length( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T2[1], + T2[2], + T2[3], + }(), + ), + ) == 1 + @test length( + MOI.get( + optimizer, + POI.DictOfParametricConstraintIndicesAndFunctions{ + T3[1], + T3[2], + T3[3], + }(), + ), + ) == 1 + @test MOI.get(optimizer, POI.ParametricObjectiveType()) == Nothing + MOI.set( + optimizer, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + 3.0x, + ) + @test MOI.get(optimizer, POI.ParametricObjectiveType()) == Nothing + @test_throws ErrorException MOI.get( + optimizer, + POI.ParametricObjectiveFunction{POI.ParametricAffineFunction{Float64}}(), + ) + @test_throws ErrorException MOI.get( + optimizer, + POI.ParametricObjectiveFunction{ + POI.ParametricQuadraticFunction{Float64}, + }(), + ) + MOI.set( + optimizer, + MOI.ObjectiveFunction{MOI.ScalarAffineFunction{Float64}}(), + 3.0x + 4.0p, + ) + @test MOI.get( + optimizer, + POI.ParametricObjectiveFunction{POI.ParametricAffineFunction{Float64}}(), + ) isa POI.ParametricAffineFunction{Float64} + @test_throws ErrorException MOI.get( + optimizer, + POI.ParametricObjectiveFunction{ + POI.ParametricQuadraticFunction{Float64}, + }(), + ) + @test MOI.get(optimizer, POI.ParametricObjectiveType()) == + POI.ParametricAffineFunction{Float64} + MOI.set( + optimizer, + MOI.ObjectiveFunction{MOI.ScalarQuadraticFunction{Float64}}(), + 3.0x * p + 4.0p, + ) + @test MOI.get(optimizer, POI.ParametricObjectiveType()) == + POI.ParametricQuadraticFunction{Float64} + @test_throws ErrorException MOI.get( + optimizer, + POI.ParametricObjectiveFunction{POI.ParametricAffineFunction{Float64}}(), + ) + @test MOI.get( + optimizer, + POI.ParametricObjectiveFunction{ + POI.ParametricQuadraticFunction{Float64}, + }(), + ) isa POI.ParametricQuadraticFunction{Float64} + return +end