diff --git a/aidge_core/unit_tests/test_operator_binding.py b/aidge_core/unit_tests/test_operator_binding.py
index c7279afed2aed00981d0b15002b1676abcaef72e..7bd1e730a973810db89aa786b52fa05c53c43590 100644
--- a/aidge_core/unit_tests/test_operator_binding.py
+++ b/aidge_core/unit_tests/test_operator_binding.py
@@ -107,8 +107,8 @@ class test_operator_binding(unittest.TestCase):
         class PythonCustomImpl(aidge_core.OperatorImpl):
             """Dummy implementation to test that C++ call python code
             """
-            def __init__(self):
-                aidge_core.OperatorImpl.__init__(self) # Recquired to avoid type error !
+            def __init__(self, op: aidge_core.Operator):
+                aidge_core.OperatorImpl.__init__(self, op) # Recquired to avoid type error !
                 self.idx = 0
 
             def forward(self):
@@ -117,8 +117,8 @@ class test_operator_binding(unittest.TestCase):
                 self.idx += 1
 
         generic_node = aidge_core.GenericOperator("Relu", 1, 1, 1, name="myReLu")
-        customImpl = PythonCustomImpl()
         generic_op = generic_node.get_operator()
+        customImpl = PythonCustomImpl(generic_op)
 
         generic_op.forward() # Do nothing, no implementation set
         generic_op.set_impl(customImpl)
diff --git a/include/aidge/backend/OperatorImpl.hpp b/include/aidge/backend/OperatorImpl.hpp
index 453e30a8636d86794c96723350bff615af090e3e..19f0837504016f38ae96dd852bc6fa41b5ab53ba 100644
--- a/include/aidge/backend/OperatorImpl.hpp
+++ b/include/aidge/backend/OperatorImpl.hpp
@@ -18,11 +18,13 @@
 #include "aidge/utils/Types.h"
 
 namespace Aidge {
+class Operator;
+
 class OperatorImpl {
 public:
-
-    virtual void forward(){};
-    virtual void backward(){};
+    OperatorImpl(const Operator& op);
+    virtual void forward();
+    virtual void backward();
 
     /**
      * @brief Minimum amount of data from a specific input required by the
@@ -31,13 +33,13 @@ public:
      * @param inputIdx Index of the input analysed.
      * @return std::size_t
      */
-    virtual NbElts_t getNbRequiredData(const IOIndex_t inputIdx) const = 0;
+    virtual NbElts_t getNbRequiredData(const IOIndex_t inputIdx) const;
 
     // Amount of input data that cannot be overwritten during the execution.
-    virtual NbElts_t getNbRequiredProtected(const IOIndex_t inputIdx) const = 0;
+    virtual NbElts_t getNbRequiredProtected(const IOIndex_t inputIdx) const;
 
     // Memory required at an output for a given input size.
-    virtual NbElts_t getRequiredMemory(const IOIndex_t outputIdx, const std::vector<DimSize_t> &inputsSize) const = 0;
+    virtual NbElts_t getRequiredMemory(const IOIndex_t outputIdx, const std::vector<DimSize_t> &inputsSize) const;
 
     /**
      * @brief Total amount of consumed data from a specific input.
@@ -45,7 +47,7 @@ public:
      * @param inputIdx Index of the input analysed.
      * @return DimSize_t
      */
-    virtual NbElts_t getNbConsumedData(const IOIndex_t inputIdx) const = 0;
+    virtual NbElts_t getNbConsumedData(const IOIndex_t inputIdx) const;
 
     /**
      * @brief Total amount of produced data ready to be used on a specific output.
@@ -53,15 +55,20 @@ public:
      * @param outputIdx Index of the output analysed.
      * @return DimSize_t
      */
-    virtual NbElts_t getNbProducedData(const IOIndex_t outputIdx) const = 0;
+    virtual NbElts_t getNbProducedData(const IOIndex_t outputIdx) const;
 
     /**
      * @brief Update the Consummer Producer system by simulating the consumption and production of i/o
      *
      */
-    virtual void updateConsummerProducer() = 0;
+    virtual void updateConsummerProducer();
 
     virtual ~OperatorImpl() = default;
+
+protected:
+    const Operator &mOp;
+    std::vector<NbElts_t> mNbConsumedData;
+    std::vector<NbElts_t> mNbProducedData;
 };
 } // namespace Aidge
 
