From 7f463e1a682325e7f885eb1c207eaa8e6fd0fd62 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Mon, 2 Jun 2025 14:25:50 +0200 Subject: [PATCH] sync with main --- ortools/glop/revised_simplex.cc | 4 +- ortools/lp_data/lp_parser.cc | 4 +- ortools/sat/2d_distances_propagator.cc | 40 +- ortools/sat/cp_model_expand.cc | 5 +- ortools/sat/cp_model_search.cc | 1 - ortools/sat/cp_model_solver.cc | 2 +- ortools/sat/cp_model_solver.h | 2 +- ortools/sat/diffn.cc | 25 +- ortools/sat/linear_propagation.cc | 7 +- ortools/sat/linear_propagation.h | 45 +- ortools/sat/opb_reader.h | 3 +- ortools/sat/samples/assignment_groups_sat.py | 1 + ortools/sat/samples/assignment_sat.py | 1 + .../sat/samples/assignment_task_sizes_sat.py | 1 + ortools/sat/samples/assignment_teams_sat.py | 1 + ortools/sat/samples/assumptions_sample_sat.py | 1 + ortools/sat/samples/bin_packing_sat.py | 1 + ortools/sat/samples/cp_is_fun_sat.py | 1 + ortools/sat/samples/cp_sat_example.py | 1 + ortools/sat/samples/minimal_jobshop_sat.py | 1 + ortools/sat/samples/multiple_knapsack_sat.py | 1 + ortools/sat/samples/nqueens_sat.py | 2 + ortools/sat/samples/nurses_sat.py | 1 + ortools/sat/samples/schedule_requests_sat.py | 1 + ortools/sat/samples/simple_sat_program.py | 1 + ortools/sat/scheduling_cuts.cc | 23 +- ortools/sat/util.h | 429 +++++++++++------- ortools/sat/util_test.cc | 28 +- ortools/util/sorted_interval_list.h | 91 +++- 29 files changed, 464 insertions(+), 260 deletions(-) diff --git a/ortools/glop/revised_simplex.cc b/ortools/glop/revised_simplex.cc index e9b26c8029..fcfe6ef92e 100644 --- a/ortools/glop/revised_simplex.cc +++ b/ortools/glop/revised_simplex.cc @@ -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; diff --git a/ortools/lp_data/lp_parser.cc b/ortools/lp_data/lp_parser.cc index c8654c49e1..e872c4fe97 100644 --- a/ortools/lp_data/lp_parser.cc +++ b/ortools/lp_data/lp_parser.cc @@ -237,8 +237,8 @@ bool LPParser::ParseConstraint(StringPiece constraint) { namespace { -template -constexpr bool dependent_false = false; // workaround before CWG2518/P2593R1 +template +constexpr bool dependent_false = false; // workaround before CWG2518/P2593R1 template bool SimpleAtoFractional(absl::string_view str, T* value) { diff --git a/ortools/sat/2d_distances_propagator.cc b/ortools/sat/2d_distances_propagator.cc index 62eb603ee8..71b44cabc3 100644 --- a/ortools/sat/2d_distances_propagator.cc +++ b/ortools/sat/2d_distances_propagator.cc @@ -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 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 reason; - std::vector 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; } diff --git a/ortools/sat/cp_model_expand.cc b/ortools/sat/cp_model_expand.cc index 08157b7341..b6d4c2404f 100644 --- a/ortools/sat/cp_model_expand.cc +++ b/ortools/sat/cp_model_expand.cc @@ -1407,9 +1407,8 @@ void ProcessOneCompressedColumn( // Simpler encoding for table constraints with 2 variables. void AddSizeTwoTable( - const std::vector& vars, - const std::vector>& tuples, - const std::vector>& values_per_var, + absl::Span vars, absl::Span> tuples, + absl::Span> values_per_var, PresolveContext* context) { CHECK_EQ(vars.size(), 2); const int left_var = vars[0]; diff --git a/ortools/sat/cp_model_search.cc b/ortools/sat/cp_model_search.cc index ff253b4041..84f28e1998 100644 --- a/ortools/sat/cp_model_search.cc +++ b/ortools/sat/cp_model_search.cc @@ -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. diff --git a/ortools/sat/cp_model_solver.cc b/ortools/sat/cp_model_solver.cc index 57a9fc7bcd..8aef798e1f 100644 --- a/ortools/sat/cp_model_solver.cc +++ b/ortools/sat/cp_model_solver.cc @@ -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); diff --git a/ortools/sat/cp_model_solver.h b/ortools/sat/cp_model_solver.h index 79d3f7195b..008c7319f8 100644 --- a/ortools/sat/cp_model_solver.h +++ b/ortools/sat/cp_model_solver.h @@ -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__ /** diff --git a/ortools/sat/diffn.cc b/ortools/sat/diffn.cc index ab5b398d19..078de5eb92 100644 --- a/ortools/sat/diffn.cc +++ b/ortools/sat/diffn.cc @@ -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& 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& x, } // At least one of the 4 options is true. - std::vector 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; } } diff --git a/ortools/sat/linear_propagation.cc b/ortools/sat/linear_propagation.cc index 31fb792911..c77ff22752 100644 --- a/ortools/sat/linear_propagation.cc +++ b/ortools/sat/linear_propagation.cc @@ -389,7 +389,8 @@ LinearPropagator::LinearPropagator(Model* model) random_(model->GetOrCreate()), shared_stats_(model->GetOrCreate()), 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. diff --git a/ortools/sat/linear_propagation.h b/ortools/sat/linear_propagation.h index 561ecdf268..b98f46711e 100644 --- a/ortools/sat/linear_propagation.h +++ b/ortools/sat/linear_propagation.h @@ -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(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::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 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(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(int)> id_to_vars_func_; // For each variable we only keep the constraint id that pushes it further. diff --git a/ortools/sat/opb_reader.h b/ortools/sat/opb_reader.h index e67b2bc309..cb6a36d500 100644 --- a/ortools/sat/opb_reader.h +++ b/ortools/sat/opb_reader.h @@ -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; diff --git a/ortools/sat/samples/assignment_groups_sat.py b/ortools/sat/samples/assignment_groups_sat.py index 482bb8f8c5..2ea4fc973b 100644 --- a/ortools/sat/samples/assignment_groups_sat.py +++ b/ortools/sat/samples/assignment_groups_sat.py @@ -16,6 +16,7 @@ """Solves an assignment problem for given group of workers.""" # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/assignment_sat.py b/ortools/sat/samples/assignment_sat.py index bbecaafa1d..96f20e8f74 100644 --- a/ortools/sat/samples/assignment_sat.py +++ b/ortools/sat/samples/assignment_sat.py @@ -21,6 +21,7 @@ import io import pandas as pd from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/assignment_task_sizes_sat.py b/ortools/sat/samples/assignment_task_sizes_sat.py index 4e1bf155ff..0baca4a6df 100644 --- a/ortools/sat/samples/assignment_task_sizes_sat.py +++ b/ortools/sat/samples/assignment_task_sizes_sat.py @@ -16,6 +16,7 @@ """Solves a simple assignment problem.""" # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/assignment_teams_sat.py b/ortools/sat/samples/assignment_teams_sat.py index 9b3ae74ed3..375087e856 100644 --- a/ortools/sat/samples/assignment_teams_sat.py +++ b/ortools/sat/samples/assignment_teams_sat.py @@ -16,6 +16,7 @@ """Solves a simple assignment problem.""" # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/assumptions_sample_sat.py b/ortools/sat/samples/assumptions_sample_sat.py index 29d4a5150e..62501b9b2f 100644 --- a/ortools/sat/samples/assumptions_sample_sat.py +++ b/ortools/sat/samples/assumptions_sample_sat.py @@ -16,6 +16,7 @@ # [START program] # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/bin_packing_sat.py b/ortools/sat/samples/bin_packing_sat.py index ecb9c9e074..8477160ea3 100644 --- a/ortools/sat/samples/bin_packing_sat.py +++ b/ortools/sat/samples/bin_packing_sat.py @@ -21,6 +21,7 @@ import io import pandas as pd from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/cp_is_fun_sat.py b/ortools/sat/samples/cp_is_fun_sat.py index 9669878c0c..7a8aeaedc0 100644 --- a/ortools/sat/samples/cp_is_fun_sat.py +++ b/ortools/sat/samples/cp_is_fun_sat.py @@ -22,6 +22,7 @@ This problem has 72 different solutions in base 10. """ # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/cp_sat_example.py b/ortools/sat/samples/cp_sat_example.py index 1c0db98e87..f7f68b2365 100755 --- a/ortools/sat/samples/cp_sat_example.py +++ b/ortools/sat/samples/cp_sat_example.py @@ -16,6 +16,7 @@ """Simple solve.""" # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/minimal_jobshop_sat.py b/ortools/sat/samples/minimal_jobshop_sat.py index 36b2961781..a79406febd 100644 --- a/ortools/sat/samples/minimal_jobshop_sat.py +++ b/ortools/sat/samples/minimal_jobshop_sat.py @@ -17,6 +17,7 @@ # [START import] import collections from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/multiple_knapsack_sat.py b/ortools/sat/samples/multiple_knapsack_sat.py index 7bdc840dd1..3f3b3e567a 100644 --- a/ortools/sat/samples/multiple_knapsack_sat.py +++ b/ortools/sat/samples/multiple_knapsack_sat.py @@ -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] diff --git a/ortools/sat/samples/nqueens_sat.py b/ortools/sat/samples/nqueens_sat.py index 81fe3a2a8f..770df0aea3 100644 --- a/ortools/sat/samples/nqueens_sat.py +++ b/ortools/sat/samples/nqueens_sat.py @@ -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] diff --git a/ortools/sat/samples/nurses_sat.py b/ortools/sat/samples/nurses_sat.py index 0bc04b16e5..16fae1af17 100644 --- a/ortools/sat/samples/nurses_sat.py +++ b/ortools/sat/samples/nurses_sat.py @@ -16,6 +16,7 @@ """Example of a simple nurse scheduling problem.""" # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/schedule_requests_sat.py b/ortools/sat/samples/schedule_requests_sat.py index ee1e1e2bd6..f89e5475a9 100644 --- a/ortools/sat/samples/schedule_requests_sat.py +++ b/ortools/sat/samples/schedule_requests_sat.py @@ -18,6 +18,7 @@ from typing import Union from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/samples/simple_sat_program.py b/ortools/sat/samples/simple_sat_program.py index 25f234e3e6..3c2041c6cf 100644 --- a/ortools/sat/samples/simple_sat_program.py +++ b/ortools/sat/samples/simple_sat_program.py @@ -16,6 +16,7 @@ """Simple solve.""" # [START import] from ortools.sat.python import cp_model + # [END import] diff --git a/ortools/sat/scheduling_cuts.cc b/ortools/sat/scheduling_cuts.cc index f99d3f0889..9842654ca0 100644 --- a/ortools/sat/scheduling_cuts.cc +++ b/ortools/sat/scheduling_cuts.cc @@ -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 events, + absl::string_view cut_name, std::vector 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"); diff --git a/ortools/sat/util.h b/ortools/sat/util.h index 4c2a26b9f3..88a5b927d3 100644 --- a/ortools/sat/util.h +++ b/ortools/sat/util.h @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -984,192 +985,288 @@ inline void CompactVectorVector::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{}) {} + + // 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; + 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>& graph + ABSL_ATTRIBUTE_LIFETIME_BOUND, + bool) + : graph_(graph), ordering_index_(-1) {} + + // Begin iterator. + explicit Iterator(const std::vector>& 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>& 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 missing_parent_numbers_; + // The current permutation. It is ensured to be a topological sorting of the + // graph outside of operator++(). + std::vector 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 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& permutation() const { return permutation_; } - private: // Graph maps indices to their children. Children must be in [0, size_). std::vector> 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 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 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 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>& 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 diff --git a/ortools/sat/util_test.cc b/ortools/sat/util_test.cc index 1e06fbfb2d..1b2be2db49 100644 --- a/ortools/sat/util_test.cc +++ b/ortools/sat/util_test.cc @@ -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> 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 permutation = {0, 1, 2, 3, 4, 5}; diff --git a/ortools/util/sorted_interval_list.h b/ortools/util/sorted_interval_list.h index 07a55b7616..f07dca7c71 100644 --- a/ortools/util/sorted_interval_list.h +++ b/ortools/util/sorted_interval_list.h @@ -14,6 +14,7 @@ #ifndef OR_TOOLS_UTIL_SORTED_INTERVAL_LIST_H_ #define OR_TOOLS_UTIL_SORTED_INTERVAL_LIST_H_ +#include #include #include #include @@ -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& 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(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(interval.start)); + } + static Iterator End(ClosedInterval interval) { + AssertNotFullInt64Range(interval); + return Iterator(static_cast(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(interval.start), + static_cast(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_