# The Branching Path¶

While the `optimal`

path is guaranteed to find the smallest estimate FLOP
cost, it spends a lot of time exploring paths which are not likely to result in
an optimal path. For instance, outer products are usually not advantageous
unless absolutely necessary. Additionally, by trying a ‘good’ path first, it
should be possible to quickly establish a threshold FLOP cost which can then be
used to prune many bad paths.

The **branching** strategy (provided by `branch()`

) does
this by taking the recursive, depth-first approach of
`optimal()`

, whilst also sorting potential contractions
based on a heuristic cost, as in `greedy()`

.

There are two main flavours:

`optimize='branch-all'`

: exploreallinner products, starting with those that look best according to the cost heuristic.

`optimize='branch-2'`

: similar, but at each step only explore the estimated besttwopossible contractions, leading to a maximum of 2^N paths assessed.

In both cases, `branch()`

takes an active approach to
pruning paths well before they hit the best *total* FLOP count, by comparing
them to the FLOP count (times some factor) achieved by the best path at the
same point in the contraction.

There is also `'branch-1'`

, which, since it only explores a single path at
each step does not really ‘branch’ - this is essentially the approach of
`'greedy'`

.
In comparison, `'branch-1'`

will be slower for large expressions, but for
small to medium expressions it might find slightly higher quality contractions
due to considering individual flop costs at each step.

The default `optimize='auto'`

mode of `opt_einsum`

will use
`'branch-all'`

for 5 or 6 tensors, though it should be able to handle
12-13 tensors in a matter or seconds. Likewise, `'branch-2'`

will be used for
7 or 8 tensors, though it should be able to handle 20-22 tensors in a matter of
seconds. Finally, `'branch-1'`

will be used by `'auto'`

for expressions of
up to 14 tensors.

## Customizing the Branching Path¶

The ‘branch and bound’ path can be customized by creating a custom
`BranchBound`

instance. For example:

```
optimizer = oe.BranchBound(nbranch=3, minimize='size', cutoff_flops_factor=None)
path, path_info = oe.contract_path(eq, *arrays, optimize=optimizer)
```

You could then tweak the settings (e.g. `optimizer.nbranch = 4`

) and the best
bound found so far will persist and be used to prune paths on the next call:

```
optimizer.nbranch = 4
path, path_info = oe.contract_path(eq, *arrays, optimize=optimizer)
```