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 @code
69 qip::ProgressBar bar(n_iterations);
70 #pragma omp parallel for schedule(dynamic)
71 for (std::size_t i = 0; i < n_iterations; ++i) {
72 // ... work ...
73 bar.update();
74 }
75 @endcode
76
77 - Put .update(); _after_ work, to avoid early "100% done" reporting
78
79 @tparam Length Total character width of the output (default 60).
80
81 @note Thread-safe; safe to call from multiple threads simultaneously.
82 But may add overhead.
83*/
84template <std::size_t Length = 60>
86private:
87 int m_max;
88 bool m_print;
89 // Atomicly tracks progress
90 std::atomic<int> m_progress{0};
91 // TTY: bar fill chars printed last time. Non-TTY: last percentage slot printed.
92 std::atomic<int> m_last_fill{-1};
93
94 static constexpr std::size_t bar_width = (Length >= 7) ? (Length - 7) : 1;
95
96public:
97 /*!
98 @brief Construct progress bar for @p max iterations.
99 @param max Total number of iterations (denominator for percentage).
100 @param print Runtime switch; if false, class does nothing.
101 */
102 ProgressBar(int max, bool print) : m_max(max), m_print(print) {
103 if (m_print)
104 print_prog_bar(0);
105 }
106
107 //! Atomically increment progress counter and print updated bar.
108 void update() {
109 if (!m_print)
110 return;
111 m_progress++;
112 const int progress = m_progress.load();
113 if (progress > m_max)
114 return;
115 const bool is_final = (progress == m_max);
116
117 // skip print if bar fill is unchanged (avoids critical section overhead)
118 const int current = int(bar_width * double(progress) / double(m_max));
119 if (!is_final && current <= m_last_fill.load(std::memory_order_relaxed))
120 return;
121
122 print_prog_bar(progress);
123 }
124
125private:
126 //----------------------------------------------------------------------------
127
128 // Prints progress bar (when stdout is a normal tty)
129 void print_prog_bar(int progress) {
130 const bool is_tty = isatty(fileno(stdout));
131 if (!is_tty) {
132 print_prog_bar_notty(progress);
133 return;
134 }
135
136 const bool is_final = (progress == m_max);
137
138 // build bar into stack buffer (char array)
139 const auto current = int(bar_width * double(progress) / double(m_max));
140 char buf[Length + 2];
141 char *p = buf;
142 *p++ = '[';
143 for (int j = 0; j < current; ++j)
144 *p++ = '=';
145 for (int j = current; j < (int)bar_width; ++j)
146 *p++ = ' ';
147 *p++ = ']';
148 *p++ = ' ';
149 p += snprintf(p, sizeof(buf) - std::size_t(p - buf) - 2, "%d%%",
150 int(100.0 * double(progress) / double(m_max)));
151 *p++ = is_final ? '\n' : '\r';
152 *p = '\0';
153
154 // serialise writes to std::out
155#pragma omp critical
156 {
157 fputs(buf, stdout);
158 fflush(stdout);
159 }
160 m_last_fill.store(current, std::memory_order_relaxed);
161 }
162
163 //----------------------------------------------------------------------------
164
165 // Prints progress "bar" (comma separated %) (when stdout is NOT a normal tty)
166 void print_prog_bar_notty(int progress) {
167 const bool is_initial = (progress == 0);
168 const bool is_final = (progress == m_max);
169
170 const int pct =
171 is_final ? 100 : int(100.0 * double(progress) / double(m_max));
172
173 // number of entries ~ Length/5 chars each; interval between prints in pct
174 static constexpr int n_entries = static_cast<int>(Length) / 5;
175 static constexpr int interval =
176 (n_entries > 1) ? (100 / (n_entries - 1)) : 100;
177 const int slot = pct / interval;
178
179 if (!is_initial && !is_final &&
180 slot <= m_last_fill.load(std::memory_order_relaxed))
181 return;
182
183 char buf[8];
184 if (is_final)
185 snprintf(buf, sizeof(buf), "100%%\n");
186 else
187 snprintf(buf, sizeof(buf), "%d%%, ", pct);
188
189#pragma omp critical
190 {
191 fputs(buf, stdout);
192 fflush(stdout);
193 }
194 if (!is_final)
195 m_last_fill.store(slot, std::memory_order_relaxed);
196 }
197};
198
199} // namespace qip
Thread-safe progress bar for OpenMP parallel loops.
Definition Widgets.hpp:85
ProgressBar(int max, bool print)
Construct progress bar for max iterations.
Definition Widgets.hpp:102
void update()
Atomically increment progress counter and print updated bar.
Definition Widgets.hpp:108
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.