|
18 | 18 | TransposeCodec, |
19 | 19 | ) |
20 | 20 | from zarr.core.buffer import default_buffer_prototype |
21 | | -from zarr.core.indexing import BasicSelection, morton_order_iter |
| 21 | +from zarr.core.indexing import BasicSelection, decode_morton, morton_order_iter |
22 | 22 | from zarr.core.metadata.v3 import ArrayV3Metadata |
23 | 23 | from zarr.dtype import UInt8 |
24 | 24 | from zarr.errors import ZarrUserWarning |
@@ -171,7 +171,8 @@ def test_open(store: Store) -> None: |
171 | 171 | assert a.metadata == b.metadata |
172 | 172 |
|
173 | 173 |
|
174 | | -def test_morton() -> None: |
| 174 | +def test_morton_exact_order() -> None: |
| 175 | + """Test exact morton ordering for power-of-2 shapes.""" |
175 | 176 | assert list(morton_order_iter((2, 2))) == [(0, 0), (1, 0), (0, 1), (1, 1)] |
176 | 177 | assert list(morton_order_iter((2, 2, 2))) == [ |
177 | 178 | (0, 0, 0), |
@@ -206,21 +207,58 @@ def test_morton() -> None: |
206 | 207 | @pytest.mark.parametrize( |
207 | 208 | "shape", |
208 | 209 | [ |
209 | | - [2, 2, 2], |
210 | | - [5, 2], |
211 | | - [2, 5], |
212 | | - [2, 9, 2], |
213 | | - [3, 2, 12], |
214 | | - [2, 5, 1], |
215 | | - [4, 3, 6, 2, 7], |
216 | | - [3, 2, 1, 6, 4, 5, 2], |
| 210 | + (2, 2, 2), |
| 211 | + (5, 2), |
| 212 | + (2, 5), |
| 213 | + (2, 9, 2), |
| 214 | + (3, 2, 12), |
| 215 | + (2, 5, 1), |
| 216 | + (4, 3, 6, 2, 7), |
| 217 | + (3, 2, 1, 6, 4, 5, 2), |
| 218 | + (1,), |
| 219 | + (1, 1), |
| 220 | + (5, 1, 3), |
| 221 | + (1, 4, 1, 2), |
217 | 222 | ], |
218 | 223 | ) |
219 | | -def test_morton2(shape: tuple[int, ...]) -> None: |
| 224 | +def test_morton_is_permutation(shape: tuple[int, ...]) -> None: |
| 225 | + """Test that morton_order_iter produces every valid coordinate exactly once.""" |
| 226 | + import itertools |
| 227 | + |
| 228 | + from zarr.core.common import product |
| 229 | + |
| 230 | + order = list(morton_order_iter(shape)) |
| 231 | + expected_len = product(shape) |
| 232 | + # completeness: every valid coordinate is present |
| 233 | + assert len(order) == expected_len |
| 234 | + # no duplicates |
| 235 | + assert len(set(order)) == expected_len |
| 236 | + # all coordinates are within bounds |
| 237 | + assert all(all(c < s for c, s in zip(coord, shape, strict=True)) for coord in order) |
| 238 | + # the set of coordinates equals the full cartesian product |
| 239 | + assert set(order) == set(itertools.product(*(range(s) for s in shape))) |
| 240 | + |
| 241 | + |
| 242 | +@pytest.mark.parametrize( |
| 243 | + "shape", |
| 244 | + [ |
| 245 | + (2, 2), |
| 246 | + (4, 4), |
| 247 | + (2, 2, 2), |
| 248 | + (4, 4, 4), |
| 249 | + (2, 2, 2, 2), |
| 250 | + ], |
| 251 | +) |
| 252 | +def test_morton_ordering(shape: tuple[int, ...]) -> None: |
| 253 | + """Test that the iteration order matches consecutive decode_morton outputs. |
| 254 | +
|
| 255 | + For power-of-2 shapes, every decode_morton output is in-bounds, |
| 256 | + so the ordering should be exactly decode_morton(0), decode_morton(1), ... |
| 257 | + """ |
| 258 | + |
220 | 259 | order = list(morton_order_iter(shape)) |
221 | | - for i, x in enumerate(order): |
222 | | - assert x not in order[:i] # no duplicates |
223 | | - assert all(x[j] < shape[j] for j in range(len(shape))) # all indices are within bounds |
| 260 | + for i, coord in enumerate(order): |
| 261 | + assert coord == decode_morton(i, shape) |
224 | 262 |
|
225 | 263 |
|
226 | 264 | @pytest.mark.parametrize("store", ["local", "memory"], indirect=["store"]) |
|
0 commit comments