From 13035b00e2c362b11901dffbd32152142709ca3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 28 Dec 2023 09:07:06 +0000 Subject: [PATCH 01/12] feat(std): add fft operator --- .../include/rtbot/std/FastFourrierTransform.h | 115 ++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 libs/std/include/rtbot/std/FastFourrierTransform.h diff --git a/libs/std/include/rtbot/std/FastFourrierTransform.h b/libs/std/include/rtbot/std/FastFourrierTransform.h new file mode 100644 index 00000000..7ebf6e32 --- /dev/null +++ b/libs/std/include/rtbot/std/FastFourrierTransform.h @@ -0,0 +1,115 @@ +#include + +#include "rtbot/Operator.h" + +namespace rtbot { + +template +class FastFourrierTransform : public Operator { + public: + FastFourrierTransform(string const& id, size_t N = 7, size_t skip = 127, emitPower = true, bool emitRePart = false, + bool emitImPart = false) + : Operator(id) { + this->n = pow(2, N); + this->emitPower = emitPower; + this->emitRePart = emitRePart; + this->emitImPart = emitImPart; + // recall that the N passed in the constructor is used to compute the size of the input buffer + // the actual size of the FFT is 2^N + this->addDataInput("i1", this->n); + // allocate the vector that will contain the FFT + this->a = vector>(this->n); + // declare the output ports based on the parameters passed in the constructor + for (int i = 0; i < this->n; i++) { + this->addOutput("w" + to_string(i + 1)); + if (emitPower) this->addOutput("p" + to_string(i + 1)); + if (emitRePart) this->addOutput("re" + to_string(i + 1)); + if (emitImPart) this->addOutput("im" + to_string(i + 1)); + } + } + + string typeName() const override { return "FastFourrierTransform"; } + + void fft(vector>& a, bool invert) { + size_t n = a.size(); + if (n == 1) return; + + vector> a0(n / 2), a1(n / 2); + for (int i = 0; 2 * i < n; i++) { + a0[i] = a[2 * i]; + a1[i] = a[2 * i + 1]; + } + fft(a0, invert); + fft(a1, invert); + + V ang = 2 * M_PI / n * (invert ? -1 : 1); + complex w(1), wn(cos(ang), sin(ang)); + for (int i = 0; 2 * i < n; i++) { + a[i] = a0[i] + w * a1[i]; + if (invert) a[i] /= 2; + w *= wn; + } + } + + map>> processData() override { + this->skipCounter++; + + if (this->skipCounter < this->skip) return map>>(); + + this->skipCounter = 0; + + string inputPort; + auto in = this->getDataInputs(); + if (in.size() == 1) + inputPort = in.at(0); + else + throw runtime_error(typeName() + " : more than 1 input port found"); + map>> outputMsgs; + + auto input = this->dataInputs.find(inputPort)->second; + for (int i = 0; i < this->n; i++) { + auto c = this->a[i]; + c.real((input.at((this->n - 1) - i).value); + c.imag(0); + } + + // compute the FFT + fft(this->a, false); + + auto time = this->getDataInputLastMessage(inputPort).time; + for (size_t i = 0; i < this->n; i++) { + if (this->emitRePart) { + Message re(time, this->a[i].real()); + vector> toEmit(re); + outputMsgs.emplace("re" + to_string(i + 1), toEmit); + } + if (this->emitImPart) { + Message im(time, this->a[i].imag())); + vector> toEmit(im); + outputMsgs.emplace("re" + to_string(i + 1), toEmit); + } + if (this->emitPower) { + Message p(time, pow(this->a[i].real(), 2) + pow(this->a[i].imag(), 2)); + vector> toEmit(p); + outputMsgs.emplace("p" + to_string(i + 1), toEmit); + } + + Message re(time, i / this->n); + vector> toEmit(re); + outputMsgs.emplace("w" + to_string(i + 1), toEmit); + } + + return outputMsgs; + } + + private: + size_t skipCounter; + size_t skip; + size_t n; + bool emitPower; + bool emitRePart; + bool emitImPart; + vector> a; +}; + +} // namespace rtbot \ No newline at end of file From b801b60026c277cd92733e519036c046192ed839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 28 Dec 2023 10:16:05 +0000 Subject: [PATCH 02/12] fix(std): compilation error --- .../include/rtbot/std/FastFourrierTransform.h | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) diff --git a/libs/std/include/rtbot/std/FastFourrierTransform.h b/libs/std/include/rtbot/std/FastFourrierTransform.h index 7ebf6e32..25ebd5d9 100644 --- a/libs/std/include/rtbot/std/FastFourrierTransform.h +++ b/libs/std/include/rtbot/std/FastFourrierTransform.h @@ -7,8 +7,8 @@ namespace rtbot { template class FastFourrierTransform : public Operator { public: - FastFourrierTransform(string const& id, size_t N = 7, size_t skip = 127, emitPower = true, bool emitRePart = false, - bool emitImPart = false) + FastFourrierTransform(string const& id, size_t N = 7, size_t skip = 127, bool emitPower = true, + bool emitRePart = false, bool emitImPart = false) : Operator(id) { this->n = pow(2, N); this->emitPower = emitPower; @@ -30,30 +30,11 @@ class FastFourrierTransform : public Operator { string typeName() const override { return "FastFourrierTransform"; } - void fft(vector>& a, bool invert) { - size_t n = a.size(); - if (n == 1) return; - - vector> a0(n / 2), a1(n / 2); - for (int i = 0; 2 * i < n; i++) { - a0[i] = a[2 * i]; - a1[i] = a[2 * i + 1]; - } - fft(a0, invert); - fft(a1, invert); - - V ang = 2 * M_PI / n * (invert ? -1 : 1); - complex w(1), wn(cos(ang), sin(ang)); - for (int i = 0; 2 * i < n; i++) { - a[i] = a0[i] + w * a1[i]; - if (invert) a[i] /= 2; - w *= wn; - } - } - map>> processData() override { this->skipCounter++; + cout << "skipCounter = " << this->skipCounter << endl; + cout << "skip = " << this->skip << endl; if (this->skipCounter < this->skip) return map>>(); this->skipCounter = 0; @@ -68,9 +49,8 @@ class FastFourrierTransform : public Operator { auto input = this->dataInputs.find(inputPort)->second; for (int i = 0; i < this->n; i++) { - auto c = this->a[i]; - c.real((input.at((this->n - 1) - i).value); - c.imag(0); + this->a[i].real((input.at((this->n - 1) - i).value)); + this->a[i].imag(0); } // compute the FFT @@ -78,27 +58,37 @@ class FastFourrierTransform : public Operator { auto time = this->getDataInputLastMessage(inputPort).time; for (size_t i = 0; i < this->n; i++) { + cout << "a[" << i << "] = " << this->a[i] << endl; if (this->emitRePart) { Message re(time, this->a[i].real()); - vector> toEmit(re); + vector> toEmit = {re}; outputMsgs.emplace("re" + to_string(i + 1), toEmit); + cout << "re" << i + 1 << " = " << re.value << endl; } if (this->emitImPart) { - Message im(time, this->a[i].imag())); - vector> toEmit(im); + Message im(time, this->a[i].imag()); + vector> toEmit = {im}; outputMsgs.emplace("re" + to_string(i + 1), toEmit); } if (this->emitPower) { Message p(time, pow(this->a[i].real(), 2) + pow(this->a[i].imag(), 2)); - vector> toEmit(p); + vector> toEmit = {p}; outputMsgs.emplace("p" + to_string(i + 1), toEmit); } - Message re(time, i / this->n); - vector> toEmit(re); + Message w(time, i * 1.0 / this->n); + vector> toEmit = {w}; outputMsgs.emplace("w" + to_string(i + 1), toEmit); } + for (const auto& pair : outputMsgs) { + std::cout << pair.first << ": "; + for (const auto& msg : pair.second) { + std::cout << "Time: " << msg.time << ", Value: " << msg.value << "; "; + } + std::cout << std::endl; + } + return outputMsgs; } @@ -110,6 +100,27 @@ class FastFourrierTransform : public Operator { bool emitRePart; bool emitImPart; vector> a; + + void fft(vector>& a, bool invert) { + size_t n = a.size(); + if (n == 1) return; + + vector> a0(n / 2), a1(n / 2); + for (int i = 0; 2 * i < n; i++) { + a0[i] = a[2 * i]; + a1[i] = a[2 * i + 1]; + } + fft(a0, invert); + fft(a1, invert); + + V ang = 2 * M_PI / n * (invert ? -1 : 1); + complex w(1), wn(cos(ang), sin(ang)); + for (int i = 0; 2 * i < n; i++) { + a[i] = a0[i] + w * a1[i]; + if (invert) a[i] /= 2; + w *= wn; + } + } }; } // namespace rtbot \ No newline at end of file From 428d0a4bcd3605efe04d0296c9ad0accac10736a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 28 Dec 2023 10:16:19 +0000 Subject: [PATCH 03/12] test(std): add test for fft --- .../std/test/test_fast_fourrier_transform.cpp | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 libs/std/test/test_fast_fourrier_transform.cpp diff --git a/libs/std/test/test_fast_fourrier_transform.cpp b/libs/std/test/test_fast_fourrier_transform.cpp new file mode 100644 index 00000000..6e80dc10 --- /dev/null +++ b/libs/std/test/test_fast_fourrier_transform.cpp @@ -0,0 +1,34 @@ +#include + +#include "rtbot/std/FastFourrierTransform.h" + +using namespace rtbot; +using namespace std; + +TEST_CASE("FastFourrierTransform") { + SECTION("FFT computation and output messages") { + auto fft = FastFourrierTransform("fft", 3, 1); + cout << "fft.n = " << 3 << endl; + // Create a sine wave input + for (uint64_t i = 0; i < 100; i++) { + double value = sin(2 * M_PI * i / 100.0); + fft.receiveData(Message(i, value)); + } + + // Process the data and get the output messages + auto emitted = fft.executeData(); + for (const auto& outerPair : emitted) { + std::cout << outerPair.first << ": " << std::endl; + for (const auto& innerPair : outerPair.second) { + std::cout << "\t" << innerPair.first << ": "; + for (const auto& msg : innerPair.second) { + std::cout << "Time: " << msg.time << ", Value: " << msg.value << "; "; + } + std::cout << std::endl; + } + } + // Check if the output messages match the expected output + // The expected output is not known in advance, so we just check if the output is valid + REQUIRE(emitted.find("fft")->second.find("w4")->second.at(0).value == Approx(-1.0)); + } +} From c4304fff224d0fa79d0a89df535c5b1b4c4833ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 28 Dec 2023 15:20:36 +0000 Subject: [PATCH 04/12] fix(std): rewrite fft algorithm --- .../include/rtbot/std/FastFourrierTransform.h | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/libs/std/include/rtbot/std/FastFourrierTransform.h b/libs/std/include/rtbot/std/FastFourrierTransform.h index 25ebd5d9..a889a5d7 100644 --- a/libs/std/include/rtbot/std/FastFourrierTransform.h +++ b/libs/std/include/rtbot/std/FastFourrierTransform.h @@ -1,4 +1,5 @@ #include +#include #include "rtbot/Operator.h" @@ -33,8 +34,6 @@ class FastFourrierTransform : public Operator { map>> processData() override { this->skipCounter++; - cout << "skipCounter = " << this->skipCounter << endl; - cout << "skip = " << this->skip << endl; if (this->skipCounter < this->skip) return map>>(); this->skipCounter = 0; @@ -49,26 +48,24 @@ class FastFourrierTransform : public Operator { auto input = this->dataInputs.find(inputPort)->second; for (int i = 0; i < this->n; i++) { - this->a[i].real((input.at((this->n - 1) - i).value)); + this->a[i].real((input.at(i).value)); this->a[i].imag(0); } // compute the FFT - fft(this->a, false); + fft(this->a); auto time = this->getDataInputLastMessage(inputPort).time; for (size_t i = 0; i < this->n; i++) { - cout << "a[" << i << "] = " << this->a[i] << endl; if (this->emitRePart) { Message re(time, this->a[i].real()); vector> toEmit = {re}; outputMsgs.emplace("re" + to_string(i + 1), toEmit); - cout << "re" << i + 1 << " = " << re.value << endl; } if (this->emitImPart) { Message im(time, this->a[i].imag()); vector> toEmit = {im}; - outputMsgs.emplace("re" + to_string(i + 1), toEmit); + outputMsgs.emplace("im" + to_string(i + 1), toEmit); } if (this->emitPower) { Message p(time, pow(this->a[i].real(), 2) + pow(this->a[i].imag(), 2)); @@ -81,17 +78,11 @@ class FastFourrierTransform : public Operator { outputMsgs.emplace("w" + to_string(i + 1), toEmit); } - for (const auto& pair : outputMsgs) { - std::cout << pair.first << ": "; - for (const auto& msg : pair.second) { - std::cout << "Time: " << msg.time << ", Value: " << msg.value << "; "; - } - std::cout << std::endl; - } - return outputMsgs; } + size_t getSize() { return this->n; } + private: size_t skipCounter; size_t skip; @@ -101,24 +92,42 @@ class FastFourrierTransform : public Operator { bool emitImPart; vector> a; - void fft(vector>& a, bool invert) { - size_t n = a.size(); - if (n == 1) return; - - vector> a0(n / 2), a1(n / 2); - for (int i = 0; 2 * i < n; i++) { - a0[i] = a[2 * i]; - a1[i] = a[2 * i + 1]; + // see https://rosettacode.org/wiki/Fast_Fourier_transform#C.2B.2B + void fft(std::vector>& x) { + // DFT + unsigned int N = x.size(), k = N, n; + double thetaT = 3.14159265358979323846264338328L / N; + std::complex phiT = std::complex(cos(thetaT), -sin(thetaT)), Tt; + while (k > 1) { + n = k; + k >>= 1; + phiT = phiT * phiT; + Tt = 1.0L; + for (unsigned int l = 0; l < k; l++) { + for (unsigned int a = l; a < N; a += n) { + unsigned int b = a + k; + std::complex t = x[a] - x[b]; + x[a] += x[b]; + x[b] = t * Tt; + } + Tt *= phiT; + } } - fft(a0, invert); - fft(a1, invert); - - V ang = 2 * M_PI / n * (invert ? -1 : 1); - complex w(1), wn(cos(ang), sin(ang)); - for (int i = 0; 2 * i < n; i++) { - a[i] = a0[i] + w * a1[i]; - if (invert) a[i] /= 2; - w *= wn; + // Decimate + unsigned int m = (unsigned int)log2(N); + for (unsigned int a = 0; a < N; a++) { + unsigned int b = a; + // Reverse bits + b = (((b & 0xaaaaaaaa) >> 1) | ((b & 0x55555555) << 1)); + b = (((b & 0xcccccccc) >> 2) | ((b & 0x33333333) << 2)); + b = (((b & 0xf0f0f0f0) >> 4) | ((b & 0x0f0f0f0f) << 4)); + b = (((b & 0xff00ff00) >> 8) | ((b & 0x00ff00ff) << 8)); + b = ((b >> 16) | (b << 16)) >> (32 - m); + if (b > a) { + std::complex t = x[a]; + x[a] = x[b]; + x[b] = t; + } } } }; From 3f0c37677a09866725f165ccf93e12d2b7531446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 28 Dec 2023 15:20:50 +0000 Subject: [PATCH 05/12] test(std): add test for fft operator --- .../std/test/test_fast_fourrier_transform.cpp | 45 ++++++++++++------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/libs/std/test/test_fast_fourrier_transform.cpp b/libs/std/test/test_fast_fourrier_transform.cpp index 6e80dc10..1b421fa1 100644 --- a/libs/std/test/test_fast_fourrier_transform.cpp +++ b/libs/std/test/test_fast_fourrier_transform.cpp @@ -5,30 +5,41 @@ using namespace rtbot; using namespace std; +void printEmittedMessages( + const std::map>>>& emitted) { + for (const auto& outerPair : emitted) { + std::cout << outerPair.first << ": " << std::endl; + for (const auto& innerPair : outerPair.second) { + std::cout << "\t" << innerPair.first << ": "; + for (const auto& msg : innerPair.second) { + std::cout << "Time: " << msg.time << ", Value: " << msg.value << "; "; + } + std::cout << std::endl; + } + } +} + TEST_CASE("FastFourrierTransform") { SECTION("FFT computation and output messages") { - auto fft = FastFourrierTransform("fft", 3, 1); - cout << "fft.n = " << 3 << endl; + auto fft = FastFourrierTransform("fft", 3, 1, true, true, true); + vector signal = {1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0}; // Create a sine wave input - for (uint64_t i = 0; i < 100; i++) { - double value = sin(2 * M_PI * i / 100.0); - fft.receiveData(Message(i, value)); + for (uint64_t i = 0; i < signal.size(); i++) { + // double value = sin(2 * M_PI * i / 100.0); + fft.receiveData(Message(i, signal[i])); } // Process the data and get the output messages auto emitted = fft.executeData(); - for (const auto& outerPair : emitted) { - std::cout << outerPair.first << ": " << std::endl; - for (const auto& innerPair : outerPair.second) { - std::cout << "\t" << innerPair.first << ": "; - for (const auto& msg : innerPair.second) { - std::cout << "Time: " << msg.time << ", Value: " << msg.value << "; "; - } - std::cout << std::endl; - } + // printEmittedMessages(emitted); + + vector realExpected = {4, 1, 0, 1, 0, 1, 0, 1}; + vector imagExpected = {0.0, -2.4142136, 0.0, -0.4142136, 0.0, 0.4142136, 0.0, 2.4142136}; + vector powerExpected = {16, 6.8284271, 0, 1.171573, 0, 1.171573, 0, 6.8284271}; + for (size_t i = 0; i < fft.getSize(); i++) { + REQUIRE(emitted.find("fft")->second.find("re" + to_string(i + 1))->second.at(0).value == Approx(realExpected[i])); + REQUIRE(emitted.find("fft")->second.find("im" + to_string(i + 1))->second.at(0).value == Approx(imagExpected[i])); + REQUIRE(emitted.find("fft")->second.find("p" + to_string(i + 1))->second.at(0).value == Approx(powerExpected[i])); } - // Check if the output messages match the expected output - // The expected output is not known in advance, so we just check if the output is valid - REQUIRE(emitted.find("fft")->second.find("w4")->second.at(0).value == Approx(-1.0)); } } From 2237dfe08582ec9f84a1dd97cf44d67776ce4636 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 28 Dec 2023 15:23:53 +0000 Subject: [PATCH 06/12] fix(std): shift emitted frequency in fft --- libs/std/include/rtbot/std/FastFourrierTransform.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/std/include/rtbot/std/FastFourrierTransform.h b/libs/std/include/rtbot/std/FastFourrierTransform.h index a889a5d7..3f557368 100644 --- a/libs/std/include/rtbot/std/FastFourrierTransform.h +++ b/libs/std/include/rtbot/std/FastFourrierTransform.h @@ -73,7 +73,7 @@ class FastFourrierTransform : public Operator { outputMsgs.emplace("p" + to_string(i + 1), toEmit); } - Message w(time, i * 1.0 / this->n); + Message w(time, (i + 1.0) / this->n); vector> toEmit = {w}; outputMsgs.emplace("w" + to_string(i + 1), toEmit); } From 80964812659e2c54b886c7a84a162ff24d431d35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 4 Jan 2024 16:52:45 +0000 Subject: [PATCH 07/12] chore(test): remove helper function --- libs/std/test/test_fast_fourrier_transform.cpp | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/libs/std/test/test_fast_fourrier_transform.cpp b/libs/std/test/test_fast_fourrier_transform.cpp index 1b421fa1..0f1ba90c 100644 --- a/libs/std/test/test_fast_fourrier_transform.cpp +++ b/libs/std/test/test_fast_fourrier_transform.cpp @@ -5,20 +5,6 @@ using namespace rtbot; using namespace std; -void printEmittedMessages( - const std::map>>>& emitted) { - for (const auto& outerPair : emitted) { - std::cout << outerPair.first << ": " << std::endl; - for (const auto& innerPair : outerPair.second) { - std::cout << "\t" << innerPair.first << ": "; - for (const auto& msg : innerPair.second) { - std::cout << "Time: " << msg.time << ", Value: " << msg.value << "; "; - } - std::cout << std::endl; - } - } -} - TEST_CASE("FastFourrierTransform") { SECTION("FFT computation and output messages") { auto fft = FastFourrierTransform("fft", 3, 1, true, true, true); From 61a9cec474567826bf24a5c4eaab7c1a5e9c633c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 4 Jan 2024 16:53:27 +0000 Subject: [PATCH 08/12] chore(docs): minor changes --- docs/site/BUILD.bazel | 2 +- docs/site/src/pages/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/site/BUILD.bazel b/docs/site/BUILD.bazel index 61ccbfc4..cd6496ac 100644 --- a/docs/site/BUILD.bazel +++ b/docs/site/BUILD.bazel @@ -104,7 +104,7 @@ docusaurus_bin.docusaurus_binary( docusaurus_bin.docusaurus_binary( name = "start", # TODO: compute the output dir - args = ["start", "docs/site"], + args = ["start", "docs/site", "--host 0.0.0.0"], data = glob( ["**/*"], exclude = ["node_modules/**/*"], diff --git a/docs/site/src/pages/index.tsx b/docs/site/src/pages/index.tsx index f6abb644..4b415bf9 100644 --- a/docs/site/src/pages/index.tsx +++ b/docs/site/src/pages/index.tsx @@ -36,7 +36,7 @@ function HomepageHeader() { export default function Home() { const { siteConfig } = useDocusaurusContext(); const programStr = simpleProgram; - const getStream = () => getNoisySinSignal(100, 0.0015, 100, 80, 2); + const getStream = () => getNoisySinSignal(20, 0.0015, 100, 80, 2); return ( From f820d2e395a2a922c9e4ca662292c0ff4157a3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 4 Jan 2024 16:53:40 +0000 Subject: [PATCH 09/12] feat(std): add sort operator --- libs/std/include/rtbot/std/Sort.h | 64 +++++++++++++++++++++++++++ libs/std/test/test_sort.cpp | 73 +++++++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) create mode 100644 libs/std/include/rtbot/std/Sort.h create mode 100644 libs/std/test/test_sort.cpp diff --git a/libs/std/include/rtbot/std/Sort.h b/libs/std/include/rtbot/std/Sort.h new file mode 100644 index 00000000..e6444759 --- /dev/null +++ b/libs/std/include/rtbot/std/Sort.h @@ -0,0 +1,64 @@ +#include + +#include "rtbot/Join.h" + +namespace rtbot { + +template +class Sort : public Join { + private: + size_t numOutputs; + size_t numInputs; + bool ascending; + + public: + Sort(string const& id, size_t numInputs, size_t numOutputs, bool ascending = true, size_t maxInputBufferSize = 100) + : Join(id) { + if (numOutputs > numInputs) throw std::runtime_error("Sort: numOutputs must be less than or equal to numInputs"); + this->numOutputs = numOutputs; + this->numInputs = numInputs; + this->ascending = ascending; + + // Register the inputs + for (size_t i = 0; i < numInputs; i++) { + this->addDataInput("i" + std::to_string(i + 1), maxInputBufferSize); + } + + // Register the outputs + for (size_t i = 0; i < numOutputs; i++) { + this->addOutput("o" + std::to_string(i + 1)); + } + } + + string typeName() const override { return "Sort"; } + + map>> processData() override { + // Get the input data + auto inputs = this->getDataInputs(); + + vector idx(this->numInputs); + iota(idx.begin(), idx.end(), 0); + + // see https://stackoverflow.com/questions/1577475/c-sorting-and-keeping-track-of-indexes + stable_sort(idx.begin(), idx.end(), [this](size_t i1, size_t i2) { + auto v1 = this->dataInputs.find("i" + std::to_string(i1 + 1))->second.front().value; + auto v2 = this->dataInputs.find("i" + std::to_string(i2 + 1))->second.front().value; + return this->ascending ? v1 < v2 : v1 > v2; + }); + + map>> outputMsgs; + // take only the first numOutputs elements + for (size_t i = 0; i < this->numOutputs; i++) { + auto inputPort = "i" + std::to_string(idx[i] + 1); + auto outputPort = "o" + std::to_string(i + 1); + Message out = this->dataInputs.find(inputPort)->second.front(); + vector> v; + v.push_back(out); + outputMsgs.emplace(outputPort, v); + } + + return outputMsgs; + } +}; + +} // namespace rtbot \ No newline at end of file diff --git a/libs/std/test/test_sort.cpp b/libs/std/test/test_sort.cpp new file mode 100644 index 00000000..84a5db99 --- /dev/null +++ b/libs/std/test/test_sort.cpp @@ -0,0 +1,73 @@ +#include + +#include "rtbot/std/Sort.h" + +using namespace rtbot; +using namespace std; + +TEST_CASE("Sort") { + SECTION("Sort ascending") { + // Create a Sort operator with 3 input and 1 output + Sort sort("sort", 3, 1); + + // Create some unsorted input data + vector> inputData1 = {{1, 5}, {2, 3}, {3, 11}, {4, 6}, {5, 12}}; + vector> inputData2 = {{1, 2}, {2, 4}, {3, 12}, {4, 8}, {5, 2}}; + vector> inputData3 = {{1, 3}, {2, 5}, {3, 13}, {4, 4}, {5, 32}}; + vector> expectedOutput1 = {{1, 2}, {2, 3}, {3, 11}, {4, 4}, {5, 2}}; + + // Feed the input data to the Sort operator + for (size_t i = 0; i < inputData1.size(); i++) { + sort.receiveData(inputData1[i], "i1"); + sort.receiveData(inputData2[i], "i2"); + sort.receiveData(inputData3[i], "i3"); + auto outputMsgs = sort.executeData(); + // Check that the output is sorted + REQUIRE(outputMsgs.find("sort")->second.find("o1")->second.at(0) == expectedOutput1[i]); + } + } + + SECTION("Sort descending") { + // Create a Sort operator with 3 input and 1 output + Sort sort("sort", 3, 1, false); + + // Create some unsorted input data + vector> inputData1 = {{1, 5}, {2, 3}, {3, 11}, {4, 6}, {5, 12}}; + vector> inputData2 = {{1, 2}, {2, 4}, {3, 12}, {4, 8}, {5, 2}}; + vector> inputData3 = {{1, 3}, {2, 5}, {3, 13}, {4, 4}, {5, 32}}; + vector> expectedOutput1 = {{1, 5}, {2, 5}, {3, 13}, {4, 8}, {5, 32}}; + + // Feed the input data to the Sort operator + for (size_t i = 0; i < inputData1.size(); i++) { + sort.receiveData(inputData1[i], "i1"); + sort.receiveData(inputData2[i], "i2"); + sort.receiveData(inputData3[i], "i3"); + auto outputMsgs = sort.executeData(); + // Check that the output is sorted + REQUIRE(outputMsgs.find("sort")->second.find("o1")->second.at(0) == expectedOutput1[i]); + } + } + + SECTION("Sort with more than 2 outputs") { + // Create a Sort operator with 3 input and 2 output + Sort sort("sort", 3, 2); + + // Create some unsorted input data + vector> inputData1 = {{1, 5}, {2, 3}, {3, 11}, {4, 6}, {5, 12}}; + vector> inputData2 = {{1, 2}, {2, 4}, {3, 12}, {4, 8}, {5, 2}}; + vector> inputData3 = {{1, 3}, {2, 5}, {3, 13}, {4, 4}, {5, 32}}; + vector> expectedOutput1 = {{1, 2}, {2, 3}, {3, 11}, {4, 4}, {5, 2}}; + vector> expectedOutput2 = {{1, 3}, {2, 4}, {3, 12}, {4, 6}, {5, 12}}; + + // Feed the input data to the Sort operator + for (size_t i = 0; i < inputData1.size(); i++) { + sort.receiveData(inputData1[i], "i1"); + sort.receiveData(inputData2[i], "i2"); + sort.receiveData(inputData3[i], "i3"); + auto outputMsgs = sort.executeData(); + // Check that the output is sorted + REQUIRE(outputMsgs.find("sort")->second.find("o1")->second.at(0) == expectedOutput1[i]); + REQUIRE(outputMsgs.find("sort")->second.find("o2")->second.at(0) == expectedOutput2[i]); + } + } +} \ No newline at end of file From 0a9d630c00f7966e2570b9dc1ad49eb81f0240d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 4 Jan 2024 16:53:48 +0000 Subject: [PATCH 10/12] feat(std): add sort index operator --- libs/std/include/rtbot/std/SortIndex.h | 67 +++++++++++++++++++++++ libs/std/test/test_sort_index.cpp | 73 ++++++++++++++++++++++++++ 2 files changed, 140 insertions(+) create mode 100644 libs/std/include/rtbot/std/SortIndex.h create mode 100644 libs/std/test/test_sort_index.cpp diff --git a/libs/std/include/rtbot/std/SortIndex.h b/libs/std/include/rtbot/std/SortIndex.h new file mode 100644 index 00000000..99c07ab0 --- /dev/null +++ b/libs/std/include/rtbot/std/SortIndex.h @@ -0,0 +1,67 @@ +#include + +#include "rtbot/Join.h" + +namespace rtbot { + +template +class SortIndex : public Join { + private: + size_t numOutputs; + size_t numInputs; + bool ascending; + + public: + SortIndex(string const& id, size_t numInputs, size_t numOutputs, bool ascending = true, + size_t maxInputBufferSize = 100) + : Join(id) { + if (numOutputs > numInputs) + throw std::runtime_error("SortIndex: numOutputs must be less than or equal to numInputs"); + this->numOutputs = numOutputs; + this->numInputs = numInputs; + this->ascending = ascending; + + // Register the inputs + for (size_t i = 0; i < numInputs; i++) { + this->addDataInput("i" + std::to_string(i + 1), maxInputBufferSize); + } + + // Register the outputs + for (size_t i = 0; i < numOutputs; i++) { + this->addOutput("o" + std::to_string(i + 1)); + } + } + + string typeName() const override { return "SortIndex"; } + + map>> processData() override { + // Get the input data + auto inputs = this->getDataInputs(); + + vector idx(this->numInputs); + iota(idx.begin(), idx.end(), 0); + + // see https://stackoverflow.com/questions/1577475/c-sorting-and-keeping-track-of-indexes + stable_sort(idx.begin(), idx.end(), [this](size_t i1, size_t i2) { + auto v1 = this->dataInputs.find("i" + std::to_string(i1 + 1))->second.front().value; + auto v2 = this->dataInputs.find("i" + std::to_string(i2 + 1))->second.front().value; + return this->ascending ? v1 < v2 : v1 > v2; + }); + + map>> outputMsgs; + // get the time from the first input + T time = this->dataInputs.find("i1")->second.front().time; + // take only the first numOutputs elements + for (size_t i = 0; i < this->numOutputs; i++) { + auto outputPort = "o" + std::to_string(i + 1); + auto out = Message(time, idx[i] + 1); + vector> v; + v.push_back(out); + outputMsgs.emplace(outputPort, v); + } + + return outputMsgs; + } +}; + +} // namespace rtbot \ No newline at end of file diff --git a/libs/std/test/test_sort_index.cpp b/libs/std/test/test_sort_index.cpp new file mode 100644 index 00000000..9a7110fb --- /dev/null +++ b/libs/std/test/test_sort_index.cpp @@ -0,0 +1,73 @@ +#include + +#include "rtbot/std/SortIndex.h" + +using namespace rtbot; +using namespace std; + +TEST_CASE("SortIndex") { + SECTION("SortIndex ascending") { + // Create a SortIndex operator with 3 input and 1 output + SortIndex sort("sort", 3, 1); + + // Create some unsorted input data + vector> inputData1 = {{1, 5}, {2, 3}, {3, 11}, {4, 6}, {5, 12}}; + vector> inputData2 = {{1, 2}, {2, 4}, {3, 12}, {4, 8}, {5, 2}}; + vector> inputData3 = {{1, 3}, {2, 5}, {3, 13}, {4, 4}, {5, 32}}; + vector> expectedOutput1 = {{1, 2}, {2, 1}, {3, 1}, {4, 3}, {5, 2}}; + + // Feed the input data to the SortIndex operator + for (size_t i = 0; i < inputData1.size(); i++) { + sort.receiveData(inputData1[i], "i1"); + sort.receiveData(inputData2[i], "i2"); + sort.receiveData(inputData3[i], "i3"); + auto outputMsgs = sort.executeData(); + // Check that the output is sorted + REQUIRE(outputMsgs.find("sort")->second.find("o1")->second.at(0) == expectedOutput1[i]); + } + } + + SECTION("SortIndex descending") { + // Create a SortIndex operator with 3 input and 1 output + SortIndex sort("sort", 3, 1, false); + + // Create some unsorted input data + vector> inputData1 = {{1, 5}, {2, 3}, {3, 11}, {4, 6}, {5, 12}}; + vector> inputData2 = {{1, 2}, {2, 4}, {3, 12}, {4, 8}, {5, 2}}; + vector> inputData3 = {{1, 3}, {2, 5}, {3, 13}, {4, 4}, {5, 32}}; + vector> expectedOutput1 = {{1, 1}, {2, 3}, {3, 3}, {4, 2}, {5, 3}}; + + // Feed the input data to the SortIndex operator + for (size_t i = 0; i < inputData1.size(); i++) { + sort.receiveData(inputData1[i], "i1"); + sort.receiveData(inputData2[i], "i2"); + sort.receiveData(inputData3[i], "i3"); + auto outputMsgs = sort.executeData(); + // Check that the output is sorted + REQUIRE(outputMsgs.find("sort")->second.find("o1")->second.at(0) == expectedOutput1[i]); + } + } + + SECTION("SortIndex with more than 2 outputs") { + // Create a SortIndex operator with 3 input and 2 output + SortIndex sort("sort", 3, 2); + + // Create some unsorted input data + vector> inputData1 = {{1, 5}, {2, 3}, {3, 11}, {4, 6}, {5, 12}}; + vector> inputData2 = {{1, 2}, {2, 4}, {3, 12}, {4, 8}, {5, 2}}; + vector> inputData3 = {{1, 3}, {2, 5}, {3, 13}, {4, 4}, {5, 32}}; + vector> expectedOutput1 = {{1, 2}, {2, 1}, {3, 1}, {4, 3}, {5, 2}}; + vector> expectedOutput2 = {{1, 3}, {2, 2}, {3, 2}, {4, 1}, {5, 1}}; + + // Feed the input data to the SortIndex operator + for (size_t i = 0; i < inputData1.size(); i++) { + sort.receiveData(inputData1[i], "i1"); + sort.receiveData(inputData2[i], "i2"); + sort.receiveData(inputData3[i], "i3"); + auto outputMsgs = sort.executeData(); + // Check that the output is sorted + REQUIRE(outputMsgs.find("sort")->second.find("o1")->second.at(0) == expectedOutput1[i]); + REQUIRE(outputMsgs.find("sort")->second.find("o2")->second.at(0) == expectedOutput2[i]); + } + } +} \ No newline at end of file From ad97c2ed35dcdba76016cd165990dac1a7f4b3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Thu, 4 Jan 2024 21:58:55 +0000 Subject: [PATCH 11/12] docs(std): add sort operator documentation --- libs/std/include/rtbot/std/Sort.md | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 libs/std/include/rtbot/std/Sort.md diff --git a/libs/std/include/rtbot/std/Sort.md b/libs/std/include/rtbot/std/Sort.md new file mode 100644 index 00000000..7045b3ad --- /dev/null +++ b/libs/std/include/rtbot/std/Sort.md @@ -0,0 +1,54 @@ +--- +behavior: + buffered: true + throughput: variable +view: + shape: circle + latex: + template: | + sort +jsonschema: + type: object + properties: + id: + type: string + description: The id of the operator + numInputs: + type: integer + description: | + The number of input ports the operator will have. If equal to $n$, + the operator will have `i1`, `i2`, ..., `iN` as input ports. + minimum: 2 + examples: [3, 10, 33] + numOutputs: + type: integer + description: | + The number of output ports the operator will have. If equal to $m$, + the operator will have `o1`, `o2`, ..., `oM` as output ports. The + number of output ports should be smaller than the number of input + ports, otherwise a runtime exception will be thrown. + minimum: 1 + examples: [1] + ascending: + type: boolean + description: | + If `true`, which is the default value, then for all `K` the value that goes out by the port `oK-1` will be smaller than the one going + out through the port `oK`. In other words the output values will be ascending as the port index `K` increases. + default: true + examples: [true] + maxInputBufferSize: + type: integer + description: | + The maximum length of the internal input buffers that will hold the incoming data until synchronization occurs. + default: 100 + minimum: 1 + examples: [100, 200, 1000] + required: ["id", "numInputs", "numOutputs"] +--- + +# Sort + +Takes $n$ input streams through the input ports `i1`, `i2`, ..., `iN`, and emits the same values, +whenever a synchronization happens, through the ports `o1`, `o2`, ..., `oM` ($M < N$), _after ordering_ them according to the `value` in the messages for _every_ synchronization time. In other words for a given $t$ through the port `o1` will go the smallest messages of all input messages, through `o2` the second smallest and so on. Using the `ascending` parameter it is possible to invert the order. + +The synchronization mechanism is inherited from the [`Join`](/docs/operators/core/Join) operator. From d9883eafbbd8d4ee0f30b029fcbc02186b460476 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Carcass=C3=A9s=20Quevedo?= Date: Tue, 6 Feb 2024 16:24:49 +0000 Subject: [PATCH 12/12] feat(api): enable recently added operators --- libs/api/BUILD.bazel | 4 +- libs/api/src/FactoryOp.cpp | 49 +++++++++++++++- libs/core/BUILD.bazel | 2 +- libs/core/include/rtbot/Join.md | 19 ++++--- ...rierTransform.h => FastFourierTransform.h} | 24 +++++--- .../include/rtbot/std/FastFourierTransform.md | 56 +++++++++++++++++++ libs/std/include/rtbot/std/Sort.h | 8 +++ libs/std/include/rtbot/std/Sort.md | 25 ++++----- libs/std/include/rtbot/std/SortIndex.h | 8 +++ libs/std/include/rtbot/std/SortIndex.md | 54 ++++++++++++++++++ ...rm.cpp => test_fast_fourier_transform.cpp} | 2 +- 11 files changed, 215 insertions(+), 36 deletions(-) rename libs/std/include/rtbot/std/{FastFourrierTransform.h => FastFourierTransform.h} (87%) create mode 100644 libs/std/include/rtbot/std/FastFourierTransform.md create mode 100644 libs/std/include/rtbot/std/SortIndex.md rename libs/std/test/{test_fast_fourrier_transform.cpp => test_fast_fourier_transform.cpp} (96%) diff --git a/libs/api/BUILD.bazel b/libs/api/BUILD.bazel index a436b882..422f967e 100644 --- a/libs/api/BUILD.bazel +++ b/libs/api/BUILD.bazel @@ -26,7 +26,7 @@ cc_library( ]), hdrs = glob(["include/**/*.h"]) + [":jsonschema_to_src"], copts = [ - "-O3", + # "-O3", "-Iexternal/json-schema-validator/src", "-Ilibs/core/include/rtbot", # this is needed when building from another workspace @@ -48,7 +48,7 @@ cc_library( ) BASE_LINKOPTS = [ - "-O3", + # "-O3", "-lembind", # Enable embind "-s MODULARIZE", "--embind-emit-tsd", diff --git a/libs/api/src/FactoryOp.cpp b/libs/api/src/FactoryOp.cpp index 1695174e..71b2fe63 100644 --- a/libs/api/src/FactoryOp.cpp +++ b/libs/api/src/FactoryOp.cpp @@ -9,6 +9,7 @@ #include "rtbot/Join.h" #include "rtbot/Operator.h" #include "rtbot/Output.h" +#include "rtbot/Pipeline.h" #include "rtbot/finance/RelativeStrengthIndex.h" #include "rtbot/std/Add.h" #include "rtbot/std/And.h" @@ -20,6 +21,7 @@ #include "rtbot/std/Difference.h" #include "rtbot/std/Division.h" #include "rtbot/std/EqualTo.h" +#include "rtbot/std/FastFourierTransform.h" #include "rtbot/std/FiniteImpulseResponse.h" #include "rtbot/std/GreaterThan.h" #include "rtbot/std/HermiteResampler.h" @@ -34,10 +36,11 @@ #include "rtbot/std/Plus.h" #include "rtbot/std/Power.h" #include "rtbot/std/Scale.h" +#include "rtbot/std/Sort.h" +#include "rtbot/std/SortIndex.h" #include "rtbot/std/StandardDeviation.h" #include "rtbot/std/TimeShift.h" #include "rtbot/std/Variable.h" -#include "rtbot/Pipeline.h" using json = nlohmann::json; @@ -107,6 +110,22 @@ void from_json(const json& j, FiniteImpulseResponse& p) { p = FiniteImpulseResponse(j["id"].get(), j["coeff"].get>()); } +template +void to_json(json& j, const FastFourrierTransform& p) { + j = json{{"type", p.typeName()}, + {"id", p.id}, + {"N", p.getN()}, + {"emitPower", p.getEmitPower()}, + {"emitRePart", p.getEmitRePart()}, + {"emitImPart", p.getEmitImPart()}}; +} + +template +void from_json(const json& j, FastFourrierTransform& p) { + p = FastFourrierTransform(j["id"].get(), j["N"].get(), j["emitPower"].get(), + j["emitRePart"].get(), j["emitImPart"].get()); +} + template void to_json(json& j, const Join& p) { j = json{{"type", p.typeName()}, {"id", p.id}, {"numPorts", p.getNumDataInputs()}}; @@ -317,6 +336,32 @@ void from_json(const json& j, Scale& p) { p = Scale(j["id"].get(), j["value"].get()); } +template +void to_json(json& j, const Sort& p) { + j = json{{"type", p.typeName()}, {"id", p.id}, + {"numInputs", p.getNumInputs()}, {"numOutputs", p.getNumOutputs()}, + {"ascending", p.getAscending()}, {"maxInputBufferSize", p.getMaxInputBufferSize()}}; +} + +template +void from_json(const json& j, Sort& p) { + p = Sort(j["id"].get(), j["numInputs"].get(), j["numOutputs"].get(), + j["ascending"].get(), j["maxInputBufferSize"].get()); +} + +template +void to_json(json& j, const SortIndex& p) { + j = json{{"type", p.typeName()}, {"id", p.id}, + {"numInputs", p.getNumInputs()}, {"numOutputs", p.getNumOutputs()}, + {"ascending", p.getAscending()}, {"maxInputBufferSize", p.getMaxInputBufferSize()}}; +} + +template +void from_json(const json& j, SortIndex& p) { + p = SortIndex(j["id"].get(), j["numInputs"].get(), j["numOutputs"].get(), + j["ascending"].get(), j["maxInputBufferSize"].get()); +} + template void to_json(json& j, const Power& p) { j = json{{"type", p.typeName()}, {"id", p.id}, {"value", p.getValue()}}; @@ -416,6 +461,8 @@ FactoryOp::FactoryOp() { op_registry_add, json>(); op_registry_add, json>(); op_registry_add, json>(); + op_registry_add, json>(); + op_registry_add, json>(); op_registry_add, json>(); op_registry_add, json>(); op_registry_add, json>(); diff --git a/libs/core/BUILD.bazel b/libs/core/BUILD.bazel index 17081c93..a0a5a417 100644 --- a/libs/core/BUILD.bazel +++ b/libs/core/BUILD.bazel @@ -25,7 +25,7 @@ cc_library( "src/**/*.cpp", ]), copts = [ - "-O3", + # "-O3", ], hdrs = glob(["include/**/*.h"]), includes = ["include"], diff --git a/libs/core/include/rtbot/Join.md b/libs/core/include/rtbot/Join.md index ecc3d3c4..3596d653 100644 --- a/libs/core/include/rtbot/Join.md +++ b/libs/core/include/rtbot/Join.md @@ -27,14 +27,15 @@ Inputs: `i1`...`iN` where N defined by `numPorts` Outputs: `o1`...`oN` where N defined by `numPorts` The `Join` operator is used to synchronize two or more incoming message streams into -a single, consistent output. +a single, consistent output. `Join` waits until it can emit a consistent output: whenever +there exist messages in all buffers with common time. In such scenario we say a synchronization +has occur and synchronized messages are emitted. -The `Join` operator holds a message buffer on `i1`, `i2`, ... , `iN` respectively, -it uses the message time field to synchronize the streams and pick 1 message per input port. -If at least one of the message buffer is empty or a message with the expected time can not -be found on one of the buffers then the synchronization will not occur. When synchronization -occurs messages with older timestamps than the synchronization time are discarded since it is -understood that they can not be synchronized in the future. The `Join` operator emits the -synchronized messages through `o1`, `o2`, ... , `oN` respectively after the synchronization takes place. +Internally the `Join` operator holds a message buffer on `i1`, `i2`, ... , `iN` respectively, +it uses the message time field to synchronize the streams and pick 1 message per input port. +If at least one of the message buffer is empty, or if for each one of the buffer there isn't +a message with the same time as the incoming message, synchronization will not occur. When synchronization +occurs messages with older timestamps than the synchronization time are discarded since it is +understood that they can not be synchronized in the future. The `Join` operator emits the +synchronized messages through `o1`, `o2`, ... , `oN` respectively after the synchronization takes place. If no synchronization occurs then an empty message {} is emitted through `o1`, `o2`, ... , `oN` respectively. - diff --git a/libs/std/include/rtbot/std/FastFourrierTransform.h b/libs/std/include/rtbot/std/FastFourierTransform.h similarity index 87% rename from libs/std/include/rtbot/std/FastFourrierTransform.h rename to libs/std/include/rtbot/std/FastFourierTransform.h index 3f557368..18d8f4fd 100644 --- a/libs/std/include/rtbot/std/FastFourrierTransform.h +++ b/libs/std/include/rtbot/std/FastFourierTransform.h @@ -11,17 +11,18 @@ class FastFourrierTransform : public Operator { FastFourrierTransform(string const& id, size_t N = 7, size_t skip = 127, bool emitPower = true, bool emitRePart = false, bool emitImPart = false) : Operator(id) { - this->n = pow(2, N); + this->N = N; + this->M = pow(2, N); this->emitPower = emitPower; this->emitRePart = emitRePart; this->emitImPart = emitImPart; // recall that the N passed in the constructor is used to compute the size of the input buffer // the actual size of the FFT is 2^N - this->addDataInput("i1", this->n); + this->addDataInput("i1", this->M); // allocate the vector that will contain the FFT - this->a = vector>(this->n); + this->a = vector>(this->M); // declare the output ports based on the parameters passed in the constructor - for (int i = 0; i < this->n; i++) { + for (int i = 0; i < this->M; i++) { this->addOutput("w" + to_string(i + 1)); if (emitPower) this->addOutput("p" + to_string(i + 1)); if (emitRePart) this->addOutput("re" + to_string(i + 1)); @@ -47,7 +48,7 @@ class FastFourrierTransform : public Operator { map>> outputMsgs; auto input = this->dataInputs.find(inputPort)->second; - for (int i = 0; i < this->n; i++) { + for (int i = 0; i < this->M; i++) { this->a[i].real((input.at(i).value)); this->a[i].imag(0); } @@ -56,7 +57,7 @@ class FastFourrierTransform : public Operator { fft(this->a); auto time = this->getDataInputLastMessage(inputPort).time; - for (size_t i = 0; i < this->n; i++) { + for (size_t i = 0; i < this->M; i++) { if (this->emitRePart) { Message re(time, this->a[i].real()); vector> toEmit = {re}; @@ -73,7 +74,7 @@ class FastFourrierTransform : public Operator { outputMsgs.emplace("p" + to_string(i + 1), toEmit); } - Message w(time, (i + 1.0) / this->n); + Message w(time, (i + 1.0) / this->M); vector> toEmit = {w}; outputMsgs.emplace("w" + to_string(i + 1), toEmit); } @@ -81,12 +82,17 @@ class FastFourrierTransform : public Operator { return outputMsgs; } - size_t getSize() { return this->n; } + size_t getSize() { return this->M; } + size_t getN() { return this->N; } + bool getEmitPower() { return this->emitPower; } + bool getEmitRePart() { return this->emitRePart; } + bool getEmitImPart() { return this->emitImPart; } private: size_t skipCounter; size_t skip; - size_t n; + size_t M; + size_t N; bool emitPower; bool emitRePart; bool emitImPart; diff --git a/libs/std/include/rtbot/std/FastFourierTransform.md b/libs/std/include/rtbot/std/FastFourierTransform.md new file mode 100644 index 00000000..a9688d6d --- /dev/null +++ b/libs/std/include/rtbot/std/FastFourierTransform.md @@ -0,0 +1,56 @@ +--- +behavior: + buffered: true + throughput: variable +view: + shape: circle + latex: + template: | + fft +jsonschema: + type: object + properties: + id: + type: string + description: The id of the operator + N: + type: integer + default: 7 + minimum: 2 + description: Use to compute the fft matrix size, which will be $2^N$ + examples: [3, 4, 5] + skip: + type: integer + default: 127 + minimum: 0 + description: Controls how many messages to skip before the next computation of the fft. This is useful for applications were the fft is not needed to be computed for each received message. Notice that a value different than 0 changes the throughput of the signal. + examples: [127, 255, 100] + emitPower: + type: boolean + description: Indicates whether the power correspondent to the different frequencies should be computed and emitted through the output ports `p1`, ..., `pM`, where $M=2^N$ + default: true + examples: [true, false] + emitRePart: + type: boolean + description: Indicates whether the real part of the amplitude correspondent to the different frequencies should be computed and emitted through the output ports `re1`, ..., `reM`, where $M=2^N$ + default: true + examples: [true, false] + emitImPart: + type: boolean + description: Indicates whether the imaginary part of the amplitude correspondent to the different frequencies should be computed and emitted through the output ports `im1`, ..., `imM`, where $M=2^N$ + default: true + examples: [true, false] + required: ["id"] +--- + +# FastFourierTransform + +Inputs: `i1` +Outputs: + +- frequencies `w1`...`wM` +- power `p1`...`pM` if `emitPower` is true +- real part `re1`...`reM` if `emitRePart` is true +- imaginary part `im1`...`imM` if `emitImPart` is true + +where `M` is equal to $2^n$. diff --git a/libs/std/include/rtbot/std/Sort.h b/libs/std/include/rtbot/std/Sort.h index e6444759..4fdba2a1 100644 --- a/libs/std/include/rtbot/std/Sort.h +++ b/libs/std/include/rtbot/std/Sort.h @@ -9,15 +9,18 @@ class Sort : public Join { private: size_t numOutputs; size_t numInputs; + size_t maxInputBufferSize; bool ascending; public: + Sort() = default; Sort(string const& id, size_t numInputs, size_t numOutputs, bool ascending = true, size_t maxInputBufferSize = 100) : Join(id) { if (numOutputs > numInputs) throw std::runtime_error("Sort: numOutputs must be less than or equal to numInputs"); this->numOutputs = numOutputs; this->numInputs = numInputs; this->ascending = ascending; + this->maxInputBufferSize = maxInputBufferSize; // Register the inputs for (size_t i = 0; i < numInputs; i++) { @@ -59,6 +62,11 @@ class Sort : public Join { return outputMsgs; } + + size_t getNumInputs() const { return this->numInputs; } + size_t getNumOutputs() const { return this->numOutputs; } + bool getAscending() const { return this->ascending; } + size_t getMaxInputBufferSize() const { return this->maxInputBufferSize; } }; } // namespace rtbot \ No newline at end of file diff --git a/libs/std/include/rtbot/std/Sort.md b/libs/std/include/rtbot/std/Sort.md index 7045b3ad..d03e7919 100644 --- a/libs/std/include/rtbot/std/Sort.md +++ b/libs/std/include/rtbot/std/Sort.md @@ -15,31 +15,27 @@ jsonschema: description: The id of the operator numInputs: type: integer - description: | - The number of input ports the operator will have. If equal to $n$, - the operator will have `i1`, `i2`, ..., `iN` as input ports. + description: "The number of input ports the operator will have. If equal to `N`, + the operator will have `i1`, `i2`, ..., `iN` as input ports." minimum: 2 examples: [3, 10, 33] numOutputs: type: integer - description: | - The number of output ports the operator will have. If equal to $m$, - the operator will have `o1`, `o2`, ..., `oM` as output ports. The - number of output ports should be smaller than the number of input - ports, otherwise a runtime exception will be thrown. + description: "The number of output ports the operator will have. If equal to `N`, + the operator will have `o1`, `o2`, ..., `oM` as output ports. The + number of output ports should be smaller than the number of input + ports, otherwise a runtime exception will be thrown." minimum: 1 examples: [1] ascending: type: boolean - description: | - If `true`, which is the default value, then for all `K` the value that goes out by the port `oK-1` will be smaller than the one going - out through the port `oK`. In other words the output values will be ascending as the port index `K` increases. + description: If `true`, which is the default value, then for all `K` the value that goes out by the port `oK-1` will be smaller than the one going out through the port `oK`. In other words the output values will be ascending as the port index `K` increases. default: true examples: [true] maxInputBufferSize: type: integer - description: | - The maximum length of the internal input buffers that will hold the incoming data until synchronization occurs. + description: "The maximum length of the internal input buffers that will hold the incoming data + until synchronization occurs." default: 100 minimum: 1 examples: [100, 200, 1000] @@ -48,6 +44,9 @@ jsonschema: # Sort +Inputs: `i1`...`iN` where N defined by `numInputs` +Outputs: `o1`...`oM` where M defined by `numOutputs` and $M < N$ + Takes $n$ input streams through the input ports `i1`, `i2`, ..., `iN`, and emits the same values, whenever a synchronization happens, through the ports `o1`, `o2`, ..., `oM` ($M < N$), _after ordering_ them according to the `value` in the messages for _every_ synchronization time. In other words for a given $t$ through the port `o1` will go the smallest messages of all input messages, through `o2` the second smallest and so on. Using the `ascending` parameter it is possible to invert the order. diff --git a/libs/std/include/rtbot/std/SortIndex.h b/libs/std/include/rtbot/std/SortIndex.h index 99c07ab0..38613f07 100644 --- a/libs/std/include/rtbot/std/SortIndex.h +++ b/libs/std/include/rtbot/std/SortIndex.h @@ -9,9 +9,11 @@ class SortIndex : public Join { private: size_t numOutputs; size_t numInputs; + size_t maxInputBufferSize; bool ascending; public: + SortIndex() = default; SortIndex(string const& id, size_t numInputs, size_t numOutputs, bool ascending = true, size_t maxInputBufferSize = 100) : Join(id) { @@ -20,6 +22,7 @@ class SortIndex : public Join { this->numOutputs = numOutputs; this->numInputs = numInputs; this->ascending = ascending; + this->maxInputBufferSize = maxInputBufferSize; // Register the inputs for (size_t i = 0; i < numInputs; i++) { @@ -62,6 +65,11 @@ class SortIndex : public Join { return outputMsgs; } + + size_t getNumInputs() const { return this->numInputs; } + size_t getNumOutputs() const { return this->numOutputs; } + bool getAscending() const { return this->ascending; } + size_t getMaxInputBufferSize() const { return this->maxInputBufferSize; } }; } // namespace rtbot \ No newline at end of file diff --git a/libs/std/include/rtbot/std/SortIndex.md b/libs/std/include/rtbot/std/SortIndex.md new file mode 100644 index 00000000..43233909 --- /dev/null +++ b/libs/std/include/rtbot/std/SortIndex.md @@ -0,0 +1,54 @@ +--- +behavior: + buffered: true + throughput: variable +view: + shape: circle + latex: + template: | + sort +jsonschema: + type: object + properties: + id: + type: string + description: The id of the operator + numInputs: + type: integer + description: "The number of input ports the operator will have. If equal to `N`, + the operator will have `i1`, `i2`, ..., `iN` as input ports." + minimum: 2 + examples: [3, 10, 33] + numOutputs: + type: integer + description: "The number of output ports the operator will have. If equal to `N`, + the operator will have `o1`, `o2`, ..., `oM` as output ports. The + number of output ports should be smaller than the number of input + ports, otherwise a runtime exception will be thrown." + minimum: 1 + examples: [1] + ascending: + type: boolean + description: Whether the indices are sort ascending according to the value in the input messages or not. + default: true + examples: [true] + maxInputBufferSize: + type: integer + description: "The maximum length of the internal input buffers that will hold the incoming data + until synchronization occurs." + default: 100 + minimum: 1 + examples: [100, 200, 1000] + required: ["id", "numInputs", "numOutputs"] +--- + +# SortIndex + +Inputs: `i1`...`iN` where N defined by `numInputs` +Outputs: `o1`...`oM` where M defined by `numOutputs` and $M < N$ + +Takes $n$ input streams through the input ports `i1`, `i2`, ..., `iN`, and emits the _indices_ (1, 2, ..., M) of the input messages, whenever a synchronization happens, through the ports `o1`, `o2`, ..., `oM` ($M < N$), _after ordering_ them according to the `value` in the messages for _every_ synchronization time. In other words for a given $t$ through the port `o1` will go the _index_ of smallest messages of all input messages, through `o2` the _index_ of the second smallest and so on. Using the `ascending` parameter it is possible to invert the order. For example, if the smallest message `value` is in the input port `i3`, then the output port `o1` will emit the value 3. + +Here index refers to the numeric suffix associated to the port. Port `i1` has index 1, port `i2` index 2 and so on. + +The synchronization mechanism is inherited from the [`Join`](/docs/operators/core/Join) operator. diff --git a/libs/std/test/test_fast_fourrier_transform.cpp b/libs/std/test/test_fast_fourier_transform.cpp similarity index 96% rename from libs/std/test/test_fast_fourrier_transform.cpp rename to libs/std/test/test_fast_fourier_transform.cpp index 0f1ba90c..ac2d1b1c 100644 --- a/libs/std/test/test_fast_fourrier_transform.cpp +++ b/libs/std/test/test_fast_fourier_transform.cpp @@ -1,6 +1,6 @@ #include -#include "rtbot/std/FastFourrierTransform.h" +#include "rtbot/std/FastFourierTransform.h" using namespace rtbot; using namespace std;