Skip to content

Commit b524c2a

Browse files
authored
Integrate PSLP Presolver (#41)
* add PSLP presolver * fix: correct the reduced cost in PSLP postsolve * add presolve stats * test PSLP relax_bounds false * remove PSLP_INF * add presolve_setup_time and presolve_time record, improve reduce prob memory * set relax_bounds to True (default) * fix Segmentation fault when presolve detected infeasible * refactor: PSLP presolve * add verbose check for log * set use_presolve to true by default * rename use_presolve to presolve * refactor: add presolve in python interface * docs: add presolve in readme * feat: release v0.2.0 * fix: off-by-one iteration error and ensure consistent final metrics
1 parent f4dd88d commit b524c2a

File tree

14 files changed

+552
-139
lines changed

14 files changed

+552
-139
lines changed

CMakeLists.txt

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ cmake_minimum_required(VERSION 3.20)
77
project(cupdlpx LANGUAGES C CXX CUDA)
88

99
set(CUPDLPX_VERSION_MAJOR 0)
10-
set(CUPDLPX_VERSION_MINOR 1)
11-
set(CUPDLPX_VERSION_PATCH 5)
10+
set(CUPDLPX_VERSION_MINOR 2)
11+
set(CUPDLPX_VERSION_PATCH 0)
1212

1313
set(CUPDLPX_VERSION "${CUPDLPX_VERSION_MAJOR}.${CUPDLPX_VERSION_MINOR}.${CUPDLPX_VERSION_PATCH}")
1414
add_compile_definitions(CUPDLPX_VERSION="${CUPDLPX_VERSION}")
@@ -75,6 +75,20 @@ if (CUPDLPX_BUILD_PYTHON)
7575
find_package(Python3 COMPONENTS Interpreter REQUIRED) # For versioning script and pybind11
7676
endif()
7777

78+
include(FetchContent)
79+
80+
set(PSLP_VERSION_TAG "v0.0.2")
81+
82+
FetchContent_Declare(
83+
pslp
84+
GIT_REPOSITORY https://github.com/dance858/PSLP.git
85+
GIT_TAG ${PSLP_VERSION_TAG}
86+
)
87+
88+
FetchContent_MakeAvailable(pslp)
89+
include_directories(${pslp_SOURCE_DIR}/PSLP)
90+
add_compile_definitions(PSLP_VERSION=\"${PSLP_VERSION_TAG}\")
91+
7892
# -----------------------------------------------------------------------------
7993
# SOURCE DISCOVERY & TARGET DEFINITION
8094
# -----------------------------------------------------------------------------
@@ -100,6 +114,7 @@ set(CORE_LINK_LIBS PUBLIC
100114
CUDA::cublas
101115
CUDA::cusparse
102116
ZLIB::ZLIB
117+
PSLP
103118
)
104119

105120
# -----------------------------------------------------------------------------

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ After building the project, the `./build/cupdlpx` binary can be invoked from the
9494
| `--eval_freq` | `int` | Termination evaluation frequency | `200` |
9595
| `--sv_max_iter` | `int` | Max iterations for singular value estimation | `5000` |
9696
| `--sv_tol` | `float` | Tolerance for singular value estimation | `1e-4` |
97+
| `--no_presolve` | `flag` | Disable presolve | `enabled` |
9798
| `-f`,`--feasibility_polishing` |`flag` | Run the polishing loop | `false` |
9899
| `--eps_feas_polish` | `double` | Relative tolerance for polishing | `1e-6` |
99100

include/cupdlpx_types.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
#pragma once
1818

19+
#include "PSLP_stats.h"
1920
#include <stdbool.h>
2021
#include <stdlib.h>
2122

@@ -30,6 +31,7 @@ extern "C"
3031
TERMINATION_REASON_OPTIMAL,
3132
TERMINATION_REASON_PRIMAL_INFEASIBLE,
3233
TERMINATION_REASON_DUAL_INFEASIBLE,
34+
TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED,
3335
TERMINATION_REASON_TIME_LIMIT,
3436
TERMINATION_REASON_ITERATION_LIMIT,
3537
TERMINATION_REASON_FEAS_POLISH_SUCCESS
@@ -91,19 +93,28 @@ extern "C"
9193
restart_parameters_t restart_params;
9294
double reflection_coefficient;
9395
bool feasibility_polishing;
96+
bool presolve;
9497
} pdhg_parameters_t;
9598

9699
typedef struct
97100
{
98101
int num_variables;
99102
int num_constraints;
103+
int num_nonzeros;
104+
105+
int num_reduced_variables;
106+
int num_reduced_constraints;
107+
int num_reduced_nonzeros;
100108

101109
double *primal_solution;
102110
double *dual_solution;
111+
double *reduced_cost;
103112

104113
int total_count;
105114
double rescaling_time_sec;
106115
double cumulative_time_sec;
116+
double presolve_time;
117+
int presolve_status;
107118

108119
double absolute_primal_residual;
109120
double relative_primal_residual;

internal/presolve.h

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#ifndef PRESOLVE_H
2+
#define PRESOLVE_H
3+
4+
#include "PSLP_API.h"
5+
#include "PSLP_stats.h"
6+
#include "cupdlpx.h"
7+
8+
#ifdef __cplusplus
9+
extern "C"
10+
{
11+
#endif
12+
13+
typedef struct
14+
{
15+
Presolver *presolver;
16+
Settings *settings;
17+
lp_problem_t *reduced_problem;
18+
bool problem_solved_during_presolve;
19+
double presolve_time;
20+
char presolve_status;
21+
} cupdlpx_presolve_info_t;
22+
23+
cupdlpx_presolve_info_t *pslp_presolve(const lp_problem_t *original_prob, const pdhg_parameters_t *params);
24+
25+
cupdlpx_result_t *create_result_from_presolve(const cupdlpx_presolve_info_t *info, const lp_problem_t *original_prob);
26+
27+
const char *get_presolve_status_str(enum PresolveStatus_ status);
28+
29+
void pslp_postsolve(cupdlpx_presolve_info_t *info,
30+
cupdlpx_result_t *reduced_result,
31+
const lp_problem_t *original_prob);
32+
33+
void cupdlpx_presolve_info_free(cupdlpx_presolve_info_t *info);
34+
35+
#ifdef __cplusplus
36+
}
37+
#endif
38+
39+
#endif // PRESOLVE_H

internal/utils.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,7 @@ extern "C"
9999

100100
void print_initial_info(const pdhg_parameters_t *params, const lp_problem_t *problem);
101101

102-
void pdhg_final_log(
103-
const pdhg_solver_state_t *solver_state,
104-
bool verbose,
105-
termination_reason_t termination_reason);
102+
void pdhg_final_log(const cupdlpx_result_t *result, const pdhg_parameters_t *params);
106103

107104
void display_iteration_stats(const pdhg_solver_state_t *solver_state, bool verbose);
108105

@@ -132,7 +129,7 @@ extern "C"
132129

133130
void print_initial_feas_polish_info(bool is_primal_polish, const pdhg_parameters_t *params);
134131

135-
void display_feas_polish_iteration_stats(const pdhg_solver_state_t *state, bool verbose, bool is_primal_polish);
132+
void display_feas_polish_iteration_stats(const pdhg_solver_state_t *state, bool verbose, bool is_primal_polish);
136133

137134
void pdhg_feas_polish_final_log(const pdhg_solver_state_t *primal_state, const pdhg_solver_state_t *dual_state, bool verbose);
138135

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
44

55
[project]
66
name = "cupdlpx"
7-
version = "0.1.5"
7+
version = "0.2.0"
88
description = "Python bindings for cuPDLPx (GPU-accelerated first-order LP solver)"
99
readme = "README.md"
1010
license = { text = "Apache-2.0" }

python/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,11 @@ Below is a list of commonly used parameters, their internal keys, and descriptio
153153
| `RestartNecessaryReduction` | `necessary_reduction_for_restart` | float | `0.5` | Necessary reduction factor required for a restart. |
154154
| `RestartKp` | `k_p` | float | `0.99` | Proportional coefficient for PID-controlled primal weight updates. |
155155
| `ReflectionCoeff` | `reflection_coefficient` | float | `1.0` | Reflection coefficient. |
156-
| `FeasibilityPolishing` | `feasibility_polishing` | bool | `False` | Run feasibility polishing process.|
157-
| `FeasibilityPolishingTol` | `eps_feas_polish_relative` | float | `1e-6` | Relative tolerance for primal/dual residual. |
158156
| `SVMaxIter` | `sv_max_iter` | int | 5000 | Maximum number of iterations for the power method |
159157
| `SVTol`| `sv_tol` | float | `1e-4` | Termination tolerance for the power method |
158+
| `Presolve`| `presolve` | float | `True` | Whether to use presolve. |
159+
| `FeasibilityPolishing` | `feasibility_polishing` | bool | `False` | Run feasibility polishing process.|
160+
| `FeasibilityPolishingTol` | `eps_feas_polish_relative` | float | `1e-6` | Relative tolerance for primal/dual residual. |
160161

161162
They can be set in multiple ways:
162163

python/cupdlpx/PDLP.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,6 @@
5656
# singular value estimation (power method)
5757
"SVMaxIter": "sv_max_iter",
5858
"SVTol": "sv_tol",
59+
# presolve
60+
"Presolve": "presolve",
5961
}

python_bindings/_core_bindings.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ static const char *status_to_str(termination_reason_t r)
170170
return "PRIMAL_INFEASIBLE";
171171
case TERMINATION_REASON_DUAL_INFEASIBLE:
172172
return "DUAL_INFEASIBLE";
173+
case TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED:
174+
return "INFEASIBLE_OR_UNBOUNDED";
173175
case TERMINATION_REASON_TIME_LIMIT:
174176
return "TIME_LIMIT";
175177
case TERMINATION_REASON_ITERATION_LIMIT:
@@ -198,6 +200,8 @@ static int status_to_code(termination_reason_t r)
198200
return 3;
199201
case TERMINATION_REASON_ITERATION_LIMIT:
200202
return 4;
203+
case TERMINATION_REASON_INFEASIBLE_OR_UNBOUNDED:
204+
return 5;
201205
case TERMINATION_REASON_UNSPECIFIED:
202206
default:
203207
return -1;
@@ -247,6 +251,9 @@ static py::dict get_default_params_py()
247251
d["sv_max_iter"] = p.sv_max_iter;
248252
d["sv_tol"] = p.sv_tol;
249253

254+
// presolve
255+
d["presolve"] = p.presolve;
256+
250257
return d;
251258
}
252259

