Skip to content

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

@LucasBoTang

Description

@LucasBoTang

Summary

The src/mps_parser.c file mishandles the OBJSENSE section in two independent ways. Together, they cause maximization instances to be solved incorrectly and silently (returning OPTIMAL status with no warnings).

Specifically:

  1. The OBJSENSE value is dropped, causing the problem to be solved as a minimization (resulting in a wrong optimum).
  2. Even if (1) is fixed, the reported objective value retains the wrong sign.

Bug 1: OBJSENSE MAXIMIZE is swallowed (Wrong Optimum)

Section headers are currently detected by treating any single alphabetic token as a header:

if (n_tokens == 1 && isalpha(tokens[0][0]))
{
    MpsSection next_section = SEC_NONE;
    if (strcmp(tokens[0], "ROWS") == 0) next_section = SEC_ROWS;
    /* ... */
    else if (strcmp(tokens[0], "OBJSENSE") == 0) next_section = SEC_OBJSENSE;
    /* ... */

    current_section = next_section;   /* unconditional */
    if (current_section == SEC_ENDATA) break;
    continue;                          /* line consumed */
}

With the standard two-line form:

OBJSENSE
    MAXIMIZE

The value line MAXIMIZE is parsed as a single alphabetic token. It enters the section header block, matches no keywords (so next_section stays SEC_NONE), unconditionally sets current_section = SEC_NONE, and skips to the next line. The case SEC_OBJSENSE: block that should set is_maximize is never reached. As a result, is_maximize defaults to false, the objective is not negated, and the problem is minimized instead.


Reproduction

Consider the following problem: maximize x s.t. x <= 5, x >= 0 (True optimum x* = 5, objective 5):

NAME          maxtest
OBJSENSE
    MAXIMIZE
ROWS
 N  obj
 L  c1
COLUMNS
    x         obj                  1.0   c1             1.0
RHS
    rhs       c1                   5.0
BOUNDS
ENDATA
  • Expected: OPTIMAL, x ~= 5, Objective = 5
  • Actual: OPTIMAL, x ~= 0, Objective = 0 (The problem was treated as a minimization)

Bug 2: Objective sign is not restored for maximization (Wrong Reported Value)

When is_maximize is successfully parsed, the parser converts max → min by negating the objective:

prob->objective_constant = state.is_maximize ? -state.objective_constant
                                             : state.objective_constant;
/* ... */
if (state.is_maximize)
    for (int i = 0; i < prob->num_variables; ++i)
        prob->objective_vector[i] *= -1.0;

However, is_maximize is strictly a local parser variable—it is not stored on the returned lp_problem_t struct, and nothing ever negates the reported objective back at the end of the solve. Consequently, the solver reports min(-c·x) = -(max value).

If Bug 1 is fixed, running the reproduction above yields the correct x* = 5 but reports an objective of -5 instead of +5 (since cupdlpx.c contains no objective-sense restoration logic).


Suggested Fixes

  1. For Bug 1: Restrict the section header detection so that it does not falsely match single-token values inside the OBJSENSE section, or explicitly check for valid section keywords before overwriting current_section.
  2. For Bug 2: Add an is_maximize flag (or an obj_sense variable) to the lp_problem_t struct to store the original problem sense. Use this flag in the solver output step to negate the final objective value back to the correct sign before reporting.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions