[CP-SAT] more work on routing cuts; graph based LNS start from the objective; better enforcement of time limits
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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())) {
|
||||
|
||||
@@ -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<int>
|
||||
NeighborhoodGeneratorHelper::ImprovableObjectiveVariablesWhileHoldingLock(
|
||||
const CpSolverResponse& initial_solution) const {
|
||||
std::vector<int> 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 <class T>
|
||||
@@ -1407,21 +1433,20 @@ Neighborhood VariableGraphNeighborhoodGenerator::Generate(
|
||||
{
|
||||
absl::ReaderMutexLock graph_lock(&helper_.graph_mutex_);
|
||||
|
||||
std::vector<int> 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<int>(random, 0, num_objective_variables)]
|
||||
: helper_.ActiveVariablesWhileHoldingLock()[absl::Uniform<int>(
|
||||
random, 0, num_active_vars)];
|
||||
initial_vars[absl::Uniform<int>(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<int>(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<int>(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<double>(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.
|
||||
//
|
||||
|
||||
@@ -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<int> 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<std::vector<int>> 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<bool> is_in_objective_;
|
||||
// If a model_proto_ variable has a positive coefficient in the objective.
|
||||
// This never changes.
|
||||
std::vector<bool> 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<int> tmp_row_;
|
||||
|
||||
mutable absl::Mutex domain_mutex_;
|
||||
mutable absl::Mutex domain_mutex_ ABSL_ACQUIRED_AFTER(graph_mutex_);
|
||||
};
|
||||
|
||||
// Base class for a CpModelProto neighborhood generator.
|
||||
|
||||
@@ -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<Trail>()->Resize(num_constraints);
|
||||
local_model.GetOrCreate<TimeLimit>()->MergeWithGlobalTimeLimit(time_limit_);
|
||||
auto* graph = local_model.GetOrCreate<BinaryImplicationGraph>();
|
||||
graph->Resize(num_constraints);
|
||||
for (const std::vector<Literal>& 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
|
||||
|
||||
@@ -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, "
|
||||
|
||||
@@ -16,12 +16,9 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -501,8 +501,11 @@ std::vector<Relation> 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);
|
||||
}
|
||||
|
||||
87
ortools/sat/render_routes_support_graph.cc
Normal file
87
ortools/sat/render_routes_support_graph.cc
Normal file
@@ -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 <cstdlib>
|
||||
#include <string>
|
||||
|
||||
#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;
|
||||
}
|
||||
29
ortools/sat/routes_support_graph.proto
Normal file
29
ortools/sat/routes_support_graph.proto
Normal file
@@ -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;
|
||||
}
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "ortools/sat/routing_cuts.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
@@ -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<IntegerVariable> implied_views;
|
||||
absl::flat_hash_set<IntegerVariable> negated_implied_views;
|
||||
absl::flat_hash_map<IntegerVariable, IntegerValue> 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<int> 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 {
|
||||
|
||||
@@ -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<int> tails = {0};
|
||||
const std::vector<int> heads = {1};
|
||||
const std::vector<Literal> 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<IntegerEncoder>();
|
||||
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<RouteRelationsHelper> 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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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<Number>::Add(Number value) {
|
||||
}
|
||||
}
|
||||
|
||||
template <class Number>
|
||||
void RunningMax<Number>::Reset() {
|
||||
values_.clear();
|
||||
last_index_ = 0;
|
||||
max_index_ = 0;
|
||||
}
|
||||
|
||||
template <class Number>
|
||||
Number RunningMax<Number>::GetCurrentMax() {
|
||||
DCHECK(!values_.empty());
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user