From d0097894dcac7074233f756dd9a2a73b69dec703 Mon Sep 17 00:00:00 2001
From: Olivier BICHLER <olivier.bichler@cea.fr>
Date: Wed, 29 Nov 2023 15:43:30 +0100
Subject: [PATCH] Clean up, removed node ordering from MetaOperator

---
 include/aidge/graph/GraphView.hpp           |   2 +-
 include/aidge/graph/Testing.hpp             |  62 ++++++
 include/aidge/operator/MetaOperator.hpp     |  24 +--
 include/aidge/operator/MetaOperatorDefs.hpp |   8 +-
 src/graph/GraphView.cpp                     | 225 +++++---------------
 src/graph/Testing.cpp                       |  81 +++++++
 src/operator/MetaOperator.cpp               |  57 +----
 unit_tests/graph/Test_Connector.cpp         |  12 ++
 unit_tests/graph/Test_GraphView.cpp         | 142 ++++++------
 9 files changed, 293 insertions(+), 320 deletions(-)
 create mode 100644 include/aidge/graph/Testing.hpp
 create mode 100644 src/graph/Testing.cpp

diff --git a/include/aidge/graph/GraphView.hpp b/include/aidge/graph/GraphView.hpp
index df8352bcf..63f034d4a 100644
--- a/include/aidge/graph/GraphView.hpp
+++ b/include/aidge/graph/GraphView.hpp
@@ -15,7 +15,7 @@
 
 #include <map>
 #include <memory>
-#include <unordered_set>
+#include <set>
 #include <string>
 #include <utility>
 #include <vector>
diff --git a/include/aidge/graph/Testing.hpp b/include/aidge/graph/Testing.hpp
new file mode 100644
index 000000000..7de03daf9
--- /dev/null
+++ b/include/aidge/graph/Testing.hpp
@@ -0,0 +1,62 @@
+/********************************************************************************
+ * 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_GRAPH_TESTING_H_
+#define AIDGE_CORE_GRAPH_TESTING_H_
+
+#include <vector>
+#include <set>
+#include <random>
+#include <algorithm>
+#include <utility>
+
+#include "aidge/graph/Node.hpp"
+#include "aidge/utils/Types.h"
+
+namespace Aidge {
+/**
+ * Random DAG generator
+*/
+struct RandomDAG {
+    /// @brief Connection density (between 0 and 1)
+    float density = 0.5;
+    /// @brief Max number of inputs per node (regardless if they are connected or not)
+    size_t maxIn = 5;
+    /// @brief Average number of inputs per node (regardless if they are connected or not)
+    float avgIn = 1.5;
+    /// @brief Max number of outputs per node (regardless if they are connected or not)
+    size_t maxOut = 2;
+    /// @brief Average number of outputs per node (regardless if they are connected or not)
+    float avgOut = 1.1;
+    /// @brief List of node types that should be generated in the graph (as GenericOperator)
+    std::vector<std::string> types = {"Fictive"};
+    /// @brief Weights of each node type, used to compute the probability of generating this type
+    std::vector<float> typesWeights = {1.0};
+
+    /**
+     * Generate a DAG according to the parameters of the class.
+     * @param seed Random seed. For an identical seed, an identical topology is 
+     * generated, but with a random node ordering in the return set of nodes.
+     * @param nbNodes Number of nodes to generate.
+    */
+    std::pair<NodePtr, std::set<NodePtr>> gen(std::mt19937::result_type seed, size_t nbNodes) const;
+};
+
+std::string nodePtrToType(NodePtr node);
+std::string nodePtrToName(NodePtr node);
+std::set<std::string> nodePtrTo(const std::set<NodePtr>& nodes,
+    std::string(*nodeTo)(NodePtr) = nodePtrToType);
+std::vector<std::pair<std::string, IOIndex_t>> nodePtrTo(
+    const std::vector<std::pair<NodePtr, IOIndex_t>>& nodes,
+    std::string(*nodeTo)(NodePtr) = nodePtrToType);
+}
+
+#endif /* AIDGE_CORE_GRAPH_TESTING_H_ */
diff --git a/include/aidge/operator/MetaOperator.hpp b/include/aidge/operator/MetaOperator.hpp
index 87a90c741..b9d049678 100644
--- a/include/aidge/operator/MetaOperator.hpp
+++ b/include/aidge/operator/MetaOperator.hpp
@@ -27,16 +27,9 @@ public:
     // Micro-graph handling:
     std::shared_ptr<GraphView> mGraph; // Meta operator micro-graph
     std::shared_ptr<SequentialScheduler> mScheduler;
-    // Need to store an ordored list of input/output nodes for the micro-graph,
-    // because input/output nodes in a GraphView are unordered.
-    // TODO: refactor GraphView to handle ordered input/output?
-    std::vector<std::pair<NodePtr, IOIndex_t>> mInputNodes;
-    std::vector<std::pair<NodePtr, IOIndex_t>> mOutputNodes;
 
    public:
