MLECO-2488: added model optimization options and updated OptimizerOptions constructor.

Signed-off-by: alexander <alexander.efremov@arm.com>
Change-Id: Ic2ad6a46c3830f2526ba8b20ca0db0780be4b9a2
diff --git a/python/pyarmnn/src/pyarmnn/__init__.py b/python/pyarmnn/src/pyarmnn/__init__.py
index 13fdf95..4499252 100644
--- a/python/pyarmnn/src/pyarmnn/__init__.py
+++ b/python/pyarmnn/src/pyarmnn/__init__.py
@@ -53,6 +53,7 @@
 # Backend
 from ._generated.pyarmnn import BackendId
 from ._generated.pyarmnn import IDeviceSpec
+from ._generated.pyarmnn import BackendOptions, BackendOption
 
 # Tensors
 from ._generated.pyarmnn import TensorInfo, TensorShape
@@ -65,7 +66,8 @@
 
 # Types
 from ._generated.pyarmnn import DataType_Float16, DataType_Float32, DataType_QAsymmU8, DataType_Signed32, \
-    DataType_Boolean, DataType_QSymmS16, DataType_QSymmS8, DataType_QAsymmS8
+    DataType_Boolean, DataType_QSymmS16, DataType_QSymmS8, DataType_QAsymmS8, ShapeInferenceMethod_ValidateOnly, \
+    ShapeInferenceMethod_InferAndValidate
 from ._generated.pyarmnn import DataLayout_NCHW, DataLayout_NHWC
 from ._generated.pyarmnn import MemorySource_Malloc, MemorySource_Undefined, MemorySource_DmaBuf, \
     MemorySource_DmaBufProtected
diff --git a/python/pyarmnn/src/pyarmnn/swig/armnn.i b/python/pyarmnn/src/pyarmnn/swig/armnn.i
index 172d2d8..c525e17 100644
--- a/python/pyarmnn/src/pyarmnn/swig/armnn.i
+++ b/python/pyarmnn/src/pyarmnn/swig/armnn.i
@@ -14,6 +14,7 @@
 
 //armnn api submodules
 %include "modules/armnn_backend.i"
+%include "modules/armnn_backend_opt.i"
 %include "modules/armnn_types.i"
 %include "modules/armnn_descriptors.i"
 %include "modules/armnn_lstmparam.i"
diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend_opt.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend_opt.i
new file mode 100644
index 0000000..2ff54aa
--- /dev/null
+++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_backend_opt.i
@@ -0,0 +1,103 @@
+//
+// Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+%{
+#include "armnn/BackendId.hpp"
+#include "armnn/BackendOptions.hpp"
+%}
+
+#pragma SWIG nowarn=SWIGWARN_PARSE_NESTED_CLASS
+
+%{
+    typedef armnn::BackendOptions::BackendOption BackendOption;
+%}
+
+%feature("docstring",
+"
+Struct for the users to pass backend specific option.
+") BackendOption;
+%nodefaultctor BackendOption;
+struct BackendOption
+{
+    BackendOption(std::string name, bool value);
+    BackendOption(std::string name, int value);
+    BackendOption(std::string name, unsigned int value);
+    BackendOption(std::string name, float value);
+    BackendOption(std::string name, std::string value);
+
+    std::string GetName();
+};
+
+namespace armnn
+{
+%feature("docstring",
+"
+Struct for backend specific options, see `BackendOption`.
+Options are assigned to a specific backend by providing a backend id.
+
+") BackendOptions;
+%nodefaultctor BackendOptions;
+struct BackendOptions
+{
+    BackendOptions(BackendId backend);
+
+    BackendOptions(const BackendOptions& other);
+    
+    %feature("docstring",
+    "
+    Add backend option.
+    
+    Args:
+       option (`BackendOption`): backend option
+    ") AddOption;
+    void AddOption(const BackendOption& option);
+    
+    %feature("docstring",
+    "
+    Get a backend id.
+    
+    Returns:
+        BackendId: assigned backend id.
+    ") GetBackendId;
+    const BackendId& GetBackendId();
+
+    %feature("docstring",
+    "
+    Get backend options count.
+
+    Returns:
+        int: number of options for a backend.
+    ") GetOptionCount;
+    size_t GetOptionCount();
+
+    %feature("docstring",
+    "
+    Get backend option by index.
+
+    Args:
+       idx (int): backend option index
+
+    Returns:
+        BackendOption: backend option.
+    ") GetOption;
+    const BackendOption& GetOption(size_t idx);
+
+    %pythoncode %{
+    def __iter__(self):
+        for count in range(self.GetOptionCount()):
+            yield self[count]
+    %}
+};
+
+%extend BackendOptions {
+
+    const BackendOption& __getitem__(size_t i) const {
+        return $self->GetOption(i);
+    }
+
+    size_t __len__() const {
+        return $self->GetOptionCount();
+    }
+}
+}
diff --git a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i
index f4581ca..d50b841 100644
--- a/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i
+++ b/python/pyarmnn/src/pyarmnn/swig/modules/armnn_network.i
@@ -11,6 +11,11 @@
 %}
 
 %include <typemaps/network_optimize.i>
+%include <typemaps/model_options.i>
+
+namespace std {
+    %template() std::vector<armnn::BackendOptions>;
+}
 
 namespace armnn
 {
@@ -24,9 +29,19 @@
                                that can not be reduced will be left in Fp32.
     m_ReduceFp32ToFp16 (bool): Reduces Fp32 network to Fp16 for faster processing. Layers
                                that can not be reduced will be left in Fp32.
-    m_ImportEnabled (bool): Enable memory import.
+    m_ImportEnabled (bool):    Enable memory import.
+    m_shapeInferenceMethod:    The ShapeInferenceMethod modifies how the output shapes are treated.
+                               When ValidateOnly is selected, the output shapes are inferred from the input parameters
+                               of the layer and any mismatch is reported.
+                               When InferAndValidate is selected 2 actions are performed: (1)infer output shape from
+                               inputs and (2)validate the shapes as in ValidateOnly. This option has been added to work
+                               with tensors which rank or dimension sizes are not specified explicitly, however this
+                               information can be calculated from the inputs.
+    m_ModelOptions:            List of backends optimisation options.
 
 ") OptimizerOptions;
+
+%model_options_typemap;
 struct OptimizerOptions
 {
     OptimizerOptions();
@@ -34,13 +49,18 @@
     OptimizerOptions(bool reduceFp32ToFp16,
                      bool debug,
                      bool reduceFp32ToBf16 = false,
-                     bool importEnabled = false);
+                     ShapeInferenceMethod shapeInferenceMethod = armnn::ShapeInferenceMethod::ValidateOnly,
+                     bool importEnabled = false,
+                     std::vector<armnn::BackendOptions> modelOptions = {});
 
     bool m_ReduceFp32ToBf16;
     bool m_ReduceFp32ToFp16;
     bool m_Debug;
+    ShapeInferenceMethod m_shapeInferenceMethod;
     bool m_ImportEnabled;
+    std::vector<armnn::BackendOptions> m_ModelOptions;
 };
+%model_options_clear;
 
 %feature("docstring",
 "
diff --git a/python/pyarmnn/src/pyarmnn/swig/typemaps/model_options.i b/python/pyarmnn/src/pyarmnn/swig/typemaps/model_options.i
new file mode 100644
index 0000000..c4acaef
--- /dev/null
+++ b/python/pyarmnn/src/pyarmnn/swig/typemaps/model_options.i
@@ -0,0 +1,55 @@
+//
+// Copyright © 2021 Arm Ltd. All rights reserved.
+// SPDX-License-Identifier: MIT
+//
+%inline %{
+
+   static PyObject* from_model_options_to_python(std::vector<armnn::BackendOptions>* input) {
+        Py_ssize_t size = input->size();
+        PyObject* localList = PyList_New(size);
+
+        if (!localList) {
+            Py_XDECREF(localList);
+            return PyErr_NoMemory();
+        }
+
+        for(Py_ssize_t i = 0; i < size; ++i) {
+
+            PyObject* obj = SWIG_NewPointerObj(SWIG_as_voidptr(&input->at(i)), SWIGTYPE_p_armnn__BackendOptions, 0 |  0 );
+
+            PyList_SET_ITEM(localList, i, obj);
+        }
+        return localList;
+    }
+%}
+
+%define %model_options_typemap
+
+// this typemap works for struct argument get
+
+    %typemap(out) std::vector<armnn::BackendOptions>* {
+        $result = from_model_options_to_python($1);
+    }
+
+// this typemap works for struct argument set
+    %typemap(in) std::vector<armnn::BackendOptions>* {
+        if (PySequence_Check($input)) {
+
+            int res = swig::asptr($input, &$1);
+            if (!SWIG_IsOK(res) || !$1) {
+                SWIG_exception_fail(SWIG_ArgError(($1 ? res : SWIG_TypeError)),
+                    "in method '" "OptimizerOptions_m_ModelOptions_set" "', argument " "2"" of type '" "std::vector< armnn::BackendOptions,std::allocator< armnn::BackendOptions > > *""'");
+            }
+
+        } else {
+            PyErr_SetString(PyExc_TypeError, "Argument value object does not provide sequence protocol.");
+            SWIG_fail;
+        }
+    }
+
+%enddef
+
+%define %model_options_clear
+    %typemap(out) std::vector<armnn::BackendOptions>*;
+    %typemap(in) std::vector<armnn::BackendOptions>*;
+%enddef
diff --git a/python/pyarmnn/test/test_modeloption.py b/python/pyarmnn/test/test_modeloption.py
new file mode 100644
index 0000000..c03d4a8
--- /dev/null
+++ b/python/pyarmnn/test/test_modeloption.py
@@ -0,0 +1,149 @@
+# Copyright © 2021 Arm Ltd and Contributors. All rights reserved.
+# SPDX-License-Identifier: MIT
+import pytest
+
+from pyarmnn import BackendOptions, BackendOption, BackendId, OptimizerOptions, ShapeInferenceMethod_InferAndValidate
+
+
+@pytest.mark.parametrize("data", (True, -100, 128, 0.12345, 'string'))
+def test_backend_option_ctor(data):
+    bo = BackendOption("name", data)
+    assert "name" == bo.GetName()
+
+
+def test_backend_options_ctor():
+    backend_id = BackendId('a')
+    bos = BackendOptions(backend_id)
+
+    assert 'a' == str(bos.GetBackendId())
+
+    another_bos = BackendOptions(bos)
+    assert 'a' == str(another_bos.GetBackendId())
+
+
+def test_backend_options_add():
+    backend_id = BackendId('a')
+    bos = BackendOptions(backend_id)
+    bo = BackendOption("name", 1)
+    bos.AddOption(bo)
+
+    assert 1 == bos.GetOptionCount()
+    assert 1 == len(bos)
+
+    assert 'name' == bos[0].GetName()
+    assert 'name' == bos.GetOption(0).GetName()
+    for option in bos:
+        assert 'name' == option.GetName()
+
+    bos.AddOption(BackendOption("name2", 2))
+
+    assert 2 == bos.GetOptionCount()
+    assert 2 == len(bos)
+
+
+def test_backend_option_ownership():
+    backend_id = BackendId('b')
+    bos = BackendOptions(backend_id)
+    bo = BackendOption('option', True)
+    bos.AddOption(bo)
+
+    assert bo.thisown
+
+    del bo
+
+    assert 1 == bos.GetOptionCount()
+    option = bos[0]
+    assert not option.thisown
+    assert 'option' == option.GetName()
+
+    del option
+
+    option_again = bos[0]
+    assert not option_again.thisown
+    assert 'option' == option_again.GetName()
+
+
+def test_optimizer_options_with_model_opt():
+    a = BackendOptions(BackendId('a'))
+
+    oo = OptimizerOptions(True,
+                          False,
+                          False,
+                          ShapeInferenceMethod_InferAndValidate,
+                          True,
+                          [a])
+
+    mo = oo.m_ModelOptions
+
+    assert 1 == len(mo)
+    assert 'a' == str(mo[0].GetBackendId())
+
+    b = BackendOptions(BackendId('b'))
+
+    c = BackendOptions(BackendId('c'))
+
+    oo.m_ModelOptions = (a, b, c)
+
+    mo = oo.m_ModelOptions
+
+    assert 3 == len(oo.m_ModelOptions)
+
+    assert 'a' == str(mo[0].GetBackendId())
+    assert 'b' == str(mo[1].GetBackendId())
+    assert 'c' == str(mo[2].GetBackendId())
+
+
+def test_optimizer_option_default():
+    oo = OptimizerOptions(True,
+                          False,
+                          False,
+                          ShapeInferenceMethod_InferAndValidate,
+                          True)
+
+    assert 0 == len(oo.m_ModelOptions)
+
+
+def test_optimizer_options_fail():
+    a = BackendOptions(BackendId('a'))
+
+    with pytest.raises(TypeError) as err:
+        OptimizerOptions(True,
+                         False,
+                         False,
+                         ShapeInferenceMethod_InferAndValidate,
+                         True,
+                         a)
+
+    assert "Wrong number or type of arguments" in str(err.value)
+
+    with pytest.raises(RuntimeError) as err:
+        OptimizerOptions(True,
+                         False,
+                         True,
+                         ShapeInferenceMethod_InferAndValidate,
+                         True,
+                         [a])
+
+    assert "BFloat16 and Float16 optimization cannot be enabled at the same time" in str(err.value)
+
+    with pytest.raises(TypeError) as err:
+        oo = OptimizerOptions(True,
+                              False,
+                              False,
+                              ShapeInferenceMethod_InferAndValidate,
+                              True)
+
+        oo.m_ModelOptions = 'nonsense'
+
+    assert "in method 'OptimizerOptions_m_ModelOptions_set', argument 2" in str(err.value)
+
+    with pytest.raises(TypeError) as err:
+        oo = OptimizerOptions(True,
+                              False,
+                              False,
+                              ShapeInferenceMethod_InferAndValidate,
+                              True)
+
+        oo.m_ModelOptions = ['nonsense', a]
+
+    assert "in method 'OptimizerOptions_m_ModelOptions_set', argument 2" in str(err.value)
diff --git a/python/pyarmnn/test/test_network.py b/python/pyarmnn/test/test_network.py
index c24b113..9b2dbf7 100644
--- a/python/pyarmnn/test/test_network.py
+++ b/python/pyarmnn/test/test_network.py
@@ -6,12 +6,15 @@
 import pytest
 import pyarmnn as ann
 
+
 def test_optimizer_options_default_values():
     opt = ann.OptimizerOptions()
     assert opt.m_ReduceFp32ToFp16 == False
     assert opt.m_Debug == False
     assert opt.m_ReduceFp32ToBf16 == False
     assert opt.m_ImportEnabled == False
+    assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly
+
 
 def test_optimizer_options_set_values1():
     opt = ann.OptimizerOptions(True, True)
@@ -19,6 +22,8 @@
     assert opt.m_Debug == True
     assert opt.m_ReduceFp32ToBf16 == False
     assert opt.m_ImportEnabled == False
+    assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly
+
 
 def test_optimizer_options_set_values2():
     opt = ann.OptimizerOptions(False, False, True)
@@ -26,13 +31,17 @@
     assert opt.m_Debug == False
     assert opt.m_ReduceFp32ToBf16 == True
     assert opt.m_ImportEnabled == False
+    assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_ValidateOnly
+
 
 def test_optimizer_options_set_values3():
-    opt = ann.OptimizerOptions(False, False, True, True)
+    opt = ann.OptimizerOptions(False, False, True, ann.ShapeInferenceMethod_InferAndValidate, True)
     assert opt.m_ReduceFp32ToFp16 == False
     assert opt.m_Debug == False
     assert opt.m_ReduceFp32ToBf16 == True
     assert opt.m_ImportEnabled == True
+    assert opt.m_shapeInferenceMethod == ann.ShapeInferenceMethod_InferAndValidate
+
 
 @pytest.fixture(scope="function")
 def get_runtime(shared_data_folder, network_file):
diff --git a/python/pyarmnn/test/test_onnx_parser.py b/python/pyarmnn/test/test_onnx_parser.py
index 99353a0..1424c24 100644
--- a/python/pyarmnn/test/test_onnx_parser.py
+++ b/python/pyarmnn/test/test_onnx_parser.py
@@ -5,7 +5,6 @@
 import pytest
 import pyarmnn as ann
 import numpy as np
-from typing import List
 
 
 @pytest.fixture()
diff --git a/python/pyarmnn/test/test_runtime.py b/python/pyarmnn/test/test_runtime.py
index 295c870..fbdd804 100644
--- a/python/pyarmnn/test/test_runtime.py
+++ b/python/pyarmnn/test/test_runtime.py
@@ -97,6 +97,7 @@
 
     assert not opt_network.thisown
 
+
 def test_load_network(random_runtime):
     preferred_backends = random_runtime[0]
     network = random_runtime[1]
@@ -109,6 +110,7 @@
     assert "" == messages
     assert net_id == 0
 
+
 def test_create_runtime_with_external_profiling_enabled():
 
     options = ann.CreationOptions()
@@ -125,6 +127,7 @@
 
     assert runtime is not None
 
+
 def test_create_runtime_with_external_profiling_enabled_invalid_options():
 
     options = ann.CreationOptions()
@@ -157,6 +160,7 @@
     assert "" == messages
     assert net_id == 0
 
+
 def test_network_properties_constructor(random_runtime):
     preferred_backends = random_runtime[0]
     network = random_runtime[1]
@@ -178,10 +182,12 @@
     assert "" == messages
     assert net_id == 0
 
+
 def test_network_properties_deprecated_constructor():
     with pytest.warns(DeprecationWarning):
         warnings.warn("Deprecated: Use constructor with MemorySource argument instead.", DeprecationWarning)
 
+
 def test_unload_network_fails_for_invalid_net_id(random_runtime):
     preferred_backends = random_runtime[0]
     network = random_runtime[1]
diff --git a/python/pyarmnn/test/test_supported_backends.py b/python/pyarmnn/test/test_supported_backends.py
index e1ca5ee..4ff5eb0 100644
--- a/python/pyarmnn/test/test_supported_backends.py
+++ b/python/pyarmnn/test/test_supported_backends.py
@@ -1,7 +1,5 @@
 # Copyright © 2020 Arm Ltd. All rights reserved.
 # SPDX-License-Identifier: MIT
-import os
-import platform
 import pytest
 import pyarmnn as ann
 
diff --git a/python/pyarmnn/test/test_tflite_parser.py b/python/pyarmnn/test/test_tflite_parser.py
index 8735eef..2764f3f 100644
--- a/python/pyarmnn/test/test_tflite_parser.py
+++ b/python/pyarmnn/test/test_tflite_parser.py
@@ -48,6 +48,7 @@
     parserOptions.m_InferAndValidate = True
     return ann.ITfLiteParser(parserOptions)
 
+
 def test_tflite_parser_with_optional_options_out_of_scope(shared_data_folder):
     parser = create_with_opt()
     network = parser.CreateNetworkFromBinaryFile(os.path.join(shared_data_folder, "mock_model.tflite"))