From a1ca1f8dad00714a0ec1f56a9e403bcd5299efc3 Mon Sep 17 00:00:00 2001
From: hrouis <houssemeddine.rouis92@gmail.com>
Date: Fri, 26 Jan 2024 11:41:39 +0100
Subject: [PATCH] add ArithmeticOperator class

---
 include/aidge/operator/ArithmeticOperator.hpp | 112 ++++++++++
 include/aidge/operator/Div.hpp                |  20 +-
 include/aidge/operator/Mul.hpp                |  19 +-
 include/aidge/operator/Pow.hpp                |  20 +-
 include/aidge/operator/Sub.hpp                |  20 +-
 python_binding/operator/pybind_Div.cpp        |   4 +-
 python_binding/operator/pybind_Mul.cpp        |   4 +-
 python_binding/operator/pybind_Pow.cpp        |   4 +-
 python_binding/operator/pybind_Sub.cpp        |   4 +-
 src/operator/ArithmeticOperator.cpp           | 192 ++++++++++++++++++
 src/operator/Div.cpp                          |  17 +-
 src/operator/Mul.cpp                          |  17 +-
 src/operator/Pow.cpp                          |  17 +-
 src/operator/Sub.cpp                          |  17 +-
 14 files changed, 332 insertions(+), 135 deletions(-)
 create mode 100644 include/aidge/operator/ArithmeticOperator.hpp
 create mode 100644 src/operator/ArithmeticOperator.cpp

