Skip to content

Commit 99d4357

Browse files
committed
[CP-SAT] lots of fuzzer bugs fixed
1 parent de72e86 commit 99d4357

20 files changed

+598
-57
lines changed

ortools/sat/2d_rectangle_presolve.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
#include "absl/types/span.h"
3232
#include "ortools/base/logging.h"
3333
#include "ortools/base/stl_util.h"
34-
#include "ortools/graph/max_flow.h"
34+
#include "ortools/graph/minimum_vertex_cover.h"
3535
#include "ortools/graph/strongly_connected_components.h"
3636
#include "ortools/sat/diffn_util.h"
3737
#include "ortools/sat/integer_base.h"

ortools/sat/BUILD.bazel

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,7 @@ cc_library(
523523
deps = [
524524
":circuit",
525525
":clause",
526+
":combine_solutions",
526527
":cp_model_cc_proto",
527528
":cp_model_checker",
528529
":cp_model_lns",
@@ -3009,7 +3010,7 @@ cc_library(
30093010
":diffn_util",
30103011
":integer_base",
30113012
"//ortools/base:stl_util",
3012-
"//ortools/graph:max_flow",
3013+
"//ortools/graph:minimum_vertex_cover",
30133014
"//ortools/graph:strongly_connected_components",
30143015
"@com_google_absl//absl/algorithm:container",
30153016
"@com_google_absl//absl/container:btree",
@@ -3701,6 +3702,32 @@ cc_library(
37013702
],
37023703
)
37033704

3705+
cc_library(
3706+
name = "combine_solutions",
3707+
srcs = ["combine_solutions.cc"],
3708+
hdrs = ["combine_solutions.h"],
3709+
deps = [
3710+
":cp_model_cc_proto",
3711+
":cp_model_checker",
3712+
":synchronization",
3713+
"@com_google_absl//absl/log:check",
3714+
"@com_google_absl//absl/strings",
3715+
"@com_google_absl//absl/types:span",
3716+
],
3717+
)
3718+
3719+
cc_test(
3720+
name = "combine_solutions_test",
3721+
srcs = ["combine_solutions_test.cc"],
3722+
deps = [
3723+
":combine_solutions",
3724+
":model",
3725+
":synchronization",
3726+
"//ortools/base:gmock_main",
3727+
"//ortools/base:parse_test_proto",
3728+
],
3729+
)
3730+
37043731
cc_library(
37053732
name = "work_assignment",
37063733
srcs = ["work_assignment.cc"],

ortools/sat/combine_solutions.cc

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright 2010-2024 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
#include "ortools/sat/combine_solutions.h"
15+
16+
#include <cstdint>
17+
#include <memory>
18+
#include <optional>
19+
#include <string>
20+
#include <string_view>
21+
#include <vector>
22+
23+
#include "absl/log/check.h"
24+
#include "absl/strings/str_cat.h"
25+
#include "absl/types/span.h"
26+
#include "ortools/sat/cp_model_checker.h"
27+
#include "ortools/sat/synchronization.h"
28+
29+
namespace operations_research {
30+
namespace sat {
31+
32+
bool TrySolution(const CpModelProto& model, absl::Span<const int64_t> solution,
33+
absl::Span<const int64_t> new_solution,
34+
absl::Span<const int64_t> base_solution,
35+
std::vector<int64_t>* new_combined_solution) {
36+
new_combined_solution->resize(new_solution.size());
37+
for (int i = 0; i < new_solution.size(); ++i) {
38+
if (new_solution[i] != base_solution[i]) {
39+
// A value that changed that we patch.
40+
(*new_combined_solution)[i] = new_solution[i];
41+
} else {
42+
(*new_combined_solution)[i] = solution[i];
43+
}
44+
}
45+
return SolutionIsFeasible(model, *new_combined_solution);
46+
}
47+
48+
std::optional<std::vector<int64_t>> FindCombinedSolution(
49+
const CpModelProto& model, absl::Span<const int64_t> new_solution,
50+
absl::Span<const int64_t> base_solution,
51+
const SharedResponseManager* response_manager, std::string* solution_info) {
52+
CHECK_EQ(new_solution.size(), base_solution.size());
53+
const std::vector<
54+
std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution>>
55+
solutions = response_manager->SolutionsRepository().GetBestNSolutions(10);
56+
57+
for (int sol_idx = 0; sol_idx < solutions.size(); ++sol_idx) {
58+
std::shared_ptr<const SharedSolutionRepository<int64_t>::Solution> s =
59+
solutions[sol_idx];
60+
DCHECK_EQ(s->variable_values.size(), new_solution.size());
61+
62+
if (s->variable_values == new_solution ||
63+
s->variable_values == base_solution) {
64+
continue;
65+
}
66+
std::vector<int64_t> new_combined_solution;
67+
if (TrySolution(model, s->variable_values, new_solution, base_solution,
68+
&new_combined_solution)) {
69+
absl::StrAppend(solution_info, " [combined with: ",
70+
std::string_view(solutions[sol_idx]->info).substr(0, 20),
71+
"...]");
72+
return new_combined_solution;
73+
}
74+
}
75+
return std::nullopt;
76+
}
77+
78+
} // namespace sat
79+
} // namespace operations_research

ortools/sat/combine_solutions.h

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// Copyright 2010-2024 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
#ifndef OR_TOOLS_SAT_COMBINE_SOLUTIONS_H_
15+
#define OR_TOOLS_SAT_COMBINE_SOLUTIONS_H_
16+
17+
#include <cstdint>
18+
#include <optional>
19+
#include <string>
20+
#include <vector>
21+
22+
#include "absl/types/span.h"
23+
#include "ortools/sat/cp_model.pb.h"
24+
#include "ortools/sat/synchronization.h"
25+
26+
namespace operations_research {
27+
namespace sat {
28+
29+
// Given a `new_solution` that was created by changing a bit a `base_solution`,
30+
// try to apply the same changes to the other solutions stored in the
31+
// `response_manager` and return any such generated solution that is valid.
32+
std::optional<std::vector<int64_t>> FindCombinedSolution(
33+
const CpModelProto& model, absl::Span<const int64_t> new_solution,
34+
absl::Span<const int64_t> base_solution,
35+
const SharedResponseManager* response_manager, std::string* solution_info);
36+
37+
} // namespace sat
38+
} // namespace operations_research
39+
40+
#endif // OR_TOOLS_SAT_COMBINE_SOLUTIONS_H_
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// Copyright 2010-2024 Google LLC
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
#include "ortools/sat/combine_solutions.h"
15+
16+
#include <cstdint>
17+
#include <string>
18+
#include <vector>
19+
20+
#include "gtest/gtest.h"
21+
#include "ortools/base/gmock.h"
22+
#include "ortools/base/parse_test_proto.h"
23+
#include "ortools/sat/model.h"
24+
#include "ortools/sat/synchronization.h"
25+
26+
namespace operations_research {
27+
namespace sat {
28+
namespace {
29+
30+
using ::google::protobuf::contrib::parse_proto::ParseTestProto;
31+
using ::testing::ElementsAre;
32+
using ::testing::Optional;
33+
34+
TEST(CombineSolutionsTest, BasicTest) {
35+
const CpModelProto model = ParseTestProto(R"pb(
36+
variables { domain: [ 0, 1 ] }
37+
variables { domain: [ 0, 1 ] }
38+
variables { domain: [ 0, 1 ] }
39+
variables { domain: [ 0, 1 ] }
40+
objective {
41+
vars: [ 0, 1, 2, 3 ]
42+
coeffs: [ 1, 1, 1, 1 ]
43+
}
44+
)pb");
45+
const std::vector<int64_t> existing_solution = {1, 0, 0, 0};
46+
const std::vector<int64_t> base_solution = {0, 1, 0, 0};
47+
const std::vector<int64_t> new_solution = {0, 1, 1, 0};
48+
Model m;
49+
SharedResponseManager response_manager(&m);
50+
response_manager.NewSolution(existing_solution, "existing", nullptr);
51+
response_manager.NewSolution(base_solution, "base", nullptr);
52+
response_manager.NewSolution(new_solution, "new", nullptr);
53+
std::string solution_info;
54+
const auto combined_solution = FindCombinedSolution(
55+
model, new_solution, base_solution, &response_manager, &solution_info);
56+
EXPECT_THAT(combined_solution, Optional(ElementsAre(1, 0, 1, 0)));
57+
}
58+
59+
} // namespace
60+
} // namespace sat
61+
} // namespace operations_research

ortools/sat/cp_model_checker.cc

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -105,13 +105,13 @@ std::string ValidateIntegerVariable(const CpModelProto& model, int v) {
105105

106106
// Internally, we often take the negation of a domain, and we also want to
107107
// have sentinel values greater than the min/max of a variable domain, so
108-
// the domain must fall in [kint64min + 2, kint64max - 1].
108+
// the domain must fall in [-kint64max / 2, kint64max / 2].
109109
const int64_t lb = proto.domain(0);
110110
const int64_t ub = proto.domain(proto.domain_size() - 1);
111-
if (lb < std::numeric_limits<int64_t>::min() + 2 ||
112-
ub > std::numeric_limits<int64_t>::max() - 1) {
111+
if (lb < -std::numeric_limits<int64_t>::max() / 2 ||
112+
ub > std::numeric_limits<int64_t>::max() / 2) {
113113
return absl::StrCat(
114-
"var #", v, " domain do not fall in [kint64min + 2, kint64max - 1]. ",
114+
"var #", v, " domain do not fall in [-kint64max / 2, kint64max / 2]. ",
115115
ProtobufShortDebugString(proto));
116116
}
117117

@@ -361,8 +361,14 @@ std::string ValidateIntProdConstraint(const CpModelProto& model,
361361
// Detect potential overflow.
362362
Domain product_domain(1);
363363
for (const LinearExpressionProto& expr : ct.int_prod().exprs()) {
364-
product_domain = product_domain.ContinuousMultiplicationBy(
365-
{MinOfExpression(model, expr), MaxOfExpression(model, expr)});
364+
const int64_t min_expr = MinOfExpression(model, expr);
365+
const int64_t max_expr = MaxOfExpression(model, expr);
366+
if (min_expr == 0 && max_expr == 0) {
367+
// An overflow multiplied by zero is still invalid.
368+
continue;
369+
}
370+
product_domain =
371+
product_domain.ContinuousMultiplicationBy({min_expr, max_expr});
366372
}
367373

368374
if (product_domain.Max() <= -std ::numeric_limits<int64_t>::max() ||
@@ -918,11 +924,6 @@ std::string ValidateSearchStrategies(const CpModelProto& model) {
918924
" has a domain too large to be used in a"
919925
" SELECT_MEDIAN_VALUE value selection strategy");
920926
}
921-
if (PossibleIntegerOverflow(model, {ref}, {1})) {
922-
// This will become an overflow if translated to an expr.
923-
return absl::StrCat("Possible integer overflow in strategy: ",
924-
ProtobufShortDebugString(strategy));
925-
}
926927
}
927928
for (const LinearExpressionProto& expr : strategy.exprs()) {
928929
for (const int var : expr.vars()) {
@@ -1025,9 +1026,6 @@ bool PossibleIntegerOverflow(const CpModelProto& model,
10251026
}
10261027

10271028
std::string ValidateCpModel(const CpModelProto& model, bool after_presolve) {
1028-
if (!after_presolve && model.has_symmetry()) {
1029-
return "The symmetry field should be empty and reserved for internal use.";
1030-
}
10311029
int64_t int128_overflow = 0;
10321030
for (int v = 0; v < model.variables_size(); ++v) {
10331031
RETURN_IF_NOT_EMPTY(ValidateIntegerVariable(model, v));
@@ -1637,26 +1635,51 @@ class ConstraintChecker {
16371635
const int num_arcs = ct.routes().tails_size();
16381636
int num_used_arcs = 0;
16391637
int num_self_arcs = 0;
1638+
1639+
// Compute the number of nodes.
16401640
int num_nodes = 0;
1641-
std::vector<int> tail_to_head;
1641+
for (int i = 0; i < num_arcs; ++i) {
1642+
num_nodes = std::max(num_nodes, 1 + ct.routes().tails(i));
1643+
num_nodes = std::max(num_nodes, 1 + ct.routes().heads(i));
1644+
}
1645+
1646+
std::vector<int> tail_to_head(num_nodes, -1);
1647+
std::vector<bool> has_incoming_arc(num_nodes, false);
1648+
std::vector<int> has_outgoing_arc(num_nodes, false);
16421649
std::vector<int> depot_nexts;
16431650
for (int i = 0; i < num_arcs; ++i) {
16441651
const int tail = ct.routes().tails(i);
16451652
const int head = ct.routes().heads(i);
1646-
num_nodes = std::max(num_nodes, 1 + tail);
1647-
num_nodes = std::max(num_nodes, 1 + head);
1648-
tail_to_head.resize(num_nodes, -1);
16491653
if (LiteralIsTrue(ct.routes().literals(i))) {
1654+
// Check for loops.
1655+
if (tail != 0) {
1656+
if (has_outgoing_arc[tail]) {
1657+
VLOG(1) << "routes: node " << tail << "has two outgoing arcs";
1658+
return false;
1659+
}
1660+
has_outgoing_arc[tail] = true;
1661+
}
1662+
if (head != 0) {
1663+
if (has_incoming_arc[head]) {
1664+
VLOG(1) << "routes: node " << head << "has two incoming arcs";
1665+
return false;
1666+
}
1667+
has_incoming_arc[head] = true;
1668+
}
1669+
16501670
if (tail == head) {
1651-
if (tail == 0) return false;
1671+
if (tail == 0) {
1672+
VLOG(1) << "Self loop on node 0 are forbidden.";
1673+
return false;
1674+
}
16521675
++num_self_arcs;
16531676
continue;
16541677
}
16551678
++num_used_arcs;
16561679
if (tail == 0) {
16571680
depot_nexts.push_back(head);
16581681
} else {
1659-
if (tail_to_head[tail] != -1) return false;
1682+
DCHECK_EQ(tail_to_head[tail], -1);
16601683
tail_to_head[tail] = head;
16611684
}
16621685
}
@@ -1769,9 +1792,9 @@ class ConstraintChecker {
17691792
// This indicates that such a constraint was not added to the model.
17701793
// It should probably be a validation error, but it is hard to
17711794
// detect beforehand.
1772-
LOG(ERROR) << "Warning, an interval constraint was likely used "
1773-
"without a corresponding linear constraint linking "
1774-
"its start, size and end.";
1795+
VLOG(1) << "Warning, an interval constraint was likely used "
1796+
"without a corresponding linear constraint linking "
1797+
"its start, size and end.";
17751798
}
17761799
return false;
17771800
}

0 commit comments

Comments
 (0)