Skip to content
Merged
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
1 change: 1 addition & 0 deletions include/cupdlpx_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ extern "C"
norm_type_t optimality_norm;
bool presolve;
double matrix_zero_tol;
double infinite_bound;
} pdhg_parameters_t;

typedef struct
Expand Down
1 change: 1 addition & 0 deletions python/cupdlpx/PDLP.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,5 @@
# presolve
"Presolve": "presolve",
"MatrixZeroTol": "matrix_zero_tol",
"InfiniteBound": "infinite_bound",
}
2 changes: 2 additions & 0 deletions python/cupdlpx/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"eps_feas_polish_relative",
"sv_tol",
"matrix_zero_tol",
"infinite_bound",
}
)
_POSITIVE_FLOAT_PARAMS = frozenset(
Expand All @@ -70,6 +71,7 @@
"eps_feasible_relative",
"eps_feas_polish_relative",
"sv_tol",
"infinite_bound",
}
)
_NONNEGATIVE_FLOAT_PARAMS = frozenset({"time_sec_limit", "matrix_zero_tol"})
Expand Down
4 changes: 4 additions & 0 deletions python_bindings/_core_bindings.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,7 @@ static py::dict get_default_params_py()
d["presolve"] = p.presolve;

d["matrix_zero_tol"] = p.matrix_zero_tol;
d["infinite_bound"] = p.infinite_bound;

return d;
}
Expand Down Expand Up @@ -416,6 +417,7 @@ static void parse_params_from_python(py::object params_obj, pdhg_parameters_t *p
getb("presolve", p->presolve);

getf("matrix_zero_tol", p->matrix_zero_tol);
getf("infinite_bound", p->infinite_bound);

if (p->termination_evaluation_frequency <= 0)
throw std::invalid_argument("termination_evaluation_frequency must be positive.");
Expand All @@ -435,6 +437,8 @@ static void parse_params_from_python(py::object params_obj, pdhg_parameters_t *p
throw std::invalid_argument("sv_tol must be positive.");
if (p->termination_criteria.time_sec_limit < 0.0)
throw std::invalid_argument("time_sec_limit must be nonnegative.");
if (p->infinite_bound <= 0.0)
throw std::invalid_argument("infinite_bound must be positive.");
if (p->matrix_zero_tol < 0.0)
throw std::invalid_argument("matrix_zero_tol must be nonnegative.");
}
Expand Down
7 changes: 7 additions & 0 deletions src/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,9 @@ void print_usage(const char *prog_name)
fprintf(stderr,
" --matrix_zero_tol <tolerance>. "
"Zero tolerance in constraint matrix.\n");
fprintf(stderr,
" --infinite_bound <value>. "
"Bounds at or beyond this are treated as infinite (default: 1e20).\n");
}

int main(int argc, char *argv[])
Expand All @@ -238,6 +241,7 @@ int main(int argc, char *argv[])
{"opt_norm", required_argument, 0, 1014},
{"no_presolve", no_argument, 0, 1015},
{"matrix_zero_tol", required_argument, 0, 1016},
{"infinite_bound", required_argument, 0, 1017},
{0, 0, 0, 0}};

int opt;
Expand Down Expand Up @@ -317,6 +321,9 @@ int main(int argc, char *argv[])
case 1016: // --matrix_zero_tol
params.matrix_zero_tol = atof(optarg);
break;
case 1017: // --infinite_bound
params.infinite_bound = atof(optarg);
break;
case '?': // Unknown option
return 1;
}
Expand Down
123 changes: 104 additions & 19 deletions src/mps_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,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 +368,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 +403,27 @@ 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"};

lp_problem_t *read_mps_file(const char *filename)
{
MpsParserState state = {0};
Expand Down Expand Up @@ -437,22 +463,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 +509,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 +567,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 +584,15 @@ 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;
}
}

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

prob->num_variables = state.col_map.size;
Expand Down Expand Up @@ -600,10 +663,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 +728,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 +765,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 +882,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 +923,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 +988,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