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 MPSolver #132

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
8 changes: 7 additions & 1 deletion ortools/linear_solver/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,17 @@ if(BUILD_TESTING)
set(CMAKE_INSTALL_RPATH "$ORIGIN/../${CMAKE_INSTALL_LIBDIR}:$ORIGIN:$ORIGIN/../lib:$ORIGIN")
endif ()

if(USE_SCIP)
add_executable(test_scip_interface scip_interface_test.cc)
target_compile_features(test_scip_interface PRIVATE cxx_std_17)
target_link_libraries(test_scip_interface PRIVATE ortools::ortools GTest::gtest_main)
add_test(NAME cxx_unittests_scip_interface COMMAND test_scip_interface)
endif()

if(USE_XPRESS)
add_executable(test_xprs_interface xpress_interface_test.cc)
target_compile_features(test_xprs_interface PRIVATE cxx_std_17)
target_link_libraries(test_xprs_interface PRIVATE ortools::ortools GTest::gtest_main)

add_test(NAME cxx_unittests_xpress_interface COMMAND test_xprs_interface)
endif()
endif ()
1 change: 1 addition & 0 deletions ortools/linear_solver/java/linear_solver.i
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,7 @@ PROTO2_RETURN(
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint();
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint(double, double, const std::string&);
%rename (makeConstraint) operations_research::MPSolver::MakeRowConstraint(const std::string&);
%rename (makeIndicatorConstraint) operations_research::MPSolver::MakeIndicatorConstraint;

// Expose the MPSolver's basic API, with trivial renames.
%rename (makeBoolVar) operations_research::MPSolver::MakeBoolVar; // no test
Expand Down
29 changes: 29 additions & 0 deletions ortools/linear_solver/linear_solver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1511,6 +1511,35 @@ MPConstraint* MPSolver::MakeRowConstraint(const LinearRange& range,
return constraint;
}

MPConstraint* MPSolver::MakeIndicatorConstraint(
double lb, double ub, const std::string& name,
Comment on lines +1514 to +1515
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This method should fail if the solver doesn't support integer variables
  2. You can call MPVariable::integer to check it's an integer variable on the indicator variable, and possibly test it's bounds (0 & 1).

const MPVariable* indicator_variable, bool indicator_value) {
DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(indicator_variable))
<< indicator_variable;
if (indicator_variable == nullptr) return nullptr;
if (!indicator_variable->integer() || indicator_variable->lb() != 0 ||
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be consistent with e.g MPObjective::SetCoefficient and MPConstraint::SetCoefficient, you'd need the following lines

Although the 2nd line is currently included in the 1st.

DLOG_IF(DFATAL, !interface_->solver_->OwnsVariable(indicator_variable)) << indicator_variable;
if (indicator_variable == nullptr) return nullptr;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch!

indicator_variable->ub() != 1) {
LOG(ERROR) << "Error adding indicator constraint " << name << ". Variable "
<< indicator_variable->name() << " is not Boolean";
return nullptr;
}
const int constraint_index = NumConstraints();
MPConstraint* const constraint =
new MPConstraint(constraint_index, lb, ub, name, interface_.get());
constraint->indicator_variable_ = indicator_variable;
constraint->indicator_value_ = indicator_value;
if (!interface_->AddIndicatorConstraint(constraint)) {
return nullptr;
}
if (constraint_name_to_index_) {
gtl::InsertOrDie(&*constraint_name_to_index_, constraint->name(),
constraint_index);
}
constraints_.push_back(constraint);
constraint_is_extracted_.push_back(false);
return constraint;
}

int MPSolver::ComputeMaxConstraintSize(int min_constraint_index,
int max_constraint_index) const {
int max_constraint_size = 0;
Expand Down
10 changes: 10 additions & 0 deletions ortools/linear_solver/linear_solver.h
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,16 @@ class MPSolver {
MPConstraint* MakeRowConstraint(const LinearRange& range,
const std::string& name);

/// Creates a named indicator constraint with given bounds and given
/// indicator variable.
Comment on lines +446 to +447
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// Creates a named indicator constraint with given bounds and given
/// indicator variable.
/// Creates a named indicator constraint with given bounds and given
/// indicator variable.
/// The constraint is active if and only if *indicator_variable has value indicator_value
/// (Only available for MILP problems)

/// The constraint is active if and only if *indicator_variable has value
/// indicator_value
/// (Only available for MILP problems)
MPConstraint* MakeIndicatorConstraint(double lb, double ub,
const std::string& name,
const MPVariable* indicator_variable,
bool indicator_value);

/**
* Returns the objective object.
*
Expand Down
1 change: 1 addition & 0 deletions ortools/linear_solver/python/linear_solver.i
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ PY_CONVERT(MPVariable);
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint();
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint(double, double, const std::string&);
%rename (Constraint) operations_research::MPSolver::MakeRowConstraint(const std::string&);
%rename (IndicatorConstraint) operations_research::MPSolver::MakeIndicatorConstraint;
%unignore operations_research::MPSolver::~MPSolver;
%newobject operations_research::MPSolver::CreateSolver;
%unignore operations_research::MPSolver::CreateSolver;
Expand Down
79 changes: 79 additions & 0 deletions ortools/linear_solver/scip_interface_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2010-2024 Google LLC
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <fstream>

#include "gtest/gtest.h"
#include "ortools/base/init_google.h"
#include "ortools/linear_solver/linear_solver.h"

namespace operations_research {

TEST(ScipInterface, IndicatorConstraint0) {
MPSolver solver("SCIP", MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING);
// 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
var->SetUB(0);
solver.Solve();
EXPECT_EQ(x->solution_value(), 10);
}

TEST(ScipInterface, IndicatorConstraint1) {
MPSolver solver("SCIP", MPSolver::SCIP_MIXED_INTEGER_PROGRAMMING);
// 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
var->SetLB(1);
solver.Solve();
EXPECT_EQ(x->solution_value(), 10);
}
} // namespace operations_research

int main(int argc, char** argv) {
absl::SetFlag(&FLAGS_stderrthreshold, 0);
testing::InitGoogleTest(&argc, argv);
auto solver = operations_research::MPSolver::CreateSolver("scip");
if (solver == nullptr) {
LOG(ERROR) << "SCIP solver is not available";
return EXIT_SUCCESS;
} else {
return RUN_ALL_TESTS();
}
}