Google OR-Tools v9.15
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
stat_tables.cc
Go to the documentation of this file.
1// Copyright 2010-2025 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 <string>
19#include <utility>
20#include <vector>
21
22#include "absl/container/btree_map.h"
23#include "absl/strings/str_cat.h"
24#include "absl/strings/str_format.h"
25#include "absl/strings/string_view.h"
26#include "absl/synchronization/mutex.h"
27#include "absl/types/span.h"
29#include "ortools/sat/clause.h"
32#include "ortools/sat/model.h"
36#include "ortools/sat/util.h"
39
41
43 absl::MutexLock mutex_lock(mutex_);
44
45 timing_table_.push_back(
46 {"Task timing", "n [ min, max] avg dev time",
47 "n [ min, max] avg dev dtime"});
48
49 lp_table_.push_back({"Lp stats", "Component", "Iterations", "AddedCuts",
50 "OPTIMAL", "DUAL_F.", "DUAL_U."});
51
52 lp_dim_table_.push_back(
53 {"Lp dimension", "Final dimension of first component"});
54
55 lp_debug_table_.push_back({"Lp debug", "CutPropag", "CutEqPropag", "Adjust",
56 "Overflow", "Bad", "BadScaling"});
57
58 lp_manager_table_.push_back({"Lp pool", "Constraints", "Updates", "Simplif",
59 "Merged", "Shortened", "Split", "Strengthened",
60 "Cuts/Call"});
61
62 lns_table_.push_back(
63 {"LNS stats", "Improv/Calls", "Closed", "Difficulty", "TimeLimit"});
64
65 ls_table_.push_back({"LS stats", "Batches", "Restarts/Perturbs", "LinMoves",
66 "GenMoves", "CompoundMoves", "Bactracks",
67 "WeightUpdates", "ScoreComputed"});
68}
69
71 absl::MutexLock mutex_lock(mutex_);
72 timing_table_.push_back({FormatName(subsolver.name()), subsolver.TimingInfo(),
73 subsolver.DeterministicTimingInfo()});
74}
75
76void SharedStatTables::AddSearchStat(absl::string_view name, Model* model) {
77 absl::MutexLock mutex_lock(mutex_);
78
79 // TODO(user): remove this step.
81 model->GetOrCreate<SharedResponseManager>()->FillSolveStatsInResponse(model,
82 &r);
83 if (search_table_.empty()) {
84 search_table_.push_back({"Search stats", "Bools", "Conflicts", "Branches",
85 "Restarts", "BacktrackToRoot", "Backtrack",
86 "BoolPropag", "IntegerPropag"});
87 }
88
89 SatSolver::Counters counters = model->GetOrCreate<SatSolver>()->counters();
90 search_table_.push_back({FormatName(name), FormatCounter(r.num_booleans()),
98}
99
100void SharedStatTables::AddClausesStat(absl::string_view name, Model* model) {
101 absl::MutexLock mutex_lock(mutex_);
102 auto* sat_solver = model->GetOrCreate<SatSolver>();
103 auto* binary = model->GetOrCreate<BinaryImplicationGraph>();
104 SatSolver::Counters counters = sat_solver->counters();
105 Vivifier::Counters vivify_counters =
106 model->GetOrCreate<Vivifier>()->counters();
107
108 if (clauses_table_.empty()) {
109 clauses_table_.push_back({"SAT stats", "ClassicMinim", "LitRemoved",
110 "LitRemovedBinary", "LitLearned", "LitForgotten",
111 "Subsumed"});
112 }
113 clauses_table_.push_back({FormatName(name),
114 FormatCounter(counters.num_minimizations),
115 FormatCounter(counters.num_literals_removed),
116 FormatCounter(binary->num_literals_removed()),
117 FormatCounter(counters.num_literals_learned),
118 FormatCounter(counters.num_literals_forgotten),
119 FormatCounter(counters.num_subsumed_clauses)});
120
121 if (vivify_table_.empty()) {
122 vivify_table_.push_back({"Vivification", "Clauses", "Decisions", "LitTrue",
123 "Subsumed", "LitRemoved", "DecisionReused",
124 "Conflicts"});
125 }
126 vivify_table_.push_back({FormatName(name),
127 FormatCounter(vivify_counters.num_clauses_vivified),
128 FormatCounter(vivify_counters.num_decisions),
129 FormatCounter(vivify_counters.num_true),
130 FormatCounter(vivify_counters.num_subsumed),
131 FormatCounter(vivify_counters.num_removed_literals),
132 FormatCounter(vivify_counters.num_reused),
133 FormatCounter(vivify_counters.num_conflicts)});
134
135 // Track reductions of Boolean variables.
136 if (bool_var_table_.empty()) {
137 bool_var_table_.push_back({"SAT formula", "Fixed", "Equiv", "Total",
138 "VarLeft", "BinaryClauses", "PermanentClauses",
139 "TemporaryClauses"});
140 }
141 auto* clause_manager = model->GetOrCreate<ClauseManager>();
142 const int64_t num_fixed = sat_solver->NumFixedVariables();
143 const int64_t num_equiv = binary->num_current_equivalences() / 2;
144 const int64_t num_bools = sat_solver->NumVariables();
145 bool_var_table_.push_back(
146 {FormatName(name), FormatCounter(num_fixed), FormatCounter(num_equiv),
147 FormatCounter(num_bools),
148 FormatCounter(num_bools - num_equiv - num_fixed),
149 FormatCounter(binary->ComputeNumImplicationsForLog()),
150 FormatCounter(clause_manager->num_watched_clauses() -
151 clause_manager->num_removable_clauses()),
152 FormatCounter(clause_manager->num_removable_clauses())});
153
154 // Track the "life of a non-binary clause".
156 model->GetOrCreate<SharedResponseManager>()->FillSolveStatsInResponse(model,
157 &r);
158 if (clauses_deletion_table_.empty()) {
159 clauses_deletion_table_.push_back(
160 {"Clause deletion", "at_true", "l_and_not(l)", "to_binary",
161 "sub_conflict", "sub_extra", "sub_decisions", "sub_eager",
162 "sub_vivify", "sub_probing", "sub_inpro", "blocked", "eliminated",
163 "forgotten", "promoted", "conflicts"});
164 }
165 absl::Span<const int64_t> deletion_by_source =
166 model->GetOrCreate<ClauseManager>()->DeletionCounters();
167 clauses_deletion_table_.push_back(
168 {FormatName(name),
169 FormatCounter(deletion_by_source[static_cast<int>(
171 FormatCounter(deletion_by_source[static_cast<int>(
173 FormatCounter(deletion_by_source[static_cast<int>(
175 FormatCounter(deletion_by_source[static_cast<int>(
177 FormatCounter(deletion_by_source[static_cast<int>(
179 FormatCounter(deletion_by_source[static_cast<int>(
181 FormatCounter(deletion_by_source[static_cast<int>(
183 FormatCounter(deletion_by_source[static_cast<int>(
185 FormatCounter(deletion_by_source[static_cast<int>(
187 FormatCounter(deletion_by_source[static_cast<int>(
189 FormatCounter(deletion_by_source[static_cast<int>(
191 FormatCounter(deletion_by_source[static_cast<int>(
193 FormatCounter(deletion_by_source[static_cast<int>(
197}
198
199void SharedStatTables::AddLpStat(absl::string_view name, Model* model) {
200 absl::MutexLock mutex_lock(mutex_);
201
202 // Sum per component for the lp_table.
203 int64_t num_compo = 0;
204 int64_t num_iter = 0;
205 int64_t num_cut = 0;
206 int64_t num_optimal = 0;
207 int64_t num_dual_feasible = 0;
208 int64_t num_dual_unbounded = 0;
209
210 // Last dimension of the first component for the lp_dim_table_.
211 std::string dimension;
212
213 // Sum per component for the lp_manager_table_.
214 int64_t num_constraints = 0;
215 int64_t num_constraint_updates = 0;
216 int64_t num_simplifications = 0;
217 int64_t num_merged_constraints = 0;
218 int64_t num_shortened_constraints = 0;
219 int64_t num_split_constraints = 0;
220 int64_t num_coeff_strengthening = 0;
221 int64_t num_cuts = 0;
222 int64_t num_add_cut_calls = 0;
223
224 // For the cut table.
225 absl::btree_map<std::string, int> type_to_num_cuts;
226
227 // For the debug table.
228 int64_t total_num_cut_propagations = 0;
229 int64_t total_num_eq_propagations = 0;
230 int64_t num_adjusts = 0;
231 int64_t num_cut_overflows = 0;
232 int64_t num_bad_cuts = 0;
233 int64_t num_scaling_issues = 0;
234
236 for (const auto* lp : *lps) {
237 const auto& manager = lp->constraint_manager();
238 ++num_compo;
239 num_iter += lp->total_num_simplex_iterations();
240 num_cut += manager.num_cuts();
241 const auto& num_solve_by_status = lp->num_solves_by_status();
242
243 const int optimal_as_int = static_cast<int>(glop::ProblemStatus::OPTIMAL);
244 if (optimal_as_int < num_solve_by_status.size()) {
245 num_optimal += num_solve_by_status[optimal_as_int];
246 }
247
248 const int dual_feasible_as_int =
249 static_cast<int>(glop::ProblemStatus::DUAL_FEASIBLE);
250 if (dual_feasible_as_int < num_solve_by_status.size()) {
251 num_dual_feasible += num_solve_by_status[dual_feasible_as_int];
252 }
253
254 const int dual_unbounded_as_int =
255 static_cast<int>(glop::ProblemStatus::DUAL_UNBOUNDED);
256 if (dual_unbounded_as_int < num_solve_by_status.size()) {
257 num_dual_unbounded += num_solve_by_status[dual_unbounded_as_int];
258 }
259
260 // In case of more than one component, we take the first one.
261 if (dimension.empty()) dimension = lp->DimensionString();
262
263 // Sum for the lp debug table.
264 total_num_cut_propagations += lp->total_num_cut_propagations();
265 total_num_eq_propagations += lp->total_num_eq_propagations();
266 num_adjusts += lp->num_adjusts();
267 num_cut_overflows += lp->num_cut_overflows();
268 num_bad_cuts += lp->num_bad_cuts();
269 num_scaling_issues += lp->num_scaling_issues();
270
271 // Sum for the lp manager table.
272 num_constraints += manager.num_constraints();
273 num_constraint_updates += manager.num_constraint_updates();
274 num_simplifications += manager.num_simplifications();
275 num_merged_constraints += manager.num_merged_constraints();
276 num_shortened_constraints += manager.num_shortened_constraints();
277 num_split_constraints += manager.num_split_constraints();
278 num_coeff_strengthening += manager.num_coeff_strenghtening();
279 num_cuts += manager.num_cuts();
280 num_add_cut_calls += manager.num_add_cut_calls();
281
282 for (const auto& [cut_name, num] : manager.type_to_num_cuts()) {
283 type_to_num_cuts[cut_name] += num;
284 }
285 }
286 if (num_compo == 0) return;
287
288 lp_table_.push_back(
289 {FormatName(name), FormatCounter(num_compo), FormatCounter(num_iter),
290 FormatCounter(num_cut), FormatCounter(num_optimal),
291 FormatCounter(num_dual_feasible), FormatCounter(num_dual_unbounded)});
292
293 if (!dimension.empty()) {
294 lp_dim_table_.push_back({FormatName(name), dimension});
295 }
296
297 if (!type_to_num_cuts.empty()) {
298 lp_cut_table_.push_back({std::string(name), std::move(type_to_num_cuts)});
299 }
300
301 lp_debug_table_.push_back(
302 {FormatName(name), FormatCounter(total_num_cut_propagations),
303 FormatCounter(total_num_eq_propagations), FormatCounter(num_adjusts),
304 FormatCounter(num_cut_overflows), FormatCounter(num_bad_cuts),
305 FormatCounter(num_scaling_issues)});
306
307 lp_manager_table_.push_back({FormatName(name), FormatCounter(num_constraints),
308 FormatCounter(num_constraint_updates),
309 FormatCounter(num_simplifications),
310 FormatCounter(num_merged_constraints),
311 FormatCounter(num_shortened_constraints),
312 FormatCounter(num_split_constraints),
313 FormatCounter(num_coeff_strengthening),
314 absl::StrCat(FormatCounter(num_cuts), "/",
315 FormatCounter(num_add_cut_calls))});
316}
317
318void SharedStatTables::AddLnsStat(absl::string_view name,
319 int64_t num_fully_solved_calls,
320 int64_t num_calls,
321 int64_t num_improving_calls,
322 double difficulty,
323 double deterministic_limit) {
324 absl::MutexLock mutex_lock(mutex_);
325 const double fully_solved_proportion =
326 static_cast<double>(num_fully_solved_calls) /
327 static_cast<double>(std::max(int64_t{1}, num_calls));
328 lns_table_.push_back(
329 {FormatName(name), absl::StrCat(num_improving_calls, "/", num_calls),
330 absl::StrFormat("%2.0f%%", 100 * fully_solved_proportion),
331 absl::StrFormat("%0.2e", difficulty),
332 absl::StrFormat("%0.2f", deterministic_limit)});
333}
334
335void SharedStatTables::AddLsStat(absl::string_view name, int64_t num_batches,
336 int64_t num_restarts, int64_t num_linear_moves,
337 int64_t num_general_moves,
338 int64_t num_compound_moves,
339 int64_t num_backtracks,
340 int64_t num_weight_updates,
341 int64_t num_scores_computed) {
342 absl::MutexLock mutex_lock(mutex_);
343 ls_table_.push_back(
344 {FormatName(name), FormatCounter(num_batches),
345 FormatCounter(num_restarts), FormatCounter(num_linear_moves),
346 FormatCounter(num_general_moves), FormatCounter(num_compound_moves),
347 FormatCounter(num_backtracks), FormatCounter(num_weight_updates),
348 FormatCounter(num_scores_computed)});
349}
350
352 if (!logger->LoggingIsEnabled()) return;
353
354 absl::MutexLock mutex_lock(mutex_);
355 if (timing_table_.size() > 1) SOLVER_LOG(logger, FormatTable(timing_table_));
356 if (search_table_.size() > 1) SOLVER_LOG(logger, FormatTable(search_table_));
357
358 if (bool_var_table_.size() > 1) {
359 SOLVER_LOG(logger, FormatTable(bool_var_table_));
360 }
361 if (clauses_table_.size() > 1) {
362 SOLVER_LOG(logger, FormatTable(clauses_table_));
363 }
364 if (vivify_table_.size() > 1) {
365 SOLVER_LOG(logger, FormatTable(vivify_table_));
366 }
367 if (clauses_deletion_table_.size() > 1) {
368 SOLVER_LOG(logger, FormatTable(clauses_deletion_table_));
369 }
370
371 if (lp_table_.size() > 1) SOLVER_LOG(logger, FormatTable(lp_table_));
372 if (lp_dim_table_.size() > 1) SOLVER_LOG(logger, FormatTable(lp_dim_table_));
373 if (lp_debug_table_.size() > 1) {
374 SOLVER_LOG(logger, FormatTable(lp_debug_table_));
375 }
376 if (lp_manager_table_.size() > 1) {
377 SOLVER_LOG(logger, FormatTable(lp_manager_table_));
378 }
379
380 // Construct and generate lp cut table.
381 // Note that this one is transposed compared to the normal one.
382 if (!lp_cut_table_.empty()) {
383 // Collect all line names.
384 absl::btree_map<std::string, int> lines;
385 for (const auto& [_, map] : lp_cut_table_) {
386 for (const auto& [type_name, _] : map) {
387 lines[type_name] = 0;
388 }
389 }
390
391 // Create header and compute index.
392 std::vector<std::vector<std::string>> table;
393 int line_index = 1;
394 const int num_cols = lp_cut_table_.size() + 1;
395 table.push_back({"Lp Cut"});
396 table[0].resize(num_cols, "");
397 for (const auto& [type_name, _] : lines) {
398 lines[type_name] = line_index++;
399 table.push_back({absl::StrCat(type_name, ":")});
400 table.back().resize(num_cols, "-");
401 }
402
403 // Fill lines.
404 int col_index = 1;
405 for (const auto& [name, map] : lp_cut_table_) {
406 table[0][col_index] =
407 num_cols > 10 && name.size() > 6 ? name.substr(0, 6) : name;
408 for (const auto& [type_name, count] : map) {
409 table[lines[type_name]][col_index] = FormatCounter(count);
410 }
411 ++col_index;
412 }
413
414 if (table.size() > 1) SOLVER_LOG(logger, FormatTable(table));
415 }
416
417 if (lns_table_.size() > 1) SOLVER_LOG(logger, FormatTable(lns_table_));
418 if (ls_table_.size() > 1) SOLVER_LOG(logger, FormatTable(ls_table_));
419}
420
421} // namespace operations_research::sat
void AddTimingStat(const SubSolver &subsolver)
void AddLsStat(absl::string_view name, int64_t num_batches, int64_t num_restarts, int64_t num_linear_moves, int64_t num_general_moves, int64_t num_compound_moves, int64_t num_backtracks, int64_t num_weight_updates, int64_t num_scores_computed)
void AddLnsStat(absl::string_view name, int64_t num_fully_solved_calls, int64_t num_calls, int64_t num_improving_calls, double difficulty, double deterministic_limit)
void AddSearchStat(absl::string_view name, Model *model)
void AddClausesStat(absl::string_view name, Model *model)
void AddLpStat(absl::string_view name, Model *model)
std::string DeterministicTimingInfo() const
Definition subsolver.h:129
std::string FormatName(absl::string_view name)
Definition util.h:342
std::string FormatTable(std::vector< std::vector< std::string > > &table, int spacing)
Definition util.cc:61
std::string FormatCounter(int64_t num)
Definition logging.cc:30
#define SOLVER_LOG(logger,...)
Definition logging.h:114