| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475 |
- import warnings
- from collections import namedtuple
- import operator
- from . import _zeros
- from ._optimize import OptimizeResult
- import numpy as np
- _iter = 100
- _xtol = 2e-12
- _rtol = 4 * np.finfo(float).eps
- __all__ = ['newton', 'bisect', 'ridder', 'brentq', 'brenth', 'toms748',
- 'RootResults']
- # Must agree with CONVERGED, SIGNERR, CONVERR, ... in zeros.h
- _ECONVERGED = 0
- _ESIGNERR = -1 # used in _chandrupatla
- _ECONVERR = -2
- _EVALUEERR = -3
- _ECALLBACK = -4
- _EINPROGRESS = 1
- CONVERGED = 'converged'
- SIGNERR = 'sign error'
- CONVERR = 'convergence error'
- VALUEERR = 'value error'
- INPROGRESS = 'No error'
- flag_map = {_ECONVERGED: CONVERGED, _ESIGNERR: SIGNERR, _ECONVERR: CONVERR,
- _EVALUEERR: VALUEERR, _EINPROGRESS: INPROGRESS}
- class RootResults(OptimizeResult):
- """Represents the root finding result.
- Attributes
- ----------
- root : float
- Estimated root location.
- iterations : int
- Number of iterations needed to find the root.
- function_calls : int
- Number of times the function was called.
- converged : bool
- True if the routine converged.
- flag : str
- Description of the cause of termination.
- method : str
- Root finding method used.
- """
- def __init__(self, root, iterations, function_calls, flag, method):
- self.root = root
- self.iterations = iterations
- self.function_calls = function_calls
- self.converged = flag == _ECONVERGED
- if flag in flag_map:
- self.flag = flag_map[flag]
- else:
- self.flag = flag
- self.method = method
- def results_c(full_output, r, method):
- if full_output:
- x, funcalls, iterations, flag = r
- results = RootResults(root=x,
- iterations=iterations,
- function_calls=funcalls,
- flag=flag, method=method)
- return x, results
- else:
- return r
- def _results_select(full_output, r, method):
- """Select from a tuple of (root, funccalls, iterations, flag)"""
- x, funcalls, iterations, flag = r
- if full_output:
- results = RootResults(root=x,
- iterations=iterations,
- function_calls=funcalls,
- flag=flag, method=method)
- return x, results
- return x
- def _wrap_nan_raise(f):
- def f_raise(x, *args):
- fx = f(x, *args)
- f_raise._function_calls += 1
- if np.isnan(fx):
- msg = (f'The function value at x={x} is NaN; '
- 'solver cannot continue.')
- err = ValueError(msg)
- err._x = x
- err._function_calls = f_raise._function_calls
- raise err
- return fx
- f_raise._function_calls = 0
- return f_raise
- def newton(func, x0, fprime=None, args=(), tol=1.48e-8, maxiter=50,
- fprime2=None, x1=None, rtol=0.0,
- full_output=False, disp=True):
- """
- Find a root of a real or complex function using the Newton-Raphson
- (or secant or Halley's) method.
- Find a root of the scalar-valued function `func` given a nearby scalar
- starting point `x0`.
- The Newton-Raphson method is used if the derivative `fprime` of `func`
- is provided, otherwise the secant method is used. If the second order
- derivative `fprime2` of `func` is also provided, then Halley's method is
- used.
- If `x0` is a sequence with more than one item, `newton` returns an array:
- the roots of the function from each (scalar) starting point in `x0`.
- In this case, `func` must be vectorized to return a sequence or array of
- the same shape as its first argument. If `fprime` (`fprime2`) is given,
- then its return must also have the same shape: each element is the first
- (second) derivative of `func` with respect to its only variable evaluated
- at each element of its first argument.
- `newton` is for finding roots of a scalar-valued functions of a single
- variable. For problems involving several variables, see `root`.
- Parameters
- ----------
- func : callable
- The function whose root is wanted. It must be a function of a
- single variable of the form ``f(x,a,b,c,...)``, where ``a,b,c,...``
- are extra arguments that can be passed in the `args` parameter.
- x0 : float, sequence, or ndarray
- An initial estimate of the root that should be somewhere near the
- actual root. If not scalar, then `func` must be vectorized and return
- a sequence or array of the same shape as its first argument.
- fprime : callable, optional
- The derivative of the function when available and convenient. If it
- is None (default), then the secant method is used.
- args : tuple, optional
- Extra arguments to be used in the function call.
- tol : float, optional
- The allowable error of the root's value. If `func` is complex-valued,
- a larger `tol` is recommended as both the real and imaginary parts
- of `x` contribute to ``|x - x0|``.
- maxiter : int, optional
- Maximum number of iterations.
- fprime2 : callable, optional
- The second order derivative of the function when available and
- convenient. If it is None (default), then the normal Newton-Raphson
- or the secant method is used. If it is not None, then Halley's method
- is used.
- x1 : float, optional
- Another estimate of the root that should be somewhere near the
- actual root. Used if `fprime` is not provided.
- rtol : float, optional
- Tolerance (relative) for termination.
- full_output : bool, optional
- If `full_output` is False (default), the root is returned.
- If True and `x0` is scalar, the return value is ``(x, r)``, where ``x``
- is the root and ``r`` is a `RootResults` object.
- If True and `x0` is non-scalar, the return value is ``(x, converged,
- zero_der)`` (see Returns section for details).
- disp : bool, optional
- If True, raise a RuntimeError if the algorithm didn't converge, with
- the error message containing the number of iterations and current
- function value. Otherwise, the convergence status is recorded in a
- `RootResults` return object.
- Ignored if `x0` is not scalar.
- *Note: this has little to do with displaying, however,
- the `disp` keyword cannot be renamed for backwards compatibility.*
- Returns
- -------
- root : float, sequence, or ndarray
- Estimated location where function is zero.
- r : `RootResults`, optional
- Present if ``full_output=True`` and `x0` is scalar.
- Object containing information about the convergence. In particular,
- ``r.converged`` is True if the routine converged.
- converged : ndarray of bool, optional
- Present if ``full_output=True`` and `x0` is non-scalar.
- For vector functions, indicates which elements converged successfully.
- zero_der : ndarray of bool, optional
- Present if ``full_output=True`` and `x0` is non-scalar.
- For vector functions, indicates which elements had a zero derivative.
- See Also
- --------
- root_scalar : interface to root solvers for scalar functions
- root : interface to root solvers for multi-input, multi-output functions
- Notes
- -----
- The convergence rate of the Newton-Raphson method is quadratic,
- the Halley method is cubic, and the secant method is
- sub-quadratic. This means that if the function is well-behaved
- the actual error in the estimated root after the nth iteration
- is approximately the square (cube for Halley) of the error
- after the (n-1)th step. However, the stopping criterion used
- here is the step size and there is no guarantee that a root
- has been found. Consequently, the result should be verified.
- Safer algorithms are brentq, brenth, ridder, and bisect,
- but they all require that the root first be bracketed in an
- interval where the function changes sign. The brentq algorithm
- is recommended for general use in one dimensional problems
- when such an interval has been found.
- When `newton` is used with arrays, it is best suited for the following
- types of problems:
- * The initial guesses, `x0`, are all relatively the same distance from
- the roots.
- * Some or all of the extra arguments, `args`, are also arrays so that a
- class of similar problems can be solved together.
- * The size of the initial guesses, `x0`, is larger than O(100) elements.
- Otherwise, a naive loop may perform as well or better than a vector.
- Examples
- --------
- >>> import numpy as np
- >>> import matplotlib.pyplot as plt
- >>> from scipy import optimize
- >>> def f(x):
- ... return (x**3 - 1) # only one real root at x = 1
- ``fprime`` is not provided, use the secant method:
- >>> root = optimize.newton(f, 1.5)
- >>> root
- 1.0000000000000016
- >>> root = optimize.newton(f, 1.5, fprime2=lambda x: 6 * x)
- >>> root
- 1.0000000000000016
- Only ``fprime`` is provided, use the Newton-Raphson method:
- >>> root = optimize.newton(f, 1.5, fprime=lambda x: 3 * x**2)
- >>> root
- 1.0
- Both ``fprime2`` and ``fprime`` are provided, use Halley's method:
- >>> root = optimize.newton(f, 1.5, fprime=lambda x: 3 * x**2,
- ... fprime2=lambda x: 6 * x)
- >>> root
- 1.0
- When we want to find roots for a set of related starting values and/or
- function parameters, we can provide both of those as an array of inputs:
- >>> f = lambda x, a: x**3 - a
- >>> fder = lambda x, a: 3 * x**2
- >>> rng = np.random.default_rng()
- >>> x = rng.standard_normal(100)
- >>> a = np.arange(-50, 50)
- >>> vec_res = optimize.newton(f, x, fprime=fder, args=(a, ), maxiter=200)
- The above is the equivalent of solving for each value in ``(x, a)``
- separately in a for-loop, just faster:
- >>> loop_res = [optimize.newton(f, x0, fprime=fder, args=(a0,),
- ... maxiter=200)
- ... for x0, a0 in zip(x, a)]
- >>> np.allclose(vec_res, loop_res)
- True
- Plot the results found for all values of ``a``:
- >>> analytical_result = np.sign(a) * np.abs(a)**(1/3)
- >>> fig, ax = plt.subplots()
- >>> ax.plot(a, analytical_result, 'o')
- >>> ax.plot(a, vec_res, '.')
- >>> ax.set_xlabel('$a$')
- >>> ax.set_ylabel('$x$ where $f(x, a)=0$')
- >>> plt.show()
- """
- if tol <= 0:
- raise ValueError(f"tol too small ({tol:g} <= 0)")
- maxiter = operator.index(maxiter)
- if maxiter < 1:
- raise ValueError("maxiter must be greater than 0")
- if np.size(x0) > 1:
- return _array_newton(func, x0, fprime, args, tol, maxiter, fprime2,
- full_output)
- # Convert to float (don't use float(x0); this works also for complex x0)
- # Use np.asarray because we want x0 to be a numpy object, not a Python
- # object. e.g. np.complex(1+1j) > 0 is possible, but (1 + 1j) > 0 raises
- # a TypeError
- x0 = np.asarray(x0)[()] * 1.0
- p0 = x0
- funcalls = 0
- if fprime is not None:
- # Newton-Raphson method
- method = "newton"
- for itr in range(maxiter):
- # first evaluate fval
- fval = func(p0, *args)
- funcalls += 1
- # If fval is 0, a root has been found, then terminate
- if fval == 0:
- return _results_select(
- full_output, (p0, funcalls, itr, _ECONVERGED), method)
- fder = fprime(p0, *args)
- funcalls += 1
- if fder == 0:
- msg = "Derivative was zero."
- if disp:
- msg += (
- f" Failed to converge after {itr + 1} iterations,"
- f" value is {p0}."
- )
- raise RuntimeError(msg)
- warnings.warn(msg, RuntimeWarning, stacklevel=2)
- return _results_select(
- full_output, (p0, funcalls, itr + 1, _ECONVERR), method)
- newton_step = fval / fder
- if fprime2:
- fder2 = fprime2(p0, *args)
- funcalls += 1
- method = "halley"
- # Halley's method:
- # newton_step /= (1.0 - 0.5 * newton_step * fder2 / fder)
- # Only do it if denominator stays close enough to 1
- # Rationale: If 1-adj < 0, then Halley sends x in the
- # opposite direction to Newton. Doesn't happen if x is close
- # enough to root.
- adj = newton_step * fder2 / fder / 2
- if np.abs(adj) < 1:
- newton_step /= 1.0 - adj
- p = p0 - newton_step
- if np.isclose(p, p0, rtol=rtol, atol=tol):
- return _results_select(
- full_output, (p, funcalls, itr + 1, _ECONVERGED), method)
- p0 = p
- else:
- # Secant method
- method = "secant"
- if x1 is not None:
- if x1 == x0:
- raise ValueError("x1 and x0 must be different")
- p1 = x1
- else:
- eps = 1e-4
- p1 = x0 * (1 + eps)
- p1 += (eps if p1 >= 0 else -eps)
- q0 = func(p0, *args)
- funcalls += 1
- q1 = func(p1, *args)
- funcalls += 1
- if abs(q1) < abs(q0):
- p0, p1, q0, q1 = p1, p0, q1, q0
- for itr in range(maxiter):
- if q1 == q0:
- if p1 != p0:
- msg = f"Tolerance of {p1 - p0} reached."
- if disp:
- msg += (
- f" Failed to converge after {itr + 1} iterations,"
- f" value is {p1}."
- )
- raise RuntimeError(msg)
- warnings.warn(msg, RuntimeWarning, stacklevel=2)
- p = (p1 + p0) / 2.0
- return _results_select(
- full_output, (p, funcalls, itr + 1, _ECONVERR), method)
- else:
- if abs(q1) > abs(q0):
- p = (-q0 / q1 * p1 + p0) / (1 - q0 / q1)
- else:
- p = (-q1 / q0 * p0 + p1) / (1 - q1 / q0)
- if np.isclose(p, p1, rtol=rtol, atol=tol):
- return _results_select(
- full_output, (p, funcalls, itr + 1, _ECONVERGED), method)
- p0, q0 = p1, q1
- p1 = p
- q1 = func(p1, *args)
- funcalls += 1
- if disp:
- msg = f"Failed to converge after {itr + 1} iterations, value is {p}."
- raise RuntimeError(msg)
- return _results_select(full_output, (p, funcalls, itr + 1, _ECONVERR), method)
- def _array_newton(func, x0, fprime, args, tol, maxiter, fprime2, full_output):
- """
- A vectorized version of Newton, Halley, and secant methods for arrays.
- Do not use this method directly. This method is called from `newton`
- when ``np.size(x0) > 1`` is ``True``. For docstring, see `newton`.
- """
- # Explicitly copy `x0` as `p` will be modified inplace, but the
- # user's array should not be altered.
- p = np.array(x0, copy=True)
- failures = np.ones_like(p, dtype=bool)
- nz_der = np.ones_like(failures)
- if fprime is not None:
- # Newton-Raphson method
- for iteration in range(maxiter):
- # first evaluate fval
- fval = np.asarray(func(p, *args))
- # If all fval are 0, all roots have been found, then terminate
- if not fval.any():
- failures = fval.astype(bool)
- break
- fder = np.asarray(fprime(p, *args))
- nz_der = (fder != 0)
- # stop iterating if all derivatives are zero
- if not nz_der.any():
- break
- # Newton step
- dp = fval[nz_der] / fder[nz_der]
- if fprime2 is not None:
- fder2 = np.asarray(fprime2(p, *args))
- dp = dp / (1.0 - 0.5 * dp * fder2[nz_der] / fder[nz_der])
- # only update nonzero derivatives
- p = np.asarray(p, dtype=np.result_type(p, dp, np.float64))
- p[nz_der] -= dp
- failures[nz_der] = np.abs(dp) >= tol # items not yet converged
- # stop iterating if there aren't any failures, not incl zero der
- if not failures[nz_der].any():
- break
- else:
- # Secant method
- dx = np.finfo(float).eps**0.33
- p1 = p * (1 + dx) + np.where(p >= 0, dx, -dx)
- q0 = np.asarray(func(p, *args))
- q1 = np.asarray(func(p1, *args))
- active = np.ones_like(p, dtype=bool)
- for iteration in range(maxiter):
- nz_der = (q1 != q0)
- # stop iterating if all derivatives are zero
- if not nz_der.any():
- p = (p1 + p) / 2.0
- break
- # Secant Step
- dp = (q1 * (p1 - p))[nz_der] / (q1 - q0)[nz_der]
- # only update nonzero derivatives
- p = np.asarray(p, dtype=np.result_type(p, p1, dp, np.float64))
- p[nz_der] = p1[nz_der] - dp
- active_zero_der = ~nz_der & active
- p[active_zero_der] = (p1 + p)[active_zero_der] / 2.0
- active &= nz_der # don't assign zero derivatives again
- failures[nz_der] = np.abs(dp) >= tol # not yet converged
- # stop iterating if there aren't any failures, not incl zero der
- if not failures[nz_der].any():
- break
- p1, p = p, p1
- q0 = q1
- q1 = np.asarray(func(p1, *args))
- zero_der = ~nz_der & failures # don't include converged with zero-ders
- if zero_der.any():
- # Secant warnings
- if fprime is None:
- nonzero_dp = (p1 != p)
- # non-zero dp, but infinite newton step
- zero_der_nz_dp = (zero_der & nonzero_dp)
- if zero_der_nz_dp.any():
- rms = np.sqrt(
- sum((p1[zero_der_nz_dp] - p[zero_der_nz_dp]) ** 2)
- )
- warnings.warn(f'RMS of {rms:g} reached', RuntimeWarning, stacklevel=3)
- # Newton or Halley warnings
- else:
- all_or_some = 'all' if zero_der.all() else 'some'
- msg = f'{all_or_some:s} derivatives were zero'
- warnings.warn(msg, RuntimeWarning, stacklevel=3)
- elif failures.any():
- all_or_some = 'all' if failures.all() else 'some'
- msg = f'{all_or_some:s} failed to converge after {maxiter:d} iterations'
- if failures.all():
- raise RuntimeError(msg)
- warnings.warn(msg, RuntimeWarning, stacklevel=3)
- if full_output:
- result = namedtuple('result', ('root', 'converged', 'zero_der'))
- p = result(p, ~failures, zero_der)
- return p
- def bisect(f, a, b, args=(),
- xtol=_xtol, rtol=_rtol, maxiter=_iter,
- full_output=False, disp=True):
- """
- Find root of a function within an interval using bisection.
- Basic bisection routine to find a root of the function `f` between the
- arguments `a` and `b`. `f(a)` and `f(b)` cannot have the same signs.
- Slow but sure.
- Parameters
- ----------
- f : function
- Python function returning a number. `f` must be continuous, and
- f(a) and f(b) must have opposite signs.
- a : scalar
- One end of the bracketing interval [a,b].
- b : scalar
- The other end of the bracketing interval [a,b].
- xtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter must be positive.
- rtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter cannot be smaller than its default value of
- ``4*np.finfo(float).eps``.
- maxiter : int, optional
- If convergence is not achieved in `maxiter` iterations, an error is
- raised. Must be >= 0.
- args : tuple, optional
- Containing extra arguments for the function `f`.
- `f` is called by ``apply(f, (x)+args)``.
- full_output : bool, optional
- If `full_output` is False, the root is returned. If `full_output` is
- True, the return value is ``(x, r)``, where x is the root, and r is
- a `RootResults` object.
- disp : bool, optional
- If True, raise RuntimeError if the algorithm didn't converge.
- Otherwise, the convergence status is recorded in a `RootResults`
- return object.
- Returns
- -------
- root : float
- Root of `f` between `a` and `b`.
- r : `RootResults` (present if ``full_output = True``)
- Object containing information about the convergence. In particular,
- ``r.converged`` is True if the routine converged.
- Notes
- -----
- As mentioned in the parameter documentation, the computed root ``x0`` will
- satisfy ``np.isclose(x, x0, atol=xtol, rtol=rtol)``, where ``x`` is the
- exact root. In equation form, this terminating condition is ``abs(x - x0)
- <= xtol + rtol * abs(x0)``.
- The default value ``xtol=2e-12`` may lead to surprising behavior if one
- expects `bisect` to always compute roots with relative error near machine
- precision. Care should be taken to select `xtol` for the use case at hand.
- Setting ``xtol=5e-324``, the smallest subnormal number, will ensure the
- highest level of accuracy. Larger values of `xtol` may be useful for saving
- function evaluations when a root is at or near zero in applications where
- the tiny absolute differences available between floating point numbers near
- zero are not meaningful.
- Examples
- --------
- >>> def f(x):
- ... return (x**2 - 1)
- >>> from scipy import optimize
- >>> root = optimize.bisect(f, 0, 2)
- >>> root
- 1.0
- >>> root = optimize.bisect(f, -2, 0)
- >>> root
- -1.0
- See Also
- --------
- brentq, brenth, bisect, newton
- fixed_point : scalar fixed-point finder
- fsolve : n-dimensional root-finding
- elementwise.find_root : efficient elementwise 1-D root-finder
- """
- if not isinstance(args, tuple):
- args = (args,)
- maxiter = operator.index(maxiter)
- if xtol <= 0:
- raise ValueError(f"xtol too small ({xtol:g} <= 0)")
- if rtol < _rtol:
- raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})")
- f = _wrap_nan_raise(f)
- r = _zeros._bisect(f, a, b, xtol, rtol, maxiter, args, full_output, disp)
- return results_c(full_output, r, "bisect")
- def ridder(f, a, b, args=(),
- xtol=_xtol, rtol=_rtol, maxiter=_iter,
- full_output=False, disp=True):
- """
- Find a root of a function in an interval using Ridder's method.
- Parameters
- ----------
- f : function
- Python function returning a number. f must be continuous, and f(a) and
- f(b) must have opposite signs.
- a : scalar
- One end of the bracketing interval [a,b].
- b : scalar
- The other end of the bracketing interval [a,b].
- xtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter must be positive.
- rtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter cannot be smaller than its default value of
- ``4*np.finfo(float).eps``.
- maxiter : int, optional
- If convergence is not achieved in `maxiter` iterations, an error is
- raised. Must be >= 0.
- args : tuple, optional
- Containing extra arguments for the function `f`.
- `f` is called by ``apply(f, (x)+args)``.
- full_output : bool, optional
- If `full_output` is False, the root is returned. If `full_output` is
- True, the return value is ``(x, r)``, where `x` is the root, and `r` is
- a `RootResults` object.
- disp : bool, optional
- If True, raise RuntimeError if the algorithm didn't converge.
- Otherwise, the convergence status is recorded in any `RootResults`
- return object.
- Returns
- -------
- root : float
- Root of `f` between `a` and `b`.
- r : `RootResults` (present if ``full_output = True``)
- Object containing information about the convergence.
- In particular, ``r.converged`` is True if the routine converged.
- See Also
- --------
- brentq, brenth, bisect, newton : 1-D root-finding
- fixed_point : scalar fixed-point finder
- elementwise.find_root : efficient elementwise 1-D root-finder
- Notes
- -----
- Uses [Ridders1979]_ method to find a root of the function `f` between the
- arguments `a` and `b`. Ridders' method is faster than bisection, but not
- generally as fast as the Brent routines. [Ridders1979]_ provides the
- classic description and source of the algorithm. A description can also be
- found in any recent edition of Numerical Recipes.
- The routine used here diverges slightly from standard presentations in
- order to be a bit more careful of tolerance.
- As mentioned in the parameter documentation, the computed root ``x0`` will
- satisfy ``np.isclose(x, x0, atol=xtol, rtol=rtol)``, where ``x`` is the
- exact root. In equation form, this terminating condition is ``abs(x - x0)
- <= xtol + rtol * abs(x0)``.
- The default value ``xtol=2e-12`` may lead to surprising behavior if one
- expects `ridder` to always compute roots with relative error near machine
- precision. Care should be taken to select `xtol` for the use case at hand.
- Setting ``xtol=5e-324``, the smallest subnormal number, will ensure the
- highest level of accuracy. Larger values of `xtol` may be useful for saving
- function evaluations when a root is at or near zero in applications where
- the tiny absolute differences available between floating point numbers near
- zero are not meaningful.
- References
- ----------
- .. [Ridders1979]
- Ridders, C. F. J. "A New Algorithm for Computing a
- Single Root of a Real Continuous Function."
- IEEE Trans. Circuits Systems 26, 979-980, 1979.
- Examples
- --------
- >>> def f(x):
- ... return (x**2 - 1)
- >>> from scipy import optimize
- >>> root = optimize.ridder(f, 0, 2)
- >>> root
- 1.0
- >>> root = optimize.ridder(f, -2, 0)
- >>> root
- -1.0
- """
- if not isinstance(args, tuple):
- args = (args,)
- maxiter = operator.index(maxiter)
- if xtol <= 0:
- raise ValueError(f"xtol too small ({xtol:g} <= 0)")
- if rtol < _rtol:
- raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})")
- f = _wrap_nan_raise(f)
- r = _zeros._ridder(f, a, b, xtol, rtol, maxiter, args, full_output, disp)
- return results_c(full_output, r, "ridder")
- def brentq(f, a, b, args=(),
- xtol=_xtol, rtol=_rtol, maxiter=_iter,
- full_output=False, disp=True):
- """
- Find a root of a function in a bracketing interval using Brent's method.
- Uses the classic Brent's method to find a root of the function `f` on
- the sign changing interval [a , b]. Generally considered the best of the
- rootfinding routines here. It is a safe version of the secant method that
- uses inverse quadratic extrapolation. Brent's method combines root
- bracketing, interval bisection, and inverse quadratic interpolation. It is
- sometimes known as the van Wijngaarden-Dekker-Brent method. Brent (1973)
- claims convergence is guaranteed for functions computable within [a,b].
- [Brent1973]_ provides the classic description of the algorithm. Another
- description can be found in a recent edition of Numerical Recipes, including
- [PressEtal1992]_. A third description is at
- http://mathworld.wolfram.com/BrentsMethod.html. It should be easy to
- understand the algorithm just by reading our code. Our code diverges a bit
- from standard presentations: we choose a different formula for the
- extrapolation step.
- Parameters
- ----------
- f : function
- Python function returning a number. The function :math:`f`
- must be continuous, and :math:`f(a)` and :math:`f(b)` must
- have opposite signs.
- a : scalar
- One end of the bracketing interval :math:`[a, b]`.
- b : scalar
- The other end of the bracketing interval :math:`[a, b]`.
- xtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter must be positive. For nice functions, Brent's
- method will often satisfy the above condition with ``xtol/2``
- and ``rtol/2``. [Brent1973]_
- rtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter cannot be smaller than its default value of
- ``4*np.finfo(float).eps``. For nice functions, Brent's
- method will often satisfy the above condition with ``xtol/2``
- and ``rtol/2``. [Brent1973]_
- maxiter : int, optional
- If convergence is not achieved in `maxiter` iterations, an error is
- raised. Must be >= 0.
- args : tuple, optional
- Containing extra arguments for the function `f`.
- `f` is called by ``apply(f, (x)+args)``.
- full_output : bool, optional
- If `full_output` is False, the root is returned. If `full_output` is
- True, the return value is ``(x, r)``, where `x` is the root, and `r` is
- a `RootResults` object.
- disp : bool, optional
- If True, raise RuntimeError if the algorithm didn't converge.
- Otherwise, the convergence status is recorded in any `RootResults`
- return object.
- Returns
- -------
- root : float
- Root of `f` between `a` and `b`.
- r : `RootResults` (present if ``full_output = True``)
- Object containing information about the convergence. In particular,
- ``r.converged`` is True if the routine converged.
- See Also
- --------
- fmin, fmin_powell, fmin_cg, fmin_bfgs, fmin_ncg : multivariate local optimizers
- leastsq : nonlinear least squares minimizer
- fmin_l_bfgs_b, fmin_tnc, fmin_cobyla : constrained multivariate optimizers
- basinhopping, differential_evolution, brute : global optimizers
- fminbound, brent, golden, bracket : local scalar minimizers
- fsolve : N-D root-finding
- brenth, ridder, bisect, newton : 1-D root-finding
- fixed_point : scalar fixed-point finder
- elementwise.find_root : efficient elementwise 1-D root-finder
- Notes
- -----
- `f` must be continuous. f(a) and f(b) must have opposite signs.
- As mentioned in the parameter documentation, the computed root ``x0`` will
- satisfy ``np.isclose(x, x0, atol=xtol, rtol=rtol)``, where ``x`` is the
- exact root. In equation form, this terminating condition is ``abs(x - x0)
- <= xtol + rtol * abs(x0)``.
- The default value ``xtol=2e-12`` may lead to surprising behavior if one
- expects `brentq` to always compute roots with relative error near machine
- precision. Care should be taken to select `xtol` for the use case at hand.
- Setting ``xtol=5e-324``, the smallest subnormal number, will ensure the
- highest level of accuracy. Larger values of `xtol` may be useful for saving
- function evaluations when a root is at or near zero in applications where
- the tiny absolute differences available between floating point numbers near
- zero are not meaningful.
- References
- ----------
- .. [Brent1973]
- Brent, R. P.,
- *Algorithms for Minimization Without Derivatives*.
- Englewood Cliffs, NJ: Prentice-Hall, 1973. Ch. 3-4.
- .. [PressEtal1992]
- Press, W. H.; Flannery, B. P.; Teukolsky, S. A.; and Vetterling, W. T.
- *Numerical Recipes in FORTRAN: The Art of Scientific Computing*, 2nd ed.
- Cambridge, England: Cambridge University Press, pp. 352-355, 1992.
- Section 9.3: "Van Wijngaarden-Dekker-Brent Method."
- Examples
- --------
- >>> def f(x):
- ... return (x**2 - 1)
- >>> from scipy import optimize
- >>> root = optimize.brentq(f, -2, 0)
- >>> root
- -1.0
- >>> root = optimize.brentq(f, 0, 2)
- >>> root
- 1.0
- """
- if not isinstance(args, tuple):
- args = (args,)
- maxiter = operator.index(maxiter)
- if xtol <= 0:
- raise ValueError(f"xtol too small ({xtol:g} <= 0)")
- if rtol < _rtol:
- raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})")
- f = _wrap_nan_raise(f)
- r = _zeros._brentq(f, a, b, xtol, rtol, maxiter, args, full_output, disp)
- return results_c(full_output, r, "brentq")
- def brenth(f, a, b, args=(),
- xtol=_xtol, rtol=_rtol, maxiter=_iter,
- full_output=False, disp=True):
- """Find a root of a function in a bracketing interval using Brent's
- method with hyperbolic extrapolation.
- A variation on the classic Brent routine to find a root of the function f
- between the arguments a and b that uses hyperbolic extrapolation instead of
- inverse quadratic extrapolation. Bus & Dekker (1975) guarantee convergence
- for this method, claiming that the upper bound of function evaluations here
- is 4 or 5 times that of bisection.
- f(a) and f(b) cannot have the same signs. Generally, on a par with the
- brent routine, but not as heavily tested. It is a safe version of the
- secant method that uses hyperbolic extrapolation.
- The version here is by Chuck Harris, and implements Algorithm M of
- [BusAndDekker1975]_, where further details (convergence properties,
- additional remarks and such) can be found
- Parameters
- ----------
- f : function
- Python function returning a number. f must be continuous, and f(a) and
- f(b) must have opposite signs.
- a : scalar
- One end of the bracketing interval [a,b].
- b : scalar
- The other end of the bracketing interval [a,b].
- xtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter must be positive. As with `brentq`, for nice
- functions the method will often satisfy the above condition
- with ``xtol/2`` and ``rtol/2``.
- rtol : number, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter cannot be smaller than its default value of
- ``4*np.finfo(float).eps``. As with `brentq`, for nice functions
- the method will often satisfy the above condition with
- ``xtol/2`` and ``rtol/2``.
- maxiter : int, optional
- If convergence is not achieved in `maxiter` iterations, an error is
- raised. Must be >= 0.
- args : tuple, optional
- Containing extra arguments for the function `f`.
- `f` is called by ``apply(f, (x)+args)``.
- full_output : bool, optional
- If `full_output` is False, the root is returned. If `full_output` is
- True, the return value is ``(x, r)``, where `x` is the root, and `r` is
- a `RootResults` object.
- disp : bool, optional
- If True, raise RuntimeError if the algorithm didn't converge.
- Otherwise, the convergence status is recorded in any `RootResults`
- return object.
- Returns
- -------
- root : float
- Root of `f` between `a` and `b`.
- r : `RootResults` (present if ``full_output = True``)
- Object containing information about the convergence. In particular,
- ``r.converged`` is True if the routine converged.
- See Also
- --------
- fmin, fmin_powell, fmin_cg, fmin_bfgs, fmin_ncg : multivariate local optimizers
- leastsq : nonlinear least squares minimizer
- fmin_l_bfgs_b, fmin_tnc, fmin_cobyla : constrained multivariate optimizers
- basinhopping, differential_evolution, brute : global optimizers
- fminbound, brent, golden, bracket : local scalar minimizers
- fsolve : N-D root-finding
- brentq, ridder, bisect, newton : 1-D root-finding
- fixed_point : scalar fixed-point finder
- elementwise.find_root : efficient elementwise 1-D root-finder
- Notes
- -----
- As mentioned in the parameter documentation, the computed root ``x0`` will
- satisfy ``np.isclose(x, x0, atol=xtol, rtol=rtol)``, where ``x`` is the
- exact root. In equation form, this terminating condition is ``abs(x - x0)
- <= xtol + rtol * abs(x0)``.
- The default value ``xtol=2e-12`` may lead to surprising behavior if one
- expects `brenth` to always compute roots with relative error near machine
- precision. Care should be taken to select `xtol` for the use case at hand.
- Setting ``xtol=5e-324``, the smallest subnormal number, will ensure the
- highest level of accuracy. Larger values of `xtol` may be useful for saving
- function evaluations when a root is at or near zero in applications where
- the tiny absolute differences available between floating point numbers near
- zero are not meaningful.
- References
- ----------
- .. [BusAndDekker1975]
- Bus, J. C. P., Dekker, T. J.,
- "Two Efficient Algorithms with Guaranteed Convergence for Finding a Zero
- of a Function", ACM Transactions on Mathematical Software, Vol. 1, Issue
- 4, Dec. 1975, pp. 330-345. Section 3: "Algorithm M".
- :doi:`10.1145/355656.355659`
- Examples
- --------
- >>> def f(x):
- ... return (x**2 - 1)
- >>> from scipy import optimize
- >>> root = optimize.brenth(f, -2, 0)
- >>> root
- -1.0
- >>> root = optimize.brenth(f, 0, 2)
- >>> root
- 1.0
- """
- if not isinstance(args, tuple):
- args = (args,)
- maxiter = operator.index(maxiter)
- if xtol <= 0:
- raise ValueError(f"xtol too small ({xtol:g} <= 0)")
- if rtol < _rtol:
- raise ValueError(f"rtol too small ({rtol:g} < {_rtol:g})")
- f = _wrap_nan_raise(f)
- r = _zeros._brenth(f, a, b, xtol, rtol, maxiter, args, full_output, disp)
- return results_c(full_output, r, "brenth")
- ################################
- # TOMS "Algorithm 748: Enclosing Zeros of Continuous Functions", by
- # Alefeld, G. E. and Potra, F. A. and Shi, Yixun,
- # See [1]
- def _notclose(fs, rtol=_rtol, atol=_xtol):
- # Ensure not None, not 0, all finite, and not very close to each other
- notclosefvals = (
- all(fs) and all(np.isfinite(fs)) and
- not any(any(np.isclose(_f, fs[i + 1:], rtol=rtol, atol=atol))
- for i, _f in enumerate(fs[:-1])))
- return notclosefvals
- def _secant(xvals, fvals):
- """Perform a secant step, taking a little care"""
- # Secant has many "mathematically" equivalent formulations
- # x2 = x0 - (x1 - x0)/(f1 - f0) * f0
- # = x1 - (x1 - x0)/(f1 - f0) * f1
- # = (-x1 * f0 + x0 * f1) / (f1 - f0)
- # = (-f0 / f1 * x1 + x0) / (1 - f0 / f1)
- # = (-f1 / f0 * x0 + x1) / (1 - f1 / f0)
- x0, x1 = xvals[:2]
- f0, f1 = fvals[:2]
- if f0 == f1:
- return np.nan
- if np.abs(f1) > np.abs(f0):
- x2 = (-f0 / f1 * x1 + x0) / (1 - f0 / f1)
- else:
- x2 = (-f1 / f0 * x0 + x1) / (1 - f1 / f0)
- return x2
- def _update_bracket(ab, fab, c, fc):
- """Update a bracket given (c, fc), return the discarded endpoints."""
- fa, fb = fab
- idx = (0 if np.sign(fa) * np.sign(fc) > 0 else 1)
- rx, rfx = ab[idx], fab[idx]
- fab[idx] = fc
- ab[idx] = c
- return rx, rfx
- def _compute_divided_differences(xvals, fvals, N=None, full=True,
- forward=True):
- """Return a matrix of divided differences for the xvals, fvals pairs
- DD[i, j] = f[x_{i-j}, ..., x_i] for 0 <= j <= i
- If full is False, just return the main diagonal(or last row):
- f[a], f[a, b] and f[a, b, c].
- If forward is False, return f[c], f[b, c], f[a, b, c]."""
- if full:
- if forward:
- xvals = np.asarray(xvals)
- else:
- xvals = np.array(xvals)[::-1]
- M = len(xvals)
- N = M if N is None else min(N, M)
- DD = np.zeros([M, N])
- DD[:, 0] = fvals[:]
- for i in range(1, N):
- DD[i:, i] = (np.diff(DD[i - 1:, i - 1]) /
- (xvals[i:] - xvals[:M - i]))
- return DD
- xvals = np.asarray(xvals)
- dd = np.array(fvals)
- row = np.array(fvals)
- idx2Use = (0 if forward else -1)
- dd[0] = fvals[idx2Use]
- for i in range(1, len(xvals)):
- denom = xvals[i:i + len(row) - 1] - xvals[:len(row) - 1]
- row = np.diff(row)[:] / denom
- dd[i] = row[idx2Use]
- return dd
- def _interpolated_poly(xvals, fvals, x):
- """Compute p(x) for the polynomial passing through the specified locations.
- Use Neville's algorithm to compute p(x) where p is the minimal degree
- polynomial passing through the points xvals, fvals"""
- xvals = np.asarray(xvals)
- N = len(xvals)
- Q = np.zeros([N, N])
- D = np.zeros([N, N])
- Q[:, 0] = fvals[:]
- D[:, 0] = fvals[:]
- for k in range(1, N):
- alpha = D[k:, k - 1] - Q[k - 1:N - 1, k - 1]
- diffik = xvals[0:N - k] - xvals[k:N]
- Q[k:, k] = (xvals[k:] - x) / diffik * alpha
- D[k:, k] = (xvals[:N - k] - x) / diffik * alpha
- # Expect Q[-1, 1:] to be small relative to Q[-1, 0] as x approaches a root
- return np.sum(Q[-1, 1:]) + Q[-1, 0]
- def _inverse_poly_zero(a, b, c, d, fa, fb, fc, fd):
- """Inverse cubic interpolation f-values -> x-values
- Given four points (fa, a), (fb, b), (fc, c), (fd, d) with
- fa, fb, fc, fd all distinct, find poly IP(y) through the 4 points
- and compute x=IP(0).
- """
- return _interpolated_poly([fa, fb, fc, fd], [a, b, c, d], 0)
- def _newton_quadratic(ab, fab, d, fd, k):
- """Apply Newton-Raphson like steps, using divided differences to approximate f'
- ab is a real interval [a, b] containing a root,
- fab holds the real values of f(a), f(b)
- d is a real number outside [ab, b]
- k is the number of steps to apply
- """
- a, b = ab
- fa, fb = fab
- _, B, A = _compute_divided_differences([a, b, d], [fa, fb, fd],
- forward=True, full=False)
- # _P is the quadratic polynomial through the 3 points
- def _P(x):
- # Horner evaluation of fa + B * (x - a) + A * (x - a) * (x - b)
- return (A * (x - b) + B) * (x - a) + fa
- if A == 0:
- r = a - fa / B
- else:
- r = (a if np.sign(A) * np.sign(fa) > 0 else b)
- # Apply k Newton-Raphson steps to _P(x), starting from x=r
- for i in range(k):
- r1 = r - _P(r) / (B + A * (2 * r - a - b))
- if not (ab[0] < r1 < ab[1]):
- if (ab[0] < r < ab[1]):
- return r
- r = sum(ab) / 2.0
- break
- r = r1
- return r
- class TOMS748Solver:
- """Solve f(x, *args) == 0 using Algorithm748 of Alefeld, Potro & Shi.
- """
- _MU = 0.5
- _K_MIN = 1
- _K_MAX = 100 # A very high value for real usage. Expect 1, 2, maybe 3.
- def __init__(self):
- self.f = None
- self.args = None
- self.function_calls = 0
- self.iterations = 0
- self.k = 2
- # ab=[a,b] is a global interval containing a root
- self.ab = [np.nan, np.nan]
- # fab is function values at a, b
- self.fab = [np.nan, np.nan]
- self.d = None
- self.fd = None
- self.e = None
- self.fe = None
- self.disp = False
- self.xtol = _xtol
- self.rtol = _rtol
- self.maxiter = _iter
- def configure(self, xtol, rtol, maxiter, disp, k):
- self.disp = disp
- self.xtol = xtol
- self.rtol = rtol
- self.maxiter = maxiter
- # Silently replace a low value of k with 1
- self.k = max(k, self._K_MIN)
- # Noisily replace a high value of k with self._K_MAX
- if self.k > self._K_MAX:
- msg = f"toms748: Overriding k: ->{self._K_MAX}"
- warnings.warn(msg, RuntimeWarning, stacklevel=3)
- self.k = self._K_MAX
- def _callf(self, x, error=True):
- """Call the user-supplied function, update book-keeping"""
- fx = self.f(x, *self.args)
- self.function_calls += 1
- if not np.isfinite(fx) and error:
- raise ValueError(f"Invalid function value: f({x:f}) -> {fx} ")
- return fx
- def get_result(self, x, flag=_ECONVERGED):
- r"""Package the result and statistics into a tuple."""
- return (x, self.function_calls, self.iterations, flag)
- def _update_bracket(self, c, fc):
- return _update_bracket(self.ab, self.fab, c, fc)
- def start(self, f, a, b, args=()):
- r"""Prepare for the iterations."""
- self.function_calls = 0
- self.iterations = 0
- self.f = f
- self.args = args
- self.ab[:] = [a, b]
- if not np.isfinite(a) or np.imag(a) != 0:
- raise ValueError(f"Invalid x value: {a} ")
- if not np.isfinite(b) or np.imag(b) != 0:
- raise ValueError(f"Invalid x value: {b} ")
- fa = self._callf(a)
- if not np.isfinite(fa) or np.imag(fa) != 0:
- raise ValueError(f"Invalid function value: f({a:f}) -> {fa} ")
- if fa == 0:
- return _ECONVERGED, a
- fb = self._callf(b)
- if not np.isfinite(fb) or np.imag(fb) != 0:
- raise ValueError(f"Invalid function value: f({b:f}) -> {fb} ")
- if fb == 0:
- return _ECONVERGED, b
- if np.sign(fb) * np.sign(fa) > 0:
- raise ValueError("f(a) and f(b) must have different signs, but "
- f"f({a:e})={fa:e}, f({b:e})={fb:e} ")
- self.fab[:] = [fa, fb]
- return _EINPROGRESS, sum(self.ab) / 2.0
- def get_status(self):
- """Determine the current status."""
- a, b = self.ab[:2]
- if np.isclose(a, b, rtol=self.rtol, atol=self.xtol):
- return _ECONVERGED, sum(self.ab) / 2.0
- if self.iterations >= self.maxiter:
- return _ECONVERR, sum(self.ab) / 2.0
- return _EINPROGRESS, sum(self.ab) / 2.0
- def iterate(self):
- """Perform one step in the algorithm.
- Implements Algorithm 4.1(k=1) or 4.2(k=2) in [APS1995]
- """
- self.iterations += 1
- eps = np.finfo(float).eps
- d, fd, e, fe = self.d, self.fd, self.e, self.fe
- ab_width = self.ab[1] - self.ab[0] # Need the start width below
- c = None
- for nsteps in range(2, self.k+2):
- # If the f-values are sufficiently separated, perform an inverse
- # polynomial interpolation step. Otherwise, nsteps repeats of
- # an approximate Newton-Raphson step.
- if _notclose(self.fab + [fd, fe], rtol=0, atol=32*eps):
- c0 = _inverse_poly_zero(self.ab[0], self.ab[1], d, e,
- self.fab[0], self.fab[1], fd, fe)
- if self.ab[0] < c0 < self.ab[1]:
- c = c0
- if c is None:
- c = _newton_quadratic(self.ab, self.fab, d, fd, nsteps)
- fc = self._callf(c)
- if fc == 0:
- return _ECONVERGED, c
- # re-bracket
- e, fe = d, fd
- d, fd = self._update_bracket(c, fc)
- # u is the endpoint with the smallest f-value
- uix = (0 if np.abs(self.fab[0]) < np.abs(self.fab[1]) else 1)
- u, fu = self.ab[uix], self.fab[uix]
- _, A = _compute_divided_differences(self.ab, self.fab,
- forward=(uix == 0), full=False)
- c = u - 2 * fu / A
- if np.abs(c - u) > 0.5 * (self.ab[1] - self.ab[0]):
- c = sum(self.ab) / 2.0
- else:
- if np.isclose(c, u, rtol=eps, atol=0):
- # c didn't change (much).
- # Either because the f-values at the endpoints have vastly
- # differing magnitudes, or because the root is very close to
- # that endpoint
- frs = np.frexp(self.fab)[1]
- if frs[uix] < frs[1 - uix] - 50: # Differ by more than 2**50
- c = (31 * self.ab[uix] + self.ab[1 - uix]) / 32
- else:
- # Make a bigger adjustment, about the
- # size of the requested tolerance.
- mm = (1 if uix == 0 else -1)
- adj = mm * np.abs(c) * self.rtol + mm * self.xtol
- c = u + adj
- if not self.ab[0] < c < self.ab[1]:
- c = sum(self.ab) / 2.0
- fc = self._callf(c)
- if fc == 0:
- return _ECONVERGED, c
- e, fe = d, fd
- d, fd = self._update_bracket(c, fc)
- # If the width of the new interval did not decrease enough, bisect
- if self.ab[1] - self.ab[0] > self._MU * ab_width:
- e, fe = d, fd
- z = sum(self.ab) / 2.0
- fz = self._callf(z)
- if fz == 0:
- return _ECONVERGED, z
- d, fd = self._update_bracket(z, fz)
- # Record d and e for next iteration
- self.d, self.fd = d, fd
- self.e, self.fe = e, fe
- status, xn = self.get_status()
- return status, xn
- def solve(self, f, a, b, args=(),
- xtol=_xtol, rtol=_rtol, k=2, maxiter=_iter, disp=True):
- r"""Solve f(x) = 0 given an interval containing a root."""
- self.configure(xtol=xtol, rtol=rtol, maxiter=maxiter, disp=disp, k=k)
- status, xn = self.start(f, a, b, args)
- if status == _ECONVERGED:
- return self.get_result(xn)
- # The first step only has two x-values.
- c = _secant(self.ab, self.fab)
- if not self.ab[0] < c < self.ab[1]:
- c = sum(self.ab) / 2.0
- fc = self._callf(c)
- if fc == 0:
- return self.get_result(c)
- self.d, self.fd = self._update_bracket(c, fc)
- self.e, self.fe = None, None
- self.iterations += 1
- while True:
- status, xn = self.iterate()
- if status == _ECONVERGED:
- return self.get_result(xn)
- if status == _ECONVERR:
- fmt = "Failed to converge after %d iterations, bracket is %s"
- if disp:
- msg = fmt % (self.iterations + 1, self.ab)
- raise RuntimeError(msg)
- return self.get_result(xn, _ECONVERR)
- def toms748(f, a, b, args=(), k=1,
- xtol=_xtol, rtol=_rtol, maxiter=_iter,
- full_output=False, disp=True):
- """
- Find a root using TOMS Algorithm 748 method.
- Implements the Algorithm 748 method of Alefeld, Potro and Shi to find a
- root of the function `f` on the interval ``[a , b]``, where `f(a)` and
- `f(b)` must have opposite signs.
- It uses a mixture of inverse cubic interpolation and
- "Newton-quadratic" steps. [APS1995].
- Parameters
- ----------
- f : function
- Python function returning a scalar. The function :math:`f`
- must be continuous, and :math:`f(a)` and :math:`f(b)`
- have opposite signs.
- a : scalar,
- lower boundary of the search interval
- b : scalar,
- upper boundary of the search interval
- args : tuple, optional
- containing extra arguments for the function `f`.
- `f` is called by ``f(x, *args)``.
- k : int, optional
- The number of Newton quadratic steps to perform each
- iteration. ``k>=1``.
- xtol : scalar, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root. The
- parameter must be positive.
- rtol : scalar, optional
- The computed root ``x0`` will satisfy ``np.isclose(x, x0,
- atol=xtol, rtol=rtol)``, where ``x`` is the exact root.
- maxiter : int, optional
- If convergence is not achieved in `maxiter` iterations, an error is
- raised. Must be >= 0.
- full_output : bool, optional
- If `full_output` is False, the root is returned. If `full_output` is
- True, the return value is ``(x, r)``, where `x` is the root, and `r` is
- a `RootResults` object.
- disp : bool, optional
- If True, raise RuntimeError if the algorithm didn't converge.
- Otherwise, the convergence status is recorded in the `RootResults`
- return object.
- Returns
- -------
- root : float
- Approximate root of `f`
- r : `RootResults` (present if ``full_output = True``)
- Object containing information about the convergence. In particular,
- ``r.converged`` is True if the routine converged.
- See Also
- --------
- brentq, brenth, ridder, bisect, newton
- fsolve : find roots in N dimensions.
- elementwise.find_root : efficient elementwise 1-D root-finder
- Notes
- -----
- `f` must be continuous.
- Algorithm 748 with ``k=2`` is asymptotically the most efficient
- algorithm known for finding roots of a four times continuously
- differentiable function.
- In contrast with Brent's algorithm, which may only decrease the length of
- the enclosing bracket on the last step, Algorithm 748 decreases it each
- iteration with the same asymptotic efficiency as it finds the root.
- For easy statement of efficiency indices, assume that `f` has 4
- continuous deriviatives.
- For ``k=1``, the convergence order is at least 2.7, and with about
- asymptotically 2 function evaluations per iteration, the efficiency
- index is approximately 1.65.
- For ``k=2``, the order is about 4.6 with asymptotically 3 function
- evaluations per iteration, and the efficiency index 1.66.
- For higher values of `k`, the efficiency index approaches
- the kth root of ``(3k-2)``, hence ``k=1`` or ``k=2`` are
- usually appropriate.
- As mentioned in the parameter documentation, the computed root ``x0`` will
- satisfy ``np.isclose(x, x0, atol=xtol, rtol=rtol)``, where ``x`` is the
- exact root. In equation form, this terminating condition is ``abs(x - x0)
- <= xtol + rtol * abs(x0)``.
- The default value ``xtol=2e-12`` may lead to surprising behavior if one
- expects `toms748` to always compute roots with relative error near machine
- precision. Care should be taken to select `xtol` for the use case at hand.
- Setting ``xtol=5e-324``, the smallest subnormal number, will ensure the
- highest level of accuracy. Larger values of `xtol` may be useful for saving
- function evaluations when a root is at or near zero in applications where
- the tiny absolute differences available between floating point numbers near
- zero are not meaningful.
- References
- ----------
- .. [APS1995]
- Alefeld, G. E. and Potra, F. A. and Shi, Yixun,
- *Algorithm 748: Enclosing Zeros of Continuous Functions*,
- ACM Trans. Math. Softw. Volume 221(1995)
- doi = {10.1145/210089.210111}
- Examples
- --------
- >>> def f(x):
- ... return (x**3 - 1) # only one real root at x = 1
- >>> from scipy import optimize
- >>> root, results = optimize.toms748(f, 0, 2, full_output=True)
- >>> root
- 1.0
- >>> results
- converged: True
- flag: converged
- function_calls: 11
- iterations: 5
- root: 1.0
- method: toms748
- """
- if xtol <= 0:
- raise ValueError(f"xtol too small ({xtol:g} <= 0)")
- if rtol < _rtol / 4:
- raise ValueError(f"rtol too small ({rtol:g} < {_rtol/4:g})")
- maxiter = operator.index(maxiter)
- if maxiter < 1:
- raise ValueError("maxiter must be greater than 0")
- if not np.isfinite(a):
- raise ValueError(f"a is not finite {a}")
- if not np.isfinite(b):
- raise ValueError(f"b is not finite {b}")
- if a >= b:
- raise ValueError(f"a and b are not an interval [{a}, {b}]")
- if not k >= 1:
- raise ValueError(f"k too small ({k} < 1)")
- if not isinstance(args, tuple):
- args = (args,)
- f = _wrap_nan_raise(f)
- solver = TOMS748Solver()
- result = solver.solve(f, a, b, args=args, k=k, xtol=xtol, rtol=rtol,
- maxiter=maxiter, disp=disp)
- x, function_calls, iterations, flag = result
- return _results_select(full_output, (x, function_calls, iterations, flag),
- "toms748")
|