|
import pytest |
|
|
|
from functools import lru_cache |
|
|
|
from numpy.testing import (assert_warns, assert_, |
|
assert_allclose, |
|
assert_equal, |
|
assert_array_equal, |
|
suppress_warnings) |
|
import numpy as np |
|
from numpy import finfo, power, nan, isclose, sqrt, exp, sin, cos |
|
|
|
from scipy import optimize |
|
from scipy.optimize import (_zeros_py as zeros, newton, root_scalar, |
|
OptimizeResult) |
|
|
|
from scipy._lib._util import getfullargspec_no_self as _getfullargspec |
|
|
|
|
|
from scipy.optimize._tstutils import get_tests, functions as tstutils_functions |
|
|
|
TOL = 4*np.finfo(float).eps |
|
|
|
_FLOAT_EPS = finfo(float).eps |
|
|
|
bracket_methods = [zeros.bisect, zeros.ridder, zeros.brentq, zeros.brenth, |
|
zeros.toms748] |
|
gradient_methods = [zeros.newton] |
|
all_methods = bracket_methods + gradient_methods |
|
|
|
|
|
|
|
def f1(x): |
|
return x ** 2 - 2 * x - 1 |
|
|
|
|
|
def f1_1(x): |
|
return 2 * x - 2 |
|
|
|
|
|
def f1_2(x): |
|
return 2.0 + 0 * x |
|
|
|
|
|
def f1_and_p_and_pp(x): |
|
return f1(x), f1_1(x), f1_2(x) |
|
|
|
|
|
|
|
def f2(x): |
|
return exp(x) - cos(x) |
|
|
|
|
|
def f2_1(x): |
|
return exp(x) + sin(x) |
|
|
|
|
|
def f2_2(x): |
|
return exp(x) + cos(x) |
|
|
|
|
|
|
|
@lru_cache |
|
def f_lrucached(x): |
|
return x |
|
|
|
|
|
class TestScalarRootFinders: |
|
|
|
|
|
xtol = 4 * np.finfo(float).eps |
|
rtol = 4 * np.finfo(float).eps |
|
|
|
def _run_one_test(self, tc, method, sig_args_keys=None, |
|
sig_kwargs_keys=None, **kwargs): |
|
method_args = [] |
|
for k in sig_args_keys or []: |
|
if k not in tc: |
|
|
|
k = {'a': 'x0', 'b': 'x1', 'func': 'f'}.get(k, k) |
|
method_args.append(tc[k]) |
|
|
|
method_kwargs = dict(**kwargs) |
|
method_kwargs.update({'full_output': True, 'disp': False}) |
|
for k in sig_kwargs_keys or []: |
|
method_kwargs[k] = tc[k] |
|
|
|
root = tc.get('root') |
|
func_args = tc.get('args', ()) |
|
|
|
try: |
|
r, rr = method(*method_args, args=func_args, **method_kwargs) |
|
return root, rr, tc |
|
except Exception: |
|
return root, zeros.RootResults(nan, -1, -1, zeros._EVALUEERR, method), tc |
|
|
|
def run_tests(self, tests, method, name, known_fail=None, **kwargs): |
|
r"""Run test-cases using the specified method and the supplied signature. |
|
|
|
Extract the arguments for the method call from the test case |
|
dictionary using the supplied keys for the method's signature.""" |
|
|
|
|
|
|
|
|
|
|
|
sig = _getfullargspec(method) |
|
assert_(not sig.kwonlyargs) |
|
nDefaults = len(sig.defaults) |
|
nRequired = len(sig.args) - nDefaults |
|
sig_args_keys = sig.args[:nRequired] |
|
sig_kwargs_keys = [] |
|
if name in ['secant', 'newton', 'halley']: |
|
if name in ['newton', 'halley']: |
|
sig_kwargs_keys.append('fprime') |
|
if name in ['halley']: |
|
sig_kwargs_keys.append('fprime2') |
|
kwargs['tol'] = self.xtol |
|
else: |
|
kwargs['xtol'] = self.xtol |
|
kwargs['rtol'] = self.rtol |
|
|
|
results = [list(self._run_one_test( |
|
tc, method, sig_args_keys=sig_args_keys, |
|
sig_kwargs_keys=sig_kwargs_keys, **kwargs)) for tc in tests] |
|
|
|
|
|
known_fail = known_fail or [] |
|
notcvgd = [elt for elt in results if not elt[1].converged] |
|
notcvgd = [elt for elt in notcvgd if elt[-1]['ID'] not in known_fail] |
|
notcvged_IDS = [elt[-1]['ID'] for elt in notcvgd] |
|
assert_equal([len(notcvged_IDS), notcvged_IDS], [0, []]) |
|
|
|
|
|
tols = {'xtol': self.xtol, 'rtol': self.rtol} |
|
tols.update(**kwargs) |
|
rtol = tols['rtol'] |
|
atol = tols.get('tol', tols['xtol']) |
|
|
|
cvgd = [elt for elt in results if elt[1].converged] |
|
approx = [elt[1].root for elt in cvgd] |
|
correct = [elt[0] for elt in cvgd] |
|
|
|
notclose = [[a] + elt for a, c, elt in zip(approx, correct, cvgd) if |
|
not isclose(a, c, rtol=rtol, atol=atol) |
|
and elt[-1]['ID'] not in known_fail] |
|
|
|
fvs = [tc['f'](aroot, *tc.get('args', tuple())) |
|
for aroot, c, fullout, tc in notclose] |
|
notclose = [[fv] + elt for fv, elt in zip(fvs, notclose) if fv != 0] |
|
assert_equal([notclose, len(notclose)], [[], 0]) |
|
method_from_result = [result[1].method for result in results] |
|
expected_method = [name for _ in results] |
|
assert_equal(method_from_result, expected_method) |
|
|
|
def run_collection(self, collection, method, name, smoothness=None, |
|
known_fail=None, **kwargs): |
|
r"""Run a collection of tests using the specified method. |
|
|
|
The name is used to determine some optional arguments.""" |
|
tests = get_tests(collection, smoothness=smoothness) |
|
self.run_tests(tests, method, name, known_fail=known_fail, **kwargs) |
|
|
|
|
|
class TestBracketMethods(TestScalarRootFinders): |
|
@pytest.mark.parametrize('method', bracket_methods) |
|
@pytest.mark.parametrize('function', tstutils_functions) |
|
def test_basic_root_scalar(self, method, function): |
|
|
|
|
|
|
|
a, b = .5, sqrt(3) |
|
|
|
r = root_scalar(function, method=method.__name__, bracket=[a, b], x0=a, |
|
xtol=self.xtol, rtol=self.rtol) |
|
assert r.converged |
|
assert_allclose(r.root, 1.0, atol=self.xtol, rtol=self.rtol) |
|
assert r.method == method.__name__ |
|
|
|
@pytest.mark.parametrize('method', bracket_methods) |
|
@pytest.mark.parametrize('function', tstutils_functions) |
|
def test_basic_individual(self, method, function): |
|
|
|
|
|
|
|
a, b = .5, sqrt(3) |
|
root, r = method(function, a, b, xtol=self.xtol, rtol=self.rtol, |
|
full_output=True) |
|
|
|
assert r.converged |
|
assert_allclose(root, 1.0, atol=self.xtol, rtol=self.rtol) |
|
|
|
@pytest.mark.parametrize('method', bracket_methods) |
|
@pytest.mark.parametrize('function', tstutils_functions) |
|
def test_bracket_is_array(self, method, function): |
|
|
|
|
|
|
|
|
|
a, b = .5, sqrt(3) |
|
r = root_scalar(function, method=method.__name__, |
|
bracket=np.array([a, b]), x0=a, xtol=self.xtol, |
|
rtol=self.rtol) |
|
assert r.converged |
|
assert_allclose(r.root, 1.0, atol=self.xtol, rtol=self.rtol) |
|
assert r.method == method.__name__ |
|
|
|
@pytest.mark.parametrize('method', bracket_methods) |
|
def test_aps_collection(self, method): |
|
self.run_collection('aps', method, method.__name__, smoothness=1) |
|
|
|
@pytest.mark.parametrize('method', [zeros.bisect, zeros.ridder, |
|
zeros.toms748]) |
|
def test_chandrupatla_collection(self, method): |
|
known_fail = {'fun7.4'} if method == zeros.ridder else {} |
|
self.run_collection('chandrupatla', method, method.__name__, |
|
known_fail=known_fail) |
|
|
|
@pytest.mark.parametrize('method', bracket_methods) |
|
def test_lru_cached_individual(self, method): |
|
|
|
|
|
a, b = -1, 1 |
|
root, r = method(f_lrucached, a, b, full_output=True) |
|
assert r.converged |
|
assert_allclose(root, 0) |
|
|
|
|
|
class TestNewton(TestScalarRootFinders): |
|
def test_newton_collections(self): |
|
known_fail = ['aps.13.00'] |
|
known_fail += ['aps.12.05', 'aps.12.17'] |
|
for collection in ['aps', 'complex']: |
|
self.run_collection(collection, zeros.newton, 'newton', |
|
smoothness=2, known_fail=known_fail) |
|
|
|
def test_halley_collections(self): |
|
known_fail = ['aps.12.06', 'aps.12.07', 'aps.12.08', 'aps.12.09', |
|
'aps.12.10', 'aps.12.11', 'aps.12.12', 'aps.12.13', |
|
'aps.12.14', 'aps.12.15', 'aps.12.16', 'aps.12.17', |
|
'aps.12.18', 'aps.13.00'] |
|
for collection in ['aps', 'complex']: |
|
self.run_collection(collection, zeros.newton, 'halley', |
|
smoothness=2, known_fail=known_fail) |
|
|
|
def test_newton(self): |
|
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]: |
|
x = zeros.newton(f, 3, tol=1e-6) |
|
assert_allclose(f(x), 0, atol=1e-6) |
|
x = zeros.newton(f, 3, x1=5, tol=1e-6) |
|
assert_allclose(f(x), 0, atol=1e-6) |
|
x = zeros.newton(f, 3, fprime=f_1, tol=1e-6) |
|
assert_allclose(f(x), 0, atol=1e-6) |
|
x = zeros.newton(f, 3, fprime=f_1, fprime2=f_2, tol=1e-6) |
|
assert_allclose(f(x), 0, atol=1e-6) |
|
|
|
def test_newton_by_name(self): |
|
r"""Invoke newton through root_scalar()""" |
|
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]: |
|
r = root_scalar(f, method='newton', x0=3, fprime=f_1, xtol=1e-6) |
|
assert_allclose(f(r.root), 0, atol=1e-6) |
|
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]: |
|
r = root_scalar(f, method='newton', x0=3, xtol=1e-6) |
|
assert_allclose(f(r.root), 0, atol=1e-6) |
|
|
|
def test_secant_by_name(self): |
|
r"""Invoke secant through root_scalar()""" |
|
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]: |
|
r = root_scalar(f, method='secant', x0=3, x1=2, xtol=1e-6) |
|
assert_allclose(f(r.root), 0, atol=1e-6) |
|
r = root_scalar(f, method='secant', x0=3, x1=5, xtol=1e-6) |
|
assert_allclose(f(r.root), 0, atol=1e-6) |
|
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]: |
|
r = root_scalar(f, method='secant', x0=3, xtol=1e-6) |
|
assert_allclose(f(r.root), 0, atol=1e-6) |
|
|
|
def test_halley_by_name(self): |
|
r"""Invoke halley through root_scalar()""" |
|
for f, f_1, f_2 in [(f1, f1_1, f1_2), (f2, f2_1, f2_2)]: |
|
r = root_scalar(f, method='halley', x0=3, |
|
fprime=f_1, fprime2=f_2, xtol=1e-6) |
|
assert_allclose(f(r.root), 0, atol=1e-6) |
|
|
|
def test_root_scalar_fail(self): |
|
message = 'fprime2 must be specified for halley' |
|
with pytest.raises(ValueError, match=message): |
|
root_scalar(f1, method='halley', fprime=f1_1, x0=3, xtol=1e-6) |
|
message = 'fprime must be specified for halley' |
|
with pytest.raises(ValueError, match=message): |
|
root_scalar(f1, method='halley', fprime2=f1_2, x0=3, xtol=1e-6) |
|
|
|
def test_array_newton(self): |
|
"""test newton with array""" |
|
|
|
def f1(x, *a): |
|
b = a[0] + x * a[3] |
|
return a[1] - a[2] * (np.exp(b / a[5]) - 1.0) - b / a[4] - x |
|
|
|
def f1_1(x, *a): |
|
b = a[3] / a[5] |
|
return -a[2] * np.exp(a[0] / a[5] + x * b) * b - a[3] / a[4] - 1 |
|
|
|
def f1_2(x, *a): |
|
b = a[3] / a[5] |
|
return -a[2] * np.exp(a[0] / a[5] + x * b) * b**2 |
|
|
|
a0 = np.array([ |
|
5.32725221, 5.48673747, 5.49539973, |
|
5.36387202, 4.80237316, 1.43764452, |
|
5.23063958, 5.46094772, 5.50512718, |
|
5.42046290 |
|
]) |
|
a1 = (np.sin(range(10)) + 1.0) * 7.0 |
|
args = (a0, a1, 1e-09, 0.004, 10, 0.27456) |
|
x0 = [7.0] * 10 |
|
x = zeros.newton(f1, x0, f1_1, args) |
|
x_expected = ( |
|
6.17264965, 11.7702805, 12.2219954, |
|
7.11017681, 1.18151293, 0.143707955, |
|
4.31928228, 10.5419107, 12.7552490, |
|
8.91225749 |
|
) |
|
assert_allclose(x, x_expected) |
|
|
|
x = zeros.newton(f1, x0, f1_1, args, fprime2=f1_2) |
|
assert_allclose(x, x_expected) |
|
|
|
x = zeros.newton(f1, x0, args=args) |
|
assert_allclose(x, x_expected) |
|
|
|
def test_array_newton_complex(self): |
|
def f(x): |
|
return x + 1+1j |
|
|
|
def fprime(x): |
|
return 1.0 |
|
|
|
t = np.full(4, 1j) |
|
x = zeros.newton(f, t, fprime=fprime) |
|
assert_allclose(f(x), 0.) |
|
|
|
|
|
t = np.ones(4) |
|
x = zeros.newton(f, t, fprime=fprime) |
|
assert_allclose(f(x), 0.) |
|
|
|
x = zeros.newton(f, t) |
|
assert_allclose(f(x), 0.) |
|
|
|
def test_array_secant_active_zero_der(self): |
|
"""test secant doesn't continue to iterate zero derivatives""" |
|
x = zeros.newton(lambda x, *a: x*x - a[0], x0=[4.123, 5], |
|
args=[np.array([17, 25])]) |
|
assert_allclose(x, (4.123105625617661, 5.0)) |
|
|
|
def test_array_newton_integers(self): |
|
|
|
x = zeros.newton(lambda y, z: z - y ** 2, [4.0] * 2, |
|
args=([15.0, 17.0],)) |
|
assert_allclose(x, (3.872983346207417, 4.123105625617661)) |
|
|
|
x = zeros.newton(lambda y, z: z - y ** 2, [4] * 2, args=([15, 17],)) |
|
assert_allclose(x, (3.872983346207417, 4.123105625617661)) |
|
|
|
@pytest.mark.thread_unsafe |
|
def test_array_newton_zero_der_failures(self): |
|
|
|
assert_warns(RuntimeWarning, zeros.newton, |
|
lambda y: y**2 - 2, [0., 0.], lambda y: 2 * y) |
|
|
|
with pytest.warns(RuntimeWarning): |
|
results = zeros.newton(lambda y: y**2 - 2, [0., 0.], |
|
lambda y: 2*y, full_output=True) |
|
assert_allclose(results.root, 0) |
|
assert results.zero_der.all() |
|
assert not results.converged.any() |
|
|
|
def test_newton_combined(self): |
|
def f1(x): |
|
return x ** 2 - 2 * x - 1 |
|
def f1_1(x): |
|
return 2 * x - 2 |
|
def f1_2(x): |
|
return 2.0 + 0 * x |
|
|
|
def f1_and_p_and_pp(x): |
|
return x**2 - 2*x-1, 2*x-2, 2.0 |
|
|
|
sol0 = root_scalar(f1, method='newton', x0=3, fprime=f1_1) |
|
sol = root_scalar(f1_and_p_and_pp, method='newton', x0=3, fprime=True) |
|
assert_allclose(sol0.root, sol.root, atol=1e-8) |
|
assert_equal(2*sol.function_calls, sol0.function_calls) |
|
|
|
sol0 = root_scalar(f1, method='halley', x0=3, fprime=f1_1, fprime2=f1_2) |
|
sol = root_scalar(f1_and_p_and_pp, method='halley', x0=3, fprime2=True) |
|
assert_allclose(sol0.root, sol.root, atol=1e-8) |
|
assert_equal(3*sol.function_calls, sol0.function_calls) |
|
|
|
def test_newton_full_output(self, capsys): |
|
|
|
|
|
|
|
|
|
x0 = 3 |
|
expected_counts = [(6, 7), (5, 10), (3, 9)] |
|
|
|
for derivs in range(3): |
|
kwargs = {'tol': 1e-6, 'full_output': True, } |
|
for k, v in [['fprime', f1_1], ['fprime2', f1_2]][:derivs]: |
|
kwargs[k] = v |
|
|
|
x, r = zeros.newton(f1, x0, disp=False, **kwargs) |
|
assert_(r.converged) |
|
assert_equal(x, r.root) |
|
assert_equal((r.iterations, r.function_calls), expected_counts[derivs]) |
|
if derivs == 0: |
|
assert r.function_calls <= r.iterations + 1 |
|
else: |
|
assert_equal(r.function_calls, (derivs + 1) * r.iterations) |
|
|
|
|
|
iters = r.iterations - 1 |
|
x, r = zeros.newton(f1, x0, maxiter=iters, disp=False, **kwargs) |
|
assert_(not r.converged) |
|
assert_equal(x, r.root) |
|
assert_equal(r.iterations, iters) |
|
|
|
if derivs == 1: |
|
|
|
|
|
msg = 'Failed to converge after %d iterations, value is .*' % (iters) |
|
with pytest.raises(RuntimeError, match=msg): |
|
x, r = zeros.newton(f1, x0, maxiter=iters, disp=True, **kwargs) |
|
|
|
@pytest.mark.thread_unsafe |
|
def test_deriv_zero_warning(self): |
|
def func(x): |
|
return x ** 2 - 2.0 |
|
def dfunc(x): |
|
return 2 * x |
|
assert_warns(RuntimeWarning, zeros.newton, func, 0.0, dfunc, disp=False) |
|
with pytest.raises(RuntimeError, match='Derivative was zero'): |
|
zeros.newton(func, 0.0, dfunc) |
|
|
|
def test_newton_does_not_modify_x0(self): |
|
|
|
x0 = np.array([0.1, 3]) |
|
x0_copy = x0.copy() |
|
newton(np.sin, x0, np.cos) |
|
assert_array_equal(x0, x0_copy) |
|
|
|
def test_gh17570_defaults(self): |
|
|
|
|
|
|
|
|
|
|
|
def f(x): |
|
assert np.isscalar(x) |
|
return f1(x) |
|
|
|
res_newton_default = root_scalar(f, method='newton', x0=3, xtol=1e-6) |
|
res_secant_default = root_scalar(f, method='secant', x0=3, x1=2, |
|
xtol=1e-6) |
|
|
|
res_secant = newton(f, x0=3, x1=2, tol=1e-6, full_output=True)[1] |
|
|
|
|
|
assert_allclose(f(res_newton_default.root), 0, atol=1e-6) |
|
assert res_newton_default.root.shape == tuple() |
|
assert_allclose(f(res_secant_default.root), 0, atol=1e-6) |
|
assert res_secant_default.root.shape == tuple() |
|
assert_allclose(f(res_secant.root), 0, atol=1e-6) |
|
assert res_secant.root.shape == tuple() |
|
|
|
|
|
assert (res_secant_default.root |
|
== res_secant.root |
|
!= res_newton_default.iterations) |
|
assert (res_secant_default.iterations |
|
== res_secant_default.function_calls - 1 |
|
== res_secant.iterations |
|
!= res_newton_default.iterations |
|
== res_newton_default.function_calls/2) |
|
|
|
@pytest.mark.parametrize('kwargs', [dict(), {'method': 'newton'}]) |
|
def test_args_gh19090(self, kwargs): |
|
def f(x, a, b): |
|
assert a == 3 |
|
assert b == 1 |
|
return (x ** a - b) |
|
|
|
res = optimize.root_scalar(f, x0=3, args=(3, 1), **kwargs) |
|
assert res.converged |
|
assert_allclose(res.root, 1) |
|
|
|
@pytest.mark.parametrize('method', ['secant', 'newton']) |
|
def test_int_x0_gh19280(self, method): |
|
|
|
|
|
|
|
def f(x): |
|
|
|
return x**-2 - 2 |
|
|
|
res = optimize.root_scalar(f, x0=1, method=method) |
|
assert res.converged |
|
assert_allclose(abs(res.root), 2**-0.5) |
|
assert res.root.dtype == np.dtype(np.float64) |
|
|
|
|
|
def test_gh_5555(): |
|
root = 0.1 |
|
|
|
def f(x): |
|
return x - root |
|
|
|
methods = [zeros.bisect, zeros.ridder] |
|
xtol = rtol = TOL |
|
for method in methods: |
|
res = method(f, -1e8, 1e7, xtol=xtol, rtol=rtol) |
|
assert_allclose(root, res, atol=xtol, rtol=rtol, |
|
err_msg=f'method {method.__name__}') |
|
|
|
|
|
def test_gh_5557(): |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def f(x): |
|
if x < 0.5: |
|
return -0.1 |
|
else: |
|
return x - 0.6 |
|
|
|
atol = 0.51 |
|
rtol = 4 * _FLOAT_EPS |
|
methods = [zeros.brentq, zeros.brenth] |
|
for method in methods: |
|
res = method(f, 0, 1, xtol=atol, rtol=rtol) |
|
assert_allclose(0.6, res, atol=atol, rtol=rtol) |
|
|
|
|
|
def test_brent_underflow_in_root_bracketing(): |
|
|
|
|
|
|
|
|
|
underflow_scenario = (-450.0, -350.0, -400.0) |
|
overflow_scenario = (350.0, 450.0, 400.0) |
|
|
|
for a, b, root in [underflow_scenario, overflow_scenario]: |
|
c = np.exp(root) |
|
for method in [zeros.brenth, zeros.brentq]: |
|
res = method(lambda x: np.exp(x)-c, a, b) |
|
assert_allclose(root, res) |
|
|
|
|
|
class TestRootResults: |
|
r = zeros.RootResults(root=1.0, iterations=44, function_calls=46, flag=0, |
|
method="newton") |
|
|
|
def test_repr(self): |
|
expected_repr = (" converged: True\n flag: converged" |
|
"\n function_calls: 46\n iterations: 44\n" |
|
" root: 1.0\n method: newton") |
|
assert_equal(repr(self.r), expected_repr) |
|
|
|
def test_type(self): |
|
assert isinstance(self.r, OptimizeResult) |
|
|
|
|
|
def test_complex_halley(): |
|
"""Test Halley's works with complex roots""" |
|
def f(x, *a): |
|
return a[0] * x**2 + a[1] * x + a[2] |
|
|
|
def f_1(x, *a): |
|
return 2 * a[0] * x + a[1] |
|
|
|
def f_2(x, *a): |
|
retval = 2 * a[0] |
|
try: |
|
size = len(x) |
|
except TypeError: |
|
return retval |
|
else: |
|
return [retval] * size |
|
|
|
z = complex(1.0, 2.0) |
|
coeffs = (2.0, 3.0, 4.0) |
|
y = zeros.newton(f, z, args=coeffs, fprime=f_1, fprime2=f_2, tol=1e-6) |
|
|
|
assert_allclose(f(y, *coeffs), 0, atol=1e-6) |
|
z = [z] * 10 |
|
coeffs = (2.0, 3.0, 4.0) |
|
y = zeros.newton(f, z, args=coeffs, fprime=f_1, fprime2=f_2, tol=1e-6) |
|
assert_allclose(f(y, *coeffs), 0, atol=1e-6) |
|
|
|
|
|
@pytest.mark.thread_unsafe |
|
def test_zero_der_nz_dp(capsys): |
|
"""Test secant method with a non-zero dp, but an infinite newton step""" |
|
|
|
|
|
|
|
|
|
|
|
dx = np.finfo(float).eps ** 0.33 |
|
|
|
|
|
p0 = (200.0 - dx) / (2.0 + dx) |
|
with suppress_warnings() as sup: |
|
sup.filter(RuntimeWarning, "RMS of") |
|
x = zeros.newton(lambda y: (y - 100.0)**2, x0=[p0] * 10) |
|
assert_allclose(x, [100] * 10) |
|
|
|
p0 = (2.0 - 1e-4) / (2.0 + 1e-4) |
|
with suppress_warnings() as sup: |
|
sup.filter(RuntimeWarning, "Tolerance of") |
|
x = zeros.newton(lambda y: (y - 1.0) ** 2, x0=p0, disp=False) |
|
assert_allclose(x, 1) |
|
with pytest.raises(RuntimeError, match='Tolerance of'): |
|
x = zeros.newton(lambda y: (y - 1.0) ** 2, x0=p0, disp=True) |
|
p0 = (-2.0 + 1e-4) / (2.0 + 1e-4) |
|
with suppress_warnings() as sup: |
|
sup.filter(RuntimeWarning, "Tolerance of") |
|
x = zeros.newton(lambda y: (y + 1.0) ** 2, x0=p0, disp=False) |
|
assert_allclose(x, -1) |
|
with pytest.raises(RuntimeError, match='Tolerance of'): |
|
x = zeros.newton(lambda y: (y + 1.0) ** 2, x0=p0, disp=True) |
|
|
|
|
|
@pytest.mark.thread_unsafe |
|
def test_array_newton_failures(): |
|
"""Test that array newton fails as expected""" |
|
|
|
|
|
|
|
diameter = 0.10 |
|
|
|
roughness = 0.00015 |
|
rho = 988.1 |
|
mu = 5.4790e-04 |
|
u = 2.488 |
|
reynolds_number = rho * u * diameter / mu |
|
|
|
def colebrook_eqn(darcy_friction, re, dia): |
|
return (1 / np.sqrt(darcy_friction) + |
|
2 * np.log10(roughness / 3.7 / dia + |
|
2.51 / re / np.sqrt(darcy_friction))) |
|
|
|
|
|
with pytest.warns(RuntimeWarning): |
|
result = zeros.newton( |
|
colebrook_eqn, x0=[0.01, 0.2, 0.02223, 0.3], maxiter=2, |
|
args=[reynolds_number, diameter], full_output=True |
|
) |
|
assert not result.converged.all() |
|
|
|
with pytest.raises(RuntimeError): |
|
result = zeros.newton( |
|
colebrook_eqn, x0=[0.01] * 2, maxiter=2, |
|
args=[reynolds_number, diameter], full_output=True |
|
) |
|
|
|
|
|
|
|
def test_gh8904_zeroder_at_root_fails(): |
|
"""Test that Newton or Halley don't warn if zero derivative at root""" |
|
|
|
|
|
def f_zeroder_root(x): |
|
return x**3 - x**2 |
|
|
|
|
|
r = zeros.newton(f_zeroder_root, x0=0) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
|
|
r = zeros.newton(f_zeroder_root, x0=[0]*10) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
|
|
|
|
def fder(x): |
|
return 3 * x**2 - 2 * x |
|
|
|
|
|
def fder2(x): |
|
return 6*x - 2 |
|
|
|
|
|
r = zeros.newton(f_zeroder_root, x0=0, fprime=fder) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
r = zeros.newton(f_zeroder_root, x0=0, fprime=fder, |
|
fprime2=fder2) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
|
|
r = zeros.newton(f_zeroder_root, x0=[0]*10, fprime=fder) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
r = zeros.newton(f_zeroder_root, x0=[0]*10, fprime=fder, |
|
fprime2=fder2) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
|
|
|
|
|
|
|
|
|
|
|
|
r = zeros.newton(f_zeroder_root, x0=0.5, fprime=fder) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
|
|
r = zeros.newton(f_zeroder_root, x0=[0.5]*10, fprime=fder) |
|
assert_allclose(r, 0, atol=zeros._xtol, rtol=zeros._rtol) |
|
|
|
|
|
|
|
def test_gh_8881(): |
|
r"""Test that Halley's method realizes that the 2nd order adjustment |
|
is too big and drops off to the 1st order adjustment.""" |
|
n = 9 |
|
|
|
def f(x): |
|
return power(x, 1.0/n) - power(n, 1.0/n) |
|
|
|
def fp(x): |
|
return power(x, (1.0-n)/n)/n |
|
|
|
def fpp(x): |
|
return power(x, (1.0-2*n)/n) * (1.0/n) * (1.0-n)/n |
|
|
|
x0 = 0.1 |
|
|
|
|
|
|
|
rt, r = newton(f, x0, fprime=fp, full_output=True) |
|
assert r.converged |
|
|
|
|
|
rt, r = newton(f, x0, fprime=fp, fprime2=fpp, full_output=True) |
|
assert r.converged |
|
|
|
|
|
def test_gh_9608_preserve_array_shape(): |
|
""" |
|
Test that shape is preserved for array inputs even if fprime or fprime2 is |
|
scalar |
|
""" |
|
def f(x): |
|
return x**2 |
|
|
|
def fp(x): |
|
return 2 * x |
|
|
|
def fpp(x): |
|
return 2 |
|
|
|
x0 = np.array([-2], dtype=np.float32) |
|
rt, r = newton(f, x0, fprime=fp, fprime2=fpp, full_output=True) |
|
assert r.converged |
|
|
|
x0_array = np.array([-2, -3], dtype=np.float32) |
|
|
|
with pytest.raises(IndexError): |
|
result = zeros.newton( |
|
f, x0_array, fprime=fp, fprime2=fpp, full_output=True |
|
) |
|
|
|
def fpp_array(x): |
|
return np.full(np.shape(x), 2, dtype=np.float32) |
|
|
|
result = zeros.newton( |
|
f, x0_array, fprime=fp, fprime2=fpp_array, full_output=True |
|
) |
|
assert result.converged.all() |
|
|
|
|
|
@pytest.mark.parametrize( |
|
"maximum_iterations,flag_expected", |
|
[(10, zeros.CONVERR), (100, zeros.CONVERGED)]) |
|
def test_gh9254_flag_if_maxiter_exceeded(maximum_iterations, flag_expected): |
|
""" |
|
Test that if the maximum iterations is exceeded that the flag is not |
|
converged. |
|
""" |
|
result = zeros.brentq( |
|
lambda x: ((1.2*x - 2.3)*x + 3.4)*x - 4.5, |
|
-30, 30, (), 1e-6, 1e-6, maximum_iterations, |
|
full_output=True, disp=False) |
|
assert result[1].flag == flag_expected |
|
if flag_expected == zeros.CONVERR: |
|
|
|
assert result[1].iterations == maximum_iterations |
|
elif flag_expected == zeros.CONVERGED: |
|
|
|
assert result[1].iterations < maximum_iterations |
|
|
|
|
|
@pytest.mark.thread_unsafe |
|
def test_gh9551_raise_error_if_disp_true(): |
|
"""Test that if disp is true then zero derivative raises RuntimeError""" |
|
|
|
def f(x): |
|
return x*x + 1 |
|
|
|
def f_p(x): |
|
return 2*x |
|
|
|
assert_warns(RuntimeWarning, zeros.newton, f, 1.0, f_p, disp=False) |
|
with pytest.raises( |
|
RuntimeError, |
|
match=r'^Derivative was zero\. Failed to converge after \d+ iterations, ' |
|
r'value is [+-]?\d*\.\d+\.$'): |
|
zeros.newton(f, 1.0, f_p) |
|
root = zeros.newton(f, complex(10.0, 10.0), f_p) |
|
assert_allclose(root, complex(0.0, 1.0)) |
|
|
|
|
|
@pytest.mark.parametrize('solver_name', |
|
['brentq', 'brenth', 'bisect', 'ridder', 'toms748']) |
|
def test_gh3089_8394(solver_name): |
|
|
|
|
|
def f(x): |
|
return np.nan |
|
|
|
solver = getattr(zeros, solver_name) |
|
with pytest.raises(ValueError, match="The function value at x..."): |
|
solver(f, 0, 1) |
|
|
|
|
|
@pytest.mark.parametrize('method', |
|
['brentq', 'brenth', 'bisect', 'ridder', 'toms748']) |
|
def test_gh18171(method): |
|
|
|
|
|
|
|
def f(x): |
|
f._count += 1 |
|
return np.nan |
|
f._count = 0 |
|
|
|
res = root_scalar(f, bracket=(0, 1), method=method) |
|
assert res.converged is False |
|
assert res.flag.startswith("The function value at x") |
|
assert res.function_calls == f._count |
|
assert str(res.root) in res.flag |
|
|
|
|
|
@pytest.mark.parametrize('solver_name', |
|
['brentq', 'brenth', 'bisect', 'ridder', 'toms748']) |
|
@pytest.mark.parametrize('rs_interface', [True, False]) |
|
def test_function_calls(solver_name, rs_interface): |
|
|
|
|
|
solver = ((lambda f, a, b, **kwargs: root_scalar(f, bracket=(a, b))) |
|
if rs_interface else getattr(zeros, solver_name)) |
|
|
|
def f(x): |
|
f.calls += 1 |
|
return x**2 - 1 |
|
f.calls = 0 |
|
|
|
res = solver(f, 0, 10, full_output=True) |
|
|
|
if rs_interface: |
|
assert res.function_calls == f.calls |
|
else: |
|
assert res[1].function_calls == f.calls |
|
|
|
|
|
@pytest.mark.thread_unsafe |
|
def test_gh_14486_converged_false(): |
|
"""Test that zero slope with secant method results in a converged=False""" |
|
def lhs(x): |
|
return x * np.exp(-x*x) - 0.07 |
|
|
|
with pytest.warns(RuntimeWarning, match='Tolerance of'): |
|
res = root_scalar(lhs, method='secant', x0=-0.15, x1=1.0) |
|
assert not res.converged |
|
assert res.flag == 'convergence error' |
|
|
|
with pytest.warns(RuntimeWarning, match='Tolerance of'): |
|
res = newton(lhs, x0=-0.15, x1=1.0, disp=False, full_output=True)[1] |
|
assert not res.converged |
|
assert res.flag == 'convergence error' |
|
|
|
|
|
@pytest.mark.parametrize('solver_name', |
|
['brentq', 'brenth', 'bisect', 'ridder', 'toms748']) |
|
@pytest.mark.parametrize('rs_interface', [True, False]) |
|
def test_gh5584(solver_name, rs_interface): |
|
|
|
|
|
solver = ((lambda f, a, b, **kwargs: root_scalar(f, bracket=(a, b))) |
|
if rs_interface else getattr(zeros, solver_name)) |
|
|
|
def f(x): |
|
return 1e-200*x |
|
|
|
|
|
with pytest.raises(ValueError, match='...must have different signs'): |
|
solver(f, -0.5, -0.4, full_output=True) |
|
|
|
|
|
res = solver(f, -0.5, 0.4, full_output=True) |
|
res = res if rs_interface else res[1] |
|
assert res.converged |
|
assert_allclose(res.root, 0, atol=1e-8) |
|
|
|
|
|
res = solver(f, -0.5, float('-0.0'), full_output=True) |
|
res = res if rs_interface else res[1] |
|
assert res.converged |
|
assert_allclose(res.root, 0, atol=1e-8) |
|
|
|
|
|
def test_gh13407(): |
|
|
|
|
|
|
|
|
|
|
|
def f(x): |
|
return x**3 - 2*x - 5 |
|
|
|
xtol = 1e-300 |
|
eps = np.finfo(float).eps |
|
x1 = zeros.toms748(f, 1e-10, 1e10, xtol=xtol, rtol=1*eps) |
|
f1 = f(x1) |
|
x4 = zeros.toms748(f, 1e-10, 1e10, xtol=xtol, rtol=4*eps) |
|
f4 = f(x4) |
|
assert f1 < f4 |
|
|
|
|
|
message = fr"rtol too small \({eps/2:g} < {eps:g}\)" |
|
with pytest.raises(ValueError, match=message): |
|
zeros.toms748(f, 1e-10, 1e10, xtol=xtol, rtol=eps/2) |
|
|
|
|
|
def test_newton_complex_gh10103(): |
|
|
|
|
|
|
|
def f(z): |
|
return z - 1 |
|
res = newton(f, 1+1j) |
|
assert_allclose(res, 1, atol=1e-12) |
|
|
|
res = root_scalar(f, x0=1+1j, x1=2+1.5j, method='secant') |
|
assert_allclose(res.root, 1, atol=1e-12) |
|
|
|
|
|
@pytest.mark.parametrize('method', all_methods) |
|
def test_maxiter_int_check_gh10236(method): |
|
|
|
|
|
message = "'float' object cannot be interpreted as an integer" |
|
with pytest.raises(TypeError, match=message): |
|
method(f1, 0.0, 1.0, maxiter=72.45) |
|
|