Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
151 changes: 132 additions & 19 deletions src/mps_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ limitations under the License.
#include <zlib.h>

#define READER_BUFFER_SIZE (4 * 1024 * 1024)
#define MPS_INFINITE_BOUND 1e20

typedef struct NameNode
{
Expand Down Expand Up @@ -319,6 +320,9 @@ typedef struct
double *var_upper_bounds;
double *constraint_lower_bounds;
double *constraint_upper_bounds;
unsigned char *col_binary_default;
unsigned char *col_has_lower;
int in_integer_block;

size_t col_capacity;
size_t constraint_capacity;
Expand Down Expand Up @@ -365,12 +369,17 @@ static bool ensure_column_capacity(MpsParserState *state)
state->objective_coeffs = (double *)safe_realloc(state->objective_coeffs, new_cap * sizeof(double));
state->var_lower_bounds = (double *)safe_realloc(state->var_lower_bounds, new_cap * sizeof(double));
state->var_upper_bounds = (double *)safe_realloc(state->var_upper_bounds, new_cap * sizeof(double));
state->col_binary_default =
(unsigned char *)safe_realloc(state->col_binary_default, new_cap * sizeof(unsigned char));
state->col_has_lower = (unsigned char *)safe_realloc(state->col_has_lower, new_cap * sizeof(unsigned char));

for (size_t i = state->col_capacity; i < new_cap; ++i)
{
state->objective_coeffs[i] = 0.0;
state->var_lower_bounds[i] = 0.0;
state->var_upper_bounds[i] = INFINITY;
state->col_binary_default[i] = 0;
state->col_has_lower[i] = 0;
}

state->col_capacity = new_cap;
Expand All @@ -395,9 +404,46 @@ typedef enum
SEC_RANGES,
SEC_BOUNDS,
SEC_OBJSENSE,
SEC_SOS,
SEC_UNSUPPORTED,
SEC_ENDATA
} MpsSection;

/* Sections that change the model beyond an LP: refuse the file rather than
* silently dropping them (HiGHS does the same for the ones it cannot parse). */
static const char *const MPS_UNSUPPORTED_SECTIONS[] = {"QUADOBJ",
"QMATRIX",
"QSECTION",
"QCMATRIX",
"CSECTION",
"INDICATORS",
"DELAYEDROWS",
"MODELCUTS",
"USERCUTS",
"GENCONS",
"PWLOBJ",
"PWLNAM",
"PWLCON"};

/* Bounds at or beyond +/-1e20 are the conventional way of writing infinity in
* MPS files; HiGHS applies the same threshold (options.infinite_bound). */
static int apply_infinite_bounds(double *lower, double *upper, size_t n, const char *what)
{
for (size_t i = 0; i < n; ++i)
{
if (lower[i] >= MPS_INFINITE_BOUND || upper[i] <= -MPS_INFINITE_BOUND)
{
fprintf(stderr, "ERROR: %s %zu has bounds [%g, %g].\n", what, i, lower[i], upper[i]);
return -1;
}
if (lower[i] <= -MPS_INFINITE_BOUND)
lower[i] = -INFINITY;
if (upper[i] >= MPS_INFINITE_BOUND)
upper[i] = INFINITY;
}
return 0;
}

