1# Copyright 2021-2022 Huawei Technologies Co., Ltd 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"""utility functions for mindspore.scipy st tests""" 16import platform 17from typing import List 18from functools import cmp_to_key 19 20import numpy as onp 21import scipy.sparse.linalg 22from scipy.linalg import eigvals 23from mindspore import Tensor, CSRTensor 24import mindspore.ops as ops 25import mindspore.numpy as mnp 26from mindspore.common import dtype as mstype 27 28 29def to_tensor(obj, dtype=None, indice_dtype=onp.int32): 30 """ 31 This function is used to initialize Tensor or CSRTensor. 32 'obj' can be three type: 33 1. tuple or list 34 Must be the format: (list, str), and str should be 'Tensor' or 'CSRTensor'. 35 2. numpy.ndarray 36 3. scipy.sparse.csr_matrix 37 """ 38 if isinstance(obj, (tuple, list)): 39 obj, tensor_type = obj 40 if tensor_type == "Tensor": 41 obj = onp.array(obj) 42 elif tensor_type == "CSRTensor": 43 obj = scipy.sparse.csr_matrix(obj) 44 45 if dtype is None: 46 dtype = obj.dtype 47 48 if isinstance(obj, onp.ndarray): 49 obj = Tensor(obj.astype(dtype)) 50 elif isinstance(obj, scipy.sparse.csr_matrix): 51 obj = CSRTensor(indptr=Tensor(obj.indptr.astype(indice_dtype)), 52 indices=Tensor(obj.indices.astype(indice_dtype)), 53 values=Tensor(obj.data.astype(dtype)), 54 shape=obj.shape) 55 56 return obj 57 58 59def to_ndarray(obj, dtype=None): 60 if isinstance(obj, Tensor): 61 obj = obj.asnumpy() 62 elif isinstance(obj, CSRTensor): 63 obj = scipy.sparse.csr_matrix((obj.values.asnumpy(), obj.indices.asnumpy(), obj.indptr.asnumpy()), 64 shape=obj.shape) 65 obj = obj.toarray() 66 67 if dtype is not None: 68 obj = obj.astype(dtype) 69 return obj 70 71 72def match_array(actual, expected, error=0, err_msg=''): 73 if isinstance(actual, int): 74 actual = onp.asarray(actual) 75 76 if isinstance(expected, (int, tuple)): 77 expected = onp.asarray(expected) 78 79 if error > 0: 80 onp.testing.assert_almost_equal(actual, expected, decimal=error, err_msg=err_msg) 81 else: 82 onp.testing.assert_equal(actual, expected, err_msg=err_msg) 83 84 85def match_matrix(actual, expected, error=0, err_msg=''): 86 if actual.shape != expected.shape: 87 raise ValueError( 88 err_msg.join(f" actual shape {actual.shape} is not equal to expected input shape {expected.shape}")) 89 sub_abs = mnp.abs(mnp.subtract(actual, expected)) 90 no_zero_max = sub_abs.max() 91 if no_zero_max > Tensor(error, dtype=mstype.float64): 92 raise ValueError( 93 err_msg.join(f" actual value: {actual} is not equal to expected input value: {expected}")) 94 95 96def create_full_rank_matrix(shape, dtype): 97 if len(shape) < 2 or shape[-1] != shape[-2]: 98 raise ValueError( 99 'Full rank matrix must be a square matrix, but has shape: ', shape) 100 101 invertible = False 102 a = None 103 while not invertible: 104 a = onp.random.random(shape).astype(dtype) 105 try: 106 onp.linalg.inv(a) 107 invertible = True 108 except onp.linalg.LinAlgError: 109 pass 110 111 return a 112 113 114def create_random_rank_matrix(shape, dtype): 115 if dtype in [onp.complex64, onp.complex128]: 116 random_data = onp.random.uniform(low=-1.0, high=1.0, size=shape).astype(dtype) 117 random_data += 1j * onp.random.uniform(low=-1.0, high=1.0, size=shape).astype(dtype) 118 elif dtype in [onp.int32, onp.int64]: 119 random_data = onp.random.randint(10000, size=shape).astype(dtype) 120 else: 121 random_data = onp.random.random(shape).astype(dtype) 122 return random_data 123 124 125def create_sym_pos_matrix(shape, dtype): 126 if len(shape) != 2 or shape[0] != shape[1]: 127 raise ValueError( 128 'Symmetric positive definite matrix must be a square matrix, but has shape: ', shape) 129 130 n = shape[-1] 131 count = 0 132 while count < 100: 133 x = onp.random.random(shape).astype(dtype) 134 a = (onp.matmul(x, x.T) + onp.eye(n)).astype(dtype) 135 count += 1 136 if onp.min(eigvals(a)) > 0: 137 return a 138 raise ValueError('Symmetric positive definite matrix create failed') 139 140 141def gradient_check(x, net, epsilon=1e-3, symmetric=False, enumerate_fn=onp.ndenumerate): 142 # Some utils 143 def _tensor_to_numpy(arg: List[Tensor]) -> List[onp.ndarray]: 144 return [_arg.asnumpy() for _arg in arg] 145 146 def _numpy_to_tensor(arg: List[onp.ndarray]) -> List[Tensor]: 147 return [Tensor(_arg) for _arg in arg] 148 149 def _add_value(arg: List[onp.ndarray], outer, inner, value): 150 arg[outer][inner] += value 151 return arg 152 153 def _flatten(arg: List[onp.ndarray]) -> onp.ndarray: 154 arg = [_arg.reshape((-1,)) for _arg in arg] 155 return onp.concatenate(arg) 156 157 if isinstance(x, Tensor): 158 x = [x] 159 160 # Using automatic differentiation to calculate gradient 161 grad_net = ops.GradOperation(get_all=True)(net) 162 x_grad = grad_net(*x) 163 x_grad = _tensor_to_numpy(x_grad) 164 165 # Using the definition of a derivative to calculate gradient 166 x = _tensor_to_numpy(x) 167 x_grad_approx = [onp.zeros_like(_x) for _x in x_grad] 168 for outer, _x in enumerate(x): 169 for inner, _ in enumerate_fn(_x): 170 x = _add_value(x, outer, inner, epsilon) 171 y_plus = net(*_numpy_to_tensor(x)).asnumpy() 172 173 x = _add_value(x, outer, inner, -2 * epsilon) 174 y_minus = net(*_numpy_to_tensor(x)).asnumpy() 175 176 y_grad = (y_plus - y_minus) / (2 * epsilon) 177 x = _add_value(x, outer, inner, epsilon) 178 x_grad_approx = _add_value(x_grad_approx, outer, inner, y_grad) 179 180 if symmetric: 181 x_grad_approx = [0.5 * (_x_grad + _x_grad.conj().T) for _x_grad in x_grad_approx] 182 x_grad = _flatten(x_grad) 183 x_grad_approx = _flatten(x_grad_approx) 184 numerator = onp.linalg.norm(x_grad - x_grad_approx) 185 denominator = onp.linalg.norm(x_grad) + onp.linalg.norm(x_grad_approx) 186 difference = numerator / denominator 187 return difference 188 189 190def compare_eigen_decomposition(src_res, tgt_res, compute_v, rtol, atol): 191 def my_argsort(w): 192 """ 193 Sort eigenvalues, by comparing the real part first, and then the image part 194 when the real part is comparatively same (less than rtol). 195 """ 196 197 def my_cmp(x_id, y_id): 198 x = w[x_id] 199 y = w[y_id] 200 if abs(onp.real(x) - onp.real(y)) < rtol: 201 return onp.imag(x) - onp.imag(y) 202 return onp.real(x) - onp.real(y) 203 204 w_ind = list(range(len(w))) 205 w_ind.sort(key=cmp_to_key(my_cmp)) 206 return w_ind 207 208 sw, mw = src_res[0], tgt_res[0] 209 s_perm = my_argsort(sw) 210 m_perm = my_argsort(mw) 211 sw = onp.take(sw, s_perm, -1) 212 mw = onp.take(mw, m_perm, -1) 213 assert onp.allclose(sw, mw, rtol=rtol, atol=atol) 214 215 if compute_v: 216 sv, mv = src_res[1], tgt_res[1] 217 sv = onp.take(sv, s_perm, -1) 218 mv = onp.take(mv, m_perm, -1) 219 220 # Normalize eigenvectors. 221 phases = onp.sum(sv.conj() * mv, -2, keepdims=True) 222 sv = phases / onp.abs(phases) * sv 223 assert onp.allclose(sv, mv, rtol=rtol, atol=atol) 224 225 226def get_platform(): 227 return platform.system().lower() 228