* bump abseil to 20250814 * bump protobuf to v32.0 * cmake: add ccache auto support * backport flatzinc, math_opt and sat update
250 lines
8.3 KiB
C++
250 lines
8.3 KiB
C++
// 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/math_opt/cpp/parameters.h"
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <type_traits>
|
|
|
|
#include "absl/status/status.h"
|
|
#include "absl/status/statusor.h"
|
|
#include "absl/time/time.h"
|
|
#include "google/protobuf/duration.pb.h"
|
|
#include "gtest/gtest.h"
|
|
#include "ortools/base/gmock.h"
|
|
#include "ortools/base/linked_hash_map.h"
|
|
#include "ortools/base/status_macros.h"
|
|
#include "ortools/math_opt/cpp/enums_testing.h"
|
|
#include "ortools/math_opt/parameters.pb.h"
|
|
#include "ortools/math_opt/solvers/glpk.pb.h"
|
|
|
|
namespace operations_research {
|
|
namespace math_opt {
|
|
namespace {
|
|
|
|
using ::testing::ElementsAre;
|
|
using ::testing::EqualsProto;
|
|
using ::testing::EquivToProto;
|
|
using ::testing::HasSubstr;
|
|
using ::testing::Optional;
|
|
using ::testing::Pair;
|
|
using ::testing::status::StatusIs;
|
|
|
|
INSTANTIATE_TYPED_TEST_SUITE_P(SolverType, EnumTest, SolverType);
|
|
INSTANTIATE_TYPED_TEST_SUITE_P(LPAlgorithm, EnumTest, LPAlgorithm);
|
|
INSTANTIATE_TYPED_TEST_SUITE_P(Emphasis, EnumTest, Emphasis);
|
|
|
|
TEST(SolverTypeTest, Flags) {
|
|
{
|
|
SolverType value = SolverType::kGlop;
|
|
std::string error;
|
|
EXPECT_TRUE(AbslParseFlag("glpk", &value, &error));
|
|
EXPECT_EQ(value, SolverType::kGlpk);
|
|
EXPECT_EQ(error, "");
|
|
|
|
EXPECT_EQ(AbslUnparseFlag(value), "glpk");
|
|
}
|
|
{
|
|
SolverType value = SolverType::kGlpk;
|
|
std::string error;
|
|
EXPECT_FALSE(AbslParseFlag("unknown", &value, &error));
|
|
EXPECT_NE(error, "");
|
|
}
|
|
}
|
|
|
|
TEST(LPAlgorithmTest, Flags) {
|
|
{
|
|
LPAlgorithm value = LPAlgorithm::kPrimalSimplex;
|
|
std::string error;
|
|
EXPECT_TRUE(AbslParseFlag("primal_simplex", &value, &error));
|
|
EXPECT_EQ(value, LPAlgorithm::kPrimalSimplex);
|
|
EXPECT_EQ(error, "");
|
|
|
|
EXPECT_EQ(AbslUnparseFlag(value), "primal_simplex");
|
|
}
|
|
{
|
|
LPAlgorithm value = LPAlgorithm::kPrimalSimplex;
|
|
std::string error;
|
|
EXPECT_FALSE(AbslParseFlag("unknown", &value, &error));
|
|
EXPECT_NE(error, "");
|
|
}
|
|
}
|
|
|
|
TEST(EmphasisTest, Flags) {
|
|
{
|
|
Emphasis value = Emphasis::kHigh;
|
|
std::string error;
|
|
EXPECT_TRUE(AbslParseFlag("high", &value, &error));
|
|
EXPECT_EQ(value, Emphasis::kHigh);
|
|
EXPECT_EQ(error, "");
|
|
|
|
EXPECT_EQ(AbslUnparseFlag(value), "high");
|
|
}
|
|
{
|
|
Emphasis value = Emphasis::kHigh;
|
|
std::string error;
|
|
EXPECT_FALSE(AbslParseFlag("unknown", &value, &error));
|
|
EXPECT_NE(error, "");
|
|
}
|
|
}
|
|
|
|
TEST(GurobiParameters, Empty) {
|
|
GurobiParameters gurobi;
|
|
EXPECT_TRUE(gurobi.empty());
|
|
gurobi.param_values["x"] = "dog";
|
|
EXPECT_FALSE(gurobi.empty());
|
|
}
|
|
|
|
TEST(GurobiParameters, ProtoRoundTrip) {
|
|
GurobiParameters gurobi;
|
|
gurobi.param_values["x"] = "dog";
|
|
gurobi.param_values["ab"] = "7";
|
|
const GurobiParametersProto proto = gurobi.Proto();
|
|
EXPECT_THAT(proto, EquivToProto(R"pb(parameters:
|
|
[ { name: "x" value: "dog" }
|
|
, { name: "ab" value: "7" }])pb"));
|
|
GurobiParameters end = GurobiParameters::FromProto(proto);
|
|
EXPECT_THAT(end.param_values, ElementsAre(Pair("x", "dog"), Pair("ab", "7")));
|
|
}
|
|
|
|
TEST(GurobiParameters, EmptyProtoRoundTrip) {
|
|
GurobiParameters gurobi;
|
|
const GurobiParametersProto proto = gurobi.Proto();
|
|
EXPECT_THAT(proto, EquivToProto(GurobiParametersProto()));
|
|
GurobiParameters end = GurobiParameters::FromProto(proto);
|
|
EXPECT_THAT(end.param_values, ::testing::IsEmpty());
|
|
}
|
|
|
|
TEST(GlpkParameters, ProtoRoundTrip) {
|
|
// Test with `optional bool` set to true.
|
|
{
|
|
GlpkParametersProto proto;
|
|
proto.set_compute_unbound_rays_if_possible(true);
|
|
EXPECT_THAT(GlpkParameters::FromProto(proto).Proto(), EqualsProto(proto));
|
|
}
|
|
// Test with `optional bool` set to false.
|
|
{
|
|
GlpkParametersProto proto;
|
|
proto.set_compute_unbound_rays_if_possible(false);
|
|
EXPECT_THAT(GlpkParameters::FromProto(proto).Proto(), EqualsProto(proto));
|
|
}
|
|
}
|
|
|
|
TEST(GlpkParameters, EmptyProtoRoundTrip) {
|
|
const GlpkParametersProto empty;
|
|
EXPECT_THAT(GlpkParameters::FromProto(empty).Proto(), EqualsProto(empty));
|
|
}
|
|
|
|
TEST(SolveParameters, EmptyProtoRoundTrip) {
|
|
const SolveParametersProto start;
|
|
ASSERT_OK_AND_ASSIGN(SolveParameters cpp_params,
|
|
SolveParameters::FromProto(start));
|
|
const SolveParametersProto end = cpp_params.Proto();
|
|
EXPECT_THAT(end, EquivToProto(start));
|
|
}
|
|
|
|
TEST(SolveParameters, CommonParamsRoundTrip) {
|
|
SolveParameters start;
|
|
start.enable_output = true;
|
|
start.time_limit = absl::Seconds(1);
|
|
start.iteration_limit = 7;
|
|
start.node_limit = 3;
|
|
start.relative_gap_tolerance = 1.0;
|
|
start.absolute_gap_tolerance = 0.1;
|
|
start.cutoff_limit = 50.1;
|
|
start.objective_limit = 51.1;
|
|
start.best_bound_limit = 52.1;
|
|
start.solution_limit = 17;
|
|
start.threads = 3;
|
|
start.random_seed = 12;
|
|
start.solution_pool_size = 44;
|
|
start.lp_algorithm = LPAlgorithm::kDualSimplex;
|
|
start.presolve = Emphasis::kMedium;
|
|
start.cuts = Emphasis::kOff;
|
|
start.scaling = Emphasis::kLow;
|
|
start.heuristics = Emphasis::kVeryHigh;
|
|
const SolveParametersProto proto = start.Proto();
|
|
EXPECT_THAT(proto, EquivToProto(R"pb(
|
|
enable_output: true
|
|
time_limit: { seconds: 1 }
|
|
iteration_limit: 7
|
|
node_limit: 3
|
|
relative_gap_tolerance: 1.0
|
|
absolute_gap_tolerance: 0.1
|
|
cutoff_limit: 50.1
|
|
objective_limit: 51.1
|
|
best_bound_limit: 52.1
|
|
solution_limit: 17
|
|
threads: 3
|
|
random_seed: 12
|
|
solution_pool_size: 44
|
|
lp_algorithm: LP_ALGORITHM_DUAL_SIMPLEX
|
|
presolve: EMPHASIS_MEDIUM
|
|
cuts: EMPHASIS_OFF
|
|
scaling: EMPHASIS_LOW
|
|
heuristics: EMPHASIS_VERY_HIGH
|
|
)pb"));
|
|
ASSERT_OK_AND_ASSIGN(const SolveParameters end,
|
|
SolveParameters::FromProto(proto));
|
|
EXPECT_TRUE(end.enable_output);
|
|
EXPECT_EQ(end.time_limit, absl::Seconds(1));
|
|
EXPECT_THAT(end.iteration_limit, Optional(7));
|
|
EXPECT_THAT(end.node_limit, Optional(3));
|
|
EXPECT_THAT(end.relative_gap_tolerance, Optional(1.0));
|
|
EXPECT_THAT(end.absolute_gap_tolerance, Optional(0.1));
|
|
EXPECT_THAT(end.cutoff_limit, Optional(50.1));
|
|
EXPECT_THAT(end.objective_limit, Optional(51.1));
|
|
EXPECT_THAT(end.best_bound_limit, Optional(52.1));
|
|
EXPECT_THAT(end.solution_limit, Optional(17));
|
|
EXPECT_THAT(end.threads, Optional(3));
|
|
EXPECT_THAT(end.random_seed, Optional(12));
|
|
EXPECT_THAT(end.solution_pool_size, Optional(44));
|
|
EXPECT_THAT(end.lp_algorithm, Optional(LPAlgorithm::kDualSimplex));
|
|
EXPECT_THAT(end.presolve, Optional(Emphasis::kMedium));
|
|
EXPECT_THAT(end.cuts, Optional(Emphasis::kOff));
|
|
EXPECT_THAT(end.scaling, Optional(Emphasis::kLow));
|
|
EXPECT_THAT(end.heuristics, Optional(Emphasis::kVeryHigh));
|
|
}
|
|
|
|
TEST(SolveParameters, SolverSpecificParamsRoundTrip) {
|
|
SolveParametersProto start;
|
|
start.set_random_seed(7);
|
|
start.mutable_gscip()->set_num_solutions(12);
|
|
auto* p = start.mutable_gurobi()->add_parameters();
|
|
p->set_name("x");
|
|
p->set_value("7");
|
|
start.mutable_glop()->set_random_seed(45);
|
|
start.mutable_cp_sat()->set_num_workers(50);
|
|
start.mutable_osqp()->set_alpha(1.2);
|
|
start.mutable_glpk()->set_compute_unbound_rays_if_possible(true);
|
|
(*start.mutable_highs()->mutable_int_options())["test_param"] = 3;
|
|
ASSERT_OK_AND_ASSIGN(SolveParameters cpp_params,
|
|
SolveParameters::FromProto(start));
|
|
const SolveParametersProto end = cpp_params.Proto();
|
|
EXPECT_THAT(end, EquivToProto(start));
|
|
}
|
|
|
|
TEST(SolveParameters, FromProtoBadTimeLimit) {
|
|
SolveParametersProto proto;
|
|
proto.mutable_time_limit()->set_seconds(1);
|
|
proto.mutable_time_limit()->set_nanos(-1);
|
|
EXPECT_THAT(
|
|
SolveParameters::FromProto(proto),
|
|
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("time_limit")));
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace math_opt
|
|
} // namespace operations_research
|