Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion cpp/include/cuopt/linear_programming/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@
#define CUOPT_MIP_RELATIVE_GAP "mip_relative_gap"
#define CUOPT_MIP_HEURISTICS_ONLY "mip_heuristics_only"
#define CUOPT_MIP_SCALING "mip_scaling"
#define CUOPT_SOL_FILE "solution_file"
#define CUOPT_SOLUTION_FILE "solution_file"
#define CUOPT_NUM_CPU_THREADS "num_cpu_threads"
#define CUOPT_USER_PROBLEM_FILE "user_problem_file"

/* @brief LP/MIP termination status constants */
#define CUOPT_TERIMINATION_STATUS_NO_TERMINATION 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ class mip_solver_settings_t {
bool log_to_console = true;
std::string log_file;
std::string sol_file;
std::string user_problem_file;

/** Initial primal solution */
std::shared_ptr<rmm::device_uvector<f_t>> initial_solution_;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,7 @@ class pdlp_solver_settings_t {
bool log_to_console{true};
std::string log_file{""};
std::string sol_file{""};
std::string user_problem_file{""};
bool per_constraint_residual{false};
bool crossover{false};
bool save_best_primal_so_far{false};
Expand Down
5 changes: 5 additions & 0 deletions cpp/src/linear_programming/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,11 @@ optimization_problem_solution_t<i_t, f_t> solve_lp(optimization_problem_t<i_t, f
problem.presolve_data.objective_offset,
problem.presolve_data.objective_scaling_factor);

if (settings.user_problem_file != "") {
CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str());
problem.write_as_mps(settings.user_problem_file);
}

// Set the hyper-parameters based on the solver_settings
if (use_pdlp_solver_mode) { set_pdlp_solver_mode(settings); }

Expand Down
6 changes: 4 additions & 2 deletions cpp/src/math_optimization/solver_settings.cu
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,10 @@ solver_settings_t<i_t, f_t>::solver_settings_t() : pdlp_settings(), mip_settings
string_parameters = {
{CUOPT_LOG_FILE, &mip_settings.log_file, ""},
{CUOPT_LOG_FILE, &pdlp_settings.log_file, ""},
{CUOPT_SOL_FILE, &mip_settings.sol_file, ""},
{CUOPT_SOL_FILE, &pdlp_settings.sol_file, ""}
{CUOPT_SOLUTION_FILE, &mip_settings.sol_file, ""},
{CUOPT_SOLUTION_FILE, &pdlp_settings.sol_file, ""},
{CUOPT_USER_PROBLEM_FILE, &mip_settings.user_problem_file, ""},
{CUOPT_USER_PROBLEM_FILE, &pdlp_settings.user_problem_file, ""}
};
// clang-format on
}
Expand Down
6 changes: 4 additions & 2 deletions cpp/src/mip/problem/write_mps.cu
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ void problem_t<i_t, f_t>::write_as_mps(const std::string& path)
// NAME section
mps_file << "NAME " << original_problem_ptr->get_problem_name() << "\n";

if (maximize) { mps_file << "OBJSENSE\n MAXIMIZE\n"; }

// ROWS section
mps_file << "ROWS\n";
mps_file << " N " << (objective_name.empty() ? "OBJ" : objective_name) << "\n";
Expand Down Expand Up @@ -86,7 +88,7 @@ void problem_t<i_t, f_t>::write_as_mps(const std::string& path)
// Write objective coefficient if non-zero
if (h_obj_coeffs[j] != 0.0) {
mps_file << " " << col_name << " " << (objective_name.empty() ? "OBJ" : objective_name)
<< " " << h_obj_coeffs[j] << "\n";
<< " " << (maximize ? -h_obj_coeffs[j] : h_obj_coeffs[j]) << "\n";
}

