• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020 The TensorFlow Authors. All Rights Reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#     http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ==============================================================================
15"""Tests for tf numpy mathematical methods."""
16
17import itertools
18from absl.testing import parameterized
19import numpy as np
20
21from tensorflow.python.framework import errors
22from tensorflow.python.framework import ops
23from tensorflow.python.ops.numpy_ops import np_array_ops
24from tensorflow.python.ops.numpy_ops import np_arrays
25from tensorflow.python.ops.numpy_ops import np_math_ops
26from tensorflow.python.platform import test
27
28
29class MathTest(test.TestCase, parameterized.TestCase):
30
31  def setUp(self):
32    super(MathTest, self).setUp()
33    self.array_transforms = [
34        lambda x: x,  # Identity,
35        ops.convert_to_tensor,
36        np.array,
37        lambda x: np.array(x, dtype=np.float32),
38        lambda x: np.array(x, dtype=np.float64),
39        np_array_ops.array,
40        lambda x: np_array_ops.array(x, dtype=np.float32),
41        lambda x: np_array_ops.array(x, dtype=np.float64),
42    ]
43    self.types = [np.int32, np.int64, np.float32, np.float64]
44
45  def _testBinaryOp(self,
46                    math_fun,
47                    np_fun,
48                    name,
49                    operands=None,
50                    extra_operands=None,
51                    check_promotion=True,
52                    check_promotion_result_type=True):
53
54    def run_test(a, b):
55      for fn in self.array_transforms:
56        arg1 = fn(a)
57        arg2 = fn(b)
58        self.match(
59            math_fun(arg1, arg2),
60            np_fun(arg1, arg2),
61            msg='{}({}, {})'.format(name, arg1, arg2))
62      # Tests type promotion
63      for type_a in self.types:
64        for type_b in self.types:
65          if not check_promotion and type_a != type_b:
66            continue
67          arg1 = np_array_ops.array(a, dtype=type_a)
68          arg2 = np_array_ops.array(b, dtype=type_b)
69          self.match(
70              math_fun(arg1, arg2),
71              np_fun(arg1, arg2),
72              msg='{}({}, {})'.format(name, arg1, arg2),
73              check_dtype=check_promotion_result_type)
74
75    if operands is None:
76      operands = [(5, 2), (5, [2, 3]), (5, [[2, 3], [6, 7]]), ([1, 2, 3], 7),
77                  ([1, 2, 3], [5, 6, 7])]
78    for operand1, operand2 in operands:
79      run_test(operand1, operand2)
80    if extra_operands is not None:
81      for operand1, operand2 in extra_operands:
82        run_test(operand1, operand2)
83
84  def testDot(self):
85    extra_operands = [([1, 2], [[5, 6, 7], [8, 9, 10]]),
86                      (np.arange(2 * 3 * 5).reshape([2, 3, 5]).tolist(),
87                       np.arange(5 * 7 * 11).reshape([7, 5, 11]).tolist())]
88    return self._testBinaryOp(
89        np_math_ops.dot, np.dot, 'dot', extra_operands=extra_operands)
90
91  def testMinimum(self):
92    # The numpy version has strange result type when promotion happens,
93    # so set check_promotion_result_type to False.
94    return self._testBinaryOp(
95        np_math_ops.minimum,
96        np.minimum,
97        'minimum',
98        check_promotion_result_type=False)
99
100  def testMaximum(self):
101    # The numpy version has strange result type when promotion happens,
102    # so set check_promotion_result_type to False.
103    return self._testBinaryOp(
104        np_math_ops.maximum,
105        np.maximum,
106        'maximum',
107        check_promotion_result_type=False)
108
109  def testMatmul(self):
110    operands = [([[1, 2]], [[3, 4, 5], [6, 7, 8]])]
111    return self._testBinaryOp(
112        np_math_ops.matmul, np.matmul, 'matmul', operands=operands)
113
114  def testMatmulError(self):
115    with self.assertRaisesRegex(ValueError, r''):
116      np_math_ops.matmul(
117          np_array_ops.ones([], np.int32), np_array_ops.ones([2, 3], np.int32))
118    with self.assertRaisesRegex(ValueError, r''):
119      np_math_ops.matmul(
120          np_array_ops.ones([2, 3], np.int32), np_array_ops.ones([], np.int32))
121
122  def testVDot(self):
123    operands = [([[1, 2], [3, 4]], [[3, 4], [6, 7]]),
124                ([[1, 2], [3, 4]], [3, 4, 6, 7])]
125    return self._testBinaryOp(
126        np_math_ops.vdot, np.vdot, 'vdot', operands=operands)
127
128  def _testUnaryOp(self, math_fun, np_fun, name):
129
130    def run_test(a):
131      for fn in self.array_transforms:
132        arg1 = fn(a)
133        self.match(
134            math_fun(arg1), np_fun(arg1), msg='{}({})'.format(name, arg1))
135
136    run_test(5)
137    run_test([2, 3])
138    run_test([[2, -3], [-6, 7]])
139
140  def testLog(self):
141    self._testUnaryOp(np_math_ops.log, np.log, 'log')
142
143  def testExp(self):
144    self._testUnaryOp(np_math_ops.exp, np.exp, 'exp')
145
146  def testTanh(self):
147    self._testUnaryOp(np_math_ops.tanh, np.tanh, 'tanh')
148
149  def testSqrt(self):
150    self._testUnaryOp(np_math_ops.sqrt, np.sqrt, 'sqrt')
151
152  def match(self, actual, expected, msg='', check_dtype=True):
153    self.assertIsInstance(actual, np_arrays.ndarray)
154    if check_dtype:
155      self.assertEqual(
156          actual.dtype, expected.dtype,
157          'Dtype mismatch.\nActual: {}\nExpected: {}\n{}'.format(
158              actual.dtype.as_numpy_dtype, expected.dtype, msg))
159    self.assertEqual(
160        actual.shape, expected.shape,
161        'Shape mismatch.\nActual: {}\nExpected: {}\n{}'.format(
162            actual.shape, expected.shape, msg))
163    np.testing.assert_allclose(actual.tolist(), expected.tolist(), rtol=1e-6)
164
165  def testArgsort(self):
166    self._testUnaryOp(np_math_ops.argsort, np.argsort, 'argsort')
167
168    # Test stability
169    r = np.arange(100)
170    a = np.zeros(100)
171    np.testing.assert_equal(np_math_ops.argsort(a, kind='stable'), r)
172
173  def testArgMaxArgMin(self):
174    data = [
175        0,
176        5,
177        [1],
178        [1, 2, 3],
179        [[1, 2, 3]],
180        [[4, 6], [7, 8]],
181        [[[4, 6], [9, 10]], [[7, 8], [12, 34]]],
182    ]
183    for fn, d in itertools.product(self.array_transforms, data):
184      arr = fn(d)
185      self.match(np_math_ops.argmax(arr), np.argmax(arr))
186      self.match(np_math_ops.argmin(arr), np.argmin(arr))
187      if hasattr(arr, 'shape'):
188        ndims = len(arr.shape)
189      else:
190        ndims = np_array_ops.array(arr, copy=False).ndim
191      if ndims == 0:
192        # Numpy flattens the scalar ndarray and treats it as a 1-d array of
193        # size 1.
194        ndims = 1
195      for axis in range(-ndims, ndims):
196        self.match(
197            np_math_ops.argmax(arr, axis=axis), np.argmax(arr, axis=axis))
198        self.match(
199            np_math_ops.argmin(arr, axis=axis), np.argmin(arr, axis=axis))
200
201  @parameterized.parameters([False, True])
202  def testIsCloseEqualNan(self, equal_nan):
203    a = np.asarray([1, 1, np.nan, 1, np.nan], np.float32)
204    b = np.asarray([1, 2, 1, np.nan, np.nan], np.float32)
205    self.match(
206        np_math_ops.isclose(a, b, equal_nan=equal_nan),
207        np.isclose(a, b, equal_nan=equal_nan))
208
209  def testAverageWrongShape(self):
210    with self.assertRaisesWithPredicateMatch(errors.InvalidArgumentError, r''):
211      np_math_ops.average(np.ones([2, 3]), weights=np.ones([2, 4]))
212    with self.assertRaisesWithPredicateMatch(errors.InvalidArgumentError, r''):
213      np_math_ops.average(np.ones([2, 3]), axis=0, weights=np.ones([2, 4]))
214    with self.assertRaisesWithPredicateMatch(errors.InvalidArgumentError, r''):
215      np_math_ops.average(np.ones([2, 3]), axis=0, weights=np.ones([]))
216    with self.assertRaisesWithPredicateMatch(errors.InvalidArgumentError, r''):
217      np_math_ops.average(np.ones([2, 3]), axis=0, weights=np.ones([5]))
218
219  def testClip(self):
220
221    def run_test(arr, *args, **kwargs):
222      check_dtype = kwargs.pop('check_dtype', True)
223      for fn in self.array_transforms:
224        arr = fn(arr)
225        self.match(
226            np_math_ops.clip(arr, *args, **kwargs),
227            np.clip(arr, *args, **kwargs),
228            check_dtype=check_dtype)
229
230    # NumPy exhibits weird typing behavior when a/a_min/a_max are scalars v/s
231    # lists, e.g.,
232    #
233    # np.clip(np.array(0, dtype=np.int32), -5, 5).dtype == np.int64
234    # np.clip(np.array([0], dtype=np.int32), -5, 5).dtype == np.int32
235    # np.clip(np.array([0], dtype=np.int32), [-5], [5]).dtype == np.int64
236    #
237    # So we skip matching type. In tf-numpy the type of the output array is
238    # always the same as the input array.
239    run_test(0, -1, 5, check_dtype=False)
240    run_test(-1, -1, 5, check_dtype=False)
241    run_test(5, -1, 5, check_dtype=False)
242    run_test(-10, -1, 5, check_dtype=False)
243    run_test(10, -1, 5, check_dtype=False)
244    run_test(10, None, 5, check_dtype=False)
245    run_test(10, -1, None, check_dtype=False)
246    run_test([0, 20, -5, 4], -1, 5, check_dtype=False)
247    run_test([0, 20, -5, 4], None, 5, check_dtype=False)
248    run_test([0, 20, -5, 4], -1, None, check_dtype=False)
249    run_test([0.5, 20.2, -5.7, 4.4], -1.5, 5.1, check_dtype=False)
250
251    run_test([0, 20, -5, 4], [-5, 0, -5, 0], [0, 5, 0, 5], check_dtype=False)
252    run_test([[1, 2, 3], [4, 5, 6]], [2, 0, 2], 5, check_dtype=False)
253    run_test([[1, 2, 3], [4, 5, 6]], 0, [5, 3, 1], check_dtype=False)
254
255  def testPtp(self):
256
257    def run_test(arr, *args, **kwargs):
258      for fn in self.array_transforms:
259        arg = fn(arr)
260        self.match(
261            np_math_ops.ptp(arg, *args, **kwargs), np.ptp(arg, *args, **kwargs))
262
263    run_test([1, 2, 3])
264    run_test([1., 2., 3.])
265    run_test([[1, 2], [3, 4]], axis=1)
266    run_test([[1, 2], [3, 4]], axis=0)
267    run_test([[1, 2], [3, 4]], axis=-1)
268    run_test([[1, 2], [3, 4]], axis=-2)
269
270  def testLinSpace(self):
271    array_transforms = [
272        lambda x: x,  # Identity,
273        ops.convert_to_tensor,
274        np.array,
275        lambda x: np.array(x, dtype=np.float32),
276        lambda x: np.array(x, dtype=np.float64),
277        np_array_ops.array,
278        lambda x: np_array_ops.array(x, dtype=np.float32),
279        lambda x: np_array_ops.array(x, dtype=np.float64)
280    ]
281
282    def run_test(start, stop, **kwargs):
283      for fn1 in array_transforms:
284        for fn2 in array_transforms:
285          arg1 = fn1(start)
286          arg2 = fn2(stop)
287          self.match(
288              np_math_ops.linspace(arg1, arg2, **kwargs),
289              np.linspace(arg1, arg2, **kwargs),
290              msg='linspace({}, {})'.format(arg1, arg2))
291
292    run_test(0, 1)
293    run_test(0, 1, num=10)
294    run_test(0, 1, endpoint=False)
295    run_test(0, -1)
296    run_test(0, -1, num=10)
297    run_test(0, -1, endpoint=False)
298
299  def testLogSpace(self):
300    array_transforms = [
301        lambda x: x,  # Identity,
302        ops.convert_to_tensor,
303        np.array,
304        lambda x: np.array(x, dtype=np.float32),
305        lambda x: np.array(x, dtype=np.float64),
306        np_array_ops.array,
307        lambda x: np_array_ops.array(x, dtype=np.float32),
308        lambda x: np_array_ops.array(x, dtype=np.float64)
309    ]
310
311    def run_test(start, stop, **kwargs):
312      for fn1 in array_transforms:
313        for fn2 in array_transforms:
314          arg1 = fn1(start)
315          arg2 = fn2(stop)
316          self.match(
317              np_math_ops.logspace(arg1, arg2, **kwargs),
318              np.logspace(arg1, arg2, **kwargs),
319              msg='logspace({}, {})'.format(arg1, arg2))
320
321    run_test(0, 5)
322    run_test(0, 5, num=10)
323    run_test(0, 5, endpoint=False)
324    run_test(0, 5, base=2.0)
325    run_test(0, -5)
326    run_test(0, -5, num=10)
327    run_test(0, -5, endpoint=False)
328    run_test(0, -5, base=2.0)
329
330  def testGeomSpace(self):
331
332    def run_test(start, stop, **kwargs):
333      arg1 = start
334      arg2 = stop
335      self.match(
336          np_math_ops.geomspace(arg1, arg2, **kwargs),
337          np.geomspace(arg1, arg2, **kwargs),
338          msg='geomspace({}, {})'.format(arg1, arg2))
339
340    run_test(1, 1000, num=5)
341    run_test(1, 1000, num=5, endpoint=False)
342    run_test(-1, -1000, num=5)
343    run_test(-1, -1000, num=5, endpoint=False)
344
345  @parameterized.parameters([
346      'T', 'ndim', 'size', 'data', '__pos__', '__round__', 'tolist',
347      'transpose', 'reshape', 'ravel', 'clip', 'astype', 'max', 'mean', 'min'])
348  def testNumpyMethodsOnTensor(self, np_method):
349    a = ops.convert_to_tensor([1, 2])
350    self.assertTrue(hasattr(a, np_method))
351
352
353if __name__ == '__main__':
354  ops.enable_eager_execution()
355  ops.enable_numpy_style_type_promotion()
356  np_math_ops.enable_numpy_methods_on_tensor()
357  test.main()
358