ENH: add `mdot`: fast dot with multiple arguments.

This is not ready to be merged!

`mdot` makes it easy to chain `dot`. Think of `mdot` as this:

    def mdot(*args): return reduce(np.dot, args)

However, `mdot` uses the fact that matrix multiplication is associative and
finds the optimal order of multiplications with dynamic programing before
doing the mutiplication. This can speed up the multiplication quite a bit.

The algorithm follows:
Cormen, "Introduction to Algorithms", Chapter 15.2, p. 370-378

Note: currently `mdot` only works with arrays which have `ndim == 2` to avoid
ambiguities of `dot`. Also see the discussion in #4311.

TODO:
- [x] Decide if `ndim != 2` should be ok
- [x] Add unittests
- [x] decide on name. possible names are:
  - `mdot`: think "multi dot"
  - `multidot`: as above :)
  - `multi_dot`: as above :)
  - `chain_dot`
  - `fdot`: think "fast dot" (should the parameter optimize be optional then? I don't think so.)
  - `ddot`: think "multiple d for multiple dot"
  - `cdot`: think "chain dot"
  - `odot`: think "optimal do

Let's just go for the first solution for now? We can always fill in the
edge cases later once we have more experience with the thing.
On 24 Oct 2014 13:50, "Stefan Otte" <notifications@github.com> wrote:

> In numpy/linalg/linalg.py:
>
> > +
> > +        def cost(A, B): return A.shape[0] * A.shape[1] * B.shape[1]
> > +
> > +    Let's assume we have three matrices
> > +    :math:`A_{10x100}, B_{100x5}, C_{5x50}$`.
> > +
> > +    The costs for the two different parenthesizations are as follows::
> > +
> > +        cost((AB)C) = 10*100*5 + 10*5*50   = 5000 + 2500   = 7500
> > +        cost(A(BC)) = 10*100*50 + 100*5*50 = 50000 + 25000 = 75000
> > +
> > +    """
> > +    n = len(arrays)
> > +    # optimization only makes sense for len(arrays) > 2
> > +    if n == 1:
> > +        return arrays[0]
>
> I had the case where a function returned a list of arrays which I then
> needed to multiply. I could use multi_dot if the function returned only
> one array. But I admit, that that is probably a rare case.
>
> There are two options:
>
>    - only accept two or more arguments and give an error otherwise, or
>    - accept 0 or more arguments and handle all cases: error if 0 arrays
>    are given, return copys otherwise.
>
> I think I like the first solution more.
>
> —
> Reply to this email directly or view it on GitHub
> <https://github.com/numpy/numpy/pull/4977/files#r19336549>.
>