Skip to content

Conversation

@ZedongPeng
Copy link
Collaborator

Description

This PR adds optional support for the PSLP presolver in cuPDLPx. When enabled with -p, the problem is presolved with PSLP before being sent to the GPU.

Summary

The integration works correctly, and PSLP significantly reduces the problem size (rows/cols/nnz). However, the presolved problem currently takes more PDHG iterations to converge.

Any feedback on whether this behavior is expected with PSLP would be appreciated. @dance858

Output

Without PSLP presolve.

$ ./build/cupdlpx L1_sixm250obs.mps.gz test/ -v
---------------------------------------------------------------------------------------
                                    cuPDLPx v0.1.3                                    
                        A GPU-Accelerated First-Order LP Solver                        
               (c) Haihao Lu, Massachusetts Institute of Technology, 2025              
---------------------------------------------------------------------------------------
problem:
  variables     : 428032
  constraints   : 986069
  nnz(A)        : 4280320
settings:
  iter_limit         : 2147483647
  time_limit         : 3600.00 sec
  eps_opt            : 1.0e-04
  eps_feas           : 1.0e-04
  eps_infeas_detect  : 1.0e-10
---------------------------------------------------------------------------------------
   runtime     |     objective      |   absolute residuals    |   relative residuals    
  iter   time  |  pr obj    du obj  |  pr res  du res   gap   |  pr res  du res   gap   
---------------------------------------------------------------------------------------
     0 4.2e-04 |  0.0e+00   0.0e+00 | 0.0e+00 4.1e+00 0.0e+00 | 0.0e+00 8.1e-01 0.0e+00 
    10 2.9e-03 |  5.3e+03   6.6e+08 | 4.7e+03 4.0e+04 6.6e+08 | 4.7e+03 7.7e+03 1.0e+00 
    20 5.3e-03 |  6.8e+03   3.3e+08 | 1.9e+03 1.9e+04 3.3e+08 | 1.9e+03 3.7e+03 1.0e+00 
    30 7.8e-03 |  8.2e+03   2.3e+08 | 9.6e+02 1.3e+04 2.3e+08 | 9.6e+02 2.6e+03 1.0e+00 
    40 1.0e-02 |  8.4e+03   1.5e+08 | 1.2e+03 9.0e+03 1.5e+08 | 1.2e+03 1.7e+03 1.0e+00 
    50 1.3e-02 |  8.0e+03   1.0e+08 | 4.6e+02 6.8e+03 1.0e+08 | 4.6e+02 1.3e+03 1.0e+00 
    60 1.5e-02 |  7.5e+03   9.5e+07 | 6.2e+02 5.8e+03 9.5e+07 | 6.2e+02 1.1e+03 1.0e+00 
    70 1.8e-02 |  7.5e+03   9.0e+07 | 4.7e+02 5.2e+03 9.0e+07 | 4.7e+02 1.0e+03 1.0e+00 
    80 2.0e-02 |  7.9e+03   8.4e+07 | 2.2e+02 4.8e+03 8.4e+07 | 2.2e+02 9.3e+02 1.0e+00 
    90 2.3e-02 |  8.1e+03   6.7e+07 | 4.4e+02 3.9e+03 6.7e+07 | 4.4e+02 7.6e+02 1.0e+00 
   100 2.5e-02 |  7.9e+03   5.3e+07 | 2.1e+02 3.4e+03 5.3e+07 | 2.1e+02 6.5e+02 1.0e+00 
...
 10000 2.2e+00 |  4.6e+03   4.6e+03 | 8.4e-03 3.0e-06 2.3e-01 | 8.4e-03 5.8e-07 2.5e-05 
 11000 2.4e+00 |  4.6e+03   4.6e+03 | 9.1e-03 2.7e-08 6.2e-04 | 9.1e-03 5.3e-09 6.7e-08 
 12000 2.6e+00 |  4.6e+03   4.6e+03 | 1.5e-04 1.1e-09 1.2e-04 | 1.5e-04 2.2e-10 1.3e-08 
---------------------------------------------------------------------------------------
Solution Summary
  Status        : OPTIMAL
  Iterations    : 12200
  Solve time    : 2.63 sec
  Primal obj    : 4629.462791
  Dual obj      : 4629.462716
  Primal infeas : 5.625e-05
  Dual infeas   : 1.478e-10

