| import math |
| import numpy as np |
| import scipy._lib._elementwise_iterative_method as eim |
| from scipy._lib._util import _RichResult |
| from scipy._lib._array_api import xp_sign, xp_copy, xp_take_along_axis |
|
|
| |
| |
| |
|
|
|
|
| def _chandrupatla(func, a, b, *, args=(), xatol=None, xrtol=None, |
| fatol=None, frtol=0, maxiter=None, callback=None): |
| """Find the root of an elementwise function using Chandrupatla's algorithm. |
| |
| For each element of the output of `func`, `chandrupatla` seeks the scalar |
| root that makes the element 0. This function allows for `a`, `b`, and the |
| output of `func` to be of any broadcastable shapes. |
| |
| Parameters |
| ---------- |
| func : callable |
| The function whose root is desired. The signature must be:: |
| |
| func(x: ndarray, *args) -> ndarray |
| |
| where each element of ``x`` is a finite real and ``args`` is a tuple, |
| which may contain an arbitrary number of components of any type(s). |
| ``func`` must be an elementwise function: each element ``func(x)[i]`` |
| must equal ``func(x[i])`` for all indices ``i``. `_chandrupatla` |
| seeks an array ``x`` such that ``func(x)`` is an array of zeros. |
| a, b : array_like |
| The lower and upper bounds of the root of the function. Must be |
| broadcastable with one another. |
| args : tuple, optional |
| Additional positional arguments to be passed to `func`. |
| xatol, xrtol, fatol, frtol : float, optional |
| Absolute and relative tolerances on the root and function value. |
| See Notes for details. |
| maxiter : int, optional |
| The maximum number of iterations of the algorithm to perform. |
| The default is the maximum possible number of bisections within |
| the (normal) floating point numbers of the relevant dtype. |
| callback : callable, optional |
| An optional user-supplied function to be called before the first |
| iteration and after each iteration. |
| Called as ``callback(res)``, where ``res`` is a ``_RichResult`` |
| similar to that returned by `_chandrupatla` (but containing the current |
| iterate's values of all variables). If `callback` raises a |
| ``StopIteration``, the algorithm will terminate immediately and |
| `_chandrupatla` will return a result. |
| |
| Returns |
| ------- |
| res : _RichResult |
| An instance of `scipy._lib._util._RichResult` with the following |
| attributes. The descriptions are written as though the values will be |
| scalars; however, if `func` returns an array, the outputs will be |
| arrays of the same shape. |
| |
| x : float |
| The root of the function, if the algorithm terminated successfully. |
| nfev : int |
| The number of times the function was called to find the root. |
| nit : int |
| The number of iterations of Chandrupatla's algorithm performed. |
| status : int |
| An integer representing the exit status of the algorithm. |
| ``0`` : The algorithm converged to the specified tolerances. |
| ``-1`` : The algorithm encountered an invalid bracket. |
| ``-2`` : The maximum number of iterations was reached. |
| ``-3`` : A non-finite value was encountered. |
| ``-4`` : Iteration was terminated by `callback`. |
| ``1`` : The algorithm is proceeding normally (in `callback` only). |
| success : bool |
| ``True`` when the algorithm terminated successfully (status ``0``). |
| fun : float |
| The value of `func` evaluated at `x`. |
| xl, xr : float |
| The lower and upper ends of the bracket. |
| fl, fr : float |
| The function value at the lower and upper ends of the bracket. |
| |
| Notes |
| ----- |
| Implemented based on Chandrupatla's original paper [1]_. |
| |
| If ``xl`` and ``xr`` are the left and right ends of the bracket, |
| ``xmin = xl if abs(func(xl)) <= abs(func(xr)) else xr``, |
| and ``fmin0 = min(func(a), func(b))``, then the algorithm is considered to |
| have converged when ``abs(xr - xl) < xatol + abs(xmin) * xrtol`` or |
| ``fun(xmin) <= fatol + abs(fmin0) * frtol``. This is equivalent to the |
| termination condition described in [1]_ with ``xrtol = 4e-10``, |
| ``xatol = 1e-5``, and ``fatol = frtol = 0``. The default values are |
| ``xatol = 4*tiny``, ``xrtol = 4*eps``, ``frtol = 0``, and ``fatol = tiny``, |
| where ``eps`` and ``tiny`` are the precision and smallest normal number |
| of the result ``dtype`` of function inputs and outputs. |
| |
| References |
| ---------- |
| |
| .. [1] Chandrupatla, Tirupathi R. |
| "A new hybrid quadratic/bisection algorithm for finding the zero of a |
| nonlinear function without using derivatives". |
| Advances in Engineering Software, 28(3), 145-149. |
| https://doi.org/10.1016/s0965-9978(96)00051-8 |
| |
| See Also |
| -------- |
| brentq, brenth, ridder, bisect, newton |
| |
| Examples |
| -------- |
| >>> from scipy import optimize |
| >>> def f(x, c): |
| ... return x**3 - 2*x - c |
| >>> c = 5 |
| >>> res = optimize._chandrupatla._chandrupatla(f, 0, 3, args=(c,)) |
| >>> res.x |
| 2.0945514818937463 |
| |
| >>> c = [3, 4, 5] |
| >>> res = optimize._chandrupatla._chandrupatla(f, 0, 3, args=(c,)) |
| >>> res.x |
| array([1.8932892 , 2. , 2.09455148]) |
| |
| """ |
| res = _chandrupatla_iv(func, args, xatol, xrtol, |
| fatol, frtol, maxiter, callback) |
| func, args, xatol, xrtol, fatol, frtol, maxiter, callback = res |
|
|
| |
| temp = eim._initialize(func, (a, b), args) |
| func, xs, fs, args, shape, dtype, xp = temp |
| x1, x2 = xs |
| f1, f2 = fs |
| status = xp.full_like(x1, xp.asarray(eim._EINPROGRESS), |
| dtype=xp.int32) |
| nit, nfev = 0, 2 |
| finfo = xp.finfo(dtype) |
| xatol = 4*finfo.smallest_normal if xatol is None else xatol |
| xrtol = 4*finfo.eps if xrtol is None else xrtol |
| fatol = finfo.smallest_normal if fatol is None else fatol |
| frtol = frtol * xp.minimum(xp.abs(f1), xp.abs(f2)) |
| maxiter = (math.log2(finfo.max) - math.log2(finfo.smallest_normal) |
| if maxiter is None else maxiter) |
| work = _RichResult(x1=x1, f1=f1, x2=x2, f2=f2, x3=None, f3=None, t=0.5, |
| xatol=xatol, xrtol=xrtol, fatol=fatol, frtol=frtol, |
| nit=nit, nfev=nfev, status=status) |
| res_work_pairs = [('status', 'status'), ('x', 'xmin'), ('fun', 'fmin'), |
| ('nit', 'nit'), ('nfev', 'nfev'), ('xl', 'x1'), |
| ('fl', 'f1'), ('xr', 'x2'), ('fr', 'f2')] |
|
|
| def pre_func_eval(work): |
| |
| x = work.x1 + work.t * (work.x2 - work.x1) |
| return x |
|
|
| def post_func_eval(x, f, work): |
| |
| |
| work.x3, work.f3 = (xp.asarray(work.x2, copy=True), |
| xp.asarray(work.f2, copy=True)) |
| j = xp.sign(f) == xp.sign(work.f1) |
| nj = ~j |
| work.x3[j], work.f3[j] = work.x1[j], work.f1[j] |
| work.x2[nj], work.f2[nj] = work.x1[nj], work.f1[nj] |
| work.x1, work.f1 = x, f |
|
|
| def check_termination(work): |
| |
| |
|
|
| |
| i = xp.abs(work.f1) < xp.abs(work.f2) |
| work.xmin = xp.where(i, work.x1, work.x2) |
| work.fmin = xp.where(i, work.f1, work.f2) |
| stop = xp.zeros_like(work.x1, dtype=xp.bool) |
|
|
| |
| |
| |
| |
| i = xp.abs(work.fmin) <= work.fatol + work.frtol |
| work.status[i] = eim._ECONVERGED |
| stop[i] = True |
|
|
| |
| |
| i = (xp_sign(work.f1) == xp_sign(work.f2)) & ~stop |
| NaN = xp.asarray(xp.nan, dtype=work.xmin.dtype) |
| work.xmin[i], work.fmin[i], work.status[i] = NaN, NaN, eim._ESIGNERR |
| stop[i] = True |
|
|
| |
| |
| x_nonfinite = ~(xp.isfinite(work.x1) & xp.isfinite(work.x2)) |
| f_nan = xp.isnan(work.f1) & xp.isnan(work.f2) |
| i = (x_nonfinite | f_nan) & ~stop |
| work.xmin[i], work.fmin[i], work.status[i] = NaN, NaN, eim._EVALUEERR |
| stop[i] = True |
|
|
| |
| |
| work.dx = xp.abs(work.x2 - work.x1) |
| work.tol = xp.abs(work.xmin) * work.xrtol + work.xatol |
| i = work.dx < work.tol |
| work.status[i] = eim._ECONVERGED |
| stop[i] = True |
|
|
| return stop |
|
|
| def post_termination_check(work): |
| |
| xi1 = (work.x1 - work.x2) / (work.x3 - work.x2) |
| with np.errstate(divide='ignore', invalid='ignore'): |
| phi1 = (work.f1 - work.f2) / (work.f3 - work.f2) |
| alpha = (work.x3 - work.x1) / (work.x2 - work.x1) |
| j = ((1 - xp.sqrt(1 - xi1)) < phi1) & (phi1 < xp.sqrt(xi1)) |
|
|
| f1j, f2j, f3j, alphaj = work.f1[j], work.f2[j], work.f3[j], alpha[j] |
| t = xp.full_like(alpha, xp.asarray(0.5)) |
| t[j] = (f1j / (f1j - f2j) * f3j / (f3j - f2j) |
| - alphaj * f1j / (f3j - f1j) * f2j / (f2j - f3j)) |
|
|
| |
| |
| tl = 0.5 * work.tol / work.dx |
| work.t = xp.clip(t, tl, 1 - tl) |
|
|
| def customize_result(res, shape): |
| xl, xr, fl, fr = res['xl'], res['xr'], res['fl'], res['fr'] |
| i = res['xl'] < res['xr'] |
| res['xl'] = xp.where(i, xl, xr) |
| res['xr'] = xp.where(i, xr, xl) |
| res['fl'] = xp.where(i, fl, fr) |
| res['fr'] = xp.where(i, fr, fl) |
| return shape |
|
|
| return eim._loop(work, callback, shape, maxiter, func, args, dtype, |
| pre_func_eval, post_func_eval, check_termination, |
| post_termination_check, customize_result, res_work_pairs, |
| xp=xp) |
|
|
|
|
| def _chandrupatla_iv(func, args, xatol, xrtol, |
| fatol, frtol, maxiter, callback): |
| |
|
|
| if not callable(func): |
| raise ValueError('`func` must be callable.') |
|
|
| if not np.iterable(args): |
| args = (args,) |
|
|
| |
| tols = np.asarray([xatol if xatol is not None else 1, |
| xrtol if xrtol is not None else 1, |
| fatol if fatol is not None else 1, |
| frtol if frtol is not None else 1]) |
| if (not np.issubdtype(tols.dtype, np.number) or np.any(tols < 0) |
| or np.any(np.isnan(tols)) or tols.shape != (4,)): |
| raise ValueError('Tolerances must be non-negative scalars.') |
|
|
| if maxiter is not None: |
| maxiter_int = int(maxiter) |
| if maxiter != maxiter_int or maxiter < 0: |
| raise ValueError('`maxiter` must be a non-negative integer.') |
|
|
| if callback is not None and not callable(callback): |
| raise ValueError('`callback` must be callable.') |
|
|
| return func, args, xatol, xrtol, fatol, frtol, maxiter, callback |
|
|
|
|
| def _chandrupatla_minimize(func, x1, x2, x3, *, args=(), xatol=None, |
| xrtol=None, fatol=None, frtol=None, maxiter=100, |
| callback=None): |
| """Find the minimizer of an elementwise function. |
| |
| For each element of the output of `func`, `_chandrupatla_minimize` seeks |
| the scalar minimizer that minimizes the element. This function allows for |
| `x1`, `x2`, `x3`, and the elements of `args` to be arrays of any |
| broadcastable shapes. |
| |
| Parameters |
| ---------- |
| func : callable |
| The function whose minimizer is desired. The signature must be:: |
| |
| func(x: ndarray, *args) -> ndarray |
| |
| where each element of ``x`` is a finite real and ``args`` is a tuple, |
| which may contain an arbitrary number of arrays that are broadcastable |
| with `x`. ``func`` must be an elementwise function: each element |
| ``func(x)[i]`` must equal ``func(x[i])`` for all indices ``i``. |
| `_chandrupatla` seeks an array ``x`` such that ``func(x)`` is an array |
| of minima. |
| x1, x2, x3 : array_like |
| The abscissae of a standard scalar minimization bracket. A bracket is |
| valid if ``x1 < x2 < x3`` and ``func(x1) > func(x2) <= func(x3)``. |
| Must be broadcastable with one another and `args`. |
| args : tuple, optional |
| Additional positional arguments to be passed to `func`. Must be arrays |
| broadcastable with `x1`, `x2`, and `x3`. If the callable to be |
| differentiated requires arguments that are not broadcastable with `x`, |
| wrap that callable with `func` such that `func` accepts only `x` and |
| broadcastable arrays. |
| xatol, xrtol, fatol, frtol : float, optional |
| Absolute and relative tolerances on the minimizer and function value. |
| See Notes for details. |
| maxiter : int, optional |
| The maximum number of iterations of the algorithm to perform. |
| callback : callable, optional |
| An optional user-supplied function to be called before the first |
| iteration and after each iteration. |
| Called as ``callback(res)``, where ``res`` is a ``_RichResult`` |
| similar to that returned by `_chandrupatla_minimize` (but containing |
| the current iterate's values of all variables). If `callback` raises a |
| ``StopIteration``, the algorithm will terminate immediately and |
| `_chandrupatla_minimize` will return a result. |
| |
| Returns |
| ------- |
| res : _RichResult |
| An instance of `scipy._lib._util._RichResult` with the following |
| attributes. (The descriptions are written as though the values will be |
| scalars; however, if `func` returns an array, the outputs will be |
| arrays of the same shape.) |
| |
| success : bool |
| ``True`` when the algorithm terminated successfully (status ``0``). |
| status : int |
| An integer representing the exit status of the algorithm. |
| ``0`` : The algorithm converged to the specified tolerances. |
| ``-1`` : The algorithm encountered an invalid bracket. |
| ``-2`` : The maximum number of iterations was reached. |
| ``-3`` : A non-finite value was encountered. |
| ``-4`` : Iteration was terminated by `callback`. |
| ``1`` : The algorithm is proceeding normally (in `callback` only). |
| x : float |
| The minimizer of the function, if the algorithm terminated |
| successfully. |
| fun : float |
| The value of `func` evaluated at `x`. |
| nfev : int |
| The number of points at which `func` was evaluated. |
| nit : int |
| The number of iterations of the algorithm that were performed. |
| xl, xm, xr : float |
| The final three-point bracket. |
| fl, fm, fr : float |
| The function value at the bracket points. |
| |
| Notes |
| ----- |
| Implemented based on Chandrupatla's original paper [1]_. |
| |
| If ``x1 < x2 < x3`` are the points of the bracket and ``f1 > f2 <= f3`` |
| are the values of ``func`` at those points, then the algorithm is |
| considered to have converged when ``x3 - x1 <= abs(x2)*xrtol + xatol`` |
| or ``(f1 - 2*f2 + f3)/2 <= abs(f2)*frtol + fatol``. Note that first of |
| these differs from the termination conditions described in [1]_. The |
| default values of `xrtol` is the square root of the precision of the |
| appropriate dtype, and ``xatol = fatol = frtol`` is the smallest normal |
| number of the appropriate dtype. |
| |
| References |
| ---------- |
| .. [1] Chandrupatla, Tirupathi R. (1998). |
| "An efficient quadratic fit-sectioning algorithm for minimization |
| without derivatives". |
| Computer Methods in Applied Mechanics and Engineering, 152 (1-2), |
| 211-217. https://doi.org/10.1016/S0045-7825(97)00190-4 |
| |
| See Also |
| -------- |
| golden, brent, bounded |
| |
| Examples |
| -------- |
| >>> from scipy.optimize._chandrupatla import _chandrupatla_minimize |
| >>> def f(x, args=1): |
| ... return (x - args)**2 |
| >>> res = _chandrupatla_minimize(f, -5, 0, 5) |
| >>> res.x |
| 1.0 |
| >>> c = [1, 1.5, 2] |
| >>> res = _chandrupatla_minimize(f, -5, 0, 5, args=(c,)) |
| >>> res.x |
| array([1. , 1.5, 2. ]) |
| """ |
| res = _chandrupatla_iv(func, args, xatol, xrtol, |
| fatol, frtol, maxiter, callback) |
| func, args, xatol, xrtol, fatol, frtol, maxiter, callback = res |
|
|
| |
| xs = (x1, x2, x3) |
| temp = eim._initialize(func, xs, args) |
| func, xs, fs, args, shape, dtype, xp = temp |
| x1, x2, x3 = xs |
| f1, f2, f3 = fs |
| phi = xp.asarray(0.5 + 0.5*5**0.5, dtype=dtype)[()] |
| status = xp.full_like(x1, xp.asarray(eim._EINPROGRESS), |
| dtype=xp.int32) |
| nit, nfev = 0, 3 |
| fatol = xp.finfo(dtype).smallest_normal if fatol is None else fatol |
| frtol = xp.finfo(dtype).smallest_normal if frtol is None else frtol |
| xatol = xp.finfo(dtype).smallest_normal if xatol is None else xatol |
| xrtol = math.sqrt(xp.finfo(dtype).eps) if xrtol is None else xrtol |
|
|
| |
| xs, fs = xp.stack((x1, x2, x3)), xp.stack((f1, f2, f3)) |
| i = xp.argsort(xs, axis=0) |
| x1, x2, x3 = xp_take_along_axis(xs, i, axis=0) |
| f1, f2, f3 = xp_take_along_axis(fs, i, axis=0) |
| q0 = xp_copy(x3) |
|
|
| work = _RichResult(x1=x1, f1=f1, x2=x2, f2=f2, x3=x3, f3=f3, phi=phi, |
| xatol=xatol, xrtol=xrtol, fatol=fatol, frtol=frtol, |
| nit=nit, nfev=nfev, status=status, q0=q0, args=args) |
| res_work_pairs = [('status', 'status'), |
| ('x', 'x2'), ('fun', 'f2'), |
| ('nit', 'nit'), ('nfev', 'nfev'), |
| ('xl', 'x1'), ('xm', 'x2'), ('xr', 'x3'), |
| ('fl', 'f1'), ('fm', 'f2'), ('fr', 'f3')] |
|
|
| def pre_func_eval(work): |
| |
| |
| x21 = work.x2 - work.x1 |
| x32 = work.x3 - work.x2 |
|
|
| |
| |
| A = x21 * (work.f3 - work.f2) |
| B = x32 * (work.f1 - work.f2) |
| C = A / (A + B) |
| |
| q1 = 0.5 * (C*(work.x1 - work.x3) + work.x2 + work.x3) |
| |
|
|
| |
| |
| i = xp.abs(q1 - work.q0) < 0.5 * xp.abs(x21) |
| xi = q1[i] |
| |
| |
| |
| |
| j = xp.abs(q1[i] - work.x2[i]) <= work.xtol[i] |
| xi[j] = work.x2[i][j] + xp_sign(x32[i][j]) * work.xtol[i][j] |
|
|
| |
| |
| |
| |
| x = work.x2 + (2 - work.phi) * x32 |
| x[i] = xi |
|
|
| |
| work.q0 = q1 |
| return x |
|
|
| def post_func_eval(x, f, work): |
| |
| |
| |
| |
| i = xp_sign(x - work.x2) == xp_sign(work.x3 - work.x2) |
| xi, x1i, x2i, x3i = x[i], work.x1[i], work.x2[i], work.x3[i], |
| fi, f1i, f2i, f3i = f[i], work.f1[i], work.f2[i], work.f3[i] |
| j = fi > f2i |
| x3i[j], f3i[j] = xi[j], fi[j] |
| j = ~j |
| x1i[j], f1i[j], x2i[j], f2i[j] = x2i[j], f2i[j], xi[j], fi[j] |
|
|
| ni = ~i |
| xni, x1ni, x2ni, x3ni = x[ni], work.x1[ni], work.x2[ni], work.x3[ni], |
| fni, f1ni, f2ni, f3ni = f[ni], work.f1[ni], work.f2[ni], work.f3[ni] |
| j = fni > f2ni |
| x1ni[j], f1ni[j] = xni[j], fni[j] |
| j = ~j |
| x3ni[j], f3ni[j], x2ni[j], f2ni[j] = x2ni[j], f2ni[j], xni[j], fni[j] |
|
|
| work.x1[i], work.x2[i], work.x3[i] = x1i, x2i, x3i |
| work.f1[i], work.f2[i], work.f3[i] = f1i, f2i, f3i |
| work.x1[ni], work.x2[ni], work.x3[ni] = x1ni, x2ni, x3ni, |
| work.f1[ni], work.f2[ni], work.f3[ni] = f1ni, f2ni, f3ni |
|
|
| def check_termination(work): |
| |
| stop = xp.zeros_like(work.x1, dtype=bool) |
|
|
| |
| i = ((work.f2 > work.f1) | (work.f2 > work.f3)) |
| work.x2[i], work.f2[i] = xp.nan, xp.nan |
| stop[i], work.status[i] = True, eim._ESIGNERR |
|
|
| |
| finite = xp.isfinite(work.x1+work.x2+work.x3+work.f1+work.f2+work.f3) |
| i = ~(finite | stop) |
| work.x2[i], work.f2[i] = xp.nan, xp.nan |
| stop[i], work.status[i] = True, eim._EVALUEERR |
|
|
| |
| |
| |
| |
| |
| i = xp.abs(work.x3 - work.x2) < xp.abs(work.x2 - work.x1) |
| temp = work.x1[i] |
| work.x1[i] = work.x3[i] |
| work.x3[i] = temp |
| temp = work.f1[i] |
| work.f1[i] = work.f3[i] |
| work.f3[i] = temp |
|
|
| |
| |
| work.xtol = xp.abs(work.x2) * work.xrtol + work.xatol |
| |
| |
| i = xp.abs(work.x3 - work.x2) <= 2 * work.xtol |
|
|
| |
| ftol = xp.abs(work.f2) * work.frtol + work.fatol |
| |
| |
| |
| i |= (work.f1 - 2 * work.f2 + work.f3) <= 2*ftol |
| i &= ~stop |
| stop[i], work.status[i] = True, eim._ECONVERGED |
|
|
| return stop |
|
|
| def post_termination_check(work): |
| pass |
|
|
| def customize_result(res, shape): |
| xl, xr, fl, fr = res['xl'], res['xr'], res['fl'], res['fr'] |
| i = res['xl'] >= res['xr'] |
| res['xl'] = xp.where(i, xr, xl) |
| res['xr'] = xp.where(i, xl, xr) |
| res['fl'] = xp.where(i, fr, fl) |
| res['fr'] = xp.where(i, fl, fr) |
| return shape |
|
|
| return eim._loop(work, callback, shape, maxiter, func, args, dtype, |
| pre_func_eval, post_func_eval, check_termination, |
| post_termination_check, customize_result, res_work_pairs, |
| xp=xp) |
|
|