Repository: scipy/scipy
File: scipy/optimize/_bracket.py (Python)

Code before:
        # (If we just found a bracket with the rightward search, we can stop
        #  the leftward search, and vice-versa.)
        # To do this, we need to set the status of the other side's search;
        # this is tricky because `work.status` contains only the *active*
        # elements, so we don't immediately know the index of the element we
        # need to set - or even if it's still there. (That search may have
        # terminated already, e.g. by reaching its `limit`.)
        # To facilitate this, `work.active` contains a unit integer index of
        # each search. Index `k` (`k < n)` and `k + n` correspond with a
        # leftward and rightward search, respectively. Elements are removed
        # from `work.active` just as they are removed from `work.status`, so
        # we use `work.active` to help find the right location in
        # `work.status`.
        # Get the integer indices of the elements that can also stop
        also_stop = (work.active[i] + work.n) % (2*work.n)
        # Check whether they are still active.
        # To start, we need to find out where in `work.active` they would
        # appear if they are indeed there.
        j = xp.searchsorted(work.active, also_stop)
        # If the location exceeds the length of the `work.active`, they are
        # not there.
        j = j[j < work.active.shape[0]]
        # Check whether they are still there.
        if j.size:
            # If j is empty, there's nothing to check.
            j = j[also_stop == work.active[j]]
        # Now convert these to boolean indices to use with `work.status`.
        i = xp.zeros_like(stop)
        i[j] = True  # boolean indices of elements that can also stop
        i = i & ~stop
        work.status[i] = _ESTOPONESIDE
        stop[i] = True

        # Condition 3: moving end of bracket reaches limit
        i = (work.x == work.limit) & ~stop
        work.status[i] = _ELIMITS
        stop[i] = True

        # Condition 4: non-finite value encountered
        i = ~(xp.isfinite(work.x) & xp.isfinite(work.f)) & ~stop
        work.status[i] = eim._EVALUEERR
        stop[i] = True

        return stop

    def post_termination_check(work):
        pass

    def customize_result(res, shape):
        n = res['x'].shape[0] // 2


Review comment (bug):
MRE of bug without this:
```python3
import numpy as np
from scipy.optimize._bracket import _bracket_root

def f(x, p):
    return np.exp(x) - p

p = np.asarray([0.29, 0.35])
res = _bracket_root(f, xl0=-1, xmin=-np.inf, xmax=0, args=(p,))
#   File "C:\Users\matth\Desktop\scipy\scipy\optimize\_bracket.py", line 294, in check_termination
#     j = j[also_stop == work.active[j]]
#           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
# ValueError: operands could not be broadcast together with shapes (2,) (0,) 
```

Code after:
        work.x = x
        work.f = f

    def check_termination(work):
        # Condition 0: initial bracket is invalid
        stop = (work.status == eim._EINPUTERR)

        # Condition 1: a valid bracket (or the root itself) has been found
        sf = xp.sign(work.f)
        sf_last = xp.sign(work.f_last)
        i = ((sf_last == -sf) | (sf_last == 0) | (sf == 0)) & ~stop
        work.status[i] = eim._ECONVERGED
        stop[i] = True

        # Condition 2: the other side's search found a valid bracket.
        # (If we just found a bracket with the rightward search, we can stop
        #  the leftward search, and vice-versa.)
        # To do this, we need to set the status of the other side's search;
        # this is tricky because `work.status` contains only the *active*
        # elements, so we don't immediately know the index of the element we
        # need to set - or even if it's still there. (That search may have
        # terminated already, e.g. by reaching its `limit`.)
        # To facilitate this, `work.active` contains a unit integer index of
        # each search. Index `k` (`k < n)` and `k + n` correspond with a
        # leftward and rightward search, respectively. Elements are removed
        # from `work.active` just as they are removed from `work.status`, so
        # we use `work.active` to help find the right location in
        # `work.status`.
        # Get the integer indices of the elements that can also stop
        also_stop = (work.active[i] + work.n) % (2*work.n)
        # Check whether they are still active. We want to find the indices
        # in work.active where the associated values in work.active are
        # contained in also_stop. xp.searchsorted let's us take advantage
        # of work.active being sorted, but requires some hackery because
        # searchsorted solves the separate but related problem of finding
        # the indices where the values in also_stop should be added to
        # maintain sorted order.
        j = xp.searchsorted(work.active, also_stop)
        # If the location exceeds the length of the `work.active`, they are
        # not there. This happens when a value in also_stop is larger than
        # the greatest value in work.active. This case needs special handling
        # because we cannot simply check that also_stop == work.active[j].
        mask = j < work.active.shape[0]
        # Note that we also have to use the mask to filter also_stop to ensure
        # that also_stop and j will still have the same shape.
        j, also_stop = j[mask], also_stop[mask]
        j = j[also_stop == work.active[j]]
        # Now convert these to boolean indices to use with `work.status`.
        i = xp.zeros_like(stop)
        i[j] = True  # boolean indices of elements that can also stop
        i = i & ~stop