286 lines
11 KiB
C++
286 lines
11 KiB
C++
// Copyright 2010-2021 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_BOP_BOP_BASE_H_
|
|
#define OR_TOOLS_BOP_BOP_BASE_H_
|
|
|
|
#include <cstdint>
|
|
#include <limits>
|
|
#include <string>
|
|
|
|
#include "absl/synchronization/mutex.h"
|
|
#include "ortools/base/basictypes.h"
|
|
#include "ortools/base/strong_vector.h"
|
|
#include "ortools/bop/bop_parameters.pb.h"
|
|
#include "ortools/bop/bop_solution.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 {
|
|
|
|
// Forward declaration.
|
|
struct LearnedInfo;
|
|
class ProblemState;
|
|
|
|
// 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(const std::string& 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);
|
|
|
|
// 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;
|
|
}
|
|
const 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 absl::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 absl::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_;
|
|
absl::StrongVector<VariableIndex, bool> is_fixed_;
|
|
absl::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_;
|
|
|
|
DISALLOW_COPY_AND_ASSIGN(ProblemState);
|
|
};
|
|
|
|
// 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 // OR_TOOLS_BOP_BOP_BASE_H_
|