Merged upgraded display.hpp code into histogram/ostream.hpp

supports all axis bin types and gracefully handles histograms with accumulators
This commit is contained in:
Hans Dembinski 2019-10-17 00:58:37 +02:00 committed by GitHub
parent 98926dbbb5
commit 30899cb45d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 647 additions and 81 deletions

View File

@ -50,5 +50,5 @@ test_script:
../../b2 $B2_OPTS toolset=clang cxxstd=14 variant=histogram_ubasan test//all
on_failure:
## Uncomment the following line to stop VM and enable interactive login
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
# Uncomment the following line to stop VM and enable interactive login
- ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))

View File

@ -27,20 +27,10 @@ int main() {
assert(os.str() ==
"histogram(\n"
" regular(2, -1, 1, metadata=\"axis 1\", options=underflow | overflow),\n"
" category(\"red\", \"blue\", metadata=\"axis 2\", options=overflow),\n"
" 0: 0\n"
" 1: 0\n"
" 2: 0\n"
" 3: 0\n"
" 4: 0\n"
" 5: 0\n"
" 6: 0\n"
" 7: 0\n"
" 8: 0\n"
" 9: 0\n"
" 10: 0\n"
" 11: 0\n"
" regular(2, -1, 1, metadata=\"axis 1\", options=underflow | overflow)\n"
" category(\"red\", \"blue\", metadata=\"axis 2\", options=overflow)\n"
" (-1 0): 0 ( 0 0): 0 ( 1 0): 0 ( 2 0): 0 (-1 1): 0 ( 0 1): 0\n"
" ( 1 1): 0 ( 2 1): 0 (-1 2): 0 ( 0 2): 0 ( 1 2): 0 ( 2 2): 0\n"
")");
}

View File

@ -7,6 +7,7 @@
#ifndef BOOST_HISTOGRAM_ACCUMULATORS_OSTREAM_HPP
#define BOOST_HISTOGRAM_ACCUMULATORS_OSTREAM_HPP
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/fwd.hpp>
#include <iosfwd>
@ -26,34 +27,61 @@
namespace boost {
namespace histogram {
namespace detail {
template <class CharT, class Traits, class T>
std::basic_ostream<CharT, Traits>& handle_nonzero_width(
std::basic_ostream<CharT, Traits>& os, const T& x) {
const auto w = os.width();
os.width(0);
counting_streambuf<CharT, Traits> cb;
const auto saved = os.rdbuf(&cb);
os << x;
os.rdbuf(saved);
if (os.flags() & std::ios::left) {
os << x;
for (auto i = cb.count; i < w; ++i) os << os.fill();
} else {
for (auto i = cb.count; i < w; ++i) os << os.fill();
os << x;
}
return os;
}
} // namespace detail
namespace accumulators {
template <class CharT, class Traits, class W>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const sum<W>& x) {
os << "sum(" << x.large() << " + " << x.small() << ")";
return os;
if (os.width() == 0) return os << "sum(" << x.large() << " + " << x.small() << ")";
return detail::handle_nonzero_width(os, x);
}
template <class CharT, class Traits, class W>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const weighted_sum<W>& x) {
os << "weighted_sum(" << x.value() << ", " << x.variance() << ")";
return os;
if (os.width() == 0)
return os << "weighted_sum(" << x.value() << ", " << x.variance() << ")";
return detail::handle_nonzero_width(os, x);
}
template <class CharT, class Traits, class W>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const mean<W>& x) {
os << "mean(" << x.count() << ", " << x.value() << ", " << x.variance() << ")";
return os;
if (os.width() == 0)
return os << "mean(" << x.count() << ", " << x.value() << ", " << x.variance() << ")";
return detail::handle_nonzero_width(os, x);
}
template <class CharT, class Traits, class W>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const weighted_mean<W>& x) {
os << "weighted_mean(" << x.sum_of_weights() << ", " << x.value() << ", "
<< x.variance() << ")";
return os;
if (os.width() == 0)
return os << "weighted_mean(" << x.sum_of_weights() << ", " << x.value() << ", "
<< x.variance() << ")";
return detail::handle_nonzero_width(os, x);
}
template <class CharT, class Traits, class T>