diff --git a/include/aidge/operator/MaxPooling.hpp b/include/aidge/operator/MaxPooling.hpp
index b8708b03e419bf98e6e07b61e83d7734a0634332..053f4f4efbb7c976750d9c9a5c6659c38630d936 100644
--- a/include/aidge/operator/MaxPooling.hpp
+++ b/include/aidge/operator/MaxPooling.hpp
@@ -30,7 +30,8 @@ namespace Aidge
 enum class MaxPoolingAttr
 {
     StrideDims,
-    KernelDims
+    KernelDims,
+    CeilMode
 };
 
 template<DimIdx_t DIM>
@@ -42,7 +43,8 @@ class MaxPooling_Op : public Operator,
                       public StaticAttributes<
                           MaxPoolingAttr,
                           std::array<DimSize_t, DIM>,
-                          std::array<DimSize_t, DIM>>
+                          std::array<DimSize_t, DIM>,
+                          bool>
 {
 private:
     // FIXME: change accessibility
@@ -57,16 +59,19 @@ public:
     using Attributes_ = StaticAttributes<
         MaxPoolingAttr,
         std::array<DimSize_t, DIM>,
-        std::array<DimSize_t, DIM>>;
+        std::array<DimSize_t, DIM>,
+        bool>;
     template<MaxPoolingAttr e> using attr = typename Attributes_::template attr<e>;
 
     constexpr MaxPooling_Op(
         const std::array<DimSize_t, DIM> &kernel_dims,
-        const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t, DIM>(1)) :
+        const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t, DIM>(1),
+        bool ceil_mode = false) :
         Operator(Type),
         Attributes_(
             attr<MaxPoolingAttr::StrideDims>(stride_dims),
-            attr<MaxPoolingAttr::KernelDims>(kernel_dims)),
+            attr<MaxPoolingAttr::KernelDims>(kernel_dims),
+            attr<MaxPoolingAttr::CeilMode>(ceil_mode)),
         mOutput(std::make_shared<Tensor>())
     {
         setDatatype(DataType::Float32);
@@ -114,13 +119,23 @@ public:
         {
             std::array<DimSize_t, DIM + 2> outputDims = {};
 
+            std::function<float(float)> roundingFunction;
+            if (this->template getAttr<MaxPoolingAttr::CeilMode>())
+            {
+                roundingFunction = [](float x) { return std::ceil(x); };
+            }
+            else
+            {
+                roundingFunction = [](float x) { return std::floor(x); };
+            }
+
             for (std::size_t dim = 0;
                  dim < this->template getAttr<MaxPoolingAttr::KernelDims>().size();
                  ++dim)
             {
                 outputDims[dim + 2]
                     = 1
-                      + static_cast<DimSize_t>(std::floor(
+                      + static_cast<DimSize_t>(roundingFunction(
                           static_cast<float>(
                               mInput->dims()[dim + 2]
                               - this->template getAttr<MaxPoolingAttr::KernelDims>()[dim])
@@ -219,14 +234,15 @@ template<std::array<DimSize_t, 1>::size_type DIM>
 inline std::shared_ptr<Node> MaxPooling(
     const std::array<DimSize_t, DIM> &kernel_dims,
     const std::string &name = "",
-    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t, DIM>(1))
+    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t, DIM>(1),
+    bool ceil_mode = false)
 {
     static_assert(
-        DIM <= kMaxDim,
+        DIM <= MaxDim,
         "Too many kernel dimensions required by MaxPooling, not supported");
     return std::make_shared<Node>(
         std::make_shared<MaxPooling_Op<static_cast<DimIdx_t>(DIM)>>(
-            kernel_dims, stride_dims),
+            kernel_dims, stride_dims, ceil_mode),
         name);
 }
 
@@ -236,12 +252,13 @@ template<DimSize_t DIM>
 inline std::shared_ptr<Node> MaxPooling(
     DimSize_t const (&kernel_dims)[DIM],
     const std::string &name = "",
-    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t, DIM>(1))
+    const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t, DIM>(1),
+    bool ceil_mode = false)
 {
     static_assert(
-        DIM <= kMaxDim,
+        DIM <= MaxDim,
         "Too many kernel dimensions required by MaxPooling, not supported");
-    return MaxPooling(to_array(kernel_dims), name, stride_dims);
+    return MaxPooling(to_array(kernel_dims), name, stride_dims, ceil_mode);
 }
 } // namespace Aidge
 
@@ -249,7 +266,7 @@ namespace
 {
 template<>
 const char *const EnumStrings<Aidge::MaxPoolingAttr>::data[]
-    = {"StrideDims", "KernelDims"};
+    = {"StrideDims", "KernelDims", "CeilMode"};
 }
 
 #endif /* AIDGE_CORE_OPERATOR_MAXPOOLING_H_ */
diff --git a/include/aidge/operator/MetaOperatorDefs.hpp b/include/aidge/operator/MetaOperatorDefs.hpp
index 6da76c930a3f08358c8c09ce75e66109370e292a..73feb134837787ae8d0d280dd723182c9d21438b 100644
--- a/include/aidge/operator/MetaOperatorDefs.hpp
+++ b/include/aidge/operator/MetaOperatorDefs.hpp
@@ -115,11 +115,12 @@ template <std::array<DimSize_t, 1>::size_type DIM>
 inline std::shared_ptr<Node> PaddedMaxPooling(const std::array<DimSize_t, DIM> &kernel_dims,
                                   const std::string& name = "",
                                   const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-                                  const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0))
+                                  const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
+                                  bool ceil_mode = false)
 {
     auto graph = Sequential({
         Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : ""),
-        MaxPooling(kernel_dims, (!name.empty()) ? name + "_maxpooling" : "", stride_dims)
+        MaxPooling(kernel_dims, (!name.empty()) ? name + "_maxpooling" : "", stride_dims, ceil_mode)
     });
 
     return MetaOperator("PaddedMaxPooling", graph, name);
@@ -131,9 +132,10 @@ inline std::shared_ptr<Node> PaddedMaxPooling(
     DimSize_t const (&kernel_dims)[DIM],
     const std::string& name = "",
     const std::array<DimSize_t, DIM> &stride_dims = create_array<DimSize_t,DIM>(1),
-    const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0))
+    const std::array<DimSize_t, 2*DIM> &padding_dims = create_array<DimSize_t,2*DIM>(0),
+    bool ceil_mode= false)
 {
-    return PaddedMaxPooling(to_array(kernel_dims), name, stride_dims, padding_dims);
+    return PaddedMaxPooling(to_array(kernel_dims), name, stride_dims, padding_dims, ceil_mode);
 }
 }  // namespace Aidge
 
