Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
bop_base.cc
Go to the documentation of this file.
1// Copyright 2010-2024 Google LLC
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
15
16#include <algorithm>
17#include <cstdint>
18#include <cstdlib>
19#include <limits>
20#include <string>
21#include <vector>
22
23#include "absl/log/check.h"
24#include "absl/strings/string_view.h"
25#include "absl/synchronization/mutex.h"
28#include "ortools/bop/bop_parameters.pb.h"
32#include "ortools/sat/boolean_problem.pb.h"
33#include "ortools/sat/clause.h"
35#include "ortools/util/stats.h"
37
38namespace operations_research {
39namespace bop {
40
41using ::operations_research::sat::LinearBooleanProblem;
42using ::operations_research::sat::LinearObjective;
43
45 : name_(name), stats_(name) {
47}
48
52
54 switch (status) {
56 return "OPTIMAL_SOLUTION_FOUND";
57 case SOLUTION_FOUND:
58 return "SOLUTION_FOUND";
59 case INFEASIBLE:
60 return "INFEASIBLE";
61 case LIMIT_REACHED:
62 return "LIMIT_REACHED";
64 return "INFORMATION_FOUND";
65 case CONTINUE:
66 return "CONTINUE";
67 case ABORT:
68 return "ABORT";
69 }
70 // Fallback. We don't use "default:" so the compiler will return an error
71 // if we forgot one enum case above.
72 LOG(DFATAL) << "Invalid Status " << static_cast<int>(status);
73 return "UNKNOWN Status";
74}
75
76//------------------------------------------------------------------------------
77// ProblemState
78//------------------------------------------------------------------------------
80
81ProblemState::ProblemState(const LinearBooleanProblem& problem)
82 : original_problem_(problem),
83 parameters_(),
84 update_stamp_(kInitialStampValue + 1),
85 is_fixed_(problem.num_variables(), false),
86 fixed_values_(problem.num_variables(), false),
87 lp_values_(),
88 solution_(problem, "AllZero"),
89 assignment_preference_(),
90 lower_bound_(std::numeric_limits<int64_t>::min()),
91 upper_bound_(std::numeric_limits<int64_t>::max()) {
92 // TODO(user): Extract to a function used by all solvers.
93 // Compute trivial unscaled lower bound.
94 const LinearObjective& objective = problem.objective();
95 lower_bound_ = 0;
96 for (int i = 0; i < objective.coefficients_size(); ++i) {
97 // Fix template version for or-tools.
98 lower_bound_ += std::min<int64_t>(int64_t{0}, objective.coefficients(i));
99 }
100 upper_bound_ = solution_.IsFeasible() ? solution_.GetCost()
101 : std::numeric_limits<int64_t>::max();
102}
103
104// TODO(user): refactor this to not rely on the optimization status.
105// All the information can be encoded in the learned_info bounds.
107 const LearnedInfo& learned_info,
108 BopOptimizerBase::Status optimization_status) {
109 const std::string kIndent(25, ' ');
110
111 bool new_lp_values = false;
112 if (!learned_info.lp_values.empty()) {
113 if (lp_values_ != learned_info.lp_values) {
114 lp_values_ = learned_info.lp_values;
115 new_lp_values = true;
116 VLOG(1) << kIndent + "New LP values.";
117 }
118 }
119
120 bool new_binary_clauses = false;
121 if (!learned_info.binary_clauses.empty()) {
122 const int old_num = binary_clause_manager_.NumClauses();
123 for (sat::BinaryClause c : learned_info.binary_clauses) {
124 const int num_vars = original_problem_.num_variables();
125 if (c.a.Variable() < num_vars && c.b.Variable() < num_vars) {
126 binary_clause_manager_.Add(c);
127 }
128 }
129 if (binary_clause_manager_.NumClauses() > old_num) {
130 new_binary_clauses = true;
131 VLOG(1) << kIndent + "Num binary clauses: "
132 << binary_clause_manager_.NumClauses();
133 }
134 }
135
136 bool new_solution = false;
137 if (learned_info.solution.IsFeasible() &&
138 (!solution_.IsFeasible() ||
139 learned_info.solution.GetCost() < solution_.GetCost())) {
140 solution_ = learned_info.solution;
141 new_solution = true;
142 VLOG(1) << kIndent + "New solution.";
143 }
144
145 bool new_lower_bound = false;
146 if (learned_info.lower_bound > lower_bound()) {
147 lower_bound_ = learned_info.lower_bound;
148 new_lower_bound = true;
149 VLOG(1) << kIndent + "New lower bound.";
150 }
151
152 if (solution_.IsFeasible()) {
153 upper_bound_ = std::min(upper_bound(), solution_.GetCost());
154 if (upper_bound() <= lower_bound() ||
155 (upper_bound() - lower_bound() <=
156 parameters_.relative_gap_limit() *
157 std::max(std::abs(upper_bound()), std::abs(lower_bound())))) {
158 // The lower bound might be greater that the cost of a feasible solution
159 // due to rounding errors in the problem scaling and Glop.
160 // As a feasible solution was found, the solution is proved optimal.
162 }
163 }
164
165 // Merge fixed variables. Note that variables added during search, i.e. not
166 // in the original problem, are ignored.
167 int num_newly_fixed_variables = 0;
168 for (const sat::Literal literal : learned_info.fixed_literals) {
169 const VariableIndex var(literal.Variable().value());
170 if (var >= original_problem_.num_variables()) {
171 continue;
172 }
173 const bool value = literal.IsPositive();
174 if (is_fixed_[var]) {
175 if (fixed_values_[var] != value) {
177 return true;
178 }
179 } else {
180 is_fixed_[var] = true;
181 fixed_values_[var] = value;
182 ++num_newly_fixed_variables;
183 }
184 }
185 if (num_newly_fixed_variables > 0) {
186 int num_fixed_variables = 0;
187 for (const bool is_fixed : is_fixed_) {
188 if (is_fixed) {
189 ++num_fixed_variables;
190 }
191 }
192 VLOG(1) << kIndent << num_newly_fixed_variables
193 << " newly fixed variables (" << num_fixed_variables << " / "
194 << is_fixed_.size() << ").";
195 if (num_fixed_variables == is_fixed_.size()) {
196 // Set the solution to the fixed variables.
197 BopSolution fixed_solution = solution_;
198 for (VariableIndex var(0); var < is_fixed_.size(); ++var) {
199 fixed_solution.SetValue(var, fixed_values_[var]);
200 }
201 if (fixed_solution.IsFeasible()) {
202 solution_ = fixed_solution;
203 }
204 if (solution_.IsFeasible()) {
206 VLOG(1) << kIndent << "Optimal";
207 } else {
209 }
210 }
211 }
212
213 bool known_status = false;
214 if (optimization_status == BopOptimizerBase::OPTIMAL_SOLUTION_FOUND) {
216 known_status = true;
217 } else if (optimization_status == BopOptimizerBase::INFEASIBLE) {
219 known_status = true;
220 }
221
222 const bool updated = new_lp_values || new_binary_clauses || new_solution ||
223 new_lower_bound || num_newly_fixed_variables > 0 ||
224 known_status;
225 if (updated) ++update_stamp_;
226 return updated;
227}
228
230 LearnedInfo learned_info(original_problem_);
231 for (VariableIndex var(0); var < is_fixed_.size(); ++var) {
232 if (is_fixed_[var]) {
233 learned_info.fixed_literals.push_back(
234 sat::Literal(sat::BooleanVariable(var.value()), fixed_values_[var]));
235 }
236 }
237 learned_info.solution = solution_;
238 learned_info.lower_bound = lower_bound();
239 learned_info.lp_values = lp_values_;
240 learned_info.binary_clauses = NewlyAddedBinaryClauses();
241
242 return learned_info;
243}
244
246 CHECK(solution_.IsFeasible());
247 lower_bound_ = upper_bound();
248 ++update_stamp_;
249}
250
252 // Mark as infeasible, i.e. set a lower_bound greater than the upper_bound.
253 CHECK(!solution_.IsFeasible());
254 if (upper_bound() == std::numeric_limits<int64_t>::max()) {
255 lower_bound_ = std::numeric_limits<int64_t>::max();
256 upper_bound_ = std::numeric_limits<int64_t>::max() - 1;
257 } else {
258 lower_bound_ = upper_bound_ - 1;
259 }
260 ++update_stamp_;
261}
262
263const std::vector<sat::BinaryClause>& ProblemState::NewlyAddedBinaryClauses()
264 const {
265 return binary_clause_manager_.newly_added();
266}
267
269 binary_clause_manager_.ClearNewlyAdded();
271} // namespace bop
272} // namespace operations_research
int64_t max
int64_t min
std::string StatString() const
Definition stats.cc:77
BopOptimizerBase(absl::string_view name)
Definition bop_base.cc:44
@ ABORT
There is no need to call this optimizer again on the same problem state.
Definition bop_base.h:87
static std::string GetStatusString(Status status)
Returns a string describing the status.
Definition bop_base.cc:53
void SetValue(VariableIndex var, bool value)
void MarkAsInfeasible()
Marks the problem state as infeasible.
Definition bop_base.cc:253
const std::vector< sat::BinaryClause > & NewlyAddedBinaryClauses() const
Returns the newly added binary clause since the last SynchronizationDone().
Definition bop_base.cc:265
bool MergeLearnedInfo(const LearnedInfo &learned_info, BopOptimizerBase::Status optimization_status)
Definition bop_base.cc:108
void MarkAsOptimal()
Marks the problem state as optimal.
Definition bop_base.cc:247
ProblemState(const sat::LinearBooleanProblem &problem)
static const int64_t kInitialStampValue
Definition bop_base.h:164
const util_intops::StrongVector< VariableIndex, bool > & is_fixed() const
Definition bop_base.h:185
const std::vector< BinaryClause > & newly_added() const
Returns the newly added BinaryClause since the last ClearNewlyAdded() call.
Definition clause.h:428
const std::string name
A name for logging purposes.
int64_t value
IntVar * var
absl::Status status
Definition g_gurobi.cc:44
int literal
In SWIG mode, we don't want anything besides these top-level includes.
STL namespace.
#define IF_STATS_ENABLED(instructions)
Definition stats.h:417
#define SCOPED_TIME_STAT(stats)
Definition stats.h:418
A binary clause. This is used by BinaryClauseManager.
Definition clause.h:395