3RNN/Lib/site-packages/scipy/optimize/_root_scalar.py
2024-05-26 19:49:15 +02:00

526 lines
19 KiB
Python

"""
Unified interfaces to root finding algorithms for real or complex
scalar functions.
Functions
---------
- root : find a root of a scalar function.
"""
import numpy as np
from . import _zeros_py as optzeros
from ._numdiff import approx_derivative
__all__ = ['root_scalar']
ROOT_SCALAR_METHODS = ['bisect', 'brentq', 'brenth', 'ridder', 'toms748',
'newton', 'secant', 'halley']
class MemoizeDer:
"""Decorator that caches the value and derivative(s) of function each
time it is called.
This is a simplistic memoizer that calls and caches a single value
of `f(x, *args)`.
It assumes that `args` does not change between invocations.
It supports the use case of a root-finder where `args` is fixed,
`x` changes, and only rarely, if at all, does x assume the same value
more than once."""
def __init__(self, fun):
self.fun = fun
self.vals = None
self.x = None
self.n_calls = 0
def __call__(self, x, *args):
r"""Calculate f or use cached value if available"""
# Derivative may be requested before the function itself, always check
if self.vals is None or x != self.x:
fg = self.fun(x, *args)
self.x = x
self.n_calls += 1
self.vals = fg[:]
return self.vals[0]
def fprime(self, x, *args):
r"""Calculate f' or use a cached value if available"""
if self.vals is None or x != self.x:
self(x, *args)
return self.vals[1]
def fprime2(self, x, *args):
r"""Calculate f'' or use a cached value if available"""
if self.vals is None or x != self.x:
self(x, *args)
return self.vals[2]
def ncalls(self):
return self.n_calls
def root_scalar(f, args=(), method=None, bracket=None,
fprime=None, fprime2=None,
x0=None, x1=None,
xtol=None, rtol=None, maxiter=None,
options=None):
"""
Find a root of a scalar function.
Parameters
----------
f : callable
A function to find a root of.
args : tuple, optional
Extra arguments passed to the objective function and its derivative(s).
method : str, optional
Type of solver. Should be one of
- 'bisect' :ref:`(see here) <optimize.root_scalar-bisect>`
- 'brentq' :ref:`(see here) <optimize.root_scalar-brentq>`
- 'brenth' :ref:`(see here) <optimize.root_scalar-brenth>`
- 'ridder' :ref:`(see here) <optimize.root_scalar-ridder>`
- 'toms748' :ref:`(see here) <optimize.root_scalar-toms748>`
- 'newton' :ref:`(see here) <optimize.root_scalar-newton>`
- 'secant' :ref:`(see here) <optimize.root_scalar-secant>`
- 'halley' :ref:`(see here) <optimize.root_scalar-halley>`
bracket: A sequence of 2 floats, optional
An interval bracketing a root. `f(x, *args)` must have different
signs at the two endpoints.
x0 : float, optional
Initial guess.
x1 : float, optional
A second guess.
fprime : bool or callable, optional
If `fprime` is a boolean and is True, `f` is assumed to return the
value of the objective function and of the derivative.
`fprime` can also be a callable returning the derivative of `f`. In
this case, it must accept the same arguments as `f`.
fprime2 : bool or callable, optional
If `fprime2` is a boolean and is True, `f` is assumed to return the
value of the objective function and of the
first and second derivatives.
`fprime2` can also be a callable returning the second derivative of `f`.
In this case, it must accept the same arguments as `f`.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
options : dict, optional
A dictionary of solver options. E.g., ``k``, see
:obj:`show_options()` for details.
Returns
-------
sol : RootResults
The solution represented as a ``RootResults`` object.
Important attributes are: ``root`` the solution , ``converged`` a
boolean flag indicating if the algorithm exited successfully and
``flag`` which describes the cause of the termination. See
`RootResults` for a description of other attributes.
See also
--------
show_options : Additional options accepted by the solvers
root : Find a root of a vector function.
Notes
-----
This section describes the available solvers that can be selected by the
'method' parameter.
The default is to use the best method available for the situation
presented.
If a bracket is provided, it may use one of the bracketing methods.
If a derivative and an initial value are specified, it may
select one of the derivative-based methods.
If no method is judged applicable, it will raise an Exception.
Arguments for each method are as follows (x=required, o=optional).
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| method | f | args | bracket | x0 | x1 | fprime | fprime2 | xtol | rtol | maxiter | options |
+===============================================+===+======+=========+====+====+========+=========+======+======+=========+=========+
| :ref:`bisect <optimize.root_scalar-bisect>` | x | o | x | | | | | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| :ref:`brentq <optimize.root_scalar-brentq>` | x | o | x | | | | | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| :ref:`brenth <optimize.root_scalar-brenth>` | x | o | x | | | | | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| :ref:`ridder <optimize.root_scalar-ridder>` | x | o | x | | | | | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| :ref:`toms748 <optimize.root_scalar-toms748>` | x | o | x | | | | | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| :ref:`secant <optimize.root_scalar-secant>` | x | o | | x | o | | | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| :ref:`newton <optimize.root_scalar-newton>` | x | o | | x | | o | | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
| :ref:`halley <optimize.root_scalar-halley>` | x | o | | x | | x | x | o | o | o | o |
+-----------------------------------------------+---+------+---------+----+----+--------+---------+------+------+---------+---------+
Examples
--------
Find the root of a simple cubic
>>> from scipy import optimize
>>> def f(x):
... return (x**3 - 1) # only one real root at x = 1
>>> def fprime(x):
... return 3*x**2
The `brentq` method takes as input a bracket
>>> sol = optimize.root_scalar(f, bracket=[0, 3], method='brentq')
>>> sol.root, sol.iterations, sol.function_calls
(1.0, 10, 11)
The `newton` method takes as input a single point and uses the
derivative(s).
>>> sol = optimize.root_scalar(f, x0=0.2, fprime=fprime, method='newton')
>>> sol.root, sol.iterations, sol.function_calls
(1.0, 11, 22)
The function can provide the value and derivative(s) in a single call.
>>> def f_p_pp(x):
... return (x**3 - 1), 3*x**2, 6*x
>>> sol = optimize.root_scalar(
... f_p_pp, x0=0.2, fprime=True, method='newton'
... )
>>> sol.root, sol.iterations, sol.function_calls
(1.0, 11, 11)
>>> sol = optimize.root_scalar(
... f_p_pp, x0=0.2, fprime=True, fprime2=True, method='halley'
... )
>>> sol.root, sol.iterations, sol.function_calls
(1.0, 7, 8)
""" # noqa: E501
if not isinstance(args, tuple):
args = (args,)
if options is None:
options = {}
# fun also returns the derivative(s)
is_memoized = False
if fprime2 is not None and not callable(fprime2):
if bool(fprime2):
f = MemoizeDer(f)
is_memoized = True
fprime2 = f.fprime2
fprime = f.fprime
else:
fprime2 = None
if fprime is not None and not callable(fprime):
if bool(fprime):
f = MemoizeDer(f)
is_memoized = True
fprime = f.fprime
else:
fprime = None
# respect solver-specific default tolerances - only pass in if actually set
kwargs = {}
for k in ['xtol', 'rtol', 'maxiter']:
v = locals().get(k)
if v is not None:
kwargs[k] = v
# Set any solver-specific options
if options:
kwargs.update(options)
# Always request full_output from the underlying method as _root_scalar
# always returns a RootResults object
kwargs.update(full_output=True, disp=False)
# Pick a method if not specified.
# Use the "best" method available for the situation.
if not method:
if bracket:
method = 'brentq'
elif x0 is not None:
if fprime:
if fprime2:
method = 'halley'
else:
method = 'newton'
elif x1 is not None:
method = 'secant'
else:
method = 'newton'
if not method:
raise ValueError('Unable to select a solver as neither bracket '
'nor starting point provided.')
meth = method.lower()
map2underlying = {'halley': 'newton', 'secant': 'newton'}
try:
methodc = getattr(optzeros, map2underlying.get(meth, meth))
except AttributeError as e:
raise ValueError('Unknown solver %s' % meth) from e
if meth in ['bisect', 'ridder', 'brentq', 'brenth', 'toms748']:
if not isinstance(bracket, (list, tuple, np.ndarray)):
raise ValueError('Bracket needed for %s' % method)
a, b = bracket[:2]
try:
r, sol = methodc(f, a, b, args=args, **kwargs)
except ValueError as e:
# gh-17622 fixed some bugs in low-level solvers by raising an error
# (rather than returning incorrect results) when the callable
# returns a NaN. It did so by wrapping the callable rather than
# modifying compiled code, so the iteration count is not available.
if hasattr(e, "_x"):
sol = optzeros.RootResults(root=e._x,
iterations=np.nan,
function_calls=e._function_calls,
flag=str(e), method=method)
else:
raise
elif meth in ['secant']:
if x0 is None:
raise ValueError('x0 must not be None for %s' % method)
if 'xtol' in kwargs:
kwargs['tol'] = kwargs.pop('xtol')
r, sol = methodc(f, x0, args=args, fprime=None, fprime2=None,
x1=x1, **kwargs)
elif meth in ['newton']:
if x0 is None:
raise ValueError('x0 must not be None for %s' % method)
if not fprime:
# approximate fprime with finite differences
def fprime(x, *args):
# `root_scalar` doesn't actually seem to support vectorized
# use of `newton`. In that case, `approx_derivative` will
# always get scalar input. Nonetheless, it always returns an
# array, so we extract the element to produce scalar output.
return approx_derivative(f, x, method='2-point', args=args)[0]
if 'xtol' in kwargs:
kwargs['tol'] = kwargs.pop('xtol')
r, sol = methodc(f, x0, args=args, fprime=fprime, fprime2=None,
**kwargs)
elif meth in ['halley']:
if x0 is None:
raise ValueError('x0 must not be None for %s' % method)
if not fprime:
raise ValueError('fprime must be specified for %s' % method)
if not fprime2:
raise ValueError('fprime2 must be specified for %s' % method)
if 'xtol' in kwargs:
kwargs['tol'] = kwargs.pop('xtol')
r, sol = methodc(f, x0, args=args, fprime=fprime, fprime2=fprime2, **kwargs)
else:
raise ValueError('Unknown solver %s' % method)
if is_memoized:
# Replace the function_calls count with the memoized count.
# Avoids double and triple-counting.
n_calls = f.n_calls
sol.function_calls = n_calls
return sol
def _root_scalar_brentq_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function.
bracket: A sequence of 2 floats, optional
An interval bracketing a root. `f(x, *args)` must have different
signs at the two endpoints.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
options: dict, optional
Specifies any method-specific options not covered above
"""
pass
def _root_scalar_brenth_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function.
bracket: A sequence of 2 floats, optional
An interval bracketing a root. `f(x, *args)` must have different
signs at the two endpoints.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
options: dict, optional
Specifies any method-specific options not covered above.
"""
pass
def _root_scalar_toms748_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function.
bracket: A sequence of 2 floats, optional
An interval bracketing a root. `f(x, *args)` must have different
signs at the two endpoints.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
options: dict, optional
Specifies any method-specific options not covered above.
"""
pass
def _root_scalar_secant_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
x0 : float, required
Initial guess.
x1 : float, required
A second guess.
options: dict, optional
Specifies any method-specific options not covered above.
"""
pass
def _root_scalar_newton_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function and its derivative.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
x0 : float, required
Initial guess.
fprime : bool or callable, optional
If `fprime` is a boolean and is True, `f` is assumed to return the
value of derivative along with the objective function.
`fprime` can also be a callable returning the derivative of `f`. In
this case, it must accept the same arguments as `f`.
options: dict, optional
Specifies any method-specific options not covered above.
"""
pass
def _root_scalar_halley_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function and its derivatives.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
x0 : float, required
Initial guess.
fprime : bool or callable, required
If `fprime` is a boolean and is True, `f` is assumed to return the
value of derivative along with the objective function.
`fprime` can also be a callable returning the derivative of `f`. In
this case, it must accept the same arguments as `f`.
fprime2 : bool or callable, required
If `fprime2` is a boolean and is True, `f` is assumed to return the
value of 1st and 2nd derivatives along with the objective function.
`fprime2` can also be a callable returning the 2nd derivative of `f`.
In this case, it must accept the same arguments as `f`.
options: dict, optional
Specifies any method-specific options not covered above.
"""
pass
def _root_scalar_ridder_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function.
bracket: A sequence of 2 floats, optional
An interval bracketing a root. `f(x, *args)` must have different
signs at the two endpoints.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
options: dict, optional
Specifies any method-specific options not covered above.
"""
pass
def _root_scalar_bisect_doc():
r"""
Options
-------
args : tuple, optional
Extra arguments passed to the objective function.
bracket: A sequence of 2 floats, optional
An interval bracketing a root. `f(x, *args)` must have different
signs at the two endpoints.
xtol : float, optional
Tolerance (absolute) for termination.
rtol : float, optional
Tolerance (relative) for termination.
maxiter : int, optional
Maximum number of iterations.
options: dict, optional
Specifies any method-specific options not covered above.
"""
pass