diff --git a/include/aidge/operator/ArithmeticOperator.hpp b/include/aidge/operator/ArithmeticOperator.hpp
new file mode 100644
index 000000000..d2a0bfb77
--- /dev/null
+++ b/include/aidge/operator/ArithmeticOperator.hpp
@@ -0,0 +1,112 @@
+/********************************************************************************
+ * Copyright (c) 2024 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_ARITHMETICOPERATOR_H_
+#define AIDGE_CORE_OPERATOR_ARITHMETICOPERATOR_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "aidge/backend/OperatorImpl.hpp"
+#include "aidge/data/Tensor.hpp"
+#include "aidge/operator/Operator.hpp"
+#include "aidge/utils/Types.h"
+
+namespace Aidge {
+
+class ArithmeticOperator : public Operator {
+    /* TODO: Add an attribute specifying the type of Data used by the Operator.
+     * The same way ``Type`` attribute specifies the type of Operator. Hence this
+     * attribute could be checked in the forwardDims function to assert Operators
+     * being used work with Tensors and cast them to OpertorTensor instead of
+     * Operator.
+     */
+    /* TODO: Maybe change type attribute of Data object by an enum instead of an
+     * array of char. Faster comparisons.
+     */
+protected:
+    std::vector<std::shared_ptr<Tensor>> mInputs;
+    std::vector<std::shared_ptr<Tensor>> mOutputs;
+
+public:
+    ArithmeticOperator() = delete;
+
+    ArithmeticOperator(const std::string& type)
+        : Operator(type, 2, 0, 1, OperatorType::Tensor),
+          mInputs(std::vector<std::shared_ptr<Tensor>>(2, nullptr)),
+          mOutputs(std::vector<std::shared_ptr<Tensor>>(1)) {
+        mOutputs[0] = std::make_shared<Tensor>();
+        mOutputs[0]->setDataType(DataType::Float32);
+    }
+
+    ArithmeticOperator(const ArithmeticOperator& other)
+        : Operator(other),
+          mInputs(std::vector<std::shared_ptr<Tensor>>(2, nullptr)),
+          mOutputs(std::vector<std::shared_ptr<Tensor>>(1)) {
+        mOutputs[0] = std::make_shared<Tensor>();
+    }
+
+    ~ArithmeticOperator();
+
+public:
+    ///////////////////////////////////////////////////
+    virtual void associateInput(const IOIndex_t inputIdx,
+                                const std::shared_ptr<Data>& data) override;
+    ///////////////////////////////////////////////////
+
+    ///////////////////////////////////////////////////
+    // Tensor access
+    // input management
+    void setInput(const IOIndex_t inputIdx, const std::shared_ptr<Data>& data) override final;
+    void setInput(const IOIndex_t inputIdx, std::shared_ptr<Data>&& data) override final;
+    const std::shared_ptr<Tensor>& getInput(const IOIndex_t inputIdx) const;
+    inline std::shared_ptr<Data> getRawInput(const IOIndex_t inputIdx) const override final {
+        return std::static_pointer_cast<Data>(getInput(inputIdx));
+    }
+
+    // output management
+    void setOutput(const IOIndex_t outputIdx, const std::shared_ptr<Data>& data) override;
+    void setOutput(const IOIndex_t outputIdx, std::shared_ptr<Data>&& data) override;
+    virtual const std::shared_ptr<Tensor>& getOutput(const IOIndex_t outputIdx) const;
+    inline std::shared_ptr<Aidge::Data> getRawOutput(const Aidge::IOIndex_t outputIdx) const override final {
+        return std::static_pointer_cast<Data>(getOutput(outputIdx));
+    }
+    
+    static const std::vector<std::string> getInputsName(){
+        return {"data_input1", "data_input2"};
+    }
+    static const std::vector<std::string> getOutputsName(){
+        return {"data_output"};
+    }
+    ///////////////////////////////////////////////////
+
+    ///////////////////////////////////////////////////
+    // Tensor dimensions
+    /**
+     * @brief For a given output feature area, compute the associated receptive
+     * field for each data input.
+     * @param firstIdx First index of the output feature.
+     * @param outputDims Size of output feature.
+     * @param outputIdx Index of the output. Default 0.
+     * @return std::vector<std::pair<std::size_t, std::vector<DimSize_t>>>
+     * For each dataInput Tensor of the Operator, the first index and dimensions of the feature area.
+     */
+    virtual std::vector<std::pair<std::vector<Aidge::DimSize_t>, std::vector<DimSize_t>>> computeReceptiveField(const std::vector<DimSize_t>& firstEltDims, const std::vector<DimSize_t>& outputDims, const IOIndex_t outputIdx = 0) const;
+    virtual void computeOutputDims();
+    virtual bool outputDimsForwarded() const;
+    ///////////////////////////////////////////////////
+
+    virtual void setDataType(const DataType& dataType) const override;
+};
+}  // namespace Aidge
+
+#endif  // AIDGE_CORE_OPERATOR_ARITHMETICOPERATOR_H_
\ No newline at end of file
diff --git a/include/aidge/operator/Div.hpp b/include/aidge/operator/Div.hpp
index 94b755e0f..ec4319e6b 100644
--- a/include/aidge/operator/Div.hpp
+++ b/include/aidge/operator/Div.hpp
@@ -17,7 +17,7 @@
 #include <vector>
 
 #include "aidge/utils/Registrar.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/data/Tensor.hpp"
 #include "aidge/graph/Node.hpp"
