Skip to content

Commit c9dd236

Browse files
author
Laxman Dhulipala
committed
Add code for reporting progress to gbbs/helpers
1 parent d062ec1 commit c9dd236

File tree

7 files changed

+1072
-0
lines changed

7 files changed

+1072
-0
lines changed

gbbs/helpers/BUILD

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
licenses(["notice"])
22

3+
load("//internal_tools:build_defs.bzl", "gbbs_cc_test")
4+
35
package(
46
default_visibility = ["//visibility:public"],
57
)
@@ -59,6 +61,41 @@ cc_library(
5961
hdrs = ["parse_command_line.h"],
6062
)
6163

64+
cc_library(
65+
name = "progress_reporting",
66+
srcs = ["progress_reporting.cc"],
67+
hdrs = ["progress_reporting.h"],
68+
deps = [
69+
"@abseil-cpp//absl/functional:any_invocable",
70+
"@abseil-cpp//absl/log:absl_check",
71+
"@abseil-cpp//absl/status",
72+
"@abseil-cpp//absl/strings",
73+
],
74+
)
75+
76+
gbbs_cc_test(
77+
name = "progress_reporting_test",
78+
srcs = ["progress_reporting_test.cc"],
79+
deps = [
80+
":progress_reporting",
81+
":progress_reporting_mock",
82+
":status_macros",
83+
"@abseil-cpp//absl/status",
84+
"@googletest//:gtest_main",
85+
],
86+
)
87+
88+
cc_library(
89+
name = "progress_reporting_mock",
90+
testonly = True,
91+
hdrs = ["progress_reporting_mock.h"],
92+
deps = [
93+
":progress_reporting",
94+
"@abseil-cpp//absl/base:core_headers",
95+
],
96+
)
97+
98+
6299
cc_library(
63100
name = "resizable_table",
64101
hdrs = ["resizable_table.h"],
@@ -94,6 +131,18 @@ cc_library(
94131
],
95132
)
96133

