@@ -105,13 +105,10 @@ class Histogram {
105105 throw std::invalid_argument (" Wrong dimensions for the value" );
106106 }
107107 if (check_limits (pos)) {
108- boost::array<array_index, M + 1 > index;
109- for (std::size_t i = 0 ; i < M; ++i) {
110- index[i] = calc_bin_index (pos[i], m_limits[i].first , m_bin_sizes[i]);
111- }
112- for (array_index i = 0 ; i < static_cast <array_index>(N); ++i) {
113- index.back () = i;
114- m_array (index) += value[static_cast <std::size_t >(i)];
108+ auto index = calc_bin_index (pos);
109+ for (std::size_t i = 0 ; i < N; ++i) {
110+ index.back () = static_cast <array_index>(i);
111+ m_array (index) += value[i];
115112 m_count (index)++;
116113 }
117114 }
@@ -121,20 +118,42 @@ class Histogram {
121118 virtual void normalize () {
122119 auto const bin_volume = std::accumulate (
123120 m_bin_sizes.begin (), m_bin_sizes.end (), U{1 }, std::multiplies<U>());
124- std::transform (
125- m_array.data (), m_array.data () + m_array. num_elements (), m_array.data (),
121+ std::ranges:: transform (
122+ std::span ( m_array.data (), m_array.num_elements () ), m_array.data (),
126123 [bin_volume](T v) { return static_cast <T>(v / bin_volume); });
127124 }
128125
126+ protected:
127+ // internal function exposed for unit testing purposes
128+ long correct_off_by_one (long bin, long n_bins) const {
129+ if (bin == -1l ) {
130+ ++bin;
131+ } else if (bin == n_bins) {
132+ --bin;
133+ }
134+ return bin;
135+ }
136+
129137private:
130138 /* *
131139 * \brief Calculate the bin index.
132- * \param value Position on that dimension.
133- * \param offset Bin offset on that dimension.
134- * \param size Bin size on that dimension.
140+ * \param pos Position.
135141 */
136- array_index calc_bin_index (double value, double offset, double size) const {
137- return static_cast <array_index>(std::floor ((value - offset) / size));
142+ auto calc_bin_index (std::span<const U> const &pos) const {
143+ boost::array<array_index, M + 1 > index;
144+ for (std::size_t i = 0 ; i < M; ++i) {
145+ auto const offset = m_limits[i].first ;
146+ auto const size = m_bin_sizes[i];
147+ auto const n_bins = static_cast <long >(m_n_bins[i]);
148+ auto const bin = static_cast <long >(std::floor ((pos[i] - offset) / size));
149+ // handle edge cases when the position is exactly between two bins:
150+ // due to precision loss in the offset subtraction, the bin index might
151+ // be off by one, so we fold it here back inside the valid range
152+ auto const folded_bin = correct_off_by_one (bin, n_bins);
153+ assert ((folded_bin == bin) or check_limits (pos));
154+ index[i] = static_cast <array_index>(folded_bin);
155+ }
156+ return index;
138157 }
139158
140159 /* *
@@ -153,7 +172,7 @@ class Histogram {
153172 * \brief Check if the position lies within the histogram limits.
154173 * \param pos Position to check.
155174 */
156- bool check_limits (std::span<const U> pos) const {
175+ bool check_limits (std::span<const U> const & pos) const {
157176 assert (pos.size () == M);
158177 bool within_range = true ;
159178 for (std::size_t i = 0 ; i < M; ++i) {
@@ -165,7 +184,7 @@ class Histogram {
165184
166185 std::array<std::size_t , M + 1 > m_array_dim () const {
167186 std::array<std::size_t , M + 1 > dimensions;
168- std::copy (m_n_bins. begin (), m_n_bins. end () , dimensions.begin ());
187+ std::ranges:: copy (m_n_bins, dimensions.begin ());
169188 dimensions.back () = N;
170189 return dimensions;
171190 }
@@ -193,14 +212,15 @@ class Histogram {
193212 */
194213template <typename T, std::size_t N, std::size_t M = 3 , typename U = double >
195214class CylindricalHistogram : public Histogram <T, N, M, U> {
196- using Histogram<T, N, M, U>::m_n_bins;
197- using Histogram<T, N, M, U>::m_limits;
198- using Histogram<T, N, M, U>::m_bin_sizes;
199- using Histogram<T, N, M, U>::m_array;
200- using typename Histogram<T, N, M, U>::array_index;
215+ using Base = Histogram<T, N, M, U>;
216+ using Base::m_array;
217+ using Base::m_bin_sizes;
218+ using Base::m_limits;
219+ using Base::m_n_bins;
220+ using typename Base::array_index;
201221
202222public:
203- using Histogram<T, N, M, U> ::Histogram;
223+ using Base ::Histogram;
204224
205225 void normalize () override {
206226 auto const min_r = m_limits[0 ].first ;
@@ -214,8 +234,8 @@ class CylindricalHistogram : public Histogram<T, N, M, U> {
214234 auto const bin_volume = (r_right * r_right - r_left * r_left) *
215235 z_bin_size * phi_bin_size / U (2 );
216236 auto *begin = m_array[i].origin ();
217- std::transform (
218- begin, begin + m_array[i].num_elements (), begin,
237+ std::ranges:: transform (
238+ std::span ( begin, m_array[i].num_elements () ), begin,
219239 [bin_volume](T v) { return static_cast <T>(v / bin_volume); });
220240 }
221241 }
0 commit comments