Repository: dask/dask

Issue:
tensordot cannot handle >26 dimensions
Currently ``tensordot`` is limited to tensors with 26 or fewer dimensions. Reproducing example:

```python
>>> import dask.array as da

>>> x = da.random.random([2]*26, chunks=-1)
>>> da.tensordot(x, x, 26)  # works fine
dask.array<sum-aggregate, shape=(), dtype=float64, chunksize=()>

>>> x = da.random.random([2]*27, chunks=-1)
>>> da.tensordot(x, x, 27)  # errors
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/home/johnnie/conda/lib/python3.6/site-packages/dask/array/routines.py", line 235, in tensordot
    right_index[r] = left_index[l]
IndexError: list index out of range
```

The culprit is these lines in ``dask/array/routines.py::tensordot``
```python
    ...
    left_index = list(alphabet[:lhs.ndim])
    right_index = list(ALPHABET[:rhs.ndim])
    ...
```
Which silently slices too few characters, which are then supplied to ``atop``.

I know this sounds like quite an niche use-case! But it would actually be super useful to support this (e.g. in quantum physics where such arrays appear often) as this is the exact kind of size where memory limitations make dask's array chunking attractive/necessary.


Discussion:
jcmgray: cc @mrocklin. 

The [test failure](https://travis-ci.org/dask/dask/jobs/468464613) seems to be an unrelated envinronmental problem?

Patch:
diff --git a/dask/array/routines.py b/dask/array/routines.py
index efb24d563..269c7c9e9 100644
--- a/dask/array/routines.py
+++ b/dask/array/routines.py
@@ -226,8 +226,8 @@ def tensordot(lhs, rhs, axes=2):
 
     dt = np.promote_types(lhs.dtype, rhs.dtype)
 
-    left_index = list(alphabet[:lhs.ndim])
-    right_index = list(ALPHABET[:rhs.ndim])
+    left_index = list(range(lhs.ndim))
+    right_index = list(range(lhs.ndim, lhs.ndim + rhs.ndim))
     out_index = left_index + right_index
 
     for l, r in zip(left_axes, right_axes):
diff --git a/docs/source/optimize.rst b/docs/source/optimize.rst
index e443fddab..17a303a34 100644
--- a/docs/source/optimize.rst
+++ b/docs/source/optimize.rst
@@ -2,19 +2,19 @@ Optimization
 ============
 
 Performance can be significantly improved in different contexts by making
-small optimizations on the dask graph before calling the scheduler.
+small optimizations on the Dask graph before calling the scheduler.
 
 The ``dask.optimization`` module contains several functions to transform graphs
 in a variety of useful ways. In most cases, users won't need to interact with
 these functions directly, as specialized subsets of these transforms are done
-automatically in the dask collections (``dask.array``, ``dask.bag``, and
+automatically in the Dask collections (``dask.array``, ``dask.bag``, and
 ``dask.dataframe``). However, users working with custom graphs or computations
 may find that applying these methods results in substantial speedups.
 
 In general, there are two goals when doing graph optimizations:
 
 1. Simplify computation
-2. Improve parallelism.
+2. Improve parallelism
 
 Simplifying computation can be done on a graph level by removing unnecessary
 tasks (``cull``), or on a task level by replacing expensive operations with
@@ -30,7 +30,7 @@ a task graph.
 Example
 -------
 
-Suppose you had a custom dask graph for doing a word counting task:
+Suppose you had a custom Dask graph for doing a word counting task:
 
 .. code-block:: python
 
@@ -63,11 +63,11 @@ Suppose you had a custom dask graph for doing a word counting task:
    :width: 65 %
    :alt: The original dask graph
 
-Here we're counting the occurrence of the words ``'orange``, ``'apple'``, and
+Here we are counting the occurrence of the words ``'orange``, ``'apple'``, and
 ``'pear'`` in the list of words, formatting an output string reporting the
-results, printing the output, then returning the output string.
+results, printing the output, and then returning the output string.
 
-To perform the computation, we pass the dask graph and the desired output keys
+To perform the computation, we pass the Dask graph and the desired output keys
 to a scheduler ``get`` function:
 
 .. code-block:: python
@@ -100,7 +100,7 @@ later steps:
    :alt: After culling
 
 Looking at the task graph above, there are multiple accesses to constants such
-as ``'val1'`` or ``'val2'`` in the dask graph. These can be inlined into the
+as ``'val1'`` or ``'val2'`` in the Dask graph. These can be inlined into the
 tasks to improve efficiency using the ``inline`` function. For example:
 
 .. code-block:: python
@@ -172,13 +172,13 @@ Putting it all together:
 
 In summary, the above operations accomplish the following:
 
-1. Removed tasks unnecessary for the desired output using ``cull``.
-2. Inlined constants using ``inline``.
-3. Inlined cheap computations using ``inline_functions``, improving parallelism.
-4. Fused linear tasks together to ensure they run on the same worker using ``fuse``.
+1. Removed tasks unnecessary for the desired output using ``cull``
+2. Inlined constants using ``inline``
+3. Inlined cheap computations using ``inline_functions``, improving parallelism
+4. Fused linear tasks together to ensure they run on the same worker using ``fuse``
 
 As stated previously, these optimizations are already performed automatically
-in the dask collections. Users not working with custom graphs or computations
+in the Dask collections. Users not working with custom graphs or computations
 should rarely need to directly interact with them.
 
 These are just a few of the optimizations provided in ``dask.optimization``. For
@@ -190,7 +190,7 @@ Rewrite Rules
 
 For context based optimizations, ``dask.rewrite`` provides functionality for
 pattern matching and term rewriting. This is useful for replacing expensive
-computations with equivalent, cheaper computations. For example, ``dask.array``
+computations with equivalent, cheaper computations. For example, Dask Array
 uses the rewrite functionality to replace series of array slicing operations
 with a more efficient single slice.
 
@@ -272,7 +272,7 @@ Keyword Arguments
 
 Some optimizations take optional keyword arguments.  To pass keywords from the
 compute call down to the right optimization, prepend the keyword with the name
-of the optimization.  For example to send a ``keys=`` keyword argument to the
+of the optimization.  For example, to send a ``keys=`` keyword argument to the
 ``fuse`` optimization from a compute call, use the ``fuse_keys=`` keyword:
 
 .. code-block:: python
@@ -287,11 +287,11 @@ Customizing Optimization
 ------------------------
 
 Dask defines a default optimization strategy for each collection type (Array,
-Bag, DataFrame, Delayed).  However different applications may have different
+Bag, DataFrame, Delayed).  However, different applications may have different
 needs.  To address this variability of needs, you can construct your own custom
 optimization function and use it instead of the default.  An optimization
 function takes in a task graph and list of desired keys and returns a new
-task graph.
+task graph:
 
 .. code-block:: python
 
@@ -300,7 +300,7 @@ task graph.
        return new_dsk
 
 You can then register this optimization class against whichever collection type
-you prefer and it will be used instead of the default scheme.
+you prefer and it will be used instead of the default scheme:
 
 .. code-block:: python
 
@@ -309,7 +309,7 @@ you prefer and it will be used instead of the default scheme.
 
 You can register separate optimization functions for different collections, or
 you can register ``None`` if you do not want particular types of collections to
-be optimized.
+be optimized:
 
 .. code-block:: python
 
@@ -318,7 +318,7 @@ be optimized.
                         delayed_optimize=my_other_optimize_function):
        ...
 
-You need not specify all collections.  Collections will default to their
+You do not need to specify all collections.  Collections will default to their
 standard optimization scheme (which is usually a good choice).
 
 


Test patch:
diff --git a/dask/array/tests/test_routines.py b/dask/array/tests/test_routines.py
index 84fefe41c..65c0032ad 100644
--- a/dask/array/tests/test_routines.py
+++ b/dask/array/tests/test_routines.py
@@ -287,6 +287,13 @@ def test_tensordot_2(axes):
               np.tensordot(x, x, axes=axes))
 
 
+def test_tensordot_more_than_26_dims():
+    ndim = 27
+    x = np.broadcast_to(1, [2] * ndim)
+    dx = da.from_array(x, chunks=-1)
+    assert_eq(da.tensordot(dx, dx, ndim), np.array(2**ndim))
+
+
 def test_dot_method():
     x = np.arange(400).reshape((20, 20))
     a = da.from_array(x, chunks=(5, 5))