134+
cc_library(
135+
name = "status_macros",
136+
srcs = ["status_macros.cc"],
137+
hdrs = ["status_macros.h"],
138+
deps = [
139+
"@googletest//:gtest",
140+
"@abseil-cpp//absl/log:absl_log",
141+
"@abseil-cpp//absl/status",
142+
"@abseil-cpp//absl/status:statusor",
143+
],
144+
)
145+
97146
cc_library(
98147
name = "undirected_edge",
99148
srcs = ["undirected_edge.cc"],

gbbs/helpers/progress_reporting.cc

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
#include "gbbs/helpers/progress_reporting.h"
2+
3+
#include "absl/status/status.h"
4+
#include "absl/strings/str_cat.h"
5+
6+
namespace gbbs {
7+
8+
absl::Status IterationProgressReporter::PreprocessingComplete() {
9+
if (!has_preprocessing_) {
10+
return absl::FailedPreconditionError(
11+
"'PreprocessingComplete()' called on an instance with "
12+
"'has_preprocessing' of false.");
13+
}
14+
if (num_steps_completed_ != 0) {
15+
return absl::FailedPreconditionError(
16+
"'PreprocessingComplete()' called more than once.");
17+
}
18+
++num_steps_completed_;
19+
ReportProgress();
20+
return absl::OkStatus();
21+
}
22+
23+
absl::Status IterationProgressReporter::IterationComplete(int iteration) {
24+
if (has_preprocessing_ && num_steps_completed_ == 0) {
25+
return absl::FailedPreconditionError(
26+
"'PreprocessingComplete()' must be called before any call of "
27+
"'IterationComplete()'.");
28+
}
29+
if (num_iterations_ <= 0) {
30+
return absl::InvalidArgumentError(
31+
"'num_iterations' must be strictly positive.");
32+
}
33+
if (iteration < 0 || iteration >= num_iterations_) {
34+
return absl::InvalidArgumentError(
35+
"'iteration' must be in the half-open interval [0, num_iterations).");
36+
}
37+
38+
const int expected_iteration =
39+
num_steps_completed_ - (has_preprocessing_ ? 1 : 0);
40+
if (iteration != expected_iteration) {
41+
return absl::InvalidArgumentError(
42+
absl::StrCat("'iteration' must be monotonically ascending; expected ",
43+
expected_iteration, " but got ", iteration, "."));
44+
}
45+
46+
++num_steps_completed_;
47+
ReportProgress();
48+
return absl::OkStatus();
49+
}
50+
51+
absl::Status IterationProgressReporter::IterationsDoneEarly() {
52+
if (has_preprocessing_ && num_steps_completed_ == 0) {
53+
return absl::FailedPreconditionError(
54+
"'PreprocessingComplete()' must be called before any call of "
55+
"'IterationsDoneEarly()'.");
56+
}
57+
num_steps_completed_ = (num_iterations_ - 1) + (has_preprocessing_ ? 1 : 0);
58+
return IterationComplete(num_iterations_ - 1);
59+
}
60+
61+
absl::Status IterationProgressReporter::PostprocessingComplete() {
62+
if (!has_postprocessing_) {
63+
return absl::FailedPreconditionError(
64+
"'PostprocessingComplete()' called on an instance with "
65+
"'has_postprocessing' of false.");
66+
}
67+
if (num_steps_completed_ <= (has_preprocessing_ ? 1 : 0)) {
68+
return absl::FailedPreconditionError(
69+
"'PostprocessingComplete()' called before any call of "
70+
"IterationComplete()' or 'IterationsDoneEarly()'.");
71+
}
72+
num_steps_completed_ = total_steps_;
73+
ReportProgress();
74+
return absl::OkStatus();
75+
}
76+
77+
void IterationProgressReporter::ReportProgress() {
78+
report_progress_(static_cast<float>(num_steps_completed_) / total_steps_);
79+
}
80+
81+
} // namespace gbbs

gbbs/helpers/progress_reporting.h

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#pragma once
2+
3+
// This library contains utilities for reporting the progress of the execution
4+
// of an algorithm.
5+
6+
#include <utility>
7+
8+
#include "absl/functional/any_invocable.h"
9+
#include "absl/status/status.h"
10+
11+
namespace gbbs {
12+
13+
// Callable object to be used for reporting the progress of the execution of an
14+
// algorithm, as a number in the closed interval [0.0, 1.0]. Algorithms must
15+
// report non-decreasing values in this range; values closer to 1.0 indicate
16+
// that the algorithm is closer to completion.
17+
//
18+
// Note: beyond the non-decreasing requirement, there's no requirement on the
19+
// concrete meaning of the reported numbers (in particular, the progress numbers
20+
// reported need not be proportional to time elapsed since the start of the
21+
// algorithm). There's also no requirement on how many times, or how often, the
22+
// object is called.
23+
//
24+
// Typically, an algorithm will make at least two calls; the first one reporting
25+
// a value of 0.0 (at the very beginning of the execution of the algorithm), and
26+
// the final one reporting a value of 1.0 (at the very end of the execution of
27+
// the algorithm).
28+
using ReportProgressT = absl::AnyInvocable<void(float progress)>;
29+
30+
// Wrapper for a `ReportProgress` instance that's useful for algorithms with the
31+
// following structure:
32+
//
33+
// - Preprocessing (optional)
34+
// - One or more iterations
35+
// - Postprocessing (optional)
36+
//
37+
// This class provides a convenient way to report the progress of the algorithm
38+
// execution. It makes the simplifying assumption that the preprocessing step
39+
// (if any), postprocessing step (if any), and each iteration all represent
40+
// equal amounts of progress.
41+
class IterationProgressReporter {
42+
public:
43+
// Creates a new `IterationProgressReporter` instance.
44+
//
45+
// Arguments:
46+
// * `report_progress`: the instance to be used for reporting the progress of
47+
// the algorithm execution.
48+
// * `num_iterations`: the number of iterations (must be strictly positive).
49+
// * `has_preprocessing`: indicates whether the algorithm has a preprocessing
50+
// step.
51+
// * `has_postprocessing`: indicates whether the algorithm has a
52+
// postprocessing step.
53+
IterationProgressReporter(ReportProgressT report_progress, int num_iterations,
54+
bool has_preprocessing = false,
55+
bool has_postprocessing = false)
56+
: report_progress_(std::move(report_progress)),
57+
num_iterations_(num_iterations),
58+
has_preprocessing_(has_preprocessing),
59+
has_postprocessing_(has_postprocessing),
60+
total_steps_(num_iterations + (has_preprocessing ? 1 : 0) +
61+
(has_postprocessing ? 1 : 0)) {}
62+
63+
// Indicate that the preprocessing step is complete. Returns an error status
64+
// if `has_preprocessing_` is false or if any call of `IterationComplete()`
65+
// has been made.
66+
absl::Status PreprocessingComplete();
67+
68+
// Indicates that the `iteration`-th iteration is complete. The `iteration`
69+
// value must be in the half-open interval [0, `num_iterations_`) (i.e.,
70+
// iteration indices are 0-based). At least one call of
71+
// this method (with argument 0) must be made.
72+
//
73+
// Returns an error status if `num_iterations` is non-positive, if
74+
// preprocessing was indicated but `PreprocessingComplete()` has not been
75+
// called, or if successive calls of this method do not pass monotonically
76+
// ascending `iteration` values of 1, 2, ..., `num_iterations`.
77+
absl::Status IterationComplete(int iteration);
78+
79+
// Indicates that all iterations are complete. This is equivalent to calling
80+
// `IterationComplete(num_iterations)`, but can be used in cases where the
81+
// algorithm detects that it has converged early and not all expected
82+
// iterations are required
83+
//
84+
// Returns an error status if `num_iterations` is non-positive or if
85+
// preprocessing was indicated but `PreprocessingComplete()` has not been
86+
// called.
87+
absl::Status IterationsDoneEarly();
88+
89+
// Indicate that the postprocessing step is complete. Returns an error status
90+
// if `has_postprocessing_` is false or if no call of `IterationComplete()`
91+
// has been made.
92+
absl::Status PostprocessingComplete();
93+
94+
private:
95+
/*const*/ ReportProgressT report_progress_;
96+
const int num_iterations_;
97+
const bool has_preprocessing_;
98+
const bool has_postprocessing_;
99+
const int total_steps_;
100+
int num_steps_completed_ = 0;
101+
102+
// Invokes the `report_progress_` callback so as to reflect
103+
// `num_steps_completed_` worth of progress out of `total_steps_`.
104+
void ReportProgress();
105+
};
106+
107+
} // namespace gbbs
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
#pragma once
2+
3+
#include <vector>
4+
5+
#include "absl/base/attributes.h"
6+
#include "gbbs/helpers/progress_reporting.h"
7+
8+
namespace gbbs {
9+
10+
// Test-only class for mocking the progress reporting of an algorithm. This
11+
// is intended to be a mix-in class to be combined into a test fixture, like
12+
// this:
13+
//
14+
// class MyAlgorithmTest : public testing::Test, public ReportProgressMock {
15+
// // ...
16+
// };
17+
//
18+
// TEST_F(MyAlgorithmTest, DoesFoo) {
19+
// // Invoke method under test.
20+
// EXPECT_THAT(clusterer_.Cluster(..., MockReportProgress()),
21+
// IsOkAndHolds(...));
22+
// // Check that the progress reports were as expected.
23+
// EXPECT_THAT(GetProgressReports(), ElementsAre(0.0, 1.0));
24+
// }
25+
//
26+
// Note that because `MockReportProgress()` clears any previous progress
27+
// reports, multiple `MockReportProgress()` ... `GetProgressReports()` call
28+
// pairs can be used in a single test.
29+
class ReportProgressMock {
30+
public:
31+
// Returns a new ReportProgress `AnyInvocable` that records its successive
32+
// arguments in `progress_reports_`. Any previously recorded progress reports
33+
// are cleared.
34+
ReportProgressT MockReportProgress() ABSL_ATTRIBUTE_LIFETIME_BOUND {
35+
progress_reports_.clear();
36+
return [this](float progress) {
37+
this->progress_reports_.push_back(progress);
38+
};
39+
}
40+
41+
// Returns the arguments to the sequence of progress report calls.
42+
std::vector<float> GetProgressReports() const { return progress_reports_; }
43+
44+
private:
45+
std::vector<float> progress_reports_;
46+
};
47+
48+
} // namespace gbbs

0 commit comments

Comments
 (0)