IVGCVSW-2375 Add ParseAddN function to TfParser

* Unit tests in AddN.cpp

Change-Id: Ifb2fa1051d5d92c5d9a5ca751abee4e81ebe39c9
diff --git a/src/armnnTfParser/TfParser.cpp b/src/armnnTfParser/TfParser.cpp
index 45c039b..1a0047f 100644
--- a/src/armnnTfParser/TfParser.cpp
+++ b/src/armnnTfParser/TfParser.cpp
@@ -327,6 +327,7 @@
 const std::map<std::string, TfParser::OperationParsingFunction> TfParser::ms_OperationNameToParsingFunctions = {
     { "Const",                 &TfParser::ParseConst },
     { "Add",                   &TfParser::ParseAdd },
+    { "AddN",                  &TfParser::ParseAddN },
     { "BiasAdd",               &TfParser::ParseBiasAdd },
     { "Identity",              &TfParser::ParseIdentity },
     { "Conv2D",                &TfParser::ParseConv2D },
@@ -610,6 +611,183 @@
     return result;
 }
 
+IConnectableLayer* TfParser::CreateAdditionLayer(
+            const tensorflow::NodeDef& nodeDef,
+            IOutputSlot* input0Slot,
+            IOutputSlot* input1Slot,
+            const std::string& layerName)
+{
+    const TensorInfo& input0Info = input0Slot->GetTensorInfo();
+    const TensorInfo& input1Info = input1Slot->GetTensorInfo();
+
+    const unsigned int input0Dim = input0Info.GetNumDimensions();
+    const unsigned int input1Dim = input1Info.GetNumDimensions();
+    if (input0Dim != input1Dim)
+    {
+        // broadcasting where input0 and input1 have different number of dimensions
+        // is only supported for 1D and 4D tensors pair
+        if (input0Dim == 1 && input1Dim == 4)
+        {
+            input0Slot = AddBroadcastReshapeLayer(input1Slot, input0Slot, true, *m_Network, nodeDef);
+        }
+        else if (input0Dim == 4 && input1Dim == 1)
+        {
+            input1Slot = AddBroadcastReshapeLayer(input0Slot, input1Slot, true, *m_Network, nodeDef);
+        }
+        else
+        {
+            throw ParseException(
+                    boost::str(
+                            boost::format("Unsupported broadcast configuration for %1% operation %2% %3%")
+                            % layerName
+                            % nodeDef.name()
+                            % CHECK_LOCATION().AsString()));
+        }
+    }
+    IConnectableLayer* const layer = m_Network->AddAdditionLayer(layerName.c_str());
+
+    input0Slot->Connect(layer->GetInputSlot(0));
+    input1Slot->Connect(layer->GetInputSlot(1));
+
+    // Ensure the output tensor has the correct dimensions even if a broadcast has been done
+    TensorInfo outputInfo = input0Slot->GetTensorInfo();
+    std::vector<unsigned int> outputShape;
+
+    const TensorShape& input0Shape = input0Slot->GetTensorInfo().GetShape();
+    const TensorShape& input1Shape = input1Slot->GetTensorInfo().GetShape();
+
+    for (unsigned int i = 0; i < input0Shape.GetNumDimensions(); i++)
+    {
+        outputShape.push_back(std::max(input0Shape[i], input1Shape[i]));
+    }
+
+    outputInfo.SetShape(TensorShape(input0Shape.GetNumDimensions(), outputShape.data()));
+    layer->GetOutputSlot(0).SetTensorInfo(outputInfo);
+
+    return layer;
+}
+
+IConnectableLayer* TfParser::CreateAdditionLayer(
+            const tensorflow::NodeDef& nodeDef,
+            IConnectableLayer* layerOne,
+            IConnectableLayer* layerTwo,
+            unsigned int numberOfAddition,
+            unsigned long numberOfLayersToConnect,
+            bool isOdd)
+{
+    IOutputSlot* input0Slot = &layerOne->GetOutputSlot(0);
+    IOutputSlot* input1Slot = &layerTwo->GetOutputSlot(0);
+    std::string layerName(nodeDef.name());
+    if (isOdd || numberOfLayersToConnect != 2)
+    {
+        // we are not connecting the final layer
+        layerName.append("_addN_").append(std::to_string(numberOfAddition));
+    }
+    return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, layerName);
+}
+
+IConnectableLayer* TfParser::CreateAdditionLayer(
+        const tensorflow::NodeDef& nodeDef,
+        const OutputOfParsedTfOperation& opOne,
+        const OutputOfParsedTfOperation& opTwo,
+        unsigned int numberOfAddition)
+{
+    IOutputSlot* input0Slot = &opOne.m_IndexedValue->ResolveArmnnOutputSlot(opOne.m_Index);
+    IOutputSlot* input1Slot = &opTwo.m_IndexedValue->ResolveArmnnOutputSlot(opTwo.m_Index);
+    std::string layerName(nodeDef.name());
+    layerName.append("_addN_").append(std::to_string(numberOfAddition));
+    return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, layerName);
+}
+
+IConnectableLayer* TfParser::CreateAdditionLayer(
+            const tensorflow::NodeDef& nodeDef,
+            const OutputOfParsedTfOperation& op,
+            IConnectableLayer* layer)
+{
+    IOutputSlot* input0Slot = &op.m_IndexedValue->ResolveArmnnOutputSlot(op.m_Index);
+    IOutputSlot* input1Slot = &layer->GetOutputSlot(0);
+    return CreateAdditionLayer(nodeDef, input0Slot, input1Slot, nodeDef.name());
+}
+
+ParsedTfOperationPtr TfParser::ParseAddN(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
+{
+    uint32_t numberOfInputs = ReadMandatoryNodeUint32Attribute(nodeDef, "N");
+    if (numberOfInputs < 2)
+    {
+        // should never happen
+        throw ParseException(
+                boost::str(
+                        boost::format(
+                                "AddN Node with name '%1%' has less than two (%2) inputs %3%")
+                        % nodeDef.name()
+                        % std::to_string(numberOfInputs)
+                        % CHECK_LOCATION().AsString()));
+    }
+    else if (numberOfInputs == 2)
+    {
+        //this is the same as a simple Add operation
+        return AddAdditionLayer(nodeDef, false);
+    }
+    else
+    {
+        // build a binary tree of Add layers and return the final Add as the return from the function
+        // if we have an odd number of inputs then the final Add will consist of a layer connecting to an
+        // OutputOfParsedTfOperation, otherwise it will be two layers being added together
+        std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, numberOfInputs);
+        unsigned int numberOfAdditions = 0;
+        std::vector<IConnectableLayer*> layers;
+        // NOTE: at this point we will have a minimum of three inputs
+        for (unsigned int i = 0; i < numberOfInputs; ++i)
+        {
+            // every time i is odd we have two inputs to process.
+            bool onSecondItem = i % 2;
+            if (onSecondItem)
+            {
+                ++numberOfAdditions;
+                IConnectableLayer* newLayer = CreateAdditionLayer(
+                        nodeDef, inputs[ i - 1], inputs[i], numberOfAdditions);
+                layers.push_back(newLayer);
+            }
+        }
+
+        std::vector<IConnectableLayer*> layersToConnect(layers);
+        unsigned long numberOfLayersToConnect = layersToConnect.size();
+        bool isOdd = numberOfInputs % 2;
+
+        while (numberOfLayersToConnect > 1)
+        {
+            layers.clear();
+            for (unsigned long i = 0; i < numberOfLayersToConnect; ++i) {
+                bool onSecondItem = i % 2;
+                if (onSecondItem) {
+                    ++numberOfAdditions;
+                    IConnectableLayer* newLayer = CreateAdditionLayer(
+                        nodeDef,
+                        layersToConnect[i - 1],
+                        layersToConnect[i],
+                        numberOfAdditions,
+                        numberOfLayersToConnect,
+                        isOdd);
+                    layers.push_back(newLayer);
+                }
+            }
+            //OK... need to go again... maybe
+            layersToConnect = layers;
+            numberOfLayersToConnect = layersToConnect.size();
+        }
+        IConnectableLayer* finalLayer = layersToConnect[0];
+        // if we had an odd number of inputs we need to connect the final layer to the
+        // last OutputOfParsedTfOperation in order to create the last Add layer we will
+        // be handing back.
+        if (isOdd)
+        {
+            // connect the final layer to the last op
+            finalLayer = CreateAdditionLayer(nodeDef, inputs[numberOfInputs - 1], finalLayer);
+        }
+        return std::make_unique<SingleLayerParsedTfOperation>(this, nodeDef, finalLayer);
+    }
+}
+
 ParsedTfOperationPtr TfParser::ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef)
 {
     std::vector<OutputOfParsedTfOperation> inputs = GetInputParsedTfOperationsChecked(nodeDef, 2);
diff --git a/src/armnnTfParser/TfParser.hpp b/src/armnnTfParser/TfParser.hpp
index 5579747..0d1e497 100644
--- a/src/armnnTfParser/TfParser.hpp
+++ b/src/armnnTfParser/TfParser.hpp
@@ -129,6 +129,7 @@
     bool HasParsedConstTensor(ParsedTfOperation* parsedTfOpPtr) const;
 
     ParsedTfOperationPtr ParseAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef);