View File

@ -182,11 +182,7 @@ std::basic_ostream<Ts...>& operator<<(std::basic_ostream<Ts...>& os,
using A = std::decay_t<decltype(x)>;
detail::static_if<detail::is_streamable<A>>(
[&os](const auto& x) { os << x; },
[](const auto&) {
BOOST_THROW_EXCEPTION(std::runtime_error(
detail::cat(detail::type_name<A>(), " is not streamable")));
},
x);
[&os](const auto&) { os << "<unstreamable>"; }, x);
},
v);
return os;

View File

@ -284,6 +284,12 @@ decltype(auto) get(const variant<Us...>& v) {
return *tp;
}
// pass-through version of visit for generic programming
template <class Visitor, class T>
decltype(auto) visit(Visitor&& vis, T&& var) {
return std::forward<Visitor>(vis)(std::forward<T>(var));
}
// pass-through version of get for generic programming
template <class T, class U>
decltype(auto) get(U&& u) {

View File

@ -0,0 +1,40 @@
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#ifndef BOOST_HISTOGRAM_DETAIL_COUNTING_STREAMBUF_HPP
#define BOOST_HISTOGRAM_DETAIL_COUNTING_STREAMBUF_HPP
#include <streambuf>
namespace boost {
namespace histogram {
namespace detail {
// detect how many characters will be printed by formatted output
template <class CharT, class Traits = std::char_traits<CharT>>
struct counting_streambuf : std::basic_streambuf<CharT, Traits> {
using base_t = std::basic_streambuf<CharT, Traits>;
using typename base_t::char_type;
using typename base_t::int_type;
std::streamsize count = 0;
std::streamsize xsputn(const char_type* /* s */, std::streamsize n) override {
count += n;
return n;
}
int_type overflow(int_type ch) override {
++count;
return ch;
}
};
} // namespace detail
} // namespace histogram
} // namespace boost
#endif

View File

@ -110,7 +110,7 @@ void fill_n_indices(Index* indices, const std::size_t start, const std::size_t s
for_each_axis(axes, [eit = extents, sit = shifts](const auto& a) mutable {
*sit++ = 0;
*eit++ = axis::traits::extent(a);
});
}); // LCOV_EXCL_LINE: gcc-8 is missing this line for no reason
// offset must be zero for growing axes
using IsGrowing = has_growing_axis<Axes>;
@ -118,7 +118,7 @@ void fill_n_indices(Index* indices, const std::size_t start, const std::size_t s
for_each_axis(axes, [&, stride = static_cast<std::size_t>(1),
pshift = shifts](auto& axis) mutable {
using Axis = std::decay_t<decltype(axis)>;
static_if<is_variant<T>>( // LCOV_EXCL_LINE: buggy in gcc-8, ok in gcc-5
static_if<is_variant<T>>( // LCOV_EXCL_LINE: gcc-8 is missing this line for no reason
[&](const auto& v) {
variant2::visit(index_visitor<Index, Axis, IsGrowing>{axis, stride, start, size,
indices, pshift},

View File

@ -1,5 +1,5 @@
// Copyright 2015-2019 Hans Dembinski
// Copyright (c) 2019 Przemyslaw Bartosik
// Copyright 2019 Przemyslaw Bartosik
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
@ -8,16 +8,21 @@
#ifndef BOOST_HISTOGRAM_OSTREAM_HPP
#define BOOST_HISTOGRAM_OSTREAM_HPP
#include <algorithm> // max_element
#include <boost/histogram.hpp>
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/axis/ostream.hpp>
#include <boost/histogram/fwd.hpp>
#include <cmath> // floor, pow
#include <iomanip> // setw
#include <iosfwd>
#include <iostream> // cout
#include <limits> // infinity
#include <boost/histogram/axis/variant.hpp>
#include <boost/histogram/detail/axes.hpp>
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/detail/detect.hpp>
#include <boost/histogram/detail/static_if.hpp>
#include <boost/histogram/indexed.hpp>
#include <cmath>
#include <iomanip>
#include <limits>
#include <numeric>
#include <ostream>
#include <streambuf>
#include <type_traits>
/**
\file boost/histogram/ostream.hpp
@ -30,19 +35,269 @@
To you use your own, simply include your own implementation instead of this header.
*/
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
namespace boost {
namespace histogram {
namespace detail {
template <class OStream, unsigned N>
class tabular_ostream_wrapper : public std::array<int, N> {
using base_t = std::array<int, N>;
using char_type = typename OStream::char_type;
using traits_type = typename OStream::traits_type;
public:
template <class T>
tabular_ostream_wrapper& operator<<(const T& t) {
if (collect_) {
if (static_cast<std::size_t>(iter_ - base_t::begin()) == size_) {
++size_;
BOOST_ASSERT(size_ <= N);
BOOST_ASSERT(iter_ != end());
*iter_ = 0;
}
cbuf_.count = 0;
os_ << t;
*iter_ = std::max(*iter_, static_cast<int>(cbuf_.count));
} else {
BOOST_ASSERT(iter_ != end());
os_ << std::setw(*iter_) << t;
}
++iter_;
return *this;
}
tabular_ostream_wrapper& operator<<(decltype(std::setprecision(0)) t) {
os_ << t;
return *this;
}
tabular_ostream_wrapper& operator<<(decltype(std::fixed) t) {
os_ << t;
return *this;
}
tabular_ostream_wrapper& row() {
iter_ = base_t::begin();
return *this;
}
explicit tabular_ostream_wrapper(OStream& os) : os_(os), orig_(os_.rdbuf(&cbuf_)) {}
auto end() { return base_t::begin() + size_; }
auto end() const { return base_t::begin() + size_; }
auto cend() const { return base_t::cbegin() + size_; }
void complete() {
BOOST_ASSERT(collect_); // only call this once
collect_ = false;
os_.rdbuf(orig_);
}
private:
typename base_t::iterator iter_ = base_t::begin();
std::size_t size_ = 0;
bool collect_ = true;
OStream& os_;
counting_streambuf<char_type, traits_type> cbuf_;
std::basic_streambuf<char_type, traits_type>* orig_;
};
template <class OStream, class T>
void ostream_value(OStream& os, const T& val) {
// a value from bin or histogram cell
os << std::left;
static_if_c<(std::is_convertible<T, double>::value && !std::is_integral<T>::value)>(
[](auto& os, const auto& val) {
const auto d = static_cast<double>(val);
if (std::isfinite(d)) {
const auto i = static_cast<std::int64_t>(d);
if (i == d) {
os << i;
return;
}
}
os << std::defaultfloat << std::setprecision(4) << d;
},
[](auto& os, const auto& val) { os << val; }, os, val);
}
template <class OStream, class Axis>
void ostream_bin(OStream& os, const Axis& ax, const int i) {
os << std::right;
static_if<has_method_value<Axis>>(
[&](const auto& ax) {
static_if<axis::traits::is_continuous<Axis>>(
[&](const auto& ax) {
os << std::defaultfloat << std::setprecision(4);
auto a = ax.value(i);
auto b = ax.value(i + 1);
// round bin edge to zero if deviation from zero is absolut and relatively
// small
const auto eps = 1e-8 * std::abs(b - a);
if (std::abs(a) < 1e-14 && std::abs(a) < eps) a = 0;
if (std::abs(b) < 1e-14 && std::abs(b) < eps) b = 0;
os << "[" << a << ", " << b << ")";
},
[&](const auto& ax) { os << ax.value(i); }, ax);
},
[&](const auto&) { os << i; }, ax);
}
template <class OStream, class... Ts>
void ostream_bin(OStream& os, const axis::category<Ts...>& ax, const int i) {
os << std::right;
if (i < ax.size())
os << ax.value(i);
else
os << "other";
}
template <class CharT>
struct line_t {
CharT ch;
int size;
};
template <class CharT>
auto line(CharT c, int n) {
return line_t<CharT>{c, n};
}
template <class C, class T>
std::basic_ostream<C, T>& operator<<(std::basic_ostream<C, T>& os, line_t<C>&& l) {
for (int i = 0; i < l.size; ++i) os << l.ch;
return os;
}
template <class OStream, class Axis, class T>
void stream_head(OStream& os, const Axis& ax, int index, const T& val) {
axis::visit(
[&](const auto& ax) {
ostream_bin(os, ax, index);
os << ' ';
ostream_value(os, val);
},
ax);
}
template <class OStream, class Histogram>
void ascii_plot(OStream& os, const Histogram& h, int w_total) {
if (w_total == 0) w_total = 78; // TODO detect actual width of terminal
const auto& ax = h.axis();
// value range; can be integer or float, positive or negative
double vmin = 0;
double vmax = 0;
tabular_ostream_wrapper<OStream, 7> tos(os);
// first pass to get widths
for (auto&& v : indexed(h, coverage::all)) {
stream_head(tos.row(), ax, v.index(), *v);
vmin = std::min(vmin, static_cast<double>(*v));
vmax = std::max(vmax, static_cast<double>(*v));
}
tos.complete();
if (vmax == 0) vmax = 1;
// calculate width useable by bar (notice extra space at top)
// <-- head --> |<--- bar ---> |
// w_head + 2 + 2
const int w_head = std::accumulate(tos.begin(), tos.end(), 0);
const int w_bar = w_total - 4 - w_head;
if (w_bar < 0) return;
// draw upper line
os << '\n' << line(' ', w_head + 1) << '+' << line('-', w_bar + 1) << "+\n";
const int zero_offset = static_cast<int>(std::lround((-vmin) / (vmax - vmin) * w_bar));
for (auto&& v : indexed(h, coverage::all)) {
stream_head(tos.row(), ax, v.index(), *v);
// rest uses os, not tos
os << " |";
const int k = static_cast<int>(std::lround(*v / (vmax - vmin) * w_bar));
if (k < 0) {
os << line(' ', zero_offset + k) << line('=', -k) << line(' ', w_bar - zero_offset);
} else {
os << line(' ', zero_offset) << line('=', k) << line(' ', w_bar - zero_offset - k);
}
os << " |\n";
}
// draw lower line
os << line(' ', w_head + 1) << '+' << line('-', w_bar + 1) << "+\n";
}
template <class OStream, class Histogram>
void ostream(OStream& os, const Histogram& h, const bool show_values = true) {
os << "histogram(";
unsigned iaxis = 0;
const auto rank = h.rank();
h.for_each_axis([&](const auto& ax) {
using A = std::decay_t<decltype(ax)>;
if ((show_values && rank > 0) || rank > 1) os << "\n ";
static_if<is_streamable<A>>([&](const auto& ax) { os << ax; },
[&](const auto&) { os << "<unstreamable>"; }, ax);
});
if (show_values && rank > 0) {
tabular_ostream_wrapper<OStream, (BOOST_HISTOGRAM_DETAIL_AXES_LIMIT + 1)> tos(os);
for (auto&& v : indexed(h, coverage::all)) {
tos.row();
for (auto i : v.indices()) tos << std::right << i;
ostream_value(tos, *v);
}
tos.complete();
const int w_item = std::accumulate(tos.begin(), tos.end(), 0) + 4 + h.rank();
const int nrow = std::max(1, 65 / w_item);
int irow = 0;
for (auto&& v : indexed(h, coverage::all)) {
os << (irow == 0 ? "\n (" : " (");
tos.row();
iaxis = 0;
for (auto i : v.indices()) {
tos << std::right << i;
os << (++iaxis == h.rank() ? "):" : " ");
}
os << ' ';
ostream_value(tos, *v);
++irow;
if (nrow > 0 && irow == nrow) irow = 0;
}
os << '\n';
}
os << ')';
}
} // namespace detail
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
template <typename CharT, typename Traits, typename A, typename S>
std::basic_ostream<CharT, Traits>& operator<<(std::basic_ostream<CharT, Traits>& os,
const histogram<A, S>& h) {
os << "histogram(";
h.for_each_axis([&](const auto& a) { os << "\n " << a << ","; });
std::size_t i = 0;
for (auto&& x : h) os << "\n " << i++ << ": " << x;
os << (h.rank() ? "\n)" : ")");
// save fmt
const auto flags = os.flags();
os.flags(std::ios::dec | std::ios::left);
const auto w = static_cast<int>(os.width());
os.width(0);
using value_type = typename histogram<A, S>::value_type;
detail::static_if<std::is_convertible<value_type, double>>(
[&os, w](const auto& h) {
if (h.rank() == 1) {
detail::ostream(os, h, false);
detail::ascii_plot(os, h, w);
} else
detail::ostream(os, h);
},
[&os](const auto& h) { detail::ostream(os, h); }, h);
// restore fmt
os.flags(flags);
return os;
}

View File

@ -91,6 +91,8 @@ boost_test(TYPE run SOURCES histogram_mixed_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES histogram_operators_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES histogram_ostream_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES histogram_test.cpp
LIBRARIES Boost::histogram Boost::core)
boost_test(TYPE run SOURCES indexed_test.cpp

View File

@ -65,6 +65,7 @@ alias cxx14 :
[ run histogram_growing_test.cpp ]
[ run histogram_mixed_test.cpp ]
[ run histogram_operators_test.cpp ]
[ run histogram_ostream_test.cpp ]
[ run histogram_test.cpp ]
[ run indexed_test.cpp ]
[ run storage_adaptor_test.cpp ]

View File

@ -19,8 +19,13 @@ using namespace boost::histogram;
using namespace std::literals;
template <class T>
auto str(const T& t) {
auto str(const T& t, int w = 0, bool left = true) {
std::ostringstream os;
os.width(w);
if (left)
os << std::left;
else
os << std::right;
os << t;
return os.str();
}
@ -30,6 +35,8 @@ int main() {
using w_t = accumulators::weighted_sum<double>;
w_t w;
BOOST_TEST_EQ(str(w), "weighted_sum(0, 0)"s);
BOOST_TEST_EQ(str(w, 20, false), " weighted_sum(0, 0)"s);
BOOST_TEST_EQ(str(w, 20, true), "weighted_sum(0, 0) "s);
BOOST_TEST_EQ(w, w_t{});
BOOST_TEST_EQ(w, w_t(0));
@ -83,6 +90,8 @@ int main() {
BOOST_TEST_EQ(a.variance(), 30);
BOOST_TEST_EQ(str(a), "mean(4, 10, 30)"s);
BOOST_TEST_EQ(str(a, 20, false), " mean(4, 10, 30)"s);
BOOST_TEST_EQ(str(a, 20, true), "mean(4, 10, 30) "s);
m_t b;
b(1e8 + 4);
@ -126,6 +135,8 @@ int main() {
BOOST_TEST_IS_CLOSE(a.variance(), 0.8, 1e-3);
BOOST_TEST_EQ(str(a), "weighted_mean(2, 2, 0.8)"s);
BOOST_TEST_EQ(str(a, 25, false), " weighted_mean(2, 2, 0.8)"s);
BOOST_TEST_EQ(str(a, 25, true), "weighted_mean(2, 2, 0.8) "s);
auto b = a;
b += a; // same as feeding all samples twice
@ -148,6 +159,9 @@ int main() {
BOOST_TEST_EQ(sum.large(), 1);
BOOST_TEST_EQ(sum.small(), 0);
BOOST_TEST_EQ(str(sum), "sum(1 + 0)"s);
BOOST_TEST_EQ(str(sum, 15, false), " sum(1 + 0)"s);
BOOST_TEST_EQ(str(sum, 15, true), "sum(1 + 0) "s);
sum += 1e100;
BOOST_TEST_EQ(str(sum), "sum(1e+100 + 1)"s);
++sum;

View File

@ -124,9 +124,7 @@ int main() {
auto test = [](auto&& a, const char* ref) {
using T = std::decay_t<decltype(a)>;
axis::variant<T> axis(std::move(a));
std::ostringstream os;
os << axis;
BOOST_TEST_EQ(os.str(), std::string(ref));
BOOST_TEST_CSTR_EQ(detail::cat(axis).c_str(), ref);
};
test(axis::regular<>(2, -1, 1, "regular1"),
@ -143,9 +141,7 @@ int main() {
auto test = [](auto&& a, const char* ref) {
using T = std::decay_t<decltype(a)>;
axis::variant<T> axis(std::move(a));
std::ostringstream os;
os << axis.bin(0);
BOOST_TEST_EQ(os.str(), std::string(ref));
BOOST_TEST_CSTR_EQ(detail::cat(axis.bin(0)).c_str(), ref);
};
test(axis::regular<>(2, 1, 2), "[1, 1.5)");
@ -194,7 +190,7 @@ int main() {
BOOST_TEST_EQ(axis.index(9), 1);
BOOST_TEST_EQ(axis.size(), 2);
BOOST_TEST_EQ(axis.metadata(), axis::null_type{});
BOOST_TEST_THROWS(std::ostringstream() << axis, std::runtime_error);
BOOST_TEST_CSTR_EQ(detail::cat(axis).c_str(), "<unstreamable>");
BOOST_TEST_THROWS(axis.value(0), std::runtime_error);
axis = axis::category<std::string>({"A", "B"}, "category");

View File

@ -9,11 +9,13 @@
#include <boost/histogram/accumulators/weighted_sum.hpp>
#include <boost/histogram/detail/cat.hpp>
#include <boost/histogram/detail/common_type.hpp>
#include <boost/histogram/detail/counting_streambuf.hpp>
#include <boost/histogram/detail/non_member_container_access.hpp>
#include <boost/histogram/fwd.hpp>
#include <boost/histogram/literals.hpp>
#include <boost/histogram/storage_adaptor.hpp>
#include <boost/histogram/unlimited_storage.hpp>
#include <ostream>
#include "std_ostream.hpp"
using namespace boost::histogram;
@ -74,5 +76,17 @@ int main() {
BOOST_TEST_EQ(dtl::size(d), 5u);
}
// counting_streambuf
{
dtl::counting_streambuf<char> cbuf;
std::ostream os(&cbuf);
os.put('x');
BOOST_TEST_EQ(cbuf.count, 1);
os << 12;
BOOST_TEST_EQ(cbuf.count, 3);
os << "123";
BOOST_TEST_EQ(cbuf.count, 6);
}
return boost::report_errors();
}

View File

@ -164,36 +164,11 @@ void run_tests() {
BOOST_TEST_THROWS(a *= b, std::invalid_argument);
BOOST_TEST_THROWS(a /= b, std::invalid_argument);
}
// histogram_ostream
{
auto a = make(Tag(), axis::regular<>(3, -1, 1, "r"));
std::ostringstream os;
os << a;
BOOST_TEST_EQ(
os.str(),
std::string("histogram(\n"
" regular(3, -1, 1, metadata=\"r\", options=underflow | overflow),\n"
" 0: 0\n"
" 1: 0\n"
" 2: 0\n"
" 3: 0\n"
" 4: 0\n"
")"));
}
}
int main() {
run_tests<static_tag>();
run_tests<dynamic_tag>();
{
// cannot make empty static histogram
auto h = histogram<std::vector<axis::regular<>>>();
std::ostringstream os;
os << h;
BOOST_TEST_EQ(os.str(), std::string("histogram()"));
}
return boost::report_errors();
}

View File

@ -0,0 +1,249 @@
// Copyright 2019 Przemyslaw Bartosik
// Copyright 2019 Hans Dembinski
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)
#include <boost/core/lightweight_test.hpp>
#include <boost/histogram/accumulators/mean.hpp>
#include <boost/histogram/accumulators/ostream.hpp>
#include <boost/histogram/axis/category.hpp>
#include <boost/histogram/axis/integer.hpp>
#include <boost/histogram/axis/option.hpp>
#include <boost/histogram/axis/regular.hpp>
#include <boost/histogram/make_histogram.hpp>
#include <boost/histogram/ostream.hpp>
#include <limits>
#include <sstream>
#include <string>
#include "throw_exception.hpp"
#include "utility_histogram.hpp"
using namespace boost::histogram;
template <class Histogram>
auto str(const Histogram& h, const unsigned width = 0) {
std::ostringstream os;
// BEGIN and END make nicer error messages
os << "BEGIN\n" << std::setw(width) << h << "END";
return os.str();
}
template <class Tag>
void run_tests() {
using R = axis::regular<>;
using R2 =
axis::regular<double, boost::use_default, axis::null_type, axis::option::none_t>;
using R3 = axis::regular<double, axis::transform::log>;
using C = axis::category<std::string>;
using I = axis::integer<>;
// regular
{
auto h = make(Tag(), R(3, -0.5, 1.0));
h.at(0) = 1;
h.at(1) = 10;
h.at(2) = 5;
const auto expected =
"BEGIN\n"
"histogram(regular(3, -0.5, 1, options=underflow | overflow))\n"
" +------------------------------------------------------------+\n"
"[-inf, -0.5) 0 | |\n"
"[-0.5, 0) 1 |====== |\n"
"[ 0, 0.5) 10 |=========================================================== |\n"
"[ 0.5, 1) 5 |============================== |\n"
"[ 1, inf) 0 | |\n"
" +------------------------------------------------------------+\n"
"END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
// regular, narrow
{
auto h = make(Tag(), R2(3, -0.5, 1.0));
h.at(0) = 1;
h.at(1) = 10;
h.at(2) = 2;
const auto expected = "BEGIN\n"
"histogram(regular(3, -0.5, 1, options=none))\n"
" +-----------------------+\n"
"[-0.5, 0) 1 |== |\n"
"[ 0, 0.5) 10 |====================== |\n"
"[ 0.5, 1) 2 |==== |\n"
" +-----------------------+\n"
"END";
BOOST_TEST_CSTR_EQ(expected, str(h, 40).c_str());
// too narrow
BOOST_TEST_CSTR_EQ("BEGIN\n"
"histogram(regular(3, -0.5, 1, options=none))END",
str(h, 10).c_str());
}
// regular2
{
auto h = make(Tag(), R2(3, -0.5, 1.0));
h.at(0) = 1;
h.at(1) = -5;
h.at(2) = 2;
const auto expected =
"BEGIN\n"
"histogram(regular(3, -0.5, 1, options=none))\n"
" +-------------------------------------------------------------+\n"
"[-0.5, 0) 1 | ========= |\n"
"[ 0, 0.5) -5 |=========================================== |\n"
"[ 0.5, 1) 2 | ================= |\n"
" +-------------------------------------------------------------+\n"
"END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
// regular with log
{
auto h = make(Tag(), R3(6, 1e-3, 1e3, "foo"));
const auto expected =
"BEGIN\n"
"histogram(regular_log(6, 0.001, 1000, metadata=\"foo\", options=underflow | "
"overflow))\n"
" +-----------------------------------------------------------+\n"
"[ 0, 0.001) 0 | |\n"
"[0.001, 0.01) 0 | |\n"
"[ 0.01, 0.1) 0 | |\n"
"[ 0.1, 1) 0 | |\n"
"[ 1, 10) 0 | |\n"
"[ 10, 100) 0 | |\n"
"[ 100, 1000) 0 | |\n"
"[ 1000, inf) 0 | |\n"
" +-----------------------------------------------------------+\n"
"END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
// integer
{
auto h = make(Tag(), I(0, 1));
h.at(0) = -10;
h.at(1) = 5;
const auto expected =
"BEGIN\n"
"histogram(integer(0, 1, options=underflow | overflow))\n"
" +---------------------------------------------------------------------+\n"
"-1 0 | |\n"
" 0 -10 |============================================= |\n"
" 1 5 | ======================= |\n"
" +---------------------------------------------------------------------+\n"
"END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
// catorgy<string>
{
auto h = make(Tag(), C({"a", "bb", "ccc", "dddd"}));
h.at(0) = 1.23;
h.at(1) = 1;
h.at(2) = 1.2345789e-3;
h.at(3) = 1.2345789e-12;
h.at(4) = std::numeric_limits<double>::quiet_NaN();
const auto expected =
"BEGIN\n"
"histogram(category(\"a\", \"bb\", \"ccc\", \"dddd\", options=overflow))\n"
" +------------------------------------------------------------+\n"
" a 1.23 |=========================================================== |\n"
" bb 1 |================================================ |\n"
" ccc 0.001235 | |\n"
" dddd 1.235e-12 | |\n"
"other nan | |\n"
" +------------------------------------------------------------+\n"
"END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
// histogram with axis that has no value method
{
struct minimal_axis {
int index(int x) const { return x % 2; }
int size() const { return 2; }
};
auto h = make(Tag(), minimal_axis{});
h.at(0) = 3;
h.at(1) = 4;
const auto expected =
"BEGIN\n"
"histogram(<unstreamable>)\n"
" +------------------------------------------------------------------------+\n"
"0 3 |===================================================== |\n"
"1 4 |======================================================================= |\n"
" +------------------------------------------------------------------------+\n"
"END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
// fallback for 2D
{
auto h = make(Tag(), R(1, -1, 1), R(2, -4, 7));
h.at(-1, 0) = 1000;
h.at(-1, -1) = 123;
h.at(1, 0) = 1.23456789;
h.at(-1, 2) = std::numeric_limits<double>::quiet_NaN();
const auto expected =
"BEGIN\n"
"histogram(\n"
" regular(1, -1, 1, options=underflow | overflow)\n"
" regular(2, -4, 7, options=underflow | overflow)\n"
" (-1 -1): 123 ( 0 -1): 0 ( 1 -1): 0 (-1 0): 1000 \n"
" ( 0 0): 0 ( 1 0): 1.235 (-1 1): 0 ( 0 1): 0 \n"
" ( 1 1): 0 (-1 2): nan ( 0 2): 0 ( 1 2): 0 \n"
")END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
// fallback for profile
{
auto h = make_s(Tag(), profile_storage(), R(1, -1, 1));
h.at(0) = accumulators::mean<>(10, 100, 1000);
const auto expected = "BEGIN\n"
"histogram(\n"
" regular(1, -1, 1, options=underflow | overflow)\n"
" (-1): mean(0, 0, -0) ( 0): mean(10, 100, 1000)\n"
" ( 1): mean(0, 0, -0) \n"
")END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
}
int main() {
run_tests<static_tag>();
run_tests<dynamic_tag>();
{
// cannot make empty static histogram
auto h = histogram<std::vector<axis::regular<>>>();
const auto expected = "BEGIN\n"
"histogram()END";
BOOST_TEST_CSTR_EQ(expected, str(h).c_str());
}
return boost::report_errors();
}