Google OR-Tools v9.11
a fast and portable software suite for combinatorial optimization
Loading...
Searching...
No Matches
demon_profiler.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
14#include <algorithm>
15#include <cmath>
16#include <cstddef>
17#include <cstdint>
18#include <string>
19#include <utility>
20#include <vector>
21
22#include "absl/container/flat_hash_map.h"
23#include "absl/status/status.h"
24#include "absl/strings/str_format.h"
25#include "absl/strings/string_view.h"
26#include "absl/time/clock.h"
27#include "absl/time/time.h"
28#include "ortools/base/file.h"
29#include "ortools/base/hash.h"
34#include "ortools/base/types.h"
37#include "ortools/constraint_solver/demon_profiler.pb.h"
38
39namespace operations_research {
40namespace {
41struct Container {
42 Container(const Constraint* ct_, int64_t value_) : ct(ct_), value(value_) {}
43 bool operator<(const Container& c) const { return value > c.value; }
44
45 const Constraint* ct;
46 int64_t value;
47};
48} // namespace
49
50// DemonProfiler manages the profiling of demons and allows access to gathered
51// data. Add this class as a parameter to Solver and access its information
52// after the end of a search.
54 public:
55 explicit DemonProfiler(Solver* const solver)
57 active_constraint_(nullptr),
58 active_demon_(nullptr),
59 start_time_ns_(absl::GetCurrentTimeNanos()) {}
60
61 ~DemonProfiler() override {
62 gtl::STLDeleteContainerPairSecondPointers(constraint_map_.begin(),
63 constraint_map_.end());
64 }
65
66 // In microseconds.
67 // TODO(user): rename and return nanoseconds.
68 int64_t CurrentTime() const {
69 return (absl::GetCurrentTimeNanos() - start_time_ns_) / 1000;
70 }
71
73 Constraint* const constraint) override {
74 if (solver()->state() == Solver::IN_SEARCH) {
75 return;
76 }
77
78 CHECK(active_constraint_ == nullptr);
79 CHECK(active_demon_ == nullptr);
80 CHECK(constraint != nullptr);
81 ConstraintRuns* const ct_run = new ConstraintRuns;
82 ct_run->set_constraint_id(constraint->DebugString());
83 ct_run->add_initial_propagation_start_time(CurrentTime());
84 active_constraint_ = constraint;
85 constraint_map_[constraint] = ct_run;
86 }
87
88 void EndConstraintInitialPropagation(Constraint* const constraint) override {
89 CHECK(active_constraint_ != nullptr);
90 CHECK(active_demon_ == nullptr);
91 CHECK(constraint != nullptr);
92 CHECK_EQ(constraint, active_constraint_);
93 ConstraintRuns* const ct_run = constraint_map_[constraint];
94 if (ct_run != nullptr) {
95 ct_run->add_initial_propagation_end_time(CurrentTime());
96 ct_run->set_failures(0);
97 }
98 active_constraint_ = nullptr;
99 }
100
102 Constraint* const constraint, Constraint* const delayed) override {
103 if (solver()->state() == Solver::IN_SEARCH) {
104 return;
105 }
106
107 CHECK(active_constraint_ == nullptr);
108 CHECK(active_demon_ == nullptr);
109 CHECK(constraint != nullptr);
110 CHECK(delayed != nullptr);
111 ConstraintRuns* const ct_run = constraint_map_[constraint];
112 ct_run->add_initial_propagation_start_time(CurrentTime());
113 active_constraint_ = constraint;
114 }
115
117 Constraint* const constraint, Constraint* const delayed) override {
118 CHECK(active_constraint_ != nullptr);
119 CHECK(active_demon_ == nullptr);
120 CHECK(constraint != nullptr);
121 CHECK(delayed != nullptr);
122 CHECK_EQ(constraint, active_constraint_);
123 ConstraintRuns* const ct_run = constraint_map_[constraint];
124 if (ct_run != nullptr) {
125 ct_run->add_initial_propagation_end_time(CurrentTime());
126 ct_run->set_failures(0);
127 }
128 active_constraint_ = nullptr;
129 }
130
131 void RegisterDemon(Demon* const demon) override {
132 if (solver()->state() == Solver::IN_SEARCH) {
133 return;
134 }
135
136 if (demon_map_.find(demon) == demon_map_.end()) {
137 CHECK(active_constraint_ != nullptr);
138 CHECK(active_demon_ == nullptr);
139 CHECK(demon != nullptr);
140 ConstraintRuns* const ct_run = constraint_map_[active_constraint_];
141 DemonRuns* const demon_run = ct_run->add_demons();
142 demon_run->set_demon_id(demon->DebugString());
143 demon_run->set_failures(0);
144 demon_map_[demon] = demon_run;
145 demons_per_constraint_[active_constraint_].push_back(demon_run);
146 }
147 }
148
149 void BeginDemonRun(Demon* const demon) override {
150 CHECK(demon != nullptr);
151 if (demon->priority() == Solver::VAR_PRIORITY) {
152 return;
153 }
154 CHECK(active_demon_ == nullptr);
155 active_demon_ = demon;
156 DemonRuns* const demon_run = demon_map_[active_demon_];
157 if (demon_run != nullptr) {
158 demon_run->add_start_time(CurrentTime());
159 }
160 }
161
162 void EndDemonRun(Demon* const demon) override {
163 CHECK(demon != nullptr);
164 if (demon->priority() == Solver::VAR_PRIORITY) {
165 return;
166 }
167 CHECK_EQ(active_demon_, demon);
168 DemonRuns* const demon_run = demon_map_[active_demon_];
169 if (demon_run != nullptr) {
170 demon_run->add_end_time(CurrentTime());
171 }
172 active_demon_ = nullptr;
173 }
174
176 void EndProcessingIntegerVariable(IntVar* const var) override {}
177 void PushContext(const std::string& context) override {}
178 void PopContext() override {}
179
180 void BeginFail() override {
181 if (active_demon_ != nullptr) {
182 DemonRuns* const demon_run = demon_map_[active_demon_];
183 if (demon_run != nullptr) {
184 demon_run->add_end_time(CurrentTime());
185 demon_run->set_failures(demon_run->failures() + 1);
186 }
187 active_demon_ = nullptr;
188 // active_constraint_ can be non null in case of initial propagation.
189 active_constraint_ = nullptr;
190 } else if (active_constraint_ != nullptr) {
191 ConstraintRuns* const ct_run = constraint_map_[active_constraint_];
192 if (ct_run != nullptr) {
193 ct_run->add_initial_propagation_end_time(CurrentTime());
194 ct_run->set_failures(1);
195 }
196 active_constraint_ = nullptr;
197 }
198 }
199
200 // Restarts a search and clears all previously collected information.
201 void RestartSearch() override {
202 gtl::STLDeleteContainerPairSecondPointers(constraint_map_.begin(),
203 constraint_map_.end());
204 constraint_map_.clear();
205 demon_map_.clear();
206 demons_per_constraint_.clear();
207 }
208
209 // IntExpr modifiers.
210 void SetMin(IntExpr* const expr, int64_t new_min) override {}
211 void SetMax(IntExpr* const expr, int64_t new_max) override {}
212 void SetRange(IntExpr* const expr, int64_t new_min,
213 int64_t new_max) override {}
214 // IntVar modifiers.
215 void SetMin(IntVar* const var, int64_t new_min) override {}
216 void SetMax(IntVar* const var, int64_t new_max) override {}
217 void SetRange(IntVar* const var, int64_t new_min, int64_t new_max) override {}
218 void RemoveValue(IntVar* const var, int64_t value) override {}
219 void SetValue(IntVar* const var, int64_t value) override {}
220 void RemoveInterval(IntVar* const var, int64_t imin, int64_t imax) override {}
221 void SetValues(IntVar* const var,
222 const std::vector<int64_t>& values) override {}
224 const std::vector<int64_t>& values) override {}
225 // IntervalVar modifiers.
226 void SetStartMin(IntervalVar* const var, int64_t new_min) override {}
227 void SetStartMax(IntervalVar* const var, int64_t new_max) override {}
228 void SetStartRange(IntervalVar* const var, int64_t new_min,
229 int64_t new_max) override {}
230 void SetEndMin(IntervalVar* const var, int64_t new_min) override {}
231 void SetEndMax(IntervalVar* const var, int64_t new_max) override {}
232 void SetEndRange(IntervalVar* const var, int64_t new_min,
233 int64_t new_max) override {}
234 void SetDurationMin(IntervalVar* const var, int64_t new_min) override {}
235 void SetDurationMax(IntervalVar* const var, int64_t new_max) override {}
236 void SetDurationRange(IntervalVar* const var, int64_t new_min,
237 int64_t new_max) override {}
238 void SetPerformed(IntervalVar* const var, bool value) override {}
239 void RankFirst(SequenceVar* const var, int index) override {}
240 void RankNotFirst(SequenceVar* const var, int index) override {}
241 void RankLast(SequenceVar* const var, int index) override {}
242 void RankNotLast(SequenceVar* const var, int index) override {}
243 void RankSequence(SequenceVar* const var, const std::vector<int>& rank_first,
244 const std::vector<int>& rank_last,
245 const std::vector<int>& unperformed) override {}
246
247 // Useful for unit tests.
248 void AddFakeRun(Demon* const demon, int64_t start_time, int64_t end_time,
249 bool is_fail) {
250 CHECK(demon != nullptr);
251 DemonRuns* const demon_run = demon_map_[demon];
252 CHECK(demon_run != nullptr);
253 demon_run->add_start_time(start_time);
254 demon_run->add_end_time(end_time);
255 if (is_fail) {
256 demon_run->set_failures(demon_run->failures() + 1);
257 }
258 }
259
260 // Exports collected data as human-readable text.
261 void PrintOverview(Solver* const solver, absl::string_view filename) {
262 const char* const kConstraintFormat =
263 " - Constraint: %s\n failures=%d, initial propagation "
264 "runtime=%d us, demons=%d, demon invocations=%d, total demon "
265 "runtime=%d us\n";
266 const char* const kDemonFormat =
267 " --- Demon: %s\n invocations=%d, failures=%d, total "
268 "runtime=%d us, [average=%.2lf, median=%.2lf, stddev=%.2lf]\n";
269 File* file;
270 const std::string model =
271 absl::StrFormat("Model %s:\n", solver->model_name());
272 if (file::Open(filename, "w", &file, file::Defaults()).ok()) {
273 file::WriteString(file, model, file::Defaults()).IgnoreError();
274 std::vector<Container> to_sort;
275 for (absl::flat_hash_map<const Constraint*,
276 ConstraintRuns*>::const_iterator it =
277 constraint_map_.begin();
278 it != constraint_map_.end(); ++it) {
279 const Constraint* const ct = it->first;
280 int64_t fails = 0;
281 int64_t demon_invocations = 0;
282 int64_t initial_propagation_runtime = 0;
283 int64_t total_demon_runtime = 0;
284 int demon_count = 0;
285 ExportInformation(ct, &fails, &initial_propagation_runtime,
286 &demon_invocations, &total_demon_runtime,
287 &demon_count);
288 to_sort.push_back(
289 Container(ct, total_demon_runtime + initial_propagation_runtime));
290 }
291 std::sort(to_sort.begin(), to_sort.end());
292
293 for (int i = 0; i < to_sort.size(); ++i) {
294 const Constraint* const ct = to_sort[i].ct;
295 int64_t fails = 0;
296 int64_t demon_invocations = 0;
297 int64_t initial_propagation_runtime = 0;
298 int64_t total_demon_runtime = 0;
299 int demon_count = 0;
300 ExportInformation(ct, &fails, &initial_propagation_runtime,
301 &demon_invocations, &total_demon_runtime,
302 &demon_count);
303 const std::string constraint_message =
304 absl::StrFormat(kConstraintFormat, ct->DebugString(), fails,
305 initial_propagation_runtime, demon_count,
306 demon_invocations, total_demon_runtime);
307 file::WriteString(file, constraint_message, file::Defaults())
308 .IgnoreError();
309 const std::vector<DemonRuns*>& demons = demons_per_constraint_[ct];
310 const int demon_size = demons.size();
311 for (int demon_index = 0; demon_index < demon_size; ++demon_index) {
312 DemonRuns* const demon_runs = demons[demon_index];
313 int64_t invocations = 0;
314 int64_t fails = 0;
315 int64_t runtime = 0;
316 double mean_runtime = 0;
317 double median_runtime = 0;
318 double standard_deviation = 0.0;
319 ExportInformation(demon_runs, &invocations, &fails, &runtime,
320 &mean_runtime, &median_runtime,
321 &standard_deviation);
322 const std::string runs = absl::StrFormat(
323 kDemonFormat, demon_runs->demon_id(), invocations, fails, runtime,
324 mean_runtime, median_runtime, standard_deviation);
325 file::WriteString(file, runs, file::Defaults()).IgnoreError();
326 }
327 }
328 }
329 file->Close(file::Defaults()).IgnoreError();
330 }
331
332 // Export Information
333 void ExportInformation(const Constraint* const constraint,
334 int64_t* const fails,
335 int64_t* const initial_propagation_runtime,
336 int64_t* const demon_invocations,
337 int64_t* const total_demon_runtime, int* demons) {
338 CHECK(constraint != nullptr);
339 ConstraintRuns* const ct_run = constraint_map_[constraint];
340 CHECK(ct_run != nullptr);
341 *demon_invocations = 0;
342 *fails = ct_run->failures();
343 *initial_propagation_runtime = 0;
344 for (int i = 0; i < ct_run->initial_propagation_start_time_size(); ++i) {
345 *initial_propagation_runtime += ct_run->initial_propagation_end_time(i) -
346 ct_run->initial_propagation_start_time(i);
347 }
348 *total_demon_runtime = 0;
349
350 // Gather information.
351 *demons = ct_run->demons_size();
352 CHECK_EQ(*demons, demons_per_constraint_[constraint].size());
353 for (int demon_index = 0; demon_index < *demons; ++demon_index) {
354 const DemonRuns& demon_runs = ct_run->demons(demon_index);
355 *fails += demon_runs.failures();
356 CHECK_EQ(demon_runs.start_time_size(), demon_runs.end_time_size());
357 const int runs = demon_runs.start_time_size();
358 *demon_invocations += runs;
359 for (int run_index = 0; run_index < runs; ++run_index) {
360 const int64_t demon_time =
361 demon_runs.end_time(run_index) - demon_runs.start_time(run_index);
362 *total_demon_runtime += demon_time;
363 }
364 }
365 }
366
367 void ExportInformation(const DemonRuns* const demon_runs,
368 int64_t* const demon_invocations, int64_t* const fails,
369 int64_t* const total_demon_runtime,
370 double* const mean_demon_runtime,
371 double* const median_demon_runtime,
372 double* const stddev_demon_runtime) {
373 CHECK(demon_runs != nullptr);
374 CHECK_EQ(demon_runs->start_time_size(), demon_runs->end_time_size());
375
376 const int runs = demon_runs->start_time_size();
377 *demon_invocations = runs;
378 *fails = demon_runs->failures();
379 *total_demon_runtime = 0;
380 *mean_demon_runtime = 0.0;
381 *median_demon_runtime = 0.0;
382 *stddev_demon_runtime = 0.0;
383 std::vector<double> runtimes;
384 for (int run_index = 0; run_index < runs; ++run_index) {
385 const int64_t demon_time =
386 demon_runs->end_time(run_index) - demon_runs->start_time(run_index);
387 *total_demon_runtime += demon_time;
388 runtimes.push_back(demon_time);
389 }
390 // Compute mean.
391 if (!runtimes.empty()) {
392 *mean_demon_runtime = (1.0L * *total_demon_runtime) / runtimes.size();
393
394 // Compute median.
395 std::sort(runtimes.begin(), runtimes.end());
396 const int pivot = runtimes.size() / 2;
397
398 if (runtimes.size() == 1) {
399 *median_demon_runtime = runtimes[0];
400 } else {
401 *median_demon_runtime =
402 runtimes.size() % 2 == 1
403 ? runtimes[pivot]
404 : (runtimes[pivot - 1] + runtimes[pivot]) / 2.0;
405 }
406
407 // Compute standard deviation.
408 double total_deviation = 0.0f;
409
410 for (int i = 0; i < runtimes.size(); ++i) {
411 total_deviation += pow(runtimes[i] - *mean_demon_runtime, 2);
412 }
413
414 *stddev_demon_runtime = sqrt(total_deviation / runtimes.size());
415 }
416 }
417
418 // The demon_profiler is added by default on the main propagation
419 // monitor. It just needs to be added to the search monitors at the
420 // start of the search.
421 void Install() override { SearchMonitor::Install(); }
422
423 std::string DebugString() const override { return "DemonProfiler"; }
424
425 private:
426 Constraint* active_constraint_;
427 Demon* active_demon_;
428 const int64_t start_time_ns_;
429 absl::flat_hash_map<const Constraint*, ConstraintRuns*> constraint_map_;
430 absl::flat_hash_map<const Demon*, DemonRuns*> demon_map_;
431 absl::flat_hash_map<const Constraint*, std::vector<DemonRuns*> >
432 demons_per_constraint_;
433};
434
435void Solver::ExportProfilingOverview(const std::string& filename) {
436 if (demon_profiler_ != nullptr) {
437 demon_profiler_->PrintOverview(this, filename);
438 }
439}
440
441// ----- Exported Functions -----
442
443void InstallDemonProfiler(DemonProfiler* monitor) { monitor->Install(); }
444
446 if (solver->IsProfilingEnabled()) {
447 return new DemonProfiler(solver);
448 } else {
449 return nullptr;
450 }
451}
452
453void DeleteDemonProfiler(DemonProfiler* monitor) { delete monitor; }
454
456 CHECK(demon != nullptr);
457 if (InstrumentsDemons()) {
458 propagation_monitor_->RegisterDemon(demon);
459 }
460 return demon;
461}
462
463// ----- Exported Methods for Unit Tests -----
464
465void RegisterDemon(Solver* const solver, Demon* const demon,
466 DemonProfiler* const monitor) {
467 monitor->RegisterDemon(demon);
468}
469
470void DemonProfilerAddFakeRun(DemonProfiler* const monitor, Demon* const demon,
471 int64_t start_time, int64_t end_time,
472 bool is_fail) {
473 monitor->AddFakeRun(demon, start_time, end_time, is_fail);
474}
475
477 const Constraint* const constraint,
478 int64_t* const fails,
479 int64_t* const initial_propagation_runtime,
480 int64_t* const demon_invocations,
481 int64_t* const total_demon_runtime,
482 int* const demon_count) {
483 monitor->ExportInformation(constraint, fails, initial_propagation_runtime,
484 demon_invocations, total_demon_runtime,
485 demon_count);
486}
487
489 Constraint* const constraint) {
490 monitor->BeginConstraintInitialPropagation(constraint);
491}
492
494 Constraint* const constraint) {
495 monitor->EndConstraintInitialPropagation(constraint);
496}
497
498} // namespace operations_research
IntegerValue size
Definition file.h:30
std::string DebugString() const override
--------------— Constraint class ----------------—
void RemoveValues(IntVar *const var, const std::vector< int64_t > &values) override
void PrintOverview(Solver *const solver, absl::string_view filename)
Exports collected data as human-readable text.
void SetEndRange(IntervalVar *const var, int64_t new_min, int64_t new_max) override
void SetMax(IntVar *const var, int64_t new_max) override
void AddFakeRun(Demon *const demon, int64_t start_time, int64_t end_time, bool is_fail)
Useful for unit tests.
void EndProcessingIntegerVariable(IntVar *const var) override
void RankNotLast(SequenceVar *const var, int index) override
DemonProfiler(Solver *const solver)
void SetStartRange(IntervalVar *const var, int64_t new_min, int64_t new_max) override
void RankSequence(SequenceVar *const var, const std::vector< int > &rank_first, const std::vector< int > &rank_last, const std::vector< int > &unperformed) override
void SetEndMax(IntervalVar *const var, int64_t new_max) override
void SetPerformed(IntervalVar *const var, bool value) override
void ExportInformation(const DemonRuns *const demon_runs, int64_t *const demon_invocations, int64_t *const fails, int64_t *const total_demon_runtime, double *const mean_demon_runtime, double *const median_demon_runtime, double *const stddev_demon_runtime)
void SetDurationMin(IntervalVar *const var, int64_t new_min) override
void EndConstraintInitialPropagation(Constraint *const constraint) override
void ExportInformation(const Constraint *const constraint, int64_t *const fails, int64_t *const initial_propagation_runtime, int64_t *const demon_invocations, int64_t *const total_demon_runtime, int *demons)
Export Information.
void SetValue(IntVar *const var, int64_t value) override
void SetMin(IntVar *const var, int64_t new_min) override
IntVar modifiers.
void RankNotFirst(SequenceVar *const var, int index) override
void SetEndMin(IntervalVar *const var, int64_t new_min) override
void SetDurationRange(IntervalVar *const var, int64_t new_min, int64_t new_max) override
void BeginNestedConstraintInitialPropagation(Constraint *const constraint, Constraint *const delayed) override
void SetMax(IntExpr *const expr, int64_t new_max) override
void SetStartMax(IntervalVar *const var, int64_t new_max) override
void BeginFail() override
Just when the failure occurs.
void BeginConstraintInitialPropagation(Constraint *const constraint) override
Propagation events.
void RankLast(SequenceVar *const var, int index) override
void EndDemonRun(Demon *const demon) override
void SetRange(IntVar *const var, int64_t new_min, int64_t new_max) override
void BeginDemonRun(Demon *const demon) override
void StartProcessingIntegerVariable(IntVar *const var) override
void SetDurationMax(IntervalVar *const var, int64_t new_max) override
void SetMin(IntExpr *const expr, int64_t new_min) override
IntExpr modifiers.
std::string DebugString() const override
void RankFirst(SequenceVar *const var, int index) override
SequenceVar modifiers.
void RegisterDemon(Demon *const demon) override
void SetStartMin(IntervalVar *const var, int64_t new_min) override
IntervalVar modifiers.
void SetRange(IntExpr *const expr, int64_t new_min, int64_t new_max) override
void RestartSearch() override
Restarts a search and clears all previously collected information.
void SetValues(IntVar *const var, const std::vector< int64_t > &values) override
void RemoveInterval(IntVar *const var, int64_t imin, int64_t imax) override
void RemoveValue(IntVar *const var, int64_t value) override
void PushContext(const std::string &context) override
void EndNestedConstraintInitialPropagation(Constraint *const constraint, Constraint *const delayed) override
virtual Solver::DemonPriority priority() const
---------------— Demon class -------------—
std::string DebugString() const override
virtual void Install()
A search monitors adds itself on the active search.
For the time being, Solver is neither MT_SAFE nor MT_HOT.
Demon * RegisterDemon(Demon *demon)
Adds a new demon and wraps it inside a DemonProfiler if necessary.
void ExportProfilingOverview(const std::string &filename)
@ VAR_PRIORITY
VAR_PRIORITY is between DELAYED_PRIORITY and NORMAL_PRIORITY.
@ IN_SEARCH
Executing the search code.
std::string model_name() const
Returns the name of the model.
bool IsProfilingEnabled() const
Returns whether we are profiling the solver.
bool InstrumentsDemons() const
Returns whether we are instrumenting demons.
const int runs
const Constraint * ct
int64_t value
IntVar * var
GRBmodel * model
GurobiMPCallbackContext * context
int index
Definition file.cc:169
absl::Status Open(absl::string_view filename, absl::string_view mode, File **f, Options options)
As of 2016-01, these methods can only be used with flags = file::Defaults().
Definition file.cc:170
absl::Status WriteString(File *file, absl::string_view contents, Options options)
Definition file.cc:231
Options Defaults()
Definition file.h:109
void STLDeleteContainerPairSecondPointers(ForwardIterator begin, ForwardIterator end)
Definition stl_util.h:353
In SWIG mode, we don't want anything besides these top-level includes.
void DeleteDemonProfiler(DemonProfiler *monitor)
void DemonProfilerEndInitialPropagation(DemonProfiler *const monitor, Constraint *const constraint)
DemonProfiler * BuildDemonProfiler(Solver *solver)
void DemonProfilerExportInformation(DemonProfiler *const monitor, const Constraint *const constraint, int64_t *const fails, int64_t *const initial_propagation_runtime, int64_t *const demon_invocations, int64_t *const total_demon_runtime, int *const demon_count)
void RegisterDemon(Solver *const solver, Demon *const demon, DemonProfiler *const monitor)
--— Exported Methods for Unit Tests --—
void DemonProfilerBeginInitialPropagation(DemonProfiler *const monitor, Constraint *const constraint)
void DemonProfilerAddFakeRun(DemonProfiler *const monitor, Demon *const demon, int64_t start_time, int64_t end_time, bool is_fail)
void InstallDemonProfiler(DemonProfiler *monitor)
--— Forward Declarations and Profiling Support --—