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
9 changes: 5 additions & 4 deletions benchmarks/linear_programming/cuopt/run_pdlp.cu
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ static void parse_arguments(argparse::ArgumentParser& program)

program.add_argument("--time-limit")
.help("Time limit in seconds")
.default_value(3600)
.scan<'f', double>();
.default_value(3600.0)
.scan<'g', double>();

program.add_argument("--iteration-limit")
.help("Iteration limit")
Expand All @@ -52,7 +52,7 @@ static void parse_arguments(argparse::ArgumentParser& program)
program.add_argument("--optimality-tolerance")
.help("Optimality tolerance")
.default_value(1e-4)
.scan<'f', double>();
.scan<'g', double>();

program.add_argument("--pdlp-solver-mode")
.help("Solver mode for PDLP. Possible values: Stable2 (default), Methodical1, Fast1")
Expand Down Expand Up @@ -102,7 +102,8 @@ static cuopt::linear_programming::pdlp_solver_settings_t<int, double> create_sol
settings.time_limit = program.get<double>("--time-limit");
settings.iteration_limit = program.get<int>("--iteration-limit");
settings.set_optimality_tolerance(program.get<double>("--optimality-tolerance"));
settings.solver_mode = string_to_pdlp_solver_mode(program.get<std::string>("--pdlp-solver-mode"));
settings.pdlp_solver_mode =
string_to_pdlp_solver_mode(program.get<std::string>("--pdlp-solver-mode"));
settings.method = static_cast<cuopt::linear_programming::method_t>(program.get<int>("--method"));
settings.crossover = program.get<int>("--crossover");

