# Buggy code
def nonlin_solve(
    F,
    x0,
    jacobian="krylov",
    iter=None,
    verbose=False,
    maxiter=None,
    f_tol=None,
    f_rtol=None,
    x_tol=None,
    x_rtol=None,
    tol_norm=None,
    line_search="armijo",
    callback=None,
    full_output=False,
    raise_exception=True,
):
    """
    Find a root of a function, in a way suitable for large-scale problems.

    Parameters
    ----------
    %(params_basic)s
    jacobian : Jacobian
        A Jacobian approximation: `Jacobian` object or something that
        `asjacobian` can transform to one. Alternatively, a string specifying
        which of the builtin Jacobian approximations to use:

            krylov, broyden1, broyden2, anderson
            diagbroyden, linearmixing, excitingmixing

    %(params_extra)s
    full_output : bool
        If true, returns a dictionary `info` containing convergence
        information.
    raise_exception : bool
        If True, a `NoConvergence` exception is raise if no solution is found.

    See Also
    --------
    asjacobian, Jacobian

    Notes
    -----
    This algorithm implements the inexact Newton method, with
    backtracking or full line searches. Several Jacobian
    approximations are available, including Krylov and Quasi-Newton
    methods.

    References
    ----------
    .. [KIM] C. T. Kelley, \"Iterative Methods for Linear and Nonlinear
       Equations\". Society for Industrial and Applied Mathematics. (1995)
       https://archive.siam.org/books/kelley/fr16/

    """

    condition = TerminationCondition(
        f_tol=f_tol, f_rtol=f_rtol, x_tol=x_tol, x_rtol=x_rtol, iter=iter, norm=tol_norm
    )

    x0 = _as_inexact(x0)
    func = lambda z: _as_inexact(F(_array_like(z, x0))).flatten()
    x = x0.flatten()

    dx = np.inf
    Fx = func(x)
    Fx_norm = norm(Fx)

    jacobian = asjacobian(jacobian)
    jacobian.setup(x.copy(), Fx, func)

    if maxiter is None:
        if iter is not None:
            maxiter = iter + 1
        else:
            maxiter = 100 * (x.size + 1)

    if line_search is True:
        line_search = "armijo"
    elif line_search is False:
        line_search = None

    if line_search not in (None, "armijo", "wolfe"):
        raise ValueError("Invalid line search")

    # Solver tolerance selection
    gamma = 0.9
    eta_max = 0.9999
    eta_treshold = 0.1
    eta = 1e-3

    for n in xrange(maxiter):
        status = condition.check(Fx, x, dx)
        if status:
            break

        # The tolerance, as computed for scipy.sparse.linalg.* routines
        tol = min(eta, eta * Fx_norm)
        dx = -jacobian.solve(Fx, tol=tol)

        if norm(dx) == 0:
            raise ValueError(
                "Jacobian inversion yielded zero vector. "
                "This indicates a bug in the Jacobian "
                "approximation."
            )

        # Line search, or Newton step
        if line_search:
            s, x, Fx, Fx_norm_new = _nonlin_line_search(func, x, Fx, dx, line_search)
        else:
            s = 1.0
            x = x + dx
            Fx = func(x)
            Fx_norm_new = norm(Fx)

        jacobian.update(x.copy(), Fx)

        if callback:
            callback(x, Fx)

        # Adjust forcing parameters for inexact methods
        eta_A = gamma * Fx_norm_new**2 / Fx_norm**2
        if gamma * eta**2 < eta_treshold:
            eta = min(eta_max, eta_A)
        else:
            eta = min(eta_max, max(eta_A, gamma * eta**2))

        Fx_norm = Fx_norm_new

        # Print status
        if verbose:
            sys.stdout.write(
                "%d:  |F(x)| = %g; step %g; tol %g\n" % (n, norm(Fx), s, eta)
            )
            sys.stdout.flush()
    else:
        if raise_exception:
            raise NoConvergence(_array_like(x, x0))
        else:
            status = 2

    if full_output:
        info = {
            "nit": condition.iteration,
            "fun": Fx,
            "status": status,
            "success": status == 1,
            "message": {
                1: "A solution was found at the specified tolerance.",
                2: "The maximum number of iterations allowed has been reached.",
            }[status],
        }
        return _array_like(x, x0), info
    else:
        return _array_like(x, x0)


