Files
ortools-clone/ortools/sat/diffn_test.cc

182 lines
6.9 KiB
C++
Raw Normal View History

2025-01-10 11:35:44 +01:00
// Copyright 2010-2025 Google LLC
2024-09-30 17:28:08 +02:00
// 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/diffn.h"
#include <vector>
#include "absl/strings/str_join.h"
#include "gtest/gtest.h"
#include "ortools/base/logging.h"
#include "ortools/sat/cp_model.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_solver.h"
#include "ortools/sat/diffn_util.h"
2024-09-30 17:28:08 +02:00
#include "ortools/sat/integer.h"
#include "ortools/sat/integer_base.h"
2024-09-30 17:28:08 +02:00
#include "ortools/sat/integer_search.h"
#include "ortools/sat/intervals.h"
#include "ortools/sat/model.h"
#include "ortools/sat/sat_base.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/sat/sat_solver.h"
namespace operations_research {
namespace sat {
namespace {
// Counts how many ways we can put two square of minimal size 1 in an n x n
// square.
//
// For n = 1, infeasible.
// For n = 2, should be 4 * 3.
// For n = 3:
// - 9 * 8 for two size 1.
// - 4 * 5 for size 2 + size 1. Times 2 for the permutation.
int CountAllTwoBoxesSolutions(int n) {
Model model;
std::vector<IntervalVariable> x;
std::vector<IntervalVariable> y;
for (int i = 0; i < 2; ++i) {
// Create a square shaped box of minimum size 1.
const IntegerVariable size = model.Add(NewIntegerVariable(1, n));
x.push_back(
model.Add(NewInterval(model.Add(NewIntegerVariable(0, n)),
model.Add(NewIntegerVariable(0, n)), size)));
y.push_back(
model.Add(NewInterval(model.Add(NewIntegerVariable(0, n)),
model.Add(NewIntegerVariable(0, n)), size)));
}
// The cumulative relaxation adds extra variables that are not completely
2024-09-30 17:28:08 +02:00
// fixed. So to not count too many solution with our code here, we disable
// that. Note that alternatively, we could have used the cp_model.proto API
2024-09-30 17:28:08 +02:00
// to do the same, and that should works even with this on.
AddNonOverlappingRectangles(/*enforcement_literals=*/{}, x, y, &model);
2024-09-30 17:28:08 +02:00
int num_solutions_found = 0;
auto* integer_trail = model.GetOrCreate<IntegerTrail>();
auto* repository = model.GetOrCreate<IntervalsRepository>();
auto start_value = [repository, integer_trail](IntervalVariable i) {
return integer_trail->LowerBound(repository->Start(i)).value();
};
auto end_value = [repository, integer_trail](IntervalVariable i) {
return integer_trail->LowerBound(repository->End(i)).value();
};
auto rectangle = [&start_value, &end_value, x, y](int r) {
return Rectangle{.x_min = start_value(x[r]),
.x_max = end_value(x[r]),
.y_min = start_value(y[r]),
.y_max = end_value(y[r])};
};
2024-09-30 17:28:08 +02:00
while (true) {
const SatSolver::Status status =
SolveIntegerProblemWithLazyEncoding(&model);
if (status != SatSolver::Status::FEASIBLE) break;
// Display the first few solutions.
if (num_solutions_found < 30) {
LOG(INFO) << "R1: " << rectangle(0) << " R2: " << rectangle(1);
2024-09-30 17:28:08 +02:00
}
num_solutions_found++;
model.Add(ExcludeCurrentSolutionAndBacktrack());
}
return num_solutions_found;
}
TEST(NonOverlappingRectanglesTest, SimpleCounting) {
EXPECT_EQ(CountAllTwoBoxesSolutions(1), 0);
EXPECT_EQ(CountAllTwoBoxesSolutions(2), 3 * 4);
EXPECT_EQ(CountAllTwoBoxesSolutions(3), 9 * 8 + 4 * 5 * 2);
EXPECT_EQ(CountAllTwoBoxesSolutions(4),
/*2 1x1 square*/ 16 * 15 +
/*2 2x2 square*/ 2 * (5 + 3 + 4 + 4) +
/*3x3 and 1x1*/ 2 * 4 * 7 +
/*2x2 amd 1x1*/ 2 * 9 * 12);
}
TEST(NonOverlappingRectanglesTest, SimpleCountingWithOptional) {
Model model;
IntervalsRepository* interval_repository =
model.GetOrCreate<IntervalsRepository>();
std::vector<IntervalVariable> x;
std::vector<IntervalVariable> y;
const Literal l1(model.Add(NewBooleanVariable()), true);
x.push_back(interval_repository->CreateInterval(
IntegerValue(0), IntegerValue(5), IntegerValue(5), l1.Index(), false));
y.push_back(interval_repository->CreateInterval(
IntegerValue(0), IntegerValue(2), IntegerValue(2), l1.Index(), false));
const Literal l2(model.Add(NewBooleanVariable()), true);
x.push_back(interval_repository->CreateInterval(
IntegerValue(4), IntegerValue(6), IntegerValue(2), l2.Index(), false));
y.push_back(interval_repository->CreateInterval(
IntegerValue(3), IntegerValue(4), IntegerValue(1), l2.Index(), false));
// The cumulative relaxation adds extra variables that are not completely
// fixed. So to not count too many solution with our code here, we disable
// that. Note that alternatively, we could have used the cp_model.proto API
// to do the same, and that should works even with this on.
// TODO(user): Fix and run with add_cumulative_relaxation = true.
AddNonOverlappingRectangles(/*enforcement_literals=*/{}, x, y, &model);
2024-09-30 17:28:08 +02:00
int num_solutions_found = 0;
while (true) {
const SatSolver::Status status =
SolveIntegerProblemWithLazyEncoding(&model);
if (status != SatSolver::Status::FEASIBLE) break;
// Display the first few solutions.
if (num_solutions_found < 30) {
LOG(INFO) << "R1: " << interval_repository->IsPresent(x[0]) << " "
<< " R2: " << interval_repository->IsPresent(x[1]) << " ";
}
num_solutions_found++;
model.Add(ExcludeCurrentSolutionAndBacktrack());
}
EXPECT_EQ(4, num_solutions_found);
}
TEST(NonOverlappingRectanglesTest, CountSolutionsWithZeroAreaBoxes) {
CpModelBuilder cp_model;
IntVar v1 = cp_model.NewIntVar({1, 2});
IntVar v2 = cp_model.NewIntVar({0, 1});
IntervalVar x1 = cp_model.NewIntervalVar(2, v2, 2 + v2);
IntervalVar x2 = cp_model.NewFixedSizeIntervalVar(1, 2);
IntervalVar y1 = cp_model.NewIntervalVar(1, v1, v1 + 1);
IntervalVar y2 = cp_model.NewFixedSizeIntervalVar(2, 0);
NoOverlap2DConstraint diffn = cp_model.AddNoOverlap2D();
diffn.AddRectangle(x1, y1);
diffn.AddRectangle(x2, y2);
Model model;
2024-11-15 14:40:47 +01:00
SatParameters* params = model.GetOrCreate<SatParameters>();
params->set_enumerate_all_solutions(true);
params->set_keep_all_feasible_solutions_in_presolve(true);
2024-09-30 17:28:08 +02:00
int count = 0;
model.Add(
NewFeasibleSolutionObserver([&count](const CpSolverResponse& response) {
LOG(INFO) << absl::StrJoin(response.solution(), " ");
++count;
}));
const CpSolverResponse response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
EXPECT_EQ(count, 2);
}
} // namespace
} // namespace sat
} // namespace operations_research