Skip to content
Open
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
13 changes: 13 additions & 0 deletions ortools/linear_solver/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,19 @@ cc_library(
],
)

cc_test(
name = "gurobi_util_test",
srcs = ["gurobi_util_test.cc"],
copts = ORTOOLS_TEST_COPTS,
linkopts = ORTOOLS_DEFAULT_LINKOPTS,
deps = [
":gurobi_util",
"//ortools/base:gmock_main",
"//ortools/third_party_solvers:gurobi_environment",
"@abseil-cpp//absl/cleanup",
],
)

# Experimental. Boolean optimization problem solver.
# This works best on MIP problem where all the variables are Boolean integers.
cc_library(
Expand Down
7 changes: 4 additions & 3 deletions ortools/linear_solver/gurobi_interface.cc
Original file line number Diff line number Diff line change
Expand Up @@ -684,9 +684,10 @@ void GurobiInterface::Reset() {
// solver_specific_parameter_string_ at the start of the solve; other
// parameters set by previous calls are only kept in the Gurobi model.
//
// TODO - b/328604189: Fix logging issue upstream, switch to a different API
// for copying parameters, or avoid calling Reset() in more places.
CheckedGurobiCall(GRBcopyparams(GRBgetenv(model_), GRBgetenv(old_model)));
// TODO - b/328604189: Fix logging issue upstream, or avoid calling Reset() in
// more places. GRBcopyparams is not exported by Gurobi 13, so we fall back to
// copying changed parameters explicitly.
CHECK_OK(CopyGurobiParameters(GRBgetenv(model_), GRBgetenv(old_model)));

CheckedGurobiCall(GRBfreemodel(old_model));
old_model = nullptr;
Expand Down
87 changes: 87 additions & 0 deletions ortools/linear_solver/gurobi_util.cc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@
#include "ortools/third_party_solvers/gurobi_environment.h"

namespace operations_research {
namespace {

constexpr int kGurobiOkCode = 0;
constexpr int kIntParam = 1;
constexpr int kDoubleParam = 2;
constexpr int kStringParam = 3;

absl::Status GurobiStatus(const int gurobi_code, GRBenv* const env,
const std::string& operation) {
if (gurobi_code == kGurobiOkCode) {
return absl::OkStatus();
}
return absl::InvalidArgumentError(
absl::StrCat(operation, " failed with Gurobi error ", gurobi_code, ": ",
GRBgeterrormsg(env)));
}

} // namespace

bool GurobiIsCorrectlyInstalled() {
absl::StatusOr<GRBenv*> status = GetGurobiEnv();
Expand Down Expand Up @@ -55,6 +73,75 @@ absl::StatusOr<GRBenv*> GetGurobiEnv() {
return env;
}

absl::Status CopyGurobiParameters(GRBenv* const dest, GRBenv* const src) {
if (GRBcopyparams) {
return GurobiStatus(GRBcopyparams(dest, src), src, "GRBcopyparams()");
}

const int num_parameters = GRBgetnumparams(src);
for (int i = 0; i < num_parameters; ++i) {
char* param_name = nullptr;
ABSL_RETURN_IF_ERROR(GurobiStatus(GRBgetparamname(src, i, &param_name), src,
"GRBgetparamname()"));
const int param_type = GRBgetparamtype(src, param_name);
switch (param_type) {
case kIntParam: {
int current_value;
int default_value;
int min_value;
int max_value;
ABSL_RETURN_IF_ERROR(GurobiStatus(
GRBgetintparaminfo(src, param_name, &current_value, &min_value,
&max_value, &default_value),
src, absl::StrCat("GRBgetintparaminfo(", param_name, ")")));
if (current_value != default_value) {
ABSL_RETURN_IF_ERROR(GurobiStatus(
GRBsetintparam(dest, param_name, current_value), dest,
absl::StrCat("GRBsetintparam(", param_name, ")")));
}
break;
}
case kDoubleParam: {
double current_value;
double default_value;
double min_value;
double max_value;
ABSL_RETURN_IF_ERROR(GurobiStatus(
GRBgetdblparaminfo(src, param_name, &current_value, &min_value,
&max_value, &default_value),
src, absl::StrCat("GRBgetdblparaminfo(", param_name, ")")));
if (current_value != default_value) {
ABSL_RETURN_IF_ERROR(GurobiStatus(
GRBsetdblparam(dest, param_name, current_value), dest,
absl::StrCat("GRBsetdblparam(", param_name, ")")));
}
break;
}
case kStringParam: {
char current_value[GRB_MAX_STRLEN + 1];
char default_value[GRB_MAX_STRLEN + 1];
ABSL_RETURN_IF_ERROR(GurobiStatus(
GRBgetstrparaminfo(src, param_name, current_value, default_value),
src, absl::StrCat("GRBgetstrparaminfo(", param_name, ")")));
// This ensures that strcmp does not go beyond the end of the char
// array.
current_value[GRB_MAX_STRLEN] = '\0';
default_value[GRB_MAX_STRLEN] = '\0';
if (std::strcmp(current_value, default_value) != 0) {
ABSL_RETURN_IF_ERROR(GurobiStatus(
GRBsetstrparam(dest, param_name, current_value), dest,
absl::StrCat("GRBsetstrparam(", param_name, ")")));
}
break;
}
default:
LOG(WARNING) << "Skipping Gurobi parameter '" << param_name
<< "' of unknown type " << param_type << ".";
}
}
return absl::OkStatus();
}

std::string GurobiParamInfoForLogging(GRBenv* grb, bool one_liner_output) {
const absl::ParsedFormat<'s', 's', 's'> kExtendedFormat(
" Parameter: '%s' value: %s default: %s");
Expand Down
7 changes: 7 additions & 0 deletions ortools/linear_solver/gurobi_util.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,20 @@
#include <string>

#include "absl/flags/declare.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "ortools/third_party_solvers/gurobi_environment.h"

namespace operations_research {

absl::StatusOr<GRBenv*> GetGurobiEnv();

// Copies all non-default Gurobi parameters from `src` to `dest`. When using the
// fallback implementation for Gurobi 13, parameters that are non-default on
// `dest` and default on `src` are not reset. This is intended for freshly
// created destination environments.
absl::Status CopyGurobiParameters(GRBenv* dest, GRBenv* src);

// This returns true if the Gurobi shared library is properly loaded (otherwise,
// tries to find it and load it) and if a Gurobi license can be obtained (it
// does that by trying to grab a license and then release it).
Expand Down
129 changes: 129 additions & 0 deletions ortools/linear_solver/gurobi_util_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// Copyright 2010-2025 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 "ortools/linear_solver/gurobi_util.h"

#include <functional>

#include "absl/cleanup/cleanup.h"
#include "gtest/gtest.h"
#include "ortools/base/gmock.h"
#include "ortools/third_party_solvers/gurobi_environment.h"

namespace operations_research {
namespace {

constexpr int kGurobiOkCode = 0;

void SetTestParameters(GRBenv* const env, const int output_flag,
const int threads, const double mip_gap,
const char* const log_file) {
ASSERT_EQ(GRBsetintparam(env, GRB_INT_PAR_OUTPUTFLAG, output_flag),
kGurobiOkCode)
<< GRBgeterrormsg(env);
ASSERT_EQ(GRBsetintparam(env, GRB_INT_PAR_THREADS, threads), kGurobiOkCode)
<< GRBgeterrormsg(env);
ASSERT_EQ(GRBsetdblparam(env, GRB_DBL_PAR_MIPGAP, mip_gap), kGurobiOkCode)
<< GRBgeterrormsg(env);
ASSERT_EQ(GRBsetstrparam(env, GRB_STR_PAR_LOGFILE, log_file), kGurobiOkCode)
<< GRBgeterrormsg(env);
}

void ExpectTestParameters(GRBenv* const env, const int expected_output_flag,
const int expected_threads,
const double expected_mip_gap,
const char* const expected_log_file) {
int output_flag = -1;
ASSERT_EQ(GRBgetintparam(env, GRB_INT_PAR_OUTPUTFLAG, &output_flag),
kGurobiOkCode)
<< GRBgeterrormsg(env);
EXPECT_EQ(output_flag, expected_output_flag);

int threads = 0;
ASSERT_EQ(GRBgetintparam(env, GRB_INT_PAR_THREADS, &threads), kGurobiOkCode)
<< GRBgeterrormsg(env);
EXPECT_EQ(threads, expected_threads);

double mip_gap = 0.0;
ASSERT_EQ(GRBgetdblparam(env, GRB_DBL_PAR_MIPGAP, &mip_gap), kGurobiOkCode)
<< GRBgeterrormsg(env);
EXPECT_DOUBLE_EQ(mip_gap, expected_mip_gap);

char log_file[GRB_MAX_STRLEN + 1];
ASSERT_EQ(GRBgetstrparam(env, GRB_STR_PAR_LOGFILE, log_file), kGurobiOkCode)
<< GRBgeterrormsg(env);
log_file[GRB_MAX_STRLEN] = '\0';
EXPECT_STREQ(log_file, expected_log_file);
}

void RunCopyGurobiParametersTest(const bool force_fallback) {
absl::StatusOr<GRBenv*> src_status = GetGurobiEnv();
if (!src_status.ok()) {
GTEST_SKIP() << src_status.status();
}
GRBenv* const src = src_status.value();
absl::Cleanup src_cleanup = [src] { GRBfreeenv(src); };

absl::StatusOr<GRBenv*> dest_status = GetGurobiEnv();
if (!dest_status.ok()) {
GTEST_SKIP() << dest_status.status();
}
GRBenv* const dest = dest_status.value();
absl::Cleanup dest_cleanup = [dest] { GRBfreeenv(dest); };

SetTestParameters(src, /*output_flag=*/0, /*threads=*/1, /*mip_gap=*/0.123,
"gurobi_util_test_src.log");
SetTestParameters(dest, /*output_flag=*/1, /*threads=*/2, /*mip_gap=*/0.321,
"gurobi_util_test_dest.log");

if (force_fallback) {
const std::function<int(GRBenv*, GRBenv*)> saved_copyparams = GRBcopyparams;
GRBcopyparams = nullptr;
absl::Cleanup restore_copyparams = [saved_copyparams] {
GRBcopyparams = saved_copyparams;
};
ASSERT_OK(CopyGurobiParameters(dest, src));
} else {
ASSERT_OK(CopyGurobiParameters(dest, src));
}

ExpectTestParameters(dest, /*expected_output_flag=*/0, /*expected_threads=*/1,
/*expected_mip_gap=*/0.123, "gurobi_util_test_src.log");
}

TEST(GurobiUtilTest, CopyGurobiParametersCopiesChangedParameters) {
RunCopyGurobiParametersTest(/*force_fallback=*/false);
}

TEST(GurobiUtilTest, CopyGurobiParametersFallbackCopiesChangedParameters) {
RunCopyGurobiParametersTest(/*force_fallback=*/true);
}

TEST(GurobiUtilTest, LoadGurobiDynamicLibraryCanBeCalledTwice) {
const absl::Status first_load = LoadGurobiDynamicLibrary({});
if (!first_load.ok()) {
GTEST_SKIP() << first_load;
}
ASSERT_OK(LoadGurobiDynamicLibrary({}));

int major = -1;
int minor = -1;
int technical = -1;
GRBversion(&major, &minor, &technical);
EXPECT_GE(major, 0);
EXPECT_GE(minor, 0);
EXPECT_GE(technical, 0);
}

} // namespace
} // namespace operations_research
2 changes: 1 addition & 1 deletion ortools/linear_solver/linear_solver.cc
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ constexpr
{MPSolver::PDLP_LINEAR_PROGRAMMING, "pdlp"},
{MPSolver::CPLEX_MIXED_INTEGER_PROGRAMMING, "cplex"},
{MPSolver::XPRESS_MIXED_INTEGER_PROGRAMMING, "xpress"},
};
};
// static
bool MPSolver::ParseSolverType(absl::string_view solver_id,
MPSolver::OptimizationProblemType* type) {
Expand Down
8 changes: 2 additions & 6 deletions ortools/lp_data/lp_types.cc
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,7 @@ std::ostream& operator<<(std::ostream& os, const SolveStatus status) {
return os;
}

SolveStatus OptimalSolveStatus() {
return {SolveStatus::Optimal{}};
}
SolveStatus OptimalSolveStatus() { return {SolveStatus::Optimal{}}; }

SolveStatus PrimalInfeasibleSolveStatus() {
return {SolveStatus::PrimalInfeasible{}};
Expand Down Expand Up @@ -302,9 +300,7 @@ SolveStatus InvalidProblemSolveStatus() {
return {SolveStatus::InvalidProblem{}};
}

SolveStatus ImpreciseSolveStatus() {
return {SolveStatus::Imprecise{}};
}
SolveStatus ImpreciseSolveStatus() { return {SolveStatus::Imprecise{}}; }

ProblemStatus SolveStatus::problem_status() const {
return std::visit([](const auto& alternative) { return alternative.status; },
Expand Down
2 changes: 2 additions & 0 deletions ortools/math_opt/solvers/gurobi/g_gurobi.cc
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@ void GurobiFreeEnv::operator()(GRBenv* const env) const {

absl::StatusOr<GRBenvUniquePtr> GurobiNewPrimaryEnv(
const std::optional<GurobiIsvKey>& isv_key) {
ABSL_RETURN_IF_ERROR(LoadGurobiDynamicLibrary({}));
if (isv_key.has_value()) {
ABSL_ASSIGN_OR_RETURN(GRBenv* const naked_primary_env,
NewPrimaryEnvFromISVKey(*isv_key));
Expand Down Expand Up @@ -195,6 +196,7 @@ Gurobi::Gurobi(GRBenvUniquePtr optional_owned_primary_env,
absl::StatusOr<std::unique_ptr<Gurobi>> Gurobi::New(
GRBenvUniquePtr optional_owned_primary_env, GRBenv* const primary_env) {
CHECK(primary_env != nullptr);
ABSL_RETURN_IF_ERROR(LoadGurobiDynamicLibrary({}));
GRBmodel* model = nullptr;
const int err = GRBnewmodel(primary_env, &model,
/*Pname=*/nullptr,
Expand Down
25 changes: 22 additions & 3 deletions ortools/third_party_solvers/dynamic_library.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@
class DynamicLibrary {
public:
DynamicLibrary() : library_handle_(nullptr) {}
DynamicLibrary(const DynamicLibrary&) = delete;
DynamicLibrary& operator=(const DynamicLibrary&) = delete;
DynamicLibrary(DynamicLibrary&&) = delete;
DynamicLibrary& operator=(DynamicLibrary&&) = delete;

~DynamicLibrary() {
if (library_handle_ == nullptr) {
Expand Down Expand Up @@ -59,6 +63,16 @@ class DynamicLibrary {

template <typename T>
std::function<T> GetFunction(const char* function_name) {
std::function<T> function = TryGetFunction<T>(function_name);

CHECK(function) << "Error: could not find function "
<< std::string(function_name) << " in " << library_name_;

return function;
}

template <typename T>
std::function<T> TryGetFunction(const char* function_name) {
#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
// On Windows, avoid casting to void*: not supported by MinGW.
FARPROC function_address =
Expand All @@ -67,9 +81,9 @@ class DynamicLibrary {
const void* function_address = dlsym(library_handle_, function_name);
#endif // MinGW.

CHECK(function_address)
<< "Error: could not find function " << std::string(function_name)
<< " in " << library_name_;
if (function_address == nullptr) {
return nullptr;
}

return TypeParser<T>::CreateFunction(function_address);
}
Expand All @@ -84,6 +98,11 @@ class DynamicLibrary {
*function = GetFunction<T>(function_name);
}

template <typename T>
void TryGetFunction(std::function<T>* function, const char* function_name) {
*function = TryGetFunction<T>(function_name);
}

template <typename T>
void GetFunction(std::function<T>* function,
const std::string function_name) {
Expand Down
Loading