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 if (m_is_tty) {
77 std::fputs("\r", stdout);
78 std::fwrite(m_header.data(), 1, m_header.size(), stdout);
79 std::fwrite(msg.data(), 1, msg.size(), stdout);
80 clear_tail(msg.size());
81 std::fflush(stdout);
82 }
83 m_last_msg = msg;
84 }
85
86 //! Update the status message. see @ref update()
87 void operator()(std::string_view msg) { return update(msg); }
88
89 //! Finalise: print last message + optional @p post, then newline.
90 //! Safe to call multiple times (only the first call has effect).
91 void done(std::string_view post = {}) {
92 if (!m_active || m_done)
93 return;
94 m_done = true;
95 if (m_is_tty) {
96 std::fputs("\r", stdout);
97 std::fwrite(m_header.data(), 1, m_header.size(), stdout);
98 }
99 std::fwrite(m_last_msg.data(), 1, m_last_msg.size(), stdout);
100 if (!post.empty())
101 std::fwrite(post.data(), 1, post.size(), stdout);
102 std::fputc('\n', stdout);
103 std::fflush(stdout);
104 }
105
106private:
107 // Overwrite any leftover characters when the new message is shorter than
108 // the previous one (the line is rewritten in place, not cleared)
109 void clear_tail(std::size_t new_size) const {
110 for (auto i = new_size; i < m_last_msg.size(); ++i) {
111 std::fputc(' ', stdout);
112 }
113 }
114};
115
116/*! @brief Basic progress bar. Prints new line if (and only if) i==(max-1)
117 @details
118 - does not work well in Parellel regions. Use @ref ProgressBar in that case
119 - Prints directly to cout - creates a mess if piped to a text file.
120 Use @ref ProgressBar if that will be an issue
121*/
122inline void progbar(int i, int max, int length = 50) {
123 const int len = (length - 1);
124 const int current = int(len * double(i) / double(max - 1));
125 std::cout << "[";
126 for (auto j = 0; j < current; ++j) {
127 std::cout << "=";
128 }
129 for (auto j = current; j < len; ++j) {
130 std::cout << " ";
131 }
132 std::cout << "] \r" << std::flush;
133 if (i == max - 1)
134 std::cout << "\n";
135}
136
137//==============================================================================
138/*!
139 @brief Thread-safe progress bar for OpenMP parallel loops.
140
141 @details
142 Displays a progress bar with percentage. The progress counter uses
143 std::atomic for thread-safe updates.
144 Each call to update() increments the counter and prints the bar.
145 The output is serialised via critical section to prevent garbled output
146 from simultaneous writes.
147
148 @warning This adds overhead.
149 Prefer not to use if each OMP task is extremely small,
150 since overhead may become noticable.
151 If each task is large, overhead negligable.
152
153 - If print set to false on construction, does nothing
154 (does not print, does not track progress).
155 Just a simple way of run-time turning off.
156
157 @note
158 When stdout is not a TTY (i.e., piped), prints comma-separated percentages
159 instead of a bar: "0%, 10%, 20%, ... 100%\n". Prints approximately
160 @p Length / 5 values, spaced evenly.
161
162 The @p Length template parameter controls output width.
163 For TTY mode: total bar width in characters.
164 For non-TTY mode: determines how many percentage values are printed (~Length/5).
165
166 @note If the loop exits before the final iteration (i.e., early `break;`),
167 then final the newline `\n` will not be printed. Output may be messy.
168 Cannot break like this in OpenMP loop anyway.
169
170 For non-parallel loops, the simpler @ref qip::progbar() function should work fine.
171
172 Typical usage in a parallel loop:
173
174 @code
175 qip::ProgressBar bar(n_iterations);
176 #pragma omp parallel for schedule(dynamic)
177 for (std::size_t i = 0; i < n_iterations; ++i) {
178 // ... work ...
179 bar.update();
180 }
181 @endcode
182
183 - Put .update(); _after_ work, to avoid early "100% done" reporting
184
185 @tparam Length Total character width of the output (default 60).
186
187 @note Thread-safe; safe to call from multiple threads simultaneously.
188 But may add overhead.
189
190 @note Counts converted to `int` internally, so the maximum supported
191 iteration count is INT_MAX.
192*/
193template <std::size_t Length = 60>
195private:
196 int m_max;
197 bool m_print;
198 // Atomicly tracks progress
199 std::atomic<int> m_progress{0};
200 // TTY: bar fill chars printed last time. Non-TTY: last percentage slot printed.
201 std::atomic<int> m_last_fill{-1};
202 // Set to true once the final bar (with '\n') has been printed; guarded by
203 // omp critical so no further '\r' output can overwrite the newline.
204 bool m_done{false};
205
206 static constexpr std::size_t bar_width = (Length >= 7) ? (Length - 7) : 1;
207
208public:
209 /*!
210 @brief Construct progress bar for @p max iterations.
211 @param max Total number of iterations (denominator for percentage).
212 Accepts any integral type; converted to int internally.
213 @param print Runtime switch; if false, class does nothing.
214 */
215 template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
216 ProgressBar(T max, bool print = true)
217 : m_max(static_cast<int>(max)), m_print(print) {
218 if (m_print)
219 print_prog_bar(0);
220 }
221
222 //! Atomically increment progress counter and print updated bar.
223 void update() {
224 if (!m_print)
225 return;
226 const int progress = ++m_progress;
227 if (progress > m_max)
228 return;
229 const bool is_final = (progress == m_max);
230
231 // skip print if bar fill is unchanged (avoids critical section overhead)
232 const int current = int(bar_width * double(progress) / double(m_max));
233 if (!is_final && current <= m_last_fill.load(std::memory_order_relaxed))
234 return;
235
236 print_prog_bar(progress);
237 }
238
239private:
240 //----------------------------------------------------------------------------
241
242 // Prints progress bar (when stdout is a normal tty)
243 void print_prog_bar(int progress) {
244 const bool is_tty = isatty(fileno(stdout));
245 if (!is_tty) {
246 print_prog_bar_notty(progress);
247 return;
248 }
249
250 const bool is_final = (progress == m_max);
251
252 // build bar into stack buffer (char array)
253 const auto current = int(bar_width * double(progress) / double(m_max));
254 char buf[Length + 2];
255 char *p = buf;
256 *p++ = '[';
257 for (int j = 0; j < current; ++j)
258 *p++ = '=';
259 for (int j = current; j < (int)bar_width; ++j)
260 *p++ = ' ';
261 *p++ = ']';
262 *p++ = ' ';
263 p += snprintf(p, sizeof(buf) - std::size_t(p - buf) - 1, "%d%%",
264 int(100.0 * double(progress) / double(m_max)));
265 *p++ = is_final ? '\n' : '\r';
266 *p = '\0';
267
268 // serialise writes; skip if final bar already printed (prevents a lagging
269 // thread's '\r' from overwriting the final '\n' on the terminal)
270 bool did_print = false;
271#pragma omp critical
272 {
273 if (!m_done) {
274 fputs(buf, stdout);
275 fflush(stdout);
276 did_print = true;
277 if (is_final)
278 m_done = true;
279 }
280 }
281 if (did_print)
282 m_last_fill.store(current, std::memory_order_relaxed);
283 }
284
285 //----------------------------------------------------------------------------
286
287 // Prints progress "bar" (comma separated %) (when stdout is NOT a normal tty)
288 void print_prog_bar_notty(int progress) {
289 const bool is_initial = (progress == 0);
290 const bool is_final = (progress == m_max);
291
292 const int pct =
293 is_final ? 100 : int(100.0 * double(progress) / double(m_max));
294
295 // number of entries ~ Length/5 chars each; interval between prints in pct
296 static constexpr int n_entries = static_cast<int>(Length) / 5;
297 static constexpr int interval =
298 (n_entries > 1) ? (100 / (n_entries - 1)) : 100;
299 const int slot = pct / interval;
300
301 if (!is_initial && !is_final &&
302 slot <= m_last_fill.load(std::memory_order_relaxed))
303 return;
304
305 char buf[8];
306 if (is_final)
307 snprintf(buf, sizeof(buf), "100%%\n");
308 else
309 snprintf(buf, sizeof(buf), "%d%%, ", pct);
310
311 bool did_print = false;
312#pragma omp critical
313 {
314 if (!m_done) {
315 fputs(buf, stdout);
316 fflush(stdout);
317 did_print = true;
318 if (is_final)
319 m_done = true;
320 }
321 }
322 if (!is_final && did_print)
323 m_last_fill.store(slot, std::memory_order_relaxed);
324 }
325};
326
327} // 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:87
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:91
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:194
ProgressBar(T max, bool print=true)
Construct progress bar for max iterations.
Definition Widgets.hpp:216
void update()
Atomically increment progress counter and print updated bar.
Definition Widgets.hpp:223
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:122
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.