ampsci
High-precision calculations for one- and two-valence atomic systems
Widgets.hpp
1#pragma once
2#include "qip/omp.hpp"
3#include <atomic>
4#include <cstdio>
5#include <iostream>
6// Note: Uses POSIX isatty(). Not portable to Windows without WSL/MinGW.
7#include <unistd.h>
8
9namespace qip {
10
11/*! @brief Basic progress bar. Prints new line if (and only if) i==(max-1)
12 @details
13 - does not work well in Parellel regions. Use @ref ProgressBar in that case
14 - Prints directly to cout - creates a mess if piped to a text file.
15 Use @ref ProgressBar if that will be an issue
16*/
17inline void progbar(int i, int max, int length = 50) {
18 const int len = (length - 1);
19 const int current = int(len * double(i) / double(max - 1));
20 std::cout << "[";
21 for (auto j = 0; j < current; ++j) {
22 std::cout << "=";
23 }
24 for (auto j = current; j < len; ++j) {
25 std::cout << " ";
26 }
27 std::cout << "] \r" << std::flush;
28 if (i == max - 1)
29 std::cout << "\n";
30}
31
32//==============================================================================
33/*!
34 @brief Thread-safe progress bar for OpenMP parallel loops.
35
36 @details
37 Displays a progress bar with percentage. The progress counter uses
38 std::atomic for thread-safe updates.
39 Each call to update() increments the counter and prints the bar.
40 The output is serialised via critical section to prevent garbled output
41 from simultaneous writes.
42
43 @warning This adds overhead.
44 Prefer not to use if each OMP task is extremely small,
45 since overhead may become noticable.
46 If each task is large, overhead negligable.
47
48 - If print set to false on construction, does nothing
49 (does not print, does not track progress).
50 Just a simple way of run-time turning off.
51
52 @note
53 When stdout is not a TTY (i.e., piped), prints comma-separated percentages
54 instead of a bar: "0%, 10%, 20%, ... 100%\n". Prints approximately
55 @p Length / 5 values, spaced evenly.
56
57 The @p Length template parameter controls output width.
58 For TTY mode: total bar width in characters.
59 For non-TTY mode: determines how many percentage values are printed (~Length/5).
60
61 @note If the loop exits before the final iteration (i.e., early `break;`),
62 then final the newline `\n` will not be printed. Output may be messy.
63 Cannot break like this in OpenMP loop anyway.
64
65 For non-parallel loops, the simpler @ref qip::progbar() function should work fine.
66
67 Typical usage in a parallel loop:
68
69 @code
70 qip::ProgressBar bar(n_iterations);
71 #pragma omp parallel for schedule(dynamic)
72 for (std::size_t i = 0; i < n_iterations; ++i) {
73 // ... work ...
74 bar.update();
75 }
76 @endcode
77
78 - Put .update(); _after_ work, to avoid early "100% done" reporting
79
80 @tparam Length Total character width of the output (default 60).
81
82 @note Thread-safe; safe to call from multiple threads simultaneously.
83 But may add overhead.
84
85 @note Counts converted to `int` internally, so the maximum supported
86 iteration count is INT_MAX.
87*/
88template <std::size_t Length = 60>
90private:
91 int m_max;
92 bool m_print;
93 // Atomicly tracks progress
94 std::atomic<int> m_progress{0};
95 // TTY: bar fill chars printed last time. Non-TTY: last percentage slot printed.
96 std::atomic<int> m_last_fill{-1};
97 // Set to true once the final bar (with '\n') has been printed; guarded by
98 // omp critical so no further '\r' output can overwrite the newline.
99 bool m_done{false};
100
101 static constexpr std::size_t bar_width = (Length >= 7) ? (Length - 7) : 1;
102
103public:
104 /*!
105 @brief Construct progress bar for @p max iterations.
106 @param max Total number of iterations (denominator for percentage).
107 Accepts any integral type; converted to int internally.
108 @param print Runtime switch; if false, class does nothing.
109 */
110 template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
111 ProgressBar(T max, bool print = true)
112 : m_max(static_cast<int>(max)), m_print(print) {
113 if (m_print)
114 print_prog_bar(0);
115 }
116
117 //! Atomically increment progress counter and print updated bar.
118 void update() {
119 if (!m_print)
120 return;
121 const int progress = ++m_progress;
122 if (progress > m_max)
123 return;
124 const bool is_final = (progress == m_max);
125
126 // skip print if bar fill is unchanged (avoids critical section overhead)
127 const int current = int(bar_width * double(progress) / double(m_max));
128 if (!is_final && current <= m_last_fill.load(std::memory_order_relaxed))
129 return;
130
131 print_prog_bar(progress);
132 }
133
134private:
135 //----------------------------------------------------------------------------
136
137 // Prints progress bar (when stdout is a normal tty)
138 void print_prog_bar(int progress) {
139 const bool is_tty = isatty(fileno(stdout));
140 if (!is_tty) {
141 print_prog_bar_notty(progress);
142 return;
143 }
144
145 const bool is_final = (progress == m_max);
146
147 // build bar into stack buffer (char array)
148 const auto current = int(bar_width * double(progress) / double(m_max));
149 char buf[Length + 2];
150 char *p = buf;
151 *p++ = '[';
152 for (int j = 0; j < current; ++j)
153 *p++ = '=';
154 for (int j = current; j < (int)bar_width; ++j)
155 *p++ = ' ';
156 *p++ = ']';
157 *p++ = ' ';
158 p += snprintf(p, sizeof(buf) - std::size_t(p - buf) - 1, "%d%%",
159 int(100.0 * double(progress) / double(m_max)));
160 *p++ = is_final ? '\n' : '\r';
161 *p = '\0';
162
163 // serialise writes; skip if final bar already printed (prevents a lagging
164 // thread's '\r' from overwriting the final '\n' on the terminal)
165 bool did_print = false;
166#pragma omp critical
167 {
168 if (!m_done) {
169 fputs(buf, stdout);
170 fflush(stdout);
171 did_print = true;
172 if (is_final)
173 m_done = true;
174 }
175 }
176 if (did_print)
177 m_last_fill.store(current, std::memory_order_relaxed);
178 }
179
180 //----------------------------------------------------------------------------
181
182 // Prints progress "bar" (comma separated %) (when stdout is NOT a normal tty)
183 void print_prog_bar_notty(int progress) {
184 const bool is_initial = (progress == 0);
185 const bool is_final = (progress == m_max);
186
187 const int pct =
188 is_final ? 100 : int(100.0 * double(progress) / double(m_max));
189
190 // number of entries ~ Length/5 chars each; interval between prints in pct
191 static constexpr int n_entries = static_cast<int>(Length) / 5;
192 static constexpr int interval =
193 (n_entries > 1) ? (100 / (n_entries - 1)) : 100;
194 const int slot = pct / interval;
195
196 if (!is_initial && !is_final &&
197 slot <= m_last_fill.load(std::memory_order_relaxed))
198 return;
199
200 char buf[8];
201 if (is_final)
202 snprintf(buf, sizeof(buf), "100%%\n");
203 else
204 snprintf(buf, sizeof(buf), "%d%%, ", pct);
205
206 bool did_print = false;
207#pragma omp critical
208 {
209 if (!m_done) {
210 fputs(buf, stdout);
211 fflush(stdout);
212 did_print = true;
213 if (is_final)
214 m_done = true;
215 }
216 }
217 if (!is_final && did_print)
218 m_last_fill.store(slot, std::memory_order_relaxed);
219 }
220};
221
222} // namespace qip
Thread-safe progress bar for OpenMP parallel loops.
Definition Widgets.hpp:89
ProgressBar(T max, bool print=true)
Construct progress bar for max iterations.
Definition Widgets.hpp:111
void update()
Atomically increment progress counter and print updated bar.
Definition Widgets.hpp:118
General-purpose utility library.
Definition Array.hpp:23
void progbar(int i, int max, int length=50)
Basic progress bar. Prints new line if (and only if) i==(max-1)
Definition Widgets.hpp:17
T max(T first, Args... rest)
Returns the maximum of any number of parameters (variadic).
Definition Maths.hpp:28
Include instead of <omp.h> to allow compilation with or without OpenMP.