+    ParsedTfOperationPtr ParseAddN(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef);
     ParsedTfOperationPtr ParseBiasAdd(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef);
     ParsedTfOperationPtr ParseConv2D(const tensorflow::NodeDef& nodeDef, const tensorflow::GraphDef& graphDef);
     ParsedTfOperationPtr ParseDepthwiseConv2D(const tensorflow::NodeDef& nodeDef,const tensorflow::GraphDef& graphDef);
@@ -187,6 +188,31 @@
             armnn::IConnectableLayer* const layer,
             const tensorflow::NodeDef& nodeDef);
 
+    armnn::IConnectableLayer* CreateAdditionLayer(
+            const tensorflow::NodeDef& nodeDef,
+            armnn::IOutputSlot* input0Slot,
+            armnn::IOutputSlot* input1Slot,
+            const std::string& layerName);
+
+    armnn::IConnectableLayer* CreateAdditionLayer(
+            const tensorflow::NodeDef& nodeDef,
+            const OutputOfParsedTfOperation& opOne,
+            const OutputOfParsedTfOperation& opTwo,
+            unsigned int numberOfAddition);
+
+    armnn::IConnectableLayer* CreateAdditionLayer(
+            const tensorflow::NodeDef& nodeDef,
+            armnn::IConnectableLayer* layerOne,
+            armnn::IConnectableLayer* layerTwo,
+            unsigned int numberOfAddition,
+            unsigned long numberOfLayersToConnect,
+            bool isOdd);
+
+    armnn::IConnectableLayer* CreateAdditionLayer(
+            const tensorflow::NodeDef& nodeDef,
+            const OutputOfParsedTfOperation& op,
+            armnn::IConnectableLayer* layer);
+
     static std::pair<armnn::LayerBindingId, armnn::TensorInfo> GetBindingInfo(const std::string& layerName,
         const char* bindingPointDesc,
         const std::unordered_map<std::string, BindingPointInfo>& nameToBindingInfo);