diff --git a/python_binding/backend/pybind_OperatorImpl.cpp b/python_binding/backend/pybind_OperatorImpl.cpp
index 8eb9e2649b19374e4346be18f9a3ab8070e4dc3c..34610069079ee792ebbe4b261b57177b3bbe2997 100644
--- a/python_binding/backend/pybind_OperatorImpl.cpp
+++ b/python_binding/backend/pybind_OperatorImpl.cpp
@@ -12,6 +12,7 @@
 #include <pybind11/pybind11.h>
 #include <pybind11/stl.h>
 
+#include "aidge/operator/Operator.hpp"
 #include "aidge/backend/OperatorImpl.hpp"
 
 namespace py = pybind11;
@@ -22,8 +23,8 @@ namespace Aidge {
  *
  */
 class pyOperatorImpl: public OperatorImpl {
-    public:
-    pyOperatorImpl(){}
+public:
+    using OperatorImpl::OperatorImpl; // Inherit constructors
 
     void forward() override {
         PYBIND11_OVERRIDE(
@@ -42,7 +43,7 @@ class pyOperatorImpl: public OperatorImpl {
         );
     }
     NbElts_t getNbRequiredData(const IOIndex_t inputIdx) const override {
-        PYBIND11_OVERRIDE_PURE_NAME(
+        PYBIND11_OVERRIDE_NAME(
             NbElts_t,
             OperatorImpl,
             "get_nb_required_data",
@@ -51,7 +52,7 @@ class pyOperatorImpl: public OperatorImpl {
         );
     }
     NbElts_t getNbRequiredProtected(const IOIndex_t inputIdx) const override {
-        PYBIND11_OVERRIDE_PURE_NAME(
+        PYBIND11_OVERRIDE_NAME(
             NbElts_t,
             OperatorImpl,
             "get_nb_required_protected",
@@ -62,7 +63,7 @@ class pyOperatorImpl: public OperatorImpl {
     }
     NbElts_t getRequiredMemory(const IOIndex_t outputIdx,
     const std::vector<DimSize_t> &inputsSize) const override {
-        PYBIND11_OVERRIDE_PURE_NAME(
+        PYBIND11_OVERRIDE_NAME(
             NbElts_t,
             OperatorImpl,
             "get_required_memory",
@@ -73,7 +74,7 @@ class pyOperatorImpl: public OperatorImpl {
         );
     }
     NbElts_t getNbConsumedData(const IOIndex_t inputIdx) const override {
-        PYBIND11_OVERRIDE_PURE_NAME(
+        PYBIND11_OVERRIDE_NAME(
             NbElts_t,
             OperatorImpl,
             "get_nb_consumed_data",
@@ -83,7 +84,7 @@ class pyOperatorImpl: public OperatorImpl {
         );
     }
     NbElts_t getNbProducedData(const IOIndex_t outputIdx) const override {
-        PYBIND11_OVERRIDE_PURE_NAME(
+        PYBIND11_OVERRIDE_NAME(
             NbElts_t,
             OperatorImpl,
             "get_nb_produced_data",
@@ -93,7 +94,7 @@ class pyOperatorImpl: public OperatorImpl {
         );
     }
     void updateConsummerProducer() override {
-        PYBIND11_OVERRIDE_PURE_NAME(
+        PYBIND11_OVERRIDE_NAME(
             void,
             OperatorImpl,
             "update_consummer_producer",
@@ -106,7 +107,7 @@ class pyOperatorImpl: public OperatorImpl {
 void init_OperatorImpl(py::module& m){
 
     py::class_<OperatorImpl, std::shared_ptr<OperatorImpl>, pyOperatorImpl>(m, "OperatorImpl", py::dynamic_attr())
-    .def(py::init<>())
+    .def(py::init<const Operator&>())
     .def("forward", &OperatorImpl::forward)
     .def("backward", &OperatorImpl::backward)
     .def("get_nb_required_data", &OperatorImpl::getNbRequiredData)
diff --git a/python_binding/operator/pybind_MaxPooling.cpp b/python_binding/operator/pybind_MaxPooling.cpp
index c83dfaa3639f05af345bd9214460f95fd661cd31..907e8cfaa6cde2451677b72beab38bd9a3938735 100644
--- a/python_binding/operator/pybind_MaxPooling.cpp
+++ b/python_binding/operator/pybind_MaxPooling.cpp
@@ -30,22 +30,26 @@ template <DimIdx_t DIM> void declare_MaxPoolingOp(py::module &m) {
     m, ("MaxPoolingOp" + std::to_string(DIM) + "D").c_str(),
     py::multiple_inheritance())
   .def(py::init<const std::array<DimSize_t, DIM> &,
-                const std::array<DimSize_t, DIM> &>(),
+                const std::array<DimSize_t, DIM> &,
+                bool>(),
         py::arg("kernel_dims"),
-        py::arg("stride_dims"))
+        py::arg("stride_dims"),
+        py::arg("ceil_mode"))
   .def("get_inputs_name", &MaxPooling_Op<DIM>::getInputsName)
   .def("get_outputs_name", &MaxPooling_Op<DIM>::getOutputsName);
 
   m.def(("MaxPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
                                                                   const std::string& name,
-                                                                  const std::vector<DimSize_t> &stride_dims) {
+                                                                  const std::vector<DimSize_t> &stride_dims,
+                                                                  bool ceil_mode) {
         AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
         AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
 
-        return MaxPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()));
+        return MaxPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), ceil_mode);
     }, py::arg("kernel_dims"),
        py::arg("name") = "",
-       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1));
+       py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
+       py::arg("ceil_mode") = false);
 
 }
 
@@ -55,8 +59,5 @@ void init_MaxPooling(py::module &m) {
   declare_MaxPoolingOp<2>(m);
   declare_MaxPoolingOp<3>(m);
 
-  // FIXME:
-  // m.def("MaxPooling1D", static_cast<NodeAPI(*)(const char*, int, int, int const
-  // (&)[1])>(&MaxPooling));
 }
 } // namespace Aidge
diff --git a/python_binding/operator/pybind_MetaOperatorDefs.cpp b/python_binding/operator/pybind_MetaOperatorDefs.cpp
index 3372d50e14be9e0d24ba5d9171766255ab49f23b..aa9f3c50e6b8c6ab9e7be46776d5fba30d775be2 100644
--- a/python_binding/operator/pybind_MetaOperatorDefs.cpp
+++ b/python_binding/operator/pybind_MetaOperatorDefs.cpp
@@ -28,7 +28,7 @@ template <DimIdx_t DIM> void declare_PaddedConvOp(py::module &m) {
   m.def(("PaddedConv" + std::to_string(DIM) + "D").c_str(), [](DimSize_t in_channels,
                                                          DimSize_t out_channels,
                                                          const std::vector<DimSize_t>& kernel_dims,
-                                                         const std::string& name, 
+                                                         const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
                                                          const std::vector<DimSize_t> &padding_dims,
                                                          const std::vector<DimSize_t> &dilation_dims)
@@ -50,7 +50,7 @@ template <DimIdx_t DIM> void declare_PaddedConvOp(py::module &m) {
 
 template <DimIdx_t DIM> void declare_PaddedConvDepthWiseOp(py::module &m) {
   m.def(("PaddedConvDepthWise" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
-                                                         const std::string& name, 
+                                                         const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
                                                          const std::vector<DimSize_t> &padding_dims,
                                                          const std::vector<DimSize_t> &dilation_dims)
@@ -66,12 +66,12 @@ template <DimIdx_t DIM> void declare_PaddedConvDepthWiseOp(py::module &m) {
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
        py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0),
        py::arg("dilation_dims") = std::vector<DimSize_t>(DIM,1));
-  
+
 }
 
 template <DimIdx_t DIM> void declare_PaddedAvgPoolingOp(py::module &m) {
   m.def(("PaddedAvgPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
-                                                         const std::string& name, 
+                                                         const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
                                                          const std::vector<DimSize_t> &padding_dims)
     {
@@ -84,25 +84,27 @@ template <DimIdx_t DIM> void declare_PaddedAvgPoolingOp(py::module &m) {
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
        py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0));
-  
+
 }
 
 template <DimIdx_t DIM> void declare_PaddedMaxPoolingOp(py::module &m) {
   m.def(("PaddedMaxPooling" + std::to_string(DIM) + "D").c_str(), [](const std::vector<DimSize_t>& kernel_dims,
-                                                         const std::string& name, 
+                                                         const std::string& name,
                                                          const std::vector<DimSize_t> &stride_dims,
-                                                         const std::vector<DimSize_t> &padding_dims)
+                                                         const std::vector<DimSize_t> &padding_dims,
+                                                         bool ceil_mode)
     {
         AIDGE_ASSERT(kernel_dims.size() == DIM, "kernel_dims size [%ld] does not match DIM [%d]", kernel_dims.size(), DIM);
         AIDGE_ASSERT(stride_dims.size() == DIM, "stride_dims size [%ld] does not match DIM [%d]", stride_dims.size(), DIM);
         AIDGE_ASSERT(padding_dims.size() == 2*DIM, "padding_dims size [%ld] does not match DIM [%d]", padding_dims.size(), 2*DIM);
 
-        return PaddedMaxPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()));
+        return PaddedMaxPooling<DIM>(to_array<DIM>(kernel_dims.begin()), name, to_array<DIM>(stride_dims.begin()), to_array<2*DIM>(padding_dims.begin()), ceil_mode);
     }, py::arg("kernel_dims"),
        py::arg("name") = "",
        py::arg("stride_dims") = std::vector<DimSize_t>(DIM,1),
-       py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0));
-  
+       py::arg("padding_dims") = std::vector<DimSize_t>(2*DIM,0),
+       py::arg("ceil_mode") = false);
+
 }
 
 void init_MetaOperatorDefs(py::module &m) {
@@ -118,9 +120,7 @@ void init_MetaOperatorDefs(py::module &m) {
   declare_PaddedMaxPoolingOp<1>(m);
   declare_PaddedMaxPoolingOp<2>(m);
   declare_PaddedMaxPoolingOp<3>(m);
- 
-  // FIXME:
-  // m.def("Conv1D", static_cast<NodeAPI(*)(const char*, int, int, int const
-  // (&)[1])>(&Conv));
+
+
 }
 } // namespace Aidge
diff --git a/src/backend/OperatorImpl.cpp b/src/backend/OperatorImpl.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..166754cc9fe9774d922ef523ab35f569673701fd
--- /dev/null
+++ b/src/backend/OperatorImpl.cpp
@@ -0,0 +1,77 @@
+/********************************************************************************
+ * 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 "aidge/backend/OperatorImpl.hpp"
+#include "aidge/operator/Operator.hpp"
+#include "aidge/data/Tensor.hpp"
+#include "aidge/utils/ErrorHandling.hpp"
+
+Aidge::OperatorImpl::OperatorImpl(const Operator& op):
+    mOp(op),
+    mNbConsumedData(mOp.nbInputs(), 0),
+    mNbProducedData(mOp.nbOutputs(), 0)
+{
+    //ctor
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbRequiredData(const Aidge::IOIndex_t inputIdx) const {
+    assert(mOp.getInput(inputIdx) && "requires valid input");
+
+    // Requires the whole tensor by default
+    return std::static_pointer_cast<Tensor>(mOp.getInput(inputIdx))->size();
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbRequiredProtected(IOIndex_t inputIdx) const {
+    assert(mOp.getInput(inputIdx) && "requires valid input");
+
+    // Protect the whole tensor by default
+    return std::static_pointer_cast<Tensor>(mOp.getInput(inputIdx))->size();
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getRequiredMemory(const Aidge::IOIndex_t outputIdx,
+                                                         const std::vector<Aidge::DimSize_t> &/*inputsSize*/) const {
+    assert(mOp.getOutput(outputIdx) && "requires valid output");
+
+    // Requires the whole tensor by default, regardless of available data on inputs
+    return std::static_pointer_cast<Tensor>(mOp.getOutput(outputIdx))->size();
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbConsumedData(Aidge::IOIndex_t inputIdx) const {
+    assert(static_cast<std::size_t>(inputIdx) < mNbConsumedData.size());
+    return mNbConsumedData[static_cast<std::size_t>(inputIdx)];
+}
+
+Aidge::NbElts_t Aidge::OperatorImpl::getNbProducedData(Aidge::IOIndex_t outputIdx) const {
+    assert(static_cast<std::size_t>(outputIdx) < mNbProducedData.size());
+    return mNbProducedData[static_cast<std::size_t>(outputIdx)];
+}
+
+void Aidge::OperatorImpl::updateConsummerProducer(){
+    // Update producer-consumer data
+    for (std::size_t inputIdx = 0; inputIdx < mNbConsumedData.size(); ++inputIdx) {
+        // each input is consumed by the minimum amount for a forward pass
+        mNbConsumedData[inputIdx] += getNbRequiredData(static_cast<IOIndex_t>(inputIdx));
+    }
+
+    for (std::size_t outputIdx = 0; outputIdx < mNbProducedData.size(); ++outputIdx) {
+        mNbProducedData[outputIdx] += getRequiredMemory(outputIdx, {});
+    }
+}
+
+void Aidge::OperatorImpl::forward() {
+    AIDGE_THROW_OR_ABORT(std::runtime_error, "forward() not implemented");
+}
+
+void Aidge::OperatorImpl::backward() {
+    AIDGE_THROW_OR_ABORT(std::runtime_error, "backward() not implemented");
+}