diff --git a/ortools/sat/BUILD.bazel b/ortools/sat/BUILD.bazel index 0788a2581f..c501ef87f4 100644 --- a/ortools/sat/BUILD.bazel +++ b/ortools/sat/BUILD.bazel @@ -209,6 +209,7 @@ cc_library( "//ortools/util:sorted_interval_list", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/log:check", "@com_google_absl//absl/status", "@com_google_absl//absl/strings", @@ -2709,6 +2710,33 @@ cc_test( ], ) +proto_library( + name = "routes_support_graph_proto", + srcs = ["routes_support_graph.proto"], +) + +cc_proto_library( + name = "routes_support_graph_cc_proto", + deps = [":routes_support_graph_proto"], +) + +cc_binary( + name = "render_routes_support_graph", + srcs = ["render_routes_support_graph.cc"], + deps = [ + ":routes_support_graph_cc_proto", + "//ortools/base", + "//ortools/base:file", + "//ortools/routing/parsers:solomon_parser", + "//ortools/util:file_util", + "@com_google_absl//absl/flags:flag", + "@com_google_absl//absl/log", + "@com_google_absl//absl/log:check", + "@com_google_absl//absl/strings", + "@com_google_absl//absl/strings:string_view", + ], +) + cc_library( name = "routing_cuts", srcs = ["routing_cuts.cc"], @@ -2724,7 +2752,9 @@ cc_library( ":linear_constraint_manager", ":model", ":precedences", + ":routes_support_graph_cc_proto", ":sat_base", + ":sat_parameters_cc_proto", ":synchronization", ":util", "//ortools/base", @@ -2734,12 +2764,12 @@ cc_library( "//ortools/graph", "//ortools/graph:connected_components", "//ortools/graph:max_flow", - "//ortools/util:bitset", "//ortools/util:strong_integers", "@com_google_absl//absl/algorithm:container", "@com_google_absl//absl/cleanup", "@com_google_absl//absl/container:flat_hash_map", "@com_google_absl//absl/container:flat_hash_set", + "@com_google_absl//absl/flags:flag", "@com_google_absl//absl/log", "@com_google_absl//absl/log:check", "@com_google_absl//absl/log:vlog_is_on", diff --git a/ortools/sat/CMakeLists.txt b/ortools/sat/CMakeLists.txt index e49cc257ce..96dd774b4c 100644 --- a/ortools/sat/CMakeLists.txt +++ b/ortools/sat/CMakeLists.txt @@ -18,6 +18,7 @@ list(REMOVE_ITEM _SRCS ${CMAKE_CURRENT_SOURCE_DIR}/opb_reader.h ${CMAKE_CURRENT_SOURCE_DIR}/sat_cnf_reader.h ${CMAKE_CURRENT_SOURCE_DIR}/sat_runner.cc + ${CMAKE_CURRENT_SOURCE_DIR}/render_routes_support_graph.cc ) set(NAME ${PROJECT_NAME}_sat) diff --git a/ortools/sat/cp_model_checker.cc b/ortools/sat/cp_model_checker.cc index 58f3102e04..5bd956e947 100644 --- a/ortools/sat/cp_model_checker.cc +++ b/ortools/sat/cp_model_checker.cc @@ -505,8 +505,8 @@ std::string ValidateElementConstraint(const CpModelProto& model, RETURN_IF_NOT_EMPTY(ValidateAffineExpression(model, expr)); LinearExpressionProto overflow_detection = ct.element().linear_target(); AppendToOverflowValidator(expr, &overflow_detection, -1); - overflow_detection.set_offset(overflow_detection.offset() - - expr.offset()); + const int64_t offset = CapSub(overflow_detection.offset(), expr.offset()); + overflow_detection.set_offset(offset); if (PossibleIntegerOverflow(model, overflow_detection.vars(), overflow_detection.coeffs(), overflow_detection.offset())) { diff --git a/ortools/sat/cp_model_lns.cc b/ortools/sat/cp_model_lns.cc index 3b73c0f2bd..dd598c65c2 100644 --- a/ortools/sat/cp_model_lns.cc +++ b/ortools/sat/cp_model_lns.cc @@ -197,9 +197,15 @@ void NeighborhoodGeneratorHelper::InitializeHelperData() { const int num_variables = model_proto_.variables().size(); is_in_objective_.resize(num_variables, false); + has_positive_objective_coefficient_.resize(num_variables, false); if (model_proto_.has_objective()) { - for (const int ref : model_proto_.objective().vars()) { + for (int i = 0; i < model_proto_.objective().vars_size(); ++i) { + const int ref = model_proto_.objective().vars(i); + const int64_t coeff = model_proto_.objective().coeffs(i); + DCHECK_NE(coeff, 0); is_in_objective_[PositiveRef(ref)] = true; + has_positive_objective_coefficient_[PositiveRef(ref)] = + ref == PositiveRef(ref) ? coeff > 0 : coeff < 0; } } } @@ -1318,6 +1324,26 @@ double NeighborhoodGenerator::Synchronize() { return total_dtime; } +std::vector +NeighborhoodGeneratorHelper::ImprovableObjectiveVariablesWhileHoldingLock( + const CpSolverResponse& initial_solution) const { + std::vector result; + absl::ReaderMutexLock lock(&domain_mutex_); + for (const int var : active_objective_variables_) { + const auto& domain = + model_proto_with_only_variables_.variables(var).domain(); + bool at_best_value = false; + if (has_positive_objective_coefficient_[var]) { + at_best_value = initial_solution.solution(var) == domain[0]; + } else { + at_best_value = + initial_solution.solution(var) == domain[domain.size() - 1]; + } + if (!at_best_value) result.push_back(var); + } + return result; +} + namespace { template @@ -1407,21 +1433,20 @@ Neighborhood VariableGraphNeighborhoodGenerator::Generate( { absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_); + std::vector initial_vars = + helper_.ImprovableObjectiveVariablesWhileHoldingLock(initial_solution); + if (initial_vars.empty()) { + initial_vars = helper_.ActiveVariablesWhileHoldingLock(); + } // The number of active variables can decrease asynchronously. // We read the exact number while locked. const int num_active_vars = helper_.ActiveVariablesWhileHoldingLock().size(); - const int num_objective_variables = - helper_.ActiveObjectiveVariablesWhileHoldingLock().size(); const int target_size = std::ceil(data.difficulty * num_active_vars); if (target_size == num_active_vars) return helper_.FullNeighborhood(); const int first_var = - num_objective_variables > 0 // Prefer objective variables. - ? helper_.ActiveObjectiveVariablesWhileHoldingLock() - [absl::Uniform(random, 0, num_objective_variables)] - : helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform( - random, 0, num_active_vars)]; + initial_vars[absl::Uniform(random, 0, initial_vars.size())]; visited_variables_set[first_var] = true; visited_variables.push_back(first_var); relaxed_variables.push_back(first_var); @@ -1478,7 +1503,8 @@ Neighborhood ArcGraphNeighborhoodGenerator::Generate( { absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_); num_active_vars = helper_.ActiveVariablesWhileHoldingLock().size(); - active_objective_vars = helper_.ActiveObjectiveVariablesWhileHoldingLock(); + active_objective_vars = + helper_.ImprovableObjectiveVariablesWhileHoldingLock(initial_solution); constraints_to_vars = helper_.ConstraintToVar(); vars_to_constraints = helper_.VarToConstraint(); } @@ -1569,13 +1595,12 @@ Neighborhood ConstraintGraphNeighborhoodGenerator::Generate( const int target_size = std::ceil(data.difficulty * num_active_vars); if (target_size == num_active_vars) return helper_.FullNeighborhood(); - // Start by a random constraint. + // Start from a random active constraint. const int num_active_constraints = helper_.ConstraintToVar().size(); - if (num_active_constraints != 0) { - next_constraints.push_back( - absl::Uniform(random, 0, num_active_constraints)); - added_constraints[next_constraints.back()] = true; - } + if (num_active_constraints == 0) return helper_.NoNeighborhood(); + next_constraints.push_back( + absl::Uniform(random, 0, num_active_constraints)); + added_constraints[next_constraints.back()] = true; while (relaxed_variables.size() < target_size) { // Stop if we have a full connected component. @@ -1667,9 +1692,9 @@ Neighborhood DecompositionGraphNeighborhoodGenerator::Generate( elements[i].tie_break = absl::Uniform(random, 0.0, 1.0); } - // We start by a random active variable. + // We start from a random active variable. // - // Note that while num_vars contains all variables, all the fixed variable + // Note that while num_vars contains all variables, all the fixed variables // will have no associated constraint, so we don't want to start from a // random variable. // diff --git a/ortools/sat/cp_model_lns.h b/ortools/sat/cp_model_lns.h index 08b837c9ab..a153789358 100644 --- a/ortools/sat/cp_model_lns.h +++ b/ortools/sat/cp_model_lns.h @@ -195,6 +195,14 @@ class NeighborhoodGeneratorHelper : public SubSolver { return result; } + // Returns the vector of objective variables that are not already at their + // best possible value. The graph_mutex_ must be locked before calling this + // method. + std::vector ImprovableObjectiveVariablesWhileHoldingLock( + const CpSolverResponse& initial_solution) const + ABSL_SHARED_LOCKS_REQUIRED(graph_mutex_) + ABSL_LOCKS_EXCLUDED(domain_mutex_); + // Constraints <-> Variables graph. // Important: // - The constraint index is NOT related to the one in the cp_model. @@ -332,9 +340,12 @@ class NeighborhoodGeneratorHelper : public SubSolver { // Constraints by types. This never changes. std::vector> type_to_constraints_; - // Whether a model_proto_ variable appear in the objective. This never + // Whether a model_proto_ variable appears in the objective. This never // changes. std::vector is_in_objective_; + // If a model_proto_ variable has a positive coefficient in the objective. + // This never changes. + std::vector has_positive_objective_coefficient_; // A copy of CpModelProto where we did some basic presolving to remove all // constraint that are always true. The Variable-Constraint graph is based on @@ -368,7 +379,7 @@ class NeighborhoodGeneratorHelper : public SubSolver { std::vector tmp_row_; - mutable absl::Mutex domain_mutex_; + mutable absl::Mutex domain_mutex_ ABSL_ACQUIRED_AFTER(graph_mutex_); }; // Base class for a CpModelProto neighborhood generator. diff --git a/ortools/sat/cp_model_presolve.cc b/ortools/sat/cp_model_presolve.cc index 2cfb421292..2dda421482 100644 --- a/ortools/sat/cp_model_presolve.cc +++ b/ortools/sat/cp_model_presolve.cc @@ -7731,6 +7731,8 @@ void CpModelPresolver::Probe() { return (void)context_->NotifyThatModelIsUnsat("during probing"); } + time_limit_->ResetHistory(); + // Update the presolve context with fixed Boolean variables. int num_fixed = 0; CHECK_EQ(sat_solver->CurrentDecisionLevel(), 0); @@ -8663,6 +8665,7 @@ void CpModelPresolver::MergeNoOverlapConstraints() { // We reuse the max-clique code from sat. Model local_model; local_model.GetOrCreate()->Resize(num_constraints); + local_model.GetOrCreate()->MergeWithGlobalTimeLimit(time_limit_); auto* graph = local_model.GetOrCreate(); graph->Resize(num_constraints); for (const std::vector& clique : cliques) { @@ -8699,6 +8702,7 @@ void CpModelPresolver::MergeNoOverlapConstraints() { new_num_intervals, " intervals)."); context_->UpdateRuleStats("no_overlap: merged constraints"); } + time_limit_->ResetHistory(); } // TODO(user): Should we take into account the exactly_one constraints? note diff --git a/ortools/sat/cp_model_solver_helpers.cc b/ortools/sat/cp_model_solver_helpers.cc index 75be23cd4f..9e013c5d33 100644 --- a/ortools/sat/cp_model_solver_helpers.cc +++ b/ortools/sat/cp_model_solver_helpers.cc @@ -88,25 +88,6 @@ #include "ortools/util/strong_integers.h" #include "ortools/util/time_limit.h" -ABSL_FLAG(bool, cp_model_dump_models, false, - "DEBUG ONLY. When set to true, SolveCpModel() will dump its model " - "protos (original model, presolved model, mapping model) in text " - "format to 'FLAGS_cp_model_dump_prefix'{model|presolved_model|" - "mapping_model}.pb.txt."); - -#if defined(_MSC_VER) -ABSL_FLAG(std::string, cp_model_dump_prefix, ".\\", - "Prefix filename for all dumped files"); -#else -ABSL_FLAG(std::string, cp_model_dump_prefix, "/tmp/", - "Prefix filename for all dumped files"); -#endif - -ABSL_FLAG(bool, cp_model_dump_submodels, false, - "DEBUG ONLY. When set to true, solve will dump all " - "lns or objective_shaving submodels proto in text format to " - "'FLAGS_cp_model_dump_prefix'xxx.pb.txt."); - ABSL_FLAG( std::string, cp_model_load_debug_solution, "", "DEBUG ONLY. When this is set to a non-empty file name, " diff --git a/ortools/sat/cp_model_solver_helpers.h b/ortools/sat/cp_model_solver_helpers.h index ed0d29c2ee..1f46f77495 100644 --- a/ortools/sat/cp_model_solver_helpers.h +++ b/ortools/sat/cp_model_solver_helpers.h @@ -16,12 +16,9 @@ #include #include -#include -#include #include #include -#include "absl/flags/declare.h" #include "absl/types/span.h" #include "ortools/base/timer.h" #include "ortools/sat/cp_model.pb.h" @@ -34,11 +31,6 @@ #include "ortools/sat/work_assignment.h" #include "ortools/util/logging.h" -ABSL_DECLARE_FLAG(bool, cp_model_dump_models); -ABSL_DECLARE_FLAG(std::string, cp_model_dump_prefix); -ABSL_DECLARE_FLAG(bool, cp_model_dump_problematic_lns); -ABSL_DECLARE_FLAG(bool, cp_model_dump_submodels); - namespace operations_research { namespace sat { diff --git a/ortools/sat/cp_model_utils.cc b/ortools/sat/cp_model_utils.cc index cefc28fef0..6af621ec36 100644 --- a/ortools/sat/cp_model_utils.cc +++ b/ortools/sat/cp_model_utils.cc @@ -23,6 +23,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" +#include "absl/flags/flag.h" #include "absl/log/check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -36,6 +37,25 @@ #include "ortools/util/saturated_arithmetic.h" #include "ortools/util/sorted_interval_list.h" +ABSL_FLAG(bool, cp_model_dump_models, false, + "DEBUG ONLY. When set to true, SolveCpModel() will dump its model " + "protos (original model, presolved model, mapping model) in text " + "format to 'FLAGS_cp_model_dump_prefix'{model|presolved_model|" + "mapping_model}.pb.txt."); + +#if defined(_MSC_VER) +ABSL_FLAG(std::string, cp_model_dump_prefix, ".\\", + "Prefix filename for all dumped files"); +#else +ABSL_FLAG(std::string, cp_model_dump_prefix, "/tmp/", + "Prefix filename for all dumped files"); +#endif + +ABSL_FLAG(bool, cp_model_dump_submodels, false, + "DEBUG ONLY. When set to true, solve will dump all " + "lns or objective_shaving submodels proto in text format to " + "'FLAGS_cp_model_dump_prefix'xxx.pb.txt."); + namespace operations_research { namespace sat { diff --git a/ortools/sat/cp_model_utils.h b/ortools/sat/cp_model_utils.h index e49d91da66..e00d556306 100644 --- a/ortools/sat/cp_model_utils.h +++ b/ortools/sat/cp_model_utils.h @@ -24,6 +24,7 @@ #if !defined(__PORTABLE_PLATFORM__) #include "ortools/base/helpers.h" #endif // !defined(__PORTABLE_PLATFORM__) +#include "absl/flags/declare.h" #include "absl/log/check.h" #include "absl/status/status.h" #include "absl/strings/match.h" @@ -37,6 +38,11 @@ #include "ortools/util/bitset.h" #include "ortools/util/sorted_interval_list.h" +ABSL_DECLARE_FLAG(bool, cp_model_dump_models); +ABSL_DECLARE_FLAG(std::string, cp_model_dump_prefix); +ABSL_DECLARE_FLAG(bool, cp_model_dump_problematic_lns); +ABSL_DECLARE_FLAG(bool, cp_model_dump_submodels); + namespace operations_research { namespace sat { diff --git a/ortools/sat/precedences_test.cc b/ortools/sat/precedences_test.cc index 630872ba2e..994abc248c 100644 --- a/ortools/sat/precedences_test.cc +++ b/ortools/sat/precedences_test.cc @@ -501,8 +501,11 @@ std::vector GetRelations(Model& model) { for (int i = 0; i < repository.size(); ++i) { Relation r = repository.relation(i); if (r.a.coeff < 0) { - r = Relation({r.enforcement, {r.a.var, -r.a.coeff}, {r.b.var, -r.b.coeff}, - -r.rhs, -r.lhs}); + r = Relation({r.enforcement, + {r.a.var, -r.a.coeff}, + {r.b.var, -r.b.coeff}, + -r.rhs, + -r.lhs}); } relations.push_back(r); } diff --git a/ortools/sat/render_routes_support_graph.cc b/ortools/sat/render_routes_support_graph.cc new file mode 100644 index 0000000000..0182ef608f --- /dev/null +++ b/ortools/sat/render_routes_support_graph.cc @@ -0,0 +1,87 @@ +// 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 +#include + +#include "absl/flags/flag.h" +#include "absl/log/check.h" +#include "absl/log/log.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "ortools/base/helpers.h" +#include "ortools/base/init_google.h" +#include "ortools/base/options.h" +#include "ortools/routing/parsers/solomon_parser.h" +#include "ortools/sat/routes_support_graph.pb.h" +#include "ortools/util/file_util.h" + +ABSL_FLAG(std::string, input, "", + "Name of the file containing the input data of the problem, in" + " Solomon format."); +ABSL_FLAG(std::string, support_graph, "", + "Name of a RoutesSupportGraphProto file for this problem."); +ABSL_FLAG(std::string, output, "", "Name of the output DOT file."); + +namespace operations_research { +namespace sat { +namespace { + +void Run() { + if (absl::GetFlag(FLAGS_input).empty()) { + LOG(QFATAL) << "Please supply a solomon input file with --input="; + } + if (absl::GetFlag(FLAGS_support_graph).empty()) { + LOG(QFATAL) << "Please supply a support graph file with --support_graph="; + } + if (absl::GetFlag(FLAGS_output).empty()) { + LOG(QFATAL) << "Please supply a DOT output file with --output="; + } + + routing::SolomonParser parser; + CHECK(parser.LoadFile(absl::GetFlag(FLAGS_input))); + RoutesSupportGraphProto support_graph; + CHECK_OK(ReadFileToProto(absl::GetFlag(FLAGS_support_graph), &support_graph)); + + std::string dot = "digraph {\n"; + absl::StrAppend(&dot, " graph [splines=\"true\"];\n"); + const auto& coordinates = parser.coordinates(); + for (int i = 0; i < coordinates.size(); ++i) { + absl::StrAppend(&dot, " ", i, " [label=", i, " pos=\"", coordinates[i].x, + ",", coordinates[i].y, "!\"];\n"); + } + for (const auto& arc : support_graph.arc_lp_values()) { + absl::StrAppend(&dot, " ", arc.tail(), " -> ", arc.head(), " [label=\"", + arc.lp_value(), "\"];\n"); + } + absl::StrAppend(&dot, "}\n"); + CHECK_OK( + file::SetContents(absl::GetFlag(FLAGS_output), dot, file::Defaults())); +} + +} // namespace +} // namespace sat +} // namespace operations_research + +static const char kUsage[] = + "Usage: see flags.\nThis utility converts a RoutesSupportGraphProto file " + "to a DOT file, using the node coordinates from the Solomon input file. It " + "assumes that the cut file was generated with " + "//ortools/bench/solomon:solomon_run with the " + "--cp_model_dump_routes_support_graphs flag."; + +int main(int argc, char** argv) { + InitGoogle(kUsage, &argc, &argv, /*remove_flags=*/true); + operations_research::sat::Run(); + return EXIT_SUCCESS; +} diff --git a/ortools/sat/routes_support_graph.proto b/ortools/sat/routes_support_graph.proto new file mode 100644 index 0000000000..2ae472b9f5 --- /dev/null +++ b/ortools/sat/routes_support_graph.proto @@ -0,0 +1,29 @@ +// 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. + +syntax = "proto2"; + +package operations_research.sat; + +// An arc of a routes constraint, with its LP value. +message ArcLpValue { + optional int32 tail = 1; + optional int32 head = 2; + optional double lp_value = 3; +} + +// The arcs of a routes constraint which have non-zero LP values, in the LP +// relaxation of the problem. +message RoutesSupportGraphProto { + repeated ArcLpValue arc_lp_values = 1; +} diff --git a/ortools/sat/routing_cuts.cc b/ortools/sat/routing_cuts.cc index 459655414c..9b65333e82 100644 --- a/ortools/sat/routing_cuts.cc +++ b/ortools/sat/routing_cuts.cc @@ -14,6 +14,7 @@ #include "ortools/sat/routing_cuts.h" #include +#include #include #include #include @@ -32,6 +33,7 @@ #include "absl/cleanup/cleanup.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" +#include "absl/flags/flag.h" #include "absl/log/check.h" #include "absl/log/log.h" #include "absl/log/vlog_is_on.h" @@ -56,11 +58,20 @@ #include "ortools/sat/linear_constraint_manager.h" #include "ortools/sat/model.h" #include "ortools/sat/precedences.h" +#include "ortools/sat/routes_support_graph.pb.h" #include "ortools/sat/sat_base.h" +#include "ortools/sat/sat_parameters.pb.h" #include "ortools/sat/synchronization.h" #include "ortools/sat/util.h" #include "ortools/util/strong_integers.h" +ABSL_FLAG(bool, cp_model_dump_routes_support_graphs, false, + "DEBUG ONLY. When set to true, SolveCpModel() dumps the arcs with " + "non-zero LP values of the routes constraints, at decision level 0, " + "which are used to subsequently generate cuts. The values are " + "written as a SupportGraphProto in text format to " + "'FLAGS_cp_model_dump_prefix'support_graph_{counter}.pb.txt."); + namespace operations_research { namespace sat { @@ -1307,13 +1318,25 @@ class RouteRelationsBuilder { binary_implication_graph.WorkDone() < 1e8 ? binary_implication_graph.GetAllImpliedLiterals(literals_[i]) : absl::MakeSpan(arc_literal_singleton); - // The integer view of the implied literals (resp. of their negation). + // The integer view of the implied literals (resp. of their negation), and + // the variable lower bounds implied directly or indirectly by the arc + // literal. absl::flat_hash_set implied_views; absl::flat_hash_set negated_implied_views; + absl::flat_hash_map implied_lower_bounds; for (const Literal implied : implied_literals) { implied_views.insert(integer_encoder.GetLiteralView(implied)); negated_implied_views.insert( integer_encoder.GetLiteralView(implied.Negated())); + for (const auto& [var, lb] : + integer_encoder.GetIntegerLiterals(implied)) { + auto it = implied_lower_bounds.find(var); + if (it == implied_lower_bounds.end()) { + implied_lower_bounds[var] = lb; + } else { + it->second = std::max(it->second, lb); + } + } } // Returns the bounds of the given expression, assuming that all the // literals implied by the arc literal are 1. @@ -1333,8 +1356,17 @@ class RouteRelationsBuilder { } } const AffineExpression e(expr.var, expr.coeff, expr.offset); - return std ::make_pair(integer_trail.LevelZeroLowerBound(e), - integer_trail.LevelZeroUpperBound(e)); + IntegerValue lb = integer_trail.LevelZeroLowerBound(e); + auto it = implied_lower_bounds.find(e.var); + if (it != implied_lower_bounds.end()) { + lb = std::max(lb, e.ValueAt(it->second)); + } + IntegerValue ub = integer_trail.LevelZeroUpperBound(e); + it = implied_lower_bounds.find(NegationOf(e.var)); + if (it != implied_lower_bounds.end()) { + ub = std::min(ub, e.ValueAt(-it->second)); + } + return std::make_pair(lb, ub); }; // Changes `expr` to a constant expression if possible, and returns true. // Otherwise, returns false. @@ -2016,6 +2048,23 @@ void RoutingCutHelper::InitializeForNewLpSolution( } ordered_arcs_.push_back({tails_[arc], heads_[arc]}); } + + if (absl::GetFlag(FLAGS_cp_model_dump_routes_support_graphs) && + trail_.CurrentDecisionLevel() == 0) { + static std::atomic counter = 0; + const std::string name = + absl::StrCat(absl::GetFlag(FLAGS_cp_model_dump_prefix), + "support_graph_", counter++, ".pb.txt"); + LOG(INFO) << "Dumping routes support graph to '" << name << "'."; + RoutesSupportGraphProto support_graph_proto; + for (auto& [tail, head, lp_value] : relevant_arcs_) { + auto* arc = support_graph_proto.add_arc_lp_values(); + arc->set_tail(tail); + arc->set_head(head); + arc->set_lp_value(lp_value); + } + CHECK(WriteModelProtoToFile(support_graph_proto, name)); + } } namespace { diff --git a/ortools/sat/routing_cuts_test.cc b/ortools/sat/routing_cuts_test.cc index 6ad02632e1..ae4337bb0b 100644 --- a/ortools/sat/routing_cuts_test.cc +++ b/ortools/sat/routing_cuts_test.cc @@ -1518,6 +1518,38 @@ TEST(RouteRelationsHelperTest, ComplexVariableRelations) { EXPECT_EQ(helper->GetArcRelation(0, 0), (HeadMinusTailBounds{30, 190})); } +TEST(RouteRelationsHelperTest, TwoUnaryRelationsPerArc) { + Model model; + // A graph with 2 nodes and the following arcs: 0--l0-->1 + const int num_nodes = 2; + const std::vector tails = {0}; + const std::vector heads = {1}; + const std::vector literals = { + Literal(model.Add(NewBooleanVariable()), true)}; + // Add relations with "capacity" variables A and B, associated with nodes 0 + // and 1, respectively. + const IntegerVariable a = model.Add(NewIntegerVariable(0, 100)); + const IntegerVariable b = model.Add(NewIntegerVariable(0, 100)); + // Two unary relations on the same arc, one for the head and one for the tail. + IntegerEncoder& encoder = *model.GetOrCreate(); + encoder.AssociateToIntegerEqualValue(literals[0], a, 20); + encoder.AssociateToIntegerLiteral(literals[0], {b, 50}); + BinaryRelationRepository repository; + repository.Build(); + + const RoutingCumulExpressions cumuls = { + .num_dimensions = 0, + .flat_node_dim_expressions = {AffineExpression(a), AffineExpression(b)}}; + std::unique_ptr helper = RouteRelationsHelper::Create( + num_nodes, tails, heads, literals, cumuls.flat_node_dim_expressions, + repository, &model); + + ASSERT_NE(helper, nullptr); + // The implied unary relations b >= 50 and a = 20 should be used to compute + // the arc relation (50 - 20 = 30, ub(b) - 20 = 80). + EXPECT_EQ(helper->GetArcRelation(0, 0), (HeadMinusTailBounds{30, 80})); +} + TEST(RouteRelationsHelperTest, SeveralRelationsPerArc) { Model model; // A graph with 3 nodes and the following arcs: 0--l0-->1--l1-->2 diff --git a/ortools/sat/sat_parameters.proto b/ortools/sat/sat_parameters.proto index eab603696e..3cbc61e560 100644 --- a/ortools/sat/sat_parameters.proto +++ b/ortools/sat/sat_parameters.proto @@ -476,7 +476,7 @@ message SatParameters { optional bool expand_alldiff_constraints = 170 [default = false]; // Max domain size for all_different constraints to be expanded. - optional int32 max_alldiff_domain_size = 320 [default = 128]; + optional int32 max_alldiff_domain_size = 320 [default = 256]; // If true, expand the reservoir constraints by creating booleans for all // possible precedences between event and encoding the constraint. diff --git a/ortools/util/running_stat.h b/ortools/util/running_stat.h index e6422e0383..7eb7e0e301 100644 --- a/ortools/util/running_stat.h +++ b/ortools/util/running_stat.h @@ -83,6 +83,8 @@ class RunningMax { // An element must have been added before calling this function. Number GetCurrentMax(); + void Reset(); + private: const int window_size_; @@ -187,6 +189,13 @@ void RunningMax::Add(Number value) { } } +template +void RunningMax::Reset() { + values_.clear(); + last_index_ = 0; + max_index_ = 0; +} + template Number RunningMax::GetCurrentMax() { DCHECK(!values_.empty()); diff --git a/ortools/util/time_limit.h b/ortools/util/time_limit.h index eccb686d26..9762baa522 100644 --- a/ortools/util/time_limit.h +++ b/ortools/util/time_limit.h @@ -280,6 +280,14 @@ class OR_DLL TimeLimit { */ double GetDeterministicLimit() const { return deterministic_limit_; } + /** + * Clears the history of the times between calls to LimitReached(). One + * should call this method when the behavior of the code that checks the time + * limit changes in a way such that past intervals between checks are no + * longer representative of the future ones. + */ + void ResetHistory() { running_max_.Reset(); } + /** * Returns information about the time limit object in a human-readable form. */