diff --git a/CHANGELOG b/CHANGELOG index 0031beb91337e681884cd5a1d8c420a099a27861..b53d52720c420583973bee58f8ba2b290f0879af 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ +# Version 0.4.0 (December 2024) + # Version 0.2.1 (May 14, 2024) -* rework export mechanism +* rework export mechanism * change `Operator::computeOutputDims()` with `Operator::forwardDims()` * automatic docstring decorators for python * add implementation of Operators only performing data/format manipulation diff --git a/include/aidge/data/Data.hpp b/include/aidge/data/Data.hpp index 5303d61f9ca0bc28687c9300506220c0d34a5c70..d641df6e688e738efaf49e5ae0c44649f465b644 100644 --- a/include/aidge/data/Data.hpp +++ b/include/aidge/data/Data.hpp @@ -20,6 +20,7 @@ #include "aidge/data/half.hpp" #include "aidge/utils/Attributes.hpp" +#include "aidge/utils/ErrorHandling.hpp" namespace Aidge { enum class DataType { @@ -98,7 +99,19 @@ DataFormatTranspose getDataFormatTranspose(const DataFormat& src, const DataForm class Data { public: + Data() = delete; + Data(Data&& other) = default; + Data(const Data& other) = default; Data(const std::string& type): mType(type) {}; + + Data& operator=(const Data& other) { + AIDGE_ASSERT(other.mType == mType, "Cannot copy a different type fo Data object."); + return *this; + }; + Data& operator=(Data&& other) { + AIDGE_ASSERT(other.mType == mType, "Cannot copy a different type fo Data object."); + return *this; + }; constexpr const std::string& type() const { return mType; } diff --git a/include/aidge/data/Tensor.hpp b/include/aidge/data/Tensor.hpp index cfd54e9aa64a0ad6b5165024284b0e3431cab28c..627a5a4784b4e6546cdfc96b65acbe2a39ee119c 100644 --- a/include/aidge/data/Tensor.hpp +++ b/include/aidge/data/Tensor.hpp @@ -23,6 +23,8 @@ #include <type_traits> // std::is_arithmetic #include <vector> +#include <fmt/core.h> + #include "aidge/backend/TensorImpl.hpp" #include "aidge/data/Data.hpp" #include "aidge/utils/ArrayHelpers.hpp" @@ -212,14 +214,13 @@ class Tensor : public Data, /** * @brief Copy dimensions, datatype and data from another Tensor. - * If current Tensor already has an implementation, data is copied to the - * existing implementation. Tensor backend/device remain untouched. - * If current Tensor does not have an implementation, only a shallow copy - * is performed and the Tensor will share data with t. + * Tensor backend/device are also copied and only a shallow copy + * is performed for data. Implementation will be shared with original Tensor. * @param other other Tensor object. * @return Tensor& */ - Tensor &operator=(const Tensor& other); + Tensor &operator=(const Tensor& other) = default; + Tensor &operator=(Tensor&& other) = default; template <typename T> constexpr Tensor &operator=(Vector<T> &&arr) { @@ -273,6 +274,17 @@ class Tensor : public Data, * @return Tensor */ Tensor operator+(const Tensor& other) const; + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + Tensor operator+(T val) const { return *this + Tensor(val); } + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + friend Tensor operator+(T val, const Tensor& other) { return other + val; } + + Tensor& operator+=(const Tensor& other); + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + Tensor& operator+=(T val) {return *this += Tensor(val); } /** * @brief Element-wise subtraction operation for two ``Tensor``s. @@ -284,6 +296,17 @@ class Tensor : public Data, * @return Tensor */ Tensor operator-(const Tensor& other) const; + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + inline Tensor operator-(T val) const { return *this - Tensor(val); } + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + friend inline Tensor operator-(T val, const Tensor& other) { return other - val; } + + Tensor& operator-=(const Tensor& other); + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + inline Tensor& operator-=(T val) {return *this -= Tensor(val); } /** * @brief Element-wise multiplication operation for two ``Tensor``s. @@ -295,6 +318,17 @@ class Tensor : public Data, * @return Tensor */ Tensor operator*(const Tensor& other) const; + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + inline Tensor operator*(T val) const { return *this * Tensor(val); } + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + friend inline Tensor operator*(T val, const Tensor& other) { return other * val; } + + Tensor& operator*=(const Tensor& other); + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + inline Tensor& operator*=(T val) {return *this *= Tensor(val); } /** * @brief Element-wise division operation for two ``Tensor``s. @@ -306,6 +340,14 @@ class Tensor : public Data, * @return Tensor */ Tensor operator/(const Tensor& other) const; + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + inline Tensor operator/(T val) const { return *this / Tensor(val); } + + Tensor& operator/=(const Tensor& other); + template<typename T, + typename VT = std::enable_if_t<std::is_arithmetic<T>::value, std::decay_t<T>>> + inline Tensor& operator/=(T val) {return *this /= Tensor(val); } /** * @brief Element-wise sqrt operation for Tensor. @@ -332,14 +374,17 @@ public: * @brief Perform a deep copy of the tensor. */ Tensor clone() const { - Tensor newTensor(*this); - if (!newTensor.isContiguous()) { - newTensor.makeContiguous(); - } - else { - std::shared_ptr<TensorImpl> newImpl = Registrar<Tensor>::create({mImpl->backend(), mDataType})(mImpl->device().second, mDims); - newImpl->copy(mImpl->rawPtr(mImplOffset), mSize); - newTensor.setImpl(newImpl); + Tensor newTensor(*this); // shallow copy + // handle deepcopy of implementation if any + if (newTensor.hasImpl()) { + if (!newTensor.isContiguous()) { + newTensor.makeContiguous(); + } + else { + std::shared_ptr<TensorImpl> newImpl = Registrar<Tensor>::create({mImpl->backend(), mDataType})(mImpl->device().second, mDims); + newImpl->copy(mImpl->rawPtr(mImplOffset), mSize); + newTensor.setImpl(newImpl); + } } return newTensor; } @@ -925,4 +970,17 @@ private: }; } // namespace Aidge +template<> +struct fmt::formatter<Aidge::Tensor> { + template<typename ParseContext> + inline constexpr auto parse(ParseContext& ctx) { + return ctx.begin(); + } + + template<typename FormatContext> + inline auto format(Aidge::Tensor const& t, FormatContext& ctx) const { + return fmt::format_to(ctx.out(), "{}", t.toString()); + } +}; + #endif /* AIDGE_CORE_DATA_TENSOR_H_ */ diff --git a/include/aidge/operator/Flatten.hpp b/include/aidge/operator/Flatten.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9bdfc932f113edf489a48a76796980bca5f2baf5 --- /dev/null +++ b/include/aidge/operator/Flatten.hpp @@ -0,0 +1,88 @@ +/******************************************************************************** + * 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 + * + ********************************************************************************/ + +#ifndef AIDGE_CORE_OPERATOR_FLATTEN_H_ +#define AIDGE_CORE_OPERATOR_FLATTEN_H_ + +#include <memory> +#include <vector> + +#include "aidge/backend/OperatorImpl.hpp" +#include "aidge/graph/Node.hpp" +#include "aidge/operator/OperatorTensor.hpp" +#include "aidge/utils/Registrar.hpp" +#include "aidge/utils/StaticAttributes.hpp" +#include "aidge/utils/Types.h" + +namespace Aidge { +class Flatten_OpImpl : public OperatorImpl { +public: + Flatten_OpImpl(const Operator& op, const std::string& backend = ""): OperatorImpl(op, backend) {} + void forward() override; +}; + +enum class FlattenAttr { Axis }; + +class Flatten_Op : public OperatorTensor, + public Registrable<Flatten_Op, std::string, std::function<std::shared_ptr<OperatorImpl>(const Flatten_Op&)>> { + +public: + static const std::string Type; + +private: + using Attributes_ = StaticAttributes<FlattenAttr, + std::int64_t>; + template <FlattenAttr e> using attr = typename Attributes_::template attr<e>; + const std::shared_ptr<Attributes_> mAttributes; + +public: + Flatten_Op() = delete; + + Flatten_Op(std::int64_t axis = 1); + + /** + * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated). + * @param op Operator to copy. + */ + Flatten_Op(const Flatten_Op& op); + + /** + * @brief Clone the operator using its copy-constructor. + * @see Operator::Flatten_Op + */ + std::shared_ptr<Operator> clone() const override; + + bool forwardDims(bool allowDataDependency = false) override final; + + void setBackend(const std::string& name, DeviceIdx_t device = 0) override final; + std::set<std::string> getAvailableBackends() const override; + + std::shared_ptr<Attributes> attributes() const override { return mAttributes; } + inline std::int64_t& axis() const { return mAttributes->template getAttr<FlattenAttr::Axis>(); } + + static const std::vector<std::string> getInputsName(){ + return {"data_input"}; + } + static const std::vector<std::string> getOutputsName(){ + return {"data_output"}; + } +}; + +std::shared_ptr<Node> Flatten(std::int64_t axis = 1, + const std::string &name = ""); +} // namespace Aidge + +namespace { +template <> +const char *const EnumStrings<Aidge::FlattenAttr>::data[] = { "axis" }; +} + +#endif /* AIDGE_CORE_OPERATOR_FLATTEN_H_ */ diff --git a/include/aidge/operator/Scaling.hpp b/include/aidge/operator/Scaling.hpp index 4ef39f63a2f9af34cd3fe28b01cf2fc195bdfc6e..9465667babb0978e33de3cf9f155d9b2e9d495b4 100644 --- a/include/aidge/operator/Scaling.hpp +++ b/include/aidge/operator/Scaling.hpp @@ -23,6 +23,9 @@ #include "aidge/utils/StaticAttributes.hpp" #include "aidge/utils/Types.h" +//Caution: This operator is now deprecated and should no longer be used. +//It has been replaced by the MetaOperator "Quantizer" (located directly in aidge_quantization). + namespace Aidge { enum class ScalingAttr { ScalingFactor, QuantizedNbBits, IsOutputUnsigned 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/include/aidge/utils/TensorUtils.hpp b/include/aidge/utils/TensorUtils.hpp index e287db4e8724f0388c13d438fc2e152fe69021cd..b5601f84c60b81d8c560b61b06c00673f51f4eee 100644 --- a/include/aidge/utils/TensorUtils.hpp +++ b/include/aidge/utils/TensorUtils.hpp @@ -44,6 +44,7 @@ bool approxEq(const Tensor& t1, const Tensor& t2, float relative = 1e-5f, float } for(size_t i = 0; i < t1.size(); ++i){ if (static_cast<float>(std::abs(t1.get<T1>(i) - t2.get<T2>(i))) > (absolute + (relative * static_cast<float>(std::abs(t2.get<T2>(i)))))){ + fmt::print("t1:\n{}\nt2\n{}\nat index {} {} != {}", t1, t2, i, t1.get<T1>(i), t2.get<T1>(i)); return false; } } diff --git a/python_binding/operator/pybind_Operator.cpp b/python_binding/operator/pybind_Operator.cpp index 514a64be9da0fc5485c1fd542d4252a47378c4bc..2191d866f2a2b1f1d490b2016de97afd8ec8157b 100644 --- a/python_binding/operator/pybind_Operator.cpp +++ b/python_binding/operator/pybind_Operator.cpp @@ -37,6 +37,7 @@ void init_Operator(py::module& m){ py::class_<Operator, std::shared_ptr<Operator>>(m, "Operator") .def("__repr__", &Operator::repr) .def("backend", &Operator::backend) + .def("clone", &Operator::clone) .def("set_output", py::overload_cast<const IOIndex_t, const std::shared_ptr<Data>&>(&Operator::setOutput, py::const_), py::arg("outputIdx"), py::arg("data")) .def("set_input", py::overload_cast<const IOIndex_t, const std::shared_ptr<Data>&>(&Operator::setInput), py::arg("inputIdx"), py::arg("data")) .def("get_raw_output", &Operator::getRawOutput, py::arg("outputIdx")) diff --git a/src/data/Tensor.cpp b/src/data/Tensor.cpp index 7eb09fe6d91d7a37cef89c98a66ef190332c8a21..ee19796098bf1d755448d833aa6a8a2c24180baa 100644 --- a/src/data/Tensor.cpp +++ b/src/data/Tensor.cpp @@ -44,7 +44,24 @@ Tensor Tensor::operator+(const Tensor& other) const { add_.setBackend(mImpl->backend()); add_.forward(); // using add_backend = std::remove_reference_t<decltype(*Registrar<Add_Op>::create("cpu")(std::declval<const Add_Op&>()))>; - return add_.getOutput(0)->clone(); + return *add_.getOutput(0); +} + +Tensor& Tensor::operator+=(const Tensor& other) { + AIDGE_ASSERT(hasImpl() && other.hasImpl(), "At least one Tensor cannot perform any binary operation because it has no implementation."); + AIDGE_ASSERT(mImpl->backend() == other.mImpl->backend(), "Tensors must have the same backend"); + AIDGE_ASSERT(dataType() == other.dataType(), "Tensors must have the same data type"); + AIDGE_ASSERT(dataFormat() == other.dataFormat(), "Tensors must have the same data format"); + auto add_ = Add_Op(); + const auto thisPtr = std::make_shared<Tensor>(*this); + add_.associateInput(0, thisPtr); + add_.associateInput(1, std::make_shared<Tensor>(other)); + add_.setOutput(0, thisPtr); + add_.setDataType(dataType()); + add_.setDataFormat(dataFormat()); + add_.setBackend(mImpl->backend()); + add_.forward(); + return *this; } @@ -61,7 +78,25 @@ Tensor Tensor::operator-(const Tensor& other) const { sub_.setBackend(mImpl->backend()); sub_.forward(); // using add_backend = std::remove_reference_t<decltype(*Registrar<Add_Op>::create("cpu")(std::declval<const Add_Op&>()))>; - return sub_.getOutput(0)->clone(); + return *sub_.getOutput(0); +} + +Tensor& Tensor::operator-=(const Tensor& other) { + AIDGE_ASSERT(hasImpl() && other.hasImpl(), "At least one Tensor cannot perform any binary operation because it has no implementation."); + AIDGE_ASSERT(mImpl->backend() == other.mImpl->backend(), "Tensors must have the same backend"); + AIDGE_ASSERT(dataType() == other.dataType(), "Tensors must have the same data type"); + AIDGE_ASSERT(dataFormat() == other.dataFormat(), "Tensors must have the same data format"); + auto sub_ = Sub_Op(); + const auto thisPtr = std::make_shared<Tensor>(*this); + sub_.associateInput(0, thisPtr); + sub_.associateInput(1, std::make_shared<Tensor>(other)); + sub_.setOutput(0, thisPtr); + sub_.setDataType(dataType()); + sub_.setDataFormat(dataFormat()); + sub_.setBackend(mImpl->backend()); + sub_.forward(); + // using add_backend = std::remove_reference_t<decltype(*Registrar<Add_Op>::create("cpu")(std::declval<const Add_Op&>()))>; + return *this; } @@ -81,6 +116,24 @@ Tensor Tensor::operator*(const Tensor& other) const { return mul_.getOutput(0)->clone(); } +Tensor& Tensor::operator*=(const Tensor& other) { + AIDGE_ASSERT(hasImpl() && other.hasImpl(), "At least one Tensor cannot perform any binary operation because it has no implementation."); + AIDGE_ASSERT(mImpl->backend() == other.mImpl->backend(), "Tensors must have the same backend"); + AIDGE_ASSERT(dataType() == other.dataType(), "Tensors must have the same data type"); + AIDGE_ASSERT(dataFormat() == other.dataFormat(), "Tensors must have the same data format"); + auto mul_ = Mul_Op(); + const auto thisPtr = std::make_shared<Tensor>(*this); + mul_.associateInput(0, thisPtr); + mul_.associateInput(1, std::make_shared<Tensor>(other)); + mul_.setOutput(0, thisPtr); + mul_.setDataType(dataType()); + mul_.setDataFormat(dataFormat()); + mul_.setBackend(mImpl->backend()); + mul_.forward(); + // using add_backend = std::remove_reference_t<decltype(*Registrar<Add_Op>::create("cpu")(std::declval<const Add_Op&>()))>; + return *this; +} + Tensor Tensor::operator/(const Tensor& other) const { AIDGE_ASSERT(hasImpl() && other.hasImpl(), "At least one Tensor cannot perform any binary operation because it has no implementation."); @@ -98,6 +151,24 @@ Tensor Tensor::operator/(const Tensor& other) const { return div_.getOutput(0)->clone(); } +Tensor& Tensor::operator/=(const Tensor& other) { + AIDGE_ASSERT(hasImpl() && other.hasImpl(), "At least one Tensor cannot perform any binary operation because it has no implementation."); + AIDGE_ASSERT(mImpl->backend() == other.mImpl->backend(), "Tensors must have the same backend"); + AIDGE_ASSERT(dataType() == other.dataType(), "Tensors must have the same data type"); + AIDGE_ASSERT(dataFormat() == other.dataFormat(), "Tensors must have the same data format"); + auto div_ = Div_Op(); + const auto thisPtr = std::make_shared<Tensor>(*this); + div_.associateInput(0, thisPtr); + div_.associateInput(1, std::make_shared<Tensor>(other)); + div_.setOutput(0, thisPtr); + div_.setDataType(dataType()); + div_.setDataFormat(dataFormat()); + div_.setBackend(mImpl->backend()); + div_.forward(); + // using add_backend = std::remove_reference_t<decltype(*Registrar<Add_Op>::create("cpu")(std::declval<const Add_Op&>()))>; + return *this; +} + Tensor Tensor::sqrt() const { AIDGE_ASSERT(hasImpl(), "Tensor has no implementation."); auto sqrt_ = Sqrt_Op(); @@ -135,24 +206,24 @@ Tensor Tensor::mean() const { return mean_.getOutput(0)->clone(); } -Tensor& Tensor::operator=(const Tensor& other) { - if (this == &other) { - return *this; - } - resize(other.dims(), other.strides()); - setDataType(other.dataType(), false); // do not convert existing data - if (other.hasImpl()) { - if (hasImpl()) { - copyFrom(other); - } else { - // Perform a shallow copy only - setImpl(other.mImpl, other.mImplOffset); - } - } else { - setImpl(nullptr); - } - return *this; -} +// Tensor& Tensor::operator=(const Tensor& other) { +// if (this == &other) { +// return *this; +// } +// resize(other.dims(), other.strides()); +// setDataType(other.dataType(), false); // do not convert existing data +// if (other.hasImpl()) { +// if (hasImpl()) { +// // copyFrom(other); +// // } else { +// // Perform a shallow copy only +// setImpl(other.mImpl, other.mImplOffset); +// } +// } else { +// setImpl(nullptr); +// } +// return *this; +// } void Tensor::setBackend(const std::string &name, DeviceIdx_t device, bool copyFrom) { diff --git a/src/graph/Node.cpp b/src/graph/Node.cpp index da6d833f3aa933cd5e707814c279142de5bc4a23..92ae463085a3583dfe894a1b9f6119fa0b099287 100644 --- a/src/graph/Node.cpp +++ b/src/graph/Node.cpp @@ -407,18 +407,18 @@ void Aidge::Node::resetConnections(bool includeLearnableParam) { /////////////////////////////////////////////////////// Aidge::NodePtr Aidge::Node::cloneSharedOperators() const { - return std::make_shared<Node>(mOperator, mAttrs); + return std::make_shared<Node>(mOperator, std::make_shared<DynamicAttributes>(*mAttrs)); } Aidge::NodePtr Aidge::Node::cloneSharedProducers() const { std::shared_ptr<Operator> op = (mOperator->type() == Producer_Op::Type) ? mOperator : mOperator->clone(); - return std::make_shared<Node>(op, mAttrs); + return std::make_shared<Node>(op, std::make_shared<DynamicAttributes>(*mAttrs)); } Aidge::NodePtr Aidge::Node::clone() const { - return std::make_shared<Node>(mOperator->clone(), mAttrs); + return std::make_shared<Node>(mOperator->clone(), std::make_shared<DynamicAttributes>(*mAttrs)); } std::set<Aidge::NodePtr> Aidge::Node::getNodeDelta(int delta, std::set<Aidge::NodePtr> nodeSee) { diff --git a/src/operator/Flatten.cpp b/src/operator/Flatten.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c77adc37400b0f14faade331965f75ac76c8c7b9 --- /dev/null +++ b/src/operator/Flatten.cpp @@ -0,0 +1,90 @@ +/******************************************************************************** + * 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 "aidge/operator/Flatten.hpp" + +#include <cstddef> // std::size_t +#include <cstdint> // std::int64_t +#include <memory> +#include <stdexcept> // std::runtime_error +#include <string> +#include <vector> + +#include "aidge/data/Tensor.hpp" +#include "aidge/utils/ErrorHandling.hpp" +#include "aidge/utils/Registrar.hpp" +#include "aidge/utils/Types.h" + +void Aidge::Flatten_OpImpl::forward() { + const Flatten_Op& op = dynamic_cast<const Flatten_Op&>(mOp); + op.getOutput(0)->getImpl()->copy(op.getInput(0)->getImpl()->rawPtr(), op.getInput(0)->size()); +} + +////////////////////////////////////////////////// + +const std::string Aidge::Flatten_Op::Type = "Flatten"; + +Aidge::Flatten_Op::Flatten_Op(const std::int64_t axis) + : OperatorTensor(Type, {InputCategory::Data}, 1), + mAttributes(std::make_shared<Attributes_>( + attr<FlattenAttr::Axis>(axis))) +{ + mImpl = std::make_shared<Flatten_OpImpl>(*this); +} + +Aidge::Flatten_Op::Flatten_Op(const Aidge::Flatten_Op& op) + : OperatorTensor(op), + mAttributes(op.mAttributes) +{ + if (!op.backend().empty()) { + SET_IMPL_MACRO(Flatten_Op, *this, op.backend()); + } + else { + mImpl = std::make_shared<Flatten_OpImpl>(*this); + } +} + +std::shared_ptr<Aidge::Operator> Aidge::Flatten_Op::clone() const { + return std::make_shared<Flatten_Op>(*this); +} + +bool Aidge::Flatten_Op::forwardDims(bool /*allowDataDependency*/) { + if (inputsAssociated()) { + const auto inDims(getInput(0)->dims()); + const auto firstDim = std::accumulate(inDims.begin(), inDims.begin() + axis(), 1ULL, std::multiplies<DimSize_t>()); + mOutputs[0]->resize({firstDim, getInput(0)->size() / firstDim}); + return true; + } + + return false; +} + +void Aidge::Flatten_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t device) { + if (Registrar<Flatten_Op>::exists({name})){ + SET_IMPL_MACRO(Flatten_Op, *this, name); + } + else { + mImpl = std::make_shared<Flatten_OpImpl>(*this); + } + mOutputs[0]->setBackend(name, device); +} + +std::set<std::string> Aidge::Flatten_Op::getAvailableBackends() const { + return Registrar<Flatten_Op>::getKeys(); +} + +////////////////////////////////////////////// + +std::shared_ptr<Aidge::Node> Aidge::Flatten(std::int64_t axis, + const std::string &name) +{ + return std::make_shared<Node>(std::make_shared<Flatten_Op>(axis), name); +} \ No newline at end of file diff --git a/src/operator/MetaOperator.cpp b/src/operator/MetaOperator.cpp index cd307c9d15043d3ee5f5de48695e04e4ad2ada6b..ae3c3ed6ca85c059204c524f467f5387f656e30b 100644 --- a/src/operator/MetaOperator.cpp +++ b/src/operator/MetaOperator.cpp @@ -96,7 +96,9 @@ void Aidge::MetaOperator_Op::setBackend(const std::string &name, Aidge::DeviceId for(auto i: mGraph->inputNodes()){ auto op_i = std::static_pointer_cast<OperatorTensor>(i->getOperator()); for(std::size_t in_idx=0; in_idx < op_i->nbInputs(); ++in_idx){ - op_i->getInput(in_idx)->setBackend(name, device); + if (op_i->getInput(in_idx)) { + op_i->getInput(in_idx)->setBackend(name, device); + } } } for(auto o: mGraph->outputNodes()){ diff --git a/src/operator/Pop.cpp b/src/operator/Pop.cpp index a27e2745b8929e84456ac079d063d94ffa359679..fa77d18e7e3c5b30466304e04cf2ad95affce20e 100644 --- a/src/operator/Pop.cpp +++ b/src/operator/Pop.cpp @@ -33,7 +33,7 @@ void Aidge::Pop_OpImpl::forward() { const Pop_Op& op = dynamic_cast<const Pop_Op&>(mOp); assert(op.getInput(0) && "missing input #0"); - *op.getOutput(0) = op.getInput(0)->extract({op.forwardStep()}); + *op.getOutput(0) = op.getInput(0)->extract({op.forwardStep()}).clone(); } ////////////////////////////////////////////////////////// diff --git a/src/operator/Producer.cpp b/src/operator/Producer.cpp index 3d48b88ab400596d68cbfa34502e795766ff94f0..9af4586886fc98c50862672392d3b704e6bc1d0c 100644 --- a/src/operator/Producer.cpp +++ b/src/operator/Producer.cpp @@ -44,7 +44,7 @@ Aidge::Producer_Op::Producer_Op(const std::shared_ptr<Aidge::Tensor> tensor, boo attr<ProdAttr::Constant>(constant))) { mOutputs[0] = tensor; // copy the pointer of the Tensor - if (mOutputs[0]->getImpl() && Registrar<Producer_Op>::exists({mOutputs[0]->getImpl()->backend()})){ + if (mOutputs[0] && mOutputs[0]->hasImpl() && Registrar<Producer_Op>::exists({mOutputs[0]->getImpl()->backend()})){ SET_IMPL_MACRO(Producer_Op, *this, mOutputs[0]->getImpl()->backend()); } else { @@ -61,7 +61,7 @@ Aidge::Producer_Op::Producer_Op(const Aidge::Producer_Op& op) : OperatorTensor(op), mAttributes(op.mAttributes) { - mOutputs[0] = std::make_shared<Tensor>(*(op.getOutput(0))); + *mOutputs[0] = *(op.getOutput(0)); if (mOutputs[0]->getImpl() && Registrar<Producer_Op>::exists({mOutputs[0]->getImpl()->backend()})){ SET_IMPL_MACRO(Producer_Op, *this, mOutputs[0]->getImpl()->backend()); } @@ -71,7 +71,12 @@ Aidge::Producer_Op::Producer_Op(const Aidge::Producer_Op& op) } std::shared_ptr<Aidge::Operator> Aidge::Producer_Op::clone() const { - return std::make_shared<Producer_Op>(*this); + // mOutput cannot be nullptr because of OperatorTensor constructor + std::shared_ptr<Tensor> newTensor = std::make_shared<Tensor>(mOutputs[0]->clone()); + + std::shared_ptr<Producer_Op> newOp = std::make_shared<Producer_Op>(newTensor, constant()); + + return newOp; } void Aidge::Producer_Op::setBackend(const std::string& name, Aidge::DeviceIdx_t device) { diff --git a/src/operator/Scaling.cpp b/src/operator/Scaling.cpp index 5ac08cd2245e0caa3ca7072c70ccc69bcfcf9558..268a14cf9759a6e03302680814778da4804dcc19 100644 --- a/src/operator/Scaling.cpp +++ b/src/operator/Scaling.cpp @@ -18,6 +18,10 @@ #include "aidge/utils/Registrar.hpp" #include "aidge/utils/Types.h" + +//Caution: This operator is now deprecated and should no longer be used. +//It has been replaced by the MetaOperator "Quantizer" (located directly in aidge_quantization). + const std::string Aidge::Scaling_Op::Type = "Scaling"; Aidge::Scaling_Op::Scaling_Op(float scalingFactor, std::size_t nbBits, bool isOutputUnsigned) @@ -26,12 +30,15 @@ Aidge::Scaling_Op::Scaling_Op(float scalingFactor, std::size_t nbBits, bool isOu attr<ScalingAttr::ScalingFactor>(scalingFactor), attr<ScalingAttr::QuantizedNbBits>(nbBits), attr<ScalingAttr::IsOutputUnsigned>(isOutputUnsigned))) -{} +{ + Log::warn("Caution: The [Scaling] operator is now deprecated and should no longer be used.\nIt has been replaced by the MetaOperator [Quantizer] (located directly in aidge_quantization)."); +} Aidge::Scaling_Op::Scaling_Op(const Aidge::Scaling_Op& op) : OperatorTensor(op), mAttributes(op.mAttributes) { + Log::warn("Caution: The [Scaling] operator is now deprecated and should no longer be used. \nIt has been replaced by the MetaOperator [Quantizer] (located directly in aidge_quantization)."); if (op.mImpl){ SET_IMPL_MACRO(Scaling_Op, *this, op.backend()); } else { 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/data/Test_Tensor.cpp b/unit_tests/data/Test_Tensor.cpp index 58003bb4009a484ca63acffdb50fbda156a48787..6c4b14602aed98ff5736d2cf30ba642f9e7ec57b 100644 --- a/unit_tests/data/Test_Tensor.cpp +++ b/unit_tests/data/Test_Tensor.cpp @@ -120,7 +120,27 @@ TEST_CASE("[core/data] Tensor(Construction)", "[Tensor][Constructor]") { )); } SECTION("copy constructor / copy assignment operator") { + Tensor t1 = Array1D<int, 2>{{1, 2}}; + Tensor t2, t3; + REQUIRE_NOTHROW(t3 = t1); + REQUIRE(t1 == t3); + + REQUIRE_NOTHROW(t2 = Tensor(t1)); + REQUIRE(t1 == t2); + + + t1.set<int>(0, 10); + + // check copies are shallow + REQUIRE(t2.get<int>(0) == 10); + REQUIRE(t3.get<int>(0) == 10); + + // set already existing Tensor + Tensor t4 = Array1D<int, 1>{{11}}; + REQUIRE_NOTHROW(t4 = t1); + REQUIRE(t4 == t1); + REQUIRE(t4.size() == 2); } SECTION("move constructor / move assignment operator") { diff --git a/unit_tests/graph/Test_GraphView.cpp b/unit_tests/graph/Test_GraphView.cpp index a7d02cd2fc1f3782046f3e8a9e7d7ca00b2ec5a7..5bd435e28718d663519e504995fd5b030913d254 100644 --- a/unit_tests/graph/Test_GraphView.cpp +++ b/unit_tests/graph/Test_GraphView.cpp @@ -816,7 +816,7 @@ TEST_CASE("[core/graph] GraphView(replace)", "[GraphView][replace]") { } } -TEST_CASE("[GraphView] clone") { +TEST_CASE("[GraphView] clone", "[GraphView][Core][Clone]") { auto dataProvider = Producer({16, 3, 224, 224}, "dataProvider"); auto conv1 = Conv(3, 32, {3, 3}, "conv1"); auto conv2 = Conv(32, 64, {3, 3}, "conv2"); diff --git a/unit_tests/operator/Test_FlattenImpl.cpp b/unit_tests/operator/Test_FlattenImpl.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1990c0b61645722526c59c5e7a136b33e8d87b7a --- /dev/null +++ b/unit_tests/operator/Test_FlattenImpl.cpp @@ -0,0 +1,98 @@ +/******************************************************************************** + * 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/Flatten.hpp" + +#include <memory> + +using namespace Aidge; + +TEST_CASE("[cpu/operator] Flatten(forward)") { + std::shared_ptr<Tensor> input = std::make_shared<Tensor>(Array4D<int32_t,1,2,3,5> { + { + { + { + { 1, 2, 3, 4, 5}, + { 6, 7, 8, 9, 10}, + {11, 12, 13, 14, 15} + }, + { + {16, 17, 18, 19, 20}, + {21, 22, 23, 24, 25}, + {26, 27, 28, 29, 30} + } + } + } + }); + + SECTION("Default (axis = 1)") { + std::shared_ptr<Node> myFlatten = Flatten(); + auto op = std::static_pointer_cast<OperatorTensor>(myFlatten -> getOperator()); + op->associateInput(0, input); + op->setDataType(DataType::Int32); + op->setBackend("cpu"); + myFlatten->forward(); + + auto expectedOutput = input->clone(); + expectedOutput.resize({1, input->size()}); + + REQUIRE(op->getOutput(0)->dims() == expectedOutput.dims()); + REQUIRE(*(op->getOutput(0)) == expectedOutput); + } + + SECTION("Axis = 0") { + std::shared_ptr<Node> myFlatten = Flatten(0); + auto op = std::static_pointer_cast<OperatorTensor>(myFlatten -> getOperator()); + op->associateInput(0, input); + op->setDataType(DataType::Int32); + op->setBackend("cpu"); + myFlatten->forward(); + + auto expectedOutput = input->clone(); + expectedOutput.resize({1, input->size()}); + + REQUIRE(op->getOutput(0)->dims() == expectedOutput.dims()); + REQUIRE(*(op->getOutput(0)) == expectedOutput); + } + + SECTION("Axis = 2") { + std::shared_ptr<Node> myFlatten = Flatten(2); + auto op = std::static_pointer_cast<OperatorTensor>(myFlatten -> getOperator()); + op->associateInput(0, input); + op->setDataType(DataType::Int32); + op->setBackend("cpu"); + myFlatten->forward(); + + auto expectedOutput = input->clone(); + expectedOutput.resize({2, input->size() / 2}); + + REQUIRE(op->getOutput(0)->dims() == expectedOutput.dims()); + REQUIRE(*(op->getOutput(0)) == expectedOutput); + } + + SECTION("Axis = 4") { + std::shared_ptr<Node> myFlatten = Flatten(4); + auto op = std::static_pointer_cast<OperatorTensor>(myFlatten -> getOperator()); + op->associateInput(0, input); + op->setDataType(DataType::Int32); + op->setBackend("cpu"); + myFlatten->forward(); + + auto expectedOutput = input->clone(); + expectedOutput.resize({input->size(), 1}); + + REQUIRE(op->getOutput(0)->dims() == expectedOutput.dims()); + REQUIRE(*(op->getOutput(0)) == expectedOutput); + } +} \ No newline at end of file diff --git a/unit_tests/operator/Test_PopImpl.cpp b/unit_tests/operator/Test_PopImpl.cpp index f46131ed8c324f38874eb433f97d13977b4253a4..d3c87ef7289e4516442885f7449060055c428c49 100644 --- a/unit_tests/operator/Test_PopImpl.cpp +++ b/unit_tests/operator/Test_PopImpl.cpp @@ -16,21 +16,22 @@ #include "aidge/operator/Pop.hpp" #include "aidge/utils/TensorUtils.hpp" -using Aidge::Tensor; -using Aidge::Pop; +using namespace Aidge; TEST_CASE("[cpu/operator] Pop(forward)", "[Pop][CPU]") { - std::shared_ptr<Tensor> pop1 = std::make_shared<Tensor>(Aidge::Array1D<int,3>{{4,5,6}}); - std::shared_ptr<Tensor> pop2 = std::make_shared<Tensor>(Aidge::Array1D<int,3>{{1,2,3}}); - std::shared_ptr<Tensor> input = std::make_shared<Tensor>(Aidge::Array2D<int,2,3>{{{1,2,3}, {4,5,6}}}); + std::shared_ptr<Tensor> pop1 = std::make_shared<Tensor>(Array1D<int,3>{{4,5,6}}); + std::shared_ptr<Tensor> pop2 = std::make_shared<Tensor>(Array1D<int,3>{{1,2,3}}); + std::shared_ptr<Tensor> input = std::make_shared<Tensor>(Array2D<int,2,3>{{{1,2,3}, {4,5,6}}}); - auto pop = Aidge::Pop("pop"); - pop->getOperator()->associateInput(0, input); - pop->getOperator()->setBackend("cpu"); - pop->getOperator()->setDataType(Aidge::DataType::Int32); + auto pop = Pop("pop"); + std::shared_ptr<Pop_Op> op = std::static_pointer_cast<Pop_Op>(pop->getOperator()); + op->associateInput(0, input); + op->setBackend("cpu"); + op->setDataType(DataType::Int32); + op->forwardDims(); REQUIRE_NOTHROW(pop->forward()); - REQUIRE(*std::static_pointer_cast<Aidge::OperatorTensor>(pop->getOperator())->getOutput(0) == *pop2); + REQUIRE(*op->getOutput(0) == *pop2); REQUIRE_NOTHROW(pop->forward()); - REQUIRE(*std::static_pointer_cast<Aidge::OperatorTensor>(pop->getOperator())->getOutput(0) == *pop1); + REQUIRE(*op->getOutput(0) == *pop1); } 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()); + } +} diff --git a/unit_tests/recipes/Test_ToGenericOp.cpp b/unit_tests/recipes/Test_ToGenericOp.cpp index 886e07f953afa26fed885d8af35b78b1fca56ae1..cb75fdb1072dee476c88c1f6d502a792b2e6abd9 100644 --- a/unit_tests/recipes/Test_ToGenericOp.cpp +++ b/unit_tests/recipes/Test_ToGenericOp.cpp @@ -63,7 +63,8 @@ TEST_CASE("[graph/convert] toGenericOp", "[toGenericOp][recipies]") { std::shared_ptr<Node> metaOpNode; - for (const auto& nodePtr : g->getNodes()) + const auto nodes = g->getNodes(); // g nodes gets modified in the loop! + for (const auto& nodePtr : nodes) { if (nodePtr->type() == "ConvReLUFC") { @@ -74,10 +75,13 @@ TEST_CASE("[graph/convert] toGenericOp", "[toGenericOp][recipies]") { } } + REQUIRE(metaOpNode); + REQUIRE(!metaOpNode->getOperator()->isAtomic()); auto newGenOp = g->getNode("ConvReLUFC_0"); // Ensure the conversion REQUIRE(newGenOp->type() == "ConvReLUFC"); + REQUIRE(std::dynamic_pointer_cast<GenericOperator_Op>(newGenOp->getOperator())); const auto metaOpAttr = *std::static_pointer_cast<DynamicAttributes>(metaOpNode->getOperator()->attributes()); const auto newGenOpAttr = *std::static_pointer_cast<DynamicAttributes>(newGenOp->getOperator()->attributes()); diff --git a/version.txt b/version.txt index 0d91a54c7d439e84e3dd17d3594f1b2b6737f430..1d0ba9ea182b0f7354f3daf12120744ec5e0c2f8 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.3.0 +0.4.0