# Traceback (ValueError)
0:  |F(x)| = 2.38882; step 1; tol 0.0790123
1:  |F(x)| = 0.707798; step 1; tol 0.0790123
2:  |F(x)| = 0.209718; step 1; tol 0.0790124
3:  |F(x)| = 0.0621386; step 1; tol 0.0790124
4:  |F(x)| = 0.0184114; step 1; tol 0.0790124
5:  |F(x)| = 0.00545524; step 1; tol 0.0790124
6:  |F(x)| = 0.00161637; step 1; tol 0.0790124
7:  |F(x)| = 0.000478924; step 1; tol 0.0790124
8:  |F(x)| = 0.000141904; step 1; tol 0.0790124
9:  |F(x)| = 4.20455e-05; step 1; tol 0.0790125
10:  |F(x)| = 1.24579e-05; step 1; tol 0.0790125
11:  |F(x)| = 3.69125e-06; step 1; tol 0.0790126
12:  |F(x)| = 1.09371e-06; step 1; tol 0.0790128
13:  |F(x)| = 3.24062e-07; step 1; tol 0.079013
14:  |F(x)| = 9.60191e-08; step 1; tol 0.0790133
15:  |F(x)| = 2.84503e-08; step 1; tol 0.0790137
16:  |F(x)| = 8.42984e-09; step 1; tol 0.0790144
17:  |F(x)| = 2.49778e-09; step 1; tol 0.0790155
18:  |F(x)| = 7.40105e-10; step 1; tol 0.079017
19:  |F(x)| = 2.193e-10; step 1; tol 0.0790193
20:  |F(x)| = 6.49821e-11; step 1; tol 0.0790228
21:  |F(x)| = 1.92559e-11; step 1; tol 0.0790281
22:  |F(x)| = 5.7063e-12; step 1; tol 0.079036
23:  |F(x)| = 1.69113e-12; step 1; tol 0.0790478
24:  |F(x)| = 5.01245e-13; step 1; tol 0.0790655
25:  |F(x)| = 1.48592e-13; step 1; tol 0.0790921
26:  |F(x)| = 4.40606e-14; step 1; tol 0.079132
27:  |F(x)| = 1.30698e-14; step 1; tol 0.0791918
28:  |F(x)| = 3.87913e-15; step 1; tol 0.0792815
29:  |F(x)| = 1.1523e-15; step 1; tol 0.0794159
30:  |F(x)| = 3.42728e-16; step 1; tol 0.0796175
31:  |F(x)| = 1.01743e-16; step 1; tol 0.0793151
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-14-eee14568de51> in <module>()
1 res = root(f, np.array((1.0, 1.0)), method='krylov',
----> 2            options={'disp': True, 'tol_norm': lambda x: 0.1})

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/optimize/_root.py in root(fun, x0, args, method, jac, tol, callback, options)
196         sol = _root_nonlin_solve(fun, x0, args=args, jac=jac,
197                                  _method=meth, _callback=callback,
--> 198                                  **options)
199     else:
200         raise ValueError('Unknown solver %s' % method)

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/optimize/_root.py in _root_nonlin_solve(func, x0, args, jac, _callback, _method, nit, disp, maxiter, ftol, fatol, xtol, xatol, tol_norm, line_search, jac_options, **unknown_options)
299                                   line_search=line_search,
300                                   callback=_callback, full_output=True,
--> 301                                   raise_exception=False)
302     sol = OptimizeResult(x=x)
303     sol.update(info)

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/optimize/nonlin.py in nonlin_solve(F, x0, jacobian, iter, verbose, maxiter, f_tol, f_rtol, x_tol, x_rtol, tol_norm, line_search, callback, full_output, raise_exception)
307         # The tolerance, as computed for scipy.sparse.linalg.* routines
308         tol = min(eta, eta*Fx_norm)
--> 309         dx = -jacobian.solve(Fx, tol=tol)
310
311         if norm(dx) == 0:

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/optimize/nonlin.py in solve(self, rhs, tol)
1455             sol, info = self.method(self.op, rhs, **self.method_kw)
1456         else:
-> 1457             sol, info = self.method(self.op, rhs, tol=tol, **self.method_kw)
1458         return sol
1459

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/sparse/linalg/isolve/lgmres.py in lgmres(A, b, x0, tol, maxiter, M, callback, inner_m, outer_k, outer_v, store_outer_Av)
201
202             if v_new is None:
--> 203                 v_new = psolve(matvec(z))
204             else:
205                 # Note: v_new is modified in-place below. Must make a

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/sparse/linalg/interface.py in matvec(self, x)
218             raise ValueError('dimension mismatch')
219
--> 220         y = self._matvec(x)
221
222         if isinstance(x, np.matrix):

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/sparse/linalg/interface.py in _matvec(self, x)
470
471     def _matvec(self, x):
--> 472         return self.__matvec_impl(x)
473
474     def _rmatvec(self, x):

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/optimize/nonlin.py in matvec(self, v)
1442
1443     def matvec(self, v):
-> 1444         nv = norm(v)
1445         if nv == 0:
1446             return 0*v

