Repository: mars-project/mars

Issue:
[BUG]Mismatch shape and results when negative axis
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

## Describe the bug
When i use tensor.linalg.norm, I found some mismatching between derived tensor shape and result from execution, when i assigned a negative axis. 

## To Reproduce
To help us reproducing this bug, please provide information below:
1. Your Python version
3.7.7
2. The version of Mars you use
0.4.2
3. Versions of crucial packages, such as numpy, scipy and protobuf
numpy: 1.18.5
scipy: 1.5.0
4. Full stack of the error.
5. Minimized code to reproduce the error.
```
In []: mt.linalg.norm(((1,1,1)), axis = -1).shape
Out[]: (3,)
In []: mt.linalg.norm(((1,1,1)), axis = -1).execute()
Out[]: 1.7320508075688772
In []: mt.linalg.norm(((1,1,1),(1,-1,1)), axis = -1).shape                         
Out[]: (2, 3)
In []: mt.linalg.norm(((1,1,1),(1,-1,1)), axis = -1).execute()                     
Out[]: array([1.73205081, 1.73205081])
```

## Expected behavior
the shape should be (1,) and (2,)

## Additional context
Add any other context about the problem here.



Discussion:
It seems that we forgot to validate axis for `mt.linalg.norm`.

Effected code is:

https://github.com/mars-project/mars/blob/50feb684c843fa21da6b7470e305e6acc6f6917c/mars/tensor/linalg/norm.py#L305-L309

There is already an util for the validation work:

https://github.com/mars-project/mars/blob/50feb684c843fa21da6b7470e305e6acc6f6917c/mars/tensor/utils.py#L103-L107

Are you interested to fix the bug? I really encourage you to do this, if so, we would be very grateful about that.

BTW, don't forget to add unittests here:

https://github.com/mars-project/mars/blob/50feb684c843fa21da6b7470e305e6acc6f6917c/mars/tensor/linalg/tests/test_linalg_execute.py#L790
I try to fix this bug but i cannot even run the script under the repository due to import errors. I am tired to fix the import errors. So i hpoe somebody can fix this.
> I try to fix this bug but i cannot even run the script under the repository due to import errors. I am tired to fix the import errors. So i hpoe somebody can fix this.

https://docs.pymars.org/en/latest/development/contributing.html#contributing-to-mars this section may help you, need to run `python setup.py build_ext -i` and `python setup.py build_proto` first.
To simplify the process, assume the path you cloned Mars is: `/to/path/mars`. Then 

```bash
cd /to/path/mars
```

Make sure the libraries required by development installed.

```bash
pip install -r requirements-dev.txt
```

Then build protobuf files and compile the code.

```bash
python setup.py build_proto
python setup.py build_ext -i
```

Now you should be able to import mars normally. Just enjoy it.

Resolving patch:
diff --git a/mars/tensor/linalg/norm.py b/mars/tensor/linalg/norm.py
--- a/mars/tensor/linalg/norm.py
+++ b/mars/tensor/linalg/norm.py
@@ -22,6 +22,7 @@
 from ... import opcodes as OperandDef
 from ...serialize import ValueType, KeyField, AnyField, TupleField, BoolField
 from ...utils import recursive_tile
+from ..utils import validate_axis
 from ..array_utils import device, as_same_device
 from ..operands import TensorHasInput, TensorOperandMixin
 from ..arithmetic import sqrt
@@ -299,14 +300,15 @@ def norm(x, ord=None, axis=None, keepdims=False):
 
     """
     x = astensor(x)
+    ndim = x.ndim
 
     if ord == 'fro':
         ord = None
     if axis is not None:
         if isinstance(axis, Iterable):
-            axis = tuple(axis)
+            axis = tuple(validate_axis(ndim, a) for a in axis)
         else:
-            axis = (axis,)
+            axis = (validate_axis(ndim, axis),)
     else:
         axis = tuple(range(x.ndim))
 


Test patch:
diff --git a/mars/tensor/linalg/tests/test_linalg_execute.py b/mars/tensor/linalg/tests/test_linalg_execute.py
--- a/mars/tensor/linalg/tests/test_linalg_execute.py
+++ b/mars/tensor/linalg/tests/test_linalg_execute.py
@@ -797,7 +797,7 @@ def testNormExecution(self):
         for i, a in enumerate(ma):
             data = d if i < 2 else d2
             for ord in (None, 'nuc', np.inf, -np.inf, 0, 1, -1, 2, -2):
-                for axis in (0, 1, (0, 1)):
+                for axis in (0, 1, (0, 1), -1):
                     for keepdims in (True, False):
                         try:
                             expected = np.linalg.norm(data, ord=ord, axis=axis, keepdims=keepdims)
@@ -805,6 +805,10 @@ def testNormExecution(self):
                             concat = t.ndim > 0
                             res = self.executor.execute_tensor(t, concat=concat)[0]
 
+                            expected_shape = expected.shape
+                            t_shape = t.shape
+                            self.assertEqual(expected_shape, t_shape)
+
                             np.testing.assert_allclose(res, expected, atol=.0001)
                         except ValueError:
                             continue