From e0111536fadd043b46c5fc8ef0a5b3053d02e416 Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Mon, 2 Jun 2025 12:45:13 -0700 Subject: [PATCH 1/5] Write out mps file containing user problem. Useful for debugging --- cpp/include/cuopt/linear_programming/constants.h | 1 + cpp/include/cuopt/linear_programming/mip/solver_settings.hpp | 1 + .../cuopt/linear_programming/pdlp/solver_settings.hpp | 1 + cpp/src/linear_programming/solve.cu | 5 +++++ cpp/src/math_optimization/solver_settings.cu | 4 +++- cpp/src/mip/solve.cu | 4 ++++ 6 files changed, 15 insertions(+), 1 deletion(-) diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index 4cf856bb86..f4b826874d 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -59,6 +59,7 @@ #define CUOPT_MIP_SCALING "mip_scaling" #define CUOPT_SOL_FILE "solution_file" #define CUOPT_NUM_CPU_THREADS "num_cpu_threads" +#define CUOPT_USER_PROBLEM_FILE "user_problem_file" /* @brief LP/MIP termination status constants */ #define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0 diff --git a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp index 850248b5f5..71afb366d5 100644 --- a/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/mip/solver_settings.hpp @@ -92,6 +92,7 @@ class mip_solver_settings_t { bool log_to_console = true; std::string log_file; std::string sol_file; + std::string user_problem_file; /** Initial primal solution */ std::shared_ptr> initial_solution_; diff --git a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp index 8631c857ef..9dcccf7a7a 100644 --- a/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp +++ b/cpp/include/cuopt/linear_programming/pdlp/solver_settings.hpp @@ -200,6 +200,7 @@ class pdlp_solver_settings_t { bool log_to_console{true}; std::string log_file{""}; std::string sol_file{""}; + std::string user_problem_file{""}; bool per_constraint_residual{false}; bool crossover{false}; bool save_best_primal_so_far{false}; diff --git a/cpp/src/linear_programming/solve.cu b/cpp/src/linear_programming/solve.cu index 12859a1183..ed2a30bcf6 100644 --- a/cpp/src/linear_programming/solve.cu +++ b/cpp/src/linear_programming/solve.cu @@ -569,6 +569,11 @@ optimization_problem_solution_t solve_lp(optimization_problem_t::solver_settings_t() : pdlp_settings(), mip_settings {CUOPT_LOG_FILE, &mip_settings.log_file, ""}, {CUOPT_LOG_FILE, &pdlp_settings.log_file, ""}, {CUOPT_SOL_FILE, &mip_settings.sol_file, ""}, - {CUOPT_SOL_FILE, &pdlp_settings.sol_file, ""} + {CUOPT_SOL_FILE, &pdlp_settings.sol_file, ""}, + {CUOPT_USER_PROBLEM_FILE, &mip_settings.user_problem_file, ""}, + {CUOPT_USER_PROBLEM_FILE, &pdlp_settings.user_problem_file, ""} }; // clang-format on } diff --git a/cpp/src/mip/solve.cu b/cpp/src/mip/solve.cu index 6ca891a3b2..e964568013 100644 --- a/cpp/src/mip/solve.cu +++ b/cpp/src/mip/solve.cu @@ -161,6 +161,10 @@ mip_solution_t solve_mip(optimization_problem_t& op_problem, // have solve, problem, solution, utils etc. in common dir detail::problem_t problem(op_problem); + if (settings.user_problem_file != "") { + CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str()); + problem.write_as_mps(settings.user_problem_file); + } // this is for PDLP, i think this should be part of pdlp solver setup_device_symbols(op_problem.get_handle_ptr()->get_stream()); From 89133fcd9b87f86599bded5cb721343b1104c348 Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Mon, 2 Jun 2025 13:45:08 -0700 Subject: [PATCH 2/5] Add documentation and constant to Python --- docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst | 6 ++++-- docs/cuopt/source/lp-milp-settings.rst | 11 +++++++++++ .../linear_programming/solver/solver_parameters.pyx | 2 ++ 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst index fbd312430e..87d14d89c3 100644 --- a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst +++ b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst @@ -129,7 +129,7 @@ The following functions are used to set and get parameters. You can find more de Parameter Constants -------------------- +------------------- These constants are used as the parameter name in the `cuOptSetParameter `_ , `cuOptGetParameter `_ and similar functions. More details on the parameters can be found in the `LP/MILP settings <../../lp-milp-settings.html>`_ section. @@ -157,7 +157,9 @@ These constants are used as the parameter name in the `cuOptSetParameter Date: Tue, 3 Jun 2025 16:38:48 -0700 Subject: [PATCH 3/5] Fix issue where variables that have 0 objective and 0 lower bound are omitted from MPS file --- cpp/src/mip/problem/write_mps.cu | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cpp/src/mip/problem/write_mps.cu b/cpp/src/mip/problem/write_mps.cu index 31315fc9c2..658a9cba87 100644 --- a/cpp/src/mip/problem/write_mps.cu +++ b/cpp/src/mip/problem/write_mps.cu @@ -146,7 +146,7 @@ void problem_t::write_as_mps(const std::string& path) h_var_ub[j] == std::numeric_limits::infinity()) { mps_file << " FR BOUND1 " << col_name << "\n"; } else { - if (h_var_lb[j] != 0.0) { + if (h_var_lb[j] != 0.0 || h_obj_coeffs[j] == 0.0) { if (h_var_lb[j] == -std::numeric_limits::infinity()) { mps_file << " MI BOUND1 " << col_name << "\n"; } else { From a822c8943c7e7367ecbca06a5bb576e3da2c1307 Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Tue, 3 Jun 2025 20:48:40 -0700 Subject: [PATCH 4/5] Add a unit test and make write_mps handle maximization problems and nonnegative integer variables --- cpp/src/mip/problem/write_mps.cu | 6 +++-- cpp/tests/mip/doc_example_test.cu | 45 +++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/cpp/src/mip/problem/write_mps.cu b/cpp/src/mip/problem/write_mps.cu index 658a9cba87..cca3cd5b1e 100644 --- a/cpp/src/mip/problem/write_mps.cu +++ b/cpp/src/mip/problem/write_mps.cu @@ -54,6 +54,8 @@ void problem_t::write_as_mps(const std::string& path) // NAME section mps_file << "NAME " << original_problem_ptr->get_problem_name() << "\n"; + if (maximize) { mps_file << "OBJSENSE\n MAXIMIZE\n"; } + // ROWS section mps_file << "ROWS\n"; mps_file << " N " << (objective_name.empty() ? "OBJ" : objective_name) << "\n"; @@ -86,7 +88,7 @@ void problem_t::write_as_mps(const std::string& path) // Write objective coefficient if non-zero if (h_obj_coeffs[j] != 0.0) { mps_file << " " << col_name << " " << (objective_name.empty() ? "OBJ" : objective_name) - << " " << h_obj_coeffs[j] << "\n"; + << " " << (maximize ? -h_obj_coeffs[j] : h_obj_coeffs[j]) << "\n"; } // Write constraint coefficients @@ -146,7 +148,7 @@ void problem_t::write_as_mps(const std::string& path) h_var_ub[j] == std::numeric_limits::infinity()) { mps_file << " FR BOUND1 " << col_name << "\n"; } else { - if (h_var_lb[j] != 0.0 || h_obj_coeffs[j] == 0.0) { + if (h_var_lb[j] != 0.0 || h_obj_coeffs[j] == 0.0 || h_var_types[j] != var_t::CONTINUOUS) { if (h_var_lb[j] == -std::numeric_limits::infinity()) { mps_file << " MI BOUND1 " << col_name << "\n"; } else { diff --git a/cpp/tests/mip/doc_example_test.cu b/cpp/tests/mip/doc_example_test.cu index e945a7ef54..7b32cf068a 100644 --- a/cpp/tests/mip/doc_example_test.cu +++ b/cpp/tests/mip/doc_example_test.cu @@ -29,6 +29,7 @@ #include #include +#include #include #include #include @@ -120,4 +121,48 @@ void test_mps_file() TEST(docs, mixed_integer_linear_programming) { test_mps_file(); } +TEST(docs, user_problem_file) +{ + const raft::handle_t handle_{}; + mip_solver_settings_t settings; + constexpr double test_time_limit = 1.; + + // Create the problem from documentation example + auto problem = create_doc_example_problem(); + + EXPECT_FALSE(std::filesystem::exists("user_problem.mps")); + + settings.time_limit = test_time_limit; + settings.user_problem_file = "user_problem.mps"; + EXPECT_EQ(solve_mip(&handle_, problem, settings).get_termination_status(), + mip_termination_status_t::Optimal); + + EXPECT_TRUE(std::filesystem::exists("user_problem.mps")); + + cuopt::mps_parser::mps_data_model_t problem2 = + cuopt::mps_parser::parse_mps("user_problem.mps", false); + + EXPECT_EQ(problem2.get_n_variables(), problem.get_n_variables()); + EXPECT_EQ(problem2.get_n_constraints(), problem.get_n_constraints()); + EXPECT_EQ(problem2.get_nnz(), problem.get_nnz()); + + settings.user_problem_file = "user_problem2.mps"; + mip_solution_t solution = solve_mip(&handle_, problem2, settings); + EXPECT_EQ(solution.get_termination_status(), mip_termination_status_t::Optimal); + + double obj_val = solution.get_objective_value(); + // Expected objective value from documentation example is approximately 303.5 + EXPECT_NEAR(303.5, obj_val, 1.0); + + // Get solution values + const auto& sol_values = solution.get_solution(); + // x should be approximately 37 and integer + EXPECT_NEAR(37.0, sol_values.element(0, handle_.get_stream()), 0.1); + EXPECT_NEAR(std::round(sol_values.element(0, handle_.get_stream())), + sol_values.element(0, handle_.get_stream()), + settings.tolerances.integrality_tolerance); // Check x is integer + // y should be approximately 39.5 + EXPECT_NEAR(39.5, sol_values.element(1, handle_.get_stream()), 0.1); +} + } // namespace cuopt::linear_programming::test From 85d07d782a2b41413dd75de03751b27aed7b3448 Mon Sep 17 00:00:00 2001 From: Christopher Maes Date: Wed, 4 Jun 2025 09:50:25 -0700 Subject: [PATCH 5/5] CUOPT_SOL_FILE -> CUOPT_SOLUTION_FILE. Add CUOPT_SOLUTION_FILE named constant to Python. Add Python unit test --- .../cuopt/linear_programming/constants.h | 2 +- cpp/src/math_optimization/solver_settings.cu | 4 +- .../source/cuopt-c/lp-milp/lp-milp-c-api.rst | 2 +- .../solver/solver_parameters.pyx | 2 + .../linear_programming/test_lp_solver.py | 38 +++++++++++++++++++ 5 files changed, 44 insertions(+), 4 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index f4b826874d..ca4377de97 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -57,7 +57,7 @@ #define CUOPT_MIP_RELATIVE_GAP "mip_relative_gap" #define CUOPT_MIP_HEURISTICS_ONLY "mip_heuristics_only" #define CUOPT_MIP_SCALING "mip_scaling" -#define CUOPT_SOL_FILE "solution_file" +#define CUOPT_SOLUTION_FILE "solution_file" #define CUOPT_NUM_CPU_THREADS "num_cpu_threads" #define CUOPT_USER_PROBLEM_FILE "user_problem_file" diff --git a/cpp/src/math_optimization/solver_settings.cu b/cpp/src/math_optimization/solver_settings.cu index 7964f04577..142ef55c5b 100644 --- a/cpp/src/math_optimization/solver_settings.cu +++ b/cpp/src/math_optimization/solver_settings.cu @@ -110,8 +110,8 @@ solver_settings_t::solver_settings_t() : pdlp_settings(), mip_settings string_parameters = { {CUOPT_LOG_FILE, &mip_settings.log_file, ""}, {CUOPT_LOG_FILE, &pdlp_settings.log_file, ""}, - {CUOPT_SOL_FILE, &mip_settings.sol_file, ""}, - {CUOPT_SOL_FILE, &pdlp_settings.sol_file, ""}, + {CUOPT_SOLUTION_FILE, &mip_settings.sol_file, ""}, + {CUOPT_SOLUTION_FILE, &pdlp_settings.sol_file, ""}, {CUOPT_USER_PROBLEM_FILE, &mip_settings.user_problem_file, ""}, {CUOPT_USER_PROBLEM_FILE, &pdlp_settings.user_problem_file, ""} }; diff --git a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst index 87d14d89c3..4e10c8c43e 100644 --- a/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst +++ b/docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst @@ -157,7 +157,7 @@ These constants are used as the parameter name in the `cuOptSetParameter