@@ -299,6 +306,9 @@ static void parse_params_from_python(py::object params_obj, pdhg_parameters_t *p
299306
// power method for singular value estimation
300307
geti("sv_max_iter", p->sv_max_iter);
301308
getf("sv_tol", p->sv_tol);
309+
310+
// presolve
311+
getb("presolve", p->presolve);
302312
}
303313

304314
// view of matrix from Python

src/cli.c

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ limitations under the License.
1616

1717
#include "cupdlpx.h"
1818
#include "mps_parser.h"
19+
#include "presolve.h"
1920
#include "solver.h"
2021
#include "utils.h"
2122
#include <getopt.h>
@@ -61,7 +62,7 @@ void save_solution(const double *data, int size, const char *output_dir,
6162
const char *instance_name, const char *suffix)
6263
{
6364
char *file_path = get_output_path(output_dir, instance_name, suffix);
64-
if (file_path == NULL)
65+
if (file_path == NULL || data == NULL)
6566
{
6667
return;
6768
}
@@ -113,7 +114,19 @@ void save_solver_summary(const cupdlpx_result_t *result, const char *output_dir,
113114
fprintf(outfile, "Absolute Objective Gap: %e\n", result->objective_gap);
114115
fprintf(outfile, "Relative Objective Gap: %e\n",
115116
result->relative_objective_gap);
116-
if(result->feasibility_polishing_time > 0.0){
117+
fprintf(outfile, "Rows: %d\n", result->num_constraints);
118+
fprintf(outfile, "Columns: %d\n", result->num_variables);
119+
fprintf(outfile, "Nonzeros: %d\n", result->num_nonzeros);
120+
if (result->presolve_time > 0.0)
121+
{
122+
fprintf(outfile, "Presolve Status: %s\n", get_presolve_status_str(result->presolve_status));
123+
fprintf(outfile, "Presolve Time (sec): %e\n", result->presolve_time);
124+
fprintf(outfile, "Reduced Rows: %d\n", result->num_reduced_constraints);
125+
fprintf(outfile, "Reduced Columns: %d\n", result->num_reduced_variables);
126+
fprintf(outfile, "Reduced Nonzeros: %d\n", result->num_reduced_nonzeros);
127+
}
128+
if (result->feasibility_polishing_time > 0.0)
129+
{
117130
fprintf(outfile, "Feasibility Polishing Time (sec): %e\n", result->feasibility_polishing_time);
118131
fprintf(outfile, "Feasibility Polishing Iteration Count: %d\n", result->feasibility_iteration);
119132
}
@@ -171,6 +184,8 @@ void print_usage(const char *prog_name)
171184
"Enable feasibility use feasibility polishing (default: false).\n");
172185
fprintf(stderr, " --eps_feas_polish <tolerance> Relative feasibility "
173186
"polish tolerance (default: 1e-6).\n");
187+
fprintf(stderr, " --no_presolve "
188+
"Disable presolve (default: enabled).\n");
174189
}
175190

176191
int main(int argc, char *argv[])
@@ -195,10 +210,11 @@ int main(int argc, char *argv[])
195210
{"sv_max_iter", required_argument, 0, 1011},
196211
{"sv_tol", required_argument, 0, 1012},
197212
{"eval_freq", required_argument, 0, 1013},
213+
{"no_presolve", no_argument, 0, 1014},
198214
{0, 0, 0, 0}};
199215

200216
int opt;
201-
while ((opt = getopt_long(argc, argv, "hvf", long_options, NULL)) != -1)
217+
while ((opt = getopt_long(argc, argv, "hvfp", long_options, NULL)) != -1)
202218
{
203219
switch (opt)
204220
{
@@ -226,7 +242,7 @@ int main(int argc, char *argv[])
226242
case 1006: // --eps_feas_polish_relative
227243
params.termination_criteria.eps_feas_polish_relative = atof(optarg);
228244
break;
229-
case 'f': // --feasibility_polishing
245+
case 'f': // --feasibility_polishing
230246
params.feasibility_polishing = true;
231247
break;
232248
case 1007: // --l_inf_ruiz_iter
@@ -250,6 +266,9 @@ int main(int argc, char *argv[])
250266
case 1013: // --eval_freq
251267
params.termination_evaluation_frequency = atoi(optarg);
252268
break;
269+
case 1014: // --no_presolve
270+
params.presolve = false;
271+
break;
253272
case '?': // Unknown option
254273
return 1;
255274
}

0 commit comments

Comments
 (0)