From 77a936e12b2c193383b6809993e9fbdbcbceedcb Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 16 Dec 2025 12:58:11 +0000 Subject: [PATCH 1/7] initial API impl --- .../cuopt/linear_programming/constants.h | 3 + .../cuopt/linear_programming/cuopt_c.h | 59 +++ cpp/src/linear_programming/cuopt_c.cpp | 73 ++++ cpp/tests/linear_programming/CMakeLists.txt | 4 + .../c_api_tests/c_api_test.c | 404 ++++++++++++++++++ .../c_api_tests/c_api_tests.cpp | 36 ++ .../c_api_tests/c_api_tests.h | 6 + 7 files changed, 585 insertions(+) diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index b512944a6..93a6ce29a 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -107,6 +107,9 @@ #define CUOPT_METHOD_DUAL_SIMPLEX 2 #define CUOPT_METHOD_BARRIER 3 +/* @brief File format constants for problem I/O */ +#define CUOPT_FILE_FORMAT_MPS 0 + /* @brief Status codes constants */ #define CUOPT_SUCCESS 0 #define CUOPT_INVALID_ARGUMENT 1 diff --git a/cpp/include/cuopt/linear_programming/cuopt_c.h b/cpp/include/cuopt/linear_programming/cuopt_c.h index 06af2ae86..bb2da0cd6 100644 --- a/cpp/include/cuopt/linear_programming/cuopt_c.h +++ b/cpp/include/cuopt/linear_programming/cuopt_c.h @@ -111,6 +111,20 @@ cuopt_int_t cuOptGetVersion(cuopt_int_t* version_major, */ cuopt_int_t cuOptReadProblem(const char* filename, cuOptOptimizationProblem* problem_ptr); +/** + * @brief Write an optimization problem to a file. + * + * @param[in] problem - The optimization problem to write. + * @param[in] filename - The path to the output file. + * @param[in] format - The file format to use. Currently only CUOPT_FILE_FORMAT_MPS is supported. + * + * @return A status code indicating success or failure. Returns CUOPT_INVALID_ARGUMENT + * if an unsupported format is specified. + */ +cuopt_int_t cuOptWriteProblem(cuOptOptimizationProblem problem, + const char* filename, + cuopt_int_t format); + /** @brief Create an optimization problem of the form * * @verbatim @@ -681,6 +695,51 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings, const char* parameter_name, cuopt_float_t* parameter_value); +/** + * @brief Set the initial primal solution for an LP solve. + * + * @param[in] settings - The solver settings object. + * @param[in] primal_solution - A pointer to an array of type cuopt_float_t + * of size num_variables containing the initial primal values. + * @param[in] num_variables - The number of variables (size of the primal_solution array). + * + * @return A status code indicating success or failure. + */ +cuopt_int_t cuOptSetInitialPrimalSolution(cuOptSolverSettings settings, + const cuopt_float_t* primal_solution, + cuopt_int_t num_variables); + +/** + * @brief Set the initial dual solution for an LP solve. + * + * @param[in] settings - The solver settings object. + * @param[in] dual_solution - A pointer to an array of type cuopt_float_t + * of size num_constraints containing the initial dual values. + * @param[in] num_constraints - The number of constraints (size of the dual_solution array). + * + * @return A status code indicating success or failure. + */ +cuopt_int_t cuOptSetInitialDualSolution(cuOptSolverSettings settings, + const cuopt_float_t* dual_solution, + cuopt_int_t num_constraints); + +/** + * @brief Add an initial solution (MIP start) for MIP solving. + * + * This function can be called multiple times to add multiple MIP starts. + * The solver will use these as starting points for the MIP search. + * + * @param[in] settings - The solver settings object. + * @param[in] solution - A pointer to an array of type cuopt_float_t + * of size num_variables containing the solution values. + * @param[in] num_variables - The number of variables (size of the solution array). + * + * @return A status code indicating success or failure. + */ +cuopt_int_t cuOptAddMIPStart(cuOptSolverSettings settings, + const cuopt_float_t* solution, + cuopt_int_t num_variables); + /** @brief Check if an optimization problem is a mixed integer programming problem. * * @param[in] problem - The optimization problem. diff --git a/cpp/src/linear_programming/cuopt_c.cpp b/cpp/src/linear_programming/cuopt_c.cpp index 0772dd14b..2d2072bad 100644 --- a/cpp/src/linear_programming/cuopt_c.cpp +++ b/cpp/src/linear_programming/cuopt_c.cpp @@ -92,6 +92,25 @@ cuopt_int_t cuOptReadProblem(const char* filename, cuOptOptimizationProblem* pro return CUOPT_SUCCESS; } +cuopt_int_t cuOptWriteProblem(cuOptOptimizationProblem problem, + const char* filename, + cuopt_int_t format) +{ + if (problem == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (filename == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (format != CUOPT_FILE_FORMAT_MPS) { return CUOPT_INVALID_ARGUMENT; } + + problem_and_stream_view_t* problem_and_stream_view = + static_cast(problem); + try { + problem_and_stream_view->op_problem->write_to_mps(std::string(filename)); + } catch (const std::exception& e) { + CUOPT_LOG_INFO("Error writing MPS file: %s", e.what()); + return CUOPT_MPS_FILE_ERROR; + } + return CUOPT_SUCCESS; +} + cuopt_int_t cuOptCreateProblem(cuopt_int_t num_constraints, cuopt_int_t num_variables, cuopt_int_t objective_sense, @@ -706,6 +725,60 @@ cuopt_int_t cuOptGetFloatParameter(cuOptSolverSettings settings, return CUOPT_SUCCESS; } +cuopt_int_t cuOptSetInitialPrimalSolution(cuOptSolverSettings settings, + const cuopt_float_t* primal_solution, + cuopt_int_t num_variables) +{ + if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (primal_solution == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (num_variables <= 0) { return CUOPT_INVALID_ARGUMENT; } + + solver_settings_t* solver_settings = + static_cast*>(settings); + try { + solver_settings->set_initial_pdlp_primal_solution(primal_solution, num_variables); + } catch (const std::exception& e) { + return CUOPT_INVALID_ARGUMENT; + } + return CUOPT_SUCCESS; +} + +cuopt_int_t cuOptSetInitialDualSolution(cuOptSolverSettings settings, + const cuopt_float_t* dual_solution, + cuopt_int_t num_constraints) +{ + if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (dual_solution == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (num_constraints <= 0) { return CUOPT_INVALID_ARGUMENT; } + + solver_settings_t* solver_settings = + static_cast*>(settings); + try { + solver_settings->set_initial_pdlp_dual_solution(dual_solution, num_constraints); + } catch (const std::exception& e) { + return CUOPT_INVALID_ARGUMENT; + } + return CUOPT_SUCCESS; +} + +cuopt_int_t cuOptAddMIPStart(cuOptSolverSettings settings, + const cuopt_float_t* solution, + cuopt_int_t num_variables) +{ + if (settings == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (solution == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (num_variables <= 0) { return CUOPT_INVALID_ARGUMENT; } + + solver_settings_t* solver_settings = + static_cast*>(settings); + try { + solver_settings->get_mip_settings().add_initial_solution(solution, num_variables); + } catch (const std::exception& e) { + return CUOPT_INVALID_ARGUMENT; + } + return CUOPT_SUCCESS; +} + cuopt_int_t cuOptIsMIP(cuOptOptimizationProblem problem, cuopt_int_t* is_mip_ptr) { if (problem == nullptr) { return CUOPT_INVALID_ARGUMENT; } diff --git a/cpp/tests/linear_programming/CMakeLists.txt b/cpp/tests/linear_programming/CMakeLists.txt index c091751f9..295044355 100644 --- a/cpp/tests/linear_programming/CMakeLists.txt +++ b/cpp/tests/linear_programming/CMakeLists.txt @@ -52,6 +52,10 @@ if (NOT SKIP_C_PYTHON_ADAPTERS) ${CUOPT_PRIVATE_CUDA_LIBS} ) + if(NOT DEFINED INSTALL_TARGET OR "${INSTALL_TARGET}" STREQUAL "") + target_link_options(C_API_TEST PRIVATE -Wl,--enable-new-dtags) + endif() + add_test(NAME C_API_TEST COMMAND C_API_TEST) install( diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_test.c b/cpp/tests/linear_programming/c_api_tests/c_api_test.c index 17f644ab0..ea443e007 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_test.c +++ b/cpp/tests/linear_programming/c_api_tests/c_api_test.c @@ -1200,3 +1200,407 @@ cuOptDestroySolution(&solution); return status; } + +/* Test cuOptWriteProblem: Create a problem, write to MPS, read it back, and solve */ +cuopt_int_t test_write_problem(const char* output_filename) +{ + cuOptOptimizationProblem problem = NULL; + cuOptOptimizationProblem problem_read = NULL; + cuOptSolverSettings settings = NULL; + cuOptSolution solution = NULL; + cuopt_int_t status; + cuopt_int_t termination_status; + cuopt_float_t objective_value; + + /* Create a simple LP: maximize x + 2y subject to x + y <= 10, x,y >= 0 */ + cuopt_int_t num_constraints = 1; + cuopt_int_t num_variables = 2; + cuopt_float_t objective_coefficients[] = {1.0, 2.0}; + cuopt_int_t row_offsets[] = {0, 2}; + cuopt_int_t column_indices[] = {0, 1}; + cuopt_float_t values[] = {1.0, 1.0}; + char constraint_sense[] = {CUOPT_LESS_THAN}; + cuopt_float_t rhs[] = {10.0}; + cuopt_float_t lower_bounds[] = {0.0, 0.0}; + cuopt_float_t upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; + char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; + + status = cuOptCreateProblem(num_constraints, + num_variables, + CUOPT_MAXIMIZE, + 0.0, + objective_coefficients, + row_offsets, + column_indices, + values, + constraint_sense, + rhs, + lower_bounds, + upper_bounds, + variable_types, + &problem); + if (status != CUOPT_SUCCESS) { + printf("Error creating problem: %d\n", status); + goto DONE; + } + + /* Write the problem to MPS file */ + status = cuOptWriteProblem(problem, output_filename, CUOPT_FILE_FORMAT_MPS); + if (status != CUOPT_SUCCESS) { + printf("Error writing problem to MPS: %d\n", status); + goto DONE; + } + printf("Problem written to %s\n", output_filename); + + /* Read the problem back */ + status = cuOptReadProblem(output_filename, &problem_read); + if (status != CUOPT_SUCCESS) { + printf("Error reading problem from MPS: %d\n", status); + goto DONE; + } + printf("Problem read back from %s\n", output_filename); + + /* Create solver settings and solve */ + status = cuOptCreateSolverSettings(&settings); + if (status != CUOPT_SUCCESS) { + printf("Error creating solver settings: %d\n", status); + goto DONE; + } + + status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, CUOPT_METHOD_PDLP); + if (status != CUOPT_SUCCESS) { + printf("Error setting method: %d\n", status); + goto DONE; + } + + status = cuOptSolve(problem_read, settings, &solution); + if (status != CUOPT_SUCCESS) { + printf("Error solving problem: %d\n", status); + goto DONE; + } + + status = cuOptGetTerminationStatus(solution, &termination_status); + if (status != CUOPT_SUCCESS) { + printf("Error getting termination status: %d\n", status); + goto DONE; + } + + status = cuOptGetObjectiveValue(solution, &objective_value); + if (status != CUOPT_SUCCESS) { + printf("Error getting objective value: %d\n", status); + goto DONE; + } + + printf("Termination status: %d, Objective: %f\n", termination_status, objective_value); + + if (termination_status != CUOPT_TERIMINATION_STATUS_OPTIMAL) { + printf("Expected optimal status\n"); + status = -1; + goto DONE; + } + + /* Optimal solution: y = 10, x = 0, objective = 20 */ + if (objective_value < 19.9 || objective_value > 20.1) { + printf("Expected objective ~20, got %f\n", objective_value); + status = -1; + goto DONE; + } + + printf("Write problem test passed\n"); + +DONE: + cuOptDestroyProblem(&problem); + cuOptDestroyProblem(&problem_read); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + return status; +} + +/* Test cuOptSetInitialPrimalSolution: Set initial primal solution for LP */ +cuopt_int_t test_initial_primal_solution(cuopt_int_t* termination_status_ptr, + cuopt_float_t* objective_ptr) +{ + cuOptOptimizationProblem problem = NULL; + cuOptSolverSettings settings = NULL; + cuOptSolution solution = NULL; + cuopt_int_t status; + cuopt_int_t num_vars; + + /* Create a simple LP: maximize x + 2y subject to x + y <= 10, x,y >= 0 */ + cuopt_int_t num_constraints = 1; + cuopt_int_t num_variables = 2; + cuopt_float_t objective_coefficients[] = {1.0, 2.0}; + cuopt_int_t row_offsets[] = {0, 2}; + cuopt_int_t column_indices[] = {0, 1}; + cuopt_float_t values[] = {1.0, 1.0}; + char constraint_sense[] = {CUOPT_LESS_THAN}; + cuopt_float_t rhs[] = {10.0}; + cuopt_float_t lower_bounds[] = {0.0, 0.0}; + cuopt_float_t upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; + char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; + + /* Initial primal solution (feasible but not optimal) */ + cuopt_float_t initial_primal[] = {5.0, 5.0}; + + status = cuOptCreateProblem(num_constraints, + num_variables, + CUOPT_MAXIMIZE, + 0.0, + objective_coefficients, + row_offsets, + column_indices, + values, + constraint_sense, + rhs, + lower_bounds, + upper_bounds, + variable_types, + &problem); + if (status != CUOPT_SUCCESS) { + printf("Error creating problem: %d\n", status); + goto DONE; + } + + status = cuOptGetNumVariables(problem, &num_vars); + if (status != CUOPT_SUCCESS) { + printf("Error getting num variables: %d\n", status); + goto DONE; + } + + status = cuOptCreateSolverSettings(&settings); + if (status != CUOPT_SUCCESS) { + printf("Error creating solver settings: %d\n", status); + goto DONE; + } + + /* Set PDLP method since initial solutions are for PDLP */ + status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, CUOPT_METHOD_PDLP); + if (status != CUOPT_SUCCESS) { + printf("Error setting method: %d\n", status); + goto DONE; + } + + /* Set initial primal solution */ + status = cuOptSetInitialPrimalSolution(settings, initial_primal, num_vars); + if (status != CUOPT_SUCCESS) { + printf("Error setting initial primal solution: %d\n", status); + goto DONE; + } + printf("Initial primal solution set\n"); + + status = cuOptSolve(problem, settings, &solution); + if (status != CUOPT_SUCCESS) { + printf("Error solving problem: %d\n", status); + goto DONE; + } + + status = cuOptGetTerminationStatus(solution, termination_status_ptr); + if (status != CUOPT_SUCCESS) { + printf("Error getting termination status: %d\n", status); + goto DONE; + } + + status = cuOptGetObjectiveValue(solution, objective_ptr); + if (status != CUOPT_SUCCESS) { + printf("Error getting objective value: %d\n", status); + goto DONE; + } + + printf("Initial primal solution test completed\n"); + +DONE: + cuOptDestroyProblem(&problem); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + return status; +} + +/* Test cuOptSetInitialDualSolution: Set initial dual solution for LP */ +cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, + cuopt_float_t* objective_ptr) +{ + cuOptOptimizationProblem problem = NULL; + cuOptSolverSettings settings = NULL; + cuOptSolution solution = NULL; + cuopt_int_t status; + cuopt_int_t num_cons; + + /* Create a simple LP: maximize x + 2y subject to x + y <= 10, x,y >= 0 */ + cuopt_int_t num_constraints = 1; + cuopt_int_t num_variables = 2; + cuopt_float_t objective_coefficients[] = {1.0, 2.0}; + cuopt_int_t row_offsets[] = {0, 2}; + cuopt_int_t column_indices[] = {0, 1}; + cuopt_float_t values[] = {1.0, 1.0}; + char constraint_sense[] = {CUOPT_LESS_THAN}; + cuopt_float_t rhs[] = {10.0}; + cuopt_float_t lower_bounds[] = {0.0, 0.0}; + cuopt_float_t upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; + char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; + + /* Initial dual solution */ + cuopt_float_t initial_dual[] = {1.0}; + + status = cuOptCreateProblem(num_constraints, + num_variables, + CUOPT_MAXIMIZE, + 0.0, + objective_coefficients, + row_offsets, + column_indices, + values, + constraint_sense, + rhs, + lower_bounds, + upper_bounds, + variable_types, + &problem); + if (status != CUOPT_SUCCESS) { + printf("Error creating problem: %d\n", status); + goto DONE; + } + + status = cuOptGetNumConstraints(problem, &num_cons); + if (status != CUOPT_SUCCESS) { + printf("Error getting num constraints: %d\n", status); + goto DONE; + } + + status = cuOptCreateSolverSettings(&settings); + if (status != CUOPT_SUCCESS) { + printf("Error creating solver settings: %d\n", status); + goto DONE; + } + + /* Set PDLP method since initial solutions are for PDLP */ + status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, CUOPT_METHOD_PDLP); + if (status != CUOPT_SUCCESS) { + printf("Error setting method: %d\n", status); + goto DONE; + } + + /* Set initial dual solution */ + status = cuOptSetInitialDualSolution(settings, initial_dual, num_cons); + if (status != CUOPT_SUCCESS) { + printf("Error setting initial dual solution: %d\n", status); + goto DONE; + } + printf("Initial dual solution set\n"); + + status = cuOptSolve(problem, settings, &solution); + if (status != CUOPT_SUCCESS) { + printf("Error solving problem: %d\n", status); + goto DONE; + } + + status = cuOptGetTerminationStatus(solution, termination_status_ptr); + if (status != CUOPT_SUCCESS) { + printf("Error getting termination status: %d\n", status); + goto DONE; + } + + status = cuOptGetObjectiveValue(solution, objective_ptr); + if (status != CUOPT_SUCCESS) { + printf("Error getting objective value: %d\n", status); + goto DONE; + } + + printf("Initial dual solution test completed\n"); + +DONE: + cuOptDestroyProblem(&problem); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + return status; +} + +/* Test cuOptAddMIPStart: Add initial solution hint for MIP solving */ +cuopt_int_t test_mip_start(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr) +{ + cuOptOptimizationProblem problem = NULL; + cuOptSolverSettings settings = NULL; + cuOptSolution solution = NULL; + cuopt_int_t status; + cuopt_int_t num_vars; + + /* Create a simple MIP: maximize x + 2y subject to x + y <= 10, x,y >= 0, x,y integer */ + cuopt_int_t num_constraints = 1; + cuopt_int_t num_variables = 2; + cuopt_float_t objective_coefficients[] = {1.0, 2.0}; + cuopt_int_t row_offsets[] = {0, 2}; + cuopt_int_t column_indices[] = {0, 1}; + cuopt_float_t values[] = {1.0, 1.0}; + char constraint_sense[] = {CUOPT_LESS_THAN}; + cuopt_float_t rhs[] = {10.0}; + cuopt_float_t lower_bounds[] = {0.0, 0.0}; + cuopt_float_t upper_bounds[] = {10.0, 10.0}; + char variable_types[] = {CUOPT_INTEGER, CUOPT_INTEGER}; + + /* MIP start: a feasible solution (x=0, y=10) which is also optimal */ + cuopt_float_t mip_start[] = {0.0, 10.0}; + + status = cuOptCreateProblem(num_constraints, + num_variables, + CUOPT_MAXIMIZE, + 0.0, + objective_coefficients, + row_offsets, + column_indices, + values, + constraint_sense, + rhs, + lower_bounds, + upper_bounds, + variable_types, + &problem); + if (status != CUOPT_SUCCESS) { + printf("Error creating problem: %d\n", status); + goto DONE; + } + + status = cuOptGetNumVariables(problem, &num_vars); + if (status != CUOPT_SUCCESS) { + printf("Error getting num variables: %d\n", status); + goto DONE; + } + + status = cuOptCreateSolverSettings(&settings); + if (status != CUOPT_SUCCESS) { + printf("Error creating solver settings: %d\n", status); + goto DONE; + } + + /* Add MIP start */ + status = cuOptAddMIPStart(settings, mip_start, num_vars); + if (status != CUOPT_SUCCESS) { + printf("Error adding MIP start: %d\n", status); + goto DONE; + } + printf("MIP start added\n"); + + status = cuOptSolve(problem, settings, &solution); + if (status != CUOPT_SUCCESS) { + printf("Error solving problem: %d\n", status); + goto DONE; + } + + status = cuOptGetTerminationStatus(solution, termination_status_ptr); + if (status != CUOPT_SUCCESS) { + printf("Error getting termination status: %d\n", status); + goto DONE; + } + + status = cuOptGetObjectiveValue(solution, objective_ptr); + if (status != CUOPT_SUCCESS) { + printf("Error getting objective value: %d\n", status); + goto DONE; + } + + printf("MIP start test completed\n"); + +DONE: + cuOptDestroyProblem(&problem); + cuOptDestroySolverSettings(&settings); + cuOptDestroySolution(&solution); + return status; +} diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp index af1295298..b4f3b678b 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp @@ -14,6 +14,8 @@ #include +#include + TEST(c_api, int_size) { EXPECT_EQ(test_int_size(), sizeof(int32_t)); } TEST(c_api, float_size) { EXPECT_EQ(test_float_size(), sizeof(double)); } @@ -128,3 +130,37 @@ TEST(c_api, test_quadratic_ranged_problem) EXPECT_EQ(termination_status, (int)CUOPT_TERIMINATION_STATUS_OPTIMAL); EXPECT_NEAR(objective, -32.0, 1e-3); } + +TEST(c_api, test_write_problem) +{ + std::string temp_file = std::filesystem::temp_directory_path().string() + "/c_api_test_write.mps"; + EXPECT_EQ(test_write_problem(temp_file.c_str()), CUOPT_SUCCESS); + std::filesystem::remove(temp_file); +} + +TEST(c_api, test_initial_primal_solution) +{ + cuopt_int_t termination_status; + cuopt_float_t objective; + EXPECT_EQ(test_initial_primal_solution(&termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); + EXPECT_NEAR(objective, 20.0, 1e-2); +} + +TEST(c_api, test_initial_dual_solution) +{ + cuopt_int_t termination_status; + cuopt_float_t objective; + EXPECT_EQ(test_initial_dual_solution(&termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); + EXPECT_NEAR(objective, 20.0, 1e-2); +} + +TEST(c_api, test_mip_start) +{ + cuopt_int_t termination_status; + cuopt_float_t objective; + EXPECT_EQ(test_mip_start(&termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); + EXPECT_NEAR(objective, 20.0, 1e-2); +} diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h index 5726c3a99..046ed8ded 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h @@ -34,6 +34,12 @@ cuopt_int_t test_quadratic_problem(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); cuopt_int_t test_quadratic_ranged_problem(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); +cuopt_int_t test_write_problem(const char* output_filename); +cuopt_int_t test_initial_primal_solution(cuopt_int_t* termination_status_ptr, + cuopt_float_t* objective_ptr); +cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, + cuopt_float_t* objective_ptr); +cuopt_int_t test_mip_start(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); #ifdef __cplusplus } From d39f23123be1c0a4d577d1d66c74c1cc3c496c64 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 16 Dec 2025 16:29:15 +0000 Subject: [PATCH 2/7] cleanup --- .../c_api_tests/c_api_test.c | 213 +++++---------- .../c_api_tests/c_api_tests.cpp | 255 +++++++++--------- .../c_api_tests/c_api_tests.h | 12 +- 3 files changed, 210 insertions(+), 270 deletions(-) diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_test.c b/cpp/tests/linear_programming/c_api_tests/c_api_test.c index ea443e007..9186d5cac 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_test.c +++ b/cpp/tests/linear_programming/c_api_tests/c_api_test.c @@ -1201,8 +1201,7 @@ cuOptDestroySolution(&solution); return status; } -/* Test cuOptWriteProblem: Create a problem, write to MPS, read it back, and solve */ -cuopt_int_t test_write_problem(const char* output_filename) +cuopt_int_t test_write_problem(const char* input_filename, const char* output_filename) { cuOptOptimizationProblem problem = NULL; cuOptOptimizationProblem problem_read = NULL; @@ -1212,35 +1211,10 @@ cuopt_int_t test_write_problem(const char* output_filename) cuopt_int_t termination_status; cuopt_float_t objective_value; - /* Create a simple LP: maximize x + 2y subject to x + y <= 10, x,y >= 0 */ - cuopt_int_t num_constraints = 1; - cuopt_int_t num_variables = 2; - cuopt_float_t objective_coefficients[] = {1.0, 2.0}; - cuopt_int_t row_offsets[] = {0, 2}; - cuopt_int_t column_indices[] = {0, 1}; - cuopt_float_t values[] = {1.0, 1.0}; - char constraint_sense[] = {CUOPT_LESS_THAN}; - cuopt_float_t rhs[] = {10.0}; - cuopt_float_t lower_bounds[] = {0.0, 0.0}; - cuopt_float_t upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; - char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; - - status = cuOptCreateProblem(num_constraints, - num_variables, - CUOPT_MAXIMIZE, - 0.0, - objective_coefficients, - row_offsets, - column_indices, - values, - constraint_sense, - rhs, - lower_bounds, - upper_bounds, - variable_types, - &problem); + /* Read the input problem */ + status = cuOptReadProblem(input_filename, &problem); if (status != CUOPT_SUCCESS) { - printf("Error creating problem: %d\n", status); + printf("Error reading problem from %s: %d\n", input_filename, status); goto DONE; } @@ -1260,7 +1234,6 @@ cuopt_int_t test_write_problem(const char* output_filename) } printf("Problem read back from %s\n", output_filename); - /* Create solver settings and solve */ status = cuOptCreateSolverSettings(&settings); if (status != CUOPT_SUCCESS) { printf("Error creating solver settings: %d\n", status); @@ -1299,13 +1272,6 @@ cuopt_int_t test_write_problem(const char* output_filename) goto DONE; } - /* Optimal solution: y = 10, x = 0, objective = 20 */ - if (objective_value < 19.9 || objective_value > 20.1) { - printf("Expected objective ~20, got %f\n", objective_value); - status = -1; - goto DONE; - } - printf("Write problem test passed\n"); DONE: @@ -1316,8 +1282,8 @@ cuopt_int_t test_write_problem(const char* output_filename) return status; } -/* Test cuOptSetInitialPrimalSolution: Set initial primal solution for LP */ -cuopt_int_t test_initial_primal_solution(cuopt_int_t* termination_status_ptr, +cuopt_int_t test_initial_primal_solution(const char* filename, + cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr) { cuOptOptimizationProblem problem = NULL; @@ -1325,39 +1291,12 @@ cuopt_int_t test_initial_primal_solution(cuopt_int_t* termination_status_ptr, cuOptSolution solution = NULL; cuopt_int_t status; cuopt_int_t num_vars; + cuopt_int_t i; + cuopt_float_t* initial_primal = NULL; - /* Create a simple LP: maximize x + 2y subject to x + y <= 10, x,y >= 0 */ - cuopt_int_t num_constraints = 1; - cuopt_int_t num_variables = 2; - cuopt_float_t objective_coefficients[] = {1.0, 2.0}; - cuopt_int_t row_offsets[] = {0, 2}; - cuopt_int_t column_indices[] = {0, 1}; - cuopt_float_t values[] = {1.0, 1.0}; - char constraint_sense[] = {CUOPT_LESS_THAN}; - cuopt_float_t rhs[] = {10.0}; - cuopt_float_t lower_bounds[] = {0.0, 0.0}; - cuopt_float_t upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; - char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; - - /* Initial primal solution (feasible but not optimal) */ - cuopt_float_t initial_primal[] = {5.0, 5.0}; - - status = cuOptCreateProblem(num_constraints, - num_variables, - CUOPT_MAXIMIZE, - 0.0, - objective_coefficients, - row_offsets, - column_indices, - values, - constraint_sense, - rhs, - lower_bounds, - upper_bounds, - variable_types, - &problem); + status = cuOptReadProblem(filename, &problem); if (status != CUOPT_SUCCESS) { - printf("Error creating problem: %d\n", status); + printf("Error reading problem from %s: %d\n", filename, status); goto DONE; } @@ -1367,26 +1306,33 @@ cuopt_int_t test_initial_primal_solution(cuopt_int_t* termination_status_ptr, goto DONE; } + initial_primal = (cuopt_float_t*)malloc(num_vars * sizeof(cuopt_float_t)); + if (initial_primal == NULL) { + printf("Error allocating memory for initial primal solution\n"); + status = -1; + goto DONE; + } + for (i = 0; i < num_vars; i++) { + initial_primal[i] = 1.0; + } + status = cuOptCreateSolverSettings(&settings); if (status != CUOPT_SUCCESS) { printf("Error creating solver settings: %d\n", status); goto DONE; } - /* Set PDLP method since initial solutions are for PDLP */ status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, CUOPT_METHOD_PDLP); if (status != CUOPT_SUCCESS) { printf("Error setting method: %d\n", status); goto DONE; } - /* Set initial primal solution */ status = cuOptSetInitialPrimalSolution(settings, initial_primal, num_vars); if (status != CUOPT_SUCCESS) { printf("Error setting initial primal solution: %d\n", status); goto DONE; } - printf("Initial primal solution set\n"); status = cuOptSolve(problem, settings, &solution); if (status != CUOPT_SUCCESS) { @@ -1406,17 +1352,19 @@ cuopt_int_t test_initial_primal_solution(cuopt_int_t* termination_status_ptr, goto DONE; } - printf("Initial primal solution test completed\n"); + printf("Initial primal solution test: status=%d, objective=%f\n", + *termination_status_ptr, *objective_ptr); DONE: + if (initial_primal != NULL) free(initial_primal); cuOptDestroyProblem(&problem); cuOptDestroySolverSettings(&settings); cuOptDestroySolution(&solution); return status; } -/* Test cuOptSetInitialDualSolution: Set initial dual solution for LP */ -cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, +cuopt_int_t test_initial_dual_solution(const char* filename, + cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr) { cuOptOptimizationProblem problem = NULL; @@ -1424,39 +1372,12 @@ cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, cuOptSolution solution = NULL; cuopt_int_t status; cuopt_int_t num_cons; + cuopt_int_t i; + cuopt_float_t* initial_dual = NULL; - /* Create a simple LP: maximize x + 2y subject to x + y <= 10, x,y >= 0 */ - cuopt_int_t num_constraints = 1; - cuopt_int_t num_variables = 2; - cuopt_float_t objective_coefficients[] = {1.0, 2.0}; - cuopt_int_t row_offsets[] = {0, 2}; - cuopt_int_t column_indices[] = {0, 1}; - cuopt_float_t values[] = {1.0, 1.0}; - char constraint_sense[] = {CUOPT_LESS_THAN}; - cuopt_float_t rhs[] = {10.0}; - cuopt_float_t lower_bounds[] = {0.0, 0.0}; - cuopt_float_t upper_bounds[] = {CUOPT_INFINITY, CUOPT_INFINITY}; - char variable_types[] = {CUOPT_CONTINUOUS, CUOPT_CONTINUOUS}; - - /* Initial dual solution */ - cuopt_float_t initial_dual[] = {1.0}; - - status = cuOptCreateProblem(num_constraints, - num_variables, - CUOPT_MAXIMIZE, - 0.0, - objective_coefficients, - row_offsets, - column_indices, - values, - constraint_sense, - rhs, - lower_bounds, - upper_bounds, - variable_types, - &problem); + status = cuOptReadProblem(filename, &problem); if (status != CUOPT_SUCCESS) { - printf("Error creating problem: %d\n", status); + printf("Error reading problem from %s: %d\n", filename, status); goto DONE; } @@ -1466,6 +1387,17 @@ cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, goto DONE; } + /* Create initial dual solution: all 1.0 (same as PDLP test) */ + initial_dual = (cuopt_float_t*)malloc(num_cons * sizeof(cuopt_float_t)); + if (initial_dual == NULL) { + printf("Error allocating memory for initial dual solution\n"); + status = -1; + goto DONE; + } + for (i = 0; i < num_cons; i++) { + initial_dual[i] = 1.0; + } + status = cuOptCreateSolverSettings(&settings); if (status != CUOPT_SUCCESS) { printf("Error creating solver settings: %d\n", status); @@ -1485,7 +1417,6 @@ cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, printf("Error setting initial dual solution: %d\n", status); goto DONE; } - printf("Initial dual solution set\n"); status = cuOptSolve(problem, settings, &solution); if (status != CUOPT_SUCCESS) { @@ -1505,56 +1436,36 @@ cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, goto DONE; } - printf("Initial dual solution test completed\n"); + printf("Initial dual solution test: status=%d, objective=%f\n", + *termination_status_ptr, *objective_ptr); DONE: + if (initial_dual != NULL) free(initial_dual); cuOptDestroyProblem(&problem); cuOptDestroySolverSettings(&settings); cuOptDestroySolution(&solution); return status; } -/* Test cuOptAddMIPStart: Add initial solution hint for MIP solving */ -cuopt_int_t test_mip_start(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr) +/* Test cuOptAddMIPStart: Add initial solution hint for MIP solving + * Uses a MIP problem file and provides an initial solution + */ +cuopt_int_t test_mip_start(const char* filename, + cuopt_int_t* termination_status_ptr, + cuopt_float_t* objective_ptr) { cuOptOptimizationProblem problem = NULL; cuOptSolverSettings settings = NULL; cuOptSolution solution = NULL; cuopt_int_t status; cuopt_int_t num_vars; + cuopt_int_t i; + cuopt_float_t* mip_start = NULL; - /* Create a simple MIP: maximize x + 2y subject to x + y <= 10, x,y >= 0, x,y integer */ - cuopt_int_t num_constraints = 1; - cuopt_int_t num_variables = 2; - cuopt_float_t objective_coefficients[] = {1.0, 2.0}; - cuopt_int_t row_offsets[] = {0, 2}; - cuopt_int_t column_indices[] = {0, 1}; - cuopt_float_t values[] = {1.0, 1.0}; - char constraint_sense[] = {CUOPT_LESS_THAN}; - cuopt_float_t rhs[] = {10.0}; - cuopt_float_t lower_bounds[] = {0.0, 0.0}; - cuopt_float_t upper_bounds[] = {10.0, 10.0}; - char variable_types[] = {CUOPT_INTEGER, CUOPT_INTEGER}; - - /* MIP start: a feasible solution (x=0, y=10) which is also optimal */ - cuopt_float_t mip_start[] = {0.0, 10.0}; - - status = cuOptCreateProblem(num_constraints, - num_variables, - CUOPT_MAXIMIZE, - 0.0, - objective_coefficients, - row_offsets, - column_indices, - values, - constraint_sense, - rhs, - lower_bounds, - upper_bounds, - variable_types, - &problem); + /* Read the MIP problem from file */ + status = cuOptReadProblem(filename, &problem); if (status != CUOPT_SUCCESS) { - printf("Error creating problem: %d\n", status); + printf("Error reading problem from %s: %d\n", filename, status); goto DONE; } @@ -1564,6 +1475,17 @@ cuopt_int_t test_mip_start(cuopt_int_t* termination_status_ptr, cuopt_float_t* o goto DONE; } + /* Create MIP start: all zeros as a starting point */ + mip_start = (cuopt_float_t*)malloc(num_vars * sizeof(cuopt_float_t)); + if (mip_start == NULL) { + printf("Error allocating memory for MIP start\n"); + status = -1; + goto DONE; + } + for (i = 0; i < num_vars; i++) { + mip_start[i] = 0.0; + } + status = cuOptCreateSolverSettings(&settings); if (status != CUOPT_SUCCESS) { printf("Error creating solver settings: %d\n", status); @@ -1576,7 +1498,6 @@ cuopt_int_t test_mip_start(cuopt_int_t* termination_status_ptr, cuopt_float_t* o printf("Error adding MIP start: %d\n", status); goto DONE; } - printf("MIP start added\n"); status = cuOptSolve(problem, settings, &solution); if (status != CUOPT_SUCCESS) { @@ -1596,9 +1517,11 @@ cuopt_int_t test_mip_start(cuopt_int_t* termination_status_ptr, cuopt_float_t* o goto DONE; } - printf("MIP start test completed\n"); + printf("MIP start test: status=%d, objective=%f\n", + *termination_status_ptr, *objective_ptr); DONE: + if (mip_start != NULL) free(mip_start); cuOptDestroyProblem(&problem); cuOptDestroySolverSettings(&settings); cuOptDestroySolution(&solution); diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp index b4f3b678b..8ecb403cc 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp @@ -16,151 +16,164 @@ #include -TEST(c_api, int_size) { EXPECT_EQ(test_int_size(), sizeof(int32_t)); } - -TEST(c_api, float_size) { EXPECT_EQ(test_float_size(), sizeof(double)); } - -TEST(c_api, afiro) -{ - const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; - int termination_status; - EXPECT_EQ(solve_mps_file(filename.c_str(), 60, CUOPT_INFINITY, &termination_status), - CUOPT_SUCCESS); - EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); -} - -// Test both LP and MIP codepaths -class TimeLimitTestFixture : public ::testing::TestWithParam> { -}; -TEST_P(TimeLimitTestFixture, time_limit) -{ - const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + std::get<0>(GetParam()); - double target_solve_time = std::get<1>(GetParam()); - int method = std::get<2>(GetParam()); - int termination_status; - double solve_time = std::numeric_limits::quiet_NaN(); - EXPECT_EQ(solve_mps_file(filename.c_str(), - target_solve_time, - CUOPT_INFINITY, - &termination_status, - &solve_time, - method), - CUOPT_SUCCESS); - EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_TIME_LIMIT); - - // Dual simplex is spending some time for factorizing the basis, and this computation does not - // check for time limit - double excess_allowed_time = 3.0; - EXPECT_NEAR(solve_time, target_solve_time, excess_allowed_time); -} -INSTANTIATE_TEST_SUITE_P( - c_api, - TimeLimitTestFixture, - ::testing::Values( - std::make_tuple("/linear_programming/square41/square41.mps", - 5, - CUOPT_METHOD_DUAL_SIMPLEX), // LP, Dual Simplex - std::make_tuple("/linear_programming/square41/square41.mps", 5, CUOPT_METHOD_PDLP), // LP, PDLP - std::make_tuple("/mip/supportcase22.mps", 15, CUOPT_METHOD_DUAL_SIMPLEX) // MIP - )); - -TEST(c_api, iteration_limit) -{ - const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; - int termination_status; - EXPECT_EQ(solve_mps_file(filename.c_str(), 60, 1, &termination_status), CUOPT_SUCCESS); - EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT); -} - -TEST(c_api, solve_time_bb_preemption) -{ - const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + "/mip/" + "bb_optimality.mps"; - int termination_status; - double solve_time = std::numeric_limits::quiet_NaN(); - EXPECT_EQ(solve_mps_file(filename.c_str(), 5, CUOPT_INFINITY, &termination_status, &solve_time), - CUOPT_SUCCESS); - EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_GT(solve_time, 0); // solve time should not be equal to 0, even on very simple instances - // solved by B&B before the diversity solver has time to run -} - -TEST(c_api, bad_parameter_name) { EXPECT_EQ(test_bad_parameter_name(), CUOPT_INVALID_ARGUMENT); } - -TEST(c_api, burglar) { EXPECT_EQ(burglar_problem(), CUOPT_SUCCESS); } - -TEST(c_api, test_missing_file) { EXPECT_EQ(test_missing_file(), CUOPT_MPS_FILE_ERROR); } - -TEST(c_api, test_infeasible_problem) { EXPECT_EQ(test_infeasible_problem(), CUOPT_SUCCESS); } - -TEST(c_api, test_ranged_problem) -{ - cuopt_int_t termination_status; - cuopt_float_t objective; - EXPECT_EQ(test_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); - EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, 32.0, 1e-3); -} - -TEST(c_api, test_invalid_bounds) -{ - // Test LP codepath - EXPECT_EQ(test_invalid_bounds(false), CUOPT_SUCCESS); - // Test MIP codepath - EXPECT_EQ(test_invalid_bounds(true), CUOPT_SUCCESS); -} - -TEST(c_api, test_quadratic_problem) -{ - cuopt_int_t termination_status; - cuopt_float_t objective; - EXPECT_EQ(test_quadratic_problem(&termination_status, &objective), CUOPT_SUCCESS); - EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, -32.0, 1e-3); -} - -TEST(c_api, test_quadratic_ranged_problem) -{ - cuopt_int_t termination_status; - cuopt_float_t objective; - EXPECT_EQ(test_quadratic_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); - EXPECT_EQ(termination_status, (int)CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, -32.0, 1e-3); -} +// TEST(c_api, int_size) { EXPECT_EQ(test_int_size(), sizeof(int32_t)); } + +// TEST(c_api, float_size) { EXPECT_EQ(test_float_size(), sizeof(double)); } + +// TEST(c_api, afiro) +// { +// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); +// std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; +// int termination_status; +// EXPECT_EQ(solve_mps_file(filename.c_str(), 60, CUOPT_INFINITY, &termination_status), +// CUOPT_SUCCESS); +// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); +// } + +// // Test both LP and MIP codepaths +// class TimeLimitTestFixture : public ::testing::TestWithParam> { +// }; +// TEST_P(TimeLimitTestFixture, time_limit) +// { +// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); +// std::string filename = rapidsDatasetRootDir + std::get<0>(GetParam()); +// double target_solve_time = std::get<1>(GetParam()); +// int method = std::get<2>(GetParam()); +// int termination_status; +// double solve_time = std::numeric_limits::quiet_NaN(); +// EXPECT_EQ(solve_mps_file(filename.c_str(), +// target_solve_time, +// CUOPT_INFINITY, +// &termination_status, +// &solve_time, +// method), +// CUOPT_SUCCESS); +// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_TIME_LIMIT); + +// // Dual simplex is spending some time for factorizing the basis, and this computation does not +// // check for time limit +// double excess_allowed_time = 3.0; +// EXPECT_NEAR(solve_time, target_solve_time, excess_allowed_time); +// } +// INSTANTIATE_TEST_SUITE_P( +// c_api, +// TimeLimitTestFixture, +// ::testing::Values( +// std::make_tuple("/linear_programming/square41/square41.mps", +// 5, +// CUOPT_METHOD_DUAL_SIMPLEX), // LP, Dual Simplex +// std::make_tuple("/linear_programming/square41/square41.mps", 5, CUOPT_METHOD_PDLP), // LP, +// PDLP std::make_tuple("/mip/supportcase22.mps", 15, CUOPT_METHOD_DUAL_SIMPLEX) // +// MIP +// )); + +// TEST(c_api, iteration_limit) +// { +// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); +// std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; +// int termination_status; +// EXPECT_EQ(solve_mps_file(filename.c_str(), 60, 1, &termination_status), CUOPT_SUCCESS); +// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT); +// } + +// TEST(c_api, solve_time_bb_preemption) +// { +// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); +// std::string filename = rapidsDatasetRootDir + "/mip/" + "bb_optimality.mps"; +// int termination_status; +// double solve_time = std::numeric_limits::quiet_NaN(); +// EXPECT_EQ(solve_mps_file(filename.c_str(), 5, CUOPT_INFINITY, &termination_status, +// &solve_time), +// CUOPT_SUCCESS); +// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); +// EXPECT_GT(solve_time, 0); // solve time should not be equal to 0, even on very simple +// instances +// // solved by B&B before the diversity solver has time to run +// } + +// TEST(c_api, bad_parameter_name) { EXPECT_EQ(test_bad_parameter_name(), CUOPT_INVALID_ARGUMENT); } + +// TEST(c_api, burglar) { EXPECT_EQ(burglar_problem(), CUOPT_SUCCESS); } + +// TEST(c_api, test_missing_file) { EXPECT_EQ(test_missing_file(), CUOPT_MPS_FILE_ERROR); } + +// TEST(c_api, test_infeasible_problem) { EXPECT_EQ(test_infeasible_problem(), CUOPT_SUCCESS); } + +// TEST(c_api, test_ranged_problem) +// { +// cuopt_int_t termination_status; +// cuopt_float_t objective; +// EXPECT_EQ(test_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); +// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); +// EXPECT_NEAR(objective, 32.0, 1e-3); +// } + +// TEST(c_api, test_invalid_bounds) +// { +// // Test LP codepath +// EXPECT_EQ(test_invalid_bounds(false), CUOPT_SUCCESS); +// // Test MIP codepath +// EXPECT_EQ(test_invalid_bounds(true), CUOPT_SUCCESS); +// } + +// TEST(c_api, test_quadratic_problem) +// { +// cuopt_int_t termination_status; +// cuopt_float_t objective; +// EXPECT_EQ(test_quadratic_problem(&termination_status, &objective), CUOPT_SUCCESS); +// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); +// EXPECT_NEAR(objective, -32.0, 1e-3); +// } + +// TEST(c_api, test_quadratic_ranged_problem) +// { +// cuopt_int_t termination_status; +// cuopt_float_t objective; +// EXPECT_EQ(test_quadratic_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); +// EXPECT_EQ(termination_status, (int)CUOPT_TERIMINATION_STATUS_OPTIMAL); +// EXPECT_NEAR(objective, -32.0, 1e-3); +// } TEST(c_api, test_write_problem) { + const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); + std::string input_file = rapidsDatasetRootDir + "/linear_programming/afiro_original.mps"; std::string temp_file = std::filesystem::temp_directory_path().string() + "/c_api_test_write.mps"; - EXPECT_EQ(test_write_problem(temp_file.c_str()), CUOPT_SUCCESS); + EXPECT_EQ(test_write_problem(input_file.c_str(), temp_file.c_str()), CUOPT_SUCCESS); std::filesystem::remove(temp_file); } TEST(c_api, test_initial_primal_solution) { + const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); + std::string filename = rapidsDatasetRootDir + "/linear_programming/afiro_original.mps"; cuopt_int_t termination_status; cuopt_float_t objective; - EXPECT_EQ(test_initial_primal_solution(&termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(test_initial_primal_solution(filename.c_str(), &termination_status, &objective), + CUOPT_SUCCESS); EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, 20.0, 1e-2); + EXPECT_NEAR(objective, -464.0, 1e-2); } TEST(c_api, test_initial_dual_solution) { + const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); + std::string filename = rapidsDatasetRootDir + "/linear_programming/afiro_original.mps"; cuopt_int_t termination_status; cuopt_float_t objective; - EXPECT_EQ(test_initial_dual_solution(&termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(test_initial_dual_solution(filename.c_str(), &termination_status, &objective), + CUOPT_SUCCESS); EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, 20.0, 1e-2); + EXPECT_NEAR(objective, -464.0, 1e-2); } TEST(c_api, test_mip_start) { + const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); + std::string filename = rapidsDatasetRootDir + "/mip/bb_optimality.mps"; cuopt_int_t termination_status; cuopt_float_t objective; - EXPECT_EQ(test_mip_start(&termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(test_mip_start(filename.c_str(), &termination_status, &objective), CUOPT_SUCCESS); EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, 20.0, 1e-2); } diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h index 046ed8ded..a2c308220 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h @@ -34,12 +34,16 @@ cuopt_int_t test_quadratic_problem(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); cuopt_int_t test_quadratic_ranged_problem(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); -cuopt_int_t test_write_problem(const char* output_filename); -cuopt_int_t test_initial_primal_solution(cuopt_int_t* termination_status_ptr, +cuopt_int_t test_write_problem(const char* input_filename, const char* output_filename); +cuopt_int_t test_initial_primal_solution(const char* filename, + cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); -cuopt_int_t test_initial_dual_solution(cuopt_int_t* termination_status_ptr, +cuopt_int_t test_initial_dual_solution(const char* filename, + cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); -cuopt_int_t test_mip_start(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); +cuopt_int_t test_mip_start(const char* filename, + cuopt_int_t* termination_status_ptr, + cuopt_float_t* objective_ptr); #ifdef __cplusplus } From 7b0739cd6767a91174ff5148b2e93e1b8306942c Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Tue, 16 Dec 2025 17:40:07 +0000 Subject: [PATCH 3/7] cleanup tests --- .../c_api_tests/c_api_test.c | 246 ------------------ .../c_api_tests/c_api_tests.cpp | 242 +++++++---------- .../c_api_tests/c_api_tests.h | 10 - 3 files changed, 101 insertions(+), 397 deletions(-) diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_test.c b/cpp/tests/linear_programming/c_api_tests/c_api_test.c index 9186d5cac..12ac890bc 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_test.c +++ b/cpp/tests/linear_programming/c_api_tests/c_api_test.c @@ -1281,249 +1281,3 @@ cuopt_int_t test_write_problem(const char* input_filename, const char* output_fi cuOptDestroySolution(&solution); return status; } - -cuopt_int_t test_initial_primal_solution(const char* filename, - cuopt_int_t* termination_status_ptr, - cuopt_float_t* objective_ptr) -{ - cuOptOptimizationProblem problem = NULL; - cuOptSolverSettings settings = NULL; - cuOptSolution solution = NULL; - cuopt_int_t status; - cuopt_int_t num_vars; - cuopt_int_t i; - cuopt_float_t* initial_primal = NULL; - - status = cuOptReadProblem(filename, &problem); - if (status != CUOPT_SUCCESS) { - printf("Error reading problem from %s: %d\n", filename, status); - goto DONE; - } - - status = cuOptGetNumVariables(problem, &num_vars); - if (status != CUOPT_SUCCESS) { - printf("Error getting num variables: %d\n", status); - goto DONE; - } - - initial_primal = (cuopt_float_t*)malloc(num_vars * sizeof(cuopt_float_t)); - if (initial_primal == NULL) { - printf("Error allocating memory for initial primal solution\n"); - status = -1; - goto DONE; - } - for (i = 0; i < num_vars; i++) { - initial_primal[i] = 1.0; - } - - status = cuOptCreateSolverSettings(&settings); - if (status != CUOPT_SUCCESS) { - printf("Error creating solver settings: %d\n", status); - goto DONE; - } - - status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, CUOPT_METHOD_PDLP); - if (status != CUOPT_SUCCESS) { - printf("Error setting method: %d\n", status); - goto DONE; - } - - status = cuOptSetInitialPrimalSolution(settings, initial_primal, num_vars); - if (status != CUOPT_SUCCESS) { - printf("Error setting initial primal solution: %d\n", status); - goto DONE; - } - - status = cuOptSolve(problem, settings, &solution); - if (status != CUOPT_SUCCESS) { - printf("Error solving problem: %d\n", status); - goto DONE; - } - - status = cuOptGetTerminationStatus(solution, termination_status_ptr); - if (status != CUOPT_SUCCESS) { - printf("Error getting termination status: %d\n", status); - goto DONE; - } - - status = cuOptGetObjectiveValue(solution, objective_ptr); - if (status != CUOPT_SUCCESS) { - printf("Error getting objective value: %d\n", status); - goto DONE; - } - - printf("Initial primal solution test: status=%d, objective=%f\n", - *termination_status_ptr, *objective_ptr); - -DONE: - if (initial_primal != NULL) free(initial_primal); - cuOptDestroyProblem(&problem); - cuOptDestroySolverSettings(&settings); - cuOptDestroySolution(&solution); - return status; -} - -cuopt_int_t test_initial_dual_solution(const char* filename, - cuopt_int_t* termination_status_ptr, - cuopt_float_t* objective_ptr) -{ - cuOptOptimizationProblem problem = NULL; - cuOptSolverSettings settings = NULL; - cuOptSolution solution = NULL; - cuopt_int_t status; - cuopt_int_t num_cons; - cuopt_int_t i; - cuopt_float_t* initial_dual = NULL; - - status = cuOptReadProblem(filename, &problem); - if (status != CUOPT_SUCCESS) { - printf("Error reading problem from %s: %d\n", filename, status); - goto DONE; - } - - status = cuOptGetNumConstraints(problem, &num_cons); - if (status != CUOPT_SUCCESS) { - printf("Error getting num constraints: %d\n", status); - goto DONE; - } - - /* Create initial dual solution: all 1.0 (same as PDLP test) */ - initial_dual = (cuopt_float_t*)malloc(num_cons * sizeof(cuopt_float_t)); - if (initial_dual == NULL) { - printf("Error allocating memory for initial dual solution\n"); - status = -1; - goto DONE; - } - for (i = 0; i < num_cons; i++) { - initial_dual[i] = 1.0; - } - - status = cuOptCreateSolverSettings(&settings); - if (status != CUOPT_SUCCESS) { - printf("Error creating solver settings: %d\n", status); - goto DONE; - } - - /* Set PDLP method since initial solutions are for PDLP */ - status = cuOptSetIntegerParameter(settings, CUOPT_METHOD, CUOPT_METHOD_PDLP); - if (status != CUOPT_SUCCESS) { - printf("Error setting method: %d\n", status); - goto DONE; - } - - /* Set initial dual solution */ - status = cuOptSetInitialDualSolution(settings, initial_dual, num_cons); - if (status != CUOPT_SUCCESS) { - printf("Error setting initial dual solution: %d\n", status); - goto DONE; - } - - status = cuOptSolve(problem, settings, &solution); - if (status != CUOPT_SUCCESS) { - printf("Error solving problem: %d\n", status); - goto DONE; - } - - status = cuOptGetTerminationStatus(solution, termination_status_ptr); - if (status != CUOPT_SUCCESS) { - printf("Error getting termination status: %d\n", status); - goto DONE; - } - - status = cuOptGetObjectiveValue(solution, objective_ptr); - if (status != CUOPT_SUCCESS) { - printf("Error getting objective value: %d\n", status); - goto DONE; - } - - printf("Initial dual solution test: status=%d, objective=%f\n", - *termination_status_ptr, *objective_ptr); - -DONE: - if (initial_dual != NULL) free(initial_dual); - cuOptDestroyProblem(&problem); - cuOptDestroySolverSettings(&settings); - cuOptDestroySolution(&solution); - return status; -} - -/* Test cuOptAddMIPStart: Add initial solution hint for MIP solving - * Uses a MIP problem file and provides an initial solution - */ -cuopt_int_t test_mip_start(const char* filename, - cuopt_int_t* termination_status_ptr, - cuopt_float_t* objective_ptr) -{ - cuOptOptimizationProblem problem = NULL; - cuOptSolverSettings settings = NULL; - cuOptSolution solution = NULL; - cuopt_int_t status; - cuopt_int_t num_vars; - cuopt_int_t i; - cuopt_float_t* mip_start = NULL; - - /* Read the MIP problem from file */ - status = cuOptReadProblem(filename, &problem); - if (status != CUOPT_SUCCESS) { - printf("Error reading problem from %s: %d\n", filename, status); - goto DONE; - } - - status = cuOptGetNumVariables(problem, &num_vars); - if (status != CUOPT_SUCCESS) { - printf("Error getting num variables: %d\n", status); - goto DONE; - } - - /* Create MIP start: all zeros as a starting point */ - mip_start = (cuopt_float_t*)malloc(num_vars * sizeof(cuopt_float_t)); - if (mip_start == NULL) { - printf("Error allocating memory for MIP start\n"); - status = -1; - goto DONE; - } - for (i = 0; i < num_vars; i++) { - mip_start[i] = 0.0; - } - - status = cuOptCreateSolverSettings(&settings); - if (status != CUOPT_SUCCESS) { - printf("Error creating solver settings: %d\n", status); - goto DONE; - } - - /* Add MIP start */ - status = cuOptAddMIPStart(settings, mip_start, num_vars); - if (status != CUOPT_SUCCESS) { - printf("Error adding MIP start: %d\n", status); - goto DONE; - } - - status = cuOptSolve(problem, settings, &solution); - if (status != CUOPT_SUCCESS) { - printf("Error solving problem: %d\n", status); - goto DONE; - } - - status = cuOptGetTerminationStatus(solution, termination_status_ptr); - if (status != CUOPT_SUCCESS) { - printf("Error getting termination status: %d\n", status); - goto DONE; - } - - status = cuOptGetObjectiveValue(solution, objective_ptr); - if (status != CUOPT_SUCCESS) { - printf("Error getting objective value: %d\n", status); - goto DONE; - } - - printf("MIP start test: status=%d, objective=%f\n", - *termination_status_ptr, *objective_ptr); - -DONE: - if (mip_start != NULL) free(mip_start); - cuOptDestroyProblem(&problem); - cuOptDestroySolverSettings(&settings); - cuOptDestroySolution(&solution); - return status; -} diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp index 8ecb403cc..13d2c99d3 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp @@ -14,166 +14,126 @@ #include -#include - -// TEST(c_api, int_size) { EXPECT_EQ(test_int_size(), sizeof(int32_t)); } - -// TEST(c_api, float_size) { EXPECT_EQ(test_float_size(), sizeof(double)); } - -// TEST(c_api, afiro) -// { -// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); -// std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; -// int termination_status; -// EXPECT_EQ(solve_mps_file(filename.c_str(), 60, CUOPT_INFINITY, &termination_status), -// CUOPT_SUCCESS); -// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); -// } - -// // Test both LP and MIP codepaths -// class TimeLimitTestFixture : public ::testing::TestWithParam> { -// }; -// TEST_P(TimeLimitTestFixture, time_limit) -// { -// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); -// std::string filename = rapidsDatasetRootDir + std::get<0>(GetParam()); -// double target_solve_time = std::get<1>(GetParam()); -// int method = std::get<2>(GetParam()); -// int termination_status; -// double solve_time = std::numeric_limits::quiet_NaN(); -// EXPECT_EQ(solve_mps_file(filename.c_str(), -// target_solve_time, -// CUOPT_INFINITY, -// &termination_status, -// &solve_time, -// method), -// CUOPT_SUCCESS); -// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_TIME_LIMIT); - -// // Dual simplex is spending some time for factorizing the basis, and this computation does not -// // check for time limit -// double excess_allowed_time = 3.0; -// EXPECT_NEAR(solve_time, target_solve_time, excess_allowed_time); -// } -// INSTANTIATE_TEST_SUITE_P( -// c_api, -// TimeLimitTestFixture, -// ::testing::Values( -// std::make_tuple("/linear_programming/square41/square41.mps", -// 5, -// CUOPT_METHOD_DUAL_SIMPLEX), // LP, Dual Simplex -// std::make_tuple("/linear_programming/square41/square41.mps", 5, CUOPT_METHOD_PDLP), // LP, -// PDLP std::make_tuple("/mip/supportcase22.mps", 15, CUOPT_METHOD_DUAL_SIMPLEX) // -// MIP -// )); - -// TEST(c_api, iteration_limit) -// { -// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); -// std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; -// int termination_status; -// EXPECT_EQ(solve_mps_file(filename.c_str(), 60, 1, &termination_status), CUOPT_SUCCESS); -// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT); -// } - -// TEST(c_api, solve_time_bb_preemption) -// { -// const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); -// std::string filename = rapidsDatasetRootDir + "/mip/" + "bb_optimality.mps"; -// int termination_status; -// double solve_time = std::numeric_limits::quiet_NaN(); -// EXPECT_EQ(solve_mps_file(filename.c_str(), 5, CUOPT_INFINITY, &termination_status, -// &solve_time), -// CUOPT_SUCCESS); -// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); -// EXPECT_GT(solve_time, 0); // solve time should not be equal to 0, even on very simple -// instances -// // solved by B&B before the diversity solver has time to run -// } - -// TEST(c_api, bad_parameter_name) { EXPECT_EQ(test_bad_parameter_name(), CUOPT_INVALID_ARGUMENT); } - -// TEST(c_api, burglar) { EXPECT_EQ(burglar_problem(), CUOPT_SUCCESS); } - -// TEST(c_api, test_missing_file) { EXPECT_EQ(test_missing_file(), CUOPT_MPS_FILE_ERROR); } - -// TEST(c_api, test_infeasible_problem) { EXPECT_EQ(test_infeasible_problem(), CUOPT_SUCCESS); } - -// TEST(c_api, test_ranged_problem) -// { -// cuopt_int_t termination_status; -// cuopt_float_t objective; -// EXPECT_EQ(test_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); -// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); -// EXPECT_NEAR(objective, 32.0, 1e-3); -// } - -// TEST(c_api, test_invalid_bounds) -// { -// // Test LP codepath -// EXPECT_EQ(test_invalid_bounds(false), CUOPT_SUCCESS); -// // Test MIP codepath -// EXPECT_EQ(test_invalid_bounds(true), CUOPT_SUCCESS); -// } - -// TEST(c_api, test_quadratic_problem) -// { -// cuopt_int_t termination_status; -// cuopt_float_t objective; -// EXPECT_EQ(test_quadratic_problem(&termination_status, &objective), CUOPT_SUCCESS); -// EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); -// EXPECT_NEAR(objective, -32.0, 1e-3); -// } - -// TEST(c_api, test_quadratic_ranged_problem) -// { -// cuopt_int_t termination_status; -// cuopt_float_t objective; -// EXPECT_EQ(test_quadratic_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); -// EXPECT_EQ(termination_status, (int)CUOPT_TERIMINATION_STATUS_OPTIMAL); -// EXPECT_NEAR(objective, -32.0, 1e-3); -// } +TEST(c_api, int_size) { EXPECT_EQ(test_int_size(), sizeof(int32_t)); } -TEST(c_api, test_write_problem) +TEST(c_api, float_size) { EXPECT_EQ(test_float_size(), sizeof(double)); } + +TEST(c_api, afiro) { const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string input_file = rapidsDatasetRootDir + "/linear_programming/afiro_original.mps"; - std::string temp_file = std::filesystem::temp_directory_path().string() + "/c_api_test_write.mps"; - EXPECT_EQ(test_write_problem(input_file.c_str(), temp_file.c_str()), CUOPT_SUCCESS); - std::filesystem::remove(temp_file); + std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; + int termination_status; + EXPECT_EQ(solve_mps_file(filename.c_str(), 60, CUOPT_INFINITY, &termination_status), + CUOPT_SUCCESS); + EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); } -TEST(c_api, test_initial_primal_solution) +// Test both LP and MIP codepaths +class TimeLimitTestFixture : public ::testing::TestWithParam> { +}; +TEST_P(TimeLimitTestFixture, time_limit) { const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + "/linear_programming/afiro_original.mps"; - cuopt_int_t termination_status; - cuopt_float_t objective; - EXPECT_EQ(test_initial_primal_solution(filename.c_str(), &termination_status, &objective), + std::string filename = rapidsDatasetRootDir + std::get<0>(GetParam()); + double target_solve_time = std::get<1>(GetParam()); + int method = std::get<2>(GetParam()); + int termination_status; + double solve_time = std::numeric_limits::quiet_NaN(); + EXPECT_EQ(solve_mps_file(filename.c_str(), + target_solve_time, + CUOPT_INFINITY, + &termination_status, + &solve_time, + method), CUOPT_SUCCESS); - EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, -464.0, 1e-2); + EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_TIME_LIMIT); + + // Dual simplex is spending some time for factorizing the basis, and this computation does not + // check for time limit + double excess_allowed_time = 3.0; + EXPECT_NEAR(solve_time, target_solve_time, excess_allowed_time); +} +INSTANTIATE_TEST_SUITE_P( + c_api, + TimeLimitTestFixture, + ::testing::Values( + std::make_tuple("/linear_programming/square41/square41.mps", + 5, + CUOPT_METHOD_DUAL_SIMPLEX), // LP, Dual Simplex + std::make_tuple("/linear_programming/square41/square41.mps", 5, CUOPT_METHOD_PDLP), // LP, PDLP + std::make_tuple("/mip/supportcase22.mps", 15, CUOPT_METHOD_DUAL_SIMPLEX) // MIP + )); + +TEST(c_api, iteration_limit) +{ + const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); + std::string filename = rapidsDatasetRootDir + "/linear_programming/" + "afiro_original.mps"; + int termination_status; + EXPECT_EQ(solve_mps_file(filename.c_str(), 60, 1, &termination_status), CUOPT_SUCCESS); + EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_ITERATION_LIMIT); } -TEST(c_api, test_initial_dual_solution) +TEST(c_api, solve_time_bb_preemption) { const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + "/linear_programming/afiro_original.mps"; + std::string filename = rapidsDatasetRootDir + "/mip/" + "bb_optimality.mps"; + int termination_status; + double solve_time = std::numeric_limits::quiet_NaN(); + EXPECT_EQ(solve_mps_file(filename.c_str(), 5, CUOPT_INFINITY, &termination_status, &solve_time), + CUOPT_SUCCESS); + EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); + EXPECT_GT(solve_time, 0); // solve time should not be equal to 0, even on very simple instances + // solved by B&B before the diversity solver has time to run +} + +TEST(c_api, bad_parameter_name) { EXPECT_EQ(test_bad_parameter_name(), CUOPT_INVALID_ARGUMENT); } + +TEST(c_api, burglar) { EXPECT_EQ(burglar_problem(), CUOPT_SUCCESS); } + +TEST(c_api, test_missing_file) { EXPECT_EQ(test_missing_file(), CUOPT_MPS_FILE_ERROR); } + +TEST(c_api, test_infeasible_problem) { EXPECT_EQ(test_infeasible_problem(), CUOPT_SUCCESS); } + +TEST(c_api, test_ranged_problem) +{ cuopt_int_t termination_status; cuopt_float_t objective; - EXPECT_EQ(test_initial_dual_solution(filename.c_str(), &termination_status, &objective), - CUOPT_SUCCESS); + EXPECT_EQ(test_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); - EXPECT_NEAR(objective, -464.0, 1e-2); + EXPECT_NEAR(objective, 32.0, 1e-3); } -TEST(c_api, test_mip_start) +TEST(c_api, test_invalid_bounds) +{ + // Test LP codepath + EXPECT_EQ(test_invalid_bounds(false), CUOPT_SUCCESS); + // Test MIP codepath + EXPECT_EQ(test_invalid_bounds(true), CUOPT_SUCCESS); +} + +TEST(c_api, test_quadratic_problem) { - const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); - std::string filename = rapidsDatasetRootDir + "/mip/bb_optimality.mps"; cuopt_int_t termination_status; cuopt_float_t objective; - EXPECT_EQ(test_mip_start(filename.c_str(), &termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(test_quadratic_problem(&termination_status, &objective), CUOPT_SUCCESS); EXPECT_EQ(termination_status, CUOPT_TERIMINATION_STATUS_OPTIMAL); + EXPECT_NEAR(objective, -32.0, 1e-3); +} + +TEST(c_api, test_quadratic_ranged_problem) +{ + cuopt_int_t termination_status; + cuopt_float_t objective; + EXPECT_EQ(test_quadratic_ranged_problem(&termination_status, &objective), CUOPT_SUCCESS); + EXPECT_EQ(termination_status, (int)CUOPT_TERIMINATION_STATUS_OPTIMAL); + EXPECT_NEAR(objective, -32.0, 1e-3); +} + +TEST(c_api, test_write_problem) +{ + const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); + std::string input_file = rapidsDatasetRootDir + "/linear_programming/afiro_original.mps"; + std::string temp_file = std::filesystem::temp_directory_path().string() + "/c_api_test_write.mps"; + EXPECT_EQ(test_write_problem(input_file.c_str(), temp_file.c_str()), CUOPT_SUCCESS); + std::filesystem::remove(temp_file); } diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h index a2c308220..d2ee03a8a 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h @@ -35,16 +35,6 @@ cuopt_int_t test_quadratic_problem(cuopt_int_t* termination_status_ptr, cuopt_int_t test_quadratic_ranged_problem(cuopt_int_t* termination_status_ptr, cuopt_float_t* objective_ptr); cuopt_int_t test_write_problem(const char* input_filename, const char* output_filename); -cuopt_int_t test_initial_primal_solution(const char* filename, - cuopt_int_t* termination_status_ptr, - cuopt_float_t* objective_ptr); -cuopt_int_t test_initial_dual_solution(const char* filename, - cuopt_int_t* termination_status_ptr, - cuopt_float_t* objective_ptr); -cuopt_int_t test_mip_start(const char* filename, - cuopt_int_t* termination_status_ptr, - cuopt_float_t* objective_ptr); - #ifdef __cplusplus } #endif From e2471093d97b2254810a65d0d689c5a097139a93 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 17 Dec 2025 13:21:17 +0000 Subject: [PATCH 4/7] write roundtrip test --- .../cuopt/linear_programming/cuopt_c.h | 2 + .../optimization_problem.hpp | 14 + cpp/libmps_parser/src/mps_writer.cpp | 28 +- cpp/src/linear_programming/cuopt_c.cpp | 27 +- .../linear_programming/cuopt_c_internal.hpp | 46 +++ .../optimization_problem.cu | 286 ++++++++++++++++++ .../c_api_tests/c_api_tests.cpp | 84 +++++ 7 files changed, 461 insertions(+), 26 deletions(-) create mode 100644 cpp/src/linear_programming/cuopt_c_internal.hpp diff --git a/cpp/include/cuopt/linear_programming/cuopt_c.h b/cpp/include/cuopt/linear_programming/cuopt_c.h index bb2da0cd6..3381d3e17 100644 --- a/cpp/include/cuopt/linear_programming/cuopt_c.h +++ b/cpp/include/cuopt/linear_programming/cuopt_c.h @@ -734,6 +734,8 @@ cuopt_int_t cuOptSetInitialDualSolution(cuOptSolverSettings settings, * of size num_variables containing the solution values. * @param[in] num_variables - The number of variables (size of the solution array). * + * @attention Currently unsupported with presolve on. + * * @return A status code indicating success or failure. */ cuopt_int_t cuOptAddMIPStart(cuOptSolverSettings settings, diff --git a/cpp/include/cuopt/linear_programming/optimization_problem.hpp b/cpp/include/cuopt/linear_programming/optimization_problem.hpp index dd912ec94..0d3319124 100644 --- a/cpp/include/cuopt/linear_programming/optimization_problem.hpp +++ b/cpp/include/cuopt/linear_programming/optimization_problem.hpp @@ -105,6 +105,20 @@ class optimization_problem_t { optimization_problem_t(raft::handle_t const* handle_ptr); optimization_problem_t(const optimization_problem_t& other); + /** + * @brief Check if this optimization problem is equivalent to another. + * + * Two problems are considered equivalent if they represent the same mathematical + * optimization problem, potentially with variables and constraints in a different order. + * The mapping between problems is determined by matching variable names and row names. + * Essentially checks for graph isomorphism given label mappings. + * + * @param other The other optimization problem to compare against. + * @return true if the problems are equivalent (up to permutation of variables/constraints), + * false otherwise. + */ + bool is_equivalent(const optimization_problem_t& other) const; + std::vector mip_callbacks_; /** diff --git a/cpp/libmps_parser/src/mps_writer.cpp b/cpp/libmps_parser/src/mps_writer.cpp index 5ec0052ce..8a9392c33 100644 --- a/cpp/libmps_parser/src/mps_writer.cpp +++ b/cpp/libmps_parser/src/mps_writer.cpp @@ -138,6 +138,7 @@ void mps_writer_t::write(const std::string& mps_file_path) // Keep a single integer section marker by going over constraints twice and writing out // integral/nonintegral nonzeros ordered map + std::vector var_in_constraint(n_variables, false); std::map>> integral_col_nnzs; std::map>> continuous_col_nnzs; for (size_t row_id = 0; row_id < (size_t)n_constraints; row_id++) { @@ -150,12 +151,37 @@ void mps_writer_t::write(const std::string& mps_file_path) } else { continuous_col_nnzs[var].emplace_back(row_id, constraint_matrix_values[k]); } + var_in_constraint[var] = true; + } + } + + // Record and explicitely declared variables not contained in any constraint and w/ a zero + // objective coefficient Necessary as some tools expect variables to be declared in "COLUMNS" + // before any "BOUNDS" statements + std::vector orphan_continuous_vars; + std::vector orphan_integer_vars; + for (i_t var = 0; var < n_variables; ++var) { + if (!var_in_constraint[var] && objective_coefficients[var] == 0) { + if (variable_types[var] == 'I') { + orphan_integer_vars.push_back(var); + } else { + orphan_continuous_vars.push_back(var); + } } } for (size_t is_integral = 0; is_integral < 2; is_integral++) { - auto& col_map = is_integral ? integral_col_nnzs : continuous_col_nnzs; + auto& col_map = is_integral ? integral_col_nnzs : continuous_col_nnzs; + auto& orphan_vars = is_integral ? orphan_integer_vars : orphan_continuous_vars; if (is_integral) mps_file << " MARK0001 'MARKER' 'INTORG'\n"; + for (auto& var_id : orphan_vars) { + std::string col_name = var_id < problem_.get_variable_names().size() + ? problem_.get_variable_names()[var_id] + : "C" + std::to_string(var_id); + mps_file << " " << col_name << " " + << (problem_.get_objective_name().empty() ? "OBJ" : problem_.get_objective_name()) + << " 0\n"; + } for (auto& [var_id, nnzs] : col_map) { std::string col_name = var_id < problem_.get_variable_names().size() ? problem_.get_variable_names()[var_id] diff --git a/cpp/src/linear_programming/cuopt_c.cpp b/cpp/src/linear_programming/cuopt_c.cpp index 2d2072bad..96593712c 100644 --- a/cpp/src/linear_programming/cuopt_c.cpp +++ b/cpp/src/linear_programming/cuopt_c.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -24,31 +25,6 @@ using namespace cuopt::mps_parser; using namespace cuopt::linear_programming; -struct problem_and_stream_view_t { - problem_and_stream_view_t() - : op_problem(nullptr), stream_view(rmm::cuda_stream_per_thread), handle(stream_view) - { - } - raft::handle_t* get_handle_ptr() { return &handle; } - cuopt::linear_programming::optimization_problem_t* op_problem; - rmm::cuda_stream_view stream_view; - raft::handle_t handle; -}; - -struct solution_and_stream_view_t { - solution_and_stream_view_t(bool solution_for_mip, rmm::cuda_stream_view stream_view) - : is_mip(solution_for_mip), - mip_solution_ptr(nullptr), - lp_solution_ptr(nullptr), - stream_view(stream_view) - { - } - bool is_mip; - mip_solution_t* mip_solution_ptr; - optimization_problem_solution_t* lp_solution_ptr; - rmm::cuda_stream_view stream_view; -}; - int8_t cuOptGetFloatSize() { return sizeof(cuopt_float_t); } int8_t cuOptGetIntSize() { return sizeof(cuopt_int_t); } @@ -98,6 +74,7 @@ cuopt_int_t cuOptWriteProblem(cuOptOptimizationProblem problem, { if (problem == nullptr) { return CUOPT_INVALID_ARGUMENT; } if (filename == nullptr) { return CUOPT_INVALID_ARGUMENT; } + if (strlen(filename) == 0) { return CUOPT_INVALID_ARGUMENT; } if (format != CUOPT_FILE_FORMAT_MPS) { return CUOPT_INVALID_ARGUMENT; } problem_and_stream_view_t* problem_and_stream_view = diff --git a/cpp/src/linear_programming/cuopt_c_internal.hpp b/cpp/src/linear_programming/cuopt_c_internal.hpp new file mode 100644 index 000000000..a141ca666 --- /dev/null +++ b/cpp/src/linear_programming/cuopt_c_internal.hpp @@ -0,0 +1,46 @@ +/* clang-format off */ +/* + * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-License-Identifier: Apache-2.0 + */ +/* clang-format on */ + +#pragma once + +#include +#include +#include +#include + +#include + +#include + +namespace cuopt::linear_programming { + +struct problem_and_stream_view_t { + problem_and_stream_view_t() + : op_problem(nullptr), stream_view(rmm::cuda_stream_per_thread), handle(stream_view) + { + } + raft::handle_t* get_handle_ptr() { return &handle; } + optimization_problem_t* op_problem; + rmm::cuda_stream_view stream_view; + raft::handle_t handle; +}; + +struct solution_and_stream_view_t { + solution_and_stream_view_t(bool solution_for_mip, rmm::cuda_stream_view stream_view) + : is_mip(solution_for_mip), + mip_solution_ptr(nullptr), + lp_solution_ptr(nullptr), + stream_view(stream_view) + { + } + bool is_mip; + mip_solution_t* mip_solution_ptr; + optimization_problem_solution_t* lp_solution_ptr; + rmm::cuda_stream_view stream_view; +}; + +} // namespace cuopt::linear_programming diff --git a/cpp/src/linear_programming/optimization_problem.cu b/cpp/src/linear_programming/optimization_problem.cu index 72d75cdc7..98248f3a6 100644 --- a/cpp/src/linear_programming/optimization_problem.cu +++ b/cpp/src/linear_programming/optimization_problem.cu @@ -17,11 +17,21 @@ #include #include +#include + +#include #include +#include +#include +#include +#include +#include +#include #include #include +#include namespace cuopt::linear_programming { @@ -78,6 +88,282 @@ optimization_problem_t::optimization_problem_t( { } +/** + * @brief Compare two CSR matrices for equivalence under row and column permutations. + * + * @param this_offsets Row offsets of first matrix + * @param this_indices Column indices of first matrix + * @param this_values Values of first matrix + * @param other_offsets Row offsets of second matrix + * @param other_indices Column indices of second matrix + * @param other_values Values of second matrix + * @param d_row_perm_inv Inverse row permutation (maps other's row indices to this's) + * @param d_col_perm_inv Inverse column permutation (maps other's col indices to this's) + * @param n_cols Number of columns (used for sort key computation) + * @param stream CUDA stream + * @return true if matrices are equivalent under the given permutations + */ +template +static bool csr_matrices_equivalent_with_permutation(const rmm::device_uvector& this_offsets, + const rmm::device_uvector& this_indices, + const rmm::device_uvector& this_values, + const rmm::device_uvector& other_offsets, + const rmm::device_uvector& other_indices, + const rmm::device_uvector& other_values, + const rmm::device_uvector& d_row_perm_inv, + const rmm::device_uvector& d_col_perm_inv, + i_t n_cols, + rmm::cuda_stream_view stream) +{ + const i_t nnz = static_cast(this_values.size()); + if (nnz != static_cast(other_values.size())) { return false; } + if (nnz == 0) { return true; } + + auto policy = rmm::exec_policy(stream); + + // Expand CSR row offsets to row indices for 'this' + rmm::device_uvector this_rows(nnz, stream); + rmm::device_uvector this_cols(nnz, stream); + rmm::device_uvector this_vals(nnz, stream); + + rmm::device_uvector entry_indices(nnz, stream); + thrust::sequence(policy, entry_indices.begin(), entry_indices.end()); + + thrust::upper_bound(policy, + this_offsets.begin(), + this_offsets.end(), + entry_indices.begin(), + entry_indices.end(), + this_rows.begin()); + thrust::transform( + policy, this_rows.begin(), this_rows.end(), this_rows.begin(), [] __device__(i_t r) { + return r - 1; + }); + + thrust::copy(policy, this_indices.begin(), this_indices.end(), this_cols.begin()); + thrust::copy(policy, this_values.begin(), this_values.end(), this_vals.begin()); + + // For 'other': expand and apply inverse permutations to map to 'this' coordinate system + rmm::device_uvector other_rows(nnz, stream); + rmm::device_uvector other_cols(nnz, stream); + rmm::device_uvector other_vals(nnz, stream); + + thrust::upper_bound(policy, + other_offsets.begin(), + other_offsets.end(), + entry_indices.begin(), + entry_indices.end(), + other_rows.begin()); + thrust::transform( + policy, other_rows.begin(), other_rows.end(), other_rows.begin(), [] __device__(i_t r) { + return r - 1; + }); + + thrust::gather( + policy, other_rows.begin(), other_rows.end(), d_row_perm_inv.begin(), other_rows.begin()); + + thrust::gather( + policy, other_indices.begin(), other_indices.end(), d_col_perm_inv.begin(), other_cols.begin()); + + thrust::copy(policy, other_values.begin(), other_values.end(), other_vals.begin()); + + // Create sort keys: row * n_cols + col (to sort by row then column) + rmm::device_uvector this_keys(nnz, stream); + rmm::device_uvector other_keys(nnz, stream); + + const int64_t n_cols_64 = n_cols; + thrust::transform(policy, + thrust::make_zip_iterator(this_rows.begin(), this_cols.begin()), + thrust::make_zip_iterator(this_rows.end(), this_cols.end()), + this_keys.begin(), + [n_cols_64] __device__(thrust::tuple rc) { + return static_cast(thrust::get<0>(rc)) * n_cols_64 + + static_cast(thrust::get<1>(rc)); + }); + + thrust::transform(policy, + thrust::make_zip_iterator(other_rows.begin(), other_cols.begin()), + thrust::make_zip_iterator(other_rows.end(), other_cols.end()), + other_keys.begin(), + [n_cols_64] __device__(thrust::tuple rc) { + return static_cast(thrust::get<0>(rc)) * n_cols_64 + + static_cast(thrust::get<1>(rc)); + }); + + thrust::sort_by_key(policy, this_keys.begin(), this_keys.end(), this_vals.begin()); + thrust::sort_by_key(policy, other_keys.begin(), other_keys.end(), other_vals.begin()); + + if (!thrust::equal(policy, this_keys.begin(), this_keys.end(), other_keys.begin())) { + return false; + } + + if (!thrust::equal(policy, this_vals.begin(), this_vals.end(), other_vals.begin())) { + return false; + } + + return true; +} + +template +bool optimization_problem_t::is_equivalent( + const optimization_problem_t& other) const +{ + if (maximize_ != other.maximize_) { return false; } + if (n_vars_ != other.n_vars_) { return false; } + if (n_constraints_ != other.n_constraints_) { return false; } + if (objective_scaling_factor_ != other.objective_scaling_factor_) { return false; } + if (objective_offset_ != other.objective_offset_) { return false; } + if (problem_category_ != other.problem_category_) { return false; } + if (A_.size() != other.A_.size()) { return false; } + + if (var_names_.empty() || other.var_names_.empty()) { return false; } + if (row_names_.empty() || other.row_names_.empty()) { return false; } + + // Build variable permutation: var_perm[i] = index j in other where var_names_[i] == + // other.var_names_[j] + std::unordered_map other_var_idx; + for (size_t j = 0; j < other.var_names_.size(); ++j) { + other_var_idx[other.var_names_[j]] = static_cast(j); + } + std::vector var_perm(n_vars_); + for (i_t i = 0; i < n_vars_; ++i) { + auto it = other_var_idx.find(var_names_[i]); + if (it == other_var_idx.end()) { return false; } + var_perm[i] = it->second; + } + + // Build row permutation: row_perm[i] = index j in other where row_names_[i] == + // other.row_names_[j] + std::unordered_map other_row_idx; + for (size_t j = 0; j < other.row_names_.size(); ++j) { + other_row_idx[other.row_names_[j]] = static_cast(j); + } + std::vector row_perm(n_constraints_); + for (i_t i = 0; i < n_constraints_; ++i) { + auto it = other_row_idx.find(row_names_[i]); + if (it == other_row_idx.end()) { return false; } + row_perm[i] = it->second; + } + + // Upload permutations to GPU + rmm::device_uvector d_var_perm(n_vars_, stream_view_); + rmm::device_uvector d_row_perm(n_constraints_, stream_view_); + raft::copy(d_var_perm.data(), var_perm.data(), n_vars_, stream_view_); + raft::copy(d_row_perm.data(), row_perm.data(), n_constraints_, stream_view_); + + auto policy = rmm::exec_policy(stream_view_); + + auto permuted_eq = [&](auto this_begin, auto this_end, auto other_begin, auto perm_begin) { + auto other_perm = thrust::make_permutation_iterator(other_begin, perm_begin); + return thrust::equal(policy, this_begin, this_end, other_perm); + }; + + // Compare variable-indexed arrays + if (!permuted_eq(c_.begin(), c_.end(), other.c_.begin(), d_var_perm.begin())) { return false; } + if (!permuted_eq(variable_lower_bounds_.begin(), + variable_lower_bounds_.end(), + other.variable_lower_bounds_.begin(), + d_var_perm.begin())) { + return false; + } + if (!permuted_eq(variable_upper_bounds_.begin(), + variable_upper_bounds_.end(), + other.variable_upper_bounds_.begin(), + d_var_perm.begin())) { + return false; + } + if (!permuted_eq(variable_types_.begin(), + variable_types_.end(), + other.variable_types_.begin(), + d_var_perm.begin())) { + return false; + } + + // Compare constraint-indexed arrays + if (!permuted_eq(b_.begin(), b_.end(), other.b_.begin(), d_row_perm.begin())) { return false; } + if (!permuted_eq(constraint_lower_bounds_.begin(), + constraint_lower_bounds_.end(), + other.constraint_lower_bounds_.begin(), + d_row_perm.begin())) { + return false; + } + if (!permuted_eq(constraint_upper_bounds_.begin(), + constraint_upper_bounds_.end(), + other.constraint_upper_bounds_.begin(), + d_row_perm.begin())) { + return false; + } + if (!permuted_eq( + row_types_.begin(), row_types_.end(), other.row_types_.begin(), d_row_perm.begin())) { + return false; + } + + // Build inverse permutations on CPU (needed for CSR comparisons) + std::vector var_perm_inv(n_vars_); + for (i_t i = 0; i < n_vars_; ++i) { + var_perm_inv[var_perm[i]] = i; + } + std::vector row_perm_inv(n_constraints_); + for (i_t i = 0; i < n_constraints_; ++i) { + row_perm_inv[row_perm[i]] = i; + } + + // Upload inverse permutations to GPU + rmm::device_uvector d_var_perm_inv(n_vars_, stream_view_); + rmm::device_uvector d_row_perm_inv(n_constraints_, stream_view_); + raft::copy(d_var_perm_inv.data(), var_perm_inv.data(), n_vars_, stream_view_); + raft::copy(d_row_perm_inv.data(), row_perm_inv.data(), n_constraints_, stream_view_); + + // Constraint matrix (A) comparison with row and column permutations + if (!csr_matrices_equivalent_with_permutation(A_offsets_, + A_indices_, + A_, + other.A_offsets_, + other.A_indices_, + other.A_, + d_row_perm_inv, + d_var_perm_inv, + n_vars_, + stream_view_)) { + return false; + } + + // Q matrix comparison (quadratic objective) with variable permutation + if (!Q_values_.empty() || !other.Q_values_.empty()) { + rmm::device_uvector d_Q_offsets(Q_offsets_.size(), stream_view_); + rmm::device_uvector d_Q_indices(Q_indices_.size(), stream_view_); + rmm::device_uvector d_Q_values(Q_values_.size(), stream_view_); + rmm::device_uvector d_other_Q_offsets(other.Q_offsets_.size(), stream_view_); + rmm::device_uvector d_other_Q_indices(other.Q_indices_.size(), stream_view_); + rmm::device_uvector d_other_Q_values(other.Q_values_.size(), stream_view_); + + raft::copy(d_Q_offsets.data(), Q_offsets_.data(), Q_offsets_.size(), stream_view_); + raft::copy(d_Q_indices.data(), Q_indices_.data(), Q_indices_.size(), stream_view_); + raft::copy(d_Q_values.data(), Q_values_.data(), Q_values_.size(), stream_view_); + raft::copy( + d_other_Q_offsets.data(), other.Q_offsets_.data(), other.Q_offsets_.size(), stream_view_); + raft::copy( + d_other_Q_indices.data(), other.Q_indices_.data(), other.Q_indices_.size(), stream_view_); + raft::copy( + d_other_Q_values.data(), other.Q_values_.data(), other.Q_values_.size(), stream_view_); + + if (!csr_matrices_equivalent_with_permutation(d_Q_offsets, + d_Q_indices, + d_Q_values, + d_other_Q_offsets, + d_other_Q_indices, + d_other_Q_values, + d_var_perm_inv, + d_var_perm_inv, + n_vars_, + stream_view_)) { + return false; + } + } + + return true; +} + template void optimization_problem_t::set_csr_constraint_matrix(const f_t* A_values, i_t size_values, diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp index 13d2c99d3..2510a9e56 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp @@ -7,7 +7,11 @@ #include "c_api_tests.h" +#include +#include + #include +#include #include #include @@ -137,3 +141,83 @@ TEST(c_api, test_write_problem) EXPECT_EQ(test_write_problem(input_file.c_str(), temp_file.c_str()), CUOPT_SUCCESS); std::filesystem::remove(temp_file); } + +static bool test_mps_roundtrip(const std::string& mps_file_path) +{ + using cuopt::linear_programming::problem_and_stream_view_t; + + cuOptOptimizationProblem original_handle = nullptr; + cuOptOptimizationProblem reread_handle = nullptr; + bool result = false; + + std::string model_basename = std::filesystem::path(mps_file_path).filename().string(); + std::string temp_file = + std::filesystem::temp_directory_path().string() + "/roundtrip_temp_" + model_basename; + + if (cuOptReadProblem(mps_file_path.c_str(), &original_handle) != CUOPT_SUCCESS) { + std::cerr << "Failed to read original MPS file: " << mps_file_path << std::endl; + goto cleanup; + } + + if (cuOptWriteProblem(original_handle, temp_file.c_str(), CUOPT_FILE_FORMAT_MPS) != + CUOPT_SUCCESS) { + std::cerr << "Failed to write MPS file: " << temp_file << std::endl; + goto cleanup; + } + + if (cuOptReadProblem(temp_file.c_str(), &reread_handle) != CUOPT_SUCCESS) { + std::cerr << "Failed to re-read MPS file: " << temp_file << std::endl; + goto cleanup; + } + + { + auto* original_problem_wrapper = static_cast(original_handle); + auto* reread_problem_wrapper = static_cast(reread_handle); + + result = + original_problem_wrapper->op_problem->is_equivalent(*reread_problem_wrapper->op_problem); + } + +cleanup: + std::filesystem::remove(temp_file); + cuOptDestroyProblem(&original_handle); + cuOptDestroyProblem(&reread_handle); + + return result; +} + +class WriteRoundtripTestFixture : public ::testing::TestWithParam {}; +TEST_P(WriteRoundtripTestFixture, roundtrip) +{ + const std::string& rapidsDatasetRootDir = cuopt::test::get_rapids_dataset_root_dir(); + EXPECT_TRUE(test_mps_roundtrip(rapidsDatasetRootDir + GetParam())); +} +INSTANTIATE_TEST_SUITE_P(c_api, + WriteRoundtripTestFixture, + ::testing::Values("/linear_programming/afiro_original.mps", + "/mip/50v-10.mps", + "/mip/fiball.mps", + "/mip/gen-ip054.mps", + "/mip/sct2.mps", + "/mip/uccase9.mps", + "/mip/drayage-25-23.mps", + "/mip/tr12-30.mps", + "/mip/neos-3004026-krka.mps", + "/mip/ns1208400.mps", + "/mip/gmu-35-50.mps", + "/mip/n2seq36q.mps", + "/mip/seymour1.mps", + "/mip/rmatr200-p5.mps", + "/mip/cvs16r128-89.mps", + "/mip/thor50dday.mps", + "/mip/stein9inf.mps", + "/mip/neos5.mps", + "/mip/neos5-free-bound.mps", + "/mip/crossing_var_bounds.mps", + "/mip/cod105_max.mps", + "/mip/sudoku.mps", + "/mip/presolve-infeasible.mps", + "/mip/swath1.mps", + "/mip/enlight_hard.mps", + "/mip/enlight11.mps", + "/mip/supportcase22.mps")); From 5fabb4804a1d3241e70d73e3cdd398dab1f24f19 Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Wed, 17 Dec 2025 14:18:34 +0000 Subject: [PATCH 5/7] fix orpham var bug --- cpp/libmps_parser/src/mps_writer.cpp | 36 ++++++++++++------- .../optimization_problem.cu | 33 +---------------- 2 files changed, 24 insertions(+), 45 deletions(-) diff --git a/cpp/libmps_parser/src/mps_writer.cpp b/cpp/libmps_parser/src/mps_writer.cpp index 8a9392c33..d303f8985 100644 --- a/cpp/libmps_parser/src/mps_writer.cpp +++ b/cpp/libmps_parser/src/mps_writer.cpp @@ -155,13 +155,11 @@ void mps_writer_t::write(const std::string& mps_file_path) } } - // Record and explicitely declared variables not contained in any constraint and w/ a zero - // objective coefficient Necessary as some tools expect variables to be declared in "COLUMNS" - // before any "BOUNDS" statements + // Record and explicitely declared variables not contained in any constraint. std::vector orphan_continuous_vars; std::vector orphan_integer_vars; for (i_t var = 0; var < n_variables; ++var) { - if (!var_in_constraint[var] && objective_coefficients[var] == 0) { + if (!var_in_constraint[var]) { if (variable_types[var] == 'I') { orphan_integer_vars.push_back(var); } else { @@ -178,9 +176,11 @@ void mps_writer_t::write(const std::string& mps_file_path) std::string col_name = var_id < problem_.get_variable_names().size() ? problem_.get_variable_names()[var_id] : "C" + std::to_string(var_id); + // Write that column even if it is orphan as has a zero objective coefficient. + // Some tools require variables to be declared in "COLUMNS" before any "BOUNDS" statements. mps_file << " " << col_name << " " << (problem_.get_objective_name().empty() ? "OBJ" : problem_.get_objective_name()) - << " 0\n"; + << " " << objective_coefficients[var_id] << "\n"; } for (auto& [var_id, nnzs] : col_map) { std::string col_name = var_id < problem_.get_variable_names().size() @@ -248,24 +248,34 @@ void mps_writer_t::write(const std::string& mps_file_path) // BOUNDS section mps_file << "BOUNDS\n"; for (size_t j = 0; j < (size_t)n_variables; j++) { - std::string col_name = j < problem_.get_variable_names().size() - ? problem_.get_variable_names()[j] - : "C" + std::to_string(j); + std::string col_name = j < problem_.get_variable_names().size() + ? problem_.get_variable_names()[j] + : "C" + std::to_string(j); + std::string lower_bound_str = variable_types[j] == 'I' ? "LI" : "LO"; + std::string upper_bound_str = variable_types[j] == 'I' ? "UI" : "UP"; if (variable_lower_bounds[j] == -std::numeric_limits::infinity() && variable_upper_bounds[j] == std::numeric_limits::infinity()) { mps_file << " FR BOUND1 " << col_name << "\n"; + } + // Ambiguity exists in the spec about the case where upper_bound == 0 and lower_bound == 0, and + // only UP is specified. Handle fixed variables explicitely to avoid this pitfall. + else if (variable_lower_bounds[j] == variable_upper_bounds[j]) { + mps_file << " FX BOUND1 " << col_name << " " << variable_lower_bounds[j] << "\n"; } else { - if (variable_lower_bounds[j] != 0.0 || objective_coefficients[j] == 0.0 || - variable_types[j] != 'C') { + if (variable_lower_bounds[j] != 0.0) { if (variable_lower_bounds[j] == -std::numeric_limits::infinity()) { mps_file << " MI BOUND1 " << col_name << "\n"; } else { - mps_file << " LO BOUND1 " << col_name << " " << variable_lower_bounds[j] << "\n"; + mps_file << " " << lower_bound_str << " BOUND1 " << col_name << " " + << variable_lower_bounds[j] << "\n"; } } - if (variable_upper_bounds[j] != std::numeric_limits::infinity()) { - mps_file << " UP BOUND1 " << col_name << " " << variable_upper_bounds[j] << "\n"; + // Integer variables get different default bounds compared to continuous variables + if (variable_upper_bounds[j] != std::numeric_limits::infinity() || + variable_types[j] == 'I') { + mps_file << " " << upper_bound_str << " BOUND1 " << col_name << " " + << variable_upper_bounds[j] << "\n"; } } } diff --git a/cpp/src/linear_programming/optimization_problem.cu b/cpp/src/linear_programming/optimization_problem.cu index 98248f3a6..eab967edc 100644 --- a/cpp/src/linear_programming/optimization_problem.cu +++ b/cpp/src/linear_programming/optimization_problem.cu @@ -328,38 +328,7 @@ bool optimization_problem_t::is_equivalent( return false; } - // Q matrix comparison (quadratic objective) with variable permutation - if (!Q_values_.empty() || !other.Q_values_.empty()) { - rmm::device_uvector d_Q_offsets(Q_offsets_.size(), stream_view_); - rmm::device_uvector d_Q_indices(Q_indices_.size(), stream_view_); - rmm::device_uvector d_Q_values(Q_values_.size(), stream_view_); - rmm::device_uvector d_other_Q_offsets(other.Q_offsets_.size(), stream_view_); - rmm::device_uvector d_other_Q_indices(other.Q_indices_.size(), stream_view_); - rmm::device_uvector d_other_Q_values(other.Q_values_.size(), stream_view_); - - raft::copy(d_Q_offsets.data(), Q_offsets_.data(), Q_offsets_.size(), stream_view_); - raft::copy(d_Q_indices.data(), Q_indices_.data(), Q_indices_.size(), stream_view_); - raft::copy(d_Q_values.data(), Q_values_.data(), Q_values_.size(), stream_view_); - raft::copy( - d_other_Q_offsets.data(), other.Q_offsets_.data(), other.Q_offsets_.size(), stream_view_); - raft::copy( - d_other_Q_indices.data(), other.Q_indices_.data(), other.Q_indices_.size(), stream_view_); - raft::copy( - d_other_Q_values.data(), other.Q_values_.data(), other.Q_values_.size(), stream_view_); - - if (!csr_matrices_equivalent_with_permutation(d_Q_offsets, - d_Q_indices, - d_Q_values, - d_other_Q_offsets, - d_other_Q_indices, - d_other_Q_values, - d_var_perm_inv, - d_var_perm_inv, - n_vars_, - stream_view_)) { - return false; - } - } + // Q matrix writing to MPS not supported yet. Don't check for equivalence here return true; } From 177b638f8d487f1296c1002ccd327ebacc6d8bfd Mon Sep 17 00:00:00 2001 From: Alice Boucher Date: Thu, 8 Jan 2026 09:31:53 +0000 Subject: [PATCH 6/7] Update copyrights --- cpp/include/cuopt/linear_programming/constants.h | 2 +- cpp/include/cuopt/linear_programming/cuopt_c.h | 2 +- cpp/include/cuopt/linear_programming/optimization_problem.hpp | 2 +- cpp/libmps_parser/src/mps_writer.cpp | 2 +- cpp/src/linear_programming/cuopt_c.cpp | 2 +- cpp/src/linear_programming/cuopt_c_internal.hpp | 2 +- cpp/tests/linear_programming/CMakeLists.txt | 2 +- cpp/tests/linear_programming/c_api_tests/c_api_test.c | 2 +- cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp | 2 +- cpp/tests/linear_programming/c_api_tests/c_api_tests.h | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cpp/include/cuopt/linear_programming/constants.h b/cpp/include/cuopt/linear_programming/constants.h index 93a6ce29a..e4c304e6f 100644 --- a/cpp/include/cuopt/linear_programming/constants.h +++ b/cpp/include/cuopt/linear_programming/constants.h @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/include/cuopt/linear_programming/cuopt_c.h b/cpp/include/cuopt/linear_programming/cuopt_c.h index 3381d3e17..7ed793512 100644 --- a/cpp/include/cuopt/linear_programming/cuopt_c.h +++ b/cpp/include/cuopt/linear_programming/cuopt_c.h @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/include/cuopt/linear_programming/optimization_problem.hpp b/cpp/include/cuopt/linear_programming/optimization_problem.hpp index 0d3319124..d0731f3aa 100644 --- a/cpp/include/cuopt/linear_programming/optimization_problem.hpp +++ b/cpp/include/cuopt/linear_programming/optimization_problem.hpp @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2022-2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/libmps_parser/src/mps_writer.cpp b/cpp/libmps_parser/src/mps_writer.cpp index d303f8985..4c562ec24 100644 --- a/cpp/libmps_parser/src/mps_writer.cpp +++ b/cpp/libmps_parser/src/mps_writer.cpp @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/src/linear_programming/cuopt_c.cpp b/cpp/src/linear_programming/cuopt_c.cpp index 96593712c..794c7f4f7 100644 --- a/cpp/src/linear_programming/cuopt_c.cpp +++ b/cpp/src/linear_programming/cuopt_c.cpp @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/src/linear_programming/cuopt_c_internal.hpp b/cpp/src/linear_programming/cuopt_c_internal.hpp index a141ca666..de9d6e559 100644 --- a/cpp/src/linear_programming/cuopt_c_internal.hpp +++ b/cpp/src/linear_programming/cuopt_c_internal.hpp @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/tests/linear_programming/CMakeLists.txt b/cpp/tests/linear_programming/CMakeLists.txt index 295044355..40e284baf 100644 --- a/cpp/tests/linear_programming/CMakeLists.txt +++ b/cpp/tests/linear_programming/CMakeLists.txt @@ -1,5 +1,5 @@ # cmake-format: off -# SPDX-FileCopyrightText: Copyright (c) 2024-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2024-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 # cmake-format: on diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_test.c b/cpp/tests/linear_programming/c_api_tests/c_api_test.c index 12ac890bc..0e3d93896 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_test.c +++ b/cpp/tests/linear_programming/c_api_tests/c_api_test.c @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp index 2510a9e56..e67e6202c 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.cpp @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ diff --git a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h index d2ee03a8a..4898e0639 100644 --- a/cpp/tests/linear_programming/c_api_tests/c_api_tests.h +++ b/cpp/tests/linear_programming/c_api_tests/c_api_tests.h @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ From 69eca525475c54546f59c7cd9fd52ab00b51df10 Mon Sep 17 00:00:00 2001 From: James Lamb Date: Wed, 7 Jan 2026 17:06:47 -0600 Subject: [PATCH 7/7] build and test against CUDA 13.1.0 (#747) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Contributes to https://github.com/rapidsai/build-planning/issues/236 Tests that CI here will work with the changes from https://github.com/rapidsai/shared-workflows/pull/483, switches CUDA 13 builds to CUDA 13.1.0 and adds some CUDA 13.1.0 test jobs. ## Summary by CodeRabbit * **New Features** - Added support for CUDA Toolkit 13.1, providing compatibility with the latest CUDA runtime and libraries. * **Chores** - Updated CI/CD workflows for building, testing, and deployment to target CUDA 13.1.0 - Updated conda environment configurations to CUDA 13.1 for ARM and x86_64 architectures - Updated copyright year to 2026 ✏️ Tip: You can customize this high-level summary in your review settings. Authors: - James Lamb (https://github.com/jameslamb) - https://github.com/jakirkham - Ramakrishnap (https://github.com/rgsl888prabhu) Approvers: - Hugo Linsenmaier (https://github.com/hlinsen) - Ramakrishnap (https://github.com/rgsl888prabhu) URL: https://github.com/NVIDIA/cuopt/pull/747 --- .github/workflows/build.yaml | 30 ++++++++--------- .github/workflows/pr.yaml | 32 +++++++++---------- .github/workflows/test.yaml | 12 +++---- .../trigger-breaking-change-alert.yaml | 4 +-- ...64.yaml => all_cuda-131_arch-aarch64.yaml} | 4 +-- ..._64.yaml => all_cuda-131_arch-x86_64.yaml} | 4 +-- cpp/src/utilities/driver_helpers.cuh | 12 +++++-- dependencies.yaml | 6 +++- 8 files changed, 57 insertions(+), 47 deletions(-) rename conda/environments/{all_cuda-130_arch-aarch64.yaml => all_cuda-131_arch-aarch64.yaml} (96%) rename conda/environments/{all_cuda-130_arch-x86_64.yaml => all_cuda-131_arch-x86_64.yaml} (96%) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 66df454bd..cddb79556 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: build @@ -44,7 +44,7 @@ concurrency: jobs: cpp-build: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -54,7 +54,7 @@ jobs: python-build: needs: [cpp-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -64,7 +64,7 @@ jobs: upload-conda: needs: [cpp-build, python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-upload-packages.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -72,7 +72,7 @@ jobs: sha: ${{ inputs.sha }} wheel-build-cuopt-mps-parser: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -87,7 +87,7 @@ jobs: wheel-publish-cuopt-mps-parser: needs: wheel-build-cuopt-mps-parser secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -98,7 +98,7 @@ jobs: wheel-build-libcuopt: needs: wheel-build-cuopt-mps-parser secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -111,7 +111,7 @@ jobs: wheel-publish-libcuopt: needs: wheel-build-libcuopt secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -122,7 +122,7 @@ jobs: wheel-build-cuopt: needs: [wheel-build-cuopt-mps-parser, wheel-build-libcuopt] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -134,7 +134,7 @@ jobs: wheel-publish-cuopt: needs: wheel-build-cuopt secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -144,7 +144,7 @@ jobs: package-type: python wheel-build-cuopt-server: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -159,7 +159,7 @@ jobs: wheel-publish-cuopt-server: needs: wheel-build-cuopt-server secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -170,7 +170,7 @@ jobs: docs-build: needs: [python-build] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} node_type: "gpu-l4-latest-1" @@ -184,7 +184,7 @@ jobs: script: "ci/build_docs.sh" wheel-build-cuopt-sh-client: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} @@ -200,7 +200,7 @@ jobs: wheel-publish-cuopt-sh-client: needs: wheel-build-cuopt-sh-client secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-publish.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type || 'branch' }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 4996f3315..4dbec6b9b 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: pr @@ -34,7 +34,7 @@ jobs: - wheel-build-cuopt-sh-client - test-self-hosted-server secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/pr-builder.yaml@cuda-13.1.0 check-lean-ci: runs-on: ubuntu-latest outputs: @@ -108,7 +108,7 @@ jobs: changed-files: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/changed-files.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/changed-files.yaml@cuda-13.1.0 with: files_yaml: | test_cpp: @@ -173,13 +173,13 @@ jobs: - '!python/nvcf_client/**' checks: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/checks.yaml@cuda-13.1.0 with: enable_check_generated_files: false conda-cpp-build: needs: [checks, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-build.yaml@cuda-13.1.0 with: build_type: pull-request script: ci/build_cpp.sh @@ -187,7 +187,7 @@ jobs: conda-cpp-tests: needs: [conda-cpp-build, changed-files, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@cuda-13.1.0 #if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_cpp with: build_type: pull-request @@ -196,7 +196,7 @@ jobs: conda-python-build: needs: [conda-cpp-build, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-build.yaml@cuda-13.1.0 with: build_type: pull-request script: ci/build_python.sh @@ -204,7 +204,7 @@ jobs: conda-python-tests: needs: [conda-python-build, changed-files, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@cuda-13.1.0 #if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python with: run_codecov: false @@ -214,7 +214,7 @@ jobs: docs-build: needs: conda-python-build secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@cuda-13.1.0 with: build_type: pull-request node_type: "gpu-l4-latest-1" @@ -226,7 +226,7 @@ jobs: wheel-build-cuopt-mps-parser: needs: compute-matrix-filters secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: pull-request script: ci/build_wheel_cuopt_mps_parser.sh @@ -238,7 +238,7 @@ jobs: wheel-build-libcuopt: needs: [wheel-build-cuopt-mps-parser, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: # build for every combination of arch and CUDA version, but only for the latest Python matrix_filter: ${{ needs.compute-matrix-filters.outputs.libcuopt_filter }} @@ -249,7 +249,7 @@ jobs: wheel-build-cuopt: needs: [wheel-build-cuopt-mps-parser, wheel-build-libcuopt, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: pull-request script: ci/build_wheel_cuopt.sh @@ -259,7 +259,7 @@ jobs: wheel-tests-cuopt: needs: [wheel-build-cuopt, wheel-build-cuopt-mps-parser, wheel-build-cuopt-sh-client, changed-files, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@cuda-13.1.0 #if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_cuopt with: build_type: pull-request @@ -268,7 +268,7 @@ jobs: wheel-build-cuopt-server: needs: [checks, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: pull-request script: ci/build_wheel_cuopt_server.sh @@ -280,7 +280,7 @@ jobs: wheel-build-cuopt-sh-client: needs: compute-matrix-filters secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-build.yaml@cuda-13.1.0 with: build_type: pull-request script: ci/build_wheel_cuopt_sh_client.sh @@ -293,7 +293,7 @@ jobs: wheel-tests-cuopt-server: needs: [wheel-build-cuopt, wheel-build-cuopt-server, changed-files, compute-matrix-filters] secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@cuda-13.1.0 #if: fromJSON(needs.changed-files.outputs.changed_file_groups).test_python_cuopt_server with: build_type: pull-request diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f4c81eb97..82748e904 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: test @@ -28,7 +28,7 @@ on: jobs: conda-cpp-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-cpp-tests.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type }} branch: ${{ inputs.branch }} @@ -37,7 +37,7 @@ jobs: script: ci/test_cpp.sh conda-python-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/conda-python-tests.yaml@cuda-13.1.0 with: run_codecov: false build_type: ${{ inputs.build_type }} @@ -47,7 +47,7 @@ jobs: script: ci/test_python.sh wheel-tests-cuopt: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type }} branch: ${{ inputs.branch }} @@ -56,7 +56,7 @@ jobs: script: ci/test_wheel_cuopt.sh wheel-tests-cuopt-server: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/wheels-test.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type }} branch: ${{ inputs.branch }} @@ -65,7 +65,7 @@ jobs: script: ci/test_wheel_cuopt_server.sh conda-notebook-tests: secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/custom-job.yaml@cuda-13.1.0 with: build_type: ${{ inputs.build_type }} branch: ${{ inputs.branch }} diff --git a/.github/workflows/trigger-breaking-change-alert.yaml b/.github/workflows/trigger-breaking-change-alert.yaml index 529f11cfa..8a5bd0e94 100644 --- a/.github/workflows/trigger-breaking-change-alert.yaml +++ b/.github/workflows/trigger-breaking-change-alert.yaml @@ -1,4 +1,4 @@ -# SPDX-FileCopyrightText: Copyright (c) 2021-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-FileCopyrightText: Copyright (c) 2021-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 name: Trigger Breaking Change Notifications @@ -15,7 +15,7 @@ jobs: trigger-notifier: if: contains(github.event.pull_request.labels.*.name, 'breaking') secrets: inherit - uses: rapidsai/shared-workflows/.github/workflows/breaking-change-alert.yaml@main + uses: rapidsai/shared-workflows/.github/workflows/breaking-change-alert.yaml@cuda-13.1.0 with: sender_login: ${{ github.event.sender.login }} sender_avatar: ${{ github.event.sender.avatar_url }} diff --git a/conda/environments/all_cuda-130_arch-aarch64.yaml b/conda/environments/all_cuda-131_arch-aarch64.yaml similarity index 96% rename from conda/environments/all_cuda-130_arch-aarch64.yaml rename to conda/environments/all_cuda-131_arch-aarch64.yaml index 938c0cce8..da9337c83 100644 --- a/conda/environments/all_cuda-130_arch-aarch64.yaml +++ b/conda/environments/all_cuda-131_arch-aarch64.yaml @@ -17,7 +17,7 @@ dependencies: - cuda-nvtx-dev - cuda-python>=13.0.1,<14.0 - cuda-sanitizer-api -- cuda-version=13.0 +- cuda-version=13.1 - cudf==26.2.*,>=0.0.0a0 - cupy>=13.6.0 - cxx-compiler @@ -75,4 +75,4 @@ dependencies: - nvidia-sphinx-theme - swagger-plugin-for-sphinx - veroviz -name: all_cuda-130_arch-aarch64 +name: all_cuda-131_arch-aarch64 diff --git a/conda/environments/all_cuda-130_arch-x86_64.yaml b/conda/environments/all_cuda-131_arch-x86_64.yaml similarity index 96% rename from conda/environments/all_cuda-130_arch-x86_64.yaml rename to conda/environments/all_cuda-131_arch-x86_64.yaml index 03030108d..c2e8d7dbc 100644 --- a/conda/environments/all_cuda-130_arch-x86_64.yaml +++ b/conda/environments/all_cuda-131_arch-x86_64.yaml @@ -17,7 +17,7 @@ dependencies: - cuda-nvtx-dev - cuda-python>=13.0.1,<14.0 - cuda-sanitizer-api -- cuda-version=13.0 +- cuda-version=13.1 - cudf==26.2.*,>=0.0.0a0 - cupy>=13.6.0 - cxx-compiler @@ -75,4 +75,4 @@ dependencies: - nvidia-sphinx-theme - swagger-plugin-for-sphinx - veroviz -name: all_cuda-130_arch-x86_64 +name: all_cuda-131_arch-x86_64 diff --git a/cpp/src/utilities/driver_helpers.cuh b/cpp/src/utilities/driver_helpers.cuh index aec097a51..8fbc10e16 100644 --- a/cpp/src/utilities/driver_helpers.cuh +++ b/cpp/src/utilities/driver_helpers.cuh @@ -1,6 +1,6 @@ /* clang-format off */ /* - * SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + * SPDX-FileCopyrightText: Copyright (c) 2025-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. * SPDX-License-Identifier: Apache-2.0 */ /* clang-format on */ @@ -15,11 +15,17 @@ namespace detail { inline auto get_driver_entry_point(const char* name) { - void* func; + void* func = nullptr; cudaDriverEntryPointQueryResult driver_status; - cudaGetDriverEntryPointByVersion(name, &func, CUDART_VERSION, cudaEnableDefault, &driver_status); + + // Request CUDA 13.0 (13000) version of symbols for Green Context API + // Green contexts are guarded by CUDART_VERSION >= 13000, so we know they're only + // used when compiled with CUDA 13.0+. Requesting v13000 ensures compatibility + // across CUDA 13.x versions (e.g., built with 13.1, run on 13.0). + cudaGetDriverEntryPointByVersion(name, &func, 13000, cudaEnableDefault, &driver_status); if (driver_status != cudaDriverEntryPointSuccess) { fprintf(stderr, "Failed to fetch symbol for %s\n", name); + return static_cast(nullptr); } return func; } diff --git a/dependencies.yaml b/dependencies.yaml index db059451a..09faffe52 100644 --- a/dependencies.yaml +++ b/dependencies.yaml @@ -7,7 +7,7 @@ files: all: output: conda matrix: - cuda: ["12.9", "13.0"] + cuda: ["12.9", "13.1"] arch: [x86_64, aarch64] includes: - build_common @@ -672,6 +672,10 @@ dependencies: cuda: "13.0" packages: - cuda-version=13.0 + - matrix: + cuda: "13.1" + packages: + - cuda-version=13.1 cuda: common: - output_types: [conda]