Skip to content

Commit 6fc8b81

Browse files
committed
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).
1 parent 5329a0e commit 6fc8b81

12 files changed

Lines changed: 139 additions & 80 deletions

File tree

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+
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);

internal/internal_types.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ typedef struct
4343
double *variable_upper_bound;
4444
double *objective_vector;
4545
double objective_constant;
46-
double objective_sign;
46+
double original_objective_sign;
4747
cu_sparse_matrix_csr_t *constraint_matrix;
4848
cu_sparse_matrix_csr_t *constraint_matrix_t;
4949
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
@@ -124,7 +124,15 @@ extern "C"
124124

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

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

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

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: 12 additions & 9 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,14 @@ static py::dict solve_once(py::object A,
492493
}
493494

494495
// build problem
495-
lp_problem_t *prob = create_lp_problem(c_ptr, // objective vector
496-
&view.desc, // constraint matrix
497-
l_ptr, // constraint lower bound
498-
u_ptr, // constraint upper bound
499-
lb_ptr, // variable lower bound
500-
ub_ptr, // variable upper bound
501-
c0_ptr // objective constant
496+
lp_problem_t *prob = create_lp_problem(c_ptr, // objective vector
497+
&view.desc, // constraint matrix
498+
l_ptr, // constraint lower bound
499+
u_ptr, // constraint upper bound
500+
lb_ptr, // variable lower bound
501+
ub_ptr, // variable upper bound
502+
c0_ptr, // objective constant
503+
minimize ? OBJECTIVE_SENSE_MINIMIZE : OBJECTIVE_SENSE_MAXIMIZE // objective sense
502504
);
503505
if (!prob)
504506
{
@@ -595,5 +597,6 @@ PYBIND11_MODULE(_cupdlpx_core, m)
595597
py::arg("constraint_upper_bound") = py::none(),
596598
py::arg("params") = py::none(),
597599
py::arg("primal_start") = py::none(),
598-
py::arg("dual_start") = py::none());
600+
py::arg("dual_start") = py::none(),
601+
py::arg("minimize") = true);
599602
}

src/cupdlpx.c

Lines changed: 3 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+
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;
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);

src/mps_parser.c

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,7 @@ lp_problem_t *read_mps_file(const char *filename)
534534
prob->num_variables = state.col_map.size;
535535
prob->num_constraints = state.row_map.size;
536536
prob->constraint_matrix_num_nonzeros = state.coo_matrix.nnz;
537-
prob->objective_constant =
538-
(state.objective_sense == OBJECTIVE_SENSE_MAXIMIZE) ? -state.objective_constant : state.objective_constant;
537+
prob->objective_constant = state.objective_constant;
539538
prob->objective_sense = state.objective_sense;
540539

541540
prob->objective_vector = state.objective_coeffs;
@@ -553,14 +552,6 @@ lp_problem_t *read_mps_file(const char *filename)
553552
state.constraint_lower_bounds = NULL;
554553
state.constraint_upper_bounds = NULL;
555554

