diff --git a/cpp/include/cuopt/linear_programming/mip/solver_stats.hpp b/cpp/include/cuopt/linear_programming/mip/solver_stats.hpp index a546354a65..0eb0ce41af 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_stats.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_stats.hpp @@ -9,7 +9,6 @@ #include #include namespace cuopt::linear_programming { - template struct solver_stats_t { // Direction-neutral placeholder; solver_context initializes based on maximize/minimize. diff --git a/cpp/src/branch_and_bound/branch_and_bound.cpp b/cpp/src/branch_and_bound/branch_and_bound.cpp index e786f35a59..ddd42a1a9c 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.cpp +++ b/cpp/src/branch_and_bound/branch_and_bound.cpp @@ -197,22 +197,12 @@ f_t user_relative_gap(const lp_problem_t& lp, f_t obj_value, f_t lower return user_mip_gap; } -template -std::string user_mip_gap(const lp_problem_t& lp, f_t obj_value, f_t lower_bound) +template +std::string to_percentage(f_t value) { - const f_t user_mip_gap = user_relative_gap(lp, obj_value, lower_bound); - if (user_mip_gap == std::numeric_limits::infinity()) { - return " - "; - } else { - constexpr int BUFFER_LEN = 32; - char buffer[BUFFER_LEN]; - if (user_mip_gap > 1e-3) { - snprintf(buffer, BUFFER_LEN - 1, "%5.1f%%", user_mip_gap * 100); - } else { - snprintf(buffer, BUFFER_LEN - 1, "%5.2f%%", user_mip_gap * 100); - } - return std::string(buffer); - } + if (value == std::numeric_limits::infinity()) return "---"; + if (value > 1e-3) { return std::format("{:5.1f}%", value * 100); } + return std::format("{:5.2f}%", value * 100); } } // namespace @@ -302,29 +292,57 @@ void branch_and_bound_t::set_initial_upper_bound(f_t bound) upper_bound_ = bound; } +template +void branch_and_bound_t::print_table_header() +{ + std::string header = std::format("{:^1}|{:^12}|{:^12}|{:^19}|{:^15}|{:^8}|{:^7}|{:^11}|{:^11}|", + "", + "Explored", + "Unexplored", + "Objective", + "Bound", + "IntInf", + "Depth", + "Iter/Node", + "Gap"); + if (settings_.deterministic) { header += std::format("{:^8}|", "Work"); } + header += std::format("{:^8}|", "Time"); + settings_.log.printf("%s\n", header.c_str()); +} + template void branch_and_bound_t::report_heuristic(f_t obj) { if (is_running_) { - f_t user_obj = compute_user_objective(original_lp_, obj); - f_t user_lower = compute_user_objective(original_lp_, get_lower_bound()); - std::string user_gap = user_mip_gap(original_lp_, obj, get_lower_bound()); - - settings_.log.printf( - "H %+13.6e %+10.6e %s %9.2f\n", - user_obj, - user_lower, - user_gap.c_str(), - toc(exploration_stats_.start_time)); + f_t lower_bound = get_lower_bound(); + f_t user_obj = compute_user_objective(original_lp_, obj); + f_t user_lower = compute_user_objective(original_lp_, lower_bound); + f_t user_gap = user_relative_gap(original_lp_, obj, lower_bound); + std::string user_gap_text = to_percentage(user_gap); + + std::string log_line = + std::format("H {:>12} {:>12} {:^+19.6e} {:^+15.6e} {:>8} {:>7} {:^11} {:^11}", + "", // nodes explored + "", // nodes unexplored + user_obj, + user_lower, + "", // integer infeasible + "", // depth + "", // iter/node + user_gap_text); + + if (settings_.deterministic) { log_line += std::format("{:^8}", ""); } + log_line += std::format(" {:>8.2f}", toc(exploration_stats_.start_time)); + settings_.log.printf("%s\n", log_line.c_str()); } else { if (solving_root_relaxation_.load()) { f_t user_obj = compute_user_objective(original_lp_, obj); - std::string user_gap = - user_mip_gap(original_lp_, obj, root_lp_current_lower_bound_.load()); - settings_.log.printf( - "New solution from primal heuristics. Objective %+.6e. Gap %s. Time %.2f\n", + f_t user_gap = user_relative_gap(original_lp_, obj, root_lp_current_lower_bound_.load()); + std::string user_gap_text = to_percentage(user_gap); + settings_.log.print_format( + "New solution from primal heuristics. Objective {:+.6e}. Gap {}. Time {:.2f}\n", user_obj, - user_gap.c_str(), + user_gap_text, toc(exploration_stats_.start_time)); } else { settings_.log.printf("New solution from primal heuristics. Objective %+.6e. Time %.2f\n", @@ -345,34 +363,23 @@ void branch_and_bound_t::report( const f_t user_lower = compute_user_objective(original_lp_, lower_bound); const f_t iters = static_cast(exploration_stats_.total_lp_iters); const f_t iter_node = nodes_explored > 0 ? iters / nodes_explored : iters; - const std::string user_gap = user_mip_gap(original_lp_, obj, lower_bound); - if (work_time >= 0) { - settings_.log.printf( - "%c %10d %10lu %+13.6e %+10.6e %6d %6d %7.1e %s %9.2f %9.2f\n", - symbol, - nodes_explored, - nodes_unexplored, - user_obj, - user_lower, - node_int_infeas, - node_depth, - iter_node, - user_gap.c_str(), - work_time, - toc(exploration_stats_.start_time)); - } else { - settings_.log.printf("%c %10d %10lu %+13.6e %+10.6e %6d %6d %7.1e %s %9.2f\n", - symbol, - nodes_explored, - nodes_unexplored, - user_obj, - user_lower, - node_int_infeas, - node_depth, - iter_node, - user_gap.c_str(), - toc(exploration_stats_.start_time)); - } + f_t user_gap = user_relative_gap(original_lp_, obj, lower_bound); + std::string user_gap_text = to_percentage(user_gap); + + std::string log_line = + std::format("{:^1} {:>12} {:>12} {:^+19.6e} {:^+15.6e} {:>8} {:>7} {:^11.1e} {:^11}", + symbol, + nodes_explored, + nodes_unexplored, + user_obj, + user_lower, + node_int_infeas, + node_depth, + iter_node, + user_gap_text); + if (work_time >= 0) { log_line += std::format(" {:>8.2f}", work_time); } + log_line += std::format(" {:>8.2f}", toc(exploration_stats_.start_time)); + settings_.log.printf("%s\n", log_line.c_str()); } template @@ -713,30 +720,27 @@ void branch_and_bound_t::set_final_solution(mip_solution_t& f_t gap_rel = user_relative_gap(original_lp_, upper_bound_.load(), lower_bound); bool is_maximization = original_lp_.obj_scale < 0.0; - settings_.log.printf("Explored %d nodes in %.2fs.\n", - exploration_stats_.nodes_explored, - toc(exploration_stats_.start_time)); + settings_.log.print_format("Explored {} nodes ({} simplex iterations) in {:.2f}s.", + exploration_stats_.nodes_explored.load(), + exploration_stats_.total_lp_iters.load(), + toc(exploration_stats_.start_time)); + if (exploration_stats_.orbital_fixing_nodes.load() > 0 || exploration_stats_.orbital_conflict_nodes.load() > 0) { - settings_.log.printf( - "Orbital fixing applied at %lld nodes, %lld total variable fixings, " - "%lld nodes with conflicting orbits\n", - (long long)exploration_stats_.orbital_fixing_nodes.load(), - (long long)exploration_stats_.orbital_fixings_applied.load(), - (long long)exploration_stats_.orbital_conflict_nodes.load()); + settings_.log.print_format( + "Orbital fixing applied at {} nodes, {} total variable fixings, " + "{} nodes with conflicting orbits\n", + exploration_stats_.orbital_fixing_nodes.load(), + exploration_stats_.orbital_fixings_applied.load(), + exploration_stats_.orbital_conflict_nodes.load()); } if (exploration_stats_.lexical_reduction_nodes.load() > 0) { - settings_.log.printf( - "Lexical reduction applied at %lld nodes, %lld total variable fixings, %lld nodes pruned\n", - (long long)exploration_stats_.lexical_reduction_nodes.load(), - (long long)exploration_stats_.lexical_reduction_fixings_applied.load(), - (long long)exploration_stats_.lexical_reduction_pruned_nodes.load()); + settings_.log.print_format( + "Lexical reduction applied at {} nodes, {} total variable fixings, {} nodes pruned\n", + exploration_stats_.lexical_reduction_nodes.load(), + exploration_stats_.lexical_reduction_fixings_applied.load(), + exploration_stats_.lexical_reduction_pruned_nodes.load()); } - settings_.log.printf("Absolute Gap %e Objective %.16e %s Bound %.16e\n", - gap, - obj, - is_maximization ? "Upper" : "Lower", - user_bound); if (gap <= settings_.absolute_mip_gap_tol || gap_rel <= settings_.relative_mip_gap_tol) { solver_status_ = mip_status_t::OPTIMAL; @@ -762,7 +766,6 @@ void branch_and_bound_t::set_final_solution(mip_solution_t& if (solver_status_ == mip_status_t::UNSET) { if (exploration_stats_.nodes_explored > 0 && exploration_stats_.nodes_unexplored == 0 && upper_bound_ == inf) { - settings_.log.printf("Integer infeasible.\n"); solver_status_ = mip_status_t::INFEASIBLE; if (settings_.heuristic_preemption_callback != nullptr) { settings_.heuristic_preemption_callback(); @@ -2115,8 +2118,7 @@ lp_status_t branch_and_bound_t::solve_root_relaxation( solver_name.c_str()); settings_.log.printf("Root relaxation objective %+.8e\n", user_objective); } else { - settings_.log.printf("Root relaxation returned: %s\n", - lp_status_to_string(root_status).c_str()); + settings_.log.debug_format("Root relaxation returned: {}", lp_status_to_string(root_status)); } settings_.log.printf("\n"); @@ -2422,8 +2424,8 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut exploration_stats_.nodes_explored = 0; original_lp_.A.to_compressed_row(Arow_); - settings_.log.printf("Reduced cost strengthening enabled: %d\n", - settings_.reduced_cost_strengthening); + settings_.log.debug("Reduced cost strengthening enabled: %d\n", + settings_.reduced_cost_strengthening); variable_bounds_t variable_bounds( original_lp_, settings_, var_types_, Arow_, new_slacks_); @@ -2497,6 +2499,17 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut nonbasic_list, root_vstatus_, edge_norms_); + if (root_status == lp_status_t::OPTIMAL) { + settings_.log.printf("\n"); + settings_.log.printf( + "Root relaxation solution found in %d iterations and %.2fs by Dual Simplex\n", + root_relax_soln_.iterations, + toc(exploration_stats_.start_time)); + settings_.log.printf("Root relaxation objective %+.8e\n", + compute_user_objective(original_lp_, root_relax_soln_.x)); + settings_.log.printf("\n"); + } + } else { settings_.log.printf("\nSolving LP root relaxation in concurrent mode\n"); root_status = solve_root_relaxation(lp_settings, @@ -2590,11 +2603,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut is_running_ = true; lower_bound_numerical_ = inf; - if (num_fractional != 0 && settings_.max_cut_passes > 0) { - settings_.log.printf( - " | Explored | Unexplored | Objective | Bound | IntInf | Depth | Iter/Node | " - "Gap | Time |\n"); - } + if (num_fractional != 0 && settings_.max_cut_passes > 0) { print_table_header(); } cut_pool_t cut_pool(original_lp_.num_cols, settings_); cut_generation_t cut_generation(cut_pool, @@ -2889,16 +2898,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut if (settings_.diving_settings.coefficient_diving != 0) { calculate_variable_locks(original_lp_, var_up_locks_, var_down_locks_); } - - if (settings_.deterministic) { - settings_.log.printf( - " | Explored | Unexplored | Objective | Bound | IntInf | Depth | Iter/Node " - "| Gap | Work | Time |\n"); - } else { - settings_.log.printf( - " | Explored | Unexplored | Objective | Bound | IntInf | Depth | Iter/Node " - "| Gap | Time |\n"); - } + print_table_header(); #pragma omp taskgroup { @@ -2931,6 +2931,7 @@ mip_status_t branch_and_bound_t::solve(mip_solution_t& solut } // Implicit barrier for all tasks created within the group (RINS, B&B workers) is_running_ = false; + settings_.log.printf("\n"); // Compute final lower bound f_t lower_bound; @@ -3410,9 +3411,10 @@ void branch_and_bound_t::deterministic_sync_callback() exploration_stats_.last_log = tic(); } - f_t obj = compute_user_objective(original_lp_, upper_bound); - f_t user_lower = compute_user_objective(original_lp_, lower_bound); - std::string gap_user = user_mip_gap(original_lp_, upper_bound, lower_bound); + f_t obj = compute_user_objective(original_lp_, upper_bound); + f_t user_lower = compute_user_objective(original_lp_, lower_bound); + f_t user_gap = user_relative_gap(original_lp_, upper_bound, lower_bound); + std::string user_gap_text = to_percentage(user_gap); std::string idle_workers; i_t idle_count = 0; @@ -3428,7 +3430,7 @@ void branch_and_bound_t::deterministic_sync_callback() exploration_stats_.nodes_unexplored, obj, user_lower, - gap_user.c_str(), + user_gap_text.c_str(), toc(exploration_stats_.start_time), state_hash, idle_workers.empty() ? "" : " ", diff --git a/cpp/src/branch_and_bound/branch_and_bound.hpp b/cpp/src/branch_and_bound/branch_and_bound.hpp index 0b32b2ece5..15a6030dbc 100644 --- a/cpp/src/branch_and_bound/branch_and_bound.hpp +++ b/cpp/src/branch_and_bound/branch_and_bound.hpp @@ -263,6 +263,7 @@ class branch_and_bound_t { omp_atomic_t lower_bound_numerical_; std::function user_bound_callback_; + void print_table_header(); void report_heuristic(f_t obj); void report(char symbol, f_t obj, diff --git a/cpp/src/branch_and_bound/symmetry.hpp b/cpp/src/branch_and_bound/symmetry.hpp index bcce5ece2a..359895e579 100644 --- a/cpp/src/branch_and_bound/symmetry.hpp +++ b/cpp/src/branch_and_bound/symmetry.hpp @@ -684,6 +684,8 @@ std::unique_ptr> detect_symmetry( const simplex_solver_settings_t& settings, bool& has_symmetry) { + settings.log.printf("\nRunning symmetry detection...\n"); + has_symmetry = false; f_t start_time = tic(); diff --git a/cpp/src/cuts/cuts.cpp b/cpp/src/cuts/cuts.cpp index a94478adc9..08f7b96846 100644 --- a/cpp/src/cuts/cuts.cpp +++ b/cpp/src/cuts/cuts.cpp @@ -3592,6 +3592,8 @@ variable_bounds_t::variable_bounds_t(const lp_problem_t& lp, } f_t start_time = tic(); + settings.log.debug("Computing variable bounds..."); + std::vector num_integer_in_row(lp.num_rows, 0); // Construct the slack map @@ -3763,7 +3765,7 @@ variable_bounds_t::variable_bounds_t(const lp_problem_t& lp, } } upper_offsets[lp.num_cols] = upper_edges; - settings.log.printf("%d variable upper bounds in %.2f seconds\n", upper_edges, toc(start_time)); + settings.log.debug("%d variable upper bounds in %.2f seconds\n", upper_edges, toc(start_time)); // Now go through all continuous variables and use the activiites to get lower variable bounds i_t lower_edges = 0; @@ -3854,7 +3856,7 @@ variable_bounds_t::variable_bounds_t(const lp_problem_t& lp, } } lower_offsets[lp.num_cols] = lower_edges; - settings.log.printf("%d variable lower bounds in %.2f seconds\n", lower_edges, toc(start_time)); + settings.log.debug("%d variable lower bounds in %.2f seconds\n", lower_edges, toc(start_time)); } template diff --git a/cpp/src/dual_simplex/logger.hpp b/cpp/src/dual_simplex/logger.hpp index f813086708..530c81d459 100644 --- a/cpp/src/dual_simplex/logger.hpp +++ b/cpp/src/dual_simplex/logger.hpp @@ -16,6 +16,9 @@ #include #include #include +#include +#include +#include namespace cuopt::linear_programming::dual_simplex { @@ -84,6 +87,29 @@ class logger_t { } } + template + void print_format(std::format_string fmt, Args&&... args) + { + if (log) { + std::string msg = std::format(fmt, std::forward(args)...); + if (log_to_console) { +#ifdef CUOPT_LOG_ACTIVE_LEVEL + std::string msg_no_newline = msg; + if (msg_no_newline.size() > 0 && msg.ends_with("\n")) { msg_no_newline.pop_back(); } + + CUOPT_LOG_INFO("%s%s", log_prefix.c_str(), msg_no_newline.c_str()); +#else + std::printf("%s", msg.c_str()); + fflush(stdout); +#endif + } + if (log_to_file && log_file != nullptr) { + std::fprintf(log_file, "%s", msg.c_str()); + fflush(log_file); + } + } + } + void debug([[maybe_unused]] const char* fmt, ...) { if (log) { @@ -118,6 +144,28 @@ class logger_t { } } + template + void debug_format(std::format_string fmt, Args&&... args) + { + if (log) { + std::string msg = std::format(fmt, std::forward(args)...); + if (log_to_console) { +#ifdef CUOPT_LOG_DEBUG + std::string msg_no_newline = msg; + if (msg_no_newline.size() > 0 && msg.ends_with("\n")) { msg_no_newline.pop_back(); } + CUOPT_LOG_TRACE("%s%s", log_prefix.c_str(), msg_no_newline.c_str()); +#else + std::printf("%s", msg.c_str()); + fflush(stdout); +#endif + } + if (log_to_file && log_file != nullptr) { + std::fprintf(log_file, "%s", msg.c_str()); + fflush(log_file); + } + } + } + bool log; bool log_to_console; std::string log_prefix; diff --git a/cpp/src/mip_heuristics/diversity/diversity_manager.cu b/cpp/src/mip_heuristics/diversity/diversity_manager.cu index 7b038d6fa6..4b0e5168ec 100644 --- a/cpp/src/mip_heuristics/diversity/diversity_manager.cu +++ b/cpp/src/mip_heuristics/diversity/diversity_manager.cu @@ -223,7 +223,7 @@ template bool diversity_manager_t::run_presolve(f_t time_limit, timer_t global_timer) { raft::common::nvtx::range fun_scope("run_presolve"); - CUOPT_LOG_INFO("Starting cuOpt presolve"); + CUOPT_LOG_INFO("\nRunning cuOpt presolve"); timer_t presolve_timer(time_limit); auto term_crit = ls.constraint_prop.bounds_update.solve(*problem_ptr); diff --git a/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp b/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp index d94cf5aa67..ef8df93e6d 100644 --- a/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp +++ b/cpp/src/mip_heuristics/presolve/third_party_presolve.cpp @@ -682,12 +682,12 @@ third_party_presolve_result_t third_party_presolve_t::apply( papilo::Problem papilo_problem = build_papilo_problem(op_problem, category, maximize_); - CUOPT_LOG_INFO("Original problem: %d constraints, %d variables, %d nonzeros", - papilo_problem.getNRows(), - papilo_problem.getNCols(), - papilo_problem.getConstraintMatrix().getNnz()); + CUOPT_LOG_DEBUG("Original problem: %d constraints, %d variables, %d nonzeros", + papilo_problem.getNRows(), + papilo_problem.getNCols(), + papilo_problem.getConstraintMatrix().getNnz()); - CUOPT_LOG_INFO("Calling Papilo presolver (git hash %s)", PAPILO_GITHASH); + CUOPT_LOG_INFO("\nRunning Papilo presolve (git hash %s)", PAPILO_GITHASH); if (category == problem_category_t::MIP) { dual_postsolve = false; } papilo::Presolve papilo_presolver; set_presolve_methods(papilo_presolver, category, dual_postsolve); diff --git a/cpp/src/mip_heuristics/solve.cu b/cpp/src/mip_heuristics/solve.cu index 2b64a7c681..004016dc70 100644 --- a/cpp/src/mip_heuristics/solve.cu +++ b/cpp/src/mip_heuristics/solve.cu @@ -735,10 +735,7 @@ mip_solution_t solve_mip_helper(optimization_problem_t& op_p } } - if (sol.get_termination_status() == mip_termination_status_t::FeasibleFound || - sol.get_termination_status() == mip_termination_status_t::Optimal) { - sol.log_detailed_summary(); - } + sol.log_detailed_summary(); if (settings.sol_file != "") { CUOPT_LOG_INFO("Writing solution to file %s", settings.sol_file.c_str()); diff --git a/cpp/src/mip_heuristics/solver.cu b/cpp/src/mip_heuristics/solver.cu index 858e865bc3..053d4be2c4 100644 --- a/cpp/src/mip_heuristics/solver.cu +++ b/cpp/src/mip_heuristics/solver.cu @@ -181,7 +181,7 @@ void extract_probing_implied_bounds( } } - CUOPT_LOG_INFO("Probing implied bounds: %d zero entries, %d one entries", zero_nnz, one_nnz); + CUOPT_LOG_INFO("\nProbing implied bounds: %d zero entries, %d one entries", zero_nnz, one_nnz); } template diff --git a/cpp/src/mip_heuristics/solver_solution.cu b/cpp/src/mip_heuristics/solver_solution.cu index 8f6f8de05f..b3920a4936 100644 --- a/cpp/src/mip_heuristics/solver_solution.cu +++ b/cpp/src/mip_heuristics/solver_solution.cu @@ -238,21 +238,39 @@ void mip_solution_t::log_summary() const template void mip_solution_t::log_detailed_summary() const { - CUOPT_LOG_INFO( - "Solution objective: %f , relative_mip_gap %f solution_bound %f presolve_time %f " - "total_solve_time %f " - "max constraint violation %f max int violation %f max var bounds violation %f " - "nodes %d simplex_iterations %d", - objective_, - mip_gap_, - stats_.get_solution_bound(), - stats_.presolve_time, - stats_.total_solve_time, - max_constraint_violation_, - max_int_violation_, - max_variable_bound_violation_, - stats_.num_nodes, - stats_.num_simplex_iterations); + switch (termination_status_) { + case mip_termination_status_t::Optimal: + case mip_termination_status_t::FeasibleFound: + CUOPT_LOG_INFO("%s\n", + std::format("Best objective {:+.6e}, best bound {:+.6e}, gap {:.2f}%.\n", + objective_, + stats_.get_solution_bound(), + mip_gap_ * 100) + .c_str()); + break; + + case mip_termination_status_t::Infeasible: + CUOPT_LOG_INFO("The problem is integer infeasible.\n"); + break; + + case mip_termination_status_t::TimeLimit: + CUOPT_LOG_INFO("No feasible solution was found within the time limit.\n"); + break; + + case mip_termination_status_t::WorkLimit: + CUOPT_LOG_INFO("No feasible solution was found within the work limit.\n"); + break; + + case mip_termination_status_t::Unbounded: CUOPT_LOG_INFO("The problem is unbounded.\n"); break; + + case mip_termination_status_t::UnboundedOrInfeasible: + CUOPT_LOG_INFO("The problem is unbounded or infeasible.\n"); + break; + + case mip_termination_status_t::NoTermination: + CUOPT_LOG_INFO("Warning: The solver did not terminate successfully.\n"); + break; + } } #if MIP_INSTANTIATE_FLOAT || PDLP_INSTANTIATE_FLOAT diff --git a/python/cuopt/cuopt/tests/linear_programming/test_cpu_only_execution.py b/python/cuopt/cuopt/tests/linear_programming/test_cpu_only_execution.py index 4453d38bcc..07ae224ef9 100644 --- a/python/cuopt/cuopt/tests/linear_programming/test_cpu_only_execution.py +++ b/python/cuopt/cuopt/tests/linear_programming/test_cpu_only_execution.py @@ -120,9 +120,9 @@ def _parse_cli_output(output): result["status"] = "Optimal" continue - # MIP solution: "Solution objective: 2.000000 , ..." + # MIP solution: "Best objective 2.000000 , ..." m = re.match( - r"Solution objective:\s*([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?)", + r"Best objective \s*([+-]?\d+\.?\d*(?:[eE][+-]?\d+)?)", stripped, ) if m: diff --git a/python/libcuopt/libcuopt/tests/test_cli.sh b/python/libcuopt/libcuopt/tests/test_cli.sh index 85f73fa58f..51c0397e74 100644 --- a/python/libcuopt/libcuopt/tests/test_cli.sh +++ b/python/libcuopt/libcuopt/tests/test_cli.sh @@ -30,4 +30,4 @@ cuopt_cli "${RAPIDS_DATASET_ROOT_DIR}"/linear_programming/good-mps-1.lp.bz2 | gr # Add a for mixed integer programming test with options -cuopt_cli "${RAPIDS_DATASET_ROOT_DIR}"/mip/sample.mps --mip-absolute-gap 0.01 --time-limit 10 | grep -q "Solution objective" || (echo "Expected solution objective not found" && exit 1) +cuopt_cli "${RAPIDS_DATASET_ROOT_DIR}"/mip/sample.mps --mip-absolute-gap 0.01 --time-limit 10 | grep -q "Best objective" || (echo "Expected solution objective not found" && exit 1)