Skip to content

Commit a7d55cb

Browse files
committed
Handle histogram edge cases on ARM hardware
1 parent 7a31550 commit a7d55cb

4 files changed

Lines changed: 93 additions & 25 deletions

File tree

.github/workflows/push_pull.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ jobs:
4040
build_procs: 3
4141
check_procs: 3
4242
with_ccache: 'true'
43+
with_walberla: 'true'
44+
with_walberla_fft: 'false'
45+
with_walberla_avx: 'false'
4346

4447
debian:
4548
runs-on: ubuntu-latest

maintainer/CI/build_cmake.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ set_default_value with_fftw true
132132
set_default_value with_gsl true
133133
set_default_value with_scafacos false
134134
set_default_value with_walberla false
135+
set_default_value with_walberla_fft true
135136
set_default_value with_walberla_avx false
136137
set_default_value with_stokesian_dynamics false
137138
set_default_value test_timeout 500
@@ -168,7 +169,9 @@ cmake_params="${cmake_params} -D ESPRESSO_BUILD_WITH_STOKESIAN_DYNAMICS=${with_s
168169
cmake_params="${cmake_params} -D ESPRESSO_BUILD_WITH_WALBERLA=${with_walberla}"
169170

170171
if [ "${with_walberla}" = true ]; then
171-
cmake_params="${cmake_params} -D ESPRESSO_BUILD_WITH_WALBERLA_FFT=ON"
172+
if [ "${with_walberla_fft}" = true ]; then
173+
cmake_params="${cmake_params} -D ESPRESSO_BUILD_WITH_WALBERLA_FFT=ON"
174+
fi
172175
if [ "${with_walberla_avx}" = true ]; then
173176
cmake_params="${cmake_params} -D ESPRESSO_BUILD_WITH_WALBERLA_AVX=ON"
174177
fi

src/utils/include/utils/Histogram.hpp

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
129137
private:
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
*/
194213
template <typename T, std::size_t N, std::size_t M = 3, typename U = double>
195214
class 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

202222
public:
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
}

src/utils/tests/histogram_test.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,3 +105,45 @@ BOOST_AUTO_TEST_CASE(cylindrical_histogram) {
105105
std::vector<double>{{0.0, 0.0}}),
106106
std::invalid_argument);
107107
}
108+
109+
BOOST_AUTO_TEST_CASE(histogram_bin_folding) {
110+
class TestHistogram : public Utils::Histogram<double, 2, 2, double> {
111+
using Base = Histogram<double, 2, 2, double>;
112+
113+
public:
114+
using Base::correct_off_by_one;
115+
using Base::Histogram;
116+
};
117+
std::array<std::size_t, 2> n_bins{{10, 10}};
118+
std::array<std::pair<double, double>, 2> limits{
119+
{std::make_pair(1.0, 20.0), std::make_pair(5.0, 10.0)}};
120+
auto hist = TestHistogram(n_bins, limits);
121+
auto old_bin = 0l;
122+
auto new_bin = 0l;
123+
auto constexpr max_index = 2l;
124+
auto constexpr min_index = 0l;
125+
// no folding from the right
126+
old_bin = max_index - 1l;
127+
new_bin = hist.correct_off_by_one(old_bin, max_index);
128+
BOOST_CHECK_EQUAL(new_bin, old_bin);
129+
// folding from the right
130+
old_bin = max_index;
131+
new_bin = hist.correct_off_by_one(old_bin, max_index);
132+
BOOST_CHECK_EQUAL(new_bin, old_bin - 1l);
133+
// no folding from the right
134+
old_bin = max_index + 1l;
135+
new_bin = hist.correct_off_by_one(old_bin, max_index);
136+
BOOST_CHECK_EQUAL(new_bin, old_bin);
137+
// no folding from the left
138+
old_bin = min_index;
139+
new_bin = hist.correct_off_by_one(old_bin, max_index);
140+
BOOST_CHECK_EQUAL(new_bin, old_bin);
141+
// folding from the left
142+
old_bin = min_index - 1l;
143+
new_bin = hist.correct_off_by_one(old_bin, max_index);
144+
BOOST_CHECK_EQUAL(new_bin, old_bin + 1l);
145+
// no folding from the left
146+
old_bin = min_index - 2l;
147+
new_bin = hist.correct_off_by_one(old_bin, max_index);
148+
BOOST_CHECK_EQUAL(new_bin, old_bin);
149+
}

0 commit comments

Comments
 (0)