# Buggy code
def __init__(self, fun, x0, args, grad, hess, finite_diff_rel_step, finite_diff_bounds):
    if not callable(grad) and grad not in FD_METHODS:
        raise ValueError(
            "`grad` must be either callable or one of {}.".format(FD_METHODS)
        )

    if not (
        callable(hess) or hess in FD_METHODS or isinstance(hess, HessianUpdateStrategy)
    ):
        raise ValueError(
            "`hess` must be either callable,HessianUpdateStrategy or one of {}.".format(
                FD_METHODS
            )
        )

    if grad in FD_METHODS and hess in FD_METHODS:
        raise ValueError(
            "Whenever the gradient is estimated via "
            "finite-differences, we require the Hessian "
            "to be estimated using one of the "
            "quasi-Newton strategies."
        )

    self.x = np.atleast_1d(x0).astype(float)
    self.n = self.x.size
    self.nfev = 0
    self.ngev = 0
    self.nhev = 0
    self.f_updated = False
    self.g_updated = False
    self.H_updated = False

    finite_diff_options = {}
    if grad in FD_METHODS:
        finite_diff_options["method"] = grad
        finite_diff_options["rel_step"] = finite_diff_rel_step
        finite_diff_options["bounds"] = finite_diff_bounds
    if hess in FD_METHODS:
        finite_diff_options["method"] = hess
        finite_diff_options["rel_step"] = finite_diff_rel_step
        finite_diff_options["as_linear_operator"] = True

    # Function evaluation
    def fun_wrapped(x):
        self.nfev += 1
        return fun(x, *args)

    def update_fun():
        self.f = fun_wrapped(self.x)

    self._update_fun_impl = update_fun
    self._update_fun()

    # Gradient evaluation
    if callable(grad):

        def grad_wrapped(x):
            self.ngev += 1
            return np.atleast_1d(grad(x, *args))

        def update_grad():
            self.g = grad_wrapped(self.x)

    elif grad in FD_METHODS:

        def update_grad():
            self._update_fun()
            self.g = approx_derivative(
                fun_wrapped, self.x, f0=self.f, **finite_diff_options
            )

    self._update_grad_impl = update_grad
    self._update_grad()

    # Hessian Evaluation
    if callable(hess):
        self.H = hess(x0, *args)
        self.H_updated = True
        self.nhev += 1

        if sps.issparse(self.H):

            def hess_wrapped(x):
                self.nhev += 1
                return sps.csr_matrix(hess(x, *args))

            self.H = sps.csr_matrix(self.H)

        elif isinstance(self.H, LinearOperator):

            def hess_wrapped(x):
                self.nhev += 1
                return hess(x, *args)

        else:

            def hess_wrapped(x):
                self.nhev += 1
                return np.atleast_2d(np.asarray(hess(x, *args)))

            self.H = np.atleast_2d(np.asarray(self.H))

        def update_hess():
            self.H = hess_wrapped(self.x)

    elif hess in FD_METHODS:

        def update_hess():
            self._update_grad()
            self.H = approx_derivative(
                grad_wrapped, self.x, f0=self.g, **finite_diff_options
            )
            return self.H

        update_hess()
        self.H_updated = True
    elif isinstance(hess, HessianUpdateStrategy):
        self.H = hess
        self.H.initialize(self.n, "hess")
        self.H_updated = True
        self.x_prev = None
        self.g_prev = None

        def update_hess():
            self._update_grad()
            self.H.update(self.x - self.x_prev, self.g - self.g_prev)

    self._update_hess_impl = update_hess

    if isinstance(hess, HessianUpdateStrategy):

        def update_x(x):
            self._update_grad()
            self.x_prev = self.x
            self.g_prev = self.g

            self.x = np.atleast_1d(x).astype(float)
            self.f_updated = False
            self.g_updated = False
            self.H_updated = False
            self._update_hess()
    else:

        def update_x(x):
            self.x = np.atleast_1d(x).astype(float)
            self.f_updated = False
            self.g_updated = False
            self.H_updated = False

    self._update_x_impl = update_x


# Traceback (ValueError)
In [158]: minimize(func, [1.1, 1.2], method='Newton-CG', jac=False)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-158-94c7580e9508> in <module>()
----> 1 minimize(func, [1.1, 1.2], method='Newton-CG', jac=False)

