diff --git a/.clang-format b/.clang-format index 3f0ad7ff0..c9d871a7a 100644 --- a/.clang-format +++ b/.clang-format @@ -34,8 +34,9 @@ BraceWrapping: BreakBeforeBinaryOperators: None BreakBeforeBraces: Attach BreakBeforeTernaryOperators: true -BreakConstructorInitializersBeforeComma: false -ColumnLimit: 78 +BreakConstructorInitializersBeforeComma: true +# BreakInheritanceListBeforeComma: true +ColumnLimit: 90 CommentPragmas: '^ IWYU pragma:' ConstructorInitializerAllOnOneLineOrOnePerLine: true ConstructorInitializerIndentWidth: 4 diff --git a/.travis.yml b/.travis.yml index 0c3a5bfb4..b18d3532a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -33,8 +33,6 @@ matrix: - os: osx # minimum osx Xcode 8.3 osx_image: xcode8.3 env: PY=OFF NUMPY=OFF SERIAL=OFF - allow_failures: - - os: osx git: depth: 10 diff --git a/README.md b/README.md index 9cedbf749..7156fc8f1 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ develop | [![Build Status Travis](https://travis-ci.org/HDembinski/histogram.svg 3. Visual Studio 14 2015 -This `C++11` library provides a multi-dimensional [histogram](https://en.wikipedia.org/wiki/Histogram) class for your statistics needs. The library is **header-only**, if you don't need the Python module. +This `C++11` open-source library provides a state-of-the-art multi-dimensional [histogram](https://en.wikipedia.org/wiki/Histogram) class for the professional statistician and everyone who needs to count things. The library is **header-only**, if you don't need the Python module. Check out the [full documentation](http://hdembinski.github.io/histogram/doc/html/). The histogram is very customisable through policy classes, but the default policies were carefully designed so that most users don't need to customize anything. In the standard configuration, this library offers a unique safety guarantee not found elsewhere: bin counts *cannot overflow* or *be capped*. While being safe to use, the library also has a convenient interface, is memory conserving, and faster than other libraries (see benchmarks). @@ -40,6 +40,7 @@ Check out the [full documentation](http://hdembinski.github.io/histogram/doc/htm * Support for under-/overflow bins (can be disabled individually for each dimension) * Support for variance tracking (++) * Support for addition and scaling of histograms +* Support for custom allocators * Optional serialization based on [Boost.Serialization](https://www.boost.org/doc/libs/release/libs/serialization/) * Optional Python-bindings that work with [Python-2.7 to 3.6](http://www.python.org) with [Boost.Python](https://www.boost.org/doc/libs/release/libs/python/) * Optional [Numpy](http://www.numpy.org) support diff --git a/build/CMakeLists.txt b/build/CMakeLists.txt index 2f2331453..7a08b0a15 100644 --- a/build/CMakeLists.txt +++ b/build/CMakeLists.txt @@ -168,7 +168,7 @@ foreach(SRC IN ITEMS ${TEST_SOURCES}) else() target_link_libraries(${BASENAME} ${LIBRARIES}) endif() - if (BASENAME MATCHES "fail") + if (BASENAME MATCHES "fail_") if (DEFINED PYTHON_EXECUTABLE) add_test(NAME ${BASENAME} COMMAND ${PYTHON_EXECUTABLE} ../test/pass_on_fail.py ${BASENAME}) diff --git a/doc/benchmarks.qbk b/doc/benchmarks.qbk index baf1c3b26..a89ba7c7c 100644 --- a/doc/benchmarks.qbk +++ b/doc/benchmarks.qbk @@ -9,14 +9,14 @@ The following plot shows results of a benchmark on a 9 GHz Macbook Pro. Random n [variablelist Plot legend: [[root] [[@https://root.cern.ch ROOT classes] (`TH1I` for 1D, `TH3I` for 3D and `THnI` for 6D)]] [[py:numpy] [numpy functions ([python]`numpy.histogram` for 1D, `numpy.histogramdd` for 2D, 3D, and 6D)]] - [[py:hd_sd] [[classref boost::histogram::dynamic_histogram] with [classref boost::histogram::adaptive_storage], called from Python]] - [[hs_ss] [[classref boost::histogram::static_histogram] with [classref boost::histogram::array_storage]]] - [[hs_sd] [[classref boost::histogram::static_histogram] with [classref boost::histogram::adaptive_storage]]] - [[hd_ss] [[classref boost::histogram::dynamic_histogram] with [classref boost::histogram::array_storage]]] - [[hd_sd] [[classref boost::histogram::dynamic_histogram] with [classref boost::histogram::adaptive_storage]]] + [[py:hd_sd] [[funcref boost::histogram::make_dynamic_histogram dynamic histogram] with [classref boost::histogram::adaptive_storage], called from Python]] + [[hs_ss] [[funcref boost::histogram::make_static_histogram static histogram] with [classref boost::histogram::array_storage]]] + [[hs_sd] [[funcref boost::histogram::make_static_histogram static histogram] with [classref boost::histogram::adaptive_storage]]] + [[hd_ss] [[funcref boost::histogram::make_dynamic_histogram dynamic histogram] with [classref boost::histogram::array_storage]]] + [[hd_sd] [[funcref boost::histogram::make_dynamic_histogram dynamic histogram] with [classref boost::histogram::adaptive_storage]]] ] -[classref boost::histogram::static_histogram] is always faster than [classref boost::histogram::dynamic_histogram] and safer to use, as more checks are done at compile time. It is recommended when working in C++ only. [classref boost::histogram::adaptive_storage] is faster than [classref boost::histogram::array_storage] for histograms with many bins, because it uses the cache more effectively due to its smaller memory consumption per bin. If the number of bins is small, it is slower because of overhead of handling memory in a dynamic way. +A [classref boost::histogram::make_static_histogram static histogram] is always faster than [classref boost::histogram::make_dynamic_histogram dynamic histogram] and safer to use, as more checks are done at compile time. It is recommended when working in C++ only. [classref boost::histogram::adaptive_storage] is faster than [classref boost::histogram::array_storage] for histograms with many bins, because it uses the cache more effectively due to its smaller memory consumption per bin. If the number of bins is small, it is slower because of overhead of handling memory in a dynamic way. The histograms in this library are mostly faster than the competition, in some cases by a factor of 2. Simultaneously they are more flexible, since binning strategies can be customised. The Python-wrapped histogram is slower than numpy's own specialized function for 1D, but beats numpy's multi-dimensional histogramming function by a factor 2 to 3. diff --git a/doc/changelog.qbk b/doc/changelog.qbk index 445e3fc46..26541e810 100644 --- a/doc/changelog.qbk +++ b/doc/changelog.qbk @@ -2,6 +2,11 @@ [master] +[heading 3.2 (not in boost)] + +* Allocator support everywhere +* Internal refactoring + [heading 3.1 (not in boost)] * Renamed `bincount` method to `size` diff --git a/doc/concepts.qbk b/doc/concepts.qbk index b5fa0a974..06e118622 100644 --- a/doc/concepts.qbk +++ b/doc/concepts.qbk @@ -8,7 +8,7 @@ An `axis_type` converts input values into bin indices. An `axis_type` is required to: -* derive publically from [classref boost::histogram::axis::axis_base] or [classref boost::histogram::axis::axis_base_uoflow] +* derive publically from [classref boost::histogram::axis::labeled_base] and [classref boost::histogram::axis::iterator_mixin] * be default/copy/move constructable * be copy/move assignable * be equal comparable @@ -37,6 +37,7 @@ A `storage_type` is required to: * be default/copy/move constructable * be copy/move assignable * be equal comparable +* have a nested type `allocator_type` * have a nested type `element_type`, which represent the bin count * have a nested type `const_reference`, its const reference version * have a constructor `storage_type(std::size_t n)`, which prepares the storage of `n` bins. diff --git a/doc/guide.qbk b/doc/guide.qbk index 04e2ab371..4cbca4243 100644 --- a/doc/guide.qbk +++ b/doc/guide.qbk @@ -18,13 +18,13 @@ The term /histogram/ is usually strictly used for something with bins over discr [section Static or dynamic histogram] -The histogram class comes in two variants with a common interface, see the [link histogram.rationale.histogram_types rationale] for more information. Using a [classref boost::histogram::static_histogram static histogram] is recommended. You need a [classref boost::histogram::dynamic_histogram dynamic histogram] instead, if: +The histogram host class can store axis objects in a static or dynamic container, see the [link histogram.rationale.histogram_host rationale] for details. Use the factory functions [funcref boost::histogram::make_static_histogram make_static_histogram] and [funcref boost::histogram::make_dynamic_histogram make_dynamic_histogram] to make the corresponding histograms. Using static histograms is recommended, because they are faster and usage errors are caught at compile-time. Use dynamic histogram, if: -* you only know the histogram configurations at runtime, not at compile-time +* You only know the axis configurations at runtime, not at compile-time. -* you want to write C++ code that interoperates with the Python module included in the library +* You want to write C++ code that interoperates with the Python module included in the library. -Use the factory function [funcref boost::histogram::make_static_histogram make_static_histogram] (or [funcref boost::histogram::make_dynamic_histogram make_dynamic_histogram], respectively) to make histograms with the default storage policy. The default storage policy makes sure that counting is safe, fast, and memory efficient. If you are curious about trying another storage policy or using your own, have a look at the section [link histogram.guide.expert Advanced Usage]. +These factory functions create histograms with the default storage type [classref boost::histogram::adaptive_storage], which provides safe counting, is fast and memory efficient. If you think you need another storage type or if you want to create your own, have a look at the section [link histogram.guide.expert Advanced Usage]. Here is an example on how to use [funcref boost::histogram::make_static_histogram make_static_histogram]. You pass one or several axis instances, which define the layout of the histogram. @@ -40,7 +40,7 @@ When you work with dynamic histograms, you can also create a sequence of axes at [funcref boost::histogram::make_static_histogram make_static_histogram] cannot handle this case because a static histogram can only be constructed when the number and types of all axes are known already at compile time. While strictly speaking that is also true in this example, you could have filled the vector also at run-time, based on run-time user input. -[note Memory for bin counters is allocated lazily, because if the default storage policy [classref boost::histogram::adaptive_storage adaptive_storage] is used. Allocation is deferred to the first time, when input values are passed to the histogram. Therefore memory allocation exceptions are not thrown when the histogram is created, but possibly later. This gives you a chance to check how much memory the histogram will allocate and possibly give a warning if that amount is excessively large. Use the method `histogram::size()` to see how many bins your axis layout requires. At the first fill, that many bytes will be allocated. The allocated amount of memory may grow further later when the capacity of the bin counters needs to grow.] +[note Memory for bin counters is allocated lazily, if the default storage policy [classref boost::histogram::adaptive_storage adaptive_storage] is used. Allocation is delayed to the first time, when input values are passed to the histogram. Therefore memory allocation exceptions are not thrown when the histogram is created, but possibly later. This gives you a chance to check how much memory the histogram will allocate and possibly give a warning if that amount is excessively large. Use the method `histogram::size()` to see how many bins your axis layout requires. At the first fill, that many bytes will be allocated. The allocated amount of memory may grow further later when the capacity of the bin counters needs to grow.] [endsect] @@ -61,7 +61,7 @@ In addition to the required parameters for an axis, you can assign an optional l Without the labels it would be difficult to remember which axis was covering which quantity, because they look the same otherwise. Labels are the only axis property that can be changed later. Axes objects with different label do not compare equal with `operator==`. -By default, additional under- and overflow bins are added automatically for each axis where that makes sense. If you create an axis with 20 bins, the histogram will actually have 22 bins along that axis. The two extra bins are generally very good to have, as explained in [link histogram.rationale.uoflow the rationale]. If you are certain that the input cannot exceed the axis range, you can disable the extra bins to save memory. This is done by passing the enum value [enumref boost::histogram::axis::uoflow uoflow::off] to the axis constructor: +By default, additional under- and overflow bins are added automatically for each axis where that makes sense. If you create an axis with 20 bins, the histogram will actually have 22 bins along that axis. The two extra bins are generally very good to have, as explained in [link histogram.rationale.uoflow the rationale]. If you are certain that the input cannot exceed the axis range, you can disable the extra bins to save memory. This is done by passing the enum value [enumref boost::histogram::axis::uoflow_type uoflow_type::off] to the axis constructor: [import ../examples/guide_axis_with_uoflow_off.cpp] [guide_axis_with_uoflow_off] @@ -95,7 +95,7 @@ Why weighted increments are sometimes useful, especially in a scientific context After the histogram has been filled, you want to access the counts per bin at some point. You may want to visualize the counts, or compute some quantities like the mean from the data distribution approximated by the histogram. -To access each bin, you use a multi-dimensional index, which consists of a sequence of bin indices for each axes in order. You can use this index to access the value for each and the variance estimate, using the method `histogram::bin(...)`. It accepts integral indices, one for each axis of the histogram, and returns the associated bin counter type. The bin counter type then allows you to access the count value and its variance. +To access each bin, you use a multi-dimensional index, which consists of a sequence of bin indices for each axes in order. You can use this index to access the value for each and the variance estimate, using the method `histogram::at(...)` (in analogy to `std::vector::at`). It accepts integral indices, one for each axis of the histogram, and returns the associated bin counter type. The bin counter type then allows you to access the count value and its variance. The calls are demonstrated in the next example. @@ -140,7 +140,7 @@ Here is an example which demonstrates the supported operators. When you have a high-dimensional histogram, sometimes you want to remove some axes and look the equivalent lower-dimensional version obtained by summing over the counts along the removed axes. Perhaps you found out that there is no interesting structure along an axis, so it is not worth keeping that axies around, or you want to visualize 1d or 2d projections of a high-dimensional histogram. -For this purpose use the `histogram::reduce_to(...)` method, which returns a new reduced histogram with fewer axes. The method accepts indices (one or more), which indicate the axes that are kept. The static histogram only accepts compile-time numbers, while the dynamic histogram also accepts runtime numbers and iterators over numbers. +For this purpose use the `histogram::reduce_to(...)` method, which returns a new reduced histogram with fewer axes. The method accepts indices (one or more), which indicate the axes that are kept. The static histogram only accepts compile-time numbers, while the dynamic histogram also accepts runtime numbers and iterators over numbers. Here is an example to illustrates this. diff --git a/doc/rationale.qbk b/doc/rationale.qbk index f94154a62..dc8e0c8e0 100644 --- a/doc/rationale.qbk +++ b/doc/rationale.qbk @@ -20,27 +20,27 @@ Design goals of the library: The library consists of three orthogonal components: -* [link histogram.rationale.histogram_types histogram types]: Host classes which defines the user interface and responsible for holding axis objects. The two variants have the same user interface, but differ internally. +* [link histogram.rationale.histogram_host histogram host class]: The histogram host class defines the public user interface and holds axis objects (one for each dimension) and a storage object. The user can chose whether axis objects are stored in a static tuple or a dynamic vector. -* [link histogram.rationale.axis_types axis types]: Defines how input values are mapped to bins. Several axis types are provided which implement different specializations. The list is user-extensible. +* [link histogram.rationale.axis_types axis types]: Defines how input values are mapped to bins. Several axis types are provided which implement different specializations. Users can make their own axis types following the axis concept and use them with the library. -* [link histogram.rationale.storage_types storage types]: Manages memory to hold bin counters. The requirements for a storage differ from those of an STL container. Two implementations are provided. +* [link histogram.rationale.storage_types storage types]: Manages a collection of bin counters. The requirements for a storage differ from those of an STL container, it needs to follow the storage concept. Two implementations are provided. [endsect] -[section:histogram_types Histograms types] +[section:histogram_host Histogram host class] -Histograms store a number of axes. A one-dimensional histogram has one axis, a multi-dimensional histogram has several. Each axis maps a value from an input tuple onto a bin in its range. +Histograms store axis objects and a storage object. A one-dimensional histogram has one axis, a multi-dimensional histogram has several. Each axis maps a value from an input tuple onto an index. The histogram host class combines these indices into a global index that is used to address bin counter in the storage object. [note To understand the need for multi-dimensional histograms, think of point coordinates. If all points that you consider lie on a line, you need only one value to describe the point. If all points lie in a plane, you need two values to describe the position. Three values are needed for a point in space. A histogram puts a discrete grid over the line, the plane or the space, and counts how many points lie in each cell of the grid. To reflect a point distribution on a line, a 1d-histogram is sufficient. To do the same in 3d-space, one needs a 3d-histogram. ] -This library supports different axis types, so that the user can customize how the mapping is done exactly, see [link histogram.rationale.axis_types axis types]. The number and concrete types of the axes objects held by the histogram may be known at compile time or only at runtime, depending on how the library is used. +This library supports different axis types, so that the user can customize how the mapping is done exactly, see [link histogram.rationale.axis_types axis types]. Users can furthermore chose between two ways of storing axis types in the histogram. -Users can chose between two histogram variants, which have the same user interface, see [classref boost::histogram::static_histogram] and [classref boost::histogram::dynamic_histogram]. The static variant is faster (see [link histogram.benchmarks benchmark]), because it can access the different axis types without any indirections or dynamic type casting. This also means that user errors are caught at compile-time rather than run-time. +When the histogram host class is configured to store axis types in a `std::tuple`, we obtain a static histogram. The number and types of the axes are known at compile-time. Axis access is done with compile-time indices. A static histogram is always faster (see [link histogram.benchmarks benchmark]), because of type conversions and run-time polymorphism are not needed, and because the compiler can inline more code. Furthermore, user errors are caught at compile-time rather than run-time. -The static variant cannot be used when the axis configuration is only known at run-time, for example, if a histogram is created from Python. The dynamic variant addresses this and allows one to set the number of axes and their types at runtime. The interface of the dynamic variant is a strict superset of the static variant. +The static histogram has many advantages, but cannot be used when the axis configuration is only known at run-time. This is the case, for example, when a histogram is created in Python. Therefore, axis types can also be stored in a generic `boost::histogram::axis::any` type, which in turn can be put in a `std::vector`. When the histogram host class is configured to storage axis types like this, we obtain a dynamic histogram. The dynamic histogram is a single type that can store an arbitrary number and permutations of axes types. The axis configuration can be varied arbitrarily at runtime. There is an additional run-time cost involved whenever an axis is queried to enable the polymorphic behavior of the generic `boost::histogram::axis::any` type. [endsect] @@ -58,13 +58,13 @@ An axis defines which range of input values is mapped to which bin. The logic is [section:storage_types Storage types] -A storage type stores the actual bin counters. It uses a one-dimensional index for lookup, computed by the histogram host from the multi-dimensional index generated by evaluating all its axes. The storage therefore needs to know nothing about axes. Users can integrate their own storage classes with the library, by providing a class compatible with the [link histogram.concepts.storage storage concept]. +A storage type stores the actual bin counters. It uses a one-dimensional index for lookup, computed by the histogram host from the indices generated from all its axes. The storage needs to know nothing about axes. Users can integrate their own storage classes with the library, by providing a class compatible with the [link histogram.concepts.storage storage concept]. -Dense (aka contiguous) storage in memory is needed for fast bin lookup, which is of the random-access variety, and may be happening in a tight loop. The builtin storage types therefore implement dense storage of bin counters. [classref boost::histogram::array_storage] implements a simple storage based on a heap-allocated array of a static counter type. That could be the end of story, but there are some issues with this approach. It is not convenient, because the user has to decide what type to use to hold the bin counts and it is not an obvious choice. An integer type needs to be large enough to avoid counter overflow, but only a fraction of the bits are used if it is too large. Using an integral type that is too large is a waste of memory. This is still a concern today since the performance of modern CPUs depends on effective utilization of the CPU cache, which is small. Using floating point numbers is similarly dangerous. They don't overflow, but cap the bin count when the bits in the mantissa are used up. +The buildin storage types are optimised for fast look-up of the random-access variety and use dense (aka contiguous) storage in memory. Bin lookup is often happening in a tight loop. [classref boost::histogram::array_storage] implements a simple storage based on a heap-allocated array of a static counter type. That could be the end of story, but there are some issues with this approach. It is not convenient, because the user has to decide what type to use to hold the bin counts and it is not an obvious choice. An integer type needs to be large enough to avoid counter overflow, but only a fraction of the bits are used if it is too large. Using an integral type that is too large is a waste of memory. This is still a concern today since the performance of modern CPUs depends on effective utilization of the CPU cache, which is small. Using floating point numbers instead of integers is also dangerous. They don't overflow, but cap the bin count when the bits in the mantissa are used up. -The standard storage used in the library is [classref boost::histogram::adaptive_storage], which solves these issues with a dynamic counter type management, based on the following insight. The [@https://en.wikipedia.org/wiki/Curse_of_dimensionality curse of dimensionality] makes the total number of bins grow very fast as the dimension of the histogram grows. However, having many bins also reduces the number of counts per bin, since the input values are spread over many more bins now. +The standard storage used in the library is [classref boost::histogram::adaptive_storage]. It solves these issues with a dynamic counter type management, based on the following insight. The [@https://en.wikipedia.org/wiki/Curse_of_dimensionality curse of dimensionality] makes the total number of bins grow very fast as the dimension of the histogram grows. However, having many bins also reduces the number of counts per bin, since the input values are spread over many more bins now. -We therefore start with a minimum amount of memory per bin counter by using the smallest integer type to hold a count. If the bin counter is about to overflow, we switch to the next larger integer type. We start with 1 byte per bin counter and then double the size as needed, until 8 byte per bin are reached. The following images illustrate this progression for a storage of 3 bin counters. A new memory block is allocated for all counters, when the first one of them hits its capacity limit. +We therefore start with a minimum amount of memory per bin counter by using the smallest integer type to hold a count. If the bin counter is about to overflow, we switch to the next larger integer type. We start with 1 byte per bin counter and then double the size as needed, until 8 byte per bin are reached. The following images illustrate this progression for a storage of 3 bin counters. A new memory block is always allocated for all counters, when the first one of them hits its capacity limit. [$../storage_3_uint8.svg] @@ -80,7 +80,7 @@ When even that is not enough, we switch to the [@boost:/libs/multiprecision/inde This approach is not only memory conserving, but also allows us to give the strong guarantee that bin counters cannot overflow. -And now comes the best part: this approach is even faster in the multi-dimensional case despite the run-time overheads of handling the counter type dynamically. The benchmarks show, that the gains from better cache usage outweigh the run-time overheads of dynamic dispatching to the right bin counter type and the additional allocation costs. Doubling the size of the bin counters each time helps, because then allocations happen only O(logN) times for N bin increments. +And now comes the best part: this approach is even faster in the multi-dimensional case despite the run-time overheads of handling the counter type dynamically. The benchmarks show that gains from better cache usage outweigh the run-time overheads of dynamic dispatching to the right bin counter type and the additional allocation costs. Doubling the size of the bin counters each time helps, too, because then allocations happen only O(logN) times for N bin increments. In a sense, [classref boost::histogram::adaptive_storage adaptive_storage] is the opposite of a `std::vector`, which keeps the size of the stored type constant, but grows to hold a larger number of elements. Here, the number of elements remains the same, but the storage grows to hold a uniform collection of larger and larger elements. @@ -96,13 +96,13 @@ Under- and overflow bins are useful in one-dimensional histograms, and nearly es * Diagnosis: Unexpected extreme values show up in the extra bins, which otherwise may be overlooked. -* Ability to reduce histograms: In multi-dimensional histograms, an out-of-range value along one axis may be paired with an in-range value along another axis. If under- and overflow bins are missing, such a value pair is lost completely. If you apply a `reduce` operation on a histogram, which removes somes axes by summing counts over that dimension, this would lead to distortions of the histogram along the remaining axes. When under- and overflow bins are present, the `reduce` operation always produces a sub-histogram identical to one obtained if it was filled from scratch with the original data. +* Ability to reduce histograms: In multi-dimensional histograms, an out-of-range value along one axis may be paired with an in-range value along another axis. If under- and overflow bins are missing, such a value pair is lost completely. If you apply a `reduce` operation on a histogram, which removes somes axes by summing all counts along that dimension, this would lead to distortions of the histogram along the remaining axes. When under- and overflow bins are present, the `reduce` operation always produces a sub-histogram identical to one obtained if it was filled from scratch with the original data. [endsect] [section:variance Variance estimates] -Once a histogram is filled, the bin counter can be accessed with the `bin(...)` method. The standard counter type has a `value()` method to return the count ['k]. It also offers a `variance()` method, which returns an estimate ['v] of the [@https://en.wikipedia.org/wiki/Variance variance] of that count. +Once a histogram is filled, the bin counter can be accessed with the `at(...)` method. The standard counter type has a `value()` method to return the count ['k]. It also offers a `variance()` method, which returns an estimate ['v] of the [@https://en.wikipedia.org/wiki/Variance variance] of that count. If the input values for the histogram come from a [@https://en.wikipedia.org/wiki/Stochastic_process stochastic process], the variance provides useful additional information. Examples for a stochastic process are a physics experiment or a random person filling out a questionaire[footnote The choices of the person are most likely not random, but if we pick a random person from a group, we randomly sample from a pool of opinions]. The variance ['v] is the square of the [@https://en.wikipedia.org/wiki/Standard_deviation standard deviation]. The standard deviation is a number that tells us how much we can expect the observed value to fluctuate if we or someone else would repeat our experiment with new random input. @@ -131,7 +131,7 @@ A histogram sorts input values into bins and increments a bin counter if an inpu [note There are several uses for weighted increments. The main use in particle physics is to adapt simulated data of an experiment to real data. Simulations are needed to determine various corrections and efficiencies, but a simulated experiment is almost never a perfect replica of the real experiment. In addition, simulations are expensive to do. So, when deviations in a simulated distribution of a variable are found, one typically does not rerun the simulations, but assigns weights to match the simulated distribution to the real one. ] -When the [classref boost::histogram::adaptive_storage adaptive_storage] is used, histograms may also be filled with weighted value tuples. The choice of using weighted fills can be made at run-time. If the call `fill(..., weight(x))` is used, two doubles per bin are stored (previous integer counts are automatically converted). The first double keeps track of the sum of weights. The second double keeps track of the sum of weights squared, which is the variance estimate in this case. The former is accessed with the `value()` method of the bin counter, and the latter with the `variance()` method. +When the [classref boost::histogram::adaptive_storage adaptive_storage] is used, histograms may also be filled with weighted value tuples. The choice of using weighted fills can be made at run-time. If the call `operator()(weight(x), ...)` is used, two doubles per bin are stored (previous integer counts are automatically converted). The first double keeps track of the sum of weights. The second double keeps track of the sum of weights squared, which is the variance estimate in this case. The former is accessed with the `value()` method of the bin counter, and the latter with the `variance()` method. [note Why the sum of weights squared is the variance estimate can be derived from the [@https://en.wikipedia.org/wiki/Variance#Properties mathematical properties of the variance]. Let us say a bin is filled ['k1] times with a fixed weight ['w1]. The sum of weights is then ['w1 k1]. It then follows from the variance properties that ['Var(w1 k1) = w1^2 Var(k1)]. Using the reasoning from before, the estimated variance of ['k1] is ['k1], so that ['Var(w1 k1) = w1^2 Var(k1) = w1^2 k1]. Variances of independent samples are additive. If the bin is further filled ['k2] times with weight ['w2], the sum of weights is ['w1 k1 + w2 k2], with variance ['w1^2 k1 + w2^2 k2]. This also holds for ['k1 = k2 = 1]. Therefore, the sum of weights ['w[i]] has variance sum of ['w[i]^2]. In other words, to incrementally keep track of the variance of the sum of weights, we need to keep track of the sum of weights squared. ] @@ -149,11 +149,11 @@ If number of dimensions is larger than one, this implementation is faster than t The Python and C++ interface were designed to be as consistent as possible, while following established style for the respective C++ or Python community. This leads to the following stylistic changes on the Python side. -Properties: Getter/setter-like functions on the C++ side are wrapped in Python as properties. Examples: `histogram.dim`, `axis.regular.uoflow`. In general, a C++ function that takes no argument but returns a value is using the property syntax on the Python side. An exception is made for the function `size()`, see next item. +Properties: Getter/setter-like functions on the C++ side are wrapped in Python as properties. Examples: `histogram.dim`, `axis.regular.uoflow`. In general, a C++ function that takes no argument but returns a value is using the property syntax on the Python side. `len(x)` versus `x.size()`: An axis instance behaves like a container of bins in C++ and like a sequence of bins in Python. To get the length of a sequence in Python one uses the `len(...)` function, while in C++ one uses the `size()` method. -Keyword-based parameters: the member function call `fill(..., weight(x))` in C++ is translated into a Python member function call `fill(..., weight=x)`. +Keyword-based parameters: the member function call `operator()(weight(x), ...)` in C++ is translated into a Python member function call `__call__(..., weight=x)`. [endsect] @@ -165,7 +165,7 @@ Serialization is implemented using [@boost:/libs/serialization/index.html Boost. [section Comparison to Boost.Accumulators] -Boost.Histogram has a weak overlap with [@boost:/libs/accumulators/index.html Boost.Accumulators]. In particular, the statistical accumulators `density` and `weighted_density` also generate one-dimensional histograms. The axis range and the bin widths are determined automatically from a cached sample of initial values. In contrast, Boost.Histogram puts the responsibility to choose range and bin widths on the user. +Boost.Histogram has a minor overlap with [@boost:/libs/accumulators/index.html Boost.Accumulators], but the scopes are rather different. The statistical accumulators `density` and `weighted_density` in Boost.Accumulators generate one-dimensional histograms. The axis range and the bin widths are determined automatically from a cached sample of initial values. Boost.Histogram focusses on multi-dimensional data and gives the user full control of how the binning should be done for each dimension. Automatic binning is not an option for Boost.Histogram, because it does not scale well to many dimensions. Because of the Curse of Dimensionality, a prohibitive number of samples would need to be collected. diff --git a/examples/guide_access_bin_counts.cpp b/examples/guide_access_bin_counts.cpp index 324fb66e2..7c448ee3c 100644 --- a/examples/guide_access_bin_counts.cpp +++ b/examples/guide_access_bin_counts.cpp @@ -28,7 +28,7 @@ int main() { << std::endl; // prints: 1 4 9 16 - // you can also make a copy of the type that holds the bin count + // this is more efficient when you want to query value and variance auto c11 = h.at(1, 1); std::cout << c11.value() << " " << c11.variance() << std::endl; // prints: 4 16 @@ -39,11 +39,24 @@ int main() { std::cout << h.at(idx).value() << std::endl; // prints: 2 - // histogram also provides extended iterators + // histogram also provides bin iterators auto sum = std::accumulate(h.begin(), h.end(), typename decltype(h)::element_type(0)); std::cout << sum.value() << " " << sum.variance() << std::endl; // prints: 10 30 + + // bin iterators are fancy iterators with extra methods + for (auto it = h.begin(), end = h.end(); it != end; ++it) { + const auto x = *it; + std::cout << it.idx(0) << " " << it.idx(1) << ": " + << x.value() << " " << x.variance() << std::endl; + } + // prints: (iteration order is an implementation detail, don't rely on it) + // 0 0: 1 1 + // 1 0: 3 9 + // ... + // 2 -1: 0 0 + // -1 -1: 0 0 } //] diff --git a/examples/guide_axis_with_uoflow_off.cpp b/examples/guide_axis_with_uoflow_off.cpp index a7b70e95e..69e5fc651 100644 --- a/examples/guide_axis_with_uoflow_off.cpp +++ b/examples/guide_axis_with_uoflow_off.cpp @@ -7,7 +7,7 @@ namespace bh = boost::histogram; int main() { // create a 1d-histogram for dice throws with integer values from 1 to 6 auto h = bh::make_static_histogram( - bh::axis::integer<>(1, 7, "eyes", bh::axis::uoflow::off)); + bh::axis::integer<>(1, 7, "eyes", bh::axis::uoflow_type::off)); // do something with h } diff --git a/examples/guide_custom_storage.cpp b/examples/guide_custom_storage.cpp index 0e64179b6..2114b19db 100644 --- a/examples/guide_custom_storage.cpp +++ b/examples/guide_custom_storage.cpp @@ -7,8 +7,8 @@ namespace bh = boost::histogram; int main() { // create static histogram with array_storage, using int as counter type - auto h = bh::make_static_histogram_with>( - bh::axis::regular<>(10, 0, 1)); + auto h = bh::make_static_histogram_with(bh::array_storage(), + bh::axis::regular<>(10, 0, 1)); // do something with h } diff --git a/examples/guide_fill_histogram.cpp b/examples/guide_fill_histogram.cpp index 68c15c9d3..625c09146 100644 --- a/examples/guide_fill_histogram.cpp +++ b/examples/guide_fill_histogram.cpp @@ -7,7 +7,7 @@ namespace bh = boost::histogram; int main() { - auto h = bh::make_static_histogram(bh::axis::integer<>(0, 4), + auto h = bh::make_static_histogram(bh::axis::regular<>(8, 0, 4), bh::axis::regular<>(10, 0, 5)); // fill histogram, number of arguments must be equal to number of axes @@ -24,14 +24,14 @@ int main() { // functional-style processing is also supported std::vector> input_data{ {0, 1.2}, {2, 3.4}, {4, 5.6}}; - // Note that std::for_each takes the functor by value, thus it makes a - // potentially expensive copy of your histogram. Passing freshly created - // histograms is ok, though, because of return-value-optimization + // std::for_each takes the functor by value, thus it potentially makes + // expensive copies of the histogram, but modern compilers are usually smart + // enough to avoid the superfluous copies auto h2 = std::for_each(input_data.begin(), input_data.end(), - bh::make_static_histogram(bh::axis::integer<>(0, 4), + bh::make_static_histogram(bh::axis::regular<>(8, 0, 4), bh::axis::regular<>(10, 0, 5))); - // h is filled + // h2 is filled } //] diff --git a/examples/guide_histogram_streaming.cpp b/examples/guide_histogram_streaming.cpp index e49267e50..1c4656716 100644 --- a/examples/guide_histogram_streaming.cpp +++ b/examples/guide_histogram_streaming.cpp @@ -12,12 +12,12 @@ int main() { enum { A, B, C }; auto h = bh::make_static_histogram( - axis::regular<>(2, -1, 1, "regular1", axis::uoflow::off), - axis::regular(2, 1, 10, "regular2"), + axis::regular<>(2, -1, 1, "regular1", axis::uoflow_type::off), + axis::regular(2, 1, 10, "regular2"), axis::circular<>(4, 0.1, 1.0, "polar"), - axis::variable<>({-1, 0, 1}, "variable", axis::uoflow::off), + axis::variable<>({-1, 0, 1}, "variable", axis::uoflow_type::off), axis::category<>({A, B, C}, "category"), - axis::integer<>(-1, 1, "integer", axis::uoflow::off)); + axis::integer<>(-1, 1, "integer", axis::uoflow_type::off)); std::cout << h << std::endl; diff --git a/examples/guide_make_dynamic_histogram.cpp b/examples/guide_make_dynamic_histogram.cpp index 2503fdee1..6e090e6cf 100644 --- a/examples/guide_make_dynamic_histogram.cpp +++ b/examples/guide_make_dynamic_histogram.cpp @@ -11,10 +11,18 @@ int main() { v.push_back(bh::axis::regular<>(100, -1, 1)); v.push_back(bh::axis::integer<>(1, 7)); - // create dynamic histogram (make_static_histogram be used with iterators) + // create dynamic histogram (make_static_histogram cannot be used with iterators) auto h = bh::make_dynamic_histogram(v.begin(), v.end()); // do something with h + + + + // make_dynamic_histogram copies axis objects; to instead move the whole axis + // vector into the histogram, create a histogram instance directly + auto h2 = bh::histogram(std::move(v)); + + // do something with h2 } //] diff --git a/examples/guide_mixed_cpp_python.cpp b/examples/guide_mixed_cpp_python.cpp index a2c05ebc3..a96ca4eb7 100644 --- a/examples/guide_mixed_cpp_python.cpp +++ b/examples/guide_mixed_cpp_python.cpp @@ -7,7 +7,7 @@ namespace bh = boost::histogram; namespace bp = boost::python; // function that runs in C++ and accepts reference to dynamic histogram -void process(bh::dynamic_histogram<>& h) { +void process(bh::histogram<>& h) { // fill histogram, in reality this would be arbitrarily complex code for (int i = 0; i < 4; ++i) h(0.25 * i, i); } diff --git a/histogram.sublime-project b/histogram.sublime-project new file mode 100644 index 000000000..ae199ae81 --- /dev/null +++ b/histogram.sublime-project @@ -0,0 +1,15 @@ +{ + "folders": + [ + { + "path": "." + } + ], + "settings": + { + "ClangFormat": + { + "format_on_save": true + } + } +} diff --git a/include/boost/histogram.hpp b/include/boost/histogram.hpp index bfdd305e4..53ac7f157 100644 --- a/include/boost/histogram.hpp +++ b/include/boost/histogram.hpp @@ -9,9 +9,8 @@ #include #include -#include +#include #include -#include #include #include #include diff --git a/include/boost/histogram/arithmetic_operators.hpp b/include/boost/histogram/arithmetic_operators.hpp index 1aa9d1e5d..b177a1e61 100644 --- a/include/boost/histogram/arithmetic_operators.hpp +++ b/include/boost/histogram/arithmetic_operators.hpp @@ -12,69 +12,66 @@ namespace boost { namespace histogram { -template -histogram&& operator+(histogram&& a, - const histogram& b) { +template +histogram&& operator+(histogram&& a, const histogram& b) { a += b; return std::move(a); } -template -histogram&& operator+(histogram&& a, - histogram&& b) { +template +histogram&& operator+(histogram&& a, histogram&& b) { a += b; return std::move(a); } -template -histogram&& operator+(const histogram& a, - histogram&& b) { +template +histogram&& operator+(const histogram& a, histogram&& b) { b += a; return std::move(b); } -template -histogram operator+(const histogram& a, - const histogram& b) { - histogram r(a); +template +histogram operator+(const histogram& a, + const histogram& b) { + histogram r(a); r += b; return r; } -template -histogram&& operator*(histogram&& a, const double x) { +template +histogram&& operator*(histogram&& a, const double x) { a *= x; return std::move(a); } -template -histogram&& operator*(const double x, histogram&& b) { +template +histogram&& operator*(const double x, histogram&& b) { b *= x; return std::move(b); } -template -histogram operator*(const histogram& a, const double x) { +template +histogram operator*(const histogram& a, const double x) { auto r = a; r *= x; return r; } -template -histogram operator*(const double x, const histogram& b) { +template +histogram operator*(const double x, const histogram& b) { auto r = b; r *= x; return r; } -template -histogram&& operator/(histogram&& a, const double x) { +template +histogram&& operator/(histogram&& a, const double x) { a /= x; return std::move(a); } -template -histogram operator/(const histogram& a, const double x) { +template +histogram operator/(const histogram& a, const double x) { auto r = a; r /= x; return r; diff --git a/include/boost/histogram/axis/any.hpp b/include/boost/histogram/axis/any.hpp index 07742d3ab..41352e105 100644 --- a/include/boost/histogram/axis/any.hpp +++ b/include/boost/histogram/axis/any.hpp @@ -58,8 +58,8 @@ struct index_visitor : public boost::static_visitor { int impl(std::false_type, const Axis&) const { throw std::runtime_error(boost::histogram::detail::cat( "cannot convert double to ", - boost::typeindex::type_id().pretty_name(), - " for ", boost::typeindex::type_id().pretty_name())); + boost::typeindex::type_id().pretty_name(), " for ", + boost::typeindex::type_id().pretty_name())); } }; @@ -70,10 +70,8 @@ struct lower_visitor : public boost::static_visitor { double operator()(const Axis& a) const { return impl( std::integral_constant< - bool, - (std::is_convertible::value && - std::is_same>::value)>(), + bool, (std::is_convertible::value && + std::is_same>::value)>(), a); } template @@ -119,8 +117,31 @@ struct assign_visitor : public boost::static_visitor { void impl(mp11::mp_false, const U&) const { throw std::invalid_argument(boost::histogram::detail::cat( "argument ", boost::typeindex::type_id().pretty_name(), - " is not a bounded type of ", - boost::typeindex::type_id().pretty_name())); + " is not a bounded type of ", boost::typeindex::type_id().pretty_name())); + } +}; + +struct get_label_visitor : public boost::static_visitor { + template + boost::string_view operator()(const T& t) const { + return t.label(); + } +}; + +struct set_label_visitor : public boost::static_visitor { + boost::string_view view; + set_label_visitor(boost::string_view v) : view(v) {} + template + void operator()(T& t) const { + t.label(view); + } +}; + +template +struct get_allocator_visitor : public boost::static_visitor { + template + U operator()(const T& t) const { + return t.get_allocator(); } }; @@ -140,8 +161,8 @@ class any : public boost::variant { private: template - using requires_bounded_type = mp11::mp_if< - mp11::mp_contains>, void>; + using requires_bounded_type = + mp11::mp_if>, void>; public: any() = default; @@ -182,10 +203,12 @@ class any : public boost::variant { } string_view label() const { - return static_cast(*this).label(); + return boost::apply_visitor(detail::get_label_visitor(), *this); } - void label(const string_view x) { static_cast(*this).label(x); } + void label(const string_view x) { + boost::apply_visitor(detail::set_label_visitor(x), *this); + } // this only works for axes with compatible bin type // and will throw a runtime_error otherwise @@ -217,8 +240,7 @@ class any : public boost::variant { } explicit operator const base&() const { - return boost::apply_visitor(detail::static_cast_visitor(), - *this); + return boost::apply_visitor(detail::static_cast_visitor(), *this); } explicit operator base&() { @@ -237,12 +259,8 @@ class any : public boost::variant { const_iterator begin() const { return const_iterator(*this, 0); } const_iterator end() const { return const_iterator(*this, size()); } - const_reverse_iterator rbegin() const { - return const_reverse_iterator(*this, size()); - } - const_reverse_iterator rend() const { - return const_reverse_iterator(*this, 0); - } + const_reverse_iterator rbegin() const { return const_reverse_iterator(*this, size()); } + const_reverse_iterator rend() const { return const_reverse_iterator(*this, 0); } private: friend class boost::serialization::access; diff --git a/include/boost/histogram/axis/base.hpp b/include/boost/histogram/axis/base.hpp index 10ee8f63f..25c177655 100644 --- a/include/boost/histogram/axis/base.hpp +++ b/include/boost/histogram/axis/base.hpp @@ -8,10 +8,10 @@ #define _BOOST_HISTOGRAM_AXIS_BASE_HPP_ #include +#include #include #include #include -#include // forward declaration for serialization namespace boost { @@ -24,7 +24,7 @@ namespace boost { namespace histogram { namespace axis { -enum class uoflow { off = 0, oflow = 1, on = 2 }; +enum class uoflow_type { off = 0, oflow = 1, on = 2 }; /// Base class for all axes class base { @@ -33,26 +33,19 @@ class base { int size() const noexcept { return size_; } /// Returns the number of bins, including overflow/underflow if enabled. int shape() const noexcept { return shape_; } - /// Returns true if axis has extra overflow and underflow bins. - bool uoflow() const noexcept { return shape_ > size_; } - /// Returns the axis label, which is a name or description. - string_view label() const noexcept { return label_; } - /// Change the label of an axis. - void label(string_view label) { label_.assign(label.begin(), label.end()); } + /// Returns number of extra bins to count under- or overflow. + int uoflow() const noexcept { return shape_ - size_; } protected: - base(unsigned size, string_view label, axis::uoflow uo) - : size_(size), - shape_(size + static_cast(uo)), - label_(label.begin(), label.end()) { + base(unsigned size, axis::uoflow_type uo) + : size_(size), shape_(size + static_cast(uo)) { if (size_ == 0) { throw std::invalid_argument("bins > 0 required"); } } base() = default; base(const base&) = default; base& operator=(const base&) = default; - base(base&& rhs) - : size_(rhs.size_), shape_(rhs.shape_), label_(std::move(rhs.label_)) { + base(base&& rhs) : size_(rhs.size_), shape_(rhs.shape_) { rhs.size_ = 0; rhs.shape_ = 0; } @@ -60,7 +53,6 @@ class base { if (this != &rhs) { size_ = rhs.size_; shape_ = rhs.shape_; - label_ = std::move(rhs.label_); rhs.size_ = 0; rhs.shape_ = 0; } @@ -68,12 +60,47 @@ class base { } bool operator==(const base& rhs) const noexcept { - return size_ == rhs.size_ && shape_ == rhs.shape_ && label_ == rhs.label_; + return size_ == rhs.size_ && shape_ == rhs.shape_; } private: int size_ = 0, shape_ = 0; - std::string label_; + + friend class ::boost::serialization::access; + template + void serialize(Archive&, unsigned); +}; + +/// Base class with a label +template +class labeled_base : public base { +public: + using allocator_type = Allocator; + + allocator_type get_allocator() const { return label_.get_allocator(); } + + /// Returns the axis label, which is a name or description. + boost::string_view label() const noexcept { return label_; } + /// Change the label of an axis. + void label(boost::string_view label) { label_.assign(label.begin(), label.end()); } + + bool operator==(const labeled_base& rhs) const noexcept { + return base::operator==(rhs) && label_ == rhs.label_; + } + +protected: + labeled_base() = default; + labeled_base(const labeled_base&) = default; + labeled_base& operator=(const labeled_base&) = default; + labeled_base(labeled_base&& rhs) = default; + labeled_base& operator=(labeled_base&& rhs) = default; + + labeled_base(unsigned size, axis::uoflow_type uo, string_view label, + const allocator_type& a) + : base(size, uo), label_(label.begin(), label.end(), a) {} + +private: + boost::container::basic_string, allocator_type> label_; friend class ::boost::serialization::access; template @@ -102,8 +129,9 @@ class iterator_mixin { return const_reverse_iterator(*static_cast(this), 0); } }; -} -} -} + +} // namespace axis +} // namespace histogram +} // namespace boost #endif diff --git a/include/boost/histogram/axis/ostream_operators.hpp b/include/boost/histogram/axis/ostream_operators.hpp index 4ab71b51d..807a853e2 100644 --- a/include/boost/histogram/axis/ostream_operators.hpp +++ b/include/boost/histogram/axis/ostream_operators.hpp @@ -12,7 +12,6 @@ #include #include #include -#include #include namespace boost { @@ -23,113 +22,130 @@ namespace detail { inline string_view to_string(const transform::identity&) { return {}; } inline string_view to_string(const transform::log&) { return {"_log", 4}; } inline string_view to_string(const transform::sqrt&) { return {"_sqrt", 5}; } + +template +void escape_string(OStream& os, const string_view s) { + os << '\''; + for (auto sit = s.begin(); sit != s.end(); ++sit) { + if (*sit == '\'' && (sit == s.begin() || *(sit - 1) != '\\')) { + os << "\\\'"; + } else { + os << *sit; + } + } + os << '\''; +} } // namespace detail -template -std::ostream& operator<<(std::ostream& os, const interval_view& i) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const interval_view& i) { os << "[" << i.lower() << ", " << i.upper() << ")"; return os; } -template -std::ostream& operator<<(std::ostream& os, const value_view& i) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const value_view& i) { os << i.value(); return os; } -template -std::ostream& operator<<(std::ostream& os, - const regular& a) { - os << "regular" << detail::to_string(Transform()) << "(" << a.size() << ", " +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const regular& a) { + os << "regular" << detail::to_string(a.transform()) << "(" << a.size() << ", " << a[0].lower() << ", " << a[a.size()].lower(); if (!a.label().empty()) { os << ", label="; - ::boost::histogram::detail::escape(os, a.label()); + detail::escape_string(os, a.label()); } if (!a.uoflow()) { os << ", uoflow=False"; } os << ")"; return os; } -template -std::ostream& operator<<(std::ostream& os, - const regular& a) { - os << "regular_pow(" << a.size() << ", " << a[0].lower() << ", " - << a[a.size()].lower() << ", " << a.transform().power; +template +std::basic_ostream& operator<<( + std::basic_ostream& os, const regular& a) { + os << "regular_pow(" << a.size() << ", " << a[0].lower() << ", " << a[a.size()].lower() + << ", " << a.transform().power; if (!a.label().empty()) { os << ", label="; - ::boost::histogram::detail::escape(os, a.label()); + detail::escape_string(os, a.label()); } if (!a.uoflow()) { os << ", uoflow=False"; } os << ")"; return os; } -template -std::ostream& operator<<(std::ostream& os, const circular& a) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const circular& a) { os << "circular(" << a.size(); if (a.phase() != 0.0) { os << ", phase=" << a.phase(); } - if (a.perimeter() != RealType(::boost::histogram::detail::two_pi)) { + if (a.perimeter() != circular::two_pi()) { os << ", perimeter=" << a.perimeter(); } if (!a.label().empty()) { os << ", label="; - ::boost::histogram::detail::escape(os, a.label()); + detail::escape_string(os, a.label()); } os << ")"; return os; } -template -std::ostream& operator<<(std::ostream& os, const variable& a) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const variable& a) { os << "variable(" << a[0].lower(); for (int i = 1; i <= a.size(); ++i) { os << ", " << a[i].lower(); } if (!a.label().empty()) { os << ", label="; - ::boost::histogram::detail::escape(os, a.label()); + detail::escape_string(os, a.label()); } if (!a.uoflow()) { os << ", uoflow=False"; } os << ")"; return os; } -template -std::ostream& operator<<(std::ostream& os, const integer& a) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const integer& a) { os << "integer(" << a[0].lower() << ", " << a[a.size()].lower(); if (!a.label().empty()) { os << ", label="; - ::boost::histogram::detail::escape(os, a.label()); + detail::escape_string(os, a.label()); } if (!a.uoflow()) { os << ", uoflow=False"; } os << ")"; return os; } -template -std::ostream& operator<<(std::ostream& os, const category& a) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const category& a) { os << "category("; - for (int i = 0; i < a.size(); ++i) { - os << a[i] << (i == (a.size() - 1) ? "" : ", "); - } + for (int i = 0; i < a.size(); ++i) { os << a[i] << (i == (a.size() - 1) ? "" : ", "); } if (!a.label().empty()) { os << ", label="; - ::boost::histogram::detail::escape(os, a.label()); + detail::escape_string(os, a.label()); } os << ")"; return os; } -template <> -inline std::ostream& operator<<(std::ostream& os, - const category& a) { +template +inline std::basic_ostream& operator<<( + std::basic_ostream& os, const category& a) { os << "category("; for (int i = 0; i < a.size(); ++i) { - ::boost::histogram::detail::escape(os, a.value(i)); + detail::escape_string(os, a.value(i)); os << (i == (a.size() - 1) ? "" : ", "); } if (!a.label().empty()) { os << ", label="; - ::boost::histogram::detail::escape(os, a.label()); + detail::escape_string(os, a.label()); } os << ")"; return os; diff --git a/include/boost/histogram/axis/types.hpp b/include/boost/histogram/axis/types.hpp index 2c574c76b..baa49f62a 100644 --- a/include/boost/histogram/axis/types.hpp +++ b/include/boost/histogram/axis/types.hpp @@ -8,19 +8,16 @@ #define _BOOST_HISTOGRAM_AXIS_TYPES_HPP_ #include -#include #include #include #include #include #include -#include #include #include #include #include #include -#include // forward declaration for serialization namespace boost { @@ -93,9 +90,7 @@ struct pow { T inverse(T v) const { return std::pow(v, 1.0 / power); } - bool operator==(const pow& other) const noexcept { - return power == other.power; - } + bool operator==(const pow& other) const noexcept { return power == other.power; } private: friend ::boost::serialization::access; @@ -110,13 +105,17 @@ struct pow { * Very fast. Binning is a O(1) operation. */ // private inheritance from Transform wastes no space if it is stateless -template -class regular : public base, - public iterator_mixin>, +template +class regular : public labeled_base, + public iterator_mixin>, Transform { + using base_type = labeled_base; + public: + using allocator_type = typename base_type::allocator_type; using value_type = RealType; using bin_type = interval_view; + using transform_type = Transform; /** Construct axis with n bins over real range [lower, upper). * @@ -127,13 +126,13 @@ class regular : public base, * \param uoflow whether to add under-/overflow bins. * \param trans arguments passed to the transform. */ - regular(unsigned n, value_type lower, value_type upper, - string_view label = {}, axis::uoflow uo = axis::uoflow::on, - Transform trans = Transform()) - : base(n, label, uo), - Transform(trans), - min_(trans.forward(lower)), - delta_((trans.forward(upper) - trans.forward(lower)) / n) { + regular(unsigned n, value_type lower, value_type upper, string_view label = {}, + uoflow_type uo = uoflow_type::on, transform_type trans = transform_type(), + const allocator_type& a = allocator_type()) + : base_type(n, uo, label, a) + , transform_type(trans) + , min_(trans.forward(lower)) + , delta_((trans.forward(upper) - trans.forward(lower)) / n) { if (lower < upper) { BOOST_ASSERT(!std::isnan(min_)); BOOST_ASSERT(!std::isnan(delta_)); @@ -151,14 +150,14 @@ class regular : public base, /// Returns the bin index for the passed argument. int index(value_type x) const noexcept { // Optimized code, measure impact of changes - const value_type z = (Transform::forward(x) - min_) / delta_; - return z < base::size() ? (z >= 0.0 ? static_cast(z) : -1) - : base::size(); + const value_type z = (transform_type::forward(x) - min_) / delta_; + return z < base_type::size() ? (z >= 0.0 ? static_cast(z) : -1) + : base_type::size(); } /// Returns lower edge of bin. value_type lower(int i) const noexcept { - const auto n = base::size(); + const auto n = base_type::size(); value_type x; if (i < 0) x = -std::numeric_limits::infinity(); @@ -168,19 +167,19 @@ class regular : public base, const auto z = value_type(i) / n; x = (1.0 - z) * min_ + z * (min_ + delta_ * n); } - return Transform::inverse(x); + return transform_type::inverse(x); } bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } bool operator==(const regular& o) const noexcept { - return base::operator==(o) && Transform::operator==(o) && - min_ == o.min_ && delta_ == o.delta_; + return base_type::operator==(o) && transform_type::operator==(o) && min_ == o.min_ && + delta_ == o.delta_; } /// Access properties of the transform. - const Transform& transform() const noexcept { - return static_cast(*this); + const transform_type& transform() const noexcept { + return static_cast(*this); } private: @@ -197,12 +196,19 @@ class regular : public base, * perimeter value. Therefore, there are no overflow/underflow * bins for this axis. Binning is a O(1) operation. */ -template -class circular : public base, public iterator_mixin> { +template +class circular : public labeled_base, + public iterator_mixin> { + using base_type = labeled_base; + public: + using allocator_type = typename base_type::allocator_type; using value_type = RealType; using bin_type = interval_view; + // two_pi can be found in boost/math, but it is defined here to reduce deps + static value_type two_pi() { return 6.283185307179586; } + /** Constructor for n bins with an optional offset. * * \param n number of bins. @@ -210,12 +216,9 @@ class circular : public base, public iterator_mixin> { * \param perimeter range after which value wraps around. * \param label description of the axis. */ - explicit circular(unsigned n, value_type phase = 0.0, - value_type perimeter = boost::histogram::detail::two_pi, - string_view label = {}) - : base(n, label, axis::uoflow::off), - phase_(phase), - perimeter_(perimeter) {} + explicit circular(unsigned n, value_type phase = 0.0, value_type perimeter = two_pi(), + string_view label = {}, const allocator_type& a = allocator_type()) + : base_type(n, uoflow_type::off, label, a), phase_(phase), perimeter_(perimeter) {} circular() = default; circular(const circular&) = default; @@ -226,22 +229,20 @@ class circular : public base, public iterator_mixin> { /// Returns the bin index for the passed argument. int index(value_type x) const noexcept { const value_type z = (x - phase_) / perimeter_; - const int i = - static_cast(std::floor(z * base::size())) % base::size(); - return i + (i < 0) * base::size(); + const int i = static_cast(std::floor(z * base_type::size())) % base_type::size(); + return i + (i < 0) * base_type::size(); } /// Returns lower edge of bin. value_type lower(int i) const noexcept { - const value_type z = value_type(i) / base::size(); + const value_type z = value_type(i) / base_type::size(); return z * perimeter_ + phase_; } bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } bool operator==(const circular& o) const noexcept { - return base::operator==(o) && phase_ == o.phase_ && - perimeter_ == o.perimeter_; + return base_type::operator==(o) && phase_ == o.phase_ && perimeter_ == o.perimeter_; } value_type perimeter() const { return perimeter_; } @@ -260,12 +261,21 @@ class circular : public base, public iterator_mixin> { * Binning is a O(log(N)) operation. If speed matters and the problem * domain allows it, prefer a regular axis, possibly with a transform. */ -template -class variable : public base, public iterator_mixin> { +template +class variable : public labeled_base, + public iterator_mixin> { + using base_type = labeled_base; + public: + using allocator_type = typename base_type::allocator_type; using value_type = RealType; using bin_type = interval_view; +private: + using value_allocator_type = + typename std::allocator_traits::template rebind_alloc; + +public: /** Construct an axis from bin edges. * * \param x sequence of bin edges. @@ -273,53 +283,85 @@ class variable : public base, public iterator_mixin> { * \param uoflow whether to add under-/overflow bins. */ variable(std::initializer_list x, string_view label = {}, - axis::uoflow uo = axis::uoflow::on) - : base(x.size() - 1, label, uo), x_(new value_type[x.size()]) { - if (x.size() >= 2) { - std::copy(x.begin(), x.end(), x_.get()); - std::sort(x_.get(), x_.get() + base::size() + 1); - } else { - throw std::invalid_argument("at least two values required"); - } - } + uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type()) + : variable(x.begin(), x.end(), label, uo, a) {} - template + template > variable(Iterator begin, Iterator end, string_view label = {}, - axis::uoflow uo = axis::uoflow::on) - : base(std::distance(begin, end) - 1, label, uo), - x_(new value_type[std::distance(begin, end)]) { - std::copy(begin, end, x_.get()); - std::sort(x_.get(), x_.get() + base::size() + 1); + uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type()) + : base_type(begin == end ? 0 : std::distance(begin, end) - 1, uo, label, a) { + value_allocator_type a2(a); + using AT = std::allocator_traits; + x_ = AT::allocate(a2, nx()); + auto xit = x_; + AT::construct(a2, xit, *begin++); + while (begin != end) { + if (*begin <= *xit) + throw std::invalid_argument("input sequence must be strictly ascending"); + AT::construct(a2, ++xit, *begin++); + } } variable() = default; - variable(const variable& o) - : base(o), x_(new value_type[base::size() + 1]) { - std::copy(o.x_.get(), o.x_.get() + base::size() + 1, x_.get()); + + variable(const variable& o) : base_type(o) { + value_allocator_type a(o.get_allocator()); + using AT = std::allocator_traits; + x_ = AT::allocate(a, nx()); + auto it = o.x_; + for (auto xit = x_, xe = xend(); xit != xe; ++xit) AT::construct(a, xit, *it++); } + variable& operator=(const variable& o) { if (this != &o) { - base::operator=(o); - x_.reset(new value_type[base::size() + 1]); - std::copy(o.x_.get(), o.x_.get() + base::size() + 1, x_.get()); + if (base_type::size() != o.size()) { + this->~variable(); + base::operator=(o); + value_allocator_type a(base_type::get_allocator()); + using AT = std::allocator_traits; + x_ = AT::allocate(a, nx()); + auto it = o.x_; + for (auto xit = x_, xe = xend(); xit != xe; ++xit) AT::construct(a, xit, *it++); + } else { + base::operator=(o); + std::copy(o.x_, o.x_ + o.nx(), x_); + } } return *this; } - variable(variable&&) = default; - variable& operator=(variable&&) = default; + + variable(variable&& o) : base_type(std::move(o)) { + x_ = o.x_; + o.x_ = nullptr; + } + + variable& operator=(variable&& o) { + this->~variable(); + base::operator=(std::move(o)); + x_ = o.x_; + o.x_ = nullptr; + return *this; + } + + ~variable() { + if (x_) { // nothing to do for empty state + value_allocator_type a(base_type::get_allocator()); + using AT = std::allocator_traits; + for (auto xit = x_, xe = xend(); xit != xe; ++xit) AT::destroy(a, xit); + AT::deallocate(a, x_, nx()); + } + } /// Returns the bin index for the passed argument. int index(value_type x) const noexcept { - return std::upper_bound(x_.get(), x_.get() + base::size() + 1, x) - - x_.get() - 1; + return std::upper_bound(x_, x_ + nx(), x) - x_ - 1; } /// Returns the starting edge of the bin. value_type lower(int i) const noexcept { if (i < 0) { return -std::numeric_limits::infinity(); } - if (i > base::size()) { - return std::numeric_limits::infinity(); - } + if (i > base_type::size()) { return std::numeric_limits::infinity(); } return x_[i]; } @@ -327,11 +369,14 @@ class variable : public base, public iterator_mixin> { bool operator==(const variable& o) const noexcept { if (!base::operator==(o)) { return false; } - return std::equal(x_.get(), x_.get() + base::size() + 1, o.x_.get()); + return std::equal(x_, x_ + nx(), o.x_); } private: - std::unique_ptr x_; // smaller size compared to std::vector + int nx() const { return base_type::size() + 1; } + value_type* xend() { return x_ + nx(); } + + value_type* x_ = nullptr; friend class ::boost::serialization::access; template @@ -343,9 +388,13 @@ class variable : public base, public iterator_mixin> { * Binning is a O(1) operation. This axis operates * faster than a regular. */ -template -class integer : public base, public iterator_mixin> { +template +class integer : public labeled_base, + public iterator_mixin> { + using base_type = labeled_base; + public: + using allocator_type = typename base_type::allocator_type; using value_type = IntType; using bin_type = interval_view; @@ -357,11 +406,9 @@ class integer : public base, public iterator_mixin> { * \param uoflow whether to add under-/overflow bins. */ integer(value_type lower, value_type upper, string_view label = {}, - axis::uoflow uo = axis::uoflow::on) - : base(upper - lower, label, uo), min_(lower) { - if (!(lower < upper)) { - throw std::invalid_argument("lower < upper required"); - } + uoflow_type uo = uoflow_type::on, const allocator_type& a = allocator_type()) + : base_type(upper - lower, uo, label, a), min_(lower) { + if (!(lower < upper)) { throw std::invalid_argument("lower < upper required"); } } integer() = default; @@ -373,20 +420,20 @@ class integer : public base, public iterator_mixin> { /// Returns the bin index for the passed argument. int index(value_type x) const noexcept { const int z = x - min_; - return z >= 0 ? (z > base::size() ? base::size() : z) : -1; + return z >= 0 ? (z > base_type::size() ? base_type::size() : z) : -1; } /// Returns lower edge of the integral bin. value_type lower(int i) const noexcept { if (i < 0) { return -std::numeric_limits::max(); } - if (i > base::size()) { return std::numeric_limits::max(); } + if (i > base_type::size()) { return std::numeric_limits::max(); } return min_ + i; } bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } bool operator==(const integer& o) const noexcept { - return base::operator==(o) && min_ == o.min_; + return base_type::operator==(o) && min_ == o.min_; } private: @@ -397,83 +444,131 @@ class integer : public base, public iterator_mixin> { void serialize(Archive&, unsigned); }; -/** Axis which maps unique single values to bins (one on one). +/** Axis which maps unique values to bins (one on one). * * The axis maps a set of values to bins, following the order of * arguments in the constructor. There is an optional overflow bin * for this axis, which counts values that are not part of the set. - * Binning is a O(1) operation. The value type must be hashable. + * Binning is a O(n) operation for n values in the worst case and O(1) in + * the best case. The value types must be equal-comparable. */ -template -class category : public base, public iterator_mixin> { - using map_type = bimap; +template +class category : public labeled_base, + public iterator_mixin> { + using base_type = labeled_base; public: + using allocator_type = typename base_type::allocator_type; using value_type = T; using bin_type = value_view; - category() = default; - category(const category& rhs) : base(rhs), map_(new map_type(*rhs.map_)) {} - category& operator=(const category& rhs) { - if (this != &rhs) { - base::operator=(rhs); - map_.reset(new map_type(*rhs.map_)); - } - return *this; - } - category(category&& rhs) = default; - category& operator=(category&& rhs) = default; +private: + using value_allocator_type = + typename std::allocator_traits::template rebind_alloc; +public: /** Construct from an initializer list of strings. * * \param seq sequence of unique values. + * \param label description of the axis. + * \param uoflow whether to add under-/overflow bins. */ category(std::initializer_list seq, string_view label = {}, - axis::uoflow uo = axis::uoflow::oflow) - : base(seq.size(), label, - uo == axis::uoflow::on ? axis::uoflow::oflow : uo), - map_(new map_type()) { - int index = 0; - for (const auto& x : seq) map_->insert({x, index++}); - if (index == 0) throw std::invalid_argument("sequence is empty"); - } + uoflow_type uo = uoflow_type::oflow, + const allocator_type& a = allocator_type()) + : category(seq.begin(), seq.end(), label, uo, a) {} template > category(Iterator begin, Iterator end, string_view label = {}, - axis::uoflow uo = axis::uoflow::oflow) - : base(std::distance(begin, end), label, - uo == axis::uoflow::on ? axis::uoflow::oflow : uo), - map_(new map_type()) { - int index = 0; - while (begin != end) map_->insert({*begin++, index++}); - if (index == 0) throw std::invalid_argument("iterator range is empty"); + uoflow_type uo = uoflow_type::oflow, + const allocator_type& a = allocator_type()) + : base_type(std::distance(begin, end), + uo == uoflow_type::on ? uoflow_type::oflow : uo, label, a) { + value_allocator_type a2(a); + using AT = std::allocator_traits; + x_ = AT::allocate(a2, nx()); + auto xit = x_; + while (begin != end) AT::construct(a2, xit++, *begin++); + } + + category() = default; + + category(const category& o) : base_type(o) { + value_allocator_type a(o.get_allocator()); + using AT = std::allocator_traits; + x_ = AT::allocate(a, base_type::size()); + auto it = o.x_; + for (auto xit = x_, xe = xend(); xit != xe; ++xit) AT::construct(a, xit, *it++); + } + + category& operator=(const category& o) { + if (this != &o) { + if (base_type::size() != o.size()) { + this->~category(); + base_type::operator=(o); + value_allocator_type a(base_type::get_allocator()); + using AT = std::allocator_traits; + x_ = AT::allocate(a, nx()); + auto it = o.x_; + for (auto xit = x_, xe = xend(); xit != xe; ++xit) AT::construct(a, xit, *it++); + } else { + base_type::operator=(o); + std::copy(o.x_, o.x_ + o.nx(), x_); + } + } + return *this; + } + + category(category&& o) : base_type(std::move(o)) { + x_ = o.x_; + o.x_ = nullptr; + } + + category& operator=(category&& o) { + this->~category(); + base_type::operator=(std::move(o)); + x_ = o.x_; + o.x_ = nullptr; + return *this; + } + + ~category() { + if (x_) { // nothing to do for empty state + value_allocator_type a(base_type::get_allocator()); + using AT = std::allocator_traits; + for (auto xit = x_, xe = xend(); xit != xe; ++xit) AT::destroy(a, xit); + AT::deallocate(a, x_, nx()); + } } /// Returns the bin index for the passed argument. int index(const value_type& x) const noexcept { - auto it = map_->left.find(x); - if (it == map_->left.end()) return base::size(); - return it->second; + if (last_ < nx() && x_[last_] == x) return last_; + last_ = 0; + for (auto xit = x_, xe = x_ + nx(); xit != xe && !(*xit == x); ++xit) ++last_; + return last_; } /// Returns the value for the bin index (performs a range check). const value_type& value(int idx) const { - auto it = map_->right.find(idx); - if (it == map_->right.end()) + if (idx < 0 || idx >= base_type::size()) throw std::out_of_range("category index out of range"); - return it->second; + return x_[idx]; } bin_type operator[](int idx) const noexcept { return bin_type(idx, *this); } bool operator==(const category& o) const noexcept { - return base::operator==(o) && - std::equal(map_->begin(), map_->end(), o.map_->begin()); + return base_type::operator==(o) && std::equal(x_, x_ + nx(), o.x_); } private: - std::unique_ptr map_; + int nx() const { return base_type::size(); } + value_type* xend() { return x_ + nx(); } + + value_type* x_ = nullptr; + mutable int last_ = 0; friend class ::boost::serialization::access; template diff --git a/include/boost/histogram/detail/axes.hpp b/include/boost/histogram/detail/axes.hpp new file mode 100644 index 000000000..e4224fb66 --- /dev/null +++ b/include/boost/histogram/detail/axes.hpp @@ -0,0 +1,631 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef _BOOST_HISTOGRAM_DETAIL_AXES_HPP_ +#define _BOOST_HISTOGRAM_DETAIL_AXES_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace boost { +namespace histogram { +namespace detail { + +namespace { + +template +struct axes_equal_static_dynamic_impl { + bool& equal; + const Tuple& t; + const Vector& v; + axes_equal_static_dynamic_impl(bool& eq, const Tuple& tt, const Vector& vv) + : equal(eq), t(tt), v(vv) {} + template + void operator()(N) const { + using T = mp11::mp_at; + auto tp = boost::relaxed_get(&v[N::value]); + equal &= (tp && *tp == std::get(t)); + } +}; + +template +bool axes_equal_static_static_impl(mp11::mp_true, const Tuple& t, const Tuple& u) { + return t == u; +} + +template +bool axes_equal_static_static_impl(mp11::mp_false, const Tuple1&, const Tuple2&) { + return false; +} + +template +struct axes_assign_static_dynamic_impl { + Tuple& t; + const Vector& v; + axes_assign_static_dynamic_impl(Tuple& tt, const Vector& vv) : t(tt), v(vv) {} + template + void operator()(N) const { + using T = mp11::mp_at; + std::get(t) = static_cast(v[N::value]); + } +}; + +template +struct axes_assign_dynamic_static_impl { + Vector& v; + const Tuple& t; + axes_assign_dynamic_static_impl(Vector& vv, const Tuple& tt) : v(vv), t(tt) {} + template + void operator()(N) const { + v[N::value] = std::get(t); + } +}; +} // namespace + +template +bool axes_equal(const std::tuple& t, const std::tuple& u) { + return axes_equal_static_static_impl( + mp11::mp_same, mp11::mp_list>(), t, u); +} + +template +bool axes_equal(const std::tuple& t, const std::vector& u) { + if (sizeof...(Ts) != u.size()) return false; + bool equal = true; + auto fn = axes_equal_static_dynamic_impl, std::vector>(equal, + t, u); + mp11::mp_for_each>(fn); + return equal; +} + +template +bool axes_equal(const std::vector& t, const std::tuple& u) { + return axes_equal(u, t); +} + +template +bool axes_equal(const std::vector& t, const std::vector& u) { + if (t.size() != u.size()) return false; + return std::equal(t.begin(), t.end(), u.begin()); +} + +template +void axes_assign(std::tuple& t, const std::tuple& u) { + static_assert(std::is_same, mp11::mp_list>::value, + "cannot assign incompatible axes"); + t = u; +} + +template +void axes_assign(std::tuple& t, const std::vector& u) { + auto fn = + axes_assign_static_dynamic_impl, std::vector>(t, u); + mp11::mp_for_each>(fn); +} + +template +void axes_assign(std::vector& t, const std::tuple& u) { + t.resize(sizeof...(Us)); + auto fn = + axes_assign_dynamic_static_impl, std::tuple>(t, u); + mp11::mp_for_each>(fn); +} + +template +void axes_assign(std::vector& t, const std::vector& u) { + t.assign(u.begin(), u.end()); +} + +template +constexpr std::size_t axes_size(const std::tuple&) { + return sizeof...(Ts); +} + +template +std::size_t axes_size(const std::vector& axes) { + return axes.size(); +} + +template +void range_check(const std::vector& axes) { + BOOST_ASSERT_MSG(N < axes.size(), "index out of range"); +} + +template +void range_check(const std::tuple&) { + static_assert(N < sizeof...(Ts), "index out of range"); +} + +namespace { +template +struct axis_at_impl {}; + +template +struct axis_at_impl> { + using type = mp11::mp_at_c, N>; +}; + +template +struct axis_at_impl> { + using type = Any; +}; +} + +template +using axis_at = typename axis_at_impl::type; + +template +auto axis_get(std::tuple& axes) -> axis_at>& { + return std::get(axes); +} + +template +auto axis_get(const std::tuple& axes) -> const axis_at>& { + return std::get(axes); +} + +template +Any& axis_get(std::vector& axes) { + return axes[N]; +} + +template +const Any& axis_get(const std::vector& axes) { + return axes[N]; +} + +template +void for_each_axis(const std::tuple& axes, F&& f) { + mp11::tuple_for_each(axes, std::forward(f)); +} + +namespace { +template +struct unary_adaptor : public boost::static_visitor { + Unary&& unary; + unary_adaptor(Unary&& u) : unary(std::forward(u)) {} + template + void operator()(const T& a) const { + unary(a); + } +}; +} + +template +void for_each_axis(const std::vector& axes, F&& f) { + for (const auto& x : axes) { + boost::apply_visitor(unary_adaptor(std::forward(f)), x); + } +} + +namespace { +struct field_counter { + std::size_t value = 1; + template + void operator()(const T& t) { + value *= t.shape(); + } +}; +} + +template +std::size_t bincount(const T& axes) { + field_counter fc; + for_each_axis(axes, fc); + return fc.value; +} + +template +void dimension_check(const std::tuple&, mp11::mp_size_t) { + static_assert(sizeof...(Ts) == N, "number of arguments does not match"); +} + +template +void dimension_check(const std::tuple&, std::size_t n) { + BOOST_ASSERT_MSG(sizeof...(Ts) == n, "number of arguments does not match"); +} + +template +void dimension_check(const std::vector& axes, mp11::mp_size_t) { + BOOST_ASSERT_MSG(axes.size() == N, "number of arguments does not match"); + boost::ignore_unused(axes); +} + +template +void dimension_check(const std::vector& axes, std::size_t n) { + BOOST_ASSERT_MSG(axes.size() == n, "number of arguments does not match"); + boost::ignore_unused(axes); + boost::ignore_unused(n); +} + +struct shape_collector { + std::vector::iterator iter; + shape_collector(std::vector::iterator i) : iter(i) {} + template + void operator()(const T& a) { + *iter++ = a.shape(); + } +}; + +namespace { + +template +struct sub_axes_impl {}; + +template +struct sub_axes_impl> { + static_assert(mp11::mp_is_set::value, + "integer arguments must be strictly ascending"); + static_assert(mp_last::value < sizeof...(Ts), "index out of range"); + template + using at = mp11::mp_at, I>; + using L = mp11::mp_rename; + using type = mp11::mp_transform; +}; + +template +struct sub_axes_impl> { + static_assert(mp11::mp_is_set::value, + "integer arguments must be strictly ascending"); + using type = std::vector; +}; +} + +template +using sub_axes = typename sub_axes_impl, T>::type; + +namespace { +template +struct sub_static_assign_impl { + const Src& src; + Dst& dst; + template + void operator()(std::pair) const { + std::get(dst) = std::get(src); + } +}; +} + +template +sub_axes, Ns...> make_sub_axes(const std::tuple& t, Ns...) { + using T = std::tuple; + using U = sub_axes, Ns...>; + U u; + using N1 = mp11::mp_list; + using N2 = mp11::mp_iota>; + using N3 = mp11::mp_transform; + mp11::mp_for_each(sub_static_assign_impl{t, u}); + return u; +} + +namespace { +template +struct sub_dynamic_assign_impl { + const T& src; + T& dst; + template + void operator()(I) const { + dst.emplace_back(src[I::value]); + } +}; +} + +template +sub_axes, Ns...> make_sub_axes(const std::vector& t, Ns...) { + using T = std::vector; + T u(t.get_allocator()); + u.reserve(sizeof...(Ns)); + using N = mp11::mp_list; + mp11::mp_for_each(sub_dynamic_assign_impl{t, u}); + return u; +} + +struct optional_index { + std::size_t idx = 0; + std::size_t stride = 1; + operator bool() const { return stride > 0; } + std::size_t operator*() const { return idx; } +}; + +// the following is highly optimized code that runs in a hot loop; +// please measure the performance impact of changes +inline void linearize(optional_index& out, const int axis_size, const int axis_shape, + int j) noexcept { + BOOST_ASSERT_MSG(out.stride == 0 || (-1 <= j && j <= axis_size), + "index must be in bounds for this algorithm"); + j += (j < 0) * (axis_size + 2); // wrap around if j < 0 + out.idx += j * out.stride; + out.stride *= (j < axis_shape) * axis_shape; // set to 0, if j is invalid +} + +template +void indices_to_index(optional_index&, const Axes&) noexcept {} + +template +void indices_to_index(optional_index& idx, const Axes& axes, const int j, + const Us... us) { + const auto& a = axis_get(axes); + const auto a_size = a.size(); + const auto a_shape = a.shape(); + idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid + linearize(idx, a_size, a_shape, j); + indices_to_index<(D + 1)>(idx, axes, us...); +} + +template +void indices_to_index_iter(mp11::mp_size_t<0>, optional_index&, const std::tuple&, + Iterator) {} + +template +void indices_to_index_iter(mp11::mp_size_t, optional_index& idx, + const std::tuple& axes, Iterator iter) { + constexpr auto D = mp11::mp_size_t() - N; + const auto& a = std::get(axes); + const auto a_size = a.size(); + const auto a_shape = a.shape(); + const auto j = static_cast(*iter); + idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid + linearize(idx, a_size, a_shape, j); + indices_to_index_iter(mp11::mp_size_t<(N - 1)>(), idx, axes, ++iter); +} + +template +void indices_to_index_iter(optional_index& idx, const std::vector& axes, + Iterator iter) { + for (const auto& a : axes) { + const auto a_size = a.size(); + const auto a_shape = a.shape(); + const auto j = static_cast(*iter++); + idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid + linearize(idx, a_size, a_shape, j); + } +} + +template +void indices_to_index_get(mp11::mp_size_t<0>, optional_index&, const Axes&, const T&) {} + +template +void indices_to_index_get(mp11::mp_size_t, optional_index& idx, const Axes& axes, + const T& t) { + constexpr std::size_t D = mp_size() - N; + const auto& a = axis_get(axes); + const auto a_size = a.size(); + const auto a_shape = a.shape(); + const auto j = static_cast(std::get(t)); + idx.stride *= (-1 <= j && j <= a_size); // set to 0, if j is invalid + linearize(idx, a_size, a_shape, j); + indices_to_index_get(mp11::mp_size_t<(N - 1)>(), idx, axes, t); +} + +template +void args_to_index(optional_index&, const std::tuple&) noexcept {} + +template +void args_to_index(optional_index& idx, const std::tuple& axes, const U& u, + const Us&... us) { + const auto a_size = std::get(axes).size(); + const auto a_shape = std::get(axes).shape(); + const auto j = std::get(axes).index(u); + linearize(idx, a_size, a_shape, j); + args_to_index<(D + 1)>(idx, axes, us...); +} + +template +void args_to_index_iter(mp11::mp_size_t<0>, optional_index&, const std::tuple&, + Iterator) {} + +template +void args_to_index_iter(mp11::mp_size_t, optional_index& idx, + const std::tuple& axes, Iterator iter) { + constexpr std::size_t D = sizeof...(Ts)-N; + const auto& a = axis_get(axes); + const auto a_size = a.size(); + const auto a_shape = a.shape(); + const auto j = a.index(*iter); + linearize(idx, a_size, a_shape, j); + args_to_index_iter(mp11::mp_size_t<(N - 1)>(), idx, axes, ++iter); +} + +template +void args_to_index_get(mp11::mp_size_t<0>, optional_index&, const std::tuple&, + const T&) {} + +template +void args_to_index_get(mp11::mp_size_t, optional_index& idx, + const std::tuple& axes, const T& t) { + constexpr std::size_t D = mp_size::value - N; + const auto a_size = std::get(axes).size(); + const auto a_shape = std::get(axes).shape(); + const auto j = std::get(axes).index(std::get(t)); + linearize(idx, a_size, a_shape, j); + args_to_index_get(mp11::mp_size_t<(N - 1)>(), idx, axes, t); +} + +namespace { +template +struct args_to_index_visitor : public boost::static_visitor { + optional_index& idx; + const T& val; + args_to_index_visitor(optional_index& i, const T& v) : idx(i), val(v) {} + template + void operator()(const Axis& a) const { + impl(std::is_convertible(), a); + } + + template + void impl(std::true_type, const Axis& a) const { + const auto a_size = a.size(); + const auto a_shape = a.shape(); + const auto j = a.index(static_cast(val)); + linearize(idx, a_size, a_shape, j); + } + + template + void impl(std::false_type, const Axis&) const { + throw std::invalid_argument(detail::cat( + "axis ", boost::typeindex::type_id().pretty_name(), ": argument ", + boost::typeindex::type_id().pretty_name(), " not convertible to value_type ", + boost::typeindex::type_id().pretty_name())); + } +}; +} + +template +void args_to_index(optional_index&, const std::vector&) {} + +template +void args_to_index(optional_index& idx, const std::vector& axes, const U& u, + const Us&... us) { + boost::apply_visitor(args_to_index_visitor(idx, u), axes[D]); + args_to_index<(D + 1)>(idx, axes, us...); +} + +template +void args_to_index_iter(optional_index& idx, const std::vector& axes, + Iterator iter) { + for (const auto& a : axes) { + // iter could be a plain pointer, so we cannot use nested value_type here + boost::apply_visitor(args_to_index_visitor(idx, *iter++), a); + } +} + +template +void args_to_index_get(mp11::mp_size_t<0>, optional_index&, const std::vector&, + const T&) {} + +template +void args_to_index_get(mp11::mp_size_t, optional_index& idx, + const std::vector& axes, const T& t) { + constexpr std::size_t D = mp_size::value - N; + using U = decltype(std::get(t)); + boost::apply_visitor(args_to_index_visitor(idx, std::get(t)), axes[D]); + args_to_index_get(mp11::mp_size_t<(N - 1)>(), idx, axes, t); +} + +// specialization for one-dimensional histograms +template +optional_index call_impl(Tag, const std::tuple& axes, const Us&... us) { + dimension_check(axes, mp11::mp_size_t()); + optional_index i; + args_to_index<0>(i, axes, us...); + return i; +} + +template +optional_index call_impl(no_container_tag, const std::tuple& axes, + const Us&... us) { + dimension_check(axes, mp11::mp_size_t()); + optional_index i; + args_to_index<0>(i, axes, us...); + return i; +} + +template +optional_index call_impl(static_container_tag, const std::tuple& axes, + const U& u) { + dimension_check(axes, mp_size()); + optional_index i; + args_to_index_get(mp_size(), i, axes, u); + return i; +} + +template +optional_index call_impl(dynamic_container_tag, const std::tuple& axes, + const U& u) { + dimension_check(axes, u.size()); + optional_index i; + args_to_index_iter(mp11::mp_size_t<(2 + sizeof...(Ts))>(), i, axes, std::begin(u)); + return i; +} + +template +optional_index call_impl(no_container_tag, const std::vector& axes, + const Us&... us) { + dimension_check(axes, mp11::mp_size_t()); + optional_index i; + args_to_index<0>(i, axes, us...); + return i; +} + +template +optional_index call_impl(static_container_tag, const std::vector& axes, + const U& u) { + dimension_check(axes, mp_size()); + optional_index i; + args_to_index_get(mp_size(), i, axes, u); + return i; +} + +template +optional_index call_impl(dynamic_container_tag, const std::vector& axes, + const U& u) { + dimension_check(axes, std::distance(std::begin(u), std::end(u))); + optional_index i; + args_to_index_iter(i, axes, std::begin(u)); + return i; +} + +/* In all at_impl, we throw instead of asserting when an index is out of + * bounds, because wrapping code cannot check this condition without spending + * a lot of extra cycles. For the wrapping code it is much easier to catch + * the exception and do something sensible. + */ + +template +std::size_t at_impl(detail::no_container_tag, const A& axes, const Us&... us) { + dimension_check(axes, mp11::mp_size_t()); + auto index = detail::optional_index(); + detail::indices_to_index<0>(index, axes, static_cast(us)...); + if (!index) throw std::out_of_range("indices out of bounds"); + return *index; +} + +template +std::size_t at_impl(detail::static_container_tag, const A& axes, const U& u) { + dimension_check(axes, mp_size()); + auto index = detail::optional_index(); + detail::indices_to_index_get(mp_size(), index, axes, u); + if (!index) throw std::out_of_range("indices out of bounds"); + return *index; +} + +template +std::size_t at_impl(detail::dynamic_container_tag, const std::tuple& axes, + const U& u) { + dimension_check(axes, std::distance(std::begin(u), std::end(u))); + auto index = detail::optional_index(); + detail::indices_to_index_iter(mp11::mp_size_t(), index, axes, + std::begin(u)); + if (!index) throw std::out_of_range("indices out of bounds"); + return *index; +} + +template +std::size_t at_impl(detail::dynamic_container_tag, const std::vector& axes, + const U& u) { + dimension_check(axes, std::distance(std::begin(u), std::end(u))); + auto index = detail::optional_index(); + detail::indices_to_index_iter(index, axes, std::begin(u)); + if (!index) throw std::out_of_range("indices out of bounds"); + return *index; +} + +} // namespace detail +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/detail/axis_visitor.hpp b/include/boost/histogram/detail/axis_visitor.hpp deleted file mode 100644 index a5aca660d..000000000 --- a/include/boost/histogram/detail/axis_visitor.hpp +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2015-2017 Hans Demsizeki -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef _BOOST_HISTOGARM_AXIS_VISITOR_HPP_ -#define _BOOST_HISTOGARM_AXIS_VISITOR_HPP_ - -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace histogram { -namespace detail { - -namespace { - -template -struct axes_equal_tuple_vecvar { - bool& equal; - const Tuple& t; - const VecVar& v; - axes_equal_tuple_vecvar(bool& eq, const Tuple& tt, const VecVar& vv) - : equal(eq), t(tt), v(vv) {} - template - void operator()(Int) const { - using T = mp11::mp_at; - auto tp = ::boost::get(&v[Int::value]); - equal &= (tp && *tp == std::get(t)); - } -}; - -template -struct axes_assign_tuple_vecvar { - Tuple& t; - const VecVar& v; - axes_assign_tuple_vecvar(Tuple& tt, const VecVar& vv) : t(tt), v(vv) {} - template - void operator()(Int) const { - using T = mp11::mp_at; - std::get(t) = ::boost::get(v[Int::value]); - } -}; - -template -struct axes_assign_vecvar_tuple { - VecVar& v; - const Tuple& t; - axes_assign_vecvar_tuple(VecVar& vv, const Tuple& tt) : v(vv), t(tt) {} - template - void operator()(Int) const { - v[Int::value] = std::get(t); - } -}; - -template -bool axes_equal_impl(mp11::mp_true, const std::tuple& t, - const std::tuple& u) { - return t == u; -} - -template -bool axes_equal_impl(mp11::mp_false, const std::tuple&, - const std::tuple&) { - return false; -} - -} // namespace - -template -bool axes_equal(const std::tuple& t, const std::tuple& u) { - return axes_equal_impl( - mp11::mp_same, mp11::mp_list>(), t, u); -} - -template -void axes_assign(std::tuple& t, const std::tuple& u) { - static_assert( - std::is_same, mp11::mp_list>::value, - "cannot assign incompatible axes"); - t = u; -} - -template -bool axes_equal(const std::tuple& t, - const std::vector>& u) { - if (sizeof...(Ts) != u.size()) return false; - bool equal = true; - auto fn = - axes_equal_tuple_vecvar, - std::vector>>(equal, t, u); - mp11::mp_for_each>(fn); - return equal; -} - -template -void axes_assign(std::tuple& t, - const std::vector>& u) { - auto fn = axes_assign_tuple_vecvar, - std::vector>>(t, u); - mp11::mp_for_each>(fn); -} - -template -bool axes_equal(const std::vector>& t, - const std::tuple& u) { - return axes_equal(u, t); -} - -template -void axes_assign(std::vector>& t, - const std::tuple& u) { - t.resize(sizeof...(Us)); - auto fn = axes_assign_vecvar_tuple>, - std::tuple>(t, u); - mp11::mp_for_each>(fn); -} - -template -bool axes_equal(const std::vector>& t, - const std::vector>& u) { - if (t.size() != u.size()) return false; - for (std::size_t i = 0; i < t.size(); ++i) { - if (t[i] != u[i]) return false; - } - return true; -} - -template -void axes_assign(std::vector>& t, - const std::vector>& u) { - for (std::size_t i = 0; i < t.size(); ++i) { t[i] = u[i]; } -} - -struct field_count_visitor : public static_visitor { - std::size_t value = 1; - template - void operator()(const T& t) { - value *= t.shape(); - } -}; - -template -struct unary_visitor : public static_visitor { - Unary& unary; - unary_visitor(Unary& u) : unary(u) {} - template - void operator()(const Axis& a) const { - unary(a); - } -}; - -struct shape_vector_visitor { - std::vector shapes; - std::vector::iterator iter; - shape_vector_visitor(unsigned n) : shapes(n) { iter = shapes.begin(); } - template - void operator()(const Axis& a) { - *iter++ = a.shape(); - } -}; - -} // namespace detail -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/detail/index_cache.hpp b/include/boost/histogram/detail/index_cache.hpp index 1d22c06bb..891102b72 100644 --- a/include/boost/histogram/detail/index_cache.hpp +++ b/include/boost/histogram/detail/index_cache.hpp @@ -13,70 +13,62 @@ namespace boost { namespace histogram { namespace detail { -struct index_cache { - struct dim_t { - int idx, size; - std::size_t stride; - }; + +struct state_t { + unsigned dim; + std::size_t idx; +}; + +struct dim_t { + int idx, size; + std::size_t stride; +}; + +union block_t { + state_t state; + dim_t dim; +}; + +struct index_cache : public std::unique_ptr { + using ptr_t = std::unique_ptr; struct dim_visitor { mutable std::size_t stride; - mutable dim_t* dims; + mutable block_t* b; template void operator()(const Axis& a) const noexcept { - *dims++ = dim_t{0, a.size(), stride}; + b->dim = dim_t{0, a.size(), stride}; + ++b; stride *= a.shape(); } }; - unsigned dim_ = 0; - std::size_t idx_ = 0; - std::unique_ptr dims_; - - index_cache() = default; - index_cache(index_cache&&) = default; - index_cache& operator=(index_cache&&) = default; - - index_cache(const index_cache& o) : dim_(o.dim_), dims_(new dim_t[o.dim_]) { - std::copy(o.dims_.get(), o.dims_.get() + dim_, dims_.get()); - } - - index_cache& operator=(const index_cache& o) { - if (this != &o) { - if (o.dim_ != dim_) { - dim_ = o.dim_; - dims_.reset(new dim_t[dim_]); - } - std::copy(o.dims_.get(), o.dims_.get() + dim_, dims_.get()); - } - return *this; - } - template - void reset(const H& h) { - if (h.dim() != dim_) { - dim_ = h.dim(); - dims_.reset(new dim_t[dim_]); + void set(const H& h) { + if (!(*this) || h.dim() != ptr_t::get()->state.dim) { + ptr_t::reset(new block_t[h.dim() + 1]); + ptr_t::get()->state.dim = h.dim(); + ptr_t::get()->state.idx = 0; } - h.for_each_axis(dim_visitor{1, dims_.get()}); + h.for_each_axis(dim_visitor{1, ptr_t::get() + 1}); } - void operator()(std::size_t idx) { - if (idx == idx_) return; - idx_ = idx; - auto dim_ptr = dims_.get(); - auto dim = dim_; - dim_ptr += dim; - while ((--dim_ptr, --dim)) { - dim_ptr->idx = idx / dim_ptr->stride; - idx -= dim_ptr->idx * dim_ptr->stride; - dim_ptr->idx -= (dim_ptr->idx > dim_ptr->size) * (dim_ptr->size + 2); + void set_idx(std::size_t idx) { + auto& s = ptr_t::get()->state; + if (idx == s.idx) return; + s.idx = idx; + auto d = s.dim; + auto b = (ptr_t::get() + 1) + d; + while ((--b, --d)) { + b->dim.idx = idx / b->dim.stride; + idx -= b->dim.idx * b->dim.stride; + b->dim.idx -= (b->dim.idx > b->dim.size) * (b->dim.size + 2); } - dim_ptr->idx = idx; - dim_ptr->idx -= (dim_ptr->idx > dim_ptr->size) * (dim_ptr->size + 2); + b->dim.idx = idx; + b->dim.idx -= (b->dim.idx > b->dim.size) * (b->dim.size + 2); } - int operator[](unsigned dim) const { return dims_[dim].idx; } + int get(unsigned d) const { return (ptr_t::get() + 1 + d)->dim.idx; } }; } } diff --git a/include/boost/histogram/detail/meta.hpp b/include/boost/histogram/detail/meta.hpp index a1f75ebb2..0893d228b 100644 --- a/include/boost/histogram/detail/meta.hpp +++ b/include/boost/histogram/detail/meta.hpp @@ -7,8 +7,8 @@ #ifndef _BOOST_HISTOGRAM_DETAIL_META_HPP_ #define _BOOST_HISTOGRAM_DETAIL_META_HPP_ +#include #include - #include #include #include @@ -35,19 +35,17 @@ namespace detail { using name = typename name##_impl::type BOOST_HISTOGRAM_MAKE_SFINAE(has_variance_support, - (std::declval().value(), - std::declval().variance())); + (std::declval().value(), std::declval().variance())); BOOST_HISTOGRAM_MAKE_SFINAE(has_method_lower, (std::declval().lower(0))); -BOOST_HISTOGRAM_MAKE_SFINAE(is_dynamic_container, - (std::begin(std::declval()))); +BOOST_HISTOGRAM_MAKE_SFINAE(is_dynamic_container, (std::begin(std::declval()))); + +BOOST_HISTOGRAM_MAKE_SFINAE(is_static_container, (std::get<0>(std::declval()))); -BOOST_HISTOGRAM_MAKE_SFINAE(is_static_container, - (std::get<0>(std::declval()))); +BOOST_HISTOGRAM_MAKE_SFINAE(is_castable_to_int, (static_cast(std::declval()))); -BOOST_HISTOGRAM_MAKE_SFINAE(is_castable_to_int, - (static_cast(std::declval()))); +BOOST_HISTOGRAM_MAKE_SFINAE(is_string, (std::declval().c_str())); struct static_container_tag {}; struct dynamic_container_tag {}; @@ -56,24 +54,29 @@ struct no_container_tag {}; template using classify_container = typename std::conditional< is_static_container::value, static_container_tag, - typename std::conditional::value, - dynamic_container_tag, - no_container_tag>::type>::type; + typename std::conditional<(is_dynamic_container::value && + !std::is_convertible::value && + !is_string::value), + dynamic_container_tag, no_container_tag>::type>::type; -template ().size(), - std::declval().increase(0), - std::declval()[0])> +template ().size(), std::declval().increase(0), + std::declval()[0])> struct requires_storage {}; -template (), ++std::declval())> +template (), ++std::declval())> struct requires_iterator {}; +template ()[0])> +struct requires_vector {}; + +template (std::declval()))> +struct requires_tuple {}; + template -using requires_axis = - decltype(std::declval().size(), std::declval().shape(), - std::declval().uoflow(), std::declval().label(), - std::declval()[0]); +using requires_axis = decltype(std::declval().size(), std::declval().shape(), + std::declval().uoflow(), std::declval().label(), + std::declval()[0]); namespace { struct bool_mask_impl { @@ -94,8 +97,7 @@ std::vector bool_mask(unsigned n, bool v) { } template -using rm_cv_ref = - typename std::remove_cv::type>::type; +using rm_cv_ref = typename std::remove_cv::type>::type; template using mp_size = mp11::mp_size>; @@ -106,56 +108,19 @@ using mp_at_c = mp11::mp_at_c, D>; template using copy_qualifiers = mp11::mp_if< std::is_rvalue_reference, T2&&, - mp11::mp_if< - std::is_lvalue_reference, - mp11::mp_if::type>, - const T2&, T2&>, - mp11::mp_if, const T2, T2>>>; + mp11::mp_if, + mp11::mp_if::type>, + const T2&, T2&>, + mp11::mp_if, const T2, T2>>>; template -using mp_set_union = - mp11::mp_apply_q, L>; - -namespace { -template -struct selection_impl { - template - using at = mp11::mp_at; - using N = mp11::mp_list; - using LNs = mp11::mp_assign; - using type = mp11::mp_transform; -}; -} - -template -using selection = typename selection_impl::type; +using mp_set_union = mp11::mp_apply_q, L>; -template -using unique_sorted = mp11::mp_unique>; +template +using arg_type = mp11::mp_at_c, N>; -namespace { -template -struct sub_tuple_assign_impl { - const Src& src; - Dst& dst; - template - void operator()(std::pair) const { - std::get(dst) = std::get(src); - } -}; -} - -template -selection make_sub_tuple(const T& t) { - using U = selection; - U u; - using N1 = mp11::mp_list; - using Len = mp11::mp_size; - using N2 = mp11::mp_iota; - using N3 = mp11::mp_transform; - mp11::mp_for_each(sub_tuple_assign_impl{t, u}); - return u; -} +template +using mp_last = mp11::mp_at_c::value - 1)>; } // namespace detail } // namespace histogram diff --git a/include/boost/histogram/detail/utility.hpp b/include/boost/histogram/detail/utility.hpp deleted file mode 100644 index e640fb0c4..000000000 --- a/include/boost/histogram/detail/utility.hpp +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef _BOOST_HISTOGRAM_DETAIL_UTILITY_HPP_ -#define _BOOST_HISTOGRAM_DETAIL_UTILITY_HPP_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace boost { -namespace histogram { -namespace detail { - -// two_pi can be found in boost/math, but it is defined here to reduce deps -constexpr double two_pi = 6.283185307179586; - -inline void escape(std::ostream& os, const string_view s) { - os << '\''; - for (auto sit = s.begin(); sit != s.end(); ++sit) { - if (*sit == '\'' && (sit == s.begin() || *(sit - 1) != '\\')) { - os << "\\\'"; - } else { - os << *sit; - } - } - os << '\''; -} - -// the following is highly optimized code that runs in a hot loop; -// please measure the performance impact of changes -inline void lin(std::size_t& out, std::size_t& stride, const int axis_size, - const int axis_shape, int j) noexcept { - BOOST_ASSERT_MSG(stride == 0 || (-1 <= j && j <= axis_size), - "index must be in bounds for this algorithm"); - j += (j < 0) * (axis_size + 2); // wrap around if j < 0 - out += j * stride; - stride *= - (j < axis_shape) * axis_shape; // stride == 0 indicates out-of-range -} - -template -typename std::enable_if<(is_castable_to_int::value), int>::type -indirect_int_cast(T&& t) noexcept { - return static_cast(std::forward(t)); -} - -template -typename std::enable_if::value), int>::type -indirect_int_cast(T&&) noexcept { - // Cannot use static_assert here, because this function is created as a - // side-effect of TMP. It must be valid at compile-time. - BOOST_ASSERT_MSG(false, "bin argument not convertible to int"); - return 0; -} - -template -void fill_storage(S& s, std::size_t idx, weight&& w) { - s.add(idx, w); -} - -template -void fill_storage(S& s, std::size_t idx) { - s.increase(idx); -} - -template -auto storage_get(const S& s, std::size_t idx, bool error) -> - typename S::const_reference { - if (error) throw std::out_of_range("bin index out of range"); - return s[idx]; -} - -} // namespace detail -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/dynamic_histogram.hpp b/include/boost/histogram/dynamic_histogram.hpp deleted file mode 100644 index 2afeb7f91..000000000 --- a/include/boost/histogram/dynamic_histogram.hpp +++ /dev/null @@ -1,518 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef _BOOST_HISTOGRAM_HISTOGRAM_DYNAMIC_IMPL_HPP_ -#define _BOOST_HISTOGRAM_HISTOGRAM_DYNAMIC_IMPL_HPP_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// forward declaration for serialization -namespace boost { -namespace serialization { -class access; -} -} // namespace boost - -// forward declaration for python -namespace boost { -namespace python { -class access; -} -} // namespace boost - -namespace boost { -namespace histogram { - -template -class histogram { - static_assert(mp11::mp_size::value > 0, "at least one axis required"); - -public: - using any_axis_type = mp11::mp_rename; - using axes_type = std::vector; - using element_type = typename Storage::element_type; - using const_reference = typename Storage::const_reference; - using const_iterator = iterator_over; - using iterator = const_iterator; - -public: - histogram() = default; - histogram(const histogram&) = default; - histogram(histogram&&) = default; - histogram& operator=(const histogram&) = default; - histogram& operator=(histogram&&) = default; - - template > - explicit histogram(Axis0&& axis0, Axis&&... axis) - : axes_({any_axis_type(std::forward(axis0)), - any_axis_type(std::forward(axis))...}) { - storage_ = Storage(size_from_axes()); - index_cache_.reset(*this); - } - - explicit histogram(axes_type&& axes) : axes_(std::move(axes)) { - storage_ = Storage(size_from_axes()); - index_cache_.reset(*this); - } - - template > - histogram(Iterator begin, Iterator end) : axes_(std::distance(begin, end)) { - std::copy(begin, end, axes_.begin()); - storage_ = Storage(size_from_axes()); - index_cache_.reset(*this); - } - - template - explicit histogram(const histogram& rhs) : storage_(rhs.storage_) { - detail::axes_assign(axes_, rhs.axes_); - index_cache_.reset(*this); - } - - template - histogram& operator=(const histogram& rhs) { - if (static_cast(this) != static_cast(&rhs)) { - detail::axes_assign(axes_, rhs.axes_); - storage_ = rhs.storage_; - index_cache_.reset(*this); - } - return *this; - } - - template - explicit histogram(dynamic_histogram&& rhs) - : axes_(std::move(rhs.axes_)), storage_(std::move(rhs.storage_)) { - index_cache_.reset(*this); - } - - template - histogram& operator=(dynamic_histogram&& rhs) { - if (static_cast(this) != static_cast(&rhs)) { - axes_ = std::move(rhs.axes_); - storage_ = std::move(rhs.storage_); - index_cache_.reset(*this); - } - return *this; - } - - template - bool operator==(const histogram& rhs) const noexcept { - return detail::axes_equal(axes_, rhs.axes_) && storage_ == rhs.storage_; - } - - template - bool operator!=(const histogram& rhs) const noexcept { - return !operator==(rhs); - } - - template - histogram& operator+=(const histogram& rhs) { - if (!detail::axes_equal(axes_, rhs.axes_)) - throw std::invalid_argument("axes of histograms differ"); - storage_ += rhs.storage_; - return *this; - } - - template - histogram& operator*=(const T& rhs) { - storage_ *= rhs; - return *this; - } - - template - histogram& operator/=(const T& rhs) { - storage_ *= 1.0 / rhs; - return *this; - } - - template - void operator()(Ts&&... ts) { - // case with one argument is ambiguous, is specialized below - BOOST_ASSERT_MSG(dim() == sizeof...(Ts), - "fill arguments does not match histogram dimension " - "(did you use weight() in the wrong place?)"); - std::size_t idx = 0, stride = 1; - xlin<0>(idx, stride, std::forward(ts)...); - if (stride) { detail::fill_storage(storage_, idx); } - } - - template - void operator()(T&& t) { - // check whether T is unpackable - if (dim() == 1) { - fill_impl(detail::no_container_tag(), std::forward(t)); - } else { - fill_impl(detail::classify_container(), std::forward(t)); - } - } - - template - void operator()(detail::weight&& w, Ts&&... ts) { - // case with one argument is ambiguous, is specialized below - BOOST_ASSERT_MSG(dim() == sizeof...(Ts), - "fill arguments does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin<0>(idx, stride, std::forward(ts)...); - if (stride) { detail::fill_storage(storage_, idx, std::move(w)); } - } - - template - void operator()(detail::weight&& w, T&& t) { - // check whether T is unpackable - if (dim() == 1) { - fill_impl(detail::no_container_tag(), std::forward(t), std::move(w)); - } else { - fill_impl(detail::classify_container(), std::forward(t), - std::move(w)); - } - } - - template - const_reference at(const Ts&... ts) const { - // case with one argument is ambiguous, is specialized below - BOOST_ASSERT_MSG(dim() == sizeof...(Ts), - "bin arguments does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - lin<0>(idx, stride, static_cast(ts)...); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - const_reference at(const T& t) const { - // check whether T is unpackable - return at_impl(detail::classify_container(), t); - } - - template - const_reference operator[](const T& t) const { - return at(t); - } - - /// Number of axes (dimensions) of histogram - unsigned dim() const noexcept { return axes_.size(); } - - /// Total number of bins in the histogram (including underflow/overflow) - std::size_t size() const noexcept { return storage_.size(); } - - /// Reset bin counters to zero - void reset() { storage_ = Storage(size_from_axes()); } - - /// Return axis \a i - any_axis_type& axis(unsigned i = 0) { - BOOST_ASSERT_MSG(i < dim(), "axis index out of range"); - return axes_[i]; - } - - /// Return axis \a i (const version) - const any_axis_type& axis(unsigned i = 0) const { - BOOST_ASSERT_MSG(i < dim(), "axis index out of range"); - return axes_[i]; - } - - /// Apply unary functor/function to each axis - template - void for_each_axis(Unary&& unary) const { - for (const auto& a : axes_) { - apply_visitor(detail::unary_visitor(unary), a); - } - } - - /// Return a lower dimensional histogram - template - histogram reduce_to(mp11::mp_int, Ts...) const { - const auto b = detail::bool_mask, Ts...>(dim(), true); - return reduce_impl(b); - } - - /// Return a lower dimensional histogram - template - histogram reduce_to(int n, Ts... ts) const { - std::vector b(dim(), false); - for (const auto& i : {n, int(ts)...}) b[i] = true; - return reduce_impl(b); - } - - /// Return a lower dimensional histogram - template > - histogram reduce_to(Iterator begin, Iterator end) const { - std::vector b(dim(), false); - for (; begin != end; ++begin) b[*begin] = true; - return reduce_impl(b); - } - - const_iterator begin() const noexcept { return const_iterator(*this, 0); } - - const_iterator end() const noexcept { - return const_iterator(*this, storage_.size()); - } - -private: - axes_type axes_; - Storage storage_; - mutable detail::index_cache index_cache_; - - std::size_t size_from_axes() const noexcept { - detail::field_count_visitor v; - for_each_axis(v); - return v.value; - } - - template - void fill_impl(detail::dynamic_container_tag, T&& t, Ts&&... ts) { - BOOST_ASSERT_MSG(dim() == std::distance(std::begin(t), std::end(t)), - "fill container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin_iter(idx, stride, std::begin(t)); - if (stride) { - detail::fill_storage(storage_, idx, std::forward(ts)...); - } - } - - template - void fill_impl(detail::static_container_tag, T&& t, Ts&&... ts) { - BOOST_ASSERT_MSG(dim() == detail::mp_size::value, - "fill container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin_get(mp11::mp_int::value>(), idx, stride, - std::forward(t)); - if (stride) { - detail::fill_storage(storage_, idx, std::forward(ts)...); - } - } - - template - void fill_impl(detail::no_container_tag, T&& t, Ts&&... ts) { - BOOST_ASSERT_MSG(dim() == 1, - "fill argument does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin<0>(idx, stride, t); - if (stride) { - detail::fill_storage(storage_, idx, std::forward(ts)...); - } - } - - template - const_reference at_impl(detail::dynamic_container_tag, const T& t) const { - BOOST_ASSERT_MSG(dim() == std::distance(std::begin(t), std::end(t)), - "bin container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - lin_iter(idx, stride, std::begin(t)); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - const_reference at_impl(detail::static_container_tag, const T& t) const { - BOOST_ASSERT_MSG(dim() == detail::mp_size::value, - "bin container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - lin_get(mp11::mp_size(), idx, stride, t); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - const_reference at_impl(detail::no_container_tag, const T& t) const { - BOOST_ASSERT_MSG(dim() == 1, - "bin argument does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - lin<0>(idx, stride, detail::indirect_int_cast(t)); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - struct xlin_visitor : public static_visitor { - std::size_t& idx; - std::size_t& stride; - const Value& val; - xlin_visitor(std::size_t& i, std::size_t& s, const Value& v) - : idx(i), stride(s), val(v) {} - template - void operator()(const Axis& a) const { - impl(std::is_convertible(), a); - } - - template - void impl(std::true_type, const Axis& a) const { - const auto a_size = a.size(); - const auto a_shape = a.shape(); - const auto j = a.index(static_cast(val)); - detail::lin(idx, stride, a_size, a_shape, j); - } - - template - void impl(std::false_type, const Axis&) const { - throw std::invalid_argument( - detail::cat("fill argument not convertible to axis value type: ", - boost::typeindex::type_id().pretty_name(), ", ", - boost::typeindex::type_id().pretty_name())); - } - }; - - template - void xlin(std::size_t&, std::size_t&) const {} - - template - void xlin(std::size_t& idx, std::size_t& stride, T&& t, Ts&&... ts) const { - apply_visitor(xlin_visitor{idx, stride, t}, axes_[D]); - xlin<(D + 1)>(idx, stride, std::forward(ts)...); - } - - template - void xlin_iter(std::size_t& idx, std::size_t& stride, Iterator iter) const { - for (const auto& a : axes_) { - apply_visitor(xlin_visitor{idx, stride, *iter++}, a); - } - } - - template - void xlin_get(mp11::mp_int<0>, std::size_t&, std::size_t&, T&&) const - noexcept {} - - template - void xlin_get(mp11::mp_int, std::size_t& idx, std::size_t& stride, - T&& t) const { - constexpr unsigned D = detail::mp_size::value - N; - apply_visitor( - xlin_visitor>{idx, stride, std::get(t)}, - axes_[D]); - xlin_get(mp11::mp_int<(N - 1)>(), idx, stride, std::forward(t)); - } - - template - void lin(std::size_t&, std::size_t&) const noexcept {} - - template - void lin(std::size_t& idx, std::size_t& stride, int j, Ts... ts) const - noexcept { - const auto& a = axes_[D]; - const auto a_size = a.size(); - const auto a_shape = a.shape(); - stride *= (-1 <= j && j <= a_size); // set stride to zero, if j is invalid - detail::lin(idx, stride, a_size, a_shape, j); - lin<(D + 1)>(idx, stride, ts...); - } - - template - void lin_iter(std::size_t& idx, std::size_t& stride, Iterator iter) const - noexcept { - for (const auto& a : axes_) { - const auto a_size = a.size(); - const auto a_shape = a.shape(); - const auto j = detail::indirect_int_cast(*iter++); - stride *= - (-1 <= j && j <= a_size); // set stride to zero, if j is invalid - detail::lin(idx, stride, a_size, a_shape, j); - } - } - - template - void lin_get(mp11::mp_size_t<0>, std::size_t&, std::size_t&, const T&) const - noexcept {} - - template - void lin_get(mp11::mp_size_t, std::size_t& idx, std::size_t& stride, - const T& t) const noexcept { - constexpr long unsigned int D = detail::mp_size::value - N; - const auto& a = axes_[D]; - const auto a_size = a.size(); - const auto a_shape = a.shape(); - const auto j = detail::indirect_int_cast(std::get(t)); - stride *= (-1 <= j && j <= a_size); // set stride to zero, if j is invalid - detail::lin(idx, stride, a_size, a_shape, j); - lin_get(mp11::mp_size_t<(N - 1)>(), idx, stride, t); - } - - histogram reduce_impl(const std::vector& b) const { - axes_type axes; - std::vector n(b.size()); - auto axes_iter = axes_.begin(); - auto n_iter = n.begin(); - for (const auto& bi : b) { - if (bi) axes.emplace_back(*axes_iter); - *n_iter = axes_iter->shape(); - ++axes_iter; - ++n_iter; - } - histogram h(std::move(axes)); - detail::index_mapper m(n, b); - do { h.storage_.add(m.second, storage_[m.first]); } while (m.next()); - return h; - } - - template - friend class histogram; - template - friend class iterator_over; - friend class ::boost::python::access; - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -template -dynamic_histogram< - mp11::mp_set_push_back...>> -make_dynamic_histogram(Axis&&... axis) { - return dynamic_histogram< - mp11::mp_set_push_back...>>( - std::forward(axis)...); -} - -template -dynamic_histogram< - mp11::mp_set_push_back...>, Storage> -make_dynamic_histogram_with(Axis&&... axis) { - return dynamic_histogram< - mp11::mp_set_push_back...>, - Storage>(std::forward(axis)...); -} - -template > -dynamic_histogram< - detail::mp_set_union> -make_dynamic_histogram(Iterator begin, Iterator end) { - return dynamic_histogram>(begin, end); -} - -template > -dynamic_histogram< - detail::mp_set_union, - Storage> -make_dynamic_histogram_with(Iterator begin, Iterator end) { - return dynamic_histogram< - detail::mp_set_union, - Storage>(begin, end); -} - -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/histogram.hpp b/include/boost/histogram/histogram.hpp new file mode 100644 index 000000000..07187f53c --- /dev/null +++ b/include/boost/histogram/histogram.hpp @@ -0,0 +1,333 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef _BOOST_HISTOGRAM_HISTOGRAM_HPP_ +#define _BOOST_HISTOGRAM_HISTOGRAM_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// forward declaration for serialization +namespace boost { +namespace serialization { +class access; +} +} + +namespace boost { +namespace histogram { + +template +class histogram { + static_assert(mp11::mp_size::value > 0, "at least one axis required"); + +public: + using axes_type = Axes; + using storage_type = Storage; + using element_type = typename storage_type::element_type; + using scale_type = detail::arg_type<1, decltype(&Storage::operator*=)>; + using const_reference = typename storage_type::const_reference; + using const_iterator = iterator_over; + + histogram() = default; + histogram(const histogram& rhs) = default; + histogram(histogram&& rhs) = default; + histogram& operator=(const histogram& rhs) = default; + histogram& operator=(histogram&& rhs) = default; + + template + explicit histogram(const histogram& rhs) : storage_(rhs.storage_) { + detail::axes_assign(axes_, rhs.axes_); + } + + template + histogram& operator=(const histogram& rhs) { + storage_ = rhs.storage_; + detail::axes_assign(axes_, rhs.axes_); + return *this; + } + + explicit histogram(const axes_type& a, const storage_type& s = storage_type()) + : axes_(a), storage_(s) { + storage_.reset(detail::bincount(axes_)); + } + + explicit histogram(axes_type&& a, storage_type&& s = storage_type()) + : axes_(std::move(a)), storage_(std::move(s)) { + storage_.reset(detail::bincount(axes_)); + } + + template + bool operator==(const histogram& rhs) const noexcept { + return detail::axes_equal(axes_, rhs.axes_) && storage_ == rhs.storage_; + } + + template + bool operator!=(const histogram& rhs) const noexcept { + return !operator==(rhs); + } + + template + histogram& operator+=(const histogram& rhs) { + if (!detail::axes_equal(axes_, rhs.axes_)) + throw std::invalid_argument("axes of histograms differ"); + storage_ += rhs.storage_; + return *this; + } + + histogram& operator*=(const scale_type rhs) { + storage_ *= rhs; + return *this; + } + + histogram& operator/=(const scale_type rhs) { + static_assert(std::is_floating_point::value, + "division requires a floating point type"); + storage_ *= scale_type(1) / rhs; + return *this; + } + + /// Number of axes (dimensions) of histogram + std::size_t dim() const noexcept { return detail::axes_size(axes_); } + + /// Total number of bins in the histogram (including underflow/overflow) + std::size_t size() const noexcept { return storage_.size(); } + + /// Reset bin counters to zero + void reset() { storage_.reset(storage_.size()); } + + /// Get N-th axis (const version) + template + auto axis(mp11::mp_size_t) const -> const detail::axis_at& { + detail::range_check(axes_); + return detail::axis_get(axes_); + } + + /// Get N-th axis + template + auto axis(mp11::mp_size_t) -> detail::axis_at& { + detail::range_check(axes_); + return detail::axis_get(axes_); + } + + /// Get first axis (convenience for 1-d histograms, const version) + const detail::axis_at<0, axes_type>& axis() const { return axis(mp11::mp_size_t<0>()); } + + /// Get first axis (convenience for 1-d histograms) + detail::axis_at<0, axes_type>& axis() { return axis(mp11::mp_size_t<0>()); } + + /// Get N-th axis with runtime index (const version) + template > + const detail::axis_at<0, U>& axis(std::size_t i) const { + BOOST_ASSERT_MSG(i < axes_.size(), "index out of range"); + return axes_[i]; + } + + /// Get N-th axis with runtime index + template > + detail::axis_at<0, U>& axis(std::size_t i) { + BOOST_ASSERT_MSG(i < axes_.size(), "index out of range"); + return axes_[i]; + } + + /// Apply unary functor/function to each axis + template + void for_each_axis(Unary&& unary) const { + detail::for_each_axis(axes_, std::forward(unary)); + } + + /// Fill histogram with a value tuple + template + void operator()(const Ts&... ts) { + // case with one argument needs special treatment, specialized below + const auto index = detail::call_impl(detail::no_container_tag(), axes_, ts...); + if (index) storage_.increase(*index); + } + + /// Fill histogram with a weight and a value tuple + template + void operator()(detail::weight_type&& w, const Ts&... ts) { + // case with one argument needs special treatment, specialized below + const auto index = detail::call_impl(detail::no_container_tag(), axes_, ts...); + if (index) storage_.add(*index, w); + } + + /// Access bin counter at indices + template + const_reference at(const Ts&... ts) const { + // case with one argument is ambiguous, is specialized below + const auto index = + detail::at_impl(detail::no_container_tag(), axes_, static_cast(ts)...); + return storage_[index]; + } + + template + void operator()(const T& t) { + // check whether we need to unpack argument + const auto index = detail::call_impl(detail::classify_container(), axes_, t); + if (index) storage_.increase(*index); + } + + template + void operator()(detail::weight_type&& w, const T& t) { + // check whether we need to unpack argument + const auto index = detail::call_impl(detail::classify_container(), axes_, t); + if (index) storage_.add(*index, w); + } + + template + const_reference at(const T& t) const { + // check whether we need to unpack argument; + return storage_[detail::at_impl(detail::classify_container(), axes_, t)]; + } + + /// Access bin counter at index + template + const_reference operator[](const T& t) const { + return at(t); + } + + /// Returns a lower-dimensional histogram + // precondition: argument sequence must be strictly ascending axis indices + template + auto reduce_to(mp11::mp_size_t, Ns...) const + -> histogram, Ns...>, storage_type> { + using N = mp11::mp_size_t; + using LN = mp11::mp_list; + detail::range_check::value>(axes_); + using sub_axes_type = detail::sub_axes; + using HR = histogram; + auto sub_axes = detail::make_sub_axes(axes_, N(), Ns()...); + auto hr = HR(std::move(sub_axes), storage_type(storage_.get_allocator())); + const auto b = detail::bool_mask(dim(), true); + std::vector shape(dim()); + for_each_axis(detail::shape_collector(shape.begin())); + detail::index_mapper m(shape, b); + do { hr.storage_.add(m.second, storage_[m.first]); } while (m.next()); + return hr; + } + + /// Returns a lower-dimensional histogram + // precondition: sequence must be strictly ascending axis indices + template , + typename = detail::requires_iterator> + histogram reduce_to(Iterator begin, Iterator end) const { + BOOST_ASSERT_MSG(std::is_sorted(begin, end, std::less_equal()), + "integer sequence must be strictly ascending"); + BOOST_ASSERT_MSG(begin == end || static_cast(*(end - 1)) < dim(), + "index out of range"); + auto sub_axes = histogram::axes_type(axes_.get_allocator()); + sub_axes.reserve(std::distance(begin, end)); + auto b = std::vector(dim(), false); + for (auto it = begin; it != end; ++it) { + sub_axes.push_back(axes_[*it]); + b[*it] = true; + } + auto hr = histogram(std::move(sub_axes), storage_type(storage_.get_allocator())); + std::vector shape(dim()); + for_each_axis(detail::shape_collector(shape.begin())); + detail::index_mapper m(shape, b); + do { hr.storage_.add(m.second, storage_[m.first]); } while (m.next()); + return hr; + } + + const_iterator begin() const noexcept { return const_iterator(*this, 0); } + + const_iterator end() const noexcept { return const_iterator(*this, size()); } + +private: + axes_type axes_; + Storage storage_; + + template + friend class histogram; + template + friend class iterator_over; + friend class python_access; + friend class ::boost::serialization::access; + template + void serialize(Archive&, unsigned); +}; + +/// static type factory with custom storage type +template +histogram...>, detail::rm_cv_ref> +make_static_histogram_with(Storage&& s, Ts&&... axis) { + using H = histogram...>, detail::rm_cv_ref>; + auto axes = typename H::axes_type(std::forward(axis)...); + return H(std::move(axes), std::forward(s)); +} + +/// static type factory with standard storage type +template +histogram...>> make_static_histogram(Ts&&... axis) { + using S = typename histogram...>>::storage_type; + return make_static_histogram_with(S(), std::forward(axis)...); +} + +namespace detail { + template + using srebind = typename std::allocator_traits::allocator_type>::template rebind_alloc; +} + +/// dynamic type factory with custom storage type +template +histogram>, detail::rm_cv_ref> +make_dynamic_histogram_with(Storage&& s, T&& axis0, Ts&&... axis) { + using H = histogram>, detail::rm_cv_ref>; + auto axes = typename H::axes_type( + {Any(std::forward(axis0)), Any(std::forward(axis))...}, s.get_allocator()); + return H(std::move(axes), std::forward(s)); +} + +/// dynamic type factory with standard storage type +template +histogram> +make_dynamic_histogram(T&& axis0, Ts&&... axis) { + using S = typename histogram>::storage_type; + return make_dynamic_histogram_with(S(), std::forward(axis0), std::forward(axis)...); +} + +/// dynamic type factory with custom storage type +template > +histogram>, detail::rm_cv_ref> +make_dynamic_histogram_with(Storage&& s, Iterator begin, Iterator end) { + using H = histogram>, detail::rm_cv_ref> +; + auto axes = typename H::axes_type(s.get_allocator()); + axes.reserve(std::distance(begin, end)); + while (begin != end) + axes.emplace_back(*begin++); + return H(std::move(axes), std::forward(s)); +} + +/// dynamic type factory with standard storage type +template > +histogram> +make_dynamic_histogram(Iterator begin, Iterator end) { + using S = typename histogram>::storage_type; + return make_dynamic_histogram_with(S(), begin, end); +} +} // namespace histogram +} // namespace boost + +#endif diff --git a/include/boost/histogram/histogram_fwd.hpp b/include/boost/histogram/histogram_fwd.hpp index 95001d7d9..df4f9b761 100644 --- a/include/boost/histogram/histogram_fwd.hpp +++ b/include/boost/histogram/histogram_fwd.hpp @@ -7,17 +7,13 @@ #ifndef _BOOST_HISTOGRAM_HISTOGRAM_FWD_HPP_ #define _BOOST_HISTOGRAM_HISTOGRAM_FWD_HPP_ -#include +#include // for std::allocator +#include #include -#include namespace boost { namespace histogram { -class adaptive_storage; -template -class array_storage; - namespace axis { namespace transform { @@ -27,68 +23,38 @@ struct sqrt; struct pow; } // namespace transform -template +template > class regular; -template +template > class circular; -template +template > class variable; -template +template > class integer; -template +template > class category; -using types = mp11::mp_list, - axis::regular, - axis::regular, - axis::regular, - axis::circular, axis::variable, - axis::integer, axis::category, - axis::category>; - template class any; -using any_std = mp11::mp_rename; +using any_std = + any>, + regular>, + regular>, + regular>, + circular>, variable>, + integer>, category>, + category>>; } // namespace axis -struct dynamic_tag {}; -struct static_tag {}; -template -class histogram; - -template -using dynamic_histogram = histogram; - -template -using static_histogram = histogram; - -namespace detail { -template -struct weight { - T value; -}; -// template struct is_weight : std::false_type {}; -// template struct is_weight> : std::true_type {}; - -template -struct sample { - T value; -}; -// template struct is_sample : std::false_type {}; -// template struct is_sample> : std::true_type {}; -} // namespace detail - -template -detail::weight weight(T&& t) { - return {t}; -} +template > +class adaptive_storage; +template > +class array_storage; -template -detail::sample sample(T&& t) { - return {t}; -} +template , class Storage = adaptive_storage<>> +class histogram; } // namespace histogram } // namespace boost diff --git a/include/boost/histogram/iterator.hpp b/include/boost/histogram/iterator.hpp index 1f0794f30..4572111de 100644 --- a/include/boost/histogram/iterator.hpp +++ b/include/boost/histogram/iterator.hpp @@ -7,50 +7,50 @@ #ifndef _BOOST_HISTOGRAM_VALUE_ITERATOR_HPP_ #define _BOOST_HISTOGRAM_VALUE_ITERATOR_HPP_ -#include -#include +#include #include #include #include -#include -#include -#include namespace boost { namespace histogram { template class iterator_over - : public iterator_facade< - iterator_over, typename Histogram::element_type, - random_access_traversal_tag, typename Histogram::const_reference> { + : public iterator_facade, typename Histogram::element_type, + random_access_traversal_tag, + typename Histogram::const_reference> { public: - iterator_over(const Histogram& h, std::size_t idx) - : histogram_(h), idx_(idx) {} + iterator_over(const Histogram& h, std::size_t idx) : histogram_(h), idx_(idx) {} - iterator_over(const iterator_over&) = default; - iterator_over& operator=(const iterator_over&) = default; + iterator_over(const iterator_over& o) : histogram_(o.histogram_), idx_(o.idx_) {} - unsigned dim() const noexcept { return histogram_.dim(); } + iterator_over& operator=(const iterator_over& o) { + histogram_ = o.histogram_; + idx_ = o.idx_; + cache_.reset(); + } + + std::size_t dim() const noexcept { return histogram_.dim(); } - int idx(unsigned dim = 0) const noexcept { - histogram_.index_cache_(idx_); - return histogram_.index_cache_[dim]; + int idx(std::size_t dim = 0) const noexcept { + if (!cache_) { cache_.set(histogram_); } + cache_.set_idx(idx_); + return cache_.get(dim); } - auto bin() const - -> decltype(std::declval().axis(mp11::mp_int<0>())[0]) { - return histogram_.axis(mp11::mp_int<0>())[idx(0)]; + auto bin() const -> decltype(std::declval().axis()[0]) { + return histogram_.axis()[idx()]; } - template - auto bin(mp11::mp_int) const - -> decltype(std::declval().axis(mp11::mp_int())[0]) { - return histogram_.axis(mp11::mp_int())[idx(Dim)]; + template + auto bin(mp11::mp_size_t) const + -> decltype(std::declval().axis(mp11::mp_size_t())[0]) { + return histogram_.axis(mp11::mp_size_t())[idx(I)]; } template // use SFINAE for this method - auto bin(unsigned dim) const -> decltype(std::declval().axis(dim)[0]) { + auto bin(std::size_t dim) const -> decltype(std::declval().axis(dim)[0]) { return histogram_.axis(dim)[idx(dim)]; } @@ -70,6 +70,7 @@ class iterator_over const Histogram& histogram_; std::size_t idx_; + mutable detail::index_cache cache_; friend class ::boost::iterator_core_access; }; diff --git a/include/boost/histogram/literals.hpp b/include/boost/histogram/literals.hpp index a9cb2d8e5..bd71907b8 100644 --- a/include/boost/histogram/literals.hpp +++ b/include/boost/histogram/literals.hpp @@ -8,6 +8,7 @@ #define _BOOST_HISTOGRAM_LITERALS_HPP_ #include +#include namespace boost { namespace histogram { @@ -17,59 +18,59 @@ template struct char2int; template <> struct char2int<'0'> { - static constexpr int value = 0; + static constexpr std::size_t value = 0; }; template <> struct char2int<'1'> { - static constexpr int value = 1; + static constexpr std::size_t value = 1; }; template <> struct char2int<'2'> { - static constexpr int value = 2; + static constexpr std::size_t value = 2; }; template <> struct char2int<'3'> { - static constexpr int value = 3; + static constexpr std::size_t value = 3; }; template <> struct char2int<'4'> { - static constexpr int value = 4; + static constexpr std::size_t value = 4; }; template <> struct char2int<'5'> { - static constexpr int value = 5; + static constexpr std::size_t value = 5; }; template <> struct char2int<'6'> { - static constexpr int value = 6; + static constexpr std::size_t value = 6; }; template <> struct char2int<'7'> { - static constexpr int value = 7; + static constexpr std::size_t value = 7; }; template <> struct char2int<'8'> { - static constexpr int value = 8; + static constexpr std::size_t value = 8; }; template <> struct char2int<'9'> { - static constexpr int value = 9; + static constexpr std::size_t value = 9; }; -template -constexpr int parse() { +template +constexpr std::size_t parse() { return N; } -template -constexpr int parse() { +template +constexpr std::size_t parse() { return parse::value, Rest...>(); } } // namespace detail template -auto operator"" _c() -> ::boost::mp11::mp_int()> { - return ::boost::mp11::mp_int()>(); +auto operator"" _c() -> ::boost::mp11::mp_size_t()> { + return ::boost::mp11::mp_size_t()>(); } } // namespace literals diff --git a/include/boost/histogram/ostream_operators.hpp b/include/boost/histogram/ostream_operators.hpp index 7a2f6de76..5096b4378 100644 --- a/include/boost/histogram/ostream_operators.hpp +++ b/include/boost/histogram/ostream_operators.hpp @@ -8,6 +8,7 @@ #define _BOOST_HISTOGRAM_OSTREAM_OPERATORS_HPP_ #include +#include #include #include @@ -15,9 +16,10 @@ namespace boost { namespace histogram { namespace detail { +template struct axis_ostream_visitor { - std::ostream& os_; - explicit axis_ostream_visitor(std::ostream& os) : os_(os) {} + OStream& os_; + explicit axis_ostream_visitor(OStream& os) : os_(os) {} template void operator()(const Axis& a) const { os_ << "\n " << a << ","; @@ -25,16 +27,19 @@ struct axis_ostream_visitor { }; } // namespace detail -template -std::ostream& operator<<(std::ostream& os, const histogram& h) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const histogram& h) { + using OS = std::basic_ostream; os << "histogram("; - h.for_each_axis(detail::axis_ostream_visitor(os)); + h.for_each_axis(detail::axis_ostream_visitor(os)); os << (h.dim() ? "\n)" : ")"); return os; } -template -std::ostream& operator<<(std::ostream& os, const weight_counter& x) { +template +std::basic_ostream& operator<<(std::basic_ostream& os, + const weight_counter& x) { os << "weight_counter(" << x.value() << ", " << x.variance() << ")"; return os; } diff --git a/include/boost/histogram/serialization.hpp b/include/boost/histogram/serialization.hpp index 001b11a3a..12d2d3606 100644 --- a/include/boost/histogram/serialization.hpp +++ b/include/boost/histogram/serialization.hpp @@ -7,10 +7,12 @@ #ifndef BOOST_HISTOGRAM_SERIALIZATION_HPP_ #define BOOST_HISTOGRAM_SERIALIZATION_HPP_ +#include +#include +#include +#include #include -#include -#include -#include +#include #include #include #include @@ -29,14 +31,28 @@ namespace histogram { namespace detail { template -struct serialize_helper { +struct serialize_t { Archive& ar_; - explicit serialize_helper(Archive& ar) : ar_(ar) {} + explicit serialize_t(Archive& ar) : ar_(ar) {} template void operator()(T& t) const { ar_& t; } }; + +struct serializer { + template + void operator()(T*, Buffer& b, Archive& ar) { + if (Archive::is_loading::value) { create(type_tag(), b); } + ar& boost::serialization::make_array(reinterpret_cast(b.ptr), b.size); + } + + template + void operator()(void*, Buffer& b, Archive&) { + if (Archive::is_loading::value) { b.ptr = nullptr; } + } +}; + } // namespace detail template @@ -47,77 +63,21 @@ void weight_counter::serialize(Archive& ar, ar& w2; } -template -void serialize(Archive& ar, array_storage& store, +template +void serialize(Archive& ar, array_storage& store, unsigned /* version */) { ar& store.array_; } +template template -void adaptive_storage::serialize(Archive& ar, unsigned /* version */) { - auto size = this->size(); - ar& size; +void adaptive_storage::serialize(Archive& ar, unsigned /* version */) { if (Archive::is_loading::value) { - auto type_id = 0u; - ar& type_id; - if (type_id == 0u) { - buffer_ = detail::array(size); - } else if (type_id == 1u) { - detail::array a(size); - ar& serialization::make_array(a.begin(), size); - buffer_ = std::move(a); - } else if (type_id == 2u) { - detail::array a(size); - ar& serialization::make_array(a.begin(), size); - buffer_ = std::move(a); - } else if (type_id == 3u) { - detail::array a(size); - ar& serialization::make_array(a.begin(), size); - buffer_ = std::move(a); - } else if (type_id == 4u) { - detail::array a(size); - ar& serialization::make_array(a.begin(), size); - buffer_ = std::move(a); - } else if (type_id == 5u) { - detail::array a(size); - ar& serialization::make_array(a.begin(), size); - buffer_ = std::move(a); - } else if (type_id == 6u) { - detail::array a(size); - ar& serialization::make_array(a.begin(), size); - buffer_ = std::move(a); - } - } else { - auto type_id = 0u; - if (get>(&buffer_)) { - type_id = 0u; - ar& type_id; - } else if (auto* a = get>(&buffer_)) { - type_id = 1u; - ar& type_id; - ar& serialization::make_array(a->begin(), size); - } else if (auto* a = get>(&buffer_)) { - type_id = 2u; - ar& type_id; - ar& serialization::make_array(a->begin(), size); - } else if (auto* a = get>(&buffer_)) { - type_id = 3u; - ar& type_id; - ar& serialization::make_array(a->begin(), size); - } else if (auto* a = get>(&buffer_)) { - type_id = 4u; - ar& type_id; - ar& serialization::make_array(a->begin(), size); - } else if (auto* a = get>(&buffer_)) { - type_id = 5u; - ar& type_id; - ar& serialization::make_array(a->begin(), size); - } else if (auto* a = get>(&buffer_)) { - type_id = 6u; - ar& type_id; - ar& serialization::make_array(a->begin(), size); - } + detail::apply(detail::destroyer(), buffer_); } + ar& buffer_.type; + ar& buffer_.size; + detail::apply(detail::serializer(), buffer_, ar); } namespace axis { @@ -126,7 +86,16 @@ template void base::serialize(Archive& ar, unsigned /* version */) { ar& size_; ar& shape_; - ar& label_; +} + +template +template +void labeled_base::serialize(Archive& ar, unsigned /* version */) { + ar& boost::serialization::base_object(*this); + auto size = label_.size(); + ar& size; + if (Archive::is_loading::value) { label_.resize(size); } + ar& serialization::make_array(label_.data(), size); } namespace transform { @@ -136,46 +105,72 @@ void pow::serialize(Archive& ar, unsigned /* version */) { } } // namespace transform -template +template template -void regular::serialize(Archive& ar, - unsigned /* version */) { - ar& boost::serialization::base_object(*this); - ar& boost::serialization::base_object(*this); +void regular::serialize(Archive& ar, unsigned /* version */) { + ar& boost::serialization::base_object>(*this); + ar& boost::serialization::base_object(*this); ar& min_; ar& delta_; } -template +template template -void circular::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object(*this); +void circular::serialize(Archive& ar, unsigned /* version */) { + ar& boost::serialization::base_object>(*this); ar& phase_; ar& perimeter_; } -template +template template -void variable::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object(*this); +void variable::serialize(Archive& ar, unsigned /* version */) { if (Archive::is_loading::value) { - x_.reset(new RealType[base::size() + 1]); + this->~variable(); } - ar& boost::serialization::make_array(x_.get(), base::size() + 1); + + ar& boost::serialization::base_object>(*this); + + if (Archive::is_loading::value) { + value_allocator_type a(base_type::get_allocator()); + using AT = std::allocator_traits; + x_ = AT::allocate(a, base_type::size() + 1); + auto xit = x_; + const auto xend = x_ + base_type::size() + 1; + while (xit != xend) + AT::construct(a, xit++); + } + + ar& boost::serialization::make_array(x_, base_type::size() + 1); } -template +template template -void integer::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object(*this); +void integer::serialize(Archive& ar, unsigned /* version */) { + ar& boost::serialization::base_object>(*this); ar& min_; } -template +template template -void category::serialize(Archive& ar, unsigned /* version */) { - ar& boost::serialization::base_object(*this); - ar& map_; +void category::serialize(Archive& ar, unsigned /* version */) { + if (Archive::is_loading::value) { + this->~category(); + } + + ar& boost::serialization::base_object>(*this); + + if (Archive::is_loading::value) { + value_allocator_type a(base_type::get_allocator()); + using AT = std::allocator_traits; + x_ = AT::allocate(a, base_type::size()); + auto xit = x_; + const auto xend = x_ + base_type::size(); + while (xit != xend) + AT::construct(a, xit++); + } + + ar& boost::serialization::make_array(x_, base_type::size()); } template @@ -186,20 +181,23 @@ void any::serialize(Archive& ar, unsigned /* version */) { } // namespace axis -template -template -void histogram::serialize(Archive& ar, - unsigned /* version */) { - detail::serialize_helper sh(ar); - mp11::tuple_for_each(axes_, sh); - ar& storage_; +namespace { +template +void serialize_axes(Archive& ar, std::tuple& axes) { + detail::serialize_t sh(ar); + mp11::tuple_for_each(axes, sh); +} + +template +void serialize_axes(Archive& ar, std::vector& axes) { + ar& axes; +} } template template -void histogram::serialize(Archive& ar, - unsigned /* version */) { - ar& axes_; +void histogram::serialize(Archive& ar, unsigned /* version */) { + serialize_axes(ar, axes_); ar& storage_; } diff --git a/include/boost/histogram/static_histogram.hpp b/include/boost/histogram/static_histogram.hpp deleted file mode 100644 index 14a36ffdc..000000000 --- a/include/boost/histogram/static_histogram.hpp +++ /dev/null @@ -1,463 +0,0 @@ -// Copyright 2015-2017 Hans Dembinski -// -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt -// or copy at http://www.boost.org/LICENSE_1_0.txt) - -#ifndef _BOOST_HISTOGRAM_HISTOGRAM_IMPL_STATIC_HPP_ -#define _BOOST_HISTOGRAM_HISTOGRAM_IMPL_STATIC_HPP_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// forward declaration for serialization -namespace boost { -namespace serialization { -class access; -} -} // namespace boost - -namespace boost { -namespace histogram { - -template -class histogram { - using axes_size = mp11::mp_size; - static_assert(axes_size::value > 0, "at least one axis required"); - -public: - using axes_type = mp11::mp_rename; - using element_type = typename Storage::element_type; - using const_reference = typename Storage::const_reference; - using const_iterator = iterator_over; - using iterator = const_iterator; - - histogram() = default; - histogram(const histogram& rhs) = default; - histogram(histogram&& rhs) = default; - histogram& operator=(const histogram& rhs) = default; - histogram& operator=(histogram&& rhs) = default; - - template > - explicit histogram(Axis0&& axis0, Axis&&... axis) - : axes_(std::forward(axis0), std::forward(axis)...) { - storage_ = Storage(size_from_axes()); - index_cache_.reset(*this); - } - - explicit histogram(axes_type&& axes) : axes_(std::move(axes)) { - storage_ = Storage(size_from_axes()); - index_cache_.reset(*this); - } - - template - explicit histogram(const static_histogram& rhs) - : axes_(rhs.axes_), storage_(rhs.storage_) { - index_cache_.reset(*this); - } - - template - histogram& operator=(const static_histogram& rhs) { - if (static_cast(this) != static_cast(&rhs)) { - axes_ = rhs.axes_; - storage_ = rhs.storage_; - index_cache_.reset(*this); - } - return *this; - } - - template - explicit histogram(const dynamic_histogram& rhs) - : storage_(rhs.storage_) { - detail::axes_assign(axes_, rhs.axes_); - index_cache_.reset(*this); - } - - template - histogram& operator=(const dynamic_histogram& rhs) { - if (static_cast(this) != static_cast(&rhs)) { - detail::axes_assign(axes_, rhs.axes_); - storage_ = rhs.storage_; - index_cache_.reset(*this); - } - return *this; - } - - template - bool operator==(const static_histogram&) const noexcept { - return false; - } - - template - bool operator==(const static_histogram& rhs) const noexcept { - return detail::axes_equal(axes_, rhs.axes_) && storage_ == rhs.storage_; - } - - template - bool operator==(const dynamic_histogram& rhs) const noexcept { - return detail::axes_equal(axes_, rhs.axes_) && storage_ == rhs.storage_; - } - - template - bool operator!=(const histogram& rhs) const noexcept { - return !operator==(rhs); - } - - template - histogram& operator+=(const static_histogram& rhs) { - if (!detail::axes_equal(axes_, rhs.axes_)) - throw std::invalid_argument("axes of histograms differ"); - storage_ += rhs.storage_; - return *this; - } - - template - histogram& operator+=(const dynamic_histogram& rhs) { - if (!detail::axes_equal(axes_, rhs.axes_)) - throw std::invalid_argument("axes of histograms differ"); - storage_ += rhs.storage_; - return *this; - } - - template - histogram& operator*=(const T& rhs) { - storage_ *= rhs; - return *this; - } - - template - histogram& operator/=(const T& rhs) { - storage_ *= 1.0 / rhs; - return *this; - } - - template - void operator()(Ts&&... ts) { - // case with one argument is ambiguous, is specialized below - static_assert(sizeof...(Ts) == axes_size::value, - "fill arguments do not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin<0>(idx, stride, ts...); - if (stride) detail::fill_storage(storage_, idx); - } - - template - void operator()(T&& t) { - // check whether we need to unpack argument - fill_impl(mp11::mp_if_c<(axes_size::value == 1), detail::no_container_tag, - detail::classify_container>(), - std::forward(t)); - } - - // TODO: merge this with unpacking - template - void operator()(detail::weight&& w, Ts&&... ts) { - // case with one argument is ambiguous, is specialized below - std::size_t idx = 0, stride = 1; - xlin<0>(idx, stride, ts...); - if (stride) detail::fill_storage(storage_, idx, std::move(w)); - } - - // TODO: remove as obsolete - template - void operator()(detail::weight&& w, T&& t) { - // check whether we need to unpack argument - fill_impl(mp11::mp_if_c<(axes_size::value == 1), detail::no_container_tag, - detail::classify_container>(), - std::forward(t), std::move(w)); - } - - template - const_reference at(const Ts&... ts) const { - // case with one argument is ambiguous, is specialized below - static_assert(sizeof...(ts) == axes_size::value, - "bin arguments do not match histogram dimension"); - std::size_t idx = 0, stride = 1; - lin<0>(idx, stride, static_cast(ts)...); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - const_reference operator[](const T& t) const { - return at(t); - } - - template - const_reference at(const T& t) const { - // check whether we need to unpack argument - return at_impl(detail::classify_container(), t); - } - - /// Number of axes (dimensions) of histogram - constexpr unsigned dim() const noexcept { return axes_size::value; } - - /// Total number of bins in the histogram (including underflow/overflow) - std::size_t size() const noexcept { return storage_.size(); } - - /// Reset bin counters to zero - void reset() { storage_ = Storage(size_from_axes()); } - - /// Get N-th axis (const version) - template - typename std::add_const::type>:: - type& axis(mp11::mp_int) const { - static_assert(N < axes_size::value, "axis index out of range"); - return std::get(axes_); - } - - /// Get N-th axis - template - typename std::tuple_element::type& axis(mp11::mp_int) { - static_assert(N < axes_size::value, "axis index out of range"); - return std::get(axes_); - } - - // Get first axis (convenience for 1-d histograms, const version) - constexpr typename std::add_const< - typename std::tuple_element<0, axes_type>::type>::type& - axis() const { - return std::get<0>(axes_); - } - - // Get first axis (convenience for 1-d histograms) - typename std::tuple_element<0, axes_type>::type& axis() { - return std::get<0>(axes_); - } - - /// Apply unary functor/function to each axis - template - void for_each_axis(Unary&& unary) const { - mp11::tuple_for_each(axes_, std::forward(unary)); - } - - /// Returns a lower-dimensional histogram - template - auto reduce_to(mp11::mp_int, Ns...) const - -> static_histogram, Ns...>, - Storage> { - using HR = - static_histogram, Ns...>, - Storage>; - auto hr = - HR(detail::make_sub_tuple, Ns...>(axes_)); - const auto b = detail::bool_mask, Ns...>(dim(), true); - reduce_impl(hr, b); - return hr; - } - - const_iterator begin() const noexcept { return const_iterator(*this, 0); } - - const_iterator end() const noexcept { - return const_iterator(*this, size()); - } - -private: - axes_type axes_; - Storage storage_; - mutable detail::index_cache index_cache_; - - std::size_t size_from_axes() const noexcept { - detail::field_count_visitor v; - for_each_axis(v); - return v.value; - } - - template - void fill_impl(detail::dynamic_container_tag, T&& t, Ts&&... ts) { - BOOST_ASSERT_MSG( - std::distance(std::begin(t), std::end(t)) == axes_size::value, - "fill container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin_iter(axes_size(), idx, stride, std::begin(t)); - if (stride) { - detail::fill_storage(storage_, idx, std::forward(ts)...); - } - } - - template - void fill_impl(detail::static_container_tag, T&& t, Ts&&... ts) { - static_assert(detail::mp_size::value == axes_size::value, - "fill container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin_get(axes_size(), idx, stride, std::forward(t)); - if (stride) { - detail::fill_storage(storage_, idx, std::forward(ts)...); - } - } - - template - void fill_impl(detail::no_container_tag, T&& t, Ts&&... ts) { - static_assert(axes_size::value == 1, - "fill argument does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - xlin<0>(idx, stride, std::forward(t)); - if (stride) { - detail::fill_storage(storage_, idx, std::forward(ts)...); - } - } - - template - const_reference at_impl(detail::dynamic_container_tag, const T& t) const { - BOOST_ASSERT_MSG( - std::distance(std::begin(t), std::end(t)) == axes_size::value, - "bin container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - lin_iter(axes_size(), idx, stride, std::begin(t)); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - const_reference at_impl(detail::static_container_tag, const T& t) const { - static_assert(mp11::mp_size::value == axes_size::value, - "bin container does not match histogram dimension"); - std::size_t idx = 0, stride = 1; - lin_get(axes_size(), idx, stride, t); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - const_reference at_impl(detail::no_container_tag, const T& t) const { - std::size_t idx = 0, stride = 1; - lin<0>(idx, stride, detail::indirect_int_cast(t)); - return detail::storage_get(storage_, idx, stride == 0); - } - - template - void xlin(std::size_t&, std::size_t&) const noexcept {} - - template - void xlin(std::size_t& idx, std::size_t& stride, T&& t, Ts&&... ts) const { - const auto a_size = std::get(axes_).size(); - const auto a_shape = std::get(axes_).shape(); - const int j = std::get(axes_).index(t); - detail::lin(idx, stride, a_size, a_shape, j); - xlin(idx, stride, std::forward(ts)...); - } - - template - void xlin_iter(mp11::mp_size_t<0>, std::size_t&, std::size_t&, - Iterator) const noexcept {} - - template - void xlin_iter(mp11::mp_size_t, std::size_t& idx, std::size_t& stride, - Iterator iter) const { - constexpr unsigned D = axes_size::value - N; - const auto a_size = std::get(axes_).size(); - const auto a_shape = std::get(axes_).shape(); - const int j = std::get(axes_).index(*iter); - detail::lin(idx, stride, a_size, a_shape, j); - xlin_iter(mp11::mp_size_t(), idx, stride, ++iter); - } - - template - void lin(std::size_t&, std::size_t&) const noexcept {} - - template - void lin(std::size_t& idx, std::size_t& stride, int j, Ts... ts) const - noexcept { - const auto a_size = std::get(axes_).size(); - const auto a_shape = std::get(axes_).shape(); - stride *= (-1 <= j && j <= a_size); // set stride to zero, if j is invalid - detail::lin(idx, stride, a_size, a_shape, j); - lin<(D + 1)>(idx, stride, ts...); - } - - template - void lin_iter(mp11::mp_size_t<0>, std::size_t&, std::size_t&, - Iterator) const noexcept {} - - template - void lin_iter(mp11::mp_size_t, std::size_t& idx, std::size_t& stride, - Iterator iter) const noexcept { - constexpr unsigned D = axes_size::value - N; - const auto a_size = std::get(axes_).size(); - const auto a_shape = std::get(axes_).shape(); - const auto j = detail::indirect_int_cast(*iter); - stride *= (-1 <= j && j <= a_size); // set stride to zero, if j is invalid - detail::lin(idx, stride, a_size, a_shape, j); - lin_iter(mp11::mp_size_t<(N - 1)>(), idx, stride, ++iter); - } - - template - void xlin_get(mp11::mp_size_t<0>, std::size_t&, std::size_t&, T&&) const - noexcept {} - - template - void xlin_get(mp11::mp_size_t, std::size_t& idx, std::size_t& stride, - T&& t) const { - constexpr unsigned D = detail::mp_size::value - N; - const auto a_size = std::get(axes_).size(); - const auto a_shape = std::get(axes_).shape(); - const auto j = std::get(axes_).index(std::get(t)); - detail::lin(idx, stride, a_size, a_shape, j); - xlin_get(mp11::mp_size_t<(N - 1)>(), idx, stride, std::forward(t)); - } - - template - void lin_get(mp11::mp_size_t<0>, std::size_t&, std::size_t&, T&&) const - noexcept {} - - template - void lin_get(mp11::mp_size_t, std::size_t& idx, std::size_t& stride, - T&& t) const noexcept { - constexpr unsigned D = detail::mp_size::value - N; - const auto a_size = std::get(axes_).size(); - const auto a_shape = std::get(axes_).shape(); - const auto j = detail::indirect_int_cast(std::get(t)); - stride *= (-1 <= j && j <= a_size); // set stride to zero, if j is invalid - detail::lin(idx, stride, a_size, a_shape, j); - lin_get(mp11::mp_size_t<(N - 1)>(), idx, stride, t); - } - - template - void reduce_impl(H& h, const std::vector& b) const { - detail::shape_vector_visitor v(dim()); - for_each_axis(v); - detail::index_mapper m(v.shapes, b); - do { h.storage_.add(m.second, storage_[m.first]); } while (m.next()); - } - - template - friend class histogram; - template - friend class iterator_over; - friend class ::boost::serialization::access; - template - void serialize(Archive&, unsigned); -}; - -/// default static type factory -template -static_histogram...>> -make_static_histogram(Axis&&... axis) { - return static_histogram...>>( - std::forward(axis)...); -} - -/// static type factory with variable storage type -template -static_histogram...>, Storage> -make_static_histogram_with(Axis&&... axis) { - return static_histogram...>, Storage>( - std::forward(axis)...); -} - -} // namespace histogram -} // namespace boost - -#endif diff --git a/include/boost/histogram/storage/adaptive_storage.hpp b/include/boost/histogram/storage/adaptive_storage.hpp index 00e0fbd93..aa914e7cb 100644 --- a/include/boost/histogram/storage/adaptive_storage.hpp +++ b/include/boost/histogram/storage/adaptive_storage.hpp @@ -8,18 +8,18 @@ #define _BOOST_HISTOGRAM_STORAGE_ADAPTIVE_HPP_ #include +#include +#include +#include #include #include #include +#include #include -#include +#include #include #include #include -#ifdef BOOST_HISTOGRAM_TRACE_ALLOCS -#include -#include -#endif // forward declaration for serialization namespace boost { @@ -28,136 +28,65 @@ class access; } } // namespace boost -// forward declaration for python -namespace boost { -namespace python { -class access; -} -} // namespace boost - namespace boost { namespace histogram { namespace detail { -using mp_int = ::boost::multiprecision::cpp_int; -using wcount = ::boost::histogram::weight_counter; +using wcount = weight_counter; +using mp_int = boost::multiprecision::cpp_int; template -T* alloc(std::size_t s) { -#ifdef BOOST_HISTOGRAM_TRACE_ALLOCS - boost::core::typeinfo const& ti = BOOST_CORE_TYPEID(T); - std::cerr << "alloc " << boost::core::demangled_name(ti) << "[" << s << "]" - << std::endl; -#endif - return new T[s]; -} - -class array_base { -public: - explicit array_base(const std::size_t s) : size(s) {} - array_base() = default; - array_base(const array_base&) = default; - array_base& operator=(const array_base&) = default; - array_base(array_base&& rhs) : size(rhs.size) { rhs.size = 0; } - array_base& operator=(array_base&& rhs) { - if (this != &rhs) { - size = rhs.size; - rhs.size = 0; - } - return *this; - } - std::size_t size = 0; +struct type_tag {}; +template <> +struct type_tag { + static constexpr char value = 0; + using next = uint8_t; }; - -template -class array : public array_base { -public: - explicit array(const std::size_t s) : array_base(s), ptr(alloc(s)) { - std::fill(begin(), end(), T(0)); - } - array() = default; - array(const array& rhs) : array_base(rhs), ptr(alloc(rhs.size)) { - std::copy(rhs.begin(), rhs.end(), begin()); - } - array& operator=(const array& rhs) { - if (this != &rhs) { - if (size != rhs.size) { - size = rhs.size; - ptr.reset(alloc(size)); - } - std::copy(rhs.begin(), rhs.end(), begin()); - } - return *this; - } - array(array&& rhs) : array_base(std::move(rhs)), ptr(std::move(rhs.ptr)) { - rhs.size = 0; - } - array& operator=(array&& rhs) { - if (this != &rhs) { - size = rhs.size; - ptr = std::move(rhs.ptr); - rhs.size = 0; - } - return *this; - } - - // copy only up to nmax elements - template - array(const array& rhs, - std::size_t nmax = std::numeric_limits::max()) - : array_base(rhs), ptr(alloc(rhs.size)) { - std::copy(rhs.begin(), rhs.begin() + std::min(nmax, size), begin()); - } - - T& operator[](const std::size_t i) { return ptr[i]; } - const T& operator[](const std::size_t i) const { return ptr[i]; } - - T* begin() { return ptr.get(); } - T* end() { return ptr.get() + size; } - const T* begin() const { return ptr.get(); } - const T* end() const { return ptr.get() + size; } - -private: - std::unique_ptr ptr; +template <> +struct type_tag { + static constexpr char value = 1; + using next = uint16_t; }; - template <> -class array : public array_base { -public: - using array_base::array_base; +struct type_tag { + static constexpr char value = 2; + using next = uint32_t; }; - -using any_array = - variant, array, array, array, - array, array, array>; - -template -struct next_type; template <> -struct next_type { - using type = uint16_t; +struct type_tag { + static constexpr char value = 3; + using next = uint64_t; }; template <> -struct next_type { - using type = uint32_t; +struct type_tag { + static constexpr char value = 4; + using next = mp_int; }; template <> -struct next_type { - using type = uint64_t; +struct type_tag { + static constexpr char value = 5; }; template <> -struct next_type { - using type = mp_int; +struct type_tag { + static constexpr char value = 6; }; + +template +using next_type = typename type_tag::next; + template -using next = typename next_type::type; +constexpr char type_index() { + return type_tag::value; +} template bool safe_increase(T& t) { - if (t == std::numeric_limits::max()) return false; - ++t; - return true; + if (t < std::numeric_limits::max()) { + ++t; + return true; + } + return false; } template @@ -171,359 +100,406 @@ bool safe_assign(T& t, const U& u) { template bool safe_radd(T& t, const U& u) { + BOOST_ASSERT(t >= 0); + BOOST_ASSERT(u >= 0); + // static_cast converts back from signed to unsigned integer if (static_cast(std::numeric_limits::max() - t) < u) return false; - t += static_cast(u); + t += static_cast(u); // static_cast to suppress conversion warning return true; } -// float rounding is a mess, the equal sign is necessary here -template -bool safe_radd(T& t, const double u) { - if ((std::numeric_limits::max() - t) <= u) return false; - t += u; - return true; +template +typename std::result_of::type apply(F&& f, A&& a, Ts&&... ts) { + // this is intentionally not a switch, the if-chain is faster in benchmarks + if (a.type == 1) + return f(reinterpret_cast(a.ptr), std::forward(a), + std::forward(ts)...); + if (a.type == 2) + return f(reinterpret_cast(a.ptr), std::forward(a), + std::forward(ts)...); + if (a.type == 3) + return f(reinterpret_cast(a.ptr), std::forward(a), + std::forward(ts)...); + if (a.type == 4) + return f(reinterpret_cast(a.ptr), std::forward(a), + std::forward(ts)...); + if (a.type == 5) + return f(reinterpret_cast(a.ptr), std::forward(a), + std::forward(ts)...); + if (a.type == 6) + return f(reinterpret_cast(a.ptr), std::forward(a), + std::forward(ts)...); + // a.type == 0 is intentionally the last in the chain, because it is rarely + // triggered + return f(a.ptr, std::forward(a), std::forward(ts)...); } -struct size_visitor : public static_visitor { - template - std::size_t operator()(const Array& b) const { - return b.size; - } -}; - -template -struct assign_visitor : public static_visitor { - any_array& lhs_any; - const std::size_t idx; - const RHS& rhs; - assign_visitor(any_array& a, const std::size_t i, const RHS& x) - : lhs_any(a), idx(i), rhs(x) {} +template +void create(type_tag, Buffer& b, const U* init = nullptr) { + using alloc_type = typename std::allocator_traits< + typename Buffer::allocator_type>::template rebind_alloc; + alloc_type a(b.alloc); // rebind allocator + using AT = std::allocator_traits; + T* p = AT::allocate(a, b.size); + if (init) { + for (auto it = p, end = p + b.size; it != end; ++it) AT::construct(a, it, *init++); + } else { + for (auto it = p, end = p + b.size; it != end; ++it) AT::construct(a, it, 0); + } + b.type = type_index(); + b.ptr = p; +} - template - void operator()(array& lhs) const { - if (!safe_assign(lhs[idx], rhs)) { - lhs_any = array>(lhs, idx); - operator()(get>>(lhs_any)); - } - } +template +void create(type_tag, Buffer& b, const U* init = nullptr) { + boost::ignore_unused(init); + BOOST_ASSERT(!init); + b.ptr = nullptr; + b.type = type_index(); +} - void operator()(array& lhs) const { - lhs_any = array(lhs.size); - operator()(get>(lhs_any)); +struct destroyer { + template + void operator()(T* tp, Buffer& b) { + using alloc_type = typename std::allocator_traits< + typename Buffer::allocator_type>::template rebind_alloc; + using AT = std::allocator_traits; + alloc_type a(b.alloc); // rebind allocator + for (auto it = tp, end = tp + b.size; it != end; ++it) AT::destroy(a, it); + AT::deallocate(a, tp, b.size); } - void operator()(array& lhs) const { lhs[idx].assign(rhs); } - - void operator()(array& lhs) const { lhs[idx] = rhs; } + template + void operator()(void*, Buffer&) {} }; -struct increase_visitor : public static_visitor { - any_array& lhs_any; - const std::size_t idx; - increase_visitor(any_array& a, const std::size_t i) : lhs_any(a), idx(i) {} - - template - void operator()(array& lhs) const { - if (!safe_increase(lhs[idx])) { - array> a = lhs; - ++a[idx]; - lhs_any = std::move(a); +struct replacer { + template + void operator()(T* optr, const OBuffer& ob, Buffer& b) { + if (b.size == ob.size && b.type == ob.type) { + std::copy(optr, optr + ob.size, reinterpret_cast(b.ptr)); + } else { + apply(destroyer(), b); + b.alloc = ob.alloc; + b.size = ob.size; + create(type_tag(), b, optr); } } - void operator()(array& lhs) const { - array a(lhs.size); - ++a[idx]; - lhs_any = std::move(a); + template + void operator()(void*, const OBuffer& ob, Buffer& b) { + apply(destroyer(), b); + b.type = 0; + b.size = ob.size; } - - void operator()(array& lhs) const { ++lhs[idx]; } - - void operator()(array& lhs) const { ++lhs[idx]; } }; -struct bin_visitor : public static_visitor { - const std::size_t idx; - bin_visitor(const std::size_t i) : idx(i) {} - - template - wcount operator()(const Array& b) const { - return wcount(static_cast(b[idx])); - } - - wcount operator()(const array& /*b*/) const { return wcount(0.0); } - - wcount operator()(const array& b) const { return b[idx]; } -}; - -template -struct radd_visitor : public static_visitor { - any_array& lhs_any; - const std::size_t idx; - const RHS& rhs; - radd_visitor(any_array& l, const std::size_t i, const RHS& r) - : lhs_any(l), idx(i), rhs(r) {} - - template - void operator()(array& lhs) const { - if (!safe_radd(lhs[idx], rhs)) { - lhs_any = array>(lhs); - operator()(get>>(lhs_any)); +struct increaser { + template + void operator()(T* tp, Buffer& b, std::size_t i) { + if (!safe_increase(tp[i])) { + using U = next_type; + create(type_tag(), b, tp); + destroyer()(tp, b); + ++reinterpret_cast(b.ptr)[i]; } } - void operator()(array& lhs) const { - if (rhs != 0) { - lhs_any = array(lhs.size); - operator()(get>(lhs_any)); - } + template + void operator()(void*, Buffer& b, std::size_t i) { + using U = next_type; + create(type_tag(), b); + ++reinterpret_cast(b.ptr)[i]; } - void operator()(array& lhs) const { - lhs[idx] += static_cast(rhs); + template + void operator()(mp_int* tp, Buffer&, std::size_t i) { + ++tp[i]; } - void operator()(array& lhs) const { lhs[idx] += rhs; } + template + void operator()(wcount* tp, Buffer&, std::size_t i) { + ++tp[i]; + } }; -template <> -struct radd_visitor : public static_visitor { - any_array& lhs_any; - const std::size_t idx; - const mp_int& rhs; - radd_visitor(any_array& l, const std::size_t i, const mp_int& r) - : lhs_any(l), idx(i), rhs(r) {} +struct adder { + template + using is_convertible_to_mp_int = typename std::is_convertible::type; - template - void operator()(array& lhs) const { - if (!safe_radd(lhs[idx], rhs)) { - lhs_any = array>(lhs); - operator()(get>>(lhs_any)); + template + using is_integral = typename std::is_integral::type; + + template + void if_integral(std::true_type, T* tp, Buffer& b, std::size_t i, const U& x) { + if (!safe_radd(tp[i], x)) { + using V = next_type; + create(type_tag(), b, tp); + destroyer()(tp, b); + operator()(reinterpret_cast(b.ptr), b, i, x); } } - void operator()(array& lhs) const { - if (rhs != 0) { - lhs_any = array(lhs.size); - operator()(get>(lhs_any)); - } + template + void if_integral(std::false_type, T* tp, Buffer& b, std::size_t i, const U& x) { + create(type_tag(), b, tp); + destroyer()(tp, b); + operator()(reinterpret_cast(b.ptr), b, i, x); } - void operator()(array& lhs) const { lhs[idx] += rhs; } - - void operator()(array& lhs) const { - lhs[idx] += static_cast(rhs); + template + void operator()(T* tp, Buffer& b, std::size_t i, const U& x) { + if_integral(is_integral(), tp, b, i, x); } -}; - -template <> -struct radd_visitor : public static_visitor { - any_array& lhs_any; - const std::size_t idx; - const wcount& rhs; - radd_visitor(any_array& l, const std::size_t i, const wcount& r) - : lhs_any(l), idx(i), rhs(r) {} - template - void operator()(array& lhs) const { - lhs_any = array(lhs); - operator()(get>(lhs_any)); + template + void operator()(void*, Buffer& b, std::size_t i, const U& x) { + using V = next_type; + create(type_tag(), b); + operator()(reinterpret_cast(b.ptr), b, i, x); } - void operator()(array& lhs) const { - lhs_any = array(lhs.size); - operator()(get>(lhs_any)); + template + void if_convertible_to_mp_int(std::true_type, mp_int* tp, Buffer&, std::size_t i, + const U& x) { + tp[i] += static_cast(x); } - void operator()(array& lhs) const { lhs[idx] += rhs; } -}; - -template <> -struct radd_visitor> : public static_visitor { - any_array& lhs_any; - const std::size_t idx; - const weight rhs; - radd_visitor(any_array& l, const std::size_t i, const double w) - : lhs_any(l), idx(i), rhs{w} {} + template + void if_convertible_to_mp_int(std::false_type, mp_int* tp, Buffer& b, std::size_t i, + const U& x) { + create(type_tag(), b, tp); + destroyer()(tp, b); + operator()(reinterpret_cast(b.ptr), b, i, x); + } - template - void operator()(array& lhs) const { - lhs_any = array(lhs); - operator()(get>(lhs_any)); + template + void operator()(mp_int* tp, Buffer& b, std::size_t i, const U& x) { + if_convertible_to_mp_int(is_convertible_to_mp_int(), tp, b, i, x); } - void operator()(array& lhs) const { - lhs_any = array(lhs.size); - operator()(get>(lhs_any)); + template + void operator()(wcount* tp, Buffer&, std::size_t i, const U& x) { + tp[i] += x; } - void operator()(array& lhs) const { lhs[idx] += rhs; } + template + void operator()(wcount* tp, Buffer&, std::size_t i, const mp_int& x) { + tp[i] += static_cast(x); + } }; -// precondition: both arrays must have same size and may not be identical -struct radd_array_visitor : public static_visitor { - any_array& lhs_any; - radd_array_visitor(any_array& l) : lhs_any(l) {} - template - void operator()(const array& rhs) const { - for (std::size_t i = 0; i < rhs.size; ++i) - apply_visitor(radd_visitor(lhs_any, i, rhs[i]), lhs_any); +struct buffer_adder { + template + void operator()(T* tp, const OBuffer&, Buffer& b) { + for (std::size_t i = 0; i < b.size; ++i) { apply(adder(), b, i, tp[i]); } } - void operator()(const array&) const {} + + template + void operator()(void*, const OBuffer&, Buffer&) {} }; -struct rmul_visitor : public static_visitor { - any_array& lhs_any; - const double x; - rmul_visitor(any_array& l, const double v) : lhs_any(l), x(v) {} - template - void operator()(array& lhs) const { - lhs_any = array(lhs); - operator()(get>(lhs_any)); +struct getter { + template + wcount operator()(T* tp, Buffer&, std::size_t i) { + return static_cast(tp[i]); } - void operator()(array&) const {} - void operator()(array& lhs) const { - for (std::size_t i = 0; i != lhs.size; ++i) lhs[i] *= x; + + template + wcount operator()(void*, Buffer&, std::size_t) { + return static_cast(0); } }; -struct bicmp_visitor : public static_visitor { - template - bool operator()(const array& b1, const array& b2) const { - if (b1.size != b2.size) return false; - return std::equal(b1.begin(), b1.end(), b2.begin()); - } +// precondition: buffers already have same size +struct comparer { + struct inner { + template + bool operator()(const U* optr, const OBuffer& ob, const T* tp) { + return std::equal(optr, optr + ob.size, tp); + } - template - bool operator()(const array& b1, const array& b2) const { - if (b1.size != b2.size) return false; - return std::all_of(b1.begin(), b1.end(), - [](const T& t) { return t == 0; }); + template + bool operator()(const U* optr, const OBuffer& ob, const void*) { + return std::all_of(optr, optr + ob.size, [](const U& x) { return x == 0; }); + } + + template + bool operator()(const void*, const OBuffer& ob, const T* tp) { + return std::all_of(tp, tp + ob.size, [](const T& x) { return x == 0; }); + } + + template + bool operator()(const void*, const OBuffer&, const void*) { + return true; + } + }; + + template + bool operator()(const T* tp, const Buffer& b, const OBuffer& ob) { + BOOST_ASSERT(b.size == ob.size); + return apply(inner(), ob, tp); } +}; - template - bool operator()(const array& b1, const array& b2) const { - return operator()(b2, b1); +struct multiplier { + template + void operator()(T* tp, Buffer& b, const double x) { + create(type_tag(), b, tp); + operator()(reinterpret_cast(b.ptr), b, x); } - bool operator()(const array& b1, const array& b2) const { - return b1.size == b2.size; + template + void operator()(void*, Buffer&, const double) {} + + template + void operator()(wcount* tp, Buffer& b, const double x) { + for (auto end = tp + b.size; tp != end; ++tp) *tp *= x; } }; } // namespace detail +template class adaptive_storage { - using buffer_type = detail::any_array; - public: - using element_type = detail::wcount; + using allocator_type = Allocator; + using element_type = weight_counter; using const_reference = element_type; - explicit adaptive_storage(std::size_t s) - : buffer_(detail::array(s)) {} - - adaptive_storage() = default; - adaptive_storage(const adaptive_storage&) = default; - adaptive_storage& operator=(const adaptive_storage&) = default; - adaptive_storage(adaptive_storage&&) = default; - adaptive_storage& operator=(adaptive_storage&&) = default; - - template - explicit adaptive_storage(const RHS& rhs) - : buffer_(detail::array(rhs.size())) { - using T = typename RHS::element_type; - for (std::size_t i = 0, n = rhs.size(); i < n; ++i) { - apply_visitor(detail::assign_visitor(buffer_, i, rhs[i]), buffer_); - } +private: + struct buffer_type { + using allocator_type = Allocator; + allocator_type alloc; + char type; + std::size_t size; + void* ptr; + buffer_type(std::size_t s = 0, const allocator_type& a = allocator_type()) + : alloc(a), type(0), size(s), ptr(nullptr) {} + }; + +public: + ~adaptive_storage() { detail::apply(detail::destroyer(), buffer_); } + + adaptive_storage(const adaptive_storage& o) { + detail::apply(detail::replacer(), o.buffer_, buffer_); } - template - adaptive_storage& operator=(const RHS& rhs) { - // no check for self-assign needed, default operator above is better match - const auto n = rhs.size(); - if (size() != n) { buffer_ = detail::array(n); } - using T = typename RHS::element_type; - for (std::size_t i = 0; i < n; ++i) { - apply_visitor(detail::assign_visitor(buffer_, i, rhs[i]), buffer_); - } + adaptive_storage& operator=(const adaptive_storage& o) { + if (this != &o) { detail::apply(detail::replacer(), o.buffer_, buffer_); } return *this; } - // used in unit tests - template - explicit adaptive_storage(const detail::array& a) : buffer_(a) {} + adaptive_storage(adaptive_storage&& o) : buffer_(std::move(o.buffer_)) { + o.buffer_.type = 0; + o.buffer_.size = 0; + o.buffer_.ptr = nullptr; + } + + adaptive_storage& operator=(adaptive_storage&& o) { + if (this != &o) { std::swap(buffer_, o.buffer_); } + return *this; + } - std::size_t size() const { - return apply_visitor(detail::size_visitor(), buffer_); + template > + explicit adaptive_storage(const S& s) : buffer_(s.size(), s.get_allocator()) { + create(detail::type_tag(), buffer_); + auto it = reinterpret_cast(buffer_.ptr); + const auto end = it + size(); + std::size_t i = 0; + while (it != end) *it++ = s[i++]; } - void increase(std::size_t i) { - apply_visitor(detail::increase_visitor(buffer_, i), buffer_); + template > + adaptive_storage& operator=(const S& s) { + // no check for self-assign needed, since S is different type + detail::apply(detail::destroyer(), buffer_); + buffer_.alloc = s.get_allocator(); + buffer_.size = s.size(); + create(detail::type_tag(), buffer_); + for (std::size_t i = 0; i < size(); ++i) { add(i, s[i]); } + return *this; } - void add(std::size_t i, const element_type& x) { - if (x.variance() == x.value()) { - apply_visitor(detail::radd_visitor(buffer_, i, x.value()), - buffer_); - } else { - apply_visitor(detail::radd_visitor(buffer_, i, x), - buffer_); - } + explicit adaptive_storage(const allocator_type& a = allocator_type()) : buffer_(0, a) { + detail::create(detail::type_tag(), buffer_); } - template - void add(std::size_t i, const T& t) { - apply_visitor(detail::radd_visitor(buffer_, i, t), buffer_); + allocator_type get_allocator() const { return buffer_.alloc; } + + void reset(std::size_t s) { + detail::apply(detail::destroyer(), buffer_); + buffer_.size = s; + create(detail::type_tag(), buffer_); + } + + std::size_t size() const { return buffer_.size; } + + void increase(std::size_t i) { + BOOST_ASSERT(i < size()); + detail::apply(detail::increaser(), buffer_, i); } template - void add(std::size_t i, const detail::weight& w) { - apply_visitor( - detail::radd_visitor>(buffer_, i, w.value), - buffer_); + void add(std::size_t i, const T& x) { + BOOST_ASSERT(i < size()); + detail::apply(detail::adder(), buffer_, i, x); } const_reference operator[](std::size_t i) const { - return apply_visitor(detail::bin_visitor(i), buffer_); + return detail::apply(detail::getter(), buffer_, i); } - bool operator==(const adaptive_storage& rhs) const { - return apply_visitor(detail::bicmp_visitor(), buffer_, rhs.buffer_); + bool operator==(const adaptive_storage& o) const { + if (size() != o.size()) return false; + return detail::apply(detail::comparer(), buffer_, o.buffer_); } // precondition: storages have same size - adaptive_storage& operator+=(const adaptive_storage& rhs) { - if (this == &rhs) { - for (std::size_t i = 0, n = size(); i < n; ++i) { - add(i, rhs[i]); // may lose precision - } + adaptive_storage& operator+=(const adaptive_storage& o) { + BOOST_ASSERT(o.size() == size()); + if (this == &o) { + /* + Self-adding is a special-case, because the source buffer ptr may be + invalided by growth. We avoid this by making a copy of the source. + This is the simplest solution, but expensive. The cost is ok, because + self-adding is only used by the unit-tests. It does not occur + frequently in real applications. + */ + const auto copy = o; + detail::apply(detail::buffer_adder(), copy.buffer_, buffer_); } else { - apply_visitor(detail::radd_array_visitor(buffer_), rhs.buffer_); + detail::apply(detail::buffer_adder(), o.buffer_, buffer_); } return *this; } // precondition: storages have same size - template - adaptive_storage& operator+=(const RHS& rhs) { - for (std::size_t i = 0, n = size(); i < n; ++i) - apply_visitor(detail::radd_visitor( - buffer_, i, rhs[i]), - buffer_); + template + adaptive_storage& operator+=(const S& o) { + BOOST_ASSERT(o.size() == size()); + for (std::size_t i = 0; i < size(); ++i) add(i, o[i]); return *this; } - template - adaptive_storage& operator*=(const T& x) { - apply_visitor(detail::rmul_visitor(buffer_, x), buffer_); + adaptive_storage& operator*=(const double x) { + detail::apply(detail::multiplier(), buffer_, x); return *this; } + // used by unit tests, not part of generic storage interface + template + adaptive_storage(std::size_t s, const T* p, const allocator_type& a = allocator_type()) + : buffer_(s, a) { + detail::create(detail::type_tag(), buffer_, p); + } + private: buffer_type buffer_; - friend class ::boost::python::access; + template + friend class adaptive_storage; + friend class python_access; friend class ::boost::serialization::access; template void serialize(Archive&, unsigned); diff --git a/include/boost/histogram/storage/array_storage.hpp b/include/boost/histogram/storage/array_storage.hpp index ade9d079e..873b24323 100644 --- a/include/boost/histogram/storage/array_storage.hpp +++ b/include/boost/histogram/storage/array_storage.hpp @@ -8,9 +8,11 @@ #define _BOOST_HISTOGRAM_STORAGE_ARRAY_HPP_ #include +#include #include #include #include +#include // forward declaration for serialization namespace boost { @@ -22,97 +24,89 @@ class access; namespace boost { namespace histogram { -template +template class array_storage { public: + using allocator_type = Allocator; using element_type = T; using const_reference = const T&; - explicit array_storage(std::size_t s) - : size_(s), array_(new element_type[s]) { - std::fill(array_.get(), array_.get() + s, element_type(0)); - } +private: + using array_type = std::vector; - array_storage() = default; - array_storage(const array_storage& other) - : size_(other.size()), array_(new element_type[other.size()]) { - std::copy(other.array_.get(), other.array_.get() + size_, array_.get()); - } - array_storage& operator=(const array_storage& other) { - if (this != &other) { - reset(other.size()); - std::copy(other.array_.get(), other.array_.get() + size_, array_.get()); - } - return *this; - } - array_storage(array_storage&& other) { - std::swap(size_, other.size_); - std::swap(array_, other.array_); - } - array_storage& operator=(array_storage&& other) { - if (this != &other) { - std::swap(size_, other.size_); - std::swap(array_, other.array_); - } - return *this; - } +public: + array_storage(const array_storage&) = default; + array_storage& operator=(const array_storage&) = default; + array_storage(array_storage&&) = default; + array_storage& operator=(array_storage&&) = default; template > - explicit array_storage(const S& other) { - reset(other.size()); - for (std::size_t i = 0; i < size_; ++i) - array_[i] = static_cast(other[i]); + explicit array_storage(const S& o) : array_(o.get_allocator()) { + array_.reserve(o.size()); + for (std::size_t i = 0; i < o.size(); ++i) + array_.emplace_back(static_cast(o[i])); } template > - array_storage& operator=(const S& other) { - reset(other.size()); - for (std::size_t i = 0; i < size_; ++i) - array_[i] = static_cast(other[i]); + array_storage& operator=(const S& o) { + array_ = array_type(o.get_allocator()); + array_.reserve(o.size()); + for (std::size_t i = 0; i < o.size(); ++i) + array_.emplace_back(static_cast(o[i])); return *this; } - std::size_t size() const noexcept { return size_; } + explicit array_storage(const allocator_type& a = allocator_type()) : array_(a) {} - void increase(std::size_t i) noexcept { ++array_[i]; } + allocator_type get_allocator() const { return array_.get_allocator(); } + + void reset(std::size_t s) { + if (s == size()) { + std::fill(array_.begin(), array_.end(), element_type(0)); + } else { + array_ = array_type(s, element_type(0), array_.get_allocator()); + } + } + + std::size_t size() const noexcept { return array_.size(); } + + void increase(std::size_t i) noexcept { + BOOST_ASSERT(i < size()); + ++array_[i]; + } template void add(std::size_t i, const U& x) noexcept { + BOOST_ASSERT(i < size()); array_[i] += x; } const_reference operator[](std::size_t i) const noexcept { + BOOST_ASSERT(i < size()); return array_[i]; } - template - bool operator==(const array_storage& rhs) const noexcept { - if (size_ != rhs.size_) return false; - return std::equal(array_.get(), array_.get() + size_, rhs.array_.get()); + template + bool operator==(const array_storage& rhs) const noexcept { + if (size() != rhs.size()) return false; + return std::equal(array_.begin(), array_.end(), rhs.array_.begin()); } template array_storage& operator+=(const S& rhs) noexcept { - for (std::size_t i = 0; i < size_; ++i) add(i, rhs[i]); + for (std::size_t i = 0; i < size(); ++i) add(i, rhs[i]); return *this; } - template - array_storage& operator*=(const U& x) noexcept { - for (std::size_t i = 0; i < size_; ++i) array_[i] *= x; + array_storage& operator*=(const element_type& x) noexcept { + for (std::size_t i = 0; i < size(); ++i) array_[i] *= x; return *this; } private: - std::size_t size_ = 0; - std::unique_ptr array_; + array_type array_; - void reset(std::size_t size) { - size_ = size; - array_.reset(new element_type[size]); - } - - template + template friend class array_storage; friend class ::boost::serialization::access; diff --git a/include/boost/histogram/storage/weight_counter.hpp b/include/boost/histogram/storage/weight_counter.hpp index dca797913..7fd3bab63 100644 --- a/include/boost/histogram/storage/weight_counter.hpp +++ b/include/boost/histogram/storage/weight_counter.hpp @@ -7,7 +7,7 @@ #ifndef _BOOST_HISTOGRAM_STORAGE_WEIGHT_COUNTER_HPP_ #define _BOOST_HISTOGRAM_STORAGE_WEIGHT_COUNTER_HPP_ -#include +#include #include namespace boost { @@ -57,7 +57,7 @@ class weight_counter { } template - weight_counter& operator+=(const detail::weight& rhs) { + weight_counter& operator+=(const detail::weight_type& rhs) { const auto x = static_cast(rhs.value); w += x; w2 += x * x; diff --git a/include/boost/histogram/weight.hpp b/include/boost/histogram/weight.hpp new file mode 100644 index 000000000..6efa10f0b --- /dev/null +++ b/include/boost/histogram/weight.hpp @@ -0,0 +1,36 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#ifndef _BOOST_HISTOGRAM_WEIGHT_HPP_ +#define _BOOST_HISTOGRAM_WEIGHT_HPP_ + +namespace boost { +namespace histogram { +namespace detail { +template +struct weight_type { + T value; +}; + +template +struct sample_type { + T value; +}; +} // namespace detail + +template +detail::weight_type weight(T&& t) { + return {t}; +} + +template +detail::sample_type sample(T&& t) { + return {t}; +} +} +} + +#endif diff --git a/src/python/axis.cpp b/src/python/axis.cpp index 5d76aa30b..16aa04d16 100644 --- a/src/python/axis.cpp +++ b/src/python/axis.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -52,9 +51,7 @@ struct generic_iterator { unsigned size = 0; }; -generic_iterator make_generic_iterator(bp::object self) { - return generic_iterator(self); -} +generic_iterator make_generic_iterator(bp::object self) { return generic_iterator(self); } template struct axis_value_view_to_python { @@ -84,7 +81,7 @@ bp::object variable_init(bp::tuple args, bp::dict kwargs) { } boost::string_view label; - auto uo = bha::uoflow::on; + auto uo = bha::uoflow_type::on; while (bp::len(kwargs) > 0) { bp::tuple kv = kwargs.popitem(); const char* key_cstr = bp::extract(kv[0]); @@ -93,7 +90,7 @@ bp::object variable_init(bp::tuple args, bp::dict kwargs) { if (k == "label") label = boost::string_view(bp::extract(v), bp::len(v)); else if (k == "uoflow") { - if (!bp::extract(v)) uo = bha::uoflow::off; + if (!bp::extract(v)) uo = bha::uoflow_type::off; } else { std::stringstream s; s << "keyword " << k << " not recognized"; @@ -102,8 +99,7 @@ bp::object variable_init(bp::tuple args, bp::dict kwargs) { } } - return self.attr("__init__")( - bha::variable<>(v.begin(), v.end(), label, uo)); + return self.attr("__init__")(bha::variable<>(v.begin(), v.end(), label, uo)); } bp::object category_init(bp::tuple args, bp::dict kwargs) { @@ -131,16 +127,14 @@ bp::object category_init(bp::tuple args, bp::dict kwargs) { } std::vector c; - for (int i = 1, n = bp::len(args); i < n; ++i) - c.push_back(bp::extract(args[i])); + for (int i = 1, n = bp::len(args); i < n; ++i) c.push_back(bp::extract(args[i])); return self.attr("__init__")(bha::category<>(c.begin(), c.end(), label)); } template void axis_set_label(T& t, bp::str s) { - t.label( - {bp::extract(s)(), static_cast(bp::len(s))}); + t.label({bp::extract(s)(), static_cast(bp::len(s))}); } template @@ -151,7 +145,7 @@ bp::str axis_get_label(const T& t) { template bp::object axis_getitem(const A& a, int i) { - if (i < -1 * a.uoflow() || i >= a.size() + 1 * a.uoflow()) + if (i < -(a.uoflow() == 2) || i >= (a.size() + int(a.uoflow() > 0))) throw std::out_of_range("index out of bounds"); return bp::make_tuple(a.lower(i), a.lower(i + 1)); } @@ -180,8 +174,7 @@ bp::object axis_array_interface(const Axis& axis) { } template <> -bp::object axis_array_interface>( - const bha::category<>& axis) { +bp::object axis_array_interface>(const bha::category<>& axis) { bp::dict d; auto shape = bp::make_tuple(axis.size()); d["shape"] = shape; @@ -210,15 +203,14 @@ struct axis_suite : public bp::def_visitor> { "\n:returns: bin index for the passed value", bp::args("self", "x")); cl.def("__len__", &T::size, - ":returns: number of bins, excluding over-/underflow bins.", - bp::arg("self")); + ":returns: number of bins, excluding over-/underflow bins.", bp::arg("self")); cl.def("__getitem__", axis_getitem, ":param integer i: bin index" "\n:returns: bin corresponding to index", bp::args("self", "i")); cl.def("__iter__", make_generic_iterator); - cl.def("__repr__", generic_repr, - ":returns: string representation of this axis", bp::arg("self")); + cl.def("__repr__", generic_repr, ":returns: string representation of this axis", + bp::arg("self")); cl.def(bp::self == bp::self); #ifdef HAVE_NUMPY cl.add_property("__array_interface__", &axis_array_interface); @@ -227,35 +219,32 @@ struct axis_suite : public bp::def_visitor> { }; template -bha::regular* regular_init(unsigned bin, double lower, - double upper, bp::str pylabel, - bool with_uoflow) { - const auto uo = with_uoflow ? bha::uoflow::on : bha::uoflow::off; - return new bha::regular( - bin, lower, upper, {bp::extract(pylabel)(), - static_cast(bp::len(pylabel))}, +bha::regular* regular_init(unsigned bin, double lower, double upper, + bp::str pylabel, bool with_uoflow) { + const auto uo = with_uoflow ? bha::uoflow_type::on : bha::uoflow_type::off; + return new bha::regular( + bin, lower, upper, + {bp::extract(pylabel)(), static_cast(bp::len(pylabel))}, uo); } -bha::regular* regular_pow_init( - unsigned bin, double lower, double upper, double power, bp::str pylabel, - bool with_uoflow) { +bha::regular* regular_pow_init(unsigned bin, double lower, + double upper, double power, + bp::str pylabel, bool with_uoflow) { using namespace ::boost::python; - const auto uo = with_uoflow ? bha::uoflow::on : bha::uoflow::off; - return new bha::regular( - bin, lower, upper, {extract(pylabel)(), - static_cast(bp::len(pylabel))}, - uo, power); + const auto uo = with_uoflow ? bha::uoflow_type::on : bha::uoflow_type::off; + return new bha::regular( + bin, lower, upper, + {extract(pylabel)(), static_cast(bp::len(pylabel))}, uo, + power); } -bha::integer<>* integer_init(int lower, int upper, bp::str pylabel, - bool with_uoflow) { +bha::integer<>* integer_init(int lower, int upper, bp::str pylabel, bool with_uoflow) { using namespace ::boost::python; - const auto uo = with_uoflow ? bha::uoflow::on : bha::uoflow::off; - return new bha::integer<>(lower, upper, - {extract(pylabel)(), - static_cast(bp::len(pylabel))}, - uo); + const auto uo = with_uoflow ? bha::uoflow_type::on : bha::uoflow_type::off; + return new bha::integer<>( + lower, upper, + {extract(pylabel)(), static_cast(bp::len(pylabel))}, uo); } void register_axis_types() { @@ -273,75 +262,67 @@ void register_axis_types() { "Axis for real-valued data and bins of equal width." "\nBinning is a O(1) operation.", no_init) - .def("__init__", - make_constructor(regular_init, - default_call_policies(), - (arg("bin"), arg("lower"), arg("upper"), - arg("label") = "", arg("uoflow") = true))) + .def("__init__", make_constructor(regular_init, + default_call_policies(), + (arg("bin"), arg("lower"), arg("upper"), + arg("label") = "", arg("uoflow") = true))) .def(axis_suite>()); -#define BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(x) \ - class_>( \ - "regular_" #x, \ - "Axis for real-valued data and bins of equal width in " #x \ - "-space." \ - "\nBinning is a O(1) operation.", \ - no_init) \ - .def("__init__", \ - make_constructor(regular_init, \ - default_call_policies(), \ - (arg("bin"), arg("lower"), arg("upper"), \ - arg("label") = "", arg("uoflow") = true))) \ - .def(axis_suite>()) +#define BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(x) \ + class_>( \ + "regular_" #x, "Axis for real-valued data and bins of equal width in " #x \ + "-space." \ + "\nBinning is a O(1) operation.", \ + no_init) \ + .def("__init__", \ + make_constructor(regular_init, default_call_policies(), \ + (arg("bin"), arg("lower"), arg("upper"), arg("label") = "", \ + arg("uoflow") = true))) \ + .def(axis_suite>()) BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(log); BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(sqrt); // BOOST_HISTOGRAM_PYTHON_REGULAR_CLASS(cos); - class_>( + class_>( "regular_pow", "Axis for real-valued data and bins of equal width in power-space." "\nBinning is a O(1) operation.", no_init) .def("__init__", - make_constructor( - regular_pow_init, default_call_policies(), - (arg("bin"), arg("lower"), arg("upper"), arg("power"), - arg("label") = "", arg("uoflow") = true))) - .def(axis_suite>()); - - class_>( - "circular", - "Axis for real-valued angles." - "\nThere are no overflow/underflow bins for this axis," - "\nsince the axis is circular and wraps around after reaching" - "\nthe perimeter value. Binning is a O(1) operation.", - no_init) + make_constructor(regular_pow_init, default_call_policies(), + (arg("bin"), arg("lower"), arg("upper"), arg("power"), + arg("label") = "", arg("uoflow") = true))) + .def(axis_suite>()); + + class_>("circular", + "Axis for real-valued angles." + "\nThere are no overflow/underflow bins for this axis," + "\nsince the axis is circular and wraps around after reaching" + "\nthe perimeter value. Binning is a O(1) operation.", + no_init) .def(init( (arg("self"), arg("bin"), arg("phase") = 0.0, - arg("perimeter") = bh::detail::two_pi, arg("label") = ""))) + arg("perimeter") = bha::circular<>::two_pi(), arg("label") = ""))) .def(axis_suite>()); - class_>( - "variable", - "Axis for real-valued data and bins of varying width." - "\nBinning is a O(log(N)) operation. If speed matters and" - "\nthe problem domain allows it, prefer a regular axis.", - no_init) + class_>("variable", + "Axis for real-valued data and bins of varying width." + "\nBinning is a O(log(N)) operation. If speed matters and" + "\nthe problem domain allows it, prefer a regular axis.", + no_init) .def("__init__", raw_function(variable_init)) .def(init&>()) .def(axis_suite>()); - class_>( - "integer", - "An axis for a contiguous range of integers with bins" - "\nthat are one integer wide. Faster than a regular axis." - "\nBinning is a O(1) operation.", - no_init) - .def("__init__", - make_constructor(integer_init, default_call_policies(), - (arg("lower"), arg("upper"), arg("label") = "", - arg("uoflow") = true))) + class_>("integer", + "An axis for a contiguous range of integers with bins" + "\nthat are one integer wide. Faster than a regular axis." + "\nBinning is a O(1) operation.", + no_init) + .def("__init__", make_constructor(integer_init, default_call_policies(), + (arg("lower"), arg("upper"), arg("label") = "", + arg("uoflow") = true))) .def(axis_suite>()); class_>( diff --git a/src/python/histogram.cpp b/src/python/histogram.cpp index cc0b8ad2f..5588349ea 100644 --- a/src/python/histogram.cpp +++ b/src/python/histogram.cpp @@ -5,10 +5,11 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include +#include #include #include #include +#include #include #include #include @@ -23,97 +24,94 @@ namespace np = boost::python::numpy; #include #ifndef BOOST_HISTOGRAM_AXIS_LIMIT -#define BOOST_HISTOGRAM_AXIS_LIMIT 32 +#define BOOST_HISTOGRAM_AXIS_LIMIT 16 #endif -namespace mpl = boost::mpl; namespace bh = boost::histogram; namespace bp = boost::python; +namespace mp11 = boost::mp11; -using pyhistogram = bh::dynamic_histogram<>; - -namespace boost { -namespace python { +using pyhistogram = bh::histogram<>; #ifdef HAVE_NUMPY -class access { +namespace boost { +namespace histogram { +class python_access { public: - using mp_int = bh::detail::mp_int; - using wcount = bh::detail::wcount; - template - using array = bh::detail::array; + using mp_int = detail::mp_int; + using wcount = detail::wcount; - struct dtype_visitor : public boost::static_visitor { - list &shapes, &strides; - dtype_visitor(list& sh, list& st) : shapes(sh), strides(st) {} - template - str operator()(const array& /*unused*/) const { + struct dtype_visitor { + template + bp::str operator()(T*, const Buffer&, bp::list&, bp::list& strides) { strides.append(sizeof(T)); - return dtype_typestr(); + return bp::dtype_typestr(); } - str operator()(const array& /*unused*/) const { + template + bp::str operator()(void*, const Buffer&, bp::list&, bp::list& strides) { strides.append(sizeof(uint8_t)); - return dtype_typestr(); + return bp::dtype_typestr(); } - str operator()(const array& /*unused*/) const { + template + bp::str operator()(mp_int*, const Buffer&, bp::list&, bp::list& strides) { strides.append(sizeof(double)); - return dtype_typestr(); + return bp::dtype_typestr(); } - str operator()(const array& /*unused*/) const { + template + bp::str operator()(wcount*, const Buffer&, bp::list& shapes, bp::list& strides) { strides.append(sizeof(double)); strides.append(strides[-1] * 2); shapes.append(2); - return dtype_typestr(); + return bp::dtype_typestr(); } }; - struct data_visitor : public boost::static_visitor { - const list& shapes; - const list& strides; - data_visitor(const list& sh, const list& st) : shapes(sh), strides(st) {} - template - object operator()(const Array& b) const { - return make_tuple(reinterpret_cast(b.begin()), true); + struct data_visitor { + template + bp::object operator()(T* tp, const Buffer&, const bp::list&, const bp::list&) const { + return bp::make_tuple(reinterpret_cast(tp), true); } - object operator()(const array& /* unused */) const { + template + bp::object operator()(void*, const Buffer&, const bp::list& shapes, + const bp::list&) const { // cannot pass non-existent memory to numpy; make new // zero-initialized uint8 array, and pass it - return np::zeros(tuple(shapes), np::dtype::get_builtin()); + return np::zeros(bp::tuple(shapes), np::dtype::get_builtin()); } - object operator()(const array& b) const { + template + bp::object operator()(mp_int* tp, const Buffer& b, const bp::list& shapes, + const bp::list& strides) const { // cannot pass cpp_int to numpy; make new // double array, fill it and pass it - auto a = np::empty(tuple(shapes), np::dtype::get_builtin()); - for (auto i = 0l, n = bp::len(shapes); i < n; ++i) - const_cast(a.get_strides())[i] = - bp::extract(strides[i]); + auto a = np::empty(bp::tuple(shapes), np::dtype::get_builtin()); + for (std::size_t i = 0, n = bp::len(shapes); i < n; ++i) + const_cast(a.get_strides())[i] = bp::extract(strides[i]); auto* buf = (double*)a.get_data(); - for (auto i = 0ul; i < b.size; ++i) buf[i] = static_cast(b[i]); + for (std::size_t i = 0; i < b.size; ++i) { buf[i] = static_cast(tp[i]); } return a; } }; - static object array_interface(const pyhistogram& self) { - dict d; - list shapes; - list strides; + static bp::object array_interface(const pyhistogram& self) { + bp::dict d; + bp::list shapes; + bp::list strides; auto& b = self.storage_.buffer_; - d["typestr"] = boost::apply_visitor(dtype_visitor(shapes, strides), b); - for (auto i = 0u; i < self.dim(); ++i) { - if (i) strides.append(strides[-1] * shapes[-1]); + d["typestr"] = bh::detail::apply(dtype_visitor(), b, shapes, strides); + for (std::size_t i = 0; i < self.dim(); ++i) { + if (i > 0) strides.append(strides[-1] * shapes[-1]); shapes.append(self.axis(i).shape()); } if (self.dim() == 0) shapes.append(0); - d["shape"] = tuple(shapes); - d["strides"] = tuple(strides); - d["data"] = boost::apply_visitor(data_visitor(shapes, strides), b); + d["shape"] = bp::tuple(shapes); + d["strides"] = bp::tuple(strides); + d["data"] = bh::detail::apply(data_visitor(), b, shapes, strides); return d; } }; -#endif - -} // namespace python +} // namespace histogram } // namespace boost +#endif struct axis_visitor : public boost::static_visitor { template @@ -141,8 +139,7 @@ struct axes_appender { bp::object histogram_axis(const pyhistogram& self, int i) { if (i < 0) i += self.dim(); - if (i < 0 || i >= int(self.dim())) - throw std::out_of_range("axis index out of range"); + if (i < 0 || i >= int(self.dim())) throw std::out_of_range("axis index out of range"); return boost::apply_visitor(axis_visitor(), self.axis(i)); } @@ -159,8 +156,7 @@ bp::object histogram_init(bp::tuple args, bp::dict kwargs) { for (unsigned i = 0; i < dim; ++i) { bp::object pa = args[i + 1]; bool success = false; - boost::mp11::mp_for_each( - axes_appender(pa, axes, success)); + boost::mp11::mp_for_each(axes_appender(pa, axes, success)); if (!success) { std::string msg = "require an axis object, got "; msg += bp::extract(bp::str(pa)); @@ -168,7 +164,7 @@ bp::object histogram_init(bp::tuple args, bp::dict kwargs) { bp::throw_error_already_set(); } } - pyhistogram h(axes.begin(), axes.end()); + pyhistogram h(axes, typename pyhistogram::storage_type()); return self.attr("__init__")(std::move(h)); } @@ -208,25 +204,23 @@ struct fetcher { template struct span { T* data; - unsigned size; + std::size_t size; const T* begin() const { return data; } const T* end() const { return data + size; } }; -bp::object histogram_fill(bp::tuple args, bp::dict kwargs) { +bp::object histogram_call(bp::tuple args, bp::dict kwargs) { const auto nargs = bp::len(args); pyhistogram& self = bp::extract(args[0]); const unsigned dim = nargs - 1; if (dim != self.dim()) { - throw std::invalid_argument( - "number of arguments and dimension do not match"); + throw std::invalid_argument("number of arguments and dimension do not match"); } if (dim > BOOST_HISTOGRAM_AXIS_LIMIT) { throw std::invalid_argument( - bh::detail::cat("too many arguments, maximum is ", - BOOST_HISTOGRAM_AXIS_LIMIT) + bh::detail::cat("too many arguments, maximum is ", BOOST_HISTOGRAM_AXIS_LIMIT) .c_str()); } @@ -254,8 +248,7 @@ bp::object histogram_fill(bp::tuple args, bp::dict kwargs) { fetch_weight.assign(kwargs.get("weight")); if (fetch_weight.n > 0) { if (n > 0 && fetch_weight.n != n) { - throw std::invalid_argument( - "length of weight sequence does not match"); + throw std::invalid_argument("length of weight sequence does not match"); } n = fetch_weight.n; } @@ -265,8 +258,7 @@ bp::object histogram_fill(bp::tuple args, bp::dict kwargs) { if (!n) ++n; if (dim == 1) { if (fetch_weight.n >= 0) { - for (auto i = 0l; i < n; ++i) - self(bh::weight(fetch_weight[i]), fetch[0][i]); + for (auto i = 0l; i < n; ++i) self(bh::weight(fetch_weight[i]), fetch[0][i]); } else { for (auto i = 0l; i < n; ++i) self(fetch[0][i]); } @@ -296,61 +288,56 @@ bp::object histogram_getitem(const pyhistogram& self, bp::object args) { } const unsigned dim = bp::len(args); - if (self.dim() != dim) { - throw std::invalid_argument("wrong number of arguments"); - } + if (self.dim() != dim) { throw std::invalid_argument("wrong number of arguments"); } if (dim > BOOST_HISTOGRAM_AXIS_LIMIT) { throw std::invalid_argument( - bh::detail::cat("too many arguments, maximum is ", - BOOST_HISTOGRAM_AXIS_LIMIT) + bh::detail::cat("too many arguments, maximum is ", BOOST_HISTOGRAM_AXIS_LIMIT) .c_str()); } int idx[BOOST_HISTOGRAM_AXIS_LIMIT]; - for (unsigned i = 0; i < dim; ++i) idx[i] = bp::extract(args[i]); + for (std::size_t i = 0; i < dim; ++i) idx[i] = bp::extract(args[i]); return bp::object(self.at(span{idx, self.dim()})); } bp::object histogram_at(bp::tuple args, bp::dict kwargs) { - const pyhistogram& self = bp::extract(args[0]); - if (kwargs) { throw std::invalid_argument("no keyword arguments allowed"); } + const pyhistogram& self = bp::extract(args[0]); bp::object a = args.slice(1, bp::_); return histogram_getitem(self, bp::extract(a)); } bp::object histogram_reduce_to(bp::tuple args, bp::dict kwargs) { + if (kwargs) { throw std::invalid_argument("no keyword arguments allowed"); } const pyhistogram& self = bp::extract(args[0]); - const unsigned nargs = bp::len(args) - 1; + const auto nargs = bp::len(args) - 1; + if (nargs == 0) { throw std::invalid_argument("at least one argument required"); } if (nargs > BOOST_HISTOGRAM_AXIS_LIMIT) { throw std::invalid_argument( - bh::detail::cat("too many arguments, maximum is ", - BOOST_HISTOGRAM_AXIS_LIMIT) + bh::detail::cat("too many arguments, maximum is ", BOOST_HISTOGRAM_AXIS_LIMIT) .c_str()); } - if (kwargs) { throw std::invalid_argument("no keyword arguments allowed"); } - int idx[BOOST_HISTOGRAM_AXIS_LIMIT]; - for (auto i = 0u; i < nargs; ++i) idx[i] = bp::extract(args[1 + i]); + for (auto i = 0u; i < nargs; ++i) { + idx[i] = bp::extract(args[1 + i]); + if (i > 0 && idx[i] <= idx[i - 1]) + throw std::invalid_argument("indices must be strictly ascending"); + } return bp::object(self.reduce_to(idx, idx + nargs)); } -std::string histogram_repr(const pyhistogram& h) { - return bh::detail::cat(h); -} +std::string histogram_repr(const pyhistogram& h) { return bh::detail::cat(h); } double element_value(const pyhistogram::element_type& b) { return b.value(); } -double element_variance(const pyhistogram::element_type& b) { - return b.variance(); -} +double element_variance(const pyhistogram::element_type& b) { return b.variance(); } double element_getitem(const pyhistogram::element_type& e, int i) { if (i < 0 || i > 1) throw std::out_of_range("element_getitem"); @@ -360,8 +347,7 @@ double element_getitem(const pyhistogram::element_type& e, int i) { int element_len(const pyhistogram::element_type&) { return 2; } std::string element_repr(const pyhistogram::element_type& e) { - return bh::detail::cat("histogram.element(", e.value(), ", ", e.variance(), - ")"); + return bh::detail::cat("histogram.element(", e.value(), ", ", e.variance(), ")"); } void register_histogram() { @@ -369,30 +355,28 @@ void register_histogram() { bp::scope s = bp::class_>( - "histogram", "N-dimensional histogram for real-valued data.", - bp::no_init) + "histogram", "N-dimensional histogram for real-valued data.", bp::no_init) .def("__init__", bp::raw_function(histogram_init), ":param axis args: axis objects" "\nPass one or more axis objects to configure the histogram.") // shadowed C++ ctors .def(bp::init()) -// .def(bp::init()) +// .def(bp::init()) move-ctor doesn't work with boost.python #ifdef HAVE_NUMPY - .add_property("__array_interface__", &bp::access::array_interface) + .add_property("__array_interface__", &bh::python_access::array_interface) #endif .add_property("dim", &pyhistogram::dim) .def("axis", histogram_axis, bp::arg("i") = 0, ":param int i: axis index" "\n:return: corresponding axis object") - .def( - "__call__", bp::raw_function(histogram_fill), - ":param double args: values (number must match dimension)" - "\n:keyword double weight: optional weight" - "\n" - "\nIf Numpy support is enabled, 1d-arrays can be passed " - "instead of" - "\nvalues, which must be equal in lenght. Arrays and values can" - "\nbe mixed arbitrarily in the same call.") + .def("__call__", bp::raw_function(histogram_call), + ":param double args: values (number must match dimension)" + "\n:keyword double weight: optional weight" + "\n" + "\nIf Numpy support is enabled, 1d-arrays can be passed " + "instead of" + "\nvalues, which must be equal in lenght. Arrays and values can" + "\nbe mixed arbitrarily in the same call.") .def("__len__", &pyhistogram::size, ":return: total number of bins, including under- and overflow") .def("at", bp::raw_function(histogram_at), @@ -406,7 +390,7 @@ void register_histogram() { .def("reduce_to", bp::raw_function(histogram_reduce_to), ":param int args: indices of the axes in the reduced histogram" "\n:return: reduced histogram with subset of axes") - .def("__iter__", bp::iterator()) + .def("__iter__", bp::iterator()) .def("__repr__", histogram_repr, ":return: string representation of the histogram") .def(bp::self == bp::self) @@ -419,8 +403,7 @@ void register_histogram() { .def_pickle(bh::serialization_suite()); bp::class_( - "element", "Holds value and variance of bin count.", - bp::init()) + "element", "Holds value and variance of bin count.", bp::init()) .add_property("value", element_value) .add_property("variance", element_variance) .def("__getitem__", element_getitem) diff --git a/src/python/module.cpp b/src/python/module.cpp index c57ea2e28..cd22e2924 100644 --- a/src/python/module.cpp +++ b/src/python/module.cpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #ifdef HAVE_NUMPY #include #endif @@ -16,6 +18,10 @@ void register_histogram(); BOOST_PYTHON_MODULE(histogram) { using namespace boost::python; + + register_exception_translator( + [](const std::out_of_range& e) { PyErr_SetString(PyExc_IndexError, e.what()); }); + scope current; #ifdef HAVE_NUMPY numpy::initialize(); diff --git a/src/python/serialization_suite.hpp b/src/python/serialization_suite.hpp index 0e6327088..bde91c5e9 100644 --- a/src/python/serialization_suite.hpp +++ b/src/python/serialization_suite.hpp @@ -38,14 +38,14 @@ class python_bytes_sink : public iostreams::sink { std::streamsize write(const char* s, std::streamsize n) { if (len_ == 0) { *pstr_ = PyBytes_FromStringAndSize(s, n); - if (*pstr_ == 0) // no point trying to recover from allocation error - std::abort(); + if (*pstr_ == nullptr) // no point trying to recover from allocation error + std::terminate(); len_ = n; } else { if (pos_ + n > len_) { len_ = pos_ + n; if (_PyBytes_Resize(pstr_, len_) == -1) - std::abort(); // no point trying to recover from allocation error + std::terminate(); // no point trying to recover from allocation error } char* b = PyBytes_AS_STRING(*pstr_); std::copy(s, s + n, b + pos_); @@ -63,7 +63,7 @@ class python_bytes_sink : public iostreams::sink { template struct serialization_suite : python::pickle_suite { static python::tuple getstate(python::object obj) { - PyObject* pobj = 0; + PyObject* pobj = nullptr; iostreams::stream os(&pobj); archive::text_oarchive oa(os); oa << python::extract(obj)(); diff --git a/test/adaptive_storage_test.cpp b/test/adaptive_storage_test.cpp index ade5e4810..3375f0031 100644 --- a/test/adaptive_storage_test.cpp +++ b/test/adaptive_storage_test.cpp @@ -4,31 +4,35 @@ // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include -#include #include #ifndef BOOST_HISTOGRAM_NO_SERIALIZATION +#include +#include #include #endif +#include #include #include #include #include +#include #include +using adaptive_storage_type = boost::histogram::adaptive_storage<>; + using namespace boost::histogram; template -adaptive_storage prepare(std::size_t n) { - auto a = detail::array(n); - return adaptive_storage(a); +adaptive_storage_type prepare(std::size_t n, const T x) { + std::unique_ptr v(new T[n]); + std::fill(v.get(), v.get() + n, static_cast(0)); + v.get()[0] = x; + return adaptive_storage_type(n, v.get()); } template -adaptive_storage prepare(std::size_t n, const T x) { - auto a = detail::array(n); - a[0] = x; - return adaptive_storage(a); +adaptive_storage_type prepare(std::size_t n) { + return adaptive_storage_type(n, static_cast(nullptr)); } template @@ -61,7 +65,7 @@ void serialization_impl() { oa << a; buf = os.str(); } - adaptive_storage b; + adaptive_storage_type b; BOOST_TEST(!(a == b)); { std::istringstream is(buf); @@ -73,7 +77,7 @@ void serialization_impl() { template <> void serialization_impl() { - adaptive_storage a(std::size_t(1)); + const auto a = prepare(1); std::ostringstream os; std::string buf; { @@ -82,7 +86,7 @@ void serialization_impl() { oa << a; buf = os2.str(); } - adaptive_storage b; + adaptive_storage_type b; BOOST_TEST(!(a == b)); { std::istringstream is(buf); @@ -95,7 +99,7 @@ void serialization_impl() { template void equal_impl() { - adaptive_storage a(std::size_t(1)); + auto a = prepare(1); auto b = prepare(1, T(0)); BOOST_TEST_EQ(a[0].value(), 0.0); BOOST_TEST_EQ(a[0].variance(), 0.0); @@ -103,7 +107,8 @@ void equal_impl() { b.increase(0); BOOST_TEST(!(a == b)); - array_storage c(std::size_t(1)); + array_storage c; + c.reset(1); auto d = prepare(1, T(0)); BOOST_TEST(c == d); c.increase(0); @@ -112,10 +117,10 @@ void equal_impl() { template <> void equal_impl() { - adaptive_storage a(std::size_t(1)); + auto a = prepare(1); auto b = prepare(1, 0); auto c = prepare(2, 0); - auto d = array_storage(std::size_t(1)); + auto d = prepare(1); BOOST_TEST_EQ(a[0].value(), 0.0); BOOST_TEST_EQ(a[0].variance(), 0.0); BOOST_TEST(a == b); @@ -141,7 +146,7 @@ void increase_and_grow_impl() { n.increase(0); - adaptive_storage x(std::size_t(2)); + auto x = prepare(2); x.increase(0); n2.add(0, x[0].value()); @@ -155,7 +160,7 @@ void increase_and_grow_impl() { template <> void increase_and_grow_impl() { - adaptive_storage s(std::size_t(2)); + auto s = prepare(2); BOOST_TEST_EQ(s[0].value(), 0); BOOST_TEST_EQ(s[1].value(), 0); s.increase(0); @@ -166,7 +171,8 @@ void increase_and_grow_impl() { template void convert_array_storage_impl() { const auto aref = prepare(1, T(0)); - array_storage s(std::size_t(1)); + array_storage s; + s.reset(1); s.increase(0); auto a = aref; @@ -176,7 +182,7 @@ void convert_array_storage_impl() { a.increase(0); BOOST_TEST(!(a == s)); - adaptive_storage b(s); + adaptive_storage_type b(s); BOOST_TEST_EQ(b[0].value(), 1.0); BOOST_TEST(b == s); b.increase(0); @@ -188,7 +194,8 @@ void convert_array_storage_impl() { BOOST_TEST(c == s); BOOST_TEST(s == c); - array_storage t(std::size_t(1)); + array_storage t; + t.reset(1); t.increase(0); while (t[0] < 1e20) t.add(0, t[0]); auto d = aref; @@ -202,7 +209,7 @@ void convert_array_storage_impl() { e.increase(0); BOOST_TEST(!(e == s)); - adaptive_storage f(s); + adaptive_storage_type f(s); BOOST_TEST_EQ(f[0].value(), 1.0); BOOST_TEST(f == s); f.increase(0); @@ -214,7 +221,8 @@ void convert_array_storage_impl() { BOOST_TEST(g == s); BOOST_TEST(s == g); - array_storage u(std::size_t(2)); + array_storage u; + u.reset(2); u.increase(0); auto h = aref; BOOST_TEST(!(h == u)); @@ -224,9 +232,10 @@ void convert_array_storage_impl() { template <> void convert_array_storage_impl() { - const auto aref = adaptive_storage(std::size_t(1)); + const auto aref = prepare(1); BOOST_TEST_EQ(aref[0].value(), 0.0); - array_storage s(std::size_t(1)); + array_storage s; + s.reset(1); s.increase(0); auto a = aref; @@ -242,7 +251,8 @@ void convert_array_storage_impl() { BOOST_TEST(c == s); BOOST_TEST(s == c); - array_storage t(std::size_t(2)); + array_storage t; + t.reset(2); t.increase(0); auto d = aref; BOOST_TEST(!(d == t)); @@ -302,7 +312,7 @@ int main() { // empty state { - adaptive_storage a; + adaptive_storage_type a; BOOST_TEST_EQ(a.size(), 0); } @@ -358,12 +368,12 @@ int main() { // add_and_grow { - adaptive_storage a(std::size_t(1)); + auto a = prepare(1); a += a; BOOST_TEST_EQ(a[0].value(), 0); a.increase(0); double x = 1; - adaptive_storage b(std::size_t(1)); + auto b = prepare(1); b.increase(0); BOOST_TEST_EQ(b[0].value(), x); for (unsigned i = 0; i < 80; ++i) { @@ -374,15 +384,15 @@ int main() { BOOST_TEST_EQ(a[0].variance(), x); BOOST_TEST_EQ(b[0].value(), x); BOOST_TEST_EQ(b[0].variance(), x); - adaptive_storage c(std::size_t(1)); + auto c = prepare(1); c.add(0, a[0].value()); BOOST_TEST_EQ(c[0].value(), x); BOOST_TEST_EQ(c[0].variance(), x); c.add(0, weight(0)); BOOST_TEST_EQ(c[0].value(), x); BOOST_TEST_EQ(c[0].variance(), x); - adaptive_storage d(std::size_t(1)); - d.add(0, weight(a[0].value())); + auto d = prepare(1); + d.add(0, weight(x)); BOOST_TEST_EQ(d[0].value(), x); BOOST_TEST_EQ(d[0].variance(), x * x); } @@ -390,7 +400,7 @@ int main() { // multiply { - adaptive_storage a(std::size_t(2)); + auto a = prepare(2); a *= 2; BOOST_TEST_EQ(a[0].value(), 0); BOOST_TEST_EQ(a[1].value(), 0); @@ -400,7 +410,7 @@ int main() { BOOST_TEST_EQ(a[0].variance(), 9); BOOST_TEST_EQ(a[1].value(), 0); BOOST_TEST_EQ(a[1].variance(), 0); - a.add(1, adaptive_storage::element_type(2, 5)); + a.add(1, adaptive_storage_type::element_type(2, 5)); BOOST_TEST_EQ(a[0].value(), 3); BOOST_TEST_EQ(a[0].variance(), 9); BOOST_TEST_EQ(a[1].value(), 2); diff --git a/test/array_storage_test.cpp b/test/array_storage_test.cpp index 65c669bd4..ff6d83e81 100644 --- a/test/array_storage_test.cpp +++ b/test/array_storage_test.cpp @@ -5,11 +5,8 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include #include -#include -#include -#include +#include #include #include #include @@ -18,17 +15,26 @@ int main() { using namespace boost::histogram; - // ctor + // ctor and reset { - array_storage a(1); - BOOST_TEST_EQ(a.size(), 1u); + array_storage a; + BOOST_TEST_EQ(a.size(), 0); + a.reset(1); + BOOST_TEST_EQ(a.size(), 1); + a.increase(0); + BOOST_TEST_EQ(a[0], 1); + a.reset(1); BOOST_TEST_EQ(a[0], 0); } // increase { - array_storage a(1), b(1); - array_storage c(1), d(2); + array_storage a, b; + a.reset(1); + b.reset(1); + array_storage c, d; + c.reset(1); + d.reset(2); a.increase(0); b.increase(0); c.increase(0); @@ -49,7 +55,8 @@ int main() { // multiply { - array_storage a(2); + array_storage a; + a.reset(2); a.increase(0); a *= 3; BOOST_TEST_EQ(a[0], 3); @@ -64,9 +71,11 @@ int main() { // copy { - array_storage a(1); + array_storage a; + a.reset(1); a.increase(0); - decltype(a) b(2); + decltype(a) b; + b.reset(2); BOOST_TEST(!(a == b)); b = a; BOOST_TEST(a == b); @@ -78,7 +87,8 @@ int main() { BOOST_TEST_EQ(c.size(), 1); BOOST_TEST_EQ(c[0], 1); - array_storage d(1); + array_storage d; + d.reset(1); BOOST_TEST(!(a == d)); d = a; BOOST_TEST(a == d); @@ -88,7 +98,8 @@ int main() { // move { - array_storage a(1); + array_storage a; + a.reset(1); a.increase(0); decltype(a) b; BOOST_TEST(!(a == b)); @@ -104,7 +115,8 @@ int main() { // with weight_counter { - array_storage> a(1); + array_storage> a; + a.reset(1); a.increase(0); a.add(0, 1); a.add(0, weight_counter(1, 0)); diff --git a/test/axis_test.cpp b/test/axis_test.cpp index f581ebb6e..e8eda59df 100644 --- a/test/axis_test.cpp +++ b/test/axis_test.cpp @@ -4,20 +4,21 @@ // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#include #include +#include #include #include #include -#include -#include +#include #include #include #include #include +#include "utility.hpp" using namespace boost::histogram; -#define BOOST_TEST_NOT(expr) BOOST_TEST(!(expr)) #define BOOST_TEST_IS_CLOSE(a, b, eps) BOOST_TEST(std::abs(a - b) < eps) template @@ -51,8 +52,7 @@ int main() { { axis::regular<> a{4, -2, 2}; BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); - BOOST_TEST_EQ(a[a.size()].upper(), - std::numeric_limits::infinity()); + BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); axis::regular<> b; BOOST_TEST_NOT(a == b); b = a; @@ -81,7 +81,7 @@ int main() { // axis::regular with log transform { - axis::regular b{2, 1e0, 1e2}; + axis::regular b{2, 1e0, 1e2}; BOOST_TEST_EQ(b[-1].lower(), 0.0); BOOST_TEST_IS_CLOSE(b[0].lower(), 1.0, 1e-9); BOOST_TEST_IS_CLOSE(b[1].lower(), 10.0, 1e-9); @@ -100,7 +100,7 @@ int main() { // axis::regular with sqrt transform { - axis::regular b{2, 0, 4}; + axis::regular b{2, 0, 4}; // this is weird: -inf * -inf = inf, thus the lower bound BOOST_TEST_EQ(b[-1].lower(), std::numeric_limits::infinity()); BOOST_TEST_IS_CLOSE(b[0].lower(), 0.0, 1e-9); @@ -150,8 +150,7 @@ int main() { { axis::variable<> a{-1, 0, 1}; BOOST_TEST_EQ(a[-1].lower(), -std::numeric_limits::infinity()); - BOOST_TEST_EQ(a[a.size()].upper(), - std::numeric_limits::infinity()); + BOOST_TEST_EQ(a[a.size()].upper(), std::numeric_limits::infinity()); axis::variable<> b; BOOST_TEST_NOT(a == b); b = a; @@ -212,6 +211,9 @@ int main() { BOOST_TEST_NOT(a == b); b = a; BOOST_TEST_EQ(a, b); + b = axis::category{{B, A, C}}; + BOOST_TEST_NOT(a == b); + b = a; b = b; BOOST_TEST_EQ(a, b); axis::category c = std::move(b); @@ -235,8 +237,8 @@ int main() { // iterators { enum { A, B, C }; - test_axis_iterator(axis::regular<>(5, 0, 1, "", axis::uoflow::off), 0, 5); - test_axis_iterator(axis::regular<>(5, 0, 1, "", axis::uoflow::on), 0, 5); + test_axis_iterator(axis::regular<>(5, 0, 1, "", axis::uoflow_type::off), 0, 5); + test_axis_iterator(axis::regular<>(5, 0, 1, "", axis::uoflow_type::on), 0, 5); test_axis_iterator(axis::circular<>(5, 0, 1, ""), 0, 5); test_axis_iterator(axis::variable<>({1, 2, 3}, ""), 0, 2); test_axis_iterator(axis::integer<>(0, 4, ""), 0, 4); @@ -262,8 +264,7 @@ int main() { a6 = a1; BOOST_TEST_EQ(a6, a1); axis::any, axis::integer<>> a7(axis::integer<>(0, 2)); - BOOST_TEST_THROWS(axis::any> a8(a7), - std::invalid_argument); + BOOST_TEST_THROWS(axis::any> a8(a7), std::invalid_argument); BOOST_TEST_THROWS(a4 = a7, std::invalid_argument); } @@ -286,18 +287,17 @@ int main() { std::string b = "B"; std::vector axes; axes.push_back(axis::regular<>{2, -1, 1, "regular1"}); - axes.push_back(axis::regular( - 2, 1, 10, "regular2", axis::uoflow::off)); - axes.push_back(axis::regular( - 2, 1, 10, "regular3", axis::uoflow::on, 0.5)); - axes.push_back(axis::regular( - 2, 1, 10, "regular4", axis::uoflow::off, -0.5)); + axes.push_back(axis::regular(2, 1, 10, "regular2", + axis::uoflow_type::off)); + axes.push_back(axis::regular(2, 1, 10, "regular3", + axis::uoflow_type::on, 0.5)); + axes.push_back(axis::regular(2, 1, 10, "regular4", + axis::uoflow_type::off, -0.5)); axes.push_back(axis::circular<>(4, 0.1, 1.0, "polar")); - axes.push_back( - axis::variable<>({-1, 0, 1}, "variable", axis::uoflow::off)); + axes.push_back(axis::variable<>({-1, 0, 1}, "variable", axis::uoflow_type::off)); axes.push_back(axis::category<>({A, B, C}, "category")); axes.push_back(axis::category({a, b}, "category2")); - axes.push_back(axis::integer<>(-1, 1, "integer", axis::uoflow::off)); + axes.push_back(axis::integer<>(-1, 1, "integer", axis::uoflow_type::off)); std::ostringstream os; for (const auto& a : axes) { os << a << "\n"; } os << axes.back()[0]; @@ -320,8 +320,8 @@ int main() { enum { A, B, C }; std::vector axes; axes.push_back(axis::regular<>{2, -1, 1}); - axes.push_back(axis::regular( - 2, 1, 4, "", axis::uoflow::on, 0.5)); + axes.push_back( + axis::regular(2, 1, 4, "", axis::uoflow_type::on, 0.5)); axes.push_back(axis::circular<>{4}); axes.push_back(axis::variable<>{-1, 0, 1}); axes.push_back(axis::category<>{A, B, C}); @@ -346,13 +346,12 @@ int main() { // sequence equality { enum { A, B, C }; - std::vector, axis::variable<>, axis::category<>, - axis::integer<>>> + std::vector< + axis::any, axis::variable<>, axis::category<>, axis::integer<>>> std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, axis::category<>{A, B, C}}; - std::vector< - axis::any, axis::variable<>, axis::category<>>> + std::vector, axis::variable<>, axis::category<>>> std_vector2 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, axis::category<>{{A, B, C}}}; @@ -366,16 +365,13 @@ int main() { BOOST_TEST_NOT(detail::axes_equal(std_vector2, std_vector3)); BOOST_TEST_NOT(detail::axes_equal(std_vector3, std_vector4)); - auto tuple1 = - std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B, C}}); + auto tuple1 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{{A, B, C}}); - auto tuple2 = - std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B}}); + auto tuple2 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{{A, B}}); - auto tuple3 = std::make_tuple(axis::regular<>{2, -1, 1}, - axis::variable<>{-1, 0, 1}); + auto tuple3 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}); BOOST_TEST(detail::axes_equal(std_vector1, tuple1)); BOOST_TEST(detail::axes_equal(tuple1, std_vector1)); @@ -387,22 +383,20 @@ int main() { // sequence assign { enum { A, B, C, D }; - std::vector, axis::variable<>, axis::category<>, - axis::integer<>>> + std::vector< + axis::any, axis::variable<>, axis::category<>, axis::integer<>>> std_vector1 = {axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, axis::category<>{A, B, C}}; - std::vector< - axis::any, axis::variable<>, axis::category<>>> + std::vector, axis::variable<>, axis::category<>>> std_vector2 = {axis::regular<>{2, -2, 2}, axis::variable<>{-2, 0, 2}, axis::category<>{A, B}}; detail::axes_assign(std_vector2, std_vector1); BOOST_TEST(detail::axes_equal(std_vector2, std_vector1)); - auto tuple1 = - std::make_tuple(axis::regular<>{2, -3, 3}, axis::variable<>{-3, 0, 3}, - axis::category<>{A, B, C, D}); + auto tuple1 = std::make_tuple(axis::regular<>{2, -3, 3}, axis::variable<>{-3, 0, 3}, + axis::category<>{A, B, C, D}); detail::axes_assign(tuple1, std_vector1); BOOST_TEST(detail::axes_equal(tuple1, std_vector1)); @@ -412,13 +406,91 @@ int main() { detail::axes_assign(std_vector3, tuple1); BOOST_TEST(detail::axes_equal(std_vector3, tuple1)); - auto tuple2 = - std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, - axis::category<>{A, B}); + auto tuple2 = std::make_tuple(axis::regular<>{2, -1, 1}, axis::variable<>{-1, 0, 1}, + axis::category<>{A, B}); detail::axes_assign(tuple2, tuple1); BOOST_TEST(detail::axes_equal(tuple2, tuple1)); } + // sub_axes + { + using ra = axis::regular<>; + using ia = axis::integer<>; + using ca = axis::category<>; + using T = std::tuple; + BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + BOOST_TEST_TRAIT_TRUE( + (std::is_same, std::tuple>)); + } + + // make_sub_tuple + { + using ia = axis::integer<>; + using T = std::tuple; + auto axes = T(ia(0, 1), ia(1, 2), ia(2, 3)); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i1(), i2()), + (std::tuple(ia(1, 2), ia(2, 3)))); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i0(), i1()), + (std::tuple(ia(0, 1), ia(1, 2)))); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i1()), (std::tuple(ia(1, 2)))); + BOOST_TEST_EQ(detail::make_sub_axes(axes, i0(), i1(), i2()), axes); + } + + // vector of axes with custom allocators + { + using T1 = axis::regular>; + using T2 = axis::circular>; + using T3 = axis::variable>; + using T4 = axis::integer>; + using T5 = axis::category>; + using axis_type = axis::any; // no heap allocation + using axes_type = std::vector>; + + tracing_allocator_db db; + { + auto a = tracing_allocator(db); + const auto label = std::string(512, 'c'); + axes_type axes(a); + axes.reserve(5); + axes.emplace_back(T1(1, 0, 1, label, axis::uoflow_type::on, {}, a)); + axes.emplace_back(T2(2, 0, T2::two_pi(), label, a)); + axes.emplace_back(T3({0., 1., 2.}, label, axis::uoflow_type::on, a)); + axes.emplace_back(T4(0, 4, label, axis::uoflow_type::on, a)); + axes.emplace_back(T5({1, 2, 3, 4, 5}, label, axis::uoflow_type::on, a)); + } + // 5 axis::any objects + BOOST_TEST_EQ(db[typeid(axis_type)].first, db[typeid(axis_type)].second); + BOOST_TEST_EQ(db[typeid(axis_type)].first, 5); + + // 5 labels + BOOST_TEST_EQ(db[typeid(char)].first, db[typeid(char)].second); + BOOST_TEST_GE(db[typeid(char)].first, 5 * 512u); + + // nothing to allocate for T1 + // nothing to allocate for T2 + // T3 allocates storage for bin edges + BOOST_TEST_EQ(db[typeid(double)].first, db[typeid(double)].second); + BOOST_TEST_EQ(db[typeid(double)].first, 3u); + // nothing to allocate for T4 + // T5 allocates storage for long array + BOOST_TEST_EQ(db[typeid(long)].first, db[typeid(long)].second); + BOOST_TEST_EQ(db[typeid(long)].first, 5u); + +#if (BOOST_MSVC) + BOOST_TEST_EQ(db.size(), 5); // axis_type, char, double, long + ??? +#else + BOOST_TEST_EQ(db.size(), 4); // axis_type, char, double, long +#endif + } + return boost::report_errors(); } diff --git a/test/detail_test.cpp b/test/detail_test.cpp index 711618cda..dfb90e696 100644 --- a/test/detail_test.cpp +++ b/test/detail_test.cpp @@ -5,56 +5,39 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include -#include -#include +#include #include -#include -#include -#include -#include #include #include -#include -#include -#include -#include #include "utility.hpp" -using namespace boost::histogram::detail; +namespace bhd = boost::histogram::detail; +namespace bhad = boost::histogram::axis::detail; int main() { // escape0 { std::ostringstream os; - escape(os, std::string("abc")); + bhad::escape_string(os, std::string("abc")); BOOST_TEST_EQ(os.str(), std::string("'abc'")); } // escape1 { std::ostringstream os; - escape(os, std::string("abc\n")); + bhad::escape_string(os, std::string("abc\n")); BOOST_TEST_EQ(os.str(), std::string("'abc\n'")); } // escape2 { std::ostringstream os; - escape(os, std::string("'abc'")); + bhad::escape_string(os, std::string("'abc'")); BOOST_TEST_EQ(os.str(), std::string("'\\\'abc\\\''")); } // cat - { BOOST_TEST_EQ(cat("foo", 1, "bar"), std::string("foo1bar")); } - - // bool mask - { - auto v1 = bool_mask(4, false); - BOOST_TEST_EQ(v1, std::vector({true, false, false, true})); - - auto v2 = bool_mask(4, true); - BOOST_TEST_EQ(v2, std::vector({false, true, false, true})); - } + { BOOST_TEST_EQ(bhd::cat("foo", 1, "bar"), std::string("foo1bar")); } return boost::report_errors(); } diff --git a/test/fail_histogram_add_dynamic_test.cpp b/test/fail_histogram_dynamic_add_test.cpp similarity index 51% rename from test/fail_histogram_add_dynamic_test.cpp rename to test/fail_histogram_dynamic_add_test.cpp index 070666ba7..741e66479 100644 --- a/test/fail_histogram_add_dynamic_test.cpp +++ b/test/fail_histogram_dynamic_add_test.cpp @@ -1,3 +1,9 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + #include using namespace boost::histogram; diff --git a/test/fail_histogram_dynamic_at_tuple_wrong_dimension_test.cpp b/test/fail_histogram_dynamic_at_tuple_wrong_dimension_test.cpp new file mode 100644 index 000000000..0ed96e267 --- /dev/null +++ b/test/fail_histogram_dynamic_at_tuple_wrong_dimension_test.cpp @@ -0,0 +1,14 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::histogram; +int main() { + auto h = make_dynamic_histogram(axis::integer<>(0, 2)); + h.at(std::make_pair(0, 0)); +} diff --git a/test/fail_histogram_dynamic_at_vector_wrong_dimension_test.cpp b/test/fail_histogram_dynamic_at_vector_wrong_dimension_test.cpp new file mode 100644 index 000000000..9c5518674 --- /dev/null +++ b/test/fail_histogram_dynamic_at_vector_wrong_dimension_test.cpp @@ -0,0 +1,14 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include + +using namespace boost::histogram; +int main() { + auto h = make_dynamic_histogram(axis::integer<>(0, 2)); + h.at(std::vector({0, 0})); +} diff --git a/test/fail_histogram_dynamic_at_wrong_dimension_test.cpp b/test/fail_histogram_dynamic_at_wrong_dimension_test.cpp new file mode 100644 index 000000000..49bd99026 --- /dev/null +++ b/test/fail_histogram_dynamic_at_wrong_dimension_test.cpp @@ -0,0 +1,13 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +using namespace boost::histogram; +int main() { + auto h = make_dynamic_histogram(axis::integer<>(0, 2)); + h.at(0, 0); +} diff --git a/test/fail_histogram_dynamic_bin_0_test.cpp b/test/fail_histogram_dynamic_bin_0_test.cpp deleted file mode 100644 index c183aacb1..000000000 --- a/test/fail_histogram_dynamic_bin_0_test.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -using namespace boost::histogram; -int main() { - auto h = make_dynamic_histogram(axis::integer<>(0, 2)); - h.at(0, 0); -} diff --git a/test/fail_histogram_dynamic_bin_1_test.cpp b/test/fail_histogram_dynamic_bin_1_test.cpp deleted file mode 100644 index 4a86e789a..000000000 --- a/test/fail_histogram_dynamic_bin_1_test.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -using namespace boost::histogram; -int main() { - auto h = make_dynamic_histogram(axis::integer<>(0, 2)); - h.at(-2); -} diff --git a/test/fail_histogram_dynamic_bin_2_test.cpp b/test/fail_histogram_dynamic_bin_2_test.cpp deleted file mode 100644 index 9993558a7..000000000 --- a/test/fail_histogram_dynamic_bin_2_test.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -using namespace boost::histogram; -int main() { - auto h = - make_dynamic_histogram(axis::integer<>(0, 2), axis::integer<>(0, 2)); - h.at(std::make_pair(-2, 0)); -} diff --git a/test/fail_histogram_dynamic_bin_3_test.cpp b/test/fail_histogram_dynamic_bin_3_test.cpp deleted file mode 100644 index 26a5f8962..000000000 --- a/test/fail_histogram_dynamic_bin_3_test.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -using namespace boost::histogram; -int main() { - auto h = - make_dynamic_histogram(axis::integer<>(0, 2), axis::integer<>(0, 2)); - h.at(std::vector({-2, 0})); -} diff --git a/test/fail_histogram_dynamic_bin_4_test.cpp b/test/fail_histogram_dynamic_bin_4_test.cpp deleted file mode 100644 index 370cd929b..000000000 --- a/test/fail_histogram_dynamic_bin_4_test.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -using namespace boost::histogram; -int main() { - auto h = make_dynamic_histogram(axis::integer<>(0, 2)); - struct non_convertible_to_int {}; - h.at(non_convertible_to_int{}); -} diff --git a/test/fail_histogram_dynamic_reduce_wrong_order_test.cpp b/test/fail_histogram_dynamic_reduce_wrong_order_test.cpp new file mode 100644 index 000000000..201f5ccf6 --- /dev/null +++ b/test/fail_histogram_dynamic_reduce_wrong_order_test.cpp @@ -0,0 +1,14 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +using namespace boost::histogram; +int main() { + auto h = make_dynamic_histogram(axis::integer<>(0, 1), axis::integer<>(1, 2)); + auto v = {0, 0}; + h.reduce_to(v.begin(), v.end()); +} diff --git a/test/fail_histogram_add_static_test.cpp b/test/fail_histogram_static_add_test.cpp similarity index 51% rename from test/fail_histogram_add_static_test.cpp rename to test/fail_histogram_static_add_test.cpp index cff1ed26d..7e73c2869 100644 --- a/test/fail_histogram_add_static_test.cpp +++ b/test/fail_histogram_static_add_test.cpp @@ -1,3 +1,9 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + #include using namespace boost::histogram; diff --git a/test/fail_histogram_static_at_vector_wrong_dimension_test.cpp b/test/fail_histogram_static_at_vector_wrong_dimension_test.cpp new file mode 100644 index 000000000..c425e5361 --- /dev/null +++ b/test/fail_histogram_static_at_vector_wrong_dimension_test.cpp @@ -0,0 +1,13 @@ +// Copyright 2018 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include + +using namespace boost::histogram; +int main() { + auto h = make_static_histogram(axis::integer<>(0, 2)); + h.at(std::vector({0, 0})); +} diff --git a/test/fail_histogram_static_bin_vector_out_of_bounds_test.cpp b/test/fail_histogram_static_bin_vector_out_of_bounds_test.cpp deleted file mode 100644 index e2ec51199..000000000 --- a/test/fail_histogram_static_bin_vector_out_of_bounds_test.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include - -using namespace boost::histogram; -int main() { - auto h = - make_static_histogram(axis::integer<>(0, 2), axis::integer<>(0, 2)); - h.at(std::vector(-2, 0)); -} diff --git a/test/fail_histogram_static_bin_wrong_size_test.cpp b/test/fail_histogram_static_bin_wrong_size_test.cpp deleted file mode 100644 index 276d1b2a1..000000000 --- a/test/fail_histogram_static_bin_wrong_size_test.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -using namespace boost::histogram; -int main() { - auto h = make_static_histogram(axis::integer<>(0, 2)); - h.at(std::vector({0, 0})); -} diff --git a/test/histogram_dynamic_test.cpp b/test/histogram_dynamic_test.cpp new file mode 100644 index 000000000..ddcbfd8b7 --- /dev/null +++ b/test/histogram_dynamic_test.cpp @@ -0,0 +1,124 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; +using namespace boost::histogram::literals; // to get _c suffix + +int main() { + // special stuff that only works with dynamic_tag + + // init + { + auto v = std::vector, axis::integer<>>>(); + v.push_back(axis::regular<>(4, -1, 1)); + v.push_back(axis::integer<>(1, 7)); + auto h = make_dynamic_histogram(v.begin(), v.end()); + BOOST_TEST_EQ(h.dim(), 2); + BOOST_TEST_EQ(h.axis(0), v[0]); + BOOST_TEST_EQ(h.axis(1), v[1]); + + auto h2 = make_dynamic_histogram_with(array_storage(), v.begin(), v.end()); + BOOST_TEST_EQ(h2.dim(), 2); + BOOST_TEST_EQ(h2.axis(0), v[0]); + BOOST_TEST_EQ(h2.axis(1), v[1]); + } + + // bad fill argument + { + auto h = make_dynamic_histogram(axis::integer<>(0, 3)); + BOOST_TEST_THROWS(h(std::string()), std::invalid_argument); + } + + // axis methods + { + enum { A, B }; + auto c = make_dynamic_histogram(axis::category<>({A, B})); + BOOST_TEST_THROWS(c.axis().lower(0), std::runtime_error); + } + + // reduce + { + auto h1 = make_dynamic_histogram(axis::integer<>(0, 2), axis::integer<>(0, 3)); + h1(0, 0); + h1(0, 1); + h1(1, 0); + h1(1, 1); + h1(1, 2); + + std::vector x; + + x = {0}; + auto h1_0 = h1.reduce_to(x.begin(), x.end()); + BOOST_TEST_EQ(h1_0.dim(), 1); + BOOST_TEST_EQ(sum(h1_0), 5); + BOOST_TEST_EQ(h1_0.at(0), 2); + BOOST_TEST_EQ(h1_0.at(1), 3); + BOOST_TEST(h1_0.axis() == h1.axis(0_c)); + + x = {1}; + auto h1_1 = h1.reduce_to(x.begin(), x.end()); + BOOST_TEST_EQ(h1_1.dim(), 1); + BOOST_TEST_EQ(sum(h1_1), 5); + BOOST_TEST_EQ(h1_1.at(0), 2); + BOOST_TEST_EQ(h1_1.at(1), 2); + BOOST_TEST_EQ(h1_1.at(2), 1); + BOOST_TEST(h1_1.axis() == h1.axis(1_c)); + } + + // histogram iterator + { + auto h = make_dynamic_histogram(axis::integer<>(0, 3)); + const auto& a = h.axis(); + h(weight(2), 0); + h(1); + h(1); + auto it = h.begin(); + BOOST_TEST_EQ(it.dim(), 1); + + BOOST_TEST_EQ(it.idx(0), 0); + BOOST_TEST_EQ(it.bin(0), a[0]); + BOOST_TEST_EQ(it->value(), 2); + BOOST_TEST_EQ(it->variance(), 4); + ++it; + BOOST_TEST_EQ(it.idx(0), 1); + BOOST_TEST_EQ(it.bin(0), a[1]); + BOOST_TEST_EQ(*it, 2); + ++it; + BOOST_TEST_EQ(it.idx(0), 2); + BOOST_TEST_EQ(it.bin(0), a[2]); + BOOST_TEST_EQ(*it, 0); + ++it; + BOOST_TEST_EQ(it.idx(0), 3); + BOOST_TEST_EQ(it.bin(0), a[3]); + BOOST_TEST_EQ(*it, 0); + ++it; + BOOST_TEST_EQ(it.idx(0), -1); + BOOST_TEST_EQ(it.bin(0), a[-1]); + BOOST_TEST_EQ(*it, 0); + ++it; + BOOST_TEST(it == h.end()); + } + + return boost::report_errors(); +} diff --git a/test/histogram_mixed_test.cpp b/test/histogram_mixed_test.cpp new file mode 100644 index 000000000..7f807bb9e --- /dev/null +++ b/test/histogram_mixed_test.cpp @@ -0,0 +1,63 @@ +// Copyright 2015-2017 Hans Dembinski +// +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include "utility.hpp" + +using namespace boost::histogram; + +template +void run_tests() { + // compare + { + auto a = make(T1{}, axis::regular<>{3, 0, 3}, axis::integer<>(0, 2)); + auto b = make_s(T2{}, array_storage(), axis::regular<>{3, 0, 3}, + axis::integer<>(0, 2)); + BOOST_TEST_EQ(a, b); + auto b2 = make(T2{}, axis::integer<>{0, 3}, axis::integer<>(0, 2)); + BOOST_TEST_NE(a, b2); + auto b3 = make(T2{}, axis::regular<>(3, 0, 4), axis::integer<>(0, 2)); + BOOST_TEST_NE(a, b3); + } + + // add + { + auto a = make(T1{}, axis::integer<>{0, 2}); + auto b = make(T2{}, axis::integer<>{0, 2}); + BOOST_TEST_EQ(a, b); + a(0); // 1 0 + b(1); // 0 1 + a += b; // 1 1 + BOOST_TEST_EQ(a[0], 1); + BOOST_TEST_EQ(a[1], 1); + + auto c = make(T2{}, axis::integer<>{0, 3}); + BOOST_TEST_THROWS(a += c, std::invalid_argument); + } + + // copy_assign + { + auto a = make(T1{}, axis::regular<>{3, 0, 3}, axis::integer<>(0, 2)); + auto b = make_s(T2{}, array_storage(), axis::regular<>{3, 0, 3}, + axis::integer<>(0, 2)); + a(1, 1); + BOOST_TEST_NE(a, b); + b = a; + BOOST_TEST_EQ(a, b); + } +} + +int main() { + run_tests(); + run_tests(); + + return boost::report_errors(); +} diff --git a/test/histogram_test.cpp b/test/histogram_test.cpp index a2e95fe5b..3a6c5aa55 100644 --- a/test/histogram_test.cpp +++ b/test/histogram_test.cpp @@ -4,9 +4,8 @@ // (See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#include #include -#include +#include #include #include #ifndef BOOST_HISTOGRAM_NO_SERIALIZATION @@ -14,138 +13,108 @@ #include #include #endif -#include -#include #include #include #include -#include -#include -#include -#include #include +#include #include #include #include +#include "utility.hpp" using namespace boost::histogram; using namespace boost::histogram::literals; // to get _c suffix -namespace mp11 = boost::mp11; -template -auto make_histogram(static_tag, Axes&&... axes) - -> decltype(make_static_histogram_with(std::forward(axes)...)) { - return make_static_histogram_with(std::forward(axes)...); -} - -template -auto make_histogram(dynamic_tag, Axes&&... axes) - -> decltype(make_dynamic_histogram_with(std::forward(axes)...)) { - return make_dynamic_histogram_with(std::forward(axes)...); -} - -int expected_moved_from_dim(static_tag, int static_value) { - return static_value; -} +int expected_moved_from_dim(static_tag, int static_value) { return static_value; } int expected_moved_from_dim(dynamic_tag, int) { return 0; } -template -typename Histogram::element_type sum(const Histogram& h) { - return std::accumulate(h.begin(), h.end(), - typename Histogram::element_type(0)); +template +void pass_histogram(boost::histogram::histogram& h) { + BOOST_TEST_EQ(h.at(0), 0); + BOOST_TEST_EQ(h.at(1), 1); + BOOST_TEST_EQ(h.at(2), 0); + BOOST_TEST_EQ(h.axis(0_c), axis::integer<>(0, 3)); } -template -void pass_histogram(boost::histogram::histogram&) {} - -template +template void run_tests() { // init_1 { - auto h = - make_histogram(Type(), axis::regular<>{3, -1, 1}); + auto h = make(Tag(), axis::regular<>{3, -1, 1}); BOOST_TEST_EQ(h.dim(), 1); BOOST_TEST_EQ(h.size(), 5); BOOST_TEST_EQ(h.axis(0_c).shape(), 5); BOOST_TEST_EQ(h.axis().shape(), 5); - auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}); + auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}); BOOST_TEST_EQ(h2, h); } // init_2 { - auto h = make_histogram( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}); + auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}); BOOST_TEST_EQ(h.dim(), 2); BOOST_TEST_EQ(h.size(), 25); BOOST_TEST_EQ(h.axis(0_c).shape(), 5); BOOST_TEST_EQ(h.axis(1_c).shape(), 5); - auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}); + auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, + axis::integer<>{-1, 2}); BOOST_TEST_EQ(h2, h); } // init_3 { - auto h = make_histogram( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}); + auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, + axis::circular<>{3}); BOOST_TEST_EQ(h.dim(), 3); BOOST_TEST_EQ(h.size(), 75); - auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}); + auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, + axis::integer<>{-1, 2}, axis::circular<>{3}); BOOST_TEST_EQ(h2, h); } // init_4 { - auto h = make_histogram( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}, axis::variable<>{-1, 0, 1}); + auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, + axis::circular<>{3}, axis::variable<>{-1, 0, 1}); BOOST_TEST_EQ(h.dim(), 4); BOOST_TEST_EQ(h.size(), 300); - auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}, axis::variable<>{-1, 0, 1}); + auto h2 = + make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, + axis::integer<>{-1, 2}, axis::circular<>{3}, axis::variable<>{-1, 0, 1}); BOOST_TEST_EQ(h2, h); } // init_5 { enum { A, B, C }; - auto h = make_histogram( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B, C}}); + auto h = make(Tag(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, + axis::circular<>{3}, axis::variable<>{-1, 0, 1}, + axis::category<>{{A, B, C}}); BOOST_TEST_EQ(h.dim(), 5); BOOST_TEST_EQ(h.size(), 1200); - auto h2 = make_histogram>( - Type(), axis::regular<>{3, -1, 1}, axis::integer<>{-1, 2}, - axis::circular<>{3}, axis::variable<>{-1, 0, 1}, - axis::category<>{{A, B, C}}); + auto h2 = make_s(Tag(), array_storage(), axis::regular<>{3, -1, 1}, + axis::integer<>{-1, 2}, axis::circular<>{3}, + axis::variable<>{-1, 0, 1}, axis::category<>{{A, B, C}}); BOOST_TEST_EQ(h2, h); } // copy_ctor { - auto h = make_histogram(Type(), axis::integer<>{0, 2}, - axis::integer<>{0, 3}); + auto h = make(Tag(), axis::integer<>{0, 2}, axis::integer<>{0, 3}); h(0, 0); auto h2 = decltype(h)(h); BOOST_TEST(h2 == h); auto h3 = - static_histogram, axis::integer<>>, - array_storage>(h); + histogram, axis::integer<>>, array_storage>( + h); BOOST_TEST_EQ(h3, h); } // copy_assign { - auto h = make_histogram(Type(), axis::integer<>(0, 1), - axis::integer<>(0, 2)); + auto h = make(Tag(), axis::integer<>(0, 1), axis::integer<>(0, 2)); h(0, 0); auto h2 = decltype(h)(); BOOST_TEST_NE(h, h2); @@ -154,29 +123,27 @@ void run_tests() { // test self-assign h2 = h2; BOOST_TEST_EQ(h, h2); - auto h3 = - static_histogram, axis::integer<>>, - array_storage>(); + auto h3 = histogram, axis::integer<>>, + array_storage>(); h3 = h; BOOST_TEST_EQ(h, h3); } // move { - auto h = make_histogram(Type(), axis::integer<>(0, 1), - axis::integer<>(0, 2)); + auto h = make(Tag(), axis::integer<>(0, 1), axis::integer<>(0, 2)); h(0, 0); const auto href = h; decltype(h) h2(std::move(h)); // static axes cannot shrink to zero - BOOST_TEST_EQ(h.dim(), expected_moved_from_dim(Type(), 2)); + BOOST_TEST_EQ(h.dim(), expected_moved_from_dim(Tag(), 2)); BOOST_TEST_EQ(sum(h).value(), 0); BOOST_TEST_EQ(h.size(), 0); BOOST_TEST_EQ(h2, href); decltype(h) h3; h3 = std::move(h2); // static axes cannot shrink to zero - BOOST_TEST_EQ(h2.dim(), expected_moved_from_dim(Type(), 2)); + BOOST_TEST_EQ(h2.dim(), expected_moved_from_dim(Tag(), 2)); BOOST_TEST_EQ(sum(h2).value(), 0); BOOST_TEST_EQ(h2.size(), 0); BOOST_TEST_EQ(h3, href); @@ -185,8 +152,7 @@ void run_tests() { // axis methods { enum { A = 3, B = 5 }; - auto a = make_histogram( - Type(), axis::regular<>(1, 1, 2, "foo")); + auto a = make(Tag(), axis::regular<>(1, 1, 2, "foo")); BOOST_TEST_EQ(a.axis().size(), 1); BOOST_TEST_EQ(a.axis().shape(), 3); BOOST_TEST_EQ(a.axis().index(1), 0); @@ -196,7 +162,7 @@ void run_tests() { a.axis().label("bar"); BOOST_TEST_EQ(a.axis().label(), "bar"); - auto b = make_histogram(Type(), axis::integer<>(1, 2)); + auto b = make(Tag(), axis::integer<>(1, 2)); BOOST_TEST_EQ(b.axis().size(), 1); BOOST_TEST_EQ(b.axis().shape(), 3); BOOST_TEST_EQ(b.axis().index(1), 0); @@ -205,33 +171,30 @@ void run_tests() { b.axis().label("foo"); BOOST_TEST_EQ(b.axis().label(), "foo"); - auto c = - make_histogram(Type(), axis::category<>({A, B})); + auto c = make(Tag(), axis::category<>({A, B})); BOOST_TEST_EQ(c.axis().size(), 2); BOOST_TEST_EQ(c.axis().shape(), 3); BOOST_TEST_EQ(c.axis().index(A), 0); BOOST_TEST_EQ(c.axis().index(B), 1); c.axis().label("foo"); BOOST_TEST_EQ(c.axis().label(), "foo"); - // need to cast here for this to work with Type == dynamic_tag, too + // need to cast here for this to work with Tag == dynamic_tag, too auto ca = static_cast&>(c.axis()); BOOST_TEST_EQ(ca[0].value(), A); } // equal_compare { - auto a = make_histogram(Type(), axis::integer<>(0, 2)); - auto b = make_histogram(Type(), axis::integer<>(0, 2), - axis::integer<>(0, 3)); + auto a = make(Tag(), axis::integer<>(0, 2)); + auto b = make(Tag(), axis::integer<>(0, 2), axis::integer<>(0, 3)); BOOST_TEST(a != b); BOOST_TEST(b != a); - auto c = make_histogram(Type(), axis::integer<>(0, 2)); + auto c = make(Tag(), axis::integer<>(0, 2)); BOOST_TEST(b != c); BOOST_TEST(c != b); BOOST_TEST(a == c); BOOST_TEST(c == a); - auto d = - make_histogram(Type(), axis::regular<>(2, 0, 1)); + auto d = make(Tag(), axis::regular<>(2, 0, 1)); BOOST_TEST(c != d); BOOST_TEST(d != c); c(0); @@ -247,7 +210,7 @@ void run_tests() { // d1 { - auto h = make_histogram(Type(), axis::integer<>{0, 2}); + auto h = make(Tag(), axis::integer<>{0, 2}); h(0); h(0); h(-1); @@ -266,8 +229,7 @@ void run_tests() { // d1_2 { - auto h = make_histogram( - Type(), axis::integer<>(0, 2, "", axis::uoflow::off)); + auto h = make(Tag(), axis::integer<>(0, 2, "", axis::uoflow_type::off)); h(0); h(-0); h(-1); @@ -284,8 +246,7 @@ void run_tests() { // d1_3 { - auto h = make_histogram( - Type(), axis::category({"A", "B"})); + auto h = make(Tag(), axis::category({"A", "B"})); h("A"); h("B"); h("D"); @@ -302,7 +263,7 @@ void run_tests() { // d1w { - auto h = make_histogram(Type(), axis::integer<>(0, 2)); + auto h = make(Tag(), axis::integer<>(0, 2)); h(-1); h(0); h(weight(0.5), 0); @@ -325,9 +286,8 @@ void run_tests() { // d2 { - auto h = make_histogram( - Type(), axis::regular<>(2, -1, 1), - axis::integer<>(-1, 2, "", axis::uoflow::off)); + auto h = make(Tag(), axis::regular<>(2, -1, 1), + axis::integer<>(-1, 2, "", axis::uoflow_type::off)); h(-1, -1); h(-1, 0); h(-1, -10); @@ -359,9 +319,8 @@ void run_tests() { // d2w { - auto h = make_histogram( - Type(), axis::regular<>(2, -1, 1), - axis::integer<>(-1, 2, "", axis::uoflow::off)); + auto h = make(Tag(), axis::regular<>(2, -1, 1), + axis::integer<>(-1, 2, "", axis::uoflow_type::off)); h(-1, 0); // -> 0, 1 h(weight(10), -1, -1); // -> 0, 0 h(weight(5), -1, -10); // is ignored @@ -405,14 +364,11 @@ void run_tests() { // d3w { - auto h = make_histogram(Type(), axis::integer<>(0, 3), - axis::integer<>(0, 4), - axis::integer<>(0, 5)); + auto h = + make(Tag(), axis::integer<>(0, 3), axis::integer<>(0, 4), axis::integer<>(0, 5)); for (auto i = 0; i < h.axis(0_c).size(); ++i) { for (auto j = 0; j < h.axis(1_c).size(); ++j) { - for (auto k = 0; k < h.axis(2_c).size(); ++k) { - h(weight(i + j + k), i, j, k); - } + for (auto k = 0; k < h.axis(2_c).size(); ++k) { h(weight(i + j + k), i, j, k); } } } @@ -428,9 +384,8 @@ void run_tests() { // add_1 { - auto a = make_histogram(Type(), axis::integer<>(0, 2)); - auto b = make_histogram>(Type(), - axis::integer<>(0, 2)); + auto a = make(Tag(), axis::integer<>(0, 2)); + auto b = make_s(Tag(), array_storage(), axis::integer<>(0, 2)); a(0); // 1 0 b(1); // 0 1 auto a2 = a; @@ -446,14 +401,14 @@ void run_tests() { BOOST_TEST_EQ(a3.at(1), 1); BOOST_TEST_EQ(a3.at(2), 0); - auto c = make_histogram(Type(), axis::integer<>(0, 3)); + auto c = make(Tag(), axis::integer<>(0, 3)); BOOST_TEST_THROWS(c += b, std::invalid_argument); } // add_2 { - auto a = make_histogram(Type(), axis::integer<>(0, 2)); - auto b = make_histogram(Type(), axis::integer<>(0, 2)); + auto a = make(Tag(), axis::integer<>(0, 2)); + auto b = make(Tag(), axis::integer<>(0, 2)); a(0); BOOST_TEST_EQ(a.at(0).variance(), 1); @@ -479,10 +434,8 @@ void run_tests() { // add_3 { - auto a = - make_histogram>(Type(), axis::integer<>(-1, 2)); - auto b = make_histogram>(Type(), - axis::integer<>(-1, 2)); + auto a = make_s(Tag(), array_storage(), axis::integer<>(-1, 2)); + auto b = make_s(Tag(), array_storage(), axis::integer<>(-1, 2)); a(-1); b(1); auto c = a; @@ -504,9 +457,7 @@ void run_tests() { // functional programming { auto v = std::vector{0, 1, 2}; - auto h = std::for_each( - v.begin(), v.end(), - make_histogram(Type(), axis::integer<>(0, 3))); + auto h = std::for_each(v.begin(), v.end(), make(Tag(), axis::integer<>(0, 3))); BOOST_TEST_EQ(h.at(0), 1); BOOST_TEST_EQ(h.at(1), 1); BOOST_TEST_EQ(h.at(2), 1); @@ -515,7 +466,7 @@ void run_tests() { // operators { - auto a = make_histogram(Type(), axis::integer<>(0, 3)); + auto a = make(Tag(), axis::integer<>(0, 3)); auto b = a; a(0); b(1); @@ -551,14 +502,12 @@ void run_tests() { // histogram_serialization { enum { A, B, C }; - auto a = make_histogram( - Type(), axis::regular<>(3, -1, 1, "r"), - axis::circular<>(4, 0.0, 1.0, "p"), - axis::regular(3, 1, 100, "lr"), - axis::regular(3, 1, 100, "pr", - axis::uoflow::on, 0.5), - axis::variable<>({0.1, 0.2, 0.3, 0.4, 0.5}, "v"), - axis::category<>{A, B, C}, axis::integer<>(0, 2, "i")); + auto a = make( + Tag(), axis::regular<>(3, -1, 1, "r"), axis::circular<>(4, 0.0, 1.0, "p"), + axis::regular(3, 1, 100, "lr"), + axis::regular(3, 1, 100, "pr", axis::uoflow_type::on, 0.5), + axis::variable<>({0.1, 0.2, 0.3, 0.4, 0.5}, "v"), axis::category<>{A, B, C}, + axis::integer<>(0, 2, "i")); a(0.5, 0.2, 20, 20, 0.25, 1, 1); std::string buf; { @@ -580,8 +529,7 @@ void run_tests() { // histogram_ostream { - auto a = make_histogram( - Type(), axis::regular<>(3, -1, 1, "r"), axis::integer<>(0, 2, "i")); + auto a = make(Tag(), axis::regular<>(3, -1, 1, "r"), axis::integer<>(0, 2, "i")); std::ostringstream os; os << a; BOOST_TEST_EQ(os.str(), @@ -593,8 +541,7 @@ void run_tests() { // histogram_reset { - auto h = make_histogram( - Type(), axis::integer<>(0, 2, "", axis::uoflow::off)); + auto h = make(Tag(), axis::integer<>(0, 2, "", axis::uoflow_type::off)); h(0); h(1); BOOST_TEST_EQ(h.at(0), 1); @@ -608,8 +555,7 @@ void run_tests() { // reduce { - auto h1 = make_histogram(Type(), axis::integer<>(0, 2), - axis::integer<>(0, 3)); + auto h1 = make(Tag(), axis::integer<>(0, 2), axis::integer<>(0, 3)); h1(0, 0); h1(0, 1); h1(1, 0); @@ -644,9 +590,8 @@ void run_tests() { BOOST_TEST_EQ(h1_1.at(2), 1); BOOST_TEST(h1_1.axis() == h1.axis(1_c)); - auto h2 = make_histogram(Type(), axis::integer<>(0, 2), - axis::integer<>(0, 3), - axis::integer<>(0, 4)); + auto h2 = + make(Tag(), axis::integer<>(0, 2), axis::integer<>(0, 3), axis::integer<>(0, 4)); h2(0, 0, 0); h2(0, 1, 0); h2(0, 1, 1); @@ -718,7 +663,7 @@ void run_tests() { int index(value_type s) const { return integer::index(std::atoi(s)); } }; - auto h = make_histogram(Type(), custom_axis(0, 3)); + auto h = make(Tag(), custom_axis(0, 3)); h("-10"); h("0"); h("1"); @@ -733,7 +678,7 @@ void run_tests() { // histogram iterator 1D { - auto h = make_histogram(Type(), axis::integer<>(0, 3)); + auto h = make(Tag(), axis::integer<>(0, 3)); const auto& a = h.axis(); h(weight(2), 0); h(1); @@ -767,9 +712,8 @@ void run_tests() { // histogram iterator 2D { - auto h = make_histogram( - Type(), axis::integer<>(0, 1), - axis::integer<>(2, 4, "", axis::uoflow::off)); + auto h = make(Tag(), axis::integer<>(0, 1), + axis::integer<>(2, 4, "", axis::uoflow_type::off)); const auto& a0 = h.axis(0_c); const auto& a1 = h.axis(1_c); h(weight(2), 0, 2); @@ -830,7 +774,7 @@ void run_tests() { // STL compatibility { - auto h = make_histogram(Type(), axis::integer<>(0, 3)); + auto h = make(Tag(), axis::integer<>(0, 3)); for (int i = 0; i < 3; ++i) h(i); auto a = std::vector>(); std::partial_sum(h.begin(), h.end(), std::back_inserter(a)); @@ -841,8 +785,7 @@ void run_tests() { // using STL containers { - auto h = make_histogram(Type(), axis::integer<>(0, 2), - axis::regular<>(2, 2, 4)); + auto h = make(Tag(), axis::integer<>(0, 2), axis::regular<>(2, 2, 4)); // vector in h(std::vector({0, 2})); // pair in @@ -868,176 +811,51 @@ void run_tests() { BOOST_TEST_EQ(h[i].variance(), 5); } - // bin args out of range + // bad bin access { - auto h1 = make_histogram(Type(), axis::integer<>(0, 2)); - BOOST_TEST_THROWS(h1.at(-2), std::out_of_range); - BOOST_TEST_THROWS(h1.at(3), std::out_of_range); - BOOST_TEST_THROWS(h1.at(std::make_tuple(-2)), std::out_of_range); - BOOST_TEST_THROWS(h1.at(std::vector({3})), std::out_of_range); - BOOST_TEST_THROWS(h1[-2], std::out_of_range); - BOOST_TEST_THROWS(h1[3], std::out_of_range); - BOOST_TEST_THROWS(h1[std::make_tuple(-2)], std::out_of_range); - BOOST_TEST_THROWS(h1[std::vector({3})], std::out_of_range); - - auto h2 = make_histogram(Type(), axis::integer<>(0, 2), - axis::integer<>(0, 2)); - BOOST_TEST_THROWS(h2.at(0, -2), std::out_of_range); - BOOST_TEST_THROWS(h2.at(std::make_tuple(0, -2)), std::out_of_range); - BOOST_TEST_THROWS(h2.at(std::vector({0, -2})), std::out_of_range); - BOOST_TEST_THROWS(h2[std::make_tuple(0, -2)], std::out_of_range); - BOOST_TEST_THROWS(h2[std::vector({0, -2})], std::out_of_range); + auto h = make(Tag(), axis::integer<>(0, 1), axis::integer<>(0, 1)); + BOOST_TEST_THROWS(h.at(0, 2), std::out_of_range); + BOOST_TEST_THROWS(h.at(std::make_pair(2, 0)), std::out_of_range); + BOOST_TEST_THROWS(h.at(std::vector({0, 2})), std::out_of_range); } // pass histogram to function { - auto h = make_histogram(Type(), axis::integer<>(0, 3)); + auto h = make(Tag(), axis::integer<>(0, 3)); + h(1); pass_histogram(h); } -} -template -void run_mixed_tests() { - // compare + // allocator support { - auto a = make_histogram(T1{}, axis::regular<>{3, 0, 3}, - axis::integer<>(0, 2)); - auto b = make_histogram>( - T2{}, axis::regular<>{3, 0, 3}, axis::integer<>(0, 2)); - BOOST_TEST_EQ(a, b); - auto b2 = make_histogram(T2{}, axis::integer<>{0, 3}, - axis::integer<>(0, 2)); - BOOST_TEST_NE(a, b2); - auto b3 = make_histogram(T2{}, axis::regular<>(3, 0, 4), - axis::integer<>(0, 2)); - BOOST_TEST_NE(a, b3); - } + tracing_allocator_db db; + { + tracing_allocator a(db); + auto h = make_s(Tag(), array_storage>(a), + axis::integer>( + 0, 1024, std::string(512, 'c'), axis::uoflow_type::on, a)); + h(0); + } - // add - { - auto a = make_histogram(T1{}, axis::integer<>{0, 2}); - auto b = make_histogram(T2{}, axis::integer<>{0, 2}); - BOOST_TEST_EQ(a, b); - a(0); // 1 0 - b(1); // 0 1 - a += b; // 1 1 - BOOST_TEST_EQ(a[0], 1); - BOOST_TEST_EQ(a[1], 1); - - auto c = make_histogram(T2{}, axis::integer<>{0, 3}); - BOOST_TEST_THROWS(a += c, std::invalid_argument); - } + // int allocation for array_storage + BOOST_TEST_EQ(db[typeid(int)].first, db[typeid(int)].second); + BOOST_TEST_GE(db[typeid(int)].first, 1024u); - // copy_assign - { - auto a = make_histogram(T1{}, axis::regular<>{3, 0, 3}, - axis::integer<>(0, 2)); - auto b = make_histogram>( - T2{}, axis::regular<>{3, 0, 3}, axis::integer<>(0, 2)); - a(1, 1); - BOOST_TEST_NE(a, b); - b = a; - BOOST_TEST_EQ(a, b); + // char allocation for axis label + BOOST_TEST_EQ(db[typeid(char)].first, db[typeid(char)].second); + BOOST_TEST_GE(db[typeid(char)].first, 512u); + + if (Tag()) { // axis::any allocation, only for dynamic histogram + using T = axis::any>>; + BOOST_TEST_EQ(db[typeid(T)].first, db[typeid(T)].second); + BOOST_TEST_GE(db[typeid(T)].first, 1u); + } } } int main() { - // common interface run_tests(); run_tests(); - // special stuff that only works with dynamic_tag - - // init - { - auto v = std::vector, axis::integer<>>>(); - v.push_back(axis::regular<>(4, -1, 1)); - v.push_back(axis::integer<>(1, 7)); - auto h = make_dynamic_histogram(v.begin(), v.end()); - BOOST_TEST_EQ(h.axis(0), v[0]); - BOOST_TEST_EQ(h.axis(1), v[1]); - - auto h2 = - make_dynamic_histogram_with>(v.begin(), v.end()); - BOOST_TEST_EQ(h.axis(0), v[0]); - BOOST_TEST_EQ(h.axis(1), v[1]); - } - - // bad fill argument - { - auto h = make_dynamic_histogram(axis::integer<>(0, 3)); - BOOST_TEST_THROWS(h(std::string()), std::invalid_argument); - } - - // axis methods - { - enum { A, B }; - auto c = make_dynamic_histogram(axis::category<>({A, B})); - BOOST_TEST_THROWS(c.axis().lower(0), std::runtime_error); - } - - // reduce - { - auto h1 = - make_dynamic_histogram(axis::integer<>(0, 2), axis::integer<>(0, 3)); - h1(0, 0); - h1(0, 1); - h1(1, 0); - h1(1, 1); - h1(1, 2); - - auto h1_0 = h1.reduce_to(0); - BOOST_TEST_EQ(h1_0.dim(), 1); - BOOST_TEST_EQ(sum(h1_0), 5); - BOOST_TEST_EQ(h1_0.at(0), 2); - BOOST_TEST_EQ(h1_0.at(1), 3); - BOOST_TEST(h1_0.axis() == h1.axis(0_c)); - - auto h1_1 = h1.reduce_to(1); - BOOST_TEST_EQ(h1_1.dim(), 1); - BOOST_TEST_EQ(sum(h1_1), 5); - BOOST_TEST_EQ(h1_1.at(0), 2); - BOOST_TEST_EQ(h1_1.at(1), 2); - BOOST_TEST_EQ(h1_1.at(2), 1); - BOOST_TEST(h1_1.axis() == h1.axis(1_c)); - } - - // histogram iterator - { - auto h = make_dynamic_histogram(axis::integer<>(0, 3)); - const auto& a = h.axis(); - h(weight(2), 0); - h(1); - h(1); - auto it = h.begin(); - BOOST_TEST_EQ(it.dim(), 1); - - BOOST_TEST_EQ(it.idx(0), 0); - BOOST_TEST_EQ(it.bin(0), a[0]); - BOOST_TEST_EQ(it->value(), 2); - BOOST_TEST_EQ(it->variance(), 4); - ++it; - BOOST_TEST_EQ(it.idx(0), 1); - BOOST_TEST_EQ(it.bin(0), a[1]); - BOOST_TEST_EQ(*it, 2); - ++it; - BOOST_TEST_EQ(it.idx(0), 2); - BOOST_TEST_EQ(it.bin(0), a[2]); - BOOST_TEST_EQ(*it, 0); - ++it; - BOOST_TEST_EQ(it.idx(0), 3); - BOOST_TEST_EQ(it.bin(0), a[3]); - BOOST_TEST_EQ(*it, 0); - ++it; - BOOST_TEST_EQ(it.idx(0), -1); - BOOST_TEST_EQ(it.bin(0), a[-1]); - BOOST_TEST_EQ(*it, 0); - ++it; - BOOST_TEST(it == h.end()); - } - - run_mixed_tests(); - run_mixed_tests(); - return boost::report_errors(); } diff --git a/test/meta_test.cpp b/test/meta_test.cpp index a8f622deb..41e8abd09 100644 --- a/test/meta_test.cpp +++ b/test/meta_test.cpp @@ -86,8 +86,15 @@ int main() { using result3a = classify_container&>; BOOST_TEST_TRAIT_TRUE((std::is_same)); - using result4 = classify_container; - BOOST_TEST_TRAIT_TRUE((std::is_same)); + // (c-)strings are not regarded as dynamic containers + using result4a = classify_container; + BOOST_TEST_TRAIT_TRUE((std::is_same)); + + using result4b = classify_container; + BOOST_TEST_TRAIT_TRUE((std::is_same)); + + using result5 = classify_container; // has no std::end + BOOST_TEST_TRAIT_TRUE((std::is_same)); } // bool mask @@ -102,17 +109,21 @@ int main() { // rm_cv_ref { using T1 = int; - using T2 = const int; - using T3 = const int&; - using T4 = volatile int; - using T5 = volatile const int; - using T6 = volatile const int&; + using T2 = int&&; + using T3 = const int; + using T4 = const int&; + using T5 = volatile int; + using T6 = volatile int&&; + using T7 = volatile const int; + using T8 = volatile const int&; BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, int>)); } // mp_size @@ -130,13 +141,10 @@ int main() { // copy_qualifiers { BOOST_TEST_TRAIT_TRUE((std::is_same, long>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, const long>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, const long>)); BOOST_TEST_TRAIT_TRUE((std::is_same, long&>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, const long&>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, long&&>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, const long&>)); + BOOST_TEST_TRAIT_TRUE((std::is_same, long&&>)); } // mp_set_union @@ -148,41 +156,5 @@ int main() { BOOST_TEST_TRAIT_TRUE((std::is_same)); } - // selection - { - using T = std::tuple; - BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE((std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE(( - std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - BOOST_TEST_TRAIT_TRUE( - (std::is_same, std::tuple>)); - } - - // unique_sorted - { - using input = mp11::mp_list_c; - using result = unique_sorted; - using expected = mp11::mp_list_c; - - BOOST_TEST_TRAIT_TRUE((std::is_same)); - } - - // make_sub_tuple - { - std::tuple t(1, 2, 3); - auto result = make_sub_tuple(t); - auto expected = std::tuple(2, 3); - - BOOST_TEST_EQ(result, expected); - } - return boost::report_errors(); } diff --git a/test/pass_on_fail.py b/test/pass_on_fail.py index 80f492f0f..41a95cf02 100644 --- a/test/pass_on_fail.py +++ b/test/pass_on_fail.py @@ -1,3 +1,11 @@ +# -*- coding: utf-8 -*- +# +# Copyright 2018 Hans Dembinski +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt +# or copy at http://www.boost.org/LICENSE_1_0.txt) + import subprocess as subp import sys import os diff --git a/test/python_suite_test.py b/test/python_suite_test.py index 35886384e..31d262841 100644 --- a/test/python_suite_test.py +++ b/test/python_suite_test.py @@ -233,7 +233,6 @@ class test_variable(unittest.TestCase): def test_init(self): variable(0, 1) - variable(1, -1) variable(0, 1, 2, 3, 4) variable(0, 1, label="va") variable(0, 1, uoflow=True) @@ -241,7 +240,11 @@ def test_init(self): with self.assertRaises(TypeError): variable() with self.assertRaises(ValueError): - variable(1.0) + variable(1) + with self.assertRaises(ValueError): + variable(1, -1) + with self.assertRaises(ValueError): + variable(1, 1) with self.assertRaises(TypeError): variable("1", 2) with self.assertRaises(KeyError): @@ -681,6 +684,13 @@ def test_reduce_to(self): self.assertEqual(h1.axis(), integer(1, 4)) self.assertEqual([h1.at(i).value for i in range(3)], [1, 1, 1]) + with self.assertRaises(ValueError): + h.reduce_to(*range(100)) + + with self.assertRaises(ValueError): + h.reduce_to(2, 1) + + def test_pickle_0(self): a = histogram(category(0, 1, 2), integer(0, 20, label='ia'), @@ -849,7 +859,7 @@ def test_numpy_conversion_5(self): integer(0, 2, uoflow=False)) a(0, 0) for i in range(80): - a += a + a = a + a # a now holds a multiprecision type a(1, 0) for i in range(2): diff --git a/test/speed_cpp.cpp b/test/speed_cpp.cpp index 838c63106..798a8e2ae 100644 --- a/test/speed_cpp.cpp +++ b/test/speed_cpp.cpp @@ -12,6 +12,7 @@ #include #include #include +#include "utility.hpp" using namespace boost::histogram; using boost::mp11::mp_list; @@ -48,13 +49,13 @@ double baseline(unsigned n) { return best; } -template +template double compare_1d(unsigned n, int distrib) { auto r = random_array(n, distrib); auto best = std::numeric_limits::max(); for (unsigned k = 0; k < 20; ++k) { - auto h = Histogram(axis::regular<>(100, 0, 1)); + auto h = make_s(Tag(), Storage(), axis::regular<>(100, 0, 1)); auto t = clock(); for (unsigned i = 0; i < n; ++i) h(r[i]); t = clock() - t; @@ -64,14 +65,14 @@ double compare_1d(unsigned n, int distrib) { return best; } -template +template double compare_2d(unsigned n, int distrib) { auto r = random_array(n, distrib); auto best = std::numeric_limits::max(); for (unsigned k = 0; k < 20; ++k) { auto h = - Histogram(axis::regular<>(100, 0, 1), axis::regular<>(100, 0, 1)); + make_s(Tag(), Storage(), axis::regular<>(100, 0, 1), axis::regular<>(100, 0, 1)); auto t = clock(); for (unsigned i = 0; i < n / 2; ++i) h(r[2 * i], r[2 * i + 1]); t = clock() - t; @@ -81,17 +82,16 @@ double compare_2d(unsigned n, int distrib) { return best; } -template +template double compare_3d(unsigned n, int distrib) { auto r = random_array(n, distrib); auto best = std::numeric_limits::max(); for (unsigned k = 0; k < 20; ++k) { - auto h = Histogram(axis::regular<>(100, 0, 1), axis::regular<>(100, 0, 1), - axis::regular<>(100, 0, 1)); + auto h = make_s(Tag(), Storage(), axis::regular<>(100, 0, 1), + axis::regular<>(100, 0, 1), axis::regular<>(100, 0, 1)); auto t = clock(); - for (unsigned i = 0; i < n / 3; ++i) - h(r[3 * i], r[3 * i + 1], r[3 * i + 2]); + for (unsigned i = 0; i < n / 3; ++i) h(r[3 * i], r[3 * i + 1], r[3 * i + 2]); t = clock() - t; best = std::min(best, double(t) / CLOCKS_PER_SEC); } @@ -99,20 +99,20 @@ double compare_3d(unsigned n, int distrib) { return best; } -template +template double compare_6d(unsigned n, int distrib) { auto r = random_array(n, distrib); auto best = std::numeric_limits::max(); for (unsigned k = 0; k < 20; ++k) { - auto h = Histogram(axis::regular<>(10, 0, 1), axis::regular<>(10, 0, 1), - axis::regular<>(10, 0, 1), axis::regular<>(10, 0, 1), - axis::regular<>(10, 0, 1), axis::regular<>(10, 0, 1)); + auto h = + make_s(Tag(), Storage(), axis::regular<>(10, 0, 1), axis::regular<>(10, 0, 1), + axis::regular<>(10, 0, 1), axis::regular<>(10, 0, 1), + axis::regular<>(10, 0, 1), axis::regular<>(10, 0, 1)); auto t = clock(); for (unsigned i = 0; i < n / 6; ++i) { - h(r[6 * i], r[6 * i + 1], r[6 * i + 2], r[6 * i + 3], r[6 * i + 4], - r[6 * i + 5]); + h(r[6 * i], r[6 * i + 1], r[6 * i + 2], r[6 * i + 3], r[6 * i + 4], r[6 * i + 5]); } t = clock() - t; best = std::min(best, double(t) / CLOCKS_PER_SEC); @@ -132,19 +132,10 @@ int main() { printf("uniform distribution\n"); else printf("normal distribution\n"); - printf("hs_ss %.3f\n", - compare_1d>, - array_storage>>(nfill, itype)); - printf("hs_sd %.3f\n", - compare_1d< - static_histogram>, adaptive_storage>>( - nfill, itype)); - printf("hd_ss %.3f\n", - compare_1d>>( - nfill, itype)); - printf("hd_sd %.3f\n", - compare_1d>( - nfill, itype)); + printf("hs_ss %.3f\n", compare_1d>(nfill, itype)); + printf("hs_sd %.3f\n", compare_1d>(nfill, itype)); + printf("hd_ss %.3f\n", compare_1d>(nfill, itype)); + printf("hd_sd %.3f\n", compare_1d>(nfill, itype)); } printf("2D\n"); @@ -153,20 +144,10 @@ int main() { printf("uniform distribution\n"); else printf("normal distribution\n"); - printf( - "hs_ss %.3f\n", - compare_2d, axis::regular<>>, - array_storage>>(nfill, itype)); - printf( - "hs_sd %.3f\n", - compare_2d, axis::regular<>>, - adaptive_storage>>(nfill, itype)); - printf("hd_ss %.3f\n", - compare_2d>>( - nfill, itype)); - printf("hd_sd %.3f\n", - compare_2d>( - nfill, itype)); + printf("hs_ss %.3f\n", compare_2d>(nfill, itype)); + printf("hs_sd %.3f\n", compare_2d>(nfill, itype)); + printf("hd_ss %.3f\n", compare_2d>(nfill, itype)); + printf("hd_sd %.3f\n", compare_2d>(nfill, itype)); } printf("3D\n"); @@ -175,20 +156,10 @@ int main() { printf("uniform distribution\n"); else printf("normal distribution\n"); - printf("hs_ss %.3f\n", - compare_3d, axis::regular<>, axis::regular<>>, - array_storage>>(nfill, itype)); - printf("hs_sd %.3f\n", - compare_3d, axis::regular<>, axis::regular<>>, - adaptive_storage>>(nfill, itype)); - printf("hd_ss %.3f\n", - compare_3d>>( - nfill, itype)); - printf("hd_sd %.3f\n", - compare_3d>( - nfill, itype)); + printf("hs_ss %.3f\n", compare_3d>(nfill, itype)); + printf("hs_sd %.3f\n", compare_3d>(nfill, itype)); + printf("hd_ss %.3f\n", compare_3d>(nfill, itype)); + printf("hd_sd %.3f\n", compare_3d>(nfill, itype)); } printf("6D\n"); @@ -197,21 +168,9 @@ int main() { printf("uniform distribution\n"); else printf("normal distribution\n"); - printf("hs_ss %.3f\n", - compare_6d, axis::regular<>, axis::regular<>, - axis::regular<>, axis::regular<>, axis::regular<>>, - array_storage>>(nfill, itype)); - printf("hs_sd %.3f\n", - compare_6d, axis::regular<>, axis::regular<>, - axis::regular<>, axis::regular<>, axis::regular<>>, - adaptive_storage>>(nfill, itype)); - printf("hd_ss %.3f\n", - compare_6d>>( - nfill, itype)); - printf("hd_sd %.3f\n", - compare_6d>( - nfill, itype)); + printf("hs_ss %.3f\n", compare_6d>(nfill, itype)); + printf("hs_sd %.3f\n", compare_6d>(nfill, itype)); + printf("hd_ss %.3f\n", compare_6d>(nfill, itype)); + printf("hd_sd %.3f\n", compare_6d>(nfill, itype)); } } diff --git a/test/speed_numpy.py b/test/speed_numpy.py index 745fc955e..65215039e 100644 --- a/test/speed_numpy.py +++ b/test/speed_numpy.py @@ -1,9 +1,10 @@ -## -## Copyright 2015-2016 Hans Dembinski -## -## Distributed under the Boost Software License, Version 1.0. -## (See accompanying file LICENSE_1_0.txt -## or copy at http://www.boost.org/LICENSE_1_0.txt) +# -*- coding: utf-8 -*- +# +# Copyright 2015-2016 Hans Dembinski +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE_1_0.txt +# or copy at http://www.boost.org/LICENSE_1_0.txt) import numpy as np from timeit import default_timer as timer diff --git a/test/utility.hpp b/test/utility.hpp index c44a629b9..949e1e255 100644 --- a/test/utility.hpp +++ b/test/utility.hpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 Hans Dembinski +// Copyright 2018 Hans Dembinski // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -7,18 +7,25 @@ #ifndef BOOST_HISTOGRAM_TEST_UTILITY_HPP_ #define BOOST_HISTOGRAM_TEST_UTILITY_HPP_ +#include #include #include +#include #include #include #include +#include +#include +#include +#include -using i0 = boost::mp11::mp_int<0>; -using i1 = boost::mp11::mp_int<1>; -using i2 = boost::mp11::mp_int<2>; -using i3 = boost::mp11::mp_int<3>; +using i0 = boost::mp11::mp_size_t<0>; +using i1 = boost::mp11::mp_size_t<1>; +using i2 = boost::mp11::mp_size_t<2>; +using i3 = boost::mp11::mp_size_t<3>; -namespace std { // never add to std, we only do it to get ADL working +namespace std { +// never add to std, we only do it to get ADL working :( template ostream& operator<<(ostream& os, const vector& v) { os << "[ "; @@ -27,21 +34,105 @@ ostream& operator<<(ostream& os, const vector& v) { return os; } -struct ostreamer { - ostream& os; - template - void operator()(const T& t) const { - os << t << " "; - } -}; +namespace detail { + struct ostreamer { + ostream& os; + template + void operator()(const T& t) const { + os << t << " "; + } + }; +} template ostream& operator<<(ostream& os, const tuple& t) { os << "[ "; - ::boost::mp11::tuple_for_each(t, ostreamer{os}); + ::boost::mp11::tuple_for_each(t, detail::ostreamer{os}); os << "]"; return os; } } // namespace std +namespace boost { +namespace histogram { +template +typename Histogram::element_type sum(const Histogram& h) { + return std::accumulate(h.begin(), h.end(), typename Histogram::element_type(0)); +} + +using static_tag = std::false_type; +using dynamic_tag = std::true_type; + +template +auto make(static_tag, Axes&&... axes) + -> decltype(make_static_histogram(std::forward(axes)...)) { + return make_static_histogram(std::forward(axes)...); +} + +template +auto make_s(static_tag, S&& s, Axes&&... axes) + -> decltype(make_static_histogram_with(s, std::forward(axes)...)) { + return make_static_histogram_with(s, std::forward(axes)...); +} + +template +auto make(dynamic_tag, Axes&&... axes) + -> decltype(make_dynamic_histogram...>>(std::forward(axes)...)) { + return make_dynamic_histogram...>>(std::forward(axes)...); +} + +template +auto make_s(dynamic_tag, S&& s, Axes&&... axes) + -> decltype(make_dynamic_histogram_with...>>(s, std::forward(axes)...)) { + return make_dynamic_histogram_with...>>(s, std::forward(axes)...); +} + +using tracing_allocator_db = std::unordered_map< + std::type_index, + std::pair +>; + +template +struct tracing_allocator { + using value_type = T; + + tracing_allocator_db* db = nullptr; + + tracing_allocator() noexcept {} + tracing_allocator(tracing_allocator_db& x) noexcept : db(&x) {} + template + tracing_allocator(const tracing_allocator& a) noexcept + : db(a.db) {} + ~tracing_allocator() noexcept {} + + T* allocate(std::size_t n) { + if (db) { + (*db)[typeid(T)].first += n; + } + return static_cast(::operator new(n * sizeof(T))); + } + + void deallocate(T*& p, std::size_t n) { + if (db) { + (*db)[typeid(T)].second += n; + } + ::operator delete((void*)p); + } +}; + +template +constexpr bool operator==(const tracing_allocator&, + const tracing_allocator&) noexcept { + return true; +} + +template +constexpr bool operator!=(const tracing_allocator& t, + const tracing_allocator& u) noexcept { + return !operator==(t, u); +} + +} // namespace histogram +} // namespace boost + #endif diff --git a/test/utility_test.cpp b/test/utility_test.cpp index 1f5e51154..b8fffaacb 100644 --- a/test/utility_test.cpp +++ b/test/utility_test.cpp @@ -1,4 +1,4 @@ -// Copyright 2015-2017 Hans Dembinski +// Copyright 2018 Hans Dembinski // // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt @@ -10,6 +10,8 @@ #include #include +using namespace boost::histogram; + int main() { // vector streaming { @@ -27,4 +29,20 @@ int main() { BOOST_TEST_EQ(os.str(), std::string("[ 1 2.5 hi ]")); } return boost::report_errors(); + + // tracing_allocator + { + tracing_allocator_db db; + tracing_allocator a(db); + auto p1 = a.allocate(2); + a.deallocate(p1, 2); + tracing_allocator b(a); + auto p2 = b.allocate(3); + b.deallocate(p2, 3); + BOOST_TEST_EQ(db.size(), 2); + BOOST_TEST_EQ(db[typeid(char)].first, 2); + BOOST_TEST_EQ(db[typeid(char)].second, 2); + BOOST_TEST_EQ(db[typeid(int)].first, 3); + BOOST_TEST_EQ(db[typeid(int)].second, 3); + } }