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#include <string>
7#include <string_view>
8// Note: Uses POSIX isatty(). Not portable to Windows without WSL/MinGW.
9#include <unistd.h>
10
11namespace qip {
12
13/*!
14 @brief Live status line for iterative loops; overwrites itself on a TTY.
15 @details
16 Construct before the loop, call operator() each iteration with the current
17 status message, then call done() (optionally with a trailing annotation) when
18 the loop finishes. The destructor calls done() automatically if it has not
19 been called explicitly, so early returns and exceptions are handled cleanly.
20
21 Behaviour depends on whether stdout is a TTY:
22 - TTY: each operator() call prints `\r{header}{msg}` and flushes,
23 overwriting the current line. done() prints `\r{header}{last_msg}{post}\n`.
24 - Non-TTY: the header is printed on construction; operator() calls are
25 silent (but the message is buffered). done() prints `{last_msg}{post}\n`.
26 This gives one clean output line with no intermediate churn.
27
28 If @p active is false all methods are no-ops (runtime print toggle).
29
30 Typical usage:
31 @code
32 bool print_message = true;
33 qip::LiveMessage status("Method iterations: ", print_message);
34 for (int it = 0; it < max_its; ++it) {
35 // ... work ...
36 status(fmt::format("{:2d} {:.1e} [{}]", it, eps, worst));
37 if (condition)
38 break;
39 }
40 // optional trailing annotation; destructor calls done() otherwise
41 status.done(" Finished");
42 @endcode
43
44 @param header Fixed prefix, always printed (on construction for non-TTY,
45 or on every line for TTY).
46 @param active If false, all methods are no-ops. Default true.
47*/
49private:
50 std::string m_header;
51 bool m_is_tty;
52 bool m_active;
53 std::string m_last_msg{};
54 bool m_done{false};
55
56public:
57 explicit LiveMessage(std::string_view header, bool active = true)
58 : m_header(header), m_is_tty(isatty(fileno(stdout))), m_active(active) {
59 if (m_active && !m_is_tty) {
60 std::fwrite(m_header.data(), 1, m_header.size(), stdout);
61 std::fflush(stdout);
62 }
63 }
64
65 ~LiveMessage() { done(); }
66
67 // Non-copyable: owns the "print header once" invariant
68 LiveMessage(const LiveMessage &) = delete;
69 LiveMessage &operator=(const LiveMessage &) = delete;
70
71 //! Update the status message. On TTY overwrites the current line; on
72 //! non-TTY buffers silently until done() is called.
73 void update(std::string_view msg) {
74 if (!m_active || m_done)
75 return;
76 m_last_msg = msg;
77 if (m_is_tty) {
78 std::fputs("\r", stdout);
79 std::fwrite(m_header.data(), 1, m_header.size(), stdout);
80 std::fwrite(msg.data(), 1, msg.size(), stdout);
81 std::fflush(stdout);
82 }
83 }
84
85 //! Update the status message. see @ref update()
86 void operator()(std::string_view msg) { return update(msg); }
87
88 //! Finalise: print last message + optional @p post, then newline.
89 //! Safe to call multiple times (only the first call has effect).
90 void done(std::string_view post = {}) {
91 if (!m_active || m_done)
92 return;
93 m_done = true;
94 if (m_is_tty) {
95 std::fputs("\r", stdout);
96 std::fwrite(m_header.data(), 1, m_header.size(), stdout);
97 }
98 std::fwrite(m_last_msg.data(), 1, m_last_msg.size(), stdout);
99 if (!post.empty())
100 std::fwrite(post.data(), 1, post.size(), stdout);
101 std::fputc('\n', stdout);
102 std::fflush(stdout);
103 }
104};
105
106/*! @brief Basic progress bar. Prints new line if (and only if) i==(max-1)
107 @details
108 - does not work well in Parellel regions. Use @ref ProgressBar in that case
109 - Prints directly to cout - creates a mess if piped to a text file.
110 Use @ref ProgressBar if that will be an issue
111*/
112inline void progbar(int i, int max, int length = 50) {
113 const int len = (length - 1);
114 const int current = int(len * double(i) / double(max - 1));
115 std::cout << "[";
116 for (auto j = 0; j < current; ++j) {
117 std::cout << "=";
118 }
119 for (auto j = current; j < len; ++j) {
120 std::cout << " ";
121 }
122 std::cout << "] \r" << std::flush;
123 if (i == max - 1)
124 std::cout << "\n";
125}
126
127//==============================================================================
128/*!
129 @brief Thread-safe progress bar for OpenMP parallel loops.
130
131 @details
132 Displays a progress bar with percentage. The progress counter uses
133 std::atomic for thread-safe updates.
134 Each call to update() increments the counter and prints the bar.
135 The output is serialised via critical section to prevent garbled output
136 from simultaneous writes.
137
138 @warning This adds overhead.
139 Prefer not to use if each OMP task is extremely small,
140 since overhead may become noticable.
141 If each task is large, overhead negligable.
142
143 - If print set to false on construction, does nothing
144 (does not print, does not track progress).
145 Just a simple way of run-time turning off.
146
147 @note
148 When stdout is not a TTY (i.e., piped), prints comma-separated percentages
149 instead of a bar: "0%, 10%, 20%, ... 100%\n". Prints approximately
150 @p Length / 5 values, spaced evenly.
151
152 The @p Length template parameter controls output width.
153 For TTY mode: total bar width in characters.
154 For non-TTY mode: determines how many percentage values are printed (~Length/5).
155
156 @note If the loop exits before the final iteration (i.e., early `break;`),
157 then final the newline `\n` will not be printed. Output may be messy.
158 Cannot break like this in OpenMP loop anyway.
159
160 For non-parallel loops, the simpler @ref qip::progbar() function should work fine.
161
162 Typical usage in a parallel loop:
163
164 @code
165 qip::ProgressBar bar(n_iterations);
166 #pragma omp parallel for schedule(dynamic)
167 for (std::size_t i = 0; i < n_iterations; ++i) {
168 // ... work ...
169 bar.update();
170 }
171 @endcode
172
173 - Put .update(); _after_ work, to avoid early "100% done" reporting
174
175 @tparam Length Total character width of the output (default 60).
176
177 @note Thread-safe; safe to call from multiple threads simultaneously.
178 But may add overhead.
179
180 @note Counts converted to `int` internally, so the maximum supported
181 iteration count is INT_MAX.
182*/
183template <std::size_t Length = 60>
185private:
186 int m_max;
187 bool m_print;
188 // Atomicly tracks progress
189 std::atomic<int> m_progress{0};
190 // TTY: bar fill chars printed last time. Non-TTY: last percentage slot printed.
191 std::atomic<int> m_last_fill{-1};
192 // Set to true once the final bar (with '\n') has been printed; guarded by
193 // omp critical so no further '\r' output can overwrite the newline.
194 bool m_done{false};
195
196 static constexpr std::size_t bar_width = (Length >= 7) ? (Length - 7) : 1;
197
198public:
199 /*!
200 @brief Construct progress bar for @p max iterations.
201 @param max Total number of iterations (denominator for percentage).
202 Accepts any integral type; converted to int internally.
203 @param print Runtime switch; if false, class does nothing.
204 */
205 template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
206 ProgressBar(T max, bool print = true)
207 : m_max(static_cast<int>(max)), m_print(print) {
208 if (m_print)
209 print_prog_bar(0);
210 }
211
212 //! Atomically increment progress counter and print updated bar.
213 void update() {
214 if (!m_print)
215 return;
216 const int progress = ++m_progress;
217 if (progress > m_max)
218 return;
219 const bool is_final = (progress == m_max);
220
221 // skip print if bar fill is unchanged (avoids critical section overhead)
222 const int current = int(bar_width * double(progress) / double(m_max));
223 if (!is_final && current <= m_last_fill.load(std::memory_order_relaxed))
224 return;
225
226 print_prog_bar(progress);
227 }
228
229private:
230 //----------------------------------------------------------------------------
231
232 // Prints progress bar (when stdout is a normal tty)
233 void print_prog_bar(int progress) {
234 const bool is_tty = isatty(fileno(stdout));
235 if (!is_tty) {
236 print_prog_bar_notty(progress);
237 return;
238 }
239
240 const bool is_final = (progress == m_max);
241
242 // build bar into stack buffer (char array)
243 const auto current = int(bar_width * double(progress) / double(m_max));
244 char buf[Length + 2];
245 char *p = buf;
246 *p++ = '[';
247 for (int j = 0; j < current; ++j)
248 *p++ = '=';
249 for (int j = current; j < (int)bar_width; ++j)
250 *p++ = ' ';
251 *p++ = ']';
252 *p++ = ' ';
253 p += snprintf(p, sizeof(buf) - std::size_t(p - buf) - 1, "%d%%",
254 int(100.0 * double(progress) / double(m_max)));
255 *p++ = is_final ? '\n' : '\r';
256 *p = '\0';
257
258 // serialise writes; skip if final bar already printed (prevents a lagging
259 // thread's '\r' from overwriting the final '\n' on the terminal)
260 bool did_print = false;
261#pragma omp critical
262 {
263 if (!m_done) {
264 fputs(buf, stdout);
265 fflush(stdout);
266 did_print = true;
267 if (is_final)
268 m_done = true;
269 }
270 }
271 if (did_print)
272 m_last_fill.store(current, std::memory_order_relaxed);
273 }
274
275 //----------------------------------------------------------------------------
276
277 // Prints progress "bar" (comma separated %) (when stdout is NOT a normal tty)
278 void print_prog_bar_notty(int progress) {
279 const bool is_initial = (progress == 0);
280 const bool is_final = (progress == m_max);
281
282 const int pct =
283 is_final ? 100 : int(100.0 * double(progress) / double(m_max));
284
285 // number of entries ~ Length/5 chars each; interval between prints in pct
286 static constexpr int n_entries = static_cast<int>(Length) / 5;
287 static constexpr int interval =
288 (n_entries > 1) ? (100 / (n_entries - 1)) : 100;
289 const int slot = pct / interval;
290
291 if (!is_initial && !is_final &&
292 slot <= m_last_fill.load(std::memory_order_relaxed))
293 return;
294
295 char buf[8];
296 if (is_final)
297 snprintf(buf, sizeof(buf), "100%%\n");
298 else
299 snprintf(buf, sizeof(buf), "%d%%, ", pct);
300
301 bool did_print = false;
302#pragma omp critical
303 {
304 if (!m_done) {
305 fputs(buf, stdout);
306 fflush(stdout);
307 did_print = true;
308 if (is_final)
309 m_done = true;
310 }
311 }
312 if (!is_final && did_print)
313 m_last_fill.store(slot, std::memory_order_relaxed);
314 }
315};
316
317} // namespace qip
Live status line for iterative loops; overwrites itself on a TTY.
Definition Widgets.hpp:48
void operator()(std::string_view msg)
Update the status message. see update()
Definition Widgets.hpp:86
void done(std::string_view post={})
Finalise: print last message + optional post, then newline. Safe to call multiple times (only the fir...
Definition Widgets.hpp:90
void update(std::string_view msg)
Update the status message. On TTY overwrites the current line; on non-TTY buffers silently until done...
Definition Widgets.hpp:73
Thread-safe progress bar for OpenMP parallel loops.
Definition Widgets.hpp:184
ProgressBar(T max, bool print=true)
Construct progress bar for max iterations.
Definition Widgets.hpp:206
void update()
Atomically increment progress counter and print updated bar.
Definition Widgets.hpp:213
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:112
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.