Repository: mars-project/mars

Issue:
[BUG][TENSOR] Execute mt.absolute with `where` failed
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**

`mt.absolute` on a tensor with `where` parameter cause failure when executed.

**To Reproduce**

```
In [1]: import numpy as np                                                          

In [2]: import mars.tensor as mt                                                    

In [3]: a = np.array([[0, -2, -1], [-3, 0, 0]])                                     

In [4]: mt.absolute(a, where=a > -2).execute()                                      
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-4-f56bac3cbb7e> in <module>
----> 1 mt.absolute(a, where=a > -2).execute()

~/Workspace/mars/mars/tensor/core.py in execute(self, session, **kw)
    362 
    363     def execute(self, session=None, **kw):
--> 364         return self._data.execute(session, **kw)
    365 
    366 

~/Workspace/mars/mars/tensor/core.py in execute(self, session, **kw)
    168         if session is None:
    169             session = Session.default_or_local()
--> 170         return session.run(self, **kw)
    171 
    172     def fetch(self, session=None, **kw):

~/Workspace/mars/mars/session.py in run(self, *tensors, **kw)
    107 
    108         tensors = tuple(mt.tensor(t) for t in tensors)
--> 109         result = self._sess.run(*tensors, **kw)
    110 
    111         for t in tensors:

~/Workspace/mars/mars/session.py in run(self, *tensors, **kw)
     49         if 'n_parallel' not in kw:
     50             kw['n_parallel'] = cpu_count()
---> 51         res = self._executor.execute_tensors(tensors, **kw)
     52         return res
     53 

~/Workspace/mars/mars/utils.py in _wrapped(*args, **kwargs)
    352     def _wrapped(*args, **kwargs):
    353         _kernel_mode.eager = False
--> 354         return_value = func(*args, **kwargs)
    355         _kernel_mode.eager = None
    356         return return_value

~/Workspace/mars/mars/executor.py in execute_tensors(self, tensors, fetch, n_parallel, n_thread, print_progress, mock, sparse_mock_percent)
    513         self.execute_graph(graph, result_keys, n_parallel=n_parallel or n_thread,
    514                            print_progress=print_progress, mock=mock,
--> 515                            sparse_mock_percent=sparse_mock_percent)
    516 
    517         results = self._chunk_result

~/Workspace/mars/mars/executor.py in execute_graph(self, graph, keys, n_parallel, print_progress, mock, sparse_mock_percent)
    447                                          print_progress=print_progress, mock=mock,
    448                                          sparse_mock_percent=sparse_mock_percent)
--> 449         res = graph_execution.execute(True)
    450         if mock:
    451             self._chunk_result.clear()

~/Workspace/mars/mars/executor.py in execute(self, retval)
    369         # wait until all the futures completed
    370         for future in executed_futures:
--> 371             future.result()
    372 
    373         if retval:

~/miniconda3/lib/python3.7/concurrent/futures/_base.py in result(self, timeout)
    423                 raise CancelledError()
    424             elif self._state == FINISHED:
--> 425                 return self.__get_result()
    426 
    427             self._condition.wait(timeout)

~/miniconda3/lib/python3.7/concurrent/futures/_base.py in __get_result(self)
    382     def __get_result(self):
    383         if self._exception:
--> 384             raise self._exception
    385         else:
    386             return self._result

~/miniconda3/lib/python3.7/concurrent/futures/thread.py in run(self)
     55 
     56         try:
---> 57             result = self.fn(*self.args, **self.kwargs)
     58         except BaseException as exc:
     59             self.future.set_exception(exc)

~/Workspace/mars/mars/executor.py in _execute_operand(self, op)
    260             # so we pass the first operand's first output to Executor.handle
    261             first_op = ops[0]
--> 262             Executor.handle(first_op.outputs[0], results, self._mock)
    263             executed_chunk_keys.update([c.key for c in first_op.outputs])
    264             op_keys.add(first_op.key)

~/Workspace/mars/mars/executor.py in handle(cls, chunk, results, mock)
    433     def handle(cls, chunk, results, mock=False):
    434         if not mock:
--> 435             return cls._get_op_runner(chunk, cls._op_runners)(results, chunk)
    436         else:
    437             return cls._get_op_runner(chunk, cls._op_size_estimators)(results, chunk)

~/Workspace/mars/mars/tensor/execution/arithmetic.py in _handle(ctx, chunk)
    169                                 dest_value = xp.empty(chunk.shape, chunk.dtype)
    170                                 kw['out'] = dest_value
--> 171                             ctx[chunk.key] = _handle_out_dtype(reduce(lambda a, b: func(a, b, **kw), inputs),
    172                                                                chunk.op.dtype)
    173                     except TypeError:

~/Workspace/mars/mars/tensor/execution/arithmetic.py in <lambda>(a, b)
    169                                 dest_value = xp.empty(chunk.shape, chunk.dtype)
    170                                 kw['out'] = dest_value
--> 171                             ctx[chunk.key] = _handle_out_dtype(reduce(lambda a, b: func(a, b, **kw), inputs),
    172                                                                chunk.op.dtype)
    173                     except TypeError:

ValueError: cannot specify 'out' as both a positional and keyword argument
```


