• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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