code: """
   Copyright (C) 2018-2021 Intel Corporation
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
  
        http://www.apache.org/licenses/LICENSE-2.0
  
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
  """
  import logging as log
  
  import numpy as np
  
  from extensions.ops.gather import Gather
  from mo.front.common.partial_infer.utils import int64_array
  from mo.front.tf.graph_utils import create_op_node_with_second_input, create_op_with_const_inputs
  from mo.graph.graph import Graph, rename_node
  from mo.middle.replacement import MiddleReplacementPattern
  from mo.ops.reshape import Reshape
  
  
  class GatherNDNormalize(MiddleReplacementPattern):
      """
      Hot fix for new speech-to-text model enabling while GatherND is not implemented in IE.
      We can replace GatherND to Reshape + Gather in case when GatherND indices have just one
      meaningful dimension.
      TODO: Investigate whether we must replace GatherND with Reshape + Gather always (due to performance benefits)
            for this particular case or only if the plugin does not support GatherND.
            And the best place for the transformation is nGraph so we need to move it.
      """
      enabled = True
      force_clean_up = True
  
      def run_before(self):
          from extensions.middle.BlockLSTMtoLSTMSequence import BlockLSTMtoLSTMSequence
          return [BlockLSTMtoLSTMSequence]
  
      def run_after(self):
          from extensions.middle.pass_separator import MiddleStart
          return [MiddleStart]
  
      def pattern(self):
          return dict(
              nodes=[('GatherND', dict(kind='op', op='GatherND', batch_dims=0))],
              edges=[]
          )
  
      @staticmethod
      def indices_check(indices: np.array, input_shape: tuple):
          """
          Check that indices have just one meaningful dimension and all other dimensions of input have size 1.
          """
          n_dims = indices.shape[-1]
          non_zero = None
          for i in range(n_dims):
              if not all(np.take(indices, indices=[i], axis=-1) == 0):
                  if non_zero is None:
                      non_zero = i
                  else:
                      return None
              else:
                  if input_shape[i] != 1:
                      return None
          return non_zero
  
      def replace_pattern(self, graph: Graph, match: dict):
          gather = match['GatherND']
          gather_name = gather.soft_get('name', gather.id)
          input_shape = gather.in_node(0).shape
          indices = gather.in_node(1).value
          if indices is None:
              # We can't do such special pass without indices value
              return
  
          # 0. All needed checks that we can replace GatherND by Gather
          gather_idx = self.indices_check(indices, input_shape)
          if gather_idx is None:
              log.warning('Node {} with op=GatherND can\'t be normalized to op=Gather.'.format(gather_name))
              return
  
          # 1. Add Reshape and connect
          new_shape = int64_array([-1] + list(input_shape[indices.shape[-1]:]))
          reshape = create_op_node_with_second_input(graph, Reshape, new_shape,
                                                     {'name': gather_name + '/Reshape_for_GatherND/'})
          gather.in_port(0).get_connection().set_destination(reshape.in_port(0))
  
          # 2. Change indices from Nd to 1d:
          new_indices = np.reshape(np.take(indices, indices=[gather_idx], axis=-1), [-1])
  
          rename_node(gather, gather_name + '/to_delete')
  
          # 3. Create new Gather operation and reconnect all inputs/outputs
          new_gather = create_op_with_const_inputs(graph, Gather, {1: new_indices, 2: int64_array(0)},
                                                   {'name': gather_name})
          rename_node(new_gather, gather_name)
  
          reshape.out_port(0).connect(new_gather.in_port(0))
  
          gather.out_port(0).get_connection().set_source(new_gather.out_port(0))
  
          # 4. Remove old Gather node
          graph.remove_node(gather.id)
   """
   Copyright (C) 2018-2021 Intel Corporation
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
  
        http://www.apache.org/licenses/LICENSE-2.0
  
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
  """
  
  import logging as log
  
  import numpy as np
  
  from mo.graph.graph import Node, Graph
  from mo.middle.passes.fusing.helpers import get_next_operation
  
  
  # TODO: unit tests
  def concat_convolutions(graph: Graph, start_node: Node, last_node: Node):
      """
      This function converts group of convolutions into one
      """
  
      # Check that concatenation makes in the same order
      conv_nodes = get_next_operation(start_node)
      assert len(conv_nodes) == len(last_node.in_nodes())
      gconv = conv_nodes[0]
  
      for id in range(len(conv_nodes)):
          conv = conv_nodes[id]
          if conv.out_node().id != last_node.in_node(id).id:
              return False
          # Check that all convolutions have same weights shapes
          if not np.array_equal(conv.in_node(1).shape, gconv.in_node(1).shape):
              log.debug('Grouped convolutions fusion : convolutions have different weights shape')
              return False
  
      # Check that split and concat dims are valid
      channel_dim = gconv.channel_dims[0]
      split_axis = start_node.in_port(1).data.get_value()
      if channel_dim != split_axis or channel_dim != last_node.axis:
          log.debug('Grouped convolutions fusion : split or concat has weird axis!')
          return False
  
      # Check that all convolutions has the same parameters
      conv_attrs = ['pad', 'stride']
      for attr in conv_attrs:
          for id in range(len(conv_nodes)):
              conv = conv_nodes[id]
              if not np.array_equal(gconv[attr], conv[attr]):
                  log.debug('Grouped convolutions fusion : attrs {} doesn\'t match'.format(attr))
                  return False
  
      # Check that all Convolutions has biases (if exists)
      has_biases = False
      for id in range(len(conv_nodes)):
          conv = conv_nodes[id]
          if len(conv.in_nodes()) == 3:
              if not has_biases:
                  has_biases = True
          elif has_biases:
              return False  # All convolution mast have biases
  
      # Check that all biases have same shape
      if has_biases:
          for id in range(len(conv_nodes)):
              conv = conv_nodes[id]
              if conv.in_node(2).shape != gconv.in_node(2).shape:
                  log.debug('Group convolutions fusion : convolutions have different biases shape {} and {}'.format(
                      conv.in_node(2).shape, gconv.in_node(2).shape))
                  return False
  
      graph.remove_edge(gconv.in_node(0).id, gconv.id)
      graph.remove_edge(gconv.id, gconv.out_node().id)
  
      input = start_node.in_node(0)
      output = last_node.out_node()
  
      # Removing edges from data nodes to Split and Concat
      graph.remove_edge(input.id, start_node.id)
      graph.remove_edge(last_node.id, output.id)
  
      # Add edges to grouped convolution
      graph.add_edges_from([
          (input.id, gconv.id, {'in': 0}),
          (gconv.id, output.id, {'out': 0})
      ])
  
      # Concatenation of convolutions
      weights_node = gconv.in_node(1)
      bias_node = gconv.in_node(2) if has_biases else None
  
      weights_value = np.array(weights_node.value)
      bias_value = np.array(bias_node.value) if has_biases else None
  
      feature_dim = 3 if graph.graph['layout'] == 'NHWC' else 0
  
      for conv in conv_nodes[1:]:
          weights_value = np.concatenate((weights_value, conv.in_node(1).value), axis=feature_dim)
          if has_biases:
              bias_value = np.concatenate((bias_value, conv.in_node(2).value), axis=-1)  # Not validated
  
      weights_node.value = np.array(weights_value)
      weights_node.shape = np.array(weights_value.shape)
  
      if has_biases:
          bias_node.value = np.array(bias_value)
          bias_node.shape = np.array(bias_value.shape)
  
      log.debug('Start node : {} Last node : {}  Nodes inside : {}'.format(start_node.id, last_node.id,
                                                                           len(start_node.out_nodes())))
      log.debug('Output shape : {}'.format(weights_value.shape))
  
      gconv.group = len(conv_nodes)
      gconv.output = weights_node.shape[feature_dim]
      gconv.output_shape[feature_dim] = weights_node.shape[feature_dim]
  
      return True
  
  
  # TODO: unit tests
  def grouped_convolutions_fusing(graph: Graph):
      while True:
          is_fused = False
          graph.clean_up()
          for node in graph.pseudo_topological_sort():
              if node.kind == 'op' and len(node.out_nodes()) > 1:
                  if node.soft_get('can_be_fused') == False:
                      continue
  
                  is_valid_convolutions = True
                  last_layer = None
  
                  next_nodes = get_next_operation(node)
                  # Check that all operation after this one are Convolutions
                  # and all convolutions has same output
                  if len(next_nodes) > 1 and all(_node.soft_get('type') in ['Convolution', 'Deconvolution'] for _node in next_nodes):
                      for conv in next_nodes:
                          conv_outputs = get_next_operation(conv)
                          if conv.soft_get('can_be_fused') == False:
                              is_valid_convolutions = False
                          if len(conv_outputs) != 1:
                              is_valid_convolutions = False
                          if last_layer is None:
                              last_layer = conv_outputs[0].id
                          # TODO: this check is not working for V10 where Biases appears as separate operations
                          elif conv_outputs[0].id != last_layer:
                              is_valid_convolutions = False
  
                      if is_valid_convolutions:
                          is_fused = concat_convolutions(graph, node, Node(graph, last_layer))
                          if is_fused:
                              break
  
          if not is_fused:
              break
   """
   Copyright (C) 2017-2021 Intel Corporation
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
  
        http://www.apache.org/licenses/LICENSE-2.0
  
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
  """
  
  import numpy as np
  
  from mo.graph.graph import Node, Graph
  from mo.ops.op import Op, PermuteAttrs
  
  
  class GatherElements(Op):
      op = 'GatherElements'
  
      def __init__(self, graph: Graph, attrs: dict):
          super().__init__(graph, {
              'op': self.op,
              'type': self.op,
              'version': 'opset6',
              'infer': self.infer,
              'in_ports_count': 2,
              'out_ports_count': 1,
              'axis': 0,
          }, attrs)
  
      def backend_attrs(self):
          return ['axis']
  
      @staticmethod
      def infer(node: Node):
          data_shape = node.in_port(0).data.get_shape()
          indices_shape = node.in_port(1).data.get_shape()
          axis = node.axis
          data_rank = len(data_shape)
  
          assert data_rank >= 1, 'data_rank must be >= 1'
          assert data_rank == len(indices_shape), 'data and indices inputs for node {} must be of the ' \
                                                  'same rank. Instead got {} and {}'.\
                                                  format(node.name, data_rank, len(indices_shape))
          assert -data_rank <= axis < data_rank, 'axis for node {0} must be within interval ' \
                                                 '[-{1}},  {1} - 1]. Instead got: axis={2}'.\
                                                 format(node.name, data_rank, axis)
          if axis < 0:
              axis += data_rank
  
          for idx, (data_sz, ind_sz) in enumerate(zip(data_shape, indices_shape)):
              if idx != axis and data_sz != ind_sz:
                  raise ValueError('Sizes along axis {} for node {} do not match. data and indices must have ' 
                                   'equal size along all axes except for axis {}'.format(idx, node.name, axis))
  
          data = node.in_port(0).data.get_value()
          indices = node.in_port(1).data.get_value()
  
          if data is not None and indices is not None:
              out_value = np.empty(indices_shape, dtype=data.dtype)
              for idx in np.ndindex(*indices_shape):
                  data_idx = list(idx)
                  data_idx[node.axis] = indices[idx]
                  out_value[idx] = data[tuple(data_idx)]
              node.out_port(0).data.set_value(out_value)
          else:
              node.out_port(0).data.set_shape(indices_shape)
  
          PermuteAttrs.create_permute_attrs(node, attrs=[('axis', 'input:0')])
   """
   Copyright (C) 2018-2021 Intel Corporation
  
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at
  
        http://www.apache.org/licenses/LICENSE-2.0
  
   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
  """
  
  import numpy as np
  
  from extensions.ops.elementwise import Add, Mul
  from mo.front.common.replacement import FrontReplacementPattern
  from mo.graph.graph import Graph
  from mo.ops.const import Const
  
  
  class BinaryFakeQuantizeNormalization(FrontReplacementPattern):
      """
      FakeQuantize in binary form has exceptional meaning of 1 and 2 input nodes.
      This nodes values should be equal and express threshold to quantize tensors to two levels..
      """
      enabled = True
  
      @staticmethod
      def pattern():
          return dict(
              nodes=[
                  ('min_in', dict()),
                  ('max_in', dict()),
                  ('quantize', dict(op='FakeQuantize', levels=2))],
              edges=[
                  ('min_in', 'quantize', {'in': 1}),
                  ('max_in', 'quantize', {'in': 2})
              ]
          )
  
      def replace_pattern(self, graph: Graph, match: dict):
          quantize = match['quantize']
  
          sum_node = Add(graph, dict()).create_node()
          const = Const(graph, {'value': np.array(0.5)}).create_node()
          mul_node = Mul(graph, dict()).create_node()
  
          mul_node.in_port(0).connect(sum_node.out_port(0))
          mul_node.in_port(1).connect(const.out_port(0))
  
          quantize.in_port(1).get_connection().get_source().connect(sum_node.in_port(0))
          quantize.in_port(2).get_connection().get_source().connect(sum_node.in_port(1))
  
          quantize.in_port(1).disconnect()
          quantize.in_port(2).disconnect()
  
          mul_node.out_port(0).connect(quantize.in_port(1))
          mul_node.out_port(0).connect(quantize.in_port(2))
  
apis: numpy.take
apis: numpy.array numpy.array_equal
apis: numpy.empty numpy.ndindex
apis: numpy.array