# 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 ConstantOp."""

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

import numpy as np

from tensorflow.python.eager import context
from tensorflow.python.eager import test
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes as dtypes_lib
from tensorflow.python.framework import errors_impl
from tensorflow.python.framework import ops
from tensorflow.python.framework import test_util
from tensorflow.python.ops import array_ops
from tensorflow.python.util import compat


# TODO(josh11b): add tests with lists/tuples, Shape.
# TODO(ashankar): Collapse with tests in constant_op_test.py and use something
# like the test_util.run_in_graph_and_eager_modes decorator to confirm
# equivalence between graph and eager execution.
class ConstantTest(test.TestCase):

  def _testCpu(self, x):
    np_ans = np.array(x)
    with context.device("/device:CPU:0"):
      tf_ans = ops.convert_to_tensor(x).numpy()
    if np_ans.dtype in [np.float32, np.float64, np.complex64, np.complex128]:
      self.assertAllClose(np_ans, tf_ans)
    else:
      self.assertAllEqual(np_ans, tf_ans)

  def _testGpu(self, x):
    device = test_util.gpu_device_name()
    if device:
      np_ans = np.array(x)
      with context.device(device):
        tf_ans = ops.convert_to_tensor(x).numpy()
      if np_ans.dtype in [np.float32, np.float64, np.complex64, np.complex128]:
        self.assertAllClose(np_ans, tf_ans)
      else:
        self.assertAllEqual(np_ans, tf_ans)

  def _testAll(self, x):
    self._testCpu(x)
    self._testGpu(x)

  def testFloat(self):
    self._testAll(np.arange(-15, 15).reshape([2, 3, 5]).astype(np.float32))
    self._testAll(
        np.random.normal(size=30).reshape([2, 3, 5]).astype(np.float32))
    self._testAll(np.empty((2, 0, 5)).astype(np.float32))

    orig = [-1.0, 2.0, 0.0]
    tf_ans = constant_op.constant(orig)
    self.assertEqual(dtypes_lib.float32, tf_ans.dtype)
    self.assertAllClose(np.array(orig), tf_ans.numpy())

    # Mix floats and ints
    orig = [-1.5, 2, 0]
    tf_ans = constant_op.constant(orig)
    self.assertEqual(dtypes_lib.float32, tf_ans.dtype)
    self.assertAllClose(np.array(orig), tf_ans.numpy())

    orig = [-5, 2.5, 0]
    tf_ans = constant_op.constant(orig)
    self.assertEqual(dtypes_lib.float32, tf_ans.dtype)
    self.assertAllClose(np.array(orig), tf_ans.numpy())

    # Mix floats and ints that don't fit in int32
    orig = [1, 2**42, 0.5]
    tf_ans = constant_op.constant(orig)
    self.assertEqual(dtypes_lib.float32, tf_ans.dtype)
    self.assertAllClose(np.array(orig), tf_ans.numpy())

  def testDouble(self):
    self._testAll(np.arange(-15, 15).reshape([2, 3, 5]).astype(np.float64))
    self._testAll(
        np.random.normal(size=30).reshape([2, 3, 5]).astype(np.float64))
    self._testAll(np.empty((2, 0, 5)).astype(np.float64))

    orig = [-5, 2.5, 0]
    tf_ans = constant_op.constant(orig, dtypes_lib.float64)
    self.assertEqual(dtypes_lib.float64, tf_ans.dtype)
    self.assertAllClose(np.array(orig), tf_ans.numpy())

    # This integer is not exactly representable as a double, gets rounded.
    tf_ans = constant_op.constant(2**54 + 1, dtypes_lib.float64)
    self.assertEqual(2**54, tf_ans.numpy())

    # This integer is larger than all non-infinite numbers representable
    # by a double, raises an exception.
    with self.assertRaisesRegexp(ValueError, "out-of-range integer"):
      constant_op.constant(10**310, dtypes_lib.float64)

  def testInt32(self):
    self._testAll(np.arange(-15, 15).reshape([2, 3, 5]).astype(np.int32))
    self._testAll(
        (100 * np.random.normal(size=30)).reshape([2, 3, 5]).astype(np.int32))
    self._testAll(np.empty((2, 0, 5)).astype(np.int32))
    self._testAll([-1, 2])

  def testInt64(self):
    self._testAll(np.arange(-15, 15).reshape([2, 3, 5]).astype(np.int64))
    self._testAll(
        (100 * np.random.normal(size=30)).reshape([2, 3, 5]).astype(np.int64))
    self._testAll(np.empty((2, 0, 5)).astype(np.int64))
    # Should detect out of range for int32 and use int64 instead.
    orig = [2, 2**48, -2**48]
    tf_ans = constant_op.constant(orig)
    self.assertEqual(dtypes_lib.int64, tf_ans.dtype)
    self.assertAllClose(np.array(orig), tf_ans.numpy())

    # Out of range for an int64
    with self.assertRaisesRegexp(ValueError, "out-of-range integer"):
      constant_op.constant([2**72])

  def testComplex64(self):
    self._testAll(
        np.complex(1, 2) *
        np.arange(-15, 15).reshape([2, 3, 5]).astype(np.complex64))
    self._testAll(
        np.complex(1, 2) *
        np.random.normal(size=30).reshape([2, 3, 5]).astype(np.complex64))
    self._testAll(np.empty((2, 0, 5)).astype(np.complex64))

  def testComplex128(self):
    self._testAll(
        np.complex(1, 2) * np.arange(-15, 15).reshape([2, 3, 5
                                                      ]).astype(np.complex128))
    self._testAll(
        np.complex(1, 2) * np.random.normal(size=30).reshape(
            [2, 3, 5]).astype(np.complex128))
    self._testAll(np.empty((2, 0, 5)).astype(np.complex128))

  def testString(self):
    val = [compat.as_bytes(str(x)) for x in np.arange(-15, 15)]
    self._testCpu(np.array(val).reshape([2, 3, 5]))
    self._testCpu(np.empty((2, 0, 5)).astype(np.str_))

  def testStringWithNulls(self):
    val = ops.convert_to_tensor(b"\0\0\0\0").numpy()
    self.assertEqual(len(val), 4)
    self.assertEqual(val, b"\0\0\0\0")

    val = ops.convert_to_tensor(b"xx\0xx").numpy()
    self.assertEqual(len(val), 5)
    self.assertAllEqual(val, b"xx\0xx")

    nested = [[b"\0\0\0\0", b"xx\0xx"], [b"\0_\0_\0_\0", b"\0"]]
    val = ops.convert_to_tensor(nested).numpy()
    # NOTE(mrry): Do not use assertAllEqual, because it converts nested to a
    #   numpy array, which loses the null terminators.
    self.assertEqual(val.tolist(), nested)

  def testExplicitShapeNumPy(self):
    c = constant_op.constant(
        np.arange(-15, 15).reshape([2, 3, 5]).astype(np.float32),
        shape=[2, 3, 5])
    self.assertEqual(c.get_shape(), [2, 3, 5])

  def testImplicitShapeNumPy(self):
    c = constant_op.constant(
        np.arange(-15, 15).reshape([2, 3, 5]).astype(np.float32))
    self.assertEqual(c.get_shape(), [2, 3, 5])

  def testExplicitShapeList(self):
    c = constant_op.constant([1, 2, 3, 4, 5, 6, 7], shape=[7])
    self.assertEqual(c.get_shape(), [7])

  def testExplicitShapeFill(self):
    c = constant_op.constant(12, shape=[7])
    self.assertEqual(c.get_shape(), [7])
    self.assertAllEqual([12, 12, 12, 12, 12, 12, 12], c.numpy())

  def testExplicitShapeReshape(self):
    c = constant_op.constant(
        np.arange(-15, 15).reshape([2, 3, 5]).astype(np.float32),
        shape=[5, 2, 3])
    self.assertEqual(c.get_shape(), [5, 2, 3])

  def testImplicitShapeList(self):
    c = constant_op.constant([1, 2, 3, 4, 5, 6, 7])
    self.assertEqual(c.get_shape(), [7])

  def testExplicitShapeNumber(self):
    c = constant_op.constant(1, shape=[1])
    self.assertEqual(c.get_shape(), [1])

  def testImplicitShapeNumber(self):
    c = constant_op.constant(1)
    self.assertEqual(c.get_shape(), [])

  def testShapeTooBig(self):
    with self.assertRaises(TypeError):
      constant_op.constant([1, 2, 3, 4, 5, 6, 7], shape=[10])

  def testShapeTooSmall(self):
    with self.assertRaises(TypeError):
      constant_op.constant([1, 2, 3, 4, 5, 6, 7], shape=[5])

  def testShapeWrong(self):
    with self.assertRaisesRegexp(TypeError, None):
      constant_op.constant([1, 2, 3, 4, 5, 6, 7], shape=[5])

  def testShape(self):
    self._testAll(constant_op.constant([1]).get_shape())

  def testDimension(self):
    x = constant_op.constant([1]).shape[0]
    self._testAll(x)

  def testDimensionList(self):
    x = [constant_op.constant([1]).shape[0]]
    self._testAll(x)

    # Mixing with regular integers is fine too
    self._testAll([1] + x)
    self._testAll(x + [1])

  def testDimensionTuple(self):
    x = constant_op.constant([1]).shape[0]
    self._testAll((x,))
    self._testAll((1, x))
    self._testAll((x, 1))

  def testInvalidLength(self):

    class BadList(list):

      def __init__(self):
        super(BadList, self).__init__([1, 2, 3])  # pylint: disable=invalid-length-returned

      def __len__(self):
        return -1

    with self.assertRaisesRegexp(ValueError, "should return >= 0"):
      constant_op.constant([BadList()])
    with self.assertRaisesRegexp(ValueError, "mixed types"):
      constant_op.constant([1, 2, BadList()])
    with self.assertRaisesRegexp(ValueError, "should return >= 0"):
      constant_op.constant(BadList())
    with self.assertRaisesRegexp(ValueError, "should return >= 0"):
      constant_op.constant([[BadList(), 2], 3])
    with self.assertRaisesRegexp(ValueError, "should return >= 0"):
      constant_op.constant([BadList(), [1, 2, 3]])
    with self.assertRaisesRegexp(ValueError, "should return >= 0"):
      constant_op.constant([BadList(), []])

    # TODO(allenl, josh11b): These cases should return exceptions rather than
    # working (currently shape checking only checks the first element of each
    # sequence recursively). Maybe the first one is fine, but the second one
    # silently truncating is rather bad.

    # with self.assertRaisesRegexp(ValueError, "should return >= 0"):
    #   constant_op.constant([[3, 2, 1], BadList()])
    # with self.assertRaisesRegexp(ValueError, "should return >= 0"):
    #   constant_op.constant([[], BadList()])

  def testSparseValuesRaiseErrors(self):
    with self.assertRaisesRegexp(ValueError, "non-rectangular Python sequence"):
      constant_op.constant([[1, 2], [3]], dtype=dtypes_lib.int32)

    with self.assertRaisesRegexp(ValueError, None):
      constant_op.constant([[1, 2], [3]])

    with self.assertRaisesRegexp(ValueError, None):
      constant_op.constant([[1, 2], [3], [4, 5]])

  # TODO(ashankar): This test fails with graph construction since
  # tensor_util.make_tensor_proto (invoked from constant_op.constant)
  # does not handle iterables (it relies on numpy conversion).
  # For consistency, should graph construction handle Python objects
  # that implement the sequence protocol (but not numpy conversion),
  # or should eager execution fail on such sequences?
  def testCustomSequence(self):

    # This is inspired by how many objects in pandas are implemented:
    # - They implement the Python sequence protocol
    # - But may raise a KeyError on __getitem__(self, 0)
    # See https://github.com/tensorflow/tensorflow/issues/20347
    class MySeq(object):

      def __getitem__(self, key):
        if key != 1 and key != 3:
          raise KeyError(key)
        return key

      def __len__(self):
        return 2

      def __iter__(self):
        l = list([1, 3])
        return l.__iter__()

    self.assertAllEqual([1, 3], self.evaluate(constant_op.constant(MySeq())))


