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