diff --git a/include/aidge/operator/Slice.hpp b/include/aidge/operator/Slice.hpp index 055e6fd1d8917ae015b88a223f1f8701fd9dce59..5bb07ae01d8f076891a803698d2b3f489d90b462 100644 --- a/include/aidge/operator/Slice.hpp +++ b/include/aidge/operator/Slice.hpp @@ -25,6 +25,12 @@ namespace Aidge { +class Slice_OpImpl : public OperatorImpl { +public: + Slice_OpImpl(const Operator& op, const std::string& backend = ""): OperatorImpl(op, backend) {} + void forward() override; +}; + enum class SliceAttr { Starts, Ends, Axes, Steps }; class Slice_Op @@ -32,13 +38,13 @@ class Slice_Op public Registrable<Slice_Op, std::string, std::function<std::shared_ptr<OperatorImpl>(const Slice_Op &)>> { public: static const std::string Type; - -private: using Attributes_ = StaticAttributes<SliceAttr, std::vector<std::int64_t>, std::vector<std::int64_t>, std::vector<std::int8_t>, std::vector<std::int64_t>>; + +private: template <SliceAttr e> using attr = typename Attributes_::template attr<e>; const std::shared_ptr<Attributes_> mAttributes; @@ -50,7 +56,6 @@ public: const std::vector<std::int8_t>& axes, const std::vector<std::int64_t>& steps); - /** * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its * input tensors (the new operator has no input associated). @@ -58,7 +63,6 @@ public: */ Slice_Op(const Slice_Op &op); -public: /** * @brief Clone the operator using its copy-constructor. * @see Operator::Slice_Op @@ -103,4 +107,4 @@ template <> const char *const EnumStrings<Aidge::SliceAttr>::data[] = { "starts", "ends", "axes", "steps" }; } -#endif /* AIDGE_CORE_OPERATOR_RELU_H_ */ +#endif /* AIDGE_CORE_OPERATOR_SLICE_H_ */ diff --git a/src/operator/Slice.cpp b/src/operator/Slice.cpp index 3bdee8c13c1759261140d634940b0a4e81210084..02dcad58c47fb804f50b9eb2e20be45a12e73fae 100644 --- a/src/operator/Slice.cpp +++ b/src/operator/Slice.cpp @@ -24,6 +24,9 @@ #include "aidge/data/Tensor.hpp" #include "aidge/utils/ErrorHandling.hpp" #include "aidge/utils/Types.h" +#include "aidge/data/Data.hpp" +#include "aidge/utils/Registrar.hpp" + const std::string Aidge::Slice_Op::Type = "Slice"; @@ -43,17 +46,18 @@ Aidge::Slice_Op::Slice_Op(const std::vector<std::int64_t>& starts, attr<SliceAttr::Ends>(ends), attr<SliceAttr::Axes>(axes), attr<SliceAttr::Steps>(steps))) -{} +{ + mImpl = std::make_shared<Slice_OpImpl>(*this); +} -Aidge::Slice_Op::Slice_Op(const Aidge::Slice_Op &op) - : OperatorTensor(op), - mAttributes(op.mAttributes) +Aidge::Slice_Op::Slice_Op(const Aidge::Slice_Op& op) + : OperatorTensor(op), mAttributes(op.mAttributes) { if (!op.backend().empty()) { SET_IMPL_MACRO(Slice_Op, *this, op.backend()); } else { - mImpl = nullptr; + mImpl = std::make_shared<Slice_OpImpl>(*this); } } @@ -61,6 +65,82 @@ std::shared_ptr<Aidge::Operator> Aidge::Slice_Op::clone() const { return std::make_shared<Slice_Op>(*this); } +// Helper function to calculate the linear index for multi-dimensional data +size_t getLinearIndex(const std::vector<size_t>& dims, const std::vector<size_t>& indices) { + size_t linearIndex = 0; + size_t stride = 1; + for (int i = dims.size() - 1; i >= 0; --i) { + linearIndex += indices[i] * stride; + stride *= dims[i]; + } + return linearIndex; +} + +void Aidge::Slice_OpImpl::forward() { + const Slice_Op& op = dynamic_cast<const Slice_Op&>(mOp); + + if (!op.getInput(0)) { + AIDGE_THROW_OR_ABORT(std::runtime_error, "{}: input #0 should be associated with a Tensor", op.Type); + } + AIDGE_ASSERT((op.axes().size() == op.ends().size()) && + (op.axes().size() == op.starts().size()), + "Starts, Ends and Axes arguments should be the same size."); + + const std::vector<size_t> inputDims = op.getInput(0)->dims(); + std::vector<size_t> indices(inputDims.size(), 0); // Initialize indices for each dimension + + // Create an array of ranges for each axis + std::vector<std::vector<int>> ranges(inputDims.size()); + + // Generate ranges dynamically for each dimension + for (size_t axisIdx = 0; axisIdx < inputDims.size(); ++axisIdx) { + if (std::find(op.axes().begin(), op.axes().end(), axisIdx) != op.axes().end()) { + // This axis is being sliced + int start = op.starts()[axisIdx]; + int end = op.ends()[axisIdx]; + int step = op.steps()[axisIdx]; + + start = start >= 0 ? start: start + inputDims[axisIdx]; + end = end >= 0 ? end: end + inputDims[axisIdx]; + + // Generate the range of indices for this axis + for (int idx = start; (step > 0) ? (idx < end) : (idx > end); idx += step) { + ranges[axisIdx].push_back(idx); + } + } else { + // This axis is not being sliced, keep its full range (just one index in the range) + ranges[axisIdx].push_back(0); + } + } + + // Use iterative stack to handle all dimensions dynamically + std::vector<size_t> currentIndex(inputDims.size(), 0); // Track current index in each dimension + std::vector<size_t> stackPointer(inputDims.size(), 0); // Pointers to ranges for each dimension + size_t dim = 0; // Start at the first dimension + size_t offset = 0; // Offset in the output tensor + + while (dim < inputDims.size()) { + if (stackPointer[dim] < ranges[dim].size()) { + // Set the current index for this dimension + currentIndex[dim] = ranges[dim][stackPointer[dim]]; + stackPointer[dim]++; + + if (dim == inputDims.size() - 1) { + // We've reached the last dimension, process this index combination + size_t linearIndex = getLinearIndex(inputDims, currentIndex); + op.getOutput(0)->getImpl()->copy(op.getInput(0)->getImpl()->rawPtr(linearIndex), 1, offset); + offset++; + } else { + // Move to the next dimension + dim++; + } + } else { + // Reset this dimension and move back to the previous one + stackPointer[dim] = 0; + dim--; + } + } +} bool Aidge::Slice_Op::dimsForwarded() const { if ((getInput(1) && !getInput(1)->undefined()) @@ -191,7 +271,7 @@ bool Aidge::Slice_Op::forwardDims(bool allowDataDependency) { } } - const std::size_t sliceLength = static_cast<std::size_t>(std::ceil((static_cast<float>(end) - static_cast<float>(start)) / static_cast<float>(step))); + const std::size_t sliceLength = static_cast<std::size_t>(std::ceil((static_cast<float>(end) - static_cast<float>(start)) / static_cast<float>((step)))); // Check if slice length is valid if (sliceLength > getInput(0)->dims()[axis]) { @@ -208,7 +288,12 @@ bool Aidge::Slice_Op::forwardDims(bool allowDataDependency) { } void Aidge::Slice_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t device) { - SET_IMPL_MACRO(Slice_Op, *this, name); + if (Registrar<Slice_Op>::exists({name})){ + SET_IMPL_MACRO(Slice_Op, *this, name); + } + else { + mImpl = std::make_shared<Slice_OpImpl>(*this); + } mOutputs[0]->setBackend(name, device); } diff --git a/unit_tests/operator/Test_SliceImpl.cpp b/unit_tests/operator/Test_SliceImpl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b856535f373fb498324674a7ab169da83f1746d2 --- /dev/null +++ b/unit_tests/operator/Test_SliceImpl.cpp @@ -0,0 +1,278 @@ +/******************************************************************************** + * Copyright (c) 2023 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 <catch2/catch_test_macros.hpp> + +#include "aidge/data/Tensor.hpp" +#include "aidge/operator/Slice.hpp" + +using namespace Aidge; + +TEST_CASE("[cpu/operator] Slice(forward)", "[Slice][CPU]") { + SECTION("1D Tensor") { + std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array1D<int,10> { + {0, 1, -2,-3, 4,-5,-6, 7, 8, 9} + }); + std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array1D<int,3> { + {0, 1, -2} + }); + std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,1>{{0}}); + std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,1>{{3}}); + std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,1>{{0}}); + + std::shared_ptr<Node> mySlice = Slice(); + auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator()); + mySlice->getOperator()->associateInput(0,input0); + mySlice->getOperator()->associateInput(1,starts); + mySlice->getOperator()->associateInput(2,ends); + mySlice->getOperator()->associateInput(3,axes); + mySlice->getOperator()->setDataType(DataType::Int32); + mySlice->getOperator()->setBackend("cpu"); + mySlice->forward(); + + REQUIRE(*(op->getOutput(0)) == *expectedOutput); + REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims()); + REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType()); + } + + SECTION("2D Tensor") { + std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array2D<int,2,10> { + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + } + }); + std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array2D<int,2,3> { + { + {-5,-6, 7}, + {-5,-6, 7} + } + }); + std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,2>{{0,5}}); + std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,2>{{2,8}}); + std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,2>{{0,1}}); + + std::shared_ptr<Node> mySlice = Slice(); + auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator()); + mySlice->getOperator()->associateInput(0,input0); + mySlice->getOperator()->associateInput(1,starts); + mySlice->getOperator()->associateInput(2,ends); + mySlice->getOperator()->associateInput(3,axes); + mySlice->getOperator()->setDataType(DataType::Int32); + mySlice->getOperator()->setBackend("cpu"); + mySlice->forward(); + op->getOutput(0)->print(); + REQUIRE(*(op->getOutput(0)) == *expectedOutput); + REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims()); + REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType()); + } + + SECTION("3D Tensor") { + std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array3D<int,2,2,10> { + { + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + }, + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + } + } + }); + std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array3D<int,1,1,3> { + { + { + { 4,-5,-6} + } + } + }); + std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,3>{{0,1,4}}); + std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,3>{{1,2,7}}); + std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,3>{{0,1,2}}); + + std::shared_ptr<Node> mySlice = Slice(); + auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator()); + mySlice->getOperator()->associateInput(0,input0); + mySlice->getOperator()->associateInput(1,starts); + mySlice->getOperator()->associateInput(2,ends); + mySlice->getOperator()->associateInput(3,axes); + mySlice->getOperator()->setDataType(DataType::Int32); + mySlice->getOperator()->setBackend("cpu"); + mySlice->forward(); + // mySlice->getOperator()->output(0).print(); + REQUIRE(*(op->getOutput(0)) == *expectedOutput); + REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims()); + REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType()); + } + + SECTION("4D Tensor") { + std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array4D<int,2,2,2,10> { + { + { + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + }, + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + } + }, + { + { + { 0, 1, 2,-3, 6,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + }, + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3,11,-5,-6, 7,-1,10} + } + } + } + }); + std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array4D<int,2,2,2,10> { + { + { + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + }, + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + } + }, + { + { + { 0, 1, 2,-3, 6,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + }, + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3,11,-5,-6, 7,-1,10} + } + } + } + }); + std::shared_ptr<Tensor> starts = std::make_shared<Tensor>(Array1D<int,4>{{0,0,0,0}}); + std::shared_ptr<Tensor> ends = std::make_shared<Tensor>(Array1D<int,4>{{2,2,2,10}}); + std::shared_ptr<Tensor> axes = std::make_shared<Tensor>(Array1D<int,4>{{0,1,2,3}}); + + std::shared_ptr<Node> mySlice = Slice(); + auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator()); + mySlice->getOperator()->associateInput(0,input0); + mySlice->getOperator()->associateInput(1,starts); + mySlice->getOperator()->associateInput(2,ends); + mySlice->getOperator()->associateInput(3,axes); + mySlice->getOperator()->setDataType(DataType::Int32); + mySlice->getOperator()->setBackend("cpu"); + mySlice->forward(); + // op->getOutput(0)->print(); + REQUIRE(*(op->getOutput(0)) == *expectedOutput); + REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims()); + REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType()); + } + + SECTION("Attributes instead of inputs") { + std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array4D<int,2,2,2,10> { + { + { + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + }, + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + } + }, + { + { + { 0, 1, 2,-3, 6,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3, 4,-5,-6, 7,-1,10} + }, + { + { 0, 1, 2,-3, 4,-5,-6, 7, 8, 9}, + {-5, 4, 2,-3,11,-5,-6, 7,-1,10} + } + } + } + }); + std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array4D<int,1,1,1,5> { + { + { + { + { 0, 1, 2,-3, 4} + } + } + } + }); + + std::shared_ptr<Node> mySlice = Slice({0,0,0,0}, {1,1,1,5}, {0,1,2,3}, {1,1,1,1}); + auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator()); + mySlice->getOperator()->associateInput(0,input0); + mySlice->getOperator()->setDataType(DataType::Int32); + mySlice->getOperator()->setBackend("cpu"); + mySlice->forward(); + // op->getOutput(0)->print(); + REQUIRE(*(op->getOutput(0)) == *expectedOutput); + REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims()); + REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType()); + } + + SECTION("Different Steps") { + std::shared_ptr<Tensor> input0 = std::make_shared<Tensor>(Array3D<int,4,2,8> { + { + { + { 0, 1, 2,-3, 4,-5,-6,7}, + {-5, 4, 2,-3, 4,-5,-6,-7} + }, + { + { 10, 11, 12,-13, 14,-15,-16,17}, + {-15, 14, 12,-13, 14,-15,-16,-17} + }, + { + { 20, 21, 22,-23, 24,-25,-26,27}, + {-25, 24, 22,-23, 24,-25,-26,-27} + }, + { + { 30, 31, 32,-33, 34,-35,-36,37}, + {-35, 34, 32,-33, 34,-35,-36,-37} + } + } + }); + std::shared_ptr<Tensor> expectedOutput = std::make_shared<Tensor>(Array3D<int,2,1,3> { + { + { + { 7, 4, 1} + }, + { + { 27, 24, 21} + } + } + }); + + std::shared_ptr<Node> mySlice = Slice({0,0,7}, {4,1,0}, {0,1,2}, {2,1,-3}); + // on Axis 0: from 0 to 4 by step of 2 + // on Axis 1: from 0 to 1 by step of 1 + // on Axis 2: from 7 to 0 by step of -3 (reverse the order of elements) + auto op = std::static_pointer_cast<OperatorTensor>(mySlice -> getOperator()); + mySlice->getOperator()->associateInput(0,input0); + mySlice->getOperator()->setDataType(DataType::Int32); + mySlice->getOperator()->setBackend("cpu"); + mySlice->forward(); + // op->getOutput(0)->print(); + REQUIRE(*(op->getOutput(0)) == *expectedOutput); + REQUIRE(op->getOutput(0)->dims() == expectedOutput->dims()); + REQUIRE(op->getOutput(0)->dataType() == expectedOutput->dataType()); + } +}