improved support and testing of axes over boost::units

This commit is contained in:
Hans Dembinski 2018-12-20 12:25:06 +01:00
parent 68ce22a323
commit 2d5fb32b88
10 changed files with 181 additions and 163 deletions

View File

@ -30,27 +30,24 @@ namespace axis {
result in a dangling reference. Rather than specialing the code to handle
this, it seems easier to just use a value instead of a view here.
*/
template <typename T>
template <typename RealType>
class polymorphic_bin {
using value_type = T;
using value_type = RealType;
public:
polymorphic_bin(value_type value)
: lower_or_value_(value), upper_(value), center_(value) {}
polymorphic_bin(value_type lower, value_type upper, value_type center)
: lower_or_value_(lower), upper_(upper), center_(center) {}
polymorphic_bin(value_type lower, value_type upper)
: lower_or_value_(lower), upper_(upper) {}
operator value_type() const noexcept { return lower_or_value_; }
value_type lower() const noexcept { return lower_or_value_; }
value_type upper() const noexcept { return upper_; }
value_type center() const noexcept { return center_; }
value_type center() const noexcept { return 0.5 * (lower() + upper()); }
value_type width() const noexcept { return upper() - lower(); }
template <typename BinType>
bool operator==(const BinType& rhs) const noexcept {
return equal_impl(rhs, detail::has_method_lower<BinType>());
return equal_impl(detail::has_method_lower<BinType>(), rhs);
}
template <typename BinType>
@ -61,22 +58,21 @@ public:
bool is_discrete() const noexcept { return lower_or_value_ == upper_; }
private:
bool equal_impl(const polymorphic_bin& rhs, std::true_type) const noexcept {
return lower_or_value_ == rhs.lower_or_value_ && upper_ == rhs.upper_ &&
center_ == rhs.center_;
bool equal_impl(std::true_type, const polymorphic_bin& rhs) const noexcept {
return lower_or_value_ == rhs.lower_or_value_ && upper_ == rhs.upper_;
}
template <typename BinType>
bool equal_impl(const BinType& rhs, std::true_type) const noexcept {
bool equal_impl(std::true_type, const BinType& rhs) const noexcept {
return lower() == rhs.lower() && upper() == rhs.upper();
}
template <typename BinType>
bool equal_impl(const BinType& rhs, std::false_type) const noexcept {
bool equal_impl(std::false_type, const BinType& rhs) const noexcept {
return is_discrete() && static_cast<value_type>(*this) == rhs;
}
const value_type lower_or_value_, upper_, center_;
const value_type lower_or_value_, upper_;
};
} // namespace axis

View File

@ -21,27 +21,6 @@
namespace boost {
namespace histogram {
namespace detail {
template <typename T>
using get_value_type = typename T::value_type;
template <typename T>
using get_unit_type = typename T::unit_type;
struct one {};
template <typename T>
T operator*(T&& t, const one&) {
return std::forward<T>(t);
}
template <typename T>
T operator/(T&& t, const one&) {
return std::forward<T>(t);
}
} // namespace detail
namespace axis {
// two_pi can be found in boost/math, but it is defined here to reduce deps
@ -115,9 +94,8 @@ class regular : public base<MetaData, Options>,
using metadata_type = MetaData;
using transform_type = Transform;
using value_type = RealType;
using unit_type = detail::mp_eval_or<detail::get_unit_type, value_type, detail::one>;
using internal_type =
detail::mp_eval_or<detail::get_value_type, value_type, value_type>;
using unit_type = detail::get_unit_type<value_type>;
using internal_type = detail::get_scale_type<value_type>;
static_assert(!(Options & option_type::circular) || !(Options & option_type::underflow),
"circular axis cannot have underflow");
@ -136,8 +114,8 @@ public:
metadata_type m = {})
: base_type(n, std::move(m))
, transform_type(std::move(trans))
, min_(this->forward(mag(start)))
, delta_(this->forward(mag(stop)) - min_) {
, min_(this->forward(detail::get_scale(start)))
, delta_(this->forward(detail::get_scale(stop)) - min_) {
if (!std::isfinite(min_) || !std::isfinite(delta_))
BOOST_THROW_EXCEPTION(
std::invalid_argument("forward transform of start or stop invalid"));
@ -160,8 +138,8 @@ public:
regular(const regular& src, int begin, int end, unsigned merge)
: base_type((end - begin) / merge, src.metadata())
, transform_type(src.transform())
, min_(this->forward(mag(src.value(begin))))
, delta_(this->forward(mag(src.value(end))) - min_) {
, min_(this->forward(detail::get_scale(src.value(begin))))
, delta_(this->forward(detail::get_scale(src.value(end))) - min_) {
BOOST_ASSERT((end - begin) % merge == 0);
if (Options & option_type::circular && !(begin == 0 && end == src.size()))
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot shrink circular axis"));
@ -222,8 +200,6 @@ public:
void serialize(Archive&, unsigned);
private:
internal_type mag(const value_type& x) const noexcept { return x / unit_type(); }
internal_type min_, delta_;
}; // namespace axis

View File

@ -7,6 +7,8 @@
#ifndef BOOST_HISTOGRAM_AXIS_TRAITS_HPP
#define BOOST_HISTOGRAM_AXIS_TRAITS_HPP
#include <boost/core/typeinfo.hpp>
#include <boost/histogram/detail/cat.hpp>
#include <boost/histogram/detail/meta.hpp>
#include <boost/histogram/histogram_fwd.hpp>
#include <boost/throw_exception.hpp>
@ -24,6 +26,42 @@ template <typename T>
axis::option_type options_impl(const T& t, std::true_type) {
return t.options();
}
template <typename FIntArg, typename FDoubleArg, typename T>
decltype(auto) value_method_switch(FIntArg&& iarg, FDoubleArg&& darg, const T& t) {
using U = unqual<T>;
return static_if<has_method_value<U>>(
[](FIntArg&& iarg, FDoubleArg&& darg, const auto& t) {
using A = unqual<decltype(t)>;
return static_if<std::is_same<arg_type<decltype(&A::value), 0>, int>>(
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
},
[](FIntArg&&, FDoubleArg&&, const auto& t) {
using A = unqual<decltype(t)>;
BOOST_THROW_EXCEPTION(std::runtime_error(detail::cat(
boost::core::demangled_name(BOOST_CORE_TYPEID(A)), " has no value method")));
return 0;
},
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
}
template <typename R1, typename R2, typename FIntArg, typename FDoubleArg, typename T>
R2 value_method_switch_with_return_type(FIntArg&& iarg, FDoubleArg&& darg, const T& t) {
using U = unqual<T>;
return static_if<has_method_value_with_convertible_return_type<U, R1>>(
[](FIntArg&& iarg, FDoubleArg&& darg, const auto& t) -> R2 {
using A = unqual<decltype(t)>;
return static_if<std::is_same<arg_type<decltype(&A::value), 0>, int>>(
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
},
[](FIntArg&&, FDoubleArg&&, const auto&) -> R2 {
BOOST_THROW_EXCEPTION(std::runtime_error(
detail::cat(boost::core::demangled_name(BOOST_CORE_TYPEID(U)),
" has no value method or return type is not convertible to ",
boost::core::demangled_name(BOOST_CORE_TYPEID(R1)))));
},
std::forward<FIntArg>(iarg), std::forward<FDoubleArg>(darg), t);
}
} // namespace detail
namespace axis {
@ -51,37 +89,31 @@ int extend(const T& t) noexcept {
}
template <typename T>
double value(const T& t, double idx) {
return detail::static_if<detail::has_method_value<detail::unqual<T>, double>>(
[&](const auto& a) {
using Arg = detail::unqual<detail::arg_type<detail::unqual<decltype(a)>>>;
return detail::static_if<std::is_integral<Arg>>(
[&](const auto& a) -> double { return a.value(static_cast<int>(idx)); },
[&](const auto& a) -> double { return a.value(idx); }, a);
},
[](const auto&) -> double {
BOOST_THROW_EXCEPTION(std::runtime_error(
"axis has no value method or return type is not convertible to double"));
return 0;
},
t);
decltype(auto) value(const T& t, double idx) {
return detail::value_method_switch(
[idx](const auto& a) { return a.value(static_cast<int>(idx)); },
[idx](const auto& a) { return a.value(idx); }, t);
}
template <typename R, typename T>
R value_as(const T& t, double idx) {
return detail::value_method_switch_with_return_type<R, R>(
[idx](const auto& a) -> R { return a.value(static_cast<int>(idx)); },
[idx](const auto& a) -> R { return a.value(idx); }, t);
}
template <typename T>
double width(const T& t, int idx) {
return detail::static_if<detail::has_method_value<detail::unqual<T>, double>>(
[&](const auto& a) {
using Arg = detail::unqual<detail::arg_type<detail::unqual<decltype(a)>>>;
return detail::static_if<std::is_integral<Arg>>(
[&](const auto&) -> double { return 1; },
[&](const auto& a) -> double { return a.value(idx + 1) - a.value(idx); }, a);
},
[](const auto&) -> double {
BOOST_THROW_EXCEPTION(std::runtime_error(
"axis has no value method or return type is not convertible to double"));
return 0;
},
t);
decltype(auto) width(const T& t, int idx) {
return detail::value_method_switch(
[](const auto&) { return 0; },
[idx](const auto& a) { return a.value(idx + 1) - a.value(idx); }, t);
}
template <typename R, typename T>
R width_as(const T& t, int idx) {
return detail::value_method_switch_with_return_type<R, R>(
[](const auto&) { return R(); },
[idx](const auto& a) -> R { return a.value(idx + 1) - a.value(idx); }, t);
}
} // namespace traits

View File

@ -27,42 +27,6 @@ namespace boost {
namespace histogram {
namespace detail {
struct get_polymorphic_bin : public boost::static_visitor<axis::polymorphic_bin<double>> {
using T = axis::polymorphic_bin<double>;
int idx;
get_polymorphic_bin(int i) : idx(i) {}
template <typename A>
T operator()(const A& a) const {
// using static_if produces internal compiler error in gcc-5.5 here
return impl(a, detail::has_method_value<A, double>());
}
template <typename A>
T impl(const A&, std::false_type) const {
BOOST_THROW_EXCEPTION(std::runtime_error(
cat(boost::core::demangled_name(BOOST_CORE_TYPEID(A)),
" has no value method with return type convertible to double")));
return T(0);
}
template <typename A>
T impl(const A& a, std::true_type) const {
using Arg = detail::unqual<detail::arg_type<detail::unqual<A>>>;
return impl(a, std::true_type(), std::is_floating_point<Arg>());
}
template <typename A>
T impl(const A& a, std::true_type, std::false_type) const {
return T(a.value(idx));
}
template <typename A>
T impl(const A& a, std::true_type, std::true_type) const {
return T(a.value(idx), a.value(idx + 1), a.value(idx + 0.5));
}
};
template <typename F, typename R>
struct functor_wrapper : public boost::static_visitor<R> {
F& fcn;
@ -201,13 +165,26 @@ public:
// Only works for axes with value method that returns something convertible to
// double and will throw a runtime_error otherwise, see axis::traits::value
double value(double idx) const {
return visit([idx](const auto& a) { return axis::traits::value(a, idx); }, *this);
return visit([idx](const auto& a) { return traits::value_as<double>(a, idx); },
*this);
}
auto operator[](const int idx) const {
// using visit here causes internal error in MSVC 2017, so we work around
return boost::apply_visitor(detail::get_polymorphic_bin(idx),
static_cast<const base_type&>(*this));
return visit(
[idx](const auto& a) {
return detail::value_method_switch_with_return_type<double,
polymorphic_bin<double>>(
[idx](const auto& a) { // axis is discrete
const auto x = a.value(idx);
return polymorphic_bin<double>(x, x);
},
[idx](const auto& a) { // axis is continuous
return polymorphic_bin<double>(a.value(idx), a.value(idx + 1));
},
a);
},
*this);
}
template <typename... Us>
@ -313,14 +290,6 @@ const T* get(const U* u) {
return std::is_same<T, detail::unqual<U>>::value ? reinterpret_cast<const T*>(u)
: nullptr;
}
// pass-through version for generic programming, if U is axis instead of variant
template <typename Functor, typename T,
typename = detail::requires_axis<detail::unqual<T>>>
decltype(auto) visit(Functor&& f, T&& t) {
return std::forward<Functor>(f)(std::forward<T>(t));
}
} // namespace axis
} // namespace histogram
} // namespace boost

View File

@ -155,14 +155,25 @@ void rank_check(const T& axes, const unsigned N) {
BOOST_ASSERT_MSG(N < axes_size(axes), "index out of range");
}
template <typename F, typename... Ts>
void for_each_axis(const std::tuple<Ts...>& axes, F&& f) {
mp11::tuple_for_each(axes, std::forward<F>(f));
template <typename F, typename T>
void for_each_axis_impl(std::true_type, const T& axes, F&& f) {
for (const auto& x : axes) { axis::visit(std::forward<F>(f), x); }
}
template <typename F, typename T>
void for_each_axis_impl(std::false_type, const T& axes, F&& f) {
for (const auto& x : axes) f(x);
}
template <typename F, typename T>
void for_each_axis(const T& axes, F&& f) {
for (const auto& x : axes) { axis::visit(std::forward<F>(f), x); }
using U = mp11::mp_first<unqual<T>>;
for_each_axis_impl(is_axis_variant<U>(), axes, std::forward<F>(f));
}
template <typename F, typename... Ts>
void for_each_axis(const std::tuple<Ts...>& axes, F&& f) {
mp11::tuple_for_each(axes, std::forward<F>(f));
}
template <typename Axes, typename T>
@ -172,8 +183,8 @@ using axes_buffer = boost::container::static_vector<
std::tuple_size, Axes>::value>;
template <typename T>
auto make_empty_axes(const std::vector<T>& t) {
auto r = std::vector<T>(t.get_allocator());
auto make_empty_axes(const T& t) {
auto r = T(t.get_allocator());
r.reserve(t.size());
for_each_axis(t, [&r](const auto& a) {
using U = unqual<decltype(a)>;
@ -199,9 +210,9 @@ auto make_sub_axes(const std::tuple<Ts...>& t, Ns... ns) {
return std::make_tuple(std::get<ns>(t)...);
}
template <typename... Ns, typename... Ts>
auto make_sub_axes(const std::vector<Ts...>& t, Ns... ns) {
return std::vector<Ts...>({t[ns]...}, t.get_allocator());
template <typename... Ns, typename T>
auto make_sub_axes(const T& t, Ns... ns) {
return T({t[ns]...}, t.get_allocator());
}
/// Index with an invalid state

View File

@ -172,27 +172,17 @@ BOOST_HISTOGRAM_MAKE_SFINAE(has_method_clear, &T::clear);
BOOST_HISTOGRAM_MAKE_SFINAE(has_method_lower, &T::lower);
template <typename T, typename X>
struct has_method_value_impl {
template <typename U, typename V = decltype(std::declval<U&>().value(0))>
static typename std::is_convertible<V, X>::type Test(void*);
template <typename U>
static std::false_type Test(...);
using type = decltype(Test<T>(nullptr));
};
template <typename T, typename X>
using has_method_value = typename has_method_value_impl<T, X>::type;
BOOST_HISTOGRAM_MAKE_SFINAE(has_method_value, &T::value);
template <typename T>
struct has_method_options_impl {
template <typename U, typename V = decltype(std::declval<const U&>().options())>
static typename std::is_same<V, axis::option_type>::type Test(void*);
template <typename U>
static std::false_type Test(...);
using type = decltype(Test<T>(nullptr));
};
template <typename T>
using has_method_options = typename has_method_options_impl<T>::type;
using get_value_method_return_type_impl = decltype(std::declval<T&>().value(0));
template <typename T, typename R>
using has_method_value_with_convertible_return_type =
typename std::is_convertible<mp_eval_or<get_value_method_return_type_impl, T, void>,
R>::type;
BOOST_HISTOGRAM_MAKE_SFINAE(has_method_options, (std::declval<const T&>().options()));
BOOST_HISTOGRAM_MAKE_SFINAE(has_allocator, &T::get_allocator);
@ -318,6 +308,45 @@ template <typename T>
constexpr bool relaxed_equal(const T& a, const T& b) noexcept {
return relaxed_equal_impl(is_equal_comparable<unqual<T>>(), a, b);
}
template <typename T>
using get_scale_type_helper = typename T::value_type;
template <typename T>
using get_scale_type = detail::mp_eval_or<detail::get_scale_type_helper, T, T>;
struct one_unit {};
template <typename T>
T operator*(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <typename T>
T operator/(T&& t, const one_unit&) {
return std::forward<T>(t);
}
template <typename T>
using get_unit_type_helper = typename T::unit_type;
template <typename T>
using get_unit_type = detail::mp_eval_or<detail::get_unit_type_helper, T, one_unit>;
template <typename T, typename R = get_scale_type<T>>
R get_scale(const T& t) {
return t / get_unit_type<T>();
}
struct product {
auto operator()() { return 1.0; }
template <class T, class... Ts>
auto operator()(T t, Ts... ts) {
return t * product()(ts...);
}
};
} // namespace detail
} // namespace histogram
} // namespace boost

View File

@ -59,11 +59,13 @@ public:
decltype(auto) bin(unsigned d) const { return parent_.hist_.axis(d)[(*this)[d]]; }
decltype(auto) density() const {
double density() const {
double x = 1;
auto it = begin();
parent_.hist_.for_each_axis(
[&](const auto& a) { x *= axis::traits::width(a, *it++); });
parent_.hist_.for_each_axis([&](const auto& a) {
const auto w = axis::traits::width_as<double>(a, *it++);
x *= w ? w : 1;
});
return *iter_ / x;
}

View File

@ -241,7 +241,7 @@ int main() {
BOOST_TEST_EQ(db.at<long>().first, 5u);
}
// testing pass-through versions of get and visit
// testing pass-through versions of get
{
axis::regular<> a(10, 0, 1);
axis::integer<> b(0, 3);
@ -251,8 +251,6 @@ int main() {
BOOST_TEST_EQ(tb, &b);
const auto* tc = axis::get<axis::regular<>>(&b);
BOOST_TEST_EQ(tc, nullptr);
axis::visit([&](const auto& x) { BOOST_TEST_EQ(a, x); }, a);
}
// iterators

View File

@ -51,13 +51,14 @@ int main() {
BOOST_TEST_EQ(b.value(3) / si::meter, std::numeric_limits<double>::infinity());
}
// histogram with quanity axis
// histogram with quantity axis
{
auto h = make_histogram(axis::regular<Q>(2, 0 * si::meter, 1 * si::meter),
axis::regular<>(2, 0, 1));
h(0.1 * si::meter, 0.1); // fills bin (0, 0)
BOOST_TEST_EQ(h.at(0, 0), 1);
for (auto&& x : indexed(h)) {
// auto d = x.density(); // NEEDS TO BE FIXED AT LEAST FOR STATIC AXIS
// BOOST_TEST_EQ(d * si::meter, 0.25);
BOOST_TEST_THROWS(x.density(), std::runtime_error); // cannot use density method
BOOST_TEST_EQ(x[0], 2.0 * x.bin(0_c).lower() / si::meter);
BOOST_TEST_EQ(x[1], 2.0 * x.bin(1_c).lower());
}

View File

@ -38,7 +38,7 @@ struct VisitorTestFunctor {
};
int main() {
// has_method_value
// has_method_value*
{
struct A {};
struct B {
@ -48,11 +48,15 @@ int main() {
char value(int) const { return 0; }
};
BOOST_TEST_TRAIT_FALSE((has_method_value<A, double>));
BOOST_TEST_TRAIT_TRUE((has_method_value<B, A>));
BOOST_TEST_TRAIT_FALSE((has_method_value<B, char>));
BOOST_TEST_TRAIT_TRUE((has_method_value<C, char>));
BOOST_TEST_TRAIT_FALSE((has_method_value<C, A>));
BOOST_TEST_TRAIT_FALSE((has_method_value<A>));
BOOST_TEST_TRAIT_TRUE((has_method_value<B>));
BOOST_TEST_TRAIT_TRUE((has_method_value<C>));
BOOST_TEST_TRAIT_FALSE((has_method_value_with_convertible_return_type<A, double>));
BOOST_TEST_TRAIT_TRUE((has_method_value_with_convertible_return_type<B, A>));
BOOST_TEST_TRAIT_FALSE((has_method_value_with_convertible_return_type<B, char>));
BOOST_TEST_TRAIT_TRUE((has_method_value_with_convertible_return_type<C, int>));
BOOST_TEST_TRAIT_FALSE((has_method_value_with_convertible_return_type<C, A>));
}
// has_method_options