diff --git a/ortools/linear_solver/CMakeLists.txt b/ortools/linear_solver/CMakeLists.txt index 74d20df48b..b4272d6c32 100644 --- a/ortools/linear_solver/CMakeLists.txt +++ b/ortools/linear_solver/CMakeLists.txt @@ -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 () diff --git a/ortools/linear_solver/java/linear_solver.i b/ortools/linear_solver/java/linear_solver.i index 855fa43291..882364c911 100644 --- a/ortools/linear_solver/java/linear_solver.i +++ b/ortools/linear_solver/java/linear_solver.i @@ -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 diff --git a/ortools/linear_solver/linear_solver.cc b/ortools/linear_solver/linear_solver.cc index b8dd07a570..1b2f75238b 100644 --- a/ortools/linear_solver/linear_solver.cc +++ b/ortools/linear_solver/linear_solver.cc @@ -1511,6 +1511,35 @@ MPConstraint* MPSolver::MakeRowConstraint(const LinearRange& range, return constraint; } +MPConstraint* MPSolver::MakeIndicatorConstraint( + double lb, double ub, const std::string& name, + 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 || + 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; diff --git a/ortools/linear_solver/linear_solver.h b/ortools/linear_solver/linear_solver.h index 0b363d5d30..59d9c53684 100644 --- a/ortools/linear_solver/linear_solver.h +++ b/ortools/linear_solver/linear_solver.h @@ -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. + /// 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. * diff --git a/ortools/linear_solver/python/linear_solver.i b/ortools/linear_solver/python/linear_solver.i index c73d11807c..970274a8cd 100644 --- a/ortools/linear_solver/python/linear_solver.i +++ b/ortools/linear_solver/python/linear_solver.i @@ -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; diff --git a/ortools/linear_solver/scip_interface_test.cc b/ortools/linear_solver/scip_interface_test.cc new file mode 100644 index 0000000000..c0e573598c --- /dev/null +++ b/ortools/linear_solver/scip_interface_test.cc @@ -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 + +#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(); + } +} \ No newline at end of file