diff --git a/src/optimizer_interface.jl b/src/optimizer_interface.jl index 18fc1816b23..a2c8df63f75 100644 --- a/src/optimizer_interface.jl +++ b/src/optimizer_interface.jl @@ -808,6 +808,8 @@ end model::GenericModel; allow_local::Bool = true, allow_almost::Bool = false, + additional_termination_statuses::Vector{TerminationStatusCode} = + TerminationStatusCode[], dual::Bool = false, result::Int = 1, ) @@ -817,30 +819,49 @@ Return `true` if: * the [`termination_status`](@ref) is one of: * [`OPTIMAL`](@ref) (the solver found a global optimum) * [`LOCALLY_SOLVED`](@ref) (the solver found a local optimum, which may also - be the global optimum, but the solver could not prove so). + be the global optimum, but the solver could not prove so) * the [`primal_status`](@ref) of the result index `result` is `FEASIBLE_POINT`. This function is conservative, in that it returns `false` for situations like the solver terminating with a feasible solution due to a time limit. +If this function returns `false`, use [`termination_status`](@ref), +[`result_count`](@ref), [`primal_status`](@ref) and [`dual_status`](@ref) to +understand what solutions are available (if any). + See also: [`assert_is_solved_and_feasible`](@ref). ## Keyword arguments +### `allow_local` + If `allow_local = false`, then this function returns `true` only if the [`termination_status`](@ref) is [`OPTIMAL`](@ref). +### `allow_almost` + If `allow_almost = true`, then the [`termination_status`](@ref) may additionally be [`ALMOST_OPTIMAL`](@ref) or [`ALMOST_LOCALLY_SOLVED`](@ref) (if `allow_local`), and the [`primal_status`](@ref) and [`dual_status`](@ref) may additionally be [`NEARLY_FEASIBLE_POINT`](@ref). -If `dual`, additionally use [`dual_status`](@ref) to check that a dual feasible -point is available. +### `additional_termination_statuses` -If this function returns `false`, use [`termination_status`](@ref), -[`result_count`](@ref), [`primal_status`](@ref) and [`dual_status`](@ref) to -understand what solutions are available (if any). +If `additional_termination_statuses` is passed, then the [`termination_status`](@ref) +may additionally be one of the values. This argument should be used to allow +limit statuses such as [`TIME_LIMIT`](@ref) to return `true` instead of the +default `false`. + +### `dual` + +If `dual`, additionally check that an optimal dual solution is available via +[`dual_status`](@ref). The `allow_` keywords control both the primal and dual +solutions. + +### `result` + +The index of the result to query. This value is passed to the `result` keyword +arguments of [`primal_status`](@ref) and [`dual_status`](@ref) ## Example @@ -849,7 +870,12 @@ julia> import Ipopt julia> model = Model(Ipopt.Optimizer); -julia> is_solved_and_feasible(model) +julia> is_solved_and_feasible( + model; + allow_almost = true, + additional_termination_statuses = [TIME_LIMIT], + dual = true, + ) false ``` """ @@ -858,6 +884,7 @@ function is_solved_and_feasible( dual::Bool = false, allow_local::Bool = true, allow_almost::Bool = false, + additional_termination_statuses::Vector{MOI.TerminationStatusCode} = MOI.TerminationStatusCode[], result::Int = 1, ) status = termination_status(model) @@ -865,7 +892,8 @@ function is_solved_and_feasible( (status == OPTIMAL) || (allow_local && (status == LOCALLY_SOLVED)) || (allow_almost && (status == ALMOST_OPTIMAL)) || - (allow_almost && allow_local && (status == ALMOST_LOCALLY_SOLVED)) + (allow_almost && allow_local && (status == ALMOST_LOCALLY_SOLVED)) || + (status in additional_termination_statuses) if ret primal = primal_status(model; result) ret &= diff --git a/test/test_model.jl b/test/test_model.jl index 74e5d7046cd..08a3f2d394a 100644 --- a/test/test_model.jl +++ b/test/test_model.jl @@ -1254,6 +1254,7 @@ function test_is_solved_and_feasible() MOI.ALMOST_OPTIMAL, MOI.ALMOST_LOCALLY_SOLVED, MOI.TIME_LIMIT, + MOI.OTHER_ERROR, ] _global = term == MOI.OPTIMAL has_local = _global || (term == MOI.LOCALLY_SOLVED) @@ -1272,6 +1273,10 @@ function test_is_solved_and_feasible() MOI.set(mock, MOI.PrimalStatus(), primal) MOI.set(mock, MOI.DualStatus(), dual) @test is_solved_and_feasible(model) == (has_local && _primal) + @test is_solved_and_feasible( + model; + additional_termination_statuses = [TIME_LIMIT], + ) == (has_local && _primal) || (term == MOI.TIME_LIMIT) @test is_solved_and_feasible(model; dual = true) == (has_local && _primal && _dual) @test is_solved_and_feasible(model; allow_local = false) ==