Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 7a67706

Browse files
committedMar 21, 2025·
[CP-SAT] more work on routing cuts; graph based LNS start from the objective; better enforcement of time limits
1 parent 65da6de commit 7a67706

18 files changed

+342
-55
lines changed
 

‎ortools/sat/BUILD.bazel

+31-1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ cc_library(
209209
"//ortools/util:sorted_interval_list",
210210
"@com_google_absl//absl/container:flat_hash_map",
211211
"@com_google_absl//absl/container:flat_hash_set",
212+
"@com_google_absl//absl/flags:flag",
212213
"@com_google_absl//absl/log:check",
213214
"@com_google_absl//absl/status",
214215
"@com_google_absl//absl/strings",
@@ -2709,6 +2710,33 @@ cc_test(
27092710
],
27102711
)
27112712

2713+
proto_library(
2714+
name = "routes_support_graph_proto",
2715+
srcs = ["routes_support_graph.proto"],
2716+
)
2717+
2718+
cc_proto_library(
2719+
name = "routes_support_graph_cc_proto",
2720+
deps = [":routes_support_graph_proto"],
2721+
)
2722+
2723+
cc_binary(
2724+
name = "render_routes_support_graph",
2725+
srcs = ["render_routes_support_graph.cc"],
2726+
deps = [
2727+
":routes_support_graph_cc_proto",
2728+
"//ortools/base",
2729+
"//ortools/base:file",
2730+
"//ortools/routing/parsers:solomon_parser",
2731+
"//ortools/util:file_util",
2732+
"@com_google_absl//absl/flags:flag",
2733+
"@com_google_absl//absl/log",
2734+
"@com_google_absl//absl/log:check",
2735+
"@com_google_absl//absl/strings",
2736+
"@com_google_absl//absl/strings:string_view",
2737+
],
2738+
)
2739+
27122740
cc_library(
27132741
name = "routing_cuts",
27142742
srcs = ["routing_cuts.cc"],
@@ -2724,7 +2752,9 @@ cc_library(
27242752
":linear_constraint_manager",
27252753
":model",
27262754
":precedences",
2755+
":routes_support_graph_cc_proto",
27272756
":sat_base",
2757+
":sat_parameters_cc_proto",
27282758
":synchronization",
27292759
":util",
27302760
"//ortools/base",
@@ -2734,12 +2764,12 @@ cc_library(
27342764
"//ortools/graph",
27352765
"//ortools/graph:connected_components",
27362766
"//ortools/graph:max_flow",
2737-
"//ortools/util:bitset",
27382767
"//ortools/util:strong_integers",
27392768
"@com_google_absl//absl/algorithm:container",
27402769
"@com_google_absl//absl/cleanup",
27412770
"@com_google_absl//absl/container:flat_hash_map",
27422771
"@com_google_absl//absl/container:flat_hash_set",
2772+
"@com_google_absl//absl/flags:flag",
27432773
"@com_google_absl//absl/log",
27442774
"@com_google_absl//absl/log:check",
27452775
"@com_google_absl//absl/log:vlog_is_on",

‎ortools/sat/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ list(REMOVE_ITEM _SRCS
1818
${CMAKE_CURRENT_SOURCE_DIR}/opb_reader.h
1919
${CMAKE_CURRENT_SOURCE_DIR}/sat_cnf_reader.h
2020
${CMAKE_CURRENT_SOURCE_DIR}/sat_runner.cc
21+
${CMAKE_CURRENT_SOURCE_DIR}/render_routes_support_graph.cc
2122
)
2223
set(NAME ${PROJECT_NAME}_sat)
2324

‎ortools/sat/cp_model_checker.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -505,8 +505,8 @@ std::string ValidateElementConstraint(const CpModelProto& model,
505505
RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, expr));
506506
LinearExpressionProto overflow_detection = ct.element().linear_target();
507507
AppendToOverflowValidator(expr, &overflow_detection, -1);
508-
overflow_detection.set_offset(overflow_detection.offset() -
509-
expr.offset());
508+
const int64_t offset = CapSub(overflow_detection.offset(), expr.offset());
509+
overflow_detection.set_offset(offset);
510510
if (PossibleIntegerOverflow(model, overflow_detection.vars(),
511511
overflow_detection.coeffs(),
512512
overflow_detection.offset())) {

‎ortools/sat/cp_model_lns.cc

+42-17
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,15 @@ void NeighborhoodGeneratorHelper::InitializeHelperData() {
197197

198198
const int num_variables = model_proto_.variables().size();
199199
is_in_objective_.resize(num_variables, false);
200+
has_positive_objective_coefficient_.resize(num_variables, false);
200201
if (model_proto_.has_objective()) {
201-
for (const int ref : model_proto_.objective().vars()) {
202+
for (int i = 0; i < model_proto_.objective().vars_size(); ++i) {
203+
const int ref = model_proto_.objective().vars(i);
204+
const int64_t coeff = model_proto_.objective().coeffs(i);
205+
DCHECK_NE(coeff, 0);
202206
is_in_objective_[PositiveRef(ref)] = true;
207+
has_positive_objective_coefficient_[PositiveRef(ref)] =
208+
ref == PositiveRef(ref) ? coeff > 0 : coeff < 0;
203209
}
204210
}
205211
}
@@ -1318,6 +1324,26 @@ double NeighborhoodGenerator::Synchronize() {
13181324
return total_dtime;
13191325
}
13201326

1327+
std::vector<int>
1328+
NeighborhoodGeneratorHelper::ImprovableObjectiveVariablesWhileHoldingLock(
1329+
const CpSolverResponse& initial_solution) const {
1330+
std::vector<int> result;
1331+
absl::ReaderMutexLock lock(&domain_mutex_);
1332+
for (const int var : active_objective_variables_) {
1333+
const auto& domain =
1334+
model_proto_with_only_variables_.variables(var).domain();
1335+
bool at_best_value = false;
1336+
if (has_positive_objective_coefficient_[var]) {
1337+
at_best_value = initial_solution.solution(var) == domain[0];
1338+
} else {
1339+
at_best_value =
1340+
initial_solution.solution(var) == domain[domain.size() - 1];
1341+
}
1342+
if (!at_best_value) result.push_back(var);
1343+
}
1344+
return result;
1345+
}
1346+
13211347
namespace {
13221348

13231349
template <class T>
@@ -1407,21 +1433,20 @@ Neighborhood VariableGraphNeighborhoodGenerator::Generate(
14071433
{
14081434
absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
14091435

1436+
std::vector<int> initial_vars =
1437+
helper_.ImprovableObjectiveVariablesWhileHoldingLock(initial_solution);
1438+
if (initial_vars.empty()) {
1439+
initial_vars = helper_.ActiveVariablesWhileHoldingLock();
1440+
}
14101441
// The number of active variables can decrease asynchronously.
14111442
// We read the exact number while locked.
14121443
const int num_active_vars =
14131444
helper_.ActiveVariablesWhileHoldingLock().size();
1414-
const int num_objective_variables =
1415-
helper_.ActiveObjectiveVariablesWhileHoldingLock().size();
14161445
const int target_size = std::ceil(data.difficulty * num_active_vars);
14171446
if (target_size == num_active_vars) return helper_.FullNeighborhood();
14181447

14191448
const int first_var =
1420-
num_objective_variables > 0 // Prefer objective variables.
1421-
? helper_.ActiveObjectiveVariablesWhileHoldingLock()
1422-
[absl::Uniform<int>(random, 0, num_objective_variables)]
1423-
: helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
1424-
random, 0, num_active_vars)];
1449+
initial_vars[absl::Uniform<int>(random, 0, initial_vars.size())];
14251450
visited_variables_set[first_var] = true;
14261451
visited_variables.push_back(first_var);
14271452
relaxed_variables.push_back(first_var);
@@ -1478,7 +1503,8 @@ Neighborhood ArcGraphNeighborhoodGenerator::Generate(
14781503
{
14791504
absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
14801505
num_active_vars = helper_.ActiveVariablesWhileHoldingLock().size();
1481-
active_objective_vars = helper_.ActiveObjectiveVariablesWhileHoldingLock();
1506+
active_objective_vars =
1507+
helper_.ImprovableObjectiveVariablesWhileHoldingLock(initial_solution);
14821508
constraints_to_vars = helper_.ConstraintToVar();
14831509
vars_to_constraints = helper_.VarToConstraint();
14841510
}
@@ -1569,13 +1595,12 @@ Neighborhood ConstraintGraphNeighborhoodGenerator::Generate(
15691595
const int target_size = std::ceil(data.difficulty * num_active_vars);
15701596
if (target_size == num_active_vars) return helper_.FullNeighborhood();
15711597

1572-
// Start by a random constraint.
1598+
// Start from a random active constraint.
15731599
const int num_active_constraints = helper_.ConstraintToVar().size();
1574-
if (num_active_constraints != 0) {
1575-
next_constraints.push_back(
1576-
absl::Uniform<int>(random, 0, num_active_constraints));
1577-
added_constraints[next_constraints.back()] = true;
1578-
}
1600+
if (num_active_constraints == 0) return helper_.NoNeighborhood();
1601+
next_constraints.push_back(
1602+
absl::Uniform<int>(random, 0, num_active_constraints));
1603+
added_constraints[next_constraints.back()] = true;
15791604

15801605
while (relaxed_variables.size() < target_size) {
15811606
// Stop if we have a full connected component.
@@ -1667,9 +1692,9 @@ Neighborhood DecompositionGraphNeighborhoodGenerator::Generate(
16671692
elements[i].tie_break = absl::Uniform<double>(random, 0.0, 1.0);
16681693
}
16691694

1670-
// We start by a random active variable.
1695+
// We start from a random active variable.
16711696
//
1672-
// Note that while num_vars contains all variables, all the fixed variable
1697+
// Note that while num_vars contains all variables, all the fixed variables
16731698
// will have no associated constraint, so we don't want to start from a
16741699
// random variable.
16751700
//

‎ortools/sat/cp_model_lns.h

+13-2
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,14 @@ class NeighborhoodGeneratorHelper : public SubSolver {
195195
return result;
196196
}
197197

198+
// Returns the vector of objective variables that are not already at their
199+
// best possible value. The graph_mutex_ must be locked before calling this
200+
// method.
201+
std::vector<int> ImprovableObjectiveVariablesWhileHoldingLock(
202+
const CpSolverResponse& initial_solution) const
203+
ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_)
204+
ABSL_LOCKS_EXCLUDED(domain_mutex_);
205+
198206
// Constraints <-> Variables graph.
199207
// Important:
200208
// - The constraint index is NOT related to the one in the cp_model.
@@ -332,9 +340,12 @@ class NeighborhoodGeneratorHelper : public SubSolver {
332340
// Constraints by types. This never changes.
333341
std::vector<std::vector<int>> type_to_constraints_;
334342

335-
// Whether a model_proto_ variable appear in the objective. This never
343+
// Whether a model_proto_ variable appears in the objective. This never
336344
// changes.
337345
std::vector<bool> is_in_objective_;
346+
// If a model_proto_ variable has a positive coefficient in the objective.
347+
// This never changes.
348+
std::vector<bool> has_positive_objective_coefficient_;
338349

339350
// A copy of CpModelProto where we did some basic presolving to remove all
340351
// constraint that are always true. The Variable-Constraint graph is based on
@@ -368,7 +379,7 @@ class NeighborhoodGeneratorHelper : public SubSolver {
368379

369380
std::vector<int> tmp_row_;
370381

371-
mutable absl::Mutex domain_mutex_;
382+
mutable absl::Mutex domain_mutex_ ABSL_ACQUIRED_AFTER(graph_mutex_);
372383
};
373384

374385
// Base class for a CpModelProto neighborhood generator.

‎ortools/sat/cp_model_presolve.cc

+4
Original file line numberDiff line numberDiff line change
@@ -7731,6 +7731,8 @@ void CpModelPresolver::Probe() {
77317731
return (void)context_->NotifyThatModelIsUnsat("during probing");
77327732
}
77337733

7734+
time_limit_->ResetHistory();
7735+
77347736
// Update the presolve context with fixed Boolean variables.
77357737
int num_fixed = 0;
77367738
CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
@@ -8663,6 +8665,7 @@ void CpModelPresolver::MergeNoOverlapConstraints() {
86638665
// We reuse the max-clique code from sat.
86648666
Model local_model;
86658667
local_model.GetOrCreate<Trail>()->Resize(num_constraints);
8668+
local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
86668669
auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
86678670
graph->Resize(num_constraints);
86688671
for (const std::vector<Literal>& clique : cliques) {
@@ -8699,6 +8702,7 @@ void CpModelPresolver::MergeNoOverlapConstraints() {
86998702
new_num_intervals, " intervals).");
87008703
context_->UpdateRuleStats("no_overlap: merged constraints");
87018704
}
8705+
time_limit_->ResetHistory();
87028706
}
87038707

87048708
// TODO(user): Should we take into account the exactly_one constraints? note

‎ortools/sat/cp_model_solver_helpers.cc

-19
Original file line numberDiff line numberDiff line change
@@ -88,25 +88,6 @@
8888
#include "ortools/util/strong_integers.h"
8989
#include "ortools/util/time_limit.h"
9090

91-
ABSL_FLAG(bool, cp_model_dump_models, false,
92-
"DEBUG ONLY. When set to true, SolveCpModel() will dump its model "
93-
"protos (original model, presolved model, mapping model) in text "
94-
"format to 'FLAGS_cp_model_dump_prefix'{model|presolved_model|"
95-
"mapping_model}.pb.txt.");
96-
97-
#if defined(_MSC_VER)
98-
ABSL_FLAG(std::string, cp_model_dump_prefix, ".\\",
99-
"Prefix filename for all dumped files");
100-
#else
101-
ABSL_FLAG(std::string, cp_model_dump_prefix, "/tmp/",
102-
"Prefix filename for all dumped files");
103-
#endif
104-
105-
ABSL_FLAG(bool, cp_model_dump_submodels, false,
106-
"DEBUG ONLY. When set to true, solve will dump all "
107-
"lns or objective_shaving submodels proto in text format to "
108-
"'FLAGS_cp_model_dump_prefix'xxx.pb.txt.");
109-
11091
ABSL_FLAG(
11192
std::string, cp_model_load_debug_solution, "",
11293
"DEBUG ONLY. When this is set to a non-empty file name, "

‎ortools/sat/cp_model_solver_helpers.h

-8
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,9 @@
1616

1717
#include <cstdint>
1818
#include <memory>
19-
#include <string>
20-
#include <tuple>
2119
#include <utility>
2220
#include <vector>
2321

24-
#include "absl/flags/declare.h"
2522
#include "absl/types/span.h"
2623
#include "ortools/base/timer.h"
2724
#include "ortools/sat/cp_model.pb.h"
@@ -34,11 +31,6 @@
3431
#include "ortools/sat/work_assignment.h"
3532
#include "ortools/util/logging.h"
3633

37-
ABSL_DECLARE_FLAG(bool, cp_model_dump_models);
38-
ABSL_DECLARE_FLAG(std::string, cp_model_dump_prefix);
39-
ABSL_DECLARE_FLAG(bool, cp_model_dump_problematic_lns);
40-
ABSL_DECLARE_FLAG(bool, cp_model_dump_submodels);
41-
4234
namespace operations_research {
4335
namespace sat {
4436

‎ortools/sat/cp_model_utils.cc

+20
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#include "absl/container/flat_hash_map.h"
2525
#include "absl/container/flat_hash_set.h"
26+
#include "absl/flags/flag.h"
2627
#include "absl/log/check.h"
2728
#include "absl/strings/str_cat.h"
2829
#include "absl/strings/string_view.h"
@@ -36,6 +37,25 @@
3637
#include "ortools/util/saturated_arithmetic.h"
3738
#include "ortools/util/sorted_interval_list.h"
3839

40+
ABSL_FLAG(bool, cp_model_dump_models, false,
41+
"DEBUG ONLY. When set to true, SolveCpModel() will dump its model "
42+
"protos (original model, presolved model, mapping model) in text "
43+
"format to 'FLAGS_cp_model_dump_prefix'{model|presolved_model|"
44+
"mapping_model}.pb.txt.");
45+
46+
#if defined(_MSC_VER)
47+
ABSL_FLAG(std::string, cp_model_dump_prefix, ".\\",
48+
"Prefix filename for all dumped files");
49+
#else
50+
ABSL_FLAG(std::string, cp_model_dump_prefix, "/tmp/",
51+
"Prefix filename for all dumped files");
52+
#endif
53+
54+
ABSL_FLAG(bool, cp_model_dump_submodels, false,
55+
"DEBUG ONLY. When set to true, solve will dump all "
56+
"lns or objective_shaving submodels proto in text format to "
57+
"'FLAGS_cp_model_dump_prefix'xxx.pb.txt.");
58+
3959
namespace operations_research {
4060
namespace sat {
4161

‎ortools/sat/cp_model_utils.h

+6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#if !defined(__PORTABLE_PLATFORM__)
2525
#include "ortools/base/helpers.h"
2626
#endif // !defined(__PORTABLE_PLATFORM__)
27+
#include "absl/flags/declare.h"
2728
#include "absl/log/check.h"
2829
#include "absl/status/status.h"
2930
#include "absl/strings/match.h"
@@ -37,6 +38,11 @@
3738
#include "ortools/util/bitset.h"
3839
#include "ortools/util/sorted_interval_list.h"
3940

41+
ABSL_DECLARE_FLAG(bool, cp_model_dump_models);
42+
ABSL_DECLARE_FLAG(std::string, cp_model_dump_prefix);
43+
ABSL_DECLARE_FLAG(bool, cp_model_dump_problematic_lns);
44+
ABSL_DECLARE_FLAG(bool, cp_model_dump_submodels);
45+
4046
namespace operations_research {
4147
namespace sat {
4248

‎ortools/sat/precedences_test.cc

+5-2
Original file line numberDiff line numberDiff line change
@@ -501,8 +501,11 @@ std::vector<Relation> GetRelations(Model& model) {
501501
for (int i = 0; i < repository.size(); ++i) {
502502
Relation r = repository.relation(i);
503503
if (r.a.coeff < 0) {
504-
r = Relation({r.enforcement, {r.a.var, -r.a.coeff}, {r.b.var, -r.b.coeff},
505-
-r.rhs, -r.lhs});
504+
r = Relation({r.enforcement,
505+
{r.a.var, -r.a.coeff},
506+
{r.b.var, -r.b.coeff},
507+
-r.rhs,
508+
-r.lhs});
506509
}
507510
relations.push_back(r);
508511
}

0 commit comments

Comments
 (0)
Please sign in to comment.