Skip to content

Commit

Permalink
HistoryBuffer<T> flipped push_back<->push_front naming
Browse files Browse the repository at this point in the history
should (hopefully) simplify and provide a more intuitive the naming.

### Storage
 - Dynamic (`N == std::dynamic_extent`): uses `std::vector<T>` (size = 2 * capacity).
 - Fixed (`N != std::dynamic_extent`): uses `std::array<T, N * 2>`.

### Key Operations
 - `push_front(const T&)`: Add new item, drop oldest if full, index `[0]` is newest.
 - `push_back(const T&)`: Add new item, drop oldest if full, index `[0]` is oldest.
 - `pop_front()`, `pop_back()`: Remove from logical front/back of the ring.
 - `front()`, `back()`: Returns the first/last item in logical order.
 - `operator[](i)`, `at(i)`: Unchecked/checked access.
 - `resize(...)` (dynamic-only): Adjust capacity, preserving existing data.
 - `get_span(...)`: Obtain a contiguous view across wrap boundaries.

 ### Code Examples
 ```cpp
 gr::history_buffer<int, 5> hb_newest;   // use push_front -> index[0] is newest
 hb_newest.push_front(1); // [1]
 hb_newest.push_front(2); // [2, 1]
 hb_newest.push_front(3); // [3, 2, 1]
 // => index[0] == 3, newest item

gr::history_buffer<int, 5> hb_oldest;   // use push_back -> index[0] is oldest
hb_oldest.push_back(10); // [10]
hb_oldest.push_back(20); // [10, 20]
hb_oldest.push_back(30); // [10, 20, 30]
// => index[0] == 10, oldest item

hb_newest.pop_front();  // remove newest => now hb_newest: [2, 1]
hb_oldest.pop_front();  // remove oldest => now hb_oldest: [20, 30]

auto val = hb_newest.front();  // val == 2
auto last = hb_newest.back();  // last == 1
 ```

 Also added/extended te `HistoryBuffer<T>` performance benchmark

Signed-off-by: rstein <r.steinhagen@gsi.de>
Signed-off-by: Ralph J. Steinhagen <r.steinhagen@gsi.de>
Signed-off-by: rstein <r.steinhagen@gsi.de>
  • Loading branch information
RalphSteinhagen committed Jan 15, 2025
1 parent f5909b9 commit d777338
Show file tree
Hide file tree
Showing 14 changed files with 139 additions and 121 deletions.
6 changes: 3 additions & 3 deletions algorithm/include/gnuradio-4.0/algorithm/SchmittTrigger.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ struct SchmittTrigger {
}

