From 087868bcc2f86d5c52a668735f6b5514cdba7b95 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Tue, 5 Feb 2019 15:06:47 +0100 Subject: [PATCH] maintenance on swig files; merge presolve context and expand context; minor fixes in the code --- makefiles/Makefile.gen.mk | 39 +- ortools/algorithms/dynamic_partition.cc | 2 +- ortools/flatzinc/solver_data.h | 2 +- ortools/glop/revised_simplex.cc | 7 + ortools/linear_solver/linear_expr.h | 2 +- ortools/linear_solver/scip_interface.cc | 12 + ortools/lp_data/lp_data.cc | 2 +- ortools/lp_data/mps_reader.cc | 2 +- ortools/port/proto_utils.h | 2 +- ortools/sat/cp_model_checker.cc | 2 +- ortools/sat/cp_model_expand.cc | 236 +++++----- ortools/sat/cp_model_expand.h | 2 +- ortools/sat/cp_model_presolve.cc | 584 +++++++++++------------- ortools/sat/cp_model_presolve.h | 132 ++++++ ortools/sat/cp_model_solver.cc | 7 +- ortools/sat/disjunctive.cc | 2 +- ortools/sat/doc/test.md | 157 ------- ortools/util/csharp/proto.i | 2 +- ortools/util/csharp/tuple_set.i | 4 +- ortools/util/file_util.cc | 3 +- ortools/util/functions_swig_helpers.h | 4 +- ortools/util/java/tuple_set.i | 1 - ortools/util/python/vector.i | 2 +- 23 files changed, 569 insertions(+), 639 deletions(-) delete mode 100644 ortools/sat/doc/test.md diff --git a/makefiles/Makefile.gen.mk b/makefiles/Makefile.gen.mk index cc9347a8cf..aa69c95c6d 100644 --- a/makefiles/Makefile.gen.mk +++ b/makefiles/Makefile.gen.mk @@ -1285,8 +1285,13 @@ objs/sat/cp_model_expand.$O: ortools/sat/cp_model_expand.cc \ ortools/base/hash.h ortools/base/basictypes.h \ ortools/base/integral_types.h ortools/base/logging.h \ ortools/base/macros.h ortools/base/map_util.h \ - ortools/sat/cp_model_utils.h ortools/util/sorted_interval_list.h \ - ortools/util/saturated_arithmetic.h ortools/util/bitset.h | $(OBJ_DIR)/sat + ortools/sat/cp_model_presolve.h ortools/sat/cp_model_utils.h \ + ortools/util/sorted_interval_list.h \ + ortools/gen/ortools/sat/sat_parameters.pb.h \ + ortools/util/affine_relation.h ortools/base/iterator_adaptors.h \ + ortools/util/bitset.h ortools/util/time_limit.h \ + ortools/base/commandlineflags.h ortools/base/timer.h \ + ortools/util/running_stat.h ortools/util/saturated_arithmetic.h | $(OBJ_DIR)/sat $(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Scp_model_expand.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Scp_model_expand.$O objs/sat/cp_model_lns.$O: ortools/sat/cp_model_lns.cc \ @@ -1330,18 +1335,20 @@ objs/sat/cp_model_objective.$O: ortools/sat/cp_model_objective.cc \ objs/sat/cp_model_presolve.$O: ortools/sat/cp_model_presolve.cc \ ortools/sat/cp_model_presolve.h ortools/gen/ortools/sat/cp_model.pb.h \ - ortools/gen/ortools/sat/sat_parameters.pb.h ortools/util/time_limit.h \ - ortools/base/commandlineflags.h ortools/base/logging.h \ - ortools/base/integral_types.h ortools/base/macros.h ortools/base/timer.h \ - ortools/base/basictypes.h ortools/util/running_stat.h \ - ortools/base/hash.h ortools/base/map_util.h ortools/base/mathutil.h \ - ortools/base/stl_util.h ortools/port/proto_utils.h \ - ortools/sat/cp_model_checker.h ortools/sat/cp_model_loader.h \ - ortools/base/int_type.h ortools/base/int_type_indexed_vector.h \ - ortools/sat/cp_model_utils.h ortools/util/sorted_interval_list.h \ - ortools/sat/integer.h ortools/graph/iterators.h ortools/sat/model.h \ - ortools/base/typeid.h ortools/sat/sat_base.h ortools/util/bitset.h \ - ortools/sat/sat_solver.h ortools/sat/clause.h \ + ortools/sat/cp_model_utils.h ortools/base/integral_types.h \ + ortools/base/logging.h ortools/base/macros.h \ + ortools/util/sorted_interval_list.h \ + ortools/gen/ortools/sat/sat_parameters.pb.h \ + ortools/util/affine_relation.h ortools/base/iterator_adaptors.h \ + ortools/util/bitset.h ortools/base/basictypes.h \ + ortools/util/time_limit.h ortools/base/commandlineflags.h \ + ortools/base/timer.h ortools/util/running_stat.h ortools/base/hash.h \ + ortools/base/map_util.h ortools/base/mathutil.h ortools/base/stl_util.h \ + ortools/port/proto_utils.h ortools/sat/cp_model_checker.h \ + ortools/sat/cp_model_loader.h ortools/base/int_type.h \ + ortools/base/int_type_indexed_vector.h ortools/sat/integer.h \ + ortools/graph/iterators.h ortools/sat/model.h ortools/base/typeid.h \ + ortools/sat/sat_base.h ortools/sat/sat_solver.h ortools/sat/clause.h \ ortools/sat/drat_proof_handler.h ortools/sat/drat_checker.h \ ortools/sat/drat_writer.h ortools/base/file.h ortools/base/status.h \ ortools/util/random_engine.h ortools/util/stats.h \ @@ -1351,8 +1358,7 @@ objs/sat/cp_model_presolve.$O: ortools/sat/cp_model_presolve.cc \ ortools/sat/cp_constraints.h ortools/sat/integer_expr.h \ ortools/sat/precedences.h ortools/sat/cp_model_objective.h \ ortools/sat/probing.h ortools/sat/simplification.h \ - ortools/base/adjustable_priority_queue.h ortools/util/affine_relation.h \ - ortools/base/iterator_adaptors.h | $(OBJ_DIR)/sat + ortools/base/adjustable_priority_queue.h | $(OBJ_DIR)/sat $(CCC) $(CFLAGS) -c $(SRC_DIR)$Sortools$Ssat$Scp_model_presolve.cc $(OBJ_OUT)$(OBJ_DIR)$Ssat$Scp_model_presolve.$O objs/sat/cp_model_search.$O: ortools/sat/cp_model_search.cc \ @@ -1400,6 +1406,7 @@ objs/sat/cp_model_solver.$O: ortools/sat/cp_model_solver.cc \ ortools/sat/cp_model_utils.h ortools/sat/intervals.h \ ortools/sat/cp_constraints.h ortools/sat/integer_expr.h \ ortools/sat/precedences.h ortools/sat/cp_model_presolve.h \ + ortools/util/affine_relation.h ortools/base/iterator_adaptors.h \ ortools/sat/cp_model_search.h ortools/sat/integer_search.h \ ortools/sat/linear_programming_constraint.h \ ortools/glop/revised_simplex.h ortools/glop/basis_representation.h \ diff --git a/ortools/algorithms/dynamic_partition.cc b/ortools/algorithms/dynamic_partition.cc index d814230d69..a14735e8af 100644 --- a/ortools/algorithms/dynamic_partition.cc +++ b/ortools/algorithms/dynamic_partition.cc @@ -122,7 +122,7 @@ void DynamicPartition::Refine(const std::vector& distinguished_subset) { } // Sort affected parts. This is important to behave as advertised in the .h. - // TODO(user, fdid): automatically switch to an O(N) sort when it's faster + // TODO(user,user): automatically switch to an O(N) sort when it's faster // than this one, which is O(K log K) with K = tmp_affected_parts_.size(). std::sort(tmp_affected_parts_.begin(), tmp_affected_parts_.end()); diff --git a/ortools/flatzinc/solver_data.h b/ortools/flatzinc/solver_data.h index 9c94f129b7..0854e5cdc3 100644 --- a/ortools/flatzinc/solver_data.h +++ b/ortools/flatzinc/solver_data.h @@ -68,7 +68,7 @@ class SolverData { absl::flat_hash_map extracted_map_; // Stores a set of sorted std::vector. - // TODO(user, fdid): If it become too slow, switch to an unordered_set, it + // TODO(user,user): If it become too slow, switch to an unordered_set, it // isn't too hard to define the hash of a vector. std::set> alldiffs_; }; diff --git a/ortools/glop/revised_simplex.cc b/ortools/glop/revised_simplex.cc index 8497e0f0eb..71b29b440f 100644 --- a/ortools/glop/revised_simplex.cc +++ b/ortools/glop/revised_simplex.cc @@ -145,6 +145,7 @@ Status RevisedSimplex::Solve(const LinearProgram& lp, TimeLimit* time_limit) { // analyzes the current solver state. const double start_time = time_limit->GetElapsedTime(); GLOP_RETURN_IF_ERROR(Initialize(lp)); + dual_infeasibility_improvement_direction_.clear(); update_row_.Invalidate(); test_lu_.Clear(); @@ -157,6 +158,12 @@ Status RevisedSimplex::Solve(const LinearProgram& lp, TimeLimit* time_limit) { optimization_time_ = 0.0; total_time_ = 0.0; + // In case we abort because of an error, we cannot assume that the current + // solution state will be in sync with all our internal data structure. In + // case we abort without resetting it, setting this allow us to still use the + // previous state info, but we will double-check everything. + solution_state_has_been_set_externally_ = true; + if (VLOG_IS_ON(1)) { ComputeNumberOfEmptyRows(); ComputeNumberOfEmptyColumns(); diff --git a/ortools/linear_solver/linear_expr.h b/ortools/linear_solver/linear_expr.h index 196dd0b820..d3a3adf905 100644 --- a/ortools/linear_solver/linear_expr.h +++ b/ortools/linear_solver/linear_expr.h @@ -175,7 +175,7 @@ LinearRange operator<=(const LinearExpr& lhs, const LinearExpr& rhs); LinearRange operator==(const LinearExpr& lhs, const LinearExpr& rhs); LinearRange operator>=(const LinearExpr& lhs, const LinearExpr& rhs); -// TODO(user, ondrasej): explore defining more overloads to support: +// TODO(user,user): explore defining more overloads to support: // solver.AddRowConstraint(0.0 <= x + y + z <= 1.0); } // namespace operations_research diff --git a/ortools/linear_solver/scip_interface.cc b/ortools/linear_solver/scip_interface.cc index e7c65e64ba..b3b19a7695 100644 --- a/ortools/linear_solver/scip_interface.cc +++ b/ortools/linear_solver/scip_interface.cc @@ -714,6 +714,18 @@ void SCIPInterface::SetRelativeMipGap(double value) { } void SCIPInterface::SetPrimalTolerance(double value) { + // SCIP automatically updates numerics/lpfeastol if the primal tolerance is + // tighter. Doing that it unconditionally logs this modification to stderr. By + // setting numerics/lpfeastol first we avoid this unwanted log. + double current_lpfeastol = 0.0; + CHECK_EQ(SCIP_OKAY, + SCIPgetRealParam(scip_, "numerics/lpfeastol", ¤t_lpfeastol)); + if (value < current_lpfeastol) { + // See the NOTE on SetRelativeMipGap(). + const auto status = + TO_STATUS(SCIPsetRealParam(scip_, "numerics/lpfeastol", value)); + if (status_.ok()) status_ = status; + } // See the NOTE on SetRelativeMipGap(). const auto status = TO_STATUS(SCIPsetRealParam(scip_, "numerics/feastol", value)); diff --git a/ortools/lp_data/lp_data.cc b/ortools/lp_data/lp_data.cc index 9031db9579..db33150b56 100644 --- a/ortools/lp_data/lp_data.cc +++ b/ortools/lp_data/lp_data.cc @@ -290,7 +290,7 @@ bool LinearProgram::IsVariableInteger(ColIndex col) const { } bool LinearProgram::IsVariableBinary(ColIndex col) const { - // TODO(user, bdb): bounds of binary variables (and of integer ones) should + // TODO(user,user): bounds of binary variables (and of integer ones) should // be integer. Add a preprocessor for that. return IsVariableInteger(col) && (variable_lower_bounds_[col] < kEpsilon) && (variable_lower_bounds_[col] > Fractional(-1)) && diff --git a/ortools/lp_data/mps_reader.cc b/ortools/lp_data/mps_reader.cc index c6294187de..c01913cbac 100644 --- a/ortools/lp_data/mps_reader.cc +++ b/ortools/lp_data/mps_reader.cc @@ -235,7 +235,7 @@ void MPSReader::ProcessLine(const std::string& line) { // fixed form, the name has at most 8 characters, and starts at a specific // position in the NAME line. For MIPLIB2010 problems (eg, air04, glass4), // the name in fixed form ends up being preceded with a whitespace. - // TODO(user, bdb): Return an error for fixed form if the problem name + // TODO(user,user): Return an error for fixed form if the problem name // does not fit. data_->SetName(problem_name_); } diff --git a/ortools/port/proto_utils.h b/ortools/port/proto_utils.h index a1e646d417..870a39c6bd 100644 --- a/ortools/port/proto_utils.h +++ b/ortools/port/proto_utils.h @@ -17,7 +17,7 @@ #include #ifndef __PORTABLE_PLATFORM__ -#include "google/protobuf/generated_enum_reflection.h" +#include "google/protobuf/descriptor.h" #include "google/protobuf/text_format.h" #endif diff --git a/ortools/sat/cp_model_checker.cc b/ortools/sat/cp_model_checker.cc index 9f42f36039..6b7e924fe5 100644 --- a/ortools/sat/cp_model_checker.cc +++ b/ortools/sat/cp_model_checker.cc @@ -456,7 +456,7 @@ class ConstraintChecker { bool CumulativeConstraintIsFeasible(const CpModelProto& model, const ConstraintProto& ct) { - // TODO(user, fdid): Improve complexity for large durations. + // TODO(user,user): Improve complexity for large durations. const int64 capacity = Value(ct.cumulative().capacity()); const int num_intervals = ct.cumulative().intervals_size(); absl::flat_hash_map usage; diff --git a/ortools/sat/cp_model_expand.cc b/ortools/sat/cp_model_expand.cc index ca9cb84e3e..866aef7add 100644 --- a/ortools/sat/cp_model_expand.cc +++ b/ortools/sat/cp_model_expand.cc @@ -19,6 +19,7 @@ #include "ortools/base/hash.h" #include "ortools/base/map_util.h" #include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_presolve.h" #include "ortools/sat/cp_model_utils.h" #include "ortools/util/saturated_arithmetic.h" @@ -26,68 +27,33 @@ namespace operations_research { namespace sat { namespace { -struct ExpansionContext { - CpModelProto working_model; +void ExpandReservoir(ConstraintProto* ct, PresolveContext* context) { + // TODO(user): Support sharing constraints in the model across constraints. absl::flat_hash_map, int> precedence_cache; - std::map stats_by_rule_name; - static const int kAlwaysTrue = kint32min; + const ReservoirConstraintProto& reservoir = ct->reservoir(); + const int num_variables = reservoir.times_size(); - void UpdateRuleStats(const std::string& rule) { stats_by_rule_name[rule]++; } - - // a => b. - void AddImplication(int a, int b) { - ConstraintProto* const ct = working_model.add_constraints(); - ct->add_enforcement_literal(a); - ct->mutable_bool_and()->add_literals(b); - } - - // b => x in [lb, ub]. - void AddImplyInDomain(int b, int x, int64 lb, int64 ub) { - ConstraintProto* const imply = working_model.add_constraints(); - imply->add_enforcement_literal(b); - imply->mutable_linear()->add_vars(x); - imply->mutable_linear()->add_coeffs(1); - imply->mutable_linear()->add_domain(lb); - imply->mutable_linear()->add_domain(ub); - } - - int AddIntVar(int64 lb, int64 ub) { - IntegerVariableProto* const var = working_model.add_variables(); - var->add_domain(lb); - var->add_domain(ub); - return working_model.variables_size() - 1; - } - - int AddBoolVar() { return AddIntVar(0, 1); } - - void AddBoolOr(const std::vector& literals) { - BoolArgumentProto* const bool_or = - working_model.add_constraints()->mutable_bool_or(); - for (const int lit : literals) { - bool_or->add_literals(lit); - } - } - - // lesseq_0 <=> (x <= 0 && lit is true). - void AddReifiedLessOrEqualThanZero(int lesseq_0, int x, int lit) { - AddImplyInDomain(lesseq_0, x, kint64min, 0); - if (lit == kAlwaysTrue) { - AddImplyInDomain(NegatedRef(lesseq_0), x, 1, kint64max); - } else { - // conjunction <=> lit && not(lesseq_0). - const int conjunction = AddBoolVar(); - AddImplication(conjunction, lit); - AddImplication(conjunction, NegatedRef(lesseq_0)); - AddBoolOr({NegatedRef(lit), lesseq_0, conjunction}); - - AddImplyInDomain(conjunction, x, 1, kint64max); - } - } + auto is_optional = [&context, &reservoir](int index) { + if (reservoir.actives_size() == 0) return false; + const int literal = reservoir.actives(index); + const int ref = PositiveRef(literal); + const IntegerVariableProto& var_proto = + context->working_model->variables(ref); + return var_proto.domain_size() != 2 || + var_proto.domain(0) != var_proto.domain(1); + }; + const int true_literal = context->GetOrCreateConstantVar(1); + auto active = [&reservoir, true_literal](int index) { + if (reservoir.actives_size() == 0) return true_literal; + return reservoir.actives(index); + }; // x_lesseq_y <=> (x <= y && l_x is true && l_y is true). - void AddReifiedPrecedence(int x_lesseq_y, int x, int y, int l_x, int l_y) { + const auto add_reified_precedence = [&context, true_literal]( + int x_lesseq_y, int x, int y, int l_x, + int l_y) { // x_lesseq_y => (x <= y) && l_x is true && l_y is true. - ConstraintProto* const lesseq = working_model.add_constraints(); + ConstraintProto* const lesseq = context->working_model->add_constraints(); lesseq->add_enforcement_literal(x_lesseq_y); lesseq->mutable_linear()->add_vars(x); lesseq->mutable_linear()->add_vars(y); @@ -95,15 +61,15 @@ struct ExpansionContext { lesseq->mutable_linear()->add_coeffs(1); lesseq->mutable_linear()->add_domain(0); lesseq->mutable_linear()->add_domain(kint64max); - if (l_x != kAlwaysTrue) { - AddImplication(x_lesseq_y, l_x); + if (l_x != true_literal) { + context->AddImplication(x_lesseq_y, l_x); } - if (l_y != kAlwaysTrue) { - AddImplication(x_lesseq_y, l_y); + if (l_y != true_literal) { + context->AddImplication(x_lesseq_y, l_y); } // Not(x_lesseq_y) && l_x && l_y => (x > y) - ConstraintProto* const greater = working_model.add_constraints(); + ConstraintProto* const greater = context->working_model->add_constraints(); greater->mutable_linear()->add_vars(x); greater->mutable_linear()->add_vars(y); greater->mutable_linear()->add_coeffs(-1); @@ -111,44 +77,28 @@ struct ExpansionContext { greater->mutable_linear()->add_domain(kint64min); greater->mutable_linear()->add_domain(-1); // Manages enforcement literal. - if (l_x == kAlwaysTrue && l_y == kAlwaysTrue) { + if (l_x == true_literal && l_y == true_literal) { greater->add_enforcement_literal(NegatedRef(x_lesseq_y)); } else { // conjunction <=> l_x && l_y && not(x_lesseq_y). - const int conjunction = AddBoolVar(); - std::vector literals = {conjunction, x_lesseq_y}; - AddImplication(conjunction, NegatedRef(x_lesseq_y)); - if (l_x != kAlwaysTrue) { - AddImplication(conjunction, l_x); - literals.push_back(NegatedRef(l_x)); + const int conjunction = context->NewBoolVar(); + context->AddImplication(conjunction, NegatedRef(x_lesseq_y)); + BoolArgumentProto* const bool_or = + context->working_model->add_constraints()->mutable_bool_or(); + bool_or->add_literals(conjunction); + bool_or->add_literals(x_lesseq_y); + + if (l_x != true_literal) { + context->AddImplication(conjunction, l_x); + bool_or->add_literals(NegatedRef(l_x)); } - if (l_y != kAlwaysTrue) { - AddImplication(conjunction, l_y); - literals.push_back(NegatedRef(l_y)); + if (l_y != true_literal) { + context->AddImplication(conjunction, l_y); + bool_or->add_literals(NegatedRef(l_y)); } - AddBoolOr(literals); greater->add_enforcement_literal(conjunction); } - } -}; - -void ExpandReservoir(ConstraintProto* ct, ExpansionContext* context) { - const ReservoirConstraintProto& reservoir = ct->reservoir(); - const int num_variables = reservoir.times_size(); - CpModelProto& working_model = context->working_model; - - auto is_optional = [&working_model, &reservoir](int index) { - if (reservoir.actives_size() == 0) return false; - const int literal = reservoir.actives(index); - const int ref = PositiveRef(literal); - const IntegerVariableProto& var_proto = working_model.variables(ref); - return var_proto.domain_size() != 2 || - var_proto.domain(0) != var_proto.domain(1); - }; - auto active = [&reservoir, &context](int index) { - if (reservoir.actives_size() == 0) return context->kAlwaysTrue; - return reservoir.actives(index); }; int num_positives = 0; @@ -169,20 +119,20 @@ void ExpandReservoir(ConstraintProto* ct, ExpansionContext* context) { const int time_j = reservoir.times(j); const std::pair p = std::make_pair(time_i, time_j); const std::pair rev_p = std::make_pair(time_j, time_i); - if (gtl::ContainsKey(context->precedence_cache, p)) continue; + if (gtl::ContainsKey(precedence_cache, p)) continue; - const int i_lesseq_j = context->AddBoolVar(); - context->precedence_cache[p] = i_lesseq_j; - const int j_lesseq_i = context->AddBoolVar(); - context->precedence_cache[rev_p] = j_lesseq_i; - context->AddReifiedPrecedence(i_lesseq_j, time_i, time_j, active(i), - active(j)); - context->AddReifiedPrecedence(j_lesseq_i, time_j, time_i, active(j), - active(i)); + const int i_lesseq_j = context->NewBoolVar(); + precedence_cache[p] = i_lesseq_j; + const int j_lesseq_i = context->NewBoolVar(); + precedence_cache[rev_p] = j_lesseq_i; + add_reified_precedence(i_lesseq_j, time_i, time_j, active(i), + active(j)); + add_reified_precedence(j_lesseq_i, time_j, time_i, active(j), + active(i)); // Consistency. This is redundant but should improves performance. auto* const bool_or = - working_model.add_constraints()->mutable_bool_or(); + context->working_model->add_constraints()->mutable_bool_or(); bool_or->add_literals(i_lesseq_j); bool_or->add_literals(j_lesseq_i); if (is_optional(i)) { @@ -201,12 +151,12 @@ void ExpandReservoir(ConstraintProto* ct, ExpansionContext* context) { for (int i = 0; i < num_variables; ++i) { const int time_i = reservoir.times(i); // Accumulates demands of all predecessors. - ConstraintProto* const level = working_model.add_constraints(); + ConstraintProto* const level = context->working_model->add_constraints(); for (int j = 0; j < num_variables; ++j) { if (i == j) continue; const int time_j = reservoir.times(j); level->mutable_linear()->add_vars(gtl::FindOrDieNoPrint( - context->precedence_cache, std::make_pair(time_j, time_i))); + precedence_cache, std::make_pair(time_j, time_i))); level->mutable_linear()->add_coeffs(reservoir.demands(j)); } // Accounts for own demand. @@ -223,7 +173,8 @@ void ExpandReservoir(ConstraintProto* ct, ExpansionContext* context) { // If all demands have the same sign, we do not care about the order, just // the sum. int64 fixed_demand = 0; - auto* const sum = working_model.add_constraints()->mutable_linear(); + auto* const sum = + context->working_model->add_constraints()->mutable_linear(); for (int i = 0; i < num_variables; ++i) { const int64 demand = reservoir.demands(i); if (demand == 0) continue; @@ -242,11 +193,30 @@ void ExpandReservoir(ConstraintProto* ct, ExpansionContext* context) { // We need to do it only if 0 is not in [min_level..max_level]. // Otherwise, the regular propagation will already check it. if (reservoir.min_level() > 0 || reservoir.max_level() < 0) { - auto* const initial_ct = working_model.add_constraints()->mutable_linear(); + auto* const initial_ct = + context->working_model->add_constraints()->mutable_linear(); for (int i = 0; i < num_variables; ++i) { const int time_i = reservoir.times(i); - const int lesseq_0 = context->AddBoolVar(); - context->AddReifiedLessOrEqualThanZero(lesseq_0, time_i, active(i)); + const int lesseq_0 = context->NewBoolVar(); + // lesseq_0 <=> (x <= 0 && lit is true). + context->AddImplyInDomain(lesseq_0, time_i, Domain(kint64min, 0)); + if (active(i) == true_literal) { + context->AddImplyInDomain(NegatedRef(lesseq_0), time_i, + Domain(1, kint64max)); + } else { + // conjunction <=> lit && not(lesseq_0). + const int conjunction = context->NewBoolVar(); + context->AddImplication(conjunction, active(i)); + context->AddImplication(conjunction, NegatedRef(lesseq_0)); + BoolArgumentProto* const bool_or = + context->working_model->add_constraints()->mutable_bool_or(); + bool_or->add_literals(NegatedRef(active(i))); + bool_or->add_literals(lesseq_0); + bool_or->add_literals(conjunction); + + context->AddImplyInDomain(conjunction, time_i, Domain(1, kint64max)); + } + initial_ct->add_vars(lesseq_0); initial_ct->add_coeffs(reservoir.demands(i)); } @@ -258,12 +228,12 @@ void ExpandReservoir(ConstraintProto* ct, ExpansionContext* context) { context->UpdateRuleStats("reservoir: expanded"); } -void ExpandIntMod(ConstraintProto* ct, ExpansionContext* context) { +void ExpandIntMod(ConstraintProto* ct, PresolveContext* context) { const IntegerArgumentProto& int_mod = ct->int_mod(); const IntegerVariableProto& var_proto = - context->working_model.variables(int_mod.vars(0)); + context->working_model->variables(int_mod.vars(0)); const IntegerVariableProto& mod_proto = - context->working_model.variables(int_mod.vars(1)); + context->working_model->variables(int_mod.vars(1)); const int target_var = int_mod.target(); const int64 mod_lb = mod_proto.domain(0); @@ -274,19 +244,20 @@ void ExpandIntMod(ConstraintProto* ct, ExpansionContext* context) { const int64 var_ub = var_proto.domain(var_proto.domain_size() - 1); // Compute domains of var / mod_proto. - const int div_var = context->AddIntVar(var_lb / mod_ub, var_ub / mod_lb); + const int div_var = + context->NewIntVar(Domain(var_lb / mod_ub, var_ub / mod_lb)); auto add_enforcement_literal_if_needed = [&]() { if (ct->enforcement_literal_size() == 0) return; const int literal = ct->enforcement_literal(0); - ConstraintProto* const last = context->working_model.mutable_constraints( - context->working_model.constraints_size() - 1); + ConstraintProto* const last = context->working_model->mutable_constraints( + context->working_model->constraints_size() - 1); last->add_enforcement_literal(literal); }; // div = var / mod. IntegerArgumentProto* const div_proto = - context->working_model.add_constraints()->mutable_int_div(); + context->working_model->add_constraints()->mutable_int_div(); div_proto->set_target(div_var); div_proto->add_vars(int_mod.vars(0)); div_proto->add_vars(int_mod.vars(1)); @@ -296,7 +267,7 @@ void ExpandIntMod(ConstraintProto* ct, ExpansionContext* context) { if (mod_lb == mod_ub) { // var - div_var * mod = target. LinearConstraintProto* const lin = - context->working_model.add_constraints()->mutable_linear(); + context->working_model->add_constraints()->mutable_linear(); lin->add_vars(int_mod.vars(0)); lin->add_coeffs(1); lin->add_vars(div_var); @@ -309,10 +280,10 @@ void ExpandIntMod(ConstraintProto* ct, ExpansionContext* context) { } else { // Create prod_var = div_var * mod. const int mod_var = int_mod.vars(1); - const int prod_var = - context->AddIntVar(var_lb * mod_lb / mod_ub, var_ub * mod_ub / mod_lb); + const int prod_var = context->NewIntVar( + Domain(var_lb * mod_lb / mod_ub, var_ub * mod_ub / mod_lb)); IntegerArgumentProto* const int_prod = - context->working_model.add_constraints()->mutable_int_prod(); + context->working_model->add_constraints()->mutable_int_prod(); int_prod->set_target(prod_var); int_prod->add_vars(div_var); int_prod->add_vars(mod_var); @@ -320,7 +291,7 @@ void ExpandIntMod(ConstraintProto* ct, ExpansionContext* context) { // var - prod_var = target. LinearConstraintProto* const lin = - context->working_model.add_constraints()->mutable_linear(); + context->working_model->add_constraints()->mutable_linear(); lin->add_vars(int_mod.vars(0)); lin->add_coeffs(1); lin->add_vars(prod_var); @@ -337,8 +308,8 @@ void ExpandIntMod(ConstraintProto* ct, ExpansionContext* context) { } void ExpandIntProdWithBoolean(int bool_ref, int int_ref, int product_ref, - ExpansionContext* context) { - ConstraintProto* const one = context->working_model.add_constraints(); + PresolveContext* context) { + ConstraintProto* const one = context->working_model->add_constraints(); one->add_enforcement_literal(bool_ref); one->mutable_linear()->add_vars(int_ref); one->mutable_linear()->add_coeffs(1); @@ -347,7 +318,7 @@ void ExpandIntProdWithBoolean(int bool_ref, int int_ref, int product_ref, one->mutable_linear()->add_domain(0); one->mutable_linear()->add_domain(0); - ConstraintProto* const zero = context->working_model.add_constraints(); + ConstraintProto* const zero = context->working_model->add_constraints(); zero->add_enforcement_literal(NegatedRef(bool_ref)); zero->mutable_linear()->add_vars(product_ref); zero->mutable_linear()->add_coeffs(1); @@ -355,15 +326,15 @@ void ExpandIntProdWithBoolean(int bool_ref, int int_ref, int product_ref, zero->mutable_linear()->add_domain(0); } -void ExpandIntProd(ConstraintProto* ct, ExpansionContext* context) { +void ExpandIntProd(ConstraintProto* ct, PresolveContext* context) { const IntegerArgumentProto& int_prod = ct->int_prod(); if (int_prod.vars_size() != 2) return; const int a = int_prod.vars(0); const int b = int_prod.vars(1); const IntegerVariableProto& a_proto = - context->working_model.variables(PositiveRef(a)); + context->working_model->variables(PositiveRef(a)); const IntegerVariableProto& b_proto = - context->working_model.variables(PositiveRef(b)); + context->working_model->variables(PositiveRef(b)); const int p = int_prod.target(); const bool a_is_boolean = RefIsPositive(a) && a_proto.domain_size() == 2 && a_proto.domain(0) == 0 && a_proto.domain(1) == 1; @@ -385,12 +356,12 @@ void ExpandIntProd(ConstraintProto* ct, ExpansionContext* context) { } // namespace -CpModelProto ExpandCpModel(const CpModelProto& initial_model, bool log) { - ExpansionContext context; - context.working_model = initial_model; - const int num_constraints = context.working_model.constraints_size(); +void ExpandCpModel(CpModelProto* working_model, bool log) { + PresolveContext context; + context.working_model = working_model; + const int num_constraints = context.working_model->constraints_size(); for (int i = 0; i < num_constraints; ++i) { - ConstraintProto* const ct = context.working_model.mutable_constraints(i); + ConstraintProto* const ct = context.working_model->mutable_constraints(i); switch (ct->constraint_case()) { case ConstraintProto::ConstraintCase::kReservoir: ExpandReservoir(ct, &context); @@ -418,8 +389,7 @@ CpModelProto ExpandCpModel(const CpModelProto& initial_model, bool log) { } } } - - return context.working_model; } + } // namespace sat } // namespace operations_research diff --git a/ortools/sat/cp_model_expand.h b/ortools/sat/cp_model_expand.h index f454036649..edf3eb95a7 100644 --- a/ortools/sat/cp_model_expand.h +++ b/ortools/sat/cp_model_expand.h @@ -23,7 +23,7 @@ namespace sat { // simpler constraints. // This is different from PresolveCpModel() as there are no reduction or // simplification of the model. Furthermore, this expansion is mandatory. -CpModelProto ExpandCpModel(const CpModelProto& initial_model, bool log); +void ExpandCpModel(CpModelProto* working_model, bool log); } // namespace sat } // namespace operations_research diff --git a/ortools/sat/cp_model_presolve.cc b/ortools/sat/cp_model_presolve.cc index 1ec7684bac..84f1bf3867 100644 --- a/ortools/sat/cp_model_presolve.cc +++ b/ortools/sat/cp_model_presolve.cc @@ -38,345 +38,298 @@ #include "ortools/sat/cp_model_checker.h" #include "ortools/sat/cp_model_loader.h" #include "ortools/sat/cp_model_objective.h" -#include "ortools/sat/cp_model_utils.h" #include "ortools/sat/probing.h" #include "ortools/sat/sat_base.h" #include "ortools/sat/sat_parameters.pb.h" #include "ortools/sat/simplification.h" -#include "ortools/util/affine_relation.h" -#include "ortools/util/bitset.h" -#include "ortools/util/sorted_interval_list.h" namespace operations_research { namespace sat { -namespace { -// Wrap the CpModelProto we are presolving with extra data structure like the -// in-memory domain of each variables and the constraint variable graph. -struct PresolveContext { - bool DomainIsEmpty(int ref) const { - return domains[PositiveRef(ref)].IsEmpty(); +int PresolveContext::NewIntVar(const Domain& domain) { + IntegerVariableProto* const var = working_model->add_variables(); + FillDomainInProto(domain, var); + return working_model->variables_size() - 1; +} + +int PresolveContext::NewBoolVar() { return NewIntVar(Domain(0, 1)); } + +int PresolveContext::GetOrCreateConstantVar(int64 cst) { + if (!gtl::ContainsKey(constant_to_ref, cst)) { + constant_to_ref[cst] = working_model->variables_size(); + IntegerVariableProto* const var_proto = working_model->add_variables(); + var_proto->add_domain(cst); + var_proto->add_domain(cst); + } + return constant_to_ref[cst]; +} + +// a => b. +void PresolveContext::AddImplication(int a, int b) { + ConstraintProto* const ct = working_model->add_constraints(); + ct->add_enforcement_literal(a); + ct->mutable_bool_and()->add_literals(b); +} + +// b => x in [lb, ub]. +void PresolveContext::AddImplyInDomain(int b, int x, const Domain& domain) { + ConstraintProto* const imply = working_model->add_constraints(); + imply->add_enforcement_literal(b); + imply->mutable_linear()->add_vars(x); + imply->mutable_linear()->add_coeffs(1); + FillDomainInProto(domain, imply->mutable_linear()); +} + +bool PresolveContext::DomainIsEmpty(int ref) const { + return domains[PositiveRef(ref)].IsEmpty(); +} + +bool PresolveContext::IsFixed(int ref) const { + CHECK(!DomainIsEmpty(ref)); + return domains[PositiveRef(ref)].Min() == domains[PositiveRef(ref)].Max(); +} + +bool PresolveContext::LiteralIsTrue(int lit) const { + if (!IsFixed(lit)) return false; + if (RefIsPositive(lit)) { + return domains[lit].Min() == 1ll; + } else { + return domains[PositiveRef(lit)].Max() == 0ll; + } +} + +bool PresolveContext::LiteralIsFalse(int lit) const { + if (!IsFixed(lit)) return false; + if (RefIsPositive(lit)) { + return domains[lit].Max() == 0ll; + } else { + return domains[PositiveRef(lit)].Min() == 1ll; + } +} + +int64 PresolveContext::MinOf(int ref) const { + CHECK(!DomainIsEmpty(ref)); + return RefIsPositive(ref) ? domains[PositiveRef(ref)].Min() + : -domains[PositiveRef(ref)].Max(); +} + +int64 PresolveContext::MaxOf(int ref) const { + CHECK(!DomainIsEmpty(ref)); + return RefIsPositive(ref) ? domains[PositiveRef(ref)].Max() + : -domains[PositiveRef(ref)].Min(); +} + +bool PresolveContext::VariableIsUniqueAndRemovable(int ref) const { + return var_to_constraints[PositiveRef(ref)].size() == 1 && + !enumerate_all_solutions; +} + +Domain PresolveContext::DomainOf(int ref) const { + if (RefIsPositive(ref)) return domains[ref]; + return domains[PositiveRef(ref)].Negation(); +} + +bool PresolveContext::IntersectDomainWith(int ref, const Domain& domain) { + CHECK(!DomainIsEmpty(ref)); + const int var = PositiveRef(ref); + + if (RefIsPositive(ref)) { + if (domains[var].IsIncludedIn(domain)) return false; + domains[var] = domains[var].IntersectionWith(domain); + } else { + const Domain temp = domain.Negation(); + if (domains[var].IsIncludedIn(temp)) return false; + domains[var] = domains[var].IntersectionWith(temp); } - bool IsFixed(int ref) const { - CHECK(!DomainIsEmpty(ref)); - return domains[PositiveRef(ref)].Min() == domains[PositiveRef(ref)].Max(); - } + modified_domains.Set(var); + if (domains[var].IsEmpty()) is_unsat = true; + return true; +} - bool LiteralIsTrue(int lit) const { - if (!IsFixed(lit)) return false; - if (RefIsPositive(lit)) { - return domains[lit].Min() == 1ll; - } else { - return domains[PositiveRef(lit)].Max() == 0ll; +void PresolveContext::SetLiteralToFalse(int lit) { + const int var = PositiveRef(lit); + const int64 value = RefIsPositive(lit) ? 0ll : 1ll; + if (IsFixed(var)) { + const int64 fixed_value = MinOf(var); + if (value != fixed_value) { + is_unsat = true; } + } else { + IntersectDomainWith(var, Domain(value)); } +} - bool LiteralIsFalse(int lit) const { - if (!IsFixed(lit)) return false; - if (RefIsPositive(lit)) { - return domains[lit].Max() == 0ll; - } else { - return domains[PositiveRef(lit)].Min() == 1ll; - } - } +void PresolveContext::SetLiteralToTrue(int lit) { + return SetLiteralToFalse(NegatedRef(lit)); +} - int64 MinOf(int ref) const { - CHECK(!DomainIsEmpty(ref)); - return RefIsPositive(ref) ? domains[PositiveRef(ref)].Min() - : -domains[PositiveRef(ref)].Max(); - } +void PresolveContext::UpdateRuleStats(const std::string& name) { + stats_by_rule_name[name]++; +} - int64 MaxOf(int ref) const { - CHECK(!DomainIsEmpty(ref)); - return RefIsPositive(ref) ? domains[PositiveRef(ref)].Max() - : -domains[PositiveRef(ref)].Min(); - } +void PresolveContext::UpdateConstraintVariableUsage(int c) { + CHECK_EQ(constraint_to_vars.size(), working_model->constraints_size()); + const ConstraintProto& ct = working_model->constraints(c); + for (const int v : constraint_to_vars[c]) var_to_constraints[v].erase(c); + constraint_to_vars[c] = UsedVariables(ct); + for (const int v : constraint_to_vars[c]) var_to_constraints[v].insert(c); +} - // Returns true if this ref only appear in one constraint. - bool VariableIsUniqueAndRemovable(int ref) const { - return var_to_constraints[PositiveRef(ref)].size() == 1 && - !enumerate_all_solutions; - } - - Domain DomainOf(int ref) const { - if (RefIsPositive(ref)) return domains[ref]; - return domains[PositiveRef(ref)].Negation(); - } - - // Returns true iff the domain changed. - bool IntersectDomainWith(int ref, const Domain& domain) { - CHECK(!DomainIsEmpty(ref)); - const int var = PositiveRef(ref); - - if (RefIsPositive(ref)) { - if (domains[var].IsIncludedIn(domain)) return false; - domains[var] = domains[var].IntersectionWith(domain); - } else { - const Domain temp = domain.Negation(); - if (domains[var].IsIncludedIn(temp)) return false; - domains[var] = domains[var].IntersectionWith(temp); - } - - modified_domains.Set(var); - if (domains[var].IsEmpty()) is_unsat = true; - return true; - } - - void SetLiteralToFalse(int lit) { - const int var = PositiveRef(lit); - const int64 value = RefIsPositive(lit) ? 0ll : 1ll; - if (IsFixed(var)) { - const int64 fixed_value = MinOf(var); - if (value != fixed_value) { - is_unsat = true; - } - } else { - IntersectDomainWith(var, Domain(value)); - } - } - - void SetLiteralToTrue(int lit) { return SetLiteralToFalse(NegatedRef(lit)); } - - void UpdateRuleStats(const std::string& name) { stats_by_rule_name[name]++; } - - // Update the constraints <-> variables graph. This needs to be called each - // time a constraint is modified. - void UpdateConstraintVariableUsage(int c) { - CHECK_EQ(constraint_to_vars.size(), working_model->constraints_size()); - const ConstraintProto& ct = working_model->constraints(c); - for (const int v : constraint_to_vars[c]) var_to_constraints[v].erase(c); - constraint_to_vars[c] = UsedVariables(ct); +void PresolveContext::UpdateNewConstraintsVariableUsage() { + const int old_size = constraint_to_vars.size(); + const int new_size = working_model->constraints_size(); + CHECK_LE(old_size, new_size); + constraint_to_vars.resize(new_size); + for (int c = old_size; c < new_size; ++c) { + constraint_to_vars[c] = UsedVariables(working_model->constraints(c)); for (const int v : constraint_to_vars[c]) var_to_constraints[v].insert(c); } +} - // Calls UpdateConstraintVariableUsage() on all newly created constraints. - void UpdateNewConstraintsVariableUsage() { - const int old_size = constraint_to_vars.size(); - const int new_size = working_model->constraints_size(); - CHECK_LE(old_size, new_size); - constraint_to_vars.resize(new_size); - for (int c = old_size; c < new_size; ++c) { - constraint_to_vars[c] = UsedVariables(working_model->constraints(c)); - for (const int v : constraint_to_vars[c]) var_to_constraints[v].insert(c); - } +bool PresolveContext::ConstraintVariableUsageIsConsistent() { + if (is_unsat) return true; + if (constraint_to_vars.size() != working_model->constraints_size()) { + LOG(INFO) << "Wrong constraint_to_vars size!"; + return false; } - - // Returns true if our current constraints <-> variables graph is ok. - // This is meant to be used in DEBUG mode only. - bool ConstraintVariableUsageIsConsistent() { - if (is_unsat) return true; - if (constraint_to_vars.size() != working_model->constraints_size()) { - LOG(INFO) << "Wrong constraint_to_vars size!"; + for (int c = 0; c < constraint_to_vars.size(); ++c) { + if (constraint_to_vars[c] != UsedVariables(working_model->constraints(c))) { + LOG(INFO) << "Wrong variables usage for constraint: \n" + << ProtobufDebugString(working_model->constraints(c)); return false; } - for (int c = 0; c < constraint_to_vars.size(); ++c) { - if (constraint_to_vars[c] != - UsedVariables(working_model->constraints(c))) { - LOG(INFO) << "Wrong variables usage for constraint: \n" - << ProtobufDebugString(working_model->constraints(c)); - return false; - } + } + return true; +} + +void PresolveContext::ExploitFixedDomain(int var) { + CHECK(IsFixed(var)); + const int min = MinOf(var); + if (gtl::ContainsKey(constant_to_ref, min)) { + const int representative = constant_to_ref[min]; + if (representative != var) { + affine_relations.TryAdd(var, representative, 1, 0); + var_equiv_relations.TryAdd(var, representative, 1, 0); } - return true; + } else { + constant_to_ref[min] = var; + } +} + +void PresolveContext::StoreAffineRelation(const ConstraintProto& ct, int ref_x, + int ref_y, int64 coeff, + int64 offset) { + int x = PositiveRef(ref_x); + int y = PositiveRef(ref_y); + if (IsFixed(x) || IsFixed(y)) return; + + int64 c = RefIsPositive(ref_x) == RefIsPositive(ref_y) ? coeff : -coeff; + int64 o = RefIsPositive(ref_x) ? offset : -offset; + const int rep_x = affine_relations.Get(x).representative; + const int rep_y = affine_relations.Get(y).representative; + + // If a Boolean variable (one with domain [0, 1]) appear in this affine + // equivalence class, then we want its representative to be Boolean. Note + // that this is always possible because a Boolean variable can never be + // equal to a multiple of another if std::abs(coeff) is greater than 1 and + // if it is not fixed to zero. This is important because it allows to simply + // use the same representative for any referenced literals. + bool allow_rep_x = MinOf(rep_x) == 0 && MaxOf(rep_x) == 1; + bool allow_rep_y = MinOf(rep_y) == 0 && MaxOf(rep_y) == 1; + if (!allow_rep_x && !allow_rep_y) { + // If none are Boolean, we can use any representative. + allow_rep_x = true; + allow_rep_y = true; } - int NewConstant(int64 cst) { - if (!gtl::ContainsKey(constant_to_ref, cst)) { - constant_to_ref[cst] = working_model->variables_size(); - IntegerVariableProto* const var_proto = working_model->add_variables(); - var_proto->add_domain(cst); - var_proto->add_domain(cst); - } - return constant_to_ref[cst]; + // TODO(user): can we force the rep and remove GetAffineRelation()? + bool added = affine_relations.TryAdd(x, y, c, o, allow_rep_x, allow_rep_y); + if ((c == 1 || c == -1) && o == 0) { + added |= var_equiv_relations.TryAdd(x, y, c, o, allow_rep_x, allow_rep_y); } - - // Regroups fixed variables with the same value. - // TODO(user): Also regroup cte and -cte? - void ExploitFixedDomain(int var) { - CHECK(IsFixed(var)); - const int min = MinOf(var); - if (gtl::ContainsKey(constant_to_ref, min)) { - const int representative = constant_to_ref[min]; - if (representative != var) { - affine_relations.TryAdd(var, representative, 1, 0); - var_equiv_relations.TryAdd(var, representative, 1, 0); - } - } else { - constant_to_ref[min] = var; - } + if (added) { + // The domain didn't change, but this notification allows to re-process + // any constraint containing these variables. + modified_domains.Set(x); + modified_domains.Set(y); + affine_constraints.insert(&ct); } +} - // Adds the relation (ref_x = coeff * ref_y + offset) to the repository. - void AddAffineRelation(const ConstraintProto& ct, int ref_x, int ref_y, - int64 coeff, int64 offset) { - int x = PositiveRef(ref_x); - int y = PositiveRef(ref_y); - if (IsFixed(x) || IsFixed(y)) return; - - int64 c = RefIsPositive(ref_x) == RefIsPositive(ref_y) ? coeff : -coeff; - int64 o = RefIsPositive(ref_x) ? offset : -offset; - const int rep_x = affine_relations.Get(x).representative; - const int rep_y = affine_relations.Get(y).representative; - - // If a Boolean variable (one with domain [0, 1]) appear in this affine - // equivalence class, then we want its representative to be Boolean. Note - // that this is always possible because a Boolean variable can never be - // equal to a multiple of another if std::abs(coeff) is greater than 1 and - // if it is not fixed to zero. This is important because it allows to simply - // use the same representative for any referenced literals. - bool allow_rep_x = MinOf(rep_x) == 0 && MaxOf(rep_x) == 1; - bool allow_rep_y = MinOf(rep_y) == 0 && MaxOf(rep_y) == 1; - if (!allow_rep_x && !allow_rep_y) { - // If none are Boolean, we can use any representative. - allow_rep_x = true; - allow_rep_y = true; - } - - // TODO(user): can we force the rep and remove GetAffineRelation()? - bool added = affine_relations.TryAdd(x, y, c, o, allow_rep_x, allow_rep_y); - if ((c == 1 || c == -1) && o == 0) { - added |= var_equiv_relations.TryAdd(x, y, c, o, allow_rep_x, allow_rep_y); - } - if (added) { - // The domain didn't change, but this notification allows to re-process - // any constraint containing these variables. - modified_domains.Set(x); - modified_domains.Set(y); - affine_constraints.insert(&ct); - } +void PresolveContext::StoreBooleanEqualityRelation(int ref_a, int ref_b) { + if (ref_a == ref_b) return; + if (ref_a == NegatedRef(ref_b)) { + is_unsat = true; + return; } - - void AddBooleanEqualityRelation(int ref_a, int ref_b) { - if (ref_a == ref_b) return; - if (ref_a == NegatedRef(ref_b)) { - is_unsat = true; - return; - } - bool added = false; - if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) { - added |= - affine_relations.TryAdd(PositiveRef(ref_a), PositiveRef(ref_b), 1, 0); - added |= var_equiv_relations.TryAdd(PositiveRef(ref_a), - PositiveRef(ref_b), 1, 0); - } else { - added |= affine_relations.TryAdd(PositiveRef(ref_a), PositiveRef(ref_b), - -1, 1); - } - if (!added) return; - - modified_domains.Set(PositiveRef(ref_a)); - modified_domains.Set(PositiveRef(ref_b)); - - // For now, we do need to add the relation ref_a == ref_b so we have a - // proper variable usage count and propagation between ref_a and ref_b. - // - // TODO(user): This looks unclean. We should probably handle the affine - // relation together without the need of keep all the constraints that - // define them around. - ConstraintProto* ct = working_model->add_constraints(); - auto* arg = ct->mutable_linear(); - arg->add_vars(PositiveRef(ref_a)); - arg->add_vars(PositiveRef(ref_b)); - if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) { - // a = b - arg->add_coeffs(1); - arg->add_coeffs(-1); - arg->add_domain(0); - arg->add_domain(0); - } else { - // a = 1 - b - arg->add_coeffs(1); - arg->add_coeffs(1); - arg->add_domain(1); - arg->add_domain(1); - } - affine_constraints.insert(ct); - UpdateNewConstraintsVariableUsage(); + bool added = false; + if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) { + added |= + affine_relations.TryAdd(PositiveRef(ref_a), PositiveRef(ref_b), 1, 0); + added |= var_equiv_relations.TryAdd(PositiveRef(ref_a), PositiveRef(ref_b), + 1, 0); + } else { + added |= + affine_relations.TryAdd(PositiveRef(ref_a), PositiveRef(ref_b), -1, 1); } + if (!added) return; - // This makes sure that the affine relation only uses one of the - // representative from the var_equiv_relations. - AffineRelation::Relation GetAffineRelation(int ref) { - AffineRelation::Relation r = affine_relations.Get(PositiveRef(ref)); - AffineRelation::Relation o = var_equiv_relations.Get(r.representative); - r.representative = o.representative; - if (o.coeff == -1) r.coeff = -r.coeff; - if (!RefIsPositive(ref)) { - r.coeff *= -1; - r.offset *= -1; - } - return r; - } + modified_domains.Set(PositiveRef(ref_a)); + modified_domains.Set(PositiveRef(ref_b)); - // Create the internal structure for any new variables in working_model. - void InitializeNewDomains() { - for (int i = domains.size(); i < working_model->variables_size(); ++i) { - domains.push_back(ReadDomainFromProto(working_model->variables(i))); - if (IsFixed(i)) ExploitFixedDomain(i); - } - modified_domains.Resize(domains.size()); - var_to_constraints.resize(domains.size()); - } - - // This regroup all the affine relations between variables. Note that the - // constraints used to detect such relations will not be removed from the - // model at detection time (thus allowing proper domain propagation). However, - // if the arity of a variable becomes one, then such constraint will be - // removed. - AffineRelation affine_relations; - AffineRelation var_equiv_relations; - - // Set of constraint that implies an "affine relation". We need to mark them, - // because we can't simplify them using the relation they added. + // For now, we do need to add the relation ref_a == ref_b so we have a + // proper variable usage count and propagation between ref_a and ref_b. // - // WARNING: This assumes the ConstraintProto* to stay valid during the full - // presolve even if we add new constraint to the CpModelProto. - absl::flat_hash_set affine_constraints; + // TODO(user): This looks unclean. We should probably handle the affine + // relation together without the need of keep all the constraints that + // define them around. + ConstraintProto* ct = working_model->add_constraints(); + auto* arg = ct->mutable_linear(); + arg->add_vars(PositiveRef(ref_a)); + arg->add_vars(PositiveRef(ref_b)); + if (RefIsPositive(ref_a) == RefIsPositive(ref_b)) { + // a = b + arg->add_coeffs(1); + arg->add_coeffs(-1); + arg->add_domain(0); + arg->add_domain(0); + } else { + // a = 1 - b + arg->add_coeffs(1); + arg->add_coeffs(1); + arg->add_domain(1); + arg->add_domain(1); + } + affine_constraints.insert(ct); + UpdateNewConstraintsVariableUsage(); +} - // For each constant variable appearing in the model, we maintain a reference - // variable with the same constant value. If two variables end up having the - // same fixed value, then we can detect it using this and add a new - // equivalence relation. See ExploitFixedDomain(). - absl::flat_hash_map constant_to_ref; +// This makes sure that the affine relation only uses one of the +// representative from the var_equiv_relations. +AffineRelation::Relation PresolveContext::GetAffineRelation(int ref) { + AffineRelation::Relation r = affine_relations.Get(PositiveRef(ref)); + AffineRelation::Relation o = var_equiv_relations.Get(r.representative); + r.representative = o.representative; + if (o.coeff == -1) r.coeff = -r.coeff; + if (!RefIsPositive(ref)) { + r.coeff *= -1; + r.offset *= -1; + } + return r; +} - // Variable <-> constraint graph. - // The vector list is sorted and contains unique elements. - // - // Important: To properly handle the objective, var_to_constraints[objective] - // contains -1 so that if the objective appear in only one constraint, the - // constraint cannot be simplified. - // - // TODO(user): Make this private? - std::vector> constraint_to_vars; - std::vector> var_to_constraints; +// Create the internal structure for any new variables in working_model. +void PresolveContext::InitializeNewDomains() { + for (int i = domains.size(); i < working_model->variables_size(); ++i) { + domains.push_back(ReadDomainFromProto(working_model->variables(i))); + if (IsFixed(i)) ExploitFixedDomain(i); + } + modified_domains.Resize(domains.size()); + var_to_constraints.resize(domains.size()); +} - CpModelProto* working_model; - CpModelProto* mapping_model; - - // Initially false, and set to true on the first inconsistency. - bool is_unsat = false; - - // Indicate if we are enumerating all solutions. This disable some presolve - // rules. - bool enumerate_all_solutions = false; - - // Just used to display statistics on the presolve rules that were used. - absl::flat_hash_map stats_by_rule_name; - - // Temporary storage. - std::vector tmp_literals; - std::vector tmp_term_domains; - std::vector tmp_left_domains; - absl::flat_hash_set tmp_literal_set; - - // Each time a domain is modified this is set to true. - SparseBitset modified_domains; - - private: - // The current domain of each variables. - std::vector domains; -}; +namespace { // ============================================================================= // Presolve functions. @@ -1133,13 +1086,13 @@ bool PresolveLinear(ConstraintProto* ct, PresolveContext* context) { const int64 coeff1 = arg.coeffs(0); const int64 coeff2 = arg.coeffs(1); if (coeff1 == 1) { - context->AddAffineRelation(*ct, v1, v2, -coeff2, rhs_max); + context->StoreAffineRelation(*ct, v1, v2, -coeff2, rhs_max); } else if (coeff2 == 1) { - context->AddAffineRelation(*ct, v2, v1, -coeff1, rhs_max); + context->StoreAffineRelation(*ct, v2, v1, -coeff1, rhs_max); } else if (coeff1 == -1) { - context->AddAffineRelation(*ct, v1, v2, coeff2, -rhs_max); + context->StoreAffineRelation(*ct, v1, v2, coeff2, -rhs_max); } else if (coeff2 == -1) { - context->AddAffineRelation(*ct, v2, v1, coeff1, -rhs_max); + context->StoreAffineRelation(*ct, v2, v1, coeff1, -rhs_max); } } } @@ -1528,8 +1481,9 @@ bool PresolveInterval(ConstraintProto* ct, PresolveContext* context) { // We add it even if the interval is optional. // TODO(user): we must verify that all the variable of an optional interval // do not appear in a constraint which is not reified by the same literal. - context->AddAffineRelation(*ct, ct->interval().end(), - ct->interval().start(), 1, context->MinOf(size)); + context->StoreAffineRelation(*ct, ct->interval().end(), + ct->interval().start(), 1, + context->MinOf(size)); } // This never change the constraint-variable graph. @@ -1712,7 +1666,7 @@ bool PresolveElement(ConstraintProto* ct, PresolveContext* context) { if (inverse * r_target.coeff + r_target.offset == value) { valid_index_values.push_back(i); if (inverse != value) { - const int cst_ref = context->NewConstant(inverse); + const int cst_ref = context->GetOrCreateConstantVar(inverse); ct->mutable_element()->set_vars(i, cst_ref); changed_values = true; } @@ -2198,8 +2152,8 @@ bool PresolveCircuit(ConstraintProto* ct, PresolveContext* context) { } if (literals.size() == 2 && literals[0] != NegatedRef(literals[1])) { context->UpdateRuleStats("circuit: degree 2"); - context->AddBooleanEqualityRelation(literals[0], - NegatedRef(literals[1])); + context->StoreBooleanEqualityRelation(literals[0], + NegatedRef(literals[1])); } } } @@ -2387,14 +2341,14 @@ void Probe(TimeLimit* global_time_limit, PresolveContext* context) { const int r_var = mapping->GetProtoVariableFromBooleanVariable(r.Variable()); CHECK_GE(r_var, 0); - context->AddBooleanEqualityRelation( + context->StoreBooleanEqualityRelation( var, r.IsPositive() ? r_var : NegatedRef(r_var)); } } } void PresolvePureSatPart(PresolveContext* context) { - // TODO(user, lperron): Reenable some SAT presolve with + // TODO(user,user): Reenable some SAT presolve with // enumerate_all_solutions set to true. if (context->is_unsat || context->enumerate_all_solutions) return; @@ -2888,7 +2842,7 @@ void TransformIntoMaxCliques(PresolveContext* context) { const Literal l = Literal(BooleanVariable(var), true); if (graph->RepresentativeOf(l) != l) { const Literal r = graph->RepresentativeOf(l); - context->AddBooleanEqualityRelation( + context->StoreBooleanEqualityRelation( var, r.IsPositive() ? r.Variable().value() : NegatedRef(r.Variable().value())); } @@ -3247,7 +3201,7 @@ void TryToSimplifyDomains(PresolveContext* context) { lin->add_domain(var_min); lin->add_domain(var_min); - context->AddAffineRelation(*ct, var, new_var_index, gcd, var_min); + context->StoreAffineRelation(*ct, var, new_var_index, gcd, var_min); context->UpdateRuleStats("variables: canonicalize affine domain"); } diff --git a/ortools/sat/cp_model_presolve.h b/ortools/sat/cp_model_presolve.h index de2bef155b..9ff0bea87a 100644 --- a/ortools/sat/cp_model_presolve.h +++ b/ortools/sat/cp_model_presolve.h @@ -17,7 +17,11 @@ #include #include "ortools/sat/cp_model.pb.h" +#include "ortools/sat/cp_model_utils.h" #include "ortools/sat/sat_parameters.pb.h" +#include "ortools/util/affine_relation.h" +#include "ortools/util/bitset.h" +#include "ortools/util/sorted_interval_list.h" #include "ortools/util/time_limit.h" namespace operations_research { @@ -29,6 +33,134 @@ struct PresolveOptions { TimeLimit* time_limit = nullptr; }; +// Wrap the CpModelProto we are presolving with extra data structure like the +// in-memory domain of each variables and the constraint variable graph. +struct PresolveContext { + int NewIntVar(const Domain& domain); + + int NewBoolVar(); + + int GetOrCreateConstantVar(int64 cst); + + // a => b. + void AddImplication(int a, int b); + + // b => x in [lb, ub]. + void AddImplyInDomain(int b, int x, const Domain& domain); + + bool DomainIsEmpty(int ref) const; + + bool IsFixed(int ref) const; + + bool LiteralIsTrue(int lit) const; + + bool LiteralIsFalse(int lit) const; + + int64 MinOf(int ref) const; + + int64 MaxOf(int ref) const; + + // Returns true if this ref only appear in one constraint. + bool VariableIsUniqueAndRemovable(int ref) const; + + Domain DomainOf(int ref) const; + + // Returns true iff the domain changed. + bool IntersectDomainWith(int ref, const Domain& domain); + + void SetLiteralToFalse(int lit); + + void SetLiteralToTrue(int lit); + + void UpdateRuleStats(const std::string& name); + + // Update the constraints <-> variables graph. This needs to be called each + // time a constraint is modified. + void UpdateConstraintVariableUsage(int c); + + // Calls UpdateConstraintVariableUsage() on all newly created constraints. + void UpdateNewConstraintsVariableUsage(); + + // Returns true if our current constraints <-> variables graph is ok. + // This is meant to be used in DEBUG mode only. + bool ConstraintVariableUsageIsConsistent(); + + // Regroups fixed variables with the same value. + // TODO(user): Also regroup cte and -cte? + void ExploitFixedDomain(int var); + + // Adds the relation (ref_x = coeff * ref_y + offset) to the repository. + void StoreAffineRelation(const ConstraintProto& ct, int ref_x, int ref_y, + int64 coeff, int64 offset); + + void StoreBooleanEqualityRelation(int ref_a, int ref_b); + + // This makes sure that the affine relation only uses one of the + // representative from the var_equiv_relations. + AffineRelation::Relation GetAffineRelation(int ref); + + // Create the internal structure for any new variables in working_model. + void InitializeNewDomains(); + + // This regroup all the affine relations between variables. Note that the + // constraints used to detect such relations will not be removed from the + // model at detection time (thus allowing proper domain propagation). However, + // if the arity of a variable becomes one, then such constraint will be + // removed. + AffineRelation affine_relations; + AffineRelation var_equiv_relations; + + // Set of constraint that implies an "affine relation". We need to mark them, + // because we can't simplify them using the relation they added. + // + // WARNING: This assumes the ConstraintProto* to stay valid during the full + // presolve even if we add new constraint to the CpModelProto. + absl::flat_hash_set affine_constraints; + + // For each constant variable appearing in the model, we maintain a reference + // variable with the same constant value. If two variables end up having the + // same fixed value, then we can detect it using this and add a new + // equivalence relation. See ExploitFixedDomain(). + absl::flat_hash_map constant_to_ref; + + // Variable <-> constraint graph. + // The vector list is sorted and contains unique elements. + // + // Important: To properly handle the objective, var_to_constraints[objective] + // contains -1 so that if the objective appear in only one constraint, the + // constraint cannot be simplified. + // + // TODO(user): Make this private? + std::vector> constraint_to_vars; + std::vector> var_to_constraints; + + CpModelProto* working_model; + CpModelProto* mapping_model; + + // Initially false, and set to true on the first inconsistency. + bool is_unsat = false; + + // Indicate if we are enumerating all solutions. This disable some presolve + // rules. + bool enumerate_all_solutions = false; + + // Just used to display statistics on the presolve rules that were used. + absl::flat_hash_map stats_by_rule_name; + + // Temporary storage. + std::vector tmp_literals; + std::vector tmp_term_domains; + std::vector tmp_left_domains; + absl::flat_hash_set tmp_literal_set; + + // Each time a domain is modified this is set to true. + SparseBitset modified_domains; + + private: + // The current domain of each variables. + std::vector domains; +}; + // Presolves the initial content of presolved_model. // // This also creates a mapping model that encode the correspondence between the diff --git a/ortools/sat/cp_model_solver.cc b/ortools/sat/cp_model_solver.cc index 94d181c074..dfb0bf25f3 100644 --- a/ortools/sat/cp_model_solver.cc +++ b/ortools/sat/cp_model_solver.cc @@ -2228,7 +2228,7 @@ CpSolverResponse SolveCpModelParallel( CpSolverResponse thread_response; if (local_params.use_lns()) { first_solution_found_or_search_finished.WaitForNotification(); - // TODO(user, lperron): Provide a better diversification for different + // TODO(user,user): Provide a better diversification for different // seeds. thread_response = SolveCpModelWithLNS( model_proto, solution_observer, num_search_workers, @@ -2335,7 +2335,8 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) { } // Starts by expanding some constraints if needed. - CpModelProto new_model = ExpandCpModel(model_proto, VLOG_IS_ON(1)); + CpModelProto new_model = model_proto; // Copy. + ExpandCpModel(&new_model, VLOG_IS_ON(1)); // Presolve? std::function postprocess_solution; @@ -2430,7 +2431,7 @@ CpSolverResponse SolveCpModel(const CpModelProto& model_proto, Model* model) { #endif // __PORTABLE_PLATFORM__ } else if (params.use_lns() && new_model.has_objective() && !params.enumerate_all_solutions()) { - // TODO(user, lperron): Provide a better diversification for different + // TODO(user,user): Provide a better diversification for different // seeds. const int random_seed = model->GetOrCreate()->random_seed(); response = SolveCpModelWithLNS(new_model, observer_function, 1, random_seed, diff --git a/ortools/sat/disjunctive.cc b/ortools/sat/disjunctive.cc index aea5634842..a8fa7fb189 100644 --- a/ortools/sat/disjunctive.cc +++ b/ortools/sat/disjunctive.cc @@ -301,7 +301,7 @@ bool DisjunctiveOverloadChecker::Propagate() { const int current_event = task_to_event_[current_task]; const IntegerValue energy_min = helper_->DurationMin(current_task); if (helper_->IsPresent(current_task)) { - // TODO(user, stevengay): Add max energy deduction for variable + // TODO(user,user): Add max energy deduction for variable // durations by putting the energy_max here and modifying the code // dealing with the optional envelope greater than current_end below. theta_tree_.AddOrUpdateEvent(current_event, event_time_[current_event], diff --git a/ortools/sat/doc/test.md b/ortools/sat/doc/test.md deleted file mode 100644 index e57c320b9b..0000000000 --- a/ortools/sat/doc/test.md +++ /dev/null @@ -1,157 +0,0 @@ -# Channeling constraints - -A *channeling constraint* links variables inside a model. They're used when you -want to express a complicated relationship between variables, such as "if this -variable satisfies a condition, force another variable to a particular value". - -Channeling is usually implemented using *half-reified* linear constraints: one -constraint implies another (a → b), but not necessarily the other way -around (a ← b). - -## If-Then-Else expressions - -Let's say you want to implement the following: "If *x* is less than 5, set *y* -to 0. Otherwise, set *y* to 10-*x*". You can do this creating an intermediate -boolean variable *b* and two half-reified constraints, shown below: - -
-
-

Python

-```python -"""Link integer constraints together.""" - -from __future__ import absolute_import -from __future__ import division -from __future__ import print_function - -from ortools.sat.python import cp_model - - -class VarArraySolutionPrinter(cp_model.CpSolverSolutionCallback): - """Print intermediate solutions.""" - - def __init__(self, variables): - cp_model.CpSolverSolutionCallback.__init__(self) - self.__variables = variables - self.__solution_count = 0 - - def on_solution_callback(self): - self.__solution_count += 1 - for v in self.__variables: - print('%s=%i' % (v, self.Value(v)), end=' ') - print() - - def solution_count(self): - return self.__solution_count - - -def ChannelingSampleSat(): - """Demonstrates how to link integer constraints together.""" - - # Model. - model = cp_model.CpModel() - - # Variables. - x = model.NewIntVar(0, 10, 'x') - y = model.NewIntVar(0, 10, 'y') - - b = model.NewBoolVar('b') - - # Implement b == (x >= 5). - model.Add(x >= 5).OnlyEnforceIf(b) - model.Add(x < 5).OnlyEnforceIf(b.Not()) - - # b implies (y == 10 - x). - model.Add(y == 10 - x).OnlyEnforceIf(b) - # not(b) implies y == 0. - model.Add(y == 0).OnlyEnforceIf(b.Not()) - - # Search for x values in increasing order. - model.AddDecisionStrategy([x], cp_model.CHOOSE_FIRST, - cp_model.SELECT_MIN_VALUE) - - # Create a solver and solve with a fixed search. - solver = cp_model.CpSolver() - - # Force solver to follow the decision strategy exactly. - solver.parameters.search_branching = cp_model.FIXED_SEARCH - - # Searches and prints out all solutions. - solution_printer = VarArraySolutionPrinter([x, y, b]) - solver.SearchForAllSolutions(model, solution_printer) - - -ChannelingSampleSat() -``` -
-
-

C++

- ```[cpp] -#include "ortools/sat/cp_model.h" -#include "ortools/sat/model.h" -#include "ortools/sat/sat_parameters.pb.h" - -namespace operations_research { -namespace sat { - -void ChannelingSampleSat() { - // Model. - CpModelBuilder cp_model; - - // Main variables. - const IntVar x = cp_model.NewIntVar({0, 10}); - const IntVar y = cp_model.NewIntVar({0, 10}); - const BoolVar b = cp_model.NewBoolVar(); - - // b == (x >= 5). - cp_model.AddGreaterOrEqual(x, 5).OnlyEnforceIf(b); - cp_model.AddLessThan(x, 5).OnlyEnforceIf(Not(b)); - - // b implies (y == 10 - x). - cp_model.AddEquality(LinearExpr::Sum({x, y}), 10).OnlyEnforceIf(b); - // not(b) implies y == 0. - cp_model.AddEquality(y, 0).OnlyEnforceIf(Not(b)); - - // Search for x values in increasing order. - cp_model.AddDecisionStrategy({x}, DecisionStrategyProto::CHOOSE_FIRST, - DecisionStrategyProto::SELECT_MIN_VALUE); - - Model model; - SatParameters parameters; - parameters.set_search_branching(SatParameters::FIXED_SEARCH); - parameters.set_enumerate_all_solutions(true); - model.Add(NewSatParameters(parameters)); - model.Add(NewFeasibleSolutionObserver([&](const CpSolverResponse& r) { - LOG(INFO) << "x=" << SolutionIntegerValue(r, x) - << " y=" << SolutionIntegerValue(r, y) - << " b=" << SolutionBooleanValue(r, b); - })); - SolveWithModel(cp_model, &model); -} - -} // namespace sat -} // namespace operations_research - -int main() { - operations_research::sat::ChannelingSampleSat(); - - return EXIT_SUCCESS; -} -``` -
- -This displays the following: - -``` -x=0 y=0 b=0 -x=1 y=0 b=0 -x=2 y=0 b=0 -x=3 y=0 b=0 -x=4 y=0 b=0 -x=5 y=5 b=1 -x=6 y=4 b=1 -x=7 y=3 b=1 -x=8 y=2 b=1 -x=9 y=1 b=1 -x=10 y=0 b=1 -``` diff --git a/ortools/util/csharp/proto.i b/ortools/util/csharp/proto.i index d22161077b..ed8a8d3589 100644 --- a/ortools/util/csharp/proto.i +++ b/ortools/util/csharp/proto.i @@ -12,7 +12,7 @@ // limitations under the License. // TODO(user): make this SWIG file comply with the SWIG style guide. -%include ortools/base/base.i +%include "ortools/base/base.i" %{ #include diff --git a/ortools/util/csharp/tuple_set.i b/ortools/util/csharp/tuple_set.i index a4dea4c168..1257fa7d05 100644 --- a/ortools/util/csharp/tuple_set.i +++ b/ortools/util/csharp/tuple_set.i @@ -24,4 +24,6 @@ #include "ortools/util/tuple_set.h" %} -%include ortools/util/tuple_set.h +// TODO(user): Replace with %ignoreall/%unignoreall +//swiglint: disable include-h-allglobals +%include "ortools/util/tuple_set.h" diff --git a/ortools/util/file_util.cc b/ortools/util/file_util.cc index ec7a723e61..2ea976e549 100644 --- a/ortools/util/file_util.cc +++ b/ortools/util/file_util.cc @@ -29,7 +29,8 @@ bool ReadFileToProto(absl::string_view filename, std::string data; CHECK_OK(file::GetContents(filename, &data, file::Defaults())); // Note that gzipped files are currently not supported. - // Try binary format first, then text format, then JSON, then give up. + // Try binary format first, then text format, then JSON, then proto3 JSON, + // then give up. if (proto->ParseFromString(data)) return true; if (google::protobuf::TextFormat::ParseFromString(data, proto)) return true; LOG(WARNING) << "Could not parse protocol buffer"; diff --git a/ortools/util/functions_swig_helpers.h b/ortools/util/functions_swig_helpers.h index 0d81d3e686..a1cb1d8ee9 100644 --- a/ortools/util/functions_swig_helpers.h +++ b/ortools/util/functions_swig_helpers.h @@ -15,7 +15,9 @@ #define OR_TOOLS_UTIL_FUNCTIONS_SWIG_HELPERS_H_ // This file contains class definitions for the wrapping of C++ std::functions -// in Java. It is #included by java/functions.i +// in Java and C#. It is #included by java/functions.i and +// csharp/functions.i. + #include #include diff --git a/ortools/util/java/tuple_set.i b/ortools/util/java/tuple_set.i index f105342481..5ef3c1482b 100644 --- a/ortools/util/java/tuple_set.i +++ b/ortools/util/java/tuple_set.i @@ -40,5 +40,4 @@ %rename (sortedLexicographically) operations_research::IntTupleSet::SortedLexicographically; %rename (value) operations_research::IntTupleSet::Value; - %include ortools/util/tuple_set.h diff --git a/ortools/util/python/vector.i b/ortools/util/python/vector.i index 7d4e83eec8..75c1536ed7 100644 --- a/ortools/util/python/vector.i +++ b/ortools/util/python/vector.i @@ -48,7 +48,7 @@ $1 = i == size; } } -%typemap(in) std::vector (std::vector temp) { +%typemap(in) std::vector(std::vector temp) { if (!vector_input_helper($input, &temp, PyObjAs)) { if (!PyErr_Occurred()) SWIG_Error(SWIG_TypeError, "sequence(type) expected");