| 1041 | } |
| 1042 | |
| 1043 | void lpKktCheck(HighsModelStatus& model_status, HighsInfo& info, |
| 1044 | const HighsLp& lp, const HighsSolution& solution, |
| 1045 | const HighsBasis& basis, const HighsOptions& options, |
| 1046 | const std::string& message) { |
| 1047 | if (!solution.value_valid) return; |
| 1048 | const bool has_dual_values = solution.dual_valid; |
| 1049 | const HighsLogOptions& log_options = options.log_options; |
| 1050 | double primal_feasibility_tolerance = options.primal_feasibility_tolerance; |
| 1051 | double dual_feasibility_tolerance = options.dual_feasibility_tolerance; |
| 1052 | double primal_residual_tolerance = options.primal_residual_tolerance; |
| 1053 | double dual_residual_tolerance = options.dual_residual_tolerance; |
| 1054 | double optimality_tolerance = options.optimality_tolerance; |
| 1055 | if (options.kkt_tolerance != kDefaultKktTolerance) { |
| 1056 | primal_feasibility_tolerance = options.kkt_tolerance; |
| 1057 | dual_feasibility_tolerance = options.kkt_tolerance; |
| 1058 | primal_residual_tolerance = options.kkt_tolerance; |
| 1059 | dual_residual_tolerance = options.kkt_tolerance; |
| 1060 | optimality_tolerance = options.kkt_tolerance; |
| 1061 | } |
| 1062 | info.objective_function_value = lp.objectiveValue(solution.col_value); |
| 1063 | HighsPrimalDualErrors primal_dual_errors; |
| 1064 | const bool get_residuals = !basis.valid; |
| 1065 | getLpKktFailures(options, lp, solution, basis, info, primal_dual_errors, |
| 1066 | get_residuals); |
| 1067 | if (model_status == HighsModelStatus::kOptimal) |
| 1068 | reportKktFailures(lp, options, info, message); |
| 1069 | // get_residuals is false when there is a valid basis, since |
| 1070 | // residual errors are assumed to be small, so |
| 1071 | // info.num_primal_residual_errors = -1, since they aren't |
| 1072 | // known. Hence don't consider this in identifying unboundedness |
| 1073 | // from HighsModelStatus::kUnboundedOrInfeasible |
| 1074 | if (model_status == HighsModelStatus::kUnboundedOrInfeasible && |
| 1075 | info.num_primal_infeasibilities == 0 && |
| 1076 | (!get_residuals || info.num_primal_residual_errors == 0)) |
| 1077 | model_status = HighsModelStatus::kUnbounded; |
| 1078 | bool was_optimal = model_status == HighsModelStatus::kOptimal; |
| 1079 | bool kkt_ok = true; |
| 1080 | bool written_optimality_error_header = false; |
| 1081 | |
| 1082 | auto foundOptimalityError = [&]() { |
| 1083 | kkt_ok = false; |
| 1084 | if (!was_optimal || written_optimality_error_header) return; |
| 1085 | highsLogUser(log_options, HighsLogType::kWarning, |
| 1086 | "LP solver claims optimality, but with\n"); |
| 1087 | written_optimality_error_header = true; |
| 1088 | }; |
| 1089 | |
| 1090 | double max_primal_tolerance_relative_violation = 0; |
| 1091 | double max_dual_tolerance_relative_violation = 0; |
| 1092 | double primal_dual_objective_tolerance_relative_violation = 0; |
| 1093 | const double max_allowed_tolerance_relative_violation = 1e2; |
| 1094 | if (basis.valid) { |
| 1095 | if (info.num_primal_infeasibilities > 0) { |
| 1096 | max_primal_tolerance_relative_violation = |
| 1097 | std::max(info.max_primal_infeasibility / primal_feasibility_tolerance, |
| 1098 | max_primal_tolerance_relative_violation); |
| 1099 | foundOptimalityError(); |
| 1100 | if (was_optimal) |
no test coverage detected