|
| 1 | +import unittest |
| 2 | + |
| 3 | +import numpy |
| 4 | +import pytest |
| 5 | + |
| 6 | +import dpnp as cupy |
| 7 | +from tests.helper import has_support_aspect64 |
| 8 | +from tests.third_party.cupy import testing |
| 9 | +from tests.third_party.cupy.testing import _condition |
| 10 | + |
| 11 | + |
| 12 | +@testing.parameterize( |
| 13 | + {"seed": None}, |
| 14 | + {"seed": 0}, |
| 15 | +) |
| 16 | +@pytest.mark.skipif(not has_support_aspect64(), reason="fp64 is required") |
| 17 | +class TestPermutations(unittest.TestCase): |
| 18 | + |
| 19 | + def _xp_random(self, xp): |
| 20 | + if self.seed is None: |
| 21 | + return xp.random |
| 22 | + else: |
| 23 | + pytest.skip("random.RandomState.permutation() is not supported yet") |
| 24 | + return xp.random.RandomState(seed=self.seed) |
| 25 | + |
| 26 | + # Test ranks |
| 27 | + |
| 28 | + # TODO(niboshi): Fix xfail |
| 29 | + @pytest.mark.xfail(reason="Explicit error types required") |
| 30 | + def test_permutation_zero_dim(self): |
| 31 | + for xp in (numpy, cupy): |
| 32 | + xp_random = self._xp_random(xp) |
| 33 | + a = testing.shaped_random((), xp) |
| 34 | + with pytest.raises(IndexError): |
| 35 | + xp_random.permutation(a) |
| 36 | + |
| 37 | + # Test same values |
| 38 | + |
| 39 | + @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) |
| 40 | + def test_permutation_sort_1dim(self, dtype): |
| 41 | + cupy_random = self._xp_random(cupy) |
| 42 | + a = cupy.arange(10, dtype=dtype) |
| 43 | + b = cupy.copy(a) |
| 44 | + c = cupy_random.permutation(a) |
| 45 | + testing.assert_allclose(a, b) |
| 46 | + testing.assert_allclose(b, cupy.sort(c)) |
| 47 | + |
| 48 | + @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) |
| 49 | + def test_permutation_sort_ndim(self, dtype): |
| 50 | + cupy_random = self._xp_random(cupy) |
| 51 | + a = cupy.arange(15, dtype=dtype).reshape(5, 3) |
| 52 | + b = cupy.copy(a) |
| 53 | + c = cupy_random.permutation(a) |
| 54 | + testing.assert_allclose(a, b) |
| 55 | + testing.assert_allclose(b, cupy.sort(c, axis=0)) |
| 56 | + |
| 57 | + # Test seed |
| 58 | + |
| 59 | + @testing.for_all_dtypes() |
| 60 | + def test_permutation_seed1(self, dtype): |
| 61 | + a = testing.shaped_random((10,), cupy, dtype) |
| 62 | + b = cupy.copy(a) |
| 63 | + |
| 64 | + cupy_random = self._xp_random(cupy) |
| 65 | + if self.seed is None: |
| 66 | + cupy_random.seed(0) |
| 67 | + pa = cupy_random.permutation(a) |
| 68 | + cupy_random = self._xp_random(cupy) |
| 69 | + if self.seed is None: |
| 70 | + cupy_random.seed(0) |
| 71 | + pb = cupy_random.permutation(b) |
| 72 | + |
| 73 | + testing.assert_allclose(pa, pb) |
| 74 | + |
| 75 | + |
| 76 | +@pytest.mark.skipif(not has_support_aspect64(), reason="fp64 is required") |
| 77 | +class TestShuffle(unittest.TestCase): |
| 78 | + |
| 79 | + # Test ranks |
| 80 | + |
| 81 | + @pytest.mark.skip("no proper validation yet") |
| 82 | + def test_shuffle_zero_dim(self): |
| 83 | + for xp in (numpy, cupy): |
| 84 | + a = testing.shaped_random((), xp) |
| 85 | + with pytest.raises(TypeError): |
| 86 | + xp.random.shuffle(a) |
| 87 | + |
| 88 | + # Test same values |
| 89 | + |
| 90 | + @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) |
| 91 | + def test_shuffle_sort_1dim(self, dtype): |
| 92 | + a = cupy.arange(10, dtype=dtype) |
| 93 | + b = cupy.copy(a) |
| 94 | + cupy.random.shuffle(a) |
| 95 | + testing.assert_allclose(cupy.sort(a), b) |
| 96 | + |
| 97 | + @testing.for_all_dtypes(no_float16=True, no_bool=True, no_complex=True) |
| 98 | + def test_shuffle_sort_ndim(self, dtype): |
| 99 | + a = cupy.arange(15, dtype=dtype).reshape(5, 3) |
| 100 | + b = cupy.copy(a) |
| 101 | + cupy.random.shuffle(a) |
| 102 | + testing.assert_allclose(cupy.sort(a, axis=0), b) |
| 103 | + |
| 104 | + # Test seed |
| 105 | + |
| 106 | + @testing.for_all_dtypes() |
| 107 | + def test_shuffle_seed1(self, dtype): |
| 108 | + a = testing.shaped_random((10,), cupy, dtype) |
| 109 | + b = cupy.copy(a) |
| 110 | + cupy.random.seed(0) |
| 111 | + cupy.random.shuffle(a) |
| 112 | + cupy.random.seed(0) |
| 113 | + cupy.random.shuffle(b) |
| 114 | + testing.assert_allclose(a, b) |
| 115 | + |
| 116 | + |
| 117 | +@testing.parameterize( |
| 118 | + *( |
| 119 | + testing.product( |
| 120 | + { |
| 121 | + # 'num': [0, 1, 100, 1000, 10000, 100000], |
| 122 | + "num": [0, 1, 100], # dpnp.random.permutation() is slow |
| 123 | + } |
| 124 | + ) |
| 125 | + ) |
| 126 | +) |
| 127 | +@pytest.mark.skipif(not has_support_aspect64(), reason="fp64 is required") |
| 128 | +class TestPermutationSoundness(unittest.TestCase): |
| 129 | + |
| 130 | + def setUp(self): |
| 131 | + a = cupy.random.permutation(self.num) |
| 132 | + self.a = a |
| 133 | + |
| 134 | + # Test soundness |
| 135 | + |
| 136 | + @_condition.repeat(3) |
| 137 | + def test_permutation_soundness(self): |
| 138 | + assert (numpy.sort(self.a) == numpy.arange(self.num)).all() |
| 139 | + |
| 140 | + |
| 141 | +@testing.parameterize( |
| 142 | + *( |
| 143 | + testing.product( |
| 144 | + { |
| 145 | + "offset": [0, 17, 34, 51], |
| 146 | + "gap": [1, 2, 3, 5, 7], |
| 147 | + "mask": [1, 2, 4, 8, 16, 32, 64, 128], |
| 148 | + } |
| 149 | + ) |
| 150 | + ) |
| 151 | +) |
| 152 | +class TestPermutationRandomness(unittest.TestCase): |
| 153 | + |
| 154 | + num = 256 |
| 155 | + |
| 156 | + def setUp(self): |
| 157 | + a = cupy.random.permutation(self.num) |
| 158 | + self.a = a |
| 159 | + self.num_half = int(self.num / 2) |
| 160 | + |
| 161 | + # Simple bit proportion test |
| 162 | + |
| 163 | + # This test is to check kind of randomness of permutation. |
| 164 | + # An intuition behind this test is that, when you make a sub-array |
| 165 | + # by regularly extracting half elements from the permuted array, |
| 166 | + # the sub-array should also hold randomness and accordingly |
| 167 | + # frequency of appearance of 0 and 1 at each bit position of |
| 168 | + # whole elements in the sub-array should become similar |
| 169 | + # when elements count of original array is 2^N. |
| 170 | + # Note that this is not an established method to check randomness. |
| 171 | + # TODO(anaruse): implement randomness check using some established methods. |
| 172 | + @_condition.repeat_with_success_at_least(5, 3) |
| 173 | + @pytest.mark.skip("no support of index as numpy array") |
| 174 | + def test_permutation_randomness(self): |
| 175 | + if self.mask > self.num_half: |
| 176 | + return |
| 177 | + index = numpy.arange(self.num_half) |
| 178 | + index = (index * self.gap + self.offset) % self.num |
| 179 | + samples = self.a[index] |
| 180 | + ret = samples & self.mask > 0 |
| 181 | + count = numpy.count_nonzero(ret) # expectation: self.num_half / 2 |
| 182 | + if count > self.num_half - count: |
| 183 | + count = self.num_half - count |
| 184 | + prob_le_count = self._calc_probability(count) |
| 185 | + if prob_le_count < 0.001: |
| 186 | + raise |
| 187 | + |
| 188 | + def _calc_probability(self, count): |
| 189 | + comb_all = self._comb(self.num, self.num_half) |
| 190 | + comb_le_count = 0 |
| 191 | + for i in range(count + 1): |
| 192 | + tmp = self._comb(self.num_half, i) |
| 193 | + comb_i = tmp * tmp |
| 194 | + comb_le_count += comb_i |
| 195 | + prob = comb_le_count / comb_all |
| 196 | + return prob |
| 197 | + |
| 198 | + def _comb(self, N, k): |
| 199 | + val = numpy.float64(1) |
| 200 | + for i in range(k): |
| 201 | + val *= (N - i) / (k - i) |
| 202 | + return val |
0 commit comments