@@ -1924,3 +1924,164 @@ function MOI.optimize!(model::Optimizer)
19241924
19251925 return nothing
19261926end
1927+
1928+ function MOI. get (model:: Optimizer , :: MOI.RawStatusString ):: String
1929+ if ! isnothing (model) && ! isnothing (model. solve_result)
1930+ return string (model. solve_result. termination. reason)
1931+ end
1932+
1933+ return " "
1934+ end
1935+
1936+ """
1937+ Additional, typically solver-specific information about termination.
1938+ """
1939+ struct ExtraTerminationDetailString <: MOI.AbstractOptimizerAttribute end
1940+ MOI. attribute_value_type (:: ExtraTerminationDetailString ) = String
1941+
1942+ function MOI. get (model:: Optimizer , :: ExtraTerminationDetailString ):: String
1943+ if ! isnothing (model) && ! isnothing (model. solve_result)
1944+ return model. solve_result. termination. detail
1945+ end
1946+
1947+ return " "
1948+ end
1949+
1950+ function MOI. get (model:: Optimizer , :: MOI.TerminationStatus ):: MOI.TerminationStatusCode
1951+ if isnothing (model) || isnothing (model. solve_result)
1952+ return MOI. OPTIMIZE_NOT_CALLED
1953+ end
1954+
1955+ if model. solve_result. termination. reason ==
1956+ TerminationReasonProto. TERMINATION_REASON_OPTIMAL
1957+ # It is expected that the LimitProto is LIMIT_UNSPECIFIED when the termination reason is OPTIMAL.
1958+ return MOI. OPTIMAL
1959+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_ITERATION
1960+ return MOI. ITERATION_LIMIT
1961+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_TIME
1962+ return MOI. TIME_LIMIT
1963+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_NODE
1964+ return MOI. NODE_LIMIT
1965+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_SOLUTION
1966+ return MOI. SOLUTION_LIMIT
1967+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_MEMORY
1968+ return MOI. MEMORY_LIMIT
1969+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_OBJECTIVE
1970+ return MOI. OBJECTIVE_LIMIT
1971+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_NORM
1972+ return MOI. NORM_LIMIT
1973+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_INTERRUPTED
1974+ return MOI. INTERRUPTED
1975+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_SLOW_PROGRESS
1976+ return MOI. SLOW_PROGRESS
1977+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_OTHER
1978+ return MOI. OTHER_LIMIT
1979+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_UNDETERMINED
1980+ # TODO : b/411325865 Follow up on support for LIMIT_UNDETERMINED in MOI.jl
1981+ # A fallback as there's currently no associated MOI.LIMIT_* that can represent this.
1982+ @info " The underlying solver does not expose which limit was reached and the actual limit is LIMIT_UNDETERMINED " \
1983+ " However, LIMIT_UNDETERMINED is not associated with a MOI.LIMIT_* hence the returned LIMIT is MOI.OTHER_LIMIT."
1984+ return MOI. OTHER_LIMIT
1985+ elseif model. solve_result. termination. limit == LimitProto. LIMIT_CUTOFF
1986+ # TODO : b/411328356 Follow up on support for LIMIT_CUTOFF in MOI.jl
1987+ # A fallback as there's currently no associated MOI.LIMIT_* that can represent this.
1988+ @info " The solver was run with a cutoff on the objective, indicating that the user did not want any solution " \
1989+ " worse than the cutoff, and the solver concluded there were no solutions at least as good as the cutoff. " \
1990+ " Typically no further solution information is provided. The actual limit is LIMIT_CUTOFF. " \
1991+ " However, LIMIT_CUTOFF is not associated with a MOI.LIMIT_* hence the returned LIMIT is MOI.OTHER_LIMIT."
1992+ return MOI. OTHER_LIMIT
1993+ else
1994+ # TODO : b/411328207 Add attribute to capture more information about the limit when LIMIT_UNSPECIFIED is the returned limit.
1995+ # The else bit falls back to MOI.LIMIT_UNSPECIFIED if the termination reason wasn't TERMINATION_REASON_OPTIMAL
1996+ @info " The solver terminated but not from a limit and the actual limit is LIMIT_UNSPECIFIED, which is used as a null. " \
1997+ " However, LIMIT_UNSPECIFIED is not associated with a MOI.LIMIT_* hence the returned LIMIT is MOI.OTHER_LIMIT."
1998+ return MOI. OTHER_LIMIT
1999+ end
2000+ end
2001+
2002+ function MOI. get (model:: Optimizer , attr:: MOI.PrimalStatus )
2003+ if isnothing (model) || isnothing (model. solve_result)
2004+ return MOI. NO_SOLUTION
2005+ end
2006+
2007+ if attr. result_index != 1
2008+ return MOI. NO_SOLUTION
2009+ elseif model. solve_result. termination. problem_status. primal_status ==
2010+ FeasibilityStatusProto. FEASIBILITY_STATUS_UNDETERMINED
2011+ return MOI. UNKNOWN_RESULT_STATUS
2012+ elseif model. solve_result. termination. problem_status. primal_status ==
2013+ FeasibilityStatusProto. FEASIBILITY_STATUS_FEASIBLE
2014+ return MOI. FEASIBLE_POINT
2015+ elseif model. solve_result. termination. problem_status. primal_status ==
2016+ FeasibilityStatusProto. FEASIBILITY_STATUS_INFEASIBLE
2017+ return MOI. INFEASIBLE_POINT
2018+ else
2019+ # For FEASIBILITY_STATUS_UNSPECIFIED which is a guard value representing no status
2020+ return MOI. NO_SOLUTION
2021+ end
2022+ end
2023+
2024+ function MOI. get (model:: Optimizer , attr:: MOI.DualStatus )
2025+ if isnothing (model) || isnothing (model. solve_result)
2026+ return MOI. NO_SOLUTION
2027+ end
2028+
2029+ if attr. result_index != 1
2030+ return MOI. NO_SOLUTION
2031+ elseif model. solve_result. termination. problem_status. dual_status ==
2032+ FeasibilityStatusProto. FEASIBILITY_STATUS_UNDETERMINED
2033+ return MOI. UNKNOWN_RESULT_STATUS
2034+ elseif model. solve_result. termination. problem_status. dual_status ==
2035+ FeasibilityStatusProto. FEASIBILITY_STATUS_FEASIBLE
2036+ return MOI. FEASIBLE_SOLUTION
2037+ elseif model. solve_result. termination. problem_status. dual_status ==
2038+ FeasibilityStatusProto. FEASIBILITY_STATUS_INFEASIBLE
2039+ return MOI. INFEASIBLE_SOLUTION
2040+ else
2041+ # For FEASIBILITY_STATUS_UNSPECIFIED which is a guard value representing no status
2042+ return MOI. NO_SOLUTION
2043+ end
2044+ end
2045+
2046+ """
2047+ When the solver claims the the primal or dual problem is infeasible, but
2048+ it does not know which (or if both are infeasible), this attribute returns `true`.
2049+ It can be true only when primal_status = dual_status = FEASIBILITY_STATUS_UNDETERMINED
2050+ (mapped to MOI.UNKNOWN_RESULT_STATUS). This extra information is often needed when
2051+ preprocessing determines there is no optimal solution to the problem
2052+ (but can't determine if it is due to infeasibility, unboundedness, or both).
2053+ """
2054+ struct PrimalOrDualInfeasible <: MOI.AbstractOptimizerAttribute end
2055+ MOI. attribute_value_type (:: PrimalOrDualInfeasible ) = Bool
2056+
2057+ function MOI. get (model:: Optimizer , :: PrimalOrDualInfeasible ):: Bool
2058+ if isnothing (model) || isnothing (model. solve_result)
2059+ return false
2060+ end
2061+
2062+ return model. solve_result. termination. problem_status. primal_status. primal_or_dual_infeasible
2063+ end
2064+
2065+ function MOI. get (model:: Optimizer , attr:: MOI.ObjectiveBound )
2066+ if isnothing (model) || isnothing (model. solve_result)
2067+ throw (MOI. GetAttributeNotAllowed (attr))
2068+ end
2069+
2070+ return model. solve_result. termination. objective_bounds. primal_bound
2071+ end
2072+
2073+ """
2074+ When the solver claims there exists a dual solution that is numerically feasible
2075+ (i.e. feasible up to the solvers tolerance), and whose objective value is
2076+ dual_bound, this attribute returns the dual bound.
2077+ """
2078+ struct DualObjectiveBound <: MOI.AbstractOptimizerAttribute end
2079+ MOI. attribute_value_type (:: DualObjectiveBound ) = Float64
2080+
2081+ function MOI. get (model:: Optimizer , attr:: DualObjectiveBound )
2082+ if isnothing (model) || isnothing (model. solve_result)
2083+ throw (MOI. GetAttributeNotAllowed (attr))
2084+ end
2085+
2086+ return model. solve_result. termination. objective_bounds. dual_bound
2087+ end
0 commit comments