With PSLP presolve

$ ./build/cupdlpx /L1_sixm250obs.mps.gz test/ -v -p
---------------------------------------------------------------------------------------
                                    cuPDLPx v0.1.3                                    
                        A GPU-Accelerated First-Order LP Solver                        
               (c) Haihao Lu, Massachusetts Institute of Technology, 2025              
---------------------------------------------------------------------------------------
problem:
  variables     : 428032
  constraints   : 986069
  nnz(A)        : 4280320
settings:
  iter_limit         : 2147483647
  time_limit         : 3600.00 sec
  eps_opt            : 1.0e-04
  eps_feas           : 1.0e-04
  eps_infeas_detect  : 1.0e-10
---------------------------------------------------------------------------------------
   runtime     |     objective      |   absolute residuals    |   relative residuals    
  iter   time  |  pr obj    du obj  |  pr res  du res   gap   |  pr res  du res   gap   
---------------------------------------------------------------------------------------
Running Presolve...

               PSLP v0.0.1 - LP presolver 
        (c) Daniel Cederberg, Stanford University, 2025
Original problem:  986069 rows, 428032 columns, 4280320 nnz
Presolved problem: 721572 rows, 310389 columns, 3151786 nnz
Presolver init & run time : 0.151 seconds, 1.047 
Presolve finished. Reduced rows: 721572, cols: 310389
     0 4.1e-04 |  0.0e+00   0.0e+00 | 0.0e+00 6.9e+01 0.0e+00 | 0.0e+00 9.9e-01 0.0e+00 
    10 2.4e-03 |  5.4e+03   3.3e+08 | 4.5e+03 1.2e+05 3.3e+08 | 4.5e+03 1.8e+03 1.0e+00 
    20 4.3e-03 |  6.9e+03   2.0e+08 | 1.6e+03 5.7e+04 2.0e+08 | 1.6e+03 8.2e+02 1.0e+00 
    30 6.3e-03 |  8.3e+03   1.5e+08 | 9.2e+02 4.5e+04 1.5e+08 | 9.2e+02 6.5e+02 1.0e+00 
    40 8.3e-03 |  8.4e+03   7.5e+07 | 1.1e+03 3.3e+04 7.5e+07 | 1.1e+03 4.8e+02 1.0e+00 
    50 1.0e-02 |  7.9e+03   3.7e+07 | 3.5e+02 2.3e+04 3.7e+07 | 3.5e+02 3.4e+02 1.0e+00 
    60 1.2e-02 |  7.5e+03   4.7e+07 | 6.4e+02 1.8e+04 4.7e+07 | 6.4e+02 2.6e+02 1.0e+00 
    70 1.4e-02 |  7.6e+03   5.7e+07 | 3.6e+02 1.6e+04 5.7e+07 | 3.6e+02 2.3e+02 1.0e+00 
    80 1.6e-02 |  8.0e+03   5.1e+07 | 2.9e+02 1.6e+04 5.1e+07 | 2.9e+02 2.3e+02 1.0e+00 
    90 1.8e-02 |  8.0e+03   3.1e+07 | 3.9e+02 1.5e+04 3.1e+07 | 3.9e+02 2.1e+02 1.0e+00 
   100 2.0e-02 |  7.8e+03   2.1e+07 | 1.2e+02 1.2e+04 2.1e+07 | 1.2e+02 1.7e+02 1.0e+00 
...
 10000 1.7e+00 |  4.6e+03   4.6e+03 | 9.4e-01 8.7e-05 7.2e+00 | 9.4e-01 1.3e-06 7.8e-04 
 11000 1.9e+00 |  4.6e+03   4.6e+03 | 6.8e-01 3.4e-05 2.8e+00 | 6.8e-01 4.9e-07 3.0e-04 
 12000 2.1e+00 |  4.6e+03   4.6e+03 | 2.3e+00 3.6e-06 2.1e-02 | 2.3e+00 5.2e-08 2.2e-06 
 13000 2.2e+00 |  4.6e+03   4.6e+03 | 1.2e-01 7.4e-07 4.2e-02 | 1.2e-01 1.1e-08 4.5e-06 
 14000 2.4e+00 |  4.6e+03   4.6e+03 | 3.0e-03 4.6e-08 3.3e-03 | 3.0e-03 6.6e-10 3.5e-07 
 15000 2.6e+00 |  4.6e+03   4.6e+03 | 2.1e-04 1.0e-09 2.5e-05 | 2.1e-04 1.5e-11 2.7e-09 