diff --git a/src/armnnTfParser/test/AddN.cpp b/src/armnnTfParser/test/AddN.cpp
new file mode 100644
index 0000000..19affa8
--- /dev/null
+++ b/src/armnnTfParser/test/AddN.cpp
@@ -0,0 +1,180 @@
+//
+// Copyright © 2017 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+
+#include <boost/assert.hpp>
+#include <boost/test/unit_test.hpp>
+
+#include "armnnTfParser/ITfParser.hpp"
+#include "ParserPrototxtFixture.hpp"
+
+#include <map>
+#include <string>
+
+
+BOOST_AUTO_TEST_SUITE(TensorflowParser)
+
+struct AddNFixture : public armnnUtils::ParserPrototxtFixture<armnnTfParser::ITfParser>
+{
+    AddNFixture(const std::vector<armnn::TensorShape> inputShapes, unsigned int numberOfInputs)
+    {
+        BOOST_ASSERT(inputShapes.size() == numberOfInputs);
+        m_Prototext = "";
+        for (unsigned int i = 0; i < numberOfInputs; i++)
+        {
+            m_Prototext.append("node { \n");
+            m_Prototext.append("  name: \"input").append(std::to_string(i)).append("\"\n");
+            m_Prototext += R"(  op: "Placeholder"
+  attr {
+    key: "dtype"
+    value {
+      type: DT_FLOAT
+    }
+  }
+  attr {
+    key: "shape"
+    value {
+      shape {
+      }
+    }
+  }
+}
+)";
+        }
+        m_Prototext += R"(node {
+  name:  "output"
+  op: "AddN"
+)";
+        for (unsigned int i = 0; i < numberOfInputs; i++)
+        {
+            m_Prototext.append("  input: \"input").append(std::to_string(i)).append("\"\n");
+        }
+        m_Prototext += R"(  attr {
+    key: "N"
+    value {
+)";
+        m_Prototext.append("      i: ").append(std::to_string(numberOfInputs)).append("\n");
+        m_Prototext += R"(    }
+  }
+  attr {
+    key: "T"
+    value {
+      type: DT_FLOAT
+    }
+  }
+})";
+
+        std::map<std::string, armnn::TensorShape> inputs;
+        for (unsigned int i = 0; i < numberOfInputs; i++)
+        {
+            std::string name("input");
+            name.append(std::to_string(i));
+            inputs.emplace(std::make_pair(name, inputShapes[i]));
+        }
+        Setup(inputs, {"output"});
+    }
+
+};
+
+// try with 2, 3, 5 and 8 inputs
+struct FiveTwoDimInputsFixture : AddNFixture
+{
+    FiveTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 } }, 5) {}
+};
+
+
+BOOST_FIXTURE_TEST_CASE(FiveTwoDimInputs, FiveTwoDimInputsFixture)
+{
+    RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } },
+                 { "input1", { 1.0, 5.0, 2.0, 2.0 } },
+                 { "input2", { 1.0, 1.0, 2.0, 2.0 } },
+                 { "input3", { 3.0, 7.0, 1.0, 2.0 } },
+                 { "input4", { 8.0, 0.0, -2.0, -3.0 } } },
+               { { "output", { 14.0, 15.0, 6.0, 7.0 } } });
+}
+
+struct TwoTwoDimInputsFixture : AddNFixture
+{
+    TwoTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 } }, 2) {}
+};
+
+BOOST_FIXTURE_TEST_CASE(TwoTwoDimInputs, TwoTwoDimInputsFixture)
+{
+    RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } },
+                 { "input1", { 1.0, 5.0, 2.0, 2.0 } } },
+               { { "output", { 2.0, 7.0, 5.0, 6.0 } } });
+}
+
+struct ThreeTwoDimInputsFixture : AddNFixture
+{
+    ThreeTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 }, { 2, 2 } }, 3) {}
+};
+
+BOOST_FIXTURE_TEST_CASE(ThreeTwoDimInputs, ThreeTwoDimInputsFixture)
+{
+    RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } },
+                 { "input1", { 1.0, 5.0, 2.0, 2.0 } },
+                 { "input2", { 1.0, 1.0, 2.0, 2.0 } } },
+               { { "output", { 3.0, 8.0, 7.0, 8.0 } } });
+}
+
+struct EightTwoDimInputsFixture : AddNFixture
+{
+    EightTwoDimInputsFixture() : AddNFixture({ { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 },
+                                               { 2, 2 }, { 2, 2 }, { 2, 2 }, { 2, 2 } }, 8) {}
+};
+
+BOOST_FIXTURE_TEST_CASE(EightTwoDimInputs, EightTwoDimInputsFixture)
+{
+    RunTest<2>({ { "input0", { 1.0, 2.0, 3.0, 4.0 } },
+                 { "input1", { 1.0, 5.0, 2.0, 2.0 } },
+                 { "input2", { 1.0, 1.0, 2.0, 2.0 } },
+                 { "input3", { 3.0, 7.0, 1.0, 2.0 } },
+                 { "input4", { 8.0, 0.0, -2.0, -3.0 } },
+                 { "input5", {-3.0, 2.0, -1.0, -5.0 } },
+                 { "input6", { 1.0, 6.0, 2.0, 2.0 } },
+                 { "input7", {-19.0, 7.0, 1.0, -10.0 } } },
+               { { "output", {-7.0, 30.0, 8.0, -6.0 } } });
+}
+
+struct ThreeInputBroadcast1D4D4DInputsFixture : AddNFixture
+{
+    ThreeInputBroadcast1D4D4DInputsFixture() : AddNFixture({ { 1 }, { 1, 1, 2, 2 }, { 1, 1, 2, 2 } }, 3) {}
+};
+
+BOOST_FIXTURE_TEST_CASE(ThreeInputBroadcast1D4D4DInputs, ThreeInputBroadcast1D4D4DInputsFixture)
+{
+    RunTest<4>({ { "input0", { 1.0 } },
+                 { "input1", { 1.0, 5.0, 2.0, 2.0 } },
+                 { "input2", { 1.0, 1.0, 2.0, 2.0 } } },
+               { { "output", { 3.0, 7.0, 5.0, 5.0 } } });
+}
+
+struct ThreeInputBroadcast4D1D4DInputsFixture : AddNFixture
+{
+    ThreeInputBroadcast4D1D4DInputsFixture() : AddNFixture({ { 1, 1, 2, 2 }, { 1 }, { 1, 1, 2, 2 } }, 3) {}
+};
+
+BOOST_FIXTURE_TEST_CASE(ThreeInputBroadcast4D1D4DInputs, ThreeInputBroadcast4D1D4DInputsFixture)
+{
+    RunTest<4>({ { "input0", { 1.0, 3.0, 9.0, 4.0 } },
+                 { "input1", {-2.0 } },
+                 { "input2", { 1.0, 1.0, 2.0, 2.0 } } },
+               { { "output", { 0.0, 2.0, 9.0, 4.0 } } });
+}
+
+struct ThreeInputBroadcast4D4D1DInputsFixture : AddNFixture
+{
+    ThreeInputBroadcast4D4D1DInputsFixture() : AddNFixture({ { 1, 1, 2, 2 }, { 1, 1, 2, 2 }, { 1 } }, 3) {}
+};
+
+BOOST_FIXTURE_TEST_CASE(ThreeInputBroadcast4D4D1DInputs, ThreeInputBroadcast4D4D1DInputsFixture)
+{
+    RunTest<4>({ { "input0", { 1.0, 5.0, 2.0, 2.0 } },
+                 { "input1", { 1.0, 1.0, 2.0, 2.0 } },
+                 { "input2", { 1.0 } } },
+               { { "output", { 3.0, 7.0, 5.0, 5.0 } } });
+}
+
+BOOST_AUTO_TEST_SUITE_END()