HART  0.2.0
High level Audio Regression and Testing
Loading...
Searching...
No Matches
hart_reducers.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <cmath>
5#include <iterator>
6#include <vector>
7
10#include "hart_utils.hpp" // nan(), floatsNotEqual(), Interpolation
11
12namespace hart
13{
14
15/// @defgroup Reducers Reducers
16/// @brief Helper functions to reduce multi-channel metrics to a single scalar
17/// @{
18
19/// @brief Value of the elements in a range that reducer is supposed to reduce
20template <typename IteratorType>
21using IteratedValueType =
22 typename std::iterator_traits<IteratorType>::value_type;
23
24/// @brief Returns `true` if all the values in the range are equal to each other within provided tolerance, `false` otherwise
26{
27 allFloatsEqualToEachOther (double toleranceLinear = 1e-8) :
28 m_toleranceLinear (toleranceLinear)
29 {}
30
31 template <typename IteratorType>
32 bool operator() (IteratorType begin, IteratorType end) const
33 {
34 if (begin == end)
35 return true;
36
37 const auto toleranceLinear = static_cast<IteratedValueType<IteratorType>> (m_toleranceLinear);
38 const IteratedValueType<IteratorType> referenceValue = *begin;
39 ++begin;
40
41 for (IteratorType it = begin; it != end; ++it)
42 if (floatsNotEqual (*it, referenceValue, toleranceLinear))
43 return false;
44
45 return true;
46 }
47private:
48 const double m_toleranceLinear;
49};
50
51/// @brief Returns `true` if all the values in the range are equal to a specific value within provided tolerance, `false` otherwise
53{
54 allFloatsEqualTo (double targetValue, double toleranceLinear = 1e-8) :
55 m_targetValue (targetValue),
56 m_toleranceLinear (toleranceLinear)
57 {}
58
59 template <typename IteratorType>
60 bool operator() (IteratorType begin, IteratorType end) const
61 {
62 if (begin == end)
63 return true;
64
65 const auto targetValue = static_cast<IteratedValueType<IteratorType>> (m_targetValue);
66 const auto toleranceLinear = static_cast<IteratedValueType<IteratorType>> (m_toleranceLinear);
67
68 for (IteratorType it = begin; it != end; ++it)
69 if (floatsNotEqual (*it, targetValue, toleranceLinear))
70 return false;
71
72 return true;
73 }
74private:
75 const double m_targetValue;
76 const double m_toleranceLinear;
77};
78
79/// @brief Returns `true` if at least one element in the range is `NaN`
80/// @details Empty ranges return `false`
81struct anyNaN
82{
83 template <typename IteratorType>
84 bool operator() (IteratorType begin, IteratorType end) const
85 {
86 for (IteratorType it = begin; it != end; ++it)
87 if (std::isnan (*it))
88 return true;
89
90 return false;
91 }
92};
93
94/// @brief Returns `true` if all elements in the range are `NaN`
95/// @details Empty ranges return `false`
96struct allNaN
97{
98 template <typename IteratorType>
99 bool operator() (IteratorType begin, IteratorType end) const
100 {
101 if (begin == end)
102 return false;
103
104 for (IteratorType it = begin; it != end; ++it)
105 if (! std::isnan (*it))
106 return false;
107
108 return true;
109 }
110};
111
112/// @brief Returns the zero-based index of the largest element in the range
113/// @details The returned index is relative to the provided iterator range, not the original container
114struct argmax
115{
116 template <typename IteratorType>
117 size_t operator() (IteratorType begin, IteratorType end) const
118 {
119 if (begin == end)
120 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", 0);
121
122 return static_cast<size_t> (std::distance (begin, std::max_element (begin, end)));
123 }
124};
125
126/// @brief Returns the zero-based index of the smallest element in the range
127/// @details The returned index is relative to the provided iterator range, not the original container
128struct argmin
129{
130 template <typename IteratorType>
131 size_t operator() (IteratorType begin, IteratorType end) const
132 {
133 if (begin == end)
134 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", 0);
135
136 return static_cast<size_t> (std::distance (begin, std::min_element (begin, end)));
137 }
138};
139
140/// @brief Forwards the entire range as an `std::vector`, preserving the original order
142{
143 template <typename IteratorType>
144 std::vector<IteratedValueType<IteratorType>>
145 operator() (IteratorType begin, IteratorType end) const
146 {
147 return std::vector<IteratedValueType<IteratorType>> (begin, end);
148 }
149};
150
151/// @brief Returns the first element in the range
152struct first
153{
154 template <typename IteratorType>
155 IteratedValueType<IteratorType>
156 operator() (IteratorType begin, IteratorType end) const
157 {
158 if (begin == end)
159 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
160
161 return *begin;
162 }
163};
164
165/// @brief Returns the last element in the range
166/// @details Requires a bidirectional iterator
167struct last
168{
169 template <typename IteratorType>
170 IteratedValueType<IteratorType>
171 operator() (IteratorType begin, IteratorType end) const
172 {
173 if (begin == end)
174 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
175
176 return *(--end);
177 }
178};
179
180/// @brief Returns the largest element in the range
181struct max
182{
183 template <typename IteratorType>
184 IteratedValueType<IteratorType>
185 operator() (IteratorType begin, IteratorType end) const
186 {
187 if (begin == end)
188 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
189
190 return *std::max_element (begin, end);
191 }
192};
193
194/// @brief Returns the arithmetic mean of all elements in the range
195struct mean
196{
197 template <typename IteratorType>
198 IteratedValueType<IteratorType>
199 operator() (IteratorType begin, IteratorType end) const
200 {
201 if (begin == end)
202 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
203
204 AccurateSum<IteratedValueType<IteratorType>> accurateSum;
205 size_t count = 0;
206
207 for (IteratorType it = begin; it != end; ++it)
208 {
209 accurateSum += *it;
210 ++count;
211 }
212
213 return accurateSum.template get<IteratedValueType<IteratorType>>() / count;
214 }
215};
216
217/// @brief Returns the smallest element in the range
218struct min
219{
220 template <typename IteratorType>
221 IteratedValueType<IteratorType>
222 operator() (IteratorType begin, IteratorType end) const
223 {
224 if (begin == end)
225 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
226
227 return *std::min_element (begin, end);
228 }
229};
230
231/// @brief Returns the nth element in the range (zero-based)
232/// @throws hart::IndexError if @p targetIndex is out of range
233/// @throws hart::SizeError is the range (or container) is empty
234struct nth
235{
236 nth (size_t targetIndex) : n_targetIndex (targetIndex) {}
237
238 template <typename IteratorType>
239 IteratedValueType<IteratorType>
240 operator() (IteratorType begin, IteratorType end) const
241 {
242 if (begin == end)
243 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
244
245 IteratorType iterator = begin;
246
247 for (size_t i = 0; i < n_targetIndex; ++i)
248 {
249 ++iterator;
250
251 if (iterator == end)
252 HART_THROW_OR_RETURN (hart::IndexError, "Target index is out of range", hart::nan<IteratedValueType<IteratorType>>());
253 }
254
255 return *iterator;
256 }
257private:
258 const size_t n_targetIndex;
259};
260
261/// @brief Returns the percentile value
262/// @param quantile Quantile value, in 0..1 range
263/// @param interpolation Type of interpolation, nearest or linear
265{
266 percentile (double quantile, Interpolation interpolation = Interpolation::nearest) :
267 m_quantile (quantile),
268 m_interpolation (interpolation)
269 {
270 }
271
272 template <typename IteratorType>
273 auto operator() (IteratorType begin, IteratorType end) const
274 -> IteratedValueType<IteratorType>
275 {
276 hassert (m_interpolation == Interpolation::nearest || m_interpolation == Interpolation::linear)
277
278 using ValueType = IteratedValueType<IteratorType>;
279
280 if (m_quantile < 0 || m_quantile > 1)
281 HART_THROW_OR_RETURN (hart::ValueError, "Quantile value should be in 0..1 range", hart::nan<ValueType>());
282
283 if (begin == end)
284 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<ValueType>());
285
286 const size_t n = static_cast<size_t> (std::distance (begin, end));
287 std::vector<ValueType> data (begin, end);
288
289 if (m_interpolation == Interpolation::nearest)
290 {
291 // Nearest-rank method, get an actual observed value
292 const size_t index = static_cast<size_t> (std::ceil (m_quantile * (n - 1)));
293
294 std::nth_element (data.begin(), data.begin() + index, data.end());
295 return data[index];
296 }
297 else
298 {
299 // Assuming Interpolation::linear
300 // Get a value with a fractional index
301 const double pos = m_quantile * (n - 1);
302 const size_t i0 = static_cast<size_t> (std::floor (pos));
303 const size_t i1 = static_cast<size_t> (std::ceil (pos));
304 const double t = pos - i0;
305
306 std::nth_element (data.begin(), data.begin() + i0, data.end());
307 const ValueType v0 = data[i0];
308
309 if (i0 == i1)
310 return v0;
311
312 std::nth_element (data.begin(), data.begin() + i1, data.end());
313 const ValueType v1 = data[i1];
314
315 return (v0 + (v1 - v0) * static_cast<ValueType> (t));
316 }
317 }
318
319private:
320 const double m_quantile;
321 const Interpolation m_interpolation;
322};
323
324/// @brief Returns the difference between largest and smallest values in the range
325struct range
326{
327 template <typename IteratorType>
328 IteratedValueType<IteratorType>
329 operator() (IteratorType begin, IteratorType end) const
330 {
331 if (begin == end)
332 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
333
334 const auto largestValue = *std::max_element (begin, end);
335 const auto smallestValue = *std::min_element (begin, end);
336 return largestValue - smallestValue;
337 }
338};
339
340/// @brief Returns the number of elements (values) in the range
341struct size
342{
343 template <typename IteratorType>
344 size_t operator() (IteratorType begin, IteratorType end) const
345 {
346 return static_cast<size_t> (std::distance (begin, end));
347 }
348};
349
350/// @brief Returns the sum of all elements in the range
351struct sum
352{
353 template <typename IteratorType>
354 IteratedValueType<IteratorType>
355 operator() (IteratorType begin, IteratorType end) const
356 {
357 if (begin == end)
358 HART_THROW_OR_RETURN (hart::SizeError, "The range is empty", hart::nan<IteratedValueType<IteratorType>>());
359
360 AccurateSum<IteratedValueType<IteratorType>> accurateSum;
361
362 for (IteratorType it = begin; it != end; ++it)
363 accurateSum += *it;
364
365 return accurateSum;
366 }
367};
368
369/// @}
370
371} // namespace hart
Implements Kahan algorithm for floating point accumulations.
Thrown when a container index is out of range.
Thrown when an unexpected container size is encountered.
Thrown when an inappropriate value is encountered.
#define hassert(condition)
Triggers a HartAssertException if the condition is false
#define HART_THROW_OR_RETURN(ExceptionType, message, returnValue)
Throws an exception if HART_DO_NOT_THROW_EXCEPTIONS is set, prints a message and returns a specified ...
Interpolation
Interpolation method.
Returns true if all the values in the range are equal to each other within provided tolerance,...
allFloatsEqualToEachOther(double toleranceLinear=1e-8)
bool operator()(IteratorType begin, IteratorType end) const
Returns true if all the values in the range are equal to a specific value within provided tolerance,...
allFloatsEqualTo(double targetValue, double toleranceLinear=1e-8)
bool operator()(IteratorType begin, IteratorType end) const
Returns true if all elements in the range are NaN
bool operator()(IteratorType begin, IteratorType end) const
Returns true if at least one element in the range is NaN
bool operator()(IteratorType begin, IteratorType end) const
Returns the zero-based index of the largest element in the range.
size_t operator()(IteratorType begin, IteratorType end) const
Returns the zero-based index of the smallest element in the range.
size_t operator()(IteratorType begin, IteratorType end) const
Forwards the entire range as an std::vector, preserving the original order.
std::vector< IteratedValueType< IteratorType > > operator()(IteratorType begin, IteratorType end) const
Returns the first element in the range.
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const
Returns the last element in the range.
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const
Returns the largest element in the range.
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const
Returns the arithmetic mean of all elements in the range.
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const
Returns the smallest element in the range.
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const
Returns the nth element in the range (zero-based)
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const
nth(size_t targetIndex)
Returns the percentile value.
auto operator()(IteratorType begin, IteratorType end) const -> IteratedValueType< IteratorType >
percentile(double quantile, Interpolation interpolation=Interpolation::nearest)
Returns the difference between largest and smallest values in the range.
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const
Returns the number of elements (values) in the range.
size_t operator()(IteratorType begin, IteratorType end) const
Returns the sum of all elements in the range.
IteratedValueType< IteratorType > operator()(IteratorType begin, IteratorType end) const