mirror of
https://github.com/boostorg/histogram.git
synced 2025-05-09 14:57:57 +00:00
reducible category axis and general reduce upgrade
* reducible category axis * new axis::trait is_ordered and ordered() * reduce accepts positional commands now * renamed (old symbols still there, but deprecated) - static_is_inclusive -> is_inclusive - static_options -> get_options - reduce_option -> reduce_command * reduce commands are now structs derived from reduce_command base instead of functions * simplified TMP code in axis::traits * updated and improved docs for reduce
This commit is contained in:
parent
9aef54cf70
commit
82d9314302
@ -7,14 +7,18 @@
|
||||
|
||||
[section:DiscreteAxis DiscreteAxis]
|
||||
|
||||
A [*DiscreteAxis] is one of two optional refinements of the [link histogram.concepts.Axis [*Axis]] concept, the other one is the [link histogram.concepts.IntervalAxis IntervalAxis]. This concept is for values that do not form intervals, and for axes with intervals that contain exactly one value.
|
||||
A [*DiscreteAxis] is one of two optional refinements of the [link histogram.concepts.Axis [*Axis]] concept, the other one is the [link histogram.concepts.IntervalAxis IntervalAxis]. This concept is for axes which in which each bin represents a single value instead of an interval.
|
||||
|
||||
Discrete axes can be further divided into ordered and unordered. An axis is ordered, when bin indices i < j < k imply that value[i] < value[j] < value[k] or value[i] > value[j] > value[k] for all i, j, k. The [classref boost::histogram::axis::integer integer axis] is ordered and the [classref boost::histogram::axis::category category axis] is unordered.
|
||||
|
||||
An unordered discrete axis cannot have an underflow bin. Since there is no order, one can only have one extra bin that counts values not handled by the axis, and by convention the overflow bin is used for that.
|
||||
|
||||
[heading Associated Types]
|
||||
|
||||
* [link histogram.concepts.Axis [*Axis]]
|
||||
* [link histogram.concepts.IntervalAxis [*IntervalAxis]]
|
||||
|
||||
[heading Requirements]
|
||||
[heading Optional features]
|
||||
|
||||
* `A` is a type meeting the requirements of [*DiscreteAxis]
|
||||
* `a` is a value of type `A`
|
||||
@ -26,6 +30,13 @@ A [*DiscreteAxis] is one of two optional refinements of the [link histogram.conc
|
||||
|
||||
[table Valid expressions
|
||||
[[Expression] [Returns] [Semantics, Pre/Post-conditions]]
|
||||
[
|
||||
[`A::ordered()`]
|
||||
[`bool`]
|
||||
[
|
||||
static constexpr member function which returns true if the axis is ordered and false if it is unordered. If this is absent, the library checks the value type is arithmetic instead, see [funcref boost::histogram::axis::traits::ordered].
|
||||
]
|
||||
]
|
||||
[
|
||||
[`a.value(i)`]
|
||||
[`V`]
|
||||
@ -75,6 +86,6 @@ A [*DiscreteAxis] is one of two optional refinements of the [link histogram.conc
|
||||
[heading Models]
|
||||
|
||||
* [classref boost::histogram::axis::category]
|
||||
* [classref boost::histogram::axis::integer], if first template parameter is [headerref boost/histogram/fwd.hpp `boost::histogram::index_type`]
|
||||
* [classref boost::histogram::axis::integer], if first template parameter is `int`
|
||||
|
||||
[endsect]
|
||||
|
@ -14,7 +14,7 @@ A [*IntervalAxis] is one of two optional refinements of the [link histogram.conc
|
||||
* [link histogram.concepts.Axis [*Axis]]
|
||||
* [link histogram.concepts.DiscreteAxis [*DiscreteAxis]]
|
||||
|
||||
[heading Requirements]
|
||||
[heading Optional features]
|
||||
|
||||
* `A` is a type meeting the requirements of [*IntervalAxis]
|
||||
* `a` is a value of type `A`
|
||||
|
@ -272,7 +272,7 @@ It is easy to iterate over all histogram cells to compute the sum of cell values
|
||||
|
||||
[section Projection]
|
||||
|
||||
It is sometimes convenient to generate a high-dimensional histogram first and then extract smaller or lower-dimensional versions from it. Lower-dimensional histograms are obtained by summing the bin contents of the removed axes. This is called a /projection/. When the histogram has under- and overflow bins along all axes, this operation creates a histogram which is identical to one that would have been obtained by filling the original data.
|
||||
It is sometimes convenient to generate a high-dimensional histogram first and then extract smaller or lower-dimensional versions from it. Lower-dimensional histograms are obtained by summing the bin contents of the removed axes. This is called a /projection/. If the histogram has under- and overflow bins along all axes, this operation creates a histogram which is identical to one that would have been obtained by filling the original data.
|
||||
|
||||
Projection is useful if you found out that there is no interesting structure along an axis, so it is not worth keeping that axis around, or if you want to visualize 1d or 2d summaries of a high-dimensional histogram.
|
||||
|
||||
@ -287,7 +287,9 @@ The library provides the [funcref boost::histogram::algorithm::project] function
|
||||
|
||||
A projection removes an axis completely. A less drastic way to obtain a smaller histogram is the [funcref boost::histogram::algorithm::reduce] function, which allows one to /slice/, /shrink/ or /rebin/ individual axes.
|
||||
|
||||
Shrinking means that the range of an axis is reduced and the number of bins along that axis. Slicing does the same, but is based on axis indices while shrinking is based on the axis values. Rebinning means that adjacent bins are merged into larger bins. For N adjacent bins, a new bin is formed which covers the common interval of the merged bins and has their added content. These two operations can be combined and applied to several axes at once (which is much more efficient than doing it sequentially).
|
||||
Shrinking means that the value range of an axis is reduced and the number of bins along that axis. Slicing does the same, but is based on axis indices while shrinking is based on the axis values. To rebin means that adjacent bins are merged into larger bins, the histogram is made coarser. For N adjacent bins, a new bin is formed which covers the common interval of the merged bins and has their added content. These two operations can be combined and applied to several axes at once. Doing it in one step is much more efficient than doing it in several steps.
|
||||
|
||||
[funcref boost::histogram::algorithm::reduce] does not change the total count if all modified axes in the histogram have underflow/overflow bins. Counts in removed bins are added to the corresponding under- and overflow bins. As in case of [funcref boost::histogram::algorithm::project], such a reduced histogram is guaranteed to be identical to one obtained from filling the original data.
|
||||
|
||||
[import ../examples/guide_histogram_reduction.cpp]
|
||||
[guide_histogram_reduction]
|
||||
|
@ -11,29 +11,38 @@
|
||||
|
||||
int main() {
|
||||
using namespace boost::histogram;
|
||||
// import reduce commands into local namespace to save typing
|
||||
using algorithm::rebin;
|
||||
using algorithm::shrink;
|
||||
using algorithm::slice;
|
||||
|
||||
// make a 2d histogram
|
||||
auto h = make_histogram(axis::regular<>(4, 0.0, 4.0), axis::regular<>(4, -1.0, 1.0));
|
||||
auto h = make_histogram(axis::regular<>(4, 0.0, 4.0), axis::regular<>(4, -2.0, 2.0));
|
||||
|
||||
h(0, -0.9);
|
||||
h(1, 0.9);
|
||||
h(2, 0.1);
|
||||
h(3, 0.1);
|
||||
|
||||
// shrink the first axis to remove the highest bin
|
||||
// rebin the second axis by merging pairs of adjacent bins
|
||||
auto h2 = algorithm::reduce(h, algorithm::shrink(0, 0.0, 3.0), algorithm::rebin(1, 2));
|
||||
// reduce takes positional commands which are applied to the axes in order
|
||||
// - shrink is applied to the first axis; the new axis range is 0.0 to 3.0
|
||||
// - rebin is applied to the second axis; pairs of adjacent bins are merged
|
||||
auto h2 = algorithm::reduce(h, shrink(0.0, 3.0), rebin(2));
|
||||
|
||||
assert(h2.axis(0).size() == 3);
|
||||
assert(h2.axis(0).bin(0).lower() == 0.0);
|
||||
assert(h2.axis(0).bin(2).upper() == 3.0);
|
||||
assert(h2.axis(1).size() == 2);
|
||||
assert(h2.axis(1).bin(0).lower() == -1.0);
|
||||
assert(h2.axis(1).bin(1).upper() == 1.0);
|
||||
assert(h2.axis(0) == axis::regular<>(3, 0.0, 3.0));
|
||||
assert(h2.axis(1) == axis::regular<>(2, -2.0, 2.0));
|
||||
|
||||
// reduce does not change the total count if the histogram has underflow/overflow bins;
|
||||
// counts in removed bins are added to the corresponding under- and overflow bins
|
||||
// reduce does not change the total count if the histogram has underflow/overflow bins
|
||||
assert(algorithm::sum(h) == 4 && algorithm::sum(h2) == 4);
|
||||
|
||||
// One can also explicitly specify the index of the axis in the histogram on which the
|
||||
// command should act, by using this index as the the first parameter. The position of
|
||||
// the command in the argument list of reduce is then ignored. We use this to slice only
|
||||
// the second axis (axis has index 1 in the histogram) from bin index 2 to 4.
|
||||
auto h3 = algorithm::reduce(h, slice(1, 2, 4));
|
||||
|
||||
assert(h3.axis(0) == h.axis(0)); // unchanged
|
||||
assert(h3.axis(1) == axis::regular<>(2, 0.0, 2.0));
|
||||
}
|
||||
|
||||
//]
|
||||
|
@ -24,193 +24,244 @@
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace detail {
|
||||
struct reduce_option {
|
||||
unsigned iaxis = 0;
|
||||
bool indices_set = false;
|
||||
axis::index_type begin = 0, end = 0, merge = 0;
|
||||
bool values_set = false;
|
||||
double lower = 0.0, upper = 0.0;
|
||||
struct reduce_command {
|
||||
static constexpr unsigned unset = static_cast<unsigned>(-1);
|
||||
unsigned iaxis;
|
||||
union {
|
||||
axis::index_type index;
|
||||
double value;
|
||||
} begin;
|
||||
union {
|
||||
axis::index_type index;
|
||||
double value;
|
||||
} end;
|
||||
unsigned merge = 0; // default value indicates unset option
|
||||
enum class state_t : char {
|
||||
rebin,
|
||||
slice,
|
||||
shrink,
|
||||
} state;
|
||||
// for internal use by the reduce algorithm
|
||||
bool is_ordered;
|
||||
bool use_underflow_bin;
|
||||
bool use_overflow_bin;
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
namespace algorithm {
|
||||
|
||||
using reduce_option = detail::reduce_option;
|
||||
/** Base type for all @ref reduce commands.
|
||||
|
||||
/**
|
||||
Shrink and rebin option to be used in reduce().
|
||||
|
||||
To shrink and rebin in one command. Equivalent to passing both the shrink() and the
|
||||
rebin() option for the same axis to reduce.
|
||||
|
||||
@param iaxis which axis to operate on.
|
||||
@param lower lowest bound that should be kept.
|
||||
@param upper highest bound that should be kept. If upper is inside bin interval, the
|
||||
whole interval is removed.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
Use this type to store commands in a container. The internals of this type are an
|
||||
implementation detail. Casting a derived command to this base is safe and never causes
|
||||
slicing.
|
||||
*/
|
||||
inline reduce_option shrink_and_rebin(unsigned iaxis, double lower, double upper,
|
||||
unsigned merge) {
|
||||
if (lower == upper)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("lower != upper required"));
|
||||
if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
|
||||
return {iaxis, false, 0, 0, static_cast<axis::index_type>(merge), true, lower, upper};
|
||||
}
|
||||
using reduce_command = detail::reduce_command;
|
||||
|
||||
/**
|
||||
Slice and rebin option to be used in reduce().
|
||||
using reduce_option [[deprecated("use reduce_command instead")]] =
|
||||
reduce_command; ///< deprecated
|
||||
|
||||
To slice and rebin in one command. Equivalent to passing both the slice() and the
|
||||
rebin() option for the same axis to reduce.
|
||||
/** Shrink and rebin command to be used in reduce().
|
||||
|
||||
@param iaxis which axis to operate on.
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
To @ref shrink and @ref rebin in one command (see the respective commands for more
|
||||
details). Equivalent to passing both commands for the same axis to @ref reduce.
|
||||
*/
|
||||
inline reduce_option slice_and_rebin(unsigned iaxis, axis::index_type begin,
|
||||
axis::index_type end, unsigned merge) {
|
||||
if (!(begin < end))
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("begin < end required"));
|
||||
if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
|
||||
return {iaxis, true, begin, end, static_cast<axis::index_type>(merge), false, 0.0, 0.0};
|
||||
}
|
||||
struct shrink_and_rebin : reduce_command {
|
||||
|
||||
/**
|
||||
Shrink option to be used in reduce().
|
||||
/** Command is applied to axis with given index.
|
||||
|
||||
@param iaxis which axis to operate on.
|
||||
@param lower lowest bound that should be kept.
|
||||
@param upper highest bound that should be kept. If upper is inside bin interval, the
|
||||
whole interval is removed.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
shrink_and_rebin(unsigned iaxis, double lower, double upper, unsigned merge) {
|
||||
if (lower == upper)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("lower != upper required"));
|
||||
if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
|
||||
reduce_command::iaxis = iaxis;
|
||||
reduce_command::begin.value = lower;
|
||||
reduce_command::end.value = upper;
|
||||
reduce_command::merge = merge;
|
||||
reduce_command::state = reduce_command::state_t::shrink;
|
||||
}
|
||||
|
||||
/** Command is applied to corresponding axis in order of reduce arguments.
|
||||
|
||||
@param lower lowest bound that should be kept.
|
||||
@param upper highest bound that should be kept. If upper is inside bin interval, the
|
||||
whole interval is removed.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
shrink_and_rebin(double lower, double upper, unsigned merge)
|
||||
: shrink_and_rebin(reduce_command::unset, lower, upper, merge) {}
|
||||
};
|
||||
|
||||
/** Slice and rebin command to be used in reduce().
|
||||
|
||||
To @ref slice and @ref rebin in one command (see the respective commands for more
|
||||
details). Equivalent to passing both commands for the same axis to @ref reduce.
|
||||
*/
|
||||
struct slice_and_rebin : reduce_command {
|
||||
|
||||
/** Command is applied to axis with given index.
|
||||
|
||||
@param iaxis which axis to operate on.
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
slice_and_rebin(unsigned iaxis, axis::index_type begin, axis::index_type end,
|
||||
unsigned merge) {
|
||||
if (!(begin < end))
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("begin < end required"));
|
||||
if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
|
||||
|
||||
reduce_command::iaxis = iaxis;
|
||||
reduce_command::begin.index = begin;
|
||||
reduce_command::end.index = end;
|
||||
reduce_command::merge = merge;
|
||||
reduce_command::state = reduce_command::state_t::slice;
|
||||
}
|
||||
|
||||
/** Command is applied to corresponding axis in order of reduce arguments.
|
||||
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
slice_and_rebin(axis::index_type begin, axis::index_type end, unsigned merge)
|
||||
: slice_and_rebin(reduce_command::unset, begin, end, merge) {}
|
||||
};
|
||||
|
||||
/** Shrink command to be used in reduce().
|
||||
|
||||
The shrink is inclusive. The bin which contains the first value starts the range of bins
|
||||
to keep. The bin which contains the second value is the last included in that range.
|
||||
When the second value is exactly equal to a lower bin edge, then the previous bin is
|
||||
the last in the range.
|
||||
|
||||
@param iaxis which axis to operate on.
|
||||
@param lower bin which contains lower is first to be kept.
|
||||
@param upper bin which contains upper is last to be kept, except if upper is equal to
|
||||
the lower edge.
|
||||
*/
|
||||
inline reduce_option shrink(unsigned iaxis, double lower, double upper) {
|
||||
return shrink_and_rebin(iaxis, lower, upper, 1u);
|
||||
}
|
||||
struct shrink : shrink_and_rebin {
|
||||
|
||||
/**
|
||||
Slice option to be used in reduce().
|
||||
/** Command is applied to axis with given index.
|
||||
|
||||
@param iaxis which axis to operate on.
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
@param iaxis which axis to operate on.
|
||||
@param lower bin which contains lower is first to be kept.
|
||||
@param upper bin which contains upper is last to be kept, except if upper is equal to
|
||||
the lower edge.
|
||||
*/
|
||||
shrink(unsigned iaxis, double lower, double upper)
|
||||
: shrink_and_rebin{iaxis, lower, upper, 1u} {}
|
||||
|
||||
/** Command is applied to corresponding axis in order of reduce arguments.
|
||||
|
||||
@param lower bin which contains lower is first to be kept.
|
||||
@param upper bin which contains upper is last to be kept, except if upper is equal to
|
||||
the lower edge.
|
||||
*/
|
||||
shrink(double lower, double upper) : shrink{reduce_command::unset, lower, upper} {}
|
||||
};
|
||||
|
||||
/** Slice command to be used in reduce().
|
||||
|
||||
Slicing works like shrinking, but uses bin indices instead of values.
|
||||
*/
|
||||
inline reduce_option slice(unsigned iaxis, axis::index_type begin, axis::index_type end) {
|
||||
return slice_and_rebin(iaxis, begin, end, 1u);
|
||||
}
|
||||
struct slice : slice_and_rebin {
|
||||
/** Command is applied to axis with given index.
|
||||
|
||||
/**
|
||||
Rebin option to be used in reduce().
|
||||
@param iaxis which axis to operate on.
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
*/
|
||||
slice(unsigned iaxis, axis::index_type begin, axis::index_type end)
|
||||
: slice_and_rebin{iaxis, begin, end, 1u} {}
|
||||
|
||||
@param iaxis which axis to operate on.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
/** Command is applied to corresponding axis in order of reduce arguments.
|
||||
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
*/
|
||||
slice(axis::index_type begin, axis::index_type end)
|
||||
: slice{reduce_command::unset, begin, end} {}
|
||||
};
|
||||
|
||||
/** Rebin command to be used in reduce().
|
||||
|
||||
The command merges N adjacent bins into one. This makes the axis coarser and the bins
|
||||
wider. The original number of bins is divided by N. If there is a rest to this devision,
|
||||
the axis is implicitly shrunk at the upper end by that rest.
|
||||
*/
|
||||
inline reduce_option rebin(unsigned iaxis, unsigned merge) {
|
||||
if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
|
||||
return reduce_option{iaxis, false, 0, 0, static_cast<axis::index_type>(merge),
|
||||
false, 0.0, 0.0};
|
||||
}
|
||||
struct rebin : reduce_command {
|
||||
/** Command is applied to axis with given index.
|
||||
|
||||
/**
|
||||
Shrink and rebin option to be used in reduce() (convenience overload for
|
||||
single axis).
|
||||
@param iaxis which axis to operate on.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
rebin(unsigned iaxis, unsigned merge) {
|
||||
if (merge == 0) BOOST_THROW_EXCEPTION(std::invalid_argument("merge > 0 required"));
|
||||
reduce_command::iaxis = iaxis;
|
||||
reduce_command::merge = merge;
|
||||
reduce_command::state = reduce_command::state_t::rebin;
|
||||
}
|
||||
|
||||
@param lower lowest bound that should be kept.
|
||||
@param upper highest bound that should be kept. If upper is inside bin interval, the
|
||||
whole interval is removed.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
inline reduce_option shrink_and_rebin(double lower, double upper, unsigned merge) {
|
||||
return shrink_and_rebin(0, lower, upper, merge);
|
||||
}
|
||||
/** Command is applied to corresponding axis in order of reduce arguments.
|
||||
|
||||
/**
|
||||
Slice and rebin option to be used in reduce() (convenience for 1D histograms).
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
rebin(unsigned merge) : rebin{reduce_command::unset, merge} {}
|
||||
};
|
||||
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
inline reduce_option slice_and_rebin(axis::index_type begin, axis::index_type end,
|
||||
unsigned merge) {
|
||||
return slice_and_rebin(0, begin, end, merge);
|
||||
}
|
||||
/** Shrink, slice, and/or rebin axes of a histogram.
|
||||
|
||||
/**
|
||||
Shrink option to be used in reduce() (convenience for 1D histograms).
|
||||
Returns a new reduced histogram and leaves the original histogram untouched.
|
||||
|
||||
@param lower lowest bound that should be kept.
|
||||
@param upper highest bound that should be kept. If upper is inside bin interval, the
|
||||
whole interval is removed.
|
||||
*/
|
||||
inline reduce_option shrink(double lower, double upper) {
|
||||
return shrink(0, lower, upper);
|
||||
}
|
||||
|
||||
/**
|
||||
Slice option to be used in reduce() (convenience for 1D histograms).
|
||||
|
||||
@param begin first index that should be kept.
|
||||
@param end one past the last index that should be kept.
|
||||
*/
|
||||
inline reduce_option slice(axis::index_type begin, axis::index_type end) {
|
||||
return slice(0, begin, end);
|
||||
}
|
||||
|
||||
/**
|
||||
Rebin option to be used in reduce() (convenience for 1D histograms).
|
||||
|
||||
@param merge how many adjacent bins to merge into one.
|
||||
*/
|
||||
inline reduce_option rebin(unsigned merge) { return rebin(0, merge); }
|
||||
|
||||
/**
|
||||
Shrink, slice, and/or rebin axes of a histogram.
|
||||
|
||||
Returns the reduced copy of the histogram.
|
||||
|
||||
Shrinking only works with axes that accept double values. Some axis types do not support
|
||||
the reduce operation, for example, the builtin category axis, which is not ordered.
|
||||
Custom axis types must implement a special constructor (see concepts) to be reducible.
|
||||
The commands @ref rebin and @ref shrink or @ref slice for the same axis are
|
||||
automatically combined, this is not an error. Passing a @ref shrink and a @ref slice
|
||||
command for the same axis or two @ref rebin commands triggers an invalid_argument
|
||||
exception. It is safe to reduce histograms with some axis that are not reducible along
|
||||
the other axes. Trying to reducing a non-reducible axis triggers an invalid_argument
|
||||
exception.
|
||||
|
||||
@param hist original histogram.
|
||||
@param options iterable sequence of reduce options, generated by shrink_and_rebin(),
|
||||
slice_and_rebin(), shrink(), slice(), and rebin().
|
||||
@param options iterable sequence of reduce commands: shrink_and_rebin, slice_and_rebin,
|
||||
@ref shrink, @ref slice, or @ref rebin. The element type of the iterable should be
|
||||
<a href="./boost/histogram/algorithm/reduce_command.html">reduce_command</a>.
|
||||
*/
|
||||
template <class Histogram, class Iterable, class = detail::requires_iterable<Iterable>>
|
||||
decltype(auto) reduce(const Histogram& hist, const Iterable& options) {
|
||||
Histogram reduce(const Histogram& hist, const Iterable& options) {
|
||||
using axis::index_type;
|
||||
|
||||
const auto& old_axes = unsafe_access::axes(hist);
|
||||
|
||||
auto opts = detail::make_stack_buffer<reduce_option>(old_axes);
|
||||
for (const reduce_option& o_in : options) {
|
||||
auto opts = detail::make_stack_buffer<reduce_command>(old_axes);
|
||||
unsigned iaxis = 0;
|
||||
for (const reduce_command& o_in : options) {
|
||||
BOOST_ASSERT(o_in.merge > 0);
|
||||
if (o_in.iaxis >= hist.rank())
|
||||
if (o_in.iaxis != reduce_command::unset && o_in.iaxis >= hist.rank())
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("invalid axis index"));
|
||||
reduce_option& o_out = opts[o_in.iaxis];
|
||||
if (o_out.merge > 0) {
|
||||
// some option was already set for this axis, see if we can merge requests
|
||||
if (o_in.merge > 1 && o_out.merge > 1)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("conflicting merge requests"));
|
||||
if ((o_in.indices_set || o_in.values_set) &&
|
||||
(o_out.indices_set || o_out.values_set))
|
||||
BOOST_THROW_EXCEPTION(
|
||||
std::invalid_argument("conflicting slice or shrink requests"));
|
||||
auto& o_out = opts[o_in.iaxis == reduce_command::unset ? iaxis : o_in.iaxis];
|
||||
if (o_out.merge == 0) {
|
||||
o_out = o_in;
|
||||
} else {
|
||||
// Some option was already set for this axis, see if we can combine requests.
|
||||
// We can combine a rebin and non-rebin request.
|
||||
if (!((o_in.state == reduce_command::state_t::rebin) ^
|
||||
(o_out.state == reduce_command::state_t::rebin)) ||
|
||||
(o_out.merge > 1 && o_in.merge > 1))
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument(
|
||||
"multiple non-fuseable reduce requests for axis " +
|
||||
std::to_string(o_in.iaxis == reduce_command::unset ? iaxis : o_in.iaxis)));
|
||||
if (o_in.state != reduce_command::state_t::rebin) {
|
||||
o_out.state = o_in.state;
|
||||
o_out.begin = o_in.begin;
|
||||
o_out.end = o_in.end;
|
||||
} else {
|
||||
o_out.merge = o_in.merge;
|
||||
}
|
||||
}
|
||||
if (o_in.values_set) {
|
||||
o_out.values_set = true;
|
||||
o_out.lower = o_in.lower;
|
||||
o_out.upper = o_in.upper;
|
||||
} else if (o_in.indices_set) {
|
||||
o_out.indices_set = true;
|
||||
o_out.begin = o_in.begin;
|
||||
o_out.end = o_in.end;
|
||||
}
|
||||
o_out.merge = (std::max)(o_in.merge, o_out.merge);
|
||||
o_out.iaxis = reduce_command::unset; // value not used below
|
||||
++iaxis;
|
||||
}
|
||||
|
||||
// make new axes container with default-constructed axis instances
|
||||
@ -226,104 +277,114 @@ decltype(auto) reduce(const Histogram& hist, const Iterable& options) {
|
||||
axes, old_axes);
|
||||
|
||||
// override default-constructed axis instances with modified instances
|
||||
unsigned iaxis = 0;
|
||||
hist.for_each_axis([&](const auto& a) {
|
||||
using A = std::decay_t<decltype(a)>;
|
||||
iaxis = 0;
|
||||
hist.for_each_axis([&](const auto& a_in) {
|
||||
using A = std::decay_t<decltype(a_in)>;
|
||||
using AO = axis::traits::get_options<A>;
|
||||
auto& o = opts[iaxis];
|
||||
o.is_ordered = axis::traits::ordered(a_in);
|
||||
if (o.merge > 0) { // option is set?
|
||||
detail::static_if_c<axis::traits::is_reducible<A>::value>(
|
||||
[&o](auto&& aout, const auto& ain) {
|
||||
using A = std::decay_t<decltype(ain)>;
|
||||
if (!o.indices_set && !o.values_set) {
|
||||
o.begin = 0;
|
||||
o.end = ain.size();
|
||||
[&o](auto&& a_out, const auto& a_in) {
|
||||
using A = std::decay_t<decltype(a_in)>;
|
||||
if (o.state == reduce_command::state_t::rebin) {
|
||||
o.begin.index = 0;
|
||||
o.end.index = a_in.size();
|
||||
} else {
|
||||
if (o.values_set) {
|
||||
o.begin = axis::traits::index(ain, o.lower);
|
||||
o.end = axis::traits::index(ain, o.upper);
|
||||
if (axis::traits::value_as<double>(ain, o.end) != o.upper) ++o.end;
|
||||
if (o.state == reduce_command::state_t::shrink) {
|
||||
const auto end_value = o.end.value;
|
||||
o.begin.index = axis::traits::index(a_in, o.begin.value);
|
||||
o.end.index = axis::traits::index(a_in, o.end.value);
|
||||
// end = index + 1, unless end_value is exactly equal to (upper) bin edge
|
||||
if (axis::traits::value_as<double>(a_in, o.end.index) != end_value)
|
||||
++o.end.index;
|
||||
}
|
||||
if (o.begin < 0) o.begin = 0;
|
||||
o.end = (std::min)(o.end, ain.size());
|
||||
// limit [begin, end] to [0, size()]
|
||||
if (o.begin.index < 0) o.begin.index = 0;
|
||||
if (o.end.index > a_in.size()) o.end.index = a_in.size();
|
||||
}
|
||||
o.end -= (o.end - o.begin) % o.merge;
|
||||
aout = A(ain, o.begin, o.end, o.merge);
|
||||
// shorten the index range to a multiple of o.merge;
|
||||
// example [1, 4] with merge = 2 is reduced to [1, 3]
|
||||
o.end.index -=
|
||||
(o.end.index - o.begin.index) % static_cast<index_type>(o.merge);
|
||||
a_out = A(a_in, o.begin.index, o.end.index, o.merge);
|
||||
},
|
||||
[iaxis](auto&&, const auto&) {
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("axis " + std::to_string(iaxis) +
|
||||
" is not reducible"));
|
||||
},
|
||||
axis::get<A>(detail::axis_get(axes, iaxis)), a);
|
||||
axis::get<A>(detail::axis_get(axes, iaxis)), a_in);
|
||||
// will be configurable with crop()
|
||||
o.use_underflow_bin = AO::test(axis::option::underflow);
|
||||
o.use_overflow_bin = AO::test(axis::option::overflow);
|
||||
} else {
|
||||
// option was not set for this axis; fill noop values and copy original axis
|
||||
o.merge = 1;
|
||||
o.begin = 0;
|
||||
o.end = a.size();
|
||||
axis::get<A>(detail::axis_get(axes, iaxis)) = a;
|
||||
o.begin.index = 0;
|
||||
o.end.index = a_in.size();
|
||||
axis::get<A>(detail::axis_get(axes, iaxis)) = a_in;
|
||||
o.use_underflow_bin = AO::test(axis::option::underflow);
|
||||
o.use_overflow_bin = AO::test(axis::option::overflow);
|
||||
}
|
||||
++iaxis;
|
||||
});
|
||||
|
||||
auto idx = detail::make_stack_buffer<int>(axes);
|
||||
auto irange = detail::make_stack_buffer<std::pair<int, int>>(axes);
|
||||
detail::for_each_axis(axes, [it = irange.begin()](const auto& a) mutable {
|
||||
using A = std::decay_t<decltype(a)>;
|
||||
it->first = axis::traits::static_options<A>::test(axis::option::underflow) ? -1 : 0;
|
||||
it->second = axis::traits::static_options<A>::test(axis::option::overflow)
|
||||
? a.size() + 1
|
||||
: a.size();
|
||||
++it;
|
||||
});
|
||||
auto idx = detail::make_stack_buffer<index_type>(axes);
|
||||
auto result =
|
||||
Histogram(std::move(axes), detail::make_default(unsafe_access::storage(hist)));
|
||||
for (auto&& x : indexed(hist, coverage::all)) {
|
||||
auto i = idx.begin();
|
||||
auto o = opts.begin();
|
||||
auto ir = irange.begin();
|
||||
bool valid = true;
|
||||
bool skip = false;
|
||||
|
||||
for (auto j : x.indices()) {
|
||||
*i = (j - o->begin);
|
||||
if (*i <= -1) {
|
||||
*i = (j - o->begin.index);
|
||||
if (o->is_ordered && *i <= -1) {
|
||||
*i = -1;
|
||||
if (*i < ir->first) valid = false;
|
||||
if (!o->use_underflow_bin) skip = true;
|
||||
} else {
|
||||
*i /= o->merge;
|
||||
const auto end = (o->end - o->begin) / o->merge;
|
||||
if (*i >= end) {
|
||||
*i = end;
|
||||
if (*i >= ir->second) valid = false;
|
||||
if (*i >= 0)
|
||||
*i /= static_cast<index_type>(o->merge);
|
||||
else
|
||||
*i = o->end.index;
|
||||
const auto reduced_axis_end =
|
||||
(o->end.index - o->begin.index) / static_cast<index_type>(o->merge);
|
||||
if (*i >= reduced_axis_end) {
|
||||
*i = reduced_axis_end;
|
||||
if (!o->use_overflow_bin) skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
++i;
|
||||
++o;
|
||||
++ir;
|
||||
}
|
||||
|
||||
if (valid) result.at(idx) += *x;
|
||||
if (!skip) result.at(idx) += *x;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Shrink, slice, and/or rebin axes of a histogram.
|
||||
/** Shrink, slice, and/or rebin axes of a histogram.
|
||||
|
||||
Returns the reduced copy of the histogram.
|
||||
Returns a new reduced histogram and leaves the original histogram untouched.
|
||||
|
||||
Shrinking only works with axes that accept double values. Some axis types do not support
|
||||
the reduce operation, for example, the builtin category axis, which is not ordered.
|
||||
Custom axis types must implement a special constructor (see concepts) to be reducible.
|
||||
The commands @ref rebin and @ref shrink or @ref slice for the same axis are
|
||||
automatically combined, this is not an error. Passing a @ref shrink and a @ref slice
|
||||
command for the same axis or two @ref rebin commands triggers an invalid_argument
|
||||
exception. It is safe to reduce histograms with some axis that are not reducible along
|
||||
the other axes. Trying to reducing a non-reducible axis triggers an invalid_argument
|
||||
exception.
|
||||
|
||||
@param hist original histogram.
|
||||
@param opt reduce option generated by shrink_and_rebin(), shrink(), and rebin().
|
||||
@param opts more reduce options.
|
||||
@param opt first reduce command; one of @ref shrink, @ref slice, @ref rebin,
|
||||
shrink_and_rebin, or slice_or_rebin.
|
||||
@param opts more reduce commands.
|
||||
*/
|
||||
template <class Histogram, class... Ts>
|
||||
decltype(auto) reduce(const Histogram& hist, const reduce_option& opt,
|
||||
const Ts&... opts) {
|
||||
Histogram reduce(const Histogram& hist, const reduce_command& opt, const Ts&... opts) {
|
||||
// this must be in one line, because any of the ts could be a temporary
|
||||
return reduce(hist, std::initializer_list<reduce_option>{opt, opts...});
|
||||
return reduce(hist, std::initializer_list<reduce_command>{opt, opts...});
|
||||
}
|
||||
|
||||
} // namespace algorithm
|
||||
|
@ -43,6 +43,7 @@ namespace axis {
|
||||
template <class Value, class MetaData, class Options, class Allocator>
|
||||
class category : public iterator_mixin<category<Value, MetaData, Options, Allocator>>,
|
||||
public metadata_base<MetaData> {
|
||||
// these must be private, so that they are not automatically inherited
|
||||
using value_type = Value;
|
||||
using metadata_type = typename metadata_base<MetaData>::metadata_type;
|
||||
using options_type = detail::replace_default<Options, option::overflow_t>;
|
||||
@ -100,6 +101,17 @@ public:
|
||||
allocator_type alloc = {})
|
||||
: category(list.begin(), list.end(), std::move(meta), std::move(alloc)) {}
|
||||
|
||||
/// Constructor used by algorithm::reduce to shrink and rebin (not for users).
|
||||
category(const category& src, index_type begin, index_type end, unsigned merge)
|
||||
// LCOV_EXCL_START: gcc-8 is missing the delegated ctor for no reason
|
||||
: category(src.vec_.begin() + begin, src.vec_.begin() + end, src.metadata(),
|
||||
src.get_allocator())
|
||||
// LCOV_EXCL_STOP
|
||||
{
|
||||
if (merge > 1)
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("cannot merge bins for category axis"));
|
||||
}
|
||||
|
||||
/// Return index for value argument.
|
||||
index_type index(const value_type& x) const noexcept {
|
||||
const auto beg = vec_.begin();
|
||||
@ -125,8 +137,8 @@ public:
|
||||
return vec_[idx];
|
||||
}
|
||||
|
||||
/// Return value for index argument.
|
||||
decltype(auto) bin(index_type idx) const noexcept { return value(idx); }
|
||||
/// Return value for index argument; alias for value(...).
|
||||
decltype(auto) bin(index_type idx) const { return value(idx); }
|
||||
|
||||
/// Returns the number of bins, without over- or underflow.
|
||||
index_type size() const noexcept { return static_cast<index_type>(vec_.size()); }
|
||||
@ -139,6 +151,9 @@ public:
|
||||
return options() & (option::overflow | option::growth);
|
||||
}
|
||||
|
||||
/// Indicate that the axis is not ordered.
|
||||
static constexpr bool ordered() noexcept { return false; }
|
||||
|
||||
template <class V, class M, class O, class A>
|
||||
bool operator==(const category<V, M, O, A>& o) const noexcept {
|
||||
const auto& a = vec_;
|
||||
@ -152,7 +167,7 @@ public:
|
||||
return !operator==(o);
|
||||
}
|
||||
|
||||
auto get_allocator() const { return vec_.get_allocator(); }
|
||||
allocator_type get_allocator() const { return vec_.get_allocator(); }
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, unsigned /* version */) {
|
||||
|
@ -40,17 +40,16 @@ namespace axis {
|
||||
template <class Value, class MetaData, class Options>
|
||||
class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
|
||||
public metadata_base<MetaData> {
|
||||
static_assert(std::is_integral<Value>::value || std::is_floating_point<Value>::value,
|
||||
"integer axis requires floating point or integral type");
|
||||
|
||||
// these must be private, so that they are not automatically inherited
|
||||
using value_type = Value;
|
||||
using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
|
||||
index_type, real_index_type>;
|
||||
|
||||
using metadata_type = typename metadata_base<MetaData>::metadata_type;
|
||||
using options_type =
|
||||
detail::replace_default<Options, decltype(option::underflow | option::overflow)>;
|
||||
|
||||
static_assert(std::is_integral<value_type>::value ||
|
||||
std::is_floating_point<value_type>::value,
|
||||
"integer axis requires floating point or integral type");
|
||||
|
||||
static_assert(!options_type::test(option::circular | option::growth) ||
|
||||
(options_type::test(option::circular) ^
|
||||
options_type::test(option::growth)),
|
||||
@ -64,6 +63,9 @@ class integer : public iterator_mixin<integer<Value, MetaData, Options>>,
|
||||
"circular or growing integer axis with integral type "
|
||||
"cannot have entries in underflow or overflow bins");
|
||||
|
||||
using local_index_type = std::conditional_t<std::is_integral<value_type>::value,
|
||||
index_type, real_index_type>;
|
||||
|
||||
public:
|
||||
constexpr integer() = default;
|
||||
|
||||
|
@ -179,6 +179,7 @@ template <class Value, class Transform, class MetaData, class Options>
|
||||
class regular : public iterator_mixin<regular<Value, Transform, MetaData, Options>>,
|
||||
protected detail::replace_default<Transform, transform::id>,
|
||||
public metadata_base<MetaData> {
|
||||
// these must be private, so that they are not automatically inherited
|
||||
using value_type = Value;
|
||||
using transform_type = detail::replace_default<Transform, transform::id>;
|
||||
using metadata_type = typename metadata_base<MetaData>::metadata_type;
|
||||
|
@ -11,6 +11,7 @@
|
||||
#include <boost/histogram/axis/option.hpp>
|
||||
#include <boost/histogram/detail/args_type.hpp>
|
||||
#include <boost/histogram/detail/detect.hpp>
|
||||
#include <boost/histogram/detail/priority.hpp>
|
||||
#include <boost/histogram/detail/static_if.hpp>
|
||||
#include <boost/histogram/detail/try_cast.hpp>
|
||||
#include <boost/histogram/detail/type_name.hpp>
|
||||
@ -28,31 +29,44 @@ namespace boost {
|
||||
namespace histogram {
|
||||
namespace detail {
|
||||
|
||||
template <class T>
|
||||
using get_options_from_method = axis::option::bitset<T::options()>;
|
||||
|
||||
template <class Axis>
|
||||
struct static_options_impl {
|
||||
static_assert(std::is_same<std::decay_t<Axis>, Axis>::value,
|
||||
"support of static_options for qualified types was removed, please use "
|
||||
"static_options<std::decay_t<...>>");
|
||||
using type = mp11::mp_eval_or<
|
||||
mp11::mp_if<has_method_update<Axis>, axis::option::growth_t, axis::option::none_t>,
|
||||
get_options_from_method, Axis>;
|
||||
struct value_type_deducer {
|
||||
using type =
|
||||
std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using get_inclusive_from_method = std::integral_constant<bool, T::inclusive()>;
|
||||
template <class Axis>
|
||||
auto traits_options(priority<2>) -> axis::option::bitset<Axis::options()>;
|
||||
|
||||
template <class Axis>
|
||||
struct static_is_inclusive_impl {
|
||||
using type = mp11::mp_eval_or<decltype(static_options_impl<Axis>::type::test(
|
||||
axis::option::underflow | axis::option::overflow)),
|
||||
get_inclusive_from_method, Axis>;
|
||||
};
|
||||
auto traits_options(priority<1>) -> decltype(&Axis::update, axis::option::growth_t{});
|
||||
|
||||
template <class Axis>
|
||||
auto traits_options(priority<0>) -> axis::option::none_t;
|
||||
|
||||
template <class Axis>
|
||||
auto traits_is_inclusive(priority<1>) -> std::integral_constant<bool, Axis::inclusive()>;
|
||||
|
||||
template <class Axis>
|
||||
auto traits_is_inclusive(priority<0>)
|
||||
-> decltype(traits_options<Axis>(priority<2>{})
|
||||
.test(axis::option::underflow | axis::option::overflow));
|
||||
|
||||
template <class Axis>
|
||||
auto traits_is_ordered(priority<1>) -> std::integral_constant<bool, Axis::ordered()>;
|
||||
|
||||
template <class Axis, class ValueType = typename value_type_deducer<Axis>::type>
|
||||
auto traits_is_ordered(priority<0>) -> typename std::is_arithmetic<ValueType>::type;
|
||||
|
||||
template <class I, class D, class A,
|
||||
class J = std::decay_t<arg_type<decltype(&A::value)>>>
|
||||
decltype(auto) value_method_switch(I&& i, D&& d, const A& a, priority<1>) {
|
||||
return static_if<std::is_same<J, axis::index_type>>(std::forward<I>(i),
|
||||
std::forward<D>(d), a);
|
||||
}
|
||||
|
||||
template <class I, class D, class A>
|
||||
double value_method_switch_impl1(std::false_type, I&&, D&&, const A&) {
|
||||
double value_method_switch(I&&, D&&, const A&, priority<0>) {
|
||||
// comma trick to make all compilers happy; some would complain about
|
||||
// unreachable code after the throw, others about a missing return
|
||||
return BOOST_THROW_EXCEPTION(
|
||||
@ -60,19 +74,6 @@ double value_method_switch_impl1(std::false_type, I&&, D&&, const A&) {
|
||||
double{};
|
||||
}
|
||||
|
||||
template <class I, class D, class A>
|
||||
decltype(auto) value_method_switch_impl1(std::true_type, I&& i, D&& d, const A& a) {
|
||||
using T = arg_type<decltype(&A::value)>;
|
||||
return static_if<std::is_same<T, axis::index_type>>(std::forward<I>(i),
|
||||
std::forward<D>(d), a);
|
||||
}
|
||||
|
||||
template <class I, class D, class A>
|
||||
decltype(auto) value_method_switch(I&& i, D&& d, const A& a) {
|
||||
return value_method_switch_impl1(has_method_value<A>{}, std::forward<I>(i),
|
||||
std::forward<D>(d), a);
|
||||
}
|
||||
|
||||
static axis::null_type null_value;
|
||||
|
||||
struct variant_access {
|
||||
@ -113,15 +114,24 @@ struct variant_access {
|
||||
namespace axis {
|
||||
namespace traits {
|
||||
|
||||
/** Get value type for axis type.
|
||||
/** Value type for axis type.
|
||||
|
||||
Doxygen does not render this well. This is a meta-function (template alias), it accepts
|
||||
an axis type and returns the value type.
|
||||
|
||||
The value type is deduced from the argument of the `Axis::index` method. Const
|
||||
references are decayed to the their value types, for example, the type deduced for
|
||||
`Axis::index(const int&)` is `int`.
|
||||
|
||||
The deduction always succeeds if the axis type models the Axis concept correctly. Errors
|
||||
come from violations of the concept, in particular, an index method that is templated or
|
||||
overloaded is not allowed.
|
||||
|
||||
@tparam Axis axis type.
|
||||
*/
|
||||
template <class Axis>
|
||||
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
|
||||
using value_type =
|
||||
std::remove_cv_t<std::remove_reference_t<detail::arg_type<decltype(&Axis::index)>>>;
|
||||
using value_type = typename detail::value_type_deducer<Axis>::type;
|
||||
#else
|
||||
struct value_type;
|
||||
#endif
|
||||
@ -129,8 +139,10 @@ struct value_type;
|
||||
/** Whether axis is continuous or discrete.
|
||||
|
||||
Doxygen does not render this well. This is a meta-function (template alias), it accepts
|
||||
an axis type and returns a compile-time boolean. If the boolean is true, the axis is
|
||||
continuous. Otherwise it is discrete.
|
||||
an axis type and returns a compile-time boolean.
|
||||
|
||||
If the boolean is true, the axis is continuous (covers a continuous range of values).
|
||||
Otherwise it is discrete (covers discrete values).
|
||||
*/
|
||||
template <class Axis>
|
||||
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
|
||||
@ -145,6 +157,9 @@ struct is_continuous;
|
||||
an axis type and represents compile-time boolean which is true or false, depending on
|
||||
whether the axis can be reduced with boost::histogram::algorithm::reduce().
|
||||
|
||||
An axis can be made reducible by adding a special constructor, see Axis concept for
|
||||
details.
|
||||
|
||||
@tparam Axis axis type.
|
||||
*/
|
||||
template <class Axis>
|
||||
@ -155,12 +170,12 @@ using is_reducible = std::is_constructible<Axis, const Axis&, axis::index_type,
|
||||
struct is_reducible;
|
||||
#endif
|
||||
|
||||
/** Get static axis options for axis type.
|
||||
/** Get axis options for axis type.
|
||||
|
||||
Doxygen does not render this well. This is a meta-function (template alias), it accepts
|
||||
an axis type and returns the boost::histogram::axis::option::bitset.
|
||||
|
||||
If Axis::options() is valid and constexpr, static_options is the corresponding
|
||||
If Axis::options() is valid and constexpr, get_options is the corresponding
|
||||
option type. Otherwise, it is boost::histogram::axis::option::growth_t, if the
|
||||
axis has a method `update`, else boost::histogram::axis::option::none_t.
|
||||
|
||||
@ -168,9 +183,13 @@ struct is_reducible;
|
||||
*/
|
||||
template <class Axis>
|
||||
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
|
||||
using static_options = typename detail::static_options_impl<Axis>::type;
|
||||
using get_options = decltype(detail::traits_options<Axis>(detail::priority<2>{}));
|
||||
|
||||
template <class Axis>
|
||||
using static_options [[deprecated("use get_options instead")]] = get_options<Axis>;
|
||||
|
||||
#else
|
||||
struct static_options;
|
||||
struct get_options;
|
||||
#endif
|
||||
|
||||
/** Meta-function to detect whether an axis is inclusive.
|
||||
@ -179,36 +198,64 @@ struct static_options;
|
||||
an axis type and represents compile-time boolean which is true or false, depending on
|
||||
whether the axis is inclusive or not.
|
||||
|
||||
An axis with underflow and overflow bins is always inclusive, but an axis may be
|
||||
inclusive under other conditions. The meta-function checks for the method `constexpr
|
||||
static bool inclusive()`, and uses the result. If this method is not present, it uses
|
||||
get_options<Axis> and checks whether the underflow and overflow bits are present.
|
||||
|
||||
An inclusive axis has a bin for every possible input value. A histogram which consists
|
||||
only of inclusive axes can be filled more efficiently, since input values always
|
||||
end up in a valid cell and there is no need to keep track of input tuples that need to
|
||||
be discarded.
|
||||
|
||||
An axis with underflow and overflow bins is always inclusive, but an axis may be
|
||||
inclusive under other conditions. The meta-function checks for the method `constexpr
|
||||
static bool inclusive()`, and uses the result. If this method is not present, it uses
|
||||
static_options<Axis> and checks whether the underflow and overflow bits are present.
|
||||
|
||||
@tparam axis type
|
||||
@tparam Axis axis type
|
||||
*/
|
||||
template <class Axis>
|
||||
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
|
||||
using static_is_inclusive = typename detail::static_is_inclusive_impl<Axis>::type;
|
||||
using is_inclusive = decltype(detail::traits_is_inclusive<Axis>(detail::priority<1>{}));
|
||||
|
||||
template <class Axis>
|
||||
using static_is_inclusive [[deprecated("use is_inclusive instead")]] = is_inclusive<Axis>;
|
||||
|
||||
#else
|
||||
struct static_is_inclusive;
|
||||
struct is_inclusive;
|
||||
#endif
|
||||
|
||||
/** Meta-function to detect whether an axis is ordered.
|
||||
|
||||
Doxygen does not render this well. This is a meta-function (template alias), it accepts
|
||||
an axis type and returns a compile-time boolean. If the boolean is true, the axis is
|
||||
ordered.
|
||||
|
||||
The meta-function checks for the method `constexpr static bool ordered()`, and uses the
|
||||
result. If this method is not present, it returns true if the value type of the Axis is
|
||||
arithmetic and false otherwise.
|
||||
|
||||
An ordered axis has a value type that is ordered, which means that indices i <
|
||||
j < k implies either value(i) < value(j) < value(k) or value(i) > value(j) > value(k)
|
||||
for all i,j,k. For example, the integer axis is ordered, but the category axis is not.
|
||||
Axis which are not ordered must not have underflow bins, because they only have an
|
||||
"other" category, which is identified with the overflow bin if it is available.
|
||||
|
||||
@tparam Axis axis type
|
||||
*/
|
||||
template <class Axis>
|
||||
#ifndef BOOST_HISTOGRAM_DOXYGEN_INVOKED
|
||||
using is_ordered = decltype(detail::traits_is_ordered<Axis>(detail::priority<1>{}));
|
||||
#else
|
||||
struct is_ordered;
|
||||
#endif
|
||||
|
||||
/** Returns axis options as unsigned integer.
|
||||
|
||||
If axis.options() is a valid expression, return the result. Otherwise, return
|
||||
static_options<Axis>::value.
|
||||
See get_options for details.
|
||||
|
||||
@param axis any axis instance
|
||||
*/
|
||||
template <class Axis>
|
||||
constexpr unsigned options(const Axis& axis) noexcept {
|
||||
boost::ignore_unused(axis);
|
||||
return static_options<Axis>::value;
|
||||
return get_options<Axis>::value;
|
||||
}
|
||||
|
||||
// specialization for variant
|
||||
@ -219,14 +266,14 @@ unsigned options(const variant<Ts...>& axis) noexcept {
|
||||
|
||||
/** Returns true if axis is inclusive or false.
|
||||
|
||||
See static_is_inclusive for details.
|
||||
See is_inclusive for details.
|
||||
|
||||
@param axis any axis instance
|
||||
*/
|
||||
template <class Axis>
|
||||
constexpr bool inclusive(const Axis& axis) noexcept {
|
||||
boost::ignore_unused(axis);
|
||||
return static_is_inclusive<Axis>::value;
|
||||
return is_inclusive<Axis>::value;
|
||||
}
|
||||
|
||||
// specialization for variant
|
||||
@ -235,6 +282,24 @@ bool inclusive(const variant<Ts...>& axis) noexcept {
|
||||
return axis.inclusive();
|
||||
}
|
||||
|
||||
/** Returns true if axis is ordered or false.
|
||||
|
||||
See is_ordered for details.
|
||||
|
||||
@param axis any axis instance
|
||||
*/
|
||||
template <class Axis>
|
||||
constexpr bool ordered(const Axis& axis) noexcept {
|
||||
boost::ignore_unused(axis);
|
||||
return is_ordered<Axis>::value;
|
||||
}
|
||||
|
||||
// specialization for variant
|
||||
template <class... Ts>
|
||||
bool ordered(const variant<Ts...>& axis) noexcept {
|
||||
return axis.ordered();
|
||||
}
|
||||
|
||||
/** Returns axis size plus any extra bins for under- and overflow.
|
||||
|
||||
@param axis any axis instance
|
||||
@ -279,7 +344,7 @@ template <class Axis>
|
||||
decltype(auto) value(const Axis& axis, real_index_type index) {
|
||||
return detail::value_method_switch(
|
||||
[index](const auto& a) { return a.value(static_cast<index_type>(index)); },
|
||||
[index](const auto& a) { return a.value(index); }, axis);
|
||||
[index](const auto& a) { return a.value(index); }, axis, detail::priority<1>{});
|
||||
}
|
||||
|
||||
/** Returns axis value for index if it is convertible to target type or throws.
|
||||
@ -340,7 +405,7 @@ unsigned rank(const axis::variant<Ts...>& axis) {
|
||||
|
||||
Throws `std::invalid_argument` if the value argument is not implicitly convertible to
|
||||
the argument expected by the `index` method. If the result of
|
||||
boost::histogram::axis::traits::static_options<decltype(axis)> has the growth flag set,
|
||||
boost::histogram::axis::traits::get_options<decltype(axis)> has the growth flag set,
|
||||
call `update` method with the argument and return the result. Otherwise, call `index`
|
||||
and return the pair of the result and a zero shift.
|
||||
|
||||
@ -350,7 +415,7 @@ unsigned rank(const axis::variant<Ts...>& axis) {
|
||||
template <class Axis, class U>
|
||||
std::pair<index_type, index_type> update(Axis& axis, const U& value) noexcept(
|
||||
std::is_convertible<U, value_type<Axis>>::value) {
|
||||
return detail::static_if_c<static_options<Axis>::test(option::growth)>(
|
||||
return detail::static_if_c<get_options<Axis>::test(option::growth)>(
|
||||
[&value](auto& a) {
|
||||
return a.update(detail::try_cast<value_type<Axis>, std::invalid_argument>(value));
|
||||
},
|
||||
@ -379,7 +444,8 @@ template <class Axis>
|
||||
decltype(auto) width(const Axis& axis, index_type index) {
|
||||
return detail::value_method_switch(
|
||||
[](const auto&) { return 0; },
|
||||
[index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis);
|
||||
[index](const auto& a) { return a.value(index + 1) - a.value(index); }, axis,
|
||||
detail::priority<1>{});
|
||||
}
|
||||
|
||||
/** Returns bin width at axis index.
|
||||
@ -398,7 +464,7 @@ Result width_as(const Axis& axis, index_type index) {
|
||||
return detail::try_cast<Result, std::runtime_error>(a.value(index + 1) -
|
||||
a.value(index));
|
||||
},
|
||||
axis);
|
||||
axis, detail::priority<1>{});
|
||||
}
|
||||
|
||||
} // namespace traits
|
||||
|
@ -47,6 +47,7 @@ namespace axis {
|
||||
template <class Value, class MetaData, class Options, class Allocator>
|
||||
class variable : public iterator_mixin<variable<Value, MetaData, Options, Allocator>>,
|
||||
public metadata_base<MetaData> {
|
||||
// these must be private, so that they are not automatically inherited
|
||||
using value_type = Value;
|
||||
using metadata_type = typename metadata_base<MetaData>::metadata_type;
|
||||
using options_type =
|
||||
|
@ -97,6 +97,11 @@ public:
|
||||
return visit([](const auto& a) { return traits::inclusive(a); }, *this);
|
||||
}
|
||||
|
||||
/// Returns true if the axis is ordered or false.
|
||||
bool ordered() const {
|
||||
return visit([](const auto& a) { return traits::ordered(a); }, *this);
|
||||
}
|
||||
|
||||
/// Return reference to const metadata or instance of null_type if axis has no
|
||||
/// metadata.
|
||||
const metadata_type& metadata() const {
|
||||
@ -178,7 +183,7 @@ public:
|
||||
const double x2 = traits::value_as<double>(a, idx + 1);
|
||||
return polymorphic_bin<double>(x1, x2);
|
||||
},
|
||||
a);
|
||||
a, detail::priority<1>{});
|
||||
},
|
||||
*this);
|
||||
}
|
||||
|
@ -324,13 +324,13 @@ auto make_stack_buffer(const T& t, V&& v) {
|
||||
|
||||
template <class T>
|
||||
using has_underflow =
|
||||
decltype(axis::traits::static_options<T>::test(axis::option::underflow));
|
||||
decltype(axis::traits::get_options<T>::test(axis::option::underflow));
|
||||
|
||||
template <class T>
|
||||
using is_growing = decltype(axis::traits::static_options<T>::test(axis::option::growth));
|
||||
using is_growing = decltype(axis::traits::get_options<T>::test(axis::option::growth));
|
||||
|
||||
template <class T>
|
||||
using is_not_inclusive = mp11::mp_not<axis::traits::static_is_inclusive<T>>;
|
||||
using is_not_inclusive = mp11::mp_not<axis::traits::is_inclusive<T>>;
|
||||
|
||||
// for vector<T>
|
||||
template <class T>
|
||||
|
@ -94,7 +94,7 @@ struct storage_grower {
|
||||
auto sit = shifts;
|
||||
auto dit = data_;
|
||||
for_each_axis(axes_, [&](const auto& a) {
|
||||
using opt = axis::traits::static_options<std::decay_t<decltype(a)>>;
|
||||
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
|
||||
if (opt::test(axis::option::underflow)) {
|
||||
if (dit->idx == 0) {
|
||||
// axis has underflow and we are in the underflow bin:
|
||||
@ -159,7 +159,7 @@ auto fill_storage_element_impl(priority<1>, T&& t) noexcept -> decltype(++t, voi
|
||||
}
|
||||
|
||||
template <class T, class... Us>
|
||||
auto fill_storage_element(T&& t, const Us&... args) noexcept {
|
||||
void fill_storage_element(T&& t, const Us&... args) noexcept {
|
||||
fill_storage_element_impl(priority<2>{}, std::forward<T>(t), args...);
|
||||
}
|
||||
|
||||
@ -299,7 +299,7 @@ decltype(auto) pack_args(mp11::mp_int<-1>, mp11::mp_int<-1>, const Args& args) n
|
||||
|
||||
template <class ArgTraits, class S, class A, class Args>
|
||||
auto fill(std::true_type, ArgTraits, const std::size_t offset, S& storage, A& axes,
|
||||
const Args& args) {
|
||||
const Args& args) -> typename S::iterator {
|
||||
using growing = has_growing_axis<A>;
|
||||
|
||||
// Sometimes we need to pack the tuple into another tuple:
|
||||
@ -337,7 +337,8 @@ auto fill(std::true_type, ArgTraits, const std::size_t offset, S& storage, A& ax
|
||||
|
||||
// empty implementation for bad arguments to stop compiler from showing internals
|
||||
template <class ArgTraits, class S, class A, class Args>
|
||||
auto fill(std::false_type, ArgTraits, const std::size_t, S& storage, A&, const Args&) {
|
||||
auto fill(std::false_type, ArgTraits, const std::size_t, S& storage, A&, const Args&) ->
|
||||
typename S::iterator {
|
||||
return storage.end();
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ struct index_visitor {
|
||||
using index_type = Index;
|
||||
using pointer = index_type*;
|
||||
using value_type = axis::traits::value_type<Axis>;
|
||||
using Opt = axis::traits::static_options<Axis>;
|
||||
using Opt = axis::traits::get_options<Axis>;
|
||||
|
||||
Axis& axis_;
|
||||
const std::size_t stride_, start_, size_; // start and size of value collection
|
||||
|
@ -50,7 +50,7 @@ template <class Index, class Axis, class Value>
|
||||
std::size_t linearize(Index& out, const std::size_t stride, const Axis& ax,
|
||||
const Value& v) {
|
||||
// mask options to reduce no. of template instantiations
|
||||
constexpr auto opts = axis::traits::static_options<Axis>{} &
|
||||
constexpr auto opts = axis::traits::get_options<Axis>{} &
|
||||
(axis::option::underflow | axis::option::overflow);
|
||||
return linearize(opts, out, stride, ax.size(), axis::traits::index(ax, v));
|
||||
}
|
||||
@ -61,7 +61,7 @@ std::size_t linearize_growth(Index& out, axis::index_type& shift,
|
||||
const std::size_t stride, Axis& a, const Value& v) {
|
||||
axis::index_type idx;
|
||||
std::tie(idx, shift) = axis::traits::update(a, v);
|
||||
constexpr bool u = axis::traits::static_options<Axis>::test(axis::option::underflow);
|
||||
constexpr bool u = axis::traits::get_options<Axis>::test(axis::option::underflow);
|
||||
if (u) ++idx;
|
||||
if (std::is_same<Index, std::size_t>::value) {
|
||||
BOOST_ASSERT(idx < axis::traits::extent(a));
|
||||
@ -79,7 +79,7 @@ std::size_t linearize_growth(Index& out, axis::index_type& shift,
|
||||
template <class A>
|
||||
std::size_t linearize_index(optional_index& out, const std::size_t stride, const A& ax,
|
||||
const axis::index_type idx) {
|
||||
// cannot use static_options here, since A may be variant
|
||||
// cannot use get_options here, since A may be variant
|
||||
const auto opt = axis::traits::options(ax);
|
||||
const axis::index_type begin = opt & axis::option::underflow ? -1 : 0;
|
||||
const axis::index_type end = opt & axis::option::overflow ? ax.size() + 1 : ax.size();
|
||||
|
@ -51,7 +51,7 @@ public:
|
||||
using value_type = typename std::iterator_traits<value_iterator>::value_type;
|
||||
|
||||
class iterator;
|
||||
using range_iterator = iterator; ///< deprecated
|
||||
using range_iterator [[deprecated("use iterator instead")]] = iterator; ///< deprecated
|
||||
|
||||
/** Lightweight view to access value and index of current cell.
|
||||
|
||||
@ -71,7 +71,8 @@ public:
|
||||
|
||||
public:
|
||||
using const_reference = const axis::index_type&;
|
||||
using reference = const_reference; ///< deprecated
|
||||
using reference [[deprecated("use const_reference instead")]] =
|
||||
const_reference; ///< deprecated
|
||||
|
||||
/// implementation detail
|
||||
class const_iterator
|
||||
@ -307,7 +308,7 @@ public:
|
||||
begin_.indices_.hist_->for_each_axis([ca = begin_.indices_.begin(), cov,
|
||||
stride = std::size_t{1},
|
||||
this](const auto& a) mutable {
|
||||
using opt = axis::traits::static_options<std::decay_t<decltype(a)>>;
|
||||
using opt = axis::traits::get_options<std::decay_t<decltype(a)>>;
|
||||
constexpr axis::index_type under = opt::test(axis::option::underflow);
|
||||
constexpr axis::index_type over = opt::test(axis::option::overflow);
|
||||
const auto size = a.size();
|
||||
|
@ -23,9 +23,7 @@ using namespace boost::histogram::algorithm;
|
||||
|
||||
template <typename Tag>
|
||||
void run_tests() {
|
||||
// reduce:
|
||||
// - does not work with arguments not convertible to double
|
||||
// - does not work with category axis, which is not ordered
|
||||
// limitations: shrink does not work with arguments not convertible to double
|
||||
|
||||
using R = axis::regular<double, axis::transform::id, axis::null_type>;
|
||||
using ID = axis::integer<double, axis::null_type>;
|
||||
@ -41,7 +39,11 @@ void run_tests() {
|
||||
// not allowed: repeated indices
|
||||
BOOST_TEST_THROWS((void)reduce(h, slice(1, 0, 2), slice(1, 1, 3)),
|
||||
std::invalid_argument);
|
||||
// two rebin requests for same axis cannot be fused
|
||||
BOOST_TEST_THROWS((void)reduce(h, rebin(0, 2), rebin(0, 2)), std::invalid_argument);
|
||||
// rebin and slice_and_rebin with merge > 1 requests for same axis cannot be fused
|
||||
BOOST_TEST_THROWS((void)reduce(h, slice_and_rebin(0, 1, 3, 2), rebin(0, 2)),
|
||||
std::invalid_argument);
|
||||
BOOST_TEST_THROWS((void)reduce(h, shrink(1, 0, 2), shrink(1, 0, 2)),
|
||||
std::invalid_argument);
|
||||
// not allowed: slice with begin >= end
|
||||
@ -62,13 +64,8 @@ void run_tests() {
|
||||
auto h = make(Tag(), ID(0, 3));
|
||||
const auto& ax = h.axis();
|
||||
BOOST_TEST_EQ(ax.value(0), 0);
|
||||
BOOST_TEST_EQ(ax.value(1), 1);
|
||||
BOOST_TEST_EQ(ax.value(2), 2);
|
||||
BOOST_TEST_EQ(ax.value(3), 3);
|
||||
BOOST_TEST_EQ(ax.index(-1), -1);
|
||||
BOOST_TEST_EQ(ax.index(0), 0);
|
||||
BOOST_TEST_EQ(ax.index(1), 1);
|
||||
BOOST_TEST_EQ(ax.index(2), 2);
|
||||
BOOST_TEST_EQ(ax.index(3), 3);
|
||||
|
||||
BOOST_TEST_EQ(reduce(h, shrink(-1, 5)).axis(), ID(0, 3));
|
||||
@ -88,10 +85,10 @@ void run_tests() {
|
||||
|
||||
/*
|
||||
matrix layout:
|
||||
x ->
|
||||
x
|
||||
y 1 0 1 0
|
||||
| 1 1 0 0
|
||||
v 0 2 1 3
|
||||
1 1 0 0
|
||||
0 2 1 3
|
||||
*/
|
||||
h.at(0, 0) = 1;
|
||||
h.at(0, 1) = 1;
|
||||
@ -109,9 +106,11 @@ void run_tests() {
|
||||
BOOST_TEST_EQ(hr.axis(1), R(3, -1, 2));
|
||||
BOOST_TEST_EQ(hr, h);
|
||||
|
||||
// noop slice
|
||||
hr = reduce(h, slice(1, 0, 4), slice(0, 0, 4));
|
||||
BOOST_TEST_EQ(hr, h);
|
||||
|
||||
// shrinking along first axis
|
||||
hr = reduce(h, shrink(0, 2, 4));
|
||||
BOOST_TEST_EQ(hr.rank(), 2);
|
||||
BOOST_TEST_EQ(sum(hr), 10);
|
||||
@ -152,27 +151,28 @@ void run_tests() {
|
||||
BOOST_TEST_EQ(hr.at(1, 0), 3); // overflow
|
||||
|
||||
// test overload that accepts iterable and test option fusion
|
||||
std::vector<reduce_option> opts{{shrink(0, 2, 5), rebin(0, 2), rebin(1, 3)}};
|
||||
std::vector<reduce_command> opts{{shrink(0, 2, 5), rebin(0, 2), rebin(1, 3)}};
|
||||
auto hr2 = reduce(h, opts);
|
||||
BOOST_TEST_EQ(hr2, hr);
|
||||
opts = {rebin(0, 2), slice(0, 1, 4), rebin(1, 3)};
|
||||
auto hr3 = reduce(h, opts);
|
||||
reduce_command opts2[3] = {rebin(1, 3), rebin(0, 2), shrink(0, 2, 5)};
|
||||
auto hr3 = reduce(h, opts2);
|
||||
BOOST_TEST_EQ(hr3, hr);
|
||||
|
||||
// test positional args
|
||||
auto hr4 = reduce(h, shrink_and_rebin(2, 5, 2), rebin(3));
|
||||
BOOST_TEST_EQ(hr4, hr);
|
||||
}
|
||||
|
||||
// mixed axis types
|
||||
{
|
||||
R r(5, 0.0, 1.0);
|
||||
R r(5, 0.0, 5.0);
|
||||
V v{{1., 2., 3.}};
|
||||
CI c{{1, 2, 3}};
|
||||
auto h = make(Tag(), r, v, c);
|
||||
auto hr = algorithm::reduce(h, shrink(0, 0.2, 0.7));
|
||||
BOOST_TEST_EQ(hr.axis(0).size(), 3);
|
||||
BOOST_TEST_EQ(hr.axis(0).bin(0).lower(), 0.2);
|
||||
BOOST_TEST_EQ(hr.axis(0).bin(2).upper(), 0.8);
|
||||
BOOST_TEST_EQ(hr.axis(1).size(), 2);
|
||||
BOOST_TEST_EQ(hr.axis(1).bin(0).lower(), 1);
|
||||
BOOST_TEST_EQ(hr.axis(1).bin(1).upper(), 3);
|
||||
auto hr = algorithm::reduce(h, shrink(0, 2, 4), slice(2, 1, 3));
|
||||
BOOST_TEST_EQ(hr.axis(0), (R{2, 2, 4}));
|
||||
BOOST_TEST_EQ(hr.axis(1), (V{{1., 2., 3.}}));
|
||||
BOOST_TEST_EQ(hr.axis(2), (CI{{2, 3}}));
|
||||
BOOST_TEST_THROWS((void)algorithm::reduce(h, rebin(2, 2)), std::invalid_argument);
|
||||
}
|
||||
|
||||
@ -261,6 +261,17 @@ void run_tests() {
|
||||
BOOST_TEST_EQ(hr.at(-1, 1), 4);
|
||||
BOOST_TEST_EQ(hr.at(0, 1), 2);
|
||||
}
|
||||
|
||||
// reduce on category axis: removed bins are added to overflow bin
|
||||
{
|
||||
auto h = make(Tag(), CI{{1, 2, 3}});
|
||||
std::fill(h.begin(), h.end(), 1);
|
||||
// original: [1: 1, 2: 1, 3: 1, overflow: 1]
|
||||
auto hr = reduce(h, slice(1, 2));
|
||||
// reduced: [2: 1, overflow: 3]
|
||||
BOOST_TEST_EQ(hr[0], 1);
|
||||
BOOST_TEST_EQ(hr[1], 3);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
|
@ -13,10 +13,32 @@
|
||||
#include "utility_axis.hpp"
|
||||
|
||||
using namespace boost::histogram::axis;
|
||||
using namespace std::literals;
|
||||
|
||||
struct ValueTypeOverride {
|
||||
index_type index(int);
|
||||
};
|
||||
|
||||
namespace boost {
|
||||
namespace histogram {
|
||||
namespace detail {
|
||||
template <>
|
||||
struct value_type_deducer<ValueTypeOverride> {
|
||||
using type = char;
|
||||
};
|
||||
} // namespace detail
|
||||
} // namespace histogram
|
||||
} // namespace boost
|
||||
|
||||
int main() {
|
||||
// value_type
|
||||
{
|
||||
struct Foo {
|
||||
void index(const long&);
|
||||
};
|
||||
|
||||
BOOST_TEST_TRAIT_SAME(traits::value_type<Foo>, long);
|
||||
BOOST_TEST_TRAIT_SAME(traits::value_type<ValueTypeOverride>, char);
|
||||
BOOST_TEST_TRAIT_SAME(traits::value_type<integer<int>>, int);
|
||||
BOOST_TEST_TRAIT_SAME(traits::value_type<category<int>>, int);
|
||||
BOOST_TEST_TRAIT_SAME(traits::value_type<regular<double>>, double);
|
||||
@ -44,10 +66,48 @@ int main() {
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_reducible<variable<>>));
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_reducible<circular<>>));
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_reducible<integer<>>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::is_reducible<category<>>));
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_reducible<category<>>));
|
||||
}
|
||||
|
||||
// static_is_inclusive
|
||||
// get_options, options()
|
||||
{
|
||||
using A = integer<>;
|
||||
BOOST_TEST_EQ(traits::get_options<A>::test(option::growth), false);
|
||||
auto expected = option::underflow | option::overflow;
|
||||
auto a = A{};
|
||||
BOOST_TEST_EQ(traits::options(a), expected);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<A&>(a)), expected);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<const A&>(a)), expected);
|
||||
BOOST_TEST_EQ(traits::options(std::move(a)), expected);
|
||||
|
||||
using B = integer<int, null_type, option::growth_t>;
|
||||
BOOST_TEST_EQ(traits::get_options<B>::test(option::growth), true);
|
||||
BOOST_TEST_EQ(traits::options(B{}), option::growth);
|
||||
|
||||
struct growing {
|
||||
auto update(double) { return std::make_pair(0, 0); }
|
||||
};
|
||||
using C = growing;
|
||||
BOOST_TEST_EQ(traits::get_options<C>::test(option::growth), true);
|
||||
auto c = C{};
|
||||
BOOST_TEST_EQ(traits::options(c), option::growth);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<C&>(c)), option::growth);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<const C&>(c)), option::growth);
|
||||
BOOST_TEST_EQ(traits::options(std::move(c)), option::growth);
|
||||
|
||||
struct notgrowing {
|
||||
auto index(double) { return 0; }
|
||||
};
|
||||
using D = notgrowing;
|
||||
BOOST_TEST_EQ(traits::get_options<D>::test(option::growth), false);
|
||||
auto d = D{};
|
||||
BOOST_TEST_EQ(traits::options(d), option::none);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<D&>(d)), option::none);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<const D&>(d)), option::none);
|
||||
BOOST_TEST_EQ(traits::options(std::move(d)), option::none);
|
||||
}
|
||||
|
||||
// is_inclusive, inclusive()
|
||||
{
|
||||
struct empty {};
|
||||
struct with_opts_not_inclusive {
|
||||
@ -55,40 +115,69 @@ int main() {
|
||||
static constexpr bool inclusive() { return false; }
|
||||
};
|
||||
|
||||
BOOST_TEST_TRAIT_FALSE((traits::static_is_inclusive<empty>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::static_is_inclusive<with_opts_not_inclusive>));
|
||||
BOOST_TEST_EQ(traits::inclusive(empty{}), false);
|
||||
BOOST_TEST_EQ(traits::inclusive(regular<>{}), true);
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::static_is_inclusive<regular<>>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::is_inclusive<empty>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::is_inclusive<with_opts_not_inclusive>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<regular<>>));
|
||||
BOOST_TEST_TRAIT_FALSE(
|
||||
(traits::static_is_inclusive<
|
||||
(traits::is_inclusive<
|
||||
regular<double, boost::use_default, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_FALSE(
|
||||
(traits::static_is_inclusive<regular<double, boost::use_default,
|
||||
boost::use_default, option::circular_t>>));
|
||||
(traits::is_inclusive<regular<double, boost::use_default, boost::use_default,
|
||||
option::circular_t>>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::static_is_inclusive<variable<>>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::static_is_inclusive<
|
||||
variable<double, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::static_is_inclusive<
|
||||
variable<double, boost::use_default, option::circular_t>>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::static_is_inclusive<integer<int>>));
|
||||
BOOST_TEST_TRAIT_TRUE((
|
||||
traits::static_is_inclusive<integer<int, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_TRUE((traits::static_is_inclusive<
|
||||
integer<int, boost::use_default, option::circular_t>>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::static_is_inclusive<integer<double>>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::static_is_inclusive<
|
||||
integer<double, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::static_is_inclusive<
|
||||
integer<double, boost::use_default, option::circular_t>>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::static_is_inclusive<category<int>>));
|
||||
BOOST_TEST_TRAIT_TRUE((traits::static_is_inclusive<
|
||||
category<int, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<variable<>>));
|
||||
BOOST_TEST_TRAIT_FALSE(
|
||||
(traits::static_is_inclusive<category<int, boost::use_default, option::none_t>>));
|
||||
(traits::is_inclusive<variable<double, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_FALSE(
|
||||
(traits::is_inclusive<variable<double, boost::use_default, option::circular_t>>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<integer<int>>));
|
||||
BOOST_TEST_TRAIT_TRUE(
|
||||
(traits::is_inclusive<integer<int, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_TRUE(
|
||||
(traits::is_inclusive<integer<int, boost::use_default, option::circular_t>>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<integer<double>>));
|
||||
BOOST_TEST_TRAIT_FALSE(
|
||||
(traits::is_inclusive<integer<double, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_FALSE(
|
||||
(traits::is_inclusive<integer<double, boost::use_default, option::circular_t>>));
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_inclusive<category<int>>));
|
||||
BOOST_TEST_TRAIT_TRUE(
|
||||
(traits::is_inclusive<category<int, boost::use_default, option::growth_t>>));
|
||||
BOOST_TEST_TRAIT_FALSE(
|
||||
(traits::is_inclusive<category<int, boost::use_default, option::none_t>>));
|
||||
}
|
||||
|
||||
// is_ordered, ordered()
|
||||
{
|
||||
struct ordered_1 {
|
||||
constexpr static bool ordered() { return true; }
|
||||
index_type index(ordered_1) { return true; }
|
||||
};
|
||||
struct ordered_2 {
|
||||
index_type index(int);
|
||||
};
|
||||
struct not_ordered_1 {
|
||||
constexpr static bool ordered() { return false; }
|
||||
index_type index(int);
|
||||
};
|
||||
struct not_ordered_2 {
|
||||
index_type index(not_ordered_2);
|
||||
};
|
||||
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_ordered<ordered_1>));
|
||||
BOOST_TEST_TRAIT_TRUE((traits::is_ordered<ordered_2>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::is_ordered<not_ordered_1>));
|
||||
BOOST_TEST_TRAIT_FALSE((traits::is_ordered<not_ordered_2>));
|
||||
|
||||
BOOST_TEST(traits::ordered(integer<>{}));
|
||||
BOOST_TEST_NOT(traits::ordered(category<int>{}));
|
||||
}
|
||||
|
||||
// index, rank, value, width
|
||||
@ -105,19 +194,19 @@ int main() {
|
||||
BOOST_TEST_EQ(traits::rank(b), 1);
|
||||
BOOST_TEST_EQ(traits::value(b, 0), 1);
|
||||
BOOST_TEST_EQ(traits::width(b, 0), 1);
|
||||
BOOST_TEST(traits::static_options<decltype(b)>::test(option::underflow));
|
||||
BOOST_TEST(traits::get_options<decltype(b)>::test(option::underflow));
|
||||
|
||||
auto c = category<std::string>{"red", "blue"};
|
||||
BOOST_TEST_EQ(traits::index(c, "blue"), 1);
|
||||
BOOST_TEST_EQ(traits::rank(c), 1);
|
||||
BOOST_TEST_EQ(traits::value(c, 0), std::string("red"));
|
||||
BOOST_TEST_EQ(traits::value(c, 0), "red"s);
|
||||
BOOST_TEST_EQ(traits::width(c, 0), 0);
|
||||
|
||||
struct D {
|
||||
index_type index(const std::tuple<int, double>& args) const {
|
||||
return static_cast<index_type>(std::get<0>(args) + std::get<1>(args));
|
||||
}
|
||||
index_type size() const { return 5u; }
|
||||
index_type size() const { return 5; }
|
||||
} d;
|
||||
BOOST_TEST_EQ(traits::index(d, std::make_tuple(1, 2.0)), 3.0);
|
||||
BOOST_TEST_EQ(traits::rank(d), 2u);
|
||||
@ -127,44 +216,14 @@ int main() {
|
||||
BOOST_TEST_EQ(traits::rank(v), 1u);
|
||||
v = d;
|
||||
BOOST_TEST_EQ(traits::rank(v), 2u);
|
||||
}
|
||||
|
||||
// static_options, options()
|
||||
{
|
||||
using A = integer<>;
|
||||
BOOST_TEST_EQ(traits::static_options<A>::test(option::growth), false);
|
||||
auto expected = option::underflow | option::overflow;
|
||||
auto a = A{};
|
||||
BOOST_TEST_EQ(traits::options(a), expected);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<A&>(a)), expected);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<const A&>(a)), expected);
|
||||
BOOST_TEST_EQ(traits::options(std::move(a)), expected);
|
||||
|
||||
using B = integer<int, null_type, option::growth_t>;
|
||||
BOOST_TEST_EQ(traits::static_options<B>::test(option::growth), true);
|
||||
BOOST_TEST_EQ(traits::options(B{}), option::growth);
|
||||
|
||||
struct growing {
|
||||
auto update(double) { return std::make_pair(0, 0); }
|
||||
struct E : integer<> {
|
||||
using integer::integer; // inherit ctors of base
|
||||
// customization point: convert argument and call base class
|
||||
auto index(const char* s) const { return integer::index(std::atoi(s)); }
|
||||
};
|
||||
using C = growing;
|
||||
BOOST_TEST_EQ(traits::static_options<C>::test(option::growth), true);
|
||||
auto c = C{};
|
||||
BOOST_TEST_EQ(traits::options(c), option::growth);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<C&>(c)), option::growth);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<const C&>(c)), option::growth);
|
||||
BOOST_TEST_EQ(traits::options(std::move(c)), option::growth);
|
||||
|
||||
struct notgrowing {
|
||||
auto index(double) { return 0; }
|
||||
};
|
||||
using D = notgrowing;
|
||||
BOOST_TEST_EQ(traits::static_options<D>::test(option::growth), false);
|
||||
auto d = D{};
|
||||
BOOST_TEST_EQ(traits::options(d), option::none);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<D&>(d)), option::none);
|
||||
BOOST_TEST_EQ(traits::options(static_cast<const D&>(d)), option::none);
|
||||
BOOST_TEST_EQ(traits::options(std::move(d)), option::none);
|
||||
BOOST_TEST_EQ(traits::index(E{0, 3}, "2"), 2);
|
||||
}
|
||||
|
||||
// update
|
||||
|
Loading…
x
Reference in New Issue
Block a user