From f5d51a18c251e8a842da5c6fab1f1638d095b2c0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Gr=C3=A9goire=20KUBLER?= <gregoire.kubler@proton.me>
Date: Tue, 10 Sep 2024 11:33:47 +0200
Subject: [PATCH] feat : operator constant of shape

---
 include/aidge/operator/ConstantOfShape.hpp    | 135 ++++++++++++++++++
 .../operator/pybind_ConstantOfShape.cpp       |  44 ++++++
 python_binding/pybind_core.cpp                |   2 +
 src/operator/ConstantOfShape.cpp              |  68 +++++++++
 unit_tests/operator/Test_ConstantOfShape.cpp  |  85 +++++++++++
 5 files changed, 334 insertions(+)
 create mode 100644 include/aidge/operator/ConstantOfShape.hpp
 create mode 100644 python_binding/operator/pybind_ConstantOfShape.cpp
 create mode 100644 src/operator/ConstantOfShape.cpp
 create mode 100644 unit_tests/operator/Test_ConstantOfShape.cpp

diff --git a/include/aidge/operator/ConstantOfShape.hpp b/include/aidge/operator/ConstantOfShape.hpp
new file mode 100644
index 000000000..1f62f6a62
--- /dev/null
+++ b/include/aidge/operator/ConstantOfShape.hpp
@@ -0,0 +1,135 @@
+/********************************************************************************
+ * 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_CONSTANT_OF_SHAPE_H_
+#define AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_
+
+#include <cstdint>
+#include <cstdlib>
+#include <functional>
+#include <limits>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "aidge/data/Data.hpp"
+#include "aidge/graph/Node.hpp"
+#include "aidge/operator/Operator.hpp"
+#include "aidge/data/Tensor.hpp"
+#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/utils/ErrorHandling.hpp"
+#include "aidge/utils/Registrar.hpp"
+#include "aidge/utils/StaticAttributes.hpp"
+#include "aidge/utils/Types.h"
+
+namespace Aidge {
+
+enum class ConstantOfShapeAttr {
+  /**
+   * @brief value to fill the output tensor with.
+   * Its a scalar tensor holding a value with a fixed datatype
+   */
+  Value,
+};
+
+/**
+ * @brief This operator's purpose is to generate a tensor of shape given via
+ * input and filled with a given value set via attribute.
+ */
+class ConstantOfShape_Op
+    : public OperatorTensor,
+      public Registrable<ConstantOfShape_Op, std::string,
+                         std::shared_ptr<OperatorImpl>(
+                             const ConstantOfShape_Op &)> {
+
+public:
+  // name of the type of the operation
+  static const std::string Type;
+
+private:
+  using Attributes_ = StaticAttributes<ConstantOfShapeAttr, Tensor>;
+  template <ConstantOfShapeAttr e>
+  using attr = typename Attributes_::template attr<e>;
+  const std::shared_ptr<Attributes_> mAttributes;
+
+public:
+  /**
+   * @brief constructor for ConstantOfShape_op
+   * @param[in] value : a scalar tensor which holds the value that will 
+   * fill the output tensor
+   */
+  ConstantOfShape_Op(const Tensor &value = Tensor(0.f))
+      : OperatorTensor(Type, {InputCategory::Data}, 1),
+        mAttributes(std::make_shared<Attributes_>(
+            attr<ConstantOfShapeAttr::Value>(value))) {}
+
+  /**
+   * @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.
+   */
+  ConstantOfShape_Op(const ConstantOfShape_Op &op)
+      : OperatorTensor(op), mAttributes(op.mAttributes) {
+    if (op.mImpl) {
+      SET_IMPL_MACRO(ConstantOfShape_Op, *this, op.backend());
+    } else {
+      mImpl = nullptr;
+    }
+  }
+
+  /**
+   * @brief Clone the operator using its copy-constructor.
+   * @see Operator::MatMul_Op
+   */
+  std::shared_ptr<Operator> clone() const override final {
+    return std::make_shared<ConstantOfShape_Op>(*this);
+  }
+
+  /**
+   * @brief Compute dimensions for the output Tensor
+   * @param allowDataDependency specify if the output shape of this operator
+   * depends on its inputs.
+   */
+  bool forwardDims(bool allowDataDependency = false) override final;
+
+  void setBackend(const std::string &name,
+                  DeviceIdx_t device = 0) override final;
+
+  inline std::shared_ptr<Attributes> attributes() const override {
+    return mAttributes;
+  }
+  inline Tensor &value() const noexcept {
+    return mAttributes->template getAttr<ConstantOfShapeAttr::Value>();
+  }
+
+  static const std::vector<std::string> getInputsName() { return {"input"}; }
+  static const std::vector<std::string> getOutputsName() {
+    return {"constant_of_shape"};
+  }
+};
+
+// helper with C-style array instead of std::array for kernel_dims to allow
+// automatic template DIM deduction
+inline std::shared_ptr<Node> ConstantOfShape(const Tensor value = Tensor(0.f),
+                                             const std::string &name = "") {
+  return std::make_shared<Node>(std::make_shared<ConstantOfShape_Op>(value),
+                                name);
+}
+} // namespace Aidge
+
+namespace {
+template <>
+const char *const EnumStrings<Aidge::ConstantOfShapeAttr>::data[] = {"Value"};
+}
+
+#endif // AIDGE_CORE_OPERATOR_CONSTANT_OF_SHAPE_H_
+
diff --git a/python_binding/operator/pybind_ConstantOfShape.cpp b/python_binding/operator/pybind_ConstantOfShape.cpp
new file mode 100644
index 000000000..b0d5ef2ef
--- /dev/null
+++ b/python_binding/operator/pybind_ConstantOfShape.cpp
@@ -0,0 +1,44 @@
+
+/********************************************************************************
+ * 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 <pybind11/pybind11.h>
+#include <string>
+#include <vector>
+
+#include "aidge/data/Tensor.hpp"
+#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ConstantOfShape.hpp"
+
+namespace py = pybind11;
+namespace Aidge {
+
+void init_ConstantOfShape(py::module &m) {
+  py::class_<ConstantOfShape_Op, std::shared_ptr<ConstantOfShape_Op>, OperatorTensor>(
+      m, "ConstantOfShapeOp", py::multiple_inheritance())
+      // Here we bind the methods of the Unsqueeze_Op that wil want to access
+      .def("get_inputs_name", &ConstantOfShape_Op::getInputsName)
+      .def("get_outputs_name", &ConstantOfShape_Op::getOutputsName)
+      .def("value", &ConstantOfShape_Op::value);
+  // Here we bind the constructor of the ConstantOfShape Node. We add an argument for
+  // each attribute of the operator (in here we only have 'axes') and the last
+  // argument is the node's name.
+  m.def("ConstantOfShape", &ConstantOfShape, py::arg("value") = Tensor(0.f),
+        py::arg("name") = "",
+        R"mydelimiter(
+    Initialize a node containing an constantOfShape operator.
+	:param value : tensor with a given datatype that contains the value that will fill the output tensor
+	:type value  : :py:class: Tensor
+    :param name  : name of the node.
+)mydelimiter");
+}
+} // namespace Aidge
+
diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp
index 7c9ac168f..616a8424b 100644
--- a/python_binding/pybind_core.cpp
+++ b/python_binding/pybind_core.cpp
@@ -33,6 +33,7 @@ void init_ArgMax(py::module&);
 void init_AvgPooling(py::module&);
 void init_BatchNorm(py::module&);
 void init_Concat(py::module&);
+void init_ConstantOfShape(py::module&);
 void init_Conv(py::module&);
 void init_ConvDepthWise(py::module&);
 void init_Div(py::module&);
@@ -113,6 +114,7 @@ void init_Aidge(py::module& m) {
     init_Concat(m);
     init_Conv(m);
     init_ConvDepthWise(m);
+    init_ConstantOfShape(m);
     init_Div(m);
     init_Erf(m);
     init_FC(m);
diff --git a/src/operator/ConstantOfShape.cpp b/src/operator/ConstantOfShape.cpp
new file mode 100644
index 000000000..4c245d27d
--- /dev/null
+++ b/src/operator/ConstantOfShape.cpp
@@ -0,0 +1,68 @@
+/********************************************************************************
+ * 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/ConstantOfShape.hpp"
+
+#include <cstdint>
+#include <fmt/format.h>
+#include <memory>
+#include <stdexcept> // std::runtime_error
+#include <string>
+#include <vector>
+
+#include "aidge/data/Data.hpp"
+#include "aidge/data/Tensor.hpp"
+#include "aidge/data/half.hpp"
+#include "aidge/utils/ErrorHandling.hpp"
+#include "aidge/utils/Types.h"
+
+namespace Aidge {
+
+const std::string ConstantOfShape_Op::Type = "ConstantOfShape";
+
+bool ConstantOfShape_Op::forwardDims(bool allowDataDependency) {
+  if (!inputsAssociated()) {
+    return false;
+  }
+
+  if (!allowDataDependency) {
+    Log::warn("{} : unable to forwardDims() because output dims are data "
+              "dependent on input#0",
+              type());
+    return false;
+  }
+
+  AIDGE_ASSERT(getInput(0)->nbDims() == 1,
+               "{} : Input tensor should have only 1 dimension. {} dimensions"
+               "received : {}",
+               __func__, getInput(0)->nbDims(), getInput(0)->dims());
+  AIDGE_ASSERT(getInput(0)->dataType() == DataType::Int64,
+               "{} : Input tensor data type should be int64t, received : {}",
+               __func__, getInput(0)->nbDims(), getInput(0)->dims());
+  std::vector<DimSize_t> output_dims;
+  output_dims.reserve(getInput(0)->size());
+  for (std::size_t i = 0; i < getInput(0)->size(); ++i) {
+    auto temp = getInput(0)->template get<std::int64_t>(i);
+    output_dims.push_back(temp);
+  }
+  mOutputs[0]->resize(output_dims);
+  return true;
+}
+
+void ConstantOfShape_Op::setBackend(const std::string &name,
+                                       Aidge::DeviceIdx_t device) {
+  SET_IMPL_MACRO(ConstantOfShape_Op, *this, name);
+  mOutputs[0]->setBackend(name, device);
+  value().setBackend(name,device);
+}
+
+} // namespace Aidge
+
diff --git a/unit_tests/operator/Test_ConstantOfShape.cpp b/unit_tests/operator/Test_ConstantOfShape.cpp
new file mode 100644
index 000000000..c10d97ce5
--- /dev/null
+++ b/unit_tests/operator/Test_ConstantOfShape.cpp
@@ -0,0 +1,85 @@
+/********************************************************************************
+ * 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 <algorithm>
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/generators/catch_generators_random.hpp>
+#include <cstddef> // std::size_t
+#include <cstdint>
+#include <functional>
+#include <memory>
+#include <numeric>
+#include <random> // std::mt19937, std::uniform_int_distribution
+#include <system_error>
+#include <vector>
+
+#include "aidge/data/Data.hpp"
+#include "aidge/data/Tensor.hpp"
+#include "aidge/filler/Filler.hpp"
+#include "aidge/operator/ConstantOfShape.hpp"
+#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/utils/Types.h"
+
+namespace Aidge {
+TEST_CASE("[core/operator] ConstantOfShape_Op(forwardDims)",
+          "[ConstantOfShape][forwardDims]") {
+  constexpr std::uint16_t NBTRIALS = 10;
+
+  // Create a random number generator
+  auto random_seed = Catch::Generators::Detail::getSeed;
+  std::mt19937 gen(random_seed());
+  std::uniform_int_distribution<std::size_t> input_tensor_dims_dist(1, 10);
+  std::uniform_int_distribution<std::size_t> input_tensor_value_dist(1, 9);
+  std::uniform_real_distribution<float> op_value_attr_value_dist(1, 10000);
+
+  std::uniform_int_distribution<std::size_t> op_value_attr_type_dist(
+      0, static_cast<int>(Aidge::DataType::UInt64));
+  // TENSORS
+  std::shared_ptr<Tensor> input_T = std::make_shared<Tensor>();
+  input_T->setDataType(Aidge::DataType::Int64);
+  input_T->setBackend("cpu");
+
+  SECTION("operator test") {
+    // Create Operator
+    for (int i = 0; i < NBTRIALS; ++i) {
+      std::shared_ptr<Node> node =
+          ConstantOfShape(Tensor(op_value_attr_value_dist(gen)));
+      auto op =
+          std::static_pointer_cast<ConstantOfShape_Op>(node->getOperator());
+      op->associateInput(0, input_T);
+
+      std::vector<DimSize_t> input_dims;
+      input_dims.push_back(input_tensor_dims_dist(gen));
+
+      Log::setConsoleLevel(Log::Debug);
+      int input_nb_elems = input_dims.at(0);
+      int output_nb_elems = 1;
+      int64_t *array_in = new int64_t[input_nb_elems];
+      for (std::size_t i = 0; i < input_nb_elems; ++i) {
+        std::int64_t val = input_tensor_value_dist(gen);
+        array_in[i] = val;
+        output_nb_elems *= val;
+      }
+
+      input_T->resize(input_dims);
+      op->setInput(0, input_T);
+      input_T->getImpl()->setRawPtr(array_in, input_nb_elems);
+
+      REQUIRE(op->forwardDims(true));
+      REQUIRE(input_T->size() == op->getOutput(0)->nbDims());
+      for (DimSize_t i = 0; i < op->getOutput(0)->nbDims(); ++i) {
+        CHECK(array_in[i] == op->getOutput(0)->dims().at(i));
+      }
+    }
+  }
+}
+} // namespace Aidge
+
-- 
GitLab