IVGCVSW-6634 SubgraphView: Add method of returning a GetSubgraphWorkingCopy

 * Add pointer to SubgraphView allowing it to store a working copy
   implementation of its own representation of graph.
 * Make SubgraphView a friend of Graph to allow access to layers.
 * Add constructor to SubgraphView taking SubgraphViewWorkingCopyPtr
 * Rewrite Graph::SubstituteSubgraph for use on SubgraphView
 * Add GetWorkingCopy() method
 * Add tests for replacement of multiplication with DepthwiseConv2d
 * Check GetBackendHint() has value before passing to PrecompiledLayer
 * Add GetOwningIConnectableLayer to IInputSlot to allow traversing from
   IConnectableLayer->IOutputSlot->IInputSlot->IConnectableLayer

Signed-off-by: Francis Murtagh <francis.murtagh@arm.com>
Change-Id: Iaaef14448d8b73867eaee9d69f4f98d5d1bf171c
diff --git a/include/armnn/INetwork.hpp b/include/armnn/INetwork.hpp
index 6a2193c..505edf8 100644
--- a/include/armnn/INetwork.hpp
+++ b/include/armnn/INetwork.hpp
@@ -28,6 +28,7 @@
 public:
     virtual const IOutputSlot* GetConnection() const = 0;
     virtual IOutputSlot* GetConnection() = 0;
+    virtual const IConnectableLayer& GetOwningIConnectableLayer() const = 0;
 
 protected:
    /// Not user deletable.
diff --git a/include/armnn/backends/SubgraphView.hpp b/include/armnn/backends/SubgraphView.hpp
index 3359331..dbf0544 100644
--- a/include/armnn/backends/SubgraphView.hpp
+++ b/include/armnn/backends/SubgraphView.hpp
@@ -151,7 +151,27 @@
 
     void Clear();
 
+    /// This method returns a copy of the original SubgraphView provided by OptimizeSubgraphView with a separate
+    /// underlying graph from the main ArmNN graph.
+    /// Backend users should edit this working copy and then add it as a SubstitutionPair, along with original
+    /// SubgraphView, to the OptimizationViews returned by OptimizeSubgraphView.
+    /// ArmNN will then decide on whether or not to carry out Substitution of the two SubgraphViews.
+    SubgraphView GetWorkingCopy();
+
+    /// These methods should be called on a working copy subgraph created from GetWorkingCopy.
+    /// They take a SubgraphView pattern to replace and the substitute layer or subgraphView to substitute in.
+    void SubstituteSubgraph(SubgraphView&, IConnectableLayer*);
+    void SubstituteSubgraph(SubgraphView&, const SubgraphView&);
+
 private:
+    struct SubgraphViewWorkingCopy;
+
+    /// Constructs a sub-graph with the given arguments.
+    SubgraphView(IConnectableLayers&& layers,
+                 IInputSlots&& inputs,
+                 IOutputSlots&& outputs,
+                 std::shared_ptr<SubgraphViewWorkingCopy> ptr);
+
     void CheckSubgraph();
 
     /// Arrange the order of layers topologically so that nodes can be visited in valid order
@@ -168,5 +188,11 @@
     /// The list of pointers to the layers of the parent graph.
     Layers m_Layers;
     IConnectableLayers m_IConnectableLayers;
+
+    /// Pointer to internal graph implementation. This stores a working copy of a graph, separate from the main
+    /// ArmNN graph, for use by Backends so that they can edit it and add as a SubstitutionPair to OptimizationViews
+    /// along with the original SubgraphView.
+    /// ArmNN will then decide on whether or not to substitute in the provided SubgraphView working copy.
+    std::shared_ptr<SubgraphViewWorkingCopy> p_WorkingCopyImpl;
 };
 } // namespace armnn
diff --git a/src/armnn/Graph.hpp b/src/armnn/Graph.hpp
index d71149d..0c34d35 100644
--- a/src/armnn/Graph.hpp
+++ b/src/armnn/Graph.hpp
@@ -280,6 +280,8 @@
     /// Also verifies weights and bias are set for FullyConnected layers.
     void ConstructErrorMessageForUnconnectedInputs(Layer* const layer,
                                                    unsigned int slotIndex);
+
+    friend class SubgraphView;
 };
 
 /// Common base class for layers in the graph.
