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

292 lines
11 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_BOP_BOP_BASE_H_
#define ORTOOLS_BOP_BOP_BASE_H_
#include <cstdint>
#include <limits>
#include <ostream>
#include <string>
#include <vector>
#include "absl/base/thread_annotations.h"
#include "absl/strings/string_view.h"
#include "absl/synchronization/mutex.h"
#include "ortools/base/strong_vector.h"
#include "ortools/bop/bop_parameters.pb.h"
#include "ortools/bop/bop_solution.h"
#include "ortools/bop/bop_types.h"
#include "ortools/lp_data/lp_types.h"
#include "ortools/sat/boolean_problem.pb.h"
#include "ortools/sat/clause.h"
#include "ortools/sat/sat_base.h"
#include "ortools/util/stats.h"
#include "ortools/util/time_limit.h"
namespace operations_research {
namespace bop {
class ProblemState;
// Forward declaration.
struct LearnedInfo;
// Base class used to optimize a ProblemState.
// Optimizers implementing this class are used in a sort of portfolio and
// are run sequentially or concurrently. See for instance BopRandomLNSOptimizer.
class BopOptimizerBase {
public:
explicit BopOptimizerBase(absl::string_view name);
virtual ~BopOptimizerBase();
// Returns the name given at construction.
const std::string& name() const { return name_; }
// Returns true if this optimizer should be run on the given problem state.
// Some optimizer requires a feasible solution to run for instance.
//
// Note that a similar effect can be achieved if Optimize() returns ABORT
// right away. However, doing the later will lower the chance of this
// optimizer to be called again since it will count as a failure to improve
// the current state.
virtual bool ShouldBeRun(const ProblemState& problem_state) const = 0;
// Return status of the Optimize() function below.
//
// TODO(user): To redesign, some are not needed anymore thanks to the
// problem state, e.g. IsOptimal().
enum Status {
OPTIMAL_SOLUTION_FOUND,
SOLUTION_FOUND,
INFEASIBLE,
LIMIT_REACHED,
// Some information was learned and the problem state will need to be
// updated. This will trigger a new optimization round.
//
// TODO(user): replace by learned_info->IsEmpty()? but we will need to clear
// the BopSolution there first.
INFORMATION_FOUND,
// This optimizer didn't learn any information yet but can be called again
// on the same problem state to resume its work.
CONTINUE,
// There is no need to call this optimizer again on the same problem state.
ABORT
};
// Tries to infer more information about the problem state, i.e. reduces the
// gap by increasing the lower bound or finding a better solution.
// Returns SOLUTION_FOUND when a new solution with a better objective cost is
// found before a time limit.
// The learned information is cleared and the filled with any new information
// about the problem, e.g. a new lower bound.
//
// Preconditions: ShouldBeRun() must returns true.
virtual Status Optimize(const BopParameters& parameters,
const ProblemState& problem_state,
LearnedInfo* learned_info, TimeLimit* time_limit) = 0;
// Returns a string describing the status.
static std::string GetStatusString(Status status);
protected:
const std::string name_;
mutable StatsGroup stats_;
};
inline std::ostream& operator<<(std::ostream& os,
BopOptimizerBase::Status status) {
os << BopOptimizerBase::GetStatusString(status);
return os;
}
// This class represents the current state of the problem with all the
// information that the solver learned about it at a given time.
class ProblemState {
public:
explicit ProblemState(const sat::LinearBooleanProblem& problem);
// This type is neither copyable nor movable.
ProblemState(const ProblemState&) = delete;
ProblemState& operator=(const ProblemState&) = delete;
// Sets parameters, used for instance to get the tolerance, the gap limit...
void SetParameters(const BopParameters& parameters) {
parameters_ = parameters;
}
const BopParameters& GetParameters() const { return parameters_; }
// Sets an assignment preference for each variable.
// This is only used for warm start.
void set_assignment_preference(const std::vector<bool>& a) {
assignment_preference_ = a;
}
std::vector<bool> assignment_preference() const {
return assignment_preference_;
}
// Merges the learned information with the current problem state. For
// instance, if variables x, and y are fixed in the current state, and z is
// learned to be fixed, the result of the merge will be x, y, and z being
// fixed in the problem state.
// Note that the LP values contained in the learned information (if any)
// will replace the LP values of the problem state, whatever the cost is.
// Returns true when the merge has changed the problem state.
bool MergeLearnedInfo(const LearnedInfo& learned_info,
BopOptimizerBase::Status optimization_status);
// Returns all the information learned so far.
// TODO(user): In the current implementation the learned information only
// contains binary clauses added since the last call to
// SynchronizationDone().
// Add an iterator on the sat::BinaryClauseManager.
LearnedInfo GetLearnedInfo() const;
// The stamp represents an upper bound on the number of times the problem
// state has been updated. If the stamp changed since last time one has
// checked the state, it's worth trying again as it might have changed
// (no guarantee).
static const int64_t kInitialStampValue;
int64_t update_stamp() const { return update_stamp_; }
// Marks the problem state as optimal.
void MarkAsOptimal();
// Marks the problem state as infeasible.
void MarkAsInfeasible();
// Returns true when the current state is proved to be optimal. In such a case
// solution() returns the optimal solution.
bool IsOptimal() const {
return solution_.IsFeasible() && solution_.GetCost() == lower_bound();
}
// Returns true when the problem is proved to be infeasible.
bool IsInfeasible() const { return lower_bound() > upper_bound(); }
// Returns true when the variable var is fixed in the current problem state.
// The value of the fixed variable is returned by GetVariableFixedValue(var).
bool IsVariableFixed(VariableIndex var) const { return is_fixed_[var]; }
const util_intops::StrongVector<VariableIndex, bool>& is_fixed() const {
return is_fixed_;
}
// Returns the value of the fixed variable var. Should be only called on fixed
// variables (CHECKed).
bool GetVariableFixedValue(VariableIndex var) const {
return fixed_values_[var];
}
const util_intops::StrongVector<VariableIndex, bool>& fixed_values() const {
return fixed_values_;
}
// Returns the values of the LP relaxation of the problem. Returns an empty
// vector when the LP has not been populated.
const glop::DenseRow& lp_values() const { return lp_values_; }
// Returns the solution to the current state problem.
// Note that the solution might not be feasible because until we find one, it
// will just be the all-false assignment.
const BopSolution& solution() const { return solution_; }
// Returns the original problem. Note that the current problem might be
// different, e.g. fixed variables, but equivalent, i.e. a solution to one
// should be a solution to the other too.
const sat::LinearBooleanProblem& original_problem() const {
return original_problem_;
}
// Returns the current lower (resp. upper) bound of the objective cost.
// For internal use only: this is the unscaled version of the lower (resp.
// upper) bound, and so should be compared only to the unscaled cost given by
// solution.GetCost().
int64_t lower_bound() const { return lower_bound_; }
int64_t upper_bound() const { return upper_bound_; }
// Returns the scaled lower bound of the original problem.
double GetScaledLowerBound() const {
return (lower_bound() + original_problem_.objective().offset()) *
original_problem_.objective().scaling_factor();
}
// Returns the newly added binary clause since the last SynchronizationDone().
const std::vector<sat::BinaryClause>& NewlyAddedBinaryClauses() const;
// Resets what is considered "new" information. This is meant to be called
// once all the optimize have been synchronized.
void SynchronizationDone();
private:
const sat::LinearBooleanProblem& original_problem_;
BopParameters parameters_;
int64_t update_stamp_;
util_intops::StrongVector<VariableIndex, bool> is_fixed_;
util_intops::StrongVector<VariableIndex, bool> fixed_values_;
glop::DenseRow lp_values_;
BopSolution solution_;
std::vector<bool> assignment_preference_;
int64_t lower_bound_;
int64_t upper_bound_;
// Manage the set of the problem binary clauses (including the learned ones).
sat::BinaryClauseManager binary_clause_manager_;
};
// This struct represents what has been learned on the problem state by
// running an optimizer. The goal is then to merge the learned information
// with the problem state in order to get a more constrained problem to be used
// by the next called optimizer.
struct LearnedInfo {
explicit LearnedInfo(const sat::LinearBooleanProblem& problem)
: fixed_literals(),
solution(problem, "AllZero"),
lower_bound(std::numeric_limits<int64_t>::min()),
lp_values(),
binary_clauses() {}
// Clears all just as if the object were a brand new one. This can be used
// to reduce the number of creation / deletion of objects.
void Clear() {
fixed_literals.clear();
lower_bound = std::numeric_limits<int64_t>::min();
lp_values.clear();
binary_clauses.clear();
}
// Vector of all literals that have been fixed.
std::vector<sat::Literal> fixed_literals;
// New solution. Note that the solution might be infeasible.
BopSolution solution;
// A lower bound (for multi-threading purpose).
int64_t lower_bound;
// An assignment for the relaxed linear programming problem (can be empty).
// This is meant to be the optimal LP solution, but can just be a feasible
// solution or any floating point assignment if the LP solver didn't solve
// the relaxed problem optimally.
glop::DenseRow lp_values;
// New binary clauses.
std::vector<sat::BinaryClause> binary_clauses;
};
} // namespace bop
} // namespace operations_research
#endif // ORTOOLS_BOP_BOP_BASE_H_