text: # Copyright 2015 The TensorFlow Authors. All Rights Reserved.
  #
  # Licensed under the Apache License, Version 2.0 (the "License");
  # you may not use this file except in compliance with the License.
  # You may obtain a copy of the License at
  #
  #     http://www.apache.org/licenses/LICENSE-2.0
  #
  # Unless required by applicable law or agreed to in writing, software
  # distributed under the License is distributed on an "AS IS" BASIS,
  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  # See the License for the specific language governing permissions and
  # limitations under the License.
  # ==============================================================================
  """Tests for tensorflow.ops.math_ops.matmul."""
  
  import operator
  
  import numpy as np
  
  from tensorflow.python import tf2
  from tensorflow.python.framework import constant_op
  from tensorflow.python.framework import dtypes
  from tensorflow.python.framework import ops
  from tensorflow.python.framework import test_util
  from tensorflow.python.ops import array_ops
  from tensorflow.python.ops import gradient_checker_v2
  from tensorflow.python.ops import math_ops
  from tensorflow.python.ops import random_ops
  from tensorflow.python.ops import variables
  from tensorflow.python.platform import test as test_lib
  
  # TODO(yangzihao): Currently matmul autotuning is disabled by default. Use
  # os.environ["TF_MATMUL_AUTOTUNE_ENABLE"] = "1" to enable it.
  
  
  @test_util.with_eager_op_as_function
  class MatMulMixedType(test_lib.TestCase):
    """Simple test for tf.matmul where Tout is different from T."""
  
    def testBatchMatMulV3OutputType(self):
      # TODO(shivaniagrawal): uint8 is not supported for mixed matmul type in XLA.
      for (a_dtype, b_dtype) in [(np.int8, np.int8), (np.uint8, np.uint8)]:
        a = np.array([[1, 2], [3, 4]], dtype=a_dtype)
        b = np.array([[1, 2], [3, 4]], dtype=b_dtype)
        c = math_ops.batch_mat_mul_v3(a, b, adj_y=True, Tout=np.int32)
        self.assertAllEqual((2, 2), c.shape)
        self.assertAllEqual([[5, 11], [11, 25]], c)
  
    def testBatchMatMulV3MixedPrec(self):
      # TODO(shivaniagrawal): uint8 is not supported for mixed matmul type in XLA.
      np_bf16 = dtypes.bfloat16.as_numpy_dtype
      a = np.array([[1, 2], [3, 4]], dtype=np.int8)
      b = np.array([[1, 2], [3, 4]], dtype=np_bf16)
      c = math_ops.batch_mat_mul_v3(a, b, adj_y=True, Tout=np_bf16)
      self.assertAllEqual((2, 2), c.shape)
      self.assertAllEqual([[5, 11], [11, 25]], c)
  
  
  @test_util.with_eager_op_as_function
  class MatVecTest(test_lib.TestCase):
    """Simple test for matvec, which is sugar on top of matmul."""
  
    def testTwoByTwoCase(self):
      a = np.array([[1, 2], [3, 4]])
      b = np.array([5, 6])
      c = math_ops.matvec(a, b)
      self.assertAllEqual((2,), c.shape)
      self.assertAllEqual([5 + 2 * 6, 3 * 5 + 4 * 6], c)
  
  
  def _AddTest(test, op_name, testcase_name, fn):
    test_name = "_".join(["test", op_name, testcase_name])
    if hasattr(test, test_name):
      raise RuntimeError("Test %s defined more than once" % test_name)
    setattr(test, test_name, test_util.deprecated_graph_mode_only(fn))
  
  
  def _GetTransposedMatrices(x, x_name, kwargs):
    if kwargs["transpose_" + x_name] is True:
      return x.T
    elif kwargs["adjoint_" + x_name] is True:
      return np.conj(x.T)
    else:
      return x
  
  
  @test_util.with_eager_op_as_function
  class MatMulTest(test_lib.TestCase):
    pass  # Filled in below
  
  
  def _GetMatMulTest(a_np_, b_np_, use_static_shape_, **kwargs_):
  
    @test_util.run_without_tensor_float_32("Tests matmul")
    def Test(self):
      np_val = np.matrix(a_np_) * np.matrix(b_np_)
  
      use_gpu = True
      if a_np_.dtype is np.float16 and (
          not test_util.GpuSupportsHalfMatMulAndConv()):
        use_gpu = False
        print("Built without fp16 matmul support for Cuda, running test on CPU.")
  
      # Transpose and possibly conjugate a_np_ and b_np_ according to the
      # attributes such that tf.matmul(effective_a_np, effective_b_np, **kwargs)
      # results in a valid matrix multiplication and produces the same result as
      # np.matrix(a_np_) * np.matrix(b_np_)
      effective_a_np = _GetTransposedMatrices(a_np_, "a", kwargs_)
      effective_b_np = _GetTransposedMatrices(b_np_, "b", kwargs_)
      with self.cached_session() as sess, test_util.device(use_gpu):
        if use_static_shape_:
          a = constant_op.constant(effective_a_np)
          b = constant_op.constant(effective_b_np)
          res = math_ops.matmul(a, b, **kwargs_)
          tf_val = self.evaluate(res)
        else:
          a = array_ops.placeholder(a_np_.dtype)
          b = array_ops.placeholder(b_np_.dtype)
          res = math_ops.matmul(a, b, **kwargs_)
          tf_val = sess.run(res, feed_dict={a: effective_a_np, b: effective_b_np})
  
      self.assertAllCloseAccordingToType(
          tf_val,
          np_val,
          float_rtol=3e-5,
          float_atol=3e-5,
          half_rtol=0.2,
          half_atol=0.2)
  
    return Test
  
  
  @test_util.with_eager_op_as_function
  class MatMulGradientTest(test_lib.TestCase):
    pass  # Will be filled in below.
  
  
  def _GetMatMulGradientTest(a_np_, b_np_, use_static_shape_, **kwargs_):
  
    def Test(self):
      if not use_static_shape_ or a_np_.dtype in (np.int32, np.int64, np.float16):
        self.skipTest("Skipping infeasible gradient test.")
  
      # Transpose and possibly conjugate a_np_ and b_np_ according to the
      # attributes such that tf.matmul(effective_a_np, effective_b_np, **kwargs)
      # results in a valid matrix multiplication and produces the same result as
      # np.matrix(a_np_) * np.matrix(b_np_)
      effective_a_np = _GetTransposedMatrices(a_np_, "a", kwargs_)
      effective_b_np = _GetTransposedMatrices(b_np_, "b", kwargs_)
  
      epsilon = np.finfo(a_np_.dtype).eps
      delta = epsilon**(1.0 / 3.0)
      tol = 20 * delta
      with self.session():
        theoretical, numerical = gradient_checker_v2.compute_gradient(
            lambda x: math_ops.matmul(x, effective_b_np, **kwargs_),
            [effective_a_np],
            delta=delta)
        self.assertAllClose(theoretical, numerical, rtol=tol, atol=tol)
  
        theoretical, numerical = gradient_checker_v2.compute_gradient(
            lambda x: math_ops.matmul(effective_a_np, x, **kwargs_),
            [effective_b_np],
            delta=delta)
        self.assertAllClose(theoretical, numerical, rtol=tol, atol=tol)
  
    return Test
  
  
  @test_util.with_eager_op_as_function
  class MatMulStatsTest(test_lib.TestCase):
  
    @test_util.run_v1_only("Test requires a Graph and NodeDef inspection")
    def testSimpleStatistics(self):
      a = variables.Variable(random_ops.random_normal([25, 16]))
      b = variables.Variable(random_ops.random_normal([16, 9]))
      math_ops.matmul(a, b)
      g = ops.get_default_graph()
      for op in g.get_operations():
        flops = ops.get_stats_for_node_def(g, op.node_def, "flops").value
        if op.name == "MatMul":
          self.assertEqual(7200, flops)
  
    @test_util.run_v1_only("Test requires a Graph and NodeDef inspection")
    def testTransposedStatistics(self):
      a = variables.Variable(random_ops.random_normal([16, 25]))
      b = variables.Variable(random_ops.random_normal([16, 9]))
      math_ops.matmul(a, b, transpose_a=True)
      g = ops.get_default_graph()
      for op in g.get_operations():
        flops = ops.get_stats_for_node_def(g, op.node_def, "flops").value
        if op.name == "MatMul":
          self.assertEqual(7200, flops)
  
  
  try:
    # @ operator supported since python 3.5.
    infix_matmul = operator.matmul
  except AttributeError:
  
    # For earlier versions of python, emulate regular behavior.
    # Useful to build and test for 3.5+ on earlier versions.
    def infix_matmul(x, y):  # pylint: disable=invalid-name
      try:
        r = type(x).__matmul__(x, y)
      except AttributeError:
        r = NotImplemented
      if r is NotImplemented and type(x) is not type(y):
        try:
          r = type(y).__rmatmul__(y, x)
        except AttributeError:
          r = NotImplemented
      if r is NotImplemented:
        raise TypeError("unsupported operand type(s) for @: '{}' and '{}'"
                        .format(type(x).__name__, type(y).__name__))
      return r
  
  
  @test_util.with_eager_op_as_function
  class MatMulInfixOperatorTest(test_lib.TestCase):
  
    def testMismatchedShape(self):
      with self.assertRaisesRegex(
          Exception, (r"(In\[0\] and In\[1\] has different ndims|In\[0\] "
                      r"ndims must be >= 2|Shape must be rank 2 but is rank 1)")):
        infix_matmul(
            ops.convert_to_tensor([10.0, 20.0, 30.0]),
            ops.convert_to_tensor([[40.0, 50.0], [60.0, 70.0]]))
  
    def testMismatchedDimensions(self):
      with self.assertRaisesRegex(
          Exception, r"(Matrix size-incompatible: In\[0\]: .* In\[1\]|"
          r"Dimensions must be equal)"):
        infix_matmul(
            ops.convert_to_tensor([[10.0, 20.0, 30.0]]),
            ops.convert_to_tensor([[40.0, 50.0], [60.0, 70.0]]))
  
    @test_util.run_v1_only("Tensor.op is generally not applicable in TF 2")
    def testInfixMatmulIsTfMatmul(self):
      a = ops.convert_to_tensor([[10.0, 20.0, 30.0]])
      b = ops.convert_to_tensor([[40.0, 50.0], [60.0, 70.0], [80.0, 90.0]])
      c = infix_matmul(a, b)
      self.assertEqual(c.op.type, "MatMul")
  
    def testInfixMatmulDoesDotProduct(self):
      a = ops.convert_to_tensor([[10.0, 20.0, 30.0]])
      b = ops.convert_to_tensor([[40.0, 50.0], [60.0, 70.0], [80.0, 90.0]])
      c = infix_matmul(a, b)
      d = math_ops.matmul(a, b)
      self.assertAllEqual(c, d)
  
  
  if __name__ == "__main__":
    sizes = [1, 3, 5]
    trans_options = [[False, False], [True, False], [False, True]]
    dtypes_to_test = [
        np.int32, np.int64, np.float16, np.float32, np.float64, np.complex64,
        np.complex128
    ]
    # TF2 does not support placeholders under eager so we skip it
    for use_static_shape in set([True, tf2.enabled()]):
      for dtype in dtypes_to_test:
        for m in sizes:
          for n in sizes:
            for k in sizes:
              # Construct compatible random matrices a_np of size [m, k] and b_np
              # of size [k, n].
              a_np = np.random.normal(-5, 5, m * k).astype(dtype).reshape([m, k])
              if dtype in (np.complex64, np.complex128):
                a_np.imag = np.random.normal(-5, 5,
                                             m * k).astype(dtype).reshape([m, k])
              b_np = np.random.normal(-5, 5, k * n).astype(dtype).reshape([k, n])
              if dtype in (np.complex64, np.complex128):
                b_np.imag = np.random.normal(-5, 5,
                                             k * n).astype(dtype).reshape([k, n])
              for adjoint_a, transpose_a in trans_options:
                for adjoint_b, transpose_b in trans_options:
                  name = "%s_%s_%s_%s_%s_%s_%s_%s_%s" % (
                      use_static_shape, dtype.__name__, m, n, k, adjoint_a,
                      transpose_a, adjoint_b, transpose_b)
                  _AddTest(MatMulTest, "MatMulTest", name,
                           _GetMatMulTest(
                               a_np,
                               b_np,
                               use_static_shape,
                               adjoint_a=adjoint_a,
                               transpose_a=transpose_a,
                               adjoint_b=adjoint_b,
                               transpose_b=transpose_b))
                  _AddTest(MatMulGradientTest, "MatMulGradientTest", name,
                           _GetMatMulGradientTest(
                               a_np,
                               b_np,
                               use_static_shape,
                               adjoint_a=adjoint_a,
                               transpose_a=transpose_a,
                               adjoint_b=adjoint_b,
                               transpose_b=transpose_b))
  
    test_lib.main()
  