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:
- [ ] Decide if `ndim != 2` should be ok
- [ ] Add unittests
- [ ] Do we need more documentation?
The default Python behavior if a function receives a keyword argument it doesn't recognize is to raise an error:

```
def f(a):
    return a

>>> f(b=1, c=2)
...
TypeError: f() got an unexpected keyword argument 'b'
```

You should probably reproduce that behavior here, e.g. by `pop`ing `'optimize'` and checking if `**kwargs` is non-empty after.