diff --git a/ortools/constraint_solver/BUILD.bazel b/ortools/constraint_solver/BUILD.bazel index a50ac8cbe7..7ca0448a2f 100644 --- a/ortools/constraint_solver/BUILD.bazel +++ b/ortools/constraint_solver/BUILD.bazel @@ -43,21 +43,6 @@ cc_proto_library( deps = [":assignment_proto"], ) -proto_library( - name = "search_stats_proto", - srcs = ["search_stats.proto"], -) - -cc_proto_library( - name = "search_stats_cc_proto", - deps = [":search_stats_proto"], -) - -# java_proto_library( -# name = "search_stats_java_proto", -# deps = [":search_stats_proto"], -# ) - proto_library( name = "search_limit_proto", srcs = ["search_limit.proto"], @@ -83,6 +68,21 @@ cc_proto_library( deps = [":demon_profiler_proto"], ) +proto_library( + name = "search_stats_proto", + srcs = ["search_stats.proto"], +) + +cc_proto_library( + name = "search_stats_cc_proto", + deps = [":search_stats_proto"], +) + +# java_proto_library( +# name = "search_stats_java_proto", +# deps = [":search_stats_proto"], +# ) + proto_library( name = "solver_parameters_proto", srcs = ["solver_parameters.proto"], @@ -161,8 +161,8 @@ cc_library( deps = [ ":assignment_cc_proto", ":demon_profiler_cc_proto", - ":search_stats_cc_proto", ":search_limit_cc_proto", + ":search_stats_cc_proto", ":solver_parameters_cc_proto", ":routing_parameters_cc_proto", "//ortools/base", @@ -175,6 +175,7 @@ cc_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", # "//zlib:zlibonly", "//ortools/base:bitmap", "//ortools/base:intops", @@ -402,6 +403,7 @@ cc_library( "@com_google_absl//absl/strings", "@com_google_absl//absl/strings:str_format", "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", "@com_google_protobuf//:protobuf", ], ) diff --git a/ortools/constraint_solver/README.md b/ortools/constraint_solver/README.md index 550dac10f8..0a6688c814 100644 --- a/ortools/constraint_solver/README.md +++ b/ortools/constraint_solver/README.md @@ -42,7 +42,6 @@ To begin, skim: The vehicle routing library lets one model and solve generic vehicle routing problems ranging from the Traveling Salesman Problem to more complex problems such as the Capacitated Vehicle Routing Problem with Time Windows. -* [routing_flags.h](../constraint_solver/routing_flags.h) ### Parameters diff --git a/ortools/constraint_solver/constraint_solver.cc b/ortools/constraint_solver/constraint_solver.cc index aa276f3443..f733785648 100644 --- a/ortools/constraint_solver/constraint_solver.cc +++ b/ortools/constraint_solver/constraint_solver.cc @@ -36,7 +36,6 @@ #include "ortools/base/commandlineflags.h" #include "ortools/base/file.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/map_util.h" #include "ortools/base/recordio.h" #include "ortools/base/stl_util.h" @@ -531,6 +530,10 @@ template class TrailPacker { public: explicit TrailPacker(int block_size) : block_size_(block_size) {} + + // This type is neither copyable nor movable. + TrailPacker(const TrailPacker&) = delete; + TrailPacker& operator=(const TrailPacker&) = delete; virtual ~TrailPacker() {} int input_size() const { return block_size_ * sizeof(addrval); } virtual void Pack(const addrval* block, std::string* packed_block) = 0; @@ -538,7 +541,6 @@ class TrailPacker { private: const int block_size_; - DISALLOW_COPY_AND_ASSIGN(TrailPacker); }; template @@ -546,6 +548,10 @@ class NoCompressionTrailPacker : public TrailPacker { public: explicit NoCompressionTrailPacker(int block_size) : TrailPacker(block_size) {} + + // This type is neither copyable nor movable. + NoCompressionTrailPacker(const NoCompressionTrailPacker&) = delete; + NoCompressionTrailPacker& operator=(const NoCompressionTrailPacker&) = delete; ~NoCompressionTrailPacker() override {} void Pack(const addrval* block, std::string* packed_block) override { DCHECK(block != nullptr); @@ -558,9 +564,6 @@ class NoCompressionTrailPacker : public TrailPacker { DCHECK(block != nullptr); memcpy(block, packed_block.c_str(), packed_block.size()); } - - private: - DISALLOW_COPY_AND_ASSIGN(NoCompressionTrailPacker); }; template @@ -571,6 +574,10 @@ class ZlibTrailPacker : public TrailPacker { tmp_size_(compressBound(this->input_size())), tmp_block_(new char[tmp_size_]) {} + // This type is neither copyable nor movable. + ZlibTrailPacker(const ZlibTrailPacker&) = delete; + ZlibTrailPacker& operator=(const ZlibTrailPacker&) = delete; + ~ZlibTrailPacker() override {} void Pack(const addrval* block, std::string* packed_block) override { @@ -599,7 +606,6 @@ class ZlibTrailPacker : public TrailPacker { private: const uint64_t tmp_size_; std::unique_ptr tmp_block_; - DISALLOW_COPY_AND_ASSIGN(ZlibTrailPacker); }; template diff --git a/ortools/constraint_solver/constraint_solver.h b/ortools/constraint_solver/constraint_solver.h index f9e9a0c3ff..1d0b8900ba 100644 --- a/ortools/constraint_solver/constraint_solver.h +++ b/ortools/constraint_solver/constraint_solver.h @@ -74,9 +74,9 @@ #include #include #include +#include #include #include -#include #include #include @@ -86,11 +86,12 @@ #include "absl/container/flat_hash_set.h" #include "absl/flags/declare.h" #include "absl/flags/flag.h" +#include "absl/log/check.h" #include "absl/random/random.h" #include "absl/strings/str_format.h" #include "absl/time/time.h" +#include "absl/types/span.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/map_util.h" #include "ortools/base/timer.h" #include "ortools/base/types.h" @@ -128,6 +129,7 @@ class IntVarAssignment; class IntVarLocalSearchFilter; class IntervalVar; class IntervalVarAssignment; +class LocalSearch; class LocalSearchFilter; class LocalSearchFilterManager; class LocalSearchMonitor; @@ -794,6 +796,13 @@ class Solver { /// Solver API explicit Solver(const std::string& name); Solver(const std::string& name, const ConstraintSolverParameters& parameters); + +#ifndef SWIG + // This type is neither copyable nor movable. + Solver(const Solver&) = delete; + Solver& operator=(const Solver&) = delete; +#endif + ~Solver(); /// Stored Parameters. @@ -1740,8 +1749,8 @@ class Solver { Constraint* MakePathPrecedenceConstraint( std::vector nexts, const std::vector>& precedences, - const std::vector& lifo_path_starts, - const std::vector& fifo_path_starts); + absl::Span lifo_path_starts, + absl::Span fifo_path_starts); /// Same as MakePathPrecedenceConstraint but will force i to be before j if /// the sum of transits on the path from i to j is strictly positive. Constraint* MakePathTransitPrecedenceConstraint( @@ -1845,10 +1854,10 @@ class Solver { const std::vector& x_size, const std::vector& y_size); Constraint* MakeNonOverlappingBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size); + absl::Span x_size, absl::Span y_size); Constraint* MakeNonOverlappingBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size); + absl::Span x_size, absl::Span y_size); /// This constraint states that all the boxes must not overlap. /// The coordinates of box i are: @@ -1863,10 +1872,10 @@ class Solver { const std::vector& x_size, const std::vector& y_size); Constraint* MakeNonOverlappingNonStrictBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size); + absl::Span x_size, absl::Span y_size); Constraint* MakeNonOverlappingNonStrictBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size); + absl::Span x_size, absl::Span y_size); /// This constraint packs all variables onto 'number_of_bins' /// variables. For any given variable, a value of 'number_of_bins' @@ -1914,20 +1923,20 @@ class Solver { /// the corresponding start variables. void MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, const std::string& name, + absl::Span durations, const std::string& name, std::vector* array); /// This method fills the vector with interval variables built with /// the corresponding start variables. void MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, const std::string& name, + absl::Span durations, const std::string& name, std::vector* array); /// This method fills the vector with interval variables built with /// the corresponding start and performed variables. void MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, + absl::Span durations, const std::vector& performed_variables, const std::string& name, std::vector* array); @@ -1935,7 +1944,7 @@ class Solver { /// the corresponding start and performed variables. void MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, + absl::Span durations, const std::vector& performed_variables, const std::string& name, std::vector* array); @@ -3078,6 +3087,7 @@ class Solver { friend class SearchMonitor; friend class SearchLimit; friend class RoutingModel; + friend class LocalSearch; friend class LocalSearchProfiler; #if !defined(SWIG) @@ -3263,8 +3273,6 @@ class Solver { std::unique_ptr local_search_monitor_; int anonymous_variable_index_; bool should_fail_; - - DISALLOW_COPY_AND_ASSIGN(Solver); }; std::ostream& operator<<(std::ostream& out, const Solver* const s); /// NOLINT @@ -3283,11 +3291,14 @@ inline int64_t One() { return 1; } class BaseObject { public: BaseObject() {} + +#ifndef SWIG + // This type is neither copyable nor movable. + BaseObject(const BaseObject&) = delete; + BaseObject& operator=(const BaseObject&) = delete; +#endif virtual ~BaseObject() {} virtual std::string DebugString() const { return "BaseObject"; } - - private: - DISALLOW_COPY_AND_ASSIGN(BaseObject); }; std::ostream& operator<<(std::ostream& out, const BaseObject* o); /// NOLINT @@ -3298,6 +3309,12 @@ std::ostream& operator<<(std::ostream& out, const BaseObject* o); /// NOLINT class PropagationBaseObject : public BaseObject { public: explicit PropagationBaseObject(Solver* const s) : solver_(s) {} + +#ifndef SWIG + // This type is neither copyable nor movable. + PropagationBaseObject(const PropagationBaseObject&) = delete; + PropagationBaseObject& operator=(const PropagationBaseObject&) = delete; +#endif ~PropagationBaseObject() override{}; std::string DebugString() const override { @@ -3351,7 +3368,6 @@ class PropagationBaseObject : public BaseObject { private: Solver* const solver_; - DISALLOW_COPY_AND_ASSIGN(PropagationBaseObject); }; /// A Decision represents a choice point in the search tree. The two main @@ -3359,6 +3375,12 @@ class PropagationBaseObject : public BaseObject { class Decision : public BaseObject { public: Decision() {} + +#ifndef SWIG + // This type is neither copyable nor movable. + Decision(const Decision&) = delete; + Decision& operator=(const Decision&) = delete; +#endif ~Decision() override {} /// Apply will be called first when the decision is executed. @@ -3370,9 +3392,6 @@ class Decision : public BaseObject { std::string DebugString() const override { return "Decision"; } /// Accepts the given visitor. virtual void Accept(DecisionVisitor* visitor) const; - - private: - DISALLOW_COPY_AND_ASSIGN(Decision); }; /// A DecisionVisitor is used to inspect a decision. @@ -3380,6 +3399,12 @@ class Decision : public BaseObject { class DecisionVisitor : public BaseObject { public: DecisionVisitor() {} + +#ifndef SWIG + // This type is neither copyable nor movable. + DecisionVisitor(const DecisionVisitor&) = delete; + DecisionVisitor& operator=(const DecisionVisitor&) = delete; +#endif ~DecisionVisitor() override {} virtual void VisitSetVariableValue(IntVar* var, int64_t value); virtual void VisitSplitVariableDomain(IntVar* var, int64_t value, @@ -3389,9 +3414,6 @@ class DecisionVisitor : public BaseObject { virtual void VisitRankFirstInterval(SequenceVar* sequence, int index); virtual void VisitRankLastInterval(SequenceVar* sequence, int index); virtual void VisitUnknownDecision(); - - private: - DISALLOW_COPY_AND_ASSIGN(DecisionVisitor); }; /// A DecisionBuilder is responsible for creating the search tree. The @@ -3399,6 +3421,12 @@ class DecisionVisitor : public BaseObject { class DecisionBuilder : public BaseObject { public: DecisionBuilder() {} + +#ifndef SWIG + // This type is neither copyable nor movable. + DecisionBuilder(const DecisionBuilder&) = delete; + DecisionBuilder& operator=(const DecisionBuilder&) = delete; +#endif ~DecisionBuilder() override {} /// This is the main method of the decision builder class. It must /// return a decision (an instance of the class Decision). If it @@ -3420,7 +3448,6 @@ class DecisionBuilder : public BaseObject { private: std::string name_; - DISALLOW_COPY_AND_ASSIGN(DecisionBuilder); }; #if !defined(SWIG) @@ -3458,6 +3485,12 @@ class Demon : public BaseObject { /// This indicates the priority of a demon. Immediate demons are treated /// separately and corresponds to variables. Demon() : stamp_(uint64_t{0}) {} + +#ifndef SWIG + // This type is neither copyable nor movable. + Demon(const Demon&) = delete; + Demon& operator=(const Demon&) = delete; +#endif ~Demon() override {} /// This is the main callback of the demon. @@ -3482,7 +3515,6 @@ class Demon : public BaseObject { void set_stamp(int64_t stamp) { stamp_ = stamp; } uint64_t stamp() const { return stamp_; } uint64_t stamp_; - DISALLOW_COPY_AND_ASSIGN(Demon); }; /// Model visitor. @@ -3739,6 +3771,12 @@ class ModelVisitor : public BaseObject { class Constraint : public PropagationBaseObject { public: explicit Constraint(Solver* const solver) : PropagationBaseObject(solver) {} + +#ifndef SWIG + // This type is neither copyable nor movable. + Constraint(const Constraint&) = delete; + Constraint& operator=(const Constraint&) = delete; +#endif ~Constraint() override {} /// This method is called when the constraint is processed by the @@ -3764,9 +3802,6 @@ class Constraint : public PropagationBaseObject { /// (false = constraint is violated, true = constraint is satisfied). It /// returns nullptr if the constraint does not support this API. virtual IntVar* Var(); - - private: - DISALLOW_COPY_AND_ASSIGN(Constraint); }; /// Cast constraints are special channeling constraints designed @@ -3792,6 +3827,12 @@ class SearchMonitor : public BaseObject { static constexpr int kNoProgress = -1; explicit SearchMonitor(Solver* const s) : solver_(s) {} + +#ifndef SWIG + // This type is neither copyable nor movable. + SearchMonitor(const SearchMonitor&) = delete; + SearchMonitor& operator=(const SearchMonitor&) = delete; +#endif ~SearchMonitor() override {} /// Beginning of the search. virtual void EnterSearch(); @@ -3882,7 +3923,6 @@ class SearchMonitor : public BaseObject { private: Solver* const solver_; - DISALLOW_COPY_AND_ASSIGN(SearchMonitor); }; /// This class adds reversibility to a POD type. @@ -3995,6 +4035,12 @@ class NumericalRevArray : public RevArray { class IntExpr : public PropagationBaseObject { public: explicit IntExpr(Solver* const s) : PropagationBaseObject(s) {} + +#ifndef SWIG + // This type is neither copyable nor movable. + IntExpr(const IntExpr&) = delete; + IntExpr& operator=(const IntExpr&) = delete; +#endif ~IntExpr() override {} virtual int64_t Min() const = 0; @@ -4048,9 +4094,6 @@ class IntExpr : public PropagationBaseObject { /// Accepts the given visitor. virtual void Accept(ModelVisitor* visitor) const; - - private: - DISALLOW_COPY_AND_ASSIGN(IntExpr); }; /// The class Iterator has two direct subclasses. HoleIterators @@ -4124,25 +4167,25 @@ class InitAndGetValues { } int64_t operator*() const { - DCHECK(it_->Ok()); - return it_->Value(); + DCHECK(it->Ok()); + return it->Value(); } Iterator& operator++() { - DCHECK(it_->Ok()); - it_->Next(); + DCHECK(it->Ok()); + it->Next(); return *this; } bool operator!=(const Iterator& other) const { - DCHECK(other.it_ == it_); - DCHECK(other.is_end_); - return it_->Ok(); + DCHECK(other.it == it); + DCHECK(other.is_end); + return it->Ok(); } private: - Iterator(IntVarIterator* it, bool is_end) : it_(it), is_end_(is_end) {} + Iterator(IntVarIterator* it, bool is_end) : it(it), is_end(is_end) {} - IntVarIterator* const it_; - const bool is_end_; + IntVarIterator* const it; + const bool is_end; }; private: @@ -4158,6 +4201,13 @@ class IntVar : public IntExpr { public: explicit IntVar(Solver* s); IntVar(Solver* s, const std::string& name); + +#ifndef SWIG + // This type is neither copyable nor movable. + IntVar(const IntVar&) = delete; + IntVar& operator=(const IntVar&) = delete; +#endif + ~IntVar() override{}; bool IsVar() const override { return true; } @@ -4252,7 +4302,6 @@ class IntVar : public IntExpr { private: const int index_; - DISALLOW_COPY_AND_ASSIGN(IntVar); }; /// This class is the root class of all solution collectors. @@ -4262,6 +4311,13 @@ class SolutionCollector : public SearchMonitor { public: SolutionCollector(Solver* solver, const Assignment* assignment); explicit SolutionCollector(Solver* solver); + +#ifndef SWIG + // This type is neither copyable nor movable. + SolutionCollector(const SolutionCollector&) = delete; + SolutionCollector& operator=(const SolutionCollector&) = delete; +#endif + ~SolutionCollector() override; void Install() override; std::string DebugString() const override { return "SolutionCollector"; } @@ -4282,9 +4338,15 @@ class SolutionCollector : public SearchMonitor { /// Returns how many solutions were stored during the search. int solution_count() const; + /// Returns whether any solutions were stored during the search. + bool has_solution() const; + /// Returns the nth solution. Assignment* solution(int n) const; + /// Returns the last solution if there are any, nullptr otherwise. + Assignment* last_solution_or_null() const; + /// Returns the wall time in ms for the nth solution. int64_t wall_time(int n) const; @@ -4354,9 +4416,6 @@ class SolutionCollector : public SearchMonitor { #if !defined(SWIG) std::vector> solution_pool_; #endif // SWIG - - private: - DISALLOW_COPY_AND_ASSIGN(SolutionCollector); }; // Base objective monitor class. All metaheuristics derive from this. @@ -4382,6 +4441,7 @@ class ObjectiveMonitor : public SearchMonitor { int Size() const { return objective_vars_.size(); } void EnterSearch() override; bool AtSolution() override; + bool AcceptDelta(Assignment* delta, Assignment* deltadelta) override; void Accept(ModelVisitor* visitor) const override; protected: @@ -4461,7 +4521,6 @@ class OptimizeVar : public ObjectiveMonitor { IntVar* var() const { return Size() == 0 ? nullptr : ObjectiveVar(0); } /// Internal methods. - bool AcceptDelta(Assignment* delta, Assignment* deltadelta) override; void BeginNextDecision(DecisionBuilder* db) override; void RefuteDecision(Decision* d) override; bool AtSolution() override; @@ -4476,6 +4535,12 @@ class OptimizeVar : public ObjectiveMonitor { class SearchLimit : public SearchMonitor { public: explicit SearchLimit(Solver* const s) : SearchMonitor(s), crossed_(false) {} + +#ifndef SWIG + // This type is neither copyable nor movable. + SearchLimit(const SearchLimit&) = delete; + SearchLimit& operator=(const SearchLimit&) = delete; +#endif ~SearchLimit() override; /// Returns true if the limit has been crossed. @@ -4514,7 +4579,6 @@ class SearchLimit : public SearchMonitor { void TopPeriodicCheck(); bool crossed_; - DISALLOW_COPY_AND_ASSIGN(SearchLimit); }; /// Usual limit based on wall_time, number of explored branches and @@ -4651,6 +4715,12 @@ class IntervalVar : public PropagationBaseObject { : PropagationBaseObject(solver) { set_name(name); } + +#ifndef SWIG + // This type is neither copyable nor movable. + IntervalVar(const IntervalVar&) = delete; + IntervalVar& operator=(const IntervalVar&) = delete; +#endif ~IntervalVar() override {} /// These methods query, set, and watch the start position of the @@ -4784,9 +4854,6 @@ class IntervalVar : public PropagationBaseObject { /// Accepts the given visitor. virtual void Accept(ModelVisitor* visitor) const = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(IntervalVar); }; /// A sequence variable is a variable whose domain is a set of possible @@ -5293,6 +5360,13 @@ class Assignment : public PropagationBaseObject { explicit Assignment(Solver* solver); explicit Assignment(const Assignment* copy); + +#ifndef SWIG + // This type is neither copyable nor movable. + Assignment(const Assignment&) = delete; + Assignment& operator=(const Assignment&) = delete; +#endif + ~Assignment() override; void Clear(); @@ -5534,7 +5608,6 @@ class Assignment : public PropagationBaseObject { IntervalContainer interval_var_container_; SequenceContainer sequence_var_container_; std::vector objective_elements_; - DISALLOW_COPY_AND_ASSIGN(Assignment); }; std::ostream& operator<<(std::ostream& out, @@ -5659,6 +5732,13 @@ class DisjunctiveConstraint : public Constraint { public: DisjunctiveConstraint(Solver* s, const std::vector& intervals, const std::string& name); + +#ifndef SWIG + // This type is neither copyable nor movable. + DisjunctiveConstraint(const DisjunctiveConstraint&) = delete; + DisjunctiveConstraint& operator=(const DisjunctiveConstraint&) = delete; +#endif + ~DisjunctiveConstraint() override; /// Creates a sequence variable from the constraint. @@ -5685,9 +5765,6 @@ class DisjunctiveConstraint : public Constraint { protected: const std::vector intervals_; Solver::IndexEvaluator2 transition_time_; - - private: - DISALLOW_COPY_AND_ASSIGN(DisjunctiveConstraint); }; /// This class is used to manage a pool of solutions. It can transform diff --git a/ortools/constraint_solver/default_search.cc b/ortools/constraint_solver/default_search.cc index e841b93ddb..795ede6eea 100644 --- a/ortools/constraint_solver/default_search.cc +++ b/ortools/constraint_solver/default_search.cc @@ -25,7 +25,6 @@ #include "absl/strings/str_format.h" #include "ortools/base/commandlineflags.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/stl_util.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" @@ -70,6 +69,10 @@ class DomainWatcher { cached_log_.Init(cache_size); } + // This type is neither copyable nor movable. + DomainWatcher(const DomainWatcher&) = delete; + DomainWatcher& operator=(const DomainWatcher&) = delete; + double LogSearchSpaceSize() { double result = 0.0; for (int index = 0; index < vars_.size(); ++index) { @@ -83,7 +86,6 @@ class DomainWatcher { private: std::vector vars_; CachedLog cached_log_; - DISALLOW_COPY_AND_ASSIGN(DomainWatcher); }; // ---------- FindVar decision visitor --------- @@ -213,6 +215,10 @@ class InitVarImpacts : public DecisionBuilder { update_impact_closure_(update_impact_closure) { CHECK(update_impact_closure_ != nullptr); } + + // This type is neither copyable nor movable. + AssignCallFail(const AssignCallFail&) = delete; + AssignCallFail& operator=(const AssignCallFail&) = delete; ~AssignCallFail() override {} void Apply(Solver* const solver) override { CHECK(var_ != nullptr); @@ -228,7 +234,6 @@ class InitVarImpacts : public DecisionBuilder { private: const std::function& update_impact_closure_; - DISALLOW_COPY_AND_ASSIGN(AssignCallFail); }; IntVar* var_; @@ -258,6 +263,10 @@ class InitVarImpactsWithSplits : public DecisionBuilder { update_impact_closure_(update_impact_closure) { CHECK(update_impact_closure_ != nullptr); } + + // This type is neither copyable nor movable. + AssignIntervalCallFail(const AssignIntervalCallFail&) = delete; + AssignIntervalCallFail& operator=(const AssignIntervalCallFail&) = delete; ~AssignIntervalCallFail() override {} void Apply(Solver* const solver) override { CHECK(var_ != nullptr); @@ -275,7 +284,6 @@ class InitVarImpactsWithSplits : public DecisionBuilder { private: const std::function& update_impact_closure_; - DISALLOW_COPY_AND_ASSIGN(AssignIntervalCallFail); }; // ----- main ----- @@ -387,6 +395,10 @@ class ImpactRecorder : public SearchMonitor { } } + // This type is neither copyable nor movable. + ImpactRecorder(const ImpactRecorder&) = delete; + ImpactRecorder& operator=(const ImpactRecorder&) = delete; + void ApplyDecision(Decision* const d) override { if (!init_done_) { return; @@ -648,8 +660,6 @@ class ImpactRecorder : public SearchMonitor { FindVar find_var_; absl::flat_hash_map var_map_; bool init_done_; - - DISALLOW_COPY_AND_ASSIGN(ImpactRecorder); }; const int ImpactRecorder::kLogCacheSize = 1000; diff --git a/ortools/constraint_solver/diffn.cc b/ortools/constraint_solver/diffn.cc index b4e56f8da2..a9f2104d39 100644 --- a/ortools/constraint_solver/diffn.cc +++ b/ortools/constraint_solver/diffn.cc @@ -18,6 +18,7 @@ #include #include "absl/strings/str_format.h" +#include "absl/types/span.h" #include "ortools/base/hash.h" #include "ortools/base/int_type.h" #include "ortools/base/logging.h" @@ -304,7 +305,7 @@ Constraint* Solver::MakeNonOverlappingBoxesConstraint( Constraint* Solver::MakeNonOverlappingBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size) { + absl::Span x_size, absl::Span y_size) { std::vector dx(x_size.size()); std::vector dy(y_size.size()); for (int i = 0; i < x_size.size(); ++i) { @@ -316,7 +317,7 @@ Constraint* Solver::MakeNonOverlappingBoxesConstraint( Constraint* Solver::MakeNonOverlappingBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size) { + absl::Span x_size, absl::Span y_size) { std::vector dx(x_size.size()); std::vector dy(y_size.size()); for (int i = 0; i < x_size.size(); ++i) { @@ -334,7 +335,7 @@ Constraint* Solver::MakeNonOverlappingNonStrictBoxesConstraint( Constraint* Solver::MakeNonOverlappingNonStrictBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size) { + absl::Span x_size, absl::Span y_size) { std::vector dx(x_size.size()); std::vector dy(y_size.size()); for (int i = 0; i < x_size.size(); ++i) { @@ -346,7 +347,7 @@ Constraint* Solver::MakeNonOverlappingNonStrictBoxesConstraint( Constraint* Solver::MakeNonOverlappingNonStrictBoxesConstraint( const std::vector& x_vars, const std::vector& y_vars, - const std::vector& x_size, const std::vector& y_size) { + absl::Span x_size, absl::Span y_size) { std::vector dx(x_size.size()); std::vector dy(y_size.size()); for (int i = 0; i < x_size.size(); ++i) { diff --git a/ortools/constraint_solver/expr_array.cc b/ortools/constraint_solver/expr_array.cc index 4f97460396..41f73e28e3 100644 --- a/ortools/constraint_solver/expr_array.cc +++ b/ortools/constraint_solver/expr_array.cc @@ -24,6 +24,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" +#include "absl/types/span.h" #include "ortools/base/logging.h" #include "ortools/base/mathutil.h" #include "ortools/base/types.h" @@ -2676,7 +2677,7 @@ class ExprLinearizer : public ModelParser { // ----- Factory functions ----- void DeepLinearize(Solver* const solver, const std::vector& pre_vars, - const std::vector& pre_coefs, + absl::Span pre_coefs, std::vector* vars, std::vector* coefs, int64_t* constant) { CHECK(solver != nullptr); diff --git a/ortools/constraint_solver/expressions.cc b/ortools/constraint_solver/expressions.cc index ed65f5066e..2557ec6445 100644 --- a/ortools/constraint_solver/expressions.cc +++ b/ortools/constraint_solver/expressions.cc @@ -24,6 +24,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/types/span.h" #include "ortools/base/commandlineflags.h" #include "ortools/base/logging.h" #include "ortools/base/map_util.h" @@ -1421,7 +1422,7 @@ class DomainIntVar : public IntVar { } } - Constraint* SetIsEqual(const std::vector& values, + Constraint* SetIsEqual(absl::Span values, const std::vector& vars) { if (value_watcher_ == nullptr) { solver()->SaveAndSetValue(reinterpret_cast(&value_watcher_), @@ -1497,7 +1498,7 @@ class DomainIntVar : public IntVar { } } - Constraint* SetIsGreaterOrEqual(const std::vector& values, + Constraint* SetIsGreaterOrEqual(absl::Span values, const std::vector& vars) { if (bound_watcher_ == nullptr) { if (CapSub(Max(), Min()) <= 256) { @@ -1627,7 +1628,7 @@ class SimpleBitSet : public DomainIntVar::BitSet { } } - SimpleBitSet(Solver* const s, const std::vector& sorted_values, + SimpleBitSet(Solver* const s, absl::Span sorted_values, int64_t vmin, int64_t vmax) : BitSet(s), bits_(nullptr), @@ -1822,7 +1823,7 @@ class SmallBitSet : public DomainIntVar::BitSet { bits_ = OneRange64(0, size_.Value() - 1); } - SmallBitSet(Solver* const s, const std::vector& sorted_values, + SmallBitSet(Solver* const s, absl::Span sorted_values, int64_t vmin, int64_t vmax) : BitSet(s), bits_(uint64_t{0}), @@ -6205,6 +6206,10 @@ class ExprWithEscapeValue : public BaseIntExpr { expression_(e), unperformed_value_(unperformed_value) {} + // This type is neither copyable nor movable. + ExprWithEscapeValue(const ExprWithEscapeValue&) = delete; + ExprWithEscapeValue& operator=(const ExprWithEscapeValue&) = delete; + ~ExprWithEscapeValue() override {} int64_t Min() const override { @@ -6301,7 +6306,6 @@ class ExprWithEscapeValue : public BaseIntExpr { IntVar* const condition_; IntExpr* const expression_; const int64_t unperformed_value_; - DISALLOW_COPY_AND_ASSIGN(ExprWithEscapeValue); }; // ----- This is a specialized case when the variable exact type is known ----- diff --git a/ortools/constraint_solver/graph_constraints.cc b/ortools/constraint_solver/graph_constraints.cc index 92e389ad30..7622a0b0f0 100644 --- a/ortools/constraint_solver/graph_constraints.cc +++ b/ortools/constraint_solver/graph_constraints.cc @@ -23,6 +23,7 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" +#include "absl/types/span.h" #include "ortools/base/logging.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" @@ -1339,7 +1340,7 @@ namespace { class PathConnectedConstraint : public Constraint { public: PathConnectedConstraint(Solver* solver, std::vector nexts, - const std::vector& sources, + absl::Span sources, std::vector sinks, std::vector status) : Constraint(solver), @@ -1452,7 +1453,7 @@ class PathTransitPrecedenceConstraint : public Constraint { }; PathTransitPrecedenceConstraint( Solver* solver, std::vector nexts, std::vector transits, - const std::vector>& precedences, + absl::Span> precedences, absl::flat_hash_map precedence_types) : Constraint(solver), nexts_(std::move(nexts)), @@ -1628,8 +1629,8 @@ Constraint* Solver::MakePathPrecedenceConstraint( Constraint* Solver::MakePathPrecedenceConstraint( std::vector nexts, const std::vector>& precedences, - const std::vector& lifo_path_starts, - const std::vector& fifo_path_starts) { + absl::Span lifo_path_starts, + absl::Span fifo_path_starts) { absl::flat_hash_map precedence_types; for (int start : lifo_path_starts) { diff --git a/ortools/constraint_solver/interval.cc b/ortools/constraint_solver/interval.cc index 52f908a9e9..dab09c47da 100644 --- a/ortools/constraint_solver/interval.cc +++ b/ortools/constraint_solver/interval.cc @@ -19,8 +19,8 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/types/span.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/constraint_solveri.h" @@ -61,6 +61,10 @@ class MirrorIntervalVar : public IntervalVar { public: MirrorIntervalVar(Solver* const s, IntervalVar* const t) : IntervalVar(s, "Mirror<" + t->name() + ">"), t_(t) {} + + // This type is neither copyable nor movable. + MirrorIntervalVar(const MirrorIntervalVar&) = delete; + MirrorIntervalVar& operator=(const MirrorIntervalVar&) = delete; ~MirrorIntervalVar() override {} // These methods query, set and watch the start position of the @@ -144,7 +148,6 @@ class MirrorIntervalVar : public IntervalVar { private: IntervalVar* const t_; - DISALLOW_COPY_AND_ASSIGN(MirrorIntervalVar); }; // An IntervalVar that passes all function calls to an underlying interval @@ -173,6 +176,12 @@ class AlwaysPerformedIntervalVarWrapper : public IntervalVar { duration_expr_(nullptr), end_expr_(nullptr) {} + // This type is neither copyable nor movable. + AlwaysPerformedIntervalVarWrapper(const AlwaysPerformedIntervalVarWrapper&) = + delete; + AlwaysPerformedIntervalVarWrapper& operator=( + const AlwaysPerformedIntervalVarWrapper&) = delete; + ~AlwaysPerformedIntervalVarWrapper() override {} int64_t StartMin() const override { return MayUnderlyingBePerformed() ? t_->StartMin() : kMinValidValue; @@ -285,7 +294,6 @@ class AlwaysPerformedIntervalVarWrapper : public IntervalVar { IntExpr* start_expr_; IntExpr* duration_expr_; IntExpr* end_expr_; - DISALLOW_COPY_AND_ASSIGN(AlwaysPerformedIntervalVarWrapper); }; // An interval variable that wraps around an underlying one, relaxing the max @@ -2058,6 +2066,12 @@ class FixedDurationSyncedIntervalVar : public IntervalVar { t_(t), duration_(duration), offset_(offset) {} + + // This type is neither copyable nor movable. + FixedDurationSyncedIntervalVar(const FixedDurationSyncedIntervalVar&) = + delete; + FixedDurationSyncedIntervalVar& operator=( + const FixedDurationSyncedIntervalVar&) = delete; ~FixedDurationSyncedIntervalVar() override {} int64_t DurationMin() const override { return duration_; } int64_t DurationMax() const override { return duration_; } @@ -2107,9 +2121,6 @@ class FixedDurationSyncedIntervalVar : public IntervalVar { IntervalVar* const t_; const int64_t duration_; const int64_t offset_; - - private: - DISALLOW_COPY_AND_ASSIGN(FixedDurationSyncedIntervalVar); }; // ----- Fixed duration interval var synced on start ----- @@ -2345,7 +2356,7 @@ void Solver::MakeFixedDurationIntervalVarArray( // the corresponding start variables. void Solver::MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, const std::string& name, + absl::Span durations, const std::string& name, std::vector* array) { CHECK(array != nullptr); CHECK_EQ(start_variables.size(), durations.size()); @@ -2359,7 +2370,7 @@ void Solver::MakeFixedDurationIntervalVarArray( void Solver::MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, const std::string& name, + absl::Span durations, const std::string& name, std::vector* array) { CHECK(array != nullptr); CHECK_EQ(start_variables.size(), durations.size()); @@ -2373,7 +2384,7 @@ void Solver::MakeFixedDurationIntervalVarArray( void Solver::MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, + absl::Span durations, const std::vector& performed_variables, const std::string& name, std::vector* array) { CHECK(array != nullptr); @@ -2387,7 +2398,7 @@ void Solver::MakeFixedDurationIntervalVarArray( void Solver::MakeFixedDurationIntervalVarArray( const std::vector& start_variables, - const std::vector& durations, + absl::Span durations, const std::vector& performed_variables, const std::string& name, std::vector* array) { CHECK(array != nullptr); diff --git a/ortools/constraint_solver/java/RoutingSolverTest.java b/ortools/constraint_solver/java/RoutingSolverTest.java index d17b8fa1a8..fee0e72874 100644 --- a/ortools/constraint_solver/java/RoutingSolverTest.java +++ b/ortools/constraint_solver/java/RoutingSolverTest.java @@ -529,7 +529,7 @@ public final class RoutingSolverTest { assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); final Assignment solution = model.solve(null); - assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertEquals(RoutingModel.ROUTING_OPTIMAL, model.status()); assertNotNull(solution); assertEquals(2 * (10 + 1), solution.objectiveValue()); } @@ -589,7 +589,7 @@ public final class RoutingSolverTest { assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); final Assignment solution = model.solve(null); - assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertEquals(RoutingModel.ROUTING_OPTIMAL, model.status()); assertNotNull(solution); for (int vehicle = 0; vehicle < 3; ++vehicle) { assertEquals(vehicle + 4, solution.max(dimension.cumulVar(model.start(vehicle)))); @@ -616,7 +616,7 @@ public final class RoutingSolverTest { assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); final Assignment solution = model.solve(null); - assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertEquals(RoutingModel.ROUTING_OPTIMAL, model.status()); assertNotNull(solution); for (int vehicle = 0; vehicle < 3; ++vehicle) { assertEquals( @@ -647,7 +647,7 @@ public final class RoutingSolverTest { assertEquals(RoutingModel.ROUTING_NOT_SOLVED, model.status()); final Assignment solution = model.solve(null); - assertEquals(RoutingModel.ROUTING_SUCCESS, model.status()); + assertEquals(RoutingModel.ROUTING_OPTIMAL, model.status()); assertNotNull(solution); for (int vehicle = 0; vehicle < 3; ++vehicle) { assertEquals(4, solution.max(dimension.cumulVar(model.start(vehicle)))); diff --git a/ortools/constraint_solver/local_search.cc b/ortools/constraint_solver/local_search.cc index a1c4840c23..559cabb555 100644 --- a/ortools/constraint_solver/local_search.cc +++ b/ortools/constraint_solver/local_search.cc @@ -36,7 +36,6 @@ #include "ortools/base/hash.h" #include "ortools/base/iterator_adaptors.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/map_util.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" @@ -1737,6 +1736,11 @@ class NearestNeighbors { public: NearestNeighbors(Solver::IndexEvaluator3 evaluator, const PathOperator& path_operator, int size); + + // This type is neither copyable nor movable. + NearestNeighbors(const NearestNeighbors&) = delete; + NearestNeighbors& operator=(const NearestNeighbors&) = delete; + virtual ~NearestNeighbors() {} void Initialize(); const std::vector& Neighbors(int index) const; @@ -1751,8 +1755,6 @@ class NearestNeighbors { const PathOperator& path_operator_; const int size_; bool initialized_; - - DISALLOW_COPY_AND_ASSIGN(NearestNeighbors); }; NearestNeighbors::NearestNeighbors(Solver::IndexEvaluator3 evaluator, @@ -4898,6 +4900,7 @@ void NestedSolveDecision::Apply(Solver* const solver) { } void NestedSolveDecision::Refute(Solver* const solver) {} +} // namespace // ----- Local search decision builder ----- @@ -5114,7 +5117,8 @@ Decision* LocalSearch::Next(Solver* const solver) { // accept up-hill moves (due to metaheuristics). In this case we need to // reset neighborhood optimal routes. ls_operator_->Reset(); - if (!LocalOptimumReached(solver->ActiveSearch())) { + if (!LocalOptimumReached(solver->ActiveSearch()) || + solver->IsUncheckedSolutionLimitReached()) { nested_decision_index_ = -1; // Stop the search } solver->Fail(); @@ -5195,7 +5199,6 @@ class DefaultSolutionPool : public SolutionPool { private: std::unique_ptr reference_assignment_; }; -} // namespace SolutionPool* Solver::MakeDefaultSolutionPool() { return RevAlloc(new DefaultSolutionPool()); diff --git a/ortools/constraint_solver/python/constraint_solver.i b/ortools/constraint_solver/python/constraint_solver.i index b9f76f0ba8..36790357f3 100644 --- a/ortools/constraint_solver/python/constraint_solver.i +++ b/ortools/constraint_solver/python/constraint_solver.i @@ -104,8 +104,10 @@ struct FailureProtect { // ============= Type conversions ============== // See ./constraint_solver_helpers.i +PY_CONVERT_HELPER_PTR(Constraint); PY_CONVERT_HELPER_PTR(Decision); PY_CONVERT_HELPER_PTR(DecisionBuilder); +PY_CONVERT_HELPER_PTR(Demon); PY_CONVERT_HELPER_PTR(SearchMonitor); PY_CONVERT_HELPER_PTR(IntervalVar); PY_CONVERT_HELPER_PTR(SequenceVar); @@ -114,11 +116,17 @@ PY_CONVERT_HELPER_PTR(LocalSearchFilter); PY_CONVERT_HELPER_PTR(LocalSearchFilterManager); PY_CONVERT_HELPER_INTEXPR_AND_INTVAR(); +%{ + +%} + // Actual conversions. This also includes the conversion to std::vector. PY_CONVERT(IntVar); PY_CONVERT(IntExpr); +PY_CONVERT(Constraint); PY_CONVERT(Decision); PY_CONVERT(DecisionBuilder); +PY_CONVERT(Demon); PY_CONVERT(SearchMonitor); PY_CONVERT(IntervalVar); PY_CONVERT(SequenceVar); @@ -603,6 +611,25 @@ PY_STRINGIFY_DEBUGSTRING(Decision); } } +%extend operations_research::IntVarLocalSearchFilter { + int64_t IndexFromVar(IntVar* const var) const { + int64_t index = -1; + $self->FindIndex(var, &index); + return index; + } +} + +// Extend IntVar to provide natural iteration over its domains. +%extend operations_research::IntVar { + %pythoncode { + def DomainIterator(self): + return iter(self.DomainIteratorAux(False)) + + def HoleIterator(self): + return iter(self.HoleIteratorAux(False)) + } // %pythoncode +} + // Extend IntVarIterator to make it iterable in python. %extend operations_research::IntVarIterator { %pythoncode { @@ -623,25 +650,6 @@ PY_STRINGIFY_DEBUGSTRING(Decision); } // %pythoncode } -// Extend IntVar to provide natural iteration over its domains. -%extend operations_research::IntVar { - %pythoncode { - def DomainIterator(self): - return iter(self.DomainIteratorAux(False)) - - def HoleIterator(self): - return iter(self.HoleIteratorAux(False)) - } // %pythoncode -} - -%extend operations_research::IntVarLocalSearchFilter { - int64_t IndexFromVar(IntVar* const var) const { - int64_t index = -1; - $self->FindIndex(var, &index); - return index; - } -} - // ############ BEGIN DUPLICATED CODE BLOCK ############ // IMPORTANT: keep this code block in sync with the .i // files in ../java and ../csharp. @@ -1285,8 +1293,8 @@ namespace operations_research { // the client needs to construct it. In these cases, we don't bother // setting the 'director' feature on individual methods, since it is done // automatically when setting it on the class. -%unignore Constraint; %feature("director") Constraint; +%unignore Constraint; %unignore Constraint::Constraint; %unignore Constraint::~Constraint; %unignore Constraint::Post; @@ -2090,10 +2098,6 @@ namespace operations_research { %pythoncode { class PyDecision(Decision): - - def __init__(self): - Decision.__init__(self) - def ApplyWrapper(self, solver): try: self.Apply(solver) @@ -2117,10 +2121,6 @@ class PyDecision(Decision): class PyDecisionBuilder(DecisionBuilder): - - def __init__(self): - DecisionBuilder.__init__(self) - def NextWrapper(self, solver): try: return self.Next(solver) @@ -2135,7 +2135,6 @@ class PyDecisionBuilder(DecisionBuilder): class PyDemon(Demon): - def RunWrapper(self, solver): try: self.Run(solver) @@ -2150,9 +2149,8 @@ class PyDemon(Demon): class PyConstraintDemon(PyDemon): - def __init__(self, ct, method, delayed, *args): - PyDemon.__init__(self) + super().__init__() self.__constraint = ct self.__method = method self.__delayed = delayed @@ -2169,9 +2167,8 @@ class PyConstraintDemon(PyDemon): class PyConstraint(Constraint): - def __init__(self, solver): - Constraint.__init__(self, solver) + super().__init__(solver) self.__demons = [] def Demon(self, method, *args): diff --git a/ortools/constraint_solver/python/cp_api_test.py b/ortools/constraint_solver/python/cp_api_test.py deleted file mode 100755 index 8d93e9d000..0000000000 --- a/ortools/constraint_solver/python/cp_api_test.py +++ /dev/null @@ -1,613 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2010-2022 Google LLC -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" Various calls to CP api from python to verify they work.""" - -import unittest -from ortools.constraint_solver import pywrapcp -from ortools.constraint_solver import search_limit_pb2 - - -class TestIntVarContainerAPI(unittest.TestCase): - - def test_contains(self): - self.assertTrue( - hasattr(pywrapcp.IntVarContainer, 'Contains'), - dir(pywrapcp.IntVarContainer)) - - def test_element(self): - self.assertTrue( - hasattr(pywrapcp.IntVarContainer, 'Element'), - dir(pywrapcp.IntVarContainer)) - - def test_size(self): - self.assertTrue( - hasattr(pywrapcp.IntVarContainer, 'Size'), dir(pywrapcp.IntVarContainer)) - - def test_store(self): - self.assertTrue( - hasattr(pywrapcp.IntVarContainer, 'Store'), dir(pywrapcp.IntVarContainer)) - - def test_restore(self): - self.assertTrue( - hasattr(pywrapcp.IntVarContainer, 'Restore'), - dir(pywrapcp.IntVarContainer)) - - -class TestIntervalVarContainerAPI(unittest.TestCase): - - def test_contains(self): - self.assertTrue( - hasattr(pywrapcp.IntervalVarContainer, 'Contains'), - dir(pywrapcp.IntervalVarContainer)) - - def test_element(self): - self.assertTrue( - hasattr(pywrapcp.IntervalVarContainer, 'Element'), - dir(pywrapcp.IntervalVarContainer)) - - def test_size(self): - self.assertTrue( - hasattr(pywrapcp.IntervalVarContainer, 'Size'), - dir(pywrapcp.IntervalVarContainer)) - - def test_store(self): - self.assertTrue( - hasattr(pywrapcp.IntervalVarContainer, 'Store'), - dir(pywrapcp.IntervalVarContainer)) - - def test_restore(self): - self.assertTrue( - hasattr(pywrapcp.IntervalVarContainer, 'Restore'), - dir(pywrapcp.IntervalVarContainer)) - - -class TestSequenceVarContainerAPI(unittest.TestCase): - - def test_contains(self): - self.assertTrue( - hasattr(pywrapcp.SequenceVarContainer, 'Contains'), - dir(pywrapcp.SequenceVarContainer)) - - def test_element(self): - self.assertTrue( - hasattr(pywrapcp.SequenceVarContainer, 'Element'), - dir(pywrapcp.SequenceVarContainer)) - - def test_size(self): - self.assertTrue( - hasattr(pywrapcp.SequenceVarContainer, 'Size'), - dir(pywrapcp.SequenceVarContainer)) - - def test_store(self): - self.assertTrue( - hasattr(pywrapcp.SequenceVarContainer, 'Store'), - dir(pywrapcp.SequenceVarContainer)) - - def test_restore(self): - self.assertTrue( - hasattr(pywrapcp.SequenceVarContainer, 'Restore'), - dir(pywrapcp.SequenceVarContainer)) - -class CPSolverTest(unittest.TestCase): - - def test_member(self): - solver = pywrapcp.Solver('test member') - x = solver.IntVar(1, 10, 'x') - ct = x.Member([1, 2, 3, 5]) - print('Constraint: {}'.format(ct)) - - def test_sparse_var(self): - solver = pywrapcp.Solver('test sparse') - x = solver.IntVar([1, 3, 5], 'x') - self.assertTrue(x.Contains(1)) - self.assertFalse(x.Contains(2)) - #print(x) - - def test_modulo(self): - solver = pywrapcp.Solver('test modulo') - x = solver.IntVar(0, 10, 'x') - y = solver.IntVar(2, 4, 'y') - print(x % 3) - print(x % y) - - def test_modulo2(self): - solver = pywrapcp.Solver('test modulo') - x = solver.IntVar([-7, 7], 'x') - y = solver.IntVar([-4, 4], 'y') - z = (x % y).Var() - t = (x // y).Var() - db = solver.Phase([x, y], solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE) - solver.NewSearch(db) - while solver.NextSolution(): - print('x = %d, y = %d, x %% y = %d, x div y = %d' % (x.Value(), - y.Value(), - z.Value(), - t.Value())) - solver.EndSearch() - - def test_limit(self): - solver = pywrapcp.Solver('test limit') - #limit_proto = solver.DefaultSearchLimitParameters() - limit_proto = search_limit_pb2.RegularLimitParameters() - limit_proto.time = 10000 - limit_proto.branches = 10 - print('limit proto: {}'.format(limit_proto)) - limit = solver.Limit(limit_proto) - print('limit: {}'.format(limit)) - - def test_export(self): - solver = pywrapcp.Solver('test export') - x = solver.IntVar(1, 10, 'x') - ct = x.Member([1, 2, 3, 5]) - solver.Add(ct) - #proto = model_pb2.CpModel() - #proto.model = 'wrong name' - #solver.ExportModel(proto) - #print(repr(proto)) - #print(str(proto)) - - def test_size_1_var(self): - solver = pywrapcp.Solver('test_size_1_var') - x = solver.IntVar([0], 'x') - - - def test_cumulative_api(self): - solver = pywrapcp.Solver('Problem') - - #Vars - S = [ - solver.FixedDurationIntervalVar(0, 10, 5, False, "S_%s" % a) - for a in range(10) - ] - C = solver.IntVar(2, 5) - D = [a % 3 + 2 for a in range(10)] - solver.Add(solver.Cumulative(S, D, C, "cumul")) - - def test_search_alldiff(self): - solver = pywrapcp.Solver('test_search_alldiff') - in_pos = [solver.IntVar(0, 7, "%i" % i) for i in range(8)] - solver.Add(solver.AllDifferent(in_pos)) - aux_phase = solver.Phase(in_pos, solver.CHOOSE_LOWEST_MIN, - solver.ASSIGN_MAX_VALUE) - collector = solver.FirstSolutionCollector() - for i in range(8): - collector.Add(in_pos[i]) - solver.Solve(aux_phase, [collector]) - for i in range(8): - print(collector.Value(0, in_pos[i])) - - -class CustomSearchMonitor(pywrapcp.SearchMonitor): - def __init__(self, solver, nexts): - print('Build') - pywrapcp.SearchMonitor.__init__(self, solver) - self._nexts = nexts - - def BeginInitialPropagation(self): - print('In BeginInitialPropagation') - print(self._nexts) - - def EndInitialPropagation(self): - print('In EndInitialPropagation') - print(self._nexts) - - -class SearchMonitorTest(unittest.TestCase): - def test_search_monitor(self): - print('test_search_monitor') - solver = pywrapcp.Solver('test search monitor') - x = solver.IntVar(1, 10, 'x') - ct = (x == 3) - solver.Add(ct) - db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE) - monitor = CustomSearchMonitor(solver, x) - solver.Solve(db, monitor) - - -class CustomDemon(pywrapcp.PyDemon): - def __init__(self, x): - pywrapcp.Demon.__init__(self) - self._x = x - print('Demon built') - - def Run(self, solver): - print('in Run(), saw ' + str(self._x)) - - -class DemonTest(unittest.TestCase): - def test_demon(self): - print('test_demon') - solver = pywrapcp.Solver('test export') - x = solver.IntVar(1, 10, 'x') - demon = CustomDemon(x) - demon.Run(solver) - - -class CustomConstraint(pywrapcp.PyConstraint): - def __init__(self, solver, x): - pywrapcp.Constraint.__init__(self, solver) - self._x = x - print('Constraint built') - - def Post(self): - print('in Post()') - self._demon = CustomDemon(self._x) - self._x.WhenBound(self._demon) - print('out of Post()') - - def InitialPropagate(self): - print('in InitialPropagate()') - self._x.SetMin(5) - print(self._x) - print('out of InitialPropagate()') - - def DebugString(self): - return ('CustomConstraint') - - -class InitialPropagateDemon(pywrapcp.PyDemon): - def __init__(self, ct): - pywrapcp.Demon.__init__(self) - self._ct = ct - - def Run(self, solver): - self._ct.InitialPropagate() - - -class DumbGreaterOrEqualToFive(pywrapcp.PyConstraint): - def __init__(self, solver, x): - pywrapcp.Constraint.__init__(self, solver) - self._x = x - - def Post(self): - self._demon = InitialPropagateDemon(self) - self._x.WhenBound(self._demon) - - def InitialPropagate(self): - if self._x.Bound(): - if self._x.Value() < 5: - print('Reject %d' % self._x.Value()) - self.solver().Fail() - else: - print('Accept %d' % self._x.Value()) - - -class WatchDomain(pywrapcp.PyDemon): - def __init__(self, x): - pywrapcp.Demon.__init__(self) - self._x = x - - def Run(self, solver): - for i in self._x.HoleIterator(): - print('Removed %d' % i) - - -class HoleConstraint(pywrapcp.PyConstraint): - def __init__(self, solver, x): - pywrapcp.Constraint.__init__(self, solver) - self._x = x - - def Post(self): - self._demon = WatchDomain(self._x) - self._x.WhenDomain(self._demon) - - def InitialPropagate(self): - self._x.RemoveValue(5) - - -class BinarySum(pywrapcp.PyConstraint): - def __init__(self, solver, x, y, z): - pywrapcp.Constraint.__init__(self, solver) - self._x = x - self._y = y - self._z = z - - def Post(self): - self._demon = InitialPropagateDemon(self) - self._x.WhenRange(self._demon) - self._y.WhenRange(self._demon) - self._z.WhenRange(self._demon) - - def InitialPropagate(self): - self._z.SetRange(self._x.Min() + self._y.Min(), - self._x.Max() + self._y.Max()) - self._x.SetRange(self._z.Min() - self._y.Max(), - self._z.Max() - self._y.Min()) - self._y.SetRange(self._z.Min() - self._x.Max(), - self._z.Max() - self._x.Min()) - -class ConstraintTest(unittest.TestCase): - def test_constraint(self): - solver = pywrapcp.Solver('test export') - x = solver.IntVar(1, 10, 'x') - myct = CustomConstraint(solver, x) - solver.Add(myct) - db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE) - solver.Solve(db) - - def test_failing_constraint(self): - solver = pywrapcp.Solver('test export') - x = solver.IntVar(1, 10, 'x') - myct = DumbGreaterOrEqualToFive(solver, x) - solver.Add(myct) - db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE) - solver.Solve(db) - - - def test_domain_iterator(self): - print('test_domain_iterator') - solver = pywrapcp.Solver('test_domain_iterator') - x = solver.IntVar([1, 2, 4, 6], 'x') - for i in x.DomainIterator(): - print(i) - - def test_hole_iterator(self): - print('test_hole_iterator') - solver = pywrapcp.Solver('test export') - x = solver.IntVar(1, 10, 'x') - myct = HoleConstraint(solver, x) - solver.Add(myct) - db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE) - solver.Solve(db) - - def test_sum_constraint(self): - print('test_sum_constraint') - solver = pywrapcp.Solver('test_sum_constraint') - x = solver.IntVar(1, 5, 'x') - y = solver.IntVar(1, 5, 'y') - z = solver.IntVar(1, 5, 'z') - binary_sum = BinarySum(solver, x, y, z) - solver.Add(binary_sum) - db = solver.Phase([x, y, z], solver.CHOOSE_FIRST_UNBOUND, - solver.ASSIGN_MIN_VALUE) - solver.NewSearch(db) - while solver.NextSolution(): - print('%d + %d == %d' % (x.Value(), y.Value(), z.Value())) - solver.EndSearch() - - -class CustomDecisionBuilder(pywrapcp.PyDecisionBuilder): - def __init__(self): - pywrapcp.PyDecisionBuilder.__init__(self) - - def Next(self, solver): - print("In Next") - return None - - def DebugString(self): - return 'CustomDecisionBuilder' - - - -class CustomDecision(pywrapcp.PyDecision): - def __init__(self): - pywrapcp.PyDecision.__init__(self) - self._val = 1 - print("Set value to", self._val) - - def Apply(self, solver): - print('In Apply') - print("Expect value", self._val) - solver.Fail() - - def Refute(self, solver): - print('In Refute') - - def DebugString(self): - return ('CustomDecision') - - -class CustomDecisionBuilderCustomDecision(pywrapcp.PyDecisionBuilder): - def __init__(self): - pywrapcp.PyDecisionBuilder.__init__(self) - self.__done = False - - def Next(self, solver): - if not self.__done: - self.__done = True - self.__decision = CustomDecision() - return self.__decision - return None - - def DebugString(self): - return 'CustomDecisionBuilderCustomDecision' - - -class DecisionTest(unittest.TestCase): - def test_custom_decision_builder(self): - solver = pywrapcp.Solver('test_custom_decision_builder') - db = CustomDecisionBuilder() - print(str(db)) - solver.Solve(db) - - def test_custom_decision(self): - solver = pywrapcp.Solver('test_custom_decision') - db = CustomDecisionBuilderCustomDecision() - print(str(db)) - solver.Solve(db) - - -class LocalSearchTest(unittest.TestCase): - class OneVarLNS(pywrapcp.BaseLns): - """One Var LNS.""" - def __init__(self, vars): - pywrapcp.BaseLns.__init__(self, vars) - self.__index = 0 - - def InitFragments(self): - self.__index = 0 - - def NextFragment(self): - if self.__index < self.Size(): - self.AppendToFragment(self.__index) - self.__index += 1 - return True - else: - return False - - - class MoveOneVar(pywrapcp.IntVarLocalSearchOperator): - """Move one var up or down.""" - def __init__(self, vars): - pywrapcp.IntVarLocalSearchOperator.__init__(self, vars) - self.__index = 0 - self.__up = False - - def OneNeighbor(self): - current_value = self.OldValue(self.__index) - if self.__up: - self.SetValue(self.__index, current_value + 1) - self.__index = (self.__index + 1) % self.Size() - else: - self.SetValue(self.__index, current_value - 1) - self.__up = not self.__up - return True - - def OnStart(self): - pass - - def IsIncremental(self): - return False - - - class SumFilter(pywrapcp.IntVarLocalSearchFilter): - """Filter to speed up LS computation.""" - def __init__(self, vars): - pywrapcp.IntVarLocalSearchFilter.__init__(self, vars) - self.__sum = 0 - - def OnSynchronize(self, delta): - self.__sum = sum(self.Value(index) for index in range(self.Size())) - - def Accept(self, delta, unused_delta_delta, unused_objective_min, - unused_objective_max): - solution_delta = delta.IntVarContainer() - solution_delta_size = solution_delta.Size() - for i in range(solution_delta_size): - if not solution_delta.Element(i).Activated(): - return True - new_sum = self.__sum - for i in range(solution_delta_size): - element = solution_delta.Element(i) - int_var = element.Var() - touched_var_index = self.IndexFromVar(int_var) - old_value = self.Value(touched_var_index) - new_value = element.Value() - new_sum += new_value - old_value - - return new_sum < self.__sum - - def IsIncremental(self): - return False - - - def Solve(self, type): - solver = pywrapcp.Solver('Solve') - int_vars = [solver.IntVar(0, 4) for _ in range(4)] - sum_var = solver.Sum(int_vars).Var() - obj = solver.Minimize(sum_var, 1) - db = solver.Phase(int_vars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MAX_VALUE) - ls = None - - if type == 0: # LNS - print('Large Neighborhood Search') - one_var_lns = self.OneVarLNS(int_vars) - ls_params = solver.LocalSearchPhaseParameters(sum_var, one_var_lns, db) - ls = solver.LocalSearchPhase(int_vars, db, ls_params) - elif type == 1: # LS - print('Local Search') - move_one_var = self.MoveOneVar(int_vars) - ls_params = solver.LocalSearchPhaseParameters(sum_var, move_one_var, db) - ls = solver.LocalSearchPhase(int_vars, db, ls_params) - else: - print('Local Search with Filter') - move_one_var = self.MoveOneVar(int_vars) - sum_filter = self.SumFilter(int_vars) - filter_manager = pywrapcp.LocalSearchFilterManager([sum_filter]) - ls_params = solver.LocalSearchPhaseParameters(sum_var, move_one_var, db, None, - filter_manager) - ls = solver.LocalSearchPhase(int_vars, db, ls_params) - - collector = solver.LastSolutionCollector() - collector.Add(int_vars) - collector.AddObjective(sum_var) - log = solver.SearchLog(1000, obj) - solver.Solve(ls, [collector, obj, log]) - print('Objective value = %d' % collector.ObjectiveValue(0)) - - def test_large_neighborhood_search(self): - self.Solve(0) - - def test_local_search(self): - pass - self.Solve(1) - - def test_local_search_with_filter(self): - pass - self.Solve(2) - - -class IntVarLocalSearchOperatorTest(unittest.TestCase): - def test_ctor(self): - solver = pywrapcp.Solver('Solve') - int_vars = [solver.IntVar(0, 4) for _ in range(4)] - ivlso = pywrapcp.IntVarLocalSearchOperator(int_vars) - assert ivlso != None - - def test_api(self): - print(f"{dir(pywrapcp.IntVarLocalSearchOperator)}") - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'Size') - - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'Var') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'AddVars') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'IsIncremental') - - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'Activate') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'Deactivate') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'Activated') - - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'OldValue') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'PrevValue') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'Value') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'SetValue') - - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'Start') - assert hasattr(pywrapcp.IntVarLocalSearchOperator, 'OnStart') - - # Activate(int64_t index) - # Deactivate(int64_t index) - # Activated(int64_t index) const - - # AddVars(const std::vector& vars) - # Size() const - # IsIncremental() const - - # OldValue(int64_t index) const - # PrevValue(int64_t index) const - - # SetValue(int64_t index, int64_t value) - # Value(int64_t index) const - - # OnStart() - # SkipUnchanged(int index) const - # Var(int64_t index) const - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/ortools/constraint_solver/python/pywrapcp_test.py b/ortools/constraint_solver/python/pywrapcp_test.py index f2fd44ed4d..77c3cf1af7 100755 --- a/ortools/constraint_solver/python/pywrapcp_test.py +++ b/ortools/constraint_solver/python/pywrapcp_test.py @@ -12,15 +12,521 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Unit tests for python/constraint_solver.i. Not exhaustive.""" +"""Test Constraint Solver API.""" import sys import unittest - +from ortools.constraint_solver import search_limit_pb2 +from ortools.constraint_solver import solver_parameters_pb2 from ortools.constraint_solver import pywrapcp -class SearchMonitorTest(pywrapcp.SearchMonitor): +def inc_callback(i): + return i + 1 + + +class ClassIncCallback: + def __init__(self, increment): + self.__increment = increment + + def inc_method(self, i): + return i + self.__increment + + +class TestIntVarContainerAPI(unittest.TestCase): + def test_contains(self): + self.assertTrue( + hasattr(pywrapcp.IntVarContainer, "Contains"), + dir(pywrapcp.IntVarContainer), + ) + + def test_element(self): + self.assertTrue( + hasattr(pywrapcp.IntVarContainer, "Element"), + dir(pywrapcp.IntVarContainer), + ) + + def test_size(self): + self.assertTrue( + hasattr(pywrapcp.IntVarContainer, "Size"), dir(pywrapcp.IntVarContainer) + ) + + def test_store(self): + self.assertTrue( + hasattr(pywrapcp.IntVarContainer, "Store"), + dir(pywrapcp.IntVarContainer), + ) + + def test_restore(self): + self.assertTrue( + hasattr(pywrapcp.IntVarContainer, "Restore"), + dir(pywrapcp.IntVarContainer), + ) + + +class TestIntervalVarContainerAPI(unittest.TestCase): + def test_contains(self): + self.assertTrue( + hasattr(pywrapcp.IntervalVarContainer, "Contains"), + dir(pywrapcp.IntervalVarContainer), + ) + + def test_element(self): + self.assertTrue( + hasattr(pywrapcp.IntervalVarContainer, "Element"), + dir(pywrapcp.IntervalVarContainer), + ) + + def test_size(self): + self.assertTrue( + hasattr(pywrapcp.IntervalVarContainer, "Size"), + dir(pywrapcp.IntervalVarContainer), + ) + + def test_store(self): + self.assertTrue( + hasattr(pywrapcp.IntervalVarContainer, "Store"), + dir(pywrapcp.IntervalVarContainer), + ) + + def test_restore(self): + self.assertTrue( + hasattr(pywrapcp.IntervalVarContainer, "Restore"), + dir(pywrapcp.IntervalVarContainer), + ) + + +class TestSequenceVarContainerAPI(unittest.TestCase): + def test_contains(self): + self.assertTrue( + hasattr(pywrapcp.SequenceVarContainer, "Contains"), + dir(pywrapcp.SequenceVarContainer), + ) + + def test_element(self): + self.assertTrue( + hasattr(pywrapcp.SequenceVarContainer, "Element"), + dir(pywrapcp.SequenceVarContainer), + ) + + def test_size(self): + self.assertTrue( + hasattr(pywrapcp.SequenceVarContainer, "Size"), + dir(pywrapcp.SequenceVarContainer), + ) + + def test_store(self): + self.assertTrue( + hasattr(pywrapcp.SequenceVarContainer, "Store"), + dir(pywrapcp.SequenceVarContainer), + ) + + def test_restore(self): + self.assertTrue( + hasattr(pywrapcp.SequenceVarContainer, "Restore"), + dir(pywrapcp.SequenceVarContainer), + ) + + +class PyWrapCPTest(unittest.TestCase): + def testRabbitPheasant(self): + # Create the solver. + solver = pywrapcp.Solver("testRabbitPheasant") + + # Create the variables. + pheasant = solver.IntVar(0, 100, "pheasant") + rabbit = solver.IntVar(0, 100, "rabbit") + + # Create the constraints. + solver.Add(pheasant + rabbit == 20) + solver.Add(pheasant * 2 + rabbit * 4 == 56) + + # Create the search phase. + db = solver.Phase( + [rabbit, pheasant], solver.INT_VAR_DEFAULT, solver.ASSIGN_MIN_VALUE + ) + + # Create assignment + solution = solver.Assignment() + solution.Add(rabbit) + solution.Add(pheasant) + + collector = solver.FirstSolutionCollector(solution) + + # And solve. + solver.Solve(db, collector) + + self.assertEqual(1, collector.SolutionCount()) + current = collector.Solution(0) + + self.assertEqual(12, current.Value(pheasant)) + self.assertEqual(8, current.Value(rabbit)) + + def testSolverParameters(self): + # Create the parameters. + params = pywrapcp.Solver.DefaultSolverParameters() + self.assertIsInstance(params, solver_parameters_pb2.ConstraintSolverParameters) + self.assertFalse(params.trace_propagation) + params.trace_propagation = True + self.assertTrue(params.trace_propagation) + + # Create the solver. + solver = pywrapcp.Solver("testRabbitPheasantWithParameters", params) + inside_params = solver.Parameters() + self.assertTrue(inside_params.trace_propagation) + + def testSolverParametersFields(self): + params = solver_parameters_pb2.ConstraintSolverParameters() + bool_params = [ + "store_names", + "name_cast_variables", + "name_all_variables", + "profile_propagation", + "trace_propagation", + "trace_search", + "print_model", + "print_model_stats", + "print_added_constraints", + "disable_solve", + ] + for p in bool_params: + for v in [True, False]: + setattr(params, p, v) + self.assertEqual(getattr(params, p), v) + + int_params = ["trail_block_size", "array_split_size"] + for p in int_params: + for v in [10, 100]: + setattr(params, p, v) + self.assertEqual(getattr(params, p), v) + + string_params = ["profile_file"] + for p in string_params: + for v in ["", "tmp_file"]: + setattr(params, p, v) + self.assertEqual(getattr(params, p), v) + + def testIntVarAPI(self): + # Create the solver. + solver = pywrapcp.Solver("testIntVarAPI") + + c = solver.IntConst(3, "c") + self.assertEqual(3, c.Min()) + self.assertEqual(3, c.Max()) + self.assertEqual(3, c.Value()) + self.assertTrue(c.Bound()) + + b = solver.BoolVar("b") + self.assertEqual(0, b.Min()) + self.assertEqual(1, b.Max()) + + v1 = solver.IntVar(3, 10, "v1") + self.assertEqual(3, v1.Min()) + self.assertEqual(10, v1.Max()) + + v2 = solver.IntVar([1, 5, 3], "v2") + self.assertEqual(1, v2.Min()) + self.assertEqual(5, v2.Max()) + self.assertEqual(3, v2.Size()) + + # pylint: disable=too-many-statements + def testIntegerArithmetic(self): + solver = pywrapcp.Solver("testIntegerArithmetic") + + v1 = solver.IntVar(0, 10, "v1") + v2 = solver.IntVar(0, 10, "v2") + v3 = solver.IntVar(0, 10, "v3") + + e1 = v1 + v2 + e2 = v1 + 2 + e3 = solver.Sum([v1, v2, v3 * 3]) + + e4 = v1 - 3 + e5 = v1 - v2 + e6 = -v1 + + e7 = abs(e6) + e8 = v3.Square() + + e9 = v1 * 3 + e10 = v1 * v2 + + e11 = v2.IndexOf([0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]) + e11b = v2.IndexOf([0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20]) + e12 = solver.Min(e1, e2) + e13 = solver.Min(e3, 3) + e14 = solver.Min([e1 + 1, e2 + 2, e3 + 3]) + + e15 = solver.Max(e1, e2) + e16 = solver.Max(e3, 3) + e17 = solver.Max([e1 + 1, e2 + 2, e3 + 3]) + + solver.Add(v1 == 1) + solver.Add(v2 == 2) + solver.Add(v3 == 3) + + db = solver.Phase([v1, v2, v3], solver.INT_VAR_DEFAULT, solver.ASSIGN_MIN_VALUE) + + solver.NewSearch(db) + solver.NextSolution() + + self.assertEqual(1, v1.Value()) + self.assertEqual(2, v2.Value()) + self.assertEqual(3, v3.Value()) + + self.assertEqual(3, e1.Min()) + self.assertEqual(3, e1.Max()) + self.assertEqual(3, e2.Min()) + self.assertEqual(3, e2.Max()) + self.assertEqual(12, e3.Min()) + self.assertEqual(12, e3.Max()) + self.assertEqual(-2, e4.Min()) + self.assertEqual(-2, e4.Max()) + self.assertEqual(-1, e5.Min()) + self.assertEqual(-1, e5.Max()) + self.assertEqual(-1, e6.Min()) + self.assertEqual(-1, e6.Max()) + self.assertEqual(1, e7.Min()) + self.assertEqual(1, e7.Max()) + self.assertEqual(9, e8.Min()) + self.assertEqual(9, e8.Max()) + self.assertEqual(3, e9.Min()) + self.assertEqual(3, e9.Max()) + self.assertEqual(2, e10.Min()) + self.assertEqual(2, e10.Max()) + self.assertEqual(4, e11.Min()) + self.assertEqual(4, e11.Max()) + self.assertEqual(4, e11b.Min()) + self.assertEqual(4, e11b.Max()) + self.assertEqual(3, e12.Min()) + self.assertEqual(3, e12.Max()) + self.assertEqual(3, e13.Min()) + self.assertEqual(3, e13.Max()) + self.assertEqual(4, e14.Min()) + self.assertEqual(4, e14.Max()) + self.assertEqual(3, e15.Min()) + self.assertEqual(3, e15.Max()) + self.assertEqual(12, e16.Min()) + self.assertEqual(12, e16.Max()) + self.assertEqual(15, e17.Min()) + self.assertEqual(15, e17.Max()) + solver.EndSearch() + + def testStatusVar(self): + solver = pywrapcp.Solver("testStatusVar") + v1 = solver.IntVar(0, 10, "v1") + v2 = solver.IntVar(0, 10, "v2") + c1 = v1 == 3 + c2 = v1 != 2 + print(c1) + print(c1.Var()) + print(c2) + print(c2.Var()) + e3 = v1 + c1 + print(e3) + e4 = c1 + c2 == 1 + print(e4) + e5 = solver.Min(c1, c2) + print(e5) + e6 = solver.Max([c1, c2, e3]) + print(e6) + e7 = 1 + c2 + print(e7) + e8 = solver.Max([v1 > 3, v1 <= 2, v2, v2 <= 0, v2 > 5]) + print(e8) + e9 = solver.Min([v1 == v2, v1 != v2, v1 < v2, v1 > v2, v1 <= v2, v1 >= v2]) + print(e9) + + def testAllowedAssignment(self): + solver = pywrapcp.Solver("testAllowedAssignment") + + v1 = solver.IntVar(0, 10, "v1") + v2 = solver.IntVar(0, 10, "v2") + v3 = solver.IntVar(0, 10, "v3") + + tuples = [(0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4)] + dvars = [v1, v2, v3] + solver.Add(solver.AllowedAssignments(dvars, tuples)) + db = solver.Phase(dvars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) + + solver.NewSearch(db) + counter = 0 + while solver.NextSolution(): + self.assertEqual(counter, v1.Value()) + self.assertEqual(counter, v2.Value()) + self.assertEqual(counter, v3.Value()) + counter += 1 + solver.EndSearch() + self.assertEqual(5, counter) + + def testAllowedAssignment2(self): + solver = pywrapcp.Solver("testAllowedAssignment") + + v1 = solver.IntVar(0, 10, "v1") + v2 = solver.IntVar(0, 10, "v2") + v3 = solver.IntVar(0, 10, "v3") + + dvars = [v1, v2, v3] + solver.Add(solver.AllowedAssignments(dvars, [(x, x, x) for x in range(5)])) + db = solver.Phase(dvars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) + + solver.NewSearch(db) + counter = 0 + while solver.NextSolution(): + self.assertEqual(counter, v1.Value()) + self.assertEqual(counter, v2.Value()) + self.assertEqual(counter, v3.Value()) + counter += 1 + solver.EndSearch() + self.assertEqual(5, counter) + + def testIntExprToIntVarCast(self): + solver = pywrapcp.Solver("testIntExprToIntVarCast") + + var1 = solver.IntVar(0, 10, "var1") + var2 = solver.IntVar(0, 10, "var2") + values = [1, 3, 5, 7, 9, 2, 4, 6, 8, 0] + # This test fails if the cast is not correctly done. + expr = (var1 + var2).IndexOf(values) + self.assertTrue(expr) + + def testIntExprToIntVarCastInSolution(self): + solver = pywrapcp.Solver("testIntExprToIntVarCastInSolution") + + var1 = solver.IntVar(0, 10, "var1") + var2 = solver.IntVar(0, 10, "var2") + solution = solver.Assignment() + expr = var1 + var2 + solution.Add(expr) + solution.Store() + # The next line fails if the cast is not correctly done. + self.assertEqual(20, solution.Max(expr)) + + def testIndexOf(self): + solver = pywrapcp.Solver("element") + index = solver.IntVar(0, 2, "index") + element = index.IndexOf([1, 2, 3]) + self.assertEqual(1, element.Min()) + self.assertEqual(3, element.Max()) + + def testElementFunction(self): + solver = pywrapcp.Solver("element") + index = solver.IntVar(0, 2, "index") + element = solver.ElementFunction(inc_callback, index) + self.assertEqual(1, element.Min()) + self.assertEqual(3, element.Max()) + + def testElementMethod(self): + solver = pywrapcp.Solver("element") + index = solver.IntVar(0, 2, "index") + class_callback = ClassIncCallback(2) + class_method = class_callback.inc_method + self.assertEqual(5, class_method(3)) + element = solver.ElementFunction(class_method, index) + self.assertEqual(2, element.Min()) + self.assertEqual(4, element.Max()) + + # TODO(user): better test all other ForwardSequence methods. + def testForwardSequence(self): + solver = pywrapcp.Solver("element") + intervals = [ + solver.FixedDurationIntervalVar(0, 10, 5, False, "Youpi") for _ in range(10) + ] + disjunction = solver.DisjunctiveConstraint(intervals, "Blup") + sequence = disjunction.SequenceVar() + assignment = solver.Assignment() + assignment.Add(sequence) + assignment.SetForwardSequence(sequence, [1, 3, 5]) + self.assertListEqual(assignment.ForwardSequence(sequence), [1, 3, 5]) + + def test_member(self): + solver = pywrapcp.Solver("test member") + x = solver.IntVar(1, 10, "x") + ct = x.Member([1, 2, 3, 5]) + print("Constraint: {}".format(ct)) + + def test_sparse_var(self): + solver = pywrapcp.Solver("test sparse") + x = solver.IntVar([1, 3, 5], "x") + self.assertTrue(x.Contains(1)) + self.assertFalse(x.Contains(2)) + # print(x) + + def test_modulo(self): + solver = pywrapcp.Solver("test modulo") + x = solver.IntVar(0, 10, "x") + y = solver.IntVar(2, 4, "y") + print(x % 3) + print(x % y) + + def test_modulo2(self): + solver = pywrapcp.Solver("test modulo") + x = solver.IntVar([-7, 7], "x") + y = solver.IntVar([-4, 4], "y") + z = (x % y).Var() + t = (x // y).Var() + db = solver.Phase([x, y], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) + solver.NewSearch(db) + while solver.NextSolution(): + print( + "x = %d, y = %d, x %% y = %d, x div y = %d" + % (x.Value(), y.Value(), z.Value(), t.Value()) + ) + solver.EndSearch() + + def test_limit(self): + solver = pywrapcp.Solver("test limit") + # limit_proto = solver.DefaultSearchLimitParameters() + limit_proto = search_limit_pb2.RegularLimitParameters() + limit_proto.time = 10000 + limit_proto.branches = 10 + print("limit proto: {}".format(limit_proto)) + limit = solver.Limit(limit_proto) + print("limit: {}".format(limit)) + + def test_export(self): + solver = pywrapcp.Solver("test export") + x = solver.IntVar(1, 10, "x") + ct = x.Member([1, 2, 3, 5]) + solver.Add(ct) + # proto = model_pb2.CpModel() + # proto.model = 'wrong name' + # solver.ExportModel(proto) + # print(repr(proto)) + # print(str(proto)) + + def test_size_1_var(self): + solver = pywrapcp.Solver("test_size_1_var") + x = solver.IntVar([0], "x") + + def test_cumulative_api(self): + solver = pywrapcp.Solver("Problem") + + # Vars + intervals = [ + solver.FixedDurationIntervalVar(0, 10, 5, False, "S_%s" % a) + for a in range(10) + ] + demands = [a % 3 + 2 for a in range(10)] + capacity = solver.IntVar(2, 5) + solver.Add(solver.Cumulative(intervals, demands, capacity, "cumul")) + + def test_search_alldiff(self): + solver = pywrapcp.Solver("test_search_alldiff") + in_pos = [solver.IntVar(0, 7, "%i" % i) for i in range(8)] + solver.Add(solver.AllDifferent(in_pos)) + aux_phase = solver.Phase( + in_pos, solver.CHOOSE_LOWEST_MIN, solver.ASSIGN_MAX_VALUE + ) + collector = solver.FirstSolutionCollector() + for i in range(8): + collector.Add(in_pos[i]) + solver.Solve(aux_phase, [collector]) + for i in range(8): + print(collector.Value(0, in_pos[i])) + + +class CustomSearchMonitor(pywrapcp.SearchMonitor): def __init__(self, solver, nexts): pywrapcp.SearchMonitor.__init__(self, solver) self._nexts = nexts @@ -32,9 +538,21 @@ class SearchMonitorTest(pywrapcp.SearchMonitor): print(self._nexts) -class DemonTest(pywrapcp.PyDemon): +class SearchMonitorTest(unittest.TestCase): + def test_search_monitor(self): + print("test_search_monitor") + solver = pywrapcp.Solver("test search monitor") + x = solver.IntVar(1, 10, "x") + ct = x == 3 + solver.Add(ct) + db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) + monitor = CustomSearchMonitor(solver, x) + solver.Solve(db, monitor) + + +class CustomDemon(pywrapcp.PyDemon): def __init__(self, x): - pywrapcp.PyDemon.__init__(self) + super().__init__() self._x = x print("Demon built") @@ -42,17 +560,26 @@ class DemonTest(pywrapcp.PyDemon): print("in Run(), saw " + str(self._x)) -class ConstraintTest(pywrapcp.PyConstraint): +class DemonTest(unittest.TestCase): + def test_demon(self): + print("test_demon") + solver = pywrapcp.Solver("test export") + x = solver.IntVar(1, 10, "x") + demon = CustomDemon(x) + demon.Run(solver) + + +class CustomConstraint(pywrapcp.PyConstraint): def __init__(self, solver, x): - pywrapcp.Constraint.__init__(self, solver) + super().__init__(solver) self._x = x print("Constraint built") def Post(self): - print("in Post()") - self._demon = DemonTest(self._x) + print("in Post()", file=sys.stderr) + self._demon = CustomDemon(self._x) self._x.WhenBound(self._demon) - print("out of Post()") + print("out of Post()", file=sys.stderr) def InitialPropagate(self): print("in InitialPropagate()") @@ -60,10 +587,13 @@ class ConstraintTest(pywrapcp.PyConstraint): print(self._x) print("out of InitialPropagate()") + def DebugString(self): + return "CustomConstraint" + class InitialPropagateDemon(pywrapcp.PyDemon): def __init__(self, ct): - pywrapcp.Demon.__init__(self) + super().__init__() self._ct = ct def Run(self, solver): @@ -72,7 +602,7 @@ class InitialPropagateDemon(pywrapcp.PyDemon): class DumbGreaterOrEqualToFive(pywrapcp.PyConstraint): def __init__(self, solver, x): - pywrapcp.Constraint.__init__(self, solver) + super().__init__(solver) self._x = x def Post(self): @@ -82,15 +612,15 @@ class DumbGreaterOrEqualToFive(pywrapcp.PyConstraint): def InitialPropagate(self): if self._x.Bound(): if self._x.Value() < 5: - print("Reject %d" % self._x.Value()) + print("Reject %d" % self._x.Value(), file=sys.stderr) self.solver().Fail() else: - print("Accept %d" % self._x.Value()) + print("Accept %d" % self._x.Value(), file=sys.stderr) class WatchDomain(pywrapcp.PyDemon): def __init__(self, x): - pywrapcp.Demon.__init__(self) + super().__init__() self._x = x def Run(self, solver): @@ -98,9 +628,9 @@ class WatchDomain(pywrapcp.PyDemon): print("Removed %d" % i) -class HoleConstraintTest(pywrapcp.PyConstraint): +class HoleConstraint(pywrapcp.PyConstraint): def __init__(self, solver, x): - pywrapcp.Constraint.__init__(self, solver) + super().__init__(solver) self._x = x def Post(self): @@ -113,7 +643,7 @@ class HoleConstraintTest(pywrapcp.PyConstraint): class BinarySum(pywrapcp.PyConstraint): def __init__(self, solver, x, y, z): - pywrapcp.Constraint.__init__(self, solver) + super().__init__(solver) self._x = x self._y = y self._z = z @@ -130,7 +660,7 @@ class BinarySum(pywrapcp.PyConstraint): self._y.SetRange(self._z.Min() - self._x.Max(), self._z.Max() - self._x.Min()) -class PyWrapCp(unittest.TestCase): +class ConstraintTest(unittest.TestCase): def test_member(self): print("test_member") solver = pywrapcp.Solver("test member") @@ -174,21 +704,21 @@ class PyWrapCp(unittest.TestCase): ct = x == 3 solver.Add(ct) db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) - monitor = SearchMonitorTest(solver, x) + monitor = CustomSearchMonitor(solver, x) solver.Solve(db, monitor) def test_demon(self): print("test_demon") solver = pywrapcp.Solver("test_demon") x = solver.IntVar(1, 10, "x") - demon = DemonTest(x) + demon = CustomDemon(x) demon.Run(solver) def test_constraint(self): print("test_constraint") solver = pywrapcp.Solver("test_constraint") x = solver.IntVar(1, 10, "x") - myct = ConstraintTest(solver, x) + myct = CustomConstraint(solver, x) solver.Add(myct) db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) solver.Solve(db) @@ -213,7 +743,7 @@ class PyWrapCp(unittest.TestCase): print("test_hole_iterator") solver = pywrapcp.Solver("test_hole_iterator") x = solver.IntVar(1, 10, "x") - myct = HoleConstraintTest(solver, x) + myct = HoleConstraint(solver, x) solver.Add(myct) db = solver.Phase([x], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE) solver.Solve(db) @@ -235,5 +765,516 @@ class PyWrapCp(unittest.TestCase): solver.EndSearch() +class CustomDecisionBuilder(pywrapcp.PyDecisionBuilder): + def Next(self, solver): + print("In Next") + return None + + def DebugString(self): + return "CustomDecisionBuilder" + + +class CustomDecision(pywrapcp.PyDecision): + def __init__(self): + super().__init__() + self._val = 1 + print("Set value to", self._val) + + def Apply(self, solver): + print("In Apply") + print("Expect value", self._val) + solver.Fail() + + def Refute(self, solver): + print("In Refute") + + def DebugString(self): + return "CustomDecision" + + +class CustomDecisionBuilderCustomDecision(pywrapcp.PyDecisionBuilder): + def __init__(self): + super().__init__() + self.__done = False + + def Next(self, solver): + if not self.__done: + self.__done = True + self.__decision = CustomDecision() + return self.__decision + return None + + def DebugString(self): + return "CustomDecisionBuilderCustomDecision" + + +class DecisionTest(unittest.TestCase): + def test_custom_decision_builder(self): + solver = pywrapcp.Solver("test_custom_decision_builder") + db = CustomDecisionBuilder() + print(str(db)) + solver.Solve(db) + + def test_custom_decision(self): + solver = pywrapcp.Solver("test_custom_decision") + db = CustomDecisionBuilderCustomDecision() + print(str(db)) + solver.Solve(db) + + +class LocalSearchTest(unittest.TestCase): + class OneVarLNS(pywrapcp.BaseLns): + """One Var LNS.""" + + def __init__(self, vars): + super().__init__(vars) + self.__index = 0 + + def InitFragments(self): + self.__index = 0 + + def NextFragment(self): + if self.__index < self.Size(): + self.AppendToFragment(self.__index) + self.__index += 1 + return True + else: + return False + + class MoveOneVar(pywrapcp.IntVarLocalSearchOperator): + """Move one var up or down.""" + + def __init__(self, int_vars): + super().__init__(int_vars) + self.__index = 0 + self.__up = False + + def OneNeighbor(self): + current_value = self.OldValue(self.__index) + if self.__up: + self.SetValue(self.__index, current_value + 1) + self.__index = (self.__index + 1) % self.Size() + else: + self.SetValue(self.__index, current_value - 1) + self.__up = not self.__up + return True + + def OnStart(self): + pass + + def IsIncremental(self): + return False + + class SumFilter(pywrapcp.IntVarLocalSearchFilter): + """Filter to speed up LS computation.""" + + def __init__(self, vars): + pywrapcp.IntVarLocalSearchFilter.__init__(self, vars) + self.__sum = 0 + + def OnSynchronize(self, delta): + self.__sum = sum(self.Value(index) for index in range(self.Size())) + + def Accept( + self, + delta, + unused_delta_delta, + unused_objective_min, + unused_objective_max, + ): + solution_delta = delta.IntVarContainer() + solution_delta_size = solution_delta.Size() + for i in range(solution_delta_size): + if not solution_delta.Element(i).Activated(): + return True + new_sum = self.__sum + for i in range(solution_delta_size): + element = solution_delta.Element(i) + int_var = element.Var() + touched_var_index = self.IndexFromVar(int_var) + old_value = self.Value(touched_var_index) + new_value = element.Value() + new_sum += new_value - old_value + + return new_sum < self.__sum + + def IsIncremental(self): + return False + + def solve(self, local_search_type): + solver = pywrapcp.Solver("Solve") + int_vars = [solver.IntVar(0, 4) for _ in range(4)] + sum_var = solver.Sum(int_vars).Var() + obj = solver.Minimize(sum_var, 1) + db = solver.Phase( + int_vars, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MAX_VALUE + ) + ls = None + if local_search_type == 0: # LNS + print("Large Neighborhood Search") + one_var_lns = self.OneVarLNS(int_vars) + ls_params = solver.LocalSearchPhaseParameters(sum_var, one_var_lns, db) + ls = solver.LocalSearchPhase(int_vars, db, ls_params) + elif local_search_type == 1: # LS + print("Local Search") + move_one_var = self.MoveOneVar(int_vars) + ls_params = solver.LocalSearchPhaseParameters(sum_var, move_one_var, db) + ls = solver.LocalSearchPhase(int_vars, db, ls_params) + else: + print("Local Search with Filter") + move_one_var = self.MoveOneVar(int_vars) + sum_filter = self.SumFilter(int_vars) + filter_manager = pywrapcp.LocalSearchFilterManager([sum_filter]) + ls_params = solver.LocalSearchPhaseParameters( + sum_var, move_one_var, db, None, filter_manager + ) + ls = solver.LocalSearchPhase(int_vars, db, ls_params) + + collector = solver.LastSolutionCollector() + collector.Add(int_vars) + collector.AddObjective(sum_var) + log = solver.SearchLog(1000, obj) + solver.Solve(ls, [collector, obj, log]) + print("Objective value = %d" % collector.ObjectiveValue(0)) + + def test_large_neighborhood_search(self): + pass + self.solve(0) + + def test_local_search(self): + pass + self.solve(1) + + def test_local_search_with_filter(self): + pass + self.solve(2) + + +class MyDecisionBuilder(pywrapcp.PyDecisionBuilder): + def __init__(self, var, value): + super().__init__() + self.__var = var + self.__value = value + + def Next(self, solver): + if not self.__var.Bound(): + decision = solver.AssignVariableValue(self.__var, self.__value) + return decision + + +class MyDecisionBuilderWithRev(pywrapcp.PyDecisionBuilder): + def __init__(self, var, value, rev): + super().__init__() + self.__var = var + self.__value = value + self.__rev = rev + + def Next(self, solver): + if not self.__var.Bound(): + if self.__var.Contains(self.__value): + decision = solver.AssignVariableValue(self.__var, self.__value) + self.__rev.SetValue(solver, self.__value) + return decision + else: + return solver.FailDecision() + + +class MyDecisionBuilderThatFailsWithRev(pywrapcp.PyDecisionBuilder): + def Next(self, solver): + solver.Fail() + return None + + +class PyWrapCPSearchTest(unittest.TestCase): + NUMBER_OF_VARIABLES = 10 + VARIABLE_MIN = 0 + VARIABLE_MAX = 10 + LNS_NEIGHBORS = 100 + LNS_VARIABLES = 4 + DECISION_BUILDER_VALUE = 5 + OTHER_DECISION_BUILDER_VALUE = 2 + + def testNewClassAsDecisionBuilder(self): + solver = pywrapcp.Solver("testNewClassAsDecisionBuilder") + x = solver.IntVar(self.VARIABLE_MIN, self.VARIABLE_MAX, "x") + phase = MyDecisionBuilder(x, self.DECISION_BUILDER_VALUE) + solver.NewSearch(phase) + solver.NextSolution() + self.assertTrue(x.Bound()) + self.assertEqual(self.DECISION_BUILDER_VALUE, x.Min()) + solver.EndSearch() + + def testComposeTwoDecisions(self): + solver = pywrapcp.Solver("testNewClassAsDecisionBuilder") + x = solver.IntVar(0, 10, "x") + y = solver.IntVar(0, 10, "y") + phase_x = MyDecisionBuilder(x, self.DECISION_BUILDER_VALUE) + phase_y = MyDecisionBuilder(y, self.OTHER_DECISION_BUILDER_VALUE) + phase = solver.Compose([phase_x, phase_y]) + solver.NewSearch(phase) + solver.NextSolution() + self.assertTrue(x.Bound()) + self.assertEqual(self.DECISION_BUILDER_VALUE, x.Min()) + self.assertTrue(y.Bound()) + self.assertEqual(self.OTHER_DECISION_BUILDER_VALUE, y.Min()) + solver.EndSearch() + + def testRandomLns(self): + solver = pywrapcp.Solver("testRandomLnsOperator") + x = [solver.BoolVar("x_%d" % i) for i in range(self.NUMBER_OF_VARIABLES)] + lns = solver.RandomLnsOperator(x, self.LNS_VARIABLES) + delta = solver.Assignment() + for _ in range(self.LNS_NEIGHBORS): + delta.Clear() + self.assertTrue(lns.NextNeighbor(delta, delta)) + self.assertLess(0, delta.Size()) + self.assertGreater(self.LNS_VARIABLES + 1, delta.Size()) + + def testConcatenateOperators(self): + solver = pywrapcp.Solver("testConcatenateOperators") + x = [solver.BoolVar("x_%d" % i) for i in range(self.NUMBER_OF_VARIABLES)] + op1 = solver.Operator(x, solver.INCREMENT) + op2 = solver.Operator(x, solver.DECREMENT) + concatenate = solver.ConcatenateOperators([op1, op2]) + solution = solver.Assignment() + solution.Add(x) + for v in x: + solution.SetValue(v, 1) + obj_var = solver.Sum(x) + objective = solver.Minimize(obj_var, 1) + collector = solver.LastSolutionCollector(solution) + ls_params = solver.LocalSearchPhaseParameters(obj_var.Var(), concatenate, None) + db = solver.LocalSearchPhase(solution, ls_params) + solver.Solve(db, [objective, collector]) + for v in x: + self.assertEqual(0, collector.Solution(0).Value(v)) + + def testRevIntegerOutsideSearch(self): + solver = pywrapcp.Solver("testRevValue") + revx = pywrapcp.RevInteger(12) + self.assertEqual(12, revx.Value()) + revx.SetValue(solver, 25) + self.assertEqual(25, revx.Value()) + + def testMemberApi(self): + solver = pywrapcp.Solver("testMemberApi") + x = solver.IntVar(0, 10, "x") + c1 = solver.MemberCt(x, [2, 5]) + c2 = x.Member([2, 5]) + self.assertEqual(str(c1), str(c2)) + c3 = solver.NotMemberCt(x, [2, 7], [4, 9]) + c4 = x.NotMember([2, 7], [4, 9]) + self.assertEqual(str(c3), str(c4)) + + def testRevIntegerInSearch(self): + solver = pywrapcp.Solver("testRevIntegerInSearch") + x = solver.IntVar(0, 10, "x") + rev = pywrapcp.RevInteger(12) + phase = MyDecisionBuilderWithRev(x, 5, rev) + solver.NewSearch(phase) + solver.NextSolution() + self.assertTrue(x.Bound()) + self.assertEqual(5, rev.Value()) + solver.NextSolution() + self.assertFalse(x.Bound()) + self.assertEqual(12, rev.Value()) + solver.EndSearch() + + def testDecisionBuilderThatFails(self): + solver = pywrapcp.Solver("testRevIntegerInSearch") + phase = MyDecisionBuilderThatFailsWithRev() + self.assertFalse(solver.Solve(phase)) + + # ----------------helper for binpacking posting---------------- + + def bin_packing_helper(self, cp, binvars, weights, loadvars): + nbins = len(loadvars) + nitems = len(binvars) + for j in range(nbins): + b = [cp.BoolVar(str(i)) for i in range(nitems)] + for i in range(nitems): + cp.Add(cp.IsEqualCstCt(binvars[i], j, b[i])) + cp.Add( + cp.Sum([b[i] * weights[i] for i in range(nitems)]) == loadvars[j] + ) + cp.Add(cp.Sum(loadvars) == sum(weights)) + + def testNoNewSearch(self): + maxcapa = 44 + weights = [4, 22, 9, 5, 8, 3, 3, 4, 7, 7, 3] + loss = [ + 0, + 11, + 10, + 9, + 8, + 7, + 6, + 5, + 4, + 3, + 2, + 1, + 0, + 1, + 0, + 2, + 1, + 0, + 0, + 0, + 0, + 2, + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 1, + 0, + 2, + 1, + 0, + 3, + 2, + 1, + 0, + 2, + 1, + 0, + 0, + 0, + ] + nbslab = 11 + + # ------------------solver and variable declaration------------- + + solver = pywrapcp.Solver("Steel Mill Slab") + x = [solver.IntVar(list(range(nbslab)), "x" + str(i)) for i in range(nbslab)] + l = [solver.IntVar(list(range(maxcapa)), "l" + str(i)) for i in range(nbslab)] + obj = solver.IntVar(list(range(nbslab * maxcapa)), "obj") + + # -------------------post of the constraints-------------- + + self.bin_packing_helper(solver, x, weights[:nbslab], l) + solver.Add(solver.Sum([l[s].IndexOf(loss) for s in range(nbslab)]) == obj) + + unused_sol = [2, 0, 0, 0, 0, 1, 2, 2, 1, 1, 2] + + # ------------start the search and optimization----------- + + unused_objective = solver.Minimize(obj, 1) + unused_db = solver.Phase(x, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT) + # solver.NewSearch(db,[objective]) #segfault if NewSearch is not called. + + while solver.NextSolution(): + print(obj, "check:", sum([loss[l[s].Min()] for s in range(nbslab)])) + print(l) + solver.EndSearch() + + +class SplitDomainDecisionBuilder(pywrapcp.PyDecisionBuilder): + def __init__(self, var, value, lower): + super().__init__() + self.__var = var + self.__value = value + self.__lower = lower + self.__done = pywrapcp.RevBool(False) + + def Next(self, solver): + if self.__done.Value(): + return None + self.__done.SetValue(solver, True) + return solver.SplitVariableDomain(self.__var, self.__value, self.__lower) + + +class PyWrapCPDecisionTest(unittest.TestCase): + def testSplitDomainLower(self): + solver = pywrapcp.Solver("testSplitDomainLower") + x = solver.IntVar(0, 10, "x") + phase = SplitDomainDecisionBuilder(x, 3, True) + solver.NewSearch(phase) + self.assertTrue(solver.NextSolution()) + self.assertEqual(0, x.Min()) + self.assertEqual(3, x.Max()) + self.assertTrue(solver.NextSolution()) + self.assertEqual(4, x.Min()) + self.assertEqual(10, x.Max()) + self.assertFalse(solver.NextSolution()) + solver.EndSearch() + + def testSplitDomainUpper(self): + solver = pywrapcp.Solver("testSplitDomainUpper") + x = solver.IntVar(0, 10, "x") + phase = SplitDomainDecisionBuilder(x, 6, False) + solver.NewSearch(phase) + self.assertTrue(solver.NextSolution()) + self.assertEqual(7, x.Min()) + self.assertEqual(10, x.Max()) + self.assertTrue(solver.NextSolution()) + self.assertEqual(0, x.Min()) + self.assertEqual(6, x.Max()) + self.assertFalse(solver.NextSolution()) + solver.EndSearch() + + def testTrueConstraint(self): + solver = pywrapcp.Solver("test") + x1 = solver.IntVar(4, 8, "x1") + x2 = solver.IntVar(3, 7, "x2") + x3 = solver.IntVar(1, 5, "x3") + solver.Add((x1 >= 3) + (x2 >= 6) + (x3 <= 3) == 3) + db = solver.Phase( + [x1, x2, x3], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE + ) + solver.NewSearch(db) + solver.NextSolution() + solver.EndSearch() + + def testFalseConstraint(self): + solver = pywrapcp.Solver("test") + x1 = solver.IntVar(4, 8, "x1") + x2 = solver.IntVar(3, 7, "x2") + x3 = solver.IntVar(1, 5, "x3") + solver.Add((x1 <= 3) + (x2 >= 6) + (x3 <= 3) == 3) + db = solver.Phase( + [x1, x2, x3], solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_MIN_VALUE + ) + solver.NewSearch(db) + solver.NextSolution() + solver.EndSearch() + + +class IntVarLocalSearchOperatorTest(unittest.TestCase): + def test_ctor(self): + solver = pywrapcp.Solver("Solve") + int_vars = [solver.IntVar(0, 4) for _ in range(4)] + ivlso = pywrapcp.IntVarLocalSearchOperator(int_vars) + self.assertTrue(ivlso != None) + + def test_api(self): + # print(f"{dir(pywrapcp.IntVarLocalSearchOperator)}") + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "Size")) + + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "Var")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "AddVars")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "IsIncremental")) + + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "Activate")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "Deactivate")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "Activated")) + + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "OldValue")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "PrevValue")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "Value")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "SetValue")) + + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "Start")) + self.assertTrue(hasattr(pywrapcp.IntVarLocalSearchOperator, "OnStart")) + + if __name__ == "__main__": unittest.main() diff --git a/ortools/constraint_solver/python/pywrapcp_util.i b/ortools/constraint_solver/python/pywrapcp_util.i index 52ef13a780..f262e34b45 100644 --- a/ortools/constraint_solver/python/pywrapcp_util.i +++ b/ortools/constraint_solver/python/pywrapcp_util.i @@ -22,67 +22,5 @@ #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/constraint_solveri.h" using std::string; -class CallPyDecisionBuilder : public operations_research::DecisionBuilder { - public: - explicit CallPyDecisionBuilder(PyObject* pydb) - : pysolver_(nullptr), pyarg_(nullptr) { - Py_INCREF(pydb); - pydb_ = pydb; - func_ = PyObject_GetAttrString(pydb_, "NextWrapper"); - Py_XINCREF(func_); - str_func_ = PyObject_GetAttrString(pydb_, "DebugString"); - Py_XINCREF(str_func_); - } - ~CallPyDecisionBuilder() override { - Py_CLEAR(pydb_); - Py_CLEAR(func_); - Py_CLEAR(str_func_); - Py_CLEAR(pysolver_); - Py_CLEAR(pyarg_); - } - operations_research::Decision* Next( - operations_research::Solver* const s) override { - if (pysolver_ == nullptr) { -//swiglint: disable swigtype-name - pysolver_ = SWIG_NewPointerObj(s, SWIGTYPE_p_operations_research__Solver, - SWIG_POINTER_EXCEPTION); -//swiglint: enable swigtype-name - pyarg_ = Py_BuildValue("(O)", pysolver_); - } - operations_research::Decision* result = nullptr; - PyObject* pyresult = PyEval_CallObject(func_, pyarg_); - if (pyresult) { -//swiglint: disable swigtype-name - if (SWIG_ConvertPtr(pyresult, reinterpret_cast(&result), - SWIGTYPE_p_operations_research__Decision, - SWIG_POINTER_EXCEPTION | 0) == -1) { -//swiglint: enable swigtype-name - LOG(INFO) << "Error in type from python Decision"; - } - Py_DECREF(pyresult); - } else { // something went wrong, we fail. - PyErr_SetString(PyExc_RuntimeError, - "ResultCallback invocation failed."); - s->FinishCurrentSearch(); - } - return result; - } - std::string DebugString() const override { - std::string result = "PyDecisionBuilder"; - if (str_func_) { - PyObject* pyresult = PyEval_CallObject(str_func_, nullptr); - if (pyresult) { - result = PyString_AsString(pyresult); - Py_DECREF(pyresult); - } - } - return result; - } - private: - PyObject* pysolver_; - PyObject* pyarg_; - PyObject* pydb_; - PyObject* func_; - PyObject* str_func_; -}; + %} diff --git a/ortools/constraint_solver/python/pywraprouting_test.py b/ortools/constraint_solver/python/pywraprouting_test.py index ef1c464876..fe28760cc1 100755 --- a/ortools/constraint_solver/python/pywraprouting_test.py +++ b/ortools/constraint_solver/python/pywraprouting_test.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -"""Test routing API.""" +"""Test Routing API.""" -from functools import partial +import functools import unittest - from ortools.constraint_solver import routing_enums_pb2 from ortools.constraint_solver import pywrapcp @@ -46,7 +45,7 @@ def Three(unused_i, unused_j): return 1 -class Callback(object): +class Callback: def __init__(self, model): self.model = model self.costs = [] @@ -55,12 +54,10 @@ class Callback(object): self.costs.append(self.model.CostVar().Max()) -class TestRoutingIndexManager(unittest.TestCase): +class TestPyWrapRoutingIndexManager(unittest.TestCase): def testCtor(self): manager = pywrapcp.RoutingIndexManager(42, 3, 7) self.assertIsNotNone(manager) - if __debug__: - print(f"class RoutingIndexManager: {dir(manager)}") self.assertEqual(42, manager.GetNumberOfNodes()) self.assertEqual(3, manager.GetNumberOfVehicles()) self.assertEqual(42 + 3 * 2 - 1, manager.GetNumberOfIndices()) @@ -89,7 +86,7 @@ class TestRoutingIndexManager(unittest.TestCase): self.assertEqual(i + 4, manager.IndexToNode(manager.GetEndIndex(i))) -class TestRoutingModel(unittest.TestCase): +class TestPyWrapRoutingModel(unittest.TestCase): def testCtor(self): manager = pywrapcp.RoutingIndexManager(42, 3, 7) self.assertIsNotNone(manager) @@ -98,8 +95,6 @@ class TestRoutingModel(unittest.TestCase): for i in range(manager.GetNumberOfVehicles()): self.assertEqual(7, manager.IndexToNode(model.Start(i))) self.assertEqual(7, manager.IndexToNode(model.End(i))) - if __debug__: - print(f"class RoutingModel: {dir(model)}") def testSolve(self): manager = pywrapcp.RoutingIndexManager(42, 3, 7) @@ -108,7 +103,7 @@ class TestRoutingModel(unittest.TestCase): self.assertIsNotNone(model) self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) assignment = model.Solve() - self.assertEqual(model.ROUTING_SUCCESS, model.status()) + self.assertEqual(model.ROUTING_OPTIMAL, model.status()) self.assertIsNotNone(assignment) self.assertEqual(0, assignment.ObjectiveValue()) @@ -119,7 +114,7 @@ class TestRoutingModel(unittest.TestCase): self.assertIsNotNone(model) self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) assignment = model.Solve() - self.assertEqual(model.ROUTING_SUCCESS, model.status()) + self.assertEqual(model.ROUTING_OPTIMAL, model.status()) self.assertIsNotNone(assignment) self.assertEqual(0, assignment.ObjectiveValue()) @@ -128,7 +123,9 @@ class TestRoutingModel(unittest.TestCase): self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) self.assertEqual(1, transit_idx) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) self.assertEqual(pywrapcp.RoutingModel.ROUTING_NOT_SOLVED, model.status()) @@ -156,13 +153,13 @@ class TestRoutingModel(unittest.TestCase): self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) - matrix = [[i + 1 for i in range(5)] for j in range(5)] + matrix = [[i + 1 for i in range(5)] for _ in range(5)] transit_idx = model.RegisterTransitMatrix(matrix) self.assertEqual(1, transit_idx) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) assignment = model.Solve() - self.assertIsNotNone(assignment) + self.assertTrue(assignment) self.assertEqual(model.ROUTING_SUCCESS, model.status()) self.assertEqual(15, assignment.ObjectiveValue()) @@ -172,7 +169,7 @@ class TestRoutingModel(unittest.TestCase): model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) transit_idx = model.RegisterUnaryTransitCallback( - partial(UnaryTransitDistance, manager) + functools.partial(UnaryTransitDistance, manager) ) self.assertEqual(1, transit_idx) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) @@ -212,12 +209,15 @@ class TestRoutingModel(unittest.TestCase): self.assertEqual(45, assignment.ObjectiveValue()) def testTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) # Solve @@ -238,12 +238,15 @@ class TestRoutingModel(unittest.TestCase): self.assertEqual(expected_visited_nodes, visited_nodes) def testVRP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 2, [0, 1], [1, 0]) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Solve search_parameters = pywrapcp.DefaultRoutingSearchParameters() @@ -263,12 +266,15 @@ class TestRoutingModel(unittest.TestCase): self.assertTrue(model.IsEnd(assignment.Value(model.NextVar(model.Start(0))))) def testDimensionTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add generic dimension model.AddDimension(transit_idx, 90, 90, True, "distance") @@ -290,12 +296,15 @@ class TestRoutingModel(unittest.TestCase): node = next_node def testDimensionWithVehicleCapacitiesTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add generic dimension model.AddDimensionWithVehicleCapacity(transit_idx, 90, [90], True, "distance") @@ -317,12 +326,15 @@ class TestRoutingModel(unittest.TestCase): node = next_node def testDimensionWithVehicleTransitsTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add generic dimension model.AddDimensionWithVehicleTransits([transit_idx], 90, 90, True, "distance") @@ -344,12 +356,15 @@ class TestRoutingModel(unittest.TestCase): node = next_node def testDimensionWithVehicleTransitsVRP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 3, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add generic dimension distances = [ @@ -381,12 +396,15 @@ class TestRoutingModel(unittest.TestCase): node = next_node def testConstantDimensionTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 3, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add constant dimension constant_id, success = model.AddConstantDimension(1, 100, True, "count") @@ -410,20 +428,23 @@ class TestRoutingModel(unittest.TestCase): self.assertEqual(10, count) def testVectorDimensionTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add vector dimension values = list(range(10)) - unary_transit_idx, success = model.AddVectorDimension( - values, 100, True, "vector" # capacity # fix_start_cumul_to_zero + unary_transit_id, success = model.AddVectorDimension( + values, 100, True, "vector" ) self.assertTrue(success) - self.assertEqual(transit_idx + 1, unary_transit_idx) + self.assertEqual(transit_idx + 1, unary_transit_id) vector_dimension = model.GetDimensionOrDie("vector") # Solve search_parameters = pywrapcp.DefaultRoutingSearchParameters() @@ -444,20 +465,21 @@ class TestRoutingModel(unittest.TestCase): node = assignment.Value(model.NextVar(node)) def testMatrixDimensionTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(5, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - matrix = [[i + j for i in range(5)] for j in range(5)] - transit_idx = model.RegisterTransitMatrix(matrix) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add matrix dimension - matrix_transit_idx, success = model.AddMatrixDimension( - matrix, 100, True, "matrix" # capacity # fix_start_cumul_to_zero + cost = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) ) + model.SetArcCostEvaluatorOfAllVehicles(cost) + # Add matrix dimension + values = [[j for _ in range(5)] for j in range(5)] + transit_id, success = model.AddMatrixDimension(values, 100, True, "matrix") self.assertTrue(success) - self.assertEqual(transit_idx + 1, matrix_transit_idx) + self.assertEqual(cost + 1, transit_id) dimension = model.GetDimensionOrDie("matrix") # Solve search_parameters = pywrapcp.DefaultRoutingSearchParameters() @@ -474,9 +496,8 @@ class TestRoutingModel(unittest.TestCase): cumul = 0 while not model.IsEnd(index): self.assertEqual(cumul, assignment.Value(dimension.CumulVar(index))) - prev_index = index + cumul += values[manager.IndexToNode(index)][manager.IndexToNode(index)] index = assignment.Value(model.NextVar(index)) - cumul += matrix[manager.IndexToNode(prev_index)][manager.IndexToNode(index)] def testMatrixDimensionVRP(self): manager = pywrapcp.RoutingIndexManager(5, 2, 0) @@ -517,12 +538,15 @@ class TestRoutingModel(unittest.TestCase): ] def testDisjunctionTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add disjunctions disjunctions = [ @@ -553,12 +577,15 @@ class TestRoutingModel(unittest.TestCase): self.assertEqual(9, count) def testDisjunctionPenaltyTSP(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Add disjunctions disjunctions = [ @@ -589,6 +616,7 @@ class TestRoutingModel(unittest.TestCase): self.assertEqual(8, count) def testRoutingModelParameters(self): + # Create routing model with parameters parameters = pywrapcp.DefaultRoutingModelParameters() parameters.solver_parameters.CopyFrom(pywrapcp.Solver.DefaultSolverParameters()) parameters.solver_parameters.trace_propagation = True @@ -613,12 +641,15 @@ class TestRoutingModel(unittest.TestCase): self.assertTrue(profile) # Verify it's not empty. def testRoutingSearchParameters(self): + # Create routing model manager = pywrapcp.RoutingIndexManager(10, 1, 0) self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Close with parameters search_parameters = pywrapcp.DefaultRoutingSearchParameters() @@ -653,7 +684,9 @@ class TestRoutingModel(unittest.TestCase): self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) callback = Callback(model) model.AddAtSolutionCallback(callback) @@ -672,7 +705,9 @@ class TestRoutingModel(unittest.TestCase): model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # TODO(user): porting this segfaults the tests. - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) routes = [ [ @@ -683,7 +718,11 @@ class TestRoutingModel(unittest.TestCase): manager.NodeToIndex(2), manager.NodeToIndex(6), ], - [manager.NodeToIndex(7), manager.NodeToIndex(9), manager.NodeToIndex(8)], + [ + manager.NodeToIndex(7), + manager.NodeToIndex(9), + manager.NodeToIndex(8), + ], ] assignment = model.ReadAssignmentFromRoutes(routes, False) search_parameters = pywrapcp.DefaultRoutingSearchParameters() @@ -707,7 +746,9 @@ class TestRoutingModel(unittest.TestCase): model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) # Solve search_parameters = pywrapcp.DefaultRoutingSearchParameters() @@ -723,13 +764,14 @@ class TestRoutingModel(unittest.TestCase): model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) model.SetArcCostEvaluatorOfAllVehicles(transit_idx) self.assertTrue(model.AddDimension(transit_idx, 0, 1000, True, "distance")) dst_dimension = model.GetDimensionOrDie("distance") # Add few Pickup and Delivery for request in [[2 * i, 2 * i + 1] for i in range(1, 15)]: - print(request) pickup_index = manager.NodeToIndex(request[0]) delivery_index = manager.NodeToIndex(request[1]) model.AddPickupAndDelivery(pickup_index, delivery_index) @@ -768,7 +810,9 @@ class TestRoutingDimension(unittest.TestCase): self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) self.assertTrue(model.AddDimension(transit_idx, 90, 90, True, "distance")) model.GetDimensionOrDie("distance") @@ -777,7 +821,9 @@ class TestRoutingDimension(unittest.TestCase): self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) self.assertTrue(model.AddDimension(transit_idx, 100, 100, True, "distance")) dimension = model.GetDimensionOrDie("distance") @@ -797,7 +843,9 @@ class TestRoutingDimension(unittest.TestCase): self.assertIsNotNone(manager) model = pywrapcp.RoutingModel(manager) self.assertIsNotNone(model) - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) + transit_idx = model.RegisterTransitCallback( + functools.partial(TransitDistance, manager) + ) self.assertTrue(model.AddDimension(transit_idx, 100, 100, True, "distance")) dimension = model.GetDimensionOrDie("distance") @@ -814,4 +862,4 @@ class TestRoutingDimension(unittest.TestCase): if __name__ == "__main__": - unittest.main(verbosity=2) + unittest.main() diff --git a/ortools/constraint_solver/python/routing_api_test.py b/ortools/constraint_solver/python/routing_api_test.py deleted file mode 100755 index ace0b149fd..0000000000 --- a/ortools/constraint_solver/python/routing_api_test.py +++ /dev/null @@ -1,706 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2010-2022 Google LLC -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -'''Test routing API''' - -from functools import partial - -import unittest - -from ortools.constraint_solver import routing_enums_pb2 -from ortools.constraint_solver import pywrapcp - - -def Distance(node_i, node_j): - return node_i + node_j - - -def TransitDistance(manager, i, j): - return Distance(manager.IndexToNode(i), manager.IndexToNode(j)) - - -def UnaryTransitDistance(manager, i): - return Distance(manager.IndexToNode(i), 0) - - -def One(unused_i, unused_j): - return 1 - - -def Two(unused_i, unused_j): - return 1 - - -def Three(unused_i, unused_j): - return 1 - - -class Callback(object): - - def __init__(self, model): - self.model = model - self.costs = [] - - def __call__(self): - self.costs.append(self.model.CostVar().Max()) - - -class TestRoutingIndexManager(unittest.TestCase): - - def testCtor(self): - manager = pywrapcp.RoutingIndexManager(42, 3, 7) - self.assertIsNotNone(manager) - if __debug__: - print(f'class RoutingIndexManager: {dir(manager)}') - self.assertEqual(42, manager.GetNumberOfNodes()) - self.assertEqual(3, manager.GetNumberOfVehicles()) - self.assertEqual(42 + 3 * 2 - 1, manager.GetNumberOfIndices()) - for i in range(manager.GetNumberOfVehicles()): - self.assertEqual(7, manager.IndexToNode(manager.GetStartIndex(i))) - self.assertEqual(7, manager.IndexToNode(manager.GetEndIndex(i))) - - def testCtorMultiDepotSame(self): - manager = pywrapcp.RoutingIndexManager(42, 3, [0, 0, 0], [0, 0, 0]) - self.assertIsNotNone(manager) - self.assertEqual(42, manager.GetNumberOfNodes()) - self.assertEqual(3, manager.GetNumberOfVehicles()) - self.assertEqual(42 + 3 * 2 - 1, manager.GetNumberOfIndices()) - for i in range(manager.GetNumberOfVehicles()): - self.assertEqual(0, manager.IndexToNode(manager.GetStartIndex(i))) - self.assertEqual(0, manager.IndexToNode(manager.GetEndIndex(i))) - - def testCtorMultiDepotAllDiff(self): - manager = pywrapcp.RoutingIndexManager(42, 3, [1, 2, 3], [4, 5, 6]) - self.assertIsNotNone(manager) - self.assertEqual(42, manager.GetNumberOfNodes()) - self.assertEqual(3, manager.GetNumberOfVehicles()) - self.assertEqual(42, manager.GetNumberOfIndices()) - for i in range(manager.GetNumberOfVehicles()): - self.assertEqual(i + 1, - manager.IndexToNode(manager.GetStartIndex(i))) - self.assertEqual(i + 4, - manager.IndexToNode(manager.GetEndIndex(i))) - - -class TestRoutingModel(unittest.TestCase): - - def testCtor(self): - manager = pywrapcp.RoutingIndexManager(42, 3, 7) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - for i in range(manager.GetNumberOfVehicles()): - self.assertEqual(7, manager.IndexToNode(model.Start(i))) - self.assertEqual(7, manager.IndexToNode(model.End(i))) - if __debug__: - print(f'class RoutingModel: {dir(model)}') - - def testSolve(self): - manager = pywrapcp.RoutingIndexManager(42, 3, 7) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.Solve() - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertIsNotNone(assignment) - self.assertEqual(0, assignment.ObjectiveValue()) - - def testSolveMultiDepot(self): - manager = pywrapcp.RoutingIndexManager(42, 3, [1, 2, 3], [4, 5, 6]) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.Solve() - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertIsNotNone(assignment) - self.assertEqual(0, assignment.ObjectiveValue()) - - def testTransitCallback(self): - manager = pywrapcp.RoutingIndexManager(5, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - self.assertEqual(1, transit_idx) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - self.assertEqual(pywrapcp.RoutingModel.ROUTING_NOT_SOLVED, - model.status()) - assignment = model.Solve() - self.assertTrue(assignment) - self.assertEqual(pywrapcp.RoutingModel.ROUTING_SUCCESS, model.status()) - self.assertEqual(20, assignment.ObjectiveValue()) - - def testTransitLambda(self): - manager = pywrapcp.RoutingIndexManager(5, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - transit_id = model.RegisterTransitCallback( - lambda from_index, to_index: 1) - self.assertEqual(1, transit_id) - model.SetArcCostEvaluatorOfAllVehicles(transit_id) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.Solve() - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertIsNotNone(assignment) - self.assertEqual(5, assignment.ObjectiveValue()) - - def testTransitMatrix(self): - manager = pywrapcp.RoutingIndexManager(5, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - matrix = [[i + 1 for i in range(5)] for j in range(5)] - transit_idx = model.RegisterTransitMatrix(matrix) - self.assertEqual(1, transit_idx) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.Solve() - self.assertIsNotNone(assignment) - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertEqual(15, assignment.ObjectiveValue()) - - def testUnaryTransitCallback(self): - manager = pywrapcp.RoutingIndexManager(5, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - transit_idx = model.RegisterUnaryTransitCallback( - partial(UnaryTransitDistance, manager)) - self.assertEqual(1, transit_idx) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.Solve() - self.assertTrue(assignment) - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertEqual(10, assignment.ObjectiveValue()) - - def testUnaryTransitLambda(self): - manager = pywrapcp.RoutingIndexManager(5, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - transit_id = model.RegisterUnaryTransitCallback(lambda from_index: 1) - self.assertEqual(1, transit_id) - model.SetArcCostEvaluatorOfAllVehicles(transit_id) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.Solve() - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertIsNotNone(assignment) - self.assertEqual(5, assignment.ObjectiveValue()) - - def testUnaryTransitVector(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - vector = [i for i in range(10)] - transit_idx = model.RegisterUnaryTransitVector(vector) - self.assertEqual(1, transit_idx) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.Solve() - self.assertTrue(assignment) - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertEqual(45, assignment.ObjectiveValue()) - - def testTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - self.assertEqual(pywrapcp.RoutingModel.ROUTING_NOT_SOLVED, - model.status()) - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(pywrapcp.RoutingModel.ROUTING_SUCCESS, model.status()) - self.assertEqual(90, assignment.ObjectiveValue()) - # Inspect solution - index = model.Start(0) - visited_nodes = [] - expected_visited_nodes = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0] - while not model.IsEnd(index): - index = assignment.Value(model.NextVar(index)) - visited_nodes.append(manager.IndexToNode(index)) - self.assertEqual(expected_visited_nodes, visited_nodes) - - def testVRP(self): - manager = pywrapcp.RoutingIndexManager(10, 2, [0, 1], [1, 0]) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(89, assignment.ObjectiveValue()) - # Inspect solution - index = model.Start(1) - visited_nodes = [] - expected_visited_nodes = [2, 4, 6, 8, 3, 5, 7, 9, 0] - while not model.IsEnd(index): - index = assignment.Value(model.NextVar(index)) - visited_nodes.append(manager.IndexToNode(index)) - self.assertEqual(expected_visited_nodes, visited_nodes) - self.assertTrue( - model.IsEnd(assignment.Value(model.NextVar(model.Start(0))))) - - def testDimensionTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add generic dimension - model.AddDimension(transit_idx, 90, 90, True, 'distance') - distance_dimension = model.GetDimensionOrDie('distance') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(90, assignment.ObjectiveValue()) - # Inspect solution - node = model.Start(0) - cumul = 0 - while not model.IsEnd(node): - self.assertEqual( - cumul, assignment.Value(distance_dimension.CumulVar(node))) - next_node = assignment.Value(model.NextVar(node)) - cumul += Distance(node, next_node) - node = next_node - - def testDimensionWithVehicleCapacitiesTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add generic dimension - model.AddDimensionWithVehicleCapacity(transit_idx, 90, [90], True, 'distance') - distance_dimension = model.GetDimensionOrDie('distance') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(90, assignment.ObjectiveValue()) - # Inspect solution - node = model.Start(0) - cumul = 0 - while not model.IsEnd(node): - self.assertEqual( - cumul, assignment.Value(distance_dimension.CumulVar(node))) - next_node = assignment.Value(model.NextVar(node)) - cumul += Distance(node, next_node) - node = next_node - - def testDimensionWithVehicleTransitsTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add generic dimension - model.AddDimensionWithVehicleTransits([transit_idx], 90, 90, True, 'distance') - distance_dimension = model.GetDimensionOrDie('distance') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(90, assignment.ObjectiveValue()) - # Inspect solution - node = model.Start(0) - cumul = 0 - while not model.IsEnd(node): - self.assertEqual( - cumul, assignment.Value(distance_dimension.CumulVar(node))) - next_node = assignment.Value(model.NextVar(node)) - cumul += Distance(node, next_node) - node = next_node - - def testDimensionWithVehicleTransitsVRP(self): - manager = pywrapcp.RoutingIndexManager(10, 3, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add generic dimension - distances = [ - model.RegisterTransitCallback(One), - model.RegisterTransitCallback(Two), - model.RegisterTransitCallback(Three) - ] - model.AddDimensionWithVehicleTransits(distances, 90, 90, True, - 'distance') - distance_dimension = model.GetDimensionOrDie('distance') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(90, assignment.ObjectiveValue()) - # Inspect solution - for vehicle in range(0, model.vehicles()): - node = model.Start(vehicle) - cumul = 0 - while not model.IsEnd(node): - self.assertEqual( - cumul, assignment.Min(distance_dimension.CumulVar(node))) - next_node = assignment.Value(model.NextVar(node)) - # Increment cumul by the vehicle distance which is equal to the vehicle - # index + 1, cf. distances. - cumul += vehicle + 1 - node = next_node - - def testConstantDimensionTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 3, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add constant dimension - constant_id, success = model.AddConstantDimension( - 1, 100, True, 'count') - self.assertTrue(success) - self.assertEqual(transit_idx + 1, constant_id) - count_dimension = model.GetDimensionOrDie('count') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(90, assignment.ObjectiveValue()) - # Inspect solution - node = model.Start(0) - count = 0 - while not model.IsEnd(node): - self.assertEqual(count, - assignment.Value(count_dimension.CumulVar(node))) - count += 1 - node = assignment.Value(model.NextVar(node)) - self.assertEqual(10, count) - - def testVectorDimensionTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add vector dimension - values = [i for i in range(10)] - unary_transit_idx, success = model.AddVectorDimension( - values, - 100, # capacity - True, # fix_start_cumul_to_zero - 'vector') - self.assertTrue(success) - self.assertEqual(transit_idx + 1, unary_transit_idx) - vector_dimension = model.GetDimensionOrDie('vector') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.SolveWithParameters(search_parameters) - self.assertIsNotNone(assignment) - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertEqual(90, assignment.ObjectiveValue()) - # Inspect solution - node = model.Start(0) - cumul = 0 - while not model.IsEnd(node): - self.assertEqual(cumul, - assignment.Value(vector_dimension.CumulVar(node))) - cumul += values[node] - node = assignment.Value(model.NextVar(node)) - - def testMatrixDimensionTSP(self): - manager = pywrapcp.RoutingIndexManager(5, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - matrix = [[i+j for i in range(5)] for j in range(5)] - transit_idx = model.RegisterTransitMatrix(matrix) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add matrix dimension - matrix_transit_idx, success = model.AddMatrixDimension( - matrix, - 100, # capacity - True, # fix_start_cumul_to_zero - 'matrix') - self.assertTrue(success) - self.assertEqual(transit_idx + 1, matrix_transit_idx) - dimension = model.GetDimensionOrDie('matrix') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.SolveWithParameters(search_parameters) - self.assertIsNotNone(assignment) - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertEqual(20, assignment.ObjectiveValue()) - # Inspect solution - index = model.Start(0) - cumul = 0 - while not model.IsEnd(index): - self.assertEqual(cumul, assignment.Value(dimension.CumulVar(index))) - prev_index = index - index = assignment.Value(model.NextVar(index)) - cumul += matrix[manager.IndexToNode(prev_index)][manager.IndexToNode(index)] - - def testMatrixDimensionVRP(self): - manager = pywrapcp.RoutingIndexManager(5, 2, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - matrix = [[i+j for i in range(5)] for j in range(5)] - transit_idx = model.RegisterTransitMatrix(matrix) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add matrix dimension - matrix_transit_idx, success = model.AddMatrixDimension( - matrix, - 10, # capacity - True, # fix_start_cumul_to_zero - 'matrix') - self.assertTrue(success) - self.assertEqual(transit_idx + 1, matrix_transit_idx) - dimension = model.GetDimensionOrDie('matrix') - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - self.assertEqual(model.ROUTING_NOT_SOLVED, model.status()) - assignment = model.SolveWithParameters(search_parameters) - self.assertIsNotNone(assignment) - self.assertEqual(model.ROUTING_SUCCESS, model.status()) - self.assertEqual(20, assignment.ObjectiveValue()) - # Inspect solution - for v in range(manager.GetNumberOfVehicles()): - index = model.Start(v) - cumul = 0 - while not model.IsEnd(index): - self.assertEqual(cumul, assignment.Value(dimension.CumulVar(index))) - prev_index = index - index = assignment.Value(model.NextVar(index)) - cumul += matrix[manager.IndexToNode(prev_index)][manager.IndexToNode(index)] - - def testDisjunctionTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add disjunctions - disjunctions = [[manager.NodeToIndex(1), - manager.NodeToIndex(2)], [manager.NodeToIndex(3)], - [manager.NodeToIndex(4)], [manager.NodeToIndex(5)], - [manager.NodeToIndex(6)], [manager.NodeToIndex(7)], - [manager.NodeToIndex(8)], [manager.NodeToIndex(9)]] - for disjunction in disjunctions: - model.AddDisjunction(disjunction) - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(86, assignment.ObjectiveValue()) - # Inspect solution - node = model.Start(0) - count = 0 - while not model.IsEnd(node): - count += 1 - node = assignment.Value(model.NextVar(node)) - self.assertEqual(9, count) - - def testDisjunctionPenaltyTSP(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Add disjunctions - disjunctions = [([manager.NodeToIndex(1), - manager.NodeToIndex(2)], 1000), - ([manager.NodeToIndex(3)], 1000), - ([manager.NodeToIndex(4)], 1000), - ([manager.NodeToIndex(5)], 1000), - ([manager.NodeToIndex(6)], 1000), - ([manager.NodeToIndex(7)], 1000), - ([manager.NodeToIndex(8)], 1000), - ([manager.NodeToIndex(9)], 0)] - for disjunction, penalty in disjunctions: - model.AddDisjunction(disjunction, penalty) - # Solve - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.FIRST_UNBOUND_MIN_VALUE) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(68, assignment.ObjectiveValue()) - # Inspect solution - node = model.Start(0) - count = 0 - while not model.IsEnd(node): - count += 1 - node = assignment.Value(model.NextVar(node)) - self.assertEqual(8, count) - - def testRoutingModelParameters(self): - parameters = pywrapcp.DefaultRoutingModelParameters() - parameters.solver_parameters.CopyFrom( - pywrapcp.Solver.DefaultSolverParameters()) - parameters.solver_parameters.trace_propagation = True - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager, parameters) - self.assertIsNotNone(model) - self.assertEqual(1, model.vehicles()) - self.assertTrue(model.solver().Parameters().trace_propagation) - - def testRoutingLocalSearchFiltering(self): - parameters = pywrapcp.DefaultRoutingModelParameters() - parameters.solver_parameters.profile_local_search = True - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - model = pywrapcp.RoutingModel(manager, parameters) - model.Solve() - profile = model.solver().LocalSearchProfile() - print(profile) - self.assertIsInstance(profile, str) - self.assertTrue(profile) # Verify it's not empty. - - def testRoutingSearchParameters(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # Add cost function - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - # Close with parameters - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.SAVINGS) - search_parameters.local_search_metaheuristic = ( - routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) - search_parameters.local_search_operators.use_two_opt = ( - pywrapcp.BOOL_FALSE) - search_parameters.solution_limit = 20 - model.CloseModelWithParameters(search_parameters) - # Solve with parameters - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual( - 11, model.GetNumberOfDecisionsInFirstSolution(search_parameters)) - self.assertEqual( - 0, model.GetNumberOfRejectsInFirstSolution(search_parameters)) - self.assertEqual(90, assignment.ObjectiveValue()) - assignment = model.SolveFromAssignmentWithParameters( - assignment, search_parameters) - self.assertEqual(90, assignment.ObjectiveValue()) - - def testFindErrorInRoutingSearchParameters(self): - params = pywrapcp.DefaultRoutingSearchParameters() - params.local_search_operators.use_cross = pywrapcp.BOOL_UNSPECIFIED - self.assertIn('cross', - pywrapcp.FindErrorInRoutingSearchParameters(params)) - - def testCallback(self): - manager = pywrapcp.RoutingIndexManager(10, 1, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - callback = Callback(model) - model.AddAtSolutionCallback(callback) - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) - assignment = model.SolveWithParameters(search_parameters) - self.assertEqual(90, assignment.ObjectiveValue()) - self.assertEqual(len(callback.costs), 1) - self.assertEqual(90, callback.costs[0]) - - def testReadAssignment(self): - manager = pywrapcp.RoutingIndexManager(10, 2, 0) - self.assertIsNotNone(manager) - model = pywrapcp.RoutingModel(manager) - self.assertIsNotNone(model) - # TODO(user): porting this segfaults the tests. - transit_idx = model.RegisterTransitCallback(partial(TransitDistance, manager)) - model.SetArcCostEvaluatorOfAllVehicles(transit_idx) - routes = [ - [ - manager.NodeToIndex(1), - manager.NodeToIndex(3), - manager.NodeToIndex(5), - manager.NodeToIndex(4), - manager.NodeToIndex(2), - manager.NodeToIndex(6) - ], - [ - manager.NodeToIndex(7), - manager.NodeToIndex(9), - manager.NodeToIndex(8) - ], - ] - assignment = model.ReadAssignmentFromRoutes(routes, False) - search_parameters = pywrapcp.DefaultRoutingSearchParameters() - search_parameters.solution_limit = 1 - solution = model.SolveFromAssignmentWithParameters( - assignment, search_parameters) - self.assertEqual(90, solution.ObjectiveValue()) - for vehicle in range(0, model.vehicles()): - node = model.Start(vehicle) - count = 0 - while not model.IsEnd(node): - node = solution.Value(model.NextVar(node)) - if not model.IsEnd(node): - self.assertEqual(routes[vehicle][count], - manager.IndexToNode(node)) - count += 1 - - -if __name__ == '__main__': - unittest.main(verbosity=2) diff --git a/ortools/constraint_solver/resource.cc b/ortools/constraint_solver/resource.cc index 97b68f5e26..23e22ed949 100644 --- a/ortools/constraint_solver/resource.cc +++ b/ortools/constraint_solver/resource.cc @@ -35,7 +35,6 @@ #include "absl/strings/str_join.h" #include "ortools/base/commandlineflags.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/mathutil.h" #include "ortools/base/stl_util.h" #include "ortools/base/types.h" @@ -961,6 +960,11 @@ class FullDisjunctiveConstraint : public DisjunctiveConstraint { mirror_not_last_(s, intervals, true, strict), strict_(strict) {} + // This type is neither copyable nor movable. + FullDisjunctiveConstraint(const FullDisjunctiveConstraint&) = delete; + FullDisjunctiveConstraint& operator=(const FullDisjunctiveConstraint&) = + delete; + ~FullDisjunctiveConstraint() override {} void Post() override { @@ -1184,7 +1188,6 @@ class FullDisjunctiveConstraint : public DisjunctiveConstraint { std::vector performed_; std::vector optional_; const bool strict_; - DISALLOW_COPY_AND_ASSIGN(FullDisjunctiveConstraint); }; // ===================================================================== @@ -1259,6 +1262,10 @@ class DualCapacityThetaTree capacity_max_(-1), residual_capacity_(-1) {} + // This type is neither copyable nor movable. + DualCapacityThetaTree(const DualCapacityThetaTree&) = delete; + DualCapacityThetaTree& operator=(const DualCapacityThetaTree&) = delete; + virtual ~DualCapacityThetaTree() {} void Init(int64_t capacity_max, int64_t residual_capacity) { @@ -1282,7 +1289,6 @@ class DualCapacityThetaTree private: int64_t capacity_max_; int64_t residual_capacity_; - DISALLOW_COPY_AND_ASSIGN(DualCapacityThetaTree); }; const int64_t DualCapacityThetaTree::kNotInitialized = -1LL; @@ -1379,6 +1385,10 @@ class UpdatesForADemand { explicit UpdatesForADemand(int size) : updates_(size, 0), up_to_date_(false) {} + // This type is neither copyable nor movable. + UpdatesForADemand(const UpdatesForADemand&) = delete; + UpdatesForADemand& operator=(const UpdatesForADemand&) = delete; + int64_t Update(int index) { return updates_[index]; } void Reset() { up_to_date_ = false; } void SetUpdate(int index, int64_t update) { @@ -1392,7 +1402,6 @@ class UpdatesForADemand { private: std::vector updates_; bool up_to_date_; - DISALLOW_COPY_AND_ASSIGN(UpdatesForADemand); }; // One-sided cumulative edge finder. @@ -1411,6 +1420,10 @@ class EdgeFinder : public Constraint { dual_capacity_tree_(tasks.size()), has_zero_demand_tasks_(true) {} + // This type is neither copyable nor movable. + EdgeFinder(const EdgeFinder&) = delete; + EdgeFinder& operator=(const EdgeFinder&) = delete; + ~EdgeFinder() override { gtl::STLDeleteElements(&tasks_); gtl::STLDeleteValues(&update_map_); @@ -1664,8 +1677,6 @@ class EdgeFinder : public Constraint { // Has one task a demand min == 0 Rev has_zero_demand_tasks_; - - DISALLOW_COPY_AND_ASSIGN(EdgeFinder); }; // A point in time where the usage profile changes. @@ -1724,6 +1735,10 @@ class CumulativeTimeTable : public Constraint { profile_unique_time_.reserve(profile_max_size); } + // This type is neither copyable nor movable. + CumulativeTimeTable(const CumulativeTimeTable&) = delete; + CumulativeTimeTable& operator=(const CumulativeTimeTable&) = delete; + ~CumulativeTimeTable() override { gtl::STLDeleteElements(&by_start_min_); } void InitialPropagate() override { @@ -1891,8 +1906,6 @@ class CumulativeTimeTable : public Constraint { Profile profile_non_unique_time_; std::vector by_start_min_; IntVar* const capacity_; - - DISALLOW_COPY_AND_ASSIGN(CumulativeTimeTable); }; // Cumulative idempotent Time-Table. @@ -2190,6 +2203,10 @@ class CumulativeConstraint : public Constraint { } } + // This type is neither copyable nor movable. + CumulativeConstraint(const CumulativeConstraint&) = delete; + CumulativeConstraint& operator=(const CumulativeConstraint&) = delete; + void Post() override { // For the cumulative constraint, there are many propagators, and they // don't dominate each other. So the strongest propagation is obtained @@ -2363,8 +2380,6 @@ class CumulativeConstraint : public Constraint { const std::vector intervals_; // Array of demands for the visitor. const std::vector demands_; - - DISALLOW_COPY_AND_ASSIGN(CumulativeConstraint); }; class VariableDemandCumulativeConstraint : public Constraint { @@ -2384,6 +2399,12 @@ class VariableDemandCumulativeConstraint : public Constraint { } } + // This type is neither copyable nor movable. + VariableDemandCumulativeConstraint( + const VariableDemandCumulativeConstraint&) = delete; + VariableDemandCumulativeConstraint& operator=( + const VariableDemandCumulativeConstraint&) = delete; + void Post() override { // For the cumulative constraint, there are many propagators, and they // don't dominate each other. So the strongest propagation is obtained @@ -2551,8 +2572,6 @@ class VariableDemandCumulativeConstraint : public Constraint { const std::vector intervals_; // Array of demands for the visitor. const std::vector demands_; - - DISALLOW_COPY_AND_ASSIGN(VariableDemandCumulativeConstraint); }; } // namespace diff --git a/ortools/constraint_solver/routing.cc b/ortools/constraint_solver/routing.cc index a5266b8ea4..11330c9359 100644 --- a/ortools/constraint_solver/routing.cc +++ b/ortools/constraint_solver/routing.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -36,6 +37,7 @@ #include "absl/flags/flag.h" #include "absl/functional/bind_front.h" #include "absl/log/check.h" +#include "absl/log/die_if_null.h" #include "absl/status/statusor.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" @@ -405,8 +407,9 @@ RoutingModel::RoutingModel(const RoutingIndexManager& index_manager, num_visit_types_(0), paths_metadata_(index_manager), manager_(index_manager), - finalizer_variables_( - std::make_unique(solver_.get())) { + finalizer_variables_(std::make_unique(solver_.get())), + interrupt_cp_sat_(false), + interrupt_cp_(false) { // Initialize vehicle costs to the zero evaluator. vehicle_to_transit_cost_.assign( vehicles_, RegisterTransitCallback( @@ -2415,6 +2418,7 @@ void RoutingModel::CloseModelWithParameters( void RoutingModel::AddSearchMonitor(SearchMonitor* const monitor) { monitors_.push_back(monitor); + secondary_ls_monitors_.push_back(monitor); } namespace { @@ -2492,10 +2496,8 @@ void MakeAllUnperformedInAssignment(const RoutingModel* model, } } // namespace -bool RoutingModel::AppendAssignmentIfFeasible( - const Assignment& assignment, - std::vector>* assignments, - bool call_at_solution_monitors) { +bool RoutingModel::CheckIfAssignmentIsFeasible(const Assignment& assignment, + bool call_at_solution_monitors) { tmp_assignment_->CopyIntersection(&assignment); std::vector monitors = call_at_solution_monitors ? at_solution_monitors_ @@ -2503,7 +2505,14 @@ bool RoutingModel::AppendAssignmentIfFeasible( monitors.push_back(collect_one_assignment_); monitors.push_back(GetOrCreateLimit()); solver_->Solve(restore_tmp_assignment_, monitors); - if (collect_one_assignment_->solution_count() == 1) { + return collect_one_assignment_->solution_count() == 1; +} + +bool RoutingModel::AppendAssignmentIfFeasible( + const Assignment& assignment, + std::vector>* assignments, + bool call_at_solution_monitors) { + if (CheckIfAssignmentIsFeasible(assignment, call_at_solution_monitors)) { assignments->push_back(std::make_unique(solver_.get())); assignments->back()->Copy(collect_one_assignment_->solution(0)); return true; @@ -2535,6 +2544,13 @@ const Assignment* RoutingModel::SolveFromAssignmentWithParameters( solutions); } +namespace { +bool PerformSecondaryLS(const RoutingSearchParameters& parameters) { + return GetTimeLimit(parameters) != absl::InfiniteDuration() && + parameters.secondary_ls_time_limit_ratio() > 0; +} +} // namespace + const Assignment* RoutingModel::SolveFromAssignmentsWithParameters( const std::vector& assignments, const RoutingSearchParameters& parameters, @@ -2553,22 +2569,30 @@ const Assignment* RoutingModel::SolveFromAssignmentsWithParameters( return nullptr; } - const auto update_time_limits = [this, start_time_ms, ¶meters]() { + const auto update_time_limits = [this, start_time_ms, + ¶meters](bool secondary_ls = false) { + if (secondary_ls && !PerformSecondaryLS(parameters)) return false; const absl::Duration elapsed_time = absl::Milliseconds(solver_->wall_time() - start_time_ms); const absl::Duration time_left = GetTimeLimit(parameters) - elapsed_time; - if (time_left >= absl::ZeroDuration()) { - limit_->UpdateLimits(time_left, std::numeric_limits::max(), - std::numeric_limits::max(), - parameters.solution_limit()); - ls_limit_->UpdateLimits(time_left, std::numeric_limits::max(), - std::numeric_limits::max(), 1); - // TODO(user): Come up with a better formula. Ideally this should be - // calibrated in the first solution strategies. - time_buffer_ = std::min(absl::Seconds(1), time_left * 0.05); - return true; - } - return false; + + if (time_left < absl::ZeroDuration()) return false; + + const absl::Duration secondary_solve_buffer = + secondary_ls || !PerformSecondaryLS(parameters) + ? absl::ZeroDuration() + : parameters.secondary_ls_time_limit_ratio() * time_left; + const absl::Duration time_limit = time_left - secondary_solve_buffer; + limit_->UpdateLimits(time_limit, std::numeric_limits::max(), + std::numeric_limits::max(), + parameters.solution_limit()); + DCHECK_NE(ls_limit_, nullptr); + ls_limit_->UpdateLimits(time_limit, std::numeric_limits::max(), + std::numeric_limits::max(), 1); + // TODO(user): Come up with a better formula. Ideally this should be + // calibrated in the first solution strategies. + time_buffer_ = std::min(absl::Seconds(1), time_limit * 0.05); + return true; }; if (!update_time_limits()) { status_ = ROUTING_FAIL_TIMEOUT; @@ -2599,6 +2623,14 @@ const Assignment* RoutingModel::SolveFromAssignmentsWithParameters( local_optimum_reached_ = false; objective_lower_bound_ = kint64min; if (parameters.use_cp() == BOOL_TRUE) { + const auto run_secondary_ls = [this, &update_time_limits]() { + if (collect_assignments_->has_solution() && + update_time_limits(/*secondary_ls=*/true)) { + assignment_->CopyIntersection( + collect_assignments_->last_solution_or_null()); + solver_->Solve(secondary_ls_db_, secondary_ls_monitors_); + } + }; if (first_solution_assignments.empty()) { bool solution_found = false; Assignment matching(solver_.get()); @@ -2624,12 +2656,14 @@ const Assignment* RoutingModel::SolveFromAssignmentsWithParameters( local_optimum_reached_ = false; if (update_time_limits()) { solver_->Solve(solve_db_, monitors_); + run_secondary_ls(); } } } else { for (const Assignment* assignment : first_solution_assignments) { assignment_->CopyIntersection(assignment); solver_->Solve(improve_db_, monitors_); + run_secondary_ls(); if (collect_assignments_->solution_count() >= 1 || !update_time_limits()) { break; @@ -2638,56 +2672,87 @@ const Assignment* RoutingModel::SolveFromAssignmentsWithParameters( } } + const SolutionCollector* const solution_collector = + collect_secondary_ls_assignments_->has_solution() + ? collect_secondary_ls_assignments_ + : collect_assignments_; + if (parameters.use_cp_sat() == BOOL_TRUE || parameters.use_generalized_cp_sat() == BOOL_TRUE || (parameters.fallback_to_cp_sat_size_threshold() >= Size() && - collect_assignments_->solution_count() == 0 && solution_pool.empty())) { + !solution_collector->has_solution() && solution_pool.empty())) { VLOG(1) << "Solving with CP-SAT"; - const int solution_count = collect_assignments_->solution_count(); - Assignment* const cp_solution = - solution_count >= 1 ? collect_assignments_->solution(solution_count - 1) - : nullptr; + Assignment* const cp_solution = solution_collector->last_solution_or_null(); Assignment sat_solution(solver_.get()); - if (SolveModelWithSat(*this, parameters, cp_solution, &sat_solution) && - AppendAssignmentIfFeasible(sat_solution, &solution_pool) && - parameters.log_search()) { - LogSolution(parameters, "SAT", solution_pool.back()->ObjectiveValue(), - start_time_ms); + if (SolveModelWithSat(this, parameters, cp_solution, &sat_solution) && + AppendAssignmentIfFeasible(sat_solution, &solution_pool)) { + if (parameters.log_search()) { + LogSolution(parameters, "SAT", solution_pool.back()->ObjectiveValue(), + start_time_ms); + } local_optimum_reached_ = true; + if (sat_solution.HasObjective()) { + objective_lower_bound_ = + std::max(objective_lower_bound_, sat_solution.ObjectiveValue()); + } } } VLOG(1) << "Objective lower bound: " << objective_lower_bound_; const absl::Duration elapsed_time = absl::Milliseconds(solver_->wall_time() - start_time_ms); - const int solution_count = collect_assignments_->solution_count(); - if (solution_count >= 1 || !solution_pool.empty()) { + + if (solution_collector->has_solution() || !solution_pool.empty()) { status_ = local_optimum_reached_ ? ROUTING_SUCCESS : ROUTING_PARTIAL_SUCCESS_LOCAL_OPTIMUM_NOT_REACHED; if (solutions != nullptr) { - int64_t min_objective_value = kint64max; - for (int i = 0; i < solution_count; ++i) { - solutions->push_back( - solver_->MakeAssignment(collect_assignments_->solution(i))); - min_objective_value = - std::min(min_objective_value, solutions->back()->ObjectiveValue()); + std::vector temp_solutions; + for (int i = 0; i < solution_collector->solution_count(); ++i) { + temp_solutions.push_back( + solver_->MakeAssignment(solution_collector->solution(i))); } for (const auto& solution : solution_pool) { - if (solutions->empty() || - solution->ObjectiveValue() < solutions->back()->ObjectiveValue()) { - solutions->push_back(solver_->MakeAssignment(solution.get())); + if (temp_solutions.empty() || + solution->ObjectiveValue() < + temp_solutions.back()->ObjectiveValue()) { + temp_solutions.push_back(solver_->MakeAssignment(solution.get())); } - min_objective_value = - std::min(min_objective_value, solutions->back()->ObjectiveValue()); } + // By construction, the last assignment in 'temp_solutions' necessarily + // has the best objective value. + DCHECK(!temp_solutions.empty()); + const int64_t min_objective_value = + temp_solutions.back()->ObjectiveValue(); + + if (temp_solutions.size() < parameters.number_of_solutions_to_collect() && + solution_collector != collect_assignments_ && + collect_assignments_->has_solution()) { + // Since the secondary LS is run starting from the primary LS's last + // assignment, and that it will be the first solution collected in the + // secondary search, we already have it in the results. + DCHECK_EQ(*collect_assignments_->last_solution_or_null(), + *temp_solutions[0]); + // Add the remaining solutions from the original assignment collector. + const size_t num_solutions = collect_assignments_->solution_count(); + const int num_solutions_to_add = std::min( + parameters.number_of_solutions_to_collect() - solutions->size(), + num_solutions - 1); + for (int i = num_solutions_to_add; i > 0; --i) { + solutions->push_back(solver_->MakeAssignment( + collect_assignments_->solution(num_solutions - 1 - i))); + DCHECK_GE(solutions->back()->ObjectiveValue(), min_objective_value); + } + } + // Keep 'solutions' sorted from worst to best solution by appending + // temp_solutions in the end. + solutions->insert(solutions->end(), temp_solutions.begin(), + temp_solutions.end()); if (min_objective_value <= objective_lower_bound_) { - status_ = ROUTING_SUCCESS; + status_ = ROUTING_OPTIMAL; } return solutions->back(); } - Assignment* best_assignment = - solution_count >= 1 ? collect_assignments_->solution(solution_count - 1) - : nullptr; + Assignment* best_assignment = solution_collector->last_solution_or_null(); for (const auto& solution : solution_pool) { if (best_assignment == nullptr || solution->ObjectiveValue() < best_assignment->ObjectiveValue()) { @@ -2695,7 +2760,7 @@ const Assignment* RoutingModel::SolveFromAssignmentsWithParameters( } } if (best_assignment->ObjectiveValue() <= objective_lower_bound_) { - status_ = ROUTING_SUCCESS; + status_ = ROUTING_OPTIMAL; } return solver_->MakeAssignment(best_assignment); } else { @@ -4040,9 +4105,10 @@ void RoutingModel::CreateNeighborhoodOperators( arc_cost_for_path_start)); } -#define CP_ROUTING_PUSH_OPERATOR(operator_type, operator_method, operators) \ - if (search_parameters.local_search_operators().use_##operator_method() == \ - BOOL_TRUE) { \ +#define CP_ROUTING_PUSH_OPERATOR(operator_type, operator_method) \ + if (operators_to_consider.contains(operator_type) && \ + search_parameters.local_search_operators().use_##operator_method() == \ + BOOL_TRUE) { \ operators.push_back(local_search_operators_[operator_type]); \ } @@ -4062,22 +4128,23 @@ LocalSearchOperator* RoutingModel::ConcatenateOperators( } LocalSearchOperator* RoutingModel::GetNeighborhoodOperators( - const RoutingSearchParameters& search_parameters) const { + const RoutingSearchParameters& search_parameters, + const absl::flat_hash_set& + operators_to_consider) const { std::vector operator_groups; std::vector operators = extra_operators_; if (!pickup_delivery_pairs_.empty()) { - CP_ROUTING_PUSH_OPERATOR(RELOCATE_PAIR, relocate_pair, operators); + CP_ROUTING_PUSH_OPERATOR(RELOCATE_PAIR, relocate_pair); // Only add the light version of relocate pair if the normal version has not // already been added as it covers a subset of its neighborhood. if (search_parameters.local_search_operators().use_relocate_pair() == BOOL_FALSE) { - CP_ROUTING_PUSH_OPERATOR(LIGHT_RELOCATE_PAIR, light_relocate_pair, - operators); + CP_ROUTING_PUSH_OPERATOR(LIGHT_RELOCATE_PAIR, light_relocate_pair); } - CP_ROUTING_PUSH_OPERATOR(EXCHANGE_PAIR, exchange_pair, operators); - CP_ROUTING_PUSH_OPERATOR(NODE_PAIR_SWAP, node_pair_swap_active, operators); - CP_ROUTING_PUSH_OPERATOR(RELOCATE_SUBTRIP, relocate_subtrip, operators); - CP_ROUTING_PUSH_OPERATOR(EXCHANGE_SUBTRIP, exchange_subtrip, operators); + CP_ROUTING_PUSH_OPERATOR(EXCHANGE_PAIR, exchange_pair); + CP_ROUTING_PUSH_OPERATOR(NODE_PAIR_SWAP, node_pair_swap_active); + CP_ROUTING_PUSH_OPERATOR(RELOCATE_SUBTRIP, relocate_subtrip); + CP_ROUTING_PUSH_OPERATOR(EXCHANGE_SUBTRIP, exchange_subtrip); } if (vehicles_ > 1) { if (GetNumOfSingletonNodes() > 0) { @@ -4085,10 +4152,10 @@ LocalSearchOperator* RoutingModel::GetNeighborhoodOperators( // work is for intra-route moves, already covered by OrOpt. // We are not disabling Exchange and Cross because there are no // intra-route equivalents. - CP_ROUTING_PUSH_OPERATOR(RELOCATE, relocate, operators); + CP_ROUTING_PUSH_OPERATOR(RELOCATE, relocate); } - CP_ROUTING_PUSH_OPERATOR(EXCHANGE, exchange, operators); - CP_ROUTING_PUSH_OPERATOR(CROSS, cross, operators); + CP_ROUTING_PUSH_OPERATOR(EXCHANGE, exchange); + CP_ROUTING_PUSH_OPERATOR(CROSS, cross); } if (!pickup_delivery_pairs_.empty() || search_parameters.local_search_operators().use_relocate_neighbors() == @@ -4102,30 +4169,27 @@ LocalSearchOperator* RoutingModel::GetNeighborhoodOperators( LocalSearchMetaheuristic::GENERIC_TABU_SEARCH && local_search_metaheuristic != LocalSearchMetaheuristic::SIMULATED_ANNEALING) { - CP_ROUTING_PUSH_OPERATOR(LIN_KERNIGHAN, lin_kernighan, operators); + CP_ROUTING_PUSH_OPERATOR(LIN_KERNIGHAN, lin_kernighan); } - CP_ROUTING_PUSH_OPERATOR(TWO_OPT, two_opt, operators); - CP_ROUTING_PUSH_OPERATOR(OR_OPT, or_opt, operators); - CP_ROUTING_PUSH_OPERATOR(RELOCATE_EXPENSIVE_CHAIN, relocate_expensive_chain, - operators); + CP_ROUTING_PUSH_OPERATOR(TWO_OPT, two_opt); + CP_ROUTING_PUSH_OPERATOR(OR_OPT, or_opt); + CP_ROUTING_PUSH_OPERATOR(RELOCATE_EXPENSIVE_CHAIN, relocate_expensive_chain); if (!disjunctions_.empty()) { - CP_ROUTING_PUSH_OPERATOR(MAKE_INACTIVE, make_inactive, operators); - CP_ROUTING_PUSH_OPERATOR(MAKE_CHAIN_INACTIVE, make_chain_inactive, - operators); - CP_ROUTING_PUSH_OPERATOR(MAKE_ACTIVE, make_active, operators); + CP_ROUTING_PUSH_OPERATOR(MAKE_INACTIVE, make_inactive); + CP_ROUTING_PUSH_OPERATOR(MAKE_CHAIN_INACTIVE, make_chain_inactive); + CP_ROUTING_PUSH_OPERATOR(MAKE_ACTIVE, make_active); // The relocate_and_make_active parameter activates all neighborhoods // relocating a node together with making another active. - CP_ROUTING_PUSH_OPERATOR(RELOCATE_AND_MAKE_ACTIVE, relocate_and_make_active, - operators); - CP_ROUTING_PUSH_OPERATOR(MAKE_ACTIVE_AND_RELOCATE, relocate_and_make_active, - operators); + CP_ROUTING_PUSH_OPERATOR(RELOCATE_AND_MAKE_ACTIVE, + relocate_and_make_active); + CP_ROUTING_PUSH_OPERATOR(MAKE_ACTIVE_AND_RELOCATE, + relocate_and_make_active); - CP_ROUTING_PUSH_OPERATOR(SWAP_ACTIVE, swap_active, operators); - CP_ROUTING_PUSH_OPERATOR(EXTENDED_SWAP_ACTIVE, extended_swap_active, - operators); + CP_ROUTING_PUSH_OPERATOR(SWAP_ACTIVE, swap_active); + CP_ROUTING_PUSH_OPERATOR(EXTENDED_SWAP_ACTIVE, extended_swap_active); CP_ROUTING_PUSH_OPERATOR(SHORTEST_PATH_SWAP_ACTIVE, - shortest_path_swap_active, operators); + shortest_path_swap_active); } operator_groups.push_back(ConcatenateOperators(search_parameters, operators)); @@ -4133,27 +4197,24 @@ LocalSearchOperator* RoutingModel::GetNeighborhoodOperators( operators.clear(); if (vehicles() > 1) { // NOTE: The following heuristic path LNS with a single vehicle are - // equivalent to using the heuristic as first solution strategy, so we only - // add these moves if we have at least 2 vehicles in the model. + // equivalent to using the heuristic as first solution strategy, so we + // only add these moves if we have at least 2 vehicles in the model. CP_ROUTING_PUSH_OPERATOR(GLOBAL_CHEAPEST_INSERTION_PATH_LNS, - global_cheapest_insertion_path_lns, operators); + global_cheapest_insertion_path_lns); CP_ROUTING_PUSH_OPERATOR(LOCAL_CHEAPEST_INSERTION_PATH_LNS, - local_cheapest_insertion_path_lns, operators); + local_cheapest_insertion_path_lns); CP_ROUTING_PUSH_OPERATOR( RELOCATE_PATH_GLOBAL_CHEAPEST_INSERTION_INSERT_UNPERFORMED, - relocate_path_global_cheapest_insertion_insert_unperformed, operators); + relocate_path_global_cheapest_insertion_insert_unperformed); } CP_ROUTING_PUSH_OPERATOR(GLOBAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS, - global_cheapest_insertion_expensive_chain_lns, - operators); + global_cheapest_insertion_expensive_chain_lns); CP_ROUTING_PUSH_OPERATOR(LOCAL_CHEAPEST_INSERTION_EXPENSIVE_CHAIN_LNS, - local_cheapest_insertion_expensive_chain_lns, - operators); + local_cheapest_insertion_expensive_chain_lns); CP_ROUTING_PUSH_OPERATOR(GLOBAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS, - global_cheapest_insertion_close_nodes_lns, - operators); + global_cheapest_insertion_close_nodes_lns); CP_ROUTING_PUSH_OPERATOR(LOCAL_CHEAPEST_INSERTION_CLOSE_NODES_LNS, - local_cheapest_insertion_close_nodes_lns, operators); + local_cheapest_insertion_close_nodes_lns); operator_groups.push_back(ConcatenateOperators(search_parameters, operators)); // Third local search loop: Expensive LNS operators. @@ -4163,19 +4224,19 @@ LocalSearchOperator* RoutingModel::GetNeighborhoodOperators( LocalSearchMetaheuristic::GENERIC_TABU_SEARCH && local_search_metaheuristic != LocalSearchMetaheuristic::SIMULATED_ANNEALING) { - CP_ROUTING_PUSH_OPERATOR(TSP_OPT, tsp_opt, operators); + CP_ROUTING_PUSH_OPERATOR(TSP_OPT, tsp_opt); } if (local_search_metaheuristic != LocalSearchMetaheuristic::TABU_SEARCH && local_search_metaheuristic != LocalSearchMetaheuristic::GENERIC_TABU_SEARCH && local_search_metaheuristic != LocalSearchMetaheuristic::SIMULATED_ANNEALING) { - CP_ROUTING_PUSH_OPERATOR(TSP_LNS, tsp_lns, operators); + CP_ROUTING_PUSH_OPERATOR(TSP_LNS, tsp_lns); } - CP_ROUTING_PUSH_OPERATOR(FULL_PATH_LNS, full_path_lns, operators); - CP_ROUTING_PUSH_OPERATOR(PATH_LNS, path_lns, operators); + CP_ROUTING_PUSH_OPERATOR(FULL_PATH_LNS, full_path_lns); + CP_ROUTING_PUSH_OPERATOR(PATH_LNS, path_lns); if (!disjunctions_.empty()) { - CP_ROUTING_PUSH_OPERATOR(INACTIVE_LNS, inactive_lns, operators); + CP_ROUTING_PUSH_OPERATOR(INACTIVE_LNS, inactive_lns); } operator_groups.push_back(ConcatenateOperators(search_parameters, operators)); @@ -5087,10 +5148,25 @@ RoutingModel::CreateIntVarFilteredDecisionBuilder(const Args&... args) { } LocalSearchPhaseParameters* RoutingModel::CreateLocalSearchParameters( - const RoutingSearchParameters& search_parameters) { + const RoutingSearchParameters& search_parameters, bool secondary_ls) { SearchLimit* lns_limit = GetOrCreateLargeNeighborhoodSearchLimit(); + absl::flat_hash_set operators_to_consider; + if (secondary_ls) { + operators_to_consider = {TWO_OPT, + OR_OPT, + LIN_KERNIGHAN, + MAKE_INACTIVE, + MAKE_CHAIN_INACTIVE, + SHORTEST_PATH_SWAP_ACTIVE}; + } else { + // Consider all operators for the primary LS phase. + for (int op = 0; op < LOCAL_SEARCH_OPERATOR_COUNTER; ++op) { + operators_to_consider.insert(RoutingLocalSearchOperator(op)); + } + } return solver_->MakeLocalSearchPhaseParameters( - CostVar(), GetNeighborhoodOperators(search_parameters), + CostVar(), + GetNeighborhoodOperators(search_parameters, operators_to_consider), solver_->MakeSolveOnce( CreateSolutionFinalizer(search_parameters, lns_limit), lns_limit), GetOrCreateLocalSearchLimit(), @@ -5099,13 +5175,13 @@ LocalSearchPhaseParameters* RoutingModel::CreateLocalSearchParameters( {/*filter_objective=*/true, /*filter_with_cp_solver=*/false})); } -DecisionBuilder* RoutingModel::CreateLocalSearchDecisionBuilder( +DecisionBuilder* RoutingModel::CreatePrimaryLocalSearchDecisionBuilder( const RoutingSearchParameters& search_parameters) { const int size = Size(); DecisionBuilder* first_solution = GetFirstSolutionDecisionBuilder(search_parameters); LocalSearchPhaseParameters* const parameters = - CreateLocalSearchParameters(search_parameters); + CreateLocalSearchParameters(search_parameters, /*secondary_ls=*/false); SearchLimit* first_solution_lns_limit = GetOrCreateFirstSolutionLargeNeighborhoodSearchLimit(); DecisionBuilder* const first_solution_sub_decision_builder = @@ -5116,19 +5192,18 @@ DecisionBuilder* RoutingModel::CreateLocalSearchDecisionBuilder( return solver_->MakeLocalSearchPhase(nexts_, first_solution, first_solution_sub_decision_builder, parameters); - } else { - const int all_size = size + size + vehicles_; - std::vector all_vars(all_size); - for (int i = 0; i < size; ++i) { - all_vars[i] = nexts_[i]; - } - for (int i = size; i < all_size; ++i) { - all_vars[i] = vehicle_vars_[i - size]; - } - return solver_->MakeLocalSearchPhase(all_vars, first_solution, - first_solution_sub_decision_builder, - parameters); } + const int all_size = size + size + vehicles_; + std::vector all_vars(all_size); + for (int i = 0; i < size; ++i) { + all_vars[i] = nexts_[i]; + } + for (int i = size; i < all_size; ++i) { + all_vars[i] = vehicle_vars_[i - size]; + } + return solver_->MakeLocalSearchPhase(all_vars, first_solution, + first_solution_sub_decision_builder, + parameters); } void RoutingModel::SetupDecisionBuilders( @@ -5142,17 +5217,25 @@ void RoutingModel::SetupDecisionBuilders( CreateSolutionFinalizer(search_parameters, first_lns_limit), first_lns_limit)); } else { - solve_db_ = CreateLocalSearchDecisionBuilder(search_parameters); + solve_db_ = CreatePrimaryLocalSearchDecisionBuilder(search_parameters); } CHECK(preassignment_ != nullptr); DecisionBuilder* restore_preassignment = solver_->MakeRestoreAssignment(preassignment_); solve_db_ = solver_->Compose(restore_preassignment, solve_db_); + improve_db_ = solver_->Compose(restore_preassignment, solver_->MakeLocalSearchPhase( GetOrCreateAssignment(), - CreateLocalSearchParameters(search_parameters))); + CreateLocalSearchParameters( + search_parameters, /*secondary_ls=*/false))); + + secondary_ls_db_ = solver_->MakeLocalSearchPhase( + GetOrCreateAssignment(), + CreateLocalSearchParameters(search_parameters, /*secondary_ls=*/true)); + secondary_ls_db_ = solver_->Compose(restore_preassignment, secondary_ls_db_); + restore_assignment_ = solver_->Compose( solver_->MakeRestoreAssignment(GetOrCreateAssignment()), CreateSolutionFinalizer(search_parameters, @@ -5226,6 +5309,7 @@ void RoutingModel::SetupMetaheuristics( << " specified without sane timeout: solve may run forever."; } monitors_.push_back(optimize); + secondary_ls_monitors_.push_back(optimize); } void RoutingModel::SetTabuVarsCallback(GetTabuVarsCallback tabu_var_callback) { @@ -5252,9 +5336,13 @@ void RoutingModel::SetupAssignmentCollector( collect_assignments_ = solver_->MakeNBestValueSolutionCollector( full_assignment, search_parameters.number_of_solutions_to_collect(), false); + collect_secondary_ls_assignments_ = solver_->MakeNBestValueSolutionCollector( + full_assignment, search_parameters.number_of_solutions_to_collect(), + false); collect_one_assignment_ = solver_->MakeFirstSolutionCollector(full_assignment); monitors_.push_back(collect_assignments_); + secondary_ls_monitors_.push_back(collect_secondary_ls_assignments_); } void RoutingModel::SetupTrace( @@ -5275,20 +5363,24 @@ void RoutingModel::SetupTrace( } search_log_parameters.display_on_new_solutions_only = false; monitors_.push_back(solver_->MakeSearchLog(search_log_parameters)); + secondary_ls_monitors_.push_back( + solver_->MakeSearchLog(search_log_parameters)); } } void RoutingModel::SetupImprovementLimit( const RoutingSearchParameters& search_parameters) { - if (search_parameters.has_improvement_limit_parameters()) { - monitors_.push_back(solver_->MakeImprovementLimit( - cost_, /*maximize=*/false, search_parameters.log_cost_scaling_factor(), - search_parameters.log_cost_offset(), - search_parameters.improvement_limit_parameters() - .improvement_rate_coefficient(), - search_parameters.improvement_limit_parameters() - .improvement_rate_solutions_distance())); - } + if (!search_parameters.has_improvement_limit_parameters()) return; + + SearchMonitor* const improvement_limit = solver_->MakeImprovementLimit( + cost_, /*maximize=*/false, search_parameters.log_cost_scaling_factor(), + search_parameters.log_cost_offset(), + search_parameters.improvement_limit_parameters() + .improvement_rate_coefficient(), + search_parameters.improvement_limit_parameters() + .improvement_rate_solutions_distance()); + monitors_.push_back(improvement_limit); + secondary_ls_monitors_.push_back(improvement_limit); } namespace { @@ -5342,6 +5434,11 @@ void RoutingModel::SetupSearchMonitors( std::max(objective_lower_bound_, CostVar()->Min()); }, [this]() { local_optimum_reached_ = true; })); + monitors_.push_back( + solver_->MakeCustomLimit([this]() -> bool { return interrupt_cp_; })); + + secondary_ls_monitors_ = monitors_; + SetupImprovementLimit(search_parameters); SetupMetaheuristics(search_parameters); SetupAssignmentCollector(search_parameters); diff --git a/ortools/constraint_solver/routing.h b/ortools/constraint_solver/routing.h index e5f185f91b..7a650cd6d0 100644 --- a/ortools/constraint_solver/routing.h +++ b/ortools/constraint_solver/routing.h @@ -158,6 +158,7 @@ #define OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_H_ #include +#include #include #include #include @@ -176,7 +177,6 @@ #include "absl/time/time.h" #include "ortools/base/int_type.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/strong_vector.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" @@ -269,7 +269,9 @@ class RoutingModel { /// Model, model parameters or flags are not valid. ROUTING_INVALID, /// Problem proven to be infeasible. - ROUTING_INFEASIBLE + ROUTING_INFEASIBLE, + /// Problem has been solved to optimality. + ROUTING_OPTIMAL }; /// Types of precedence policy applied to pickup and delivery pairs. @@ -538,6 +540,11 @@ class RoutingModel { explicit RoutingModel(const RoutingIndexManager& index_manager); RoutingModel(const RoutingIndexManager& index_manager, const RoutingModelParameters& parameters); + + // This type is neither copyable nor movable. + RoutingModel(const RoutingModel&) = delete; + RoutingModel& operator=(const RoutingModel&) = delete; + ~RoutingModel(); /// Represents the sign of values returned by a transit evaluator. @@ -1240,6 +1247,9 @@ class RoutingModel { /// these cases). // TODO(user): Add support for non-homogeneous costs and disjunctions. int64_t ComputeLowerBound(); + /// Returns the current lower bound found by internal solvers during the + /// search. + int64_t objective_lower_bound() const { return objective_lower_bound_; } /// Returns the current status of the routing model. Status status() const { return status_; } /// Returns the value of the internal enable_deep_serialization_ parameter. @@ -1652,6 +1662,9 @@ class RoutingModel { std::vector>> GetCumulBounds( const Assignment& solution_assignment, const RoutingDimension& dimension); #endif + /// Checks if an assignment is feasible. + bool CheckIfAssignmentIsFeasible(const Assignment& assignment, + bool call_at_solution_monitors); /// Returns the underlying constraint solver. Can be used to add extra /// constraints and/or modify search algorithms. Solver* solver() const { return solver_.get(); } @@ -1672,6 +1685,16 @@ class RoutingModel { /// Returns the time buffer to safely return a solution. absl::Duration TimeBuffer() const { return time_buffer_; } + /// Returns the atomic to stop the CP-SAT solver. + std::atomic* GetMutableCPSatInterrupt() { return &interrupt_cp_sat_; } + /// Returns the atomic to stop the CP solver. + std::atomic* GetMutableCPInterrupt() { return &interrupt_cp_; } + /// Cancels the current search. + void CancelSearch() { + interrupt_cp_sat_ = true; + interrupt_cp_ = true; + } + /// Sizes and indices /// Returns the number of nodes in the model. int nodes() const { return nodes_; } @@ -1976,9 +1999,7 @@ class RoutingModel { Assignment* CompactAssignmentInternal(const Assignment& assignment, bool check_compact_assignment) const; /// Checks that the current search parameters are valid for the current - /// model's specific settings. This assumes that FindErrorInSearchParameters() - /// from - /// ./routing_flags.h caught no error. + /// model's specific settings. std::string FindErrorInSearchParametersForModel( const RoutingSearchParameters& search_parameters) const; /// Sets up search objects, such as decision builders and monitors. @@ -2095,7 +2116,9 @@ class RoutingModel { const RoutingSearchParameters& search_parameters, const std::vector& operators) const; LocalSearchOperator* GetNeighborhoodOperators( - const RoutingSearchParameters& search_parameters) const; + const RoutingSearchParameters& search_parameters, + const absl::flat_hash_set& + operators_to_consider) const; struct FilterOptions { bool filter_objective; @@ -2129,8 +2152,8 @@ class RoutingModel { const Args&... args); #endif LocalSearchPhaseParameters* CreateLocalSearchParameters( - const RoutingSearchParameters& search_parameters); - DecisionBuilder* CreateLocalSearchDecisionBuilder( + const RoutingSearchParameters& search_parameters, bool secondary_ls); + DecisionBuilder* CreatePrimaryLocalSearchDecisionBuilder( const RoutingSearchParameters& search_parameters); void SetupDecisionBuilders(const RoutingSearchParameters& search_parameters); void SetupMetaheuristics(const RoutingSearchParameters& search_parameters); @@ -2347,15 +2370,18 @@ class RoutingModel { FirstSolutionStrategy::UNSET; std::vector local_search_operators_; std::vector monitors_; + std::vector secondary_ls_monitors_; std::vector at_solution_monitors_; bool local_optimum_reached_ = false; // Best lower bound found during the search. int64_t objective_lower_bound_ = kint64min; SolutionCollector* collect_assignments_ = nullptr; + SolutionCollector* collect_secondary_ls_assignments_ = nullptr; SolutionCollector* collect_one_assignment_ = nullptr; SolutionCollector* optimized_dimensions_assignment_collector_ = nullptr; DecisionBuilder* solve_db_ = nullptr; DecisionBuilder* improve_db_ = nullptr; + DecisionBuilder* secondary_ls_db_ = nullptr; DecisionBuilder* restore_assignment_ = nullptr; DecisionBuilder* restore_tmp_assignment_ = nullptr; Assignment* assignment_ = nullptr; @@ -2397,6 +2423,9 @@ class RoutingModel { RegularLimit* first_solution_lns_limit_ = nullptr; absl::Duration time_buffer_; + std::atomic interrupt_cp_sat_; + std::atomic interrupt_cp_; + typedef std::pair CacheKey; typedef absl::flat_hash_map TransitCallbackCache; typedef absl::flat_hash_map @@ -2424,8 +2453,6 @@ class RoutingModel { friend class RoutingDimension; friend class RoutingModelInspector; friend class ResourceGroup::Resource; - - DISALLOW_COPY_AND_ASSIGN(RoutingModel); }; /// Routing model visitor. @@ -2859,6 +2886,10 @@ class SimpleBoundCosts { /// to have this information here. class RoutingDimension { public: + // This type is neither copyable nor movable. + RoutingDimension(const RoutingDimension&) = delete; + RoutingDimension& operator=(const RoutingDimension&) = delete; + ~RoutingDimension(); /// Returns the model on which the dimension was created. RoutingModel* model() const { return model_; } @@ -3377,15 +3408,13 @@ class RoutingDimension { vehicle_quadratic_cost_soft_span_upper_bound_; friend class RoutingModel; friend class RoutingModelInspector; - - DISALLOW_COPY_AND_ASSIGN(RoutingDimension); }; /// Attempts to solve the model using the cp-sat solver. As of 5/2019, will /// solve the TSP corresponding to the model if it has a single vehicle. /// Therefore the resulting solution might not actually be feasible. Will return /// false if a solution could not be found. -bool SolveModelWithSat(const RoutingModel& model, +bool SolveModelWithSat(RoutingModel* model, const RoutingSearchParameters& search_parameters, const Assignment* initial_solution, Assignment* solution); diff --git a/ortools/constraint_solver/routing_decision_builders.cc b/ortools/constraint_solver/routing_decision_builders.cc index 4dd72180e3..b641d0911b 100644 --- a/ortools/constraint_solver/routing_decision_builders.cc +++ b/ortools/constraint_solver/routing_decision_builders.cc @@ -21,6 +21,7 @@ #include #include "absl/log/check.h" +#include "absl/types/span.h" #include "ortools/base/map_util.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/routing.h" @@ -137,7 +138,7 @@ bool DimensionFixedTransitsEqualTransitEvaluators( void ConcatenateRouteCumulAndBreakVarAndValues( const RoutingDimension& dimension, int vehicle, const std::vector& cumul_values, - const std::vector& break_values, std::vector* variables, + absl::Span break_values, std::vector* variables, std::vector* values) { *values = cumul_values; variables->clear(); diff --git a/ortools/constraint_solver/routing_filters.cc b/ortools/constraint_solver/routing_filters.cc index 0cf8048e9b..ad2694095e 100644 --- a/ortools/constraint_solver/routing_filters.cc +++ b/ortools/constraint_solver/routing_filters.cc @@ -37,6 +37,7 @@ #include "absl/flags/flag.h" #include "absl/log/check.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" #include "ortools/base/logging.h" #include "ortools/base/small_map.h" #include "ortools/base/strong_vector.h" @@ -437,6 +438,10 @@ void BasePathFilter::SynchronizeFullAssignment() { } node_path_starts_[next] = start; } + for (const int touched : delta_touched_) { + new_nexts_[touched] = kUnassigned; + } + delta_touched_.clear(); OnBeforeSynchronizePaths(); UpdateAllRanks(); OnAfterSynchronizePaths(); @@ -477,6 +482,10 @@ void BasePathFilter::OnSynchronize(const Assignment* delta) { } } } + for (const int touched : delta_touched_) { + new_nexts_[touched] = kUnassigned; + } + delta_touched_.clear(); OnBeforeSynchronizePaths(); for (const int64_t touched_start : touched_paths_.PositionsSetAtLeastOnce()) { int64_t node = touched_start; @@ -1136,7 +1145,7 @@ class PathCumulFilter : public BasePathFilter { // the LP, for a perfect filtering. bool PickupToDeliveryLimitsRespected( const PathTransits& path_transits, int path, - const std::vector& min_path_cumuls) const; + absl::Span min_path_cumuls) const; // Computes the maximum cumul value of nodes along the path using // [current|delta]_path_transits_, and stores the min/max cumul @@ -1146,8 +1155,9 @@ class PathCumulFilter : public BasePathFilter { // "delta" or "current" members. When true, the nodes for which the min/max // cumul has changed from the current value are marked in // delta_nodes_with_precedences_and_changed_cumul_. - void StoreMinMaxCumulOfNodesOnPath( - int path, const std::vector& min_path_cumuls, bool is_delta); + void StoreMinMaxCumulOfNodesOnPath(int path, + absl::Span min_path_cumuls, + bool is_delta); // Compute the max start cumul value for a given path and a given minimal end // cumul value. @@ -1914,7 +1924,7 @@ void PathCumulFilter::InitializeSupportedPathCumul( bool PathCumulFilter::PickupToDeliveryLimitsRespected( const PathTransits& path_transits, int path, - const std::vector& min_path_cumuls) const { + absl::Span min_path_cumuls) const { if (!dimension_.HasPickupToDeliveryLimits()) { return true; } @@ -1976,7 +1986,7 @@ bool PathCumulFilter::PickupToDeliveryLimitsRespected( } void PathCumulFilter::StoreMinMaxCumulOfNodesOnPath( - int path, const std::vector& min_path_cumuls, bool is_delta) { + int path, absl::Span min_path_cumuls, bool is_delta) { const PathTransits& path_transits = is_delta ? delta_path_transits_ : current_path_transits_; diff --git a/ortools/constraint_solver/routing_flags.cc b/ortools/constraint_solver/routing_flags.cc deleted file mode 100644 index 008f699eaf..0000000000 --- a/ortools/constraint_solver/routing_flags.cc +++ /dev/null @@ -1,351 +0,0 @@ -// Copyright 2010-2022 Google LLC -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "ortools/constraint_solver/routing_flags.h" - -#include -#include -#include -#include -#include - -#include "absl/status/status.h" -#include "absl/time/time.h" -#include "ortools/base/map_util.h" -#include "ortools/base/protoutil.h" -#include "ortools/constraint_solver/constraint_solver.h" -#include "ortools/constraint_solver/routing_enums.pb.h" -#include "ortools/constraint_solver/routing_parameters.h" -#include "ortools/util/optional_boolean.pb.h" - -// --- Routing search flags --- - -// Neighborhood activation/deactivation -ABSL_FLAG(bool, routing_no_lns, false, - "Routing: forbids use of Large Neighborhood Search."); -ABSL_FLAG(bool, routing_no_fullpathlns, true, - "Routing: forbids use of Full-path Large Neighborhood Search."); -ABSL_FLAG(bool, routing_no_relocate, false, - "Routing: forbids use of Relocate neighborhood."); -ABSL_FLAG(bool, routing_no_relocate_neighbors, true, - "Routing: forbids use of RelocateNeighbors neighborhood."); -ABSL_FLAG(bool, routing_no_relocate_subtrip, false, - "Routing: forbids use of RelocateSubtrips neighborhood."); -ABSL_FLAG(bool, routing_no_exchange, false, - "Routing: forbids use of Exchange neighborhood."); -ABSL_FLAG(bool, routing_no_exchange_subtrip, false, - "Routing: forbids use of ExchangeSubtrips neighborhood."); -ABSL_FLAG(bool, routing_no_cross, false, - "Routing: forbids use of Cross neighborhood."); -ABSL_FLAG(bool, routing_no_2opt, false, - "Routing: forbids use of 2Opt neighborhood."); -ABSL_FLAG(bool, routing_no_oropt, false, - "Routing: forbids use of OrOpt neighborhood."); -ABSL_FLAG(bool, routing_no_make_active, false, - "Routing: forbids use of MakeActive/SwapActive/MakeInactive " - "neighborhoods."); -ABSL_FLAG(bool, routing_no_lkh, false, - "Routing: forbids use of LKH neighborhood."); -ABSL_FLAG(bool, routing_no_relocate_expensive_chain, false, - "Routing: forbids use of RelocateExpensiveChain operator."); -ABSL_FLAG(bool, routing_no_tsp, true, - "Routing: forbids use of TSPOpt neighborhood."); -ABSL_FLAG(bool, routing_no_tsplns, true, - "Routing: forbids use of TSPLNS neighborhood."); -ABSL_FLAG(bool, routing_use_chain_make_inactive, false, - "Routing: use chain version of MakeInactive neighborhood."); -ABSL_FLAG(bool, routing_use_extended_swap_active, false, - "Routing: use extended version of SwapActive neighborhood."); - -// Meta-heuristics -ABSL_FLAG(bool, routing_guided_local_search, false, "Routing: use GLS."); -ABSL_FLAG(double, routing_guided_local_search_lambda_coefficient, 0.1, - "Lambda coefficient in GLS."); -ABSL_FLAG(bool, routing_simulated_annealing, false, - "Routing: use simulated annealing."); -ABSL_FLAG(bool, routing_tabu_search, false, "Routing: use tabu search."); -ABSL_FLAG(bool, routing_generic_tabu_search, false, - "Routing: use tabu search based on a list of values."); - -// Search limits -ABSL_FLAG(int64_t, routing_solution_limit, std::numeric_limits::max(), - "Routing: number of solutions limit."); -ABSL_FLAG(int64_t, routing_time_limit, std::numeric_limits::max(), - "Routing: time limit in ms."); -ABSL_FLAG(int64_t, routing_lns_time_limit, 100, - "Routing: time limit in ms for LNS sub-decisionbuilder."); - -// Search control -ABSL_FLAG(std::string, routing_first_solution, "", - "Routing first solution heuristic. See SetupParametersFromFlags " - "in the code to get a full list."); -ABSL_FLAG(bool, routing_use_filtered_first_solutions, true, - "Use filtered version of first solution heuristics if available."); -ABSL_FLAG(double, savings_neighbors_ratio, 1, - "Ratio of neighbors to consider for each node when " - "constructing the savings."); -ABSL_FLAG(bool, savings_add_reverse_arcs, false, - "Add savings related to reverse arcs when finding the nearest " - "neighbors of the nodes."); -ABSL_FLAG(double, savings_arc_coefficient, 1.0, - "Coefficient of the cost of the arc for which the saving value " - "is being computed."); -ABSL_FLAG(double, cheapest_insertion_farthest_seeds_ratio, 0, - "Ratio of available vehicles in the model on which farthest " - "nodes of the model are inserted as seeds."); -ABSL_FLAG(double, cheapest_insertion_first_solution_neighbors_ratio, 1.0, - "Ratio of nodes considered as neighbors in the " - "GlobalCheapestInsertion first solution heuristic."); -ABSL_FLAG(bool, routing_dfs, false, - "Routing: use a complete depth-first search."); -ABSL_FLAG(double, routing_optimization_step, 0.0, "Optimization step."); -ABSL_FLAG(int, routing_number_of_solutions_to_collect, 1, - "Number of solutions to collect."); -ABSL_FLAG(int, routing_relocate_expensive_chain_num_arcs_to_consider, 4, - "Number of arcs to consider in the RelocateExpensiveChain " - "neighborhood operator."); - -// Propagation control -ABSL_FLAG(bool, routing_use_light_propagation, true, - "Use constraints with light propagation in routing model."); - -// Cache settings. -ABSL_FLAG(bool, routing_cache_callbacks, false, "Cache callback calls."); -ABSL_FLAG(int64_t, routing_max_cache_size, 1000, - "Maximum cache size when callback caching is on."); - -// Misc -ABSL_FLAG(bool, routing_trace, false, "Routing: trace search."); -ABSL_FLAG(bool, routing_profile, false, "Routing: profile search."); - -// --- Routing model flags --- -ABSL_FLAG(bool, routing_use_homogeneous_costs, true, - "Routing: use homogeneous cost model when possible."); -ABSL_FLAG(bool, routing_gzip_compress_trail, false, - "Use gzip to compress the trail, zippy otherwise."); - -namespace operations_research { - -void SetFirstSolutionStrategyFromFlags(RoutingSearchParameters* parameters) { - CHECK(parameters != nullptr); - const std::map - first_solution_string_to_parameters = { - {"PathCheapestArc", FirstSolutionStrategy::PATH_CHEAPEST_ARC}, - {"PathMostConstrainedArc", - FirstSolutionStrategy::PATH_MOST_CONSTRAINED_ARC}, - {"EvaluatorStrategy", FirstSolutionStrategy::EVALUATOR_STRATEGY}, - {"Savings", FirstSolutionStrategy::SAVINGS}, - {"Sweep", FirstSolutionStrategy::SWEEP}, - {"Christofides", FirstSolutionStrategy::CHRISTOFIDES}, - {"AllUnperformed", FirstSolutionStrategy::ALL_UNPERFORMED}, - {"BestInsertion", FirstSolutionStrategy::BEST_INSERTION}, - {"GlobalCheapestInsertion", - FirstSolutionStrategy::PARALLEL_CHEAPEST_INSERTION}, - {"SequentialGlobalCheapestInsertion", - FirstSolutionStrategy::SEQUENTIAL_CHEAPEST_INSERTION}, - {"LocalCheapestInsertion", - FirstSolutionStrategy::LOCAL_CHEAPEST_INSERTION}, - {"GlobalCheapestArc", FirstSolutionStrategy::GLOBAL_CHEAPEST_ARC}, - {"LocalCheapestArc", FirstSolutionStrategy::LOCAL_CHEAPEST_ARC}, - {"DefaultStrategy", FirstSolutionStrategy::FIRST_UNBOUND_MIN_VALUE}, - {"", FirstSolutionStrategy::FIRST_UNBOUND_MIN_VALUE}}; - FirstSolutionStrategy::Value strategy; - if (gtl::FindCopy(first_solution_string_to_parameters, - absl::GetFlag(FLAGS_routing_first_solution), &strategy)) { - parameters->set_first_solution_strategy(strategy); - } - parameters->set_use_unfiltered_first_solution_strategy( - !absl::GetFlag(FLAGS_routing_use_filtered_first_solutions)); - parameters->set_savings_neighbors_ratio( - absl::GetFlag(FLAGS_savings_neighbors_ratio)); - parameters->set_savings_max_memory_usage_bytes(6e9); - parameters->set_savings_add_reverse_arcs( - absl::GetFlag(FLAGS_savings_add_reverse_arcs)); - parameters->set_savings_arc_coefficient( - absl::GetFlag(FLAGS_savings_arc_coefficient)); - parameters->set_cheapest_insertion_farthest_seeds_ratio( - absl::GetFlag(FLAGS_cheapest_insertion_farthest_seeds_ratio)); - parameters->set_cheapest_insertion_first_solution_neighbors_ratio( - absl::GetFlag(FLAGS_cheapest_insertion_first_solution_neighbors_ratio)); - parameters->set_cheapest_insertion_first_solution_min_neighbors(1); -} - -void SetLocalSearchMetaheuristicFromFlags(RoutingSearchParameters* parameters) { - CHECK(parameters != nullptr); - if (absl::GetFlag(FLAGS_routing_tabu_search)) { - parameters->set_local_search_metaheuristic( - LocalSearchMetaheuristic::TABU_SEARCH); - } else if (absl::GetFlag(FLAGS_routing_generic_tabu_search)) { - parameters->set_local_search_metaheuristic( - LocalSearchMetaheuristic::GENERIC_TABU_SEARCH); - } else if (absl::GetFlag(FLAGS_routing_simulated_annealing)) { - parameters->set_local_search_metaheuristic( - LocalSearchMetaheuristic::SIMULATED_ANNEALING); - } else if (absl::GetFlag(FLAGS_routing_guided_local_search)) { - parameters->set_local_search_metaheuristic( - LocalSearchMetaheuristic::GUIDED_LOCAL_SEARCH); - } - parameters->set_guided_local_search_lambda_coefficient( - absl::GetFlag(FLAGS_routing_guided_local_search_lambda_coefficient)); -} - -namespace { -OptionalBoolean ToOptionalBoolean(bool x) { return x ? BOOL_TRUE : BOOL_FALSE; } -} // namespace - -void AddLocalSearchNeighborhoodOperatorsFromFlags( - RoutingSearchParameters* parameters) { - CHECK(parameters != nullptr); - parameters->set_cheapest_insertion_ls_operator_neighbors_ratio(1.0); - parameters->set_cheapest_insertion_ls_operator_min_neighbors(1); - RoutingSearchParameters::LocalSearchNeighborhoodOperators* const - local_search_operators = parameters->mutable_local_search_operators(); - - // TODO(user): Remove these overrides: they should be set by the caller, via - // a baseline RoutingSearchParameters obtained from DefaultSearchParameters(). - local_search_operators->set_use_relocate_pair(BOOL_TRUE); - local_search_operators->set_use_light_relocate_pair(BOOL_TRUE); - local_search_operators->set_use_exchange_pair(BOOL_TRUE); - local_search_operators->set_use_relocate_and_make_active(BOOL_FALSE); - local_search_operators->set_use_node_pair_swap_active(BOOL_FALSE); - local_search_operators->set_use_cross_exchange(BOOL_FALSE); - local_search_operators->set_use_global_cheapest_insertion_path_lns(BOOL_TRUE); - local_search_operators->set_use_local_cheapest_insertion_path_lns(BOOL_TRUE); - local_search_operators - ->set_use_relocate_path_global_cheapest_insertion_insert_unperformed( - BOOL_TRUE); - local_search_operators->set_use_global_cheapest_insertion_expensive_chain_lns( - BOOL_FALSE); - local_search_operators->set_use_local_cheapest_insertion_expensive_chain_lns( - BOOL_FALSE); - local_search_operators->set_use_global_cheapest_insertion_close_nodes_lns( - BOOL_FALSE); - local_search_operators->set_use_local_cheapest_insertion_close_nodes_lns( - BOOL_FALSE); - - local_search_operators->set_use_relocate( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_relocate))); - local_search_operators->set_use_relocate_neighbors( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_relocate_neighbors))); - local_search_operators->set_use_relocate_subtrip( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_relocate_subtrip))); - local_search_operators->set_use_exchange_subtrip( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_exchange_subtrip))); - local_search_operators->set_use_exchange( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_exchange))); - local_search_operators->set_use_cross( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_cross))); - local_search_operators->set_use_two_opt( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_2opt))); - local_search_operators->set_use_or_opt( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_oropt))); - local_search_operators->set_use_lin_kernighan( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_lkh))); - local_search_operators->set_use_relocate_expensive_chain(ToOptionalBoolean( - !absl::GetFlag(FLAGS_routing_no_relocate_expensive_chain))); - local_search_operators->set_use_tsp_opt( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_tsp))); - local_search_operators->set_use_make_active( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_make_active))); - local_search_operators->set_use_make_inactive( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_use_chain_make_inactive) && - !absl::GetFlag(FLAGS_routing_no_make_active))); - local_search_operators->set_use_make_chain_inactive( - ToOptionalBoolean(absl::GetFlag(FLAGS_routing_use_chain_make_inactive) && - !absl::GetFlag(FLAGS_routing_no_make_active))); - local_search_operators->set_use_swap_active(ToOptionalBoolean( - !absl::GetFlag(FLAGS_routing_use_extended_swap_active) && - !absl::GetFlag(FLAGS_routing_no_make_active))); - local_search_operators->set_use_extended_swap_active( - ToOptionalBoolean(absl::GetFlag(FLAGS_routing_use_extended_swap_active) && - !absl::GetFlag(FLAGS_routing_no_make_active))); - local_search_operators->set_use_path_lns( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_lns))); - local_search_operators->set_use_inactive_lns( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_lns))); - local_search_operators->set_use_full_path_lns( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_fullpathlns))); - local_search_operators->set_use_tsp_lns( - ToOptionalBoolean(!absl::GetFlag(FLAGS_routing_no_tsplns))); -} - -void SetSearchLimitsFromFlags(RoutingSearchParameters* parameters) { - CHECK(parameters != nullptr); - parameters->set_use_depth_first_search(absl::GetFlag(FLAGS_routing_dfs)); - parameters->set_use_cp(BOOL_TRUE); - parameters->set_use_cp_sat(BOOL_FALSE); - parameters->set_optimization_step( - absl::GetFlag(FLAGS_routing_optimization_step)); - parameters->set_number_of_solutions_to_collect( - absl::GetFlag(FLAGS_routing_number_of_solutions_to_collect)); - parameters->set_solution_limit(absl::GetFlag(FLAGS_routing_solution_limit)); - if (absl::GetFlag(FLAGS_routing_time_limit) != - std::numeric_limits::max()) { - CHECK_OK(util_time::EncodeGoogleApiProto( - absl::Milliseconds(absl::GetFlag(FLAGS_routing_time_limit)), - parameters->mutable_time_limit())); - } - if (absl::GetFlag(FLAGS_routing_lns_time_limit) != - std::numeric_limits::max()) { - CHECK_OK(util_time::EncodeGoogleApiProto( - absl::Milliseconds(absl::GetFlag(FLAGS_routing_lns_time_limit)), - parameters->mutable_lns_time_limit())); - } -} - -void SetMiscellaneousParametersFromFlags(RoutingSearchParameters* parameters) { - CHECK(parameters != nullptr); - parameters->set_use_full_propagation( - !absl::GetFlag(FLAGS_routing_use_light_propagation)); - parameters->set_log_search(absl::GetFlag(FLAGS_routing_trace)); - parameters->set_log_cost_scaling_factor(1.0); - parameters->set_relocate_expensive_chain_num_arcs_to_consider(absl::GetFlag( - FLAGS_routing_relocate_expensive_chain_num_arcs_to_consider)); - parameters->set_heuristic_expensive_chain_lns_num_arcs_to_consider(4); - parameters->set_heuristic_close_nodes_lns_num_nodes(5); - parameters->set_continuous_scheduling_solver( - RoutingSearchParameters::SCHEDULING_GLOP); - parameters->set_mixed_integer_scheduling_solver( - RoutingSearchParameters::SCHEDULING_CP_SAT); -} - -RoutingSearchParameters BuildSearchParametersFromFlags() { - RoutingSearchParameters parameters; - SetFirstSolutionStrategyFromFlags(¶meters); - SetLocalSearchMetaheuristicFromFlags(¶meters); - AddLocalSearchNeighborhoodOperatorsFromFlags(¶meters); - SetSearchLimitsFromFlags(¶meters); - SetMiscellaneousParametersFromFlags(¶meters); - const std::string error = FindErrorInRoutingSearchParameters(parameters); - LOG_IF(DFATAL, !error.empty()) - << "Error in the routing search parameters built from flags: " << error; - return parameters; -} - -RoutingModelParameters BuildModelParametersFromFlags() { - RoutingModelParameters parameters; - ConstraintSolverParameters* const solver_parameters = - parameters.mutable_solver_parameters(); - *solver_parameters = Solver::DefaultSolverParameters(); - parameters.set_reduce_vehicle_cost_model( - absl::GetFlag(FLAGS_routing_use_homogeneous_costs)); - if (absl::GetFlag(FLAGS_routing_cache_callbacks)) { - parameters.set_max_callback_cache_size( - absl::GetFlag(FLAGS_routing_max_cache_size)); - } - solver_parameters->set_profile_local_search( - absl::GetFlag(FLAGS_routing_profile)); - return parameters; -} - -} // namespace operations_research diff --git a/ortools/constraint_solver/routing_flags.h b/ortools/constraint_solver/routing_flags.h deleted file mode 100644 index 898a40fd2c..0000000000 --- a/ortools/constraint_solver/routing_flags.h +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright 2010-2022 Google LLC -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#ifndef OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_FLAGS_H_ -#define OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_FLAGS_H_ - -#include -#include - -#include "ortools/base/commandlineflags.h" -#include "ortools/constraint_solver/routing_parameters.pb.h" - -/// Neighborhood activation/deactivation -ABSL_DECLARE_FLAG(bool, routing_no_lns); -ABSL_DECLARE_FLAG(bool, routing_no_fullpathlns); -ABSL_DECLARE_FLAG(bool, routing_no_relocate); -ABSL_DECLARE_FLAG(bool, routing_no_relocate_neighbors); -ABSL_DECLARE_FLAG(bool, routing_no_relocate_subtrip); -ABSL_DECLARE_FLAG(bool, routing_no_exchange); -ABSL_DECLARE_FLAG(bool, routing_no_exchange_subtrip); -ABSL_DECLARE_FLAG(bool, routing_no_cross); -ABSL_DECLARE_FLAG(bool, routing_no_2opt); -ABSL_DECLARE_FLAG(bool, routing_no_oropt); -ABSL_DECLARE_FLAG(bool, routing_no_make_active); -ABSL_DECLARE_FLAG(bool, routing_no_lkh); -ABSL_DECLARE_FLAG(bool, routing_no_relocate_expensive_chain); -ABSL_DECLARE_FLAG(bool, routing_no_tsp); -ABSL_DECLARE_FLAG(bool, routing_no_tsplns); -ABSL_DECLARE_FLAG(bool, routing_use_chain_make_inactive); -ABSL_DECLARE_FLAG(bool, routing_use_extended_swap_active); - -/// Meta-heuristics -ABSL_DECLARE_FLAG(bool, routing_guided_local_search); -ABSL_DECLARE_FLAG(double, routing_guided_local_search_lambda_coefficient); -ABSL_DECLARE_FLAG(bool, routing_simulated_annealing); -ABSL_DECLARE_FLAG(bool, routing_tabu_search); -ABSL_DECLARE_FLAG(bool, routing_generic_tabu_search); - -/// Search limits -ABSL_DECLARE_FLAG(int64_t, routing_solution_limit); -ABSL_DECLARE_FLAG(int64_t, routing_time_limit); -ABSL_DECLARE_FLAG(int64_t, routing_lns_time_limit); - -/// Search control -ABSL_DECLARE_FLAG(std::string, routing_first_solution); -ABSL_DECLARE_FLAG(bool, routing_use_filtered_first_solutions); -ABSL_DECLARE_FLAG(double, savings_neighbors_ratio); -ABSL_DECLARE_FLAG(bool, savings_add_reverse_arcs); -ABSL_DECLARE_FLAG(double, savings_arc_coefficient); -ABSL_DECLARE_FLAG(double, cheapest_insertion_farthest_seeds_ratio); -ABSL_DECLARE_FLAG(double, cheapest_insertion_first_solution_neighbors_ratio); -ABSL_DECLARE_FLAG(bool, routing_dfs); -ABSL_DECLARE_FLAG(double, routing_optimization_step); -ABSL_DECLARE_FLAG(int, routing_number_of_solutions_to_collect); -ABSL_DECLARE_FLAG(int, routing_relocate_expensive_chain_num_arcs_to_consider); - -/// Propagation control -ABSL_DECLARE_FLAG(bool, routing_use_light_propagation); - -/// Cache settings. -ABSL_DECLARE_FLAG(bool, routing_cache_callbacks); -ABSL_DECLARE_FLAG(int64_t, routing_max_cache_size); - -/// Misc -ABSL_DECLARE_FLAG(bool, routing_trace); -ABSL_DECLARE_FLAG(bool, routing_profile); - -/// --- Routing model flags --- -ABSL_DECLARE_FLAG(bool, routing_use_homogeneous_costs); -ABSL_DECLARE_FLAG(bool, routing_gzip_compress_trail); - -namespace operations_research { - -/// Builds routing search parameters from flags. -RoutingModelParameters BuildModelParametersFromFlags(); - -/// Builds routing search parameters from flags. -// TODO(user): Make this return a StatusOr, verifying that the flags -/// describe a valid set of routing search parameters. -RoutingSearchParameters BuildSearchParametersFromFlags(); - -} // namespace operations_research - -#endif // OR_TOOLS_CONSTRAINT_SOLVER_ROUTING_FLAGS_H_ diff --git a/ortools/constraint_solver/routing_flow.cc b/ortools/constraint_solver/routing_flow.cc index 294a1a4eda..c407e5dfbf 100644 --- a/ortools/constraint_solver/routing_flow.cc +++ b/ortools/constraint_solver/routing_flow.cc @@ -24,6 +24,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" +#include "absl/types/span.h" #include "ortools/base/int_type.h" #include "ortools/base/logging.h" #include "ortools/base/map_util.h" @@ -42,7 +43,7 @@ namespace { // Compute set of disjunctions involved in a pickup and delivery pair. template void AddDisjunctionsFromNodes(const RoutingModel& model, - const std::vector& nodes, + absl::Span nodes, Disjunctions* disjunctions) { for (int64_t node : nodes) { for (const auto disjunction : model.GetDisjunctionIndices(node)) { diff --git a/ortools/constraint_solver/routing_lp_scheduling.cc b/ortools/constraint_solver/routing_lp_scheduling.cc index 8adf449dd6..06b16d0342 100644 --- a/ortools/constraint_solver/routing_lp_scheduling.cc +++ b/ortools/constraint_solver/routing_lp_scheduling.cc @@ -33,14 +33,15 @@ #include "absl/log/check.h" #include "absl/strings/str_format.h" #include "absl/time/time.h" -#include "ortools/base/dump_vars.h" #include "ortools/base/logging.h" +#include "ortools/base/map_util.h" #include "ortools/base/mathutil.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/routing.h" #include "ortools/constraint_solver/routing_parameters.pb.h" #include "ortools/glop/parameters.pb.h" +#include "ortools/graph/ebert_graph.h" #include "ortools/graph/min_cost_flow.h" #include "ortools/sat/cp_model.pb.h" #include "ortools/sat/lp_utils.h" @@ -1651,6 +1652,21 @@ bool DimensionCumulOptimizerCore::SetRouteTravelConstraints( return true; } +namespace { +bool RouteIsValid(const RoutingModel& model, int vehicle, + const std::function& next_accessor) { + absl::flat_hash_set visited; + int node = model.Start(vehicle); + visited.insert(node); + while (!model.IsEnd(node)) { + node = next_accessor(node); + if (visited.contains(node)) return false; + visited.insert(node); + } + return visited.size() >= 2; +} +} // namespace + bool DimensionCumulOptimizerCore::SetRouteCumulConstraints( int vehicle, const std::function& next_accessor, const std::function& transit_accessor, @@ -1661,6 +1677,7 @@ bool DimensionCumulOptimizerCore::SetRouteCumulConstraints( // Extract the vehicle's path from next_accessor. std::vector path; { + DCHECK(RouteIsValid(*model, vehicle, next_accessor)); int node = model->Start(vehicle); path.push_back(node); while (!model->IsEnd(node)) { diff --git a/ortools/constraint_solver/routing_parameters.cc b/ortools/constraint_solver/routing_parameters.cc index 1a4fb1ac5e..fc71fbffd1 100644 --- a/ortools/constraint_solver/routing_parameters.cc +++ b/ortools/constraint_solver/routing_parameters.cc @@ -125,6 +125,7 @@ RoutingSearchParameters CreateDefaultRoutingSearchParameters() { p.set_use_generalized_cp_sat(BOOL_FALSE); p.mutable_sat_parameters()->set_linearization_level(2); p.mutable_sat_parameters()->set_num_search_workers(1); + p.set_report_intermediate_cp_sat_solutions(false); p.set_fallback_to_cp_sat_size_threshold(20); p.set_continuous_scheduling_solver(RoutingSearchParameters::SCHEDULING_GLOP); p.set_mixed_integer_scheduling_solver( @@ -135,6 +136,7 @@ RoutingSearchParameters CreateDefaultRoutingSearchParameters() { // No global time_limit by default. p.set_solution_limit(kint64max); p.mutable_lns_time_limit()->set_nanos(100000000); // 0.1s. + p.set_secondary_ls_time_limit_ratio(0); p.set_use_full_propagation(false); p.set_log_search(false); p.set_log_cost_scaling_factor(1.0); @@ -314,6 +316,11 @@ std::vector FindErrorsInRoutingSearchParameters( errors.emplace_back("Invalid lns_time_limit: " + search_parameters.lns_time_limit().ShortDebugString()); } + if (const double ratio = search_parameters.secondary_ls_time_limit_ratio(); + std::isnan(ratio) || ratio < 0 || ratio >= 1) { + errors.emplace_back( + StrCat("Invalid secondary_ls_time_limit_ratio: ", ratio)); + } if (!FirstSolutionStrategy::Value_IsValid( search_parameters.first_solution_strategy())) { errors.emplace_back(StrCat("Invalid first_solution_strategy: ", diff --git a/ortools/constraint_solver/routing_parameters.proto b/ortools/constraint_solver/routing_parameters.proto index 36d378c203..25c12f61f3 100644 --- a/ortools/constraint_solver/routing_parameters.proto +++ b/ortools/constraint_solver/routing_parameters.proto @@ -35,7 +35,7 @@ package operations_research; // then the routing library will pick its preferred value for that parameter // automatically: this should be the case for most parameters. // To see those "default" parameters, call GetDefaultRoutingSearchParameters(). -// Next ID: 56 +// Next ID: 58 message RoutingSearchParameters { // First solution strategies, used as starting point of local search. FirstSolutionStrategy.Value first_solution_strategy = 1; @@ -473,6 +473,9 @@ message RoutingSearchParameters { // If use_cp_sat or use_generalized_cp_sat is true, contains the SAT algorithm // parameters which will be used. sat.SatParameters sat_parameters = 48; + // If use_cp_sat or use_generalized_cp_sat is true, will report intermediate + // solutions found by CP-SAT to solution listeners. + bool report_intermediate_cp_sat_solutions = 56; // If model.Size() is less than the threshold and that no solution has been // found, attempt a pass with CP-SAT. int32 fallback_to_cp_sat_size_threshold = 52; @@ -504,6 +507,13 @@ message RoutingSearchParameters { // Limit to the time spent in the completion search for each local search // neighbor. google.protobuf.Duration lns_time_limit = 10; + // Ratio of the overall time limit spent in a secondary LS phase with only + // intra-route and insertion operators, meant to "cleanup" the current + // solution before stopping the search. + // TODO(user): Since these operators are very fast, add a parameter to cap + // the max time allocated for this second phase (e.g. + // Duration max_secondary_ls_time_limit). + double secondary_ls_time_limit_ratio = 57; // Parameters required for the improvement search limit. message ImprovementSearchLimitParameters { diff --git a/ortools/constraint_solver/routing_sat.cc b/ortools/constraint_solver/routing_sat.cc index f5cc21de71..b461bad611 100644 --- a/ortools/constraint_solver/routing_sat.cc +++ b/ortools/constraint_solver/routing_sat.cc @@ -12,6 +12,7 @@ // limitations under the License. #include +#include #include #include #include @@ -112,6 +113,40 @@ struct Arc { using ArcVarMap = std::map; // needs to be stable when iterating +void AddSoftCumulBounds(const RoutingDimension* dimension, int index, int cumul, + int64_t cumul_min, int64_t cumul_max, + CpModelProto* cp_model) { + { + const int64_t soft_ub_coef = + dimension->GetCumulVarSoftUpperBoundCoefficient(index); + if (soft_ub_coef != 0) { + const int64_t soft_ub = dimension->GetCumulVarSoftUpperBound(index); + const int soft_ub_var = + AddVariable(cp_model, 0, CapSub(cumul_max, soft_ub)); + // soft_ub_var >= cumul - soft_ub + AddLinearConstraint(cp_model, std::numeric_limits::min(), + soft_ub, {{cumul, 1}, {soft_ub_var, -1}}); + cp_model->mutable_objective()->add_vars(soft_ub_var); + cp_model->mutable_objective()->add_coeffs(soft_ub_coef); + } + } + { + const int64_t soft_lb_coef = + dimension->GetCumulVarSoftLowerBoundCoefficient(index); + if (soft_lb_coef != 0) { + const int64_t soft_lb = dimension->GetCumulVarSoftLowerBound(index); + const int soft_lb_var = + AddVariable(cp_model, 0, CapSub(soft_lb, cumul_min)); + // soft_lb_var >= soft_lb - cumul + AddLinearConstraint(cp_model, soft_lb, + std::numeric_limits::max(), + {{cumul, 1}, {soft_lb_var, 1}}); + cp_model->mutable_objective()->add_vars(soft_lb_var); + cp_model->mutable_objective()->add_coeffs(soft_lb_coef); + } + } +} + // Adds all dimensions to a CpModelProto. Only adds path cumul constraints and // cumul bounds. void AddDimensions(const RoutingModel& model, const ArcVarMap& arc_vars, @@ -136,6 +171,8 @@ void AddDimensions(const RoutingModel& model, const ArcVarMap& arc_vars, std::min(dimension->cumuls()[i]->Max(), CapSub(max_end, transit(i, model.End(0))))); cumuls[i] = AddVariable(cp_model, cumul_min, cumul_max); + AddSoftCumulBounds(dimension, i, cumuls[i], cumul_min, cumul_max, + cp_model); } for (const auto arc_var : arc_vars) { const int tail = arc_var.first.tail; @@ -368,13 +405,42 @@ ArcVarMap PopulateModelFromRoutingModel(const RoutingModel& model, return PopulateMultiRouteModelFromRoutingModel(model, cp_model); } +void ConvertObjectiveToSolution(const CpSolverResponse& response, + const CpObjectiveProto& objective, + const RoutingModel& model, + Assignment* solution) { + if (response.status() == CpSolverStatus::OPTIMAL) { + // If the solution was proven optimal by CP-SAT, add the objective value to + // the solution; it will be a proper lower bound of the routing objective. + // Recomputing the objective value to avoid rounding errors due to scaling. + // Note: We could use inner_objective_lower_bound if we were sure + // absolute_gap_limit was 0 (which is not guaranteed). + int64_t cost_value = 0; + for (int i = 0; i < objective.coeffs_size(); ++i) { + cost_value = CapAdd( + cost_value, + CapProd(objective.coeffs(i), response.solution(objective.vars(i)))); + } + solution->AddObjective(model.CostVar()); + solution->SetObjectiveValue(cost_value); + } else if (response.status() == CpSolverStatus::FEASIBLE) { + // If the solution is feasible only, add the lower bound of the objective to + // the solution; it will be a proper lower bound of the routing objective. + solution->AddObjective(model.CostVar()); + solution->SetObjectiveValue(response.inner_objective_lower_bound()); + } +} + // Converts a CpSolverResponse to an Assignment containing next variables. bool ConvertToSolution(const CpSolverResponse& response, + const CpObjectiveProto& objective, const RoutingModel& model, const ArcVarMap& arc_vars, Assignment* solution) { + solution->Clear(); if (response.status() != CpSolverStatus::OPTIMAL && - response.status() != CpSolverStatus::FEASIBLE) + response.status() != CpSolverStatus::FEASIBLE) { return false; + } const int depot = GetDepotFromModel(model); int vehicle = 0; for (const auto& arc_var : arc_vars) { @@ -393,11 +459,14 @@ bool ConvertToSolution(const CpSolverResponse& response, // Close open routes. for (int v = 0; v < model.vehicles(); ++v) { int current = model.Start(v); - while (solution->Contains(model.NextVar(current))) { + while (!model.IsEnd(current) && + solution->Contains(model.NextVar(current))) { current = solution->Value(model.NextVar(current)); } + if (model.IsEnd(current)) continue; solution->Add(model.NextVar(current))->SetValue(model.End(v)); } + ConvertObjectiveToSolution(response, objective, model, solution); return true; } @@ -423,6 +492,8 @@ void AddGeneralizedDimensions( std::min(cumul_max, dimension->vehicle_capacities()[vehicle]); } cumuls[cp_node] = AddVariable(cp_model, cumul_min, cumul_max); + AddSoftCumulBounds(dimension, node, cumuls[cp_node], cumul_min, cumul_max, + cp_model); } // Constrain cumuls with vehicle capacities. @@ -945,9 +1016,11 @@ ArcVarMap PopulateGeneralizedRouteModelFromRoutingModel( // Converts a CpSolverResponse to an Assignment containing next variables. bool ConvertGeneralizedResponseToSolution(const CpSolverResponse& response, + const CpObjectiveProto& objective, const RoutingModel& model, const ArcVarMap& arc_vars, Assignment* solution) { + solution->Clear(); if (response.status() != CpSolverStatus::OPTIMAL && response.status() != CpSolverStatus::FEASIBLE) { return false; @@ -959,6 +1032,7 @@ bool ConvertGeneralizedResponseToSolution(const CpSolverResponse& response, if (head == depot || tail == depot) continue; solution->Add(model.NextVar(tail - 1))->SetValue(head - 1); } + ConvertObjectiveToSolution(response, objective, model, solution); return true; } @@ -1015,6 +1089,7 @@ void AddSolutionAsHintToModel(const Assignment* solution, // Returns the response of the search. CpSolverResponse SolveRoutingModel( const CpModelProto& cp_model, absl::Duration remaining_time, + std::atomic* interrupt_solve, const RoutingSearchParameters& search_parameters, const std::function& observer) { // Copying to set remaining time. @@ -1029,6 +1104,8 @@ CpSolverResponse SolveRoutingModel( } Model model; model.Add(NewSatParameters(sat_parameters)); + model.GetOrCreate()->RegisterExternalBooleanAsLimit( + interrupt_solve); if (observer != nullptr) { model.Add(NewFeasibleSolutionObserver(observer)); } @@ -1056,7 +1133,7 @@ bool IsFeasibleArcVarMap(const ArcVarMap& arc_vars, int max_node_index) { // Solves a RoutingModel using the CP-SAT solver. Returns false if no solution // was found. -bool SolveModelWithSat(const RoutingModel& model, +bool SolveModelWithSat(RoutingModel* model, const RoutingSearchParameters& search_parameters, const Assignment* initial_solution, Assignment* solution) { @@ -1064,26 +1141,51 @@ bool SolveModelWithSat(const RoutingModel& model, cp_model.mutable_objective()->set_scaling_factor( search_parameters.log_cost_scaling_factor()); cp_model.mutable_objective()->set_offset(search_parameters.log_cost_offset()); + const sat::CpObjectiveProto& objective = cp_model.objective(); + const std::function + null_observer; if (search_parameters.use_generalized_cp_sat() == BOOL_TRUE) { const sat::ArcVarMap arc_vars = - sat::PopulateGeneralizedRouteModelFromRoutingModel(model, &cp_model); - const int max_node_index = model.Nexts().size() + model.vehicles(); + sat::PopulateGeneralizedRouteModelFromRoutingModel(*model, &cp_model); + const int max_node_index = model->Nexts().size() + model->vehicles(); if (!sat::IsFeasibleArcVarMap(arc_vars, max_node_index)) return false; - sat::AddSolutionAsHintToGeneralizedModel(initial_solution, model, arc_vars, + sat::AddSolutionAsHintToGeneralizedModel(initial_solution, *model, arc_vars, &cp_model); + const std::function observer = + search_parameters.report_intermediate_cp_sat_solutions() ? + [model, &objective, &arc_vars, solution] + (const sat::CpSolverResponse& response) { + // TODO(user): Check that performance is acceptable. + sat::ConvertGeneralizedResponseToSolution( + response, objective, *model, arc_vars, solution); + model->CheckIfAssignmentIsFeasible( + *solution, + /*call_at_solution_monitors=*/true); + } : null_observer; return sat::ConvertGeneralizedResponseToSolution( - sat::SolveRoutingModel(cp_model, model.RemainingTime(), - search_parameters, nullptr), - model, arc_vars, solution); + sat::SolveRoutingModel(cp_model, model->RemainingTime(), + model->GetMutableCPSatInterrupt(), + search_parameters, observer), + objective, *model, arc_vars, solution); } - if (!sat::RoutingModelCanBeSolvedBySat(model)) return false; + if (!sat::RoutingModelCanBeSolvedBySat(*model)) return false; const sat::ArcVarMap arc_vars = - sat::PopulateModelFromRoutingModel(model, &cp_model); - sat::AddSolutionAsHintToModel(initial_solution, model, arc_vars, &cp_model); + sat::PopulateModelFromRoutingModel(*model, &cp_model); + sat::AddSolutionAsHintToModel(initial_solution, *model, arc_vars, &cp_model); + const std::function observer = + search_parameters.report_intermediate_cp_sat_solutions() ? + [model, &objective, &arc_vars, solution] + (const sat::CpSolverResponse& response) { + // TODO(user): Check that performance is acceptable. + sat::ConvertToSolution(response, objective, *model, arc_vars, solution); + model->CheckIfAssignmentIsFeasible(*solution, + /*call_at_solution_monitors=*/true); + } : null_observer; return sat::ConvertToSolution( - sat::SolveRoutingModel(cp_model, model.RemainingTime(), search_parameters, - nullptr), - model, arc_vars, solution); + sat::SolveRoutingModel(cp_model, model->RemainingTime(), + model->GetMutableCPSatInterrupt(), + search_parameters, observer), + objective, *model, arc_vars, solution); } } // namespace operations_research diff --git a/ortools/constraint_solver/routing_search.cc b/ortools/constraint_solver/routing_search.cc index 06c6a80d74..f09e4078f5 100644 --- a/ortools/constraint_solver/routing_search.cc +++ b/ortools/constraint_solver/routing_search.cc @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -41,11 +40,11 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/flags/flag.h" +#include "absl/log/check.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "ortools/base/adjustable_priority_queue.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/map_util.h" #include "ortools/base/stl_util.h" #include "ortools/base/types.h" @@ -545,24 +544,21 @@ CheapestInsertionFilteredHeuristic::ComputeStartEndDistanceForVehicles( if (Contains(node)) continue; std::vector& start_end_distances = start_end_distances_per_node[node]; - const IntVar* const vehicle_var = model()->VehicleVar(node); - const int64_t num_allowed_vehicles = vehicle_var->Size(); - const auto add_distance = [this, node, num_allowed_vehicles, - &start_end_distances](int vehicle) { + const auto add_distance = [this, node, &start_end_distances](int vehicle) { const int64_t start = model()->Start(vehicle); const int64_t end = model()->End(vehicle); - // We compute the distance of node to the start/end nodes of the route. const int64_t distance = CapAdd(model()->GetArcCostForVehicle(start, node, vehicle), model()->GetArcCostForVehicle(node, end, vehicle)); - start_end_distances.push_back({num_allowed_vehicles, distance, vehicle}); + start_end_distances.push_back({distance, vehicle}); }; // Iterating over an IntVar domain is faster than calling Contains. // Therefore we iterate on 'vehicles' only if it's smaller than the domain // size of the VehicleVar. - if (num_allowed_vehicles < vehicles.size()) { + const IntVar* const vehicle_var = model()->VehicleVar(node); + if (vehicle_var->Size() < vehicles.size()) { std::unique_ptr it( vehicle_var->MakeDomainIterator(false)); for (const int64_t vehicle : InitAndGetValues(it.get())) { @@ -586,24 +582,33 @@ CheapestInsertionFilteredHeuristic::ComputeStartEndDistanceForVehicles( return start_end_distances_per_node; } -template -void CheapestInsertionFilteredHeuristic::InitializePriorityQueue( +void CheapestInsertionFilteredHeuristic::AddSeedToQueue( + int node, std::vector* start_end_distances, SeedQueue* sq) { + if (start_end_distances->empty()) { + return; + } + + // Put the best StartEndValue for this node in the priority queue. + StartEndValue& start_end_value = start_end_distances->back(); + if (sq->prioritize_farthest_nodes) { + start_end_value.distance = CapOpp(start_end_value.distance); + } + const uint64_t num_allowed_vehicles = model()->VehicleVar(node)->Size(); + const int64_t neg_penalty = CapOpp(model()->UnperformedPenalty(node)); + sq->priority_queue.push( + {num_allowed_vehicles, neg_penalty, start_end_value, node}); + start_end_distances->pop_back(); +} + +void CheapestInsertionFilteredHeuristic::InitializeSeedQueue( std::vector>* start_end_distances_per_node, - Queue* priority_queue) { + SeedQueue* sq) { const int num_nodes = model()->Size(); DCHECK_EQ(start_end_distances_per_node->size(), num_nodes); for (int node = 0; node < num_nodes; node++) { if (Contains(node)) continue; - std::vector& start_end_distances = - (*start_end_distances_per_node)[node]; - if (start_end_distances.empty()) { - continue; - } - // Put the best StartEndValue for this node in the priority queue. - const StartEndValue& start_end_value = start_end_distances.back(); - priority_queue->push(std::make_pair(start_end_value, node)); - start_end_distances.pop_back(); + AddSeedToQueue(node, &start_end_distances_per_node->at(node), sq); } } @@ -1356,9 +1361,8 @@ bool GlobalCheapestInsertionFilteredHeuristic::SequentialInsertNodes( std::vector> start_end_distances_per_node = ComputeStartEndDistanceForVehicles(unused_vehicles); - std::priority_queue, std::greater> - first_node_queue; - InitializePriorityQueue(&start_end_distances_per_node, &first_node_queue); + SeedQueue first_node_queue(/*prioritize_farthest_nodes=*/false); + InitializeSeedQueue(&start_end_distances_per_node, &first_node_queue); int vehicle = InsertSeedNode(&start_end_distances_per_node, &first_node_queue, &is_vehicle_used); @@ -1429,8 +1433,8 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertFarthestNodesAsSeeds() { // Priority queue where the Seeds with a larger distance are given higher // priority. - std::priority_queue farthest_node_queue; - InitializePriorityQueue(&start_end_distances_per_node, &farthest_node_queue); + SeedQueue farthest_node_queue(/*prioritize_farthest_nodes=*/true); + InitializeSeedQueue(&start_end_distances_per_node, &farthest_node_queue); int inserted_seeds = 0; while (inserted_seeds++ < num_seeds) { @@ -1449,16 +1453,16 @@ void GlobalCheapestInsertionFilteredHeuristic::InsertFarthestNodesAsSeeds() { [this](int vehicle) { return !VehicleIsEmpty(vehicle); }); } -template int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode( std::vector>* start_end_distances_per_node, - Queue* priority_queue, std::vector* is_vehicle_used) { - while (!priority_queue->empty()) { + SeedQueue* sq, std::vector* is_vehicle_used) { + auto& priority_queue = sq->priority_queue; + while (!priority_queue.empty()) { if (StopSearch()) return -1; - const Seed& seed = priority_queue->top(); - - const int seed_node = seed.second; - const int seed_vehicle = seed.first.vehicle; + const Seed& seed = priority_queue.top(); + const int seed_node = seed.node; + const int seed_vehicle = seed.start_end_value.vehicle; + priority_queue.pop(); std::vector& other_start_end_values = (*start_end_distances_per_node)[seed_node]; @@ -1466,7 +1470,6 @@ int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode( if (Contains(seed_node)) { // The node is already inserted, it is therefore no longer considered as // a potential seed. - priority_queue->pop(); other_start_end_values.clear(); continue; } @@ -1477,7 +1480,6 @@ int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode( DCHECK_EQ(Value(start), end); InsertBetween(seed_node, start, end, seed_vehicle); if (Evaluate(/*commit=*/true).has_value()) { - priority_queue->pop(); (*is_vehicle_used)[seed_vehicle] = true; other_start_end_values.clear(); SetVehicleIndex(seed_node, seed_vehicle); @@ -1485,15 +1487,9 @@ int GlobalCheapestInsertionFilteredHeuristic::InsertSeedNode( } } // Either the vehicle is already used, or the Commit() wasn't successful. - // In both cases, we remove this Seed from the priority queue, and insert - // the next StartEndValue from start_end_distances_per_node[seed_node] - // in the priority queue. - priority_queue->pop(); - if (!other_start_end_values.empty()) { - const StartEndValue& next_seed_value = other_start_end_values.back(); - priority_queue->push(std::make_pair(next_seed_value, seed_node)); - other_start_end_values.pop_back(); - } + // In both cases, we insert the next StartEndValue from + // start_end_distances_per_node[seed_node] in the priority queue. + AddSeedToQueue(seed_node, &other_start_end_values, sq); } // No seed node was inserted. return -1; @@ -2332,24 +2328,39 @@ void LocalCheapestInsertionFilteredHeuristic::InsertBestPairMultitour( }; // Fills value field of all insertions, kint64max if unevaluable. - auto price_insertion_sequences_evaluator = [this]() { - for (InsertionSequence sequence : insertion_container_) { - int64_t sequence_cost = 0; - int previous_node = -1; - int previous_succ = -1; - for (const Insertion& insertion : sequence) { - const int succ = previous_node == insertion.pred - ? previous_succ - : Value(insertion.pred); - const int64_t cost = GetInsertionCostForNodeAtPosition( - insertion.node, insertion.pred, succ, sequence.Vehicle()); - sequence_cost = CapAdd(sequence_cost, cost); - previous_node = insertion.node; - previous_succ = succ; - } - sequence.Cost() = sequence_cost; - } - }; + auto price_insertion_sequences_evaluator = + [this](BinCapacities* bin_capacities) { + for (InsertionSequence sequence : insertion_container_) { + int64_t sequence_cost = 0; + int previous_node = -1; + int previous_succ = -1; + for (const Insertion& insertion : sequence) { + const int succ = previous_node == insertion.pred + ? previous_succ + : Value(insertion.pred); + const int64_t cost = GetInsertionCostForNodeAtPosition( + insertion.node, insertion.pred, succ, sequence.Vehicle()); + sequence_cost = CapAdd(sequence_cost, cost); + previous_node = insertion.node; + previous_succ = succ; + } + sequence.Cost() = sequence_cost; + } + if (bin_capacities == nullptr) return; + for (InsertionSequence sequence : insertion_container_) { + const int64_t old_cost = bin_capacities->TotalCost(); + for (const Insertion& insertion : sequence) { + bin_capacities->AddItemToBin(insertion.node, sequence.Vehicle()); + } + const int64_t new_cost = bin_capacities->TotalCost(); + const int64_t delta_cost = CapSub(new_cost, old_cost); + sequence.Cost() = CapAdd(sequence.Cost(), delta_cost); + for (const Insertion& insertion : sequence) { + bin_capacities->RemoveItemFromBin(insertion.node, + sequence.Vehicle()); + } + } + }; auto price_insertion_sequences_no_evaluator = [this]() { for (InsertionSequence sequence : insertion_container_) { @@ -2391,7 +2402,7 @@ void LocalCheapestInsertionFilteredHeuristic::InsertBestPairMultitour( if (evaluator_ == nullptr) { price_insertion_sequences_no_evaluator(); } else { - price_insertion_sequences_evaluator(); + price_insertion_sequences_evaluator(bin_capacities_); } if (StopSearch()) return; insertion_container_.RemoveIf( @@ -2444,32 +2455,47 @@ bool LocalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() { model()->GetPickupAndDeliveryPairs(); // Sort pairs according to number of possible vehicles. struct PairDomainSize { - uint64_t domain_size; + uint64_t num_allowed_vehicles; + int64_t neg_penalty; int pair_index; bool operator<(const PairDomainSize& other) const { - return std::tie(domain_size, pair_index) < - std::tie(other.domain_size, other.pair_index); + return std::tie(num_allowed_vehicles, neg_penalty, pair_index) < + std::tie(other.num_allowed_vehicles, other.neg_penalty, + other.pair_index); } }; std::vector pair_domain_sizes; + absl::flat_hash_set pair_nodes; for (int pair_index = 0; pair_index < index_pairs.size(); ++pair_index) { bool pickup_is_contained = false; uint64_t domain_size = std::numeric_limits::max(); + int64_t pickup_penalty = 0; for (int64_t pickup : index_pairs[pair_index].first) { domain_size = std::min(domain_size, model()->VehicleVar(pickup)->Size()); + pickup_penalty = + std::max(pickup_penalty, model()->UnperformedPenalty(pickup)); pickup_is_contained |= Contains(pickup); } bool delivery_is_contained = false; + int64_t delivery_penalty = 0; for (int64_t delivery : index_pairs[pair_index].second) { domain_size = std::min(domain_size, model()->VehicleVar(delivery)->Size()); + delivery_penalty = + std::max(delivery_penalty, model()->UnperformedPenalty(delivery)); delivery_is_contained |= Contains(delivery); } if (pickup_is_contained && delivery_is_contained) { SetIndexPairVisited(index_pairs[pair_index]); } else if (!pickup_is_contained && !delivery_is_contained) { - pair_domain_sizes.push_back({domain_size, pair_index}); + pair_domain_sizes.push_back( + {domain_size, CapOpp(CapAdd(pickup_penalty, delivery_penalty)), + pair_index}); + pair_nodes.insert(index_pairs[pair_index].first.begin(), + index_pairs[pair_index].first.end()); + pair_nodes.insert(index_pairs[pair_index].second.begin(), + index_pairs[pair_index].second.end()); } } std::sort(pair_domain_sizes.begin(), pair_domain_sizes.end()); @@ -2500,50 +2526,76 @@ bool LocalCheapestInsertionFilteredHeuristic::BuildSolutionInternal() { } } - // Try to insert each pair by increasing amount of its possible vehicles. - for (const PairDomainSize& pair_domain_size : pair_domain_sizes) { - const auto index_pair = index_pairs[pair_domain_size.pair_index]; - switch (pair_insertion_strategy_) { - case RoutingSearchParameters::AUTOMATIC: - case RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR: - InsertBestPair(index_pair); - break; - case RoutingSearchParameters::BEST_PICKUP_THEN_BEST_DELIVERY: - InsertBestPickupThenDelivery(index_pair); - break; - case RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR_MULTITOUR: - InsertBestPairMultitour(index_pair, node_is_pickup, node_is_delivery); - break; - default: - LOG(ERROR) << "Unknown pair insertion strategy value."; - break; - } - if (StopSearch()) { - return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value(); - } - SetIndexPairVisited(index_pair); - } + // TODO(user): A priority queue is not actually needed here as we only use + // the "Seed" values to determine the static order of insertion of the nodes. + // This can be done with a simple sorted container, which could also contain + // the data for pairs in the same place to determine the overall order of + // insertion. + SeedQueue sq(/*prioritize_farthest_nodes=*/true); + InitializeSeedQueue(&start_end_distances_per_node_, &sq); + auto& node_queue = sq.priority_queue; - std::priority_queue node_queue; - InitializePriorityQueue(&start_end_distances_per_node_, &node_queue); - - // Possible positions where the current node can be inserted. - while (!node_queue.empty()) { - const int node = node_queue.top().second; - node_queue.pop(); - if (Contains(node) || visited_[node]) continue; - for (const NodeInsertion& insertion : - ComputeEvaluatorSortedPositions(node)) { + // Inserting pairs and single nodes simultaneously, to make sure the most + // critical ones (fewer allowed vehicles and high penalties) get inserted + // first. + // TODO(user): Explore other metrics to evaluate criticality, more directly + // mixing penalties and allowed vehicles (such as a ratio between the two). + int pair_domain_index = 0; + while (pair_domain_index < pair_domain_sizes.size() || !node_queue.empty()) { + bool insert_pair = false; + if (node_queue.empty()) { + insert_pair = true; + } else if (pair_domain_index < pair_domain_sizes.size()) { + const PairDomainSize& pair_domain_size = + pair_domain_sizes[pair_domain_index]; + const Seed& seed_node = node_queue.top(); + insert_pair = + std::tie(pair_domain_size.num_allowed_vehicles, + pair_domain_size.neg_penalty) <= + std::tie(seed_node.num_allowed_vehicles, seed_node.neg_penalty); + } + if (insert_pair) { + const auto index_pair = + index_pairs[pair_domain_sizes[pair_domain_index].pair_index]; + switch (pair_insertion_strategy_) { + case RoutingSearchParameters::AUTOMATIC: + case RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR: + InsertBestPair(index_pair); + break; + case RoutingSearchParameters::BEST_PICKUP_THEN_BEST_DELIVERY: + InsertBestPickupThenDelivery(index_pair); + break; + case RoutingSearchParameters::BEST_PICKUP_DELIVERY_PAIR_MULTITOUR: + InsertBestPairMultitour(index_pair, node_is_pickup, node_is_delivery); + break; + default: + LOG(ERROR) << "Unknown pair insertion strategy value."; + break; + } if (StopSearch()) { return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value(); } - InsertBetween(node, insertion.insert_after, Value(insertion.insert_after), - insertion.vehicle); - if (Evaluate(/*commit=*/true).has_value()) { - if (bin_capacities_) { - bin_capacities_->AddItemToBin(node, insertion.vehicle); + SetIndexPairVisited(index_pair); + pair_domain_index++; + } else { + const int node = node_queue.top().node; + node_queue.pop(); + if (Contains(node) || visited_[node] || pair_nodes.contains(node)) { + continue; + } + for (const NodeInsertion& insertion : + ComputeEvaluatorSortedPositions(node)) { + if (StopSearch()) { + return MakeUnassignedNodesUnperformed() && Evaluate(true).has_value(); + } + InsertBetween(node, insertion.insert_after, + Value(insertion.insert_after), insertion.vehicle); + if (Evaluate(/*commit=*/true).has_value()) { + if (bin_capacities_) { + bin_capacities_->AddItemToBin(node, insertion.vehicle); + } + break; } - break; } } } @@ -2567,8 +2619,22 @@ LocalCheapestInsertionFilteredHeuristic::ComputeEvaluatorSortedPositions( continue; } const int64_t start = model()->Start(vehicle); + const size_t old_num_insertions = sorted_insertions.size(); AppendInsertionPositionsAfter(node, start, Value(start), vehicle, /*ignore_cost=*/false, &sorted_insertions); + if (bin_capacities_ && evaluator_) { + // Compute cost incurred from soft capacities. + const int64_t old_cost = bin_capacities_->TotalCost(); + bin_capacities_->AddItemToBin(node, vehicle); + const int64_t new_cost = bin_capacities_->TotalCost(); + bin_capacities_->RemoveItemFromBin(node, vehicle); + const int64_t delta_cost = CapSub(new_cost, old_cost); + // Add soft cost to new insertions. + for (size_t i = old_num_insertions; i < sorted_insertions.size(); ++i) { + sorted_insertions[i].value = + CapAdd(sorted_insertions[i].value, delta_cost); + } + } } std::sort(sorted_insertions.begin(), sorted_insertions.end()); return sorted_insertions; @@ -2628,15 +2694,23 @@ LocalCheapestInsertionFilteredHeuristic::ComputeEvaluatorSortedPairPositions( vehicle}); } } else { - sorted_pickup_delivery_insertions.push_back( - {insert_pickup_after, insert_delivery_after, - CapAdd(GetInsertionCostForNodeAtPosition( - pickup, insert_pickup_after, insert_pickup_before, - vehicle), - GetInsertionCostForNodeAtPosition( - delivery, insert_delivery_after, - insert_delivery_before, vehicle)), - vehicle}); + const int64_t pickup_cost = GetInsertionCostForNodeAtPosition( + pickup, insert_pickup_after, insert_pickup_before, vehicle); + const int64_t delivery_cost = GetInsertionCostForNodeAtPosition( + delivery, insert_delivery_after, insert_delivery_before, vehicle); + int64_t total_cost = CapAdd(pickup_cost, delivery_cost); + if (bin_capacities_) { + const int64_t old_cost = bin_capacities_->TotalCost(); + bin_capacities_->AddItemToBin(pickup, vehicle); + bin_capacities_->AddItemToBin(delivery, vehicle); + const int64_t new_cost = bin_capacities_->TotalCost(); + total_cost = CapAdd(total_cost, CapSub(new_cost, old_cost)); + bin_capacities_->RemoveItemFromBin(pickup, vehicle); + bin_capacities_->RemoveItemFromBin(delivery, vehicle); + } + sorted_pickup_delivery_insertions.push_back({insert_pickup_after, + insert_delivery_after, + total_cost, vehicle}); } insert_delivery_after = insert_delivery_before; } @@ -4608,6 +4682,11 @@ class GuidedSlackFinalizer : public DecisionBuilder { public: GuidedSlackFinalizer(const RoutingDimension* dimension, RoutingModel* model, std::function initializer); + + // This type is neither copyable nor movable. + GuidedSlackFinalizer(const GuidedSlackFinalizer&) = delete; + GuidedSlackFinalizer& operator=(const GuidedSlackFinalizer&) = delete; + Decision* Next(Solver* solver) override; private: @@ -4622,8 +4701,6 @@ class GuidedSlackFinalizer : public DecisionBuilder { Rev current_index_; Rev current_route_; RevArray last_delta_used_; - - DISALLOW_COPY_AND_ASSIGN(GuidedSlackFinalizer); }; GuidedSlackFinalizer::GuidedSlackFinalizer( @@ -4769,6 +4846,10 @@ class GreedyDescentLSOperator : public LocalSearchOperator { public: explicit GreedyDescentLSOperator(std::vector variables); + // This type is neither copyable nor movable. + GreedyDescentLSOperator(const GreedyDescentLSOperator&) = delete; + GreedyDescentLSOperator& operator=(const GreedyDescentLSOperator&) = delete; + bool MakeNextNeighbor(Assignment* delta, Assignment* deltadelta) override; void Start(const Assignment* assignment) override; @@ -4785,8 +4866,6 @@ class GreedyDescentLSOperator : public LocalSearchOperator { // (0, ... 0, current_step_), (0, ... 0, -current_step_). // current_direction_ keeps track what was the last returned delta. int64_t current_direction_; - - DISALLOW_COPY_AND_ASSIGN(GreedyDescentLSOperator); }; GreedyDescentLSOperator::GreedyDescentLSOperator(std::vector variables) diff --git a/ortools/constraint_solver/routing_search.h b/ortools/constraint_solver/routing_search.h index 1d7fe81023..7dfc0bb74a 100644 --- a/ortools/constraint_solver/routing_search.h +++ b/ortools/constraint_solver/routing_search.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -34,8 +35,8 @@ #include "absl/container/flat_hash_set.h" #include "absl/log/check.h" +#include "absl/types/span.h" #include "ortools/base/adjustable_priority_queue.h" -#include "ortools/base/macros.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/constraint_solveri.h" #include "ortools/constraint_solver/routing.h" @@ -330,37 +331,62 @@ class CheapestInsertionFilteredHeuristic : public RoutingFilteredHeuristic { } }; struct StartEndValue { - int64_t num_allowed_vehicles; int64_t distance; int vehicle; bool operator<(const StartEndValue& other) const { - return std::tie(num_allowed_vehicles, distance, vehicle) < - std::tie(other.num_allowed_vehicles, other.distance, - other.vehicle); + return std::tie(distance, vehicle) < + std::tie(other.distance, other.vehicle); } }; - typedef std::pair Seed; + struct Seed { + uint64_t num_allowed_vehicles; + int64_t neg_penalty; + StartEndValue start_end_value; + int node; + + bool operator>(const Seed& other) const { + return std::tie(num_allowed_vehicles, neg_penalty, start_end_value, + node) > std::tie(other.num_allowed_vehicles, + other.neg_penalty, other.start_end_value, + other.node); + } + }; + // clang-format off + struct SeedQueue { + explicit SeedQueue(bool prioritize_farthest_nodes) : + prioritize_farthest_nodes(prioritize_farthest_nodes) {} + + /// By default, the priority is given (hierarchically) to nodes with lower + /// number of allowed vehicles, higher penalty and lower start/end distance. + std::priority_queue, std::greater > + priority_queue; + /// When 'prioritize_farthest_nodes' is true, the start/end distance is + /// inverted so higher priority is given to farther nodes. + const bool prioritize_farthest_nodes; + }; /// Computes and returns the distance of each uninserted node to every vehicle - /// in "vehicles" as a std::vector>, + /// in 'vehicles' as a std::vector>, /// start_end_distances_per_node. /// For each node, start_end_distances_per_node[node] is sorted in decreasing /// order. - // clang-format off std::vector > ComputeStartEndDistanceForVehicles(const std::vector& vehicles); - /// Initializes the priority_queue by inserting the best entry corresponding + /// Initializes sq->priority_queue by inserting the best entry corresponding /// to each node, i.e. the last element of start_end_distances_per_node[node], /// which is supposed to be sorted in decreasing order. - /// Queue is a priority queue containing Seeds. - template - void InitializePriorityQueue( + void InitializeSeedQueue( std::vector >* start_end_distances_per_node, - Queue* priority_queue); + SeedQueue* sq); // clang-format on + /// Adds a Seed corresponding to the given 'node' to sq.priority_queue, based + /// on the last entry in its 'start_end_distances' (from which it's deleted). + void AddSeedToQueue(int node, std::vector* start_end_distances, + SeedQueue* sq); + /// Inserts 'node' just after 'predecessor', and just before 'successor' on /// the route of 'vehicle', resulting in the following subsequence: /// predecessor -> node -> successor. @@ -625,10 +651,9 @@ class GlobalCheapestInsertionFilteredHeuristic /// used to insert a new entry for that node if necessary (next best vehicle). /// If a seed node is successfully inserted, updates is_vehicle_used and /// returns the vehice of the corresponding route. Returns -1 otherwise. - template int InsertSeedNode( std::vector>* start_end_distances_per_node, - Queue* priority_queue, std::vector* is_vehicle_used); + SeedQueue* sq, std::vector* is_vehicle_used); // clang-format on /// Initializes the priority queue and the pair entries for the given pair @@ -932,7 +957,7 @@ class InsertionSequenceContainer { // Adds an insertion sequence to the container. void AddInsertionSequence(int vehicle, - const std::vector& insertion_sequence) { + absl::Span insertion_sequence) { insertion_bounds_.push_back( {.begin = insertions_.size(), .end = insertions_.size() + insertion_sequence.size(), @@ -1382,6 +1407,11 @@ class SweepArranger { public: explicit SweepArranger( const std::vector>& points); + + // This type is neither copyable nor movable. + SweepArranger(const SweepArranger&) = delete; + SweepArranger& operator=(const SweepArranger&) = delete; + virtual ~SweepArranger() {} void ArrangeIndices(std::vector* indices); void SetSectors(int sectors) { sectors_ = sectors; } @@ -1389,8 +1419,6 @@ class SweepArranger { private: std::vector coordinates_; int sectors_; - - DISALLOW_COPY_AND_ASSIGN(SweepArranger); }; #endif // SWIG diff --git a/ortools/constraint_solver/samples/vrp_initial_routes.cc b/ortools/constraint_solver/samples/vrp_initial_routes.cc index ecb4c056ec..19af4a85c6 100644 --- a/ortools/constraint_solver/samples/vrp_initial_routes.cc +++ b/ortools/constraint_solver/samples/vrp_initial_routes.cc @@ -15,10 +15,15 @@ // [START import] #include #include +#include #include #include +#include "google/protobuf/duration.pb.h" +#include "ortools/base/logging.h" +#include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/routing.h" +#include "ortools/constraint_solver/routing_enums.pb.h" #include "ortools/constraint_solver/routing_index_manager.h" #include "ortools/constraint_solver/routing_parameters.h" // [END import] @@ -151,11 +156,14 @@ void VrpInitialRoutes() { // Close model with the custom search parameters // [START parameters] RoutingSearchParameters searchParameters = DefaultRoutingSearchParameters(); - searchParameters.set_first_solution_strategy(FirstSolutionStrategy_Value_PATH_CHEAPEST_ARC); - searchParameters.set_local_search_metaheuristic(LocalSearchMetaheuristic::GUIDED_LOCAL_SEARCH); + searchParameters.set_first_solution_strategy( + FirstSolutionStrategy::PATH_CHEAPEST_ARC); + searchParameters.set_local_search_metaheuristic( + LocalSearchMetaheuristic::GUIDED_LOCAL_SEARCH); searchParameters.mutable_time_limit()->set_seconds(5); - // When an initial solution is given for search, the model will be closed with the default - // search parameters unless it is explicitly closed with the custom search parameters. + // When an initial solution is given for search, the model will be closed with + // the default search parameters unless it is explicitly closed with the + // custom search parameters. routing.CloseModelWithParameters(searchParameters); // [END parameters] diff --git a/ortools/constraint_solver/samples/vrp_initial_routes.py b/ortools/constraint_solver/samples/vrp_initial_routes.py index 065bc9e1d8..9b6906f67a 100755 --- a/ortools/constraint_solver/samples/vrp_initial_routes.py +++ b/ortools/constraint_solver/samples/vrp_initial_routes.py @@ -48,10 +48,12 @@ def create_data_model(): ] # [START initial_routes] data["initial_routes"] = [ - [8, 16, 14, 13, 12, 11], - [3, 4, 9, 10], - [15, 1], - [7, 5, 2, 6], + # fmt: off + [8, 16, 14, 13, 12, 11], + [3, 4, 9, 10], + [15, 1], + [7, 5, 2, 6], + # fmt: on ] # [END initial_routes] data["num_vehicles"] = 4 @@ -81,7 +83,6 @@ def print_solution(data, manager, routing, solution): print(plan_output) max_route_distance = max(route_distance, max_route_distance) print(f"Maximum of the route distances: {max_route_distance}m") - # [END solution_printer] @@ -139,12 +140,15 @@ def main(): # [START parameters] search_parameters = pywrapcp.DefaultRoutingSearchParameters() search_parameters.first_solution_strategy = ( - routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) + routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC + ) search_parameters.local_search_metaheuristic = ( - routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) + routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH + ) search_parameters.time_limit.FromSeconds(5) - # When an initial solution is given for search, the model will be closed with the default - # search parameters unless it is explicitly closed with the custom search parameters. + # When an initial solution is given for search, the model will be closed with + # the default search parameters unless it is explicitly closed with the custom + # search parameters. routing.CloseModelWithParameters(search_parameters) # [END parameters] diff --git a/ortools/constraint_solver/samples/vrp_resources.py b/ortools/constraint_solver/samples/vrp_resources.py index 3ad160814f..f037130b4a 100755 --- a/ortools/constraint_solver/samples/vrp_resources.py +++ b/ortools/constraint_solver/samples/vrp_resources.py @@ -188,7 +188,7 @@ def main(): # [END depot_load_time] # [START depot_capacity] - depot_usage = [1 for i in range(len(intervals))] + depot_usage = [1 for _ in range(len(intervals))] solver.Add( solver.Cumulative(intervals, depot_usage, data["depot_capacity"], "depot") ) diff --git a/ortools/constraint_solver/sched_expr.cc b/ortools/constraint_solver/sched_expr.cc index df50c1d93e..c0bfb0d3ec 100644 --- a/ortools/constraint_solver/sched_expr.cc +++ b/ortools/constraint_solver/sched_expr.cc @@ -17,7 +17,6 @@ #include "absl/strings/str_format.h" #include "ortools/base/logging.h" -#include "ortools/base/macros.h" #include "ortools/base/types.h" #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/constraint_solveri.h" @@ -32,6 +31,10 @@ class IntervalVarStartExpr : public BaseIntExpr { public: explicit IntervalVarStartExpr(IntervalVar* const i) : BaseIntExpr(i->solver()), interval_(i) {} + + // This type is neither copyable nor movable. + IntervalVarStartExpr(const IntervalVarStartExpr&) = delete; + IntervalVarStartExpr& operator=(const IntervalVarStartExpr&) = delete; ~IntervalVarStartExpr() override {} int64_t Min() const override { return interval_->StartMin(); } @@ -66,13 +69,16 @@ class IntervalVarStartExpr : public BaseIntExpr { private: IntervalVar* interval_; - DISALLOW_COPY_AND_ASSIGN(IntervalVarStartExpr); }; class IntervalVarEndExpr : public BaseIntExpr { public: explicit IntervalVarEndExpr(IntervalVar* const i) : BaseIntExpr(i->solver()), interval_(i) {} + + // This type is neither copyable nor movable. + IntervalVarEndExpr(const IntervalVarEndExpr&) = delete; + IntervalVarEndExpr& operator=(const IntervalVarEndExpr&) = delete; ~IntervalVarEndExpr() override {} int64_t Min() const override { return interval_->EndMin(); } @@ -105,13 +111,16 @@ class IntervalVarEndExpr : public BaseIntExpr { private: IntervalVar* interval_; - DISALLOW_COPY_AND_ASSIGN(IntervalVarEndExpr); }; class IntervalVarDurationExpr : public BaseIntExpr { public: explicit IntervalVarDurationExpr(IntervalVar* const i) : BaseIntExpr(i->solver()), interval_(i) {} + + // This type is neither copyable nor movable. + IntervalVarDurationExpr(const IntervalVarDurationExpr&) = delete; + IntervalVarDurationExpr& operator=(const IntervalVarDurationExpr&) = delete; ~IntervalVarDurationExpr() override {} int64_t Min() const override { return interval_->DurationMin(); } @@ -146,7 +155,6 @@ class IntervalVarDurationExpr : public BaseIntExpr { private: IntervalVar* interval_; - DISALLOW_COPY_AND_ASSIGN(IntervalVarDurationExpr); }; } // namespace diff --git a/ortools/constraint_solver/search.cc b/ortools/constraint_solver/search.cc index 394f413a84..b0df855708 100644 --- a/ortools/constraint_solver/search.cc +++ b/ortools/constraint_solver/search.cc @@ -24,6 +24,10 @@ #include #include +#include "absl/container/flat_hash_map.h" +#include "absl/flags/flag.h" +#include "absl/log/check.h" +#include "absl/random/distributions.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" @@ -36,6 +40,8 @@ #include "ortools/constraint_solver/constraint_solver.h" #include "ortools/constraint_solver/constraint_solveri.h" #include "ortools/constraint_solver/search_limit.pb.h" +#include "ortools/util/bitset.h" +#include "ortools/util/saturated_arithmetic.h" #include "ortools/util/string_array.h" ABSL_FLAG(bool, cp_use_sparse_gls_penalties, false, @@ -209,7 +215,7 @@ void SearchLog::NoMoreSolutions() { OutputLine(buffer); } -void SearchLog::ApplyDecision(Decision* const decision) { +void SearchLog::ApplyDecision(Decision* const) { Maintain(); const int64_t b = solver()->branches(); if (b % period_ == 0 && b > 0) { @@ -697,7 +703,7 @@ TryDecision::TryDecision(TryDecisionBuilder* const try_builder) TryDecision::~TryDecision() {} -void TryDecision::Apply(Solver* const solver) {} +void TryDecision::Apply(Solver* const) {} void TryDecision::Refute(Solver* const solver) { try_builder_->AdvanceToNextBuilder(solver); @@ -838,7 +844,7 @@ class BaseVariableAssignmentSelector : public BaseObject { // ----- Choose first unbound -- -int64_t ChooseFirstUnbound(Solver* solver, const std::vector& vars, +int64_t ChooseFirstUnbound(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { for (int64_t i = first_unbound; i <= last_unbound; ++i) { if (!vars[i]->Bound()) { @@ -850,7 +856,7 @@ int64_t ChooseFirstUnbound(Solver* solver, const std::vector& vars, // ----- Choose Min Size Lowest Min ----- -int64_t ChooseMinSizeLowestMin(Solver* solver, const std::vector& vars, +int64_t ChooseMinSizeLowestMin(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { uint64_t best_size = std::numeric_limits::max(); int64_t best_min = std::numeric_limits::max(); @@ -871,8 +877,7 @@ int64_t ChooseMinSizeLowestMin(Solver* solver, const std::vector& vars, // ----- Choose Min Size Highest Min ----- -int64_t ChooseMinSizeHighestMin(Solver* solver, - const std::vector& vars, +int64_t ChooseMinSizeHighestMin(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { uint64_t best_size = std::numeric_limits::max(); int64_t best_min = std::numeric_limits::min(); @@ -893,7 +898,7 @@ int64_t ChooseMinSizeHighestMin(Solver* solver, // ----- Choose Min Size Lowest Max ----- -int64_t ChooseMinSizeLowestMax(Solver* solver, const std::vector& vars, +int64_t ChooseMinSizeLowestMax(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { uint64_t best_size = std::numeric_limits::max(); int64_t best_max = std::numeric_limits::max(); @@ -914,8 +919,7 @@ int64_t ChooseMinSizeLowestMax(Solver* solver, const std::vector& vars, // ----- Choose Min Size Highest Max ----- -int64_t ChooseMinSizeHighestMax(Solver* solver, - const std::vector& vars, +int64_t ChooseMinSizeHighestMax(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { uint64_t best_size = std::numeric_limits::max(); int64_t best_max = std::numeric_limits::min(); @@ -936,7 +940,7 @@ int64_t ChooseMinSizeHighestMax(Solver* solver, // ----- Choose Lowest Min -- -int64_t ChooseLowestMin(Solver* solver, const std::vector& vars, +int64_t ChooseLowestMin(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { int64_t best_min = std::numeric_limits::max(); int64_t best_index = -1; @@ -954,7 +958,7 @@ int64_t ChooseLowestMin(Solver* solver, const std::vector& vars, // ----- Choose Highest Max ----- -int64_t ChooseHighestMax(Solver* solver, const std::vector& vars, +int64_t ChooseHighestMax(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { int64_t best_max = std::numeric_limits::min(); int64_t best_index = -1; @@ -972,7 +976,7 @@ int64_t ChooseHighestMax(Solver* solver, const std::vector& vars, // ----- Choose Lowest Size -- -int64_t ChooseMinSize(Solver* solver, const std::vector& vars, +int64_t ChooseMinSize(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { uint64_t best_size = std::numeric_limits::max(); int64_t best_index = -1; @@ -990,7 +994,7 @@ int64_t ChooseMinSize(Solver* solver, const std::vector& vars, // ----- Choose Highest Size ----- -int64_t ChooseMaxSize(Solver* solver, const std::vector& vars, +int64_t ChooseMaxSize(Solver*, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { uint64_t best_size = 0; int64_t best_index = -1; @@ -1034,7 +1038,7 @@ class HighestRegretSelectorOnMin : public BaseObject { std::vector iterators_; }; -int64_t HighestRegretSelectorOnMin::Choose(Solver* const s, +int64_t HighestRegretSelectorOnMin::Choose(Solver* const, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { @@ -1083,7 +1087,7 @@ class CheapestVarSelector : public BaseObject { std::function var_evaluator_; }; -int64_t CheapestVarSelector::Choose(Solver* const s, +int64_t CheapestVarSelector::Choose(Solver* const, const std::vector& vars, int64_t first_unbound, int64_t last_unbound) { @@ -1108,8 +1112,7 @@ class PathSelector : public BaseObject { public: PathSelector() : first_(std::numeric_limits::max()) {} ~PathSelector() override{}; - int64_t Choose(Solver* s, const std::vector& vars, - int64_t first_unbound, int64_t last_unbound); + int64_t Choose(Solver* s, const std::vector& vars); std::string DebugString() const override { return "ChooseNextOnPath"; } private: @@ -1119,8 +1122,8 @@ class PathSelector : public BaseObject { Rev first_; }; -int64_t PathSelector::Choose(Solver* const s, const std::vector& vars, - int64_t first_unbound, int64_t last_unbound) { +int64_t PathSelector::Choose(Solver* const s, + const std::vector& vars) { int64_t index = first_.Value(); if (!UpdateIndex(vars, &index)) { return -1; @@ -1197,15 +1200,15 @@ bool PathSelector::FindPathStart(const std::vector& vars, // ----- Select min ----- -int64_t SelectMinValue(const IntVar* v, int64_t id) { return v->Min(); } +int64_t SelectMinValue(const IntVar* v, int64_t) { return v->Min(); } // ----- Select max ----- -int64_t SelectMaxValue(const IntVar* v, int64_t id) { return v->Max(); } +int64_t SelectMaxValue(const IntVar* v, int64_t) { return v->Max(); } // ----- Select random ----- -int64_t SelectRandomValue(const IntVar* v, int64_t id) { +int64_t SelectRandomValue(const IntVar* v, int64_t) { const uint64_t span = v->Max() - v->Min() + 1; if (span > absl::GetFlag(FLAGS_cp_large_domain_no_splitting_limit)) { // Do not create holes in large domains. @@ -1248,7 +1251,7 @@ int64_t SelectRandomValue(const IntVar* v, int64_t id) { // ----- Select center ----- -int64_t SelectCenterValue(const IntVar* v, int64_t id) { +int64_t SelectCenterValue(const IntVar* v, int64_t) { const int64_t vmin = v->Min(); const int64_t vmax = v->Max(); if (vmax - vmin > absl::GetFlag(FLAGS_cp_large_domain_no_splitting_limit)) { @@ -1273,7 +1276,7 @@ int64_t SelectCenterValue(const IntVar* v, int64_t id) { // ----- Select center ----- -int64_t SelectSplitValue(const IntVar* v, int64_t id) { +int64_t SelectSplitValue(const IntVar* v, int64_t) { const int64_t vmin = v->Min(); const int64_t vmax = v->Max(); const uint64_t delta = vmax - vmin; @@ -1444,7 +1447,7 @@ DynamicEvaluatorSelector::DynamicEvaluatorSelector( first_(-1), tie_breaker_(std::move(tie_breaker)) {} -int64_t DynamicEvaluatorSelector::SelectValue(const IntVar* var, int64_t id) { +int64_t DynamicEvaluatorSelector::SelectValue(const IntVar*, int64_t) { return cache_[first_].value; } @@ -1530,7 +1533,7 @@ StaticEvaluatorSelector::StaticEvaluatorSelector( comp_(evaluator), first_(-1) {} -int64_t StaticEvaluatorSelector::SelectValue(const IntVar* var, int64_t id) { +int64_t StaticEvaluatorSelector::SelectValue(const IntVar*, int64_t) { return elements_[first_].value; } @@ -1600,9 +1603,9 @@ std::string AssignOneVariableValue::DebugString() const { value_, var_->DebugString(), value_); } -void AssignOneVariableValue::Apply(Solver* const s) { var_->SetValue(value_); } +void AssignOneVariableValue::Apply(Solver* const) { var_->SetValue(value_); } -void AssignOneVariableValue::Refute(Solver* const s) { +void AssignOneVariableValue::Refute(Solver* const) { var_->RemoveValue(value_); } } // namespace @@ -1638,7 +1641,7 @@ std::string AssignOneVariableValueOrFail::DebugString() const { return absl::StrFormat("[%s == %d] or fail", var_->DebugString(), value_); } -void AssignOneVariableValueOrFail::Apply(Solver* const s) { +void AssignOneVariableValueOrFail::Apply(Solver* const) { var_->SetValue(value_); } @@ -1658,8 +1661,8 @@ class AssignOneVariableValueDoNothing : public Decision { AssignOneVariableValueDoNothing(IntVar* const v, int64_t value) : var_(v), value_(value) {} ~AssignOneVariableValueDoNothing() override {} - void Apply(Solver* const s) override { var_->SetValue(value_); } - void Refute(Solver* const s) override {} + void Apply(Solver* const) override { var_->SetValue(value_); } + void Refute(Solver* const) override {} std::string DebugString() const override { return absl::StrFormat("[%s == %d] or []", var_->DebugString(), value_); } @@ -1711,7 +1714,7 @@ std::string SplitOneVariable::DebugString() const { } } -void SplitOneVariable::Apply(Solver* const s) { +void SplitOneVariable::Apply(Solver* const) { if (start_with_lower_half_) { var_->SetMax(value_); } else { @@ -1719,7 +1722,7 @@ void SplitOneVariable::Apply(Solver* const s) { } } -void SplitOneVariable::Refute(Solver* const s) { +void SplitOneVariable::Refute(Solver* const) { if (start_with_lower_half_) { var_->SetMin(value_ + 1); } else { @@ -1805,7 +1808,7 @@ std::string AssignVariablesValues::DebugString() const { return out; } -void AssignVariablesValues::Apply(Solver* const s) { +void AssignVariablesValues::Apply(Solver* const) { if (vars_.empty()) return; vars_[0]->FreezeQueue(); for (int i = 0; i < vars_.size(); ++i) { @@ -1918,10 +1921,8 @@ class BaseAssignVariables : public DecisionBuilder { } case Solver::CHOOSE_PATH: { PathSelector* const selector = s->RevAlloc(new PathSelector()); - return [selector](Solver* solver, const std::vector& vars, - int first_unbound, int last_unbound) { - return selector->Choose(solver, vars, first_unbound, last_unbound); - }; + return [selector](Solver* solver, const std::vector& vars, int, + int) { return selector->Choose(solver, vars); }; } default: LOG(FATAL) << "Unknown int var strategy " << str; @@ -1930,7 +1931,7 @@ class BaseAssignVariables : public DecisionBuilder { } static Solver::VariableValueSelector MakeValueSelector( - Solver* const s, Solver::IntValueStrategy val_str) { + Solver* const, Solver::IntValueStrategy val_str) { switch (val_str) { case Solver::INT_VALUE_DEFAULT: case Solver::INT_VALUE_SIMPLE: @@ -2450,8 +2451,14 @@ Assignment* SolutionCollector::solution(int n) const { return solution_data_[n].solution; } +Assignment* SolutionCollector::last_solution_or_null() const { + return solution_data_.empty() ? nullptr : solution_data_.back().solution; +} + int SolutionCollector::solution_count() const { return solution_data_.size(); } +bool SolutionCollector::has_solution() const { return !solution_data_.empty(); } + int64_t SolutionCollector::wall_time(int n) const { check_index(n); return solution_data_[n].time; @@ -2755,7 +2762,7 @@ class NBestValueSolutionCollector : public SolutionCollector { void Install() override; std::string DebugString() const override; - public: + private: void Clear(); const std::vector maximize_; @@ -2992,6 +2999,46 @@ bool ObjectiveMonitor::AtSolution() { return true; } +bool ObjectiveMonitor::AcceptDelta(Assignment* delta, Assignment*) { + if (delta == nullptr) return true; + const bool delta_has_objective = delta->HasObjective(); + if (!delta_has_objective) { + delta->AddObjectives(objective_vars()); + } + const Assignment* const local_search_state = + solver()->GetOrCreateLocalSearchState(); + for (int i = 0; i < Size(); ++i) { + if (delta->ObjectiveFromIndex(i) == ObjectiveVar(i)) { + if (Maximize(i)) { + int64_t obj_min = ObjectiveVar(i)->Min(); + if (delta_has_objective) { + obj_min = std::max(obj_min, delta->ObjectiveMinFromIndex(i)); + } + if (solver()->UseFastLocalSearch() && + i < local_search_state->NumObjectives()) { + obj_min = std::max( + obj_min, + CapAdd(local_search_state->ObjectiveMinFromIndex(i), Step(i))); + } + delta->SetObjectiveMinFromIndex(i, obj_min); + } else { + int64_t obj_max = ObjectiveVar(i)->Max(); + if (delta_has_objective) { + obj_max = std::min(obj_max, delta->ObjectiveMaxFromIndex(i)); + } + if (solver()->UseFastLocalSearch() && + i < local_search_state->NumObjectives()) { + obj_max = std::min( + obj_max, + CapSub(local_search_state->ObjectiveMaxFromIndex(i), Step(i))); + } + delta->SetObjectiveMaxFromIndex(i, obj_max); + } + } + } + return true; +} + void ObjectiveMonitor::Accept(ModelVisitor* const visitor) const { visitor->BeginVisitExtension(ModelVisitor::kObjectiveExtension); visitor->VisitIntegerArrayArgument(ModelVisitor::kStepArgument, steps_); @@ -3024,7 +3071,7 @@ OptimizeVar::OptimizeVar(Solver* solver, const std::vector& maximize, std::vector vars, std::vector steps) : ObjectiveMonitor(solver, maximize, std::move(vars), std::move(steps)) {} -void OptimizeVar::BeginNextDecision(DecisionBuilder* db) { +void OptimizeVar::BeginNextDecision(DecisionBuilder*) { if (solver()->SearchDepth() == 0) { // after a restart. ApplyBound(); } @@ -3037,7 +3084,7 @@ void OptimizeVar::ApplyBound() { } } -void OptimizeVar::RefuteDecision(Decision* d) { ApplyBound(); } +void OptimizeVar::RefuteDecision(Decision*) { ApplyBound(); } bool OptimizeVar::AcceptSolution() { if (!found_initial_solution_) { @@ -3060,45 +3107,6 @@ bool OptimizeVar::AtSolution() { return ObjectiveMonitor::AtSolution(); } -bool OptimizeVar::AcceptDelta(Assignment* delta, Assignment* deltadelta) { - if (delta != nullptr) { - const bool delta_has_objective = delta->HasObjective(); - if (!delta_has_objective) { - delta->AddObjectives(objective_vars()); - } - const Assignment* const local_search_state = - solver()->GetOrCreateLocalSearchState(); - for (int i = 0; i < Size(); ++i) { - if (delta->ObjectiveFromIndex(i) == ObjectiveVar(i)) { - if (Maximize(i)) { - int64_t obj_min = ObjectiveVar(i)->Min(); - if (delta_has_objective) { - obj_min = std::max(obj_min, delta->ObjectiveMinFromIndex(i)); - } - if (i < local_search_state->NumObjectives()) { - obj_min = std::max( - obj_min, - CapAdd(local_search_state->ObjectiveMinFromIndex(i), Step(i))); - } - delta->SetObjectiveMinFromIndex(i, obj_min); - } else { - int64_t obj_max = ObjectiveVar(i)->Max(); - if (delta_has_objective) { - obj_max = std::min(obj_max, delta->ObjectiveMaxFromIndex(i)); - } - if (i < local_search_state->NumObjectives()) { - obj_max = std::min( - obj_max, - CapSub(local_search_state->ObjectiveMaxFromIndex(i), Step(i))); - } - delta->SetObjectiveMaxFromIndex(i, obj_max); - } - } - } - } - return true; -} - std::string OptimizeVar::Name() const { return "objective"; } std::string OptimizeVar::DebugString() const { @@ -3207,7 +3215,6 @@ class Metaheuristic : public ObjectiveMonitor { void EnterSearch() override; void RefuteDecision(Decision* d) override; - bool AcceptDelta(Assignment* delta, Assignment* deltadelta) override; }; Metaheuristic::Metaheuristic(Solver* solver, const std::vector& maximize, @@ -3223,7 +3230,7 @@ void Metaheuristic::EnterSearch() { solver()->SetUseFastLocalSearch(false); } -void Metaheuristic::RefuteDecision(Decision* d) { +void Metaheuristic::RefuteDecision(Decision*) { for (int i = 0; i < Size(); ++i) { const int64_t objective_value = MinimizationVar(i)->Min(); if (objective_value > BestInternalValue(i)) break; @@ -3232,27 +3239,6 @@ void Metaheuristic::RefuteDecision(Decision* d) { solver()->Fail(); } -bool Metaheuristic::AcceptDelta(Assignment* delta, Assignment* deltadelta) { - if (delta != nullptr) { - if (!delta->HasObjective()) { - delta->AddObjectives(objective_vars()); - } - for (int i = 0; i < Size(); ++i) { - IntVar* const original_objective = ObjectiveVar(i); - if (delta->ObjectiveFromIndex(i) == original_objective) { - if (Maximize(i)) { - delta->SetObjectiveMin( - std::max(original_objective->Min(), delta->ObjectiveMin())); - } else { - delta->SetObjectiveMax( - std::min(original_objective->Max(), delta->ObjectiveMax())); - } - } - } - } - return true; -} - // ---------- Tabu Search ---------- class TabuSearch : public Metaheuristic { @@ -3313,6 +3299,7 @@ TabuSearch::TabuSearch(Solver* solver, const std::vector& maximize, void TabuSearch::EnterSearch() { Metaheuristic::EnterSearch(); + solver()->SetUseFastLocalSearch(true); stamp_ = 0; } @@ -3410,6 +3397,7 @@ bool TabuSearch::AtSolution() { } bool TabuSearch::LocalOptimum() { + solver()->SetUseFastLocalSearch(false); AgeLists(); for (int i = 0; i < Size(); ++i) { SetCurrentInternalValue(i, std::numeric_limits::max()); @@ -3918,6 +3906,7 @@ bool GuidedLocalSearch

