Skip to content

Commit

Permalink
roc-streaminggh-752: Implemented Moving Histogram algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
spran180 authored and gavv committed Jul 28, 2024
1 parent 968677e commit 30e22dc
Show file tree
Hide file tree
Showing 2 changed files with 228 additions and 0 deletions.
123 changes: 123 additions & 0 deletions src/internal_modules/roc_core/mov_histogram.h
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_
105 changes: 105 additions & 0 deletions src/tests/roc_core/test_mov_histogram.cpp
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

0 comments on commit 30e22dc

Please sign in to comment.