lp_problem_t *read_mps_file(const char *filename)
{
MpsParserState state = {0};
Expand Down Expand Up @@ -437,22 +483,42 @@ lp_problem_t *read_mps_file(const char *filename)

if (isalpha((unsigned char)tokens[0][0]))
{
/* A section header is alone on its line; OBJSENSE may carry its value.
* Checking the token count first keeps this off the hot path. */
MpsSection next_section = SEC_NONE;
if (strcmp(tokens[0], "ROWS") == 0)
next_section = SEC_ROWS;
else if (strcmp(tokens[0], "COLUMNS") == 0)
next_section = SEC_COLUMNS;
else if (strcmp(tokens[0], "RHS") == 0)
next_section = SEC_RHS;
else if (strcmp(tokens[0], "RANGES") == 0)
next_section = SEC_RANGES;
else if (strcmp(tokens[0], "BOUNDS") == 0)
next_section = SEC_BOUNDS;
if (n_tokens == 1)
{
if (strcmp(tokens[0], "ROWS") == 0)
next_section = SEC_ROWS;
else if (strcmp(tokens[0], "COLUMNS") == 0)
next_section = SEC_COLUMNS;
else if (strcmp(tokens[0], "RHS") == 0)
next_section = SEC_RHS;
else if (strcmp(tokens[0], "RANGES") == 0)
next_section = SEC_RANGES;
else if (strcmp(tokens[0], "BOUNDS") == 0)
next_section = SEC_BOUNDS;
else if (strcmp(tokens[0], "OBJSENSE") == 0 || strcmp(tokens[0], "OBJSENS") == 0)
next_section = SEC_OBJSENSE;
else if (strcmp(tokens[0], "SOS") == 0 || strcmp(tokens[0], "SETS") == 0)
next_section = SEC_SOS;
else if (strcmp(tokens[0], "ENDATA") == 0)
next_section = SEC_ENDATA;
else
{
for (size_t k = 0; k < sizeof(MPS_UNSUPPORTED_SECTIONS) / sizeof(MPS_UNSUPPORTED_SECTIONS[0]); ++k)
{
if (strcmp(tokens[0], MPS_UNSUPPORTED_SECTIONS[k]) == 0)
{
next_section = SEC_UNSUPPORTED;
break;
}
}
}
}
else if (strcmp(tokens[0], "OBJSENSE") == 0 || strcmp(tokens[0], "OBJSENS") == 0)
next_section = SEC_OBJSENSE;
else if (strcmp(tokens[0], "ENDATA") == 0)
{
next_section = SEC_ENDATA;
next_section = SEC_OBJSENSE;
}

bool inline_max = next_section == SEC_OBJSENSE && n_tokens >= 2 &&
Expand All @@ -463,6 +529,12 @@ lp_problem_t *read_mps_file(const char *filename)

if (is_header)
{
if (next_section == SEC_UNSUPPORTED)
{
fprintf(stderr, "ERROR: MPS file reader cannot parse %s section.\n", tokens[0]);
state.error_flag = 1;
break;
}
if (current_section == SEC_ROWS && next_section != SEC_ROWS && !rows_finalized)
{
if (finalize_rows(&state) != 0)
Expand Down Expand Up @@ -515,6 +587,8 @@ lp_problem_t *read_mps_file(const char *filename)
if (parse_bounds_section(&state, tokens, n_tokens) != 0)
state.error_flag = 1;
break;
case SEC_SOS:
break;
default:

break;
Expand All @@ -530,6 +604,23 @@ lp_problem_t *read_mps_file(const char *filename)
return NULL;
}

for (size_t i = 0; i < state.col_map.size; ++i)
{
if (state.col_binary_default[i])
{
state.var_lower_bounds[i] = 0.0;
state.var_upper_bounds[i] = 1.0;
}
}

if (apply_infinite_bounds(state.var_lower_bounds, state.var_upper_bounds, state.col_map.size, "Column") != 0 ||
apply_infinite_bounds(
state.constraint_lower_bounds, state.constraint_upper_bounds, state.row_map.size, "Row") != 0)
{
free_parser_state(&state);
return NULL;
}

lp_problem_t *prob = safe_calloc(1, sizeof(lp_problem_t));

prob->num_variables = state.col_map.size;
Expand Down Expand Up @@ -600,10 +691,8 @@ static int finalize_rows(MpsParserState *state)
}
}

if (obj_idx == -1 && state->num_buffered_rows > 0)
{
obj_idx = 0;
}
if (obj_idx == -1)
fprintf(stderr, "WARNING: No objective (N) row found in ROWS section; objective is zero.\n");

if (obj_idx != -1)
{
Expand Down Expand Up @@ -667,6 +756,13 @@ static int parse_columns_section(MpsParserState *state, char **tokens, int n_tok

if (n_tokens >= 2 && strcmp(tokens[1], "'MARKER'") == 0)
{
if (n_tokens >= 3)
{
if (strcmp(tokens[2], "'INTORG'") == 0)
state->in_integer_block = 1;
else if (strcmp(tokens[2], "'INTEND'") == 0)
state->in_integer_block = 0;
}
return 0;
}

Expand Down Expand Up @@ -697,9 +793,12 @@ static int parse_columns_section(MpsParserState *state, char **tokens, int n_tok
if (!ensure_column_capacity(state))
return -1;

size_t n_cols_before = state->col_map.size;
int col_idx = namemap_put(&state->col_map, col_name);
if (col_idx == -1)
return -1;
if (state->col_map.size > n_cols_before && state->in_integer_block)
state->col_binary_default[col_idx] = 1;

for (int i = pair_start_index; i + 1 < n_tokens; i += 2)
{
Expand Down Expand Up @@ -811,27 +910,38 @@ static int parse_bounds_section(MpsParserState *state, char **tokens, int n_toke
if (col_idx == -1)
return 0;

if (strcmp(bound_type, "LO") == 0)
/* Any BOUNDS entry cancels the implicit [0, 1] default of an integer column. */
state->col_binary_default[col_idx] = 0;

if (strcmp(bound_type, "LO") == 0 || strcmp(bound_type, "LI") == 0)
{
state->var_lower_bounds[col_idx] = value;
state->col_has_lower[col_idx] = 1;
}
else if (strcmp(bound_type, "UP") == 0)
else if (strcmp(bound_type, "UP") == 0 || strcmp(bound_type, "UI") == 0)
{
state->var_upper_bounds[col_idx] = value;
/* A negative upper bound on a column with no explicit lower bound means
* the lower bound is -infinity (CPLEX/Gurobi/SCIP convention). */
if (value < 0.0 && !state->col_has_lower[col_idx])
state->var_lower_bounds[col_idx] = -INFINITY;
}
else if (strcmp(bound_type, "FX") == 0)
{
state->var_lower_bounds[col_idx] = value;
state->var_upper_bounds[col_idx] = value;
state->col_has_lower[col_idx] = 1;
}
else if (strcmp(bound_type, "FR") == 0)
{
state->var_lower_bounds[col_idx] = -INFINITY;
state->var_upper_bounds[col_idx] = INFINITY;
state->col_has_lower[col_idx] = 1;
}
else if (strcmp(bound_type, "MI") == 0)
{
state->var_lower_bounds[col_idx] = -INFINITY;
state->col_has_lower[col_idx] = 1;
}
else if (strcmp(bound_type, "PL") == 0)
{
Expand All @@ -841,6 +951,7 @@ static int parse_bounds_section(MpsParserState *state, char **tokens, int n_toke
{
state->var_lower_bounds[col_idx] = 0.0;
state->var_upper_bounds[col_idx] = 1.0;
state->col_has_lower[col_idx] = 1;
}
return 0;
}
Expand Down Expand Up @@ -905,6 +1016,8 @@ static void free_parser_state(MpsParserState *state)
free(state->objective_coeffs);
free(state->var_lower_bounds);
free(state->var_upper_bounds);
free(state->col_binary_default);
free(state->col_has_lower);
free(state->constraint_lower_bounds);
free(state->constraint_upper_bounds);
free(state->objective_row_name);
Expand Down
Loading
Loading