class AsTensorTest(test.TestCase):

  def testAsTensorForTensorInput(self):
    t = constant_op.constant(10.0)
    x = ops.convert_to_tensor(t)
    self.assertIs(t, x)

  def testAsTensorForNonTensorInput(self):
    x = ops.convert_to_tensor(10.0)
    self.assertTrue(isinstance(x, ops.EagerTensor))


class ZerosTest(test.TestCase):

  def _Zeros(self, shape):
    ret = array_ops.zeros(shape)
    self.assertEqual(shape, ret.get_shape())
    return ret.numpy()

  def testConst(self):
    self.assertTrue(
        np.array_equal(self._Zeros([2, 3]), np.array([[0] * 3] * 2)))

  def testScalar(self):
    self.assertEqual(0, self._Zeros([]))
    self.assertEqual(0, self._Zeros(()))
    scalar = array_ops.zeros(constant_op.constant([], dtype=dtypes_lib.int32))
    self.assertEqual(0, scalar.numpy())

  def testDynamicSizes(self):
    np_ans = np.array([[0] * 3] * 2)
    # Creates a tensor of 2 x 3.
    d = array_ops.fill([2, 3], 12., name="fill")
    # Constructs a tensor of zeros of the same dimensions as "d".
    z = array_ops.zeros(array_ops.shape(d))
    out = z.numpy()
    self.assertAllEqual(np_ans, out)
    self.assertShapeEqual(np_ans, d)
    self.assertShapeEqual(np_ans, z)

  def testDtype(self):
    d = array_ops.fill([2, 3], 12., name="fill")
    self.assertEqual(d.get_shape(), [2, 3])
    # Test default type for both constant size and dynamic size
    z = array_ops.zeros([2, 3])
    self.assertEqual(z.dtype, dtypes_lib.float32)
    self.assertEqual([2, 3], z.get_shape())
    self.assertAllEqual(z.numpy(), np.zeros([2, 3]))
    z = array_ops.zeros(array_ops.shape(d))
    self.assertEqual(z.dtype, dtypes_lib.float32)
    self.assertEqual([2, 3], z.get_shape())
    self.assertAllEqual(z.numpy(), np.zeros([2, 3]))
    # Test explicit type control
    for dtype in [
        dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.int32,
        dtypes_lib.uint8, dtypes_lib.int16, dtypes_lib.int8,
        dtypes_lib.complex64, dtypes_lib.complex128, dtypes_lib.int64,
        dtypes_lib.bool,
        # TODO(josh11b): Support string type here.
        # dtypes_lib.string
    ]:
      z = array_ops.zeros([2, 3], dtype=dtype)
      self.assertEqual(z.dtype, dtype)
      self.assertEqual([2, 3], z.get_shape())
      z_value = z.numpy()
      self.assertFalse(np.any(z_value))
      self.assertEqual((2, 3), z_value.shape)
      z = array_ops.zeros(array_ops.shape(d), dtype=dtype)
      self.assertEqual(z.dtype, dtype)
      self.assertEqual([2, 3], z.get_shape())
      z_value = z.numpy()
      self.assertFalse(np.any(z_value))
      self.assertEqual((2, 3), z_value.shape)