// Write constraint coefficients
Expand Down Expand Up @@ -146,7 +148,7 @@ void problem_t<i_t, f_t>::write_as_mps(const std::string& path)
h_var_ub[j] == std::numeric_limits<f_t>::infinity()) {
mps_file << " FR BOUND1 " << col_name << "\n";
} else {
if (h_var_lb[j] != 0.0) {
if (h_var_lb[j] != 0.0 || h_obj_coeffs[j] == 0.0 || h_var_types[j] != var_t::CONTINUOUS) {
if (h_var_lb[j] == -std::numeric_limits<f_t>::infinity()) {
mps_file << " MI BOUND1 " << col_name << "\n";
} else {
Expand Down
4 changes: 4 additions & 0 deletions cpp/src/mip/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ mip_solution_t<i_t, f_t> solve_mip(optimization_problem_t<i_t, f_t>& op_problem,

// have solve, problem, solution, utils etc. in common dir
detail::problem_t<i_t, f_t> problem(op_problem);
if (settings.user_problem_file != "") {
CUOPT_LOG_INFO("Writing user problem to file: %s", settings.user_problem_file.c_str());
problem.write_as_mps(settings.user_problem_file);
}

// this is for PDLP, i think this should be part of pdlp solver
setup_device_symbols(op_problem.get_handle_ptr()->get_stream());
Expand Down
45 changes: 45 additions & 0 deletions cpp/tests/mip/doc_example_test.cu
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <gtest/gtest.h>

#include <cstdint>
#include <filesystem>
#include <sstream>
#include <string>
#include <vector>
Expand Down Expand Up @@ -120,4 +121,48 @@ void test_mps_file()

TEST(docs, mixed_integer_linear_programming) { test_mps_file(); }

TEST(docs, user_problem_file)
{
const raft::handle_t handle_{};
mip_solver_settings_t<int, double> settings;
constexpr double test_time_limit = 1.;

// Create the problem from documentation example
auto problem = create_doc_example_problem();

EXPECT_FALSE(std::filesystem::exists("user_problem.mps"));

settings.time_limit = test_time_limit;
settings.user_problem_file = "user_problem.mps";
EXPECT_EQ(solve_mip(&handle_, problem, settings).get_termination_status(),
mip_termination_status_t::Optimal);

EXPECT_TRUE(std::filesystem::exists("user_problem.mps"));

cuopt::mps_parser::mps_data_model_t<int, double> problem2 =
cuopt::mps_parser::parse_mps<int, double>("user_problem.mps", false);

EXPECT_EQ(problem2.get_n_variables(), problem.get_n_variables());
EXPECT_EQ(problem2.get_n_constraints(), problem.get_n_constraints());
EXPECT_EQ(problem2.get_nnz(), problem.get_nnz());

settings.user_problem_file = "user_problem2.mps";
mip_solution_t<int, double> solution = solve_mip(&handle_, problem2, settings);
EXPECT_EQ(solution.get_termination_status(), mip_termination_status_t::Optimal);

double obj_val = solution.get_objective_value();
// Expected objective value from documentation example is approximately 303.5
EXPECT_NEAR(303.5, obj_val, 1.0);

// Get solution values
const auto& sol_values = solution.get_solution();
// x should be approximately 37 and integer
EXPECT_NEAR(37.0, sol_values.element(0, handle_.get_stream()), 0.1);
EXPECT_NEAR(std::round(sol_values.element(0, handle_.get_stream())),
sol_values.element(0, handle_.get_stream()),
settings.tolerances.integrality_tolerance); // Check x is integer
// y should be approximately 39.5
EXPECT_NEAR(39.5, sol_values.element(1, handle_.get_stream()), 0.1);
}

} // namespace cuopt::linear_programming::test
6 changes: 4 additions & 2 deletions docs/cuopt/source/cuopt-c/lp-milp/lp-milp-c-api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ The following functions are used to set and get parameters. You can find more de


Parameter Constants
-------------------
-------------------

These constants are used as the parameter name in the `cuOptSetParameter <lp-milp-c-api.html#c.cuOptSetParameter>`_ , `cuOptGetParameter <lp-milp-c-api.html#c.cuOptGetParameter>`_ and similar functions. More details on the parameters can be found in the `LP/MILP settings <../../lp-milp-settings.html>`_ section.

Expand Down Expand Up @@ -157,7 +157,9 @@ These constants are used as the parameter name in the `cuOptSetParameter <lp-mil
.. doxygendefine:: CUOPT_MIP_INTEGRALITY_TOLERANCE
.. doxygendefine:: CUOPT_MIP_SCALING
.. doxygendefine:: CUOPT_MIP_HEURISTICS_ONLY
.. doxygendefine:: CUOPT_SOLUTION_FILE
.. doxygendefine:: CUOPT_NUM_CPU_THREADS
.. doxygendefine:: CUOPT_USER_PROBLEM_FILE

PDLP Solver Mode Constants
--------------------------
Expand Down Expand Up @@ -190,7 +192,7 @@ LP and MIP solves are performed by calling the `cuOptSolve` function
Solution
--------

The output of a solve is a `cuOptSolution` object.
The output of a solve is a `cuOptSolution` object.

.. doxygentypedef:: cuOptSolution

Expand Down
11 changes: 11 additions & 0 deletions docs/cuopt/source/lp-milp-settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,17 @@ Log File

Note: the default value is ``""`` and no log file is written.

Solution File
^^^^^^^^^^^^^
``CUOPT_SOL_FILE`` controls the name of a file where cuOpt should write the solution.

Note: the default value is ``""`` and no solution file is written.

User Problem File
^^^^^^^^^^^^^^^^
``CUOPT_USER_PROBLEM_FILE`` controls the name of a file where cuOpt should write the user problem.

Note: the default value is ``""`` and no user problem file is written.

Num CPU Threads
^^^^^^^^^^^^^^^
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ cdef extern from "cuopt/linear_programming/constants.h": # noqa
cdef const char* c_CUOPT_MIP_RELATIVE_GAP "CUOPT_MIP_RELATIVE_GAP" # noqa
cdef const char* c_CUOPT_MIP_HEURISTICS_ONLY "CUOPT_MIP_HEURISTICS_ONLY" # noqa
cdef const char* c_CUOPT_MIP_SCALING "CUOPT_MIP_SCALING" # noqa
cdef const char* c_CUOPT_SOLUTION_FILE "CUOPT_SOLUTION_FILE" # noqa
cdef const char* c_CUOPT_NUM_CPU_THREADS "CUOPT_NUM_CPU_THREADS" # noqa
Comment thread
chris-maes marked this conversation as resolved.
cdef const char* c_CUOPT_USER_PROBLEM_FILE "CUOPT_USER_PROBLEM_FILE" # noqa


# Create Python string constants from C string literals
Expand Down Expand Up @@ -97,4 +99,6 @@ CUOPT_MIP_ABSOLUTE_GAP = c_CUOPT_MIP_ABSOLUTE_GAP.decode('utf-8') # noqa
CUOPT_MIP_RELATIVE_GAP = c_CUOPT_MIP_RELATIVE_GAP.decode('utf-8') # noqa
CUOPT_MIP_HEURISTICS_ONLY = c_CUOPT_MIP_HEURISTICS_ONLY.decode('utf-8') # noqa
CUOPT_MIP_SCALING = c_CUOPT_MIP_SCALING.decode('utf-8') # noqa
CUOPT_SOLUTION_FILE = c_CUOPT_SOLUTION_FILE.decode('utf-8') # noqa
CUOPT_NUM_CPU_THREADS = c_CUOPT_NUM_CPU_THREADS.decode('utf-8') # noqa
CUOPT_USER_PROBLEM_FILE = c_CUOPT_USER_PROBLEM_FILE.decode('utf-8') # noqa
Comment thread
chris-maes marked this conversation as resolved.
38 changes: 38 additions & 0 deletions python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
CUOPT_RELATIVE_DUAL_TOLERANCE,
CUOPT_RELATIVE_GAP_TOLERANCE,
CUOPT_RELATIVE_PRIMAL_TOLERANCE,
CUOPT_SOLUTION_FILE,
CUOPT_TIME_LIMIT,
CUOPT_USER_PROBLEM_FILE,
)
from cuopt.linear_programming.solver.solver_wrapper import (
ErrorStatus,
Expand Down Expand Up @@ -668,3 +670,39 @@ def test_bound_in_maximization():
upper_bound = solution.get_milp_stats()["solution_bound"]
assert upper_bound == pytest.approx(280, 1e-6)
assert solution.get_primal_objective() == pytest.approx(280, 1e-6)


def test_write_files():

file_path = (
RAPIDS_DATASET_ROOT_DIR + "/linear_programming/afiro_original.mps"
)
data_model_obj = cuopt_mps_parser.ParseMps(file_path)

settings = solver_settings.SolverSettings()
settings.set_parameter(CUOPT_METHOD, SolverMethod.DualSimplex)
settings.set_parameter(CUOPT_USER_PROBLEM_FILE, "afiro_out.mps")

solver.Solve(data_model_obj, settings)

assert os.path.isfile("afiro_out.mps")

afiro = cuopt_mps_parser.ParseMps("afiro_out.mps")
os.remove("afiro_out.mps")

settings.set_parameter(CUOPT_USER_PROBLEM_FILE, "")
settings.set_parameter(CUOPT_SOLUTION_FILE, "afiro.sol")

solution = solver.Solve(afiro, settings)

assert solution.get_termination_status() == LPTerminationStatus.Optimal
assert solution.get_primal_objective() == pytest.approx(-464.7531)

assert os.path.isfile("afiro.sol")

with open("afiro.sol") as f:
for line in f:
if "X01" in line:
assert float(line.split()[-1]) == pytest.approx(80)

os.remove("afiro.sol")