if constexpr (Method == BASIC_LINEAR_INTERPOLATION) {
_historyBuffer.push_back(input);
_historyBuffer.push_front(input);
if (_historyBuffer.size() < 2) {
return EdgeDetection::NONE;
}
Expand All @@ -113,7 +113,7 @@ struct SchmittTrigger {

auto computeEdgePosition = [&](const EdgeType& y1, const EdgeType& y2) -> std::pair<std::int32_t, EdgeType> {
if (y1 == y2) {
return {static_cast<std::int32_t>(0), static_cast<EdgeType>(0)};
return {static_cast<std::int32_t>(0U), static_cast<EdgeType>(0)};
}
const EdgeType offset = (EdgeType(_offset) - y1) / (y2 - y1);
std::int32_t intPart = static_cast<std::int32_t>(std::floor(gr::value(offset)));
Expand Down Expand Up @@ -145,7 +145,7 @@ struct SchmittTrigger {
}

if constexpr (Method == LINEAR_INTERPOLATION) {
_historyBuffer.push_back(input);
_historyBuffer.push_front(input);

if (_historyBuffer.size() < 2) {
return EdgeDetection::NONE;
Expand Down
20 changes: 10 additions & 10 deletions algorithm/include/gnuradio-4.0/algorithm/filter/FilterTool.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,35 +124,35 @@ template<typename T, std::size_t bufferSize, Form form = std::is_floating_point_
if constexpr (form == Form::DF_I) {
// y[n] = b[0]·x[n] + b[1]·x[n-1] + … + b[N]·x[n-N]
// - a[1]·y[n-1] - a[2]·y[n-2] - … - a[M]·y[n-M]
inputHistory.push_back(input);
inputHistory.push_front(input);
T output = std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()) // feed-forward path
- std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()); // feedback path
outputHistory.push_back(output);
outputHistory.push_front(output);
return output;
} else if constexpr (form == Form::DF_II) {
// w[n] = x[n] - a[1]·w[n-1] - a[2]·w[n-2] - … - a[M]·w[n-M]
// y[n] = b[0]·w[n] + b[1]·w[n-1] + … + b[N]·w[n-N]
if (a.size() > 1) {
const T w = input - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
inputHistory.push_back(w);
inputHistory.push_front(w);
return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
} else {
inputHistory.push_back(input);
inputHistory.push_front(input);
return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
}
} else if constexpr (form == Form::DF_I_TRANSPOSED) {
// w_1[n] = x[n] - a[1]·w_2[n-1] - a[2]·w_2[n-2] - … - a[M]·w_2[n-M]
// y[n] = b[0]·w_2[n] + b[1]·w_2[n-1] + … + b[N]·w_2[n-N]
T v0 = input - std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>());
outputHistory.push_back(v0);
outputHistory.push_front(v0);
return std::transform_reduce(execPolicy, b.cbegin(), b.cend(), outputHistory.cbegin(), T{0}, std::plus<>(), std::multiplies<>());
} else if constexpr (form == Form::DF_II_TRANSPOSED) {
// y[n] = b_0·f[n] + \sum_(k=1)^N(b_k·f[n−k] − a_k·y[n−k])
T output = b[0] * input //
+ std::transform_reduce(execPolicy, std::next(b.cbegin()), b.cend(), inputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>()) //
- std::transform_reduce(execPolicy, std::next(a.cbegin()), a.cend(), outputHistory.cbegin(), static_cast<T>(0), std::plus<>(), std::multiplies<>());
inputHistory.push_back(input);
outputHistory.push_back(output);
inputHistory.push_front(input);
outputHistory.push_front(output);
return output;
} else {
static_assert(gr::meta::always_false<T>, "should not reach here");
Expand Down Expand Up @@ -276,12 +276,12 @@ struct Filter<UncertainValue<T>, bufferSize, form, execPolicy> {
const auto& autocorrelationFunction = section.autoCorrelation;

// Feed-forward path (uncorrelated uncertainties)
inputHistory.push_back(inputUncertainty * inputUncertainty);
inputHistory.push_front(inputUncertainty * inputUncertainty);
TBaseType feedForwardUncertainty = std::transform_reduce(execPolicy, b.cbegin(), b.cend(), inputHistory.cbegin(), static_cast<TBaseType>(0), //
std::plus<>(), [](TBaseType bVal, TBaseType sigma2) { return bVal * bVal * sigma2; });

if (a.size() <= 1 || autocorrelationFunction.empty()) {
outputHistory.push_back(feedForwardUncertainty);
outputHistory.push_front(feedForwardUncertainty);
return feedForwardUncertainty;
}

Expand All @@ -296,7 +296,7 @@ struct Filter<UncertainValue<T>, bufferSize, form, execPolicy> {
}

TBaseType totalUncertainty = feedForwardUncertainty + feedbackUncertainty;
outputHistory.push_back(totalUncertainty);
outputHistory.push_front(totalUncertainty);
return totalUncertainty;
}

Expand Down
2 changes: 1 addition & 1 deletion algorithm/test/qa_FilterTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,7 @@ const boost::ut::suite<"IIR & FIR Benchmarks"> filterBenchmarks = [] {
gr::HistoryBuffer<T, 8> buffer;
::benchmark::benchmark<10>(fmt::format("HistoryBuffer<{}>", gr::meta::type_name<T>()), nSamples) = [&actualGain, &buffer, &yValues] {
for (auto& yValue : yValues) {
buffer.push_back(yValue);
buffer.push_front(yValue);
actualGain = std::max(actualGain, buffer[0]);
}
};
Expand Down
4 changes: 2 additions & 2 deletions blocks/basic/include/gnuradio-4.0/basic/DataSink.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -591,7 +591,7 @@ This block type is mean for non-data set input streams. For input streams of typ
listener->process(historyView, inData, tagData);
}
if (_history) {
_history->push_back_bulk(inData.begin(), inData.end());
_history->push_front(inData);
}
}
return work::Status::OK;
Expand All @@ -609,7 +609,7 @@ This block type is mean for non-data set input streams. For input streams of typ

auto new_history = gr::HistoryBuffer<T>(new_size);
if (_history) {
new_history.push_back_bulk(_history->begin(), _history->end());
new_history.push_front(_history->begin(), _history->end());
}
_history = std::move(new_history);
}
Expand Down
6 changes: 2 additions & 4 deletions blocks/basic/include/gnuradio-4.0/basic/StreamToDataSet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,7 @@ If multiple 'start' or 'stop' Tags arrive in a single merged tag, only one DataS
throw gr::exception("n_pre must be <= output port CircularBuffer size");
}
}
auto newBuffer = HistoryBuffer<T>(MIN_BUFFER_SIZE + std::get<gr::Size_t>(newSettings.at("n_pre")));
newBuffer.push_back_bulk(_history);
_history = std::move(newBuffer);
_history.resize(MIN_BUFFER_SIZE + std::get<gr::Size_t>(newSettings.at("n_pre")));
}

