Files
ortools-clone/ortools/sat/lb_tree_search.h
Corentin Le Molgat b4b226801b update include guards
2025-11-05 11:54:02 +01:00

246 lines
8.5 KiB
C++

// Copyright 2010-2025 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 ORTOOLS_SAT_LB_TREE_SEARCH_H_
#define ORTOOLS_SAT_LB_TREE_SEARCH_H_
#include <stdint.h>
#include <algorithm>
#include <functional>
#include <limits>
#include <string>
#include <vector>
#include "absl/log/check.h"
#include "absl/types/span.h"
#include "ortools/base/strong_vector.h"
#include "ortools/glop/variables_info.h"
#include "ortools/sat/integer.h"
#include "ortools/sat/integer_base.h"
#include "ortools/sat/integer_search.h"
#include "ortools/sat/linear_programming_constraint.h"
#include "ortools/sat/model.h"
#include "ortools/sat/pseudo_costs.h"
#include "ortools/sat/sat_base.h"
#include "ortools/sat/sat_decision.h"
#include "ortools/sat/sat_parameters.pb.h"
#include "ortools/sat/sat_solver.h"
#include "ortools/sat/synchronization.h"
#include "ortools/sat/util.h"
#include "ortools/util/strong_integers.h"
#include "ortools/util/time_limit.h"
namespace operations_research {
namespace sat {
// Implement a "classic" MIP tree search by having an exhaustive list of open
// nodes.
//
// The goal of this subsolver is to improve the objective lower bound. It is
// meant to be used in a multi-thread portfolio, and as such it really do not
// care about finding solution. It is all about improving the lower bound.
//
// TODO(user): What this is doing is really similar to asking a SAT solver if
// the current objective lower bound is reachable by solving a SAT problem.
// However, this code handle on the side all the "conflict" of the form
// objective > current_lb. As a result, when it is UNSAT, we can bump the lower
// bound by a bigger amount than one. We also do not completely loose everything
// learned so far for the next iteration.
class LbTreeSearch {
public:
explicit LbTreeSearch(Model* model);
// Explores the search space.
SatSolver::Status Search(
const std::function<void()>& feasible_solution_observer);
private:
// Code a binary tree.
DEFINE_STRONG_INDEX_TYPE(NodeIndex);
struct Node {
explicit Node(IntegerValue lb) : true_objective(lb), false_objective(lb) {}
// The objective lower bound at this node.
IntegerValue MinObjective() const {
return std::min(true_objective, false_objective);
}
// Invariant: the objective bounds only increase.
void UpdateObjective(IntegerValue v) {
true_objective = std::max(true_objective, v);
false_objective = std::max(false_objective, v);
}
void UpdateTrueObjective(IntegerValue v) {
true_objective = std::max(true_objective, v);
}
void UpdateFalseObjective(IntegerValue v) {
false_objective = std::max(false_objective, v);
}
// Should be called only once.
void SetDecision(Literal l) {
DCHECK(!is_deleted);
DCHECK_EQ(literal_index, kNoLiteralIndex);
literal_index = l.Index();
}
Literal Decision() const {
DCHECK(!is_deleted);
DCHECK_NE(literal_index, kNoLiteralIndex);
return sat::Literal(literal_index);
}
bool IsLeaf() const { return literal_index == kNoLiteralIndex; }
// The decision for the true and false branch under this node.
// Initially this is kNoLiteralIndex until SetDecision() is called.
LiteralIndex literal_index = kNoLiteralIndex;
// The objective lower bound in both branches.
IntegerValue true_objective;
IntegerValue false_objective;
// Points to adjacent nodes in the tree. Large if no connection.
NodeIndex true_child = NodeIndex(std::numeric_limits<int32_t>::max());
NodeIndex false_child = NodeIndex(std::numeric_limits<int32_t>::max());
// Indicates if this nodes was removed from the tree.
bool is_deleted = false;
// Experimental. Store the optimal basis at each node.
int64_t basis_timestamp;
glop::BasisState basis;
};
// Regroup some logic done when we are back at level zero in Search().
// Returns false if UNSAT.
bool LevelZeroLogic();
// Returns true if we save/load LP basis.
// Note that when this is true we also do not solve the LP as often.
bool SaveLpBasisOption() const {
return lp_constraint_ != nullptr &&
parameters_.save_lp_basis_in_lb_tree_search();
}
// Display the current tree, this is mainly here to investigate ideas to
// improve the code.
std::string NodeDebugString(NodeIndex node) const;
void DebugDisplayTree(NodeIndex root) const;
// Updates the objective of the node in the current branch at level n from
// the one at level n - 1.
void UpdateObjectiveFromParent(int level);
// Updates the objective of the node in the current branch at level n - 1 from
// the one at level n.
void UpdateParentObjective(int level);
// Returns false on conflict.
bool FullRestart();
// Loads any known basis that is the closest to the current branch.
void EnableLpAndLoadBestBasis();
void SaveLpBasisInto(Node& node);
bool NodeHasUpToDateBasis(const Node& node) const;
bool NodeHasBasis(const Node& node) const;
// Mark the given node as deleted. Its literal is assumed to be set. We also
// delete the subtree that is not longer relevant.
void MarkAsDeletedNodeAndUnreachableSubtree(Node& node);
void MarkBranchAsInfeasible(Node& node, bool true_branch);
void MarkSubtreeAsDeleted(NodeIndex root);
// Create a new node at the end of the current branch.
// This assume the last decision in the branch is assigned.
NodeIndex CreateNewEmptyNodeIfNeeded();
void AppendNewNodeToCurrentBranch(Literal decision);
// Update the bounds on the given nodes by using reduced costs if possible.
void ExploitReducedCosts(NodeIndex n);
// Returns a small number of decision needed to reach the same conflict.
// We basically reduce the number of decision at each level to 1.
std::vector<Literal> ExtractDecisions(int base_level,
absl::Span<const Literal> conflict);
// Used in the solve logs.
std::string SmallProgressString() const;
// Save the current number of iterations on creation and add the difference
// to the counter when the returned function is called. This is meant to
// be used with:
// const auto cleanup = absl::MakeCleanup(UpdateLpIters(&counter));
std::function<void()> UpdateLpIters(int64_t* counter);
// Model singleton class used here.
const std::string name_;
TimeLimit* time_limit_;
ModelRandomGenerator* random_;
SatSolver* sat_solver_;
IntegerEncoder* integer_encoder_;
Trail* trail_;
const VariablesAssignment& assignment_;
IntegerTrail* integer_trail_;
GenericLiteralWatcher* watcher_;
SharedResponseManager* shared_response_;
PseudoCosts* pseudo_costs_;
SatDecisionPolicy* sat_decision_;
IntegerSearchHelper* search_helper_;
IntegerVariable objective_var_;
const SatParameters& parameters_;
// This can stay null. Otherwise it will be the lp constraint with
// objective_var_ as objective.
LinearProgrammingConstraint* lp_constraint_ = nullptr;
// We temporarily cache the shared_response_ objective lb here.
IntegerValue current_objective_lb_;
// Memory for all the nodes.
int num_nodes_in_tree_ = 0;
util_intops::StrongVector<NodeIndex, Node> nodes_;
// The list of nodes in the current branch, in order from the root.
std::vector<NodeIndex> current_branch_;
// Our heuristic used to explore the tree. See code for detail.
std::function<BooleanOrIntegerLiteral()> search_heuristic_;
int64_t num_rc_detected_ = 0;
// Counts the number of decisions we are taking while exploring the search
// tree.
int64_t num_decisions_taken_ = 0;
// Counts number of lp iterations at various places.
int64_t num_lp_iters_at_level_zero_ = 0;
int64_t num_lp_iters_save_basis_ = 0;
int64_t num_lp_iters_first_branch_ = 0;
int64_t num_lp_iters_dive_ = 0;
// Used to trigger the initial restarts and imports.
int num_full_restarts_ = 0;
int64_t num_decisions_taken_at_last_restart_ = 0;
int64_t num_decisions_taken_at_last_level_zero_ = 0;
// Count the number of time we are back to decision level zero.
int64_t num_back_to_root_node_ = 0;
};
} // namespace sat
} // namespace operations_research
#endif // ORTOOLS_SAT_LB_TREE_SEARCH_H_