from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import math
import unittest
import numpy as np
import tensorflow as tf
from onnx_tf.backend import onnx_graph_to_tensorflow_rep
from onnx_tf.backend import run_node
from onnx_tf.common import supports_device
from onnx_tf.common.legacy import legacy_onnx_pre_ver, legacy_opset_pre_ver
from onnx_tf.common.pooling_helper import py_pool
from onnx import helper
from onnx import TensorProto
from onnx import defs


class TestNode(unittest.TestCase):
  """ Tests for nodes
  """

  def _get_rnd_float32(self, low=-1.0, high=1.0, shape=None):
    output = np.random.uniform(low, high, shape)
    if shape is None:
      return np.float32(output)
    else:
      return output.astype(np.float32)

  def _get_rnd_int(self, low, high=None, shape=None, dtype=np.int32):
    return np.random.randint(low, high, size=shape, dtype=dtype)

  def _elu(self, x):
    # f(x) = alpha * (exp(x) - 1.) for x < 0,
    # f(x) = x for x >= 0
    if x < 0.:
      return np.expm1(x)
    return x

  def _leaky_relu(self, x, alpha):
    # f(x) = alpha * x for x < 0,
    # f(x) = x for x >= 0
    if x < 0.:
      return alpha * x
    return x

  def test_abs(self):
    node_def = helper.make_node("Abs", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.abs(x))

  def test_acosh(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Acosh.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Acosh", ["X"], ["Y"])
    x = self._get_rnd_float32(low=1.0, high=np.finfo(np.float32).max, shape=[3, 4, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.arccosh(x))

  def test_add(self):
    node_def = helper.make_node("Add", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=[5, 10, 5, 5])
    y = self._get_rnd_float32(shape=[10, 1, 1])
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"],
                                   np.add(x, y.reshape([1, 10, 1, 1])))

    # node_def = helper.make_node("Add", ["A", "B"], ["C"], broadcast=1)
    # a = self._get_rnd([10, 10])
    # b = self._get_rnd([10, 10])
    # output = run_node(node_def, [a, b])
    # np.testing.assert_almost_equal(output["C"], np.add(a, b))

    # node_def = helper.make_node("Add", ["A", "B"], ["C"], broadcast=1)
    # a = self._get_rnd([10, 10])
    # b = self._get_rnd([10,])
    # output = run_node(node_def, [a, b])
    # np.testing.assert_almost_equal(output["C"], np.add(a, b))

  def test_arg_max(self):
    for axis in [0, 1]:
      node_def = helper.make_node("ArgMax", ["data"], ["reduced"],
                                  axis=axis,
                                  keepdims=0)
      data = self._get_rnd_float32(shape=[10, 10])
      output = run_node(node_def, [data])
      np.testing.assert_almost_equal(output["reduced"],
                                     np.argmax(data, axis=axis))
      # test select_last_index
      if not legacy_opset_pre_ver(12):
        # select_last_index = 0
        node_def = helper.make_node("ArgMax", ["data"], ["reduced"],
                                    axis=axis,
                                    keepdims=0,
                                    select_last_index=0)
        data = self._get_rnd_float32(shape=[10, 10])
        output = run_node(node_def, [data])
        np.testing.assert_almost_equal(output["reduced"],
                                       np.argmax(data, axis=axis))
        # select_last_index = 1
        node_def = helper.make_node("ArgMax", ["data"], ["reduced"],
                                    axis=axis,
                                    keepdims=0,
                                    select_last_index=1)
        data = np.array([[1, 2, 3, 5, 3, 4, 5, 1], [2, 9, 3, 5, 9, 4, 5, 1]])
        output = run_node(node_def, [data])
        data = np.flip(data, axis)
        result = np.argmax(data, axis=axis)
        result = data.shape[axis] - result - 1
        np.testing.assert_almost_equal(output["reduced"], result)

  def test_arg_min(self):
    for axis in [0, 1]:
      node_def = helper.make_node("ArgMin", ["data"], ["reduced"],
                                  axis=axis,
                                  keepdims=0)
      data = self._get_rnd_float32(shape=[10, 10])
      output = run_node(node_def, [data])
      np.testing.assert_almost_equal(output["reduced"],
                                     np.argmin(data, axis=axis))
      # test select_last_index
      if not legacy_opset_pre_ver(12):
        # select_last_index = 0
        node_def = helper.make_node("ArgMin", ["data"], ["reduced"],
                                    axis=axis,
                                    keepdims=0,
                                    select_last_index=0)
        data = self._get_rnd_float32(shape=[10, 10])
        output = run_node(node_def, [data])
        np.testing.assert_almost_equal(output["reduced"],
                                       np.argmin(data, axis=axis))
        # select_last_index = 1
        node_def = helper.make_node("ArgMin", ["data"], ["reduced"],
                                    axis=axis,
                                    keepdims=0,
                                    select_last_index=1)
        data = np.array([[1, 2, 3, 5, 3, 4, 5, 1], [2, 7, 3, 5, 2, 4, 5, 6]])
        output = run_node(node_def, [data])
        data = np.flip(data, axis)
        result = np.argmin(data, axis=axis)
        result = data.shape[axis] - result - 1
        np.testing.assert_almost_equal(output["reduced"], result)

  def test_asinh(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Asinh.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Asinh", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.arcsinh(x))

  def test_atanh(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Atanh.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Atanh", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.arctanh(x))

  def _batch_normalization(self, x, mean, variance, bias, scale,
                           variance_epsilon):
    inv = np.reciprocal(np.sqrt(variance + variance_epsilon))
    if scale is not None:
      inv *= scale
    return x * inv + (bias - mean * inv if bias is not None else -mean * inv)

  def test_batch_normalization(self):
    if legacy_opset_pre_ver(6):
      raise unittest.SkipTest("Backend doesn't support consumed flag")
    node_def = helper.make_node("BatchNormalization",
                                ["X", "scale", "bias", "mean", "var"], ["Y"],
                                epsilon=0.001)
    x_shape = [3, 5, 4, 2]
    param_shape = [5]
    _param_shape = [1, 5, 1, 1]
    x = self._get_rnd_float32(0, 1, shape=x_shape)
    m = self._get_rnd_float32(0, 1, shape=param_shape)
    _m = m.reshape(_param_shape)
    v = self._get_rnd_float32(0, 1, shape=param_shape)
    _v = v.reshape(_param_shape)
    scale = self._get_rnd_float32(0, 1, shape=param_shape)
    _scale = scale.reshape(_param_shape)
    bias = self._get_rnd_float32(0, 1, shape=param_shape)
    _bias = bias.reshape(_param_shape)
    golden = self._batch_normalization(x, _m, _v, _bias, _scale, 0.001)
    output = run_node(node_def, [x, scale, bias, m, v])
    np.testing.assert_almost_equal(output["Y"], golden, decimal=5)

  def test_cast(self):
    if legacy_onnx_pre_ver(1, 2) or legacy_opset_pre_ver(6):
      test_cases = [("FLOAT", tf.float32), ("UINT8", tf.uint8),
                    ("INT8", tf.int8),
                    ("UINT16", tf.uint16), ("INT16", tf.int16),
                    ("INT32", tf.int32), ("INT64", tf.int64), ("BOOL", tf.bool),
                    ("FLOAT16", tf.float16), ("DOUBLE", tf.float64),
                    ("COMPLEX64", tf.complex64), ("COMPLEX128", tf.complex128)]
    else:
      test_cases = [(TensorProto.FLOAT, tf.float32),
                    (TensorProto.UINT8, tf.uint8), (TensorProto.INT8, tf.int8),
                    (TensorProto.UINT16, tf.uint16),
                    (TensorProto.INT16, tf.int16),
                    (TensorProto.INT32, tf.int32),
                    (TensorProto.INT64, tf.int64), (TensorProto.BOOL, tf.bool),
                    (TensorProto.FLOAT16, tf.float16),
                    (TensorProto.DOUBLE, tf.float64),
                    (TensorProto.COMPLEX64, tf.complex64),
                    (TensorProto.COMPLEX128, tf.complex128)]
      if not legacy_opset_pre_ver(9):
        test_cases.append((TensorProto.STRING, tf.string))
    for ty, tf_type in test_cases:
      node_def = helper.make_node("Cast", ["input"], ["output"], to=ty)
      vector = [2, 3]
      output = run_node(node_def, [vector])
      np.testing.assert_equal(output["output"].dtype, tf_type)

    if not legacy_opset_pre_ver(9):
      test_cases2 = [(TensorProto.FLOAT, tf.float32),
                     (TensorProto.INT32, tf.int32),
                     (TensorProto.INT64, tf.int64),
                     (TensorProto.DOUBLE, tf.float64)]
      for ty, tf_type in test_cases2:
        node_def = helper.make_node("Cast", ["input"], ["output"], to=ty)
        vector = ['2', '3']
        output = run_node(node_def, [vector])
        np.testing.assert_equal(output["output"].dtype, tf_type)

  def test_ceil(self):
    node_def = helper.make_node("Ceil", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.ceil(x))

  def test_compress(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support Compress.".format(
              defs.onnx_opset_version()))
    axis = 1
    node_def = helper.make_node("Compress",
                                inputs=['X', 'condition'],
                                outputs=['Y'],
                                axis=axis)
    x = self._get_rnd_float32(shape=[5, 5, 5])
    cond = np.array([1, 0, 1])
    output = run_node(node_def, inputs=[x, cond])
    np.testing.assert_almost_equal(output['Y'], np.compress(cond, x, axis=axis))

  def test_concat(self):
    shape = [10, 20, 5]
    for axis in range(len(shape)):
      node_def = helper.make_node("Concat", ["X1", "X2"], ["Y"], axis=axis)
      x1 = self._get_rnd_float32(shape=shape)
      x2 = self._get_rnd_float32(shape=shape)
      output = run_node(node_def, [x1, x2])
      np.testing.assert_almost_equal(output["Y"], np.concatenate((x1, x2),
                                                                 axis))

  def test_constant(self):
    shape = [16, 16]
    values = np.random.randn(*shape).flatten().astype(float)
    const2_onnx = helper.make_tensor("const2", TensorProto.DOUBLE, shape,
                                     values)
    node_def = helper.make_node("Constant", [], ["Y"], value=const2_onnx)
    output = run_node(node_def, [])
    np.testing.assert_equal(output["Y"].shape, shape)
    np.testing.assert_almost_equal(output["Y"].flatten(), values)

    # test sparse tensor
    if not legacy_opset_pre_ver(11):
      expected = np.array([[1, 0, 0, 0], [0, 0, 2, 0], [0, 0, 0, 0]])
      x = np.array([[0, 0], [1, 2]]).flatten().astype(np.int64)
      values = helper.make_tensor("values", TensorProto.INT32, [2], [1, 2])
      indices = helper.make_tensor("indices", TensorProto.INT64, [2, 2], x)
      a = helper.make_sparse_tensor(values, indices, [3, 4])
      node_def = helper.make_node("Constant", [], ["Y"], sparse_value=a)
      output = run_node(node_def, [])
      b = tf.sparse_to_dense(output["Y"].indices, output["Y"].dense_shape,
                             output["Y"].values)
      result = b.eval(session=tf.Session())
      np.testing.assert_equal(result, expected)

    if not legacy_opset_pre_ver(12):
      float_attr = 1.0
      floats_attr = [1.0, 2.0, 3.0]
      int_attr = np.int64(123)
      ints_attr = [np.int64(4), np.int64(5), np.int64(6)]
      string_attr = 'The Cat in the Hat'
      strings_attr = [
          'Green Eggs and Ham', 'How the Grinch Stole Christmas!',
          'The Cat in the Hat Comes Back'
      ]
      testcases = [(helper.make_node("Constant", [], ["Y"],
                                     value_float=float_attr), float_attr),
                   (helper.make_node("Constant", [], ["Y"],
                                     value_floats=floats_attr), floats_attr),
                   (helper.make_node("Constant", [], ["Y"],
                                     value_int=int_attr), int_attr),
                   (helper.make_node("Constant", [], ["Y"],
                                     value_ints=ints_attr), ints_attr),
                   (helper.make_node("Constant", [], ["Y"],
                                     value_string=string_attr), string_attr),
                   (helper.make_node("Constant", [], ["Y"],
                                     value_strings=strings_attr), strings_attr)]
      for node_def, expected in testcases:
        output = run_node(node_def, [])
        if isinstance(expected, str):
          np.testing.assert_string_equal(output["Y"].decode('UTF-8'), expected)
        elif isinstance(expected, list) and isinstance(expected[0], str):
          for i in range(len(expected)):
            np.testing.assert_string_equal(output['Y'][i].decode('UTF-8'),
                                           expected[i])
        else:
          np.testing.assert_equal(output["Y"], expected)

  def test_constant_fill(self):
    if not legacy_opset_pre_ver(9):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support ConstantFill.".format(
              defs.onnx_opset_version()))
    shape = [1, 2, 3, 4]
    extra_shape = [5, 6]
    value = 3.
    node_def = helper.make_node(
        "ConstantFill",
        ["X"],
        ["Y"],
        value=value,
        extra_shape=extra_shape,
        dtype=1,
    )
    x = self._get_rnd_float32(shape=shape)
    y = np.zeros(shape + extra_shape)
    y.fill(value)
    output = run_node(node_def, [x])
    np.testing.assert_equal(output["Y"].dtype, tf.float32)
    np.testing.assert_equal(output["Y"], y)

  def test_constant_of_shape(self):
    if defs.onnx_opset_version() < 9:
      raise unittest.SkipTest(
          "ONNX version {} doesn't support ConstantOfShape.".format(
              defs.onnx_opset_version()))
    v = helper.make_tensor("value", TensorProto.FLOAT, [1], [1])
    node_def = helper.make_node("ConstantOfShape", ["X"], ["Y"], value=v)
    x = np.array([4, 3, 2])
    output = run_node(node_def, inputs=[x])
    np.testing.assert_almost_equal(output["Y"], np.ones(x, dtype=np.float32))
    v = helper.make_tensor("value", TensorProto.INT32, [1], [0])
    node_def = helper.make_node("ConstantOfShape", ["X"], ["Y"], value=v)
    x = np.array([10, 6])
    output = run_node(node_def, inputs=[x])
    np.testing.assert_almost_equal(output["Y"], np.zeros(x, dtype=np.int32))

  def test_conv(self):
    device = "CUDA" if supports_device("CUDA") else "CPU"

    N, C, H, W = 4, 3, 5, 5
    x_shape = [N, C, H, W]
    K, kH, kW = 6, 3, 3
    weight_shape = [K, C, kH, kW]
    node_def = helper.make_node("Conv", ["X", "weights"], ["Y"],
                                pads=[1, 1, 1, 1],
                                kernel_shape=[kH, kW])

    x = self._get_rnd_float32(shape=x_shape)
    weights = self._get_rnd_float32(shape=weight_shape)
    output = run_node(node_def, [x, weights], device=device)

    out_shape = [N, K, H, W]
    test_output = np.zeros(out_shape)
    for n in range(N):
      for c in range(C):
        for h in range(H):
          for w in range(W):
            for k in range(K):
              for kh in range(kH):
                for kw in range(kW):
                  h_in_range = (h - kH // 2 + kh) < H and (h - kH // 2 +
                                                           kh) >= 0
                  w_in_range = (w - kW // 2 + kw) < W and (w - kW // 2 +
                                                           kw) >= 0
                  if h_in_range and w_in_range:
                    test_output[n][k][h][w] += (
                        x[n][c][h - kH // 2 + kh][w - kW // 2 + kw] *
                        weights[k][c][kh][kw])

    np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)

  def test_conv_integer(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support ConvInteger.".format(
              defs.onnx_opset_version()))

    # Test w_zero_point
    x = np.array([2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.int8).reshape(
        (1, 1, 3, 3))
    w = np.array([2, 2, 2, 2]).astype(np.int8).reshape((1, 1, 2, 2))
    w_zero_point = np.int8(1)
    y = np.array([16, 20, 28, 32]).astype(np.int32).reshape((1, 1, 2, 2))

    node = helper.make_node("ConvInteger",
                            ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
                            kernel_shape=[2, 2],
                            pads=[0, 0, 0, 0],
                            dilations=[1, 1])
    output = run_node(node, [x, w, np.int8(0), w_zero_point])
    np.testing.assert_almost_equal(output["Y"], y)

    # Test x_zero_point and w_zero_point
    x = np.array([2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.int8).reshape(
        (1, 1, 3, 3))
    x_zero_point = np.int8(1)
    w = np.array([2, 2, 2, 2]).astype(np.int8).reshape((1, 1, 2, 2))
    w_zero_point = np.int8(1)
    y = np.array([12, 16, 24, 28]).astype(np.int32).reshape((1, 1, 2, 2))

    node = helper.make_node("ConvInteger",
                            ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
                            kernel_shape=[2, 2],
                            pads=[0, 0, 0, 0],
                            dilations=[1, 1])
    output = run_node(node, [x, w, x_zero_point, w_zero_point])
    np.testing.assert_almost_equal(output["Y"], y)

    # Test w_zero_point as 1d tensor
    x = np.array([2, 3, 4, 5, 6, 7, 8, 9, 10]).astype(np.int8).reshape(
        (1, 1, 3, 3))
    w = np.array([2, 2, 2, 2]).astype(np.int8).reshape((1, 1, 2, 2))
    w_zero_point = np.array([1]).astype(np.int8)
    y = np.array([16, 20, 28, 32]).astype(np.int32).reshape((1, 1, 2, 2))

    node = helper.make_node("ConvInteger",
                            ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
                            kernel_shape=[2, 2],
                            pads=[0, 0, 0, 0],
                            dilations=[1, 1])
    output = run_node(node, [x, w, np.int8(0), w_zero_point])
    np.testing.assert_almost_equal(output["Y"], y)

    # Test w_zero_point as 1d tensor shape 2
    x = np.array([1, 2, 3, 4, 5, 6, 7, 8, 9]).astype(np.int8).reshape(
        (1, 1, 3, 3))
    w = np.array([2, 2, 2, 2, 2, 2, 2, 2]).astype(np.int8).reshape((2, 1, 2, 2))
    w_zero_point = np.array([1, 2]).astype(np.int8)
    y = np.array([12, 16, 24, 28, 0, 0, 0, 0]).astype(np.int32).reshape(
        (1, 2, 2, 2))

    node = helper.make_node("ConvInteger",
                            ["X", "W", "x_zero_point", "w_zero_point"], ["Y"],
                            kernel_shape=[2, 2],
                            pads=[0, 0, 0, 0],
                            dilations=[1, 1])
    output = run_node(node, [x, w, np.int8(0), w_zero_point])
    np.testing.assert_almost_equal(output["Y"], y)

  def test_conv_transpose(self):
    device = "CUDA" if supports_device("CUDA") else "CPU"

    pads = [1, 1]
    node_def = helper.make_node("ConvTranspose", ["X", "weights"], ["Y"],
                                pads=pads)
    x_shape = [1, 3, 4]
    x = self._get_rnd_float32(shape=x_shape)
    weight_shape = [3, 5, 2]
    weights = self._get_rnd_float32(shape=weight_shape)
    output = run_node(node_def, [x, weights], device=device)

    padh_left =  weight_shape[2]-1-pads[0]
    padh_right = weight_shape[2]-1-pads[1]
    kh = weight_shape[2]
    outh = x_shape[2] + padh_right + padh_right - (kh - 1)

    out_shape = [x_shape[0], weight_shape[1], outh]

    test_output = np.zeros(out_shape)
    for b in range(0, x_shape[0]):
      for m in range(0, weight_shape[1]):
        for c in range(0, x_shape[1]):
          for h in range(0, outh):
            for k in range(h , h + kh):
              if (k - padh_left >= 0):
                test_output[b][m][h] += x[b][c][k-padh_left] * weights[c][m][kh+h-1-k]

    np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)

    # test for spatial dimension of colnolution is 2
    pads = [1, 1, 1, 1]
    node_def = helper.make_node("ConvTranspose", ["X", "weights"], ["Y"],
                                pads=pads)
    x_shape = [1, 3, 4, 6]
    x = self._get_rnd_float32(shape=x_shape)
    weight_shape = [3, 5, 2, 2]
    weights = self._get_rnd_float32(shape=weight_shape)
    output = run_node(node_def, [x, weights],device=device)

    padh_left =  weight_shape[2]-1-pads[0]
    padh_right = weight_shape[2]-1-pads[1]
    padw_left =  weight_shape[3]-1-pads[2]
    padw_right = weight_shape[3]-1-pads[3]

    kh = weight_shape[2]
    kw = weight_shape[3]
    outh = x_shape[2] + padh_right + padh_right - (kh - 1)
    outw = x_shape[3] + padw_right + padw_right - (kw - 1)

    out_shape = [x_shape[0], weight_shape[1], outh, outw]

    test_output = np.zeros(out_shape)
    for b in range(0, x_shape[0]):
      for m in range(0, weight_shape[1]):
        for c in range(0, x_shape[1]):
          for h in range(0, outh):
            for w in range(0, outw):
              for k1 in range(h , h + kh):
                for k2 in range(w , w + kw):
                  if (k1 - padh_left >= 0 and k2 - padw_left >= 0):
                    test_output[b][m][h][w] += x[b][c][k1-padh_left][k2-padw_left] * weights[c][m][kh+h-1-k1][kw+w-1-k2]

    np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)

  def test_cosh(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Cosh.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Cosh", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.cosh(x))

  def test_cumsum(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest("ONNX version {} doesn't support CumSum.".format(
          defs.onnx_opset_version()))
    x = np.array([[1, 2, 3], [4, 5, 6]]).astype(np.int32)
    axis = 0
    node_def = helper.make_node("CumSum", ["x", "axis"], ["y"])
    # note: if axis is not provided, np.cumsum() will compute over flattened array,
    # which is different than the TensorFlow behavior
    y = np.cumsum(x, axis).astype(np.int32)
    output = run_node(node_def, [x, axis])
    np.testing.assert_almost_equal(output["y"], y)

  def test_depth_to_space(self):
    node_def = helper.make_node("DepthToSpace", ["X"], ["Y"], blocksize=2)
    x_shape = [1, 12, 1, 1]
    x = self._get_rnd_float32(shape=x_shape)
    output = run_node(node_def, [x])
    x = np.transpose(x, (0, 2, 3, 1))
    y = np.reshape(np.swapaxes(x.reshape(1, 1, 1, 2, 2, 3), 2, 3), (1, 2, 2, 3))
    y = np.transpose(y, (0, 3, 1, 2))
    np.testing.assert_almost_equal(output["Y"], y, decimal=5)

  def test_dequantize_linear(self):
    node_def = helper.make_node("DequantizeLinear",
                                ["x", "x_scale", "x_zero_point"], ["y"])
    for x, x_zero_point in [[
        self._get_rnd_int(-128, 127, [2, 6], np.int8),
        self._get_rnd_int(-128, 127, dtype=np.int8)
    ],
                            [
                                self._get_rnd_int(0, 255, [2, 6], np.uint8),
                                self._get_rnd_int(0, 255, dtype=np.uint8)
                            ],
                            [self._get_rnd_int(-512, 512, [2, 6]),
                             np.int32(0)]]:
      x_scale = self._get_rnd_float32(-10., 10)
      y = np.subtract(np.float32(x), np.float32(x_zero_point))
      y = np.multiply(y, x_scale)
      output = run_node(node_def, [x, x_scale, x_zero_point])
      np.testing.assert_almost_equal(output["y"], y)

  def test_div(self):
    node_def = helper.make_node("Div", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=[10, 10])
    y = self._get_rnd_float32(shape=[10, 10])
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"], np.divide(x, y))

  def test_dropout(self):
    # Since current ONNX only support inference and
    # dropout at inference mode is a no-op,
    # therefore dropout is always a no-op operator
    # in ONNX.
    node_def = helper.make_node("Dropout", ["X"], ["Y"])
    if legacy_opset_pre_ver(7):
      # at inference mode, is_test is always set to 1
      node_def = helper.make_node("Dropout", ["X"], ["Y"], is_test=1)
    x = self._get_rnd_float32(shape=[3, 4, 5])
    y = x
    output = run_node(node_def, [x])
    np.testing.assert_equal(output["Y"], y)

  def test_dot(self):
    # this op is removed
    # remove this test in the future
    return
    node_def = helper.make_node("Dot", ["X", "Y"], ["Z"])
    x = np.floor(self._get_rnd_float32(shape=[10, 10]))
    y = np.floor(self._get_rnd_float32(shape=[10, 10]))
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"], np.dot(x, y))

  def test_dynamic_quantize_linear(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support DynamicQuantizeLinear.".format(
              defs.onnx_opset_version()))
    node_def = helper.make_node("DynamicQuantizeLinear", ["X"],
                                ["Y", "Y_Scale", "Y_Zero_Point"])
    x = self._get_rnd_float32(shape=[3, 4])
    min_x = np.minimum(0, np.min(x))
    max_x = np.maximum(0, np.max(x))
    y_scale = np.float32((max_x - min_x) / (255 - 0))  # uint8 -> [0, 255]
    y_zero_point = np.clip(round((0 - min_x) / y_scale), 0,
                           255).astype(np.uint8)
    y = np.clip(np.round(x / y_scale) + y_zero_point, 0, 255).astype(np.uint8)
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], y)
    np.testing.assert_almost_equal(output["Y_Scale"], y_scale)
    np.testing.assert_almost_equal(output["Y_Zero_Point"], y_zero_point)

  def test_elu(self):
    node_def = helper.make_node("Elu", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[100])
    output = run_node(node_def, [x])
    test_output = [self._elu(a) for a in x]
    np.testing.assert_almost_equal(output["Y"], test_output)

  def test_equal(self):
    node_def = helper.make_node("Equal", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=[5, 3, 3, 2])
    y = self._get_rnd_float32(shape=[3, 3, 1])
    output = run_node(node_def, [x, y])
    np.testing.assert_equal(output["Z"], np.equal(x,
                                                  np.reshape(y, [1, 3, 3, 1])))

  def test_erf(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Erf.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Erf", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    output = run_node(node_def, [x])
    exp_output = np.vectorize(math.erf)(x).astype(np.float32)
    np.testing.assert_allclose(output['Y'], exp_output, rtol=1e-6, atol=1e-6)

  def test_exp(self):
    node_def = helper.make_node("Exp", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[100])
    x = x - 3.6
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.exp(x))

  def test_expand(self):
    node_def = helper.make_node("Expand", ["X", "shape"], ["Y"])
    x = [[True], [False], [True]]
    shape = [2, 1, 6]
    y = x * np.ones(shape, dtype=np.bool)
    output = run_node(node_def, [x, shape])
    np.testing.assert_almost_equal(output["Y"], y)

  def test_eye_like(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support EyeLike.".format(
          defs.onnx_opset_version()))
    for shape in [[6, 10], [10, 6]]:
      for off_diagonal_offset in [-10, -6, -3, 0, 3, 6, 7, 10]:
        node_def = helper.make_node("EyeLike", ['x'], ['y'],
                                    dtype=1,
                                    k=off_diagonal_offset)
        x = self._get_rnd_int(0, 100, shape=shape)
        y = np.eye(shape[0], shape[1], k=off_diagonal_offset, dtype=np.float32)
        output = run_node(node_def, [x])
        np.testing.assert_equal(output['y'], y)

  def test_flatten(self):
    shape = [10, 2, 3, 4, 5]
    x = self._get_rnd_float32(shape=shape)
    for axis in range(-len(shape), len(shape)):
      node_def = helper.make_node("Flatten", ["X"], ["Y"], axis=axis)
      output = run_node(node_def, [x])
      if axis == 0:
        new_shape = (1, -1)
      else:
        new_shape = (np.prod(shape[0:axis]).astype(int), -1)
      np.testing.assert_almost_equal(output["Y"], np.reshape(x, new_shape))

  def test_gather(self):
    node_def = helper.make_node("Gather", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=[10, 10])
    y = [[0, 1], [1, 2]]
    output = run_node(node_def, [x, y])
    test_output = np.zeros((2, 2, 10))
    for i in range(0, 2):
      for j in range(0, 10):
        test_output[0][i][j] = x[i][j]
    for i in range(0, 2):
      for j in range(0, 10):
        test_output[1][i][j] = x[i + 1][j]
    np.testing.assert_almost_equal(output["Z"], test_output)
    if defs.onnx_opset_version() >= 11:
      # test negative indices
      y = [[-10, -9], [1, -8]]
      output = run_node(node_def, [x, y])
      np.testing.assert_almost_equal(output["Z"], test_output)
      # test out of bound indices
      for y in ([[-10, 11], [1, -8]], [[-10, -11], [1, -8]]):
        try:
          output = run_node(node_def, [x, y])
          np.testing.assert_almost_equal(output["Z"], test_output)
          raise AssertionError("Expected ValueError not raised for indices %d" %
                               str(y))
        except tf.errors.InvalidArgumentError as e:
          assert 'Gather indices are out of bound' in str(e), str(y)
      # test non-0 and negative axis
      axis = -3
      node_def = helper.make_node("Gather", ["X", "Y"], ["Z"], axis=axis)
      x = np.reshape(np.arange(5 * 4 * 3 * 2), (5, 4, 3, 2))
      y = np.array([0, 1, 3])
      test_output = np.take(x, y, axis=axis)
      output = run_node(node_def, [x, y])
      np.testing.assert_almost_equal(output["Z"], test_output)
      # test axis attribute validation
      for axis in [-5, 4, 10]:
        try:
          node_def = helper.make_node("Gather", ["X", "Y"], ["Z"], axis=axis)
          run_node(node_def, [x, y])
          raise AssertionError(
              "Expected ValueError not raised for axis value %d" % axis)
        except ValueError as e:
          assert 'out of bounds' in str(e), str(e) + ' for axis ' + str(axis)

  def test_gather_elements(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support GatherElements.".format(
              defs.onnx_opset_version()))

    data_dtype = np.int32
    data = np.array([
        [[10, 11], [12, 13], [14, 15], [16, 17], [18, 19]],
        [[20, 21], [22, 23], [24, 25], [26, 27], [28, 29]],
        [[30, 31], [32, 33], [34, 35], [36, 37], [38, 39]],
        [[40, 41], [42, 43], [44, 45], [46, 47], [48, 49]],
    ],
                    dtype=data_dtype)

    # test default axis
    indices = np.array([[[3, 0], [2, 0], [1, 0], [0, 0], [3, 1]]],
                       dtype=np.int64)
    ref_output = np.array([[[40, 11], [32, 13], [24, 15], [16, 17], [48, 29]]],
                          dtype=data_dtype)
    node_def = helper.make_node("GatherElements", ["data", "indices"],
                                ["outputs"])
    output = run_node(node_def, [data, indices])
    np.testing.assert_almost_equal(output["outputs"], ref_output)

    # test non-default axis
    indices = np.array([[[3, 0], [2, 1], [1, 2], [0, 3]]], dtype=data_dtype)
    ref_output = np.array([
        [[16, 11], [14, 13], [12, 15], [10, 17]],
    ],
                          dtype=data_dtype)
    node_def = helper.make_node("GatherElements", ["data", "indices"],
                                ["outputs"],
                                axis=1)
    output = run_node(node_def, [data, indices])
    np.testing.assert_almost_equal(output["outputs"], ref_output)

    # test negative axis
    indices = np.array([
        [[1, 1, -2], [1, -2, 1], [-1, 1, -1], [-2, 1, -2], [-2, 1, 1]],
    ],
                       dtype=data_dtype)
    ref_output = np.array([
        [[11, 11, 10], [13, 12, 13], [15, 15, 15], [16, 17, 16], [18, 19, 19]],
    ],
                          dtype=data_dtype)
    node_def = helper.make_node("GatherElements", ["data", "indices"],
                                ["outputs"],
                                axis=2)
    output = run_node(node_def, [data, indices])
    np.testing.assert_almost_equal(output["outputs"], ref_output)

  def test_gather_nd(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support GatherND.".format(
              defs.onnx_opset_version()))

    # valid positive and negative indices for elements
    data = np.array([[0, 1], [2, 3]], dtype=np.int64)
    indices = np.array([[0, 0], [1, 1], [-1, -2]], dtype=np.int64)
    ref_output = np.array([0, 3, 2], dtype=np.int64)
    node_def = helper.make_node("GatherND", ["data", "indices"], ["outputs"])
    output = run_node(node_def, [data, indices])
    np.testing.assert_almost_equal(output["outputs"], ref_output)

    # valid positive and negative indices for slices
    data = np.arange(16, dtype=np.int32).reshape([2, 2, 4])
    indices = np.array([[0, 0], [-1, -2]], dtype=np.int64)
    ref_output = np.array([[0, 1, 2, 3], [8, 9, 10, 11]], dtype=np.int32)
    output = run_node(node_def, [data, indices])
    np.testing.assert_almost_equal(output["outputs"], ref_output)
    indices = np.array([[[0, 0]], [[-1, 0]]], dtype=np.int64)
    ref_output = np.array([[[0, 1, 2, 3]], [[8, 9, 10, 11]]], dtype=np.int32)
    output = run_node(node_def, [data, indices])
    np.testing.assert_almost_equal(output["outputs"], ref_output)

    # indices out of bounds
    indices = np.array([[5, 0], [-1, -3]], dtype=np.int64)
    with np.testing.assert_raises(tf.errors.InvalidArgumentError):
      output = run_node(node_def, [data, indices])
    indices = np.array([[1, 1, 6], [-2, -1, -9]], dtype=np.int32)
    with np.testing.assert_raises(tf.errors.InvalidArgumentError):
      output = run_node(node_def, [data, indices])

  def test_gemm(self):
    # Compute Y = alpha * A * B + beta * C
    node_def = helper.make_node("Gemm", ["A", "B", "C"], ["Y"],
                                transA=0,
                                transB=0,
                                alpha=1.0,
                                beta=1.0)
    x = np.floor(self._get_rnd_float32(shape=[10, 10]))
    y = np.floor(self._get_rnd_float32(shape=[10, 10]))
    z = np.floor(self._get_rnd_float32(shape=[10, 10]))
    output = run_node(node_def, [x, y, z])
    test_output = np.matmul(x, y) + z
    np.testing.assert_almost_equal(output["Y"], test_output)

  def test_global_average_pool(self):
    #   Image case:  (N x C x H x W), where N is the batch size,
    # C is the number of channels, and H and W are the height
    # and the width of the data
    #
    #   Non-image case: (N x C x D1 x D2 ... Dn)
    #
    #   Output data tensor from pooling across the input tensor.
    # Dimensions will be N x C x 1 x 1
    node_def = helper.make_node("GlobalAveragePool", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[10, 10, 2, 3])
    output = run_node(node_def, [x])
    test_output = np.zeros([10, 10, 1, 1])
    for i1 in range(0, 10):
      for i2 in range(0, 10):
        sum = 0
        for j1 in range(0, 2):
          for j2 in range(0, 3):
            sum += x[i1][i2][j1][j2]
        test_output[i1][i2][0][0] = sum / 6.
    np.testing.assert_almost_equal(output["Y"], test_output)

  def test_hardmax(self):
    shape = [2, 3, 4, 5]
    x = self._get_rnd_float32(shape=shape)
    for axis in range(-len(shape), len(shape)):
      node_def = helper.make_node("Hardmax", ["X"], ["Y"], axis=axis)
      output = run_node(node_def, [x])
      shape_in_2d = (np.prod(shape[0:axis]).astype(int),
                     np.prod(shape[axis:len(shape)]))
      x_in_2d = np.reshape(x, shape_in_2d)
      y = np.eye(x_in_2d.shape[1], dtype=x.dtype)[np.argmax(x_in_2d, axis=1)]
      np.testing.assert_almost_equal(output["Y"], np.reshape(y, shape))

  def test_if(self):
    true_val = helper.make_tensor(name='true_tensor',
                                  data_type=TensorProto.INT64,
                                  dims=(),
                                  vals=[np.int64(1)])
    false_val = helper.make_tensor(name='false_tensor',
                                   data_type=TensorProto.INT64,
                                   dims=(),
                                   vals=[np.int64(0)])
    true_node = helper.make_node('Constant',
                                 inputs=[],
                                 outputs=['true'],
                                 value=true_val)
    false_node = helper.make_node('Constant',
                                  inputs=[],
                                  outputs=['false'],
                                  value=false_val)

    true_out = helper.make_tensor_value_info('true', TensorProto.INT64, [])
    false_out = helper.make_tensor_value_info('false', TensorProto.INT64, [])

    true_graph = helper.make_graph(nodes=[true_node],
                                   name="true_graph",
                                   inputs=[],
                                   outputs=[true_out])
    false_graph = helper.make_graph(nodes=[false_node],
                                    name="false_graph",
                                    inputs=[],
                                    outputs=[false_out])

    node_def = helper.make_node('If', ['cond'], ['outputs'],
                                then_branch=true_graph,
                                else_branch=false_graph)

    for cond, exp in [[True, true_val], [False, false_val]]:
      output = run_node(node_def, [cond])
      np.testing.assert_equal(output['outputs'], exp.int64_data)

    x = self._get_rnd_int(low=-50, high=50, dtype=np.int64)
    y = self._get_rnd_int(low=-50, high=50, dtype=np.int64)
    z = self._get_rnd_int(low=-50, high=50, dtype=np.int64)
    x_val = helper.make_tensor(name='x_tensor',
                               data_type=TensorProto.INT64,
                               dims=(),
                               vals=[x])
    y_val = helper.make_tensor(name='y_tensor',
                               data_type=TensorProto.INT64,
                               dims=(),
                               vals=[y])
    z_val = helper.make_tensor(name='z_tensor',
                               data_type=TensorProto.INT64,
                               dims=(),
                               vals=[z])
    x_node = helper.make_node('Constant', inputs=[], outputs=['x'], value=x_val)
    y_node = helper.make_node('Constant', inputs=[], outputs=['y'], value=y_val)
    z_node = helper.make_node('Constant', inputs=[], outputs=['z'], value=z_val)
    add_node = helper.make_node('Add', inputs=['x', 'y'], outputs=['sum'])
    sub_node = helper.make_node('Sub', inputs=['x', 'y'], outputs=['diff'])
    mul1_node = helper.make_node('Mul', inputs=['sum', 'z'], outputs=['prod1'])
    mul2_node = helper.make_node('Mul', inputs=['diff', 'z'], outputs=['prod2'])

    x_out = helper.make_tensor_value_info('x', TensorProto.INT64, [])
    y_out = helper.make_tensor_value_info('y', TensorProto.INT64, [])
    z_out = helper.make_tensor_value_info('z', TensorProto.INT64, [])
    sum_out = helper.make_tensor_value_info('sum', TensorProto.INT64, [])
    diff_out = helper.make_tensor_value_info('diff', TensorProto.INT64, [])
    prod1_out = helper.make_tensor_value_info('prod1', TensorProto.INT64, [])
    prod2_out = helper.make_tensor_value_info('prod2', TensorProto.INT64, [])

    true_graph = helper.make_graph(nodes=[add_node, mul1_node],
                                   name="true_graph",
                                   inputs=[x_out, y_out, z_out],
                                   outputs=[sum_out, prod1_out])
    false_graph = helper.make_graph(nodes=[sub_node, mul2_node],
                                    name="false_graph",
                                    inputs=[x_out, y_out, z_out],
                                    outputs=[diff_out, prod2_out])

    less_node = helper.make_node('Less', inputs=['x', 'y'], outputs=['cond'])
    if_node = helper.make_node('If',
                               inputs=['cond'],
                               outputs=['result'],
                               then_branch=true_graph,
                               else_branch=false_graph)

    result_out = helper.make_tensor_value_info('result', TensorProto.INT64, [])

    graph = helper.make_graph(
        nodes=[x_node, y_node, z_node, less_node, if_node],
        name="test_if",
        inputs=[],
        outputs=[result_out])

    tf_rep = onnx_graph_to_tensorflow_rep(graph)
    output = tf_rep.run({})
    expected = [x + y, (x + y) * z] if x < y else [x - y, (x - y) * z]
    np.testing.assert_equal(output['result'], expected)

  def test_image_sacler(self):
    # Input:  (N x C x H x W), where N is the batch size,
    # C is the number of channels, and H and W are the height
    # and the width of the data
    # Scale: (flout, default 1.0) the scale to apply
    # Bias: applied to each channel, same size as C
    # Output has same shape and type as input
    x = self._get_rnd_float32(shape=[1, 3, 224, 224])
    #random distribution over [0,1), so add 0.1
    scale = np.random.rand(1)[0] + 0.1
    bias = np.random.rand(3)
    node_def = helper.make_node("ImageScaler", ["X"], ["Y"],
                                scale=scale,
                                bias=bias)
    output = run_node(node_def, [x])
    test_out = np.multiply(x, scale)
    test_out = np.transpose(test_out, [0, 2, 3, 1])
    test_out = np.add(test_out, bias)
    test_out = np.transpose(test_out, [0, 3, 1, 2])
    np.testing.assert_almost_equal(output["Y"], test_out)

  def test_is_inf(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest("ONNX version {} doesn't support IsInf.".format(
          defs.onnx_opset_version()))
    input = np.array([-1.2, np.nan, np.inf, 2.8, np.NINF, np.inf],
                     dtype=np.float32)
    expected_output = {
        "node_def": np.isinf(input),
        "node_def_neg_false": np.isposinf(input),
        "node_def_pos_false": np.isneginf(input)
    }
    node_defs = {
        "node_def":
            helper.make_node("IsInf", ["X"], ["Y"]),
        "node_def_neg_false":
            helper.make_node("IsInf", ["X"], ["Y"], detect_negative=0),
        "node_def_pos_false":
            helper.make_node("IsInf", ["X"], ["Y"], detect_positive=0)
    }
    for key in node_defs:
      output = run_node(node_defs[key], [input])
      np.testing.assert_equal(output["Y"], expected_output[key])

  def test_isnan(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support IsNaN.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("IsNaN", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 3])
    x[0][1] = x[1][0] = x[2][2] = np.nan
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.isnan(x))

  def test_global_lp_pool(self):
    #   Image case:  (N x C x H x W), where N is the batch size,
    # C is the number of channels, and H and W are the height
    # and the width of the data
    #
    #   Non-image case: (N x C x D1 x D2 ... Dn)
    #
    #   Output data tensor from pooling across the input tensor.
    # Dimensions will be N x C x 1 x 1
    node_def = helper.make_node("GlobalLpPool", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[10, 10, 2, 3])
    output = run_node(node_def, [x])
    test_output = np.zeros([10, 10, 1, 1])
    for i1 in range(0, 10):
      for i2 in range(0, 10):
        tmp = np.zeros([2, 3])
        for j1 in range(0, 2):
          for j2 in range(0, 3):
            tmp[j1][j2] = x[i1][i2][j1][j2]
        test_output[i1][i2][0][0] = np.linalg.norm(tmp)
    np.testing.assert_almost_equal(output["Y"], test_output, decimal=5)

  def test_global_max_pool(self):
    #   Image case:  (N x C x H x W), where N is the batch size,
    # C is the number of channels, and H and W are the height
    # and the width of the data
    #
    #   Non-image case: (N x C x D1 x D2 ... Dn)
    #
    #   Output data tensor from pooling across the input tensor.
    # Dimensions will be N x C x 1 x 1
    node_def = helper.make_node("GlobalMaxPool", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[10, 10, 2, 3])
    output = run_node(node_def, [x])
    test_output = np.zeros([10, 10, 1, 1])
    for i1 in range(0, 10):
      for i2 in range(0, 10):
        max = x[i1][i2][0][0]
        for j1 in range(0, 2):
          for j2 in range(0, 3):
            if max < x[i1][i2][j1][j2]:
              max = x[i1][i2][j1][j2]
        test_output[i1][i2][0][0] = max
    np.testing.assert_almost_equal(output["Y"], test_output)

  def test_less(self):
    node_def = helper.make_node("Less", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=[5, 3, 3, 2])
    y = self._get_rnd_float32(shape=[3, 3, 1])
    output = run_node(node_def, [x, y])
    np.testing.assert_equal(output["Z"], np.less(x, np.reshape(y,
                                                               [1, 3, 3, 1])))

  def test_lp_normalization(self):
    for ordr in range(1, 3):
      node_def = helper.make_node("LpNormalization", ["X"], ["Y"], p=ordr)
      x = self._get_rnd_float32(shape=[2, 2, 3, 2])
      output = run_node(node_def, [x])
      np.testing.assert_allclose(
          output["Y"],
          x / np.expand_dims(np.linalg.norm(x, axis=-1, ord=ordr), -1),
          rtol=1e-3)

  def test_l_r_n(self):
    # Each input value is divided by:
    #
    # (bias+(alpha/size)*sum(xi^2 for every xi in the local region))^beta
    alpha = 2.0
    beta = 1.0
    bias = 5.0
    size = 3
    node_def = helper.make_node("LRN", ["X"], ["Y"],
                                alpha=alpha,
                                beta=beta,
                                bias=bias,
                                size=size)
    x = self._get_rnd_float32(shape=[10, 2, 10, 10])
    output = run_node(node_def, [x])
    test_output = np.zeros([10, 10, 10, 2])
    x = np.transpose(x, axes=[0, 2, 3, 1])
    for i1 in range(0, 10):
      for i2 in range(0, 10):
        for j1 in range(0, 10):
          for j2 in range(0, 2):
            sqr_sum = 0.
            # size of 3 means radius 1 in TF speak
            # i.e. the immediate neighbouring values
            # if "previous" neighbour exists
            if j2 > 0:
              sqr_sum += x[i1][i2][j1][j2 - 1] * x[i1][i2][j1][j2 - 1]
            # current value
            sqr_sum += x[i1][i2][j1][j2] * x[i1][i2][j1][j2]
            # if "next" neighbour exists
            if j2 < 2 - 1:
              sqr_sum += x[i1][i2][j1][j2 + 1] * x[i1][i2][j1][j2 + 1]
            test_output[i1][i2][j1][j2] = \
              x[i1][i2][j1][j2] / ((bias + (alpha * 1. / size) * sqr_sum) ** beta)
    test_output = np.transpose(test_output, axes=[0, 3, 1, 2])
    np.testing.assert_almost_equal(output["Y"], test_output)

  def test_floor(self):
    node_def = helper.make_node("Floor", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[100])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.floor(x))

  def test_leakyrelu(self):
    node_def = helper.make_node("LeakyRelu", ["X"], ["Y"], alpha=0.8)
    x = np.floor(self._get_rnd_float32(shape=[100]))
    output = run_node(node_def, [x])
    test_output = [self._leaky_relu(a, 0.8) for a in x]
    np.testing.assert_almost_equal(output["Y"], test_output)

  def test_log(self):
    node_def = helper.make_node("Log", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[100])
    x = x + 3.6
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.log(x))

  def test_loop(self):
    add1_node = helper.make_node('Add', inputs=['x', 'x'], outputs=['sum1'])
    neg_node = helper.make_node('Neg', inputs=['sum1'], outputs=['neg_sum1'])
    add2_node = helper.make_node('Add',
                                 inputs=['y', 'neg_sum1'],
                                 outputs=['sum2'])
    add3_node = helper.make_node('Add',
                                 inputs=['sum1', 'sum2'],
                                 outputs=['sum3'])
    less_node = helper.make_node('Less',
                                 inputs=['sum1', 'sum2'],
                                 outputs=['new_cond'])
    greater_node = helper.make_node('Greater',
                                    inputs=['sum1', 'sum2'],
                                    outputs=['new_cond'])

    m_in = helper.make_tensor_value_info('M', TensorProto.INT64, [])
    cond_in = helper.make_tensor_value_info('cond', TensorProto.BOOL, [])
    cond_int_in = helper.make_tensor_value_info('cond', TensorProto.INT32, [])
    x_in = helper.make_tensor_value_info('x', TensorProto.INT32, [None])
    y_in = helper.make_tensor_value_info('y', TensorProto.INT32, [None])

    cond_out = helper.make_tensor_value_info('cond', TensorProto.BOOL, [])
    new_cond_out = helper.make_tensor_value_info('new_cond', TensorProto.BOOL,
                                                 [])
    sum1_out = helper.make_tensor_value_info('sum1', TensorProto.INT32, [None])
    sum2_out = helper.make_tensor_value_info('sum2', TensorProto.INT32, [None])
    sum3_out = helper.make_tensor_value_info('sum3', TensorProto.INT32, [None])

    v1_initial = np.array([1, 1], dtype=np.int32)
    v2_initial = np.array([100, 100], dtype=np.int32)

    # test for loop
    M = np.int64(10)
    cond = True  # value will be ignore because optional "cond" input will be skip
    graph = helper.make_graph(nodes=[add1_node, neg_node, add2_node, add3_node],
                              name="for_loop_graph",
                              inputs=[m_in, cond_in, x_in, y_in],
                              outputs=[cond_out, sum1_out, sum2_out, sum3_out])
    node_def = helper.make_node('Loop', ['M', '', 'v1_initial', 'v2_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v2_initial])
    v_final = [
        np.array([1024, 1024], dtype=np.int32),
        np.array([-1946, -1946], dtype=np.int32)
    ]
    scan_outputs = [
        np.array([[100, 100], [98, 98], [94, 94], [86, 86], [70, 70], [38, 38],
                  [-26, -26], [-154, -154], [-410, -410], [-922, -922]],
                 dtype=np.int32)
    ]
    np.testing.assert_almost_equal(output['v_final'], v_final)
    np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)

    # test while loop
    M = 0  # value will be ignore because optional "M" input will be skip
    cond = v1_initial < v2_initial
    graph = helper.make_graph(
        nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
        name="while_loop_graph",
        inputs=[m_in, cond_in, x_in, y_in],
        outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
    node_def = helper.make_node('Loop',
                                ['', 'cond', 'v1_initial', 'v2_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v2_initial])
    v_final = [
        np.array([64, 64], dtype=np.int32),
        np.array([-26, -26], dtype=np.int32)
    ]
    scan_outputs = [
        np.array([[100, 100], [98, 98], [94, 94], [86, 86], [70, 70], [38, 38]],
                 dtype=np.int32)
    ]
    np.testing.assert_almost_equal(output['v_final'], v_final)
    np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)

    # test do-while loop
    M = 0  # value will be ignore because optional "M" input will be skip
    cond = 1
    graph = helper.make_graph(
        nodes=[add1_node, neg_node, add2_node, add3_node, greater_node],
        name="do_while_loop_graph",
        inputs=[m_in, cond_int_in, x_in, y_in],
        outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
    node_def = helper.make_node('Loop',
                                ['', 'cond', 'v1_initial', 'v2_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v2_initial])
    v_final = [
        np.array([2, 2], dtype=np.int32),
        np.array([98, 98], dtype=np.int32)
    ]
    scan_outputs = [np.array([[100, 100]], dtype=np.int32)]
    np.testing.assert_almost_equal(output['v_final'], v_final)
    np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)

    # test for loop and while loop conbine
    M = np.int64(4)
    cond = v1_initial < v2_initial
    graph = helper.make_graph(
        nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
        name="for_and_while_loop_graph",
        inputs=[m_in, cond_in, x_in, y_in],
        outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
    node_def = helper.make_node('Loop',
                                ['M', 'cond', 'v1_initial', 'v2_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v2_initial])
    v_final = [
        np.array([16, 16], dtype=np.int32),
        np.array([70, 70], dtype=np.int32)
    ]
    scan_outputs = [
        np.array([[100, 100], [98, 98], [94, 94], [86, 86]], dtype=np.int32)
    ]
    np.testing.assert_almost_equal(output['v_final'], v_final)
    np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)

    # test for loop that doesn't run at all (M = 0)
    M = np.int64(0)
    cond = True  # value will be ignore because optional "cond" input will be skip
    graph = helper.make_graph(nodes=[add1_node, neg_node, add2_node, add3_node],
                              name="for_loop_graph",
                              inputs=[m_in, cond_in, x_in, y_in],
                              outputs=[cond_out, sum1_out, sum2_out, sum3_out])
    node_def = helper.make_node('Loop', ['M', '', 'v1_initial', 'v2_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v2_initial])
    v_final = [
        np.array([1, 1], dtype=np.int32),
        np.array([100, 100], dtype=np.int32)
    ]
    scan_outputs = np.array([], dtype=np.int32).reshape(1, 0, 2)
    np.testing.assert_almost_equal(output['v_final'], v_final)
    np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)

    # test while loop that doesn't run at all (cond = False)
    M = 0  # value will be ignore because optional "M" input will be skip
    cond = False
    graph = helper.make_graph(
        nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
        name="while_loop_graph",
        inputs=[m_in, cond_in, x_in, y_in],
        outputs=[new_cond_out, sum1_out, sum2_out, sum3_out])
    node_def = helper.make_node('Loop',
                                ['', 'cond', 'v1_initial', 'v2_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v2_initial])
    v_final = [
        np.array([1, 1], dtype=np.int32),
        np.array([100, 100], dtype=np.int32)
    ]
    scan_outputs = np.array([], dtype=np.int32).reshape(1, 0, 2)
    np.testing.assert_almost_equal(output['v_final'], v_final)
    np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)

    # test while loop that doesn't have any scan_outputs
    M = np.int64(4)
    cond = v1_initial < v2_initial
    graph = helper.make_graph(nodes=[add1_node, neg_node, add2_node, less_node],
                              name="while_loop_graph",
                              inputs=[m_in, cond_in, x_in, y_in],
                              outputs=[new_cond_out, sum1_out, sum2_out])
    node_def = helper.make_node('Loop',
                                ['M', 'cond', 'v1_initial', 'v2_initial'],
                                ['v_final'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v2_initial])
    v_final = [
        np.array([16, 16], dtype=np.int32),
        np.array([70, 70], dtype=np.int32)
    ]
    np.testing.assert_almost_equal(output['v_final'], v_final)

    # test for loop that doesn't run at all (M = 0)
    # and the scan_outputs shape is not the same as the inputs
    v1_initial = np.array([[1, 1, 1], [2, 2, 2]], dtype=np.int32)
    v3_initial = np.array([[1, 1], [2, 2], [3, 3]], dtype=np.int32)
    matmul_node = helper.make_node('MatMul',
                                   inputs=['x', 'z'],
                                   outputs=['product'])
    x_in = helper.make_tensor_value_info('x', TensorProto.INT32, [None, None])
    z_in = helper.make_tensor_value_info('z', TensorProto.INT32, [None, None])
    sum1_out = helper.make_tensor_value_info('sum1', TensorProto.INT32,
                                             [None, None])
    z_out = helper.make_tensor_value_info('z', TensorProto.INT32, [None, None])
    product_out = helper.make_tensor_value_info('product', TensorProto.INT32,
                                                [None, None])

    M = np.int64(0)
    cond = True  # value will be ignore because optional "cond" input will be skip
    graph = helper.make_graph(nodes=[add1_node, matmul_node],
                              name="for_loop_graph",
                              inputs=[m_in, cond_in, x_in, z_in],
                              outputs=[cond_out, sum1_out, z_out, product_out])
    node_def = helper.make_node('Loop', ['M', '', 'v1_initial', 'v3_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    output = run_node(node_def, [M, cond, v1_initial, v3_initial])
    v_final = [v1_initial, v3_initial]
    scan_outputs = np.array([], dtype=np.int32).reshape(1, 0, 2, 2)
    for i in range(len(output['v_final'])):
      np.testing.assert_almost_equal(output['v_final'][i], v_final[i])
    np.testing.assert_almost_equal(output['scan_outputs'], scan_outputs)

    # verify infinite loop will get exception
    M = 0  # value will be ignore because optional "M" input will be skip
    cond = True  # value will be ignore because optional "cond" input will be skip
    graph = helper.make_graph(
        nodes=[add1_node, neg_node, add2_node, add3_node, less_node],
        name="while_loop_graph",
        inputs=[m_in, cond_in, x_in, y_in],
        outputs=[cond_out, sum1_out, sum2_out, sum3_out])
    node_def = helper.make_node('Loop', ['', '', 'v1_initial', 'v2_initial'],
                                ['v_final', 'scan_outputs'],
                                body=graph)
    try:
      output = run_node(node_def, [M, cond, v1_initial, v2_initial])
      raise AssertionError("Expected RuntimeError not raise when Loop inputs " +
                           "M and cond are both not set at the same time")
    except RuntimeError as e:
      assert "M and cond in Loop are not set" in str(e)

  def test_matmul_integer(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support MatMulInteger.".format(
              defs.onnx_opset_version()))

    node_def = helper.make_node("MatMulInteger",
                                ["A", "B", "a_zero_point", "b_zero_point"],
                                ["Z"])
    lower_bound = {np.uint8: 0, np.int8: -20}
    for dtype in [np.uint8, np.int8]:
      # A & B are 3-D tensor and a_zero_point & b_zero_point are scalar
      A = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 3, 4),
                            dtype=dtype)
      B = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 4, 6),
                            dtype=dtype)
      a_zero_point = self._get_rnd_int(lower_bound[dtype], 20, dtype=dtype)
      b_zero_point = self._get_rnd_int(lower_bound[dtype], 20, dtype=dtype)
      A_minus_zero_point = np.subtract(A.astype(np.int32),
                                       a_zero_point.astype(np.int32))
      B_minus_zero_point = np.subtract(B.astype(np.int32),
                                       b_zero_point.astype(np.int32))
      z = np.matmul(A_minus_zero_point, B_minus_zero_point)
      output = run_node(node_def, [A, B, a_zero_point, b_zero_point])
      np.testing.assert_almost_equal(output["Z"], z)
      # A & B are 4-D tensor and a_zero_point & b_zero_point are 1-D tensor
      A = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 5, 3, 4),
                            dtype=dtype)
      B = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 1, 4, 6),
                            dtype=dtype)
      a_zero_point = self._get_rnd_int(lower_bound[dtype],
                                       20,
                                       shape=(A.shape[-2]),
                                       dtype=dtype)
      b_zero_point = self._get_rnd_int(lower_bound[dtype],
                                       20,
                                       shape=(B.shape[-1]),
                                       dtype=dtype)
      a_zero_point_with_reshape = np.reshape(a_zero_point, [A.shape[-2], 1])
      A_minus_zero_point = np.subtract(
          A.astype(np.int32), a_zero_point_with_reshape.astype(np.int32))
      B_minus_zero_point = np.subtract(B.astype(np.int32),
                                       b_zero_point.astype(np.int32))
      z = np.matmul(A_minus_zero_point, B_minus_zero_point)
      output = run_node(node_def, [A, B, a_zero_point, b_zero_point])
      np.testing.assert_almost_equal(output["Z"], z)

    node_def = helper.make_node("MatMulInteger", ["A", "B"], ["Z"])
    for dtype in [np.uint8, np.int8]:
      # A & B are 3-D tensor
      A = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 3, 4),
                            dtype=dtype)
      B = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 4, 6),
                            dtype=dtype)
      z = np.matmul(A.astype(np.int32), B.astype(np.int32))
      output = run_node(node_def, [A, B])
      np.testing.assert_almost_equal(output["Z"], z)
      # A & B are 4-D tensor
      A = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 5, 3, 4),
                            dtype=dtype)
      B = self._get_rnd_int(lower_bound[dtype],
                            20,
                            shape=(2, 1, 4, 6),
                            dtype=dtype)
      z = np.matmul(A.astype(np.int32), B.astype(np.int32))
      output = run_node(node_def, [A, B])
      np.testing.assert_almost_equal(output["Z"], z)

  def test_max(self):
    node_def = helper.make_node("Max", ["X1", "X2", "X3", "X4"], ["Z"])
    x1 = self._get_rnd_float32(shape=[10, 10])
    x2 = self._get_rnd_float32(shape=[10, 10])
    x3 = self._get_rnd_float32(shape=[10, 10])
    x4 = self._get_rnd_float32(shape=[10, 10])
    output = run_node(node_def, [x1, x2, x3, x4])
    test_output = np.maximum(np.maximum(np.maximum(x1, x2), x3), x4)
    np.testing.assert_almost_equal(output["Z"], test_output)

  def _test_pooling(self,
                    input_shape,
                    kernel_shape,
                    strides=None,
                    dilations=None,
                    pads=None,
                    auto_pad=None,
                    ceil_mode=None,
                    count_include_pad=None,
                    pooling_type="MAX",
                    input_dtype=np.float32,
                    p=None):

    op = "MaxPool" if pooling_type.upper().startswith("MAX") else \
         "AveragePool" if pooling_type.upper() == "AVG" else "LpPool"
    node_def_kwargs = {
        "op_type": op,
        "inputs": ["X"],
        "outputs": ["Y"],
        "kernel_shape": kernel_shape
    }

    if strides is not None:
      node_def_kwargs["strides"] = strides
    if dilations is not None:
      node_def_kwargs["dilations"] = dilations
    if pads is not None:
      node_def_kwargs["pads"] = pads
    if auto_pad is not None:
      node_def_kwargs["auto_pad"] = auto_pad
      pads = auto_pad
    if ceil_mode is not None:
      node_def_kwargs["ceil_mode"] = ceil_mode
    else:
      ceil_mode = 0
    if count_include_pad is not None:
      node_def_kwargs["count_include_pad"] = count_include_pad
    if p is not None:
      node_def_kwargs["p"] = p

    node_def = helper.make_node(**node_def_kwargs)

    if input_dtype == np.float32:
        x = self._get_rnd_float32(shape=input_shape)
    else:
        x = self._get_rnd_int(low = np.iinfo(input_dtype).min,
                              high = np.iinfo(input_dtype).max,
                              shape=input_shape, dtype=input_dtype)

    x = self._get_rnd_float32(shape=input_shape)
    output = run_node(node_def, [x])

    test_output = py_pool(x,
                          kernel_shape=kernel_shape,
                          strides=strides,
                          dilations=dilations,
                          padding=pads,
                          ceil_mode=ceil_mode,
                          pooling_type=pooling_type,
                          include_indices=False,
                          p=p)

    np.testing.assert_almost_equal(output["Y"], test_output,
                                   decimal=5 if pooling_type=="LP" else 7)

  def test_max_pool_2d(self):
    kernel_shape = [1, 2]
    strides = [1, 2]

    input_shape = [10, 10, 4, 4]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides)

  def test_max_pool_2d_same_lower(self):
    kernel_shape = [1, 2]
    strides = [1, 2]
    auto_pad = "SAME_LOWER"

    input_shape = [10, 10, 7, 7]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       auto_pad=auto_pad)

  def test_max_pool_2d_ceil_same_lower(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support ceil mode.".format(
              defs.onnx_opset_version()))

    kernel_shape = [2, 1]
    strides = [1, 2]
    auto_pad = "SAME_LOWER"
    ceil_mode = 1

    input_shape = [10, 10, 7, 7]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       auto_pad=auto_pad,
                       ceil_mode=ceil_mode)

  def test_max_pool_2d_same_upper(self):
    kernel_shape = [1, 2]
    strides = [1, 2]
    auto_pad = "SAME_UPPER"

    input_shape = [10, 10, 7, 7]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       auto_pad=auto_pad)

  def test_max_pool_2d_ceil(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support ceil mode.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    ceil_mode = 1

    input_shape = [10, 3, 24, 24]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       ceil_mode=ceil_mode)

  def test_max_pool_2d_dilations(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    dilations = [3, 3]
    node_def = helper.make_node("MaxPool", ["X"], ["Y"],
                                kernel_shape=kernel_shape,
                                strides=strides,
                                dilations=dilations)

    input_shape = [10, 3, 24, 24]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations)

  def test_max_pool_2d_dilations_ceil(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations nor ceil mode.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    dilations = [3, 3]
    ceil_mode = 1

    input_shape = [10, 3, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       ceil_mode=ceil_mode)

  def test_max_pool_2d_dilations_pads(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    dilations = [3, 3]
    pads = [1, 1, 2, 2]

    input_shape = [10, 3, 24, 24]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       pads=pads)

  def test_max_pool_2d_dilations_ceil_pads(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations nor ceil mode.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    dilations = [3, 3]
    pads = [1, 1, 2, 2]
    ceil_mode = 1

    input_shape = [10, 3, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       pads=pads,
                       ceil_mode=ceil_mode)

  def test_max_pool_2d_dilations_same_lower(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    dilations = [3, 3]
    auto_pad = "SAME_LOWER"

    input_shape = [10, 3, 24, 24]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       auto_pad=auto_pad)

  def test_max_pool_2d_dilations_same_upper(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations.".format(
              defs.onnx_opset_version()))

    kernel_shape = [2, 3]
    strides = [4, 2]
    dilations = [3, 5]
    auto_pad = "SAME_UPPER"

    input_shape = [10, 3, 24, 24]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       auto_pad=auto_pad)

  def test_max_pool_2d_dilations_ceil_pads_int8(self):
    if legacy_opset_pre_ver(12):
      raise unittest.SkipTest(
          "ONNX version {} does not support int8 input type.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    dilations = [3, 3]
    pads = [1, 1, 2, 2]
    ceil_mode = 1

    input_shape = [10, 3, 23, 23]
    self._test_pooling(input_shape=input_shape, kernel_shape=kernel_shape,
                       strides=strides, dilations=dilations, pads=pads,
                       ceil_mode=ceil_mode, input_dtype=np.int8)

  def test_max_pool_3d(self):
    kernel_shape = [3, 3, 3]
    strides = [2, 2, 2]

    input_shape = [10, 3, 23, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides)

  def test_max_pool_3d_dilations_ceil_pads(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations nor ceil mode.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3, 3]
    strides = [2, 2, 2]
    dilations = [3, 3, 3]
    pads = [1, 1, 2, 2, 1, 1]
    ceil_mode = 1

    input_shape = [10, 3, 23, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       pads=pads,
                       ceil_mode=ceil_mode)

  def test_max_pool_3d_dilations_same_lower(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 1, 2]
    strides = [2, 2, 1]
    dilations = [3, 2, 5]
    auto_pad = "SAME_LOWER"

    input_shape = [10, 3, 23, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       auto_pad=auto_pad)

  def test_max_pool_1d_dilations_ceil_pads(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations nor ceil mode.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3]
    strides = [2]
    dilations = [3]
    pads = [1, 2]
    ceil_mode = 1

    input_shape = [10, 3, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       dilations=dilations,
                       pads=pads,
                       ceil_mode=ceil_mode)

  def test_max_pool_1d(self):
    kernel_shape = [3]
    strides = [2]

    input_shape = [10, 3, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides)

  def test_max_pool_with_argmax_2d_dilations_ceil_pads(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support dilations nor ceil mode.".format(
              defs.onnx_opset_version()))

    kernel_shape = [3, 3]
    strides = [2, 2]
    dilations = [3, 3]
    pads = [1, 1, 2, 2]
    ceil_mode = True
    node_def = helper.make_node("MaxPool", ["X"], ["Y", "Ind"],
                                kernel_shape=kernel_shape,
                                strides=strides,
                                dilations=dilations,
                                pads=pads,
                                ceil_mode=ceil_mode)

    input_shape = [10, 1, 23, 23]
    x = self._get_rnd_float32(shape=input_shape) - 2
    output = run_node(node_def, [x])

    test_output, test_ind = py_pool(x,
                                    kernel_shape=kernel_shape,
                                    strides=strides,
                                    dilations=dilations,
                                    padding=pads,
                                    ceil_mode=ceil_mode,
                                    pooling_type="MAX")

    np.testing.assert_almost_equal(output["Y"], test_output)
    np.testing.assert_almost_equal(output["Ind"], test_ind)

  def test_max_pool_with_argmax_3d(self):
    kernel_shape = [3, 3, 3]
    strides = [2, 2, 2]
    node_def = helper.make_node("MaxPool", ["X"], ["Y", "Ind"],
                                kernel_shape=kernel_shape,
                                strides=strides)

    input_shape = [10, 1, 23, 23, 23]
    x = self._get_rnd_float32(shape=input_shape)
    self.assertRaises(RuntimeError, run_node, node_def, [x])

  def test_max_pool_4d(self):
    kernel_shape = [3, 3, 3, 3]
    strides = [2, 2, 2, 2]
    node_def = helper.make_node("MaxPool", ["X"], ["Y", "Ind"],
                                kernel_shape=kernel_shape,
                                strides=strides)

    input_shape = [1, 1, 4, 4, 4, 4]
    x = self._get_rnd_float32(shape=input_shape)
    self.assertRaises(RuntimeError, run_node, node_def, [x])

  def test_max_unpool(self):
    input_shape = [10, 10, 4, 4]
    x = self._get_rnd_float32(shape=input_shape)

    X = helper.make_tensor_value_info('X', TensorProto.FLOAT, input_shape)
    Y = helper.make_tensor_value_info('Y', TensorProto.FLOAT, input_shape)

    node_def = helper.make_node("MaxPool", ["X"], ["Pool", "Indices"],
                                kernel_shape=[2, 2],
                                strides=[2, 2])
    output_pool = run_node(node_def, [x])

    node_def = helper.make_node("MaxUnpool", ["Pool", "Indices"], ["Y"],
                                kernel_shape=[2, 2],
                                strides=[2, 2])
    output_unpool = run_node(node_def,
                             [output_pool["Pool"], output_pool["Indices"]])

    test_output = np.zeros(input_shape)
    for i1 in range(0, input_shape[0]):
      for i2 in range(0, input_shape[1]):
        for i3 in range(0, input_shape[2], 2):
          for i4 in range(0, input_shape[3], 2):
            max_val = float('-inf')
            for j1 in range(i3, i3 + 2):
              for j2 in range(i4, i4 + 2):
                if x[i1][i2][j1][j2] > max_val:
                  max_val = x[i1][i2][j1][j2]
                  max_ind = (j1, j2)
            j1, j2 = max_ind
            test_output[i1][i2][j1][j2] = max_val
    np.testing.assert_almost_equal(output_unpool["Y"], test_output)

  def test_average_pool_1d(self):
    kernel_shape = [3]
    strides = [2]

    input_shape = [10, 3, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pooling_type="AVG")

  def test_average_pool_2d(self):
    kernel_shape = [1, 2]
    strides = [1, 2]

    input_shape = [10, 10, 4, 4]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pooling_type="AVG")

  def test_average_pool_2d_same_upper(self):
    kernel_shape=[1, 2]
    strides=[1, 2]
    auto_pad="SAME_UPPER"

    input_shape = [10, 10, 7, 7]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       auto_pad=auto_pad,
                       pooling_type="AVG")

  def test_average_pool_3d(self):
    kernel_shape = [3, 3, 3]
    strides = [2, 2, 2]

    input_shape = [10, 3, 23, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pooling_type="AVG")

  def test_lp2_pool_2d(self):
    kernel_shape = [1, 2]
    strides = [1, 2]
    p = 2

    input_shape = [10, 10, 4, 4]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pooling_type="LP",
                       p=p)

  def test_lp2_pool_2d_same_lower(self):
    kernel_shape = [1, 2]
    strides = [1, 2]
    p = 2
    auto_pad = "SAME_LOWER"

    input_shape = [10, 10, 7, 7]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       auto_pad=auto_pad,
                       pooling_type="LP",
                       p=p)

  def test_lp2_pool_2d_same_upper(self):
    kernel_shape = [1, 2]
    strides = [1, 2]
    p = 2
    auto_pad = "SAME_UPPER"

    input_shape = [10, 10, 7, 7]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       auto_pad=auto_pad,
                       pooling_type="LP",
                       p=p)

  def test_lp2_pool_2d_pads(self):
    kernel_shape = [3, 3]
    strides = [2, 2]
    p = 2
    pads = [1, 1, 2, 2]

    input_shape = [10, 3, 24, 24]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pads=pads,
                       pooling_type="LP",
                       p=p)

  def test_lp2_pool_3d(self):
    kernel_shape = [3, 3, 3]
    strides = [2, 2, 2]
    p = 2

    input_shape = [10, 3, 23, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pooling_type="LP",
                       p=p)

  def test_lp2_pool_1d(self):
    kernel_shape = [3]
    strides = [2]
    p = 2

    input_shape = [10, 3, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pooling_type="LP",
                       p=p)

  def test_lp3_pool_2d_pads(self):
    kernel_shape = [3, 3]
    strides = [2, 2]
    p = 3
    pads = [1, 1, 2, 2]

    input_shape = [10, 3, 24, 24]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pads=pads,
                       pooling_type="LP",
                       p=p)

  def test_lp3_pool_3d(self):
    kernel_shape = [3, 3, 3]
    strides = [2, 2, 2]
    p = 3

    input_shape = [10, 3, 23, 23, 23]
    self._test_pooling(input_shape=input_shape,
                       kernel_shape=kernel_shape,
                       strides=strides,
                       pooling_type="LP",
                       p=p)

  def test_mean_variance_normalization(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest(
          "ONNX version {} doesn't have test for MeanVarianceNormalization".
          format(defs.onnx_opset_version()))

    input_data = self._get_rnd_float32(shape=[2, 2, 2, 2])
    # Calculate expected output data using formula:
    # (Input - Mean)/SD
    mean = np.mean(input_data, keepdims=1, axis=(0, 2, 3))
    std = np.std(input_data, keepdims=1, axis=(0, 2, 3))
    expected_output = (input_data - mean) / std
    # Testing without "axes" argument should default to axes=[0,2,3]
    node_def = helper.make_node("MeanVarianceNormalization", ["X"], ["Y"])
    output = run_node(node_def, [input_data])
    np.testing.assert_almost_equal(output["Y"], expected_output, decimal=5)

  def test_min(self):
    node_def = helper.make_node("Min", ["X1", "X2", "X3", "X4"], ["Z"])
    x1 = self._get_rnd_float32(shape=[10, 10])
    x2 = self._get_rnd_float32(shape=[10, 10])
    x3 = self._get_rnd_float32(shape=[10, 10])
    x4 = self._get_rnd_float32(shape=[10, 10])
    output = run_node(node_def, [x1, x2, x3, x4])
    test_output = np.minimum(np.minimum(np.minimum(x1, x2), x3), x4)
    np.testing.assert_almost_equal(output["Z"], test_output)

  def test_mul(self):
    node_def = helper.make_node("Mul", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=[5, 10, 5, 5])
    y = self._get_rnd_float32(shape=[10, 1, 1])
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"],
                                   np.multiply(x, y.reshape([1, 10, 1, 1])))

  def test_mod(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest("ONNX version {} doesn't support Mod.".format(
          defs.onnx_opset_version()))
    x = self._get_rnd_float32(shape=[5, 5])
    y = self._get_rnd_float32(shape=[5, 5])
    node_def = helper.make_node("Mod", ["X", "Y"], ["Z"], fmod=0)
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"], np.mod(x, y))
    node_def = helper.make_node("Mod", ["X", "Y"], ["Z"], fmod=1)
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"], np.fmod(x, y))

  def test_neg(self):
    node_def = helper.make_node("Neg", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.negative(x))

  def test_non_zero(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support NonZero.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("NonZero", ["x"], ["y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    y = np.array(np.nonzero(x))
    output = run_node(node_def, [x])
    np.testing.assert_equal(output["y"], y)

  def test_onehot(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support OneHot.".format(
          defs.onnx_opset_version()))
    indices = np.array([[0, 2], [1, 2], [0, 1]])
    depth = np.int32(5)
    on_value = 6.0
    off_value = 2.0
    values = np.array([off_value, on_value])
    node_def = helper.make_node('OneHot',
                                inputs=['indices', 'depth', 'values'],
                                outputs=['y'],
                                axis=-1)
    y = (np.arange(depth) == indices[..., None]).astype(int)
    y = y * (on_value - off_value) + off_value
    output = run_node(node_def, inputs=[indices, depth, values])
    np.testing.assert_equal(output['y'], y)

  def test_range(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest("ONNX version {} doesn't support Range.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Range", ['start', 'limit', 'delta'], ['y'])
    # test positive_delta
    start = self._get_rnd_int(low=0, high=3)
    limit = self._get_rnd_int(low=10, high=30)
    delta = np.int32(3)
    output = run_node(node_def, [start, limit, delta])
    np.testing.assert_equal(output['y'], range(start, limit, delta))
    # test negative_delta
    start = self._get_rnd_int(low=20, high=30)
    limit = self._get_rnd_int(low=1, high=5)
    delta = np.int32(-2)
    output = run_node(node_def, [start, limit, delta])
    np.testing.assert_equal(output['y'], range(start, limit, delta))

  def test_resize(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support Resize with attributes: " +
          "coordinate_transformation_mode, cubic_coeff_a, exclude_outside, " +
          "extrapolation_value, nearest_mode and inputs: roi and sizes".format(
              defs.onnx_opset_version()))
    data = np.reshape(np.arange(1, 101, dtype=np.float32), [1, 1, 10, 10])
    roi = np.array([], dtype=np.float32)

    # resize_nearest_round_prefer_ceil_align_corners_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                coordinate_transformation_mode='align_corners',
                                mode='nearest',
                                nearest_mode='round_prefer_ceil')
    scales = np.array([1, 1, 0.9, 0.9], dtype=np.float32)
    expected = np.array(
        [[[[1, 2, 3, 4, 6, 7, 8, 9, 10], [11, 12, 13, 14, 16, 17, 18, 19, 20],
           [21, 22, 23, 24, 26, 27, 28, 29, 30],
           [31, 32, 33, 34, 36, 37, 38, 39, 40],
           [51, 52, 53, 54, 56, 57, 58, 59, 60],
           [61, 62, 63, 64, 66, 67, 68, 69, 70],
           [71, 72, 73, 74, 76, 77, 78, 79, 80],
           [81, 82, 83, 84, 86, 87, 88, 89, 90],
           [91, 92, 93, 94, 96, 97, 98, 99, 100]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_nearest_round_prefer_ceil_align_corners_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                coordinate_transformation_mode='align_corners',
                                mode='nearest',
                                nearest_mode='round_prefer_ceil')
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[1, 3, 4, 6, 7, 9, 10], [21, 23, 24, 26, 27, 29, 30],
           [31, 33, 34, 36, 37, 39, 40], [51, 53, 54, 56, 57, 59, 60],
           [61, 63, 64, 66, 67, 69, 70], [81, 83, 84, 86, 87, 89, 90],
           [91, 93, 94, 96, 97, 99, 100]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_nearest_floor_asymmetric_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                coordinate_transformation_mode='asymmetric',
                                mode='nearest',
                                nearest_mode='floor')
    scales = np.array([1.0, 1.0, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[1, 2, 3, 4, 6, 7, 8, 9], [11, 12, 13, 14, 16, 17, 18, 19],
           [21, 22, 23, 24, 26, 27, 28, 29], [31, 32, 33, 34, 36, 37, 38, 39],
           [51, 52, 53, 54, 56, 57, 58, 59], [61, 62, 63, 64, 66, 67, 68, 69],
           [71, 72, 73, 74, 76, 77, 78, 79], [81, 82, 83, 84, 86, 87, 88, 89]]]
        ],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_nearest_floor_asymmetric_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                coordinate_transformation_mode='asymmetric',
                                mode='nearest',
                                nearest_mode='floor')
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[1, 2, 3, 5, 6, 8, 9], [11, 12, 13, 15, 16, 18, 19],
           [21, 22, 23, 25, 26, 28, 29], [41, 42, 43, 45, 46, 48, 49],
           [51, 52, 53, 55, 56, 58, 59], [71, 72, 73, 75, 76, 78, 79],
           [81, 82, 83, 85, 86, 88, 89]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_nearest_floor_half_pixel_scales
    node_def = helper.make_node(
        "Resize",
        inputs=['X', 'roi', 'scales'],
        outputs=['Y'],
        coordinate_transformation_mode='tf_half_pixel_for_nn',
        mode='nearest',
        nearest_mode='floor')
    scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[1, 2, 4, 5, 6, 7, 9, 10], [11, 12, 14, 15, 16, 17, 19, 20],
           [31, 32, 34, 35, 36, 37, 39, 40], [41, 42, 44, 45, 46, 47, 49, 50],
           [51, 52, 54, 55, 56, 57, 59, 60], [61, 62, 64, 65, 66, 67, 69, 70],
           [81, 82, 84, 85, 86, 87, 89, 90], [91, 92, 94, 95, 96, 97, 99, 100]]]
        ],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_nearest_floor_half_pixel_sizes
    node_def = helper.make_node(
        "Resize",
        inputs=['X', 'roi', 'scales', 'sizes'],
        outputs=['Y'],
        coordinate_transformation_mode='tf_half_pixel_for_nn',
        mode='nearest',
        nearest_mode='floor')
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[1, 3, 4, 6, 7, 8, 10], [21, 23, 24, 26, 27, 28, 30],
           [31, 33, 34, 36, 37, 38, 40], [51, 53, 54, 56, 57, 58, 60],
           [61, 63, 64, 66, 67, 68, 70], [71, 73, 74, 76, 77, 78, 80],
           [91, 93, 94, 96, 97, 98, 100]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_linear_align_corners_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                coordinate_transformation_mode="align_corners",
                                mode='linear')
    scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[
            1., 2.2857141, 3.5714285, 4.857143, 6.142857, 7.428571, 8.714286,
            10.
        ],
           [
               13.857142, 15.142857, 16.428572, 17.714287, 19., 20.285715,
               21.571428, 22.857143
           ],
           [
               26.714287, 28., 29.285713, 30.571426, 31.857141, 33.142857,
               34.428574, 35.714283
           ],
           [
               39.57143, 40.857143, 42.14286, 43.428574, 44.714287, 46.,
               47.285717, 48.57143
           ],
           [
               52.428574, 53.714283, 55., 56.285713, 57.571426, 58.857143,
               60.142857, 61.428566
           ],
           [
               65.28571, 66.57143, 67.85714, 69.14285, 70.428566, 71.71428, 73.,
               74.28571
           ],
           [
               78.14286, 79.42857, 80.71428, 82., 83.28572, 84.57143, 85.85715,
               87.14286
           ],
           [
               91., 92.28571, 93.57143, 94.85715, 96.14285, 97.42857, 98.71429,
               100.
           ]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)

    # resize_linear_align_corners_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                coordinate_transformation_mode="align_corners",
                                mode='linear')
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[1., 2.5, 4., 5.5, 7., 8.5, 10.],
           [16., 17.5, 19., 20.5, 22., 23.5, 25.],
           [31., 32.5, 34., 35.5, 37., 38.5, 40.],
           [46., 47.5, 49., 50.5, 52., 53.5, 55.],
           [61., 62.5, 64., 65.5, 67., 68.5, 70.],
           [76., 77.5, 79., 80.5, 82., 83.5, 85.],
           [91., 92.5, 94., 95.5, 97., 98.5, 100.]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_linear_asymmetric_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                coordinate_transformation_mode="asymmetric",
                                mode='linear')
    scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[1., 2.25, 3.5, 4.75, 6., 7.25, 8.5, 9.75],
           [13.5, 14.75, 16., 17.25, 18.5, 19.75, 21., 22.25],
           [26., 27.25, 28.5, 29.75, 31., 32.25, 33.5, 34.75],
           [38.5, 39.75, 41., 42.25, 43.5, 44.75, 46., 47.25],
           [51., 52.25, 53.5, 54.75, 56., 57.25, 58.5, 59.75],
           [63.5, 64.75, 66., 67.25, 68.5, 69.75, 71., 72.25],
           [76., 77.25, 78.5, 79.75, 81., 82.25, 83.5, 84.75],
           [88.5, 89.75, 91., 92.25, 93.5, 94.75, 96., 97.25]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_linear_asymmetric_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                coordinate_transformation_mode="asymmetric",
                                mode='linear')
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array([[
        [[1., 2.4285715, 3.857143, 5.285714, 6.714286, 8.142857, 9.571428],
         [15.285715, 16.714287, 18.142857, 19.571428, 21., 22.42857, 23.857141],
         [29.571428, 31., 32.428574, 33.857143, 35.285717, 36.714287, 38.14286],
         [43.857143, 45.28571, 46.714283, 48.14286, 49.571434, 51., 52.42857],
         [
             58.14286, 59.57143, 61.000004, 62.428574, 63.857143, 65.28572,
             66.71429
         ], [72.42857, 73.85713, 75.28572, 76.71429, 78.14286, 79.57143, 81.],
         [86.71429, 88.14285, 89.57143, 91., 92.42857, 93.85714, 95.28571]]
    ]],
                        dtype=np.float32
                       )  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)

    # resize_linear_half_pixel_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                mode='linear')
    scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[2.375, 3.625, 4.875, 6.125, 7.375, 8.625, 9.875, 11.125],
           [14.875, 16.125, 17.375, 18.625, 19.875, 21.125, 22.375, 23.625],
           [27.375, 28.625, 29.875, 31.125, 32.375, 33.625, 34.875, 36.125],
           [39.875, 41.125, 42.375, 43.625, 44.875, 46.125, 47.375, 48.625],
           [52.375, 53.625, 54.875, 56.125, 57.375, 58.625, 59.875, 61.125],
           [64.875, 66.125, 67.375, 68.625, 69.875, 71.125, 72.375, 73.625],
           [77.375, 78.625, 79.875, 81.125, 82.375, 83.625, 84.875, 86.125],
           [89.875, 91.125, 92.375, 93.625, 94.875, 96.125, 97.375, 98.625]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_linear_half_pixel_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                mode='linear')
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array([[[[
        3.357143, 4.785714, 6.214286, 7.642857, 9.071428, 10.5, 11.928572
    ], [
        17.642857, 19.071428, 20.5, 21.92857, 23.357141, 24.785713, 26.214285
    ], [
        31.928572, 33.357143, 34.785713, 36.214287, 37.642857, 39.071426, 40.5
    ], [
        46.214287, 47.642857, 49.071426, 50.5, 51.928574, 53.357143, 54.785713
    ], [60.5, 61.928577, 63.357147, 64.78572, 66.21429, 67.64286, 69.07143
       ], [
           74.78572, 76.21429, 77.64285, 79.07143, 80.50001, 81.92857, 83.35715
       ], [89.07143, 90.5, 91.928566, 93.35715, 94.78571, 96.21428, 97.64285]]]
                        ],
                        dtype=np.float32
                       )  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)

    # resize_cubic_align_corners_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                coordinate_transformation_mode="align_corners",
                                mode='cubic',
                                cubic_coeff_a=-0.5,
                                exclude_outside=1)
    scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[
            1., 2.285714, 3.5714293, 4.857143, 6.142857, 7.4285717, 8.714287,
            10.
        ],
           [
               13.857139, 15.142854, 16.42857, 17.714281, 18.999994, 20.28571,
               21.571426, 22.857138
           ],
           [
               26.71429, 28.000004, 29.285723, 30.571436, 31.85715, 33.142864,
               34.428577, 35.71429
           ],
           [
               39.57143, 40.857143, 42.142864, 43.428574, 44.714287, 46.000004,
               47.28572, 48.57143
           ],
           [
               52.42857, 53.714287, 55., 56.285717, 57.571423, 58.857143,
               60.14286, 61.42857
           ],
           [
               65.28571, 66.57144, 67.85715, 69.14285, 70.428566, 71.71429,
               73.00001, 74.28571
           ],
           [
               78.14287, 79.42857, 80.7143, 82.00001, 83.28573, 84.571434,
               85.857155, 87.14287
           ],
           [
               91., 92.28572, 93.57144, 94.85715, 96.14285, 97.42858, 98.714294,
               100.
           ]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-1, atol=1e-6)

    # resize_cubic_align_corners_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                coordinate_transformation_mode="align_corners",
                                mode='cubic',
                                cubic_coeff_a=-0.5,
                                exclude_outside=1)
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[1., 2.5, 4., 5.5, 7., 8.5, 10.],
           [16., 17.5, 19., 20.5, 22., 23.5, 25.],
           [31., 32.5, 34., 35.5, 37., 38.5, 40.],
           [46., 47.5, 49., 50.5, 52., 53.5, 55.],
           [61., 62.5, 64., 65.5, 67., 68.5, 70.],
           [76., 77.5, 79., 80.5, 82., 83.5, 85.],
           [91., 92.5, 94., 95.5, 97., 98.5, 100.]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_almost_equal(output["Y"], expected)

    # resize_cubic_asymmetric_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                coordinate_transformation_mode="asymmetric",
                                mode='cubic',
                                cubic_coeff_a=-0.5,
                                exclude_outside=1)
    scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[1., 2.25, 3.5, 4.75, 6., 7.25, 8.5, 9.832117],
           [13.5, 14.75, 16., 17.25, 18.5, 19.75, 21., 22.332117],
           [26., 27.25, 28.5, 29.75, 31., 32.25, 33.5, 34.83212],
           [38.5, 39.75, 41., 42.25, 43.5, 44.75, 46., 47.332115],
           [51., 52.25, 53.5, 54.75, 56., 57.25, 58.5, 59.83212],
           [63.5, 64.75, 66., 67.25, 68.5, 69.75, 71., 72.332115],
           [76., 77.25, 78.5, 79.75, 81., 82.25, 83.5, 84.832115],
           [
               89.32117, 90.57117, 91.82117, 93.07117, 94.32117, 95.57117,
               96.82117, 98.15329
           ]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-1, atol=1e-6)

    # resize_cubic_asymmetric_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                coordinate_transformation_mode="asymmetric",
                                mode='cubic',
                                cubic_coeff_a=-0.5,
                                exclude_outside=1)
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[1., 2.4285715, 3.8571432, 5.285714, 6.7142863, 8.142857, 9.66485],
           [
               15.285712, 16.714287, 18.142855, 19.571426, 21., 22.428568,
               23.950563
           ],
           [
               29.57143, 31.000004, 32.428574, 33.857147, 35.285713, 36.714283,
               38.236282
           ],
           [
               43.857143, 45.285717, 46.714287, 48.142868, 49.571434, 50.999992,
               52.52199
           ],
           [
               58.142864, 59.57144, 61.000004, 62.428585, 63.857155, 65.28572,
               66.80771
           ],
           [
               72.428566, 73.85715, 75.28572, 76.714294, 78.14285, 79.57143,
               81.09343
           ],
           [
               87.6485, 89.07708, 90.505646, 91.93422, 93.36279, 94.79135,
               96.313354
           ]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-1, atol=1e-6)

    # resize_cubic_half_pixel_scales
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales'],
                                outputs=['Y'],
                                mode='cubic',
                                cubic_coeff_a=-0.5,
                                exclude_outside=1)
    scales = np.array([1, 1, 0.8, 0.8], dtype=np.float32)
    expected = np.array([[
        [[
            1.8098788, 3.1112535, 4.3612537, 5.6112533, 6.8612533, 8.111254,
            9.361254, 10.662629
        ],
         [14.823625, 16.125, 17.375, 18.625, 19.875, 21.125, 22.375, 23.676373],
         [27.323626, 28.625, 29.875, 31.125, 32.375, 33.625, 34.875, 36.176376],
         [39.823627, 41.125, 42.375, 43.625, 44.875, 46.125, 47.375, 48.676376],
         [52.323624, 53.625, 54.875, 56.125, 57.375, 58.625, 59.875, 61.176373],
         [64.82362, 66.125, 67.375, 68.625, 69.875, 71.125, 72.375, 73.67638],
         [77.32362, 78.625, 79.875, 81.125, 82.375, 83.625, 84.875, 86.17638],
         [
             90.33737, 91.63874, 92.88875, 94.13875, 95.38875, 96.63875,
             97.88875, 99.190125
         ]]
    ]],
                        dtype=np.float32
                       )  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-6, atol=1e-6)

    # resize_cubic_half_pixel_sizes
    node_def = helper.make_node("Resize",
                                inputs=['X', 'roi', 'scales', 'sizes'],
                                outputs=['Y'],
                                mode='cubic',
                                cubic_coeff_a=-0.5,
                                exclude_outside=1)
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array([[[
        [2.52846, 4.0323663, 5.460938, 6.889509, 8.318081, 9.746653, 11.250559],
        [
            17.567522, 19.071426, 20.499996, 21.928568, 23.357141, 24.785715,
            26.28962
        ],
        [
            31.853237, 33.357143, 34.785713, 36.21429, 37.64286, 39.07143,
            40.575344
        ],
        [46.13895, 47.642857, 49.071434, 50.5, 51.928566, 53.357147, 54.861053],
        [
            60.42467, 61.92858, 63.357147, 64.78572, 66.214294, 67.64287,
            69.14677
        ],
        [
            74.710396, 76.214294, 77.64286, 79.07144, 80.50001, 81.92858,
            83.432495
        ],
        [89.749466, 91.253365, 92.68193, 94.1105, 95.53907, 96.96766, 98.47156]
    ]]],
                        dtype=np.float32
                       )  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_allclose(output['Y'], expected, rtol=1e-2, atol=1e-6)

    # crop_and_resize_nearest with scales
    node_def = helper.make_node(
        "Resize",
        inputs=['X', 'roi', 'scales'],
        outputs=['Y'],
        coordinate_transformation_mode='tf_crop_and_resize',
        mode='nearest',
        nearest_mode='round_prefer_ceil',
        extrapolation_value=-20.0)
    roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
    scales = np.array([1.0, 1.0, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[46., 48., 49., -20., -20., -20., -20., -20.],
           [56., 58., 59., -20., -20., -20., -20., -20.],
           [66., 68., 69., -20., -20., -20., -20., -20.],
           [76., 78., 79., -20., -20., -20., -20., -20.],
           [86., 88., 89., -20., -20., -20., -20., -20.],
           [96., 98., 99., -20., -20., -20., -20., -20.],
           [-20., -20., -20., -20., -20., -20., -20., -20.],
           [-20., -20., -20., -20., -20., -20., -20., -20.]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_almost_equal(output["Y"], expected)

    # crop_and_resize_nearest with sizes
    node_def = helper.make_node(
        "Resize",
        inputs=['X', 'roi', 'scales', 'sizes'],
        outputs=['Y'],
        coordinate_transformation_mode='tf_crop_and_resize',
        mode='nearest',
        nearest_mode='round_prefer_ceil',
    )
    roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[46., 48., 50., 0., 0., 0., 0.], [56., 58., 60., 0., 0., 0., 0.],
           [66., 68., 70., 0., 0., 0., 0.], [76., 78., 80., 0., 0., 0., 0.],
           [86., 88., 90., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0.],
           [0., 0., 0., 0., 0., 0., 0.]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_almost_equal(output["Y"], expected)

    # crop_and_resize_linear with scales
    node_def = helper.make_node(
        "Resize",
        inputs=['X', 'roi', 'scales'],
        outputs=['Y'],
        mode='linear',
        coordinate_transformation_mode='tf_crop_and_resize',
        extrapolation_value=20.0)
    roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
    scales = np.array([1.0, 1.0, 0.8, 0.8], dtype=np.float32)
    expected = np.array(
        [[[[42.4, 43.814285, 45.228573, 20., 20., 20., 20., 20.],
           [52.685715, 54.100002, 55.514286, 20., 20., 20., 20., 20.],
           [62.971436, 64.38572, 65.80001, 20., 20., 20., 20., 20.],
           [73.25715, 74.67143, 76.08572, 20., 20., 20., 20., 20.],
           [83.54286, 84.957146, 86.37143, 20., 20., 20., 20., 20.],
           [93.82858, 95.24287, 96.65715, 20., 20., 20., 20., 20.],
           [20., 20., 20., 20., 20., 20., 20., 20.],
           [20., 20., 20., 20., 20., 20., 20., 20.]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales])
    np.testing.assert_allclose(output["Y"], expected, rtol=1e-6, atol=1e-6)

    # crop_and_resize_linear with sizes
    node_def = helper.make_node(
        "Resize",
        inputs=['X', 'roi', 'scales', 'sizes'],
        outputs=['Y'],
        mode='linear',
        coordinate_transformation_mode='tf_crop_and_resize',
        extrapolation_value=50.0)
    roi = np.array([0, 0, 0.4, 0.6, 1, 1, 1.2, 1.7], dtype=np.float32)
    scales = np.array([], dtype=np.float32)
    sizes = np.array([1, 1, 7, 7], dtype=np.int64)
    expected = np.array(
        [[[[42.4, 44.05, 45.7, 50., 50., 50., 50.],
           [54.4, 56.050003, 57.700005, 50., 50., 50., 50.],
           [66.40001, 68.05, 69.700005, 50., 50., 50., 50.],
           [78.40001, 80.05001, 81.700005, 50., 50., 50., 50.],
           [90.40001, 92.05, 93.70001, 50., 50., 50., 50.],
           [50., 50., 50., 50., 50., 50., 50.],
           [50., 50., 50., 50., 50., 50., 50.]]]],
        dtype=np.float32)  # expected value is calculated by onnx-runtime
    output = run_node(node_def, [data, roi, scales, sizes])
    np.testing.assert_allclose(output["Y"], expected, rtol=1e-6, atol=1e-6)

  def test_round(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest("ONNX version {} doesn't support Round.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Round", ["X"], ["Y"])
    x = self._get_rnd_float32(-20.0, 20.0, shape=[1000])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.round(x))

  def test_qLinearMatMul(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support QLinearMatMul.".format(
              defs.onnx_opset_version()))

    def qLinearMatMul(a, a_scale, a_zero_point, b, b_scale, b_zero_point,
                      y_scale, y_zero_point):
      y_dtype = y_zero_point.dtype
      # reshape 1-D a_scale, a_zero_point, y_scale and
      # y_zero_point so it can broadcast in arithmetic
      # operations later
      a_scale_shape = a_scale.shape
      if a_scale_shape and a_scale_shape[0] > 1:
        a_scale = np.reshape(a_scale, [a_scale_shape[0], 1])
        a_zero_point = np.reshape(a_zero_point, [a_scale_shape[0], 1])
      y_scale_shape = y_scale.shape
      if y_scale_shape and y_scale_shape[0] > 1:
        y_scale = np.reshape(y_scale, [y_scale_shape[0], 1])
        y_zero_point = np.reshape(y_zero_point, [y_scale_shape[0], 1])
      # cast everything to float32
      a = a.astype(np.float32)
      a_zero_point = a_zero_point.astype(np.float32)
      b = b.astype(np.float32)
      b_zero_point = b_zero_point.astype(np.float32)
      y_zero_point = y_zero_point.astype(np.float32)
      # dequantize a and b
      dequantized_a = np.subtract(a, a_zero_point)
      dequantized_a = np.multiply(dequantized_a, a_scale)
      dequantized_b = np.subtract(b, b_zero_point)
      dequantized_b = np.multiply(dequantized_b, b_scale)
      # matmul a and b
      x = np.matmul(dequantized_a, dequantized_b)
      # quantize x
      y = np.divide(x, y_scale)
      y = np.round(y)
      y = np.add(y, y_zero_point)
      y = np.clip(y, np.iinfo(y_dtype).min, np.iinfo(y_dtype).max)
      y = y.astype(y_dtype)
      return y

    node_def = helper.make_node('QLinearMatMul', [
        'a', 'a_scale', 'a_zero_point', 'b', 'b_scale', 'b_zero_point',
        'y_scale', 'y_zero_point'
    ], ['y'])
    for dtype in [np.int8, np.uint8]:
      low = np.iinfo(dtype).min
      high = np.iinfo(dtype).max
      a = self._get_rnd_int(low, high, [3, 4, 5, 6], dtype)
      a_scale = self._get_rnd_float32(-0.005, 0.005, [5])
      a_zero_point = self._get_rnd_int(low, high, [5], dtype)
      b = self._get_rnd_int(low, high, [3, 4, 6, 2], dtype)
      b_scale = self._get_rnd_float32(-0.005, 0.005, [2])
      b_zero_point = self._get_rnd_int(low, high, [2], dtype)
      y_scale = self._get_rnd_float32(-0.05, 0.05, [5])
      y_zero_point = self._get_rnd_int(low, high, [5], dtype)
      y = qLinearMatMul(a, a_scale, a_zero_point, b, b_scale, b_zero_point,
                        y_scale, y_zero_point)
      output = run_node(node_def, [
          a, a_scale, a_zero_point, b, b_scale, b_zero_point, y_scale,
          y_zero_point
      ])
      np.testing.assert_almost_equal(output['y'], y)

  def test_relu(self):
    node_def = helper.make_node("Relu", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.maximum(x, 0))

  def test_pad(self):
    x = self._get_rnd_float32(shape=[100, 100])
    if legacy_opset_pre_ver(11):  # for opset = 1 or 2
      # mode = constant
      node_def = helper.make_node("Pad", ["X"], ["Y"],
                                  mode="constant",
                                  pads=[1, 1, 1, 1],
                                  value=2.0)
      output = run_node(node_def, [x])
      y = np.pad(x, ((1, 1), (1, 1)), 'constant', constant_values=(2, 2))
      np.testing.assert_almost_equal(output["Y"], y)
      # mode = reflect and edge
      for mode in ['edge', 'reflect']:
        node_def = helper.make_node("Pad", ["X"], ["Y"],
                                    mode=mode,
                                    pads=[1, 1, 1, 1])
        output = run_node(node_def, [x])
        y = np.pad(x, ((1, 1), (1, 1)), mode)
        np.testing.assert_almost_equal(output["Y"], y)
    else:  # for opset = 11
      # mode = constant
      node_def = helper.make_node("Pad", ["X", "pads", "constant_values"],
                                  ["Y"],
                                  mode="constant")
      pads = np.array([1, 1, 1, 1], dtype=np.int64)
      constant_values = 2.0
      output = run_node(node_def, [x, pads, constant_values])
      y = np.pad(x, ((1, 1), (1, 1)), 'constant', constant_values=(2, 2))
      np.testing.assert_almost_equal(output["Y"], y)
      # mode = reflect and edge
      for mode in ['edge', 'reflect']:
        node_def = helper.make_node("Pad", ["X", "pads"], ["Y"], mode=mode)
        output = run_node(node_def, [x, pads])
        y = np.pad(x, ((1, 1), (1, 1)), mode)
        np.testing.assert_almost_equal(output["Y"], y)

  def test_qlinearconv(self):
    if legacy_opset_pre_ver(10):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support QLinearConv.".format(
              defs.onnx_opset_version()))

    # Test w_scale and w_zero_point as scalar
    node_def = helper.make_node("QLinearConv",
                                inputs=[
                                    "x", "x_scale", "x_zero_point", "w",
                                    "w_scale", "w_zero_point", "y_scale",
                                    "y_zero_point"
                                ],
                                outputs=["Y"])
    x = np.array([
        [255, 174, 162, 25, 203, 168, 58],
        [15, 59, 237, 95, 129, 0, 64],
        [56, 242, 153, 221, 168, 12, 166],
        [232, 178, 186, 195, 237, 162, 237],
        [188, 39, 124, 77, 80, 102, 43],
        [127, 230, 21, 83, 41, 40, 134],
        [255, 154, 92, 141, 42, 148, 247],
    ],
                 dtype=np.uint8).reshape((1, 1, 7, 7))
    x_scale = np.float32(0.00369204697)
    x_zero_point = np.uint8(132)

    w = np.array([0], dtype=np.uint8).reshape((1, 1, 1, 1))
    w_scale = np.float32(0.00172794575)
    w_zero_point = np.uint8(255)

    y = np.array([
        [0, 81, 93, 230, 52, 87, 197],
        [240, 196, 18, 160, 126, 255, 191],
        [199, 13, 102, 34, 87, 243, 89],
        [23, 77, 69, 60, 18, 93, 18],
        [67, 216, 131, 178, 175, 153, 212],
        [128, 25, 234, 172, 214, 215, 121],
        [0, 101, 163, 114, 213, 107, 8],
    ],
                 dtype=np.uint8).reshape((1, 1, 7, 7))
    y_scale = np.float32(0.00162681262)
    y_zero_point = np.uint8(123)

    output = run_node(node_def, [
        x, x_scale, x_zero_point, w, w_scale, w_zero_point, y_scale,
        y_zero_point
    ])
    np.testing.assert_almost_equal(output["Y"], y)

  def test_quantize_linear(self):
    node_def = helper.make_node("QuantizeLinear",
                                ["x", "y_scale", "y_zero_point"], ["y"])
    for x in [
        self._get_rnd_float32(-512., 512., [2, 6]),
        self._get_rnd_int(-512, 512, [2, 6])
    ]:
      y_scale = self._get_rnd_float32(-10., 10.)
      for y_zero_point in [
          self._get_rnd_int(-128, 127, dtype=np.int8),
          self._get_rnd_int(0, 255, dtype=np.uint8)
      ]:
        y = np.divide(x, y_scale)
        y = np.round(y)
        y = np.add(y, y_zero_point)
        if y_zero_point.dtype.type is np.int8:
          y = np.clip(y, -128, 127).astype(np.int8)
        else:
          y = np.clip(y, 0, 255).astype(np.uint8)
        output = run_node(node_def, [x, y_scale, y_zero_point])
        np.testing.assert_almost_equal(output["y"], y)

  def test_reciprocal(self):
    node_def = helper.make_node("Reciprocal", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], 1.0 / x)

  def test_reduce_l1(self):
    node_def = helper.make_node("ReduceL1", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"],
                                   np.linalg.norm(x, 1, (1, 2), True))

  def test_reduce_log_sum_exp(self):
    node_def = helper.make_node("ReduceLogSumExp", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.log(
                                   np.sum(np.exp(x), axis=(1, 2),
                                          keepdims=True)),
                               rtol=1e-3)

  def test_reduce_max(self):
    node_def = helper.make_node("ReduceMax", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.max(x, (1, 2), keepdims=True),
                               rtol=1e-3)

    # test tensor(uint8), tensor(int8)
    node_def = helper.make_node("ReduceMax", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_int(0, 100, [5, 10, 10, 3], np.uint8)
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.max(x, (1, 2), keepdims=True),
                               rtol=1e-3)

    node_def = helper.make_node("ReduceMax", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_int(-100, 100, [5, 10, 10, 3], np.int8)
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.max(x, (1, 2), keepdims=True),
                               rtol=1e-3)

  def test_reduce_mean(self):
    node_def = helper.make_node("ReduceMean", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.mean(x, (1, 2), keepdims=True),
                               rtol=1e-3)

  def test_reduce_min(self):
    node_def = helper.make_node("ReduceMin", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.min(x, (1, 2), keepdims=True),
                               rtol=1e-3)

    # test tensor(uint8), tensor(int8)
    node_def = helper.make_node("ReduceMin", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_int(0, 100, [5, 10, 10, 3], np.uint8)
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.min(x, (1, 2), keepdims=True),
                               rtol=1e-3)

    node_def = helper.make_node("ReduceMin", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_int(-100, 100, [5, 10, 10, 3], np.int8)
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.min(x, (1, 2), keepdims=True),
                               rtol=1e-3)

  def test_reduce_prod(self):
    node_def = helper.make_node("ReduceProd", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[1, 5, 5, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.prod(x, (1, 2), keepdims=True),
                               rtol=1e-3)

  def test_reduce_sum(self):
    node_def = helper.make_node("ReduceSum", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.sum(x, (1, 2), keepdims=True),
                               rtol=1e-3)

  def test_reduce_sum_square(self):
    node_def = helper.make_node("ReduceSumSquare", ["X"], ["Y"], axes=[1, 2])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"],
                               np.sum(np.square(x), (1, 2), keepdims=True),
                               rtol=1e-3)

  def test_pow(self):
    node_def = helper.make_node("Pow", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=1000) / 2.0 + 0.5
    y = self._get_rnd_float32(shape=1000) / 2.0 + 0.5
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"], np.power(x, y))

  def test_reshape(self):
    x = self._get_rnd_float32(shape=100)
    shape = [10, 10]
    if defs.onnx_opset_version() < 5:
      node_def = helper.make_node("Reshape", ["X"], ["Z"], shape=shape)
      output = run_node(node_def, [x])
    else:
      node_def = helper.make_node("Reshape", ["X", "Y"], ["Z"])
      output = run_node(node_def, [x, shape])

    np.testing.assert_almost_equal(output["Z"], x.reshape([10, 10]))

  def test_reshape_with_copy(self):
    x = self._get_rnd_float32(shape=[10, 20 * 30])
    shape = [0, 20, 30]
    if defs.onnx_opset_version() < 5:
      node_def = helper.make_node("Reshape", ["X"], ["Z"], shape=shape)
      output = run_node(node_def, [x])
    else:
      node_def = helper.make_node("Reshape", ["X", "Y"], ["Z"])
      output = run_node(node_def, [x, shape])

    np.testing.assert_almost_equal(output["Z"], x.reshape([10, 20, 30]))

  def test_selu(self):
    node_def = helper.make_node("Selu", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000])
    output = run_node(node_def, [x])
    alpha = 1.6732
    gamma = 1.0507
    x[x <= 0] = gamma * (alpha * np.exp(x[x <= 0]) - alpha)
    x[x > 0] = gamma * x[x > 0]
    np.testing.assert_allclose(output["Y"], x, rtol=1e-3, atol=1e-7)

  def _run_scan_node(self,
                     initial,
                     x1,
                     x2,
                     input_shape,
                     output_shape,
                     scan_input_axes=None,
                     scan_input_directions=None,
                     scan_output_axes=None,
                     scan_output_directions=None,
                     sequence_lens=None,
                     directions=None):
    """
      Subgraph looks like this.

      [const1]       state_in                    concat1_in    concat2_in_
            \           |                                 \     /
             \--------[Add]                               [Concat]
                        |                                    |
                        |                                 concat_out
                        |                                    |
                        |                                  [Add]----------[const1]
                        |                                    |
                        |                                add_out_1
                        |                                    |
                        |                                 [Split]
                        |                               /  |   |   \
                   state_out                  split1_out    ...     split4_out
    """
    val_1 = helper.make_tensor(
        name='const_tensor',
        data_type=TensorProto.FLOAT,
        dims=[1],
        vals=[1],
    )
    constant_node = helper.make_node("Constant", [], ["const_1"], value=val_1)
    state_add_node = helper.make_node("Add", ["state_in", "const_1"],
                                      ["state_out"])
    concat_node = helper.make_node("Concat", ["concat1_in", "concat2_in"],
                                   ["concat_out"],
                                   axis=0)
    add_node = helper.make_node("Add", ["concat_out", "const_1"], ["add_out"])
    split_node = helper.make_node(
        "Split", ["add_out"],
        ["split1_out", "split2_out", "split3_out", "split4_out"])

    state_in = helper.make_tensor_value_info('state_in', TensorProto.FLOAT, [1])
    concat1_in = helper.make_tensor_value_info('concat1_in', TensorProto.FLOAT,
                                               input_shape)
    concat2_in = helper.make_tensor_value_info('concat2_in', TensorProto.FLOAT,
                                               input_shape)
    state_out = helper.make_tensor_value_info('state_out', TensorProto.FLOAT,
                                              [1])
    split1_out = helper.make_tensor_value_info('split1_out', TensorProto.FLOAT,
                                               output_shape)
    split2_out = helper.make_tensor_value_info('split2_out', TensorProto.FLOAT,
                                               output_shape)
    split3_out = helper.make_tensor_value_info('split3_out', TensorProto.FLOAT,
                                               output_shape)
    split4_out = helper.make_tensor_value_info('split4_out', TensorProto.FLOAT,
                                               output_shape)

    scan_body = helper.make_graph(
        [constant_node, state_add_node, concat_node, add_node, split_node],
        "scan_body",
        [state_in, concat1_in, concat2_in],
        [state_out, split1_out, split2_out, split3_out, split4_out],
    )

    node_kwargs = {
        "op_type": "Scan",
        "inputs": ["initial", "x1", "x2"],
        "outputs": ["y", "z1", "z2", "z3", "z4"],
        "num_scan_inputs": 2,
        "body": scan_body
    }
    if sequence_lens is not None:
      node_kwargs["inputs"] = ["" if sequence_lens is str else "seq_lens"
                              ] + node_kwargs["inputs"]

    if scan_input_axes is not None:
      node_kwargs["scan_input_axes"] = scan_input_axes
    if scan_input_directions is not None:
      node_kwargs["scan_input_directions"] = scan_input_directions
    if scan_output_axes is not None:
      node_kwargs["scan_output_axes"] = scan_output_axes
    if scan_output_directions is not None:
      node_kwargs["scan_output_directions"] = scan_output_directions
    if directions is not None:
      node_kwargs["directions"] = directions

    scan_node = helper.make_node(**node_kwargs)

    if sequence_lens is None:
      inputs = [initial, x1, x2]
    else:
      inputs = [sequence_lens, initial, x1, x2]

    return run_node(scan_node, inputs)

  def test_scan_v8(self):
    if legacy_opset_pre_ver(8) or not legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} not supported.".format(
          defs.onnx_opset_version()))

    initial = self._get_rnd_int(0, 100, shape=[5, 1]).astype(np.float32)
    x1 = self._get_rnd_float32(0, 1000, shape=[5, 20, 6, 2])
    x2 = self._get_rnd_float32(0, 1000, shape=[5, 20, 6, 2])

    directions = [0, 1]
    sequence_lens = np.array([15, 20, 14, 18, 20]).astype(np.int32)

    Y = initial + (np.shape(x1)[1] if sequence_lens is str else \
                  np.reshape(sequence_lens,[-1, 1]))
    x1_out = x1 + 1
    # left-right flip x2 (reverse direction)
    x2_out = x2[:, ::-1] + 1

    Z = np.concatenate([x1_out, x2_out], 2)
    if sequence_lens is not str:
      for batch in range(len(sequence_lens)):
        # zero pad from the sequence_lens
        shape = list(np.shape(Z[batch]))
        seq_len = sequence_lens[batch]

        zero_pad = np.zeros([shape[0] - seq_len] + shape[1:])
        Z[batch] = np.concatenate([Z[batch][:seq_len], zero_pad])

    output = self._run_scan_node(initial,
                                 x1,
                                 x2, [6, 4], [3, 2],
                                 sequence_lens=sequence_lens,
                                 directions=directions)
    output_z = np.concatenate(
        [output["z1"], output["z2"], output["z3"], output["z4"]], 2)

    np.testing.assert_almost_equal(output["y"], Y)
    np.testing.assert_almost_equal(output_z, Z)

  def test_scan(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} not supported.".format(
          defs.onnx_opset_version()))

    initial = self._get_rnd_int(0, 100, shape=[2]).astype(np.float32)
    x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
    x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])

    Y = initial + np.shape(x1)[0]
    Z = np.concatenate([x1, x2], 1) + 1

    output = self._run_scan_node(initial, x1, x2, [6, 2], [3, 2])
    output_z = np.concatenate(
        [output["z1"], output["z2"], output["z3"], output["z4"]], 1)

    np.testing.assert_almost_equal(output["y"], Y)
    np.testing.assert_almost_equal(output_z, Z)

  def test_scan_input_directions(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} not supported.".format(
          defs.onnx_opset_version()))

    initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
    x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
    x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])

    Y = initial + np.shape(x1)[0]
    Z = np.concatenate([x1[::-1], x2], 1) + 1

    output = self._run_scan_node(initial,
                                 x1,
                                 x2, [6, 2], [3, 2],
                                 scan_input_directions=[1, 0])
    output_z = np.concatenate(
        [output["z1"], output["z2"], output["z3"], output["z4"]], 1)

    np.testing.assert_almost_equal(output["y"], Y)
    np.testing.assert_almost_equal(output_z, Z)

  def test_scan_input_axes(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} not supported.".format(
          defs.onnx_opset_version()))

    initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
    x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
    x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])

    Y = initial + np.shape(x1)[1]
    x1_transpose = np.transpose(x1, (1, 0, 2))
    x2_transpose = np.transpose(x2, (1, 0, 2))
    Z = np.concatenate([x1_transpose, x2_transpose], 1) + 1

    output = self._run_scan_node(initial,
                                 x1,
                                 x2, [3, 2], [10, 2],
                                 scan_input_axes=[1, 1])
    output_z = np.concatenate(
        [output["z1"], output["z2"], output["z3"], output["z4"]], 1)

    np.testing.assert_almost_equal(output["y"], Y)
    np.testing.assert_almost_equal(output_z, Z)

  def test_scan_output_directions(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} not supported.".format(
          defs.onnx_opset_version()))

    initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
    x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
    x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])

    Y = initial + np.shape(x1)[0]
    Z = np.concatenate([x1, x2], 1) + 1

    output = self._run_scan_node(initial,
                                 x1,
                                 x2, [6, 2], [3, 2],
                                 scan_output_directions=[1, 0, 0, 1])
    output_z = np.concatenate(
        [output["z1"][::-1], output["z2"], output["z3"], output["z4"][::-1]], 1)

    np.testing.assert_almost_equal(output["y"], Y)
    np.testing.assert_almost_equal(output_z, Z)

  def test_scan_output_axes(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} not supported.".format(
          defs.onnx_opset_version()))

    initial = self._get_rnd_int(0, 100, shape=[1]).astype(np.float32)
    x1 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])
    x2 = self._get_rnd_float32(0, 1000, shape=[20, 6, 2])

    Y = initial + np.shape(x1)[0]
    Z = np.concatenate([x1, x2], 1) + 1
    Z = np.transpose(Z, (1, 0, 2))

    output = self._run_scan_node(initial,
                                 x1,
                                 x2, [10, 2], [3, 2],
                                 scan_output_axes=[1, 1, 1, 1])
    output_z = np.concatenate(
        [output["z1"], output["z2"], output["z3"], output["z4"]], 0)

    np.testing.assert_almost_equal(output["y"], Y)
    np.testing.assert_almost_equal(output_z, Z)

  def test_scatter_elements1(self):
    data = np.array([[1.0, 2.0, 3.0, 4.0, 5.0]], dtype=np.float32)
    indices = np.array([[1, 3]], dtype=np.int64)
    updates = np.array([[1.1, 2.1]], dtype=np.float32)
    axis = 1
    ref_output = np.array([[1.0, 1.1, 3.0, 2.1, 5.0]], dtype=np.float32)

    if legacy_opset_pre_ver(11):
      node_def = helper.make_node("Scatter", ["data", "indices", "updates"],
                                  ["outputs"],
                                  axis=axis)
      output = run_node(node_def, [data, indices, updates])
      np.testing.assert_almost_equal(output["outputs"], ref_output)
    else:
      node_def = helper.make_node("ScatterElements",
                                  ["data", "indices", "updates"], ["outputs"],
                                  axis=axis)
      output = run_node(node_def, [data, indices, updates])
      np.testing.assert_almost_equal(output["outputs"], ref_output)

  def test_scatter_elements2(self):
    data = np.array([
        [0.0, 0.0, 0.0],
        [0.0, 0.0, 0.0],
        [0.0, 0.0, 0.0],
    ],
                    dtype=np.float32)
    indices = np.array([
        [1, 0, 2],
        [0, 2, 1],
    ], dtype=np.int64)
    updates = np.array([
        [1.0, 1.1, 1.2],
        [2.0, 2.1, 2.2],
    ], dtype=np.float32)
    ref_output = np.array([
        [2.0, 1.1, 0.0],
        [1.0, 0.0, 2.2],
        [0.0, 2.1, 1.2],
    ],
                          dtype=np.float32)

    if legacy_opset_pre_ver(11):
      node_def = helper.make_node("Scatter", ["data", "indices", "updates"],
                                  ["outputs"])
      output = run_node(node_def, [data, indices, updates])
      np.testing.assert_almost_equal(output["outputs"], ref_output)
    else:
      node_def = helper.make_node("ScatterElements",
                                  ["data", "indices", "updates"], ["outputs"])
      output = run_node(node_def, [data, indices, updates])
      np.testing.assert_almost_equal(output["outputs"], ref_output)

  def test_scatter_elements3(self):
    # indices out of bounds
    data = np.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]], dtype=np.float32)
    indices = np.array([[0, 1, 2]], dtype=np.int64)
    updates = np.array([[1.1, 2.1, 3.1]], dtype=np.float32)

    if legacy_opset_pre_ver(11):
      node_def = helper.make_node("Scatter", ["data", "indices", "updates"],
                                  ["outputs"])
    else:
      node_def = helper.make_node("ScatterElements",
                                  ["data", "indices", "updates"], ["outputs"])
    with np.testing.assert_raises(tf.errors.InvalidArgumentError):
      output = run_node(node_def, [data, indices, updates])

  def test_scatter_nd(self):
    if legacy_opset_pre_ver(11):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support ScatterND.".format(
              defs.onnx_opset_version()))

    # valid positve and negative indices for elements
    data = np.array([1, 2, 3, 4, 5, 6, 7, 8], dtype=np.float32)
    indices = np.array([[4], [3], [1], [7]], dtype=np.int64)
    updates = np.array([9, 10, 11, 12], dtype=np.float32)
    ref_output = np.array([1, 11, 3, 10, 9, 6, 7, 12], dtype=np.float32)
    node_def = helper.make_node("ScatterND", ["data", "indices", "updates"],
                                ["outputs"])
    output = run_node(node_def, [data, indices, updates])
    np.testing.assert_almost_equal(output["outputs"], ref_output)

    # valid positive and negative indices for slices
    data = np.reshape(np.arange(1, 25, dtype=np.float32), [2, 3, 4])
    indices = np.array([[-2, -1], [1, 0]], dtype=np.int64)
    updates = np.array([[39, 40, 41, 42], [43, 44, 45, 46]], dtype=np.float32)
    ref_output = np.array(
        [[[1, 2, 3, 4], [5, 6, 7, 8], [39, 40, 41, 42]],
         [[43, 44, 45, 46], [17, 18, 19, 20], [21, 22, 23, 24]]],
        dtype=np.float32)
    output = run_node(node_def, [data, indices, updates])
    np.testing.assert_almost_equal(output["outputs"], ref_output)
    indices = np.array([[-1]], dtype=np.int64)
    updates = np.array([[[43, 44, 45, 46], [47, 48, 49, 50], [51, 52, 53, 54]]],
                       dtype=np.float32)
    ref_output = np.array(
        [[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]],
         [[43, 44, 45, 46], [47, 48, 49, 50], [51, 52, 53, 54]]],
        dtype=np.float32)
    output = run_node(node_def, [data, indices, updates])
    np.testing.assert_almost_equal(output["outputs"], ref_output)

    # indices out of bounds
    indices = np.array([[0, 1, 2], [-1, -1, -3], [-2, -3, -4], [0, 2, -5]],
                       dtype=np.int64)
    updates = np.array([37, 52, 30, 39], dtype=np.float32)
    with np.testing.assert_raises(tf.errors.InvalidArgumentError):
      output = run_node(node_def, [data, indices, updates])
    indices = np.array([[0, 1], [-1, -1], [-2, -4]], dtype=np.int64)
    updates = np.array([[35, 36, 37, 38], [51, 52, 53, 54], [31, 32, 33, 34]],
                       dtype=np.float32)
    with np.testing.assert_raises(tf.errors.InvalidArgumentError):
      output = run_node(node_def, [data, indices, updates])

  def test_shape(self):
    node_def = helper.make_node("Shape", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_allclose(output["Y"], np.shape(x))

  def test_shrink(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Shrink.".format(
          defs.onnx_opset_version()))

    node_def = helper.make_node("Shrink", ["X"], ["Y"], bias=1.5, lambd=1.5)

    X = np.arange(-2.0, 2.1, dtype=np.float32)
    Y = np.array([-0.5, 0, 0, 0, 0.5], dtype=np.float32)
    output = run_node(node_def, [X])
    np.testing.assert_almost_equal(output["Y"], Y)

  def test_sigmoid(self):
    node_def = helper.make_node("Sigmoid", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], 1 / (1 + np.exp(-x)))

  def test_sign(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Sign.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Sign", ["X"], ["Y"])
    x = self._get_rnd_float32(-10, 10, [3, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.sign(x))

  def test_sinh(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Sinh.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Sinh", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.sinh(x))

  def test_size(self):
    node_def = helper.make_node("Size", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[5, 10, 10, 3])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.size(x))

  def test_slice(self):
    # test case 1 with normal inputs
    axes = [0, 1, 2]
    starts = [0, 0, 0]
    ends = [2, 2, 2]
    steps = [1, 1, 1]

    if legacy_opset_pre_ver(10):
      node_def = helper.make_node("Slice", ["X"], ["S"],
                                  axes=axes,
                                  starts=starts,
                                  ends=ends)
      x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
      output = run_node(node_def, [x])
      np.testing.assert_almost_equal(output["S"], x[0:2, 0:2, 0:2])
    else:
      node_def = helper.make_node("Slice",
                                  ["X", "starts", "ends", "axes", "steps"],
                                  ["S"])
      x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
      output = run_node(node_def, [x, starts, ends, axes, steps])
      np.testing.assert_almost_equal(output["S"], x[0:2, 0:2, 0:2])

    # test case 2 with negative, out-of-bound and default inputs
    axes = [0, 2]
    starts = [0, -7]
    ends = [-8, 20]

    if legacy_opset_pre_ver(10):
      node_def = helper.make_node("Slice", ["X"], ["S"],
                                  axes=axes,
                                  starts=starts,
                                  ends=ends)
      x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
      output = run_node(node_def, [x])
      np.testing.assert_almost_equal(output["S"], x[0:-8, :, -7:20])
    else:
      node_def = helper.make_node("Slice", ["X", "starts", "ends", "axes"],
                                  ["S"])
      x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
      output = run_node(node_def, [x, starts, ends, axes])
      np.testing.assert_almost_equal(output["S"], x[0:-8, :, -7:20])

    # test case 3 with non-default steps
    axes = [0, 1, 2]
    starts = [0, 0, 0]
    ends = [2, 2, 2]
    steps = [2, -2, -1]

    if legacy_opset_pre_ver(10) == False:
      node_def = helper.make_node("Slice",
                                  ["X", "starts", "ends", "axes", "steps"],
                                  ["S"])
      x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
      output = run_node(node_def, [x, starts, ends, axes, steps])
      np.testing.assert_almost_equal(output["S"], x[0:2:2, 0:2:-2, 0:2:-1])

  def test_softplus(self):
    node_def = helper.make_node("Softplus", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.log(np.exp(x) + 1), decimal=5)

  def test_softsign(self):
    node_def = helper.make_node("Softsign", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[3, 4, 5])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], x / (1 + np.abs(x)))

  def test_space_to_depth(self):
    node_def = helper.make_node("SpaceToDepth", ["X"], ["Y"], blocksize=2)
    x_shape = [1, 3, 2, 2]
    x = self._get_rnd_float32(shape=x_shape)
    output = run_node(node_def, [x])
    x = np.transpose(x, (0, 2, 3, 1))
    y = np.reshape(np.swapaxes(x.reshape(1, 1, 1, 1, 1, 12), 2, 3),
                   (1, 1, 1, 12))
    y = np.transpose(y, (0, 3, 1, 2))
    np.testing.assert_allclose(output["Y"], y, rtol=1e-3)

  def test_split(self):
    split = [3, 3, 4]
    node_def = helper.make_node("Split", ["X"],
                                ["Z%i" % i for i in range(len(split))],
                                axis=0,
                                split=split)
    x = self._get_rnd_float32(shape=[100]).reshape([10, 10])

    output = run_node(node_def, [x])
    for a, b in zip(list(output), np.split(x, np.cumsum(split))[:-1]):
      np.testing.assert_almost_equal(a, b)

    # test axis out of bound
    node_def = helper.make_node("Split", ["X"],
                                ["Z%i" % i for i in range(len(split))],
                                axis=3,
                                split=split)
    with np.testing.assert_raises(ValueError):
      output = run_node(node_def, [x])

  def test_sqrt(self):
    node_def = helper.make_node("Sqrt", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000]) + 1.0
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.sqrt(x), decimal=5)

  def test_squeeze(self):
    node_def = helper.make_node("Squeeze", ["X"], ["Y"], axes=[2])
    x = np.array([[[0], [1], [2]]])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.squeeze(x, axis=2))

  def test_sub(self):
    node_def = helper.make_node("Sub", ["X", "Y"], ["Z"])
    x = self._get_rnd_float32(shape=[10, 10])
    y = self._get_rnd_float32(shape=[10, 10])
    output = run_node(node_def, [x, y])
    np.testing.assert_almost_equal(output["Z"], np.subtract(x, y))

  def test_sum(self):
    node_def = helper.make_node("Sum", ["X1", "X2", "X3", "X4"], ["Z"])
    x1 = self._get_rnd_float32(shape=[10, 10])
    x2 = self._get_rnd_float32(shape=[10, 10])
    x3 = self._get_rnd_float32(shape=[10, 10])
    x4 = self._get_rnd_float32(shape=[10, 10])
    output = run_node(node_def, [x1, x2, x3, x4])
    test_output = x1 + x2 + x3 + x4
    np.testing.assert_almost_equal(output["Z"], test_output)

  def test_tanh(self):
    node_def = helper.make_node("Tanh", ["X"], ["Y"])
    x = self._get_rnd_float32(shape=[1000]) + 1.0
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.tanh(x), decimal=5)

  def test_tfidf_vectorizer(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest(
          "ONNX version {} doesn't support TfIdfVectorizer.".format(
              defs.onnx_opset_version()))

    def run_test_ints():
      node_def = helper.make_node("TfIdfVectorizer", ["X"], ["Y"],
                                  mode=mode,
                                  min_gram_length=min_gram_len,
                                  max_gram_length=max_gram_len,
                                  max_skip_count=max_skip,
                                  ngram_counts=ngram_counts,
                                  ngram_indexes=ngram_indexes,
                                  weights=weights,
                                  pool_int64s=pool_int64s)
      output = run_node(node_def, [x])
      np.testing.assert_almost_equal(output["Y"], y)

    def run_test_strings():
      node_def = helper.make_node("TfIdfVectorizer", ["X"], ["Y"],
                                  mode=mode,
                                  min_gram_length=min_gram_len,
                                  max_gram_length=max_gram_len,
                                  max_skip_count=max_skip,
                                  ngram_counts=ngram_counts,
                                  ngram_indexes=ngram_indexes,
                                  weights=weights,
                                  pool_strings=pool_strings)
      output = run_node(node_def, [x])
      np.testing.assert_almost_equal(output["Y"], y)

    # test 2d inputs with 3 elements, output contains 1-grams and 2-grams
    x = np.array([[1, 1, 3, 3, 3, 7], [8, 6, 7, 5, 6, 8], [8, 6, 7, 5, 6,
                                                           8]]).astype(np.int32)
    y = np.array([[0., 3., 0., 0., 0., 0., 0.], [0., 0., 1., 0., 1., 0., 1.],
                  [0., 0., 1., 0., 1., 0., 1.]]).astype(np.float32)
    ngram_counts = np.array([0, 4]).astype(np.int64)
    ngram_indexes = np.array([0, 1, 2, 3, 4, 5, 6]).astype(np.int64)
    pool_int64s = np.array([2, 3, 5, 4, 5, 6, 7, 8, 6, 7]).astype(np.int64)
    min_gram_len = 1
    max_gram_len = 2
    max_skip = 0
    mode = 'TF'
    weights = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
    run_test_ints()

    # test 1d inputs with indexes in non-default order, max_skip=3, output 2-grams
    x = np.array([1, 1, 3, 3, 3, 7, 8, 6, 7, 5, 6, 8]).astype(np.int32)
    y = np.array([0., 1., 0., 1., 0., 0., 2.]).astype(np.float32)
    ngram_counts = np.array([0, 4]).astype(np.int64)
    ngram_indexes = np.array([5, 0, 2, 4, 1, 6, 3]).astype(np.int64)
    pool_int64s = np.array([2, 3, 5, 4, 5, 6, 7, 8, 6, 7]).astype(np.int64)
    min_gram_len = 2
    max_gram_len = 2
    max_skip = 3
    mode = 'TF'
    weights = np.array([1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0])
    run_test_ints()

    # test IDF mode with weights, max_skip=5, output contains 1-grams and 2-grams
    x = np.array([[1, 1, 3, 3, 3, 7], [8, 6, 7, 5, 6, 8]]).astype(np.int32)
    y = np.array([[0., 0.1, 0., 0., 0., 0., 0.],
                  [0., 0., 0.1, 0., 0.5, 0.5, 0.5]]).astype(np.float32)
    ngram_counts = np.array([0, 4]).astype(np.int64)
    ngram_indexes = np.array([0, 1, 2, 3, 4, 5, 6]).astype(np.int64)
    pool_int64s = np.array([2, 3, 5, 4, 5, 6, 7, 8, 6, 7]).astype(np.int64)
    min_gram_len = 1
    max_gram_len = 2
    max_skip = 5
    mode = 'IDF'
    weights = np.array([0.1, 0.1, 0.1, 0.1, 0.5, 0.5, 0.5])
    run_test_ints()

    # test strings inputs, max_skip=5, output contains 1-grams and 2-grams
    x = np.array(['a', 'a', 'b', 'b', 'b', 'c', 'd', 'e', 'c', 'f', 'e', 'd'])
    y = np.array([0., 3., 1., 0., 1., 3., 1.]).astype(np.float32)
    ngram_counts = np.array([0, 4]).astype(np.int64)
    ngram_indexes = np.array([0, 1, 2, 3, 4, 5, 6]).astype(np.int64)
    pool_strings = np.array(['x', 'b', 'f', 'y', 'f', 'e', 'c', 'd', 'e', 'c'])
    min_gram_len = 1
    max_gram_len = 2
    max_skip = 5
    mode = 'TF'
    run_test_strings()

  def test_thresholded_relu(self):
    alpha = 2.0
    node_def = helper.make_node("ThresholdedRelu", ["X"], ["Y"], alpha=alpha)
    x = self._get_rnd_float32(-3.0, 3.0, [10])
    y = np.clip(x, alpha, np.inf)
    y[y == alpha] = 0
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], y)

  def test_tile(self):
    if legacy_onnx_pre_ver(1, 2):
      raise unittest.SkipTest(
          "The current version of ONNX does not record correctly the opset of Tile."
      )
    node_def = helper.make_node("Tile", ["X1", "X2"], ["Z"])
    x = self._get_rnd_float32(shape=[3, 5, 5, 3])
    repeats = [1, 1, 2, 1]
    output = run_node(node_def, [x, repeats])
    np.testing.assert_allclose(output["Z"], np.tile(x, repeats), rtol=1e-3)

  def test_transpose(self):
    node_def = helper.make_node("Transpose", ["X"], ["Y"], perm=[0, 2, 1])
    x = self._get_rnd_float32(shape=[1000]).reshape([10, 10, 10])
    output = run_node(node_def, [x])
    np.testing.assert_almost_equal(output["Y"], np.transpose(x, (0, 2, 1)))

  def test_topk(self):
    x = np.arange(15, dtype=np.float32).reshape(3, 5)
    values = np.array([[4, 3], [9, 8], [14, 13]], dtype=np.float32)
    indices = np.array([[4, 3], [4, 3], [4, 3]], dtype=np.int64)
    if legacy_opset_pre_ver(10):  # for opset = 1
      node_def = helper.make_node("TopK", ["x"], ["values", "indices"], k=2)
      output = run_node(node_def, [x])
    elif legacy_opset_pre_ver(11):  # for opset = 10
      k = np.array([2], dtype=np.int64)
      node_def = helper.make_node("TopK", ["x", "k"], ["values", "indices"])
      output = run_node(node_def, [x, k])
    else:  # for opset = 11
      x = np.array([[3, 2, 5, 10, 7], [12, 15, 10, 7, 20], [21, 16, 5, 3, 6]],
                   dtype=np.float32)
      values = np.array([[3, 2], [10, 7], [5, 3]], dtype=np.float32)
      indices = np.array([[0, 1], [2, 3], [2, 3]], dtype=np.int64)
      k = np.array([2], dtype=np.int64)
      node_def = helper.make_node("TopK", ["x", "k"], ["values", "indices"],
                                  largest=0,
                                  sorted=0)
      output = run_node(node_def, [x, k])
    np.testing.assert_almost_equal(output["values"], values)
    np.testing.assert_almost_equal(output["indices"], indices)

  def test_where(self):
    if legacy_opset_pre_ver(9):
      raise unittest.SkipTest("ONNX version {} doesn't support Where.".format(
          defs.onnx_opset_version()))
    node_def = helper.make_node("Where", ["C", "X", "Y"], ["Z"])
    c = np.array([[1, 0], [1, 1]], dtype=np.bool)
    x = np.array([[1, 2], [3, 4]], dtype=np.float32)
    y = np.array([[9, 8], [7, 6]], dtype=np.float32)
    output = run_node(node_def, [c, x, y])
    np.testing.assert_almost_equal(output["Z"], np.where(c, x, y))


if __name__ == '__main__':
  unittest.main()
