Skip to content

Commit 909ea01

Browse files
Fix: handle MPS OBJSENSE MAXIMIZE in reader (#93)
* Fix: handle MPS OBJSENSE MAXIMIZE in reader * Fix: support inline OBJSENSE section declaration in MPS reader * Style: clang-format continuation indent * Handle objective sense (max/min) consistently across solver, API, and 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). * apply clang format * Fix: make objective_sense optional in create_lp_problem and repair test build * Fix: free reduced_cost in cupdlpx_result_free --------- Co-authored-by: Zedong Peng <peng_zedong@126.com>
1 parent f16a48d commit 909ea01

15 files changed

Lines changed: 181 additions & 85 deletions

File tree

docs/C_API.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ lp_problem_t *create_lp_problem(
1515
const double *con_ub, // constraint upper bounds (length m)
1616
const double *var_lb, // variable lower bounds (length n)
1717
const double *var_ub, // variable upper bounds (length n)
18-
const double *objective_constant // scalar objective offset
18+
const double *objective_constant, // scalar objective offset
19+
const objective_sense_t *objective_sense // objective sense (NULL → minimize)
1920
);
2021

2122
cupdlpx_result_t* solve_lp_problem(
@@ -32,6 +33,7 @@ cupdlpx_result_t* solve_lp_problem(
3233
- `var_lb`: Variable lower bounds. If `NULL`, defaults to all `-INFINITY`.
3334
- `var_ub`: Variable upper bounds. If `NULL`, defaults to all `+INFINITY`.
3435
- `objective_constant`: Scalar constant term added to the objective value. If `NULL`, defaults to `0.0`.
36+
- `objective_sense`: Objective sense, `OBJECTIVE_SENSE_MINIMIZE` or `OBJECTIVE_SENSE_MAXIMIZE`. If `NULL`, defaults to minimize.
3537

3638

3739
`solve_lp_problem` parameters:
@@ -70,7 +72,7 @@ int main() {
7072

7173
// Build the problem
7274
lp_problem_t* prob = create_lp_problem(
73-
c, &A_desc, l, u, NULL, NULL, NULL);
75+
c, &A_desc, l, u, NULL, NULL, NULL, NULL);
7476

7577
// Solve (NULL → use default parameters)
7678
cupdlpx_result_t* res = solve_lp_problem(prob, NULL);

include/cupdlpx.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ extern "C"
3030
const double *con_ub,
3131
const double *var_lb,
3232
const double *var_ub,
33-
const double *objective_constant);
33+
const double *objective_constant,
34+
const objective_sense_t *objective_sense);
3435

3536
// Set up initial primal and dual solution for an lp_problem_t
3637
void set_start_values(lp_problem_t *prob, const double *primal, const double *dual);

include/cupdlpx_types.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@ extern "C"
4343
NORM_TYPE_L_INF = 1
4444
} norm_type_t;
4545