if constexpr (!streamOut) {
Expand Down Expand Up @@ -321,7 +319,7 @@ If multiple 'start' or 'stop' Tags arrive in a single merged tag, only one DataS
void copyInputSamplesToHistory(InputSpanLike auto& inSamples, std::size_t maxSamplesToCopy) {
if (n_pre > 0) {
const auto samplesToCopy = std::min(maxSamplesToCopy, inSamples.size());
_history.push_back_bulk(inSamples.begin(), std::next(inSamples.begin(), static_cast<std::ptrdiff_t>(samplesToCopy)));
_history.push_front(inSamples.begin(), std::next(inSamples.begin(), static_cast<std::ptrdiff_t>(samplesToCopy)));
}
}

Expand Down
1 change: 0 additions & 1 deletion blocks/basic/include/gnuradio-4.0/basic/SyncBlock.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
#define GNURADIO_SYNC_BLOCK_HPP

#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/HistoryBuffer.hpp>

namespace gr::basic {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/BlockRegistry.hpp>

#include <gnuradio-4.0/HistoryBuffer.hpp>
#include <gnuradio-4.0/algorithm/filter/FilterTool.hpp>
#include <gnuradio-4.0/meta/UncertainValue.hpp>

Expand Down
12 changes: 6 additions & 6 deletions blocks/filter/include/gnuradio-4.0/filter/FrequencyEstimator.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,10 @@ This block estimates the frequency of a signal using the time-domain algorithm d
requires(TParent::ResamplingControl::kIsConst)
{
// process input sample through the IIR filter
_inputHistory.push_back(input);
_inputHistory.push_front(input);
const T output = std::inner_product(_singleFilterSection.b.cbegin(), _singleFilterSection.b.cend(), _inputHistory.cbegin(), static_cast<T>(0)) // feed-forward
- std::inner_product(_singleFilterSection.a.cbegin() + 1, _singleFilterSection.a.cend(), _outputHistory.cbegin(), static_cast<T>(0)); // feed-back
_outputHistory.push_back(output);
_outputHistory.push_front(output);
_prevFrequency = estimateFrequency();
return _prevFrequency;
}
Expand All @@ -106,12 +106,12 @@ This block estimates the frequency of a signal using the time-domain algorithm d

for (const T& sample : chunk) {
// process input sample through the IIR filter
_inputHistory.push_back(sample);
_inputHistory.push_front(sample);

const T output_sample = std::inner_product(_singleFilterSection.b.cbegin(), _singleFilterSection.b.cend(), _inputHistory.cbegin(), static_cast<T>(0)) // feed-forward
- std::inner_product(_singleFilterSection.a.cbegin() + 1, _singleFilterSection.a.cend(), _outputHistory.cbegin(), static_cast<T>(0)); // feed-back

_outputHistory.push_back(output_sample);
_outputHistory.push_front(output_sample);
}

_prevFrequency = estimateFrequency();
Expand Down Expand Up @@ -241,7 +241,7 @@ This block estimates the frequency of a signal using the frequency-domain algori
[[nodiscard]] T processOne(T input) noexcept
requires(TParent::ResamplingControl::kIsConst)
{
_inputHistory.push_back(input);
_inputHistory.push_front(input);
_prevFrequency = estimateFrequencyFFT();
return _prevFrequency;
}
Expand All @@ -260,7 +260,7 @@ This block estimates the frequency of a signal using the frequency-domain algori
std::span<const T> chunk = input.subspan(offset, this->input_chunk_size);

for (const T& sample : chunk) {
_inputHistory.push_back(sample);
_inputHistory.push_front(sample);
}

_prevFrequency = estimateFrequencyFFT();
Expand Down
14 changes: 7 additions & 7 deletions blocks/filter/include/gnuradio-4.0/filter/time_domain_filter.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ H(z) = b[0] + b[1]*z^-1 + b[2]*z^-2 + ... + b[N]*z^-N
}

constexpr T processOne(T input) noexcept {
inputHistory.push_back(input);
inputHistory.push_front(input);
return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
}
};
Expand Down Expand Up @@ -85,32 +85,32 @@ a are the feedback coefficients
if constexpr (form == IIRForm::DF_I) {
// y[n] = b[0] * x[n] + b[1] * x[n-1] + ... + b[N] * x[n-N]
// - a[1] * y[n-1] - a[2] * y[n-2] - ... - a[M] * y[n-M]
inputHistory.push_back(input);
inputHistory.push_front(input);
const T feedforward = std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
const T feedback = std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
const T output = feedforward - feedback;
outputHistory.push_back(output);
outputHistory.push_front(output);
return output;
} else if constexpr (form == IIRForm::DF_II) {
// w[n] = x[n] - a[1] * w[n-1] - a[2] * w[n-2] - ... - a[M] * w[n-M]
// y[n] = b[0] * w[n] + b[1] * w[n-1] + ... + b[N] * w[n-N]
const T w = input - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
inputHistory.push_back(w);
inputHistory.push_front(w);

return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
} else if constexpr (form == IIRForm::DF_I_TRANSPOSED) {
// w_1[n] = x[n] - a[1] * w_2[n-1] - a[2] * w_2[n-2] - ... - a[M] * w_2[n-M]
// y[n] = b[0] * w_2[n] + b[1] * w_2[n-1] + ... + b[N] * w_2[n-N]
const T v0 = input - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
outputHistory.push_back(v0);
outputHistory.push_front(v0);

return std::transform_reduce(std::execution::unseq, b.cbegin(), b.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});
} else if constexpr (form == IIRForm::DF_II_TRANSPOSED) {
// y[n] = b_0 * f[n] + Σ (b_k * f[n−k] − a_k * y[n−k]) for k = 1 to N
const T output = b[0] * input + std::transform_reduce(std::execution::unseq, b.cbegin() + 1, b.cend(), inputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{}) - std::transform_reduce(std::execution::unseq, a.cbegin() + 1, a.cend(), outputHistory.cbegin(), T{0}, std::plus<>{}, std::multiplies<>{});

inputHistory.push_back(input);
outputHistory.push_back(output);
inputHistory.push_front(input);
outputHistory.push_front(output);
return output;
}
}
Expand Down
29 changes: 11 additions & 18 deletions blocks/testing/include/gnuradio-4.0/testing/ImChartMonitor.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa

GR_MAKE_REFLECTABLE(ImChartMonitor, in, sample_rate, signal_name);

HistoryBuffer<T> _historyBufferX{1000};
HistoryBuffer<T> _historyBufferY{1000};
HistoryBuffer<Tag> _historyBufferTags{1000};
HistoryBuffer<T> _historyBufferX{1000UZ};
HistoryBuffer<T> _historyBufferY{1000UZ};
HistoryBuffer<T> _historyBufferTags{1000UZ};

void start() {
fmt::println("started sink {} aka. '{}'", this->unique_name, this->name);
Expand All @@ -44,11 +44,14 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa
_historyBufferY.push_back(input);

if (this->inputTagsPresent()) { // received tag
_historyBufferTags.push_back(this->mergedInputTag());
_historyBufferTags[1].index = 0;
_historyBufferTags.push_back(_historyBufferY.back());
this->_mergedInputTag.map.clear(); // TODO: provide proper API for clearing tags
} else {
_historyBufferTags.push_back(Tag(0UZ, property_map()));
if constexpr (std::is_floating_point_v<T>) {
_historyBufferTags.push_back(std::numeric_limits<T>::quiet_NaN());
} else {
_historyBufferTags.push_back(std::numeric_limits<T>::lowest());
}
}
}

Expand All @@ -66,16 +69,6 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa
gr::graphs::resetView();
}

// create reversed copies -- draw(...) expects std::ranges::input_range ->
std::vector<T> reversedX(_historyBufferX.rbegin(), _historyBufferX.rend());
std::vector<T> reversedY(_historyBufferY.rbegin(), _historyBufferY.rend());
std::vector<T> reversedTag(_historyBufferX.size());
if constexpr (std::is_floating_point_v<T>) {
std::transform(_historyBufferTags.rbegin(), _historyBufferTags.rend(), _historyBufferY.rbegin(), reversedTag.begin(), [](const Tag& tag, const T& yValue) { return tag.map.empty() ? std::numeric_limits<T>::quiet_NaN() : yValue; });
} else {
std::transform(_historyBufferTags.rbegin(), _historyBufferTags.rend(), _historyBufferY.rbegin(), reversedTag.begin(), [](const Tag& tag, const T& yValue) { return tag.map.empty() ? std::numeric_limits<T>::lowest() : yValue; });
}

auto adjustRange = [](T min, T max) {
min = std::min(min, T(0));
max = std::max(max, T(0));
Expand All @@ -84,8 +77,8 @@ struct ImChartMonitor : public Block<ImChartMonitor<T>, BlockingIO<false>, Drawa
};

auto chart = gr::graphs::ImChart<130, 28>({{*xMin, *xMax}, adjustRange(*yMin, *yMax)});
chart.draw(reversedX, reversedY, signal_name);
chart.draw<gr::graphs::Style::Marker>(reversedX, reversedTag, "Tags");
chart.draw(_historyBufferX, _historyBufferY, signal_name);
chart.draw<gr::graphs::Style::Marker>(_historyBufferX, _historyBufferTags, "Tags");
chart.draw();
} else if constexpr (gr::DataSetLike<T>) {
if (_historyBufferY.empty()) {
Expand Down
Loading

0 comments on commit d777338

Please sign in to comment.