Discussion:

Resolving patch:
diff --git a/mars/tensor/execution/arithmetic.py b/mars/tensor/execution/arithmetic.py
--- a/mars/tensor/execution/arithmetic.py
+++ b/mars/tensor/execution/arithmetic.py
@@ -149,6 +149,8 @@ def _handle(ctx, chunk):
                 inputs, kw['out'], kw['where'] = inputs[:-2], inputs[-2].copy(), inputs[-1]
             elif chunk.op.out:
                 inputs, kw['out'] = inputs[:-1], inputs[-1].copy()
+            elif chunk.op.where:
+                inputs, kw['where'] = inputs[:-1], inputs[-1]
 
             with np.errstate(**chunk.op.err):
                 if len(inputs) == 1:
diff --git a/mars/tensor/expressions/arithmetic/core.py b/mars/tensor/expressions/arithmetic/core.py
--- a/mars/tensor/expressions/arithmetic/core.py
+++ b/mars/tensor/expressions/arithmetic/core.py
@@ -159,7 +159,7 @@ def calc_shape(self, *inputs_shape):
 
     def _call(self, x1, x2, out=None, where=None):
         # if x1 or x2 is scalar, and out is none, to constant
-        if (np.isscalar(x1) or np.isscalar(x2)) and not out:
+        if (np.isscalar(x1) or np.isscalar(x2)) and not out and not where:
             return self.to_constant(x1, x2)
 
         x1, x2, out, where = self._process_inputs(x1, x2, out, where)


Test patch:
diff --git a/mars/tensor/execution/tests/test_arithmetic_execute.py b/mars/tensor/execution/tests/test_arithmetic_execute.py
--- a/mars/tensor/execution/tests/test_arithmetic_execute.py
+++ b/mars/tensor/execution/tests/test_arithmetic_execute.py
@@ -247,6 +247,21 @@ def testAddWithOutExecution(self):
         self.assertTrue(np.array_equal(
             res, np.true_divide(data1, data2, out=data1.copy(), where=data2 > .5)))
 
+        arr1 = tensor(data1.copy(), chunk_size=4)
+        arr2 = tensor(data2.copy(), chunk_size=4)
+
+        arr3 = add(arr1, arr2, where=arr1 > .5)
+        res = self.executor.execute_tensor(arr3, concat=True)[0]
+        expected = np.add(data1, data2, where=data1 > .5)
+        self.assertTrue(np.array_equal(res[data1 > .5], expected[data1 > .5]))
+
+        arr1 = tensor(data1.copy(), chunk_size=4)
+
+        arr3 = add(arr1, 1, where=arr1 > .5)
+        res = self.executor.execute_tensor(arr3, concat=True)[0]
+        expected = np.add(data1, 1, where=data1 > .5)
+        self.assertTrue(np.array_equal(res[data1 > .5], expected[data1 > .5]))
+
     def testFrexpExecution(self):
         data1 = np.random.random((5, 9, 4))