---------------------------------------------------------------------------------------
Solution Summary
  Status        : OPTIMAL
  Iterations    : 15200
  Solve time    : 2.6 sec
  Primal obj    : 4629.462796
  Dual obj      : 4629.462801
  Primal infeas : 1.166e-05
  Dual infeas   : 6.269e-12
Postsolve time: 0.0149 seconds

src/presolve.c Outdated
if (!reduced_result || !info->presolver) return NULL;

int n_red = info->presolver->reduced_prob->n;
double *z_dummy = (double*)calloc(n_red, sizeof(double));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function "postsolve" recovers optimal primal and dual variables of the original problem. For the dual variables to be recovered correctly, the optimal dual variable z for the reduced problem must be correct. So this will likely result in the wrong dual variables for the original problem. However, the recovered primal variables will be correct.

@dance858
Copy link

I think this looks correct, with one small exception (see my comment above).

At the moment, I don’t have a clear hypothesis about whether the presolved problem should require fewer or more iterations. Whenever you have the opportunity to run the benchmarks, it would be very interesting to hear your insights on the following:

  1. Does the presolved problem require more iterations on the majority of instances?
  2. Is there a noticeable difference depending on whether cuPDLPx is run at high vs. low accuracy?
  3. It may be worth setting settings->relax_bounds = false to see whether keeping the bounds affects the iteration count. When this option is set to false, redundant bounds remain in the problem. (They are redundant in the sense that if the other primal constraints are satisfied, the bounds will be as well.) But who knows, perhaps cuPDLPx benefits from having these redundant bounds present during the projection step?

@dance858
Copy link

I thought of something else. When PSLP fixes variables, it updates an offset in the objective. The offset is stored in presolver->prob->obj->offset. I think it makes sense to include this offset when evaluating the termination criteria inside cuPDLPx for the reduced problem.

@ZedongPeng
Copy link
Collaborator Author

ZedongPeng commented Nov 25, 2025

I thought of something else. When PSLP fixes variables, it updates an offset in the objective. The offset is stored in presolver->prob->obj->offset. I think it makes sense to include this offset when evaluating the termination criteria inside cuPDLPx for the reduced problem.

@dance858 It might be better to add the definition of offset directly in the reduced problem. The Objective struct is defined in Problem.h, which I regard as an internal struct:
https://github.com/dance858/PSLP/blob/259ca2e1e1097c4acb36788d374e938bc00aafca/include/core/Problem.h#L31-L35

@dance858
Copy link

Good point and I totally agree. I'm pushing up the fix now. The offset is stored in PresolvedProblem->obj_offset.

src/presolve.c Outdated
Comment on lines 12 to 32
void sanitize_infinity_for_pslp(double* arr, int n, double pslp_inf_val) {
for (int i = 0; i < n; ++i) {
if (isinf(arr[i]) || fabs(arr[i]) >= pslp_inf_val) {
arr[i] = (arr[i] > 0) ? pslp_inf_val : -pslp_inf_val;
}
}
}

#define CUPDLP_INF std::numeric_limits<double>::infinity()

void restore_infinity_for_cupdlpx(double* arr, int n, double pslp_inf_val) {
for (int i = 0; i < n; ++i) {
if (arr[i] >= pslp_inf_val * 0.99) {
arr[i] = INFINITY;
}
else if (arr[i] <= -pslp_inf_val * 0.99) {
arr[i] = -INFINITY;
}
}
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the correct and best way to handle PSLP_INF? @dance858

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it makes sense. PSLP_INF is defined internally as 1e20. cuPDLPx seems to use a built-in definition of infinity? I might be able to update PSLP to use INFINITY defined in math.h. Do you think that makes sense?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I think that makes more sense. It’ll make this integration and future ones much easier.

@ZedongPeng
Copy link
Collaborator Author

Hi @dance858 . I have updated the reduced cost computation in postsolve. Could you take a look and let me know if it looks correct?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants