forked from roc-streaming/roc-toolkit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
roc-streaminggh-752: Implemented Moving Histogram algorithm
- Loading branch information
Showing
2 changed files
with
228 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
/* | ||
* Copyright (c) 2023 Roc Streaming authors | ||
* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
//! @file roc_core/mov_histogram.h | ||
//! @brief Rolling window moving histogram. | ||
|
||
#ifndef ROC_CORE_MOV_HISTOGRAM_H_ | ||
#define ROC_CORE_MOV_HISTOGRAM_H_ | ||
|
||
#include "roc_core/array.h" | ||
#include "roc_core/iarena.h" | ||
#include "roc_core/panic.h" | ||
#include "roc_core/ring_queue.h" | ||
|
||
namespace roc { | ||
namespace core { | ||
|
||
//! @brief A class that implements a rolling window moving histogram. | ||
//! The MovHistogram class maintains a histogram of values within a specified window | ||
//! length. It divides the range of values into a specified number of bins and updates the | ||
//! histogram as new values are added and old values are removed from the window. | ||
//! @tparam T The type of values to be histogrammed. | ||
|
||
template <typename T> class MovHistogram { | ||
public: | ||
//! @brief Constructs a moving histogram. | ||
//! @param arena Memory arena for dynamic allocations. | ||
//! @param value_range_min The minimum value of the range to be histogrammed. | ||
//! @param value_range_max The maximum value of the range to be histogrammed. | ||
//! @param num_bins The number of bins in the histogram. Each bin represents a | ||
//! subrange of the value range. | ||
//! @param window_length The length of the moving window. Only values within this | ||
//! window are considered in the histogram. | ||
|
||
MovHistogram(IArena& arena, | ||
T value_range_min, | ||
T value_range_max, | ||
size_t num_bins, | ||
size_t window_length) | ||
: value_range_min_(value_range_min) | ||
, value_range_max_(value_range_max) | ||
, num_bins_(num_bins) | ||
, window_length_(window_length) | ||
, ring_buffer_(arena, window_length) | ||
, bins_(arena) | ||
, valid_(false) { | ||
if (num_bins == 0 || window_length == 0 || value_range_min >= value_range_max) { | ||
roc_panic("mov histogram: number of bins and window length must be greater " | ||
"than 0 and value_range_min must be less than value_range_max"); | ||
} | ||
|
||
bin_width_ = (value_range_max - value_range_min) / static_cast<T>(num_bins); | ||
|
||
if (!ring_buffer_.is_valid() || !bins_.resize(num_bins)) { | ||
return; | ||
} | ||
|
||
valid_ = true; | ||
} | ||
|
||
//! Check if the histogram is valid. | ||
bool is_valid() const { | ||
return valid_; | ||
} | ||
|
||
//! Add a value to the histogram. | ||
void add_value(const T& value) { | ||
T clamped_value = value; | ||
|
||
if (clamped_value < value_range_min_) { | ||
clamped_value = value_range_min_; | ||
} else if (clamped_value > value_range_max_) { | ||
clamped_value = value_range_max_; | ||
} | ||
|
||
if (ring_buffer_.size() == window_length_) { | ||
T oldest_value = ring_buffer_.front(); | ||
ring_buffer_.pop_front(); | ||
size_t oldest_bin_index = get_bin_index_(oldest_value); | ||
bins_[oldest_bin_index]--; | ||
} | ||
|
||
ring_buffer_.push_back(clamped_value); | ||
size_t new_bin_index = get_bin_index_(clamped_value); | ||
if (new_bin_index < num_bins_) { | ||
bins_[new_bin_index]++; | ||
} | ||
} | ||
|
||
//! Get the number of values in the given bin. | ||
size_t get_bin_counter(size_t bin_index) const { | ||
return bins_[bin_index]; | ||
} | ||
|
||
private: | ||
//! Get the bin index for the given value. | ||
size_t get_bin_index_(const T& value) const { | ||
if (value == value_range_max_) { | ||
return num_bins_ - 1; | ||
} | ||
|
||
return static_cast<size_t>((value - value_range_min_) / bin_width_); | ||
} | ||
|
||
T value_range_min_; | ||
T value_range_max_; | ||
size_t num_bins_; | ||
size_t window_length_; | ||
T bin_width_; | ||
RingQueue<T> ring_buffer_; | ||
Array<size_t> bins_; | ||
bool valid_; | ||
}; | ||
|
||
} // namespace core | ||
} // namespace roc | ||
|
||
#endif // ROC_CORE_MOV_HISTOGRAM_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
/* | ||
* Copyright (c) 2023 Roc Streaming authors | ||
* | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. | ||
*/ | ||
|
||
#include <CppUTest/TestHarness.h> | ||
|
||
#include "roc_core/heap_arena.h" | ||
#include "roc_core/mov_histogram.h" | ||
|
||
namespace roc { | ||
namespace core { | ||
|
||
namespace { | ||
|
||
enum { NumObjects = 10, EmbeddedCap = 5 }; | ||
|
||
struct Object { | ||
static long n_objects; | ||
|
||
size_t value; | ||
|
||
Object(size_t v = 0) | ||
: value(v) { | ||
n_objects++; | ||
} | ||
|
||
Object(const Object& other) | ||
: value(other.value) { | ||
n_objects++; | ||
} | ||
|
||
~Object() { | ||
n_objects--; | ||
} | ||
}; | ||
|
||
long Object::n_objects = 0; | ||
|
||
} // namespace | ||
|
||
TEST_GROUP(movhistogram) { | ||
HeapArena arena; | ||
}; | ||
|
||
TEST(movhistogram, single_pass) { | ||
const size_t value_range_min = 0; | ||
const size_t value_range_max = 100; | ||
const size_t num_bins = 10; | ||
const size_t win_length = 10; | ||
|
||
MovHistogram<size_t> hist(arena, value_range_min, value_range_max, num_bins, | ||
win_length); | ||
CHECK(hist.is_valid()); | ||
|
||
for (size_t i = 0; i < win_length; i++) { | ||
hist.add_value(i * num_bins); | ||
} | ||
|
||
for (size_t i = 0; i < num_bins; ++i) { | ||
LONGS_EQUAL(1, hist.get_bin_counter(i)); | ||
} | ||
} | ||
|
||
TEST(movhistogram, rolling_window) { | ||
const size_t value_range_min = 0; | ||
const size_t value_range_max = 100; | ||
const size_t num_bins = 10; | ||
const size_t win_length = 5; | ||
|
||
MovHistogram<size_t> hist(arena, value_range_min, value_range_max, num_bins, | ||
win_length); | ||
CHECK(hist.is_valid()); | ||
|
||
for (size_t i = 0; i < win_length * 2; i++) { | ||
hist.add_value(i * (value_range_max / num_bins)); | ||
} | ||
|
||
for (size_t i = 0; i < num_bins; ++i) { | ||
LONGS_EQUAL(i < win_length ? 0 : 1, hist.get_bin_counter(i)); | ||
} | ||
} | ||
|
||
TEST(movhistogram, value_equal_to_value_range_max) { | ||
const size_t value_range_min = 0; | ||
const size_t value_range_max = 100; | ||
const size_t num_bins = 10; | ||
const size_t win_length = 10; | ||
|
||
MovHistogram<size_t> hist(arena, value_range_min, value_range_max, num_bins, | ||
win_length); | ||
CHECK(hist.is_valid()); | ||
|
||
size_t test_value = value_range_max; | ||
hist.add_value(test_value); | ||
|
||
size_t expected_bin_index = num_bins - 1; | ||
LONGS_EQUAL(1, hist.get_bin_counter(expected_bin_index)); | ||
} | ||
|
||
} // namespace core | ||
} // namespace roc |