556-
if (state.objective_sense == OBJECTIVE_SENSE_MAXIMIZE)
557-
{
558-
for (int i = 0; i < prob->num_variables; ++i)
559-
{
560-
prob->objective_vector[i] *= -1.0;
561-
}
562-
}
563-
564555
if (mps_coo_to_csr(prob, &state.coo_matrix, prob->num_constraints) != 0)
565556
{
566557
fprintf(stderr, "ERROR: Failed to convert matrix to CSR format.\n");

src/presolve.c

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ void pslp_postsolve(const cupdlpx_presolve_info_t *info, cupdlpx_result_t *resul
189189
result->num_reduced_nonzeros = info->presolver->reduced_prob->nnz;
190190
result->presolve_status = info->presolve_status;
191191

192+
free(result->primal_solution);
193+
free(result->dual_solution);
194+
free(result->reduced_cost);
195+
192196
result->primal_solution = (double *)safe_malloc(original_prob->num_variables * sizeof(double));
193197
result->dual_solution = (double *)safe_malloc(original_prob->num_constraints * sizeof(double));
194198
result->reduced_cost = (double *)safe_malloc(original_prob->num_variables * sizeof(double));
@@ -217,9 +221,8 @@ void pslp_postsolve(const cupdlpx_presolve_info_t *info, cupdlpx_result_t *resul
217221
obj += original_prob->objective_vector[i] * result->primal_solution[i];
218222
}
219223
obj += original_prob->objective_constant;
220-
double objective_sign = (original_prob->objective_sense == OBJECTIVE_SENSE_MAXIMIZE) ? -1.0 : 1.0;
221-
result->primal_objective_value = objective_sign * obj;
222-
result->dual_objective_value = objective_sign * obj;
224+
result->primal_objective_value = obj;
225+
result->dual_objective_value = obj;
223226
}
224227
// if (info->presolver->stats != NULL) {
225228
// result->presolve_stats = *(info->presolver->stats);

src/solver.cu

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ static cupdlpx_result_t *create_result_from_state(pdhg_solver_state_t *state, co
6262
static void perform_restart(pdhg_solver_state_t *state, const pdhg_parameters_t *params);
6363
static void initialize_step_size_and_primal_weight(pdhg_solver_state_t *state, const pdhg_parameters_t *params);
6464
static pdhg_solver_state_t *initialize_solver_state(const lp_problem_t *working_problem,
65-
const pdhg_parameters_t *params);
65+
const pdhg_parameters_t *params,
66+
objective_sense_t original_objective_sense);
6667
static void compute_fixed_point_error(pdhg_solver_state_t *state);
6768
void pdhg_solver_state_free(pdhg_solver_state_t *state);
6869
void rescale_info_free(rescale_info_t *info);
@@ -127,28 +128,33 @@ static void sync_step_sizes_to_gpu(pdhg_solver_state_t *state);
127128
void sync_inner_count_to_gpu(pdhg_solver_state_t *state);
128129
static void check_params_validity(const pdhg_parameters_t *params);
129130

130-
cupdlpx_result_t *optimize(const pdhg_parameters_t *params, lp_problem_t *original_problem)
131+
cupdlpx_result_t *optimize(const pdhg_parameters_t *params, const lp_problem_t *original_problem)
131132
{
132133
check_params_validity(params);
134+
133135
print_initial_info(params, original_problem);
134136

135-
cupdlpx_presolve_info_t *presolve_info = NULL;
136-
const lp_problem_t *working_problem = original_problem;
137+
lp_problem_t preprocessed_problem = preprocess_problem(original_problem, params);
138+
const lp_problem_t *working_problem = &preprocessed_problem;
137139

140+
cupdlpx_presolve_info_t *presolve_info = NULL;
138141
if (params->presolve)
139142
{
140-
presolve_info = pslp_presolve(original_problem, params);
143+
presolve_info = pslp_presolve(&preprocessed_problem, params);
141144
if (presolve_info->problem_solved_during_presolve)
142145
{
143-
cupdlpx_result_t *result = create_result_from_presolve(presolve_info, original_problem);
146+
cupdlpx_result_t *result = create_result_from_presolve(presolve_info, &preprocessed_problem);
144147
cupdlpx_presolve_info_free(presolve_info);
148+
restore_original_objective_sense(result, original_problem->objective_sense);
145149
pdhg_final_log(result, params);
150+
free_preprocessed_problem(&preprocessed_problem, original_problem);
146151
return result;
147152
}
148153
working_problem = presolve_info->reduced_problem;
149154
}
150155

151-
pdhg_solver_state_t *state = initialize_solver_state(working_problem, params);
156+
pdhg_solver_state_t *state =
157+
initialize_solver_state(working_problem, params, original_problem->objective_sense);
152158
display_iteration_stats(state, params->verbose);
153159

154160
initialize_step_size_and_primal_weight(state, params);
@@ -244,17 +250,20 @@ cupdlpx_result_t *optimize(const pdhg_parameters_t *params, lp_problem_t *origin
244250
feasibility_polish(params, state);
245251
}
246252

247-
cupdlpx_result_t *result = create_result_from_state(state, original_problem);
253+
cupdlpx_result_t *result = create_result_from_state(state, &preprocessed_problem);
248254

249255
if (params->presolve && presolve_info)
250256
{
251-
pslp_postsolve(presolve_info, result, original_problem);
257+
pslp_postsolve(presolve_info, result, &preprocessed_problem);
252258
cupdlpx_presolve_info_free(presolve_info);
253259
}
254260

255-
pdhg_final_log(result, params);
256261
pdhg_solver_state_free(state);
257262
CUDA_CHECK(cudaGetLastError());
263+
264+
restore_original_objective_sense(result, original_problem->objective_sense);
265+
pdhg_final_log(result, params);
266+
free_preprocessed_problem(&preprocessed_problem, original_problem);
258267
return result;
259268
}
260269

@@ -314,7 +323,8 @@ __global__ void compute_and_rescale_reduced_cost_kernel(double *__restrict__ red
314323
}
315324

316325
static pdhg_solver_state_t *initialize_solver_state(const lp_problem_t *working_problem,
317-
const pdhg_parameters_t *params)
326+
const pdhg_parameters_t *params,
327+
objective_sense_t original_objective_sense)
318328
{
319329
pdhg_solver_state_t *state = (pdhg_solver_state_t *)safe_calloc(1, sizeof(pdhg_solver_state_t));
320330

@@ -327,7 +337,7 @@ static pdhg_solver_state_t *initialize_solver_state(const lp_problem_t *working_
327337
state->num_variables = n_vars;
328338
state->num_constraints = n_cons;
329339
state->objective_constant = working_problem->objective_constant;
330-
state->objective_sign = (working_problem->objective_sense == OBJECTIVE_SENSE_MAXIMIZE) ? -1.0 : 1.0;
340+
state->original_objective_sign = (original_objective_sense == OBJECTIVE_SENSE_MAXIMIZE) ? -1.0 : 1.0;
331341

332342
state->constraint_matrix = (cu_sparse_matrix_csr_t *)safe_malloc(sizeof(cu_sparse_matrix_csr_t));
333343
state->constraint_matrix_t = (cu_sparse_matrix_csr_t *)safe_malloc(sizeof(cu_sparse_matrix_csr_t));
@@ -1261,8 +1271,8 @@ static cupdlpx_result_t *create_result_from_state(pdhg_solver_state_t *state, co
12611271
results->cumulative_time_sec = state->cumulative_time_sec;
12621272
results->relative_primal_residual = state->relative_primal_residual;
12631273
results->relative_dual_residual = state->relative_dual_residual;
1264-
results->primal_objective_value = state->objective_sign * state->primal_objective_value;
1265-
results->dual_objective_value = state->objective_sign * state->dual_objective_value;
1274+
results->primal_objective_value = state->primal_objective_value;
1275+
results->dual_objective_value = state->dual_objective_value;
12661276
results->objective_gap = state->objective_gap;
12671277
results->relative_objective_gap = state->relative_objective_gap;
12681278
results->max_primal_ray_infeasibility = state->max_primal_ray_infeasibility;

0 commit comments

Comments
 (0)