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

Add support for indicator constraints in XPRESS interface #133

Draft
wants to merge 3 commits into
base: mpsolver_support_indicator_cts
Choose a base branch
from
Draft
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
27 changes: 25 additions & 2 deletions ortools/linear_solver/xpress_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ class XpressMPCallbackContext : public MPCallbackContext {
: xprsprob_(xprsprob),
event_(event),
num_nodes_(num_nodes),
variable_values_(0) {};
variable_values_(0){};

// Implementation of the interface.
MPCallbackEvent Event() override { return event_; };
Expand Down Expand Up @@ -260,7 +260,7 @@ class XpressMPCallbackContext : public MPCallbackContext {
// Wraps the MPCallback in order to catch and store exceptions
class MPCallbackWrapper {
public:
explicit MPCallbackWrapper(MPCallback* callback) : callback_(callback) {};
explicit MPCallbackWrapper(MPCallback* callback) : callback_(callback){};
MPCallback* GetCallback() const { return callback_; }
// Since our (C++) call-back functions are called from the XPRESS (C) code,
// exceptions thrown in our call-back code are not caught by XPRESS.
Expand Down Expand Up @@ -328,6 +328,7 @@ class XpressInterface : public MPSolverInterface {
void SetConstraintBounds(int row_index, double lb, double ub) override;

void AddRowConstraint(MPConstraint* ct) override;
bool AddIndicatorConstraint(MPConstraint* ct) override;
void AddVariable(MPVariable* var) override;
void SetCoefficient(MPConstraint* constraint, MPVariable const* variable,
double new_value, double old_value) override;
Expand Down Expand Up @@ -1541,6 +1542,10 @@ void XpressInterface::ExtractNewConstraints() {
unique_ptr<char[]> sense(new char[chunk]);
unique_ptr<double[]> rhs(new double[chunk]);
unique_ptr<double[]> rngval(new double[chunk]);
int n_indicators = 0;
std::vector<int> indicator_rowind;
std::vector<int> indicator_colind;
std::vector<int> indicator_complement;

// Loop over the new constraints, collecting rows for up to
// CHUNK constraints into the arrays so that adding constraints
Expand Down Expand Up @@ -1576,13 +1581,26 @@ void XpressInterface::ExtractNewConstraints() {
++nextNz;
}
}

// Detect & store indicator constraints
if (ct->indicator_variable() != nullptr) {
n_indicators++;
indicator_rowind.push_back(nextRow);
indicator_colind.push_back(ct->indicator_variable()->index());
indicator_complement.push_back(ct->indicator_value() ? 1 : -1);
}
}
if (nextRow > 0) {
CHECK_STATUS(XPRSaddrows(mLp, nextRow, nextNz, sense.get(), rhs.get(),
rngval.get(), rmatbeg.get(), rmatind.get(),
rmatval.get()));
}
}

// Set indicator constraints in XPRESS
CHECK_STATUS(XPRSsetindicators(mLp, n_indicators, indicator_rowind.data(),
indicator_colind.data(),
indicator_complement.data()));
} catch (...) {
// Undo all changes in case of error.
int const rows = getnumrows(mLp);
Expand Down Expand Up @@ -2288,4 +2306,9 @@ double XpressMPCallbackContext::SuggestSolution(
return NAN;
}

bool XpressInterface::AddIndicatorConstraint(MPConstraint* ct) {
InvalidateModelSynchronization();
return !IsContinuous();
}

} // namespace operations_research
57 changes: 55 additions & 2 deletions ortools/linear_solver/xpress_interface_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,9 @@ class MyMPCallback : public MPCallback {
MyMPCallback(MPSolver* mpSolver, bool should_throw)
: MPCallback(false, false),
mpSolver_(mpSolver),
should_throw_(should_throw) {};
should_throw_(should_throw){};

~MyMPCallback() override {};
~MyMPCallback() override{};

void RunCallback(MPCallbackContext* callback_context) override {
if (should_throw_) {
Expand Down Expand Up @@ -1411,6 +1411,59 @@ TEST_F(XpressFixtureMIP, CallbackThrowsException) {
ASSERT_NE(errors.find(expected_error), std::string::npos);
}

TEST_F(XpressFixtureMIP, IndicatorConstraint0) {
solver.EnableOutput();
// Maximize x <= 100
auto x = solver.MakeNumVar(0, 100, "x");
solver.MutableObjective()->SetMaximization();
solver.MutableObjective()->SetCoefficient(x, 1);
// With indicator constraint
// if var = 0, then x <= 10
auto var = solver.MakeBoolVar("indicator_var");
auto ct = solver.MakeIndicatorConstraint(0, 10, "test", var, false);
ct->SetCoefficient(x, 1);

// Leave var free ==> x = 100
solver.Solve();
EXPECT_EQ(var->solution_value(), 1);
EXPECT_EQ(x->solution_value(), 100);

// Force var to 0 ==> x = 10
// WARNING : can't use var->SetUB(0), because then XPRESS would automatically
// change its type to continuous, then fail on indicator variable evaluation.
// We have to add a constraint instead.
ct = solver.MakeRowConstraint(0, 0, "set_indicator_var_to_0");
ct->SetCoefficient(var, 1);
solver.Solve();
EXPECT_EQ(x->solution_value(), 10);
}

TEST_F(XpressFixtureMIP, IndicatorConstraint1) {
// Maximize x <= 100
auto x = solver.MakeNumVar(0, 100, "x");
solver.MutableObjective()->SetMaximization();
solver.MutableObjective()->SetCoefficient(x, 1);
// With indicator constraint
// if var = 1, then x <= 10
auto var = solver.MakeBoolVar("indicator_var");
auto ct = solver.MakeIndicatorConstraint(0, 10, "test", var, true);
ct->SetCoefficient(x, 1);

// Leave var free ==> x = 100
solver.Solve();
EXPECT_EQ(var->solution_value(), 0);
EXPECT_EQ(x->solution_value(), 100);

// Force var to 0 ==> x = 10
// WARNING : can't use var->SetLB(1), because then XPRESS would automatically
// change its type to continuous, then fail on indicator variable evaluation.
// We have to add a constraint instead.
ct = solver.MakeRowConstraint(1, 1, "set_indicator_var_to_1");
ct->SetCoefficient(var, 1);
solver.Solve();
EXPECT_EQ(x->solution_value(), 10);
}

} // namespace operations_research

int main(int argc, char** argv) {
Expand Down
2 changes: 2 additions & 0 deletions ortools/xpress/environment.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ std::function<int(XPRSprob prob, int nrows, int ncoefs, const char rowtype[], co
std::function<int(XPRSprob prob, int nrows, const int rowind[])> XPRSdelrows = nullptr;
std::function<int(XPRSprob prob, int ncols, int ncoefs, const double objcoef[], const int start[], const int rowind[], const double rowcoef[], const double lb[], const double ub[])> XPRSaddcols = nullptr;
std::function<int(XPRSprob prob, int type, const char names[], int first, int last)> XPRSaddnames = nullptr;
std::function<int(XPRSprob prob, int nrows, const int rowind[], const int colind[], const int complement[])> XPRSsetindicators = nullptr;
std::function<int(XPRSprob prob, int type, char names[], int first, int last)> XPRSgetnames = nullptr;
std::function<int(XPRSprob prob, int ncols, const int colind[])> XPRSdelcols = nullptr;
std::function<int(XPRSprob prob, int ncols, const int colind[], const char coltype[])> XPRSchgcoltype = nullptr;
Expand Down Expand Up @@ -137,6 +138,7 @@ void LoadXpressFunctions(DynamicLibrary* xpress_dynamic_library) {
xpress_dynamic_library->GetFunction(&XPRSdelrows, "XPRSdelrows");
xpress_dynamic_library->GetFunction(&XPRSaddcols, "XPRSaddcols");
xpress_dynamic_library->GetFunction(&XPRSaddnames, "XPRSaddnames");
xpress_dynamic_library->GetFunction(&XPRSsetindicators, "XPRSsetindicators");
xpress_dynamic_library->GetFunction(&XPRSgetnames, "XPRSgetnames");
xpress_dynamic_library->GetFunction(&XPRSdelcols, "XPRSdelcols");
xpress_dynamic_library->GetFunction(&XPRSchgcoltype, "XPRSchgcoltype");
Expand Down
1 change: 1 addition & 0 deletions ortools/xpress/environment.h
Original file line number Diff line number Diff line change
Expand Up @@ -469,6 +469,7 @@ extern std::function<int(XPRSprob prob, int nrows, int ncoefs, const char rowtyp
extern std::function<int(XPRSprob prob, int nrows, const int rowind[])> XPRSdelrows;
extern std::function<int(XPRSprob prob, int ncols, int ncoefs, const double objcoef[], const int start[], const int rowind[], const double rowcoef[], const double lb[], const double ub[])> XPRSaddcols;
extern std::function<int(XPRSprob prob, int type, const char names[], int first, int last)> XPRSaddnames;
extern std::function<int(XPRSprob prob, int nrows, const int rowind[], const int colind[], const int complement[])> XPRSsetindicators;
extern std::function<int(XPRSprob prob, int type, char names[], int first, int last)> XPRSgetnames;
extern std::function<int(XPRSprob prob, int ncols, const int colind[])> XPRSdelcols;
extern std::function<int(XPRSprob prob, int ncols, const int colind[], const char coltype[])> XPRSchgcoltype;
Expand Down