_trustregion_dogleg.py 4.3 KB

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