class ZerosLikeTest(test.TestCase):

  def _compareZeros(self, dtype, use_gpu):
    # Creates a tensor of non-zero values with shape 2 x 3.
    # NOTE(kearnes): The default numpy dtype associated with tf.string is
    # np.object (and can't be changed without breaking a lot things), which
    # causes a TypeError in constant_op.constant below. Here we catch the
    # special case of tf.string and set the numpy dtype appropriately.
    if dtype == dtypes_lib.string:
      numpy_dtype = np.string_
    else:
      numpy_dtype = dtype.as_numpy_dtype
    d = constant_op.constant(np.ones((2, 3), dtype=numpy_dtype), dtype=dtype)
    # Constructs a tensor of zeros of the same dimensions and type as "d".
    z_var = array_ops.zeros_like(d)
    # Test that the type is correct
    self.assertEqual(z_var.dtype, dtype)
    # Test that the shape is correct
    self.assertEqual([2, 3], z_var.get_shape())

    # Test that the value is correct
    z_value = z_var.numpy()
    self.assertFalse(np.any(z_value))
    self.assertEqual((2, 3), z_value.shape)

  def testZerosLikeCPU(self):
    for dtype in [
        dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.int32,
        dtypes_lib.uint8, dtypes_lib.int16, dtypes_lib.int8,
        dtypes_lib.complex64, dtypes_lib.complex128, dtypes_lib.int64,
        # TODO(josh11b): Support string type here.
        # dtypes_lib.string
    ]:
      self._compareZeros(dtype, use_gpu=False)

  def testZerosLikeGPU(self):
    for dtype in [
        dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.int32,
        dtypes_lib.bool, dtypes_lib.int64,
        # TODO(josh11b): Support string type here.
        # dtypes_lib.string
    ]:
      self._compareZeros(dtype, use_gpu=True)

  def testZerosLikeDtype(self):
    # Make sure zeros_like works even for dtypes that cannot be cast between
    shape = (3, 5)
    dtypes = np.float32, np.complex64
    for in_type in dtypes:
      x = np.arange(15).astype(in_type).reshape(*shape)
      for out_type in dtypes:
        y = array_ops.zeros_like(x, dtype=out_type).numpy()
        self.assertEqual(y.dtype, out_type)
        self.assertEqual(y.shape, shape)
        self.assertAllEqual(y, np.zeros(shape, dtype=out_type))


