test__root.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. """
  2. Unit tests for optimization routines from _root.py.
  3. """
  4. from numpy.testing import assert_, assert_equal
  5. import pytest
  6. from pytest import raises as assert_raises, warns as assert_warns
  7. import numpy as np
  8. from scipy.optimize import root
  9. class TestRoot:
  10. def test_tol_parameter(self):
  11. # Check that the minimize() tol= argument does something
  12. def func(z):
  13. x, y = z
  14. return np.array([x**3 - 1, y**3 - 1])
  15. def dfunc(z):
  16. x, y = z
  17. return np.array([[3*x**2, 0], [0, 3*y**2]])
  18. for method in ['hybr', 'lm', 'broyden1', 'broyden2', 'anderson',
  19. 'diagbroyden', 'krylov']:
  20. if method in ('linearmixing', 'excitingmixing'):
  21. # doesn't converge
  22. continue
  23. if method in ('hybr', 'lm'):
  24. jac = dfunc
  25. else:
  26. jac = None
  27. sol1 = root(func, [1.1,1.1], jac=jac, tol=1e-4, method=method)
  28. sol2 = root(func, [1.1,1.1], jac=jac, tol=0.5, method=method)
  29. msg = f"{method}: {func(sol1.x)} vs. {func(sol2.x)}"
  30. assert_(sol1.success, msg)
  31. assert_(sol2.success, msg)
  32. assert_(abs(func(sol1.x)).max() < abs(func(sol2.x)).max(),
  33. msg)
  34. def test_tol_norm(self):
  35. def norm(x):
  36. return abs(x[0])
  37. for method in ['excitingmixing',
  38. 'diagbroyden',
  39. 'linearmixing',
  40. 'anderson',
  41. 'broyden1',
  42. 'broyden2',
  43. 'krylov']:
  44. root(np.zeros_like, np.zeros(2), method=method,
  45. options={"tol_norm": norm})
  46. def test_minimize_scalar_coerce_args_param(self):
  47. # GitHub issue #3503
  48. def func(z, f=1):
  49. x, y = z
  50. return np.array([x**3 - 1, y**3 - f])
  51. root(func, [1.1, 1.1], args=1.5)
  52. def test_f_size(self):
  53. # gh8320
  54. # check that decreasing the size of the returned array raises an error
  55. # and doesn't segfault
  56. class fun:
  57. def __init__(self):
  58. self.count = 0
  59. def __call__(self, x):
  60. self.count += 1
  61. if not (self.count % 5):
  62. ret = x[0] + 0.5 * (x[0] - x[1]) ** 3 - 1.0
  63. else:
  64. ret = ([x[0] + 0.5 * (x[0] - x[1]) ** 3 - 1.0,
  65. 0.5 * (x[1] - x[0]) ** 3 + x[1]])
  66. return ret
  67. F = fun()
  68. with assert_raises(ValueError):
  69. root(F, [0.1, 0.0], method='lm')
  70. def test_gh_10370(self):
  71. # gh-10370 reported that passing both `args` and `jac` to `root` with
  72. # `method='krylov'` caused a failure. Ensure that this is fixed whether
  73. # the gradient is passed via `jac` or as a second output of `fun`.
  74. def fun(x, ignored):
  75. return [3*x[0] - 0.25*x[1]**2 + 10, 0.1*x[0]**2 + 5*x[1] - 2]
  76. def grad(x, ignored):
  77. return [[3, 0.5 * x[1]], [0.2 * x[0], 5]]
  78. def fun_grad(x, ignored):
  79. return fun(x, ignored), grad(x, ignored)
  80. x0 = np.zeros(2)
  81. ref = root(fun, x0, args=(1,), method='krylov')
  82. message = 'Method krylov does not use the jacobian'
  83. with assert_warns(RuntimeWarning, match=message):
  84. res1 = root(fun, x0, args=(1,), method='krylov', jac=grad)
  85. with assert_warns(RuntimeWarning, match=message):
  86. res2 = root(fun_grad, x0, args=(1,), method='krylov', jac=True)
  87. assert_equal(res1.x, ref.x)
  88. assert_equal(res2.x, ref.x)
  89. assert res1.success is res2.success is ref.success is True
  90. @pytest.mark.parametrize("method", ["hybr", "lm", "broyden1", "broyden2",
  91. "anderson", "linearmixing",
  92. "diagbroyden", "excitingmixing",
  93. "krylov", "df-sane"])
  94. def test_method_in_result(self, method):
  95. def func(x):
  96. return x - 1
  97. res = root(func, x0=[1], method=method)
  98. assert res.method == method