Files
ortools-clone/ortools/sat/cp_model_test.cc
Laurent Perron f4f39748a1 minor optims
2025-04-10 11:40:38 +02:00

1979 lines
67 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/cp_model.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <string>
#include <utility>
#include <vector>
#include "absl/types/span.h"
#include "gtest/gtest.h"
#include "ortools/base/container_logging.h"
#include "ortools/base/gmock.h"
#include "ortools/base/logging.h"
#include "ortools/base/parse_test_proto.h"
#include "ortools/sat/cp_model.pb.h"
#include "ortools/sat/cp_model_checker.h"
#include "ortools/sat/cp_model_solver.h"
#include "ortools/sat/model.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/util/sorted_interval_list.h"
namespace operations_research {
namespace sat {
namespace {
using ::google::protobuf::contrib::parse_proto::ParseTestProto;
using ::testing::ElementsAre;
using ::testing::EqualsProto;
using ::testing::HasSubstr;
TEST(BoolVarTest, NullAPI) {
BoolVar var;
EXPECT_EQ(var.Name(), "null");
EXPECT_EQ(var.DebugString(), "null");
if (!DEBUG_MODE) var.WithName("ignored"); // Do not crash.
}
TEST(BoolVarTest, TestApi) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = b1.Not();
const BoolVar b3 = b2.Not();
EXPECT_EQ(b1.DebugString(), "b1(0, 1)");
EXPECT_EQ(b2.DebugString(), "Not(b1(0, 1))");
EXPECT_EQ(b1, b3);
EXPECT_EQ(b1.Not(), Not(b1));
EXPECT_EQ(b1.Name(), "b1");
EXPECT_EQ(b2.Name(), "Not(b1)");
ASSERT_EQ(Domain(0, 1), IntVar(b1).Domain());
const BoolVar t = cp_model.TrueVar();
ASSERT_EQ(Domain(1), IntVar(t).Domain());
EXPECT_EQ(t.DebugString(), "true");
const BoolVar f = cp_model.FalseVar();
ASSERT_EQ(Domain(0), IntVar(f).Domain());
EXPECT_EQ(f.DebugString(), "false");
}
TEST(IntVarTest, NullAPI) {
IntVar var;
EXPECT_EQ(var.Name(), "null");
EXPECT_EQ(var.DebugString(), "null");
if (!DEBUG_MODE) var.WithName("ignored"); // Do not crash.
}
TEST(IntVarTest, NullApiFromBoolVar) {
IntVar var = IntVar(BoolVar());
EXPECT_EQ(var.Name(), "null");
EXPECT_EQ(var.DebugString(), "null");
if (!DEBUG_MODE) var.WithName("ignored"); // Do not crash.
}
TEST(IntVarTest, TestApi) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({2, 8}).WithName("x");
EXPECT_EQ("x", x.Name());
ASSERT_EQ(Domain(2, 8), x.Domain());
EXPECT_EQ(x.DebugString(), "x(2, 8)");
const IntVar y =
cp_model.NewIntVar(Domain::FromValues({1, 2, 5, 3})).WithName("y");
EXPECT_EQ("y", y.Name());
ASSERT_EQ(Domain::FromValues({1, 2, 3, 5}), y.Domain());
const IntVar z1 = cp_model.NewConstant(5);
const IntVar z2 = cp_model.NewConstant(6);
const IntVar z3 = cp_model.NewConstant(5);
EXPECT_EQ(z1.DebugString(), "5");
EXPECT_EQ(z1, z3);
EXPECT_NE(z1, z2);
}
TEST(CpModelBuilderTest, UsingUninitializedVariableResultInInvalidModel) {
// This test the non-debug behavior.
if (DEBUG_MODE) return;
CpModelBuilder builder;
IntVar x, y;
builder.AddEquality(x, y);
CpModelProto model = builder.Build();
EXPECT_NE(ValidateCpModel(model), "");
}
TEST(CpModelBuilderTest, FixVariableApi) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({2, 8});
const BoolVar a = cp_model.NewBoolVar();
const BoolVar b = a.Not();
EXPECT_EQ(Domain(2, 8), x.Domain());
cp_model.FixVariable(x, 10);
EXPECT_EQ(Domain(10), x.Domain());
EXPECT_EQ("BoolVar1(0, 1)", a.DebugString());
cp_model.FixVariable(a, true);
EXPECT_EQ("true", a.DebugString());
EXPECT_EQ("Not(true)", b.DebugString());
cp_model.FixVariable(b, true);
EXPECT_EQ("false", a.DebugString());
EXPECT_EQ("Not(false)", b.DebugString());
}
TEST(IntVarTest, TestBoolVarToIntVarCast) {
CpModelBuilder cp_model;
const BoolVar b = cp_model.NewBoolVar().WithName("b");
const IntVar x = IntVar(b);
ASSERT_EQ(Domain(0, 1), x.Domain());
EXPECT_EQ(x.Name(), "b");
}
TEST(IntVarTest, TestNotBoolVarToIntVarCast) {
CpModelBuilder cp_model;
const BoolVar b = cp_model.NewBoolVar().WithName("b");
// This cast create an extra variable.
const IntVar x = IntVar(b.Not());
EXPECT_EQ(x.Name(), "Not(b)");
EXPECT_EQ(x.DebugString(), "Not(b)(0, 1)");
EXPECT_EQ(x.index(), 1);
}
TEST(IntVarTest, TestIntVarToBoolVarCast) {
CpModelBuilder cp_model;
const IntVar int_1 = cp_model.NewIntVar({0, 1});
const BoolVar bool_1 = int_1.ToBoolVar();
EXPECT_EQ(int_1.index(), bool_1.index());
const IntVar int_true = cp_model.NewIntVar({1, 1});
const BoolVar bool_true = int_true.ToBoolVar();
EXPECT_EQ(int_true.index(), bool_true.index());
const IntVar int_false = cp_model.NewIntVar({0, 0});
const BoolVar bool_false = int_false.ToBoolVar();
EXPECT_EQ(int_false.index(), bool_false.index());
}
TEST(LinearExprTest, TestApi) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({2, 8}).WithName("x");
const IntVar y = cp_model.NewIntVar({1, 5}).WithName("y");
const BoolVar b = cp_model.NewBoolVar().WithName("b");
LinearExpr e1(x);
EXPECT_THAT(e1.variables(), ElementsAre(x.index()));
EXPECT_THAT(e1.coefficients(), ElementsAre(1));
EXPECT_EQ(e1.constant(), 0);
e1 += b;
EXPECT_THAT(e1.variables(), ElementsAre(x.index(), b.index()));
EXPECT_THAT(e1.coefficients(), ElementsAre(1, 1));
e1 += y * 3;
EXPECT_THAT(e1.variables(), ElementsAre(x.index(), b.index(), y.index()));
EXPECT_THAT(e1.coefficients(), ElementsAre(1, 1, 3));
e1 += 10;
EXPECT_EQ(10, e1.constant());
LinearExpr e2(b);
EXPECT_THAT(e2.variables(), ElementsAre(b.index()));
EXPECT_THAT(e2.coefficients(), ElementsAre(1));
EXPECT_EQ(0, e2.constant());
LinearExpr e3(-5);
ASSERT_EQ(0, e3.variables().size());
ASSERT_EQ(0, e3.coefficients().size());
EXPECT_EQ(-5, e3.constant());
e3 += b.Not();
EXPECT_THAT(e3.variables(), ElementsAre(b.index()));
EXPECT_THAT(e3.coefficients(), ElementsAre(-1));
EXPECT_EQ(-4, e3.constant());
LinearExpr e4(b.Not());
EXPECT_THAT(e4.variables(), ElementsAre(b.index()));
EXPECT_THAT(e4.coefficients(), ElementsAre(-1));
EXPECT_EQ(1, e4.constant());
LinearExpr e5 = x + 22;
EXPECT_THAT(e5.variables(), ElementsAre(x.index()));
EXPECT_THAT(e5.coefficients(), ElementsAre(1));
EXPECT_EQ(22, e5.constant());
LinearExpr e6 = b + b + 23;
EXPECT_THAT(e6.variables(), ElementsAre(b.index(), b.index()));
EXPECT_THAT(e6.coefficients(), ElementsAre(1, 1));
EXPECT_EQ(23, e6.constant());
LinearExpr e7 = -5 * x + 24;
EXPECT_THAT(e7.variables(), ElementsAre(x.index()));
EXPECT_THAT(e7.coefficients(), ElementsAre(-5));
EXPECT_EQ(24, e7.constant());
LinearExpr e8 = LinearExpr::WeightedSum({b}, {17}) + 26;
EXPECT_THAT(e8.variables(), ElementsAre(b.index()));
EXPECT_THAT(e8.coefficients(), ElementsAre(17));
EXPECT_EQ(26, e8.constant());
LinearExpr e9 = LinearExpr::Sum({x});
EXPECT_THAT(e9.variables(), ElementsAre(x.index()));
EXPECT_THAT(e9.coefficients(), ElementsAre(1));
EXPECT_EQ(0, e9.constant());
LinearExpr e10 = LinearExpr::Sum({b});
EXPECT_THAT(e10.variables(), ElementsAre(b.index()));
EXPECT_THAT(e10.coefficients(), ElementsAre(1));
EXPECT_EQ(0, e10.constant());
LinearExpr e11 = LinearExpr::WeightedSum({x}, {-5});
EXPECT_THAT(e11.variables(), ElementsAre(x.index()));
EXPECT_THAT(e11.coefficients(), ElementsAre(-5));
EXPECT_EQ(0, e11.constant());
LinearExpr e12 = LinearExpr::WeightedSum({b}, {17});
EXPECT_THAT(e12.variables(), ElementsAre(b.index()));
EXPECT_THAT(e12.coefficients(), ElementsAre(17));
EXPECT_EQ(0, e12.constant());
LinearExpr e13 = LinearExpr::Sum({b});
EXPECT_THAT(e13.variables(), ElementsAre(b.index()));
EXPECT_THAT(e13.coefficients(), ElementsAre(1));
EXPECT_EQ(0, e13.constant());
LinearExpr e14 = LinearExpr::Sum({b, b}) + 23;
EXPECT_THAT(e14.variables(), ElementsAre(b.index(), b.index()));
EXPECT_THAT(e14.coefficients(), ElementsAre(1, 1));
EXPECT_EQ(23, e14.constant());
LinearExpr e15 = LinearExpr::WeightedSum({b}, {17});
EXPECT_THAT(e15.variables(), ElementsAre(b.index()));
EXPECT_THAT(e15.coefficients(), ElementsAre(17));
EXPECT_EQ(0, e15.constant());
LinearExpr e16 = LinearExpr::WeightedSum({b}, {17}) + 26;
EXPECT_THAT(e16.variables(), ElementsAre(b.index()));
EXPECT_THAT(e16.coefficients(), ElementsAre(17));
EXPECT_EQ(26, e16.constant());
std::vector<BoolVar> bools = {b};
LinearExpr e17 = LinearExpr::Sum(bools) + 23 - 10;
EXPECT_THAT(e17.variables(), ElementsAre(b.index()));
EXPECT_THAT(e17.coefficients(), ElementsAre(1));
EXPECT_EQ(13, e17.constant());
LinearExpr e18 = LinearExpr::Term(x, -5);
EXPECT_THAT(e18.variables(), ElementsAre(x.index()));
EXPECT_THAT(e18.coefficients(), ElementsAre(-5));
EXPECT_EQ(0, e18.constant());
LinearExpr e19 = LinearExpr::Term(b.Not(), 4);
EXPECT_THAT(e19.variables(), ElementsAre(b.index()));
EXPECT_THAT(e19.coefficients(), ElementsAre(-4));
EXPECT_EQ(4, e19.constant());
LinearExpr e20 = x + 7;
EXPECT_THAT(e20.variables(), ElementsAre(x.index()));
EXPECT_THAT(e20.coefficients(), ElementsAre(1));
EXPECT_EQ(e20.constant(), 7);
}
TEST(DoubleLinearExprTest, TestApi) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({2, 8}).WithName("x");
const IntVar y = cp_model.NewIntVar({1, 5}).WithName("y");
const BoolVar b = cp_model.NewBoolVar().WithName("b");
DoubleLinearExpr e1(x);
EXPECT_THAT(e1.variables(), ElementsAre(x.index()));
EXPECT_THAT(e1.coefficients(), ElementsAre(1));
EXPECT_EQ(e1.constant(), 0);
e1 += b;
EXPECT_THAT(e1.variables(), ElementsAre(x.index(), b.index()));
EXPECT_THAT(e1.coefficients(), ElementsAre(1, 1));
e1.AddTerm(y, 3);
EXPECT_THAT(e1.variables(), ElementsAre(x.index(), b.index(), y.index()));
EXPECT_THAT(e1.coefficients(), ElementsAre(1, 1, 3));
e1 += 10;
EXPECT_EQ(10, e1.constant());
DoubleLinearExpr e2(b);
EXPECT_THAT(e2.variables(), ElementsAre(b.index()));
EXPECT_THAT(e2.coefficients(), ElementsAre(1));
EXPECT_EQ(0, e2.constant());
DoubleLinearExpr e3(-5);
ASSERT_EQ(0, e3.variables().size());
ASSERT_EQ(0, e3.coefficients().size());
EXPECT_EQ(-5, e3.constant());
e3 += b.Not();
EXPECT_THAT(e3.variables(), ElementsAre(b.index()));
EXPECT_THAT(e3.coefficients(), ElementsAre(-1));
EXPECT_EQ(-4, e3.constant());
DoubleLinearExpr e4(b.Not());
EXPECT_THAT(e4.variables(), ElementsAre(b.index()));
EXPECT_THAT(e4.coefficients(), ElementsAre(-1));
EXPECT_EQ(1, e4.constant());
DoubleLinearExpr e5 = DoubleLinearExpr(x) + 22;
EXPECT_THAT(e5.variables(), ElementsAre(x.index()));
EXPECT_THAT(e5.coefficients(), ElementsAre(1));
EXPECT_EQ(22, e5.constant());
DoubleLinearExpr e6 = DoubleLinearExpr::Sum({b, b}) + 23;
EXPECT_THAT(e6.variables(), ElementsAre(b.index(), b.index()));
EXPECT_THAT(e6.coefficients(), ElementsAre(1, 1));
EXPECT_EQ(23, e6.constant());
DoubleLinearExpr e7 = -5 * DoubleLinearExpr(x) + 24;
EXPECT_THAT(e7.variables(), ElementsAre(x.index()));
EXPECT_THAT(e7.coefficients(), ElementsAre(-5));
EXPECT_EQ(24, e7.constant());
DoubleLinearExpr e8 = DoubleLinearExpr::WeightedSum({b}, {17}) + 26;
EXPECT_THAT(e8.variables(), ElementsAre(b.index()));
EXPECT_THAT(e8.coefficients(), ElementsAre(17));
EXPECT_EQ(26, e8.constant());
DoubleLinearExpr e10 = DoubleLinearExpr::Sum({b});
EXPECT_THAT(e10.variables(), ElementsAre(b.index()));
EXPECT_THAT(e10.coefficients(), ElementsAre(1));
EXPECT_EQ(0, e10.constant());
DoubleLinearExpr e11 = -5 * DoubleLinearExpr(x);
EXPECT_THAT(e11.variables(), ElementsAre(x.index()));
EXPECT_THAT(e11.coefficients(), ElementsAre(-5));
EXPECT_EQ(0, e11.constant());
DoubleLinearExpr e12 = DoubleLinearExpr::WeightedSum({b}, {17});
EXPECT_THAT(e12.variables(), ElementsAre(b.index()));
EXPECT_THAT(e12.coefficients(), ElementsAre(17));
EXPECT_EQ(0, e12.constant());
DoubleLinearExpr e13 = DoubleLinearExpr::Sum({b});
EXPECT_THAT(e13.variables(), ElementsAre(b.index()));
EXPECT_THAT(e13.coefficients(), ElementsAre(1));
EXPECT_EQ(0, e13.constant());
DoubleLinearExpr e14 = DoubleLinearExpr::Sum({b, b}) + 23;
EXPECT_THAT(e14.variables(), ElementsAre(b.index(), b.index()));
EXPECT_THAT(e14.coefficients(), ElementsAre(1, 1));
EXPECT_EQ(23, e14.constant());
DoubleLinearExpr e15 = DoubleLinearExpr::WeightedSum({b}, {17});
EXPECT_THAT(e15.variables(), ElementsAre(b.index()));
EXPECT_THAT(e15.coefficients(), ElementsAre(17));
EXPECT_EQ(0, e15.constant());
DoubleLinearExpr e16 = DoubleLinearExpr::WeightedSum({b}, {17}) + 26;
EXPECT_THAT(e16.variables(), ElementsAre(b.index()));
EXPECT_THAT(e16.coefficients(), ElementsAre(17));
EXPECT_EQ(26, e16.constant());
std::vector<BoolVar> bools = {b};
DoubleLinearExpr e17 = DoubleLinearExpr::Sum(bools) + 23 - 10;
EXPECT_THAT(e17.variables(), ElementsAre(b.index()));
EXPECT_THAT(e17.coefficients(), ElementsAre(1));
EXPECT_EQ(13, e17.constant());
DoubleLinearExpr e18 = DoubleLinearExpr(x) - 5;
EXPECT_THAT(e18.variables(), ElementsAre(x.index()));
EXPECT_THAT(e18.coefficients(), ElementsAre(1));
EXPECT_EQ(-5, e18.constant());
DoubleLinearExpr e19 = 4 * DoubleLinearExpr(b.Not());
EXPECT_THAT(e19.variables(), ElementsAre(b.index()));
EXPECT_THAT(e19.coefficients(), ElementsAre(-4));
EXPECT_EQ(4, e19.constant());
DoubleLinearExpr e20 = DoubleLinearExpr(x) - 7;
EXPECT_THAT(e20.variables(), ElementsAre(x.index()));
EXPECT_THAT(e20.coefficients(), ElementsAre(1));
EXPECT_EQ(e20.constant(), -7);
}
TEST(ConstraintTest, TestAPI) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
const BoolVar b3 = cp_model.NewBoolVar().WithName("b3");
const BoolVar b4 = cp_model.NewBoolVar().WithName("b4");
Constraint ct = cp_model.AddBoolOr({b1, b2.Not(), b3}).OnlyEnforceIf(Not(b4));
ct.WithName("bool_or");
ASSERT_EQ(ct.Name(), "bool_or");
}
TEST(CpModelTest, TestBoolOr) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
const BoolVar b3 = cp_model.NewBoolVar().WithName("b3");
const BoolVar b4 = cp_model.NewBoolVar().WithName("b4");
cp_model.AddBoolOr({b1, b2.Not(), b3}).OnlyEnforceIf(Not(b4));
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(3, cp_model.Proto().constraints(0).bool_or().literals_size());
ASSERT_EQ(1, cp_model.Proto().constraints(0).enforcement_literal_size());
}
TEST(CpModelTest, TestAtMostOne) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
const BoolVar b3 = cp_model.NewBoolVar().WithName("b3");
cp_model.AddAtMostOne({b1, b2.Not(), b3});
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(3, cp_model.Proto().constraints(0).at_most_one().literals_size());
}
TEST(CpModelTest, TestExactlyOne) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
const BoolVar b3 = cp_model.NewBoolVar().WithName("b3");
cp_model.AddExactlyOne({b1, b2.Not(), b3});
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(3, cp_model.Proto().constraints(0).exactly_one().literals_size());
}
TEST(CpModelTest, TestBoolAnd) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
const BoolVar b3 = cp_model.NewBoolVar().WithName("b3");
const BoolVar b4 = cp_model.NewBoolVar().WithName("b4");
cp_model.AddBoolAnd({b1, b2.Not(), b3, b4});
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(4, cp_model.Proto().constraints(0).bool_and().literals_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).enforcement_literal_size());
}
TEST(CpModelTest, TestBoolXor) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
const BoolVar b3 = cp_model.NewBoolVar().WithName("b3");
const BoolVar b4 = cp_model.NewBoolVar().WithName("b4");
cp_model.AddBoolXor({b1, b2.Not(), b3, b4});
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(4, cp_model.Proto().constraints(0).bool_xor().literals_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).enforcement_literal_size());
}
TEST(CpModelTest, TestLinearizedBoolAndEqual) {
CpModelBuilder cp_model;
const BoolVar t = cp_model.NewBoolVar();
const BoolVar a = cp_model.NewBoolVar();
const BoolVar b = cp_model.NewBoolVar();
cp_model.AddBoolAnd({a, b}).OnlyEnforceIf(t);
cp_model.AddEquality(t, 1).OnlyEnforceIf({a, b});
Model model;
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& /*r*/) { num_solutions++; }));
const CpSolverResponse response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(num_solutions, 4);
}
TEST(CpModelTest, TestBoolXorOneFalseVar) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.FalseVar();
cp_model.AddBoolXor({b1});
const CpSolverResponse response = Solve(cp_model.Build());
ASSERT_EQ(response.status(), CpSolverStatus::INFEASIBLE);
}
TEST(CpModelTest, TestBoolXorTwoTrueVars) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar();
const BoolVar b2 = cp_model.NewBoolVar();
cp_model.AddBoolXor({b1, b2});
cp_model.AddEquality(b1, true);
cp_model.AddEquality(b2, true);
const CpSolverResponse response = Solve(cp_model.Build());
ASSERT_EQ(response.status(), CpSolverStatus::INFEASIBLE);
}
TEST(CpModelTest, TestImplication) {
CpModelBuilder cp_model;
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
cp_model.AddImplication(b1, b2);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(2, cp_model.Proto().constraints(0).bool_or().literals_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).enforcement_literal_size());
EXPECT_EQ(-1, cp_model.Proto().constraints(0).bool_or().literals(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).bool_or().literals(1));
}
TEST(CpModelTest, TestEquality) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
cp_model.AddEquality(x, 10);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(10, cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(10, cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestBooleanEquality) {
CpModelBuilder cp_model;
const BoolVar x = cp_model.NewBoolVar().WithName("x");
cp_model.AddEquality(x, 1);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestNotBooleanEquality) {
CpModelBuilder cp_model;
const BoolVar x = cp_model.NewBoolVar().WithName("x");
cp_model.AddEquality(Not(x), 0);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(-1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(-1, cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(-1, cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestGreaterOrEqual) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
cp_model.AddGreaterOrEqual(x, 10);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(10, cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(std::numeric_limits<int64_t>::max(),
cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestGreater) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
cp_model.AddGreaterThan(x, 10);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(11, cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(std::numeric_limits<int64_t>::max(),
cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestLessOrEqual) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
cp_model.AddLessOrEqual(x, 10);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(std::numeric_limits<int64_t>::min(),
cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(10, cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestLess) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
cp_model.AddLessThan(x, 10);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(std::numeric_limits<int64_t>::min(),
cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(9, cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestLinearConstraint) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
cp_model.AddLinearConstraint(LinearExpr::Sum({x, y}), {1, 9});
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(2, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars(1));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(1));
ASSERT_EQ(2, cp_model.Proto().constraints(0).linear().domain_size());
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(9, cp_model.Proto().constraints(0).linear().domain(1));
}
TEST(CpModelTest, TestNotEqual) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
cp_model.AddNotEqual(x, y);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(2, cp_model.Proto().constraints(0).linear().vars_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).linear().vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().vars(1));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().coeffs(0));
EXPECT_EQ(-1, cp_model.Proto().constraints(0).linear().coeffs(1));
EXPECT_EQ(std::numeric_limits<int64_t>::min(),
cp_model.Proto().constraints(0).linear().domain(0));
EXPECT_EQ(-1, cp_model.Proto().constraints(0).linear().domain(1));
EXPECT_EQ(1, cp_model.Proto().constraints(0).linear().domain(2));
EXPECT_EQ(std::numeric_limits<int64_t>::max(),
cp_model.Proto().constraints(0).linear().domain(3));
}
TEST(CpModelTest, TestAllDifferent) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
const IntVar z = cp_model.NewIntVar({0, 20}).WithName("z");
cp_model.AddAllDifferent({x, z, y});
ASSERT_EQ(1, cp_model.Proto().constraints_size());
EXPECT_EQ(3, cp_model.Proto().constraints(0).all_diff().exprs_size());
EXPECT_EQ(0, cp_model.Proto().constraints(0).all_diff().exprs(0).vars(0));
EXPECT_EQ(2, cp_model.Proto().constraints(0).all_diff().exprs(1).vars(0));
EXPECT_EQ(1, cp_model.Proto().constraints(0).all_diff().exprs(2).vars(0));
}
TEST(CpModelTest, TestVariableElement) {
CpModelBuilder cp_model;
const IntVar index = cp_model.NewIntVar({0, 2}).WithName("index");
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
const IntVar z = cp_model.NewIntVar({0, 20}).WithName("z");
const IntVar target = cp_model.NewIntVar({5, 15}).WithName("target");
cp_model.AddVariableElement(index, {x, z, y}, target);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
const ElementConstraintProto& element =
cp_model.Proto().constraints(0).element();
EXPECT_EQ(3, element.exprs_size());
EXPECT_EQ(0, element.linear_index().vars(0));
EXPECT_EQ(1, element.exprs(0).vars(0));
EXPECT_EQ(3, element.exprs(1).vars(0));
EXPECT_EQ(2, element.exprs(2).vars(0));
EXPECT_EQ(4, element.linear_target().vars(0));
}
TEST(CpModelTest, TestExprElement) {
CpModelBuilder cp_model;
const IntVar index = cp_model.NewIntVar({0, 2}).WithName("index");
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
const IntVar z = cp_model.NewIntVar({0, 20}).WithName("z");
const IntVar target = cp_model.NewIntVar({5, 15}).WithName("target");
cp_model.AddElement(2 * index - 1, {-x, 2 * z, y + 2, 11}, 5 - target);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
const ElementConstraintProto& element =
cp_model.Proto().constraints(0).element();
EXPECT_EQ(4, element.exprs_size());
EXPECT_EQ(0, element.linear_index().vars(0));
EXPECT_EQ(2, element.linear_index().coeffs(0));
EXPECT_EQ(-1, element.linear_index().offset());
EXPECT_EQ(1, element.exprs(0).vars(0));
EXPECT_EQ(-1, element.exprs(0).coeffs(0));
EXPECT_EQ(0, element.exprs(0).offset());
EXPECT_EQ(3, element.exprs(1).vars(0));
EXPECT_EQ(2, element.exprs(1).coeffs(0));
EXPECT_EQ(0, element.exprs(1).offset());
EXPECT_EQ(2, element.exprs(2).vars(0));
EXPECT_EQ(1, element.exprs(2).coeffs(0));
EXPECT_EQ(2, element.exprs(2).offset());
EXPECT_EQ(11, element.exprs(3).offset());
EXPECT_EQ(4, element.linear_target().vars(0));
EXPECT_EQ(-1, element.linear_target().coeffs(0));
EXPECT_EQ(5, element.linear_target().offset());
}
TEST(CpModelTest, TestExprElementWithOnlyVarAndConstants) {
CpModelBuilder cp_model;
const IntVar index = cp_model.NewIntVar({0, 2}).WithName("index");
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
const IntVar z = cp_model.NewIntVar({0, 20}).WithName("z");
const IntVar target = cp_model.NewIntVar({5, 15}).WithName("target");
cp_model.AddElement(2 * index - 1, {x, z, y, 11}, 5 - target);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
const ElementConstraintProto& element =
cp_model.Proto().constraints(0).element();
EXPECT_EQ(4, element.exprs_size());
EXPECT_EQ(0, element.linear_index().vars(0));
EXPECT_EQ(2, element.linear_index().coeffs(0));
EXPECT_EQ(-1, element.linear_index().offset());
EXPECT_EQ(1, element.exprs(0).vars(0));
EXPECT_EQ(1, element.exprs(0).coeffs(0));
EXPECT_EQ(0, element.exprs(0).offset());
EXPECT_EQ(3, element.exprs(1).vars(0));
EXPECT_EQ(1, element.exprs(1).coeffs(0));
EXPECT_EQ(0, element.exprs(1).offset());
EXPECT_EQ(2, element.exprs(2).vars(0));
EXPECT_EQ(1, element.exprs(2).coeffs(0));
EXPECT_EQ(0, element.exprs(2).offset());
EXPECT_EQ(11, element.exprs(3).offset());
EXPECT_EQ(4, element.linear_target().vars(0));
EXPECT_EQ(-1, element.linear_target().coeffs(0));
EXPECT_EQ(5, element.linear_target().offset());
}
TEST(CpModelTest, TestElement) {
CpModelBuilder cp_model;
const IntVar index = cp_model.NewIntVar({0, 2}).WithName("index");
const IntVar target = cp_model.NewIntVar({5, 15}).WithName("target");
cp_model.AddElement(index, {1, 12, 5}, target);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
const ElementConstraintProto& element =
cp_model.Proto().constraints(0).element();
EXPECT_EQ(3, element.exprs_size());
EXPECT_EQ(1, element.exprs(0).offset());
EXPECT_EQ(12, element.exprs(1).offset());
EXPECT_EQ(5, element.exprs(2).offset());
EXPECT_EQ(0, element.linear_index().vars(0));
EXPECT_EQ(1, element.linear_target().vars(0));
}
TEST(CpModelTest, TestElementWithBooleanVar) {
CpModelBuilder cp_model;
const IntVar index = cp_model.NewIntVar({0, 2}).WithName("index");
const BoolVar target = cp_model.NewBoolVar().WithName("target");
cp_model.AddElement(index, {1, 0, 1}, IntVar(Not(target)));
const CpModelProto expected_model = ParseTestProto(R"pb(
variables {
name: "index"
domain: [ 0, 2 ]
}
variables {
name: "target"
domain: [ 0, 1 ]
}
variables {
name: "Not(target)"
domain: [ 0, 1 ]
}
constraints {
linear {
vars: [ 2, 1 ]
coeffs: [ 1, 1 ]
domain: [ 1, 1 ]
}
}
constraints {
element {
linear_index: { vars: 0 coeffs: 1 }
linear_target { vars: 2 coeffs: 1 }
exprs { offset: 1 }
exprs {}
exprs { offset: 1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestElementValuesFromInt64Vector) {
CpModelBuilder cp_model;
const IntVar index = cp_model.NewIntVar({0, 2}).WithName("index");
const IntVar target = cp_model.NewIntVar({5, 15}).WithName("target");
const std::vector<int64_t> values = {1, 12, 5};
cp_model.AddElement(index, values, target);
ASSERT_EQ(1, cp_model.Proto().constraints_size());
const ElementConstraintProto& element =
cp_model.Proto().constraints(0).element();
EXPECT_EQ(3, element.exprs_size());
EXPECT_EQ(1, element.exprs(0).offset());
EXPECT_EQ(12, element.exprs(1).offset());
EXPECT_EQ(5, element.exprs(2).offset());
EXPECT_EQ(0, element.linear_index().vars(0));
EXPECT_EQ(1, element.linear_target().vars(0));
}
TEST(CpModelTest, TestCircuit) {
CpModelBuilder cp_model;
std::vector<BoolVar> vars;
for (int i = 0; i < 3; ++i) {
vars.push_back(cp_model.NewBoolVar());
}
CircuitConstraint circuit = cp_model.AddCircuitConstraint();
circuit.AddArc(0, 0, vars[0]);
circuit.AddArc(0, 1, vars[1]);
circuit.AddArc(1, 0, vars[2]);
const CpSolverResponse response = Solve(cp_model.Build());
ASSERT_EQ(response.status(), CpSolverStatus::OPTIMAL);
EXPECT_FALSE(SolutionBooleanValue(response, vars[0]));
EXPECT_TRUE(SolutionBooleanValue(response, vars[1]));
EXPECT_TRUE(SolutionBooleanValue(response, vars[2]));
}
TEST(CpModelTest, TestAllowedAssignment) {
CpModelBuilder cp_model;
std::vector<IntVar> vars;
for (int i = 0; i < 3; ++i) {
vars.push_back(cp_model.NewIntVar({0, 3}));
}
TableConstraint ct = cp_model.AddAllowedAssignments(vars);
ct.AddTuple({1, 1, 2});
ct.AddTuple({0, 1, 2});
ct.AddTuple({4, 1, 2}); // Tuple is invalid.
ct.AddTuple({2, 1, 0});
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& /*r*/) { num_solutions++; }));
const CpSolverResponse response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(num_solutions, 3);
}
TEST(CpModelTest, TestForbiddenAssignments) {
CpModelBuilder cp_model;
std::vector<IntVar> vars;
for (int i = 0; i < 3; ++i) {
vars.push_back(cp_model.NewIntVar({0, 3}));
}
TableConstraint ct = cp_model.AddForbiddenAssignments(vars);
ct.AddTuple({1, 1, 2});
ct.AddTuple({0, 1, 2});
ct.AddTuple({4, 1, 2}); // Tuple is invalid.
ct.AddTuple({2, 1, 0});
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& r) { num_solutions++; }));
const CpSolverResponse response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(num_solutions, 4 * 4 * 4 - 3);
}
TEST(CpModelTest, TestInverseConstraint) {
const int kNumVars = 4;
CpModelBuilder cp_model;
std::vector<IntVar> vars;
std::vector<IntVar> i_vars;
for (int i = 0; i < kNumVars; ++i) {
vars.push_back(cp_model.NewIntVar({0, kNumVars - 1}));
i_vars.push_back(cp_model.NewIntVar({0, kNumVars - 1}));
}
cp_model.AddInverseConstraint(vars, i_vars);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 3 ] }
variables { domain: [ 0, 3 ] }
variables { domain: [ 0, 3 ] }
variables { domain: [ 0, 3 ] }
variables { domain: [ 0, 3 ] }
variables { domain: [ 0, 3 ] }
variables { domain: [ 0, 3 ] }
variables { domain: [ 0, 3 ] }
constraints {
inverse {
f_direct: [ 0, 2, 4, 6 ],
f_inverse: [ 1, 3, 5, 7 ]
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestReservoirConstraint) {
CpModelBuilder cp_model;
ReservoirConstraint reservoir = cp_model.AddReservoirConstraint(1, 5);
reservoir.AddEvent(cp_model.NewIntVar({0, 10}), 1);
reservoir.AddEvent(cp_model.NewIntVar({0, 10}), 2);
const BoolVar is_active = cp_model.NewBoolVar();
reservoir.AddOptionalEvent(cp_model.NewIntVar({0, 10}), -3, is_active);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 1, 1 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 10 ] }
constraints {
reservoir {
time_exprs: { vars: 0 coeffs: 1 }
time_exprs: { vars: 2 coeffs: 1 }
time_exprs: { vars: 4 coeffs: 1 }
level_changes: { offset: 1 }
level_changes: { offset: 2 }
level_changes: { offset: -3 }
active_literals: [ 1, 1, 3 ],
min_level: 1,
max_level: 5
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntMax) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
cp_model.AddMaxEquality(x, {y, z});
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
constraints {
lin_max {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntMin) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
cp_model.AddMinEquality(x, {y, z});
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
constraints {
lin_max {
target { vars: 0 coeffs: -1 }
exprs { vars: 1 coeffs: -1 }
exprs { vars: 2 coeffs: -1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntDiv) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
cp_model.AddDivisionEquality(x, y, z);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
constraints {
int_div {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntAbs) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({-20, 20});
cp_model.AddAbsEquality(x, y);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ -20, 20 ] }
constraints {
lin_max {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 1 coeffs: -1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntModulo) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
cp_model.AddModuloEquality(x, y, z);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
constraints {
int_mod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntProd) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
cp_model.AddMultiplicationEquality(x, {y, z});
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
constraints {
int_prod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntProdLeftRight) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
cp_model.AddMultiplicationEquality(x, y, z);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
constraints {
int_prod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestIntProdVar) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
cp_model.AddMultiplicationEquality(x, {y, z});
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
variables { domain: [ 0, 20 ] }
constraints {
int_prod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(IntervalVarTest, NullAPI) {
IntervalVar var;
EXPECT_EQ(var.Name(), "null");
EXPECT_EQ(var.DebugString(), "null");
if (!DEBUG_MODE) {
// We don't crash, but we only return expression at zero.
EXPECT_TRUE(var.StartExpr().IsConstant());
EXPECT_TRUE(var.EndExpr().IsConstant());
EXPECT_TRUE(var.SizeExpr().IsConstant());
// And the presence is the undefined BoolVar().
EXPECT_EQ(var.PresenceBoolVar(), BoolVar());
}
}
TEST(CpModelTest, TestIntervalVar) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
const IntVar z = cp_model.NewIntVar({0, 20}).WithName("z");
const IntervalVar interval =
cp_model.NewIntervalVar(x, y, z).WithName("interval");
EXPECT_EQ(interval.StartExpr().variables()[0], x.index());
EXPECT_EQ(interval.SizeExpr().variables()[0], y.index());
EXPECT_EQ(interval.EndExpr().variables()[0], z.index());
EXPECT_EQ(interval.PresenceBoolVar(), cp_model.TrueVar());
EXPECT_EQ("interval", interval.Name());
EXPECT_EQ(interval.DebugString(),
"interval(x(0, 20), y(0, 20), z(0, 20), true)");
const IntVar t = cp_model.NewIntVar({0, 20}).WithName("t");
const IntervalVar other = cp_model.NewIntervalVar(x, 5, t);
EXPECT_EQ(other.DebugString(), "IntervalVar1(x(0, 20), 5, t(0, 20), true)");
EXPECT_NE(interval, other);
EXPECT_EQ(interval, interval);
}
TEST(CpModelTest, TestBooleanIntervalVar) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const BoolVar y = cp_model.NewBoolVar().WithName("y");
const IntVar z = cp_model.NewIntVar({0, 20}).WithName("z");
const IntervalVar interval =
cp_model.NewIntervalVar(x, Not(y), z).WithName("interval");
EXPECT_THAT(interval.StartExpr().variables(), ElementsAre(x.index()));
EXPECT_EQ(interval.SizeExpr().variables()[0], y.index());
EXPECT_EQ(interval.SizeExpr().coefficients()[0], -1);
EXPECT_EQ(interval.SizeExpr().constant(), 1);
EXPECT_THAT(interval.EndExpr().variables(), ElementsAre(z.index()));
EXPECT_EQ(interval.PresenceBoolVar(), cp_model.TrueVar());
EXPECT_EQ(interval.DebugString(),
"interval(x(0, 20), -y(0, 1) + 1, z(0, 20), true)");
}
TEST(CpModelTest, TestOptionalIntervalVar) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
const IntVar z = cp_model.NewIntVar({0, 20}).WithName("z");
const BoolVar b = cp_model.NewBoolVar().WithName("b");
const IntervalVar interval =
cp_model.NewOptionalIntervalVar(x, y, z, b).WithName("interval");
EXPECT_THAT(interval.StartExpr().variables(), ElementsAre(x.index()));
EXPECT_THAT(interval.SizeExpr().variables(), ElementsAre(y.index()));
EXPECT_THAT(interval.EndExpr().variables(), ElementsAre(z.index()));
EXPECT_EQ(interval.PresenceBoolVar(), b);
EXPECT_EQ("interval", interval.Name());
}
TEST(CpModelTest, TestNoOverlap) {
CpModelBuilder cp_model;
IntVar x_start = cp_model.NewIntVar({0, 20});
IntVar x_end = cp_model.NewIntVar({0, 20});
const IntervalVar x = cp_model.NewIntervalVar(x_start, 5, x_end);
IntVar y_start = cp_model.NewIntVar({0, 20});
IntVar y_end = cp_model.NewIntVar({0, 20});
const IntervalVar y = cp_model.NewIntervalVar(y_start, 5, y_end);
IntVar z_start = cp_model.NewIntVar({0, 20});
IntVar z_end = cp_model.NewIntVar({0, 20});
const IntervalVar z = cp_model.NewIntervalVar(z_start, 5, z_end);
cp_model.AddNoOverlap({x, y, z});
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
constraints {
interval {
start { vars: 0 coeffs: 1 }
end { vars: 1 coeffs: 1 }
size { offset: 5 }
}
}
constraints {
interval {
start { vars: 2 coeffs: 1 }
end { vars: 3 coeffs: 1 }
size { offset: 5 }
}
}
constraints {
interval {
start { vars: 4 coeffs: 1 }
end { vars: 5 coeffs: 1 }
size { offset: 5 }
}
}
constraints { no_overlap { intervals: 0 intervals: 1 intervals: 2 } })pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestNoOverlap2D) {
CpModelBuilder cp_model;
IntVar x_start = cp_model.NewIntVar({0, 20});
IntVar x_end = cp_model.NewIntVar({0, 20});
const IntervalVar x =
cp_model.NewIntervalVar(x_start, cp_model.NewConstant(5), x_end);
IntVar y_start = cp_model.NewIntVar({0, 20});
IntVar y_end = cp_model.NewIntVar({0, 20});
const IntervalVar y =
cp_model.NewIntervalVar(y_start, cp_model.NewConstant(5), y_end);
IntVar z_start = cp_model.NewIntVar({0, 20});
IntVar z_end = cp_model.NewIntVar({0, 20});
const IntervalVar z =
cp_model.NewIntervalVar(z_start, cp_model.NewConstant(5), z_end);
IntVar t_start = cp_model.NewIntVar({0, 20});
IntVar t_end = cp_model.NewIntVar({0, 20});
const IntervalVar t =
cp_model.NewIntervalVar(t_start, cp_model.NewConstant(5), t_end);
NoOverlap2DConstraint ct = cp_model.AddNoOverlap2D();
ct.AddRectangle(x, y);
ct.AddRectangle(z, t);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 5 domain: 5 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
constraints {
interval {
start { vars: 0 coeffs: 1 }
end { vars: 1 coeffs: 1 }
size { vars: 2 coeffs: 1 }
}
}
constraints {
interval {
start { vars: 3 coeffs: 1 }
end { vars: 4 coeffs: 1 }
size { vars: 2 coeffs: 1 }
}
}
constraints {
interval {
start { vars: 5 coeffs: 1 }
end { vars: 6 coeffs: 1 }
size { vars: 2 coeffs: 1 }
}
}
constraints {
interval {
start { vars: 7 coeffs: 1 }
end { vars: 8 coeffs: 1 }
size { vars: 2 coeffs: 1 }
}
}
constraints {
no_overlap_2d {
x_intervals: 0
x_intervals: 2
y_intervals: 1
y_intervals: 3
}
})pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, TestCumulative) {
CpModelBuilder cp_model;
IntVar x_start = cp_model.NewIntVar({0, 20});
IntVar x_end = cp_model.NewIntVar({0, 20});
const IntervalVar x = cp_model.NewIntervalVar(x_start, 5, x_end);
IntVar y_start = cp_model.NewIntVar({0, 20});
IntVar y_end = cp_model.NewIntVar({0, 20});
const IntervalVar y = cp_model.NewIntervalVar(y_start, 5, y_end);
const IntVar a = cp_model.NewIntVar({5, 10});
const IntVar b = cp_model.NewIntVar({5, 10});
CumulativeConstraint cumul = cp_model.AddCumulative(a);
cumul.AddDemand(x, b);
cumul.AddDemand(y, 8);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 0 domain: 20 }
variables { domain: 5 domain: 10 }
variables { domain: 5 domain: 10 }
constraints {
interval {
start { vars: 0 coeffs: 1 }
end { vars: 1 coeffs: 1 }
size { offset: 5 }
}
}
constraints {
interval {
start { vars: 2 coeffs: 1 }
end { vars: 3 coeffs: 1 }
size { offset: 5 }
}
}
constraints {
cumulative {
capacity { vars: 4 coeffs: 1 }
intervals: 0
intervals: 1
demands { vars: 5 coeffs: 1 }
demands { offset: 8 }
}
})pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, RabbitsAndPheasants) {
CpModelBuilder cp_model;
Domain all_animals(0, 20);
const IntVar rabbits = cp_model.NewIntVar(all_animals).WithName("rabbits");
const IntVar pheasants =
cp_model.NewIntVar(all_animals).WithName("pheasants");
cp_model.AddEquality(LinearExpr::Sum({rabbits, pheasants}), 20);
cp_model.AddEquality(LinearExpr::WeightedSum({rabbits, pheasants}, {4, 2}),
56);
const CpSolverResponse response = Solve(cp_model.Build());
ASSERT_EQ(response.status(), CpSolverStatus::OPTIMAL);
EXPECT_EQ(SolutionIntegerValue(response, rabbits), 8);
EXPECT_EQ(SolutionIntegerValue(response, pheasants), 12);
}
TEST(CpModelTest, BoolAnd) {
CpModelBuilder cp_model;
BoolVar a = cp_model.NewBoolVar();
BoolVar b = cp_model.NewBoolVar();
cp_model.AddBoolAnd({a, b});
const CpSolverResponse response = Solve(cp_model.Build());
ASSERT_EQ(response.status(), CpSolverStatus::OPTIMAL);
}
TEST(CpModelTest, Min) {
CpModelBuilder cp_model;
IntVar x0 = cp_model.NewIntVar(Domain(1, 2));
IntVar x1 = cp_model.NewIntVar(Domain(0, 1));
IntVar x2 = cp_model.NewIntVar(Domain(-2, -1));
IntVar target = cp_model.NewIntVar(Domain(-3, 0));
LinearExpr expr1;
expr1 += x0 * 2;
expr1 += x1 * 3;
expr1 += -5;
LinearExpr expr2;
expr2 += x1 * 2;
expr2 += x2 * -5;
expr2 += 6;
LinearExpr expr3;
expr3 += x0 * 2;
expr3 += x2 * 3;
expr3 += 0;
std::vector<LinearExpr> exprs = {expr1, expr2, expr3};
cp_model.AddMinEquality(target, exprs);
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
parameters.set_linearization_level(2);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& r) { num_solutions++; }));
const CpSolverResponse& response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
int expected_num_solutions = 0;
for (int x0 = 1; x0 <= 2; ++x0) {
for (int x1 = 0; x1 <= 1; ++x1) {
for (int x2 = -2; x2 <= -1; ++x2) {
for (int target = -3; target <= 0; ++target) {
if (target == std::min({2 * x0 + 3 * x1 - 5, 2 * x1 - 5 * x2 + 6,
2 * x0 + 3 * x2})) {
expected_num_solutions++;
}
}
}
}
}
EXPECT_EQ(expected_num_solutions, num_solutions);
}
TEST(CpModelTest, Min2) {
CpModelBuilder cp_model;
IntVar x0 = cp_model.NewIntVar(Domain(0, 6));
IntVar x1 = cp_model.NewIntVar(Domain(0, 6));
IntVar x2 = cp_model.NewIntVar(Domain(0, 6));
IntVar target = cp_model.NewIntVar(Domain(5, 5));
LinearExpr expr1;
expr1 += x0 * 1;
expr1 += x1 * 1;
expr1 += x2 * 1;
expr1 += 0;
LinearExpr expr2;
expr2 += 100;
std::vector<LinearExpr> exprs = {expr1, expr2};
cp_model.AddMinEquality(target, exprs);
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_cp_model_presolve(false);
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& r) { num_solutions++; }));
const CpSolverResponse& response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
int expected_num_solutions = 0;
for (int x0 = 0; x0 <= 6; ++x0) {
for (int x1 = 0; x1 <= 6; ++x1) {
for (int x2 = 0; x2 <= 6; ++x2) {
for (int target = 5; target <= 5; ++target) {
if (target == std::min({x0 + x1 + x2, 100})) {
expected_num_solutions++;
}
}
}
}
}
EXPECT_EQ(expected_num_solutions, num_solutions);
}
TEST(CpModelTest, Max) {
CpModelBuilder cp_model;
IntVar x0 = cp_model.NewIntVar(Domain(1, 2));
IntVar x1 = cp_model.NewIntVar(Domain(0, 1));
IntVar x2 = cp_model.NewIntVar(Domain(-2, -1));
IntVar target = cp_model.NewIntVar(Domain(-3, 0));
LinearExpr expr1;
expr1 += x0 * 2;
expr1 += x1 * 3;
expr1 += -5;
LinearExpr expr2;
expr2 += x1 * 2;
expr2 += x2 * 5;
expr2 += 6;
LinearExpr expr3;
expr3 += x0 * 2;
expr3 += x2 * 3;
expr3 += 0;
std::vector<LinearExpr> exprs = {expr1, expr2, expr3};
cp_model.AddMaxEquality(target, exprs);
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& r) { num_solutions++; }));
const CpSolverResponse& response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
int expected_num_solutions = 0;
for (int x0 = 1; x0 <= 2; ++x0) {
for (int x1 = 0; x1 <= 1; ++x1) {
for (int x2 = -2; x2 <= -1; ++x2) {
for (int target = -3; target <= 0; ++target) {
if (target == std::max({2 * x0 + 3 * x1 - 5, 2 * x1 + 5 * x2 + 6,
2 * x0 + 3 * x2})) {
expected_num_solutions++;
}
}
}
}
}
EXPECT_EQ(expected_num_solutions, num_solutions);
}
TEST(CpModelTest, MinExpression) {
CpModelBuilder cp_model;
IntVar x0 = cp_model.NewIntVar(Domain(1, 2));
IntVar x1 = cp_model.NewIntVar(Domain(0, 1));
IntVar y0 = cp_model.NewIntVar(Domain(1, 2));
IntVar y1 = cp_model.NewIntVar(Domain(-1, 0));
LinearExpr target;
target -= y0 * 2;
target += y1 * 1;
target += 2;
LinearExpr expr1;
expr1 += x0 * 2;
expr1 += x1 * 3;
expr1 -= 5;
LinearExpr expr2;
expr2 += x0 * 2;
expr2 += x1 * 1;
expr2 -= 4;
std::vector<LinearExpr> exprs = {expr1, expr2};
cp_model.AddMinEquality(target, exprs);
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& r) { num_solutions++; }));
const CpSolverResponse& response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
int expected_num_solutions = 0;
for (int x0 = 1; x0 <= 2; ++x0) {
for (int x1 = 0; x1 <= 1; ++x1) {
for (int y0 = 1; y0 <= 2; ++y0) {
for (int y1 = -1; y1 <= 0; ++y1) {
if ((-2 * y0 + y1 + 2) ==
std::min(2 * x0 + 3 * x1 - 5, 2 * x0 + x1 - 4)) {
expected_num_solutions++;
}
}
}
}
}
EXPECT_EQ(expected_num_solutions, num_solutions);
}
TEST(CpModelTest, MaxExpression) {
CpModelBuilder cp_model;
IntVar x0 = cp_model.NewIntVar(Domain(1, 2));
IntVar x1 = cp_model.NewIntVar(Domain(0, 1));
IntVar y0 = cp_model.NewIntVar(Domain(1, 2));
IntVar y1 = cp_model.NewIntVar(Domain(-1, 0));
LinearExpr target;
target -= y0 * 2;
target += y1 * 1;
target += 2;
LinearExpr expr1;
expr1 += x0 * 2;
expr1 += x1 * 3;
expr1 -= 5;
LinearExpr expr2;
expr2 += x0 * 2;
expr2 += x1 * 1;
expr2 -= 4;
std::vector<LinearExpr> exprs = {expr1, expr2};
cp_model.AddMaxEquality(target, exprs);
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver(
[&](const CpSolverResponse& r) { num_solutions++; }));
const CpSolverResponse& response = SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
int expected_num_solutions = 0;
for (int x0 = 1; x0 <= 2; ++x0) {
for (int x1 = 0; x1 <= 1; ++x1) {
for (int y0 = 1; y0 <= 2; ++y0) {
for (int y1 = -1; y1 <= 0; ++y1) {
if ((-2 * y0 + y1 + 2) ==
std::max(2 * x0 + 3 * x1 - 5, 2 * x0 + x1 - 4)) {
expected_num_solutions++;
}
}
}
}
}
EXPECT_EQ(expected_num_solutions, num_solutions);
}
TEST(CpModelTest, MinExpressionInfeasible) {
CpModelBuilder cp_model;
IntVar x0 = cp_model.NewIntVar(Domain(1, 2));
IntVar x1 = cp_model.NewIntVar(Domain(0, 1));
IntVar y0 = cp_model.NewIntVar(Domain(1, 2));
IntVar y1 = cp_model.NewIntVar(Domain(0, 0));
LinearExpr target;
target -= y0 * 2;
target += y1 * 1;
target += 2;
LinearExpr expr1;
expr1 += x0 * 2;
expr1 += x1 * 3;
expr1 -= 5;
LinearExpr expr2;
expr2 += x0 * 2;
expr2 += x1 * 1;
expr2 -= 4;
std::vector<LinearExpr> exprs = {expr1, expr2};
cp_model.AddMinEquality(target, exprs);
const CpSolverResponse& response = Solve(cp_model.Build());
EXPECT_EQ(response.status(), CpSolverStatus::INFEASIBLE);
}
TEST(CpModelTest, MaxExpressionInfeasible) {
CpModelBuilder cp_model;
IntVar x0 = cp_model.NewIntVar(Domain(1, 2));
IntVar x1 = cp_model.NewIntVar(Domain(0, 1));
IntVar y0 = cp_model.NewIntVar(Domain(1, 2));
IntVar y1 = cp_model.NewIntVar(Domain(-1, -1));
LinearExpr target;
target -= y0 * 2;
target += y1 * 1;
target += 2;
LinearExpr expr1;
expr1 += x0 * 2;
expr1 += x1 * 3;
expr1 -= 5;
LinearExpr expr2;
expr2 += x0 * 2;
expr2 += x1 * 1;
expr2 -= 4;
std::vector<LinearExpr> exprs = {expr1, expr2};
cp_model.AddMaxEquality(target, exprs);
const CpSolverResponse& response = Solve(cp_model.Build());
EXPECT_EQ(response.status(), CpSolverStatus::INFEASIBLE);
}
TEST(CpModelTest, Hinting) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const IntVar y = cp_model.NewIntVar({0, 20}).WithName("y");
cp_model.AddLinearConstraint(LinearExpr::Sum({x, y}), {1, 9});
cp_model.AddHint(x, 4);
cp_model.AddHint(y, 3);
const CpSolverResponse response =
SolveWithParameters(cp_model.Build(), "cp_model_presolve:false");
ASSERT_EQ(response.status(), CpSolverStatus::OPTIMAL);
EXPECT_EQ(4, SolutionIntegerValue(response, x));
EXPECT_EQ(3, SolutionIntegerValue(response, y));
}
TEST(CpModelTest, PositiveTable) {
CpModelBuilder cp_model;
std::vector<IntVar> vars;
for (int i = 0; i < 3; ++i) {
vars.push_back(cp_model.NewIntVar(Domain(0, 1)));
}
vars.push_back(cp_model.NewIntVar(Domain(0, 3)));
TableConstraint ct = cp_model.AddAllowedAssignments(vars);
const std::vector<std::vector<int64_t>> tuples = {
{0, 0, 0, 0},
{1, 1, 0, 2},
{0, 0, 1, 3},
{0, 1, 1, 3},
};
for (const std::vector<int64_t>& tuple : tuples) {
ct.AddTuple(tuple);
}
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
parameters.set_cp_model_presolve(false);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) {
LOG(INFO) << gtl::LogContainer(r.solution());
num_solutions++;
}));
SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(num_solutions, tuples.size());
}
TEST(CpModelTest, NegativeTable) {
CpModelBuilder cp_model;
std::vector<IntVar> vars;
for (int i = 0; i < 3; ++i) {
vars.push_back(cp_model.NewIntVar(Domain(0, 1)));
}
vars.push_back(cp_model.NewIntVar(Domain(0, 3)));
TableConstraint ct = cp_model.AddForbiddenAssignments(vars);
const std::vector<std::vector<int64_t>> tuples = {
{0, 0, 0, 0},
{1, 1, 0, 2},
{0, 0, 1, 3},
{0, 1, 1, 3},
};
for (const std::vector<int64_t>& tuple : tuples) {
ct.AddTuple(tuple);
}
Model model;
// Tell the solver to enumerate all solutions.
SatParameters parameters;
parameters.set_enumerate_all_solutions(true);
parameters.set_num_workers(1);
parameters.set_cp_model_presolve(false);
model.Add(NewSatParameters(parameters));
int num_solutions = 0;
model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) {
LOG(INFO) << gtl::LogContainer(r.solution());
num_solutions++;
}));
SolveCpModel(cp_model.Build(), &model);
EXPECT_EQ(num_solutions, 32 - tuples.size());
}
TEST(CpModelTest, WrongPresolve) {
CpModelBuilder cp_model;
const IntVar word_var = cp_model.NewIntVar(Domain::FromValues({0, 1}));
const std::vector<int64_t> weights{2, 1};
const IntVar weight_var = cp_model.NewIntVar(Domain::FromValues({1, 2}));
cp_model.AddElement(word_var, weights, weight_var);
cp_model.Maximize(weight_var);
SatParameters parameters;
parameters.set_cp_model_presolve(false);
parameters.set_log_search_progress(true);
const CpSolverResponse response =
SolveWithParameters(cp_model.Build(), parameters);
EXPECT_EQ(response.status(), CpSolverStatus::OPTIMAL);
}
TEST(CpModelTest, TestSearchStrategy) {
CpModelBuilder cp_model;
const IntVar i1 = cp_model.NewIntVar({0, 2}).WithName("i1");
const IntVar i2 = cp_model.NewIntVar({0, 2}).WithName("i2");
const IntVar i3 = cp_model.NewIntVar({0, 2}).WithName("i3");
const BoolVar b1 = cp_model.NewBoolVar().WithName("b1");
const BoolVar b2 = cp_model.NewBoolVar().WithName("b2");
cp_model.AddDecisionStrategy({i2, i1, i3},
DecisionStrategyProto::CHOOSE_FIRST,
DecisionStrategyProto::SELECT_MIN_VALUE);
cp_model.AddDecisionStrategy({b1, b2.Not()},
DecisionStrategyProto::CHOOSE_FIRST,
DecisionStrategyProto::SELECT_MAX_VALUE);
const CpModelProto expected_model = ParseTestProto(R"pb(
variables { name: "i1" domain: 0 domain: 2 }
variables { name: "i2" domain: 0 domain: 2 }
variables { name: "i3" domain: 0 domain: 2 }
variables { name: "b1" domain: 0 domain: 1 }
variables { name: "b2" domain: 0 domain: 1 }
search_strategy {
exprs { vars: 1 coeffs: 1 }
exprs { vars: 0 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
search_strategy {
domain_reduction_strategy: SELECT_MAX_VALUE
exprs { vars: 3 coeffs: 1 }
exprs { vars: 4 coeffs: -1 offset: 1 }
}
)pb");
EXPECT_THAT(cp_model.Proto(), EqualsProto(expected_model));
}
TEST(CpModelTest, DeepCopy) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20}).WithName("x");
const BoolVar b = cp_model.NewBoolVar();
cp_model.AddEquality(x, 10).OnlyEnforceIf(b);
CpModelBuilder copy = cp_model.Clone();
const IntVar copy_x = copy.GetIntVarFromProtoIndex(x.index());
const BoolVar copy_b = copy.GetBoolVarFromProtoIndex(b.index());
EXPECT_EQ(x.index(), copy_x.index());
EXPECT_EQ(b.index(), copy_b.index());
EXPECT_THAT(cp_model.Proto(), EqualsProto(copy.Proto()));
}
TEST(LinearExpr, NaturalApi) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const BoolVar b = cp_model.NewBoolVar();
EXPECT_EQ(LinearExpr(3 * x + 4).DebugString(), "3 * V0 + 4");
EXPECT_EQ(LinearExpr(-x).DebugString(), "-V0");
EXPECT_EQ(LinearExpr(-(x + y)).DebugString(), "-V0 - V1");
EXPECT_EQ(LinearExpr(-2 * (x + y)).DebugString(), "-2 * V0 - 2 * V1");
EXPECT_EQ(LinearExpr(3 * x + b + 4).DebugString(), "3 * V0 + V2 + 4");
EXPECT_EQ(LinearExpr(b * 2 + 3 * x).DebugString(), "2 * V2 + 3 * V0");
EXPECT_EQ(LinearExpr(b + b + b.Not()).DebugString(), "V2 + V2 - V2 + 1");
}
TEST(LinearExpr, NaturalApiNegation) {
CpModelBuilder cp_model;
const IntVar x = cp_model.NewIntVar({0, 20});
const IntVar y = cp_model.NewIntVar({0, 20});
const IntVar z = cp_model.NewIntVar({0, 20});
{
LinearExpr a = x + y;
LinearExpr b = x + y + z;
EXPECT_EQ((std::move(a) - std::move(b)).DebugString(),
"V0 + V1 - V0 - V1 - V2");
}
{
LinearExpr a = x + y;
LinearExpr b = x + y + z;
EXPECT_EQ((std::move(a) - b).DebugString(), "V0 + V1 - V0 - V1 - V2");
}
{
// Note that we re-order this one to optimize memory.
LinearExpr a = x + y;
LinearExpr b = x + y + z;
EXPECT_EQ((a - std::move(b)).DebugString(), "-V0 - V1 - V2 + V0 + V1");
}
{
LinearExpr a = x + y;
LinearExpr b = x + y + z;
EXPECT_EQ((std::move(b) - std::move(a)).DebugString(),
"V0 + V1 + V2 - V0 - V1");
}
}
TEST(LinearExpr, ComplexityIsOk) {
// We rely on the move semantics to not be in O(n^2).
// Note that this is not a code style to follow!
CpModelBuilder cp_model;
LinearExpr expr;
for (int i = 0; i < 1e6; ++i) {
expr = cp_model.NewBoolVar() + std::move(expr) +
i * cp_model.NewIntVar({0, 20});
}
}
TEST(Hints, HintIsComplete) {
// Build model.
CpModelBuilder model;
IntVar start1 = model.NewIntVar(Domain(0, 10)).WithName("start1");
IntVar length1 = model.NewIntVar(Domain(0, 10)).WithName("length1");
IntVar end1 = model.NewIntVar(Domain(0, 10)).WithName("end1");
IntVar start2 = model.NewIntVar(Domain(0, 10)).WithName("start2");
IntVar length2 = model.NewIntVar(Domain(0, 10)).WithName("length2");
IntVar end2 = model.NewIntVar(Domain(0, 10)).WithName("end2");
model.NewIntervalVar(start1, length1, end1);
model.NewIntervalVar(start2, length2, end2);
// Add hint.
model.AddHint(start1, 0);
model.AddHint(length1, 4);
model.AddHint(end1, 4);
model.AddHint(start2, 4);
model.AddHint(length2, 6);
model.AddHint(end2, 10);
// Solve model.
SatParameters parameters;
parameters.set_log_search_progress(true);
parameters.set_log_to_response(true);
parameters.set_num_workers(1);
Model sat_model;
const CpSolverResponse response =
SolveWithParameters(model.Build(), parameters);
EXPECT_THAT(response.solve_log(),
HasSubstr("The solution hint is complete and is feasible."));
}
TEST(Hints, HintObjectiveValue) {
// Build model.
CpModelBuilder model;
IntVar start1 = model.NewIntVar(Domain(0, 10)).WithName("start1");
IntVar length1 = model.NewIntVar(Domain(0, 10)).WithName("length1");
IntVar end1 = model.NewIntVar(Domain(0, 10)).WithName("end1");
IntVar start2 = model.NewIntVar(Domain(0, 10)).WithName("start2");
IntVar length2 = model.NewIntVar(Domain(0, 10)).WithName("length2");
IntVar end2 = model.NewIntVar(Domain(0, 10)).WithName("end2");
model.NewIntervalVar(start1, length1, end1);
model.NewIntervalVar(start2, length2, end2);
model.Minimize(start1 + length1 + end1 + start2 + length2 + end2);
// Add hint.
model.AddHint(start1, 0);
model.AddHint(length1, 4);
model.AddHint(end1, 4);
model.AddHint(start2, 4);
model.AddHint(length2, 6);
model.AddHint(end2, 10);
// Solve model.
SatParameters parameters;
parameters.set_log_search_progress(true);
parameters.set_log_to_response(true);
parameters.set_num_workers(1);
Model sat_model;
const CpSolverResponse response =
SolveWithParameters(model.Build(), parameters);
EXPECT_THAT(response.solve_log(),
HasSubstr("The solution hint is complete and is feasible. Its "
"objective value is 28."));
}
} // namespace
} // namespace sat
} // namespace operations_research