Skip to content

Commit 5e6f8cf

Browse files
committed
fixes
1 parent f49f4ff commit 5e6f8cf

11 files changed

Lines changed: 77 additions & 70 deletions

ortools/graph/hamiltonian_path.h

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
//
2222
// The Shortest Hamiltonian Path Problem (SHPP) is similar to the Traveling
2323
// Salesperson Problem (TSP).
24-
// You have to visit all the cities, starting from a given kOne and you
24+
// You have to visit all the cities, starting from a given one and you
2525
// do not need to return to your starting point. With the TSP, you can start
2626
// anywhere, but you have to return to your start location.
2727
//
@@ -41,12 +41,16 @@
4141
// Here is how the algorithm works:
4242
// Let us denote the nodes to be visited by their indices 0 .. n - 1
4343
// Let us pick 0 as the starting node.
44-
// Let d(i,j) denote the distance (or cost) from i to j.
45-
// f(S, j) where S is a set of nodes and j is a node in S is defined as follows:
44+
// Let cost(i,j) denote the cost (or distance) to go from i to j.
45+
// f(S, j), where S is a set of nodes and j is a node in S, is defined as the
46+
// total cost of the shortest path from 0 to j going through all nodes of S.
47+
//
48+
// We can prove easily that it satisfy the following relation:
4649
// f(S, j) = min (i in S \ {j}, f(S \ {j}, i) + cost(i, j))
4750
// (j is an element of S)
51+
//
4852
// Note that this formulation, from the original Held-Karp paper is a bit
49-
// different, but equivalent to the kOne used in Caseau and Laburthe, Solving
53+
// different, but equivalent to the one used in Caseau and Laburthe, Solving
5054
// Small TSPs with Constraints, 1997, ICLP
5155
// f(S, j) = min (i in S, f(S \ {i}, i) + cost(i, j))
5256
// (j is not an element of S)
@@ -73,6 +77,8 @@
7377
// To implement dynamic programming, we store the preceding results of
7478
// computing f(S,j) in an array M[Offset(S,j)]. See the comments about
7579
// LatticeMemoryManager::BaseOffset() to see how this is computed.
80+
// This is really what brings the performance of the algorithm, because memory
81+
// is accessed in sequential order, without risking to thrash the cache.
7682
//
7783
// Keywords: Traveling Salesman, Hamiltonian Path, Dynamic Programming,
7884
// Held, Karp.
@@ -129,9 +135,9 @@ class Set {
129135
typedef Integer IntegerType;
130136

131137
// Useful constants.
132-
static constexpr Integer kOne = static_cast<Integer>(1);
133-
static constexpr Integer kZero = static_cast<Integer>(0);
134-
static const int MaxCardinality = 8 * sizeof(Integer); // NOLINT
138+
static constexpr Integer kOne = Integer{1};
139+
static constexpr Integer kZero = Integer{0};
140+
static constexpr int kMaxCardinality = std::numeric_limits<Integer>::digits;
135141

136142
// Construct a set from an Integer.
137143
explicit Set(Integer n) : value_(n) {
@@ -143,7 +149,7 @@ class Set {
143149
Integer value() const { return value_; }
144150

145151
static Set FullSet(Integer card) {
146-
return card == 0 ? Set(0) : Set(~kZero >> (MaxCardinality - card));
152+
return card == 0 ? Set(0) : Set(~kZero >> (kMaxCardinality - card));
147153
}
148154

149155
// Returns the singleton set with 'n' as its only element.
@@ -355,12 +361,12 @@ class LatticeMemoryManager {
355361
template <typename Set, typename CostType>
356362
void LatticeMemoryManager<Set, CostType>::Init(int max_card) {
357363
DCHECK_LT(0, max_card);
358-
DCHECK_GE(Set::MaxCardinality, max_card);
364+
DCHECK_LE(max_card, Set::kMaxCardinality);
359365
if (max_card <= max_card_) return;
360366
max_card_ = max_card;
361367
binomial_coefficients_.resize(max_card_ + 1);
362368

363-
// Initialize binomial_coefficients_ using Pascal's triangle recursion.
369+
// Initialize binomial_coefficients_ using Pascal's triangle recurrence
364370
for (int n = 0; n <= max_card_; ++n) {
365371
binomial_coefficients_[n].resize(n + 2);
366372
binomial_coefficients_[n][0] = 1;
@@ -418,7 +424,7 @@ uint64_t LatticeMemoryManager<Set, CostType>::BaseOffset(int card,
418424
DCHECK_EQ(card, node_rank);
419425
// Note(user): It is possible to get rid of base_offset_[card] by using a 2-D
420426
// array. It would also make it possible to free all the memory but the layer
421-
// being constructed and the preceding kOne, if another lattice of paths is
427+
// being constructed and the preceding one, if another lattice of paths is
422428
// constructed.
423429
// TODO(user): Evaluate the interest of the above.
424430
// There are 'card' f(set, j) to store. That is why we need to multiply
@@ -465,14 +471,14 @@ class HamiltonianPathSolver {
465471
// stored
466472
public:
467473
// In 2010, 26 was the maximum solvable with 24 Gigs of RAM, and it took
468-
// several minutes. With this 2014 version of the code, kOne may go a little
474+
// several minutes. With this 2014 version of the code, one may go a little
469475
// higher, but considering the complexity of the algorithm (n*2^n), and that
470476
// there are very good ways to solve TSP with more than 32 cities,
471477
// we limit ourselves to 32 cites.
472478
// This is why we define the type NodeSet to be 32-bit wide.
473479
// TODO(user): remove this limitation by using pruning techniques.
474-
typedef uint32_t Integer;
475-
typedef Set<Integer> NodeSet;
480+
using Integer = uint32_t;
481+
using NodeSet = Set<Integer>;
476482

477483
explicit HamiltonianPathSolver(CostFunction cost);
478484
HamiltonianPathSolver(int num_nodes, CostFunction cost);
@@ -553,7 +559,7 @@ class HamiltonianPathSolver {
553559
// Returns the cost value between two nodes.
554560
CostType Cost(int i, int j) { return cost_(i, j); }
555561

556-
// Does all the Dynamic Progamming iterations.
562+
// Does all the Dynamic Programming iterations.
557563
void Solve();
558564

559565
// Computes a path by looking at the information in mem_.
@@ -618,7 +624,7 @@ HamiltonianPathSolver<CostType, CostFunction>::HamiltonianPathSolver(
618624
robustness_checked_(false),
619625
triangle_inequality_checked_(false),
620626
solved_(false) {
621-
CHECK_GE(NodeSet::MaxCardinality, num_nodes_);
627+
CHECK_GE(NodeSet::kMaxCardinality, num_nodes_);
622628
CHECK(cost_.Check());
623629
}
624630

@@ -636,7 +642,7 @@ void HamiltonianPathSolver<CostType, CostFunction>::ChangeCostMatrix(
636642
solved_ = false;
637643
cost_.Reset(cost);
638644
num_nodes_ = num_nodes;
639-
CHECK_GE(NodeSet::MaxCardinality, num_nodes_);
645+
CHECK_GE(NodeSet::kMaxCardinality, num_nodes_);
640646
CHECK(cost_.Check());
641647
}
642648

@@ -701,7 +707,7 @@ void HamiltonianPathSolver<CostType, CostFunction>::Solve() {
701707

702708
const NodeSet full_set = NodeSet::FullSet(num_nodes_);
703709

704-
// Get the cost of the tsp from node 0. It is the path that leaves 0 and goes
710+
// Get the cost of the TSP from node 0. It is the path that leaves 0 and goes
705711
// through all other nodes, and returns at 0, with minimal cost.
706712
tsp_cost_ = mem_.Value(full_set, 0);
707713
tsp_path_ = ComputePath(tsp_cost_, full_set, 0);
@@ -710,7 +716,7 @@ void HamiltonianPathSolver<CostType, CostFunction>::Solve() {
710716
hamiltonian_costs_.resize(num_nodes_);
711717
// Compute the cost of the Hamiltonian paths starting from node 0, going
712718
// through all the other nodes, and ending at end_node. Compute the minimum
713-
// kOne along the way.
719+
// one along the way.
714720
CostType min_hamiltonian_cost = std::numeric_limits<CostType>::max();
715721
const NodeSet hamiltonian_set = full_set.RemoveElement(0);
716722
for (int end_node : hamiltonian_set) {
@@ -885,7 +891,7 @@ class PruningHamiltonianSolver {
885891
// guaranteed to be smaller than or equal to the cost of Hamiltonian path,
886892
// because Hamiltonian path is a spanning tree itself.
887893

888-
// TODO(user): Use generic map-based cache instead of lattice-based kOne.
894+
// TODO(user): Use generic map-based cache instead of lattice-based one.
889895
// TODO(user): Use SaturatedArithmetic for better precision.
890896

891897
public:

ortools/sat/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2934,6 +2934,7 @@ cc_test(
29342934
"//ortools/util:strong_integers",
29352935
"@abseil-cpp//absl/base:log_severity",
29362936
"@abseil-cpp//absl/random",
2937+
"@abseil-cpp//absl/strings",
29372938
"@abseil-cpp//absl/types:span",
29382939
],
29392940
)

ortools/sat/clause.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -904,7 +904,6 @@ class BinaryImplicationGraph : public SatPropagator {
904904
// enough for us and we could store in common the inlined/not-inlined size.
905905
util_intops::StrongVector<LiteralIndex, absl::InlinedVector<Literal, 6>>
906906
implications_;
907-
int64_t num_implications_ = 0;
908907

909908
// Used by RemoveDuplicates() and NotifyPossibleDuplicate().
910909
util_intops::StrongVector<LiteralIndex, bool> might_have_dups_;

ortools/sat/cp_model_presolve.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8311,7 +8311,7 @@ bool CpModelPresolver::PresolvePureSatPart() {
83118311
for (int i = 0; i < sat_solver->LiteralTrail().Index(); ++i) {
83128312
sat_postsolver.FixVariable(sat_solver->LiteralTrail()[i]);
83138313
}
8314-
sat_solver->ExtractClauses(&sat_presolver);
8314+
if (!sat_solver->ExtractClauses(&sat_presolver)) return false;
83158315

83168316
// Run the presolve for a small number of passes.
83178317
// TODO(user): Add a local time limit? this can be slow on big SAT problem.

ortools/sat/primary_variables_test.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ TEST(PrimaryVariablesTest, WithExactlyOne) {
129129
ComputeVariableRelationships(model);
130130
EXPECT_EQ(relationships.secondary_variables.size(), 1);
131131
const ConstraintProto expected = ParseTestProto(R"pb(
132-
exactly_one { literals: [ 0, 1, 2, 3 ] }
132+
exactly_one { literals: [ 0, 1, 2, 3 ] }
133133
)pb");
134134
EXPECT_THAT(relationships.dependency_resolution_constraint,
135135
ElementsAre(EqualsProto(expected)));

ortools/sat/probing.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -894,8 +894,9 @@ bool FailedLiteralProbingRound(ProbingOptions options, Model* model) {
894894
time_limit->GetElapsedDeterministicTime() > limit;
895895
LOG_IF(INFO, options.log_info)
896896
<< "Probing. "
897-
<< " num_probed: " << num_probed << " num_fixed: +" << num_newly_fixed
898-
<< " (" << num_fixed << "/" << num_variables << ")"
897+
<< " num_probed: " << num_probed << "/" << probing_order.size()
898+
<< " num_fixed: +" << num_newly_fixed << " (" << num_fixed << "/"
899+
<< num_variables << ")"
899900
<< " explicit_fix:" << num_explicit_fix
900901
<< " num_conflicts:" << num_conflicts
901902
<< " new_binary_clauses: " << num_new_binary

ortools/sat/sat_inprocessing.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,12 @@ bool Inprocessing::PresolveLoop(SatPresolveOptions options) {
156156
break;
157157
}
158158

159-
// TODO(user): Maintain the total number of literals in the watched clauses.
159+
// Tricky: It is important to clean-up any potential equivalence left in
160+
// case we aborted early due to the limit.
161+
RETURN_IF_FALSE(RemoveFixedAndEquivalentVariables(log_round_info));
160162
if (!LevelZeroPropagate()) return false;
161163

164+
// TODO(user): Maintain the total number of literals in the watched clauses.
162165
SOLVER_LOG(
163166
logger_, "[Pure SAT presolve]", " num_fixed: ", trail_->Index(),
164167
" num_redundant: ", implication_graph_->num_redundant_literals() / 2, "/",

ortools/sat/sat_solver.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1850,6 +1850,7 @@ void SatSolver::ProcessNewlyFixedVariables() {
18501850
// We remove the clauses that are always true and the fixed literals from the
18511851
// others. Note that none of the clause should be all false because we should
18521852
// have detected a conflict before this is called.
1853+
const int saved_index = trail_->Index();
18531854
for (SatClause* clause : clauses_propagator_->AllClausesInCreationOrder()) {
18541855
if (clause->IsRemoved()) continue;
18551856

@@ -1877,6 +1878,15 @@ void SatSolver::ProcessNewlyFixedVariables() {
18771878
AddBinaryClauseInternal(clause->FirstLiteral(), clause->SecondLiteral());
18781879
clauses_propagator_->LazyDetach(clause);
18791880
++num_binary;
1881+
1882+
// Tricky: AddBinaryClauseInternal() might fix literal if there is some
1883+
// unprocessed equivalent literal, and the binary clause turn out to be
1884+
// unary. This shouldn't happen otherwise the logic of
1885+
// RemoveFixedLiteralsAndTestIfTrue() might fail.
1886+
//
1887+
// TODO(user): This still happen in SAT22.Carry_Save_Fast_1.cnf.cnf.xz,
1888+
// it might not directly lead to a bug, but should still be fixed.
1889+
DCHECK_EQ(trail_->Index(), saved_index);
18801890
continue;
18811891
}
18821892
}

ortools/sat/sat_solver.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -345,10 +345,8 @@ class SatSolver {
345345
//
346346
// TODO(user): also copy the removable clauses?
347347
template <typename Output>
348-
void ExtractClauses(Output* out) {
349-
CHECK(!ModelIsUnsat());
350-
Backtrack(0);
351-
if (!FinishPropagation()) return;
348+
bool ExtractClauses(Output* out) {
349+
if (!ResetToLevelZero()) return false;
352350

353351
// It is important to process the newly fixed variables, so they are not
354352
// present in the clauses we export.
@@ -366,6 +364,8 @@ class SatSolver {
366364
out->AddClause(clause->AsSpan());
367365
}
368366
}
367+
368+
return true;
369369
}
370370

371371
// Functions to manage the set of learned binary clauses.

0 commit comments

Comments
 (0)