From e617397a970c807d57b10a36b93dcfd72c0f3564 Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Tue, 3 Sep 2024 16:37:06 +0200
Subject: [PATCH] Multiple fixes

---
 aidge_core/export_utils/operator_export.py    | 58 +++++++++++++++++
 include/aidge/backend/OperatorImpl.hpp        |  4 ++
 include/aidge/utils/Registrar.hpp             |  5 +-
 .../backend/pybind_OperatorImpl.cpp           |  1 +
 python_binding/data/pybind_Tensor.cpp         |  1 +
 python_binding/data/pybind_TensorImpl.cpp     | 64 +++++++++++++++++++
 python_binding/pybind_core.cpp                |  2 +
 python_binding/recipes/pybind_Recipes.cpp     |  7 ++
 src/backend/OperatorImpl.cpp                  | 11 ++--
 src/recipes/AdaptToBackend.cpp                |  7 +-
 10 files changed, 153 insertions(+), 7 deletions(-)
 create mode 100644 aidge_core/export_utils/operator_export.py
 create mode 100644 python_binding/data/pybind_TensorImpl.cpp

diff --git a/aidge_core/export_utils/operator_export.py b/aidge_core/export_utils/operator_export.py
new file mode 100644
index 000000000..c68f6b319
--- /dev/null
+++ b/aidge_core/export_utils/operator_export.py
@@ -0,0 +1,58 @@
+# TODO: this file is here for prototyping only
+# IT NEEDS TO BE REMOVED BEFORE ANY MERGE!
+
+import aidge_core
+
+
+class OperatorExport_arm_cortexm(aidge_core.OperatorImpl):
+    register = dict()
+
+    def __init__(self, operator):
+        super(OperatorExport_arm_cortexm, self).__init__(operator, "export_arm_cortexm")
+        print("ok")
+
+    def get_available_impl_specs(self):
+        print(f"get_available_impl_specs: {self.get_operator().type()}")
+        if self.get_operator().type() in self.register:
+            print(f"  : {list(self.register[self.get_operator().type()].keys())}")
+            return list(self.register[self.get_operator().type()].keys())
+        else:
+            return []
+
+    class FCOpKernel():
+        def generate():
+            print("Gen code for FCOp")
+
+
+# Register all operator types
+aidge_core.register_FCOp("export_arm_cortexm", OperatorExport_arm_cortexm)
+aidge_core.register_ReLUOp("export_arm_cortexm", OperatorExport_arm_cortexm)
+
+# Use the CPU backend for Tensor in the "export_arm_cortexm" backend
+aidge_core.register_Tensor(["export_arm_cortexm", aidge_core.dtype.float32],
+                           aidge_core.get_key_value_Tensor(["cpu", aidge_core.dtype.float32]))
+
+# Register kernels
+OperatorExport_arm_cortexm.register[aidge_core.FCOp.Type] = dict()
+OperatorExport_arm_cortexm.register[aidge_core.FCOp.Type][aidge_core.ImplSpec(aidge_core.IOSpec(aidge_core.dtype.int16))] = OperatorExport_arm_cortexm.FCOpKernel
+
+
+
+model = aidge_core.sequential(
+    [
+        aidge_core.FC(
+            in_channels=32 * 32 * 3, out_channels=512, name="InputNode"
+        ),
+        aidge_core.ReLU(name="Relu0"),
+        aidge_core.FC(in_channels=512, out_channels=256, name="FC1"),
+        aidge_core.ReLU(name="Relu1"),
+        aidge_core.FC(in_channels=256, out_channels=128, name="FC2"),
+        aidge_core.ReLU(name="Relu2"),
+        aidge_core.FC(in_channels=128, out_channels=10, name="OutputNode"),
+    ]
+)
+model.set_backend("export_arm_cortexm")
+
+aidge_core.adapt_to_backend(model)
+
+model.save("test", verbose=True)
\ No newline at end of file
diff --git a/include/aidge/backend/OperatorImpl.hpp b/include/aidge/backend/OperatorImpl.hpp
index 9c76f49b8..e145475c6 100644
--- a/include/aidge/backend/OperatorImpl.hpp
+++ b/include/aidge/backend/OperatorImpl.hpp
@@ -100,6 +100,10 @@ public:
         return mBackend;
     }
 
