[CP-SAT] more work on routing cuts; graph based LNS start from the objective; better enforcement of time limits

This commit is contained in:
Laurent Perron
2025-03-21 05:45:59 -07:00
parent 65da6ded39
commit 7a67706aa9
18 changed files with 342 additions and 55 deletions

View File

@@ -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",

View File

@@ -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)

View File

@@ -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())) {

View File

@@ -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.
//

View File

@@ -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.

View File

@@ -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

View File

@@ -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, "

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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);
}

View 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;
}

View 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;
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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.

View File

@@ -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());

View File

@@ -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.
*/