/home/tmabbot/anaconda3/lib/python3.5/site-packages/scipy/linalg/misc.py in norm(a, ord, axis, keepdims)
127     """
128     # Differs from numpy only in non-finite handling and the use of blas.
--> 129     a = np.asarray_chkfinite(a)
130
131     # Only use optimized norms if axis and keepdims are not specified.

/home/tmabbot/anaconda3/lib/python3.5/site-packages/numpy/lib/function_base.py in asarray_chkfinite(a, dtype, order)
1020     if a.dtype.char in typecodes['AllFloat'] and not np.isfinite(a).all():
1021         raise ValueError(
-> 1022             "array must not contain infs or NaNs")
1023     return a
1024

ValueError: array must not contain infs or NaNs

# Fixed code
def nonlin_solve(
    F,
    x0,
    jacobian="krylov",
    iter=None,
    verbose=False,
    maxiter=None,
    f_tol=None,
    f_rtol=None,
    x_tol=None,
    x_rtol=None,
    tol_norm=None,
    line_search="armijo",
    callback=None,
    full_output=False,
    raise_exception=True,
):
    """
    Find a root of a function, in a way suitable for large-scale problems.

    Parameters
    ----------
    %(params_basic)s
    jacobian : Jacobian
        A Jacobian approximation: `Jacobian` object or something that
        `asjacobian` can transform to one. Alternatively, a string specifying
        which of the builtin Jacobian approximations to use:

            krylov, broyden1, broyden2, anderson
            diagbroyden, linearmixing, excitingmixing

    %(params_extra)s
    full_output : bool
        If true, returns a dictionary `info` containing convergence
        information.
    raise_exception : bool
        If True, a `NoConvergence` exception is raise if no solution is found.

    See Also
    --------
    asjacobian, Jacobian

    Notes
    -----
    This algorithm implements the inexact Newton method, with
    backtracking or full line searches. Several Jacobian
    approximations are available, including Krylov and Quasi-Newton
    methods.

    References
    ----------
    .. [KIM] C. T. Kelley, \"Iterative Methods for Linear and Nonlinear
       Equations\". Society for Industrial and Applied Mathematics. (1995)
       https://archive.siam.org/books/kelley/fr16/

    """
    # Can't use default parameters because it's being explicitly passed as None
    # from the calling function, so we need to set it here.
    tol_norm = maxnorm if tol_norm is None else tol_norm
    condition = TerminationCondition(
        f_tol=f_tol, f_rtol=f_rtol, x_tol=x_tol, x_rtol=x_rtol, iter=iter, norm=tol_norm
    )

    x0 = _as_inexact(x0)
    func = lambda z: _as_inexact(F(_array_like(z, x0))).flatten()
    x = x0.flatten()

    dx = np.inf
    Fx = func(x)
    Fx_norm = norm(Fx)

    jacobian = asjacobian(jacobian)
    jacobian.setup(x.copy(), Fx, func)

    if maxiter is None:
        if iter is not None:
            maxiter = iter + 1
        else:
            maxiter = 100 * (x.size + 1)

    if line_search is True:
        line_search = "armijo"
    elif line_search is False:
        line_search = None

    if line_search not in (None, "armijo", "wolfe"):
        raise ValueError("Invalid line search")

    # Solver tolerance selection
    gamma = 0.9
    eta_max = 0.9999
    eta_treshold = 0.1
    eta = 1e-3

    for n in xrange(maxiter):
        status = condition.check(Fx, x, dx)
        if status:
            break

        # The tolerance, as computed for scipy.sparse.linalg.* routines
        tol = min(eta, eta * Fx_norm)
        dx = -jacobian.solve(Fx, tol=tol)

        if norm(dx) == 0:
            raise ValueError(
                "Jacobian inversion yielded zero vector. "
                "This indicates a bug in the Jacobian "
                "approximation."
            )

        # Line search, or Newton step
        if line_search:
            s, x, Fx, Fx_norm_new = _nonlin_line_search(func, x, Fx, dx, line_search)
        else:
            s = 1.0
            x = x + dx
            Fx = func(x)
            Fx_norm_new = norm(Fx)

        jacobian.update(x.copy(), Fx)

        if callback:
            callback(x, Fx)

        # Adjust forcing parameters for inexact methods
        eta_A = gamma * Fx_norm_new**2 / Fx_norm**2
        if gamma * eta**2 < eta_treshold:
            eta = min(eta_max, eta_A)
        else:
            eta = min(eta_max, max(eta_A, gamma * eta**2))

        Fx_norm = Fx_norm_new

        # Print status
        if verbose:
            sys.stdout.write("%d:  |F(x)| = %g; step %g\n" % (n, tol_norm(Fx), s))
            sys.stdout.flush()
    else:
        if raise_exception:
            raise NoConvergence(_array_like(x, x0))
        else:
            status = 2

    if full_output:
        info = {
            "nit": condition.iteration,
            "fun": Fx,
            "status": status,
            "success": status == 1,
            "message": {
                1: "A solution was found at the specified tolerance.",
                2: "The maximum number of iterations allowed has been reached.",
            }[status],
        }
        return _array_like(x, x0), info
    else:
        return _array_like(x, x0)