[Routing] add search event: at optimal

This commit is contained in:
Laurent Perron
2025-12-01 11:34:13 +01:00
committed by Corentin Le Molgat
parent 032fad4c94
commit 6d3a78e9d5
5 changed files with 57 additions and 14 deletions

View File

@@ -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",

View File

@@ -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<void(int64_t)> 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<void(int64_t)> on_optimal_found_;
};
/// Base class of all search limits.

View File

@@ -25,6 +25,7 @@
#include <vector>
#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<SearchMonitor*>& 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<SearchMonitor*> monitors_;
const std::vector<SearchMonitor*> monitors_;
int state_;
};
NestedSolveDecision::NestedSolveDecision(
DecisionBuilder* const db, bool restore,
DecisionBuilder* absl_nonnull db, bool restore,
const std::vector<SearchMonitor*>& 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);

View File

@@ -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)

View File

@@ -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()) {