46+
typedef enum
47+
{
48+
OBJECTIVE_SENSE_MINIMIZE = 0,
49+
OBJECTIVE_SENSE_MAXIMIZE = 1
50+
} objective_sense_t;
51+
4652
typedef struct
4753
{
4854
int num_variables;
@@ -51,6 +57,7 @@ extern "C"
5157
double *variable_upper_bound;
5258
double *objective_vector;
5359
double objective_constant;
60+
objective_sense_t objective_sense;
5461

5562
int *constraint_matrix_row_pointers;
5663
int *constraint_matrix_col_indices;

internal/internal_types.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ typedef struct
4545
double *variable_upper_bound;
4646
double *objective_vector;
4747
double objective_constant;
48+
double original_objective_sign;
4849
cu_sparse_matrix_csr_t *constraint_matrix;
4950
cu_sparse_matrix_csr_t *constraint_matrix_t;
5051
double *constraint_lower_bound;

internal/solver.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ extern "C"
2323
{
2424
#endif
2525

26-
cupdlpx_result_t *optimize(const pdhg_parameters_t *params, lp_problem_t *original_problem);
26+
cupdlpx_result_t *optimize(const pdhg_parameters_t *params, const lp_problem_t *original_problem);
2727

2828
#ifdef __cplusplus
2929
}

internal/utils.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,15 @@ extern "C"
125125

126126
void check_termination_criteria(pdhg_solver_state_t *solver_state, const termination_criteria_t *criteria);
127127

128-
void print_initial_info(const pdhg_parameters_t *params, lp_problem_t *problem);
128+
void print_initial_info(const pdhg_parameters_t *params, const lp_problem_t *problem);
129+
130+
void filter_constraint_matrix_entries(lp_problem_t *out, const lp_problem_t *in, const pdhg_parameters_t *params);
131+
132+
lp_problem_t preprocess_problem(const lp_problem_t *original, const pdhg_parameters_t *params);
133+
134+
void free_preprocessed_problem(const lp_problem_t *preprocessed, const lp_problem_t *original);
135+
136+
void restore_original_objective_sense(cupdlpx_result_t *result, objective_sense_t sense);
129137

130138
void pdhg_final_log(const cupdlpx_result_t *result, const pdhg_parameters_t *params);
131139

python/cupdlpx/model.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -355,23 +355,20 @@ def optimize(self):
355355
# check model sense
356356
if self.ModelSense not in (PDLP.MINIMIZE, PDLP.MAXIMIZE):
357357
raise ValueError("model_sense must be PDLP.MINIMIZE or PDLP.MAXIMIZE")
358-
# determine sign
359-
sign = 1 if self.ModelSense == PDLP.MINIMIZE else -1
360-
# effective objective based on sense
361-
c_eff = sign * self.c if self.c is not None else None
362-
c0_eff = sign * self.c0 if self.c0 is not None else None
358+
minimize = self.ModelSense == PDLP.MINIMIZE
363359
# call the core solver
364360
info = solve_once(
365361
self.A,
366-
c_eff,
367-
c0_eff,
362+
self.c,
363+
self.c0,
368364
self.lb,
369365
self.ub,
370366
self.constr_lb,
371367
self.constr_ub,
372368
params=self._params,
373369
primal_start=self._primal_start,
374-
dual_start=self._dual_start
370+
dual_start=self._dual_start,
371+
minimize=minimize,
375372
)
376373
# solutions
377374
self._x = np.asarray(info.get("X")) if info.get("X") is not None else None
@@ -380,8 +377,8 @@ def optimize(self):
380377
# objectives & gaps
381378
primal_obj_eff = info.get("PrimalObj")
382379
dual_obj_eff = info.get("DualObj")
383-
self._objval = sign * primal_obj_eff if primal_obj_eff is not None else None
384-
self._dualobj = sign * dual_obj_eff if dual_obj_eff is not None else None
380+
self._objval = primal_obj_eff if primal_obj_eff is not None else None
381+
self._dualobj = dual_obj_eff if dual_obj_eff is not None else None
385382
self._gap = info.get("ObjectiveGap")
386383
self._rel_gap = info.get("RelativeObjectiveGap")
387384
# status & counters
@@ -398,8 +395,8 @@ def optimize(self):
398395
self._max_d_ray = info.get("MaxDualRayInfeas")
399396
p_ray_lin_eff = info.get("PrimalRayLinObj")
400397
d_ray_obj_eff = info.get("DualRayObj")
401-
self._p_ray_lin_obj = sign * p_ray_lin_eff if p_ray_lin_eff is not None else None
402-
self._d_ray_obj = sign * d_ray_obj_eff if d_ray_obj_eff is not None else None
398+
self._p_ray_lin_obj = p_ray_lin_eff if p_ray_lin_eff is not None else None
399+
self._d_ray_obj = d_ray_obj_eff if d_ray_obj_eff is not None else None
403400

404401
def _clear_solution_cache(self) -> None:
405402
"""

python_bindings/_core_bindings.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,8 @@ static py::dict solve_once(py::object A,
464464
py::object constraint_upper_bound, // u (optional → inf)
465465
py::object params = py::none(), // PDHG parameters (optional → default)
466466
py::object primal_start = py::none(), // warm start primal solution (optional)
467-
py::object dual_start = py::none() // warm start dual solution (optional)
467+
py::object dual_start = py::none(), // warm start dual solution (optional)
468+
bool minimize = true // objective sense (true → minimize)
468469
)
469470
{
470471
// parse matrix
@@ -492,13 +493,15 @@ static py::dict solve_once(py::object A,
492493
}
493494

494495
// build problem
496+
objective_sense_t sense = minimize ? OBJECTIVE_SENSE_MINIMIZE : OBJECTIVE_SENSE_MAXIMIZE;
495497
lp_problem_t *prob = create_lp_problem(c_ptr, // objective vector
496498
&view.desc, // constraint matrix
497499
l_ptr, // constraint lower bound
498500
u_ptr, // constraint upper bound
499501
lb_ptr, // variable lower bound
500502
ub_ptr, // variable upper bound
501-
c0_ptr // objective constant
503+
c0_ptr, // objective constant
504+
&sense // objective sense
502505
);
503506
if (!prob)
504507
{
@@ -595,5 +598,6 @@ PYBIND11_MODULE(_cupdlpx_core, m)
595598
py::arg("constraint_upper_bound") = py::none(),
596599
py::arg("params") = py::none(),
597600
py::arg("primal_start") = py::none(),
598-
py::arg("dual_start") = py::none());
601+
py::arg("dual_start") = py::none(),
602+
py::arg("minimize") = true);
599603
}

src/cupdlpx.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ lp_problem_t *create_lp_problem(const double *objective_c,
2929
const double *con_ub,
3030
const double *var_lb,
3131
const double *var_ub,
32-
const double *objective_constant)
32+
const double *objective_constant,
33+
const objective_sense_t *objective_sense)
3334
{
3435
lp_problem_t *prob = (lp_problem_t *)safe_malloc(sizeof(lp_problem_t));
3536
prob->primal_start = NULL;
@@ -105,6 +106,7 @@ lp_problem_t *create_lp_problem(const double *objective_c,
105106

106107
// default fill values
107108
prob->objective_constant = objective_constant ? *objective_constant : 0.0;
109+
prob->objective_sense = objective_sense ? *objective_sense : OBJECTIVE_SENSE_MINIMIZE;
108110
fill_or_copy(&prob->objective_vector, prob->num_variables, objective_c, 0.0);
109111
fill_or_copy(&prob->variable_lower_bound, prob->num_variables, var_lb, -INFINITY);
110112
fill_or_copy(&prob->variable_upper_bound, prob->num_variables, var_ub, INFINITY);
@@ -123,6 +125,7 @@ void cupdlpx_result_free(cupdlpx_result_t *results)
123125

124126
free(results->primal_solution);
125127
free(results->dual_solution);
128+
free(results->reduced_cost);
126129
free(results);
127130
}
128131

src/mps_parser.c

Lines changed: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ typedef struct
326326
char *objective_row_name;
327327
char *current_col_name;
328328
double objective_constant;
329-
bool is_maximize;
329+
objective_sense_t objective_sense;
330330
int error_flag;
331331

332332
} MpsParserState;
@@ -435,7 +435,7 @@ lp_problem_t *read_mps_file(const char *filename)
435435
if (n_tokens == 0)
436436
continue;
437437

438-
if (n_tokens == 1 && isalpha(tokens[0][0]))
438+
if (isalpha((unsigned char)tokens[0][0]))
439439
{
440440
MpsSection next_section = SEC_NONE;
441441
if (strcmp(tokens[0], "ROWS") == 0)
@@ -448,32 +448,51 @@ lp_problem_t *read_mps_file(const char *filename)
448448
next_section = SEC_RANGES;
449449
else if (strcmp(tokens[0], "BOUNDS") == 0)
450450
next_section = SEC_BOUNDS;
451-
else if (strcmp(tokens[0], "OBJSENSE") == 0)
451+
else if (strcmp(tokens[0], "OBJSENSE") == 0 || strcmp(tokens[0], "OBJSENS") == 0)
452452
next_section = SEC_OBJSENSE;
453453
else if (strcmp(tokens[0], "ENDATA") == 0)
454454
{
455455
next_section = SEC_ENDATA;
456456
}
457457

458-
if (current_section == SEC_ROWS && next_section != SEC_ROWS && !rows_finalized)
458+
bool inline_max = next_section == SEC_OBJSENSE && n_tokens >= 2 &&
459+
(strcmp(tokens[1], "MAX") == 0 || strcmp(tokens[1], "MAXIMIZE") == 0);
460+
bool inline_min = next_section == SEC_OBJSENSE && n_tokens >= 2 &&
461+
(strcmp(tokens[1], "MIN") == 0 || strcmp(tokens[1], "MINIMIZE") == 0);
462+
bool is_header = next_section != SEC_NONE && (n_tokens == 1 || inline_max || inline_min);
463+
464+
if (is_header)
459465
{
460-
if (finalize_rows(&state) != 0)
461-
state.error_flag = 1;
462-
rows_finalized = true;
463-
}
466+
if (current_section == SEC_ROWS && next_section != SEC_ROWS && !rows_finalized)
467+
{
468+
if (finalize_rows(&state) != 0)
469+
state.error_flag = 1;
470+
rows_finalized = true;
471+
}
464472

465-
current_section = next_section;
466-
if (current_section == SEC_ENDATA)
467-
break;
468-
continue;
473+
current_section = next_section;
474+
if (current_section == SEC_ENDATA)
475+
break;
476+
477+
if (inline_max)
478+
state.objective_sense = OBJECTIVE_SENSE_MAXIMIZE;
479+
else if (inline_min)
480+
state.objective_sense = OBJECTIVE_SENSE_MINIMIZE;
481+
482+
continue;
483+
}
469484
}
470485

471486
switch (current_section)
472487
{
473488
case SEC_OBJSENSE:
474-
if (n_tokens > 0 && (strcmp(tokens[0], "MAX") == 0 || strcmp(tokens[0], "MAXIMIZE") == 0))
489+
if (strcmp(tokens[0], "MAX") == 0 || strcmp(tokens[0], "MAXIMIZE") == 0)
490+
{
491+
state.objective_sense = OBJECTIVE_SENSE_MAXIMIZE;
492+
}
493+
else if (strcmp(tokens[0], "MIN") == 0 || strcmp(tokens[0], "MINIMIZE") == 0)
475494
{
476-
state.is_maximize = true;
495+
state.objective_sense = OBJECTIVE_SENSE_MINIMIZE;
477496
}
478497
break;
479498
case SEC_ROWS:
@@ -516,7 +535,8 @@ lp_problem_t *read_mps_file(const char *filename)
516535
prob->num_variables = state.col_map.size;
517536
prob->num_constraints = state.row_map.size;
518537
prob->constraint_matrix_num_nonzeros = state.coo_matrix.nnz;
519-
prob->objective_constant = state.is_maximize ? -state.objective_constant : state.objective_constant;
538+
prob->objective_constant = state.objective_constant;
539+
prob->objective_sense = state.objective_sense;
520540

521541
prob->objective_vector = state.objective_coeffs;
522542
prob->variable_lower_bound = state.var_lower_bounds;
@@ -533,14 +553,6 @@ lp_problem_t *read_mps_file(const char *filename)
533553
state.constraint_lower_bounds = NULL;
534554
state.constraint_upper_bounds = NULL;
535555

536-
if (state.is_maximize)
537-
{
538-
for (int i = 0; i < prob->num_variables; ++i)
539-
{
540-
prob->objective_vector[i] *= -1.0;
541-
}
542-
}
543-
544556
if (mps_coo_to_csr(prob, &state.coo_matrix, prob->num_constraints) != 0)
545557
{
546558
fprintf(stderr, "ERROR: Failed to convert matrix to CSR format.\n");

0 commit comments

Comments
 (0)