Repository: mars-project/mars

Issue:
[BUG][TENSOR] TensorZeros generated in TensorDiag.tile have the same key even if they have different shapes
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**

`TensorDiag.tile` may generate chunks whose op is TensorZeros, they will have the same key even if their shape are different.

**To Reproduce**

```python
In [94]: a = mt.arange(5, chunk_size=2)                                         

In [95]: d = mt.diag(a)                                                         

In [96]: d.tiles()                                                              
Out[96]: <mars.tensor.core.Tensor at 0x136df1dc8>

In [99]: d.chunks[1].shape, d.chunks[1].op.key                                  
Out[99]: ((2, 2), 'd6d8d339b2cbac64ae65cb29ff3f6785')

In [100]: d.chunks[2].shape, d.chunks[1].op.key                                 
Out[100]: ((2, 1), 'd6d8d339b2cbac64ae65cb29ff3f6785')
```

**Expected behavior**

Chunks of TensorZeros should have different keys if their shapes are different, this is rightly handled for TensorZeros.tile, but when the TensorZeros op is created manually, this bug could happen.



Discussion:

Resolving patch:
diff --git a/mars/tensor/expressions/datasource/core.py b/mars/tensor/expressions/datasource/core.py
--- a/mars/tensor/expressions/datasource/core.py
+++ b/mars/tensor/expressions/datasource/core.py
@@ -72,6 +72,14 @@ def check_inputs(self, inputs):
     def calc_shape(self, *inputs_shape):
         return self.outputs[0].shape
 
+    def _new_chunks(self, inputs, shape, **kw):
+        self.params['shape'] = shape  # set shape to make the operand key different
+        return super(TensorNoInput, self)._new_chunks(inputs, shape, **kw)
+
+    def _new_entities(self, inputs, shape, **kw):
+        self.params['shape'] = shape  # set shape to make the operand key different
+        return super(TensorNoInput, self)._new_entities(inputs, shape, **kw)
+
     def __call__(self, shape, chunk_size=None):
         shape = normalize_shape(shape)
         return self.new_tensor(None, shape, raw_chunk_size=chunk_size)


Test patch:
diff --git a/mars/tensor/expressions/tests/test_core.py b/mars/tensor/expressions/tests/test_core.py
--- a/mars/tensor/expressions/tests/test_core.py
+++ b/mars/tensor/expressions/tests/test_core.py
@@ -20,13 +20,13 @@
 import numpy as np
 import scipy.sparse as sps
 
-from mars.tensor import ones, tensor, full, arange, diag, linspace, triu, tril, ones_like, dot
+from mars.tensor import ones, zeros, tensor, full, arange, diag, linspace, triu, tril, ones_like, dot
 from mars.tensor.expressions.datasource import fromdense
 from mars.tensor.expressions.datasource.tri import TensorTriu, TensorTril
 from mars.tensor.expressions.datasource.zeros import TensorZeros
 from mars.tensor.expressions.datasource.fromdense import DenseToSparse
 from mars.tensor.expressions.datasource.array import CSRMatrixDataSource
-from mars.tensor.expressions.datasource.ones import TensorOnesLike
+from mars.tensor.expressions.datasource.ones import TensorOnes, TensorOnesLike
 from mars.tensor.expressions.fuse.core import TensorFuseChunk
 from mars.tensor.core import Tensor, SparseTensor, TensorChunk, build_mode
 from mars.graph import DAG
@@ -214,6 +214,44 @@ def testOnes(self):
         tensor = ones((2, 3, 4))
         self.assertEqual(len(list(tensor)), 2)
 
+        tensor2 = ones((2, 3, 4), chunk_size=1)
+        # tensor's op key must be equal to tensor2
+        self.assertEqual(tensor.op.key, tensor2.op.key)
+        self.assertNotEqual(tensor.key, tensor2.key)
+
+        tensor3 = ones((2, 3, 3))
+        self.assertNotEqual(tensor.op.key, tensor3.op.key)
+        self.assertNotEqual(tensor.key, tensor3.key)
+
+        # test create chunk op of ones manually
+        chunk_op1 = TensorOnes(dtype=tensor.dtype)
+        chunk1 = chunk_op1.new_chunk(None, (3, 3), index=(0, 0))
+        chunk_op2 = TensorOnes(dtype=tensor.dtype)
+        chunk2 = chunk_op2.new_chunk(None, (3, 4), index=(0, 1))
+        self.assertNotEqual(chunk1.op.key, chunk2.op.key)
+        self.assertNotEqual(chunk1.key, chunk2.key)
+
+    def testZeros(self):
+        tensor = zeros((2, 3, 4))
+        self.assertEqual(len(list(tensor)), 2)
+
+        tensor2 = zeros((2, 3, 4), chunk_size=1)
+        # tensor's op key must be equal to tensor2
+        self.assertEqual(tensor.op.key, tensor2.op.key)
+        self.assertNotEqual(tensor.key, tensor2.key)
+
+        tensor3 = zeros((2, 3, 3))
+        self.assertNotEqual(tensor.op.key, tensor3.op.key)
+        self.assertNotEqual(tensor.key, tensor3.key)
+
+        # test create chunk op of zeros manually
+        chunk_op1 = TensorZeros(dtype=tensor.dtype)
+        chunk1 = chunk_op1.new_chunk(None, (3, 3), index=(0, 0))
+        chunk_op2 = TensorZeros(dtype=tensor.dtype)
+        chunk2 = chunk_op2.new_chunk(None, (3, 4), index=(0, 1))
+        self.assertNotEqual(chunk1.op.key, chunk2.op.key)
+        self.assertNotEqual(chunk1.key, chunk2.key)
+
     def testDataSource(self):
         from mars.tensor.expressions.base.broadcast_to import TensorBroadcastTo
 
@@ -448,6 +486,13 @@ def testDiag(self):
         self.assertEqual(tuple(sum(s) for s in t.nsplits), t.shape)
         self.assertEqual(calc_shape(t.chunks[0]), t.chunks[0].shape)
 
+        # test tiled zeros' keys
+        a = arange(5, chunk_size=2)
+        t = diag(a)
+        t.tiles()
+        # 1 and 2 of t.chunks is ones, they have different shapes
+        self.assertNotEqual(t.chunks[1].op.key, t.chunks[2].op.key)
+
     def testLinspace(self):
         a = linspace(2.0, 3.0, num=5, chunk_size=2)