diff --git a/src/armnn/Layer.cpp b/src/armnn/Layer.cpp
index 88bc6d5..4ed179f 100644
--- a/src/armnn/Layer.cpp
+++ b/src/armnn/Layer.cpp
@@ -488,4 +488,9 @@
     return m_OwningLayer;
 }
 
+const IConnectableLayer& InputSlot::GetOwningIConnectableLayer() const
+{
+    return m_OwningLayer;
+}
+
 } // namespace armnn
diff --git a/src/armnn/Layer.hpp b/src/armnn/Layer.hpp
index 23aa86a..23561cb 100644
--- a/src/armnn/Layer.hpp
+++ b/src/armnn/Layer.hpp
@@ -55,6 +55,8 @@
     const OutputSlot* GetConnectedOutputSlot() const { return m_Connection; }
     OutputSlot* GetConnectedOutputSlot() { return m_Connection; }
 
+    const IConnectableLayer& GetOwningIConnectableLayer() const override;
+
     /// Links the slot to an output slot or breaks an existing link if passing nullptr.
     void SetConnection(OutputSlot* source)
     {
diff --git a/src/armnn/Network.cpp b/src/armnn/Network.cpp
index de60e11..8ec8b42 100644
--- a/src/armnn/Network.cpp
+++ b/src/armnn/Network.cpp
@@ -2857,7 +2857,7 @@
     {
         layer->SetBackendId(backend.value());
     }
-    else
+    else if (layer->GetBackendHint().has_value())
     {
         layer->SetBackendId(layer->GetBackendHint().value());
     }
diff --git a/src/armnn/SubgraphView.cpp b/src/armnn/SubgraphView.cpp
index aac2a35..5f972a9 100644
--- a/src/armnn/SubgraphView.cpp
+++ b/src/armnn/SubgraphView.cpp
@@ -65,9 +65,9 @@
 }
 
 /// IConnectable Duplication to maintain backwards compatibility
-SubgraphView::SubgraphView(SubgraphView::IConnectableLayers &&layers,
-                           SubgraphView::IInputSlots &&inputs,
-                           SubgraphView::IOutputSlots &&outputs)
+SubgraphView::SubgraphView(SubgraphView::IConnectableLayers&& layers,
+                           SubgraphView::IInputSlots&& inputs,
+                           SubgraphView::IOutputSlots&& outputs)
         : m_IInputSlots{inputs}
         , m_IOutputSlots{outputs}
         , m_IConnectableLayers(IConnectableLayers{layers.begin(), layers.end()})
@@ -79,6 +79,42 @@
     };
     std::transform(layers.begin(), layers.end(), std::back_inserter(m_Layers), f);
 
+    m_InputSlots.resize(inputs.size());
+    m_IInputSlots.resize(inputs.size());
+    for (unsigned int i = 0; i < inputs.size(); i++)
+    {
+        m_InputSlots.at(i) = PolymorphicDowncast<InputSlot*>(inputs[i]);
+        m_IInputSlots.at(i) = inputs[i];
+    }
+
+    m_OutputSlots.resize(outputs.size());
+    m_IOutputSlots.resize(outputs.size());
+    for (unsigned int i = 0; i < outputs.size(); i++)
+    {
+        m_OutputSlots.at(i) = PolymorphicDowncast<OutputSlot*>(outputs[i]);
+        m_IOutputSlots.at(i) = outputs[i];
+    }
+
+    ArrangeBySortOrder();
+    CheckSubgraph();
+}
+
+/// IConnectable Duplication to maintain backwards compatibility
+SubgraphView::SubgraphView(SubgraphView::IConnectableLayers&& layers,
+                           SubgraphView::IInputSlots&& inputs,
+                           SubgraphView::IOutputSlots&& outputs,
+                           std::shared_ptr<SubgraphViewWorkingCopy> ptr)
+        : m_IInputSlots{inputs}
+        , m_IOutputSlots{outputs}
+        , m_IConnectableLayers(IConnectableLayers{layers.begin(), layers.end()})
+        , p_WorkingCopyImpl(std::move(ptr))
+{
+    // Cast from IConnectableLayer to Layer for backward compatibility
+    auto f = [](IConnectableLayer* value)
+    {
+        return PolymorphicDowncast<Layer*>(value);
+    };
+    std::transform(layers.begin(), layers.end(), std::back_inserter(m_Layers), f);
 
     m_InputSlots.resize(inputs.size());
     m_IInputSlots.resize(inputs.size());
@@ -367,4 +403,195 @@
     m_IConnectableLayers.sort(compareIConnectableLayerPriority);
 }
 