Expand Down
33 changes: 18 additions & 15 deletions cpp/include/cuopt/linear_programming/pdlp/solver_solution.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -62,41 +62,44 @@ class optimization_problem_solution_t : public base_solution_t {
*/
struct additional_termination_information_t {
/** Number of pdlp steps taken before termination */
i_t number_of_steps_taken;
i_t number_of_steps_taken{-1};
/** Number of pdhg steps taken before termination */
i_t total_number_of_attempted_steps;
i_t total_number_of_attempted_steps{-1};
/** L2 norm of the primal residual (absolute primal residual) */
f_t l2_primal_residual;
f_t l2_primal_residual{std::numeric_limits<f_t>::signaling_NaN()};
/** L2 norm of the primal residual divided by the L2 norm of the right hand side (b) */
f_t l2_relative_primal_residual;
f_t l2_relative_primal_residual{std::numeric_limits<f_t>::signaling_NaN()};
/** L2 norm of the dual residual */
f_t l2_dual_residual;
f_t l2_dual_residual{std::numeric_limits<f_t>::signaling_NaN()};
/** L2 norm of the dual residual divided by the L2 norm of the objective coefficient (c) */
f_t l2_relative_dual_residual;
f_t l2_relative_dual_residual{std::numeric_limits<f_t>::signaling_NaN()};

/** Primal Objective */
f_t primal_objective;
f_t primal_objective{std::numeric_limits<f_t>::signaling_NaN()};
/** Dual Objective */
f_t dual_objective;
f_t dual_objective{std::numeric_limits<f_t>::signaling_NaN()};

/** Gap between primal and dual objective value */

f_t gap;
f_t gap{std::numeric_limits<f_t>::signaling_NaN()};
/** Gap divided by the absolute sum of the primal and dual objective values */
f_t relative_gap;
f_t relative_gap{std::numeric_limits<f_t>::signaling_NaN()};

/** Maximum error for the linear constraints and sign constraints */
f_t max_primal_ray_infeasibility;
f_t max_primal_ray_infeasibility{std::numeric_limits<f_t>::signaling_NaN()};
/** Objective value for the extreme primal ray */
f_t primal_ray_linear_objective;
f_t primal_ray_linear_objective{std::numeric_limits<f_t>::signaling_NaN()};

/** Maximum constraint error */
f_t max_dual_ray_infeasibility;
f_t max_dual_ray_infeasibility{std::numeric_limits<f_t>::signaling_NaN()};
/** Objective value for the extreme dual ray */
f_t dual_ray_linear_objective;
f_t dual_ray_linear_objective{std::numeric_limits<f_t>::signaling_NaN()};

/** Solve time in seconds */
double solve_time;
double solve_time{std::numeric_limits<double>::signaling_NaN()};

/** Whether the problem was solved by PDLP or Dual Simplex */
bool solved_by_pdlp{false};
};

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ struct linear_programming_ret_t {
double gap_;
int nb_iterations_;
double solve_time_;
bool solved_by_pdlp_;
};

struct mip_ret_t {
Expand Down
1 change: 1 addition & 0 deletions cpp/src/linear_programming/solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,7 @@ optimization_problem_solution_t<i_t, f_t> convert_dual_simplex_sol(
info.primal_objective = solution.user_objective;
info.solve_time = duration;
info.number_of_steps_taken = solution.iterations;
info.solved_by_pdlp = false;

pdlp_termination_status_t termination_status = to_termination_status(status);
auto sol = optimization_problem_solution_t<i_t, f_t>(final_primal_solution,
Expand Down
66 changes: 35 additions & 31 deletions cpp/src/linear_programming/solver_solution.cu
Original file line number Diff line number Diff line change
Expand Up @@ -152,37 +152,41 @@ void optimization_problem_solution_t<i_t, f_t>::write_additional_termination_sta
myfile << "\t\"Additional termination information\" : { " << std::endl;
myfile << "\t\"Number of steps taken\" : " << termination_stats_.number_of_steps_taken << ","
<< std::endl;
myfile << "\t\"Total number of attempted steps\" : "
<< termination_stats_.total_number_of_attempted_steps << "," << std::endl;
myfile << "\t\"Total solve time (ms)\" : " << termination_stats_.solve_time << "," << std::endl;

myfile << "\t\t\"Convergence measures\" : { " << std::endl;
myfile << "\t\t\t\"Absolute primal residual\" : " << termination_stats_.l2_primal_residual << ","
<< std::endl;
myfile << "\t\t\t\"Relative primal residual\" : "
<< termination_stats_.l2_relative_primal_residual << "," << std::endl;
myfile << "\t\t\t\"Absolute dual residual\" : " << termination_stats_.l2_dual_residual << ","
<< std::endl;
myfile << "\t\t\t\"Relative dual residual\" : " << termination_stats_.l2_relative_dual_residual
<< "," << std::endl;
myfile << "\t\t\t\"Primal objective value\" : " << termination_stats_.primal_objective << ","
<< std::endl;
myfile << "\t\t\t\"Dual objective value\" : " << termination_stats_.dual_objective << ","
<< std::endl;
myfile << "\t\t\t\"Gap\" : " << termination_stats_.gap << std::endl;
myfile << "\t\t\t\"Relative gap\" : " << termination_stats_.relative_gap << "," << std::endl;
myfile << "\t\t}, " << std::endl;

myfile << "\t\t\"Infeasibility measures\" : {" << std::endl;
myfile << "\t\t\t\"Maximum error for the linear constraints and sign constraints\" : "
<< termination_stats_.max_primal_ray_infeasibility << "," << std::endl;
myfile << "\t\t\t\"Objective value for the extreme primal ray\" : "
<< termination_stats_.primal_ray_linear_objective << "," << std::endl;
myfile << "\t\t\t\"Maximum constraint error\" : " << termination_stats_.max_dual_ray_infeasibility
<< "," << std::endl;
myfile << "\t\t\t\"Objective value for the extreme dual ray\" : "
<< termination_stats_.dual_ray_linear_objective << std::endl;
myfile << "\t\t} " << std::endl;
if (termination_stats_.solved_by_pdlp) {
myfile << "\t\"Total number of attempted steps\" : "
<< termination_stats_.total_number_of_attempted_steps << "," << std::endl;
}
myfile << "\t\"Total solve time\" : " << termination_stats_.solve_time;
if (termination_stats_.solved_by_pdlp) {
myfile << "," << std::endl;
myfile << "\t\t\"Convergence measures\" : { " << std::endl;
myfile << "\t\t\t\"Absolute primal residual\" : " << termination_stats_.l2_primal_residual
<< "," << std::endl;
myfile << "\t\t\t\"Relative primal residual\" : "
<< termination_stats_.l2_relative_primal_residual << "," << std::endl;
myfile << "\t\t\t\"Absolute dual residual\" : " << termination_stats_.l2_dual_residual << ","
<< std::endl;
myfile << "\t\t\t\"Relative dual residual\" : " << termination_stats_.l2_relative_dual_residual
<< "," << std::endl;
myfile << "\t\t\t\"Primal objective value\" : " << termination_stats_.primal_objective << ","
<< std::endl;
myfile << "\t\t\t\"Dual objective value\" : " << termination_stats_.dual_objective << ","
<< std::endl;
myfile << "\t\t\t\"Gap\" : " << termination_stats_.gap << "," << std::endl;
myfile << "\t\t\t\"Relative gap\" : " << termination_stats_.relative_gap << std::endl;
myfile << "\t\t}, " << std::endl;
myfile << "\t\t\"Infeasibility measures\" : {" << std::endl;
myfile << "\t\t\t\"Maximum error for the linear constraints and sign constraints\" : "
<< termination_stats_.max_primal_ray_infeasibility << "," << std::endl;
myfile << "\t\t\t\"Objective value for the extreme primal ray\" : "
<< termination_stats_.primal_ray_linear_objective << "," << std::endl;
myfile << "\t\t\t\"Maximum constraint error\" : "
<< termination_stats_.max_dual_ray_infeasibility << "," << std::endl;
myfile << "\t\t\t\"Objective value for the extreme dual ray\" : "
<< termination_stats_.dual_ray_linear_objective << std::endl;
myfile << "\t\t} " << std::endl;
} else
myfile << std::endl;

myfile << "\t} " << std::endl;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ pdlp_termination_strategy_t<i_t, f_t>::fill_return_problem_solution(
infeasibility_information_view.dual_ray_linear_objective,
1,
stream_view_);
term_stats.solved_by_pdlp = (termination_status != pdlp_termination_status_t::ConcurrentLimit);

RAFT_CUDA_TRY(cudaStreamSynchronize(stream_view_));

Expand Down
3 changes: 2 additions & 1 deletion cpp/src/linear_programming/utilities/cython_solve.cu
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ linear_programming_ret_t call_solve_lp(
solution.get_additional_termination_information().dual_objective,
solution.get_additional_termination_information().gap,
solution.get_additional_termination_information().number_of_steps_taken,
solution.get_additional_termination_information().solve_time};
solution.get_additional_termination_information().solve_time,
solution.get_additional_termination_information().solved_by_pdlp};

return lp_ret;
}
Expand Down
12 changes: 11 additions & 1 deletion python/cuopt/cuopt/linear_programming/solution/solution.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,9 @@ class Solution:
Note: Applicable to only MILP
Time used for pre-solve
solve_time: Float64
Solve time in milliseconds
Solve time in seconds
solved_by_pdlp: bool
Whether the problem was solved by PDLP or Dual Simplex
"""

def __init__(
Expand Down Expand Up @@ -164,6 +166,7 @@ def __init__(
dual_objective=0.0,
gap=0.0,
nb_iterations=0,
solved_by_pdlp=None,
mip_gap=0.0,
solution_bound=0.0,
presolve_time=0.0,
Expand Down Expand Up @@ -202,6 +205,7 @@ def __init__(
self.primal_objective = primal_objective
self.dual_objective = dual_objective
self.solve_time = solve_time
self.solved_by_pdlp = solved_by_pdlp
self.vars = vars
self.lp_stats = {
"primal_residual": primal_residual,
Expand Down Expand Up @@ -297,6 +301,12 @@ def get_solve_time(self):
"""
return self.solve_time

def get_solved_by_pdlp(self):
"""
Returns whether the problem was solved by PDLP or Dual Simplex
"""
return self.solved_by_pdlp

def get_vars(self):
"""
Returns the dictionnary mapping each variable (name) to its value.
Expand Down
1 change: 1 addition & 0 deletions python/cuopt/cuopt/linear_programming/solver/solver.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ cdef extern from "cuopt/linear_programming/utilities/cython_solve.hpp" namespace
double gap_
int nb_iterations_
double solve_time_
bool solved_by_pdlp_

cdef cppclass mip_ret_t:
unique_ptr[device_buffer] solution_
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr,
gap = sol_ret.lp_ret.gap_
nb_iterations = sol_ret.lp_ret.nb_iterations_
solve_time = sol_ret.lp_ret.solve_time_
solved_by_pdlp = sol_ret.lp_ret.solved_by_pdlp_

# In BatchSolve, we don't get the warm start data
if not is_batch:
Expand Down Expand Up @@ -647,6 +648,7 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr,
dual_objective,
gap,
nb_iterations,
solved_by_pdlp,
)
return Solution(
problem_category=ProblemCategory(sol_ret.problem_type),
Expand All @@ -664,6 +666,7 @@ cdef create_solution(unique_ptr[solver_ret_t] sol_ret_ptr,
dual_objective=dual_objective,
gap=gap,
nb_iterations=nb_iterations,
solved_by_pdlp=solved_by_pdlp,
)


Expand Down
14 changes: 7 additions & 7 deletions python/cuopt/cuopt/tests/linear_programming/test_lp_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def test_solver():

settings = solver_settings.SolverSettings()
settings.set_optimality_tolerance(1e-2)
settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP)

solution = solver.Solve(data_model_obj, settings)
assert solution.get_termination_reason() == "Optimal"
Expand All @@ -81,6 +82,7 @@ def test_solver():
assert solution.get_primal_objective() == pytest.approx(0.0)
assert solution.get_dual_objective() == pytest.approx(0.0)
assert solution.get_lp_stats()["gap"] == pytest.approx(0.0)
assert solution.get_solved_by_pdlp()


def test_parser_and_solver():
Expand Down Expand Up @@ -379,9 +381,6 @@ def test_check_data_model_validity():
solver.Solve(data_model_obj)


@pytest.mark.skip(
reason="skip until with can pick Method=PDLP on the Python side"
) # noqa
def test_parse_var_names():
file_path = (
RAPIDS_DATASET_ROOT_DIR + "/linear_programming/afiro_original.mps"
Expand Down Expand Up @@ -426,7 +425,9 @@ def test_parse_var_names():
for i, name in enumerate(data_model_obj.get_variable_names()):
assert expected_names[i] == name

solution = solver.Solve(data_model_obj)
settings = solver_settings.SolverSettings()
settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP)
solution = solver.Solve(data_model_obj, settings)

expected_dict = {
"X01": 80.00603991232295,
Expand Down Expand Up @@ -472,9 +473,6 @@ def test_parse_var_names():
)


@pytest.mark.skip(
reason="skip until with can pick Method=PDLP on the Python side"
) # noqa
def test_parser_and_batch_solver():

data_model_list = []
Expand All @@ -488,6 +486,7 @@ def test_parser_and_batch_solver():
data_model_list.append(cuopt_mps_parser.ParseMps(file_path))

settings = solver_settings.SolverSettings()
settings.set_parameter(CUOPT_METHOD, SolverMethod.PDLP)
settings.set_optimality_tolerance(1e-4)

# Call BatchSolve
Expand Down Expand Up @@ -602,6 +601,7 @@ def test_dual_simplex():

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


def test_heuristics_only():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,8 @@ def create_solution_obj(solver_response):
solve_time=sol["solver_time"],
termination_status=status,
primal_solution=np.array(sol["primal_solution"]),
reduced_cost=np.array(sol["reduced_cost"]),
primal_objective=sol["primal_objective"],
dual_objective=sol["dual_objective"],
mip_gap=sol["milp_statistics"]["mip_gap"],
solution_bound=sol["milp_statistics"]["solution_bound"],
presolve_time=sol["milp_statistics"]["presolve_time"],
Expand All @@ -192,6 +192,10 @@ def create_solution_obj(solver_response):
max_variable_bound_violation=sol["milp_statistics"][
"max_variable_bound_violation"
],
num_nodes=sol["milp_statistics"]["num_nodes"],
num_simplex_iterations=sol["milp_statistics"][
"num_simplex_iterations"
],
)
else:
solution_obj = solution.Solution(
Expand All @@ -201,12 +205,14 @@ def create_solution_obj(solver_response):
termination_status=status,
primal_solution=np.array(sol["primal_solution"]),
dual_solution=np.array(sol["dual_solution"]),
reduced_cost=np.array(sol["lp_statistics"]["reduced_cost"]),
reduced_cost=np.array(sol["reduced_cost"]),
primal_residual=sol["lp_statistics"]["primal_residual"],
dual_residual=sol["lp_statistics"]["dual_residual"],
gap=sol["lp_statistics"]["gap"],
nb_iterations=sol["lp_statistics"]["nb_iterations"],
primal_objective=sol["primal_objective"],
dual_objective=sol["dual_objective"],
gap=sol["lp_statistics"]["gap"],
solved_by_pdlp=sol["solved_by_pdlp"],
)
return status, solution_obj

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,12 @@ class SolutionData(StrictModel):
default=None,
description=("Returns the engine solve time in seconds"),
)
solved_by_pdlp: bool = Field(
default=None,
description=(
"Returns whether problem was solved by PDLP or Dual Simplex"
),
)
primal_objective: float = Field(
default=None,
description=("Primal objective of the LP problem"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,7 @@ def create_solution(sol):
sol.get_dual_objective
)
solution["solver_time"] = sol.get_solve_time()
solution["solved_by_pdlp"] = sol.get_solved_by_pdlp()
solution["vars"] = sol.get_vars()
solution["lp_statistics"] = {} if lp_stats is None else lp_stats
solution["reduced_cost"] = reduced_cost
Expand Down