diff --git a/src/Test/test_solve.jl b/src/Test/test_solve.jl index d948297408..ae846ed52e 100644 --- a/src/Test/test_solve.jl +++ b/src/Test/test_solve.jl @@ -913,3 +913,536 @@ function setup_test( ) return end + +""" + test_solve_conflict_bound_bound(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when two variable bounds are in the conflict. +""" +function test_solve_conflict_bound_bound( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x = MOI.add_variable(model) + c1 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.GreaterThan(T(2))) + c2 = MOI.add_constraint(model, MOI.SingleVariable(x), MOI.LessThan(one(T))) + @test MOI.get(model, MOI.ConflictStatus()) == + MOI.COMPUTE_CONFLICT_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT + return +end + +function setup_test( + ::typeof(test_solve_conflict_bound_bound), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION; + constraint_conflict_status = [ + (MOI.SingleVariable, MOI.LessThan{Float64}) => + [MOI.IN_CONFLICT], + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => + [MOI.IN_CONFLICT], + ], + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND) + end, + ) + return +end + +""" + test_solve_conflict_two_affine(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when two affine constraints are in the conflict. +""" +function test_solve_conflict_two_affine( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x = MOI.add_variable(model) + c1 = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([one(T)], [x]), zero(T)), + MOI.GreaterThan(T(2)), + ) + c2 = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.([one(T)], [x]), zero(T)), + MOI.LessThan(T(1)), + ) + @test MOI.get(model, MOI.ConflictStatus()) == + MOI.COMPUTE_CONFLICT_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT + return +end + +function setup_test( + ::typeof(test_solve_conflict_two_affine), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION; + constraint_conflict_status = [ + ( + MOI.ScalarAffineFunction{Float64}, + MOI.LessThan{Float64}, + ) => [MOI.IN_CONFLICT], + ( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + ) => [MOI.IN_CONFLICT], + ], + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND) + end, + ) + return +end + +""" + test_solve_conflict_invalid_interval(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when an interval bound has upper < lower. +""" +function test_solve_conflict_invalid_interval( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x = MOI.add_variable(model) + c1 = MOI.add_constraint( + model, + MOI.SingleVariable(x), + MOI.Interval(one(T), zero(T)), + ) + @test MOI.get(model, MOI.ConflictStatus()) == + MOI.COMPUTE_CONFLICT_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT + return +end + +function setup_test( + ::typeof(test_solve_conflict_invalid_interval), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION; + constraint_conflict_status = [ + (MOI.SingleVariable, MOI.Interval{Float64}) => + [MOI.IN_CONFLICT], + ], + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND) + end, + ) + return +end + +""" + test_solve_conflict_affine_affine(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when two constraints are in the conflict. +""" +function test_solve_conflict_affine_affine( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x = MOI.add_variable(model) + y = MOI.add_variable(model) + b1 = MOI.add_constraint( + model, + MOI.SingleVariable(x), + MOI.GreaterThan(zero(T)), + ) + b2 = MOI.add_constraint( + model, + MOI.SingleVariable(y), + MOI.GreaterThan(zero(T)), + ) + cf1 = + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), [x, y]), zero(T)) + c1 = MOI.add_constraint(model, cf1, MOI.LessThan(-one(T))) + cf2 = MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([one(T), -one(T)], [x, y]), + zero(T), + ) + c2 = MOI.add_constraint(model, cf2, MOI.GreaterThan(one(T))) + @test MOI.get(model, MOI.ConflictStatus()) == + MOI.COMPUTE_CONFLICT_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + @test MOI.get(model, MOI.ConstraintConflictStatus(), b1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), b2) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == + MOI.NOT_IN_CONFLICT + return +end + +function setup_test( + ::typeof(test_solve_conflict_affine_affine), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION; + constraint_conflict_status = [ + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => + [MOI.IN_CONFLICT, MOI.IN_CONFLICT], + ( + MOI.ScalarAffineFunction{Float64}, + MOI.LessThan{Float64}, + ) => [MOI.IN_CONFLICT], + ( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + ) => [MOI.NOT_IN_CONFLICT], + ], + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND) + end, + ) + return +end + +""" + test_solve_conflict_EqualTo(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when some constraints are EqualTo. +""" +function test_solve_conflict_EqualTo( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x = MOI.add_variable(model) + y = MOI.add_variable(model) + b1 = MOI.add_constraint( + model, + MOI.SingleVariable(x), + MOI.GreaterThan(zero(T)), + ) + b2 = MOI.add_constraint( + model, + MOI.SingleVariable(y), + MOI.GreaterThan(zero(T)), + ) + cf1 = + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), [x, y]), zero(T)) + c1 = MOI.add_constraint(model, cf1, MOI.EqualTo(-one(T))) + cf2 = MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([one(T), -one(T)], [x, y]), + zero(T), + ) + c2 = MOI.add_constraint(model, cf2, MOI.GreaterThan(one(T))) + @test MOI.get(model, MOI.ConflictStatus()) == + MOI.COMPUTE_CONFLICT_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + @test MOI.get(model, MOI.ConstraintConflictStatus(), b1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), b2) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == + MOI.NOT_IN_CONFLICT + return +end + +function setup_test( + ::typeof(test_solve_conflict_EqualTo), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION; + constraint_conflict_status = [ + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => + [MOI.IN_CONFLICT, MOI.IN_CONFLICT], + (MOI.ScalarAffineFunction{Float64}, MOI.EqualTo{Float64}) => [MOI.IN_CONFLICT], + ( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + ) => [MOI.NOT_IN_CONFLICT], + ], + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND) + end, + ) + return +end + +""" + test_solve_conflict_NOT_IN_CONFLICT(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when some constraints are not in the conlict. +""" +function test_solve_conflict_NOT_IN_CONFLICT( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x = MOI.add_variable(model) + y = MOI.add_variable(model) + z = MOI.add_variable(model) + S = MOI.GreaterThan(zero(T)) + b1 = MOI.add_constraint(model, MOI.SingleVariable(x), S) + b2 = MOI.add_constraint(model, MOI.SingleVariable(y), S) + b3 = MOI.add_constraint(model, MOI.SingleVariable(z), S) + cf1 = + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), [x, y]), zero(T)) + c1 = MOI.add_constraint(model, cf1, MOI.LessThan(-one(T))) + cf2 = MOI.ScalarAffineFunction( + MOI.ScalarAffineTerm.([one(T), -one(T), one(T)], [x, y, z]), + zero(T), + ) + c2 = MOI.add_constraint(model, cf2, MOI.GreaterThan(one(T))) + @test MOI.get(model, MOI.ConflictStatus()) == + MOI.COMPUTE_CONFLICT_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + @test MOI.get(model, MOI.ConstraintConflictStatus(), b1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), b2) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), b3) == + MOI.NOT_IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c1) == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == + MOI.NOT_IN_CONFLICT + return +end + +function setup_test( + ::typeof(test_solve_conflict_NOT_IN_CONFLICT), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION; + constraint_conflict_status = [ + (MOI.SingleVariable, MOI.GreaterThan{Float64}) => [ + MOI.IN_CONFLICT, + MOI.IN_CONFLICT, + MOI.NOT_IN_CONFLICT, + ], + ( + MOI.ScalarAffineFunction{Float64}, + MOI.LessThan{Float64}, + ) => [MOI.IN_CONFLICT], + ( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + ) => [MOI.NOT_IN_CONFLICT], + ], + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND) + end, + ) + return +end + +""" + test_solve_conflict_feasible(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when the problem is feasible. +""" +function test_solve_conflict_feasible( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x = MOI.add_variable(model) + f = MOI.ScalarAffineFunction([MOI.ScalarAffineTerm(one(T), x)], zero(T)) + _ = MOI.add_constraint(model, f, MOI.GreaterThan(one(T))) + _ = MOI.add_constraint(model, f, MOI.LessThan(T(2))) + @test MOI.get(model, MOI.ConflictStatus()) == + MOI.COMPUTE_CONFLICT_NOT_CALLED + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == config.optimal_status + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.NO_CONFLICT_EXISTS + return +end + +function setup_test( + ::typeof(test_solve_conflict_feasible), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.OPTIMAL, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION, + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.NO_CONFLICT_EXISTS) + end, + ) + return +end + +""" + test_solve_conflict_zeroone(model::MOI.ModelLike, config::Config) + +Test the ConflictStatus API when an integrality is in the conflict. +""" +function test_solve_conflict_zeroone( + model::MOI.ModelLike, + config::Config{T}, +) where {T} + @requires _supports(config, MOI.compute_conflict!) + @requires _supports(config, MOI.optimize!) + try + MOI.get(model, MOI.ConflictStatus()) + catch + return # If this fails, skip the test. + end + x, c1 = MOI.add_constrained_variable(model, MOI.ZeroOne()) + c2 = MOI.add_constraint( + model, + MOI.ScalarAffineFunction(MOI.ScalarAffineTerm.(one(T), [x]), zero(T)), + MOI.GreaterThan(T(2)), + ) + MOI.optimize!(model) + @test MOI.get(model, MOI.TerminationStatus()) == MOI.INFEASIBLE + MOI.compute_conflict!(model) + @test MOI.get(model, MOI.ConflictStatus()) == MOI.CONFLICT_FOUND + zeroone_conflict = MOI.get(model, MOI.ConstraintConflictStatus(), c1) + @test zeroone_conflict == MOI.MAYBE_IN_CONFLICT || + zeroone_conflict == MOI.IN_CONFLICT + @test MOI.get(model, MOI.ConstraintConflictStatus(), c2) == MOI.IN_CONFLICT + return +end + +function setup_test( + ::typeof(test_solve_conflict_zeroone), + model::MOIU.MockOptimizer, + ::Config, +) + MOIU.set_mock_optimize!( + model, + mock::MOIU.MockOptimizer -> begin + MOIU.mock_optimize!( + mock, + MOI.INFEASIBLE, + MOI.NO_SOLUTION, + MOI.NO_SOLUTION; + constraint_conflict_status = [ + (MOI.SingleVariable, MOI.ZeroOne) => + [MOI.MAYBE_IN_CONFLICT], + ( + MOI.ScalarAffineFunction{Float64}, + MOI.GreaterThan{Float64}, + ) => [MOI.IN_CONFLICT], + ], + ) + MOI.set(mock, MOI.ConflictStatus(), MOI.CONFLICT_FOUND) + end, + ) + return +end diff --git a/src/Utilities/mockoptimizer.jl b/src/Utilities/mockoptimizer.jl index b0fb34a730..91520a5bcb 100644 --- a/src/Utilities/mockoptimizer.jl +++ b/src/Utilities/mockoptimizer.jl @@ -57,6 +57,7 @@ mutable struct MockOptimizer{MT<:MOI.ModelLike} <: MOI.AbstractOptimizer dual_status::Dict{Int,MOI.ResultStatusCode} constraint_dual::Dict{MOI.ConstraintIndex,Dict{Int,Any}} # Constraint conflicts + compute_conflict_called::Bool conflict_status::MOI.ConflictStatusCode constraint_conflict_status::Dict{ MOI.ConstraintIndex, @@ -114,6 +115,7 @@ function MockOptimizer( Dict{Int,MOI.ResultStatusCode}(), Dict{MOI.ConstraintIndex,Dict{Int,Any}}(), # + false, MOI.COMPUTE_CONFLICT_NOT_CALLED, Dict{MOI.ConstraintIndex,MOI.ConflictParticipationStatusCode}(), # Basis status @@ -199,7 +201,10 @@ function MOI.optimize!(mock::MockOptimizer) return end -MOI.compute_conflict!(::MockOptimizer) = nothing +function MOI.compute_conflict!(model::MockOptimizer) + model.compute_conflict_called = true + return +end function throw_mock_unsupported_names(attr) return throw( @@ -292,7 +297,12 @@ function MOI.set( return end -MOI.get(mock::MockOptimizer, ::MOI.ConflictStatus) = mock.conflict_status +function MOI.get(mock::MockOptimizer, ::MOI.ConflictStatus) + if mock.compute_conflict_called + return mock.conflict_status + end + return MOI.COMPUTE_CONFLICT_NOT_CALLED +end function MOI.set(mock::MockOptimizer, ::MockModelAttribute, value::Integer) mock.mock_model_attribute = value @@ -429,7 +439,7 @@ function MOI.set( idx::MOI.ConstraintIndex, value, ) - mock.constraint_conflict_status[idx] = value + mock.constraint_conflict_status[xor_index(idx)] = value return end @@ -699,7 +709,7 @@ function MOI.get( idx::MOI.ConstraintIndex, ) MOI.throw_if_not_valid(mock, idx) - return mock.constraint_conflict_status[idx] + return mock.constraint_conflict_status[xor_index(idx)] end function _safe_set_result( @@ -745,6 +755,9 @@ function MOI.empty!(mock::MockOptimizer) empty!(mock.mock_constraint_attribute) mock.optimize_called = false mock.termination_status = MOI.OPTIMIZE_NOT_CALLED + mock.compute_conflict_called = false + mock.conflict_status = MOI.COMPUTE_CONFLICT_NOT_CALLED + empty!(mock.constraint_conflict_status) empty!(mock.objective_value) empty!(mock.dual_objective_value) empty!(mock.primal_status) @@ -970,6 +983,7 @@ end constraint_duals::Pair{Tuple{DataTypeDataType},<:Vector}...; constraint_basis_status = Pair{Tuple{DataTypeDataType},<:Vector}[], variable_basis_status = MOI.BasisStatusCode[], + constraint_conflict_status = Pair{Tuple{Type,Type},<:Vector}[], ) Fake the result of a call to `optimize!` in the mock optimizer by storing the @@ -1001,6 +1015,10 @@ solution. * `variable_basis_status`: a vector of `MOI.BasisStatusCode`, corresponding to the `MOI.VariableBasisStatus` attribute of the variables in the order returned by `MOI.ListOfVariableIndices`. + + * `constraint_conflict_status`: a vector of pairs similar to + `constraint_duals`, except this time for the `MOI.ConstraintConflictStatus` + attribute. """ function mock_optimize!( mock::MockOptimizer, @@ -1013,6 +1031,7 @@ function mock_optimize!( dual_status_constraint_duals...; constraint_basis_status = [], variable_basis_status = MOI.BasisStatusCode[], + constraint_conflict_status = [], var_basis = nothing, con_basis = nothing, ) @@ -1046,6 +1065,18 @@ function mock_optimize!( ) end end + for con_conflict_pair in constraint_conflict_status + F, S = con_conflict_pair.first + indices = MOI.get(mock, MOI.ListOfConstraintIndices{F,S}()) + for (i, ci) in enumerate(indices) + MOI.set( + mock, + MOI.ConstraintConflictStatus(), + ci, + con_conflict_pair.second[i], + ) + end + end if length(variable_basis_status) > 0 variables = MOI.get(mock, MOI.ListOfVariableIndices()) @assert length(variable_basis_status) == length(variables) diff --git a/test/Utilities/cachingoptimizer.jl b/test/Utilities/cachingoptimizer.jl index 5ebc304f8d..284196fb7f 100644 --- a/test/Utilities/cachingoptimizer.jl +++ b/test/Utilities/cachingoptimizer.jl @@ -121,6 +121,41 @@ function test_MOI_Test() return end +# !!! warning +# This is some type piracy! To enable CachingOptimizer to pass some MOI.Test +# functions with MockOptimizer, we overload `setup_test` to setup the inner +# mock optimizer. +# +# This is pretty fragile! It requires the inner optimizer to be attached, +# amongst other things. It's used by `test_compute_conflict` below, but not +# by test_MOI_Test above (test_MOI_Test has `exclude = Any[MOI.optimize!]`). +function MOI.Test.setup_test( + f::Any, + model::MOI.Utilities.CachingOptimizer{ + MOI.Utilities.MockOptimizer{ + MOI.Utilities.UniversalFallback{MOI.Utilities.Model{Float64}}, + }, + MOI.Utilities.Model{Float64}, + }, + config::MOI.Test.Config, +) + MOI.Test.setup_test(f, model.optimizer, config) + return +end + +function test_compute_conflict() + mock = MOI.Utilities.MockOptimizer( + MOI.Utilities.UniversalFallback(MOI.Utilities.Model{Float64}()), + ) + model = MOI.Utilities.CachingOptimizer(MOI.Utilities.Model{Float64}(), mock) + MOI.Test.runtests( + model, + MOI.Test.Config(), + include = ["test_solve_conflict"], + ) + return +end + """ Without an optimizer attached (i.e., `MOI.state(model) == NO_OPTIMIZER`) we need throw nice errors for attributes that are based on the optimizer. For