Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
pseudo_costs.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 <cmath>
18#include <cstdint>
19#include <cstdlib>
20#include <limits>
21#include <string>
22#include <tuple>
23#include <utility>
24#include <vector>
25
26#include "absl/log/check.h"
27#include "absl/strings/str_cat.h"
28#include "absl/types/span.h"
31#include "ortools/sat/integer.h"
34#include "ortools/sat/model.h"
36#include "ortools/sat/sat_parameters.pb.h"
37#include "ortools/sat/util.h"
39
40namespace operations_research {
41namespace sat {
42
43// We prefer the product to combine the cost of two branches.
44double PseudoCosts::CombineScores(double down_branch, double up_branch) const {
45 if (true) {
46 return std::max(1e-6, down_branch) * std::max(1e-6, up_branch);
47 } else {
48 const double min_value = std::min(up_branch, down_branch);
49 const double max_value = std::max(up_branch, down_branch);
50 const double mu = 1.0 / 6.0;
51 return (1.0 - mu) * min_value + mu * max_value;
52 }
53}
54
55std::string PseudoCosts::ObjectiveInfo::DebugString() const {
56 return absl::StrCat("lb: ", lb, " ub:", ub, " lp_bound:", lp_bound);
57}
58
60 : parameters_(*model->GetOrCreate<SatParameters>()),
61 integer_trail_(model->GetOrCreate<IntegerTrail>()),
62 encoder_(model->GetOrCreate<IntegerEncoder>()),
63 lp_values_(model->GetOrCreate<ModelLpValues>()),
64 lps_(model->GetOrCreate<LinearProgrammingConstraintCollection>()) {
65 const int num_vars = integer_trail_->NumIntegerVariables().value();
66 pseudo_costs_.resize(num_vars);
67 is_relevant_.resize(num_vars, false);
68 scores_.resize(num_vars, 0.0);
69
70 // If objective_var == kNoIntegerVariable, there is not really any point using
71 // this class.
72 auto* objective = model->Get<ObjectiveDefinition>();
73 if (objective != nullptr) {
74 objective_var_ = objective->objective_var;
75 }
76}
77
78PseudoCosts::ObjectiveInfo PseudoCosts::GetCurrentObjectiveInfo() {
79 ObjectiveInfo result;
80 if (objective_var_ == kNoIntegerVariable) return result;
81
82 result.lb = integer_trail_->LowerBound(objective_var_);
83 result.ub = integer_trail_->UpperBound(objective_var_);
84
85 // We sum the objectives over the LP components.
86 // Note that in practice, when we use the pseudo-costs, there is just one.
87 result.lp_bound = 0.0;
88 result.lp_at_optimal = true;
89 for (const auto* lp : *lps_) {
90 if (!lp->AtOptimal()) result.lp_at_optimal = false;
91 result.lp_bound += lp->ObjectiveLpLowerBound();
92 }
93 return result;
94}
95
97 saved_info_ = GetCurrentObjectiveInfo();
98 return saved_info_.lp_at_optimal;
99}
100
102 absl::Span<const double> lp_values) {
103 bound_changes_ = GetBoundChanges(decision, lp_values);
104}
105
107 if (objective_var_ == kNoIntegerVariable) return;
108 SaveLpInfo();
109 SaveBoundChanges(decision, *lp_values_);
110}
111
113 IntegerVariable var, absl::Span<const double> lp_values) {
114 DCHECK_NE(var, kNoIntegerVariable);
115 BranchingInfo result;
116 const IntegerValue lb = integer_trail_->LowerBound(var);
117 const IntegerValue ub = integer_trail_->UpperBound(var);
118 if (lb == ub) {
119 result.is_fixed = true;
120 return result;
121 }
122
123 const double lp_value = lp_values[var.value()];
124 double down_fractionality = lp_value - std::floor(lp_value);
125 IntegerValue down_target = IntegerValue(std::floor(lp_value));
126 if (lp_value >= ToDouble(ub)) {
127 down_fractionality = 1.0;
128 down_target = ub - 1;
129 } else if (lp_value <= ToDouble(lb)) {
130 down_fractionality = 0.0;
131 down_target = lb;
132 }
133
134 result.is_integer = std::abs(lp_value - std::round(lp_value)) < 1e-6;
135 result.down_fractionality = down_fractionality;
136 result.down_branch = IntegerLiteral::LowerOrEqual(var, down_target);
137
138 const int max_index = std::max(var.value(), NegationOf(var).value());
139 if (max_index < average_unit_objective_increase_.size()) {
140 result.down_score =
141 down_fractionality *
142 average_unit_objective_increase_[NegationOf(var)].CurrentAverage();
143 result.up_score = (1.0 - down_fractionality) *
144 average_unit_objective_increase_[var].CurrentAverage();
145 result.score = CombineScores(result.down_score, result.up_score);
146
147 const int reliablitity = std::min(
148 average_unit_objective_increase_[var].NumRecords(),
149 average_unit_objective_increase_[NegationOf(var)].NumRecords());
150 result.is_reliable = reliablitity >= 4;
151 }
152
153 return result;
154}
155
156void PseudoCosts::UpdateBoolPseudoCosts(absl::Span<const Literal> reason,
157 IntegerValue objective_increase) {
158 const double relative_increase =
159 ToDouble(objective_increase) / static_cast<double>(reason.size());
160 for (const Literal lit : reason) {
161 if (lit.Index() >= lit_pseudo_costs_.size()) {
162 lit_pseudo_costs_.resize(lit.Index() + 1);
163 }
164 lit_pseudo_costs_[lit].AddData(relative_increase);
165 }
166}
167
168double PseudoCosts::BoolPseudoCost(Literal lit, double lp_value) const {
169 if (lit.Index() >= lit_pseudo_costs_.size()) return 0.0;
170
171 const double down_fractionality = lp_value;
172 const double up_fractionality = 1.0 - lp_value;
173 const double up_branch =
174 up_fractionality * lit_pseudo_costs_[lit].CurrentAverage();
175 const double down_branch =
176 down_fractionality *
177 lit_pseudo_costs_[lit.NegatedIndex()].CurrentAverage();
178 return CombineScores(down_branch, up_branch);
179}
180
181double PseudoCosts::ObjectiveIncrease(bool conflict) {
182 const ObjectiveInfo new_info = GetCurrentObjectiveInfo();
183 const double obj_lp_diff =
184 std::max(0.0, new_info.lp_bound - saved_info_.lp_bound);
185 const IntegerValue obj_int_diff = new_info.lb - saved_info_.lb;
186
187 double obj_increase =
188 obj_lp_diff > 0.0 ? obj_lp_diff : ToDouble(obj_int_diff);
189 if (conflict) {
190 // We count a conflict as a max increase + 1.0
191 obj_increase = ToDouble(saved_info_.ub) - ToDouble(saved_info_.lb) + 1.0;
192 }
193 return obj_increase;
194}
195
197 if (objective_var_ == kNoIntegerVariable) return;
198 const ObjectiveInfo new_info = GetCurrentObjectiveInfo();
199
200 // We store a pseudo cost for this literal. We prefer the pure LP version, but
201 // revert to integer version if there is no lp. TODO(user): tune that.
202 //
203 // We only collect lp increase when the lp is at optimal, otherwise it might
204 // just be the "artificial" continuing of the current lp solve that create the
205 // increase.
206 if (saved_info_.lp_at_optimal) {
207 // Update the average unit increases.
208 const double obj_increase = ObjectiveIncrease(conflict);
209 for (const auto [var, lb_change, lp_increase] : bound_changes_) {
210 if (lp_increase < 1e-6) continue;
211 if (var >= average_unit_objective_increase_.size()) {
212 average_unit_objective_increase_.resize(var + 1);
213 }
214 average_unit_objective_increase_[var].AddData(obj_increase / lp_increase);
215 }
216 }
217
218 // TODO(user): Handle this case.
219 if (conflict) return;
220
221 // We also store one for any associated IntegerVariable.
222 const IntegerValue obj_bound_improvement =
223 (new_info.lb - saved_info_.lb) + (saved_info_.ub - new_info.ub);
224 DCHECK_GE(obj_bound_improvement, 0);
225 if (obj_bound_improvement == IntegerValue(0)) return;
226
227 for (const auto [var, lb_change, lp_increase] : bound_changes_) {
228 if (lb_change == IntegerValue(0)) continue;
229
230 if (var >= pseudo_costs_.size()) {
231 // Create space for new variable and its negation.
232 const int new_size = std::max(var, NegationOf(var)).value() + 1;
233 is_relevant_.resize(new_size, false);
234 scores_.resize(new_size, 0.0);
235 pseudo_costs_.resize(new_size, IncrementalAverage(0.0));
236 }
237
238 pseudo_costs_[var].AddData(ToDouble(obj_bound_improvement) /
239 ToDouble(lb_change));
240
241 const IntegerVariable positive_var = PositiveVariable(var);
242 const IntegerVariable negative_var = NegationOf(positive_var);
243 const int64_t count = pseudo_costs_[positive_var].NumRecords() +
244 pseudo_costs_[negative_var].NumRecords();
245 if (count >= parameters_.pseudo_cost_reliability_threshold()) {
246 scores_[positive_var] =
247 CombineScores(GetCost(positive_var), GetCost(negative_var));
248 if (!is_relevant_[positive_var]) {
249 is_relevant_[positive_var] = true;
250 relevant_variables_.push_back(positive_var);
251 }
252 }
253 }
254}
255
256// TODO(user): Supports search randomization tolerance.
257// TODO(user): Implement generic class to choose the randomized
258// solution, and supports sub-linear variable selection.
260 IntegerVariable chosen_var = kNoIntegerVariable;
261 double best_score = -std::numeric_limits<double>::infinity();
262
263 // TODO(user): Avoid the O(num_relevant_variable) loop.
264 // In practice since a variable only become relevant after 100 records, this
265 // list might be small compared to the number of variable though.
266 for (const IntegerVariable positive_var : relevant_variables_) {
267 const IntegerValue lb = integer_trail_->LowerBound(positive_var);
268 const IntegerValue ub = integer_trail_->UpperBound(positive_var);
269 if (lb >= ub) continue;
270 if (scores_[positive_var] > best_score) {
271 chosen_var = positive_var;
272 best_score = scores_[positive_var];
273 }
274 }
275
276 // Pick the direction with best pseudo cost.
277 if (chosen_var != kNoIntegerVariable &&
278 GetCost(chosen_var) < GetCost(NegationOf(chosen_var))) {
279 chosen_var = NegationOf(chosen_var);
280 }
281 return chosen_var;
282}
283
284std::vector<PseudoCosts::VariableBoundChange> PseudoCosts::GetBoundChanges(
285 Literal decision, absl::Span<const double> lp_values) {
286 std::vector<PseudoCosts::VariableBoundChange> bound_changes;
287
288 for (const IntegerLiteral l : encoder_->GetIntegerLiterals(decision)) {
290 entry.var = l.var;
291 entry.lower_bound_change = l.bound - integer_trail_->LowerBound(l.var);
292 if (l.var < lp_values.size()) {
293 entry.lp_increase =
294 std::max(0.0, ToDouble(l.bound) - lp_values[l.var.value()]);
295 }
296 bound_changes.push_back(entry);
297 }
298
299 // NOTE: We ignore literal associated to var != value.
300 for (const auto [var, value] : encoder_->GetEqualityLiterals(decision)) {
301 {
303 entry.var = var;
304 entry.lower_bound_change = value - integer_trail_->LowerBound(var);
305 bound_changes.push_back(entry);
306 }
307
308 // Also do the negation.
309 {
311 entry.var = NegationOf(var);
312 entry.lower_bound_change =
313 (-value) - integer_trail_->LowerBound(NegationOf(var));
314 bound_changes.push_back(entry);
315 }
316 }
317
318 return bound_changes;
319}
320
321} // namespace sat
322} // namespace operations_research
Manages incremental averages.
Definition util.h:523
const InlinedIntegerValueVector & GetEqualityLiterals(Literal lit) const
Definition integer.h:593
const InlinedIntegerLiteralVector & GetIntegerLiterals(Literal lit) const
Returns the IntegerLiterals that were associated with the given Literal.
Definition integer.h:584
IntegerValue LowerBound(IntegerVariable i) const
Returns the current lower/upper bound of the given integer variable.
Definition integer.h:1717
IntegerValue UpperBound(IntegerVariable i) const
Definition integer.h:1721
IntegerVariable NumIntegerVariables() const
Definition integer.h:806
A class that stores the collection of all LP constraints in a model.
void AfterTakingDecision(bool conflict=false)
double CombineScores(double down_branch, double up_branch) const
Combines the score of the two branch into one score.
void UpdateBoolPseudoCosts(absl::Span< const Literal > reason, IntegerValue objective_increase)
void BeforeTakingDecision(Literal decision)
IntegerVariable GetBestDecisionVar()
Returns the variable with best reliable pseudo cost that is not fixed.
BranchingInfo EvaluateVar(IntegerVariable var, absl::Span< const double > lp_values)
double BoolPseudoCost(Literal lit, double lp_value) const
std::vector< VariableBoundChange > GetBoundChanges(Literal decision, absl::Span< const double > lp_values)
double ObjectiveIncrease(bool conflict)
void SaveBoundChanges(Literal decision, absl::Span< const double > lp_values)
double GetCost(IntegerVariable var) const
Returns the pseudo cost of given variable. Currently used for testing only.
void resize(size_type new_size)
int64_t value
IntVar * var
GRBmodel * model
int lit
std::vector< IntegerVariable > NegationOf(const std::vector< IntegerVariable > &vars)
Returns the vector of the negated variables.
Definition integer.cc:51
const IntegerVariable kNoIntegerVariable(-1)
IntegerVariable PositiveVariable(IntegerVariable i)
Definition integer.h:193
double ToDouble(IntegerValue value)
Definition integer.h:73
In SWIG mode, we don't want anything besides these top-level includes.
static IntegerLiteral LowerOrEqual(IntegerVariable i, IntegerValue bound)
Definition integer.h:1673