1063 lines
37 KiB
C++
1063 lines
37 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/sat/synchronization.h"
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <string>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#include "absl/time/time.h"
|
|
#include "absl/types/span.h"
|
|
#include "gtest/gtest.h"
|
|
#include "ortools/base/gmock.h"
|
|
#include "ortools/base/parse_test_proto.h"
|
|
#include "ortools/sat/cp_model.pb.h"
|
|
#include "ortools/sat/integer_base.h"
|
|
#include "ortools/sat/model.h"
|
|
#include "ortools/sat/util.h"
|
|
#include "ortools/util/random_engine.h"
|
|
|
|
namespace operations_research {
|
|
namespace sat {
|
|
namespace {
|
|
|
|
using ::google::protobuf::contrib::parse_proto::ParseTestProto;
|
|
using ::testing::ElementsAre;
|
|
using ::testing::EqualsProto;
|
|
using ::testing::IsEmpty;
|
|
|
|
TEST(SharedSolutionRepository, Api) {
|
|
SharedSolutionRepository<int64_t> repository(3);
|
|
EXPECT_EQ(repository.NumSolutions(), 0);
|
|
repository.Add({8, {1, 2}});
|
|
repository.Add({1, {2, 3}});
|
|
EXPECT_EQ(repository.NumSolutions(), 0);
|
|
repository.Synchronize();
|
|
EXPECT_EQ(repository.NumSolutions(), 2);
|
|
EXPECT_EQ(repository.GetSolution(0)->rank, 1);
|
|
EXPECT_EQ(repository.GetSolution(1)->rank, 8);
|
|
EXPECT_EQ(repository.GetVariableValueInSolution(/*var_index=*/1,
|
|
/*solution_index=*/0),
|
|
3);
|
|
EXPECT_EQ(repository.GetVariableValueInSolution(/*var_index=*/0,
|
|
/*solution_index=*/1),
|
|
1);
|
|
|
|
repository.Add({3, {1, 2}});
|
|
repository.Add({5, {1, 2}});
|
|
repository.Add({1, {2, 3}});
|
|
repository.Add({9, {1, 2}});
|
|
EXPECT_EQ(repository.NumSolutions(), 2);
|
|
repository.Synchronize();
|
|
EXPECT_EQ(repository.NumSolutions(), 3);
|
|
EXPECT_EQ(repository.GetSolution(0)->rank, 1);
|
|
EXPECT_EQ(repository.GetSolution(1)->rank, 3);
|
|
EXPECT_EQ(repository.GetSolution(2)->rank, 5);
|
|
EXPECT_EQ(repository.GetVariableValueInSolution(/*var_index=*/1,
|
|
/*solution_index=*/0),
|
|
3);
|
|
EXPECT_EQ(repository.GetVariableValueInSolution(/*var_index=*/1,
|
|
/*solution_index=*/1),
|
|
2);
|
|
}
|
|
|
|
TEST(SharedSolutionRepository, DuplicateSolutionAreMerged) {
|
|
SharedSolutionRepository<int64_t> repository(3);
|
|
EXPECT_EQ(repository.NumSolutions(), 0);
|
|
repository.Add({1, {1, 50}});
|
|
|
|
// In practice we shouldn't have the same variable values and different
|
|
// objective, but the code don't care about this and just test the perfect
|
|
// equality.
|
|
repository.Add({5, {1, 50}});
|
|
repository.Add({1, {1, 50}});
|
|
EXPECT_EQ(repository.NumSolutions(), 0);
|
|
repository.Synchronize();
|
|
EXPECT_EQ(repository.NumSolutions(), 2);
|
|
EXPECT_EQ(repository.GetSolution(0)->rank, 1);
|
|
EXPECT_EQ(repository.GetSolution(1)->rank, 5);
|
|
}
|
|
|
|
TEST(SharedSolutionRepository, DuplicateSolutionAreMerged2) {
|
|
SharedSolutionRepository<int64_t> repository(3);
|
|
EXPECT_EQ(repository.NumSolutions(), 0);
|
|
|
|
// All this should count as 1 solution.
|
|
repository.Add({3, {1, 50}});
|
|
repository.Add({3, {1, 50}});
|
|
repository.Add({3, {1, 50}});
|
|
repository.Add({3, {1, 50}});
|
|
repository.Add({3, {1, 50}});
|
|
|
|
// So we should be able to Enqueue worse ones.
|
|
repository.Add({5, {1, 50}});
|
|
repository.Add({6, {1, 50}});
|
|
|
|
repository.Synchronize();
|
|
EXPECT_EQ(repository.NumSolutions(), 3);
|
|
EXPECT_EQ(repository.GetSolution(0)->rank, 3);
|
|
EXPECT_EQ(repository.GetSolution(1)->rank, 5);
|
|
EXPECT_EQ(repository.GetSolution(2)->rank, 6);
|
|
}
|
|
|
|
TEST(SharedSolutionRepository, GetRandomBiasedSolution) {
|
|
SharedSolutionRepository<int64_t> repository(5);
|
|
EXPECT_EQ(repository.NumSolutions(), 0);
|
|
|
|
repository.Add({3, {1, 50}});
|
|
repository.Add({3, {1, 51}});
|
|
repository.Add({3, {1, 52}});
|
|
|
|
// Enqueue worse ones.
|
|
repository.Add({5, {1, 50}});
|
|
repository.Add({6, {1, 50}});
|
|
|
|
repository.Synchronize();
|
|
EXPECT_EQ(repository.NumSolutions(), 5);
|
|
|
|
// We select one of the solution with best objective.
|
|
random_engine_t random(0);
|
|
for (int i = 0; i < 10; ++i) {
|
|
EXPECT_EQ(repository.GetRandomBiasedSolution(random)->rank, 3);
|
|
}
|
|
}
|
|
|
|
TEST(SharedLPSolutionRepository, NewLPSolution) {
|
|
SharedLPSolutionRepository lp_solutions(1);
|
|
|
|
lp_solutions.NewLPSolution({1.0, 2.0, 1.0});
|
|
|
|
lp_solutions.Synchronize();
|
|
EXPECT_EQ(lp_solutions.NumSolutions(), 1);
|
|
EXPECT_EQ(lp_solutions.GetSolution(0)->variable_values[0], 1.0);
|
|
EXPECT_EQ(lp_solutions.GetSolution(0)->variable_values[1], 2.0);
|
|
EXPECT_EQ(lp_solutions.GetSolution(0)->variable_values[2], 1.0);
|
|
|
|
lp_solutions.NewLPSolution({2.0, 3.0, 0.0});
|
|
|
|
lp_solutions.Synchronize();
|
|
EXPECT_EQ(lp_solutions.NumSolutions(), 1);
|
|
EXPECT_EQ(lp_solutions.GetSolution(0)->variable_values[0], 2.0);
|
|
EXPECT_EQ(lp_solutions.GetSolution(0)->variable_values[1], 3.0);
|
|
EXPECT_EQ(lp_solutions.GetSolution(0)->variable_values[2], 0.0);
|
|
}
|
|
|
|
TEST(SharedIncompleteSolutionManager, AddAndRemoveSolutions) {
|
|
SharedIncompleteSolutionManager incomplete_solutions;
|
|
|
|
EXPECT_FALSE(incomplete_solutions.HasSolution());
|
|
incomplete_solutions.AddSolution({1.0, 2.0, 1.0});
|
|
EXPECT_TRUE(incomplete_solutions.HasSolution());
|
|
std::vector<double> solution = incomplete_solutions.PopLast();
|
|
EXPECT_THAT(solution, ::testing::ElementsAre(1.0, 2.0, 1.0));
|
|
|
|
EXPECT_FALSE(incomplete_solutions.HasSolution());
|
|
incomplete_solutions.AddSolution({2.0, 3.0, 2.0});
|
|
incomplete_solutions.AddSolution({3.0, 4.0, 3.0});
|
|
EXPECT_TRUE(incomplete_solutions.HasSolution());
|
|
solution = incomplete_solutions.PopLast();
|
|
EXPECT_THAT(solution, ::testing::ElementsAre(3.0, 4.0, 3.0));
|
|
|
|
EXPECT_TRUE(incomplete_solutions.HasSolution());
|
|
solution = incomplete_solutions.PopLast();
|
|
EXPECT_THAT(solution, ::testing::ElementsAre(2.0, 3.0, 2.0));
|
|
|
|
EXPECT_FALSE(incomplete_solutions.HasSolution());
|
|
}
|
|
|
|
TEST(SharedBoundsManagerTest, Api) {
|
|
const CpModelProto model = ParseTestProto(R"pb(
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
)pb");
|
|
SharedBoundsManager manager(model);
|
|
manager.ReportPotentialNewBounds("auto", {2, 4, 6}, {1, 2, 3}, {11, 12, 13});
|
|
manager.Synchronize();
|
|
|
|
std::vector<int> vars;
|
|
std::vector<int64_t> lbs;
|
|
std::vector<int64_t> ubs;
|
|
|
|
EXPECT_EQ(manager.RegisterNewId(), 0);
|
|
manager.GetChangedBounds(0, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, ElementsAre(2, 4));
|
|
EXPECT_THAT(lbs, ElementsAre(1, 2));
|
|
EXPECT_THAT(ubs, ElementsAre(11, 12));
|
|
|
|
EXPECT_EQ(manager.RegisterNewId(), 1);
|
|
manager.GetChangedBounds(1, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, ElementsAre(2, 4));
|
|
EXPECT_THAT(lbs, ElementsAre(1, 2));
|
|
EXPECT_THAT(ubs, ElementsAre(11, 12));
|
|
|
|
EXPECT_EQ(manager.RegisterNewId(), 2);
|
|
manager.GetChangedBounds(2, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, ElementsAre(2, 4));
|
|
EXPECT_THAT(lbs, ElementsAre(1, 2));
|
|
EXPECT_THAT(ubs, ElementsAre(11, 12));
|
|
|
|
// Test nilpotence.
|
|
manager.GetChangedBounds(2, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, IsEmpty());
|
|
EXPECT_THAT(lbs, IsEmpty());
|
|
EXPECT_THAT(ubs, IsEmpty());
|
|
|
|
// Non improving bounds, and partially improving bounds.
|
|
manager.ReportPotentialNewBounds("fixed", {2, 4}, {0, 5}, {20, 20});
|
|
manager.Synchronize();
|
|
|
|
manager.GetChangedBounds(0, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, ElementsAre(4));
|
|
EXPECT_THAT(lbs, ElementsAre(5));
|
|
EXPECT_THAT(ubs, ElementsAre(12));
|
|
|
|
manager.GetChangedBounds(1, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, ElementsAre(4));
|
|
EXPECT_THAT(lbs, ElementsAre(5));
|
|
EXPECT_THAT(ubs, ElementsAre(12));
|
|
|
|
manager.GetChangedBounds(2, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, ElementsAre(4));
|
|
EXPECT_THAT(lbs, ElementsAre(5));
|
|
EXPECT_THAT(ubs, ElementsAre(12));
|
|
|
|
// Matching bounds.
|
|
manager.ReportPotentialNewBounds("fixed", {2}, {1}, {11});
|
|
manager.Synchronize();
|
|
|
|
manager.GetChangedBounds(0, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, IsEmpty());
|
|
EXPECT_THAT(lbs, IsEmpty());
|
|
EXPECT_THAT(ubs, IsEmpty());
|
|
|
|
manager.GetChangedBounds(1, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, IsEmpty());
|
|
EXPECT_THAT(lbs, IsEmpty());
|
|
EXPECT_THAT(ubs, IsEmpty());
|
|
|
|
manager.GetChangedBounds(2, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, IsEmpty());
|
|
EXPECT_THAT(lbs, IsEmpty());
|
|
EXPECT_THAT(ubs, IsEmpty());
|
|
}
|
|
|
|
TEST(SharedBoundsManagerTest, WithSymmetry) {
|
|
const CpModelProto model = ParseTestProto(R"pb(
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
variables { domain: [ 0, 20 ] }
|
|
symmetry {
|
|
permutations {
|
|
support: [ 0, 1, 2 ]
|
|
cycle_sizes: [ 3 ]
|
|
}
|
|
}
|
|
)pb");
|
|
SharedBoundsManager manager(model);
|
|
manager.ReportPotentialNewBounds("auto", /*variables=*/{2, 1}, {4, 2},
|
|
{7, 9});
|
|
manager.Synchronize();
|
|
|
|
std::vector<int> vars;
|
|
std::vector<int64_t> lbs;
|
|
std::vector<int64_t> ubs;
|
|
|
|
EXPECT_EQ(manager.RegisterNewId(), 0);
|
|
manager.GetChangedBounds(0, &vars, &lbs, &ubs);
|
|
EXPECT_THAT(vars, ElementsAre(0, 1, 2));
|
|
EXPECT_THAT(lbs, ElementsAre(4, 4, 4));
|
|
EXPECT_THAT(ubs, ElementsAre(7, 7, 7));
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, InitialResponseSAT) {
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: UNKNOWN,
|
|
)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, InitialResponseMinimization) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: { scaling_factor: 1 })pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: UNKNOWN,
|
|
objective_value: inf,
|
|
best_objective_bound: -inf,
|
|
inner_objective_lower_bound: -9223372036854775808,
|
|
)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, InitialResponseMaximization) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: { scaling_factor: -1 })pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: UNKNOWN,
|
|
objective_value: -inf,
|
|
best_objective_bound: inf,
|
|
inner_objective_lower_bound: -9223372036854775808,
|
|
)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, UnknownResponseMinimization) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: { scaling_factor: 0 })pb");
|
|
|
|
Model model;
|
|
auto* shared_time_limit = model.GetOrCreate<ModelSharedTimeLimit>();
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(-4),
|
|
IntegerValue(7));
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
shared_time_limit->AdvanceDeterministicTime(1.0);
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(1.0 * std::log(1 + 11), shared_response->GapIntegral());
|
|
|
|
const std::string response = R"pb(
|
|
objective_value: 7,
|
|
best_objective_bound: -4,
|
|
inner_objective_lower_bound: -4
|
|
)pb";
|
|
auto result = shared_response->GetResponse();
|
|
EXPECT_EQ(result.objective_value(), 7);
|
|
EXPECT_EQ(result.best_objective_bound(), -4);
|
|
EXPECT_EQ(result.inner_objective_lower_bound(), -4);
|
|
EXPECT_EQ(result.status(), UNKNOWN);
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, UnknownResponseMaximization) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: { scaling_factor: -4 })pb");
|
|
|
|
Model model;
|
|
auto* shared_time_limit = model.GetOrCreate<ModelSharedTimeLimit>();
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(-4),
|
|
IntegerValue(7));
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
shared_time_limit->AdvanceDeterministicTime(1.0);
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(1.0 * log(1 + 4.0 * 11), shared_response->GapIntegral());
|
|
|
|
const std::string response = R"pb(
|
|
objective_value: -28,
|
|
best_objective_bound: 16,
|
|
inner_objective_lower_bound: -4
|
|
)pb";
|
|
auto result = shared_response->GetResponse();
|
|
EXPECT_EQ(result.objective_value(), -28);
|
|
EXPECT_EQ(result.best_objective_bound(), 16);
|
|
EXPECT_EQ(result.inner_objective_lower_bound(), -4);
|
|
EXPECT_EQ(result.status(), UNKNOWN);
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, GapIntegralTest) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: { scaling_factor: -4 offset: 100 })pb");
|
|
|
|
Model model;
|
|
auto* shared_time_limit = model.GetOrCreate<ModelSharedTimeLimit>();
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
// At the beginning the primal integral is zero, and will start counting
|
|
// on the first update, ignoring any earlier time. This leave a change to
|
|
// use reasonable bound on the objective.
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
shared_time_limit->AdvanceDeterministicTime(1.0);
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
|
|
// Unknown count as max possible difference.
|
|
shared_time_limit->AdvanceDeterministicTime(1.0);
|
|
shared_response->UpdateGapIntegral();
|
|
const double value1 =
|
|
1.0 *
|
|
log(1 + 4 * (static_cast<double>(std::numeric_limits<int64_t>::max()) -
|
|
static_cast<double>(std::numeric_limits<int64_t>::min())));
|
|
EXPECT_EQ(value1, shared_response->GapIntegral());
|
|
|
|
// No time, so still same. But the function height will change.
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(-4),
|
|
IntegerValue(7));
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(value1, shared_response->GapIntegral());
|
|
|
|
// Add time, increase the integral.
|
|
shared_time_limit->AdvanceDeterministicTime(3.0);
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(value1 + 3.0 * log(1 + 4.0 * 11), shared_response->GapIntegral());
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, GapIntegralOnEachChangeTest) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: { scaling_factor: -4 offset: 100 })pb");
|
|
|
|
Model model;
|
|
auto* shared_time_limit = model.GetOrCreate<ModelSharedTimeLimit>();
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
// Starts with reasonable bound this time.
|
|
shared_time_limit->AdvanceDeterministicTime(1.0);
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(0),
|
|
IntegerValue(10));
|
|
shared_response->SetUpdateGapIntegralOnEachChange(true);
|
|
shared_response->UpdateGapIntegral();
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
|
|
// First Update.
|
|
shared_time_limit->AdvanceDeterministicTime(1.0);
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(0),
|
|
IntegerValue(7));
|
|
shared_response->UpdateGapIntegral();
|
|
double expected = 1.0 * log(1 + 4 * 10);
|
|
EXPECT_EQ(expected, shared_response->GapIntegral());
|
|
|
|
// Updating bound with no time, do not do anything.
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(0),
|
|
IntegerValue(3));
|
|
EXPECT_EQ(expected, shared_response->GapIntegral());
|
|
|
|
// Add time, and change bound increase the integral.
|
|
shared_time_limit->AdvanceDeterministicTime(3.0);
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(0),
|
|
IntegerValue(2));
|
|
expected += 3.0 * log(1 + 4.0 * 3);
|
|
EXPECT_EQ(expected, shared_response->GapIntegral());
|
|
|
|
// Closing the search still increase it. And we deal correcly with bound
|
|
// crossing.
|
|
shared_time_limit->AdvanceDeterministicTime(1.0);
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(10),
|
|
IntegerValue(0));
|
|
expected += 1.0 * log(1 + 4.0 * 2);
|
|
EXPECT_EQ(expected, shared_response->GapIntegral());
|
|
}
|
|
|
|
TEST(SharedResponseManagerDeathTest, UpdateInnerObjectiveBoundsOnSAT) {
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
EXPECT_DEATH(shared_response->UpdateInnerObjectiveBounds("", IntegerValue(0),
|
|
IntegerValue(0)),
|
|
"");
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, Infeasible) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: { scaling_factor: 0 })pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(-4),
|
|
IntegerValue(7));
|
|
shared_response->NotifyThatImprovingProblemIsInfeasible("");
|
|
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: INFEASIBLE,
|
|
)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_TRUE(shared_response->ProblemIsSolved());
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, Solution) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(2),
|
|
IntegerValue(20));
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
)pb");
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
|
|
{
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: FEASIBLE,
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
objective_value: 12
|
|
best_objective_bound: 2
|
|
inner_objective_lower_bound: 2)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_FALSE(shared_response->ProblemIsSolved());
|
|
}
|
|
|
|
// Optimal.
|
|
shared_response->NotifyThatImprovingProblemIsInfeasible("");
|
|
{
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: OPTIMAL,
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
objective_value: 12
|
|
best_objective_bound: 12
|
|
inner_objective_lower_bound: 12)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_TRUE(shared_response->ProblemIsSolved());
|
|
}
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, BestBound) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
offset: 0.5
|
|
scaling_factor: 3.0
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
double result = 0.0;
|
|
auto observer = [&result](double value) { result = value; };
|
|
shared_response->AddBestBoundCallback(observer);
|
|
shared_response->UpdateInnerObjectiveBounds("", 2, 20);
|
|
EXPECT_EQ(result, 7.5);
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, SolutionToFeasibilityProblem) {
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
)pb");
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
|
|
{
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: OPTIMAL,
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ])pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_TRUE(shared_response->ProblemIsSolved());
|
|
}
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, OptimalReachedBecauseOfBounds) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_time_limit = model.GetOrCreate<ModelSharedTimeLimit>();
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
shared_time_limit->AdvanceDeterministicTime(10.0);
|
|
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(12),
|
|
IntegerValue(12));
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
)pb");
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
|
|
shared_time_limit->AdvanceDeterministicTime(10.0);
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: OPTIMAL,
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
objective_value: 12
|
|
best_objective_bound: 12
|
|
inner_objective_lower_bound: 12)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_TRUE(shared_response->ProblemIsSolved());
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, ProblemCanBeClosedWithJustBoundUpdates) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_time_limit = model.GetOrCreate<ModelSharedTimeLimit>();
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
|
|
shared_time_limit->AdvanceDeterministicTime(10.0);
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(13),
|
|
IntegerValue(12));
|
|
|
|
EXPECT_EQ(0.0, shared_response->GapIntegral());
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: INFEASIBLE)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_TRUE(shared_response->ProblemIsSolved());
|
|
}
|
|
|
|
TEST(SharedResponseManagerTest, ProblemCanBeClosedWithJustBoundUpdates2) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
)pb");
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(13),
|
|
IntegerValue(15));
|
|
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: OPTIMAL,
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
objective_value: 12
|
|
best_objective_bound: 12,
|
|
inner_objective_lower_bound: 12)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
EXPECT_TRUE(shared_response->ProblemIsSolved());
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
|
|
// TODO(user): Having a check sometime fail in multithread. Understand how
|
|
// the code can push an invalid lower bound (and still be valid). The likely
|
|
// behavior, is that at the end of the search, when the improving problem is
|
|
// infeasible, then we might have no guarantee that while incorporating new
|
|
// bounds, one thread pushes the lower bound too high ?
|
|
TEST(SharedResponseManagerDeathTest, InnerBoundMustBeValid) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
shared_response->UpdateInnerObjectiveBounds("", IntegerValue(20),
|
|
IntegerValue(25));
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
)pb");
|
|
|
|
// The lower bound is not globally valid!
|
|
EXPECT_DEATH(shared_response->NewSolution(solution.solution(),
|
|
solution.solution_info()),
|
|
"");
|
|
}
|
|
|
|
TEST(SharedResponseManagerDeathTest, OptimalCannotBeImproved) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
shared_response->NewSolution({1, 2, 1}, "test");
|
|
shared_response->NotifyThatImprovingProblemIsInfeasible("");
|
|
shared_response->NotifyThatImprovingProblemIsInfeasible("");
|
|
{
|
|
const CpSolverResponse response = ParseTestProto(R"pb(
|
|
status: OPTIMAL,
|
|
solution_info: "test"
|
|
solution: [ 1, 2, 1 ]
|
|
objective_value: 12
|
|
best_objective_bound: 12
|
|
inner_objective_lower_bound: 12)pb");
|
|
EXPECT_THAT(shared_response->GetResponse(), EqualsProto(response));
|
|
}
|
|
|
|
{
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 1, 1 ]
|
|
)pb");
|
|
EXPECT_DEATH(shared_response->NewSolution(solution.solution(),
|
|
solution.solution_info()),
|
|
"");
|
|
}
|
|
}
|
|
|
|
TEST(SharedResponseManagerDeathTest,
|
|
BetterSolutionMustNotArriveAfterInfeasible) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
// First solution.
|
|
shared_response->NewSolution({1, 1, 1}, "test");
|
|
shared_response->NotifyThatImprovingProblemIsInfeasible("");
|
|
shared_response->NotifyThatImprovingProblemIsInfeasible("");
|
|
EXPECT_EQ(shared_response->GetResponse().status(), CpSolverStatus::OPTIMAL);
|
|
|
|
{
|
|
// Better solution are not possible! otherwise there is a bug.
|
|
EXPECT_DEATH(shared_response->NewSolution({1, 0, 1}, "test2"), "");
|
|
}
|
|
}
|
|
|
|
#endif // NDEBUG
|
|
|
|
TEST(SharedResponseManagerTest, Callback) {
|
|
const CpModelProto model_proto = ParseTestProto(R"pb(
|
|
objective: {
|
|
vars: [ 0, 1, 2 ]
|
|
coeffs: [ 2, 3, 4 ]
|
|
})pb");
|
|
|
|
Model model;
|
|
auto* shared_response = model.GetOrCreate<SharedResponseManager>();
|
|
shared_response->InitializeObjective(model_proto);
|
|
|
|
int num_solutions = 0;
|
|
const int callback_id = shared_response->AddSolutionCallback(
|
|
[&num_solutions](const CpSolverResponse& /*response*/) {
|
|
++num_solutions;
|
|
});
|
|
|
|
{
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 1, 1 ]
|
|
)pb");
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
EXPECT_EQ(num_solutions, 1);
|
|
}
|
|
|
|
// Not improving, so not called.
|
|
{
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 1, 1, 1 ]
|
|
)pb");
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
EXPECT_EQ(num_solutions, 1);
|
|
}
|
|
|
|
// Improving, so called.
|
|
{
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 0, 1, 1 ]
|
|
)pb");
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
EXPECT_EQ(num_solutions, 2);
|
|
}
|
|
|
|
// Improving, but unregistered, so not called.
|
|
{
|
|
const CpSolverResponse solution = ParseTestProto(R"pb(
|
|
solution_info: "test"
|
|
solution: [ 0, 1, 0 ]
|
|
)pb");
|
|
|
|
shared_response->UnregisterCallback(callback_id);
|
|
shared_response->NewSolution(solution.solution(), solution.solution_info());
|
|
EXPECT_EQ(num_solutions, 2);
|
|
}
|
|
}
|
|
|
|
TEST(SharedClausesManagerTest, SyncApi) {
|
|
SharedClausesManager manager(/*always_synchronize=*/true);
|
|
EXPECT_EQ(0, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
EXPECT_EQ(1, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
|
|
manager.AddBinaryClause(/*id=*/0, 1, 2);
|
|
std::vector<std::pair<int, int>> new_clauses;
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_EQ(1, new_clauses.size());
|
|
EXPECT_THAT(new_clauses, ::testing::ElementsAre(std::make_pair(1, 2)));
|
|
manager.AddBinaryClause(/*id=*/1, 2, 3);
|
|
manager.AddBinaryClause(/*id=*/1, 3, 2);
|
|
manager.AddBinaryClause(/*id=*/0, 0, 1);
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_THAT(new_clauses, ::testing::ElementsAre(std::make_pair(2, 3),
|
|
std::make_pair(0, 1)));
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_THAT(new_clauses, ::testing::ElementsAre(std::make_pair(0, 1)));
|
|
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
}
|
|
|
|
TEST(UniqueClauseStreamTest, AddIgnoresDuplicates) {
|
|
UniqueClauseStream stream;
|
|
|
|
EXPECT_TRUE(stream.Add({1, 2, 3}));
|
|
EXPECT_FALSE(stream.Add({3, 2, 1}));
|
|
EXPECT_EQ(stream.NumBufferedLiterals(), 3);
|
|
}
|
|
|
|
TEST(UniqueClauseStreamTest, AddIgnoresInvalidSizeClauses) {
|
|
UniqueClauseStream stream;
|
|
std::vector<int> long_clause;
|
|
long_clause.resize(UniqueClauseStream::kMaxClauseSize + 1);
|
|
for (int i = 0; i < long_clause.size(); ++i) long_clause[i] = i;
|
|
|
|
EXPECT_FALSE(stream.Add({2, 1}));
|
|
EXPECT_FALSE(stream.Add(long_clause));
|
|
EXPECT_EQ(stream.NumBufferedLiterals(), 0);
|
|
}
|
|
|
|
TEST(UniqueClauseStreamTest, ExportsShortestClauses) {
|
|
UniqueClauseStream stream;
|
|
for (int i = 0; i < 1024 / 4; ++i) {
|
|
stream.Add({i + 1, i + 256, i + 512, -4});
|
|
}
|
|
for (int i = 0; i < 1024 / 3; ++i) {
|
|
stream.Add({i + 1, i + 256, i + 512});
|
|
}
|
|
for (int i = 0; i < 1024 / 5; ++i) {
|
|
stream.Add({i + 1, i + 256, i + 512, i + 1024, -2048});
|
|
}
|
|
|
|
// Batch 1 should be filled with size 3 clauses.
|
|
EXPECT_EQ(stream.NextBatch().size(),
|
|
UniqueClauseStream::kMaxLiteralsPerBatch / 3);
|
|
// Batch 2 should be empty.
|
|
EXPECT_TRUE(stream.NextBatch().empty());
|
|
}
|
|
|
|
TEST(UniqueClauseStreamTest, DropsClauses) {
|
|
UniqueClauseStream stream;
|
|
int literals_successfully_added = 0;
|
|
for (int i = 0; i < 256 / 4; ++i) {
|
|
literals_successfully_added +=
|
|
4 * stream.Add({i + 1, i + 256, i + 512, -4});
|
|
}
|
|
for (int i = 0; i < UniqueClauseStream::kMaxLiteralsPerBatch / 3; ++i) {
|
|
literals_successfully_added += 3 * stream.Add({i + 1, i + 256, i + 512});
|
|
}
|
|
for (int i = 0; i < 1024 * 1024 / 5; ++i) {
|
|
literals_successfully_added +=
|
|
5 * stream.Add({i + 1, i + 256, i + 512, i + 1024, -2048});
|
|
}
|
|
|
|
EXPECT_GT(stream.NumBufferedLiterals(),
|
|
UniqueClauseStream::kMaxLiteralsPerBatch - 5);
|
|
// Batch should be filled with size 3 clauses.
|
|
EXPECT_EQ(stream.NextBatch().size(),
|
|
UniqueClauseStream::kMaxLiteralsPerBatch / 3);
|
|
EXPECT_TRUE(stream.NextBatch().empty());
|
|
}
|
|
|
|
TEST(SharedClausesManagerTest, NonSyncApi) {
|
|
SharedClausesManager manager(/*always_synchronize=*/false);
|
|
EXPECT_EQ(0, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
EXPECT_EQ(1, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
|
|
manager.AddBinaryClause(/*id=*/0, 1, 2);
|
|
std::vector<std::pair<int, int>> new_clauses;
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
|
|
manager.Synchronize();
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_EQ(1, new_clauses.size());
|
|
EXPECT_THAT(new_clauses, ::testing::ElementsAre(std::make_pair(1, 2)));
|
|
|
|
manager.AddBinaryClause(/*id=*/1, 2, 3);
|
|
manager.AddBinaryClause(/*id=*/1, 3, 2);
|
|
manager.AddBinaryClause(/*id=*/0, 0, 1);
|
|
|
|
// Not synced.
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
|
|
// After sync.
|
|
manager.Synchronize();
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_THAT(new_clauses, ::testing::ElementsAre(std::make_pair(2, 3),
|
|
std::make_pair(0, 1)));
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_THAT(new_clauses, ::testing::ElementsAre(std::make_pair(0, 1)));
|
|
|
|
// Not synced.
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
|
|
// After sync.
|
|
manager.Synchronize();
|
|
manager.GetUnseenBinaryClauses(/*id=*/0, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
manager.GetUnseenBinaryClauses(/*id=*/1, &new_clauses);
|
|
EXPECT_TRUE(new_clauses.empty());
|
|
}
|
|
|
|
TEST(SharedClausesManagerTest, ShareGlueClauses) {
|
|
SharedClausesManager manager(/*always_synchronize=*/true);
|
|
ASSERT_EQ(0, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
ASSERT_EQ(1, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
UniqueClauseStream stream0;
|
|
UniqueClauseStream stream1;
|
|
// Add a bunch of clauses that will be skipped batch.
|
|
for (int i = 0; i < UniqueClauseStream::kMaxLiteralsPerBatch / 8; ++i) {
|
|
EXPECT_TRUE(stream0.Add({1, 2, 3, 4, 5, 6, 7, i + 8}));
|
|
}
|
|
EXPECT_EQ(stream0.NumBufferedLiterals(),
|
|
UniqueClauseStream::kMaxLiteralsPerBatch);
|
|
// Fill 1 batch of shorter clauses.
|
|
for (int i = 0; i < UniqueClauseStream::kMaxLiteralsPerBatch / 4; ++i) {
|
|
stream1.Add({1, 2, 3, i + 4});
|
|
}
|
|
manager.AddBatch(0, stream0.NextBatch());
|
|
manager.AddBatch(1, stream1.NextBatch());
|
|
manager.Synchronize();
|
|
|
|
EXPECT_THAT(manager.GetUnseenClauses(0),
|
|
::testing::SizeIs(UniqueClauseStream::kMaxLiteralsPerBatch / 4));
|
|
EXPECT_THAT(manager.GetUnseenClauses(1),
|
|
::testing::SizeIs(UniqueClauseStream::kMaxLiteralsPerBatch / 4));
|
|
EXPECT_THAT(manager.GetUnseenClauses(0), ::testing::IsEmpty());
|
|
EXPECT_THAT(manager.GetUnseenClauses(1), ::testing::IsEmpty());
|
|
}
|
|
|
|
TEST(SharedClausesManagerTest, LbdThresholdIncrease) {
|
|
SharedClausesManager manager(/*always_synchronize=*/true);
|
|
ASSERT_EQ(0, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
ASSERT_EQ(1, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
UniqueClauseStream stream0;
|
|
UniqueClauseStream stream1;
|
|
const int kExpectedClauses = UniqueClauseStream::kMaxLiteralsPerBatch / 5;
|
|
for (int i = 0; i < kExpectedClauses; ++i) {
|
|
stream0.Add({i + 1, i + 513, 2048, 2049, -10});
|
|
stream1.Add({i + 1, i + 513, 2048, 2049, -10});
|
|
}
|
|
manager.AddBatch(0, stream0.NextBatch());
|
|
manager.AddBatch(1, stream1.NextBatch());
|
|
manager.Synchronize();
|
|
|
|
EXPECT_THAT(manager.GetUnseenClauses(0), ::testing::SizeIs(kExpectedClauses));
|
|
EXPECT_THAT(manager.GetUnseenClauses(1), ::testing::SizeIs(kExpectedClauses));
|
|
EXPECT_EQ(stream0.lbd_threshold(), 2);
|
|
EXPECT_EQ(stream1.lbd_threshold(), 2);
|
|
manager.Synchronize();
|
|
manager.AddBatch(0, stream0.NextBatch());
|
|
manager.AddBatch(1, stream1.NextBatch());
|
|
EXPECT_THAT(manager.GetUnseenClauses(0), ::testing::IsEmpty());
|
|
EXPECT_THAT(manager.GetUnseenClauses(1), ::testing::IsEmpty());
|
|
EXPECT_EQ(stream0.lbd_threshold(), 3);
|
|
EXPECT_EQ(stream1.lbd_threshold(), 3);
|
|
}
|
|
|
|
TEST(SharedClausesManagerTest, LbdThresholdDecrease) {
|
|
SharedClausesManager manager(/*always_synchronize=*/true);
|
|
ASSERT_EQ(0, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
ASSERT_EQ(1, manager.RegisterNewId(/*may_terminate_early=*/false));
|
|
UniqueClauseStream stream0;
|
|
UniqueClauseStream stream1;
|
|
|
|
manager.AddBatch(0, stream0.NextBatch());
|
|
manager.AddBatch(1, stream1.NextBatch());
|
|
const int kSize4Clauses = UniqueClauseStream::kMaxLiteralsPerBatch / 4 / 2;
|
|
const int kSize5ClausesAdded = UniqueClauseStream::kMaxLiteralsPerBatch / 5;
|
|
// Then add 1/2 batch of size 4 clauses to each worker.
|
|
for (int i = 0; i < kSize4Clauses; ++i) {
|
|
stream0.Add({i + 1, i + 512, 2048, 2049});
|
|
stream1.Add({i + 1, i + 513, 2048, -123});
|
|
}
|
|
// Than add loads of longer clauses to just stream0.
|
|
for (int i = 0; i < kSize5ClausesAdded; ++i) {
|
|
stream0.Add({i + 1, 2, 3, -10, 12});
|
|
}
|
|
|
|
EXPECT_EQ(stream0.lbd_threshold(), 3);
|
|
EXPECT_EQ(stream1.lbd_threshold(), 3);
|
|
manager.AddBatch(0, stream0.NextBatch());
|
|
manager.AddBatch(1, stream1.NextBatch());
|
|
manager.Synchronize();
|
|
EXPECT_THAT(manager.GetUnseenClauses(0),
|
|
::testing::SizeIs(2 * kSize4Clauses));
|
|
EXPECT_THAT(manager.GetUnseenClauses(1),
|
|
::testing::SizeIs(2 * kSize4Clauses));
|
|
EXPECT_EQ(stream0.lbd_threshold(), 2);
|
|
}
|
|
} // namespace
|
|
} // namespace sat
|
|
} // namespace operations_research
|