~/miniconda3scipy/lib/python3.5/site-packages/scipy-1.2.0.dev0+6d455f3-py3.5-macosx-10.6-x86_64.egg/scipy/optimize/_minimize.py in minimize(fun, x0, args, method, jac, hess, hessp, bounds, constraints, tol, callback, options)
600     elif meth == 'newton-cg':
601         return _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback,
--> 602                                   **options)
603     elif meth == 'l-bfgs-b':
604         return _minimize_lbfgsb(fun, x0, args, jac, bounds,

~/miniconda3scipy/lib/python3.5/site-packages/scipy-1.2.0.dev0+6d455f3-py3.5-macosx-10.6-x86_64.egg/scipy/optimize/optimize.py in _minimize_newtoncg(fun, x0, args, jac, hess, hessp, callback, xtol, eps, maxiter, disp, return_all, **unknown_options)
1494     _check_unknown_options(unknown_options)
1495     if jac is None:
-> 1496         raise ValueError('Jacobian is required for Newton-CG method')
1497     f = fun
1498     fprime = jac

ValueError: Jacobian is required for Newton-CG method

# Fixed code
def __init__(
    self,
    fun,
    x0,
    args,
    grad,
    hess,
    finite_diff_rel_step,
    finite_diff_bounds,
    epsilon=None,
):
    if not callable(grad) and grad not in FD_METHODS:
        raise ValueError(
            "`grad` must be either callable or one of {}.".format(FD_METHODS)
        )

    if not (
        callable(hess) or hess in FD_METHODS or isinstance(hess, HessianUpdateStrategy)
    ):
        raise ValueError(
            "`hess` must be either callable,HessianUpdateStrategy or one of {}.".format(
                FD_METHODS
            )
        )

    if grad in FD_METHODS and hess in FD_METHODS:
        raise ValueError(
            "Whenever the gradient is estimated via "
            "finite-differences, we require the Hessian "
            "to be estimated using one of the "
            "quasi-Newton strategies."
        )

    self.x = np.atleast_1d(x0).astype(float)
    self.n = self.x.size
    self.nfev = 0
    self.ngev = 0
    self.nhev = 0
    self.f_updated = False
    self.g_updated = False
    self.H_updated = False

    finite_diff_options = {}
    if grad in FD_METHODS:
        finite_diff_options["method"] = grad
        finite_diff_options["rel_step"] = finite_diff_rel_step
        finite_diff_options["abs_step"] = epsilon
        finite_diff_options["bounds"] = finite_diff_bounds
    if hess in FD_METHODS:
        finite_diff_options["method"] = hess
        finite_diff_options["rel_step"] = finite_diff_rel_step
        finite_diff_options["abs_step"] = epsilon
        finite_diff_options["as_linear_operator"] = True

    # Function evaluation
    def fun_wrapped(x):
        self.nfev += 1
        return fun(x, *args)

    def update_fun():
        self.f = fun_wrapped(self.x)

    self._update_fun_impl = update_fun
    self._update_fun()

    # Gradient evaluation
    if callable(grad):

        def grad_wrapped(x):
            self.ngev += 1
            return np.atleast_1d(grad(x, *args))

        def update_grad():
            self.g = grad_wrapped(self.x)

    elif grad in FD_METHODS:

        def update_grad():
            self._update_fun()
            self.ngev += 1
            self.g = approx_derivative(
                fun_wrapped, self.x, f0=self.f, **finite_diff_options
            )

    self._update_grad_impl = update_grad
    self._update_grad()

    # Hessian Evaluation
    if callable(hess):
        self.H = hess(x0, *args)
        self.H_updated = True
        self.nhev += 1

        if sps.issparse(self.H):

            def hess_wrapped(x):
                self.nhev += 1
                return sps.csr_matrix(hess(x, *args))

            self.H = sps.csr_matrix(self.H)

        elif isinstance(self.H, LinearOperator):

            def hess_wrapped(x):
                self.nhev += 1
                return hess(x, *args)

        else:

            def hess_wrapped(x):
                self.nhev += 1
                return np.atleast_2d(np.asarray(hess(x, *args)))

            self.H = np.atleast_2d(np.asarray(self.H))

        def update_hess():
            self.H = hess_wrapped(self.x)

    elif hess in FD_METHODS:

        def update_hess():
            self._update_grad()
            self.H = approx_derivative(
                grad_wrapped, self.x, f0=self.g, **finite_diff_options
            )
            return self.H

        update_hess()
        self.H_updated = True
    elif isinstance(hess, HessianUpdateStrategy):
        self.H = hess
        self.H.initialize(self.n, "hess")
        self.H_updated = True
        self.x_prev = None
        self.g_prev = None

        def update_hess():
            self._update_grad()
            self.H.update(self.x - self.x_prev, self.g - self.g_prev)

    self._update_hess_impl = update_hess

    if isinstance(hess, HessianUpdateStrategy):

        def update_x(x):
            self._update_grad()
            self.x_prev = self.x
            self.g_prev = self.g

            self.x = np.atleast_1d(x).astype(float)
            self.f_updated = False
            self.g_updated = False
            self.H_updated = False
            self._update_hess()
    else:

        def update_x(x):
            self.x = np.atleast_1d(x).astype(float)
            self.f_updated = False
            self.g_updated = False
            self.H_updated = False

    self._update_x_impl = update_x