Skip to content

Fix: handle MPS OBJSENSE MAXIMIZE in reader#93

Open
LucasBoTang wants to merge 7 commits into
MIT-Lu-Lab:mainfrom
LucasBoTang:fix/mps-objsense-maximize
Open

Fix: handle MPS OBJSENSE MAXIMIZE in reader#93
LucasBoTang wants to merge 7 commits into
MIT-Lu-Lab:mainfrom
LucasBoTang:fix/mps-objsense-maximize

Conversation

@LucasBoTang

Copy link
Copy Markdown
Collaborator

Fixes #92.

Problem

Two bugs in src/mps_parser.c made every maximization MPS instance silently wrong (status OPTIMAL, no warning) — full write-up in #92.

  1. Wrong optimum. The OBJSENSE value line (MAXIMIZE) is a single token, which the section detector consumed as a (bogus) header, so is_maximize was never set and the problem was solved as a minimization.
  2. Wrong reported objective sign. The parser negates the objective to convert max→min, but the sense was a parser-local flag that was never propagated, so the reported objective kept the wrong sign.

Fix

  • Treat a single token as a section header only when it matches a known section keyword; otherwise fall through to the section handler, so the OBJSENSE value reaches case SEC_OBJSENSE.
  • Carry the sense on lp_problem_t (objective_sense_t objective_sense), derive it once into objective_sign on the solver state, and apply it when reporting the primal/dual objectives (and in the presolve postsolve path).

Verification

  • maximize x s.t. x <= 5x = 5, objective = 5 (previously x = 0, obj = 0)
  • a minimization instance is unchanged
  • builds clean; both the main-solver and presolve paths report the correct sign

@ZedongPeng

Copy link
Copy Markdown
Collaborator

We might also need to support the same line section declaration.
https://github.com/scipopt/scip/blob/f8a5aa2ac5681bbbdf41be6e491f7d0c3e84299d/src/scip/reader_mps.c#L788-L799

@LucasBoTang

Copy link
Copy Markdown
Collaborator Author

@ZedongPeng
Done. Handles the inline OBJSENSE MAXIMIZE now. Kept the guard tight so only an OBJSENSE line followed by a real sense keyword is parsed inline; other sections still need the keyword alone, so a col/row named like a section keyword won't be misread.

LucasBoTang and others added 3 commits June 23, 2026 15:26
… MPS reader

Keep problems in their original sense and normalize max→min once inside the core, reporting results back in the caller's sense (replaces the scattered
sign-flipping that caused objective/dual-sign bugs).
@ZedongPeng

Copy link
Copy Markdown
Collaborator

