Skip to content

Commit

Permalink
Add blocks: Multiply, Divide, Add, Subtract (#395)
Browse files Browse the repository at this point in the history
* Implement dynamic number of n_inputs
* Use std::algorithm instead for loop
* Add unit test

---------

Signed-off-by: Eltos <[email protected]>
  • Loading branch information
eltos authored Oct 31, 2024
1 parent ced4e41 commit 5b88526
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 4 deletions.
57 changes: 53 additions & 4 deletions blocks/math/include/gnuradio-4.0/math/Math.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ namespace gr::blocks::math {

namespace detail {
template<typename T>
T
defaultValue() noexcept {
T defaultValue() noexcept {
if constexpr (gr::arithmetic_or_complex_like<T> || gr::UncertainValueLike<T>) {
return static_cast<T>(1);
} else if constexpr (std::is_arithmetic_v<T>) {
Expand All @@ -31,8 +30,7 @@ struct MathOpImpl : public gr::Block<MathOpImpl<T, op>> {
GR_MAKE_REFLECTABLE(MathOpImpl, in, out, value);

template<gr::meta::t_or_simd<T> V>
[[nodiscard]] constexpr V
processOne(const V &a) const noexcept {
[[nodiscard]] constexpr V processOne(const V& a) const noexcept {
if constexpr (op == '*') {
return a * value;
} else if constexpr (op == '/') {
Expand All @@ -57,13 +55,64 @@ using MultiplyConst = MathOpImpl<T, '*'>;
template<typename T>
using DivideConst = MathOpImpl<T, '/'>;

template<typename T, typename op>
requires(std::is_arithmetic_v<T>)
struct MathOpMultiPortImpl : public gr::Block<MathOpMultiPortImpl<T, op>> {
using Description = Doc<R""(
@brief Math block combining multiple inputs into a single output with a given operation
Depending on the operator op this block computes:
- Multiply: out = in_1 * in_2 * in_3 * ...
- Divide: out = in_1 / in_2 / in_3 / ...
- Add: out = in_1 + in_2 + in_3 + ...
- Subtract: out = in_1 - in_2 - in_3 - ...
)"">;

// ports
std::vector<PortIn<T>> in;
PortOut<T> out;

// settings
Annotated<gr::Size_t, "n_inputs", Visible, Doc<"Number of inputs">, Limits<1U, 32U>> n_inputs = 0U;

GR_MAKE_REFLECTABLE(MathOpMultiPortImpl, in, out, n_inputs);

void settingsChanged(const gr::property_map& old_settings, const gr::property_map& new_settings) {
if (new_settings.contains("n_inputs") && old_settings.at("n_inputs") != new_settings.at("n_inputs")) {
in.resize(n_inputs);
}
}

template<gr::InputSpanLike TInSpan>
gr::work::Status processBulk(const std::span<TInSpan>& ins, gr::OutputSpanLike auto& sout) const {
std::copy(ins[0].begin(), ins[0].end(), sout.begin());
for (std::size_t n = 1; n < ins.size(); n++) {
std::transform(sout.begin(), sout.end(), ins[n].begin(), sout.begin(), op{});
}
return gr::work::Status::OK;
}
};

template<typename T>
using Add = MathOpMultiPortImpl<T, std::plus<T>>;
template<typename T>
using Subtract = MathOpMultiPortImpl<T, std::minus<T>>;
template<typename T>
using Multiply = MathOpMultiPortImpl<T, std::multiplies<T>>;
template<typename T>
using Divide = MathOpMultiPortImpl<T, std::divides<T>>;

} // namespace gr::blocks::math

// clang-format off
const inline auto registerConstMath = gr::registerBlock<gr::blocks::math::AddConst, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry())
| gr::registerBlock<gr::blocks::math::SubtractConst, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry())
| gr::registerBlock<gr::blocks::math::MultiplyConst, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry())
| gr::registerBlock<gr::blocks::math::DivideConst, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry());
const inline auto registerMultiMath = gr::registerBlock<gr::blocks::math::Add, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry())
| gr::registerBlock<gr::blocks::math::Subtract, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry())
| gr::registerBlock<gr::blocks::math::Multiply, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry())
| gr::registerBlock<gr::blocks::math::Divide, uint8_t, uint16_t, uint32_t, uint64_t, int8_t, int16_t, int32_t, int64_t, float, double /*, gr::UncertainValue<float>, gr::UncertainValue<double>, std::complex<float>, std::complex<double>, std::string, gr::Packet<float>, gr::Packet<double>, gr::Tensor<float>, gr::Tensor<double>, gr::DataSet<float>, gr::DataSet<double> */>(gr::globalBlockRegistry());
// clang-format on

#endif // GNURADIO_MATH_HPP
105 changes: 105 additions & 0 deletions blocks/math/test/qa_Math.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,41 @@

#include <gnuradio-4.0/math/Math.hpp>

#include <gnuradio-4.0/Block.hpp>
#include <gnuradio-4.0/Graph.hpp>
#include <gnuradio-4.0/Scheduler.hpp>
#include <gnuradio-4.0/testing/TagMonitors.hpp>

template<typename T>
struct TestParameters {
std::vector<std::vector<T>> inputs;
std::vector<T> output;
};

template<typename T, typename BlockUnderTest>
void test_block(const TestParameters<T> p) {
using namespace boost::ut;
using namespace gr;
using namespace gr::testing;
using namespace gr::blocks::math;
const Size_t n_inputs = static_cast<Size_t>(p.inputs.size());

// build test graph
Graph graph;
auto& block = graph.emplaceBlock<BlockUnderTest>({{"n_inputs", n_inputs}});
for (Size_t i = 0; i < n_inputs; ++i) {
auto& src = graph.emplaceBlock<TagSource<T>>({{"values", p.inputs[i]}, {"n_samples_max", static_cast<Size_t>(p.inputs[i].size())}});
expect(eq(graph.connect(src, "out"s, block, "in#"s + std::to_string(i)), ConnectionResult::SUCCESS)) << fmt::format("Failed to connect output port of src {} to input port 'in#{}' of block", i, i);
}
auto& sink = graph.emplaceBlock<TagSink<T, ProcessFunction::USE_PROCESS_ONE>>();
expect(eq(graph.connect<"out">(block).template to<"in">(sink), ConnectionResult::SUCCESS)) << "Failed to connect output port 'out' of block to input port of sink";

// execute and confirm result
gr::scheduler::Simple scheduler{std::move(graph)};
expect(scheduler.runAndWait().has_value()) << "Failed to run graph: No value";
expect(std::ranges::equal(sink._samples, p.output)) << fmt::format("Failed to validate block output: Expected {} but got {} for input {}", p.output, sink._samples, p.inputs);
};

const boost::ut::suite<"basic math tests"> basicMath = [] {
using namespace boost::ut;
using namespace gr;
Expand All @@ -10,6 +45,76 @@ const boost::ut::suite<"basic math tests"> basicMath = [] {
double /*, gr::UncertainValue<float>, gr::UncertainValue<double>,
std::complex<float>, std::complex<double>*/>();

// clang-format off

"Add"_test = []<typename T>(const T&) {
test_block<T, Add<T>>({
.inputs = {{1, 2, 8, 17}},
.output = { 1, 2, 8, 17}
});
test_block<T, Add<T>>({
.inputs = {{1, 2, 3, T( 4.2)},
{5, 6, 7, T( 8.3)}},
.output = { 6, 8, 10, T(12.5)}
});
test_block<T, Add<T>>({
.inputs = {{12, 35, 18, 17},
{31, 15, 27, 36},
{83, 46, 37, 41}},
.output = {126, 96, 82, 94}
});
} | kArithmeticTypes;

"Subtract"_test = []<typename T>(const T&) {
test_block<T, Subtract<T>>({
.inputs = {{1, 2, 8, 17}},
.output = { 1, 2, 8, 17}
});
test_block<T, Subtract<T>>({
.inputs = {{9, 7, 5, T(3.5)},
{3, 2, 0, T(1.2)}},
.output = { 6, 5, 5, T(2.3)}});
test_block<T, Subtract<T>>({
.inputs = {{15, 38, 88, 29},
{ 3, 12, 26, 18},
{ 0, 10, 50, 7}},
.output = { 12, 16, 12, 4}});
} | kArithmeticTypes;

"Multiply"_test = []<typename T>(const T&) {
test_block<T, Multiply<T>>({
.inputs = {{1, 2, 8, 17}},
.output = { 1, 2, 8, 17}
});
test_block<T, Multiply<T>>({
.inputs = {{1, 2, 3, T( 4.0)},
{4, 5, 6, T( 7.1)}},
.output = { 4, 10, 18, T(28.4)}});
test_block<T, Multiply<T>>({
.inputs = {{0, 1, 2, 3},
{4, 5, 6, 2},
{8, 9, 10, 11}},
.output = { 0, 45, 120, 66}});
} | kArithmeticTypes;

"Divide"_test = []<typename T>(const T&) {
test_block<T, Divide<T>>({
.inputs = {{1, 2, 8, 17}},
.output = { 1, 2, 8, 17}
});
test_block<T, Divide<T>>({
.inputs = {{9, 4, 5, T(7.0)},
{3, 4, 1, T(2.0)}},
.output = { 3, 1, 5, T(3.5)}});
test_block<T, Divide<T>>({
.inputs = {{0, 10, 40, 80},
{1, 2, 4, 20},
{1, 5, 5, 2}},
.output = { 0, 1, 2, 2}});
} | kArithmeticTypes;

// clang-format on

"AddConst"_test = []<typename T>(const T&) {
expect(eq(AddConst<T>().processOne(T(4)), T(4) + T(1))) << fmt::format("AddConst test for type {}\n", meta::type_name<T>());
auto block = AddConst<T>(property_map{{"value", T(2)}});
Expand Down

0 comments on commit 5b88526

Please sign in to comment.