-    MetaOperator_Op(const char *type, const std::shared_ptr<GraphView>& graph,
-        std::vector<NodePtr> inputNodes = std::vector<NodePtr>(),
-        std::vector<NodePtr> outputNodes = std::vector<NodePtr>());
+    MetaOperator_Op(const char *type, const std::shared_ptr<GraphView>& graph);
 
     /**
      * @brief Copy-constructor. Copy the operator attributes and its output tensor(s), but not its input tensors (the new operator has no input associated).
@@ -47,9 +40,6 @@ public:
           mGraph(op.mGraph->clone())
     {
         // cpy-ctor
-        // TODO: FIXME: mInputNodes and mOutputNodes are not populated!
-        // Issue: how to map new (cloned) nodes with old nodes? getNodes() does
-        // not garantee any order! Check issue #52.
     }
 
     /**
@@ -71,7 +61,7 @@ public:
     void associateInput(const IOIndex_t inputIdx, std::shared_ptr<Data> data) override final {
         assert(strcmp(data->type(), Tensor::Type) == 0 && "input data must be of Tensor type");
 
-        const auto& inputOp = mInputNodes[inputIdx];
+        const auto& inputOp = mGraph->getOrderedInputs()[inputIdx];
         inputOp.first->getOperator()->associateInput(inputOp.second, data);
 
         // Associate inputs for custom implementation
@@ -83,8 +73,8 @@ public:
         mGraph->forwardDims();
 
         // Associate outputs to micro-graph outputs for custom implementation
-        for (size_t outputIdx = 0; outputIdx < mOutputNodes.size(); ++outputIdx) {
-            const auto& outputOp = mOutputNodes[outputIdx];
+        for (size_t outputIdx = 0; outputIdx < mGraph->getOrderedOutputs().size(); ++outputIdx) {
+            const auto& outputOp = mGraph->getOrderedOutputs()[outputIdx];
             mOutputs[outputIdx] = outputOp.first->getOperator()->getOutput(outputOp.second);
         }
     }
@@ -159,11 +149,9 @@ public:
 
 inline std::shared_ptr<Node> MetaOperator(const char *type,
                                   const std::shared_ptr<GraphView>& graph,
-                                  const std::string& name = "",
-                                  std::vector<NodePtr> inputNodes = std::vector<NodePtr>(),
-                                  std::vector<NodePtr> outputNodes = std::vector<NodePtr>())
+                                  const std::string& name = "")
 {
-    return std::make_shared<Node>(std::make_shared<MetaOperator_Op>(type, graph, inputNodes, outputNodes), name);
+    return std::make_shared<Node>(std::make_shared<MetaOperator_Op>(type, graph), name);
 }
 }  // namespace Aidge
 
diff --git a/include/aidge/operator/MetaOperatorDefs.hpp b/include/aidge/operator/MetaOperatorDefs.hpp
index 73feb1348..a53ce18f7 100644
--- a/include/aidge/operator/MetaOperatorDefs.hpp
+++ b/include/aidge/operator/MetaOperatorDefs.hpp
@@ -32,10 +32,8 @@ inline std::shared_ptr<Node> PaddedConv(DimSize_t in_channels,
     // Construct micro-graph
     auto pad = Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : "", PadBorderType::Constant, 0.0);
     auto conv = std::make_shared<Node>(std::make_shared<Conv_Op<static_cast<DimIdx_t>(DIM)>>(in_channels, out_channels, kernel_dims, stride_dims, dilation_dims), (!name.empty()) ? name + "_conv" : "");
-    // Need to specify the ordered list of input operators
-    const std::vector<NodePtr> orderedInputNodes = {pad, conv};
 
-    auto metaOp = MetaOperator("PaddedConv", Sequential({pad, conv}), name, orderedInputNodes);
+    auto metaOp = MetaOperator("PaddedConv", Sequential({pad, conv}), name);
     addProducer(metaOp, 1, append(out_channels, append(in_channels, kernel_dims)), "w");
     addProducer(metaOp, 2, {out_channels}, "b");
     return metaOp;
@@ -65,10 +63,8 @@ inline std::shared_ptr<Node> PaddedConvDepthWise(const std::array<DimSize_t, DIM
     // Construct micro-graph
     auto pad = Pad<DIM>(padding_dims, (!name.empty()) ? name + "_pad" : "", PadBorderType::Constant, 0.0);
     auto conv = std::make_shared<Node>(std::make_shared<ConvDepthWise_Op<static_cast<DimIdx_t>(DIM)>>(kernel_dims, stride_dims, dilation_dims), (!name.empty()) ? name + "_conv" : "");
-    // Need to specify the ordered list of input operators
-    const std::vector<NodePtr> orderedInputNodes = {pad, conv};
 
-    auto metaOp = MetaOperator("PaddedConvDepthWise", Sequential({pad, conv}), name, orderedInputNodes);
+    auto metaOp = MetaOperator("PaddedConvDepthWise", Sequential({pad, conv}), name);
     addProducer(metaOp, 1, std::array<DimSize_t,0>({}), "w");
     addProducer(metaOp, 2, std::array<DimSize_t,0>({}), "b");
     return metaOp;
diff --git a/src/graph/GraphView.cpp b/src/graph/GraphView.cpp
index 3499bd093..b77283ed0 100644
--- a/src/graph/GraphView.cpp
+++ b/src/graph/GraphView.cpp
@@ -303,57 +303,7 @@ void Aidge::GraphView::setDatatype(const DataType &datatype) {
     node->getOperator()->setDatatype(datatype);
   }
 }
-/*
-void Aidge::GraphView::updateOutputNodes() {
-  mOutputNodes.clear();
-  for (const std::shared_ptr<Node>& go_it : mNodes) {
-    if (go_it->nbOutputs() !=
-        go_it->nbValidOutputs()) { // an output linked to nothing
-      mOutputNodes.insert(go_it);
-      continue;
-    }
-    for (const std::shared_ptr<Node>& ch_ptr : go_it->getChildren()) {
-      if (mNodes.find(ch_ptr) == mNodes.end()) { // Child not in the graph
-        mOutputNodes.insert(go_it);
-        break;
-      }
-    }
-  }
-}
 
-void Aidge::GraphView::updateOutputNodes(std::shared_ptr<Node> node) {
-  if (node->nbOutputs() !=
-      node->nbValidOutputs()) { // an output linked to nothing
-    mOutputNodes.insert(node);
-  } else { // don't enter if was already added to outputNodes
-    for (const std::shared_ptr<Node> &ch_ptr : node->getChildren()) {
-      if (mNodes.find(ch_ptr) == mNodes.end()) { // Child not in the graph
-        mOutputNodes.insert(node);
-        break;
-      }
-    }
-  }
-  // update other outputNodes
-  for (const std::shared_ptr<Node> &pa_ptr :
-       node->getParents()) { // check if any parent is in OutputNodes too
-    if ((pa_ptr != nullptr) &&
-        (mOutputNodes.find(pa_ptr) !=
-         mOutputNodes.end())) { // it's a match! Must check if the outputNode
-                                // found is still an outputNode
-      bool remove = (pa_ptr->nbOutputs() == pa_ptr->nbValidOutputs());
-      for (const std::shared_ptr<Node>& ch_ptr : pa_ptr->getChildren()) {
-        if (mNodes.find(ch_ptr) == mNodes.end()) { // Child not in the graph
-          remove = false;
-          break;
-        }
-      }
-      if (remove) {
-        mOutputNodes.erase(pa_ptr);
-      }
-    }
-  }
-}
-*/
 std::vector<
     std::vector<std::pair<std::shared_ptr<Aidge::Node>, Aidge::IOIndex_t>>>
 Aidge::GraphView::outputs() const {
@@ -841,35 +791,6 @@ bool Aidge::GraphView::replace(const std::set<Aidge::NodePtr>& oldNodes, const s
     return true;
 }
 
-/*
-void Aidge::GraphView::updateInputNodes() {
-  std::set<std::pair<NodePtr, IOIndex_t>> inputNodes;
-  for (const std::shared_ptr<Node>& go_ptr : mNodes) {
-    size_t inputIdx = 0;
-    for (const std::shared_ptr<Node>& pa_ptr : go_ptr->getParents()) {
-      if ((pa_ptr == nullptr) ||
-          (mNodes.find(pa_ptr) ==
-           mNodes.end())) { // Parent doesn't exist || Parent not in the graph
-        inputNodes.insert(std::make_pair(go_ptr, inputIdx));
-      }
-      ++inputIdx;
-    }
-  }
-
-  // Remove inputs that are not input anymore (deleted node or input connected internally)
-  for (auto it = mInputNodes.begin(); it != mInputNodes.end(); ++it) {
-    if (inputNodes.find(*it) == inputNodes.end()) {
-      it = mInputNodes.erase(it);
-    }
-  }
-
-  // Add remaining new inputs
-  for (auto inputNode : inputNodes) {
-    mInputNodes.push_back(inputNode);
-  }
-}
-*/
-
 void Aidge::GraphView::updateInputsOutputsNew(std::shared_ptr<Node> newNode) {
   // Can be called several times with the same node, e.g. when addChild() is
   // called on a node already part of the GraphView. In this case, inputs/outputs
@@ -1111,71 +1032,6 @@ void Aidge::GraphView::updateInputsOutputsNodes() {
   }
 }
 
-/*
-void Aidge::GraphView::updateInputNodes(std::shared_ptr<Node> node) {
-  // add node_ptr to inputNode if it can
-  std::size_t filledWithKnownInputs = 0U;
-  bool wasAdded = mInputNodes.find(node) != mInputNodes.end();
-  for (const std::shared_ptr<Node>& pa_ptr : node->getParents()) {
-    if ((pa_ptr == nullptr) ||
-        (mNodes.find(pa_ptr) ==
-         mNodes.end())) { // Parent doesn't exist || Parent not in the graph
-      mInputNodes.insert(node);
-      wasAdded = true;
-      break;
-    }
-    ++filledWithKnownInputs;
-  }
-  if (filledWithKnownInputs == node->nbInputs() && wasAdded) {
-    mInputNodes.erase(node);
-  }
-  // update other inputNodes
-  for (const std::shared_ptr<Node>& ch_ptr :
-       node->getChildren()) { // check if any child is in InputNodes too
-    if (mInputNodes.find(ch_ptr) !=
-        mInputNodes.end()) { // it's a match! Must check if the inputNode found
-                             // is still an inputNode
-                             // change here
-      bool remove = true;
-      for (const std::shared_ptr<Node>& pa_ptr : ch_ptr->getParents()) {
-        if (pa_ptr == nullptr ||
-            mNodes.find(pa_ptr) ==
-                mNodes
-                    .end()) { // Parent doesn't exist || Parent not in the graph
-          remove = false;
-          break;
-        }
-      }
-      if (remove) {
-        mInputNodes.erase(ch_ptr);
-      }
-    }
-  }
-}
-*/
-/*
-void Aidge::GraphView::removeInputNode(const std::string nodeName) {
-  std::map<std::string, std::shared_ptr<Node>>::iterator it =
-      mNodeRegistry.find(nodeName);
-  if (it != mNodeRegistry.end()) {
-    const std::shared_ptr<Node> val = (*it).second;
-    if (mInputNodes.find(val) != mInputNodes.end()) {
-      mInputNodes.erase(val);
-    }
-  }
-}
-
-void Aidge::GraphView::removeOutputNode(const std::string nodeName) {
-  std::map<std::string, std::shared_ptr<Node>>::iterator it =
-      mNodeRegistry.find(nodeName);
-  if (it != mNodeRegistry.end()) {
-    const std::shared_ptr<Node> val = (*it).second;
-    if (mOutputNodes.find(val) != mOutputNodes.end()) {
-      mOutputNodes.erase(val);
-    }
-  }
-}
-*/
 std::shared_ptr<Aidge::GraphView> Aidge::GraphView::cloneCallback(NodePtr(*cloneNode)(NodePtr)) const {
   std::shared_ptr<GraphView> newGraph = std::make_shared<GraphView>(mName);
 
@@ -1183,37 +1039,44 @@ std::shared_ptr<Aidge::GraphView> Aidge::GraphView::cloneCallback(NodePtr(*clone
   std::map<NodePtr, NodePtr> oldToNewNodes;
 
   for (const std::shared_ptr<Node> &node_ptr : mNodes) {
-    oldToNewNodes[node_ptr] = cloneNode(node_ptr);
+    auto clonedNode = cloneNode(node_ptr);
+    if (clonedNode == nullptr) {
+      AIDGE_ASSERT(node_ptr->getChildren().size() <= 1, "deleted nodes in GraphView::clone() cannot have multiple children");
+      AIDGE_ASSERT(node_ptr->nbDataInputs() <= 1, "deleted nodes in GraphView::clone() cannot have multiple data input parents");
+    }
+    oldToNewNodes[node_ptr] = clonedNode;
   }
 
   // For each node, convert old node -> new node connections
   for (auto &oldToNewNode : oldToNewNodes) {
-    if (oldToNewNode.second == nullptr)
+    if (oldToNewNode.second == nullptr) {
       continue;  // deleted node
+    }
 
     // Connect parent nodes. Nodes that were removed with cloneNode() are set to nullptr
     size_t parentId = 0;
     for (auto parent : oldToNewNode.first->inputs()) {
-      while (oldToNewNodes[parent.first] == nullptr) {
-        // Find next valid parent in line, going backward in the graph
-        AIDGE_ASSERT(parent.first->getChildren().size() == 1, "deleted nodes in GraphView::clone() cannot have multiple children");
-        AIDGE_ASSERT(parent.first->nbDataInputs() <= 1, "deleted nodes in GraphView::clone() cannot have multiple data input parents");
-        const auto& parents = parent.first->dataInputs();
-
-        if (!parents.empty() && parents[0].first != nullptr // a valid parent exists
-          && oldToNewNodes.find(parents[0].first) != oldToNewNodes.end()) // parent is in the GraphView
-        {
-          parent = parents[0];
-        }
-        else {
-          break;
+      if (parent.first != nullptr) {
+        while (oldToNewNodes[parent.first] == nullptr) {
+          // Find next valid parent in line, going backward in the graph
+          AIDGE_INTERNAL_ASSERT(parent.first->getChildren().size() == 1);
+          AIDGE_INTERNAL_ASSERT(parent.first->nbDataInputs() <= 1);
+          const auto& parents = parent.first->dataInputs();
+
+          if (!parents.empty() && parents[0].first != nullptr // a valid parent exists
+            && oldToNewNodes.find(parents[0].first) != oldToNewNodes.end()) // parent is in the GraphView
+          {
+            parent = parents[0];
+          }
+          else {
+            break;
+          }
         }
-      }
 
-      if (oldToNewNodes[parent.first]) {
-        AIDGE_ASSERT(oldToNewNodes[parent.first]->nbOutputs() == parent.first->nbOutputs(),
-          "next valid parent after deleted nodes in GraphView::clone() has wrong number of outputs");
-        oldToNewNodes[parent.first]->addChild(oldToNewNode.second, parent.second, parentId);
+        if (oldToNewNodes[parent.first]) {
+          AIDGE_INTERNAL_ASSERT(oldToNewNodes[parent.first]->nbOutputs() == parent.first->nbOutputs());
+          oldToNewNodes[parent.first]->addChild(oldToNewNode.second, parent.second, parentId);
+        }
       }
 
       ++parentId;
@@ -1224,6 +1087,11 @@ std::shared_ptr<Aidge::GraphView> Aidge::GraphView::cloneCallback(NodePtr(*clone
   // This has to be done in a second step to ensure that new GraphView inputs/outputs
   // are properly set (otherwise, some node's inputs/outputs may be wrongly registered as
   // GraphView inputs/outputs because not yet connected to other nodes)
+  if (oldToNewNodes[mRootNode] != nullptr) {
+    // Add root node first if is still exists!
+    newGraph->add(oldToNewNodes[mRootNode], false);
+  }
+
   for (auto &oldToNewNode : oldToNewNodes) {
     if (oldToNewNode.second == nullptr)
       continue;  // deleted node
@@ -1237,19 +1105,22 @@ std::shared_ptr<Aidge::GraphView> Aidge::GraphView::cloneCallback(NodePtr(*clone
     // If input node was removed, find next valid input
     while (oldToNewNodes[it->first] == nullptr) {
       // Removed node should have only one connected output, otherwise cloning is invalid
-      AIDGE_INTERNAL_ASSERT(it->first->getChildren().size() == 1);
-      auto child = *it->first->getChildren().begin();
-
+      AIDGE_INTERNAL_ASSERT(it->first->getChildren().size() <= 1);
       bool found = false;
-      std::size_t inputIdx = 0;
-      for (auto parent : child->getParents()) {
-        if (parent == it->first) {
-          it->first = child;
-          it->second = inputIdx;
-          found = true;
-          break;
+
+      if (it->first->getChildren().size() == 1) {
+        auto child = *it->first->getChildren().begin();
+
+        std::size_t inputIdx = 0;
+        for (auto parent : child->getParents()) {
+          if (parent == it->first) {
+            it->first = child;
+            it->second = inputIdx;
+            found = true;
+            break;
+          }
+          ++inputIdx;
         }
-        ++inputIdx;
       }
 
       if (!found) {
@@ -1275,7 +1146,9 @@ std::shared_ptr<Aidge::GraphView> Aidge::GraphView::cloneCallback(NodePtr(*clone
       AIDGE_INTERNAL_ASSERT(it->first->nbDataInputs() <= 1);
       auto parents = it->first->dataInputs();
 
-      if (!parents.empty()) {
+      if (!parents.empty() && parents[0].first != nullptr // a valid parent exists
+        && oldToNewNodes.find(parents[0].first) != oldToNewNodes.end()) // parent is in the GraphView
+      {
         *it = parents[0];
       }
       else {
diff --git a/src/graph/Testing.cpp b/src/graph/Testing.cpp
new file mode 100644
index 000000000..bb029520f
--- /dev/null
+++ b/src/graph/Testing.cpp
@@ -0,0 +1,81 @@
+/********************************************************************************
+ * 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/graph/Testing.hpp"
+#include "aidge/operator/GenericOperator.hpp"
+
+std::pair<Aidge::NodePtr, std::set<Aidge::NodePtr>> Aidge::RandomDAG::gen(std::mt19937::result_type seed, size_t nbNodes) const {
+    std::mt19937 gen(seed);
+    std::binomial_distribution<> dIn(maxIn - 1, avgIn/maxIn);
+    std::binomial_distribution<> dOut(maxOut - 1, avgOut/maxOut);
+    std::binomial_distribution<> dLink(1, density);
+    std::discrete_distribution<> dType(typesWeights.begin(), typesWeights.end());
+
+    std::vector<std::pair<int, int>> nbIOs;
+    for (size_t i = 0; i < nbNodes; ++i) {
+        const auto nbIn = 1 + dIn(gen);
+        nbIOs.push_back(std::make_pair(nbIn, 1 + dOut(gen)));
+    }
+
+    std::vector<int> nodesSeq(nbNodes);
+    std::iota(nodesSeq.begin(), nodesSeq.end(), 0);
+    // Don't use gen or seed here, must be different each time!
+    std::shuffle(nodesSeq.begin(), nodesSeq.end(), std::default_random_engine(std::random_device{}()));
+
+    std::vector<NodePtr> nodes(nbNodes, nullptr);
+    for (auto idx : nodesSeq) {
+        const std::string type = types[dType(gen)];
+        const std::string name = type + std::to_string(idx);
+        nodes[idx] = GenericOperator(type.c_str(), nbIOs[idx].first, nbIOs[idx].first, nbIOs[idx].second, name.c_str());
+    }
+
+    for (size_t i = 0; i < nbNodes; ++i) {
+        for (size_t j = i + 1; j < nbNodes; ++j) {
+            for (size_t outId = 0; outId < nodes[i]->nbOutputs(); ++outId) {
+                for (size_t inId = 0; inId < nodes[j]->nbInputs(); ++inId) {
+                    if (dLink(gen)) {
+                        nodes[i]->addChild(nodes[j], outId, inId);
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    return std::make_pair(nodes[0], std::set<NodePtr>(nodes.begin(), nodes.end()));
+}
+
+std::string Aidge::nodePtrToType(NodePtr node) {
+    return node->type();
+}
+
+std::string Aidge::nodePtrToName(NodePtr node) {
+    return node->name();
+}
+
+std::set<std::string> Aidge::nodePtrTo(const std::set<NodePtr>& nodes,
+    std::string(*nodeTo)(NodePtr))
+{
+    std::set<std::string> nodesStr;
+    std::transform(nodes.begin(), nodes.end(), std::inserter(nodesStr, nodesStr.begin()), nodeTo);
+    return nodesStr;
+}
+
+std::vector<std::pair<std::string, Aidge::IOIndex_t>> Aidge::nodePtrTo(
+    const std::vector<std::pair<NodePtr, IOIndex_t>>& nodes,
+    std::string(*nodeTo)(NodePtr))
+{
+    std::vector<std::pair<std::string, IOIndex_t>> nodesStr;
+    std::transform(nodes.begin(), nodes.end(), std::back_inserter(nodesStr),
+        [nodeTo](const std::pair<NodePtr, IOIndex_t>& node) {
+            return std::make_pair(nodeTo(node.first), node.second);
+        });
+    return nodesStr;
+}
diff --git a/src/operator/MetaOperator.cpp b/src/operator/MetaOperator.cpp
index 28fafb6a8..183c7cbd8 100644
--- a/src/operator/MetaOperator.cpp
+++ b/src/operator/MetaOperator.cpp
@@ -12,9 +12,7 @@
 #include "aidge/operator/MetaOperator.hpp"
 #include "aidge/utils/ErrorHandling.hpp"
 
-Aidge::MetaOperator_Op::MetaOperator_Op(const char *type, const std::shared_ptr<GraphView>& graph,
-    std::vector<NodePtr> inputNodes,
-    std::vector<NodePtr> outputNodes)
+Aidge::MetaOperator_Op::MetaOperator_Op(const char *type, const std::shared_ptr<GraphView>& graph)
     : Operator(type),
         mGraph(graph)
 {
@@ -26,53 +24,6 @@ Aidge::MetaOperator_Op::MetaOperator_Op(const char *type, const std::shared_ptr<
     for (std::size_t i = 0; i < mOutputs.size(); ++i) {
         mOutputs[i] = std::make_shared<Tensor>();
     }
-
-    // Fill inputsNodes and outputsNodes when there is no ambiguity
-    if (inputNodes.empty()) {
-        AIDGE_ASSERT(mGraph->inputNodes().size() == 1, "need to specify internal nodes input mapping");
-        inputNodes.push_back(*mGraph->inputNodes().begin());
-    }
-
-    if (outputNodes.empty()) {
-        AIDGE_ASSERT(mGraph->outputNodes().size() == 1, "need to specify internal nodes output mapping");
-        outputNodes.push_back(*mGraph->outputNodes().begin());
-    }
-
-    AIDGE_ASSERT(mGraph->inputNodes().size() == inputNodes.size(), "wrong number of specified input nodes");
-    AIDGE_ASSERT(mGraph->outputNodes().size() == outputNodes.size(), "wrong number of specified output nodes");
-
-    // Identify inputs that are outside the micro-graph
-    for (const auto& inputNode : inputNodes) {
-        AIDGE_ASSERT(mGraph->inView(inputNode), "input node must be in the graph");
-        const std::vector<std::pair<std::shared_ptr<Node>, IOIndex_t>> inputNodeinputs =
-            inputNode->inputs();
-        
-        int inputIdx = 0;   // input idx relative to the current node
-        for (const auto& in : inputNodeinputs) {
-            if (in.first == nullptr || !mGraph->inView(in.first)) {
-                // The input is not connected inside the micro-graph
-                // (no connection to this input or connection outside the micro-graph)
-                // => it is therefore an input for the meta-operator
-                mInputNodes.push_back(std::make_pair(inputNode, inputIdx));
-            }
-
-            ++inputIdx;
-        }
-    }
-
-    // The outputs of the output nodes are also the outputs of the meta-operator
-    for (const auto& outputNode : outputNodes) {
-        AIDGE_ASSERT(mGraph->inView(outputNode), "output node must be in the graph");
-        const std::vector<std::vector<std::pair<std::shared_ptr<Node>, IOIndex_t>>> outputNodeoutputs =
-            outputNode->outputs();
-
-        for (size_t outputIdx = 0; outputIdx < outputNodeoutputs.size(); ++outputIdx) {
-            mOutputNodes.push_back(std::make_pair(outputNode, outputIdx));
-        }
-    }
-
-    AIDGE_INTERNAL_ASSERT(mInputNodes.size() == mGraph->inputs().size());
-    AIDGE_INTERNAL_ASSERT(mOutputNodes.size() == mGraph->outputs().size());
 }
 
 Aidge::NbElts_t Aidge::MetaOperator_Op::getNbRequiredData(const IOIndex_t inputIdx) const {
@@ -80,7 +31,7 @@ Aidge::NbElts_t Aidge::MetaOperator_Op::getNbRequiredData(const IOIndex_t inputI
         return mImpl->getNbRequiredData(inputIdx);
     }
     else {
-        const auto& inputOp = mInputNodes[inputIdx];
+        const auto& inputOp = mGraph->getOrderedInputs()[inputIdx];
         return inputOp.first->getOperator()->getNbRequiredData(inputOp.second);
     }
 }
@@ -90,7 +41,7 @@ Aidge::NbElts_t Aidge::MetaOperator_Op::getNbConsumedData(IOIndex_t inputIdx) co
         return mImpl->getNbConsumedData(inputIdx);
     }
     else {
-        const auto& inputOp = mInputNodes[inputIdx];
+        const auto& inputOp = mGraph->getOrderedInputs()[inputIdx];
         return inputOp.first->getOperator()->getNbConsumedData(inputOp.second);
     }
 }
@@ -100,7 +51,7 @@ Aidge::NbElts_t Aidge::MetaOperator_Op::getNbProducedData(IOIndex_t outputIdx) c
         return mImpl->getNbProducedData(outputIdx);
     }
     else {
-        const auto& outputOp = mOutputNodes[outputIdx];
+        const auto& outputOp = mGraph->getOrderedOutputs()[outputIdx];
         return outputOp.first->getOperator()->getNbProducedData(outputOp.second);
     }
 }
diff --git a/unit_tests/graph/Test_Connector.cpp b/unit_tests/graph/Test_Connector.cpp
index ef70521d0..4c78fba6f 100644
--- a/unit_tests/graph/Test_Connector.cpp
+++ b/unit_tests/graph/Test_Connector.cpp
@@ -16,6 +16,7 @@
 #include "aidge/operator/GenericOperator.hpp"
 #include "aidge/graph/GraphView.hpp"
 #include "aidge/graph/OpArgs.hpp"
+#include "aidge/graph/Testing.hpp"
 
 using namespace Aidge;
 
@@ -113,6 +114,8 @@ TEST_CASE("GraphGeneration from Connector", "[GraphView]") {
     x = (*node10)({a, x});
     std::shared_ptr<GraphView> gv = generateGraph({x});
     gv->save("GraphGeneration");
+    REQUIRE(nodePtrTo(gv->getOrderedInputs()) == std::vector<std::pair<std::string, IOIndex_t>>({}));
+    REQUIRE(nodePtrTo(gv->getOrderedOutputs()) == std::vector<std::pair<std::string, IOIndex_t>>({{"g_matmul1", 0}}));
 }
 
 TEST_CASE("Connector connection GraphView", "[Connector]") {
@@ -131,6 +134,9 @@ TEST_CASE("Connector connection GraphView", "[Connector]") {
             GenericOperator("g_conv3", 1, 1,1),
             GenericOperator("g_matmul1", 2,2,1)
         });
+        REQUIRE(nodePtrTo(g->getOrderedInputs()) == std::vector<std::pair<std::string, IOIndex_t>>({{"g_conv1", 0}}));
+        REQUIRE(nodePtrTo(g->getOrderedOutputs()) == std::vector<std::pair<std::string, IOIndex_t>>({{"g_matmul1", 0}}));
+
         x = (*prod)({});
         x = (*g)({x});
         std::shared_ptr<GraphView> g2 = generateGraph({x});
@@ -151,9 +157,13 @@ TEST_CASE("Connector connection GraphView", "[Connector]") {
             GenericOperator("g_concat", 3,3,1),
             GenericOperator("g_conv3", 1, 1,1)
         });
+        REQUIRE(nodePtrTo(g->getOrderedInputs()) == std::vector<std::pair<std::string, IOIndex_t>>({{"ElemWise", 0}, {"ElemWise", 1}, {"ElemWise", 2}}));
+        REQUIRE(nodePtrTo(g->getOrderedOutputs()) == std::vector<std::pair<std::string, IOIndex_t>>({{"g_conv3", 0}}));
 
         x = (*g)({x, y, z});
         std::shared_ptr<GraphView> gv = generateGraph({x});
+        REQUIRE(nodePtrTo(gv->getOrderedInputs()) == std::vector<std::pair<std::string, IOIndex_t>>({}));
+        REQUIRE(nodePtrTo(gv->getOrderedOutputs()) == std::vector<std::pair<std::string, IOIndex_t>>({{"g_conv3", 0}}));
         gv->save("MultiInputSequentialConnector");
         REQUIRE(gv->inputNodes().size() == 0U);
     }
@@ -169,6 +179,8 @@ TEST_CASE("Connector Mini-graph", "[Connector]") {
     }
     y = (*GenericOperator("ElemWise",2,2,1))({y, x});
     std::shared_ptr<GraphView> g = generateGraph({y});
+    REQUIRE(nodePtrTo(g->getOrderedInputs()) == std::vector<std::pair<std::string, IOIndex_t>>({}));
+    REQUIRE(nodePtrTo(g->getOrderedOutputs()) == std::vector<std::pair<std::string, IOIndex_t>>({{"ElemWise", 0}}));
     g->save("TestGraph");
 }
 
diff --git a/unit_tests/graph/Test_GraphView.cpp b/unit_tests/graph/Test_GraphView.cpp
index 8da67e784..5acab4c44 100644
--- a/unit_tests/graph/Test_GraphView.cpp
+++ b/unit_tests/graph/Test_GraphView.cpp
@@ -14,79 +14,19 @@
 #include <memory>
 #include <set>
 #include <string>
-#include <random>
-#include <algorithm>
-#include <utility>
 
 #include <catch2/catch_test_macros.hpp>
 
 #include "aidge/backend/OperatorImpl.hpp"
 #include "aidge/data/Tensor.hpp"
 #include "aidge/graph/GraphView.hpp"
+#include "aidge/graph/Testing.hpp"
 #include "aidge/operator/Conv.hpp"
 #include "aidge/operator/GenericOperator.hpp"
 #include "aidge/operator/Producer.hpp"
 
 using namespace Aidge;
 
-std::pair<NodePtr, std::set<NodePtr>> genRandomDAG(std::mt19937::result_type seed, size_t nbNodes, float density = 0.5, size_t maxIn = 5, float avgIn = 1.5, size_t maxOut = 2, float avgOut = 1.1) {
-    std::mt19937 gen(seed);
-    std::binomial_distribution<> dIn(maxIn - 1, avgIn/maxIn);
-    std::binomial_distribution<> dOut(maxOut - 1, avgOut/maxOut);
-    std::binomial_distribution<> dLink(1, density);
-
-    std::vector<std::pair<int, int>> nbIOs;
-    for (size_t i = 0; i < nbNodes; ++i) {
-        const auto nbIn = 1 + dIn(gen);
-        nbIOs.push_back(std::make_pair(nbIn, 1 + dOut(gen)));
-    }
-
-    std::vector<int> nodesSeq(nbNodes);
-    std::iota(nodesSeq.begin(), nodesSeq.end(), 0);
-    // Don't use gen or seed here, must be different each time!
-    std::shuffle(nodesSeq.begin(), nodesSeq.end(), std::default_random_engine(std::random_device{}()));
-
-    std::vector<NodePtr> nodes(nbNodes, nullptr);
-    for (auto idx : nodesSeq) {
-        const std::string type = "Fictive";
-        const std::string name = type + std::to_string(idx);
-        nodes[idx] = GenericOperator(type.c_str(), nbIOs[idx].first, nbIOs[idx].first, nbIOs[idx].second, name.c_str());
-    }
-
-    for (size_t i = 0; i < nbNodes; ++i) {
-        for (size_t j = i + 1; j < nbNodes; ++j) {
-            for (size_t outId = 0; outId < nodes[i]->nbOutputs(); ++outId) {
-                for (size_t inId = 0; inId < nodes[j]->nbInputs(); ++inId) {
-                    if (dLink(gen)) {
-                        nodes[i]->addChild(nodes[j], outId, inId);
-                        break;
-                    }
-                }
-            }
-        }
-    }
-    return std::make_pair(nodes[0], std::set<NodePtr>(nodes.begin(), nodes.end()));
-}
-
-std::set<std::string> nodePtrToName(const std::set<NodePtr>& nodes) {
-    std::set<std::string> nodesName;
-    std::transform(nodes.begin(), nodes.end(), std::inserter(nodesName, nodesName.begin()),
-        [](const NodePtr& node) {
-            return node->name();
-        });
-    return nodesName;
-}
-
-std::vector<std::pair<std::string, IOIndex_t>> nodePtrToName(const std::vector<std::pair<NodePtr, IOIndex_t>>& nodes) {
-    std::vector<std::pair<std::string, IOIndex_t>> nodesName;
-    std::transform(nodes.begin(), nodes.end(), std::back_inserter(nodesName),
-        [](const std::pair<NodePtr, IOIndex_t>& node) {
-            return std::make_pair(node.first->name(), node.second);
-        });
-    return nodesName;
-}
-
-
 TEST_CASE("genRandomDAG") {
     const size_t nbTests = 100;
     size_t nbUnicity = 0;
@@ -95,10 +35,11 @@ TEST_CASE("genRandomDAG") {
         std::random_device rd;
         const std::mt19937::result_type seed(rd());
 
+        RandomDAG randDAG;
         const auto g1 = std::make_shared<GraphView>("g1");
-        const bool unicity1 = g1->add(genRandomDAG(seed, 10, 0.5));
+        const bool unicity1 = g1->add(randDAG.gen(seed, 10));
         const auto g2 = std::make_shared<GraphView>("g2");
-        const bool unicity2 = g2->add(genRandomDAG(seed, 10, 0.5));
+        const bool unicity2 = g2->add(randDAG.gen(seed, 10));
 
         g1->save("./genRandomDAG1");
         g2->save("./genRandomDAG2");
@@ -106,9 +47,9 @@ TEST_CASE("genRandomDAG") {
         REQUIRE(unicity1 == unicity2);
 
         if (unicity1) {
-            REQUIRE(nodePtrToName(g1->getNodes()) == nodePtrToName(g2->getNodes()));
-            REQUIRE(nodePtrToName(g1->getOrderedInputs()) == nodePtrToName(g2->getOrderedInputs()));
-            REQUIRE(nodePtrToName(g1->getOrderedOutputs()) == nodePtrToName(g2->getOrderedOutputs()));
+            REQUIRE(nodePtrTo(g1->getNodes(), nodePtrToName) == nodePtrTo(g2->getNodes(), nodePtrToName));
+            REQUIRE(nodePtrTo(g1->getOrderedInputs(), nodePtrToName) == nodePtrTo(g2->getOrderedInputs(), nodePtrToName));
+            REQUIRE(nodePtrTo(g1->getOrderedOutputs(), nodePtrToName) == nodePtrTo(g2->getOrderedOutputs(), nodePtrToName));
             ++nbUnicity;
         }
     }
@@ -116,6 +57,68 @@ TEST_CASE("genRandomDAG") {
     printf("nbUnicity = %zu/%zu\n", nbUnicity, nbTests);
 }
 
+TEST_CASE("clone") {
+    const size_t nbTests = 100;
+
+    for (int test = 0; test < nbTests; ++test) {
+        std::random_device rd;
+        const std::mt19937::result_type seed(rd());
+
+        RandomDAG randDAG;
+        const auto g1 = std::make_shared<GraphView>("g1");
+        g1->add(randDAG.gen(seed, 10));
+
+        const auto g2 = g1->clone();
+
+        REQUIRE(nodePtrTo(g1->getNodes(), nodePtrToName) == nodePtrTo(g2->getNodes(), nodePtrToName));
+        REQUIRE(nodePtrTo(g1->getOrderedInputs(), nodePtrToName) == nodePtrTo(g2->getOrderedInputs(), nodePtrToName));
+        REQUIRE(nodePtrTo(g1->getOrderedOutputs(), nodePtrToName) == nodePtrTo(g2->getOrderedOutputs(), nodePtrToName));
+    }
+}
+
+NodePtr nodeDel(NodePtr node) {
+    if (node->type() == "DelFictive") {
+        return nullptr;
+    }
+    return node->clone();
+}
+
+TEST_CASE("clone_with_delete") {
+    const size_t nbTests = 100;
+    size_t nbClonedWithDelete = 0;
+
+    for (int test = 0; test < nbTests; ++test) {
+        std::random_device rd;
+        const std::mt19937::result_type seed(rd());
+
+        RandomDAG randDAG;
+        randDAG.types = {"Fictive", "DelFictive"};
+        randDAG.typesWeights = {0.9, 0.1};
+        const auto g1 = std::make_shared<GraphView>("g1");
+        g1->add(randDAG.gen(seed, 10));
+
+        g1->save("./clone_with_delete1");
+
+        try {
+            const auto g2 = g1->cloneCallback(&nodeDel);
+
+            if (g2->getNodes().size() < g1->getNodes().size()) {
+                g2->save("./clone_with_delete2");
+
+                // These tests are not necessarily true if the deleted node is an input/output node!
+                //REQUIRE(g1->getOrderedInputs().size() == g2->getOrderedInputs().size());
+                //REQUIRE(g1->getOrderedOutputs().size() == g2->getOrderedOutputs().size());
+                ++nbClonedWithDelete;
+            }
+        }
+        catch (const std::runtime_error& error) {
+            // pass
+        }
+    }
+
+    printf("nbClonedWithDelete = %zu/%zu\n", nbClonedWithDelete, nbTests);
+}
+
 TEST_CASE("[core/graph] GraphView(Constructor)") {
     std::shared_ptr<GraphView> g0 = std::make_shared<GraphView>();
     std::shared_ptr<GraphView> g1 = std::make_shared<GraphView>("G1");
@@ -138,6 +141,9 @@ TEST_CASE("[core/graph] GraphView(add)") {
         g->add(GOp5);
         std::shared_ptr<Node> GOp6 = GenericOperator("Fictive", 1, 2, 1, "Gop6");
         g->add(GOp6);
+        g->save("node_alone");
+        REQUIRE(nodePtrTo(g->getOrderedInputs(), nodePtrToName) == std::vector<std::pair<std::string, IOIndex_t>>({{"Gop3", 0}, {"Gop4", 0}, {"Gop5", 0}, {"Gop6", 0}, {"Gop6", 1}}));
+        REQUIRE(nodePtrTo(g->getOrderedOutputs(), nodePtrToName) == std::vector<std::pair<std::string, IOIndex_t>>({{"Gop2", 0}, {"Gop5", 0}, {"Gop6", 0}}));
     }
 
     SECTION("Several Nodes") {
@@ -148,10 +154,14 @@ TEST_CASE("[core/graph] GraphView(add)") {
         GOp1parent->addChild(GOp1, 0, 0);
         g->add(GOp1);
         REQUIRE(g->getNodes() == std::set<std::shared_ptr<Node>>({GOp1, GOp1parent}));
+        REQUIRE(nodePtrTo(g->getOrderedInputs(), nodePtrToName) == std::vector<std::pair<std::string, IOIndex_t>>({}));
+        REQUIRE(nodePtrTo(g->getOrderedOutputs(), nodePtrToName) == std::vector<std::pair<std::string, IOIndex_t>>({{"Gop1", 0}}));
 
         // there should be no deplicates
         g->add(GOp1);
         REQUIRE(g->getNodes() == std::set<std::shared_ptr<Node>>({GOp1, GOp1parent}));
+        REQUIRE(nodePtrTo(g->getOrderedInputs(), nodePtrToName) == std::vector<std::pair<std::string, IOIndex_t>>({}));
+        REQUIRE(nodePtrTo(g->getOrderedOutputs(), nodePtrToName) == std::vector<std::pair<std::string, IOIndex_t>>({{"Gop1", 0}}));
     }
 
     SECTION("Initializer list ofr Node") {
-- 
GitLab