6fc8b81 makes objective-sense handling consistent across the whole stack. Previously the maximize→minimize sign flip was applied in several different places (MPS parser, Python layer, presolve), which caused the objective/dual-sign bugs (e.g. the objective-constant issue in #90).

Design

Keep every problem in its original objective sense end-to-end, normalize maximize → minimize exactly once inside the core (preprocess_problem), and map the result back to the caller's sense exactly once (restore_original_objective_sense). The PDHG core only ever sees a minimization problem; per-iteration logs still print objectives in the caller's sense via original_objective_sign.

What changed

  • Core (solver.cu / utils.cu): preprocess_problem negates the objective for a maximize problem; restore_original_objective_sense flips the signed result quantities (objective, dual solution, reduced costs, ray objectives) back once. Solver state stays in min-sense throughout.
  • MPS reader: support the inline OBJSENSE MAX form (plus MAXIMIZE/MINIMIZE and the OBJSENS spelling); the parser no longer negates the objective — it stores it in the original sense.
  • Public C API: create_lp_problem now takes objective_sense, so callers build a complete problem in one call instead of mutating the struct afterward.
  • Python: solve_once gains a minimize argument; Model passes the model sense through and no longer flips signs itself.
  • Presolve: postsolve / result construction operate on the consistent min-sense convention.

Tests

Fixed the maximize dual-solution signs in test_basic.

@ZedongPeng

Copy link
Copy Markdown
Collaborator

Example:

NAME          BIGMAX
OBJSENSE
 MAX
ROWS
 N  OBJ
 L  C1
 L  C2
 L  C3
 L  C4
 L  C5
COLUMNS
    X1        OBJ            5.0        C1             2.0
    X1        C2             1.0        C3             3.0
    X1        C4             1.0        C5             4.0
    X2        OBJ            4.0        C1             3.0
    X2        C2             2.0        C3             1.0
    X2        C4             1.0        C5             2.0
    X3        OBJ            3.0        C1             1.0
    X3        C2             3.0        C3             2.0
    X3        C4             1.0        C5             1.0
    X4        OBJ            6.0        C1             4.0
    X4        C2             1.0        C3             2.0
    X4        C4             1.0        C5             3.0
    X5        OBJ            2.0        C1             1.0
    X5        C2             2.0        C3             3.0
    X5        C4             1.0        C5             1.0
    X6        OBJ            7.0        C1             2.0
    X6        C2             1.0        C3             1.0
    X6        C4             1.0        C5             2.0
RHS
    RHS       C1             40.0       C2             30.0
    RHS       C3             35.0       C4             20.0
    RHS       C5             45.0
BOUNDS
ENDATA

Log of 5329a0e

$ ./build/cupdlpx bigmax.mps test/ -v
---------------------------------------------------------------------------------------
                                    cuPDLPx v0.2.9                                    
                        A GPU-Accelerated First-Order LP Solver                        
               (c) Haihao Lu, Massachusetts Institute of Technology, 2025              
---------------------------------------------------------------------------------------
Problem: 5 rows, 6 columns, 30 nonzeros
Settings:
  iter_limit         : 2147483647
  time_limit         : 3600.00 sec
  eps_opt            : 1.0e-04
  eps_feas           : 1.0e-04
  spmv_backend       : cusparseSpMVOp (auto)

Running presolver (PSLP v0.0.8)...
  status          : UNCHANGED
  presolve time   : 0.000277 sec
  reduced problem : 5 rows, 6 columns, 30 nonzeros

Preconditioning
  Ruiz scaling (10 iterations)
  Pock-Chambolle scaling (alpha=1.0000)
  Bound-objective scaling
---------------------------------------------------------------------------------------
   runtime     |     objective      |   absolute residuals    |   relative residuals    
  iter   time  |  pr obj    du obj  |  pr res  du res   gap   |  pr res  du res   gap   
---------------------------------------------------------------------------------------
     0 0.0e+00 |  0.0e+00   0.0e+00 | 0.0e+00 0.0e+00 0.0e+00 | 0.0e+00 0.0e+00 0.0e+00 
   200 0.0e+00 | -1.4e+02  -1.4e+02 | 2.0e-01 4.0e-02 8.5e-01 | 2.5e-03 3.1e-03 3.0e-03 
   400 6.4e-03 | -1.4e+02  -1.4e+02 | 8.9e-04 3.5e-04 1.6e-03 | 1.1e-05 2.8e-05 5.8e-06 
---------------------------------------------------------------------------------------
Solution Summary
  Status                 : OPTIMAL
  Presolve time          : 0.000277 sec
  Precondition time      : 0.001526 sec
  Solve time             : 0.00884 sec
  Iterations             : 400
  Primal objective       : 140.0019964
  Dual objective         : 140.0003713
  Objective gap          : 5.783e-06
  Primal infeas          : 1.124e-05
  Dual infeas            : 2.771e-05

Log of acc8205

$ ./build/cupdlpx bigmax.mps test/ -v
---------------------------------------------------------------------------------------
                                    cuPDLPx v0.2.9                                    
                        A GPU-Accelerated First-Order LP Solver                        
               (c) Haihao Lu, Massachusetts Institute of Technology, 2025              
---------------------------------------------------------------------------------------
Problem: 5 rows, 6 columns, 30 nonzeros
Settings:
  iter_limit         : 2147483647
  time_limit         : 3600.00 sec
  eps_opt            : 1.0e-04
  eps_feas           : 1.0e-04
  spmv_backend       : cusparseSpMVOp (auto)

Running presolver (PSLP v0.0.8)...
  status          : UNCHANGED
  presolve time   : 0.000214 sec
  reduced problem : 5 rows, 6 columns, 30 nonzeros

Preconditioning
  Ruiz scaling (10 iterations)
  Pock-Chambolle scaling (alpha=1.0000)
  Bound-objective scaling
---------------------------------------------------------------------------------------
   runtime     |     objective      |   absolute residuals    |   relative residuals    
  iter   time  |  pr obj    du obj  |  pr res  du res   gap   |  pr res  du res   gap   
---------------------------------------------------------------------------------------
     0 0.0e+00 | -0.0e+00  -0.0e+00 | 0.0e+00 0.0e+00 0.0e+00 | 0.0e+00 0.0e+00 0.0e+00 
   200 0.0e+00 |  1.4e+02   1.4e+02 | 2.0e-01 4.0e-02 8.5e-01 | 2.5e-03 3.1e-03 3.0e-03 
   400 6.3e-03 |  1.4e+02   1.4e+02 | 8.9e-04 3.5e-04 1.6e-03 | 1.1e-05 2.8e-05 5.8e-06 
---------------------------------------------------------------------------------------
Solution Summary
  Status                 : OPTIMAL
  Presolve time          : 0.000214 sec
  Precondition time      : 0.001596 sec
  Solve time             : 0.00867 sec
  Iterations             : 400
  Primal objective       : 140.0019964
  Dual objective         : 140.0003713
  Objective gap          : 5.783e-06
  Primal infeas          : 1.124e-05
  Dual infeas            : 2.771e-05

@LucasBoTang

LucasBoTang commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

Hi @ZedongPeng , I pushed two commits (959a783, efa3b9d) on top of your objective-sense refactor.

The overall approach looks great.

My changes are mostly API/test cleanup:

  1. Made objective_sense optional in create_lp_problem by using a nullable pointer (NULL defaults to minimize), consistent with objective_constant. I updated the header, C API implementation, Python binding, and C_API.md.

  2. Fixed the test build. The new parameter was missing from three test call sites, and test_interface.c still had stale assignments to matrix_desc_t.zero_tolerance.

  3. Fixed a pre-existing leak in cupdlpx_result_free where reduced_cost was not freed.

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.

MPS reader mishandles OBJSENSE: maximization is solved as minimization and reported with the wrong objective sign

2 participants