Files
ortools-clone/ortools/sat/cp_model_checker_test.cc
Mizux Seiha 4f381f6d07 backport from main:
* bump abseil to 20250814
* bump protobuf to v32.0
* cmake: add ccache auto support
* backport flatzinc, math_opt and sat update
2025-09-16 16:25:04 +02:00

759 lines
21 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_checker.h"
#include <cstdint>
#include <limits>
#include <string>
#include "absl/log/check.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"
namespace operations_research {
namespace sat {
namespace {
using ::google::protobuf::contrib::parse_proto::ParseTestProto;
using ::testing::HasSubstr;
// This just checks that the code is at least properly executed.
TEST(SolutionIsFeasibleTest, BasicExample) {
const CpModelProto model = ParseTestProto(R"pb(
variables { name: 'x' domain: 0 domain: 10 }
variables { name: 'y' domain: 0 domain: 10 }
constraints {
linear { vars: 0 coeffs: 1 vars: 1 coeffs: 1 domain: 0 domain: 10 }
}
)pb");
EXPECT_FALSE(SolutionIsFeasible(model, {8, 8}));
EXPECT_FALSE(SolutionIsFeasible(model, {11, -1}));
EXPECT_TRUE(SolutionIsFeasible(model, {5, 5}));
}
TEST(SolutionIsFeasibleTest, LinMax) {
const CpModelProto model = ParseTestProto(R"pb(
variables { name: 'x' domain: 0 domain: 10 }
variables { name: 'y' domain: 0 domain: 30 }
constraints {
lin_max {
target { vars: 1 coeffs: 1 offset: 0 }
exprs { vars: 0 coeffs: 2 offset: 1 }
exprs { vars: 0 coeffs: 3 offset: -5 }
}
}
)pb");
EXPECT_FALSE(SolutionIsFeasible(model, {2, 4}));
EXPECT_FALSE(SolutionIsFeasible(model, {11, -1}));
EXPECT_TRUE(SolutionIsFeasible(model, {2, 5}));
EXPECT_TRUE(SolutionIsFeasible(model, {8, 19}));
}
TEST(SolutionIsFeasibleTest, OrToolsIssue3769) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 1, 2 ] }
variables { domain: [ 0, 1 ] }
constraints {
no_overlap_2d {
x_intervals: [ 1, 2 ]
y_intervals: [ 3, 4 ]
}
}
constraints {
interval {
start { offset: 2 }
end {
vars: [ 1 ]
coeffs: [ 1 ]
offset: 2
}
size {
vars: [ 1 ]
coeffs: [ 1 ]
}
}
}
constraints {
interval {
start { offset: 1 }
end { offset: 3 }
size { offset: 2 }
}
}
constraints {
interval {
start { offset: 1 }
end {
vars: [ 0 ]
coeffs: [ 1 ]
offset: 1
}
size {
vars: [ 0 ]
coeffs: [ 1 ]
}
}
}
constraints {
interval {
start { offset: 2 }
end { offset: 2 }
size {}
}
}
)pb");
EXPECT_TRUE(SolutionIsFeasible(model, {1, 0}));
EXPECT_TRUE(SolutionIsFeasible(model, {1, 1}));
EXPECT_FALSE(SolutionIsFeasible(model, {2, 0}));
}
TEST(SolutionIsFeasibleTest, Reservoir) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 2 ] }
variables { domain: [ 0, 2 ] }
variables { domain: [ 1, 1 ] }
variables { domain: [ 0, 1 ] }
constraints {
reservoir {
time_exprs: { vars: 0 coeffs: 1 }
time_exprs: { vars: 1 coeffs: 1 }
level_changes: { offset: -1 }
level_changes: { offset: 1 }
active_literals: [ 2, 3 ]
min_level: 0
max_level: 2
}
}
)pb");
EXPECT_FALSE(SolutionIsFeasible(model, {0, 0, 1, 0}));
EXPECT_TRUE(SolutionIsFeasible(model, {0, 0, 1, 1}));
EXPECT_TRUE(SolutionIsFeasible(model, {1, 0, 1, 1}));
EXPECT_FALSE(SolutionIsFeasible(model, {0, 1, 1, 1}));
EXPECT_FALSE(SolutionIsFeasible(model, {0, 0, 1, 0}));
}
TEST(SolutionIsFeasibleTest, ReservoirWithNegativeTime) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ -2, 2 ] }
variables { domain: [ -2, 2 ] }
constraints {
reservoir {
time_exprs: { vars: 0 coeffs: 1 }
time_exprs: { vars: 1 coeffs: 1 }
level_changes: { offset: 2 }
level_changes: { offset: -2 }
min_level: 0
max_level: 2
}
}
)pb");
EXPECT_TRUE(SolutionIsFeasible(model, {1, 1}));
EXPECT_TRUE(SolutionIsFeasible(model, {0, 0}));
EXPECT_FALSE(SolutionIsFeasible(model, {1, 0}));
EXPECT_TRUE(SolutionIsFeasible(model, {0, 1}));
EXPECT_TRUE(SolutionIsFeasible(model, {-2, 2}));
}
TEST(SolutionIsFeasibleTest, SelfArcAreOk) {
// The literal -1 is the negation of the first variable.
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
constraints {
circuit {
literals: [ -1, 1, 2, 3, 0 ]
tails: [ 0, 1, 2, 3, 0 ]
heads: [ 0, 2, 3, 1, 2 ]
}
}
)pb");
EXPECT_TRUE(SolutionIsFeasible(model, {0, 1, 1, 1}));
EXPECT_FALSE(SolutionIsFeasible(model, {1, 1, 1, 1}));
}
TEST(SolutionIsFeasibleTest, SparseCircuit) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
constraints {
circuit {
literals: [ 0, 1, 2, 3 ]
tails: [ -10, 10, 9, 1000 ]
heads: [ 10, 9, 1000, -10 ]
}
}
)pb");
EXPECT_TRUE(SolutionIsFeasible(model, {1, 1, 1, 1}));
EXPECT_FALSE(SolutionIsFeasible(model, {1, 0, 1, 1}));
}
TEST(SolutionIsFeasibleTest, BoolXor) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
constraints { bool_xor { literals: [ 0, 1, 2, 3 ] } }
)pb");
EXPECT_TRUE(SolutionIsFeasible(model, {1, 0, 0, 0}));
EXPECT_TRUE(SolutionIsFeasible(model, {1, 1, 1, 0}));
EXPECT_FALSE(SolutionIsFeasible(model, {1, 1, 1, 1}));
EXPECT_FALSE(SolutionIsFeasible(model, {1, 0, 1, 0}));
}
TEST(SolutionIsFeasibleTest, WithEnforcement) {
const CpModelProto model = ParseTestProto(R"pb(
variables { name: 'a' domain: 0 domain: 1 }
variables { name: 'b' domain: 0 domain: 1 }
variables { name: 'y' domain: 0 domain: 10 }
constraints {
enforcement_literal: [ 0, 1 ]
linear { vars: 2 coeffs: 1 domain: 7 domain: 7 }
}
)pb");
EXPECT_TRUE(SolutionIsFeasible(model, {0, 0, 5}));
EXPECT_TRUE(SolutionIsFeasible(model, {0, 1, 5}));
EXPECT_TRUE(SolutionIsFeasible(model, {1, 0, 5}));
EXPECT_FALSE(SolutionIsFeasible(model, {1, 1, 5}));
EXPECT_TRUE(SolutionIsFeasible(model, {1, 1, 7}));
}
TEST(SolutionIsFeasibleTest, ObjectiveDomain) {
const CpModelProto model = ParseTestProto(R"pb(
variables { name: 'x' domain: 0 domain: 10 }
variables { name: 'y' domain: 0 domain: 10 }
objective {
vars: [ 0, 1 ]
coeffs: [ 1, 1 ]
domain: [ 5, 15 ]
}
)pb");
EXPECT_FALSE(SolutionIsFeasible(model, {8, 8}));
EXPECT_TRUE(SolutionIsFeasible(model, {5, 5}));
EXPECT_FALSE(SolutionIsFeasible(model, {0, 0}));
}
TEST(ValidateCpModelTest, BadVariableDomain1) {
const CpModelProto model = ParseTestProto(R"pb(
variables { name: 'a' domain: 0 domain: 1 domain: 3 }
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("odd domain"));
}
TEST(ValidateCpModelTest, VariableUpperBoundTooLarge) {
const CpModelProto model = ParseTestProto(R"pb(
variables {
name: 'a'
domain: [ 0, 9223372036854775807 ]
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("do not fall in"));
}
TEST(ValidateCpModelTest, VariableLowerBoundTooLarge1) {
const CpModelProto model = ParseTestProto(R"pb(
variables {
name: 'a'
domain: [ -9223372036854775807, 0 ]
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("do not fall in"));
}
TEST(ValidateCpModelTest, VariableLowerBoundTooLarge2) {
const CpModelProto model = ParseTestProto(R"pb(
variables {
name: 'a'
domain: [ -9223372036854775808, 0 ]
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("do not fall in"));
}
TEST(ValidateCpModelTest, VariableDomainOverflow) {
CHECK_EQ(std::numeric_limits<int64_t>::max() / 2,
int64_t{4611686018427387903});
const CpModelProto model_ok = ParseTestProto(R"pb(
variables {
name: 'a'
domain: -4611686018427387903
domain: 4611686018427387903
}
)pb");
EXPECT_TRUE(ValidateCpModel(model_ok).empty());
const CpModelProto model_bad0 = ParseTestProto(R"pb(
variables { name: 'a' domain: 0 domain: 4611686018427387904 }
)pb");
EXPECT_THAT(ValidateCpModel(model_bad0), HasSubstr("do not fall in"));
const CpModelProto model_bad1 = ParseTestProto(R"pb(
variables { name: 'a' domain: -4611686018427387904 domain: 0 }
)pb");
EXPECT_THAT(ValidateCpModel(model_bad1), HasSubstr("do not fall in"));
CHECK_EQ(std::numeric_limits<int64_t>::min() + 2,
int64_t{-9223372036854775806});
const CpModelProto model_bad2 = ParseTestProto(R"pb(
variables { name: 'a' domain: -9223372036854775806 domain: 2 }
)pb");
EXPECT_THAT(ValidateCpModel(model_bad2), HasSubstr("do not fall in"));
}
TEST(ValidateCpModelTest, ObjectiveOverflow) {
CHECK_EQ(std::numeric_limits<int64_t>::max() / 4,
int64_t{2305843009213693951});
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ -2305843009213693951, 2305843009213693951 ] }
variables { domain: [ -2305843009213693951, 2305843009213693951 ] }
variables { domain: [ -2305843009213693951, 2305843009213693951 ] }
objective {
vars: [ 0, 1, 2 ]
coeffs: [ 1, 1, 1 ]
}
)pb");
// The min/max sum do not overflow, but their difference do.
EXPECT_THAT(ValidateCpModel(model), HasSubstr("overflow"));
}
TEST(ValidateCpModelTest, ValidSolutionHint) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
solution_hint {
vars: [ 0, 1 ]
values: [ 1, 2 ]
}
)pb");
EXPECT_TRUE(ValidateCpModel(model).empty());
}
TEST(ValidateCpModelTest, SolutionHint1) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
solution_hint {
vars: [ 0, 1, 2 ]
values: [ 1, 2, 3, 4 ]
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("same size"));
}
TEST(ValidateCpModelTest, SolutionHint2) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
solution_hint {
vars: [ 0, 10, 2 ]
values: [ 1, 2, 3 ]
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("Invalid variable"));
}
TEST(ValidateCpModelTest, SolutionHint3) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
solution_hint {
vars: [ 0, 2, 0 ]
values: [ 1, 2, 3 ]
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("duplicate"));
}
TEST(ValidateCpModelTest, Assumptions) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
assumptions: [ 0, 1, 4 ]
)pb");
EXPECT_THAT(ValidateCpModel(model),
"Invalid literal reference 4 in the 'assumptions' field.");
}
TEST(ValidateCpModelTest, NegativeValueInIntervalSizeDomain) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 0 ] }
variables { domain: [ -7, -7, 0, 0 ] }
constraints {
interval {
start { vars: 0 coeffs: 1 }
end { vars: 1 coeffs: 1 }
size { vars: 1 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model),
HasSubstr("The size of a performed interval must be >= 0"));
}
TEST(ValidateCpModelTest, ParallelVectorMustHaveTheSameSize) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: 0 domain: 4503599627370529 }
constraints {
interval {
start { offset: 1 }
size { offset: 2 }
end { offset: 3 }
}
}
constraints {
no_overlap_2d { x_intervals: 0 y_intervals: 0 y_intervals: 0 }
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("must have the same size"));
}
TEST(ValidateCpModelTest, InvalidDomainInLinear) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: -288230376151711744 domain: 262144 }
variables { domain: 0 domain: 5 }
constraints {
linear {
vars: [ 1, 0 ]
coeffs: [ 1, 2 ]
domain: [ 1, 3, 5 ]
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("Invalid domain"));
}
TEST(ValidateCpModelTest, InvalidDomainInLinear2) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: -288230376151711744 domain: 262144 }
variables { domain: 0 domain: 5 }
constraints {
name: "T"
linear {
vars: [ 1, 0 ]
coeffs: [ 1, 2 ]
domain: [ 3, 0 ]
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("Invalid domain"));
}
TEST(ValidateCpModelTest, NegatedReferenceInLinear) {
const CpModelProto model = ParseTestProto(R"pb(
variables { name: "c" domain: 1 domain: 1 }
variables { domain: 0 domain: 1 }
constraints {
int_div {
target {}
exprs {}
exprs { vars: -2 coeffs: 792633495762501632 }
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("Invalid negated variable"));
}
TEST(ValidateCpModelTest, ArityOneInIntProd) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
constraints {
int_prod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
}
}
)pb");
EXPECT_TRUE(ValidateCpModel(model).empty());
}
TEST(ValidateCpModelTest, ArityThreeInIntProd) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
constraints {
int_prod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
}
}
)pb");
EXPECT_TRUE(ValidateCpModel(model).empty());
}
TEST(ValidateCpModelTest, WrongArityInIntDiv) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
constraints {
int_div {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("have exactly 2 terms"));
}
TEST(ValidateCpModelTest, DivisorDomainContainsZero) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ -3, 3 ] }
constraints {
int_div {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model),
HasSubstr("The domain of the divisor cannot contain 0"));
}
TEST(ValidateCpModelTest, DivisorSpanningAcrossZero) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ -3, 3 ] }
constraints {
int_div {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 2 offset: -3 }
}
}
)pb");
EXPECT_TRUE(ValidateCpModel(model).empty());
}
TEST(ValidateCpModelTest, DivisorIsZero) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
constraints {
int_div {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs {}
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("Division by 0"));
}
TEST(ValidateCpModelTest, WrongArityInIntMod) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
constraints {
int_mod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("have exactly 2 terms"));
}
TEST(ValidateCpModelTest, NegativeModulo) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ -3, 3 ] }
constraints {
int_mod {
target { vars: 0 coeffs: 1 }
exprs { vars: 1 coeffs: 1 }
exprs { vars: 2 coeffs: 1 }
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model),
HasSubstr("strictly positive modulo argument"));
}
TEST(ValidateCpModelTest, IncompatibleAutomatonTransitions) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: 0 domain: 1 }
constraints {
automaton {
final_states: 0
transition_tail: 0
transition_tail: 0
transition_head: 0
transition_head: 1
transition_label: 0
transition_label: 0
vars: 0
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model),
HasSubstr("automaton: incompatible transitions"));
}
TEST(ValidateCpModelTest, DuplicateAutomatonTransitions) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: 0 domain: 1 }
constraints {
automaton {
final_states: 0
transition_tail: 0
transition_tail: 0
transition_head: 0
transition_head: 0
transition_label: 0
transition_label: 0
vars: 0
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model),
HasSubstr("automaton: duplicate transition"));
}
TEST(ValidateCpModelTest, IntervalMustAppearBeforeTheyAreUsed) {
const CpModelProto model = ParseTestProto(R"pb(
constraints { no_overlap { intervals: [ 1, 2 ] } }
constraints {
interval {
start { offset: 0 }
end { offset: 4 }
size { offset: 4 }
}
}
constraints {
interval {
start { offset: 4 }
end { offset: 5 }
size { offset: 1 }
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model, /*after_presolve=*/true),
HasSubstr("must appear before"));
}
TEST(ValidateCpModelTest, ValidNodeExpressions) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 10 ] }
variables { domain: [ 0, 10 ] }
constraints {
routes {
tails: [ 0, 1 ]
heads: [ 1, 0 ]
literals: [ 0, 1 ]
dimensions {
exprs {
vars: [ 2 ]
coeffs: [ 1 ]
}
exprs {
vars: [ 3 ]
coeffs: [ 2 ]
}
}
dimensions {
exprs {}
exprs {}
}
}
}
)pb");
EXPECT_TRUE(ValidateCpModel(model).empty());
}
TEST(ValidateCpModelTest, InvalidNodeExpressionsCount) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 10 ] }
constraints {
routes {
tails: [ 0, 1 ]
heads: [ 1, 0 ]
literals: [ 0, 1 ]
dimensions {
exprs {
vars: [ 2 ]
coeffs: [ 1 ]
}
exprs {
vars: [ 3 ]
coeffs: [ 1 ]
}
exprs {
vars: [ 2 ]
coeffs: [ 1 ]
}
}
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model), HasSubstr("must be of size num_nodes:2"));
}
TEST(ValidateCpModelTest, InvalidNodeExpressionInRoutesConstraint) {
const CpModelProto model = ParseTestProto(R"pb(
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 1 ] }
variables { domain: [ 0, 10 ] }
constraints {
routes {
tails: [ 0, 1 ]
heads: [ 1, 0 ]
literals: [ 0, 1 ]
dimensions {
exprs {
vars: [ 2 ]
coeffs: [ 1 ]
}
exprs {
vars: [ 3 ]
coeffs: [ 1 ]
}
}
}
}
)pb");
EXPECT_THAT(ValidateCpModel(model),
HasSubstr("Out of bound integer variable 3 in route constraint"));
}
} // namespace
} // namespace sat
} // namespace operations_research