+struct SubgraphView::SubgraphViewWorkingCopy
+{
+public:
+
+    SubgraphViewWorkingCopy() = default;
+    SubgraphViewWorkingCopy(Graph graph)
+                            : m_Graph(graph)
+    {};
+
+    Graph m_Graph;
+
+};
+
+SubgraphView SubgraphView::GetWorkingCopy()
+{
+    if (p_WorkingCopyImpl)
+    {
+        throw Exception("The SubgraphView calling GetWorkingCopy() is already a working copy. This function "
+                        "should be called on original SubgraphView obtained from OptimizeSubgraphView()");
+    }
+
+    // Create a cut down SubgraphView with underlying graph containing only the relevant layers.
+    // It needs its own underlying layers so that they can be replaced safely.
+    Graph newGraph = Graph();
+    std::unordered_map<const IConnectableLayer*, IConnectableLayer*> originalToClonedLayerMap;
+    std::list<armnn::IConnectableLayer*> originalSubgraphLayers = GetIConnectableLayers();
+
+    auto ptr = std::make_shared<SubgraphViewWorkingCopy>(std::move(newGraph));
+    SubgraphView::IInputSlots workingCopyInputs;
+
+    for (auto&& originalLayer : originalSubgraphLayers)
+    {
+        Layer* const layer = PolymorphicDowncast<const Layer*>(originalLayer)->Clone(ptr->m_Graph);
+        originalToClonedLayerMap.emplace(originalLayer, layer);
+    }
+
+    // Add IInputSlots to workingCopy
+    std::vector<const IConnectableLayer*> processed;
+    for (auto originalSubgraphInputSlot : GetIInputSlots())
+    {
+        const IConnectableLayer& originalSubgraphLayer =
+                PolymorphicDowncast<InputSlot*>(originalSubgraphInputSlot)->GetOwningLayer();
+
+        // Only need process Slots of layer once
+        if (std::find(processed.begin(), processed.end(), &originalSubgraphLayer) == processed.end())
+        {
+            IConnectableLayer* clonedLayer = originalToClonedLayerMap[&originalSubgraphLayer];
+
+            // Add the InputSlot to WorkingCopy InputSlots
+            for (unsigned int i = 0; i < clonedLayer->GetNumInputSlots(); i++)
+            {
+                workingCopyInputs.push_back(&clonedLayer->GetInputSlot(i));
+            }
+            processed.push_back(&originalSubgraphLayer);
+        }
+    }
+    // Empty processed
+    processed.clear();
+
+    for (auto originalSubgraphLayer : originalSubgraphLayers)
+    {
+        IConnectableLayer* const clonedLayer = originalToClonedLayerMap[originalSubgraphLayer];
+
+        // connect all cloned layers as per original subgraph
+        for (unsigned int i = 0; i < clonedLayer->GetNumOutputSlots(); i++)
+        {
+            // OutputLayers have no OutputSlots to be connected
+            if (clonedLayer->GetType() != LayerType::Output)
+            {
+                auto& outputSlot = clonedLayer->GetOutputSlot(i);
+                for (unsigned int k = 0; k < originalSubgraphLayer->GetNumOutputSlots(); k++)
+                {
+                    auto& originalOutputSlot = originalSubgraphLayer->GetOutputSlot(k);
+                    for (unsigned int j = 0; j < originalOutputSlot.GetNumConnections(); j++)
+                    {
+                        // nextLayer is the layer with IInputSlot connected to IOutputSlot we are working on
+                        const IConnectableLayer& nextLayer =
+                                originalOutputSlot.GetConnection(j)->GetOwningIConnectableLayer();
+
+                        // Check the layer is in our map and so has a clonedLayer
+                        if (originalToClonedLayerMap.find(&nextLayer) != originalToClonedLayerMap.end())
+                        {
+                            IConnectableLayer* newGraphTargetLayer = originalToClonedLayerMap[&nextLayer];
+
+                            IInputSlot& inputSlot =
+                                    newGraphTargetLayer->GetInputSlot(
+                                            PolymorphicDowncast<OutputSlot*>(
+                                                    &originalOutputSlot)->GetConnection(j)->GetSlotIndex());
+
+                            // Then make the connection
+                            outputSlot.Connect(inputSlot);
+                        }
+                    }
+                    // Copy the tensorInfo to the clonedOutputSlot
+                    outputSlot.SetTensorInfo(originalOutputSlot.GetTensorInfo());
+                }
+            }
+        }
+    }
+
+    SubgraphView::IOutputSlots workingCopyOutputs;
+
+    // Add IOutputSlots to workingCopy
+    for (auto outputSlot : GetIOutputSlots())
+    {
+
+        const IConnectableLayer& originalSubgraphLayer = outputSlot->GetOwningIConnectableLayer();
+
+        // OutputLayers have no OutputSlots to be connected
+        // Only need process Slots of layer once
+        if (originalSubgraphLayer.GetType() != LayerType::Output &&
+            std::find(processed.begin(), processed.end(), &originalSubgraphLayer) == processed.end())
+        {
+            IConnectableLayer* clonedLayer = originalToClonedLayerMap[&originalSubgraphLayer];
+
+            // Add the OutputSlot to WorkingCopy InputSlots
+            for (unsigned int i = 0; i < clonedLayer->GetNumOutputSlots(); i++)
+            {
+                workingCopyOutputs.push_back(&clonedLayer->GetOutputSlot(i));
+            }
+            processed.push_back(&originalSubgraphLayer);
+        }
+    }
+    processed.clear();
+
+    SubgraphView::IConnectableLayers workingCopyLayers;
+    for (auto& pair : originalToClonedLayerMap)
+    {
+        workingCopyLayers.push_back(pair.second);
+    }
+
+    return {std::move(workingCopyLayers),
+            std::move(workingCopyInputs),
+            std::move(workingCopyOutputs),
+            ptr};
+}
+
+void SubgraphView::SubstituteSubgraph(SubgraphView& subgraph, IConnectableLayer* substituteLayer)
+{
+    ARMNN_ASSERT(substituteLayer != nullptr);
+    SubgraphView substituteSubgraph(substituteLayer);
+
+    SubstituteSubgraph(subgraph, substituteSubgraph);
+}
+
+void SubgraphView::SubstituteSubgraph(SubgraphView& patternSubgraph, const SubgraphView& substituteSubgraph)
+{
+    if (!p_WorkingCopyImpl)
+    {
+        throw NullPointerException("The SubgraphView calling SubstituteSubgraphView is not a working copy. "
+                                   "Call this function on SubgraphView returned from SubgraphView::GetWorkingCopy()");
+    }
+
+    // Add substitute layer to the Main graph i.e. graph in p_WorkingCopyImpl
+    auto workingCopyGraph = &p_WorkingCopyImpl->m_Graph;
+    substituteSubgraph.ForEachIConnectableLayer([workingCopyGraph](IConnectableLayer* iConnectableLayer)
+                                                {
+                                                    // Search WorkingCopy Graph for substituteLayer and add if missing
+                                                    if (std::find(std::begin(workingCopyGraph->m_Layers),
+                                                                  std::end(workingCopyGraph->m_Layers),
+                                                                  iConnectableLayer) ==
+                                                        std::end(workingCopyGraph->m_Layers))
+                                                    {
+                                                        auto layer = PolymorphicDowncast<Layer*>(iConnectableLayer);
+
+                                                        layer->Reparent(*workingCopyGraph,
+                                                                        (workingCopyGraph->m_Layers).end());
+
+                                                        workingCopyGraph->m_LayersInOrder = false;
+                                                    }
+                                                });
+
+    // Replace the old connections with connections to new layer
+    workingCopyGraph->ReplaceSubgraphConnections(patternSubgraph, substituteSubgraph);
+
+    // Update input/outputSlot pointers
+    m_IInputSlots = std::move(substituteSubgraph.m_IInputSlots);
+    m_IOutputSlots = std::move(substituteSubgraph.m_IOutputSlots);
+
+    // Delete the old layers.
+    workingCopyGraph->EraseSubgraphLayers(patternSubgraph);
+
+    // Sort
+    workingCopyGraph->TopologicalSort();
+
+    // Update SubgraphView layer pointers to match those of the internal WorkingCopy layer pointers
+    m_IConnectableLayers = IConnectableLayers{ workingCopyGraph->m_Layers.begin(),
+                                               workingCopyGraph->m_Layers.end() };
+}
+
+
 } // namespace armnn
