Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BUG: Gracefully fail for OOM (Out of Memory) in Presolve #1467

Open
wants to merge 9 commits into
base: latest
Choose a base branch
from
124 changes: 124 additions & 0 deletions check/TestOOMKill.cpp
@@ -0,0 +1,124 @@
#include "HCheckConfig.h"
#include "Highs.h"
#include <iostream>
#include <vector>
#include <cstdlib>
#include <ctime>

#include "catch.hpp"
#include "lp_data/HConst.h"

TEST_CASE("linprog_oom", "[highs_solver]") {
const HighsInt n_ctr = 500000;
const HighsInt n_var = 500;

// Seed random number generator
std::srand(0);

// Create c_ vector of ones
std::vector<double> c_(n_var, 1.0);

// Create A_ub matrix with random values between 0 and 1
std::vector<double> Avalue(n_ctr * n_var);
for (int i = 0; i < n_ctr * n_var; ++i) {
Avalue[i] = (double)rand() / RAND_MAX;
}

// Create b_ub vector of zeros and set bounds
std::vector<double> rowUpper(n_ctr, 0.0);
std::vector<double> rowLower(n_ctr, -kHighsInf);

// Assuming A_ub is in column-wise format, similar to the provided example
std::vector<HighsInt> Astart(n_var + 1);
std::vector<HighsInt> Aindex(n_ctr * n_var);
for (int col = 0; col < n_var; ++col) {
Astart[col] = col * n_ctr;
}
for (int idx = 0; idx < n_ctr * n_var; ++idx) {
Aindex[idx] = idx % n_ctr;
}
Astart[n_var] = n_ctr * n_var;

// Column bounds are (0, infinity)
std::vector<double> colUpper(n_var, kHighsInf);
std::vector<double> colLower(n_var, 0);

Highs highs;

// Set up the LP externally
HighsLp lp;
lp.num_col_ = n_var;
lp.num_row_ = n_ctr;
lp.col_cost_ = c_;
lp.col_lower_ = colLower;
lp.col_upper_ = colUpper;
lp.row_lower_ = rowLower;
lp.row_upper_ = rowUpper;
lp.a_matrix_.start_ = Astart;
lp.a_matrix_.index_ = Aindex;
lp.a_matrix_.value_ = Avalue;
lp.a_matrix_.format_ = MatrixFormat::kColwise;
highs.setOptionValue("log_dev_level", kHighsLogDevLevelVerbose);
// highs.setOptionValue("threads", 1);
REQUIRE(highs.setOptionValue("presolve", "on") == HighsStatus::kOk);
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
REQUIRE(highs.run() == HighsStatus::kError);
}


