cepgen is hosted by Hepforge, IPPP Durham
CepGen 1.2.5
Central exclusive processes event generator
Loading...
Searching...
No Matches
TextDrawer.cpp
Go to the documentation of this file.
1/*
2 * CepGen: a central exclusive processes event generator
3 * Copyright (C) 2022-2024 Laurent Forthomme
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include <array>
20#include <cmath>
21#include <iomanip>
22
24#include "CepGen/Utils/Drawer.h"
25#include "CepGen/Utils/Graph.h"
27#include "CepGen/Utils/Math.h"
29#include "CepGen/Utils/String.h"
30
31namespace cepgen {
32 namespace utils {
33 class TextDrawer : public Drawer {
34 public:
35 explicit TextDrawer(const ParametersList&);
36
38 auto desc = Drawer::description();
39 desc.setDescription("Text-based drawing module");
40 desc.add<int>("width", 50);
41 desc.add<bool>("colourise", true).setDescription("colourise the output (for TTY-compatible displays)");
42 return desc;
43 }
44
45 const TextDrawer& draw(const Graph1D&, const Mode&) const override;
46 const TextDrawer& draw(const Graph2D&, const Mode&) const override;
47 const TextDrawer& draw(const Hist1D&, const Mode&) const override;
48 const TextDrawer& draw(const Hist2D&, const Mode&) const override;
49
50 const TextDrawer& draw(const DrawableColl&,
51 const std::string& name = "",
52 const std::string& title = "",
53 const Mode& mode = Mode::none) const override;
54
55 private:
56 friend class Drawable;
57 static const std::array<Colour, 7> kColours;
58 static const std::string kEmptyLabel;
59
60 void drawValues(std::ostream&, const Drawable&, const Drawable::axis_t&, const Mode&, bool effects) const;
61 void drawValues(std::ostream&, const Drawable&, const Drawable::dualaxis_t&, const Mode&, bool effects) const;
62
63 const char CHAR, ERR_CHAR, NEG_CHAR;
64 const std::string MARKERS_CHAR, VALUES_CHAR;
65
66 static std::string delatexify(const std::string&);
67
69 struct map_elements {
71 bool operator()(const std::pair<Drawable::coord_t, Value>& lhs,
72 const std::pair<Drawable::coord_t, Value>& rhs) {
73 return lhs.second < rhs.second;
74 }
75 };
76
77 const size_t width_{0};
78 const bool colourise_{true};
79 };
80
81 const std::array<Colour, 7> TextDrawer::kColours = {
83
84 const std::string TextDrawer::kEmptyLabel = "E M P T Y ";
85
87 : Drawer(params),
88 CHAR('*'),
89 ERR_CHAR('-'),
90 NEG_CHAR('-'),
91 MARKERS_CHAR("o.#@"),
92 VALUES_CHAR(" .:oO0@%#"),
93 width_(steerAs<int, size_t>("width")),
94 colourise_(steer<bool>("colourise")) {}
95
96 const TextDrawer& TextDrawer::draw(const Graph1D& graph, const Mode& mode) const {
97 CG_LOG.log([&](auto& log) {
98 if (!graph.name().empty())
99 log << "plot of \"" << graph.name() << "\"\n";
100 drawValues(log.stream(), graph, graph.points(), mode, colourise_);
101 });
102 return *this;
103 }
104
105 const TextDrawer& TextDrawer::draw(const Graph2D& graph, const Mode& mode) const {
106 CG_LOG.log([&](auto& log) {
107 if (!graph.name().empty())
108 log << "plot of \"" << graph.name() << "\"\n";
109 drawValues(log.stream(), graph, graph.points(), mode, colourise_);
110 });
111 return *this;
112 }
113
114 const TextDrawer& TextDrawer::draw(const Hist1D& hist, const Mode& mode) const {
115 CG_LOG.log([&](auto& log) {
116 if (!hist.name().empty())
117 log << "plot of \"" << hist.name() << "\"\n";
118 drawValues(log.stream(), hist, hist.axis(), mode, colourise_);
119 const double bin_width = hist.range().range() / hist.nbins();
120 log << "\tbin width=" << utils::s("unit", bin_width, true) << ", "
121 << "mean=" << hist.mean() << ", "
122 << "st.dev.=" << hist.rms() << "\n\t"
123 << "integr.=" << hist.integral();
124 if (hist.underflow() > 0ull)
125 log << ", underflow: " << hist.underflow();
126 if (hist.overflow() > 0ull)
127 log << ", overflow: " << hist.overflow();
128 });
129 return *this;
130 }
131
132 const TextDrawer& TextDrawer::draw(const Hist2D& hist, const Mode& mode) const {
133 CG_LOG.log([&](auto& log) {
134 if (!hist.name().empty())
135 log << "plot of \"" << hist.name() << "\"\n";
137 for (size_t binx = 0; binx < hist.nbinsX(); ++binx) {
138 const auto& range_x = hist.binRangeX(binx);
139 auto& axis_x = axes[Drawable::coord_t{
140 range_x.x(0.5), 0.5 * range_x.range(), utils::format("[%7.2f,%7.2f)", range_x.min(), range_x.max())}];
141 for (size_t biny = 0; biny < hist.nbinsY(); ++biny) {
142 const auto& range_y = hist.binRangeY(biny);
143 axis_x[Drawable::coord_t{range_y.x(0.5), 0.5 * range_y.range(), utils::format("%+g", range_y.min())}] =
144 hist.value(binx, biny);
145 }
146 }
147 drawValues(log.stream(), hist, axes, mode, colourise_);
148 const auto &x_range = hist.rangeX(), &y_range = hist.rangeY();
149 const double bin_width_x = x_range.range() / hist.nbinsX(), bin_width_y = y_range.range() / hist.nbinsY();
150 log << "\t"
151 << " x-axis: "
152 << "bin width=" << utils::s("unit", bin_width_x, true) << ", "
153 << "mean=" << hist.meanX() << ","
154 << "st.dev.=" << hist.rmsX() << "\n\t"
155 << " y-axis: "
156 << "bin width=" << utils::s("unit", bin_width_y, true) << ", "
157 << "mean=" << hist.meanY() << ","
158 << "st.dev.=" << hist.rmsY() << ",\n\t"
159 << " integral=" << hist.integral();
160 const auto& cnt = hist.outOfRange();
161 if (cnt.total() > 0) {
162 log << ", outside range (in/overflow):\n" << cnt;
163 }
164 });
165 return *this;
166 }
167
169 const std::string& name,
170 const std::string&,
171 const Mode& mode) const {
172 CG_WARNING("TextDrawer:draw") << "Multi-plots is now only partially supported (no axes rescaling).";
173 auto inside_plot = [](const std::string& str) -> std::string {
174 std::istringstream ss(str);
175 std::ostringstream out;
176 for (std::string line; std::getline(ss, line);) {
177 const auto tok = utils::split(line, ':');
178 if (tok.size() == 3)
179 out << tok.at(1) << "\n";
180 }
181 return out.str();
182 };
183 auto replace_plot = [](const std::string& orig, const std::string& new_plot) -> std::string {
184 std::istringstream ss(orig), ssnew(new_plot);
185 std::ostringstream out;
186 for (std::string line; std::getline(ss, line);) {
187 auto tok = utils::split(line, ':');
188 if (tok.size() == 3) {
189 std::getline(ssnew, tok[1]);
190 tok[2].clear();
191 out << utils::merge(tok, ":") << "\n";
192 } else
193 out << line << "\n";
194 }
195 return out.str();
196 };
197 std::stringstream buf, os_base;
198 size_t num_plts = 0;
199 auto add_plot = [this, &buf, &num_plts](const std::string& plt) {
200 ++num_plts;
201 if (plt.empty())
202 return;
203 std::istringstream ss(plt);
204 std::ostringstream out;
205 for (std::string line; std::getline(ss, line);) {
206 std::string base(line.size(), ' ');
207 if (!buf.str().empty() && !std::getline(buf, base)) {
208 CG_WARNING("TextDrawer:draw") << "Invalid plot to be produced... Aborting the multiplot.";
209 return;
210 }
211 for (size_t j = 0; j < line.size(); ++j) {
212 if (line[j] == CHAR)
213 base[j] = (num_plts > 1 ? MARKERS_CHAR[num_plts - 2] : CHAR);
214 else if (line[j] == ERR_CHAR)
215 base[j] = ERR_CHAR;
216 }
217 out << base << "\n";
218 }
219 buf.str(out.str());
220 };
221 std::vector<std::string> plt_names;
222 for (const auto* obj : objs)
223 if (obj->isHist1D()) {
224 if (const auto* hist = dynamic_cast<const Hist1D*>(obj); hist) {
225 if (os_base.str().empty()) {
226 drawValues(os_base, *hist, hist->axis(), mode, false);
227 add_plot(inside_plot(os_base.str()));
228 } else {
229 std::ostringstream os;
230 drawValues(os, *hist, hist->axis(), mode, false);
231 add_plot(inside_plot(os.str()));
232 }
233 plt_names.emplace_back(hist->name());
234 }
235 } else if (obj->isGraph1D()) {
236 if (const auto* gr = dynamic_cast<const Graph1D*>(obj); gr) {
237 if (os_base.str().empty()) {
238 drawValues(os_base, *gr, gr->points(), mode, false);
239 add_plot(inside_plot(os_base.str()));
240 } else {
241 std::ostringstream os;
242 drawValues(os, *gr, gr->points(), mode, false);
243 add_plot(inside_plot(os.str()));
244 }
245 plt_names.emplace_back(gr->name());
246 }
247 } else {
248 CG_WARNING("TextDrawer:draw") << "Cannot add drawable '" << obj->name() << "' to the stack.";
249 continue;
250 }
251 CG_LOG.log([&](auto& log) {
252 if (!name.empty())
253 log << "plot of \"" << name << "\"\n";
254 log << replace_plot(os_base.str(), buf.str());
255 if (num_plts > 1)
256 log << "\tLegend:\n\t " << CHAR << ": " << plt_names.at(0);
257 for (size_t i = 1; i < num_plts; ++i)
258 log << "\n\t " << MARKERS_CHAR[i - 1] << ": " << plt_names.at(i);
259 });
260 return *this;
261 }
262
263 void TextDrawer::drawValues(
264 std::ostream& os, const Drawable& dr, const Drawable::axis_t& axis, const Mode& mode, bool effects) const {
265 const std::string sep(17, ' ');
266 const double max_val = std::max_element(axis.begin(), axis.end(), map_elements())->second *
267 ((mode & Mode::logy) ? 5. : 1.2),
268 min_val = std::min_element(axis.begin(), axis.end(), map_elements())->second;
269 const double min_val_log = std::log(std::max(min_val, 1.e-10));
270 const double max_val_log = std::log(std::min(max_val, 1.e+10));
271 if (!dr.yAxis().label().empty()) {
272 const auto ylabel = delatexify(dr.yAxis().label());
273 os << sep << std::string(std::max(0., 2. + width_ - ylabel.size()), ' ') << ylabel << "\n";
274 }
275 os << sep << utils::format("%-5.2f ", (mode & Mode::logy) ? std::exp(min_val_log) : min_val)
276 << std::setw(width_ - 11) << std::left << ((mode & Mode::logy) ? "logarithmic scale" : "linear scale")
277 << utils::format("%5.2e", (mode & Mode::logy) ? std::exp(max_val_log) : max_val) << "\n"
278 << sep << std::string(width_ + 2, '.'); // abscissa axis
279 size_t idx = 0;
280 for (const auto& coord_set : axis) {
281 const auto left_label =
282 coord_set.first.label.empty() ? utils::format("%17g", coord_set.first.value) : coord_set.first.label;
283 if (min_val == max_val) {
284 os << "\n" << left_label << ":";
285 if (idx == axis.size() / 2)
286 os << std::string((width_ - kEmptyLabel.size()) / 2, ' ') << kEmptyLabel
287 << std::string((width_ - kEmptyLabel.size()) / 2, ' ');
288 else
289 os << std::string(width_, ' ');
290 os << ":";
291 } else {
292 const auto& val = coord_set.second;
293 size_t ival = 0ull, ierr = 0ull;
294 {
295 double val_dbl = width_, unc_dbl = width_;
296 if (mode & Mode::logy) {
297 val_dbl *= (val > 0. && max_val > 0.)
298 ? std::max((std::log(val) - min_val_log) / (max_val_log - min_val_log), 0.)
299 : 0.;
300 unc_dbl *= (val > 0. && max_val > 0.)
301 ? std::max((std::log(val.uncertainty()) - min_val_log) / (max_val_log - min_val_log), 0.)
302 : 0.;
303 } else if (max_val > 0.) {
304 val_dbl *= (val - min_val) / (max_val - min_val);
305 unc_dbl *= val.uncertainty() / (max_val - min_val);
306 }
307 ival = std::ceil(val_dbl);
308 ierr = std::ceil(unc_dbl);
309 }
310 os << "\n"
311 << left_label << ":" << (ival > ierr ? std::string(ival - ierr, ' ') : "")
312 << (ierr > 0 ? std::string(ierr, ERR_CHAR) : "")
313 << (effects ? utils::boldify(std::string(1, CHAR)) : std::string(1, CHAR))
314 << (ierr > 0 ? std::string(std::min(width_ - ival - 1, ierr), ERR_CHAR) : "")
315 << (ival + ierr < width_ + 1 ? std::string(width_ - ival - ierr - 1, ' ') : "") << ": "
316 << utils::format("%6.2e +/- %6.2e", val, val.uncertainty());
317 }
318 ++idx;
319 }
320 os << "\n"
321 << utils::format("%17s", delatexify(dr.xAxis().label()).c_str()) << ":" << std::string(width_, '.')
322 << ":\n"; // 2nd abscissa axis
323 }
324
325 void TextDrawer::drawValues(
326 std::ostream& os, const Drawable& dr, const Drawable::dualaxis_t& axes, const Mode& mode, bool effects) const {
327 const std::string sep(17, ' ');
328 if (!dr.yAxis().label().empty()) {
329 const auto ylabel = delatexify(dr.yAxis().label());
330 os << sep << std::string(std::max(0., 2. + width_ - ylabel.size()), ' ') << ylabel << "\n";
331 }
332 // find the maximum element of the graph
333 double min_val = -Limits::INVALID, max_val = Limits::INVALID;
334 double min_logval = -3.;
335 for (const auto& xval : axes) {
336 min_val =
337 std::min(min_val, (double)std::min_element(xval.second.begin(), xval.second.end(), map_elements())->second);
338 max_val =
339 std::max(max_val, (double)std::max_element(xval.second.begin(), xval.second.end(), map_elements())->second);
340 if (mode & Mode::logz)
341 for (const auto& yval : xval.second)
342 if (yval.second > 0.)
343 min_logval = std::min(min_logval, std::log(yval.second / max_val));
344 }
345 const auto& y_axis = axes.begin()->second;
346 os << sep << utils::format("%-5.2f", y_axis.begin()->first.value) << std::string(axes.size() - 11, ' ')
347 << utils::format("%5.2e", y_axis.rbegin()->first.value) << "\n"
348 << utils::format("%17s", delatexify(dr.xAxis().label()).c_str())
349 << std::string(1 + y_axis.size() + 1, '.'); // abscissa axis
350 size_t idx = 0;
351 for (const auto& xval : axes) {
352 os << "\n" << (xval.first.label.empty() ? utils::format("%16g ", xval.first.value) : xval.first.label) << ":";
353 if (min_val == max_val) {
354 if (idx == axes.size() / 2)
355 os << std::string((width_ - kEmptyLabel.size()) / 2, ' ') << kEmptyLabel
356 << std::string((width_ - kEmptyLabel.size()) / 2, ' ');
357 else
358 os << std::string(width_, ' ');
359 } else {
360 for (const auto& yval : xval.second) {
361 const double val = yval.second;
362 double val_norm = 0.;
363 if (mode & Mode::logz)
364 val_norm = positive(val) ? std::max(0., (std::log(val / max_val) - min_logval) / fabs(min_logval)) : 0.;
365 else
366 val_norm = val / max_val;
367 if (std::isnan(val_norm)) {
368 os << (effects ? utils::colourise("!", kColours.at(0)) : "!");
369 continue;
370 }
371 const short sign = (val_norm == 0. ? 0 : val_norm / fabs(val_norm));
372 val_norm *= sign;
373 if (sign == -1)
374 os << (effects ? utils::colourise(std::string(1, NEG_CHAR), kColours.at(0)) : std::string(1, NEG_CHAR));
375 else {
376 size_t ch_id = ceil(val_norm * (VALUES_CHAR.size() - 1));
377 size_t col_id = 1 + val_norm * (kColours.size() - 2);
378 os << (effects ? utils::colourise(std::string(1, VALUES_CHAR.at(ch_id)),
379 kColours.at(col_id),
380 (val_norm > 0.75 ? utils::Modifier::bold : utils::Modifier::reset))
381 : std::string(1, VALUES_CHAR.at(ch_id)));
382 }
383 }
384 }
385 os << ":";
386 ++idx;
387 }
388 std::vector<std::string> ylabels;
389 std::transform(y_axis.begin(), y_axis.end(), std::back_inserter(ylabels), [](auto& bin) {
390 return bin.first.label.empty() ? utils::format("%+g", bin.first.value) : bin.first.label;
391 });
392 struct stringlen {
393 bool operator()(const std::string& a, const std::string& b) { return a.size() < b.size(); }
394 };
395 for (size_t i = 0; i < std::max_element(ylabels.begin(), ylabels.end(), stringlen())->size(); ++i) {
396 os << "\n" << sep << ":";
397 for (const auto& lab : ylabels)
398 os << (lab.size() > i ? lab.at(i) : ' ');
399 os << ":";
400 }
401 os << "\n"
402 << sep << ":" << std::string(y_axis.size(), '.') << ": " // 2nd abscissa axis
403 << delatexify(dr.yAxis().label()) << "\n\t"
404 << "(scale: \"" << VALUES_CHAR << "\", ";
405 for (size_t i = 0; i < kColours.size(); ++i)
406 os << (effects ? utils::colourise("*", kColours.at(i)) : "") << (i == 0 ? "|" : "");
407 os << ")\n";
408 }
409
410 std::string TextDrawer::delatexify(const std::string& tok) { return utils::replaceAll(tok, {{"$", ""}}); }
411 } // namespace utils
412} // namespace cepgen
#define REGISTER_DRAWER(name, obj)
Add a drawing utilitary.
#define CG_WARNING(mod)
Definition Message.h:228
#define CG_LOG
Definition Message.h:212
static constexpr double INVALID
Invalid value placeholder (single-edged or invalid limits)
Definition Limits.h:86
double range() const
Full variable range allowed.
Definition Limits.cpp:65
const std::string & name() const
Module unique indexing name.
Definition NamedModule.h:42
A description object for parameters collection.
static ParametersDescription description()
Description of all object parameters.
Definition Steerable.cpp:42
const std::string & label() const
Axis title.
Definition Drawable.h:55
A generic object which can be drawn in the standard output.
Definition Drawable.h:31
std::map< coord_t, Value > axis_t
Metadata for an axis (coordinates and bins value)
Definition Drawable.h:95
const std::string & name() const
Drawable name.
Definition Drawable.h:37
AxisInfo & yAxis()
Definition Drawable.h:81
AxisInfo & xAxis()
Definition Drawable.h:79
std::map< coord_t, axis_t > dualaxis_t
Metadata for a two-dimensional axis definition (coordinates and bins values)
Definition Drawable.h:103
A generic drawing utility.
Definition Drawer.h:36
A one-dimensional graph object.
Definition Graph.h:29
const axis_t & points() const
Retrieve all values in the graph.
Definition Graph.h:38
A two-dimensional graph object.
Definition Graph.h:58
const dualaxis_t & points() const
Retrieve all values in the graph.
Definition Graph.h:67
1D histogram container
Definition Histogram.h:72
double integral(bool=false) const override
Compute the histogram integral.
Definition Hist1D.cpp:227
double mean() const
Compute the mean histogram value over full range.
Definition Hist1D.cpp:207
size_t nbins() const
Number of histogram bins.
Definition Hist1D.cpp:155
axis_t axis() const
Axis content.
Definition Hist1D.cpp:145
size_t overflow() const
Definition Histogram.h:124
double rms() const
Compute the root-mean-square value over full range.
Definition Hist1D.cpp:212
size_t underflow() const
Definition Histogram.h:123
Limits range() const
Axis range.
Definition Hist1D.cpp:160
2D histogram container
Definition Histogram.h:146
Limits binRangeX(size_t bin) const
Range for a single x-axis bin.
Definition Hist2D.cpp:183
double integral(bool=false) const override
Compute the histogram integral.
Definition Hist2D.cpp:273
Limits rangeX() const
x-axis range
Definition Hist2D.cpp:178
size_t nbinsX() const
Number of x-axis bins.
Definition Hist2D.cpp:173
Value value(size_t bin_x, size_t bin_y) const
Retrieve the value + uncertainty for one bin.
Definition Hist2D.cpp:227
double rmsX() const
Compute the root-mean-square value over full x-axis range.
Definition Hist2D.cpp:248
double meanY() const
Compute the mean histogram value over full y-axis range.
Definition Hist2D.cpp:253
const contents_t & outOfRange() const
Definition Histogram.h:229
double rmsY() const
Compute the root-mean-square value over full y-axis range.
Definition Hist2D.cpp:258
size_t nbinsY() const
Number of y-axis bins.
Definition Hist2D.cpp:196
Limits binRangeY(size_t bin) const
Range for a single y-axis bin.
Definition Hist2D.cpp:206
double meanX() const
Compute the mean histogram value over full x-axis range.
Definition Hist2D.cpp:243
Limits rangeY() const
y-axis range
Definition Hist2D.cpp:201
const TextDrawer & draw(const Graph1D &, const Mode &) const override
Draw a one-dimensional graph.
static ParametersDescription description()
TextDrawer(const ParametersList &)
std::string format(const std::string &fmt, Args... args)
Format a string using a printf style format descriptor.
Definition String.h:61
std::string s(const std::string &word, float num, bool show_number)
Add a trailing "s" when needed.
Definition String.cpp:228
std::string boldify(std::string str)
String implementation of the boldification procedure.
Definition String.cpp:49
bool positive(const T &val)
Check if a number is positive and finite.
Definition Math.cpp:26
std::vector< const Drawable * > DrawableColl
A collection of drawable objects.
Definition Drawer.h:34
std::string colourise(const std::string &str, const Colour &col, const Modifier &mod)
Colourise a string for TTY-type output streams.
Definition String.cpp:70
size_t replaceAll(std::string &str, const std::string &from, const std::string &to)
Replace all occurrences of a text by another.
Definition String.cpp:118
std::string merge(const std::vector< T > &vec, const std::string &delim)
Merge a collection of a printable type in a single string.
Definition String.cpp:248
std::vector< std::string > split(const std::string &str, char delim, bool trim)
Split a string according to a separation character.
Definition String.cpp:233
Common namespace for this Monte Carlo generator.
Generic bin coordinate and its human-readable label.
Definition Drawable.h:87