diff --git a/include/aidge/data/Spikegen.hpp b/include/aidge/data/Spikegen.hpp new file mode 100644 index 0000000000000000000000000000000000000000..30fd5decba675d168d7872f03221bfa6664f7f71 --- /dev/null +++ b/include/aidge/data/Spikegen.hpp @@ -0,0 +1,31 @@ +/******************************************************************************** + * Copyright (c) 2025 CEA-List + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + +#ifndef AIDGE_CORE_DATA_SPIKEGEN_H_ +#define AIDGE_CORE_DATA_SPIKEGEN_H_ + +#include <cstdint> + +#include "aidge/data/Tensor.hpp" + +namespace Aidge { + +/* + * @brief Spike rate encoding of input data + */ +Tensor spikegenRate(std::shared_ptr<Tensor> tensor, std::uint32_t numSteps); + + +Tensor spikegenLatency(std::shared_ptr<Tensor> tensor); +} + + +#endif diff --git a/include/aidge/data/Tensor.hpp b/include/aidge/data/Tensor.hpp index 5df59becdc41f12768935544a42aac24ffb3a333..76295270e4081d45cf0289f2e9feba5e3e4855fb 100644 --- a/include/aidge/data/Tensor.hpp +++ b/include/aidge/data/Tensor.hpp @@ -13,9 +13,7 @@ #define AIDGE_CORE_DATA_TENSOR_H_ #include <algorithm> -#include <cstddef> // std::size_t -#include <cstring> -#include <functional> // std::multiplies +#include <cstddef> // std::size_t #include <cstring> #include <functional> // std::multiplies #include <set> #include <memory> #include <numeric> // std::accumulate @@ -989,6 +987,18 @@ public: return ref(fallback, targetReqs.dataType(), device.first, device.second); } + + /** + * @brief Repeat the tensor along a new first dimension. + * For example, if the current tensor has dimensions (n, m), + * calling repeat(10) returns a tensor of shape (10, n, m) + * with 10 copies of the original data. + * + * @param times number of repetitions (must be positive) + * @return Tensor new tensor containing the repeated data. + */ + Tensor repeat(int times) const; + private: /** * @brief Compute the number of elements in the Tensor. diff --git a/python_binding/data/pybind_Spikegen.cpp b/python_binding/data/pybind_Spikegen.cpp new file mode 100644 index 0000000000000000000000000000000000000000..de4636bb29719741e43d14346fa57465caddd63a --- /dev/null +++ b/python_binding/data/pybind_Spikegen.cpp @@ -0,0 +1,26 @@ +/******************************************************************************** + * Copyright (c) 2025 CEA-List + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + +#include <pybind11/pybind11.h> + +#include "aidge/data/Spikegen.hpp" +#include "aidge/data/Tensor.hpp" + +namespace py = pybind11; +namespace Aidge { + +void init_Spikegen(py::module &m) { + m.def("spikegen_rate", &spikegenRate, + py::arg("tensor"), py::arg("numSteps"), + "Performs spike rate encoding on a Tensor"); +} + +} // namespace Aidge diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp index ef1111b39a2f6fff3153dfb7441543ff5c3956c2..e9e246ad1d663901ff8c02ea2d8f744fdbf90e3b 100644 --- a/python_binding/pybind_core.cpp +++ b/python_binding/pybind_core.cpp @@ -32,8 +32,10 @@ void init_OperatorImpl(py::module&); void init_Log(py::module&); void init_Operator(py::module&); void init_OperatorTensor(py::module&); +void init_Spikegen(py::module&); void init_StaticAnalysis(py::module&); + void init_Abs(py::module&); void init_Add(py::module&); void init_And(py::module&); @@ -122,6 +124,7 @@ void init_Aidge(py::module& m) { init_Tensor(m); init_TensorImpl(m); init_Attributes(m); + init_Spikegen(m); init_Node(m); init_GraphView(m); diff --git a/src/data/SpikeGen.cpp b/src/data/SpikeGen.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eecd6c86ebcdc7d3ce52ebf6513305364a7737b2 --- /dev/null +++ b/src/data/SpikeGen.cpp @@ -0,0 +1,41 @@ +/******************************************************************************** + * Copyright (c) 2025 CEA-List + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + +#include <memory> +#include <random> + +#include "aidge/data/Spikegen.hpp" + +namespace Aidge { +static Tensor rateConvert(const Tensor& tensor) { + + auto result = tensor.clone(); + + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_real_distribution<float> dis(0.0f, 1.0f); + + // Clip values between 0 and 1, equivalent to torch.clamp(min=0, max=1) + for (size_t i = 0; i < tensor.size(); i++) { + auto val = tensor.get<float>(i); + val = (val < 0.0f) ? 0.0f : ((val > 1.0f) ? 1.0f : val); + auto randomValue = dis(gen); + result.set(i, randomValue < val ? 1.0f : 0.0f); + } + + return result; +} + +Tensor spikegenRate(std::shared_ptr<Tensor> tensor, std::uint32_t numSteps) { + auto newTensor = tensor->repeat(numSteps); + return rateConvert(newTensor); +} +} // namespace Aidge diff --git a/src/data/Tensor.cpp b/src/data/Tensor.cpp index b128833c9099385c72f25057400a65a6b9034c32..d441685649ee571265469bc142288f736d6969d1 100644 --- a/src/data/Tensor.cpp +++ b/src/data/Tensor.cpp @@ -802,6 +802,40 @@ const Tensor& Tensor::ref(std::shared_ptr<Tensor>& fallback, } } +Tensor Tensor::repeat(int times) const { + AIDGE_ASSERT(times > 0, "repeat count must be positive"); + + Tensor src = *this; + if (not src.isContiguous()) { + src = src.clone(); + src.makeContiguous(); + } + + // Build new dimensions: new_dims = {times} followed by current dims + std::vector<DimSize_t> newDims; + newDims.push_back(static_cast<DimSize_t>(times)); + for (const auto &d : dims()) { + newDims.push_back(d); + } + + Tensor out(newDims); + out.setDataType(dataType(), false); + out.setDataFormat(dataFormat()); + if (hasImpl()) { + out.setBackend(getImpl()->backend(), device()); + } + + // Each "block" is a copy of the data from the original tensor. + const std::size_t block = src.size(); + for (int i = 0; i < times; ++i) { + // out.getImpl()->copy(source pointer, number of elements, destination offset) + out.getImpl()->copy(src.getImpl()->rawPtr(src.getImplOffset()), + block, + i * block); + } + return out; +} + std::vector<std::size_t> Tensor::toCoord(const std::vector<DimSize_t>& dimensions, std::size_t index) { diff --git a/unit_tests/data/Test_Spikegen.cpp b/unit_tests/data/Test_Spikegen.cpp new file mode 100644 index 0000000000000000000000000000000000000000..06f33197d15116466e38df424dfa4a53b5775930 --- /dev/null +++ b/unit_tests/data/Test_Spikegen.cpp @@ -0,0 +1,42 @@ +/******************************************************************************** + * Copyright (c) 2025 CEA-List + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + ********************************************************************************/ + +#include <cstdint> // std::uint8_t, std::uint16_t, std::int32_t +#include <vector> // std::vector + +#include <catch2/catch_test_macros.hpp> + +#include "aidge/data/Spikegen.hpp" +#include "aidge/data/Tensor.hpp" +#include "aidge/utils/ArrayHelpers.hpp" +#include "aidge/utils/TensorUtils.hpp" + + +namespace Aidge +{ +TEST_CASE("[core/data] SpikeGen zeros", "[SpikeGen]") { + auto input = Tensor(Array1D<float, 3>({0,0,0})); + auto expectedOutput = Tensor(Array2D<float, 3, 3>({{{0,0,0}, {0,0,0}, {0,0,0}}})); + + auto spikes = spikegenRate(std::make_shared<Tensor>(input), 3); + + REQUIRE(approxEq<float>(spikes, expectedOutput)); +} + +TEST_CASE("[core/data] SpikeGen ones", "[SpikeGen]") { + auto input = Tensor(Array1D<float, 3>({1,1,1})); + auto expectedOutput = Tensor(Array2D<float, 3, 3>({{{1,1,1}, {1,1,1}, {1,1,1}}})); + + auto spikes = spikegenRate(std::make_shared<Tensor>(input), 3); + + REQUIRE(approxEq<float>(spikes, expectedOutput)); +} +} // namespace Aidge diff --git a/unit_tests/data/Test_Tensor.cpp b/unit_tests/data/Test_Tensor.cpp index bfdc1a6b9c058b348942e9c29a77ac4d6db5086f..28f3a5fdec1836d5d5f5c2a42375e49bc4f1dcc5 100644 --- a/unit_tests/data/Test_Tensor.cpp +++ b/unit_tests/data/Test_Tensor.cpp @@ -505,6 +505,35 @@ TEST_CASE("[core/data] Tensor(other)", "[Tensor][extract][zeros][print]") { } } + SECTION("repeat") { + Tensor tensor = Array2D<int, 2, 3>{{{1, 2, 3}, + {4, 5, 6}}}; + const int repeatTimes = 4; + + Tensor repeated; + REQUIRE_NOTHROW(repeated = tensor.repeat(repeatTimes)); + + // The expected shape after repeating is {repeatTimes, 2, 3} + std::vector<DimSize_t> expectedDims = {static_cast<DimSize_t>(repeatTimes), 2, 3}; + CHECK(repeated.dims() == expectedDims); + + // For each repetition along the new dimension, extract the slice and verify + // that it matches the original tensor + for (int i = 0; i < repeatTimes; ++i) { + Tensor slice; + REQUIRE_NOTHROW(slice = repeated.extract({static_cast<std::size_t>(i)})); + CHECK(slice.dims() == tensor.dims()); + + // Compare slice with original tensor elementwise + for (std::size_t idx = 0; idx < tensor.size(); ++idx) { + int expectedVal = tensor.get<int>(idx); + int sliceVal = slice.get<int>(idx); + INFO("Mismatch in repetition " << i << " at flat index " << idx); + CHECK(sliceVal == expectedVal); + } + } + } + // print, toString, SECTION("Pretty printing for debug") { Tensor x{};