sync with main

This commit is contained in:
Laurent Perron
2025-06-02 14:25:50 +02:00
parent 6a10e3a7c7
commit 7f463e1a68
29 changed files with 464 additions and 260 deletions

View File

@@ -474,8 +474,8 @@ ABSL_MUST_USE_RESULT Status RevisedSimplex::SolveInternal(
// it is hard to claim we are really unbounded. This is a quick
// heuristic to error on the side of optimality rather than
// unboundedness.
double max_magnitude = 0.0;
double min_distance = kInfinity;
Fractional max_magnitude = 0.0;
Fractional min_distance = kInfinity;
const DenseRow& lower_bounds = variables_info_.GetVariableLowerBounds();
const DenseRow& upper_bounds = variables_info_.GetVariableUpperBounds();
Fractional cost_delta = 0.0;

View File

@@ -237,8 +237,8 @@ bool LPParser::ParseConstraint(StringPiece constraint) {
namespace {
template<class>
constexpr bool dependent_false = false; // workaround before CWG2518/P2593R1
template <class>
constexpr bool dependent_false = false; // workaround before CWG2518/P2593R1
template <typename T>
bool SimpleAtoFractional(absl::string_view str, T* value) {

View File

@@ -30,7 +30,6 @@
#include "ortools/sat/model.h"
#include "ortools/sat/no_overlap_2d_helper.h"
#include "ortools/sat/precedences.h"
#include "ortools/sat/sat_base.h"
#include "ortools/sat/scheduling_helpers.h"
#include "ortools/sat/synchronization.h"
@@ -58,10 +57,10 @@ void Precedences2DPropagator::CollectPairsOfBoxesWithNonTrivialDistance() {
for (int dim = 0; dim < 2; ++dim) {
const SchedulingConstraintHelper& dim_helper =
dim == 0 ? helper_.x_helper() : helper_.y_helper();
for (int i = 0; i < helper_.NumBoxes(); ++i) {
for (int j = 0; j < 2; ++j) {
const absl::Span<const AffineExpression> interval_points =
i == 0 ? dim_helper.Starts() : dim_helper.Ends();
for (int j = 0; j < 2; ++j) {
j == 0 ? dim_helper.Starts() : dim_helper.Ends();
for (int i = 0; i < helper_.NumBoxes(); ++i) {
if (interval_points[i].var != kNoIntegerVariable) {
var_to_box_and_coeffs[PositiveVariable(interval_points[i].var)]
.boxes[dim][j]
@@ -89,13 +88,14 @@ void Precedences2DPropagator::CollectPairsOfBoxesWithNonTrivialDistance() {
dim == 0 ? helper_.x_helper() : helper_.y_helper();
for (const int box1 : usage1.boxes[dim][0 /* start */]) {
for (const int box2 : usage2.boxes[dim][1 /* end */]) {
if (box1 == box2) continue;
const AffineExpression& start = dim_helper.Starts()[box1];
const AffineExpression& end = dim_helper.Ends()[box2];
LinearExpression2 expr2;
expr2.vars[0] = start.var;
expr2.vars[1] = NegationOf(end.var);
expr2.vars[1] = end.var;
expr2.coeffs[0] = start.coeff;
expr2.coeffs[1] = end.coeff;
expr2.coeffs[1] = -end.coeff;
expr2.SimpleCanonicalization();
expr2.DivideByGcd();
if (expr == expr2) {
@@ -133,7 +133,8 @@ bool Precedences2DPropagator::Propagate() {
for (const auto& [box1, box2] : non_trivial_pairs_) {
DCHECK(box1 < helper_.NumBoxes());
DCHECK(box2 < helper_.NumBoxes());
if (!helper_.IsPresent(box1) && !helper_.IsPresent(box2)) {
DCHECK_NE(box1, box2);
if (!helper_.IsPresent(box1) || !helper_.IsPresent(box2)) {
continue;
}
@@ -148,9 +149,9 @@ bool Precedences2DPropagator::Propagate() {
}
LinearExpression2 expr;
expr.vars[0] = helper->Starts()[b1].var;
expr.vars[1] = NegationOf(helper->Ends()[b2].var);
expr.vars[1] = helper->Ends()[b2].var;
expr.coeffs[0] = helper->Starts()[b1].coeff;
expr.coeffs[1] = helper->Ends()[b2].coeff;
expr.coeffs[1] = -helper->Ends()[b2].coeff;
const IntegerValue ub_of_start_minus_end_value =
binary_relations_maps_->UpperBound(expr) +
helper->Starts()[b1].constant - helper->Ends()[b2].constant;
@@ -158,8 +159,8 @@ bool Precedences2DPropagator::Propagate() {
is_unfeasible = false;
break;
}
if (!is_unfeasible) break;
}
if (!is_unfeasible) break;
}
if (!is_unfeasible) continue;
@@ -167,8 +168,6 @@ bool Precedences2DPropagator::Propagate() {
helper_.ClearReason();
num_conflicts_++;
std::vector<IntegerLiteral> reason;
std::vector<Literal> lit_reason;
for (int dim = 0; dim < 2; dim++) {
SchedulingConstraintHelper* helper = helpers[dim];
@@ -180,23 +179,18 @@ bool Precedences2DPropagator::Propagate() {
}
LinearExpression2 expr;
expr.vars[0] = helper->Starts()[b1].var;
expr.vars[1] = NegationOf(helper->Ends()[b2].var);
expr.vars[1] = helper->Ends()[b2].var;
expr.coeffs[0] = helper->Starts()[b1].coeff;
expr.coeffs[1] = helper->Ends()[b2].coeff;
expr.coeffs[1] = -helper->Ends()[b2].coeff;
binary_relations_maps_->AddReasonForUpperBoundLowerThan(
expr,
-(helper->Starts()[b1].constant - helper->Ends()[b2].constant),
&lit_reason, &reason);
-(helper->Starts()[b1].constant - helper->Ends()[b2].constant) - 1,
helper_.x_helper().MutableLiteralReason(),
helper_.x_helper().MutableIntegerReason());
}
}
helper_.AddPresenceReason(box1);
helper_.AddPresenceReason(box2);
helper_.x_helper().MutableIntegerReason()->insert(
helper_.x_helper().MutableIntegerReason()->end(), reason.begin(),
reason.end());
helper_.x_helper().MutableLiteralReason()->insert(
helper_.x_helper().MutableLiteralReason()->end(), lit_reason.begin(),
lit_reason.end());
return helper_.ReportConflict();
}
return true;
@@ -205,7 +199,7 @@ bool Precedences2DPropagator::Propagate() {
int Precedences2DPropagator::RegisterWith(GenericLiteralWatcher* watcher) {
const int id = watcher->Register(this);
helper_.WatchAllBoxes(id);
// TODO(user): add an API to BinaryRelationsMaps to watch linear2
binary_relations_maps_->WatchAllLinearExpressions2(id);
return id;
}

View File

@@ -1407,9 +1407,8 @@ void ProcessOneCompressedColumn(
// Simpler encoding for table constraints with 2 variables.
void AddSizeTwoTable(
const std::vector<int>& vars,
const std::vector<std::vector<int64_t>>& tuples,
const std::vector<absl::flat_hash_set<int64_t>>& values_per_var,
absl::Span<const int> vars, absl::Span<const std::vector<int64_t>> tuples,
absl::Span<const absl::flat_hash_set<int64_t>> values_per_var,
PresolveContext* context) {
CHECK_EQ(vars.size(), 2);
const int left_var = vars[0];

View File

@@ -188,7 +188,6 @@ void AddExtraSchedulingPropagators(SatParameters& new_params) {
new_params.set_use_energetic_reasoning_in_no_overlap_2d(true);
new_params.set_use_area_energetic_reasoning_in_no_overlap_2d(true);
new_params.set_use_try_edge_reasoning_in_no_overlap_2d(true);
new_params.set_no_overlap_2d_boolean_relations_limit(100);
}
// We want a random tie breaking among variables with equivalent values.

View File

@@ -3009,7 +3009,7 @@ CpSolverResponse SolveWithParameters(const CpModelProto& model_proto,
}
CpSolverResponse SolveWithParameters(const CpModelProto& model_proto,
const std::string& params) {
absl::string_view params) {
Model model;
model.Add(NewSatParameters(params));
return SolveCpModel(model_proto, &model);

View File

@@ -71,7 +71,7 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model);
* format, and returns an instance of CpSolverResponse.
*/
CpSolverResponse SolveWithParameters(const CpModelProto& model_proto,
const std::string& params);
absl::string_view params);
#endif // !__PORTABLE_PLATFORM__
/**

View File

@@ -32,7 +32,6 @@
#include "absl/log/vlog_is_on.h"
#include "absl/numeric/bits.h"
#include "absl/types/span.h"
// #include "ortools/base/stl_util.h"
#include "ortools/sat/2d_distances_propagator.h"
#include "ortools/sat/2d_mandatory_overlap_propagator.h"
#include "ortools/sat/2d_orthogonal_packing.h"
@@ -277,11 +276,11 @@ void AddNonOverlappingRectangles(const std::vector<IntervalVariable>& x,
DCHECK_EQ(sat_solver->CurrentDecisionLevel(), 0);
for (int i = 0; i < num_boxes; ++i) {
if (repository->IsAbsent(x[i])) continue;
if (repository->IsAbsent(y[i])) continue;
if (repository->IsOptional(x[i])) continue;
if (repository->IsOptional(y[i])) continue;
for (int j = i + 1; j < num_boxes; ++j) {
if (repository->IsAbsent(x[j])) continue;
if (repository->IsAbsent(y[j])) continue;
if (repository->IsOptional(x[j])) continue;
if (repository->IsOptional(y[j])) continue;
// At most one of these two x options is true.
const Literal x_ij = repository->GetOrCreatePrecedenceLiteral(
@@ -308,21 +307,7 @@ void AddNonOverlappingRectangles(const std::vector<IntervalVariable>& x,
}
// At least one of the 4 options is true.
std::vector<Literal> clause = {x_ij, x_ji, y_ij, y_ji};
if (repository->IsOptional(x[i])) {
clause.push_back(repository->PresenceLiteral(x[i]).Negated());
}
if (repository->IsOptional(y[i])) {
clause.push_back(repository->PresenceLiteral(y[i]).Negated());
}
if (repository->IsOptional(x[j])) {
clause.push_back(repository->PresenceLiteral(x[j]).Negated());
}
if (repository->IsOptional(y[j])) {
clause.push_back(repository->PresenceLiteral(y[j]).Negated());
}
gtl::STLSortAndRemoveDuplicates(&clause);
if (!sat_solver->AddProblemClause(clause)) {
if (!sat_solver->AddProblemClause({x_ij, x_ji, y_ij, y_ji})) {
return;
}
}

View File

@@ -389,7 +389,8 @@ LinearPropagator::LinearPropagator(Model* model)
random_(model->GetOrCreate<ModelRandomGenerator>()),
shared_stats_(model->GetOrCreate<SharedStatistics>()),
watcher_id_(watcher_->Register(this)),
order_(random_, [this](int id) { return GetVariables(infos_[id]); }) {
order_(random_, time_limit_,
[this](int id) { return GetVariables(infos_[id]); }) {
// Note that we need this class always in sync.
integer_trail_->RegisterWatcher(&modified_vars_);
integer_trail_->RegisterReversibleClass(this);
@@ -475,6 +476,8 @@ void LinearPropagator::SetPropagatedBy(IntegerVariable var, int id) {
void LinearPropagator::OnVariableChange(IntegerVariable var, IntegerValue lb,
int id) {
DCHECK_EQ(lb, integer_trail_->LowerBound(var));
// If no constraint use this var, we just ignore it.
const int size = var_to_constraint_ids_[var].size();
if (size == 0) return;
@@ -1331,7 +1334,7 @@ bool LinearPropagator::DisassembleSubtree(int root_id, int num_tight) {
if (next_increase > 0) {
disassemble_queue_.push_back({id, next_var, next_increase});
// We know this will push later, so we register hit with a sentinel
// We know this will push later, so we register it with a sentinel
// value so that it do not block any earlier propagation. Hopefully,
// adding this "dependency" should help find a better propagation
// order.

View File

@@ -149,16 +149,19 @@ class EnforcementPropagator : public SatPropagator {
// Each constraint might push some variables which might in turn make other
// constraint tighter. In general, it seems better to make sure we push first
// constraints that are not affected by other variables and delay the
// propagation of constraint that we know will become tigher.
// propagation of constraint that we know will become tigher. This also likely
// simplifies the reasons.
//
// Note that we can have cycle in this graph, and that this is not necessarily a
// conflict.
class ConstraintPropagationOrder {
public:
ConstraintPropagationOrder(
ModelRandomGenerator* random,
ModelRandomGenerator* random, TimeLimit* time_limit,
std::function<absl::Span<const IntegerVariable>(int)> id_to_vars)
: random_(random), id_to_vars_func_(std::move(id_to_vars)) {}
: random_(random),
time_limit_(time_limit),
id_to_vars_func_(std::move(id_to_vars)) {}
void Resize(int num_vars, int num_ids) {
var_has_entry_.Resize(IntegerVariable(num_vars));
@@ -166,7 +169,7 @@ class ConstraintPropagationOrder {
var_to_lb_.resize(num_vars);
var_to_pos_.resize(num_vars);
in_ids_.Resize(num_ids);
in_ids_.resize(num_ids);
}
void Register(int id, IntegerVariable var, IntegerValue lb) {
@@ -203,15 +206,17 @@ class ConstraintPropagationOrder {
// Return -1 if there is none.
// This returns a constraint with min degree.
//
// TODO(user): fix quadratic algo? We can use var_to_ids_func_() to maintain
// the degree. But note that with the start_ optim and because we expect
// mainly degree zero, this seems to be faster.
// TODO(user): fix quadratic or even linear algo? We can use
// var_to_ids_func_() to maintain the degree. But note that since we reorder
// constraints and because we expect mainly degree zero, this seems to be
// faster.
int NextId() {
if (ids_.empty()) return -1;
int best_id = 0;
int best_num_vars = 0;
int best_degree = std::numeric_limits<int>::max();
int64_t work_done = 0;
const int size = ids_.size();
const auto var_has_entry = var_has_entry_.const_view();
for (int i = 0; i < size; ++i) {
@@ -219,10 +224,28 @@ class ConstraintPropagationOrder {
ids_.pop_front();
DCHECK(in_ids_[id]);
// By degree, we mean the number of variables of the constraint that do
// not have yet their lower bounds up to date; they will be pushed by
// other constraints as we propagate them. If possible, we want to delay
// the propagation of a constraint with positive degree until all involved
// lower bounds are up to date (i.e. degree == 0).
int degree = 0;
absl::Span<const IntegerVariable> vars = id_to_vars_func_(id);
work_done += vars.size();
for (const IntegerVariable var : vars) {
if (var_has_entry[var]) ++degree;
if (var_has_entry[var]) {
if (var_has_entry[NegationOf(var)] &&
var_to_id_[NegationOf(var)] == id) {
// We have two constraints, this one (id) push NegationOf(var), and
// var_to_id_[var] push var. So whichever order we choose, the first
// constraint will need to be scanned at least twice. Lets not count
// this situation in the degree.
continue;
}
DCHECK_NE(var_to_id_[var], id);
++degree;
}
}
// We select the min-degree and prefer lower constraint size.
@@ -241,6 +264,11 @@ class ConstraintPropagationOrder {
ids_.push_back(id);
}
if (work_done > 100) {
time_limit_->AdvanceDeterministicTime(static_cast<double>(work_done) *
5e-9);
}
// We didn't find any degree zero, we scanned the whole queue.
// Extract best_id while keeping the order stable.
//
@@ -277,6 +305,7 @@ class ConstraintPropagationOrder {
public:
ModelRandomGenerator* random_;
TimeLimit* time_limit_;
std::function<absl::Span<const IntegerVariable>(int)> id_to_vars_func_;
// For each variable we only keep the constraint id that pushes it further.

View File

@@ -28,6 +28,7 @@
#include "absl/log/log.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_split.h"
#include "absl/strings/string_view.h"
#include "absl/types/span.h"
#include "ortools/base/logging.h"
#include "ortools/base/stl_util.h"
@@ -274,7 +275,7 @@ class OpbReader {
return true;
}
static int ParseIndex(const std::string& word) {
static int ParseIndex(absl::string_view word) {
int index;
CHECK(absl::SimpleAtoi(word, &index));
return index;

View File

@@ -16,6 +16,7 @@
"""Solves an assignment problem for given group of workers."""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -21,6 +21,7 @@ import io
import pandas as pd
from ortools.sat.python import cp_model
# [END import]

View File

@@ -16,6 +16,7 @@
"""Solves a simple assignment problem."""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -16,6 +16,7 @@
"""Solves a simple assignment problem."""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -16,6 +16,7 @@
# [START program]
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -21,6 +21,7 @@ import io
import pandas as pd
from ortools.sat.python import cp_model
# [END import]

View File

@@ -22,6 +22,7 @@ This problem has 72 different solutions in base 10.
"""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -16,6 +16,7 @@
"""Simple solve."""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -17,6 +17,7 @@
# [START import]
import collections
from ortools.sat.python import cp_model
# [END import]

View File

@@ -16,6 +16,7 @@
"""Solves a multiple knapsack problem using the CP-SAT solver."""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -18,6 +18,7 @@
import sys
import time
from ortools.sat.python import cp_model
# [END import]
@@ -54,6 +55,7 @@ class NQueenSolutionPrinter(cp_model.CpSolverSolutionCallback):
print()
print()
# [END solution_printer]

View File

@@ -16,6 +16,7 @@
"""Example of a simple nurse scheduling problem."""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -18,6 +18,7 @@
from typing import Union
from ortools.sat.python import cp_model
# [END import]

View File

@@ -16,6 +16,7 @@
"""Simple solve."""
# [START import]
from ortools.sat.python import cp_model
# [END import]

View File

@@ -1337,21 +1337,19 @@ CompletionTimeExplorationStatus ComputeMinSumOfWeightedEndMins(
}
}
}
if (!helper.valid_permutation_iterator_.Init()) {
return CompletionTimeExplorationStatus::NO_VALID_PERMUTATION;
}
bool is_dag = false;
int num_valid_permutations = 0;
do {
for (const auto& permutation : helper.valid_permutation_iterator_) {
is_dag = true;
if (--exploration_credit < 0) break;
IntegerValue sum_of_ends = 0;
IntegerValue sum_of_weighted_ends = 0;
if (ComputeWeightedSumOfEndMinsOfOnePermutation(
events, helper.valid_permutation_iterator_.permutation(),
capacity_max, helper, sum_of_ends, sum_of_weighted_ends,
cut_use_precedences)) {
events, permutation, capacity_max, helper, sum_of_ends,
sum_of_weighted_ends, cut_use_precedences)) {
min_sum_of_ends = std::min(ToDouble(sum_of_ends), min_sum_of_ends);
min_sum_of_weighted_ends =
std::min(ToDouble(sum_of_weighted_ends), min_sum_of_weighted_ends);
@@ -1362,7 +1360,10 @@ CompletionTimeExplorationStatus ComputeMinSumOfWeightedEndMins(
break;
}
}
} while (helper.valid_permutation_iterator_.Increase());
}
if (!is_dag) {
return CompletionTimeExplorationStatus::NO_VALID_PERMUTATION;
}
const CompletionTimeExplorationStatus status =
exploration_credit < 0 ? CompletionTimeExplorationStatus::ABORTED
: num_valid_permutations > 0
@@ -1383,7 +1384,7 @@ CompletionTimeExplorationStatus ComputeMinSumOfWeightedEndMins(
// - detect disjoint tasks (no need to crossover to the second part)
// - better caching of explored states
ABSL_MUST_USE_RESULT bool GenerateShortCompletionTimeCutsWithExactBound(
const std::string& cut_name, std::vector<CompletionTimeEvent> events,
absl::string_view cut_name, std::vector<CompletionTimeEvent> events,
IntegerValue capacity_max, CtExhaustiveHelper& helper, Model* model,
LinearConstraintManager* manager) {
TopNCuts top_n_cuts(5);
@@ -1476,7 +1477,7 @@ ABSL_MUST_USE_RESULT bool GenerateShortCompletionTimeCutsWithExactBound(
is_lifted |= event.lifted;
cut.AddTerm(event.end, IntegerValue(1));
}
std::string full_name = cut_name;
std::string full_name(cut_name);
if (cut_use_precedences) full_name.append("_prec");
if (is_lifted) full_name.append("_lifted");
top_n_cuts.AddCut(cut.Build(), full_name, manager->LpValues());
@@ -1493,7 +1494,7 @@ ABSL_MUST_USE_RESULT bool GenerateShortCompletionTimeCutsWithExactBound(
is_lifted |= event.lifted;
cut.AddTerm(event.end, event.energy_min);
}
std::string full_name = cut_name;
std::string full_name(cut_name);
if (is_lifted) full_name.append("_lifted");
if (cut_use_precedences) full_name.append("_prec");
full_name.append("_weighted");

View File

@@ -19,6 +19,7 @@
#include <cstddef>
#include <cstdint>
#include <deque>
#include <iterator>
#include <limits>
#include <memory>
#include <string>
@@ -984,192 +985,288 @@ inline void CompactVectorVector<K, V>::ResetFromTranspose(
//
// If the graph has no edges, it will generate all possible permutations.
//
// If the graph has edges, it will generate all possible permutations of the
// dag that are a topological sorting of the graph.
// If the graph has edges, it will generate all possible permutations of the dag
// that are a topological sorting of the graph.
//
// The class maintains 5 fields:
// - graph_: a vector of vectors, where graph_[i] contains the list of
// elements that are adjacent to element i.
// - size_: the size of the graph.
// Typical usage:
//
// - missing_parent_numbers_: a vector of integers, where
// missing_parent_numbers_[i] is the number of parents of element i that are
// not yet in permutation_. Before Init() is called, no element is yet in
// permutation_ so that it is the number of parents of i. After Init(), and
// before Increase() returns true, it is always 0 (except during the
// execution of Increase(), see below).
// DagTopologicalSortIterator dag_topological_sort(5);
//
// - permutation_: a vector of integers, that after Init() is called, and
// before Increase() returns false, it is a topological sorting of the graph
// (except during the execution of Increase()).
// - element_original_position_: a vector of integers, where
// element_original_position_[i] is the original position of element i in the
// permutation_. See the algorithm below for more details.
// dag_topological_sort.AddArc(0, 1);
// dag_topological_sort.AddArc(1, 2);
// dag_topological_sort.AddArc(3, 4);
//
// for (const auto& permutation : dag_topological_sort) {
// // Do something with each permutation.
// }
//
// Note: to test if there are cycles, it is enough to check if at least one
// iteration occurred in the above loop.
//
// Note 2: adding an arc during an iteration is not supported and the behavior
// is undefined.
class DagTopologicalSortIterator {
public:
// Graph maps indices to their children. Any children must exist.
DagTopologicalSortIterator() : size_(0) {}
explicit DagTopologicalSortIterator(int size) : size_(size) { Reset(size); }
DagTopologicalSortIterator() = default;
void Reset(int size) {
size_ = size;
graph_.assign(size, {});
missing_parent_numbers_.assign(size, 0);
permutation_.clear();
element_original_position_.assign(size, 0);
// Graph maps indices to their children. Any children must exist.
explicit DagTopologicalSortIterator(int size)
: graph_(size, std::vector<int>{}) {}
// An iterator class to generate all possible topological sorting of a dag.
//
// If the graph has no edges, it will generate all possible permutations.
//
// If the graph has edges, it will generate all possible permutations of the
// dag that are a topological sorting of the graph.
//
// The class maintains 5 fields:
// - graph_: a vector of vectors, where graph_[i] contains the list of
// elements that are adjacent to element i. This is not owned.
// - size_: the size of the graph.
// - missing_parent_numbers_: a vector of integers, where
// missing_parent_numbers_[i] is the number of parents of element i that
// are not yet in permutation_. It is always 0 except during the
// execution of operator++().
// - permutation_: a vector of integers, that is a topological sorting of the
// graph except during the execution of operator++().
// - element_original_position_: a vector of integers, where
// element_original_position_[i] is the original position of element i in
// the permutation_. See the algorithm below for more details.
class Iterator {
friend class DagTopologicalSortIterator;
public:
using iterator_category = std::input_iterator_tag;
using value_type = const std::vector<int>;
using difference_type = ptrdiff_t;
using pointer = value_type*;
using reference = value_type&;
Iterator& operator++();
friend bool operator==(const Iterator& a, const Iterator& b) {
return &a.graph_ == &b.graph_ && a.ordering_index_ == b.ordering_index_;
}
friend bool operator!=(const Iterator& a, const Iterator& b) {
return !(a == b);
}
reference operator*() const { return permutation_; }
pointer operator->() const { return &permutation_; }
private:
// End iterator.
explicit Iterator(const std::vector<std::vector<int>>& graph
ABSL_ATTRIBUTE_LIFETIME_BOUND,
bool)
: graph_(graph), ordering_index_(-1) {}
// Begin iterator.
explicit Iterator(const std::vector<std::vector<int>>& graph
ABSL_ATTRIBUTE_LIFETIME_BOUND);
// Unset the element at pos.
void Unset(int pos);
// Set the element at pos to the element at k.
void Set(int pos, int k);
// Graph maps indices to their children. Children must be in [0, size_).
const std::vector<std::vector<int>>& graph_;
// Number of elements in graph_.
int size_;
// For each element in graph_, the number of parents it has that are not yet
// in permutation_. In particular, it is always 0 outside of operator++().
std::vector<int> missing_parent_numbers_;
// The current permutation. It is ensured to be a topological sorting of the
// graph outside of operator++().
std::vector<int> permutation_;
// Keeps track of the original position of the element in permutation_[i].
// See the comment above the class for the detailed algorithm.
std::vector<int> element_original_position_;
// Index of the current ordering. Used to compare iterators. It is -1 if the
// end has been reached.
int64_t ordering_index_;
};
Iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return Iterator(graph_);
}
Iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND {
return Iterator(graph_, true);
}
// Must be called before Init().
void Reset(int size) { graph_.assign(size, {}); }
// Must be called before iteration starts or between iterations.
void AddArc(int from, int to) {
DCHECK_GE(from, 0);
DCHECK_LT(from, size_);
DCHECK_LT(from, graph_.size());
DCHECK_GE(to, 0);
DCHECK_LT(to, size_);
DCHECK_LT(to, graph_.size());
graph_[from].push_back(to);
missing_parent_numbers_[to]++;
}
// To describe the algorithm in Increase() and Init(), we consider the
// following invariant, called Invariant(pos) for a position pos in [0,
// size_):
// 1. permutations_[0], ..., permutations_[pos] form a prefix of a
// topological ordering of the graph;
// 2. permutations_[pos + 1], ..., permutations_.back() are all other
// elements that have all their parents in permutations_[0], ...,
// permutations_[pos], ordered lexicographically by the index of their
// last parent in permutations_[0], ... permutations_[pos] and then by
// their index in the graph;
// 3. missing_parent_numbers_[i] is the number of parents of element i that
// are not in {permutations_[0], ..., permutations_[pos]}.
// 4. element_original_position_[i] is the original position of element i of
// the permutation following the order described in 2. In particular,
// element_original_position_[i] = i for i > pos.
// Set and Unset maintain these invariants.
// Precondition: Invariant(size_ - 1) holds.
// Postcondition: Invariant(size_ - 1) holds if Increase() returns true.
// If Increase() returns false, all topological orderings of the graph have
// been generated and the state of permutation_ is not specified..
bool Increase() {
Unset(size_ - 1);
for (int pos = size_ - 2; pos >= 0; --pos) {
// Invariant(pos) holds.
// Increasing logic: once permutation_[pos] has been put back to its
// original position by Unset(pos), elements permutations_[pos], ...,
// permutations_.back() are in their original ordering, in particular in
// the same order as last time the iteration on permutation_[pos]
// occurred (according to Invariant(pos).2, these are exactly the elements
// that have to be tried at pos).
// All possibilities in permutations_[pos], ...,
// permutations_[element_original_position_[pos]] have been run through.
// The next to test is permutations_[element_original_position_[pos] + 1].
const int k = element_original_position_[pos] + 1;
Unset(pos);
// Invariant(pos - 1) holds.
// No more elements to iterate on at position pos. Go backwards one
// position to increase that one.
if (k == permutation_.size()) continue;
Set(pos, k);
// Invariant(pos) holds.
for (++pos; pos < size_; ++pos) {
// Invariant(pos - 1) holds.
// According to Invariant(pos - 1).2, if pos >= permutation_.size(),
// there are no more elements we can add to the permutation which means
// that we detected a cycle. It would be a bug as we would have detected
// it in Init().
CHECK_LT(pos, permutation_.size()) << "Cycle detected";
// According to Invariant(pos - 1).2, elements that can be used at pos
// are permutations_[pos], ..., permutations_.back(). Starts the
// iteration at permutations_[pos].
Set(pos, pos);
// Invariant(pos) holds.
}
// Invariant(size_ - 1) holds.
return true;
}
return false;
}
// Must be called before Increase().
ABSL_MUST_USE_RESULT bool Init() {
for (int i = 0; i < size_; ++i) {
if (missing_parent_numbers_[i] == 0) {
permutation_.push_back(i);
}
}
for (int pos = 0; pos < size_; ++pos) {
// Invariant(pos - 1) holds.
// According to Invariant(pos - 1).2, if pos >= permutation_.size(),
// there are no more elements we can add to the permutation.
if (pos >= permutation_.size()) return false;
// According to Invariant(pos - 1).2, elements that can be used at pos
// are permutations_[pos], ..., permutations_.back(). Starts the
// iteration at permutations_[pos].
Set(pos, pos);
// Invariant(pos) holds.
}
// Invariant(pos - 1) hold. We have a permutation.
return true;
}
const std::vector<int>& permutation() const { return permutation_; }
private:
// Graph maps indices to their children. Children must be in [0, size_).
std::vector<std::vector<int>> graph_;
// Number of elements in graph_.
int size_;
// For each element in graph_, the number of parents it has that are not yet
// in permutation_. In particular, it is always 0 when Init has been called
// and when Increase is not in progress (and has not yet returned false).
std::vector<int> missing_parent_numbers_;
// The current permutation. It is ensured to be a topological sorting of the
// graph once Init has been called and Increase has not yet returned false.
std::vector<int> permutation_;
// Keeps track of the original position of the element in permutation_[i]. See
// the comment above the class for the detailed algorithm.
std::vector<int> element_original_position_;
// Unset the element at pos.
//
// - Precondition: Invariant(pos) holds.
// - Postcondition: Invariant(pos - 1) holds.
void Unset(int pos) {
const int n = permutation_[pos];
// Before the loop: Invariant(pos).2 and Invariant(pos).3 hold.
// After the swap below: Invariant(pos - 1).2 and Invariant(pos - 1).3 hold.
for (const int c : graph_[n]) {
if (missing_parent_numbers_[c] == 0) permutation_.pop_back();
++missing_parent_numbers_[c];
}
std::swap(permutation_[element_original_position_[pos]], permutation_[pos]);
// Invariant(pos).4 -> Invariant(pos - 1).4.
element_original_position_[pos] = pos;
}
// Set the element at pos to the element at k.
//
// - Precondition: Invariant(pos - 1) holds and k in [pos,
// permutation_.size()).
// - Postcondition: Invariant(pos) holds and permutation_[pos] has been
// swapped with permutation_[k].
void Set(int pos, int k) {
int n = permutation_[k];
// Before the loop: Invariant(pos - 1).2 and Invariant(pos - 1).3 hold.
// After the loop: Invariant(pos).2 and Invariant(pos).3 hold.
for (int c : graph_[n]) {
--missing_parent_numbers_[c];
if (missing_parent_numbers_[c] == 0) permutation_.push_back(c);
}
// Invariant(pos - 1).1 -> Invariant(pos).1.
std::swap(permutation_[k], permutation_[pos]);
// Invariant(pos - 1).4 -> Invariant(pos).4.
element_original_position_[pos] = k;
}
};
// To describe the algorithm in operator++() and constructor(), we consider the
// following invariant, called Invariant(pos) for a position pos in [0, size_):
// 1. permutations_[0], ..., permutations_[pos] form a prefix of a topological
// ordering of the graph;
// 2. permutations_[pos + 1], ..., permutations_.back() are all other elements
// that have all their parents in permutations_[0], ..., permutations_[pos],
// ordered lexicographically by the index of their last parent in
// permutations_[0], ... permutations_[pos] and then by their index in the
// graph;
// 3. missing_parent_numbers_[i] is the number of parents of element i that are
// not in {permutations_[0], ..., permutations_[pos]}.
// 4. element_original_position_[i] is the original position of element i of
// the permutation following the order described in 2. In particular,
// element_original_position_[i] = i for i > pos.
// Set and Unset maintain these invariants.
// Precondition: Invariant(size_ - 1) holds.
// Postcondition: Invariant(size_ - 1) holds if the end of the iteration is not
// reached.
inline DagTopologicalSortIterator::Iterator&
DagTopologicalSortIterator::Iterator::operator++() {
CHECK_GE(ordering_index_, 0) << "Iteration past end";
if (size_ == 0) {
// Special case: empty graph, only one topological ordering is
// generated.
ordering_index_ = -1;
return *this;
}
Unset(size_ - 1);
for (int pos = size_ - 2; pos >= 0; --pos) {
// Invariant(pos) holds.
// Increasing logic: once permutation_[pos] has been put back to its
// original position by Unset(pos), elements permutations_[pos], ...,
// permutations_.back() are in their original ordering, in particular in
// the same order as last time the iteration on permutation_[pos] occurred
// (according to Invariant(pos).2, these are exactly the elements that have
// to be tried at pos). All possibilities in permutations_[pos], ...,
// permutations_[element_original_position_[pos]] have been run through.
// The next to test is permutations_[element_original_position_[pos] + 1].
const int k = element_original_position_[pos] + 1;
Unset(pos);
// Invariant(pos - 1) holds.
// No more elements to iterate on at position pos. Go backwards one position
// to increase that one.
if (k == permutation_.size()) continue;
Set(pos, k);
// Invariant(pos) holds.
for (++pos; pos < size_; ++pos) {
// Invariant(pos - 1) holds.
// According to Invariant(pos - 1).2, if pos >= permutation_.size(), there
// are no more elements we can add to the permutation which means that we
// detected a cycle. It would be a bug as we would have detected it in
// the constructor.
CHECK_LT(pos, permutation_.size())
<< "Unexpected cycle detected during iteration";
// According to Invariant(pos - 1).2, elements that can be used at pos are
// permutations_[pos], ..., permutations_.back(). Starts the iteration at
// permutations_[pos].
Set(pos, pos);
// Invariant(pos) holds.
}
// Invariant(size_ - 1) holds.
++ordering_index_;
return *this;
}
ordering_index_ = -1;
return *this;
}
inline DagTopologicalSortIterator::Iterator::Iterator(
const std::vector<std::vector<int>>& graph)
: graph_(graph),
size_(graph.size()),
missing_parent_numbers_(size_, 0),
element_original_position_(size_, 0),
ordering_index_(0) {
if (size_ == 0) {
// Special case: empty graph, only one topological ordering is generated,
// which is the "empty" ordering.
return;
}
for (const auto& children : graph_) {
for (const int child : children) {
missing_parent_numbers_[child]++;
}
}
for (int i = 0; i < size_; ++i) {
if (missing_parent_numbers_[i] == 0) {
permutation_.push_back(i);
}
}
for (int pos = 0; pos < size_; ++pos) {
// Invariant(pos - 1) holds.
// According to Invariant(pos - 1).2, if pos >= permutation_.size(), there
// are no more elements we can add to the permutation.
if (pos >= permutation_.size()) {
ordering_index_ = -1;
return;
}
// According to Invariant(pos - 1).2, elements that can be used at pos are
// permutations_[pos], ..., permutations_.back(). Starts the iteration at
// permutations_[pos].
Set(pos, pos);
// Invariant(pos) holds.
}
// Invariant(pos - 1) hold. We have a permutation.
}
// Unset the element at pos.
//
// - Precondition: Invariant(pos) holds.
// - Postcondition: Invariant(pos - 1) holds.
inline void DagTopologicalSortIterator::Iterator::Unset(int pos) {
const int n = permutation_[pos];
// Before the loop: Invariant(pos).2 and Invariant(pos).3 hold.
// After the swap below: Invariant(pos - 1).2 and Invariant(pos - 1).3 hold.
for (const int c : graph_[n]) {
if (missing_parent_numbers_[c] == 0) permutation_.pop_back();
++missing_parent_numbers_[c];
}
std::swap(permutation_[element_original_position_[pos]], permutation_[pos]);
// Invariant(pos).4 -> Invariant(pos - 1).4.
element_original_position_[pos] = pos;
}
// Set the element at pos to the element at k.
//
// - Precondition: Invariant(pos - 1) holds and k in [pos,
// permutation_.size()).
// - Postcondition: Invariant(pos) holds and permutation_[pos] has been swapped
// with permutation_[k].
inline void DagTopologicalSortIterator::Iterator::Set(int pos, int k) {
int n = permutation_[k];
// Before the loop: Invariant(pos - 1).2 and Invariant(pos - 1).3 hold.
// After the loop: Invariant(pos).2 and Invariant(pos).3 hold.
for (int c : graph_[n]) {
--missing_parent_numbers_[c];
if (missing_parent_numbers_[c] == 0) permutation_.push_back(c);
}
// Invariant(pos - 1).1 -> Invariant(pos).1.
std::swap(permutation_[k], permutation_[pos]);
// Invariant(pos - 1).4 -> Invariant(pos).4.
element_original_position_[pos] = k;
}
} // namespace sat
} // namespace operations_research

View File

@@ -1075,43 +1075,39 @@ TEST(DagTopologicalSortIteratorTest, GenerateValidPermutations) {
dag_iterator.AddArc(4, 1);
dag_iterator.AddArc(2, 3);
dag_iterator.AddArc(3, 1);
EXPECT_TRUE(dag_iterator.Init());
int count = 0;
do {
for ([[maybe_unused]] const auto& permutation : dag_iterator) {
++count;
} while (dag_iterator.Increase());
}
EXPECT_EQ(count, 13);
}
TEST(DagTopologicalSortIteratorTest, GenerateAllPermutations) {
DagTopologicalSortIterator dag_iterator(6);
EXPECT_TRUE(dag_iterator.Init());
int count = 0;
do {
for ([[maybe_unused]] const auto& permutation : dag_iterator) {
++count;
} while (dag_iterator.Increase());
}
EXPECT_EQ(count, 720);
}
TEST(DagTopologicalSortIteratorTest, OnePrecedence) {
DagTopologicalSortIterator dag_iterator(6);
dag_iterator.AddArc(5, 2);
EXPECT_TRUE(dag_iterator.Init());
int count = 0;
do {
for ([[maybe_unused]] const auto& permutation : dag_iterator) {
++count;
} while (dag_iterator.Increase());
}
EXPECT_EQ(count, 360);
}
TEST(DagTopologicalSortIteratorTest, ReversePrecedence) {
DagTopologicalSortIterator dag_iterator(6);
dag_iterator.AddArc(2, 5);
EXPECT_TRUE(dag_iterator.Init());
int count = 0;
do {
for ([[maybe_unused]] const auto& permutation : dag_iterator) {
++count;
} while (dag_iterator.Increase());
}
EXPECT_EQ(count, 360);
}
@@ -1133,11 +1129,9 @@ TEST(DagTopologicalSortIteratorTest, RandomTest) {
absl::flat_hash_set<std::vector<int>> iterator_solutions;
int count_iterator = 0;
if (dag_iterator.Init()) {
do {
++count_iterator;
iterator_solutions.insert(dag_iterator.permutation());
} while (dag_iterator.Increase());
for (const auto& permutation : dag_iterator) {
++count_iterator;
iterator_solutions.insert(permutation);
}
std::vector<int> permutation = {0, 1, 2, 3, 4, 5};

View File

@@ -14,6 +14,7 @@
#ifndef OR_TOOLS_UTIL_SORTED_INTERVAL_LIST_H_
#define OR_TOOLS_UTIL_SORTED_INTERVAL_LIST_H_
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <ostream>
@@ -32,21 +33,41 @@ namespace operations_research {
* Represents a closed interval [start, end]. We must have start <= end.
*/
struct ClosedInterval {
#if !defined(SWIG)
/**
* An iterator over the values of a ClosedInterval object.
*
* To iterate over the values, you can use either a range for loop:
*
* ClosedInterval interval = {0, 100};
* for (const int64_t value : interval) { Work(value); }
*
* or a classical for loop:
* for (auto it = begin(interval); it != end(interval); ++it) { Work(*it); }
*
* The iterator is designed to be very efficient, using just a single counter.
* It works correctly for any combination of `start` and `end` except the full
* int64_t range (start == INT64_MIN && end == INT64_MAX).
*/
class Iterator;
#endif // !defined(SWIG)
ClosedInterval() {}
explicit ClosedInterval(int64_t v) : start(v), end(v) {}
ClosedInterval(int64_t s, int64_t e) : start(s), end(e) {
DLOG_IF(DFATAL, s > e) << "Invalid ClosedInterval(" << s << ", " << e
<< ")";
}
std::string DebugString() const;
bool operator==(const ClosedInterval& other) const {
constexpr bool operator==(const ClosedInterval& other) const {
return start == other.start && end == other.end;
}
// Because we mainly manipulate vector of disjoint intervals, we only need to
// sort by the start. We do not care about the order in which interval with
// the same start appear since they will always be merged into one interval.
bool operator<(const ClosedInterval& other) const {
constexpr bool operator<(const ClosedInterval& other) const {
return start < other.start;
}
@@ -59,6 +80,11 @@ struct ClosedInterval {
int64_t end = 0; // Inclusive.
};
#if !defined(SWIG)
inline ClosedInterval::Iterator begin(ClosedInterval interval);
inline ClosedInterval::Iterator end(ClosedInterval interval);
#endif // !defined(SWIG)
std::ostream& operator<<(std::ostream& out, const ClosedInterval& interval);
std::ostream& operator<<(std::ostream& out,
const std::vector<ClosedInterval>& intervals);
@@ -648,6 +674,67 @@ class SortedDisjointIntervalList {
IntervalSet intervals_;
};
// Implementation details.
#if !defined(SWIG)
class ClosedInterval::Iterator {
public:
using value_type = int64_t;
using difference_type = std::ptrdiff_t;
Iterator(const Iterator&) = default;
int64_t operator*() const { return static_cast<int64_t>(current_); }
Iterator& operator++() {
++current_;
return *this;
}
void operator++(int) { ++current_; }
bool operator==(Iterator other) const { return current_ == other.current_; }
bool operator!=(Iterator other) const { return current_ != other.current_; }
Iterator& operator=(const Iterator&) = default;
static Iterator Begin(ClosedInterval interval) {
AssertNotFullInt64Range(interval);
return Iterator(static_cast<uint64_t>(interval.start));
}
static Iterator End(ClosedInterval interval) {
AssertNotFullInt64Range(interval);
return Iterator(static_cast<uint64_t>(interval.end) + 1);
}
private:
explicit Iterator(uint64_t current) : current_(current) {}
// Triggers a DCHECK-failure when `interval` represents the full int64_t
// range.
static void AssertNotFullInt64Range(ClosedInterval interval) {
DCHECK_NE(static_cast<uint64_t>(interval.start),
static_cast<uint64_t>(interval.end) + 1)
<< "Iteration over the full int64_t range is not supported.";
}
// Implementation note: In C++, integer overflow is well-defined only for
// unsigned integers. To avoid any compilation issues or UBSan failures, the
// iterator uses uint64_t internally and relies on the fact that since C++20
// unsigned->signed conversion is well-defined for all values using modulo
// arithmetic.
uint64_t current_;
};
// begin()/end() are required for iteration over ClosedInterval in a range for
// loop.
inline ClosedInterval::Iterator begin(ClosedInterval interval) {
return ClosedInterval::Iterator::Begin(interval);
}
inline ClosedInterval::Iterator end(ClosedInterval interval) {
return ClosedInterval::Iterator::End(interval);
}
#endif // !defined(SWIG)
} // namespace operations_research
#endif // OR_TOOLS_UTIL_SORTED_INTERVAL_LIST_H_