Skip to content

Commit 8d4ea3d

Browse files
committed
[CP-SAT] more work on routing cuts; fix #4582
1 parent 7b94d9a commit 8d4ea3d

29 files changed

+1605
-265
lines changed

ortools/sat/BUILD.bazel

+35-3
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,31 @@ cc_test(
147147
],
148148
)
149149

150+
cc_library(
151+
name = "primary_variables",
152+
srcs = ["primary_variables.cc"],
153+
hdrs = ["primary_variables.h"],
154+
deps = [
155+
":cp_model_cc_proto",
156+
":cp_model_utils",
157+
"//ortools/base:stl_util",
158+
"//ortools/util:bitset",
159+
"@com_google_absl//absl/algorithm:container",
160+
"@com_google_absl//absl/container:btree",
161+
"@com_google_absl//absl/log:check",
162+
],
163+
)
164+
165+
cc_test(
166+
name = "primary_variables_test",
167+
srcs = ["primary_variables_test.cc"],
168+
deps = [
169+
":primary_variables",
170+
"//ortools/base:gmock_main",
171+
"//ortools/base:parse_test_proto",
172+
],
173+
)
174+
150175
cc_proto_library(
151176
name = "cp_model_cc_proto",
152177
deps = [":cp_model_proto"],
@@ -245,6 +270,7 @@ cc_library(
245270
":cp_model_cc_proto",
246271
":cp_model_utils",
247272
":diffn_util",
273+
":primary_variables",
248274
":sat_parameters_cc_proto",
249275
"//ortools/base",
250276
"//ortools/port:proto_utils",
@@ -254,6 +280,7 @@ cc_library(
254280
"@com_google_absl//absl/container:btree",
255281
"@com_google_absl//absl/container:flat_hash_map",
256282
"@com_google_absl//absl/container:flat_hash_set",
283+
"@com_google_absl//absl/flags:flag",
257284
"@com_google_absl//absl/log:check",
258285
"@com_google_absl//absl/meta:type_traits",
259286
"@com_google_absl//absl/strings",
@@ -623,6 +650,7 @@ cc_library(
623650
":parameters_validation",
624651
":precedences",
625652
":presolve_context",
653+
":primary_variables",
626654
":probing",
627655
":rins",
628656
":routing_cuts",
@@ -680,8 +708,8 @@ cc_library(
680708
deps = [
681709
":cp_model_cc_proto",
682710
":cp_model_utils",
711+
":integer",
683712
":integer_base",
684-
":intervals",
685713
":linear_constraint",
686714
":model",
687715
":sat_base",
@@ -1875,7 +1903,6 @@ cc_library(
18751903
"@com_google_absl//absl/container:inlined_vector",
18761904
"@com_google_absl//absl/log",
18771905
"@com_google_absl//absl/log:check",
1878-
"@com_google_absl//absl/meta:type_traits",
18791906
"@com_google_absl//absl/types:span",
18801907
],
18811908
)
@@ -1885,6 +1912,8 @@ cc_test(
18851912
size = "small",
18861913
srcs = ["precedences_test.cc"],
18871914
deps = [
1915+
":cp_model_mapping",
1916+
":cp_model_solver_helpers",
18881917
":integer",
18891918
":integer_base",
18901919
":integer_search",
@@ -1893,9 +1922,9 @@ cc_test(
18931922
":sat_base",
18941923
":sat_solver",
18951924
"//ortools/base:gmock_main",
1925+
"//ortools/base:parse_test_proto",
18961926
"//ortools/util:sorted_interval_list",
18971927
"@com_google_absl//absl/container:flat_hash_map",
1898-
"@com_google_absl//absl/types:span",
18991928
],
19001929
)
19011930

@@ -2042,6 +2071,7 @@ cc_library(
20422071
":sat_solver",
20432072
"//ortools/base",
20442073
"//ortools/graph:strongly_connected_components",
2074+
"//ortools/util:bitset",
20452075
"//ortools/util:sort",
20462076
"//ortools/util:strong_integers",
20472077
"@com_google_absl//absl/algorithm:container",
@@ -2661,6 +2691,7 @@ cc_library(
26612691
srcs = ["routing_cuts.cc"],
26622692
hdrs = ["routing_cuts.h"],
26632693
deps = [
2694+
":clause",
26642695
":cp_model_cc_proto",
26652696
":cp_model_utils",
26662697
":cuts",
@@ -2698,6 +2729,7 @@ cc_test(
26982729
name = "routing_cuts_test",
26992730
srcs = ["routing_cuts_test.cc"],
27002731
deps = [
2732+
":clause",
27012733
":cp_model",
27022734
":cuts",
27032735
":integer",

ortools/sat/all_different.cc

+4-9
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ AllDifferentBoundsPropagator::AllDifferentBoundsPropagator(
424424
const int capacity = expressions.size() + 2;
425425
index_to_start_index_.resize(capacity);
426426
index_to_end_index_.resize(capacity);
427-
index_is_present_.resize(capacity, false);
427+
index_is_present_.Resize(capacity);
428428
index_to_expr_.resize(capacity, kNoIntegerVariable);
429429

430430
for (int i = 0; i < expressions.size(); ++i) {
@@ -535,11 +535,7 @@ bool AllDifferentBoundsPropagator::PropagateLowerBoundsInternal(
535535
// Make sure we change our base_ so that GetIndex() fit in our buffers.
536536
base_ = min_lb - IntegerValue(1);
537537

538-
// Sparse cleaning of index_is_present_.
539-
for (const int i : indices_to_clear_) {
540-
index_is_present_[i] = false;
541-
}
542-
indices_to_clear_.clear();
538+
index_is_present_.ResetAllToFalse();
543539

544540
// Sort bounds by increasing ub.
545541
std::sort(bounds.begin(), bounds.end(),
@@ -600,15 +596,14 @@ bool AllDifferentBoundsPropagator::PropagateLowerBoundsInternal(
600596
{
601597
index_to_start_index_[new_index] = start_index;
602598
index_to_expr_[new_index] = expr;
603-
index_is_present_[new_index] = true;
604-
indices_to_clear_.push_back(new_index);
599+
index_is_present_.Set(new_index);
605600
}
606601

607602
// In most situation, we cannot have a conflict now, because it should have
608603
// been detected before by pushing an interval lower bound past its upper
609604
// bound. However, it is possible that when we push one bound, other bounds
610605
// change. So if the upper bound is smaller than the current interval end,
611-
// we abort so that the conflit reason will be better on the next call to
606+
// we abort so that the conflict reason will be better on the next call to
612607
// the propagator.
613608
const IntegerValue end = GetValue(end_index);
614609
if (end > integer_trail_->UpperBound(expr)) return true;

ortools/sat/all_different.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "ortools/sat/integer_base.h"
2626
#include "ortools/sat/model.h"
2727
#include "ortools/sat/sat_base.h"
28+
#include "ortools/util/bitset.h"
2829
#include "ortools/util/strong_integers.h"
2930

3031
namespace operations_research {
@@ -218,10 +219,9 @@ class AllDifferentBoundsPropagator : public PropagatorInterface {
218219

219220
// Non-consecutive intervals related data-structures.
220221
IntegerValue base_;
221-
std::vector<int> indices_to_clear_;
222222
std::vector<int> index_to_start_index_;
223223
std::vector<int> index_to_end_index_;
224-
std::vector<bool> index_is_present_;
224+
SparseBitset<int> index_is_present_;
225225
std::vector<AffineExpression> index_to_expr_;
226226

227227
// Temporary integer reason.

ortools/sat/clause.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -346,7 +346,7 @@ void ClauseManager::AttachAllClauses() {
346346
if (all_clauses_are_attached_) return;
347347
all_clauses_are_attached_ = true;
348348

349-
needs_cleaning_.ClearAll(); // This doesn't resize it.
349+
needs_cleaning_.ResetAllToFalse(); // This doesn't resize it.
350350
watchers_on_false_.resize(needs_cleaning_.size().value());
351351

352352
DeleteRemovedClauses();

ortools/sat/cp_model_checker.cc

+18
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "absl/container/btree_map.h"
2828
#include "absl/container/flat_hash_map.h"
2929
#include "absl/container/flat_hash_set.h"
30+
#include "absl/flags/flag.h"
3031
#include "absl/log/check.h"
3132
#include "absl/meta/type_traits.h"
3233
#include "absl/strings/str_cat.h"
@@ -36,10 +37,15 @@
3637
#include "ortools/sat/cp_model.pb.h"
3738
#include "ortools/sat/cp_model_utils.h"
3839
#include "ortools/sat/diffn_util.h"
40+
#include "ortools/sat/primary_variables.h"
3941
#include "ortools/sat/sat_parameters.pb.h"
4042
#include "ortools/util/saturated_arithmetic.h"
4143
#include "ortools/util/sorted_interval_list.h"
4244

45+
ABSL_FLAG(bool, cp_model_check_dependent_variables, false,
46+
"When true, check that solutions can be computed only from their "
47+
"free variables.");
48+
4349
namespace operations_research {
4450
namespace sat {
4551
namespace {
@@ -1957,6 +1963,18 @@ bool SolutionIsFeasible(const CpModelProto& model,
19571963
VLOG(2) << "Checker inner objective = " << inner_objective;
19581964
VLOG(2) << "Checker scaled objective = " << scaled_objective;
19591965
}
1966+
if (absl::GetFlag(FLAGS_cp_model_check_dependent_variables)) {
1967+
const VariableRelationships relationships =
1968+
ComputeVariableRelationships(model);
1969+
std::vector<int64_t> all_variables(variable_values.begin(),
1970+
variable_values.end());
1971+
for (const int var : relationships.secondary_variables) {
1972+
all_variables[var] = -999999; // Those values should be overwritten.
1973+
}
1974+
CHECK(ComputeAllVariablesFromPrimaryVariables(model, relationships,
1975+
&all_variables));
1976+
CHECK(absl::MakeSpan(all_variables) == variable_values);
1977+
}
19601978

19611979
return true;
19621980
}

ortools/sat/cp_model_lns.cc

+4
Original file line numberDiff line numberDiff line change
@@ -2426,6 +2426,10 @@ Neighborhood RectanglesPackingRelaxOneNeighborhoodGenerator::Generate(
24262426
Neighborhood neighborhood =
24272427
helper_.FixGivenVariables(initial_solution, variables_to_freeze);
24282428

2429+
neighborhood.is_simple = false;
2430+
neighborhood.is_reduced = true;
2431+
neighborhood.variables_that_can_be_fixed_to_local_optimum.clear();
2432+
24292433
// The call above add the relaxed variables to the neighborhood using the
24302434
// current bounds at level 0. For big problems, this might create a hard model
24312435
// with a large complicated landscape of fixed boxes with a lot of potential

ortools/sat/cp_model_lns.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,8 @@ struct Neighborhood {
5959
// True if neighborhood generator was able to generate a neighborhood.
6060
bool is_generated = false;
6161

62-
// True if an optimal solution to the neighborhood is also an optimal solution
63-
// to the original model.
62+
// False if an optimal solution to the neighborhood is also an optimal
63+
// solution to the original model.
6464
bool is_reduced = false;
6565

6666
// True if this neighborhood was just obtained by fixing some variables.

ortools/sat/cp_model_loader.cc

+3-50
Original file line numberDiff line numberDiff line change
@@ -74,27 +74,6 @@ std::vector<int64_t> ValuesFromProto(const Values& values) {
7474
return std::vector<int64_t>(values.begin(), values.end());
7575
}
7676

77-
void ComputeLinearBounds(const LinearConstraintProto& proto,
78-
CpModelMapping* mapping, IntegerTrail* integer_trail,
79-
int64_t* sum_min, int64_t* sum_max) {
80-
*sum_min = 0;
81-
*sum_max = 0;
82-
83-
for (int i = 0; i < proto.vars_size(); ++i) {
84-
const int64_t coeff = proto.coeffs(i);
85-
const IntegerVariable var = mapping->Integer(proto.vars(i));
86-
const int64_t lb = integer_trail->LowerBound(var).value();
87-
const int64_t ub = integer_trail->UpperBound(var).value();
88-
if (coeff >= 0) {
89-
(*sum_min) += coeff * lb;
90-
(*sum_max) += coeff * ub;
91-
} else {
92-
(*sum_min) += coeff * ub;
93-
(*sum_max) += coeff * lb;
94-
}
95-
}
96-
}
97-
9877
// We check if the constraint is a sum(ax * xi) == value.
9978
bool ConstraintIsEq(const LinearConstraintProto& proto) {
10079
return proto.domain_size() == 2 && proto.domain(0) == proto.domain(1);
@@ -104,9 +83,8 @@ bool ConstraintIsEq(const LinearConstraintProto& proto) {
10483
bool ConstraintIsNEq(const LinearConstraintProto& proto,
10584
CpModelMapping* mapping, IntegerTrail* integer_trail,
10685
int64_t* single_value) {
107-
int64_t sum_min = 0;
108-
int64_t sum_max = 0;
109-
ComputeLinearBounds(proto, mapping, integer_trail, &sum_min, &sum_max);
86+
const auto [sum_min, sum_max] =
87+
mapping->ComputeMinMaxActivity(proto, integer_trail);
11088

11189
const Domain complement =
11290
Domain(sum_min, sum_max)
@@ -1280,32 +1258,6 @@ void LoadLinearConstraint(const ConstraintProto& ct, Model* m) {
12801258
max_sum += std::max(term_a, term_b);
12811259
}
12821260

1283-
// Load conditional precedences and always true binary relations.
1284-
const SatParameters& params = *m->GetOrCreate<SatParameters>();
1285-
if (ct.enforcement_literal().size() <= 1 && vars.size() <= 2) {
1286-
// To avoid overflow in the code below, we tighten the bounds.
1287-
int64_t rhs_min = ct.linear().domain(0);
1288-
int64_t rhs_max = ct.linear().domain(ct.linear().domain().size() - 1);
1289-
rhs_min = std::max(rhs_min, min_sum.value());
1290-
rhs_max = std::min(rhs_max, max_sum.value());
1291-
1292-
auto* repository = m->GetOrCreate<BinaryRelationRepository>();
1293-
if (ct.enforcement_literal().empty()) {
1294-
if (vars.size() == 2) {
1295-
repository->Add(Literal(kNoLiteralIndex), {vars[0], coeffs[0]},
1296-
{vars[1], coeffs[1]}, rhs_min, rhs_max);
1297-
}
1298-
} else {
1299-
const Literal lit = mapping->Literal(ct.enforcement_literal(0));
1300-
if (vars.size() == 1) {
1301-
repository->Add(lit, {vars[0], coeffs[0]}, {}, rhs_min, rhs_max);
1302-
} else if (vars.size() == 2) {
1303-
repository->Add(lit, {vars[0], coeffs[0]}, {vars[1], coeffs[1]},
1304-
rhs_min, rhs_max);
1305-
}
1306-
}
1307-
}
1308-
13091261
// Load precedences.
13101262
if (!HasEnforcementLiteral(ct)) {
13111263
auto* precedences = m->GetOrCreate<PrecedenceRelations>();
@@ -1366,6 +1318,7 @@ void LoadLinearConstraint(const ConstraintProto& ct, Model* m) {
13661318
}
13671319
}
13681320

1321+
const SatParameters& params = *m->GetOrCreate<SatParameters>();
13691322
const IntegerValue domain_size_limit(
13701323
params.max_domain_size_when_encoding_eq_neq_constraints());
13711324
if (ct.linear().vars_size() == 2 && !integer_trail->IsFixed(vars[0]) &&

ortools/sat/cp_model_mapping.h

+26-1
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#ifndef OR_TOOLS_SAT_CP_MODEL_MAPPING_H_
1515
#define OR_TOOLS_SAT_CP_MODEL_MAPPING_H_
1616

17+
#include <cstdint>
18+
#include <utility>
1719
#include <vector>
1820

1921
#include "absl/container/flat_hash_set.h"
@@ -22,8 +24,8 @@
2224
#include "ortools/base/strong_vector.h"
2325
#include "ortools/sat/cp_model.pb.h"
2426
#include "ortools/sat/cp_model_utils.h"
27+
#include "ortools/sat/integer.h"
2528
#include "ortools/sat/integer_base.h"
26-
#include "ortools/sat/intervals.h"
2729
#include "ortools/sat/linear_constraint.h"
2830
#include "ortools/sat/model.h"
2931
#include "ortools/sat/sat_base.h"
@@ -189,6 +191,29 @@ class CpModelMapping {
189191
return CanonicalizeExpr(expr);
190192
}
191193

194+
// Returns the min/max activity of the linear constraint under the current
195+
// integer_trail bounds.
196+
std::pair<int64_t, int64_t> ComputeMinMaxActivity(
197+
const LinearConstraintProto& proto, IntegerTrail* integer_trail) {
198+
int64_t sum_min = 0;
199+
int64_t sum_max = 0;
200+
201+
for (int i = 0; i < proto.vars_size(); ++i) {
202+
const int64_t coeff = proto.coeffs(i);
203+
const IntegerVariable var = this->Integer(proto.vars(i));
204+
const int64_t lb = integer_trail->LowerBound(var).value();
205+
const int64_t ub = integer_trail->UpperBound(var).value();
206+
if (coeff >= 0) {
207+
sum_min += coeff * lb;
208+
sum_max += coeff * ub;
209+
} else {
210+
sum_min += coeff * ub;
211+
sum_max += coeff * lb;
212+
}
213+
}
214+
return {sum_min, sum_max};
215+
}
216+
192217
// For logging only, these are not super efficient.
193218
int NumIntegerVariables() const {
194219
int result = 0;

0 commit comments

Comments
 (0)