diff --git a/src/armnn/test/SubgraphViewTests.cpp b/src/armnn/test/SubgraphViewTests.cpp
index 14c18ee..e8012b5 100644
--- a/src/armnn/test/SubgraphViewTests.cpp
+++ b/src/armnn/test/SubgraphViewTests.cpp
@@ -6,6 +6,7 @@
 #include <Graph.hpp>
 #include <SubgraphViewSelector.hpp>
 
+#include <armnn/backends/OptimizationViews.hpp>
 #include <armnn/backends/SubgraphView.hpp>
 #include <armnn/backends/TensorHandle.hpp>
 #include <armnn/utility/NumericCast.hpp>
@@ -87,7 +88,7 @@
 SubgraphView::IOutputSlots CreateIOutputsFrom(const std::vector<armnn::IConnectableLayer*>& layers)
 {
     SubgraphView::IOutputSlots result;
-    for (auto &&layer: layers)
+    for (auto&& layer: layers)
     {
         for (unsigned int i = 0; i < layer->GetNumOutputSlots(); ++i)
         {
@@ -1459,14 +1460,14 @@
             for (uint32_t i = 0; i < numConcats; ++i)
             {
                 std::string name = "concat" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N");
-                uint32_t numInputs = 1 + GetRandom(3u);
+                numInputs = 1 + GetRandom(3u);
                 OriginsDescriptor concatDesc(numInputs);
                 graph.AddLayer<ConcatLayer>(concatDesc, name.c_str());
             }
             for (uint32_t i = 0; i < numSplits; ++i)
             {
                 std::string name = "split" + std::to_string(i) + (GetRandomFlag(supportedProb) ? "S" : "N");
-                uint32_t numOutputs = 1 + GetRandom(3u);
+                numOutputs = 1 + GetRandom(3u);
                 ViewsDescriptor splitDesc(numOutputs);
                 graph.AddLayer<SplitterLayer>(splitDesc, name.c_str());
             }
@@ -1891,4 +1892,361 @@
     );
 }
 
+TEST_CASE("SubgraphViewWorkingCopy")
+{
+    Graph graph;
+
+    auto input      = graph.AddLayer<InputLayer>(0, "Input");
+    auto activation = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "Activation");
+    auto output     = graph.AddLayer<OutputLayer>(1, "Output");
+
+    input->GetOutputSlot(0).Connect(activation->GetInputSlot(0));
+    activation->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    //Add in out of order
+    auto view = CreateSubgraphViewFrom({output, input, activation},
+                                       {},
+                                       {});
+
+    SubgraphView workingCopy = view->GetWorkingCopy();
+
+    // Check the layers are sorted topologically in the view
+    int idx=0;
+    LayerType expectedSorted[] = {LayerType::Input, LayerType::Activation, LayerType::Output};
+    workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l)
+        {
+            CHECK((expectedSorted[idx] == l->GetType()));
+            idx++;
+        }
+    );
+}
+
+bool ReplaceConstantMultiplicationWithDepthwise(SubgraphView& subgraph,
+                                                IConnectableLayer* layer)
+{
+    if (layer->GetType() == LayerType::Multiplication)
+    {
+        IInputSlot* patternSubgraphInput = &layer->GetInputSlot(0);
+
+        const IConnectableLayer* inputLayer    = &patternSubgraphInput->GetConnection()->GetOwningIConnectableLayer();
+        const IConnectableLayer* constantLayer = &layer->GetInputSlot(1).GetConnection()->GetOwningIConnectableLayer();
+
+        // Figure out which of the two inputs is the constant
+        if (constantLayer->GetType() != LayerType::Constant)
+        {
+            patternSubgraphInput = &layer->GetInputSlot(1);
+            std::swap(inputLayer, constantLayer);
+        }
+
+        if (constantLayer->GetType() == LayerType::Constant)
+        {
+            const TensorInfo& inputInfo = inputLayer->GetOutputSlot(0).GetTensorInfo();
+            const TensorInfo& constInfo = constantLayer->GetOutputSlot(0).GetTensorInfo();
+
+            // Add a Depthwise only where the constant input is a scalar that takes the form { 1, 1, 1, C }.
+            // The scalar is used as weights for the convolution.
+            if (constInfo.GetShape() == TensorShape({ 1, 1, 1, inputInfo.GetShape()[3] }))
+            {
+                auto replacementGraph = INetwork::Create();
+
+                DepthwiseConvolution2dDescriptor desc;
+                desc.m_DataLayout = DataLayout::NHWC;
+
+                TensorInfo weightInfo        = constInfo;
+                const TensorInfo& outputInfo = layer->GetOutputSlot(0).GetTensorInfo();
+                unsigned int M               = outputInfo.GetShape()[3] / inputInfo.GetShape()[3];
+                ARMNN_ASSERT_MSG(M == 1, "Constant multiplication only support 1x1x1xC, so M should always be 1 here");
+                weightInfo.SetShape({ 1, 1, 1, constInfo.GetShape()[3] * M });    //1HW(I*M)
+
+                const void* weightData =  PolymorphicPointerDowncast<const ConstantLayer>(constantLayer)
+                        ->m_LayerOutput->GetConstTensor<void>();
+                TensorInfo            weightsInfo = constInfo;
+                ConstTensor           weights(weightsInfo, weightData);
+
+                const auto depthwiseLayer = replacementGraph->AddDepthwiseConvolution2dLayer(
+                        desc, weights, armnn::EmptyOptional(), "Replacement for Constant-Multiplication");
+
+                auto& outslot = layer->GetOutputSlot(0);
+                SubgraphView::IOutputSlots outputs{ &outslot };
+                SubgraphView::IConnectableLayers layers;
+                layers.push_back(layer);
+                layers.push_back(const_cast<IConnectableLayer*>(constantLayer));
+
+                SubgraphView patternSubgraph(std::move(layers), {patternSubgraphInput}, {&layer->GetOutputSlot(0)});
+
+                subgraph.SubstituteSubgraph(patternSubgraph, depthwiseLayer );
+
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+bool ReplaceTestMultiplication(SubgraphView& subgraph,
+                           IConnectableLayer* layer)
+{
+    if (layer->GetType() == LayerType::Multiplication)
+    {
+
+        switch (layer->GetType())
+        {
+            case LayerType::Multiplication:
+                return ReplaceConstantMultiplicationWithDepthwise(subgraph, layer);
+                break;
+            default:
+                throw Exception("Found unknown MultiplicationSupportedMode value");
+                break;
+        }
+    }
+    return false;
+}
+
+void ReplaceUnsupportedLayers(SubgraphView& subgraph)
+{
+    using ReplacementFunc                    = bool (*)(SubgraphView&, IConnectableLayer*);
+    const ReplacementFunc replacementFuncs[] = {
+            &ReplaceTestMultiplication,
+    };
+
+    subgraph.ForEachLayer([replacementFuncs, &subgraph](IConnectableLayer* layer)
+           {
+               auto madeChange = false;
+               for (const ReplacementFunc f : replacementFuncs)
+               {
+                   madeChange = f(subgraph, layer);
+                   if (madeChange)
+                   {
+                       goto nextIteration;
+                   }
+               }
+               nextIteration:;
+           }
+    );
+}
+
+TEST_CASE("SubgraphViewWorkingCopyReplacementFunc")
+{
+    Graph graph;
+
+    const TensorInfo inputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0);
+    const TensorInfo constInfo({ 1, 1, 1, 16 }, DataType::QAsymmU8, 0.9f, 0, true);
+    const TensorInfo outputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0);
+
+    std::vector<uint8_t> constData(constInfo.GetNumElements(), 0);
+    std::iota(constData.begin(), constData.end(), 0);
+    ConstTensor constTensor(constInfo, constData);
+
+    // Add the original pattern
+    IConnectableLayer* input = graph.AddLayer<InputLayer>(0, "input");
+    auto constant = graph.AddLayer<ConstantLayer>("const");
+
+    constant->m_LayerOutput = std::make_shared<ScopedTensorHandle>(constTensor);
+    IConnectableLayer* mul      = graph.AddLayer<MultiplicationLayer>("mul");
+    IConnectableLayer* output   = graph.AddLayer<OutputLayer>(0, "output");
+
+    // Create connections between layers
+    input->GetOutputSlot(0).SetTensorInfo(inputInfo);
+    constant->GetOutputSlot(0).SetTensorInfo(constInfo);
+    mul->GetOutputSlot(0).SetTensorInfo(outputInfo);
+
+    input->GetOutputSlot(0).Connect(mul->GetInputSlot(0));
+    constant->GetOutputSlot(0).Connect(mul->GetInputSlot(1));
+    mul->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    //Add in out of order
+    auto view = CreateSubgraphViewFrom({output, input, mul, constant},
+                                       {},
+                                       {});
+
+    SubgraphView workingCopy = view->GetWorkingCopy();
+
+    // Check the WorkingCopy is as expected before replacement
+    CHECK(workingCopy.GetIConnectableLayers().size() == 4);
+    int idx=0;
+    LayerType expectedSorted[] = {LayerType::Input, LayerType::Constant, LayerType::Multiplication, LayerType::Output};
+    workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l)
+                                         {
+                                             CHECK((expectedSorted[idx] == l->GetType()));
+                                             idx++;
+                                         }
+    );
+
+    // Replace Multiplication and Constant with Depthwise
+    ReplaceUnsupportedLayers(workingCopy);
+
+    // Check the layers are as expected
+    CHECK(workingCopy.GetIConnectableLayers().size() == 3);
+    idx=0;
+    LayerType expectedSortedReplaced[] = {LayerType::Input, LayerType::DepthwiseConvolution2d, LayerType::Output};
+    workingCopy.ForEachIConnectableLayer([&idx, &expectedSortedReplaced](const IConnectableLayer* l)
+                                         {
+                                             CHECK((expectedSortedReplaced[idx] == l->GetType()));
+                                             idx++;
+                                         }
+    );
+}
+
+TEST_CASE("SubgraphViewWorkingCopySubstituteSubgraph")
+{
+    Graph graph;
+
+    auto input = graph.AddLayer<InputLayer>(0, "Input");
+    auto activation = graph.AddLayer<ActivationLayer>(ActivationDescriptor{}, "Activation");
+    auto output = graph.AddLayer<OutputLayer>(1, "Output");
+
+    input->GetOutputSlot(0).Connect(activation->GetInputSlot(0));
+    activation->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    //Add in out of order
+    auto view = CreateSubgraphViewFrom({output, input, activation},
+                                       {},
+                                       {});
+
+    // Check SubstituteSubgraphView throws when called on original SubgraphView
+    SubgraphView temp(input);
+    CHECK_THROWS_AS(view->SubstituteSubgraph(temp, input), NullPointerException);
+
+    // Check that GetWorkingCopy() being called on a working copy throws an exception
+    auto workingCopy = view->GetWorkingCopy();
+    CHECK_THROWS_AS(workingCopy.GetWorkingCopy(), Exception);
+}
+
+TEST_CASE("SubgraphViewWorkingCopyOptimizationViews")
+{
+    Graph graph;
+
+    const TensorInfo inputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0);
+    const TensorInfo constInfo({ 1, 1, 1, 16 }, DataType::QAsymmU8, 0.9f, 0, true);
+    const TensorInfo outputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0);
+
+    std::vector<uint8_t> constData(constInfo.GetNumElements(), 0);
+    std::iota(constData.begin(), constData.end(), 0);
+    ConstTensor constTensor(constInfo, constData);
+
+    // Add the original pattern
+    IConnectableLayer* input = graph.AddLayer<InputLayer>(0, "input");
+    auto constant = graph.AddLayer<ConstantLayer>("const");
+
+    constant->m_LayerOutput = std::make_shared<ScopedTensorHandle>(constTensor);
+    IConnectableLayer* mul      = graph.AddLayer<MultiplicationLayer>("mul");
+    IConnectableLayer* output   = graph.AddLayer<OutputLayer>(0, "output");
+
+    // Create connections between layers
+    input->GetOutputSlot(0).SetTensorInfo(inputInfo);
+    constant->GetOutputSlot(0).SetTensorInfo(constInfo);
+    mul->GetOutputSlot(0).SetTensorInfo(outputInfo);
+
+    input->GetOutputSlot(0).Connect(mul->GetInputSlot(0));
+    constant->GetOutputSlot(0).Connect(mul->GetInputSlot(1));
+    mul->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    //Add in out of order
+    auto view = CreateSubgraphViewFrom({output, input, mul, constant},
+                                       {},
+                                       {});
+
+    SubgraphView workingCopy = view->GetWorkingCopy();
+
+    // Check the WorkingCopy is as expected before replacement
+    int idx=0;
+    LayerType expectedSorted[] = {LayerType::Input, LayerType::Constant, LayerType::Multiplication, LayerType::Output};
+    workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l)
+                                         {
+                                             CHECK((expectedSorted[idx] == l->GetType()));
+                                             idx++;
+                                         }
+    );
+
+    // Replace Multiplication and Constant with Depthwise
+    ReplaceUnsupportedLayers(workingCopy);
+
+    // Check the layers are as expected
+    idx=0;
+    LayerType expectedSortedReplaced[] = {LayerType::Input, LayerType::DepthwiseConvolution2d, LayerType::Output};
+    workingCopy.ForEachIConnectableLayer([&idx, &expectedSortedReplaced](const IConnectableLayer* l)
+                                         {
+                                             CHECK((expectedSortedReplaced[idx] == l->GetType()));
+                                             idx++;
+                                         }
+    );
+
+
+    // At this stage NPU would take the working copy and create CompiledBlocPtr with it.
+
+    // We will just check that the procompiledLayer can still be added to the optimizationViews via a SubgraphView.
+    OptimizationViews optimizationViews;
+
+    CompiledBlobPtr ptr;
+    IConnectableLayer* preCompiledLayer = optimizationViews.GetINetwork()->AddPrecompiledLayer(
+            PreCompiledDescriptor(view->GetNumInputSlots(), view->GetNumOutputSlots()),
+            std::move(ptr),
+            EmptyOptional(),
+            "pre-compiled");
+
+
+    optimizationViews.AddSubstitution({ *view, SubgraphView(preCompiledLayer) });
+    CHECK(optimizationViews.Validate(*view));
+}
+
+TEST_CASE("SubgraphViewWorkingCopyReplaceSlots")
+{
+    Graph graph;
+    const TensorInfo inputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0);
+    const TensorInfo constInfo({ 1, 1, 1, 16 }, DataType::QAsymmU8, 0.9f, 0, true);
+    const TensorInfo outputInfo({ 1, 8, 8, 16 }, DataType::QAsymmU8, 1.0f, 0);
+
+    std::vector<uint8_t> constData(constInfo.GetNumElements(), 0);
+    std::iota(constData.begin(), constData.end(), 0);
+    ConstTensor constTensor(constInfo, constData);
+
+    // Add the original pattern
+    IConnectableLayer* input = graph.AddLayer<InputLayer>(0, "input");
+    auto constant = graph.AddLayer<ConstantLayer>("const");
+
+    constant->m_LayerOutput = std::make_shared<ScopedTensorHandle>(constTensor);
+    IConnectableLayer* mul      = graph.AddLayer<MultiplicationLayer>("mul");
+    IConnectableLayer* output   = graph.AddLayer<OutputLayer>(0, "output");
+
+    // Create connections between layers
+    input->GetOutputSlot(0).SetTensorInfo(inputInfo);
+    constant->GetOutputSlot(0).SetTensorInfo(constInfo);
+    mul->GetOutputSlot(0).SetTensorInfo(outputInfo);
+
+    input->GetOutputSlot(0).Connect(mul->GetInputSlot(0));
+    constant->GetOutputSlot(0).Connect(mul->GetInputSlot(1));
+    mul->GetOutputSlot(0).Connect(output->GetInputSlot(0));
+
+    auto view = CreateSubgraphViewFrom({output, input, mul, constant},
+                                       CreateIInputsFrom({mul}),
+                                        CreateIOutputsFrom({mul}));
+
+    SubgraphView workingCopy = view->GetWorkingCopy();
+
+    // Check the WorkingCopy is as expected before replacement
+    CHECK(workingCopy.GetIConnectableLayers().size() == 4);
+    int idx=0;
+    LayerType expectedSorted[] = {LayerType::Input, LayerType::Constant, LayerType::Multiplication, LayerType::Output};
+    workingCopy.ForEachIConnectableLayer([&idx, &expectedSorted](const IConnectableLayer* l)
+                                         {
+                                             CHECK((expectedSorted[idx] == l->GetType()));
+                                             idx++;
+                                         }
+    );
+
+    // Replace Multiplication and Constant with Depthwise
+    ReplaceUnsupportedLayers(workingCopy);
+
+    // Check the layers are as expected
+    idx=0;
+    LayerType expectedSortedReplaced[] = {LayerType::Input, LayerType::DepthwiseConvolution2d, LayerType::Output};
+    CHECK(workingCopy.GetIConnectableLayers().size() == 3);
+    workingCopy.ForEachIConnectableLayer([&idx, &expectedSortedReplaced](const IConnectableLayer* l)
+                                         {
+                                             CHECK((expectedSortedReplaced[idx] == l->GetType()));
+                                             idx++;
+                                         }
+    );
+}
+
 }