class OnesTest(test.TestCase):

  def _Ones(self, shape):
    ret = array_ops.ones(shape)
    self.assertEqual(shape, ret.get_shape())
    return ret.numpy()

  def testConst(self):
    self.assertTrue(np.array_equal(self._Ones([2, 3]), np.array([[1] * 3] * 2)))

  def testScalar(self):
    self.assertEqual(1, self._Ones([]))
    self.assertEqual(1, self._Ones(()))
    scalar = array_ops.ones(constant_op.constant([], dtype=dtypes_lib.int32))
    self.assertEqual(1, scalar.numpy())

  def testDynamicSizes(self):
    np_ans = np.array([[1] * 3] * 2)
    # Creates a tensor of 2 x 3.
    d = array_ops.fill([2, 3], 12., name="fill")
    # Constructs a tensor of ones of the same dimensions as "d".
    z = array_ops.ones(array_ops.shape(d))
    out = z.numpy()
    self.assertAllEqual(np_ans, out)
    self.assertShapeEqual(np_ans, d)
    self.assertShapeEqual(np_ans, z)

  def testDtype(self):
    d = array_ops.fill([2, 3], 12., name="fill")
    self.assertEqual(d.get_shape(), [2, 3])
    # Test default type for both constant size and dynamic size
    z = array_ops.ones([2, 3])
    self.assertEqual(z.dtype, dtypes_lib.float32)
    self.assertEqual([2, 3], z.get_shape())
    self.assertAllEqual(z.numpy(), np.ones([2, 3]))
    z = array_ops.ones(array_ops.shape(d))
    self.assertEqual(z.dtype, dtypes_lib.float32)
    self.assertEqual([2, 3], z.get_shape())
    self.assertAllEqual(z.numpy(), np.ones([2, 3]))
    # Test explicit type control
    for dtype in (dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.int32,
                  dtypes_lib.uint8, dtypes_lib.int16, dtypes_lib.int8,
                  dtypes_lib.complex64, dtypes_lib.complex128, dtypes_lib.int64,
                  dtypes_lib.bool):
      z = array_ops.ones([2, 3], dtype=dtype)
      self.assertEqual(z.dtype, dtype)
      self.assertEqual([2, 3], z.get_shape())
      self.assertAllEqual(z.numpy(), np.ones([2, 3]))
      z = array_ops.ones(array_ops.shape(d), dtype=dtype)
      self.assertEqual(z.dtype, dtype)
      self.assertEqual([2, 3], z.get_shape())
      self.assertAllEqual(z.numpy(), np.ones([2, 3]))