TEST_CASE("linprog_oom_no_presolve", "[highs_solver]") {
const HighsInt n_ctr = 500000;
const HighsInt n_var = 500;

// Seed random number generator
std::srand(0);

// Create c_ vector of ones
std::vector<double> c_(n_var, 1.0);

// Create A_ub matrix with random values between 0 and 1
std::vector<double> Avalue(n_ctr * n_var);
for (int i = 0; i < n_ctr * n_var; ++i) {
Avalue[i] = (double)rand() / RAND_MAX;
}

// Create b_ub vector of zeros and set bounds
std::vector<double> rowUpper(n_ctr, 0.0);
std::vector<double> rowLower(n_ctr, -kHighsInf);

// Assuming A_ub is in column-wise format, similar to the provided example
std::vector<HighsInt> Astart(n_var + 1);
std::vector<HighsInt> Aindex(n_ctr * n_var);
for (int col = 0; col < n_var; ++col) {
Astart[col] = col * n_ctr;
}
for (int idx = 0; idx < n_ctr * n_var; ++idx) {
Aindex[idx] = idx % n_ctr;
}
Astart[n_var] = n_ctr * n_var;

// Column bounds are (0, infinity)
std::vector<double> colUpper(n_var, kHighsInf);
std::vector<double> colLower(n_var, 0);

Highs highs;

// Set up the LP externally
HighsLp lp;
lp.num_col_ = n_var;
lp.num_row_ = n_ctr;
lp.col_cost_ = c_;
lp.col_lower_ = colLower;
lp.col_upper_ = colUpper;
lp.row_lower_ = rowLower;
lp.row_upper_ = rowUpper;
lp.a_matrix_.start_ = Astart;
lp.a_matrix_.index_ = Aindex;
lp.a_matrix_.value_ = Avalue;
lp.a_matrix_.format_ = MatrixFormat::kColwise;
highs.setOptionValue("log_dev_level", kHighsLogDevLevelVerbose);
// highs.setOptionValue("threads", 1);
REQUIRE(highs.setOptionValue("presolve", "off") == HighsStatus::kOk);
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
REQUIRE(highs.run() == HighsStatus::kOk);
}
1 change: 1 addition & 0 deletions check/meson.build
Expand Up @@ -54,6 +54,7 @@ test_array = [
['test_lpvalidation', 'TestLpValidation.cpp'],
['test_lpmodification', 'TestLpModification.cpp'],
['test_lporientation', 'TestLpOrientation.cpp'],
['test_oomkill', 'TestOOMKill.cpp'],
]
foreach test : test_array
test(test.get(0),
Expand Down
1 change: 1 addition & 0 deletions src/Highs.h
Expand Up @@ -23,6 +23,7 @@
#include "model/HighsModel.h"
#include "presolve/ICrash.h"
#include "presolve/PresolveComponent.h"
#include "util/HighsExceptions.h"

/**
* @brief Return the version
Expand Down
1 change: 1 addition & 0 deletions src/lp_data/HConst.h
Expand Up @@ -168,6 +168,7 @@ enum class HighsPresolveStatus {
kNullError, // V2.0: Delete since it's not used!
kOptionsError, // V2.0: Delete since it's not used!
kNotSet,
kError,
};

enum class HighsPostsolveStatus { // V2.0: Delete if not used!
Expand Down
2 changes: 2 additions & 0 deletions src/lp_data/Highs.cpp
Expand Up @@ -2903,6 +2903,8 @@ std::string Highs::presolveStatusToString(
return "Null error";
case HighsPresolveStatus::kOptionsError:
return "Options error";
case HighsPresolveStatus::kError:
return "Presolve error";
default:
assert(1 == 0);
return "Unrecognised presolve status";
Expand Down
23 changes: 18 additions & 5 deletions src/presolve/HPresolve.cpp
Expand Up @@ -3685,10 +3685,18 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
// assert(non_fractional);
if (!non_fractional) return Result::kPrimalInfeasible;
}
postsolve_stack.fixedColAtLower(nonzero.index(),
model->col_lower_[nonzero.index()],
model->col_cost_[nonzero.index()],
getColumnVector(nonzero.index()));
try {
postsolve_stack.fixedColAtLower(
nonzero.index(), model->col_lower_[nonzero.index()],
model->col_cost_[nonzero.index()],
getColumnVector(nonzero.index()));
} catch (const DataStackOverflow& e) {
highsLogUser(options->log_options, HighsLogType::kError,
"Problem too large for Presolve, try without\n");
highsLogUser(options->log_options, HighsLogType::kInfo,
"Specific error raised:\n%s\n", e.what());
return Result::kError;
}
if (model->col_upper_[nonzero.index()] >
model->col_lower_[nonzero.index()])
changeColUpper(nonzero.index(),
Expand Down Expand Up @@ -4289,9 +4297,14 @@ HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) {
case Result::kPrimalInfeasible:
presolve_status_ = HighsPresolveStatus::kInfeasible;
return HighsModelStatus::kInfeasible;
case Result::kDualInfeasible:
case Result::kDualInfeasible: {
presolve_status_ = HighsPresolveStatus::kUnboundedOrInfeasible;
return HighsModelStatus::kUnboundedOrInfeasible;
}
case Result::kError: {
presolve_status_ = HighsPresolveStatus::kError;
return HighsModelStatus::kPresolveError;
}
}

if (options->presolve != kHighsOffString &&
Expand Down
1 change: 1 addition & 0 deletions src/presolve/HPresolve.h
Expand Up @@ -134,6 +134,7 @@ class HPresolve {
kPrimalInfeasible,
kDualInfeasible,
kStopped,
kError,
};
HighsPresolveStatus presolve_status_;
HPresolveAnalysis analysis_;
Expand Down
15 changes: 13 additions & 2 deletions src/util/HighsDataStack.h
Expand Up @@ -16,9 +16,11 @@
#define UTIL_HIGHS_DATA_STACK_H_

#include <cstring>
#include <string>
#include <type_traits>
#include <vector>

#include "util/HighsExceptions.h"
#include "util/HighsInt.h"

#if __GNUG__ && __GNUC__ < 5 && !defined(__clang__)
Expand All @@ -37,8 +39,17 @@ class HighsDataStack {
template <typename T,
typename std::enable_if<IS_TRIVIALLY_COPYABLE(T), int>::type = 0>
void push(const T& r) {
std::size_t dataSize = data.size();
data.resize(dataSize + sizeof(T));
HighsInt dataSize = data.size();
HighsInt newSize = dataSize + sizeof(T);
try {
data.resize(newSize);
} catch (const std::length_error& e) {
throw DataStackOverflow(
"Failed to resize the vector. Requested new size: " +
std::to_string(newSize) + ". Size to add is " +
std::to_string(sizeof(T)) +
". Current size: " + std::to_string(data.size()) + ".");
}
std::memcpy(data.data() + dataSize, &r, sizeof(T));
}

Expand Down
8 changes: 8 additions & 0 deletions src/util/HighsExceptions.h
@@ -0,0 +1,8 @@
#pragma once
#include <stdexcept>

class DataStackOverflow : public std::runtime_error {
public:
explicit DataStackOverflow(const std::string& msg)
: std::runtime_error(msg) {}
};