::AtSolution() { template void GuidedLocalSearch

::EnterSearch() { Metaheuristic::EnterSearch(); + solver()->SetUseFastLocalSearch(true); penalized_objective_ = nullptr; ResetPenalties(); } @@ -3969,6 +3958,7 @@ bool GuidedLocalSearch

::AcceptDelta(Assignment* delta, // utility(var, value) = cost(var, value) / (1 + penalty(var, value)) template bool GuidedLocalSearch

::LocalOptimum() { + solver()->SetUseFastLocalSearch(false); std::vector utilities(num_vars_); double max_utility = -std::numeric_limits::infinity(); for (int var = 0; var < num_vars_; ++var) { @@ -4257,12 +4247,12 @@ void SearchLimit::EnterSearch() { Init(); } -void SearchLimit::BeginNextDecision(DecisionBuilder* const b) { +void SearchLimit::BeginNextDecision(DecisionBuilder* const) { PeriodicCheck(); TopPeriodicCheck(); } -void SearchLimit::RefuteDecision(Decision* const d) { +void SearchLimit::RefuteDecision(Decision* const) { PeriodicCheck(); TopPeriodicCheck(); } @@ -4536,8 +4526,9 @@ void ImprovementSearchLimit::Install() { } void ImprovementSearchLimit::Init() { - for (int i = 0; i < best_objectives_.size(); ++i) { + for (int i = 0; i < objective_vars_.size(); ++i) { best_objectives_[i] = std::numeric_limits::infinity(); + improvements_[i].clear(); thresholds_[i] = std::numeric_limits::infinity(); } objective_updated_ = false; @@ -4568,7 +4559,7 @@ SearchLimit* ImprovementSearchLimit::MakeClone() const { improvement_rate_solutions_distance_)); } -bool ImprovementSearchLimit::CheckWithOffset(absl::Duration offset) { +bool ImprovementSearchLimit::CheckWithOffset(absl::Duration) { if (!objective_updated_) { return false; } @@ -4700,7 +4691,7 @@ class ORLimit : public SearchLimit { limit_2_->Init(); } - void Copy(const SearchLimit* const limit) override { + void Copy(const SearchLimit* const) override { LOG(FATAL) << "Not implemented."; } @@ -4757,7 +4748,7 @@ class CustomLimit : public SearchLimit { CustomLimit::CustomLimit(Solver* const s, std::function limiter) : SearchLimit(s), limiter_(std::move(limiter)) {} -bool CustomLimit::CheckWithOffset(absl::Duration offset) { +bool CustomLimit::CheckWithOffset(absl::Duration) { // TODO(user): Consider the offset in limiter_. if (limiter_) return limiter_(); return false; @@ -5123,7 +5114,7 @@ class SymmetryManager : public SearchMonitor { ~SymmetryManager() override {} - void EndNextDecision(DecisionBuilder* const db, Decision* const d) override { + void EndNextDecision(DecisionBuilder* const, Decision* const d) override { if (d) { for (int i = 0; i < visitors_.size(); ++i) { const void* const last = clauses_[i].Last(); diff --git a/ortools/constraint_solver/table.cc b/ortools/constraint_solver/table.cc index eaffa64339..08967ed890 100644 --- a/ortools/constraint_solver/table.cc +++ b/ortools/constraint_solver/table.cc @@ -24,6 +24,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/strings/str_format.h" #include "absl/strings/str_join.h" +#include "absl/types/span.h" #include "ortools/base/commandlineflags.h" #include "ortools/base/logging.h" #include "ortools/base/map_util.h" @@ -1163,7 +1164,7 @@ class TransitionConstraint : public Constraint { TransitionConstraint(Solver* const s, const std::vector& vars, const IntTupleSet& transition_table, int64_t initial_state, - const std::vector& final_states) + absl::Span final_states) : Constraint(s), vars_(vars), transition_table_(transition_table),