From 6d3a78e9d50c6805ef15cc7902b714c1ae4ee160 Mon Sep 17 00:00:00 2001 From: Laurent Perron Date: Mon, 1 Dec 2025 11:34:13 +0100 Subject: [PATCH] [Routing] add search event: at optimal --- ortools/constraint_solver/BUILD.bazel | 1 + ortools/constraint_solver/constraint_solver.h | 12 ++++++++ ortools/constraint_solver/local_search.cc | 21 ++++++------- ortools/constraint_solver/routing.cc | 7 ++++- ortools/constraint_solver/search.cc | 30 ++++++++++++++++++- 5 files changed, 57 insertions(+), 14 deletions(-) diff --git a/ortools/constraint_solver/BUILD.bazel b/ortools/constraint_solver/BUILD.bazel index daef322c02..97a76963c9 100644 --- a/ortools/constraint_solver/BUILD.bazel +++ b/ortools/constraint_solver/BUILD.bazel @@ -214,6 +214,7 @@ cc_library( "@abseil-cpp//absl/algorithm:container", "@abseil-cpp//absl/base:core_headers", "@abseil-cpp//absl/base:log_severity", + "@abseil-cpp//absl/base:nullability", "@abseil-cpp//absl/container:flat_hash_map", "@abseil-cpp//absl/container:flat_hash_set", "@abseil-cpp//absl/flags:flag", diff --git a/ortools/constraint_solver/constraint_solver.h b/ortools/constraint_solver/constraint_solver.h index 9bb4ebe1f2..590ace73a4 100644 --- a/ortools/constraint_solver/constraint_solver.h +++ b/ortools/constraint_solver/constraint_solver.h @@ -4663,6 +4663,15 @@ class OptimizeVar : public ObjectiveMonitor { /// Returns the variable that is optimized. IntVar* var() const { return Size() == 0 ? nullptr : ObjectiveVar(0); } +#ifndef SWIG + /// Sets a callback to be called when the objective value is found to be + /// optimal. + void SetOnOptimalFoundcallback( + std::function on_optimal_found) { + on_optimal_found_ = std::move(on_optimal_found); + } +#endif // SWIG + /// Internal methods. void BeginNextDecision(DecisionBuilder* db) override; void RefuteDecision(Decision* d) override; @@ -4672,6 +4681,9 @@ class OptimizeVar : public ObjectiveMonitor { std::string DebugString() const override; void ApplyBound(); + + private: + std::function on_optimal_found_; }; /// Base class of all search limits. diff --git a/ortools/constraint_solver/local_search.cc b/ortools/constraint_solver/local_search.cc index 2a0f237af5..ad30188e7f 100644 --- a/ortools/constraint_solver/local_search.cc +++ b/ortools/constraint_solver/local_search.cc @@ -25,6 +25,7 @@ #include #include "absl/algorithm/container.h" +#include "absl/base/nullability.h" #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/flags/flag.h" @@ -4372,7 +4373,7 @@ namespace { // ----- NestedSolve decision wrapper ----- // This decision calls a nested Solve on the given DecisionBuilder in its -// left branch; does nothing in the left branch. +// left branch; does nothing in the right branch. // The state of the decision corresponds to the result of the nested Solve: // DECISION_PENDING - Nested Solve not called yet // DECISION_FAILED - Nested Solve failed @@ -4383,9 +4384,9 @@ class NestedSolveDecision : public Decision { // This enum is used internally to tag states in the local search tree. enum StateType { DECISION_PENDING, DECISION_FAILED, DECISION_FOUND }; - NestedSolveDecision(DecisionBuilder* db, bool restore, + NestedSolveDecision(DecisionBuilder* absl_nonnull db, bool restore, const std::vector& monitors); - NestedSolveDecision(DecisionBuilder* db, bool restore); + NestedSolveDecision(DecisionBuilder* absl_nonnull db, bool restore); ~NestedSolveDecision() override {} void Apply(Solver* solver) override; void Refute(Solver* solver) override; @@ -4395,25 +4396,21 @@ class NestedSolveDecision : public Decision { private: DecisionBuilder* const db_; const bool restore_; - std::vector monitors_; + const std::vector monitors_; int state_; }; NestedSolveDecision::NestedSolveDecision( - DecisionBuilder* const db, bool restore, + DecisionBuilder* absl_nonnull db, bool restore, const std::vector& monitors) : db_(db), restore_(restore), monitors_(monitors), - state_(DECISION_PENDING) { - CHECK(nullptr != db); -} + state_(DECISION_PENDING) {} -NestedSolveDecision::NestedSolveDecision(DecisionBuilder* const db, +NestedSolveDecision::NestedSolveDecision(DecisionBuilder* absl_nonnull db, bool restore) - : db_(db), restore_(restore), state_(DECISION_PENDING) { - CHECK(nullptr != db); -} + : NestedSolveDecision(db, restore, {}) {} void NestedSolveDecision::Apply(Solver* const solver) { CHECK(nullptr != solver); diff --git a/ortools/constraint_solver/routing.cc b/ortools/constraint_solver/routing.cc index 2f2e3c08d8..17b43e351c 100644 --- a/ortools/constraint_solver/routing.cc +++ b/ortools/constraint_solver/routing.cc @@ -6410,7 +6410,12 @@ void RoutingModel::SetupMetaheuristics( } default: limit_too_long = false; - optimize = solver_->MakeMinimize(cost_, optimization_step); + OptimizeVar* const minimize = + solver_->MakeMinimize(cost_, optimization_step); + optimize = minimize; + minimize->SetOnOptimalFoundcallback([this](int64_t value) { + objective_lower_bound_ = std::max(objective_lower_bound_, value); + }); } if (limit_too_long) { LOG(WARNING) << LocalSearchMetaheuristic::Value_Name(metaheuristic) diff --git a/ortools/constraint_solver/search.cc b/ortools/constraint_solver/search.cc index b3014ebf4e..4ba1f25210 100644 --- a/ortools/constraint_solver/search.cc +++ b/ortools/constraint_solver/search.cc @@ -3217,7 +3217,35 @@ void OptimizeVar::ApplyBound() { } } -void OptimizeVar::RefuteDecision(Decision*) { ApplyBound(); } +namespace { +class ApplyBoundDecisionBuilder : public DecisionBuilder { + public: + explicit ApplyBoundDecisionBuilder(OptimizeVar* optimize_var) + : optimize_var_(optimize_var) {} + ~ApplyBoundDecisionBuilder() override = default; + Decision* Next(Solver*) override { + optimize_var_->ApplyBound(); + return nullptr; + } + + private: + OptimizeVar* optimize_var_; +}; +} // namespace + +void OptimizeVar::RefuteDecision(Decision*) { + if (!solver()->SolveAndCommit( + solver()->RevAlloc(new ApplyBoundDecisionBuilder(this)))) { + if (on_optimal_found_) { + // TODO(user): Support multiple objectives. + const int64_t value = CurrentInternalValue(0); + on_optimal_found_(objective_vars()[0] == minimization_vars()[0] + ? value + : CapOpp(value)); + } + solver()->Fail(); + } +} bool OptimizeVar::AcceptSolution() { if (!found_initial_solution_ || !is_active()) {