+    const Operator& getOperator() const noexcept {
+        return mOp;
+    }
+
     /**
      * @brief Get the operator required implementation specification, according
      * to the current operator configuration.
diff --git a/include/aidge/utils/Registrar.hpp b/include/aidge/utils/Registrar.hpp
index 6323b2abd..de5f6f80f 100644
--- a/include/aidge/utils/Registrar.hpp
+++ b/include/aidge/utils/Registrar.hpp
@@ -101,11 +101,14 @@ template <class C>
 void declare_registrable(py::module& m, const std::string& class_name){
     typedef typename C::registrar_key registrar_key;
     typedef typename C::registrar_type registrar_type;
-    m.def(("register_"+ class_name).c_str(), [](registrar_key& key, registrar_type function){
+    m.def(("register_"+ class_name).c_str(), [](const registrar_key& key, registrar_type function){
         Registrar<C>(key, function);
     })
     .def(("get_keys_"+ class_name).c_str(), [](){
         return Registrar<C>::getKeys();
+    })
+    .def(("get_key_value_"+ class_name).c_str(), [](const registrar_key& key){
+        return Registrar<C>::create(key);
     });
 }
 #endif
diff --git a/python_binding/backend/pybind_OperatorImpl.cpp b/python_binding/backend/pybind_OperatorImpl.cpp
index a129af8a2..be1d26e78 100644
--- a/python_binding/backend/pybind_OperatorImpl.cpp
+++ b/python_binding/backend/pybind_OperatorImpl.cpp
@@ -87,6 +87,7 @@ void init_OperatorImpl(py::module& m){
     .def("backward", &OperatorImpl::backward)
     .def("prod_conso", &OperatorImpl::prodConso)
     .def("backend", &OperatorImpl::backend)
+    .def("get_operator", &OperatorImpl::getOperator)
     .def("get_required_spec", &OperatorImpl::getRequiredSpec)
     .def("get_best_match", &OperatorImpl::getBestMatch)
     .def("get_adaptation", &OperatorImpl::getAdaptation)
diff --git a/python_binding/data/pybind_Tensor.cpp b/python_binding/data/pybind_Tensor.cpp
index 8f96c28d4..bbbbf39a7 100644
--- a/python_binding/data/pybind_Tensor.cpp
+++ b/python_binding/data/pybind_Tensor.cpp
@@ -584,5 +584,6 @@ void init_Tensor(py::module& m){
     // Handles python scalars and numpy scalars with a single overload
     addScalarCtor(pyClassTensor);
 
+    declare_registrable<Tensor>(m, "Tensor");
 }
 }
diff --git a/python_binding/data/pybind_TensorImpl.cpp b/python_binding/data/pybind_TensorImpl.cpp
new file mode 100644
index 000000000..4c664274e
--- /dev/null
+++ b/python_binding/data/pybind_TensorImpl.cpp
@@ -0,0 +1,64 @@
+/********************************************************************************
+ * 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 <pybind11/stl.h>
+#include <pybind11/operators.h>
+#include <pybind11/numpy.h>
+
+#include "aidge/data/Tensor.hpp"
+#include "aidge/data/Data.hpp"
+#include "aidge/utils/Registrar.hpp"
+#include "aidge/utils/Types.h"
+#include "aidge/backend/TensorImpl.hpp"
+#include "aidge/backend/cpu/data/TensorImpl.hpp"
+
+namespace py = pybind11;
+namespace Aidge {
+
+void init_TensorImpl(py::module& m){
+  py::class_<TensorImpl, std::shared_ptr<TensorImpl>>(m, "TensorImpl");
+
+  py::class_<TensorImpl_cpu<double>, std::shared_ptr<TensorImpl_cpu<double>>, TensorImpl>(m, "TensorImpl_cpu_float64")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+    
+  py::class_<TensorImpl_cpu<float>, std::shared_ptr<TensorImpl_cpu<float>>, TensorImpl>(m, "TensorImpl_cpu_float32")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<half_float::half>, std::shared_ptr<TensorImpl_cpu<half_float::half>>, TensorImpl>(m, "TensorImpl_cpu_float16")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<int64_t>, std::shared_ptr<TensorImpl_cpu<int64_t>>, TensorImpl>(m, "TensorImpl_cpu_int64")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<int32_t>, std::shared_ptr<TensorImpl_cpu<int32_t>>, TensorImpl>(m, "TensorImpl_cpu_int32")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<int16_t>, std::shared_ptr<TensorImpl_cpu<int16_t>>, TensorImpl>(m, "TensorImpl_cpu_int16")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<int8_t>, std::shared_ptr<TensorImpl_cpu<int8_t>>, TensorImpl>(m, "TensorImpl_cpu_int8")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<uint64_t>, std::shared_ptr<TensorImpl_cpu<uint64_t>>, TensorImpl>(m, "TensorImpl_cpu_uint64")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<uint32_t>, std::shared_ptr<TensorImpl_cpu<uint32_t>>, TensorImpl>(m, "TensorImpl_cpu_uint32")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<uint16_t>, std::shared_ptr<TensorImpl_cpu<uint16_t>>, TensorImpl>(m, "TensorImpl_cpu_uint16")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+  py::class_<TensorImpl_cpu<uint8_t>, std::shared_ptr<TensorImpl_cpu<uint8_t>>, TensorImpl>(m, "TensorImpl_cpu_uint8")
+    .def(py::init<DeviceIdx_t, std::vector<DimSize_t>>());
+
+}
+}
diff --git a/python_binding/pybind_core.cpp b/python_binding/pybind_core.cpp
index 9cf240d14..ae577246f 100644
--- a/python_binding/pybind_core.cpp
+++ b/python_binding/pybind_core.cpp
@@ -21,6 +21,7 @@ void init_Data(py::module&);
 void init_Database(py::module&);
 void init_DataProvider(py::module&);
 void init_Tensor(py::module&);
+void init_TensorImpl(py::module&);
 void init_Attributes(py::module&);
 void init_OperatorImpl(py::module&);
 void init_Log(py::module&);
@@ -89,6 +90,7 @@ void init_Aidge(py::module& m) {
     init_Database(m);
     init_DataProvider(m);
     init_Tensor(m);
+    init_TensorImpl(m);
     init_Attributes(m);
 
     init_Node(m);
diff --git a/python_binding/recipes/pybind_Recipes.cpp b/python_binding/recipes/pybind_Recipes.cpp
index 1c04a320d..144166876 100644
--- a/python_binding/recipes/pybind_Recipes.cpp
+++ b/python_binding/recipes/pybind_Recipes.cpp
@@ -122,6 +122,13 @@ void init_Recipes(py::module &m)
     :return: Number of sub-graph actually fused in a Meta Operator.
     :rtype: int
     )mydelimiter");
+
+  m.def("adapt_to_backend", adaptToBackend, py::arg("graph_view"), R"mydelimiter(
+    Adapt the graph to a specific backend.
+
+    :param graph_view: Graph view on which we want to apply the recipe
+    :type graph_view: :py:class:`aidge_core.GraphView`
+    )mydelimiter");
 }
 
 } // namespace Aidge
diff --git a/src/backend/OperatorImpl.cpp b/src/backend/OperatorImpl.cpp
index 5b53f9bef..a62da5f8e 100644
--- a/src/backend/OperatorImpl.cpp
+++ b/src/backend/OperatorImpl.cpp
@@ -221,7 +221,7 @@ std::shared_ptr<Aidge::Node> Aidge::OperatorImpl::getAdaptation(const ImplSpec&
 
     // Adapt inputs
     for (size_t i = 0; i < requiredSpecs.inputs.size(); ++i) {
-        const ImplSpec::IOSpec& IOSpec = spec.inputs[i];
+        const auto IOSpec = (i < spec.inputs.size()) ? spec.inputs[i] : spec.inputs.back();
         const ImplSpec::IOSpec& requiredIOSpec = requiredSpecs.inputs[i];
         std::shared_ptr<Node> parent = node;
 
@@ -272,7 +272,7 @@ std::shared_ptr<Aidge::Node> Aidge::OperatorImpl::getAdaptation(const ImplSpec&
 
     // Adapt outputs
     for (size_t i = 0; i < requiredSpecs.outputs.size(); ++i) {
-        const ImplSpec::IOSpec& IOSpec = spec.outputs[i];
+        const auto IOSpec = (i < spec.outputs.size()) ? spec.outputs[i] : spec.outputs.back();
         const ImplSpec::IOSpec& requiredIOSpec = requiredSpecs.outputs[i];
         std::shared_ptr<Node> parent = node;
 
@@ -321,11 +321,12 @@ std::shared_ptr<Aidge::Node> Aidge::OperatorImpl::getAdaptation(const ImplSpec&
         }
     }
 
-    return MetaOperator("", getConnectedGraphView(node));
+    return MetaOperator(std::string("Adapted_" + op->type()).c_str(), getConnectedGraphView(node));
 }
 
 std::shared_ptr<Aidge::Node> Aidge::OperatorImpl::getBestAdaptation(const ImplSpec& requiredSpecs) const {
     const auto availableSpecs = getAvailableImplSpecs();
+    Log::debug("Adapt operator type {}: {} impl. available", mOp.type(), availableSpecs.size());
 
     using AdaptationCost = int;
     std::map<std::shared_ptr<Node>, AdaptationCost> adaptations;
@@ -334,11 +335,13 @@ std::shared_ptr<Aidge::Node> Aidge::OperatorImpl::getBestAdaptation(const ImplSp
         auto adaptation = getAdaptation(availableSpecs[s], requiredSpecs);
 
         if (adaptation) {
-            auto microGraph = std::dynamic_pointer_cast<MetaOperator_Op>(adaptation)->getMicroGraph();
+            auto microGraph = std::dynamic_pointer_cast<MetaOperator_Op>(adaptation->getOperator())->getMicroGraph();
             adaptations.insert(std::make_pair(adaptation, microGraph->getNodes().size()));
         }
     }
 
+    Log::debug("Adapt operator type {}: found {} possible adaptations", mOp.type(), adaptations.size());
+
     if (!adaptations.empty()) {
         // Return best adaptation (with min. AdaptationCost)
         const auto bestAdaptation = std::min_element(adaptations.begin(), adaptations.end(),
diff --git a/src/recipes/AdaptToBackend.cpp b/src/recipes/AdaptToBackend.cpp
index b1e62c02b..e625a52f6 100644
--- a/src/recipes/AdaptToBackend.cpp
+++ b/src/recipes/AdaptToBackend.cpp
@@ -18,15 +18,18 @@
 #include "aidge/recipes/Recipes.hpp"
 
 void Aidge::adaptToBackend(std::shared_ptr<GraphView> graphView) {
-    for (auto node : graphView->getNodes()) {
+    const auto nodes = graphView->getNodes();
+    for (auto node : nodes) {
         auto impl = node->getOperator()->getImpl();
+        AIDGE_ASSERT(impl, "Missing implementation for node {} (of type {})",
+            node->name(), node->type());
         auto adaptedNode = impl->getBestAdaptation(impl->getRequiredSpec());
 
         if (adaptedNode == nullptr) {
             Log::notice("Unable to adapt node {} (of type {}) to backend {}",
                 node->name(), node->type(), impl->backend());
         }
-        else if (!node->getOperator()->isAtomic()) {
+        else if (!adaptedNode->getOperator()->isAtomic()) {
             Log::info("Adapted node {} (of type {}) to backend {}",
                 node->name(), node->type(), impl->backend());
             AIDGE_ASSERT(GraphView::replace({node}, {adaptedNode}), "Unable to replace adapted node!");
-- 
GitLab