@@ -25,21 +25,19 @@
 
 namespace Aidge {
 
-class Div_Op : public OperatorTensor,
+class Div_Op : public ArithmeticOperator,
     public Registrable<Div_Op, std::string, std::unique_ptr<OperatorImpl>(const Div_Op&)> {
 
 public:
     static const std::string Type;
 
-    Div_Op() : OperatorTensor(Type, 2, 0, 1) {}
+    Div_Op() : ArithmeticOperator(Type) {}
 
     /**
      * @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.
      */
-    Div_Op(const Div_Op& op)
-        : OperatorTensor(op)
-    {
+    Div_Op(const Div_Op& op) : ArithmeticOperator(op){
         mImpl = op.mImpl ? Registrar<Div_Op>::create(op.mOutputs[0]->getImpl()->backend())(*this) : nullptr;
     }
 
@@ -51,20 +49,10 @@ public:
         return std::make_shared<Div_Op>(*this);
     }
 
-    void computeOutputDims() override final;
-
-
     void setBackend(const std::string& name, DeviceIdx_t device = 0) override {
         mImpl = Registrar<Div_Op>::create(name)(*this);
         mOutputs[0]->setBackend(name, device);
     }
-
-    static const std::vector<std::string> getInputsName(){
-        return {"data_input"};
-    }
-    static const std::vector<std::string> getOutputsName(){
-        return {"data_output"};
-    }
 };
 
 inline std::shared_ptr<Node> Div(const std::string& name = "") {
diff --git a/include/aidge/operator/Mul.hpp b/include/aidge/operator/Mul.hpp
index 78b2fa5f9..2a1ad1d5b 100644
--- a/include/aidge/operator/Mul.hpp
+++ b/include/aidge/operator/Mul.hpp
@@ -17,7 +17,7 @@
 #include <vector>
 
 #include "aidge/utils/Registrar.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/data/Tensor.hpp"
 #include "aidge/graph/Node.hpp"
@@ -28,21 +28,19 @@ namespace Aidge {
 /**
  * @brief Tensor element-wise multiplication.
  */
-class Mul_Op : public OperatorTensor,
+class Mul_Op : public ArithmeticOperator,
     public Registrable<Mul_Op, std::string, std::unique_ptr<OperatorImpl>(const Mul_Op&)> {
 public:
     static const std::string Type;
 
-    Mul_Op() : OperatorTensor(Type, 2, 0, 1) {}
+    Mul_Op() : ArithmeticOperator(Type) {}
 
     /**
      * @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.
      */
-    Mul_Op(const Mul_Op& op)
-        : OperatorTensor(op)
-    {
+    Mul_Op(const Mul_Op& op) : ArithmeticOperator(op){
         mImpl = op.mImpl ? Registrar<Mul_Op>::create(op.mOutputs[0]->getImpl()->backend())(*this) : nullptr;
     }
 
@@ -54,19 +52,10 @@ public:
         return std::make_shared<Mul_Op>(*this);
     }
 
-    void computeOutputDims() override final;
-
     void setBackend(const std::string& name, DeviceIdx_t device = 0) override {
         mImpl = Registrar<Mul_Op>::create(name)(*this);
         mOutputs[0]->setBackend(name, device);
     }
-
-    static const std::vector<std::string> getInputsName(){
-        return {"data_input"};
-    }
-    static const std::vector<std::string> getOutputsName(){
-        return {"data_output"};
-    }
 };
 
 inline std::shared_ptr<Node> Mul(const std::string& name = "") {
diff --git a/include/aidge/operator/Pow.hpp b/include/aidge/operator/Pow.hpp
index d498cacc7..f1bd3ad51 100644
--- a/include/aidge/operator/Pow.hpp
+++ b/include/aidge/operator/Pow.hpp
@@ -17,7 +17,7 @@
 #include <vector>
 
 #include "aidge/utils/Registrar.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/data/Tensor.hpp"
 #include "aidge/data/Data.hpp"
@@ -26,20 +26,18 @@
 
 namespace Aidge {
 
-class Pow_Op : public OperatorTensor,
+class Pow_Op : public ArithmeticOperator,
     public Registrable<Pow_Op, std::string, std::unique_ptr<OperatorImpl>(const Pow_Op&)> {
 public:
     static const std::string Type;
 
-    Pow_Op() : OperatorTensor(Type, 2, 0, 1) {}
+    Pow_Op() : ArithmeticOperator(Type) {}
 
     /**
      * @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.
      */
-    Pow_Op(const Pow_Op& op)
-        : OperatorTensor(op)
-    {
+    Pow_Op(const Pow_Op& op) : ArithmeticOperator(op){
         mImpl = op.mImpl ? Registrar<Pow_Op>::create(op.mOutputs[0]->getImpl()->backend())(*this) : nullptr;
     }
 
@@ -51,20 +49,10 @@ public:
         return std::make_shared<Pow_Op>(*this);
     }
 
-    void computeOutputDims() override final;
-
-
     void setBackend(const std::string& name, DeviceIdx_t device = 0) override {
         mImpl = Registrar<Pow_Op>::create(name)(*this);
         mOutputs[0]->setBackend(name, device);
     }
-
-    static const std::vector<std::string> getInputsName(){
-        return {"data_input"};
-    }
-    static const std::vector<std::string> getOutputsName(){
-        return {"data_output"};
-    }
 };
 
 inline std::shared_ptr<Node> Pow(const std::string& name = "") {
diff --git a/include/aidge/operator/Sub.hpp b/include/aidge/operator/Sub.hpp
index ee5efa24d..d00e9f5f0 100644
--- a/include/aidge/operator/Sub.hpp
+++ b/include/aidge/operator/Sub.hpp
@@ -17,7 +17,7 @@
 #include <vector>
 
 #include "aidge/utils/Registrar.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/data/Tensor.hpp"
 #include "aidge/data/Data.hpp"
@@ -26,7 +26,7 @@
 
 namespace Aidge {
 
-class Sub_Op : public OperatorTensor,
+class Sub_Op : public ArithmeticOperator,
     public Registrable<Sub_Op, std::string, std::unique_ptr<OperatorImpl>(const Sub_Op&)> {
 public:
     // FIXME: change accessibility
@@ -36,15 +36,13 @@ public:
 public:
     static const std::string Type;
 
-    Sub_Op() : OperatorTensor(Type, 2, 0, 1) {}
+    Sub_Op() : ArithmeticOperator(Type) {}
 
     /**
      * @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.
      */
-    Sub_Op(const Sub_Op& op)
-        : OperatorTensor(op)
-    {
+    Sub_Op(const Sub_Op& op) : ArithmeticOperator(op){
         mImpl = op.mImpl ? Registrar<Sub_Op>::create(op.mOutputs[0]->getImpl()->backend())(*this) : nullptr;
     }
 
@@ -56,20 +54,10 @@ public:
         return std::make_shared<Sub_Op>(*this);
     }
 
-    void computeOutputDims() override final;
-
-
     void setBackend(const std::string& name, DeviceIdx_t device = 0) override {
         mImpl = Registrar<Sub_Op>::create(name)(*this);
         mOutputs[0]->setBackend(name, device);
     }
-
-    static const std::vector<std::string> getInputsName(){
-        return {"data_input"};
-    }
-    static const std::vector<std::string> getOutputsName(){
-        return {"data_output"};
-    }
 };
 
 inline std::shared_ptr<Node> Sub(const std::string& name = "") {
diff --git a/python_binding/operator/pybind_Div.cpp b/python_binding/operator/pybind_Div.cpp
index 6d14510f3..ff9933cdc 100644
--- a/python_binding/operator/pybind_Div.cpp
+++ b/python_binding/operator/pybind_Div.cpp
@@ -12,13 +12,13 @@
 #include <pybind11/pybind11.h>
 
 #include "aidge/operator/Div.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
 
 void init_Div(py::module& m) {
-    py::class_<Div_Op, std::shared_ptr<Div_Op>, OperatorTensor>(m, "DivOp", py::multiple_inheritance())
+    py::class_<Div_Op, std::shared_ptr<Div_Op>, ArithmeticOperator>(m, "DivOp", py::multiple_inheritance())
     .def("get_inputs_name", &Div_Op::getInputsName)
     .def("get_outputs_name", &Div_Op::getOutputsName);
 
diff --git a/python_binding/operator/pybind_Mul.cpp b/python_binding/operator/pybind_Mul.cpp
index 21f510d98..7ad55c3ad 100644
--- a/python_binding/operator/pybind_Mul.cpp
+++ b/python_binding/operator/pybind_Mul.cpp
@@ -12,13 +12,13 @@
 #include <pybind11/pybind11.h>
 
 #include "aidge/operator/Mul.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
 
 void init_Mul(py::module& m) {
-    py::class_<Mul_Op, std::shared_ptr<Mul_Op>, OperatorTensor>(m, "MulOp", py::multiple_inheritance())
+    py::class_<Mul_Op, std::shared_ptr<Mul_Op>, ArithmeticOperator>(m, "MulOp", py::multiple_inheritance())
     .def("get_inputs_name", &Mul_Op::getInputsName)
     .def("get_outputs_name", &Mul_Op::getOutputsName);
 
diff --git a/python_binding/operator/pybind_Pow.cpp b/python_binding/operator/pybind_Pow.cpp
index 09d1e4ad2..7bc8f05a8 100644
--- a/python_binding/operator/pybind_Pow.cpp
+++ b/python_binding/operator/pybind_Pow.cpp
@@ -12,13 +12,13 @@
 #include <pybind11/pybind11.h>
 
 #include "aidge/operator/Pow.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
 
 void init_Pow(py::module& m) {
-    py::class_<Pow_Op, std::shared_ptr<Pow_Op>, OperatorTensor>(m, "PowOp", py::multiple_inheritance())
+    py::class_<Pow_Op, std::shared_ptr<Pow_Op>, ArithmeticOperator>(m, "PowOp", py::multiple_inheritance())
     .def("get_inputs_name", &Pow_Op::getInputsName)
     .def("get_outputs_name", &Pow_Op::getOutputsName);
 
diff --git a/python_binding/operator/pybind_Sub.cpp b/python_binding/operator/pybind_Sub.cpp
index dce1ab6cb..c2670389e 100644
--- a/python_binding/operator/pybind_Sub.cpp
+++ b/python_binding/operator/pybind_Sub.cpp
@@ -12,13 +12,13 @@
 #include <pybind11/pybind11.h>
 
 #include "aidge/operator/Sub.hpp"
-#include "aidge/operator/OperatorTensor.hpp"
+#include "aidge/operator/ArithmeticOperator.hpp"
 
 namespace py = pybind11;
 namespace Aidge {
 
 void init_Sub(py::module& m) {
-    py::class_<Sub_Op, std::shared_ptr<Sub_Op>, OperatorTensor>(m, "SubOp", py::multiple_inheritance())
+    py::class_<Sub_Op, std::shared_ptr<Sub_Op>, ArithmeticOperator>(m, "SubOp", py::multiple_inheritance())
     .def("get_inputs_name", &Sub_Op::getInputsName)
     .def("get_outputs_name", &Sub_Op::getOutputsName);
 
diff --git a/src/operator/ArithmeticOperator.cpp b/src/operator/ArithmeticOperator.cpp
new file mode 100644
index 000000000..b2ea9dd12
--- /dev/null
+++ b/src/operator/ArithmeticOperator.cpp
@@ -0,0 +1,192 @@
+/********************************************************************************
+ * 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 <cassert>
+#include <memory>
+
+#include "aidge/operator/ArithmeticOperator.hpp"
+#include "aidge/data/Data.hpp"
+#include "aidge/data/Tensor.hpp"
+#include "aidge/utils/Types.h"
+#include "aidge/utils/ErrorHandling.hpp"
+
+
+void Aidge::ArithmeticOperator::associateInput(const Aidge::IOIndex_t inputIdx, const std::shared_ptr<Aidge::Data>& data) {
+    if (inputIdx >= 2) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator has %hu inputs", type().c_str(),2);
+    }
+    if (strcmp((data)->type(), Tensor::Type) != 0) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "Input data must be of Tensor type");
+    }
+        mInputs[inputIdx] = std::dynamic_pointer_cast<Tensor>(data);
+}
+
+void Aidge::ArithmeticOperator::setInput(const Aidge::IOIndex_t inputIdx, const std::shared_ptr<Aidge::Data>& data) {
+    if (strcmp(data->type(), "Tensor") != 0) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator only accepts Tensors as inputs", type().c_str());
+    }
+    if (getInput(inputIdx)) {
+        *mInputs[inputIdx] = *std::dynamic_pointer_cast<Tensor>(data);
+    } else {
+        mInputs[inputIdx] = std::make_shared<Tensor>(*std::dynamic_pointer_cast<Tensor>(data));
+    }
+}
+
+Aidge::ArithmeticOperator::~ArithmeticOperator() = default;
+
+void Aidge::ArithmeticOperator::setInput(const Aidge::IOIndex_t inputIdx, std::shared_ptr<Aidge::Data>&& data) {
+    if (strcmp(data->type(), "Tensor") != 0) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator only accepts Tensors as inputs", type().c_str());
+    }
+    if (getInput(inputIdx)) {
+        *mInputs[inputIdx] = std::move(*std::dynamic_pointer_cast<Tensor>(data));
+    } else {
+        mInputs[inputIdx] = std::make_shared<Tensor>(std::move(*std::dynamic_pointer_cast<Tensor>(data)));
+    }
+}
+
+const std::shared_ptr<Aidge::Tensor>& Aidge::ArithmeticOperator::getInput(const Aidge::IOIndex_t inputIdx) const {
+    if (inputIdx >= nbInputs()) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator has %hu inputs", type().c_str(), nbInputs());
+    }
+    return mInputs[inputIdx];
+}
+
+void Aidge::ArithmeticOperator::setOutput(const Aidge::IOIndex_t outputIdx, const std::shared_ptr<Aidge::Data>& data) {
+    if (strcmp(data->type(), "Tensor") != 0) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator only accepts Tensors as inputs", type().c_str());
+    }
+    if (outputIdx >= nbOutputs()) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator has %hu outputs", type().c_str(), nbOutputs());
+    }
+    *mOutputs[outputIdx] = *std::dynamic_pointer_cast<Tensor>(data);
+}
+
+void Aidge::ArithmeticOperator::setOutput(const Aidge::IOIndex_t outputIdx, std::shared_ptr<Aidge::Data>&& data) {
+    if (strcmp(data->type(), "Tensor") != 0) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator only accepts Tensors as inputs", type().c_str());
+    }
+    if (outputIdx >= nbOutputs()) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator has %hu outputs", type().c_str(), nbOutputs());
+    }
+    *mOutputs[outputIdx] = std::move(*std::dynamic_pointer_cast<Tensor>(data));
+}
+
+const std::shared_ptr<Aidge::Tensor>& Aidge::ArithmeticOperator::getOutput(const Aidge::IOIndex_t outputIdx) const {
+    if (outputIdx >= nbOutputs()) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "%s Operator has %hu outputs", type().c_str(), nbOutputs());
+    }
+    return mOutputs[outputIdx];
+}
+
+
+std::vector<std::pair<std::vector<Aidge::DimSize_t>, std::vector<Aidge::DimSize_t>>> Aidge::ArithmeticOperator::computeReceptiveField(
+        const std::vector<DimSize_t>& firstEltDims,
+        const std::vector<Aidge::DimSize_t>& outputDims,
+        const Aidge::IOIndex_t outputIdx) const
+{
+    static_cast<void>(outputIdx);
+    if (outputIdx >= nbOutputs()) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "Operator output index out of range.");
+    }
+    if (nbInputs() != nbData()) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "Operator has attributes. Must be handled in an overrided function.");
+    }
+    if (!outputDimsForwarded() || getOutput(0)->nbDims() != outputDims.size()) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "Given outputDim out of range or output dim not forwarded yet.");
+    }
+    for (DimIdx_t i = 0; i < outputDims.size(); ++i) {
+        if (((outputDims[i] + firstEltDims[i]) > getOutput(0)->dims()[i]) || (outputDims[i] == 0)) {
+            AIDGE_THROW_OR_ABORT(std::runtime_error, "Given outputDim out of range for dimension %lu (%lu + %lu)", static_cast<std::size_t>(i), firstEltDims[i], outputDims[i]);
+        }
+    }
+    // return the same Tensor description as given in function parameter for each data input
+    return std::vector<std::pair<std::vector<Aidge::DimSize_t>, std::vector<Aidge::DimSize_t>>>(nbData(),std::pair<std::vector<Aidge::DimSize_t>, std::vector<Aidge::DimSize_t>>(firstEltDims, outputDims));
+}
+
+void Aidge::ArithmeticOperator::computeOutputDims() {
+    // check inputs have been associated
+    if (!getInput(0) || !getInput(1)) {
+        AIDGE_THROW_OR_ABORT(std::runtime_error, "At least one input was not connected");
+    }
+
+    // if (getInput(0)->empty() || getInput(1)->empty()) {
+    //     AIDGE_THROW_OR_ABORT(std::runtime_error, "At least one input is empty");
+    // }
+
+    std::vector<std::vector<std::size_t>> inputsDims;
+    for (std::size_t i = 0; i < nbInputs(); i++)
+    {
+        inputsDims.push_back(getInput(i)->dims());
+    }
+
+    std::size_t outNbDims = 1;
+
+    for(size_t i=0; i<inputsDims.size() ; ++i)
+        outNbDims = inputsDims[i].size()>outNbDims?inputsDims[i].size():outNbDims;
+
+    std::vector<std::size_t> outDims(outNbDims, 1);
+
+    std::vector<std::size_t>::iterator it = outDims.end();
+    while (it != outDims.begin())
+    {
+        --it;
+        for (size_t i = 0; i < inputsDims.size(); i++)
+        {
+            if(!inputsDims[i].empty())
+            {
+                std::size_t dim = inputsDims[i].back();
+                inputsDims[i].pop_back();
+                if (*it != dim)
+                {
+                    if(dim != 1)
+                    {
+                        if (*it != 1)
+                        {
+                            AIDGE_THROW_OR_ABORT(std::runtime_error, "Unsopported Tensor shape for Arithmetic Operation");
+                        }
+                        else
+                        {
+                            *it = dim;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    mOutputs[0]->resize(outDims);
+}
+
+bool Aidge::ArithmeticOperator::outputDimsForwarded() const {
+    bool forwarded = true;
+    // check both inputs and outputs have been filled
+    for (IOIndex_t i = 0; i < nbInputs(); ++i) {
+        forwarded &= mInputs[i] ? !(getInput(i)->empty()) : false;
+    }
+    for (IOIndex_t i = 0; i < nbOutputs(); ++i) {
+        forwarded &= !(getOutput(i)->empty());
+    }
+    return forwarded;
+}
+
+void Aidge::ArithmeticOperator::setDataType(const DataType& dataType) const {
+    for (IOIndex_t i = 0; i < nbOutputs(); ++i) {
+        getOutput(i)->setDataType(dataType);
+    }
+    for (IOIndex_t i = 0; i < nbInputs(); ++i) {
+        if (!getInput(i)) {
+            AIDGE_THROW_OR_ABORT(std::runtime_error, "Input was not set");
+        }
+        else {
+            getInput(i)->setDataType(dataType);
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/operator/Div.cpp b/src/operator/Div.cpp
index 85db3ac6e..e5fe78b31 100644
--- a/src/operator/Div.cpp
+++ b/src/operator/Div.cpp
@@ -20,19 +20,4 @@
 #include "aidge/utils/Types.h"
 #include "aidge/utils/ErrorHandling.hpp"
 
-const std::string Aidge::Div_Op::Type = "Div";
-
-void Aidge::Div_Op::computeOutputDims() {
-    // check inputs have been associated
-    if (!getInput(0) || !getInput(1)) {
-        AIDGE_THROW_OR_ABORT(std::runtime_error, "At least one input was not connected");
-    }
-
-    if ((!getInput(0)->empty()) &&
-        ((getInput(1)->size() == 1) || // div by a single value
-        (getInput(1)->size() == getInput(0)->size()) || // div elem-wise
-        (getInput(1)->nbDims() == 1 && getInput(1)->size() == getInput(0)->dims()[getInput(0)->nbDims()-1]))) // div by a Tensor with one dimension of output size
-    {
-        mOutputs[0]->resize(getInput(0)->dims());
-    }
-}
\ No newline at end of file
+const std::string Aidge::Div_Op::Type = "Div";
\ No newline at end of file
diff --git a/src/operator/Mul.cpp b/src/operator/Mul.cpp
index bc268263e..4de84e45b 100644
--- a/src/operator/Mul.cpp
+++ b/src/operator/Mul.cpp
@@ -19,19 +19,4 @@
 #include "aidge/utils/Types.h"
 #include "aidge/utils/ErrorHandling.hpp"
 
-const std::string Aidge::Mul_Op::Type = "Mul";
-
-void Aidge::Mul_Op::computeOutputDims() {
-    // check inputs have been associated
-    if (!getInput(0) || !getInput(1)) {
-        AIDGE_THROW_OR_ABORT(std::runtime_error, "At least one input was not connected");
-    }
-
-    if ((!getInput(0)->empty()) &&
-        ((getInput(1)->size() == 1) || // mul by a single value
-        (getInput(1)->size() == getInput(0)->size()) || // mul elem-wise
-        (getInput(1)->nbDims() == 1 && getInput(1)->size() == getInput(0)->dims()[getInput(0)->nbDims()-1]))) // mul by a Tensor with one dimension of output size
-    {
-        mOutputs[0]->resize(getInput(0)->dims());
-    }
-}
\ No newline at end of file
+const std::string Aidge::Mul_Op::Type = "Mul";
\ No newline at end of file
diff --git a/src/operator/Pow.cpp b/src/operator/Pow.cpp
index de1f0c369..932b31e97 100644
--- a/src/operator/Pow.cpp
+++ b/src/operator/Pow.cpp
@@ -19,19 +19,4 @@
 #include "aidge/utils/Types.h"
 #include "aidge/utils/ErrorHandling.hpp"
 
-const std::string Aidge::Pow_Op::Type = "Pow";
-
-void Aidge::Pow_Op::computeOutputDims() {
-    // check inputs have been associated
-    if (!getInput(0) || !getInput(1)) {
-        AIDGE_THROW_OR_ABORT(std::runtime_error, "At least one input was not connected");
-    }
-
-    if ((!getInput(0)->empty()) &&
-        ((getInput(1)->size() == 1) || // pow by a single value
-        (getInput(1)->size() == getInput(0)->size()) || // pow elem-wise
-        (getInput(1)->nbDims() == 1 && getInput(1)->size() == getInput(0)->dims()[getInput(0)->nbDims()-1]))) // pow by a Tensor with one dimension of output size
-    {
-        mOutputs[0]->resize(getInput(0)->dims());
-    }
-}
\ No newline at end of file
+const std::string Aidge::Pow_Op::Type = "Pow";
\ No newline at end of file
diff --git a/src/operator/Sub.cpp b/src/operator/Sub.cpp
index 639eaf798..74f9e8ca1 100644
--- a/src/operator/Sub.cpp
+++ b/src/operator/Sub.cpp
@@ -19,19 +19,4 @@
 #include "aidge/utils/Types.h"
 #include "aidge/utils/ErrorHandling.hpp"
 
-const std::string Aidge::Sub_Op::Type = "Sub";
-
-void Aidge::Sub_Op::computeOutputDims() {
-    // check inputs have been associated
-    if (!getInput(0) || !getInput(1)) {
-        AIDGE_THROW_OR_ABORT(std::runtime_error, "At least one input was not connected");
-    }
-
-    if ((!getInput(0)->empty()) &&
-        ((getInput(1)->size() == 1) || // sub by a single value
-        (getInput(1)->size() == getInput(0)->size()) || // sub elem-wise
-        (getInput(1)->nbDims() == 1 && getInput(1)->size() == getInput(0)->dims()[getInput(0)->nbDims()-1]))) // sub by a Tensor with one dimension of output size
-    {
-        mOutputs[0]->resize(getInput(0)->dims());
-    }
-}
\ No newline at end of file
+const std::string Aidge::Sub_Op::Type = "Sub";
\ No newline at end of file
-- 
GitLab