# -*- coding: utf-8 -*- # MegEngine is Licensed under the Apache License, Version 2.0 (the "License") # # Copyright (c) 2014-2020 Megvii Inc. All rights reserved. # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT ARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. import collections import numpy as np import pytest import megengine.core.ops.builtin import megengine.core.tensor.raw_tensor from megengine.core._trace_option import use_symbolic_shape from megengine.core.ops._internal import all_ops from megengine.core.tensor import Tensor from megengine.core.tensor.core import apply from megengine.core.tensor.raw_tensor import RawTensor, as_raw_tensor def cvt_to_shape_desc(val, inpvar, config=None): def as_tensor(val, device): assert device is not None, "can not infer device" # TODO: should copy to appropriate device val = as_raw_tensor(val, device=device) return val device = None if inpvar is not None: assert isinstance(inpvar, RawTensor) device = device or inpvar.device if config is not None: device = device or config.device if isinstance(val, RawTensor): return as_tensor(val, device) if not isinstance(val, collections.abc.Iterable): val = [val] components = [] on_host = True for i in val: if isinstance(i, RawTensor): on_host = False device = device or i.device else: assert isinstance(i, int), ( "shape desc could contain either int or Tensor, got {}" " actually".format(repr(i)) ) components.append(i) assert components, "shape desc could not be empty" if on_host: shape = np.ascontiguousarray(components, dtype=np.int32) assert np.all(shape == components), "failed to convert to shape: {}".format( components ) return as_tensor(shape, device) for idx, v in enumerate(components): if not isinstance(v, RawTensor): vi = int(v) assert vi == v, "could not convert {} to int".format(v) v = vi components[idx] = as_tensor(v, device) return invoke_op(all_oprs.Concat(axis=0), components) def canonize_reshape(inputs, *, config): src, tshape = inputs tshape = cvt_to_shape_desc(tshape, src, config) return src, tshape def canonize_inputs(inputs, *, config): """convert immediate numbers and SharedND to SymbolVar in inputs; at least one of the inputs must be SymbolVar, so comp node and comp graph can beinferred :return: list of converted vars """ if ( isinstance(inputs, (list, tuple)) and len(inputs) == 1 and isinstance(inputs[0], (list, tuple)) ): # handle the case when a list is passed to a function with # variable-length argument (e.g. concat has signature concat(*inputs) # and is called with concat([a, b])) inputs = inputs[0] if isinstance(inputs, RawTensor): return [inputs] old_inputs = inputs inputs = [] get_comp_node = None need_cvt = False for i in old_inputs: if isinstance(i, RawTensor): get_comp_node = lambda cn=i.device.to_c(): cn else: need_cvt = True inputs.append(i) if not need_cvt: return inputs if get_comp_node is None: def get_comp_node(): return config.comp_node for idx, var in enumerate(inputs): if not isinstance(var, RawTensor): var = as_raw_tensor(var) inputs[idx] = var return inputs def invoke_op(op, inputs_, cvt_inputs=canonize_inputs): inputs = cvt_inputs( inputs_, config=megengine.core._imperative_rt.OperatorNodeConfig() ) return apply(op, *inputs) def unpack_getitem(inp, tuple_val, *, allow_newaxis=True): assert isinstance(inp, RawTensor) if not isinstance(tuple_val, tuple): tuple_val = (tuple_val,) def as_tensor(v): if not isinstance(v, RawTensor): vi = np.ascontiguousarray(v, dtype=np.int32) assert np.abs(vi - v).max() == 0, "bad index: {!r}".format(v) v = as_raw_tensor(vi) return v new_axes = [] tensors = [] items = [] cur_axis = -1 for i_idx, i in enumerate(tuple_val): cur_axis += 1 if i is np.newaxis: if cur_axis >= 0: new_axes.append(cur_axis) continue if i is Ellipsis: cur_axis = -1 for j in tuple_val[:i_idx:-1]: if j is Ellipsis: raise IndexError("only one ellipsis is allowed") if j is np.newaxis: new_axes.append(cur_axis) cur_axis -= 1 continue item = [ cur_axis, ] def push(v, item, tensors): if v is None: item.append(False) else: item.append(True) tensors.append(as_tensor(v)) if isinstance(i, slice): if i.start is None and i.stop is None and i.step is None: continue push(i.start, item, tensors) push(i.stop, item, tensors) push(i.step, item, tensors) item.append(False) # idx else: item += [False,] * 3 # begin, end, stop push(i, item, tensors) assert len(item) == 5 items.append(item) if new_axes: raise IndexError("newaxis is not allowed here") return inp, tensors, items def transpose(*args, **kwargs): op = all_ops.Dimshuffle(**kwargs).to_c() return invoke_op(op, args) def broadcast(input, tshape): op = all_ops.Broadcast().to_c() return invoke_op(op, (input, tshape), canonize_reshape) def subtensor(input, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.Subtensor(items).to_c() return invoke_op(op, (input, *tensors)) def set_subtensor(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.SetSubtensor(items).to_c() return invoke_op(op, (input, value, *tensors)) def incr_subtensor(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.IncrSubtensor(items).to_c() return invoke_op(op, (input, value, *tensors)) def advance_indexing(input, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.IndexingMultiAxisVec(items).to_c() return invoke_op(op, (input, *tensors)) def set_advance_indexing(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.IndexingSetMultiAxisVec(items).to_c() return invoke_op(op, (input, value, *tensors)) def incr_advance_indexing(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.IndexingIncrMultiAxisVec(items).to_c() return invoke_op(op, (input, value, *tensors)) def mesh_indexing(input, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.MeshIndexing(items).to_c() return invoke_op(op, (input, *tensors)) def set_mesh_indexing(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.SetMeshIndexing(items).to_c() return invoke_op(op, (input, value, *tensors)) def incr_mesh_indexing(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.IncrMeshIndexing(items).to_c() return invoke_op(op, (input, value, *tensors)) def batched_mesh_indexing(input, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.BatchedMeshIndexing(items).to_c() return invoke_op(op, (input, *tensors)) def batched_set_mesh_indexing(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.BatchedSetMeshIndexing(items).to_c() return invoke_op(op, (input, value, *tensors)) def batched_incr_mesh_indexing(input, value, tuple_val): input, tensors, items = unpack_getitem(input, tuple_val) op = all_ops.BatchedIncrMeshIndexing(items).to_c() return invoke_op(op, (input, value, *tensors)) def test_transpose(): x = np.arange(10).reshape(2, 5).astype("int32") xx = as_raw_tensor(x) (yy,) = transpose(xx, pattern="1x0") np.testing.assert_equal(np.expand_dims(x.transpose(), axis=1), yy.numpy()) def test_broadcast(): x = np.arange(10).reshape(1, 10).astype("int32") xx = as_raw_tensor(x) (yy,) = broadcast(xx, (10, 10)) np.testing.assert_equal(np.repeat(x, 10, 0), yy.numpy()) def test_subtensor(): x = np.arange(25).reshape(5, 5).astype("int32") d = np.arange(2).astype("int32") xx = as_raw_tensor(x) (yy0,) = subtensor(xx, (slice(0, 4, 2), 3)) (yy1,) = set_subtensor(xx, d, (slice(0, 4, 2), 3)) (yy2,) = incr_subtensor(xx, d, (slice(0, 4, 2), 3)) np.testing.assert_equal(x[0:4:2, 3], yy0.numpy()) x_ = x.copy() x_[0:4:2, 3] = d np.testing.assert_equal(x_, yy1.numpy()) x_ = x.copy() x_[0:4:2, 3] += d np.testing.assert_equal(x_, yy2.numpy()) def test_advance_indexing(): x = np.arange(25).reshape(5, 5).astype("int32") d = np.arange(15).reshape(3, 5).astype("int32") xx = as_raw_tensor(x) (yy0,) = advance_indexing(xx, ((0, 4, 2), slice(None, None, None))) (yy1,) = set_advance_indexing(xx, d, ((0, 4, 2), slice(None, None, None))) (yy2,) = incr_advance_indexing(xx, d, ((0, 4, 2), slice(None, None, None))) np.testing.assert_equal(x[(0, 4, 2), :], yy0.numpy()) x_ = x.copy() x_[(0, 4, 2), :] = d np.testing.assert_equal(x_, yy1.numpy()) x_ = x.copy() x_[(0, 4, 2), :] += d np.testing.assert_equal(x_, yy2.numpy()) def test_mesh_indexing(): x = np.arange(25).reshape(5, 5).astype("int32") d = np.arange(6).reshape(3, 2).astype("int32") xx = as_raw_tensor(x) (yy0,) = mesh_indexing(xx, (slice(0, 5, 2), (1, 3))) (yy1,) = set_mesh_indexing(xx, d, (slice(0, 5, 2), (1, 3))) (yy2,) = incr_mesh_indexing(xx, d, (slice(0, 5, 2), (1, 3))) r = np.ndarray(shape=(3, 2), dtype="int32") for i0, i1 in enumerate(range(0, 5, 2)): for j0, j1 in enumerate((1, 3)): r[i0, j0] = x[i1, j1] np.testing.assert_equal(r, yy0.numpy()) r = x.copy() for i0, i1 in enumerate(range(0, 5, 2)): for j0, j1 in enumerate((1, 3)): r[i1, j1] = d[i0, j0] np.testing.assert_equal(r, yy1.numpy()) r = x.copy() for i0, i1 in enumerate(range(0, 5, 2)): for j0, j1 in enumerate((1, 3)): r[i1, j1] += d[i0, j0] np.testing.assert_equal(r, yy2.numpy()) def test_batched_mesh_indexing(): x = np.arange(24).reshape(2, 3, 4).astype("int32") d = np.arange(12).reshape(2, 2, 3).astype("int32") xx = as_raw_tensor(x) s = [(0, 1, 2), (1, 2, 3)] (yy0,) = batched_mesh_indexing(xx, (slice(None, None, None), [(0, 2)] * 2, s)) (yy1,) = batched_set_mesh_indexing( xx, d, (slice(None, None, None), [(0, 2)] * 2, s) ) (yy2,) = batched_incr_mesh_indexing( xx, d, (slice(None, None, None), [(0, 2)] * 2, s) ) r = np.ndarray(shape=(2, 2, 3), dtype="int32") for i in range(2): for j0, j1 in enumerate((0, 2)): for k0, k1 in enumerate(s[i]): r[i, j0, k0] = x[i, j1, k1] np.testing.assert_equal(r, yy0.numpy()) r = x.copy() for i in range(2): for j0, j1 in enumerate((0, 2)): for k0, k1 in enumerate(s[i]): r[i, j1, k1] = d[i, j0, k0] np.testing.assert_equal(r, yy1.numpy()) r = x.copy() for i in range(2): for j0, j1 in enumerate((0, 2)): for k0, k1 in enumerate(s[i]): r[i, j1, k1] += d[i, j0, k0] np.testing.assert_equal(r, yy2.numpy()) # high level def test_advance_indexing_high_level(): x = np.arange(25).reshape(5, 5).astype("int32") d = np.arange(15).reshape(3, 5).astype("int32") xx = Tensor(x) np.testing.assert_equal(x[1, :], xx[1, :].numpy()) np.testing.assert_equal(x[:, 1], xx[:, 1].numpy()) np.testing.assert_equal(x[1:3, :], xx[1:3, :].numpy()) np.testing.assert_equal(x[:, :], xx[:, :].numpy()) np.testing.assert_equal(x[1, 1], xx[1, 1].numpy()) yy = xx[(0, 4, 2), :] np.testing.assert_equal(x[(0, 4, 2), :], yy.numpy()) x_ = x.copy() x_[(0, 4, 2), :] = d xx_ = Tensor(xx) xx_[(0, 4, 2), :] = d np.testing.assert_equal(x_, xx_.numpy()) x = np.arange(27).reshape(3, 3, 3).astype("int32") xx = Tensor(x) np.testing.assert_equal(x[1, :, :], xx[1, :, :].numpy()) np.testing.assert_equal(x[1, :, 1], xx[1, :, 1].numpy()) np.testing.assert_equal(x[1, 0:1, :], xx[1, 0:1, :].numpy()) np.testing.assert_equal(x[0:1, 1, 1], xx[0:1, 1, 1].numpy()) np.testing.assert_equal(x[:, 1, 1], xx[:, 1, 1].numpy()) np.testing.assert_equal(x[:, 1], xx[:, 1].numpy()) np.testing.assert_equal(x[1, 1:2], xx[1, 1:2].numpy()) x_ = x.copy() x_[1, 1, 1] = -1 xx[1, 1, 1] = -1 np.testing.assert_equal(x_, xx.numpy()) x_[:, 1, 1] = -2 xx[:, 1, 1] = x_[:, 1, 1] np.testing.assert_equal(x_, xx.numpy()) x_[0:1, :, 1] = -3 xx[0:1, :, 1] = x_[0:1, :, 1] np.testing.assert_equal(x_, xx.numpy()) x_[0:1, :, 1] = -4 y = Tensor(x_) xx[0:1, :, 1] = y[0:1, :, 1] np.testing.assert_equal(y.numpy(), xx.numpy()) x[:] = 1 xx[:] = 1 np.testing.assert_equal(x, xx.numpy()) x = np.arange(9).reshape(3, 3).astype("int32") xx = Tensor(x) y = np.array([1, 2]) yy = Tensor(y) np.testing.assert_equal(x[:, y[0]], xx[:, y[0]].numpy()) np.testing.assert_equal(x[:, y[0]], xx[:, yy[0]].numpy()) np.testing.assert_equal(x[:, y], xx[:, y].numpy()) np.testing.assert_equal(x[:, y], xx[:, yy].numpy()) x_ = x.copy() x_[:, y[0]] = -1 xx_ = Tensor(x_) xx[:, yy[0]] = xx_[:, yy[0]] np.testing.assert_equal(x_, xx.numpy()) x_[:, y] = -1 xx_ = Tensor(x_) xx[:, yy] = xx_[:, yy] np.testing.assert_equal(x_, xx.numpy()) x = np.arange(9).reshape(3, 3).astype("int32") xx = Tensor(x) y = np.array([1]) yy = Tensor(y) np.testing.assert_equal(x[:, y[0]], xx[:, y[0]].numpy()) np.testing.assert_equal(x[:, y[0]], xx[:, yy[0]].numpy()) np.testing.assert_equal(x[:, y], xx[:, y].numpy()) np.testing.assert_equal(x[:, y], xx[:, yy].numpy()) x = np.arange(9).reshape(3, 3).astype("int32") xx = Tensor(x) np.testing.assert_equal(x[[0, 1], 0], xx[[0, 1], 0].numpy()) np.testing.assert_equal(x[0:2, 0], xx[0:2, 0].numpy()) def test_advance_indexing_with_bool(): a = np.arange(9).reshape(3, 3).astype(np.float32) b = np.array([1, 2, 3]) c = np.array([1, 2, 3]) aa = Tensor(a) bb = Tensor(b) cc = Tensor(c) np.testing.assert_equal(a[b == 1, c == 2], aa[bb == 1, cc == 2].numpy()) a[b == 1, c == 2] = -1.0 aa[bb == 1, cc == 2] = -1.0 np.testing.assert_equal(a, aa.numpy()) a = np.arange(9).reshape(3, 3).astype(np.float32) b = np.array([False, True, True]) c = np.array([2, 0]).astype(np.int32) aa = Tensor(a) bb = Tensor(b) cc = Tensor(c) np.testing.assert_equal(a[b, c], aa[bb, cc].numpy()) a[b, c] = -1.0 aa[bb, cc] = -1.0 np.testing.assert_equal(a, aa.numpy()) d = np.array([-1, -2], dtype=np.float32) dd = Tensor(d) a[b, c] = d aa[bb, cc] = dd np.testing.assert_equal(a, aa.numpy()) a = np.ones((2, 2)) b = np.array([[True, False], [False, True]]) aa = Tensor(a) bb = Tensor(b) np.testing.assert_equal(a[b], aa[bb].numpy()) b[:] = True bb[:] = True np.testing.assert_equal(a[b], aa[bb].numpy()) np.testing.assert_equal(a[:, [True, False]], aa[:, [True, False]].numpy()) a = np.array([[True, False], [False, True]]) b = np.array([1]) aa = Tensor(a) bb = Tensor(b) np.testing.assert_equal(a[b], aa[bb].numpy()) b = np.array([[True, True], [False, True]]) bb = Tensor(b) np.testing.assert_equal(a[b], aa[bb].numpy()) a[b] = False aa[bb] = False np.testing.assert_equal(a, aa.numpy()) # XXX: trace does not expect empty condtake tensor if not use_symbolic_shape(): a = np.ones((2, 2), dtype=np.int32) b = np.array([[False, False], [False, False]]) aa = Tensor(a) bb = Tensor(b) np.testing.assert_equal(a[b], aa[b].numpy()) np.testing.assert_equal(a[b], aa[bb].numpy()) b = np.array([False, False]) bb = Tensor(b) np.testing.assert_equal(a[b], aa[bb].numpy().reshape(a[b].shape)) # FIXME a = np.arange(576).reshape(2, 3, 4, 3, 4, 2).astype("int32") aa = Tensor(a) b = (np.random.sample((2, 3, 4)) > 0.5).astype("bool") bb = Tensor(b) np.testing.assert_equal(a[b, :, 0:4:2], aa[bb, :, 0:4:2].numpy()) b = (np.random.sample((4, 3, 4)) > 0.5).astype("bool") bb = Tensor(b) np.testing.assert_equal(a[..., b, 0:2], aa[..., bb, 0:2].numpy()) b = (np.random.sample((3, 4, 3)) > 0.5).astype("bool") bb = Tensor(b) np.testing.assert_equal( a[:, b, 0:2, [True, False]], aa[:, bb, 0:2, [True, False]].numpy() )