class OnesLikeTest(test.TestCase):

  def testOnesLike(self):
    for dtype in [
        dtypes_lib.float32, dtypes_lib.float64, dtypes_lib.int32,
        dtypes_lib.uint8, dtypes_lib.int16, dtypes_lib.int8,
        dtypes_lib.complex64, dtypes_lib.complex128, dtypes_lib.int64
    ]:
      numpy_dtype = dtype.as_numpy_dtype
      # Creates a tensor of non-zero values with shape 2 x 3.
      d = constant_op.constant(np.ones((2, 3), dtype=numpy_dtype), dtype=dtype)
      # Constructs a tensor of zeros of the same dimensions and type as "d".
      z_var = array_ops.ones_like(d)
      # Test that the type is correct
      self.assertEqual(z_var.dtype, dtype)
      z_value = z_var.numpy()

      # Test that the value is correct
      self.assertTrue(np.array_equal(z_value, np.array([[1] * 3] * 2)))
      self.assertEqual([2, 3], z_var.get_shape())


class FillTest(test.TestCase):

  def _compare(self, dims, val, np_ans, use_gpu):
    ctx = context.context()
    device = "GPU:0" if (use_gpu and ctx.num_gpus()) else "CPU:0"
    with ops.device(device):
      tf_ans = array_ops.fill(dims, val, name="fill")
      out = tf_ans.numpy()
    self.assertAllClose(np_ans, out)

  def _compareAll(self, dims, val, np_ans):
    self._compare(dims, val, np_ans, False)
    self._compare(dims, val, np_ans, True)

  def testFillFloat(self):
    np_ans = np.array([[3.1415] * 3] * 2).astype(np.float32)
    self._compareAll([2, 3], np_ans[0][0], np_ans)

  def testFillDouble(self):
    np_ans = np.array([[3.1415] * 3] * 2).astype(np.float64)
    self._compareAll([2, 3], np_ans[0][0], np_ans)

  def testFillInt32(self):
    np_ans = np.array([[42] * 3] * 2).astype(np.int32)
    self._compareAll([2, 3], np_ans[0][0], np_ans)

  def testFillInt64(self):
    np_ans = np.array([[-42] * 3] * 2).astype(np.int64)
    self._compareAll([2, 3], np_ans[0][0], np_ans)

  def testFillComplex64(self):
    np_ans = np.array([[0.15] * 3] * 2).astype(np.complex64)
    self._compare([2, 3], np_ans[0][0], np_ans, use_gpu=False)

  def testFillComplex128(self):
    np_ans = np.array([[0.15] * 3] * 2).astype(np.complex128)
    self._compare([2, 3], np_ans[0][0], np_ans, use_gpu=False)

  def testFillString(self):
    np_ans = np.array([[b"yolo"] * 3] * 2)
    tf_ans = array_ops.fill([2, 3], np_ans[0][0], name="fill").numpy()
    self.assertAllEqual(np_ans, tf_ans)

  def testFillNegative(self):
    for shape in (-1,), (2, -1), (-1, 2), (-2), (-3):
      with self.assertRaises(errors_impl.InvalidArgumentError):
        array_ops.fill(shape, 7)

  def testShapeFunctionEdgeCases(self):
    # Non-vector dimensions.
    with self.assertRaises(errors_impl.InvalidArgumentError):
      array_ops.fill([[0, 1], [2, 3]], 1.0)

    # Non-scalar value.
    with self.assertRaises(errors_impl.InvalidArgumentError):
      array_ops.fill([3, 2], [1.0, 2.0])


if __name__ == "__main__":
  test.main()
