_trustregion_dogleg.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. """Dog-leg trust-region optimization."""
  2. import numpy as np
  3. import scipy.linalg
  4. from ._trustregion import (_minimize_trust_region, BaseQuadraticSubproblem)
  5. __all__ = []
  6. def _minimize_dogleg(fun, x0, args=(), jac=None, hess=None,
  7. **trust_region_options):
  8. """
  9. Minimization of scalar function of one or more variables using
  10. the dog-leg trust-region algorithm.
  11. Options
  12. -------
  13. initial_trust_radius : float
  14. Initial trust-region radius.
  15. max_trust_radius : float
  16. Maximum value of the trust-region radius. No steps that are longer
  17. than this value will be proposed.
  18. eta : float
  19. Trust region related acceptance stringency for proposed steps.
  20. gtol : float
  21. Gradient norm must be less than `gtol` before successful
  22. termination.
  23. """
  24. if jac is None:
  25. raise ValueError('Jacobian is required for dogleg minimization')
  26. if not callable(hess):
  27. raise ValueError('Hessian is required for dogleg minimization')
  28. return _minimize_trust_region(fun, x0, args=args, jac=jac, hess=hess,
  29. subproblem=DoglegSubproblem,
  30. **trust_region_options)
  31. class DoglegSubproblem(BaseQuadraticSubproblem):
  32. """Quadratic subproblem solved by the dogleg method"""
  33. def cauchy_point(self):
  34. """
  35. The Cauchy point is minimal along the direction of steepest descent.
  36. """
  37. if self._cauchy_point is None:
  38. g = self.jac
  39. Bg = self.hessp(g)
  40. self._cauchy_point = -(np.dot(g, g) / np.dot(g, Bg)) * g
  41. return self._cauchy_point
  42. def newton_point(self):
  43. """
  44. The Newton point is a global minimum of the approximate function.
  45. """
  46. if self._newton_point is None:
  47. g = self.jac
  48. B = self.hess
  49. cho_info = scipy.linalg.cho_factor(B)
  50. self._newton_point = -scipy.linalg.cho_solve(cho_info, g)
  51. return self._newton_point
  52. def solve(self, trust_radius):
  53. """
  54. Minimize a function using the dog-leg trust-region algorithm.
  55. This algorithm requires function values and first and second derivatives.
  56. It also performs a costly Hessian decomposition for most iterations,
  57. and the Hessian is required to be positive definite.
  58. Parameters
  59. ----------
  60. trust_radius : float
  61. We are allowed to wander only this far away from the origin.
  62. Returns
  63. -------
  64. p : ndarray
  65. The proposed step.
  66. hits_boundary : bool
  67. True if the proposed step is on the boundary of the trust region.
  68. Notes
  69. -----
  70. The Hessian is required to be positive definite.
  71. References
  72. ----------
  73. .. [1] Jorge Nocedal and Stephen Wright,
  74. Numerical Optimization, second edition,
  75. Springer-Verlag, 2006, page 73.
  76. """
  77. # Compute the Newton point.
  78. # This is the optimum for the quadratic model function.
  79. # If it is inside the trust radius then return this point.
  80. p_best = self.newton_point()
  81. if scipy.linalg.norm(p_best) < trust_radius:
  82. hits_boundary = False
  83. return p_best, hits_boundary
  84. # Compute the Cauchy point.
  85. # This is the predicted optimum along the direction of steepest descent.
  86. p_u = self.cauchy_point()
  87. # If the Cauchy point is outside the trust region,
  88. # then return the point where the path intersects the boundary.
  89. p_u_norm = scipy.linalg.norm(p_u)
  90. if p_u_norm >= trust_radius:
  91. p_boundary = p_u * (trust_radius / p_u_norm)
  92. hits_boundary = True
  93. return p_boundary, hits_boundary
  94. # Compute the intersection of the trust region boundary
  95. # and the line segment connecting the Cauchy and Newton points.
  96. # This requires solving a quadratic equation.
  97. # ||p_u + t*(p_best - p_u)||**2 == trust_radius**2
  98. # Solve this for positive time t using the quadratic formula.
  99. _, tb = self.get_boundaries_intersections(p_u, p_best - p_u,
  100. trust_radius)
  101. p_boundary = p_u + tb * (p_best - p_u)
  102. hits_boundary = True
  103. return p_boundary, hits_boundary